@declaro/data 2.0.0-beta.97 → 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.
- package/dist/browser/index.js +8 -8
- package/dist/browser/index.js.map +3 -3
- package/dist/node/index.cjs +20 -13
- package/dist/node/index.cjs.map +3 -3
- package/dist/node/index.js +20 -13
- package/dist/node/index.js.map +3 -3
- package/dist/ts/domain/services/model-service.d.ts +8 -0
- package/dist/ts/domain/services/model-service.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/domain/services/model-service.test.ts +245 -0
- package/src/domain/services/model-service.ts +37 -14
|
@@ -9,6 +9,14 @@ export interface IUpdateOptions extends IActionOptions {
|
|
|
9
9
|
}
|
|
10
10
|
export declare class ModelService<TSchema extends AnyModelSchema> extends ReadOnlyModelService<TSchema> {
|
|
11
11
|
constructor(args: IModelServiceArgs<TSchema>);
|
|
12
|
+
/**
|
|
13
|
+
* Normalizes input data before processing. This method can be overridden by subclasses
|
|
14
|
+
* to implement custom input normalization logic (e.g., trimming strings, setting defaults, etc.).
|
|
15
|
+
* By default, this method returns the input unchanged.
|
|
16
|
+
* @param input The input data to normalize.
|
|
17
|
+
* @returns The normalized input data.
|
|
18
|
+
*/
|
|
19
|
+
protected normalizeInput(input: InferInput<TSchema>): Promise<InferInput<TSchema>>;
|
|
12
20
|
/**
|
|
13
21
|
* Removes a record by its lookup criteria.
|
|
14
22
|
* @param lookup The lookup criteria to find the record.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model-service.d.ts","sourceRoot":"","sources":["../../../../src/domain/services/model-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AACnD,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,qCAAqC,CAAA;AAG7G,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AAC7D,OAAO,EAAE,oBAAoB,EAAE,KAAK,YAAY,EAAE,MAAM,2BAA2B,CAAA;AACnF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAE1D,MAAM,WAAW,cAAe,SAAQ,cAAc;CAAG;AACzD,MAAM,WAAW,cAAe,SAAQ,cAAc;CAAG;AAEzD,qBAAa,YAAY,CAAC,OAAO,SAAS,cAAc,CAAE,SAAQ,oBAAoB,CAAC,OAAO,CAAC;gBAC/E,IAAI,EAAE,iBAAiB,CAAC,OAAO,CAAC;IAI5C;;;;OAIG;IACG,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;IAsBlG;;;;;OAKG;IACG,OAAO,CAAC,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;IAsB7F,MAAM,CAAC,KAAK,EAAE,UAAU,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"model-service.d.ts","sourceRoot":"","sources":["../../../../src/domain/services/model-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AACnD,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,qCAAqC,CAAA;AAG7G,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AAC7D,OAAO,EAAE,oBAAoB,EAAE,KAAK,YAAY,EAAE,MAAM,2BAA2B,CAAA;AACnF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAE1D,MAAM,WAAW,cAAe,SAAQ,cAAc;CAAG;AACzD,MAAM,WAAW,cAAe,SAAQ,cAAc;CAAG;AAEzD,qBAAa,YAAY,CAAC,OAAO,SAAS,cAAc,CAAE,SAAQ,oBAAoB,CAAC,OAAO,CAAC;gBAC/E,IAAI,EAAE,iBAAiB,CAAC,OAAO,CAAC;IAI5C;;;;;;OAMG;cACa,cAAc,CAAC,KAAK,EAAE,UAAU,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;IAIxF;;;;OAIG;IACG,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;IAsBlG;;;;;OAKG;IACG,OAAO,CAAC,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;IAsB7F,MAAM,CAAC,KAAK,EAAE,UAAU,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IAyB3F,MAAM,CACR,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,EAC5B,KAAK,EAAE,UAAU,CAAC,OAAO,CAAC,EAC1B,OAAO,CAAC,EAAE,cAAc,GACzB,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IAyBhC;;;;;OAKG;IACG,MAAM,CAAC,KAAK,EAAE,UAAU,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,cAAc,GAAG,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IAkDlH;;;;;OAKG;IACG,UAAU,CACZ,MAAM,EAAE,UAAU,CAAC,OAAO,CAAC,EAAE,EAC7B,OAAO,CAAC,EAAE,cAAc,GAAG,cAAc,GAC1C,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC;CA+IrC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@declaro/data",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.98",
|
|
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.
|
|
26
|
-
"@declaro/core": "^2.0.0-beta.
|
|
27
|
-
"@declaro/zod": "^2.0.0-beta.
|
|
25
|
+
"@declaro/auth": "^2.0.0-beta.98",
|
|
26
|
+
"@declaro/core": "^2.0.0-beta.98",
|
|
27
|
+
"@declaro/zod": "^2.0.0-beta.98",
|
|
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": "
|
|
46
|
+
"gitHead": "5f998cfb45e04482853d7511c04348d668c292da"
|
|
47
47
|
}
|
|
@@ -628,4 +628,249 @@ describe('ModelService', () => {
|
|
|
628
628
|
expect(results).toEqual(inputs)
|
|
629
629
|
})
|
|
630
630
|
})
|
|
631
|
+
|
|
632
|
+
describe('normalizeInput functionality', () => {
|
|
633
|
+
class TestModelService extends ModelService<typeof mockSchema> {
|
|
634
|
+
public testNormalizeInput(input: any) {
|
|
635
|
+
return this.normalizeInput(input)
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
protected async normalizeInput(input: any) {
|
|
639
|
+
return {
|
|
640
|
+
...input,
|
|
641
|
+
title: input.title?.trim(),
|
|
642
|
+
author: input.author?.trim(),
|
|
643
|
+
normalizedAt: new Date('2023-01-01'),
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
let testService: TestModelService
|
|
649
|
+
|
|
650
|
+
beforeEach(() => {
|
|
651
|
+
testService = new TestModelService({ repository, emitter, schema: mockSchema, namespace })
|
|
652
|
+
})
|
|
653
|
+
|
|
654
|
+
it('should use default normalizeInput method (no changes) when not overridden', async () => {
|
|
655
|
+
const input = { title: ' Test Book ', author: ' Author Name ', publishedDate: new Date() }
|
|
656
|
+
const normalized = await service['normalizeInput'](input)
|
|
657
|
+
|
|
658
|
+
expect(normalized).toEqual(input)
|
|
659
|
+
expect(normalized).toBe(input) // Should be the exact same reference
|
|
660
|
+
})
|
|
661
|
+
|
|
662
|
+
it('should use custom normalizeInput method for create operation', async () => {
|
|
663
|
+
const input = { title: ' Test Book ', author: ' Author Name ', publishedDate: new Date() }
|
|
664
|
+
const createdItem = await testService.create(input)
|
|
665
|
+
|
|
666
|
+
expect(createdItem.title).toBe('Test Book')
|
|
667
|
+
expect(createdItem.author).toBe('Author Name')
|
|
668
|
+
expect((createdItem as any).normalizedAt).toEqual(new Date('2023-01-01'))
|
|
669
|
+
})
|
|
670
|
+
|
|
671
|
+
it('should use custom normalizeInput method for update operation', async () => {
|
|
672
|
+
const input = { id: 42, title: 'Original Book', author: 'Original Author', publishedDate: new Date() }
|
|
673
|
+
const createdItem = await testService.create(input)
|
|
674
|
+
|
|
675
|
+
const updateInput = { title: ' Updated Book ', author: ' Updated Author ', publishedDate: new Date() }
|
|
676
|
+
const updatedItem = await testService.update({ id: createdItem.id }, updateInput)
|
|
677
|
+
|
|
678
|
+
expect(updatedItem.title).toBe('Updated Book')
|
|
679
|
+
expect(updatedItem.author).toBe('Updated Author')
|
|
680
|
+
expect((updatedItem as any).normalizedAt).toEqual(new Date('2023-01-01'))
|
|
681
|
+
})
|
|
682
|
+
|
|
683
|
+
it('should use custom normalizeInput method for upsert operation', async () => {
|
|
684
|
+
const input = { id: 42, title: ' Test Book ', author: ' Author Name ', publishedDate: new Date() }
|
|
685
|
+
const upsertedItem = await testService.upsert(input)
|
|
686
|
+
|
|
687
|
+
expect(upsertedItem.title).toBe('Test Book')
|
|
688
|
+
expect(upsertedItem.author).toBe('Author Name')
|
|
689
|
+
expect((upsertedItem as any).normalizedAt).toEqual(new Date('2023-01-01'))
|
|
690
|
+
|
|
691
|
+
// Upsert again with different data
|
|
692
|
+
const updateInput = {
|
|
693
|
+
id: 42,
|
|
694
|
+
title: ' Updated Book ',
|
|
695
|
+
author: ' Updated Author ',
|
|
696
|
+
publishedDate: new Date(),
|
|
697
|
+
}
|
|
698
|
+
const updatedItem = await testService.upsert(updateInput)
|
|
699
|
+
|
|
700
|
+
expect(updatedItem.title).toBe('Updated Book')
|
|
701
|
+
expect(updatedItem.author).toBe('Updated Author')
|
|
702
|
+
expect((updatedItem as any).normalizedAt).toEqual(new Date('2023-01-01'))
|
|
703
|
+
})
|
|
704
|
+
|
|
705
|
+
it('should use custom normalizeInput method for bulkUpsert operation', async () => {
|
|
706
|
+
const inputs = [
|
|
707
|
+
{ id: 1, title: ' Book One ', author: ' Author One ', publishedDate: new Date() },
|
|
708
|
+
{ id: 2, title: ' Book Two ', author: ' Author Two ', publishedDate: new Date() },
|
|
709
|
+
{ title: ' Book Three ', author: ' Author Three ', publishedDate: new Date() }, // No ID - will be created
|
|
710
|
+
]
|
|
711
|
+
|
|
712
|
+
const results = await testService.bulkUpsert(inputs)
|
|
713
|
+
|
|
714
|
+
expect(results).toHaveLength(3)
|
|
715
|
+
expect(results[0].title).toBe('Book One')
|
|
716
|
+
expect(results[0].author).toBe('Author One')
|
|
717
|
+
expect((results[0] as any).normalizedAt).toEqual(new Date('2023-01-01'))
|
|
718
|
+
|
|
719
|
+
expect(results[1].title).toBe('Book Two')
|
|
720
|
+
expect(results[1].author).toBe('Author Two')
|
|
721
|
+
expect((results[1] as any).normalizedAt).toEqual(new Date('2023-01-01'))
|
|
722
|
+
|
|
723
|
+
expect(results[2].title).toBe('Book Three')
|
|
724
|
+
expect(results[2].author).toBe('Author Three')
|
|
725
|
+
expect((results[2] as any).normalizedAt).toEqual(new Date('2023-01-01'))
|
|
726
|
+
})
|
|
727
|
+
|
|
728
|
+
it('should preserve events order with normalized input in create operation', async () => {
|
|
729
|
+
const input = { title: ' Test Book ', author: ' Author Name ', publishedDate: new Date() }
|
|
730
|
+
await testService.create(input)
|
|
731
|
+
|
|
732
|
+
// Debug: Let's see what was actually called
|
|
733
|
+
const beforeCreateCall = beforeCreateSpy.mock.calls[0][0]
|
|
734
|
+
const afterCreateCall = afterCreateSpy.mock.calls[0][0]
|
|
735
|
+
|
|
736
|
+
expect(beforeCreateCall.meta.input.title).toBe('Test Book')
|
|
737
|
+
expect(beforeCreateCall.meta.input.author).toBe('Author Name')
|
|
738
|
+
expect(beforeCreateCall.meta.input.normalizedAt).toEqual(new Date('2023-01-01'))
|
|
739
|
+
|
|
740
|
+
expect(afterCreateCall.meta.input.title).toBe('Test Book')
|
|
741
|
+
expect(afterCreateCall.meta.input.author).toBe('Author Name')
|
|
742
|
+
expect(afterCreateCall.meta.input.normalizedAt).toEqual(new Date('2023-01-01'))
|
|
743
|
+
})
|
|
744
|
+
|
|
745
|
+
it('should handle complex async normalization logic', async () => {
|
|
746
|
+
class ComplexNormalizationService extends ModelService<typeof mockSchema> {
|
|
747
|
+
protected async normalizeInput(input: any) {
|
|
748
|
+
// Simulate async operations like database lookups, API calls, etc.
|
|
749
|
+
await new Promise((resolve) => setTimeout(resolve, 10))
|
|
750
|
+
|
|
751
|
+
const normalized = { ...input }
|
|
752
|
+
|
|
753
|
+
// Complex normalization logic
|
|
754
|
+
if (normalized.title) {
|
|
755
|
+
normalized.title = normalized.title
|
|
756
|
+
.trim()
|
|
757
|
+
.replace(/\s+/g, ' ')
|
|
758
|
+
.toLowerCase()
|
|
759
|
+
.replace(/\b\w/g, (l: string) => l.toUpperCase()) // Title case
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
if (normalized.author) {
|
|
763
|
+
normalized.author = normalized.author.trim()
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// Add metadata
|
|
767
|
+
normalized.processedAt = new Date('2023-01-01')
|
|
768
|
+
normalized.version = (normalized.version || 0) + 1
|
|
769
|
+
|
|
770
|
+
return normalized
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
const complexService = new ComplexNormalizationService({
|
|
775
|
+
repository,
|
|
776
|
+
emitter,
|
|
777
|
+
schema: mockSchema,
|
|
778
|
+
namespace,
|
|
779
|
+
})
|
|
780
|
+
|
|
781
|
+
const input = {
|
|
782
|
+
title: ' the great book ',
|
|
783
|
+
author: ' John Doe ',
|
|
784
|
+
publishedDate: new Date(),
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
const result = await complexService.create(input)
|
|
788
|
+
|
|
789
|
+
expect(result.title).toBe('The Great Book')
|
|
790
|
+
expect(result.author).toBe('John Doe')
|
|
791
|
+
expect((result as any).processedAt).toEqual(new Date('2023-01-01'))
|
|
792
|
+
expect((result as any).version).toBe(1)
|
|
793
|
+
})
|
|
794
|
+
|
|
795
|
+
it('should call normalizeInput method exactly once per input during bulkUpsert with Promise.all', async () => {
|
|
796
|
+
const normalizeInputSpy = mock(async (input: any) => ({ ...input, normalized: true }))
|
|
797
|
+
|
|
798
|
+
class SpyService extends ModelService<typeof mockSchema> {
|
|
799
|
+
protected async normalizeInput(input: any) {
|
|
800
|
+
return normalizeInputSpy(input)
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
const spyService = new SpyService({ repository, emitter, schema: mockSchema, namespace })
|
|
805
|
+
|
|
806
|
+
const inputs = [
|
|
807
|
+
{ id: 1, title: 'Book One', author: 'Author One', publishedDate: new Date() },
|
|
808
|
+
{ id: 2, title: 'Book Two', author: 'Author Two', publishedDate: new Date() },
|
|
809
|
+
]
|
|
810
|
+
|
|
811
|
+
await spyService.bulkUpsert(inputs)
|
|
812
|
+
|
|
813
|
+
expect(normalizeInputSpy).toHaveBeenCalledTimes(2)
|
|
814
|
+
expect(normalizeInputSpy).toHaveBeenNthCalledWith(1, inputs[0])
|
|
815
|
+
expect(normalizeInputSpy).toHaveBeenNthCalledWith(2, inputs[1])
|
|
816
|
+
})
|
|
817
|
+
|
|
818
|
+
it('should handle async normalization errors gracefully', async () => {
|
|
819
|
+
class ErrorNormalizationService extends ModelService<typeof mockSchema> {
|
|
820
|
+
protected async normalizeInput(input: any) {
|
|
821
|
+
if (input.title === 'ERROR') {
|
|
822
|
+
throw new Error('Normalization failed')
|
|
823
|
+
}
|
|
824
|
+
return input
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
const errorService = new ErrorNormalizationService({
|
|
829
|
+
repository,
|
|
830
|
+
emitter,
|
|
831
|
+
schema: mockSchema,
|
|
832
|
+
namespace,
|
|
833
|
+
})
|
|
834
|
+
|
|
835
|
+
const input = { title: 'ERROR', author: 'Author Name', publishedDate: new Date() }
|
|
836
|
+
|
|
837
|
+
await expect(errorService.create(input)).rejects.toThrow('Normalization failed')
|
|
838
|
+
})
|
|
839
|
+
|
|
840
|
+
it('should process bulk normalization in parallel for performance', async () => {
|
|
841
|
+
const processingTimes: number[] = []
|
|
842
|
+
|
|
843
|
+
class TimingNormalizationService extends ModelService<typeof mockSchema> {
|
|
844
|
+
protected async normalizeInput(input: any) {
|
|
845
|
+
const start = Date.now()
|
|
846
|
+
// Simulate some async work
|
|
847
|
+
await new Promise((resolve) => setTimeout(resolve, 50))
|
|
848
|
+
processingTimes.push(Date.now() - start)
|
|
849
|
+
return input
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
const timingService = new TimingNormalizationService({
|
|
854
|
+
repository,
|
|
855
|
+
emitter,
|
|
856
|
+
schema: mockSchema,
|
|
857
|
+
namespace,
|
|
858
|
+
})
|
|
859
|
+
|
|
860
|
+
const inputs = [
|
|
861
|
+
{ id: 1, title: 'Book One', author: 'Author One', publishedDate: new Date() },
|
|
862
|
+
{ id: 2, title: 'Book Two', author: 'Author Two', publishedDate: new Date() },
|
|
863
|
+
{ id: 3, title: 'Book Three', author: 'Author Three', publishedDate: new Date() },
|
|
864
|
+
]
|
|
865
|
+
|
|
866
|
+
const start = Date.now()
|
|
867
|
+
await timingService.bulkUpsert(inputs)
|
|
868
|
+
const totalTime = Date.now() - start
|
|
869
|
+
|
|
870
|
+
// With Promise.all, total time should be closer to single operation time rather than sum of all
|
|
871
|
+
// Allow some variance for test stability
|
|
872
|
+
expect(totalTime).toBeLessThan(150) // Much less than 3 * 50ms = 150ms
|
|
873
|
+
expect(processingTimes).toHaveLength(3)
|
|
874
|
+
})
|
|
875
|
+
})
|
|
631
876
|
})
|
|
@@ -14,6 +14,17 @@ export class ModelService<TSchema extends AnyModelSchema> extends ReadOnlyModelS
|
|
|
14
14
|
super(args)
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Normalizes input data before processing. This method can be overridden by subclasses
|
|
19
|
+
* to implement custom input normalization logic (e.g., trimming strings, setting defaults, etc.).
|
|
20
|
+
* By default, this method returns the input unchanged.
|
|
21
|
+
* @param input The input data to normalize.
|
|
22
|
+
* @returns The normalized input data.
|
|
23
|
+
*/
|
|
24
|
+
protected async normalizeInput(input: InferInput<TSchema>): Promise<InferInput<TSchema>> {
|
|
25
|
+
return input
|
|
26
|
+
}
|
|
27
|
+
|
|
17
28
|
/**
|
|
18
29
|
* Removes a record by its lookup criteria.
|
|
19
30
|
* @param lookup The lookup criteria to find the record.
|
|
@@ -70,20 +81,23 @@ export class ModelService<TSchema extends AnyModelSchema> extends ReadOnlyModelS
|
|
|
70
81
|
}
|
|
71
82
|
|
|
72
83
|
async create(input: InferInput<TSchema>, options?: ICreateOptions): Promise<InferDetail<TSchema>> {
|
|
84
|
+
// Normalize the input data
|
|
85
|
+
const normalizedInput = await this.normalizeInput(input)
|
|
86
|
+
|
|
73
87
|
// Emit the before create event
|
|
74
88
|
const beforeCreateEvent = new MutationEvent<InferDetail<TSchema>, InferInput<TSchema>>(
|
|
75
89
|
this.getDescriptor(ModelMutationAction.BeforeCreate),
|
|
76
|
-
|
|
90
|
+
normalizedInput,
|
|
77
91
|
)
|
|
78
92
|
await this.emitter.emitAsync(beforeCreateEvent)
|
|
79
93
|
|
|
80
94
|
// Perform the creation
|
|
81
|
-
const result = await this.repository.create(
|
|
95
|
+
const result = await this.repository.create(normalizedInput, options)
|
|
82
96
|
|
|
83
97
|
// Emit the after create event
|
|
84
98
|
const afterCreateEvent = new MutationEvent<InferDetail<TSchema>, InferInput<TSchema>>(
|
|
85
99
|
this.getDescriptor(ModelMutationAction.AfterCreate),
|
|
86
|
-
|
|
100
|
+
normalizedInput,
|
|
87
101
|
).setResult(result)
|
|
88
102
|
await this.emitter.emitAsync(afterCreateEvent)
|
|
89
103
|
|
|
@@ -96,20 +110,23 @@ export class ModelService<TSchema extends AnyModelSchema> extends ReadOnlyModelS
|
|
|
96
110
|
input: InferInput<TSchema>,
|
|
97
111
|
options?: IUpdateOptions,
|
|
98
112
|
): Promise<InferDetail<TSchema>> {
|
|
113
|
+
// Normalize the input data
|
|
114
|
+
const normalizedInput = await this.normalizeInput(input)
|
|
115
|
+
|
|
99
116
|
// Emit the before update event
|
|
100
117
|
const beforeUpdateEvent = new MutationEvent<InferDetail<TSchema>, InferInput<TSchema>>(
|
|
101
118
|
this.getDescriptor(ModelMutationAction.BeforeUpdate),
|
|
102
|
-
|
|
119
|
+
normalizedInput,
|
|
103
120
|
)
|
|
104
121
|
await this.emitter.emitAsync(beforeUpdateEvent)
|
|
105
122
|
|
|
106
123
|
// Perform the update
|
|
107
|
-
const result = await this.repository.update(lookup,
|
|
124
|
+
const result = await this.repository.update(lookup, normalizedInput, options)
|
|
108
125
|
|
|
109
126
|
// Emit the after update event
|
|
110
127
|
const afterUpdateEvent = new MutationEvent<InferDetail<TSchema>, InferInput<TSchema>>(
|
|
111
128
|
this.getDescriptor(ModelMutationAction.AfterUpdate),
|
|
112
|
-
|
|
129
|
+
normalizedInput,
|
|
113
130
|
).setResult(result)
|
|
114
131
|
await this.emitter.emitAsync(afterUpdateEvent)
|
|
115
132
|
|
|
@@ -124,7 +141,10 @@ export class ModelService<TSchema extends AnyModelSchema> extends ReadOnlyModelS
|
|
|
124
141
|
* @returns The upserted record.
|
|
125
142
|
*/
|
|
126
143
|
async upsert(input: InferInput<TSchema>, options?: ICreateOptions | IUpdateOptions): Promise<InferDetail<TSchema>> {
|
|
127
|
-
|
|
144
|
+
// Normalize the input data
|
|
145
|
+
const normalizedInput = await this.normalizeInput(input)
|
|
146
|
+
|
|
147
|
+
const primaryKeyValue = this.getPrimaryKeyValue(normalizedInput)
|
|
128
148
|
|
|
129
149
|
let beforeOperation: ModelMutationAction
|
|
130
150
|
let afterOperation: ModelMutationAction
|
|
@@ -152,17 +172,17 @@ export class ModelService<TSchema extends AnyModelSchema> extends ReadOnlyModelS
|
|
|
152
172
|
// Emit the before upsert event
|
|
153
173
|
const beforeUpsertEvent = new MutationEvent<InferDetail<TSchema>, InferInput<TSchema>>(
|
|
154
174
|
this.getDescriptor(beforeOperation),
|
|
155
|
-
|
|
175
|
+
normalizedInput,
|
|
156
176
|
)
|
|
157
177
|
await this.emitter.emitAsync(beforeUpsertEvent)
|
|
158
178
|
|
|
159
179
|
// Perform the upsert operation
|
|
160
|
-
const result = await this.repository.upsert(
|
|
180
|
+
const result = await this.repository.upsert(normalizedInput, options)
|
|
161
181
|
|
|
162
182
|
// Emit the after upsert event
|
|
163
183
|
const afterUpsertEvent = new MutationEvent<InferDetail<TSchema>, InferInput<TSchema>>(
|
|
164
184
|
this.getDescriptor(afterOperation),
|
|
165
|
-
|
|
185
|
+
normalizedInput,
|
|
166
186
|
).setResult(result)
|
|
167
187
|
await this.emitter.emitAsync(afterUpsertEvent)
|
|
168
188
|
|
|
@@ -184,6 +204,9 @@ export class ModelService<TSchema extends AnyModelSchema> extends ReadOnlyModelS
|
|
|
184
204
|
return []
|
|
185
205
|
}
|
|
186
206
|
|
|
207
|
+
// Normalize all input data in parallel using Promise.all
|
|
208
|
+
const normalizedInputs = await Promise.all(inputs.map((input) => this.normalizeInput(input)))
|
|
209
|
+
|
|
187
210
|
// Build a map of primary key to input and lookup info
|
|
188
211
|
type EntityInfo = {
|
|
189
212
|
input: InferInput<TSchema>
|
|
@@ -196,8 +219,8 @@ export class ModelService<TSchema extends AnyModelSchema> extends ReadOnlyModelS
|
|
|
196
219
|
const entityInfoMap = new Map<string | number, EntityInfo>()
|
|
197
220
|
const inputsWithoutPrimaryKey: InferInput<TSchema>[] = []
|
|
198
221
|
|
|
199
|
-
// Process each input and organize by primary key
|
|
200
|
-
for (const input of
|
|
222
|
+
// Process each normalized input and organize by primary key
|
|
223
|
+
for (const input of normalizedInputs) {
|
|
201
224
|
const primaryKeyValue = this.getPrimaryKeyValue(input)
|
|
202
225
|
|
|
203
226
|
if (primaryKeyValue !== undefined) {
|
|
@@ -264,8 +287,8 @@ export class ModelService<TSchema extends AnyModelSchema> extends ReadOnlyModelS
|
|
|
264
287
|
// Emit all before events
|
|
265
288
|
await Promise.all(beforeEvents.map((event) => this.emitter.emitAsync(event)))
|
|
266
289
|
|
|
267
|
-
// Perform the bulk upsert operation
|
|
268
|
-
const results = await this.repository.bulkUpsert(
|
|
290
|
+
// Perform the bulk upsert operation with normalized inputs
|
|
291
|
+
const results = await this.repository.bulkUpsert(normalizedInputs, options)
|
|
269
292
|
|
|
270
293
|
// Create a map of result primary keys to results for matching
|
|
271
294
|
const resultsByPrimaryKey = new Map<string | number, InferDetail<TSchema>>()
|