@declaro/data 2.0.0-beta.14 → 2.0.0-beta.140
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/{LICENSE → LICENSE.md} +1 -1
- package/README.md +0 -0
- package/dist/browser/index.js +26 -0
- package/dist/browser/index.js.map +93 -0
- package/dist/node/index.cjs +13372 -0
- package/dist/node/index.cjs.map +93 -0
- package/dist/node/index.js +13351 -0
- package/dist/node/index.js.map +93 -0
- package/dist/ts/application/model-controller.d.ts +60 -0
- package/dist/ts/application/model-controller.d.ts.map +1 -0
- package/dist/ts/application/model-controller.test.d.ts +2 -0
- package/dist/ts/application/model-controller.test.d.ts.map +1 -0
- package/dist/ts/application/read-only-model-controller.d.ts +24 -0
- package/dist/ts/application/read-only-model-controller.d.ts.map +1 -0
- package/dist/ts/application/read-only-model-controller.test.d.ts +2 -0
- package/dist/ts/application/read-only-model-controller.test.d.ts.map +1 -0
- package/dist/ts/domain/events/domain-event.d.ts +41 -0
- package/dist/ts/domain/events/domain-event.d.ts.map +1 -0
- package/dist/ts/domain/events/domain-event.test.d.ts +2 -0
- package/dist/ts/domain/events/domain-event.test.d.ts.map +1 -0
- package/dist/ts/domain/events/event-types.d.ts +37 -0
- package/dist/ts/domain/events/event-types.d.ts.map +1 -0
- package/dist/ts/domain/events/mutation-event.d.ts +41 -0
- package/dist/ts/domain/events/mutation-event.d.ts.map +1 -0
- package/dist/ts/domain/events/mutation-event.test.d.ts +2 -0
- package/dist/ts/domain/events/mutation-event.test.d.ts.map +1 -0
- package/dist/ts/domain/events/query-event.d.ts +8 -0
- package/dist/ts/domain/events/query-event.d.ts.map +1 -0
- package/dist/ts/domain/events/query-event.test.d.ts +2 -0
- package/dist/ts/domain/events/query-event.test.d.ts.map +1 -0
- package/dist/ts/domain/events/request-event.d.ts +26 -0
- package/dist/ts/domain/events/request-event.d.ts.map +1 -0
- package/dist/ts/domain/events/request-event.test.d.ts +2 -0
- package/dist/ts/domain/events/request-event.test.d.ts.map +1 -0
- package/dist/ts/domain/interfaces/repository.d.ts +110 -0
- package/dist/ts/domain/interfaces/repository.d.ts.map +1 -0
- package/dist/ts/domain/models/pagination.d.ts +28 -0
- package/dist/ts/domain/models/pagination.d.ts.map +1 -0
- package/dist/ts/domain/services/base-model-service.d.ts +23 -0
- package/dist/ts/domain/services/base-model-service.d.ts.map +1 -0
- package/dist/ts/domain/services/model-service-args.d.ts +9 -0
- package/dist/ts/domain/services/model-service-args.d.ts.map +1 -0
- package/dist/ts/domain/services/model-service.d.ts +99 -0
- package/dist/ts/domain/services/model-service.d.ts.map +1 -0
- package/dist/ts/domain/services/model-service.normalization.test.d.ts +2 -0
- package/dist/ts/domain/services/model-service.normalization.test.d.ts.map +1 -0
- package/dist/ts/domain/services/model-service.test.d.ts +2 -0
- package/dist/ts/domain/services/model-service.test.d.ts.map +1 -0
- package/dist/ts/domain/services/read-only-model-service.d.ts +90 -0
- package/dist/ts/domain/services/read-only-model-service.d.ts.map +1 -0
- package/dist/ts/domain/services/read-only-model-service.test.d.ts +2 -0
- package/dist/ts/domain/services/read-only-model-service.test.d.ts.map +1 -0
- package/dist/ts/index.d.ts +18 -0
- package/dist/ts/index.d.ts.map +1 -0
- package/dist/ts/shared/utils/schema-inference.d.ts +23 -0
- package/dist/ts/shared/utils/schema-inference.d.ts.map +1 -0
- package/dist/ts/shared/utils/schema-inheritance.d.ts +24 -0
- package/dist/ts/shared/utils/schema-inheritance.d.ts.map +1 -0
- 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/dist/ts/test/mock/models/mock-book-models.d.ts +42 -0
- package/dist/ts/test/mock/models/mock-book-models.d.ts.map +1 -0
- package/dist/ts/test/mock/repositories/mock-memory-repository.assign.test.d.ts +2 -0
- package/dist/ts/test/mock/repositories/mock-memory-repository.assign.test.d.ts.map +1 -0
- package/dist/ts/test/mock/repositories/mock-memory-repository.basic.test.d.ts +2 -0
- package/dist/ts/test/mock/repositories/mock-memory-repository.basic.test.d.ts.map +1 -0
- package/dist/ts/test/mock/repositories/mock-memory-repository.bulk-upsert.test.d.ts +2 -0
- package/dist/ts/test/mock/repositories/mock-memory-repository.bulk-upsert.test.d.ts.map +1 -0
- package/dist/ts/test/mock/repositories/mock-memory-repository.count.test.d.ts +2 -0
- package/dist/ts/test/mock/repositories/mock-memory-repository.count.test.d.ts.map +1 -0
- package/dist/ts/test/mock/repositories/mock-memory-repository.d.ts +62 -0
- package/dist/ts/test/mock/repositories/mock-memory-repository.d.ts.map +1 -0
- package/dist/ts/test/mock/repositories/mock-memory-repository.search.test.d.ts +2 -0
- package/dist/ts/test/mock/repositories/mock-memory-repository.search.test.d.ts.map +1 -0
- 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/dist/ts/test/mock/repositories/mock-memory-repository.upsert.test.d.ts +2 -0
- package/dist/ts/test/mock/repositories/mock-memory-repository.upsert.test.d.ts.map +1 -0
- package/package.json +46 -42
- package/src/application/model-controller.test.ts +694 -0
- package/src/application/model-controller.ts +186 -0
- package/src/application/read-only-model-controller.test.ts +335 -0
- package/src/application/read-only-model-controller.ts +79 -0
- package/src/domain/events/domain-event.test.ts +82 -0
- package/src/domain/events/domain-event.ts +69 -0
- package/src/domain/events/event-types.ts +37 -0
- package/src/domain/events/mutation-event.test.ts +390 -0
- package/src/domain/events/mutation-event.ts +53 -0
- package/src/domain/events/query-event.test.ts +228 -0
- package/src/domain/events/query-event.ts +14 -0
- package/src/domain/events/request-event.test.ts +38 -0
- package/src/domain/events/request-event.ts +47 -0
- package/src/domain/interfaces/repository.ts +136 -0
- package/src/domain/models/pagination.ts +28 -0
- package/src/domain/services/base-model-service.ts +54 -0
- package/src/domain/services/model-service-args.ts +9 -0
- package/src/domain/services/model-service.normalization.test.ts +704 -0
- package/src/domain/services/model-service.test.ts +1616 -0
- package/src/domain/services/model-service.ts +597 -0
- package/src/domain/services/read-only-model-service.test.ts +1130 -0
- package/src/domain/services/read-only-model-service.ts +211 -0
- package/src/index.ts +17 -4
- package/src/shared/utils/schema-inference.ts +26 -0
- package/src/shared/utils/schema-inheritance.test.ts +295 -0
- package/src/shared/utils/schema-inheritance.ts +28 -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/src/test/mock/models/mock-book-models.ts +78 -0
- package/src/test/mock/repositories/mock-memory-repository.assign.test.ts +215 -0
- package/src/test/mock/repositories/mock-memory-repository.basic.test.ts +129 -0
- package/src/test/mock/repositories/mock-memory-repository.bulk-upsert.test.ts +159 -0
- package/src/test/mock/repositories/mock-memory-repository.count.test.ts +98 -0
- package/src/test/mock/repositories/mock-memory-repository.search.test.ts +265 -0
- package/src/test/mock/repositories/mock-memory-repository.trash.test.ts +736 -0
- package/src/test/mock/repositories/mock-memory-repository.ts +401 -0
- package/src/test/mock/repositories/mock-memory-repository.upsert.test.ts +108 -0
- package/dist/databaseConnection.d.ts +0 -24
- package/dist/datastoreAbstract.d.ts +0 -37
- package/dist/declaro-data.cjs +0 -1
- package/dist/declaro-data.mjs +0 -250
- package/dist/hydrateEntity.d.ts +0 -8
- package/dist/index.d.ts +0 -4
- package/dist/serverConnection.d.ts +0 -15
- package/dist/trackedStatus.d.ts +0 -15
- package/src/databaseConnection.ts +0 -137
- package/src/datastoreAbstract.ts +0 -190
- package/src/hydrateEntity.ts +0 -36
- package/src/placeholder.test.ts +0 -7
- package/src/serverConnection.ts +0 -74
- package/src/trackedStatus.ts +0 -35
- package/tsconfig.json +0 -10
- package/vite.config.ts +0 -23
|
@@ -0,0 +1,736 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it } from 'bun:test'
|
|
2
|
+
import { MockBookSchema } from '../models/mock-book-models'
|
|
3
|
+
import { MockMemoryRepository } from './mock-memory-repository'
|
|
4
|
+
|
|
5
|
+
describe('MockMemoryRepository - Trash Functionality', () => {
|
|
6
|
+
const mockSchema = MockBookSchema
|
|
7
|
+
|
|
8
|
+
let repository: MockMemoryRepository<typeof mockSchema>
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
repository = new MockMemoryRepository({ schema: mockSchema })
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
describe('remove', () => {
|
|
15
|
+
it('should be able to remove an item', async () => {
|
|
16
|
+
// Create a test book
|
|
17
|
+
const book = await repository.create({
|
|
18
|
+
title: 'Book to Remove',
|
|
19
|
+
author: 'Test Author',
|
|
20
|
+
publishedDate: new Date(),
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
// Verify the book exists
|
|
24
|
+
const loadedBook = await repository.load({ id: book.id })
|
|
25
|
+
expect(loadedBook).not.toBeNull()
|
|
26
|
+
expect(loadedBook?.title).toBe('Book to Remove')
|
|
27
|
+
|
|
28
|
+
// Remove the book
|
|
29
|
+
const removedBook = await repository.remove({ id: book.id })
|
|
30
|
+
expect(removedBook.id).toBe(book.id)
|
|
31
|
+
|
|
32
|
+
// Verify the book is no longer in the main data store
|
|
33
|
+
const afterRemove = await repository.load({ id: book.id })
|
|
34
|
+
expect(afterRemove).toBeNull()
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('should not be able to load a removed item by default', async () => {
|
|
38
|
+
const book = await repository.create({
|
|
39
|
+
title: 'Book to Remove',
|
|
40
|
+
author: 'Test Author',
|
|
41
|
+
publishedDate: new Date(),
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
await repository.remove({ id: book.id })
|
|
45
|
+
|
|
46
|
+
// Default load should not find removed items
|
|
47
|
+
const loadedBook = await repository.load({ id: book.id })
|
|
48
|
+
expect(loadedBook).toBeNull()
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
describe('load with removedOnly option', () => {
|
|
53
|
+
it('should be able to load a removed item with removedOnly option', async () => {
|
|
54
|
+
const book = await repository.create({
|
|
55
|
+
title: 'Removed Book',
|
|
56
|
+
author: 'Test Author',
|
|
57
|
+
publishedDate: new Date(),
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
await repository.remove({ id: book.id })
|
|
61
|
+
|
|
62
|
+
// Load with removedOnly should find the removed item
|
|
63
|
+
const loadedBook = await repository.load({ id: book.id }, { removedOnly: true })
|
|
64
|
+
expect(loadedBook).not.toBeNull()
|
|
65
|
+
expect(loadedBook?.title).toBe('Removed Book')
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('should not load a non-removed item with removedOnly option', async () => {
|
|
69
|
+
const book = await repository.create({
|
|
70
|
+
title: 'Active Book',
|
|
71
|
+
author: 'Test Author',
|
|
72
|
+
publishedDate: new Date(),
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
// Load with removedOnly should not find active items
|
|
76
|
+
const loadedBook = await repository.load({ id: book.id }, { removedOnly: true })
|
|
77
|
+
expect(loadedBook).toBeNull()
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
describe('load with includeRemoved option', () => {
|
|
82
|
+
it('should be able to load a removed item with includeRemoved option', async () => {
|
|
83
|
+
const book = await repository.create({
|
|
84
|
+
title: 'Removed Book',
|
|
85
|
+
author: 'Test Author',
|
|
86
|
+
publishedDate: new Date(),
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
await repository.remove({ id: book.id })
|
|
90
|
+
|
|
91
|
+
// Load with includeRemoved should find the removed item
|
|
92
|
+
const loadedBook = await repository.load({ id: book.id }, { includeRemoved: true })
|
|
93
|
+
expect(loadedBook).not.toBeNull()
|
|
94
|
+
expect(loadedBook?.title).toBe('Removed Book')
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('should be able to load a non-removed item with includeRemoved option', async () => {
|
|
98
|
+
const book = await repository.create({
|
|
99
|
+
title: 'Active Book',
|
|
100
|
+
author: 'Test Author',
|
|
101
|
+
publishedDate: new Date(),
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
// Load with includeRemoved should find active items
|
|
105
|
+
const loadedBook = await repository.load({ id: book.id }, { includeRemoved: true })
|
|
106
|
+
expect(loadedBook).not.toBeNull()
|
|
107
|
+
expect(loadedBook?.title).toBe('Active Book')
|
|
108
|
+
})
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
describe('search with removedOnly option', () => {
|
|
112
|
+
it('should be able to search removed items with removedOnly option', async () => {
|
|
113
|
+
const repositoryWithFilter = new MockMemoryRepository({
|
|
114
|
+
schema: mockSchema,
|
|
115
|
+
filter: (data, filters) => {
|
|
116
|
+
if (filters.text) {
|
|
117
|
+
return data.title.toLowerCase().includes(filters.text.toLowerCase())
|
|
118
|
+
}
|
|
119
|
+
return true
|
|
120
|
+
},
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
// Create test data
|
|
124
|
+
await repositoryWithFilter.create({ title: 'Active Book 1', author: 'Author 1', publishedDate: new Date() })
|
|
125
|
+
const removedBook1 = await repositoryWithFilter.create({
|
|
126
|
+
title: 'Removed Book 1',
|
|
127
|
+
author: 'Author 2',
|
|
128
|
+
publishedDate: new Date(),
|
|
129
|
+
})
|
|
130
|
+
const removedBook2 = await repositoryWithFilter.create({
|
|
131
|
+
title: 'Removed Book 2',
|
|
132
|
+
author: 'Author 3',
|
|
133
|
+
publishedDate: new Date(),
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
// Remove two books
|
|
137
|
+
await repositoryWithFilter.remove({ id: removedBook1.id })
|
|
138
|
+
await repositoryWithFilter.remove({ id: removedBook2.id })
|
|
139
|
+
|
|
140
|
+
// Search with removedOnly should only find removed items
|
|
141
|
+
const results = await repositoryWithFilter.search({}, { removedOnly: true })
|
|
142
|
+
expect(results.results).toHaveLength(2)
|
|
143
|
+
expect(results.results.every((book) => book.title.startsWith('Removed'))).toBe(true)
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('should be able to filter removed items with removedOnly option', async () => {
|
|
147
|
+
const repositoryWithFilter = new MockMemoryRepository({
|
|
148
|
+
schema: mockSchema,
|
|
149
|
+
filter: (data, filters) => {
|
|
150
|
+
if (filters.text) {
|
|
151
|
+
return data.title.toLowerCase().includes(filters.text.toLowerCase())
|
|
152
|
+
}
|
|
153
|
+
return true
|
|
154
|
+
},
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
// Create test data
|
|
158
|
+
const removedBook1 = await repositoryWithFilter.create({
|
|
159
|
+
title: 'Test Removed Book',
|
|
160
|
+
author: 'Author 1',
|
|
161
|
+
publishedDate: new Date(),
|
|
162
|
+
})
|
|
163
|
+
const removedBook2 = await repositoryWithFilter.create({
|
|
164
|
+
title: 'Other Removed Book',
|
|
165
|
+
author: 'Author 2',
|
|
166
|
+
publishedDate: new Date(),
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
await repositoryWithFilter.remove({ id: removedBook1.id })
|
|
170
|
+
await repositoryWithFilter.remove({ id: removedBook2.id })
|
|
171
|
+
|
|
172
|
+
// Search with removedOnly and filter
|
|
173
|
+
const results = await repositoryWithFilter.search({ text: 'Test' }, { removedOnly: true })
|
|
174
|
+
expect(results.results).toHaveLength(1)
|
|
175
|
+
expect(results.results[0].title).toBe('Test Removed Book')
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
it('should not return non-removed items with removedOnly option', async () => {
|
|
179
|
+
const repositoryWithFilter = new MockMemoryRepository({
|
|
180
|
+
schema: mockSchema,
|
|
181
|
+
filter: (data, filters) => {
|
|
182
|
+
if (filters.text) {
|
|
183
|
+
return data.title.toLowerCase().includes(filters.text.toLowerCase())
|
|
184
|
+
}
|
|
185
|
+
return true
|
|
186
|
+
},
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
// Create active items only
|
|
190
|
+
await repositoryWithFilter.create({ title: 'Active Book 1', author: 'Author 1', publishedDate: new Date() })
|
|
191
|
+
await repositoryWithFilter.create({ title: 'Active Book 2', author: 'Author 2', publishedDate: new Date() })
|
|
192
|
+
|
|
193
|
+
// Search with removedOnly should return empty results
|
|
194
|
+
const results = await repositoryWithFilter.search({}, { removedOnly: true })
|
|
195
|
+
expect(results.results).toHaveLength(0)
|
|
196
|
+
})
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
describe('search with includeRemoved option', () => {
|
|
200
|
+
it('should be able to search both removed and non-removed items with includeRemoved option', async () => {
|
|
201
|
+
const repositoryWithFilter = new MockMemoryRepository({
|
|
202
|
+
schema: mockSchema,
|
|
203
|
+
filter: (data, filters) => {
|
|
204
|
+
if (filters.text) {
|
|
205
|
+
return data.title.toLowerCase().includes(filters.text.toLowerCase())
|
|
206
|
+
}
|
|
207
|
+
return true
|
|
208
|
+
},
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
// Create test data
|
|
212
|
+
await repositoryWithFilter.create({ title: 'Active Book 1', author: 'Author 1', publishedDate: new Date() })
|
|
213
|
+
await repositoryWithFilter.create({ title: 'Active Book 2', author: 'Author 2', publishedDate: new Date() })
|
|
214
|
+
const removedBook = await repositoryWithFilter.create({
|
|
215
|
+
title: 'Removed Book',
|
|
216
|
+
author: 'Author 3',
|
|
217
|
+
publishedDate: new Date(),
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
await repositoryWithFilter.remove({ id: removedBook.id })
|
|
221
|
+
|
|
222
|
+
// Search with includeRemoved should find all items
|
|
223
|
+
const results = await repositoryWithFilter.search({}, { includeRemoved: true })
|
|
224
|
+
expect(results.results).toHaveLength(3)
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
it('should be able to filter across removed and non-removed items with includeRemoved option', async () => {
|
|
228
|
+
const repositoryWithFilter = new MockMemoryRepository({
|
|
229
|
+
schema: mockSchema,
|
|
230
|
+
filter: (data, filters) => {
|
|
231
|
+
if (filters.text) {
|
|
232
|
+
return data.title.toLowerCase().includes(filters.text.toLowerCase())
|
|
233
|
+
}
|
|
234
|
+
return true
|
|
235
|
+
},
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
// Create test data
|
|
239
|
+
await repositoryWithFilter.create({
|
|
240
|
+
title: 'Test Active Book',
|
|
241
|
+
author: 'Author 1',
|
|
242
|
+
publishedDate: new Date(),
|
|
243
|
+
})
|
|
244
|
+
const removedBook = await repositoryWithFilter.create({
|
|
245
|
+
title: 'Test Removed Book',
|
|
246
|
+
author: 'Author 2',
|
|
247
|
+
publishedDate: new Date(),
|
|
248
|
+
})
|
|
249
|
+
await repositoryWithFilter.create({ title: 'Other Book', author: 'Author 3', publishedDate: new Date() })
|
|
250
|
+
const otherRemovedBook = await repositoryWithFilter.create({
|
|
251
|
+
title: 'Another Removed Book',
|
|
252
|
+
author: 'Author 4',
|
|
253
|
+
publishedDate: new Date(),
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
await repositoryWithFilter.remove({ id: removedBook.id })
|
|
257
|
+
await repositoryWithFilter.remove({ id: otherRemovedBook.id })
|
|
258
|
+
|
|
259
|
+
// Search with includeRemoved and filter
|
|
260
|
+
const results = await repositoryWithFilter.search({ text: 'Test' }, { includeRemoved: true })
|
|
261
|
+
expect(results.results).toHaveLength(2)
|
|
262
|
+
expect(results.results.some((book) => book.title === 'Test Active Book')).toBe(true)
|
|
263
|
+
expect(results.results.some((book) => book.title === 'Test Removed Book')).toBe(true)
|
|
264
|
+
})
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
describe('permanentlyDeleteFromTrash', () => {
|
|
268
|
+
it('should be able to permanently delete a previously removed item', async () => {
|
|
269
|
+
const book = await repository.create({
|
|
270
|
+
title: 'Book to Permanently Delete',
|
|
271
|
+
author: 'Test Author',
|
|
272
|
+
publishedDate: new Date(),
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
// Remove the book
|
|
276
|
+
await repository.remove({ id: book.id })
|
|
277
|
+
|
|
278
|
+
// Verify it's in trash
|
|
279
|
+
const inTrash = await repository.load({ id: book.id }, { removedOnly: true })
|
|
280
|
+
expect(inTrash).not.toBeNull()
|
|
281
|
+
|
|
282
|
+
// Permanently delete from trash
|
|
283
|
+
const deleted = await repository.permanentlyDeleteFromTrash({ id: book.id })
|
|
284
|
+
expect(deleted.id).toBe(book.id)
|
|
285
|
+
|
|
286
|
+
// Verify it's no longer in trash
|
|
287
|
+
const afterDelete = await repository.load({ id: book.id }, { removedOnly: true })
|
|
288
|
+
expect(afterDelete).toBeNull()
|
|
289
|
+
|
|
290
|
+
// Verify it's not in main data either
|
|
291
|
+
const inMain = await repository.load({ id: book.id })
|
|
292
|
+
expect(inMain).toBeNull()
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
it('should throw error when trying to permanently delete non-existent item from trash', async () => {
|
|
296
|
+
await expect(repository.permanentlyDeleteFromTrash({ id: 999 })).rejects.toThrow()
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
it('should throw error when trying to permanently delete a non-removed item from trash', async () => {
|
|
300
|
+
const book = await repository.create({
|
|
301
|
+
title: 'Active Book',
|
|
302
|
+
author: 'Test Author',
|
|
303
|
+
publishedDate: new Date(),
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
// Trying to permanently delete from trash without removing first should fail
|
|
307
|
+
await expect(repository.permanentlyDeleteFromTrash({ id: book.id })).rejects.toThrow()
|
|
308
|
+
})
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
describe('permanentlyDelete', () => {
|
|
312
|
+
it('should be able to permanently delete a previously removed item', async () => {
|
|
313
|
+
const book = await repository.create({
|
|
314
|
+
title: 'Book to Permanently Delete',
|
|
315
|
+
author: 'Test Author',
|
|
316
|
+
publishedDate: new Date(),
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
// Remove the book
|
|
320
|
+
await repository.remove({ id: book.id })
|
|
321
|
+
|
|
322
|
+
// Verify it's in trash
|
|
323
|
+
const inTrash = await repository.load({ id: book.id }, { removedOnly: true })
|
|
324
|
+
expect(inTrash).not.toBeNull()
|
|
325
|
+
|
|
326
|
+
// Permanently delete
|
|
327
|
+
const deleted = await repository.permanentlyDelete({ id: book.id })
|
|
328
|
+
expect(deleted.id).toBe(book.id)
|
|
329
|
+
|
|
330
|
+
// Verify it's no longer anywhere
|
|
331
|
+
const afterDelete = await repository.load({ id: book.id }, { removedOnly: true })
|
|
332
|
+
expect(afterDelete).toBeNull()
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
it('should be able to permanently delete a non-removed item', async () => {
|
|
336
|
+
const book = await repository.create({
|
|
337
|
+
title: 'Active Book to Delete',
|
|
338
|
+
author: 'Test Author',
|
|
339
|
+
publishedDate: new Date(),
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
// Verify it exists
|
|
343
|
+
const exists = await repository.load({ id: book.id })
|
|
344
|
+
expect(exists).not.toBeNull()
|
|
345
|
+
|
|
346
|
+
// Permanently delete without removing first
|
|
347
|
+
const deleted = await repository.permanentlyDelete({ id: book.id })
|
|
348
|
+
expect(deleted.id).toBe(book.id)
|
|
349
|
+
|
|
350
|
+
// Verify it's no longer in main data
|
|
351
|
+
const afterDelete = await repository.load({ id: book.id })
|
|
352
|
+
expect(afterDelete).toBeNull()
|
|
353
|
+
|
|
354
|
+
// Verify it's not in trash either
|
|
355
|
+
const inTrash = await repository.load({ id: book.id }, { removedOnly: true })
|
|
356
|
+
expect(inTrash).toBeNull()
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
it('should throw error when trying to permanently delete non-existent item', async () => {
|
|
360
|
+
await expect(repository.permanentlyDelete({ id: 999 })).rejects.toThrow()
|
|
361
|
+
})
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
describe('emptyTrash', () => {
|
|
365
|
+
it('should permanently delete all items from trash when no filter is provided', async () => {
|
|
366
|
+
// Create and remove multiple items
|
|
367
|
+
const book1 = await repository.create({ title: 'Book 1', author: 'Author 1', publishedDate: new Date() })
|
|
368
|
+
const book2 = await repository.create({ title: 'Book 2', author: 'Author 2', publishedDate: new Date() })
|
|
369
|
+
const book3 = await repository.create({ title: 'Book 3', author: 'Author 3', publishedDate: new Date() })
|
|
370
|
+
|
|
371
|
+
await repository.remove({ id: book1.id })
|
|
372
|
+
await repository.remove({ id: book2.id })
|
|
373
|
+
await repository.remove({ id: book3.id })
|
|
374
|
+
|
|
375
|
+
// Verify all are in trash
|
|
376
|
+
const trashResults = await repository.search({}, { removedOnly: true })
|
|
377
|
+
expect(trashResults.results).toHaveLength(3)
|
|
378
|
+
|
|
379
|
+
// Empty trash
|
|
380
|
+
const deletedCount = await repository.emptyTrash()
|
|
381
|
+
expect(deletedCount).toBe(3)
|
|
382
|
+
|
|
383
|
+
// Verify trash is empty
|
|
384
|
+
const afterEmpty = await repository.search({}, { removedOnly: true })
|
|
385
|
+
expect(afterEmpty.results).toHaveLength(0)
|
|
386
|
+
})
|
|
387
|
+
|
|
388
|
+
it('should permanently delete filtered items from trash when filter is provided', async () => {
|
|
389
|
+
const repositoryWithFilter = new MockMemoryRepository({
|
|
390
|
+
schema: mockSchema,
|
|
391
|
+
filter: (data, filters) => {
|
|
392
|
+
if (filters.text) {
|
|
393
|
+
return data.title.toLowerCase().includes(filters.text.toLowerCase())
|
|
394
|
+
}
|
|
395
|
+
return true
|
|
396
|
+
},
|
|
397
|
+
})
|
|
398
|
+
|
|
399
|
+
// Create and remove multiple items
|
|
400
|
+
const book1 = await repositoryWithFilter.create({
|
|
401
|
+
title: 'Test Book 1',
|
|
402
|
+
author: 'Author 1',
|
|
403
|
+
publishedDate: new Date(),
|
|
404
|
+
})
|
|
405
|
+
const book2 = await repositoryWithFilter.create({
|
|
406
|
+
title: 'Test Book 2',
|
|
407
|
+
author: 'Author 2',
|
|
408
|
+
publishedDate: new Date(),
|
|
409
|
+
})
|
|
410
|
+
const book3 = await repositoryWithFilter.create({
|
|
411
|
+
title: 'Other Book',
|
|
412
|
+
author: 'Author 3',
|
|
413
|
+
publishedDate: new Date(),
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
await repositoryWithFilter.remove({ id: book1.id })
|
|
417
|
+
await repositoryWithFilter.remove({ id: book2.id })
|
|
418
|
+
await repositoryWithFilter.remove({ id: book3.id })
|
|
419
|
+
|
|
420
|
+
// Empty trash with filter
|
|
421
|
+
const deletedCount = await repositoryWithFilter.emptyTrash({ text: 'Test' })
|
|
422
|
+
expect(deletedCount).toBe(2)
|
|
423
|
+
|
|
424
|
+
// Verify only filtered items were deleted
|
|
425
|
+
const trashResults = await repositoryWithFilter.search({}, { removedOnly: true })
|
|
426
|
+
expect(trashResults.results).toHaveLength(1)
|
|
427
|
+
expect(trashResults.results[0].title).toBe('Other Book')
|
|
428
|
+
})
|
|
429
|
+
|
|
430
|
+
it('should return 0 when trash is already empty', async () => {
|
|
431
|
+
const deletedCount = await repository.emptyTrash()
|
|
432
|
+
expect(deletedCount).toBe(0)
|
|
433
|
+
})
|
|
434
|
+
|
|
435
|
+
it('should not affect non-removed items', async () => {
|
|
436
|
+
// Create items
|
|
437
|
+
await repository.create({ title: 'Active Book 1', author: 'Author 1', publishedDate: new Date() })
|
|
438
|
+
await repository.create({ title: 'Active Book 2', author: 'Author 2', publishedDate: new Date() })
|
|
439
|
+
|
|
440
|
+
const book3 = await repository.create({
|
|
441
|
+
title: 'Book to Remove',
|
|
442
|
+
author: 'Author 3',
|
|
443
|
+
publishedDate: new Date(),
|
|
444
|
+
})
|
|
445
|
+
await repository.remove({ id: book3.id })
|
|
446
|
+
|
|
447
|
+
// Empty trash
|
|
448
|
+
await repository.emptyTrash()
|
|
449
|
+
|
|
450
|
+
// Verify active items are still there
|
|
451
|
+
const activeResults = await repository.search({})
|
|
452
|
+
expect(activeResults.results).toHaveLength(2)
|
|
453
|
+
})
|
|
454
|
+
|
|
455
|
+
it('when no filter function is configured, remove all items from trash', async () => {
|
|
456
|
+
// Create and remove items
|
|
457
|
+
const book1 = await repository.create({
|
|
458
|
+
title: 'Test Book 1',
|
|
459
|
+
author: 'Author 1',
|
|
460
|
+
publishedDate: new Date(),
|
|
461
|
+
})
|
|
462
|
+
const book2 = await repository.create({
|
|
463
|
+
title: 'Other Book 2',
|
|
464
|
+
author: 'Author 2',
|
|
465
|
+
publishedDate: new Date(),
|
|
466
|
+
})
|
|
467
|
+
|
|
468
|
+
await repository.remove({ id: book1.id })
|
|
469
|
+
await repository.remove({ id: book2.id })
|
|
470
|
+
|
|
471
|
+
// Empty trash with filter
|
|
472
|
+
// Since no filter function is provided, all items in trash should be deleted
|
|
473
|
+
const deletedCount = await repository.emptyTrash({ text: 'Test' })
|
|
474
|
+
expect(deletedCount).toBe(2)
|
|
475
|
+
|
|
476
|
+
// Verify all items are still in trash
|
|
477
|
+
const trashResults = await repository.search({}, { removedOnly: true })
|
|
478
|
+
expect(trashResults.results).toHaveLength(0)
|
|
479
|
+
})
|
|
480
|
+
})
|
|
481
|
+
|
|
482
|
+
describe('restore', () => {
|
|
483
|
+
it('should restore a removed item back to active state', async () => {
|
|
484
|
+
const book = await repository.create({
|
|
485
|
+
title: 'Book to Restore',
|
|
486
|
+
author: 'Test Author',
|
|
487
|
+
publishedDate: new Date(),
|
|
488
|
+
})
|
|
489
|
+
|
|
490
|
+
// Remove the book
|
|
491
|
+
await repository.remove({ id: book.id })
|
|
492
|
+
|
|
493
|
+
// Verify it's in trash
|
|
494
|
+
const inTrash = await repository.load({ id: book.id }, { removedOnly: true })
|
|
495
|
+
expect(inTrash).not.toBeNull()
|
|
496
|
+
|
|
497
|
+
// Restore the book
|
|
498
|
+
const restored = await repository.restore({ id: book.id })
|
|
499
|
+
expect(restored.id).toBe(book.id)
|
|
500
|
+
|
|
501
|
+
// Verify it's back in main data
|
|
502
|
+
const restoredBook = await repository.load({ id: book.id })
|
|
503
|
+
expect(restoredBook).not.toBeNull()
|
|
504
|
+
expect(restoredBook?.title).toBe('Book to Restore')
|
|
505
|
+
|
|
506
|
+
// Verify it's no longer in trash
|
|
507
|
+
const notInTrash = await repository.load({ id: book.id }, { removedOnly: true })
|
|
508
|
+
expect(notInTrash).toBeNull()
|
|
509
|
+
})
|
|
510
|
+
|
|
511
|
+
it('should throw error when trying to restore non-existent item', async () => {
|
|
512
|
+
await expect(repository.restore({ id: 999 })).rejects.toThrow('Item not found in trash')
|
|
513
|
+
})
|
|
514
|
+
|
|
515
|
+
it('should throw error when trying to restore a non-removed item', async () => {
|
|
516
|
+
const book = await repository.create({
|
|
517
|
+
title: 'Active Book',
|
|
518
|
+
author: 'Test Author',
|
|
519
|
+
publishedDate: new Date(),
|
|
520
|
+
})
|
|
521
|
+
|
|
522
|
+
// Trying to restore an active item should fail
|
|
523
|
+
await expect(repository.restore({ id: book.id })).rejects.toThrow('Item not found in trash')
|
|
524
|
+
})
|
|
525
|
+
})
|
|
526
|
+
|
|
527
|
+
describe('count with trash options', () => {
|
|
528
|
+
it('should count only active items by default', async () => {
|
|
529
|
+
await repository.create({
|
|
530
|
+
title: 'Active Book',
|
|
531
|
+
author: 'Author 1',
|
|
532
|
+
publishedDate: new Date(),
|
|
533
|
+
})
|
|
534
|
+
const book2 = await repository.create({
|
|
535
|
+
title: 'Book to Remove',
|
|
536
|
+
author: 'Author 2',
|
|
537
|
+
publishedDate: new Date(),
|
|
538
|
+
})
|
|
539
|
+
|
|
540
|
+
await repository.remove({ id: book2.id })
|
|
541
|
+
|
|
542
|
+
const count = await repository.count({})
|
|
543
|
+
expect(count).toBe(1)
|
|
544
|
+
})
|
|
545
|
+
|
|
546
|
+
it('should count only removed items with removedOnly option', async () => {
|
|
547
|
+
await repository.create({
|
|
548
|
+
title: 'Active Book',
|
|
549
|
+
author: 'Author 1',
|
|
550
|
+
publishedDate: new Date(),
|
|
551
|
+
})
|
|
552
|
+
const book2 = await repository.create({
|
|
553
|
+
title: 'Book to Remove 1',
|
|
554
|
+
author: 'Author 2',
|
|
555
|
+
publishedDate: new Date(),
|
|
556
|
+
})
|
|
557
|
+
const book3 = await repository.create({
|
|
558
|
+
title: 'Book to Remove 2',
|
|
559
|
+
author: 'Author 3',
|
|
560
|
+
publishedDate: new Date(),
|
|
561
|
+
})
|
|
562
|
+
|
|
563
|
+
await repository.remove({ id: book2.id })
|
|
564
|
+
await repository.remove({ id: book3.id })
|
|
565
|
+
|
|
566
|
+
const count = await repository.count({}, { removedOnly: true })
|
|
567
|
+
expect(count).toBe(2)
|
|
568
|
+
})
|
|
569
|
+
|
|
570
|
+
it('should count both removed and active items with includeRemoved option', async () => {
|
|
571
|
+
await repository.create({
|
|
572
|
+
title: 'Active Book',
|
|
573
|
+
author: 'Author 1',
|
|
574
|
+
publishedDate: new Date(),
|
|
575
|
+
})
|
|
576
|
+
const book2 = await repository.create({
|
|
577
|
+
title: 'Book to Remove',
|
|
578
|
+
author: 'Author 2',
|
|
579
|
+
publishedDate: new Date(),
|
|
580
|
+
})
|
|
581
|
+
|
|
582
|
+
await repository.remove({ id: book2.id })
|
|
583
|
+
|
|
584
|
+
const count = await repository.count({}, { includeRemoved: true })
|
|
585
|
+
expect(count).toBe(2)
|
|
586
|
+
})
|
|
587
|
+
|
|
588
|
+
it('should count filtered active items by default', async () => {
|
|
589
|
+
const repositoryWithFilter = new MockMemoryRepository({
|
|
590
|
+
schema: mockSchema,
|
|
591
|
+
filter: (data, filters) => {
|
|
592
|
+
if (filters.text) {
|
|
593
|
+
return data.title.toLowerCase().includes(filters.text.toLowerCase())
|
|
594
|
+
}
|
|
595
|
+
return true
|
|
596
|
+
},
|
|
597
|
+
})
|
|
598
|
+
|
|
599
|
+
await repositoryWithFilter.create({ title: 'Test Book 1', author: 'Author 1', publishedDate: new Date() })
|
|
600
|
+
await repositoryWithFilter.create({ title: 'Test Book 2', author: 'Author 2', publishedDate: new Date() })
|
|
601
|
+
await repositoryWithFilter.create({ title: 'Other Book', author: 'Author 3', publishedDate: new Date() })
|
|
602
|
+
const removedBook = await repositoryWithFilter.create({
|
|
603
|
+
title: 'Test Book 3',
|
|
604
|
+
author: 'Author 4',
|
|
605
|
+
publishedDate: new Date(),
|
|
606
|
+
})
|
|
607
|
+
|
|
608
|
+
await repositoryWithFilter.remove({ id: removedBook.id })
|
|
609
|
+
|
|
610
|
+
// Count with filter should only count active items matching filter
|
|
611
|
+
const count = await repositoryWithFilter.count({ text: 'Test' })
|
|
612
|
+
expect(count).toBe(2)
|
|
613
|
+
})
|
|
614
|
+
|
|
615
|
+
it('should count filtered removed items with removedOnly option', async () => {
|
|
616
|
+
const repositoryWithFilter = new MockMemoryRepository({
|
|
617
|
+
schema: mockSchema,
|
|
618
|
+
filter: (data, filters) => {
|
|
619
|
+
if (filters.text) {
|
|
620
|
+
return data.title.toLowerCase().includes(filters.text.toLowerCase())
|
|
621
|
+
}
|
|
622
|
+
return true
|
|
623
|
+
},
|
|
624
|
+
})
|
|
625
|
+
|
|
626
|
+
const book1 = await repositoryWithFilter.create({
|
|
627
|
+
title: 'Test Removed Book 1',
|
|
628
|
+
author: 'Author 1',
|
|
629
|
+
publishedDate: new Date(),
|
|
630
|
+
})
|
|
631
|
+
const book2 = await repositoryWithFilter.create({
|
|
632
|
+
title: 'Test Removed Book 2',
|
|
633
|
+
author: 'Author 2',
|
|
634
|
+
publishedDate: new Date(),
|
|
635
|
+
})
|
|
636
|
+
const book3 = await repositoryWithFilter.create({
|
|
637
|
+
title: 'Other Removed Book',
|
|
638
|
+
author: 'Author 3',
|
|
639
|
+
publishedDate: new Date(),
|
|
640
|
+
})
|
|
641
|
+
await repositoryWithFilter.create({ title: 'Active Book', author: 'Author 4', publishedDate: new Date() })
|
|
642
|
+
|
|
643
|
+
await repositoryWithFilter.remove({ id: book1.id })
|
|
644
|
+
await repositoryWithFilter.remove({ id: book2.id })
|
|
645
|
+
await repositoryWithFilter.remove({ id: book3.id })
|
|
646
|
+
|
|
647
|
+
// Count with filter and removedOnly should only count removed items matching filter
|
|
648
|
+
const count = await repositoryWithFilter.count({ text: 'Test' }, { removedOnly: true })
|
|
649
|
+
expect(count).toBe(2)
|
|
650
|
+
})
|
|
651
|
+
|
|
652
|
+
it('should count filtered items across active and removed with includeRemoved option', async () => {
|
|
653
|
+
const repositoryWithFilter = new MockMemoryRepository({
|
|
654
|
+
schema: mockSchema,
|
|
655
|
+
filter: (data, filters) => {
|
|
656
|
+
if (filters.text) {
|
|
657
|
+
return data.title.toLowerCase().includes(filters.text.toLowerCase())
|
|
658
|
+
}
|
|
659
|
+
return true
|
|
660
|
+
},
|
|
661
|
+
})
|
|
662
|
+
|
|
663
|
+
await repositoryWithFilter.create({
|
|
664
|
+
title: 'Test Active Book 1',
|
|
665
|
+
author: 'Author 1',
|
|
666
|
+
publishedDate: new Date(),
|
|
667
|
+
})
|
|
668
|
+
await repositoryWithFilter.create({
|
|
669
|
+
title: 'Test Active Book 2',
|
|
670
|
+
author: 'Author 2',
|
|
671
|
+
publishedDate: new Date(),
|
|
672
|
+
})
|
|
673
|
+
const removedBook1 = await repositoryWithFilter.create({
|
|
674
|
+
title: 'Test Removed Book 1',
|
|
675
|
+
author: 'Author 3',
|
|
676
|
+
publishedDate: new Date(),
|
|
677
|
+
})
|
|
678
|
+
const removedBook2 = await repositoryWithFilter.create({
|
|
679
|
+
title: 'Test Removed Book 2',
|
|
680
|
+
author: 'Author 4',
|
|
681
|
+
publishedDate: new Date(),
|
|
682
|
+
})
|
|
683
|
+
await repositoryWithFilter.create({
|
|
684
|
+
title: 'Other Active Book',
|
|
685
|
+
author: 'Author 5',
|
|
686
|
+
publishedDate: new Date(),
|
|
687
|
+
})
|
|
688
|
+
|
|
689
|
+
await repositoryWithFilter.remove({ id: removedBook1.id })
|
|
690
|
+
await repositoryWithFilter.remove({ id: removedBook2.id })
|
|
691
|
+
|
|
692
|
+
// Count with filter and includeRemoved should count all items matching filter
|
|
693
|
+
const count = await repositoryWithFilter.count({ text: 'Test' }, { includeRemoved: true })
|
|
694
|
+
expect(count).toBe(4)
|
|
695
|
+
})
|
|
696
|
+
|
|
697
|
+
it('should return 0 when no items match filter in active items', async () => {
|
|
698
|
+
const repositoryWithFilter = new MockMemoryRepository({
|
|
699
|
+
schema: mockSchema,
|
|
700
|
+
filter: (data, filters) => {
|
|
701
|
+
if (filters.text) {
|
|
702
|
+
return data.title.toLowerCase().includes(filters.text.toLowerCase())
|
|
703
|
+
}
|
|
704
|
+
return true
|
|
705
|
+
},
|
|
706
|
+
})
|
|
707
|
+
|
|
708
|
+
await repositoryWithFilter.create({ title: 'Active Book', author: 'Author 1', publishedDate: new Date() })
|
|
709
|
+
|
|
710
|
+
const count = await repositoryWithFilter.count({ text: 'NonExistent' })
|
|
711
|
+
expect(count).toBe(0)
|
|
712
|
+
})
|
|
713
|
+
|
|
714
|
+
it('should return 0 when no removed items match filter with removedOnly option', async () => {
|
|
715
|
+
const repositoryWithFilter = new MockMemoryRepository({
|
|
716
|
+
schema: mockSchema,
|
|
717
|
+
filter: (data, filters) => {
|
|
718
|
+
if (filters.text) {
|
|
719
|
+
return data.title.toLowerCase().includes(filters.text.toLowerCase())
|
|
720
|
+
}
|
|
721
|
+
return true
|
|
722
|
+
},
|
|
723
|
+
})
|
|
724
|
+
|
|
725
|
+
const book = await repositoryWithFilter.create({
|
|
726
|
+
title: 'Removed Book',
|
|
727
|
+
author: 'Author 1',
|
|
728
|
+
publishedDate: new Date(),
|
|
729
|
+
})
|
|
730
|
+
await repositoryWithFilter.remove({ id: book.id })
|
|
731
|
+
|
|
732
|
+
const count = await repositoryWithFilter.count({ text: 'NonExistent' }, { removedOnly: true })
|
|
733
|
+
expect(count).toBe(0)
|
|
734
|
+
})
|
|
735
|
+
})
|
|
736
|
+
})
|