@softure-ai/db 0.0.0-stage → 0.1.5

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 (88) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +189 -2
  3. package/dist/cli/bin.d.ts +3 -0
  4. package/dist/cli/bin.d.ts.map +1 -0
  5. package/dist/cli/bin.js +5 -0
  6. package/dist/cli/bin.js.map +1 -0
  7. package/dist/cli/command.d.ts +8 -0
  8. package/dist/cli/command.d.ts.map +1 -0
  9. package/dist/cli/command.js +38 -0
  10. package/dist/cli/command.js.map +1 -0
  11. package/dist/cli/index.d.ts +3 -0
  12. package/dist/cli/index.d.ts.map +1 -0
  13. package/dist/cli/index.js +4 -0
  14. package/dist/cli/index.js.map +1 -0
  15. package/dist/cli/run.d.ts +20 -0
  16. package/dist/cli/run.d.ts.map +1 -0
  17. package/dist/cli/run.js +155 -0
  18. package/dist/cli/run.js.map +1 -0
  19. package/dist/client.d.ts +42 -0
  20. package/dist/client.d.ts.map +1 -0
  21. package/dist/client.js +34 -0
  22. package/dist/client.js.map +1 -0
  23. package/dist/index.d.ts +11 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +12 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/migrations/adopt.d.ts +26 -0
  28. package/dist/migrations/adopt.d.ts.map +1 -0
  29. package/dist/migrations/adopt.js +131 -0
  30. package/dist/migrations/adopt.js.map +1 -0
  31. package/dist/migrations/export.d.ts +10 -0
  32. package/dist/migrations/export.d.ts.map +1 -0
  33. package/dist/migrations/export.js +58 -0
  34. package/dist/migrations/export.js.map +1 -0
  35. package/dist/migrations/exported.d.ts +20 -0
  36. package/dist/migrations/exported.d.ts.map +1 -0
  37. package/dist/migrations/exported.js +61 -0
  38. package/dist/migrations/exported.js.map +1 -0
  39. package/dist/migrations/files.d.ts +21 -0
  40. package/dist/migrations/files.d.ts.map +1 -0
  41. package/dist/migrations/files.js +112 -0
  42. package/dist/migrations/files.js.map +1 -0
  43. package/dist/migrations/introspect.d.ts +6 -0
  44. package/dist/migrations/introspect.d.ts.map +1 -0
  45. package/dist/migrations/introspect.js +117 -0
  46. package/dist/migrations/introspect.js.map +1 -0
  47. package/dist/migrations/ledger.d.ts +27 -0
  48. package/dist/migrations/ledger.d.ts.map +1 -0
  49. package/dist/migrations/ledger.js +45 -0
  50. package/dist/migrations/ledger.js.map +1 -0
  51. package/dist/migrations/migrator.d.ts +69 -0
  52. package/dist/migrations/migrator.d.ts.map +1 -0
  53. package/dist/migrations/migrator.js +217 -0
  54. package/dist/migrations/migrator.js.map +1 -0
  55. package/dist/migrations/problems.d.ts +84 -0
  56. package/dist/migrations/problems.d.ts.map +1 -0
  57. package/dist/migrations/problems.js +56 -0
  58. package/dist/migrations/problems.js.map +1 -0
  59. package/dist/migrations/session.d.ts +14 -0
  60. package/dist/migrations/session.d.ts.map +1 -0
  61. package/dist/migrations/session.js +39 -0
  62. package/dist/migrations/session.js.map +1 -0
  63. package/dist/shared.d.ts +9 -0
  64. package/dist/shared.d.ts.map +1 -0
  65. package/dist/shared.js +32 -0
  66. package/dist/shared.js.map +1 -0
  67. package/dist/testing.d.ts +14 -0
  68. package/dist/testing.d.ts.map +1 -0
  69. package/dist/testing.js +43 -0
  70. package/dist/testing.js.map +1 -0
  71. package/package.json +56 -4
  72. package/src/cli/bin.ts +5 -0
  73. package/src/cli/command.ts +47 -0
  74. package/src/cli/index.ts +3 -0
  75. package/src/cli/run.ts +189 -0
  76. package/src/client.ts +79 -0
  77. package/src/index.ts +37 -0
  78. package/src/migrations/adopt.ts +171 -0
  79. package/src/migrations/export.ts +66 -0
  80. package/src/migrations/exported.ts +82 -0
  81. package/src/migrations/files.ts +129 -0
  82. package/src/migrations/introspect.ts +165 -0
  83. package/src/migrations/ledger.ts +75 -0
  84. package/src/migrations/migrator.ts +285 -0
  85. package/src/migrations/problems.ts +91 -0
  86. package/src/migrations/session.ts +48 -0
  87. package/src/shared.ts +36 -0
  88. package/src/testing.ts +52 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 SOFTURE
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,190 @@
1
- # Temporary Holding Version
1
+ # @softure-ai/db
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ **Status:** wave 0 · implemented in FD-4 (`db-migrator`), 0.1.5, prepared for its first release (the owner's tag, `scripts/release/README.md`).
4
+
5
+ The database layer every SOFTURE module stands on. Standard:
6
+ [docs/02-module-standard.md](../../docs/02-module-standard.md) §4. Sources in FIRE_TRACKER:
7
+ `src/db/client.ts`, `src/db/test-db.ts`, `scripts/migrate.ts`, `docker/Dockerfile` (`migrate.cjs`).
8
+
9
+ ## 1. What it provides
10
+
11
+ A pg/PGlite client chosen by URL, a migrator that runs each module's SQL in the module's own
12
+ Postgres schema (with a ledger, checksums, a lock, a dry run and adoption of existing tables),
13
+ `createTestDatabase` for unit tests, and the `softure migrate` command.
14
+
15
+ ## 2. Installation
16
+
17
+ ```bash
18
+ npm install @softure-ai/db drizzle-orm
19
+ ```
20
+
21
+ Node ≥ 22, ESM only. `drizzle-orm` (`^0.45.2`) is a peer dependency, so the app, the modules and
22
+ this package share one drizzle. `pg` and `@electric-sql/pglite` come with the package and load
23
+ only when used.
24
+
25
+ ## 3. Configuration
26
+
27
+ The database URL comes from the app config (`database.url` in `defineSoftureConfig`), usually
28
+ `process.env.DATABASE_URL`:
29
+
30
+ | URL | Driver |
31
+ | --- | --- |
32
+ | `postgres://…`, `postgresql://…` | node-postgres pool (production) |
33
+ | `pglite://<dir>` | PGlite, a real Postgres in-process, data in `<dir>` (dev without a server) |
34
+ | `pglite://` | PGlite in memory |
35
+
36
+ ```ts
37
+ import { createDatabase, type Queryable } from "@softure-ai/db";
38
+
39
+ const handle = await createDatabase(config.database.url, { max: 10 }); // max: pg pool size
40
+ await handle.db.select().from(notes); // drizzle
41
+ await handle.close();
42
+ ```
43
+
44
+ - `DatabaseHandle` is `{ kind: "postgres", db, pool, close } | { kind: "pglite", db, client, close }`.
45
+ - `Database` is the drizzle database of either driver; `Queryable` is a `Database` or an open
46
+ transaction, so one helper serves both (`db.transaction(async (tx) => helper(tx))`).
47
+ - An unsupported scheme throws, naming the scheme only; the URL (and its password) is never
48
+ printed.
49
+
50
+ ## 4. Mounting
51
+
52
+ Nothing to mount. Migrations run as a deploy step:
53
+
54
+ - **Dev:** `npx softure migrate` finds `softure.config.{ts,mts,js,mjs}` in the working directory
55
+ (or `--config <file>`). The file must be one Node can import: `.js`/`.mjs`, or `.ts` where Node
56
+ strips types (Node ≥ 22.18, relative imports written with `.ts`). Otherwise use the script below.
57
+ The lookup is `@softure-ai/core/cli`'s, shared with `softure-mail` and `softure-blog`.
58
+ - **App script and container image:** the app owns a three-line script, which esbuild bundles:
59
+
60
+ ```ts
61
+ // scripts/migrate.ts
62
+ import { runMigrateCli } from "@softure-ai/db/cli";
63
+ import config from "../softure.config";
64
+
65
+ process.exitCode = await runMigrateCli({ config, argv: process.argv.slice(2) });
66
+ ```
67
+
68
+ ```dockerfile
69
+ # build stage: copy each module's SQL next to the bundle, then bundle the runner
70
+ RUN npx tsx scripts/migrate.ts --export-migrations ./softure-migrations
71
+ RUN npx esbuild scripts/migrate.ts --bundle --platform=node --format=esm --target=node22 \
72
+ --external:pg --external:@electric-sql/pglite --outfile=migrate.mjs
73
+ # run stage (needs pg, and @electric-sql/pglite for --adopt)
74
+ COPY --from=builder /app/migrate.mjs ./migrate.mjs
75
+ COPY --from=builder /app/softure-migrations ./softure-migrations
76
+ CMD ["node", "migrate.mjs", "--migrations-dir", "./softure-migrations"]
77
+ ```
78
+
79
+ A bundle cannot find package folders (`import.meta.url` points at the bundle), hence
80
+ `--export-migrations` at build time and `--migrations-dir` at run time. The ledger's own migration
81
+ ships as code.
82
+
83
+ **Options of `softure migrate`:**
84
+
85
+ | Option | Effect |
86
+ | --- | --- |
87
+ | (none) | apply every pending migration |
88
+ | `--plan` | print what would be applied (or adopted); change nothing |
89
+ | `--adopt <module>@<version>` | record an existing schema as migrated after comparing it (section 5) |
90
+ | `--migrations-dir <dir>` | read module files from `<dir>/<module id>/` |
91
+ | `--export-migrations <dir>` | copy module files to `<dir>/<module id>/` and exit; needs no database; replaces older `.sql` copies and refuses a module folder holding anything else |
92
+
93
+ Exit codes: 0 done, 1 a problem or failure (each printed on stderr), 2 a usage error.
94
+
95
+ The same operations as functions: `migrate(handle, { modules, migrationsDir?, onApplied? })`,
96
+ `planMigrations(handle, { modules })`, `adoptModule(handle, { modules, module, version, dryRun? })`,
97
+ `exportMigrations(modules, dir)`. Each returns `{ ok: true, value } | { ok: false, error, problems }`;
98
+ `describeProblem(problem)` gives the English line.
99
+
100
+ For a deploy, `checkExportedMigrations(dir, journal)` runs the same comparison on an exported folder without the app
101
+ config: `journal` is `readJournal(session)` of the target database, and the result lists the pending files and the
102
+ ledger modules the folder does not hold, or every problem. `softure-deploy schema-guard` (`@softure-ai/deploy`) is
103
+ built on it.
104
+
105
+ ## 5. Migrations and tables
106
+
107
+ **Owned:** schema `softure`, table `softure.migrations` (`id, module, version, name, checksum,
108
+ module_version, method ('applied' | 'adopted'), applied_at`, primary key `(module, version)`).
109
+
110
+ **Module files** (`migrations/NNNN_<lower_snake>.sql`, numbered 1..n without gaps):
111
+ - the file opens with a comment block that contains `Rollback:` and says how to undo it;
112
+ - no top-level statement may begin, end or abort a transaction (`BEGIN`, `COMMIT`, `ROLLBACK`,
113
+ `END`, `ABORT`, `START TRANSACTION`, `PREPARE TRANSACTION`); bodies in quotes or dollar quotes
114
+ (PL/pgSQL `BEGIN … END;`) are fine, SQL-standard `BEGIN ATOMIC` bodies are not. Each file runs in
115
+ its own transaction with its ledger row, and a file that still ended it is reported as such,
116
+ not as rolled back;
117
+ - unqualified names land in the module's schema: the runner runs `CREATE SCHEMA IF NOT EXISTS` and
118
+ `SET LOCAL search_path TO <schema>, public` first. Name other modules' tables with their schema
119
+ (`REFERENCES notes.notes (id)`), and create extensions `WITH SCHEMA public`.
120
+
121
+ **Each run:** modules in dependency order (`sortModulesByDependencies`), files in number order.
122
+ Before anything runs, every file of every module is checked: an applied file that was edited or
123
+ renamed (`db.migration_changed`), deleted (`db.migration_missing`), or a new file numbered below an
124
+ applied one (`db.migration_out_of_order`) refuses the whole run. The run holds a session-level
125
+ `pg_advisory_lock`, so a second runner waits, then finds nothing to do. A failing file is rolled
126
+ back completely and stops the run; the files before it stay applied. The migration connection is
127
+ closed after the run, never returned to the pool.
128
+
129
+ **Refused modules:** the id `softure`; the schemas `softure`, `public`, `information_schema`,
130
+ `pg_*` and names over 63 bytes; migrations without a `dbSchema`.
131
+
132
+ **Adoption** ([docs/05](../../docs/05-adoption-playbook.md) step 3): after the app's own migration
133
+ moved its tables into the module's schema, `--adopt <module>@<version>` (with `--plan` first)
134
+ builds the schema the module's migrations create on a scratch PGlite and compares it with the live
135
+ one through `pg_catalog`: relations (and whether they are unlogged), columns (type, collation,
136
+ not null, default, identity, generated), sequences with their parameters, constraints and indexes
137
+ (names and definitions), triggers, functions, and enum, domain, range and composite types. Only an
138
+ exact match records the files as `adopted`; every difference is printed (`missing in database: …`,
139
+ `unexpected in database: …`). The version must equal the enabled module's, the module must be new
140
+ to the ledger, and the modules it depends on must be fully migrated or adopted first. Undo:
141
+ `DELETE FROM softure.migrations WHERE module = '<id>' AND method = 'adopted'`.
142
+
143
+ **Tests:** `createTestDatabase(modules)` from `@softure-ai/db/testing` returns `{ db, client, close }`:
144
+ an in-memory PGlite with the ledger and the listed modules migrated (list the module and the
145
+ modules it depends on). Each module set is migrated once per test worker and copied per call; a
146
+ broken migration throws naming the file.
147
+
148
+ ```ts
149
+ import { createTestDatabase } from "@softure-ai/db/testing";
150
+
151
+ const database = await createTestDatabase([notes()]);
152
+ afterEach(() => database.close());
153
+ ```
154
+
155
+ ## 6. Environment variables
156
+
157
+ None read by the package. The app passes `DATABASE_URL` (or any variable) through
158
+ `database.url` in its config.
159
+
160
+ ## 7. Switches
161
+
162
+ None.
163
+
164
+ ## 8. Appearance
165
+
166
+ None.
167
+
168
+ ## 9. Copy
169
+
170
+ None: no user-facing text. Command output and problem lines are English developer messages.
171
+
172
+ ## 10. Hooks
173
+
174
+ `onApplied(step)` in `migrate` options, called after each committed file.
175
+
176
+ ## 11. GDPR
177
+
178
+ No personal data. The ledger holds module ids, file names and checksums.
179
+
180
+ ## 12. Limitations
181
+
182
+ - Forward only: there are no down migrations; each file states its rollback in its comment.
183
+ - A file cannot run outside a transaction (`CREATE INDEX CONCURRENTLY`, `ALTER TYPE … ADD VALUE`
184
+ before Postgres 12).
185
+ - No lock timeout: a stuck runner holds the lock until its connection ends (visible in `pg_locks`).
186
+ The session-level lock needs a direct connection or a session-mode pooler, not a
187
+ transaction-mode PgBouncer.
188
+ - Adoption does not compare grants, comments, column order, row-level security policies or view
189
+ bodies, and needs PGlite installed where it runs (also in an image that otherwise uses `pg`).
190
+ - No cached connection for Next.js dev reloads yet; the Next adapter (identity ID-1, FD-7) adds it.
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=bin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../../src/cli/bin.ts"],"names":[],"mappings":""}
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ // The `softure` executable. All logic lives in command.ts, which the tests call directly.
3
+ import { runSoftureCommand } from "./command.js";
4
+ process.exitCode = await runSoftureCommand({ argv: process.argv.slice(2), cwd: process.cwd() });
5
+ //# sourceMappingURL=bin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.js","sourceRoot":"","sources":["../../src/cli/bin.ts"],"names":[],"mappings":";AACA,0FAA0F;AAC1F,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEjD,OAAO,CAAC,QAAQ,GAAG,MAAM,iBAAiB,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { type CliOutput } from "./run.js";
2
+ export interface RunSoftureCommandOptions {
3
+ readonly argv: readonly string[];
4
+ readonly cwd: string;
5
+ readonly output?: CliOutput;
6
+ }
7
+ export declare function runSoftureCommand(options: RunSoftureCommandOptions): Promise<number>;
8
+ //# sourceMappingURL=command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../src/cli/command.ts"],"names":[],"mappings":"AAKA,OAAO,EAAkE,KAAK,SAAS,EAAE,MAAM,UAAU,CAAC;AAE1G,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC;CAC7B;AASD,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,CA0B1F"}
@@ -0,0 +1,38 @@
1
+ // The `softure` bin: `softure migrate [--config <file>] [options]`. It loads the app's config through
2
+ // `@softure-ai/core/cli`, so the file must be loadable by Node itself: `.js`/`.mjs`, or `.ts` where
3
+ // Node strips types (Node >= 22.18; relative imports then need `.ts` extensions). Anything else
4
+ // uses an app script with `runMigrateCli` instead (see run.ts).
5
+ import { loadAppConfig, takeConfigOption } from "@softure-ai/core/cli";
6
+ import { EXIT_FAILED, EXIT_OK, EXIT_USAGE, MIGRATE_USAGE, runMigrateCli } from "./run.js";
7
+ const APP_SCRIPT = { runner: "runMigrateCli", packageName: "@softure-ai/db" };
8
+ const consoleOutput = {
9
+ log: (line) => console.log(line),
10
+ error: (line) => console.error(line),
11
+ };
12
+ export async function runSoftureCommand(options) {
13
+ const output = options.output ?? consoleOutput;
14
+ const [command, ...rest] = options.argv;
15
+ if (command !== "migrate") {
16
+ output.error(command === undefined ? "softure: missing command" : `softure: unknown command "${command}"`);
17
+ output.error("Commands: migrate");
18
+ output.error(MIGRATE_USAGE);
19
+ return EXIT_USAGE;
20
+ }
21
+ if (rest.includes("--help")) {
22
+ output.log(MIGRATE_USAGE);
23
+ return EXIT_OK;
24
+ }
25
+ const taken = takeConfigOption(rest);
26
+ if (!taken.ok) {
27
+ output.error(`softure migrate: ${taken.problem}`);
28
+ output.error(MIGRATE_USAGE);
29
+ return EXIT_USAGE;
30
+ }
31
+ const loaded = await loadAppConfig({ cwd: options.cwd, configPath: taken.configPath, appScript: APP_SCRIPT });
32
+ if (!loaded.ok) {
33
+ output.error(`softure: ${loaded.problem}`);
34
+ return EXIT_FAILED;
35
+ }
36
+ return runMigrateCli({ config: loaded.config, argv: taken.argv, cwd: options.cwd, output });
37
+ }
38
+ //# sourceMappingURL=command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command.js","sourceRoot":"","sources":["../../src/cli/command.ts"],"names":[],"mappings":"AAAA,sGAAsG;AACtG,oGAAoG;AACpG,gGAAgG;AAChG,gEAAgE;AAChE,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACvE,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,aAAa,EAAkB,MAAM,UAAU,CAAC;AAQ1G,MAAM,UAAU,GAAG,EAAE,MAAM,EAAE,eAAe,EAAE,WAAW,EAAE,gBAAgB,EAAE,CAAC;AAE9E,MAAM,aAAa,GAAc;IAC/B,GAAG,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;IAChC,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC;CACrC,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,OAAiC;IACvE,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,aAAa,CAAC;IAC/C,MAAM,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IACxC,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,MAAM,CAAC,KAAK,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,0BAA0B,CAAC,CAAC,CAAC,6BAA6B,OAAO,GAAG,CAAC,CAAC;QAC3G,MAAM,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAC;QAClC,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;QAC5B,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC5B,MAAM,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;QAC1B,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,MAAM,KAAK,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;IACrC,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC;QACd,MAAM,CAAC,KAAK,CAAC,oBAAoB,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QAClD,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;QAC5B,OAAO,UAAU,CAAC;IACpB,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC,CAAC;IAC9G,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QACf,MAAM,CAAC,KAAK,CAAC,YAAY,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC;QAC3C,OAAO,WAAW,CAAC;IACrB,CAAC;IACD,OAAO,aAAa,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC;AAC9F,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { runSoftureCommand, type RunSoftureCommandOptions } from "./command.js";
2
+ export { EXIT_FAILED, EXIT_OK, EXIT_USAGE, MIGRATE_USAGE, runMigrateCli, type CliOutput, type RunMigrateCliOptions } from "./run.js";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,KAAK,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAChF,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,aAAa,EAAE,KAAK,SAAS,EAAE,KAAK,oBAAoB,EAAE,MAAM,UAAU,CAAC"}
@@ -0,0 +1,4 @@
1
+ // `@softure-ai/db/cli`: the `softure migrate` command for app scripts and container bundles.
2
+ export { runSoftureCommand } from "./command.js";
3
+ export { EXIT_FAILED, EXIT_OK, EXIT_USAGE, MIGRATE_USAGE, runMigrateCli } from "./run.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,OAAO,EAAE,iBAAiB,EAAiC,MAAM,cAAc,CAAC;AAChF,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,aAAa,EAA6C,MAAM,UAAU,CAAC"}
@@ -0,0 +1,20 @@
1
+ import type { SoftureConfig } from "@softure-ai/core";
2
+ export interface CliOutput {
3
+ readonly log: (line: string) => void;
4
+ readonly error: (line: string) => void;
5
+ }
6
+ export interface RunMigrateCliOptions {
7
+ readonly config: Pick<SoftureConfig, "database" | "modules">;
8
+ /** The arguments after the command name, e.g. `["--plan"]`. */
9
+ readonly argv: readonly string[];
10
+ /** Relative paths resolve against it. Default: `process.cwd()`. */
11
+ readonly cwd?: string;
12
+ readonly output?: CliOutput;
13
+ }
14
+ export declare const EXIT_OK = 0;
15
+ export declare const EXIT_FAILED = 1;
16
+ export declare const EXIT_USAGE = 2;
17
+ export declare const MIGRATE_USAGE = "Usage: softure migrate [options]\n\nApplies the SQL migrations of every enabled module, each in its own schema.\n\nOptions:\n --plan show what would be applied (or adopted) and change nothing\n --adopt <module>@<version> record an existing schema as migrated, after comparing it\n --migrations-dir <dir> read module files from <dir>/<module id>/ (bundled runners)\n --export-migrations <dir> copy module files to <dir>/<module id>/ and exit (build stage)\n --help show this help";
18
+ /** Runs the command and returns the process exit code: 0 done, 1 failed, 2 usage error. */
19
+ export declare function runMigrateCli(options: RunMigrateCliOptions): Promise<number>;
20
+ //# sourceMappingURL=run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/cli/run.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAOtD,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,QAAQ,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACxC;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,aAAa,EAAE,UAAU,GAAG,SAAS,CAAC,CAAC;IAC7D,+DAA+D;IAC/D,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,mEAAmE;IACnE,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC;CAC7B;AAED,eAAO,MAAM,OAAO,IAAI,CAAC;AACzB,eAAO,MAAM,WAAW,IAAI,CAAC;AAC7B,eAAO,MAAM,UAAU,IAAI,CAAC;AAE5B,eAAO,MAAM,aAAa,oiBASqB,CAAC;AAehD,2FAA2F;AAC3F,wBAAsB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,MAAM,CAAC,CA0ClF"}
@@ -0,0 +1,155 @@
1
+ // `softure migrate` (docs/02-module-standard.md §4). The app passes its validated config, so the
2
+ // same function serves the `softure` bin and an app script that esbuild bundles for a container:
3
+ //
4
+ // // scripts/migrate.ts
5
+ // import { runMigrateCli } from "@softure-ai/db/cli";
6
+ // import config from "../softure.config";
7
+ // process.exitCode = await runMigrateCli({ config, argv: process.argv.slice(2) });
8
+ import { resolve } from "node:path";
9
+ import { parseArgs } from "node:util";
10
+ import { pathToFileURL } from "node:url";
11
+ import { createDatabase } from "../client.js";
12
+ import { adoptModule } from "../migrations/adopt.js";
13
+ import { exportMigrations } from "../migrations/export.js";
14
+ import { migrate, planMigrations } from "../migrations/migrator.js";
15
+ import { describeProblem } from "../migrations/problems.js";
16
+ export const EXIT_OK = 0;
17
+ export const EXIT_FAILED = 1;
18
+ export const EXIT_USAGE = 2;
19
+ export const MIGRATE_USAGE = `Usage: softure migrate [options]
20
+
21
+ Applies the SQL migrations of every enabled module, each in its own schema.
22
+
23
+ Options:
24
+ --plan show what would be applied (or adopted) and change nothing
25
+ --adopt <module>@<version> record an existing schema as migrated, after comparing it
26
+ --migrations-dir <dir> read module files from <dir>/<module id>/ (bundled runners)
27
+ --export-migrations <dir> copy module files to <dir>/<module id>/ and exit (build stage)
28
+ --help show this help`;
29
+ const ADOPT_TARGET = /^([a-z][a-z0-9]*(?:-[a-z0-9]+)*)@(\d+\.\d+\.\d+)$/;
30
+ const consoleOutput = {
31
+ log: (line) => console.log(line),
32
+ error: (line) => console.error(line),
33
+ };
34
+ /** Runs the command and returns the process exit code: 0 done, 1 failed, 2 usage error. */
35
+ export async function runMigrateCli(options) {
36
+ const output = options.output ?? consoleOutput;
37
+ const cwd = options.cwd ?? process.cwd();
38
+ const command = parseCommand(options.argv);
39
+ if (typeof command === "string") {
40
+ output.error(`softure migrate: ${command}`);
41
+ output.error(MIGRATE_USAGE);
42
+ return EXIT_USAGE;
43
+ }
44
+ if (command.kind === "help") {
45
+ output.log(MIGRATE_USAGE);
46
+ return EXIT_OK;
47
+ }
48
+ if (command.kind === "export") {
49
+ const result = await exportMigrations(options.config.modules, resolve(cwd, command.targetDir));
50
+ if (!result.ok)
51
+ return reportFailure(output, result);
52
+ result.value.forEach((step) => output.log(`exported ${formatStep(step)}`));
53
+ return EXIT_OK;
54
+ }
55
+ const url = options.config.database?.url;
56
+ if (url === undefined) {
57
+ output.error("softure migrate: the config has no database; set database.url in softure.config");
58
+ return EXIT_FAILED;
59
+ }
60
+ let handle;
61
+ try {
62
+ handle = await createDatabase(url);
63
+ }
64
+ catch (error) {
65
+ output.error(`softure migrate: ${describeError(error)}`);
66
+ return EXIT_FAILED;
67
+ }
68
+ try {
69
+ const migrationsDir = command.migrationsDir === undefined ? undefined : pathToFileURL(`${resolve(cwd, command.migrationsDir)}/`);
70
+ return await runDatabaseCommand(handle, { command, modules: options.config.modules, migrationsDir, output });
71
+ }
72
+ catch (error) {
73
+ // Connection and driver errors: the message only, never a stack or the database URL.
74
+ output.error(`softure migrate: ${describeError(error)}`);
75
+ return EXIT_FAILED;
76
+ }
77
+ finally {
78
+ await handle.close();
79
+ }
80
+ }
81
+ async function runDatabaseCommand(handle, input) {
82
+ const { command, modules, output } = input;
83
+ const dirOption = input.migrationsDir === undefined ? {} : { migrationsDir: input.migrationsDir };
84
+ if (command.kind === "adopt") {
85
+ const result = await adoptModule(handle, { modules, module: command.module, version: command.version, dryRun: command.plan, ...dirOption });
86
+ if (!result.ok)
87
+ return reportFailure(output, result);
88
+ const verb = command.plan ? "would" : "did";
89
+ result.value.ledger.forEach((step) => output.log(`${verb} apply ${formatStep(step)}`));
90
+ result.value.adopted.forEach((step) => output.log(`${verb} adopt ${formatStep(step)}`));
91
+ output.log(`${command.module}@${command.version}: the schema matches its migrations${command.plan ? " (plan only, nothing written)" : "; adopted"}`);
92
+ return EXIT_OK;
93
+ }
94
+ if (command.plan) {
95
+ const result = await planMigrations(handle, { modules, ...dirOption });
96
+ if (!result.ok)
97
+ return reportFailure(output, result);
98
+ result.value.pending.forEach((step) => output.log(`pending ${formatStep(step)}`));
99
+ output.log(result.value.pending.length === 0 ? "nothing to apply" : `${result.value.pending.length} migration(s) to apply`);
100
+ return EXIT_OK;
101
+ }
102
+ const result = await migrate(handle, { modules, ...dirOption, onApplied: (step) => output.log(`applied ${formatStep(step)}`) });
103
+ if (!result.ok)
104
+ return reportFailure(output, result);
105
+ output.log(result.value.applied.length === 0 ? "nothing to apply" : `${result.value.applied.length} migration(s) applied`);
106
+ return EXIT_OK;
107
+ }
108
+ function parseCommand(argv) {
109
+ let values;
110
+ try {
111
+ ({ values } = parseArgs({
112
+ args: [...argv],
113
+ options: {
114
+ plan: { type: "boolean" },
115
+ adopt: { type: "string" },
116
+ "migrations-dir": { type: "string" },
117
+ "export-migrations": { type: "string" },
118
+ help: { type: "boolean" },
119
+ },
120
+ strict: true,
121
+ allowPositionals: false,
122
+ }));
123
+ }
124
+ catch (error) {
125
+ return describeError(error);
126
+ }
127
+ if (values.help === true)
128
+ return { kind: "help" };
129
+ const exportDir = values["export-migrations"];
130
+ if (exportDir !== undefined) {
131
+ const others = ["plan", "adopt", "migrations-dir"].filter((name) => values[name] !== undefined);
132
+ return others.length > 0 ? `--export-migrations cannot be combined with --${others.join(", --")}` : { kind: "export", targetDir: exportDir };
133
+ }
134
+ const plan = values.plan === true;
135
+ const migrationsDir = values["migrations-dir"];
136
+ if (values.adopt === undefined) {
137
+ return { kind: "migrate", plan, migrationsDir };
138
+ }
139
+ const match = ADOPT_TARGET.exec(values.adopt);
140
+ if (match?.[1] === undefined || match[2] === undefined) {
141
+ return `--adopt expects <module>@<x.y.z>, e.g. auth@0.1.0, got "${values.adopt}"`;
142
+ }
143
+ return { kind: "adopt", plan, module: match[1], version: match[2], migrationsDir };
144
+ }
145
+ function reportFailure(output, failure) {
146
+ failure.problems.forEach((problem) => output.error(describeProblem(problem)));
147
+ return EXIT_FAILED;
148
+ }
149
+ function formatStep(step) {
150
+ return `${step.module} ${String(step.version).padStart(4, "0")}_${step.name}.sql`;
151
+ }
152
+ function describeError(error) {
153
+ return error instanceof Error ? error.message : String(error);
154
+ }
155
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/cli/run.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ import type { PGlite } from "@electric-sql/pglite";
2
+ import type { NodePgDatabase } from "drizzle-orm/node-postgres";
3
+ import type { PgliteDatabase } from "drizzle-orm/pglite";
4
+ import type { Pool } from "pg";
5
+ export type PostgresDatabase = NodePgDatabase & {
6
+ $client: Pool;
7
+ };
8
+ export type PgliteClientDatabase = PgliteDatabase & {
9
+ $client: PGlite;
10
+ };
11
+ /** The drizzle database modules and the app query through. */
12
+ export type Database = PostgresDatabase | PgliteClientDatabase;
13
+ /**
14
+ * A connection *or* an open transaction. Query helpers take this, so one function serves a
15
+ * plain read and a step inside `db.transaction()` (FIRE `client.ts`).
16
+ */
17
+ export type Queryable = Database | Parameters<Parameters<Database["transaction"]>[0]>[0];
18
+ export type DatabaseHandle = {
19
+ readonly kind: "postgres";
20
+ readonly db: PostgresDatabase;
21
+ readonly pool: Pool;
22
+ readonly close: () => Promise<void>;
23
+ } | {
24
+ readonly kind: "pglite";
25
+ readonly db: PgliteClientDatabase;
26
+ readonly client: PGlite;
27
+ readonly close: () => Promise<void>;
28
+ };
29
+ export interface CreateDatabaseOptions {
30
+ /** Pool size for Postgres; ignored by PGlite, which has a single connection. Default 10. */
31
+ readonly max?: number;
32
+ }
33
+ /**
34
+ * Opens the database a URL points at. Throws when the scheme is not supported: that is a
35
+ * deployment bug. The message names the scheme only, never the URL, which holds a password.
36
+ */
37
+ export declare function createDatabase(url: string, options?: CreateDatabaseOptions): Promise<DatabaseHandle>;
38
+ /** Wraps an open PGlite instance (used by `createTestDatabase`). */
39
+ export declare function createPgliteHandle(client: PGlite): Promise<DatabaseHandle & {
40
+ kind: "pglite";
41
+ }>;
42
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC;AAK/B,MAAM,MAAM,gBAAgB,GAAG,cAAc,GAAG;IAAE,OAAO,EAAE,IAAI,CAAA;CAAE,CAAC;AAClE,MAAM,MAAM,oBAAoB,GAAG,cAAc,GAAG;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAExE,8DAA8D;AAC9D,MAAM,MAAM,QAAQ,GAAG,gBAAgB,GAAG,oBAAoB,CAAC;AAE/D;;;GAGG;AACH,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,UAAU,CAAC,UAAU,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAEzF,MAAM,MAAM,cAAc,GACtB;IACE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,EAAE,EAAE,gBAAgB,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACrC,GACD;IACE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,EAAE,EAAE,oBAAoB,CAAC;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACrC,CAAC;AAEN,MAAM,WAAW,qBAAqB;IACpC,4FAA4F;IAC5F,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,qBAA0B,GAAG,OAAO,CAAC,cAAc,CAAC,CAmB9G;AAED,oEAAoE;AACpE,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC,CAGrG"}
package/dist/client.js ADDED
@@ -0,0 +1,34 @@
1
+ const PGLITE_PREFIX = "pglite://";
2
+ const POSTGRES_PREFIXES = ["postgres://", "postgresql://"];
3
+ /**
4
+ * Opens the database a URL points at. Throws when the scheme is not supported: that is a
5
+ * deployment bug. The message names the scheme only, never the URL, which holds a password.
6
+ */
7
+ export async function createDatabase(url, options = {}) {
8
+ if (url.startsWith(PGLITE_PREFIX)) {
9
+ const dataDir = url.slice(PGLITE_PREFIX.length);
10
+ const { PGlite } = await import("@electric-sql/pglite");
11
+ const client = dataDir === "" ? new PGlite() : new PGlite(dataDir);
12
+ return createPgliteHandle(client);
13
+ }
14
+ if (POSTGRES_PREFIXES.some((prefix) => url.startsWith(prefix))) {
15
+ const { default: pg } = await import("pg");
16
+ const { drizzle } = await import("drizzle-orm/node-postgres");
17
+ const pool = new pg.Pool({ connectionString: url, max: options.max ?? 10 });
18
+ // An idle client that loses its connection emits 'error' on the pool; unhandled, it would
19
+ // crash the process. The pool drops that client and the next query opens a new one.
20
+ pool.on("error", () => undefined);
21
+ return { kind: "postgres", db: drizzle({ client: pool }), pool, close: () => pool.end() };
22
+ }
23
+ throw new Error(`createDatabase: unsupported database URL scheme ${describeScheme(url)}; use postgres://, postgresql:// or pglite://<dir>`);
24
+ }
25
+ /** Wraps an open PGlite instance (used by `createTestDatabase`). */
26
+ export async function createPgliteHandle(client) {
27
+ const { drizzle } = await import("drizzle-orm/pglite");
28
+ return { kind: "pglite", db: drizzle({ client }), client, close: () => client.close() };
29
+ }
30
+ function describeScheme(url) {
31
+ const scheme = /^([a-z][a-z0-9+.-]*):/i.exec(url)?.[1];
32
+ return scheme === undefined ? "(none)" : `"${scheme}:"`;
33
+ }
34
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAUA,MAAM,aAAa,GAAG,WAAW,CAAC;AAClC,MAAM,iBAAiB,GAAG,CAAC,aAAa,EAAE,eAAe,CAAC,CAAC;AAiC3D;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,GAAW,EAAE,UAAiC,EAAE;IACnF,IAAI,GAAG,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;QAClC,MAAM,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;QAChD,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,sBAAsB,CAAC,CAAC;QACxD,MAAM,MAAM,GAAG,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,MAAM,EAAE,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC;QACnE,OAAO,kBAAkB,CAAC,MAAM,CAAC,CAAC;IACpC,CAAC;IACD,IAAI,iBAAiB,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;QAC/D,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;QAC3C,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,MAAM,CAAC,2BAA2B,CAAC,CAAC;QAC9D,MAAM,IAAI,GAAG,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,gBAAgB,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,IAAI,EAAE,EAAE,CAAC,CAAC;QAC5E,0FAA0F;QAC1F,oFAAoF;QACpF,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAClC,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;IAC5F,CAAC;IACD,MAAM,IAAI,KAAK,CACb,mDAAmD,cAAc,CAAC,GAAG,CAAC,oDAAoD,CAC3H,CAAC;AACJ,CAAC;AAED,oEAAoE;AACpE,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,MAAc;IACrD,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC;IACvD,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,CAAC;AAC1F,CAAC;AAED,SAAS,cAAc,CAAC,GAAW;IACjC,MAAM,MAAM,GAAG,wBAAwB,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACvD,OAAO,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,MAAM,IAAI,CAAC;AAC1D,CAAC"}
@@ -0,0 +1,11 @@
1
+ export { createDatabase, type CreateDatabaseOptions, type Database, type DatabaseHandle, type PgliteClientDatabase, type PostgresDatabase, type Queryable, } from "./client.js";
2
+ export { closeSharedDatabases, getSharedDatabase } from "./shared.js";
3
+ export { computeChecksum, readMigrationFiles, type MigrationFile } from "./migrations/files.js";
4
+ export { describeProblem, type MigrationErrorCode, type MigrationFailure, type MigrationProblem, type MigrationResult, } from "./migrations/problems.js";
5
+ export { migrate, planMigrations, type MigrateOptions, type MigrationPlan, type MigrationReport, type MigrationStep, } from "./migrations/migrator.js";
6
+ export { adoptModule, type AdoptionReport, type AdoptOptions } from "./migrations/adopt.js";
7
+ export { exportMigrations } from "./migrations/export.js";
8
+ export { checkExportedMigrations, type ExportedMigration, type ExportedMigrationsCheck, } from "./migrations/exported.js";
9
+ export { readJournal, type JournalRow, type MigrationMethod } from "./migrations/ledger.js";
10
+ export type { MigrationSession } from "./migrations/session.js";
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,cAAc,EACd,KAAK,qBAAqB,EAC1B,KAAK,QAAQ,EACb,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,SAAS,GACf,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACtE,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,KAAK,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAChG,OAAO,EACL,eAAe,EACf,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,eAAe,GACrB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,OAAO,EACP,cAAc,EACd,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,aAAa,GACnB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,WAAW,EAAE,KAAK,cAAc,EAAE,KAAK,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC5F,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EACL,uBAAuB,EACvB,KAAK,iBAAiB,EACtB,KAAK,uBAAuB,GAC7B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,WAAW,EAAE,KAAK,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC5F,YAAY,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ // Public API of @softure-ai/db: the database client and the module migrator
2
+ // (docs/02-module-standard.md §4). Test databases: `@softure-ai/db/testing`; CLI: `@softure-ai/db/cli`.
3
+ export { createDatabase, } from "./client.js";
4
+ export { closeSharedDatabases, getSharedDatabase } from "./shared.js";
5
+ export { computeChecksum, readMigrationFiles } from "./migrations/files.js";
6
+ export { describeProblem, } from "./migrations/problems.js";
7
+ export { migrate, planMigrations, } from "./migrations/migrator.js";
8
+ export { adoptModule } from "./migrations/adopt.js";
9
+ export { exportMigrations } from "./migrations/export.js";
10
+ export { checkExportedMigrations, } from "./migrations/exported.js";
11
+ export { readJournal } from "./migrations/ledger.js";
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,wGAAwG;AACxG,OAAO,EACL,cAAc,GAOf,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACtE,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAsB,MAAM,uBAAuB,CAAC;AAChG,OAAO,EACL,eAAe,GAKhB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,OAAO,EACP,cAAc,GAKf,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,WAAW,EAA0C,MAAM,uBAAuB,CAAC;AAC5F,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EACL,uBAAuB,GAGxB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,WAAW,EAAyC,MAAM,wBAAwB,CAAC"}