@synap-core/sdk 1.1.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 +138 -6
- package/dist/chunk-J7XHAAFJ.js +79 -0
- package/dist/chunk-J7XHAAFJ.js.map +1 -0
- package/dist/client-CVUT1Mcf.d.cts +253 -0
- package/dist/client-CVUT1Mcf.d.ts +253 -0
- 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/index.cjs +48 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +118 -7
- package/dist/index.d.ts +118 -7
- package/dist/index.js +41 -2
- package/dist/index.js.map +1 -1
- package/dist/public.cjs +627 -0
- package/dist/public.cjs.map +1 -0
- package/dist/public.d.cts +228 -0
- package/dist/public.d.ts +228 -0
- package/dist/public.js +578 -0
- package/dist/public.js.map +1 -0
- package/dist/testing.cjs +671 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +181 -0
- package/dist/testing.d.ts +181 -0
- package/dist/testing.js +614 -0
- package/dist/testing.js.map +1 -0
- package/dist/types-DpqPNaaa.d.cts +76 -0
- package/dist/types-DpqPNaaa.d.ts +76 -0
- package/package.json +16 -3
- package/src/client.ts +24 -7
- 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/index.ts +13 -0
- package/src/public/client.ts +579 -0
- package/src/public/form-model.ts +210 -0
- package/src/public/index.ts +12 -0
- package/src/public/safe-markdown.ts +114 -0
- package/src/public/share-model.ts +47 -0
- package/src/public/site-model.ts +217 -0
- package/src/templates.ts +122 -0
- package/src/testing/auth-server.ts +380 -0
- package/src/testing/fixtures.ts +132 -0
- package/src/testing/index.ts +359 -0
- package/src/public.ts +0 -262
package/dist/public.cjs
ADDED
|
@@ -0,0 +1,627 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/public/index.ts
|
|
21
|
+
var public_exports = {};
|
|
22
|
+
__export(public_exports, {
|
|
23
|
+
SITE_MAX_LIMIT: () => SITE_MAX_LIMIT,
|
|
24
|
+
SITE_MAX_OFFSET: () => SITE_MAX_OFFSET,
|
|
25
|
+
SITE_MAX_SORT_KEYS: () => SITE_MAX_SORT_KEYS,
|
|
26
|
+
SITE_PUBLISHED_AT_SORT: () => SITE_PUBLISHED_AT_SORT,
|
|
27
|
+
UNAVAILABLE_TITLE: () => UNAVAILABLE_TITLE,
|
|
28
|
+
captchaSatisfied: () => captchaSatisfied,
|
|
29
|
+
createPublicClient: () => createPublicClient,
|
|
30
|
+
formatPublicValue: () => formatPublicValue,
|
|
31
|
+
initialValues: () => initialValues,
|
|
32
|
+
loadErrorMessage: () => loadErrorMessage,
|
|
33
|
+
pageQuery: () => pageQuery,
|
|
34
|
+
parseInlines: () => parseInlines,
|
|
35
|
+
parsePageParam: () => parsePageParam,
|
|
36
|
+
parseSafeMarkdown: () => parseSafeMarkdown,
|
|
37
|
+
safeHref: () => safeHref,
|
|
38
|
+
submitFailure: () => submitFailure,
|
|
39
|
+
submitWaitRemaining: () => submitWaitRemaining,
|
|
40
|
+
toCollectionPageModel: () => toCollectionPageModel,
|
|
41
|
+
toFieldModels: () => toFieldModels,
|
|
42
|
+
toRecordViewModel: () => toRecordViewModel,
|
|
43
|
+
toSharePageModel: () => toSharePageModel,
|
|
44
|
+
toSubmission: () => toSubmission,
|
|
45
|
+
validate: () => validate
|
|
46
|
+
});
|
|
47
|
+
module.exports = __toCommonJS(public_exports);
|
|
48
|
+
|
|
49
|
+
// src/public/client.ts
|
|
50
|
+
var SITE_MAX_OFFSET = 1e4;
|
|
51
|
+
var SITE_MAX_LIMIT = 100;
|
|
52
|
+
var SITE_MAX_SORT_KEYS = 3;
|
|
53
|
+
var SITE_PUBLISHED_AT_SORT = "publishedAt";
|
|
54
|
+
var HONEYPOT_KEY = "hp";
|
|
55
|
+
var MAX_TICKET_WAIT_MS = 1e4;
|
|
56
|
+
var MAX_INVALID_KEYS = 100;
|
|
57
|
+
var MAX_KEY_LENGTH = 200;
|
|
58
|
+
function waitMs(form) {
|
|
59
|
+
const n = form?.minSubmitMs;
|
|
60
|
+
return typeof n === "number" && Number.isFinite(n) && n > 0 ? Math.min(n, MAX_TICKET_WAIT_MS) : 0;
|
|
61
|
+
}
|
|
62
|
+
function parse422(body) {
|
|
63
|
+
if (typeof body !== "object" || body === null) return null;
|
|
64
|
+
const b = body;
|
|
65
|
+
if (b.retry === true) return { retry: true };
|
|
66
|
+
if (!Array.isArray(b.invalid)) return null;
|
|
67
|
+
return {
|
|
68
|
+
fields: b.invalid.filter((k) => typeof k === "string" && k.length <= MAX_KEY_LENGTH).slice(0, MAX_INVALID_KEYS)
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
var DOORS_DISABLED_CODE = "public_doors_disabled";
|
|
72
|
+
function isDoorsDisabledBody(body) {
|
|
73
|
+
return typeof body === "object" && body !== null && body.code === DOORS_DISABLED_CODE;
|
|
74
|
+
}
|
|
75
|
+
function classify(status, body) {
|
|
76
|
+
if (status === 404) return "not_found";
|
|
77
|
+
if (status === 429) return "rate_limited";
|
|
78
|
+
if (status === 403 && isDoorsDisabledBody(body)) return "doors_disabled";
|
|
79
|
+
return "server";
|
|
80
|
+
}
|
|
81
|
+
function classifySite(status, body) {
|
|
82
|
+
if (status === 400) return "bad_request";
|
|
83
|
+
if (status === 502 || status === 503 || status === 504) return "pod_unreachable";
|
|
84
|
+
return classify(status, body);
|
|
85
|
+
}
|
|
86
|
+
var MAX_RETRY_AFTER_SEC = 86400;
|
|
87
|
+
function retryAfterSec(res) {
|
|
88
|
+
const raw = res.headers.get("retry-after")?.trim();
|
|
89
|
+
if (!raw) return void 0;
|
|
90
|
+
let sec;
|
|
91
|
+
if (/^\d+$/.test(raw)) {
|
|
92
|
+
sec = Number(raw);
|
|
93
|
+
} else {
|
|
94
|
+
const at = Date.parse(raw);
|
|
95
|
+
if (Number.isNaN(at)) return void 0;
|
|
96
|
+
sec = Math.ceil((at - Date.now()) / 1e3);
|
|
97
|
+
}
|
|
98
|
+
return Math.min(Math.max(0, sec), MAX_RETRY_AFTER_SEC);
|
|
99
|
+
}
|
|
100
|
+
function fail(error, res) {
|
|
101
|
+
const sec = error === "rate_limited" && res ? retryAfterSec(res) : void 0;
|
|
102
|
+
return sec === void 0 ? { ok: false, error } : { ok: false, error, retryAfterSec: sec };
|
|
103
|
+
}
|
|
104
|
+
async function readBody(res) {
|
|
105
|
+
try {
|
|
106
|
+
return await res.json();
|
|
107
|
+
} catch {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function segment(raw) {
|
|
112
|
+
if (typeof raw !== "string" || raw === "" || raw === "." || raw === "..") return null;
|
|
113
|
+
return encodeURIComponent(raw);
|
|
114
|
+
}
|
|
115
|
+
var SORT_TERM = /^(-?)([A-Za-z][A-Za-z0-9_]{0,63})$/;
|
|
116
|
+
function collectionSearch(q) {
|
|
117
|
+
const params = [];
|
|
118
|
+
if (q?.offset !== void 0) {
|
|
119
|
+
if (!Number.isInteger(q.offset) || q.offset < 0 || q.offset > SITE_MAX_OFFSET) return null;
|
|
120
|
+
params.push(`offset=${q.offset}`);
|
|
121
|
+
}
|
|
122
|
+
if (q?.limit !== void 0) {
|
|
123
|
+
if (!Number.isInteger(q.limit) || q.limit < 0) return null;
|
|
124
|
+
params.push(`limit=${Math.min(SITE_MAX_LIMIT, Math.max(1, q.limit))}`);
|
|
125
|
+
}
|
|
126
|
+
if (q?.sort !== void 0) {
|
|
127
|
+
const terms = typeof q.sort === "string" ? q.sort.split(",") : [...q.sort];
|
|
128
|
+
if (terms.length > SITE_MAX_SORT_KEYS) return null;
|
|
129
|
+
const keys = /* @__PURE__ */ new Set();
|
|
130
|
+
for (const term of terms) {
|
|
131
|
+
const m = SORT_TERM.exec(term);
|
|
132
|
+
if (!m || keys.has(m[2])) return null;
|
|
133
|
+
keys.add(m[2]);
|
|
134
|
+
}
|
|
135
|
+
if (terms.length > 0) params.push(`sort=${terms.join(",")}`);
|
|
136
|
+
}
|
|
137
|
+
return params.length > 0 ? `?${params.join("&")}` : "";
|
|
138
|
+
}
|
|
139
|
+
function createPublicClient(opts) {
|
|
140
|
+
const base = `${opts.podUrl.replace(/\/+$/, "")}/api/hub/public`;
|
|
141
|
+
const doFetch = () => {
|
|
142
|
+
const f = opts.fetch ?? globalThis.fetch;
|
|
143
|
+
if (!f) throw new Error("No fetch available: pass `fetch` to createPublicClient");
|
|
144
|
+
return f;
|
|
145
|
+
};
|
|
146
|
+
const url = (resource, token) => `${base}/${resource}/${encodeURIComponent(token)}`;
|
|
147
|
+
async function send(target, init) {
|
|
148
|
+
try {
|
|
149
|
+
return await doFetch()(target, {
|
|
150
|
+
method: init.method,
|
|
151
|
+
credentials: "omit",
|
|
152
|
+
headers: init.method === "POST" ? { "Content-Type": "application/json" } : init.ifNoneMatch ? { Accept: "application/json", "If-None-Match": init.ifNoneMatch } : { Accept: "application/json" },
|
|
153
|
+
...init.body !== void 0 ? { body: init.body } : {}
|
|
154
|
+
});
|
|
155
|
+
} catch {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
async function getJson(resource, token) {
|
|
160
|
+
const res = await send(url(resource, token), { method: "GET" });
|
|
161
|
+
if (!res) return { ok: false, error: "network" };
|
|
162
|
+
if (res.status !== 200) return fail(classify(res.status, await readBody(res)), res);
|
|
163
|
+
try {
|
|
164
|
+
return { ok: true, data: await res.json() };
|
|
165
|
+
} catch {
|
|
166
|
+
return { ok: false, error: "server" };
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
async function siteRead(path, ifNoneMatch) {
|
|
170
|
+
if (path === null) return { ok: false, error: "not_found" };
|
|
171
|
+
const revalidate = typeof ifNoneMatch === "string" && ifNoneMatch !== "" ? ifNoneMatch : void 0;
|
|
172
|
+
const res = await send(`${base}/sites/${path}`, { method: "GET", ifNoneMatch: revalidate });
|
|
173
|
+
if (!res) return { ok: false, error: "pod_unreachable" };
|
|
174
|
+
const etag = res.headers.get("etag");
|
|
175
|
+
if (res.status === 304 && revalidate !== void 0) {
|
|
176
|
+
return { ok: true, notModified: true, etag: etag ?? revalidate };
|
|
177
|
+
}
|
|
178
|
+
if (res.status !== 200) return fail(classifySite(res.status, await readBody(res)), res);
|
|
179
|
+
try {
|
|
180
|
+
return { ok: true, data: await res.json(), etag };
|
|
181
|
+
} catch {
|
|
182
|
+
return { ok: false, error: "server" };
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function getSite(siteToken, opts2) {
|
|
186
|
+
return siteRead(segment(siteToken), opts2?.ifNoneMatch);
|
|
187
|
+
}
|
|
188
|
+
async function listCollection(siteToken, key, query) {
|
|
189
|
+
const token = segment(siteToken);
|
|
190
|
+
const k = segment(key);
|
|
191
|
+
if (token === null || k === null) return { ok: false, error: "not_found" };
|
|
192
|
+
const search = collectionSearch(query);
|
|
193
|
+
if (search === null) return { ok: false, error: "bad_request" };
|
|
194
|
+
return siteRead(`${token}/collections/${k}${search}`, query?.ifNoneMatch);
|
|
195
|
+
}
|
|
196
|
+
function getRecord(siteToken, key, slugOrPublicId, opts2) {
|
|
197
|
+
const parts = [segment(siteToken), segment(key), segment(slugOrPublicId)];
|
|
198
|
+
const path = parts.every((p) => p !== null) ? `${parts[0]}/collections/${parts[1]}/${parts[2]}` : null;
|
|
199
|
+
return siteRead(path, opts2?.ifNoneMatch);
|
|
200
|
+
}
|
|
201
|
+
const sleep = opts.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
202
|
+
async function postOnce(token, input, ticket) {
|
|
203
|
+
const body = {
|
|
204
|
+
fields: input.fields,
|
|
205
|
+
[HONEYPOT_KEY]: ""
|
|
206
|
+
};
|
|
207
|
+
if (ticket !== void 0) body.ticket = ticket;
|
|
208
|
+
if (input.captchaToken !== void 0) body.captchaToken = input.captchaToken;
|
|
209
|
+
if (input.idempotencyKey !== void 0) body.idempotencyKey = input.idempotencyKey;
|
|
210
|
+
const res = await send(url("forms", token), {
|
|
211
|
+
method: "POST",
|
|
212
|
+
body: JSON.stringify(body)
|
|
213
|
+
});
|
|
214
|
+
const done = (result) => ({ retry: false, result });
|
|
215
|
+
if (!res) return done({ ok: false, error: "network" });
|
|
216
|
+
if (res.status === 202) return done({ ok: true, received: true });
|
|
217
|
+
if (res.status === 422) {
|
|
218
|
+
let parsed = null;
|
|
219
|
+
try {
|
|
220
|
+
parsed = parse422(await res.json());
|
|
221
|
+
} catch {
|
|
222
|
+
parsed = null;
|
|
223
|
+
}
|
|
224
|
+
if (!parsed) return done({ ok: false, error: "server" });
|
|
225
|
+
if ("retry" in parsed) return { retry: true };
|
|
226
|
+
return done({ ok: false, error: "invalid", fields: parsed.fields });
|
|
227
|
+
}
|
|
228
|
+
if (res.status === 503) return done({ ok: false, error: "unavailable" });
|
|
229
|
+
if (res.status === 429) return done(fail("rate_limited", res));
|
|
230
|
+
if (res.status === 403 && isDoorsDisabledBody(await readBody(res))) {
|
|
231
|
+
return done({ ok: false, error: "doors_disabled" });
|
|
232
|
+
}
|
|
233
|
+
return done({ ok: false, error: "server" });
|
|
234
|
+
}
|
|
235
|
+
return {
|
|
236
|
+
readShare: (token) => getJson("shares", token),
|
|
237
|
+
getForm: (token) => getJson("forms", token),
|
|
238
|
+
async submitForm(token, input) {
|
|
239
|
+
const first = await postOnce(token, input, input.ticket);
|
|
240
|
+
if (!first.retry) return first.result;
|
|
241
|
+
const fresh = await getJson("forms", token);
|
|
242
|
+
if (!fresh.ok) return { ok: false, error: "retry" };
|
|
243
|
+
await sleep(waitMs(fresh.data));
|
|
244
|
+
const second = await postOnce(token, input, fresh.data.ticket);
|
|
245
|
+
return second.retry ? { ok: false, error: "retry" } : second.result;
|
|
246
|
+
},
|
|
247
|
+
getSite,
|
|
248
|
+
listCollection,
|
|
249
|
+
getRecord
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// src/public/form-model.ts
|
|
254
|
+
var INPUT_TYPE = {
|
|
255
|
+
text: "text",
|
|
256
|
+
richtext: "text",
|
|
257
|
+
email: "email",
|
|
258
|
+
phone: "tel",
|
|
259
|
+
url: "url",
|
|
260
|
+
number: "number",
|
|
261
|
+
boolean: "text",
|
|
262
|
+
date: "date",
|
|
263
|
+
enum: "text"
|
|
264
|
+
};
|
|
265
|
+
function controlFor(type) {
|
|
266
|
+
if (type === "richtext") return "textarea";
|
|
267
|
+
if (type === "enum") return "select";
|
|
268
|
+
if (type === "boolean") return "checkbox";
|
|
269
|
+
return "input";
|
|
270
|
+
}
|
|
271
|
+
function toFieldModels(form) {
|
|
272
|
+
return form.fields.map((f) => ({
|
|
273
|
+
key: f.key,
|
|
274
|
+
label: f.label,
|
|
275
|
+
help: f.help,
|
|
276
|
+
required: f.required === true,
|
|
277
|
+
control: controlFor(f.type),
|
|
278
|
+
inputType: INPUT_TYPE[f.type],
|
|
279
|
+
options: f.constraints?.enum ?? [],
|
|
280
|
+
maxLength: f.constraints?.maxLength,
|
|
281
|
+
min: f.constraints?.min,
|
|
282
|
+
max: f.constraints?.max
|
|
283
|
+
}));
|
|
284
|
+
}
|
|
285
|
+
function initialValues(fields) {
|
|
286
|
+
return Object.fromEntries(fields.map((f) => [f.key, f.control === "checkbox" ? false : ""]));
|
|
287
|
+
}
|
|
288
|
+
function validate(fields, values) {
|
|
289
|
+
const errors = {};
|
|
290
|
+
for (const f of fields) {
|
|
291
|
+
const v = values[f.key];
|
|
292
|
+
if (f.control === "checkbox") {
|
|
293
|
+
if (f.required && v !== true) errors[f.key] = "Required";
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
const s = typeof v === "string" ? v.trim() : "";
|
|
297
|
+
if (s === "") {
|
|
298
|
+
if (f.required) errors[f.key] = "Required";
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
if (f.maxLength !== void 0 && s.length > f.maxLength) {
|
|
302
|
+
errors[f.key] = `At most ${f.maxLength} characters`;
|
|
303
|
+
} else if (f.control === "select" && f.options.length > 0 && !f.options.includes(s)) {
|
|
304
|
+
errors[f.key] = "Pick one of the options";
|
|
305
|
+
} else if (f.inputType === "email" && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s)) {
|
|
306
|
+
errors[f.key] = "Enter a valid email";
|
|
307
|
+
} else if (f.inputType === "number") {
|
|
308
|
+
const n = Number(s);
|
|
309
|
+
if (!Number.isFinite(n)) errors[f.key] = "Enter a number";
|
|
310
|
+
else if (f.min !== void 0 && n < f.min) errors[f.key] = `At least ${f.min}`;
|
|
311
|
+
else if (f.max !== void 0 && n > f.max) errors[f.key] = `At most ${f.max}`;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return errors;
|
|
315
|
+
}
|
|
316
|
+
function toSubmission(fields, values) {
|
|
317
|
+
const out = {};
|
|
318
|
+
for (const f of fields) {
|
|
319
|
+
const v = values[f.key];
|
|
320
|
+
if (f.control === "checkbox") {
|
|
321
|
+
out[f.key] = v === true;
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
const s = typeof v === "string" ? v.trim() : "";
|
|
325
|
+
if (s === "") continue;
|
|
326
|
+
out[f.key] = f.inputType === "number" ? Number(s) : s;
|
|
327
|
+
}
|
|
328
|
+
return out;
|
|
329
|
+
}
|
|
330
|
+
function captchaSatisfied(form, captchaToken) {
|
|
331
|
+
return !form.captcha.required || captchaToken !== null && captchaToken !== "";
|
|
332
|
+
}
|
|
333
|
+
function loadErrorMessage(error) {
|
|
334
|
+
switch (error) {
|
|
335
|
+
case "not_found":
|
|
336
|
+
return "This form isn't available.";
|
|
337
|
+
case "rate_limited":
|
|
338
|
+
return "Too many requests. Try again in a minute.";
|
|
339
|
+
case "doors_disabled":
|
|
340
|
+
return "This form isn't accepting responses right now.";
|
|
341
|
+
case "network":
|
|
342
|
+
return "We couldn't reach the server. Check your connection.";
|
|
343
|
+
case "server":
|
|
344
|
+
return "Something went wrong loading this form.";
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
function submitFailure(res, fields) {
|
|
348
|
+
const none = { fieldErrors: {}, retryable: false, refreshForm: false };
|
|
349
|
+
switch (res.error) {
|
|
350
|
+
case "invalid": {
|
|
351
|
+
const known = new Set(fields.map((f) => f.key));
|
|
352
|
+
const fieldErrors = Object.fromEntries(
|
|
353
|
+
res.fields.filter((k) => known.has(k)).map((k) => [k, "Check this answer."])
|
|
354
|
+
);
|
|
355
|
+
const n = Object.keys(fieldErrors).length;
|
|
356
|
+
return {
|
|
357
|
+
...none,
|
|
358
|
+
fieldErrors,
|
|
359
|
+
message: n > 0 ? "Some answers need another look. Your message was not sent." : "We couldn't read that as a message. Reload the page and try again."
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
case "retry":
|
|
363
|
+
return {
|
|
364
|
+
...none,
|
|
365
|
+
retryable: true,
|
|
366
|
+
refreshForm: true,
|
|
367
|
+
message: "That was sent a little too fast. Your message was not sent; try again."
|
|
368
|
+
};
|
|
369
|
+
case "unavailable":
|
|
370
|
+
return {
|
|
371
|
+
...none,
|
|
372
|
+
retryable: true,
|
|
373
|
+
message: "We couldn't save your message just now. It was not sent; try again."
|
|
374
|
+
};
|
|
375
|
+
case "rate_limited":
|
|
376
|
+
return { ...none, message: "Too many attempts. Try again in a minute." };
|
|
377
|
+
case "doors_disabled":
|
|
378
|
+
return {
|
|
379
|
+
...none,
|
|
380
|
+
message: "This form isn't accepting responses right now. Your message was not sent."
|
|
381
|
+
};
|
|
382
|
+
case "network":
|
|
383
|
+
return {
|
|
384
|
+
...none,
|
|
385
|
+
retryable: true,
|
|
386
|
+
message: "We couldn't reach the server. Your message was not sent."
|
|
387
|
+
};
|
|
388
|
+
case "server":
|
|
389
|
+
return { ...none, message: "Something went wrong. Your message was not sent." };
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
function submitWaitRemaining(minSubmitMs, loadedAt, now) {
|
|
393
|
+
const wait = Number.isFinite(minSubmitMs) && minSubmitMs > 0 ? minSubmitMs : 0;
|
|
394
|
+
return Math.max(0, loadedAt + wait - now);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// src/public/safe-markdown.ts
|
|
398
|
+
var SAFE_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "mailto:"]);
|
|
399
|
+
function safeHref(raw) {
|
|
400
|
+
let url;
|
|
401
|
+
try {
|
|
402
|
+
url = new URL(raw.trim());
|
|
403
|
+
} catch {
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
return SAFE_PROTOCOLS.has(url.protocol) ? url.href : null;
|
|
407
|
+
}
|
|
408
|
+
var INLINE = /\*\*([^*]+)\*\*|\[([^\]]+)\]\(((?:[^()\s]|\([^()\s]*\))+)\)/g;
|
|
409
|
+
function parseInlines(text) {
|
|
410
|
+
const out = [];
|
|
411
|
+
let last = 0;
|
|
412
|
+
for (const m of text.matchAll(INLINE)) {
|
|
413
|
+
const at = m.index ?? 0;
|
|
414
|
+
if (at > last) out.push({ type: "text", text: text.slice(last, at) });
|
|
415
|
+
if (m[1] !== void 0) {
|
|
416
|
+
out.push({ type: "strong", text: m[1] });
|
|
417
|
+
} else {
|
|
418
|
+
const href = safeHref(m[3]);
|
|
419
|
+
out.push(href ? { type: "link", text: m[2], href } : { type: "text", text: m[2] });
|
|
420
|
+
}
|
|
421
|
+
last = at + m[0].length;
|
|
422
|
+
}
|
|
423
|
+
if (last < text.length) out.push({ type: "text", text: text.slice(last) });
|
|
424
|
+
return out;
|
|
425
|
+
}
|
|
426
|
+
var HEADING = /^(#{1,6})\s+(.*)$/;
|
|
427
|
+
var BULLET = /^\s*[-*]\s+(.*)$/;
|
|
428
|
+
var ORDERED = /^\s*\d+[.)]\s+(.*)$/;
|
|
429
|
+
var FENCE = /^\s*(```|~~~)/;
|
|
430
|
+
function parseSafeMarkdown(source) {
|
|
431
|
+
const lines = source.replace(/\r\n?/g, "\n").split("\n");
|
|
432
|
+
const blocks = [];
|
|
433
|
+
let para = [];
|
|
434
|
+
let list = null;
|
|
435
|
+
const flush = () => {
|
|
436
|
+
if (para.length) blocks.push({ type: "paragraph", inlines: parseInlines(para.join(" ")) });
|
|
437
|
+
para = [];
|
|
438
|
+
if (list) blocks.push({ type: "list", ...list });
|
|
439
|
+
list = null;
|
|
440
|
+
};
|
|
441
|
+
for (let i = 0; i < lines.length; i++) {
|
|
442
|
+
const line = lines[i];
|
|
443
|
+
const fence = line.match(FENCE);
|
|
444
|
+
if (fence) {
|
|
445
|
+
flush();
|
|
446
|
+
const code = [];
|
|
447
|
+
i++;
|
|
448
|
+
while (i < lines.length && !lines[i].trimStart().startsWith(fence[1])) code.push(lines[i++]);
|
|
449
|
+
blocks.push({ type: "code", text: code.join("\n") });
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
if (line.trim() === "") {
|
|
453
|
+
flush();
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
const heading = line.match(HEADING);
|
|
457
|
+
if (heading) {
|
|
458
|
+
flush();
|
|
459
|
+
const level = heading[1].length;
|
|
460
|
+
blocks.push({ type: "heading", level, inlines: parseInlines(heading[2]) });
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
463
|
+
const bullet = line.match(BULLET);
|
|
464
|
+
const ordered = bullet ? null : line.match(ORDERED);
|
|
465
|
+
if (bullet || ordered) {
|
|
466
|
+
const isOrdered = ordered !== null;
|
|
467
|
+
if (para.length || list && list.ordered !== isOrdered) flush();
|
|
468
|
+
if (!list) list = { ordered: isOrdered, items: [] };
|
|
469
|
+
list.items.push(parseInlines((bullet ?? ordered)[1]));
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
if (list) flush();
|
|
473
|
+
para.push(line.trim());
|
|
474
|
+
}
|
|
475
|
+
flush();
|
|
476
|
+
return blocks;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// src/public/site-model.ts
|
|
480
|
+
var GROUPING = { useGrouping: "min2" };
|
|
481
|
+
var ISO_DATE = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
482
|
+
var ISO_DATE_TIME = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d{1,9})?)?(Z|[+-]\d{2}:\d{2})$/;
|
|
483
|
+
function isCalendarDay(s) {
|
|
484
|
+
const m = ISO_DATE.exec(s);
|
|
485
|
+
if (!m) return false;
|
|
486
|
+
const d = new Date(Date.UTC(Number(m[1]), Number(m[2]) - 1, Number(m[3])));
|
|
487
|
+
return d.toISOString().slice(0, 10) === s;
|
|
488
|
+
}
|
|
489
|
+
function formatPublicValue(value, opts = {}) {
|
|
490
|
+
const locale = opts.locale ?? "en";
|
|
491
|
+
if (typeof value === "boolean") return value ? opts.labels?.yes ?? "Yes" : opts.labels?.no ?? "No";
|
|
492
|
+
if (typeof value === "number") {
|
|
493
|
+
return Number.isFinite(value) ? new Intl.NumberFormat(locale, GROUPING).format(value) : String(value);
|
|
494
|
+
}
|
|
495
|
+
if (isCalendarDay(value)) {
|
|
496
|
+
return new Intl.DateTimeFormat(locale, { dateStyle: "medium", timeZone: "UTC" }).format(
|
|
497
|
+
/* @__PURE__ */ new Date(`${value}T00:00:00Z`)
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
if (ISO_DATE_TIME.test(value)) {
|
|
501
|
+
const at = new Date(value);
|
|
502
|
+
if (!Number.isNaN(at.getTime())) {
|
|
503
|
+
return new Intl.DateTimeFormat(locale, {
|
|
504
|
+
dateStyle: "medium",
|
|
505
|
+
timeStyle: "short",
|
|
506
|
+
timeZone: opts.timeZone ?? "UTC"
|
|
507
|
+
}).format(at);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
return value;
|
|
511
|
+
}
|
|
512
|
+
var MAX_PAGE_PARAM = 1e6;
|
|
513
|
+
function parsePageParam(raw) {
|
|
514
|
+
if (raw === void 0 || raw === null || raw === "") return 1;
|
|
515
|
+
if (Array.isArray(raw)) return null;
|
|
516
|
+
if (!/^[1-9]\d*$/.test(raw)) return null;
|
|
517
|
+
const n = Number(raw);
|
|
518
|
+
return n <= MAX_PAGE_PARAM ? n : null;
|
|
519
|
+
}
|
|
520
|
+
function pageQuery(page, pageSize) {
|
|
521
|
+
if (!Number.isInteger(page) || page < 1) return null;
|
|
522
|
+
const limit = Math.min(SITE_MAX_LIMIT, Math.max(1, Math.floor(pageSize) || 1));
|
|
523
|
+
const offset = (page - 1) * limit;
|
|
524
|
+
return offset <= SITE_MAX_OFFSET ? { offset, limit } : null;
|
|
525
|
+
}
|
|
526
|
+
function toCollectionPageModel(page, opts) {
|
|
527
|
+
const pageSize = Math.max(1, page.limit);
|
|
528
|
+
const current = Math.floor(Math.max(0, page.offset) / pageSize) + 1;
|
|
529
|
+
const reachable = Math.floor(SITE_MAX_OFFSET / pageSize) + 1;
|
|
530
|
+
const pageCount = Math.max(1, Math.min(Math.ceil(page.total / pageSize), reachable));
|
|
531
|
+
const param = opts.param ?? "page";
|
|
532
|
+
const sep = opts.basePath.includes("?") ? "&" : "?";
|
|
533
|
+
const link = (n) => ({
|
|
534
|
+
page: n,
|
|
535
|
+
href: n === 1 ? opts.basePath : `${opts.basePath}${sep}${param}=${n}`,
|
|
536
|
+
current: n === current
|
|
537
|
+
});
|
|
538
|
+
const window = Math.max(0, Math.floor(opts.window ?? 1));
|
|
539
|
+
const shown = /* @__PURE__ */ new Set([1, pageCount]);
|
|
540
|
+
for (let n = current - window; n <= current + window; n++) {
|
|
541
|
+
if (n >= 1 && n <= pageCount) shown.add(n);
|
|
542
|
+
}
|
|
543
|
+
const pages = [];
|
|
544
|
+
let last = 0;
|
|
545
|
+
for (const n of [...shown].sort((a, b) => a - b)) {
|
|
546
|
+
if (n - last === 2) pages.push({ kind: "page", ...link(n - 1) });
|
|
547
|
+
else if (n - last > 2) pages.push({ kind: "gap", key: `gap-${last}` });
|
|
548
|
+
pages.push({ kind: "page", ...link(n) });
|
|
549
|
+
last = n;
|
|
550
|
+
}
|
|
551
|
+
const count = page.items.length;
|
|
552
|
+
const from = count > 0 ? page.offset + 1 : 0;
|
|
553
|
+
return {
|
|
554
|
+
items: page.items,
|
|
555
|
+
total: page.total,
|
|
556
|
+
page: current,
|
|
557
|
+
pageCount,
|
|
558
|
+
pageSize,
|
|
559
|
+
from,
|
|
560
|
+
to: count > 0 ? page.offset + count : 0,
|
|
561
|
+
empty: page.total === 0,
|
|
562
|
+
outOfRange: page.total > 0 && count === 0,
|
|
563
|
+
prev: current > 1 ? link(Math.min(current - 1, pageCount)) : null,
|
|
564
|
+
next: current < pageCount ? link(current + 1) : null,
|
|
565
|
+
pages
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
function toRecordViewModel(record, opts = {}) {
|
|
569
|
+
return {
|
|
570
|
+
title: record.title ?? null,
|
|
571
|
+
publicId: record.publicId,
|
|
572
|
+
slug: record.slug,
|
|
573
|
+
properties: Object.entries(record.properties ?? {}).map(([key, value]) => ({
|
|
574
|
+
key,
|
|
575
|
+
value: formatPublicValue(value, opts)
|
|
576
|
+
})),
|
|
577
|
+
blocks: record.body ? parseSafeMarkdown(record.body.content) : [],
|
|
578
|
+
publishedOn: record.publishedOn
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// src/public/share-model.ts
|
|
583
|
+
var UNAVAILABLE_TITLE = "This link isn't available";
|
|
584
|
+
function isRetryable(error) {
|
|
585
|
+
return error === "network" || error === "rate_limited" || error === "server";
|
|
586
|
+
}
|
|
587
|
+
function toSharePageModel(result, opts = {}) {
|
|
588
|
+
if (!result.ok) return { kind: "unavailable", retryable: isRetryable(result.error) };
|
|
589
|
+
const share = result.data;
|
|
590
|
+
return {
|
|
591
|
+
kind: "share",
|
|
592
|
+
title: share.title ?? null,
|
|
593
|
+
properties: Object.entries(share.properties ?? {}).map(([key, value]) => ({
|
|
594
|
+
key,
|
|
595
|
+
value: formatPublicValue(value, opts)
|
|
596
|
+
})),
|
|
597
|
+
blocks: share.body ? parseSafeMarkdown(share.body.content) : [],
|
|
598
|
+
publishedOn: share.publishedOn
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
602
|
+
0 && (module.exports = {
|
|
603
|
+
SITE_MAX_LIMIT,
|
|
604
|
+
SITE_MAX_OFFSET,
|
|
605
|
+
SITE_MAX_SORT_KEYS,
|
|
606
|
+
SITE_PUBLISHED_AT_SORT,
|
|
607
|
+
UNAVAILABLE_TITLE,
|
|
608
|
+
captchaSatisfied,
|
|
609
|
+
createPublicClient,
|
|
610
|
+
formatPublicValue,
|
|
611
|
+
initialValues,
|
|
612
|
+
loadErrorMessage,
|
|
613
|
+
pageQuery,
|
|
614
|
+
parseInlines,
|
|
615
|
+
parsePageParam,
|
|
616
|
+
parseSafeMarkdown,
|
|
617
|
+
safeHref,
|
|
618
|
+
submitFailure,
|
|
619
|
+
submitWaitRemaining,
|
|
620
|
+
toCollectionPageModel,
|
|
621
|
+
toFieldModels,
|
|
622
|
+
toRecordViewModel,
|
|
623
|
+
toSharePageModel,
|
|
624
|
+
toSubmission,
|
|
625
|
+
validate
|
|
626
|
+
});
|
|
627
|
+
//# sourceMappingURL=public.cjs.map
|