entropic-bond 1.48.1 → 1.50.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/release.yml +26 -0
- package/CHANGELOG.md +1151 -0
- package/docs/.nojekyll +1 -0
- package/docs/README.md +94 -0
- package/docs/classes/Auth.md +391 -0
- package/docs/classes/AuthMock.md +278 -0
- package/docs/classes/AuthService.md +188 -0
- package/docs/classes/CloudFunctions.md +123 -0
- package/docs/classes/CloudFunctionsMock.md +97 -0
- package/docs/classes/CloudStorage.md +215 -0
- package/docs/classes/DataSource.md +248 -0
- package/docs/classes/EntropicComponent.md +666 -0
- package/docs/classes/JsonDataSource.md +328 -0
- package/docs/classes/MockCloudStorage.md +279 -0
- package/docs/classes/Model.md +274 -0
- package/docs/classes/Observable.md +120 -0
- package/docs/classes/Persistent.md +420 -0
- package/docs/classes/ServerAuth.md +211 -0
- package/docs/classes/ServerAuthMock.md +176 -0
- package/docs/classes/ServerAuthService.md +130 -0
- package/docs/classes/Store.md +218 -0
- package/docs/classes/StoredFile.md +636 -0
- package/docs/enums/StoredFileEvent.md +41 -0
- package/docs/interfaces/AuthError.md +30 -0
- package/docs/interfaces/CloudFunctionsService.md +69 -0
- package/docs/interfaces/Collection.md +13 -0
- package/docs/interfaces/CustomCredentials.md +7 -0
- package/docs/interfaces/DocumentReference.md +49 -0
- package/docs/interfaces/JsonRawData.md +7 -0
- package/docs/interfaces/SignData.md +63 -0
- package/docs/interfaces/StoreParams.md +52 -0
- package/docs/interfaces/StoredFileChange.md +41 -0
- package/docs/interfaces/UploadControl.md +90 -0
- package/docs/interfaces/UserCredentials.md +113 -0
- package/docs/interfaces/Values.md +17 -0
- package/docs/modules.md +1273 -0
- package/package.json +23 -19
- package/src/auth/auth-mock.spec.ts +168 -0
- package/src/auth/auth-mock.ts +129 -0
- package/src/auth/auth.ts +185 -0
- package/src/auth/user-auth-types.ts +21 -0
- package/src/cloud-functions/cloud-functions-mock.spec.ts +136 -0
- package/src/cloud-functions/cloud-functions-mock.ts +23 -0
- package/src/cloud-functions/cloud-functions.ts +83 -0
- package/src/cloud-storage/cloud-storage.spec.ts +207 -0
- package/src/cloud-storage/cloud-storage.ts +60 -0
- package/src/cloud-storage/mock-cloud-storage.ts +72 -0
- package/src/cloud-storage/stored-file.ts +102 -0
- package/src/index.ts +19 -0
- package/src/observable/observable.spec.ts +105 -0
- package/src/observable/observable.ts +67 -0
- package/src/persistent/entropic-component.spec.ts +143 -0
- package/src/persistent/entropic-component.ts +135 -0
- package/src/persistent/persistent.spec.ts +828 -0
- package/src/persistent/persistent.ts +650 -0
- package/src/server-auth/server-auth-mock.spec.ts +53 -0
- package/src/server-auth/server-auth-mock.ts +45 -0
- package/src/server-auth/server-auth.ts +49 -0
- package/src/store/data-source.ts +186 -0
- package/src/store/json-data-source.spec.ts +100 -0
- package/src/store/json-data-source.ts +256 -0
- package/src/store/mocks/mock-data.json +155 -0
- package/src/store/mocks/test-user.ts +122 -0
- package/src/store/model.spec.ts +659 -0
- package/src/store/model.ts +462 -0
- package/src/store/store.spec.ts +30 -0
- package/src/store/store.ts +113 -0
- package/src/types/utility-types.spec.ts +117 -0
- package/src/types/utility-types.ts +116 -0
- package/src/utils/test-utils/test-person.ts +44 -0
- package/src/utils/utils.spec.ts +95 -0
- package/{lib/utils/utils.d.ts → src/utils/utils.ts} +34 -10
- package/tsconfig-build.json +7 -0
- package/tsconfig-cjs.json +9 -0
- package/tsconfig.json +33 -0
- package/vite.config.ts +22 -0
- package/lib/auth/auth-mock.d.ts +0 -21
- package/lib/auth/auth-mock.js +0 -108
- package/lib/auth/auth-mock.js.map +0 -1
- package/lib/auth/auth.d.ts +0 -129
- package/lib/auth/auth.js +0 -146
- package/lib/auth/auth.js.map +0 -1
- package/lib/auth/user-auth-types.d.ts +0 -19
- package/lib/auth/user-auth-types.js +0 -3
- package/lib/auth/user-auth-types.js.map +0 -1
- package/lib/cloud-functions/cloud-functions-mock.d.ts +0 -11
- package/lib/cloud-functions/cloud-functions-mock.js +0 -19
- package/lib/cloud-functions/cloud-functions-mock.js.map +0 -1
- package/lib/cloud-functions/cloud-functions.d.ts +0 -19
- package/lib/cloud-functions/cloud-functions.js +0 -64
- package/lib/cloud-functions/cloud-functions.js.map +0 -1
- package/lib/cloud-storage/cloud-storage.d.ts +0 -24
- package/lib/cloud-storage/cloud-storage.js +0 -37
- package/lib/cloud-storage/cloud-storage.js.map +0 -1
- package/lib/cloud-storage/mock-cloud-storage.d.ts +0 -20
- package/lib/cloud-storage/mock-cloud-storage.js +0 -68
- package/lib/cloud-storage/mock-cloud-storage.js.map +0 -1
- package/lib/cloud-storage/stored-file.d.ts +0 -39
- package/lib/cloud-storage/stored-file.js +0 -106
- package/lib/cloud-storage/stored-file.js.map +0 -1
- package/lib/index.d.ts +0 -19
- package/lib/index.js +0 -36
- package/lib/index.js.map +0 -1
- package/lib/observable/observable.d.ts +0 -52
- package/lib/observable/observable.js +0 -66
- package/lib/observable/observable.js.map +0 -1
- package/lib/persistent/entropic-component.d.ts +0 -76
- package/lib/persistent/entropic-component.js +0 -109
- package/lib/persistent/entropic-component.js.map +0 -1
- package/lib/persistent/persistent.d.ts +0 -281
- package/lib/persistent/persistent.js +0 -539
- package/lib/persistent/persistent.js.map +0 -1
- package/lib/server-auth/server-auth-mock.d.ts +0 -12
- package/lib/server-auth/server-auth-mock.js +0 -39
- package/lib/server-auth/server-auth-mock.js.map +0 -1
- package/lib/server-auth/server-auth.d.ts +0 -24
- package/lib/server-auth/server-auth.js +0 -36
- package/lib/server-auth/server-auth.js.map +0 -1
- package/lib/store/data-source.d.ts +0 -137
- package/lib/store/data-source.js +0 -62
- package/lib/store/data-source.js.map +0 -1
- package/lib/store/json-data-source.d.ts +0 -68
- package/lib/store/json-data-source.js +0 -199
- package/lib/store/json-data-source.js.map +0 -1
- package/lib/store/mocks/test-user.d.ts +0 -49
- package/lib/store/mocks/test-user.js +0 -135
- package/lib/store/mocks/test-user.js.map +0 -1
- package/lib/store/model.d.ts +0 -238
- package/lib/store/model.js +0 -417
- package/lib/store/model.js.map +0 -1
- package/lib/store/store.d.ts +0 -62
- package/lib/store/store.js +0 -102
- package/lib/store/store.js.map +0 -1
- package/lib/types/utility-types.d.ts +0 -45
- package/lib/types/utility-types.js +0 -3
- package/lib/types/utility-types.js.map +0 -1
- package/lib/utils/test-utils/test-person.d.ts +0 -33
- package/lib/utils/test-utils/test-person.js +0 -25
- package/lib/utils/test-utils/test-person.js.map +0 -1
- package/lib/utils/utils.js +0 -76
- package/lib/utils/utils.js.map +0 -1
|
@@ -0,0 +1,650 @@
|
|
|
1
|
+
import { v4 as uuid } from "uuid"
|
|
2
|
+
import { ClassPropNames, SomeClassProps, UnderscoredProp } from '../types/utility-types'
|
|
3
|
+
|
|
4
|
+
export type PersistentConstructor = new () => Persistent
|
|
5
|
+
|
|
6
|
+
interface FactoryMap {
|
|
7
|
+
[ id: string ]: {
|
|
8
|
+
factory: PersistentConstructor
|
|
9
|
+
annotation: unknown
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The corresponding type of the plain object of a persistent class.
|
|
15
|
+
*/
|
|
16
|
+
export type PersistentObject<T extends Persistent> = Omit<SomeClassProps<T>, 'className'> & Partial<DocumentReference> & {
|
|
17
|
+
__className: string
|
|
18
|
+
__rootCollections?: Collections
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type PersistentObjectWithId<T extends Persistent> = PersistentObject<T> & {
|
|
22
|
+
id: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The type of the plain object of a persistent class for all the nested properties.
|
|
27
|
+
*/
|
|
28
|
+
export type MakePersistentObjects<T> = {
|
|
29
|
+
[A in keyof T]: T[A] extends Persistent? PersistentObject<T[A]> : MakePersistentObjects<T[A]>
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* A collection of document objects typically returned by Persistent.toObject()
|
|
34
|
+
* @see Persistent.toObject
|
|
35
|
+
*/
|
|
36
|
+
export type Collections = {
|
|
37
|
+
[ collectionPath: string ]: PersistentObjectWithId<Persistent>[] | undefined
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Stores information about a reference in another collection.
|
|
42
|
+
*/
|
|
43
|
+
export interface DocumentReference {
|
|
44
|
+
id: string
|
|
45
|
+
__className: string
|
|
46
|
+
__documentReference: {
|
|
47
|
+
storedInCollection: string
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* A class that provides several methods to serialize and deserialize objects.
|
|
53
|
+
*/
|
|
54
|
+
export class Persistent {
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Registers a class to be used by the persistence engine.
|
|
58
|
+
* @param className the name of the class to be registered
|
|
59
|
+
* @param factory the constructor of the registered class
|
|
60
|
+
* @param annotation an annotation associated with the class
|
|
61
|
+
*/
|
|
62
|
+
static registerFactory( className: string, factory: PersistentConstructor, annotation?: unknown ) {
|
|
63
|
+
this._factoryMap[ className ] = { factory, annotation }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Returns the constructor of a registered class
|
|
68
|
+
* @param className the name of the class to be retrieved
|
|
69
|
+
* @returns the constructor of the class
|
|
70
|
+
* @throws an error if the class is not registered
|
|
71
|
+
* @see registerFactory
|
|
72
|
+
* @see registeredClasses
|
|
73
|
+
* @see classesExtending
|
|
74
|
+
* @see annotations
|
|
75
|
+
*/
|
|
76
|
+
static classFactory( className: string | undefined ) {
|
|
77
|
+
if ( !className ) throw new Error( `You should provide a class name.` )
|
|
78
|
+
if ( !this._factoryMap[ className ] ) throw new Error( `You should register class ${ className } prior to use.` )
|
|
79
|
+
return this._factoryMap[ className ]!.factory
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Returns the names of all registered classes
|
|
84
|
+
* @returns the names of all registered classes
|
|
85
|
+
* @see registerFactory
|
|
86
|
+
* @see classFactory
|
|
87
|
+
*/
|
|
88
|
+
static registeredClasses() {
|
|
89
|
+
return Object.keys( this._factoryMap )
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Returns the names of all registered classes that extend a given class
|
|
94
|
+
* @param derivedFrom the class to be extended
|
|
95
|
+
* @returns the names of all registered classes that extend the given class
|
|
96
|
+
* @see registerFactory
|
|
97
|
+
* @see classFactory
|
|
98
|
+
*/
|
|
99
|
+
static classesExtending( derivedFrom: PersistentConstructor | Function ) {
|
|
100
|
+
return Object.entries( this._factoryMap )
|
|
101
|
+
.filter(([ , obj ]) => new ( obj.factory ) instanceof derivedFrom )
|
|
102
|
+
.map(([ className ]) => className )
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Returns the annotation associated with a registered class
|
|
107
|
+
* @param className the name of the class to be retrieved
|
|
108
|
+
* @returns the annotation associated with the class
|
|
109
|
+
* @throws an error if the class is not registered
|
|
110
|
+
* @see registerFactory
|
|
111
|
+
*/
|
|
112
|
+
static annotations( className: string | Persistent | PersistentConstructor ) {
|
|
113
|
+
if ( className instanceof Persistent ) className = className.className
|
|
114
|
+
else if ( typeof className === 'string' ) className
|
|
115
|
+
else className = new className().className
|
|
116
|
+
|
|
117
|
+
if ( !this._factoryMap[ className ] ) throw new Error( `You should register class ${ className } prior to use.` )
|
|
118
|
+
return this._factoryMap[ className ]!.annotation
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Returns a new instance of Persistent class.
|
|
123
|
+
* @param className the initial id of this instance. If not provided, a new id will be generated
|
|
124
|
+
*/
|
|
125
|
+
constructor( id: string = uuid() ) {
|
|
126
|
+
this._id = id
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Gets the class name of this instance.
|
|
131
|
+
*/
|
|
132
|
+
get className(): string {
|
|
133
|
+
return this[ '__className' ];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Sets the id of this instance.
|
|
138
|
+
* @param value the id of this instance
|
|
139
|
+
*/
|
|
140
|
+
protected setId( value: string ) {
|
|
141
|
+
this._id = value
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Returns the id of this instance.
|
|
146
|
+
* @returns the id of this instance
|
|
147
|
+
*/
|
|
148
|
+
get id() {
|
|
149
|
+
return this._id;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* This method is called by the persistence engine when the instance has been
|
|
154
|
+
* just serialized. It is called after the properties are initialized with
|
|
155
|
+
* serialized data.
|
|
156
|
+
*/
|
|
157
|
+
protected afterDeserialize() {}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* This method is called by the persistence engine before the instance is
|
|
161
|
+
* serialized.
|
|
162
|
+
*/
|
|
163
|
+
protected beforeSerialize() {}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Returns an array of the persistent properties of this instance.
|
|
167
|
+
* @returns an array of the persistent properties of this instance
|
|
168
|
+
*/
|
|
169
|
+
getPersistentProperties(): readonly PersistentProperty[] {
|
|
170
|
+
if ( !this._persistentProperties ) return []
|
|
171
|
+
return this._persistentProperties.map( prop => ({
|
|
172
|
+
...prop,
|
|
173
|
+
name: prop.name.slice( 1 )
|
|
174
|
+
}))
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Get the property information of this instance
|
|
179
|
+
* @param propName the persistent property name
|
|
180
|
+
* @returns the property information
|
|
181
|
+
*/
|
|
182
|
+
getPropInfo<T extends this>( propName: ClassPropNames<T> ): PersistentProperty {
|
|
183
|
+
const propInfo = this.getPersistentProperties().find( prop => prop.name === propName as string )
|
|
184
|
+
if ( !propInfo ) throw new Error( `Property "${ propName as string }" has not been registered.` )
|
|
185
|
+
return propInfo
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Query if the property is required
|
|
190
|
+
* To mark a property as required, use the @required decorator
|
|
191
|
+
* @param propName the persistent property name
|
|
192
|
+
* @returns true if the property is required
|
|
193
|
+
* @see required
|
|
194
|
+
*/
|
|
195
|
+
isRequired<T extends this>( propName: ClassPropNames<T> ): boolean {
|
|
196
|
+
const validator = this.getPropInfo( propName ).validator
|
|
197
|
+
return validator !== undefined && validator !== null
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Query if the property value is valid
|
|
202
|
+
* Define the validator function using the @required decorator
|
|
203
|
+
* @param propName the persistent property name
|
|
204
|
+
* @returns true if the property value is valid using the validator function
|
|
205
|
+
* passed to the @required decorator
|
|
206
|
+
* @see required
|
|
207
|
+
*/
|
|
208
|
+
isPropValueValid<T extends this>( propName: ClassPropNames<T> ): boolean {
|
|
209
|
+
const propInfo = this.getPropInfo( propName )
|
|
210
|
+
if ( !propInfo.validator ) return true
|
|
211
|
+
return propInfo.validator( this[ propInfo.name ], propInfo, this )
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Copy the persistent properties of the given instance to this instance.
|
|
216
|
+
* The property `id` will be ignored.
|
|
217
|
+
* Only the properties that are not null or undefined will be copied.
|
|
218
|
+
* @param instance the instance to be copied
|
|
219
|
+
* @returns this instance
|
|
220
|
+
* @see fromObject
|
|
221
|
+
* @see toObject
|
|
222
|
+
*/
|
|
223
|
+
clone( instance: Persistent ): this {
|
|
224
|
+
const obj = instance.toObject() as any
|
|
225
|
+
delete obj['id']
|
|
226
|
+
return this.fromObject( obj )
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Initializes the persistent properties of this instance from the properties
|
|
231
|
+
* of given object.
|
|
232
|
+
* @param obj the object to be copied
|
|
233
|
+
* @returns this instance
|
|
234
|
+
* @see clone
|
|
235
|
+
* @see toObject
|
|
236
|
+
*/
|
|
237
|
+
fromObject( obj: Partial<PersistentObject<this>> |{}): this {
|
|
238
|
+
this.fromObj( obj )
|
|
239
|
+
this.afterDeserialize()
|
|
240
|
+
|
|
241
|
+
return this
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
private fromObj( obj: Partial<PersistentObject<this>> | {}) {
|
|
245
|
+
if ( !this._persistentProperties ) return this
|
|
246
|
+
|
|
247
|
+
this._persistentProperties.forEach( prop => {
|
|
248
|
+
const propName = this.removeUnderscore( prop )
|
|
249
|
+
|
|
250
|
+
const value = obj[ propName ]
|
|
251
|
+
if ( value !== undefined && value !== null ) {
|
|
252
|
+
this[ prop.name ] = this.fromDeepObject( value )
|
|
253
|
+
}
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
return this
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Returns a plain object representation of this instance.
|
|
261
|
+
* Only the properties that are not null or undefined will be copied.
|
|
262
|
+
* @returns a plain object representation of this instance
|
|
263
|
+
* @see fromObject
|
|
264
|
+
* @see clone
|
|
265
|
+
*/
|
|
266
|
+
toObject(): PersistentObject<this> {
|
|
267
|
+
const rootCollections: Collections = {}
|
|
268
|
+
const obj = this.toObj( rootCollections )
|
|
269
|
+
this.pushDocument( rootCollections, this.className, obj )
|
|
270
|
+
|
|
271
|
+
return {
|
|
272
|
+
...obj,
|
|
273
|
+
__rootCollections: rootCollections
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
private toObj( rootCollections: Collections ): PersistentObject<this> {
|
|
278
|
+
if ( !this._persistentProperties ) return {} as PersistentObject<this>
|
|
279
|
+
this.beforeSerialize()
|
|
280
|
+
|
|
281
|
+
const obj: PersistentObject<this> = {} as any
|
|
282
|
+
if ( !this.className ) throw new Error( 'You should register this class prior to streaming it.' )
|
|
283
|
+
|
|
284
|
+
this._persistentProperties.forEach( prop => {
|
|
285
|
+
const propValue = this[ prop.name ]
|
|
286
|
+
const propName = this.removeUnderscore( prop )
|
|
287
|
+
|
|
288
|
+
if ( propValue !== undefined && propValue !== null ) {
|
|
289
|
+
|
|
290
|
+
if ( prop.isReference ) {
|
|
291
|
+
obj[ propName ] = this.toReferenceObj( prop, rootCollections )
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
obj[ propName ] = this.toDeepObj( propValue, rootCollections )
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if ( prop.searchableArray ) {
|
|
298
|
+
obj[ Persistent.searchableArrayNameFor( propName ) ] = propValue.map(( value: PersistentObject<Persistent> ) => value.id )
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
obj[ '__className' ] = this.className
|
|
304
|
+
|
|
305
|
+
return obj
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
static searchableArrayNameFor( propName: string ) {
|
|
309
|
+
return `__${ propName }_searchable`
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
private fromDeepObject( value: unknown ) {
|
|
313
|
+
if ( value === undefined || value === null ) return value
|
|
314
|
+
|
|
315
|
+
if ( Array.isArray( value ) ) {
|
|
316
|
+
return value.map( item => this.fromDeepObject( item ) )
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if ( value[ '__documentReference' ] ) {
|
|
320
|
+
const ref: DocumentReference = value as DocumentReference
|
|
321
|
+
const emptyInstance = Persistent.createInstance( ref )
|
|
322
|
+
// emptyInstance._id = ref.id
|
|
323
|
+
emptyInstance['__documentReference'] = value[ '__documentReference' ]
|
|
324
|
+
return emptyInstance
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if ( value[ '__className' ] ) {
|
|
328
|
+
return Persistent.createInstance( value as PersistentObject<Persistent> )
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if ( typeof value === 'object' ) {
|
|
332
|
+
const newObject = {}
|
|
333
|
+
|
|
334
|
+
Object.entries( value ).forEach(
|
|
335
|
+
( [ key, value ] ) => newObject[ key ] = this.fromDeepObject( value )
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
return newObject
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return value
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
private toDeepObj( value: any, rootCollections: Collections ) {
|
|
345
|
+
if ( value === null || value === undefined ) {
|
|
346
|
+
return undefined
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if ( Array.isArray( value ) ) {
|
|
350
|
+
return value.map( item => this.toDeepObj( item, rootCollections ) )
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if ( value[ '__documentReference' ] ) return value
|
|
354
|
+
|
|
355
|
+
if ( value instanceof Persistent ) {
|
|
356
|
+
return value.toObj( rootCollections )
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if ( typeof value === 'object' ) {
|
|
360
|
+
const newObject = {}
|
|
361
|
+
|
|
362
|
+
Object.entries( value ).forEach(
|
|
363
|
+
( [ key, val ] ) => newObject[ key ] = this.toDeepObj( val, rootCollections )
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
return newObject
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return value
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
private toReferenceObj( prop: PersistentProperty, rootCollections: Collections ) {
|
|
373
|
+
const propValue: Persistent | Persistent[] = this[ prop.name ]
|
|
374
|
+
|
|
375
|
+
const collectionPath = ( value: Persistent ) => {
|
|
376
|
+
let storeInCollection: string
|
|
377
|
+
|
|
378
|
+
if ( typeof prop.storeInCollection === 'function' ) {
|
|
379
|
+
storeInCollection = prop.storeInCollection( value, prop )
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
storeInCollection = prop.storeInCollection || value.className
|
|
383
|
+
}
|
|
384
|
+
return storeInCollection
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if ( Array.isArray( propValue ) ) {
|
|
388
|
+
|
|
389
|
+
return propValue.map( item => {
|
|
390
|
+
if ( !prop.isPureReference ) {
|
|
391
|
+
this.pushDocument( rootCollections, collectionPath( item ), item )
|
|
392
|
+
}
|
|
393
|
+
return this.buildRefObject( item, collectionPath( item ), prop.forcedPersistentProps )
|
|
394
|
+
})
|
|
395
|
+
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
if ( !prop.isPureReference ) {
|
|
399
|
+
this.pushDocument( rootCollections, collectionPath( propValue ), propValue )
|
|
400
|
+
}
|
|
401
|
+
return this.buildRefObject( propValue, collectionPath( propValue ), prop.forcedPersistentProps )
|
|
402
|
+
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
private buildRefObject( value: Persistent, storeInCollection: string, forcedPersistentProps?: ClassPropNames<Persistent>[] ): DocumentReference {
|
|
407
|
+
const forcedObject = forcedPersistentProps?.reduce( ( obj, propName ) => {
|
|
408
|
+
if ( value[ propName ] !== undefined ) obj[ propName ] = value[ propName ]
|
|
409
|
+
return obj
|
|
410
|
+
}, {})
|
|
411
|
+
|
|
412
|
+
return {
|
|
413
|
+
id: value.id,
|
|
414
|
+
__className: value.className || value['__className'],
|
|
415
|
+
__documentReference: {
|
|
416
|
+
storedInCollection: storeInCollection
|
|
417
|
+
},
|
|
418
|
+
...forcedObject
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
private pushDocument( collections: Collections, collectionName: string, value: DocumentReference | Persistent | PersistentObject<this> ) {
|
|
423
|
+
if ( '__documentReference' in value && value.__documentReference ) return
|
|
424
|
+
|
|
425
|
+
if ( !collections[ collectionName ] ) collections[ collectionName ] = []
|
|
426
|
+
const document = this.toDeepObj( value, collections )
|
|
427
|
+
collections[ collectionName ]!.push( document )
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
private removeUnderscore( prop: PersistentProperty ) {
|
|
431
|
+
return prop.name.slice(1)
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
static createReference<T extends Persistent>( obj: PersistentObject<T> | string ): T {
|
|
435
|
+
const instance = Persistent.createInstance( obj )
|
|
436
|
+
instance['__documentReference'] = obj['__documentReference'] || { storedInCollection: instance.className }
|
|
437
|
+
return instance
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
static createInstance<T extends Persistent>( obj: PersistentObject<T> | string ): T {
|
|
441
|
+
if ( typeof obj === 'string' ) {
|
|
442
|
+
return new ( Persistent.classFactory( obj ) ) as T
|
|
443
|
+
}
|
|
444
|
+
else {
|
|
445
|
+
try {
|
|
446
|
+
const instance = new ( Persistent.classFactory( obj.__className ) )
|
|
447
|
+
return instance.fromObject( obj ) as T
|
|
448
|
+
}
|
|
449
|
+
catch ( e ) {
|
|
450
|
+
const stringifiedObj = Object.entries( obj )
|
|
451
|
+
.filter(([ _key, value ])=> value !== undefined && value !== null && typeof value !== 'function' )
|
|
452
|
+
.map(([ key, value ])=>`${ key }: ${ value }` )
|
|
453
|
+
.join( ',\n\t' )
|
|
454
|
+
throw new Error( `${ e }\n-----> Class name not found in object:\n{\n\t ${ stringifiedObj } \n}\n` )
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
static propInfo<T extends Persistent>( registeredClassName: string, propName: ClassPropNames<T> ): PersistentProperty {
|
|
460
|
+
const inst = Persistent.createInstance( registeredClassName )
|
|
461
|
+
return inst.getPropInfo( propName )
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
@persistent private _id: string
|
|
465
|
+
private _persistentProperties: PersistentProperty[] | undefined
|
|
466
|
+
private static _factoryMap: FactoryMap = {}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
///////////////////////////////////
|
|
470
|
+
//Decorators
|
|
471
|
+
///////////////////////////////////
|
|
472
|
+
|
|
473
|
+
type CollectionPathCallback = ( value: Persistent, prop: PersistentProperty ) => string
|
|
474
|
+
type ValidatorFunction<T extends Persistent, P extends ClassPropNames<T>> = ( value: T[P], property: PersistentProperty, persistentInstance: T ) => boolean
|
|
475
|
+
|
|
476
|
+
interface PersistentProperty {
|
|
477
|
+
name: string
|
|
478
|
+
isReference?: boolean
|
|
479
|
+
isPureReference?: boolean
|
|
480
|
+
storeInCollection?: string | CollectionPathCallback
|
|
481
|
+
subCollection?: string
|
|
482
|
+
forcedPersistentProps?: ClassPropNames<Persistent>[]
|
|
483
|
+
toObjectSpecial?: ( classObj: any ) => any
|
|
484
|
+
fromObjectSpecial?: ( obj: any ) => any
|
|
485
|
+
searchableArray?: boolean
|
|
486
|
+
validator?: ValidatorFunction<any, any>
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Decorator for a property that you want to persist.
|
|
491
|
+
*/
|
|
492
|
+
export function persistent( target: Persistent, property: string ) {
|
|
493
|
+
return persistentParser()( target, property );
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Decorator for a property that is a reference to a persistent object and should be stored
|
|
498
|
+
* in a specific collection.
|
|
499
|
+
* @param collectionPath the path to the collection where the reference should be stored.
|
|
500
|
+
* @returns
|
|
501
|
+
*/
|
|
502
|
+
export function persistentReferenceAt( collectionPath: string | CollectionPathCallback ) {
|
|
503
|
+
return function( target: Persistent, property: string ) {
|
|
504
|
+
return persistentParser({
|
|
505
|
+
storeInCollection: collectionPath,
|
|
506
|
+
isReference: true
|
|
507
|
+
})( target, property )
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Decorator for a property that is a reference to a persistent object.
|
|
513
|
+
* The reference content is automatically stored in a collection. The collection
|
|
514
|
+
* is determined by the class name of the decorated property.
|
|
515
|
+
* @see persistentPureReference
|
|
516
|
+
*/
|
|
517
|
+
export function persistentReference( target: Persistent, property: string ) {
|
|
518
|
+
return persistentParser({ isReference: true })( target, property )
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Decorator to declare a persistent reference (see @persistentReference) that stores
|
|
523
|
+
* the values in forcedPersistentProps as values in the reference object. This is useful
|
|
524
|
+
* when you are not able to wait for population of referenced properties.
|
|
525
|
+
* @param forcedPersistentProps the properties whose values should be stored in the reference object
|
|
526
|
+
* @param storedInCollection indicates the path of the collection where this reference is stored
|
|
527
|
+
*/
|
|
528
|
+
export function persistentReferenceWithPersistentProps<T extends Persistent>( forcedPersistentProps: ClassPropNames<T>[], storeInCollection?: string | CollectionPathCallback ) {
|
|
529
|
+
return function( target: Persistent, property: string ) {
|
|
530
|
+
const persistentProps: Partial<PersistentProperty> = {
|
|
531
|
+
isReference: true,
|
|
532
|
+
forcedPersistentProps: forcedPersistentProps as ClassPropNames<Persistent>[],
|
|
533
|
+
storeInCollection: storeInCollection
|
|
534
|
+
}
|
|
535
|
+
return persistentParser( persistentProps )( target, property )
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Decorator for a property that is a reference to a persistent object.
|
|
541
|
+
* In this case, and contrary to the @persistentReference decorator, the reference
|
|
542
|
+
* contents is not stored even it has been changed. Only the reference information
|
|
543
|
+
* is stored.
|
|
544
|
+
* @see persistentReference
|
|
545
|
+
*/
|
|
546
|
+
export function persistentPureReference( target: Persistent, property: string, storeInCollection?: string | CollectionPathCallback ) {
|
|
547
|
+
return persistentParser({ isReference: true, isPureReference: true, storeInCollection })( target, property )
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* Decorator to declare a persistent property as a pure reference (see @persistentPureReference) that stores
|
|
552
|
+
* the values of the properties listed in forcedPersistentProps as values in the reference object. This is useful
|
|
553
|
+
* when you only need a few properties to be available without needing to populate the referenced property.
|
|
554
|
+
* @param forcedPersistentProps the properties whose values should be stored in the reference object
|
|
555
|
+
* @param storedInCollection indicates the path of the collection where this reference is stored
|
|
556
|
+
* @see persistentReferenceWithPersistentProps
|
|
557
|
+
* @see persistentPureReference
|
|
558
|
+
* @sample
|
|
559
|
+
* class User extends Persistent {
|
|
560
|
+
* @persistentPureReferenceWithPersistentProps( ['name', 'email'] ) private _friend: User
|
|
561
|
+
* }
|
|
562
|
+
* // the reference object will contain the properties name and email of the referenced user
|
|
563
|
+
* // without having to populate the _friend property
|
|
564
|
+
*/
|
|
565
|
+
export function persistentPureReferenceWithPersistentProps<T extends Persistent>( forcedPersistentProps: ClassPropNames<T>[], storeInCollection?: string | CollectionPathCallback ) {
|
|
566
|
+
return function( target: Persistent, property: string ) {
|
|
567
|
+
return persistentParser({ isReference: true, isPureReference: true, forcedPersistentProps: forcedPersistentProps as ClassPropNames<Persistent>[], storeInCollection })( target, property )
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
export function persistentParser( options?: Partial<PersistentProperty> ) {
|
|
572
|
+
return function( target: Persistent, property: string ) {
|
|
573
|
+
|
|
574
|
+
// from: https://stackoverflow.com/questions/43912168/typescript-decorators-with-inheritance
|
|
575
|
+
// should work like this in order to avoid propagation of persistent properties from one class to others
|
|
576
|
+
if ( !Object.getOwnPropertyDescriptor( target, '_persistentProperties' ) ) {
|
|
577
|
+
if ( target[ '_persistentProperties' ] ) {
|
|
578
|
+
target[ '_persistentProperties' ] = [ ...target[ '_persistentProperties' ] ]
|
|
579
|
+
}
|
|
580
|
+
else target[ '_persistentProperties' ] = []
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
const propInfo = target[ '_persistentProperties' ]!.find( prop => prop.name === property )
|
|
584
|
+
if ( propInfo ) {
|
|
585
|
+
Object.assign( propInfo, options )
|
|
586
|
+
}
|
|
587
|
+
else {
|
|
588
|
+
target[ '_persistentProperties' ]!.push({
|
|
589
|
+
name: property,
|
|
590
|
+
...options
|
|
591
|
+
})
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* Decorator to register a persistent class. Entropic Bond needs that you register
|
|
598
|
+
* all persistent classes that you want to use in any persistent stream.
|
|
599
|
+
* @param className the name of the class
|
|
600
|
+
* @param annotation an optional annotation that can be used to store additional information
|
|
601
|
+
*/
|
|
602
|
+
export function registerPersistentClass( className: string, annotation?: unknown ) {
|
|
603
|
+
return ( constructor: PersistentConstructor ) => {
|
|
604
|
+
Persistent.registerFactory( className, constructor, annotation )
|
|
605
|
+
constructor.prototype.__className = className
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Decorator to register a legacy name for a persistent class. This is useful when you want to
|
|
611
|
+
* be able to load old data that was stored with a different class name.
|
|
612
|
+
* @param legacyName the legacy name of the class
|
|
613
|
+
*/
|
|
614
|
+
export function registerLegacyClassName( legacyName: string ) {
|
|
615
|
+
return ( constructor: PersistentConstructor ) => {
|
|
616
|
+
Persistent.registerFactory( legacyName, constructor )
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Decorator to make a `Persistent` array property searchable by the
|
|
622
|
+
* persistance engine.
|
|
623
|
+
* When a property is marked as searchable, the persistance engine will
|
|
624
|
+
* generate internally a new property with the same name but with the suffix `_searchable`
|
|
625
|
+
* and prefixed with the `_` character. This new property will contain an array
|
|
626
|
+
* with the `id` of the persistent elements in the original array.
|
|
627
|
+
*/
|
|
628
|
+
export function searchableArray( target: Persistent, property: string ) {
|
|
629
|
+
return persistentParser({ searchableArray: true })( target, property )
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Decorator to mark the property as required.
|
|
634
|
+
* @see requiredWithValidator
|
|
635
|
+
*/
|
|
636
|
+
export function required( target: Persistent, property: string ) {
|
|
637
|
+
return persistentParser({ validator: ( value: any ) => value !== undefined && value !== null })( target, property )
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* Decorator to mark the property as required.
|
|
642
|
+
* @param validator a function that returns true if the property value is valid.
|
|
643
|
+
* By default, the property is valid if it is not undefined and not null.
|
|
644
|
+
* @see required
|
|
645
|
+
*/
|
|
646
|
+
export function requiredWithValidator<T extends Persistent, P extends ClassPropNames<T>>( validator: ValidatorFunction<T, P> = ( value: T[P] ) => value !== undefined && value !== null ) {
|
|
647
|
+
return function( target: T, property: UnderscoredProp<P> ) {
|
|
648
|
+
return persistentParser({ validator: validator })( target, property )
|
|
649
|
+
}
|
|
650
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { ServerAuth } from './server-auth'
|
|
2
|
+
import { ServerAuthMock } from './server-auth-mock'
|
|
3
|
+
|
|
4
|
+
describe( 'Server Auth Mock', ()=>{
|
|
5
|
+
|
|
6
|
+
beforeEach(()=>{
|
|
7
|
+
ServerAuth.useServerAuthService( new ServerAuthMock({
|
|
8
|
+
testUser1: { id: 'testUser1', email: 'testUser1@acme.com', customData: { a: 'aCustomData' }}
|
|
9
|
+
}))
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it( 'should get user credentials', async ()=>{
|
|
13
|
+
const user = await ServerAuth.instance.getUser( 'testUser1' )
|
|
14
|
+
expect( user?.email ).toEqual( 'testUser1@acme.com' )
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it( 'should set custom credentials', async ()=>{
|
|
18
|
+
await ServerAuth.instance.setCustomCredentials( 'testUser1', {
|
|
19
|
+
a: 'anotherTestCustomData',
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
const user = await ServerAuth.instance.getUser( 'testUser1' )
|
|
23
|
+
expect( user?.email ).toEqual( 'testUser1@acme.com' )
|
|
24
|
+
expect( user?.customData?.a ).toEqual( 'anotherTestCustomData' )
|
|
25
|
+
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it( 'should throw if user does not exists when setting custom claims', ()=>{
|
|
29
|
+
expect(
|
|
30
|
+
()=>ServerAuth.instance.setCustomCredentials( 'nonExistingUser', {
|
|
31
|
+
a: 'anotherTestCustomData'
|
|
32
|
+
})
|
|
33
|
+
).toThrow( /not found/ )
|
|
34
|
+
expect.assertions(1)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it( 'should update user credentials from non existing user', async ()=>{
|
|
38
|
+
await ServerAuth.instance.updateUser( 'nonExistingUser', {
|
|
39
|
+
id: 'nonExistingUser',
|
|
40
|
+
email: 'nonExistingUser@acme.com',
|
|
41
|
+
customData: { a: 'aTestCustomData' }
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
const user = await ServerAuth.instance.getUser( 'nonExistingUser' )
|
|
45
|
+
expect( user?.email ).toEqual( 'nonExistingUser@acme.com' )
|
|
46
|
+
expect( user?.customData?.a ).toEqual( 'aTestCustomData' )
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it( 'should return undefined if user does not exists', async ()=>{
|
|
50
|
+
expect( await ServerAuth.instance.getUser( 'nonExistingUser' ) ).toBeUndefined()
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
})
|