@coffre/db 0.6.1 → 0.7.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 (64) hide show
  1. package/dist/connect.d.ts +1 -1
  2. package/dist/{database-C63WjCrC.d.ts → database-BEnexh-3.d.ts} +1 -1
  3. package/dist/dialect.d.ts +1 -1
  4. package/dist/grants.d.ts +1 -1
  5. package/dist/hyperdrive.d.ts +1 -1
  6. package/dist/index.d.ts +2 -2
  7. package/dist/log.d.ts +22 -1
  8. package/dist/log.js +37 -2
  9. package/dist/migrations/postgres/0004_environment_parents.sql +5 -0
  10. package/dist/migrations/postgres/0005_personal_tokens.sql +5 -0
  11. package/dist/migrations/postgres/0006_uploaded_keys.sql +2 -0
  12. package/dist/migrations/postgres/0007_personal_configs.sql +4 -0
  13. package/dist/migrations/postgres/0008_recipes.sql +19 -0
  14. package/dist/migrations/postgres/meta/0004_snapshot.json +2913 -0
  15. package/dist/migrations/postgres/meta/0005_snapshot.json +2929 -0
  16. package/dist/migrations/postgres/meta/0006_snapshot.json +2935 -0
  17. package/dist/migrations/postgres/meta/0007_snapshot.json +2967 -0
  18. package/dist/migrations/postgres/meta/0008_snapshot.json +2985 -0
  19. package/dist/migrations/postgres/meta/_journal.json +35 -0
  20. package/dist/migrations/sqlite/0004_environment_parents.sql +3 -0
  21. package/dist/migrations/sqlite/0005_personal_tokens.sql +37 -0
  22. package/dist/migrations/sqlite/0006_uploaded_keys.sql +1 -0
  23. package/dist/migrations/sqlite/0007_personal_configs.sql +3 -0
  24. package/dist/migrations/sqlite/0008_recipes.sql +15 -0
  25. package/dist/migrations/sqlite/meta/0004_snapshot.json +2641 -0
  26. package/dist/migrations/sqlite/meta/0005_snapshot.json +2656 -0
  27. package/dist/migrations/sqlite/meta/0006_snapshot.json +2663 -0
  28. package/dist/migrations/sqlite/meta/0007_snapshot.json +2683 -0
  29. package/dist/migrations/sqlite/meta/0008_snapshot.json +2704 -0
  30. package/dist/migrations/sqlite/meta/_journal.json +35 -0
  31. package/dist/portable.d.ts +2 -2
  32. package/dist/{schema-a7PkFgea.d.ts → schema-BZRkBJQZ.d.ts} +102 -0
  33. package/dist/schema-sqlite.d.ts +114 -0
  34. package/dist/schema-sqlite.js +20 -4
  35. package/dist/schema-version.d.ts +1 -1
  36. package/dist/schema-version.js +70 -0
  37. package/dist/schema.d.ts +1 -1
  38. package/dist/schema.js +20 -4
  39. package/package.json +2 -2
  40. package/src/log.ts +40 -1
  41. package/src/migrations/postgres/0004_environment_parents.sql +5 -0
  42. package/src/migrations/postgres/0005_personal_tokens.sql +5 -0
  43. package/src/migrations/postgres/0006_uploaded_keys.sql +2 -0
  44. package/src/migrations/postgres/0007_personal_configs.sql +4 -0
  45. package/src/migrations/postgres/0008_recipes.sql +19 -0
  46. package/src/migrations/postgres/meta/0004_snapshot.json +2913 -0
  47. package/src/migrations/postgres/meta/0005_snapshot.json +2929 -0
  48. package/src/migrations/postgres/meta/0006_snapshot.json +2935 -0
  49. package/src/migrations/postgres/meta/0007_snapshot.json +2967 -0
  50. package/src/migrations/postgres/meta/0008_snapshot.json +2985 -0
  51. package/src/migrations/postgres/meta/_journal.json +35 -0
  52. package/src/migrations/sqlite/0004_environment_parents.sql +3 -0
  53. package/src/migrations/sqlite/0005_personal_tokens.sql +37 -0
  54. package/src/migrations/sqlite/0006_uploaded_keys.sql +1 -0
  55. package/src/migrations/sqlite/0007_personal_configs.sql +3 -0
  56. package/src/migrations/sqlite/0008_recipes.sql +15 -0
  57. package/src/migrations/sqlite/meta/0004_snapshot.json +2641 -0
  58. package/src/migrations/sqlite/meta/0005_snapshot.json +2656 -0
  59. package/src/migrations/sqlite/meta/0006_snapshot.json +2663 -0
  60. package/src/migrations/sqlite/meta/0007_snapshot.json +2683 -0
  61. package/src/migrations/sqlite/meta/0008_snapshot.json +2704 -0
  62. package/src/migrations/sqlite/meta/_journal.json +35 -0
  63. package/src/schema.sqlite.ts +31 -3
  64. package/src/schema.ts +37 -3
package/dist/connect.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { t as Database } from "./database-C63WjCrC.js";
1
+ import { t as Database } from "./database-BEnexh-3.js";
2
2
  import { Engine } from "./dialect.js";
3
3
  import { ClientConfig } from "pg";
4
4
  //#region src/postgres.d.ts
@@ -1,4 +1,4 @@
1
- import { m as schema_d_exports } from "./schema-a7PkFgea.js";
1
+ import { m as schema_d_exports } from "./schema-BZRkBJQZ.js";
2
2
  import { Table } from "drizzle-orm";
3
3
  import { NodePgDatabase } from "drizzle-orm/node-postgres";
4
4
  import pg from "pg";
package/dist/dialect.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { i as Queryable } from "./database-C63WjCrC.js";
1
+ import { i as Queryable } from "./database-BEnexh-3.js";
2
2
  import { SQL, SQLWrapper } from "drizzle-orm";
3
3
  import { PgTable } from "drizzle-orm/pg-core";
4
4
  //#region src/dialect.d.ts
package/dist/grants.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { i as Queryable } from "./database-C63WjCrC.js";
1
+ import { i as Queryable } from "./database-BEnexh-3.js";
2
2
  import { Authenticity, Newest } from "./log.js";
3
3
  import { Conversion, ConversionInput, InstanceRole, Scope } from "@coffre/core/access";
4
4
  import { StoredEntry } from "@coffre/core/audit";
@@ -1,4 +1,4 @@
1
- import { n as PoolLike } from "./database-C63WjCrC.js";
1
+ import { n as PoolLike } from "./database-BEnexh-3.js";
2
2
  import pg from "pg";
3
3
  //#region src/hyperdrive.d.ts
4
4
  /** Postgres through a Hyperdrive binding: `postgres(env.HYPERDRIVE)`. */
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- import { m as schema_d_exports } from "./schema-a7PkFgea.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-C63WjCrC.js";
1
+ import { m as schema_d_exports } from "./schema-BZRkBJQZ.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-BEnexh-3.js";
3
3
  export { Database, PoolLike, QueryOutsideTransaction, Queryable, Transaction, createDatabase, own, schema_d_exports as schema, tablesOf };
package/dist/log.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { a as Transaction, i as Queryable } from "./database-C63WjCrC.js";
1
+ import { a as Transaction, i as Queryable } from "./database-BEnexh-3.js";
2
+ import { SQL } from "drizzle-orm";
2
3
  import { LogKey, StoredEntry } from "@coffre/core/audit";
3
4
  //#region src/log.d.ts
4
5
  /**
@@ -441,6 +442,26 @@ export declare function entryColumns(db: Queryable): {
441
442
  pgColumnBuilderBrand: "PgCustomColumnBuilderBrand";
442
443
  }>;
443
444
  };
445
+ /**
446
+ * Metadata a reader of many entries holds at once, at most: an entry about
447
+ * a read of several secrets lists them, a thousand some 150 kilobytes, so a
448
+ * count of entries alone does not bound a batch's or a page's memory within
449
+ * a Worker's 128 megabytes.
450
+ */
451
+ export declare const BATCH_BYTES: number;
452
+ /**
453
+ * Of the entries `where` matches, in `order` of seq, the first `limit` or
454
+ * fewer that hold no more than `BATCH_BYTES` of metadata between them, but
455
+ * always the first: the seqs they run from and to, and whether the bytes
456
+ * cut the run short of `limit`. Null when none matches. A reader asks this
457
+ * first, then reads `where` between the two.
458
+ */
459
+ export declare function withinBytes(db: Queryable, where: SQL | undefined, limit: number, order?: 'asc' | 'desc'): Promise<{
460
+ from: bigint;
461
+ to: bigint;
462
+ cut: boolean;
463
+ count: number;
464
+ } | null>;
444
465
  /**
445
466
  * How a reader tells the vault's own entries from those forged in its name:
446
467
  * by their MAC, which only the vault holds the key to check. Each it finds
package/dist/log.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { t as afterCommit } from "./commits-BuW-NL3n.js";
2
2
  import { tablesOf } from "./index.js";
3
3
  import { clockMillis, forUpdate } from "./dialect.js";
4
- import { and, desc, eq, lt } from "drizzle-orm";
4
+ import { and, asc, desc, eq, lt, sql } from "drizzle-orm";
5
5
  import { GENESIS_HASH, sealEntry } from "@coffre/core/audit";
6
6
  //#region src/log.ts
7
7
  /** The log's head does not name its last entry: something moved it, or cut the log behind it. */
@@ -163,6 +163,41 @@ function entryColumns(db) {
163
163
  hash: auditLog.hash
164
164
  };
165
165
  }
166
+ /**
167
+ * Metadata a reader of many entries holds at once, at most: an entry about
168
+ * a read of several secrets lists them, a thousand some 150 kilobytes, so a
169
+ * count of entries alone does not bound a batch's or a page's memory within
170
+ * a Worker's 128 megabytes.
171
+ */
172
+ const BATCH_BYTES = 8388608;
173
+ /**
174
+ * Of the entries `where` matches, in `order` of seq, the first `limit` or
175
+ * fewer that hold no more than `BATCH_BYTES` of metadata between them, but
176
+ * always the first: the seqs they run from and to, and whether the bytes
177
+ * cut the run short of `limit`. Null when none matches. A reader asks this
178
+ * first, then reads `where` between the two.
179
+ */
180
+ async function withinBytes(db, where, limit, order = "asc") {
181
+ const { auditLog } = tablesOf(db);
182
+ const sizes = await db.select({
183
+ seq: auditLog.seq,
184
+ bytes: sql`length(${auditLog.metadata})`
185
+ }).from(auditLog).where(where).orderBy(order === "asc" ? asc(auditLog.seq) : desc(auditLog.seq)).limit(limit);
186
+ let held = 0;
187
+ let count = 0;
188
+ for (const { bytes } of sizes) {
189
+ held += Number(bytes);
190
+ if (count > 0 && held > 8388608) break;
191
+ count += 1;
192
+ }
193
+ if (count === 0) return null;
194
+ return {
195
+ from: sizes[0].seq,
196
+ to: sizes[count - 1].seq,
197
+ cut: count < sizes.length,
198
+ count
199
+ };
200
+ }
166
201
  /** Up to `limit` of the entries in the vault's name, allowed, of one `action`, before `before` (the newest when undefined), newest first. */
167
202
  async function newestVaultEntries(db, action, limit, before) {
168
203
  const { auditLog } = tablesOf(db);
@@ -194,4 +229,4 @@ async function newestAuthentic(page, check) {
194
229
  return "unreached";
195
230
  }
196
231
  //#endregion
197
- export { LOOK_BACK, LogHeadMismatch, LogRewound, appendEntries, entryColumns, forgetLogHeads, lockLogHead, newestAuthentic, newestVaultEntries };
232
+ export { BATCH_BYTES, LOOK_BACK, LogHeadMismatch, LogRewound, appendEntries, entryColumns, forgetLogHeads, lockLogHead, newestAuthentic, newestVaultEntries, withinBytes };
@@ -0,0 +1,5 @@
1
+ -- The app sets parent_id when it makes an environment, and never after: it gets no UPDATE grant, so a parent cannot change.
2
+ ALTER TABLE "environments" ADD COLUMN "parent_id" uuid;--> statement-breakpoint
3
+ ALTER TABLE "environments" ADD CONSTRAINT "environments_parent_in_project" FOREIGN KEY ("parent_id","project_id") REFERENCES "public"."environments"("id","project_id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
4
+ CREATE INDEX "environments_parent_idx" ON "environments" USING btree ("parent_id");--> statement-breakpoint
5
+ ALTER TABLE "environments" ADD CONSTRAINT "environments_parent_check" CHECK ("environments"."parent_id" IS NULL OR "environments"."parent_id" <> "environments"."id");
@@ -0,0 +1,5 @@
1
+ -- Personal tokens (0.7). The check widens to their kind: the release before makes none, and refuses one, whose prefix, coffre_pat_, it does not take for a token.
2
+ ALTER TABLE "credentials" DROP CONSTRAINT "credentials_kind_check";--> statement-breakpoint
3
+ ALTER TABLE "credentials" ADD CONSTRAINT "credentials_kind_check" CHECK ("credentials"."kind" IN ('browser', 'cli', 'service', 'personal'));--> statement-breakpoint
4
+ -- A token's creation, by the credential's ID: the label a personal token's entries are shown with.
5
+ CREATE INDEX "audit_log_token_create_idx" ON "audit_log" USING btree ((("metadata")::jsonb ->> 'targetCredentialId')) WHERE "audit_log"."author" = 'app' AND "audit_log"."action" = 'token.create' AND "audit_log"."decision" = 'allow';
@@ -0,0 +1,2 @@
1
+ -- An issuer's keys, uploaded with a binding where coffre fetches none. The app inserts them with the binding, under its table-wide INSERT, and no UPDATE grant names them: they never change in place.
2
+ ALTER TABLE "service_bindings" ADD COLUMN "jwks" text;
@@ -0,0 +1,4 @@
1
+ -- The app sets owner when it makes a personal config, and never after: it gets no UPDATE grant, so an owner cannot change. The vault binds each value to it, so a forged owner opens nothing.
2
+ ALTER TABLE "environments" ADD COLUMN "owner" text;--> statement-breakpoint
3
+ CREATE UNIQUE INDEX "environments_parent_id_owner_key" ON "environments" USING btree ("parent_id","owner") WHERE strpos("environments"."slug", '~') = 0;--> statement-breakpoint
4
+ ALTER TABLE "environments" ADD CONSTRAINT "environments_owner_check" CHECK ("environments"."owner" IS NULL OR ("environments"."parent_id" IS NOT NULL AND "environments"."owner" LIKE 'user:_%'));
@@ -0,0 +1,19 @@
1
+ ALTER TABLE "secret_versions" ADD COLUMN "recipe" text;--> statement-breakpoint
2
+ ALTER TABLE "secret_versions" ADD COLUMN "pair_id" uuid;--> statement-breakpoint
3
+ ALTER TABLE "secret_versions" ADD COLUMN "pair_half" text;--> statement-breakpoint
4
+ -- A version is still only ever erased: its recipe and its pair with it.
5
+ CREATE OR REPLACE FUNCTION secret_versions_erase_only() RETURNS trigger LANGUAGE plpgsql AS $$
6
+ BEGIN
7
+ IF octet_length(NEW.ciphertext) <> 0 OR octet_length(NEW.wrapped_dek) <> 0
8
+ OR (NEW.id, NEW.secret_id, NEW.version, NEW.envelope_version, NEW.iv, NEW.auth_tag,
9
+ NEW.kek_provider, NEW.kek_id, NEW.kek_version, NEW.created_at, NEW.created_by,
10
+ NEW.recipe, NEW.pair_id, NEW.pair_half)
11
+ IS DISTINCT FROM
12
+ (OLD.id, OLD.secret_id, OLD.version, OLD.envelope_version, OLD.iv, OLD.auth_tag,
13
+ OLD.kek_provider, OLD.kek_id, OLD.kek_version, OLD.created_at, OLD.created_by,
14
+ OLD.recipe, OLD.pair_id, OLD.pair_half) THEN
15
+ RAISE EXCEPTION 'a secret version is only ever erased' USING ERRCODE = 'insufficient_privilege';
16
+ END IF;
17
+ RETURN NEW;
18
+ END
19
+ $$;