@creatornader/agent-bridge 0.3.1 → 0.5.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/CHANGELOG.md +222 -1
- package/README.md +312 -12
- package/ROADMAP.md +88 -7
- package/SECURITY.md +78 -5
- package/SKILL.md +108 -1
- package/dist/archive.d.ts +2 -2
- package/dist/archive.js +2 -2
- package/dist/{bridge-service-hX1ZQp4W.d.ts → bridge-service-CNBKdYXh.d.ts} +2 -1
- package/dist/{bridge-store-DPVSCwfs.d.ts → bridge-store-ByJ6CaJk.d.ts} +3 -0
- package/dist/bridge.d.ts +25 -8
- package/dist/bridge.js +19 -9
- package/dist/{chunk-MBPZXTJA.js → chunk-7VJI2MXI.js} +285 -42
- package/dist/chunk-7VJI2MXI.js.map +1 -0
- package/dist/{chunk-4VB44KJU.js → chunk-AWE3DFZV.js} +3 -2
- package/dist/chunk-AWE3DFZV.js.map +1 -0
- package/dist/{chunk-GADH244D.js → chunk-E4OWHGUE.js} +16 -5
- package/dist/chunk-E4OWHGUE.js.map +1 -0
- package/dist/{chunk-NCO7RLBM.js → chunk-GTK4KOED.js} +108 -8
- package/dist/chunk-GTK4KOED.js.map +1 -0
- package/dist/{chunk-SPSMZOCJ.js → chunk-HUHG7XTB.js} +6 -5
- package/dist/chunk-HUHG7XTB.js.map +1 -0
- package/dist/{chunk-5GMB3A77.js → chunk-IVG4XWWS.js} +26 -14
- package/dist/chunk-IVG4XWWS.js.map +1 -0
- package/dist/{chunk-WAI2IYIQ.js → chunk-P635FAN4.js} +50 -7
- package/dist/chunk-P635FAN4.js.map +1 -0
- package/dist/{chunk-45BZGB77.js → chunk-QNXHDOJ5.js} +218 -24
- package/dist/chunk-QNXHDOJ5.js.map +1 -0
- package/dist/{chunk-UAHMP6GC.js → chunk-QPC6JHA3.js} +10 -7
- package/dist/chunk-QPC6JHA3.js.map +1 -0
- package/dist/{chunk-Q6PPMXJC.js → chunk-SN7PIVDF.js} +377 -21
- package/dist/chunk-SN7PIVDF.js.map +1 -0
- package/dist/{chunk-A6W4QALF.js → chunk-TC6A7GMF.js} +2 -2
- package/dist/{chunk-2DFNOLMO.js → chunk-XDNT6ZNZ.js} +9 -6
- package/dist/chunk-XDNT6ZNZ.js.map +1 -0
- package/dist/{chunk-CEXBMOI5.js → chunk-Z6JP6MYD.js} +2 -2
- package/dist/cli.js +5867 -438
- package/dist/cli.js.map +1 -1
- package/dist/client-runtime.d.ts +4 -2
- package/dist/client-runtime.js +7 -7
- package/dist/contracts/generate.js +1 -1
- package/dist/dr.d.ts +9 -2
- package/dist/dr.js +6 -4
- package/dist/{gateway-CggQ8vTC.d.ts → gateway-CW-61lvs.d.ts} +27 -3
- package/dist/gateway-main.js +71 -7
- package/dist/gateway-main.js.map +1 -1
- package/dist/gateway.d.ts +3 -3
- package/dist/gateway.js +3 -3
- package/dist/index.js +8 -8
- package/dist/{postgres-bridge-store-Bfd3hRId.d.ts → postgres-bridge-store-C9KG9Z69.d.ts} +1 -1
- package/dist/postgres-production-preflight.d.ts +32 -0
- package/dist/postgres-production-preflight.js +249 -0
- package/dist/postgres-production-preflight.js.map +1 -0
- package/dist/server.js +8 -8
- package/dist/sqlite-edge-store-CdMIOrho.d.ts +148 -0
- package/dist/sqlite.d.ts +19 -80
- package/dist/sqlite.js +11 -3
- package/docs/architecture-v2.md +292 -15
- package/docs/decisions/0004-client-lifecycle-and-endpoint-migration.md +204 -0
- package/docs/deployment.md +349 -0
- package/docs/troubleshooting.md +23 -0
- package/llms.txt +69 -1
- package/openapi/agent-bridge-v2.openapi.json +1151 -124
- package/package.json +10 -2
- package/schemas/agent-bridge-v2.capabilities.json +26 -1
- package/schemas/agent-bridge-v2.mcp.json +11 -1
- package/schemas/agent-bridge-v2.schema.json +167 -0
- package/sql/migrations/007_runtime_role.sql +8 -4
- package/sql/migrations/013_row_isolation.sql +71 -8
- package/sql/migrations/014_owner_control_plane.sql +124 -31
- package/sql/migrations/015_portable_archives.sql +143 -41
- package/sql/migrations/016_native_dr_fence.sql +102 -35
- package/sql/migrations/017_gateway_authority_binding.sql +99 -0
- package/sql/migrations/018_endpoint_migration_challenges.sql +1054 -0
- package/sql/migrations/019_endpoint_migration_same_successor.sql +348 -0
- package/sql/migrations/020_managed_authority_compat.sql +698 -0
- package/sql/migrations/021_native_backup_reader.sql +421 -0
- package/sql/setup.sql +1 -3
- package/dist/chunk-2DFNOLMO.js.map +0 -1
- package/dist/chunk-45BZGB77.js.map +0 -1
- package/dist/chunk-4VB44KJU.js.map +0 -1
- package/dist/chunk-5GMB3A77.js.map +0 -1
- package/dist/chunk-GADH244D.js.map +0 -1
- package/dist/chunk-MBPZXTJA.js.map +0 -1
- package/dist/chunk-NCO7RLBM.js.map +0 -1
- package/dist/chunk-Q6PPMXJC.js.map +0 -1
- package/dist/chunk-SPSMZOCJ.js.map +0 -1
- package/dist/chunk-UAHMP6GC.js.map +0 -1
- package/dist/chunk-WAI2IYIQ.js.map +0 -1
- /package/dist/{chunk-A6W4QALF.js.map → chunk-TC6A7GMF.js.map} +0 -0
- /package/dist/{chunk-CEXBMOI5.js.map → chunk-Z6JP6MYD.js.map} +0 -0
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
SQLITE_INITIALIZATION_BUSY_TIMEOUT_MS,
|
|
3
4
|
assertEdgeUpgradeCandidate,
|
|
4
5
|
installEdgeMarkers,
|
|
5
6
|
retrySqliteBusy
|
|
6
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-IVG4XWWS.js";
|
|
7
8
|
import {
|
|
8
9
|
preparePrivateSqliteLocation,
|
|
9
10
|
securePrivatePath,
|
|
11
|
+
securePrivateSqliteSidecar,
|
|
10
12
|
verifyPrivatePathAccess
|
|
11
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-GTK4KOED.js";
|
|
12
14
|
import {
|
|
13
15
|
cursorScope,
|
|
14
16
|
decodeCursor,
|
|
@@ -19,11 +21,16 @@ import {
|
|
|
19
21
|
import { createHash, randomUUID } from "crypto";
|
|
20
22
|
import { existsSync, lstatSync } from "fs";
|
|
21
23
|
import { createRequire } from "module";
|
|
24
|
+
import { pathToFileURL } from "url";
|
|
22
25
|
var require2 = createRequire(import.meta.url);
|
|
23
26
|
function openDatabase(path) {
|
|
24
27
|
const { DatabaseSync } = require2("node:sqlite");
|
|
25
28
|
return new DatabaseSync(path);
|
|
26
29
|
}
|
|
30
|
+
function openReadOnlyDatabase(path) {
|
|
31
|
+
const { DatabaseSync } = require2("node:sqlite");
|
|
32
|
+
return new DatabaseSync(`${pathToFileURL(path).toString()}?immutable=1`, { readOnly: true });
|
|
33
|
+
}
|
|
27
34
|
var MAX_SEQUENCE = 9223372036854775807n;
|
|
28
35
|
var schema = `
|
|
29
36
|
PRAGMA journal_mode = WAL;
|
|
@@ -42,6 +49,21 @@ CREATE TABLE IF NOT EXISTS edge_scopes (
|
|
|
42
49
|
last_attempt_at TEXT,
|
|
43
50
|
cache_contract INTEGER NOT NULL DEFAULT 1
|
|
44
51
|
);
|
|
52
|
+
CREATE TABLE IF NOT EXISTS edge_migration_gates (
|
|
53
|
+
scope_key TEXT PRIMARY KEY REFERENCES edge_scopes(scope_key),
|
|
54
|
+
state TEXT NOT NULL CHECK(state IN ('active', 'draining', 'retired')),
|
|
55
|
+
operation_id TEXT,
|
|
56
|
+
lease_token TEXT,
|
|
57
|
+
lease_expires_at TEXT,
|
|
58
|
+
updated_at TEXT NOT NULL,
|
|
59
|
+
CHECK(
|
|
60
|
+
(state='active' AND operation_id IS NULL AND lease_token IS NULL AND lease_expires_at IS NULL)
|
|
61
|
+
OR (state='draining' AND operation_id IS NOT NULL AND lease_token IS NOT NULL AND lease_expires_at IS NOT NULL)
|
|
62
|
+
OR (state='retired' AND operation_id IS NOT NULL AND lease_token IS NULL AND lease_expires_at IS NULL)
|
|
63
|
+
)
|
|
64
|
+
);
|
|
65
|
+
CREATE INDEX IF NOT EXISTS edge_migration_gates_state
|
|
66
|
+
ON edge_migration_gates(state, lease_expires_at);
|
|
45
67
|
CREATE TABLE IF NOT EXISTS edge_outbox (
|
|
46
68
|
position INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
47
69
|
scope_key TEXT NOT NULL REFERENCES edge_scopes(scope_key),
|
|
@@ -62,6 +84,15 @@ CREATE TABLE IF NOT EXISTS edge_outbox (
|
|
|
62
84
|
);
|
|
63
85
|
CREATE INDEX IF NOT EXISTS edge_outbox_due
|
|
64
86
|
ON edge_outbox(scope_key, state, available_at, position);
|
|
87
|
+
CREATE TRIGGER IF NOT EXISTS edge_outbox_migration_gate_insert
|
|
88
|
+
BEFORE INSERT ON edge_outbox
|
|
89
|
+
WHEN NOT EXISTS (
|
|
90
|
+
SELECT 1 FROM edge_migration_gates
|
|
91
|
+
WHERE scope_key=NEW.scope_key AND state='active'
|
|
92
|
+
)
|
|
93
|
+
BEGIN
|
|
94
|
+
SELECT RAISE(ABORT, 'edge migration gate rejects outbox publication');
|
|
95
|
+
END;
|
|
65
96
|
CREATE TABLE IF NOT EXISTS edge_inbox (
|
|
66
97
|
scope_key TEXT NOT NULL REFERENCES edge_scopes(scope_key),
|
|
67
98
|
message_id TEXT NOT NULL,
|
|
@@ -87,12 +118,81 @@ CREATE INDEX IF NOT EXISTS edge_inbox_thread
|
|
|
87
118
|
CREATE INDEX IF NOT EXISTS edge_inbox_created
|
|
88
119
|
ON edge_inbox(scope_key, created_at, sequence_key);
|
|
89
120
|
`;
|
|
121
|
+
function inspectEdgeScopeReadOnly(path, scope, now = /* @__PURE__ */ new Date()) {
|
|
122
|
+
if (!existsSync(path)) {
|
|
123
|
+
return { exists: false, gate: { scopeKey: edgeScopeKey(scope), state: "active", operationId: void 0, leaseExpiresAt: void 0, updatedAt: (/* @__PURE__ */ new Date(0)).toISOString() }, pending: 0, due: 0, scheduled: 0, leased: 0, blocked: 0, cached: 0 };
|
|
124
|
+
}
|
|
125
|
+
verifyPrivatePathAccess(path, "file");
|
|
126
|
+
const details = lstatSync(path);
|
|
127
|
+
if (!details.isFile() || details.isSymbolicLink()) throw new EdgeConflictError("edge database is not a private regular file");
|
|
128
|
+
if (existsSync(`${path}-wal`) || existsSync(`${path}-shm`)) {
|
|
129
|
+
throw new EdgeConflictError("read-only migration inspection refuses an edge with live WAL state");
|
|
130
|
+
}
|
|
131
|
+
const db = openReadOnlyDatabase(path);
|
|
132
|
+
try {
|
|
133
|
+
const tables = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name IN ('edge_scopes','edge_migration_gates','edge_outbox','edge_inbox') ORDER BY name").all();
|
|
134
|
+
if (tables.length === 0) {
|
|
135
|
+
const userTables = db.prepare("SELECT count(*) AS count FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'").get();
|
|
136
|
+
if (Number(userTables.count) !== 0) throw new EdgeConflictError("edge database is not an Agent Bridge edge store");
|
|
137
|
+
return { exists: true, gate: { scopeKey: edgeScopeKey(scope), state: "active", operationId: void 0, leaseExpiresAt: void 0, updatedAt: (/* @__PURE__ */ new Date(0)).toISOString() }, pending: 0, due: 0, scheduled: 0, leased: 0, blocked: 0, cached: 0 };
|
|
138
|
+
}
|
|
139
|
+
if (tables.length !== 4) throw new EdgeConflictError("edge database schema is incomplete");
|
|
140
|
+
const key = edgeScopeKey(scope);
|
|
141
|
+
const scopeRow = db.prepare("SELECT 1 AS present FROM edge_scopes WHERE scope_key=?").get(key);
|
|
142
|
+
if (!scopeRow) {
|
|
143
|
+
return { exists: true, gate: { scopeKey: key, state: "active", operationId: void 0, leaseExpiresAt: void 0, updatedAt: (/* @__PURE__ */ new Date(0)).toISOString() }, pending: 0, due: 0, scheduled: 0, leased: 0, blocked: 0, cached: 0 };
|
|
144
|
+
}
|
|
145
|
+
const gateRow = db.prepare("SELECT state,operation_id,lease_expires_at,updated_at FROM edge_migration_gates WHERE scope_key=?").get(key);
|
|
146
|
+
if (!gateRow || !["active", "draining", "retired"].includes(String(gateRow.state))) throw new EdgeConflictError("edge migration gate is missing or invalid");
|
|
147
|
+
const nowText = now.toISOString();
|
|
148
|
+
const counts = db.prepare(`SELECT
|
|
149
|
+
sum(CASE WHEN state='pending' THEN 1 ELSE 0 END) AS pending,
|
|
150
|
+
sum(CASE WHEN state='pending' AND available_at<=? AND (lease_expires_at IS NULL OR lease_expires_at<=?) THEN 1 ELSE 0 END) AS due,
|
|
151
|
+
sum(CASE WHEN state='pending' AND available_at>? AND (lease_expires_at IS NULL OR lease_expires_at<=?) THEN 1 ELSE 0 END) AS scheduled,
|
|
152
|
+
sum(CASE WHEN state='pending' AND lease_expires_at>? THEN 1 ELSE 0 END) AS leased,
|
|
153
|
+
sum(CASE WHEN state='blocked' THEN 1 ELSE 0 END) AS blocked
|
|
154
|
+
FROM edge_outbox WHERE scope_key=?`).get(nowText, nowText, nowText, nowText, nowText, key);
|
|
155
|
+
const cached = db.prepare("SELECT count(*) AS count FROM edge_inbox WHERE scope_key=?").get(key);
|
|
156
|
+
return {
|
|
157
|
+
exists: true,
|
|
158
|
+
gate: { scopeKey: key, state: gateRow.state, operationId: gateRow.operation_id ? String(gateRow.operation_id) : void 0, leaseExpiresAt: gateRow.lease_expires_at ? String(gateRow.lease_expires_at) : void 0, updatedAt: gateRow.updated_at ? String(gateRow.updated_at) : (/* @__PURE__ */ new Date(0)).toISOString() },
|
|
159
|
+
pending: Number(counts.pending ?? 0),
|
|
160
|
+
due: Number(counts.due ?? 0),
|
|
161
|
+
scheduled: Number(counts.scheduled ?? 0),
|
|
162
|
+
leased: Number(counts.leased ?? 0),
|
|
163
|
+
blocked: Number(counts.blocked ?? 0),
|
|
164
|
+
cached: Number(cached.count ?? 0)
|
|
165
|
+
};
|
|
166
|
+
} finally {
|
|
167
|
+
db.close();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
90
170
|
var EdgeConflictError = class extends Error {
|
|
91
171
|
constructor() {
|
|
92
172
|
super(...arguments);
|
|
93
173
|
this.code = "edge_idempotency_conflict";
|
|
94
174
|
}
|
|
95
175
|
};
|
|
176
|
+
var EdgeMigrationDrainingError = class extends Error {
|
|
177
|
+
constructor(gate) {
|
|
178
|
+
super("edge scope is draining for a client migration");
|
|
179
|
+
this.gate = gate;
|
|
180
|
+
this.code = "client_migration_draining";
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
var EdgeMigrationRetiredError = class extends Error {
|
|
184
|
+
constructor(gate) {
|
|
185
|
+
super("edge scope was retired by a client migration");
|
|
186
|
+
this.gate = gate;
|
|
187
|
+
this.code = "client_migration_retired";
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
var EdgeMigrationLeaseError = class extends Error {
|
|
191
|
+
constructor() {
|
|
192
|
+
super("client migration worker no longer holds the edge drain lease");
|
|
193
|
+
this.code = "client_migration_lease_lost";
|
|
194
|
+
}
|
|
195
|
+
};
|
|
96
196
|
function canonical(value) {
|
|
97
197
|
if (value === void 0) return "null";
|
|
98
198
|
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
@@ -143,20 +243,26 @@ function parseDraft(value) {
|
|
|
143
243
|
function parseMessage(value) {
|
|
144
244
|
return JSON.parse(String(value));
|
|
145
245
|
}
|
|
246
|
+
function safeOperationId(value) {
|
|
247
|
+
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value)) {
|
|
248
|
+
throw new Error("client migration operation ID is invalid");
|
|
249
|
+
}
|
|
250
|
+
return value.toLowerCase();
|
|
251
|
+
}
|
|
146
252
|
var SQLiteEdgeStore = class {
|
|
147
253
|
constructor(path, scope, busyTimeoutMs = 2e3) {
|
|
148
254
|
this.path = path;
|
|
149
255
|
this.scope = scope;
|
|
150
256
|
this.busyTimeoutMs = busyTimeoutMs;
|
|
151
257
|
this.initialized = false;
|
|
152
|
-
const selected = preparePrivateSqliteLocation(path, true);
|
|
258
|
+
const selected = preparePrivateSqliteLocation(path, true, "defer");
|
|
153
259
|
this.databasePath = selected;
|
|
154
260
|
this.preexistingFiles = new Set(selected === ":memory:" ? [] : [selected, `${selected}-wal`, `${selected}-shm`].filter(existsSync));
|
|
155
261
|
const before = selected === ":memory:" || !existsSync(selected) ? void 0 : lstatSync(selected);
|
|
156
262
|
this.db = openDatabase(selected);
|
|
157
263
|
try {
|
|
158
264
|
this.db.exec(`PRAGMA busy_timeout = ${Math.max(1, Math.trunc(this.busyTimeoutMs))}`);
|
|
159
|
-
this.restrictFiles();
|
|
265
|
+
this.restrictFiles(false);
|
|
160
266
|
if (before) {
|
|
161
267
|
const after = lstatSync(selected);
|
|
162
268
|
if (after.dev !== before.dev || after.ino !== before.ino) throw new Error("SQLite edge database path identity changed while opening");
|
|
@@ -167,17 +273,59 @@ var SQLiteEdgeStore = class {
|
|
|
167
273
|
}
|
|
168
274
|
this.key = edgeScopeKey(scope);
|
|
169
275
|
}
|
|
170
|
-
restrictFiles() {
|
|
276
|
+
restrictFiles(includeSidecars = true) {
|
|
171
277
|
if (this.databasePath === ":memory:") return;
|
|
172
|
-
|
|
278
|
+
const paths = includeSidecars ? [this.databasePath, `${this.databasePath}-wal`, `${this.databasePath}-shm`] : [this.databasePath];
|
|
279
|
+
for (const path of paths) {
|
|
173
280
|
if (!existsSync(path)) continue;
|
|
174
281
|
if (path === this.databasePath && this.preexistingFiles.has(path)) verifyPrivatePathAccess(path, "file");
|
|
175
|
-
else securePrivatePath(path, "file");
|
|
282
|
+
else if (path === this.databasePath) securePrivatePath(path, "file");
|
|
283
|
+
else securePrivateSqliteSidecar(path);
|
|
176
284
|
}
|
|
177
285
|
}
|
|
178
286
|
get scopeKey() {
|
|
179
287
|
return this.key;
|
|
180
288
|
}
|
|
289
|
+
gateFromRow(row) {
|
|
290
|
+
const state = String(row.state);
|
|
291
|
+
if (state !== "active" && state !== "draining" && state !== "retired") {
|
|
292
|
+
throw new EdgeConflictError("edge migration gate state is invalid");
|
|
293
|
+
}
|
|
294
|
+
const operationId = row.operation_id === null || row.operation_id === void 0 ? void 0 : safeOperationId(String(row.operation_id));
|
|
295
|
+
const leaseExpiresAt = row.lease_expires_at === null || row.lease_expires_at === void 0 ? void 0 : String(row.lease_expires_at);
|
|
296
|
+
const updatedAt = String(row.updated_at ?? "");
|
|
297
|
+
if (!updatedAt || !Number.isFinite(Date.parse(updatedAt))) {
|
|
298
|
+
throw new EdgeConflictError("edge migration gate timestamp is invalid");
|
|
299
|
+
}
|
|
300
|
+
const leaseToken = row.lease_token === null || row.lease_token === void 0 ? void 0 : String(row.lease_token);
|
|
301
|
+
if (state === "active" && (operationId || leaseExpiresAt || leaseToken) || state === "draining" && (!operationId || !leaseExpiresAt || !leaseToken) || state === "retired" && (!operationId || leaseExpiresAt || leaseToken)) {
|
|
302
|
+
throw new EdgeConflictError("edge migration gate state is invalid");
|
|
303
|
+
}
|
|
304
|
+
return { scopeKey: this.key, state, operationId, leaseExpiresAt, updatedAt };
|
|
305
|
+
}
|
|
306
|
+
gateInTransaction() {
|
|
307
|
+
const row = this.db.prepare(`SELECT state,operation_id,lease_token,lease_expires_at,updated_at
|
|
308
|
+
FROM edge_migration_gates WHERE scope_key=?`).get(this.key);
|
|
309
|
+
if (!row) throw new EdgeConflictError("edge migration gate is missing");
|
|
310
|
+
return this.gateFromRow(row);
|
|
311
|
+
}
|
|
312
|
+
assertScopeActiveInTransaction() {
|
|
313
|
+
const gate = this.gateInTransaction();
|
|
314
|
+
if (gate.state === "draining") throw new EdgeMigrationDrainingError(gate);
|
|
315
|
+
if (gate.state === "retired") throw new EdgeMigrationRetiredError(gate);
|
|
316
|
+
}
|
|
317
|
+
assertDrainLeaseInTransaction(lease, now) {
|
|
318
|
+
const gate = this.gateInTransaction();
|
|
319
|
+
if (gate.state !== "draining" || gate.operationId !== lease.operationId || gate.leaseExpiresAt !== lease.leaseExpiresAt || Date.parse(gate.leaseExpiresAt) <= now.getTime()) {
|
|
320
|
+
throw new EdgeMigrationLeaseError();
|
|
321
|
+
}
|
|
322
|
+
const row = this.db.prepare("SELECT lease_token FROM edge_migration_gates WHERE scope_key=?").get(this.key);
|
|
323
|
+
if (row.lease_token !== lease.leaseToken) throw new EdgeMigrationLeaseError();
|
|
324
|
+
}
|
|
325
|
+
assertClaimAuthorityInTransaction(now, lease) {
|
|
326
|
+
if (lease) this.assertDrainLeaseInTransaction(lease, now);
|
|
327
|
+
else this.assertScopeActiveInTransaction();
|
|
328
|
+
}
|
|
181
329
|
async initialize() {
|
|
182
330
|
if (this.initialized) return;
|
|
183
331
|
if (!this.initialization) {
|
|
@@ -192,8 +340,9 @@ var SQLiteEdgeStore = class {
|
|
|
192
340
|
}
|
|
193
341
|
async initializeOnce() {
|
|
194
342
|
assertEdgeUpgradeCandidate(this.db);
|
|
195
|
-
|
|
196
|
-
await retrySqliteBusy(() => this.db.exec(
|
|
343
|
+
const initializationTimeoutMs = Math.max(this.busyTimeoutMs, SQLITE_INITIALIZATION_BUSY_TIMEOUT_MS);
|
|
344
|
+
await retrySqliteBusy(() => this.db.exec(schema), initializationTimeoutMs);
|
|
345
|
+
await retrySqliteBusy(() => this.db.exec("BEGIN IMMEDIATE"), initializationTimeoutMs);
|
|
197
346
|
try {
|
|
198
347
|
const scopeColumns = this.db.prepare("PRAGMA table_info(edge_scopes)").all();
|
|
199
348
|
if (!scopeColumns.some((column) => column.name === "cache_contract")) {
|
|
@@ -223,6 +372,10 @@ var SQLiteEdgeStore = class {
|
|
|
223
372
|
const endpointHash = createHash("sha256").update(this.scope.endpoint.replace(/\/$/, "")).digest("hex");
|
|
224
373
|
this.db.prepare(`INSERT INTO edge_scopes (scope_key, endpoint_hash, workspace, agent)
|
|
225
374
|
VALUES (?, ?, ?, ?) ON CONFLICT(scope_key) DO NOTHING`).run(this.key, endpointHash, this.scope.principal.workspace, this.scope.principal.agent);
|
|
375
|
+
this.db.prepare(`INSERT INTO edge_migration_gates
|
|
376
|
+
(scope_key,state,operation_id,lease_token,lease_expires_at,updated_at)
|
|
377
|
+
SELECT scope_key,'active',NULL,NULL,NULL,? FROM edge_scopes
|
|
378
|
+
WHERE scope_key=? ON CONFLICT(scope_key) DO NOTHING`).run((/* @__PURE__ */ new Date()).toISOString(), this.key);
|
|
226
379
|
const contract = this.db.prepare("SELECT cache_contract FROM edge_scopes WHERE scope_key=?").get(this.key);
|
|
227
380
|
if (Number(contract.cache_contract) < 1) {
|
|
228
381
|
this.db.exec("BEGIN IMMEDIATE");
|
|
@@ -238,6 +391,162 @@ var SQLiteEdgeStore = class {
|
|
|
238
391
|
async ready() {
|
|
239
392
|
await this.initialize();
|
|
240
393
|
}
|
|
394
|
+
async migrationGate() {
|
|
395
|
+
await this.ready();
|
|
396
|
+
return this.gateInTransaction();
|
|
397
|
+
}
|
|
398
|
+
/** Refuse new normal-client writes before any remote attempt begins. */
|
|
399
|
+
async assertScopeActive() {
|
|
400
|
+
await this.ready();
|
|
401
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
402
|
+
try {
|
|
403
|
+
this.assertScopeActiveInTransaction();
|
|
404
|
+
this.db.exec("COMMIT");
|
|
405
|
+
} catch (error) {
|
|
406
|
+
this.db.exec("ROLLBACK");
|
|
407
|
+
throw error;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
/** Refuse remote publication unless this worker still owns the durable drain lease. */
|
|
411
|
+
async assertDrainLease(lease, now = /* @__PURE__ */ new Date()) {
|
|
412
|
+
await this.ready();
|
|
413
|
+
if (lease.scopeKey !== this.key) throw new EdgeMigrationLeaseError();
|
|
414
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
415
|
+
try {
|
|
416
|
+
this.assertDrainLeaseInTransaction(lease, now);
|
|
417
|
+
this.db.exec("COMMIT");
|
|
418
|
+
} catch (error) {
|
|
419
|
+
this.db.exec("ROLLBACK");
|
|
420
|
+
throw error;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
async claimDrainLease(operationId, now, leaseMs, mode) {
|
|
424
|
+
await this.ready();
|
|
425
|
+
const normalizedOperationId = safeOperationId(operationId);
|
|
426
|
+
const duration = Math.max(1, Math.trunc(leaseMs));
|
|
427
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
428
|
+
try {
|
|
429
|
+
const gate = this.gateInTransaction();
|
|
430
|
+
if (gate.state === "retired") throw new EdgeMigrationRetiredError(gate);
|
|
431
|
+
if (mode === "begin" && gate.state !== "active") throw new EdgeMigrationDrainingError(gate);
|
|
432
|
+
if (mode === "resume") {
|
|
433
|
+
if (gate.state !== "draining" || gate.operationId !== normalizedOperationId || !gate.leaseExpiresAt || Date.parse(gate.leaseExpiresAt) > now.getTime()) {
|
|
434
|
+
throw new EdgeMigrationDrainingError(gate);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
const leaseToken = randomUUID();
|
|
438
|
+
const leaseExpiresAt = new Date(now.getTime() + duration).toISOString();
|
|
439
|
+
const updated = this.db.prepare(`UPDATE edge_migration_gates
|
|
440
|
+
SET state='draining',operation_id=?,lease_token=?,lease_expires_at=?,updated_at=?
|
|
441
|
+
WHERE scope_key=?`).run(
|
|
442
|
+
normalizedOperationId,
|
|
443
|
+
leaseToken,
|
|
444
|
+
leaseExpiresAt,
|
|
445
|
+
now.toISOString(),
|
|
446
|
+
this.key
|
|
447
|
+
);
|
|
448
|
+
if (updated.changes !== 1) throw new EdgeConflictError("edge migration gate could not be acquired");
|
|
449
|
+
this.db.exec("COMMIT");
|
|
450
|
+
return { scopeKey: this.key, operationId: normalizedOperationId, leaseToken, leaseExpiresAt };
|
|
451
|
+
} catch (error) {
|
|
452
|
+
this.db.exec("ROLLBACK");
|
|
453
|
+
throw error;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
/** Start a new durable drain only from an active edge scope. */
|
|
457
|
+
async beginDrain(operationId, now = /* @__PURE__ */ new Date(), leaseMs = 3e4) {
|
|
458
|
+
return this.claimDrainLease(operationId, now, leaseMs, "begin");
|
|
459
|
+
}
|
|
460
|
+
/** Resume the same persisted drain only after the prior worker lease expires. */
|
|
461
|
+
async resumeDrain(operationId, now = /* @__PURE__ */ new Date(), leaseMs = 3e4) {
|
|
462
|
+
return this.claimDrainLease(operationId, now, leaseMs, "resume");
|
|
463
|
+
}
|
|
464
|
+
/** Backward-compatible dispatcher for callers that have already inspected the gate. */
|
|
465
|
+
async acquireDrainLease(operationId, now = /* @__PURE__ */ new Date(), leaseMs = 3e4) {
|
|
466
|
+
const gate = await this.migrationGate();
|
|
467
|
+
if (gate.state === "active") return this.beginDrain(operationId, now, leaseMs);
|
|
468
|
+
return this.resumeDrain(operationId, now, leaseMs);
|
|
469
|
+
}
|
|
470
|
+
/** Verify the held lease and require an exact zero-work outbox before a cutover continues. */
|
|
471
|
+
async assertDrainComplete(lease, now = /* @__PURE__ */ new Date()) {
|
|
472
|
+
await this.ready();
|
|
473
|
+
if (lease.scopeKey !== this.key) throw new EdgeMigrationLeaseError();
|
|
474
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
475
|
+
try {
|
|
476
|
+
this.assertDrainLeaseInTransaction(lease, now);
|
|
477
|
+
const counts = this.db.prepare(`SELECT
|
|
478
|
+
sum(CASE WHEN state='pending' THEN 1 ELSE 0 END) AS pending,
|
|
479
|
+
sum(CASE WHEN state='pending' AND available_at<=? AND (lease_expires_at IS NULL OR lease_expires_at<=?) THEN 1 ELSE 0 END) AS due,
|
|
480
|
+
sum(CASE WHEN state='pending' AND available_at>? AND (lease_expires_at IS NULL OR lease_expires_at<=?) THEN 1 ELSE 0 END) AS scheduled,
|
|
481
|
+
sum(CASE WHEN state='pending' AND lease_expires_at>? THEN 1 ELSE 0 END) AS leased,
|
|
482
|
+
sum(CASE WHEN state='blocked' THEN 1 ELSE 0 END) AS blocked
|
|
483
|
+
FROM edge_outbox WHERE scope_key=?`).get(
|
|
484
|
+
now.toISOString(),
|
|
485
|
+
now.toISOString(),
|
|
486
|
+
now.toISOString(),
|
|
487
|
+
now.toISOString(),
|
|
488
|
+
now.toISOString(),
|
|
489
|
+
this.key
|
|
490
|
+
);
|
|
491
|
+
if (Number(counts.pending ?? 0) !== 0 || Number(counts.due ?? 0) !== 0 || Number(counts.scheduled ?? 0) !== 0 || Number(counts.leased ?? 0) !== 0 || Number(counts.blocked ?? 0) !== 0) {
|
|
492
|
+
throw new EdgeConflictError("edge scope cannot complete a drain with retained outbox work");
|
|
493
|
+
}
|
|
494
|
+
this.db.exec("COMMIT");
|
|
495
|
+
} catch (error) {
|
|
496
|
+
this.db.exec("ROLLBACK");
|
|
497
|
+
throw error;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
async renewDrainLease(lease, now = /* @__PURE__ */ new Date(), leaseMs = 3e4) {
|
|
501
|
+
await this.ready();
|
|
502
|
+
if (lease.scopeKey !== this.key) throw new EdgeMigrationLeaseError();
|
|
503
|
+
const duration = Math.max(1, Math.trunc(leaseMs));
|
|
504
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
505
|
+
try {
|
|
506
|
+
this.assertDrainLeaseInTransaction(lease, now);
|
|
507
|
+
const leaseExpiresAt = new Date(now.getTime() + duration).toISOString();
|
|
508
|
+
const updated = this.db.prepare(`UPDATE edge_migration_gates
|
|
509
|
+
SET lease_expires_at=?,updated_at=? WHERE scope_key=? AND state='draining'
|
|
510
|
+
AND operation_id=? AND lease_token=? AND lease_expires_at=?`).run(
|
|
511
|
+
leaseExpiresAt,
|
|
512
|
+
now.toISOString(),
|
|
513
|
+
this.key,
|
|
514
|
+
lease.operationId,
|
|
515
|
+
lease.leaseToken,
|
|
516
|
+
lease.leaseExpiresAt
|
|
517
|
+
);
|
|
518
|
+
if (updated.changes !== 1) throw new EdgeMigrationLeaseError();
|
|
519
|
+
this.db.exec("COMMIT");
|
|
520
|
+
return { ...lease, leaseExpiresAt };
|
|
521
|
+
} catch (error) {
|
|
522
|
+
this.db.exec("ROLLBACK");
|
|
523
|
+
throw error;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
async retireScope(lease, now = /* @__PURE__ */ new Date()) {
|
|
527
|
+
await this.ready();
|
|
528
|
+
if (lease.scopeKey !== this.key) throw new EdgeMigrationLeaseError();
|
|
529
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
530
|
+
try {
|
|
531
|
+
this.assertDrainLeaseInTransaction(lease, now);
|
|
532
|
+
const stats = this.db.prepare(`SELECT count(*) AS count FROM edge_outbox
|
|
533
|
+
WHERE scope_key=?`).get(this.key);
|
|
534
|
+
if (Number(stats.count) !== 0) throw new EdgeConflictError("edge scope cannot retire with retained outbox work");
|
|
535
|
+
const updated = this.db.prepare(`UPDATE edge_migration_gates
|
|
536
|
+
SET state='retired',lease_token=NULL,lease_expires_at=NULL,updated_at=?
|
|
537
|
+
WHERE scope_key=? AND state='draining' AND operation_id=? AND lease_token=?`).run(
|
|
538
|
+
now.toISOString(),
|
|
539
|
+
this.key,
|
|
540
|
+
lease.operationId,
|
|
541
|
+
lease.leaseToken
|
|
542
|
+
);
|
|
543
|
+
if (updated.changes !== 1) throw new EdgeMigrationLeaseError();
|
|
544
|
+
this.db.exec("COMMIT");
|
|
545
|
+
} catch (error) {
|
|
546
|
+
this.db.exec("ROLLBACK");
|
|
547
|
+
throw error;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
241
550
|
async enqueue(input, now = /* @__PURE__ */ new Date()) {
|
|
242
551
|
await this.ready();
|
|
243
552
|
const draft = stableIdempotency(input);
|
|
@@ -246,6 +555,7 @@ var SQLiteEdgeStore = class {
|
|
|
246
555
|
const nowText = now.toISOString();
|
|
247
556
|
this.db.exec("BEGIN IMMEDIATE");
|
|
248
557
|
try {
|
|
558
|
+
this.assertScopeActiveInTransaction();
|
|
249
559
|
const existing = this.db.prepare(`SELECT * FROM edge_outbox
|
|
250
560
|
WHERE scope_key=? AND (message_id=? OR idempotency_key=?) ORDER BY position LIMIT 1`).get(this.key, draft.id, draft.idempotencyKey);
|
|
251
561
|
if (existing) {
|
|
@@ -265,11 +575,12 @@ var SQLiteEdgeStore = class {
|
|
|
265
575
|
throw error;
|
|
266
576
|
}
|
|
267
577
|
}
|
|
268
|
-
async claimNext(now = /* @__PURE__ */ new Date(), leaseMs = 3e4) {
|
|
578
|
+
async claimNext(now = /* @__PURE__ */ new Date(), leaseMs = 3e4, migrationLease) {
|
|
269
579
|
await this.ready();
|
|
270
580
|
const nowText = now.toISOString();
|
|
271
581
|
this.db.exec("BEGIN IMMEDIATE");
|
|
272
582
|
try {
|
|
583
|
+
this.assertClaimAuthorityInTransaction(now, migrationLease);
|
|
273
584
|
const head = this.db.prepare(`SELECT * FROM edge_outbox
|
|
274
585
|
WHERE scope_key=? AND state='pending' AND available_at<=?
|
|
275
586
|
AND (lease_expires_at IS NULL OR lease_expires_at<=?)
|
|
@@ -499,6 +810,7 @@ var SQLiteEdgeStore = class {
|
|
|
499
810
|
const state = this.db.prepare(`SELECT pull_cursor, last_sync_at, last_error,
|
|
500
811
|
last_outbound_sync_at,last_inbound_sync_at,last_attempt_at
|
|
501
812
|
FROM edge_scopes WHERE scope_key=?`).get(this.key);
|
|
813
|
+
const migrationGate = this.gateInTransaction();
|
|
502
814
|
const oldestDueAt = counts.oldest_due ? String(counts.oldest_due) : void 0;
|
|
503
815
|
const oldestBlockedAt = Number(counts.blocked_age_unknown ?? 0) > 0 ? void 0 : counts.oldest_blocked ? String(counts.oldest_blocked) : void 0;
|
|
504
816
|
return {
|
|
@@ -520,7 +832,10 @@ var SQLiteEdgeStore = class {
|
|
|
520
832
|
nextRetryAt: counts.next_retry ? String(counts.next_retry) : void 0,
|
|
521
833
|
lastOutboundSyncAt: state.last_outbound_sync_at ? String(state.last_outbound_sync_at) : void 0,
|
|
522
834
|
lastInboundSyncAt: state.last_inbound_sync_at ? String(state.last_inbound_sync_at) : void 0,
|
|
523
|
-
lastAttemptAt: state.last_attempt_at ? String(state.last_attempt_at) : void 0
|
|
835
|
+
lastAttemptAt: state.last_attempt_at ? String(state.last_attempt_at) : void 0,
|
|
836
|
+
migrationState: migrationGate.state,
|
|
837
|
+
migrationOperationId: migrationGate.operationId,
|
|
838
|
+
migrationLeaseExpiresAt: migrationGate.leaseExpiresAt
|
|
524
839
|
};
|
|
525
840
|
}
|
|
526
841
|
async close() {
|
|
@@ -545,7 +860,7 @@ function isRetryableSyncError(error) {
|
|
|
545
860
|
const status = statusOf(error);
|
|
546
861
|
const code = codeOf(error);
|
|
547
862
|
if (["network_error", "request_timeout"].includes(code)) return true;
|
|
548
|
-
if (["invalid_input", "idempotency_conflict", "edge_idempotency_conflict", "principal_mismatch", "protocol_mismatch", "store_closed"].includes(code)) return false;
|
|
863
|
+
if (["invalid_input", "idempotency_conflict", "edge_idempotency_conflict", "principal_mismatch", "protocol_mismatch", "store_closed", "client_migration_draining", "client_migration_retired", "client_migration_lease_lost"].includes(code)) return false;
|
|
549
864
|
return status === 0 || status === 408 || status === 425 || status === 429 || status !== void 0 && status >= 500;
|
|
550
865
|
}
|
|
551
866
|
function provisional(draft, createdAt) {
|
|
@@ -567,12 +882,18 @@ var SyncingBridgeStore = class {
|
|
|
567
882
|
this.leaseMs = options.leaseMs ?? 3e4;
|
|
568
883
|
this.autoSync = options.autoSync ?? true;
|
|
569
884
|
this.idleDelayMs = Math.max(this.baseDelayMs + 1, options.idleDelayMs ?? 3e4);
|
|
885
|
+
this.edgeDrainLease = options.edgeDrainLease;
|
|
886
|
+
this.closeEdge = options.closeEdge ?? true;
|
|
570
887
|
this.loopState = this.autoSync ? "idle" : "disabled";
|
|
571
888
|
}
|
|
572
889
|
async initialize() {
|
|
573
890
|
await this.edge.initialize();
|
|
574
891
|
if (this.autoSync) this.startTransportLoop();
|
|
575
892
|
}
|
|
893
|
+
/** Replace a renewed maintenance lease without creating another edge wrapper. */
|
|
894
|
+
setDrainLease(lease) {
|
|
895
|
+
this.edgeDrainLease = lease;
|
|
896
|
+
}
|
|
576
897
|
startTransportLoop() {
|
|
577
898
|
if (this.background) return;
|
|
578
899
|
this.background = this.transportLoop().catch((error) => {
|
|
@@ -620,6 +941,11 @@ var SyncingBridgeStore = class {
|
|
|
620
941
|
this.loopState = "running";
|
|
621
942
|
const report = await this.sync({ maxPush: 100, maxPages: 1, signal: this.closeController.signal });
|
|
622
943
|
if (this.stopped) break;
|
|
944
|
+
if (report.migrationState !== "active" && !this.edgeDrainLease) {
|
|
945
|
+
this.loopState = "paused";
|
|
946
|
+
await this.wait(this.idleDelayMs);
|
|
947
|
+
continue;
|
|
948
|
+
}
|
|
623
949
|
if (report.failureRetryable === false) {
|
|
624
950
|
this.backgroundError = Object.assign(
|
|
625
951
|
new Error(`gateway synchronization failed permanently: ${report.lastError ?? "sync_error"}`),
|
|
@@ -668,6 +994,13 @@ var SyncingBridgeStore = class {
|
|
|
668
994
|
}
|
|
669
995
|
async flushOne(signal) {
|
|
670
996
|
await this.edge.noteAttempt(this.now());
|
|
997
|
+
try {
|
|
998
|
+
if (this.edgeDrainLease) await this.edge.assertDrainLease(this.edgeDrainLease, this.now());
|
|
999
|
+
else await this.edge.assertScopeActive();
|
|
1000
|
+
} catch (error) {
|
|
1001
|
+
if (error instanceof EdgeMigrationDrainingError || error instanceof EdgeMigrationRetiredError || error instanceof EdgeMigrationLeaseError) return { state: "paused", error };
|
|
1002
|
+
throw error;
|
|
1003
|
+
}
|
|
671
1004
|
try {
|
|
672
1005
|
await this.ensureRemote(signal);
|
|
673
1006
|
} catch (error) {
|
|
@@ -678,7 +1011,13 @@ var SyncingBridgeStore = class {
|
|
|
678
1011
|
retryable: isRetryableSyncError(error)
|
|
679
1012
|
};
|
|
680
1013
|
}
|
|
681
|
-
|
|
1014
|
+
let record;
|
|
1015
|
+
try {
|
|
1016
|
+
record = await this.edge.claimNext(this.now(), this.leaseMs, this.edgeDrainLease);
|
|
1017
|
+
} catch (error) {
|
|
1018
|
+
if (error instanceof EdgeMigrationDrainingError || error instanceof EdgeMigrationRetiredError || error instanceof EdgeMigrationLeaseError) return { state: "paused", error };
|
|
1019
|
+
throw error;
|
|
1020
|
+
}
|
|
682
1021
|
if (!record) return { state: "empty" };
|
|
683
1022
|
try {
|
|
684
1023
|
const result = await this.remote.insertMessage(record.draft, { signal });
|
|
@@ -723,6 +1062,7 @@ var SyncingBridgeStore = class {
|
|
|
723
1062
|
async insertMessage(message) {
|
|
724
1063
|
this.assertHealthy();
|
|
725
1064
|
this.assertPrincipal({ workspace: message.workspace, agent: message.source }, false);
|
|
1065
|
+
await this.edge.assertScopeActive();
|
|
726
1066
|
let remoteError;
|
|
727
1067
|
try {
|
|
728
1068
|
await this.ensureRemote();
|
|
@@ -829,13 +1169,17 @@ var SyncingBridgeStore = class {
|
|
|
829
1169
|
continue;
|
|
830
1170
|
}
|
|
831
1171
|
if (result.state === "fatal") throw result.error;
|
|
1172
|
+
if (result.state === "paused") {
|
|
1173
|
+
lastError = codeOf(result.error);
|
|
1174
|
+
break;
|
|
1175
|
+
}
|
|
832
1176
|
lastError = codeOf(result.error);
|
|
833
1177
|
failureRetryable = result.state === "retry" ? result.retryable : void 0;
|
|
834
1178
|
if (result.state === "retry") online = false;
|
|
835
1179
|
if (result.state === "retry") break;
|
|
836
1180
|
}
|
|
837
1181
|
let pulled = 0;
|
|
838
|
-
if (maxPages > 0 && !this.cancelled(options.signal)) {
|
|
1182
|
+
if (maxPages > 0 && !this.cancelled(options.signal) && !lastError?.startsWith("client_migration_")) {
|
|
839
1183
|
const pull = await this.pull(maxPages, options.signal);
|
|
840
1184
|
online = online && pull.online;
|
|
841
1185
|
pulled = pull.pulled;
|
|
@@ -856,7 +1200,10 @@ var SyncingBridgeStore = class {
|
|
|
856
1200
|
cursor: stats.pullCursor,
|
|
857
1201
|
lastSyncedAt: stats.lastSyncAt,
|
|
858
1202
|
lastError: lastError ?? stats.lastError,
|
|
859
|
-
failureRetryable
|
|
1203
|
+
failureRetryable,
|
|
1204
|
+
migrationState: stats.migrationState,
|
|
1205
|
+
migrationOperationId: stats.migrationOperationId,
|
|
1206
|
+
migrationLeaseExpiresAt: stats.migrationLeaseExpiresAt
|
|
860
1207
|
};
|
|
861
1208
|
}
|
|
862
1209
|
async listMessages(principal, query = {}) {
|
|
@@ -1001,7 +1348,10 @@ var SyncingBridgeStore = class {
|
|
|
1001
1348
|
lastSyncAttemptAt: edge.lastAttemptAt,
|
|
1002
1349
|
syncLoopState: this.backgroundError ? "failed" : this.loopState,
|
|
1003
1350
|
syncLoopError: this.backgroundError ? codeOf(this.backgroundError) : void 0,
|
|
1004
|
-
remoteError
|
|
1351
|
+
remoteError,
|
|
1352
|
+
migrationState: edge.migrationState,
|
|
1353
|
+
migrationOperationId: edge.migrationOperationId,
|
|
1354
|
+
migrationLeaseExpiresAt: edge.migrationLeaseExpiresAt
|
|
1005
1355
|
};
|
|
1006
1356
|
}
|
|
1007
1357
|
async heartbeat(principal, leaseMs, runtimeType, capabilities = []) {
|
|
@@ -1028,10 +1378,12 @@ var SyncingBridgeStore = class {
|
|
|
1028
1378
|
]);
|
|
1029
1379
|
const failures = results.filter((result) => result.status === "rejected").map((result) => result.reason);
|
|
1030
1380
|
if (this.backgroundError) failures.push(this.backgroundError);
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1381
|
+
if (this.closeEdge) {
|
|
1382
|
+
try {
|
|
1383
|
+
await this.edge.close();
|
|
1384
|
+
} catch (error) {
|
|
1385
|
+
failures.push(error);
|
|
1386
|
+
}
|
|
1035
1387
|
}
|
|
1036
1388
|
if (failures.length === 1) throw failures[0];
|
|
1037
1389
|
if (failures.length > 1) throw Object.assign(new Error("failed to close synchronized bridge store"), { errors: failures });
|
|
@@ -1039,7 +1391,11 @@ var SyncingBridgeStore = class {
|
|
|
1039
1391
|
};
|
|
1040
1392
|
|
|
1041
1393
|
export {
|
|
1394
|
+
inspectEdgeScopeReadOnly,
|
|
1042
1395
|
EdgeConflictError,
|
|
1396
|
+
EdgeMigrationDrainingError,
|
|
1397
|
+
EdgeMigrationRetiredError,
|
|
1398
|
+
EdgeMigrationLeaseError,
|
|
1043
1399
|
edgeMessageFingerprint,
|
|
1044
1400
|
stableIdempotency,
|
|
1045
1401
|
edgeScopeKey,
|
|
@@ -1047,4 +1403,4 @@ export {
|
|
|
1047
1403
|
isRetryableSyncError,
|
|
1048
1404
|
SyncingBridgeStore
|
|
1049
1405
|
};
|
|
1050
|
-
//# sourceMappingURL=chunk-
|
|
1406
|
+
//# sourceMappingURL=chunk-SN7PIVDF.js.map
|