@rayfold/server 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/LICENSE +202 -0
- package/NOTICE +10 -0
- package/README.md +70 -0
- package/args.d.ts +15 -0
- package/args.js +226 -0
- package/args.js.map +1 -0
- package/batch.d.ts +54 -0
- package/batch.js +483 -0
- package/batch.js.map +1 -0
- package/bindings.d.ts +37 -0
- package/bindings.js +284 -0
- package/bindings.js.map +1 -0
- package/capability-scope.d.ts +8 -0
- package/capability-scope.js +19 -0
- package/capability-scope.js.map +1 -0
- package/capability.d.ts +56 -0
- package/capability.js +112 -0
- package/capability.js.map +1 -0
- package/context.d.ts +74 -0
- package/context.js +109 -0
- package/context.js.map +1 -0
- package/core.d.ts +18 -0
- package/core.js +18 -0
- package/core.js.map +1 -0
- package/cost.d.ts +15 -0
- package/cost.js +110 -0
- package/cost.js.map +1 -0
- package/executor.d.ts +89 -0
- package/executor.js +695 -0
- package/executor.js.map +1 -0
- package/guard.d.ts +33 -0
- package/guard.js +65 -0
- package/guard.js.map +1 -0
- package/http.d.ts +33 -0
- package/http.js +379 -0
- package/http.js.map +1 -0
- package/index.d.ts +8 -0
- package/index.js +9 -0
- package/index.js.map +1 -0
- package/instrumentation.d.ts +36 -0
- package/instrumentation.js +2 -0
- package/instrumentation.js.map +1 -0
- package/live.d.ts +37 -0
- package/live.js +240 -0
- package/live.js.map +1 -0
- package/mcp.d.ts +55 -0
- package/mcp.js +314 -0
- package/mcp.js.map +1 -0
- package/openapi.d.ts +11 -0
- package/openapi.js +124 -0
- package/openapi.js.map +1 -0
- package/package.json +53 -0
- package/policy.d.ts +17 -0
- package/policy.js +64 -0
- package/policy.js.map +1 -0
- package/protocol.d.ts +139 -0
- package/protocol.js +98 -0
- package/protocol.js.map +1 -0
- package/server.d.ts +58 -0
- package/server.js +79 -0
- package/server.js.map +1 -0
- package/usage.d.ts +39 -0
- package/usage.js +44 -0
- package/usage.js.map +1 -0
- package/views.d.ts +33 -0
- package/views.js +108 -0
- package/views.js.map +1 -0
- package/wiring.d.ts +16 -0
- package/wiring.js +57 -0
- package/wiring.js.map +1 -0
- package/ws.d.ts +21 -0
- package/ws.js +209 -0
- package/ws.js.map +1 -0
package/http.js
ADDED
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
/** HTTP transport for Node. Spec: spec/04-frames-and-transport.md §4. */
|
|
2
|
+
import { createServer } from "node:http";
|
|
3
|
+
import { canonicalJson, fieldsOf, fromBase64url, sha256Hex, annotation } from "@rayfold/schema";
|
|
4
|
+
import { HTTP_STATUS, RayfoldError } from "./protocol.js";
|
|
5
|
+
import { referencesViewer } from "@rayfold/schema";
|
|
6
|
+
import { RbCodec, RB_CONTENT_TYPE } from "@rayfold/rb";
|
|
7
|
+
import { openApiFor } from "./openapi.js";
|
|
8
|
+
import { BodyTooLarge, PROBLEM_TYPE_BASE, hostProblem, mediaType, originProblem, refuse, refuseBody } from "./guard.js";
|
|
9
|
+
const FRAMES_TYPE = "application/rayfold-frames+json";
|
|
10
|
+
const codecs = new WeakMap();
|
|
11
|
+
/** One RB codec per server: its key dictionary comes from the schema. */
|
|
12
|
+
export function codecFor(server) {
|
|
13
|
+
let c = codecs.get(server);
|
|
14
|
+
if (!c)
|
|
15
|
+
codecs.set(server, (c = new RbCodec(server.ir)));
|
|
16
|
+
return c;
|
|
17
|
+
}
|
|
18
|
+
const PROBLEM_TYPE = "application/problem+json";
|
|
19
|
+
const KEEP_ALIVE_RB = Uint8Array.of(0);
|
|
20
|
+
const BODY_TYPES = new Set(["application/rayfold+json", "application/json", RB_CONTENT_TYPE]);
|
|
21
|
+
/** The IR without policy expressions: clients need names and types, not how access is decided. */
|
|
22
|
+
export function publicIR(ir) {
|
|
23
|
+
return JSON.parse(JSON.stringify(ir), (_k, v) => {
|
|
24
|
+
if (!v || typeof v !== "object" || Array.isArray(v))
|
|
25
|
+
return v;
|
|
26
|
+
const o = v;
|
|
27
|
+
const isPolicy = (o["name"] === "allow" || o["name"] === "deny") && !("type" in o) && !!o["args"] && typeof o["args"] === "object" && !Array.isArray(o["args"]);
|
|
28
|
+
return isPolicy ? { ...o, args: {} } : v;
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
export function createHttpHandler(server, opts = {}) {
|
|
32
|
+
const base = opts.path ?? "/rayfold";
|
|
33
|
+
const maxBody = opts.maxBody ?? 1_048_576;
|
|
34
|
+
return async (req, res) => {
|
|
35
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
36
|
+
res.setHeader("Rayfold-Schema", server.hash);
|
|
37
|
+
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
38
|
+
const badHost = hostProblem(req, opts);
|
|
39
|
+
if (badHost)
|
|
40
|
+
return refuse(res, 403, "permission_denied", badHost);
|
|
41
|
+
if (opts.cors) {
|
|
42
|
+
res.setHeader("Access-Control-Allow-Origin", opts.cors);
|
|
43
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, Rayfold-Client, Rayfold-Deadline, Rayfold-Safe");
|
|
44
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, QUERY, OPTIONS");
|
|
45
|
+
if (req.method === "OPTIONS") {
|
|
46
|
+
res.writeHead(204).end();
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (!url.pathname.startsWith(base)) {
|
|
51
|
+
problem(res, "not_found", `No route for ${url.pathname}`);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const sub = url.pathname.slice(base.length);
|
|
55
|
+
try {
|
|
56
|
+
if (sub === "/manifest" && req.method === "GET") {
|
|
57
|
+
if (opts.manifest === "off")
|
|
58
|
+
throw new RayfoldError("not_found", `No route for ${url.pathname}`);
|
|
59
|
+
json(res, 200, { ...server.manifest(), schema: opts.manifest === "full" ? server.ir : publicIR(server.ir) });
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (sub === "/openapi.json" && req.method === "GET") {
|
|
63
|
+
json(res, 200, openApiFor(server.ir));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
let envelope;
|
|
67
|
+
let safe = false;
|
|
68
|
+
if (sub === "" || sub === "/") {
|
|
69
|
+
if (req.method === "POST" || req.method === "QUERY") {
|
|
70
|
+
// JSON-only bodies force browsers into a CORS preflight, so a cross-site form or text/plain post cannot run anything.
|
|
71
|
+
const ct = mediaType(req);
|
|
72
|
+
if (!BODY_TYPES.has(ct))
|
|
73
|
+
return refuse(res, 415, "invalid_argument", `Content-Type ${ct || "(none)"} is not accepted; send application/rayfold+json`, "unsupported_media_type", { "Accept-Post": [...BODY_TYPES].join(", ") });
|
|
74
|
+
// Safe requests (QUERY, or POST with Rayfold-Safe, which may hold only queries) cannot change data. A foreign page
|
|
75
|
+
// cannot send them without a CORS preflight and cannot read the answer, so only data-changing requests need the
|
|
76
|
+
// Origin check. This keeps reads working behind proxies that rewrite Host.
|
|
77
|
+
const declaredSafe = req.method === "QUERY" || req.headers["rayfold-safe"] === "true";
|
|
78
|
+
const badOrigin = declaredSafe ? null : originProblem(req, opts);
|
|
79
|
+
if (badOrigin)
|
|
80
|
+
return refuse(res, 403, "permission_denied", badOrigin);
|
|
81
|
+
const body = await readBody(req, maxBody);
|
|
82
|
+
if (ct === RB_CONTENT_TYPE) {
|
|
83
|
+
try {
|
|
84
|
+
envelope = codecFor(server).decode(new Uint8Array(body));
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
throw new RayfoldError("invalid_argument", "Body is not valid RB");
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
try {
|
|
92
|
+
envelope = JSON.parse(body.toString("utf8"));
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
throw new RayfoldError("invalid_argument", "Body is not valid JSON");
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
safe = req.method === "QUERY" || req.headers["rayfold-safe"] === "true";
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
res.setHeader("Allow", "POST, QUERY");
|
|
102
|
+
res.setHeader("Accept-Query", "application/rayfold+json");
|
|
103
|
+
throw new RayfoldError("unimplemented", `Method ${req.method} not allowed on ${base}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
else if (req.method === "GET") {
|
|
107
|
+
// GET /rayfold/{op}?a=<b64url json>&s=<shape id>&v=<b64url json>
|
|
108
|
+
const op = sub.slice(1);
|
|
109
|
+
const a = url.searchParams.get("a");
|
|
110
|
+
const s = url.searchParams.get("s");
|
|
111
|
+
const v = url.searchParams.get("v");
|
|
112
|
+
const one = { id: 1, op };
|
|
113
|
+
if (a)
|
|
114
|
+
one.args = parseB64(a, "a");
|
|
115
|
+
if (s)
|
|
116
|
+
one.shape = s;
|
|
117
|
+
if (v)
|
|
118
|
+
one.vars = parseB64(v, "v");
|
|
119
|
+
envelope = { ops: [one] };
|
|
120
|
+
safe = true;
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
throw new RayfoldError("not_found", `No route for ${req.method} ${url.pathname}`);
|
|
124
|
+
}
|
|
125
|
+
// a body that parses but is no envelope (null, a number, ops that are not a list) is the client's error, not a 500
|
|
126
|
+
if (!envelope || typeof envelope !== "object" || Array.isArray(envelope))
|
|
127
|
+
throw new RayfoldError("invalid_argument", "Body must be { ops: [...] }");
|
|
128
|
+
if (safe && Array.isArray(envelope.ops) && envelope.ops.some((o) => server.ir.ops[o?.op ?? ""]?.kind !== "query")) {
|
|
129
|
+
throw new RayfoldError("invalid_argument", "Safe requests (GET/QUERY) may only contain queries");
|
|
130
|
+
}
|
|
131
|
+
const client = req.headers["rayfold-client"];
|
|
132
|
+
const deadline = req.headers["rayfold-deadline"];
|
|
133
|
+
const traceparent = req.headers["traceparent"];
|
|
134
|
+
const tracestate = req.headers["tracestate"];
|
|
135
|
+
if (typeof client === "string" || typeof deadline === "string" || typeof traceparent === "string") {
|
|
136
|
+
envelope.meta = { ...(envelope.meta ?? {}) };
|
|
137
|
+
if (typeof client === "string")
|
|
138
|
+
envelope.meta.client = client;
|
|
139
|
+
if (typeof deadline === "string" && /^\d+$/.test(deadline))
|
|
140
|
+
envelope.meta.deadline = Number(deadline);
|
|
141
|
+
if (typeof traceparent === "string") {
|
|
142
|
+
envelope.meta.traceparent = traceparent;
|
|
143
|
+
if (typeof tracestate === "string")
|
|
144
|
+
envelope.meta.tracestate = tracestate;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
const viewer = opts.viewer ? await opts.viewer(req) : null;
|
|
148
|
+
const ac = new AbortController();
|
|
149
|
+
req.on("close", () => ac.abort());
|
|
150
|
+
const accepted = new Set((req.headers["accept"] ?? "").split(",").map((t) => t.split(";")[0].trim().toLowerCase()));
|
|
151
|
+
const wantsRb = accepted.has(RB_CONTENT_TYPE) && !accepted.has(FRAMES_TYPE) && !accepted.has("application/json") && !accepted.has("application/rayfold+json");
|
|
152
|
+
const wantsSingle = !wantsRb && accepted.has("application/json") && Array.isArray(envelope.ops) && envelope.ops.length === 1;
|
|
153
|
+
if (wantsSingle || safe) {
|
|
154
|
+
// Buffer so we can set status / cache headers from the complete result.
|
|
155
|
+
const frames = [];
|
|
156
|
+
for await (const f of server.execute(envelope, { viewer, signal: ac.signal }))
|
|
157
|
+
frames.push(f);
|
|
158
|
+
if (safe)
|
|
159
|
+
applyCacheHeaders(server, envelope, frames, viewer, res);
|
|
160
|
+
if (wantsSingle && frames.length === 1) {
|
|
161
|
+
const f = frames[0];
|
|
162
|
+
const status = "error" in f ? HTTP_STATUS[f.error.code] : 200;
|
|
163
|
+
const body = canonicalJson(f);
|
|
164
|
+
if (safe && req.headers["if-none-match"] && req.headers["if-none-match"] === res.getHeader("ETag")) {
|
|
165
|
+
res.removeHeader("X-Content-Type-Options"); // no body to sniff; the cached response keeps its headers
|
|
166
|
+
res.writeHead(304).end();
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
res.writeHead(status, { "Content-Type": "application/json; charset=utf-8" }).end(body);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (safe && req.headers["if-none-match"] && req.headers["if-none-match"] === res.getHeader("ETag")) {
|
|
173
|
+
res.removeHeader("X-Content-Type-Options"); // no body to sniff; the cached response keeps its headers
|
|
174
|
+
res.writeHead(304).end();
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (wantsRb) {
|
|
178
|
+
res.writeHead(200, { "Content-Type": RB_CONTENT_TYPE }).end(Buffer.from(codecFor(server).encodeFrames(frames)));
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const body = frames.map((f) => JSON.stringify(f)).join("\n") + "\n";
|
|
182
|
+
res.writeHead(200, { "Content-Type": FRAMES_TYPE }).end(body);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
res.writeHead(200, { "Content-Type": wantsRb ? RB_CONTENT_TYPE : FRAMES_TYPE, "Cache-Control": "no-store", "X-Accel-Buffering": "no" });
|
|
186
|
+
res.flushHeaders();
|
|
187
|
+
const codec = wantsRb ? codecFor(server) : null;
|
|
188
|
+
let quiet = true;
|
|
189
|
+
const keepAlive = setInterval(() => {
|
|
190
|
+
if (quiet)
|
|
191
|
+
res.write(codec ? KEEP_ALIVE_RB : "\n");
|
|
192
|
+
quiet = true;
|
|
193
|
+
}, opts.keepAliveMs ?? 15_000);
|
|
194
|
+
try {
|
|
195
|
+
for await (const f of server.execute(envelope, { viewer, signal: ac.signal })) {
|
|
196
|
+
res.write(codec ? Buffer.from(codec.encodeFrames([f])) : JSON.stringify(f) + "\n");
|
|
197
|
+
quiet = false;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
finally {
|
|
201
|
+
clearInterval(keepAlive);
|
|
202
|
+
}
|
|
203
|
+
res.end();
|
|
204
|
+
}
|
|
205
|
+
catch (e) {
|
|
206
|
+
if (e instanceof BodyTooLarge && !res.headersSent)
|
|
207
|
+
return refuseBody(res, e);
|
|
208
|
+
if (res.headersSent) {
|
|
209
|
+
res.end();
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
const code = e instanceof RayfoldError ? e.code : "internal";
|
|
213
|
+
problem(res, code, e instanceof RayfoldError ? e.message : "Internal error", e instanceof RayfoldError ? e.toWire() : undefined);
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
/** Cache-Control/ETag for safe requests (spec 07 §2): min over @cache of ops and entity types touched. */
|
|
218
|
+
export function applyCacheHeaders(server, envelope, frames, viewer, res) {
|
|
219
|
+
let maxAge = Number.POSITIVE_INFINITY;
|
|
220
|
+
let swr = 0;
|
|
221
|
+
let scope = "public";
|
|
222
|
+
const consider = (annotations) => {
|
|
223
|
+
const c = annotations.find((a) => a.name === "cache");
|
|
224
|
+
if (c) {
|
|
225
|
+
const ma = c.args["maxAge"];
|
|
226
|
+
if (ma && typeof ma === "object" && "$duration" in ma)
|
|
227
|
+
maxAge = Math.min(maxAge, ma.$duration / 1000);
|
|
228
|
+
const sw = c.args["swr"];
|
|
229
|
+
if (sw && typeof sw === "object" && "$duration" in sw)
|
|
230
|
+
swr = Math.max(swr, sw.$duration / 1000);
|
|
231
|
+
const sc = c.args["scope"];
|
|
232
|
+
if (sc && typeof sc === "object" && "$ident" in sc && sc.$ident === "private")
|
|
233
|
+
scope = "private";
|
|
234
|
+
}
|
|
235
|
+
for (const a of annotations) {
|
|
236
|
+
if ((a.name === "allow" || a.name === "deny") && Object.values(a.args).some((v) => v && typeof v === "object" && "$expr" in v && referencesViewer(v.$expr)))
|
|
237
|
+
scope = "private";
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
for (const o of Array.isArray(envelope.ops) ? envelope.ops : []) {
|
|
241
|
+
const op = o && typeof o === "object" ? server.ir.ops[o.op] : undefined;
|
|
242
|
+
if (op)
|
|
243
|
+
consider(op.annotations);
|
|
244
|
+
}
|
|
245
|
+
// Only types and fields actually present in the response count (spec 07 s1: "touches such a field"). A compact frame
|
|
246
|
+
// leaves out `$type` wherever the schema fixes it, so the walk follows each op's return type, and reads `$type` only
|
|
247
|
+
// where it is still there, as on a union member.
|
|
248
|
+
const seen = new Set();
|
|
249
|
+
const walk = (v, t) => {
|
|
250
|
+
if (!v || typeof v !== "object")
|
|
251
|
+
return;
|
|
252
|
+
if (Array.isArray(v)) {
|
|
253
|
+
for (const x of v)
|
|
254
|
+
walk(x, t?.kind === "list" ? t.of : undefined);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const o = v;
|
|
258
|
+
const tn = o["$type"];
|
|
259
|
+
const ref = typeof tn === "string" ? { kind: "named", name: tn, nullable: false } : t?.kind === "named" ? t : undefined;
|
|
260
|
+
const def = ref ? server.ir.types[ref.name] : undefined;
|
|
261
|
+
if (def && !seen.has(def.name)) {
|
|
262
|
+
seen.add(def.name);
|
|
263
|
+
consider(def.annotations);
|
|
264
|
+
}
|
|
265
|
+
const fields = ref ? (fieldsOf(server.ir, ref) ?? []) : [];
|
|
266
|
+
for (const [k, x] of Object.entries(o)) {
|
|
267
|
+
if (k === "$type")
|
|
268
|
+
continue;
|
|
269
|
+
const fd = fields.find((field) => field.name === k);
|
|
270
|
+
if (fd && (annotation(fd, "allow") || annotation(fd, "deny")))
|
|
271
|
+
consider(fd.annotations);
|
|
272
|
+
walk(x, fd?.type);
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
// the static type at a deferred frame's path, such as "items.0.author"
|
|
276
|
+
const typeAt = (root, path) => {
|
|
277
|
+
let t = root;
|
|
278
|
+
for (const seg of path === "" ? [] : path.split(".")) {
|
|
279
|
+
if (!t)
|
|
280
|
+
return undefined;
|
|
281
|
+
if (/^\d+$/.test(seg)) {
|
|
282
|
+
if (t.kind === "list")
|
|
283
|
+
t = t.of;
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
while (t.kind === "list")
|
|
287
|
+
t = t.of;
|
|
288
|
+
t = (fieldsOf(server.ir, t) ?? []).find((field) => field.name === seg)?.type;
|
|
289
|
+
}
|
|
290
|
+
return t;
|
|
291
|
+
};
|
|
292
|
+
const opNames = new Map();
|
|
293
|
+
for (const o of Array.isArray(envelope.ops) ? envelope.ops : [])
|
|
294
|
+
if (o && typeof o === "object")
|
|
295
|
+
opNames.set(o.id, o.op);
|
|
296
|
+
for (const f of frames) {
|
|
297
|
+
if (!("data" in f))
|
|
298
|
+
continue;
|
|
299
|
+
const returns = server.ir.ops[opNames.get(f.id) ?? ""]?.returns;
|
|
300
|
+
walk(f.data, "at" in f ? typeAt(returns, f.at) : returns);
|
|
301
|
+
}
|
|
302
|
+
if (viewer !== null && viewer !== undefined)
|
|
303
|
+
scope = "private";
|
|
304
|
+
if (!Number.isFinite(maxAge))
|
|
305
|
+
maxAge = 0;
|
|
306
|
+
const directives = [scope, `max-age=${Math.floor(maxAge)}`];
|
|
307
|
+
if (swr > 0)
|
|
308
|
+
directives.push(`stale-while-revalidate=${Math.floor(swr)}`);
|
|
309
|
+
if (maxAge === 0 && swr === 0)
|
|
310
|
+
directives.push("no-cache");
|
|
311
|
+
res.setHeader("Cache-Control", directives.join(", "));
|
|
312
|
+
res.setHeader("Vary", "Rayfold-Client, Accept, Authorization");
|
|
313
|
+
const payload = frames.map((f) => {
|
|
314
|
+
if ("meta" in f && f.meta) {
|
|
315
|
+
const { ms: _ms, ...rest } = f.meta;
|
|
316
|
+
return { ...f, meta: rest };
|
|
317
|
+
}
|
|
318
|
+
return f;
|
|
319
|
+
});
|
|
320
|
+
res.setHeader("ETag", `"sha256-${sha256Hex(canonicalJson(payload))}"`);
|
|
321
|
+
}
|
|
322
|
+
function parseB64(v, name) {
|
|
323
|
+
try {
|
|
324
|
+
return JSON.parse(fromBase64url(v));
|
|
325
|
+
}
|
|
326
|
+
catch {
|
|
327
|
+
throw new RayfoldError("invalid_argument", `Query parameter ${name} is not base64url JSON`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
/** How much of an over-limit body is drained (so the refusal can be read) before the socket is dropped. */
|
|
331
|
+
const DRAIN_LIMIT = 1_048_576;
|
|
332
|
+
function readBody(req, max) {
|
|
333
|
+
return new Promise((resolve, reject) => {
|
|
334
|
+
const chunks = [];
|
|
335
|
+
let size = 0;
|
|
336
|
+
let over = false;
|
|
337
|
+
req.on("data", (c) => {
|
|
338
|
+
size += c.length;
|
|
339
|
+
if (over) {
|
|
340
|
+
// A client that keeps streaming long after the refusal is cut off rather than read forever.
|
|
341
|
+
if (size > max + DRAIN_LIMIT)
|
|
342
|
+
req.destroy();
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
if (size > max) {
|
|
346
|
+
// Stop buffering but keep draining: destroying the socket here would lose the refusal on its way out.
|
|
347
|
+
over = true;
|
|
348
|
+
chunks.length = 0;
|
|
349
|
+
reject(new BodyTooLarge(max));
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
chunks.push(c);
|
|
353
|
+
});
|
|
354
|
+
req.on("end", () => resolve(Buffer.concat(chunks)));
|
|
355
|
+
req.on("error", reject);
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
function json(res, status, body) {
|
|
359
|
+
res.writeHead(status, { "Content-Type": "application/json; charset=utf-8" }).end(JSON.stringify(body));
|
|
360
|
+
}
|
|
361
|
+
function problem(res, code, detail, wire) {
|
|
362
|
+
const status = HTTP_STATUS[code];
|
|
363
|
+
const body = {
|
|
364
|
+
type: PROBLEM_TYPE_BASE + code,
|
|
365
|
+
title: code.replace(/_/g, " "),
|
|
366
|
+
status,
|
|
367
|
+
detail,
|
|
368
|
+
code,
|
|
369
|
+
...(wire?.data !== undefined ? { data: wire.data } : {}),
|
|
370
|
+
};
|
|
371
|
+
res.writeHead(status, { "Content-Type": PROBLEM_TYPE, "Cache-Control": "no-store" }).end(JSON.stringify(body));
|
|
372
|
+
}
|
|
373
|
+
/** Start a Node HTTP server hosting the Rayfold endpoint. */
|
|
374
|
+
export function listen(server, port, opts = {}) {
|
|
375
|
+
const handler = createHttpHandler(server, opts);
|
|
376
|
+
const http = createServer((req, res) => void handler(req, res));
|
|
377
|
+
return new Promise((resolve) => http.listen(port, () => resolve(http)));
|
|
378
|
+
}
|
|
379
|
+
//# sourceMappingURL=http.js.map
|
package/http.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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","sourcesContent":["/** HTTP transport for Node. Spec: spec/04-frames-and-transport.md §4. */\nimport { createServer, type IncomingMessage, type Server, type ServerResponse } from \"node:http\";\nimport { canonicalJson, fieldsOf, fromBase64url, sha256Hex, annotation, type RayfoldSchemaIR, type TypeRef } from \"@rayfold/schema\";\nimport type { RayfoldServer } from \"./server.ts\";\nimport { HTTP_STATUS, RayfoldError, type ErrorCode, type Frame, type RequestEnvelope, type WireError } from \"./protocol.ts\";\nimport { referencesViewer, type Expr } from \"@rayfold/schema\";\nimport { RbCodec, RB_CONTENT_TYPE } from \"@rayfold/rb\";\nimport { openApiFor } from \"./openapi.ts\";\nimport { BodyTooLarge, PROBLEM_TYPE_BASE, hostProblem, mediaType, originProblem, refuse, refuseBody, type OriginOptions } from \"./guard.ts\";\n\nexport interface HttpOptions extends OriginOptions {\n /** What GET {path}/manifest serves: the schema without policy expressions (default), the full IR, or nothing. */\n manifest?: \"redacted\" | \"full\" | \"off\";\n /** Path prefix, default \"/rayfold\". */\n path?: string;\n /** Turn the incoming request into a viewer (e.g. parse a Bearer token). */\n viewer?: (req: IncomingMessage) => unknown | Promise<unknown>;\n /** Max request body in bytes, default 1 MiB. */\n maxBody?: number;\n /** Extra CORS origin to allow (development). */\n cors?: string;\n /**\n * When a whole interval of this many milliseconds passes without a frame, a streaming response gets a keep-alive: an\n * empty line, or a zero-length RB frame (spec 04 section 4), so proxies do not close an idle live query. Default 15 000.\n */\n keepAliveMs?: number;\n}\n\nconst FRAMES_TYPE = \"application/rayfold-frames+json\";\nconst codecs = new WeakMap<RayfoldServer, RbCodec>();\n/** One RB codec per server: its key dictionary comes from the schema. */\nexport function codecFor(server: RayfoldServer): RbCodec {\n let c = codecs.get(server);\n if (!c) codecs.set(server, (c = new RbCodec(server.ir)));\n return c;\n}\nconst PROBLEM_TYPE = \"application/problem+json\";\nconst KEEP_ALIVE_RB = Uint8Array.of(0);\nconst BODY_TYPES = new Set([\"application/rayfold+json\", \"application/json\", RB_CONTENT_TYPE]);\n\n/** The IR without policy expressions: clients need names and types, not how access is decided. */\nexport function publicIR(ir: RayfoldSchemaIR): RayfoldSchemaIR {\n return JSON.parse(JSON.stringify(ir), (_k, v: unknown) => {\n if (!v || typeof v !== \"object\" || Array.isArray(v)) return v;\n const o = v as Record<string, unknown>;\n const isPolicy = (o[\"name\"] === \"allow\" || o[\"name\"] === \"deny\") && !(\"type\" in o) && !!o[\"args\"] && typeof o[\"args\"] === \"object\" && !Array.isArray(o[\"args\"]);\n return isPolicy ? { ...o, args: {} } : v;\n }) as RayfoldSchemaIR;\n}\n\nexport function createHttpHandler(server: RayfoldServer, opts: HttpOptions = {}): (req: IncomingMessage, res: ServerResponse) => Promise<void> {\n const base = opts.path ?? \"/rayfold\";\n const maxBody = opts.maxBody ?? 1_048_576;\n\n return async (req, res) => {\n const url = new URL(req.url ?? \"/\", \"http://localhost\");\n res.setHeader(\"Rayfold-Schema\", server.hash);\n res.setHeader(\"X-Content-Type-Options\", \"nosniff\");\n const badHost = hostProblem(req, opts);\n if (badHost) return refuse(res, 403, \"permission_denied\", badHost);\n if (opts.cors) {\n res.setHeader(\"Access-Control-Allow-Origin\", opts.cors);\n res.setHeader(\"Access-Control-Allow-Headers\", \"Content-Type, Authorization, Rayfold-Client, Rayfold-Deadline, Rayfold-Safe\");\n res.setHeader(\"Access-Control-Allow-Methods\", \"GET, POST, QUERY, OPTIONS\");\n if (req.method === \"OPTIONS\") {\n res.writeHead(204).end();\n return;\n }\n }\n if (!url.pathname.startsWith(base)) {\n problem(res, \"not_found\", `No route for ${url.pathname}`);\n return;\n }\n const sub = url.pathname.slice(base.length);\n try {\n if (sub === \"/manifest\" && req.method === \"GET\") {\n if (opts.manifest === \"off\") throw new RayfoldError(\"not_found\", `No route for ${url.pathname}`);\n json(res, 200, { ...server.manifest(), schema: opts.manifest === \"full\" ? server.ir : publicIR(server.ir) });\n return;\n }\n if (sub === \"/openapi.json\" && req.method === \"GET\") {\n json(res, 200, openApiFor(server.ir));\n return;\n }\n let envelope: RequestEnvelope;\n let safe = false;\n if (sub === \"\" || sub === \"/\") {\n if (req.method === \"POST\" || req.method === \"QUERY\") {\n // JSON-only bodies force browsers into a CORS preflight, so a cross-site form or text/plain post cannot run anything.\n const ct = mediaType(req);\n if (!BODY_TYPES.has(ct)) return refuse(res, 415, \"invalid_argument\", `Content-Type ${ct || \"(none)\"} is not accepted; send application/rayfold+json`, \"unsupported_media_type\", { \"Accept-Post\": [...BODY_TYPES].join(\", \") });\n // Safe requests (QUERY, or POST with Rayfold-Safe, which may hold only queries) cannot change data. A foreign page\n // cannot send them without a CORS preflight and cannot read the answer, so only data-changing requests need the\n // Origin check. This keeps reads working behind proxies that rewrite Host.\n const declaredSafe = req.method === \"QUERY\" || req.headers[\"rayfold-safe\"] === \"true\";\n const badOrigin = declaredSafe ? null : originProblem(req, opts);\n if (badOrigin) return refuse(res, 403, \"permission_denied\", badOrigin);\n const body = await readBody(req, maxBody);\n if (ct === RB_CONTENT_TYPE) {\n try {\n envelope = codecFor(server).decode(new Uint8Array(body)) as RequestEnvelope;\n } catch {\n throw new RayfoldError(\"invalid_argument\", \"Body is not valid RB\");\n }\n } else {\n try {\n envelope = JSON.parse(body.toString(\"utf8\")) as RequestEnvelope;\n } catch {\n throw new RayfoldError(\"invalid_argument\", \"Body is not valid JSON\");\n }\n }\n safe = req.method === \"QUERY\" || req.headers[\"rayfold-safe\"] === \"true\";\n } else {\n res.setHeader(\"Allow\", \"POST, QUERY\");\n res.setHeader(\"Accept-Query\", \"application/rayfold+json\");\n throw new RayfoldError(\"unimplemented\", `Method ${req.method} not allowed on ${base}`);\n }\n } else if (req.method === \"GET\") {\n // GET /rayfold/{op}?a=<b64url json>&s=<shape id>&v=<b64url json>\n const op = sub.slice(1);\n const a = url.searchParams.get(\"a\");\n const s = url.searchParams.get(\"s\");\n const v = url.searchParams.get(\"v\");\n const one: RequestEnvelope[\"ops\"][number] = { id: 1, op };\n if (a) one.args = parseB64(a, \"a\") as Record<string, unknown>;\n if (s) one.shape = s;\n if (v) one.vars = parseB64(v, \"v\") as Record<string, never>;\n envelope = { ops: [one] };\n safe = true;\n } else {\n throw new RayfoldError(\"not_found\", `No route for ${req.method} ${url.pathname}`);\n }\n\n // a body that parses but is no envelope (null, a number, ops that are not a list) is the client's error, not a 500\n if (!envelope || typeof envelope !== \"object\" || Array.isArray(envelope)) throw new RayfoldError(\"invalid_argument\", \"Body must be { ops: [...] }\");\n if (safe && Array.isArray(envelope.ops) && envelope.ops.some((o) => server.ir.ops[(o as { op?: string } | null)?.op ?? \"\"]?.kind !== \"query\")) {\n throw new RayfoldError(\"invalid_argument\", \"Safe requests (GET/QUERY) may only contain queries\");\n }\n const client = req.headers[\"rayfold-client\"];\n const deadline = req.headers[\"rayfold-deadline\"];\n const traceparent = req.headers[\"traceparent\"];\n const tracestate = req.headers[\"tracestate\"];\n if (typeof client === \"string\" || typeof deadline === \"string\" || typeof traceparent === \"string\") {\n envelope.meta = { ...(envelope.meta ?? {}) };\n if (typeof client === \"string\") envelope.meta.client = client;\n if (typeof deadline === \"string\" && /^\\d+$/.test(deadline)) envelope.meta.deadline = Number(deadline);\n if (typeof traceparent === \"string\") {\n envelope.meta.traceparent = traceparent;\n if (typeof tracestate === \"string\") envelope.meta.tracestate = tracestate;\n }\n }\n\n const viewer = opts.viewer ? await opts.viewer(req) : null;\n const ac = new AbortController();\n req.on(\"close\", () => ac.abort());\n\n const accepted = new Set((req.headers[\"accept\"] ?? \"\").split(\",\").map((t) => t.split(\";\")[0]!.trim().toLowerCase()));\n const wantsRb = accepted.has(RB_CONTENT_TYPE) && !accepted.has(FRAMES_TYPE) && !accepted.has(\"application/json\") && !accepted.has(\"application/rayfold+json\");\n const wantsSingle = !wantsRb && accepted.has(\"application/json\") && Array.isArray(envelope.ops) && envelope.ops.length === 1;\n if (wantsSingle || safe) {\n // Buffer so we can set status / cache headers from the complete result.\n const frames: Frame[] = [];\n for await (const f of server.execute(envelope, { viewer, signal: ac.signal })) frames.push(f);\n if (safe) applyCacheHeaders(server, envelope, frames, viewer, res);\n if (wantsSingle && frames.length === 1) {\n const f = frames[0]!;\n const status = \"error\" in f ? HTTP_STATUS[f.error.code] : 200;\n const body = canonicalJson(f);\n if (safe && req.headers[\"if-none-match\"] && req.headers[\"if-none-match\"] === res.getHeader(\"ETag\")) {\n res.removeHeader(\"X-Content-Type-Options\"); // no body to sniff; the cached response keeps its headers\n res.writeHead(304).end();\n return;\n }\n res.writeHead(status, { \"Content-Type\": \"application/json; charset=utf-8\" }).end(body);\n return;\n }\n if (safe && req.headers[\"if-none-match\"] && req.headers[\"if-none-match\"] === res.getHeader(\"ETag\")) {\n res.removeHeader(\"X-Content-Type-Options\"); // no body to sniff; the cached response keeps its headers\n res.writeHead(304).end();\n return;\n }\n if (wantsRb) {\n res.writeHead(200, { \"Content-Type\": RB_CONTENT_TYPE }).end(Buffer.from(codecFor(server).encodeFrames(frames)));\n return;\n }\n const body = frames.map((f) => JSON.stringify(f)).join(\"\\n\") + \"\\n\";\n res.writeHead(200, { \"Content-Type\": FRAMES_TYPE }).end(body);\n return;\n }\n\n res.writeHead(200, { \"Content-Type\": wantsRb ? RB_CONTENT_TYPE : FRAMES_TYPE, \"Cache-Control\": \"no-store\", \"X-Accel-Buffering\": \"no\" });\n res.flushHeaders();\n const codec = wantsRb ? codecFor(server) : null;\n let quiet = true;\n const keepAlive = setInterval(() => {\n if (quiet) res.write(codec ? KEEP_ALIVE_RB : \"\\n\");\n quiet = true;\n }, opts.keepAliveMs ?? 15_000);\n try {\n for await (const f of server.execute(envelope, { viewer, signal: ac.signal })) {\n res.write(codec ? Buffer.from(codec.encodeFrames([f])) : JSON.stringify(f) + \"\\n\");\n quiet = false;\n }\n } finally {\n clearInterval(keepAlive);\n }\n res.end();\n } catch (e) {\n if (e instanceof BodyTooLarge && !res.headersSent) return refuseBody(res, e);\n if (res.headersSent) {\n res.end();\n return;\n }\n const code: ErrorCode = e instanceof RayfoldError ? e.code : \"internal\";\n problem(res, code, e instanceof RayfoldError ? e.message : \"Internal error\", e instanceof RayfoldError ? e.toWire() : undefined);\n }\n };\n}\n\n/** Cache-Control/ETag for safe requests (spec 07 §2): min over @cache of ops and entity types touched. */\nexport function applyCacheHeaders(server: RayfoldServer, envelope: RequestEnvelope, frames: Frame[], viewer: unknown, res: ServerResponse): void {\n let maxAge = Number.POSITIVE_INFINITY;\n let swr = 0;\n let scope: \"public\" | \"private\" = \"public\";\n const consider = (annotations: { name: string; args: Record<string, unknown> }[]) => {\n const c = annotations.find((a) => a.name === \"cache\");\n if (c) {\n const ma = c.args[\"maxAge\"];\n if (ma && typeof ma === \"object\" && \"$duration\" in ma) maxAge = Math.min(maxAge, (ma as { $duration: number }).$duration / 1000);\n const sw = c.args[\"swr\"];\n if (sw && typeof sw === \"object\" && \"$duration\" in sw) swr = Math.max(swr, (sw as { $duration: number }).$duration / 1000);\n const sc = c.args[\"scope\"];\n if (sc && typeof sc === \"object\" && \"$ident\" in sc && (sc as { $ident: string }).$ident === \"private\") scope = \"private\";\n }\n for (const a of annotations) {\n if ((a.name === \"allow\" || a.name === \"deny\") && Object.values(a.args).some((v) => v && typeof v === \"object\" && \"$expr\" in v && referencesViewer((v as { $expr: Expr }).$expr))) scope = \"private\";\n }\n };\n for (const o of Array.isArray(envelope.ops) ? envelope.ops : []) {\n const op = o && typeof o === \"object\" ? server.ir.ops[o.op] : undefined;\n if (op) consider(op.annotations);\n }\n // Only types and fields actually present in the response count (spec 07 s1: \"touches such a field\"). A compact frame\n // leaves out `$type` wherever the schema fixes it, so the walk follows each op's return type, and reads `$type` only\n // where it is still there, as on a union member.\n const seen = new Set<string>();\n const walk = (v: unknown, t: TypeRef | undefined): void => {\n if (!v || typeof v !== \"object\") return;\n if (Array.isArray(v)) {\n for (const x of v) walk(x, t?.kind === \"list\" ? t.of : undefined);\n return;\n }\n const o = v as Record<string, unknown>;\n const tn = o[\"$type\"];\n const ref: TypeRef | undefined = typeof tn === \"string\" ? { kind: \"named\", name: tn, nullable: false } : t?.kind === \"named\" ? t : undefined;\n const def = ref ? server.ir.types[ref.name] : undefined;\n if (def && !seen.has(def.name)) {\n seen.add(def.name);\n consider(def.annotations);\n }\n const fields = ref ? (fieldsOf(server.ir, ref) ?? []) : [];\n for (const [k, x] of Object.entries(o)) {\n if (k === \"$type\") continue;\n const fd = fields.find((field) => field.name === k);\n if (fd && (annotation(fd, \"allow\") || annotation(fd, \"deny\"))) consider(fd.annotations);\n walk(x, fd?.type);\n }\n };\n // the static type at a deferred frame's path, such as \"items.0.author\"\n const typeAt = (root: TypeRef | undefined, path: string): TypeRef | undefined => {\n let t = root;\n for (const seg of path === \"\" ? [] : path.split(\".\")) {\n if (!t) return undefined;\n if (/^\\d+$/.test(seg)) {\n if (t.kind === \"list\") t = t.of;\n continue;\n }\n while (t.kind === \"list\") t = t.of;\n t = (fieldsOf(server.ir, t) ?? []).find((field) => field.name === seg)?.type;\n }\n return t;\n };\n const opNames = new Map<unknown, string>();\n for (const o of Array.isArray(envelope.ops) ? envelope.ops : []) if (o && typeof o === \"object\") opNames.set(o.id, o.op);\n for (const f of frames) {\n if (!(\"data\" in f)) continue;\n const returns = server.ir.ops[opNames.get(f.id) ?? \"\"]?.returns;\n walk(f.data, \"at\" in f ? typeAt(returns, f.at) : returns);\n }\n if (viewer !== null && viewer !== undefined) scope = \"private\";\n if (!Number.isFinite(maxAge)) maxAge = 0;\n const directives = [scope, `max-age=${Math.floor(maxAge)}`];\n if (swr > 0) directives.push(`stale-while-revalidate=${Math.floor(swr)}`);\n if (maxAge === 0 && swr === 0) directives.push(\"no-cache\");\n res.setHeader(\"Cache-Control\", directives.join(\", \"));\n res.setHeader(\"Vary\", \"Rayfold-Client, Accept, Authorization\");\n const payload = frames.map((f) => {\n if (\"meta\" in f && f.meta) {\n const { ms: _ms, ...rest } = f.meta;\n return { ...f, meta: rest };\n }\n return f;\n });\n res.setHeader(\"ETag\", `\"sha256-${sha256Hex(canonicalJson(payload))}\"`);\n}\n\nfunction parseB64(v: string, name: string): unknown {\n try {\n return JSON.parse(fromBase64url(v));\n } catch {\n throw new RayfoldError(\"invalid_argument\", `Query parameter ${name} is not base64url JSON`);\n }\n}\n\n/** How much of an over-limit body is drained (so the refusal can be read) before the socket is dropped. */\nconst DRAIN_LIMIT = 1_048_576;\n\nfunction readBody(req: IncomingMessage, max: number): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n let size = 0;\n let over = false;\n req.on(\"data\", (c: Buffer) => {\n size += c.length;\n if (over) {\n // A client that keeps streaming long after the refusal is cut off rather than read forever.\n if (size > max + DRAIN_LIMIT) req.destroy();\n return;\n }\n if (size > max) {\n // Stop buffering but keep draining: destroying the socket here would lose the refusal on its way out.\n over = true;\n chunks.length = 0;\n reject(new BodyTooLarge(max));\n return;\n }\n chunks.push(c);\n });\n req.on(\"end\", () => resolve(Buffer.concat(chunks)));\n req.on(\"error\", reject);\n });\n}\n\nfunction json(res: ServerResponse, status: number, body: unknown): void {\n res.writeHead(status, { \"Content-Type\": \"application/json; charset=utf-8\" }).end(JSON.stringify(body));\n}\n\nfunction problem(res: ServerResponse, code: ErrorCode, detail: string, wire?: WireError): void {\n const status = HTTP_STATUS[code];\n const body = {\n type: PROBLEM_TYPE_BASE + code,\n title: code.replace(/_/g, \" \"),\n status,\n detail,\n code,\n ...(wire?.data !== undefined ? { data: wire.data } : {}),\n };\n res.writeHead(status, { \"Content-Type\": PROBLEM_TYPE, \"Cache-Control\": \"no-store\" }).end(JSON.stringify(body));\n}\n\n/** Start a Node HTTP server hosting the Rayfold endpoint. */\nexport function listen(server: RayfoldServer, port: number, opts: HttpOptions = {}): Promise<Server> {\n const handler = createHttpHandler(server, opts);\n const http = createServer((req, res) => void handler(req, res));\n return new Promise((resolve) => http.listen(port, () => resolve(http)));\n}\n"]}
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from "./core.js";
|
|
2
|
+
export { createHttpHandler, listen, applyCacheHeaders, type HttpOptions } from "./http.js";
|
|
3
|
+
export { createBindingHandler, bindingsOf, type Binding, type BindingOptions } from "./bindings.js";
|
|
4
|
+
export { openApiFor } from "./openapi.js";
|
|
5
|
+
export { attachWebSocket, encodeFrame, decodeFrame, SUBPROTOCOL, type WsOptions } from "./ws.js";
|
|
6
|
+
export { createMcpHandler, handleMcp, mcpTools, mcpResources, jsonSchemaFor, MCP_PROTOCOL_VERSION, type McpTool, type McpResource } from "./mcp.js";
|
|
7
|
+
export { PROBLEM_TYPE_BASE, hostProblem, originProblem, type OriginOptions } from "./guard.js";
|
|
8
|
+
export { Capabilities, type Capability, type CapabilitiesOptions, type MintOptions } from "./capability.js";
|
package/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from "./core.js";
|
|
2
|
+
export { createHttpHandler, listen, applyCacheHeaders } from "./http.js";
|
|
3
|
+
export { createBindingHandler, bindingsOf } from "./bindings.js";
|
|
4
|
+
export { openApiFor } from "./openapi.js";
|
|
5
|
+
export { attachWebSocket, encodeFrame, decodeFrame, SUBPROTOCOL } from "./ws.js";
|
|
6
|
+
export { createMcpHandler, handleMcp, mcpTools, mcpResources, jsonSchemaFor, MCP_PROTOCOL_VERSION } from "./mcp.js";
|
|
7
|
+
export { PROBLEM_TYPE_BASE, hostProblem, originProblem } from "./guard.js";
|
|
8
|
+
export { Capabilities } from "./capability.js";
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
package/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,OAAO,EAAE,iBAAiB,EAAE,MAAM,EAAE,iBAAiB,EAAoB,MAAM,WAAW,CAAC;AAC3F,OAAO,EAAE,oBAAoB,EAAE,UAAU,EAAqC,MAAM,eAAe,CAAC;AACpG,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAkB,MAAM,SAAS,CAAC;AACjG,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,QAAQ,EAAE,YAAY,EAAE,aAAa,EAAE,oBAAoB,EAAkC,MAAM,UAAU,CAAC;AACpJ,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,aAAa,EAAsB,MAAM,YAAY,CAAC;AAC/F,OAAO,EAAE,YAAY,EAA+D,MAAM,iBAAiB,CAAC","sourcesContent":["export * from \"./core.ts\";\nexport { createHttpHandler, listen, applyCacheHeaders, type HttpOptions } from \"./http.ts\";\nexport { createBindingHandler, bindingsOf, type Binding, type BindingOptions } from \"./bindings.ts\";\nexport { openApiFor } from \"./openapi.ts\";\nexport { attachWebSocket, encodeFrame, decodeFrame, SUBPROTOCOL, type WsOptions } from \"./ws.ts\";\nexport { createMcpHandler, handleMcp, mcpTools, mcpResources, jsonSchemaFor, MCP_PROTOCOL_VERSION, type McpTool, type McpResource } from \"./mcp.ts\";\nexport { PROBLEM_TYPE_BASE, hostProblem, originProblem, type OriginOptions } from \"./guard.ts\";\nexport { Capabilities, type Capability, type CapabilitiesOptions, type MintOptions } from \"./capability.ts\";\n"]}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** Hooks around batches, ops and loaders, for tracing and metrics without a dependency. See `@rayfold/otel`. */
|
|
2
|
+
import type { OpDef } from "@rayfold/schema";
|
|
3
|
+
import type { RequestMeta, WireError } from "./protocol.js";
|
|
4
|
+
export interface BatchInfo {
|
|
5
|
+
/** Ops in the envelope (0 when the envelope is malformed). */
|
|
6
|
+
ops: number;
|
|
7
|
+
/** The envelope's meta, with `traceparent` and `tracestate` when the caller sent W3C trace context. */
|
|
8
|
+
meta: RequestMeta;
|
|
9
|
+
}
|
|
10
|
+
export interface OpInfo {
|
|
11
|
+
id: number;
|
|
12
|
+
name: string;
|
|
13
|
+
kind: OpDef["kind"];
|
|
14
|
+
/** The op's static cost (spec 06 section 5). */
|
|
15
|
+
cost: number;
|
|
16
|
+
}
|
|
17
|
+
export interface LoaderInfo {
|
|
18
|
+
type: string;
|
|
19
|
+
field: string;
|
|
20
|
+
/** Parents the one loader call serves: the whole level of a nested shape. */
|
|
21
|
+
parents: number;
|
|
22
|
+
}
|
|
23
|
+
/** How a batch or an op ended: `error` when it failed. A failed op is reported here, not thrown. */
|
|
24
|
+
export interface Outcome {
|
|
25
|
+
error?: WireError;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Wraps the work the runtime does. Each hook must call `run` exactly once and return what it returns. The hooks nest,
|
|
29
|
+
* loaders inside their op and ops inside their batch, so a tracer that keeps the active span in async context (as
|
|
30
|
+
* OpenTelemetry does on Node) parents them, and any span a resolver starts, without further help.
|
|
31
|
+
*/
|
|
32
|
+
export interface Instrumentation {
|
|
33
|
+
batch?(info: BatchInfo, run: () => Promise<Outcome>): Promise<Outcome>;
|
|
34
|
+
op?(info: OpInfo, run: () => Promise<Outcome>): Promise<Outcome>;
|
|
35
|
+
loader?<T>(info: LoaderInfo, run: () => Promise<T>): Promise<T>;
|
|
36
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"instrumentation.js","sourceRoot":"","sources":["../src/instrumentation.ts"],"names":[],"mappings":"","sourcesContent":["/** Hooks around batches, ops and loaders, for tracing and metrics without a dependency. See `@rayfold/otel`. */\nimport type { OpDef } from \"@rayfold/schema\";\nimport type { RequestMeta, WireError } from \"./protocol.ts\";\n\nexport interface BatchInfo {\n /** Ops in the envelope (0 when the envelope is malformed). */\n ops: number;\n /** The envelope's meta, with `traceparent` and `tracestate` when the caller sent W3C trace context. */\n meta: RequestMeta;\n}\n\nexport interface OpInfo {\n id: number;\n name: string;\n kind: OpDef[\"kind\"];\n /** The op's static cost (spec 06 section 5). */\n cost: number;\n}\n\nexport interface LoaderInfo {\n type: string;\n field: string;\n /** Parents the one loader call serves: the whole level of a nested shape. */\n parents: number;\n}\n\n/** How a batch or an op ended: `error` when it failed. A failed op is reported here, not thrown. */\nexport interface Outcome {\n error?: WireError;\n}\n\n/**\n * Wraps the work the runtime does. Each hook must call `run` exactly once and return what it returns. The hooks nest,\n * loaders inside their op and ops inside their batch, so a tracer that keeps the active span in async context (as\n * OpenTelemetry does on Node) parents them, and any span a resolver starts, without further help.\n */\nexport interface Instrumentation {\n batch?(info: BatchInfo, run: () => Promise<Outcome>): Promise<Outcome>;\n op?(info: OpInfo, run: () => Promise<Outcome>): Promise<Outcome>;\n loader?<T>(info: LoaderInfo, run: () => Promise<T>): Promise<T>;\n}\n"]}
|
package/live.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live queries (extension `live`, spec/08): the query is the subscription.
|
|
3
|
+
* A live query records its read set (entity keys + op name). Every command's patch and every explicit
|
|
4
|
+
* `changes.publish()` feeds a ChangeBus; when a change intersects a read set the query re-runs and the
|
|
5
|
+
* client receives either a minimal `patch` frame (same result structure, changed fields) or a fresh
|
|
6
|
+
* `data` frame (membership/order changed).
|
|
7
|
+
*/
|
|
8
|
+
import type { Frame, PatchOp } from "./protocol.js";
|
|
9
|
+
export interface Change {
|
|
10
|
+
keys: Set<string>;
|
|
11
|
+
ops: Set<string>;
|
|
12
|
+
}
|
|
13
|
+
export declare class ChangeBus {
|
|
14
|
+
private readonly subs;
|
|
15
|
+
publish(c: Change): void;
|
|
16
|
+
subscribe(fn: (c: Change) => void): () => void;
|
|
17
|
+
get size(): number;
|
|
18
|
+
}
|
|
19
|
+
export declare function changeFromPatch(patch: PatchOp[]): Change;
|
|
20
|
+
/** Entities keyed by "$type:id" with nested entities replaced by refs; plus the skeleton. */
|
|
21
|
+
export declare function normalizeResult(data: unknown): {
|
|
22
|
+
entities: Map<string, Record<string, unknown>>;
|
|
23
|
+
skeleton: unknown;
|
|
24
|
+
};
|
|
25
|
+
export declare function readSetOf(data: unknown): Set<string>;
|
|
26
|
+
/**
|
|
27
|
+
* Compare two results. Returns a `patch` frame body when the difference can be described (changed entity fields,
|
|
28
|
+
* changed fields of a plain object, rows added to or removed from a list), a full `data` replacement when it
|
|
29
|
+
* cannot, or null when nothing changed. A patch that would cost more than the result itself is not worth sending.
|
|
30
|
+
*/
|
|
31
|
+
export declare function diffResults(prev: unknown, next: unknown): {
|
|
32
|
+
patch: PatchOp[];
|
|
33
|
+
} | {
|
|
34
|
+
data: unknown;
|
|
35
|
+
} | null;
|
|
36
|
+
/** Fold `at` frames into a data value so deferred parts take part in diffs. */
|
|
37
|
+
export declare function foldFrames(frames: Frame[]): unknown;
|