@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
|
@@ -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,8 +1,14 @@
|
|
|
1
1
|
import { AnySchema } from "./schema/create.js";
|
|
2
2
|
import { Cursor, CursorData, CursorResult, decodeCursor } from "./query/cursor.js";
|
|
3
|
+
import { IUnitOfWork, IUnitOfWorkRestricted, TypedUnitOfWork, UOWCompiler, UOWDecoder, UOWExecutor, UnitOfWork, createUnitOfWork } from "./query/unit-of-work.js";
|
|
3
4
|
import { AbstractQuery } from "./query/query.js";
|
|
4
5
|
import { DatabaseAdapter } from "./adapters/adapters.js";
|
|
5
|
-
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";
|
|
6
12
|
|
|
7
13
|
//#region src/mod.d.ts
|
|
8
14
|
declare const fragnoDatabaseFakeSymbol: "$fragno-database";
|
|
@@ -12,21 +18,6 @@ interface CreateFragnoDatabaseDefinitionOptions<T extends AnySchema> {
|
|
|
12
18
|
schema: T;
|
|
13
19
|
}
|
|
14
20
|
declare function isFragnoDatabase(value: unknown): value is FragnoDatabase<AnySchema>;
|
|
15
|
-
/**
|
|
16
|
-
* Definition of a Fragno database schema and namespace.
|
|
17
|
-
* Created by library authors using defineFragnoDatabase().
|
|
18
|
-
* Apps instantiate it by calling .create(adapter).
|
|
19
|
-
*/
|
|
20
|
-
declare class FragnoDatabaseDefinition<const T extends AnySchema> {
|
|
21
|
-
#private;
|
|
22
|
-
constructor(options: CreateFragnoDatabaseDefinitionOptions<T>);
|
|
23
|
-
get namespace(): string;
|
|
24
|
-
get schema(): T;
|
|
25
|
-
/**
|
|
26
|
-
* Creates a FragnoDatabase instance by binding an adapter to this definition.
|
|
27
|
-
*/
|
|
28
|
-
create<TUOWConfig = void>(adapter: DatabaseAdapter<TUOWConfig>): FragnoDatabase<T, TUOWConfig>;
|
|
29
|
-
}
|
|
30
21
|
/**
|
|
31
22
|
* A Fragno database instance with a bound adapter.
|
|
32
23
|
* Created from a FragnoDatabaseDefinition by calling .create(adapter).
|
|
@@ -45,7 +36,6 @@ declare class FragnoDatabase<const T extends AnySchema, TUOWConfig = void> {
|
|
|
45
36
|
get schema(): T;
|
|
46
37
|
get adapter(): DatabaseAdapter<TUOWConfig>;
|
|
47
38
|
}
|
|
48
|
-
declare function defineFragnoDatabase<const TSchema extends AnySchema>(options: CreateFragnoDatabaseDefinitionOptions<TSchema>): FragnoDatabaseDefinition<TSchema>;
|
|
49
39
|
//#endregion
|
|
50
|
-
export { 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 };
|
|
51
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,5 +1,10 @@
|
|
|
1
|
-
import { DatabaseFragmentBuilder, defineFragmentWithDatabase } from "./fragment.js";
|
|
2
1
|
import { Cursor, decodeCursor } from "./query/cursor.js";
|
|
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";
|
|
3
8
|
|
|
4
9
|
//#region src/mod.ts
|
|
5
10
|
const fragnoDatabaseFakeSymbol = "$fragno-database";
|
|
@@ -10,35 +15,6 @@ function isFragnoDatabase(value) {
|
|
|
10
15
|
return fragnoDatabaseFakeSymbol in value && value[fragnoDatabaseFakeSymbol] === fragnoDatabaseFakeSymbol;
|
|
11
16
|
}
|
|
12
17
|
/**
|
|
13
|
-
* Definition of a Fragno database schema and namespace.
|
|
14
|
-
* Created by library authors using defineFragnoDatabase().
|
|
15
|
-
* Apps instantiate it by calling .create(adapter).
|
|
16
|
-
*/
|
|
17
|
-
var FragnoDatabaseDefinition = class {
|
|
18
|
-
#namespace;
|
|
19
|
-
#schema;
|
|
20
|
-
constructor(options) {
|
|
21
|
-
this.#namespace = options.namespace;
|
|
22
|
-
this.#schema = options.schema;
|
|
23
|
-
}
|
|
24
|
-
get namespace() {
|
|
25
|
-
return this.#namespace;
|
|
26
|
-
}
|
|
27
|
-
get schema() {
|
|
28
|
-
return this.#schema;
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Creates a FragnoDatabase instance by binding an adapter to this definition.
|
|
32
|
-
*/
|
|
33
|
-
create(adapter) {
|
|
34
|
-
return new FragnoDatabase({
|
|
35
|
-
namespace: this.#namespace,
|
|
36
|
-
schema: this.#schema,
|
|
37
|
-
adapter
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
/**
|
|
42
18
|
* A Fragno database instance with a bound adapter.
|
|
43
19
|
* Created from a FragnoDatabaseDefinition by calling .create(adapter).
|
|
44
20
|
*/
|
|
@@ -75,10 +51,7 @@ var FragnoDatabase = class {
|
|
|
75
51
|
return this.#adapter;
|
|
76
52
|
}
|
|
77
53
|
};
|
|
78
|
-
function defineFragnoDatabase(options) {
|
|
79
|
-
return new FragnoDatabaseDefinition(options);
|
|
80
|
-
}
|
|
81
54
|
|
|
82
55
|
//#endregion
|
|
83
|
-
export { Cursor,
|
|
56
|
+
export { Cursor, DatabaseFragmentDefinitionBuilder, ExponentialBackoffRetryPolicy, FragnoDatabase, LinearBackoffRetryPolicy, NoRetryPolicy, TypedUnitOfWork, UnitOfWork, createUnitOfWork, decodeCursor, executeUnitOfWork, fragnoDatabaseFakeSymbol, fragnoDatabaseLibraryVersion, internalFragmentDef, isFragnoDatabase, withDatabase };
|
|
84
57
|
//# sourceMappingURL=mod.js.map
|
package/dist/mod.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mod.js","names":["#namespace","#schema","#adapter"],"sources":["../src/mod.ts"],"sourcesContent":["import type { DatabaseAdapter } from \"./adapters/adapters\";\nimport type { AnySchema } from \"./schema/create\";\nimport type { AbstractQuery } from \"./query/query\";\nimport type { CursorResult } from \"./query/cursor\";\nimport { Cursor } from \"./query/cursor\";\n\nexport type { DatabaseAdapter, CursorResult };\nexport { Cursor };\n\nexport const fragnoDatabaseFakeSymbol = \"$fragno-database\" as const;\nexport const fragnoDatabaseLibraryVersion = \"0.1\" as const;\n\nexport interface CreateFragnoDatabaseDefinitionOptions<T extends AnySchema> {\n namespace: string;\n schema: T;\n}\n\nexport function isFragnoDatabase(value: unknown): value is FragnoDatabase<AnySchema> {\n if (value instanceof FragnoDatabase) {\n return true;\n }\n\n if (typeof value !== \"object\" || value === null) {\n return false;\n }\n\n return (\n fragnoDatabaseFakeSymbol in value &&\n value[fragnoDatabaseFakeSymbol] === fragnoDatabaseFakeSymbol\n );\n}\n\n/**\n *
|
|
1
|
+
{"version":3,"file":"mod.js","names":["#namespace","#schema","#adapter"],"sources":["../src/mod.ts"],"sourcesContent":["import type { DatabaseAdapter } from \"./adapters/adapters\";\nimport type { AnySchema } from \"./schema/create\";\nimport type { AbstractQuery } from \"./query/query\";\nimport type { CursorResult } from \"./query/cursor\";\nimport { Cursor } from \"./query/cursor\";\n\nexport type { DatabaseAdapter, CursorResult };\nexport { Cursor };\n\nexport const fragnoDatabaseFakeSymbol = \"$fragno-database\" as const;\nexport const fragnoDatabaseLibraryVersion = \"0.1\" as const;\n\nexport interface CreateFragnoDatabaseDefinitionOptions<T extends AnySchema> {\n namespace: string;\n schema: T;\n}\n\nexport function isFragnoDatabase(value: unknown): value is FragnoDatabase<AnySchema> {\n if (value instanceof FragnoDatabase) {\n return true;\n }\n\n if (typeof value !== \"object\" || value === null) {\n return false;\n }\n\n return (\n fragnoDatabaseFakeSymbol in value &&\n value[fragnoDatabaseFakeSymbol] === fragnoDatabaseFakeSymbol\n );\n}\n\n/**\n * A Fragno database instance with a bound adapter.\n * Created from a FragnoDatabaseDefinition by calling .create(adapter).\n */\nexport class FragnoDatabase<const T extends AnySchema, TUOWConfig = void> {\n #namespace: string;\n #schema: T;\n #adapter: DatabaseAdapter<TUOWConfig>;\n\n constructor(options: { namespace: string; schema: T; adapter: DatabaseAdapter<TUOWConfig> }) {\n this.#namespace = options.namespace;\n this.#schema = options.schema;\n this.#adapter = options.adapter;\n }\n\n get [fragnoDatabaseFakeSymbol](): typeof fragnoDatabaseFakeSymbol {\n return fragnoDatabaseFakeSymbol;\n }\n\n async createClient(): Promise<AbstractQuery<T, TUOWConfig>> {\n const dbVersion = await this.#adapter.getSchemaVersion(this.#namespace);\n if (dbVersion !== this.#schema.version.toString()) {\n throw new Error(\n `Database is not at expected version. Did you forget to run migrations?` +\n ` Current version: ${dbVersion}, Expected version: ${this.#schema.version}`,\n );\n }\n\n return this.#adapter.createQueryEngine(this.#schema, this.#namespace);\n }\n\n async runMigrations(): Promise<boolean> {\n if (!this.#adapter.createMigrationEngine) {\n throw new Error(\"Migration engine not supported for this adapter.\");\n }\n\n const migrator = this.#adapter.createMigrationEngine(this.#schema, this.#namespace);\n const preparedMigration = await migrator.prepareMigration();\n await preparedMigration.execute();\n\n return preparedMigration.operations.length > 0;\n }\n\n get namespace() {\n return this.#namespace;\n }\n\n get schema() {\n return this.#schema;\n }\n\n get adapter(): DatabaseAdapter<TUOWConfig> {\n return this.#adapter;\n }\n}\n\nexport {\n DatabaseFragmentDefinitionBuilder,\n type FragnoPublicConfigWithDatabase,\n type DatabaseFragmentContext,\n type DatabaseHandlerContext as DatabaseRequestContext,\n type ImplicitDatabaseDependencies,\n} from \"./db-fragment-definition-builder\";\n\nexport { withDatabase } from \"./with-database\";\n\nexport { decodeCursor, type CursorData } from \"./query/cursor\";\n\nexport {\n createUnitOfWork,\n UnitOfWork,\n TypedUnitOfWork,\n type IUnitOfWork,\n type IUnitOfWorkRestricted,\n type UOWCompiler,\n type UOWExecutor,\n type UOWDecoder,\n} from \"./query/unit-of-work\";\n\nexport {\n type RetryPolicy,\n NoRetryPolicy,\n ExponentialBackoffRetryPolicy,\n LinearBackoffRetryPolicy,\n} from \"./query/retry-policy\";\n\nexport {\n executeUnitOfWork,\n type ExecuteUnitOfWorkResult,\n type ExecuteUnitOfWorkCallbacks,\n type ExecuteUnitOfWorkOptions,\n} from \"./query/execute-unit-of-work\";\n\nexport { type BoundServices } from \"@fragno-dev/core\";\n\nexport { internalFragmentDef } from \"./fragments/internal-fragment\";\n"],"mappings":";;;;;;;;;AASA,MAAa,2BAA2B;AACxC,MAAa,+BAA+B;AAO5C,SAAgB,iBAAiB,OAAoD;AACnF,KAAI,iBAAiB,eACnB,QAAO;AAGT,KAAI,OAAO,UAAU,YAAY,UAAU,KACzC,QAAO;AAGT,QACE,4BAA4B,SAC5B,MAAM,8BAA8B;;;;;;AAQxC,IAAa,iBAAb,MAA0E;CACxE;CACA;CACA;CAEA,YAAY,SAAiF;AAC3F,QAAKA,YAAa,QAAQ;AAC1B,QAAKC,SAAU,QAAQ;AACvB,QAAKC,UAAW,QAAQ;;CAG1B,KAAK,4BAA6D;AAChE,SAAO;;CAGT,MAAM,eAAsD;EAC1D,MAAM,YAAY,MAAM,MAAKA,QAAS,iBAAiB,MAAKF,UAAW;AACvE,MAAI,cAAc,MAAKC,OAAQ,QAAQ,UAAU,CAC/C,OAAM,IAAI,MACR,2FACuB,UAAU,sBAAsB,MAAKA,OAAQ,UACrE;AAGH,SAAO,MAAKC,QAAS,kBAAkB,MAAKD,QAAS,MAAKD,UAAW;;CAGvE,MAAM,gBAAkC;AACtC,MAAI,CAAC,MAAKE,QAAS,sBACjB,OAAM,IAAI,MAAM,mDAAmD;EAIrE,MAAM,oBAAoB,MADT,MAAKA,QAAS,sBAAsB,MAAKD,QAAS,MAAKD,UAAW,CAC1C,kBAAkB;AAC3D,QAAM,kBAAkB,SAAS;AAEjC,SAAO,kBAAkB,WAAW,SAAS;;CAG/C,IAAI,YAAY;AACd,SAAO,MAAKA;;CAGd,IAAI,SAAS;AACX,SAAO,MAAKC;;CAGd,IAAI,UAAuC;AACzC,SAAO,MAAKC"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
//#region ../../node_modules/.pnpm/rou3@0.7.8/node_modules/rou3/dist/index.mjs
|
|
2
|
+
const NullProtoObj = /* @__PURE__ */ (() => {
|
|
3
|
+
const e = function() {};
|
|
4
|
+
return e.prototype = Object.create(null), Object.freeze(e.prototype), e;
|
|
5
|
+
})();
|
|
6
|
+
/**
|
|
7
|
+
* Create a new router context.
|
|
8
|
+
*/
|
|
9
|
+
function createRouter() {
|
|
10
|
+
return {
|
|
11
|
+
root: { key: "" },
|
|
12
|
+
static: new NullProtoObj()
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function splitPath(path) {
|
|
16
|
+
const [_, ...s] = path.split("/");
|
|
17
|
+
return s[s.length - 1] === "" ? s.slice(0, -1) : s;
|
|
18
|
+
}
|
|
19
|
+
function getMatchParams(segments, paramsMap) {
|
|
20
|
+
const params = new NullProtoObj();
|
|
21
|
+
for (const [index, name] of paramsMap) {
|
|
22
|
+
const segment = index < 0 ? segments.slice(-1 * index).join("/") : segments[index];
|
|
23
|
+
if (typeof name === "string") params[name] = segment;
|
|
24
|
+
else {
|
|
25
|
+
const match = segment.match(name);
|
|
26
|
+
if (match) for (const key in match.groups) params[key] = match.groups[key];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return params;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Add a route to the router context.
|
|
33
|
+
*/
|
|
34
|
+
function addRoute(ctx, method = "", path, data) {
|
|
35
|
+
method = method.toUpperCase();
|
|
36
|
+
if (path.charCodeAt(0) !== 47) path = `/${path}`;
|
|
37
|
+
const segments = splitPath(path);
|
|
38
|
+
let node = ctx.root;
|
|
39
|
+
let _unnamedParamIndex = 0;
|
|
40
|
+
const paramsMap = [];
|
|
41
|
+
const paramsRegexp = [];
|
|
42
|
+
for (let i = 0; i < segments.length; i++) {
|
|
43
|
+
const segment = segments[i];
|
|
44
|
+
if (segment.startsWith("**")) {
|
|
45
|
+
if (!node.wildcard) node.wildcard = { key: "**" };
|
|
46
|
+
node = node.wildcard;
|
|
47
|
+
paramsMap.push([
|
|
48
|
+
-i,
|
|
49
|
+
segment.split(":")[1] || "_",
|
|
50
|
+
segment.length === 2
|
|
51
|
+
]);
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
if (segment === "*" || segment.includes(":")) {
|
|
55
|
+
if (!node.param) node.param = { key: "*" };
|
|
56
|
+
node = node.param;
|
|
57
|
+
if (segment === "*") paramsMap.push([
|
|
58
|
+
i,
|
|
59
|
+
`_${_unnamedParamIndex++}`,
|
|
60
|
+
true
|
|
61
|
+
]);
|
|
62
|
+
else if (segment.includes(":", 1)) {
|
|
63
|
+
const regexp = getParamRegexp(segment);
|
|
64
|
+
paramsRegexp[i] = regexp;
|
|
65
|
+
node.hasRegexParam = true;
|
|
66
|
+
paramsMap.push([
|
|
67
|
+
i,
|
|
68
|
+
regexp,
|
|
69
|
+
false
|
|
70
|
+
]);
|
|
71
|
+
} else paramsMap.push([
|
|
72
|
+
i,
|
|
73
|
+
segment.slice(1),
|
|
74
|
+
false
|
|
75
|
+
]);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
const child = node.static?.[segment];
|
|
79
|
+
if (child) node = child;
|
|
80
|
+
else {
|
|
81
|
+
const staticNode = { key: segment };
|
|
82
|
+
if (!node.static) node.static = new NullProtoObj();
|
|
83
|
+
node.static[segment] = staticNode;
|
|
84
|
+
node = staticNode;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const hasParams = paramsMap.length > 0;
|
|
88
|
+
if (!node.methods) node.methods = new NullProtoObj();
|
|
89
|
+
node.methods[method] ??= [];
|
|
90
|
+
node.methods[method].push({
|
|
91
|
+
data: data || null,
|
|
92
|
+
paramsRegexp,
|
|
93
|
+
paramsMap: hasParams ? paramsMap : void 0
|
|
94
|
+
});
|
|
95
|
+
if (!hasParams) ctx.static[path] = node;
|
|
96
|
+
}
|
|
97
|
+
function getParamRegexp(segment) {
|
|
98
|
+
const regex = segment.replace(/:(\w+)/g, (_, id) => `(?<${id}>[^/]+)`).replace(/\./g, "\\.");
|
|
99
|
+
return /* @__PURE__ */ new RegExp(`^${regex}$`);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Find a route by path.
|
|
103
|
+
*/
|
|
104
|
+
function findRoute(ctx, method = "", path, opts) {
|
|
105
|
+
if (path.charCodeAt(path.length - 1) === 47) path = path.slice(0, -1);
|
|
106
|
+
const staticNode = ctx.static[path];
|
|
107
|
+
if (staticNode && staticNode.methods) {
|
|
108
|
+
const staticMatch = staticNode.methods[method] || staticNode.methods[""];
|
|
109
|
+
if (staticMatch !== void 0) return staticMatch[0];
|
|
110
|
+
}
|
|
111
|
+
const segments = splitPath(path);
|
|
112
|
+
const match = _lookupTree(ctx, ctx.root, method, segments, 0)?.[0];
|
|
113
|
+
if (match === void 0) return;
|
|
114
|
+
if (opts?.params === false) return match;
|
|
115
|
+
return {
|
|
116
|
+
data: match.data,
|
|
117
|
+
params: match.paramsMap ? getMatchParams(segments, match.paramsMap) : void 0
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function _lookupTree(ctx, node, method, segments, index) {
|
|
121
|
+
if (index === segments.length) {
|
|
122
|
+
if (node.methods) {
|
|
123
|
+
const match = node.methods[method] || node.methods[""];
|
|
124
|
+
if (match) return match;
|
|
125
|
+
}
|
|
126
|
+
if (node.param && node.param.methods) {
|
|
127
|
+
const match = node.param.methods[method] || node.param.methods[""];
|
|
128
|
+
if (match) {
|
|
129
|
+
const pMap = match[0].paramsMap;
|
|
130
|
+
if (pMap?.[pMap?.length - 1]?.[2]) return match;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (node.wildcard && node.wildcard.methods) {
|
|
134
|
+
const match = node.wildcard.methods[method] || node.wildcard.methods[""];
|
|
135
|
+
if (match) {
|
|
136
|
+
const pMap = match[0].paramsMap;
|
|
137
|
+
if (pMap?.[pMap?.length - 1]?.[2]) return match;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const segment = segments[index];
|
|
143
|
+
if (node.static) {
|
|
144
|
+
const staticChild = node.static[segment];
|
|
145
|
+
if (staticChild) {
|
|
146
|
+
const match = _lookupTree(ctx, staticChild, method, segments, index + 1);
|
|
147
|
+
if (match) return match;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (node.param) {
|
|
151
|
+
const match = _lookupTree(ctx, node.param, method, segments, index + 1);
|
|
152
|
+
if (match) {
|
|
153
|
+
if (node.param.hasRegexParam) {
|
|
154
|
+
const exactMatch = match.find((m) => m.paramsRegexp[index]?.test(segment)) || match.find((m) => !m.paramsRegexp[index]);
|
|
155
|
+
return exactMatch ? [exactMatch] : void 0;
|
|
156
|
+
}
|
|
157
|
+
return match;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (node.wildcard && node.wildcard.methods) return node.wildcard.methods[method] || node.wildcard.methods[""];
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
//#endregion
|
|
164
|
+
export { addRoute, createRouter, findRoute };
|
|
165
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../../../../../../../node_modules/.pnpm/rou3@0.7.8/node_modules/rou3/dist/index.mjs"],"sourcesContent":["//#region src/object.ts\nconst NullProtoObj = /* @__PURE__ */ (() => {\n\tconst e = function() {};\n\treturn e.prototype = Object.create(null), Object.freeze(e.prototype), e;\n})();\n\n//#endregion\n//#region src/context.ts\n/**\n* Create a new router context.\n*/\nfunction createRouter() {\n\tconst ctx = {\n\t\troot: { key: \"\" },\n\t\tstatic: new NullProtoObj()\n\t};\n\treturn ctx;\n}\n\n//#endregion\n//#region src/operations/_utils.ts\nfunction splitPath(path) {\n\tconst [_, ...s] = path.split(\"/\");\n\treturn s[s.length - 1] === \"\" ? s.slice(0, -1) : s;\n}\nfunction getMatchParams(segments, paramsMap) {\n\tconst params = new NullProtoObj();\n\tfor (const [index, name] of paramsMap) {\n\t\tconst segment = index < 0 ? segments.slice(-1 * index).join(\"/\") : segments[index];\n\t\tif (typeof name === \"string\") params[name] = segment;\n\t\telse {\n\t\t\tconst match = segment.match(name);\n\t\t\tif (match) for (const key in match.groups) params[key] = match.groups[key];\n\t\t}\n\t}\n\treturn params;\n}\n\n//#endregion\n//#region src/operations/add.ts\n/**\n* Add a route to the router context.\n*/\nfunction addRoute(ctx, method = \"\", path, data) {\n\tmethod = method.toUpperCase();\n\tif (path.charCodeAt(0) !== 47) path = `/${path}`;\n\tconst segments = splitPath(path);\n\tlet node = ctx.root;\n\tlet _unnamedParamIndex = 0;\n\tconst paramsMap = [];\n\tconst paramsRegexp = [];\n\tfor (let i = 0; i < segments.length; i++) {\n\t\tconst segment = segments[i];\n\t\tif (segment.startsWith(\"**\")) {\n\t\t\tif (!node.wildcard) node.wildcard = { key: \"**\" };\n\t\t\tnode = node.wildcard;\n\t\t\tparamsMap.push([\n\t\t\t\t-i,\n\t\t\t\tsegment.split(\":\")[1] || \"_\",\n\t\t\t\tsegment.length === 2\n\t\t\t]);\n\t\t\tbreak;\n\t\t}\n\t\tif (segment === \"*\" || segment.includes(\":\")) {\n\t\t\tif (!node.param) node.param = { key: \"*\" };\n\t\t\tnode = node.param;\n\t\t\tif (segment === \"*\") paramsMap.push([\n\t\t\t\ti,\n\t\t\t\t`_${_unnamedParamIndex++}`,\n\t\t\t\ttrue\n\t\t\t]);\n\t\t\telse if (segment.includes(\":\", 1)) {\n\t\t\t\tconst regexp = getParamRegexp(segment);\n\t\t\t\tparamsRegexp[i] = regexp;\n\t\t\t\tnode.hasRegexParam = true;\n\t\t\t\tparamsMap.push([\n\t\t\t\t\ti,\n\t\t\t\t\tregexp,\n\t\t\t\t\tfalse\n\t\t\t\t]);\n\t\t\t} else paramsMap.push([\n\t\t\t\ti,\n\t\t\t\tsegment.slice(1),\n\t\t\t\tfalse\n\t\t\t]);\n\t\t\tcontinue;\n\t\t}\n\t\tconst child = node.static?.[segment];\n\t\tif (child) node = child;\n\t\telse {\n\t\t\tconst staticNode = { key: segment };\n\t\t\tif (!node.static) node.static = new NullProtoObj();\n\t\t\tnode.static[segment] = staticNode;\n\t\t\tnode = staticNode;\n\t\t}\n\t}\n\tconst hasParams = paramsMap.length > 0;\n\tif (!node.methods) node.methods = new NullProtoObj();\n\tnode.methods[method] ??= [];\n\tnode.methods[method].push({\n\t\tdata: data || null,\n\t\tparamsRegexp,\n\t\tparamsMap: hasParams ? paramsMap : void 0\n\t});\n\tif (!hasParams) ctx.static[path] = node;\n}\nfunction getParamRegexp(segment) {\n\tconst regex = segment.replace(/:(\\w+)/g, (_, id) => `(?<${id}>[^/]+)`).replace(/\\./g, \"\\\\.\");\n\treturn new RegExp(`^${regex}$`);\n}\n\n//#endregion\n//#region src/operations/find.ts\n/**\n* Find a route by path.\n*/\nfunction findRoute(ctx, method = \"\", path, opts) {\n\tif (path.charCodeAt(path.length - 1) === 47) path = path.slice(0, -1);\n\tconst staticNode = ctx.static[path];\n\tif (staticNode && staticNode.methods) {\n\t\tconst staticMatch = staticNode.methods[method] || staticNode.methods[\"\"];\n\t\tif (staticMatch !== void 0) return staticMatch[0];\n\t}\n\tconst segments = splitPath(path);\n\tconst match = _lookupTree(ctx, ctx.root, method, segments, 0)?.[0];\n\tif (match === void 0) return;\n\tif (opts?.params === false) return match;\n\treturn {\n\t\tdata: match.data,\n\t\tparams: match.paramsMap ? getMatchParams(segments, match.paramsMap) : void 0\n\t};\n}\nfunction _lookupTree(ctx, node, method, segments, index) {\n\tif (index === segments.length) {\n\t\tif (node.methods) {\n\t\t\tconst match = node.methods[method] || node.methods[\"\"];\n\t\t\tif (match) return match;\n\t\t}\n\t\tif (node.param && node.param.methods) {\n\t\t\tconst match = node.param.methods[method] || node.param.methods[\"\"];\n\t\t\tif (match) {\n\t\t\t\tconst pMap = match[0].paramsMap;\n\t\t\t\tif (pMap?.[pMap?.length - 1]?.[2]) return match;\n\t\t\t}\n\t\t}\n\t\tif (node.wildcard && node.wildcard.methods) {\n\t\t\tconst match = node.wildcard.methods[method] || node.wildcard.methods[\"\"];\n\t\t\tif (match) {\n\t\t\t\tconst pMap = match[0].paramsMap;\n\t\t\t\tif (pMap?.[pMap?.length - 1]?.[2]) return match;\n\t\t\t}\n\t\t}\n\t\treturn void 0;\n\t}\n\tconst segment = segments[index];\n\tif (node.static) {\n\t\tconst staticChild = node.static[segment];\n\t\tif (staticChild) {\n\t\t\tconst match = _lookupTree(ctx, staticChild, method, segments, index + 1);\n\t\t\tif (match) return match;\n\t\t}\n\t}\n\tif (node.param) {\n\t\tconst match = _lookupTree(ctx, node.param, method, segments, index + 1);\n\t\tif (match) {\n\t\t\tif (node.param.hasRegexParam) {\n\t\t\t\tconst exactMatch = match.find((m) => m.paramsRegexp[index]?.test(segment)) || match.find((m) => !m.paramsRegexp[index]);\n\t\t\t\treturn exactMatch ? [exactMatch] : void 0;\n\t\t\t}\n\t\t\treturn match;\n\t\t}\n\t}\n\tif (node.wildcard && node.wildcard.methods) return node.wildcard.methods[method] || node.wildcard.methods[\"\"];\n\treturn;\n}\n\n//#endregion\n//#region src/operations/remove.ts\n/**\n* Remove a route from the router context.\n*/\nfunction removeRoute(ctx, method, path) {\n\tconst segments = splitPath(path);\n\treturn _remove(ctx.root, method || \"\", segments, 0);\n}\nfunction _remove(node, method, segments, index) {\n\tif (index === segments.length) {\n\t\tif (node.methods && method in node.methods) {\n\t\t\tdelete node.methods[method];\n\t\t\tif (Object.keys(node.methods).length === 0) node.methods = void 0;\n\t\t}\n\t\treturn;\n\t}\n\tconst segment = segments[index];\n\tif (segment === \"*\") {\n\t\tif (node.param) {\n\t\t\t_remove(node.param, method, segments, index + 1);\n\t\t\tif (_isEmptyNode(node.param)) node.param = void 0;\n\t\t}\n\t\treturn;\n\t}\n\tif (segment.startsWith(\"**\")) {\n\t\tif (node.wildcard) {\n\t\t\t_remove(node.wildcard, method, segments, index + 1);\n\t\t\tif (_isEmptyNode(node.wildcard)) node.wildcard = void 0;\n\t\t}\n\t\treturn;\n\t}\n\tconst childNode = node.static?.[segment];\n\tif (childNode) {\n\t\t_remove(childNode, method, segments, index + 1);\n\t\tif (_isEmptyNode(childNode)) {\n\t\t\tdelete node.static[segment];\n\t\t\tif (Object.keys(node.static).length === 0) node.static = void 0;\n\t\t}\n\t}\n}\nfunction _isEmptyNode(node) {\n\treturn node.methods === void 0 && node.static === void 0 && node.param === void 0 && node.wildcard === void 0;\n}\n\n//#endregion\n//#region src/operations/find-all.ts\n/**\n* Find all route patterns that match the given path.\n*/\nfunction findAllRoutes(ctx, method = \"\", path, opts) {\n\tif (path.charCodeAt(path.length - 1) === 47) path = path.slice(0, -1);\n\tconst segments = splitPath(path);\n\tconst matches = _findAll(ctx, ctx.root, method, segments, 0);\n\tif (opts?.params === false) return matches;\n\treturn matches.map((m) => {\n\t\treturn {\n\t\t\tdata: m.data,\n\t\t\tparams: m.paramsMap ? getMatchParams(segments, m.paramsMap) : void 0\n\t\t};\n\t});\n}\nfunction _findAll(ctx, node, method, segments, index, matches = []) {\n\tconst segment = segments[index];\n\tif (node.wildcard && node.wildcard.methods) {\n\t\tconst match = node.wildcard.methods[method] || node.wildcard.methods[\"\"];\n\t\tif (match) matches.push(...match);\n\t}\n\tif (node.param) {\n\t\t_findAll(ctx, node.param, method, segments, index + 1, matches);\n\t\tif (index === segments.length && node.param.methods) {\n\t\t\tconst match = node.param.methods[method] || node.param.methods[\"\"];\n\t\t\tif (match) {\n\t\t\t\tconst pMap = match[0].paramsMap;\n\t\t\t\tif (pMap?.[pMap?.length - 1]?.[2]) matches.push(...match);\n\t\t\t}\n\t\t}\n\t}\n\tconst staticChild = node.static?.[segment];\n\tif (staticChild) _findAll(ctx, staticChild, method, segments, index + 1, matches);\n\tif (index === segments.length && node.methods) {\n\t\tconst match = node.methods[method] || node.methods[\"\"];\n\t\tif (match) matches.push(...match);\n\t}\n\treturn matches;\n}\n\n//#endregion\n//#region src/regexp.ts\nfunction routeToRegExp(route = \"/\") {\n\tconst reSegments = [];\n\tlet idCtr = 0;\n\tfor (const segment of route.split(\"/\")) {\n\t\tif (!segment) continue;\n\t\tif (segment === \"*\") reSegments.push(`(?<_${idCtr++}>[^/]*)`);\n\t\telse if (segment.startsWith(\"**\")) reSegments.push(segment === \"**\" ? \"?(?<_>.*)\" : `?(?<${segment.slice(3)}>.+)`);\n\t\telse if (segment.includes(\":\")) reSegments.push(segment.replace(/:(\\w+)/g, (_, id) => `(?<${id}>[^/]+)`).replace(/\\./g, \"\\\\.\"));\n\t\telse reSegments.push(segment);\n\t}\n\treturn new RegExp(`^/${reSegments.join(\"/\")}/?$`);\n}\n\n//#endregion\nexport { NullProtoObj, addRoute, createRouter, findAllRoutes, findRoute, removeRoute, routeToRegExp };"],"x_google_ignoreList":[0],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//#region ../fragno/dist/api/bind-services.js
|
|
2
|
+
/**
|
|
3
|
+
* Bind all functions in a service object to a specific context.
|
|
4
|
+
* This allows services to use `this` to access the context.
|
|
5
|
+
*
|
|
6
|
+
* @param services - The service object to bind
|
|
7
|
+
* @param context - The context to bind to (e.g., { getUnitOfWork })
|
|
8
|
+
* @returns A new object with all functions bound to the context
|
|
9
|
+
*/
|
|
10
|
+
function bindServicesToContext(services, context) {
|
|
11
|
+
const bound = {};
|
|
12
|
+
for (const [key, value] of Object.entries(services)) if (typeof value === "function") bound[key] = value.bind(context);
|
|
13
|
+
else if (value && typeof value === "object" && !Array.isArray(value)) bound[key] = bindServicesToContext(value, context);
|
|
14
|
+
else bound[key] = value;
|
|
15
|
+
return bound;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
//#endregion
|
|
19
|
+
export { bindServicesToContext };
|
|
20
|
+
//# sourceMappingURL=bind-services.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bind-services.js","names":[],"sources":["../../../../../../fragno/dist/api/bind-services.js"],"sourcesContent":["//#region src/api/bind-services.ts\n/**\n* Bind all functions in a service object to a specific context.\n* This allows services to use `this` to access the context.\n*\n* @param services - The service object to bind\n* @param context - The context to bind to (e.g., { getUnitOfWork })\n* @returns A new object with all functions bound to the context\n*/\nfunction bindServicesToContext(services, context) {\n\tconst bound = {};\n\tfor (const [key, value] of Object.entries(services)) if (typeof value === \"function\") bound[key] = value.bind(context);\n\telse if (value && typeof value === \"object\" && !Array.isArray(value)) bound[key] = bindServicesToContext(value, context);\n\telse bound[key] = value;\n\treturn bound;\n}\n\n//#endregion\nexport { bindServicesToContext };\n//# sourceMappingURL=bind-services.js.map"],"mappings":";;;;;;;;;AASA,SAAS,sBAAsB,UAAU,SAAS;CACjD,MAAM,QAAQ,EAAE;AAChB,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,SAAS,CAAE,KAAI,OAAO,UAAU,WAAY,OAAM,OAAO,MAAM,KAAK,QAAQ;UAC7G,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,MAAM,CAAE,OAAM,OAAO,sBAAsB,OAAO,QAAQ;KACnH,OAAM,OAAO;AAClB,QAAO"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
//#region ../fragno/dist/api/error.js
|
|
2
|
+
var FragnoApiError = class extends Error {
|
|
3
|
+
#status;
|
|
4
|
+
#code;
|
|
5
|
+
constructor({ message, code }, status) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "FragnoApiError";
|
|
8
|
+
this.#status = status;
|
|
9
|
+
this.#code = code;
|
|
10
|
+
}
|
|
11
|
+
get status() {
|
|
12
|
+
return this.#status;
|
|
13
|
+
}
|
|
14
|
+
get code() {
|
|
15
|
+
return this.#code;
|
|
16
|
+
}
|
|
17
|
+
toResponse() {
|
|
18
|
+
return Response.json({
|
|
19
|
+
message: this.message,
|
|
20
|
+
code: this.code
|
|
21
|
+
}, { status: this.status });
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
var FragnoApiValidationError = class extends FragnoApiError {
|
|
25
|
+
#issues;
|
|
26
|
+
constructor(message, issues) {
|
|
27
|
+
super({
|
|
28
|
+
message,
|
|
29
|
+
code: "FRAGNO_VALIDATION_ERROR"
|
|
30
|
+
}, 400);
|
|
31
|
+
this.name = "FragnoApiValidationError";
|
|
32
|
+
this.#issues = issues;
|
|
33
|
+
}
|
|
34
|
+
get issues() {
|
|
35
|
+
return this.#issues;
|
|
36
|
+
}
|
|
37
|
+
toResponse() {
|
|
38
|
+
return Response.json({
|
|
39
|
+
message: this.message,
|
|
40
|
+
issues: this.#issues,
|
|
41
|
+
code: this.code
|
|
42
|
+
}, { status: this.status });
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
//#endregion
|
|
47
|
+
export { FragnoApiError, FragnoApiValidationError };
|
|
48
|
+
//# sourceMappingURL=error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error.js","names":["#status","#code","#issues"],"sources":["../../../../../../fragno/dist/api/error.js"],"sourcesContent":["//#region src/api/error.ts\nvar FragnoApiError = class extends Error {\n\t#status;\n\t#code;\n\tconstructor({ message, code }, status) {\n\t\tsuper(message);\n\t\tthis.name = \"FragnoApiError\";\n\t\tthis.#status = status;\n\t\tthis.#code = code;\n\t}\n\tget status() {\n\t\treturn this.#status;\n\t}\n\tget code() {\n\t\treturn this.#code;\n\t}\n\ttoResponse() {\n\t\treturn Response.json({\n\t\t\tmessage: this.message,\n\t\t\tcode: this.code\n\t\t}, { status: this.status });\n\t}\n};\nvar FragnoApiValidationError = class extends FragnoApiError {\n\t#issues;\n\tconstructor(message, issues) {\n\t\tsuper({\n\t\t\tmessage,\n\t\t\tcode: \"FRAGNO_VALIDATION_ERROR\"\n\t\t}, 400);\n\t\tthis.name = \"FragnoApiValidationError\";\n\t\tthis.#issues = issues;\n\t}\n\tget issues() {\n\t\treturn this.#issues;\n\t}\n\ttoResponse() {\n\t\treturn Response.json({\n\t\t\tmessage: this.message,\n\t\t\tissues: this.#issues,\n\t\t\tcode: this.code\n\t\t}, { status: this.status });\n\t}\n};\n\n//#endregion\nexport { FragnoApiError, FragnoApiValidationError };\n//# sourceMappingURL=error.js.map"],"mappings":";AACA,IAAI,iBAAiB,cAAc,MAAM;CACxC;CACA;CACA,YAAY,EAAE,SAAS,QAAQ,QAAQ;AACtC,QAAM,QAAQ;AACd,OAAK,OAAO;AACZ,QAAKA,SAAU;AACf,QAAKC,OAAQ;;CAEd,IAAI,SAAS;AACZ,SAAO,MAAKD;;CAEb,IAAI,OAAO;AACV,SAAO,MAAKC;;CAEb,aAAa;AACZ,SAAO,SAAS,KAAK;GACpB,SAAS,KAAK;GACd,MAAM,KAAK;GACX,EAAE,EAAE,QAAQ,KAAK,QAAQ,CAAC;;;AAG7B,IAAI,2BAA2B,cAAc,eAAe;CAC3D;CACA,YAAY,SAAS,QAAQ;AAC5B,QAAM;GACL;GACA,MAAM;GACN,EAAE,IAAI;AACP,OAAK,OAAO;AACZ,QAAKC,SAAU;;CAEhB,IAAI,SAAS;AACZ,SAAO,MAAKA;;CAEb,aAAa;AACZ,SAAO,SAAS,KAAK;GACpB,SAAS,KAAK;GACd,QAAQ,MAAKA;GACb,MAAM,KAAK;GACX,EAAE,EAAE,QAAQ,KAAK,QAAQ,CAAC"}
|