@softure-ai/db 0.0.0-stage → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/LICENSE +21 -0
  3. package/README.md +383 -2
  4. package/dist/cli/bin.d.ts +3 -0
  5. package/dist/cli/bin.d.ts.map +1 -0
  6. package/dist/cli/bin.js +5 -0
  7. package/dist/cli/bin.js.map +1 -0
  8. package/dist/cli/command.d.ts +8 -0
  9. package/dist/cli/command.d.ts.map +1 -0
  10. package/dist/cli/command.js +38 -0
  11. package/dist/cli/command.js.map +1 -0
  12. package/dist/cli/index.d.ts +3 -0
  13. package/dist/cli/index.d.ts.map +1 -0
  14. package/dist/cli/index.js +4 -0
  15. package/dist/cli/index.js.map +1 -0
  16. package/dist/cli/run.d.ts +27 -0
  17. package/dist/cli/run.d.ts.map +1 -0
  18. package/dist/cli/run.js +205 -0
  19. package/dist/cli/run.js.map +1 -0
  20. package/dist/client.d.ts +73 -0
  21. package/dist/client.d.ts.map +1 -0
  22. package/dist/client.js +90 -0
  23. package/dist/client.js.map +1 -0
  24. package/dist/configured.d.ts +31 -0
  25. package/dist/configured.d.ts.map +1 -0
  26. package/dist/configured.js +74 -0
  27. package/dist/configured.js.map +1 -0
  28. package/dist/index.d.ts +12 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.js +13 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/migrations/adopt.d.ts +33 -0
  33. package/dist/migrations/adopt.d.ts.map +1 -0
  34. package/dist/migrations/adopt.js +97 -0
  35. package/dist/migrations/adopt.js.map +1 -0
  36. package/dist/migrations/apply.d.ts +35 -0
  37. package/dist/migrations/apply.d.ts.map +1 -0
  38. package/dist/migrations/apply.js +115 -0
  39. package/dist/migrations/apply.js.map +1 -0
  40. package/dist/migrations/export.d.ts +10 -0
  41. package/dist/migrations/export.d.ts.map +1 -0
  42. package/dist/migrations/export.js +58 -0
  43. package/dist/migrations/export.js.map +1 -0
  44. package/dist/migrations/exported.d.ts +20 -0
  45. package/dist/migrations/exported.d.ts.map +1 -0
  46. package/dist/migrations/exported.js +61 -0
  47. package/dist/migrations/exported.js.map +1 -0
  48. package/dist/migrations/files.d.ts +21 -0
  49. package/dist/migrations/files.d.ts.map +1 -0
  50. package/dist/migrations/files.js +112 -0
  51. package/dist/migrations/files.js.map +1 -0
  52. package/dist/migrations/introspect.d.ts +6 -0
  53. package/dist/migrations/introspect.d.ts.map +1 -0
  54. package/dist/migrations/introspect.js +117 -0
  55. package/dist/migrations/introspect.js.map +1 -0
  56. package/dist/migrations/ledger.d.ts +27 -0
  57. package/dist/migrations/ledger.d.ts.map +1 -0
  58. package/dist/migrations/ledger.js +45 -0
  59. package/dist/migrations/ledger.js.map +1 -0
  60. package/dist/migrations/migrator.d.ts +97 -0
  61. package/dist/migrations/migrator.d.ts.map +1 -0
  62. package/dist/migrations/migrator.js +301 -0
  63. package/dist/migrations/migrator.js.map +1 -0
  64. package/dist/migrations/problems.d.ts +93 -0
  65. package/dist/migrations/problems.d.ts.map +1 -0
  66. package/dist/migrations/problems.js +62 -0
  67. package/dist/migrations/problems.js.map +1 -0
  68. package/dist/migrations/reference.d.ts +23 -0
  69. package/dist/migrations/reference.d.ts.map +1 -0
  70. package/dist/migrations/reference.js +64 -0
  71. package/dist/migrations/reference.js.map +1 -0
  72. package/dist/migrations/session.d.ts +15 -0
  73. package/dist/migrations/session.d.ts.map +1 -0
  74. package/dist/migrations/session.js +56 -0
  75. package/dist/migrations/session.js.map +1 -0
  76. package/dist/migrations/stubs.d.ts +23 -0
  77. package/dist/migrations/stubs.d.ts.map +1 -0
  78. package/dist/migrations/stubs.js +99 -0
  79. package/dist/migrations/stubs.js.map +1 -0
  80. package/dist/shared.d.ts +9 -0
  81. package/dist/shared.d.ts.map +1 -0
  82. package/dist/shared.js +32 -0
  83. package/dist/shared.js.map +1 -0
  84. package/dist/testing.d.ts +23 -0
  85. package/dist/testing.d.ts.map +1 -0
  86. package/dist/testing.js +62 -0
  87. package/dist/testing.js.map +1 -0
  88. package/package.json +73 -4
  89. package/src/cli/bin.ts +5 -0
  90. package/src/cli/command.ts +47 -0
  91. package/src/cli/index.ts +3 -0
  92. package/src/cli/run.ts +250 -0
  93. package/src/client.ts +151 -0
  94. package/src/configured.ts +109 -0
  95. package/src/index.ts +46 -0
  96. package/src/migrations/adopt.ts +136 -0
  97. package/src/migrations/apply.ts +135 -0
  98. package/src/migrations/export.ts +66 -0
  99. package/src/migrations/exported.ts +82 -0
  100. package/src/migrations/files.ts +129 -0
  101. package/src/migrations/introspect.ts +165 -0
  102. package/src/migrations/ledger.ts +75 -0
  103. package/src/migrations/migrator.ts +403 -0
  104. package/src/migrations/problems.ts +99 -0
  105. package/src/migrations/reference.ts +78 -0
  106. package/src/migrations/session.ts +74 -0
  107. package/src/migrations/stubs.ts +139 -0
  108. package/src/shared.ts +36 -0
  109. package/src/testing.ts +80 -0
@@ -0,0 +1,97 @@
1
+ // Adoption (docs/02-module-standard.md §4, docs/05-adoption-playbook.md step 3): an existing app
2
+ // has moved its own tables into a module's schema with its own migration. `adoptModule` checks
3
+ // that the schema now equals what the module's files 1..through would create, then records those
4
+ // files as `adopted` without running them; the later files stay pending for `migrate`. Any
5
+ // difference refuses: marking a schema as migrated when it is not is the one mistake a later
6
+ // migration cannot recover from. The pending files of the modules it depends on run first.
7
+ import { ok } from "@softure-ai/core";
8
+ import { applyFile, recordAdoption, withMigrationLock } from "./apply.js";
9
+ import { describeSchema, diffSchemas } from "./introspect.js";
10
+ import { LEDGER_MODULE_ID, readJournal } from "./ledger.js";
11
+ import { checkThrough, compareJournal, prepareUnits, toStep } from "./migrator.js";
12
+ import { failWith } from "./problems.js";
13
+ import { buildReferenceSchema, collectWithDependencies, listOwnedSchemas } from "./reference.js";
14
+ import { withSession } from "./session.js";
15
+ import { readAppTables } from "./stubs.js";
16
+ export async function adoptModule(handle, options) {
17
+ const target = options.modules.find((module) => module.id === options.module);
18
+ if (target === undefined) {
19
+ return failWith([{ code: "db.adopt_unknown_module", module: options.module }]);
20
+ }
21
+ if (target.manifest.version !== options.version) {
22
+ return failWith([{ code: "db.adopt_version_mismatch", module: target.id, requested: options.version, enabled: target.manifest.version }]);
23
+ }
24
+ const units = await prepareUnits(options.modules, options.migrationsDir);
25
+ if (!units.ok) {
26
+ return units;
27
+ }
28
+ const unit = units.value.find((candidate) => candidate.module === target.id);
29
+ if (unit === undefined) {
30
+ return failWith([{ code: "db.adopt_no_schema", module: target.id }]);
31
+ }
32
+ const through = options.through ?? unit.files.length;
33
+ const throughProblems = checkThrough(unit, through);
34
+ if (throughProblems.length > 0) {
35
+ return failWith(throughProblems);
36
+ }
37
+ return withSession(handle, (session) => withMigrationLock(session, async () => {
38
+ const journal = await readJournal(session);
39
+ const comparison = compareJournal(units.value, journal);
40
+ const problems = [...comparison.problems, ...checkAdoptable(unit, journal)];
41
+ if (problems.length > 0) {
42
+ return failWith(problems);
43
+ }
44
+ const dependencyIds = collectWithDependencies(options.modules, target.id);
45
+ dependencyIds.delete(target.id);
46
+ const ledger = comparison.pending.filter((step) => step.module === LEDGER_MODULE_ID);
47
+ const dependencies = comparison.pending.filter((step) => dependencyIds.has(step.module));
48
+ const files = unit.files.filter((file) => file.version <= through);
49
+ const adopted = files.map((file) => toStep(unit, file));
50
+ const report = { module: unit.module, version: options.version, dryRun: options.dryRun === true, ledger, dependencies, adopted };
51
+ // Built from the live database's app tables (module SQL may reference them, issue #170), so
52
+ // inside the lock and only once the checks above passed.
53
+ const appTables = await readAppTables(session, listOwnedSchemas(units.value));
54
+ const reference = await buildReferenceSchema({ modules: options.modules, units: units.value, target: unit, through, appTables });
55
+ if (!reference.ok) {
56
+ return reference;
57
+ }
58
+ // The target's own schema only, compared before anything is written: applying a dependency
59
+ // never changes it.
60
+ const differences = diffSchemas(reference.value, await describeSchema(session, unit.schema));
61
+ if (differences.length > 0) {
62
+ return failWith([{ code: "db.schema_mismatch", module: unit.module, schema: unit.schema, differences }]);
63
+ }
64
+ if (options.dryRun === true) {
65
+ return ok(report);
66
+ }
67
+ const ledgerFailure = await applyPending(session, units.value, ledger);
68
+ if (ledgerFailure !== null) {
69
+ return failWith([ledgerFailure.problem]);
70
+ }
71
+ const dependencyFailure = await applyPending(session, units.value, dependencies);
72
+ if (dependencyFailure !== null) {
73
+ return failWith([dependencyFailure.problem, { code: "db.adopt_dependency_pending", module: unit.module, dependency: dependencyFailure.step.module }]);
74
+ }
75
+ const failure = await recordAdoption(session, unit, files);
76
+ return failure === null ? ok(report) : failWith([failure]);
77
+ }));
78
+ }
79
+ /** The adopted module must be new to the ledger. */
80
+ function checkAdoptable(unit, journal) {
81
+ return journal.some((row) => row.module === unit.module) ? [{ code: "db.adopt_already_applied", module: unit.module }] : [];
82
+ }
83
+ /** Applies the steps in order, each file in its own transaction; stops at the first failure. */
84
+ async function applyPending(session, units, steps) {
85
+ for (const step of steps) {
86
+ const unit = units.find((candidate) => candidate.module === step.module);
87
+ const file = unit?.files.find((candidate) => candidate.version === step.version);
88
+ if (unit === undefined || file === undefined) {
89
+ throw new Error(`adoptModule: step ${step.module} ${step.version} has no file; compareJournal is broken`);
90
+ }
91
+ const failure = await applyFile(session, { unit, file, method: "applied" });
92
+ if (failure !== null)
93
+ return { problem: failure, step };
94
+ }
95
+ return null;
96
+ }
97
+ //# sourceMappingURL=adopt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adopt.js","sourceRoot":"","sources":["../../src/migrations/adopt.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ import type { MigrationFile } from "./files.js";
2
+ import { type MigrationMethod } from "./ledger.js";
3
+ import type { MigrationProblem } from "./problems.js";
4
+ import type { MigrationSession } from "./session.js";
5
+ /** One migration owner: the ledger itself or an enabled module with a schema. */
6
+ export interface MigrationUnit {
7
+ readonly module: string;
8
+ readonly schema: string;
9
+ readonly moduleVersion: string;
10
+ readonly files: readonly MigrationFile[];
11
+ }
12
+ /**
13
+ * Runs one file and its ledger row in one transaction, inside the unit's schema. Returns the
14
+ * problem when the SQL fails; the transaction is rolled back first.
15
+ */
16
+ export declare function applyFile(session: MigrationSession, input: {
17
+ unit: MigrationUnit;
18
+ file: MigrationFile;
19
+ method: MigrationMethod;
20
+ }): Promise<MigrationProblem | null>;
21
+ /**
22
+ * Rolls the open transaction back and returns the reason to report. A failing ROLLBACK (a lost
23
+ * connection) is added to the reason instead of hiding the original error.
24
+ */
25
+ export declare function rollBack(session: MigrationSession, error: unknown): Promise<string>;
26
+ /** Holds the session-level advisory lock while `run` runs; a second runner waits for it. */
27
+ export declare function withMigrationLock<T>(session: MigrationSession, run: () => Promise<T>): Promise<T>;
28
+ /**
29
+ * Records `files` of the unit as adopted, all in one transaction: either every row is written or
30
+ * none. Returns the problem when a write fails.
31
+ */
32
+ export declare function recordAdoption(session: MigrationSession, unit: MigrationUnit, files: readonly MigrationFile[]): Promise<MigrationProblem | null>;
33
+ export declare function quoteIdentifier(name: string): string;
34
+ export declare function describeError(error: unknown): string;
35
+ //# sourceMappingURL=apply.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply.d.ts","sourceRoot":"","sources":["../../src/migrations/apply.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AACpE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,iFAAiF;AACjF,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,KAAK,EAAE,SAAS,aAAa,EAAE,CAAC;CAC1C;AAKD;;;GAGG;AACH,wBAAsB,SAAS,CAC7B,OAAO,EAAE,gBAAgB,EACzB,KAAK,EAAE;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,IAAI,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,eAAe,CAAA;CAAE,GAC3E,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CA6BlC;AAeD;;;GAGG;AACH,wBAAsB,QAAQ,CAAC,OAAO,EAAE,gBAAgB,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAQzF;AAED,4FAA4F;AAC5F,wBAAsB,iBAAiB,CAAC,CAAC,EAAE,OAAO,EAAE,gBAAgB,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAgBvG;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,gBAAgB,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,SAAS,aAAa,EAAE,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAoBtJ;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEpD"}
@@ -0,0 +1,115 @@
1
+ import { recordMigration } from "./ledger.js";
2
+ // Any constant works; it only has to be the same for every runner of every app.
3
+ const MIGRATION_LOCK_KEY = "73012026";
4
+ /**
5
+ * Runs one file and its ledger row in one transaction, inside the unit's schema. Returns the
6
+ * problem when the SQL fails; the transaction is rolled back first.
7
+ */
8
+ export async function applyFile(session, input) {
9
+ const { unit, file, method } = input;
10
+ // The schema passed the manifest's lower_snake_case rule and checkModule; quoting is a second line.
11
+ const schema = quoteIdentifier(unit.schema);
12
+ try {
13
+ await session.exec(`BEGIN; CREATE SCHEMA IF NOT EXISTS ${schema}; SET LOCAL search_path TO ${schema}, public;`);
14
+ const transactionId = await readTransactionId(session);
15
+ if (method === "applied") {
16
+ await session.exec(file.sql);
17
+ }
18
+ // Second line behind findTransactionControl: a file that still ended the transaction left
19
+ // part of itself committed, so it must not be reported as cleanly rolled back.
20
+ if ((await readTransactionId(session)) !== transactionId) {
21
+ throw new TransactionEndedError();
22
+ }
23
+ await recordMigration(session, {
24
+ module: unit.module,
25
+ version: file.version,
26
+ name: file.name,
27
+ checksum: file.checksum,
28
+ moduleVersion: unit.moduleVersion,
29
+ method,
30
+ });
31
+ await session.exec("COMMIT");
32
+ return null;
33
+ }
34
+ catch (error) {
35
+ const reason = await rollBack(session, error);
36
+ return { code: "db.migration_failed", module: unit.module, version: file.version, name: file.name, reason };
37
+ }
38
+ }
39
+ class TransactionEndedError extends Error {
40
+ constructor() {
41
+ super("the file ended the migrator's transaction, so the statements before that point may be committed; check the schema by hand");
42
+ }
43
+ }
44
+ // The transaction id, not now(): two transactions can share a start time (PGlite's clock ticks in
45
+ // milliseconds), but never an id.
46
+ async function readTransactionId(session) {
47
+ const [row] = await session.query("SELECT pg_current_xact_id()::text AS id");
48
+ return row?.id ?? "";
49
+ }
50
+ /**
51
+ * Rolls the open transaction back and returns the reason to report. A failing ROLLBACK (a lost
52
+ * connection) is added to the reason instead of hiding the original error.
53
+ */
54
+ export async function rollBack(session, error) {
55
+ const reason = describeError(error);
56
+ try {
57
+ await session.exec("ROLLBACK");
58
+ return reason;
59
+ }
60
+ catch (rollbackError) {
61
+ return `${reason} (and ROLLBACK failed: ${describeError(rollbackError)})`;
62
+ }
63
+ }
64
+ /** Holds the session-level advisory lock while `run` runs; a second runner waits for it. */
65
+ export async function withMigrationLock(session, run) {
66
+ await session.query("SELECT pg_advisory_lock($1)", [MIGRATION_LOCK_KEY]);
67
+ let result;
68
+ try {
69
+ result = await run();
70
+ }
71
+ catch (error) {
72
+ // The run's own error wins; an unlock failure here is secondary (the session is discarded).
73
+ await session.query("SELECT pg_advisory_unlock($1)", [MIGRATION_LOCK_KEY]).catch((unlockError) => {
74
+ throw new Error(`migration run failed (${describeError(error)}) and releasing the lock failed: ${describeError(unlockError)}`, {
75
+ cause: error,
76
+ });
77
+ });
78
+ throw error;
79
+ }
80
+ await session.query("SELECT pg_advisory_unlock($1)", [MIGRATION_LOCK_KEY]);
81
+ return result;
82
+ }
83
+ /**
84
+ * Records `files` of the unit as adopted, all in one transaction: either every row is written or
85
+ * none. Returns the problem when a write fails.
86
+ */
87
+ export async function recordAdoption(session, unit, files) {
88
+ try {
89
+ await session.exec("BEGIN");
90
+ for (const file of files) {
91
+ await recordMigration(session, {
92
+ module: unit.module,
93
+ version: file.version,
94
+ name: file.name,
95
+ checksum: file.checksum,
96
+ moduleVersion: unit.moduleVersion,
97
+ method: "adopted",
98
+ });
99
+ }
100
+ await session.exec("COMMIT");
101
+ return null;
102
+ }
103
+ catch (error) {
104
+ const reason = await rollBack(session, error);
105
+ const first = files[0];
106
+ return { code: "db.migration_failed", module: unit.module, version: first?.version ?? 0, name: first?.name ?? "", reason };
107
+ }
108
+ }
109
+ export function quoteIdentifier(name) {
110
+ return `"${name.replace(/"/g, '""')}"`;
111
+ }
112
+ export function describeError(error) {
113
+ return error instanceof Error ? error.message : String(error);
114
+ }
115
+ //# sourceMappingURL=apply.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply.js","sourceRoot":"","sources":["../../src/migrations/apply.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import { type AnySoftureModule } from "@softure-ai/core";
2
+ import { type MigrationStep } from "./migrator.js";
3
+ import { type MigrationResult } from "./problems.js";
4
+ /**
5
+ * Copies each module's migration files to `<targetDir>/<module id>/`, replacing older `.sql`
6
+ * copies. A module folder holding anything but `.sql` files is refused, never emptied: it is
7
+ * not an earlier export (e.g. `softure migrate --export-migrations .` in an app with an `auth/`).
8
+ */
9
+ export declare function exportMigrations(modules: readonly AnySoftureModule[], targetDir: string): Promise<MigrationResult<MigrationStep[]>>;
10
+ //# sourceMappingURL=export.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export.d.ts","sourceRoot":"","sources":["../../src/migrations/export.ts"],"names":[],"mappings":"AAMA,OAAO,EAAM,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAE7D,OAAO,EAAgB,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AACjE,OAAO,EAAmC,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAEtF;;;;GAIG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,SAAS,gBAAgB,EAAE,EACpC,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,eAAe,CAAC,aAAa,EAAE,CAAC,CAAC,CAmC3C"}
@@ -0,0 +1,58 @@
1
+ import { mkdir, readdir, rm, writeFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { ok } from "@softure-ai/core";
4
+ import { LEDGER_MODULE_ID } from "./ledger.js";
5
+ import { prepareUnits } from "./migrator.js";
6
+ import { failWith } from "./problems.js";
7
+ /**
8
+ * Copies each module's migration files to `<targetDir>/<module id>/`, replacing older `.sql`
9
+ * copies. A module folder holding anything but `.sql` files is refused, never emptied: it is
10
+ * not an earlier export (e.g. `softure migrate --export-migrations .` in an app with an `auth/`).
11
+ */
12
+ export async function exportMigrations(modules, targetDir) {
13
+ const units = await prepareUnits(modules, undefined);
14
+ if (!units.ok) {
15
+ return units;
16
+ }
17
+ const moduleUnits = units.value.filter((candidate) => candidate.module !== LEDGER_MODULE_ID);
18
+ const problems = [];
19
+ for (const unit of moduleUnits) {
20
+ const foreign = (await listEntries(join(targetDir, unit.module))).filter((entry) => !entry.isFile() || !entry.name.endsWith(".sql"));
21
+ if (foreign.length > 0) {
22
+ problems.push({
23
+ code: "db.export_target_not_empty",
24
+ module: unit.module,
25
+ dir: join(targetDir, unit.module),
26
+ entries: foreign.map((entry) => entry.name).sort(),
27
+ });
28
+ }
29
+ }
30
+ if (problems.length > 0) {
31
+ return failWith(problems);
32
+ }
33
+ const exported = [];
34
+ for (const unit of moduleUnits) {
35
+ const moduleDir = join(targetDir, unit.module);
36
+ for (const entry of await listEntries(moduleDir)) {
37
+ await rm(join(moduleDir, entry.name));
38
+ }
39
+ await mkdir(moduleDir, { recursive: true });
40
+ for (const file of unit.files) {
41
+ await writeFile(join(moduleDir, file.fileName), file.sql, "utf8");
42
+ exported.push({ module: unit.module, schema: unit.schema, version: file.version, name: file.name, checksum: file.checksum });
43
+ }
44
+ }
45
+ return ok(exported);
46
+ }
47
+ async function listEntries(dir) {
48
+ try {
49
+ return await readdir(dir, { withFileTypes: true });
50
+ }
51
+ catch (error) {
52
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
53
+ return [];
54
+ }
55
+ throw error;
56
+ }
57
+ }
58
+ //# sourceMappingURL=export.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export.js","sourceRoot":"","sources":["../../src/migrations/export.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import { type JournalRow } from "./ledger.js";
2
+ import { type MigrationResult } from "./problems.js";
3
+ export interface ExportedMigration {
4
+ readonly module: string;
5
+ readonly version: number;
6
+ readonly name: string;
7
+ readonly checksum: string;
8
+ }
9
+ export interface ExportedMigrationsCheck {
10
+ /** Files the database has not applied yet, per module in folder order; `softure migrate` applies them. */
11
+ readonly pending: readonly ExportedMigration[];
12
+ /** Modules the ledger knows but the folder does not hold: not enabled in the image, so the migrator ignores them. */
13
+ readonly absent: readonly string[];
14
+ }
15
+ /**
16
+ * Compares `<migrationsDir>/<module id>/*.sql` and the built-in ledger migration with the journal. Fails with every
17
+ * problem: an applied file edited or missing, a pending file numbered below an applied one, an invalid folder.
18
+ */
19
+ export declare function checkExportedMigrations(migrationsDir: URL, journal: readonly JournalRow[]): Promise<MigrationResult<ExportedMigrationsCheck>>;
20
+ //# sourceMappingURL=exported.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exported.d.ts","sourceRoot":"","sources":["../../src/migrations/exported.ts"],"names":[],"mappings":"AAQA,OAAO,EAAiE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAE7G,OAAO,EAAmC,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAEtF,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,uBAAuB;IACtC,0GAA0G;IAC1G,QAAQ,CAAC,OAAO,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAC/C,qHAAqH;IACrH,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CAC3C,aAAa,EAAE,GAAG,EAClB,OAAO,EAAE,SAAS,UAAU,EAAE,GAC7B,OAAO,CAAC,eAAe,CAAC,uBAAuB,CAAC,CAAC,CA+BnD"}
@@ -0,0 +1,61 @@
1
+ import { readdir } from "node:fs/promises";
2
+ import { fileURLToPath } from "node:url";
3
+ import { ok } from "@softure-ai/core";
4
+ import { readMigrationFiles } from "./files.js";
5
+ import { LEDGER_FILES, LEDGER_MODULE_ID, LEDGER_SCHEMA, LEDGER_VERSION } from "./ledger.js";
6
+ import { compareJournal } from "./migrator.js";
7
+ import { failWith } from "./problems.js";
8
+ /**
9
+ * Compares `<migrationsDir>/<module id>/*.sql` and the built-in ledger migration with the journal. Fails with every
10
+ * problem: an applied file edited or missing, a pending file numbered below an applied one, an invalid folder.
11
+ */
12
+ export async function checkExportedMigrations(migrationsDir, journal) {
13
+ const folders = await listModuleFolders(migrationsDir);
14
+ if (!folders.ok) {
15
+ return folders;
16
+ }
17
+ const problems = [];
18
+ const units = [
19
+ { module: LEDGER_MODULE_ID, schema: LEDGER_SCHEMA, moduleVersion: LEDGER_VERSION, files: LEDGER_FILES },
20
+ ];
21
+ for (const module of folders.value) {
22
+ if (module === LEDGER_MODULE_ID) {
23
+ problems.push({ code: "db.reserved_module", module });
24
+ continue;
25
+ }
26
+ const files = await readMigrationFiles(module, new URL(`${encodeURIComponent(module)}/`, ensureFolderUrl(migrationsDir)));
27
+ if (files.ok) {
28
+ // The schema is not part of the export; the comparison does not read it.
29
+ units.push({ module, schema: "", moduleVersion: "", files: files.value });
30
+ }
31
+ else {
32
+ problems.push(...files.problems);
33
+ }
34
+ }
35
+ const comparison = compareJournal(units, journal);
36
+ problems.push(...comparison.problems);
37
+ if (problems.length > 0) {
38
+ return failWith(problems);
39
+ }
40
+ const known = new Set(units.map((unit) => unit.module));
41
+ const absent = [...new Set(journal.map((row) => row.module))].filter((module) => !known.has(module)).sort();
42
+ const pending = comparison.pending.map(({ module, version, name, checksum }) => ({ module, version, name, checksum }));
43
+ return ok({ pending, absent });
44
+ }
45
+ /** The module folders, sorted; files beside them (a README) are ignored. */
46
+ async function listModuleFolders(dir) {
47
+ let entries;
48
+ try {
49
+ entries = await readdir(fileURLToPath(dir), { withFileTypes: true });
50
+ }
51
+ catch (error) {
52
+ const code = error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : "unknown error";
53
+ const reason = code === "ENOENT" ? "the folder does not exist" : code;
54
+ return failWith([{ code: "db.migrations_unreadable", module: "(export)", dir: fileURLToPath(dir), reason }]);
55
+ }
56
+ return ok(entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort());
57
+ }
58
+ function ensureFolderUrl(url) {
59
+ return url.href.endsWith("/") ? url : new URL(`${url.href}/`);
60
+ }
61
+ //# sourceMappingURL=exported.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exported.js","sourceRoot":"","sources":["../../src/migrations/exported.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import { type MigrationResult } from "./problems.js";
2
+ export interface MigrationFile {
3
+ /** The number in the file name: 1 for `0001_create_notes.sql`. */
4
+ readonly version: number;
5
+ /** The description in the file name: `create_notes`. */
6
+ readonly name: string;
7
+ readonly fileName: string;
8
+ /** The file content with a BOM removed and CRLF line ends turned into LF. */
9
+ readonly sql: string;
10
+ /** sha256 of `sql`, hex. */
11
+ readonly checksum: string;
12
+ }
13
+ /** Normalises line ends so a Windows checkout does not look like an edited migration. */
14
+ export declare function normalizeSql(text: string): string;
15
+ export declare function computeChecksum(sql: string): string;
16
+ export declare function createMigrationFile(fileName: string, text: string): MigrationFile | string;
17
+ /** The module's migration files sorted by number, or every problem found in the folder. */
18
+ export declare function readMigrationFiles(moduleId: string, dir: URL): Promise<MigrationResult<MigrationFile[]>>;
19
+ /** The first top-level statement that begins, ends or aborts a transaction, or null. */
20
+ export declare function findTransactionControl(sql: string): string | null;
21
+ //# sourceMappingURL=files.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../../src/migrations/files.ts"],"names":[],"mappings":"AAOA,OAAO,EAAmC,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAEtF,MAAM,WAAW,aAAa;IAC5B,kEAAkE;IAClE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,wDAAwD;IACxD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,6EAA6E;IAC7E,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,4BAA4B;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAWD,yFAAyF;AACzF,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,MAAM,CAkB1F;AAED,2FAA2F;AAC3F,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,eAAe,CAAC,aAAa,EAAE,CAAC,CAAC,CAsB9G;AAgBD,wFAAwF;AACxF,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAUjE"}
@@ -0,0 +1,112 @@
1
+ // Reading a module's migration folder (docs/02-module-standard.md §4): plain SQL files named
2
+ // `NNNN_<description>.sql`, numbered 1..n without gaps, each opening with a comment that states
3
+ // the rollback plan. Other files (a README) are ignored.
4
+ import { createHash } from "node:crypto";
5
+ import { readdir, readFile } from "node:fs/promises";
6
+ import { fileURLToPath } from "node:url";
7
+ import { ok } from "@softure-ai/core";
8
+ import { failWith } from "./problems.js";
9
+ const FILE_NAME = /^(\d{4})_([a-z0-9]+(?:_[a-z0-9]+)*)\.sql$/;
10
+ const ROLLBACK_NOTE = /rollback:/i;
11
+ // Statements that end or open a transaction would split the per-file transaction (a top-level
12
+ // `END;` commits). Bodies in quotes or dollar quotes (PL/pgSQL `BEGIN … END;`) are not statements.
13
+ const TRANSACTION_KEYWORDS = new Set(["begin", "commit", "rollback", "end", "abort"]);
14
+ const TRANSACTION_PHRASES = [["start", "transaction"], ["prepare", "transaction"]];
15
+ // Comments, quoted strings, quoted identifiers and dollar-quoted bodies, in that order of match.
16
+ const NON_CODE = /--[^\n]*|\/\*[\s\S]*?\*\/|'(?:[^']|'')*'|"(?:[^"]|"")*"|(\$[A-Za-z_]*\$)[\s\S]*?\1/g;
17
+ /** Normalises line ends so a Windows checkout does not look like an edited migration. */
18
+ export function normalizeSql(text) {
19
+ return text.replace(/^\uFEFF/, "").replace(/\r\n/g, "\n");
20
+ }
21
+ export function computeChecksum(sql) {
22
+ return createHash("sha256").update(normalizeSql(sql), "utf8").digest("hex");
23
+ }
24
+ export function createMigrationFile(fileName, text) {
25
+ const match = FILE_NAME.exec(fileName);
26
+ if (match === null) {
27
+ return "the name must look like 0001_create_table.sql (four digits, then lower_snake_case)";
28
+ }
29
+ const version = Number(match[1]);
30
+ if (version === 0) {
31
+ return "numbering starts at 0001";
32
+ }
33
+ const sql = normalizeSql(text);
34
+ if (!hasRollbackNote(sql)) {
35
+ return 'the file must open with a comment stating the rollback plan, e.g. "-- Rollback: DROP TABLE notes;"';
36
+ }
37
+ const control = findTransactionControl(sql);
38
+ if (control !== null) {
39
+ return `the file must not control transactions ("${control}"): the migrator runs each file in its own transaction`;
40
+ }
41
+ return { version, name: match[2] ?? "", fileName, sql, checksum: computeChecksum(sql) };
42
+ }
43
+ /** The module's migration files sorted by number, or every problem found in the folder. */
44
+ export async function readMigrationFiles(moduleId, dir) {
45
+ const dirPath = fileURLToPath(dir);
46
+ let fileNames;
47
+ try {
48
+ const entries = await readdir(dirPath, { withFileTypes: true });
49
+ fileNames = entries.filter((entry) => entry.isFile() && entry.name.endsWith(".sql")).map((entry) => entry.name);
50
+ }
51
+ catch (error) {
52
+ return failWith([{ code: "db.migrations_unreadable", module: moduleId, dir: dirPath, reason: describeFsError(error) }]);
53
+ }
54
+ const problems = [];
55
+ const files = [];
56
+ for (const fileName of fileNames.sort()) {
57
+ const file = createMigrationFile(fileName, await readFile(`${dirPath}/${fileName}`, "utf8"));
58
+ if (typeof file === "string") {
59
+ problems.push({ code: "db.invalid_migration_file", module: moduleId, file: fileName, reason: file });
60
+ }
61
+ else {
62
+ files.push(file);
63
+ }
64
+ }
65
+ problems.push(...checkNumbering(moduleId, files));
66
+ return problems.length > 0 ? failWith(problems) : ok(files);
67
+ }
68
+ /** Numbers must run 1, 2, 3 …: a repeat is ambiguous and a gap usually means a deleted file. */
69
+ function checkNumbering(moduleId, files) {
70
+ const problems = [];
71
+ files.forEach((file, index) => {
72
+ const previous = files[index - 1];
73
+ if (previous !== undefined && previous.version === file.version) {
74
+ problems.push({ code: "db.invalid_migration_file", module: moduleId, file: file.fileName, reason: `repeats number ${previous.fileName.slice(0, 4)}` });
75
+ }
76
+ else if (file.version !== (previous?.version ?? 0) + 1) {
77
+ problems.push({ code: "db.invalid_migration_file", module: moduleId, file: file.fileName, reason: `breaks the sequence: expected number ${String((previous?.version ?? 0) + 1).padStart(4, "0")}` });
78
+ }
79
+ });
80
+ return problems;
81
+ }
82
+ /** The first top-level statement that begins, ends or aborts a transaction, or null. */
83
+ export function findTransactionControl(sql) {
84
+ const statements = sql.replace(NON_CODE, " ").split(";");
85
+ for (const statement of statements) {
86
+ const words = statement.trim().toLowerCase().split(/\s+/);
87
+ const [first = "", second = ""] = words;
88
+ if (TRANSACTION_KEYWORDS.has(first) || TRANSACTION_PHRASES.some(([verb, noun]) => first === verb && second === noun)) {
89
+ return words.slice(0, 3).join(" ");
90
+ }
91
+ }
92
+ return null;
93
+ }
94
+ /** The leading `--` comment block (after blank lines) must mention "Rollback:". */
95
+ function hasRollbackNote(sql) {
96
+ const lines = sql.split("\n").map((line) => line.trim());
97
+ const firstContent = lines.findIndex((line) => line !== "");
98
+ const leading = [];
99
+ for (const line of lines.slice(Math.max(firstContent, 0))) {
100
+ if (!line.startsWith("--"))
101
+ break;
102
+ leading.push(line);
103
+ }
104
+ return leading.some((line) => ROLLBACK_NOTE.test(line));
105
+ }
106
+ function describeFsError(error) {
107
+ if (error instanceof Error && "code" in error && typeof error.code === "string") {
108
+ return error.code === "ENOENT" ? "the folder does not exist" : error.code;
109
+ }
110
+ return "unknown error";
111
+ }
112
+ //# sourceMappingURL=files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"files.js","sourceRoot":"","sources":["../../src/migrations/files.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { MigrationSession } from "./session.js";
2
+ /** Every object of `schema` as one sorted line each. Runs in its own read-only transaction. */
3
+ export declare function describeSchema(session: MigrationSession, schema: string): Promise<string[]>;
4
+ /** What is in `expected` and not in `actual`, and the other way round, as readable lines. */
5
+ export declare function diffSchemas(expected: readonly string[], actual: readonly string[]): string[];
6
+ //# sourceMappingURL=introspect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"introspect.d.ts","sourceRoot":"","sources":["../../src/migrations/introspect.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAkGrD,+FAA+F;AAC/F,wBAAsB,cAAc,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAgCjG;AAED,6FAA6F;AAC7F,wBAAgB,WAAW,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAO5F"}