Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: Add stricter type checking #2538

Merged
merged 2 commits into from
Mar 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .npmignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
types/tests
types/tests.ts
types/eslint.config.mjs
6 changes: 5 additions & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ module.exports = tseslint.config({
files: ['**/*.js', '**/*.ts'],
extends: [
eslint.configs.recommended,
...tseslint.configs.recommended,
...tseslint.configs.stylistic,
...tseslint.configs.strict,
],
plugins: {
'@typescript-eslint': tseslint.plugin,
Expand Down Expand Up @@ -34,6 +35,9 @@ module.exports = tseslint.config({
"@typescript-eslint/no-var-requires": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-require-imports": "off",
"@typescript-eslint/no-dynamic-delete": "off",
"@typescript-eslint/prefer-for-of": "off",
"@typescript-eslint/no-extraneous-class": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
Expand Down
4 changes: 0 additions & 4 deletions integration/test/ParseUserTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@ const { v4: uuidv4 } = require('uuid');
const { twitterAuthData } = require('./helper');

class CustomUser extends Parse.User {
constructor(attributes) {
super(attributes);
}

doSomething() {
return 5;
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@
"posttest:mongodb": "mongodb-runner stop --all",
"lint": "eslint --cache src/ integration/",
"lint:fix": "eslint --fix --cache src/ integration/",
"test:types": "eslint types/tests.ts -c eslint.config.test.mjs",
"test:types": "eslint types/tests.ts -c ./types/eslint.config.mjs",
"watch": "cross-env PARSE_BUILD=${PARSE_BUILD} gulp watch",
"watch:browser": "cross-env PARSE_BUILD=browser npm run watch",
"watch:node": "cross-env PARSE_BUILD=node npm run watch",
Expand Down
4 changes: 2 additions & 2 deletions src/Analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import CoreManager from './CoreManager';
* @returns {Promise} A promise that is resolved when the round-trip
* to the server completes.
*/
export function track(name: string, dimensions: { [key: string]: string }): Promise<void> {
export function track(name: string, dimensions: Record<string, string>): Promise<void> {
name = name || '';
name = name.replace(/^\s*/, '');
name = name.replace(/\s*$/, '');
Expand All @@ -58,7 +58,7 @@ export function track(name: string, dimensions: { [key: string]: string }): Prom
}

const DefaultController = {
track(name: string, dimensions: { [key: string]: string }) {
track(name: string, dimensions: Record<string, string>) {
const path = 'events/' + name;
const RESTController = CoreManager.getRESTController();
return RESTController.request('POST', path, { dimensions });
Expand Down
133 changes: 63 additions & 70 deletions src/CoreManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,25 @@ import type ParseConfig from './ParseConfig';
import type LiveQueryClient from './LiveQueryClient';
import type ParseInstallation from './ParseInstallation';

type AnalyticsController = {
track: (name: string, dimensions: { [key: string]: string }) => Promise<any>;
};
type CloudController = {
export interface AnalyticsController {
track: (name: string, dimensions: Record<string, string>) => Promise<any>;
}
export interface CloudController {
run: (name: string, data: any, options?: RequestOptions) => Promise<any>;
getJobsData: (options?: RequestOptions) => Promise<any>;
/** Returns promise which resolves with JobStatusId of the job */
startJob: (name: string, data: any, options?: RequestOptions) => Promise<string>;
};
type ConfigController = {
}
export interface ConfigController {
current: () => Promise<ParseConfig> | ParseConfig;
get: (opts?: RequestOptions) => Promise<ParseConfig>;
save: (
attrs: { [key: string]: any },
masterKeyOnlyFlags?: { [key: string]: any }
) => Promise<void>;
};
type CryptoController = {
save: (attrs: Record<string, any>, masterKeyOnlyFlags?: Record<string, any>) => Promise<void>;
}
export interface CryptoController {
encrypt: (obj: any, secretKey: string) => string;
decrypt: (encryptedText: string, secretKey: any) => string;
};
type FileController = {
}
export interface FileController {
saveFile: (name: string, source: FileSource, options?: FullOptions) => Promise<any>;
saveBase64: (
name: string,
Expand All @@ -45,34 +42,34 @@ type FileController = {
) => Promise<{ name: string; url: string }>;
download: (uri: string, options?: any) => Promise<{ base64?: string; contentType?: string }>;
deleteFile: (name: string, options?: { useMasterKey?: boolean }) => Promise<void>;
};
type InstallationController = {
}
export interface InstallationController {
currentInstallationId: () => Promise<string>;
currentInstallation: () => Promise<ParseInstallation | null>;
updateInstallationOnDisk: (installation: ParseInstallation) => Promise<void>;
};
type ObjectController = {
}
export interface ObjectController {
fetch: (
object: ParseObject | Array<ParseObject>,
object: ParseObject | ParseObject[],
forceFetch: boolean,
options?: RequestOptions
) => Promise<Array<ParseObject | undefined> | ParseObject | undefined>;
) => Promise<(ParseObject | undefined)[] | ParseObject | undefined>;
save: (
object: ParseObject | Array<ParseObject | ParseFile> | null,
object: ParseObject | (ParseObject | ParseFile)[] | null,
options?: RequestOptions
) => Promise<ParseObject | Array<ParseObject> | ParseFile | undefined>;
) => Promise<ParseObject | ParseObject[] | ParseFile | undefined>;
destroy: (
object: ParseObject | Array<ParseObject>,
object: ParseObject | ParseObject[],
options?: RequestOptions
) => Promise<ParseObject | Array<ParseObject>>;
};
type ObjectStateController = {
) => Promise<ParseObject | ParseObject[]>;
}
export interface ObjectStateController {
getState: (obj: any) => State | null;
initializeState: (obj: any, initial?: State) => State;
removeState: (obj: any) => State | null;
getServerData: (obj: any) => AttributeMap;
setServerData: (obj: any, attributes: AttributeMap) => void;
getPendingOps: (obj: any) => Array<OpsMap>;
getPendingOps: (obj: any) => OpsMap[];
setPendingOp: (obj: any, attr: string, op?: Op) => void;
pushPendingState: (obj: any) => void;
popPendingState: (obj: any) => OpsMap | undefined;
Expand All @@ -84,23 +81,19 @@ type ObjectStateController = {
enqueueTask: (obj: any, task: () => Promise<void>) => Promise<void>;
clearAllState: () => void;
duplicateState: (source: any, dest: any) => void;
};
type PushController = {
}
export interface PushController {
send: (data: PushData, options?: FullOptions) => Promise<any>;
};
type QueryController = {
}
export interface QueryController {
find(
className: string,
params: QueryJSON,
options?: RequestOptions
): Promise<{ results?: Array<ParseObject>; className?: string; count?: number }>;
aggregate(
className: string,
params: any,
options?: RequestOptions
): Promise<{ results?: Array<any> }>;
};
export type QueueObject = {
): Promise<{ results?: ParseObject[]; className?: string; count?: number }>;
aggregate(className: string, params: any, options?: RequestOptions): Promise<{ results?: any[] }>;
}
export interface QueueObject {
queueId: string;
action: string;
object: ParseObject;
Expand All @@ -109,9 +102,9 @@ export type QueueObject = {
className: string;
hash: string;
createdAt: Date;
};
export type Queue = Array<QueueObject>;
export type EventuallyQueue = {
}
export type Queue = QueueObject[];
export interface EventuallyQueue {
save: (object: ParseObject, serverOptions?: SaveOptions) => Promise<void>;
destroy: (object: ParseObject, serverOptions?: RequestOptions) => Promise<void>;
generateQueueId: (action: string, object: ParseObject) => string;
Expand All @@ -138,8 +131,8 @@ export type EventuallyQueue = {
byId(ObjectType: any, queueObject: any): Promise<void>;
byHash(ObjectType: any, queueObject: any): Promise<void>;
};
};
type RESTController = {
}
export interface RESTController {
request: (method: string, path: string, data?: any, options?: RequestOptions) => Promise<any>;
ajax: (
method: string,
Expand All @@ -149,18 +142,18 @@ type RESTController = {
options?: FullOptions
) => Promise<any>;
handleError: (err?: any) => void;
};
type SchemaController = {
}
export interface SchemaController {
purge: (className: string) => Promise<any>;
get: (className: string, options?: RequestOptions) => Promise<any>;
delete: (className: string, options?: RequestOptions) => Promise<void>;
create: (className: string, params: any, options?: RequestOptions) => Promise<any>;
update: (className: string, params: any, options?: RequestOptions) => Promise<any>;
send(className: string, method: string, params: any, options?: RequestOptions): Promise<any>;
};
type SessionController = {
}
export interface SessionController {
getSession: (options?: RequestOptions) => Promise<ParseSession>;
};
}
type StorageController =
| {
async: 0;
Expand All @@ -171,8 +164,8 @@ type StorageController =
setItemAsync?: (path: string, value: string) => Promise<void>;
removeItemAsync?: (path: string) => Promise<void>;
clear: () => void;
getAllKeys?: () => Array<string>;
getAllKeysAsync?: () => Promise<Array<string>>;
getAllKeys?: () => string[];
getAllKeysAsync?: () => Promise<string[]>;
}
| {
async: 1;
Expand All @@ -183,19 +176,19 @@ type StorageController =
setItemAsync: (path: string, value: string) => Promise<void>;
removeItemAsync: (path: string) => Promise<void>;
clear: () => void;
getAllKeys?: () => Array<string>;
getAllKeysAsync?: () => Promise<Array<string>>;
getAllKeys?: () => string[];
getAllKeysAsync?: () => Promise<string[]>;
};
type LocalDatastoreController = {
export interface LocalDatastoreController {
fromPinWithName: (name: string) => any | undefined;
pinWithName: (name: string, objects: any) => void;
unPinWithName: (name: string) => void;
getAllContents: () => any | undefined;
clear: () => void;
// Use for testing
// getRawStorage(): Promise<Object>,
};
type UserController = {
}
export interface UserController {
setCurrentUser: (user: ParseUser) => Promise<void>;
currentUser: () => ParseUser | null;
currentUserAsync: () => Promise<ParseUser | null>;
Expand All @@ -210,29 +203,29 @@ type UserController = {
updateUserOnDisk: (user: ParseUser) => Promise<ParseUser>;
upgradeToRevocableSession: (user: ParseUser, options?: RequestOptions) => Promise<void>;
linkWith: (user: ParseUser, authData: AuthData, options?: FullOptions) => Promise<ParseUser>;
removeUserFromDisk: () => Promise<ParseUser | void>;
removeUserFromDisk: () => Promise<void>;
verifyPassword: (
username: string,
password: string,
options?: RequestOptions
) => Promise<ParseUser>;
requestEmailVerification: (email: string, options?: RequestOptions) => Promise<void>;
};
type HooksController = {
}
export interface HooksController {
get: (type: string, functionName?: string, triggerName?: string) => Promise<any>;
create: (hook: HookDeclaration) => Promise<any>;
remove: (hook: HookDeleteArg) => Promise<any>;
update: (hook: HookDeclaration) => Promise<any>;
// Renamed to sendRequest since ParseHooks file & tests file uses this. (originally declared as just "send")
sendRequest?: (method: string, path: string, body?: any) => Promise<any>;
};
type LiveQueryControllerType = {
}
export interface LiveQueryControllerType {
setDefaultLiveQueryClient(liveQueryClient: LiveQueryClient): void;
getDefaultLiveQueryClient(): Promise<LiveQueryClient>;
_clearCachedDefaultClient(): void;
};
}
/** Based on https://github.com/react-native-async-storage/async-storage/blob/main/packages/default-storage-backend/src/types.ts */
type AsyncStorageType = {
export interface AsyncStorageType {
/** Fetches an item for a `key` and invokes a callback upon completion. */
getItem: (
key: string,
Expand Down Expand Up @@ -302,16 +295,16 @@ type AsyncStorageType = {
keyValuePairs: [string, string][],
callback?: (errors?: readonly (Error | null)[] | null) => void
) => Promise<void>;
};
export type WebSocketController = {
}
export interface WebSocketController {
onopen: () => void;
onmessage: (message: any) => void;
onclose: (arg?: any) => void;
onerror: (error: any) => void;
send: (data: any) => void;
close: () => void;
};
type Config = {
}
interface Config {
AnalyticsController?: AnalyticsController;
CloudController?: CloudController;
ConfigController?: ConfigController;
Expand All @@ -334,9 +327,9 @@ type Config = {
) => WebSocketController;
LiveQueryController?: LiveQueryControllerType;
AsyncStorage?: AsyncStorageType;
};
}

const config: Config & { [key: string]: any } = {
const config: Config & Record<string, any> = {
IS_NODE:
typeof process !== 'undefined' &&
!!process.versions &&
Expand Down Expand Up @@ -364,7 +357,7 @@ const config: Config & { [key: string]: any } = {
PARSE_ERRORS: [],
};

function requireMethods(name: string, methods: Array<string>, controller: any) {
function requireMethods(name: string, methods: string[], controller: any) {
methods.forEach(func => {
if (typeof controller[func] !== 'function') {
throw new Error(`${name} must implement ${func}()`);
Expand Down
6 changes: 3 additions & 3 deletions src/LocalDatastore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const LocalDatastore = {
isEnabled: false,
isSyncing: false,

fromPinWithName(name: string): Promise<Array<any>> {
fromPinWithName(name: string): Promise<any[]> {
const controller = CoreManager.getLocalDatastoreController();
return controller.fromPinWithName(name);
},
Expand Down Expand Up @@ -61,7 +61,7 @@ const LocalDatastore = {

// Pin the object and children recursively
// Saves the object and children key to Pin Name
async _handlePinAllWithName(name: string, objects: Array<ParseObject>): Promise<void> {
async _handlePinAllWithName(name: string, objects: ParseObject[]): Promise<void> {
const pinName = this.getPinName(name);
const toPinPromises = [];
const objectKeys = [];
Expand All @@ -86,7 +86,7 @@ const LocalDatastore = {

// Removes object and children keys from pin name
// Keeps the object and children pinned
async _handleUnPinAllWithName(name: string, objects: Array<ParseObject>) {
async _handleUnPinAllWithName(name: string, objects: ParseObject[]) {
const localDatastore = await this._getAllContents();
const pinName = this.getPinName(name);
const promises = [];
Expand Down
2 changes: 1 addition & 1 deletion src/LocalDatastoreController.default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { isLocalDatastoreKey } from './LocalDatastoreUtils';
import Storage from './Storage';

const LocalDatastoreController = {
async fromPinWithName(name: string): Promise<Array<any>> {
async fromPinWithName(name: string): Promise<any[]> {
const values = await Storage.getItemAsync(name);
if (!values) {
return [];
Expand Down
2 changes: 1 addition & 1 deletion src/LocalDatastoreController.react-native.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { isLocalDatastoreKey } from './LocalDatastoreUtils';
import RNStorage from './StorageController.react-native';

const LocalDatastoreController = {
async fromPinWithName(name: string): Promise<Array<any>> {
async fromPinWithName(name: string): Promise<any[]> {
const values = await RNStorage.getItemAsync(name);
if (!values) {
return [];
Expand Down
Loading