@helipod/cli 0.1.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/bin.js +14 -0
- package/dist/bin.js.map +1 -0
- package/dist/chunk-EPHSHGFU.js +86 -0
- package/dist/chunk-EPHSHGFU.js.map +1 -0
- package/dist/chunk-IBFZ2HUR.js +2653 -0
- package/dist/chunk-IBFZ2HUR.js.map +1 -0
- package/dist/chunk-N5J276OX.js +218 -0
- package/dist/chunk-N5J276OX.js.map +1 -0
- package/dist/http-handler-BFprdo3f.d.ts +133 -0
- package/dist/http-handler.d.ts +8 -0
- package/dist/http-handler.js +7 -0
- package/dist/http-handler.js.map +1 -0
- package/dist/index.d.ts +366 -0
- package/dist/index.js +120 -0
- package/dist/index.js.map +1 -0
- package/dist/project.d.ts +53 -0
- package/dist/project.js +9 -0
- package/dist/project.js.map +1 -0
- package/package.json +92 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
// src/http-handler.ts
|
|
2
|
+
import { convexToJson } from "@helipod/values";
|
|
3
|
+
import { getHttpStatus, toHelipodError, NotShardOwnerError, CommitGuardRejection } from "@helipod/errors";
|
|
4
|
+
import { matchRoute } from "@helipod/executor";
|
|
5
|
+
import { DEFAULT_SHARD } from "@helipod/id-codec";
|
|
6
|
+
import { handleAdminRequest, verifyAdminKey } from "@helipod/admin";
|
|
7
|
+
function isFleetIdempotencyConflict(e) {
|
|
8
|
+
return e instanceof CommitGuardRejection && e.rejectionCode === "FLEET_IDEMPOTENCY_CONFLICT";
|
|
9
|
+
}
|
|
10
|
+
function idempotencyReplayBody(hit) {
|
|
11
|
+
return {
|
|
12
|
+
replayed: true,
|
|
13
|
+
commitTs: String(hit.commitTs),
|
|
14
|
+
...hit.hasValue ? { value: hit.value } : { valueMissing: true }
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function json(status, value) {
|
|
18
|
+
return { status, headers: { "content-type": "application/json" }, body: JSON.stringify(value) };
|
|
19
|
+
}
|
|
20
|
+
function html(body) {
|
|
21
|
+
return { status: 200, headers: { "content-type": "text/html; charset=utf-8" }, body };
|
|
22
|
+
}
|
|
23
|
+
function bearer(authorization) {
|
|
24
|
+
const m = /^Bearer (.+)$/.exec(authorization ?? "");
|
|
25
|
+
return m ? m[1] : void 0;
|
|
26
|
+
}
|
|
27
|
+
function escapeHtml(s) {
|
|
28
|
+
return s.replace(
|
|
29
|
+
/[&<>"']/g,
|
|
30
|
+
(c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c] ?? c
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
function dashboardHtml(info) {
|
|
34
|
+
const li = (items) => items.map((i) => `<li><code>${escapeHtml(i)}</code></li>`).join("") || "<li><em>none</em></li>";
|
|
35
|
+
return `<!doctype html><html><head><meta charset="utf-8"><title>Helipod</title>
|
|
36
|
+
<style>body{font:14px system-ui;margin:2rem;max-width:48rem}code{background:#f4f4f5;padding:.1rem .3rem;border-radius:4px}</style>
|
|
37
|
+
</head><body>
|
|
38
|
+
<h1>Helipod \u2014 dev</h1>
|
|
39
|
+
<p>The reactive backend is running.</p>
|
|
40
|
+
<h2>Tables (${info.tables.length})</h2><ul>${li(info.tables)}</ul>
|
|
41
|
+
<h2>Functions (${info.functions.length})</h2><ul>${li(info.functions)}</ul>
|
|
42
|
+
</body></html>`;
|
|
43
|
+
}
|
|
44
|
+
async function handleHttpRequest(runtime, req, info, admin, routes, deploy, fleet, replicaWriterUrl) {
|
|
45
|
+
if (fleet && req.method === "POST" && req.path === "/_fleet/run") {
|
|
46
|
+
if (!admin || !verifyAdminKey(admin.key, bearer(req.authorization))) return json(401, { error: "unauthorized" });
|
|
47
|
+
try {
|
|
48
|
+
const p = JSON.parse(req.body ?? "{}");
|
|
49
|
+
if (!p.path) return json(400, { error: "missing function path" });
|
|
50
|
+
const identity = p.identity ?? null;
|
|
51
|
+
const shardId = p.shardId ?? DEFAULT_SHARD;
|
|
52
|
+
const dedup = p.clientId !== void 0 && p.seq !== void 0 ? { clientId: p.clientId, seq: p.seq } : void 0;
|
|
53
|
+
if (p.forwarded && fleet.isLocalWriter && !fleet.isLocalWriter(shardId)) {
|
|
54
|
+
throw new NotShardOwnerError(
|
|
55
|
+
`fleet: this node is not the owner of shard '${shardId}' \u2014 refresh and retry against the current owner`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
const idempotencyKey = p.idempotencyKey;
|
|
59
|
+
if (idempotencyKey && fleet.idempotencyLookup && !dedup) {
|
|
60
|
+
const hit = await fleet.idempotencyLookup(idempotencyKey);
|
|
61
|
+
if (hit) return json(200, idempotencyReplayBody(hit));
|
|
62
|
+
}
|
|
63
|
+
const isInternalForwardPath = p.path.split(":").some((seg) => seg.startsWith("_"));
|
|
64
|
+
const commitMeta = idempotencyKey ? { idempotencyKey } : void 0;
|
|
65
|
+
let result;
|
|
66
|
+
try {
|
|
67
|
+
result = p.kind === "action" ? await runtime.runAction(p.path, p.args ?? {}, { identity }) : isInternalForwardPath ? await runtime.runSystem(p.path, p.args ?? {}, { shardId, commitMeta }) : await runtime.run(p.path, p.args ?? {}, { identity, commitMeta, dedup });
|
|
68
|
+
} catch (runErr) {
|
|
69
|
+
if (idempotencyKey && fleet.idempotencyLookup && isFleetIdempotencyConflict(runErr)) {
|
|
70
|
+
const hit = await fleet.idempotencyLookup(idempotencyKey);
|
|
71
|
+
if (hit) return json(200, idempotencyReplayBody(hit));
|
|
72
|
+
}
|
|
73
|
+
throw runErr;
|
|
74
|
+
}
|
|
75
|
+
if (result.clientReplay) {
|
|
76
|
+
return json(200, { clientReplay: result.clientReplay });
|
|
77
|
+
}
|
|
78
|
+
if (idempotencyKey && fleet.idempotencyRecordValue) {
|
|
79
|
+
try {
|
|
80
|
+
await fleet.idempotencyRecordValue(idempotencyKey, convexToJson(result.value));
|
|
81
|
+
} catch {
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return json(200, {
|
|
85
|
+
value: convexToJson(result.value),
|
|
86
|
+
commitTs: String(result.oplog?.commitTs ?? result.commitTs ?? 0n),
|
|
87
|
+
...result.oplog?.shardId !== void 0 ? { shardId: result.oplog.shardId } : {}
|
|
88
|
+
});
|
|
89
|
+
} catch (e) {
|
|
90
|
+
const err = toHelipodError(e);
|
|
91
|
+
return json(getHttpStatus(err), { error: err.message, code: err.code, errorJson: err.toJSON() });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (admin && deploy && req.method === "GET" && req.path === "/_admin/deploy/modules") {
|
|
95
|
+
if (!verifyAdminKey(admin.key, bearer(req.authorization))) return json(401, { ok: false, error: "unauthorized" });
|
|
96
|
+
return json(200, deploy.modules());
|
|
97
|
+
}
|
|
98
|
+
if (admin && deploy && req.method === "POST" && req.path === "/_admin/deploy") {
|
|
99
|
+
if (!verifyAdminKey(admin.key, bearer(req.authorization))) return json(401, { ok: false, error: "unauthorized" });
|
|
100
|
+
let payload;
|
|
101
|
+
try {
|
|
102
|
+
payload = JSON.parse(req.body ?? "{}");
|
|
103
|
+
} catch {
|
|
104
|
+
return json(400, { ok: false, kind: "load-error", error: "invalid deploy payload" });
|
|
105
|
+
}
|
|
106
|
+
const result = await deploy.apply(payload);
|
|
107
|
+
const status = result.ok ? 200 : result.kind === "schema-incompatible" || result.kind === "stale-base" ? 409 : 400;
|
|
108
|
+
return json(status, result);
|
|
109
|
+
}
|
|
110
|
+
if (admin && req.method === "POST" && req.path === "/_admin/wake") {
|
|
111
|
+
if (!verifyAdminKey(admin.key, bearer(req.authorization))) return json(401, { ok: false, error: "unauthorized" });
|
|
112
|
+
runtime.fireDueTimers();
|
|
113
|
+
return json(200, { ok: true });
|
|
114
|
+
}
|
|
115
|
+
if (admin && req.path.startsWith("/_admin/")) {
|
|
116
|
+
const res = await handleAdminRequest(admin.api, admin.key, {
|
|
117
|
+
method: req.method,
|
|
118
|
+
path: req.path,
|
|
119
|
+
query: req.query ?? {},
|
|
120
|
+
body: req.body,
|
|
121
|
+
authorization: req.authorization
|
|
122
|
+
});
|
|
123
|
+
return json(res.status, res.body);
|
|
124
|
+
}
|
|
125
|
+
if (req.method === "GET" && (req.path === "/_dashboard" || req.path === "/_dashboard/")) {
|
|
126
|
+
return html(dashboardHtml(info));
|
|
127
|
+
}
|
|
128
|
+
if (req.method === "GET" && req.path === "/api/health") {
|
|
129
|
+
const fs = fleet?.frontierStats?.() ?? null;
|
|
130
|
+
const gc = fleet?.groupCommitStats?.() ?? null;
|
|
131
|
+
return json(200, {
|
|
132
|
+
status: "ok",
|
|
133
|
+
functions: info.functions.length,
|
|
134
|
+
tables: info.tables.length,
|
|
135
|
+
...fs ? { fleet: { frontier: String(fs.frontier), lagMs: fs.lagMs, pinningShard: fs.pinningShard, ...gc ? { groupCommit: gc } : {} } } : {}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
if (req.method === "POST" && req.path === "/api/run") {
|
|
139
|
+
try {
|
|
140
|
+
const parsed = JSON.parse(req.body ?? "{}");
|
|
141
|
+
if (!parsed.path) return json(400, { error: "missing function path" });
|
|
142
|
+
if (parsed.forwarded && replicaWriterUrl !== void 0) {
|
|
143
|
+
return json(409, {
|
|
144
|
+
error: "helipod: this node is a read replica configured to forward writes (--writer-url) and received an already-forwarded write \u2014 check --writer-url points at the actual writer, not another replica."
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
const identity = bearer(req.authorization) ?? null;
|
|
148
|
+
const result = await runtime.run(parsed.path, parsed.args ?? {}, { identity });
|
|
149
|
+
return json(200, {
|
|
150
|
+
value: convexToJson(result.value),
|
|
151
|
+
committed: result.committed,
|
|
152
|
+
// Additive: lets a forwarding replica's `ReplicaWriteForwarder` report the writer's real
|
|
153
|
+
// commitTs instead of a hardcoded 0 — see `EmbeddedRuntime.forwardedResult`'s doc comment.
|
|
154
|
+
commitTs: String(result.oplog?.commitTs ?? result.commitTs ?? 0n)
|
|
155
|
+
});
|
|
156
|
+
} catch (e) {
|
|
157
|
+
const err = toHelipodError(e);
|
|
158
|
+
return json(getHttpStatus(err), { error: err.message, code: err.code });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const match = routes && routes.length > 0 ? matchRoute(routes, req.method, req.path) : void 0;
|
|
162
|
+
if (match) {
|
|
163
|
+
if (fleet && fleet.role() === "sync") {
|
|
164
|
+
try {
|
|
165
|
+
const writerUrl = await fleet.writerUrl();
|
|
166
|
+
if (!writerUrl) return json(503, { error: "fleet: no writer available" });
|
|
167
|
+
const qs = req.query && Object.keys(req.query).length ? "?" + new URLSearchParams(req.query).toString() : "";
|
|
168
|
+
const target = `${writerUrl.replace(/\/$/, "")}${req.path}${qs}`;
|
|
169
|
+
const headers = new Headers(req.headers ?? {});
|
|
170
|
+
if (req.authorization && !headers.has("authorization")) headers.set("authorization", req.authorization);
|
|
171
|
+
headers.delete("host");
|
|
172
|
+
headers.delete("content-length");
|
|
173
|
+
const hasBody = req.method !== "GET" && req.method !== "HEAD" && req.body !== void 0;
|
|
174
|
+
const resp = await fetch(target, { method: req.method, headers, ...hasBody ? { body: req.body } : {} });
|
|
175
|
+
const outHeaders = {};
|
|
176
|
+
resp.headers.forEach((v, k) => {
|
|
177
|
+
outHeaders[k] = v;
|
|
178
|
+
});
|
|
179
|
+
delete outHeaders["content-encoding"];
|
|
180
|
+
delete outHeaders["content-length"];
|
|
181
|
+
delete outHeaders["transfer-encoding"];
|
|
182
|
+
delete outHeaders["connection"];
|
|
183
|
+
return { status: resp.status, headers: outHeaders, body: await resp.text() };
|
|
184
|
+
} catch (e) {
|
|
185
|
+
return json(502, { error: `fleet: httpAction proxy to writer failed: ${e instanceof Error ? e.message : String(e)}` });
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
const headers = new Headers(req.headers ?? {});
|
|
190
|
+
if (req.authorization && !headers.has("authorization")) headers.set("authorization", req.authorization);
|
|
191
|
+
const qs = req.query && Object.keys(req.query).length ? "?" + new URLSearchParams(req.query).toString() : "";
|
|
192
|
+
const host = headers.get("host") ?? "localhost";
|
|
193
|
+
const url = `http://${host}${req.path}${qs}`;
|
|
194
|
+
const hasBody = req.method !== "GET" && req.method !== "HEAD" && req.body !== void 0;
|
|
195
|
+
const request = new Request(url, { method: req.method, headers, ...hasBody ? { body: req.body } : {} });
|
|
196
|
+
const auth = headers.get("authorization") ?? "";
|
|
197
|
+
const identity = auth.startsWith("Bearer ") ? auth.slice("Bearer ".length) : null;
|
|
198
|
+
const response = await runtime.runHttpAction(match.handlerPath, request, { identity });
|
|
199
|
+
if (!(response instanceof Response)) {
|
|
200
|
+
return json(500, { error: "httpAction must return a Response" });
|
|
201
|
+
}
|
|
202
|
+
const outHeaders = {};
|
|
203
|
+
response.headers.forEach((v, k) => {
|
|
204
|
+
outHeaders[k] = v;
|
|
205
|
+
});
|
|
206
|
+
return { status: response.status, headers: outHeaders, body: await response.text() };
|
|
207
|
+
} catch (e) {
|
|
208
|
+
const err = toHelipodError(e);
|
|
209
|
+
return json(getHttpStatus(err), { error: err.message, code: err.code });
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return json(404, { error: "not found" });
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export {
|
|
216
|
+
handleHttpRequest
|
|
217
|
+
};
|
|
218
|
+
//# sourceMappingURL=chunk-N5J276OX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/http-handler.ts"],"sourcesContent":["/**\n * HTTP routing for the dev server — a pure function over the runtime so it's testable without\n * a socket. Routes: the `_dashboard` status page, a health check, `POST /api/run` for direct\n * function invocation, and `/_admin/*` for the admin API (behind an admin key).\n */\nimport { convexToJson, type JSONValue, type Value } from \"@helipod/values\";\nimport { getHttpStatus, toHelipodError, NotShardOwnerError, CommitGuardRejection } from \"@helipod/errors\";\nimport { matchRoute } from \"@helipod/executor\";\nimport { DEFAULT_SHARD, type ShardId } from \"@helipod/id-codec\";\nimport type { EmbeddedRuntime } from \"@helipod/runtime-embedded\";\nimport { handleAdminRequest, verifyAdminKey, type AdminApi } from \"@helipod/admin\";\nimport type { ResolvedRoute } from \"./project\";\nimport type { DeployWireResult } from \"./deploy-apply\";\n\nexport interface HttpRequest {\n method: string;\n path: string;\n body?: string;\n query?: Record<string, string>;\n authorization?: string;\n headers?: Record<string, string>;\n}\n\n/**\n * The fleet node handle the HTTP layer consumes (structural mirror of `@helipod/fleet`'s\n * `FleetHandles` — declared here so core cli has no static dependency on the enterprise package).\n * `role()` decides whether a sync node proxies public httpActions to the writer; `writerUrl()` is\n * that proxy target. Present only when `serve --fleet` is active.\n */\nexport interface FleetHandles {\n role(): \"sync\" | \"writer\";\n writerUrl(): Promise<string>;\n onPromoted(cb: () => void): void;\n /** Frontier-lag reading for /api/health (B2a, D5): the fleet-wide `min(frontier_ts)`, how long it's\n * been stuck (ms), and which shard is pinning it. Null before the first frontier beat. Optional so\n * older/stub `FleetHandles` (and pre-B2a fleet builds) satisfy the structural mirror; the health\n * handler optional-chains it. The real `@helipod/fleet` node always provides it. */\n frontierStats?(): { frontier: bigint; lagMs: number; pinningShard: string } | null;\n /**\n * Group-commit counters (Fleet B4, T4 health observability): the aggregate `EmbeddedRuntime.\n * groupCommitStats()` reading plus a derived `flushesPerSec` (a rolling delta between successive\n * calls — see `@helipod/fleet`'s `node.ts`). Structurally all-zero when `HELIPOD_GROUP_COMMIT`\n * is off (the underlying counters are simply never touched on the single-commit path), so the\n * health handler below shows zeroed fields rather than omitting them once fleet mode + this method\n * are both present — `undefined` is reserved for \"not wired at all\" (an older/stub `FleetHandles`),\n * mirroring `frontierStats?`'s same optional-for-backward-compat shape. The real `@helipod/fleet`\n * node always provides it.\n */\n groupCommitStats?(): { lastBatchSize: number; maxBatchSize: number; flushCount: number; flushesPerSec: number };\n /**\n * Per-shard ownership (B2b, D1): true when THIS node currently holds `shardId`'s write lease.\n * Backs the `/_fleet/run` single-hop guard below — optional so older/stub `FleetHandles` satisfy\n * the structural mirror; the guard skips (fail open) when absent. The real `@helipod/fleet` node\n * always provides it (delegates to its `WriteForwarder.isLocalWriter`).\n */\n isLocalWriter?(shardId: string): boolean;\n /**\n * Effectively-once forwarding (Fleet B3, D3): read back `key`'s `fleet_idempotency` row for a\n * replay decision — a hit means this write already committed (possibly on a sibling concurrent\n * attempt) and the caller must NOT re-execute. Optional so older/stub `FleetHandles` satisfy the\n * structural mirror; the `/_fleet/run` handler below skips the whole idempotency path when absent\n * (byte-identical to before this feature existed). The real `@helipod/fleet` node always\n * provides it (delegates to `LeaseManager.lookupIdempotency`).\n */\n idempotencyLookup?(key: string): Promise<{ commitTs: bigint; hasValue: boolean; value: JSONValue | null; oversized: boolean } | null>;\n /** Best-effort post-run recording of a forwarded mutation's return value — see\n * `LeaseManager.recordIdempotencyValue`. Optional for the same reason as `idempotencyLookup`. */\n idempotencyRecordValue?(key: string, value: JSONValue): Promise<void>;\n stop(): Promise<void>;\n}\n\n/**\n * True iff `e` is the fleet idempotency guard's rejection (Fleet B3, D3) — the concurrent-duplicate\n * race's loser: its own commit guard INSERT collided with a sibling attempt that committed first,\n * aborting its ENTIRE commit transaction. Since the Receipted Outbox split-retry (decision 2) the\n * guard converts the raw Postgres `unique_violation` into a typed `CommitGuardRejection` carrying\n * `rejectionCode === \"FLEET_IDEMPOTENCY_CONFLICT\"`, so this detection is now a typed `instanceof`\n * rather than a raw-23505 sniff. This is caught on the OWNER (a local throw, before the fleet-hop\n * serialization at the bottom of `/_fleet/run`), so the raw `instanceof` holds. It must stay narrow:\n * an app-schema unique violation surfaces as a raw driver error (NOT a `CommitGuardRejection`) and\n * must NEVER be turned into a replay — the typed guard makes that distinction structural.\n */\nfunction isFleetIdempotencyConflict(e: unknown): boolean {\n return e instanceof CommitGuardRejection && e.rejectionCode === \"FLEET_IDEMPOTENCY_CONFLICT\";\n}\n\n/** The `/_fleet/run` replay response body (Fleet B3, D3) — additive: `replayed: true` alongside\n * either `value` (a genuinely-recorded result, including a legitimate JSON `null`) or\n * `valueMissing: true` (the crash-window or oversized-cap case — `hasValue` is false either way). */\nfunction idempotencyReplayBody(hit: {\n commitTs: bigint;\n hasValue: boolean;\n value: JSONValue | null;\n}): { replayed: true; commitTs: string; value?: JSONValue; valueMissing?: true } {\n return {\n replayed: true,\n commitTs: String(hit.commitTs),\n ...(hit.hasValue ? { value: hit.value } : { valueMissing: true }),\n };\n}\n\nexport interface HttpResponse {\n status: number;\n headers: Record<string, string>;\n body: string;\n}\n\nexport interface ServerInfo {\n functions: string[];\n tables: string[];\n}\n\nfunction json(status: number, value: unknown): HttpResponse {\n return { status, headers: { \"content-type\": \"application/json\" }, body: JSON.stringify(value) };\n}\nfunction html(body: string): HttpResponse {\n return { status: 200, headers: { \"content-type\": \"text/html; charset=utf-8\" }, body };\n}\n\nfunction bearer(authorization?: string): string | undefined {\n const m = /^Bearer (.+)$/.exec(authorization ?? \"\");\n return m ? m[1] : undefined;\n}\n\nfunction escapeHtml(s: string): string {\n return s.replace(\n /[&<>\"']/g,\n (c) => ({ \"&\": \"&\", \"<\": \"<\", \">\": \">\", '\"': \""\", \"'\": \"'\" })[c] ?? c,\n );\n}\n\nfunction dashboardHtml(info: ServerInfo): string {\n const li = (items: string[]) => items.map((i) => `<li><code>${escapeHtml(i)}</code></li>`).join(\"\") || \"<li><em>none</em></li>\";\n return `<!doctype html><html><head><meta charset=\"utf-8\"><title>Helipod</title>\n<style>body{font:14px system-ui;margin:2rem;max-width:48rem}code{background:#f4f4f5;padding:.1rem .3rem;border-radius:4px}</style>\n</head><body>\n<h1>Helipod — dev</h1>\n<p>The reactive backend is running.</p>\n<h2>Tables (${info.tables.length})</h2><ul>${li(info.tables)}</ul>\n<h2>Functions (${info.functions.length})</h2><ul>${li(info.functions)}</ul>\n</body></html>`;\n}\n\nexport async function handleHttpRequest(\n runtime: EmbeddedRuntime,\n req: HttpRequest,\n info: ServerInfo,\n admin?: { api: AdminApi; key: string },\n routes?: ResolvedRoute[],\n deploy?: {\n apply: (\n payload:\n | { files: Array<{ path: string; code: string }> }\n | { changed: Array<{ path: string; code: string }>; unchanged: Array<{ path: string; sha256: string }> },\n ) => Promise<DeployWireResult>;\n modules: () => Record<string, string>;\n },\n fleet?: FleetHandles,\n /**\n * Tier 3 Slice 8 follow-on (replica write-forwarding): set only when THIS node is itself a\n * `--replica` configured with `--writer-url` — i.e. it forwards mutations/actions elsewhere\n * via its own `ReplicaWriteForwarder` (`replica-forward.ts`). Used SOLELY for the `/api/run`\n * single-hop defensive guard below; the value itself is never read here (a plain presence\n * check — `boot.ts`/`serve.ts` own using it to construct the actual forwarder).\n */\n replicaWriterUrl?: string,\n): Promise<HttpResponse> {\n // Fleet write-forwarding target: a sync node's `WriteForwarder` POSTs mutations/actions here.\n // Enabled only in fleet mode (harmless on the writer — `runtime.run` executes locally there).\n // Admin-key gated (same bearer as `/_admin/*`): it can run any function under an arbitrary\n // identity, so it must never be reachable without the deployment admin key.\n if (fleet && req.method === \"POST\" && req.path === \"/_fleet/run\") {\n if (!admin || !verifyAdminKey(admin.key, bearer(req.authorization))) return json(401, { error: \"unauthorized\" });\n try {\n const p = JSON.parse(req.body ?? \"{}\") as {\n path?: string;\n args?: JSONValue;\n identity?: string | null;\n kind?: string;\n shardId?: string;\n /** Set by the forwarder (T2, single-hop guard) — signals this is a fleet-internal hop, not\n * a fresh dispatch, so the receiver checks ownership itself rather than trusting the caller. */\n forwarded?: boolean;\n /** Fleet B3, D3 (effectively-once forwarding): the forwarder's one-per-logical-write UUID,\n * reused verbatim across its retry-once — see `WriteForwarder.forward`. Absent for a\n * non-forwarded call (e.g. a fleet-internal system forward from an older node, or any\n * direct non-fleet caller), which carries no idempotency tracking at all. */\n idempotencyKey?: string;\n /** Receipted Outbox: the durable client dedup key (verdict §(c)). Present → this OWNER\n * classifies the write (the placement rule — repair 3); a recorded verdict replays here. */\n clientId?: string;\n seq?: number;\n };\n if (!p.path) return json(400, { error: \"missing function path\" });\n const identity = p.identity ?? null;\n const shardId = (p.shardId ?? DEFAULT_SHARD) as ShardId;\n const dedup = p.clientId !== undefined && p.seq !== undefined ? { clientId: p.clientId, seq: p.seq } : undefined;\n // Single-hop guard (B2b, D1 spec-review edit): a forward that lands on a node which is ALSO\n // not `shardId`'s owner (a point-in-time race during rebalance/failover convergence) must\n // NEVER re-forward — that would let a forward chase a moving target unboundedly. Reject with\n // a typed, RETRYABLE error instead; the ORIGINAL forwarder's refresh+retry-once re-reads the\n // lease and re-routes to the current owner. `isLocalWriter` is optional (older/stub\n // `FleetHandles`) — absent, this check is skipped (fail open): ordinary, non-forwarded traffic\n // is unaffected either way, since the executor's own per-shard router already forwards\n // correctly on a genuine ownership mismatch.\n if (p.forwarded && fleet.isLocalWriter && !fleet.isLocalWriter(shardId)) {\n throw new NotShardOwnerError(\n `fleet: this node is not the owner of shard '${shardId}' — refresh and retry against the current owner`,\n );\n }\n\n const idempotencyKey = p.idempotencyKey;\n // SELECT-first (Fleet B3, D3): a duplicate delivery of an ALREADY-committed write replays\n // without ever touching the runtime — no re-execution, no double side effects. `idempotencyLookup`\n // is optional (older/stub `FleetHandles`) — absent, this whole path is skipped and behavior is\n // byte-identical to before this feature existed.\n //\n // Receipted Outbox precedence (verdict Risk R2): when a DURABLE client dedup key is present, the\n // client-verdict classification (run inside `runtime.run` below) is authoritative and runs\n // BEFORE any fleet-idempotency decision — so skip the per-hop fleet pre-select here. The client\n // verdict outlives the fleet key's TTL and catches BOTH the cross-reconnect resend and the\n // same-hop retry-once (each mints a fresh `idempotencyKey`, so the fleet pre-select would miss\n // a resend anyway). The fleet `idempotencyKey` still rides `commitMeta` as a belt-and-suspenders\n // guard, but the client receipts guard is the real barrier.\n if (idempotencyKey && fleet.idempotencyLookup && !dedup) {\n const hit = await fleet.idempotencyLookup(idempotencyKey);\n if (hit) return json(200, idempotencyReplayBody(hit));\n }\n\n // `_system:*`/`_storage:*` (and any other underscore-namespaced) built-in mutations reach here\n // when a privileged doc mutation — the admin dashboard editor's `_system:patchDocument`/\n // `deleteDocument`/`insertDocument`, which already carries its resolved `shardId` (B2a) — is\n // forwarded here because this node doesn't hold that shard (B2b, D1: \"the router forwards them\n // like any other when the shard isn't held\"). `runtime.run`'s PUBLIC gate rejects any\n // underscore-segment path outright (`FunctionNotFoundError`), so those must instead route\n // through the SAME privileged entrypoint the origin node used — `runSystem`, with the resolved\n // `shardId` threaded through — rather than the public `run`.\n const isInternalForwardPath = p.path.split(\":\").some((seg) => seg.startsWith(\"_\"));\n // Opaque commit metadata (Fleet B3, D3): threaded through `run`/`runSystem` -> `RunOptions.\n // commitMeta` -> ... -> the fleet commit guard's atomic `fleet_idempotency` INSERT. Actions\n // never reach a top-level commit themselves (their inner `ctx.runMutation` calls are each a\n // fresh `invoke` with their own options), so this is a harmless no-op for `kind === \"action\"`.\n const commitMeta = idempotencyKey ? { idempotencyKey } : undefined;\n\n let result: Awaited<ReturnType<typeof runtime.run>>;\n try {\n result =\n p.kind === \"action\"\n ? await runtime.runAction(p.path, p.args ?? {}, { identity })\n : isInternalForwardPath\n ? await runtime.runSystem(p.path, p.args ?? {}, { shardId, commitMeta })\n : await runtime.run(p.path, p.args ?? {}, { identity, commitMeta, dedup });\n } catch (runErr) {\n // The concurrent-duplicate race's loser (Fleet B3, D3 spec-review requirement): this\n // attempt ALSO passed the SELECT-miss above, then its own commit guard's `fleet_idempotency`\n // INSERT collided with a sibling attempt that committed first — aborting this entire commit\n // transaction (nothing landed; see `installCommitGuard`'s doc comment). That is NOT a\n // caller-visible failure: re-SELECT the row the winner just committed and replay it, rather\n // than surfacing a generic 500 for what is, from the caller's perspective, a successful\n // (if duplicated) write.\n if (idempotencyKey && fleet.idempotencyLookup && isFleetIdempotencyConflict(runErr)) {\n const hit = await fleet.idempotencyLookup(idempotencyKey);\n if (hit) return json(200, idempotencyReplayBody(hit));\n }\n throw runErr;\n }\n\n // Receipted Outbox: the owner classified this dedup forward as a REPLAY of a recorded verdict\n // (no commit happened) — surface it in its own `clientReplay` body (distinct from the fleet\n // per-hop `replayed` body). The sync node's `WriteForwarder` reads `clientReplay` and builds a\n // `MutationReplay`. Skips the fleet value-recording below (there is no fresh value).\n if (result.clientReplay) {\n return json(200, { clientReplay: result.clientReplay });\n }\n\n // Post-run best-effort value recording (Fleet B3, D3): the guard only ever saw `commitTs` (the\n // VALUE isn't known inside the commit transaction), so record it now that `run`/`runAction`/\n // `runSystem` has returned. A failure here (e.g. a transient connection hiccup) must NOT fail\n // an otherwise-successful mutation response — a later replay simply reports `valueMissing:\n // true` for this key, the documented crash-window contract.\n if (idempotencyKey && fleet.idempotencyRecordValue) {\n try {\n await fleet.idempotencyRecordValue(idempotencyKey, convexToJson(result.value as Value));\n } catch {\n // best-effort — see the doc comment above.\n }\n }\n\n // Stringified: a replica's `WriteForwarder` waits on this via `ReplicaTailer.waitFor` for\n // read-your-own-writes, and bigints don't survive JSON.stringify. `result.oplog` is null for\n // actions (they never commit directly) — fall back to `result.commitTs`, which the executor\n // now surfaces as the MAX commitTs across the action's inner runMutation/runAction invokes\n // (0n if it committed nothing), so fleet RYOW covers actions too.\n // `shardId` (B2a, additive): the shard this run committed on, from the commit's oplog (present\n // for a committed mutation; absent for a read/action that committed nothing — omitted then).\n return json(200, {\n value: convexToJson(result.value as Value),\n commitTs: String(result.oplog?.commitTs ?? result.commitTs ?? 0n),\n ...(result.oplog?.shardId !== undefined ? { shardId: result.oplog.shardId } : {}),\n });\n } catch (e) {\n // Preserve the typed error's identity across the fleet hop: return its REAL http status and the\n // full serialized error (`errorJson`) so the forwarding SYNC node can rehydrate it and surface\n // the correct 4xx/5xx + code/retryable, instead of collapsing every forwarded failure to a 500.\n // `error` (the flat message) is kept for back-compat / human-readable logs.\n const err = toHelipodError(e);\n return json(getHttpStatus(err), { error: err.message, code: err.code, errorJson: err.toJSON() });\n }\n }\n if (admin && deploy && req.method === \"GET\" && req.path === \"/_admin/deploy/modules\") {\n if (!verifyAdminKey(admin.key, bearer(req.authorization))) return json(401, { ok: false, error: \"unauthorized\" });\n return json(200, deploy.modules());\n }\n if (admin && deploy && req.method === \"POST\" && req.path === \"/_admin/deploy\") {\n if (!verifyAdminKey(admin.key, bearer(req.authorization))) return json(401, { ok: false, error: \"unauthorized\" });\n let payload:\n | { files: Array<{ path: string; code: string }> }\n | { changed: Array<{ path: string; code: string }>; unchanged: Array<{ path: string; sha256: string }> };\n try {\n payload = JSON.parse(req.body ?? \"{}\");\n } catch {\n return json(400, { ok: false, kind: \"load-error\", error: \"invalid deploy payload\" });\n }\n const result = await deploy.apply(payload);\n const status = result.ok ? 200 : result.kind === \"schema-incompatible\" || result.kind === \"stale-base\" ? 409 : 400;\n return json(status, result);\n }\n // The wake seam's firing half (`serve --wake-url`): the host's alarm went off and is telling this\n // process to run whatever driver timers are due. It CAN'T be a direct call — the alarm lives in the\n // host (a Durable Object), across a network boundary from this process — and the same request that\n // fires the timers is also what BOOTS a stopped container: one mechanism, both jobs. Admin-key\n // gated like every other `/_admin/*` route (it drives privileged driver work), and registered\n // unconditionally: without a `wakeHost` nothing ever arms a wake, and `fireDueTimers()` is a no-op.\n if (admin && req.method === \"POST\" && req.path === \"/_admin/wake\") {\n if (!verifyAdminKey(admin.key, bearer(req.authorization))) return json(401, { ok: false, error: \"unauthorized\" });\n runtime.fireDueTimers();\n return json(200, { ok: true });\n }\n if (admin && req.path.startsWith(\"/_admin/\")) {\n const res = await handleAdminRequest(admin.api, admin.key, {\n method: req.method,\n path: req.path,\n query: req.query ?? {},\n body: req.body,\n authorization: req.authorization,\n });\n return json(res.status, res.body);\n }\n if (req.method === \"GET\" && (req.path === \"/_dashboard\" || req.path === \"/_dashboard/\")) {\n return html(dashboardHtml(info));\n }\n if (req.method === \"GET\" && req.path === \"/api/health\") {\n // Fleet frontier-lag observability (B2a, D5): additive `fleet` field when running under --fleet\n // and a frontier reading is available. `frontier` is a stringified bigint (JSON can't carry one).\n const fs = fleet?.frontierStats?.() ?? null;\n // Fleet B4 (T4) group-commit counters: additive `fleet.groupCommit`, nested inside the SAME `fleet`\n // gate as the frontier reading above (so a build/fleet-mode that hasn't reported ANY health data\n // yet stays byte-identical to before this field existed). `gc` is zeroed (not absent) when\n // `HELIPOD_GROUP_COMMIT` is off — `groupCommitStats()` structurally returns zeros in that case\n // (see its doc comment) — and the field is omitted only when `groupCommitStats` itself isn't wired\n // (an older/stub `FleetHandles`), which is what `?.` falls through to `null` for.\n const gc = fleet?.groupCommitStats?.() ?? null;\n return json(200, {\n status: \"ok\",\n functions: info.functions.length,\n tables: info.tables.length,\n ...(fs\n ? { fleet: { frontier: String(fs.frontier), lagMs: fs.lagMs, pinningShard: fs.pinningShard, ...(gc ? { groupCommit: gc } : {}) } }\n : {}),\n });\n }\n if (req.method === \"POST\" && req.path === \"/api/run\") {\n try {\n const parsed = JSON.parse(req.body ?? \"{}\") as {\n path?: string;\n args?: JSONValue;\n /** Tier 3 Slice 8 follow-on (replica write-forwarding): set by `ReplicaWriteForwarder`\n * (`replica-forward.ts`) — signals this is ALREADY a forward hop, so the receiver must\n * not treat it as a fresh direct call needing forwarding of its own. Ignored on a WRITER\n * (which has no `writeRouter` and so runs everything locally regardless); checked below\n * ONLY to guard against a request landing on ANOTHER replica by misconfiguration. */\n forwarded?: boolean;\n };\n if (!parsed.path) return json(400, { error: \"missing function path\" });\n // Single-hop guard (Tier 3 Slice 8 follow-on): a `forwarded: true` request must land on the\n // actual writer. If THIS node is itself a replica configured to forward elsewhere\n // (`replicaWriterUrl` set), receiving one here means some caller's `--writer-url` points at\n // this replica instead of the real writer — reject clearly rather than silently forwarding\n // again (which, under a symmetric misconfiguration, could loop between two replicas).\n if (parsed.forwarded && replicaWriterUrl !== undefined) {\n return json(409, {\n error:\n \"helipod: this node is a read replica configured to forward writes (--writer-url) and \" +\n \"received an already-forwarded write — check --writer-url points at the actual writer, not another replica.\",\n });\n }\n // Identity pass-through (Tier 3 Slice 8 follow-on): `/api/run` had no identity support\n // before this — additive/backward-compatible (no `Authorization` header -> `identity:\n // null`, byte-identical to before). Mirrors the httpAction router's own bearer -> identity\n // convention (the caller's raw token is passed straight through, unresolved).\n const identity = bearer(req.authorization) ?? null;\n const result = await runtime.run(parsed.path, parsed.args ?? {}, { identity });\n return json(200, {\n value: convexToJson(result.value as Value),\n committed: result.committed,\n // Additive: lets a forwarding replica's `ReplicaWriteForwarder` report the writer's real\n // commitTs instead of a hardcoded 0 — see `EmbeddedRuntime.forwardedResult`'s doc comment.\n commitTs: String(result.oplog?.commitTs ?? result.commitTs ?? 0n),\n });\n } catch (e) {\n const err = toHelipodError(e);\n return json(getHttpStatus(err), { error: err.message, code: err.code });\n }\n }\n // User httpAction routes — matched AFTER the built-ins, only for non-reserved paths.\n const match = routes && routes.length > 0 ? matchRoute(routes, req.method, req.path) : undefined;\n if (match) {\n // Fleet sync node: an httpAction runs like an action (it may `ctx.runMutation`), so the WHOLE\n // request is proxied to the writer and executed there — never run locally on a replica. The\n // writer's Response (status/headers/body) is streamed back verbatim.\n if (fleet && fleet.role() === \"sync\") {\n try {\n const writerUrl = await fleet.writerUrl();\n if (!writerUrl) return json(503, { error: \"fleet: no writer available\" });\n const qs = req.query && Object.keys(req.query).length ? \"?\" + new URLSearchParams(req.query).toString() : \"\";\n const target = `${writerUrl.replace(/\\/$/, \"\")}${req.path}${qs}`;\n const headers = new Headers(req.headers ?? {});\n if (req.authorization && !headers.has(\"authorization\")) headers.set(\"authorization\", req.authorization);\n // `fetch` recomputes these from the URL/body; a stale copied value would mismatch the\n // re-encoded body (or point at this node, not the writer).\n headers.delete(\"host\");\n headers.delete(\"content-length\");\n const hasBody = req.method !== \"GET\" && req.method !== \"HEAD\" && req.body !== undefined;\n const resp = await fetch(target, { method: req.method, headers, ...(hasBody ? { body: req.body } : {}) });\n const outHeaders: Record<string, string> = {};\n resp.headers.forEach((v, k) => { outHeaders[k] = v; });\n // Hop-by-hop / body-framing headers: `undici` already decompressed the body (so a copied\n // content-encoding/content-length would mismatch what we actually relay), and\n // transfer-encoding/connection are connection-scoped, never meaningful to forward verbatim.\n delete outHeaders[\"content-encoding\"];\n delete outHeaders[\"content-length\"];\n delete outHeaders[\"transfer-encoding\"];\n delete outHeaders[\"connection\"];\n return { status: resp.status, headers: outHeaders, body: await resp.text() };\n } catch (e) {\n return json(502, { error: `fleet: httpAction proxy to writer failed: ${e instanceof Error ? e.message : String(e)}` });\n }\n }\n try {\n const headers = new Headers(req.headers ?? {});\n if (req.authorization && !headers.has(\"authorization\")) headers.set(\"authorization\", req.authorization);\n const qs = req.query && Object.keys(req.query).length ? \"?\" + new URLSearchParams(req.query).toString() : \"\";\n const host = headers.get(\"host\") ?? \"localhost\";\n const url = `http://${host}${req.path}${qs}`;\n const hasBody = req.method !== \"GET\" && req.method !== \"HEAD\" && req.body !== undefined;\n const request = new Request(url, { method: req.method, headers, ...(hasBody ? { body: req.body } : {}) });\n\n const auth = headers.get(\"authorization\") ?? \"\";\n const identity = auth.startsWith(\"Bearer \") ? auth.slice(\"Bearer \".length) : null;\n\n const response = await runtime.runHttpAction(match.handlerPath, request, { identity });\n if (!(response instanceof Response)) {\n return json(500, { error: \"httpAction must return a Response\" });\n }\n const outHeaders: Record<string, string> = {};\n response.headers.forEach((v, k) => { outHeaders[k] = v; });\n return { status: response.status, headers: outHeaders, body: await response.text() };\n } catch (e) {\n const err = toHelipodError(e);\n return json(getHttpStatus(err), { error: err.message, code: err.code });\n }\n }\n\n return json(404, { error: \"not found\" });\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { JSONValue } from '@helipod/values';
|
|
2
|
+
import { EmbeddedRuntime } from '@helipod/runtime-embedded';
|
|
3
|
+
import { AdminApi } from '@helipod/admin';
|
|
4
|
+
import { ResolvedRoute } from './project.js';
|
|
5
|
+
|
|
6
|
+
/** The wire-safe subset of `DeployResult` — identical shape minus `modules` (a `Map`, which must
|
|
7
|
+
* never cross HTTP). `serve.ts`'s `deploy.apply` closure strips it before returning; this is the
|
|
8
|
+
* type that actually travels over `/_admin/deploy`. */
|
|
9
|
+
type DeployWireResult = {
|
|
10
|
+
ok: true;
|
|
11
|
+
rev: string;
|
|
12
|
+
functions: number;
|
|
13
|
+
} | {
|
|
14
|
+
ok: false;
|
|
15
|
+
kind: "load-error" | "schema-incompatible" | "stale-base";
|
|
16
|
+
error: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* HTTP routing for the dev server — a pure function over the runtime so it's testable without
|
|
21
|
+
* a socket. Routes: the `_dashboard` status page, a health check, `POST /api/run` for direct
|
|
22
|
+
* function invocation, and `/_admin/*` for the admin API (behind an admin key).
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
interface HttpRequest {
|
|
26
|
+
method: string;
|
|
27
|
+
path: string;
|
|
28
|
+
body?: string;
|
|
29
|
+
query?: Record<string, string>;
|
|
30
|
+
authorization?: string;
|
|
31
|
+
headers?: Record<string, string>;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The fleet node handle the HTTP layer consumes (structural mirror of `@helipod/fleet`'s
|
|
35
|
+
* `FleetHandles` — declared here so core cli has no static dependency on the enterprise package).
|
|
36
|
+
* `role()` decides whether a sync node proxies public httpActions to the writer; `writerUrl()` is
|
|
37
|
+
* that proxy target. Present only when `serve --fleet` is active.
|
|
38
|
+
*/
|
|
39
|
+
interface FleetHandles {
|
|
40
|
+
role(): "sync" | "writer";
|
|
41
|
+
writerUrl(): Promise<string>;
|
|
42
|
+
onPromoted(cb: () => void): void;
|
|
43
|
+
/** Frontier-lag reading for /api/health (B2a, D5): the fleet-wide `min(frontier_ts)`, how long it's
|
|
44
|
+
* been stuck (ms), and which shard is pinning it. Null before the first frontier beat. Optional so
|
|
45
|
+
* older/stub `FleetHandles` (and pre-B2a fleet builds) satisfy the structural mirror; the health
|
|
46
|
+
* handler optional-chains it. The real `@helipod/fleet` node always provides it. */
|
|
47
|
+
frontierStats?(): {
|
|
48
|
+
frontier: bigint;
|
|
49
|
+
lagMs: number;
|
|
50
|
+
pinningShard: string;
|
|
51
|
+
} | null;
|
|
52
|
+
/**
|
|
53
|
+
* Group-commit counters (Fleet B4, T4 health observability): the aggregate `EmbeddedRuntime.
|
|
54
|
+
* groupCommitStats()` reading plus a derived `flushesPerSec` (a rolling delta between successive
|
|
55
|
+
* calls — see `@helipod/fleet`'s `node.ts`). Structurally all-zero when `HELIPOD_GROUP_COMMIT`
|
|
56
|
+
* is off (the underlying counters are simply never touched on the single-commit path), so the
|
|
57
|
+
* health handler below shows zeroed fields rather than omitting them once fleet mode + this method
|
|
58
|
+
* are both present — `undefined` is reserved for "not wired at all" (an older/stub `FleetHandles`),
|
|
59
|
+
* mirroring `frontierStats?`'s same optional-for-backward-compat shape. The real `@helipod/fleet`
|
|
60
|
+
* node always provides it.
|
|
61
|
+
*/
|
|
62
|
+
groupCommitStats?(): {
|
|
63
|
+
lastBatchSize: number;
|
|
64
|
+
maxBatchSize: number;
|
|
65
|
+
flushCount: number;
|
|
66
|
+
flushesPerSec: number;
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Per-shard ownership (B2b, D1): true when THIS node currently holds `shardId`'s write lease.
|
|
70
|
+
* Backs the `/_fleet/run` single-hop guard below — optional so older/stub `FleetHandles` satisfy
|
|
71
|
+
* the structural mirror; the guard skips (fail open) when absent. The real `@helipod/fleet` node
|
|
72
|
+
* always provides it (delegates to its `WriteForwarder.isLocalWriter`).
|
|
73
|
+
*/
|
|
74
|
+
isLocalWriter?(shardId: string): boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Effectively-once forwarding (Fleet B3, D3): read back `key`'s `fleet_idempotency` row for a
|
|
77
|
+
* replay decision — a hit means this write already committed (possibly on a sibling concurrent
|
|
78
|
+
* attempt) and the caller must NOT re-execute. Optional so older/stub `FleetHandles` satisfy the
|
|
79
|
+
* structural mirror; the `/_fleet/run` handler below skips the whole idempotency path when absent
|
|
80
|
+
* (byte-identical to before this feature existed). The real `@helipod/fleet` node always
|
|
81
|
+
* provides it (delegates to `LeaseManager.lookupIdempotency`).
|
|
82
|
+
*/
|
|
83
|
+
idempotencyLookup?(key: string): Promise<{
|
|
84
|
+
commitTs: bigint;
|
|
85
|
+
hasValue: boolean;
|
|
86
|
+
value: JSONValue | null;
|
|
87
|
+
oversized: boolean;
|
|
88
|
+
} | null>;
|
|
89
|
+
/** Best-effort post-run recording of a forwarded mutation's return value — see
|
|
90
|
+
* `LeaseManager.recordIdempotencyValue`. Optional for the same reason as `idempotencyLookup`. */
|
|
91
|
+
idempotencyRecordValue?(key: string, value: JSONValue): Promise<void>;
|
|
92
|
+
stop(): Promise<void>;
|
|
93
|
+
}
|
|
94
|
+
interface HttpResponse {
|
|
95
|
+
status: number;
|
|
96
|
+
headers: Record<string, string>;
|
|
97
|
+
body: string;
|
|
98
|
+
}
|
|
99
|
+
interface ServerInfo {
|
|
100
|
+
functions: string[];
|
|
101
|
+
tables: string[];
|
|
102
|
+
}
|
|
103
|
+
declare function handleHttpRequest(runtime: EmbeddedRuntime, req: HttpRequest, info: ServerInfo, admin?: {
|
|
104
|
+
api: AdminApi;
|
|
105
|
+
key: string;
|
|
106
|
+
}, routes?: ResolvedRoute[], deploy?: {
|
|
107
|
+
apply: (payload: {
|
|
108
|
+
files: Array<{
|
|
109
|
+
path: string;
|
|
110
|
+
code: string;
|
|
111
|
+
}>;
|
|
112
|
+
} | {
|
|
113
|
+
changed: Array<{
|
|
114
|
+
path: string;
|
|
115
|
+
code: string;
|
|
116
|
+
}>;
|
|
117
|
+
unchanged: Array<{
|
|
118
|
+
path: string;
|
|
119
|
+
sha256: string;
|
|
120
|
+
}>;
|
|
121
|
+
}) => Promise<DeployWireResult>;
|
|
122
|
+
modules: () => Record<string, string>;
|
|
123
|
+
}, fleet?: FleetHandles,
|
|
124
|
+
/**
|
|
125
|
+
* Tier 3 Slice 8 follow-on (replica write-forwarding): set only when THIS node is itself a
|
|
126
|
+
* `--replica` configured with `--writer-url` — i.e. it forwards mutations/actions elsewhere
|
|
127
|
+
* via its own `ReplicaWriteForwarder` (`replica-forward.ts`). Used SOLELY for the `/api/run`
|
|
128
|
+
* single-hop defensive guard below; the value itself is never read here (a plain presence
|
|
129
|
+
* check — `boot.ts`/`serve.ts` own using it to construct the actual forwarder).
|
|
130
|
+
*/
|
|
131
|
+
replicaWriterUrl?: string): Promise<HttpResponse>;
|
|
132
|
+
|
|
133
|
+
export { type DeployWireResult as D, type FleetHandles as F, type HttpRequest as H, type ServerInfo as S, type HttpResponse as a, handleHttpRequest as h };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import '@helipod/values';
|
|
2
|
+
import '@helipod/runtime-embedded';
|
|
3
|
+
import '@helipod/admin';
|
|
4
|
+
import './project.js';
|
|
5
|
+
export { F as FleetHandles, H as HttpRequest, a as HttpResponse, S as ServerInfo, h as handleHttpRequest } from './http-handler-BFprdo3f.js';
|
|
6
|
+
import '@helipod/executor';
|
|
7
|
+
import '@helipod/codegen';
|
|
8
|
+
import '@helipod/component';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|