@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,69 @@
1
+ import type { IAuthSession } from '@declaro/auth'
2
+ import { type IEvent, type IActionDescriptor, ActionDescriptor, type IActionDescriptorInput } from '@declaro/core'
3
+ import { v4 as uuid } from 'uuid'
4
+
5
+ export interface IDomainEvent<T, M = any> extends IEvent {
6
+ eventId: string
7
+ data?: T
8
+ meta?: M
9
+ timestamp: Date
10
+ descriptor: IActionDescriptor
11
+ session?: IAuthSession
12
+ }
13
+
14
+ export interface IDomainEventOptions<TData, TMeta = any> {
15
+ type?: string
16
+ eventId?: string
17
+ data?: TData
18
+ timestamp?: Date
19
+ descriptor?: IActionDescriptorInput
20
+ session?: IAuthSession
21
+ meta?: TMeta
22
+ }
23
+
24
+ export interface IDomainEventJSON<T, M = any> {
25
+ eventId: string
26
+ data?: T
27
+ meta: M
28
+ timestamp: string // JSON-compatible format
29
+ type: string
30
+ session?: { id: string } // Simplified session representation
31
+ }
32
+
33
+ export class DomainEvent<T, M = any> implements IDomainEvent<T, M> {
34
+ readonly eventId: string
35
+ public data?: T
36
+ public timestamp: Date
37
+ public type: string = 'UNKNOWN_EVENT'
38
+ public descriptor: ActionDescriptor
39
+ public session?: IAuthSession
40
+ public meta: M
41
+
42
+ constructor(options: IDomainEventOptions<T, M> = {}) {
43
+ if (options.type) {
44
+ this.type = options.type
45
+ }
46
+ this.eventId = options.eventId ?? uuid()
47
+ this.timestamp = options.timestamp ?? new Date()
48
+ this.descriptor = options.descriptor
49
+ ? ActionDescriptor.fromJSON(options.descriptor)
50
+ : ActionDescriptor.fromString(this.type)
51
+ if (options.descriptor && this.type === 'UNKNOWN_EVENT') {
52
+ this.type = this.descriptor.toString()
53
+ }
54
+ this.data = options.data
55
+ this.meta = options.meta ?? ({} as M) // Ensure meta is always defined, defaulting to an empty object
56
+ this.session = options.session
57
+ }
58
+
59
+ toJSON(): IDomainEventJSON<T, M> {
60
+ return {
61
+ eventId: this.eventId,
62
+ data: this.data,
63
+ meta: this.meta,
64
+ timestamp: this.timestamp.toISOString(),
65
+ type: this.type,
66
+ session: this.session ? { id: this.session.id } : undefined,
67
+ }
68
+ }
69
+ }
@@ -0,0 +1,37 @@
1
+ export enum ModelQueryEvent {
2
+ BeforeLoad = 'beforeLoad',
3
+ AfterLoad = 'afterLoad',
4
+ BeforeLoadMany = 'beforeLoadMany',
5
+ AfterLoadMany = 'afterLoadMany',
6
+ BeforeSearch = 'beforeSearch',
7
+ AfterSearch = 'afterSearch',
8
+ BeforeCount = 'beforeCount',
9
+ AfterCount = 'afterCount',
10
+ }
11
+
12
+ export enum ModelMutationAction {
13
+ Create = 'create',
14
+ BeforeCreate = 'beforeCreate',
15
+ AfterCreate = 'afterCreate',
16
+ Update = 'update',
17
+ BeforeUpdate = 'beforeUpdate',
18
+ AfterUpdate = 'afterUpdate',
19
+ Duplicate = 'duplicate',
20
+ BeforeDuplicate = 'beforeDuplicate',
21
+ AfterDuplicate = 'afterDuplicate',
22
+ Remove = 'remove',
23
+ BeforeRemove = 'beforeRemove',
24
+ AfterRemove = 'afterRemove',
25
+ Restore = 'restore',
26
+ BeforeRestore = 'beforeRestore',
27
+ AfterRestore = 'afterRestore',
28
+ EmptyTrash = 'emptyTrash',
29
+ BeforeEmptyTrash = 'beforeEmptyTrash',
30
+ AfterEmptyTrash = 'afterEmptyTrash',
31
+ PermanentlyDeleteFromTrash = 'permanentlyDeleteFromTrash',
32
+ BeforePermanentlyDeleteFromTrash = 'beforePermanentlyDeleteFromTrash',
33
+ AfterPermanentlyDeleteFromTrash = 'afterPermanentlyDeleteFromTrash',
34
+ PermanentlyDelete = 'permanentlyDelete',
35
+ BeforePermanentlyDelete = 'beforePermanentlyDelete',
36
+ AfterPermanentlyDelete = 'afterPermanentlyDelete',
37
+ }
@@ -0,0 +1,390 @@
1
+ import { describe, it, expect } from 'bun:test'
2
+ import { MutationEvent } from './mutation-event'
3
+ import { ActionDescriptor, type IActionDescriptorInput } from '@declaro/core'
4
+
5
+ interface IBookResult {
6
+ id: string
7
+ title: string
8
+ description: string
9
+ author: string
10
+ year: number
11
+ }
12
+
13
+ interface IBookInput {
14
+ title: string
15
+ description: string
16
+ author: string
17
+ year: number
18
+ }
19
+
20
+ describe('MutationEvent', () => {
21
+ it('should create a mutation event with input and descriptor', () => {
22
+ const input: IBookInput = {
23
+ title: 'Test Book',
24
+ description: 'A book for testing',
25
+ author: 'Author Name',
26
+ year: 2024,
27
+ }
28
+ const descriptor: IActionDescriptorInput = { namespace: 'books', resource: 'book', action: 'create' }
29
+ const event = new MutationEvent<IBookResult, IBookInput>(descriptor, input)
30
+
31
+ expect(event.input).toEqual(input)
32
+ expect(event.descriptor.toString()).toEqual(new ActionDescriptor(descriptor).toString())
33
+ expect(event.meta).toEqual({})
34
+ expect(event.data).toBeUndefined()
35
+ })
36
+
37
+ it('should allow setting existing result in meta', () => {
38
+ const input: IBookInput = {
39
+ title: 'Test Book',
40
+ description: 'A book for testing',
41
+ author: 'Author Name',
42
+ year: 2024,
43
+ }
44
+ const existing: IBookResult = {
45
+ id: '1',
46
+ title: 'Old Title',
47
+ description: 'Old Description',
48
+ author: 'Old Author',
49
+ year: 2000,
50
+ }
51
+
52
+ const descriptor: IActionDescriptorInput = { namespace: 'books', resource: 'book', action: 'update' }
53
+ const event = new MutationEvent<IBookResult, IBookInput>(descriptor, input, {
54
+ existing,
55
+ })
56
+
57
+ expect(event.input).toEqual(input)
58
+ expect(event.meta.existing).toEqual(existing)
59
+ })
60
+
61
+ it('should update meta correctly', () => {
62
+ const input: IBookInput = {
63
+ title: 'Test Book',
64
+ description: 'A book for testing',
65
+ author: 'Author Name',
66
+ year: 2024,
67
+ }
68
+
69
+ const descriptor: IActionDescriptorInput = { namespace: 'books', resource: 'book', action: 'create' }
70
+ const event = new MutationEvent<IBookResult, IBookInput>(descriptor, input)
71
+
72
+ const existing: IBookResult = {
73
+ id: '1',
74
+ title: 'Old Title',
75
+ description: 'Old Description',
76
+ author: 'Old Author',
77
+ year: 2000,
78
+ }
79
+
80
+ event.setMeta({
81
+ existing,
82
+ })
83
+
84
+ expect(event.meta.existing).toEqual(existing)
85
+ })
86
+
87
+ it('should set result correctly', () => {
88
+ const input: IBookInput = {
89
+ title: 'Test Book',
90
+ description: 'A book for testing',
91
+ author: 'Author Name',
92
+ year: 2024,
93
+ }
94
+ const descriptor: IActionDescriptorInput = { namespace: 'books', resource: 'book', action: 'create' }
95
+ const event = new MutationEvent<IBookResult, IBookInput>(descriptor, input)
96
+
97
+ expect(event.data).toBeUndefined()
98
+
99
+ const result: IBookResult = {
100
+ id: '1',
101
+ title: 'Test Book',
102
+ description: 'A book for testing',
103
+ author: 'Author Name',
104
+ year: 2024,
105
+ }
106
+ event.setResult(result)
107
+
108
+ expect(event.data).toEqual(result)
109
+ })
110
+
111
+ it('should serialize to JSON with eventId and timestamp', () => {
112
+ const input: IBookInput = {
113
+ title: 'Test Book',
114
+ description: 'A book for testing',
115
+ author: 'Author Name',
116
+ year: 2024,
117
+ }
118
+ const descriptor: IActionDescriptorInput = { namespace: 'books', resource: 'book', action: 'create' }
119
+ const event = new MutationEvent<IBookResult, IBookInput>(descriptor, input)
120
+
121
+ const result: IBookResult = {
122
+ id: '1',
123
+ title: 'Test Book',
124
+ description: 'A book for testing',
125
+ author: 'Author Name',
126
+ year: 2024,
127
+ }
128
+ event.setResult(result)
129
+
130
+ const json = event.toJSON()
131
+
132
+ expect(json.eventId).toBeDefined()
133
+ expect(json.timestamp).toBeDefined()
134
+ expect(json.type).toBe('books::book.create')
135
+ expect(json.input).toEqual(input)
136
+ expect(json.data).toEqual(result)
137
+ })
138
+
139
+ it('should handle chaining of setter methods', () => {
140
+ const input: IBookInput = {
141
+ title: 'Test Book',
142
+ description: 'A book for testing',
143
+ author: 'Author Name',
144
+ year: 2024,
145
+ }
146
+ const existing: IBookResult = {
147
+ id: '1',
148
+ title: 'Old Title',
149
+ description: 'Old Description',
150
+ author: 'Old Author',
151
+ year: 2000,
152
+ }
153
+ const result: IBookResult = {
154
+ id: '1',
155
+ title: 'Test Book',
156
+ description: 'A book for testing',
157
+ author: 'Author Name',
158
+ year: 2024,
159
+ }
160
+
161
+ const descriptor: IActionDescriptorInput = { namespace: 'books', resource: 'book', action: 'update' }
162
+ const event = new MutationEvent<IBookResult, IBookInput>(descriptor, input)
163
+ .setMeta({ existing })
164
+ .setResult(result)
165
+
166
+ expect(event.input).toEqual(input)
167
+ expect(event.meta.existing).toEqual(existing)
168
+ expect(event.data).toEqual(result)
169
+ })
170
+
171
+ it('should update input after initialization', () => {
172
+ const input: IBookInput = {
173
+ title: 'Test Book',
174
+ description: 'A book for testing',
175
+ author: 'Author Name',
176
+ year: 2024,
177
+ }
178
+ const descriptor: IActionDescriptorInput = { namespace: 'books', resource: 'book', action: 'create' }
179
+ const event = new MutationEvent<IBookResult, IBookInput>(descriptor, input)
180
+
181
+ const newInput: IBookInput = {
182
+ title: 'Updated Book',
183
+ description: 'Updated description',
184
+ author: 'New Author',
185
+ year: 2025,
186
+ }
187
+ event.setInput(newInput)
188
+
189
+ expect(event.input).toEqual(newInput)
190
+ })
191
+
192
+ it('should allow setting input multiple times', () => {
193
+ const input: IBookInput = {
194
+ title: 'First Book',
195
+ description: 'First description',
196
+ author: 'First Author',
197
+ year: 2024,
198
+ }
199
+ const descriptor: IActionDescriptorInput = { namespace: 'books', resource: 'book', action: 'create' }
200
+ const event = new MutationEvent<IBookResult, IBookInput>(descriptor, input)
201
+
202
+ const secondInput: IBookInput = {
203
+ title: 'Second Book',
204
+ description: 'Second description',
205
+ author: 'Second Author',
206
+ year: 2025,
207
+ }
208
+ event.setInput(secondInput)
209
+ expect(event.input).toEqual(secondInput)
210
+
211
+ const thirdInput: IBookInput = {
212
+ title: 'Third Book',
213
+ description: 'Third description',
214
+ author: 'Third Author',
215
+ year: 2026,
216
+ }
217
+ event.setInput(thirdInput)
218
+ expect(event.input).toEqual(thirdInput)
219
+ })
220
+
221
+ it('should return this from setInput for chaining', () => {
222
+ const input: IBookInput = {
223
+ title: 'Test Book',
224
+ description: 'A book for testing',
225
+ author: 'Author Name',
226
+ year: 2024,
227
+ }
228
+ const descriptor: IActionDescriptorInput = { namespace: 'books', resource: 'book', action: 'create' }
229
+ const event = new MutationEvent<IBookResult, IBookInput>(descriptor, input)
230
+
231
+ const newInput: IBookInput = {
232
+ title: 'Updated Book',
233
+ description: 'Updated description',
234
+ author: 'New Author',
235
+ year: 2025,
236
+ }
237
+ const result: IBookResult = {
238
+ id: '1',
239
+ ...newInput,
240
+ }
241
+
242
+ const returned = event.setInput(newInput).setResult(result)
243
+
244
+ expect(returned).toBe(event)
245
+ expect(event.input).toEqual(newInput)
246
+ expect(event.data).toEqual(result)
247
+ })
248
+
249
+ it('should replace entire input object when using setInput', () => {
250
+ const input: IBookInput = {
251
+ title: 'Original Book',
252
+ description: 'Original description',
253
+ author: 'Original Author',
254
+ year: 2024,
255
+ }
256
+ const descriptor: IActionDescriptorInput = { namespace: 'books', resource: 'book', action: 'update' }
257
+ const event = new MutationEvent<IBookResult, IBookInput>(descriptor, input)
258
+
259
+ expect(event.input.title).toBe('Original Book')
260
+
261
+ const partialInput: IBookInput = {
262
+ title: 'New Title',
263
+ description: 'New description',
264
+ author: 'New Author',
265
+ year: 2025,
266
+ }
267
+ event.setInput(partialInput)
268
+
269
+ expect(event.input).toEqual(partialInput)
270
+ expect(event.input.title).toBe('New Title')
271
+ expect(event.input.year).toBe(2025)
272
+ })
273
+
274
+ it('should update existing in meta', () => {
275
+ const input: IBookInput = {
276
+ title: 'Test Book',
277
+ description: 'A book for testing',
278
+ author: 'Author Name',
279
+ year: 2024,
280
+ }
281
+ const existing: IBookResult = {
282
+ id: '1',
283
+ title: 'Old Title',
284
+ description: 'Old Description',
285
+ author: 'Old Author',
286
+ year: 2000,
287
+ }
288
+ const descriptor: IActionDescriptorInput = { namespace: 'books', resource: 'book', action: 'update' }
289
+ const event = new MutationEvent<IBookResult, IBookInput>(descriptor, input, { existing })
290
+
291
+ const newExisting: IBookResult = {
292
+ id: '1',
293
+ title: 'Different Title',
294
+ description: 'Different Description',
295
+ author: 'Different Author',
296
+ year: 2010,
297
+ }
298
+ event.setMeta({ existing: newExisting })
299
+
300
+ expect(event.meta.existing).toEqual(newExisting)
301
+ })
302
+
303
+ it('should handle delete action with existing record', () => {
304
+ const input = { id: '1' }
305
+ const existing: IBookResult = {
306
+ id: '1',
307
+ title: 'Book to Delete',
308
+ description: 'This will be deleted',
309
+ author: 'Some Author',
310
+ year: 2020,
311
+ }
312
+ const descriptor: IActionDescriptorInput = { namespace: 'books', resource: 'book', action: 'delete' }
313
+ const event = new MutationEvent<IBookResult, { id: string }>(descriptor, input, { existing })
314
+
315
+ expect(event.descriptor.action).toBe('delete')
316
+ expect(event.meta.existing).toEqual(existing)
317
+ expect(event.input).toEqual(input)
318
+ })
319
+
320
+ it('should maintain type throughout lifecycle', () => {
321
+ const input: IBookInput = {
322
+ title: 'Test Book',
323
+ description: 'A book for testing',
324
+ author: 'Author Name',
325
+ year: 2024,
326
+ }
327
+ const descriptor: IActionDescriptorInput = { namespace: 'books', resource: 'book', action: 'create' }
328
+ const event = new MutationEvent<IBookResult, IBookInput>(descriptor, input)
329
+
330
+ expect(event.type).toBe('books::book.create')
331
+
332
+ event.setInput({ ...input, title: 'Updated' })
333
+ expect(event.type).toBe('books::book.create')
334
+
335
+ const result: IBookResult = {
336
+ id: '1',
337
+ title: 'Updated',
338
+ description: 'A book for testing',
339
+ author: 'Author Name',
340
+ year: 2024,
341
+ }
342
+ event.setResult(result)
343
+ expect(event.type).toBe('books::book.create')
344
+ })
345
+
346
+ it('should serialize existing to JSON for update action', () => {
347
+ const input: IBookInput = {
348
+ title: 'Updated Title',
349
+ description: 'Updated description',
350
+ author: 'Author Name',
351
+ year: 2024,
352
+ }
353
+ const existing: IBookResult = {
354
+ id: '1',
355
+ title: 'Old Title',
356
+ description: 'Old Description',
357
+ author: 'Old Author',
358
+ year: 2020,
359
+ }
360
+ const descriptor: IActionDescriptorInput = { namespace: 'books', resource: 'book', action: 'update' }
361
+ const event = new MutationEvent<IBookResult, IBookInput>(descriptor, input, { existing })
362
+
363
+ const json = event.toJSON()
364
+
365
+ expect(json.meta.existing).toEqual(existing)
366
+ expect(json.input).toEqual(input)
367
+ })
368
+
369
+ it('should handle create action without existing', () => {
370
+ const input: IBookInput = {
371
+ title: 'New Book',
372
+ description: 'Brand new',
373
+ author: 'New Author',
374
+ year: 2024,
375
+ }
376
+ const descriptor: IActionDescriptorInput = { namespace: 'books', resource: 'book', action: 'create' }
377
+ const event = new MutationEvent<IBookResult, IBookInput>(descriptor, input)
378
+
379
+ expect(event.meta.existing).toBeUndefined()
380
+ expect(event.descriptor.action).toBe('create')
381
+
382
+ const result: IBookResult = {
383
+ id: '1',
384
+ ...input,
385
+ }
386
+ event.setResult(result)
387
+
388
+ expect(event.data).toEqual(result)
389
+ })
390
+ })
@@ -0,0 +1,53 @@
1
+ import type { IActionDescriptorInput } from '@declaro/core'
2
+ import { RequestEvent, type IRequestEventMeta } from './request-event'
3
+
4
+ export interface IMutationEventMeta<TResult> extends IRequestEventMeta {
5
+ existing?: TResult
6
+ }
7
+
8
+ export interface ICreateEventMeta<TResult, TInput> extends IMutationEventMeta<TResult> {
9
+ args?: {
10
+ input: TInput
11
+ options?: Record<string, unknown>
12
+ }
13
+ }
14
+
15
+ export interface IUpdateEventMeta<TResult, TLookup, TInput> extends IMutationEventMeta<TResult> {
16
+ args?: {
17
+ lookup: TLookup
18
+ input: TInput
19
+ options?: Record<string, unknown>
20
+ }
21
+ }
22
+
23
+ export interface IRemoveEventMeta<TResult, TLookup> extends IMutationEventMeta<TResult> {
24
+ args?: {
25
+ lookup: TLookup
26
+ options?: Record<string, unknown>
27
+ }
28
+ }
29
+
30
+ export interface IRestoreEventMeta<TResult, TLookup> extends IMutationEventMeta<TResult> {
31
+ args?: {
32
+ lookup: TLookup
33
+ options?: Record<string, unknown>
34
+ }
35
+ }
36
+
37
+ export interface IDuplicateEventMeta<TResult, TLookup, TInput> extends IMutationEventMeta<TResult> {
38
+ args?: {
39
+ lookup: TLookup
40
+ overrides?: Partial<TInput>
41
+ options?: Record<string, unknown>
42
+ }
43
+ }
44
+
45
+ export class MutationEvent<
46
+ TResult,
47
+ TInput,
48
+ TMeta extends IMutationEventMeta<TResult> = IMutationEventMeta<TResult>,
49
+ > extends RequestEvent<TResult, TInput, TMeta> {
50
+ constructor(descriptor: IActionDescriptorInput, input: TInput, meta: TMeta = {} as TMeta) {
51
+ super(descriptor, input, meta)
52
+ }
53
+ }