@synap-core/sdk 1.2.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/testing.cjs CHANGED
@@ -20,13 +20,21 @@ 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_APP: () => MOCK_APP,
24
+ MOCK_APP_AGENT_ID: () => MOCK_APP_AGENT_ID,
25
+ MOCK_AUTH_ISSUER: () => MOCK_AUTH_ISSUER,
26
+ MOCK_CLOUD_URL: () => MOCK_CLOUD_URL,
23
27
  MOCK_FAILURE_TOKENS: () => MOCK_FAILURE_TOKENS,
24
28
  MOCK_FORM: () => MOCK_FORM,
29
+ MOCK_GRANT: () => MOCK_GRANT,
25
30
  MOCK_POD_URL: () => MOCK_POD_URL,
26
31
  MOCK_RETRY_AFTER_SEC: () => MOCK_RETRY_AFTER_SEC,
27
32
  MOCK_SHARE: () => MOCK_SHARE,
28
33
  MOCK_SITE: () => MOCK_SITE,
29
34
  MOCK_TOKENS: () => MOCK_TOKENS,
35
+ MOCK_USER: () => MOCK_USER,
36
+ createMockAuthServer: () => createMockAuthServer,
37
+ createMockCloud: () => createMockCloud,
30
38
  createMockPod: () => createMockPod
31
39
  });
32
40
  module.exports = __toCommonJS(testing_exports);
@@ -126,6 +134,402 @@ var MOCK_SITE = {
126
134
  }
127
135
  };
128
136
 
137
+ // src/connect/encoding.ts
138
+ var encoder = new TextEncoder();
139
+ var decoder = new TextDecoder();
140
+ function utf8(text) {
141
+ return encoder.encode(text);
142
+ }
143
+ function base64url(bytes) {
144
+ let binary = "";
145
+ for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
146
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
147
+ }
148
+ function randomBytes(length) {
149
+ const out = new Uint8Array(length);
150
+ crypto.getRandomValues(out);
151
+ return out;
152
+ }
153
+ function randomToken() {
154
+ return base64url(randomBytes(32));
155
+ }
156
+ async function pkceChallenge(verifier) {
157
+ const digest = await crypto.subtle.digest("SHA-256", utf8(verifier));
158
+ return base64url(new Uint8Array(digest));
159
+ }
160
+
161
+ // src/testing/auth-server.ts
162
+ var MOCK_AUTH_ISSUER = "https://pod.mock-synap.test";
163
+ var MOCK_GRANT = [
164
+ { permission: "entity.post.read", workspaceId: "ws_mock_studio", workspaceName: "Studio" },
165
+ { permission: "entity.post.create", workspaceId: "ws_mock_studio", workspaceName: "Studio" }
166
+ ];
167
+ var MOCK_USER = { id: "user_mock_ada", name: "Ada Lovelace" };
168
+ var MOCK_APP = { id: "app_mock_studio", manageUrl: "https://pod-admin.mock-synap.test/apps/app_mock_studio" };
169
+ var MOCK_APP_AGENT_ID = "agent_mock_app";
170
+ function json(status, body, headers = {}) {
171
+ return new Response(JSON.stringify(body), {
172
+ status,
173
+ headers: { "Content-Type": "application/json", "Cache-Control": "no-store", ...headers }
174
+ });
175
+ }
176
+ function oauthError(error, description, status = 400) {
177
+ return json(status, { error, error_description: description });
178
+ }
179
+ function createMockAuthServer(options = {}) {
180
+ const issuer = options.issuer ?? MOCK_AUTH_ISSUER;
181
+ const now = options.now ?? Date.now;
182
+ const refresh = options.refresh ?? true;
183
+ const accessTtl = options.accessTtlSec ?? 3600;
184
+ const refreshIdle = options.refreshIdleSec ?? 30 * 24 * 3600;
185
+ const grant = options.grant ?? MOCK_GRANT;
186
+ const user = options.user ?? MOCK_USER;
187
+ const codes = /* @__PURE__ */ new Map();
188
+ const access = /* @__PURE__ */ new Map();
189
+ const refreshTokens = /* @__PURE__ */ new Map();
190
+ const requests = [];
191
+ const writes = [];
192
+ const proposals = [];
193
+ const entities = [...options.entities ?? []];
194
+ const graceMs = (options.refreshGraceSec ?? 30) * 1e3;
195
+ const app = options.app === void 0 ? MOCK_APP : options.app;
196
+ let proposalSeq = 0;
197
+ let graceAnswers = 0;
198
+ const metadata = () => {
199
+ const base = {
200
+ issuer,
201
+ authorization_endpoint: `${issuer}/authorize`,
202
+ token_endpoint: `${issuer}/token`,
203
+ registration_endpoint: `${issuer}/register`,
204
+ revocation_endpoint: `${issuer}/revoke`,
205
+ revocation_endpoint_auth_methods_supported: ["none"],
206
+ response_types_supported: ["code"],
207
+ grant_types_supported: refresh ? ["authorization_code", "refresh_token"] : ["authorization_code"],
208
+ code_challenge_methods_supported: ["S256"],
209
+ token_endpoint_auth_methods_supported: ["none"],
210
+ authorization_response_iss_parameter_supported: true,
211
+ client_id_metadata_document_supported: true
212
+ };
213
+ for (const [k, v] of Object.entries(options.metadata ?? {})) {
214
+ if (v === void 0) delete base[k];
215
+ else base[k] = v;
216
+ }
217
+ return base;
218
+ };
219
+ let loseNextRace = false;
220
+ let reuses = 0;
221
+ const revokeFamily = (family) => {
222
+ for (const r of refreshTokens.values()) if (r.family === family) r.revoked = true;
223
+ for (const a of access.values()) if (a.family === family) a.revoked = true;
224
+ };
225
+ const tokenBody = (scope, clientId, family, pair) => {
226
+ const accessToken = pair?.accessToken ?? `synap_hub_mock_${randomToken()}`;
227
+ if (!pair) access.set(accessToken, { expiresAt: now() + accessTtl * 1e3, revoked: false, family: refresh ? family : null, scope });
228
+ const row = access.get(accessToken);
229
+ const body = {
230
+ access_token: accessToken,
231
+ token_type: "Bearer",
232
+ // As the pod: what the key has left, never an assumed hour.
233
+ expires_in: Math.max(0, Math.floor((row.expiresAt - now()) / 1e3)),
234
+ scope: scope.join(" "),
235
+ synap_grant: grant.filter((g) => scope.includes(g.permission)),
236
+ synap_user: user,
237
+ ...app ? { synap_app: app } : {}
238
+ };
239
+ if (pair) {
240
+ body.refresh_token = pair.refreshToken;
241
+ if (options.advertiseRefreshExpiry) body.refresh_token_expires_in = Math.floor((refreshTokens.get(pair.refreshToken).idleUntil - now()) / 1e3);
242
+ } else if (refresh) {
243
+ const refreshToken = randomToken();
244
+ refreshTokens.set(refreshToken, { clientId, family, idleUntil: now() + refreshIdle * 1e3, used: false, revoked: false, scope });
245
+ body.refresh_token = refreshToken;
246
+ if (options.advertiseRefreshExpiry) body.refresh_token_expires_in = refreshIdle;
247
+ }
248
+ return body;
249
+ };
250
+ async function token(form) {
251
+ if (form.grant_type === "authorization_code") {
252
+ const row = form.code ? codes.get(form.code) : void 0;
253
+ if (form.code) codes.delete(form.code);
254
+ if (!row) return oauthError("invalid_grant", "Invalid, expired, or already-redeemed authorization code");
255
+ if (row.clientId !== form.client_id) return oauthError("invalid_grant", "client_id does not match the code");
256
+ if (row.redirectUri !== form.redirect_uri) return oauthError("invalid_grant", "redirect_uri mismatch");
257
+ if (!form.code_verifier || await pkceChallenge(form.code_verifier) !== row.challenge) {
258
+ return oauthError("invalid_grant", "PKCE verification failed");
259
+ }
260
+ if (form.resource !== void 0 && row.resource && form.resource !== row.resource) {
261
+ return oauthError("invalid_target", "resource does not match the authorization request");
262
+ }
263
+ for (const r of refreshTokens.values()) if (r.clientId === row.clientId) revokeFamily(r.family);
264
+ return json(200, tokenBody(row.scope, row.clientId, randomToken()));
265
+ }
266
+ if (form.grant_type === "refresh_token") {
267
+ if (!refresh) return oauthError("unsupported_grant_type", 'Only grant_type "authorization_code" is supported');
268
+ if (!form.refresh_token || !form.client_id) return oauthError("invalid_request", "refresh_token and client_id are required");
269
+ const row = refreshTokens.get(form.refresh_token);
270
+ if (!row || row.clientId !== form.client_id || row.revoked) {
271
+ return oauthError("invalid_grant", "Invalid, expired, revoked or already-used refresh token");
272
+ }
273
+ if (row.used) {
274
+ const successor = row.successor ? refreshTokens.get(row.successor) : void 0;
275
+ const key = row.pairAccess ? access.get(row.pairAccess) : void 0;
276
+ const inGrace = graceMs > 0 && row.usedAt !== void 0 && now() - row.usedAt < graceMs;
277
+ if (inGrace && successor && !successor.used && !successor.revoked && key && !key.revoked && key.expiresAt > now()) {
278
+ graceAnswers++;
279
+ return json(200, tokenBody(row.scope, row.clientId, row.family, { accessToken: row.pairAccess, refreshToken: row.successor }));
280
+ }
281
+ reuses++;
282
+ revokeFamily(row.family);
283
+ return oauthError("invalid_grant", "Invalid, expired, revoked or already-used refresh token");
284
+ }
285
+ if (row.idleUntil <= now()) return oauthError("invalid_grant", "Invalid, expired, revoked or already-used refresh token");
286
+ if (loseNextRace) {
287
+ loseNextRace = false;
288
+ return oauthError("invalid_grant", "Invalid, expired, revoked or already-used refresh token");
289
+ }
290
+ row.used = true;
291
+ row.usedAt = now();
292
+ for (const a of access.values()) if (a.family === row.family) a.revoked = true;
293
+ const body = tokenBody(row.scope, row.clientId, row.family);
294
+ row.successor = body.refresh_token;
295
+ row.pairAccess = body.access_token;
296
+ return json(200, body);
297
+ }
298
+ return oauthError("unsupported_grant_type", "Unsupported grant_type");
299
+ }
300
+ function hub(method, path, query, headers, body) {
301
+ const failure2 = options.failures?.[path];
302
+ if (failure2 === "unreachable") return "reject";
303
+ const bearer = /^Bearer (.+)$/.exec(headers.authorization ?? "")?.[1];
304
+ const row = bearer ? access.get(bearer) : void 0;
305
+ if (!row || row.revoked || row.expiresAt <= now()) return json(401, { error: "Unauthorized" });
306
+ switch (failure2) {
307
+ case "rate_limited":
308
+ return json(429, { error: "Too many requests" }, { "Retry-After": "30" });
309
+ case "forbidden":
310
+ return json(403, { error: "Forbidden" });
311
+ case "door_unsupported":
312
+ return json(403, { error: "This door is not available to a space-scoped key", code: "GRANT_DOOR_UNSUPPORTED" });
313
+ case "server":
314
+ return json(500, { error: "Internal error" });
315
+ case "unavailable":
316
+ return json(503, { error: "Unavailable" });
317
+ }
318
+ if (path.startsWith("/knowledge/") || path === "/entities/retrieve" || path === "/memory/search") {
319
+ return json(403, { error: "This door is not available to a space-scoped key", code: "GRANT_DOOR_UNSUPPORTED" });
320
+ }
321
+ if (method === "GET" && path === "/users/me") return json(200, user);
322
+ if (method === "GET" && path === "/entities") {
323
+ const ws = query.get("workspaceId");
324
+ const kind = query.get("profileSlug");
325
+ const offset = Number(query.get("offset") ?? 0) || 0;
326
+ const limit = Number(query.get("limit") ?? 50) || 50;
327
+ const rows = entities.filter((e) => (!ws || e.workspaceId === ws) && (!kind || e.profileSlug === kind));
328
+ return json(200, rows.slice(offset, offset + limit));
329
+ }
330
+ if (method === "GET" && path === "/proposals") {
331
+ const readable = new Set(grant.filter((g) => g.permission === "proposal.read" && row.scope.includes(g.permission)).map((g) => g.workspaceId));
332
+ const status = query.get("status") ?? "pending";
333
+ const ws = query.get("workspaceId");
334
+ const limit = Math.min(Math.max(Number(query.get("limit") ?? 50) || 50, 1), 200);
335
+ const offset = Number(query.get("offset") ?? 0) || 0;
336
+ const rows = proposals.filter((p) => p.workspaceId !== null && readable.has(p.workspaceId)).filter((p) => (status === "all" || p.status === status) && (!ws || p.workspaceId === ws)).reverse();
337
+ const page = rows.slice(offset, offset + limit);
338
+ return json(200, { proposals: page, limit, offset, total: rows.length, hasMore: offset + page.length < rows.length });
339
+ }
340
+ if (method !== "GET") {
341
+ writes.push({ method, path, body });
342
+ const fields = body && typeof body === "object" ? body : {};
343
+ const workspaceId = typeof fields.workspaceId === "string" ? fields.workspaceId : null;
344
+ if ((options.writeMode ?? "proposed") === "proposed") {
345
+ const proposalId = `prop_mock_${++proposalSeq}`;
346
+ proposals.push({
347
+ id: proposalId,
348
+ proposalType: method === "POST" ? "create" : method === "DELETE" ? "delete" : "update",
349
+ targetType: path.split("/")[1]?.replace(/s$/, "") ?? "entity",
350
+ targetId: null,
351
+ status: "pending",
352
+ workspaceId,
353
+ createdAt: new Date(now()).toISOString(),
354
+ correlationId: null,
355
+ sessionId: null,
356
+ agentUserId: MOCK_APP_AGENT_ID,
357
+ ...typeof fields.title === "string" ? { summary: `Create "${fields.title}"` } : {}
358
+ });
359
+ return json(200, {
360
+ status: "proposed",
361
+ message: "Sent for review",
362
+ proposalId,
363
+ reviewUrl: `${issuer}/open/${proposalId}`
364
+ });
365
+ }
366
+ const id = `ent_mock_${writes.length}`;
367
+ if (method === "POST" && path === "/entities") entities.push({ id, ...fields });
368
+ return json(200, { status: "created", id });
369
+ }
370
+ return json(404, { error: "Not found" });
371
+ }
372
+ const mockFetch = async (input, init) => {
373
+ const request = typeof input === "object" && "method" in input ? input : null;
374
+ const url = new URL(typeof input === "string" ? input : input instanceof URL ? input.href : input.url);
375
+ const method = (init?.method ?? request?.method ?? "GET").toUpperCase();
376
+ const headers = {};
377
+ new Headers(init?.headers ?? request?.headers).forEach((value, name) => {
378
+ headers[name.toLowerCase()] = value;
379
+ });
380
+ const rawBody = typeof init?.body === "string" ? init.body : request ? await request.clone().text() : "";
381
+ const isForm = (headers["content-type"] ?? "").includes("application/x-www-form-urlencoded");
382
+ const form = isForm ? Object.fromEntries(new URLSearchParams(rawBody)) : null;
383
+ requests.push({ method, url: url.href, headers, form });
384
+ if (url.origin !== new URL(issuer).origin) throw new TypeError(`Failed to fetch: ${url.origin} is not the mock pod`);
385
+ const path = url.pathname;
386
+ if (method === "GET" && path === "/.well-known/oauth-authorization-server") return json(200, metadata());
387
+ if (method === "POST" && path === "/token") return token(form ?? {});
388
+ if (method === "POST" && path === "/revoke") {
389
+ const t = form?.token ?? "";
390
+ const r = refreshTokens.get(t);
391
+ if (r && form?.client_id && form.client_id !== r.clientId) {
392
+ return oauthError("unauthorized_client", "The token was not issued to this client");
393
+ }
394
+ if (r) revokeFamily(r.family);
395
+ const a = access.get(t);
396
+ if (a) a.revoked = true;
397
+ return new Response(null, { status: 200 });
398
+ }
399
+ const hubPath = /^\/api\/hub(\/.*)$/.exec(path)?.[1];
400
+ if (hubPath) {
401
+ let body = void 0;
402
+ try {
403
+ body = rawBody ? JSON.parse(rawBody) : void 0;
404
+ } catch {
405
+ body = rawBody;
406
+ }
407
+ const res = hub(method, hubPath, url.searchParams, headers, body);
408
+ if (res === "reject") throw new TypeError("Failed to fetch: mock pod unreachable");
409
+ return res;
410
+ }
411
+ return json(404, { error: "Not found" });
412
+ };
413
+ return {
414
+ issuer,
415
+ fetch: mockFetch,
416
+ requests,
417
+ writes,
418
+ proposals,
419
+ settleProposal(id, status) {
420
+ const p = proposals.find((x) => x.id === id);
421
+ if (p) p.status = status;
422
+ },
423
+ async redeemElsewhere(t) {
424
+ const row = refreshTokens.get(t);
425
+ const res = await token({ grant_type: "refresh_token", refresh_token: t, client_id: row?.clientId ?? "" });
426
+ if (res.status !== 200) throw new Error(`redeemElsewhere: the pod answered ${res.status}`);
427
+ return await res.json();
428
+ },
429
+ get graceAnswers() {
430
+ return graceAnswers;
431
+ },
432
+ authorize(authorizeUrl, decision = {}) {
433
+ const url = new URL(authorizeUrl);
434
+ if (`${url.origin}${url.pathname}` !== `${issuer}/authorize`) throw new Error(`Not this pod's /authorize: ${authorizeUrl}`);
435
+ const q = url.searchParams;
436
+ const redirectUri = q.get("redirect_uri");
437
+ const clientId = q.get("client_id");
438
+ if (!clientId || !redirectUri) throw new Error("Authorization request rejected: client_id and redirect_uri are required");
439
+ const answer = new URL(redirectUri);
440
+ const state = decision.state ?? q.get("state");
441
+ const finish = (fields) => {
442
+ for (const [k, v] of Object.entries(fields)) answer.searchParams.set(k, v);
443
+ if (state !== null) answer.searchParams.set("state", state);
444
+ const iss = decision.iss === void 0 ? issuer : decision.iss;
445
+ if (iss !== null) answer.searchParams.set("iss", iss);
446
+ return answer.href;
447
+ };
448
+ if (q.get("response_type") !== "code") return finish({ error: "unsupported_response_type" });
449
+ if (q.get("code_challenge_method") !== "S256" || !q.get("code_challenge")) {
450
+ return finish({ error: "invalid_request", error_description: "PKCE S256 is required" });
451
+ }
452
+ const resource = q.get("resource");
453
+ if (resource !== null && resource !== `${issuer}/api/hub` && resource !== `${issuer}/mcp`) {
454
+ return finish({ error: "invalid_target" });
455
+ }
456
+ const scope = (q.get("scope") ?? "").split(/\s+/).filter(Boolean);
457
+ if (scope.length === 0) return finish({ error: "invalid_scope" });
458
+ if (decision.deny) return finish({ error: "access_denied" });
459
+ const code = randomToken();
460
+ codes.set(code, { clientId, redirectUri, challenge: q.get("code_challenge"), resource, scope });
461
+ return finish({ code });
462
+ },
463
+ consumeRefreshToken(t) {
464
+ const row = refreshTokens.get(t);
465
+ if (row) row.used = true;
466
+ },
467
+ loseNextRefreshRace() {
468
+ loseNextRace = true;
469
+ },
470
+ get reuses() {
471
+ return reuses;
472
+ },
473
+ revokeAll() {
474
+ for (const row of access.values()) row.revoked = true;
475
+ for (const row of refreshTokens.values()) row.revoked = true;
476
+ },
477
+ liveRefreshTokens() {
478
+ return [...refreshTokens].filter(([, r]) => !r.used && !r.revoked).map(([t]) => t);
479
+ },
480
+ reset() {
481
+ requests.length = 0;
482
+ writes.length = 0;
483
+ }
484
+ };
485
+ }
486
+
487
+ // src/testing/cloud.ts
488
+ var MOCK_CLOUD_URL = "https://cp.mock-synap.test";
489
+ var STATE_RE = /^[A-Za-z0-9._~-]{16,256}$/;
490
+ var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
491
+ function createMockCloud(options = {}) {
492
+ const base = (options.url ?? MOCK_CLOUD_URL).replace(/\/+$/, "");
493
+ const requests = [];
494
+ return {
495
+ url: base,
496
+ requests,
497
+ choose(location, { pod, metadata }) {
498
+ const back = check(location, metadata);
499
+ back.searchParams.set("iss", pod);
500
+ back.searchParams.set("state", new URL(location).searchParams.get("state"));
501
+ return back.href;
502
+ },
503
+ cancel(location, { metadata }) {
504
+ const back = check(location, metadata);
505
+ back.searchParams.set("state", new URL(location).searchParams.get("state"));
506
+ back.searchParams.set("error", "access_denied");
507
+ return back.href;
508
+ }
509
+ };
510
+ function check(location, metadata) {
511
+ requests.push(location);
512
+ const url = new URL(location);
513
+ if (`${url.origin}${url.pathname}` !== `${base}/connect/pod`) throw new Error(`Not this CP's /connect/pod: ${location}`);
514
+ const clientId = url.searchParams.get("client_id");
515
+ const state = url.searchParams.get("state");
516
+ if (!clientId || !cimdUrlAccepted(clientId)) throw new Error("Chooser refused: client_id must be an https CIMD URL");
517
+ if (!state || !STATE_RE.test(state)) throw new Error("Chooser refused: state is missing or malformed");
518
+ if (clientId !== metadata.client_id) throw new Error("Chooser refused: the client metadata is not this client_id's");
519
+ return new URL(metadata.initiate_login_uri);
520
+ }
521
+ function cimdUrlAccepted(clientId) {
522
+ let url;
523
+ try {
524
+ url = new URL(clientId);
525
+ } catch {
526
+ return false;
527
+ }
528
+ if (url.protocol === "https:") return true;
529
+ return url.protocol === "http:" && options.allowInsecureLoopback === true && LOOPBACK_HOSTS.has(url.hostname);
530
+ }
531
+ }
532
+
129
533
  // src/testing/index.ts
130
534
  var MOCK_POD_URL = "https://mock.synap.invalid";
131
535
  var MOCK_TOKENS = { form: "demo-form", share: "demo", site: "demo-site" };
@@ -160,7 +564,7 @@ var MAX_OFFSET = 1e4;
160
564
  var MAX_LIMIT = 100;
161
565
  var MAX_SORT_KEYS = 3;
162
566
  var PUBLISHED_AT = "publishedAt";
163
- function json(status, body, headers = {}) {
567
+ function json2(status, body, headers = {}) {
164
568
  return new Response(JSON.stringify(body), {
165
569
  status,
166
570
  headers: { "Content-Type": "application/json", "Cache-Control": "no-cache", ...headers }
@@ -183,15 +587,15 @@ function etagMatches(header, etag) {
183
587
  function failure(token) {
184
588
  switch (token) {
185
589
  case MOCK_FAILURE_TOKENS.rateLimited:
186
- return json(429, { error: "Too many requests", retryAfter: "5 minutes" });
590
+ return json2(429, { error: "Too many requests", retryAfter: "5 minutes" });
187
591
  case MOCK_FAILURE_TOKENS.rateLimitedRetryAfter:
188
- return json(429, { error: "Too many requests" }, { "Retry-After": String(MOCK_RETRY_AFTER_SEC) });
592
+ return json2(429, { error: "Too many requests" }, { "Retry-After": String(MOCK_RETRY_AFTER_SEC) });
189
593
  case MOCK_FAILURE_TOKENS.doorsDisabled:
190
- return json(403, DOORS_DISABLED);
594
+ return json2(403, DOORS_DISABLED);
191
595
  case MOCK_FAILURE_TOKENS.serverError:
192
- return json(500, { error: "Internal error" });
596
+ return json2(500, { error: "Internal error" });
193
597
  case MOCK_FAILURE_TOKENS.unavailable:
194
- return json(503, { received: false });
598
+ return json2(503, { received: false });
195
599
  case MOCK_FAILURE_TOKENS.unreachable:
196
600
  return "reject";
197
601
  default:
@@ -266,17 +670,17 @@ function cached(view, ifNoneMatch) {
266
670
  if (etagMatches(ifNoneMatch, etag)) {
267
671
  return new Response(null, { status: 304, headers: { ETag: etag, "Cache-Control": "no-cache" } });
268
672
  }
269
- return json(200, view, { ETag: etag });
673
+ return json2(200, view, { ETag: etag });
270
674
  }
271
675
  function siteDoor(site, rest, params, ifNoneMatch) {
272
676
  if (rest.length === 0) return cached(manifest(site), ifNoneMatch);
273
677
  if (rest[0] !== "collections" || rest.length > 3 || !Object.hasOwn(site.collections, rest[1])) {
274
- return json(404, NOT_FOUND);
678
+ return json2(404, NOT_FOUND);
275
679
  }
276
680
  const c = site.collections[rest[1]];
277
681
  if (rest.length === 2) {
278
682
  const q = parseQuery(params, c);
279
- if (typeof q === "string") return json(400, { error: q });
683
+ if (typeof q === "string") return json2(400, { error: q });
280
684
  const sorted = [...c.records].sort(compareRecords(q.sort));
281
685
  const page = {
282
686
  items: sorted.slice(q.offset, q.offset + q.limit).map(summary),
@@ -288,10 +692,10 @@ function siteDoor(site, rest, params, ifNoneMatch) {
288
692
  }
289
693
  const address = rest[2];
290
694
  const byPublicId = PUBLIC_ID_RE.test(address);
291
- if (!byPublicId && !(address.length <= SLUG_MAX_LENGTH && SLUG_RE.test(address))) return json(404, NOT_FOUND);
695
+ if (!byPublicId && !(address.length <= SLUG_MAX_LENGTH && SLUG_RE.test(address))) return json2(404, NOT_FOUND);
292
696
  const record = c.records.find((r) => byPublicId ? r.publicId === address : r.slug === address);
293
- if (!record) return json(404, NOT_FOUND);
294
- if (params.has("embed")) return json(400, { error: "embed is not available" });
697
+ if (!record) return json2(404, NOT_FOUND);
698
+ if (params.has("embed")) return json2(400, { error: "embed is not available" });
295
699
  return cached(record, ifNoneMatch);
296
700
  }
297
701
  function invalidKeys(form, body) {
@@ -328,12 +732,12 @@ function createMockPod(options = {}) {
328
732
  if (preflighted.length > 0) throw new TypeError(`Failed to fetch: preflight refuses ${preflighted.join(", ")}`);
329
733
  }
330
734
  const m = url.pathname.match(/^\/api\/hub\/public\/(forms|shares|sites)\/(.+)$/);
331
- if (!m) return json(404, NOT_FOUND);
735
+ if (!m) return json2(404, NOT_FOUND);
332
736
  let segments;
333
737
  try {
334
738
  segments = m[2].split("/").map(decodeURIComponent);
335
739
  } catch {
336
- return json(404, NOT_FOUND);
740
+ return json2(404, NOT_FOUND);
337
741
  }
338
742
  const [token, ...rest] = segments;
339
743
  const failed = failure(token);
@@ -351,15 +755,15 @@ function createMockPod(options = {}) {
351
755
  const invalid = form ? invalidKeys(form, body) : [];
352
756
  const status = invalid.length > 0 ? 422 : 202;
353
757
  submissions.push({ token, body, status });
354
- return status === 422 ? json(422, { received: false, invalid }) : json(202, { received: true });
758
+ return status === 422 ? json2(422, { received: false, invalid }) : json2(202, { received: true });
355
759
  }
356
- if (method !== "GET") return json(404, NOT_FOUND);
357
- if (door === "forms" && rest.length === 0 && Object.hasOwn(forms, token)) return json(200, forms[token]);
358
- if (door === "shares" && rest.length === 0 && Object.hasOwn(shares, token)) return json(200, shares[token]);
760
+ if (method !== "GET") return json2(404, NOT_FOUND);
761
+ if (door === "forms" && rest.length === 0 && Object.hasOwn(forms, token)) return json2(200, forms[token]);
762
+ if (door === "shares" && rest.length === 0 && Object.hasOwn(shares, token)) return json2(200, shares[token]);
359
763
  if (door === "sites" && Object.hasOwn(sites, token)) {
360
764
  return siteDoor(sites[token], rest, url.searchParams, headers["if-none-match"] ?? null);
361
765
  }
362
- return json(404, NOT_FOUND);
766
+ return json2(404, NOT_FOUND);
363
767
  };
364
768
  return {
365
769
  podUrl: MOCK_POD_URL,
@@ -374,13 +778,21 @@ function createMockPod(options = {}) {
374
778
  }
375
779
  // Annotate the CommonJS export names for ESM import in node:
376
780
  0 && (module.exports = {
781
+ MOCK_APP,
782
+ MOCK_APP_AGENT_ID,
783
+ MOCK_AUTH_ISSUER,
784
+ MOCK_CLOUD_URL,
377
785
  MOCK_FAILURE_TOKENS,
378
786
  MOCK_FORM,
787
+ MOCK_GRANT,
379
788
  MOCK_POD_URL,
380
789
  MOCK_RETRY_AFTER_SEC,
381
790
  MOCK_SHARE,
382
791
  MOCK_SITE,
383
792
  MOCK_TOKENS,
793
+ MOCK_USER,
794
+ createMockAuthServer,
795
+ createMockCloud,
384
796
  createMockPod
385
797
  });
386
798
  //# sourceMappingURL=testing.cjs.map