@synap-core/sdk 1.1.0 → 1.2.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.
@@ -0,0 +1,110 @@
1
+ import { P as PublicForm, g as PublicShare, e as PublicRecord } from './client-C5sF20f1.cjs';
2
+
3
+ /**
4
+ * Demo data for the mock pod. Shapes are the `@synap-core/sdk/public` wire
5
+ * types, so a fixture that drifts from the contract fails to compile.
6
+ */
7
+
8
+ declare const MOCK_FORM: PublicForm;
9
+ declare const MOCK_SHARE: PublicShare;
10
+ /** One collection of a mock site: its door config and its live records. */
11
+ interface MockCollection {
12
+ /** Default sort keys (`-` = descending); also the sort keys the door accepts. */
13
+ sort: string[];
14
+ pageSize: number;
15
+ addressing: "slug" | "publicId";
16
+ records: PublicRecord[];
17
+ }
18
+ /** A mock published site. The manifest is derived from it (counts included). */
19
+ interface MockSite {
20
+ title?: string;
21
+ description?: string;
22
+ publishedOn: string;
23
+ collections: Record<string, MockCollection>;
24
+ }
25
+ /**
26
+ * The demo site: `paper` (12 records, 5 per page, by slug: pagination),
27
+ * `person` (by public id), `event` (nothing published: the empty state).
28
+ */
29
+ declare const MOCK_SITE: MockSite;
30
+
31
+ /**
32
+ * @synap-core/sdk/testing — a mock pod: a fake `fetch` that answers the pod's
33
+ * public doors (forms, shares, sites) from fixtures, with no network. Inject
34
+ * it into the REAL client (`createPublicClient({ podUrl, fetch: pod.fetch })`)
35
+ * so everything above the wire — status handling, typed errors, the models —
36
+ * is the production path. For unit tests, a site's mock mode and a kit's
37
+ * states gallery.
38
+ *
39
+ * Wire behaviour mirrors the backend doors: the uniform 404 for every miss,
40
+ * the constant 202 on a submit, the site doors' query bounds (400), weak
41
+ * ETags with If-None-Match → 304. Like a browser against the real doors, a
42
+ * `credentials: "include"` request is refused (it rejects, as a CORS failure
43
+ * does); with `{ browser: true }` a non-safelisted request header is too.
44
+ *
45
+ * tokens MOCK_TOKENS.form / .share / .site the demo form, share and site
46
+ * tokens MOCK_FAILURE_TOKENS.* one failure on every door
47
+ * anything else the uniform 404
48
+ */
49
+
50
+ /** A placeholder origin; never contacted. */
51
+ declare const MOCK_POD_URL = "https://mock.synap.invalid";
52
+ declare const MOCK_TOKENS: {
53
+ readonly form: "demo-form";
54
+ readonly share: "demo";
55
+ readonly site: "demo-site";
56
+ };
57
+ /** The `Retry-After` the `rateLimitedRetryAfter` token sends. */
58
+ declare const MOCK_RETRY_AFTER_SEC = 30;
59
+ /** Tokens that make every door answer one failure, so each UI state can be rendered. */
60
+ declare const MOCK_FAILURE_TOKENS: {
61
+ /** 429 with no `Retry-After` (what the pod's public doors send today). */
62
+ readonly rateLimited: "mock-rate-limited";
63
+ /** 429 with `Retry-After: 30`. */
64
+ readonly rateLimitedRetryAfter: "mock-rate-limited-retry-after";
65
+ /** 403 `public_doors_disabled`: the owner switched public pages off. */
66
+ readonly doorsDisabled: "mock-doors-disabled";
67
+ /** 500. */
68
+ readonly serverError: "mock-server-error";
69
+ /** 503 (a read: `pod_unreachable` / `server`; a submit: `unavailable`). */
70
+ readonly unavailable: "mock-unavailable";
71
+ /** No answer: the fetch rejects (`network` / `pod_unreachable`). */
72
+ readonly unreachable: "mock-unreachable";
73
+ };
74
+ interface MockPodOptions {
75
+ /** Forms by token (default: the demo form under `MOCK_TOKENS.form`). */
76
+ forms?: Record<string, PublicForm>;
77
+ /** Shares by token (default: the demo share under `MOCK_TOKENS.share`). */
78
+ shares?: Record<string, PublicShare>;
79
+ /** Sites by token (default: the demo site under `MOCK_TOKENS.site`). */
80
+ sites?: Record<string, MockSite>;
81
+ /** Refuse non-safelisted request headers, as a browser's CORS preflight would. */
82
+ browser?: boolean;
83
+ }
84
+ interface MockRequest {
85
+ method: string;
86
+ url: string;
87
+ /** Lower-cased header names. */
88
+ headers: Record<string, string>;
89
+ credentials: RequestCredentials | undefined;
90
+ }
91
+ interface MockSubmission {
92
+ token: string;
93
+ /** The parsed envelope (`fields`, `ticket`, `hp`, …). */
94
+ body: unknown;
95
+ /** The answer the mock gave. */
96
+ status: 202 | 422;
97
+ }
98
+ interface MockPod {
99
+ podUrl: string;
100
+ fetch: typeof fetch;
101
+ /** Every request seen, in order (assert credentials and headers on it). */
102
+ requests: MockRequest[];
103
+ /** Every submit seen, in order. */
104
+ submissions: MockSubmission[];
105
+ /** Forget `requests` and `submissions`. */
106
+ reset(): void;
107
+ }
108
+ declare function createMockPod(options?: MockPodOptions): MockPod;
109
+
110
+ export { MOCK_FAILURE_TOKENS, MOCK_FORM, MOCK_POD_URL, MOCK_RETRY_AFTER_SEC, MOCK_SHARE, MOCK_SITE, MOCK_TOKENS, type MockCollection, type MockPod, type MockPodOptions, type MockRequest, type MockSite, type MockSubmission, createMockPod };
@@ -0,0 +1,110 @@
1
+ import { P as PublicForm, g as PublicShare, e as PublicRecord } from './client-C5sF20f1.js';
2
+
3
+ /**
4
+ * Demo data for the mock pod. Shapes are the `@synap-core/sdk/public` wire
5
+ * types, so a fixture that drifts from the contract fails to compile.
6
+ */
7
+
8
+ declare const MOCK_FORM: PublicForm;
9
+ declare const MOCK_SHARE: PublicShare;
10
+ /** One collection of a mock site: its door config and its live records. */
11
+ interface MockCollection {
12
+ /** Default sort keys (`-` = descending); also the sort keys the door accepts. */
13
+ sort: string[];
14
+ pageSize: number;
15
+ addressing: "slug" | "publicId";
16
+ records: PublicRecord[];
17
+ }
18
+ /** A mock published site. The manifest is derived from it (counts included). */
19
+ interface MockSite {
20
+ title?: string;
21
+ description?: string;
22
+ publishedOn: string;
23
+ collections: Record<string, MockCollection>;
24
+ }
25
+ /**
26
+ * The demo site: `paper` (12 records, 5 per page, by slug: pagination),
27
+ * `person` (by public id), `event` (nothing published: the empty state).
28
+ */
29
+ declare const MOCK_SITE: MockSite;
30
+
31
+ /**
32
+ * @synap-core/sdk/testing — a mock pod: a fake `fetch` that answers the pod's
33
+ * public doors (forms, shares, sites) from fixtures, with no network. Inject
34
+ * it into the REAL client (`createPublicClient({ podUrl, fetch: pod.fetch })`)
35
+ * so everything above the wire — status handling, typed errors, the models —
36
+ * is the production path. For unit tests, a site's mock mode and a kit's
37
+ * states gallery.
38
+ *
39
+ * Wire behaviour mirrors the backend doors: the uniform 404 for every miss,
40
+ * the constant 202 on a submit, the site doors' query bounds (400), weak
41
+ * ETags with If-None-Match → 304. Like a browser against the real doors, a
42
+ * `credentials: "include"` request is refused (it rejects, as a CORS failure
43
+ * does); with `{ browser: true }` a non-safelisted request header is too.
44
+ *
45
+ * tokens MOCK_TOKENS.form / .share / .site the demo form, share and site
46
+ * tokens MOCK_FAILURE_TOKENS.* one failure on every door
47
+ * anything else the uniform 404
48
+ */
49
+
50
+ /** A placeholder origin; never contacted. */
51
+ declare const MOCK_POD_URL = "https://mock.synap.invalid";
52
+ declare const MOCK_TOKENS: {
53
+ readonly form: "demo-form";
54
+ readonly share: "demo";
55
+ readonly site: "demo-site";
56
+ };
57
+ /** The `Retry-After` the `rateLimitedRetryAfter` token sends. */
58
+ declare const MOCK_RETRY_AFTER_SEC = 30;
59
+ /** Tokens that make every door answer one failure, so each UI state can be rendered. */
60
+ declare const MOCK_FAILURE_TOKENS: {
61
+ /** 429 with no `Retry-After` (what the pod's public doors send today). */
62
+ readonly rateLimited: "mock-rate-limited";
63
+ /** 429 with `Retry-After: 30`. */
64
+ readonly rateLimitedRetryAfter: "mock-rate-limited-retry-after";
65
+ /** 403 `public_doors_disabled`: the owner switched public pages off. */
66
+ readonly doorsDisabled: "mock-doors-disabled";
67
+ /** 500. */
68
+ readonly serverError: "mock-server-error";
69
+ /** 503 (a read: `pod_unreachable` / `server`; a submit: `unavailable`). */
70
+ readonly unavailable: "mock-unavailable";
71
+ /** No answer: the fetch rejects (`network` / `pod_unreachable`). */
72
+ readonly unreachable: "mock-unreachable";
73
+ };
74
+ interface MockPodOptions {
75
+ /** Forms by token (default: the demo form under `MOCK_TOKENS.form`). */
76
+ forms?: Record<string, PublicForm>;
77
+ /** Shares by token (default: the demo share under `MOCK_TOKENS.share`). */
78
+ shares?: Record<string, PublicShare>;
79
+ /** Sites by token (default: the demo site under `MOCK_TOKENS.site`). */
80
+ sites?: Record<string, MockSite>;
81
+ /** Refuse non-safelisted request headers, as a browser's CORS preflight would. */
82
+ browser?: boolean;
83
+ }
84
+ interface MockRequest {
85
+ method: string;
86
+ url: string;
87
+ /** Lower-cased header names. */
88
+ headers: Record<string, string>;
89
+ credentials: RequestCredentials | undefined;
90
+ }
91
+ interface MockSubmission {
92
+ token: string;
93
+ /** The parsed envelope (`fields`, `ticket`, `hp`, …). */
94
+ body: unknown;
95
+ /** The answer the mock gave. */
96
+ status: 202 | 422;
97
+ }
98
+ interface MockPod {
99
+ podUrl: string;
100
+ fetch: typeof fetch;
101
+ /** Every request seen, in order (assert credentials and headers on it). */
102
+ requests: MockRequest[];
103
+ /** Every submit seen, in order. */
104
+ submissions: MockSubmission[];
105
+ /** Forget `requests` and `submissions`. */
106
+ reset(): void;
107
+ }
108
+ declare function createMockPod(options?: MockPodOptions): MockPod;
109
+
110
+ export { MOCK_FAILURE_TOKENS, MOCK_FORM, MOCK_POD_URL, MOCK_RETRY_AFTER_SEC, MOCK_SHARE, MOCK_SITE, MOCK_TOKENS, type MockCollection, type MockPod, type MockPodOptions, type MockRequest, type MockSite, type MockSubmission, createMockPod };
@@ -0,0 +1,352 @@
1
+ // src/testing/fixtures.ts
2
+ var MOCK_FORM = {
3
+ title: "Get in touch",
4
+ fields: [
5
+ { key: "name", label: "Name", type: "text", required: true, constraints: { maxLength: 120 } },
6
+ { key: "email", label: "Email", type: "email", required: true },
7
+ {
8
+ key: "topic",
9
+ label: "Topic",
10
+ type: "enum",
11
+ constraints: { enum: ["Sales", "Support", "Partnership"] }
12
+ },
13
+ { key: "message", label: "Message", type: "richtext", help: "A few lines is plenty." }
14
+ ],
15
+ successMessage: "Thanks \u2014 we received your message.",
16
+ honeypotField: "hp",
17
+ ticket: "mock-ticket",
18
+ minSubmitMs: 0,
19
+ captcha: { required: false }
20
+ };
21
+ var MOCK_SHARE = {
22
+ resourceType: "entity",
23
+ title: "Launch notes",
24
+ properties: { status: "open", owner: "Ada", featured: true },
25
+ body: {
26
+ format: "markdown",
27
+ content: [
28
+ "# Launch notes",
29
+ "",
30
+ "This page is a **published snapshot** from a Synap pod.",
31
+ "",
32
+ "- Read the [Synap docs](https://synap.live)",
33
+ "- Links other than http, https and mailto render as plain text: [bad](javascript:alert(1))",
34
+ "",
35
+ "<script>raw HTML is shown as text, never run</script>"
36
+ ].join("\n")
37
+ },
38
+ publishedOn: "2026-09-27"
39
+ };
40
+ function publicId(prefix, n) {
41
+ return `p_${prefix}${String(n).padStart(16 - prefix.length, "0")}`;
42
+ }
43
+ function slugify(s) {
44
+ return s.normalize("NFKD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 96);
45
+ }
46
+ var PAPER_TITLES = [
47
+ "Sparse graphs at scale",
48
+ "Learning to rank, revisited",
49
+ "A note on causal tracing",
50
+ "Small models, long contexts",
51
+ "Evaluating retrieval honestly",
52
+ "Robust tokenisation",
53
+ "Graphs that forget",
54
+ "Calibration under shift",
55
+ "Latent space cartography",
56
+ "Proofs as programs, again",
57
+ "Cheap uncertainty",
58
+ "R\xE9sum\xE9 of a decade"
59
+ ];
60
+ var VENUES = ["NeurIPS", "ICML", "ACL", "ICLR"];
61
+ var PAPERS = PAPER_TITLES.map((title, i) => ({
62
+ collection: "paper",
63
+ publicId: publicId("paper", i + 1),
64
+ slug: slugify(title),
65
+ title,
66
+ properties: {
67
+ year: 2026 - i % 5,
68
+ venue: VENUES[i % VENUES.length],
69
+ open: i % 3 !== 0
70
+ },
71
+ publishedOn: `2026-09-${String(28 - i).padStart(2, "0")}`,
72
+ body: i % 4 === 3 ? null : { format: "markdown", content: `# ${title}
73
+
74
+ Abstract of **${title}**.` }
75
+ }));
76
+ var PEOPLE = ["Ada Lovelace", "Alan Turing", "Grace Hopper"].map((name, i) => ({
77
+ collection: "person",
78
+ publicId: publicId("person", i + 1),
79
+ slug: null,
80
+ title: name,
81
+ properties: { name, role: i === 0 ? "Lead" : "Researcher" },
82
+ publishedOn: "2026-09-01",
83
+ body: null
84
+ }));
85
+ var MOCK_SITE = {
86
+ title: "Demo lab",
87
+ description: "What the lab publishes",
88
+ publishedOn: "2026-09-01",
89
+ collections: {
90
+ paper: { sort: ["-year"], pageSize: 5, addressing: "slug", records: PAPERS },
91
+ person: { sort: ["name"], pageSize: 20, addressing: "publicId", records: PEOPLE },
92
+ event: { sort: [], pageSize: 10, addressing: "slug", records: [] }
93
+ }
94
+ };
95
+
96
+ // src/testing/index.ts
97
+ var MOCK_POD_URL = "https://mock.synap.invalid";
98
+ var MOCK_TOKENS = { form: "demo-form", share: "demo", site: "demo-site" };
99
+ var MOCK_RETRY_AFTER_SEC = 30;
100
+ var MOCK_FAILURE_TOKENS = {
101
+ /** 429 with no `Retry-After` (what the pod's public doors send today). */
102
+ rateLimited: "mock-rate-limited",
103
+ /** 429 with `Retry-After: 30`. */
104
+ rateLimitedRetryAfter: "mock-rate-limited-retry-after",
105
+ /** 403 `public_doors_disabled`: the owner switched public pages off. */
106
+ doorsDisabled: "mock-doors-disabled",
107
+ /** 500. */
108
+ serverError: "mock-server-error",
109
+ /** 503 (a read: `pod_unreachable` / `server`; a submit: `unavailable`). */
110
+ unavailable: "mock-unavailable",
111
+ /** No answer: the fetch rejects (`network` / `pod_unreachable`). */
112
+ unreachable: "mock-unreachable"
113
+ };
114
+ var NOT_FOUND = { error: "Not found" };
115
+ var DOORS_DISABLED = {
116
+ error: "The owner has not turned on public pages and forms for this workspace.",
117
+ code: "public_doors_disabled"
118
+ };
119
+ var SAFELISTED = /* @__PURE__ */ new Set(["accept", "accept-language", "content-language", "content-type"]);
120
+ var PREFLIGHT_ALLOWED = /* @__PURE__ */ new Set(["content-type"]);
121
+ var PUBLIC_ID_RE = /^p_[A-Za-z0-9]{16}$/;
122
+ var SLUG_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
123
+ var SLUG_MAX_LENGTH = 96;
124
+ var DIGITS = /^\d{1,9}$/;
125
+ var SORT_TERM = /^(-?)([A-Za-z][A-Za-z0-9_]{0,63})$/;
126
+ var MAX_OFFSET = 1e4;
127
+ var MAX_LIMIT = 100;
128
+ var MAX_SORT_KEYS = 3;
129
+ var PUBLISHED_AT = "publishedAt";
130
+ function json(status, body, headers = {}) {
131
+ return new Response(JSON.stringify(body), {
132
+ status,
133
+ headers: { "Content-Type": "application/json", "Cache-Control": "no-cache", ...headers }
134
+ });
135
+ }
136
+ function weakEtag(value) {
137
+ let h = 2166136261;
138
+ for (const ch of JSON.stringify(value)) {
139
+ h ^= ch.charCodeAt(0);
140
+ h = Math.imul(h, 16777619) >>> 0;
141
+ }
142
+ return `W/"${h.toString(16).padStart(8, "0")}"`;
143
+ }
144
+ function etagMatches(header, etag) {
145
+ if (!header) return false;
146
+ if (header.trim() === "*") return true;
147
+ const strip = (t) => t.trim().replace(/^W\//, "");
148
+ return header.split(",").some((t) => strip(t) === strip(etag));
149
+ }
150
+ function failure(token) {
151
+ switch (token) {
152
+ case MOCK_FAILURE_TOKENS.rateLimited:
153
+ return json(429, { error: "Too many requests", retryAfter: "5 minutes" });
154
+ case MOCK_FAILURE_TOKENS.rateLimitedRetryAfter:
155
+ return json(429, { error: "Too many requests" }, { "Retry-After": String(MOCK_RETRY_AFTER_SEC) });
156
+ case MOCK_FAILURE_TOKENS.doorsDisabled:
157
+ return json(403, DOORS_DISABLED);
158
+ case MOCK_FAILURE_TOKENS.serverError:
159
+ return json(500, { error: "Internal error" });
160
+ case MOCK_FAILURE_TOKENS.unavailable:
161
+ return json(503, { received: false });
162
+ case MOCK_FAILURE_TOKENS.unreachable:
163
+ return "reject";
164
+ default:
165
+ return null;
166
+ }
167
+ }
168
+ function summary(record) {
169
+ const { body: _body, ...rest } = record;
170
+ return rest;
171
+ }
172
+ function manifest(site) {
173
+ return {
174
+ ...site.title !== void 0 ? { title: site.title } : {},
175
+ ...site.description !== void 0 ? { description: site.description } : {},
176
+ publishedOn: site.publishedOn,
177
+ collections: Object.keys(site.collections).sort().map((key) => {
178
+ const c = site.collections[key];
179
+ return { key, count: c.records.length, sort: [...c.sort], pageSize: c.pageSize, addressing: c.addressing };
180
+ })
181
+ };
182
+ }
183
+ function parseQuery(params, c) {
184
+ for (const name of params.keys()) {
185
+ if (name === "embed") return "embed is not available";
186
+ if (name === "ref" || name.startsWith("ref[")) return "ref filter is not available";
187
+ }
188
+ const one = (name) => {
189
+ const all = params.getAll(name);
190
+ return all.length === 0 ? void 0 : all.length === 1 ? all[0] : null;
191
+ };
192
+ const rawOffset = one("offset");
193
+ if (rawOffset === null || rawOffset !== void 0 && !DIGITS.test(rawOffset)) return "invalid offset";
194
+ const offset = rawOffset === void 0 ? 0 : Number(rawOffset);
195
+ if (offset > MAX_OFFSET) return "offset too large";
196
+ const rawLimit = one("limit");
197
+ if (rawLimit === null || rawLimit !== void 0 && !DIGITS.test(rawLimit)) return "invalid limit";
198
+ const limit = Math.min(MAX_LIMIT, Math.max(1, rawLimit === void 0 ? c.pageSize : Number(rawLimit)));
199
+ const rawSort = one("sort");
200
+ if (rawSort === null) return "invalid sort";
201
+ const terms = rawSort !== void 0 ? rawSort.split(",") : c.sort.length > 0 ? [...c.sort] : [`-${PUBLISHED_AT}`];
202
+ if (terms.length > MAX_SORT_KEYS) return "invalid sort";
203
+ const allowed = /* @__PURE__ */ new Set([PUBLISHED_AT, ...c.sort.map((s) => s.replace(/^-/, ""))]);
204
+ const sort = [];
205
+ for (const term of terms) {
206
+ const m = SORT_TERM.exec(term);
207
+ if (!m || !allowed.has(m[2]) || sort.some((s) => s.key === m[2])) return "invalid sort";
208
+ sort.push({ key: m[2], desc: m[1] === "-" });
209
+ }
210
+ return { offset, limit, sort };
211
+ }
212
+ function compareRecords(sort) {
213
+ const rank = (v) => typeof v === "string" ? 0 : typeof v === "number" ? 1 : 2;
214
+ const valueOf = (r, key) => key === PUBLISHED_AT ? r.publishedOn : r.properties[key];
215
+ return (a, b) => {
216
+ for (const { key, desc } of sort) {
217
+ const va = valueOf(a, key);
218
+ const vb = valueOf(b, key);
219
+ if (va === void 0 || vb === void 0) {
220
+ if (va !== vb) return va === void 0 ? 1 : -1;
221
+ continue;
222
+ }
223
+ let d = rank(va) - rank(vb);
224
+ if (d === 0) d = va < vb ? -1 : va > vb ? 1 : 0;
225
+ if (d !== 0) return desc ? -d : d;
226
+ }
227
+ if (a.publishedOn !== b.publishedOn) return a.publishedOn < b.publishedOn ? 1 : -1;
228
+ return a.publicId < b.publicId ? -1 : a.publicId > b.publicId ? 1 : 0;
229
+ };
230
+ }
231
+ function cached(view, ifNoneMatch) {
232
+ const etag = weakEtag(view);
233
+ if (etagMatches(ifNoneMatch, etag)) {
234
+ return new Response(null, { status: 304, headers: { ETag: etag, "Cache-Control": "no-cache" } });
235
+ }
236
+ return json(200, view, { ETag: etag });
237
+ }
238
+ function siteDoor(site, rest, params, ifNoneMatch) {
239
+ if (rest.length === 0) return cached(manifest(site), ifNoneMatch);
240
+ if (rest[0] !== "collections" || rest.length > 3 || !Object.hasOwn(site.collections, rest[1])) {
241
+ return json(404, NOT_FOUND);
242
+ }
243
+ const c = site.collections[rest[1]];
244
+ if (rest.length === 2) {
245
+ const q = parseQuery(params, c);
246
+ if (typeof q === "string") return json(400, { error: q });
247
+ const sorted = [...c.records].sort(compareRecords(q.sort));
248
+ const page = {
249
+ items: sorted.slice(q.offset, q.offset + q.limit).map(summary),
250
+ total: sorted.length,
251
+ offset: q.offset,
252
+ limit: q.limit
253
+ };
254
+ return cached(page, ifNoneMatch);
255
+ }
256
+ const address = rest[2];
257
+ const byPublicId = PUBLIC_ID_RE.test(address);
258
+ if (!byPublicId && !(address.length <= SLUG_MAX_LENGTH && SLUG_RE.test(address))) return json(404, NOT_FOUND);
259
+ const record = c.records.find((r) => byPublicId ? r.publicId === address : r.slug === address);
260
+ if (!record) return json(404, NOT_FOUND);
261
+ if (params.has("embed")) return json(400, { error: "embed is not available" });
262
+ return cached(record, ifNoneMatch);
263
+ }
264
+ function invalidKeys(form, body) {
265
+ const fields = body?.fields;
266
+ if (typeof fields !== "object" || fields === null) return [];
267
+ const answers = fields;
268
+ return form.fields.filter((f) => {
269
+ const v = answers[f.key];
270
+ if (v === void 0 || v === "" || v === null) return f.required === true;
271
+ if (f.type === "boolean") return typeof v !== "boolean" || f.required === true && v !== true;
272
+ const allowed = f.constraints?.enum;
273
+ return allowed !== void 0 && !allowed.includes(String(v));
274
+ }).map((f) => f.key);
275
+ }
276
+ function createMockPod(options = {}) {
277
+ const forms = options.forms ?? { [MOCK_TOKENS.form]: MOCK_FORM };
278
+ const shares = options.shares ?? { [MOCK_TOKENS.share]: MOCK_SHARE };
279
+ const sites = options.sites ?? { [MOCK_TOKENS.site]: MOCK_SITE };
280
+ const requests = [];
281
+ const submissions = [];
282
+ const mockFetch = async (input, init) => {
283
+ const request = typeof input === "object" && "method" in input ? input : null;
284
+ const url = new URL(typeof input === "string" ? input : input instanceof URL ? input.href : input.url);
285
+ const method = (init?.method ?? request?.method ?? "GET").toUpperCase();
286
+ const headers = {};
287
+ new Headers(init?.headers ?? request?.headers).forEach((value, name) => {
288
+ headers[name.toLowerCase()] = value;
289
+ });
290
+ const credentials = init?.credentials ?? request?.credentials;
291
+ requests.push({ method, url: url.href, headers, credentials });
292
+ if (credentials === "include") throw new TypeError("Failed to fetch: credentialed request to a public door");
293
+ if (options.browser) {
294
+ const preflighted = Object.keys(headers).filter((h) => !SAFELISTED.has(h) && !PREFLIGHT_ALLOWED.has(h));
295
+ if (preflighted.length > 0) throw new TypeError(`Failed to fetch: preflight refuses ${preflighted.join(", ")}`);
296
+ }
297
+ const m = url.pathname.match(/^\/api\/hub\/public\/(forms|shares|sites)\/(.+)$/);
298
+ if (!m) return json(404, NOT_FOUND);
299
+ let segments;
300
+ try {
301
+ segments = m[2].split("/").map(decodeURIComponent);
302
+ } catch {
303
+ return json(404, NOT_FOUND);
304
+ }
305
+ const [token, ...rest] = segments;
306
+ const failed = failure(token);
307
+ if (failed === "reject") throw new TypeError("Failed to fetch: mock pod unreachable");
308
+ if (failed) return failed;
309
+ const door = m[1];
310
+ if (door === "forms" && method === "POST" && rest.length === 0) {
311
+ let body = null;
312
+ try {
313
+ body = JSON.parse(typeof init?.body === "string" ? init.body : "null");
314
+ } catch {
315
+ body = null;
316
+ }
317
+ const form = Object.hasOwn(forms, token) ? forms[token] : void 0;
318
+ const invalid = form ? invalidKeys(form, body) : [];
319
+ const status = invalid.length > 0 ? 422 : 202;
320
+ submissions.push({ token, body, status });
321
+ return status === 422 ? json(422, { received: false, invalid }) : json(202, { received: true });
322
+ }
323
+ if (method !== "GET") return json(404, NOT_FOUND);
324
+ if (door === "forms" && rest.length === 0 && Object.hasOwn(forms, token)) return json(200, forms[token]);
325
+ if (door === "shares" && rest.length === 0 && Object.hasOwn(shares, token)) return json(200, shares[token]);
326
+ if (door === "sites" && Object.hasOwn(sites, token)) {
327
+ return siteDoor(sites[token], rest, url.searchParams, headers["if-none-match"] ?? null);
328
+ }
329
+ return json(404, NOT_FOUND);
330
+ };
331
+ return {
332
+ podUrl: MOCK_POD_URL,
333
+ fetch: mockFetch,
334
+ requests,
335
+ submissions,
336
+ reset() {
337
+ requests.length = 0;
338
+ submissions.length = 0;
339
+ }
340
+ };
341
+ }
342
+ export {
343
+ MOCK_FAILURE_TOKENS,
344
+ MOCK_FORM,
345
+ MOCK_POD_URL,
346
+ MOCK_RETRY_AFTER_SEC,
347
+ MOCK_SHARE,
348
+ MOCK_SITE,
349
+ MOCK_TOKENS,
350
+ createMockPod
351
+ };
352
+ //# sourceMappingURL=testing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/testing/fixtures.ts","../src/testing/index.ts"],"sourcesContent":["/**\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 * @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"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synap-core/sdk",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Type-safe tRPC client SDK for Synap data pods",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -16,6 +16,11 @@
16
16
  "types": "./dist/public.d.ts",
17
17
  "import": "./dist/public.js",
18
18
  "require": "./dist/public.cjs"
19
+ },
20
+ "./testing": {
21
+ "types": "./dist/testing.d.ts",
22
+ "import": "./dist/testing.js",
23
+ "require": "./dist/testing.cjs"
19
24
  }
20
25
  },
21
26
  "files": [