@declaro/data 2.0.0-beta.11 → 2.0.0-beta.111

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