entropic-bond 1.49.0 → 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 -22
  78. package/lib/auth/auth-mock.js +0 -111
  79. package/lib/auth/auth-mock.js.map +0 -1
  80. package/lib/auth/auth.d.ts +0 -131
  81. package/lib/auth/auth.js +0 -149
  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,659 @@
1
+ import { JsonDataSource } from './json-data-source'
2
+ import { DerivedUser, SubClass, TestUser } from './mocks/test-user'
3
+ import { Model } from './model'
4
+ import { Store } from './store'
5
+ import testData from './mocks/mock-data.json'
6
+ import { DataSource } from './data-source'
7
+ import { Persistent } from '../persistent/persistent'
8
+
9
+ describe( 'Model', ()=>{
10
+ let model: Model< TestUser >
11
+ let testUser: TestUser
12
+ const rawData = ()=> ( Store.dataSource as JsonDataSource ).rawData
13
+
14
+ beforeEach( async ()=> {
15
+ Store.useDataSource( new JsonDataSource( structuredClone( testData ) ) )
16
+
17
+ testUser = new TestUser()
18
+ testUser.name = {
19
+ firstName: 'testUserFirstName',
20
+ lastName: 'testUserLastName',
21
+ ancestorName: {}
22
+ }
23
+ testUser.age = 35
24
+ testUser.skills = [ 'lazy', 'dirty' ]
25
+
26
+ model = Store.getModel<TestUser>( 'TestUser' )
27
+ })
28
+
29
+ it( 'should get model from class name string and class instance', ()=>{
30
+ expect(
31
+ Store.getModel( testUser ).collectionName
32
+ ).toEqual( model.collectionName )
33
+
34
+ expect( model.collectionName ).toEqual( 'TestUser' )
35
+ })
36
+
37
+ it( 'should find document by id', async ()=>{
38
+ const user = await model.findById( 'user1' )
39
+
40
+ expect( user ).toBeInstanceOf( TestUser )
41
+ expect( user?.id ).toEqual( 'user1' )
42
+ expect( user?.name?.firstName ).toEqual( 'userFirstName1' )
43
+ })
44
+
45
+ it( 'should not throw if a document id doesn\'t exists', ()=>{
46
+ expect( async ()=>{
47
+ await model.findById( 'nonExistingId' )
48
+ }).not.toThrow()
49
+ })
50
+
51
+ it( 'should return undefined if a document id doesn\'t exists', async ()=>{
52
+ expect( await model.findById( 'nonExistingId' ) ).toBeUndefined()
53
+ })
54
+
55
+ it( 'should return empty array if no document matches condition', async ()=>{
56
+ expect( await model.find().where( 'age', '<', 0 ).get() ).toHaveLength( 0 )
57
+ })
58
+
59
+ it( 'should return all documents if no where specified', async ()=>{
60
+ const docs = await model.find().get()
61
+ expect( docs.length ).toBeGreaterThan( 1 )
62
+ })
63
+
64
+ it( 'should write a document', async ()=>{
65
+ await model.save( testUser )
66
+
67
+ expect( rawData()[ 'TestUser' ]?.[ testUser.id ] ).toEqual( expect.objectContaining({
68
+ name: {
69
+ firstName: 'testUserFirstName',
70
+ lastName: 'testUserLastName',
71
+ ancestorName: {}
72
+ }
73
+ }))
74
+ })
75
+
76
+ it( 'should delete a document by id', async ()=>{
77
+ expect( rawData()[ 'TestUser' ]?.[ 'user1'] ).toBeDefined()
78
+ await model.delete( 'user1' )
79
+
80
+ expect( rawData()[ 'TestUser' ]?.[ 'user1' ] ).toBeUndefined()
81
+ })
82
+
83
+ describe( 'Generic find', ()=>{
84
+ it( 'should query all admins with query object', async ()=>{
85
+ const admins = await model.query({
86
+ operations: [{
87
+ property: 'admin',
88
+ operator: '==',
89
+ value: true
90
+ }]
91
+ })
92
+
93
+ expect( admins[0] ).toBeInstanceOf( TestUser )
94
+ expect( admins ).toHaveLength( 2 )
95
+ })
96
+
97
+ it( 'should query by instance', async ()=>{
98
+ expect( await model.query({}) ).toHaveLength( 6 )
99
+ expect( await model.query({}, new DerivedUser() ) ).toHaveLength( 1 )
100
+ expect( await model.query({}, 'TestUser' ) ).toHaveLength( 5 )
101
+ })
102
+
103
+ it( 'should find all admins with where methods', async ()=>{
104
+ const admins = await model.find().where( 'admin', '==', true ).get()
105
+
106
+ expect( admins[0] ).toBeInstanceOf( TestUser )
107
+ expect( admins ).toHaveLength( 2 )
108
+ })
109
+
110
+ it( 'should query by subproperties', async ()=>{
111
+ const users = await model.query({
112
+ operations: [
113
+ {
114
+ property: 'name',
115
+ operator: '==',
116
+ value: { firstName: 'userFirstName3' }
117
+ },
118
+ { property: 'age', operator: '!=', value: 134 }
119
+ ]
120
+ })
121
+
122
+ expect( users[0]?.id ).toBe( 'user3' )
123
+ })
124
+
125
+ it( 'should find by subproperties', async ()=>{
126
+ const users = await model.find()
127
+ .where( 'name', '==', { firstName: 'userFirstName3' })
128
+ .get()
129
+
130
+ expect( users[0]?.id ).toBe( 'user3' )
131
+ })
132
+
133
+ it( 'should find by property path', async ()=>{
134
+ const users = await model.find()
135
+ .whereDeepProp( 'name.firstName', '==', 'userFirstName3' )
136
+ .get()
137
+
138
+ expect( users[0]?.id ).toBe( 'user3' )
139
+ })
140
+
141
+ it( 'should find by superdeep property path', async ()=>{
142
+ const users = await model.find()
143
+ .whereDeepProp( 'name.ancestorName.father', '==', 'user3Father')
144
+ .get()
145
+
146
+ expect( users[0]?.id ).toEqual( 'user3' )
147
+ })
148
+
149
+ it( 'should find by swallow property path', async ()=>{
150
+ const users = await model.find()
151
+ .whereDeepProp( 'age', '==', 21 )
152
+ .get()
153
+
154
+ expect( users[0]?.id ).toEqual( 'user2' )
155
+ })
156
+
157
+ it( 'should return the whole collection on undefined query object', async ()=>{
158
+ const users = await model.query()
159
+
160
+ expect( users ).toHaveLength( 6 )
161
+ })
162
+
163
+ it( 'should return the whole collection on empty query object', async ()=>{
164
+ const users = await model.query({})
165
+
166
+ expect( users ).toHaveLength( 6 )
167
+ })
168
+
169
+ })
170
+
171
+ describe( 'Derived classes should fit on parent collection', ()=>{
172
+
173
+ it( 'should save derived object in parent collection', async ()=>{
174
+ const derived = new DerivedUser()
175
+ derived.name = { firstName: 'Fulanito', lastName: 'Derived', ancestorName: {} }
176
+ derived.salary = 3900
177
+
178
+ await model.save( derived )
179
+
180
+ expect( rawData()[ 'TestUser' ]?.[ derived.id ]?.[ 'salary' ] ).toBe( 3900 )
181
+ expect( rawData()[ 'TestUser' ]?.[ derived.id ]?.[ '__className' ] ).toEqual( 'DerivedUser' )
182
+ })
183
+
184
+ it( 'should retrieve derived object by id ', async ()=>{
185
+ const derived = await model.findById<DerivedUser>( 'user4' )
186
+
187
+ expect( derived ).toBeInstanceOf( DerivedUser )
188
+ expect( ( derived as DerivedUser ).salary ).toBe( 2800 )
189
+ })
190
+
191
+ it( 'should find instances of derived classes', async ()=>{
192
+ const derived = await model.find().instanceOf<DerivedUser>( 'DerivedUser' ).get()
193
+
194
+ expect( derived[0] ).toBeInstanceOf( DerivedUser )
195
+ expect( ( derived[0] as DerivedUser ).salary ).toBe( 2800 )
196
+ })
197
+
198
+ })
199
+
200
+ describe( 'References to documents', ()=>{
201
+ let ref1: SubClass, ref2: SubClass
202
+
203
+ beforeEach( async ()=>{
204
+ testUser.documentRef = new SubClass()
205
+ testUser.documentRef.year = 2045
206
+ ref1 = new SubClass(); ref1.year = 2081
207
+ ref2 = new SubClass(); ref2.year = 2082
208
+ testUser.manyRefs.push( ref1 )
209
+ testUser.manyRefs.push( ref2 )
210
+ testUser.derived = new DerivedUser()
211
+ testUser.derived.salary = 1350
212
+ testUser.manyDerived = [ new DerivedUser(), new DerivedUser() ]
213
+ testUser.manyDerived[0]!.salary = 990
214
+ testUser.manyDerived[1]!.salary = 1990
215
+ await model.save( testUser )
216
+ })
217
+
218
+ it( 'should save a document as a reference', async ()=>{
219
+ expect( rawData()[ 'SubClass' ] ).toBeDefined()
220
+ expect( rawData()[ 'SubClass' ]?.[ testUser.documentRef!.id ] ).toEqual(
221
+ expect.objectContaining({
222
+ __className: 'SubClass',
223
+ year: 2045
224
+ })
225
+ )
226
+ })
227
+
228
+ it( 'should read a swallow document reference', async ()=>{
229
+ const loadedUser = await model.findById( testUser.id )
230
+
231
+ expect( loadedUser?.documentRef ).toBeInstanceOf( SubClass )
232
+ expect( loadedUser?.documentRef?.id ).toBeDefined()
233
+ expect( loadedUser?.documentRef?.year ).toBeUndefined()
234
+ })
235
+
236
+ it( 'should fill data of swallow document reference', async ()=>{
237
+ const loadedUser = await model.findById( testUser.id )
238
+
239
+ await Store.populate( loadedUser?.documentRef! )
240
+ expect( loadedUser?.documentRef?.id ).toBeDefined()
241
+ expect( loadedUser?.documentRef?.year ).toBe( 2045 )
242
+ })
243
+
244
+ it( 'should save and array of references', ()=>{
245
+ expect( rawData()[ 'SubClass' ]?.[ ref1.id ] ).toBeDefined()
246
+ expect( rawData()[ 'SubClass' ]?.[ ref1.id ]?.[ 'year'] ).toBe( 2081 )
247
+ expect( rawData()[ 'SubClass' ]?.[ ref2.id ] ).toBeDefined()
248
+ expect( rawData()[ 'SubClass' ]?.[ ref2.id ]?.[ 'year'] ).toBe( 2082 )
249
+ })
250
+
251
+ it( 'should read an array of references', async ()=>{
252
+ const loadedUser = await model.findById( testUser.id )
253
+
254
+ expect( loadedUser?.manyRefs ).toHaveLength( 2 )
255
+ expect( loadedUser?.manyRefs[0] ).toBeInstanceOf( SubClass )
256
+ expect( loadedUser?.manyRefs[0]?.id ).toEqual( testUser.manyRefs[0]!.id )
257
+ expect( loadedUser?.manyRefs[0]?.year ).toBeUndefined()
258
+ expect( loadedUser?.manyRefs[1] ).toBeInstanceOf( SubClass )
259
+ expect( loadedUser?.manyRefs[1]?.id ).toEqual( testUser.manyRefs[1]!.id )
260
+ expect( loadedUser?.manyRefs[1]?.year ).toBeUndefined()
261
+ })
262
+
263
+ it( 'should fill array of refs', async ()=>{
264
+ const loadedUser = await model.findById( testUser.id )
265
+ await Store.populate( loadedUser?.manyRefs! )
266
+
267
+ expect( loadedUser?.manyRefs[0]?.year ).toBe( 2081 )
268
+ expect( loadedUser?.manyRefs[1]?.year ).toBe( 2082 )
269
+ })
270
+
271
+ it( 'should save a reference when declared @persistentAt', async ()=>{
272
+ const loadedUser = await model.findById( testUser.id )
273
+
274
+ expect( loadedUser?.derived?.id ).toEqual( testUser.derived!.id )
275
+ expect( loadedUser?.derived?.salary ).toBeUndefined()
276
+
277
+ await Store.populate( loadedUser?.derived! )
278
+
279
+ expect( loadedUser?.derived?.salary ).toBe( 1350 )
280
+ expect( loadedUser?.derived?.id ).toBe( testUser.derived!.id )
281
+ })
282
+
283
+ it( 'should populate from special collection when declared with @persistentRefAt', async ()=>{
284
+ const loadedUser = await model.findById( 'user6' )
285
+ await Store.populate( loadedUser?.derived! )
286
+
287
+ expect( loadedUser?.derived?.salary ).toBe( 2800 )
288
+ expect( loadedUser?.derived?.id ).toBe( 'user4' )
289
+ })
290
+
291
+ it( 'should save a reference when declared @persistentAt as array', async ()=>{
292
+ const loadedUser = await model.findById( testUser.id )
293
+
294
+ expect( loadedUser?.manyDerived[0]?.id ).toEqual( testUser.manyDerived[0]!.id )
295
+ expect( loadedUser?.manyDerived[0]?.salary ).toBeUndefined()
296
+ expect( loadedUser?.manyDerived[1]?.salary ).toBeUndefined()
297
+
298
+ await Store.populate( loadedUser?.manyDerived! )
299
+
300
+ expect( loadedUser?.manyDerived[0]?.salary ).toBe( 990 )
301
+ expect( loadedUser?.manyDerived[0]?.id ).toBe( testUser.manyDerived[0]!.id )
302
+ expect( loadedUser?.manyDerived[1]?.salary ).toBe( 1990 )
303
+ expect( loadedUser?.manyDerived[1]?.id ).toBe( testUser.manyDerived[1]!.id )
304
+ })
305
+
306
+ it( 'should not overwrite not filled ref in collection', async ()=>{
307
+ const loadedUser = await model.findById( 'user6' )
308
+ await model.save( loadedUser! )
309
+ const refInCollection = await model.findById<DerivedUser>( 'user4' )
310
+
311
+ expect( refInCollection?.salary ).toBe( 2800 )
312
+ })
313
+
314
+ it( 'should save loaded ref with assigned new instance', async ()=>{
315
+ const loadedUser = await model.findById( 'user6' )
316
+ loadedUser!.derived = new DerivedUser()
317
+ loadedUser!.derived.salary = 345
318
+ await model.save( loadedUser! )
319
+
320
+ const refInCollection = await model.findById<DerivedUser>( loadedUser!.derived.id )
321
+ expect( refInCollection?.salary ).toBe( 345 )
322
+ })
323
+
324
+ it( 'should save loaded ref with modified ref data', async ()=>{
325
+ const loadedUser = await model.findById( 'user6' )
326
+ await Store.populate( loadedUser?.derived! )
327
+ loadedUser!.derived!.salary = 1623
328
+ await model.save( loadedUser!! )
329
+
330
+ const refInCollection = await model.findById<DerivedUser>( 'user4' )
331
+ expect( refInCollection?.salary ).toBe( 1623 )
332
+ })
333
+
334
+ it( 'should find by object ref', async ()=>{
335
+ const loadedDerived = await model.findById( 'user4' )
336
+ const loadedUser = await model.find().where( 'derived', '==', loadedDerived! ).get()
337
+
338
+ expect( loadedUser[0]?.id ).toEqual( 'user6' )
339
+ })
340
+
341
+ it( 'should not throw on calling populate several times on same object', async ()=>{
342
+ const loadedUser = await model.findById( 'user6' )
343
+ await Store.populate( loadedUser?.derived! )
344
+ expect( loadedUser?.derived?.['_documentRef'] ).not.toBeDefined()
345
+ let thrown = false
346
+
347
+ try {
348
+ await Store.populate( loadedUser?.derived! )
349
+ }
350
+ catch ( err ) {
351
+ thrown = true
352
+ }
353
+ expect( thrown ).toBeFalsy()
354
+ })
355
+
356
+ it( 'should not throw on populating undefined instances', async ()=>{
357
+ const loadedUser = await model.findById( 'user6' )
358
+ loadedUser!.derived = undefined as any
359
+ let thrown = false
360
+
361
+ try {
362
+ await Store.populate( loadedUser?.derived! )
363
+ }
364
+ catch ( err ) {
365
+ thrown = true
366
+ }
367
+ expect( thrown ).toBeFalsy()
368
+ })
369
+
370
+ it( 'should not throw on populating non existing reference', async ()=>{
371
+ const loadedUser = await model.findById( 'user6' )
372
+
373
+ loadedUser!.derived![ '_id' ] = 'non-existing'
374
+ let thrown = false
375
+
376
+ try {
377
+ await Store.populate( loadedUser?.derived! )
378
+ }
379
+ catch ( err ) {
380
+ thrown = true
381
+ }
382
+ expect( thrown ).toBeFalsy()
383
+ })
384
+
385
+ it( 'should remove deleted references when populating from the returned array', async ()=>{
386
+ const loadedUser = await model.findById( testUser.id )
387
+ const deletedId = loadedUser?.manyDerived[0]?.id
388
+ await model.delete( deletedId! )
389
+
390
+ const manyDerived = await Store.populate( loadedUser?.manyDerived! )
391
+
392
+ expect( manyDerived[0]?.id ).not.toBe( deletedId )
393
+ expect( manyDerived ).toHaveLength( 1 )
394
+ })
395
+
396
+ it( 'should report if populated for single reference', async ()=>{
397
+ const loadedUser = await model.findById( 'user6' )
398
+ expect( Store.isPopulated( loadedUser?.derived! ) ).toBeFalsy()
399
+
400
+ await Store.populate( loadedUser?.derived! )
401
+ expect( Store.isPopulated( loadedUser?.derived! ) ).toBeTruthy()
402
+ })
403
+
404
+ it( 'should report if populated for multiple references', async ()=>{
405
+ const loadedUser = await model.findById( testUser.id )
406
+
407
+ expect( Store.isPopulated( loadedUser?.manyDerived! ) ).toBeFalsy()
408
+
409
+ await Store.populate( loadedUser?.manyDerived! )
410
+ expect( Store.isPopulated( loadedUser?.manyDerived! ) ).toBeTruthy()
411
+ })
412
+
413
+ })
414
+
415
+ describe( 'Operations on queries', ()=>{
416
+ it( 'should limit the result set', async ()=>{
417
+ const unlimited = await model.find().get()
418
+ const limited = await model.find().limit( 2 ).get()
419
+
420
+ expect( unlimited.length ).not.toBe( limited.length )
421
+ expect( limited ).toHaveLength( 2 )
422
+ })
423
+
424
+ it( 'should sort ascending the result set', async ()=>{
425
+ const docs = await model.find().orderBy( 'age' ).get()
426
+
427
+ expect( docs[0]?.id ).toEqual( 'user2' )
428
+ expect( docs[1]?.id ).toEqual( 'user1' )
429
+ })
430
+
431
+ it( 'should sort descending the result set', async ()=>{
432
+ const docs = await model.find().orderBy( 'age', 'desc' ).get()
433
+
434
+ expect( docs[0]?.id ).toEqual( 'user3' )
435
+ expect( docs[1]?.id ).toEqual( 'user5' )
436
+ })
437
+
438
+ it( 'should sort by deep property path', async ()=>{
439
+ const docs = await model.find().orderByDeepProp( 'name.firstName', 'desc' ).get()
440
+
441
+ expect( docs[0]?.id ).toEqual( 'user6' )
442
+ expect( docs[1]?.id ).toEqual( 'user5' )
443
+ })
444
+
445
+ it( 'should sort by swallow property path', async ()=>{
446
+ const docs = await model.find().orderByDeepProp( 'age' ).get()
447
+
448
+ expect( docs[0]?.id ).toEqual( 'user2' )
449
+ expect( docs[1]?.id ).toEqual( 'user1' )
450
+ })
451
+
452
+ it( 'should count the documents in the collection', async ()=>{
453
+ expect( await model.find().count() ).toBe( 6 )
454
+ })
455
+ })
456
+
457
+ describe( 'Compound queries', ()=>{
458
+ it( 'should find documents using `AND` compound query', async ()=>{
459
+ const admins = await model.find()
460
+ .where( 'admin', '==', true )
461
+ .where( 'age', '<', 50 )
462
+ .get()
463
+
464
+ expect( admins ).toHaveLength( 1 )
465
+ expect( admins[0]?.age ).toBeLessThan( 50 )
466
+ })
467
+
468
+ it( 'should find using `OR` query', async ()=>{
469
+ const docs = await model.find().or( 'age', '==', 23 ).or( 'age', '==', 41 ).get()
470
+
471
+ expect( docs ).toHaveLength( 2 )
472
+ expect( docs ).toEqual( expect.arrayContaining([
473
+ expect.objectContaining({ id: 'user1', age: 23 }),
474
+ expect.objectContaining({ id: 'user5', age: 41 })
475
+ ]))
476
+ })
477
+
478
+ it( 'should find combining `OR` query and `where` query', async ()=>{
479
+ const docs = await model.find().where( 'age', '>', 50 ).or( 'age', '==', 23 ).or( 'age', '==', 41 ).get()
480
+
481
+ expect( docs ).toHaveLength( 3 )
482
+ expect( docs ).toEqual( expect.arrayContaining([
483
+ expect.objectContaining({ id: 'user1', age: 23 }),
484
+ expect.objectContaining({ id: 'user5', age: 41 }),
485
+ expect.objectContaining({ id: 'user3', age: 56 })
486
+ ]))
487
+ })
488
+
489
+ it( 'should find combining `OR` query and `where` query in a range', async ()=>{
490
+ const docs = await model.find().where( 'age', '<', 22 ).or( 'age', '>', 50 ).get()
491
+
492
+ expect( docs ).toHaveLength( 2 )
493
+ expect( docs ).toEqual( expect.arrayContaining([
494
+ expect.objectContaining({ id: 'user2', age: 21 }),
495
+ expect.objectContaining({ id: 'user3', age: 56 })
496
+ ]))
497
+ })
498
+
499
+ it( 'should throw if a `where` query is used after an `or` query', ()=>{
500
+ expect(
501
+ ()=> model.find().or( 'age', '==', 23 ).where( 'age', '>', 50 )
502
+ ).toThrow( Model.error.invalidQueryOrder )
503
+ })
504
+
505
+ it( 'should evaluate mixing operands', async ()=>{
506
+ const docs = await model.find().where( 'age', '>', 39 ).and( 'age', '<', 57 ).or( 'age', '==', 23 ).or( 'age', '==', 21 ).get()
507
+ expect( docs ).toHaveLength( 5 )
508
+ expect( docs ).toEqual( expect.arrayContaining([
509
+ expect.objectContaining({ id: 'user1', age: 23 }),
510
+ expect.objectContaining({ id: 'user2', age: 21 }),
511
+ expect.objectContaining({ id: 'user3', age: 56 }),
512
+ expect.objectContaining({ id: 'user5', age: 41 }),
513
+ expect.objectContaining({ id: 'user6', age: 40 })
514
+ ]))
515
+
516
+ const docs1 = await model.find().where( 'age', '==', 41 ).and( 'age', '==', 56 ).or( 'age', '==', 23 ).or( 'age', '==', 21 ).get()
517
+ expect( docs1 ).toHaveLength( 2 )
518
+ expect( docs1 ).toEqual( expect.arrayContaining([
519
+ expect.objectContaining({ id: 'user1', age: 23 }),
520
+ expect.objectContaining({ id: 'user2', age: 21 })
521
+ ]))
522
+
523
+ const docs2 = await model.find().where( 'age', '==', 41 ).or( 'age', '==', 56 ).or( 'age', '==', 23 ).or( 'age', '==', 21 ).get()
524
+ expect( docs2 ).toHaveLength( 4 )
525
+ expect( docs2 ).toEqual( expect.arrayContaining([
526
+ expect.objectContaining({ id: 'user1', age: 23 }),
527
+ expect.objectContaining({ id: 'user2', age: 21 }),
528
+ expect.objectContaining({ id: 'user3', age: 56 }),
529
+ expect.objectContaining({ id: 'user5', age: 41 })
530
+ ]))
531
+ })
532
+ })
533
+
534
+ describe( 'Searchable array property', ()=>{
535
+ it( 'should save searchable array property', async ()=>{
536
+ const user = new TestUser( 'user7' )
537
+ user.colleagues = [ new TestUser( 'cUser1' ), new TestUser( 'cUser2' ) ]
538
+ await model.save( user )
539
+
540
+ const loadedUser = await model.findById( 'user7' )
541
+ expect( loadedUser?.colleagues ).toHaveLength( 2 )
542
+ expect( loadedUser![ Persistent.searchableArrayNameFor( 'colleagues' )] ).toBeUndefined()
543
+ const rawUserSearchableContent = rawData()[ 'TestUser' ]!['user7']![Persistent.searchableArrayNameFor( 'colleagues' )]
544
+ expect( rawUserSearchableContent ).toEqual([ 'cUser1', 'cUser2' ])
545
+ })
546
+
547
+ it( 'should find documents using `containsAny` operator', async ()=>{
548
+ const colleague1 = new TestUser( 'colleague1' )
549
+ const colleague2 = new TestUser( 'colleague2' )
550
+ const docs = await model.find().where( 'colleagues', 'containsAny', [ colleague1, colleague2 ]).get()
551
+
552
+ expect( docs ).toHaveLength( 3 )
553
+ expect( docs ).toEqual([
554
+ expect.objectContaining({ id: 'user2' }),
555
+ expect.objectContaining({ id: 'user4' }),
556
+ expect.objectContaining({ id: 'user6' })
557
+ ])
558
+ })
559
+
560
+ it( 'should find documents using `contains` operator', async ()=>{
561
+ const colleague2 = new TestUser( 'colleague2' )
562
+ const docs = await model.find().where( 'colleagues', 'contains', colleague2 ).get()
563
+
564
+ expect( docs ).toHaveLength( 2 )
565
+ expect( docs ).toEqual([
566
+ expect.objectContaining({ id: 'user4' }),
567
+ expect.objectContaining({ id: 'user6' })
568
+ ])
569
+ })
570
+ })
571
+
572
+ describe( 'Data Cursors', ()=>{
573
+ beforeEach( async ()=>{
574
+ await model.find().get( 2 )
575
+ })
576
+
577
+ it( 'should get next result set', async ()=>{
578
+ const docs = await model.next()
579
+ expect( docs ).toHaveLength( 2 )
580
+ expect( docs[0]?.id ).toEqual( 'user3' )
581
+ })
582
+
583
+ it( 'should not go beyond the end of result set', async ()=>{
584
+ await model.next()
585
+ await model.next()
586
+ const docs = await model.next()
587
+ expect( docs ).toHaveLength( 0 )
588
+ })
589
+ })
590
+
591
+ describe( 'Utility methods', ()=>{
592
+
593
+ it( 'should transform query object operations to property path', ()=>{
594
+ const operations = DataSource.toPropertyPathOperations<TestUser>([
595
+ {
596
+ property: 'name',
597
+ operator: '==',
598
+ value: { ancestorName: { father: 'Felipe' }}
599
+ },
600
+ {
601
+ property: 'age',
602
+ operator: '==',
603
+ value: 23
604
+ }
605
+ ])
606
+
607
+ expect( operations[0] ).toEqual({
608
+ property: 'name.ancestorName.father',
609
+ operator: '==',
610
+ value: 'Felipe'
611
+ })
612
+
613
+ expect( operations[1] ).toEqual({
614
+ property: 'age',
615
+ operator: '==',
616
+ value: 23
617
+ })
618
+ })
619
+
620
+ })
621
+
622
+ describe( 'SubCollections', ()=>{
623
+ let subCollectionModel: Model<SubClass>
624
+
625
+ beforeEach(async ()=>{
626
+ const user = await model.findById( 'user1' )
627
+ subCollectionModel = Store.getModelForSubCollection<SubClass>( user!, 'SubClass' )
628
+ })
629
+
630
+ it( 'should get model for subCollection', ()=>{
631
+ const model = Store.getModelForSubCollection<SubClass>( testUser, 'SubClass' )
632
+ expect( model.collectionName ).toEqual( `TestUser/${ testUser.id }/SubClass` )
633
+ })
634
+
635
+ it( 'should find subCollection document by id', async ()=>{
636
+ const subClass = await subCollectionModel.findById( 'subClass1' )
637
+
638
+ expect( subClass ).toBeInstanceOf( SubClass )
639
+ expect( subClass?.year ).toBe( 1326 )
640
+ })
641
+
642
+ it( 'should save data to subCollection', async ()=>{
643
+ const subClass = new SubClass()
644
+ subClass.year = 3452
645
+
646
+ await subCollectionModel.save( subClass )
647
+ const loaded = await subCollectionModel.findById( subClass.id )
648
+
649
+ expect( loaded?.year ).toBe( 3452 )
650
+ })
651
+
652
+ })
653
+
654
+ it('should pass Type tests', ()=>{
655
+ //@ts-expect-error
656
+ ()=>model.find().whereDeepProp( 'not-prop', '==', 'userFirstName3' )
657
+ expect( true ).toBeTruthy()
658
+ })
659
+ })