@synap-core/sdk 1.2.0 → 1.3.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/README.md +80 -0
- package/dist/chunk-J7XHAAFJ.js +79 -0
- package/dist/chunk-J7XHAAFJ.js.map +1 -0
- package/dist/{client-C5sF20f1.d.cts → client-CVUT1Mcf.d.cts} +1 -1
- package/dist/{client-C5sF20f1.d.ts → client-CVUT1Mcf.d.ts} +1 -1
- package/dist/connect.browser.cjs +22 -0
- package/dist/connect.browser.cjs.map +1 -0
- package/dist/connect.browser.d.cts +2 -0
- package/dist/connect.browser.d.ts +2 -0
- package/dist/connect.browser.js +5 -0
- package/dist/connect.browser.js.map +1 -0
- package/dist/connect.cjs +1206 -0
- package/dist/connect.cjs.map +1 -0
- package/dist/connect.d.cts +442 -0
- package/dist/connect.d.ts +442 -0
- package/dist/connect.js +1092 -0
- package/dist/connect.js.map +1 -0
- package/dist/public.d.cts +2 -2
- package/dist/public.d.ts +2 -2
- package/dist/testing.cjs +304 -19
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +73 -2
- package/dist/testing.d.ts +73 -2
- package/dist/testing.js +281 -19
- package/dist/testing.js.map +1 -1
- package/dist/types-DpqPNaaa.d.cts +76 -0
- package/dist/types-DpqPNaaa.d.ts +76 -0
- package/package.json +11 -3
- package/src/connect/auth.ts +405 -0
- package/src/connect/browser.ts +10 -0
- package/src/connect/client.ts +193 -0
- package/src/connect/discovery.ts +262 -0
- package/src/connect/encoding.ts +93 -0
- package/src/connect/errors.ts +115 -0
- package/src/connect/flow.ts +449 -0
- package/src/connect/guard.ts +25 -0
- package/src/connect/index.ts +59 -0
- package/src/connect/seal.ts +119 -0
- package/src/connect/store.ts +151 -0
- package/src/connect/types.ts +80 -0
- package/src/testing/auth-server.ts +380 -0
- package/src/testing/index.ts +5 -0
package/dist/testing.cjs
CHANGED
|
@@ -20,13 +20,17 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/testing/index.ts
|
|
21
21
|
var testing_exports = {};
|
|
22
22
|
__export(testing_exports, {
|
|
23
|
+
MOCK_AUTH_ISSUER: () => MOCK_AUTH_ISSUER,
|
|
23
24
|
MOCK_FAILURE_TOKENS: () => MOCK_FAILURE_TOKENS,
|
|
24
25
|
MOCK_FORM: () => MOCK_FORM,
|
|
26
|
+
MOCK_GRANT: () => MOCK_GRANT,
|
|
25
27
|
MOCK_POD_URL: () => MOCK_POD_URL,
|
|
26
28
|
MOCK_RETRY_AFTER_SEC: () => MOCK_RETRY_AFTER_SEC,
|
|
27
29
|
MOCK_SHARE: () => MOCK_SHARE,
|
|
28
30
|
MOCK_SITE: () => MOCK_SITE,
|
|
29
31
|
MOCK_TOKENS: () => MOCK_TOKENS,
|
|
32
|
+
MOCK_USER: () => MOCK_USER,
|
|
33
|
+
createMockAuthServer: () => createMockAuthServer,
|
|
30
34
|
createMockPod: () => createMockPod
|
|
31
35
|
});
|
|
32
36
|
module.exports = __toCommonJS(testing_exports);
|
|
@@ -126,6 +130,283 @@ var MOCK_SITE = {
|
|
|
126
130
|
}
|
|
127
131
|
};
|
|
128
132
|
|
|
133
|
+
// src/connect/encoding.ts
|
|
134
|
+
var encoder = new TextEncoder();
|
|
135
|
+
var decoder = new TextDecoder();
|
|
136
|
+
function utf8(text) {
|
|
137
|
+
return encoder.encode(text);
|
|
138
|
+
}
|
|
139
|
+
function base64url(bytes) {
|
|
140
|
+
let binary = "";
|
|
141
|
+
for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
|
|
142
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
143
|
+
}
|
|
144
|
+
function randomBytes(length) {
|
|
145
|
+
const out = new Uint8Array(length);
|
|
146
|
+
crypto.getRandomValues(out);
|
|
147
|
+
return out;
|
|
148
|
+
}
|
|
149
|
+
function randomToken() {
|
|
150
|
+
return base64url(randomBytes(32));
|
|
151
|
+
}
|
|
152
|
+
async function pkceChallenge(verifier) {
|
|
153
|
+
const digest = await crypto.subtle.digest("SHA-256", utf8(verifier));
|
|
154
|
+
return base64url(new Uint8Array(digest));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// src/testing/auth-server.ts
|
|
158
|
+
var MOCK_AUTH_ISSUER = "https://pod.mock-synap.test";
|
|
159
|
+
var MOCK_GRANT = [
|
|
160
|
+
{ permission: "entity.post.read", workspaceId: "ws_mock_studio", workspaceName: "Studio" },
|
|
161
|
+
{ permission: "entity.post.create", workspaceId: "ws_mock_studio", workspaceName: "Studio" }
|
|
162
|
+
];
|
|
163
|
+
var MOCK_USER = { id: "user_mock_ada", name: "Ada Lovelace" };
|
|
164
|
+
function json(status, body, headers = {}) {
|
|
165
|
+
return new Response(JSON.stringify(body), {
|
|
166
|
+
status,
|
|
167
|
+
headers: { "Content-Type": "application/json", "Cache-Control": "no-store", ...headers }
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
function oauthError(error, description, status = 400) {
|
|
171
|
+
return json(status, { error, error_description: description });
|
|
172
|
+
}
|
|
173
|
+
function createMockAuthServer(options = {}) {
|
|
174
|
+
const issuer = options.issuer ?? MOCK_AUTH_ISSUER;
|
|
175
|
+
const now = options.now ?? Date.now;
|
|
176
|
+
const refresh = options.refresh ?? true;
|
|
177
|
+
const accessTtl = options.accessTtlSec ?? 3600;
|
|
178
|
+
const refreshIdle = options.refreshIdleSec ?? 30 * 24 * 3600;
|
|
179
|
+
const grant = options.grant ?? MOCK_GRANT;
|
|
180
|
+
const user = options.user ?? MOCK_USER;
|
|
181
|
+
const codes = /* @__PURE__ */ new Map();
|
|
182
|
+
const access = /* @__PURE__ */ new Map();
|
|
183
|
+
const refreshTokens = /* @__PURE__ */ new Map();
|
|
184
|
+
const requests = [];
|
|
185
|
+
const writes = [];
|
|
186
|
+
let proposalSeq = 0;
|
|
187
|
+
const metadata = () => {
|
|
188
|
+
const base = {
|
|
189
|
+
issuer,
|
|
190
|
+
authorization_endpoint: `${issuer}/authorize`,
|
|
191
|
+
token_endpoint: `${issuer}/token`,
|
|
192
|
+
registration_endpoint: `${issuer}/register`,
|
|
193
|
+
revocation_endpoint: `${issuer}/revoke`,
|
|
194
|
+
revocation_endpoint_auth_methods_supported: ["none"],
|
|
195
|
+
response_types_supported: ["code"],
|
|
196
|
+
grant_types_supported: refresh ? ["authorization_code", "refresh_token"] : ["authorization_code"],
|
|
197
|
+
code_challenge_methods_supported: ["S256"],
|
|
198
|
+
token_endpoint_auth_methods_supported: ["none"],
|
|
199
|
+
authorization_response_iss_parameter_supported: true,
|
|
200
|
+
client_id_metadata_document_supported: true
|
|
201
|
+
};
|
|
202
|
+
for (const [k, v] of Object.entries(options.metadata ?? {})) {
|
|
203
|
+
if (v === void 0) delete base[k];
|
|
204
|
+
else base[k] = v;
|
|
205
|
+
}
|
|
206
|
+
return base;
|
|
207
|
+
};
|
|
208
|
+
let loseNextRace = false;
|
|
209
|
+
let reuses = 0;
|
|
210
|
+
const revokeFamily = (family) => {
|
|
211
|
+
for (const r of refreshTokens.values()) if (r.family === family) r.revoked = true;
|
|
212
|
+
for (const a of access.values()) if (a.family === family) a.revoked = true;
|
|
213
|
+
};
|
|
214
|
+
const tokenBody = (scope, clientId, family) => {
|
|
215
|
+
const accessToken = `synap_hub_mock_${randomToken()}`;
|
|
216
|
+
access.set(accessToken, { expiresAt: now() + accessTtl * 1e3, revoked: false, family: refresh ? family : null });
|
|
217
|
+
const body = {
|
|
218
|
+
access_token: accessToken,
|
|
219
|
+
token_type: "Bearer",
|
|
220
|
+
expires_in: accessTtl,
|
|
221
|
+
scope: scope.join(" "),
|
|
222
|
+
synap_grant: grant.filter((g) => scope.includes(g.permission)),
|
|
223
|
+
synap_user: user
|
|
224
|
+
};
|
|
225
|
+
if (refresh) {
|
|
226
|
+
const refreshToken = randomToken();
|
|
227
|
+
refreshTokens.set(refreshToken, { clientId, family, idleUntil: now() + refreshIdle * 1e3, used: false, revoked: false, scope });
|
|
228
|
+
body.refresh_token = refreshToken;
|
|
229
|
+
if (options.advertiseRefreshExpiry) body.refresh_token_expires_in = refreshIdle;
|
|
230
|
+
}
|
|
231
|
+
return body;
|
|
232
|
+
};
|
|
233
|
+
async function token(form) {
|
|
234
|
+
if (form.grant_type === "authorization_code") {
|
|
235
|
+
const row = form.code ? codes.get(form.code) : void 0;
|
|
236
|
+
if (form.code) codes.delete(form.code);
|
|
237
|
+
if (!row) return oauthError("invalid_grant", "Invalid, expired, or already-redeemed authorization code");
|
|
238
|
+
if (row.clientId !== form.client_id) return oauthError("invalid_grant", "client_id does not match the code");
|
|
239
|
+
if (row.redirectUri !== form.redirect_uri) return oauthError("invalid_grant", "redirect_uri mismatch");
|
|
240
|
+
if (!form.code_verifier || await pkceChallenge(form.code_verifier) !== row.challenge) {
|
|
241
|
+
return oauthError("invalid_grant", "PKCE verification failed");
|
|
242
|
+
}
|
|
243
|
+
if (form.resource !== void 0 && row.resource && form.resource !== row.resource) {
|
|
244
|
+
return oauthError("invalid_target", "resource does not match the authorization request");
|
|
245
|
+
}
|
|
246
|
+
for (const r of refreshTokens.values()) if (r.clientId === row.clientId) revokeFamily(r.family);
|
|
247
|
+
return json(200, tokenBody(row.scope, row.clientId, randomToken()));
|
|
248
|
+
}
|
|
249
|
+
if (form.grant_type === "refresh_token") {
|
|
250
|
+
if (!refresh) return oauthError("unsupported_grant_type", 'Only grant_type "authorization_code" is supported');
|
|
251
|
+
if (!form.refresh_token || !form.client_id) return oauthError("invalid_request", "refresh_token and client_id are required");
|
|
252
|
+
const row = refreshTokens.get(form.refresh_token);
|
|
253
|
+
if (!row || row.clientId !== form.client_id || row.revoked) {
|
|
254
|
+
return oauthError("invalid_grant", "Invalid, expired, revoked or already-used refresh token");
|
|
255
|
+
}
|
|
256
|
+
if (row.used) {
|
|
257
|
+
reuses++;
|
|
258
|
+
revokeFamily(row.family);
|
|
259
|
+
return oauthError("invalid_grant", "Invalid, expired, revoked or already-used refresh token");
|
|
260
|
+
}
|
|
261
|
+
if (row.idleUntil <= now()) return oauthError("invalid_grant", "Invalid, expired, revoked or already-used refresh token");
|
|
262
|
+
if (loseNextRace) {
|
|
263
|
+
loseNextRace = false;
|
|
264
|
+
return oauthError("invalid_grant", "Invalid, expired, revoked or already-used refresh token");
|
|
265
|
+
}
|
|
266
|
+
row.used = true;
|
|
267
|
+
return json(200, tokenBody(row.scope, row.clientId, row.family));
|
|
268
|
+
}
|
|
269
|
+
return oauthError("unsupported_grant_type", "Unsupported grant_type");
|
|
270
|
+
}
|
|
271
|
+
function hub(method, path, headers, body) {
|
|
272
|
+
const failure2 = options.failures?.[path];
|
|
273
|
+
if (failure2 === "unreachable") return "reject";
|
|
274
|
+
const bearer = /^Bearer (.+)$/.exec(headers.authorization ?? "")?.[1];
|
|
275
|
+
const row = bearer ? access.get(bearer) : void 0;
|
|
276
|
+
if (!row || row.revoked || row.expiresAt <= now()) return json(401, { error: "Unauthorized" });
|
|
277
|
+
switch (failure2) {
|
|
278
|
+
case "rate_limited":
|
|
279
|
+
return json(429, { error: "Too many requests" }, { "Retry-After": "30" });
|
|
280
|
+
case "forbidden":
|
|
281
|
+
return json(403, { error: "Forbidden" });
|
|
282
|
+
case "door_unsupported":
|
|
283
|
+
return json(403, { error: "This door is not available to a space-scoped key", code: "GRANT_DOOR_UNSUPPORTED" });
|
|
284
|
+
case "server":
|
|
285
|
+
return json(500, { error: "Internal error" });
|
|
286
|
+
case "unavailable":
|
|
287
|
+
return json(503, { error: "Unavailable" });
|
|
288
|
+
}
|
|
289
|
+
if (path.startsWith("/knowledge/") || path === "/entities/retrieve" || path === "/memory/search") {
|
|
290
|
+
return json(403, { error: "This door is not available to a space-scoped key", code: "GRANT_DOOR_UNSUPPORTED" });
|
|
291
|
+
}
|
|
292
|
+
if (method === "GET" && path === "/users/me") return json(200, user);
|
|
293
|
+
if (method === "GET" && path === "/entities") return json(200, { entities: [], total: 0 });
|
|
294
|
+
if (method !== "GET") {
|
|
295
|
+
writes.push({ method, path, body });
|
|
296
|
+
if ((options.writeMode ?? "proposed") === "proposed") {
|
|
297
|
+
const proposalId = `prop_mock_${++proposalSeq}`;
|
|
298
|
+
return json(200, {
|
|
299
|
+
status: "proposed",
|
|
300
|
+
message: "Sent for review",
|
|
301
|
+
proposalId,
|
|
302
|
+
reviewUrl: `${issuer}/open/${proposalId}`
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
return json(200, { status: "created", id: `ent_mock_${writes.length}` });
|
|
306
|
+
}
|
|
307
|
+
return json(404, { error: "Not found" });
|
|
308
|
+
}
|
|
309
|
+
const mockFetch = async (input, init) => {
|
|
310
|
+
const request = typeof input === "object" && "method" in input ? input : null;
|
|
311
|
+
const url = new URL(typeof input === "string" ? input : input instanceof URL ? input.href : input.url);
|
|
312
|
+
const method = (init?.method ?? request?.method ?? "GET").toUpperCase();
|
|
313
|
+
const headers = {};
|
|
314
|
+
new Headers(init?.headers ?? request?.headers).forEach((value, name) => {
|
|
315
|
+
headers[name.toLowerCase()] = value;
|
|
316
|
+
});
|
|
317
|
+
const rawBody = typeof init?.body === "string" ? init.body : request ? await request.clone().text() : "";
|
|
318
|
+
const isForm = (headers["content-type"] ?? "").includes("application/x-www-form-urlencoded");
|
|
319
|
+
const form = isForm ? Object.fromEntries(new URLSearchParams(rawBody)) : null;
|
|
320
|
+
requests.push({ method, url: url.href, headers, form });
|
|
321
|
+
if (url.origin !== new URL(issuer).origin) throw new TypeError(`Failed to fetch: ${url.origin} is not the mock pod`);
|
|
322
|
+
const path = url.pathname;
|
|
323
|
+
if (method === "GET" && path === "/.well-known/oauth-authorization-server") return json(200, metadata());
|
|
324
|
+
if (method === "POST" && path === "/token") return token(form ?? {});
|
|
325
|
+
if (method === "POST" && path === "/revoke") {
|
|
326
|
+
const t = form?.token ?? "";
|
|
327
|
+
const r = refreshTokens.get(t);
|
|
328
|
+
if (r && form?.client_id && form.client_id !== r.clientId) {
|
|
329
|
+
return oauthError("unauthorized_client", "The token was not issued to this client");
|
|
330
|
+
}
|
|
331
|
+
if (r) revokeFamily(r.family);
|
|
332
|
+
const a = access.get(t);
|
|
333
|
+
if (a) a.revoked = true;
|
|
334
|
+
return new Response(null, { status: 200 });
|
|
335
|
+
}
|
|
336
|
+
const hubPath = /^\/api\/hub(\/.*)$/.exec(path)?.[1];
|
|
337
|
+
if (hubPath) {
|
|
338
|
+
let body = void 0;
|
|
339
|
+
try {
|
|
340
|
+
body = rawBody ? JSON.parse(rawBody) : void 0;
|
|
341
|
+
} catch {
|
|
342
|
+
body = rawBody;
|
|
343
|
+
}
|
|
344
|
+
const res = hub(method, hubPath, headers, body);
|
|
345
|
+
if (res === "reject") throw new TypeError("Failed to fetch: mock pod unreachable");
|
|
346
|
+
return res;
|
|
347
|
+
}
|
|
348
|
+
return json(404, { error: "Not found" });
|
|
349
|
+
};
|
|
350
|
+
return {
|
|
351
|
+
issuer,
|
|
352
|
+
fetch: mockFetch,
|
|
353
|
+
requests,
|
|
354
|
+
writes,
|
|
355
|
+
authorize(authorizeUrl, decision = {}) {
|
|
356
|
+
const url = new URL(authorizeUrl);
|
|
357
|
+
if (`${url.origin}${url.pathname}` !== `${issuer}/authorize`) throw new Error(`Not this pod's /authorize: ${authorizeUrl}`);
|
|
358
|
+
const q = url.searchParams;
|
|
359
|
+
const redirectUri = q.get("redirect_uri");
|
|
360
|
+
const clientId = q.get("client_id");
|
|
361
|
+
if (!clientId || !redirectUri) throw new Error("Authorization request rejected: client_id and redirect_uri are required");
|
|
362
|
+
const answer = new URL(redirectUri);
|
|
363
|
+
const state = decision.state ?? q.get("state");
|
|
364
|
+
const finish = (fields) => {
|
|
365
|
+
for (const [k, v] of Object.entries(fields)) answer.searchParams.set(k, v);
|
|
366
|
+
if (state !== null) answer.searchParams.set("state", state);
|
|
367
|
+
const iss = decision.iss === void 0 ? issuer : decision.iss;
|
|
368
|
+
if (iss !== null) answer.searchParams.set("iss", iss);
|
|
369
|
+
return answer.href;
|
|
370
|
+
};
|
|
371
|
+
if (q.get("response_type") !== "code") return finish({ error: "unsupported_response_type" });
|
|
372
|
+
if (q.get("code_challenge_method") !== "S256" || !q.get("code_challenge")) {
|
|
373
|
+
return finish({ error: "invalid_request", error_description: "PKCE S256 is required" });
|
|
374
|
+
}
|
|
375
|
+
const resource = q.get("resource");
|
|
376
|
+
if (resource !== null && resource !== `${issuer}/api/hub` && resource !== `${issuer}/mcp`) {
|
|
377
|
+
return finish({ error: "invalid_target" });
|
|
378
|
+
}
|
|
379
|
+
const scope = (q.get("scope") ?? "").split(/\s+/).filter(Boolean);
|
|
380
|
+
if (scope.length === 0) return finish({ error: "invalid_scope" });
|
|
381
|
+
if (decision.deny) return finish({ error: "access_denied" });
|
|
382
|
+
const code = randomToken();
|
|
383
|
+
codes.set(code, { clientId, redirectUri, challenge: q.get("code_challenge"), resource, scope });
|
|
384
|
+
return finish({ code });
|
|
385
|
+
},
|
|
386
|
+
consumeRefreshToken(t) {
|
|
387
|
+
const row = refreshTokens.get(t);
|
|
388
|
+
if (row) row.used = true;
|
|
389
|
+
},
|
|
390
|
+
loseNextRefreshRace() {
|
|
391
|
+
loseNextRace = true;
|
|
392
|
+
},
|
|
393
|
+
get reuses() {
|
|
394
|
+
return reuses;
|
|
395
|
+
},
|
|
396
|
+
revokeAll() {
|
|
397
|
+
for (const row of access.values()) row.revoked = true;
|
|
398
|
+
for (const row of refreshTokens.values()) row.revoked = true;
|
|
399
|
+
},
|
|
400
|
+
liveRefreshTokens() {
|
|
401
|
+
return [...refreshTokens].filter(([, r]) => !r.used && !r.revoked).map(([t]) => t);
|
|
402
|
+
},
|
|
403
|
+
reset() {
|
|
404
|
+
requests.length = 0;
|
|
405
|
+
writes.length = 0;
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
|
|
129
410
|
// src/testing/index.ts
|
|
130
411
|
var MOCK_POD_URL = "https://mock.synap.invalid";
|
|
131
412
|
var MOCK_TOKENS = { form: "demo-form", share: "demo", site: "demo-site" };
|
|
@@ -160,7 +441,7 @@ var MAX_OFFSET = 1e4;
|
|
|
160
441
|
var MAX_LIMIT = 100;
|
|
161
442
|
var MAX_SORT_KEYS = 3;
|
|
162
443
|
var PUBLISHED_AT = "publishedAt";
|
|
163
|
-
function
|
|
444
|
+
function json2(status, body, headers = {}) {
|
|
164
445
|
return new Response(JSON.stringify(body), {
|
|
165
446
|
status,
|
|
166
447
|
headers: { "Content-Type": "application/json", "Cache-Control": "no-cache", ...headers }
|
|
@@ -183,15 +464,15 @@ function etagMatches(header, etag) {
|
|
|
183
464
|
function failure(token) {
|
|
184
465
|
switch (token) {
|
|
185
466
|
case MOCK_FAILURE_TOKENS.rateLimited:
|
|
186
|
-
return
|
|
467
|
+
return json2(429, { error: "Too many requests", retryAfter: "5 minutes" });
|
|
187
468
|
case MOCK_FAILURE_TOKENS.rateLimitedRetryAfter:
|
|
188
|
-
return
|
|
469
|
+
return json2(429, { error: "Too many requests" }, { "Retry-After": String(MOCK_RETRY_AFTER_SEC) });
|
|
189
470
|
case MOCK_FAILURE_TOKENS.doorsDisabled:
|
|
190
|
-
return
|
|
471
|
+
return json2(403, DOORS_DISABLED);
|
|
191
472
|
case MOCK_FAILURE_TOKENS.serverError:
|
|
192
|
-
return
|
|
473
|
+
return json2(500, { error: "Internal error" });
|
|
193
474
|
case MOCK_FAILURE_TOKENS.unavailable:
|
|
194
|
-
return
|
|
475
|
+
return json2(503, { received: false });
|
|
195
476
|
case MOCK_FAILURE_TOKENS.unreachable:
|
|
196
477
|
return "reject";
|
|
197
478
|
default:
|
|
@@ -266,17 +547,17 @@ function cached(view, ifNoneMatch) {
|
|
|
266
547
|
if (etagMatches(ifNoneMatch, etag)) {
|
|
267
548
|
return new Response(null, { status: 304, headers: { ETag: etag, "Cache-Control": "no-cache" } });
|
|
268
549
|
}
|
|
269
|
-
return
|
|
550
|
+
return json2(200, view, { ETag: etag });
|
|
270
551
|
}
|
|
271
552
|
function siteDoor(site, rest, params, ifNoneMatch) {
|
|
272
553
|
if (rest.length === 0) return cached(manifest(site), ifNoneMatch);
|
|
273
554
|
if (rest[0] !== "collections" || rest.length > 3 || !Object.hasOwn(site.collections, rest[1])) {
|
|
274
|
-
return
|
|
555
|
+
return json2(404, NOT_FOUND);
|
|
275
556
|
}
|
|
276
557
|
const c = site.collections[rest[1]];
|
|
277
558
|
if (rest.length === 2) {
|
|
278
559
|
const q = parseQuery(params, c);
|
|
279
|
-
if (typeof q === "string") return
|
|
560
|
+
if (typeof q === "string") return json2(400, { error: q });
|
|
280
561
|
const sorted = [...c.records].sort(compareRecords(q.sort));
|
|
281
562
|
const page = {
|
|
282
563
|
items: sorted.slice(q.offset, q.offset + q.limit).map(summary),
|
|
@@ -288,10 +569,10 @@ function siteDoor(site, rest, params, ifNoneMatch) {
|
|
|
288
569
|
}
|
|
289
570
|
const address = rest[2];
|
|
290
571
|
const byPublicId = PUBLIC_ID_RE.test(address);
|
|
291
|
-
if (!byPublicId && !(address.length <= SLUG_MAX_LENGTH && SLUG_RE.test(address))) return
|
|
572
|
+
if (!byPublicId && !(address.length <= SLUG_MAX_LENGTH && SLUG_RE.test(address))) return json2(404, NOT_FOUND);
|
|
292
573
|
const record = c.records.find((r) => byPublicId ? r.publicId === address : r.slug === address);
|
|
293
|
-
if (!record) return
|
|
294
|
-
if (params.has("embed")) return
|
|
574
|
+
if (!record) return json2(404, NOT_FOUND);
|
|
575
|
+
if (params.has("embed")) return json2(400, { error: "embed is not available" });
|
|
295
576
|
return cached(record, ifNoneMatch);
|
|
296
577
|
}
|
|
297
578
|
function invalidKeys(form, body) {
|
|
@@ -328,12 +609,12 @@ function createMockPod(options = {}) {
|
|
|
328
609
|
if (preflighted.length > 0) throw new TypeError(`Failed to fetch: preflight refuses ${preflighted.join(", ")}`);
|
|
329
610
|
}
|
|
330
611
|
const m = url.pathname.match(/^\/api\/hub\/public\/(forms|shares|sites)\/(.+)$/);
|
|
331
|
-
if (!m) return
|
|
612
|
+
if (!m) return json2(404, NOT_FOUND);
|
|
332
613
|
let segments;
|
|
333
614
|
try {
|
|
334
615
|
segments = m[2].split("/").map(decodeURIComponent);
|
|
335
616
|
} catch {
|
|
336
|
-
return
|
|
617
|
+
return json2(404, NOT_FOUND);
|
|
337
618
|
}
|
|
338
619
|
const [token, ...rest] = segments;
|
|
339
620
|
const failed = failure(token);
|
|
@@ -351,15 +632,15 @@ function createMockPod(options = {}) {
|
|
|
351
632
|
const invalid = form ? invalidKeys(form, body) : [];
|
|
352
633
|
const status = invalid.length > 0 ? 422 : 202;
|
|
353
634
|
submissions.push({ token, body, status });
|
|
354
|
-
return status === 422 ?
|
|
635
|
+
return status === 422 ? json2(422, { received: false, invalid }) : json2(202, { received: true });
|
|
355
636
|
}
|
|
356
|
-
if (method !== "GET") return
|
|
357
|
-
if (door === "forms" && rest.length === 0 && Object.hasOwn(forms, token)) return
|
|
358
|
-
if (door === "shares" && rest.length === 0 && Object.hasOwn(shares, token)) return
|
|
637
|
+
if (method !== "GET") return json2(404, NOT_FOUND);
|
|
638
|
+
if (door === "forms" && rest.length === 0 && Object.hasOwn(forms, token)) return json2(200, forms[token]);
|
|
639
|
+
if (door === "shares" && rest.length === 0 && Object.hasOwn(shares, token)) return json2(200, shares[token]);
|
|
359
640
|
if (door === "sites" && Object.hasOwn(sites, token)) {
|
|
360
641
|
return siteDoor(sites[token], rest, url.searchParams, headers["if-none-match"] ?? null);
|
|
361
642
|
}
|
|
362
|
-
return
|
|
643
|
+
return json2(404, NOT_FOUND);
|
|
363
644
|
};
|
|
364
645
|
return {
|
|
365
646
|
podUrl: MOCK_POD_URL,
|
|
@@ -374,13 +655,17 @@ function createMockPod(options = {}) {
|
|
|
374
655
|
}
|
|
375
656
|
// Annotate the CommonJS export names for ESM import in node:
|
|
376
657
|
0 && (module.exports = {
|
|
658
|
+
MOCK_AUTH_ISSUER,
|
|
377
659
|
MOCK_FAILURE_TOKENS,
|
|
378
660
|
MOCK_FORM,
|
|
661
|
+
MOCK_GRANT,
|
|
379
662
|
MOCK_POD_URL,
|
|
380
663
|
MOCK_RETRY_AFTER_SEC,
|
|
381
664
|
MOCK_SHARE,
|
|
382
665
|
MOCK_SITE,
|
|
383
666
|
MOCK_TOKENS,
|
|
667
|
+
MOCK_USER,
|
|
668
|
+
createMockAuthServer,
|
|
384
669
|
createMockPod
|
|
385
670
|
});
|
|
386
671
|
//# sourceMappingURL=testing.cjs.map
|
package/dist/testing.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/testing/index.ts","../src/testing/fixtures.ts"],"sourcesContent":["/**\n * @synap-core/sdk/testing — a mock pod: a fake `fetch` that answers the pod's\n * public doors (forms, shares, sites) from fixtures, with no network. Inject\n * it into the REAL client (`createPublicClient({ podUrl, fetch: pod.fetch })`)\n * so everything above the wire — status handling, typed errors, the models —\n * is the production path. For unit tests, a site's mock mode and a kit's\n * states gallery.\n *\n * Wire behaviour mirrors the backend doors: the uniform 404 for every miss,\n * the constant 202 on a submit, the site doors' query bounds (400), weak\n * ETags with If-None-Match → 304. Like a browser against the real doors, a\n * `credentials: \"include\"` request is refused (it rejects, as a CORS failure\n * does); with `{ browser: true }` a non-safelisted request header is too.\n *\n * tokens MOCK_TOKENS.form / .share / .site the demo form, share and site\n * tokens MOCK_FAILURE_TOKENS.* one failure on every door\n * anything else the uniform 404\n */\nimport type {\n PublicCollectionPage,\n PublicForm,\n PublicRecord,\n PublicRecordSummary,\n PublicShare,\n PublicSite,\n} from \"../public/client\";\nimport { MOCK_FORM, MOCK_SHARE, MOCK_SITE, type MockSite } from \"./fixtures\";\n\nexport { MOCK_FORM, MOCK_SHARE, MOCK_SITE };\nexport type { MockCollection, MockSite } from \"./fixtures\";\n\n/** A placeholder origin; never contacted. */\nexport const MOCK_POD_URL = \"https://mock.synap.invalid\";\n\nexport const MOCK_TOKENS = { form: \"demo-form\", share: \"demo\", site: \"demo-site\" } as const;\n\n/** The `Retry-After` the `rateLimitedRetryAfter` token sends. */\nexport const MOCK_RETRY_AFTER_SEC = 30;\n\n/** Tokens that make every door answer one failure, so each UI state can be rendered. */\nexport const MOCK_FAILURE_TOKENS = {\n /** 429 with no `Retry-After` (what the pod's public doors send today). */\n rateLimited: \"mock-rate-limited\",\n /** 429 with `Retry-After: 30`. */\n rateLimitedRetryAfter: \"mock-rate-limited-retry-after\",\n /** 403 `public_doors_disabled`: the owner switched public pages off. */\n doorsDisabled: \"mock-doors-disabled\",\n /** 500. */\n serverError: \"mock-server-error\",\n /** 503 (a read: `pod_unreachable` / `server`; a submit: `unavailable`). */\n unavailable: \"mock-unavailable\",\n /** No answer: the fetch rejects (`network` / `pod_unreachable`). */\n unreachable: \"mock-unreachable\",\n} as const;\n\nexport interface MockPodOptions {\n /** Forms by token (default: the demo form under `MOCK_TOKENS.form`). */\n forms?: Record<string, PublicForm>;\n /** Shares by token (default: the demo share under `MOCK_TOKENS.share`). */\n shares?: Record<string, PublicShare>;\n /** Sites by token (default: the demo site under `MOCK_TOKENS.site`). */\n sites?: Record<string, MockSite>;\n /** Refuse non-safelisted request headers, as a browser's CORS preflight would. */\n browser?: boolean;\n}\n\nexport interface MockRequest {\n method: string;\n url: string;\n /** Lower-cased header names. */\n headers: Record<string, string>;\n credentials: RequestCredentials | undefined;\n}\n\nexport interface MockSubmission {\n token: string;\n /** The parsed envelope (`fields`, `ticket`, `hp`, …). */\n body: unknown;\n /** The answer the mock gave. */\n status: 202 | 422;\n}\n\nexport interface MockPod {\n podUrl: string;\n fetch: typeof fetch;\n /** Every request seen, in order (assert credentials and headers on it). */\n requests: MockRequest[];\n /** Every submit seen, in order. */\n submissions: MockSubmission[];\n /** Forget `requests` and `submissions`. */\n reset(): void;\n}\n\nconst NOT_FOUND = { error: \"Not found\" };\nconst DOORS_DISABLED = {\n error: \"The owner has not turned on public pages and forms for this workspace.\",\n code: \"public_doors_disabled\",\n};\n/** Request headers a browser sends cross-origin without a preflight. */\nconst SAFELISTED = new Set([\"accept\", \"accept-language\", \"content-language\", \"content-type\"]);\n/** The only request header the real doors allow on a preflight. */\nconst PREFLIGHT_ALLOWED = new Set([\"content-type\"]);\n\nconst PUBLIC_ID_RE = /^p_[A-Za-z0-9]{16}$/;\nconst SLUG_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;\nconst SLUG_MAX_LENGTH = 96;\nconst DIGITS = /^\\d{1,9}$/;\nconst SORT_TERM = /^(-?)([A-Za-z][A-Za-z0-9_]{0,63})$/;\nconst MAX_OFFSET = 10_000;\nconst MAX_LIMIT = 100;\nconst MAX_SORT_KEYS = 3;\nconst PUBLISHED_AT = \"publishedAt\";\n\nfunction json(status: number, body: unknown, headers: Record<string, string> = {}): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: { \"Content-Type\": \"application/json\", \"Cache-Control\": \"no-cache\", ...headers },\n });\n}\n\n/** FNV-1a, 32 bits: a stable weak validator, no crypto needed. */\nfunction weakEtag(value: unknown): string {\n let h = 0x811c9dc5;\n for (const ch of JSON.stringify(value)) {\n h ^= ch.charCodeAt(0);\n h = Math.imul(h, 0x01000193) >>> 0;\n }\n return `W/\"${h.toString(16).padStart(8, \"0\")}\"`;\n}\n\nfunction etagMatches(header: string | null, etag: string): boolean {\n if (!header) return false;\n if (header.trim() === \"*\") return true;\n const strip = (t: string) => t.trim().replace(/^W\\//, \"\");\n return header.split(\",\").some((t) => strip(t) === strip(etag));\n}\n\nfunction failure(token: string): Response | \"reject\" | null {\n switch (token) {\n case MOCK_FAILURE_TOKENS.rateLimited:\n return json(429, { error: \"Too many requests\", retryAfter: \"5 minutes\" });\n case MOCK_FAILURE_TOKENS.rateLimitedRetryAfter:\n return json(429, { error: \"Too many requests\" }, { \"Retry-After\": String(MOCK_RETRY_AFTER_SEC) });\n case MOCK_FAILURE_TOKENS.doorsDisabled:\n return json(403, DOORS_DISABLED);\n case MOCK_FAILURE_TOKENS.serverError:\n return json(500, { error: \"Internal error\" });\n case MOCK_FAILURE_TOKENS.unavailable:\n return json(503, { received: false });\n case MOCK_FAILURE_TOKENS.unreachable:\n return \"reject\";\n default:\n return null;\n }\n}\n\nfunction summary(record: PublicRecord): PublicRecordSummary {\n const { body: _body, ...rest } = record;\n return rest;\n}\n\nfunction manifest(site: MockSite): PublicSite {\n return {\n ...(site.title !== undefined ? { title: site.title } : {}),\n ...(site.description !== undefined ? { description: site.description } : {}),\n publishedOn: site.publishedOn,\n collections: Object.keys(site.collections)\n .sort()\n .map((key) => {\n const c = site.collections[key];\n return { key, count: c.records.length, sort: [...c.sort], pageSize: c.pageSize, addressing: c.addressing };\n }),\n };\n}\n\ntype Sort = Array<{ key: string; desc: boolean }>;\n\n/** The door's `parseCollectionQuery`: the same bounds, the same 400s. */\nfunction parseQuery(params: URLSearchParams, c: { sort: string[]; pageSize: number }): { offset: number; limit: number; sort: Sort } | string {\n for (const name of params.keys()) {\n if (name === \"embed\") return \"embed is not available\";\n if (name === \"ref\" || name.startsWith(\"ref[\")) return \"ref filter is not available\";\n }\n const one = (name: string): string | undefined | null => {\n const all = params.getAll(name);\n return all.length === 0 ? undefined : all.length === 1 ? all[0] : null;\n };\n const rawOffset = one(\"offset\");\n if (rawOffset === null || (rawOffset !== undefined && !DIGITS.test(rawOffset))) return \"invalid offset\";\n const offset = rawOffset === undefined ? 0 : Number(rawOffset);\n if (offset > MAX_OFFSET) return \"offset too large\";\n const rawLimit = one(\"limit\");\n if (rawLimit === null || (rawLimit !== undefined && !DIGITS.test(rawLimit))) return \"invalid limit\";\n const limit = Math.min(MAX_LIMIT, Math.max(1, rawLimit === undefined ? c.pageSize : Number(rawLimit)));\n const rawSort = one(\"sort\");\n if (rawSort === null) return \"invalid sort\";\n const terms =\n rawSort !== undefined ? rawSort.split(\",\") : c.sort.length > 0 ? [...c.sort] : [`-${PUBLISHED_AT}`];\n if (terms.length > MAX_SORT_KEYS) return \"invalid sort\";\n const allowed = new Set([PUBLISHED_AT, ...c.sort.map((s) => s.replace(/^-/, \"\"))]);\n const sort: Sort = [];\n for (const term of terms) {\n const m = SORT_TERM.exec(term);\n if (!m || !allowed.has(m[2]) || sort.some((s) => s.key === m[2])) return \"invalid sort\";\n sort.push({ key: m[2], desc: m[1] === \"-\" });\n }\n return { offset, limit, sort };\n}\n\n/** jsonb-like order within a type (strings < numbers < booleans); a missing value sorts last. */\nfunction compareRecords(sort: Sort) {\n const rank = (v: unknown) => (typeof v === \"string\" ? 0 : typeof v === \"number\" ? 1 : 2);\n const valueOf = (r: PublicRecord, key: string) => (key === PUBLISHED_AT ? r.publishedOn : r.properties[key]);\n return (a: PublicRecord, b: PublicRecord): number => {\n for (const { key, desc } of sort) {\n const va = valueOf(a, key);\n const vb = valueOf(b, key);\n if (va === undefined || vb === undefined) {\n if (va !== vb) return va === undefined ? 1 : -1;\n continue;\n }\n let d = rank(va) - rank(vb);\n if (d === 0) d = va < vb ? -1 : va > vb ? 1 : 0;\n if (d !== 0) return desc ? -d : d;\n }\n if (a.publishedOn !== b.publishedOn) return a.publishedOn < b.publishedOn ? 1 : -1;\n return a.publicId < b.publicId ? -1 : a.publicId > b.publicId ? 1 : 0;\n };\n}\n\n/** 200 with a weak ETag, or 304 when If-None-Match already holds it. */\nfunction cached(view: unknown, ifNoneMatch: string | null): Response {\n const etag = weakEtag(view);\n if (etagMatches(ifNoneMatch, etag)) {\n return new Response(null, { status: 304, headers: { ETag: etag, \"Cache-Control\": \"no-cache\" } });\n }\n return json(200, view, { ETag: etag });\n}\n\nfunction siteDoor(site: MockSite, rest: string[], params: URLSearchParams, ifNoneMatch: string | null): Response {\n if (rest.length === 0) return cached(manifest(site), ifNoneMatch);\n if (rest[0] !== \"collections\" || rest.length > 3 || !Object.hasOwn(site.collections, rest[1])) {\n return json(404, NOT_FOUND);\n }\n const c = site.collections[rest[1]];\n if (rest.length === 2) {\n const q = parseQuery(params, c);\n if (typeof q === \"string\") return json(400, { error: q });\n const sorted = [...c.records].sort(compareRecords(q.sort));\n const page: PublicCollectionPage = {\n items: sorted.slice(q.offset, q.offset + q.limit).map(summary),\n total: sorted.length,\n offset: q.offset,\n limit: q.limit,\n };\n return cached(page, ifNoneMatch);\n }\n const address = rest[2];\n const byPublicId = PUBLIC_ID_RE.test(address);\n if (!byPublicId && !(address.length <= SLUG_MAX_LENGTH && SLUG_RE.test(address))) return json(404, NOT_FOUND);\n const record = c.records.find((r) => (byPublicId ? r.publicId === address : r.slug === address));\n if (!record) return json(404, NOT_FOUND);\n if (params.has(\"embed\")) return json(400, { error: \"embed is not available\" });\n return cached(record, ifNoneMatch);\n}\n\n/** The door's 422 for answers that do not satisfy the form (required, enum). */\nfunction invalidKeys(form: PublicForm, body: unknown): string[] {\n const fields = (body as { fields?: unknown } | null)?.fields;\n if (typeof fields !== \"object\" || fields === null) return [];\n const answers = fields as Record<string, unknown>;\n return form.fields\n .filter((f) => {\n const v = answers[f.key];\n if (v === undefined || v === \"\" || v === null) return f.required === true;\n if (f.type === \"boolean\") return typeof v !== \"boolean\" || (f.required === true && v !== true);\n const allowed = f.constraints?.enum;\n return allowed !== undefined && !allowed.includes(String(v));\n })\n .map((f) => f.key);\n}\n\nexport function createMockPod(options: MockPodOptions = {}): MockPod {\n const forms = options.forms ?? { [MOCK_TOKENS.form]: MOCK_FORM };\n const shares = options.shares ?? { [MOCK_TOKENS.share]: MOCK_SHARE };\n const sites = options.sites ?? { [MOCK_TOKENS.site]: MOCK_SITE };\n const requests: MockRequest[] = [];\n const submissions: MockSubmission[] = [];\n\n const mockFetch = async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {\n const request = typeof input === \"object\" && \"method\" in input ? input : null;\n const url = new URL(typeof input === \"string\" ? input : input instanceof URL ? input.href : input.url);\n const method = (init?.method ?? request?.method ?? \"GET\").toUpperCase();\n const headers: Record<string, string> = {};\n new Headers(init?.headers ?? request?.headers).forEach((value, name) => {\n headers[name.toLowerCase()] = value;\n });\n const credentials = init?.credentials ?? request?.credentials;\n requests.push({ method, url: url.href, headers, credentials });\n\n // What a browser does against the real doors (`Access-Control-Allow-Origin: *`).\n if (credentials === \"include\") throw new TypeError(\"Failed to fetch: credentialed request to a public door\");\n if (options.browser) {\n const preflighted = Object.keys(headers).filter((h) => !SAFELISTED.has(h) && !PREFLIGHT_ALLOWED.has(h));\n if (preflighted.length > 0) throw new TypeError(`Failed to fetch: preflight refuses ${preflighted.join(\", \")}`);\n }\n\n const m = url.pathname.match(/^\\/api\\/hub\\/public\\/(forms|shares|sites)\\/(.+)$/);\n if (!m) return json(404, NOT_FOUND);\n let segments: string[];\n try {\n segments = m[2].split(\"/\").map(decodeURIComponent);\n } catch {\n return json(404, NOT_FOUND);\n }\n const [token, ...rest] = segments;\n const failed = failure(token);\n if (failed === \"reject\") throw new TypeError(\"Failed to fetch: mock pod unreachable\");\n if (failed) return failed;\n\n const door = m[1];\n if (door === \"forms\" && method === \"POST\" && rest.length === 0) {\n let body: unknown = null;\n try {\n body = JSON.parse(typeof init?.body === \"string\" ? init.body : \"null\");\n } catch {\n body = null;\n }\n const form = Object.hasOwn(forms, token) ? forms[token] : undefined;\n const invalid = form ? invalidKeys(form, body) : [];\n const status = invalid.length > 0 ? 422 : 202;\n submissions.push({ token, body, status });\n return status === 422 ? json(422, { received: false, invalid }) : json(202, { received: true });\n }\n if (method !== \"GET\") return json(404, NOT_FOUND);\n if (door === \"forms\" && rest.length === 0 && Object.hasOwn(forms, token)) return json(200, forms[token]);\n if (door === \"shares\" && rest.length === 0 && Object.hasOwn(shares, token)) return json(200, shares[token]);\n if (door === \"sites\" && Object.hasOwn(sites, token)) {\n return siteDoor(sites[token], rest, url.searchParams, headers[\"if-none-match\"] ?? null);\n }\n return json(404, NOT_FOUND);\n };\n\n return {\n podUrl: MOCK_POD_URL,\n fetch: mockFetch as typeof fetch,\n requests,\n submissions,\n reset() {\n requests.length = 0;\n submissions.length = 0;\n },\n };\n}\n","/**\n * Demo data for the mock pod. Shapes are the `@synap-core/sdk/public` wire\n * types, so a fixture that drifts from the contract fails to compile.\n */\nimport type { PublicForm, PublicRecord, PublicShare } from \"../public/client\";\n\nexport const MOCK_FORM: PublicForm = {\n title: \"Get in touch\",\n fields: [\n { key: \"name\", label: \"Name\", type: \"text\", required: true, constraints: { maxLength: 120 } },\n { key: \"email\", label: \"Email\", type: \"email\", required: true },\n {\n key: \"topic\",\n label: \"Topic\",\n type: \"enum\",\n constraints: { enum: [\"Sales\", \"Support\", \"Partnership\"] },\n },\n { key: \"message\", label: \"Message\", type: \"richtext\", help: \"A few lines is plenty.\" },\n ],\n successMessage: \"Thanks — we received your message.\",\n honeypotField: \"hp\",\n ticket: \"mock-ticket\",\n minSubmitMs: 0,\n captcha: { required: false },\n};\n\nexport const MOCK_SHARE: PublicShare = {\n resourceType: \"entity\",\n title: \"Launch notes\",\n properties: { status: \"open\", owner: \"Ada\", featured: true },\n body: {\n format: \"markdown\",\n content: [\n \"# Launch notes\",\n \"\",\n \"This page is a **published snapshot** from a Synap pod.\",\n \"\",\n \"- Read the [Synap docs](https://synap.live)\",\n \"- Links other than http, https and mailto render as plain text: [bad](javascript:alert(1))\",\n \"\",\n \"<script>raw HTML is shown as text, never run</script>\",\n ].join(\"\\n\"),\n },\n publishedOn: \"2026-09-27\",\n};\n\n/** One collection of a mock site: its door config and its live records. */\nexport interface MockCollection {\n /** Default sort keys (`-` = descending); also the sort keys the door accepts. */\n sort: string[];\n pageSize: number;\n addressing: \"slug\" | \"publicId\";\n records: PublicRecord[];\n}\n\n/** A mock published site. The manifest is derived from it (counts included). */\nexport interface MockSite {\n title?: string;\n description?: string;\n publishedOn: string;\n collections: Record<string, MockCollection>;\n}\n\n/** A public id of the door's shape (`p_` + 16 base62), deterministic. */\nfunction publicId(prefix: string, n: number): string {\n return `p_${prefix}${String(n).padStart(16 - prefix.length, \"0\")}`;\n}\n\nfunction slugify(s: string): string {\n return s\n .normalize(\"NFKD\")\n .replace(/[̀-ͯ]/g, \"\")\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .slice(0, 96);\n}\n\nconst PAPER_TITLES = [\n \"Sparse graphs at scale\",\n \"Learning to rank, revisited\",\n \"A note on causal tracing\",\n \"Small models, long contexts\",\n \"Evaluating retrieval honestly\",\n \"Robust tokenisation\",\n \"Graphs that forget\",\n \"Calibration under shift\",\n \"Latent space cartography\",\n \"Proofs as programs, again\",\n \"Cheap uncertainty\",\n \"Résumé of a decade\",\n];\nconst VENUES = [\"NeurIPS\", \"ICML\", \"ACL\", \"ICLR\"];\n\nconst PAPERS: PublicRecord[] = PAPER_TITLES.map((title, i) => ({\n collection: \"paper\",\n publicId: publicId(\"paper\", i + 1),\n slug: slugify(title),\n title,\n properties: {\n year: 2026 - (i % 5),\n venue: VENUES[i % VENUES.length],\n open: i % 3 !== 0,\n },\n publishedOn: `2026-09-${String(28 - i).padStart(2, \"0\")}`,\n body: i % 4 === 3 ? null : { format: \"markdown\", content: `# ${title}\\n\\nAbstract of **${title}**.` },\n}));\n\nconst PEOPLE: PublicRecord[] = [\"Ada Lovelace\", \"Alan Turing\", \"Grace Hopper\"].map((name, i) => ({\n collection: \"person\",\n publicId: publicId(\"person\", i + 1),\n slug: null,\n title: name,\n properties: { name, role: i === 0 ? \"Lead\" : \"Researcher\" },\n publishedOn: \"2026-09-01\",\n body: null,\n}));\n\n/**\n * The demo site: `paper` (12 records, 5 per page, by slug: pagination),\n * `person` (by public id), `event` (nothing published: the empty state).\n */\nexport const MOCK_SITE: MockSite = {\n title: \"Demo lab\",\n description: \"What the lab publishes\",\n publishedOn: \"2026-09-01\",\n collections: {\n paper: { sort: [\"-year\"], pageSize: 5, addressing: \"slug\", records: PAPERS },\n person: { sort: [\"name\"], pageSize: 20, addressing: \"publicId\", records: PEOPLE },\n event: { sort: [], pageSize: 10, addressing: \"slug\", records: [] },\n },\n};\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/testing/index.ts","../src/testing/fixtures.ts","../src/connect/encoding.ts","../src/testing/auth-server.ts"],"sourcesContent":["/**\n * @synap-core/sdk/testing — a mock pod: a fake `fetch` that answers the pod's\n * public doors (forms, shares, sites) from fixtures, with no network. Inject\n * it into the REAL client (`createPublicClient({ podUrl, fetch: pod.fetch })`)\n * so everything above the wire — status handling, typed errors, the models —\n * is the production path. For unit tests, a site's mock mode and a kit's\n * states gallery.\n *\n * Wire behaviour mirrors the backend doors: the uniform 404 for every miss,\n * the constant 202 on a submit, the site doors' query bounds (400), weak\n * ETags with If-None-Match → 304. Like a browser against the real doors, a\n * `credentials: \"include\"` request is refused (it rejects, as a CORS failure\n * does); with `{ browser: true }` a non-safelisted request header is too.\n *\n * tokens MOCK_TOKENS.form / .share / .site the demo form, share and site\n * tokens MOCK_FAILURE_TOKENS.* one failure on every door\n * anything else the uniform 404\n *\n * Pod-app sign-in (`@synap-core/sdk/connect`): `createMockAuthServer()`, see\n * ./auth-server.ts.\n */\nimport type {\n PublicCollectionPage,\n PublicForm,\n PublicRecord,\n PublicRecordSummary,\n PublicShare,\n PublicSite,\n} from \"../public/client\";\nimport { MOCK_FORM, MOCK_SHARE, MOCK_SITE, type MockSite } from \"./fixtures\";\n\nexport { MOCK_FORM, MOCK_SHARE, MOCK_SITE };\nexport type { MockCollection, MockSite } from \"./fixtures\";\n// Pod-app sign-in: a mock authorization server + Hub REST doors.\nexport * from \"./auth-server\";\n\n/** A placeholder origin; never contacted. */\nexport const MOCK_POD_URL = \"https://mock.synap.invalid\";\n\nexport const MOCK_TOKENS = { form: \"demo-form\", share: \"demo\", site: \"demo-site\" } as const;\n\n/** The `Retry-After` the `rateLimitedRetryAfter` token sends. */\nexport const MOCK_RETRY_AFTER_SEC = 30;\n\n/** Tokens that make every door answer one failure, so each UI state can be rendered. */\nexport const MOCK_FAILURE_TOKENS = {\n /** 429 with no `Retry-After` (what the pod's public doors send today). */\n rateLimited: \"mock-rate-limited\",\n /** 429 with `Retry-After: 30`. */\n rateLimitedRetryAfter: \"mock-rate-limited-retry-after\",\n /** 403 `public_doors_disabled`: the owner switched public pages off. */\n doorsDisabled: \"mock-doors-disabled\",\n /** 500. */\n serverError: \"mock-server-error\",\n /** 503 (a read: `pod_unreachable` / `server`; a submit: `unavailable`). */\n unavailable: \"mock-unavailable\",\n /** No answer: the fetch rejects (`network` / `pod_unreachable`). */\n unreachable: \"mock-unreachable\",\n} as const;\n\nexport interface MockPodOptions {\n /** Forms by token (default: the demo form under `MOCK_TOKENS.form`). */\n forms?: Record<string, PublicForm>;\n /** Shares by token (default: the demo share under `MOCK_TOKENS.share`). */\n shares?: Record<string, PublicShare>;\n /** Sites by token (default: the demo site under `MOCK_TOKENS.site`). */\n sites?: Record<string, MockSite>;\n /** Refuse non-safelisted request headers, as a browser's CORS preflight would. */\n browser?: boolean;\n}\n\nexport interface MockRequest {\n method: string;\n url: string;\n /** Lower-cased header names. */\n headers: Record<string, string>;\n credentials: RequestCredentials | undefined;\n}\n\nexport interface MockSubmission {\n token: string;\n /** The parsed envelope (`fields`, `ticket`, `hp`, …). */\n body: unknown;\n /** The answer the mock gave. */\n status: 202 | 422;\n}\n\nexport interface MockPod {\n podUrl: string;\n fetch: typeof fetch;\n /** Every request seen, in order (assert credentials and headers on it). */\n requests: MockRequest[];\n /** Every submit seen, in order. */\n submissions: MockSubmission[];\n /** Forget `requests` and `submissions`. */\n reset(): void;\n}\n\nconst NOT_FOUND = { error: \"Not found\" };\nconst DOORS_DISABLED = {\n error: \"The owner has not turned on public pages and forms for this workspace.\",\n code: \"public_doors_disabled\",\n};\n/** Request headers a browser sends cross-origin without a preflight. */\nconst SAFELISTED = new Set([\"accept\", \"accept-language\", \"content-language\", \"content-type\"]);\n/** The only request header the real doors allow on a preflight. */\nconst PREFLIGHT_ALLOWED = new Set([\"content-type\"]);\n\nconst PUBLIC_ID_RE = /^p_[A-Za-z0-9]{16}$/;\nconst SLUG_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;\nconst SLUG_MAX_LENGTH = 96;\nconst DIGITS = /^\\d{1,9}$/;\nconst SORT_TERM = /^(-?)([A-Za-z][A-Za-z0-9_]{0,63})$/;\nconst MAX_OFFSET = 10_000;\nconst MAX_LIMIT = 100;\nconst MAX_SORT_KEYS = 3;\nconst PUBLISHED_AT = \"publishedAt\";\n\nfunction json(status: number, body: unknown, headers: Record<string, string> = {}): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: { \"Content-Type\": \"application/json\", \"Cache-Control\": \"no-cache\", ...headers },\n });\n}\n\n/** FNV-1a, 32 bits: a stable weak validator, no crypto needed. */\nfunction weakEtag(value: unknown): string {\n let h = 0x811c9dc5;\n for (const ch of JSON.stringify(value)) {\n h ^= ch.charCodeAt(0);\n h = Math.imul(h, 0x01000193) >>> 0;\n }\n return `W/\"${h.toString(16).padStart(8, \"0\")}\"`;\n}\n\nfunction etagMatches(header: string | null, etag: string): boolean {\n if (!header) return false;\n if (header.trim() === \"*\") return true;\n const strip = (t: string) => t.trim().replace(/^W\\//, \"\");\n return header.split(\",\").some((t) => strip(t) === strip(etag));\n}\n\nfunction failure(token: string): Response | \"reject\" | null {\n switch (token) {\n case MOCK_FAILURE_TOKENS.rateLimited:\n return json(429, { error: \"Too many requests\", retryAfter: \"5 minutes\" });\n case MOCK_FAILURE_TOKENS.rateLimitedRetryAfter:\n return json(429, { error: \"Too many requests\" }, { \"Retry-After\": String(MOCK_RETRY_AFTER_SEC) });\n case MOCK_FAILURE_TOKENS.doorsDisabled:\n return json(403, DOORS_DISABLED);\n case MOCK_FAILURE_TOKENS.serverError:\n return json(500, { error: \"Internal error\" });\n case MOCK_FAILURE_TOKENS.unavailable:\n return json(503, { received: false });\n case MOCK_FAILURE_TOKENS.unreachable:\n return \"reject\";\n default:\n return null;\n }\n}\n\nfunction summary(record: PublicRecord): PublicRecordSummary {\n const { body: _body, ...rest } = record;\n return rest;\n}\n\nfunction manifest(site: MockSite): PublicSite {\n return {\n ...(site.title !== undefined ? { title: site.title } : {}),\n ...(site.description !== undefined ? { description: site.description } : {}),\n publishedOn: site.publishedOn,\n collections: Object.keys(site.collections)\n .sort()\n .map((key) => {\n const c = site.collections[key];\n return { key, count: c.records.length, sort: [...c.sort], pageSize: c.pageSize, addressing: c.addressing };\n }),\n };\n}\n\ntype Sort = Array<{ key: string; desc: boolean }>;\n\n/** The door's `parseCollectionQuery`: the same bounds, the same 400s. */\nfunction parseQuery(params: URLSearchParams, c: { sort: string[]; pageSize: number }): { offset: number; limit: number; sort: Sort } | string {\n for (const name of params.keys()) {\n if (name === \"embed\") return \"embed is not available\";\n if (name === \"ref\" || name.startsWith(\"ref[\")) return \"ref filter is not available\";\n }\n const one = (name: string): string | undefined | null => {\n const all = params.getAll(name);\n return all.length === 0 ? undefined : all.length === 1 ? all[0] : null;\n };\n const rawOffset = one(\"offset\");\n if (rawOffset === null || (rawOffset !== undefined && !DIGITS.test(rawOffset))) return \"invalid offset\";\n const offset = rawOffset === undefined ? 0 : Number(rawOffset);\n if (offset > MAX_OFFSET) return \"offset too large\";\n const rawLimit = one(\"limit\");\n if (rawLimit === null || (rawLimit !== undefined && !DIGITS.test(rawLimit))) return \"invalid limit\";\n const limit = Math.min(MAX_LIMIT, Math.max(1, rawLimit === undefined ? c.pageSize : Number(rawLimit)));\n const rawSort = one(\"sort\");\n if (rawSort === null) return \"invalid sort\";\n const terms =\n rawSort !== undefined ? rawSort.split(\",\") : c.sort.length > 0 ? [...c.sort] : [`-${PUBLISHED_AT}`];\n if (terms.length > MAX_SORT_KEYS) return \"invalid sort\";\n const allowed = new Set([PUBLISHED_AT, ...c.sort.map((s) => s.replace(/^-/, \"\"))]);\n const sort: Sort = [];\n for (const term of terms) {\n const m = SORT_TERM.exec(term);\n if (!m || !allowed.has(m[2]) || sort.some((s) => s.key === m[2])) return \"invalid sort\";\n sort.push({ key: m[2], desc: m[1] === \"-\" });\n }\n return { offset, limit, sort };\n}\n\n/** jsonb-like order within a type (strings < numbers < booleans); a missing value sorts last. */\nfunction compareRecords(sort: Sort) {\n const rank = (v: unknown) => (typeof v === \"string\" ? 0 : typeof v === \"number\" ? 1 : 2);\n const valueOf = (r: PublicRecord, key: string) => (key === PUBLISHED_AT ? r.publishedOn : r.properties[key]);\n return (a: PublicRecord, b: PublicRecord): number => {\n for (const { key, desc } of sort) {\n const va = valueOf(a, key);\n const vb = valueOf(b, key);\n if (va === undefined || vb === undefined) {\n if (va !== vb) return va === undefined ? 1 : -1;\n continue;\n }\n let d = rank(va) - rank(vb);\n if (d === 0) d = va < vb ? -1 : va > vb ? 1 : 0;\n if (d !== 0) return desc ? -d : d;\n }\n if (a.publishedOn !== b.publishedOn) return a.publishedOn < b.publishedOn ? 1 : -1;\n return a.publicId < b.publicId ? -1 : a.publicId > b.publicId ? 1 : 0;\n };\n}\n\n/** 200 with a weak ETag, or 304 when If-None-Match already holds it. */\nfunction cached(view: unknown, ifNoneMatch: string | null): Response {\n const etag = weakEtag(view);\n if (etagMatches(ifNoneMatch, etag)) {\n return new Response(null, { status: 304, headers: { ETag: etag, \"Cache-Control\": \"no-cache\" } });\n }\n return json(200, view, { ETag: etag });\n}\n\nfunction siteDoor(site: MockSite, rest: string[], params: URLSearchParams, ifNoneMatch: string | null): Response {\n if (rest.length === 0) return cached(manifest(site), ifNoneMatch);\n if (rest[0] !== \"collections\" || rest.length > 3 || !Object.hasOwn(site.collections, rest[1])) {\n return json(404, NOT_FOUND);\n }\n const c = site.collections[rest[1]];\n if (rest.length === 2) {\n const q = parseQuery(params, c);\n if (typeof q === \"string\") return json(400, { error: q });\n const sorted = [...c.records].sort(compareRecords(q.sort));\n const page: PublicCollectionPage = {\n items: sorted.slice(q.offset, q.offset + q.limit).map(summary),\n total: sorted.length,\n offset: q.offset,\n limit: q.limit,\n };\n return cached(page, ifNoneMatch);\n }\n const address = rest[2];\n const byPublicId = PUBLIC_ID_RE.test(address);\n if (!byPublicId && !(address.length <= SLUG_MAX_LENGTH && SLUG_RE.test(address))) return json(404, NOT_FOUND);\n const record = c.records.find((r) => (byPublicId ? r.publicId === address : r.slug === address));\n if (!record) return json(404, NOT_FOUND);\n if (params.has(\"embed\")) return json(400, { error: \"embed is not available\" });\n return cached(record, ifNoneMatch);\n}\n\n/** The door's 422 for answers that do not satisfy the form (required, enum). */\nfunction invalidKeys(form: PublicForm, body: unknown): string[] {\n const fields = (body as { fields?: unknown } | null)?.fields;\n if (typeof fields !== \"object\" || fields === null) return [];\n const answers = fields as Record<string, unknown>;\n return form.fields\n .filter((f) => {\n const v = answers[f.key];\n if (v === undefined || v === \"\" || v === null) return f.required === true;\n if (f.type === \"boolean\") return typeof v !== \"boolean\" || (f.required === true && v !== true);\n const allowed = f.constraints?.enum;\n return allowed !== undefined && !allowed.includes(String(v));\n })\n .map((f) => f.key);\n}\n\nexport function createMockPod(options: MockPodOptions = {}): MockPod {\n const forms = options.forms ?? { [MOCK_TOKENS.form]: MOCK_FORM };\n const shares = options.shares ?? { [MOCK_TOKENS.share]: MOCK_SHARE };\n const sites = options.sites ?? { [MOCK_TOKENS.site]: MOCK_SITE };\n const requests: MockRequest[] = [];\n const submissions: MockSubmission[] = [];\n\n const mockFetch = async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {\n const request = typeof input === \"object\" && \"method\" in input ? input : null;\n const url = new URL(typeof input === \"string\" ? input : input instanceof URL ? input.href : input.url);\n const method = (init?.method ?? request?.method ?? \"GET\").toUpperCase();\n const headers: Record<string, string> = {};\n new Headers(init?.headers ?? request?.headers).forEach((value, name) => {\n headers[name.toLowerCase()] = value;\n });\n const credentials = init?.credentials ?? request?.credentials;\n requests.push({ method, url: url.href, headers, credentials });\n\n // What a browser does against the real doors (`Access-Control-Allow-Origin: *`).\n if (credentials === \"include\") throw new TypeError(\"Failed to fetch: credentialed request to a public door\");\n if (options.browser) {\n const preflighted = Object.keys(headers).filter((h) => !SAFELISTED.has(h) && !PREFLIGHT_ALLOWED.has(h));\n if (preflighted.length > 0) throw new TypeError(`Failed to fetch: preflight refuses ${preflighted.join(\", \")}`);\n }\n\n const m = url.pathname.match(/^\\/api\\/hub\\/public\\/(forms|shares|sites)\\/(.+)$/);\n if (!m) return json(404, NOT_FOUND);\n let segments: string[];\n try {\n segments = m[2].split(\"/\").map(decodeURIComponent);\n } catch {\n return json(404, NOT_FOUND);\n }\n const [token, ...rest] = segments;\n const failed = failure(token);\n if (failed === \"reject\") throw new TypeError(\"Failed to fetch: mock pod unreachable\");\n if (failed) return failed;\n\n const door = m[1];\n if (door === \"forms\" && method === \"POST\" && rest.length === 0) {\n let body: unknown = null;\n try {\n body = JSON.parse(typeof init?.body === \"string\" ? init.body : \"null\");\n } catch {\n body = null;\n }\n const form = Object.hasOwn(forms, token) ? forms[token] : undefined;\n const invalid = form ? invalidKeys(form, body) : [];\n const status = invalid.length > 0 ? 422 : 202;\n submissions.push({ token, body, status });\n return status === 422 ? json(422, { received: false, invalid }) : json(202, { received: true });\n }\n if (method !== \"GET\") return json(404, NOT_FOUND);\n if (door === \"forms\" && rest.length === 0 && Object.hasOwn(forms, token)) return json(200, forms[token]);\n if (door === \"shares\" && rest.length === 0 && Object.hasOwn(shares, token)) return json(200, shares[token]);\n if (door === \"sites\" && Object.hasOwn(sites, token)) {\n return siteDoor(sites[token], rest, url.searchParams, headers[\"if-none-match\"] ?? null);\n }\n return json(404, NOT_FOUND);\n };\n\n return {\n podUrl: MOCK_POD_URL,\n fetch: mockFetch as typeof fetch,\n requests,\n submissions,\n reset() {\n requests.length = 0;\n submissions.length = 0;\n },\n };\n}\n","/**\n * Demo data for the mock pod. Shapes are the `@synap-core/sdk/public` wire\n * types, so a fixture that drifts from the contract fails to compile.\n */\nimport type { PublicForm, PublicRecord, PublicShare } from \"../public/client\";\n\nexport const MOCK_FORM: PublicForm = {\n title: \"Get in touch\",\n fields: [\n { key: \"name\", label: \"Name\", type: \"text\", required: true, constraints: { maxLength: 120 } },\n { key: \"email\", label: \"Email\", type: \"email\", required: true },\n {\n key: \"topic\",\n label: \"Topic\",\n type: \"enum\",\n constraints: { enum: [\"Sales\", \"Support\", \"Partnership\"] },\n },\n { key: \"message\", label: \"Message\", type: \"richtext\", help: \"A few lines is plenty.\" },\n ],\n successMessage: \"Thanks — we received your message.\",\n honeypotField: \"hp\",\n ticket: \"mock-ticket\",\n minSubmitMs: 0,\n captcha: { required: false },\n};\n\nexport const MOCK_SHARE: PublicShare = {\n resourceType: \"entity\",\n title: \"Launch notes\",\n properties: { status: \"open\", owner: \"Ada\", featured: true },\n body: {\n format: \"markdown\",\n content: [\n \"# Launch notes\",\n \"\",\n \"This page is a **published snapshot** from a Synap pod.\",\n \"\",\n \"- Read the [Synap docs](https://synap.live)\",\n \"- Links other than http, https and mailto render as plain text: [bad](javascript:alert(1))\",\n \"\",\n \"<script>raw HTML is shown as text, never run</script>\",\n ].join(\"\\n\"),\n },\n publishedOn: \"2026-09-27\",\n};\n\n/** One collection of a mock site: its door config and its live records. */\nexport interface MockCollection {\n /** Default sort keys (`-` = descending); also the sort keys the door accepts. */\n sort: string[];\n pageSize: number;\n addressing: \"slug\" | \"publicId\";\n records: PublicRecord[];\n}\n\n/** A mock published site. The manifest is derived from it (counts included). */\nexport interface MockSite {\n title?: string;\n description?: string;\n publishedOn: string;\n collections: Record<string, MockCollection>;\n}\n\n/** A public id of the door's shape (`p_` + 16 base62), deterministic. */\nfunction publicId(prefix: string, n: number): string {\n return `p_${prefix}${String(n).padStart(16 - prefix.length, \"0\")}`;\n}\n\nfunction slugify(s: string): string {\n return s\n .normalize(\"NFKD\")\n .replace(/[̀-ͯ]/g, \"\")\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .slice(0, 96);\n}\n\nconst PAPER_TITLES = [\n \"Sparse graphs at scale\",\n \"Learning to rank, revisited\",\n \"A note on causal tracing\",\n \"Small models, long contexts\",\n \"Evaluating retrieval honestly\",\n \"Robust tokenisation\",\n \"Graphs that forget\",\n \"Calibration under shift\",\n \"Latent space cartography\",\n \"Proofs as programs, again\",\n \"Cheap uncertainty\",\n \"Résumé of a decade\",\n];\nconst VENUES = [\"NeurIPS\", \"ICML\", \"ACL\", \"ICLR\"];\n\nconst PAPERS: PublicRecord[] = PAPER_TITLES.map((title, i) => ({\n collection: \"paper\",\n publicId: publicId(\"paper\", i + 1),\n slug: slugify(title),\n title,\n properties: {\n year: 2026 - (i % 5),\n venue: VENUES[i % VENUES.length],\n open: i % 3 !== 0,\n },\n publishedOn: `2026-09-${String(28 - i).padStart(2, \"0\")}`,\n body: i % 4 === 3 ? null : { format: \"markdown\", content: `# ${title}\\n\\nAbstract of **${title}**.` },\n}));\n\nconst PEOPLE: PublicRecord[] = [\"Ada Lovelace\", \"Alan Turing\", \"Grace Hopper\"].map((name, i) => ({\n collection: \"person\",\n publicId: publicId(\"person\", i + 1),\n slug: null,\n title: name,\n properties: { name, role: i === 0 ? \"Lead\" : \"Researcher\" },\n publishedOn: \"2026-09-01\",\n body: null,\n}));\n\n/**\n * The demo site: `paper` (12 records, 5 per page, by slug: pagination),\n * `person` (by public id), `event` (nothing published: the empty state).\n */\nexport const MOCK_SITE: MockSite = {\n title: \"Demo lab\",\n description: \"What the lab publishes\",\n publishedOn: \"2026-09-01\",\n collections: {\n paper: { sort: [\"-year\"], pageSize: 5, addressing: \"slug\", records: PAPERS },\n person: { sort: [\"name\"], pageSize: 20, addressing: \"publicId\", records: PEOPLE },\n event: { sort: [], pageSize: 10, addressing: \"slug\", records: [] },\n },\n};\n","/**\n * Web-standard primitives (WebCrypto, TextEncoder, btoa/atob): the same code\n * runs on Node 18+, edge runtimes and workers. No Node import.\n */\n\nconst encoder = new TextEncoder();\nconst decoder = new TextDecoder();\n\n/** `Uint8Array<ArrayBuffer>`: what WebCrypto's `BufferSource` takes under TS 5.7+ lib types. */\nexport type Bytes = Uint8Array<ArrayBuffer>;\n\nexport function utf8(text: string): Bytes {\n return encoder.encode(text);\n}\n\nexport function fromUtf8(bytes: Uint8Array): string {\n return decoder.decode(bytes);\n}\n\nexport function base64url(bytes: Uint8Array): string {\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);\n return btoa(binary).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\n/** Null when `text` is not base64url. */\nexport function fromBase64url(text: string): Bytes | null {\n if (!/^[A-Za-z0-9_-]*$/.test(text)) return null;\n const padded = text.replace(/-/g, \"+\").replace(/_/g, \"/\") + \"===\".slice((text.length + 3) % 4);\n let binary: string;\n try {\n binary = atob(padded);\n } catch {\n return null;\n }\n const out = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);\n return out;\n}\n\nexport function randomBytes(length: number): Bytes {\n const out = new Uint8Array(length);\n crypto.getRandomValues(out);\n return out;\n}\n\n/** 32 random bytes, base64url (43 chars): a state value or a PKCE verifier. */\nexport function randomToken(): string {\n return base64url(randomBytes(32));\n}\n\n/** RFC 7636 §4.2: `BASE64URL(SHA256(ASCII(code_verifier)))`. */\nexport async function pkceChallenge(verifier: string): Promise<string> {\n const digest = await crypto.subtle.digest(\"SHA-256\", utf8(verifier));\n return base64url(new Uint8Array(digest));\n}\n\n/** Compare two strings without an early exit on the first differing byte. */\nexport function constantTimeEqual(a: string, b: string): boolean {\n const x = utf8(a);\n const y = utf8(b);\n let diff = x.length ^ y.length;\n for (let i = 0; i < Math.max(x.length, y.length); i++) diff |= (x[i] ?? 0) ^ (y[i] ?? 0);\n return diff === 0;\n}\n\n/** The request's cookies by name (the first value wins, as browsers order the most specific first). */\nexport function parseCookies(header: string | null): Map<string, string> {\n const out = new Map<string, string>();\n if (!header) return out;\n for (const part of header.split(\";\")) {\n const eq = part.indexOf(\"=\");\n if (eq < 0) continue;\n const name = part.slice(0, eq).trim();\n if (!name || out.has(name)) continue;\n let value = part.slice(eq + 1).trim();\n if (value.startsWith('\"') && value.endsWith('\"')) value = value.slice(1, -1);\n out.set(name, value);\n }\n return out;\n}\n\nexport interface CookieAttributes {\n maxAgeSec: number;\n secure: boolean;\n}\n\n/** `HttpOnly; SameSite=Lax; Path=/` always; `Secure` on https. Never a `Domain`. */\nexport function serializeCookie(name: string, value: string, attrs: CookieAttributes): string {\n const parts = [`${name}=${value}`, \"Path=/\", \"HttpOnly\", \"SameSite=Lax\", `Max-Age=${Math.max(0, Math.floor(attrs.maxAgeSec))}`];\n if (attrs.secure) parts.push(\"Secure\");\n return parts.join(\"; \");\n}\n","/**\n * A mock pod for pod-app sign-in: its OAuth authorization server (RFC 8414\n * metadata, /authorize as a function standing in for the consent screen,\n * /token, /revoke) and a few Hub REST doors behind the Bearer token. Inject\n * `fetch` into the REAL `@synap-core/sdk/connect` (`createPodAppAuth({ fetch })`)\n * so everything above the wire is the production path. No network.\n *\n * Wire behaviour mirrors synap-backend `routers/oauth/routes.ts` for a CIMD\n * client: `iss` on every authorization answer (RFC 9207), PKCE S256 checked at\n * /token, single-use codes bound to client_id + redirect_uri + resource, the\n * token response's `synap_grant` / `synap_user`. Refresh mirrors PB3\n * (`routers/oauth/refresh.ts`): rotating and single-use, 30 days from issue;\n * a token presented AGAIN after its use is a reuse that revokes its whole\n * family AND the live access key; a new code ends the client's older\n * families; /revoke with a refresh token kills its family and access key.\n * `refresh: false` is a pod before PB3 (no refresh_token at all).\n *\n * mock.authorize(authorizeUrl) → the callback URL (approved)\n * mock.authorize(authorizeUrl, { deny: true }) → error=access_denied\n * mock.authorize(authorizeUrl, { iss: \"https://evil.example\" }) → a mix-up attempt\n * mock.loseNextRefreshRace() → the next redeem is a concurrent loser (refused, no revoke)\n * mock.consumeRefreshToken(t) → the token was used elsewhere (presenting it is a reuse)\n * mock.revokeAll() → the person removed the app in their pod\n */\nimport { pkceChallenge, randomToken } from \"../connect/encoding\";\nimport type { PodGrant, PodUser } from \"../connect/types\";\n\n/** A placeholder pod origin; never contacted. */\nexport const MOCK_AUTH_ISSUER = \"https://pod.mock-synap.test\";\n\nexport const MOCK_GRANT: PodGrant[] = [\n { permission: \"entity.post.read\", workspaceId: \"ws_mock_studio\", workspaceName: \"Studio\" },\n { permission: \"entity.post.create\", workspaceId: \"ws_mock_studio\", workspaceName: \"Studio\" },\n];\n\nexport const MOCK_USER: PodUser = { id: \"user_mock_ada\", name: \"Ada Lovelace\" };\n\nexport type MockHubFailure = \"rate_limited\" | \"forbidden\" | \"door_unsupported\" | \"unreachable\" | \"server\" | \"unavailable\";\n\nexport interface MockAuthServerOptions {\n issuer?: string;\n /** Issue rotating refresh tokens (PB3). Default true; false = a pod before PB3. */\n refresh?: boolean;\n /** Default 3600 (the pod's 1 h). */\n accessTtlSec?: number;\n /** Default 30 days. */\n refreshIdleSec?: number;\n /** Send `refresh_token_expires_in` with each refresh token. Default false (the pod does not say). */\n advertiseRefreshExpiry?: boolean;\n grant?: PodGrant[];\n user?: PodUser;\n /** Override or remove (undefined) metadata fields: a pod too old, a lying issuer. */\n metadata?: Record<string, unknown>;\n /** Hub writes: \"proposed\" (ask-first, the default for an app) or \"applied\". */\n writeMode?: \"proposed\" | \"applied\";\n /** Hub paths (after /api/hub) that answer one failure. */\n failures?: Record<string, MockHubFailure>;\n now?: () => number;\n}\n\nexport interface MockAuthRequest {\n method: string;\n url: string;\n /** Lower-cased header names. */\n headers: Record<string, string>;\n /** The form body of a /token or /revoke call. */\n form: Record<string, string> | null;\n}\n\nexport interface AuthorizeDecision {\n /** The person said no. */\n deny?: boolean;\n /** Override `iss` on the answer (null = omit it): a mix-up attack. */\n iss?: string | null;\n /** Override `state` on the answer: a forged answer. */\n state?: string;\n}\n\nexport interface MockAuthServer {\n issuer: string;\n fetch: typeof fetch;\n requests: MockAuthRequest[];\n /** Hub writes seen, in order. */\n writes: Array<{ method: string; path: string; body: unknown }>;\n /** Stand-in for the consent screen: validates the request like /authorize, returns where the pod redirects. */\n authorize(authorizeUrl: string, decision?: AuthorizeDecision): string;\n /** Mark a refresh token used elsewhere: presenting it now is a REUSE (family + live key revoked). */\n consumeRefreshToken(token: string): void;\n /** The next refresh redeem loses a concurrent race: invalid_grant, nothing revoked. */\n loseNextRefreshRace(): void;\n /** Refresh-token redeems that were reuses (each revoked a family). */\n reuses: number;\n /** The person revoked the app: every token stops working. */\n revokeAll(): void;\n /** Live (unrevoked) refresh tokens. */\n liveRefreshTokens(): string[];\n reset(): void;\n}\n\ninterface CodeRow {\n clientId: string;\n redirectUri: string;\n challenge: string;\n resource: string | null;\n scope: string[];\n}\n\ninterface AccessRow {\n expiresAt: number;\n revoked: boolean;\n family: string | null;\n}\n\ninterface RefreshRow {\n clientId: string;\n family: string;\n idleUntil: number;\n used: boolean;\n revoked: boolean;\n scope: string[];\n}\n\nfunction json(status: number, body: unknown, headers: Record<string, string> = {}): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: { \"Content-Type\": \"application/json\", \"Cache-Control\": \"no-store\", ...headers },\n });\n}\n\nfunction oauthError(error: string, description: string, status = 400): Response {\n return json(status, { error, error_description: description });\n}\n\nexport function createMockAuthServer(options: MockAuthServerOptions = {}): MockAuthServer {\n const issuer = options.issuer ?? MOCK_AUTH_ISSUER;\n const now = options.now ?? Date.now;\n const refresh = options.refresh ?? true;\n const accessTtl = options.accessTtlSec ?? 3600;\n const refreshIdle = options.refreshIdleSec ?? 30 * 24 * 3600;\n const grant = options.grant ?? MOCK_GRANT;\n const user = options.user ?? MOCK_USER;\n const codes = new Map<string, CodeRow>();\n const access = new Map<string, AccessRow>();\n const refreshTokens = new Map<string, RefreshRow>();\n const requests: MockAuthRequest[] = [];\n const writes: Array<{ method: string; path: string; body: unknown }> = [];\n let proposalSeq = 0;\n\n const metadata = (): Record<string, unknown> => {\n const base: Record<string, unknown> = {\n issuer,\n authorization_endpoint: `${issuer}/authorize`,\n token_endpoint: `${issuer}/token`,\n registration_endpoint: `${issuer}/register`,\n revocation_endpoint: `${issuer}/revoke`,\n revocation_endpoint_auth_methods_supported: [\"none\"],\n response_types_supported: [\"code\"],\n grant_types_supported: refresh ? [\"authorization_code\", \"refresh_token\"] : [\"authorization_code\"],\n code_challenge_methods_supported: [\"S256\"],\n token_endpoint_auth_methods_supported: [\"none\"],\n authorization_response_iss_parameter_supported: true,\n client_id_metadata_document_supported: true,\n };\n for (const [k, v] of Object.entries(options.metadata ?? {})) {\n if (v === undefined) delete base[k];\n else base[k] = v;\n }\n return base;\n };\n\n let loseNextRace = false;\n let reuses = 0;\n const revokeFamily = (family: string) => {\n for (const r of refreshTokens.values()) if (r.family === family) r.revoked = true;\n for (const a of access.values()) if (a.family === family) a.revoked = true;\n };\n\n const tokenBody = (scope: string[], clientId: string, family: string) => {\n const accessToken = `synap_hub_mock_${randomToken()}`;\n access.set(accessToken, { expiresAt: now() + accessTtl * 1000, revoked: false, family: refresh ? family : null });\n const body: Record<string, unknown> = {\n access_token: accessToken,\n token_type: \"Bearer\",\n expires_in: accessTtl,\n scope: scope.join(\" \"),\n synap_grant: grant.filter((g) => scope.includes(g.permission)),\n synap_user: user,\n };\n if (refresh) {\n const refreshToken = randomToken();\n refreshTokens.set(refreshToken, { clientId, family, idleUntil: now() + refreshIdle * 1000, used: false, revoked: false, scope });\n body.refresh_token = refreshToken;\n if (options.advertiseRefreshExpiry) body.refresh_token_expires_in = refreshIdle;\n }\n return body;\n };\n\n async function token(form: Record<string, string>): Promise<Response> {\n if (form.grant_type === \"authorization_code\") {\n const row = form.code ? codes.get(form.code) : undefined;\n if (form.code) codes.delete(form.code); // claim first: a failed exchange burns the code\n if (!row) return oauthError(\"invalid_grant\", \"Invalid, expired, or already-redeemed authorization code\");\n if (row.clientId !== form.client_id) return oauthError(\"invalid_grant\", \"client_id does not match the code\");\n if (row.redirectUri !== form.redirect_uri) return oauthError(\"invalid_grant\", \"redirect_uri mismatch\");\n if (!form.code_verifier || (await pkceChallenge(form.code_verifier)) !== row.challenge) {\n return oauthError(\"invalid_grant\", \"PKCE verification failed\");\n }\n if (form.resource !== undefined && row.resource && form.resource !== row.resource) {\n return oauthError(\"invalid_target\", \"resource does not match the authorization request\");\n }\n // The latest connect wins: a new code ends this client's older families.\n for (const r of refreshTokens.values()) if (r.clientId === row.clientId) revokeFamily(r.family);\n return json(200, tokenBody(row.scope, row.clientId, randomToken()));\n }\n if (form.grant_type === \"refresh_token\") {\n if (!refresh) return oauthError(\"unsupported_grant_type\", 'Only grant_type \"authorization_code\" is supported');\n if (!form.refresh_token || !form.client_id) return oauthError(\"invalid_request\", \"refresh_token and client_id are required\");\n const row = refreshTokens.get(form.refresh_token);\n if (!row || row.clientId !== form.client_id || row.revoked) {\n return oauthError(\"invalid_grant\", \"Invalid, expired, revoked or already-used refresh token\");\n }\n if (row.used) {\n reuses++;\n revokeFamily(row.family);\n return oauthError(\"invalid_grant\", \"Invalid, expired, revoked or already-used refresh token\");\n }\n if (row.idleUntil <= now()) return oauthError(\"invalid_grant\", \"Invalid, expired, revoked or already-used refresh token\");\n if (loseNextRace) {\n loseNextRace = false;\n return oauthError(\"invalid_grant\", \"Invalid, expired, revoked or already-used refresh token\");\n }\n row.used = true;\n return json(200, tokenBody(row.scope, row.clientId, row.family));\n }\n return oauthError(\"unsupported_grant_type\", \"Unsupported grant_type\");\n }\n\n function hub(method: string, path: string, headers: Record<string, string>, body: unknown): Response | \"reject\" {\n const failure = options.failures?.[path];\n if (failure === \"unreachable\") return \"reject\";\n const bearer = /^Bearer (.+)$/.exec(headers.authorization ?? \"\")?.[1];\n const row = bearer ? access.get(bearer) : undefined;\n if (!row || row.revoked || row.expiresAt <= now()) return json(401, { error: \"Unauthorized\" });\n switch (failure) {\n case \"rate_limited\":\n return json(429, { error: \"Too many requests\" }, { \"Retry-After\": \"30\" });\n case \"forbidden\":\n return json(403, { error: \"Forbidden\" });\n case \"door_unsupported\":\n return json(403, { error: \"This door is not available to a space-scoped key\", code: \"GRANT_DOOR_UNSUPPORTED\" });\n case \"server\":\n return json(500, { error: \"Internal error\" });\n case \"unavailable\":\n return json(503, { error: \"Unavailable\" });\n }\n // The grant fence (spec 17 §5): aggregate reads are refused to a scoped token.\n if (path.startsWith(\"/knowledge/\") || path === \"/entities/retrieve\" || path === \"/memory/search\") {\n return json(403, { error: \"This door is not available to a space-scoped key\", code: \"GRANT_DOOR_UNSUPPORTED\" });\n }\n if (method === \"GET\" && path === \"/users/me\") return json(200, user);\n if (method === \"GET\" && path === \"/entities\") return json(200, { entities: [], total: 0 });\n if (method !== \"GET\") {\n writes.push({ method, path, body });\n if ((options.writeMode ?? \"proposed\") === \"proposed\") {\n const proposalId = `prop_mock_${++proposalSeq}`;\n return json(200, {\n status: \"proposed\",\n message: \"Sent for review\",\n proposalId,\n reviewUrl: `${issuer}/open/${proposalId}`,\n });\n }\n return json(200, { status: \"created\", id: `ent_mock_${writes.length}` });\n }\n return json(404, { error: \"Not found\" });\n }\n\n const mockFetch = async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {\n const request = typeof input === \"object\" && \"method\" in input ? input : null;\n const url = new URL(typeof input === \"string\" ? input : input instanceof URL ? input.href : input.url);\n const method = (init?.method ?? request?.method ?? \"GET\").toUpperCase();\n const headers: Record<string, string> = {};\n new Headers(init?.headers ?? request?.headers).forEach((value, name) => {\n headers[name.toLowerCase()] = value;\n });\n const rawBody = typeof init?.body === \"string\" ? init.body : request ? await request.clone().text() : \"\";\n const isForm = (headers[\"content-type\"] ?? \"\").includes(\"application/x-www-form-urlencoded\");\n const form = isForm ? Object.fromEntries(new URLSearchParams(rawBody)) : null;\n requests.push({ method, url: url.href, headers, form });\n\n if (url.origin !== new URL(issuer).origin) throw new TypeError(`Failed to fetch: ${url.origin} is not the mock pod`);\n const path = url.pathname;\n if (method === \"GET\" && path === \"/.well-known/oauth-authorization-server\") return json(200, metadata());\n if (method === \"POST\" && path === \"/token\") return token(form ?? {});\n if (method === \"POST\" && path === \"/revoke\") {\n const t = form?.token ?? \"\";\n const r = refreshTokens.get(t);\n if (r && form?.client_id && form.client_id !== r.clientId) {\n return oauthError(\"unauthorized_client\", \"The token was not issued to this client\");\n }\n if (r) revokeFamily(r.family);\n const a = access.get(t);\n if (a) a.revoked = true;\n return new Response(null, { status: 200 });\n }\n const hubPath = /^\\/api\\/hub(\\/.*)$/.exec(path)?.[1];\n if (hubPath) {\n let body: unknown = undefined;\n try {\n body = rawBody ? JSON.parse(rawBody) : undefined;\n } catch {\n body = rawBody;\n }\n const res = hub(method, hubPath, headers, body);\n if (res === \"reject\") throw new TypeError(\"Failed to fetch: mock pod unreachable\");\n return res;\n }\n return json(404, { error: \"Not found\" });\n };\n\n return {\n issuer,\n fetch: mockFetch as typeof fetch,\n requests,\n writes,\n authorize(authorizeUrl, decision = {}) {\n const url = new URL(authorizeUrl);\n if (`${url.origin}${url.pathname}` !== `${issuer}/authorize`) throw new Error(`Not this pod's /authorize: ${authorizeUrl}`);\n const q = url.searchParams;\n const redirectUri = q.get(\"redirect_uri\");\n const clientId = q.get(\"client_id\");\n // Phase 1 (shown, never redirected): no verified redirect yet.\n if (!clientId || !redirectUri) throw new Error(\"Authorization request rejected: client_id and redirect_uri are required\");\n const answer = new URL(redirectUri);\n const state = decision.state ?? q.get(\"state\");\n const finish = (fields: Record<string, string>) => {\n for (const [k, v] of Object.entries(fields)) answer.searchParams.set(k, v);\n if (state !== null) answer.searchParams.set(\"state\", state);\n const iss = decision.iss === undefined ? issuer : decision.iss;\n if (iss !== null) answer.searchParams.set(\"iss\", iss);\n return answer.href;\n };\n if (q.get(\"response_type\") !== \"code\") return finish({ error: \"unsupported_response_type\" });\n if (q.get(\"code_challenge_method\") !== \"S256\" || !q.get(\"code_challenge\")) {\n return finish({ error: \"invalid_request\", error_description: \"PKCE S256 is required\" });\n }\n const resource = q.get(\"resource\");\n if (resource !== null && resource !== `${issuer}/api/hub` && resource !== `${issuer}/mcp`) {\n return finish({ error: \"invalid_target\" });\n }\n const scope = (q.get(\"scope\") ?? \"\").split(/\\s+/).filter(Boolean);\n if (scope.length === 0) return finish({ error: \"invalid_scope\" });\n if (decision.deny) return finish({ error: \"access_denied\" });\n const code = randomToken();\n codes.set(code, { clientId, redirectUri, challenge: q.get(\"code_challenge\")!, resource, scope });\n return finish({ code });\n },\n consumeRefreshToken(t) {\n const row = refreshTokens.get(t);\n if (row) row.used = true;\n },\n loseNextRefreshRace() {\n loseNextRace = true;\n },\n get reuses() {\n return reuses;\n },\n revokeAll() {\n for (const row of access.values()) row.revoked = true;\n for (const row of refreshTokens.values()) row.revoked = true;\n },\n liveRefreshTokens() {\n return [...refreshTokens].filter(([, r]) => !r.used && !r.revoked).map(([t]) => t);\n },\n reset() {\n requests.length = 0;\n writes.length = 0;\n },\n };\n}\n"],"mappings":"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","names":["failure","json"]}
|