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

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 (47) hide show
  1. package/dist/browser/index.js +14 -14
  2. package/dist/browser/index.js.map +11 -11
  3. package/dist/node/index.cjs +163 -45
  4. package/dist/node/index.cjs.map +11 -11
  5. package/dist/node/index.js +163 -45
  6. package/dist/node/index.js.map +11 -11
  7. package/dist/ts/application/model-controller.d.ts +22 -1
  8. package/dist/ts/application/model-controller.d.ts.map +1 -1
  9. package/dist/ts/domain/events/domain-event.d.ts +1 -1
  10. package/dist/ts/domain/events/domain-event.d.ts.map +1 -1
  11. package/dist/ts/domain/events/event-types.d.ts +10 -1
  12. package/dist/ts/domain/events/event-types.d.ts.map +1 -1
  13. package/dist/ts/domain/events/mutation-event.d.ts +5 -2
  14. package/dist/ts/domain/events/mutation-event.d.ts.map +1 -1
  15. package/dist/ts/domain/events/query-event.d.ts +4 -2
  16. package/dist/ts/domain/events/query-event.d.ts.map +1 -1
  17. package/dist/ts/domain/events/request-event.d.ts +17 -2
  18. package/dist/ts/domain/events/request-event.d.ts.map +1 -1
  19. package/dist/ts/domain/interfaces/repository.d.ts +26 -0
  20. package/dist/ts/domain/interfaces/repository.d.ts.map +1 -1
  21. package/dist/ts/domain/services/model-service.d.ts +19 -1
  22. package/dist/ts/domain/services/model-service.d.ts.map +1 -1
  23. package/dist/ts/domain/services/read-only-model-service.d.ts +19 -0
  24. package/dist/ts/domain/services/read-only-model-service.d.ts.map +1 -1
  25. package/dist/ts/test/mock/repositories/mock-memory-repository.d.ts +21 -3
  26. package/dist/ts/test/mock/repositories/mock-memory-repository.d.ts.map +1 -1
  27. package/dist/ts/test/mock/repositories/mock-memory-repository.trash.test.d.ts +2 -0
  28. package/dist/ts/test/mock/repositories/mock-memory-repository.trash.test.d.ts.map +1 -0
  29. package/package.json +5 -5
  30. package/src/application/model-controller.test.ts +191 -0
  31. package/src/application/model-controller.ts +44 -1
  32. package/src/domain/events/domain-event.ts +1 -1
  33. package/src/domain/events/event-types.ts +9 -0
  34. package/src/domain/events/mutation-event.test.ts +369 -17
  35. package/src/domain/events/mutation-event.ts +10 -2
  36. package/src/domain/events/query-event.test.ts +218 -18
  37. package/src/domain/events/query-event.ts +8 -2
  38. package/src/domain/events/request-event.test.ts +1 -1
  39. package/src/domain/events/request-event.ts +22 -7
  40. package/src/domain/interfaces/repository.ts +29 -0
  41. package/src/domain/services/model-service.normalization.test.ts +6 -6
  42. package/src/domain/services/model-service.test.ts +311 -7
  43. package/src/domain/services/model-service.ts +88 -1
  44. package/src/domain/services/read-only-model-service.test.ts +396 -0
  45. package/src/domain/services/read-only-model-service.ts +23 -3
  46. package/src/test/mock/repositories/mock-memory-repository.trash.test.ts +736 -0
  47. package/src/test/mock/repositories/mock-memory-repository.ts +146 -46
@@ -10,7 +10,7 @@ import type {
10
10
  InferSummary,
11
11
  } from '../../../shared/utils/schema-inference'
12
12
  import { v4 as uuid } from 'uuid'
13
- import type { ISearchOptions } from '../../../domain/services/read-only-model-service'
13
+ import type { ILoadOptions, ISearchOptions } from '../../../domain/services/read-only-model-service'
14
14
  import type { ICreateOptions, IUpdateOptions } from '../../../domain/services/model-service'
15
15
 
16
16
  export interface IMockMemoryRepositoryArgs<TSchema extends AnyModelSchema> {
@@ -33,17 +33,63 @@ export class MockMemoryRepository<TSchema extends AnyModelSchema> implements IRe
33
33
  }
34
34
  }
35
35
 
36
- async load(input: InferLookup<TSchema>): Promise<InferDetail<TSchema> | null> {
36
+ private findOne(
37
+ lookup: InferLookup<TSchema>,
38
+ map: Map<string, InferDetail<TSchema>>,
39
+ ): InferDetail<TSchema> | undefined {
40
+ if (typeof this.args.lookup === 'function') {
41
+ return Array.from(map.values()).find((data) => this.args.lookup!(data, lookup))
42
+ } else {
43
+ // Default lookup by primary key
44
+ return map.get(lookup[this.entityMetadata.primaryKey])
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Find an item and return both the item and its key
50
+ * @param lookup - The lookup criteria
51
+ * @param map - The map to search in
52
+ * @returns Object containing the item and its key, or undefined if not found
53
+ */
54
+ private findOneWithKey(
55
+ lookup: InferLookup<TSchema>,
56
+ map: Map<string, InferDetail<TSchema>>,
57
+ ): { item: InferDetail<TSchema>; key: string } | undefined {
58
+ if (typeof this.args.lookup === 'function') {
59
+ const item = Array.from(map.values()).find((data) => this.args.lookup!(data, lookup))
60
+ if (item) {
61
+ return { item, key: item[this.entityMetadata.primaryKey!] }
62
+ }
63
+ } else {
64
+ // Default lookup by primary key
65
+ const key = lookup[this.entityMetadata.primaryKey]
66
+ const item = map.get(key)
67
+ if (item) {
68
+ return { item, key }
69
+ }
70
+ }
71
+ return undefined
72
+ }
73
+
74
+ /**
75
+ * Loads a single item by lookup criteria.
76
+ * @param input - The lookup criteria.
77
+ * @param options - Optional load options including removedOnly and includeRemoved.
78
+ * @returns The found item or null if not found.
79
+ */
80
+ async load(input: InferLookup<TSchema>, options: ILoadOptions = {}): Promise<InferDetail<TSchema> | null> {
37
81
  if (!this.entityMetadata?.primaryKey) {
38
82
  throw new Error('Primary key is not defined in the schema metadata')
39
83
  }
40
84
 
41
85
  let item: InferDetail<TSchema> | undefined
42
- if (typeof this.args.lookup === 'function') {
43
- item = Array.from(this.data.values()).find((data) => this.args.lookup!(data, input))
86
+
87
+ if (options.removedOnly) {
88
+ item = this.findOne(input, this.trash)
89
+ } else if (options.includeRemoved) {
90
+ item = this.findOne(input, this.data) ?? this.findOne(input, this.trash)
44
91
  } else {
45
- // Default lookup by primary key
46
- item = await this.data.get(input[this.entityMetadata.primaryKey])
92
+ item = this.findOne(input, this.data)
47
93
  }
48
94
 
49
95
  return item || null
@@ -73,7 +119,7 @@ export class MockMemoryRepository<TSchema extends AnyModelSchema> implements IRe
73
119
  options?: ISearchOptions<TSchema>,
74
120
  ): Promise<InferSearchResults<TSchema>> {
75
121
  const pagination = options?.pagination || { page: 1, pageSize: 25 }
76
- let items = this.applyFilters(input)
122
+ let items = this.applyFilters(input, options)
77
123
 
78
124
  // Apply sorting if provided
79
125
  if (options?.sort && Array.isArray(options.sort)) {
@@ -119,54 +165,30 @@ export class MockMemoryRepository<TSchema extends AnyModelSchema> implements IRe
119
165
  throw new Error('Primary key is not defined in the schema metadata')
120
166
  }
121
167
 
122
- let item: InferDetail<TSchema> | undefined
123
- let itemKey: string
124
-
125
- if (typeof this.args.lookup === 'function') {
126
- item = Array.from(this.data.values()).find((data) => this.args.lookup!(data, lookup))
127
- if (item) {
128
- itemKey = item[this.entityMetadata.primaryKey!]
129
- }
130
- } else {
131
- // Default lookup by primary key
132
- itemKey = lookup[this.entityMetadata.primaryKey]
133
- item = this.data.get(itemKey)
134
- }
135
-
136
- if (!item) {
168
+ const found = this.findOneWithKey(lookup, this.data)
169
+ if (!found) {
137
170
  throw new Error('Item not found')
138
171
  }
172
+
139
173
  // Move the item to trash
140
- this.trash.set(itemKey!, item)
174
+ this.trash.set(found.key, found.item)
141
175
  // Remove the item from data
142
- this.data.delete(itemKey!)
143
- return item
176
+ this.data.delete(found.key)
177
+ return found.item
144
178
  }
145
179
  async restore(lookup: InferLookup<TSchema>): Promise<InferSummary<TSchema>> {
146
180
  if (!this.entityMetadata?.primaryKey) {
147
181
  throw new Error('Primary key is not defined in the schema metadata')
148
182
  }
149
183
 
150
- let item: InferDetail<TSchema> | undefined
151
- let itemKey: string
152
-
153
- if (typeof this.args.lookup === 'function') {
154
- item = Array.from(this.trash.values()).find((data) => this.args.lookup!(data, lookup))
155
- if (item) {
156
- itemKey = item[this.entityMetadata.primaryKey!]
157
- }
158
- } else {
159
- // Default lookup by primary key
160
- itemKey = lookup[this.entityMetadata.primaryKey]
161
- item = this.trash.get(itemKey)
162
- }
163
-
164
- if (!item) {
184
+ const found = this.findOneWithKey(lookup, this.trash)
185
+ if (!found) {
165
186
  throw new Error('Item not found in trash')
166
187
  }
167
- this.trash.delete(itemKey!)
168
- this.data.set(itemKey!, item)
169
- return item
188
+
189
+ this.trash.delete(found.key)
190
+ this.data.set(found.key, found.item)
191
+ return found.item
170
192
  }
171
193
 
172
194
  async create(input: InferInput<TSchema>): Promise<InferDetail<TSchema>> {
@@ -224,7 +246,7 @@ export class MockMemoryRepository<TSchema extends AnyModelSchema> implements IRe
224
246
  }
225
247
 
226
248
  async count(search: InferFilters<TSchema>, options?: ISearchOptions<TSchema> | undefined): Promise<number> {
227
- const filteredItems = this.applyFilters(search)
249
+ const filteredItems = this.applyFilters(search, options)
228
250
  return filteredItems.length
229
251
  }
230
252
 
@@ -253,13 +275,91 @@ export class MockMemoryRepository<TSchema extends AnyModelSchema> implements IRe
253
275
  return await Promise.all(inputs.map((input) => this.upsert(input, options)))
254
276
  }
255
277
 
278
+ async permanentlyDelete(lookup: InferLookup<TSchema>): Promise<InferSummary<TSchema>> {
279
+ if (!this.entityMetadata?.primaryKey) {
280
+ throw new Error('Primary key is not defined in the schema metadata')
281
+ }
282
+
283
+ // Try to find in main data first, then trash
284
+ const foundInData = this.findOneWithKey(lookup, this.data)
285
+ if (foundInData) {
286
+ this.data.delete(foundInData.key)
287
+ return foundInData.item
288
+ }
289
+
290
+ const foundInTrash = this.findOneWithKey(lookup, this.trash)
291
+ if (foundInTrash) {
292
+ this.trash.delete(foundInTrash.key)
293
+ return foundInTrash.item
294
+ }
295
+
296
+ throw new Error('Item not found')
297
+ }
298
+
299
+ async permanentlyDeleteFromTrash(lookup: InferLookup<TSchema>): Promise<InferSummary<TSchema>> {
300
+ if (!this.entityMetadata?.primaryKey) {
301
+ throw new Error('Primary key is not defined in the schema metadata')
302
+ }
303
+
304
+ const found = this.findOneWithKey(lookup, this.trash)
305
+ if (!found) {
306
+ throw new Error('Item not found in trash')
307
+ }
308
+
309
+ this.trash.delete(found.key)
310
+ return found.item
311
+ }
312
+
313
+ async emptyTrash(filters?: InferFilters<TSchema>): Promise<number> {
314
+ if (!filters || Object.keys(filters).length === 0) {
315
+ // Delete all items from trash
316
+ const count = this.trash.size
317
+ this.trash.clear()
318
+ return count
319
+ }
320
+
321
+ // Apply filters to trash items
322
+ const itemsToDelete: string[] = []
323
+ for (const [key, item] of this.trash.entries()) {
324
+ if (typeof this.args.filter === 'function') {
325
+ if (this.args.filter(item, filters)) {
326
+ itemsToDelete.push(key)
327
+ }
328
+ } else {
329
+ // No filter function provided - match all items
330
+ itemsToDelete.push(key)
331
+ }
332
+ }
333
+
334
+ // Delete filtered items
335
+ for (const key of itemsToDelete) {
336
+ this.trash.delete(key)
337
+ }
338
+
339
+ return itemsToDelete.length
340
+ }
341
+
256
342
  /**
257
343
  * Apply filtering logic to all items based on the provided search criteria
258
344
  * @param input - The search/filter criteria
345
+ * @param options - Optional search options including removedOnly and includeRemoved
259
346
  * @returns Filtered array of items
260
347
  */
261
- protected applyFilters(input: InferFilters<TSchema>): InferDetail<TSchema>[] {
262
- return Array.from(this.data.values()).filter((item) => {
348
+ protected applyFilters(input: InferFilters<TSchema>, options?: ISearchOptions<TSchema>): InferDetail<TSchema>[] {
349
+ let sourceItems: InferDetail<TSchema>[]
350
+
351
+ if (options?.removedOnly) {
352
+ // Only search in trash
353
+ sourceItems = Array.from(this.trash.values())
354
+ } else if (options?.includeRemoved) {
355
+ // Search in both data and trash
356
+ sourceItems = [...Array.from(this.data.values()), ...Array.from(this.trash.values())]
357
+ } else {
358
+ // Default: only search in active data
359
+ sourceItems = Array.from(this.data.values())
360
+ }
361
+
362
+ return sourceItems.filter((item) => {
263
363
  // Apply filtering logic based on the input
264
364
  if (typeof this.args.filter === 'function') {
265
365
  return this.args.filter(item, input)