@cortexkit/opencode-openai-auth 0.11.1 → 1.0.1
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/README.md +15 -26
- package/dist/index-766sfrh5.js +1 -0
- package/dist/index-8kj7sa1p.js +12 -0
- package/dist/index-b3q3j39x.js +2 -0
- package/dist/index-bmd7x5aq.js +2 -0
- package/dist/index-eqh53k8v.js +2 -0
- package/dist/{index-sdt2d2ra.js → index-hy9030jh.js} +1 -1
- package/dist/index-jyr8y8k4.js +7 -0
- package/dist/index-rv08jmd4.js +2 -0
- package/dist/index-ww91hyzr.js +221 -0
- package/dist/index.d.ts +578 -145
- package/dist/index.js +1 -110
- package/dist/pool-lifecycle-DbDmgabd.d.ts +2430 -0
- package/dist/port-file-Do3lQIEk.d.ts +29 -0
- package/dist/protocol-BOBdkqqm.d.ts +198 -0
- package/dist/rpc/port-file.d.ts +4 -17
- package/dist/rpc/port-file.js +1 -1
- package/dist/rpc/protocol.d.ts +2 -2
- package/dist/rpc/protocol.js +1 -0
- package/dist/rpc/rpc-client.d.ts +5 -4
- package/dist/rpc/rpc-client.js +1 -1
- package/dist/rpc/rpc-dir.d.ts +2 -0
- package/dist/rpc/rpc-dir.js +1 -1
- package/dist/sidebar-state-D6cUKgkm.d.ts +385 -0
- package/dist/sidebar-state.d.ts +2 -2
- package/dist/sidebar-state.js +1 -1
- package/dist/token-util-wdza526f.js +1 -0
- package/dist/{token-9823y0pc.js → token-ypk61xj0.js} +1 -1
- package/dist/tui-preferences.js +5 -2
- package/dist/tui.d.ts +9 -4
- package/dist/v2/server.d.ts +54 -0
- package/dist/v2/server.js +4 -0
- package/package.json +21 -9
- package/src/tui/entry.mjs +26 -25
- package/src/tui-compiled/raw/logger.js +24 -0
- package/src/tui-compiled/raw/rpc/protocol.js +3 -0
- package/src/tui-compiled/raw/rpc/rpc-client.js +7 -0
- package/src/tui-compiled/raw/rpc/rpc-dir.js +27 -0
- package/src/tui-compiled/raw/selector.js +21 -0
- package/src/tui-compiled/raw/shared/0332701351b3eeec-format.js +138 -0
- package/src/tui-compiled/raw/shared/04a6357627de5ed0-errors.js +38 -0
- package/src/tui-compiled/raw/shared/053cc17b5419261e-codec.js +12 -0
- package/src/tui-compiled/raw/shared/0766d78c6f481f18-watcher.js +157 -0
- package/src/tui-compiled/raw/shared/08eef45626a94992-reserved.js +13 -0
- package/src/tui-compiled/raw/shared/39c3cb05738fcf6d-projection.js +134 -0
- package/src/tui-compiled/raw/shared/3c2d4169d00f907e-tui-support.js +5 -0
- package/src/tui-compiled/raw/shared/3e7d45a77c6ad4d5-index.js +4 -0
- package/src/tui-compiled/raw/shared/415792e85d80819e-capture-sink.js +13 -0
- package/src/tui-compiled/raw/shared/42ae93ab554fcec2-open-url.js +28 -0
- package/src/tui-compiled/raw/shared/43fc9e6b994519c7-refresh-lock.js +136 -0
- package/src/tui-compiled/raw/shared/458b934e92490331-mutate.js +500 -0
- package/src/tui-compiled/raw/shared/45bf5343bbbf9add-admission.js +142 -0
- package/src/tui-compiled/raw/shared/46f3dff1266160e8-client.js +2 -0
- package/src/tui-compiled/raw/shared/4825e03209d6ae3e-with-lock.js +48 -0
- package/src/tui-compiled/raw/shared/4f36d333d081d040-merge.js +162 -0
- package/src/tui-compiled/raw/shared/54eba94fd64c8c3c-index.js +7 -0
- package/src/tui-compiled/raw/shared/7211cfec06a298bc-atomic-write.js +80 -0
- package/src/tui-compiled/raw/shared/754d6017f8e419cf-builtins.js +534 -0
- package/src/tui-compiled/raw/shared/75bd8c3bd63b315f-identity.js +79 -0
- package/src/tui-compiled/raw/shared/7a91bb630e6f4804-index.js +1 -0
- package/src/tui-compiled/raw/shared/826342a4b6a8b89b-model.js +16 -0
- package/src/tui-compiled/raw/shared/83068c0dbe8a6f13-publication.js +304 -0
- package/src/tui-compiled/raw/shared/836fc615de6afbd9-settings.js +120 -0
- package/src/tui-compiled/raw/shared/83bc3ba9d0fe2761-runtime.js +68 -0
- package/src/tui-compiled/raw/shared/8a18abe4fa2977c8-menu.js +300 -0
- package/src/tui-compiled/raw/shared/901e149c5a0dbbff-sticky.js +339 -0
- package/src/tui-compiled/raw/shared/9020c407ce64f672-ordered.js +58 -0
- package/src/tui-compiled/raw/shared/905bffc0220f43d0-map.js +105 -0
- package/src/tui-compiled/raw/shared/94820e324f2b4c09-seam.js +301 -0
- package/src/tui-compiled/raw/shared/97484d9bb98c705b-provider-state.js +226 -0
- package/src/tui-compiled/raw/shared/9968e3cade6969a1-index.js +2 -0
- package/src/tui-compiled/raw/shared/9b4da614c45f9cd0-lock-constants.js +14 -0
- package/src/tui-compiled/raw/shared/9d3fc9e37456504e-refresh.js +225 -0
- package/src/tui-compiled/raw/shared/9deae3c4da4fd63b-hooks.js +34 -0
- package/src/tui-compiled/raw/shared/a09b6a533c11858f-tui-preferences.js +77 -0
- package/src/tui-compiled/raw/shared/a62a27a204266acd-attribution.js +60 -0
- package/src/tui-compiled/raw/shared/a910277ac43a8626-pull.js +143 -0
- package/src/tui-compiled/raw/shared/b85c5301fddcb98a-paths.js +5 -0
- package/src/tui-compiled/raw/shared/c14988621dc60a75-port-file.js +217 -0
- package/src/tui-compiled/raw/shared/c64d7afbc14ca125-sidebar-file.js +157 -0
- package/src/tui-compiled/raw/shared/c6b176708c446d6d-pins.js +34 -0
- package/src/tui-compiled/raw/shared/c7d252bedee01600-pi.js +206 -0
- package/src/tui-compiled/raw/shared/c852a15cfe489d12-pool.js +120 -0
- package/src/tui-compiled/raw/shared/cc9e0702a752cea7-index.js +3 -0
- package/src/tui-compiled/raw/shared/cd94316a888b0a8e-schema.js +754 -0
- package/src/tui-compiled/raw/shared/d319517e3ea6ad70-redact.js +83 -0
- package/src/tui-compiled/raw/shared/d5a8f1061ee2f114-index.js +5 -0
- package/src/tui-compiled/raw/shared/d669d72c40e9e116-logger.js +19 -0
- package/src/tui-compiled/raw/shared/d7dcba96fea1685c-engine.js +246 -0
- package/src/tui-compiled/raw/shared/dbef697d1df1cd84-index.js +5 -0
- package/src/tui-compiled/raw/shared/e4acc4cb713f300c-protocol.js +3 -0
- package/src/tui-compiled/raw/shared/e8cd4929507508d8-refresh-file-lock.js +501 -0
- package/src/tui-compiled/raw/shared/ec971142548df90e-index.js +6 -0
- package/src/tui-compiled/raw/shared/f37fcefc7aedbf25-error.js +11 -0
- package/src/tui-compiled/raw/shared/fbcdcd0f925e74c0-rpc-client.js +253 -0
- package/src/tui-compiled/raw/shared/fd45e82f2f9c0a0d-torn.js +294 -0
- package/src/tui-compiled/raw/shared/fe02abeadc535f77-rows.js +1258 -0
- package/src/tui-compiled/raw/sidebar-state.js +960 -0
- package/src/tui-compiled/raw/tui/command-dialogs.tsx +424 -0
- package/src/tui-compiled/raw/tui-preferences.js +135 -0
- package/src/tui-compiled/raw/tui.tsx +135 -143
- package/src/tui-compiled/runtime/logger.js +24 -0
- package/src/tui-compiled/runtime/rpc/protocol.js +3 -0
- package/src/tui-compiled/runtime/rpc/rpc-client.js +7 -0
- package/src/tui-compiled/runtime/rpc/rpc-dir.js +27 -0
- package/src/tui-compiled/runtime/selector.js +21 -0
- package/src/tui-compiled/runtime/shared/0332701351b3eeec-format.js +138 -0
- package/src/tui-compiled/runtime/shared/04a6357627de5ed0-errors.js +38 -0
- package/src/tui-compiled/runtime/shared/053cc17b5419261e-codec.js +12 -0
- package/src/tui-compiled/runtime/shared/0766d78c6f481f18-watcher.js +157 -0
- package/src/tui-compiled/runtime/shared/08eef45626a94992-reserved.js +13 -0
- package/src/tui-compiled/runtime/shared/39c3cb05738fcf6d-projection.js +134 -0
- package/src/tui-compiled/runtime/shared/3c2d4169d00f907e-tui-support.js +5 -0
- package/src/tui-compiled/runtime/shared/3e7d45a77c6ad4d5-index.js +4 -0
- package/src/tui-compiled/runtime/shared/415792e85d80819e-capture-sink.js +13 -0
- package/src/tui-compiled/runtime/shared/42ae93ab554fcec2-open-url.js +28 -0
- package/src/tui-compiled/runtime/shared/43fc9e6b994519c7-refresh-lock.js +136 -0
- package/src/tui-compiled/runtime/shared/458b934e92490331-mutate.js +500 -0
- package/src/tui-compiled/runtime/shared/45bf5343bbbf9add-admission.js +142 -0
- package/src/tui-compiled/runtime/shared/46f3dff1266160e8-client.js +2 -0
- package/src/tui-compiled/runtime/shared/4825e03209d6ae3e-with-lock.js +48 -0
- package/src/tui-compiled/runtime/shared/4f36d333d081d040-merge.js +162 -0
- package/src/tui-compiled/runtime/shared/54eba94fd64c8c3c-index.js +7 -0
- package/src/tui-compiled/runtime/shared/7211cfec06a298bc-atomic-write.js +80 -0
- package/src/tui-compiled/runtime/shared/754d6017f8e419cf-builtins.js +534 -0
- package/src/tui-compiled/runtime/shared/75bd8c3bd63b315f-identity.js +79 -0
- package/src/tui-compiled/runtime/shared/7a91bb630e6f4804-index.js +1 -0
- package/src/tui-compiled/runtime/shared/826342a4b6a8b89b-model.js +16 -0
- package/src/tui-compiled/runtime/shared/83068c0dbe8a6f13-publication.js +304 -0
- package/src/tui-compiled/runtime/shared/836fc615de6afbd9-settings.js +120 -0
- package/src/tui-compiled/runtime/shared/83bc3ba9d0fe2761-runtime.js +68 -0
- package/src/tui-compiled/runtime/shared/8a18abe4fa2977c8-menu.js +300 -0
- package/src/tui-compiled/runtime/shared/901e149c5a0dbbff-sticky.js +339 -0
- package/src/tui-compiled/runtime/shared/9020c407ce64f672-ordered.js +58 -0
- package/src/tui-compiled/runtime/shared/905bffc0220f43d0-map.js +105 -0
- package/src/tui-compiled/runtime/shared/94820e324f2b4c09-seam.js +301 -0
- package/src/tui-compiled/runtime/shared/97484d9bb98c705b-provider-state.js +226 -0
- package/src/tui-compiled/runtime/shared/9968e3cade6969a1-index.js +2 -0
- package/src/tui-compiled/runtime/shared/9b4da614c45f9cd0-lock-constants.js +14 -0
- package/src/tui-compiled/runtime/shared/9d3fc9e37456504e-refresh.js +225 -0
- package/src/tui-compiled/runtime/shared/9deae3c4da4fd63b-hooks.js +34 -0
- package/src/tui-compiled/runtime/shared/a09b6a533c11858f-tui-preferences.js +77 -0
- package/src/tui-compiled/runtime/shared/a62a27a204266acd-attribution.js +60 -0
- package/src/tui-compiled/runtime/shared/a910277ac43a8626-pull.js +143 -0
- package/src/tui-compiled/runtime/shared/b85c5301fddcb98a-paths.js +5 -0
- package/src/tui-compiled/runtime/shared/c14988621dc60a75-port-file.js +217 -0
- package/src/tui-compiled/runtime/shared/c64d7afbc14ca125-sidebar-file.js +157 -0
- package/src/tui-compiled/runtime/shared/c6b176708c446d6d-pins.js +34 -0
- package/src/tui-compiled/runtime/shared/c7d252bedee01600-pi.js +206 -0
- package/src/tui-compiled/runtime/shared/c852a15cfe489d12-pool.js +120 -0
- package/src/tui-compiled/runtime/shared/cc9e0702a752cea7-index.js +3 -0
- package/src/tui-compiled/runtime/shared/cd94316a888b0a8e-schema.js +754 -0
- package/src/tui-compiled/runtime/shared/d319517e3ea6ad70-redact.js +83 -0
- package/src/tui-compiled/runtime/shared/d5a8f1061ee2f114-index.js +5 -0
- package/src/tui-compiled/runtime/shared/d669d72c40e9e116-logger.js +19 -0
- package/src/tui-compiled/runtime/shared/d7dcba96fea1685c-engine.js +246 -0
- package/src/tui-compiled/runtime/shared/dbef697d1df1cd84-index.js +5 -0
- package/src/tui-compiled/runtime/shared/e4acc4cb713f300c-protocol.js +3 -0
- package/src/tui-compiled/runtime/shared/e8cd4929507508d8-refresh-file-lock.js +501 -0
- package/src/tui-compiled/runtime/shared/ec971142548df90e-index.js +6 -0
- package/src/tui-compiled/runtime/shared/f37fcefc7aedbf25-error.js +11 -0
- package/src/tui-compiled/runtime/shared/fbcdcd0f925e74c0-rpc-client.js +253 -0
- package/src/tui-compiled/runtime/shared/fd45e82f2f9c0a0d-torn.js +294 -0
- package/src/tui-compiled/runtime/shared/fe02abeadc535f77-rows.js +1258 -0
- package/src/tui-compiled/runtime/sidebar-state.js +960 -0
- package/src/tui-compiled/runtime/tui/command-dialogs.js +378 -0
- package/src/tui-compiled/runtime/tui-preferences.js +135 -0
- package/src/tui-compiled/runtime/{tui.tsx → tui.js} +288 -316
- package/dist/index-4effjmr6.js +0 -2
- package/dist/index-9ehhb051.js +0 -2
- package/dist/index-g3hsdjh4.js +0 -2
- package/dist/index-hh52v8sb.js +0 -2
- package/dist/index-jwn4r3sw.js +0 -1
- package/dist/index-sq36b120.js +0 -211
- package/dist/internal-BTndPmW2.d.ts +0 -914
- package/dist/quota-normalize-08hpp51v.js +0 -1
- package/dist/sidebar-state-CoKYKcLn.d.ts +0 -220
- package/dist/token-util-ypgz7rge.js +0 -1
- package/src/tui-compiled/raw/core-account-paths.ts +0 -114
- package/src/tui-compiled/raw/core-custody-state.ts +0 -243
- package/src/tui-compiled/raw/logger.ts +0 -53
- package/src/tui-compiled/raw/rpc-port-file.ts +0 -177
- package/src/tui-compiled/raw/rpc-protocol.ts +0 -12
- package/src/tui-compiled/raw/rpc-rpc-client.ts +0 -82
- package/src/tui-compiled/raw/rpc-rpc-dir.ts +0 -46
- package/src/tui-compiled/raw/sidebar-state.ts +0 -1659
- package/src/tui-compiled/raw/tui-command-dialogs.tsx +0 -1096
- package/src/tui-compiled/raw/tui-preferences.ts +0 -328
- package/src/tui-compiled/runtime/core-account-paths.ts +0 -114
- package/src/tui-compiled/runtime/core-custody-state.ts +0 -243
- package/src/tui-compiled/runtime/logger.ts +0 -53
- package/src/tui-compiled/runtime/rpc-port-file.ts +0 -177
- package/src/tui-compiled/runtime/rpc-protocol.ts +0 -12
- package/src/tui-compiled/runtime/rpc-rpc-client.ts +0 -82
- package/src/tui-compiled/runtime/rpc-rpc-dir.ts +0 -46
- package/src/tui-compiled/runtime/sidebar-state.ts +0 -1659
- package/src/tui-compiled/runtime/tui-command-dialogs.tsx +0 -855
- package/src/tui-compiled/runtime/tui-preferences.ts +0 -328
- package/src/tui-compiled/shared/internal.ts +0 -6
- package/src/tui-compiled/shared/logger.ts +0 -264
- package/src/tui-compiled/shared/paths.ts +0 -33
- package/src/tui-compiled/shared/refresh-file-lock.ts +0 -384
- package/src/tui-compiled/shared/util/error.ts +0 -9
- package/src/tui-compiled/shared/util/open-url.ts +0 -30
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { open } from "node:fs/promises";
|
|
2
|
+
import { connect } from "node:net";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
import { discoverPortFile, portFileIdentity, } from "./c14988621dc60a75-port-file.js";
|
|
5
|
+
export const DEFAULT_RPC_TIMEOUT_MS = 2_000;
|
|
6
|
+
const selections = new Map();
|
|
7
|
+
const discoveries = new Map();
|
|
8
|
+
// The identity a cached selection is checked against must belong to the bytes
|
|
9
|
+
// that named this server. Statting the path after discovery is not enough: the
|
|
10
|
+
// file can be replaced in between, which would pin the old server to the new
|
|
11
|
+
// file's identity. So read and fstat one open descriptor, and bind only when
|
|
12
|
+
// those bytes still name the discovered entry.
|
|
13
|
+
async function boundIdentity(dir, entry) {
|
|
14
|
+
const path = join(resolve(dir), `port-${entry.pid}.json`);
|
|
15
|
+
let handle;
|
|
16
|
+
try {
|
|
17
|
+
handle = await open(path, 'r');
|
|
18
|
+
const info = await handle.stat();
|
|
19
|
+
const current = JSON.parse(await handle.readFile('utf8'));
|
|
20
|
+
if (current.pid !== entry.pid ||
|
|
21
|
+
current.port !== entry.port ||
|
|
22
|
+
current.token !== entry.token)
|
|
23
|
+
return null;
|
|
24
|
+
return `${path}:${info.dev}:${info.ino}:${info.mtimeMs}:${info.size}`;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
finally {
|
|
30
|
+
await handle?.close().catch(() => { });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async function select(key, dir, expectedPid, options) {
|
|
34
|
+
const cached = selections.get(key);
|
|
35
|
+
if (cached && (await portFileIdentity(dir, cached.entry)) === cached.identity)
|
|
36
|
+
return cached;
|
|
37
|
+
selections.delete(key);
|
|
38
|
+
const pending = discoveries.get(key);
|
|
39
|
+
if (pending)
|
|
40
|
+
return pending;
|
|
41
|
+
const discovery = (async () => {
|
|
42
|
+
const entry = await (options.discover ?? discoverPortFile)(dir, expectedPid, options);
|
|
43
|
+
if (!entry)
|
|
44
|
+
return null;
|
|
45
|
+
const identity = await boundIdentity(dir, entry);
|
|
46
|
+
// The file changed under discovery: use what was discovered for this
|
|
47
|
+
// call, as an uncached selection, so the next call discovers again.
|
|
48
|
+
if (!identity)
|
|
49
|
+
return { entry, identity: '' };
|
|
50
|
+
const selected = { entry, identity };
|
|
51
|
+
selections.set(key, selected);
|
|
52
|
+
return selected;
|
|
53
|
+
})();
|
|
54
|
+
discoveries.set(key, discovery);
|
|
55
|
+
try {
|
|
56
|
+
return await discovery;
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
if (discoveries.get(key) === discovery)
|
|
60
|
+
discoveries.delete(key);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async function request(entry, method, params, timeoutMs = DEFAULT_RPC_TIMEOUT_MS) {
|
|
64
|
+
// A raw loopback socket never consults runtime HTTP proxy settings, which
|
|
65
|
+
// otherwise can expose the bearer token to a configured proxy. timeoutMs is
|
|
66
|
+
// a total deadline for connect, request and the full response, not idle time.
|
|
67
|
+
return new Promise((resolve) => {
|
|
68
|
+
let socket;
|
|
69
|
+
let settled = false;
|
|
70
|
+
let connected = false;
|
|
71
|
+
const done = (value, stale = false) => {
|
|
72
|
+
if (settled)
|
|
73
|
+
return;
|
|
74
|
+
settled = true;
|
|
75
|
+
clearTimeout(timer);
|
|
76
|
+
socket?.destroy();
|
|
77
|
+
resolve({ value, stale });
|
|
78
|
+
};
|
|
79
|
+
const timer = setTimeout(() => done(null), timeoutMs);
|
|
80
|
+
try {
|
|
81
|
+
const body = JSON.stringify(params);
|
|
82
|
+
socket = connect({ host: '127.0.0.1', port: entry.port });
|
|
83
|
+
socket.on('connect', () => {
|
|
84
|
+
connected = true;
|
|
85
|
+
// HTTP/1.0 avoids chunked responses; explicitly request connection close
|
|
86
|
+
// because older Bun servers can keep delayed HTTP/1.0 replies open.
|
|
87
|
+
socket?.write(`POST /rpc/${method} HTTP/1.0\r\n` +
|
|
88
|
+
`Host: 127.0.0.1:${entry.port}\r\n` +
|
|
89
|
+
'Connection: close\r\n' +
|
|
90
|
+
'Content-Type: application/json\r\n' +
|
|
91
|
+
`Content-Length: ${Buffer.byteLength(body)}\r\n` +
|
|
92
|
+
`Authorization: Bearer ${entry.token}\r\n\r\n` +
|
|
93
|
+
body);
|
|
94
|
+
});
|
|
95
|
+
const chunks = [];
|
|
96
|
+
let bytes = 0;
|
|
97
|
+
let headerBytes;
|
|
98
|
+
let contentLength;
|
|
99
|
+
let prefix = Buffer.alloc(0);
|
|
100
|
+
const finishBody = () => {
|
|
101
|
+
if (headerBytes === undefined)
|
|
102
|
+
return done(null);
|
|
103
|
+
try {
|
|
104
|
+
const payload = Buffer.concat(chunks).subarray(headerBytes);
|
|
105
|
+
done(JSON.parse(payload.toString('utf8')));
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
done(null);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
socket.on('data', (chunk) => {
|
|
112
|
+
if (settled)
|
|
113
|
+
return;
|
|
114
|
+
bytes += chunk.length;
|
|
115
|
+
chunks.push(chunk);
|
|
116
|
+
if (headerBytes === undefined) {
|
|
117
|
+
prefix = Buffer.concat([prefix, chunk]);
|
|
118
|
+
const separator = prefix.indexOf('\r\n\r\n');
|
|
119
|
+
if (separator >= 0)
|
|
120
|
+
headerBytes = separator + 4;
|
|
121
|
+
if ((headerBytes ?? prefix.length) > 16 * 1024)
|
|
122
|
+
return done(null);
|
|
123
|
+
if (headerBytes !== undefined) {
|
|
124
|
+
const headers = prefix
|
|
125
|
+
.subarray(0, separator)
|
|
126
|
+
.toString('latin1')
|
|
127
|
+
.split('\r\n');
|
|
128
|
+
const status = /^HTTP\/1\.[01] (\d{3})(?: |$)/.exec(headers[0] ?? '');
|
|
129
|
+
if (!status)
|
|
130
|
+
return done(null);
|
|
131
|
+
const code = Number(status[1]);
|
|
132
|
+
if (code < 200 || code >= 300)
|
|
133
|
+
// Only statuses meaning "this is not the server you meant" may
|
|
134
|
+
// rediscover and resend. A 504 is this server's apply deadline:
|
|
135
|
+
// the handler can still be running, so resending it to another
|
|
136
|
+
// server could run the command twice. 5xx is never retried.
|
|
137
|
+
return done(null, [401, 403, 404, 410].includes(code));
|
|
138
|
+
for (const header of headers.slice(1)) {
|
|
139
|
+
const colon = header.indexOf(':');
|
|
140
|
+
const name = header.slice(0, colon).toLowerCase();
|
|
141
|
+
const value = header.slice(colon + 1).trim();
|
|
142
|
+
if (name === 'transfer-encoding')
|
|
143
|
+
return done(null);
|
|
144
|
+
if (name === 'content-length') {
|
|
145
|
+
if (contentLength !== undefined || !/^\d+$/.test(value))
|
|
146
|
+
return done(null);
|
|
147
|
+
contentLength = Number(value);
|
|
148
|
+
if (!Number.isSafeInteger(contentLength) ||
|
|
149
|
+
contentLength > 8 * 1024 * 1024)
|
|
150
|
+
return done(null);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
prefix = Buffer.alloc(0);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const bodyBytes = bytes - (headerBytes ?? bytes);
|
|
157
|
+
if (bodyBytes > 8 * 1024 * 1024)
|
|
158
|
+
return done(null);
|
|
159
|
+
if (contentLength !== undefined) {
|
|
160
|
+
if (bodyBytes > contentLength)
|
|
161
|
+
return done(null);
|
|
162
|
+
// Bun 1.3.14 can keep an async HTTP/1.0 reply open after sending
|
|
163
|
+
// its complete Content-Length body; don't wait for EOF in that case.
|
|
164
|
+
if (bodyBytes === contentLength)
|
|
165
|
+
finishBody();
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
socket.on('end', () => {
|
|
169
|
+
if (contentLength !== undefined &&
|
|
170
|
+
bytes - (headerBytes ?? 0) !== contentLength)
|
|
171
|
+
return done(null);
|
|
172
|
+
finishBody();
|
|
173
|
+
});
|
|
174
|
+
socket.on('error', () => done(null, !connected));
|
|
175
|
+
socket.on('close', () => done(null));
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
done(null, !connected);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
async function call(dir, expectedPid, options, onSelected, method, params, timeoutMs = DEFAULT_RPC_TIMEOUT_MS) {
|
|
183
|
+
const key = JSON.stringify([
|
|
184
|
+
resolve(dir),
|
|
185
|
+
expectedPid ?? null,
|
|
186
|
+
options.exactPid === true,
|
|
187
|
+
]);
|
|
188
|
+
const selected = await select(key, dir, expectedPid, options);
|
|
189
|
+
onSelected(selected?.entry ?? null);
|
|
190
|
+
if (!selected)
|
|
191
|
+
return null;
|
|
192
|
+
const deadline = Date.now() + timeoutMs;
|
|
193
|
+
const out = await request(selected.entry, method, params, timeoutMs);
|
|
194
|
+
if (!out.stale)
|
|
195
|
+
return out.value;
|
|
196
|
+
if (selections.get(key) === selected)
|
|
197
|
+
selections.delete(key);
|
|
198
|
+
// Retry only definite pre-dispatch/stale-server failures, never an ambiguous
|
|
199
|
+
// response or timeout that could replay an already executed apply command.
|
|
200
|
+
const replacement = await select(key, dir, expectedPid, options);
|
|
201
|
+
if (!replacement || Date.now() >= deadline)
|
|
202
|
+
return null;
|
|
203
|
+
if (replacement.entry.port === selected.entry.port &&
|
|
204
|
+
replacement.entry.pid === selected.entry.pid &&
|
|
205
|
+
replacement.entry.token === selected.entry.token) {
|
|
206
|
+
if (selections.get(key) === replacement)
|
|
207
|
+
selections.delete(key);
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
const retry = await request(replacement.entry, method, params, deadline - Date.now());
|
|
211
|
+
if (retry.stale && selections.get(key) === replacement)
|
|
212
|
+
selections.delete(key);
|
|
213
|
+
return retry.value;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* A client for the server in `dir`, preferring `expectedPid`'s.
|
|
217
|
+
*
|
|
218
|
+
* `onSelected` reports the first selection: it is told which entry (or null)
|
|
219
|
+
* discovery chose, until one call of it returns normally. It is a report, not
|
|
220
|
+
* a gate: an observer that throws rejects that call before any request is
|
|
221
|
+
* sent and is asked again on the next call, but nothing stops a later call
|
|
222
|
+
* once an observer has returned. A caller that must never reach another
|
|
223
|
+
* server passes `{ exactPid: true }`. Validated selections are shared by
|
|
224
|
+
* directory, expected PID and exactPid until their file identity changes or a
|
|
225
|
+
* connect/auth/stale-server failure triggers one rediscovery in the same call.
|
|
226
|
+
*/
|
|
227
|
+
export function createRpcClient(dir, expectedPid, onSelected, options = {}) {
|
|
228
|
+
const discoverOptions = {
|
|
229
|
+
exactPid: options.exactPid,
|
|
230
|
+
discover: options.discover,
|
|
231
|
+
};
|
|
232
|
+
let reportedSelection = false;
|
|
233
|
+
const reportSelected = (entry) => {
|
|
234
|
+
if (reportedSelection)
|
|
235
|
+
return;
|
|
236
|
+
onSelected?.(entry);
|
|
237
|
+
// Counted only after the observer returns, so a throwing one is not
|
|
238
|
+
// silently skipped on later calls.
|
|
239
|
+
reportedSelection = true;
|
|
240
|
+
};
|
|
241
|
+
return {
|
|
242
|
+
async pending(lastReceivedId, sessionId, timeoutMs) {
|
|
243
|
+
const out = await call(dir, expectedPid, discoverOptions, reportSelected, 'pending-notifications', { lastReceivedId, sessionId }, timeoutMs);
|
|
244
|
+
return out?.messages ?? [];
|
|
245
|
+
},
|
|
246
|
+
async apply(request, timeoutMs) {
|
|
247
|
+
const out = await call(dir, expectedPid, discoverOptions, reportSelected, 'apply', {
|
|
248
|
+
...request,
|
|
249
|
+
}, timeoutMs);
|
|
250
|
+
return out ?? { text: 'apply failed', knobs: {} };
|
|
251
|
+
},
|
|
252
|
+
};
|
|
253
|
+
}
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { disableIdentityDuplicates, disableIn, enableIn, IDENTITY_CONTRADICTED_REASON_PREFIX, recordIdentityIn, } from "./75bd8c3bd63b315f-identity.js";
|
|
2
|
+
import { buildRawRows, CREDENTIAL_STAMP_KEY, credentialDigest, dispatchDigest, entryIn, isRecord, parseStamp, rosterRowIn, setEntryIn, } from "./cd94316a888b0a8e-schema.js";
|
|
3
|
+
/*
|
|
4
|
+
* A replace changes both files: the state file gets the new credential and
|
|
5
|
+
* the config gets the new epoch, identity or endpoint. It writes the state
|
|
6
|
+
* file first, stamping the credential with the epoch it belongs to and the
|
|
7
|
+
* binding (identity, endpoint) the config is about to get. A crash between
|
|
8
|
+
* the two writes therefore leaves a credential whose stamp is ahead of the
|
|
9
|
+
* config's epoch, and that is how every reader tells such a torn row from a
|
|
10
|
+
* whole one. The stamp holds everything the config write would have written,
|
|
11
|
+
* so a torn row is completed forward: readers are shown the completed row
|
|
12
|
+
* (never a candidate until it is on disk) and the next store write on it
|
|
13
|
+
* writes the config to match.
|
|
14
|
+
*
|
|
15
|
+
* A stamp counts only beside the credential it was written with (its digest
|
|
16
|
+
* matches): a writer that does not know about stamps may put another
|
|
17
|
+
* credential beside an old one, and that stamp then says nothing. A stamp at
|
|
18
|
+
* or behind the config's epoch is never torn as a replace: this store's own
|
|
19
|
+
* writes leave the state file ahead of the config, never behind it.
|
|
20
|
+
*
|
|
21
|
+
* Only a replace's stamp is ever completed as a replace. Since 0.4.4 every
|
|
22
|
+
* stamp carries a binding, so a replace's says so itself (`replace: true`); a
|
|
23
|
+
* stamp written by 0.4.3 or earlier has no dispatch digest, and those
|
|
24
|
+
* versions wrote a binding only on replace, so such a stamp with a binding is
|
|
25
|
+
* a replace's. A stamp from any other write that sits ahead of the config was
|
|
26
|
+
* not left by a crash (those writes keep the row's epoch), and completing it
|
|
27
|
+
* would rewrite the row's identity and drop its quota on a foreign writer's
|
|
28
|
+
* say-so. With `requireCredentialStamps` a 0.4.3-shaped replace stamp is not
|
|
29
|
+
* completed either: it proves nothing about the token sent, so the strict
|
|
30
|
+
* store leaves the row as it is on disk (`legacy`, unbound) rather than act
|
|
31
|
+
* on it; a store without the option completes it as before.
|
|
32
|
+
*
|
|
33
|
+
* A strict store also completes a replace only when its stamp describes what
|
|
34
|
+
* the completed row would send: the stamp's dispatch digest must be that of
|
|
35
|
+
* the credential beside it, with an API key moved to the endpoint the binding
|
|
36
|
+
* names. The lineage digest alone covers the refresh token or API key, so
|
|
37
|
+
* without this check another writer that changed only the access token, the
|
|
38
|
+
* expiry or the endpoint would still have the strict store rewrite the row's
|
|
39
|
+
* epoch, identity and quota before refusing the row as unbound. A store
|
|
40
|
+
* without the option never compared dispatch digests here and still does not.
|
|
41
|
+
*
|
|
42
|
+
* A write that gives a row its first identity (`recordIdentity`, or a
|
|
43
|
+
* `rotate` or refresh that learns one) follows the same order: the stamp
|
|
44
|
+
* naming the identity first, then the config recording it. A crash between
|
|
45
|
+
* leaves a stamp at the row's epoch, matching the credential beside it
|
|
46
|
+
* exactly (digest and dispatch digest), that names an identity the config
|
|
47
|
+
* does not record; that is completed forward the same way, by recording the
|
|
48
|
+
* identity.
|
|
49
|
+
*
|
|
50
|
+
* An attributed `disable` or `enable` that also changes the provider state
|
|
51
|
+
* follows the same order. Its state write carries the new value and, in the
|
|
52
|
+
* stamp, the transition (`transition: {mark, enabled, reason?}`, see
|
|
53
|
+
* `StampedTransition`); its config write then flips the row and records the
|
|
54
|
+
* transition's mark in the row's entry (`transitionMark`). A stamp bound to
|
|
55
|
+
* the row (this credential, epoch and identity, the binding that also shows
|
|
56
|
+
* the provider state beside it) whose transition mark the entry does not
|
|
57
|
+
* record is such a write stopped between its two writes, and is completed
|
|
58
|
+
* forward the same way: readers are shown the row disabled or enabled as the
|
|
59
|
+
* transition says, beside the value written with it. Once the config records
|
|
60
|
+
* the mark the transition says nothing more, so a later `enable` or
|
|
61
|
+
* `disable` of the row is never undone by it; a credential write drops it
|
|
62
|
+
* with the rest of the old stamp.
|
|
63
|
+
*/
|
|
64
|
+
/** Key, inside a credential stamp, of an attributed enable or disable. */
|
|
65
|
+
export const TRANSITION_STAMP_KEY = 'transition';
|
|
66
|
+
/**
|
|
67
|
+
* Key, inside a per-row config entry, of the mark of the last transition the
|
|
68
|
+
* config carries out. Older readers ignore it.
|
|
69
|
+
*/
|
|
70
|
+
export const TRANSITION_MARK_KEY = 'transitionMark';
|
|
71
|
+
/** The well-formed transition in a raw stamp, or undefined. */
|
|
72
|
+
function stampedTransition(rawStamp) {
|
|
73
|
+
if (!isRecord(rawStamp))
|
|
74
|
+
return undefined;
|
|
75
|
+
const raw = rawStamp[TRANSITION_STAMP_KEY];
|
|
76
|
+
if (!isRecord(raw))
|
|
77
|
+
return undefined;
|
|
78
|
+
if (typeof raw.mark !== 'string' || raw.mark.length === 0)
|
|
79
|
+
return undefined;
|
|
80
|
+
if (raw.enabled === true)
|
|
81
|
+
return { mark: raw.mark, enabled: true };
|
|
82
|
+
if (raw.enabled === false && typeof raw.reason === 'string')
|
|
83
|
+
return { mark: raw.mark, enabled: false, reason: raw.reason };
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Carries a transition out in a config being edited: the row's enabled flag
|
|
88
|
+
* and reason as the transition says, and its mark recorded in the entry (a
|
|
89
|
+
* row without an entry gets one at epoch 1, as `disableIn` gives it). An
|
|
90
|
+
* enable then applies the duplicate-identity rule, as every write that
|
|
91
|
+
* enables an OAuth row with a known identity does: the earlier row in roster
|
|
92
|
+
* order keeps the identity.
|
|
93
|
+
*/
|
|
94
|
+
export function applyTransition(editor, id, transition) {
|
|
95
|
+
if (transition.enabled)
|
|
96
|
+
enableIn(editor, id);
|
|
97
|
+
else
|
|
98
|
+
disableIn(editor, id, transition.reason ?? '');
|
|
99
|
+
if (!editor.rosterRow(id))
|
|
100
|
+
return;
|
|
101
|
+
const entry = editor.entry(id) ?? {
|
|
102
|
+
credentialEpoch: 1,
|
|
103
|
+
needsFirstReading: true,
|
|
104
|
+
};
|
|
105
|
+
editor.setEntry(id, { ...entry, [TRANSITION_MARK_KEY]: transition.mark });
|
|
106
|
+
if (!transition.enabled)
|
|
107
|
+
return;
|
|
108
|
+
const row = editor.rows().find((candidate) => candidate.id === id);
|
|
109
|
+
if (row?.type === 'oauth' && row.identity !== undefined)
|
|
110
|
+
disableIdentityDuplicates(editor, row.identity);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* The credential a torn replace leaves once its config write lands: the
|
|
114
|
+
* credential loaded beside the stamp, with the endpoint the stamp's binding
|
|
115
|
+
* names. Only an API key's endpoint lives in the config (an OAuth credential
|
|
116
|
+
* is entirely in the state file, which the replace has already written). A
|
|
117
|
+
* replace that moves a key to a new endpoint leaves the old `baseURL` and
|
|
118
|
+
* `authHeader` in the config until its config write, so the binding's
|
|
119
|
+
* `baseURL` and `authHeader` are the ones the replacement will be sent with.
|
|
120
|
+
*/
|
|
121
|
+
function projectedReplacement(credential, binding) {
|
|
122
|
+
if (credential.type !== 'api')
|
|
123
|
+
return credential;
|
|
124
|
+
return {
|
|
125
|
+
...credential,
|
|
126
|
+
...(binding.baseURL !== undefined ? { baseURL: binding.baseURL } : {}),
|
|
127
|
+
...(binding.authHeader !== undefined
|
|
128
|
+
? { authHeader: binding.authHeader }
|
|
129
|
+
: {}),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/** Whether a well-formed stamp was written by a replace. */
|
|
133
|
+
function isReplaceStamp(stamp) {
|
|
134
|
+
if (!stamp.binding)
|
|
135
|
+
return false;
|
|
136
|
+
return stamp.replace === true || stamp.dispatch === undefined;
|
|
137
|
+
}
|
|
138
|
+
/** Rows left between the two writes of an operation, by row id. */
|
|
139
|
+
export function tornStamps(config, state, codec, options = {}) {
|
|
140
|
+
const accounts = isRecord(state.accounts) ? state.accounts : {};
|
|
141
|
+
const torn = new Map();
|
|
142
|
+
for (const row of buildRawRows(config, state, codec)) {
|
|
143
|
+
if (row.invalid || row.staged || !row.credential)
|
|
144
|
+
continue;
|
|
145
|
+
const account = Object.hasOwn(accounts, row.id)
|
|
146
|
+
? accounts[row.id]
|
|
147
|
+
: undefined;
|
|
148
|
+
if (!isRecord(account))
|
|
149
|
+
continue;
|
|
150
|
+
const stamp = parseStamp(account[CREDENTIAL_STAMP_KEY]);
|
|
151
|
+
if (!stamp?.binding)
|
|
152
|
+
continue;
|
|
153
|
+
if (stamp.digest !== credentialDigest(row.credential))
|
|
154
|
+
continue;
|
|
155
|
+
// A row without an entry is at epoch 1, as everywhere else.
|
|
156
|
+
const epoch = row.credentialEpoch ?? 1;
|
|
157
|
+
if (stamp.credentialEpoch > epoch) {
|
|
158
|
+
if (!isReplaceStamp(stamp))
|
|
159
|
+
continue;
|
|
160
|
+
if (options.requireCredentialStamps) {
|
|
161
|
+
if (stamp.dispatch === undefined)
|
|
162
|
+
continue;
|
|
163
|
+
// `digest` covers only the refresh token or API key, so another
|
|
164
|
+
// writer may still have changed the access token, the expiry or the
|
|
165
|
+
// endpoint the stamp names. `dispatch` covers all of what a request
|
|
166
|
+
// sends; completing the replace when it disagrees would move the
|
|
167
|
+
// row's epoch, identity and quota on a stamp that does not describe
|
|
168
|
+
// the credential. So the replacement as it would be sent (the loaded
|
|
169
|
+
// credential with the binding's endpoint) must have the stamp's
|
|
170
|
+
// dispatch digest; otherwise the row stays as on disk (unbound).
|
|
171
|
+
if (stamp.dispatch !==
|
|
172
|
+
dispatchDigest(projectedReplacement(row.credential, stamp.binding)))
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
torn.set(row.id, {
|
|
176
|
+
kind: 'replace',
|
|
177
|
+
stamp: { ...stamp, binding: stamp.binding },
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
else if (stamp.credentialEpoch === epoch &&
|
|
181
|
+
stamp.dispatch !== undefined &&
|
|
182
|
+
stamp.dispatch === dispatchDigest(row.credential) &&
|
|
183
|
+
stamp.binding.identity !== undefined &&
|
|
184
|
+
row.identity === undefined) {
|
|
185
|
+
torn.set(row.id, { kind: 'identity', identity: stamp.binding.identity });
|
|
186
|
+
}
|
|
187
|
+
else if (stamp.credentialEpoch === epoch &&
|
|
188
|
+
stamp.binding.identity === row.identity) {
|
|
189
|
+
// The lineage digest alone misses an access-token or expiry edit. A
|
|
190
|
+
// strict store must not repair the config from a transition whose stamp
|
|
191
|
+
// no longer describes the credential; the row stays unbound instead.
|
|
192
|
+
if (options.requireCredentialStamps &&
|
|
193
|
+
stamp.dispatch !== dispatchDigest(row.credential))
|
|
194
|
+
continue;
|
|
195
|
+
const transition = stampedTransition(account[CREDENTIAL_STAMP_KEY]);
|
|
196
|
+
if (transition &&
|
|
197
|
+
entryIn(config, row.id)?.[TRANSITION_MARK_KEY] !== transition.mark)
|
|
198
|
+
torn.set(row.id, { kind: 'transition', transition });
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return torn;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* The config half of a replacement: the row's entry moves to the new epoch,
|
|
205
|
+
* loses its quota and needs a first reading; its roster row gets the new
|
|
206
|
+
* identity (or loses the old one) and, for an API key, the new endpoint.
|
|
207
|
+
* Disabling later rows that share the new identity is left to the caller
|
|
208
|
+
* (`disableIdentityDuplicates`), so several rows can be bound first.
|
|
209
|
+
*/
|
|
210
|
+
export function bindReplacement(editor, id, credentialEpoch, binding) {
|
|
211
|
+
const entry = editor.entry(id) ?? {};
|
|
212
|
+
const next = {
|
|
213
|
+
...entry,
|
|
214
|
+
credentialEpoch,
|
|
215
|
+
needsFirstReading: true,
|
|
216
|
+
};
|
|
217
|
+
delete next.quota;
|
|
218
|
+
// An explicit identity supplied with a replacement is the adapter's validated
|
|
219
|
+
// account binding. Clear its quarantine on completion, including crash recovery,
|
|
220
|
+
// but leave the row disabled until a separate enable.
|
|
221
|
+
if (binding.identity !== undefined &&
|
|
222
|
+
typeof next.disabledReason === 'string' &&
|
|
223
|
+
next.disabledReason.startsWith(IDENTITY_CONTRADICTED_REASON_PREFIX))
|
|
224
|
+
delete next.disabledReason;
|
|
225
|
+
editor.setEntry(id, next);
|
|
226
|
+
const raw = editor.rosterRow(id);
|
|
227
|
+
if (!raw)
|
|
228
|
+
return;
|
|
229
|
+
if (binding.identity !== undefined)
|
|
230
|
+
raw.accountId = binding.identity;
|
|
231
|
+
else
|
|
232
|
+
delete raw.accountId;
|
|
233
|
+
if (binding.baseURL !== undefined)
|
|
234
|
+
raw.baseURL = binding.baseURL;
|
|
235
|
+
if (binding.authHeader !== undefined)
|
|
236
|
+
raw.authHeader = binding.authHeader;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* The config with every torn row completed as its interrupted write would
|
|
240
|
+
* have left it, and the ids completed. The config passed in is not modified;
|
|
241
|
+
* when nothing is torn it is returned as is.
|
|
242
|
+
*/
|
|
243
|
+
export function completeTornRows(config, state, codec, options = {}) {
|
|
244
|
+
const completions = tornStamps(config, state, codec, options);
|
|
245
|
+
if (completions.size === 0)
|
|
246
|
+
return { config, torn: [] };
|
|
247
|
+
const next = structuredClone(config);
|
|
248
|
+
const editor = {
|
|
249
|
+
rows: () => buildRawRows(next, state, codec),
|
|
250
|
+
rosterRow: (id) => rosterRowIn(next, id),
|
|
251
|
+
entry: (id) => entryIn(next, id),
|
|
252
|
+
setEntry: (id, entry) => setEntryIn(next, id, entry),
|
|
253
|
+
};
|
|
254
|
+
for (const [id, completion] of completions)
|
|
255
|
+
if (completion.kind === 'replace')
|
|
256
|
+
bindReplacement(editor, id, completion.stamp.credentialEpoch, completion.stamp.binding);
|
|
257
|
+
for (const [id, completion] of completions) {
|
|
258
|
+
if (completion.kind === 'identity')
|
|
259
|
+
recordIdentityIn(editor, id, completion.identity);
|
|
260
|
+
else if (completion.kind === 'transition')
|
|
261
|
+
applyTransition(editor, id, completion.transition);
|
|
262
|
+
else if (completion.stamp.binding.identity !== undefined)
|
|
263
|
+
disableIdentityDuplicates(editor, completion.stamp.binding.identity);
|
|
264
|
+
}
|
|
265
|
+
return { config: next, torn: [...completions.keys()] };
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* The rows every reader gets. A torn row is shown completed (the identity,
|
|
269
|
+
* endpoint and epoch its stamp names, beside the credential it stamps), is
|
|
270
|
+
* marked `torn` and is never a candidate; every other row is as on disk.
|
|
271
|
+
* With `requireCredentialStamps`, a row whose stamp is not `bound` is marked
|
|
272
|
+
* `unbound` and is never a candidate either. A torn row is shown with the
|
|
273
|
+
* stamp of the interrupted write, which binds the completed row, so it is not
|
|
274
|
+
* unbound; once a store write puts its completion on disk it is no longer
|
|
275
|
+
* torn and is a candidate again like any other row.
|
|
276
|
+
*/
|
|
277
|
+
export function loadRows(config, state, codec, options = {}) {
|
|
278
|
+
const { config: whole, torn } = completeTornRows(config, state, codec, options);
|
|
279
|
+
// A torn replace wrote its provider state beside the new credential, under
|
|
280
|
+
// the stamp naming the new epoch, so the completed row shows the new
|
|
281
|
+
// credential with the state written for it, never with the old one.
|
|
282
|
+
const rows = buildRawRows(whole, state, codec, options.providerState);
|
|
283
|
+
for (const row of rows) {
|
|
284
|
+
if (torn.includes(row.id)) {
|
|
285
|
+
row.torn = true;
|
|
286
|
+
row.candidate = false;
|
|
287
|
+
}
|
|
288
|
+
if (options.requireCredentialStamps && row.stamp !== 'bound') {
|
|
289
|
+
row.unbound = true;
|
|
290
|
+
row.candidate = false;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return rows;
|
|
294
|
+
}
|