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