From f7da07be7bd79d5b4162bc5e3e93f4408bea15e8 Mon Sep 17 00:00:00 2001
From: Colin Ulin <47982430+pocketcolin@users.noreply.github.com>
Date: Tue, 19 Nov 2019 18:52:46 -0500
Subject: [PATCH] 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
---
types/parse/index.d.ts | 52 ++++++++++------
types/parse/parse-tests.ts | 120 +++++++++++++++++++++++++++++++++++++
types/parse/tslint.json | 1 -
3 files changed, 155 insertions(+), 18 deletions(-)
diff --git a/types/parse/index.d.ts b/types/parse/index.d.ts
index 45e678b437..94c142984b 100644
--- a/types/parse/index.d.ts
+++ b/types/parse/index.d.ts
@@ -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
+// Definitions by: Ullisen Media Group
// David Poetzsch-Heffter
// Cedric Kemp
// Flavio Negrão
@@ -10,6 +10,8 @@
// Alexandre Hétu Rivard
// Diamond Lewis
// Jong Eun Lee
+// Colin Ulin
+// Robert Helms
// Julien Quere
// Yago Tomé
// Thibault MOCELLIN
@@ -339,17 +341,17 @@ declare namespace Parse {
*
* Creates a new model with defined attributes.
*/
- class Object extends BaseObject {
+ class Object 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(id: string): T;
static destroyAll(list: T[], options?: Object.DestroyAllOptions): Promise;
@@ -388,7 +390,7 @@ declare namespace Parse {
fetch(options?: Object.FetchOptions): Promise;
fetchFromLocalDatastore(): Promise | void;
fetchWithInclude(keys: string | Array>, options?: RequestOptions): Promise;
- get(attr: string): any | undefined;
+ get>(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;
- save(key: string, value: any, options?: Object.SaveOptions): Promise;
- save(attrs: object, options?: Object.SaveOptions): Promise;
- set(key: string, value: any, options?: Object.SetOptions): this | false;
- set(attrs: object, options?: Object.SetOptions): this | false;
+ save(
+ attrs?: Partial | null,
+ options?: Object.SaveOptions
+ ): Promise;
+ save(
+ key: K,
+ value: T[K],
+ options?: Object.SaveOptions
+ ): Promise;
+ set(
+ attrs: Partial,
+ options?: Object.SetOptions
+ ): this | false;
+ set(
+ key: K,
+ value: T[K],
+ options?: Object.SetOptions
+ ): this | false;
setACL(acl: ACL, options?: SuccessFailureOptions): this | false;
toPointer(): Pointer;
unPin(): Promise;
@@ -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 extends Object {
+
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 extends Object {
+
constructor(name: string, acl: ACL);
getRoles(): Relation;
@@ -722,7 +739,7 @@ subscription.on('close', () => {});
setName(name: string, options?: SuccessFailureOptions): any;
}
- class Config extends Object {
+ class Config extends Object {
static get(options?: SuccessFailureOptions): Promise;
static current(): Config;
static save(attr: any): Promise;
@@ -731,7 +748,7 @@ subscription.on('close', () => {});
escape(attr: string): any;
}
- class Session extends Object {
+ class Session extends Object {
static current(): Promise;
getSessionToken(): string;
@@ -747,7 +764,8 @@ subscription.on('close', () => {});
* user specific methods, like authentication, signing up, and validation of
* uniqueness.
*/
- class User extends Object {
+ class User extends Object {
+
static allowCustomUserClass(isAllowed: boolean): void;
static become(sessionToken: string, options?: UseMasterKeyOption): Promise;
static current(): User | undefined;
diff --git a/types/parse/parse-tests.ts b/types/parse/parse-tests.ts
index 36e59651f5..ee38af7e98 100644
--- a/types/parse/parse-tests.ts
+++ b/types/parse/parse-tests.ts
@@ -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>('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
+}
diff --git a/types/parse/tslint.json b/types/parse/tslint.json
index 158db48e64..9c76d1ea7e 100644
--- a/types/parse/tslint.json
+++ b/types/parse/tslint.json
@@ -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,