@mailwoman/sqlite 9.2.0
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/lib/batched.ts +69 -0
- package/lib/client.ts +163 -0
- package/lib/database-schema.ts +30 -0
- package/lib/dialect-config.ts +30 -0
- package/lib/dialect.ts +67 -0
- package/lib/driver.ts +175 -0
- package/lib/index.ts +25 -0
- package/lib/introspection.ts +57 -0
- package/lib/schema-columns.ts +52 -0
- package/lib/sealed-build.ts +117 -0
- package/lib/sealed-db.ts +170 -0
- package/lib/sealed.ts +38 -0
- package/out/batched.d.ts +40 -0
- package/out/batched.d.ts.map +1 -0
- package/out/batched.js +41 -0
- package/out/batched.js.map +1 -0
- package/out/client.d.ts +86 -0
- package/out/client.d.ts.map +1 -0
- package/out/client.js +85 -0
- package/out/client.js.map +1 -0
- package/out/database-schema.d.ts +30 -0
- package/out/database-schema.d.ts.map +1 -0
- package/out/database-schema.js +22 -0
- package/out/database-schema.js.map +1 -0
- package/out/dialect-config.d.ts +27 -0
- package/out/dialect-config.d.ts.map +1 -0
- package/out/dialect-config.js +7 -0
- package/out/dialect-config.js.map +1 -0
- package/out/dialect.d.ts +39 -0
- package/out/dialect.d.ts.map +1 -0
- package/out/dialect.js +49 -0
- package/out/dialect.js.map +1 -0
- package/out/driver.d.ts +22 -0
- package/out/driver.d.ts.map +1 -0
- package/out/driver.js +114 -0
- package/out/driver.js.map +1 -0
- package/out/index.d.ts +25 -0
- package/out/index.d.ts.map +1 -0
- package/out/index.js +25 -0
- package/out/index.js.map +1 -0
- package/out/introspection.d.ts +34 -0
- package/out/introspection.d.ts.map +1 -0
- package/out/introspection.js +46 -0
- package/out/introspection.js.map +1 -0
- package/out/schema-columns.d.ts +31 -0
- package/out/schema-columns.d.ts.map +1 -0
- package/out/schema-columns.js +38 -0
- package/out/schema-columns.js.map +1 -0
- package/out/sealed-build.d.ts +51 -0
- package/out/sealed-build.d.ts.map +1 -0
- package/out/sealed-build.js +71 -0
- package/out/sealed-build.js.map +1 -0
- package/out/sealed-db.d.ts +74 -0
- package/out/sealed-db.d.ts.map +1 -0
- package/out/sealed-db.js +140 -0
- package/out/sealed-db.js.map +1 -0
- package/out/sealed.d.ts +27 -0
- package/out/sealed.d.ts.map +1 -0
- package/out/sealed.js +31 -0
- package/out/sealed.js.map +1 -0
- package/out/tsconfig.test.tsbuildinfo +1 -0
- package/package.json +172 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* The sealed polygon-artifact build sequence: one temp file beside the destination, two handles, every
|
|
7
|
+
* cleanup path, then `VACUUM` → seal → atomic swap.
|
|
8
|
+
*
|
|
9
|
+
* THE INGEST AND THE FINISH PHASES USE SEPARATE HANDLES, ALWAYS — including the in-process path, which does
|
|
10
|
+
* not need them separated. The batched path DOES: its children open the same file, so the parent's handle
|
|
11
|
+
* has to be closed across them, and a single shared handle silently becomes a closed one by the time the
|
|
12
|
+
* finish phase runs. Doing it one way in both paths is what puts the fixture suites on the same sequence a
|
|
13
|
+
* national build takes.
|
|
14
|
+
*/
|
|
15
|
+
import { removePathIfPresent } from "@mailwoman/core/fs/writers";
|
|
16
|
+
import { DatabaseClient } from "#client";
|
|
17
|
+
import { sealDatabase, swapDatabaseIntoPlace } from "#sealed-db";
|
|
18
|
+
/**
|
|
19
|
+
* Run one sealed-artifact build.
|
|
20
|
+
*
|
|
21
|
+
* Every failure path removes the temp file: a failed build otherwise leaves a partial multi-gigabyte file whose name
|
|
22
|
+
* carries this process's pid, so nothing would ever pick it up again — the difference between a retry loop that fails
|
|
23
|
+
* and one that fills a disk.
|
|
24
|
+
*/
|
|
25
|
+
export async function buildSealedArtifact(options) {
|
|
26
|
+
const tmpPath = `${options.out}.tmp-${process.pid}`;
|
|
27
|
+
let streamed;
|
|
28
|
+
{
|
|
29
|
+
await using kdb = new DatabaseClient(tmpPath);
|
|
30
|
+
try {
|
|
31
|
+
kdb.exec("PRAGMA journal_mode = OFF");
|
|
32
|
+
kdb.exec("PRAGMA synchronous = OFF");
|
|
33
|
+
await options.createTables(kdb);
|
|
34
|
+
streamed = await options.ingest(kdb);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
await kdb.destroy().catch(() => undefined);
|
|
38
|
+
await removePathIfPresent(tmpPath);
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (streamed === undefined) {
|
|
43
|
+
if (!options.batched) {
|
|
44
|
+
throw new Error("buildSealedArtifact: the in-process ingest deferred and no batched ingest was supplied");
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
streamed = await options.batched(tmpPath);
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
await removePathIfPresent(tmpPath);
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const kdb = new DatabaseClient(tmpPath);
|
|
55
|
+
try {
|
|
56
|
+
kdb.exec("PRAGMA journal_mode = OFF");
|
|
57
|
+
kdb.exec("PRAGMA synchronous = OFF");
|
|
58
|
+
const result = await options.finish(kdb, streamed);
|
|
59
|
+
kdb.exec("VACUUM");
|
|
60
|
+
await kdb.destroy();
|
|
61
|
+
await sealDatabase(tmpPath);
|
|
62
|
+
await swapDatabaseIntoPlace(tmpPath, options.out);
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
await kdb.destroy().catch(() => undefined);
|
|
67
|
+
await removePathIfPresent(tmpPath);
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=sealed-build.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sealed-build.js","sourceRoot":"","sources":["../lib/sealed-build.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AAEhE,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AACxC,OAAO,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AA8BhE;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACxC,OAAyD;IAEzD,MAAM,OAAO,GAAG,GAAG,OAAO,CAAC,GAAG,QAAQ,OAAO,CAAC,GAAG,EAAE,CAAA;IAEnD,IAAI,QAA8B,CAAA;IAElC,CAAC;QACA,YAAY,GAAG,GAAG,IAAI,cAAc,CAAK,OAAO,CAAC,CAAA;QAEjD,IAAI,CAAC;YACJ,GAAG,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAA;YACrC,GAAG,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAA;YAEpC,MAAM,OAAO,CAAC,YAAY,CAAC,GAAG,CAAC,CAAA;YAE/B,QAAQ,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QACrC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,MAAM,GAAG,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAA;YAC1C,MAAM,mBAAmB,CAAC,OAAO,CAAC,CAAA;YAElC,MAAM,KAAK,CAAA;QACZ,CAAC;IACF,CAAC;IAED,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CAAC,wFAAwF,CAAC,CAAA;QAC1G,CAAC;QAED,IAAI,CAAC;YACJ,QAAQ,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;QAC1C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,MAAM,mBAAmB,CAAC,OAAO,CAAC,CAAA;YAElC,MAAM,KAAK,CAAA;QACZ,CAAC;IACF,CAAC;IAED,MAAM,GAAG,GAAG,IAAI,cAAc,CAAK,OAAO,CAAC,CAAA;IAE3C,IAAI,CAAC;QACJ,GAAG,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAA;QACrC,GAAG,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAA;QAEpC,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAA;QAElD,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QAElB,MAAM,GAAG,CAAC,OAAO,EAAE,CAAA;QAEnB,MAAM,YAAY,CAAC,OAAO,CAAC,CAAA;QAC3B,MAAM,qBAAqB,CAAC,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,CAAA;QAEjD,OAAO,MAAM,CAAA;IACd,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,MAAM,GAAG,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAA;QAC1C,MAAM,mBAAmB,CAAC,OAAO,CAAC,CAAA;QAElC,MAAM,KAAK,CAAA;IACZ,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* The sealed-artifact invariant: every SQLite DB a build produces is a READ-ONLY asset. `sealDatabase`
|
|
7
|
+
* is the last step of every builder — checkpoint, freeze the journal, chmod 0444. `openBuiltClient`
|
|
8
|
+
* (`@mailwoman/sqlite/sealed`) is how anything opens a data artifact; a write-mode open of a sealed
|
|
9
|
+
* file throws a NAMED error pointing at the rebuild command instead of a cryptic SQLITE_READONLY. Unsealing is deliberate and
|
|
10
|
+
* manual (`chmod u+w`), never programmatic — rebuild, don't mutate.
|
|
11
|
+
*
|
|
12
|
+
* `swapDatabaseIntoPlace` is the other half of that invariant — the atomic publish step AGENTS.md
|
|
13
|
+
* specifies in prose ("build it successfully, then move the previous version to a temp directory,
|
|
14
|
+
* and then move the new version into place"). It lives here because this module already owns the
|
|
15
|
+
* built-artifact lifecycle, and because a rule the project states in prose and implements more than
|
|
16
|
+
* once in code should be a function.
|
|
17
|
+
*/
|
|
18
|
+
import type { DatabaseSync } from "node:sqlite";
|
|
19
|
+
/**
|
|
20
|
+
* The one capability {@link assertDatabaseIntegrity} needs. Narrowing to it rather than naming a handle type lets a
|
|
21
|
+
* `DatabaseClient` satisfy the parameter with no import and no cast.
|
|
22
|
+
*/
|
|
23
|
+
type IntegrityProbe = Pick<DatabaseSync, "prepare">;
|
|
24
|
+
/**
|
|
25
|
+
* A write-mode open was attempted on a sealed (0444) data artifact.
|
|
26
|
+
*/
|
|
27
|
+
export declare class SealedArtifactError extends Error {
|
|
28
|
+
constructor(path: string);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* True when the artifact exists and carries no write bits (the sealed state {@link sealDatabase} leaves).
|
|
32
|
+
*/
|
|
33
|
+
export declare function isSealed(path: string): Promise<boolean>;
|
|
34
|
+
/**
|
|
35
|
+
* Finalize a built DB: WAL-checkpoint → `journal_mode = DELETE` → remove `-wal`/`-shm` sidecars → `chmod 0o444`.
|
|
36
|
+
* Idempotent. Throws if the checkpoint cannot complete (another writer holds the DB).
|
|
37
|
+
*/
|
|
38
|
+
export declare function sealDatabase(path: string): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Verify a freshly-built database is not corrupt, immediately before it is sealed and published.
|
|
41
|
+
*
|
|
42
|
+
* Belongs beside {@link sealDatabase} for the same reason `swapDatabaseIntoPlace` does: the check is part of the
|
|
43
|
+
* built-artifact lifecycle, and every builder was running it from its own copy. `integrity_check` answers with the
|
|
44
|
+
* single row `{ integrity_check: "ok" }` on a healthy file and one row per problem otherwise, so only the first matters
|
|
45
|
+
* — a builder that reads the column and compares it to `"ok"` has done the whole check.
|
|
46
|
+
*
|
|
47
|
+
* @throws When the database reports anything other than `ok`, naming the artifact and what SQLite said.
|
|
48
|
+
*/
|
|
49
|
+
export declare function assertDatabaseIntegrity(db: IntegrityProbe, artifact: string): void;
|
|
50
|
+
/**
|
|
51
|
+
* Refuse a write-mode open of a sealed artifact, naming the rebuild command instead of letting SQLite answer
|
|
52
|
+
* `SQLITE_READONLY`.
|
|
53
|
+
*
|
|
54
|
+
* The open itself lives in `@mailwoman/sqlite/sealed` — `openBuiltClient`, which every caller uses. This half stays
|
|
55
|
+
* here because it is a filesystem predicate, and because this module reaches `node:sqlite` through
|
|
56
|
+
* {@link process.getBuiltinModule} to keep the `@mailwoman/core/utils` barrel free of it.
|
|
57
|
+
*
|
|
58
|
+
* @throws {SealedArtifactError} When `path` is sealed.
|
|
59
|
+
*/
|
|
60
|
+
export declare function assertUnsealedForWrite(path: string): Promise<void>;
|
|
61
|
+
/**
|
|
62
|
+
* Atomically move a freshly-built database into its published location.
|
|
63
|
+
*
|
|
64
|
+
* The build writes to a temp path, so a mid-build crash never leaves a half-written DB at `finalPath`. This moves any
|
|
65
|
+
* prior version aside, slots the new one in, then drops the old — the previous file stays intact until the replacement
|
|
66
|
+
* is committed, and the `-wal`/`-shm` siblings of BOTH paths are cleared so a stale journal can never be paired with a
|
|
67
|
+
* new main file.
|
|
68
|
+
*
|
|
69
|
+
* Sealing (`sealDatabase`) happens on the temp file BEFORE the swap: a sealed artifact is what gets published, and 0444
|
|
70
|
+
* does not prevent a rename.
|
|
71
|
+
*/
|
|
72
|
+
export declare function swapDatabaseIntoPlace(tmpPath: string, finalPath: string): Promise<void>;
|
|
73
|
+
export {};
|
|
74
|
+
//# sourceMappingURL=sealed-db.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sealed-db.d.ts","sourceRoot":"","sources":["../lib/sealed-db.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAM/C;;;GAGG;AACH,KAAK,cAAc,GAAG,IAAI,CAAC,YAAY,EAAE,SAAS,CAAC,CAAA;AAYnD;;GAEG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;gBACjC,IAAI,EAAE,MAAM;CAQxB;AAED;;GAEG;AACH,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAE7D;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA8B9D;AAED;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAOlF;AAED;;;;;;;;;GASG;AACH,wBAAsB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAExE;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA4B7F"}
|
package/out/sealed-db.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* The sealed-artifact invariant: every SQLite DB a build produces is a READ-ONLY asset. `sealDatabase`
|
|
7
|
+
* is the last step of every builder — checkpoint, freeze the journal, chmod 0444. `openBuiltClient`
|
|
8
|
+
* (`@mailwoman/sqlite/sealed`) is how anything opens a data artifact; a write-mode open of a sealed
|
|
9
|
+
* file throws a NAMED error pointing at the rebuild command instead of a cryptic SQLITE_READONLY. Unsealing is deliberate and
|
|
10
|
+
* manual (`chmod u+w`), never programmatic — rebuild, don't mutate.
|
|
11
|
+
*
|
|
12
|
+
* `swapDatabaseIntoPlace` is the other half of that invariant — the atomic publish step AGENTS.md
|
|
13
|
+
* specifies in prose ("build it successfully, then move the previous version to a temp directory,
|
|
14
|
+
* and then move the new version into place"). It lives here because this module already owns the
|
|
15
|
+
* built-artifact lifecycle, and because a rule the project states in prose and implements more than
|
|
16
|
+
* once in code should be a function.
|
|
17
|
+
*/
|
|
18
|
+
import { pathExists, statPath } from "@mailwoman/core/fs/readers";
|
|
19
|
+
import { changeMode, movePath, removePath, removePathIfPresent } from "@mailwoman/core/fs/writers";
|
|
20
|
+
import { basename } from "path-ts";
|
|
21
|
+
/**
|
|
22
|
+
* `node:sqlite` via {@link process.getBuiltinModule} — invisible to bundlers. A static import here would ride the
|
|
23
|
+
* `@mailwoman/core/utils` barrel into every consumer, and the docs' plugin loader (which transpiles `docusaurus.config`
|
|
24
|
+
* imports) can't resolve `node:sqlite` (CI: "Cannot find module 'sqlite'"). The builtin accessor keeps `node:sqlite`
|
|
25
|
+
* out of the static import graph with zero resolve surface.
|
|
26
|
+
*/
|
|
27
|
+
function sqlite() {
|
|
28
|
+
return process.getBuiltinModule("node:sqlite");
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* A write-mode open was attempted on a sealed (0444) data artifact.
|
|
32
|
+
*/
|
|
33
|
+
export class SealedArtifactError extends Error {
|
|
34
|
+
constructor(path) {
|
|
35
|
+
super(`${basename(path)} is a sealed read-only artifact — rebuild it via \`mailwoman gazetteer build …\`, ` +
|
|
36
|
+
`don't mutate it. (Deliberate unseal: chmod u+w — but prefer a rebuild.)`);
|
|
37
|
+
this.name = "SealedArtifactError";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* True when the artifact exists and carries no write bits (the sealed state {@link sealDatabase} leaves).
|
|
42
|
+
*/
|
|
43
|
+
export async function isSealed(path) {
|
|
44
|
+
return (await pathExists(path)) && ((await statPath(path)).mode & 0o222) === 0;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Finalize a built DB: WAL-checkpoint → `journal_mode = DELETE` → remove `-wal`/`-shm` sidecars → `chmod 0o444`.
|
|
48
|
+
* Idempotent. Throws if the checkpoint cannot complete (another writer holds the DB).
|
|
49
|
+
*/
|
|
50
|
+
export async function sealDatabase(path) {
|
|
51
|
+
// A previously sealed artifact needs the write bit back for the journal-mode switch.
|
|
52
|
+
if (await isSealed(path)) {
|
|
53
|
+
await changeMode(path, 0o644);
|
|
54
|
+
}
|
|
55
|
+
let mode;
|
|
56
|
+
{
|
|
57
|
+
using db = new (sqlite().DatabaseSync)(path);
|
|
58
|
+
const checkpoint = db.prepare("PRAGMA wal_checkpoint(TRUNCATE)").get();
|
|
59
|
+
if (checkpoint.busy !== 0) {
|
|
60
|
+
throw new Error(`sealDatabase: WAL checkpoint busy on ${path} — close all writers first`);
|
|
61
|
+
}
|
|
62
|
+
mode = db.prepare("PRAGMA journal_mode = DELETE").get();
|
|
63
|
+
}
|
|
64
|
+
if (mode.journal_mode !== "delete") {
|
|
65
|
+
throw new Error(`sealDatabase: journal_mode switch failed on ${path} (still ${mode.journal_mode})`);
|
|
66
|
+
}
|
|
67
|
+
for (const sidecar of [`${path}-wal`, `${path}-shm`]) {
|
|
68
|
+
if (await pathExists(sidecar)) {
|
|
69
|
+
await removePath(sidecar);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
await changeMode(path, 0o444);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Verify a freshly-built database is not corrupt, immediately before it is sealed and published.
|
|
76
|
+
*
|
|
77
|
+
* Belongs beside {@link sealDatabase} for the same reason `swapDatabaseIntoPlace` does: the check is part of the
|
|
78
|
+
* built-artifact lifecycle, and every builder was running it from its own copy. `integrity_check` answers with the
|
|
79
|
+
* single row `{ integrity_check: "ok" }` on a healthy file and one row per problem otherwise, so only the first matters
|
|
80
|
+
* — a builder that reads the column and compares it to `"ok"` has done the whole check.
|
|
81
|
+
*
|
|
82
|
+
* @throws When the database reports anything other than `ok`, naming the artifact and what SQLite said.
|
|
83
|
+
*/
|
|
84
|
+
export function assertDatabaseIntegrity(db, artifact) {
|
|
85
|
+
const row = db.prepare("PRAGMA integrity_check").get();
|
|
86
|
+
const verdict = row?.integrity_check;
|
|
87
|
+
if (verdict !== "ok") {
|
|
88
|
+
throw new Error(`integrity_check failed on ${basename(artifact)}: ${verdict ?? "no result"}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Refuse a write-mode open of a sealed artifact, naming the rebuild command instead of letting SQLite answer
|
|
93
|
+
* `SQLITE_READONLY`.
|
|
94
|
+
*
|
|
95
|
+
* The open itself lives in `@mailwoman/sqlite/sealed` — `openBuiltClient`, which every caller uses. This half stays
|
|
96
|
+
* here because it is a filesystem predicate, and because this module reaches `node:sqlite` through
|
|
97
|
+
* {@link process.getBuiltinModule} to keep the `@mailwoman/core/utils` barrel free of it.
|
|
98
|
+
*
|
|
99
|
+
* @throws {SealedArtifactError} When `path` is sealed.
|
|
100
|
+
*/
|
|
101
|
+
export async function assertUnsealedForWrite(path) {
|
|
102
|
+
if (await isSealed(path))
|
|
103
|
+
throw new SealedArtifactError(path);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Atomically move a freshly-built database into its published location.
|
|
107
|
+
*
|
|
108
|
+
* The build writes to a temp path, so a mid-build crash never leaves a half-written DB at `finalPath`. This moves any
|
|
109
|
+
* prior version aside, slots the new one in, then drops the old — the previous file stays intact until the replacement
|
|
110
|
+
* is committed, and the `-wal`/`-shm` siblings of BOTH paths are cleared so a stale journal can never be paired with a
|
|
111
|
+
* new main file.
|
|
112
|
+
*
|
|
113
|
+
* Sealing (`sealDatabase`) happens on the temp file BEFORE the swap: a sealed artifact is what gets published, and 0444
|
|
114
|
+
* does not prevent a rename.
|
|
115
|
+
*/
|
|
116
|
+
export async function swapDatabaseIntoPlace(tmpPath, finalPath) {
|
|
117
|
+
const aside = `${finalPath}.old-${process.pid}`;
|
|
118
|
+
if (await pathExists(finalPath)) {
|
|
119
|
+
await movePath(finalPath, aside);
|
|
120
|
+
}
|
|
121
|
+
for (const sfx of ["-wal", "-shm"]) {
|
|
122
|
+
await removePathIfPresent(finalPath + sfx);
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
await movePath(tmpPath, finalPath);
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
// The prior version is already aside at this point — a failed forward rename must not leave
|
|
129
|
+
// the slot empty while a restorable artifact sits one rename away.
|
|
130
|
+
if ((await pathExists(aside)) && !(await pathExists(finalPath))) {
|
|
131
|
+
await movePath(aside, finalPath);
|
|
132
|
+
}
|
|
133
|
+
throw error;
|
|
134
|
+
}
|
|
135
|
+
for (const sfx of ["-wal", "-shm"]) {
|
|
136
|
+
await removePathIfPresent(tmpPath + sfx);
|
|
137
|
+
}
|
|
138
|
+
await removePathIfPresent(aside);
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=sealed-db.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sealed-db.js","sourceRoot":"","sources":["../lib/sealed-db.ts"],"names":[],"mappings":"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"}
|
package/out/sealed.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* Opening a built data artifact.
|
|
7
|
+
*
|
|
8
|
+
* This is the other half of the sealed-artifact invariant in `../utils/sealed-db.ts`: that module owns the
|
|
9
|
+
* filesystem side (seal, unseal-refusal, atomic swap) and stays free of a static `node:sqlite` import, because it
|
|
10
|
+
* rides the `@mailwoman/core/utils` barrel into consumers that cannot resolve one. The open lives here, where
|
|
11
|
+
* `DatabaseClient` is already imported and the cost is paid. The open is asynchronous: the write branch awaits the
|
|
12
|
+
* seal check against the filesystem before the handle is constructed.
|
|
13
|
+
*/
|
|
14
|
+
import { DatabaseClient } from "#client";
|
|
15
|
+
import type { Database } from "#database-schema";
|
|
16
|
+
/**
|
|
17
|
+
* Open a built data artifact. Read-only by default; `write: true` is for builders working on UNsealed staging, and
|
|
18
|
+
* throws `SealedArtifactError` against a sealed file.
|
|
19
|
+
*
|
|
20
|
+
* Together with `new DatabaseClient(path)` this is the whole set of ways a connection comes into being. `DatabaseSync`
|
|
21
|
+
* is constructed in exactly one place, inside `DatabaseClient`, so no caller holds a raw handle and no database ends up
|
|
22
|
+
* described by two schemas.
|
|
23
|
+
*/
|
|
24
|
+
export declare function openBuiltClient<DB = Database>(path: string, opts?: {
|
|
25
|
+
write?: boolean;
|
|
26
|
+
}): Promise<DatabaseClient<DB>>;
|
|
27
|
+
//# sourceMappingURL=sealed.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sealed.d.ts","sourceRoot":"","sources":["../lib/sealed.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AACxC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAGhD;;;;;;;GAOG;AACH,wBAAsB,eAAe,CAAC,EAAE,GAAG,QAAQ,EAClD,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAO,GAC5B,OAAO,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC,CAQ7B"}
|
package/out/sealed.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* Opening a built data artifact.
|
|
7
|
+
*
|
|
8
|
+
* This is the other half of the sealed-artifact invariant in `../utils/sealed-db.ts`: that module owns the
|
|
9
|
+
* filesystem side (seal, unseal-refusal, atomic swap) and stays free of a static `node:sqlite` import, because it
|
|
10
|
+
* rides the `@mailwoman/core/utils` barrel into consumers that cannot resolve one. The open lives here, where
|
|
11
|
+
* `DatabaseClient` is already imported and the cost is paid. The open is asynchronous: the write branch awaits the
|
|
12
|
+
* seal check against the filesystem before the handle is constructed.
|
|
13
|
+
*/
|
|
14
|
+
import { DatabaseClient } from "#client";
|
|
15
|
+
import { assertUnsealedForWrite } from "#sealed-db";
|
|
16
|
+
/**
|
|
17
|
+
* Open a built data artifact. Read-only by default; `write: true` is for builders working on UNsealed staging, and
|
|
18
|
+
* throws `SealedArtifactError` against a sealed file.
|
|
19
|
+
*
|
|
20
|
+
* Together with `new DatabaseClient(path)` this is the whole set of ways a connection comes into being. `DatabaseSync`
|
|
21
|
+
* is constructed in exactly one place, inside `DatabaseClient`, so no caller holds a raw handle and no database ends up
|
|
22
|
+
* described by two schemas.
|
|
23
|
+
*/
|
|
24
|
+
export async function openBuiltClient(path, opts = {}) {
|
|
25
|
+
if (opts.write) {
|
|
26
|
+
await assertUnsealedForWrite(path);
|
|
27
|
+
return new DatabaseClient(path);
|
|
28
|
+
}
|
|
29
|
+
return new DatabaseClient(path, { readOnly: true });
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=sealed.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sealed.js","sourceRoot":"","sources":["../lib/sealed.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAExC,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAA;AAEnD;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACpC,IAAY,EACZ,OAA4B,EAAE;IAE9B,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QAChB,MAAM,sBAAsB,CAAC,IAAI,CAAC,CAAA;QAElC,OAAO,IAAI,cAAc,CAAK,IAAI,CAAC,CAAA;IACpC,CAAC;IAED,OAAO,IAAI,cAAc,CAAK,IAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;AACxD,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"root":["../test/unit/client.test.ts","../test/unit/introspection.test.ts","../test/unit/sealed-db.test.ts"],"version":"6.0.3"}
|
package/package.json
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mailwoman/sqlite",
|
|
3
|
+
"version": "9.2.0",
|
|
4
|
+
"description": "The one way a Mailwoman SQLite connection is opened: a typed Kysely client over node:sqlite, plus the sealed-artifact lifecycle.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"kysely",
|
|
7
|
+
"sqlite"
|
|
8
|
+
],
|
|
9
|
+
"license": "AGPL-3.0-only OR LicenseRef-Commercial",
|
|
10
|
+
"contributors": [
|
|
11
|
+
{
|
|
12
|
+
"name": "Teffen Ellis",
|
|
13
|
+
"email": "teffen@sister.software"
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "https://github.com/sister-software/mailwoman.git",
|
|
19
|
+
"directory": "packages/sqlite"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"out/",
|
|
23
|
+
"*.ts",
|
|
24
|
+
"!*.test.ts",
|
|
25
|
+
"!test/**"
|
|
26
|
+
],
|
|
27
|
+
"type": "module",
|
|
28
|
+
"sideEffects": false,
|
|
29
|
+
"imports": {
|
|
30
|
+
"#*": {
|
|
31
|
+
"types": "./out/*.d.ts",
|
|
32
|
+
"node": "./out/*.js",
|
|
33
|
+
"default": "./out/*.js"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"exports": {
|
|
37
|
+
"./package.json": "./package.json",
|
|
38
|
+
".": {
|
|
39
|
+
"types": "./out/index.d.ts",
|
|
40
|
+
"node": "./out/index.js",
|
|
41
|
+
"default": "./out/index.js"
|
|
42
|
+
},
|
|
43
|
+
"./client": {
|
|
44
|
+
"types": "./out/client.d.ts",
|
|
45
|
+
"node": "./out/client.js",
|
|
46
|
+
"default": "./out/client.js"
|
|
47
|
+
},
|
|
48
|
+
"./introspection": {
|
|
49
|
+
"types": "./out/introspection.d.ts",
|
|
50
|
+
"node": "./out/introspection.js",
|
|
51
|
+
"default": "./out/introspection.js"
|
|
52
|
+
},
|
|
53
|
+
"./dialect": {
|
|
54
|
+
"types": "./out/dialect.d.ts",
|
|
55
|
+
"node": "./out/dialect.js",
|
|
56
|
+
"default": "./out/dialect.js"
|
|
57
|
+
},
|
|
58
|
+
"./dialect-config": {
|
|
59
|
+
"types": "./out/dialect-config.d.ts",
|
|
60
|
+
"node": "./out/dialect-config.js",
|
|
61
|
+
"default": "./out/dialect-config.js"
|
|
62
|
+
},
|
|
63
|
+
"./sealed": {
|
|
64
|
+
"types": "./out/sealed.d.ts",
|
|
65
|
+
"node": "./out/sealed.js",
|
|
66
|
+
"default": "./out/sealed.js"
|
|
67
|
+
},
|
|
68
|
+
"./sealed-db": {
|
|
69
|
+
"types": "./out/sealed-db.d.ts",
|
|
70
|
+
"node": "./out/sealed-db.js",
|
|
71
|
+
"default": "./out/sealed-db.js"
|
|
72
|
+
},
|
|
73
|
+
"./database-schema": {
|
|
74
|
+
"types": "./out/database-schema.d.ts",
|
|
75
|
+
"node": "./out/database-schema.js",
|
|
76
|
+
"default": "./out/database-schema.js"
|
|
77
|
+
},
|
|
78
|
+
"./batched": {
|
|
79
|
+
"types": "./out/batched.d.ts",
|
|
80
|
+
"node": "./out/batched.js",
|
|
81
|
+
"default": "./out/batched.js"
|
|
82
|
+
},
|
|
83
|
+
"./schema-columns": {
|
|
84
|
+
"types": "./out/schema-columns.d.ts",
|
|
85
|
+
"node": "./out/schema-columns.js",
|
|
86
|
+
"default": "./out/schema-columns.js"
|
|
87
|
+
},
|
|
88
|
+
"./sealed-build": {
|
|
89
|
+
"types": "./out/sealed-build.d.ts",
|
|
90
|
+
"node": "./out/sealed-build.js",
|
|
91
|
+
"default": "./out/sealed-build.js"
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
"publishConfig": {
|
|
95
|
+
"access": "public",
|
|
96
|
+
"exports": {
|
|
97
|
+
"./package.json": "./package.json",
|
|
98
|
+
".": {
|
|
99
|
+
"types": "./out/index.d.ts",
|
|
100
|
+
"node": "./out/index.js",
|
|
101
|
+
"default": "./out/index.js"
|
|
102
|
+
},
|
|
103
|
+
"./client": {
|
|
104
|
+
"types": "./out/client.d.ts",
|
|
105
|
+
"node": "./out/client.js",
|
|
106
|
+
"default": "./out/client.js"
|
|
107
|
+
},
|
|
108
|
+
"./introspection": {
|
|
109
|
+
"types": "./out/introspection.d.ts",
|
|
110
|
+
"node": "./out/introspection.js",
|
|
111
|
+
"default": "./out/introspection.js"
|
|
112
|
+
},
|
|
113
|
+
"./dialect": {
|
|
114
|
+
"types": "./out/dialect.d.ts",
|
|
115
|
+
"node": "./out/dialect.js",
|
|
116
|
+
"default": "./out/dialect.js"
|
|
117
|
+
},
|
|
118
|
+
"./dialect-config": {
|
|
119
|
+
"types": "./out/dialect-config.d.ts",
|
|
120
|
+
"node": "./out/dialect-config.js",
|
|
121
|
+
"default": "./out/dialect-config.js"
|
|
122
|
+
},
|
|
123
|
+
"./sealed": {
|
|
124
|
+
"types": "./out/sealed.d.ts",
|
|
125
|
+
"node": "./out/sealed.js",
|
|
126
|
+
"default": "./out/sealed.js"
|
|
127
|
+
},
|
|
128
|
+
"./sealed-db": {
|
|
129
|
+
"types": "./out/sealed-db.d.ts",
|
|
130
|
+
"node": "./out/sealed-db.js",
|
|
131
|
+
"default": "./out/sealed-db.js"
|
|
132
|
+
},
|
|
133
|
+
"./database-schema": {
|
|
134
|
+
"types": "./out/database-schema.d.ts",
|
|
135
|
+
"node": "./out/database-schema.js",
|
|
136
|
+
"default": "./out/database-schema.js"
|
|
137
|
+
},
|
|
138
|
+
"./batched": {
|
|
139
|
+
"types": "./out/batched.d.ts",
|
|
140
|
+
"node": "./out/batched.js",
|
|
141
|
+
"default": "./out/batched.js"
|
|
142
|
+
},
|
|
143
|
+
"./schema-columns": {
|
|
144
|
+
"types": "./out/schema-columns.d.ts",
|
|
145
|
+
"node": "./out/schema-columns.js",
|
|
146
|
+
"default": "./out/schema-columns.js"
|
|
147
|
+
},
|
|
148
|
+
"./sealed-build": {
|
|
149
|
+
"types": "./out/sealed-build.d.ts",
|
|
150
|
+
"node": "./out/sealed-build.js",
|
|
151
|
+
"default": "./out/sealed-build.js"
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
"imports": {
|
|
155
|
+
"#*": {
|
|
156
|
+
"types": "./out/*.d.ts",
|
|
157
|
+
"node": "./out/*.js",
|
|
158
|
+
"default": "./out/*.js"
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
"dependencies": {
|
|
163
|
+
"kysely": "^0.29.5",
|
|
164
|
+
"path-ts": "^2.3.0"
|
|
165
|
+
},
|
|
166
|
+
"devDependencies": {
|
|
167
|
+
"@mailwoman/core": "9.2.0"
|
|
168
|
+
},
|
|
169
|
+
"engines": {
|
|
170
|
+
"node": ">=24.18.0"
|
|
171
|
+
}
|
|
172
|
+
}
|