@fragno-dev/db 0.1.14 → 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 -139
- package/CHANGELOG.md +24 -0
- package/dist/adapters/adapters.d.ts +15 -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 +3 -1
- package/dist/adapters/drizzle/drizzle-adapter.d.ts.map +1 -1
- package/dist/adapters/drizzle/drizzle-adapter.js +9 -2
- package/dist/adapters/drizzle/drizzle-adapter.js.map +1 -1
- package/dist/adapters/drizzle/drizzle-query.js +2 -2
- package/dist/adapters/drizzle/drizzle-query.js.map +1 -1
- package/dist/adapters/drizzle/drizzle-uow-compiler.js +27 -8
- package/dist/adapters/drizzle/drizzle-uow-compiler.js.map +1 -1
- package/dist/adapters/drizzle/drizzle-uow-decoder.js +22 -15
- 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/kysely/kysely-adapter.d.ts +3 -1
- package/dist/adapters/kysely/kysely-adapter.d.ts.map +1 -1
- package/dist/adapters/kysely/kysely-adapter.js +7 -1
- 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 +25 -18
- package/dist/adapters/kysely/kysely-query.js.map +1 -1
- package/dist/adapters/kysely/kysely-shared.d.ts +3 -0
- package/dist/adapters/kysely/kysely-shared.d.ts.map +1 -1
- 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 +34 -11
- 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 -20
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +7 -35
- 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 +2 -2
- 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 +104 -50
- package/dist/query/unit-of-work.d.ts.map +1 -1
- package/dist/query/unit-of-work.js +384 -194
- 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 +9 -2
- package/src/adapters/adapters.ts +16 -0
- package/src/adapters/drizzle/drizzle-adapter-pglite.test.ts +80 -16
- package/src/adapters/drizzle/drizzle-adapter-sqlite.test.ts +158 -2
- package/src/adapters/drizzle/drizzle-adapter.test.ts +3 -51
- package/src/adapters/drizzle/drizzle-adapter.ts +20 -7
- package/src/adapters/drizzle/drizzle-query.ts +1 -2
- 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 +21 -4
- package/src/adapters/drizzle/drizzle-uow-compiler.ts +44 -3
- package/src/adapters/drizzle/drizzle-uow-decoder.ts +32 -22
- 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 +64 -20
- package/src/adapters/kysely/kysely-adapter-sqlite.test.ts +156 -0
- package/src/adapters/kysely/kysely-adapter.ts +9 -1
- package/src/adapters/kysely/kysely-query-compiler.ts +3 -8
- package/src/adapters/kysely/kysely-query.ts +34 -25
- package/src/adapters/kysely/kysely-shared.ts +34 -0
- package/src/adapters/kysely/kysely-uow-compiler.test.ts +61 -73
- package/src/adapters/kysely/kysely-uow-compiler.ts +44 -12
- package/src/adapters/kysely/kysely-uow-executor.ts +26 -7
- package/src/adapters/kysely/kysely-uow-joins.test.ts +31 -48
- 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 +25 -52
- 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 +2 -2
- 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 +2 -2
- package/src/query/unit-of-work.test.ts +873 -191
- package/src/query/unit-of-work.ts +602 -409
- package/src/schema/serialize.ts +22 -11
- package/src/with-database.ts +140 -0
- package/tsdown.config.ts +1 -0
- package/dist/bind-services.d.ts +0 -7
- package/dist/bind-services.d.ts.map +0 -1
- package/dist/bind-services.js +0 -14
- package/dist/bind-services.js.map +0 -1
- package/dist/fragment.d.ts +0 -173
- package/dist/fragment.d.ts.map +0 -1
- package/dist/fragment.js +0 -191
- 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/bind-services.test.ts +0 -214
- package/src/bind-services.ts +0 -37
- package/src/db-fragment.test.ts +0 -800
- package/src/fragment.ts +0 -727
- package/src/query/unit-of-work-multi-schema.test.ts +0 -64
- package/src/shared/settings-schema.ts +0 -61
- package/src/uow-context-integration.test.ts +0 -102
- package/src/uow-context.test.ts +0 -182
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import "./query/unit-of-work.js";
|
|
2
|
+
import { executeRestrictedUnitOfWork } from "./query/execute-unit-of-work.js";
|
|
3
|
+
|
|
4
|
+
//#region src/db-fragment-definition-builder.ts
|
|
5
|
+
/**
|
|
6
|
+
* Create database context from options.
|
|
7
|
+
* This extracts the database adapter and creates the ORM instance.
|
|
8
|
+
*/
|
|
9
|
+
function createDatabaseContext(options, schema, namespace) {
|
|
10
|
+
const databaseAdapter = options.databaseAdapter;
|
|
11
|
+
if (!databaseAdapter) throw new Error("Database fragment requires a database adapter to be provided in options.databaseAdapter");
|
|
12
|
+
return {
|
|
13
|
+
databaseAdapter,
|
|
14
|
+
db: databaseAdapter.createQueryEngine(schema, namespace)
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Builder for database fragments that wraps the core fragment builder
|
|
19
|
+
* and provides database-specific functionality.
|
|
20
|
+
*
|
|
21
|
+
* Database fragments always require FragnoPublicConfigWithDatabase (which includes databaseAdapter).
|
|
22
|
+
*/
|
|
23
|
+
var DatabaseFragmentDefinitionBuilder = class DatabaseFragmentDefinitionBuilder {
|
|
24
|
+
#baseBuilder;
|
|
25
|
+
#schema;
|
|
26
|
+
#namespace;
|
|
27
|
+
constructor(baseBuilder, schema, namespace) {
|
|
28
|
+
this.#baseBuilder = baseBuilder;
|
|
29
|
+
this.#schema = schema;
|
|
30
|
+
this.#namespace = namespace ?? baseBuilder.name + "-db";
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Define dependencies for this database fragment.
|
|
34
|
+
* The context includes database adapter and ORM instance.
|
|
35
|
+
*/
|
|
36
|
+
withDependencies(fn) {
|
|
37
|
+
const wrappedFn = (context) => {
|
|
38
|
+
const dbContext = createDatabaseContext(context.options, this.#schema, this.#namespace);
|
|
39
|
+
const userDeps = fn({
|
|
40
|
+
config: context.config,
|
|
41
|
+
options: context.options,
|
|
42
|
+
db: dbContext.db,
|
|
43
|
+
databaseAdapter: dbContext.databaseAdapter
|
|
44
|
+
});
|
|
45
|
+
const createUow = () => dbContext.db.createUnitOfWork();
|
|
46
|
+
const implicitDeps = {
|
|
47
|
+
db: dbContext.db,
|
|
48
|
+
schema: this.#schema,
|
|
49
|
+
namespace: this.#namespace,
|
|
50
|
+
createUnitOfWork: createUow
|
|
51
|
+
};
|
|
52
|
+
return {
|
|
53
|
+
...userDeps,
|
|
54
|
+
...implicitDeps
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
return new DatabaseFragmentDefinitionBuilder(this.#baseBuilder.withDependencies(wrappedFn), this.#schema, this.#namespace);
|
|
58
|
+
}
|
|
59
|
+
providesBaseService(fn) {
|
|
60
|
+
return new DatabaseFragmentDefinitionBuilder(this.#baseBuilder.providesBaseService(fn), this.#schema, this.#namespace);
|
|
61
|
+
}
|
|
62
|
+
providesService(serviceName, fn) {
|
|
63
|
+
return new DatabaseFragmentDefinitionBuilder(this.#baseBuilder.providesService(serviceName, fn), this.#schema, this.#namespace);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Declare that this fragment uses a required service provided by the runtime.
|
|
67
|
+
* Delegates to the base builder.
|
|
68
|
+
*/
|
|
69
|
+
usesService(serviceName) {
|
|
70
|
+
return new DatabaseFragmentDefinitionBuilder(this.#baseBuilder.usesService(serviceName), this.#schema, this.#namespace);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Declare that this fragment uses an optional service provided by the runtime.
|
|
74
|
+
* Delegates to the base builder.
|
|
75
|
+
*/
|
|
76
|
+
usesOptionalService(serviceName) {
|
|
77
|
+
return new DatabaseFragmentDefinitionBuilder(this.#baseBuilder.usesOptionalService(serviceName), this.#schema, this.#namespace);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Build the final database fragment definition.
|
|
81
|
+
* This includes the request context setup for UnitOfWork management.
|
|
82
|
+
* Note: TDeps already includes ImplicitDatabaseDependencies from withDatabase().
|
|
83
|
+
*/
|
|
84
|
+
build() {
|
|
85
|
+
const dependencies = (context) => {
|
|
86
|
+
const userDeps = this.#baseBuilder.build().dependencies?.(context);
|
|
87
|
+
const { db } = createDatabaseContext(context.options, this.#schema, this.#namespace);
|
|
88
|
+
const implicitDeps = {
|
|
89
|
+
db,
|
|
90
|
+
schema: this.#schema,
|
|
91
|
+
namespace: this.#namespace,
|
|
92
|
+
createUnitOfWork: () => db.createUnitOfWork()
|
|
93
|
+
};
|
|
94
|
+
return {
|
|
95
|
+
...userDeps,
|
|
96
|
+
...implicitDeps
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
return {
|
|
100
|
+
...this.#baseBuilder.withExternalRequestStorage(({ options }) => {
|
|
101
|
+
return createDatabaseContext(options, this.#schema, this.#namespace).databaseAdapter.contextStorage;
|
|
102
|
+
}).withRequestStorage(({ options }) => {
|
|
103
|
+
return { uow: createDatabaseContext(options, this.#schema, this.#namespace).db.createUnitOfWork() };
|
|
104
|
+
}).withThisContext(({ storage }) => {
|
|
105
|
+
function forSchema(schema) {
|
|
106
|
+
const uow$1 = storage.getStore()?.uow;
|
|
107
|
+
if (!uow$1) throw new Error("No UnitOfWork in context. Service must be called within a route handler OR using `withUnitOfWork`.");
|
|
108
|
+
return uow$1.restrict().forSchema(schema);
|
|
109
|
+
}
|
|
110
|
+
const serviceContext = { uow: forSchema };
|
|
111
|
+
async function uow(callback, options) {
|
|
112
|
+
const currentStorage = storage.getStore();
|
|
113
|
+
if (!currentStorage) throw new Error("No storage in context. Handler must be called within a request context.");
|
|
114
|
+
const wrappedCallback = async (context) => {
|
|
115
|
+
return await callback(context);
|
|
116
|
+
};
|
|
117
|
+
return executeRestrictedUnitOfWork(wrappedCallback, {
|
|
118
|
+
...options,
|
|
119
|
+
createUnitOfWork: () => {
|
|
120
|
+
currentStorage.uow.reset();
|
|
121
|
+
return currentStorage.uow;
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
serviceContext,
|
|
127
|
+
handlerContext: { uow }
|
|
128
|
+
};
|
|
129
|
+
}).build(),
|
|
130
|
+
dependencies
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
//#endregion
|
|
136
|
+
export { DatabaseFragmentDefinitionBuilder };
|
|
137
|
+
//# sourceMappingURL=db-fragment-definition-builder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db-fragment-definition-builder.js","names":["#baseBuilder","#schema","#namespace","implicitDeps: ImplicitDatabaseDependencies<TSchema>","uow","serviceContext: DatabaseServiceContext"],"sources":["../src/db-fragment-definition-builder.ts"],"sourcesContent":["import type { AnySchema } from \"./schema/create\";\nimport type { AbstractQuery } from \"./query/query\";\nimport type { DatabaseAdapter } from \"./adapters/adapters\";\nimport type { IUnitOfWork } from \"./query/unit-of-work\";\nimport { TypedUnitOfWork, UnitOfWork } from \"./query/unit-of-work\";\nimport type { RequestThisContext, FragnoPublicConfig } from \"@fragno-dev/core\";\nimport {\n FragmentDefinitionBuilder,\n type FragmentDefinition,\n type ServiceConstructorFn,\n} from \"@fragno-dev/core\";\nimport {\n executeRestrictedUnitOfWork,\n type AwaitedPromisesInObject,\n type ExecuteRestrictedUnitOfWorkOptions,\n} from \"./query/execute-unit-of-work\";\n\n/**\n * Extended FragnoPublicConfig that includes a database adapter.\n * Use this type when creating fragments with database support.\n */\nexport type FragnoPublicConfigWithDatabase = FragnoPublicConfig & {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n databaseAdapter: DatabaseAdapter<any>;\n};\n\n/**\n * Implicit dependencies that database fragments get automatically.\n * These are injected without requiring explicit configuration.\n */\nexport type ImplicitDatabaseDependencies<TSchema extends AnySchema> = {\n /**\n * Database query engine for the fragment's schema.\n */\n db: AbstractQuery<TSchema>;\n /**\n * The schema definition for this fragment.\n */\n schema: TSchema;\n /**\n * The database namespace for this fragment.\n */\n namespace: string;\n /**\n * Create a new Unit of Work for database operations.\n */\n createUnitOfWork: () => IUnitOfWork;\n};\n\n/**\n * Service context for database fragments - provides restricted UOW access without execute methods.\n */\nexport type DatabaseServiceContext = RequestThisContext & {\n /**\n * Get a typed, restricted Unit of Work for the given schema.\n * @param schema - Schema to get a typed view for\n * @returns TypedUnitOfWork (restricted version without execute methods)\n */\n uow<TSchema extends AnySchema>(schema: TSchema): TypedUnitOfWork<TSchema, [], unknown>;\n};\n\n/**\n * Handler context for database fragments - provides UOW execution with automatic retry support.\n */\nexport type DatabaseHandlerContext = RequestThisContext & {\n /**\n * Execute a Unit of Work with explicit phase control and automatic retry support.\n * This method provides an API where users call forSchema to create a schema-specific\n * UOW, then call executeRetrieve() and executeMutate() to execute the phases. The entire\n * callback is re-executed on optimistic concurrency conflicts, ensuring retries work properly.\n * Automatically provides the UOW factory from context.\n * Promises in the returned object are awaited 1 level deep.\n *\n * @param callback - Async function that receives a context with forSchema, executeRetrieve, executeMutate, nonce, and currentAttempt\n * @param options - Optional configuration for retry policy and abort signal\n * @returns Promise resolving to the callback's return value with promises awaited 1 level deep\n * @throws Error if retries are exhausted or callback throws an error\n *\n * @example\n * ```ts\n * const result = await this.uow(async ({ forSchema, executeRetrieve, executeMutate, nonce, currentAttempt }) => {\n * const uow = forSchema(schema);\n * const userId = uow.create(\"users\", { name: \"John\" });\n *\n * // Execute retrieval phase\n * await executeRetrieve();\n *\n * const profileId = uow.create(\"profiles\", { userId });\n *\n * // Execute mutation phase\n * await executeMutate();\n *\n * return { userId, profileId };\n * });\n * ```\n */\n uow<TResult>(\n callback: (context: {\n forSchema: <S extends AnySchema>(schema: S) => TypedUnitOfWork<S, [], unknown>;\n executeRetrieve: () => Promise<void>;\n executeMutate: () => Promise<void>;\n nonce: string;\n currentAttempt: number;\n }) => Promise<TResult> | TResult,\n options?: Omit<ExecuteRestrictedUnitOfWorkOptions, \"createUnitOfWork\">,\n ): Promise<AwaitedPromisesInObject<TResult>>;\n};\n\n/**\n * Database fragment context provided to user callbacks.\n */\nexport type DatabaseFragmentContext<TSchema extends AnySchema> = {\n /**\n * Database adapter instance.\n */\n databaseAdapter: DatabaseAdapter<any>; // eslint-disable-line @typescript-eslint/no-explicit-any\n /**\n * ORM query engine for the fragment's schema.\n */\n db: AbstractQuery<TSchema>;\n};\n\n/**\n * Create database context from options.\n * This extracts the database adapter and creates the ORM instance.\n */\nfunction createDatabaseContext<TSchema extends AnySchema>(\n options: FragnoPublicConfigWithDatabase,\n schema: TSchema,\n namespace: string,\n): DatabaseFragmentContext<TSchema> {\n const databaseAdapter = options.databaseAdapter;\n\n if (!databaseAdapter) {\n throw new Error(\n \"Database fragment requires a database adapter to be provided in options.databaseAdapter\",\n );\n }\n\n const db = databaseAdapter.createQueryEngine(schema, namespace);\n\n return { databaseAdapter, db };\n}\n\n/**\n * Storage type for database fragments - stores the Unit of Work.\n */\nexport type DatabaseRequestStorage = {\n uow: IUnitOfWork;\n};\n\n/**\n * Builder for database fragments that wraps the core fragment builder\n * and provides database-specific functionality.\n *\n * Database fragments always require FragnoPublicConfigWithDatabase (which includes databaseAdapter).\n */\nexport class DatabaseFragmentDefinitionBuilder<\n TSchema extends AnySchema,\n TConfig,\n TDeps,\n TBaseServices,\n TServices,\n TServiceDependencies,\n TPrivateServices,\n TServiceThisContext extends RequestThisContext = DatabaseHandlerContext,\n THandlerThisContext extends RequestThisContext = DatabaseHandlerContext,\n> {\n // Store the base builder - we'll replace its storage and context setup when building\n #baseBuilder: FragmentDefinitionBuilder<\n TConfig,\n FragnoPublicConfigWithDatabase,\n TDeps,\n TBaseServices,\n TServices,\n TServiceDependencies,\n TPrivateServices,\n TServiceThisContext,\n THandlerThisContext,\n DatabaseRequestStorage\n >;\n #schema: TSchema;\n #namespace: string;\n\n constructor(\n baseBuilder: FragmentDefinitionBuilder<\n TConfig,\n FragnoPublicConfigWithDatabase,\n TDeps,\n TBaseServices,\n TServices,\n TServiceDependencies,\n TPrivateServices,\n TServiceThisContext,\n THandlerThisContext,\n DatabaseRequestStorage\n >,\n schema: TSchema,\n namespace?: string,\n ) {\n this.#baseBuilder = baseBuilder;\n this.#schema = schema;\n this.#namespace = namespace ?? baseBuilder.name + \"-db\";\n }\n\n /**\n * Define dependencies for this database fragment.\n * The context includes database adapter and ORM instance.\n */\n withDependencies<TNewDeps>(\n fn: (context: {\n config: TConfig;\n options: FragnoPublicConfigWithDatabase;\n db: AbstractQuery<TSchema>;\n databaseAdapter: DatabaseAdapter<any>; // eslint-disable-line @typescript-eslint/no-explicit-any\n }) => TNewDeps,\n ): DatabaseFragmentDefinitionBuilder<\n TSchema,\n TConfig,\n TNewDeps & ImplicitDatabaseDependencies<TSchema>,\n {},\n {},\n TServiceDependencies,\n {},\n TServiceThisContext,\n THandlerThisContext\n > {\n // Wrap user function to inject DB context\n const wrappedFn = (context: { config: TConfig; options: FragnoPublicConfigWithDatabase }) => {\n const dbContext = createDatabaseContext(context.options, this.#schema, this.#namespace);\n\n // Call user function with enriched context\n const userDeps = fn({\n config: context.config,\n options: context.options,\n db: dbContext.db,\n databaseAdapter: dbContext.databaseAdapter,\n });\n\n // Create implicit dependencies\n const createUow = () => dbContext.db.createUnitOfWork();\n const implicitDeps: ImplicitDatabaseDependencies<TSchema> = {\n db: dbContext.db,\n schema: this.#schema,\n namespace: this.#namespace,\n createUnitOfWork: createUow,\n };\n\n return {\n ...userDeps,\n ...implicitDeps,\n };\n };\n\n // Create new base builder with wrapped function\n const newBaseBuilder = this.#baseBuilder.withDependencies(wrappedFn);\n\n // Return new database builder with updated base\n return new DatabaseFragmentDefinitionBuilder(newBaseBuilder, this.#schema, this.#namespace);\n }\n\n providesBaseService<TNewService>(\n fn: ServiceConstructorFn<\n TConfig,\n FragnoPublicConfigWithDatabase,\n TDeps,\n TServiceDependencies,\n TPrivateServices,\n TNewService,\n TServiceThisContext\n >,\n ): DatabaseFragmentDefinitionBuilder<\n TSchema,\n TConfig,\n TDeps,\n TNewService,\n TServices,\n TServiceDependencies,\n TPrivateServices,\n TServiceThisContext,\n THandlerThisContext\n > {\n const newBaseBuilder = this.#baseBuilder.providesBaseService<TNewService>(fn);\n\n return new DatabaseFragmentDefinitionBuilder(newBaseBuilder, this.#schema, this.#namespace);\n }\n\n providesService<TServiceName extends string, TService>(\n serviceName: TServiceName,\n fn: ServiceConstructorFn<\n TConfig,\n FragnoPublicConfigWithDatabase,\n TDeps,\n TServiceDependencies,\n TPrivateServices,\n TService,\n TServiceThisContext\n >,\n ): DatabaseFragmentDefinitionBuilder<\n TSchema,\n TConfig,\n TDeps,\n TBaseServices,\n TServices & { [K in TServiceName]: TService },\n TServiceDependencies,\n TPrivateServices,\n TServiceThisContext,\n THandlerThisContext\n > {\n const newBaseBuilder = this.#baseBuilder.providesService<TServiceName, TService>(\n serviceName,\n fn,\n );\n\n return new DatabaseFragmentDefinitionBuilder(newBaseBuilder, this.#schema, this.#namespace);\n }\n\n /**\n * Declare that this fragment uses a required service provided by the runtime.\n * Delegates to the base builder.\n */\n usesService<TServiceName extends string, TService>(\n serviceName: TServiceName,\n ): DatabaseFragmentDefinitionBuilder<\n TSchema,\n TConfig,\n TDeps,\n TBaseServices,\n TServices,\n TServiceDependencies & { [K in TServiceName]: TService },\n TPrivateServices,\n TServiceThisContext,\n THandlerThisContext\n > {\n const newBaseBuilder = this.#baseBuilder.usesService<TServiceName, TService>(serviceName);\n\n return new DatabaseFragmentDefinitionBuilder(newBaseBuilder, this.#schema, this.#namespace);\n }\n\n /**\n * Declare that this fragment uses an optional service provided by the runtime.\n * Delegates to the base builder.\n */\n usesOptionalService<TServiceName extends string, TService>(\n serviceName: TServiceName,\n ): DatabaseFragmentDefinitionBuilder<\n TSchema,\n TConfig,\n TDeps,\n TBaseServices,\n TServices,\n TServiceDependencies & { [K in TServiceName]: TService | undefined },\n TPrivateServices,\n TServiceThisContext,\n THandlerThisContext\n > {\n const newBaseBuilder = this.#baseBuilder.usesOptionalService<TServiceName, TService>(\n serviceName,\n );\n\n return new DatabaseFragmentDefinitionBuilder(newBaseBuilder, this.#schema, this.#namespace);\n }\n\n /**\n * Build the final database fragment definition.\n * This includes the request context setup for UnitOfWork management.\n * Note: TDeps already includes ImplicitDatabaseDependencies from withDatabase().\n */\n build(): FragmentDefinition<\n TConfig,\n FragnoPublicConfigWithDatabase,\n TDeps,\n TBaseServices,\n TServices,\n TServiceDependencies,\n TPrivateServices,\n DatabaseServiceContext,\n DatabaseHandlerContext,\n DatabaseRequestStorage\n > {\n // Ensure dependencies callback always exists for database fragments\n // If no user dependencies were defined, create a minimal one that only adds implicit deps\n const dependencies = (context: {\n config: TConfig;\n options: FragnoPublicConfigWithDatabase;\n }): TDeps => {\n const baseDef = this.#baseBuilder.build();\n const userDeps = baseDef.dependencies?.(context);\n\n const { db } = createDatabaseContext(context.options, this.#schema, this.#namespace);\n\n const implicitDeps: ImplicitDatabaseDependencies<TSchema> = {\n db,\n schema: this.#schema,\n namespace: this.#namespace,\n createUnitOfWork: () => db.createUnitOfWork(),\n };\n\n return {\n ...userDeps,\n ...implicitDeps,\n } as TDeps;\n };\n\n // Use the adapter's shared context storage (all fragments using the same adapter share this storage)\n const builderWithExternalStorage = this.#baseBuilder.withExternalRequestStorage(\n ({ options }) => {\n const dbContext = createDatabaseContext(options, this.#schema, this.#namespace);\n return dbContext.databaseAdapter.contextStorage;\n },\n );\n\n // Set up request storage to initialize the Unit of Work\n const builderWithStorage = builderWithExternalStorage.withRequestStorage(\n ({ options }): DatabaseRequestStorage => {\n // Create database context - needed here to create the UOW\n const dbContextForStorage = createDatabaseContext(options, this.#schema, this.#namespace);\n\n // Create a new Unit of Work for this request\n const uow: IUnitOfWork = dbContextForStorage.db.createUnitOfWork();\n\n return { uow };\n },\n );\n\n // Services get restricted context (no execute methods), handlers get execution context\n const builderWithContext = builderWithStorage.withThisContext<\n DatabaseServiceContext,\n DatabaseHandlerContext\n >(({ storage }) => {\n // Service context - forSchema method to get restricted typed UOW\n function forSchema<TSchema extends AnySchema>(\n schema: TSchema,\n ): TypedUnitOfWork<TSchema, [], unknown> {\n const uow = storage.getStore()?.uow;\n if (!uow) {\n throw new Error(\n \"No UnitOfWork in context. Service must be called within a route handler OR using `withUnitOfWork`.\",\n );\n }\n\n // Return typed view of restricted UOW\n return uow.restrict().forSchema(schema);\n }\n\n const serviceContext: DatabaseServiceContext = {\n uow: forSchema,\n };\n\n // Handler context - only executeRestrictedUnitOfWork\n async function uow<TResult>(\n callback: (context: {\n forSchema: <S extends AnySchema>(schema: S) => TypedUnitOfWork<S, [], unknown>;\n executeRetrieve: () => Promise<void>;\n executeMutate: () => Promise<void>;\n nonce: string;\n currentAttempt: number;\n }) => Promise<TResult> | TResult,\n options?: Omit<ExecuteRestrictedUnitOfWorkOptions, \"createUnitOfWork\">,\n ): Promise<AwaitedPromisesInObject<TResult>> {\n const currentStorage = storage.getStore();\n if (!currentStorage) {\n throw new Error(\n \"No storage in context. Handler must be called within a request context.\",\n );\n }\n\n // Wrap callback to ensure it always returns a Promise\n const wrappedCallback = async (context: {\n forSchema: <S extends AnySchema>(schema: S) => TypedUnitOfWork<S, [], unknown>;\n executeRetrieve: () => Promise<void>;\n executeMutate: () => Promise<void>;\n nonce: string;\n currentAttempt: number;\n }): Promise<TResult> => {\n return await callback(context);\n };\n\n // Use the UOW from storage - reset it before each attempt for retry support\n // Cast is safe because IUnitOfWork is actually implemented by UnitOfWork\n return executeRestrictedUnitOfWork(wrappedCallback, {\n ...options,\n createUnitOfWork: () => {\n currentStorage.uow.reset();\n return currentStorage.uow as UnitOfWork;\n },\n });\n }\n\n const handlerContext: DatabaseHandlerContext = {\n uow,\n };\n\n return { serviceContext, handlerContext };\n });\n\n // Build the final definition\n const finalDef = builderWithContext.build();\n\n // Return the complete definition with proper typing and dependencies\n return {\n ...finalDef,\n dependencies,\n };\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { AnyColumn, AnyRelation, Column, FragnoId, IdColumn, Index, Schema, Table } from "../schema/create.js";
|
|
2
|
+
import { DatabaseHandlerContext, DatabaseRequestStorage, DatabaseServiceContext, FragnoPublicConfigWithDatabase, ImplicitDatabaseDependencies } from "../db-fragment-definition-builder.js";
|
|
3
|
+
import * as _fragno_dev_core0 from "@fragno-dev/core";
|
|
4
|
+
|
|
5
|
+
//#region src/fragments/internal-fragment.d.ts
|
|
6
|
+
declare const internalFragmentDef: _fragno_dev_core0.FragmentDefinition<{}, FragnoPublicConfigWithDatabase, ImplicitDatabaseDependencies<Schema<Record<"fragno_db_settings", Table<Record<string, AnyColumn> & Record<"id", IdColumn<"varchar(30)", string | FragnoId | null, FragnoId>> & Record<"key", Column<"string", string, string>> & Record<"value", Column<"string", string, string>>, Record<string, AnyRelation>, Record<string, Index<AnyColumn[], readonly string[]>> & Record<"unique_key", Index<readonly [Column<"string", string, string>] & AnyColumn[], readonly ["key"]>>>>>>, {}, {
|
|
7
|
+
settingsService: {
|
|
8
|
+
get(key: string): Promise<{
|
|
9
|
+
id: FragnoId;
|
|
10
|
+
key: string;
|
|
11
|
+
value: string;
|
|
12
|
+
} | undefined>;
|
|
13
|
+
set(key: string, value: string): Promise<void>;
|
|
14
|
+
delete(id: FragnoId): Promise<void>;
|
|
15
|
+
};
|
|
16
|
+
}, {}, {}, DatabaseServiceContext, DatabaseHandlerContext, DatabaseRequestStorage, {}>;
|
|
17
|
+
//#endregion
|
|
18
|
+
export { internalFragmentDef };
|
|
19
|
+
//# sourceMappingURL=internal-fragment.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"internal-fragment.d.ts","names":[],"sources":["../../src/fragments/internal-fragment.ts"],"sourcesContent":[],"mappings":";;;;;AA6Ba,cAAA,mBAuDH,oBAvDsB,kBAuDtB,CAAA,CAAA,CAAA,EAvDsB,8BAuDtB,EAvDsB,4BAuDtB,CAvDsB,MAuDtB,CAvDsB,MAuDtB,CAAA,oBAAA,EAvDsB,KAuDtB,CAvDsB,MAuDtB,CAAA,MAAA,EAvDsB,SAAA,CAuDtB,GAvDsB,MAuDtB,CAAA,IAAA,EAvDsB,QAuDtB,CAAA,aAAA,EAAA,MAAA,GAvDsB,QAuDtB,GAAA,IAAA,EAvDsB,QAuDtB,CAAA,CAAA,GAvDsB,MAuDtB,CAAA,KAAA,EAvDsB,MAuDtB,CAAA,QAAA,EAAA,MAAA,EAAA,MAAA,CAAA,CAAA,GAvDsB,MAuDtB,CAAA,OAAA,EAvDsB,MAuDtB,CAAA,QAAA,EAAA,MAAA,EAAA,MAAA,CAAA,CAAA,EAvDsB,MAuDtB,CAAA,MAAA,EAvDsB,WAAA,CAuDtB,EAvDsB,MAuDtB,CAAA,MAAA,EAvDsB,KAuDtB,CAvDsB,SAAA,EAuDtB,EAAA,SAAA,MAAA,EAAA,CAAA,CAAA,GAvDsB,MAuDtB,CAAA,YAAA,EAvDsB,KAuDtB,CAAA,SAAA,CAvDsB,MAuDtB,CAAA,QAAA,EAAA,MAAA,EAAA,MAAA,CAAA,CAAA,GAvDsB,SAAA,EAuDtB,EAAA,SAAA,CAAA,KAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,EAAA,CAAA,CAAA,EAAA;EAvDsB,eAAA,EAAA;IAAA,GAAA,CAAA,GAAA,EAAA,MAAA,CAAA,EAkBF,OAlBE,CAAA;MAAA,EAAA,EAkBY,QAlBZ;MAAA,GAAA,EAAA,MAAA;MAAA,KAAA,EAAA,MAAA;IAAA,CAAA,GAAA,SAAA,CAAA;IAAA,GAAA,CAAA,GAAA,EAAA,MAAA,EAAA,KAAA,EAAA,MAAA,CAAA,EA0BU,OA1BV,CAAA,IAAA,CAAA;IAAA,MAAA,CAAA,EAAA,EAgDT,QAhDS,CAAA,EAgDD,OAhDC,CAAA,IAAA,CAAA;EAAA,CAAA;CAAA,EAAA,CAAA,CAAA,EAAA,CAAA,CAAA,wBAAA,wBAAA,wBAAA,EAAA,CAAA,CAAA,CAAA"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { column, idColumn, schema } from "../schema/create.js";
|
|
2
|
+
import { FragmentDefinitionBuilder } from "../packages/fragno/dist/api/fragment-definition-builder.js";
|
|
3
|
+
import { DatabaseFragmentDefinitionBuilder } from "../db-fragment-definition-builder.js";
|
|
4
|
+
|
|
5
|
+
//#region src/fragments/internal-fragment.ts
|
|
6
|
+
const SETTINGS_TABLE_NAME = "fragno_db_settings";
|
|
7
|
+
const SETTINGS_NAMESPACE = "fragno-db-settings";
|
|
8
|
+
const settingsSchema = schema((s) => {
|
|
9
|
+
return s.addTable(SETTINGS_TABLE_NAME, (t) => {
|
|
10
|
+
return t.addColumn("id", idColumn()).addColumn("key", column("string")).addColumn("value", column("string")).createIndex("unique_key", ["key"], { unique: true });
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
const internalFragmentDef = new DatabaseFragmentDefinitionBuilder(new FragmentDefinitionBuilder("$fragno-internal-fragment"), settingsSchema, "").providesService("settingsService", ({ defineService }) => {
|
|
14
|
+
return defineService({
|
|
15
|
+
async get(key) {
|
|
16
|
+
const [results] = await this.uow(settingsSchema).find(SETTINGS_TABLE_NAME, (b) => b.whereIndex("unique_key", (eb) => eb("key", "=", `${SETTINGS_NAMESPACE}.${key}`))).retrievalPhase;
|
|
17
|
+
return results?.[0];
|
|
18
|
+
},
|
|
19
|
+
async set(key, value) {
|
|
20
|
+
const uow = this.uow(settingsSchema);
|
|
21
|
+
const [existing] = await uow.find(SETTINGS_TABLE_NAME, (b) => b.whereIndex("unique_key", (eb) => eb("key", "=", `${SETTINGS_NAMESPACE}.${key}`))).retrievalPhase;
|
|
22
|
+
if (existing?.[0]) uow.update(SETTINGS_TABLE_NAME, existing[0].id, (b) => b.set({ value }).check());
|
|
23
|
+
else uow.create(SETTINGS_TABLE_NAME, {
|
|
24
|
+
key: `${SETTINGS_NAMESPACE}.${key}`,
|
|
25
|
+
value
|
|
26
|
+
});
|
|
27
|
+
await uow.mutationPhase;
|
|
28
|
+
},
|
|
29
|
+
async delete(id) {
|
|
30
|
+
const uow = this.uow(settingsSchema);
|
|
31
|
+
uow.delete(SETTINGS_TABLE_NAME, id);
|
|
32
|
+
await uow.mutationPhase;
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}).build();
|
|
36
|
+
|
|
37
|
+
//#endregion
|
|
38
|
+
export { SETTINGS_NAMESPACE, SETTINGS_TABLE_NAME, internalFragmentDef, settingsSchema };
|
|
39
|
+
//# sourceMappingURL=internal-fragment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"internal-fragment.js","names":[],"sources":["../../src/fragments/internal-fragment.ts"],"sourcesContent":["import { FragmentDefinitionBuilder } from \"@fragno-dev/core\";\nimport {\n DatabaseFragmentDefinitionBuilder,\n type DatabaseHandlerContext,\n type DatabaseRequestStorage,\n type DatabaseServiceContext,\n type FragnoPublicConfigWithDatabase,\n type ImplicitDatabaseDependencies,\n} from \"../db-fragment-definition-builder\";\nimport type { FragnoId } from \"../schema/create\";\nimport { schema, idColumn, column } from \"../schema/create\";\n\n// Constants for Fragno's internal settings table\nexport const SETTINGS_TABLE_NAME = \"fragno_db_settings\" as const;\nexport const SETTINGS_NAMESPACE = \"fragno-db-settings\" as const;\n\n// Settings schema for storing Fragno's internal key-value settings\nexport const settingsSchema = schema((s) => {\n return s.addTable(SETTINGS_TABLE_NAME, (t) => {\n return t\n .addColumn(\"id\", idColumn())\n .addColumn(\"key\", column(\"string\"))\n .addColumn(\"value\", column(\"string\"))\n .createIndex(\"unique_key\", [\"key\"], { unique: true });\n });\n});\n\n// This uses DatabaseFragmentDefinitionBuilder directly\n// to avoid circular dependency (it doesn't need to link to itself)\nexport const internalFragmentDef = new DatabaseFragmentDefinitionBuilder(\n new FragmentDefinitionBuilder<\n {},\n FragnoPublicConfigWithDatabase,\n ImplicitDatabaseDependencies<typeof settingsSchema>,\n {},\n {},\n {},\n {},\n DatabaseServiceContext,\n DatabaseHandlerContext,\n DatabaseRequestStorage\n >(\"$fragno-internal-fragment\"),\n settingsSchema,\n \"\", // intentionally blank namespace so there is no prefix\n)\n .providesService(\"settingsService\", ({ defineService }) => {\n return defineService({\n async get(key: string): Promise<{ id: FragnoId; key: string; value: string } | undefined> {\n const uow = this.uow(settingsSchema).find(SETTINGS_TABLE_NAME, (b) =>\n b.whereIndex(\"unique_key\", (eb) => eb(\"key\", \"=\", `${SETTINGS_NAMESPACE}.${key}`)),\n );\n const [results] = await uow.retrievalPhase;\n return results?.[0];\n },\n\n async set(key: string, value: string) {\n const uow = this.uow(settingsSchema);\n\n // First, find if the key already exists\n const findUow = uow.find(SETTINGS_TABLE_NAME, (b) =>\n b.whereIndex(\"unique_key\", (eb) => eb(\"key\", \"=\", `${SETTINGS_NAMESPACE}.${key}`)),\n );\n const [existing] = await findUow.retrievalPhase;\n\n if (existing?.[0]) {\n uow.update(SETTINGS_TABLE_NAME, existing[0].id, (b) => b.set({ value }).check());\n } else {\n uow.create(SETTINGS_TABLE_NAME, {\n key: `${SETTINGS_NAMESPACE}.${key}`,\n value,\n });\n }\n\n // Await mutation phase - will throw if mutation fails\n await uow.mutationPhase;\n },\n\n async delete(id: FragnoId) {\n const uow = this.uow(settingsSchema);\n uow.delete(SETTINGS_TABLE_NAME, id);\n await uow.mutationPhase;\n },\n });\n })\n .build();\n"],"mappings":";;;;;AAaA,MAAa,sBAAsB;AACnC,MAAa,qBAAqB;AAGlC,MAAa,iBAAiB,QAAQ,MAAM;AAC1C,QAAO,EAAE,SAAS,sBAAsB,MAAM;AAC5C,SAAO,EACJ,UAAU,MAAM,UAAU,CAAC,CAC3B,UAAU,OAAO,OAAO,SAAS,CAAC,CAClC,UAAU,SAAS,OAAO,SAAS,CAAC,CACpC,YAAY,cAAc,CAAC,MAAM,EAAE,EAAE,QAAQ,MAAM,CAAC;GACvD;EACF;AAIF,MAAa,sBAAsB,IAAI,kCACrC,IAAI,0BAWF,4BAA4B,EAC9B,gBACA,GACD,CACE,gBAAgB,oBAAoB,EAAE,oBAAoB;AACzD,QAAO,cAAc;EACnB,MAAM,IAAI,KAAgF;GAIxF,MAAM,CAAC,WAAW,MAHN,KAAK,IAAI,eAAe,CAAC,KAAK,sBAAsB,MAC9D,EAAE,WAAW,eAAe,OAAO,GAAG,OAAO,KAAK,GAAG,mBAAmB,GAAG,MAAM,CAAC,CACnF,CAC2B;AAC5B,UAAO,UAAU;;EAGnB,MAAM,IAAI,KAAa,OAAe;GACpC,MAAM,MAAM,KAAK,IAAI,eAAe;GAMpC,MAAM,CAAC,YAAY,MAHH,IAAI,KAAK,sBAAsB,MAC7C,EAAE,WAAW,eAAe,OAAO,GAAG,OAAO,KAAK,GAAG,mBAAmB,GAAG,MAAM,CAAC,CACnF,CACgC;AAEjC,OAAI,WAAW,GACb,KAAI,OAAO,qBAAqB,SAAS,GAAG,KAAK,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC;OAEhF,KAAI,OAAO,qBAAqB;IAC9B,KAAK,GAAG,mBAAmB,GAAG;IAC9B;IACD,CAAC;AAIJ,SAAM,IAAI;;EAGZ,MAAM,OAAO,IAAc;GACzB,MAAM,MAAM,KAAK,IAAI,eAAe;AACpC,OAAI,OAAO,qBAAqB,GAAG;AACnC,SAAM,IAAI;;EAEb,CAAC;EACF,CACD,OAAO"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generation-engine.d.ts","names":[],"sources":["../../src/migration-engine/generation-engine.ts"],"sourcesContent":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"generation-engine.d.ts","names":[],"sources":["../../src/migration-engine/generation-engine.ts"],"sourcesContent":[],"mappings":";;;;;UAeiB,sBAAA;;EAAA,IAAA,EAAA,MAAA;EAMA,SAAA,EAAA,MAAA;AASjB;AAOsB,UAhBL,wBAAA,CAgB+B;EAEN,MAAA,EAAA,MAAA;EAAf,IAAA,EAAA,MAAA;EAEd,SAAA,EAAA,MAAA;EAMF,WAAA,EAAA,MAAA;EAAR,SAAA,EAAA,MAAA;EAAO,iBAAA,CAAA,EApBY,iBAoBZ;AA8KV;AAAgF,UA/L/D,sBAAA,CA+L+D;EAAf,SAAA,EAAA,MAAA;EACpD,UAAA,EAAA,OAAA;EACF,WAAA,EAAA,MAAA;EAAR,SAAA,EAAA,MAAA;;AA0Ja,iBApVM,0BAqVb,CAAA,yBAnVkB,cAoVxB,CApVuC,SAoVjB,EAAA,GAAA,CAAA,EAAA,CAAA,CAAA,SAAA,EAlVZ,UAkVY,EAAA,QAAA,EAAA;;;;IA5UtB,QAAQ;;;;;;;;iBA8KW,2CAA2C,eAAe,yBACnE,aACV,QAAQ;;;;;;;;;;;iBA0JK,6BAAA,QACP,6BACN"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { instantiate } from "../packages/fragno/dist/api/fragment-instantiator.js";
|
|
2
|
+
import { SETTINGS_NAMESPACE, SETTINGS_TABLE_NAME, internalFragmentDef, settingsSchema } from "../fragments/internal-fragment.js";
|
|
1
3
|
import { fragnoDatabaseAdapterNameFakeSymbol, fragnoDatabaseAdapterVersionFakeSymbol } from "../adapters/adapters.js";
|
|
2
|
-
import { SETTINGS_NAMESPACE, createSettingsManager, settingsSchema } from "../shared/settings-schema.js";
|
|
3
4
|
|
|
4
5
|
//#region src/migration-engine/generation-engine.ts
|
|
5
6
|
async function generateMigrationsOrSchema(databases, options) {
|
|
@@ -8,28 +9,40 @@ async function generateMigrationsOrSchema(databases, options) {
|
|
|
8
9
|
const adapter = firstDb.adapter;
|
|
9
10
|
if (adapter.createSchemaGenerator) {
|
|
10
11
|
if (options?.toVersion !== void 0 || options?.fromVersion !== void 0) console.warn("⚠️ Warning: --from and --to version options are not supported when generating schemas for multiple fragments and will be ignored.");
|
|
11
|
-
const
|
|
12
|
+
const fragmentsMap = /* @__PURE__ */ new Map();
|
|
13
|
+
fragmentsMap.set("", {
|
|
14
|
+
schema: settingsSchema,
|
|
15
|
+
namespace: ""
|
|
16
|
+
});
|
|
17
|
+
for (const db of databases) if (!fragmentsMap.has(db.namespace)) fragmentsMap.set(db.namespace, {
|
|
12
18
|
schema: db.schema,
|
|
13
19
|
namespace: db.namespace
|
|
14
|
-
})
|
|
20
|
+
});
|
|
21
|
+
const allFragments = Array.from(fragmentsMap.values());
|
|
15
22
|
return [{
|
|
16
|
-
...adapter.createSchemaGenerator(
|
|
23
|
+
...adapter.createSchemaGenerator(allFragments, { path: options?.path }).generateSchema(),
|
|
17
24
|
namespace: firstDb.namespace
|
|
18
25
|
}];
|
|
19
26
|
}
|
|
20
27
|
if (!adapter.createMigrationEngine) throw new Error("Adapter does not support migration-based schema generation. Ensure your adapter implements createMigrationEngine.");
|
|
21
28
|
if (!await adapter.isConnectionHealthy()) throw new Error("Database connection is not healthy. Please check your database connection and try again.");
|
|
22
|
-
const
|
|
29
|
+
const internalFragment = instantiate(internalFragmentDef).withConfig({}).withOptions({ databaseAdapter: adapter }).build();
|
|
23
30
|
let settingsSourceVersion;
|
|
24
31
|
try {
|
|
25
|
-
const result = await
|
|
32
|
+
const result = await internalFragment.inContext(async function() {
|
|
33
|
+
return await this.uow(async ({ executeRetrieve }) => {
|
|
34
|
+
const v = internalFragment.services.settingsService.get("version");
|
|
35
|
+
await executeRetrieve();
|
|
36
|
+
return v;
|
|
37
|
+
});
|
|
38
|
+
});
|
|
26
39
|
if (!result) settingsSourceVersion = 0;
|
|
27
40
|
else settingsSourceVersion = parseInt(result.value);
|
|
28
41
|
} catch {
|
|
29
42
|
settingsSourceVersion = 0;
|
|
30
43
|
}
|
|
31
44
|
const generatedFiles = [];
|
|
32
|
-
const settingsMigrator = adapter.createMigrationEngine(settingsSchema,
|
|
45
|
+
const settingsMigrator = adapter.createMigrationEngine(settingsSchema, "");
|
|
33
46
|
const settingsTargetVersion = settingsSchema.version;
|
|
34
47
|
const settingsMigration = await settingsMigrator.prepareMigrationTo(settingsTargetVersion, { fromVersion: settingsSourceVersion });
|
|
35
48
|
if (!settingsMigration.getSQL) throw new Error("Migration engine does not support SQL generation. Ensure your adapter's migration engine provides getSQL().");
|
|
@@ -37,7 +50,7 @@ async function generateMigrationsOrSchema(databases, options) {
|
|
|
37
50
|
if (settingsSql.trim()) generatedFiles.push({
|
|
38
51
|
schema: settingsSql,
|
|
39
52
|
path: "settings-migration.sql",
|
|
40
|
-
namespace:
|
|
53
|
+
namespace: "",
|
|
41
54
|
fromVersion: settingsSourceVersion,
|
|
42
55
|
toVersion: settingsTargetVersion,
|
|
43
56
|
preparedMigration: settingsMigration
|
|
@@ -83,15 +96,22 @@ async function executeMigrations(databases) {
|
|
|
83
96
|
if (!await adapter.isConnectionHealthy()) throw new Error("Database connection is not healthy. Please check your database connection and try again.");
|
|
84
97
|
const results = [];
|
|
85
98
|
const migrationsToExecute = [];
|
|
86
|
-
const
|
|
99
|
+
const internalFragment = instantiate(internalFragmentDef).withConfig({}).withOptions({ databaseAdapter: adapter }).build();
|
|
87
100
|
let settingsSourceVersion;
|
|
88
101
|
try {
|
|
89
|
-
const result = await
|
|
102
|
+
const result = await internalFragment.inContext(async function() {
|
|
103
|
+
return this.uow(async ({ forSchema, executeRetrieve }) => {
|
|
104
|
+
const findOp = forSchema(settingsSchema).find(SETTINGS_TABLE_NAME, (b) => b.whereIndex("unique_key", (eb) => eb("key", "=", `${SETTINGS_NAMESPACE}.version`)));
|
|
105
|
+
await executeRetrieve();
|
|
106
|
+
const [results$1] = await findOp.retrievalPhase;
|
|
107
|
+
return results$1?.[0];
|
|
108
|
+
});
|
|
109
|
+
});
|
|
90
110
|
settingsSourceVersion = result ? parseInt(result.value) : 0;
|
|
91
111
|
} catch {
|
|
92
112
|
settingsSourceVersion = 0;
|
|
93
113
|
}
|
|
94
|
-
const settingsMigrator = adapter.createMigrationEngine(settingsSchema,
|
|
114
|
+
const settingsMigrator = adapter.createMigrationEngine(settingsSchema, "");
|
|
95
115
|
const settingsTargetVersion = settingsSchema.version;
|
|
96
116
|
if (settingsSourceVersion < settingsTargetVersion) {
|
|
97
117
|
const settingsMigration = await settingsMigrator.prepareMigrationTo(settingsTargetVersion, {
|
|
@@ -99,7 +119,7 @@ async function executeMigrations(databases) {
|
|
|
99
119
|
updateSettings: true
|
|
100
120
|
});
|
|
101
121
|
if (settingsMigration.operations.length > 0) migrationsToExecute.push({
|
|
102
|
-
namespace:
|
|
122
|
+
namespace: "",
|
|
103
123
|
fromVersion: settingsSourceVersion,
|
|
104
124
|
toVersion: settingsTargetVersion,
|
|
105
125
|
preparedMigration: settingsMigration
|
|
@@ -150,15 +170,15 @@ async function executeMigrations(databases) {
|
|
|
150
170
|
function postProcessMigrationFilenames(files) {
|
|
151
171
|
if (files.length === 0) return [];
|
|
152
172
|
const sortedFiles = [...files].sort((a, b) => {
|
|
153
|
-
if (a.namespace ===
|
|
154
|
-
if (b.namespace ===
|
|
173
|
+
if (a.namespace === "") return -1;
|
|
174
|
+
if (b.namespace === "") return 1;
|
|
155
175
|
return a.namespace.localeCompare(b.namespace);
|
|
156
176
|
});
|
|
157
177
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0].replace(/-/g, "");
|
|
158
178
|
return sortedFiles.map((file, index) => {
|
|
159
179
|
const fromVersion = file.fromVersion ?? 0;
|
|
160
180
|
const toVersion = file.toVersion ?? 0;
|
|
161
|
-
const newPath = `${date}_${(index + 1).toString().padStart(3, "0")}_f${fromVersion.toString().padStart(3, "0")}_t${toVersion.toString().padStart(3, "0")}_${file.namespace.replace(/[^a-z0-9-]/gi, "_")}.sql`;
|
|
181
|
+
const newPath = `${date}_${(index + 1).toString().padStart(3, "0")}_f${fromVersion.toString().padStart(3, "0")}_t${toVersion.toString().padStart(3, "0")}_${file.namespace === "" ? "fragno_db_settings" : file.namespace.replace(/[^a-z0-9-]/gi, "_")}.sql`;
|
|
162
182
|
return {
|
|
163
183
|
schema: file.schema,
|
|
164
184
|
path: newPath,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generation-engine.js","names":["settingsSourceVersion: number","generatedFiles: GenerationInternalResult[]","results: ExecuteMigrationResult[]","migrationsToExecute: Array<{\n namespace: string;\n fromVersion: number;\n toVersion: number;\n preparedMigration: PreparedMigration;\n }>"],"sources":["../../src/migration-engine/generation-engine.ts"],"sourcesContent":["import type { FragnoDatabase } from \"../mod\";\nimport type { AnySchema } from \"../schema/create\";\nimport type { PreparedMigration } from \"./create\";\nimport {\n settingsSchema,\n SETTINGS_NAMESPACE,\n createSettingsManager,\n} from \"../shared/settings-schema\";\nimport {\n fragnoDatabaseAdapterNameFakeSymbol,\n fragnoDatabaseAdapterVersionFakeSymbol,\n} from \"../adapters/adapters\";\n\nexport interface GenerationEngineResult {\n schema: string;\n path: string;\n namespace: string;\n}\n\nexport interface GenerationInternalResult {\n schema: string;\n path: string;\n namespace: string;\n fromVersion: number;\n toVersion: number;\n preparedMigration?: PreparedMigration;\n}\n\nexport interface ExecuteMigrationResult {\n namespace: string;\n didMigrate: boolean;\n fromVersion: number;\n toVersion: number;\n}\n\nexport async function generateMigrationsOrSchema<\n // oxlint-disable-next-line no-explicit-any\n const TDatabases extends FragnoDatabase<AnySchema, any>[],\n>(\n databases: TDatabases,\n options?: {\n path?: string;\n toVersion?: number;\n fromVersion?: number;\n },\n): Promise<GenerationEngineResult[]> {\n if (databases.length === 0) {\n throw new Error(\"No databases provided for schema generation\");\n }\n\n const firstDb = databases[0];\n const adapter = firstDb.adapter;\n\n // If adapter has createSchemaGenerator, use it for combined generation (e.g., Drizzle)\n if (adapter.createSchemaGenerator) {\n if (options?.toVersion !== undefined || options?.fromVersion !== undefined) {\n console.warn(\n \"⚠️ Warning: --from and --to version options are not supported when generating schemas for multiple fragments and will be ignored.\",\n );\n }\n\n const fragments = databases.map((db) => ({\n schema: db.schema,\n namespace: db.namespace,\n }));\n\n const generator = adapter.createSchemaGenerator(fragments, {\n path: options?.path,\n });\n\n return [\n {\n ...generator.generateSchema(),\n namespace: firstDb.namespace,\n },\n ];\n }\n\n // Otherwise, use migration engine for individual generation (e.g., Kysely)\n if (!adapter.createMigrationEngine) {\n throw new Error(\n \"Adapter does not support migration-based schema generation. Ensure your adapter implements createMigrationEngine.\",\n );\n }\n\n if (!(await adapter.isConnectionHealthy())) {\n throw new Error(\n \"Database connection is not healthy. Please check your database connection and try again.\",\n );\n }\n\n const settingsQueryEngine = adapter.createQueryEngine(settingsSchema, \"\");\n const settingsManager = createSettingsManager(settingsQueryEngine, SETTINGS_NAMESPACE);\n\n let settingsSourceVersion: number;\n try {\n const result = await settingsManager.get(\"version\");\n\n if (!result) {\n settingsSourceVersion = 0;\n } else {\n settingsSourceVersion = parseInt(result.value);\n }\n } catch {\n // We don't really have a way to verify this error happens because the key doesn't exist in the database\n settingsSourceVersion = 0;\n }\n\n const generatedFiles: GenerationInternalResult[] = [];\n\n const settingsMigrator = adapter.createMigrationEngine(settingsSchema, SETTINGS_NAMESPACE);\n const settingsTargetVersion = settingsSchema.version;\n\n // Generate settings table migration\n const settingsMigration = await settingsMigrator.prepareMigrationTo(settingsTargetVersion, {\n fromVersion: settingsSourceVersion,\n });\n\n if (!settingsMigration.getSQL) {\n throw new Error(\n \"Migration engine does not support SQL generation. Ensure your adapter's migration engine provides getSQL().\",\n );\n }\n\n const settingsSql = settingsMigration.getSQL();\n\n if (settingsSql.trim()) {\n generatedFiles.push({\n schema: settingsSql,\n path: \"settings-migration.sql\", // Placeholder, will be renamed in post-processing\n namespace: SETTINGS_NAMESPACE,\n fromVersion: settingsSourceVersion,\n toVersion: settingsTargetVersion,\n preparedMigration: settingsMigration,\n });\n }\n\n // Generate migration for each fragment\n for (const db of databases) {\n const dbAdapter = db.adapter;\n\n // Use migration engine\n if (!dbAdapter.createMigrationEngine) {\n throw new Error(\n `Adapter for ${db.namespace} does not support schema generation. ` +\n `Ensure your adapter implements either createSchemaGenerator or createMigrationEngine.`,\n );\n }\n\n const migrator = dbAdapter.createMigrationEngine(db.schema, db.namespace);\n const targetVersion = options?.toVersion ?? db.schema.version;\n const sourceVersion = options?.fromVersion ?? 0;\n\n // Generate migration from source to target version\n const preparedMigration = await migrator.prepareMigrationTo(targetVersion, {\n fromVersion: sourceVersion,\n });\n\n if (!preparedMigration.getSQL) {\n throw new Error(\n \"Migration engine does not support SQL generation. Ensure your adapter's migration engine provides getSQL().\",\n );\n }\n\n const sql = preparedMigration.getSQL();\n\n // If no migrations needed, skip this fragment\n if (sql.trim()) {\n generatedFiles.push({\n schema: sql,\n path: \"schema.sql\", // Placeholder, will be renamed in post-processing\n namespace: db.namespace,\n fromVersion: sourceVersion,\n toVersion: targetVersion,\n preparedMigration: preparedMigration,\n });\n }\n }\n\n // Post-process filenames with ordering\n return postProcessMigrationFilenames(generatedFiles);\n}\n\n/**\n * Execute migrations for all fragments in the correct order.\n * Migrates settings table first, then fragments alphabetically.\n *\n * @param databases - Array of FragnoDatabase instances to migrate\n * @returns Array of execution results for each migration\n */\nexport async function executeMigrations<const TDatabases extends FragnoDatabase<AnySchema>[]>(\n databases: TDatabases,\n): Promise<ExecuteMigrationResult[]> {\n if (databases.length === 0) {\n throw new Error(\"No databases provided for migration\");\n }\n\n const firstDb = databases[0];\n const adapter = firstDb.adapter;\n\n // Validate adapter supports migrations\n if (!adapter.createMigrationEngine) {\n throw new Error(\n \"Adapter does not support running migrations. The adapter only supports schema generation.\\n\" +\n \"Try using 'generateMigrationsOrSchema' instead to generate schema files.\",\n );\n }\n\n // Validate all use same adapter name and version\n const firstAdapterName = adapter[fragnoDatabaseAdapterNameFakeSymbol];\n const firstAdapterVersion = adapter[fragnoDatabaseAdapterVersionFakeSymbol];\n\n for (const db of databases) {\n const dbAdapterName = db.adapter[fragnoDatabaseAdapterNameFakeSymbol];\n const dbAdapterVersion = db.adapter[fragnoDatabaseAdapterVersionFakeSymbol];\n\n if (dbAdapterName !== firstAdapterName || dbAdapterVersion !== firstAdapterVersion) {\n throw new Error(\n `All fragments must use the same database adapter. ` +\n `Found: ${firstAdapterName}@${firstAdapterVersion} and ${dbAdapterName}@${dbAdapterVersion}`,\n );\n }\n }\n\n if (!(await adapter.isConnectionHealthy())) {\n throw new Error(\n \"Database connection is not healthy. Please check your database connection and try again.\",\n );\n }\n\n const results: ExecuteMigrationResult[] = [];\n const migrationsToExecute: Array<{\n namespace: string;\n fromVersion: number;\n toVersion: number;\n preparedMigration: PreparedMigration;\n }> = [];\n\n // 1. Prepare settings table migration\n const settingsQueryEngine = adapter.createQueryEngine(settingsSchema, \"\");\n const settingsManager = createSettingsManager(settingsQueryEngine, SETTINGS_NAMESPACE);\n\n let settingsSourceVersion: number;\n try {\n const result = await settingsManager.get(\"version\");\n settingsSourceVersion = result ? parseInt(result.value) : 0;\n } catch {\n settingsSourceVersion = 0;\n }\n\n const settingsMigrator = adapter.createMigrationEngine(settingsSchema, SETTINGS_NAMESPACE);\n const settingsTargetVersion = settingsSchema.version;\n\n if (settingsSourceVersion < settingsTargetVersion) {\n const settingsMigration = await settingsMigrator.prepareMigrationTo(settingsTargetVersion, {\n fromVersion: settingsSourceVersion,\n updateSettings: true,\n });\n\n if (settingsMigration.operations.length > 0) {\n migrationsToExecute.push({\n namespace: SETTINGS_NAMESPACE,\n fromVersion: settingsSourceVersion,\n toVersion: settingsTargetVersion,\n preparedMigration: settingsMigration,\n });\n }\n }\n\n // 2. Prepare fragment migrations (sorted alphabetically)\n const sortedDatabases = [...databases].sort((a, b) => a.namespace.localeCompare(b.namespace));\n\n for (const fragnoDb of sortedDatabases) {\n const migrator = adapter.createMigrationEngine(fragnoDb.schema, fragnoDb.namespace);\n const currentVersion = await migrator.getVersion();\n const targetVersion = fragnoDb.schema.version;\n\n if (currentVersion < targetVersion) {\n const preparedMigration = await migrator.prepareMigrationTo(targetVersion, {\n updateSettings: true,\n });\n\n if (preparedMigration.operations.length > 0) {\n migrationsToExecute.push({\n namespace: fragnoDb.namespace,\n fromVersion: currentVersion,\n toVersion: targetVersion,\n preparedMigration: preparedMigration,\n });\n }\n }\n }\n\n // 3. Execute all migrations in order\n for (const migration of migrationsToExecute) {\n await migration.preparedMigration.execute();\n results.push({\n namespace: migration.namespace,\n didMigrate: true,\n fromVersion: migration.fromVersion,\n toVersion: migration.toVersion,\n });\n }\n\n // 4. Add skipped migrations (already up-to-date)\n for (const fragnoDb of databases) {\n if (!results.find((r) => r.namespace === fragnoDb.namespace)) {\n results.push({\n namespace: fragnoDb.namespace,\n didMigrate: false,\n fromVersion: fragnoDb.schema.version,\n toVersion: fragnoDb.schema.version,\n });\n }\n }\n\n return results;\n}\n\n/**\n * Post-processes migration files to add ordering and standardize naming.\n *\n * Sorts files with settings namespace first, then alphabetically by namespace,\n * and assigns ordering numbers. Transforms filenames to format:\n * `<date>_<n>_f<from>_t<to>_<namespace>.sql`\n *\n * @param files - Array of generated migration files with version information\n * @returns Array of files with standardized paths and ordering\n */\nexport function postProcessMigrationFilenames(\n files: GenerationInternalResult[],\n): GenerationEngineResult[] {\n if (files.length === 0) {\n return [];\n }\n\n // Sort files: settings namespace first, then alphabetically by namespace\n const sortedFiles = [...files].sort((a, b) => {\n if (a.namespace === SETTINGS_NAMESPACE) {\n return -1;\n }\n if (b.namespace === SETTINGS_NAMESPACE) {\n return 1;\n }\n return a.namespace.localeCompare(b.namespace);\n });\n\n // Generate date prefix for filenames\n const date = new Date().toISOString().split(\"T\")[0].replace(/-/g, \"\");\n\n // Rename files with ordering\n return sortedFiles.map((file, index) => {\n const fromVersion = file.fromVersion ?? 0;\n const toVersion = file.toVersion ?? 0;\n\n // Create new filename with ordering\n const orderNum = (index + 1).toString().padStart(3, \"0\");\n const fromPadded = fromVersion.toString().padStart(3, \"0\");\n const toPadded = toVersion.toString().padStart(3, \"0\");\n const safeName = file.namespace.replace(/[^a-z0-9-]/gi, \"_\");\n const newPath = `${date}_${orderNum}_f${fromPadded}_t${toPadded}_${safeName}.sql`;\n\n return {\n schema: file.schema,\n path: newPath,\n namespace: file.namespace,\n };\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"generation-engine.js","names":["settingsSourceVersion: number","generatedFiles: GenerationInternalResult[]","results: ExecuteMigrationResult[]","migrationsToExecute: Array<{\n namespace: string;\n fromVersion: number;\n toVersion: number;\n preparedMigration: PreparedMigration;\n }>","results"],"sources":["../../src/migration-engine/generation-engine.ts"],"sourcesContent":["import type { FragnoDatabase } from \"../mod\";\nimport type { AnySchema } from \"../schema/create\";\nimport type { PreparedMigration } from \"./create\";\nimport {\n fragnoDatabaseAdapterNameFakeSymbol,\n fragnoDatabaseAdapterVersionFakeSymbol,\n} from \"../adapters/adapters\";\nimport {\n internalFragmentDef,\n settingsSchema,\n SETTINGS_TABLE_NAME,\n SETTINGS_NAMESPACE,\n} from \"../fragments/internal-fragment\";\nimport { instantiate } from \"@fragno-dev/core\";\n\nexport interface GenerationEngineResult {\n schema: string;\n path: string;\n namespace: string;\n}\n\nexport interface GenerationInternalResult {\n schema: string;\n path: string;\n namespace: string;\n fromVersion: number;\n toVersion: number;\n preparedMigration?: PreparedMigration;\n}\n\nexport interface ExecuteMigrationResult {\n namespace: string;\n didMigrate: boolean;\n fromVersion: number;\n toVersion: number;\n}\n\nexport async function generateMigrationsOrSchema<\n // oxlint-disable-next-line no-explicit-any\n const TDatabases extends FragnoDatabase<AnySchema, any>[],\n>(\n databases: TDatabases,\n options?: {\n path?: string;\n toVersion?: number;\n fromVersion?: number;\n },\n): Promise<GenerationEngineResult[]> {\n if (databases.length === 0) {\n throw new Error(\"No databases provided for schema generation\");\n }\n\n const firstDb = databases[0];\n const adapter = firstDb.adapter;\n\n // If adapter has createSchemaGenerator, use it for combined generation (e.g., Drizzle)\n if (adapter.createSchemaGenerator) {\n if (options?.toVersion !== undefined || options?.fromVersion !== undefined) {\n console.warn(\n \"⚠️ Warning: --from and --to version options are not supported when generating schemas for multiple fragments and will be ignored.\",\n );\n }\n\n // Collect all schemas, de-duplicating by namespace.\n // The internal fragment (settings schema) is always included first since all database\n // fragments automatically link to it via withDatabase().\n const fragmentsMap = new Map<string, { schema: AnySchema; namespace: string }>();\n\n // Include internal fragment first with empty namespace (settings table has no prefix)\n fragmentsMap.set(\"\", {\n schema: settingsSchema,\n namespace: \"\",\n });\n\n // Add user fragments, de-duplicating by namespace\n // Each FragnoDatabase has a unique namespace, so this prevents duplicate schema generation\n for (const db of databases) {\n if (!fragmentsMap.has(db.namespace)) {\n fragmentsMap.set(db.namespace, {\n schema: db.schema,\n namespace: db.namespace,\n });\n }\n }\n\n const allFragments = Array.from(fragmentsMap.values());\n const generator = adapter.createSchemaGenerator(allFragments, {\n path: options?.path,\n });\n\n return [\n {\n ...generator.generateSchema(),\n namespace: firstDb.namespace,\n },\n ];\n }\n\n // Otherwise, use migration engine for individual generation (e.g., Kysely)\n if (!adapter.createMigrationEngine) {\n throw new Error(\n \"Adapter does not support migration-based schema generation. Ensure your adapter implements createMigrationEngine.\",\n );\n }\n\n if (!(await adapter.isConnectionHealthy())) {\n throw new Error(\n \"Database connection is not healthy. Please check your database connection and try again.\",\n );\n }\n\n // Use the internal fragment for settings management\n const internalFragment = instantiate(internalFragmentDef)\n .withConfig({})\n .withOptions({ databaseAdapter: adapter })\n .build();\n\n let settingsSourceVersion: number;\n try {\n const result = await internalFragment.inContext(async function () {\n return await this.uow(async ({ executeRetrieve }) => {\n const v = internalFragment.services.settingsService.get(\"version\");\n await executeRetrieve();\n\n return v;\n });\n });\n\n if (!result) {\n settingsSourceVersion = 0;\n } else {\n settingsSourceVersion = parseInt(result.value);\n }\n } catch {\n // Settings table doesn't exist yet (first migration)\n settingsSourceVersion = 0;\n }\n\n const generatedFiles: GenerationInternalResult[] = [];\n\n // Use empty namespace for settings (SETTINGS_NAMESPACE is for prefixing keys, not the database namespace)\n const settingsMigrator = adapter.createMigrationEngine(settingsSchema, \"\");\n const settingsTargetVersion = settingsSchema.version;\n\n // Generate settings table migration\n const settingsMigration = await settingsMigrator.prepareMigrationTo(settingsTargetVersion, {\n fromVersion: settingsSourceVersion,\n });\n\n if (!settingsMigration.getSQL) {\n throw new Error(\n \"Migration engine does not support SQL generation. Ensure your adapter's migration engine provides getSQL().\",\n );\n }\n\n const settingsSql = settingsMigration.getSQL();\n\n if (settingsSql.trim()) {\n generatedFiles.push({\n schema: settingsSql,\n path: \"settings-migration.sql\", // Placeholder, will be renamed in post-processing\n namespace: \"\", // Empty namespace for settings table\n fromVersion: settingsSourceVersion,\n toVersion: settingsTargetVersion,\n preparedMigration: settingsMigration,\n });\n }\n\n // Generate migration for each fragment\n for (const db of databases) {\n const dbAdapter = db.adapter;\n\n // Use migration engine\n if (!dbAdapter.createMigrationEngine) {\n throw new Error(\n `Adapter for ${db.namespace} does not support schema generation. ` +\n `Ensure your adapter implements either createSchemaGenerator or createMigrationEngine.`,\n );\n }\n\n const migrator = dbAdapter.createMigrationEngine(db.schema, db.namespace);\n const targetVersion = options?.toVersion ?? db.schema.version;\n const sourceVersion = options?.fromVersion ?? 0;\n\n // Generate migration from source to target version\n const preparedMigration = await migrator.prepareMigrationTo(targetVersion, {\n fromVersion: sourceVersion,\n });\n\n if (!preparedMigration.getSQL) {\n throw new Error(\n \"Migration engine does not support SQL generation. Ensure your adapter's migration engine provides getSQL().\",\n );\n }\n\n const sql = preparedMigration.getSQL();\n\n // If no migrations needed, skip this fragment\n if (sql.trim()) {\n generatedFiles.push({\n schema: sql,\n path: \"schema.sql\", // Placeholder, will be renamed in post-processing\n namespace: db.namespace,\n fromVersion: sourceVersion,\n toVersion: targetVersion,\n preparedMigration: preparedMigration,\n });\n }\n }\n\n // Post-process filenames with ordering\n return postProcessMigrationFilenames(generatedFiles);\n}\n\n/**\n * Execute migrations for all fragments in the correct order.\n * Migrates settings table first, then fragments alphabetically.\n *\n * @param databases - Array of FragnoDatabase instances to migrate\n * @returns Array of execution results for each migration\n */\nexport async function executeMigrations<const TDatabases extends FragnoDatabase<AnySchema>[]>(\n databases: TDatabases,\n): Promise<ExecuteMigrationResult[]> {\n if (databases.length === 0) {\n throw new Error(\"No databases provided for migration\");\n }\n\n const firstDb = databases[0];\n const adapter = firstDb.adapter;\n\n // Validate adapter supports migrations\n if (!adapter.createMigrationEngine) {\n throw new Error(\n \"Adapter does not support running migrations. The adapter only supports schema generation.\\n\" +\n \"Try using 'generateMigrationsOrSchema' instead to generate schema files.\",\n );\n }\n\n // Validate all use same adapter name and version\n const firstAdapterName = adapter[fragnoDatabaseAdapterNameFakeSymbol];\n const firstAdapterVersion = adapter[fragnoDatabaseAdapterVersionFakeSymbol];\n\n for (const db of databases) {\n const dbAdapterName = db.adapter[fragnoDatabaseAdapterNameFakeSymbol];\n const dbAdapterVersion = db.adapter[fragnoDatabaseAdapterVersionFakeSymbol];\n\n if (dbAdapterName !== firstAdapterName || dbAdapterVersion !== firstAdapterVersion) {\n throw new Error(\n `All fragments must use the same database adapter. ` +\n `Found: ${firstAdapterName}@${firstAdapterVersion} and ${dbAdapterName}@${dbAdapterVersion}`,\n );\n }\n }\n\n if (!(await adapter.isConnectionHealthy())) {\n throw new Error(\n \"Database connection is not healthy. Please check your database connection and try again.\",\n );\n }\n\n const results: ExecuteMigrationResult[] = [];\n const migrationsToExecute: Array<{\n namespace: string;\n fromVersion: number;\n toVersion: number;\n preparedMigration: PreparedMigration;\n }> = [];\n\n // 1. Prepare settings table migration\n // Use the internal fragment for settings management\n const internalFragment = instantiate(internalFragmentDef)\n .withConfig({})\n .withOptions({ databaseAdapter: adapter })\n .build();\n\n let settingsSourceVersion: number;\n try {\n const result = await internalFragment.inContext(async function () {\n return this.uow(async ({ forSchema, executeRetrieve }) => {\n const uow = forSchema(settingsSchema);\n const findOp = uow.find(SETTINGS_TABLE_NAME, (b) =>\n b.whereIndex(\"unique_key\", (eb) => eb(\"key\", \"=\", `${SETTINGS_NAMESPACE}.version`)),\n );\n\n await executeRetrieve();\n\n const [results] = await findOp.retrievalPhase;\n return results?.[0];\n });\n });\n settingsSourceVersion = result ? parseInt(result.value) : 0;\n } catch {\n // Settings table doesn't exist yet (first migration)\n settingsSourceVersion = 0;\n }\n\n // Use empty namespace for settings (SETTINGS_NAMESPACE is for prefixing keys, not the database namespace)\n const settingsMigrator = adapter.createMigrationEngine(settingsSchema, \"\");\n const settingsTargetVersion = settingsSchema.version;\n\n if (settingsSourceVersion < settingsTargetVersion) {\n const settingsMigration = await settingsMigrator.prepareMigrationTo(settingsTargetVersion, {\n fromVersion: settingsSourceVersion,\n updateSettings: true,\n });\n\n if (settingsMigration.operations.length > 0) {\n migrationsToExecute.push({\n namespace: \"\", // Empty namespace for settings table\n fromVersion: settingsSourceVersion,\n toVersion: settingsTargetVersion,\n preparedMigration: settingsMigration,\n });\n }\n }\n\n // 2. Prepare fragment migrations (sorted alphabetically)\n const sortedDatabases = [...databases].sort((a, b) => a.namespace.localeCompare(b.namespace));\n\n for (const fragnoDb of sortedDatabases) {\n const migrator = adapter.createMigrationEngine(fragnoDb.schema, fragnoDb.namespace);\n const currentVersion = await migrator.getVersion();\n const targetVersion = fragnoDb.schema.version;\n\n if (currentVersion < targetVersion) {\n const preparedMigration = await migrator.prepareMigrationTo(targetVersion, {\n updateSettings: true,\n });\n\n if (preparedMigration.operations.length > 0) {\n migrationsToExecute.push({\n namespace: fragnoDb.namespace,\n fromVersion: currentVersion,\n toVersion: targetVersion,\n preparedMigration: preparedMigration,\n });\n }\n }\n }\n\n // 3. Execute all migrations in order\n for (const migration of migrationsToExecute) {\n await migration.preparedMigration.execute();\n results.push({\n namespace: migration.namespace,\n didMigrate: true,\n fromVersion: migration.fromVersion,\n toVersion: migration.toVersion,\n });\n }\n\n // 4. Add skipped migrations (already up-to-date)\n for (const fragnoDb of databases) {\n if (!results.find((r) => r.namespace === fragnoDb.namespace)) {\n results.push({\n namespace: fragnoDb.namespace,\n didMigrate: false,\n fromVersion: fragnoDb.schema.version,\n toVersion: fragnoDb.schema.version,\n });\n }\n }\n\n return results;\n}\n\n/**\n * Post-processes migration files to add ordering and standardize naming.\n *\n * Sorts files with settings namespace first, then alphabetically by namespace,\n * and assigns ordering numbers. Transforms filenames to format:\n * `<date>_<n>_f<from>_t<to>_<namespace>.sql`\n *\n * @param files - Array of generated migration files with version information\n * @returns Array of files with standardized paths and ordering\n */\nexport function postProcessMigrationFilenames(\n files: GenerationInternalResult[],\n): GenerationEngineResult[] {\n if (files.length === 0) {\n return [];\n }\n\n // Sort files: settings namespace first (empty string), then alphabetically by namespace\n const sortedFiles = [...files].sort((a, b) => {\n // Settings table has empty namespace - sort it first\n if (a.namespace === \"\") {\n return -1;\n }\n if (b.namespace === \"\") {\n return 1;\n }\n return a.namespace.localeCompare(b.namespace);\n });\n\n // Generate date prefix for filenames\n const date = new Date().toISOString().split(\"T\")[0].replace(/-/g, \"\");\n\n // Rename files with ordering\n return sortedFiles.map((file, index) => {\n const fromVersion = file.fromVersion ?? 0;\n const toVersion = file.toVersion ?? 0;\n\n // Create new filename with ordering\n const orderNum = (index + 1).toString().padStart(3, \"0\");\n const fromPadded = fromVersion.toString().padStart(3, \"0\");\n const toPadded = toVersion.toString().padStart(3, \"0\");\n\n // For settings table (empty namespace), use \"fragno_db_settings\" in the filename\n // For other tables, use their namespace\n const safeName =\n file.namespace === \"\" ? \"fragno_db_settings\" : file.namespace.replace(/[^a-z0-9-]/gi, \"_\");\n const newPath = `${date}_${orderNum}_f${fromPadded}_t${toPadded}_${safeName}.sql`;\n\n return {\n schema: file.schema,\n path: newPath,\n namespace: file.namespace,\n };\n });\n}\n"],"mappings":"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"}
|
package/dist/mod.d.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { AnySchema } from "./schema/create.js";
|
|
2
2
|
import { Cursor, CursorData, CursorResult, decodeCursor } from "./query/cursor.js";
|
|
3
|
-
import {
|
|
3
|
+
import { IUnitOfWork, IUnitOfWorkRestricted, TypedUnitOfWork, UOWCompiler, UOWDecoder, UOWExecutor, UnitOfWork, createUnitOfWork } from "./query/unit-of-work.js";
|
|
4
4
|
import { AbstractQuery } from "./query/query.js";
|
|
5
5
|
import { DatabaseAdapter } from "./adapters/adapters.js";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { ExponentialBackoffRetryPolicy, LinearBackoffRetryPolicy, NoRetryPolicy, RetryPolicy } from "./query/retry-policy.js";
|
|
7
|
+
import { ExecuteUnitOfWorkCallbacks, ExecuteUnitOfWorkOptions, ExecuteUnitOfWorkResult, executeUnitOfWork } from "./query/execute-unit-of-work.js";
|
|
8
|
+
import { DatabaseFragmentContext, DatabaseFragmentDefinitionBuilder, DatabaseHandlerContext, FragnoPublicConfigWithDatabase, ImplicitDatabaseDependencies } from "./db-fragment-definition-builder.js";
|
|
9
|
+
import { withDatabase } from "./with-database.js";
|
|
10
|
+
import { internalFragmentDef } from "./fragments/internal-fragment.js";
|
|
11
|
+
import { BoundServices } from "@fragno-dev/core";
|
|
8
12
|
|
|
9
13
|
//#region src/mod.d.ts
|
|
10
14
|
declare const fragnoDatabaseFakeSymbol: "$fragno-database";
|
|
@@ -14,21 +18,6 @@ interface CreateFragnoDatabaseDefinitionOptions<T extends AnySchema> {
|
|
|
14
18
|
schema: T;
|
|
15
19
|
}
|
|
16
20
|
declare function isFragnoDatabase(value: unknown): value is FragnoDatabase<AnySchema>;
|
|
17
|
-
/**
|
|
18
|
-
* Definition of a Fragno database schema and namespace.
|
|
19
|
-
* Created by library authors using defineFragnoDatabase().
|
|
20
|
-
* Apps instantiate it by calling .create(adapter).
|
|
21
|
-
*/
|
|
22
|
-
declare class FragnoDatabaseDefinition<const T extends AnySchema> {
|
|
23
|
-
#private;
|
|
24
|
-
constructor(options: CreateFragnoDatabaseDefinitionOptions<T>);
|
|
25
|
-
get namespace(): string;
|
|
26
|
-
get schema(): T;
|
|
27
|
-
/**
|
|
28
|
-
* Creates a FragnoDatabase instance by binding an adapter to this definition.
|
|
29
|
-
*/
|
|
30
|
-
create<TUOWConfig = void>(adapter: DatabaseAdapter<TUOWConfig>): FragnoDatabase<T, TUOWConfig>;
|
|
31
|
-
}
|
|
32
21
|
/**
|
|
33
22
|
* A Fragno database instance with a bound adapter.
|
|
34
23
|
* Created from a FragnoDatabaseDefinition by calling .create(adapter).
|
|
@@ -47,7 +36,6 @@ declare class FragnoDatabase<const T extends AnySchema, TUOWConfig = void> {
|
|
|
47
36
|
get schema(): T;
|
|
48
37
|
get adapter(): DatabaseAdapter<TUOWConfig>;
|
|
49
38
|
}
|
|
50
|
-
declare function defineFragnoDatabase<const TSchema extends AnySchema>(options: CreateFragnoDatabaseDefinitionOptions<TSchema>): FragnoDatabaseDefinition<TSchema>;
|
|
51
39
|
//#endregion
|
|
52
|
-
export { type BoundServices, CreateFragnoDatabaseDefinitionOptions, Cursor, type CursorData, type CursorResult, type DatabaseAdapter,
|
|
40
|
+
export { type BoundServices, CreateFragnoDatabaseDefinitionOptions, Cursor, type CursorData, type CursorResult, type DatabaseAdapter, type DatabaseFragmentContext, DatabaseFragmentDefinitionBuilder, type DatabaseHandlerContext as DatabaseRequestContext, type ExecuteUnitOfWorkCallbacks, type ExecuteUnitOfWorkOptions, type ExecuteUnitOfWorkResult, ExponentialBackoffRetryPolicy, FragnoDatabase, type FragnoPublicConfigWithDatabase, type IUnitOfWork, type IUnitOfWorkRestricted, type ImplicitDatabaseDependencies, LinearBackoffRetryPolicy, NoRetryPolicy, type RetryPolicy, TypedUnitOfWork, type UOWCompiler, type UOWDecoder, type UOWExecutor, UnitOfWork, createUnitOfWork, decodeCursor, executeUnitOfWork, fragnoDatabaseFakeSymbol, fragnoDatabaseLibraryVersion, internalFragmentDef, isFragnoDatabase, withDatabase };
|
|
53
41
|
//# sourceMappingURL=mod.d.ts.map
|
package/dist/mod.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mod.d.ts","names":[],"sources":["../src/mod.ts"],"sourcesContent":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"mod.d.ts","names":[],"sources":["../src/mod.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;cASa;cACA;UAEI,gDAAgD;;UAEvD;;AALG,iBAQG,gBAAA,CARmD,KAAA,EAAA,OAAA,CAAA,EAAA,KAAA,IAQR,cARQ,CAQO,SARP,CAAA;AACnE;AAEA;AAKA;AAmBA;AAA4C,cAA/B,cAA+B,CAAA,gBAAA,SAAA,EAAA,aAAA,IAAA,CAAA,CAAA;EAKQ,CAAA,OAAA;EAA4B,WAAA,CAAA,OAAA,EAAA;IAAhB,SAAA,EAAA,MAAA;IAMrB,MAAA,EANS,CAMT;IAApC,OAAA,EANyD,eAMzD,CANyE,UAMzE,CAAA;EAIuC,CAAA;EAAG,KAJ1C,wBAAA,GAI0C,EAAA,OAJN,wBAIM;EAAjB,YAAA,CAAA,CAAA,EAAR,OAAQ,CAAA,aAAA,CAAc,CAAd,EAAiB,UAAjB,CAAA,CAAA;EAAR,aAAA,CAAA,CAAA,EAYC,OAZD,CAAA,OAAA,CAAA;EAYC,IAAA,SAAA,CAAA,CAAA,EAAA,MAAA;EAgBb,IAAA,MAAA,CAAA,CAAA,EAAA,CAAA;EAIqB,IAAA,OAAA,CAAA,CAAA,EAAhB,eAAgB,CAAA,UAAA,CAAA"}
|
package/dist/mod.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import { DatabaseFragmentBuilder, defineFragmentWithDatabase, serviceContext, uowStorage, withUnitOfWork } from "./fragment.js";
|
|
2
1
|
import { Cursor, decodeCursor } from "./query/cursor.js";
|
|
3
|
-
import {
|
|
2
|
+
import { TypedUnitOfWork, UnitOfWork, createUnitOfWork } from "./query/unit-of-work.js";
|
|
3
|
+
import { ExponentialBackoffRetryPolicy, LinearBackoffRetryPolicy, NoRetryPolicy } from "./query/retry-policy.js";
|
|
4
|
+
import { executeUnitOfWork } from "./query/execute-unit-of-work.js";
|
|
5
|
+
import { DatabaseFragmentDefinitionBuilder } from "./db-fragment-definition-builder.js";
|
|
6
|
+
import { internalFragmentDef } from "./fragments/internal-fragment.js";
|
|
7
|
+
import { withDatabase } from "./with-database.js";
|
|
4
8
|
|
|
5
9
|
//#region src/mod.ts
|
|
6
10
|
const fragnoDatabaseFakeSymbol = "$fragno-database";
|
|
@@ -11,35 +15,6 @@ function isFragnoDatabase(value) {
|
|
|
11
15
|
return fragnoDatabaseFakeSymbol in value && value[fragnoDatabaseFakeSymbol] === fragnoDatabaseFakeSymbol;
|
|
12
16
|
}
|
|
13
17
|
/**
|
|
14
|
-
* Definition of a Fragno database schema and namespace.
|
|
15
|
-
* Created by library authors using defineFragnoDatabase().
|
|
16
|
-
* Apps instantiate it by calling .create(adapter).
|
|
17
|
-
*/
|
|
18
|
-
var FragnoDatabaseDefinition = class {
|
|
19
|
-
#namespace;
|
|
20
|
-
#schema;
|
|
21
|
-
constructor(options) {
|
|
22
|
-
this.#namespace = options.namespace;
|
|
23
|
-
this.#schema = options.schema;
|
|
24
|
-
}
|
|
25
|
-
get namespace() {
|
|
26
|
-
return this.#namespace;
|
|
27
|
-
}
|
|
28
|
-
get schema() {
|
|
29
|
-
return this.#schema;
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Creates a FragnoDatabase instance by binding an adapter to this definition.
|
|
33
|
-
*/
|
|
34
|
-
create(adapter) {
|
|
35
|
-
return new FragnoDatabase({
|
|
36
|
-
namespace: this.#namespace,
|
|
37
|
-
schema: this.#schema,
|
|
38
|
-
adapter
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
/**
|
|
43
18
|
* A Fragno database instance with a bound adapter.
|
|
44
19
|
* Created from a FragnoDatabaseDefinition by calling .create(adapter).
|
|
45
20
|
*/
|
|
@@ -76,10 +51,7 @@ var FragnoDatabase = class {
|
|
|
76
51
|
return this.#adapter;
|
|
77
52
|
}
|
|
78
53
|
};
|
|
79
|
-
function defineFragnoDatabase(options) {
|
|
80
|
-
return new FragnoDatabaseDefinition(options);
|
|
81
|
-
}
|
|
82
54
|
|
|
83
55
|
//#endregion
|
|
84
|
-
export { Cursor,
|
|
56
|
+
export { Cursor, DatabaseFragmentDefinitionBuilder, ExponentialBackoffRetryPolicy, FragnoDatabase, LinearBackoffRetryPolicy, NoRetryPolicy, TypedUnitOfWork, UnitOfWork, createUnitOfWork, decodeCursor, executeUnitOfWork, fragnoDatabaseFakeSymbol, fragnoDatabaseLibraryVersion, internalFragmentDef, isFragnoDatabase, withDatabase };
|
|
85
57
|
//# sourceMappingURL=mod.js.map
|