@coffre/db 0.1.15 → 0.1.17
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/README.md +3 -2
- package/dist/connect.d.ts +1 -1
- package/dist/{database-CNMfHZwF.d.ts → database-Cq_52uUo.d.ts} +1 -1
- package/dist/dialect.d.ts +1 -1
- package/dist/hyperdrive.d.ts +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +8 -4
- package/dist/log.d.ts +1 -1
- package/dist/migrate.d.ts +16 -0
- package/dist/migrate.js +23 -4
- package/dist/migrations/postgres/0002_service_bindings.sql +28 -0
- package/dist/migrations/postgres/0003_exchanges.sql +10 -0
- package/dist/migrations/postgres/0004_live_indexes.sql +3 -0
- package/dist/migrations/postgres/meta/0002_snapshot.json +1870 -0
- package/dist/migrations/postgres/meta/0003_snapshot.json +1917 -0
- package/dist/migrations/postgres/meta/0004_snapshot.json +2006 -0
- package/dist/migrations/postgres/meta/_journal.json +21 -0
- package/dist/migrations/sqlite/0002_service_bindings.sql +23 -0
- package/dist/migrations/sqlite/0003_exchanges.sql +7 -0
- package/dist/migrations/sqlite/0004_live_indexes.sql +3 -0
- package/dist/migrations/sqlite/meta/0002_snapshot.json +1736 -0
- package/dist/migrations/sqlite/meta/0003_snapshot.json +1781 -0
- package/dist/migrations/sqlite/meta/0004_snapshot.json +1811 -0
- package/dist/migrations/sqlite/meta/_journal.json +21 -0
- package/dist/portable.d.ts +2 -2
- package/dist/{schema-AmvM6w0D.d.ts → schema-Wt-p8AOh.d.ts} +305 -2
- package/dist/schema-sqlite.d.ts +305 -1
- package/dist/schema-sqlite.js +38 -3
- package/dist/schema-version.d.ts +1 -1
- package/dist/schema-version.js +74 -26
- package/dist/schema.d.ts +2 -2
- package/dist/schema.js +53 -3
- package/package.json +2 -2
- package/src/database.ts +15 -5
- package/src/migrate.ts +29 -9
- package/src/migrations/postgres/0002_service_bindings.sql +28 -0
- package/src/migrations/postgres/0003_exchanges.sql +10 -0
- package/src/migrations/postgres/0004_live_indexes.sql +3 -0
- package/src/migrations/postgres/meta/0002_snapshot.json +1870 -0
- package/src/migrations/postgres/meta/0003_snapshot.json +1917 -0
- package/src/migrations/postgres/meta/0004_snapshot.json +2006 -0
- package/src/migrations/postgres/meta/_journal.json +21 -0
- package/src/migrations/sqlite/0002_service_bindings.sql +23 -0
- package/src/migrations/sqlite/0003_exchanges.sql +7 -0
- package/src/migrations/sqlite/0004_live_indexes.sql +3 -0
- package/src/migrations/sqlite/meta/0002_snapshot.json +1736 -0
- package/src/migrations/sqlite/meta/0003_snapshot.json +1781 -0
- package/src/migrations/sqlite/meta/0004_snapshot.json +1811 -0
- package/src/migrations/sqlite/meta/_journal.json +21 -0
- package/src/schema.sqlite.ts +61 -0
- package/src/schema.ts +74 -0
package/README.md
CHANGED
|
@@ -5,8 +5,9 @@ coffre uses in tests and local development, their migrations and the
|
|
|
5
5
|
migrator, the helpers for what the two engines do differently, and the
|
|
6
6
|
connections, Hyperdrive's included.
|
|
7
7
|
|
|
8
|
-
A deployment does not import it.
|
|
9
|
-
`coffre
|
|
8
|
+
A deployment does not import it. Its own CLI applies the migrations,
|
|
9
|
+
`pnpm exec coffre migrate`, before the deploy; `coffre-server migrate` does
|
|
10
|
+
the same for local SQLite and tests. The server and vault open the same database with
|
|
10
11
|
separate logins, `coffre_runtime` and `coffre_vault_runtime`. The migrations
|
|
11
12
|
grant each its own rights; only the owner runs migrations. The owner needs
|
|
12
13
|
`CREATEROLE` and `CREATEDB`, and `ADMIN OPTION` on existing group roles.
|
package/dist/connect.d.ts
CHANGED
package/dist/dialect.d.ts
CHANGED
package/dist/hyperdrive.d.ts
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a as Transaction, c as tablesOf, i as Queryable, n as PoolLike, o as createDatabase, r as QueryOutsideTransaction, s as own, t as Database } from "./database-
|
|
1
|
+
import { u as schema_d_exports } from "./schema-Wt-p8AOh.js";
|
|
2
|
+
import { a as Transaction, c as tablesOf, i as Queryable, n as PoolLike, o as createDatabase, r as QueryOutsideTransaction, s as own, t as Database } from "./database-Cq_52uUo.js";
|
|
3
3
|
export { Database, PoolLike, QueryOutsideTransaction, Queryable, Transaction, createDatabase, own, schema_d_exports as schema, tablesOf };
|
package/dist/index.js
CHANGED
|
@@ -22,7 +22,7 @@ const inside = new AsyncLocalStorage();
|
|
|
22
22
|
/**
|
|
23
23
|
* `pool`, refusing a query or a transaction asked from inside a transaction
|
|
24
24
|
* it is already running. Work from any other flow, a concurrent request's,
|
|
25
|
-
* or what the transaction left running
|
|
25
|
+
* or what the transaction left running once its work was done, goes on as before.
|
|
26
26
|
*/
|
|
27
27
|
function guarded(pool) {
|
|
28
28
|
return new Proxy(pool, { get(target, key) {
|
|
@@ -49,11 +49,15 @@ function createDatabase(pool) {
|
|
|
49
49
|
open: true
|
|
50
50
|
};
|
|
51
51
|
try {
|
|
52
|
-
return await run((tx) => inside.run(open, () =>
|
|
52
|
+
return await run((tx) => inside.run(open, async () => {
|
|
53
|
+
try {
|
|
54
|
+
return await work(tx);
|
|
55
|
+
} finally {
|
|
56
|
+
open.open = false;
|
|
57
|
+
}
|
|
58
|
+
}), config);
|
|
53
59
|
} catch (error) {
|
|
54
60
|
throw brokenRule(error) ?? error;
|
|
55
|
-
} finally {
|
|
56
|
-
open.open = false;
|
|
57
61
|
}
|
|
58
62
|
});
|
|
59
63
|
return trackCommits(db);
|
package/dist/log.d.ts
CHANGED
package/dist/migrate.d.ts
CHANGED
|
@@ -8,6 +8,22 @@ export declare function restrictDatabase(client: Pick<PoolClient, 'query'>): Pro
|
|
|
8
8
|
* in the built package, where the build copies them.
|
|
9
9
|
*/
|
|
10
10
|
export declare function migrationsFolder(engine: Engine): string;
|
|
11
|
+
/**
|
|
12
|
+
* A database a newer coffre migrated: it has applied every migration this
|
|
13
|
+
* version knows, and more. The ledger keeps no names, so each one it does not
|
|
14
|
+
* know is named by when it was made and its hash.
|
|
15
|
+
*/
|
|
16
|
+
export declare class DatabaseAhead extends Error {
|
|
17
|
+
readonly known: string[];
|
|
18
|
+
readonly unknown: {
|
|
19
|
+
createdAt: string;
|
|
20
|
+
hash: string;
|
|
21
|
+
}[];
|
|
22
|
+
constructor(known: string[], unknown: {
|
|
23
|
+
createdAt: string;
|
|
24
|
+
hash: string;
|
|
25
|
+
}[]);
|
|
26
|
+
}
|
|
11
27
|
/**
|
|
12
28
|
* A migration run's plan: `applied` of the `total` migrations there before
|
|
13
29
|
* it, and the tags of the rest, which it applies.
|
package/dist/migrate.js
CHANGED
|
@@ -47,14 +47,33 @@ async function expectedMigrations(engine) {
|
|
|
47
47
|
for (let index = 1; index < expected.length; index += 1) if (BigInt(expected[index - 1].createdAt) >= BigInt(expected[index].createdAt)) throw new Error("Drizzle migration journal timestamps must be strictly increasing");
|
|
48
48
|
return expected;
|
|
49
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* A database a newer coffre migrated: it has applied every migration this
|
|
52
|
+
* version knows, and more. The ledger keeps no names, so each one it does not
|
|
53
|
+
* know is named by when it was made and its hash.
|
|
54
|
+
*/
|
|
55
|
+
var DatabaseAhead = class extends Error {
|
|
56
|
+
known;
|
|
57
|
+
unknown;
|
|
58
|
+
constructor(known, unknown) {
|
|
59
|
+
const made = unknown.map(({ createdAt, hash }) => `one made ${new Date(Number(createdAt)).toISOString().slice(0, 16).replace("T", " ")} UTC (${hash.slice(0, 12)})`);
|
|
60
|
+
super(`the database has applied ${unknown.length === 1 ? "a migration" : `${unknown.length} migrations`} this version does not know, after ${known.at(-1) ?? "none"}: ${made.join(", ")}. A newer coffre migrated it`);
|
|
61
|
+
this.name = "DatabaseAhead";
|
|
62
|
+
this.known = known;
|
|
63
|
+
this.unknown = unknown;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
50
66
|
function verifyHistory(expected, applied, requireComplete) {
|
|
51
67
|
const rows = applied ?? [];
|
|
52
|
-
|
|
53
|
-
if (requireComplete && rows.length !== expected.length) throw new Error(`migration history is incomplete: expected ${expected.length}, found ${rows.length}`);
|
|
54
|
-
rows.forEach((row, index) => {
|
|
68
|
+
rows.slice(0, expected.length).forEach((row, index) => {
|
|
55
69
|
const local = expected[index];
|
|
56
70
|
if (row.created_at !== local.createdAt || row.hash !== local.hash) throw new Error(`migration history diverged at ${local.tag}; applied migrations must never be edited`);
|
|
57
71
|
});
|
|
72
|
+
if (rows.length > expected.length) throw new DatabaseAhead(expected.map((entry) => entry.tag), rows.slice(expected.length).map((row) => ({
|
|
73
|
+
createdAt: row.created_at,
|
|
74
|
+
hash: row.hash
|
|
75
|
+
})));
|
|
76
|
+
if (requireComplete && rows.length !== expected.length) throw new Error(`migration history is incomplete: expected ${expected.length}, found ${rows.length}`);
|
|
58
77
|
}
|
|
59
78
|
async function postgresMigrator(url) {
|
|
60
79
|
const [{ default: pg }, { drizzle }, { migrate }] = await Promise.all([
|
|
@@ -166,4 +185,4 @@ async function migrateDatabase(url, onPlan) {
|
|
|
166
185
|
}
|
|
167
186
|
}
|
|
168
187
|
//#endregion
|
|
169
|
-
export { migrateDatabase, migrationStatus, migrationsFolder, restrictDatabase };
|
|
188
|
+
export { DatabaseAhead, migrateDatabase, migrationStatus, migrationsFolder, restrictDatabase };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
CREATE TABLE "service_bindings" (
|
|
2
|
+
"id" uuid PRIMARY KEY NOT NULL,
|
|
3
|
+
"auth_mac" "bytea" NOT NULL,
|
|
4
|
+
"principal" text NOT NULL,
|
|
5
|
+
"generation" integer NOT NULL,
|
|
6
|
+
"profile" text NOT NULL,
|
|
7
|
+
"issuer" text NOT NULL,
|
|
8
|
+
"jwks_uri" text NOT NULL,
|
|
9
|
+
"claims" text NOT NULL,
|
|
10
|
+
"label" text,
|
|
11
|
+
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
12
|
+
"created_by" text NOT NULL,
|
|
13
|
+
"last_used_at" timestamp with time zone,
|
|
14
|
+
"revoked_at" timestamp with time zone,
|
|
15
|
+
"revoked_by" text,
|
|
16
|
+
CONSTRAINT "service_bindings_principal_check" CHECK ("service_bindings"."principal" LIKE 'token:%'),
|
|
17
|
+
CONSTRAINT "service_bindings_auth_mac_check" CHECK (octet_length("service_bindings"."auth_mac") = 32),
|
|
18
|
+
CONSTRAINT "service_bindings_claims_check" CHECK ("service_bindings"."claims"::jsonb IS NOT NULL)
|
|
19
|
+
);
|
|
20
|
+
--> statement-breakpoint
|
|
21
|
+
ALTER TABLE "service_bindings" ADD CONSTRAINT "service_bindings_principal_fkey" FOREIGN KEY ("principal") REFERENCES "public"."vault_members"("principal") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
|
|
22
|
+
CREATE INDEX "service_bindings_principal_idx" ON "service_bindings" USING btree ("principal","issuer");--> statement-breakpoint
|
|
23
|
+
CREATE INDEX "audit_log_unbind_idx" ON "audit_log" USING btree ((("metadata")::jsonb ->> 'bindingId')) WHERE "audit_log"."author" = 'app' AND "audit_log"."action" = 'token.unbind' AND "audit_log"."decision" = 'allow';--> statement-breakpoint
|
|
24
|
+
-- The app reads and adds bindings, and changes only what the MAC allows to
|
|
25
|
+
-- change in place: its label, its last use, and its revocation (with the
|
|
26
|
+
-- MAC that covers it). A binding's policy is never updated, nor deleted.
|
|
27
|
+
GRANT SELECT, INSERT ON service_bindings TO coffre_app;--> statement-breakpoint
|
|
28
|
+
GRANT UPDATE (label, last_used_at, revoked_at, revoked_by, auth_mac) ON service_bindings TO coffre_app;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
CREATE TABLE "consumed_tokens" (
|
|
2
|
+
"hash" "bytea" PRIMARY KEY NOT NULL,
|
|
3
|
+
"consumed_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
4
|
+
CONSTRAINT "consumed_tokens_hash_check" CHECK (octet_length("consumed_tokens"."hash") = 32)
|
|
5
|
+
);
|
|
6
|
+
--> statement-breakpoint
|
|
7
|
+
CREATE INDEX "audit_log_exchange_idx" ON "audit_log" USING btree ((("metadata")::jsonb ->> 'credentialId')) WHERE "audit_log"."author" = 'app' AND "audit_log"."action" = 'token.exchange' AND "audit_log"."decision" = 'allow';--> statement-breakpoint
|
|
8
|
+
-- The app records each token it exchanges, and reads whether one was; it
|
|
9
|
+
-- never changes or deletes a record, so a token stays spent.
|
|
10
|
+
GRANT SELECT, INSERT ON consumed_tokens TO coffre_app;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
CREATE INDEX "credentials_live_idx" ON "credentials" USING btree ("principal","expires_at") WHERE "credentials"."revoked_at" IS NULL;--> statement-breakpoint
|
|
2
|
+
CREATE INDEX "credentials_issued_by_idx" ON "credentials" USING btree ("principal","created_by","created_at");--> statement-breakpoint
|
|
3
|
+
CREATE INDEX "service_bindings_live_idx" ON "service_bindings" USING btree ("principal","issuer","generation","created_at","id") WHERE "service_bindings"."revoked_at" IS NULL;
|