@coffre/db 0.0.0 → 0.1.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.
Files changed (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +13 -3
  3. package/dist/commits-BuW-NL3n.js +30 -0
  4. package/dist/connect.d.ts +21 -0
  5. package/dist/connect.js +155 -0
  6. package/dist/database-CWpo5aF0.d.ts +39 -0
  7. package/dist/dialect.d.ts +88 -0
  8. package/dist/dialect.js +128 -0
  9. package/dist/hyperdrive.d.ts +32 -0
  10. package/dist/hyperdrive.js +53 -0
  11. package/dist/index.d.ts +3 -0
  12. package/dist/index.js +34 -0
  13. package/dist/log.d.ts +72 -0
  14. package/dist/log.js +133 -0
  15. package/dist/migrate.d.ts +10 -0
  16. package/dist/migrate.js +129 -0
  17. package/dist/migrations/postgres/0000_baseline.sql +502 -0
  18. package/dist/migrations/postgres/meta/0000_snapshot.json +1939 -0
  19. package/dist/migrations/postgres/meta/_journal.json +13 -0
  20. package/dist/migrations/sqlite/0000_baseline.sql +293 -0
  21. package/dist/migrations/sqlite/meta/0000_snapshot.json +1811 -0
  22. package/dist/migrations/sqlite/meta/_journal.json +13 -0
  23. package/dist/portable.d.ts +33 -0
  24. package/dist/portable.js +12 -0
  25. package/dist/relations-DqgxtlKu.js +58 -0
  26. package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
  27. package/dist/schema-Cd4WOnB5.d.ts +2503 -0
  28. package/dist/schema-sqlite.d.ts +2587 -0
  29. package/dist/schema-sqlite.js +401 -0
  30. package/dist/schema-version.d.ts +11 -0
  31. package/dist/schema-version.js +16 -0
  32. package/dist/schema.d.ts +2 -0
  33. package/dist/schema.js +448 -0
  34. package/package.json +91 -5
  35. package/src/baseline/postgres.sql +238 -0
  36. package/src/baseline/sqlite.sql +27 -0
  37. package/src/baseline.ts +104 -0
  38. package/src/commits.ts +47 -0
  39. package/src/connect.ts +168 -0
  40. package/src/database.ts +63 -0
  41. package/src/dialect.ts +174 -0
  42. package/src/drizzle.postgres.config.ts +7 -0
  43. package/src/drizzle.sqlite.config.ts +7 -0
  44. package/src/hyperdrive.ts +62 -0
  45. package/src/log.ts +177 -0
  46. package/src/migrate-main.ts +8 -0
  47. package/src/migrate.ts +206 -0
  48. package/src/migrations/postgres/0000_baseline.sql +502 -0
  49. package/src/migrations/postgres/meta/0000_snapshot.json +1939 -0
  50. package/src/migrations/postgres/meta/_journal.json +13 -0
  51. package/src/migrations/sqlite/0000_baseline.sql +293 -0
  52. package/src/migrations/sqlite/meta/0000_snapshot.json +1811 -0
  53. package/src/migrations/sqlite/meta/_journal.json +13 -0
  54. package/src/portable.ts +54 -0
  55. package/src/relations.ts +53 -0
  56. package/src/schema-version.ts +13 -0
  57. package/src/schema.sqlite.ts +511 -0
  58. package/src/schema.ts +548 -0
  59. package/src/verify-runtime.ts +120 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Erwin Kuhn
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,5 +1,15 @@
1
1
  # @coffre/db
2
2
 
3
- A placeholder that holds the name. The first release of
4
- [coffre](https://github.com/erwinkn/coffre), a self-hosted secrets manager,
5
- replaces it, published from GitHub Actions with provenance.
3
+ coffre's database: the Drizzle schemas for Postgres and for SQLite, which
4
+ coffre uses in tests and local development, their migrations and the
5
+ migrator, the helpers for what the two engines do differently, and the
6
+ connections, Hyperdrive's included.
7
+
8
+ A deployment does not import it. `@coffre/server` runs its migrations with
9
+ `coffre-server migrate`. The server and vault open the same database with
10
+ separate logins, `coffre_runtime` and `coffre_vault_runtime`. The migrations
11
+ grant each its own rights; only the owner runs migrations.
12
+
13
+ Part of [coffre](https://github.com/erwinkn/coffre), a secrets manager you
14
+ deploy as a small project of your own. Its eight `@coffre/*` packages are
15
+ released together, at one version. MIT licensed.
@@ -0,0 +1,30 @@
1
+ //#region src/commits.ts
2
+ const pending = /* @__PURE__ */ new WeakMap();
3
+ /**
4
+ * Run `then` once `tx` has committed, all the way out. Returns false, and
5
+ * never runs it, for a transaction this package did not open.
6
+ */
7
+ function afterCommit(tx, then) {
8
+ const list = pending.get(tx);
9
+ if (list === void 0) return false;
10
+ list.push(then);
11
+ return true;
12
+ }
13
+ /** `db`, its transactions tracked for `afterCommit`. */
14
+ function trackCommits(db, outer = null) {
15
+ const run = db.transaction.bind(db);
16
+ const transaction = async (work, ...rest) => {
17
+ const list = [];
18
+ const result = await run((tx) => {
19
+ pending.set(tx, list);
20
+ return work(trackCommits(tx, list));
21
+ }, ...rest);
22
+ if (outer !== null) outer.push(...list);
23
+ else for (const then of list) then();
24
+ return result;
25
+ };
26
+ db.transaction = transaction;
27
+ return db;
28
+ }
29
+ //#endregion
30
+ export { trackCommits as n, afterCommit as t };
@@ -0,0 +1,21 @@
1
+ import { t as Database } from "./database-CWpo5aF0.js";
2
+ import { Engine } from "./dialect.js";
3
+ //#region src/connect.d.ts
4
+ /**
5
+ * Open a database from its URL, on a Node server:
6
+ *
7
+ * postgres://… or postgresql://… node-postgres
8
+ * file:… or libsql:… @libsql/client (SQLite)
9
+ *
10
+ * The SQLite driver loads only when asked for. The Worker does not
11
+ * come through here; it builds its Postgres database from the Hyperdrive
12
+ * pool with createDatabase.
13
+ */
14
+ export declare function openDatabase(url: string): Promise<OpenDatabase>;
15
+ export type OpenDatabase = {
16
+ engine: Engine;
17
+ db: Database;
18
+ close: () => Promise<void>;
19
+ };
20
+ export declare function engineOfUrl(url: string): Engine;
21
+ //#endregion
@@ -0,0 +1,155 @@
1
+ import { createDatabase } from "./index.js";
2
+ import { asPostgresDatabase } from "./portable.js";
3
+ import pg from "pg";
4
+ import { resolve } from "node:path";
5
+ //#region src/connect.ts
6
+ /**
7
+ * Open a database from its URL, on a Node server:
8
+ *
9
+ * postgres://… or postgresql://… node-postgres
10
+ * file:… or libsql:… @libsql/client (SQLite)
11
+ *
12
+ * The SQLite driver loads only when asked for. The Worker does not
13
+ * come through here; it builds its Postgres database from the Hyperdrive
14
+ * pool with createDatabase.
15
+ */
16
+ async function openDatabase(url) {
17
+ switch (engineOfUrl(url)) {
18
+ case "postgres": {
19
+ const pool = new pg.Pool({ connectionString: url });
20
+ return {
21
+ engine: "postgres",
22
+ db: createDatabase(pool),
23
+ close: () => pool.end()
24
+ };
25
+ }
26
+ case "sqlite": {
27
+ const [{ createClient }, { drizzle }, schema] = await Promise.all([
28
+ import("@libsql/client"),
29
+ import("drizzle-orm/libsql"),
30
+ import("./schema-sqlite.js")
31
+ ]);
32
+ const client = createClient({
33
+ url,
34
+ timeout: 5e3,
35
+ intMode: "bigint"
36
+ });
37
+ if (url.startsWith("file:")) await client.execute("PRAGMA journal_mode = WAL");
38
+ const db = drizzle({
39
+ client: oneTransactionAtATime(client, writeQueueFor(url)),
40
+ schema
41
+ });
42
+ return {
43
+ engine: "sqlite",
44
+ db: asPostgresDatabase(db),
45
+ close: async () => client.close()
46
+ };
47
+ }
48
+ }
49
+ }
50
+ function engineOfUrl(url) {
51
+ const scheme = url.slice(0, url.indexOf(":"));
52
+ if (scheme === "postgres" || scheme === "postgresql") return "postgres";
53
+ if (scheme === "mysql") throw new Error("coffre supports Postgres; MySQL support was removed");
54
+ if (scheme === "file" || scheme === "libsql") return "sqlite";
55
+ throw new Error(`unsupported database URL scheme: ${scheme}:`);
56
+ }
57
+ /**
58
+ * SQLite lets one connection write at a time and fails the others at once
59
+ * with SQLITE_BUSY rather than queueing them; 24 concurrent audit appends were
60
+ * enough to see it. So transactions queue here instead, in the order they
61
+ * asked, one open at a time per database file, however many clients or
62
+ * connections reach it. Each begins IMMEDIATE (libsql's default), taking the
63
+ * write lock with its first statement, which is what makes a read inside it
64
+ * as good as a locked one (see forUpdate).
65
+ */
66
+ var WriteQueue = class {
67
+ #tail = Promise.resolve();
68
+ /** Wait for our turn; the returned function ends it. */
69
+ acquire() {
70
+ let release;
71
+ const turn = new Promise((done) => release = done);
72
+ const ready = this.#tail.then(() => release);
73
+ this.#tail = this.#tail.then(() => turn);
74
+ return ready;
75
+ }
76
+ };
77
+ const queues = /* @__PURE__ */ new Map();
78
+ function writeQueueFor(url) {
79
+ const key = url.startsWith("file:") ? resolve(url.replace(/^file:(\/\/)?/, "").split("?")[0]) : url;
80
+ let queue = queues.get(key);
81
+ if (queue === void 0) queues.set(key, queue = new WriteQueue());
82
+ return queue;
83
+ }
84
+ function oneTransactionAtATime(client, queue) {
85
+ return new Proxy(client, { get(target, key) {
86
+ if (key === "execute") return async (...args) => numbers(await target.execute(...args));
87
+ if (key === "transaction") return async (mode) => {
88
+ const release = await queue.acquire();
89
+ try {
90
+ return settling(await target.transaction(mode), release);
91
+ } catch (error) {
92
+ release();
93
+ throw error;
94
+ }
95
+ };
96
+ if (key === "batch" || key === "migrate" || key === "executeMultiple") return async (...args) => {
97
+ const release = await queue.acquire();
98
+ try {
99
+ return (await target[key](...args))?.map(numbers);
100
+ } finally {
101
+ release();
102
+ }
103
+ };
104
+ const value = Reflect.get(target, key, target);
105
+ return typeof value === "function" ? value.bind(target) : value;
106
+ } });
107
+ }
108
+ /** Ends the transaction's turn when it commits, rolls back or closes. */
109
+ function settling(tx, release) {
110
+ let settled = false;
111
+ const settle = () => {
112
+ if (settled) return;
113
+ settled = true;
114
+ release();
115
+ };
116
+ const { commit, rollback, close, execute, batch } = tx;
117
+ tx.execute = async (...args) => numbers(await execute.apply(tx, args));
118
+ tx.batch = async (...args) => (await batch.apply(tx, args)).map(numbers);
119
+ tx.commit = async () => {
120
+ try {
121
+ return await commit.call(tx);
122
+ } finally {
123
+ settle();
124
+ }
125
+ };
126
+ tx.rollback = async () => {
127
+ try {
128
+ return await rollback.call(tx);
129
+ } finally {
130
+ settle();
131
+ }
132
+ };
133
+ tx.close = () => {
134
+ try {
135
+ close.call(tx);
136
+ } finally {
137
+ settle();
138
+ }
139
+ };
140
+ return tx;
141
+ }
142
+ /** Keep large counters exact; Drizzle's booleans and dates still need safe numbers. */
143
+ function numbers(result) {
144
+ result.rows = result.rows.map((row) => {
145
+ const properties = Object.getOwnPropertyDescriptors(row);
146
+ for (const property of Object.values(properties)) {
147
+ const value = property.value;
148
+ if (typeof value === "bigint" && value >= BigInt(Number.MIN_SAFE_INTEGER) && value <= BigInt(Number.MAX_SAFE_INTEGER)) property.value = Number(value);
149
+ }
150
+ return Object.create(Object.getPrototypeOf(row), properties);
151
+ });
152
+ return result;
153
+ }
154
+ //#endregion
155
+ export { engineOfUrl, openDatabase };
@@ -0,0 +1,39 @@
1
+ import { l as schema_d_exports } from "./schema-Cd4WOnB5.js";
2
+ import { Table } from "drizzle-orm";
3
+ import { NodePgDatabase } from "drizzle-orm/node-postgres";
4
+ import pg from "pg";
5
+ //#region src/database.d.ts
6
+ /**
7
+ * A database, typed as Postgres whichever it is; see portable.ts for why
8
+ * that holds. connect.ts opens one of each from a URL.
9
+ */
10
+ type Database = NodePgDatabase<typeof schema_d_exports>;
11
+ type Transaction = Parameters<Parameters<Database['transaction']>[0]>[0];
12
+ /** Anything a read can run on: the database, or an open transaction. */
13
+ type Queryable = Database | Transaction;
14
+ /**
15
+ * What Drizzle needs from a connection pool. A `pg.Pool` fits, and so does the
16
+ * per-invocation Hyperdrive pool, whose class name ends in `Pool` for the
17
+ * same reason: that is how Drizzle knows to check out a client for a
18
+ * transaction rather than run BEGIN on a shared connection.
19
+ */
20
+ type PoolLike = pg.Pool | {
21
+ query: pg.Pool['query'];
22
+ connect: () => Promise<Pick<pg.PoolClient, 'query'> & {
23
+ release: () => void;
24
+ }>;
25
+ };
26
+ declare function createDatabase(pool: PoolLike): Database;
27
+ /**
28
+ * The tables of the database's own dialect: the Postgres ones on Postgres,
29
+ * the SQLite ones on SQLite. Every query builds on these.
30
+ */
31
+ declare function tablesOf(db: Queryable): typeof schema_d_exports;
32
+ /**
33
+ * The database's own twin of `table`, which may be another dialect's: the
34
+ * server names tables by importing schema.ts, and the database decides
35
+ * which one that means.
36
+ */
37
+ declare function own<T extends Table>(db: Queryable, table: T): T;
38
+ //#endregion
39
+ export { createDatabase as a, Transaction as i, PoolLike as n, own as o, Queryable as r, tablesOf as s, Database as t };
@@ -0,0 +1,88 @@
1
+ import { r as Queryable } from "./database-CWpo5aF0.js";
2
+ import { SQL } from "drizzle-orm";
3
+ import { PgTable } from "drizzle-orm/pg-core";
4
+ //#region src/dialect.d.ts
5
+ /**
6
+ * Differences between Postgres and the SQLite used for tests and local dev.
7
+ * Queries stay in the server's queries.ts; it never asks which database it has.
8
+ *
9
+ * Postgres SQLite
10
+ * isolation READ COMMITTED one writer at a time
11
+ * lock a row SELECT … FOR UPDATE one writer at a time
12
+ * rows changed rowCount rowsAffected
13
+ * duplicate key SQLSTATE 23505 SQLITE_CONSTRAINT_UNIQUE
14
+ * clock CURRENT_TIMESTAMP strftime(…, 'now')
15
+ * clock, in ms clock_timestamp() unixepoch('subsec')
16
+ * a condition read true / false 1 / 0
17
+ *
18
+ * Nothing here imports SQLite code, so the Worker bundles none.
19
+ */
20
+ export type Table = PgTable;
21
+ export type Engine = 'postgres' | 'sqlite';
22
+ /** Which database this is, from the Drizzle dialect it was opened with. */
23
+ export declare function engineOf(db: Queryable): Engine;
24
+ /**
25
+ * Lock the rows a select reads until the transaction ends. SQLite has no row
26
+ * locks and needs none: connect.ts runs one transaction at a time, and each
27
+ * holds the database's write lock from its first statement.
28
+ *
29
+ * `no key update` is for rows others reference by foreign key: it keeps out
30
+ * every other writer of the row, but not an insert that points at it.
31
+ */
32
+ export declare function forUpdate<Query extends {
33
+ for: (strength: 'update' | 'no key update') => unknown;
34
+ }>(db: Queryable, query: Query, strength?: 'update' | 'no key update'): ReturnType<Query['for']>;
35
+ /** Insert the rows whose unique keys are free; returns how many that was. */
36
+ export declare function insertIfAbsent(db: Queryable, table: Table, rows: object[]): Promise<number>;
37
+ /**
38
+ * Insert the rows, or where one repeats the unique key `target`, overwrite
39
+ * `columns` of the existing row with the values it was about to insert.
40
+ */
41
+ export declare function upsert<T extends Table>(db: Queryable, table: T, rows: object[], target: (keyof T['$inferSelect'])[], columns: (keyof T['$inferSelect'])[]): Promise<void>;
42
+ /** A condition read as a boolean: Postgres returns true or false, SQLite 1 or 0. */
43
+ export declare function truth(condition: SQL): SQL<boolean>;
44
+ /** How many rows an insert, update or delete touched. */
45
+ export declare function changedRows(result: unknown): number;
46
+ /** Whether an insert or update failed on a unique constraint. */
47
+ export declare function isUniqueViolation(error: unknown): boolean;
48
+ /**
49
+ * A consistent read of several statements that cannot write, such as
50
+ * verifying the chain. SQLite ignores it: its transactions already see one
51
+ * snapshot, and connect.ts runs them one at a time.
52
+ */
53
+ export declare const SNAPSHOT: {
54
+ readonly isolationLevel: "repeatable read";
55
+ readonly accessMode: "read only";
56
+ };
57
+ /**
58
+ * The database clock as text, for canonicalTimestamp. Entries from several
59
+ * servers order by it, so it is never an application server's.
60
+ */
61
+ export declare function clock(db: Queryable): SQL<string>;
62
+ /**
63
+ * The database clock as milliseconds since the epoch, as the statement runs:
64
+ * Postgres's `clock_timestamp()`, not `CURRENT_TIMESTAMP`, which is the
65
+ * transaction's start and would date an entry that waited for the log's lock
66
+ * before one written while it waited. Read it in a statement after the lock.
67
+ */
68
+ export declare function clockMillis(db: Queryable): SQL<number>;
69
+ /**
70
+ * The migrator's own ledger, read by readiness to tell a database that is
71
+ * up but not yet migrated from one that is ready. Drizzle keeps it in a
72
+ * `drizzle` schema on Postgres, and in a plain table elsewhere.
73
+ */
74
+ export declare function migrationLedger(db: Queryable): SQL;
75
+ /**
76
+ * The one rendering of a timestamp read as text: UTC, microseconds, `Z`.
77
+ *
78
+ * The audit chain covers occurred_at as text, so reading a row back has to
79
+ * reproduce it byte for byte. Databases hand timestamps back in their own
80
+ * shapes (`2026-09-27 12:34:56.1+02` from Postgres, ISO text from SQLite),
81
+ * and a Date would drop the microseconds, so every value goes through here
82
+ * on the way in and out:
83
+ *
84
+ * 2026-09-27 12:34:56.1+02 -> 2026-09-27T10:34:56.100000Z
85
+ * 2026-09-27T10:34:56.1Z -> 2026-09-27T10:34:56.100000Z
86
+ */
87
+ export declare function canonicalTimestamp(raw: string): string;
88
+ //#endregion
@@ -0,0 +1,128 @@
1
+ import { entityKind, getTableColumns, sql } from "drizzle-orm";
2
+ //#region src/dialect.ts
3
+ /** Which database this is, from the Drizzle dialect it was opened with. */
4
+ function engineOf(db) {
5
+ const kind = db.dialect.constructor[entityKind];
6
+ if (kind.startsWith("Pg")) return "postgres";
7
+ if (kind.startsWith("SQLite")) return "sqlite";
8
+ throw new Error(`unsupported database: ${kind}`);
9
+ }
10
+ /**
11
+ * Lock the rows a select reads until the transaction ends. SQLite has no row
12
+ * locks and needs none: connect.ts runs one transaction at a time, and each
13
+ * holds the database's write lock from its first statement.
14
+ *
15
+ * `no key update` is for rows others reference by foreign key: it keeps out
16
+ * every other writer of the row, but not an insert that points at it.
17
+ */
18
+ function forUpdate(db, query, strength = "update") {
19
+ if (engineOf(db) === "sqlite") return query;
20
+ return query.for(strength);
21
+ }
22
+ /** Insert the rows whose unique keys are free; returns how many that was. */
23
+ async function insertIfAbsent(db, table, rows) {
24
+ return changedRows(await db.insert(table).values(rows).onConflictDoNothing());
25
+ }
26
+ /**
27
+ * Insert the rows, or where one repeats the unique key `target`, overwrite
28
+ * `columns` of the existing row with the values it was about to insert.
29
+ */
30
+ async function upsert(db, table, rows, target, columns) {
31
+ const all = getTableColumns(table);
32
+ const incoming = (name) => sql`excluded.${sql.identifier(all[name].name)}`;
33
+ const set = Object.fromEntries(columns.map((name) => [name, incoming(name)]));
34
+ await db.insert(table).values(rows).onConflictDoUpdate({
35
+ target: target.map((name) => all[name]),
36
+ set
37
+ });
38
+ }
39
+ /** A condition read as a boolean: Postgres returns true or false, SQLite 1 or 0. */
40
+ function truth(condition) {
41
+ return condition.mapWith((value) => Number(value) === 1);
42
+ }
43
+ /** How many rows an insert, update or delete touched. */
44
+ function changedRows(result) {
45
+ const header = result;
46
+ return header.rowCount ?? header.rowsAffected ?? 0;
47
+ }
48
+ const DUPLICATE_KEY = /* @__PURE__ */ new Set([
49
+ "23505",
50
+ "SQLITE_CONSTRAINT_UNIQUE",
51
+ "SQLITE_CONSTRAINT_PRIMARYKEY"
52
+ ]);
53
+ /** Whether an insert or update failed on a unique constraint. */
54
+ function isUniqueViolation(error) {
55
+ for (let cause = error; typeof cause === "object" && cause !== null;) {
56
+ const { code, extendedCode } = cause;
57
+ if (DUPLICATE_KEY.has(code) || DUPLICATE_KEY.has(extendedCode)) return true;
58
+ cause = cause.cause;
59
+ }
60
+ return false;
61
+ }
62
+ /**
63
+ * A consistent read of several statements that cannot write, such as
64
+ * verifying the chain. SQLite ignores it: its transactions already see one
65
+ * snapshot, and connect.ts runs them one at a time.
66
+ */
67
+ const SNAPSHOT = {
68
+ isolationLevel: "repeatable read",
69
+ accessMode: "read only"
70
+ };
71
+ /**
72
+ * The database clock as text, for canonicalTimestamp. Entries from several
73
+ * servers order by it, so it is never an application server's.
74
+ */
75
+ function clock(db) {
76
+ switch (engineOf(db)) {
77
+ case "postgres": return sql`CURRENT_TIMESTAMP`;
78
+ case "sqlite": return sql`strftime('%Y-%m-%dT%H:%M:%fZ', 'now')`;
79
+ }
80
+ }
81
+ /**
82
+ * The database clock as milliseconds since the epoch, as the statement runs:
83
+ * Postgres's `clock_timestamp()`, not `CURRENT_TIMESTAMP`, which is the
84
+ * transaction's start and would date an entry that waited for the log's lock
85
+ * before one written while it waited. Read it in a statement after the lock.
86
+ */
87
+ function clockMillis(db) {
88
+ switch (engineOf(db)) {
89
+ case "postgres": return sql`(extract(epoch from clock_timestamp()) * 1000)::bigint`.mapWith(Number);
90
+ case "sqlite": return sql`CAST(unixepoch('subsec') * 1000 AS INTEGER)`.mapWith(Number);
91
+ }
92
+ }
93
+ /**
94
+ * The migrator's own ledger, read by readiness to tell a database that is
95
+ * up but not yet migrated from one that is ready. Drizzle keeps it in a
96
+ * `drizzle` schema on Postgres, and in a plain table elsewhere.
97
+ */
98
+ function migrationLedger(db) {
99
+ const table = sql.identifier("__drizzle_migrations");
100
+ return engineOf(db) === "postgres" ? sql`${sql.identifier("drizzle")}.${table}` : sql`${table}`;
101
+ }
102
+ const TIMESTAMP = /^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,6}))?(Z|[+-]\d{2}(?::?\d{2})?)?$/;
103
+ /**
104
+ * The one rendering of a timestamp read as text: UTC, microseconds, `Z`.
105
+ *
106
+ * The audit chain covers occurred_at as text, so reading a row back has to
107
+ * reproduce it byte for byte. Databases hand timestamps back in their own
108
+ * shapes (`2026-09-27 12:34:56.1+02` from Postgres, ISO text from SQLite),
109
+ * and a Date would drop the microseconds, so every value goes through here
110
+ * on the way in and out:
111
+ *
112
+ * 2026-09-27 12:34:56.1+02 -> 2026-09-27T10:34:56.100000Z
113
+ * 2026-09-27T10:34:56.1Z -> 2026-09-27T10:34:56.100000Z
114
+ */
115
+ function canonicalTimestamp(raw) {
116
+ const match = TIMESTAMP.exec(raw);
117
+ if (match === null) throw new Error(`unexpected timestamp from the database: ${raw}`);
118
+ const [, year, month, day, hour, minute, second, fraction = "", zone = "Z"] = match;
119
+ let offsetMinutes = 0;
120
+ if (zone !== "Z") {
121
+ const sign = zone.startsWith("-") ? -1 : 1;
122
+ const digits = zone.slice(1).replace(":", "");
123
+ offsetMinutes = sign * (Number(digits.slice(0, 2)) * 60 + Number(digits.slice(2) || "0"));
124
+ }
125
+ return `${(/* @__PURE__ */ new Date(Date.UTC(+year, +month - 1, +day, +hour, +minute, +second) - offsetMinutes * 6e4)).toISOString().slice(0, 19)}.${fraction.padEnd(6, "0")}Z`;
126
+ }
127
+ //#endregion
128
+ export { SNAPSHOT, canonicalTimestamp, changedRows, clock, clockMillis, engineOf, forUpdate, insertIfAbsent, isUniqueViolation, migrationLedger, truth, upsert };
@@ -0,0 +1,32 @@
1
+ import { n as PoolLike } from "./database-CWpo5aF0.js";
2
+ import pg from "pg";
3
+ //#region src/hyperdrive.d.ts
4
+ /** Postgres through a Hyperdrive binding: `postgres(env.HYPERDRIVE)`. */
5
+ export type PostgresDatabase = {
6
+ readonly engine: 'postgres';
7
+ readonly hyperdrive: {
8
+ readonly connectionString: string;
9
+ };
10
+ };
11
+ /** Postgres through Hyperdrive, the one database a Worker runs on, the app's and the vault's. */
12
+ export declare function postgres(hyperdrive: {
13
+ readonly connectionString: string;
14
+ }): PostgresDatabase;
15
+ /**
16
+ * Request-scoped node-postgres adapter for Hyperdrive.
17
+ *
18
+ * Hyperdrive owns the durable origin pool. The Worker creates edge clients
19
+ * only inside one invocation, which prevents I/O objects from leaking across
20
+ * Worker request contexts while keeping the Pool shape Drizzle expects. The
21
+ * class name must end in `Pool`: that is how Drizzle knows to check out a
22
+ * client for each transaction.
23
+ */
24
+ export declare class HyperdrivePool implements Exclude<PoolLike, pg.Pool> {
25
+ #private;
26
+ constructor(connectionString: string);
27
+ readonly query: pg.Pool['query'];
28
+ connect(): Promise<Pick<pg.PoolClient, 'query'> & {
29
+ release: () => void;
30
+ }>;
31
+ }
32
+ //#endregion
@@ -0,0 +1,53 @@
1
+ import pg from "pg";
2
+ //#region src/hyperdrive.ts
3
+ /** Postgres through Hyperdrive, the one database a Worker runs on, the app's and the vault's. */
4
+ function postgres(hyperdrive) {
5
+ if (typeof hyperdrive?.connectionString !== "string") throw new Error("postgres() takes the Hyperdrive binding, e.g. postgres(env.HYPERDRIVE)");
6
+ return {
7
+ engine: "postgres",
8
+ hyperdrive
9
+ };
10
+ }
11
+ /**
12
+ * Request-scoped node-postgres adapter for Hyperdrive.
13
+ *
14
+ * Hyperdrive owns the durable origin pool. The Worker creates edge clients
15
+ * only inside one invocation, which prevents I/O objects from leaking across
16
+ * Worker request contexts while keeping the Pool shape Drizzle expects. The
17
+ * class name must end in `Pool`: that is how Drizzle knows to check out a
18
+ * client for each transaction.
19
+ */
20
+ var HyperdrivePool = class {
21
+ #connectionString;
22
+ constructor(connectionString) {
23
+ if (connectionString.trim().length === 0) throw new Error("Hyperdrive returned an empty connection string");
24
+ this.#connectionString = connectionString;
25
+ }
26
+ query = (async (...args) => {
27
+ const client = await this.#connectClient();
28
+ try {
29
+ return await client.query(...args);
30
+ } finally {
31
+ await client.end();
32
+ }
33
+ });
34
+ async connect() {
35
+ const client = await this.#connectClient();
36
+ let released = false;
37
+ return {
38
+ query: client.query.bind(client),
39
+ release: () => {
40
+ if (released) return;
41
+ released = true;
42
+ client.end().catch(() => {});
43
+ }
44
+ };
45
+ }
46
+ async #connectClient() {
47
+ const client = new pg.Client({ connectionString: this.#connectionString });
48
+ await client.connect();
49
+ return client;
50
+ }
51
+ };
52
+ //#endregion
53
+ export { HyperdrivePool, postgres };
@@ -0,0 +1,3 @@
1
+ import { l as schema_d_exports } from "./schema-Cd4WOnB5.js";
2
+ import { a as createDatabase, i as Transaction, n as PoolLike, o as own, r as Queryable, s as tablesOf, t as Database } from "./database-CWpo5aF0.js";
3
+ export { Database, PoolLike, Queryable, Transaction, createDatabase, own, schema_d_exports as schema, tablesOf };
package/dist/index.js ADDED
@@ -0,0 +1,34 @@
1
+ import { n as trackCommits } from "./commits-BuW-NL3n.js";
2
+ import { t as schema_exports } from "./schema.js";
3
+ import { Table, getTableName, is } from "drizzle-orm";
4
+ import { drizzle } from "drizzle-orm/node-postgres";
5
+ //#region src/database.ts
6
+ function createDatabase(pool) {
7
+ return trackCommits(drizzle(pool, { schema: schema_exports }));
8
+ }
9
+ /**
10
+ * The tables of the database's own dialect: the Postgres ones on Postgres,
11
+ * the SQLite ones on SQLite. Every query builds on these.
12
+ */
13
+ function tablesOf(db) {
14
+ return db._.fullSchema;
15
+ }
16
+ const byName = /* @__PURE__ */ new WeakMap();
17
+ /**
18
+ * The database's own twin of `table`, which may be another dialect's: the
19
+ * server names tables by importing schema.ts, and the database decides
20
+ * which one that means.
21
+ */
22
+ function own(db, table) {
23
+ const tables = tablesOf(db);
24
+ let named = byName.get(tables);
25
+ if (named === void 0) {
26
+ named = new Map(Object.values(tables).filter((value) => is(value, Table)).map((value) => [getTableName(value), value]));
27
+ byName.set(tables, named);
28
+ }
29
+ const found = named.get(getTableName(table));
30
+ if (found === void 0) throw new Error(`no table ${getTableName(table)} in this database`);
31
+ return found;
32
+ }
33
+ //#endregion
34
+ export { createDatabase, own, schema_exports as schema, tablesOf };