@declaro/data 2.0.0-beta.126 → 2.0.0-beta.128
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 +217 -92
- package/dist/node/index.cjs.map +6 -6
- package/dist/node/index.js +209 -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 +27 -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 +460 -0
- package/src/domain/services/model-service.ts +165 -67
- 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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ActionDescriptor, AnyModelSchema, IActionDescriptor } from '@declaro/core'
|
|
1
|
+
import type { ActionDescriptor, AnyModelSchema, IActionDescriptor, IAnyModel } from '@declaro/core'
|
|
2
2
|
import type {
|
|
3
3
|
InferDetail,
|
|
4
4
|
InferFilters,
|
|
@@ -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>
|
|
@@ -39,6 +49,72 @@ export class ModelService<TSchema extends AnyModelSchema> extends ReadOnlyModelS
|
|
|
39
49
|
return input
|
|
40
50
|
}
|
|
41
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Converts a detail object to a valid input for this service's schema.
|
|
54
|
+
* Picks only fields that exist in the input model and validates/coerces
|
|
55
|
+
* them through the input schema. Useful for duplicating entities or
|
|
56
|
+
* converting a detail from one entity type into an input for another.
|
|
57
|
+
* @param detail The detail object to convert (can be from any schema).
|
|
58
|
+
* @returns A validated input object with coerced values.
|
|
59
|
+
*/
|
|
60
|
+
async detailsToInput(detail: Record<string, unknown>): Promise<InferInput<TSchema>> {
|
|
61
|
+
const inputModel: IAnyModel = this.schema.definition.input
|
|
62
|
+
const inputJsonSchema = inputModel.toJSONSchema()
|
|
63
|
+
const inputFields = Object.keys(inputJsonSchema.properties ?? {})
|
|
64
|
+
|
|
65
|
+
// Pick only fields that exist in the input model
|
|
66
|
+
const picked: Record<string, unknown> = {}
|
|
67
|
+
for (const field of inputFields) {
|
|
68
|
+
if (field in detail) {
|
|
69
|
+
picked[field] = detail[field]
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Validate through the input model to coerce values
|
|
74
|
+
const result = await inputModel.validate(picked as any)
|
|
75
|
+
|
|
76
|
+
if ('value' in result) {
|
|
77
|
+
return result.value as InferInput<TSchema>
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return picked as InferInput<TSchema>
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Duplicates an existing entity by loading it, converting to input, removing the
|
|
85
|
+
* primary key, and creating a new record. Accepts an optional partial input to
|
|
86
|
+
* merge on top of the converted copy before creation.
|
|
87
|
+
* @param lookup The lookup criteria to find the record to duplicate.
|
|
88
|
+
* @param overrides Optional partial input to merge on top of the duplicated data.
|
|
89
|
+
* @param options Optional create options.
|
|
90
|
+
* @returns The newly created duplicate record.
|
|
91
|
+
*/
|
|
92
|
+
async duplicate(
|
|
93
|
+
lookup: InferLookup<TSchema>,
|
|
94
|
+
overrides?: Partial<InferInput<TSchema>>,
|
|
95
|
+
options?: ICreateOptions,
|
|
96
|
+
): Promise<InferDetail<TSchema>> {
|
|
97
|
+
// Load the existing entity
|
|
98
|
+
const existing = await this.load(lookup)
|
|
99
|
+
if (!existing) {
|
|
100
|
+
throw new Error('Item not found')
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Convert the detail to an input
|
|
104
|
+
const input = await this.detailsToInput(existing as Record<string, unknown>)
|
|
105
|
+
|
|
106
|
+
// Remove the primary key to ensure a new record gets created
|
|
107
|
+
if (this.entityMetadata?.primaryKey) {
|
|
108
|
+
delete (input as Record<string, unknown>)[this.entityMetadata.primaryKey]
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Merge optional overrides
|
|
112
|
+
const finalInput = overrides ? Object.assign({}, input, overrides) : input
|
|
113
|
+
|
|
114
|
+
// Create the new record
|
|
115
|
+
return this.create(finalInput as InferInput<TSchema>, options)
|
|
116
|
+
}
|
|
117
|
+
|
|
42
118
|
/**
|
|
43
119
|
* Removes a record by its lookup criteria.
|
|
44
120
|
* @param lookup The lookup criteria to find the record.
|
|
@@ -101,21 +177,25 @@ export class ModelService<TSchema extends AnyModelSchema> extends ReadOnlyModelS
|
|
|
101
177
|
})
|
|
102
178
|
|
|
103
179
|
// Emit the before create event
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
180
|
+
if (!options?.doNotDispatchEvents) {
|
|
181
|
+
const beforeCreateEvent = new MutationEvent<InferDetail<TSchema>, InferInput<TSchema>>(
|
|
182
|
+
this.getDescriptor(ModelMutationAction.BeforeCreate),
|
|
183
|
+
normalizedInput,
|
|
184
|
+
)
|
|
185
|
+
await this.emitter.emitAsync(beforeCreateEvent)
|
|
186
|
+
}
|
|
109
187
|
|
|
110
188
|
// Perform the creation
|
|
111
189
|
const result = await this.repository.create(normalizedInput, options)
|
|
112
190
|
|
|
113
191
|
// Emit the after create event
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
192
|
+
if (!options?.doNotDispatchEvents) {
|
|
193
|
+
const afterCreateEvent = new MutationEvent<InferDetail<TSchema>, InferInput<TSchema>>(
|
|
194
|
+
this.getDescriptor(ModelMutationAction.AfterCreate),
|
|
195
|
+
normalizedInput,
|
|
196
|
+
).setResult(result)
|
|
197
|
+
await this.emitter.emitAsync(afterCreateEvent)
|
|
198
|
+
}
|
|
119
199
|
|
|
120
200
|
// Return the results of the creation
|
|
121
201
|
return await this.normalizeDetail(result)
|
|
@@ -126,7 +206,7 @@ export class ModelService<TSchema extends AnyModelSchema> extends ReadOnlyModelS
|
|
|
126
206
|
input: InferInput<TSchema>,
|
|
127
207
|
options?: IUpdateOptions,
|
|
128
208
|
): Promise<InferDetail<TSchema>> {
|
|
129
|
-
const existing = await this.repository.load(lookup, options)
|
|
209
|
+
const existing = await this.repository.load(lookup, { ...options, doNotDispatchEvents: true })
|
|
130
210
|
// Normalize the input data
|
|
131
211
|
const normalizedInput = await this.normalizeInput(input, {
|
|
132
212
|
existing,
|
|
@@ -134,21 +214,25 @@ export class ModelService<TSchema extends AnyModelSchema> extends ReadOnlyModelS
|
|
|
134
214
|
})
|
|
135
215
|
|
|
136
216
|
// Emit the before update event
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
217
|
+
if (!options?.doNotDispatchEvents) {
|
|
218
|
+
const beforeUpdateEvent = new MutationEvent<InferDetail<TSchema>, InferInput<TSchema>>(
|
|
219
|
+
this.getDescriptor(ModelMutationAction.BeforeUpdate),
|
|
220
|
+
normalizedInput,
|
|
221
|
+
)
|
|
222
|
+
await this.emitter.emitAsync(beforeUpdateEvent)
|
|
223
|
+
}
|
|
142
224
|
|
|
143
225
|
// Perform the update
|
|
144
226
|
const result = await this.repository.update(lookup, normalizedInput, options)
|
|
145
227
|
|
|
146
228
|
// Emit the after update event
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
229
|
+
if (!options?.doNotDispatchEvents) {
|
|
230
|
+
const afterUpdateEvent = new MutationEvent<InferDetail<TSchema>, InferInput<TSchema>>(
|
|
231
|
+
this.getDescriptor(ModelMutationAction.AfterUpdate),
|
|
232
|
+
normalizedInput,
|
|
233
|
+
).setResult(result)
|
|
234
|
+
await this.emitter.emitAsync(afterUpdateEvent)
|
|
235
|
+
}
|
|
152
236
|
|
|
153
237
|
// Return the results of the update
|
|
154
238
|
return await this.normalizeDetail(result)
|
|
@@ -177,7 +261,10 @@ export class ModelService<TSchema extends AnyModelSchema> extends ReadOnlyModelS
|
|
|
177
261
|
{
|
|
178
262
|
[this.entityMetadata.primaryKey]: primaryKeyValue,
|
|
179
263
|
} as InferLookup<TSchema>,
|
|
180
|
-
|
|
264
|
+
{
|
|
265
|
+
...options,
|
|
266
|
+
doNotDispatchEvents: true,
|
|
267
|
+
},
|
|
181
268
|
)
|
|
182
269
|
|
|
183
270
|
if (existingItem) {
|
|
@@ -198,21 +285,25 @@ export class ModelService<TSchema extends AnyModelSchema> extends ReadOnlyModelS
|
|
|
198
285
|
})
|
|
199
286
|
|
|
200
287
|
// Emit the before upsert event
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
288
|
+
if (!options?.doNotDispatchEvents) {
|
|
289
|
+
const beforeUpsertEvent = new MutationEvent<InferDetail<TSchema>, InferInput<TSchema>>(
|
|
290
|
+
this.getDescriptor(beforeOperation),
|
|
291
|
+
normalizedInput,
|
|
292
|
+
)
|
|
293
|
+
await this.emitter.emitAsync(beforeUpsertEvent)
|
|
294
|
+
}
|
|
206
295
|
|
|
207
296
|
// Perform the upsert operation
|
|
208
297
|
const result = await this.repository.upsert(normalizedInput, options)
|
|
209
298
|
|
|
210
299
|
// Emit the after upsert event
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
300
|
+
if (!options?.doNotDispatchEvents) {
|
|
301
|
+
const afterUpsertEvent = new MutationEvent<InferDetail<TSchema>, InferInput<TSchema>>(
|
|
302
|
+
this.getDescriptor(afterOperation),
|
|
303
|
+
normalizedInput,
|
|
304
|
+
).setResult(result)
|
|
305
|
+
await this.emitter.emitAsync(afterUpsertEvent)
|
|
306
|
+
}
|
|
216
307
|
|
|
217
308
|
// Return the results of the upsert operation
|
|
218
309
|
return await this.normalizeDetail(result)
|
|
@@ -268,7 +359,10 @@ export class ModelService<TSchema extends AnyModelSchema> extends ReadOnlyModelS
|
|
|
268
359
|
const existingEntitiesMap = new Map<string | number, InferDetail<TSchema>>()
|
|
269
360
|
if (uniqueLookups.size > 0) {
|
|
270
361
|
const lookups = Array.from(uniqueLookups.values())
|
|
271
|
-
const existingEntities = await this.loadMany(lookups,
|
|
362
|
+
const existingEntities = await this.loadMany(lookups, {
|
|
363
|
+
...options,
|
|
364
|
+
doNotDispatchEvents: true,
|
|
365
|
+
})
|
|
272
366
|
existingEntities.forEach((entity) => {
|
|
273
367
|
if (entity) {
|
|
274
368
|
const pkValue = this.getPrimaryKeyValue(entity)
|
|
@@ -306,44 +400,48 @@ export class ModelService<TSchema extends AnyModelSchema> extends ReadOnlyModelS
|
|
|
306
400
|
const normalizedInputs = await Promise.all(normalizationPromises)
|
|
307
401
|
|
|
308
402
|
// Create before events
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
403
|
+
if (!options?.doNotDispatchEvents) {
|
|
404
|
+
const beforeEvents: MutationEvent<InferDetail<TSchema>, InferInput<TSchema>>[] = []
|
|
405
|
+
for (const inputInfo of inputInfos) {
|
|
406
|
+
beforeEvents.push(
|
|
407
|
+
new MutationEvent<InferDetail<TSchema>, InferInput<TSchema>>(
|
|
408
|
+
this.getDescriptor(inputInfo.operation!),
|
|
409
|
+
inputInfo.input,
|
|
410
|
+
),
|
|
411
|
+
)
|
|
412
|
+
}
|
|
318
413
|
|
|
319
|
-
|
|
320
|
-
|
|
414
|
+
// Emit all before events
|
|
415
|
+
await Promise.all(beforeEvents.map((event) => this.emitter.emitAsync(event)))
|
|
416
|
+
}
|
|
321
417
|
|
|
322
418
|
// Perform the bulk upsert operation with all normalized inputs
|
|
323
419
|
const results = await this.repository.bulkUpsert(normalizedInputs, options)
|
|
324
420
|
|
|
325
421
|
// Create after events and return results
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
422
|
+
if (!options?.doNotDispatchEvents) {
|
|
423
|
+
const afterEvents: MutationEvent<InferDetail<TSchema>, InferInput<TSchema>>[] = []
|
|
424
|
+
|
|
425
|
+
for (let i = 0; i < inputInfos.length; i++) {
|
|
426
|
+
const inputInfo = inputInfos[i]
|
|
427
|
+
const result = results[i]
|
|
428
|
+
|
|
429
|
+
const afterOperation =
|
|
430
|
+
inputInfo.operation === ModelMutationAction.BeforeCreate
|
|
431
|
+
? ModelMutationAction.AfterCreate
|
|
432
|
+
: ModelMutationAction.AfterUpdate
|
|
433
|
+
|
|
434
|
+
afterEvents.push(
|
|
435
|
+
new MutationEvent<InferDetail<TSchema>, InferInput<TSchema>>(
|
|
436
|
+
this.getDescriptor(afterOperation),
|
|
437
|
+
inputInfo.input,
|
|
438
|
+
).setResult(result),
|
|
439
|
+
)
|
|
440
|
+
}
|
|
344
441
|
|
|
345
|
-
|
|
346
|
-
|
|
442
|
+
// Emit all after events
|
|
443
|
+
await Promise.all(afterEvents.map((event) => this.emitter.emitAsync(event)))
|
|
444
|
+
}
|
|
347
445
|
|
|
348
446
|
// Return normalized results
|
|
349
447
|
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
|
})
|