@declaro/data 2.0.0-beta.120 → 2.0.0-beta.121
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 +14 -14
- package/dist/browser/index.js.map +7 -7
- package/dist/node/index.cjs +150 -41
- package/dist/node/index.cjs.map +7 -7
- package/dist/node/index.js +150 -41
- package/dist/node/index.js.map +7 -7
- package/dist/ts/application/model-controller.d.ts +22 -1
- package/dist/ts/application/model-controller.d.ts.map +1 -1
- package/dist/ts/domain/events/event-types.d.ts +10 -1
- package/dist/ts/domain/events/event-types.d.ts.map +1 -1
- package/dist/ts/domain/interfaces/repository.d.ts +26 -0
- package/dist/ts/domain/interfaces/repository.d.ts.map +1 -1
- package/dist/ts/domain/services/model-service.d.ts +19 -1
- package/dist/ts/domain/services/model-service.d.ts.map +1 -1
- package/dist/ts/domain/services/read-only-model-service.d.ts +19 -0
- package/dist/ts/domain/services/read-only-model-service.d.ts.map +1 -1
- package/dist/ts/test/mock/repositories/mock-memory-repository.d.ts +21 -3
- package/dist/ts/test/mock/repositories/mock-memory-repository.d.ts.map +1 -1
- package/dist/ts/test/mock/repositories/mock-memory-repository.trash.test.d.ts +2 -0
- package/dist/ts/test/mock/repositories/mock-memory-repository.trash.test.d.ts.map +1 -0
- package/package.json +5 -5
- package/src/application/model-controller.test.ts +191 -0
- package/src/application/model-controller.ts +44 -1
- package/src/domain/events/event-types.ts +9 -0
- package/src/domain/interfaces/repository.ts +29 -0
- package/src/domain/services/model-service.test.ts +307 -0
- package/src/domain/services/model-service.ts +88 -1
- package/src/domain/services/read-only-model-service.test.ts +396 -0
- package/src/domain/services/read-only-model-service.ts +23 -3
- package/src/test/mock/repositories/mock-memory-repository.trash.test.ts +736 -0
- 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
|
-
|
|
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
|
-
|
|
43
|
-
|
|
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
|
-
|
|
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
|
-
|
|
123
|
-
|
|
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(
|
|
174
|
+
this.trash.set(found.key, found.item)
|
|
141
175
|
// Remove the item from data
|
|
142
|
-
this.data.delete(
|
|
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
|
-
|
|
151
|
-
|
|
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
|
-
|
|
168
|
-
this.
|
|
169
|
-
|
|
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
|
-
|
|
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)
|