@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.
Files changed (178) hide show
  1. package/.turbo/turbo-build.log +179 -132
  2. package/CHANGELOG.md +30 -0
  3. package/dist/adapters/adapters.d.ts +27 -1
  4. package/dist/adapters/adapters.d.ts.map +1 -1
  5. package/dist/adapters/adapters.js.map +1 -1
  6. package/dist/adapters/drizzle/drizzle-adapter.d.ts +5 -1
  7. package/dist/adapters/drizzle/drizzle-adapter.d.ts.map +1 -1
  8. package/dist/adapters/drizzle/drizzle-adapter.js +15 -3
  9. package/dist/adapters/drizzle/drizzle-adapter.js.map +1 -1
  10. package/dist/adapters/drizzle/drizzle-query.js +7 -5
  11. package/dist/adapters/drizzle/drizzle-query.js.map +1 -1
  12. package/dist/adapters/drizzle/drizzle-uow-compiler.d.ts +0 -1
  13. package/dist/adapters/drizzle/drizzle-uow-compiler.d.ts.map +1 -1
  14. package/dist/adapters/drizzle/drizzle-uow-compiler.js +76 -44
  15. package/dist/adapters/drizzle/drizzle-uow-compiler.js.map +1 -1
  16. package/dist/adapters/drizzle/drizzle-uow-decoder.js +23 -16
  17. package/dist/adapters/drizzle/drizzle-uow-decoder.js.map +1 -1
  18. package/dist/adapters/drizzle/drizzle-uow-executor.js +18 -7
  19. package/dist/adapters/drizzle/drizzle-uow-executor.js.map +1 -1
  20. package/dist/adapters/drizzle/generate.d.ts +4 -1
  21. package/dist/adapters/drizzle/generate.d.ts.map +1 -1
  22. package/dist/adapters/drizzle/generate.js +11 -18
  23. package/dist/adapters/drizzle/generate.js.map +1 -1
  24. package/dist/adapters/drizzle/shared.d.ts +14 -1
  25. package/dist/adapters/drizzle/shared.d.ts.map +1 -0
  26. package/dist/adapters/kysely/kysely-adapter.d.ts +5 -1
  27. package/dist/adapters/kysely/kysely-adapter.d.ts.map +1 -1
  28. package/dist/adapters/kysely/kysely-adapter.js +14 -3
  29. package/dist/adapters/kysely/kysely-adapter.js.map +1 -1
  30. package/dist/adapters/kysely/kysely-query-builder.js +1 -1
  31. package/dist/adapters/kysely/kysely-query-compiler.js +3 -2
  32. package/dist/adapters/kysely/kysely-query-compiler.js.map +1 -1
  33. package/dist/adapters/kysely/kysely-query.d.ts +1 -0
  34. package/dist/adapters/kysely/kysely-query.d.ts.map +1 -1
  35. package/dist/adapters/kysely/kysely-query.js +28 -19
  36. package/dist/adapters/kysely/kysely-query.js.map +1 -1
  37. package/dist/adapters/kysely/kysely-shared.d.ts +14 -0
  38. package/dist/adapters/kysely/kysely-shared.d.ts.map +1 -0
  39. package/dist/adapters/kysely/kysely-shared.js +16 -1
  40. package/dist/adapters/kysely/kysely-shared.js.map +1 -1
  41. package/dist/adapters/kysely/kysely-uow-compiler.js +68 -16
  42. package/dist/adapters/kysely/kysely-uow-compiler.js.map +1 -1
  43. package/dist/adapters/kysely/kysely-uow-executor.js +8 -4
  44. package/dist/adapters/kysely/kysely-uow-executor.js.map +1 -1
  45. package/dist/adapters/kysely/migration/execute-base.js +1 -1
  46. package/dist/adapters/kysely/migration/execute-base.js.map +1 -1
  47. package/dist/db-fragment-definition-builder.d.ts +152 -0
  48. package/dist/db-fragment-definition-builder.d.ts.map +1 -0
  49. package/dist/db-fragment-definition-builder.js +137 -0
  50. package/dist/db-fragment-definition-builder.js.map +1 -0
  51. package/dist/fragments/internal-fragment.d.ts +19 -0
  52. package/dist/fragments/internal-fragment.d.ts.map +1 -0
  53. package/dist/fragments/internal-fragment.js +39 -0
  54. package/dist/fragments/internal-fragment.js.map +1 -0
  55. package/dist/migration-engine/generation-engine.d.ts.map +1 -1
  56. package/dist/migration-engine/generation-engine.js +35 -15
  57. package/dist/migration-engine/generation-engine.js.map +1 -1
  58. package/dist/mod.d.ts +8 -18
  59. package/dist/mod.d.ts.map +1 -1
  60. package/dist/mod.js +7 -34
  61. package/dist/mod.js.map +1 -1
  62. package/dist/node_modules/.pnpm/rou3@0.7.8/node_modules/rou3/dist/index.js +165 -0
  63. package/dist/node_modules/.pnpm/rou3@0.7.8/node_modules/rou3/dist/index.js.map +1 -0
  64. package/dist/packages/fragno/dist/api/bind-services.js +20 -0
  65. package/dist/packages/fragno/dist/api/bind-services.js.map +1 -0
  66. package/dist/packages/fragno/dist/api/error.js +48 -0
  67. package/dist/packages/fragno/dist/api/error.js.map +1 -0
  68. package/dist/packages/fragno/dist/api/fragment-definition-builder.js +320 -0
  69. package/dist/packages/fragno/dist/api/fragment-definition-builder.js.map +1 -0
  70. package/dist/packages/fragno/dist/api/fragment-instantiator.js +487 -0
  71. package/dist/packages/fragno/dist/api/fragment-instantiator.js.map +1 -0
  72. package/dist/packages/fragno/dist/api/fragno-response.js +73 -0
  73. package/dist/packages/fragno/dist/api/fragno-response.js.map +1 -0
  74. package/dist/packages/fragno/dist/api/internal/response-stream.js +81 -0
  75. package/dist/packages/fragno/dist/api/internal/response-stream.js.map +1 -0
  76. package/dist/packages/fragno/dist/api/internal/route.js +10 -0
  77. package/dist/packages/fragno/dist/api/internal/route.js.map +1 -0
  78. package/dist/packages/fragno/dist/api/mutable-request-state.js +97 -0
  79. package/dist/packages/fragno/dist/api/mutable-request-state.js.map +1 -0
  80. package/dist/packages/fragno/dist/api/request-context-storage.js +43 -0
  81. package/dist/packages/fragno/dist/api/request-context-storage.js.map +1 -0
  82. package/dist/packages/fragno/dist/api/request-input-context.js +118 -0
  83. package/dist/packages/fragno/dist/api/request-input-context.js.map +1 -0
  84. package/dist/packages/fragno/dist/api/request-middleware.js +83 -0
  85. package/dist/packages/fragno/dist/api/request-middleware.js.map +1 -0
  86. package/dist/packages/fragno/dist/api/request-output-context.js +119 -0
  87. package/dist/packages/fragno/dist/api/request-output-context.js.map +1 -0
  88. package/dist/packages/fragno/dist/api/route.js +17 -0
  89. package/dist/packages/fragno/dist/api/route.js.map +1 -0
  90. package/dist/packages/fragno/dist/internal/symbols.js +10 -0
  91. package/dist/packages/fragno/dist/internal/symbols.js.map +1 -0
  92. package/dist/query/cursor.d.ts +10 -2
  93. package/dist/query/cursor.d.ts.map +1 -1
  94. package/dist/query/cursor.js +11 -4
  95. package/dist/query/cursor.js.map +1 -1
  96. package/dist/query/execute-unit-of-work.d.ts +123 -0
  97. package/dist/query/execute-unit-of-work.d.ts.map +1 -0
  98. package/dist/query/execute-unit-of-work.js +184 -0
  99. package/dist/query/execute-unit-of-work.js.map +1 -0
  100. package/dist/query/query.d.ts +3 -3
  101. package/dist/query/query.d.ts.map +1 -1
  102. package/dist/query/result-transform.js +4 -2
  103. package/dist/query/result-transform.js.map +1 -1
  104. package/dist/query/retry-policy.d.ts +88 -0
  105. package/dist/query/retry-policy.d.ts.map +1 -0
  106. package/dist/query/retry-policy.js +61 -0
  107. package/dist/query/retry-policy.js.map +1 -0
  108. package/dist/query/unit-of-work.d.ts +171 -32
  109. package/dist/query/unit-of-work.d.ts.map +1 -1
  110. package/dist/query/unit-of-work.js +530 -133
  111. package/dist/query/unit-of-work.js.map +1 -1
  112. package/dist/schema/serialize.js +12 -7
  113. package/dist/schema/serialize.js.map +1 -1
  114. package/dist/with-database.d.ts +28 -0
  115. package/dist/with-database.d.ts.map +1 -0
  116. package/dist/with-database.js +34 -0
  117. package/dist/with-database.js.map +1 -0
  118. package/package.json +10 -3
  119. package/src/adapters/adapters.ts +30 -0
  120. package/src/adapters/drizzle/drizzle-adapter-pglite.test.ts +86 -17
  121. package/src/adapters/drizzle/drizzle-adapter-sqlite.test.ts +291 -7
  122. package/src/adapters/drizzle/drizzle-adapter.test.ts +3 -51
  123. package/src/adapters/drizzle/drizzle-adapter.ts +35 -7
  124. package/src/adapters/drizzle/drizzle-query.ts +25 -15
  125. package/src/adapters/drizzle/drizzle-uow-compiler-mysql.test.ts +1442 -0
  126. package/src/adapters/drizzle/drizzle-uow-compiler-sqlite.test.ts +1414 -0
  127. package/src/adapters/drizzle/drizzle-uow-compiler.test.ts +78 -61
  128. package/src/adapters/drizzle/drizzle-uow-compiler.ts +123 -42
  129. package/src/adapters/drizzle/drizzle-uow-decoder.ts +34 -27
  130. package/src/adapters/drizzle/drizzle-uow-executor.ts +41 -8
  131. package/src/adapters/drizzle/generate.test.ts +102 -269
  132. package/src/adapters/drizzle/generate.ts +12 -30
  133. package/src/adapters/drizzle/test-utils.ts +36 -5
  134. package/src/adapters/kysely/kysely-adapter-pglite.test.ts +66 -22
  135. package/src/adapters/kysely/kysely-adapter-sqlite.test.ts +156 -0
  136. package/src/adapters/kysely/kysely-adapter.ts +25 -2
  137. package/src/adapters/kysely/kysely-query-compiler.ts +3 -8
  138. package/src/adapters/kysely/kysely-query.ts +57 -37
  139. package/src/adapters/kysely/kysely-shared.ts +34 -0
  140. package/src/adapters/kysely/kysely-uow-compiler.test.ts +62 -74
  141. package/src/adapters/kysely/kysely-uow-compiler.ts +92 -24
  142. package/src/adapters/kysely/kysely-uow-executor.ts +26 -7
  143. package/src/adapters/kysely/kysely-uow-joins.test.ts +33 -50
  144. package/src/adapters/kysely/migration/execute-base.ts +1 -1
  145. package/src/db-fragment-definition-builder.test.ts +887 -0
  146. package/src/db-fragment-definition-builder.ts +506 -0
  147. package/src/db-fragment-instantiator.test.ts +467 -0
  148. package/src/db-fragment-integration.test.ts +408 -0
  149. package/src/fragments/internal-fragment.test.ts +160 -0
  150. package/src/fragments/internal-fragment.ts +85 -0
  151. package/src/migration-engine/generation-engine.test.ts +58 -15
  152. package/src/migration-engine/generation-engine.ts +78 -25
  153. package/src/mod.ts +35 -43
  154. package/src/query/cursor.test.ts +119 -0
  155. package/src/query/cursor.ts +17 -4
  156. package/src/query/execute-unit-of-work.test.ts +1310 -0
  157. package/src/query/execute-unit-of-work.ts +463 -0
  158. package/src/query/query.ts +4 -4
  159. package/src/query/result-transform.test.ts +129 -0
  160. package/src/query/result-transform.ts +4 -1
  161. package/src/query/retry-policy.test.ts +217 -0
  162. package/src/query/retry-policy.ts +141 -0
  163. package/src/query/unit-of-work-coordinator.test.ts +833 -0
  164. package/src/query/unit-of-work-types.test.ts +15 -2
  165. package/src/query/unit-of-work.test.ts +878 -200
  166. package/src/query/unit-of-work.ts +963 -321
  167. package/src/schema/serialize.ts +22 -11
  168. package/src/with-database.ts +140 -0
  169. package/tsdown.config.ts +1 -0
  170. package/dist/fragment.d.ts +0 -54
  171. package/dist/fragment.d.ts.map +0 -1
  172. package/dist/fragment.js +0 -92
  173. package/dist/fragment.js.map +0 -1
  174. package/dist/shared/settings-schema.js +0 -36
  175. package/dist/shared/settings-schema.js.map +0 -1
  176. package/src/fragment.test.ts +0 -341
  177. package/src/fragment.ts +0 -198
  178. package/src/shared/settings-schema.ts +0 -61
@@ -0,0 +1,467 @@
1
+ import { describe, it, expect, vi, assert } from "vitest";
2
+ import { instantiate, defineFragment } from "@fragno-dev/core";
3
+ import { defineRoutes } from "@fragno-dev/core/route";
4
+ import { withDatabase } from "./with-database";
5
+ import { schema, idColumn, column } from "./schema/create";
6
+ import type { DatabaseAdapter } from "./adapters/adapters";
7
+ import type { AbstractQuery } from "./query/query";
8
+ import { RequestContextStorage } from "@fragno-dev/core/internal/request-context-storage";
9
+ import { z } from "zod";
10
+
11
+ // Create a test schema
12
+ const testSchema = schema((s) => {
13
+ return s.addTable("users", (t) => {
14
+ return t.addColumn("id", idColumn()).addColumn("name", column("string"));
15
+ });
16
+ });
17
+
18
+ type TestSchema = typeof testSchema;
19
+
20
+ // Mock database adapter
21
+ function createMockAdapter(): DatabaseAdapter {
22
+ const mockdb = {
23
+ createUnitOfWork: vi.fn(() => {
24
+ // Create a mock restricted UOW
25
+ const createMockRestrictedUow = () => ({
26
+ forSchema: vi.fn((schema) => ({
27
+ schema,
28
+ table: vi.fn(() => ({
29
+ findMany: vi.fn(),
30
+ })),
31
+ restrict: vi.fn(() => createMockRestrictedUow()),
32
+ })),
33
+ restrict: vi.fn(() => createMockRestrictedUow()),
34
+ table: vi.fn(() => ({
35
+ findMany: vi.fn(),
36
+ })),
37
+ });
38
+
39
+ return {
40
+ forSchema: vi.fn((schema) => ({
41
+ schema,
42
+ table: vi.fn(() => ({
43
+ findMany: vi.fn(),
44
+ })),
45
+ restrict: vi.fn(() => createMockRestrictedUow()),
46
+ })),
47
+ restrict: vi.fn(() => createMockRestrictedUow()),
48
+ executeRetrieve: vi.fn(),
49
+ executeMutations: vi.fn(),
50
+ commit: vi.fn(),
51
+ rollback: vi.fn(),
52
+ reset: vi.fn(),
53
+ table: vi.fn(() => ({
54
+ findMany: vi.fn(),
55
+ })),
56
+ };
57
+ }),
58
+ type: "mock",
59
+ } as unknown as AbstractQuery<TestSchema>;
60
+
61
+ return {
62
+ createQueryEngine: vi.fn(() => mockdb),
63
+ migrate: vi.fn(),
64
+ close: vi.fn(),
65
+ type: "mock",
66
+ contextStorage: new RequestContextStorage(),
67
+ } as unknown as DatabaseAdapter;
68
+ }
69
+
70
+ describe("db-fragment-instantiator", () => {
71
+ describe("Unit of Work in request context", () => {
72
+ it("should provide executeRestrictedUnitOfWork on this context in route handlers", async () => {
73
+ const definition = defineFragment("test-db-fragment")
74
+ .extend(withDatabase(testSchema))
75
+ .build();
76
+
77
+ const routes = defineRoutes(definition).create(({ defineRoute }) => [
78
+ defineRoute({
79
+ method: "GET",
80
+ path: "/test",
81
+ handler: async function (_input, { json }) {
82
+ // Access executeRestrictedUnitOfWork from this context
83
+ expect(this.uow).toBeDefined();
84
+
85
+ return json({ hasExecuteMethod: !!this.uow });
86
+ },
87
+ }),
88
+ ]);
89
+
90
+ const mockAdapter = createMockAdapter();
91
+ const fragment = instantiate(definition)
92
+ .withRoutes([routes])
93
+ .withOptions({ mountRoute: "/api", databaseAdapter: mockAdapter })
94
+ .build();
95
+
96
+ const response = await fragment.handler(new Request("http://localhost/api/test"));
97
+ const data = await response.json();
98
+
99
+ expect(data).toEqual({ hasExecuteMethod: true });
100
+ });
101
+
102
+ it("should provide schema-typed UOW via executeRestrictedUnitOfWork", async () => {
103
+ const definition = defineFragment("test-db-fragment")
104
+ .extend(withDatabase(testSchema))
105
+ .build();
106
+
107
+ const routes = defineRoutes(definition).create(({ defineRoute }) => [
108
+ defineRoute({
109
+ method: "GET",
110
+ path: "/test",
111
+ handler: async function (_input, { json }) {
112
+ const result = await this.uow(async ({ forSchema }) => {
113
+ const uow = forSchema(testSchema);
114
+ return { hasSchemaUow: !!uow };
115
+ });
116
+
117
+ return json(result);
118
+ },
119
+ }),
120
+ ]);
121
+
122
+ const mockAdapter = createMockAdapter();
123
+ const fragment = instantiate(definition)
124
+ .withRoutes([routes])
125
+ .withOptions({ mountRoute: "/api", databaseAdapter: mockAdapter })
126
+ .build();
127
+
128
+ const response = await fragment.handler(new Request("http://localhost/api/test"));
129
+ const data = await response.json();
130
+
131
+ expect(data).toEqual({ hasSchemaUow: true });
132
+ });
133
+ });
134
+
135
+ describe("implicit database dependencies", () => {
136
+ it("should provide db dependency to services", () => {
137
+ const definition = defineFragment("test-db-fragment")
138
+ .extend(withDatabase(testSchema))
139
+ .providesBaseService(({ deps }) => ({
140
+ getDb: () => deps.db,
141
+ }))
142
+ .build();
143
+
144
+ const mockAdapter = createMockAdapter();
145
+ const fragment = instantiate(definition)
146
+ .withOptions({ databaseAdapter: mockAdapter })
147
+ .build();
148
+
149
+ const db = fragment.services.getDb();
150
+ expect(db).toBeDefined();
151
+ expect(db.createUnitOfWork).toBeDefined();
152
+ });
153
+
154
+ it("should provide schema dependency to services", () => {
155
+ const definition = defineFragment("test-db-fragment")
156
+ .extend(withDatabase(testSchema))
157
+ .providesBaseService(({ deps }) => ({
158
+ getSchema: () => deps.schema,
159
+ }))
160
+ .build();
161
+
162
+ const mockAdapter = createMockAdapter();
163
+ const fragment = instantiate(definition)
164
+ .withOptions({ databaseAdapter: mockAdapter })
165
+ .build();
166
+
167
+ const schemaFromService = fragment.services.getSchema();
168
+ expect(schemaFromService).toBe(testSchema);
169
+ });
170
+
171
+ it("should provide createUnitOfWork dependency", () => {
172
+ const definition = defineFragment("test-db-fragment")
173
+ .extend(withDatabase(testSchema))
174
+ .providesBaseService(({ deps }) => ({
175
+ createUow: () => deps.createUnitOfWork(),
176
+ }))
177
+ .build();
178
+
179
+ const mockAdapter = createMockAdapter();
180
+ const fragment = instantiate(definition)
181
+ .withOptions({ databaseAdapter: mockAdapter })
182
+ .build();
183
+
184
+ const uow = fragment.services.createUow();
185
+ expect(uow).toBeDefined();
186
+ expect(uow.executeRetrieve).toBeDefined();
187
+ expect(uow.executeMutations).toBeDefined();
188
+ });
189
+ });
190
+
191
+ describe("database operations with UOW", () => {
192
+ it("should allow accessing schema-typed UOW in handlers via executeRestrictedUnitOfWork", async () => {
193
+ const testSchemaWithCounter = schema((s) => {
194
+ return s.addTable("counters", (t) => {
195
+ return t.addColumn("id", idColumn()).addColumn("value", column("integer"));
196
+ });
197
+ });
198
+
199
+ const definition = defineFragment("test-db-fragment")
200
+ .extend(withDatabase(testSchemaWithCounter))
201
+ .build();
202
+
203
+ const routes = defineRoutes(definition).create(({ defineRoute }) => [
204
+ defineRoute({
205
+ method: "GET",
206
+ path: "/counters",
207
+ handler: async function (_input, { json }) {
208
+ const result = await this.uow(async ({ forSchema }) => {
209
+ const uow = forSchema(testSchemaWithCounter);
210
+ // Verify that we can access the UOW
211
+ return { hasCountersTable: !!uow };
212
+ });
213
+
214
+ return json(result);
215
+ },
216
+ }),
217
+ ]);
218
+
219
+ const mockAdapter = createMockAdapter();
220
+ const fragment = instantiate(definition)
221
+ .withRoutes([routes])
222
+ .withOptions({ mountRoute: "/api", databaseAdapter: mockAdapter })
223
+ .build();
224
+
225
+ const response = await fragment.handler(new Request("http://localhost/api/counters"));
226
+ const data = await response.json();
227
+
228
+ expect(data).toEqual({ hasCountersTable: true });
229
+ });
230
+ });
231
+
232
+ describe("service integration with UOW", () => {
233
+ it("should allow services to access UOW via forSchema", async () => {
234
+ const definition = defineFragment("test-db-fragment")
235
+ .extend(withDatabase(testSchema))
236
+ .providesBaseService(({ defineService }) =>
237
+ defineService({
238
+ checkTypedUowExists: function () {
239
+ const uow = this.uow(testSchema);
240
+ return !!uow;
241
+ },
242
+ }),
243
+ )
244
+ .build();
245
+
246
+ const routes = defineRoutes(definition).create(({ services, defineRoute }) => [
247
+ defineRoute({
248
+ method: "GET",
249
+ path: "/check",
250
+ outputSchema: z.object({ hasTypedUow: z.boolean() }),
251
+ handler: async function (_input, { json }) {
252
+ const hasTypedUow = services.checkTypedUowExists();
253
+ return json({ hasTypedUow });
254
+ },
255
+ }),
256
+ ]);
257
+
258
+ const mockAdapter = createMockAdapter();
259
+ const fragment = instantiate(definition)
260
+ .withRoutes([routes])
261
+ .withOptions({ mountRoute: "/api", databaseAdapter: mockAdapter })
262
+ .build();
263
+
264
+ const response = await fragment.callRoute("GET", "/check");
265
+ expect(response.status).toBe(200);
266
+ assert(response.type === "json");
267
+ expect(response.data).toEqual({ hasTypedUow: true });
268
+ });
269
+
270
+ it.skip("should share same UOW across multiple service calls from handler", async () => {
271
+ const definition = defineFragment("test-db-fragment")
272
+ .extend(withDatabase(testSchema))
273
+ .providesService("helpers", ({ defineService }) =>
274
+ defineService({
275
+ logUow: function () {
276
+ return this.uow(testSchema);
277
+ },
278
+ }),
279
+ )
280
+ .providesService("main", ({ defineService }) =>
281
+ defineService({
282
+ markUow: function () {
283
+ return this.uow(testSchema);
284
+ },
285
+ }),
286
+ )
287
+ .build();
288
+
289
+ const routes = defineRoutes(definition).create(({ services, defineRoute }) => [
290
+ defineRoute({
291
+ method: "GET",
292
+ path: "/nested",
293
+ handler: async function (_input, { json }) {
294
+ // Mark the UOW with an ID
295
+ const uow1 = services.main.markUow();
296
+ const uow2 = services.helpers.logUow();
297
+ const uow3 = services.main.markUow();
298
+
299
+ console.log({
300
+ x: uow1 === uow2,
301
+ y: uow2 === uow3,
302
+ z: uow1 === uow3,
303
+ });
304
+
305
+ return json({
306
+ same: uow1 === uow2 && uow2 === uow3,
307
+ });
308
+ },
309
+ }),
310
+ ]);
311
+
312
+ const mockAdapter = createMockAdapter();
313
+ const fragment = instantiate(definition)
314
+ .withRoutes([routes])
315
+ .withOptions({ mountRoute: "/api", databaseAdapter: mockAdapter })
316
+ .build();
317
+
318
+ const response = await fragment.handler(new Request("http://localhost/api/nested"));
319
+ const data = await response.json();
320
+
321
+ expect(data).toEqual({
322
+ same: true,
323
+ });
324
+ });
325
+ });
326
+
327
+ describe("inContext with database fragments", () => {
328
+ it("should allow calling services with UOW via inContext", () => {
329
+ const definition = defineFragment("test-db-fragment")
330
+ .extend(withDatabase(testSchema))
331
+ .providesBaseService(({ defineService }) =>
332
+ defineService({
333
+ getUowExists: function () {
334
+ const uow = this.uow(testSchema);
335
+ return !!uow;
336
+ },
337
+ }),
338
+ )
339
+ .build();
340
+
341
+ const mockAdapter = createMockAdapter();
342
+ const fragment = instantiate(definition)
343
+ .withOptions({ databaseAdapter: mockAdapter })
344
+ .build();
345
+
346
+ const result = fragment.inContext(() => fragment.services.getUowExists());
347
+ expect(result).toBe(true);
348
+ });
349
+ });
350
+
351
+ describe("UOW isolation per request", () => {
352
+ it("should create fresh UOW for each request", async () => {
353
+ let createUowCallCount = 0;
354
+ const mockAdapter = createMockAdapter();
355
+ const queryEngine = mockAdapter.createQueryEngine(testSchema, "test");
356
+
357
+ // Track how many times createUnitOfWork is called
358
+ const originalCreateUow = queryEngine.createUnitOfWork;
359
+ queryEngine.createUnitOfWork = vi.fn(() => {
360
+ createUowCallCount++;
361
+ return originalCreateUow();
362
+ });
363
+
364
+ const definition = defineFragment("test-db-fragment")
365
+ .extend(withDatabase(testSchema))
366
+ .build();
367
+
368
+ const routes = defineRoutes(definition).create(({ defineRoute }) => [
369
+ defineRoute({
370
+ method: "GET",
371
+ path: "/test",
372
+ handler: async function (_input, { json }) {
373
+ const result = await this.uow(async ({ forSchema }) => {
374
+ const uow = forSchema(testSchema);
375
+ return { hasUow: !!uow };
376
+ });
377
+ return json(result);
378
+ },
379
+ }),
380
+ ]);
381
+
382
+ const fragment = instantiate(definition)
383
+ .withRoutes([routes])
384
+ .withOptions({ mountRoute: "/api", databaseAdapter: mockAdapter })
385
+ .build();
386
+
387
+ // Make two requests
388
+ await fragment.handler(new Request("http://localhost/api/test"));
389
+ await fragment.handler(new Request("http://localhost/api/test"));
390
+
391
+ // Verify that createUnitOfWork was called twice (once per request)
392
+ expect(createUowCallCount).toBe(2);
393
+ expect(queryEngine.createUnitOfWork).toHaveBeenCalledTimes(2);
394
+ });
395
+ });
396
+
397
+ describe("withDependencies with database context", () => {
398
+ it("should provide db and databaseAdapter in withDependencies context", () => {
399
+ interface Config {
400
+ prefix: string;
401
+ }
402
+
403
+ const definition = defineFragment<Config>("test-db-fragment")
404
+ .extend(withDatabase(testSchema))
405
+ .withDependencies(({ config, db, databaseAdapter }) => ({
406
+ userService: {
407
+ prefix: config.prefix,
408
+ hasAdapter: !!databaseAdapter,
409
+ hasDb: !!db,
410
+ },
411
+ }))
412
+ .providesBaseService(({ deps }) => ({
413
+ getUserServicePrefix: () => deps.userService.prefix,
414
+ hasAdapter: () => deps.userService.hasAdapter,
415
+ hasDb: () => deps.userService.hasDb,
416
+ }))
417
+ .build();
418
+
419
+ const mockAdapter = createMockAdapter();
420
+ const fragment = instantiate(definition)
421
+ .withConfig({ prefix: "USER_" })
422
+ .withOptions({ databaseAdapter: mockAdapter })
423
+ .build();
424
+
425
+ expect(fragment.services.getUserServicePrefix()).toBe("USER_");
426
+ expect(fragment.services.hasAdapter()).toBe(true);
427
+ expect(fragment.services.hasDb()).toBe(true);
428
+ });
429
+ });
430
+
431
+ describe("error handling", () => {
432
+ it("should throw when databaseAdapter is not provided", () => {
433
+ const definition = defineFragment("test-db-fragment")
434
+ .extend(withDatabase(testSchema))
435
+ .build();
436
+
437
+ expect(() => {
438
+ instantiate(definition)
439
+ // @ts-expect-error - Test case
440
+ .withOptions({})
441
+ .build();
442
+ }).toThrow("Database fragment requires a database adapter");
443
+ });
444
+
445
+ it("should throw when forSchema called outside request context", () => {
446
+ const definition = defineFragment("test-db-fragment")
447
+ .extend(withDatabase(testSchema))
448
+ .providesBaseService(({ defineService }) =>
449
+ defineService({
450
+ tryGetUow: function () {
451
+ return this.uow(testSchema);
452
+ },
453
+ }),
454
+ )
455
+ .build();
456
+
457
+ const mockAdapter = createMockAdapter();
458
+ const fragment = instantiate(definition)
459
+ .withOptions({ databaseAdapter: mockAdapter })
460
+ .build();
461
+
462
+ expect(() => fragment.services.tryGetUow()).toThrow(
463
+ "No storage found in RequestContextStorage. Service must be called within a route handler OR using `inContext`.",
464
+ );
465
+ });
466
+ });
467
+ });