@coffre/db 0.6.2 → 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
@@ -29,6 +29,41 @@
29
29
  "when": 1791676487593,
30
30
  "tag": "0003_credential_scopes",
31
31
  "breakpoints": true
32
+ },
33
+ {
34
+ "idx": 4,
35
+ "version": "6",
36
+ "when": 1792244020959,
37
+ "tag": "0004_environment_parents",
38
+ "breakpoints": true
39
+ },
40
+ {
41
+ "idx": 5,
42
+ "version": "6",
43
+ "when": 1792250910230,
44
+ "tag": "0005_personal_tokens",
45
+ "breakpoints": true
46
+ },
47
+ {
48
+ "idx": 6,
49
+ "version": "6",
50
+ "when": 1792252693912,
51
+ "tag": "0006_uploaded_keys",
52
+ "breakpoints": true
53
+ },
54
+ {
55
+ "idx": 7,
56
+ "version": "6",
57
+ "when": 1792253795070,
58
+ "tag": "0007_personal_configs",
59
+ "breakpoints": true
60
+ },
61
+ {
62
+ "idx": 8,
63
+ "version": "6",
64
+ "when": 1792255560312,
65
+ "tag": "0008_recipes",
66
+ "breakpoints": true
32
67
  }
33
68
  ]
34
69
  }
@@ -1,5 +1,5 @@
1
- import { m as schema_d_exports } from "./schema-a7PkFgea.js";
2
- import { t as Database } from "./database-C63WjCrC.js";
1
+ import { m as schema_d_exports } from "./schema-BZRkBJQZ.js";
2
+ import { t as Database } from "./database-BEnexh-3.js";
3
3
  import { t as schema_sqlite_d_exports } from "./schema-sqlite.js";
4
4
  import { LibSQLDatabase } from "drizzle-orm/libsql";
5
5
  //#region src/portable.d.ts
@@ -216,6 +216,40 @@ declare const environments: import("drizzle-orm/pg-core").PgTableWithColumns<{
216
216
  identity: undefined;
217
217
  generated: undefined;
218
218
  }, {}, {}>;
219
+ parentId: import("drizzle-orm/pg-core").PgColumn<{
220
+ name: "parent_id";
221
+ tableName: "environments";
222
+ dataType: "string";
223
+ columnType: "PgUUID";
224
+ data: string;
225
+ driverParam: string;
226
+ notNull: false;
227
+ hasDefault: false;
228
+ isPrimaryKey: false;
229
+ isAutoincrement: false;
230
+ hasRuntimeDefault: false;
231
+ enumValues: undefined;
232
+ baseColumn: never;
233
+ identity: undefined;
234
+ generated: undefined;
235
+ }, {}, {}>;
236
+ owner: import("drizzle-orm/pg-core").PgColumn<{
237
+ name: "owner";
238
+ tableName: "environments";
239
+ dataType: "string";
240
+ columnType: "PgText";
241
+ data: string;
242
+ driverParam: string;
243
+ notNull: false;
244
+ hasDefault: false;
245
+ isPrimaryKey: false;
246
+ isAutoincrement: false;
247
+ hasRuntimeDefault: false;
248
+ enumValues: [string, ...string[]];
249
+ baseColumn: never;
250
+ identity: undefined;
251
+ generated: undefined;
252
+ }, {}, {}>;
219
253
  };
220
254
  dialect: "pg";
221
255
  }>;
@@ -629,6 +663,57 @@ declare const secretVersions: import("drizzle-orm/pg-core").PgTableWithColumns<{
629
663
  identity: undefined;
630
664
  generated: undefined;
631
665
  }, {}, {}>;
666
+ recipe: import("drizzle-orm/pg-core").PgColumn<{
667
+ name: "recipe";
668
+ tableName: "secret_versions";
669
+ dataType: "string";
670
+ columnType: "PgText";
671
+ data: string;
672
+ driverParam: string;
673
+ notNull: false;
674
+ hasDefault: false;
675
+ isPrimaryKey: false;
676
+ isAutoincrement: false;
677
+ hasRuntimeDefault: false;
678
+ enumValues: [string, ...string[]];
679
+ baseColumn: never;
680
+ identity: undefined;
681
+ generated: undefined;
682
+ }, {}, {}>;
683
+ pairId: import("drizzle-orm/pg-core").PgColumn<{
684
+ name: "pair_id";
685
+ tableName: "secret_versions";
686
+ dataType: "string";
687
+ columnType: "PgUUID";
688
+ data: string;
689
+ driverParam: string;
690
+ notNull: false;
691
+ hasDefault: false;
692
+ isPrimaryKey: false;
693
+ isAutoincrement: false;
694
+ hasRuntimeDefault: false;
695
+ enumValues: undefined;
696
+ baseColumn: never;
697
+ identity: undefined;
698
+ generated: undefined;
699
+ }, {}, {}>;
700
+ pairHalf: import("drizzle-orm/pg-core").PgColumn<{
701
+ name: "pair_half";
702
+ tableName: "secret_versions";
703
+ dataType: "string";
704
+ columnType: "PgText";
705
+ data: string;
706
+ driverParam: string;
707
+ notNull: false;
708
+ hasDefault: false;
709
+ isPrimaryKey: false;
710
+ isAutoincrement: false;
711
+ hasRuntimeDefault: false;
712
+ enumValues: [string, ...string[]];
713
+ baseColumn: never;
714
+ identity: undefined;
715
+ generated: undefined;
716
+ }, {}, {}>;
632
717
  };
633
718
  dialect: "pg";
634
719
  }>;
@@ -2123,6 +2208,23 @@ declare const serviceBindings: import("drizzle-orm/pg-core").PgTableWithColumns<
2123
2208
  identity: undefined;
2124
2209
  generated: undefined;
2125
2210
  }, {}, {}>;
2211
+ jwks: import("drizzle-orm/pg-core").PgColumn<{
2212
+ name: "jwks";
2213
+ tableName: "service_bindings";
2214
+ dataType: "string";
2215
+ columnType: "PgText";
2216
+ data: string;
2217
+ driverParam: string;
2218
+ notNull: false;
2219
+ hasDefault: false;
2220
+ isPrimaryKey: false;
2221
+ isAutoincrement: false;
2222
+ hasRuntimeDefault: false;
2223
+ enumValues: [string, ...string[]];
2224
+ baseColumn: never;
2225
+ identity: undefined;
2226
+ generated: undefined;
2227
+ }, {}, {}>;
2126
2228
  claims: import("drizzle-orm/pg-core").PgColumn<{
2127
2229
  name: "claims";
2128
2230
  tableName: "service_bindings";
@@ -232,6 +232,44 @@ export declare const environments: import("drizzle-orm/sqlite-core").SQLiteTable
232
232
  identity: undefined;
233
233
  generated: undefined;
234
234
  }, {}, {}>;
235
+ parentId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
236
+ name: "parent_id";
237
+ tableName: "environments";
238
+ dataType: "string";
239
+ columnType: "SQLiteText";
240
+ data: string;
241
+ driverParam: string;
242
+ notNull: false;
243
+ hasDefault: false;
244
+ isPrimaryKey: false;
245
+ isAutoincrement: false;
246
+ hasRuntimeDefault: false;
247
+ enumValues: [string, ...string[]];
248
+ baseColumn: never;
249
+ identity: undefined;
250
+ generated: undefined;
251
+ }, {}, {
252
+ length: number | undefined;
253
+ }>;
254
+ owner: import("drizzle-orm/sqlite-core").SQLiteColumn<{
255
+ name: "owner";
256
+ tableName: "environments";
257
+ dataType: "string";
258
+ columnType: "SQLiteText";
259
+ data: string;
260
+ driverParam: string;
261
+ notNull: false;
262
+ hasDefault: false;
263
+ isPrimaryKey: false;
264
+ isAutoincrement: false;
265
+ hasRuntimeDefault: false;
266
+ enumValues: [string, ...string[]];
267
+ baseColumn: never;
268
+ identity: undefined;
269
+ generated: undefined;
270
+ }, {}, {
271
+ length: undefined;
272
+ }>;
235
273
  };
236
274
  dialect: "sqlite";
237
275
  }>;
@@ -661,6 +699,63 @@ export declare const secretVersions: import("drizzle-orm/sqlite-core").SQLiteTab
661
699
  }, {}, {
662
700
  length: number | undefined;
663
701
  }>;
702
+ recipe: import("drizzle-orm/sqlite-core").SQLiteColumn<{
703
+ name: "recipe";
704
+ tableName: "secret_versions";
705
+ dataType: "string";
706
+ columnType: "SQLiteText";
707
+ data: string;
708
+ driverParam: string;
709
+ notNull: false;
710
+ hasDefault: false;
711
+ isPrimaryKey: false;
712
+ isAutoincrement: false;
713
+ hasRuntimeDefault: false;
714
+ enumValues: [string, ...string[]];
715
+ baseColumn: never;
716
+ identity: undefined;
717
+ generated: undefined;
718
+ }, {}, {
719
+ length: undefined;
720
+ }>;
721
+ pairId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
722
+ name: "pair_id";
723
+ tableName: "secret_versions";
724
+ dataType: "string";
725
+ columnType: "SQLiteText";
726
+ data: string;
727
+ driverParam: string;
728
+ notNull: false;
729
+ hasDefault: false;
730
+ isPrimaryKey: false;
731
+ isAutoincrement: false;
732
+ hasRuntimeDefault: false;
733
+ enumValues: [string, ...string[]];
734
+ baseColumn: never;
735
+ identity: undefined;
736
+ generated: undefined;
737
+ }, {}, {
738
+ length: number | undefined;
739
+ }>;
740
+ pairHalf: import("drizzle-orm/sqlite-core").SQLiteColumn<{
741
+ name: "pair_half";
742
+ tableName: "secret_versions";
743
+ dataType: "string";
744
+ columnType: "SQLiteText";
745
+ data: string;
746
+ driverParam: string;
747
+ notNull: false;
748
+ hasDefault: false;
749
+ isPrimaryKey: false;
750
+ isAutoincrement: false;
751
+ hasRuntimeDefault: false;
752
+ enumValues: [string, ...string[]];
753
+ baseColumn: never;
754
+ identity: undefined;
755
+ generated: undefined;
756
+ }, {}, {
757
+ length: number | undefined;
758
+ }>;
664
759
  };
665
760
  dialect: "sqlite";
666
761
  }>;
@@ -2212,6 +2307,25 @@ export declare const serviceBindings: import("drizzle-orm/sqlite-core").SQLiteTa
2212
2307
  }, {}, {
2213
2308
  length: number | undefined;
2214
2309
  }>;
2310
+ jwks: import("drizzle-orm/sqlite-core").SQLiteColumn<{
2311
+ name: "jwks";
2312
+ tableName: "service_bindings";
2313
+ dataType: "string";
2314
+ columnType: "SQLiteText";
2315
+ data: string;
2316
+ driverParam: string;
2317
+ notNull: false;
2318
+ hasDefault: false;
2319
+ isPrimaryKey: false;
2320
+ isAutoincrement: false;
2321
+ hasRuntimeDefault: false;
2322
+ enumValues: [string, ...string[]];
2323
+ baseColumn: never;
2324
+ identity: undefined;
2325
+ generated: undefined;
2326
+ }, {}, {
2327
+ length: undefined;
2328
+ }>;
2215
2329
  claims: import("drizzle-orm/sqlite-core").SQLiteColumn<{
2216
2330
  name: "claims";
2217
2331
  tableName: "service_bindings";
@@ -54,16 +54,27 @@ const environments = sqliteTable("environments", {
54
54
  slug: text().notNull(),
55
55
  name: text().notNull(),
56
56
  createdAt: createdAt(),
57
- archivedAt: time("archived_at")
57
+ archivedAt: time("archived_at"),
58
+ parentId: text("parent_id"),
59
+ owner: text()
58
60
  }, (table) => [
59
61
  unique("environments_project_id_slug_key").on(table.projectId, table.slug),
60
62
  unique("environments_project_scoped").on(table.id, table.projectId),
61
63
  check("environments_slug_check", isPlaceSlug(table.slug)),
64
+ check("environments_parent_check", sql`${table.parentId} IS NULL OR ${table.parentId} <> ${table.id}`),
65
+ check("environments_owner_check", sql`${table.owner} IS NULL OR (${table.parentId} IS NOT NULL AND ${table.owner} LIKE 'user:_%')`),
66
+ uniqueIndex("environments_parent_id_owner_key").on(table.parentId, table.owner).where(sql`instr(${table.slug}, '~') = 0`),
62
67
  foreignKey({
63
68
  name: "environments_project_id_fkey",
64
69
  columns: [table.projectId],
65
70
  foreignColumns: [projects.id]
66
- }).onDelete("restrict")
71
+ }).onDelete("restrict"),
72
+ foreignKey({
73
+ name: "environments_parent_in_project",
74
+ columns: [table.parentId, table.projectId],
75
+ foreignColumns: [table.id, table.projectId]
76
+ }).onDelete("no action"),
77
+ index("environments_parent_idx").on(table.parentId)
67
78
  ]);
68
79
  const secrets = sqliteTable("secrets", {
69
80
  id: text().primaryKey(),
@@ -110,7 +121,10 @@ const secretVersions = sqliteTable("secret_versions", {
110
121
  kekId: text("kek_id").notNull(),
111
122
  kekVersion: text("kek_version").notNull(),
112
123
  createdAt: createdAt(),
113
- createdBy: text("created_by").notNull()
124
+ createdBy: text("created_by").notNull(),
125
+ recipe: text(),
126
+ pairId: text("pair_id"),
127
+ pairHalf: text("pair_half")
114
128
  }, (table) => [
115
129
  unique("secret_versions_secret_id_version_key").on(table.secretId, table.version),
116
130
  check("secret_versions_version_check", sql`${table.version} > 0`),
@@ -267,6 +281,7 @@ const auditLog = sqliteTable("audit_log", {
267
281
  index("audit_log_subject_idx").on(table.author, table.subjectPrincipal, table.seq),
268
282
  index("audit_log_unbind_idx").on(sql`json_extract(${table.metadata}, '$.bindingId')`).where(sql`${table.author} = 'app' AND ${table.action} = 'token.unbind' AND ${table.decision} = 'allow'`),
269
283
  index("audit_log_exchange_idx").on(sql`json_extract(${table.metadata}, '$.credentialId')`).where(sql`${table.author} = 'app' AND ${table.action} = 'token.exchange' AND ${table.decision} = 'allow'`),
284
+ index("audit_log_token_create_idx").on(sql`json_extract(${table.metadata}, '$.targetCredentialId')`).where(sql`${table.author} = 'app' AND ${table.action} = 'token.create' AND ${table.decision} = 'allow'`),
270
285
  index("audit_log_reference_end_idx").on(table.relatedSeq).where(sql`${table.author} = 'vault' AND ${table.action} = 'reference.end' AND ${table.decision} = 'allow'`),
271
286
  index("audit_log_also_project_idx").on(sql`json_extract(${table.metadata}, '$.also.projectId')`, table.seq).where(sql`json_extract(${table.metadata}, '$.also') IS NOT NULL`),
272
287
  index("audit_log_also_environment_idx").on(sql`json_extract(${table.metadata}, '$.also.environmentId')`, table.seq).where(sql`json_extract(${table.metadata}, '$.also') IS NOT NULL`),
@@ -351,6 +366,7 @@ const serviceBindings = sqliteTable("service_bindings", {
351
366
  profile: text().notNull(),
352
367
  issuer: text().notNull(),
353
368
  jwksUri: text("jwks_uri").notNull(),
369
+ jwks: text(),
354
370
  claims: text().notNull(),
355
371
  scope: text(),
356
372
  label: text(),
@@ -481,7 +497,7 @@ const credentials = sqliteTable("credentials", {
481
497
  revokedBy: text("revoked_by")
482
498
  }, (table) => [
483
499
  unique("credentials_token_hash_key").on(table.tokenHash),
484
- check("credentials_kind_check", sql`${table.kind} IN ('browser', 'cli', 'service')`),
500
+ check("credentials_kind_check", sql`${table.kind} IN ('browser', 'cli', 'service', 'personal')`),
485
501
  check("credentials_principal_check", sql`${table.principal} LIKE 'user:%' OR ${table.principal} LIKE 'token:%'`),
486
502
  check("credentials_kind_matches_principal", sql`(${table.kind} = 'service') = (${table.principal} LIKE 'token:%')`),
487
503
  check("credentials_token_hash_check", sql`octet_length(${table.tokenHash}) = 32`),
@@ -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 { Engine } from "./dialect.js";
3
3
  //#region src/schema-version.d.ts
4
4
  /**
@@ -36,6 +36,41 @@ const KNOWN_MIGRATIONS = {
36
36
  "when": 1791676486914,
37
37
  "tag": "0003_credential_scopes",
38
38
  "breakpoints": true
39
+ },
40
+ {
41
+ "idx": 4,
42
+ "version": "7",
43
+ "when": 1792244020330,
44
+ "tag": "0004_environment_parents",
45
+ "breakpoints": true
46
+ },
47
+ {
48
+ "idx": 5,
49
+ "version": "7",
50
+ "when": 1792250909587,
51
+ "tag": "0005_personal_tokens",
52
+ "breakpoints": true
53
+ },
54
+ {
55
+ "idx": 6,
56
+ "version": "7",
57
+ "when": 1792252693050,
58
+ "tag": "0006_uploaded_keys",
59
+ "breakpoints": true
60
+ },
61
+ {
62
+ "idx": 7,
63
+ "version": "7",
64
+ "when": 1792253794481,
65
+ "tag": "0007_personal_configs",
66
+ "breakpoints": true
67
+ },
68
+ {
69
+ "idx": 8,
70
+ "version": "7",
71
+ "when": 1792255559691,
72
+ "tag": "0008_recipes",
73
+ "breakpoints": true
39
74
  }
40
75
  ].map((entry) => entry.tag),
41
76
  sqlite: [
@@ -66,6 +101,41 @@ const KNOWN_MIGRATIONS = {
66
101
  "when": 1791676487593,
67
102
  "tag": "0003_credential_scopes",
68
103
  "breakpoints": true
104
+ },
105
+ {
106
+ "idx": 4,
107
+ "version": "6",
108
+ "when": 1792244020959,
109
+ "tag": "0004_environment_parents",
110
+ "breakpoints": true
111
+ },
112
+ {
113
+ "idx": 5,
114
+ "version": "6",
115
+ "when": 1792250910230,
116
+ "tag": "0005_personal_tokens",
117
+ "breakpoints": true
118
+ },
119
+ {
120
+ "idx": 6,
121
+ "version": "6",
122
+ "when": 1792252693912,
123
+ "tag": "0006_uploaded_keys",
124
+ "breakpoints": true
125
+ },
126
+ {
127
+ "idx": 7,
128
+ "version": "6",
129
+ "when": 1792253795070,
130
+ "tag": "0007_personal_configs",
131
+ "breakpoints": true
132
+ },
133
+ {
134
+ "idx": 8,
135
+ "version": "6",
136
+ "when": 1792255560312,
137
+ "tag": "0008_recipes",
138
+ "breakpoints": true
69
139
  }
70
140
  ].map((entry) => entry.tag)
71
141
  };
package/dist/schema.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { _ as secrets, a as deviceAuthorizations, b as vaultGrants, c as environmentsRelations, d as mcpConnections, f as oauthClients, g as secretVersions, h as secretReferences, i as credentials, l as identities, n as auditLog, o as dismissedKeys, p as projects, r as consumedTokens, s as environments, t as auditChainHead, u as mcpApprovals, v as secretsRelations, x as vaultMembers, y as serviceBindings } from "./schema-a7PkFgea.js";
1
+ import { _ as secrets, a as deviceAuthorizations, b as vaultGrants, c as environmentsRelations, d as mcpConnections, f as oauthClients, g as secretVersions, h as secretReferences, i as credentials, l as identities, n as auditLog, o as dismissedKeys, p as projects, r as consumedTokens, s as environments, t as auditChainHead, u as mcpApprovals, v as secretsRelations, x as vaultMembers, y as serviceBindings } from "./schema-BZRkBJQZ.js";
2
2
  export { auditChainHead, auditLog, consumedTokens, credentials, deviceAuthorizations, dismissedKeys, environments, environmentsRelations, identities, mcpApprovals, mcpConnections, oauthClients, projects, secretReferences, secretVersions, secrets, secretsRelations, serviceBindings, vaultGrants, vaultMembers };
package/dist/schema.js CHANGED
@@ -59,16 +59,27 @@ const environments = pgTable("environments", {
59
59
  slug: text().notNull(),
60
60
  name: text().notNull(),
61
61
  createdAt: createdAt(),
62
- archivedAt: timestamp("archived_at", { withTimezone: true })
62
+ archivedAt: timestamp("archived_at", { withTimezone: true }),
63
+ parentId: uuid("parent_id"),
64
+ owner: text()
63
65
  }, (table) => [
64
66
  unique("environments_project_id_slug_key").on(table.projectId, table.slug),
65
67
  unique("environments_project_scoped").on(table.id, table.projectId),
66
68
  check("environments_slug_check", sql`${table.slug} ~ ${PLACE_SLUG}`),
69
+ check("environments_parent_check", sql`${table.parentId} IS NULL OR ${table.parentId} <> ${table.id}`),
70
+ check("environments_owner_check", sql`${table.owner} IS NULL OR (${table.parentId} IS NOT NULL AND ${table.owner} LIKE 'user:_%')`),
71
+ uniqueIndex("environments_parent_id_owner_key").on(table.parentId, table.owner).where(sql`strpos(${table.slug}, '~') = 0`),
67
72
  foreignKey({
68
73
  name: "environments_project_id_fkey",
69
74
  columns: [table.projectId],
70
75
  foreignColumns: [projects.id]
71
- }).onDelete("restrict")
76
+ }).onDelete("restrict"),
77
+ foreignKey({
78
+ name: "environments_parent_in_project",
79
+ columns: [table.parentId, table.projectId],
80
+ foreignColumns: [table.id, table.projectId]
81
+ }).onDelete("no action"),
82
+ index("environments_parent_idx").on(table.parentId)
72
83
  ]);
73
84
  const secrets = pgTable("secrets", {
74
85
  id: uuid().primaryKey().defaultRandom(),
@@ -115,7 +126,10 @@ const secretVersions = pgTable("secret_versions", {
115
126
  kekId: text("kek_id").notNull(),
116
127
  kekVersion: text("kek_version").notNull(),
117
128
  createdAt: createdAt(),
118
- createdBy: text("created_by").notNull()
129
+ createdBy: text("created_by").notNull(),
130
+ recipe: text(),
131
+ pairId: uuid("pair_id"),
132
+ pairHalf: text("pair_half")
119
133
  }, (table) => [
120
134
  unique("secret_versions_secret_id_version_key").on(table.secretId, table.version),
121
135
  check("secret_versions_version_check", sql`${table.version} > 0`),
@@ -292,6 +306,7 @@ const auditLog = pgTable("audit_log", {
292
306
  index("audit_log_subject_idx").on(table.author, table.subjectPrincipal, table.seq),
293
307
  index("audit_log_unbind_idx").on(sql`((${table.metadata})::jsonb ->> 'bindingId')`).where(sql`${table.author} = 'app' AND ${table.action} = 'token.unbind' AND ${table.decision} = 'allow'`),
294
308
  index("audit_log_exchange_idx").on(sql`((${table.metadata})::jsonb ->> 'credentialId')`).where(sql`${table.author} = 'app' AND ${table.action} = 'token.exchange' AND ${table.decision} = 'allow'`),
309
+ index("audit_log_token_create_idx").on(sql`((${table.metadata})::jsonb ->> 'targetCredentialId')`).where(sql`${table.author} = 'app' AND ${table.action} = 'token.create' AND ${table.decision} = 'allow'`),
295
310
  index("audit_log_reference_end_idx").on(table.relatedSeq).where(sql`${table.author} = 'vault' AND ${table.action} = 'reference.end' AND ${table.decision} = 'allow'`),
296
311
  index("audit_log_also_project_idx").on(sql`(((${table.metadata})::jsonb -> 'also') ->> 'projectId')`, table.seq).where(sql`((${table.metadata})::jsonb -> 'also') IS NOT NULL`),
297
312
  index("audit_log_also_environment_idx").on(sql`(((${table.metadata})::jsonb -> 'also') ->> 'environmentId')`, table.seq).where(sql`((${table.metadata})::jsonb -> 'also') IS NOT NULL`),
@@ -399,6 +414,7 @@ const serviceBindings = pgTable("service_bindings", {
399
414
  profile: text().notNull(),
400
415
  issuer: text().notNull(),
401
416
  jwksUri: text("jwks_uri").notNull(),
417
+ jwks: text(),
402
418
  claims: text().notNull(),
403
419
  scope: text(),
404
420
  label: text(),
@@ -547,7 +563,7 @@ const credentials = pgTable("credentials", {
547
563
  revokedBy: text("revoked_by")
548
564
  }, (table) => [
549
565
  unique("credentials_token_hash_key").on(table.tokenHash),
550
- check("credentials_kind_check", sql`${table.kind} IN ('browser', 'cli', 'service')`),
566
+ check("credentials_kind_check", sql`${table.kind} IN ('browser', 'cli', 'service', 'personal')`),
551
567
  check("credentials_principal_check", sql`${table.principal} LIKE 'user:%' OR ${table.principal} LIKE 'token:%'`),
552
568
  check("credentials_kind_matches_principal", sql`(${table.kind} = 'service') = (${table.principal} LIKE 'token:%')`),
553
569
  check("credentials_token_hash_check", sql`octet_length(${table.tokenHash}) = 32`),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coffre/db",
3
- "version": "0.6.2",
3
+ "version": "0.7.0",
4
4
  "description": "coffre's database: the schema, its migrations and the connections, on Postgres or SQLite.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -79,7 +79,7 @@
79
79
  "drizzle-orm": "0.45.2",
80
80
  "pg": "8.16.3",
81
81
  "pg-connection-string": "2.14.0",
82
- "@coffre/core": "0.6.2"
82
+ "@coffre/core": "0.7.0"
83
83
  },
84
84
  "devDependencies": {
85
85
  "@types/node": "26.1.1",
package/src/log.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { GENESIS_HASH, sealEntry, type LogFields, type LogKey, type StoredEntry } from '@coffre/core/audit';
2
- import { and, desc, eq, lt } from 'drizzle-orm';
2
+ import { and, asc, desc, eq, lt, sql, type SQL } from 'drizzle-orm';
3
3
 
4
4
  import { afterCommit } from './commits.ts';
5
5
  import { tablesOf, type Queryable, type Transaction } from './database.ts';
@@ -218,6 +218,45 @@ export function entryColumns(db: Queryable) {
218
218
  };
219
219
  }
220
220
 
221
+ /**
222
+ * Metadata a reader of many entries holds at once, at most: an entry about
223
+ * a read of several secrets lists them, a thousand some 150 kilobytes, so a
224
+ * count of entries alone does not bound a batch's or a page's memory within
225
+ * a Worker's 128 megabytes.
226
+ */
227
+ export const BATCH_BYTES = 8 * 1024 * 1024;
228
+
229
+ /**
230
+ * Of the entries `where` matches, in `order` of seq, the first `limit` or
231
+ * fewer that hold no more than `BATCH_BYTES` of metadata between them, but
232
+ * always the first: the seqs they run from and to, and whether the bytes
233
+ * cut the run short of `limit`. Null when none matches. A reader asks this
234
+ * first, then reads `where` between the two.
235
+ */
236
+ export async function withinBytes(
237
+ db: Queryable,
238
+ where: SQL | undefined,
239
+ limit: number,
240
+ order: 'asc' | 'desc' = 'asc',
241
+ ): Promise<{ from: bigint; to: bigint; cut: boolean; count: number } | null> {
242
+ const { auditLog } = tablesOf(db);
243
+ const sizes = await db
244
+ .select({ seq: auditLog.seq, bytes: sql<number | string>`length(${auditLog.metadata})` })
245
+ .from(auditLog)
246
+ .where(where)
247
+ .orderBy(order === 'asc' ? asc(auditLog.seq) : desc(auditLog.seq))
248
+ .limit(limit);
249
+ let held = 0;
250
+ let count = 0;
251
+ for (const { bytes } of sizes) {
252
+ held += Number(bytes);
253
+ if (count > 0 && held > BATCH_BYTES) break;
254
+ count += 1;
255
+ }
256
+ if (count === 0) return null;
257
+ return { from: sizes[0]!.seq, to: sizes[count - 1]!.seq, cut: count < sizes.length, count };
258
+ }
259
+
221
260
  /**
222
261
  * How a reader tells the vault's own entries from those forged in its name:
223
262
  * by their MAC, which only the vault holds the key to check. Each it finds
@@ -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
+ $$;