Generics and typed attributes for Parse.Object and sub classes (#40429)

* parse: generics and typed attributes for Parse.Object and sub classes

* parse: Fix dt-header tslint rule
This commit is contained in:
Colin Ulin
2019-11-19 15:52:46 -08:00
committed by Pranav Senthilnathan
parent 4311598e93
commit f7da07be7b
3 changed files with 155 additions and 18 deletions
+35 -17
View File
@@ -1,6 +1,6 @@
// Type definitions for parse 2.2.9
// Type definitions for parse 2.9
// Project: https://parseplatform.org/
// Definitions by: Ullisen Media Group <http://ullisenmedia.com>
// Definitions by: Ullisen Media Group <https://github.com/ullisenmedia>
// David Poetzsch-Heffter <https://github.com/dpoetzsch>
// Cedric Kemp <https://github.com/jaeggerr>
// Flavio Negrão <https://github.com/flavionegrao>
@@ -10,6 +10,8 @@
// Alexandre Hétu Rivard <https://github.com/AlexandreHetu>
// Diamond Lewis <https://github.com/dplewis>
// Jong Eun Lee <https://github.com/yomybaby>
// Colin Ulin <https://github.com/pocketcolin>
// Robert Helms <https://github.com/rdhelms>
// Julien Quere <https://github.com/jlnquere>
// Yago Tomé <https://github.com/yagotome>
// Thibault MOCELLIN <https://github.com/tybi>
@@ -339,17 +341,17 @@ declare namespace Parse {
*
* Creates a new model with defined attributes.
*/
class Object extends BaseObject {
class Object<T extends any = any> extends BaseObject {
id: string;
createdAt: Date;
updatedAt: Date;
attributes: any;
attributes: T;
cid: string;
changed: boolean;
className: string;
constructor(className?: string, options?: any);
constructor(attributes?: string[], options?: any);
constructor(className?: string, attributes?: T, options?: any);
static createWithoutData<T extends Object>(id: string): T;
static destroyAll<T extends Object>(list: T[], options?: Object.DestroyAllOptions): Promise<T[]>;
@@ -388,7 +390,7 @@ declare namespace Parse {
fetch(options?: Object.FetchOptions): Promise<this>;
fetchFromLocalDatastore(): Promise<this> | void;
fetchWithInclude(keys: string | Array<string | Array<string>>, options?: RequestOptions): Promise<this>;
get(attr: string): any | undefined;
get<K extends Exclude<keyof T, symbol | number>>(attr: K): T[K];
getACL(): ACL | undefined;
has(attr: string): boolean;
hasChanged(attr: string): boolean;
@@ -407,11 +409,24 @@ declare namespace Parse {
removeAll(attr: string, items: any): this | false;
revert(): void;
revert(...keys: string[]): void;
save(attrs?: { [key: string]: any } | null, options?: Object.SaveOptions): Promise<this>;
save(key: string, value: any, options?: Object.SaveOptions): Promise<this>;
save(attrs: object, options?: Object.SaveOptions): Promise<this>;
set(key: string, value: any, options?: Object.SetOptions): this | false;
set(attrs: object, options?: Object.SetOptions): this | false;
save(
attrs?: Partial<T> | null,
options?: Object.SaveOptions
): Promise<this>;
save<K extends keyof T>(
key: K,
value: T[K],
options?: Object.SaveOptions
): Promise<this>;
set(
attrs: Partial<T>,
options?: Object.SetOptions
): this | false;
set<K extends keyof T>(
key: K,
value: T[K],
options?: Object.SetOptions
): this | false;
setACL(acl: ACL, options?: SuccessFailureOptions): this | false;
toPointer(): Pointer;
unPin(): Promise<void>;
@@ -452,7 +467,8 @@ declare namespace Parse {
* Every Parse application installed on a device registered for
* push notifications has an associated Installation object.
*/
class Installation extends Object {
class Installation<T extends any = any> extends Object<T> {
badge: any;
channels: string[];
timeZone: any;
@@ -713,7 +729,8 @@ subscription.on('close', () => {});
* A Parse.Role is a local representation of a role persisted to the Parse
* cloud.
*/
class Role extends Object {
class Role<T extends any = any> extends Object<T> {
constructor(name: string, acl: ACL);
getRoles(): Relation<Role, Role>;
@@ -722,7 +739,7 @@ subscription.on('close', () => {});
setName(name: string, options?: SuccessFailureOptions): any;
}
class Config extends Object {
class Config<T extends any = any> extends Object<T> {
static get(options?: SuccessFailureOptions): Promise<Config>;
static current(): Config;
static save(attr: any): Promise<Config>;
@@ -731,7 +748,7 @@ subscription.on('close', () => {});
escape(attr: string): any;
}
class Session extends Object {
class Session<T extends any = any> extends Object<T> {
static current(): Promise<Session>;
getSessionToken(): string;
@@ -747,7 +764,8 @@ subscription.on('close', () => {});
* user specific methods, like authentication, signing up, and validation of
* uniqueness.</p>
*/
class User extends Object {
class User<T extends any = any> extends Object<T> {
static allowCustomUserClass(isAllowed: boolean): void;
static become(sessionToken: string, options?: UseMasterKeyOption): Promise<User>;
static current(): User | undefined;
+120
View File
@@ -678,3 +678,123 @@ async function test_schema() {
schema.update({ sessionToken: '' }).then(results => {});
}
function test_generic_object() {
// Test Parse.Object instantiation with no type assertion
const objAny = new Parse.Object('TestObject')
objAny.attributes.whatever // any
objAny.attributes.createdAt // Date | undefined
objAny.attributes.updatedAt // Date | undefined
objAny.attributes.objectId // string | undefined
objAny.attributes.ACL // Parse.ACL | undefined
// Test Parse.Object instantiation with manual type assertion
const objWithTypedAttrs = new Parse.Object<{ example: string }>('TestObject')
const exampleAttr = objWithTypedAttrs.attributes.example // string
const exampleGet = objWithTypedAttrs.get('example') // string
// const badAttr = objWithTypedAttrs.attributes.other // error
// Test Parse.Object instantiation with attributes
const objWithAttrsAndOptions = new Parse.Object('TestObject', { example: 200 })
const exampleAttrFromOptions = objWithAttrsAndOptions.attributes.example // number
const exampleGetFromOptions = objWithAttrsAndOptions.get('example') // number
// const badAttr2 = objWithAttrsAndOptions.attributes.other // error
}
function test_to_json_generic() {
const exampleObjAny = new Parse.Object('TestObject')
const exampleJsonAny = exampleObjAny.toJSON() // Parse.Object.Attributes
const exampleObjTyped = new Parse.Object('TestObject', {
exampleProp: false,
regexProp: new RegExp(''),
dateProp: new Date(),
parseProp: new Parse.Object('nestedObject', {
exampleProp: false,
regexProp: new RegExp(''),
dateProp: new Date(),
parseProp2: new Parse.Object('nestedObject', {
exampleProp2: false,
regexProp2: new RegExp(''),
dateProp2: new Date()
})
})
})
const exampleJsonTyped = exampleObjTyped.toJSON()
exampleJsonTyped.exampleProp // boolean
exampleJsonTyped.dateProp // string
exampleJsonTyped.regexProp // string
exampleJsonTyped.parseProp.dateProp // string
exampleJsonTyped.parseProp.parseProp2.regexProp2 // string
exampleJsonTyped.parseProp.ACL // Parse.ACL | undefined
exampleJsonTyped.parseProp.objectId // string | undefined
}
async function test_save_generic() {
const exampleObjAny = new Parse.Object('TestObject')
const savedAny = await exampleObjAny.save()
savedAny.attributes.anything // any
const savedNewExampleAttrAny = await exampleObjAny.save({ example: true })
savedNewExampleAttrAny.attributes.anythingElse // any
// Calling Parse.Object.save({}) with an existing attribute type checks
const exampleObjTyped = new Parse.Object<{ example: number; newExample: string }>('TestObject')
// const badSavedExampleAttr = await exampleObjTyped.save({ example: 'hello' }) // error
const savedExampleAttr = await exampleObjTyped.save({ example: 5 })
savedExampleAttr.attributes.example // number
// const badSavedExampleAttr = await exampleObjTyped.save({ createdAt: new Date() }) // never
// Calling Parse.Object.save({}) with a new attribute adds that attribute as a known key
const savedNewExampleAttr = await exampleObjTyped.save({ newExample: 'hello' })
savedNewExampleAttr.attributes.example // number
savedNewExampleAttr.attributes.newExample // string
// Parse.Object.save(key, value) type checks existing keys
// const badSavedExampleAttr2 = await exampleObjTyped.save('example', 'hello') // error
const savedExampleAttr2 = await exampleObjTyped.save('example', 10)
savedExampleAttr2.attributes.example // number
// const badSavedExampleAttr2 = await exampleObjTyped.save('createdAt', 10) // never
// Parse.Object.save(key, value) with a new attribute adds that attribute as a known key
const savedNewExampleAttr2 = await exampleObjTyped.save('newExample', 'hello again')
savedNewExampleAttr2.attributes.example // number
savedNewExampleAttr2.attributes.newExample // string
}
function test_set_generic() {
const exampleObjAny = new Parse.Object('TestObject')
exampleObjAny.attributes // any
exampleObjAny.set('propA', 'some value')
const exampleObjTyped = new Parse.Object<{ example: boolean }>('TestObject')
// Parse.Object.set({}) type checks existing keys, does not allow new keys
const setThingObj = exampleObjTyped.set({ example: false })
setThingObj && setThingObj.attributes.example // boolean
// exampleObjTyped.set({ example: 100 }) // error
// const badSetNewThingObj = exampleObjTyped.set({ newExample: 'something' }) // error
// Parse.Object.set(key, value) does not allow new keys
const setThingKeyVal = exampleObjTyped.set('example', false)
setThingKeyVal && setThingKeyVal.attributes.example // boolean
// const badSetThingNewKeyVal = exampleObjTyped.set('newExample', 100) // error
}
async function test_query_generic() {
const exampleQuery = new Parse.Query<Parse.Object<{ example: string }>>('TestObject')
const gotExampleObj = await exampleQuery.get('objectId')
const gotAttrExample = gotExampleObj.attributes.example // string
const gotGetExample = gotExampleObj.get('example') // string
// const badGotGetExample = gotExampleObj.get('anything') // error
const foundExampleObj = await exampleQuery.find()
foundExampleObj[0] && foundExampleObj[0].attributes.example // string
const firstExampleObj = await exampleQuery.first()
firstExampleObj && firstExampleObj.attributes.example // string
}
-1
View File
@@ -7,7 +7,6 @@
"ban-types": false,
"callable-types": false,
"comment-format": false,
"dt-header": false,
"npm-naming": false,
"eofline": false,
"export-just-namespace": false,