@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.
- package/dist/cloudflare.js +1 -1
- package/dist/node.js +40 -5
- package/dist/{vault-DEjPmLNt.js → vault-C5n9wSvj.js} +2 -7
- package/package.json +3 -3
- package/src/failure.ts +20 -0
- package/src/node.ts +10 -5
- package/src/vault.ts +2 -10
package/dist/cloudflare.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-B0d-W-SI.js";
|
|
2
|
-
import { n as prepareVault, t as openVault } from "./vault-
|
|
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-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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,
|
|
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 (!
|
|
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.
|
|
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.
|
|
40
|
-
"@coffre/db": "0.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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 (!
|
|
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
|
|