@declaro/data 2.0.0-beta.96 → 2.0.0-beta.98

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.
@@ -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()