@declaro/data 2.0.0-beta.95 → 2.0.0-beta.97

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.
@@ -7,6 +7,7 @@ export interface IMockMemoryRepositoryArgs<TSchema extends AnyModelSchema> {
7
7
  schema: TSchema;
8
8
  lookup?: (data: InferDetail<TSchema>, lookup: InferLookup<TSchema>) => boolean;
9
9
  filter?: (data: InferSummary<TSchema>, filters: InferFilters<TSchema>) => boolean;
10
+ assign?: (data: InferDetail<TSchema>, input: InferInput<TSchema>) => InferDetail<TSchema>;
10
11
  }
11
12
  export declare class MockMemoryRepository<TSchema extends AnyModelSchema> implements IRepository<TSchema> {
12
13
  protected args: IMockMemoryRepositoryArgs<TSchema>;
@@ -31,6 +32,13 @@ export declare class MockMemoryRepository<TSchema extends AnyModelSchema> implem
31
32
  * @returns Filtered array of items
32
33
  */
33
34
  protected applyFilters(input: InferFilters<TSchema>): InferDetail<TSchema>[];
35
+ /**
36
+ * Assign input data to existing data using the provided assign function or default Object.assign
37
+ * @param existingData - The existing data to merge with
38
+ * @param input - The input data to assign
39
+ * @returns The merged data
40
+ */
41
+ protected assignInput(existingData: InferDetail<TSchema>, input: InferInput<TSchema>): InferDetail<TSchema>;
34
42
  protected generatePrimaryKey(): Promise<string | number>;
35
43
  }
36
44
  //# sourceMappingURL=mock-memory-repository.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mock-memory-repository.d.ts","sourceRoot":"","sources":["../../../../../src/test/mock/repositories/mock-memory-repository.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mock-memory-repository.d.ts","sourceRoot":"","sources":["../../../../../src/test/mock/repositories/mock-memory-repository.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@declaro/data",
3
- "version": "2.0.0-beta.95",
3
+ "version": "2.0.0-beta.97",
4
4
  "description": "A data-mapper framework for managing application data across integrated systems.",
5
5
  "main": "dist/node/index.cjs",
6
6
  "module": "dist/node/index.js",
@@ -22,9 +22,9 @@
22
22
  "@declaro/zod": "^2.0.0-beta.51"
23
23
  },
24
24
  "devDependencies": {
25
- "@declaro/auth": "^2.0.0-beta.95",
26
- "@declaro/core": "^2.0.0-beta.95",
27
- "@declaro/zod": "^2.0.0-beta.95",
25
+ "@declaro/auth": "^2.0.0-beta.97",
26
+ "@declaro/core": "^2.0.0-beta.97",
27
+ "@declaro/zod": "^2.0.0-beta.97",
28
28
  "crypto-browserify": "^3.12.1",
29
29
  "typescript": "^5.8.3",
30
30
  "uuid": "^11.1.0",
@@ -43,5 +43,5 @@
43
43
  "require": "./dist/node/index.cjs",
44
44
  "browser": "./dist/browser/index.js"
45
45
  },
46
- "gitHead": "9b00e9de1fd859c26610126a123636e5211502a6"
46
+ "gitHead": "f5735a57bfd6cc342c60e9974395c389f43c3f93"
47
47
  }
@@ -23,18 +23,21 @@ describe('ModelController', () => {
23
23
  getMockAuthSession({
24
24
  claims: ['books::book.write:all'],
25
25
  }),
26
+ null,
26
27
  authService,
27
28
  )
28
29
  invalidAuthValidator = new AuthValidator(
29
30
  getMockAuthSession({
30
31
  claims: ['authors::author.write:all'],
31
32
  }),
33
+ null,
32
34
  authService,
33
35
  )
34
36
  readAuthValidator = new AuthValidator(
35
37
  getMockAuthSession({
36
38
  claims: ['books::book.read:all'],
37
39
  }),
40
+ null,
38
41
  authService,
39
42
  )
40
43
  service = new ModelService({
@@ -274,6 +277,7 @@ describe('ModelController', () => {
274
277
  getMockAuthSession({
275
278
  claims: ['books::book.create:all', 'books::book.update:all'],
276
279
  }),
280
+ null,
277
281
  authService,
278
282
  )
279
283
  const controller = new ModelController(service, createUpdateValidator)
@@ -289,6 +293,7 @@ describe('ModelController', () => {
289
293
  getMockAuthSession({
290
294
  claims: ['books::book.create:all', 'books::book.update:all'],
291
295
  }),
296
+ null,
292
297
  authService,
293
298
  )
294
299
  const controller = new ModelController(service, createUpdateValidator)
@@ -308,6 +313,7 @@ describe('ModelController', () => {
308
313
  getMockAuthSession({
309
314
  claims: ['books::book.create:all'],
310
315
  }),
316
+ null,
311
317
  authService,
312
318
  )
313
319
  const controller = new ModelController(service, createOnlyValidator)
@@ -322,6 +328,7 @@ describe('ModelController', () => {
322
328
  getMockAuthSession({
323
329
  claims: ['books::book.update:all'],
324
330
  }),
331
+ null,
325
332
  authService,
326
333
  )
327
334
  const controller = new ModelController(service, updateOnlyValidator)
@@ -336,6 +343,7 @@ describe('ModelController', () => {
336
343
  getMockAuthSession({
337
344
  claims: ['books::book.create:all'],
338
345
  }),
346
+ null,
339
347
  authService,
340
348
  )
341
349
  const controller = new ModelController(service, createOnlyValidator)
@@ -353,6 +361,7 @@ describe('ModelController', () => {
353
361
  getMockAuthSession({
354
362
  claims: ['books::book.update:all'],
355
363
  }),
364
+ null,
356
365
  authService,
357
366
  )
358
367
  const controller = new ModelController(service, updateOnlyValidator)
@@ -390,6 +399,7 @@ describe('ModelController', () => {
390
399
  getMockAuthSession({
391
400
  claims: ['books::book.create:all'],
392
401
  }),
402
+ null,
393
403
  authService,
394
404
  )
395
405
  const controller = new ModelController(service, createOnlyValidator)
@@ -405,6 +415,7 @@ describe('ModelController', () => {
405
415
  getMockAuthSession({
406
416
  claims: ['books::book.update:all'],
407
417
  }),
418
+ null,
408
419
  authService,
409
420
  )
410
421
  const controller = new ModelController(service, updateOnlyValidator)
@@ -423,6 +434,7 @@ describe('ModelController', () => {
423
434
  getMockAuthSession({
424
435
  claims: ['books::book.remove:all'],
425
436
  }),
437
+ null,
426
438
  authService,
427
439
  )
428
440
  const controller = new ModelController(service, removeOnlyValidator)
@@ -440,6 +452,7 @@ describe('ModelController', () => {
440
452
  getMockAuthSession({
441
453
  claims: ['books::book.restore:all'],
442
454
  }),
455
+ null,
443
456
  authService,
444
457
  )
445
458
  const controller = new ModelController(service, restoreOnlyValidator)
@@ -458,6 +471,7 @@ describe('ModelController', () => {
458
471
  getMockAuthSession({
459
472
  claims: ['users::user.write:all'], // Wrong namespace
460
473
  }),
474
+ null,
461
475
  authService,
462
476
  )
463
477
  const controller = new ModelController(service, wrongNamespaceValidator)
@@ -474,6 +488,7 @@ describe('ModelController', () => {
474
488
  getMockAuthSession({
475
489
  claims: ['books::author.write:all'], // Wrong resource
476
490
  }),
491
+ null,
477
492
  authService,
478
493
  )
479
494
  const controller = new ModelController(service, wrongResourceValidator)
@@ -22,12 +22,14 @@ describe('ReadOnlyModelController', () => {
22
22
  getMockAuthSession({
23
23
  claims: ['books::book.read:all'],
24
24
  }),
25
+ null,
25
26
  authService,
26
27
  )
27
28
  invalidAuthValidator = new AuthValidator(
28
29
  getMockAuthSession({
29
30
  claims: ['authors::author.read:all'],
30
31
  }),
32
+ null,
31
33
  authService,
32
34
  )
33
35
  service = new ReadOnlyModelService({
@@ -203,6 +205,7 @@ describe('ReadOnlyModelController', () => {
203
205
  getMockAuthSession({
204
206
  claims: ['books::book.load:all'],
205
207
  }),
208
+ null,
206
209
  authService,
207
210
  )
208
211
  const controller = new ReadOnlyModelController(service, loadOnlyValidator)
@@ -220,6 +223,7 @@ describe('ReadOnlyModelController', () => {
220
223
  getMockAuthSession({
221
224
  claims: ['books::book.loadMany:all'],
222
225
  }),
226
+ null,
223
227
  authService,
224
228
  )
225
229
  const controller = new ReadOnlyModelController(service, loadManyOnlyValidator)
@@ -239,6 +243,7 @@ describe('ReadOnlyModelController', () => {
239
243
  getMockAuthSession({
240
244
  claims: ['books::book.search:all'],
241
245
  }),
246
+ null,
242
247
  authService,
243
248
  )
244
249
  const controller = new ReadOnlyModelController(service, searchOnlyValidator)
@@ -259,6 +264,7 @@ describe('ReadOnlyModelController', () => {
259
264
  getMockAuthSession({
260
265
  claims: ['books::book.count:all'],
261
266
  }),
267
+ null,
262
268
  authService,
263
269
  )
264
270
  const controller = new ReadOnlyModelController(service, countOnlyValidator)
@@ -278,6 +284,7 @@ describe('ReadOnlyModelController', () => {
278
284
  getMockAuthSession({
279
285
  claims: ['users::user.read:all'], // Wrong namespace
280
286
  }),
287
+ null,
281
288
  authService,
282
289
  )
283
290
  const controller = new ReadOnlyModelController(service, wrongNamespaceValidator)
@@ -293,6 +300,7 @@ describe('ReadOnlyModelController', () => {
293
300
  getMockAuthSession({
294
301
  claims: ['books::author.read:all'], // Wrong resource
295
302
  }),
303
+ null,
296
304
  authService,
297
305
  )
298
306
  const controller = new ReadOnlyModelController(service, wrongResourceValidator)
@@ -712,4 +712,208 @@ describe('MockMemoryRepository', () => {
712
712
  expect(await repository.load({ id: 100 })).toEqual(inputs[99])
713
713
  })
714
714
  })
715
+
716
+ describe('assign functionality', () => {
717
+ it('should use default Object.assign for create when no custom assign function is provided', async () => {
718
+ const input = { title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
719
+ const createdItem = await repository.create(input)
720
+
721
+ expect(createdItem).toMatchObject(input)
722
+ expect(createdItem.id).toBeDefined()
723
+ })
724
+
725
+ it('should use default Object.assign for update when no custom assign function is provided', async () => {
726
+ const input = { id: 42, title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
727
+ const createdItem = await repository.create(input)
728
+
729
+ const updateInput = { title: 'Updated Book', author: 'Updated Author', publishedDate: new Date() }
730
+ const updatedItem = await repository.update({ id: createdItem.id }, updateInput)
731
+
732
+ expect(updatedItem).toEqual({
733
+ id: createdItem.id,
734
+ title: 'Updated Book',
735
+ author: 'Updated Author',
736
+ publishedDate: updateInput.publishedDate,
737
+ })
738
+ })
739
+
740
+ it('should use default Object.assign for upsert when no custom assign function is provided', async () => {
741
+ const input = { id: 42, title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
742
+ const upsertedItem = await repository.upsert(input)
743
+
744
+ expect(upsertedItem).toEqual(input)
745
+
746
+ const updateInput = { id: 42, title: 'Updated Book', author: 'Updated Author', publishedDate: new Date() }
747
+ const updatedItem = await repository.upsert(updateInput)
748
+
749
+ expect(updatedItem).toEqual(updateInput)
750
+ })
751
+
752
+ it('should use custom assign function for create operation', async () => {
753
+ const customAssignMock = mock((existing: any, input: any) => ({
754
+ ...existing,
755
+ ...input,
756
+ customField: 'custom_create_value',
757
+ }))
758
+
759
+ const customRepository = new MockMemoryRepository({
760
+ schema: mockSchema,
761
+ assign: customAssignMock,
762
+ })
763
+
764
+ const input = { title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
765
+ const createdItem = await customRepository.create(input)
766
+
767
+ expect(customAssignMock).toHaveBeenCalledWith({}, input)
768
+ expect((createdItem as any).customField).toBe('custom_create_value')
769
+ expect(createdItem).toMatchObject(input)
770
+ })
771
+
772
+ it('should use custom assign function for update operation', async () => {
773
+ const customAssignMock = mock((existing: any, input: any) => ({
774
+ ...existing,
775
+ ...input,
776
+ customField: 'custom_update_value',
777
+ lastModified: new Date('2023-01-01'),
778
+ }))
779
+
780
+ const customRepository = new MockMemoryRepository({
781
+ schema: mockSchema,
782
+ assign: customAssignMock,
783
+ })
784
+
785
+ const input = { id: 42, title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
786
+ const createdItem = await customRepository.create(input)
787
+
788
+ const updateInput = { title: 'Updated Book', author: 'Updated Author', publishedDate: new Date() }
789
+ const updatedItem = await customRepository.update({ id: createdItem.id }, updateInput)
790
+
791
+ expect(customAssignMock).toHaveBeenCalledWith(createdItem, updateInput)
792
+ expect((updatedItem as any).customField).toBe('custom_update_value')
793
+ expect((updatedItem as any).lastModified).toEqual(new Date('2023-01-01'))
794
+ expect(updatedItem.title).toBe('Updated Book')
795
+ })
796
+
797
+ it('should use custom assign function for upsert operation on existing item', async () => {
798
+ const customAssignMock = mock((existing: any, input: any) => ({
799
+ ...existing,
800
+ ...input,
801
+ mergeTimestamp: new Date('2023-01-01'),
802
+ isUpserted: true,
803
+ }))
804
+
805
+ const customRepository = new MockMemoryRepository({
806
+ schema: mockSchema,
807
+ assign: customAssignMock,
808
+ })
809
+
810
+ const input = { id: 42, title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
811
+ const createdItem = await customRepository.create(input)
812
+
813
+ const upsertInput = { id: 42, title: 'Upserted Book', author: 'Upserted Author', publishedDate: new Date() }
814
+ const upsertedItem = await customRepository.upsert(upsertInput)
815
+
816
+ // Should have been called twice: once for create, once for upsert
817
+ expect(customAssignMock).toHaveBeenCalledTimes(2)
818
+ expect(customAssignMock).toHaveBeenLastCalledWith(createdItem, upsertInput)
819
+ expect((upsertedItem as any).mergeTimestamp).toEqual(new Date('2023-01-01'))
820
+ expect((upsertedItem as any).isUpserted).toBe(true)
821
+ expect(upsertedItem.title).toBe('Upserted Book')
822
+ })
823
+
824
+ it('should use custom assign function for upsert operation on new item', async () => {
825
+ const customAssignMock = mock((existing: any, input: any) => ({
826
+ ...existing,
827
+ ...input,
828
+ createdViaUpsert: true,
829
+ }))
830
+
831
+ const customRepository = new MockMemoryRepository({
832
+ schema: mockSchema,
833
+ assign: customAssignMock,
834
+ })
835
+
836
+ const input = { id: 42, title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
837
+ const upsertedItem = await customRepository.upsert(input)
838
+
839
+ expect(customAssignMock).toHaveBeenCalledWith({}, input)
840
+ expect((upsertedItem as any).createdViaUpsert).toBe(true)
841
+ expect(upsertedItem).toMatchObject(input)
842
+ })
843
+
844
+ it('should use custom assign function for bulkUpsert operation', async () => {
845
+ const customAssignMock = mock((existing: any, input: any) => ({
846
+ ...existing,
847
+ ...input,
848
+ bulkProcessed: true,
849
+ }))
850
+
851
+ const customRepository = new MockMemoryRepository({
852
+ schema: mockSchema,
853
+ assign: customAssignMock,
854
+ })
855
+
856
+ const inputs = [
857
+ { id: 1, title: 'Book 1', author: 'Author 1', publishedDate: new Date() },
858
+ { id: 2, title: 'Book 2', author: 'Author 2', publishedDate: new Date() },
859
+ ]
860
+
861
+ const results = await customRepository.bulkUpsert(inputs)
862
+
863
+ expect(customAssignMock).toHaveBeenCalledTimes(2)
864
+ expect((results[0] as any).bulkProcessed).toBe(true)
865
+ expect((results[1] as any).bulkProcessed).toBe(true)
866
+ expect(results[0]).toMatchObject(inputs[0])
867
+ expect(results[1]).toMatchObject(inputs[1])
868
+ })
869
+
870
+ it('should handle complex custom assign logic with conditional merging', async () => {
871
+ const customAssign = (existing: any, input: any) => {
872
+ const result = { ...existing, ...input }
873
+
874
+ // Custom logic: preserve original author if input doesn't have one
875
+ if (!input.author && existing.author) {
876
+ result.author = existing.author
877
+ }
878
+
879
+ // Custom logic: track modification count
880
+ result.modificationCount = (existing.modificationCount || 0) + 1
881
+
882
+ return result
883
+ }
884
+
885
+ const customRepository = new MockMemoryRepository({
886
+ schema: mockSchema,
887
+ assign: customAssign,
888
+ })
889
+
890
+ const input = { id: 42, title: 'Test Book', author: 'Original Author', publishedDate: new Date() }
891
+ const createdItem = await customRepository.create(input)
892
+
893
+ expect((createdItem as any).modificationCount).toBe(1)
894
+
895
+ // Update with partial data - use existing values for required fields
896
+ const updateInput = {
897
+ title: 'Updated Book',
898
+ author: createdItem.author,
899
+ publishedDate: createdItem.publishedDate,
900
+ }
901
+ const updatedItem = await customRepository.update({ id: createdItem.id }, updateInput)
902
+
903
+ expect(updatedItem.author).toBe('Original Author')
904
+ expect(updatedItem.title).toBe('Updated Book')
905
+ expect((updatedItem as any).modificationCount).toBe(2)
906
+
907
+ // Update with new author
908
+ const updateWithAuthor = {
909
+ title: updatedItem.title,
910
+ author: 'New Author',
911
+ publishedDate: updatedItem.publishedDate,
912
+ }
913
+ const finalItem = await customRepository.update({ id: createdItem.id }, updateWithAuthor)
914
+
915
+ expect(finalItem.author).toBe('New Author')
916
+ expect((finalItem as any).modificationCount).toBe(3)
917
+ })
918
+ })
715
919
  })
@@ -17,6 +17,7 @@ export interface IMockMemoryRepositoryArgs<TSchema extends AnyModelSchema> {
17
17
  schema: TSchema
18
18
  lookup?: (data: InferDetail<TSchema>, lookup: InferLookup<TSchema>) => boolean
19
19
  filter?: (data: InferSummary<TSchema>, filters: InferFilters<TSchema>) => boolean
20
+ assign?: (data: InferDetail<TSchema>, input: InferInput<TSchema>) => InferDetail<TSchema>
20
21
  }
21
22
 
22
23
  export class MockMemoryRepository<TSchema extends AnyModelSchema> implements IRepository<TSchema> {
@@ -140,17 +141,16 @@ export class MockMemoryRepository<TSchema extends AnyModelSchema> implements IRe
140
141
  throw new Error('Item with the same primary key already exists')
141
142
  }
142
143
 
143
- const payload = {
144
- ...(input as any),
145
- }
144
+ const baseData = {} as InferDetail<TSchema>
145
+ const payload = this.assignInput(baseData, input)
146
146
 
147
147
  if (!payload[this.entityMetadata.primaryKey]) {
148
148
  // Generate a new primary key if not provided
149
149
  payload[this.entityMetadata.primaryKey!] = await this.generatePrimaryKey()
150
150
  }
151
151
 
152
- this.data.set(payload[this.entityMetadata.primaryKey!], payload as InferDetail<TSchema>)
153
- return payload as InferDetail<TSchema>
152
+ this.data.set(payload[this.entityMetadata.primaryKey!], payload)
153
+ return payload
154
154
  }
155
155
 
156
156
  async update(lookup: InferLookup<TSchema>, input: InferInput<TSchema>): Promise<InferDetail<TSchema>> {
@@ -167,7 +167,7 @@ export class MockMemoryRepository<TSchema extends AnyModelSchema> implements IRe
167
167
  throw new Error('Item not found')
168
168
  }
169
169
 
170
- const updatedItem = Object.assign({}, existingItem, input) as InferDetail<TSchema>
170
+ const updatedItem = this.assignInput(existingItem, input)
171
171
  this.data.set(primaryKeyValue, updatedItem)
172
172
  return updatedItem
173
173
  }
@@ -179,13 +179,13 @@ export class MockMemoryRepository<TSchema extends AnyModelSchema> implements IRe
179
179
 
180
180
  async upsert(input: InferInput<TSchema>, options?: ICreateOptions | IUpdateOptions): Promise<InferDetail<TSchema>> {
181
181
  const primaryKeyValue = input[this.entityMetadata.primaryKey]
182
- let existingItem: InferDetail<TSchema> | undefined = undefined
182
+ let existingItem: InferDetail<TSchema> = {} as InferDetail<TSchema>
183
183
 
184
184
  if (primaryKeyValue) {
185
- existingItem = this.data.get(primaryKeyValue) ?? {}
185
+ existingItem = this.data.get(primaryKeyValue) ?? ({} as InferDetail<TSchema>)
186
186
  }
187
187
 
188
- const updatedItem = Object.assign({}, existingItem, input) as InferDetail<TSchema>
188
+ const updatedItem = this.assignInput(existingItem, input)
189
189
  if (!updatedItem[this.entityMetadata.primaryKey!]) {
190
190
  updatedItem[this.entityMetadata.primaryKey!] = await this.generatePrimaryKey()
191
191
  }
@@ -218,6 +218,21 @@ export class MockMemoryRepository<TSchema extends AnyModelSchema> implements IRe
218
218
  })
219
219
  }
220
220
 
221
+ /**
222
+ * Assign input data to existing data using the provided assign function or default Object.assign
223
+ * @param existingData - The existing data to merge with
224
+ * @param input - The input data to assign
225
+ * @returns The merged data
226
+ */
227
+ protected assignInput(existingData: InferDetail<TSchema>, input: InferInput<TSchema>): InferDetail<TSchema> {
228
+ if (typeof this.args.assign === 'function') {
229
+ return this.args.assign(existingData, input)
230
+ } else {
231
+ // Default implementation using Object.assign
232
+ return Object.assign({}, existingData, input) as InferDetail<TSchema>
233
+ }
234
+ }
235
+
221
236
  protected async generatePrimaryKey() {
222
237
  const lookupModel: Model<any, any> = this.args.schema.definition.lookup
223
238
  const lookupMeta = await lookupModel.toJSONSchema()