@declaro/data 2.0.0-beta.125 → 2.0.0-beta.127
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 +11 -11
- package/dist/browser/index.js.map +6 -6
- package/dist/node/index.cjs +189 -92
- package/dist/node/index.cjs.map +6 -6
- package/dist/node/index.js +181 -84
- package/dist/node/index.js.map +6 -6
- package/dist/ts/application/model-controller.d.ts +15 -5
- package/dist/ts/application/model-controller.d.ts.map +1 -1
- package/dist/ts/application/read-only-model-controller.d.ts +5 -1
- package/dist/ts/application/read-only-model-controller.d.ts.map +1 -1
- package/dist/ts/domain/services/model-service.d.ts +8 -0
- package/dist/ts/domain/services/model-service.d.ts.map +1 -1
- package/dist/ts/domain/services/read-only-model-service.d.ts +8 -0
- package/dist/ts/domain/services/read-only-model-service.d.ts.map +1 -1
- package/dist/ts/shared/utils/schema-inheritance.test.d.ts +2 -0
- package/dist/ts/shared/utils/schema-inheritance.test.d.ts.map +1 -0
- package/dist/ts/shared/utils/test/animal-schema.d.ts +57 -0
- package/dist/ts/shared/utils/test/animal-schema.d.ts.map +1 -0
- package/dist/ts/shared/utils/test/animal-trait-schema.d.ts +55 -0
- package/dist/ts/shared/utils/test/animal-trait-schema.d.ts.map +1 -0
- package/dist/ts/shared/utils/test/elephant-schema.d.ts +30 -0
- package/dist/ts/shared/utils/test/elephant-schema.d.ts.map +1 -0
- package/dist/ts/shared/utils/test/elephant-trait-schema.d.ts +26 -0
- package/dist/ts/shared/utils/test/elephant-trait-schema.d.ts.map +1 -0
- package/package.json +5 -5
- package/src/application/model-controller.ts +110 -59
- package/src/application/read-only-model-controller.ts +43 -25
- package/src/domain/services/model-service.test.ts +280 -0
- package/src/domain/services/model-service.ts +98 -66
- package/src/domain/services/read-only-model-service.test.ts +230 -0
- package/src/domain/services/read-only-model-service.ts +65 -40
- package/src/shared/utils/schema-inheritance.test.ts +295 -0
- package/src/shared/utils/test/animal-schema.ts +46 -0
- package/src/shared/utils/test/animal-trait-schema.ts +45 -0
- package/src/shared/utils/test/elephant-schema.ts +58 -0
- package/src/shared/utils/test/elephant-trait-schema.ts +53 -0
- package/dist/ts/test/mock/repositories/mock-memory-repository.custom-lookup.test.d.ts +0 -1
- package/dist/ts/test/mock/repositories/mock-memory-repository.custom-lookup.test.d.ts.map +0 -1
- package/src/test/mock/repositories/mock-memory-repository.custom-lookup.test.ts +0 -0
|
@@ -12,8 +12,18 @@ import type { IModelServiceArgs } from './model-service-args'
|
|
|
12
12
|
import { ReadOnlyModelService, type ILoadOptions } from './read-only-model-service'
|
|
13
13
|
import type { IActionOptions } from './base-model-service'
|
|
14
14
|
|
|
15
|
-
export interface ICreateOptions extends IActionOptions {
|
|
16
|
-
|
|
15
|
+
export interface ICreateOptions extends IActionOptions {
|
|
16
|
+
/**
|
|
17
|
+
* If true, skips dispatching events for this action.
|
|
18
|
+
*/
|
|
19
|
+
doNotDispatchEvents?: boolean
|
|
20
|
+
}
|
|
21
|
+
export interface IUpdateOptions extends IActionOptions {
|
|
22
|
+
/**
|
|
23
|
+
* If true, skips dispatching events for this action.
|
|
24
|
+
*/
|
|
25
|
+
doNotDispatchEvents?: boolean
|
|
26
|
+
}
|
|
17
27
|
|
|
18
28
|
export interface INormalizeInputArgs<TSchema extends AnyModelSchema> {
|
|
19
29
|
existing?: InferDetail<TSchema>
|
|
@@ -101,21 +111,25 @@ export class ModelService<TSchema extends AnyModelSchema> extends ReadOnlyModelS
|
|
|
101
111
|
})
|
|
102
112
|
|
|
103
113
|
// Emit the before create event
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
114
|
+
if (!options?.doNotDispatchEvents) {
|
|
115
|
+
const beforeCreateEvent = new MutationEvent<InferDetail<TSchema>, InferInput<TSchema>>(
|
|
116
|
+
this.getDescriptor(ModelMutationAction.BeforeCreate),
|
|
117
|
+
normalizedInput,
|
|
118
|
+
)
|
|
119
|
+
await this.emitter.emitAsync(beforeCreateEvent)
|
|
120
|
+
}
|
|
109
121
|
|
|
110
122
|
// Perform the creation
|
|
111
123
|
const result = await this.repository.create(normalizedInput, options)
|
|
112
124
|
|
|
113
125
|
// Emit the after create event
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
126
|
+
if (!options?.doNotDispatchEvents) {
|
|
127
|
+
const afterCreateEvent = new MutationEvent<InferDetail<TSchema>, InferInput<TSchema>>(
|
|
128
|
+
this.getDescriptor(ModelMutationAction.AfterCreate),
|
|
129
|
+
normalizedInput,
|
|
130
|
+
).setResult(result)
|
|
131
|
+
await this.emitter.emitAsync(afterCreateEvent)
|
|
132
|
+
}
|
|
119
133
|
|
|
120
134
|
// Return the results of the creation
|
|
121
135
|
return await this.normalizeDetail(result)
|
|
@@ -126,7 +140,7 @@ export class ModelService<TSchema extends AnyModelSchema> extends ReadOnlyModelS
|
|
|
126
140
|
input: InferInput<TSchema>,
|
|
127
141
|
options?: IUpdateOptions,
|
|
128
142
|
): Promise<InferDetail<TSchema>> {
|
|
129
|
-
const existing = await this.repository.load(lookup, options)
|
|
143
|
+
const existing = await this.repository.load(lookup, { ...options, doNotDispatchEvents: true })
|
|
130
144
|
// Normalize the input data
|
|
131
145
|
const normalizedInput = await this.normalizeInput(input, {
|
|
132
146
|
existing,
|
|
@@ -134,21 +148,25 @@ export class ModelService<TSchema extends AnyModelSchema> extends ReadOnlyModelS
|
|
|
134
148
|
})
|
|
135
149
|
|
|
136
150
|
// Emit the before update event
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
151
|
+
if (!options?.doNotDispatchEvents) {
|
|
152
|
+
const beforeUpdateEvent = new MutationEvent<InferDetail<TSchema>, InferInput<TSchema>>(
|
|
153
|
+
this.getDescriptor(ModelMutationAction.BeforeUpdate),
|
|
154
|
+
normalizedInput,
|
|
155
|
+
)
|
|
156
|
+
await this.emitter.emitAsync(beforeUpdateEvent)
|
|
157
|
+
}
|
|
142
158
|
|
|
143
159
|
// Perform the update
|
|
144
160
|
const result = await this.repository.update(lookup, normalizedInput, options)
|
|
145
161
|
|
|
146
162
|
// Emit the after update event
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
163
|
+
if (!options?.doNotDispatchEvents) {
|
|
164
|
+
const afterUpdateEvent = new MutationEvent<InferDetail<TSchema>, InferInput<TSchema>>(
|
|
165
|
+
this.getDescriptor(ModelMutationAction.AfterUpdate),
|
|
166
|
+
normalizedInput,
|
|
167
|
+
).setResult(result)
|
|
168
|
+
await this.emitter.emitAsync(afterUpdateEvent)
|
|
169
|
+
}
|
|
152
170
|
|
|
153
171
|
// Return the results of the update
|
|
154
172
|
return await this.normalizeDetail(result)
|
|
@@ -177,7 +195,10 @@ export class ModelService<TSchema extends AnyModelSchema> extends ReadOnlyModelS
|
|
|
177
195
|
{
|
|
178
196
|
[this.entityMetadata.primaryKey]: primaryKeyValue,
|
|
179
197
|
} as InferLookup<TSchema>,
|
|
180
|
-
|
|
198
|
+
{
|
|
199
|
+
...options,
|
|
200
|
+
doNotDispatchEvents: true,
|
|
201
|
+
},
|
|
181
202
|
)
|
|
182
203
|
|
|
183
204
|
if (existingItem) {
|
|
@@ -198,21 +219,25 @@ export class ModelService<TSchema extends AnyModelSchema> extends ReadOnlyModelS
|
|
|
198
219
|
})
|
|
199
220
|
|
|
200
221
|
// Emit the before upsert event
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
222
|
+
if (!options?.doNotDispatchEvents) {
|
|
223
|
+
const beforeUpsertEvent = new MutationEvent<InferDetail<TSchema>, InferInput<TSchema>>(
|
|
224
|
+
this.getDescriptor(beforeOperation),
|
|
225
|
+
normalizedInput,
|
|
226
|
+
)
|
|
227
|
+
await this.emitter.emitAsync(beforeUpsertEvent)
|
|
228
|
+
}
|
|
206
229
|
|
|
207
230
|
// Perform the upsert operation
|
|
208
231
|
const result = await this.repository.upsert(normalizedInput, options)
|
|
209
232
|
|
|
210
233
|
// Emit the after upsert event
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
234
|
+
if (!options?.doNotDispatchEvents) {
|
|
235
|
+
const afterUpsertEvent = new MutationEvent<InferDetail<TSchema>, InferInput<TSchema>>(
|
|
236
|
+
this.getDescriptor(afterOperation),
|
|
237
|
+
normalizedInput,
|
|
238
|
+
).setResult(result)
|
|
239
|
+
await this.emitter.emitAsync(afterUpsertEvent)
|
|
240
|
+
}
|
|
216
241
|
|
|
217
242
|
// Return the results of the upsert operation
|
|
218
243
|
return await this.normalizeDetail(result)
|
|
@@ -268,7 +293,10 @@ export class ModelService<TSchema extends AnyModelSchema> extends ReadOnlyModelS
|
|
|
268
293
|
const existingEntitiesMap = new Map<string | number, InferDetail<TSchema>>()
|
|
269
294
|
if (uniqueLookups.size > 0) {
|
|
270
295
|
const lookups = Array.from(uniqueLookups.values())
|
|
271
|
-
const existingEntities = await this.loadMany(lookups,
|
|
296
|
+
const existingEntities = await this.loadMany(lookups, {
|
|
297
|
+
...options,
|
|
298
|
+
doNotDispatchEvents: true,
|
|
299
|
+
})
|
|
272
300
|
existingEntities.forEach((entity) => {
|
|
273
301
|
if (entity) {
|
|
274
302
|
const pkValue = this.getPrimaryKeyValue(entity)
|
|
@@ -306,44 +334,48 @@ export class ModelService<TSchema extends AnyModelSchema> extends ReadOnlyModelS
|
|
|
306
334
|
const normalizedInputs = await Promise.all(normalizationPromises)
|
|
307
335
|
|
|
308
336
|
// Create before events
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
337
|
+
if (!options?.doNotDispatchEvents) {
|
|
338
|
+
const beforeEvents: MutationEvent<InferDetail<TSchema>, InferInput<TSchema>>[] = []
|
|
339
|
+
for (const inputInfo of inputInfos) {
|
|
340
|
+
beforeEvents.push(
|
|
341
|
+
new MutationEvent<InferDetail<TSchema>, InferInput<TSchema>>(
|
|
342
|
+
this.getDescriptor(inputInfo.operation!),
|
|
343
|
+
inputInfo.input,
|
|
344
|
+
),
|
|
345
|
+
)
|
|
346
|
+
}
|
|
318
347
|
|
|
319
|
-
|
|
320
|
-
|
|
348
|
+
// Emit all before events
|
|
349
|
+
await Promise.all(beforeEvents.map((event) => this.emitter.emitAsync(event)))
|
|
350
|
+
}
|
|
321
351
|
|
|
322
352
|
// Perform the bulk upsert operation with all normalized inputs
|
|
323
353
|
const results = await this.repository.bulkUpsert(normalizedInputs, options)
|
|
324
354
|
|
|
325
355
|
// Create after events and return results
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
356
|
+
if (!options?.doNotDispatchEvents) {
|
|
357
|
+
const afterEvents: MutationEvent<InferDetail<TSchema>, InferInput<TSchema>>[] = []
|
|
358
|
+
|
|
359
|
+
for (let i = 0; i < inputInfos.length; i++) {
|
|
360
|
+
const inputInfo = inputInfos[i]
|
|
361
|
+
const result = results[i]
|
|
362
|
+
|
|
363
|
+
const afterOperation =
|
|
364
|
+
inputInfo.operation === ModelMutationAction.BeforeCreate
|
|
365
|
+
? ModelMutationAction.AfterCreate
|
|
366
|
+
: ModelMutationAction.AfterUpdate
|
|
367
|
+
|
|
368
|
+
afterEvents.push(
|
|
369
|
+
new MutationEvent<InferDetail<TSchema>, InferInput<TSchema>>(
|
|
370
|
+
this.getDescriptor(afterOperation),
|
|
371
|
+
inputInfo.input,
|
|
372
|
+
).setResult(result),
|
|
373
|
+
)
|
|
374
|
+
}
|
|
344
375
|
|
|
345
|
-
|
|
346
|
-
|
|
376
|
+
// Emit all after events
|
|
377
|
+
await Promise.all(afterEvents.map((event) => this.emitter.emitAsync(event)))
|
|
378
|
+
}
|
|
347
379
|
|
|
348
380
|
// Return normalized results
|
|
349
381
|
return await Promise.all(results.map((result) => this.normalizeDetail(result)))
|
|
@@ -825,4 +825,234 @@ describe('ReadOnlyModelService', () => {
|
|
|
825
825
|
})
|
|
826
826
|
})
|
|
827
827
|
})
|
|
828
|
+
|
|
829
|
+
describe('doNotDispatchEvents Option', () => {
|
|
830
|
+
const beforeLoadSpy = mock(
|
|
831
|
+
(event: QueryEvent<InferDetail<typeof mockSchema>, InferLookup<typeof mockSchema>>) => {},
|
|
832
|
+
)
|
|
833
|
+
const afterLoadSpy = mock(
|
|
834
|
+
(event: QueryEvent<InferDetail<typeof mockSchema>, InferLookup<typeof mockSchema>>) => {},
|
|
835
|
+
)
|
|
836
|
+
|
|
837
|
+
const beforeLoadManySpy = mock(
|
|
838
|
+
(event: QueryEvent<InferDetail<typeof mockSchema>[], InferLookup<typeof mockSchema>[]>) => {},
|
|
839
|
+
)
|
|
840
|
+
const afterLoadManySpy = mock(
|
|
841
|
+
(event: QueryEvent<InferDetail<typeof mockSchema>[], InferLookup<typeof mockSchema>[]>) => {},
|
|
842
|
+
)
|
|
843
|
+
|
|
844
|
+
const beforeSearchSpy = mock(
|
|
845
|
+
(event: QueryEvent<InferSearchResults<typeof mockSchema>[], InferFilters<typeof mockSchema>[]>) => {},
|
|
846
|
+
)
|
|
847
|
+
const afterSearchSpy = mock(
|
|
848
|
+
(event: QueryEvent<InferSearchResults<typeof mockSchema>[], InferFilters<typeof mockSchema>[]>) => {},
|
|
849
|
+
)
|
|
850
|
+
|
|
851
|
+
const beforeCountSpy = mock((event: QueryEvent<number, InferFilters<typeof mockSchema>>) => {})
|
|
852
|
+
const afterCountSpy = mock((event: QueryEvent<number, InferFilters<typeof mockSchema>>) => {})
|
|
853
|
+
|
|
854
|
+
beforeEach(() => {
|
|
855
|
+
repository = new MockMemoryRepository({ schema: mockSchema })
|
|
856
|
+
emitter = new EventManager()
|
|
857
|
+
|
|
858
|
+
beforeLoadSpy.mockClear()
|
|
859
|
+
afterLoadSpy.mockClear()
|
|
860
|
+
beforeLoadManySpy.mockClear()
|
|
861
|
+
afterLoadManySpy.mockClear()
|
|
862
|
+
beforeSearchSpy.mockClear()
|
|
863
|
+
afterSearchSpy.mockClear()
|
|
864
|
+
beforeCountSpy.mockClear()
|
|
865
|
+
afterCountSpy.mockClear()
|
|
866
|
+
|
|
867
|
+
emitter.on<QueryEvent<InferDetail<typeof mockSchema>, InferLookup<typeof mockSchema>>>(
|
|
868
|
+
'books::book.beforeLoad',
|
|
869
|
+
beforeLoadSpy,
|
|
870
|
+
)
|
|
871
|
+
emitter.on<QueryEvent<InferDetail<typeof mockSchema>, InferLookup<typeof mockSchema>>>(
|
|
872
|
+
'books::book.afterLoad',
|
|
873
|
+
afterLoadSpy,
|
|
874
|
+
)
|
|
875
|
+
|
|
876
|
+
emitter.on<QueryEvent<InferDetail<typeof mockSchema>[], InferLookup<typeof mockSchema>[]>>(
|
|
877
|
+
'books::book.beforeLoadMany',
|
|
878
|
+
beforeLoadManySpy,
|
|
879
|
+
)
|
|
880
|
+
emitter.on<QueryEvent<InferDetail<typeof mockSchema>[], InferLookup<typeof mockSchema>[]>>(
|
|
881
|
+
'books::book.afterLoadMany',
|
|
882
|
+
afterLoadManySpy,
|
|
883
|
+
)
|
|
884
|
+
|
|
885
|
+
emitter.on<QueryEvent<InferSearchResults<typeof mockSchema>[], InferFilters<typeof mockSchema>[]>>(
|
|
886
|
+
'books::book.beforeSearch',
|
|
887
|
+
beforeSearchSpy,
|
|
888
|
+
)
|
|
889
|
+
emitter.on<QueryEvent<InferSearchResults<typeof mockSchema>[], InferFilters<typeof mockSchema>[]>>(
|
|
890
|
+
'books::book.afterSearch',
|
|
891
|
+
afterSearchSpy,
|
|
892
|
+
)
|
|
893
|
+
|
|
894
|
+
emitter.on<QueryEvent<number, InferFilters<typeof mockSchema>>>('books::book.beforeCount', beforeCountSpy)
|
|
895
|
+
emitter.on<QueryEvent<number, InferFilters<typeof mockSchema>>>('books::book.afterCount', afterCountSpy)
|
|
896
|
+
|
|
897
|
+
service = new ReadOnlyModelService({ repository, emitter, schema: mockSchema, namespace })
|
|
898
|
+
})
|
|
899
|
+
|
|
900
|
+
describe('load', () => {
|
|
901
|
+
it('should not dispatch events when doNotDispatchEvents is true', async () => {
|
|
902
|
+
const input = { id: 1, title: 'Test Book', author: 'Author', publishedDate: new Date() }
|
|
903
|
+
await repository.create(input)
|
|
904
|
+
|
|
905
|
+
const result = await service.load({ id: 1 }, { doNotDispatchEvents: true })
|
|
906
|
+
|
|
907
|
+
expect(result).toEqual(input)
|
|
908
|
+
expect(beforeLoadSpy).not.toHaveBeenCalled()
|
|
909
|
+
expect(afterLoadSpy).not.toHaveBeenCalled()
|
|
910
|
+
})
|
|
911
|
+
|
|
912
|
+
it('should dispatch events when doNotDispatchEvents is false', async () => {
|
|
913
|
+
const input = { id: 2, title: 'Test Book 2', author: 'Author', publishedDate: new Date() }
|
|
914
|
+
await repository.create(input)
|
|
915
|
+
|
|
916
|
+
const result = await service.load({ id: 2 }, { doNotDispatchEvents: false })
|
|
917
|
+
|
|
918
|
+
expect(result).toEqual(input)
|
|
919
|
+
expect(beforeLoadSpy).toHaveBeenCalledTimes(1)
|
|
920
|
+
expect(afterLoadSpy).toHaveBeenCalledTimes(1)
|
|
921
|
+
})
|
|
922
|
+
|
|
923
|
+
it('should dispatch events when doNotDispatchEvents is not specified', async () => {
|
|
924
|
+
const input = { id: 3, title: 'Test Book 3', author: 'Author', publishedDate: new Date() }
|
|
925
|
+
await repository.create(input)
|
|
926
|
+
|
|
927
|
+
const result = await service.load({ id: 3 })
|
|
928
|
+
|
|
929
|
+
expect(result).toEqual(input)
|
|
930
|
+
expect(beforeLoadSpy).toHaveBeenCalledTimes(1)
|
|
931
|
+
expect(afterLoadSpy).toHaveBeenCalledTimes(1)
|
|
932
|
+
})
|
|
933
|
+
})
|
|
934
|
+
|
|
935
|
+
describe('loadMany', () => {
|
|
936
|
+
it('should not dispatch events when doNotDispatchEvents is true', async () => {
|
|
937
|
+
const input1 = { id: 1, title: 'Test Book 1', author: 'Author 1', publishedDate: new Date() }
|
|
938
|
+
const input2 = { id: 2, title: 'Test Book 2', author: 'Author 2', publishedDate: new Date() }
|
|
939
|
+
await repository.create(input1)
|
|
940
|
+
await repository.create(input2)
|
|
941
|
+
|
|
942
|
+
const result = await service.loadMany([{ id: 1 }, { id: 2 }], { doNotDispatchEvents: true })
|
|
943
|
+
|
|
944
|
+
expect(result).toEqual([input1, input2])
|
|
945
|
+
expect(beforeLoadManySpy).not.toHaveBeenCalled()
|
|
946
|
+
expect(afterLoadManySpy).not.toHaveBeenCalled()
|
|
947
|
+
})
|
|
948
|
+
|
|
949
|
+
it('should dispatch events when doNotDispatchEvents is false', async () => {
|
|
950
|
+
const input1 = { id: 3, title: 'Test Book 3', author: 'Author 3', publishedDate: new Date() }
|
|
951
|
+
const input2 = { id: 4, title: 'Test Book 4', author: 'Author 4', publishedDate: new Date() }
|
|
952
|
+
await repository.create(input1)
|
|
953
|
+
await repository.create(input2)
|
|
954
|
+
|
|
955
|
+
const result = await service.loadMany([{ id: 3 }, { id: 4 }], { doNotDispatchEvents: false })
|
|
956
|
+
|
|
957
|
+
expect(result).toEqual([input1, input2])
|
|
958
|
+
expect(beforeLoadManySpy).toHaveBeenCalledTimes(1)
|
|
959
|
+
expect(afterLoadManySpy).toHaveBeenCalledTimes(1)
|
|
960
|
+
})
|
|
961
|
+
|
|
962
|
+
it('should dispatch events when doNotDispatchEvents is not specified', async () => {
|
|
963
|
+
const input1 = { id: 5, title: 'Test Book 5', author: 'Author 5', publishedDate: new Date() }
|
|
964
|
+
const input2 = { id: 6, title: 'Test Book 6', author: 'Author 6', publishedDate: new Date() }
|
|
965
|
+
await repository.create(input1)
|
|
966
|
+
await repository.create(input2)
|
|
967
|
+
|
|
968
|
+
const result = await service.loadMany([{ id: 5 }, { id: 6 }])
|
|
969
|
+
|
|
970
|
+
expect(result).toEqual([input1, input2])
|
|
971
|
+
expect(beforeLoadManySpy).toHaveBeenCalledTimes(1)
|
|
972
|
+
expect(afterLoadManySpy).toHaveBeenCalledTimes(1)
|
|
973
|
+
})
|
|
974
|
+
})
|
|
975
|
+
|
|
976
|
+
describe('search', () => {
|
|
977
|
+
it('should not dispatch events when doNotDispatchEvents is true', async () => {
|
|
978
|
+
const input1 = { id: 1, title: 'Test Book 1', author: 'Author 1', publishedDate: new Date() }
|
|
979
|
+
const input2 = { id: 2, title: 'Test Book 2', author: 'Author 2', publishedDate: new Date() }
|
|
980
|
+
await repository.create(input1)
|
|
981
|
+
await repository.create(input2)
|
|
982
|
+
|
|
983
|
+
const result = await service.search({}, { doNotDispatchEvents: true })
|
|
984
|
+
|
|
985
|
+
expect(result.results).toEqual([input1, input2])
|
|
986
|
+
expect(beforeSearchSpy).not.toHaveBeenCalled()
|
|
987
|
+
expect(afterSearchSpy).not.toHaveBeenCalled()
|
|
988
|
+
})
|
|
989
|
+
|
|
990
|
+
it('should dispatch events when doNotDispatchEvents is false', async () => {
|
|
991
|
+
const input1 = { id: 3, title: 'Test Book 3', author: 'Author 3', publishedDate: new Date() }
|
|
992
|
+
const input2 = { id: 4, title: 'Test Book 4', author: 'Author 4', publishedDate: new Date() }
|
|
993
|
+
await repository.create(input1)
|
|
994
|
+
await repository.create(input2)
|
|
995
|
+
|
|
996
|
+
const result = await service.search({}, { doNotDispatchEvents: false })
|
|
997
|
+
|
|
998
|
+
expect(result.results).toEqual([input1, input2])
|
|
999
|
+
expect(beforeSearchSpy).toHaveBeenCalledTimes(1)
|
|
1000
|
+
expect(afterSearchSpy).toHaveBeenCalledTimes(1)
|
|
1001
|
+
})
|
|
1002
|
+
|
|
1003
|
+
it('should dispatch events when doNotDispatchEvents is not specified', async () => {
|
|
1004
|
+
const input1 = { id: 5, title: 'Test Book 5', author: 'Author 5', publishedDate: new Date() }
|
|
1005
|
+
const input2 = { id: 6, title: 'Test Book 6', author: 'Author 6', publishedDate: new Date() }
|
|
1006
|
+
await repository.create(input1)
|
|
1007
|
+
await repository.create(input2)
|
|
1008
|
+
|
|
1009
|
+
const result = await service.search({})
|
|
1010
|
+
|
|
1011
|
+
expect(result.results).toEqual([input1, input2])
|
|
1012
|
+
expect(beforeSearchSpy).toHaveBeenCalledTimes(1)
|
|
1013
|
+
expect(afterSearchSpy).toHaveBeenCalledTimes(1)
|
|
1014
|
+
})
|
|
1015
|
+
})
|
|
1016
|
+
|
|
1017
|
+
describe('count', () => {
|
|
1018
|
+
it('should not dispatch events when doNotDispatchEvents is true', async () => {
|
|
1019
|
+
const input1 = { id: 1, title: 'Test Book 1', author: 'Author 1', publishedDate: new Date() }
|
|
1020
|
+
const input2 = { id: 2, title: 'Test Book 2', author: 'Author 2', publishedDate: new Date() }
|
|
1021
|
+
await repository.create(input1)
|
|
1022
|
+
await repository.create(input2)
|
|
1023
|
+
|
|
1024
|
+
const result = await service.count({}, { doNotDispatchEvents: true })
|
|
1025
|
+
|
|
1026
|
+
expect(result).toBe(2)
|
|
1027
|
+
expect(beforeCountSpy).not.toHaveBeenCalled()
|
|
1028
|
+
expect(afterCountSpy).not.toHaveBeenCalled()
|
|
1029
|
+
})
|
|
1030
|
+
|
|
1031
|
+
it('should dispatch events when doNotDispatchEvents is false', async () => {
|
|
1032
|
+
const input1 = { id: 3, title: 'Test Book 3', author: 'Author 3', publishedDate: new Date() }
|
|
1033
|
+
const input2 = { id: 4, title: 'Test Book 4', author: 'Author 4', publishedDate: new Date() }
|
|
1034
|
+
await repository.create(input1)
|
|
1035
|
+
await repository.create(input2)
|
|
1036
|
+
|
|
1037
|
+
const result = await service.count({}, { doNotDispatchEvents: false })
|
|
1038
|
+
|
|
1039
|
+
expect(result).toBe(2)
|
|
1040
|
+
expect(beforeCountSpy).toHaveBeenCalledTimes(1)
|
|
1041
|
+
expect(afterCountSpy).toHaveBeenCalledTimes(1)
|
|
1042
|
+
})
|
|
1043
|
+
|
|
1044
|
+
it('should dispatch events when doNotDispatchEvents is not specified', async () => {
|
|
1045
|
+
const input1 = { id: 5, title: 'Test Book 5', author: 'Author 5', publishedDate: new Date() }
|
|
1046
|
+
const input2 = { id: 6, title: 'Test Book 6', author: 'Author 6', publishedDate: new Date() }
|
|
1047
|
+
await repository.create(input1)
|
|
1048
|
+
await repository.create(input2)
|
|
1049
|
+
|
|
1050
|
+
const result = await service.count({})
|
|
1051
|
+
|
|
1052
|
+
expect(result).toBe(2)
|
|
1053
|
+
expect(beforeCountSpy).toHaveBeenCalledTimes(1)
|
|
1054
|
+
expect(afterCountSpy).toHaveBeenCalledTimes(1)
|
|
1055
|
+
})
|
|
1056
|
+
})
|
|
1057
|
+
})
|
|
828
1058
|
})
|
|
@@ -23,6 +23,11 @@ export interface ILoadOptions extends IActionOptions {
|
|
|
23
23
|
* If true, both removed and non-removed records will be returned.
|
|
24
24
|
*/
|
|
25
25
|
includeRemoved?: boolean
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* If true, skips dispatching events for this action.
|
|
29
|
+
*/
|
|
30
|
+
doNotDispatchEvents?: boolean
|
|
26
31
|
}
|
|
27
32
|
export interface ISearchOptions<TSchema extends AnyModelSchema> extends IActionOptions {
|
|
28
33
|
pagination?: IPaginationInput
|
|
@@ -35,6 +40,10 @@ export interface ISearchOptions<TSchema extends AnyModelSchema> extends IActionO
|
|
|
35
40
|
* If true, both removed and non-removed records will be returned.
|
|
36
41
|
*/
|
|
37
42
|
includeRemoved?: boolean
|
|
43
|
+
/**
|
|
44
|
+
* If true, skips dispatching events for this action.
|
|
45
|
+
*/
|
|
46
|
+
doNotDispatchEvents?: boolean
|
|
38
47
|
}
|
|
39
48
|
|
|
40
49
|
export class ReadOnlyModelService<TSchema extends AnyModelSchema> extends BaseModelService<TSchema> {
|
|
@@ -69,21 +78,25 @@ export class ReadOnlyModelService<TSchema extends AnyModelSchema> extends BaseMo
|
|
|
69
78
|
*/
|
|
70
79
|
async load(lookup: InferLookup<TSchema>, options?: ILoadOptions): Promise<InferDetail<TSchema>> {
|
|
71
80
|
// Emit the before load event
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
81
|
+
if (!options?.doNotDispatchEvents) {
|
|
82
|
+
const beforeLoadEvent = new QueryEvent<InferDetail<TSchema>, InferLookup<TSchema>>(
|
|
83
|
+
this.getDescriptor(ModelQueryEvent.BeforeLoad, options?.scope),
|
|
84
|
+
lookup,
|
|
85
|
+
)
|
|
86
|
+
await this.emitter.emitAsync(beforeLoadEvent)
|
|
87
|
+
}
|
|
77
88
|
|
|
78
89
|
// Load the details from the repository
|
|
79
90
|
const details = await this.repository.load(lookup, options)
|
|
80
91
|
|
|
81
92
|
// Emit the after load event
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
93
|
+
if (!options?.doNotDispatchEvents) {
|
|
94
|
+
const afterLoadEvent = new QueryEvent<InferDetail<TSchema>, InferLookup<TSchema>>(
|
|
95
|
+
this.getDescriptor(ModelQueryEvent.AfterLoad, options?.scope),
|
|
96
|
+
lookup,
|
|
97
|
+
).setResult(details)
|
|
98
|
+
await this.emitter.emitAsync(afterLoadEvent)
|
|
99
|
+
}
|
|
87
100
|
|
|
88
101
|
return await this.normalizeDetail(details)
|
|
89
102
|
}
|
|
@@ -96,21 +109,25 @@ export class ReadOnlyModelService<TSchema extends AnyModelSchema> extends BaseMo
|
|
|
96
109
|
*/
|
|
97
110
|
async loadMany(lookups: InferLookup<TSchema>[], options?: ILoadOptions): Promise<InferDetail<TSchema>[]> {
|
|
98
111
|
// Emit the before load many event
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
112
|
+
if (!options?.doNotDispatchEvents) {
|
|
113
|
+
const beforeLoadManyEvent = new QueryEvent<InferDetail<TSchema>[], InferLookup<TSchema>[]>(
|
|
114
|
+
this.getDescriptor(ModelQueryEvent.BeforeLoadMany, options?.scope),
|
|
115
|
+
lookups,
|
|
116
|
+
)
|
|
117
|
+
await this.emitter.emitAsync(beforeLoadManyEvent)
|
|
118
|
+
}
|
|
104
119
|
|
|
105
120
|
// Load the details from the repository
|
|
106
121
|
const details = await this.repository.loadMany(lookups, options)
|
|
107
122
|
|
|
108
123
|
// Emit the after load many event
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
124
|
+
if (!options?.doNotDispatchEvents) {
|
|
125
|
+
const afterLoadManyEvent = new QueryEvent<InferDetail<TSchema>[], InferLookup<TSchema>[]>(
|
|
126
|
+
this.getDescriptor(ModelQueryEvent.AfterLoadMany, options?.scope),
|
|
127
|
+
lookups,
|
|
128
|
+
).setResult(details)
|
|
129
|
+
await this.emitter.emitAsync(afterLoadManyEvent)
|
|
130
|
+
}
|
|
114
131
|
|
|
115
132
|
return await Promise.all(details.map((detail) => this.normalizeDetail(detail)))
|
|
116
133
|
}
|
|
@@ -126,21 +143,25 @@ export class ReadOnlyModelService<TSchema extends AnyModelSchema> extends BaseMo
|
|
|
126
143
|
options?: ISearchOptions<TSchema>,
|
|
127
144
|
): Promise<InferSearchResults<TSchema>> {
|
|
128
145
|
// Emit the before search event
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
146
|
+
if (!options?.doNotDispatchEvents) {
|
|
147
|
+
const beforeSearchEvent = new QueryEvent<InferSearchResults<TSchema>, InferFilters<TSchema>>(
|
|
148
|
+
this.getDescriptor(ModelQueryEvent.BeforeSearch, options?.scope),
|
|
149
|
+
filters,
|
|
150
|
+
)
|
|
151
|
+
await this.emitter.emitAsync(beforeSearchEvent)
|
|
152
|
+
}
|
|
134
153
|
|
|
135
154
|
// Search the repository with the provided filters
|
|
136
155
|
const results = await this.repository.search(filters, options)
|
|
137
156
|
|
|
138
157
|
// Emit the after search event
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
158
|
+
if (!options?.doNotDispatchEvents) {
|
|
159
|
+
const afterSearchEvent = new QueryEvent<InferSearchResults<TSchema>, InferFilters<TSchema>>(
|
|
160
|
+
this.getDescriptor(ModelQueryEvent.AfterSearch, options?.scope),
|
|
161
|
+
filters,
|
|
162
|
+
).setResult(results)
|
|
163
|
+
await this.emitter.emitAsync(afterSearchEvent)
|
|
164
|
+
}
|
|
144
165
|
|
|
145
166
|
// Return the search results
|
|
146
167
|
return {
|
|
@@ -156,21 +177,25 @@ export class ReadOnlyModelService<TSchema extends AnyModelSchema> extends BaseMo
|
|
|
156
177
|
*/
|
|
157
178
|
async count(filters: InferFilters<TSchema>, options?: ISearchOptions<TSchema>): Promise<number> {
|
|
158
179
|
// Emit the before count event
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
180
|
+
if (!options?.doNotDispatchEvents) {
|
|
181
|
+
const beforeCountEvent = new QueryEvent<number, InferFilters<TSchema>>(
|
|
182
|
+
this.getDescriptor(ModelQueryEvent.BeforeCount, options?.scope),
|
|
183
|
+
filters,
|
|
184
|
+
)
|
|
185
|
+
await this.emitter.emitAsync(beforeCountEvent)
|
|
186
|
+
}
|
|
164
187
|
|
|
165
188
|
// Count the records in the repository
|
|
166
189
|
const count = await this.repository.count(filters, options)
|
|
167
190
|
|
|
168
191
|
// Emit the after count event
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
192
|
+
if (!options?.doNotDispatchEvents) {
|
|
193
|
+
const afterCountEvent = new QueryEvent<number, InferFilters<TSchema>>(
|
|
194
|
+
this.getDescriptor(ModelQueryEvent.AfterCount, options?.scope),
|
|
195
|
+
filters,
|
|
196
|
+
).setResult(count)
|
|
197
|
+
await this.emitter.emitAsync(afterCountEvent)
|
|
198
|
+
}
|
|
174
199
|
|
|
175
200
|
// Return the count
|
|
176
201
|
return count
|