@coffre/vault 0.12.1 → 0.14.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.
@@ -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-B0d-W-SI.js";
2
- import { n as prepareVault, t as openVault } from "./vault-DEjPmLNt.js";
2
+ import { n as prepareVault, t as openVault } from "./vault-C5n9wSvj.js";
3
3
  import { createDatabase } from "@coffre/db";
4
4
  import { HyperdrivePool, postgres } from "@coffre/db/hyperdrive";
5
5
  import { WorkerEntrypoint } from "cloudflare:workers";
package/dist/node.js CHANGED
@@ -1,8 +1,41 @@
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-B0d-W-SI.js";
2
- import { n as prepareVault, t as openVault } from "./vault-DEjPmLNt.js";
2
+ import { n as prepareVault, t as openVault } from "./vault-C5n9wSvj.js";
3
+ import { isUnavailable } from "@coffre/db/dialect";
3
4
  import { chmodSync, existsSync, lstatSync, rmSync } from "node:fs";
4
5
  import { Agent, createServer, request } from "node:http";
5
6
  import { openDatabase } from "@coffre/db/connect";
7
+ //#region src/failure.ts
8
+ /**
9
+ * A vault call's failure, as `serveVault` answers it: its message, and the
10
+ * code its causes carry, which `connectVault` puts back on its error, so
11
+ * the app reads it as it would in process. Its database, or a key service,
12
+ * not answering is the app's outage too, not a bug: 503. One only its
13
+ * words say (pg's "Connection terminated") goes as Postgres's code for a
14
+ * connection that failed, 08006.
15
+ */
16
+ function failureOf(error) {
17
+ const message = error instanceof Error ? error.message : "the vault failed";
18
+ let code;
19
+ for (let cause = error; typeof cause === "object" && cause !== null && code === void 0; cause = cause.cause) {
20
+ const found = cause.code;
21
+ if (typeof found === "string") code = found;
22
+ }
23
+ if (!isUnavailable(error)) return {
24
+ status: 500,
25
+ body: code === void 0 ? { error: message } : {
26
+ error: message,
27
+ code
28
+ }
29
+ };
30
+ return {
31
+ status: 503,
32
+ body: {
33
+ error: message,
34
+ code: code !== void 0 && isUnavailable({ code }) ? code : "08006"
35
+ }
36
+ };
37
+ }
38
+ //#endregion
6
39
  //#region src/local.ts
7
40
  /** Every call the vault answers, in the order of the `Vault` interface. */
8
41
  const METHODS = [
@@ -89,7 +122,8 @@ async function serveVault(config) {
89
122
  reply(200, result === void 0 ? {} : { result });
90
123
  } catch (error) {
91
124
  console.error(`vault ${name} failed`, error);
92
- reply(500, { error: error instanceof Error ? error.message : "the vault failed" });
125
+ const { status, body } = failureOf(error);
126
+ reply(status, body);
93
127
  }
94
128
  });
95
129
  server.keepAliveTimeout = 0;
@@ -127,11 +161,12 @@ function connectVault(socket) {
127
161
  }, (res) => {
128
162
  readBody(res).then((text) => {
129
163
  const payload = JSON.parse(text);
130
- if (res.statusCode === 200) resolve(payload.result);
131
- else reject(/* @__PURE__ */ new Error(`vault ${name}: ${payload.error ?? `status ${res.statusCode}`}`));
164
+ if (res.statusCode === 200) return resolve(payload.result);
165
+ const failed = /* @__PURE__ */ new Error(`vault ${name}: ${payload.error ?? `status ${res.statusCode}`}`);
166
+ reject(payload.code === void 0 ? failed : Object.assign(failed, { code: payload.code }));
132
167
  }, reject);
133
168
  });
134
- req.on("error", (error) => reject(/* @__PURE__ */ new Error(`the vault at ${socket} is unreachable: ${error.message}`)));
169
+ req.on("error", (error) => reject(new Error(`the vault at ${socket} is unreachable: ${error.message}`, { cause: error.code === "ENOENT" ? Object.assign(error, { code: "ECONNREFUSED" }) : error })));
135
170
  req.end(body);
136
171
  });
137
172
  return Object.fromEntries(METHODS.map((name) => [name, call(name)]));
@@ -6,7 +6,7 @@ import { DEFAULT_POLICY, EVERYWHERE, EVERY_ENVIRONMENT, FIXED_ROLES, INSTANCE_RO
6
6
  import { GENESIS_HASH, deriveLogKey, verifyEntries } from "@coffre/core/audit";
7
7
  import { checkContext, ownerKey } from "@coffre/core/envelope";
8
8
  import { TOMBSTONE, shownMember } from "@coffre/core/schemas";
9
- import { SNAPSHOT, clockMillis, engineOf, forUpdate, isUniqueViolation, isUnreachable, rowsOf, tombstone, truth } from "@coffre/db/dialect";
9
+ import { SNAPSHOT, clockMillis, engineOf, forUpdate, isUnavailable, isUniqueViolation, rowsOf, tombstone, truth } from "@coffre/db/dialect";
10
10
  import { accessEntriesAbout, everyProjectConversion, insertGrant, liveProjects, newestAccess, readGrants, reportStands, roleColumns, storedRole } from "@coffre/db/grants";
11
11
  import { LogHeadMismatch, appendEntries, entryColumns, lockLogHead, missingEntry, newestAuthentic, newestVaultEntries, withinBytes } from "@coffre/db/log";
12
12
  import { and, asc, desc, eq, gt, gte, inArray, isNotNull, isNull, lt, lte, or, sql } from "drizzle-orm";
@@ -4019,7 +4019,7 @@ var VaultService = class {
4019
4019
  if (walked.ok) try {
4020
4020
  scrub = await this.#db.transaction((tx) => this.#scrub(tx, walked.from), SNAPSHOT);
4021
4021
  } catch (error) {
4022
- if (!isUnreachable(error) && !timedOut(error)) throw error;
4022
+ if (!isUnavailable(error)) throw error;
4023
4023
  stalled = `the rehash of the past did not run, and goes on where it was next time: ${error.message}`;
4024
4024
  }
4025
4025
  return this.#decide([], async (d) => {
@@ -4438,11 +4438,6 @@ var VaultService = class {
4438
4438
  return null;
4439
4439
  }
4440
4440
  };
4441
- /** Whether a statement was cancelled for taking too long: Postgres's `query_canceled`, a statement timeout among them. */
4442
- function timedOut(error) {
4443
- for (let cause = error; typeof cause === "object" && cause !== null; cause = cause.cause) if (cause.code === "57014") return true;
4444
- return false;
4445
- }
4446
4441
  const FIRST_ENTRY = {
4447
4442
  ok: true,
4448
4443
  entry: null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coffre/vault",
3
- "version": "0.12.1",
3
+ "version": "0.14.0",
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.12.1",
40
- "@coffre/db": "0.12.1"
39
+ "@coffre/core": "0.14.0",
40
+ "@coffre/db": "0.14.0"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/node": "26.1.1",
package/src/failure.ts ADDED
@@ -0,0 +1,20 @@
1
+ import { isUnavailable } from '@coffre/db/dialect';
2
+
3
+ /**
4
+ * A vault call's failure, as `serveVault` answers it: its message, and the
5
+ * code its causes carry, which `connectVault` puts back on its error, so
6
+ * the app reads it as it would in process. Its database, or a key service,
7
+ * not answering is the app's outage too, not a bug: 503. One only its
8
+ * words say (pg's "Connection terminated") goes as Postgres's code for a
9
+ * connection that failed, 08006.
10
+ */
11
+ export function failureOf(error: unknown): { status: number; body: { error: string; code?: string } } {
12
+ const message = error instanceof Error ? error.message : 'the vault failed';
13
+ let code: string | undefined;
14
+ for (let cause = error; typeof cause === 'object' && cause !== null && code === undefined; cause = (cause as { cause?: unknown }).cause) {
15
+ const found = (cause as { code?: unknown }).code;
16
+ if (typeof found === 'string') code = found;
17
+ }
18
+ if (!isUnavailable(error)) return { status: 500, body: code === undefined ? { error: message } : { error: message, code } };
19
+ return { status: 503, body: { error: message, code: code !== undefined && isUnavailable({ code }) ? code : '08006' } };
20
+ }
package/src/node.ts CHANGED
@@ -22,6 +22,7 @@ import type { Database } from '@coffre/db';
22
22
  import { openDatabase } from '@coffre/db/connect';
23
23
 
24
24
  import { resolveVaultConfig, type VaultConfig } from './config.ts';
25
+ import { failureOf } from './failure.ts';
25
26
  import { METHODS, openLocalVault, type LocalVault } from './local.ts';
26
27
  import type { VaultOptions } from './vault.ts';
27
28
 
@@ -75,7 +76,8 @@ export async function serveVault(config: NodeVaultConfig & { socket: string }):
75
76
  reply(200, result === undefined ? {} : { result });
76
77
  } catch (error) {
77
78
  console.error(`vault ${name} failed`, error);
78
- reply(500, { error: error instanceof Error ? error.message : 'the vault failed' });
79
+ const { status, body } = failureOf(error);
80
+ reply(status, body);
79
81
  }
80
82
  });
81
83
 
@@ -123,13 +125,16 @@ export function connectVault(socket: string): Vault {
123
125
  },
124
126
  (res) => {
125
127
  readBody(res).then((text) => {
126
- const payload = JSON.parse(text) as { result?: unknown; error?: string };
127
- if (res.statusCode === 200) resolve(payload.result);
128
- else reject(new Error(`vault ${name}: ${payload.error ?? `status ${res.statusCode}`}`));
128
+ const payload = JSON.parse(text) as { result?: unknown; error?: string; code?: string };
129
+ if (res.statusCode === 200) return resolve(payload.result);
130
+ const failed = new Error(`vault ${name}: ${payload.error ?? `status ${res.statusCode}`}`);
131
+ reject(payload.code === undefined ? failed : Object.assign(failed, { code: payload.code }));
129
132
  }, reject);
130
133
  },
131
134
  );
132
- req.on('error', (error) => reject(new Error(`the vault at ${socket} is unreachable: ${error.message}`)));
135
+ // No socket file is a vault that stopped cleanly, as a refused connection is one that did not: the same outage.
136
+ req.on('error', (error: NodeJS.ErrnoException) =>
137
+ reject(new Error(`the vault at ${socket} is unreachable: ${error.message}`, { cause: error.code === 'ENOENT' ? Object.assign(error, { code: 'ECONNREFUSED' }) : error })));
133
138
  req.end(body);
134
139
  });
135
140
  return Object.fromEntries(METHODS.map((name) => [name, call(name)])) as unknown as Vault;
package/src/vault.ts CHANGED
@@ -98,7 +98,7 @@ import {
98
98
  type WrappedKey,
99
99
  } from '@coffre/core/vault';
100
100
  import type { Database, Queryable, Transaction } from '@coffre/db';
101
- import { isUniqueViolation, isUnreachable, SNAPSHOT } from '@coffre/db/dialect';
101
+ import { isUnavailable, isUniqueViolation, SNAPSHOT } from '@coffre/db/dialect';
102
102
  import { everyProjectConversion, newestAccess, reportStands } from '@coffre/db/grants';
103
103
  import { appendEntries, lockLogHead, missingEntry, newestAuthentic, type Appended, type Authenticity, type Newest, type NewEntry } from '@coffre/db/log';
104
104
 
@@ -2550,7 +2550,7 @@ class VaultService implements Vault {
2550
2550
  scrub = await this.#db.transaction((tx) => this.#scrub(tx, walked.from), SNAPSHOT);
2551
2551
  } catch (error) {
2552
2552
  // Only the database not answering, or a statement it gave up on: anything else is coffre's to fix.
2553
- if (!isUnreachable(error) && !timedOut(error)) throw error;
2553
+ if (!isUnavailable(error)) throw error;
2554
2554
  stalled = `the rehash of the past did not run, and goes on where it was next time: ${(error as Error).message}`;
2555
2555
  }
2556
2556
  }
@@ -2913,14 +2913,6 @@ type Resumed = {
2913
2913
  /** A lap of the rehash of the past: the last entry rehashed, and the prefix it rehashes to, signed when it began. */
2914
2914
  type Lap = { at: Position; end: Position };
2915
2915
 
2916
- /** Whether a statement was cancelled for taking too long: Postgres's `query_canceled`, a statement timeout among them. */
2917
- function timedOut(error: unknown): boolean {
2918
- for (let cause = error; typeof cause === 'object' && cause !== null; cause = (cause as { cause?: unknown }).cause) {
2919
- if ((cause as { code?: unknown }).code === '57014') return true;
2920
- }
2921
- return false;
2922
- }
2923
-
2924
2916
  /** Where the next rehash of the past goes on from, null for the first entry; or what it found. */
2925
2917
  type Scrubbed = { ok: true; next: Lap | null } | Broken;
2926
2918