@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.
Files changed (142) 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 +13372 -0
  6. package/dist/node/index.cjs.map +93 -0
  7. package/dist/node/index.js +13351 -0
  8. package/dist/node/index.js.map +93 -0
  9. package/dist/ts/application/model-controller.d.ts +60 -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 +24 -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 +37 -0
  22. package/dist/ts/domain/events/event-types.d.ts.map +1 -0
  23. package/dist/ts/domain/events/mutation-event.d.ts +41 -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 +8 -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 +26 -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 +110 -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 +99 -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 +90 -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/shared/utils/schema-inheritance.test.d.ts +2 -0
  60. package/dist/ts/shared/utils/schema-inheritance.test.d.ts.map +1 -0
  61. package/dist/ts/shared/utils/test/animal-schema.d.ts +57 -0
  62. package/dist/ts/shared/utils/test/animal-schema.d.ts.map +1 -0
  63. package/dist/ts/shared/utils/test/animal-trait-schema.d.ts +55 -0
  64. package/dist/ts/shared/utils/test/animal-trait-schema.d.ts.map +1 -0
  65. package/dist/ts/shared/utils/test/elephant-schema.d.ts +30 -0
  66. package/dist/ts/shared/utils/test/elephant-schema.d.ts.map +1 -0
  67. package/dist/ts/shared/utils/test/elephant-trait-schema.d.ts +26 -0
  68. package/dist/ts/shared/utils/test/elephant-trait-schema.d.ts.map +1 -0
  69. package/dist/ts/test/mock/models/mock-book-models.d.ts +42 -0
  70. package/dist/ts/test/mock/models/mock-book-models.d.ts.map +1 -0
  71. package/dist/ts/test/mock/repositories/mock-memory-repository.assign.test.d.ts +2 -0
  72. package/dist/ts/test/mock/repositories/mock-memory-repository.assign.test.d.ts.map +1 -0
  73. package/dist/ts/test/mock/repositories/mock-memory-repository.basic.test.d.ts +2 -0
  74. package/dist/ts/test/mock/repositories/mock-memory-repository.basic.test.d.ts.map +1 -0
  75. package/dist/ts/test/mock/repositories/mock-memory-repository.bulk-upsert.test.d.ts +2 -0
  76. package/dist/ts/test/mock/repositories/mock-memory-repository.bulk-upsert.test.d.ts.map +1 -0
  77. package/dist/ts/test/mock/repositories/mock-memory-repository.count.test.d.ts +2 -0
  78. package/dist/ts/test/mock/repositories/mock-memory-repository.count.test.d.ts.map +1 -0
  79. package/dist/ts/test/mock/repositories/mock-memory-repository.d.ts +62 -0
  80. package/dist/ts/test/mock/repositories/mock-memory-repository.d.ts.map +1 -0
  81. package/dist/ts/test/mock/repositories/mock-memory-repository.search.test.d.ts +2 -0
  82. package/dist/ts/test/mock/repositories/mock-memory-repository.search.test.d.ts.map +1 -0
  83. package/dist/ts/test/mock/repositories/mock-memory-repository.trash.test.d.ts +2 -0
  84. package/dist/ts/test/mock/repositories/mock-memory-repository.trash.test.d.ts.map +1 -0
  85. package/dist/ts/test/mock/repositories/mock-memory-repository.upsert.test.d.ts +2 -0
  86. package/dist/ts/test/mock/repositories/mock-memory-repository.upsert.test.d.ts.map +1 -0
  87. package/package.json +46 -42
  88. package/src/application/model-controller.test.ts +694 -0
  89. package/src/application/model-controller.ts +186 -0
  90. package/src/application/read-only-model-controller.test.ts +335 -0
  91. package/src/application/read-only-model-controller.ts +79 -0
  92. package/src/domain/events/domain-event.test.ts +82 -0
  93. package/src/domain/events/domain-event.ts +69 -0
  94. package/src/domain/events/event-types.ts +37 -0
  95. package/src/domain/events/mutation-event.test.ts +390 -0
  96. package/src/domain/events/mutation-event.ts +53 -0
  97. package/src/domain/events/query-event.test.ts +228 -0
  98. package/src/domain/events/query-event.ts +14 -0
  99. package/src/domain/events/request-event.test.ts +38 -0
  100. package/src/domain/events/request-event.ts +47 -0
  101. package/src/domain/interfaces/repository.ts +136 -0
  102. package/src/domain/models/pagination.ts +28 -0
  103. package/src/domain/services/base-model-service.ts +54 -0
  104. package/src/domain/services/model-service-args.ts +9 -0
  105. package/src/domain/services/model-service.normalization.test.ts +704 -0
  106. package/src/domain/services/model-service.test.ts +1616 -0
  107. package/src/domain/services/model-service.ts +597 -0
  108. package/src/domain/services/read-only-model-service.test.ts +1130 -0
  109. package/src/domain/services/read-only-model-service.ts +211 -0
  110. package/src/index.ts +17 -4
  111. package/src/shared/utils/schema-inference.ts +26 -0
  112. package/src/shared/utils/schema-inheritance.test.ts +295 -0
  113. package/src/shared/utils/schema-inheritance.ts +28 -0
  114. package/src/shared/utils/test/animal-schema.ts +46 -0
  115. package/src/shared/utils/test/animal-trait-schema.ts +45 -0
  116. package/src/shared/utils/test/elephant-schema.ts +58 -0
  117. package/src/shared/utils/test/elephant-trait-schema.ts +53 -0
  118. package/src/test/mock/models/mock-book-models.ts +78 -0
  119. package/src/test/mock/repositories/mock-memory-repository.assign.test.ts +215 -0
  120. package/src/test/mock/repositories/mock-memory-repository.basic.test.ts +129 -0
  121. package/src/test/mock/repositories/mock-memory-repository.bulk-upsert.test.ts +159 -0
  122. package/src/test/mock/repositories/mock-memory-repository.count.test.ts +98 -0
  123. package/src/test/mock/repositories/mock-memory-repository.search.test.ts +265 -0
  124. package/src/test/mock/repositories/mock-memory-repository.trash.test.ts +736 -0
  125. package/src/test/mock/repositories/mock-memory-repository.ts +401 -0
  126. package/src/test/mock/repositories/mock-memory-repository.upsert.test.ts +108 -0
  127. package/dist/databaseConnection.d.ts +0 -24
  128. package/dist/datastoreAbstract.d.ts +0 -37
  129. package/dist/declaro-data.cjs +0 -1
  130. package/dist/declaro-data.mjs +0 -250
  131. package/dist/hydrateEntity.d.ts +0 -8
  132. package/dist/index.d.ts +0 -4
  133. package/dist/serverConnection.d.ts +0 -15
  134. package/dist/trackedStatus.d.ts +0 -15
  135. package/src/databaseConnection.ts +0 -137
  136. package/src/datastoreAbstract.ts +0 -190
  137. package/src/hydrateEntity.ts +0 -36
  138. package/src/placeholder.test.ts +0 -7
  139. package/src/serverConnection.ts +0 -74
  140. package/src/trackedStatus.ts +0 -35
  141. package/tsconfig.json +0 -10
  142. package/vite.config.ts +0 -23
@@ -0,0 +1,159 @@
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 - Bulk Upsert Functionality', () => {
6
+ const mockSchema = MockBookSchema
7
+
8
+ let repository: MockMemoryRepository<typeof mockSchema>
9
+
10
+ beforeEach(() => {
11
+ repository = new MockMemoryRepository({ schema: mockSchema })
12
+ })
13
+
14
+ it('should upsert multiple new items', async () => {
15
+ const inputs = [
16
+ { id: 1, title: 'Book 1', author: 'Author 1', publishedDate: new Date('2023-01-01') },
17
+ { id: 2, title: 'Book 2', author: 'Author 2', publishedDate: new Date('2023-02-01') },
18
+ { id: 3, title: 'Book 3', author: 'Author 3', publishedDate: new Date('2023-03-01') },
19
+ ]
20
+
21
+ const upsertedItems = await repository.bulkUpsert(inputs)
22
+
23
+ expect(upsertedItems).toHaveLength(3)
24
+ expect(upsertedItems).toEqual(inputs)
25
+
26
+ // Verify all items were created
27
+ for (const input of inputs) {
28
+ expect(await repository.load({ id: input.id })).toEqual(input)
29
+ }
30
+ })
31
+
32
+ it('should update existing items and create new ones in same operation', async () => {
33
+ // Create some initial items
34
+ const existing1 = {
35
+ id: 1,
36
+ title: 'Original Book 1',
37
+ author: 'Original Author 1',
38
+ publishedDate: new Date('2023-01-01'),
39
+ }
40
+ const existing2 = {
41
+ id: 2,
42
+ title: 'Original Book 2',
43
+ author: 'Original Author 2',
44
+ publishedDate: new Date('2023-02-01'),
45
+ }
46
+ await repository.create(existing1)
47
+ await repository.create(existing2)
48
+
49
+ // Bulk upsert with mix of updates and new items
50
+ const upsertInputs = [
51
+ { id: 1, title: 'Updated Book 1', author: 'Updated Author 1', publishedDate: new Date('2023-06-01') }, // Update
52
+ { id: 2, title: 'Updated Book 2', author: 'Updated Author 2', publishedDate: new Date('2023-07-01') }, // Update
53
+ { id: 3, title: 'New Book 3', author: 'New Author 3', publishedDate: new Date('2023-08-01') }, // Create
54
+ { id: 4, title: 'New Book 4', author: 'New Author 4', publishedDate: new Date('2023-09-01') }, // Create
55
+ ]
56
+
57
+ const upsertedItems = await repository.bulkUpsert(upsertInputs)
58
+
59
+ expect(upsertedItems).toHaveLength(4)
60
+ expect(upsertedItems).toEqual(upsertInputs)
61
+
62
+ // Verify all items have the updated/new values
63
+ for (const input of upsertInputs) {
64
+ expect(await repository.load({ id: input.id })).toEqual(input)
65
+ }
66
+ })
67
+
68
+ it('should handle bulk upsert with items without primary keys', async () => {
69
+ const inputs = [
70
+ { title: 'Book Without ID 1', author: 'Author 1', publishedDate: new Date() },
71
+ { title: 'Book Without ID 2', author: 'Author 2', publishedDate: new Date() },
72
+ { title: 'Book Without ID 3', author: 'Author 3', publishedDate: new Date() },
73
+ ]
74
+
75
+ const upsertedItems = await repository.bulkUpsert(inputs)
76
+
77
+ expect(upsertedItems).toHaveLength(3)
78
+
79
+ // All items should have generated IDs
80
+ expect(upsertedItems[0].id).toBe(1)
81
+ expect(upsertedItems[1].id).toBe(2)
82
+ expect(upsertedItems[2].id).toBe(3)
83
+
84
+ // Verify content is preserved
85
+ for (let i = 0; i < inputs.length; i++) {
86
+ expect(upsertedItems[i].title).toBe(inputs[i].title)
87
+ expect(upsertedItems[i].author).toBe(inputs[i].author)
88
+ expect(await repository.load({ id: upsertedItems[i].id })).toEqual(upsertedItems[i])
89
+ }
90
+ })
91
+
92
+ it('should handle empty bulk upsert', async () => {
93
+ const upsertedItems = await repository.bulkUpsert([])
94
+
95
+ expect(upsertedItems).toEqual([])
96
+ })
97
+
98
+ it('should handle bulk upsert with partial updates', async () => {
99
+ // Create initial items
100
+ const initial1 = {
101
+ id: 1,
102
+ title: 'Original Book 1',
103
+ author: 'Original Author 1',
104
+ publishedDate: new Date('2023-01-01'),
105
+ }
106
+ const initial2 = {
107
+ id: 2,
108
+ title: 'Original Book 2',
109
+ author: 'Original Author 2',
110
+ publishedDate: new Date('2023-02-01'),
111
+ }
112
+ await repository.create(initial1)
113
+ await repository.create(initial2)
114
+
115
+ // Updates with all required fields but only changing title
116
+ const partialUpdates = [
117
+ { id: 1, title: 'Updated Title 1', author: 'Original Author 1', publishedDate: new Date('2023-01-01') },
118
+ { id: 2, title: 'Updated Title 2', author: 'Original Author 2', publishedDate: new Date('2023-02-01') },
119
+ ]
120
+
121
+ const upsertedItems = await repository.bulkUpsert(partialUpdates)
122
+
123
+ expect(upsertedItems).toHaveLength(2)
124
+
125
+ // Should have updated titles but kept other properties
126
+ expect(upsertedItems[0].title).toBe('Updated Title 1')
127
+ expect(upsertedItems[0].author).toBe('Original Author 1')
128
+ expect(upsertedItems[0].publishedDate).toEqual(initial1.publishedDate)
129
+
130
+ expect(upsertedItems[1].title).toBe('Updated Title 2')
131
+ expect(upsertedItems[1].author).toBe('Original Author 2')
132
+ expect(upsertedItems[1].publishedDate).toEqual(initial2.publishedDate)
133
+ })
134
+
135
+ it('should handle large bulk operations efficiently', async () => {
136
+ // Create a large number of items to test performance
137
+ const inputs: Array<{ id: number; title: string; author: string; publishedDate: Date }> = []
138
+ for (let i = 1; i <= 100; i++) {
139
+ inputs.push({
140
+ id: i,
141
+ title: `Book ${i}`,
142
+ author: `Author ${i}`,
143
+ publishedDate: new Date(`2023-${String((i % 12) + 1).padStart(2, '0')}-01`),
144
+ })
145
+ }
146
+
147
+ const startTime = Date.now()
148
+ const upsertedItems = await repository.bulkUpsert(inputs)
149
+ const endTime = Date.now()
150
+
151
+ expect(upsertedItems).toHaveLength(100)
152
+ expect(endTime - startTime).toBeLessThan(1000) // Should complete in under 1 second
153
+
154
+ // Verify a few random items
155
+ expect(await repository.load({ id: 1 })).toEqual(inputs[0])
156
+ expect(await repository.load({ id: 50 })).toEqual(inputs[49])
157
+ expect(await repository.load({ id: 100 })).toEqual(inputs[99])
158
+ })
159
+ })
@@ -0,0 +1,98 @@
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 - Count Functionality', () => {
6
+ const mockSchema = MockBookSchema
7
+
8
+ let repository: MockMemoryRepository<typeof mockSchema>
9
+
10
+ beforeEach(() => {
11
+ repository = new MockMemoryRepository({ schema: mockSchema })
12
+ })
13
+
14
+ it('should count all items when no filter is provided', async () => {
15
+ // Create test data
16
+ await repository.create({ title: 'Book 1', author: 'Author 1', publishedDate: new Date() })
17
+ await repository.create({ title: 'Book 2', author: 'Author 2', publishedDate: new Date() })
18
+ await repository.create({ title: 'Book 3', author: 'Author 3', publishedDate: new Date() })
19
+
20
+ const count = await repository.count({})
21
+ expect(count).toBe(3)
22
+ })
23
+
24
+ it('should count filtered items when filter function is provided', async () => {
25
+ const repositoryWithFilter = new MockMemoryRepository({
26
+ schema: mockSchema,
27
+ filter: (data, filters) => {
28
+ if (filters.text) {
29
+ return data.title.toLowerCase().includes(filters.text.toLowerCase())
30
+ }
31
+ return true
32
+ },
33
+ })
34
+
35
+ // Create test data
36
+ await repositoryWithFilter.create({ title: 'Test Book A', author: 'Author 1', publishedDate: new Date() })
37
+ await repositoryWithFilter.create({ title: 'Test Book B', author: 'Author 2', publishedDate: new Date() })
38
+ await repositoryWithFilter.create({ title: 'Other Book', author: 'Author 3', publishedDate: new Date() })
39
+ await repositoryWithFilter.create({ title: 'Another Book', author: 'Author 4', publishedDate: new Date() })
40
+
41
+ // Count all items
42
+ const totalCount = await repositoryWithFilter.count({})
43
+ expect(totalCount).toBe(4)
44
+
45
+ // Count filtered items
46
+ const filteredCount = await repositoryWithFilter.count({ text: 'Test' })
47
+ expect(filteredCount).toBe(2)
48
+
49
+ // Count with no matches
50
+ const noMatchCount = await repositoryWithFilter.count({ text: 'Ruby' })
51
+ expect(noMatchCount).toBe(0)
52
+ })
53
+
54
+ it('should count items correctly after CRUD operations', async () => {
55
+ const repositoryWithFilter = new MockMemoryRepository({
56
+ schema: mockSchema,
57
+ filter: (data, filters) => {
58
+ if (filters.text) {
59
+ return data.title.toLowerCase().includes(filters.text.toLowerCase())
60
+ }
61
+ return true
62
+ },
63
+ })
64
+
65
+ // Initial count should be 0
66
+ expect(await repositoryWithFilter.count({})).toBe(0)
67
+
68
+ // Create items
69
+ const book1 = await repositoryWithFilter.create({
70
+ title: 'Test Book 1',
71
+ author: 'Author 1',
72
+ publishedDate: new Date(),
73
+ })
74
+ const book2 = await repositoryWithFilter.create({
75
+ title: 'Other Book',
76
+ author: 'Author 2',
77
+ publishedDate: new Date(),
78
+ })
79
+
80
+ // Count after creation
81
+ expect(await repositoryWithFilter.count({})).toBe(2)
82
+ expect(await repositoryWithFilter.count({ text: 'Test' })).toBe(1)
83
+
84
+ // Remove an item
85
+ await repositoryWithFilter.remove({ id: book1.id })
86
+
87
+ // Count after removal
88
+ expect(await repositoryWithFilter.count({})).toBe(1)
89
+ expect(await repositoryWithFilter.count({ text: 'Test' })).toBe(0)
90
+
91
+ // Restore the item
92
+ await repositoryWithFilter.restore({ id: book1.id })
93
+
94
+ // Count after restore
95
+ expect(await repositoryWithFilter.count({})).toBe(2)
96
+ expect(await repositoryWithFilter.count({ text: 'Test' })).toBe(1)
97
+ })
98
+ })
@@ -0,0 +1,265 @@
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 - Search Functionality', () => {
6
+ const mockSchema = MockBookSchema
7
+
8
+ let repository: MockMemoryRepository<typeof mockSchema>
9
+
10
+ beforeEach(() => {
11
+ repository = new MockMemoryRepository({ schema: mockSchema })
12
+ })
13
+
14
+ it('should search for all items when no filter is provided', async () => {
15
+ const input1 = { title: 'Book One', author: 'Author A', publishedDate: new Date('2023-01-01') }
16
+ const input2 = { title: 'Book Two', author: 'Author B', publishedDate: new Date('2023-02-01') }
17
+ const input3 = { title: 'Book Three', author: 'Author C', publishedDate: new Date('2023-03-01') }
18
+
19
+ const item1 = await repository.create(input1)
20
+ const item2 = await repository.create(input2)
21
+ const item3 = await repository.create(input3)
22
+
23
+ const results = await repository.search({})
24
+
25
+ expect(results.results).toHaveLength(3)
26
+ expect(results.results).toEqual(expect.arrayContaining([item1, item2, item3]))
27
+ expect(results.pagination.total).toBe(3)
28
+ expect(results.pagination.page).toBe(1)
29
+ expect(results.pagination.pageSize).toBe(25)
30
+ expect(results.pagination.totalPages).toBe(1)
31
+ })
32
+
33
+ it('should filter items using custom filter function', async () => {
34
+ const repositoryWithFilter = new MockMemoryRepository({
35
+ schema: mockSchema,
36
+ filter: (data, filters) => {
37
+ if (filters.text) {
38
+ return (
39
+ data.title.toLowerCase().includes(filters.text.toLowerCase()) ||
40
+ data.author.toLowerCase().includes(filters.text.toLowerCase())
41
+ )
42
+ }
43
+ return true
44
+ },
45
+ })
46
+
47
+ const input1 = { title: 'JavaScript Guide', author: 'John Doe', publishedDate: new Date() }
48
+ const input2 = { title: 'Python Handbook', author: 'Jane Smith', publishedDate: new Date() }
49
+ const input3 = { title: 'Java Programming', author: 'John Johnson', publishedDate: new Date() }
50
+
51
+ const item1 = await repositoryWithFilter.create(input1)
52
+ const item2 = await repositoryWithFilter.create(input2)
53
+ const item3 = await repositoryWithFilter.create(input3)
54
+
55
+ // Search by title
56
+ const titleResults = await repositoryWithFilter.search({ text: 'JavaScript' })
57
+ expect(titleResults.results).toEqual([item1])
58
+
59
+ // Search by author
60
+ const authorResults = await repositoryWithFilter.search({ text: 'John' })
61
+ expect(authorResults.results).toHaveLength(2)
62
+ expect(authorResults.results).toEqual(expect.arrayContaining([item1, item3]))
63
+
64
+ // Search with no matches
65
+ const noResults = await repositoryWithFilter.search({ text: 'Ruby' })
66
+ expect(noResults.results).toEqual([])
67
+ expect(noResults.pagination.total).toBe(0)
68
+ })
69
+
70
+ it('should handle pagination correctly', async () => {
71
+ // Create 10 items
72
+ for (let i = 1; i <= 10; i++) {
73
+ const input = { title: `Book ${i}`, author: `Author ${i}`, publishedDate: new Date() }
74
+ await repository.create(input)
75
+ }
76
+
77
+ // Test first page with pageSize 3
78
+ const page1 = await repository.search(
79
+ {},
80
+ {
81
+ pagination: { page: 1, pageSize: 3 },
82
+ },
83
+ )
84
+ expect(page1.results).toHaveLength(3)
85
+ expect(page1.pagination.page).toBe(1)
86
+ expect(page1.pagination.pageSize).toBe(3)
87
+ expect(page1.pagination.total).toBe(10)
88
+ expect(page1.pagination.totalPages).toBe(4)
89
+
90
+ // Test second page
91
+ const page2 = await repository.search(
92
+ {},
93
+ {
94
+ pagination: { page: 2, pageSize: 3 },
95
+ },
96
+ )
97
+ expect(page2.results).toHaveLength(3)
98
+ expect(page2.pagination.page).toBe(2)
99
+ expect(page2.pagination.pageSize).toBe(3)
100
+
101
+ // Test last page (should have 1 item)
102
+ const page4 = await repository.search(
103
+ {},
104
+ {
105
+ pagination: { page: 4, pageSize: 3 },
106
+ },
107
+ )
108
+ expect(page4.results).toHaveLength(1)
109
+ expect(page4.pagination.page).toBe(4)
110
+
111
+ // Test page beyond available data
112
+ const pageEmpty = await repository.search(
113
+ {},
114
+ {
115
+ pagination: { page: 5, pageSize: 3 },
116
+ },
117
+ )
118
+ expect(pageEmpty.results).toHaveLength(0)
119
+ })
120
+
121
+ it('should use default pagination when not provided', async () => {
122
+ // Create 30 items to test default pagination
123
+ for (let i = 1; i <= 30; i++) {
124
+ await repository.create({ title: `Book ${i}`, author: `Author ${i}`, publishedDate: new Date() })
125
+ }
126
+
127
+ const results = await repository.search({})
128
+ expect(results.pagination.page).toBe(1)
129
+ expect(results.pagination.pageSize).toBe(25)
130
+ expect(results.pagination.total).toBe(30)
131
+ expect(results.pagination.totalPages).toBe(2)
132
+ expect(results.results).toHaveLength(25)
133
+ })
134
+
135
+ it('should handle edge cases with pagination', async () => {
136
+ // Test with empty repository
137
+ const emptyResults = await repository.search(
138
+ {},
139
+ {
140
+ pagination: { page: 1, pageSize: 10 },
141
+ },
142
+ )
143
+ expect(emptyResults.results).toEqual([])
144
+ expect(emptyResults.pagination.total).toBe(0)
145
+ expect(emptyResults.pagination.totalPages).toBe(0)
146
+
147
+ // Test with page 0 (results in empty due to slice calculation)
148
+ await repository.create({ title: 'Test Book', author: 'Test Author', publishedDate: new Date() })
149
+ const page0Results = await repository.search(
150
+ {},
151
+ {
152
+ pagination: { page: 0, pageSize: 10 },
153
+ },
154
+ )
155
+ expect(page0Results.pagination.page).toBe(0)
156
+ expect(page0Results.results).toHaveLength(0) // slice(-10, 0) returns empty array
157
+ })
158
+
159
+ it('should handle sorting correctly', async () => {
160
+ const input1 = { title: 'C Book', author: 'Author Z', publishedDate: new Date('2023-03-01') }
161
+ const input2 = { title: 'A Book', author: 'Author Y', publishedDate: new Date('2023-01-01') }
162
+ const input3 = { title: 'B Book', author: 'Author X', publishedDate: new Date('2023-02-01') }
163
+
164
+ const item1 = await repository.create(input1)
165
+ const item2 = await repository.create(input2)
166
+ const item3 = await repository.create(input3)
167
+
168
+ // Sort by title ascending
169
+ const titleAscResults = await repository.search(
170
+ {},
171
+ {
172
+ sort: [{ title: 'asc' }],
173
+ },
174
+ )
175
+ expect(titleAscResults.results.map((r) => r.title)).toEqual(['A Book', 'B Book', 'C Book'])
176
+
177
+ // Sort by title descending
178
+ const titleDescResults = await repository.search(
179
+ {},
180
+ {
181
+ sort: [{ title: 'desc' }],
182
+ },
183
+ )
184
+ expect(titleDescResults.results.map((r) => r.title)).toEqual(['C Book', 'B Book', 'A Book'])
185
+
186
+ // Sort by author ascending
187
+ const authorAscResults = await repository.search(
188
+ {},
189
+ {
190
+ sort: [{ author: 'asc' }],
191
+ },
192
+ )
193
+ expect(authorAscResults.results.map((r) => r.author)).toEqual(['Author X', 'Author Y', 'Author Z'])
194
+
195
+ // Multiple field sort: title asc, then author desc
196
+ const multiSortResults = await repository.search(
197
+ {},
198
+ {
199
+ sort: [{ title: 'asc' }, { author: 'desc' }],
200
+ },
201
+ )
202
+ expect(multiSortResults.results.map((r) => r.title)).toEqual(['A Book', 'B Book', 'C Book'])
203
+ })
204
+
205
+ it('should handle sorting with pagination', async () => {
206
+ for (let i = 1; i <= 5; i++) {
207
+ await repository.create({
208
+ title: `Book ${String.fromCharCode(69 - i)}`, // D, C, B, A, @
209
+ author: `Author ${i}`,
210
+ publishedDate: new Date(),
211
+ })
212
+ }
213
+
214
+ // Sort by title ascending and get first page
215
+ const sortedPage1 = await repository.search(
216
+ {},
217
+ {
218
+ sort: [{ title: 'asc' }],
219
+ pagination: { page: 1, pageSize: 2 },
220
+ },
221
+ )
222
+ expect(sortedPage1.results.map((r) => r.title)).toEqual(['Book @', 'Book A'])
223
+ expect(sortedPage1.pagination.totalPages).toBe(3)
224
+
225
+ // Get second page with same sort
226
+ const sortedPage2 = await repository.search(
227
+ {},
228
+ {
229
+ sort: [{ title: 'asc' }],
230
+ pagination: { page: 2, pageSize: 2 },
231
+ },
232
+ )
233
+ expect(sortedPage2.results.map((r) => r.title)).toEqual(['Book B', 'Book C'])
234
+ })
235
+
236
+ it('should handle combined filtering, sorting, and pagination', async () => {
237
+ const repositoryWithFilter = new MockMemoryRepository({
238
+ schema: mockSchema,
239
+ filter: (data, filters) => {
240
+ if (filters.text) {
241
+ return data.title.toLowerCase().includes(filters.text.toLowerCase())
242
+ }
243
+ return true
244
+ },
245
+ })
246
+
247
+ await repositoryWithFilter.create({ title: 'Test Z Book', author: 'Author 1', publishedDate: new Date() })
248
+ await repositoryWithFilter.create({ title: 'Test A Book', author: 'Author 2', publishedDate: new Date() })
249
+ await repositoryWithFilter.create({ title: 'Other Book', author: 'Author 3', publishedDate: new Date() })
250
+ await repositoryWithFilter.create({ title: 'Test B Book', author: 'Author 4', publishedDate: new Date() })
251
+
252
+ const results = await repositoryWithFilter.search(
253
+ { text: 'Test' },
254
+ {
255
+ sort: [{ title: 'asc' }],
256
+ pagination: { page: 1, pageSize: 2 },
257
+ },
258
+ )
259
+
260
+ expect(results.results).toHaveLength(2)
261
+ expect(results.results.map((r) => r.title)).toEqual(['Test A Book', 'Test B Book'])
262
+ expect(results.pagination.total).toBe(3) // 3 "Test" books total
263
+ expect(results.pagination.totalPages).toBe(2)
264
+ })
265
+ })