@marktiderman/genesis-forms 0.1.0 → 0.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.
- package/README.md +8 -4
- package/dist/{engine-LsPRTzt3.d.cts → engine-Bj8-mMVb.d.cts} +6 -6
- package/dist/{engine-Cp5t2_Am.d.ts → engine-Coc-gQIB.d.ts} +6 -6
- package/dist/index.cjs +95 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +30 -23
- package/dist/index.d.ts +30 -23
- package/dist/index.js +95 -5
- package/dist/index.js.map +1 -1
- package/dist/server.cjs +108 -12
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +11 -11
- package/dist/server.d.ts +11 -11
- package/dist/server.js +108 -12
- package/dist/server.js.map +1 -1
- package/dist/testing.cjs +108 -12
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +3 -3
- package/dist/testing.d.ts +3 -3
- package/dist/testing.js +108 -12
- package/dist/testing.js.map +1 -1
- package/dist/{validate-j8Z188mF.d.cts → validate-BbackAjB.d.cts} +8 -1
- package/dist/{validate-j8Z188mF.d.ts → validate-BbackAjB.d.ts} +8 -1
- package/package.json +3 -3
package/dist/testing.cjs
CHANGED
|
@@ -230,6 +230,7 @@ function compactEvents(events) {
|
|
|
230
230
|
|
|
231
231
|
// src/hidden.ts
|
|
232
232
|
var MAX_HIDDEN_STRING_LENGTH = 1e3;
|
|
233
|
+
var DECIMAL = /^-?\d+(\.\d+)?$/;
|
|
233
234
|
function coerceHiddenValue(value, type) {
|
|
234
235
|
if (typeof value === "string" && value.length > MAX_HIDDEN_STRING_LENGTH) return void 0;
|
|
235
236
|
if (type === "string") {
|
|
@@ -239,7 +240,7 @@ function coerceHiddenValue(value, type) {
|
|
|
239
240
|
}
|
|
240
241
|
if (type === "number") {
|
|
241
242
|
if (typeof value === "number") return Number.isFinite(value) ? value : void 0;
|
|
242
|
-
if (typeof value !== "string" ||
|
|
243
|
+
if (typeof value !== "string" || !DECIMAL.test(value)) return void 0;
|
|
243
244
|
const n = Number(value);
|
|
244
245
|
return Number.isFinite(n) ? n : void 0;
|
|
245
246
|
}
|
|
@@ -564,7 +565,7 @@ function build(definition, options, replaying = false) {
|
|
|
564
565
|
for (const id of state.path) {
|
|
565
566
|
const field = fields.get(id);
|
|
566
567
|
const value = own(state.values, id);
|
|
567
|
-
if (field && value
|
|
568
|
+
if (field && !isEmptyValue(value, field)) out[id] = normalizeValue(value, field);
|
|
568
569
|
}
|
|
569
570
|
return out;
|
|
570
571
|
}
|
|
@@ -772,7 +773,30 @@ function coverage(form, opts = {}) {
|
|
|
772
773
|
}
|
|
773
774
|
|
|
774
775
|
// src/parse.ts
|
|
776
|
+
function shown(v) {
|
|
777
|
+
let text;
|
|
778
|
+
try {
|
|
779
|
+
text = JSON.stringify(v);
|
|
780
|
+
} catch {
|
|
781
|
+
text = void 0;
|
|
782
|
+
}
|
|
783
|
+
text ??= typeof v === "bigint" ? `${v}n` : typeof v;
|
|
784
|
+
return text.length > 60 ? `${text.slice(0, 57)}...` : text;
|
|
785
|
+
}
|
|
775
786
|
var isObject = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
787
|
+
var isInt = (v) => typeof v === "number" && Number.isInteger(v);
|
|
788
|
+
var LITERAL_TYPE = {
|
|
789
|
+
"short-text": "string",
|
|
790
|
+
"long-text": "string",
|
|
791
|
+
email: "string",
|
|
792
|
+
phone: "string",
|
|
793
|
+
rating: "number",
|
|
794
|
+
nps: "number",
|
|
795
|
+
"single-choice": "string",
|
|
796
|
+
"multi-choice": "string",
|
|
797
|
+
"yes-no": "boolean"
|
|
798
|
+
};
|
|
799
|
+
var ORDERING = /* @__PURE__ */ new Set(["lt", "lte", "gt", "gte"]);
|
|
776
800
|
var OPERATORS = /* @__PURE__ */ new Set([
|
|
777
801
|
"eq",
|
|
778
802
|
"neq",
|
|
@@ -845,6 +869,7 @@ function parseForm(input) {
|
|
|
845
869
|
const keys = /* @__PURE__ */ new Map();
|
|
846
870
|
const fieldTypes = /* @__PURE__ */ new Map();
|
|
847
871
|
const order = /* @__PURE__ */ new Map();
|
|
872
|
+
const choices = /* @__PURE__ */ new Map();
|
|
848
873
|
input.blocks.forEach((block, i) => {
|
|
849
874
|
const path = `blocks[${i}]`;
|
|
850
875
|
if (!isObject(block) || typeof block.id !== "string" || !block.id) {
|
|
@@ -878,8 +903,19 @@ function parseForm(input) {
|
|
|
878
903
|
if (!isObject(o) || typeof o.value !== "string") issue(at, "Each option needs a string value.");
|
|
879
904
|
else optionalText(o.label, `${at}.label`);
|
|
880
905
|
});
|
|
906
|
+
choices.set(
|
|
907
|
+
block.id,
|
|
908
|
+
new Set(options.flatMap((o) => isObject(o) && typeof o.value === "string" ? [o.value] : []))
|
|
909
|
+
);
|
|
881
910
|
}
|
|
882
911
|
}
|
|
912
|
+
const { max, maxLength: maxLength2 } = isObject(block.props) ? block.props : {};
|
|
913
|
+
if (block.type === "rating" && max !== void 0 && !(isInt(max) && max >= 2 && max <= 10)) {
|
|
914
|
+
issue(`${path}.props.max`, "Must be a whole number from 2 to 10.");
|
|
915
|
+
}
|
|
916
|
+
if (maxLength2 !== void 0 && !(isInt(maxLength2) && maxLength2 > 0)) {
|
|
917
|
+
issue(`${path}.props.maxLength`, "Must be a whole number above 0.");
|
|
918
|
+
}
|
|
883
919
|
}
|
|
884
920
|
if (validKey) {
|
|
885
921
|
if (keys.has(block.key)) issue(`${path}.key`, `Duplicate key "${String(block.key)}".`);
|
|
@@ -896,6 +932,7 @@ function parseForm(input) {
|
|
|
896
932
|
}
|
|
897
933
|
});
|
|
898
934
|
const hiddenNames = /* @__PURE__ */ new Set();
|
|
935
|
+
const hiddenTypes = /* @__PURE__ */ new Map();
|
|
899
936
|
if (input.hidden !== void 0 && !Array.isArray(input.hidden)) issue("hidden", "Must be an array.");
|
|
900
937
|
(Array.isArray(input.hidden) ? input.hidden : []).forEach((h, i) => {
|
|
901
938
|
if (!isObject(h) || typeof h.name !== "string" || !h.name) {
|
|
@@ -911,7 +948,9 @@ function parseForm(input) {
|
|
|
911
948
|
issue(`hidden[${i}].type`, "Must be string, number or boolean.");
|
|
912
949
|
}
|
|
913
950
|
optionalBoolean(h.sensitive, `hidden[${i}].sensitive`);
|
|
951
|
+
optionalBoolean(h.identifying, `hidden[${i}].identifying`);
|
|
914
952
|
hiddenNames.add(h.name);
|
|
953
|
+
if (typeof h.type === "string") hiddenTypes.set(h.name, h.type);
|
|
915
954
|
});
|
|
916
955
|
const endingIds = /* @__PURE__ */ new Set();
|
|
917
956
|
if (input.endings !== void 0 && !Array.isArray(input.endings)) issue("endings", "Must be an array.");
|
|
@@ -960,8 +999,45 @@ function parseForm(input) {
|
|
|
960
999
|
if (typeof c.left.field === "string") {
|
|
961
1000
|
const manifest = getFieldType(fieldTypes.get(c.left.field) ?? "");
|
|
962
1001
|
if (manifest && OPERATORS.has(c.op) && !manifest.operators.includes(c.op)) {
|
|
963
|
-
issue(`${path}.op`, `"${c.op}" does not apply to a ${manifest.type} field.`);
|
|
1002
|
+
return issue(`${path}.op`, `"${c.op}" does not apply to a ${manifest.type} field.`);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
checkLiterals(c.op, c.left, c.right, path);
|
|
1006
|
+
};
|
|
1007
|
+
const checkLiterals = (op, left, right, path) => {
|
|
1008
|
+
if (op === "answered" || op === "empty" || !OPERATORS.has(op)) return;
|
|
1009
|
+
const literal = isObject(right) && "value" in right;
|
|
1010
|
+
if (right !== void 0 && !literal) return;
|
|
1011
|
+
let what = "the left side";
|
|
1012
|
+
let type;
|
|
1013
|
+
let options;
|
|
1014
|
+
if (typeof left.field === "string" && fieldTypes.has(left.field)) {
|
|
1015
|
+
const fieldType = fieldTypes.get(left.field);
|
|
1016
|
+
what = `"${keyOf(left.field)}"`;
|
|
1017
|
+
type = LITERAL_TYPE[fieldType];
|
|
1018
|
+
if (fieldType === "single-choice" || fieldType === "multi-choice") options = choices.get(left.field);
|
|
1019
|
+
} else if (typeof left.hidden === "string" && hiddenTypes.has(left.hidden)) {
|
|
1020
|
+
what = `hidden input "${left.hidden}"`;
|
|
1021
|
+
type = hiddenTypes.get(left.hidden);
|
|
1022
|
+
}
|
|
1023
|
+
const outcome = op === "neq" ? "always true" : "never true";
|
|
1024
|
+
let values;
|
|
1025
|
+
if (op === "in") {
|
|
1026
|
+
if (!literal || !Array.isArray(right.value)) {
|
|
1027
|
+
return void warnings.push({ path: `${path}.right`, message: "`in` needs a list of values; it never matches." });
|
|
1028
|
+
}
|
|
1029
|
+
values = right.value;
|
|
1030
|
+
} else if (!literal) return;
|
|
1031
|
+
else values = [right.value];
|
|
1032
|
+
if (ORDERING.has(op)) {
|
|
1033
|
+
if (type !== void 0 && type !== "number") {
|
|
1034
|
+
return void warnings.push({ path: `${path}.op`, message: `"${op}" compares numbers, but ${what} holds a ${type}; it is never true.` });
|
|
964
1035
|
}
|
|
1036
|
+
type = "number";
|
|
1037
|
+
}
|
|
1038
|
+
for (const v of values) {
|
|
1039
|
+
const message = type !== void 0 && typeof v !== type ? `${what} holds a ${type}, but ${shown(v)} is not one; this comparison is ${outcome}.` : options && typeof v === "string" && !options.has(v) ? `${shown(v)} is not an option of ${what}; this comparison is ${outcome}.` : null;
|
|
1040
|
+
if (message) warnings.push({ path: `${path}.right`, message });
|
|
965
1041
|
}
|
|
966
1042
|
};
|
|
967
1043
|
const keyOf = (id) => [...keys].find(([, v]) => v === id)?.[0] ?? id;
|
|
@@ -1038,6 +1114,18 @@ function parseForm(input) {
|
|
|
1038
1114
|
}
|
|
1039
1115
|
}
|
|
1040
1116
|
});
|
|
1117
|
+
const extra = [["description", input.description]];
|
|
1118
|
+
(Array.isArray(input.endings) ? input.endings : []).forEach((e, i) => {
|
|
1119
|
+
if (isObject(e)) extra.push([`endings[${i}]`, e.title], [`endings[${i}]`, e.description]);
|
|
1120
|
+
});
|
|
1121
|
+
for (const [path, t] of extra) {
|
|
1122
|
+
if (typeof t !== "string") continue;
|
|
1123
|
+
for (const ref of recallRefs(t)) {
|
|
1124
|
+
if (ref.kind === "hidden" ? !hiddenNames.has(ref.name) : !keys.has(ref.name)) {
|
|
1125
|
+
warnings.push({ path, message: `Recalls unknown ${ref.kind === "hidden" ? "hidden input" : "field"} "${ref.name}"; it shows its fallback text.` });
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1041
1129
|
return issues.length ? fail() : { ok: true, form: input, warnings };
|
|
1042
1130
|
}
|
|
1043
1131
|
|
|
@@ -1074,18 +1162,16 @@ function validateSubmission(definition, submission, options = {}) {
|
|
|
1074
1162
|
if (invalid) return { ok: false, errors: { "": invalid } };
|
|
1075
1163
|
const input = wellFormed(submission);
|
|
1076
1164
|
if (!input) return MALFORMED;
|
|
1077
|
-
const ignored =
|
|
1165
|
+
const ignored = /* @__PURE__ */ new Set();
|
|
1078
1166
|
const client = coerceHidden(definition, input.hidden, { sources: ["url", "host"] });
|
|
1079
|
-
for (const name of client.ignored) ignored.
|
|
1167
|
+
for (const name of client.ignored) ignored.add(`hidden.${name}`);
|
|
1080
1168
|
const hidden = client.values;
|
|
1081
1169
|
const context = coerceHidden(definition, options.context, { sources: ["server"] }).values;
|
|
1082
1170
|
const fields = fieldMap(definition);
|
|
1083
|
-
const dropped = /* @__PURE__ */ new Set();
|
|
1084
1171
|
const accept = (id) => {
|
|
1085
|
-
if (!fields.has(id)) return void ignored.
|
|
1172
|
+
if (!fields.has(id)) return void ignored.add(id);
|
|
1086
1173
|
if (!withheld(definition, fields.get(id))) return true;
|
|
1087
|
-
|
|
1088
|
-
dropped.add(id);
|
|
1174
|
+
ignored.add(id);
|
|
1089
1175
|
return false;
|
|
1090
1176
|
};
|
|
1091
1177
|
const engineHidden = { ...hidden, ...context };
|
|
@@ -1138,15 +1224,25 @@ function validateSubmission(definition, submission, options = {}) {
|
|
|
1138
1224
|
if (message === null) answers[id] = normalizeValue(value, field);
|
|
1139
1225
|
}
|
|
1140
1226
|
}
|
|
1227
|
+
const store = (values, report) => {
|
|
1228
|
+
if (definition.settings?.anonymous !== true) return values;
|
|
1229
|
+
const identifying = new Set((definition.hidden ?? []).filter((h) => h.identifying === true).map((h) => h.name));
|
|
1230
|
+
const kept = {};
|
|
1231
|
+
for (const [name, value] of Object.entries(values)) {
|
|
1232
|
+
if (!identifying.has(name)) kept[name] = value;
|
|
1233
|
+
else if (report) ignored.add(`hidden.${name}`);
|
|
1234
|
+
}
|
|
1235
|
+
return kept;
|
|
1236
|
+
};
|
|
1141
1237
|
return {
|
|
1142
1238
|
ok: true,
|
|
1143
1239
|
submission: {
|
|
1144
1240
|
status: complete ? "completed" : "draft",
|
|
1145
1241
|
answers,
|
|
1146
|
-
hidden,
|
|
1147
|
-
context,
|
|
1242
|
+
hidden: store(hidden, true),
|
|
1243
|
+
context: store(context, false),
|
|
1148
1244
|
ending: complete ? state.ending : null,
|
|
1149
|
-
ignored
|
|
1245
|
+
ignored: [...ignored]
|
|
1150
1246
|
}
|
|
1151
1247
|
};
|
|
1152
1248
|
}
|