@ai-matrx/records 0.87.32 → 0.92.1
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/CHANGELOG.md +100 -0
- package/README.md +115 -0
- package/dist/{client-E0VQ0NPO.d.ts → client-Cn3odIIB.d.cts} +10 -5
- package/dist/{client-C3ZZNfGH.d.cts → client-Dzd8gtAo.d.ts} +10 -5
- package/dist/core/index.cjs +28 -10
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +23 -12
- package/dist/core/index.d.ts +23 -12
- package/dist/core/index.js +28 -10
- package/dist/core/index.js.map +1 -1
- package/dist/data/index.cjs +22 -10
- package/dist/data/index.cjs.map +1 -1
- package/dist/data/index.d.cts +12 -142
- package/dist/data/index.d.ts +12 -142
- package/dist/data/index.js +22 -10
- package/dist/data/index.js.map +1 -1
- package/dist/{declareTable-BK3zufr7.d.cts → declareTable-CrL6AZKB.d.cts} +4 -4
- package/dist/{declareTable-B12XybRp.d.ts → declareTable-ZRW5EsTQ.d.ts} +4 -4
- package/dist/entity/index.cjs +984 -0
- package/dist/entity/index.cjs.map +1 -0
- package/dist/entity/index.d.cts +36 -0
- package/dist/entity/index.d.ts +36 -0
- package/dist/entity/index.js +964 -0
- package/dist/entity/index.js.map +1 -0
- package/dist/entity/react.cjs +1024 -0
- package/dist/entity/react.cjs.map +1 -0
- package/dist/entity/react.d.cts +511 -0
- package/dist/entity/react.d.ts +511 -0
- package/dist/entity/react.js +1015 -0
- package/dist/entity/react.js.map +1 -0
- package/dist/{field-BanTVGK2.d.cts → field-CCY1rG9l.d.cts} +1 -1
- package/dist/{field-BPABOJ2Y.d.ts → field-enufS5ry.d.ts} +1 -1
- package/dist/forms/index.cjs +984 -0
- package/dist/forms/index.cjs.map +1 -0
- package/dist/forms/index.d.cts +333 -0
- package/dist/forms/index.d.ts +333 -0
- package/dist/forms/index.js +961 -0
- package/dist/forms/index.js.map +1 -0
- package/dist/index.cjs +10 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -8
- package/dist/index.d.ts +11 -8
- package/dist/index.js +10 -1
- package/dist/index.js.map +1 -1
- package/dist/memory/index.d.cts +7 -6
- package/dist/memory/index.d.ts +7 -6
- package/dist/port-C54ykAOv.d.ts +301 -0
- package/dist/port-Drioh9al.d.cts +301 -0
- package/dist/portal-DRdmZ-xv.d.cts +331 -0
- package/dist/portal-or_f7BkT.d.ts +331 -0
- package/dist/{ports-TMWX-t9l.d.ts → ports-BeOgjwYl.d.ts} +2 -2
- package/dist/{ports-D8-iuY1P.d.cts → ports-s0D2jvqn.d.cts} +2 -2
- package/dist/react/index.cjs +58 -526
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +16 -1
- package/dist/react/index.d.ts +16 -1
- package/dist/react/index.js +55 -530
- package/dist/react/index.js.map +1 -1
- package/dist/realtime/index.d.cts +2 -2
- package/dist/realtime/index.d.ts +2 -2
- package/dist/{record-DpB2QgAZ.d.cts → record-BwCf9wKW.d.cts} +7 -1
- package/dist/{record-DpB2QgAZ.d.ts → record-BwCf9wKW.d.ts} +7 -1
- package/dist/relation-8vAWCa67.d.cts +230 -0
- package/dist/relation-eLMgv_dV.d.ts +230 -0
- package/dist/rule-D9yNiorf.d.ts +250 -0
- package/dist/rule-wHI5B0Ka.d.cts +250 -0
- package/dist/search/index.cjs +283 -0
- package/dist/search/index.cjs.map +1 -0
- package/dist/search/index.d.cts +44 -0
- package/dist/search/index.d.ts +44 -0
- package/dist/search/index.js +260 -0
- package/dist/search/index.js.map +1 -0
- package/dist/setup/index.cjs +6854 -0
- package/dist/setup/index.cjs.map +1 -0
- package/dist/setup/index.d.cts +87 -0
- package/dist/setup/index.d.ts +87 -0
- package/dist/setup/index.js +6829 -0
- package/dist/setup/index.js.map +1 -0
- package/dist/{subscription-D7KQBHXN.d.ts → subscription-BynsG1K6.d.ts} +3 -249
- package/dist/{subscription-BD9DnJxX.d.cts → subscription-CdoI-UaB.d.cts} +3 -249
- package/dist/{table-kind-BZu8mtND.d.cts → table-kind-DzctQ7dO.d.cts} +3 -558
- package/dist/{table-kind-xKxTHtrV.d.ts → table-kind-i3lZJa3f.d.ts} +3 -558
- package/dist/templates/index.d.cts +10 -9
- package/dist/templates/index.d.ts +10 -9
- package/dist/typed-table/index.cjs +22 -10
- package/dist/typed-table/index.cjs.map +1 -1
- package/dist/typed-table/index.d.cts +10 -7
- package/dist/typed-table/index.d.ts +10 -7
- package/dist/typed-table/index.js +22 -10
- package/dist/typed-table/index.js.map +1 -1
- package/dist/types-BKYEeFt4.d.cts +139 -0
- package/dist/{types-Dv61jStB.d.ts → types-BvL_oKFl.d.ts} +2 -2
- package/dist/{types-CLP5H4Wk.d.cts → types-D8qdG3Lg.d.cts} +2 -2
- package/dist/{types-C2uspRtf.d.cts → types-DAFP6XFe.d.cts} +2 -2
- package/dist/{types-DmPkWQic.d.ts → types-DYzXhD7j.d.ts} +2 -2
- package/dist/types-cpKY7Pjb.d.ts +139 -0
- package/dist/use-cases/index.d.cts +5 -5
- package/dist/use-cases/index.d.ts +5 -5
- package/dist/{validate-CWVToe1T.d.ts → validate-DbvAParj.d.ts} +1 -1
- package/dist/{validate-DEB_mn4Y.d.cts → validate-DfNMxjmU.d.cts} +1 -1
- package/dist/versions/index.cjs +375 -0
- package/dist/versions/index.cjs.map +1 -0
- package/dist/versions/index.d.cts +160 -0
- package/dist/versions/index.d.ts +160 -0
- package/dist/versions/index.js +353 -0
- package/dist/versions/index.js.map +1 -0
- package/package.json +62 -2
|
@@ -0,0 +1,984 @@
|
|
|
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/forms/index.ts
|
|
21
|
+
var forms_exports = {};
|
|
22
|
+
__export(forms_exports, {
|
|
23
|
+
FORM_EMBEDS: () => FORM_EMBEDS,
|
|
24
|
+
FORM_FLOWS: () => FORM_FLOWS,
|
|
25
|
+
FORM_STATE_WORDS: () => FORM_STATE_WORDS,
|
|
26
|
+
answerRefusal: () => answerRefusal,
|
|
27
|
+
endingDocument: () => endingDocument,
|
|
28
|
+
endingsFromDocument: () => endingsFromDocument,
|
|
29
|
+
formDeclareArgs: () => formDeclareArgs,
|
|
30
|
+
formEmbedSnippet: () => formEmbedSnippet,
|
|
31
|
+
formFromSummary: () => formFromSummary,
|
|
32
|
+
formLookDocument: () => formLookDocument,
|
|
33
|
+
formLookFromDocument: () => formLookFromDocument,
|
|
34
|
+
formPresentation: () => formPresentation,
|
|
35
|
+
formRoutes: () => formRoutes,
|
|
36
|
+
hiddenFromLink: () => hiddenFromLink,
|
|
37
|
+
notifyFromDocument: () => notifyFromDocument,
|
|
38
|
+
pipeAnswers: () => pipeAnswers,
|
|
39
|
+
publicReadOf: () => publicReadOf,
|
|
40
|
+
submissionStamp: () => submissionStamp,
|
|
41
|
+
thankYouDocument: () => thankYouDocument,
|
|
42
|
+
thankYouFromDocument: () => thankYouFromDocument,
|
|
43
|
+
themeDocument: () => themeDocument,
|
|
44
|
+
themeFromDocument: () => themeFromDocument,
|
|
45
|
+
typedAnswersFor: () => typedAnswersFor,
|
|
46
|
+
welcomeDocument: () => welcomeDocument,
|
|
47
|
+
welcomeFromDocument: () => welcomeFromDocument
|
|
48
|
+
});
|
|
49
|
+
module.exports = __toCommonJS(forms_exports);
|
|
50
|
+
|
|
51
|
+
// src/portal.ts
|
|
52
|
+
var PORTAL_ACCENTS = ["slate", "green", "amber", "red", "blue", "violet", "teal"];
|
|
53
|
+
function isPortalAccent(word) {
|
|
54
|
+
return typeof word === "string" && PORTAL_ACCENTS.includes(word);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/forms/forms.ts
|
|
58
|
+
var FORM_STATE_WORDS = {
|
|
59
|
+
draft: "Not published \u2014 the link does not answer yet.",
|
|
60
|
+
open: "Open \u2014 anyone with the link can answer it.",
|
|
61
|
+
closed: "Closed \u2014 it keeps its answers and takes no more.",
|
|
62
|
+
full: "Full \u2014 it has all the answers it was set up to take."
|
|
63
|
+
};
|
|
64
|
+
var FORM_FLOWS = ["one-at-a-time", "single-page"];
|
|
65
|
+
function formPresentation(spec) {
|
|
66
|
+
return {
|
|
67
|
+
intro: spec.intro ?? null,
|
|
68
|
+
flow: spec.flow ?? "one-at-a-time",
|
|
69
|
+
thank_you: thankYouDocument(spec.thankYou ?? null),
|
|
70
|
+
submit_label: spec.submitLabel ?? null,
|
|
71
|
+
look: formLookDocument(spec.look ?? null),
|
|
72
|
+
welcome: welcomeDocument(spec.welcome ?? null),
|
|
73
|
+
endings: (spec.endings ?? []).map(endingDocument),
|
|
74
|
+
hidden_fields: spec.hiddenFields ?? [],
|
|
75
|
+
theme: themeDocument(spec.theme ?? null),
|
|
76
|
+
...typeof spec.showOwnerHeader === "boolean" ? { show_owner_header: spec.showOwnerHeader } : {},
|
|
77
|
+
...typeof spec.autoAdvance === "boolean" ? { auto_advance: spec.autoAdvance } : {},
|
|
78
|
+
...spec.notify && (spec.notify.webhookUrl || spec.notify.webhookAutomationId || spec.notify.emailRespondent) ? {
|
|
79
|
+
notify: strip({
|
|
80
|
+
webhook_url: text(spec.notify.webhookUrl),
|
|
81
|
+
webhook_automation_id: text(spec.notify.webhookAutomationId),
|
|
82
|
+
email_respondent: spec.notify.emailRespondent ? true : null,
|
|
83
|
+
respondent_field: spec.notify.emailRespondent ? text(spec.notify.respondentField) : null
|
|
84
|
+
})
|
|
85
|
+
} : {}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function notifyFromDocument(raw) {
|
|
89
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
90
|
+
const d = raw;
|
|
91
|
+
const n = {
|
|
92
|
+
webhookUrl: text(d["webhook_url"]),
|
|
93
|
+
webhookAutomationId: text(d["webhook_automation_id"]),
|
|
94
|
+
emailRespondent: d["email_respondent"] === true ? true : null,
|
|
95
|
+
respondentField: text(d["respondent_field"])
|
|
96
|
+
};
|
|
97
|
+
return n.webhookUrl || n.webhookAutomationId || n.emailRespondent ? n : null;
|
|
98
|
+
}
|
|
99
|
+
var bool = (v) => typeof v === "boolean" ? v : null;
|
|
100
|
+
function answerRefusal(kind, required, value) {
|
|
101
|
+
const empty = value === void 0 || value === null || value === "" || Array.isArray(value) && value.length === 0;
|
|
102
|
+
if (empty) return required ? "This field is required" : null;
|
|
103
|
+
if (typeof value !== "string") return null;
|
|
104
|
+
const v = value.trim();
|
|
105
|
+
if (kind === "email" && !/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(v)) return "Enter a valid email";
|
|
106
|
+
if (kind === "phone" && (v.replace(/\D/g, "").length < 7 || /[^\d\s()+.\-x]/i.test(v))) return "Enter a valid phone number";
|
|
107
|
+
if (kind === "url" && !/^(https?:\/\/)?[^\s.\/]+(\.[^\s.\/]+)+(\/\S*)?$/i.test(v)) return "Enter a valid link";
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
function pipeAnswers(text2, answers, questions, labelOf) {
|
|
111
|
+
if (!text2.includes("{{")) return text2;
|
|
112
|
+
return text2.replace(/\{\{\s*([^{}]+?)\s*\}\}/g, (_all, name) => {
|
|
113
|
+
const wanted = name.trim().toLowerCase();
|
|
114
|
+
const q = questions.find((x) => x.key.toLowerCase() === wanted) ?? questions.find((x) => (x.ask ?? "").trim().toLowerCase() === wanted);
|
|
115
|
+
if (!q) return "";
|
|
116
|
+
const value = answers[q.key];
|
|
117
|
+
if (value === void 0 || value === null) return "";
|
|
118
|
+
const one = (v) => labelOf?.(q.key, v) ?? String(v);
|
|
119
|
+
return Array.isArray(value) ? value.map(one).join(", ") : one(value);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
var text = (v) => typeof v === "string" && v.trim() !== "" ? v.trim() : null;
|
|
123
|
+
var num = (v) => typeof v === "number" && Number.isFinite(v) ? v : null;
|
|
124
|
+
var strip = (doc) => Object.fromEntries(Object.entries(doc).filter(([, v]) => v !== null && v !== void 0));
|
|
125
|
+
function themeDocument(theme) {
|
|
126
|
+
if (!theme) return {};
|
|
127
|
+
return strip({
|
|
128
|
+
accent: theme.accent ?? null,
|
|
129
|
+
align: theme.align ?? null,
|
|
130
|
+
font: text(theme.font),
|
|
131
|
+
button: text(theme.button),
|
|
132
|
+
background: text(theme.background),
|
|
133
|
+
background_file_id: text(theme.backgroundFileId)
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
function themeFromDocument(raw) {
|
|
137
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
138
|
+
const d = raw;
|
|
139
|
+
const align = d["align"] === "center" ? "center" : d["align"] === "left" ? "left" : null;
|
|
140
|
+
return {
|
|
141
|
+
accent: text(d["accent"]),
|
|
142
|
+
align,
|
|
143
|
+
font: text(d["font"]),
|
|
144
|
+
button: text(d["button"]),
|
|
145
|
+
background: text(d["background"]),
|
|
146
|
+
backgroundFileId: text(d["background_file_id"]),
|
|
147
|
+
backgroundUrl: text(d["background_url"])
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
function welcomeDocument(welcome) {
|
|
151
|
+
if (!welcome) return null;
|
|
152
|
+
const doc = strip({
|
|
153
|
+
title: text(welcome.title),
|
|
154
|
+
body: text(welcome.body),
|
|
155
|
+
picture_file_id: text(welcome.pictureFileId),
|
|
156
|
+
button_label: text(welcome.buttonLabel)
|
|
157
|
+
});
|
|
158
|
+
return Object.keys(doc).length > 0 ? doc : null;
|
|
159
|
+
}
|
|
160
|
+
function welcomeFromDocument(raw) {
|
|
161
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
162
|
+
const d = raw;
|
|
163
|
+
const w = {
|
|
164
|
+
title: text(d["title"]),
|
|
165
|
+
body: text(d["body"]),
|
|
166
|
+
pictureFileId: text(d["picture_file_id"]),
|
|
167
|
+
pictureUrl: text(d["picture_url"]),
|
|
168
|
+
buttonLabel: text(d["button_label"])
|
|
169
|
+
};
|
|
170
|
+
return w.title || w.body || w.pictureFileId || w.pictureUrl || w.buttonLabel ? w : null;
|
|
171
|
+
}
|
|
172
|
+
function endingDocument(ending) {
|
|
173
|
+
return strip({
|
|
174
|
+
id: ending.id,
|
|
175
|
+
title: text(ending.title),
|
|
176
|
+
body: text(ending.body),
|
|
177
|
+
picture_file_id: text(ending.pictureFileId),
|
|
178
|
+
button_label: text(ending.buttonLabel),
|
|
179
|
+
redirect_url: text(ending.redirectUrl),
|
|
180
|
+
when: ending.when ?? null,
|
|
181
|
+
score_min: num(ending.scoreMin),
|
|
182
|
+
score_max: num(ending.scoreMax)
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
function endingsFromDocument(raw) {
|
|
186
|
+
if (!Array.isArray(raw)) return [];
|
|
187
|
+
return raw.filter((e) => !!e && typeof e === "object" && typeof e["id"] === "string").map((d) => ({
|
|
188
|
+
id: String(d["id"]),
|
|
189
|
+
title: text(d["title"]),
|
|
190
|
+
body: text(d["body"]),
|
|
191
|
+
pictureFileId: text(d["picture_file_id"]),
|
|
192
|
+
pictureUrl: text(d["picture_url"]),
|
|
193
|
+
buttonLabel: text(d["button_label"]),
|
|
194
|
+
redirectUrl: text(d["redirect_url"]),
|
|
195
|
+
when: d["when"] ?? null,
|
|
196
|
+
scoreMin: num(d["score_min"]),
|
|
197
|
+
scoreMax: num(d["score_max"])
|
|
198
|
+
}));
|
|
199
|
+
}
|
|
200
|
+
function hiddenFromLink(hiddenFields, params) {
|
|
201
|
+
const out = {};
|
|
202
|
+
for (const key of hiddenFields ?? []) {
|
|
203
|
+
const raw = params[key];
|
|
204
|
+
const one = Array.isArray(raw) ? raw[raw.length - 1] : raw;
|
|
205
|
+
if (typeof one === "string" && one.trim() !== "") out[key] = one.trim().slice(0, 500);
|
|
206
|
+
}
|
|
207
|
+
return out;
|
|
208
|
+
}
|
|
209
|
+
function formRoutes(form) {
|
|
210
|
+
return (form.questions ?? []).some((q) => q.showIf || q.jumps && q.jumps.length > 0 || q.points) || (form.endings ?? []).length > 0;
|
|
211
|
+
}
|
|
212
|
+
var FORM_EMBEDS = ["inline", "popup", "slider"];
|
|
213
|
+
function formEmbedSnippet(kind, formUrl, options) {
|
|
214
|
+
const url = new URL(formUrl);
|
|
215
|
+
url.searchParams.set("embed", "1");
|
|
216
|
+
const src = url.toString();
|
|
217
|
+
const label = (options?.label ?? "").trim() || "Open the form";
|
|
218
|
+
const esc = (v) => v.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<");
|
|
219
|
+
if (kind === "inline") {
|
|
220
|
+
const height = options?.height && options.height > 0 ? Math.round(options.height) : 640;
|
|
221
|
+
return `<iframe src="${esc(src)}" title="${esc(label)}" style="width:100%;height:${height}px;border:0" loading="lazy" allow="clipboard-write"></iframe>`;
|
|
222
|
+
}
|
|
223
|
+
const script = `${url.origin}/embed/form.js`;
|
|
224
|
+
return `<button type="button" data-matrx-form="${esc(src)}" data-matrx-form-mode="${kind}">${esc(label)}</button>
|
|
225
|
+
<script src="${esc(script)}" async><\/script>`;
|
|
226
|
+
}
|
|
227
|
+
function formLookDocument(look) {
|
|
228
|
+
if (!look) return null;
|
|
229
|
+
const out = {};
|
|
230
|
+
if (look.accent) out["accent"] = look.accent;
|
|
231
|
+
if (look.logoFileId) out["logo_file_id"] = look.logoFileId;
|
|
232
|
+
if (look.coverFileId) out["cover_file_id"] = look.coverFileId;
|
|
233
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
234
|
+
}
|
|
235
|
+
function formLookFromDocument(raw) {
|
|
236
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
237
|
+
const doc = raw;
|
|
238
|
+
const id = (v) => typeof v === "string" && v.trim() !== "" ? v : null;
|
|
239
|
+
const look = {
|
|
240
|
+
accent: isPortalAccent(doc["accent"]) ? doc["accent"] : null,
|
|
241
|
+
logoFileId: id(doc["logo_file_id"]),
|
|
242
|
+
coverFileId: id(doc["cover_file_id"])
|
|
243
|
+
};
|
|
244
|
+
return look.accent || look.logoFileId || look.coverFileId ? look : null;
|
|
245
|
+
}
|
|
246
|
+
function thankYouDocument(thankYou) {
|
|
247
|
+
if (!thankYou) return null;
|
|
248
|
+
const title = thankYou.title?.trim() || null;
|
|
249
|
+
const body = thankYou.body?.trim() || null;
|
|
250
|
+
const redirect = thankYou.redirectUrl?.trim() || null;
|
|
251
|
+
if (!title && !body && !redirect) return null;
|
|
252
|
+
return { title, body, ...redirect ? { redirect_url: redirect } : {} };
|
|
253
|
+
}
|
|
254
|
+
function thankYouFromDocument(raw) {
|
|
255
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
256
|
+
const doc = raw;
|
|
257
|
+
const text2 = (v) => typeof v === "string" && v.trim() !== "" ? v : null;
|
|
258
|
+
const thankYou = {
|
|
259
|
+
title: text2(doc["title"]),
|
|
260
|
+
body: text2(doc["body"]),
|
|
261
|
+
redirectUrl: text2(doc["redirect_url"] ?? doc["redirectUrl"])
|
|
262
|
+
};
|
|
263
|
+
return thankYou.title || thankYou.body || thankYou.redirectUrl ? thankYou : null;
|
|
264
|
+
}
|
|
265
|
+
function formDeclareArgs(form) {
|
|
266
|
+
const saved = form;
|
|
267
|
+
return {
|
|
268
|
+
table_id: form.subject,
|
|
269
|
+
title: form.name,
|
|
270
|
+
questions: form.questions ?? [],
|
|
271
|
+
presentation: formPresentation(form),
|
|
272
|
+
submission_cap: saved.submissionCap ?? null,
|
|
273
|
+
quarantine_rule_id: saved.quarantineRuleId ?? null,
|
|
274
|
+
notify_rule_id: saved.notifyRuleId ?? null,
|
|
275
|
+
form_id: saved.id ?? null,
|
|
276
|
+
slug: saved.slug ?? null
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
function formFromSummary(summary) {
|
|
280
|
+
const presentation = summary.presentation ?? {};
|
|
281
|
+
const flow = String(presentation["flow"] ?? "one-at-a-time");
|
|
282
|
+
return {
|
|
283
|
+
id: summary.form_id,
|
|
284
|
+
name: summary.title ?? summary.slug,
|
|
285
|
+
subject: summary.table_id,
|
|
286
|
+
intro: presentation["intro"] ?? null,
|
|
287
|
+
questions: readQuestions(presentation["questions"]),
|
|
288
|
+
flow: FORM_FLOWS.includes(flow) ? flow : "one-at-a-time",
|
|
289
|
+
theme: themeFromDocument(presentation["theme"]),
|
|
290
|
+
welcome: welcomeFromDocument(presentation["welcome"]),
|
|
291
|
+
endings: endingsFromDocument(presentation["endings"]),
|
|
292
|
+
hiddenFields: Array.isArray(presentation["hidden_fields"]) ? presentation["hidden_fields"].filter((k) => typeof k === "string") : [],
|
|
293
|
+
thankYou: thankYouFromDocument(presentation["thank_you"]),
|
|
294
|
+
look: formLookFromDocument(presentation["look"]),
|
|
295
|
+
submitLabel: presentation["submit_label"] ?? null,
|
|
296
|
+
showOwnerHeader: bool(presentation["show_owner_header"]),
|
|
297
|
+
autoAdvance: bool(presentation["auto_advance"]),
|
|
298
|
+
notify: notifyFromDocument(presentation["notify"]),
|
|
299
|
+
// A form is public when the store says it is OPEN. A screen never decides
|
|
300
|
+
// this from a flag of its own: the link answers or it does not.
|
|
301
|
+
isPublic: summary.state === "open",
|
|
302
|
+
state: summary.state,
|
|
303
|
+
slug: summary.slug,
|
|
304
|
+
submissionCap: summary.submission_cap,
|
|
305
|
+
quarantineRuleId: summary.quarantine_rule_id ?? null,
|
|
306
|
+
notifyRuleId: summary.notify_rule_id ?? null
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
function readQuestions(raw) {
|
|
310
|
+
if (!Array.isArray(raw)) return [];
|
|
311
|
+
const out = [];
|
|
312
|
+
for (const entry of raw) {
|
|
313
|
+
if (!entry || typeof entry !== "object") continue;
|
|
314
|
+
const q = entry;
|
|
315
|
+
const field = String(q["field"] ?? q["key"] ?? "").trim();
|
|
316
|
+
if (field === "") continue;
|
|
317
|
+
out.push({
|
|
318
|
+
field,
|
|
319
|
+
ask: q["ask"] ?? null,
|
|
320
|
+
help: q["help"] ?? null,
|
|
321
|
+
required: q["required"] ?? null,
|
|
322
|
+
showIf: q["showIf"] ?? q["show_if"] ?? null,
|
|
323
|
+
jumps: Array.isArray(q["jumps"]) ? q["jumps"] : null,
|
|
324
|
+
points: q["points"] && typeof q["points"] === "object" && !Array.isArray(q["points"]) ? q["points"] : null
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
return out;
|
|
328
|
+
}
|
|
329
|
+
function submissionStamp(args) {
|
|
330
|
+
return {
|
|
331
|
+
_source: {
|
|
332
|
+
via: "form",
|
|
333
|
+
form_id: args.formId,
|
|
334
|
+
form_name: args.formName,
|
|
335
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
336
|
+
actor: args.actor,
|
|
337
|
+
...args.onBehalfOf ? { on_behalf_of: args.onBehalfOf } : {}
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// src/forms/publicBinding.ts
|
|
343
|
+
function publicReadOf(binding) {
|
|
344
|
+
if (binding.kind === "table" && binding.table_id) return { read: "table", tableId: binding.table_id };
|
|
345
|
+
if (binding.kind === "view" && binding.table_id) {
|
|
346
|
+
return { read: "view", tableId: binding.table_id, viewId: binding.resource_id };
|
|
347
|
+
}
|
|
348
|
+
if (binding.kind === "record") return { read: "record", recordId: binding.resource_id };
|
|
349
|
+
return { read: "unknown" };
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// src/button.ts
|
|
353
|
+
function isButtonField(field) {
|
|
354
|
+
return field?.type === "text" && String(field.format ?? "") === "button";
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// src/newerKinds.ts
|
|
358
|
+
var import_format = require("@ai-matrx/kit/format");
|
|
359
|
+
function newerKindOf(field) {
|
|
360
|
+
const format = String(field.format ?? "");
|
|
361
|
+
const config = field.config ?? {};
|
|
362
|
+
switch (field.type) {
|
|
363
|
+
case "range":
|
|
364
|
+
if (format === "rating") return "rating";
|
|
365
|
+
if (format === "duration") return "duration";
|
|
366
|
+
return null;
|
|
367
|
+
case "text":
|
|
368
|
+
if (format === "address") return "address";
|
|
369
|
+
if (format === "barcode") return "barcode";
|
|
370
|
+
if (format === "rich_text") return "rich_text";
|
|
371
|
+
return null;
|
|
372
|
+
case "list":
|
|
373
|
+
return format === "status" ? "status" : null;
|
|
374
|
+
case "formula":
|
|
375
|
+
if (format === "count" && config["agg"] === "count") return "count";
|
|
376
|
+
if (config["system"] === "created_by") return "created_by";
|
|
377
|
+
if (config["system"] === "modified_by") return "modified_by";
|
|
378
|
+
return null;
|
|
379
|
+
default:
|
|
380
|
+
return null;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
function ratingMaxOf(field) {
|
|
384
|
+
const rules = field.rules ?? [];
|
|
385
|
+
const max = rules.find((r) => r?.kind === "max")?.value;
|
|
386
|
+
const n = typeof max === "number" ? max : typeof max === "string" ? Number(max) : NaN;
|
|
387
|
+
return Number.isInteger(n) && n >= 1 && n <= 10 ? n : 5;
|
|
388
|
+
}
|
|
389
|
+
function readTypedDuration(text2) {
|
|
390
|
+
const t = text2.trim().toLowerCase();
|
|
391
|
+
if (t === "") return null;
|
|
392
|
+
const clock = /^(\d+):([0-5]?\d)(?::([0-5]?\d))?$/.exec(t);
|
|
393
|
+
if (clock) return Number(clock[1]) * 3600 + Number(clock[2]) * 60 + Number(clock[3] ?? 0);
|
|
394
|
+
if (/^\d+(\.\d+)?$/.test(t)) return Math.round(Number(t) * 60);
|
|
395
|
+
const unitRe = /(\d+(?:\.\d+)?)\s*(h|hr|hrs|hour|hours|m|min|mins|minute|minutes|s|sec|secs|second|seconds)\b/g;
|
|
396
|
+
let total = 0;
|
|
397
|
+
let matched = "";
|
|
398
|
+
for (const m of t.matchAll(unitRe)) {
|
|
399
|
+
const n = Number(m[1]);
|
|
400
|
+
const u = m[2];
|
|
401
|
+
total += u.startsWith("h") ? n * 3600 : u.startsWith("m") ? n * 60 : n;
|
|
402
|
+
matched += m[0];
|
|
403
|
+
}
|
|
404
|
+
if (matched === "" || t.replace(unitRe, "").replace(/[\s,and]+/g, "") !== "") return null;
|
|
405
|
+
return Math.round(total);
|
|
406
|
+
}
|
|
407
|
+
function addressFromText(text2) {
|
|
408
|
+
const t = text2.trim();
|
|
409
|
+
if (t === "") return null;
|
|
410
|
+
const bits = t.split(",").map((b) => b.trim()).filter(Boolean);
|
|
411
|
+
if (bits.length >= 3) {
|
|
412
|
+
const [street, city, regionPostal, ...rest] = bits;
|
|
413
|
+
const rp = /^(.*?)\s*([A-Za-z0-9-]*\d[A-Za-z0-9 -]*)?$/.exec(regionPostal ?? "");
|
|
414
|
+
const out = { street, city };
|
|
415
|
+
if (rp?.[1]) out.region = rp[1];
|
|
416
|
+
if (rp?.[2]) out.postal_code = rp[2].trim();
|
|
417
|
+
if (rest.length > 0) out.country = rest.join(", ");
|
|
418
|
+
return out;
|
|
419
|
+
}
|
|
420
|
+
return { street: t };
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// src/typedDate.ts
|
|
424
|
+
var MONTHS = {
|
|
425
|
+
jan: 1,
|
|
426
|
+
january: 1,
|
|
427
|
+
feb: 2,
|
|
428
|
+
february: 2,
|
|
429
|
+
mar: 3,
|
|
430
|
+
march: 3,
|
|
431
|
+
apr: 4,
|
|
432
|
+
april: 4,
|
|
433
|
+
may: 5,
|
|
434
|
+
jun: 6,
|
|
435
|
+
june: 6,
|
|
436
|
+
jul: 7,
|
|
437
|
+
july: 7,
|
|
438
|
+
aug: 8,
|
|
439
|
+
august: 8,
|
|
440
|
+
sep: 9,
|
|
441
|
+
sept: 9,
|
|
442
|
+
september: 9,
|
|
443
|
+
oct: 10,
|
|
444
|
+
october: 10,
|
|
445
|
+
nov: 11,
|
|
446
|
+
november: 11,
|
|
447
|
+
dec: 12,
|
|
448
|
+
december: 12
|
|
449
|
+
};
|
|
450
|
+
var WEEKDAYS = /\b(mon|tue|tues|wed|thu|thur|thurs|fri|sat|sun)(day|nesday|sday|urday)?\b\.?,?/gi;
|
|
451
|
+
function localeDateOrder(locale) {
|
|
452
|
+
try {
|
|
453
|
+
const parts = new Intl.DateTimeFormat(locale, { year: "numeric", month: "2-digit", day: "2-digit" }).formatToParts(
|
|
454
|
+
new Date(2026, 10, 22)
|
|
455
|
+
);
|
|
456
|
+
const order = parts.filter((p) => p.type === "year" || p.type === "month" || p.type === "day").map((p) => p.type.charAt(0)).join("");
|
|
457
|
+
if (order === "mdy" || order === "dmy" || order === "ymd") return order;
|
|
458
|
+
} catch {
|
|
459
|
+
}
|
|
460
|
+
return "mdy";
|
|
461
|
+
}
|
|
462
|
+
function pad(n, width = 2) {
|
|
463
|
+
return String(n).padStart(width, "0");
|
|
464
|
+
}
|
|
465
|
+
function daysIn(year, month) {
|
|
466
|
+
return new Date(Date.UTC(year, month, 0)).getUTCDate();
|
|
467
|
+
}
|
|
468
|
+
var HOW = "Write it as 2026-10-03, or with the year in full the way your spreadsheet writes dates.";
|
|
469
|
+
function twoDigitYear(yy) {
|
|
470
|
+
return yy < 30 ? 2e3 + yy : 1900 + yy;
|
|
471
|
+
}
|
|
472
|
+
function checkDay(year, month, day, raw) {
|
|
473
|
+
if (year < 1e3 || year > 9999) return `\u201C${raw}\u201D has no four-digit year. ${HOW}`;
|
|
474
|
+
if (month < 1 || month > 12) return `\u201C${raw}\u201D names no month (month ${month}). ${HOW}`;
|
|
475
|
+
if (day < 1 || day > daysIn(year, month)) return `\u201C${raw}\u201D is not a day on the calendar. ${HOW}`;
|
|
476
|
+
return null;
|
|
477
|
+
}
|
|
478
|
+
var TIME_HOW = "Write it like 2:30 PM or 14:30.";
|
|
479
|
+
var MERIDIEM = String.raw`([ap])\.?\s*(?:m\.?)?`;
|
|
480
|
+
var CLOCK = new RegExp(String.raw`^(\d{1,2})(?::(\d{2})(?::(\d{2}))?)?\s*(?:${MERIDIEM})?$`, "i");
|
|
481
|
+
var COMPACT = new RegExp(String.raw`^(\d{3,4})\s*(?:${MERIDIEM})?$`, "i");
|
|
482
|
+
function readTypedTime(raw) {
|
|
483
|
+
const text2 = raw.trim();
|
|
484
|
+
if (text2 === "") return { ok: false, why: "Nothing was typed." };
|
|
485
|
+
let h;
|
|
486
|
+
let m;
|
|
487
|
+
let s = null;
|
|
488
|
+
let meridiem;
|
|
489
|
+
const clock = CLOCK.exec(text2);
|
|
490
|
+
const compact = clock ? null : COMPACT.exec(text2);
|
|
491
|
+
if (clock) {
|
|
492
|
+
h = Number(clock[1]);
|
|
493
|
+
meridiem = clock[4]?.toLowerCase();
|
|
494
|
+
if (clock[2] === void 0 && !meridiem) {
|
|
495
|
+
return { ok: false, why: `\u201C${raw}\u201D could be in the morning or the evening. ${TIME_HOW}` };
|
|
496
|
+
}
|
|
497
|
+
m = clock[2] === void 0 ? 0 : Number(clock[2]);
|
|
498
|
+
if (clock[3] !== void 0) s = Number(clock[3]);
|
|
499
|
+
} else if (compact) {
|
|
500
|
+
const digits = compact[1];
|
|
501
|
+
meridiem = compact[2]?.toLowerCase();
|
|
502
|
+
if (digits.length === 3 && !meridiem) {
|
|
503
|
+
return { ok: false, why: `\u201C${raw}\u201D could be in the morning or the evening. ${TIME_HOW}` };
|
|
504
|
+
}
|
|
505
|
+
h = Number(digits.slice(0, digits.length - 2));
|
|
506
|
+
m = Number(digits.slice(-2));
|
|
507
|
+
} else {
|
|
508
|
+
return { ok: false, why: `\u201C${raw}\u201D is not a time of day. ${TIME_HOW}` };
|
|
509
|
+
}
|
|
510
|
+
if (meridiem) {
|
|
511
|
+
if (h < 1 || h > 12) return { ok: false, why: `\u201C${raw}\u201D is not a time of day. ${TIME_HOW}` };
|
|
512
|
+
if (meridiem === "p" && h !== 12) h += 12;
|
|
513
|
+
if (meridiem === "a" && h === 12) h = 0;
|
|
514
|
+
}
|
|
515
|
+
if (h > 23 || m > 59 || s !== null && s > 59) return { ok: false, why: `\u201C${raw}\u201D is not a time of day. ${TIME_HOW}` };
|
|
516
|
+
return { ok: true, value: [h, m, ...s === null ? [] : [s]].map((n) => pad(n)).join(":") };
|
|
517
|
+
}
|
|
518
|
+
function splitTime(text2) {
|
|
519
|
+
const match = /(?:^|[\sT,]+)(?:at\s+)?(\d{1,4}(?::\d{2}){0,2}\s*(?:[ap]\.?\s*(?:m\.?)?)?)$/i.exec(text2);
|
|
520
|
+
if (!match) return { date: text2, time: null, bad: false };
|
|
521
|
+
const token = match[1];
|
|
522
|
+
if (!/:|[ap]\.?\s*(?:m\.?)?$/i.test(token)) return { date: text2, time: null, bad: false };
|
|
523
|
+
const read = readTypedTime(token);
|
|
524
|
+
if (!read.ok) return { date: text2, time: null, bad: true };
|
|
525
|
+
const [h, m, s] = read.value.split(":").map(Number);
|
|
526
|
+
return { date: text2.slice(0, match.index).replace(/[\s,]+$/, ""), time: { h, m, s: s ?? 0 }, bad: false };
|
|
527
|
+
}
|
|
528
|
+
function readTypedDate(raw, opts = {}) {
|
|
529
|
+
const text2 = raw.trim();
|
|
530
|
+
if (text2 === "") return { ok: false, why: "Nothing was typed." };
|
|
531
|
+
const iso = /^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})(?::(\d{2})(?:\.\d+)?)?(Z|[+-]\d{2}:?\d{2})$/i.exec(text2);
|
|
532
|
+
if (iso) {
|
|
533
|
+
const bad2 = checkDay(Number(iso[1]), Number(iso[2]), Number(iso[3]), raw);
|
|
534
|
+
if (bad2) return { ok: false, why: bad2 };
|
|
535
|
+
const at = new Date(Date.parse(text2));
|
|
536
|
+
if (Number.isNaN(at.getTime())) return { ok: false, why: `\u201C${raw}\u201D is not a moment this can read. ${HOW}` };
|
|
537
|
+
const day2 = `${at.getFullYear()}-${pad(at.getMonth() + 1)}-${pad(at.getDate())}`;
|
|
538
|
+
return { ok: true, day: day2, instant: opts.withTime ? at.toISOString() : null };
|
|
539
|
+
}
|
|
540
|
+
const split = splitTime(text2.replace(/(\d)T(\d)/, "$1 $2"));
|
|
541
|
+
if (split.bad) return { ok: false, why: `\u201C${raw}\u201D has a time of day that is not one. Write it as 14:30 or 2:30 PM.` };
|
|
542
|
+
const dateText = split.date.replace(WEEKDAYS, " ").replace(/\s+/g, " ").trim();
|
|
543
|
+
let year = null;
|
|
544
|
+
let month = null;
|
|
545
|
+
let day = null;
|
|
546
|
+
const numeric = /^(\d{1,4})[/.\-](\d{1,2})[/.\-](\d{1,4})$/.exec(dateText);
|
|
547
|
+
const compact = /^(\d{8}|\d{6})$/.exec(dateText);
|
|
548
|
+
if (compact) {
|
|
549
|
+
const digits = compact[1];
|
|
550
|
+
const order = localeDateOrder(opts.locale);
|
|
551
|
+
const first2 = Number(digits.slice(0, 2));
|
|
552
|
+
const mid2 = Number(digits.slice(2, 4));
|
|
553
|
+
if (digits.length === 6) {
|
|
554
|
+
year = twoDigitYear(Number(digits.slice(4)));
|
|
555
|
+
if (order === "dmy") {
|
|
556
|
+
day = first2;
|
|
557
|
+
month = mid2;
|
|
558
|
+
} else {
|
|
559
|
+
month = first2;
|
|
560
|
+
day = mid2;
|
|
561
|
+
}
|
|
562
|
+
} else {
|
|
563
|
+
const yearLast = Number(digits.slice(4));
|
|
564
|
+
const yearFirst = Number(digits.slice(0, 4));
|
|
565
|
+
const localeReading = () => order === "dmy" ? [yearLast, mid2, first2] : [yearLast, first2, mid2];
|
|
566
|
+
const [ly, lm, ld] = localeReading();
|
|
567
|
+
const isoReading = [yearFirst, Number(digits.slice(4, 6)), Number(digits.slice(6))];
|
|
568
|
+
const fits = (y, m, d) => checkDay(y, m, d, raw) === null;
|
|
569
|
+
const useIso = order === "ymd" || !fits(ly, lm, ld) && fits(...isoReading);
|
|
570
|
+
[year, month, day] = useIso ? isoReading : [ly, lm, ld];
|
|
571
|
+
}
|
|
572
|
+
} else if (numeric) {
|
|
573
|
+
const [a, b, c] = [numeric[1], numeric[2], numeric[3]];
|
|
574
|
+
if (a.length === 4) {
|
|
575
|
+
year = Number(a);
|
|
576
|
+
month = Number(b);
|
|
577
|
+
day = Number(c);
|
|
578
|
+
} else {
|
|
579
|
+
if (c.length !== 4 && c.length !== 2) {
|
|
580
|
+
return { ok: false, why: `\u201C${raw}\u201D has no four-digit year. ${HOW}` };
|
|
581
|
+
}
|
|
582
|
+
const order = localeDateOrder(opts.locale);
|
|
583
|
+
year = c.length === 2 ? twoDigitYear(Number(c)) : Number(c);
|
|
584
|
+
if (order === "dmy") {
|
|
585
|
+
day = Number(a);
|
|
586
|
+
month = Number(b);
|
|
587
|
+
} else {
|
|
588
|
+
month = Number(a);
|
|
589
|
+
day = Number(b);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
} else {
|
|
593
|
+
const tokens = dateText.toLowerCase().replace(/,/g, " ").split(/[\s/.\-]+/).filter(Boolean);
|
|
594
|
+
const numbers = [];
|
|
595
|
+
for (const token of tokens) {
|
|
596
|
+
const word = token.replace(/\.$/, "");
|
|
597
|
+
if (/^\d+(st|nd|rd|th)?$/.test(word)) numbers.push(word.replace(/\D/g, ""));
|
|
598
|
+
else if (word in MONTHS) {
|
|
599
|
+
if (month !== null) return { ok: false, why: `\u201C${raw}\u201D names two months. ${HOW}` };
|
|
600
|
+
month = MONTHS[word];
|
|
601
|
+
} else if (word !== "of" && word !== "the") {
|
|
602
|
+
return { ok: false, why: `\u201C${raw}\u201D is not a date this can read. ${HOW}` };
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
if (month === null) return { ok: false, why: `\u201C${raw}\u201D is not a date this can read. ${HOW}` };
|
|
606
|
+
const years = numbers.filter((n) => n.length === 4);
|
|
607
|
+
const days = numbers.filter((n) => n.length <= 2);
|
|
608
|
+
if (years.length === 0) {
|
|
609
|
+
return {
|
|
610
|
+
ok: false,
|
|
611
|
+
why: numbers.some((n) => n.length === 3) ? `\u201C${raw}\u201D has no four-digit year. ${HOW}` : `\u201C${raw}\u201D has no year, and which year it means is a guess. Write the year too (${days[0] ?? "3"} ${Object.keys(MONTHS).find((k) => MONTHS[k] === month && k.length > 3) ?? "October"} 2026).`
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
if (years.length > 1 || days.length !== 1 || numbers.length !== 2) {
|
|
615
|
+
return { ok: false, why: `\u201C${raw}\u201D is not a date this can read. ${HOW}` };
|
|
616
|
+
}
|
|
617
|
+
year = Number(years[0]);
|
|
618
|
+
day = Number(days[0]);
|
|
619
|
+
}
|
|
620
|
+
const bad = checkDay(year, month, day, raw);
|
|
621
|
+
if (bad) return { ok: false, why: bad };
|
|
622
|
+
const dayText = `${pad(year, 4)}-${pad(month)}-${pad(day)}`;
|
|
623
|
+
if (!opts.withTime) return { ok: true, day: dayText, instant: null };
|
|
624
|
+
const t = split.time ?? { h: 0, m: 0, s: 0 };
|
|
625
|
+
const local = new Date(year, month - 1, day, t.h, t.m, t.s);
|
|
626
|
+
return { ok: true, day: dayText, instant: local.toISOString() };
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// src/typedNumber.ts
|
|
630
|
+
function readTypedNumber(raw) {
|
|
631
|
+
const text2 = raw.trim();
|
|
632
|
+
if (text2 === "") return { ok: false, why: "Nothing was typed." };
|
|
633
|
+
const bracketed = /^\(.*\)$/.test(text2);
|
|
634
|
+
const inner = (bracketed ? text2.slice(1, -1) : text2).replace(/[\s,]/g, "");
|
|
635
|
+
const head = /^[^\d.]*/.exec(inner)?.[0] ?? "";
|
|
636
|
+
const bare = inner.slice(head.length).replace(/[^\d.eE+-]+$/, "");
|
|
637
|
+
const minus = (head.match(/[-−]/g) ?? []).length % 2 === 1;
|
|
638
|
+
const n = Number(bare);
|
|
639
|
+
if (bare === "" || Number.isNaN(n) || /[\d]/.test(head)) {
|
|
640
|
+
return {
|
|
641
|
+
ok: false,
|
|
642
|
+
why: `\u201C${raw}\u201D is not a number. Write digits, like 1,250.50 \u2014 a currency symbol, a percent sign, commas and a minus sign or brackets for a negative are all fine.`
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
return { ok: true, value: bracketed !== minus ? -n : n };
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// src/coerce.ts
|
|
649
|
+
var import_uuid = require("@ai-matrx/kit/uuid");
|
|
650
|
+
function fieldKindFor(field, kernel) {
|
|
651
|
+
const format = (field.format ?? "").toLowerCase();
|
|
652
|
+
const config = field.config ?? {};
|
|
653
|
+
switch (field.type) {
|
|
654
|
+
case "boolean":
|
|
655
|
+
return "checkbox";
|
|
656
|
+
case "text":
|
|
657
|
+
if (format === "email") return "email";
|
|
658
|
+
if (format === "phone") return "phone";
|
|
659
|
+
if (format === "url") return "url";
|
|
660
|
+
if (format === "json" || config["json"] === true) return "json";
|
|
661
|
+
if (format === "long" || config["multiline"] === true) return "long_text";
|
|
662
|
+
return "text";
|
|
663
|
+
case "range": {
|
|
664
|
+
const kind = String(config["kind"] ?? "number");
|
|
665
|
+
if (kind === "date" || kind === "datetime") return "datetime";
|
|
666
|
+
if (format === "currency") return "currency";
|
|
667
|
+
if (format === "percent" || field.unit === "%") return "percent";
|
|
668
|
+
return "number";
|
|
669
|
+
}
|
|
670
|
+
case "list":
|
|
671
|
+
return field.multi ? "multi_select" : "select";
|
|
672
|
+
case "relation": {
|
|
673
|
+
if (isEntityReferenceConfig(config)) return "entity_reference";
|
|
674
|
+
const parity = String(config["parity"] ?? "");
|
|
675
|
+
if (parity === "attachment" || parity === "member") return parity;
|
|
676
|
+
if (kernel?.file && field.relation_target === kernel.file) return "attachment";
|
|
677
|
+
if (kernel?.person && field.relation_target === kernel.person) return "member";
|
|
678
|
+
return "relation";
|
|
679
|
+
}
|
|
680
|
+
case "formula": {
|
|
681
|
+
const via = config["via"];
|
|
682
|
+
if (via && config["agg"]) return "rollup";
|
|
683
|
+
if (via) return "lookup";
|
|
684
|
+
return "formula";
|
|
685
|
+
}
|
|
686
|
+
default:
|
|
687
|
+
return "text";
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
function relationNameKey(name) {
|
|
691
|
+
return name.trim().replace(/\s+/g, " ").toLowerCase();
|
|
692
|
+
}
|
|
693
|
+
function splitListCell(text2) {
|
|
694
|
+
return text2.split(/\s*[;,|]\s*/).map((w) => w.trim()).filter((w) => w.length > 0);
|
|
695
|
+
}
|
|
696
|
+
var MONEY_SYMBOLS = { $: "USD", "\u20AC": "EUR", "\xA3": "GBP", "\u20B9": "INR", "\u20A9": "KRW", "\u20AA": "ILS", "\u20BA": "TRY", "\u20BD": "RUB" };
|
|
697
|
+
function columnCurrency(unit) {
|
|
698
|
+
const u = (unit ?? "").trim();
|
|
699
|
+
if (u === "") return null;
|
|
700
|
+
if (MONEY_SYMBOLS[u]) return MONEY_SYMBOLS[u];
|
|
701
|
+
return /^[A-Za-z]{3}$/.test(u) ? u.toUpperCase() : null;
|
|
702
|
+
}
|
|
703
|
+
function foreignUnit(kind, text2, label, raw, unit, showsAsPercent) {
|
|
704
|
+
const hasPercent = /%/.test(text2);
|
|
705
|
+
const symbols = [...text2].filter((ch) => ch in MONEY_SYMBOLS);
|
|
706
|
+
if (kind === "percent" || showsAsPercent) {
|
|
707
|
+
if (symbols.length > 0) return `${label} holds a percent, and \u201C${raw}\u201D is an amount of money. Write it like 45%.`;
|
|
708
|
+
return null;
|
|
709
|
+
}
|
|
710
|
+
if (hasPercent) {
|
|
711
|
+
return kind === "currency" ? `${label} holds an amount of money, and \u201C${raw}\u201D is a percent. Write the amount, like $1,250.50.` : `${label} holds a number, and \u201C${raw}\u201D is a percent, which this column does not show. Write 45 or 0.45 \u2014 or make the column show as a percent.`;
|
|
712
|
+
}
|
|
713
|
+
if (kind === "currency") {
|
|
714
|
+
const own = columnCurrency(unit);
|
|
715
|
+
const typed = symbols.map((ch) => MONEY_SYMBOLS[ch]);
|
|
716
|
+
if (own && typed.some((code) => code !== own)) {
|
|
717
|
+
return `${label} holds an amount in ${own}, and \u201C${raw}\u201D is written in ${typed.find((code) => code !== own)}. Write the amount in ${own}.`;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
return null;
|
|
721
|
+
}
|
|
722
|
+
function isEntityReferenceConfig(config) {
|
|
723
|
+
const allowed = config?.["allowed_types"];
|
|
724
|
+
return Array.isArray(allowed) && allowed.length > 0;
|
|
725
|
+
}
|
|
726
|
+
function entityReferenceKindsOf(field) {
|
|
727
|
+
const allowed = field.config?.["allowed_types"];
|
|
728
|
+
return Array.isArray(allowed) ? allowed.map((k) => String(k).trim().toLowerCase()).filter(Boolean) : [];
|
|
729
|
+
}
|
|
730
|
+
var TRUE_WORDS = /* @__PURE__ */ new Set(["true", "yes", "y", "1", "\u2713", "x", "checked", "on"]);
|
|
731
|
+
var FALSE_WORDS = /* @__PURE__ */ new Set(["false", "no", "n", "0", "", "unchecked", "off"]);
|
|
732
|
+
function coercionLabel(field) {
|
|
733
|
+
const label = (field.label ?? "").trim();
|
|
734
|
+
if (label !== "") return label;
|
|
735
|
+
const name = (field.name ?? "").trim();
|
|
736
|
+
if (name !== "") return name;
|
|
737
|
+
return field.key.replace(/[_-]+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()).trim();
|
|
738
|
+
}
|
|
739
|
+
function optionsFromPublicChoices(field) {
|
|
740
|
+
const offered = field.public_choices;
|
|
741
|
+
if (!Array.isArray(offered) || offered.length === 0) return null;
|
|
742
|
+
const byWord = /* @__PURE__ */ new Map();
|
|
743
|
+
const labels = [];
|
|
744
|
+
for (const c of offered) {
|
|
745
|
+
const key = typeof c?.key === "string" ? c.key : null;
|
|
746
|
+
if (!key) continue;
|
|
747
|
+
const words = c.label;
|
|
748
|
+
const choiceLabel = typeof words === "string" && words.trim() !== "" ? words.trim() : key;
|
|
749
|
+
byWord.set(key.toLowerCase(), key);
|
|
750
|
+
byWord.set(choiceLabel.toLowerCase(), key);
|
|
751
|
+
labels.push(choiceLabel);
|
|
752
|
+
}
|
|
753
|
+
return labels.length > 0 ? { byWord, labels } : null;
|
|
754
|
+
}
|
|
755
|
+
function coerceTypedAnswer(field, raw, opts = {}) {
|
|
756
|
+
const label = opts.label ?? coercionLabel(field);
|
|
757
|
+
const text2 = raw.trim();
|
|
758
|
+
const kind = fieldKindFor(field, opts.kernel);
|
|
759
|
+
if (text2 === "" && kind !== "checkbox") return { value: null };
|
|
760
|
+
if (isButtonField(field)) return { refusal: `${label} is a button: it is pressed, and holds nothing to type.` };
|
|
761
|
+
const newer = newerKindOf(field);
|
|
762
|
+
if (newer === "rating") {
|
|
763
|
+
const max = ratingMaxOf(field);
|
|
764
|
+
const n = Number(text2);
|
|
765
|
+
if (Number.isInteger(n) && n >= 0 && n <= max) return { value: n };
|
|
766
|
+
return { refusal: `${label} is a whole number of stars from 0 to ${max}, and \u201C${raw}\u201D is not one.` };
|
|
767
|
+
}
|
|
768
|
+
if (newer === "duration") {
|
|
769
|
+
const seconds = readTypedDuration(text2);
|
|
770
|
+
if (seconds === null || seconds < 0) {
|
|
771
|
+
return { refusal: `${label} is a length of time, and \u201C${raw}\u201D is not one. Write it like 45 min, 1:30 or 1h 30m.` };
|
|
772
|
+
}
|
|
773
|
+
if (seconds > 315576e3) return { refusal: `${label} is longer than ten years, and a length of time here is at most ten years.` };
|
|
774
|
+
return { value: seconds };
|
|
775
|
+
}
|
|
776
|
+
if (newer === "address") return { value: addressFromText(text2) };
|
|
777
|
+
if (newer === "barcode") return { value: text2 };
|
|
778
|
+
switch (kind) {
|
|
779
|
+
case "checkbox": {
|
|
780
|
+
const word = text2.toLowerCase();
|
|
781
|
+
if (TRUE_WORDS.has(word)) return { value: true };
|
|
782
|
+
if (FALSE_WORDS.has(word)) return { value: word === "" ? null : false };
|
|
783
|
+
return {
|
|
784
|
+
refusal: `${label} is a tick box, so it takes yes or no \u2014 \u201C${raw}\u201D is neither. Write TRUE/FALSE, yes/no or 1/0.`
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
case "number":
|
|
788
|
+
case "currency":
|
|
789
|
+
case "percent": {
|
|
790
|
+
const read = readTypedNumber(text2);
|
|
791
|
+
if (!read.ok) {
|
|
792
|
+
return {
|
|
793
|
+
refusal: `${label} holds a number, and \u201C${raw}\u201D is not one. Write digits, like 1,250.50 \u2014 a currency symbol, a percent sign, commas and a minus sign or brackets for a negative are all fine.`
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
const foreign = foreignUnit(kind, text2, label, raw, field.unit, opts.showsAsPercent === true);
|
|
797
|
+
if (foreign) return { refusal: foreign };
|
|
798
|
+
return { value: read.value };
|
|
799
|
+
}
|
|
800
|
+
case "datetime": {
|
|
801
|
+
const wantsTime = String(field.config?.["kind"] ?? "date") === "datetime";
|
|
802
|
+
const read = readTypedDate(text2, { withTime: wantsTime, ...opts.locale ? { locale: opts.locale } : {} });
|
|
803
|
+
if (!read.ok) return { refusal: `${label} holds a date, and ${read.why}` };
|
|
804
|
+
return { value: wantsTime ? read.instant : read.day };
|
|
805
|
+
}
|
|
806
|
+
case "json": {
|
|
807
|
+
try {
|
|
808
|
+
return { value: JSON.parse(text2) };
|
|
809
|
+
} catch {
|
|
810
|
+
return {
|
|
811
|
+
refusal: `${label} holds a document, and \u201C${raw}\u201D is not valid JSON. Write the whole object, braces and all.`
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
case "select":
|
|
816
|
+
case "multi_select": {
|
|
817
|
+
const options = opts.options ?? optionsFromPublicChoices(field);
|
|
818
|
+
if (!options) {
|
|
819
|
+
return {
|
|
820
|
+
refusal: `${label} is a list and its options have not been read yet, so nothing can be matched to \u201C${raw}\u201D. Try again once the column's choices have loaded.`
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
const wanted = kind === "multi_select" ? text2.split(/[,;]/).map((w) => w.trim()).filter((w) => w.length > 0) : [text2];
|
|
824
|
+
if (wanted.length === 0) return { value: kind === "multi_select" ? [] : null };
|
|
825
|
+
const matched = [];
|
|
826
|
+
const unmatched = [];
|
|
827
|
+
for (const word of wanted) {
|
|
828
|
+
const hit = options.byWord.get(word.toLowerCase());
|
|
829
|
+
if (hit === void 0) unmatched.push(word);
|
|
830
|
+
else matched.push(hit);
|
|
831
|
+
}
|
|
832
|
+
if (unmatched.length > 0) {
|
|
833
|
+
const offered = options.labels.length > 0 ? options.labels.join(", ") : "it has none yet";
|
|
834
|
+
return {
|
|
835
|
+
refusal: `${label} does not have ${unmatched.map((w) => `\u201C${w}\u201D`).join(" or ")} among its options. It offers: ${offered}.`,
|
|
836
|
+
unmatched
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
return { value: kind === "multi_select" ? matched : matched[0] };
|
|
840
|
+
}
|
|
841
|
+
// AN IDENTITY IS NOT A WORD. Writing the word would put a string where an
|
|
842
|
+
// id belongs, and every filter, colour and rollup built on that column
|
|
843
|
+
// would stop agreeing with it. So it is refused and the person is told
|
|
844
|
+
// which control does hold it.
|
|
845
|
+
//
|
|
846
|
+
// BUT AN ID IS AN IDENTITY (RELATION-TARGETS, 2026-09-23). A Person or File
|
|
847
|
+
// column holds the kernel Person / File record, and the store itself now
|
|
848
|
+
// turns the id a writer actually holds — a member's user id, an uploaded
|
|
849
|
+
// file's id, or the record's own id — into that record at every write door
|
|
850
|
+
// (`custom.relation_kernel_record`), refusing anything else in its own
|
|
851
|
+
// words ("… names someone who is not a member of …"). So an import column
|
|
852
|
+
// of member ids, or an agent handing over the file it just uploaded, is a
|
|
853
|
+
// value and not a refusal; the store, not this mirror, decides whether the
|
|
854
|
+
// id is somebody here.
|
|
855
|
+
case "member":
|
|
856
|
+
if ((0, import_uuid.isUuidShape)(text2)) return { value: text2.toLowerCase() };
|
|
857
|
+
return {
|
|
858
|
+
refusal: `${label} names a person in this organization, so it is chosen rather than typed. Open it and pick them.`
|
|
859
|
+
};
|
|
860
|
+
case "attachment":
|
|
861
|
+
if ((0, import_uuid.isUuidShape)(text2)) return { value: text2.toLowerCase() };
|
|
862
|
+
return {
|
|
863
|
+
refusal: `${label} holds a file, which cannot arrive as typed-in text. Open it and attach the file.`
|
|
864
|
+
};
|
|
865
|
+
case "relation": {
|
|
866
|
+
const parts = field.multi ? splitListCell(text2) : [text2];
|
|
867
|
+
const ids = [];
|
|
868
|
+
const words = [];
|
|
869
|
+
const none = [];
|
|
870
|
+
const many = [];
|
|
871
|
+
for (const part of parts) {
|
|
872
|
+
if ((0, import_uuid.isUuidShape)(part)) {
|
|
873
|
+
if (!ids.includes(part.toLowerCase())) ids.push(part.toLowerCase());
|
|
874
|
+
continue;
|
|
875
|
+
}
|
|
876
|
+
if (!opts.relationNames) {
|
|
877
|
+
return {
|
|
878
|
+
refusal: `${label} points at another record, so it is chosen rather than typed. Open it and pick the record.`
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
const hits = opts.relationNames.byName.get(relationNameKey(part)) ?? [];
|
|
882
|
+
if (hits.length === 0) none.push(part);
|
|
883
|
+
else if (hits.length > 1) many.push(part);
|
|
884
|
+
else if (!ids.includes(hits[0].id)) {
|
|
885
|
+
ids.push(hits[0].id);
|
|
886
|
+
words.push(hits[0].words);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
if (none.length > 0 || many.length > 0) {
|
|
890
|
+
const table = opts.relationNames?.tableName ?? "that table";
|
|
891
|
+
const said = many.length > 0 ? `${many.map((w) => `\u201C${w}\u201D`).join(", ")} names more than one record in ${table}.` : `${table} has no record called ${none.map((w) => `\u201C${w}\u201D`).join(" or ")}.`;
|
|
892
|
+
return { refusal: `${label}: ${said}`, unmatchedNames: none, ambiguousNames: many };
|
|
893
|
+
}
|
|
894
|
+
if (ids.length === 0) return { value: field.multi ? [] : null };
|
|
895
|
+
return { value: field.multi ? ids : ids[0], ...words.length > 0 ? { words: words.join(", ") } : {} };
|
|
896
|
+
}
|
|
897
|
+
// SC-R / P12. An entity reference names a platform thing by kind and id.
|
|
898
|
+
// An import or an agent may hand over `note:<id>` — or a bare id when the
|
|
899
|
+
// column allows exactly one kind; the store then decides whether the thing
|
|
900
|
+
// is there and whether the writer may open it. Words are chosen, not typed.
|
|
901
|
+
case "entity_reference": {
|
|
902
|
+
const allowed = entityReferenceKindsOf(field);
|
|
903
|
+
const [maybeToken, maybeId] = text2.includes(":") ? text2.split(/:(.*)/s, 2) : ["", text2];
|
|
904
|
+
const token = (maybeToken ?? "").trim().toLowerCase() || (allowed.length === 1 ? allowed[0] : "");
|
|
905
|
+
const id = (maybeId ?? "").trim();
|
|
906
|
+
if (token && (0, import_uuid.isUuidShape)(id)) {
|
|
907
|
+
const ref = { token, id: id.toLowerCase() };
|
|
908
|
+
return { value: field.multi ? [ref] : ref };
|
|
909
|
+
}
|
|
910
|
+
return {
|
|
911
|
+
refusal: `${label} points at something on the platform, so it is chosen rather than typed. Open it and pick it.`
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
case "formula":
|
|
915
|
+
case "lookup":
|
|
916
|
+
case "rollup":
|
|
917
|
+
return {
|
|
918
|
+
refusal: `${label} is worked out by the system, so nothing can be written into it.`
|
|
919
|
+
};
|
|
920
|
+
// A LINK AND AN EMAIL ARE READ, THE WAY AIRTABLE READS THEM (merged-grid review 2, A9).
|
|
921
|
+
// "company.com" is a link a person meant: it is kept as https://company.com. Words that
|
|
922
|
+
// cannot be one are refused NAMING THE KIND, WITH ONE EXAMPLE — never "not written the way
|
|
923
|
+
// this field expects". Any further format Rule on the column is still the store's to judge.
|
|
924
|
+
case "url": {
|
|
925
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(text2) || /^mailto:/i.test(text2)) return { value: text2 };
|
|
926
|
+
if (!/\s/.test(text2) && /^[^./@\s]+(\.[^./\s]+)+(\/.*)?$/.test(text2)) return { value: `https://${text2}` };
|
|
927
|
+
return { refusal: `${label} holds a link, and \u201C${raw}\u201D is not one. A link looks like company.com or https://company.com/page.` };
|
|
928
|
+
}
|
|
929
|
+
case "email": {
|
|
930
|
+
if (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(text2)) return { value: text2 };
|
|
931
|
+
return { refusal: `${label} holds an email address, and \u201C${raw}\u201D is not one. An email looks like name@company.com.` };
|
|
932
|
+
}
|
|
933
|
+
default:
|
|
934
|
+
return { value: raw };
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
function coerceTypedAnswers(fields, answers, opts = {}) {
|
|
938
|
+
const byKey = new Map(fields.map((f) => [f.key, f]));
|
|
939
|
+
const values = {};
|
|
940
|
+
const refusals = {};
|
|
941
|
+
const unknownKeys = [];
|
|
942
|
+
for (const [key, given] of Object.entries(answers)) {
|
|
943
|
+
const field = byKey.get(key);
|
|
944
|
+
if (!field) {
|
|
945
|
+
unknownKeys.push(key);
|
|
946
|
+
values[key] = given;
|
|
947
|
+
continue;
|
|
948
|
+
}
|
|
949
|
+
if (typeof given !== "string") {
|
|
950
|
+
values[key] = given;
|
|
951
|
+
continue;
|
|
952
|
+
}
|
|
953
|
+
const opt = {};
|
|
954
|
+
const choices = opts.options?.get(key);
|
|
955
|
+
if (choices) opt.options = choices;
|
|
956
|
+
const named = opts.relationNames?.get(key);
|
|
957
|
+
if (named) opt.relationNames = named;
|
|
958
|
+
if (opts.kernel) opt.kernel = opts.kernel;
|
|
959
|
+
const label = opts.labels?.[key];
|
|
960
|
+
if (label) opt.label = label;
|
|
961
|
+
const answered = coerceTypedAnswer(field, given, opt);
|
|
962
|
+
if ("refusal" in answered) refusals[key] = answered.refusal;
|
|
963
|
+
else values[key] = answered.value;
|
|
964
|
+
}
|
|
965
|
+
return { values, refusals, unknownKeys };
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// src/forms/typedAnswers.ts
|
|
969
|
+
function typedAnswersFor(fields, values) {
|
|
970
|
+
if (!Array.isArray(fields) || fields.length === 0) {
|
|
971
|
+
return { values, refusal: null, byKey: {} };
|
|
972
|
+
}
|
|
973
|
+
const coerced = coerceTypedAnswers(fields, values);
|
|
974
|
+
const keys = Object.keys(coerced.refusals);
|
|
975
|
+
if (keys.length === 0) return { values: coerced.values, refusal: null, byKey: {} };
|
|
976
|
+
const order = new Map(fields.map((f, at) => [String(f.key ?? ""), at]));
|
|
977
|
+
keys.sort((a, b) => (order.get(a) ?? 0) - (order.get(b) ?? 0));
|
|
978
|
+
return {
|
|
979
|
+
values: coerced.values,
|
|
980
|
+
refusal: keys.map((k) => coerced.refusals[k]).join(" "),
|
|
981
|
+
byKey: coerced.refusals
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
//# sourceMappingURL=index.cjs.map
|