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.
Files changed (141) hide show
  1. package/.github/workflows/release.yml +26 -0
  2. package/CHANGELOG.md +1151 -0
  3. package/docs/.nojekyll +1 -0
  4. package/docs/README.md +94 -0
  5. package/docs/classes/Auth.md +391 -0
  6. package/docs/classes/AuthMock.md +278 -0
  7. package/docs/classes/AuthService.md +188 -0
  8. package/docs/classes/CloudFunctions.md +123 -0
  9. package/docs/classes/CloudFunctionsMock.md +97 -0
  10. package/docs/classes/CloudStorage.md +215 -0
  11. package/docs/classes/DataSource.md +248 -0
  12. package/docs/classes/EntropicComponent.md +666 -0
  13. package/docs/classes/JsonDataSource.md +328 -0
  14. package/docs/classes/MockCloudStorage.md +279 -0
  15. package/docs/classes/Model.md +274 -0
  16. package/docs/classes/Observable.md +120 -0
  17. package/docs/classes/Persistent.md +420 -0
  18. package/docs/classes/ServerAuth.md +211 -0
  19. package/docs/classes/ServerAuthMock.md +176 -0
  20. package/docs/classes/ServerAuthService.md +130 -0
  21. package/docs/classes/Store.md +218 -0
  22. package/docs/classes/StoredFile.md +636 -0
  23. package/docs/enums/StoredFileEvent.md +41 -0
  24. package/docs/interfaces/AuthError.md +30 -0
  25. package/docs/interfaces/CloudFunctionsService.md +69 -0
  26. package/docs/interfaces/Collection.md +13 -0
  27. package/docs/interfaces/CustomCredentials.md +7 -0
  28. package/docs/interfaces/DocumentReference.md +49 -0
  29. package/docs/interfaces/JsonRawData.md +7 -0
  30. package/docs/interfaces/SignData.md +63 -0
  31. package/docs/interfaces/StoreParams.md +52 -0
  32. package/docs/interfaces/StoredFileChange.md +41 -0
  33. package/docs/interfaces/UploadControl.md +90 -0
  34. package/docs/interfaces/UserCredentials.md +113 -0
  35. package/docs/interfaces/Values.md +17 -0
  36. package/docs/modules.md +1273 -0
  37. package/package.json +23 -19
  38. package/src/auth/auth-mock.spec.ts +168 -0
  39. package/src/auth/auth-mock.ts +129 -0
  40. package/src/auth/auth.ts +185 -0
  41. package/src/auth/user-auth-types.ts +21 -0
  42. package/src/cloud-functions/cloud-functions-mock.spec.ts +136 -0
  43. package/src/cloud-functions/cloud-functions-mock.ts +23 -0
  44. package/src/cloud-functions/cloud-functions.ts +83 -0
  45. package/src/cloud-storage/cloud-storage.spec.ts +207 -0
  46. package/src/cloud-storage/cloud-storage.ts +60 -0
  47. package/src/cloud-storage/mock-cloud-storage.ts +72 -0
  48. package/src/cloud-storage/stored-file.ts +102 -0
  49. package/src/index.ts +19 -0
  50. package/src/observable/observable.spec.ts +105 -0
  51. package/src/observable/observable.ts +67 -0
  52. package/src/persistent/entropic-component.spec.ts +143 -0
  53. package/src/persistent/entropic-component.ts +135 -0
  54. package/src/persistent/persistent.spec.ts +828 -0
  55. package/src/persistent/persistent.ts +650 -0
  56. package/src/server-auth/server-auth-mock.spec.ts +53 -0
  57. package/src/server-auth/server-auth-mock.ts +45 -0
  58. package/src/server-auth/server-auth.ts +49 -0
  59. package/src/store/data-source.ts +186 -0
  60. package/src/store/json-data-source.spec.ts +100 -0
  61. package/src/store/json-data-source.ts +256 -0
  62. package/src/store/mocks/mock-data.json +155 -0
  63. package/src/store/mocks/test-user.ts +122 -0
  64. package/src/store/model.spec.ts +659 -0
  65. package/src/store/model.ts +462 -0
  66. package/src/store/store.spec.ts +30 -0
  67. package/src/store/store.ts +113 -0
  68. package/src/types/utility-types.spec.ts +117 -0
  69. package/src/types/utility-types.ts +116 -0
  70. package/src/utils/test-utils/test-person.ts +44 -0
  71. package/src/utils/utils.spec.ts +95 -0
  72. package/{lib/utils/utils.d.ts → src/utils/utils.ts} +34 -10
  73. package/tsconfig-build.json +7 -0
  74. package/tsconfig-cjs.json +9 -0
  75. package/tsconfig.json +33 -0
  76. package/vite.config.ts +22 -0
  77. package/lib/auth/auth-mock.d.ts +0 -21
  78. package/lib/auth/auth-mock.js +0 -108
  79. package/lib/auth/auth-mock.js.map +0 -1
  80. package/lib/auth/auth.d.ts +0 -129
  81. package/lib/auth/auth.js +0 -146
  82. package/lib/auth/auth.js.map +0 -1
  83. package/lib/auth/user-auth-types.d.ts +0 -19
  84. package/lib/auth/user-auth-types.js +0 -3
  85. package/lib/auth/user-auth-types.js.map +0 -1
  86. package/lib/cloud-functions/cloud-functions-mock.d.ts +0 -11
  87. package/lib/cloud-functions/cloud-functions-mock.js +0 -19
  88. package/lib/cloud-functions/cloud-functions-mock.js.map +0 -1
  89. package/lib/cloud-functions/cloud-functions.d.ts +0 -19
  90. package/lib/cloud-functions/cloud-functions.js +0 -64
  91. package/lib/cloud-functions/cloud-functions.js.map +0 -1
  92. package/lib/cloud-storage/cloud-storage.d.ts +0 -24
  93. package/lib/cloud-storage/cloud-storage.js +0 -37
  94. package/lib/cloud-storage/cloud-storage.js.map +0 -1
  95. package/lib/cloud-storage/mock-cloud-storage.d.ts +0 -20
  96. package/lib/cloud-storage/mock-cloud-storage.js +0 -68
  97. package/lib/cloud-storage/mock-cloud-storage.js.map +0 -1
  98. package/lib/cloud-storage/stored-file.d.ts +0 -39
  99. package/lib/cloud-storage/stored-file.js +0 -106
  100. package/lib/cloud-storage/stored-file.js.map +0 -1
  101. package/lib/index.d.ts +0 -19
  102. package/lib/index.js +0 -36
  103. package/lib/index.js.map +0 -1
  104. package/lib/observable/observable.d.ts +0 -52
  105. package/lib/observable/observable.js +0 -66
  106. package/lib/observable/observable.js.map +0 -1
  107. package/lib/persistent/entropic-component.d.ts +0 -76
  108. package/lib/persistent/entropic-component.js +0 -109
  109. package/lib/persistent/entropic-component.js.map +0 -1
  110. package/lib/persistent/persistent.d.ts +0 -281
  111. package/lib/persistent/persistent.js +0 -539
  112. package/lib/persistent/persistent.js.map +0 -1
  113. package/lib/server-auth/server-auth-mock.d.ts +0 -12
  114. package/lib/server-auth/server-auth-mock.js +0 -39
  115. package/lib/server-auth/server-auth-mock.js.map +0 -1
  116. package/lib/server-auth/server-auth.d.ts +0 -24
  117. package/lib/server-auth/server-auth.js +0 -36
  118. package/lib/server-auth/server-auth.js.map +0 -1
  119. package/lib/store/data-source.d.ts +0 -137
  120. package/lib/store/data-source.js +0 -62
  121. package/lib/store/data-source.js.map +0 -1
  122. package/lib/store/json-data-source.d.ts +0 -68
  123. package/lib/store/json-data-source.js +0 -199
  124. package/lib/store/json-data-source.js.map +0 -1
  125. package/lib/store/mocks/test-user.d.ts +0 -49
  126. package/lib/store/mocks/test-user.js +0 -135
  127. package/lib/store/mocks/test-user.js.map +0 -1
  128. package/lib/store/model.d.ts +0 -238
  129. package/lib/store/model.js +0 -417
  130. package/lib/store/model.js.map +0 -1
  131. package/lib/store/store.d.ts +0 -62
  132. package/lib/store/store.js +0 -102
  133. package/lib/store/store.js.map +0 -1
  134. package/lib/types/utility-types.d.ts +0 -45
  135. package/lib/types/utility-types.js +0 -3
  136. package/lib/types/utility-types.js.map +0 -1
  137. package/lib/utils/test-utils/test-person.d.ts +0 -33
  138. package/lib/utils/test-utils/test-person.js +0 -25
  139. package/lib/utils/test-utils/test-person.js.map +0 -1
  140. package/lib/utils/utils.js +0 -76
  141. 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
+ })