@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,506 @@
1
+ import type { AnySchema } from "./schema/create";
2
+ import type { AbstractQuery } from "./query/query";
3
+ import type { DatabaseAdapter } from "./adapters/adapters";
4
+ import type { IUnitOfWork } from "./query/unit-of-work";
5
+ import { TypedUnitOfWork, UnitOfWork } from "./query/unit-of-work";
6
+ import type { RequestThisContext, FragnoPublicConfig } from "@fragno-dev/core";
7
+ import {
8
+ FragmentDefinitionBuilder,
9
+ type FragmentDefinition,
10
+ type ServiceConstructorFn,
11
+ } from "@fragno-dev/core";
12
+ import {
13
+ executeRestrictedUnitOfWork,
14
+ type AwaitedPromisesInObject,
15
+ type ExecuteRestrictedUnitOfWorkOptions,
16
+ } from "./query/execute-unit-of-work";
17
+
18
+ /**
19
+ * Extended FragnoPublicConfig that includes a database adapter.
20
+ * Use this type when creating fragments with database support.
21
+ */
22
+ export type FragnoPublicConfigWithDatabase = FragnoPublicConfig & {
23
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
24
+ databaseAdapter: DatabaseAdapter<any>;
25
+ };
26
+
27
+ /**
28
+ * Implicit dependencies that database fragments get automatically.
29
+ * These are injected without requiring explicit configuration.
30
+ */
31
+ export type ImplicitDatabaseDependencies<TSchema extends AnySchema> = {
32
+ /**
33
+ * Database query engine for the fragment's schema.
34
+ */
35
+ db: AbstractQuery<TSchema>;
36
+ /**
37
+ * The schema definition for this fragment.
38
+ */
39
+ schema: TSchema;
40
+ /**
41
+ * The database namespace for this fragment.
42
+ */
43
+ namespace: string;
44
+ /**
45
+ * Create a new Unit of Work for database operations.
46
+ */
47
+ createUnitOfWork: () => IUnitOfWork;
48
+ };
49
+
50
+ /**
51
+ * Service context for database fragments - provides restricted UOW access without execute methods.
52
+ */
53
+ export type DatabaseServiceContext = RequestThisContext & {
54
+ /**
55
+ * Get a typed, restricted Unit of Work for the given schema.
56
+ * @param schema - Schema to get a typed view for
57
+ * @returns TypedUnitOfWork (restricted version without execute methods)
58
+ */
59
+ uow<TSchema extends AnySchema>(schema: TSchema): TypedUnitOfWork<TSchema, [], unknown>;
60
+ };
61
+
62
+ /**
63
+ * Handler context for database fragments - provides UOW execution with automatic retry support.
64
+ */
65
+ export type DatabaseHandlerContext = RequestThisContext & {
66
+ /**
67
+ * Execute a Unit of Work with explicit phase control and automatic retry support.
68
+ * This method provides an API where users call forSchema to create a schema-specific
69
+ * UOW, then call executeRetrieve() and executeMutate() to execute the phases. The entire
70
+ * callback is re-executed on optimistic concurrency conflicts, ensuring retries work properly.
71
+ * Automatically provides the UOW factory from context.
72
+ * Promises in the returned object are awaited 1 level deep.
73
+ *
74
+ * @param callback - Async function that receives a context with forSchema, executeRetrieve, executeMutate, nonce, and currentAttempt
75
+ * @param options - Optional configuration for retry policy and abort signal
76
+ * @returns Promise resolving to the callback's return value with promises awaited 1 level deep
77
+ * @throws Error if retries are exhausted or callback throws an error
78
+ *
79
+ * @example
80
+ * ```ts
81
+ * const result = await this.uow(async ({ forSchema, executeRetrieve, executeMutate, nonce, currentAttempt }) => {
82
+ * const uow = forSchema(schema);
83
+ * const userId = uow.create("users", { name: "John" });
84
+ *
85
+ * // Execute retrieval phase
86
+ * await executeRetrieve();
87
+ *
88
+ * const profileId = uow.create("profiles", { userId });
89
+ *
90
+ * // Execute mutation phase
91
+ * await executeMutate();
92
+ *
93
+ * return { userId, profileId };
94
+ * });
95
+ * ```
96
+ */
97
+ uow<TResult>(
98
+ callback: (context: {
99
+ forSchema: <S extends AnySchema>(schema: S) => TypedUnitOfWork<S, [], unknown>;
100
+ executeRetrieve: () => Promise<void>;
101
+ executeMutate: () => Promise<void>;
102
+ nonce: string;
103
+ currentAttempt: number;
104
+ }) => Promise<TResult> | TResult,
105
+ options?: Omit<ExecuteRestrictedUnitOfWorkOptions, "createUnitOfWork">,
106
+ ): Promise<AwaitedPromisesInObject<TResult>>;
107
+ };
108
+
109
+ /**
110
+ * Database fragment context provided to user callbacks.
111
+ */
112
+ export type DatabaseFragmentContext<TSchema extends AnySchema> = {
113
+ /**
114
+ * Database adapter instance.
115
+ */
116
+ databaseAdapter: DatabaseAdapter<any>; // eslint-disable-line @typescript-eslint/no-explicit-any
117
+ /**
118
+ * ORM query engine for the fragment's schema.
119
+ */
120
+ db: AbstractQuery<TSchema>;
121
+ };
122
+
123
+ /**
124
+ * Create database context from options.
125
+ * This extracts the database adapter and creates the ORM instance.
126
+ */
127
+ function createDatabaseContext<TSchema extends AnySchema>(
128
+ options: FragnoPublicConfigWithDatabase,
129
+ schema: TSchema,
130
+ namespace: string,
131
+ ): DatabaseFragmentContext<TSchema> {
132
+ const databaseAdapter = options.databaseAdapter;
133
+
134
+ if (!databaseAdapter) {
135
+ throw new Error(
136
+ "Database fragment requires a database adapter to be provided in options.databaseAdapter",
137
+ );
138
+ }
139
+
140
+ const db = databaseAdapter.createQueryEngine(schema, namespace);
141
+
142
+ return { databaseAdapter, db };
143
+ }
144
+
145
+ /**
146
+ * Storage type for database fragments - stores the Unit of Work.
147
+ */
148
+ export type DatabaseRequestStorage = {
149
+ uow: IUnitOfWork;
150
+ };
151
+
152
+ /**
153
+ * Builder for database fragments that wraps the core fragment builder
154
+ * and provides database-specific functionality.
155
+ *
156
+ * Database fragments always require FragnoPublicConfigWithDatabase (which includes databaseAdapter).
157
+ */
158
+ export class DatabaseFragmentDefinitionBuilder<
159
+ TSchema extends AnySchema,
160
+ TConfig,
161
+ TDeps,
162
+ TBaseServices,
163
+ TServices,
164
+ TServiceDependencies,
165
+ TPrivateServices,
166
+ TServiceThisContext extends RequestThisContext = DatabaseHandlerContext,
167
+ THandlerThisContext extends RequestThisContext = DatabaseHandlerContext,
168
+ > {
169
+ // Store the base builder - we'll replace its storage and context setup when building
170
+ #baseBuilder: FragmentDefinitionBuilder<
171
+ TConfig,
172
+ FragnoPublicConfigWithDatabase,
173
+ TDeps,
174
+ TBaseServices,
175
+ TServices,
176
+ TServiceDependencies,
177
+ TPrivateServices,
178
+ TServiceThisContext,
179
+ THandlerThisContext,
180
+ DatabaseRequestStorage
181
+ >;
182
+ #schema: TSchema;
183
+ #namespace: string;
184
+
185
+ constructor(
186
+ baseBuilder: FragmentDefinitionBuilder<
187
+ TConfig,
188
+ FragnoPublicConfigWithDatabase,
189
+ TDeps,
190
+ TBaseServices,
191
+ TServices,
192
+ TServiceDependencies,
193
+ TPrivateServices,
194
+ TServiceThisContext,
195
+ THandlerThisContext,
196
+ DatabaseRequestStorage
197
+ >,
198
+ schema: TSchema,
199
+ namespace?: string,
200
+ ) {
201
+ this.#baseBuilder = baseBuilder;
202
+ this.#schema = schema;
203
+ this.#namespace = namespace ?? baseBuilder.name + "-db";
204
+ }
205
+
206
+ /**
207
+ * Define dependencies for this database fragment.
208
+ * The context includes database adapter and ORM instance.
209
+ */
210
+ withDependencies<TNewDeps>(
211
+ fn: (context: {
212
+ config: TConfig;
213
+ options: FragnoPublicConfigWithDatabase;
214
+ db: AbstractQuery<TSchema>;
215
+ databaseAdapter: DatabaseAdapter<any>; // eslint-disable-line @typescript-eslint/no-explicit-any
216
+ }) => TNewDeps,
217
+ ): DatabaseFragmentDefinitionBuilder<
218
+ TSchema,
219
+ TConfig,
220
+ TNewDeps & ImplicitDatabaseDependencies<TSchema>,
221
+ {},
222
+ {},
223
+ TServiceDependencies,
224
+ {},
225
+ TServiceThisContext,
226
+ THandlerThisContext
227
+ > {
228
+ // Wrap user function to inject DB context
229
+ const wrappedFn = (context: { config: TConfig; options: FragnoPublicConfigWithDatabase }) => {
230
+ const dbContext = createDatabaseContext(context.options, this.#schema, this.#namespace);
231
+
232
+ // Call user function with enriched context
233
+ const userDeps = fn({
234
+ config: context.config,
235
+ options: context.options,
236
+ db: dbContext.db,
237
+ databaseAdapter: dbContext.databaseAdapter,
238
+ });
239
+
240
+ // Create implicit dependencies
241
+ const createUow = () => dbContext.db.createUnitOfWork();
242
+ const implicitDeps: ImplicitDatabaseDependencies<TSchema> = {
243
+ db: dbContext.db,
244
+ schema: this.#schema,
245
+ namespace: this.#namespace,
246
+ createUnitOfWork: createUow,
247
+ };
248
+
249
+ return {
250
+ ...userDeps,
251
+ ...implicitDeps,
252
+ };
253
+ };
254
+
255
+ // Create new base builder with wrapped function
256
+ const newBaseBuilder = this.#baseBuilder.withDependencies(wrappedFn);
257
+
258
+ // Return new database builder with updated base
259
+ return new DatabaseFragmentDefinitionBuilder(newBaseBuilder, this.#schema, this.#namespace);
260
+ }
261
+
262
+ providesBaseService<TNewService>(
263
+ fn: ServiceConstructorFn<
264
+ TConfig,
265
+ FragnoPublicConfigWithDatabase,
266
+ TDeps,
267
+ TServiceDependencies,
268
+ TPrivateServices,
269
+ TNewService,
270
+ TServiceThisContext
271
+ >,
272
+ ): DatabaseFragmentDefinitionBuilder<
273
+ TSchema,
274
+ TConfig,
275
+ TDeps,
276
+ TNewService,
277
+ TServices,
278
+ TServiceDependencies,
279
+ TPrivateServices,
280
+ TServiceThisContext,
281
+ THandlerThisContext
282
+ > {
283
+ const newBaseBuilder = this.#baseBuilder.providesBaseService<TNewService>(fn);
284
+
285
+ return new DatabaseFragmentDefinitionBuilder(newBaseBuilder, this.#schema, this.#namespace);
286
+ }
287
+
288
+ providesService<TServiceName extends string, TService>(
289
+ serviceName: TServiceName,
290
+ fn: ServiceConstructorFn<
291
+ TConfig,
292
+ FragnoPublicConfigWithDatabase,
293
+ TDeps,
294
+ TServiceDependencies,
295
+ TPrivateServices,
296
+ TService,
297
+ TServiceThisContext
298
+ >,
299
+ ): DatabaseFragmentDefinitionBuilder<
300
+ TSchema,
301
+ TConfig,
302
+ TDeps,
303
+ TBaseServices,
304
+ TServices & { [K in TServiceName]: TService },
305
+ TServiceDependencies,
306
+ TPrivateServices,
307
+ TServiceThisContext,
308
+ THandlerThisContext
309
+ > {
310
+ const newBaseBuilder = this.#baseBuilder.providesService<TServiceName, TService>(
311
+ serviceName,
312
+ fn,
313
+ );
314
+
315
+ return new DatabaseFragmentDefinitionBuilder(newBaseBuilder, this.#schema, this.#namespace);
316
+ }
317
+
318
+ /**
319
+ * Declare that this fragment uses a required service provided by the runtime.
320
+ * Delegates to the base builder.
321
+ */
322
+ usesService<TServiceName extends string, TService>(
323
+ serviceName: TServiceName,
324
+ ): DatabaseFragmentDefinitionBuilder<
325
+ TSchema,
326
+ TConfig,
327
+ TDeps,
328
+ TBaseServices,
329
+ TServices,
330
+ TServiceDependencies & { [K in TServiceName]: TService },
331
+ TPrivateServices,
332
+ TServiceThisContext,
333
+ THandlerThisContext
334
+ > {
335
+ const newBaseBuilder = this.#baseBuilder.usesService<TServiceName, TService>(serviceName);
336
+
337
+ return new DatabaseFragmentDefinitionBuilder(newBaseBuilder, this.#schema, this.#namespace);
338
+ }
339
+
340
+ /**
341
+ * Declare that this fragment uses an optional service provided by the runtime.
342
+ * Delegates to the base builder.
343
+ */
344
+ usesOptionalService<TServiceName extends string, TService>(
345
+ serviceName: TServiceName,
346
+ ): DatabaseFragmentDefinitionBuilder<
347
+ TSchema,
348
+ TConfig,
349
+ TDeps,
350
+ TBaseServices,
351
+ TServices,
352
+ TServiceDependencies & { [K in TServiceName]: TService | undefined },
353
+ TPrivateServices,
354
+ TServiceThisContext,
355
+ THandlerThisContext
356
+ > {
357
+ const newBaseBuilder = this.#baseBuilder.usesOptionalService<TServiceName, TService>(
358
+ serviceName,
359
+ );
360
+
361
+ return new DatabaseFragmentDefinitionBuilder(newBaseBuilder, this.#schema, this.#namespace);
362
+ }
363
+
364
+ /**
365
+ * Build the final database fragment definition.
366
+ * This includes the request context setup for UnitOfWork management.
367
+ * Note: TDeps already includes ImplicitDatabaseDependencies from withDatabase().
368
+ */
369
+ build(): FragmentDefinition<
370
+ TConfig,
371
+ FragnoPublicConfigWithDatabase,
372
+ TDeps,
373
+ TBaseServices,
374
+ TServices,
375
+ TServiceDependencies,
376
+ TPrivateServices,
377
+ DatabaseServiceContext,
378
+ DatabaseHandlerContext,
379
+ DatabaseRequestStorage
380
+ > {
381
+ // Ensure dependencies callback always exists for database fragments
382
+ // If no user dependencies were defined, create a minimal one that only adds implicit deps
383
+ const dependencies = (context: {
384
+ config: TConfig;
385
+ options: FragnoPublicConfigWithDatabase;
386
+ }): TDeps => {
387
+ const baseDef = this.#baseBuilder.build();
388
+ const userDeps = baseDef.dependencies?.(context);
389
+
390
+ const { db } = createDatabaseContext(context.options, this.#schema, this.#namespace);
391
+
392
+ const implicitDeps: ImplicitDatabaseDependencies<TSchema> = {
393
+ db,
394
+ schema: this.#schema,
395
+ namespace: this.#namespace,
396
+ createUnitOfWork: () => db.createUnitOfWork(),
397
+ };
398
+
399
+ return {
400
+ ...userDeps,
401
+ ...implicitDeps,
402
+ } as TDeps;
403
+ };
404
+
405
+ // Use the adapter's shared context storage (all fragments using the same adapter share this storage)
406
+ const builderWithExternalStorage = this.#baseBuilder.withExternalRequestStorage(
407
+ ({ options }) => {
408
+ const dbContext = createDatabaseContext(options, this.#schema, this.#namespace);
409
+ return dbContext.databaseAdapter.contextStorage;
410
+ },
411
+ );
412
+
413
+ // Set up request storage to initialize the Unit of Work
414
+ const builderWithStorage = builderWithExternalStorage.withRequestStorage(
415
+ ({ options }): DatabaseRequestStorage => {
416
+ // Create database context - needed here to create the UOW
417
+ const dbContextForStorage = createDatabaseContext(options, this.#schema, this.#namespace);
418
+
419
+ // Create a new Unit of Work for this request
420
+ const uow: IUnitOfWork = dbContextForStorage.db.createUnitOfWork();
421
+
422
+ return { uow };
423
+ },
424
+ );
425
+
426
+ // Services get restricted context (no execute methods), handlers get execution context
427
+ const builderWithContext = builderWithStorage.withThisContext<
428
+ DatabaseServiceContext,
429
+ DatabaseHandlerContext
430
+ >(({ storage }) => {
431
+ // Service context - forSchema method to get restricted typed UOW
432
+ function forSchema<TSchema extends AnySchema>(
433
+ schema: TSchema,
434
+ ): TypedUnitOfWork<TSchema, [], unknown> {
435
+ const uow = storage.getStore()?.uow;
436
+ if (!uow) {
437
+ throw new Error(
438
+ "No UnitOfWork in context. Service must be called within a route handler OR using `withUnitOfWork`.",
439
+ );
440
+ }
441
+
442
+ // Return typed view of restricted UOW
443
+ return uow.restrict().forSchema(schema);
444
+ }
445
+
446
+ const serviceContext: DatabaseServiceContext = {
447
+ uow: forSchema,
448
+ };
449
+
450
+ // Handler context - only executeRestrictedUnitOfWork
451
+ async function uow<TResult>(
452
+ callback: (context: {
453
+ forSchema: <S extends AnySchema>(schema: S) => TypedUnitOfWork<S, [], unknown>;
454
+ executeRetrieve: () => Promise<void>;
455
+ executeMutate: () => Promise<void>;
456
+ nonce: string;
457
+ currentAttempt: number;
458
+ }) => Promise<TResult> | TResult,
459
+ options?: Omit<ExecuteRestrictedUnitOfWorkOptions, "createUnitOfWork">,
460
+ ): Promise<AwaitedPromisesInObject<TResult>> {
461
+ const currentStorage = storage.getStore();
462
+ if (!currentStorage) {
463
+ throw new Error(
464
+ "No storage in context. Handler must be called within a request context.",
465
+ );
466
+ }
467
+
468
+ // Wrap callback to ensure it always returns a Promise
469
+ const wrappedCallback = async (context: {
470
+ forSchema: <S extends AnySchema>(schema: S) => TypedUnitOfWork<S, [], unknown>;
471
+ executeRetrieve: () => Promise<void>;
472
+ executeMutate: () => Promise<void>;
473
+ nonce: string;
474
+ currentAttempt: number;
475
+ }): Promise<TResult> => {
476
+ return await callback(context);
477
+ };
478
+
479
+ // Use the UOW from storage - reset it before each attempt for retry support
480
+ // Cast is safe because IUnitOfWork is actually implemented by UnitOfWork
481
+ return executeRestrictedUnitOfWork(wrappedCallback, {
482
+ ...options,
483
+ createUnitOfWork: () => {
484
+ currentStorage.uow.reset();
485
+ return currentStorage.uow as UnitOfWork;
486
+ },
487
+ });
488
+ }
489
+
490
+ const handlerContext: DatabaseHandlerContext = {
491
+ uow,
492
+ };
493
+
494
+ return { serviceContext, handlerContext };
495
+ });
496
+
497
+ // Build the final definition
498
+ const finalDef = builderWithContext.build();
499
+
500
+ // Return the complete definition with proper typing and dependencies
501
+ return {
502
+ ...finalDef,
503
+ dependencies,
504
+ };
505
+ }
506
+ }