@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,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
|
+
}
|