@declaro/data 2.0.0-beta.12 → 2.0.0-beta.120

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.
Files changed (127) hide show
  1. package/{LICENSE → LICENSE.md} +1 -1
  2. package/README.md +0 -0
  3. package/dist/browser/index.js +26 -0
  4. package/dist/browser/index.js.map +93 -0
  5. package/dist/node/index.cjs +13117 -0
  6. package/dist/node/index.cjs.map +93 -0
  7. package/dist/node/index.js +13096 -0
  8. package/dist/node/index.js.map +93 -0
  9. package/dist/ts/application/model-controller.d.ts +29 -0
  10. package/dist/ts/application/model-controller.d.ts.map +1 -0
  11. package/dist/ts/application/model-controller.test.d.ts +2 -0
  12. package/dist/ts/application/model-controller.test.d.ts.map +1 -0
  13. package/dist/ts/application/read-only-model-controller.d.ts +20 -0
  14. package/dist/ts/application/read-only-model-controller.d.ts.map +1 -0
  15. package/dist/ts/application/read-only-model-controller.test.d.ts +2 -0
  16. package/dist/ts/application/read-only-model-controller.test.d.ts.map +1 -0
  17. package/dist/ts/domain/events/domain-event.d.ts +41 -0
  18. package/dist/ts/domain/events/domain-event.d.ts.map +1 -0
  19. package/dist/ts/domain/events/domain-event.test.d.ts +2 -0
  20. package/dist/ts/domain/events/domain-event.test.d.ts.map +1 -0
  21. package/dist/ts/domain/events/event-types.d.ts +25 -0
  22. package/dist/ts/domain/events/event-types.d.ts.map +1 -0
  23. package/dist/ts/domain/events/mutation-event.d.ts +6 -0
  24. package/dist/ts/domain/events/mutation-event.d.ts.map +1 -0
  25. package/dist/ts/domain/events/mutation-event.test.d.ts +2 -0
  26. package/dist/ts/domain/events/mutation-event.test.d.ts.map +1 -0
  27. package/dist/ts/domain/events/query-event.d.ts +6 -0
  28. package/dist/ts/domain/events/query-event.d.ts.map +1 -0
  29. package/dist/ts/domain/events/query-event.test.d.ts +2 -0
  30. package/dist/ts/domain/events/query-event.test.d.ts.map +1 -0
  31. package/dist/ts/domain/events/request-event.d.ts +11 -0
  32. package/dist/ts/domain/events/request-event.d.ts.map +1 -0
  33. package/dist/ts/domain/events/request-event.test.d.ts +2 -0
  34. package/dist/ts/domain/events/request-event.test.d.ts.map +1 -0
  35. package/dist/ts/domain/interfaces/repository.d.ts +84 -0
  36. package/dist/ts/domain/interfaces/repository.d.ts.map +1 -0
  37. package/dist/ts/domain/models/pagination.d.ts +28 -0
  38. package/dist/ts/domain/models/pagination.d.ts.map +1 -0
  39. package/dist/ts/domain/services/base-model-service.d.ts +23 -0
  40. package/dist/ts/domain/services/base-model-service.d.ts.map +1 -0
  41. package/dist/ts/domain/services/model-service-args.d.ts +9 -0
  42. package/dist/ts/domain/services/model-service-args.d.ts.map +1 -0
  43. package/dist/ts/domain/services/model-service.d.ts +54 -0
  44. package/dist/ts/domain/services/model-service.d.ts.map +1 -0
  45. package/dist/ts/domain/services/model-service.normalization.test.d.ts +2 -0
  46. package/dist/ts/domain/services/model-service.normalization.test.d.ts.map +1 -0
  47. package/dist/ts/domain/services/model-service.test.d.ts +2 -0
  48. package/dist/ts/domain/services/model-service.test.d.ts.map +1 -0
  49. package/dist/ts/domain/services/read-only-model-service.d.ts +57 -0
  50. package/dist/ts/domain/services/read-only-model-service.d.ts.map +1 -0
  51. package/dist/ts/domain/services/read-only-model-service.test.d.ts +2 -0
  52. package/dist/ts/domain/services/read-only-model-service.test.d.ts.map +1 -0
  53. package/dist/ts/index.d.ts +18 -0
  54. package/dist/ts/index.d.ts.map +1 -0
  55. package/dist/ts/shared/utils/schema-inference.d.ts +23 -0
  56. package/dist/ts/shared/utils/schema-inference.d.ts.map +1 -0
  57. package/dist/ts/shared/utils/schema-inheritance.d.ts +24 -0
  58. package/dist/ts/shared/utils/schema-inheritance.d.ts.map +1 -0
  59. package/dist/ts/test/mock/models/mock-book-models.d.ts +42 -0
  60. package/dist/ts/test/mock/models/mock-book-models.d.ts.map +1 -0
  61. package/dist/ts/test/mock/repositories/mock-memory-repository.assign.test.d.ts +2 -0
  62. package/dist/ts/test/mock/repositories/mock-memory-repository.assign.test.d.ts.map +1 -0
  63. package/dist/ts/test/mock/repositories/mock-memory-repository.basic.test.d.ts +2 -0
  64. package/dist/ts/test/mock/repositories/mock-memory-repository.basic.test.d.ts.map +1 -0
  65. package/dist/ts/test/mock/repositories/mock-memory-repository.bulk-upsert.test.d.ts +2 -0
  66. package/dist/ts/test/mock/repositories/mock-memory-repository.bulk-upsert.test.d.ts.map +1 -0
  67. package/dist/ts/test/mock/repositories/mock-memory-repository.count.test.d.ts +2 -0
  68. package/dist/ts/test/mock/repositories/mock-memory-repository.count.test.d.ts.map +1 -0
  69. package/dist/ts/test/mock/repositories/mock-memory-repository.custom-lookup.test.d.ts +1 -0
  70. package/dist/ts/test/mock/repositories/mock-memory-repository.custom-lookup.test.d.ts.map +1 -0
  71. package/dist/ts/test/mock/repositories/mock-memory-repository.d.ts +44 -0
  72. package/dist/ts/test/mock/repositories/mock-memory-repository.d.ts.map +1 -0
  73. package/dist/ts/test/mock/repositories/mock-memory-repository.search.test.d.ts +2 -0
  74. package/dist/ts/test/mock/repositories/mock-memory-repository.search.test.d.ts.map +1 -0
  75. package/dist/ts/test/mock/repositories/mock-memory-repository.upsert.test.d.ts +2 -0
  76. package/dist/ts/test/mock/repositories/mock-memory-repository.upsert.test.d.ts.map +1 -0
  77. package/package.json +45 -42
  78. package/src/application/model-controller.test.ts +503 -0
  79. package/src/application/model-controller.ts +92 -0
  80. package/src/application/read-only-model-controller.test.ts +335 -0
  81. package/src/application/read-only-model-controller.ts +61 -0
  82. package/src/domain/events/domain-event.test.ts +82 -0
  83. package/src/domain/events/domain-event.ts +69 -0
  84. package/src/domain/events/event-types.ts +25 -0
  85. package/src/domain/events/mutation-event.test.ts +38 -0
  86. package/src/domain/events/mutation-event.ts +8 -0
  87. package/src/domain/events/query-event.test.ts +28 -0
  88. package/src/domain/events/query-event.ts +8 -0
  89. package/src/domain/events/request-event.test.ts +38 -0
  90. package/src/domain/events/request-event.ts +32 -0
  91. package/src/domain/interfaces/repository.ts +107 -0
  92. package/src/domain/models/pagination.ts +28 -0
  93. package/src/domain/services/base-model-service.ts +54 -0
  94. package/src/domain/services/model-service-args.ts +9 -0
  95. package/src/domain/services/model-service.normalization.test.ts +704 -0
  96. package/src/domain/services/model-service.test.ts +633 -0
  97. package/src/domain/services/model-service.ts +345 -0
  98. package/src/domain/services/read-only-model-service.test.ts +432 -0
  99. package/src/domain/services/read-only-model-service.ts +158 -0
  100. package/src/index.ts +17 -4
  101. package/src/shared/utils/schema-inference.ts +26 -0
  102. package/src/shared/utils/schema-inheritance.ts +28 -0
  103. package/src/test/mock/models/mock-book-models.ts +78 -0
  104. package/src/test/mock/repositories/mock-memory-repository.assign.test.ts +215 -0
  105. package/src/test/mock/repositories/mock-memory-repository.basic.test.ts +129 -0
  106. package/src/test/mock/repositories/mock-memory-repository.bulk-upsert.test.ts +159 -0
  107. package/src/test/mock/repositories/mock-memory-repository.count.test.ts +98 -0
  108. package/src/test/mock/repositories/mock-memory-repository.custom-lookup.test.ts +0 -0
  109. package/src/test/mock/repositories/mock-memory-repository.search.test.ts +265 -0
  110. package/src/test/mock/repositories/mock-memory-repository.ts +301 -0
  111. package/src/test/mock/repositories/mock-memory-repository.upsert.test.ts +108 -0
  112. package/dist/databaseConnection.d.ts +0 -24
  113. package/dist/datastoreAbstract.d.ts +0 -37
  114. package/dist/declaro-data.cjs +0 -1
  115. package/dist/declaro-data.mjs +0 -250
  116. package/dist/hydrateEntity.d.ts +0 -8
  117. package/dist/index.d.ts +0 -4
  118. package/dist/serverConnection.d.ts +0 -15
  119. package/dist/trackedStatus.d.ts +0 -15
  120. package/src/databaseConnection.ts +0 -137
  121. package/src/datastoreAbstract.ts +0 -190
  122. package/src/hydrateEntity.ts +0 -36
  123. package/src/placeholder.test.ts +0 -7
  124. package/src/serverConnection.ts +0 -74
  125. package/src/trackedStatus.ts +0 -35
  126. package/tsconfig.json +0 -10
  127. package/vite.config.ts +0 -23
@@ -0,0 +1,345 @@
1
+ import type { ActionDescriptor, AnyModelSchema, IActionDescriptor } from '@declaro/core'
2
+ import type { InferDetail, InferInput, InferLookup, InferSummary } from '../../shared/utils/schema-inference'
3
+ import { ModelMutationAction, ModelQueryEvent } from '../events/event-types'
4
+ import { MutationEvent } from '../events/mutation-event'
5
+ import type { IModelServiceArgs } from './model-service-args'
6
+ import { ReadOnlyModelService, type ILoadOptions } from './read-only-model-service'
7
+ import type { IActionOptions } from './base-model-service'
8
+
9
+ export interface ICreateOptions extends IActionOptions {}
10
+ export interface IUpdateOptions extends IActionOptions {}
11
+
12
+ export interface INormalizeInputArgs<TSchema extends AnyModelSchema> {
13
+ existing?: InferDetail<TSchema>
14
+ descriptor: ActionDescriptor
15
+ }
16
+
17
+ export class ModelService<TSchema extends AnyModelSchema> extends ReadOnlyModelService<TSchema> {
18
+ constructor(args: IModelServiceArgs<TSchema>) {
19
+ super(args)
20
+ }
21
+
22
+ /**
23
+ * Normalizes input data before processing. This method can be overridden by subclasses
24
+ * to implement custom input normalization logic (e.g., trimming strings, setting defaults, etc.).
25
+ * By default, this method returns the input unchanged.
26
+ * @param input The input data to normalize.
27
+ * @returns The normalized input data.
28
+ */
29
+ protected async normalizeInput(
30
+ input: InferInput<TSchema>,
31
+ args: INormalizeInputArgs<TSchema>,
32
+ ): Promise<InferInput<TSchema>> {
33
+ return input
34
+ }
35
+
36
+ /**
37
+ * Removes a record by its lookup criteria.
38
+ * @param lookup The lookup criteria to find the record.
39
+ * @returns The removed record.
40
+ */
41
+ async remove(lookup: InferLookup<TSchema>, options?: ILoadOptions): Promise<InferSummary<TSchema>> {
42
+ // Emit the before remove event
43
+ const beforeRemoveEvent = new MutationEvent<InferSummary<TSchema>, InferLookup<TSchema>>(
44
+ this.getDescriptor(ModelMutationAction.BeforeRemove),
45
+ lookup,
46
+ )
47
+ await this.emitter.emitAsync(beforeRemoveEvent)
48
+
49
+ // Perform the removal
50
+ const result = await this.repository.remove(lookup, options)
51
+
52
+ // Emit the after remove event
53
+ const afterRemoveEvent = new MutationEvent<InferSummary<TSchema>, InferLookup<TSchema>>(
54
+ this.getDescriptor(ModelMutationAction.AfterRemove),
55
+ lookup,
56
+ ).setResult(result)
57
+ await this.emitter.emitAsync(afterRemoveEvent)
58
+
59
+ // Return the results of the removal
60
+ return await this.normalizeSummary(result)
61
+ }
62
+
63
+ /**
64
+ * Restores a record by its lookup criteria.
65
+ * If a soft-deleted copy exists, it will be restored.
66
+ * @param lookup The lookup criteria to find the record to restore.
67
+ * @returns
68
+ */
69
+ async restore(lookup: InferLookup<TSchema>, options?: ILoadOptions): Promise<InferSummary<TSchema>> {
70
+ // Emit the before restore event
71
+ const beforeRestoreEvent = new MutationEvent<InferSummary<TSchema>, InferLookup<TSchema>>(
72
+ this.getDescriptor(ModelMutationAction.BeforeRestore),
73
+ lookup,
74
+ )
75
+ await this.emitter.emitAsync(beforeRestoreEvent)
76
+
77
+ // Perform the restore operation
78
+ const result = await this.repository.restore(lookup, options)
79
+
80
+ // Emit the after restore event
81
+ const afterRestoreEvent = new MutationEvent<InferSummary<TSchema>, InferLookup<TSchema>>(
82
+ this.getDescriptor(ModelMutationAction.AfterRestore),
83
+ lookup,
84
+ ).setResult(result)
85
+ await this.emitter.emitAsync(afterRestoreEvent)
86
+
87
+ // Return the results of the restore operation
88
+ return await this.normalizeSummary(result)
89
+ }
90
+
91
+ async create(input: InferInput<TSchema>, options?: ICreateOptions): Promise<InferDetail<TSchema>> {
92
+ // Normalize the input data
93
+ const normalizedInput = await this.normalizeInput(input, {
94
+ descriptor: this.getDescriptor(ModelMutationAction.Create),
95
+ })
96
+
97
+ // Emit the before create event
98
+ const beforeCreateEvent = new MutationEvent<InferDetail<TSchema>, InferInput<TSchema>>(
99
+ this.getDescriptor(ModelMutationAction.BeforeCreate),
100
+ normalizedInput,
101
+ )
102
+ await this.emitter.emitAsync(beforeCreateEvent)
103
+
104
+ // Perform the creation
105
+ const result = await this.repository.create(normalizedInput, options)
106
+
107
+ // Emit the after create event
108
+ const afterCreateEvent = new MutationEvent<InferDetail<TSchema>, InferInput<TSchema>>(
109
+ this.getDescriptor(ModelMutationAction.AfterCreate),
110
+ normalizedInput,
111
+ ).setResult(result)
112
+ await this.emitter.emitAsync(afterCreateEvent)
113
+
114
+ // Return the results of the creation
115
+ return await this.normalizeDetail(result)
116
+ }
117
+
118
+ async update(
119
+ lookup: InferLookup<TSchema>,
120
+ input: InferInput<TSchema>,
121
+ options?: IUpdateOptions,
122
+ ): Promise<InferDetail<TSchema>> {
123
+ const existing = await this.repository.load(lookup, options)
124
+ // Normalize the input data
125
+ const normalizedInput = await this.normalizeInput(input, {
126
+ existing,
127
+ descriptor: this.getDescriptor(ModelMutationAction.Update),
128
+ })
129
+
130
+ // Emit the before update event
131
+ const beforeUpdateEvent = new MutationEvent<InferDetail<TSchema>, InferInput<TSchema>>(
132
+ this.getDescriptor(ModelMutationAction.BeforeUpdate),
133
+ normalizedInput,
134
+ )
135
+ await this.emitter.emitAsync(beforeUpdateEvent)
136
+
137
+ // Perform the update
138
+ const result = await this.repository.update(lookup, normalizedInput, options)
139
+
140
+ // Emit the after update event
141
+ const afterUpdateEvent = new MutationEvent<InferDetail<TSchema>, InferInput<TSchema>>(
142
+ this.getDescriptor(ModelMutationAction.AfterUpdate),
143
+ normalizedInput,
144
+ ).setResult(result)
145
+ await this.emitter.emitAsync(afterUpdateEvent)
146
+
147
+ // Return the results of the update
148
+ return await this.normalizeDetail(result)
149
+ }
150
+
151
+ /**
152
+ * Upserts a record (creates if it doesn't exist, updates if it does).
153
+ * @param input The input data for the upsert operation.
154
+ * @param options Optional create or update options.
155
+ * @returns The upserted record.
156
+ */
157
+ async upsert(input: InferInput<TSchema>, options?: ICreateOptions | IUpdateOptions): Promise<InferDetail<TSchema>> {
158
+ const primaryKeyValue = this.getPrimaryKeyValue(input)
159
+
160
+ let operation: ModelMutationAction
161
+ let beforeOperation: ModelMutationAction
162
+ let afterOperation: ModelMutationAction
163
+ let existingItem: InferDetail<TSchema> | undefined = undefined
164
+
165
+ if (primaryKeyValue === undefined) {
166
+ operation = ModelMutationAction.Create
167
+ beforeOperation = ModelMutationAction.BeforeCreate
168
+ afterOperation = ModelMutationAction.AfterCreate
169
+ } else {
170
+ existingItem = await this.load(
171
+ {
172
+ [this.entityMetadata.primaryKey]: primaryKeyValue,
173
+ } as InferLookup<TSchema>,
174
+ options,
175
+ )
176
+
177
+ if (existingItem) {
178
+ operation = ModelMutationAction.Update
179
+ beforeOperation = ModelMutationAction.BeforeUpdate
180
+ afterOperation = ModelMutationAction.AfterUpdate
181
+ } else {
182
+ operation = ModelMutationAction.Create
183
+ beforeOperation = ModelMutationAction.BeforeCreate
184
+ afterOperation = ModelMutationAction.AfterCreate
185
+ }
186
+ }
187
+
188
+ // Normalize the input data
189
+ const normalizedInput = await this.normalizeInput(input, {
190
+ descriptor: this.getDescriptor(operation),
191
+ existing: existingItem,
192
+ })
193
+
194
+ // Emit the before upsert event
195
+ const beforeUpsertEvent = new MutationEvent<InferDetail<TSchema>, InferInput<TSchema>>(
196
+ this.getDescriptor(beforeOperation),
197
+ normalizedInput,
198
+ )
199
+ await this.emitter.emitAsync(beforeUpsertEvent)
200
+
201
+ // Perform the upsert operation
202
+ const result = await this.repository.upsert(normalizedInput, options)
203
+
204
+ // Emit the after upsert event
205
+ const afterUpsertEvent = new MutationEvent<InferDetail<TSchema>, InferInput<TSchema>>(
206
+ this.getDescriptor(afterOperation),
207
+ normalizedInput,
208
+ ).setResult(result)
209
+ await this.emitter.emitAsync(afterUpsertEvent)
210
+
211
+ // Return the results of the upsert operation
212
+ return await this.normalizeDetail(result)
213
+ }
214
+
215
+ /**
216
+ * Bulk upserts multiple records (creates if they don't exist, updates if they do).
217
+ * @param inputs Array of input data for the bulk upsert operation.
218
+ * @param options Optional create or update options.
219
+ * @returns Array of upserted records.
220
+ */
221
+ async bulkUpsert(
222
+ inputs: InferInput<TSchema>[],
223
+ options?: ICreateOptions | IUpdateOptions,
224
+ ): Promise<InferDetail<TSchema>[]> {
225
+ if (inputs.length === 0) {
226
+ return []
227
+ }
228
+
229
+ // Keep track of input metadata for each position (preserves order and duplicates)
230
+ type InputInfo = {
231
+ input: InferInput<TSchema>
232
+ index: number
233
+ primaryKeyValue?: string | number
234
+ existingEntity?: InferDetail<TSchema>
235
+ operation?: ModelMutationAction
236
+ }
237
+
238
+ const inputInfos: InputInfo[] = []
239
+ const uniqueLookups = new Map<string | number, InferLookup<TSchema>>()
240
+
241
+ // Process each input and collect unique lookups
242
+ for (let i = 0; i < inputs.length; i++) {
243
+ const input = inputs[i]
244
+ const primaryKeyValue = this.getPrimaryKeyValue(input)
245
+
246
+ const inputInfo: InputInfo = {
247
+ input,
248
+ index: i,
249
+ primaryKeyValue,
250
+ }
251
+ inputInfos.push(inputInfo)
252
+
253
+ // Collect unique lookups for entities that have primary keys
254
+ if (primaryKeyValue !== undefined) {
255
+ uniqueLookups.set(primaryKeyValue, {
256
+ [this.entityMetadata.primaryKey]: primaryKeyValue,
257
+ } as InferLookup<TSchema>)
258
+ }
259
+ }
260
+
261
+ // Load existing entities for unique primary keys
262
+ const existingEntitiesMap = new Map<string | number, InferDetail<TSchema>>()
263
+ if (uniqueLookups.size > 0) {
264
+ const lookups = Array.from(uniqueLookups.values())
265
+ const existingEntities = await this.loadMany(lookups, options)
266
+ existingEntities.forEach((entity) => {
267
+ if (entity) {
268
+ const pkValue = this.getPrimaryKeyValue(entity)
269
+ if (pkValue !== undefined) {
270
+ existingEntitiesMap.set(pkValue, entity)
271
+ }
272
+ }
273
+ })
274
+ }
275
+
276
+ // Normalize all inputs and determine operations in parallel
277
+ const normalizationPromises = inputInfos.map(async (inputInfo) => {
278
+ // Set existing entity if found
279
+ if (inputInfo.primaryKeyValue !== undefined) {
280
+ inputInfo.existingEntity = existingEntitiesMap.get(inputInfo.primaryKeyValue)
281
+ }
282
+
283
+ // Determine operation type
284
+ inputInfo.operation = inputInfo.existingEntity
285
+ ? ModelMutationAction.BeforeUpdate
286
+ : ModelMutationAction.BeforeCreate
287
+
288
+ // Normalize the input
289
+ const normalizedInput = await this.normalizeInput(inputInfo.input, {
290
+ existing: inputInfo.existingEntity,
291
+ descriptor: this.getDescriptor(
292
+ inputInfo.existingEntity ? ModelMutationAction.Update : ModelMutationAction.Create,
293
+ ),
294
+ })
295
+
296
+ inputInfo.input = normalizedInput
297
+ return normalizedInput
298
+ })
299
+
300
+ const normalizedInputs = await Promise.all(normalizationPromises)
301
+
302
+ // Create before events
303
+ const beforeEvents: MutationEvent<InferDetail<TSchema>, InferInput<TSchema>>[] = []
304
+ for (const inputInfo of inputInfos) {
305
+ beforeEvents.push(
306
+ new MutationEvent<InferDetail<TSchema>, InferInput<TSchema>>(
307
+ this.getDescriptor(inputInfo.operation!),
308
+ inputInfo.input,
309
+ ),
310
+ )
311
+ }
312
+
313
+ // Emit all before events
314
+ await Promise.all(beforeEvents.map((event) => this.emitter.emitAsync(event)))
315
+
316
+ // Perform the bulk upsert operation with all normalized inputs
317
+ const results = await this.repository.bulkUpsert(normalizedInputs, options)
318
+
319
+ // Create after events and return results
320
+ const afterEvents: MutationEvent<InferDetail<TSchema>, InferInput<TSchema>>[] = []
321
+
322
+ for (let i = 0; i < inputInfos.length; i++) {
323
+ const inputInfo = inputInfos[i]
324
+ const result = results[i]
325
+
326
+ const afterOperation =
327
+ inputInfo.operation === ModelMutationAction.BeforeCreate
328
+ ? ModelMutationAction.AfterCreate
329
+ : ModelMutationAction.AfterUpdate
330
+
331
+ afterEvents.push(
332
+ new MutationEvent<InferDetail<TSchema>, InferInput<TSchema>>(
333
+ this.getDescriptor(afterOperation),
334
+ inputInfo.input,
335
+ ).setResult(result),
336
+ )
337
+ }
338
+
339
+ // Emit all after events
340
+ await Promise.all(afterEvents.map((event) => this.emitter.emitAsync(event)))
341
+
342
+ // Return normalized results
343
+ return await Promise.all(results.map((result) => this.normalizeDetail(result)))
344
+ }
345
+ }