@fragno-dev/db 0.1.13 → 0.1.15
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/.turbo/turbo-build.log +179 -132
- package/CHANGELOG.md +30 -0
- package/dist/adapters/adapters.d.ts +27 -1
- package/dist/adapters/adapters.d.ts.map +1 -1
- package/dist/adapters/adapters.js.map +1 -1
- package/dist/adapters/drizzle/drizzle-adapter.d.ts +5 -1
- package/dist/adapters/drizzle/drizzle-adapter.d.ts.map +1 -1
- package/dist/adapters/drizzle/drizzle-adapter.js +15 -3
- package/dist/adapters/drizzle/drizzle-adapter.js.map +1 -1
- package/dist/adapters/drizzle/drizzle-query.js +7 -5
- package/dist/adapters/drizzle/drizzle-query.js.map +1 -1
- package/dist/adapters/drizzle/drizzle-uow-compiler.d.ts +0 -1
- package/dist/adapters/drizzle/drizzle-uow-compiler.d.ts.map +1 -1
- package/dist/adapters/drizzle/drizzle-uow-compiler.js +76 -44
- package/dist/adapters/drizzle/drizzle-uow-compiler.js.map +1 -1
- package/dist/adapters/drizzle/drizzle-uow-decoder.js +23 -16
- package/dist/adapters/drizzle/drizzle-uow-decoder.js.map +1 -1
- package/dist/adapters/drizzle/drizzle-uow-executor.js +18 -7
- package/dist/adapters/drizzle/drizzle-uow-executor.js.map +1 -1
- package/dist/adapters/drizzle/generate.d.ts +4 -1
- package/dist/adapters/drizzle/generate.d.ts.map +1 -1
- package/dist/adapters/drizzle/generate.js +11 -18
- package/dist/adapters/drizzle/generate.js.map +1 -1
- package/dist/adapters/drizzle/shared.d.ts +14 -1
- package/dist/adapters/drizzle/shared.d.ts.map +1 -0
- package/dist/adapters/kysely/kysely-adapter.d.ts +5 -1
- package/dist/adapters/kysely/kysely-adapter.d.ts.map +1 -1
- package/dist/adapters/kysely/kysely-adapter.js +14 -3
- package/dist/adapters/kysely/kysely-adapter.js.map +1 -1
- package/dist/adapters/kysely/kysely-query-builder.js +1 -1
- package/dist/adapters/kysely/kysely-query-compiler.js +3 -2
- package/dist/adapters/kysely/kysely-query-compiler.js.map +1 -1
- package/dist/adapters/kysely/kysely-query.d.ts +1 -0
- package/dist/adapters/kysely/kysely-query.d.ts.map +1 -1
- package/dist/adapters/kysely/kysely-query.js +28 -19
- package/dist/adapters/kysely/kysely-query.js.map +1 -1
- package/dist/adapters/kysely/kysely-shared.d.ts +14 -0
- package/dist/adapters/kysely/kysely-shared.d.ts.map +1 -0
- package/dist/adapters/kysely/kysely-shared.js +16 -1
- package/dist/adapters/kysely/kysely-shared.js.map +1 -1
- package/dist/adapters/kysely/kysely-uow-compiler.js +68 -16
- package/dist/adapters/kysely/kysely-uow-compiler.js.map +1 -1
- package/dist/adapters/kysely/kysely-uow-executor.js +8 -4
- package/dist/adapters/kysely/kysely-uow-executor.js.map +1 -1
- package/dist/adapters/kysely/migration/execute-base.js +1 -1
- package/dist/adapters/kysely/migration/execute-base.js.map +1 -1
- package/dist/db-fragment-definition-builder.d.ts +152 -0
- package/dist/db-fragment-definition-builder.d.ts.map +1 -0
- package/dist/db-fragment-definition-builder.js +137 -0
- package/dist/db-fragment-definition-builder.js.map +1 -0
- package/dist/fragments/internal-fragment.d.ts +19 -0
- package/dist/fragments/internal-fragment.d.ts.map +1 -0
- package/dist/fragments/internal-fragment.js +39 -0
- package/dist/fragments/internal-fragment.js.map +1 -0
- package/dist/migration-engine/generation-engine.d.ts.map +1 -1
- package/dist/migration-engine/generation-engine.js +35 -15
- package/dist/migration-engine/generation-engine.js.map +1 -1
- package/dist/mod.d.ts +8 -18
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +7 -34
- package/dist/mod.js.map +1 -1
- package/dist/node_modules/.pnpm/rou3@0.7.8/node_modules/rou3/dist/index.js +165 -0
- package/dist/node_modules/.pnpm/rou3@0.7.8/node_modules/rou3/dist/index.js.map +1 -0
- package/dist/packages/fragno/dist/api/bind-services.js +20 -0
- package/dist/packages/fragno/dist/api/bind-services.js.map +1 -0
- package/dist/packages/fragno/dist/api/error.js +48 -0
- package/dist/packages/fragno/dist/api/error.js.map +1 -0
- package/dist/packages/fragno/dist/api/fragment-definition-builder.js +320 -0
- package/dist/packages/fragno/dist/api/fragment-definition-builder.js.map +1 -0
- package/dist/packages/fragno/dist/api/fragment-instantiator.js +487 -0
- package/dist/packages/fragno/dist/api/fragment-instantiator.js.map +1 -0
- package/dist/packages/fragno/dist/api/fragno-response.js +73 -0
- package/dist/packages/fragno/dist/api/fragno-response.js.map +1 -0
- package/dist/packages/fragno/dist/api/internal/response-stream.js +81 -0
- package/dist/packages/fragno/dist/api/internal/response-stream.js.map +1 -0
- package/dist/packages/fragno/dist/api/internal/route.js +10 -0
- package/dist/packages/fragno/dist/api/internal/route.js.map +1 -0
- package/dist/packages/fragno/dist/api/mutable-request-state.js +97 -0
- package/dist/packages/fragno/dist/api/mutable-request-state.js.map +1 -0
- package/dist/packages/fragno/dist/api/request-context-storage.js +43 -0
- package/dist/packages/fragno/dist/api/request-context-storage.js.map +1 -0
- package/dist/packages/fragno/dist/api/request-input-context.js +118 -0
- package/dist/packages/fragno/dist/api/request-input-context.js.map +1 -0
- package/dist/packages/fragno/dist/api/request-middleware.js +83 -0
- package/dist/packages/fragno/dist/api/request-middleware.js.map +1 -0
- package/dist/packages/fragno/dist/api/request-output-context.js +119 -0
- package/dist/packages/fragno/dist/api/request-output-context.js.map +1 -0
- package/dist/packages/fragno/dist/api/route.js +17 -0
- package/dist/packages/fragno/dist/api/route.js.map +1 -0
- package/dist/packages/fragno/dist/internal/symbols.js +10 -0
- package/dist/packages/fragno/dist/internal/symbols.js.map +1 -0
- package/dist/query/cursor.d.ts +10 -2
- package/dist/query/cursor.d.ts.map +1 -1
- package/dist/query/cursor.js +11 -4
- package/dist/query/cursor.js.map +1 -1
- package/dist/query/execute-unit-of-work.d.ts +123 -0
- package/dist/query/execute-unit-of-work.d.ts.map +1 -0
- package/dist/query/execute-unit-of-work.js +184 -0
- package/dist/query/execute-unit-of-work.js.map +1 -0
- package/dist/query/query.d.ts +3 -3
- package/dist/query/query.d.ts.map +1 -1
- package/dist/query/result-transform.js +4 -2
- package/dist/query/result-transform.js.map +1 -1
- package/dist/query/retry-policy.d.ts +88 -0
- package/dist/query/retry-policy.d.ts.map +1 -0
- package/dist/query/retry-policy.js +61 -0
- package/dist/query/retry-policy.js.map +1 -0
- package/dist/query/unit-of-work.d.ts +171 -32
- package/dist/query/unit-of-work.d.ts.map +1 -1
- package/dist/query/unit-of-work.js +530 -133
- package/dist/query/unit-of-work.js.map +1 -1
- package/dist/schema/serialize.js +12 -7
- package/dist/schema/serialize.js.map +1 -1
- package/dist/with-database.d.ts +28 -0
- package/dist/with-database.d.ts.map +1 -0
- package/dist/with-database.js +34 -0
- package/dist/with-database.js.map +1 -0
- package/package.json +10 -3
- package/src/adapters/adapters.ts +30 -0
- package/src/adapters/drizzle/drizzle-adapter-pglite.test.ts +86 -17
- package/src/adapters/drizzle/drizzle-adapter-sqlite.test.ts +291 -7
- package/src/adapters/drizzle/drizzle-adapter.test.ts +3 -51
- package/src/adapters/drizzle/drizzle-adapter.ts +35 -7
- package/src/adapters/drizzle/drizzle-query.ts +25 -15
- package/src/adapters/drizzle/drizzle-uow-compiler-mysql.test.ts +1442 -0
- package/src/adapters/drizzle/drizzle-uow-compiler-sqlite.test.ts +1414 -0
- package/src/adapters/drizzle/drizzle-uow-compiler.test.ts +78 -61
- package/src/adapters/drizzle/drizzle-uow-compiler.ts +123 -42
- package/src/adapters/drizzle/drizzle-uow-decoder.ts +34 -27
- package/src/adapters/drizzle/drizzle-uow-executor.ts +41 -8
- package/src/adapters/drizzle/generate.test.ts +102 -269
- package/src/adapters/drizzle/generate.ts +12 -30
- package/src/adapters/drizzle/test-utils.ts +36 -5
- package/src/adapters/kysely/kysely-adapter-pglite.test.ts +66 -22
- package/src/adapters/kysely/kysely-adapter-sqlite.test.ts +156 -0
- package/src/adapters/kysely/kysely-adapter.ts +25 -2
- package/src/adapters/kysely/kysely-query-compiler.ts +3 -8
- package/src/adapters/kysely/kysely-query.ts +57 -37
- package/src/adapters/kysely/kysely-shared.ts +34 -0
- package/src/adapters/kysely/kysely-uow-compiler.test.ts +62 -74
- package/src/adapters/kysely/kysely-uow-compiler.ts +92 -24
- package/src/adapters/kysely/kysely-uow-executor.ts +26 -7
- package/src/adapters/kysely/kysely-uow-joins.test.ts +33 -50
- package/src/adapters/kysely/migration/execute-base.ts +1 -1
- package/src/db-fragment-definition-builder.test.ts +887 -0
- package/src/db-fragment-definition-builder.ts +506 -0
- package/src/db-fragment-instantiator.test.ts +467 -0
- package/src/db-fragment-integration.test.ts +408 -0
- package/src/fragments/internal-fragment.test.ts +160 -0
- package/src/fragments/internal-fragment.ts +85 -0
- package/src/migration-engine/generation-engine.test.ts +58 -15
- package/src/migration-engine/generation-engine.ts +78 -25
- package/src/mod.ts +35 -43
- package/src/query/cursor.test.ts +119 -0
- package/src/query/cursor.ts +17 -4
- package/src/query/execute-unit-of-work.test.ts +1310 -0
- package/src/query/execute-unit-of-work.ts +463 -0
- package/src/query/query.ts +4 -4
- package/src/query/result-transform.test.ts +129 -0
- package/src/query/result-transform.ts +4 -1
- package/src/query/retry-policy.test.ts +217 -0
- package/src/query/retry-policy.ts +141 -0
- package/src/query/unit-of-work-coordinator.test.ts +833 -0
- package/src/query/unit-of-work-types.test.ts +15 -2
- package/src/query/unit-of-work.test.ts +878 -200
- package/src/query/unit-of-work.ts +963 -321
- package/src/schema/serialize.ts +22 -11
- package/src/with-database.ts +140 -0
- package/tsdown.config.ts +1 -0
- package/dist/fragment.d.ts +0 -54
- package/dist/fragment.d.ts.map +0 -1
- package/dist/fragment.js +0 -92
- package/dist/fragment.js.map +0 -1
- package/dist/shared/settings-schema.js +0 -36
- package/dist/shared/settings-schema.js.map +0 -1
- package/src/fragment.test.ts +0 -341
- package/src/fragment.ts +0 -198
- package/src/shared/settings-schema.ts +0 -61
|
@@ -0,0 +1,887 @@
|
|
|
1
|
+
import { describe, it, expect, vi, expectTypeOf } from "vitest";
|
|
2
|
+
import { defineFragment } from "@fragno-dev/core";
|
|
3
|
+
import {
|
|
4
|
+
DatabaseFragmentDefinitionBuilder,
|
|
5
|
+
type ImplicitDatabaseDependencies,
|
|
6
|
+
} from "./db-fragment-definition-builder";
|
|
7
|
+
import { withDatabase } from "./with-database";
|
|
8
|
+
import { schema, column, idColumn } from "./schema/create";
|
|
9
|
+
import type { AbstractQuery } from "./query/query";
|
|
10
|
+
import type { DatabaseAdapter } from "./adapters/adapters";
|
|
11
|
+
|
|
12
|
+
// Create a test schema
|
|
13
|
+
const testSchema = schema((s) => {
|
|
14
|
+
return s.addTable("users", (t) => {
|
|
15
|
+
return t
|
|
16
|
+
.addColumn("id", idColumn())
|
|
17
|
+
.addColumn("name", column("string"))
|
|
18
|
+
.addColumn("email", column("string"));
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
type TestSchema = typeof testSchema;
|
|
23
|
+
|
|
24
|
+
// Mock database adapter
|
|
25
|
+
function createMockAdapter(): DatabaseAdapter {
|
|
26
|
+
const mockdb = {
|
|
27
|
+
createUnitOfWork: vi.fn(() => ({
|
|
28
|
+
forSchema: vi.fn(),
|
|
29
|
+
executeRetrieve: vi.fn(),
|
|
30
|
+
executeMutations: vi.fn(),
|
|
31
|
+
})),
|
|
32
|
+
} as unknown as AbstractQuery<TestSchema>;
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
createQueryEngine: vi.fn(() => mockdb),
|
|
36
|
+
migrate: vi.fn(),
|
|
37
|
+
close: vi.fn(),
|
|
38
|
+
} as unknown as DatabaseAdapter;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
describe("DatabaseFragmentDefinitionBuilder", () => {
|
|
42
|
+
describe("withDatabase helper", () => {
|
|
43
|
+
it("should create a database fragment builder", () => {
|
|
44
|
+
const baseBuilder = defineFragment("test-db-fragment");
|
|
45
|
+
const dbBuilder = withDatabase(testSchema)(baseBuilder);
|
|
46
|
+
|
|
47
|
+
expect(dbBuilder).toBeInstanceOf(DatabaseFragmentDefinitionBuilder);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("should work with .extend() API", () => {
|
|
51
|
+
const dbBuilder = defineFragment("test-db-fragment").extend(withDatabase(testSchema));
|
|
52
|
+
|
|
53
|
+
expect(dbBuilder).toBeInstanceOf(DatabaseFragmentDefinitionBuilder);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("should build with database-specific features", () => {
|
|
57
|
+
const _definition = defineFragment("test-db-fragment")
|
|
58
|
+
.withDependencies(() => ({ apiKey: "key" }))
|
|
59
|
+
.build();
|
|
60
|
+
|
|
61
|
+
// Convert to database builder
|
|
62
|
+
const dbDefinition = withDatabase(testSchema)(defineFragment("test-db-fragment"))
|
|
63
|
+
.withDependencies(() => ({
|
|
64
|
+
apiKey: "key",
|
|
65
|
+
customDep: "value",
|
|
66
|
+
}))
|
|
67
|
+
.build();
|
|
68
|
+
|
|
69
|
+
expect(dbDefinition.dependencies).toBeDefined();
|
|
70
|
+
expect(dbDefinition.createThisContext).toBeDefined();
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe("extend() API ordering", () => {
|
|
75
|
+
it("should extend before withDependencies", () => {
|
|
76
|
+
const mockAdapter = createMockAdapter();
|
|
77
|
+
|
|
78
|
+
const definition = defineFragment("test-frag")
|
|
79
|
+
.extend(withDatabase(testSchema))
|
|
80
|
+
.withDependencies(({ db }) => {
|
|
81
|
+
expect(db).toBeDefined();
|
|
82
|
+
return {
|
|
83
|
+
customDep: "value",
|
|
84
|
+
};
|
|
85
|
+
})
|
|
86
|
+
.build();
|
|
87
|
+
|
|
88
|
+
const deps = definition.dependencies!({
|
|
89
|
+
config: {},
|
|
90
|
+
options: { databaseAdapter: mockAdapter },
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
expect(deps.customDep).toBe("value");
|
|
94
|
+
expect(deps.db).toBeDefined();
|
|
95
|
+
expect(deps.schema).toBeDefined();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("should extend after withDependencies", () => {
|
|
99
|
+
const mockAdapter = createMockAdapter();
|
|
100
|
+
|
|
101
|
+
const definition = defineFragment("test-frag")
|
|
102
|
+
.withDependencies(() => ({
|
|
103
|
+
apiKey: "key123",
|
|
104
|
+
}))
|
|
105
|
+
.extend(withDatabase(testSchema))
|
|
106
|
+
.build();
|
|
107
|
+
|
|
108
|
+
const deps = definition.dependencies!({
|
|
109
|
+
config: {},
|
|
110
|
+
options: { databaseAdapter: mockAdapter },
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
expect(deps.apiKey).toBe("key123");
|
|
114
|
+
expect(deps.db).toBeDefined();
|
|
115
|
+
expect(deps.schema).toBeDefined();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("should extend before providesService", () => {
|
|
119
|
+
const mockAdapter = createMockAdapter();
|
|
120
|
+
|
|
121
|
+
const definition = defineFragment("test-frag")
|
|
122
|
+
.extend(withDatabase(testSchema))
|
|
123
|
+
.providesService("users", ({ deps }) => {
|
|
124
|
+
expect(deps.db).toBeDefined();
|
|
125
|
+
return {
|
|
126
|
+
list: () => "users list",
|
|
127
|
+
};
|
|
128
|
+
})
|
|
129
|
+
.build();
|
|
130
|
+
|
|
131
|
+
const deps = definition.dependencies!({
|
|
132
|
+
config: {},
|
|
133
|
+
options: { databaseAdapter: mockAdapter },
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const userService = definition.namedServices!.users({
|
|
137
|
+
config: {},
|
|
138
|
+
options: { databaseAdapter: mockAdapter },
|
|
139
|
+
deps,
|
|
140
|
+
serviceDeps: {},
|
|
141
|
+
privateServices: {},
|
|
142
|
+
defineService: (svc) => svc,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
expect(userService.list()).toBe("users list");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("should extend after providesService", () => {
|
|
149
|
+
const mockAdapter = createMockAdapter();
|
|
150
|
+
|
|
151
|
+
const definition = defineFragment("test-frag")
|
|
152
|
+
.providesService("basic", () => ({
|
|
153
|
+
ping: () => "pong",
|
|
154
|
+
}))
|
|
155
|
+
.extend(withDatabase(testSchema))
|
|
156
|
+
.providesService("users", ({ deps }) => {
|
|
157
|
+
expect(deps.db).toBeDefined();
|
|
158
|
+
return {
|
|
159
|
+
list: () => "users list",
|
|
160
|
+
};
|
|
161
|
+
})
|
|
162
|
+
.build();
|
|
163
|
+
|
|
164
|
+
const deps = definition.dependencies!({
|
|
165
|
+
config: {},
|
|
166
|
+
options: { databaseAdapter: mockAdapter },
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const basicService = definition.namedServices!.basic({
|
|
170
|
+
config: {},
|
|
171
|
+
options: { databaseAdapter: mockAdapter },
|
|
172
|
+
deps,
|
|
173
|
+
serviceDeps: {},
|
|
174
|
+
privateServices: {},
|
|
175
|
+
defineService: (svc) => svc,
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const userService = definition.namedServices!.users({
|
|
179
|
+
config: {},
|
|
180
|
+
options: { databaseAdapter: mockAdapter },
|
|
181
|
+
deps,
|
|
182
|
+
serviceDeps: {},
|
|
183
|
+
privateServices: {},
|
|
184
|
+
defineService: (svc) => svc,
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
expect(basicService.ping()).toBe("pong");
|
|
188
|
+
expect(userService.list()).toBe("users list");
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("should extend before usesService", () => {
|
|
192
|
+
interface EmailService {
|
|
193
|
+
send: (to: string) => void;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const definition = defineFragment("test-frag")
|
|
197
|
+
.extend(withDatabase(testSchema))
|
|
198
|
+
.usesService<"email", EmailService>("email")
|
|
199
|
+
.build();
|
|
200
|
+
|
|
201
|
+
expect(definition.serviceDependencies).toBeDefined();
|
|
202
|
+
expect(definition.serviceDependencies!.email).toEqual({
|
|
203
|
+
name: "email",
|
|
204
|
+
required: true,
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it("should extend after usesService", () => {
|
|
209
|
+
interface LogService {
|
|
210
|
+
log: (msg: string) => void;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const definition = defineFragment("test-frag")
|
|
214
|
+
.usesService<"logger", LogService>("logger")
|
|
215
|
+
.extend(withDatabase(testSchema))
|
|
216
|
+
.build();
|
|
217
|
+
|
|
218
|
+
expect(definition.serviceDependencies).toBeDefined();
|
|
219
|
+
expect(definition.serviceDependencies!.logger).toEqual({
|
|
220
|
+
name: "logger",
|
|
221
|
+
required: true,
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it("should extend before providesBaseService", () => {
|
|
226
|
+
const mockAdapter = createMockAdapter();
|
|
227
|
+
|
|
228
|
+
const definition = defineFragment("test-frag")
|
|
229
|
+
.extend(withDatabase(testSchema))
|
|
230
|
+
.providesBaseService(({ deps }) => {
|
|
231
|
+
expect(deps.db).toBeDefined();
|
|
232
|
+
return {
|
|
233
|
+
healthCheck: () => "healthy",
|
|
234
|
+
};
|
|
235
|
+
})
|
|
236
|
+
.build();
|
|
237
|
+
|
|
238
|
+
const deps = definition.dependencies!({
|
|
239
|
+
config: {},
|
|
240
|
+
options: { databaseAdapter: mockAdapter },
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
const services = definition.baseServices!({
|
|
244
|
+
config: {},
|
|
245
|
+
options: { databaseAdapter: mockAdapter },
|
|
246
|
+
deps,
|
|
247
|
+
serviceDeps: {},
|
|
248
|
+
privateServices: {},
|
|
249
|
+
defineService: (svc) => svc,
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
expect(services.healthCheck()).toBe("healthy");
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it("should extend after providesBaseService replaces previous base service", () => {
|
|
256
|
+
const mockAdapter = createMockAdapter();
|
|
257
|
+
|
|
258
|
+
const definition = defineFragment("test-frag")
|
|
259
|
+
.providesBaseService(() => ({
|
|
260
|
+
ping: () => "pong",
|
|
261
|
+
}))
|
|
262
|
+
.extend(withDatabase(testSchema))
|
|
263
|
+
.providesBaseService(({ deps }) => {
|
|
264
|
+
expect(deps.db).toBeDefined();
|
|
265
|
+
return {
|
|
266
|
+
status: () => "ok",
|
|
267
|
+
};
|
|
268
|
+
})
|
|
269
|
+
.build();
|
|
270
|
+
|
|
271
|
+
const deps = definition.dependencies!({
|
|
272
|
+
config: {},
|
|
273
|
+
options: { databaseAdapter: mockAdapter },
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
const services = definition.baseServices!({
|
|
277
|
+
config: {},
|
|
278
|
+
options: { databaseAdapter: mockAdapter },
|
|
279
|
+
deps,
|
|
280
|
+
serviceDeps: {},
|
|
281
|
+
privateServices: {},
|
|
282
|
+
defineService: (svc) => svc,
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// Second providesBaseService replaces the first one
|
|
286
|
+
expect(services.status()).toBe("ok");
|
|
287
|
+
expect(services).not.toHaveProperty("ping");
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it("should chain multiple operations after extend", () => {
|
|
291
|
+
const mockAdapter = createMockAdapter();
|
|
292
|
+
|
|
293
|
+
interface LogService {
|
|
294
|
+
log: (msg: string) => void;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const definition = defineFragment("test-frag")
|
|
298
|
+
.extend(withDatabase(testSchema))
|
|
299
|
+
.withDependencies(({ db }) => {
|
|
300
|
+
expect(db).toBeDefined();
|
|
301
|
+
return {
|
|
302
|
+
apiKey: "secret",
|
|
303
|
+
};
|
|
304
|
+
})
|
|
305
|
+
.usesOptionalService<"logger", LogService>("logger")
|
|
306
|
+
.providesBaseService(({ deps, serviceDeps }) => ({
|
|
307
|
+
init: () => {
|
|
308
|
+
if (serviceDeps.logger) {
|
|
309
|
+
serviceDeps.logger.log(`Initialized with key: ${deps.apiKey}`);
|
|
310
|
+
}
|
|
311
|
+
return "initialized";
|
|
312
|
+
},
|
|
313
|
+
}))
|
|
314
|
+
.providesService("data", ({ deps }) => {
|
|
315
|
+
expect(deps.db).toBeDefined();
|
|
316
|
+
return {
|
|
317
|
+
fetch: () => `Fetching with ${deps.apiKey}`,
|
|
318
|
+
};
|
|
319
|
+
})
|
|
320
|
+
.build();
|
|
321
|
+
|
|
322
|
+
const logs: string[] = [];
|
|
323
|
+
const deps = definition.dependencies!({
|
|
324
|
+
config: {},
|
|
325
|
+
options: { databaseAdapter: mockAdapter },
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
const services = definition.baseServices!({
|
|
329
|
+
config: {},
|
|
330
|
+
options: { databaseAdapter: mockAdapter },
|
|
331
|
+
deps,
|
|
332
|
+
serviceDeps: {
|
|
333
|
+
logger: {
|
|
334
|
+
log: (msg) => logs.push(msg),
|
|
335
|
+
},
|
|
336
|
+
},
|
|
337
|
+
privateServices: {},
|
|
338
|
+
defineService: (svc) => svc,
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
expect(services.init()).toBe("initialized");
|
|
342
|
+
expect(logs).toContain("Initialized with key: secret");
|
|
343
|
+
|
|
344
|
+
const dataService = definition.namedServices!.data({
|
|
345
|
+
config: {},
|
|
346
|
+
options: { databaseAdapter: mockAdapter },
|
|
347
|
+
deps,
|
|
348
|
+
serviceDeps: {
|
|
349
|
+
logger: {
|
|
350
|
+
log: (msg) => logs.push(msg),
|
|
351
|
+
},
|
|
352
|
+
},
|
|
353
|
+
privateServices: {},
|
|
354
|
+
defineService: (svc) => svc,
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
expect(dataService.fetch()).toBe("Fetching with secret");
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
it("should support complex ordering: deps -> extend -> deps replaces previous", () => {
|
|
361
|
+
const mockAdapter = createMockAdapter();
|
|
362
|
+
|
|
363
|
+
const definition = defineFragment("test-frag")
|
|
364
|
+
.withDependencies(() => ({
|
|
365
|
+
baseConfig: "config1",
|
|
366
|
+
}))
|
|
367
|
+
.extend(withDatabase(testSchema))
|
|
368
|
+
.withDependencies(({ db }) => {
|
|
369
|
+
expect(db).toBeDefined();
|
|
370
|
+
// Second withDependencies replaces the first one
|
|
371
|
+
return {
|
|
372
|
+
enhancedConfig: "enhanced",
|
|
373
|
+
};
|
|
374
|
+
})
|
|
375
|
+
.providesService("combined", ({ deps }) => {
|
|
376
|
+
expect(deps.db).toBeDefined();
|
|
377
|
+
return {
|
|
378
|
+
getConfig: () => deps.enhancedConfig,
|
|
379
|
+
};
|
|
380
|
+
})
|
|
381
|
+
.build();
|
|
382
|
+
|
|
383
|
+
const deps = definition.dependencies!({
|
|
384
|
+
config: {},
|
|
385
|
+
options: { databaseAdapter: mockAdapter },
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
// Only the second withDependencies is used (plus implicit deps)
|
|
389
|
+
expect(deps).not.toHaveProperty("baseConfig");
|
|
390
|
+
expect(deps.enhancedConfig).toBe("enhanced");
|
|
391
|
+
expect(deps.db).toBeDefined();
|
|
392
|
+
|
|
393
|
+
const combinedService = definition.namedServices!.combined({
|
|
394
|
+
config: {},
|
|
395
|
+
options: { databaseAdapter: mockAdapter },
|
|
396
|
+
deps,
|
|
397
|
+
serviceDeps: {},
|
|
398
|
+
privateServices: {},
|
|
399
|
+
defineService: (svc) => svc,
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
expect(combinedService.getConfig()).toBe("enhanced");
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
it("should preserve type safety with typed config across extend", () => {
|
|
406
|
+
interface MyConfig {
|
|
407
|
+
dbUrl: string;
|
|
408
|
+
timeout: number;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const mockAdapter = createMockAdapter();
|
|
412
|
+
|
|
413
|
+
const definition = defineFragment<MyConfig>("test-frag")
|
|
414
|
+
.withDependencies(({ config }) => {
|
|
415
|
+
expectTypeOf(config).toExtend<MyConfig>();
|
|
416
|
+
return {
|
|
417
|
+
connectionTimeout: config.timeout,
|
|
418
|
+
};
|
|
419
|
+
})
|
|
420
|
+
.extend(withDatabase(testSchema))
|
|
421
|
+
.withDependencies(({ config, db }) => {
|
|
422
|
+
expectTypeOf(config).toExtend<MyConfig>();
|
|
423
|
+
expect(db).toBeDefined();
|
|
424
|
+
// Second withDependencies replaces the first, so combine them here
|
|
425
|
+
return {
|
|
426
|
+
connectionTimeout: config.timeout,
|
|
427
|
+
dbConnectionString: config.dbUrl,
|
|
428
|
+
};
|
|
429
|
+
})
|
|
430
|
+
.build();
|
|
431
|
+
|
|
432
|
+
const deps = definition.dependencies!({
|
|
433
|
+
config: { dbUrl: "postgres://localhost", timeout: 5000 },
|
|
434
|
+
options: { databaseAdapter: mockAdapter },
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
expect(deps.connectionTimeout).toBe(5000);
|
|
438
|
+
expect(deps.dbConnectionString).toBe("postgres://localhost");
|
|
439
|
+
expect(deps.db).toBeDefined();
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
it("recommended pattern: extend first then configure", () => {
|
|
443
|
+
const mockAdapter = createMockAdapter();
|
|
444
|
+
|
|
445
|
+
interface LogService {
|
|
446
|
+
log: (msg: string) => void;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
interface AuthService {
|
|
450
|
+
checkAuth: (token: string) => boolean;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Best practice: .extend() early, then add all your features
|
|
454
|
+
const definition = defineFragment("best-practice")
|
|
455
|
+
.extend(withDatabase(testSchema))
|
|
456
|
+
.withDependencies(({ db }) => {
|
|
457
|
+
expect(db).toBeDefined();
|
|
458
|
+
return {
|
|
459
|
+
apiKey: "secret",
|
|
460
|
+
timeout: 5000,
|
|
461
|
+
};
|
|
462
|
+
})
|
|
463
|
+
.usesService<"logger", LogService>("logger")
|
|
464
|
+
.usesOptionalService<"auth", AuthService>("auth")
|
|
465
|
+
.providesBaseService(({ deps }) => ({
|
|
466
|
+
healthCheck: () => {
|
|
467
|
+
expect(deps.db).toBeDefined();
|
|
468
|
+
return `OK (timeout: ${deps.timeout})`;
|
|
469
|
+
},
|
|
470
|
+
}))
|
|
471
|
+
.providesService("users", ({ deps, serviceDeps }) => ({
|
|
472
|
+
list: () => {
|
|
473
|
+
if (serviceDeps.logger) {
|
|
474
|
+
serviceDeps.logger.log("Listing users");
|
|
475
|
+
}
|
|
476
|
+
return `Users (key: ${deps.apiKey})`;
|
|
477
|
+
},
|
|
478
|
+
}))
|
|
479
|
+
.build();
|
|
480
|
+
|
|
481
|
+
const logs: string[] = [];
|
|
482
|
+
const deps = definition.dependencies!({
|
|
483
|
+
config: {},
|
|
484
|
+
options: { databaseAdapter: mockAdapter },
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
// Verify all dependencies including implicit ones
|
|
488
|
+
expect(deps.apiKey).toBe("secret");
|
|
489
|
+
expect(deps.timeout).toBe(5000);
|
|
490
|
+
expect(deps.db).toBeDefined();
|
|
491
|
+
expect(deps.schema).toBeDefined();
|
|
492
|
+
expect(deps.createUnitOfWork).toBeDefined();
|
|
493
|
+
|
|
494
|
+
const baseServices = definition.baseServices!({
|
|
495
|
+
config: {},
|
|
496
|
+
options: { databaseAdapter: mockAdapter },
|
|
497
|
+
deps,
|
|
498
|
+
serviceDeps: {
|
|
499
|
+
logger: { log: (msg) => logs.push(msg) },
|
|
500
|
+
auth: undefined, // Optional service
|
|
501
|
+
},
|
|
502
|
+
privateServices: {},
|
|
503
|
+
defineService: (svc) => svc,
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
expect(baseServices.healthCheck()).toBe("OK (timeout: 5000)");
|
|
507
|
+
|
|
508
|
+
const userService = definition.namedServices!.users({
|
|
509
|
+
config: {},
|
|
510
|
+
options: { databaseAdapter: mockAdapter },
|
|
511
|
+
deps,
|
|
512
|
+
serviceDeps: {
|
|
513
|
+
logger: { log: (msg) => logs.push(msg) },
|
|
514
|
+
auth: undefined, // Optional service
|
|
515
|
+
},
|
|
516
|
+
privateServices: {},
|
|
517
|
+
defineService: (svc) => svc,
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
expect(userService.list()).toBe("Users (key: secret)");
|
|
521
|
+
expect(logs).toContain("Listing users");
|
|
522
|
+
});
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
describe("withDependencies", () => {
|
|
526
|
+
it("should inject database context into dependencies", () => {
|
|
527
|
+
const mockAdapter = createMockAdapter();
|
|
528
|
+
|
|
529
|
+
const definition = withDatabase(testSchema)(defineFragment("db-frag"))
|
|
530
|
+
.withDependencies(({ db, databaseAdapter }) => {
|
|
531
|
+
expect(db).toBeDefined();
|
|
532
|
+
expect(databaseAdapter).toBeDefined();
|
|
533
|
+
return {
|
|
534
|
+
customDep: "test",
|
|
535
|
+
};
|
|
536
|
+
})
|
|
537
|
+
.build();
|
|
538
|
+
|
|
539
|
+
expect(definition.dependencies).toBeDefined();
|
|
540
|
+
|
|
541
|
+
// Call dependencies with mock adapter
|
|
542
|
+
const deps = definition.dependencies!({
|
|
543
|
+
config: {},
|
|
544
|
+
options: { databaseAdapter: mockAdapter },
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
// Should have implicit deps
|
|
548
|
+
expect(deps).toHaveProperty("db");
|
|
549
|
+
expect(deps).toHaveProperty("schema");
|
|
550
|
+
expect(deps).toHaveProperty("createUnitOfWork");
|
|
551
|
+
expect(deps).toHaveProperty("customDep");
|
|
552
|
+
expect(deps.customDep).toBe("test");
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
it("should provide implicit database dependencies", () => {
|
|
556
|
+
const mockAdapter = createMockAdapter();
|
|
557
|
+
|
|
558
|
+
const definition = withDatabase(testSchema)(defineFragment("db-frag")).build();
|
|
559
|
+
|
|
560
|
+
// Even without explicit dependencies, should add implicit ones
|
|
561
|
+
expect(definition.dependencies).toBeDefined();
|
|
562
|
+
|
|
563
|
+
const deps = definition.dependencies!({
|
|
564
|
+
config: {},
|
|
565
|
+
options: { databaseAdapter: mockAdapter },
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
// Check implicit dependencies structure
|
|
569
|
+
expect(deps.db).toBeDefined();
|
|
570
|
+
expect(deps.schema).toBeDefined();
|
|
571
|
+
expect(typeof deps.createUnitOfWork).toBe("function");
|
|
572
|
+
});
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
describe("providesBaseService", () => {
|
|
576
|
+
it("should inject database context into base services", () => {
|
|
577
|
+
const mockAdapter = createMockAdapter();
|
|
578
|
+
|
|
579
|
+
const definition = withDatabase(testSchema)(defineFragment("db-frag"))
|
|
580
|
+
.withDependencies(() => ({ apiKey: "key" }))
|
|
581
|
+
.providesBaseService(({ deps, defineService }) => {
|
|
582
|
+
expect(deps.apiKey).toBe("key");
|
|
583
|
+
expect(deps.db).toBeDefined();
|
|
584
|
+
expect(defineService).toBeDefined();
|
|
585
|
+
|
|
586
|
+
return defineService({
|
|
587
|
+
getUsers: function () {
|
|
588
|
+
return "users";
|
|
589
|
+
},
|
|
590
|
+
});
|
|
591
|
+
})
|
|
592
|
+
.build();
|
|
593
|
+
|
|
594
|
+
expect(definition.baseServices).toBeDefined();
|
|
595
|
+
|
|
596
|
+
// Get implicit deps first
|
|
597
|
+
const deps = definition.dependencies!({
|
|
598
|
+
config: {},
|
|
599
|
+
options: { databaseAdapter: mockAdapter },
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
const services = definition.baseServices!({
|
|
603
|
+
config: {},
|
|
604
|
+
options: { databaseAdapter: mockAdapter },
|
|
605
|
+
deps,
|
|
606
|
+
serviceDeps: {},
|
|
607
|
+
privateServices: {},
|
|
608
|
+
defineService: (svc) => svc,
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
expect(services.getUsers()).toBe("users");
|
|
612
|
+
});
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
describe("providesService", () => {
|
|
616
|
+
it("should inject database context into named services", () => {
|
|
617
|
+
const mockAdapter = createMockAdapter();
|
|
618
|
+
|
|
619
|
+
const definition = withDatabase(testSchema)(defineFragment("db-frag"))
|
|
620
|
+
.withDependencies(() => ({ apiKey: "key" }))
|
|
621
|
+
.providesService("users", ({ deps }) => ({
|
|
622
|
+
list: () => `Listing users with ${deps.apiKey}`,
|
|
623
|
+
create: (name: string) => `Creating user ${name}`,
|
|
624
|
+
}))
|
|
625
|
+
.build();
|
|
626
|
+
|
|
627
|
+
expect(definition.namedServices).toBeDefined();
|
|
628
|
+
expect(definition.namedServices!.users).toBeDefined();
|
|
629
|
+
|
|
630
|
+
// Get implicit deps
|
|
631
|
+
const deps = definition.dependencies!({
|
|
632
|
+
config: {},
|
|
633
|
+
options: { databaseAdapter: mockAdapter },
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
const userService = definition.namedServices!.users({
|
|
637
|
+
config: {},
|
|
638
|
+
options: { databaseAdapter: mockAdapter },
|
|
639
|
+
deps,
|
|
640
|
+
serviceDeps: {},
|
|
641
|
+
privateServices: {},
|
|
642
|
+
defineService: (svc) => svc,
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
expect(userService.list()).toBe("Listing users with key");
|
|
646
|
+
expect(userService.create("John")).toBe("Creating user John");
|
|
647
|
+
});
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
describe("usesService", () => {
|
|
651
|
+
it("should delegate service dependencies to base builder", () => {
|
|
652
|
+
interface EmailService {
|
|
653
|
+
send: (to: string) => void;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
const definition = withDatabase(testSchema)(defineFragment("db-frag"))
|
|
657
|
+
.usesService<"email", EmailService>("email")
|
|
658
|
+
.build();
|
|
659
|
+
|
|
660
|
+
expect(definition.serviceDependencies).toBeDefined();
|
|
661
|
+
expect(definition.serviceDependencies!.email).toEqual({
|
|
662
|
+
name: "email",
|
|
663
|
+
required: true,
|
|
664
|
+
});
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
it("should support optional service dependencies", () => {
|
|
668
|
+
interface LogService {
|
|
669
|
+
log: (msg: string) => void;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
const definition = withDatabase(testSchema)(defineFragment("db-frag"))
|
|
673
|
+
.usesOptionalService<"logger", LogService>("logger")
|
|
674
|
+
.build();
|
|
675
|
+
|
|
676
|
+
expect(definition.serviceDependencies!.logger.required).toBe(false);
|
|
677
|
+
});
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
describe("createRequestStorage and createThisContext", () => {
|
|
681
|
+
it("should create request storage with UnitOfWork", () => {
|
|
682
|
+
const mockAdapter = createMockAdapter();
|
|
683
|
+
|
|
684
|
+
const definition = withDatabase(testSchema)(defineFragment("db-frag")).build();
|
|
685
|
+
|
|
686
|
+
expect(definition.createRequestStorage).toBeDefined();
|
|
687
|
+
expect(definition.createThisContext).toBeDefined();
|
|
688
|
+
|
|
689
|
+
// Create storage
|
|
690
|
+
const storage = definition.createRequestStorage!({
|
|
691
|
+
config: {},
|
|
692
|
+
options: { databaseAdapter: mockAdapter },
|
|
693
|
+
deps: {} as any, // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
expect(storage).toBeDefined();
|
|
697
|
+
expect(storage.uow).toBeDefined();
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
it("should provide DatabaseServiceContext with forSchema and DatabaseHandlerContext with executeRestrictedUnitOfWork", () => {
|
|
701
|
+
const mockAdapter = createMockAdapter();
|
|
702
|
+
|
|
703
|
+
const definition = withDatabase(testSchema)(defineFragment("db-frag")).build();
|
|
704
|
+
|
|
705
|
+
// Create a mock storage
|
|
706
|
+
const mockStorage = {
|
|
707
|
+
getStore: () => ({
|
|
708
|
+
uow: mockAdapter.createQueryEngine(testSchema, "test").createUnitOfWork(),
|
|
709
|
+
}),
|
|
710
|
+
} as any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
711
|
+
|
|
712
|
+
const contexts = definition.createThisContext!({
|
|
713
|
+
config: {},
|
|
714
|
+
options: { databaseAdapter: mockAdapter },
|
|
715
|
+
deps: {} as any, // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
716
|
+
storage: mockStorage,
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
expect(typeof contexts.serviceContext.uow).toBe("function");
|
|
720
|
+
expect(typeof contexts.handlerContext.uow).toBe("function");
|
|
721
|
+
});
|
|
722
|
+
});
|
|
723
|
+
|
|
724
|
+
describe("complex database fragment", () => {
|
|
725
|
+
it("should support full database fragment definition", () => {
|
|
726
|
+
interface Config {
|
|
727
|
+
dbConnectionString: string;
|
|
728
|
+
debug: boolean;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
interface LogService {
|
|
732
|
+
log: (msg: string) => void;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
const mockAdapter = createMockAdapter();
|
|
736
|
+
|
|
737
|
+
const definition = withDatabase(
|
|
738
|
+
testSchema,
|
|
739
|
+
"my-namespace",
|
|
740
|
+
)(defineFragment<Config>("complex-db-frag"))
|
|
741
|
+
.withDependencies(({ config }) => ({
|
|
742
|
+
connectionString: config.dbConnectionString,
|
|
743
|
+
debug: config.debug,
|
|
744
|
+
}))
|
|
745
|
+
.usesOptionalService<"logger", LogService>("logger")
|
|
746
|
+
.providesBaseService(({ serviceDeps }) => ({
|
|
747
|
+
healthCheck: () => {
|
|
748
|
+
if (serviceDeps.logger) {
|
|
749
|
+
serviceDeps.logger.log("Health check");
|
|
750
|
+
}
|
|
751
|
+
return "OK";
|
|
752
|
+
},
|
|
753
|
+
}))
|
|
754
|
+
.providesService("users", () => ({
|
|
755
|
+
getAll: () => "all users",
|
|
756
|
+
create: (name: string) => `Created user ${name}`,
|
|
757
|
+
}))
|
|
758
|
+
.build();
|
|
759
|
+
|
|
760
|
+
expect(definition.name).toBe("complex-db-frag");
|
|
761
|
+
expect(definition.dependencies).toBeDefined();
|
|
762
|
+
expect(definition.baseServices).toBeDefined();
|
|
763
|
+
expect(definition.namedServices).toBeDefined();
|
|
764
|
+
expect(definition.serviceDependencies).toBeDefined();
|
|
765
|
+
expect(definition.createThisContext).toBeDefined();
|
|
766
|
+
|
|
767
|
+
// Test execution
|
|
768
|
+
const logs: string[] = [];
|
|
769
|
+
const deps = definition.dependencies!({
|
|
770
|
+
config: { dbConnectionString: "postgres://localhost", debug: true },
|
|
771
|
+
options: { databaseAdapter: mockAdapter },
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
const services = definition.baseServices!({
|
|
775
|
+
config: { dbConnectionString: "postgres://localhost", debug: true },
|
|
776
|
+
options: { databaseAdapter: mockAdapter },
|
|
777
|
+
deps,
|
|
778
|
+
serviceDeps: {
|
|
779
|
+
logger: {
|
|
780
|
+
log: (msg) => logs.push(msg),
|
|
781
|
+
},
|
|
782
|
+
},
|
|
783
|
+
privateServices: {},
|
|
784
|
+
defineService: (svc) => svc,
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
expect(services.healthCheck()).toBe("OK");
|
|
788
|
+
expect(logs).toContain("Health check");
|
|
789
|
+
|
|
790
|
+
const userService = definition.namedServices!.users({
|
|
791
|
+
config: { dbConnectionString: "postgres://localhost", debug: true },
|
|
792
|
+
options: { databaseAdapter: mockAdapter },
|
|
793
|
+
deps,
|
|
794
|
+
serviceDeps: {
|
|
795
|
+
logger: {
|
|
796
|
+
log: (msg) => logs.push(msg),
|
|
797
|
+
},
|
|
798
|
+
},
|
|
799
|
+
privateServices: {},
|
|
800
|
+
defineService: (svc) => svc,
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
expect(userService.getAll()).toBe("all users");
|
|
804
|
+
expect(userService.create("Alice")).toBe("Created user Alice");
|
|
805
|
+
});
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
describe("error handling", () => {
|
|
809
|
+
it("should throw error if database adapter is missing", () => {
|
|
810
|
+
const definition = withDatabase(testSchema)(defineFragment("db-frag")).build();
|
|
811
|
+
|
|
812
|
+
expect(() => {
|
|
813
|
+
definition.dependencies!({
|
|
814
|
+
config: {},
|
|
815
|
+
// @ts-expect-error No databaseAdapter - intentionally invalid for runtime error test
|
|
816
|
+
options: {},
|
|
817
|
+
});
|
|
818
|
+
}).toThrow("Database fragment requires a database adapter");
|
|
819
|
+
});
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
describe("type inference", () => {
|
|
823
|
+
it("should correctly infer implicit dependencies", () => {
|
|
824
|
+
const mockAdapter = createMockAdapter();
|
|
825
|
+
|
|
826
|
+
const definition = withDatabase(testSchema)(defineFragment("db-frag"))
|
|
827
|
+
.withDependencies(() => ({
|
|
828
|
+
customDep: "test",
|
|
829
|
+
}))
|
|
830
|
+
.build();
|
|
831
|
+
|
|
832
|
+
const deps = definition.dependencies!({
|
|
833
|
+
config: {},
|
|
834
|
+
options: { databaseAdapter: mockAdapter },
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
expectTypeOf(deps).toExtend<
|
|
838
|
+
{
|
|
839
|
+
customDep: string;
|
|
840
|
+
} & ImplicitDatabaseDependencies<TestSchema>
|
|
841
|
+
>();
|
|
842
|
+
|
|
843
|
+
expect(deps.customDep).toBe("test");
|
|
844
|
+
expect(deps.db).toBeDefined();
|
|
845
|
+
});
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
// describe("defineService", () => {
|
|
849
|
+
// it("getUnitOfWork should be available in defineService", () => {
|
|
850
|
+
// const mockAdapter = createMockAdapter();
|
|
851
|
+
|
|
852
|
+
// const definition = withDatabase(testSchema)(defineFragment("db-frag"))
|
|
853
|
+
// .withDependencies(() => ({ apiKey: "key" }))
|
|
854
|
+
// .providesBaseService(({ deps, defineService }) => {
|
|
855
|
+
// expect(deps.apiKey).toBe("key");
|
|
856
|
+
// expect(deps.db).toBeDefined();
|
|
857
|
+
// expect(defineService).toBeDefined();
|
|
858
|
+
|
|
859
|
+
// return defineService({
|
|
860
|
+
// getUsers: function () {
|
|
861
|
+
// return typeof this.getUnitOfWork;
|
|
862
|
+
// },
|
|
863
|
+
// });
|
|
864
|
+
// })
|
|
865
|
+
// .build();
|
|
866
|
+
|
|
867
|
+
// expect(definition.baseServices).toBeDefined();
|
|
868
|
+
|
|
869
|
+
// // Get implicit deps first
|
|
870
|
+
// const deps = definition.dependencies!({
|
|
871
|
+
// config: {},
|
|
872
|
+
// options: { databaseAdapter: mockAdapter },
|
|
873
|
+
// });
|
|
874
|
+
|
|
875
|
+
// const services = definition.baseServices!({
|
|
876
|
+
// config: {},
|
|
877
|
+
// options: { databaseAdapter: mockAdapter },
|
|
878
|
+
// deps,
|
|
879
|
+
// serviceDeps: {},
|
|
880
|
+
// privateServices: {},
|
|
881
|
+
// defineService: (svc) => svc,
|
|
882
|
+
// });
|
|
883
|
+
|
|
884
|
+
// expect(services.getUsers()).toBe("function");
|
|
885
|
+
// });
|
|
886
|
+
// });
|
|
887
|
+
});
|