@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,58 @@
|
|
|
1
|
+
// Elephant Schema inheriting from Animal Schema
|
|
2
|
+
|
|
3
|
+
import z4 from 'zod/v4'
|
|
4
|
+
import {
|
|
5
|
+
AnimalDetailSchema,
|
|
6
|
+
AnimalFiltersSchema,
|
|
7
|
+
AnimalLookupSchema,
|
|
8
|
+
AnimalSortSchema,
|
|
9
|
+
AnimalSummarySchema,
|
|
10
|
+
} from './animal-schema'
|
|
11
|
+
import { ModelSchema } from '@declaro/core'
|
|
12
|
+
import { ZodModel } from '@declaro/zod'
|
|
13
|
+
import { ElephantTraitsDetailSchema, ElephantTraitsSummarySchema } from './elephant-trait-schema'
|
|
14
|
+
|
|
15
|
+
export const ElephantDetailSchema = z4.object({
|
|
16
|
+
...AnimalDetailSchema.shape,
|
|
17
|
+
trunkLength: z4.number(),
|
|
18
|
+
get traits() {
|
|
19
|
+
return z4.array(ElephantTraitsDetailSchema)
|
|
20
|
+
},
|
|
21
|
+
get elephantDetails() {
|
|
22
|
+
return z4.object({
|
|
23
|
+
favoriteFood: z4.string(),
|
|
24
|
+
weight: z4.number(),
|
|
25
|
+
})
|
|
26
|
+
},
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
export const ElephantSummarySchema = z4.object({
|
|
30
|
+
...AnimalSummarySchema.shape,
|
|
31
|
+
trunkLength: z4.number(),
|
|
32
|
+
get traits() {
|
|
33
|
+
return z4.array(ElephantTraitsSummarySchema)
|
|
34
|
+
},
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
export const ElephantLookupSchema = z4.object({
|
|
38
|
+
...AnimalLookupSchema.shape,
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
export const ElephantFiltersSchema = z4.object({
|
|
42
|
+
...AnimalFiltersSchema.shape,
|
|
43
|
+
minTrunkLength: z4.number().optional(),
|
|
44
|
+
maxTrunkLength: z4.number().optional(),
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
export const ElephantSchema = ModelSchema.create('ElephantSchema')
|
|
48
|
+
.read({
|
|
49
|
+
detail: (h) => new ZodModel('ElephantDetail', ElephantDetailSchema),
|
|
50
|
+
lookup: (h) => new ZodModel('ElephantLookup', ElephantLookupSchema),
|
|
51
|
+
})
|
|
52
|
+
.search({
|
|
53
|
+
filters: (h) => new ZodModel('ElephantFilters', ElephantFiltersSchema),
|
|
54
|
+
sort: (h) => new ZodModel('ElephantSort', AnimalSortSchema),
|
|
55
|
+
summary: (h) => new ZodModel('ElephantSummary', ElephantSummarySchema),
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
export type IElephantSchema = typeof ElephantSchema
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Elephant Traits Schema inheriting from Animal Traits Schema
|
|
2
|
+
|
|
3
|
+
import z4 from 'zod/v4'
|
|
4
|
+
import { ModelSchema } from '@declaro/core'
|
|
5
|
+
import { ZodModel } from '@declaro/zod'
|
|
6
|
+
import {
|
|
7
|
+
AnimalTraitsDetailSchema,
|
|
8
|
+
AnimalTraitsFiltersSchema,
|
|
9
|
+
AnimalTraitsLookupSchema,
|
|
10
|
+
AnimalTraitsSortSchema,
|
|
11
|
+
AnimalTraitsSummarySchema,
|
|
12
|
+
} from './animal-trait-schema'
|
|
13
|
+
import { ElephantDetailSchema, ElephantSummarySchema } from './elephant-schema'
|
|
14
|
+
|
|
15
|
+
export const ElephantTraitsDetailSchema = z4.object({
|
|
16
|
+
...AnimalTraitsDetailSchema.shape,
|
|
17
|
+
isSharp: z4.boolean(),
|
|
18
|
+
length: z4.number(),
|
|
19
|
+
get animal() {
|
|
20
|
+
return ElephantDetailSchema.optional()
|
|
21
|
+
},
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
export const ElephantTraitsSummarySchema = z4.object({
|
|
25
|
+
...AnimalTraitsSummarySchema.shape,
|
|
26
|
+
isSharp: z4.boolean(),
|
|
27
|
+
length: z4.number(),
|
|
28
|
+
get animal() {
|
|
29
|
+
return ElephantSummarySchema.optional()
|
|
30
|
+
},
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
export const ElephantTraitsLookupSchema = z4.object({
|
|
34
|
+
...AnimalTraitsLookupSchema.shape,
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
export const ElephantTraitsFiltersSchema = z4.object({
|
|
38
|
+
...AnimalTraitsFiltersSchema.shape,
|
|
39
|
+
isSharp: z4.boolean().optional(),
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
export const ElephantTraitsSchema = ModelSchema.create('ElephantTraitsSchema')
|
|
43
|
+
.read({
|
|
44
|
+
detail: (h) => new ZodModel('ElephantTraitsDetail', ElephantTraitsDetailSchema),
|
|
45
|
+
lookup: (h) => new ZodModel('ElephantTraitsLookup', ElephantTraitsLookupSchema),
|
|
46
|
+
})
|
|
47
|
+
.search({
|
|
48
|
+
filters: (h) => new ZodModel('ElephantTraitsFilters', ElephantTraitsFiltersSchema),
|
|
49
|
+
sort: (h) => new ZodModel('ElephantTraitsSort', AnimalTraitsSortSchema),
|
|
50
|
+
summary: (h) => new ZodModel('ElephantTraitsSummary', ElephantTraitsSummarySchema),
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
export type IElephantTraitSchema = typeof ElephantTraitsSchema
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { ModelSchema } from '@declaro/core'
|
|
2
|
+
import { sortArray, ZodModel } from '@declaro/zod'
|
|
3
|
+
import { z } from 'zod/v4'
|
|
4
|
+
import type {
|
|
5
|
+
InferFilters,
|
|
6
|
+
InferLookup,
|
|
7
|
+
InferDetail,
|
|
8
|
+
InferSummary,
|
|
9
|
+
InferSort,
|
|
10
|
+
InferInput,
|
|
11
|
+
InferSearchResults,
|
|
12
|
+
InferEntityMetadata,
|
|
13
|
+
} from '../../../shared/utils/schema-inference'
|
|
14
|
+
|
|
15
|
+
export const MockBookSchema = ModelSchema.create('Book')
|
|
16
|
+
.read({
|
|
17
|
+
detail: (h) =>
|
|
18
|
+
new ZodModel(
|
|
19
|
+
h.name,
|
|
20
|
+
z.object({
|
|
21
|
+
id: z.number().int().positive(),
|
|
22
|
+
title: z.string().min(2).max(100),
|
|
23
|
+
author: z.string().min(2).max(100),
|
|
24
|
+
publishedDate: z.coerce.date(),
|
|
25
|
+
}),
|
|
26
|
+
),
|
|
27
|
+
lookup: (h) =>
|
|
28
|
+
new ZodModel(
|
|
29
|
+
h.name,
|
|
30
|
+
z.object({
|
|
31
|
+
id: z.number().int().positive(),
|
|
32
|
+
}),
|
|
33
|
+
),
|
|
34
|
+
})
|
|
35
|
+
.search({
|
|
36
|
+
filters: (h) =>
|
|
37
|
+
new ZodModel(
|
|
38
|
+
h.name,
|
|
39
|
+
z.object({
|
|
40
|
+
text: z.string().optional(),
|
|
41
|
+
}),
|
|
42
|
+
),
|
|
43
|
+
summary: (h) =>
|
|
44
|
+
new ZodModel(
|
|
45
|
+
h.name,
|
|
46
|
+
z.object({
|
|
47
|
+
id: z.number().int().positive(),
|
|
48
|
+
title: z.string().min(2).max(100),
|
|
49
|
+
author: z.string().min(2).max(100),
|
|
50
|
+
publishedDate: z.coerce.date(),
|
|
51
|
+
}),
|
|
52
|
+
),
|
|
53
|
+
sort: (h) => new ZodModel(h.name, sortArray(['title', 'author'])),
|
|
54
|
+
})
|
|
55
|
+
.write({
|
|
56
|
+
input: (h) =>
|
|
57
|
+
new ZodModel(
|
|
58
|
+
h.name,
|
|
59
|
+
z.object({
|
|
60
|
+
id: z.number().int().positive().optional(),
|
|
61
|
+
title: z.string().min(2).max(100),
|
|
62
|
+
author: z.string().min(2).max(100),
|
|
63
|
+
publishedDate: z.coerce.date(),
|
|
64
|
+
}),
|
|
65
|
+
),
|
|
66
|
+
})
|
|
67
|
+
.entity({
|
|
68
|
+
primaryKey: 'id',
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
export type MockBookFilters = InferFilters<typeof MockBookSchema>
|
|
72
|
+
export type MockBookLookup = InferLookup<typeof MockBookSchema>
|
|
73
|
+
export type MockBookDetail = InferDetail<typeof MockBookSchema>
|
|
74
|
+
export type MockBookSummary = InferSummary<typeof MockBookSchema>
|
|
75
|
+
export type MockBookSort = InferSort<typeof MockBookSchema>
|
|
76
|
+
export type MockBookInput = InferInput<typeof MockBookSchema>
|
|
77
|
+
export type MockBookSearchResults = InferSearchResults<typeof MockBookSchema>
|
|
78
|
+
export type MockBookEntityMetadata = InferEntityMetadata<typeof MockBookSchema>
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, mock } from 'bun:test'
|
|
2
|
+
import { MockBookSchema } from '../models/mock-book-models'
|
|
3
|
+
import { MockMemoryRepository } from './mock-memory-repository'
|
|
4
|
+
|
|
5
|
+
describe('MockMemoryRepository - Assign 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 use default Object.assign for create when no custom assign function is provided', async () => {
|
|
15
|
+
const input = { title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
16
|
+
const createdItem = await repository.create(input)
|
|
17
|
+
|
|
18
|
+
expect(createdItem).toMatchObject(input)
|
|
19
|
+
expect(createdItem.id).toBeDefined()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('should use default Object.assign for update when no custom assign function is provided', async () => {
|
|
23
|
+
const input = { id: 42, title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
24
|
+
const createdItem = await repository.create(input)
|
|
25
|
+
|
|
26
|
+
const updateInput = { title: 'Updated Book', author: 'Updated Author', publishedDate: new Date() }
|
|
27
|
+
const updatedItem = await repository.update({ id: createdItem.id }, updateInput)
|
|
28
|
+
|
|
29
|
+
expect(updatedItem).toEqual({
|
|
30
|
+
id: createdItem.id,
|
|
31
|
+
title: 'Updated Book',
|
|
32
|
+
author: 'Updated Author',
|
|
33
|
+
publishedDate: updateInput.publishedDate,
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('should use default Object.assign for upsert when no custom assign function is provided', async () => {
|
|
38
|
+
const input = { id: 42, title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
39
|
+
const upsertedItem = await repository.upsert(input)
|
|
40
|
+
|
|
41
|
+
expect(upsertedItem).toEqual(input)
|
|
42
|
+
|
|
43
|
+
const updateInput = { id: 42, title: 'Updated Book', author: 'Updated Author', publishedDate: new Date() }
|
|
44
|
+
const updatedItem = await repository.upsert(updateInput)
|
|
45
|
+
|
|
46
|
+
expect(updatedItem).toEqual(updateInput)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('should use custom assign function for create operation', async () => {
|
|
50
|
+
const customAssignMock = mock((existing: any, input: any) => ({
|
|
51
|
+
...existing,
|
|
52
|
+
...input,
|
|
53
|
+
customField: 'custom_create_value',
|
|
54
|
+
}))
|
|
55
|
+
|
|
56
|
+
const customRepository = new MockMemoryRepository({
|
|
57
|
+
schema: mockSchema,
|
|
58
|
+
assign: customAssignMock,
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
const input = { title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
62
|
+
const createdItem = await customRepository.create(input)
|
|
63
|
+
|
|
64
|
+
expect(customAssignMock).toHaveBeenCalledWith({}, input)
|
|
65
|
+
expect((createdItem as any).customField).toBe('custom_create_value')
|
|
66
|
+
expect(createdItem).toMatchObject(input)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('should use custom assign function for update operation', async () => {
|
|
70
|
+
const customAssignMock = mock((existing: any, input: any) => ({
|
|
71
|
+
...existing,
|
|
72
|
+
...input,
|
|
73
|
+
customField: 'custom_update_value',
|
|
74
|
+
lastModified: new Date('2023-01-01'),
|
|
75
|
+
}))
|
|
76
|
+
|
|
77
|
+
const customRepository = new MockMemoryRepository({
|
|
78
|
+
schema: mockSchema,
|
|
79
|
+
assign: customAssignMock,
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
const input = { id: 42, title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
83
|
+
const createdItem = await customRepository.create(input)
|
|
84
|
+
|
|
85
|
+
const updateInput = { title: 'Updated Book', author: 'Updated Author', publishedDate: new Date() }
|
|
86
|
+
const updatedItem = await customRepository.update({ id: createdItem.id }, updateInput)
|
|
87
|
+
|
|
88
|
+
expect(customAssignMock).toHaveBeenCalledWith(createdItem, updateInput)
|
|
89
|
+
expect((updatedItem as any).customField).toBe('custom_update_value')
|
|
90
|
+
expect((updatedItem as any).lastModified).toEqual(new Date('2023-01-01'))
|
|
91
|
+
expect(updatedItem.title).toBe('Updated Book')
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('should use custom assign function for upsert operation on existing item', async () => {
|
|
95
|
+
const customAssignMock = mock((existing: any, input: any) => ({
|
|
96
|
+
...existing,
|
|
97
|
+
...input,
|
|
98
|
+
mergeTimestamp: new Date('2023-01-01'),
|
|
99
|
+
isUpserted: true,
|
|
100
|
+
}))
|
|
101
|
+
|
|
102
|
+
const customRepository = new MockMemoryRepository({
|
|
103
|
+
schema: mockSchema,
|
|
104
|
+
assign: customAssignMock,
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
const input = { id: 42, title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
108
|
+
const createdItem = await customRepository.create(input)
|
|
109
|
+
|
|
110
|
+
const upsertInput = { id: 42, title: 'Upserted Book', author: 'Upserted Author', publishedDate: new Date() }
|
|
111
|
+
const upsertedItem = await customRepository.upsert(upsertInput)
|
|
112
|
+
|
|
113
|
+
// Should have been called twice: once for create, once for upsert
|
|
114
|
+
expect(customAssignMock).toHaveBeenCalledTimes(2)
|
|
115
|
+
expect(customAssignMock).toHaveBeenLastCalledWith(createdItem, upsertInput)
|
|
116
|
+
expect((upsertedItem as any).mergeTimestamp).toEqual(new Date('2023-01-01'))
|
|
117
|
+
expect((upsertedItem as any).isUpserted).toBe(true)
|
|
118
|
+
expect(upsertedItem.title).toBe('Upserted Book')
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('should use custom assign function for upsert operation on new item', async () => {
|
|
122
|
+
const customAssignMock = mock((existing: any, input: any) => ({
|
|
123
|
+
...existing,
|
|
124
|
+
...input,
|
|
125
|
+
createdViaUpsert: true,
|
|
126
|
+
}))
|
|
127
|
+
|
|
128
|
+
const customRepository = new MockMemoryRepository({
|
|
129
|
+
schema: mockSchema,
|
|
130
|
+
assign: customAssignMock,
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
const input = { id: 42, title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
134
|
+
const upsertedItem = await customRepository.upsert(input)
|
|
135
|
+
|
|
136
|
+
expect(customAssignMock).toHaveBeenCalledWith({}, input)
|
|
137
|
+
expect((upsertedItem as any).createdViaUpsert).toBe(true)
|
|
138
|
+
expect(upsertedItem).toMatchObject(input)
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
it('should use custom assign function for bulkUpsert operation', async () => {
|
|
142
|
+
const customAssignMock = mock((existing: any, input: any) => ({
|
|
143
|
+
...existing,
|
|
144
|
+
...input,
|
|
145
|
+
bulkProcessed: true,
|
|
146
|
+
}))
|
|
147
|
+
|
|
148
|
+
const customRepository = new MockMemoryRepository({
|
|
149
|
+
schema: mockSchema,
|
|
150
|
+
assign: customAssignMock,
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
const inputs = [
|
|
154
|
+
{ id: 1, title: 'Book 1', author: 'Author 1', publishedDate: new Date() },
|
|
155
|
+
{ id: 2, title: 'Book 2', author: 'Author 2', publishedDate: new Date() },
|
|
156
|
+
]
|
|
157
|
+
|
|
158
|
+
const results = await customRepository.bulkUpsert(inputs)
|
|
159
|
+
|
|
160
|
+
expect(customAssignMock).toHaveBeenCalledTimes(2)
|
|
161
|
+
expect((results[0] as any).bulkProcessed).toBe(true)
|
|
162
|
+
expect((results[1] as any).bulkProcessed).toBe(true)
|
|
163
|
+
expect(results[0]).toMatchObject(inputs[0])
|
|
164
|
+
expect(results[1]).toMatchObject(inputs[1])
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
it('should handle complex custom assign logic with conditional merging', async () => {
|
|
168
|
+
const customAssign = (existing: any, input: any) => {
|
|
169
|
+
const result = { ...existing, ...input }
|
|
170
|
+
|
|
171
|
+
// Custom logic: preserve original author if input doesn't have one
|
|
172
|
+
if (!input.author && existing.author) {
|
|
173
|
+
result.author = existing.author
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Custom logic: track modification count
|
|
177
|
+
result.modificationCount = (existing.modificationCount || 0) + 1
|
|
178
|
+
|
|
179
|
+
return result
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const customRepository = new MockMemoryRepository({
|
|
183
|
+
schema: mockSchema,
|
|
184
|
+
assign: customAssign,
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
const input = { id: 42, title: 'Test Book', author: 'Original Author', publishedDate: new Date() }
|
|
188
|
+
const createdItem = await customRepository.create(input)
|
|
189
|
+
|
|
190
|
+
expect((createdItem as any).modificationCount).toBe(1)
|
|
191
|
+
|
|
192
|
+
// Update with partial data - use existing values for required fields
|
|
193
|
+
const updateInput = {
|
|
194
|
+
title: 'Updated Book',
|
|
195
|
+
author: createdItem.author,
|
|
196
|
+
publishedDate: createdItem.publishedDate,
|
|
197
|
+
}
|
|
198
|
+
const updatedItem = await customRepository.update({ id: createdItem.id }, updateInput)
|
|
199
|
+
|
|
200
|
+
expect(updatedItem.author).toBe('Original Author')
|
|
201
|
+
expect(updatedItem.title).toBe('Updated Book')
|
|
202
|
+
expect((updatedItem as any).modificationCount).toBe(2)
|
|
203
|
+
|
|
204
|
+
// Update with new author
|
|
205
|
+
const updateWithAuthor = {
|
|
206
|
+
title: updatedItem.title,
|
|
207
|
+
author: 'New Author',
|
|
208
|
+
publishedDate: updatedItem.publishedDate,
|
|
209
|
+
}
|
|
210
|
+
const finalItem = await customRepository.update({ id: createdItem.id }, updateWithAuthor)
|
|
211
|
+
|
|
212
|
+
expect(finalItem.author).toBe('New Author')
|
|
213
|
+
expect((finalItem as any).modificationCount).toBe(3)
|
|
214
|
+
})
|
|
215
|
+
})
|
|
@@ -0,0 +1,129 @@
|
|
|
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
|
+
import { z } from 'zod/v4'
|
|
5
|
+
import { ZodModel } from '@declaro/zod'
|
|
6
|
+
|
|
7
|
+
describe('MockMemoryRepository - Basic Operations', () => {
|
|
8
|
+
const mockSchema = MockBookSchema
|
|
9
|
+
|
|
10
|
+
let repository: MockMemoryRepository<typeof mockSchema>
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
repository = new MockMemoryRepository({ schema: mockSchema })
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('should create an item', async () => {
|
|
17
|
+
const input = { id: 42, title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
18
|
+
const createdItem = await repository.create(input)
|
|
19
|
+
|
|
20
|
+
expect(createdItem).toEqual(input)
|
|
21
|
+
expect(await repository.load({ id: createdItem.id })).toEqual(createdItem)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('should throw an error when creating an item with duplicate primary key', async () => {
|
|
25
|
+
const input = { id: 42, title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
26
|
+
const createdItem = await repository.create(input)
|
|
27
|
+
|
|
28
|
+
await expect(repository.create({ ...input, id: createdItem.id })).rejects.toThrow(
|
|
29
|
+
'Item with the same primary key already exists',
|
|
30
|
+
)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('should update an existing item', async () => {
|
|
34
|
+
const input = { id: 42, title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
35
|
+
const createdItem = await repository.create(input)
|
|
36
|
+
|
|
37
|
+
const updatedInput = { title: 'Updated Book', author: 'Updated Author', publishedDate: new Date() }
|
|
38
|
+
const updatedItem = await repository.update({ id: createdItem.id }, updatedInput)
|
|
39
|
+
|
|
40
|
+
expect(updatedItem).toEqual({ id: createdItem.id, ...updatedInput })
|
|
41
|
+
expect(await repository.load({ id: createdItem.id })).toEqual({ id: createdItem.id, ...updatedInput })
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('should throw an error when updating a non-existent item', async () => {
|
|
45
|
+
const input = { id: 42, title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
46
|
+
|
|
47
|
+
await expect(repository.update({ id: 999 }, input)).rejects.toThrow('Item not found')
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('should remove an item', async () => {
|
|
51
|
+
const input = { id: 42, title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
52
|
+
const createdItem = await repository.create(input)
|
|
53
|
+
|
|
54
|
+
const removedItem = await repository.remove({ id: createdItem.id })
|
|
55
|
+
|
|
56
|
+
expect(removedItem).toEqual(input)
|
|
57
|
+
const loadedItem = await repository.load({ id: createdItem.id })
|
|
58
|
+
expect(loadedItem).toBeNull()
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('should restore a removed item', async () => {
|
|
62
|
+
const input = { id: 42, title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
63
|
+
const createdItem = await repository.create(input)
|
|
64
|
+
await repository.remove({ id: createdItem.id })
|
|
65
|
+
|
|
66
|
+
const restoredItem = await repository.restore({ id: createdItem.id })
|
|
67
|
+
|
|
68
|
+
expect(restoredItem).toEqual(input)
|
|
69
|
+
expect(await repository.load({ id: createdItem.id })).toEqual(input)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('should throw an error when restoring a non-existent item', async () => {
|
|
73
|
+
await expect(repository.restore({ id: 999 })).rejects.toThrow('Item not found in trash')
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('should allow me to create an item without a primary key', async () => {
|
|
77
|
+
const input = { title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
78
|
+
const createdItem = await repository.create(input)
|
|
79
|
+
|
|
80
|
+
expect(createdItem.id).toBeDefined()
|
|
81
|
+
expect(createdItem.title).toBe(input.title)
|
|
82
|
+
expect(createdItem.author).toBe(input.author)
|
|
83
|
+
expect(await repository.load({ id: createdItem.id })).toEqual(createdItem)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('should increment ids when creating items without a primary key', async () => {
|
|
87
|
+
const input1 = { title: 'Test Book 1', author: 'Author Name 1', publishedDate: new Date() }
|
|
88
|
+
const input2 = { title: 'Test Book 2', author: 'Author Name 2', publishedDate: new Date() }
|
|
89
|
+
|
|
90
|
+
const createdItem1 = await repository.create(input1)
|
|
91
|
+
const createdItem2 = await repository.create(input2)
|
|
92
|
+
|
|
93
|
+
expect(createdItem1.id).toBe(1)
|
|
94
|
+
expect(createdItem2.id).toBe(2)
|
|
95
|
+
expect(await repository.load({ id: createdItem1.id })).toEqual(createdItem1)
|
|
96
|
+
expect(await repository.load({ id: createdItem2.id })).toEqual(createdItem2)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('should return null when loading a non-existent item', async () => {
|
|
100
|
+
const result = await repository.load({ id: 999 })
|
|
101
|
+
expect(result).toBeNull()
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it('should be able to load items from a custom filter', async () => {
|
|
105
|
+
// Creating a hypothetical schema with a custom filter, and a title lookup attribute
|
|
106
|
+
const repository = new MockMemoryRepository({
|
|
107
|
+
schema: mockSchema.custom({
|
|
108
|
+
lookup: (h) =>
|
|
109
|
+
new ZodModel(
|
|
110
|
+
h.name,
|
|
111
|
+
z.object({
|
|
112
|
+
id: z.number().optional(),
|
|
113
|
+
title: z.string().optional(),
|
|
114
|
+
}),
|
|
115
|
+
),
|
|
116
|
+
}),
|
|
117
|
+
lookup: (data, lookup) =>
|
|
118
|
+
data.id === lookup.id || data.title?.toLowerCase() === lookup.title?.toLowerCase(),
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
const input = { title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
122
|
+
const createdItem = await repository.create(input)
|
|
123
|
+
|
|
124
|
+
const loadedItem = await repository.load({ id: createdItem.id })
|
|
125
|
+
const titleLoadedItem = await repository.load({ title: createdItem.title })
|
|
126
|
+
expect(loadedItem).toEqual(createdItem)
|
|
127
|
+
expect(titleLoadedItem).toEqual(createdItem)
|
|
128
|
+
})
|
|
129
|
+
})
|