@coffre/vault 0.1.8 → 0.1.10

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.
@@ -10,7 +10,9 @@ export type WorkersVaultConfig = VaultConfig & {
10
10
  };
11
11
  /**
12
12
  * What the app's `VAULT` service binding calls. Each call gets a database of
13
- * its own, since a Worker's connections belong to the call that made them.
13
+ * its own, one client that its queries take turns on, since a Worker's
14
+ * connections belong to the call that made them; it is closed once the
15
+ * call is done.
14
16
  */
15
17
  export declare class VaultEntrypoint extends WorkerEntrypoint implements Vault {
16
18
  #private;
@@ -1,5 +1,5 @@
1
1
  import { a as verifyCheckpoint, i as checkpointMessage, n as KekUnavailableError, o as derivedSigningKey, r as awsKms, s as resolveVaultConfig, t as KekBadClaimError } from "./src-DHuqw-IB.js";
2
- import { n as prepareVault, t as openVault } from "./vault-BV36qTZy.js";
2
+ import { n as prepareVault, t as openVault } from "./vault-DfviNOts.js";
3
3
  import { createDatabase } from "@coffre/db";
4
4
  import { HyperdrivePool, postgres } from "@coffre/db/hyperdrive";
5
5
  import { WorkerEntrypoint } from "cloudflare:workers";
@@ -14,10 +14,21 @@ let configure = null;
14
14
  const ready = /* @__PURE__ */ new WeakMap();
15
15
  /**
16
16
  * What the app's `VAULT` service binding calls. Each call gets a database of
17
- * its own, since a Worker's connections belong to the call that made them.
17
+ * its own, one client that its queries take turns on, since a Worker's
18
+ * connections belong to the call that made them; it is closed once the
19
+ * call is done.
18
20
  */
19
21
  var VaultEntrypoint = class extends WorkerEntrypoint {
20
- async #vault() {
22
+ async #call(run) {
23
+ const { prepared, database } = await this.#ready();
24
+ const pool = new HyperdrivePool(database.hyperdrive.connectionString);
25
+ try {
26
+ return await run(openVault(createDatabase(pool), prepared));
27
+ } finally {
28
+ this.ctx.waitUntil(pool.end());
29
+ }
30
+ }
31
+ async #ready() {
21
32
  const env = this.env;
22
33
  let entry = ready.get(env);
23
34
  if (entry === void 0) {
@@ -30,41 +41,40 @@ var VaultEntrypoint = class extends WorkerEntrypoint {
30
41
  };
31
42
  ready.set(env, entry);
32
43
  }
33
- const { database, prepared } = entry;
34
- return openVault(createDatabase(new HyperdrivePool(database.hyperdrive.connectionString)), prepared);
44
+ return entry;
35
45
  }
36
46
  async unwrap(input) {
37
- return (await this.#vault()).unwrap(input);
47
+ return this.#call((vault) => vault.unwrap(input));
38
48
  }
39
49
  async wrap(input) {
40
- return (await this.#vault()).wrap(input);
50
+ return this.#call((vault) => vault.wrap(input));
41
51
  }
42
52
  async rewrap(input) {
43
- return (await this.#vault()).rewrap(input);
53
+ return this.#call((vault) => vault.rewrap(input));
44
54
  }
45
55
  async access(principal) {
46
- return (await this.#vault()).access(principal);
56
+ return this.#call((vault) => vault.access(principal));
47
57
  }
48
58
  async setAccess(input) {
49
- return (await this.#vault()).setAccess(input);
59
+ return this.#call((vault) => vault.setAccess(input));
50
60
  }
51
61
  async admit(input) {
52
- return (await this.#vault()).admit(input);
62
+ return this.#call((vault) => vault.admit(input));
53
63
  }
54
64
  async remove(input) {
55
- return (await this.#vault()).remove(input);
65
+ return this.#call((vault) => vault.remove(input));
56
66
  }
57
67
  async checkpoint() {
58
- return (await this.#vault()).checkpoint();
68
+ return this.#call((vault) => vault.checkpoint());
59
69
  }
60
70
  async about() {
61
- return (await this.#vault()).about();
71
+ return this.#call((vault) => vault.about());
62
72
  }
63
73
  async keyChecks() {
64
- return (await this.#vault()).keyChecks();
74
+ return this.#call((vault) => vault.keyChecks());
65
75
  }
66
76
  async verifyLog(input) {
67
- return (await this.#vault()).verifyLog(input);
77
+ return this.#call((vault) => vault.verifyLog(input));
68
78
  }
69
79
  /** No HTTP surface: only the app's service binding reaches the vault. */
70
80
  fetch() {
package/dist/node.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { a as verifyCheckpoint, i as checkpointMessage, n as KekUnavailableError, o as derivedSigningKey, r as awsKms, s as resolveVaultConfig, t as KekBadClaimError } from "./src-DHuqw-IB.js";
2
- import { n as prepareVault, t as openVault } from "./vault-BV36qTZy.js";
2
+ import { n as prepareVault, t as openVault } from "./vault-DfviNOts.js";
3
3
  import { chmodSync, existsSync, lstatSync, rmSync } from "node:fs";
4
4
  import { Agent, createServer, request } from "node:http";
5
5
  import { openDatabase } from "@coffre/db/connect";
@@ -1441,10 +1441,16 @@ var VaultService = class {
1441
1441
  * it like anyone's. Logged, and made in a transaction of its own, under
1442
1442
  * the log's lock: a decision that held the head and then waited for a
1443
1443
  * member row would take the locks out of order. The row never makes
1444
- * anyone a root admin; the configuration does.
1444
+ * anyone a root admin; the configuration does. Rows are never deleted, so
1445
+ * one read finds it made already, as on every fresh isolate but the first,
1446
+ * without queueing on the log's lock.
1445
1447
  */
1446
1448
  async #rootRow(principal) {
1447
1449
  if (this.#prepared.rooted.has(principal) || await this.#settled() !== null) return;
1450
+ if (await member(this.#db, principal) !== void 0) {
1451
+ this.#prepared.rooted.add(principal);
1452
+ return;
1453
+ }
1448
1454
  await this.#db.transaction(async (tx) => {
1449
1455
  await lockLogHead(tx);
1450
1456
  if (await member(tx, principal) !== void 0) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coffre/vault",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "coffre's vault: keys, grants and members, in the database the app uses, as a Cloudflare Worker or on Node.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -36,8 +36,8 @@
36
36
  },
37
37
  "dependencies": {
38
38
  "drizzle-orm": "0.45.2",
39
- "@coffre/core": "0.1.8",
40
- "@coffre/db": "0.1.8"
39
+ "@coffre/core": "0.1.10",
40
+ "@coffre/db": "0.1.10"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/node": "26.1.1",
package/src/cloudflare.ts CHANGED
@@ -51,10 +51,22 @@ const ready = new WeakMap<object, { database: PostgresDatabase; prepared: Prepar
51
51
 
52
52
  /**
53
53
  * What the app's `VAULT` service binding calls. Each call gets a database of
54
- * its own, since a Worker's connections belong to the call that made them.
54
+ * its own, one client that its queries take turns on, since a Worker's
55
+ * connections belong to the call that made them; it is closed once the
56
+ * call is done.
55
57
  */
56
58
  export class VaultEntrypoint extends WorkerEntrypoint implements Vault {
57
- async #vault(): Promise<Vault> {
59
+ async #call<T>(run: (vault: Vault) => Promise<T>): Promise<T> {
60
+ const { prepared, database } = await this.#ready();
61
+ const pool = new HyperdrivePool(database.hyperdrive.connectionString);
62
+ try {
63
+ return await run(openVault(createDatabase(pool), prepared));
64
+ } finally {
65
+ this.ctx.waitUntil(pool.end());
66
+ }
67
+ }
68
+
69
+ async #ready(): Promise<{ database: PostgresDatabase; prepared: PreparedVault }> {
58
70
  const env = this.env as object;
59
71
  let entry = ready.get(env);
60
72
  if (entry === undefined) {
@@ -65,21 +77,20 @@ export class VaultEntrypoint extends WorkerEntrypoint implements Vault {
65
77
  entry = ready.get(env) ?? { database: config.database, prepared };
66
78
  ready.set(env, entry);
67
79
  }
68
- const { database, prepared } = entry;
69
- return openVault(createDatabase(new HyperdrivePool(database.hyperdrive.connectionString)), prepared);
80
+ return entry;
70
81
  }
71
82
 
72
- async unwrap(input: UnwrapInput) { return (await this.#vault()).unwrap(input); }
73
- async wrap(input: WrapInput) { return (await this.#vault()).wrap(input); }
74
- async rewrap(input: RewrapInput) { return (await this.#vault()).rewrap(input); }
75
- async access(principal: string) { return (await this.#vault()).access(principal); }
76
- async setAccess(input: SetAccessInput) { return (await this.#vault()).setAccess(input); }
77
- async admit(input: AdmitInput) { return (await this.#vault()).admit(input); }
78
- async remove(input: RemoveInput) { return (await this.#vault()).remove(input); }
79
- async checkpoint() { return (await this.#vault()).checkpoint(); }
80
- async about() { return (await this.#vault()).about(); }
81
- async keyChecks() { return (await this.#vault()).keyChecks(); }
82
- async verifyLog(input: VerifyLogInput) { return (await this.#vault()).verifyLog(input); }
83
+ async unwrap(input: UnwrapInput) { return this.#call((vault) => vault.unwrap(input)); }
84
+ async wrap(input: WrapInput) { return this.#call((vault) => vault.wrap(input)); }
85
+ async rewrap(input: RewrapInput) { return this.#call((vault) => vault.rewrap(input)); }
86
+ async access(principal: string) { return this.#call((vault) => vault.access(principal)); }
87
+ async setAccess(input: SetAccessInput) { return this.#call((vault) => vault.setAccess(input)); }
88
+ async admit(input: AdmitInput) { return this.#call((vault) => vault.admit(input)); }
89
+ async remove(input: RemoveInput) { return this.#call((vault) => vault.remove(input)); }
90
+ async checkpoint() { return this.#call((vault) => vault.checkpoint()); }
91
+ async about() { return this.#call((vault) => vault.about()); }
92
+ async keyChecks() { return this.#call((vault) => vault.keyChecks()); }
93
+ async verifyLog(input: VerifyLogInput) { return this.#call((vault) => vault.verifyLog(input)); }
83
94
 
84
95
  /** No HTTP surface: only the app's service binding reaches the vault. */
85
96
  fetch() {
package/src/vault.ts CHANGED
@@ -884,10 +884,16 @@ class VaultService implements Vault {
884
884
  * it like anyone's. Logged, and made in a transaction of its own, under
885
885
  * the log's lock: a decision that held the head and then waited for a
886
886
  * member row would take the locks out of order. The row never makes
887
- * anyone a root admin; the configuration does.
887
+ * anyone a root admin; the configuration does. Rows are never deleted, so
888
+ * one read finds it made already, as on every fresh isolate but the first,
889
+ * without queueing on the log's lock.
888
890
  */
889
891
  async #rootRow(principal: string): Promise<void> {
890
892
  if (this.#prepared.rooted.has(principal) || (await this.#settled()) !== null) return;
893
+ if ((await store.member(this.#db, principal)) !== undefined) {
894
+ this.#prepared.rooted.add(principal);
895
+ return;
896
+ }
891
897
  await this.#db.transaction(async (tx) => {
892
898
  await lockLogHead(tx);
893
899
  if ((await store.member(tx, principal)) !== undefined) return;