@atscript/ui 0.1.102 → 0.1.104

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/dist/index.mjs CHANGED
@@ -29,7 +29,7 @@ const UI_FORM_SUFFIX_ICON = "ui.form.suffix.icon";
29
29
  const UI_TABLE_WIDTH = "ui.table.width";
30
30
  const UI_TABLE_COMPONENT = "ui.table.component";
31
31
  const UI_TABLE_SELECT_WITH = "ui.table.selectWith";
32
- const UI_TABLE_HIDDEN = "ui.table.hidden";
32
+ const UI_TABLE_EXCLUDE = "ui.table.exclude";
33
33
  const UI_TABLE_ATTR = "ui.table.attr";
34
34
  const UI_TABLE_CLASSES = "ui.table.classes";
35
35
  const UI_TABLE_STYLES = "ui.table.styles";
@@ -810,6 +810,35 @@ function setByPath(obj, path, value) {
810
810
  }
811
811
  current[last] = value;
812
812
  }
813
+ /**
814
+ * Deletes the own key at a dot-separated path (form-data wrapper aware — derefs
815
+ * `obj.value` first). Walks to the parent WITHOUT vivifying intermediate nodes:
816
+ * if any ancestor is missing, the call is a no-op (nothing to delete).
817
+ *
818
+ * Unlike `setByPath(obj, path, undefined)`, this leaves NO own key behind — the
819
+ * leaf reads as absent (`'k' in parent === false`), which keeps `deepEqual`
820
+ * structural comparisons in sync (a present `undefined` own-key and an absent
821
+ * key are NOT structurally equal under the own-key walk). Used by
822
+ * {@link applyFormChanges} to apply a clear-to-`undefined` change as a delete.
823
+ *
824
+ * Empty path clears the root domain value (`obj.value = undefined`).
825
+ */
826
+ function deleteByPath(obj, path) {
827
+ if (!path) {
828
+ obj.value = void 0;
829
+ return;
830
+ }
831
+ const keys = path.split(".");
832
+ const last = keys.pop();
833
+ if (last === void 0) return;
834
+ let current = obj.value;
835
+ for (const key of keys) {
836
+ if (current === null || current === void 0 || typeof current !== "object") return;
837
+ current = current[key];
838
+ }
839
+ if (current === null || current === void 0 || typeof current !== "object") return;
840
+ delete current[last];
841
+ }
813
842
  function parseStaticDefault(raw, prop) {
814
843
  if (typeof raw !== "string") return raw;
815
844
  if (prop.type.kind === "" && prop.type.designType === "string") return raw;
@@ -883,8 +912,11 @@ function detectUnionVariant(value, variants) {
883
912
  const disc = getVariantsDiscriminator(variants);
884
913
  if (disc && value !== null && typeof value === "object") {
885
914
  const tag = value[disc.propertyName];
886
- const idx = disc.indexMapping[String(tag)];
887
- if (idx !== void 0) return idx;
915
+ const key = String(tag);
916
+ if (Object.prototype.hasOwnProperty.call(disc.indexMapping, key)) {
917
+ const idx = disc.indexMapping[key];
918
+ if (idx !== void 0) return idx;
919
+ }
888
920
  }
889
921
  for (let i = 0; i < variants.length; i++) try {
890
922
  if (getVariantValidator(variants[i]).validate(value, true)) return i;
@@ -892,6 +924,35 @@ function detectUnionVariant(value, variants) {
892
924
  return 0;
893
925
  }
894
926
  //#endregion
927
+ //#region src/form/clone.ts
928
+ /**
929
+ * Structural deep clone of plain JSON-ish data (objects / arrays / primitives /
930
+ * `Date`). Walks OWN-ENUMERABLE keys only (matches the own-key discipline in
931
+ * `diff.ts` — never copies an accidental prototype) and copies leaves by value.
932
+ *
933
+ * `structuredClone` is deliberately NOT used: it throws on functions and on Vue
934
+ * reactive proxies. The optional `unwrap` hook lets a framework caller
935
+ * de-proxy each value first (vue-form passes `toRaw`); the core omits it.
936
+ *
937
+ * The SINGLE deep-clone primitive for the form engine — used by
938
+ * `applyFormChanges`, `buildFormRebase`, and vue-form's baseline snapshot. Do
939
+ * not reimplement structural cloning elsewhere.
940
+ */
941
+ function deepClone(value, unwrap) {
942
+ const v = unwrap ? unwrap(value) : value;
943
+ if (v === null || typeof v !== "object") return v;
944
+ if (v instanceof Date) return new Date(v.getTime());
945
+ if (Array.isArray(v)) {
946
+ const out = [];
947
+ for (let i = 0; i < v.length; i++) out.push(deepClone(v[i], unwrap));
948
+ return out;
949
+ }
950
+ const src = v;
951
+ const out = {};
952
+ for (const k of Object.keys(src)) out[k] = deepClone(src[k], unwrap);
953
+ return out;
954
+ }
955
+ //#endregion
895
956
  //#region src/form/validate.ts
896
957
  let defaultValidatorPlugins = [];
897
958
  /** Replace the default validator plugins applied to every form/field validator. */
@@ -947,6 +1008,661 @@ function createFieldValidator(prop, opts) {
947
1008
  };
948
1009
  }
949
1010
  //#endregion
1011
+ //#region src/form/diff.ts
1012
+ /**
1013
+ * Diffs a form's `current` data against its `baseline` snapshot, producing both
1014
+ * a changed-fields list and an `@atscript/db` patch object.
1015
+ *
1016
+ * Both `baseline` and `current` are the WRAPPED form-data container
1017
+ * (`{ value: domainData }`) so this reuses {@link getByPath}.
1018
+ *
1019
+ * Revert-aware: a value edited back to its baseline produces no change and no
1020
+ * patch entry.
1021
+ *
1022
+ * Snapshot contract: the result is NOT a deep copy. `$insert` items, `$replace`
1023
+ * arrays, scalar leaf values, and `changes[].before/after` all hold live
1024
+ * references into `baseline` / `current`. Callers that keep editing the form
1025
+ * after building the patch must snapshot first (e.g. build the patch at submit
1026
+ * time on a frozen clone). This is the common Vue v-model flow.
1027
+ */
1028
+ function buildFormDiff(def, baseline, current, opts) {
1029
+ const changes = [];
1030
+ const patch = {};
1031
+ const versionColumn = findVersionColumn(def);
1032
+ diffFields(def.fields, "", baseline, current, changes, patch, versionColumn, def.flatMap);
1033
+ if ((opts?.cas ?? true) && versionColumn && Object.keys(patch).length > 0) {
1034
+ const baselineVersion = getByPath(baseline, versionColumn);
1035
+ if (typeof baselineVersion === "number" && Number.isInteger(baselineVersion)) patch.$cas = { [versionColumn]: baselineVersion };
1036
+ }
1037
+ return {
1038
+ isDirty: changes.length > 0,
1039
+ changes,
1040
+ patch
1041
+ };
1042
+ }
1043
+ /**
1044
+ * Diffs a list of sibling fields. `prefix` is the dot-path of the parent
1045
+ * context relative to the form root (used only for the change list `path`);
1046
+ * patch entries are written into the local `patch` object so callers can place
1047
+ * the whole sub-object as a nested partial.
1048
+ *
1049
+ * `versionColumn` is the top-level `@db.column.version` field name (or
1050
+ * undefined). When set, the matching top-level field is skipped entirely — it
1051
+ * is server-managed and may only be round-tripped via `$cas`.
1052
+ *
1053
+ * `inlineFlatMap` is the form's `flatMap`, supplied only at the top-level walk.
1054
+ * It lets {@link diffScalarField} read `@db.patch.strategy` off the object
1055
+ * ancestors of an INLINED leaf (a dotted-path leaf with no `FormObjectFieldDef`
1056
+ * node — `createFormDef` dissolves unlabelled objects into dot-paths). At a
1057
+ * default (replace) ancestor the whole sub-object must be emitted. It is
1058
+ * intentionally NOT propagated into `diffObjectField` recursion, where the
1059
+ * strategy decision is already made per structured object.
1060
+ */
1061
+ function diffFields(fields, prefix, baseline, current, changes, patch, versionColumn, inlineFlatMap) {
1062
+ for (const field of fields) {
1063
+ if (field.path === "" && fields.length === 1) {
1064
+ diffLeafRoot(field, baseline, current, changes, patch);
1065
+ continue;
1066
+ }
1067
+ if (field.phantom) continue;
1068
+ if (versionColumn !== void 0 && !prefix && field.path === versionColumn) continue;
1069
+ const fullPath = prefix ? `${prefix}.${field.path}` : field.path;
1070
+ if (isArrayField(field)) {
1071
+ diffArrayField(field, fullPath, baseline, current, changes, patch);
1072
+ continue;
1073
+ }
1074
+ if (isObjectField(field)) {
1075
+ diffObjectField(field, fullPath, baseline, current, changes, patch);
1076
+ continue;
1077
+ }
1078
+ diffScalarField(field, fullPath, baseline, current, changes, patch, inlineFlatMap);
1079
+ }
1080
+ }
1081
+ /** Single-leaf root form (non-object root). Whole value is the patch. */
1082
+ function diffLeafRoot(field, baseline, current, changes, patch) {
1083
+ const before = getByPath(baseline, "");
1084
+ const after = getByPath(current, "");
1085
+ if (deepEqual(before, after)) return;
1086
+ if (isArrayField(field)) {
1087
+ const arrayPatch = diffArray(field, before, after);
1088
+ if (arrayPatch === void 0) return;
1089
+ changes.push({
1090
+ path: "",
1091
+ kind: "array",
1092
+ before,
1093
+ after
1094
+ });
1095
+ patch.value = arrayPatch;
1096
+ } else {
1097
+ changes.push({
1098
+ path: "",
1099
+ kind: "set",
1100
+ before,
1101
+ after
1102
+ });
1103
+ patch.value = after === void 0 ? null : after;
1104
+ }
1105
+ }
1106
+ /** Scalar / union / tuple / ref field — whole-value compare; clear → null. */
1107
+ function diffScalarField(field, fullPath, baseline, current, changes, patch, inlineFlatMap) {
1108
+ const before = getByPath(baseline, fullPath);
1109
+ const after = getByPath(current, fullPath);
1110
+ if (deepEqual(before, after)) return;
1111
+ changes.push({
1112
+ path: fullPath,
1113
+ kind: "set",
1114
+ before,
1115
+ after
1116
+ });
1117
+ if (inlineFlatMap && field.path.includes(".")) {
1118
+ const cutoff = inlinedReplaceCutoff(field.path, inlineFlatMap);
1119
+ if (cutoff !== void 0) {
1120
+ const prefixLen = fullPath.length - field.path.length;
1121
+ const sub = getByPath(current, prefixLen > 0 ? fullPath.slice(0, prefixLen) + cutoff : cutoff);
1122
+ setPatchLeaf(patch, cutoff, sub === void 0 ? null : sub);
1123
+ return;
1124
+ }
1125
+ }
1126
+ setPatchLeaf(patch, field.path, after === void 0 ? null : after);
1127
+ }
1128
+ /**
1129
+ * For a dotted INLINED leaf path, returns the path of the shallowest object
1130
+ * ancestor whose `@db.patch.strategy` is the default (`replace`), or undefined
1131
+ * when every ancestor is `merge` (then the leaf partial is correct). At a
1132
+ * replace ancestor the whole sub-object must be present in the patch.
1133
+ *
1134
+ * Walks ancestor segments (`a`, `a.b`, … but not the leaf itself); the first
1135
+ * one that is an object AND not merge is the cutoff. merge does NOT propagate,
1136
+ * so a default-replace level below a merge level still cuts off there.
1137
+ */
1138
+ function inlinedReplaceCutoff(leafPath, flatMap) {
1139
+ const segs = leafPath.split(".");
1140
+ let acc = "";
1141
+ for (let i = 0; i < segs.length - 1; i++) {
1142
+ acc = acc ? `${acc}.${segs[i]}` : segs[i];
1143
+ const prop = flatMap.get(acc);
1144
+ if (!prop || prop.type.kind !== "object") continue;
1145
+ if (getFieldMeta(prop, "db.patch.strategy") !== "merge") return acc;
1146
+ }
1147
+ }
1148
+ /**
1149
+ * Inlined-or-structured object field — recurse, emit a nested partial OR the
1150
+ * whole sub-object depending on the field's `@db.patch.strategy`.
1151
+ *
1152
+ * atscript-db's DEFAULT nested-object patch strategy is `replace` (strict —
1153
+ * every required child must be present, else 400; omitted optionals are
1154
+ * null-filled). A changed-leaves-only partial is a valid patch ONLY when the
1155
+ * object field carries `@db.patch.strategy 'merge'`. For the default (replace)
1156
+ * case we therefore emit the WHOLE current sub-object so the validator passes
1157
+ * and no optional leaf is silently nulled. `merge` does NOT propagate, so a
1158
+ * descendant object without its own `merge` again emits its full sub-object
1159
+ * (handled by recursion — `diffFields` re-enters this function per child).
1160
+ *
1161
+ * Wholesale-clear: if the sub-object was a defined object in `baseline` but is
1162
+ * now undefined/null, emit `field: null` (object removed) instead of a partial
1163
+ * of nulled leaves.
1164
+ */
1165
+ function diffObjectField(field, fullPath, baseline, current, changes, patch) {
1166
+ const beforeObj = getByPath(baseline, fullPath);
1167
+ const afterObj = getByPath(current, fullPath);
1168
+ const nested = {};
1169
+ diffFields(field.objectDef.fields, fullPath, baseline, current, changes, nested, void 0, field.objectDef.flatMap);
1170
+ if ((afterObj === void 0 || afterObj === null) && isPlainObject(beforeObj)) {
1171
+ setPatchLeaf(patch, field.path, null);
1172
+ return;
1173
+ }
1174
+ if (Object.keys(nested).length === 0) return;
1175
+ if (getFieldMeta(field.prop, "db.patch.strategy") === "merge") setPatchLeaf(patch, field.path, nested);
1176
+ else setPatchLeaf(patch, field.path, afterObj === void 0 ? null : afterObj);
1177
+ }
1178
+ /** Array field — keyed → $update/$insert/$remove; unkeyed → $replace. */
1179
+ function diffArrayField(field, fullPath, baseline, current, changes, patch) {
1180
+ const before = getByPath(baseline, fullPath);
1181
+ const after = getByPath(current, fullPath);
1182
+ if (deepEqual(before, after)) return;
1183
+ const arrayPatch = diffArray(field, before, after);
1184
+ if (arrayPatch === void 0) return;
1185
+ changes.push({
1186
+ path: fullPath,
1187
+ kind: "array",
1188
+ before,
1189
+ after
1190
+ });
1191
+ setPatchLeaf(patch, field.path, arrayPatch);
1192
+ }
1193
+ /**
1194
+ * Produces a `TArrayPatch` value for one array field, or `undefined` when no
1195
+ * real op results (the caller then skips the field entirely).
1196
+ *
1197
+ * - Keyed arrays (item object has `@expect.array.key`): emit `$update`
1198
+ * (key + changed leaves), `$insert` (wholly-new items, whole), `$remove`
1199
+ * (key only). Reorder-only (same key membership, same content, different
1200
+ * order) → `$replace` (key-ops can't express a pure reorder).
1201
+ * Ambiguous keys (duplicate or missing key values) → `$replace` (the only
1202
+ * faithful op when key identity is unreliable).
1203
+ * - Unkeyed object arrays / primitive arrays: `$replace` with the whole array.
1204
+ * Primitive arrays with `@expect.array.uniqueItems` use by-value
1205
+ * `$insert` / `$remove` (set semantics).
1206
+ *
1207
+ * Deliberate `$insert`-not-`$upsert`: wholly-new keyed items use `$insert`
1208
+ * (pure append) rather than `$upsert`. This is safe because `$insert` is only
1209
+ * ever used for keys ABSENT from baseline; existing keys go through `$update`.
1210
+ * `$upsert` would dedupe-by-key, which we don't need given that invariant.
1211
+ */
1212
+ function diffArray(field, before, after) {
1213
+ const beforeArr = Array.isArray(before) ? before : [];
1214
+ const afterArr = Array.isArray(after) ? after : [];
1215
+ const keyProps = getArrayKeyProps(field.itemType);
1216
+ if (keyProps.length > 0) return diffKeyedArray(beforeArr, afterArr, keyProps);
1217
+ if (getFieldMeta(field.prop, "expect.array.uniqueItems") !== void 0 && isPrimitiveItem(field)) {
1218
+ const setPatch = diffUniqueArray(beforeArr, afterArr);
1219
+ if (setPatch) return setPatch;
1220
+ }
1221
+ return { $replace: afterArr };
1222
+ }
1223
+ /**
1224
+ * Keyed array diff. Reorder-only (same membership) falls back to $replace.
1225
+ *
1226
+ * Returns `undefined` when no $update/$insert/$remove op is produced (so the
1227
+ * caller skips the field rather than emitting a malformed empty `{}`).
1228
+ *
1229
+ * Ambiguity fallback: if either side has DUPLICATE key buckets, or ANY element
1230
+ * is missing all of its key values, key identity is unreliable — fall back to
1231
+ * `{ $replace: after }`, the only faithful op (last-write-wins collapse would
1232
+ * silently drop items, and a key-less `$update` is unmatchable by the DB).
1233
+ */
1234
+ function diffKeyedArray(before, after, keyProps) {
1235
+ if (hasKeylessItem(before, keyProps) || hasKeylessItem(after, keyProps)) return { $replace: after };
1236
+ const beforeByKey = /* @__PURE__ */ new Map();
1237
+ for (const el of before) if (isPlainObject(el)) beforeByKey.set(keyOf(el, keyProps), el);
1238
+ const afterByKey = /* @__PURE__ */ new Map();
1239
+ for (const el of after) if (isPlainObject(el)) afterByKey.set(keyOf(el, keyProps), el);
1240
+ if (beforeByKey.size !== before.length || afterByKey.size !== after.length) return { $replace: after };
1241
+ if (beforeByKey.size === afterByKey.size) {
1242
+ let sameMembershipAndContent = true;
1243
+ for (const [k, el] of afterByKey) {
1244
+ const prev = beforeByKey.get(k);
1245
+ if (prev === void 0 || !deepEqual(prev, el)) {
1246
+ sameMembershipAndContent = false;
1247
+ break;
1248
+ }
1249
+ }
1250
+ if (sameMembershipAndContent) return { $replace: after };
1251
+ }
1252
+ const $insert = [];
1253
+ const $update = [];
1254
+ const $remove = [];
1255
+ for (const [k, el] of afterByKey) {
1256
+ const prev = beforeByKey.get(k);
1257
+ if (prev === void 0) {
1258
+ $insert.push(el);
1259
+ continue;
1260
+ }
1261
+ if (!deepEqual(prev, el)) {
1262
+ const partial = buildKeyedUpdate(prev, el, keyProps);
1263
+ if (partial) $update.push(partial);
1264
+ }
1265
+ }
1266
+ for (const [k, el] of beforeByKey) if (!afterByKey.has(k)) $remove.push(pickKeys(el, keyProps));
1267
+ return arrayOps({
1268
+ $update,
1269
+ $insert,
1270
+ $remove
1271
+ });
1272
+ }
1273
+ /** Builds a `$update` partial: key fields + changed leaves only. */
1274
+ function buildKeyedUpdate(prev, next, keyProps) {
1275
+ const partial = {};
1276
+ for (const k of keyProps) partial[k] = next[k];
1277
+ let changed = false;
1278
+ const allKeys = new Set([...Object.keys(prev), ...Object.keys(next)]);
1279
+ for (const k of allKeys) {
1280
+ if (keyProps.includes(k)) continue;
1281
+ const a = prev[k];
1282
+ const b = next[k];
1283
+ if (!deepEqual(a, b)) {
1284
+ partial[k] = b === void 0 ? null : b;
1285
+ changed = true;
1286
+ }
1287
+ }
1288
+ return changed ? partial : void 0;
1289
+ }
1290
+ /** Primitive uniqueItems set diff. Returns undefined if neither side differs. */
1291
+ function diffUniqueArray(before, after) {
1292
+ const beforeSet = new Set(before.map((v) => stableKey(v)));
1293
+ const afterSet = new Set(after.map((v) => stableKey(v)));
1294
+ const $insert = [];
1295
+ const $remove = [];
1296
+ const seenInsert = /* @__PURE__ */ new Set();
1297
+ const seenRemove = /* @__PURE__ */ new Set();
1298
+ for (const v of after) {
1299
+ const k = stableKey(v);
1300
+ if (!beforeSet.has(k) && !seenInsert.has(k)) {
1301
+ $insert.push(v);
1302
+ seenInsert.add(k);
1303
+ }
1304
+ }
1305
+ for (const v of before) {
1306
+ const k = stableKey(v);
1307
+ if (!afterSet.has(k) && !seenRemove.has(k)) {
1308
+ $remove.push(v);
1309
+ seenRemove.add(k);
1310
+ }
1311
+ }
1312
+ return arrayOps({
1313
+ $insert,
1314
+ $remove
1315
+ });
1316
+ }
1317
+ /**
1318
+ * Assembles a `TArrayPatch` from named op-arrays, dropping empty ones. Returns
1319
+ * `undefined` when no op carries any item, so the caller skips the field rather
1320
+ * than emitting a malformed empty `{}`.
1321
+ */
1322
+ function arrayOps(ops) {
1323
+ const result = {};
1324
+ for (const k in ops) if (ops[k].length > 0) result[k] = ops[k];
1325
+ return Object.keys(result).length > 0 ? result : void 0;
1326
+ }
1327
+ /** Reads `@expect.array.key` props from an array item's object type. */
1328
+ function getArrayKeyProps(itemType) {
1329
+ if (itemType.type.kind !== "object") return [];
1330
+ const props = itemType.type.props;
1331
+ const keys = [];
1332
+ for (const [name, prop] of props.entries()) if (getFieldMeta(prop, "expect.array.key") !== void 0) keys.push(name);
1333
+ return keys;
1334
+ }
1335
+ /** True when the array's item type is a primitive (designType, kind === ''). */
1336
+ function isPrimitiveItem(field) {
1337
+ return field.itemType.type.kind === "";
1338
+ }
1339
+ /** Finds the form's `@db.column.version` column name, if any (top-level only). */
1340
+ function findVersionColumn(def) {
1341
+ for (const [path, prop] of def.flatMap.entries()) {
1342
+ if (!path || path.includes(".")) continue;
1343
+ if (getFieldMeta(prop, "db.column.version") !== void 0) return path;
1344
+ }
1345
+ }
1346
+ /** True when any element lacks ALL of its key values (un-keyable identity). */
1347
+ function hasKeylessItem(arr, keyProps) {
1348
+ for (const el of arr) {
1349
+ if (!isPlainObject(el)) return true;
1350
+ let hasAnyKey = false;
1351
+ for (const k of keyProps) {
1352
+ const v = el[k];
1353
+ if (v !== void 0 && v !== null) {
1354
+ hasAnyKey = true;
1355
+ break;
1356
+ }
1357
+ }
1358
+ if (!hasAnyKey) return true;
1359
+ }
1360
+ return false;
1361
+ }
1362
+ /** Composite key string for a keyed-array element. */
1363
+ function keyOf(el, keyProps) {
1364
+ if (keyProps.length === 1) return stableKey(el[keyProps[0]]);
1365
+ return keyProps.map((k) => stableKey(el[k])).join(" ");
1366
+ }
1367
+ /** Picks only the key fields from an element (for $remove). */
1368
+ function pickKeys(el, keyProps) {
1369
+ const out = {};
1370
+ for (const k of keyProps) out[k] = el[k];
1371
+ return out;
1372
+ }
1373
+ /** Writes a value into a (possibly dotted) leaf path on a local patch object. */
1374
+ function setPatchLeaf(patch, path, value) {
1375
+ if (!path.includes(".")) {
1376
+ patch[path] = value;
1377
+ return;
1378
+ }
1379
+ const keys = path.split(".");
1380
+ const last = keys.pop();
1381
+ let cur = patch;
1382
+ for (const k of keys) {
1383
+ let next = cur[k];
1384
+ if (next === void 0 || next === null || typeof next !== "object") {
1385
+ next = {};
1386
+ cur[k] = next;
1387
+ }
1388
+ cur = next;
1389
+ }
1390
+ cur[last] = value;
1391
+ }
1392
+ function isPlainObject(v) {
1393
+ return typeof v === "object" && v !== null && !Array.isArray(v);
1394
+ }
1395
+ /**
1396
+ * Stable string key for primitives / values (used for set membership).
1397
+ *
1398
+ * Key equality is TYPE-STRICT: a number `1` and a string `'1'` produce
1399
+ * distinct keys, so a keyed-array item whose key changes JS representation
1400
+ * between baseline and current is treated as a remove + insert. Callers that
1401
+ * round-trip keys with loose typing should normalise the key type first.
1402
+ */
1403
+ function stableKey(v) {
1404
+ if (typeof v === "string") return `s:${v}`;
1405
+ if (typeof v === "number" || typeof v === "boolean") return `p:${String(v)}`;
1406
+ if (v === null) return "null";
1407
+ if (v === void 0) return "undef";
1408
+ return `j:${JSON.stringify(v)}`;
1409
+ }
1410
+ /**
1411
+ * Structural deep equality (order-sensitive for arrays). `NaN` equals `NaN`
1412
+ * (revert-aware for NaN scalars) while `0` / `-0` stay equal (matches DB
1413
+ * intent — `===` treats them equal, only NaN is special-cased).
1414
+ *
1415
+ * The single comparator shared across the form engine: diff, conflict
1416
+ * detection ({@link buildFormRebase}), and apply all route through this — never
1417
+ * reimplement equality elsewhere.
1418
+ */
1419
+ function deepEqual(a, b) {
1420
+ if (a === b) return true;
1421
+ if (typeof a === "number" && typeof b === "number") return Number.isNaN(a) && Number.isNaN(b);
1422
+ if (a === null || b === null || a === void 0 || b === void 0) return false;
1423
+ if (typeof a !== "object" || typeof b !== "object") return false;
1424
+ const aIsArr = Array.isArray(a);
1425
+ const bIsArr = Array.isArray(b);
1426
+ if (aIsArr !== bIsArr) return false;
1427
+ if (aIsArr && bIsArr) {
1428
+ if (a.length !== b.length) return false;
1429
+ for (let i = 0; i < a.length; i++) if (!deepEqual(a[i], b[i])) return false;
1430
+ return true;
1431
+ }
1432
+ const ao = a;
1433
+ const bo = b;
1434
+ const aKeys = Object.keys(ao);
1435
+ const bKeys = Object.keys(bo);
1436
+ if (aKeys.length !== bKeys.length) return false;
1437
+ for (const k of aKeys) {
1438
+ if (!Object.prototype.hasOwnProperty.call(bo, k)) return false;
1439
+ if (!deepEqual(ao[k], bo[k])) return false;
1440
+ }
1441
+ return true;
1442
+ }
1443
+ //#endregion
1444
+ //#region src/form/dirty.ts
1445
+ /**
1446
+ * True when the field at dot-path `path` is dirty given a {@link FormFieldChange}
1447
+ * list (as produced by {@link buildFormDiff}).
1448
+ *
1449
+ * The change list is leaf-grained for scalars/objects but WHOLE-ARRAY for arrays,
1450
+ * so a field at `path` is dirty iff some change path equals `path` OR starts with
1451
+ * `path + "."`:
1452
+ *
1453
+ * - scalar / leaf field (incl. nested `address.city`) → exact match.
1454
+ * - object / section container → no entry at its own path, only its leaves →
1455
+ * matched by the PREFIX branch.
1456
+ * - whole-array field → one entry at the array root → exact match.
1457
+ * - a field rendered for an array-ITEM leaf (e.g. `items.0.qty`) → NOT detectable:
1458
+ * the array diff emits a single whole-array change at the array root, never
1459
+ * per-item leaf paths, so this correctly returns false (the array container
1460
+ * lights up instead). This is a known, documented limitation.
1461
+ *
1462
+ * The prefix uses `path + "."` so field `item` never matches a change at `items`
1463
+ * (no false positives).
1464
+ *
1465
+ * Empty `path` `''` is the wrapped form root — every change is nested under it,
1466
+ * so it is considered dirty iff there are ANY changes.
1467
+ */
1468
+ function isPathDirty(changes, path) {
1469
+ if (path === "") return changes.length > 0;
1470
+ const prefix = `${path}.`;
1471
+ for (const change of changes) if (change.path === path || change.path.startsWith(prefix)) return true;
1472
+ return false;
1473
+ }
1474
+ /**
1475
+ * Precomputes the set of ALL dirty paths from a {@link FormFieldChange} list so
1476
+ * that membership is an O(1) `Set.has(path)` instead of {@link isPathDirty}'s
1477
+ * per-call O(changes) prefix scan. Callers that probe many fields against the
1478
+ * same change list (e.g. a form rendering one field per leaf) build this once
1479
+ * and query it per field.
1480
+ *
1481
+ * For each change path `C` it adds `C` AND every dot-prefix ancestor of `C`
1482
+ * (so `'address.city'` adds both `'address.city'` and `'address'`), matching
1483
+ * `isPathDirty`'s "exact OR `path + '.'` prefix" predicate — an ancestor
1484
+ * container is dirty exactly when some change is nested under it. The wrapped
1485
+ * root `''` is added iff there are ANY changes, mirroring `isPathDirty('')`.
1486
+ *
1487
+ * INVARIANT (locked, tested): for EVERY path `P`,
1488
+ * `collectDirtyPaths(changes).has(P) === isPathDirty(changes, P)`. This is a
1489
+ * precompute of the SAME predicate, not a second one — keep them in lockstep.
1490
+ */
1491
+ function collectDirtyPaths(changes) {
1492
+ const dirty = /* @__PURE__ */ new Set();
1493
+ if (changes.length === 0) return dirty;
1494
+ dirty.add("");
1495
+ for (const change of changes) {
1496
+ const path = change.path;
1497
+ dirty.add(path);
1498
+ let dot = path.indexOf(".");
1499
+ while (dot !== -1) {
1500
+ dirty.add(path.slice(0, dot));
1501
+ dot = path.indexOf(".", dot + 1);
1502
+ }
1503
+ }
1504
+ return dirty;
1505
+ }
1506
+ //#endregion
1507
+ //#region src/form/apply.ts
1508
+ /**
1509
+ * Applies a {@link FormFieldChange} list onto a WRAPPED form-data container
1510
+ * (`{ value: domainData }`), mutating it in place and returning the same
1511
+ * reference. The inverse direction of {@link buildFormDiff}: where the diff
1512
+ * READS `(baseline, current)` into changes, this WRITES changes onto data.
1513
+ *
1514
+ * IMPORTANT: pass a CLONE, never the live fetched row — every write mutates
1515
+ * `data` directly. Callers that need the original intact should
1516
+ * `deepClone(data)` first (see {@link deepClone}).
1517
+ *
1518
+ * Per-change semantics (the single place the apply rules live, so
1519
+ * {@link buildFormRebase} stays consistent):
1520
+ *
1521
+ * - `kind: 'set'`:
1522
+ * - `change.after === undefined` → DELETE the own key at `change.path` (walk
1523
+ * to parent, `delete`). A cleared field must read as ABSENT, not as a
1524
+ * present `undefined` own-key — otherwise a re-diff sees a structural
1525
+ * mismatch where the form intends "no value". `setByPath(…, undefined)`
1526
+ * leaves an own key behind, so we use {@link deleteByPath} instead.
1527
+ * - otherwise → `setByPath(data, change.path, change.after)`.
1528
+ * - `kind: 'array'`: whole-array set via `setByPath(data, change.path,
1529
+ * change.after)` (LOCKED Option A — no per-element merge; the diff already
1530
+ * carried the full after-array).
1531
+ *
1532
+ * The `def` is currently unused by the apply walk (paths fully describe the
1533
+ * write target) but is part of the signature for parity with
1534
+ * `buildFormDiff`/`buildFormRebase`, so the rebase engine threads one `def`
1535
+ * uniformly through diff + apply.
1536
+ */
1537
+ function applyFormChanges(_def, data, changes) {
1538
+ for (const change of changes) if (change.kind === "set" && change.after === void 0) deleteByPath(data, change.path);
1539
+ else setByPath(data, change.path, change.after);
1540
+ return data;
1541
+ }
1542
+ //#endregion
1543
+ //#region src/form/rebase.ts
1544
+ /**
1545
+ * Pure 3-way rebase for a change-tracked form. Given the current baseline `B0`,
1546
+ * the live form `C`, and a fresh upstream `U`, produces the form rewritten as
1547
+ * `U` + the local diff (`C` vs `B0`) reapplied on top:
1548
+ *
1549
+ * - Fields the user never touched adopt upstream's value.
1550
+ * - Local edits survive (reapplied onto the upstream clone).
1551
+ * - Fields changed on BOTH sides to different values are conflicts, resolved by
1552
+ * `opts.conflict` (`'ours'` keeps local, `'theirs'` takes upstream).
1553
+ *
1554
+ * All inputs are WRAPPED form-data containers (`{ value: domainData }`). The
1555
+ * result `next` is a fresh container; no input is mutated.
1556
+ *
1557
+ * `diffOptions` are forwarded to BOTH internal `buildFormDiff` passes so the
1558
+ * same field exclusions apply (notably the `@db.column.version` column and the
1559
+ * `$cas` policy) on the local and upstream sides — keep them identical to the
1560
+ * options the caller uses for its own change tracking.
1561
+ */
1562
+ function buildFormRebase(def, baseline, current, upstream, opts, diffOptions) {
1563
+ const conflictMode = opts?.conflict ?? "ours";
1564
+ const local = buildFormDiff(def, baseline, current, diffOptions).changes;
1565
+ const upstreamChanges = buildFormDiff(def, baseline, upstream, diffOptions).changes;
1566
+ const upstreamByPath = /* @__PURE__ */ new Map();
1567
+ for (const uc of upstreamChanges) upstreamByPath.set(uc.path, uc);
1568
+ const next = deepClone(upstream);
1569
+ const conflicts = [];
1570
+ for (const lc of local) {
1571
+ const clearedAncestor = findClearedAncestor(lc.path, baseline, upstream);
1572
+ if (clearedAncestor !== void 0) {
1573
+ conflicts.push(clearedAncestor);
1574
+ if (conflictMode === "ours") setByPath(next, clearedAncestor, deepClone(getByPath(current, clearedAncestor)));
1575
+ continue;
1576
+ }
1577
+ const uc = upstreamByPath.get(lc.path);
1578
+ if (uc !== void 0) {
1579
+ if (deepEqual(lc.after, uc.after)) continue;
1580
+ conflicts.push(lc.path);
1581
+ if (conflictMode === "ours") reapply(def, next, lc);
1582
+ continue;
1583
+ }
1584
+ reapply(def, next, lc);
1585
+ }
1586
+ const reapplied = buildFormDiff(def, upstream, deepClone(next), diffOptions).changes;
1587
+ return {
1588
+ next,
1589
+ conflicts: [...new Set(conflicts)],
1590
+ reapplied
1591
+ };
1592
+ }
1593
+ /**
1594
+ * Reapplies a single local change onto `next`, DEEP-CLONING its `after` value
1595
+ * first. `lc.after` is a LIVE reference into `current` (buildFormDiff holds live
1596
+ * refs), so for a `kind:'array'` or whole-object/union `set` change a raw apply
1597
+ * would make `next.value`'s node `===` `current.value`'s node — violating the
1598
+ * `FormRebaseResult.next` contract ("never aliases any input container"). The
1599
+ * ancestor-clear branch already deep-clones before writing; this keeps the two
1600
+ * leaf-reapply sites consistent.
1601
+ */
1602
+ function reapply(def, next, lc) {
1603
+ applyFormChanges(def, next, [{
1604
+ ...lc,
1605
+ after: deepClone(lc.after)
1606
+ }]);
1607
+ }
1608
+ /**
1609
+ * Returns the SHALLOWEST strict ancestor of `leafPath` that was an object/array
1610
+ * in `baseline` but is `null`/`undefined` in `upstream` (upstream cleared the
1611
+ * subtree), or `undefined` when no ancestor was cleared. The leaf path itself is
1612
+ * never considered an ancestor.
1613
+ */
1614
+ function findClearedAncestor(leafPath, baseline, upstream) {
1615
+ if (!leafPath.includes(".")) return void 0;
1616
+ const segs = leafPath.split(".");
1617
+ let acc = "";
1618
+ for (let i = 0; i < segs.length - 1; i++) {
1619
+ acc = acc ? `${acc}.${segs[i]}` : segs[i];
1620
+ const base = getByPath(baseline, acc);
1621
+ if (typeof base !== "object" || base === null) continue;
1622
+ const up = getByPath(upstream, acc);
1623
+ if (up === null || up === void 0) return acc;
1624
+ }
1625
+ }
1626
+ //#endregion
1627
+ //#region src/form/union-detect.ts
1628
+ /**
1629
+ * True when ANY union field in the form resolves to a DIFFERENT discriminated
1630
+ * variant between two wrapped data containers. A variant picker typically
1631
+ * detects its variant index once at setup and keys the variant subtree on it,
1632
+ * so a rebase that lands a different variant (via conflict OR an upstream-only
1633
+ * switch) needs a remount to re-detect. This walks union + nested-object fields
1634
+ * and compares `detectUnionVariant` at each union path.
1635
+ *
1636
+ * Scope note (pragmatic): walks standalone + nested-OBJECT union fields. Unions
1637
+ * nested INSIDE array items are not walked — an array renderer that keeps a
1638
+ * stable per-item key across in-place value mutations would not remount an
1639
+ * existing row's picker on an upstream-driven variant flip, but that collision
1640
+ * (a 3-way rebase landing a different union variant inside an unchanged array
1641
+ * row) is a rare edge. TODO: extend to array-item unions if a real consumer
1642
+ * hits a stuck picker inside an array row.
1643
+ */
1644
+ function unionVariantChanged(def, before, after) {
1645
+ return walkUnionFields(def.fields, "", before, after);
1646
+ }
1647
+ function walkUnionFields(fields, prefix, before, after) {
1648
+ for (const field of fields) {
1649
+ if (field.phantom) continue;
1650
+ const fullPath = field.path ? prefix ? `${prefix}.${field.path}` : field.path : prefix;
1651
+ if (isUnionField(field)) {
1652
+ const variants = field.unionVariants;
1653
+ if (variants.length > 1) {
1654
+ if (detectUnionVariant(getByPath(before, fullPath), variants) !== detectUnionVariant(getByPath(after, fullPath), variants)) return true;
1655
+ }
1656
+ continue;
1657
+ }
1658
+ if (isObjectField(field)) {
1659
+ const objectDef = field.objectDef;
1660
+ if (walkUnionFields(objectDef.fields, fullPath, before, after)) return true;
1661
+ }
1662
+ }
1663
+ return false;
1664
+ }
1665
+ //#endregion
950
1666
  //#region src/form/error-utils.ts
951
1667
  /**
952
1668
  * Framework-agnostic helpers for working with form-error maps keyed by
@@ -1431,6 +2147,7 @@ function createTableDef(meta, preDeserializedType) {
1431
2147
  const kind = prop.type.kind;
1432
2148
  if (path.includes(".") || kind === "object" || kind === "array") continue;
1433
2149
  }
2150
+ if (getFieldMeta(prop, "ui.table.exclude") !== void 0) continue;
1434
2151
  const fieldMeta = meta.fields[path];
1435
2152
  const options = extractLiteralOptions(prop);
1436
2153
  const valueHelpInfo = extractValueHelp(prop);
@@ -1447,7 +2164,6 @@ function createTableDef(meta, preDeserializedType) {
1447
2164
  sortable: fieldMeta?.sortable ?? false,
1448
2165
  filterable: fieldMeta?.filterable ?? false,
1449
2166
  nullable: prop.optional === true,
1450
- visible: getFieldMeta(prop, UI_TABLE_HIDDEN) === void 0,
1451
2167
  width: getFieldMeta(prop, UI_TABLE_WIDTH),
1452
2168
  maxLen: maxLengthMeta?.length,
1453
2169
  order: getFieldMeta(prop, "ui.table.order") ?? Infinity,
@@ -1463,6 +2179,7 @@ function createTableDef(meta, preDeserializedType) {
1463
2179
  type,
1464
2180
  columns,
1465
2181
  flatMap,
2182
+ fetchableFields: new Set(Object.keys(meta.fields)),
1466
2183
  primaryKeys: meta.primaryKeys,
1467
2184
  preferredId: meta.preferredId ?? meta.primaryKeys,
1468
2185
  versionColumn: meta.versionColumn,
@@ -1536,10 +2253,6 @@ function str(value) {
1536
2253
  }
1537
2254
  //#endregion
1538
2255
  //#region src/table/column-resolver.ts
1539
- /** Get visible columns only, already sorted by order. */
1540
- function getVisibleColumns(def) {
1541
- return def.columns.filter((c) => c.visible);
1542
- }
1543
2256
  /** Get sortable columns. */
1544
2257
  function getSortableColumns(def) {
1545
2258
  return def.columns.filter((c) => c.sortable);
@@ -1553,4 +2266,4 @@ function getColumn(def, path) {
1553
2266
  return def.columns.find((c) => c.path === path);
1554
2267
  }
1555
2268
  //#endregion
1556
- export { DB_AMOUNT_CURRENCY, DB_AMOUNT_CURRENCY_REF, DB_COLUMN_PRECISION, DB_HTTP_PATH, DB_REL_FK, DB_UNIT, DB_UNIT_REF, DEFAULT_COL_SPAN, DEFAULT_ROW_SPAN, EXPECT_MAX_LENGTH, META_DEFAULT, META_DESCRIPTION, META_ID, META_LABEL, META_READONLY, META_REQUIRED, META_SENSITIVE, StaticFieldResolver, UI_DICT_ATTR, UI_DICT_DESCR, UI_DICT_FILTERABLE, UI_DICT_LABEL, UI_DICT_SEARCHABLE, UI_DICT_SORTABLE, UI_FORM_ACTION, UI_FORM_ATTR, UI_FORM_AUTOCOMPLETE, UI_FORM_CLASSES, UI_FORM_COMPONENT, UI_FORM_DISABLED, UI_FORM_FN_ATTR, UI_FORM_FN_CLASSES, UI_FORM_FN_DESCRIPTION, UI_FORM_FN_DISABLED, UI_FORM_FN_HIDDEN, UI_FORM_FN_HINT, UI_FORM_FN_LABEL, UI_FORM_FN_OPTIONS, UI_FORM_FN_PLACEHOLDER, UI_FORM_FN_PREFIX, UI_FORM_FN_READONLY, UI_FORM_FN_STYLES, UI_FORM_FN_SUBMIT_DISABLED, UI_FORM_FN_SUBMIT_TEXT, UI_FORM_FN_TITLE, UI_FORM_FN_VALUE, UI_FORM_GRID_COL_SPAN, UI_FORM_GRID_ROW_SPAN, UI_FORM_HIDDEN, UI_FORM_HINT, UI_FORM_LABEL_SINGULAR, UI_FORM_OPTIONS, UI_FORM_ORDER, UI_FORM_PLACEHOLDER, UI_FORM_PREFIX, UI_FORM_PREFIX_ICON, UI_FORM_PREFIX_REF, UI_FORM_STYLES, UI_FORM_SUBMIT_TEXT, UI_FORM_SUFFIX, UI_FORM_SUFFIX_ICON, UI_FORM_SUFFIX_REF, UI_FORM_TYPE, UI_FORM_VALIDATE, UI_TABLE_ATTR, UI_TABLE_CLASSES, UI_TABLE_COMPONENT, UI_TABLE_FN_ATTR, UI_TABLE_FN_CLASSES, UI_TABLE_FN_PREFIX, UI_TABLE_FN_STYLES, UI_TABLE_HIDDEN, UI_TABLE_ORDER, UI_TABLE_SELECT_WITH, UI_TABLE_STYLES, UI_TABLE_TYPE, UI_TABLE_WIDTH, UI_TYPE, ValueHelpClient, WF_ACTION_WITH_DATA, asArray, buildDescendantErrorCounts, buildGridClasses, buildUnionVariants, createFieldValidator, createFormData, createFormDef, createFormValueResolver, createTableDef, defaultResolver, detectUnionVariant, enforceScale, extractLiteralOptions, extractMeasurement, extractValueHelp, formatDecimalForDisplay, getByPath, getColumn, getCurrencyDecimals, getCurrencyDisplayParts, getDecimalSeparator, getDeclaredFormActions, getDefaultClientFactory, getDefaultValidatorPlugins, getFieldMeta, getFilterableColumns, getFormValidator, getMetaEntry, getResolver, getSortableColumns, getThousandsSeparator, getVisibleColumns, groupInteger, hasComputedAnnotations, isArrayField, isObjectField, isPureLiteralUnion, isTupleField, isUnionField, iteratePathAncestors, joinDecimalString, mergeErrorMaps, optKey, optLabel, parseColSpan, parseDecimalInput, parseRowSpan, parseStaticAttrs, parseStaticOptions, resetDefaultClientFactory, resetMetaCache, resetValueHelpCache, resolveAttrs, resolveFieldProp, resolveFormProp, resolveGridSpec, resolveOptions, resolveSingularLabel, resolveStatic, resolveValueHelp, setByPath, setDefaultClientFactory, setDefaultValidatorPlugins, setResolver, splitDecimalString, str, valueHelpDictPaths };
2269
+ export { DB_AMOUNT_CURRENCY, DB_AMOUNT_CURRENCY_REF, DB_COLUMN_PRECISION, DB_HTTP_PATH, DB_REL_FK, DB_UNIT, DB_UNIT_REF, DEFAULT_COL_SPAN, DEFAULT_ROW_SPAN, EXPECT_MAX_LENGTH, META_DEFAULT, META_DESCRIPTION, META_ID, META_LABEL, META_READONLY, META_REQUIRED, META_SENSITIVE, StaticFieldResolver, UI_DICT_ATTR, UI_DICT_DESCR, UI_DICT_FILTERABLE, UI_DICT_LABEL, UI_DICT_SEARCHABLE, UI_DICT_SORTABLE, UI_FORM_ACTION, UI_FORM_ATTR, UI_FORM_AUTOCOMPLETE, UI_FORM_CLASSES, UI_FORM_COMPONENT, UI_FORM_DISABLED, UI_FORM_FN_ATTR, UI_FORM_FN_CLASSES, UI_FORM_FN_DESCRIPTION, UI_FORM_FN_DISABLED, UI_FORM_FN_HIDDEN, UI_FORM_FN_HINT, UI_FORM_FN_LABEL, UI_FORM_FN_OPTIONS, UI_FORM_FN_PLACEHOLDER, UI_FORM_FN_PREFIX, UI_FORM_FN_READONLY, UI_FORM_FN_STYLES, UI_FORM_FN_SUBMIT_DISABLED, UI_FORM_FN_SUBMIT_TEXT, UI_FORM_FN_TITLE, UI_FORM_FN_VALUE, UI_FORM_GRID_COL_SPAN, UI_FORM_GRID_ROW_SPAN, UI_FORM_HIDDEN, UI_FORM_HINT, UI_FORM_LABEL_SINGULAR, UI_FORM_OPTIONS, UI_FORM_ORDER, UI_FORM_PLACEHOLDER, UI_FORM_PREFIX, UI_FORM_PREFIX_ICON, UI_FORM_PREFIX_REF, UI_FORM_STYLES, UI_FORM_SUBMIT_TEXT, UI_FORM_SUFFIX, UI_FORM_SUFFIX_ICON, UI_FORM_SUFFIX_REF, UI_FORM_TYPE, UI_FORM_VALIDATE, UI_TABLE_ATTR, UI_TABLE_CLASSES, UI_TABLE_COMPONENT, UI_TABLE_EXCLUDE, UI_TABLE_FN_ATTR, UI_TABLE_FN_CLASSES, UI_TABLE_FN_PREFIX, UI_TABLE_FN_STYLES, UI_TABLE_ORDER, UI_TABLE_SELECT_WITH, UI_TABLE_STYLES, UI_TABLE_TYPE, UI_TABLE_WIDTH, UI_TYPE, ValueHelpClient, WF_ACTION_WITH_DATA, applyFormChanges, asArray, buildDescendantErrorCounts, buildFormDiff, buildFormRebase, buildGridClasses, buildUnionVariants, collectDirtyPaths, createFieldValidator, createFormData, createFormDef, createFormValueResolver, createTableDef, deepClone, deepEqual, defaultResolver, deleteByPath, detectUnionVariant, enforceScale, extractLiteralOptions, extractMeasurement, extractValueHelp, formatDecimalForDisplay, getByPath, getColumn, getCurrencyDecimals, getCurrencyDisplayParts, getDecimalSeparator, getDeclaredFormActions, getDefaultClientFactory, getDefaultValidatorPlugins, getFieldMeta, getFilterableColumns, getFormValidator, getMetaEntry, getResolver, getSortableColumns, getThousandsSeparator, groupInteger, hasComputedAnnotations, isArrayField, isObjectField, isPathDirty, isPureLiteralUnion, isTupleField, isUnionField, iteratePathAncestors, joinDecimalString, mergeErrorMaps, optKey, optLabel, parseColSpan, parseDecimalInput, parseRowSpan, parseStaticAttrs, parseStaticOptions, resetDefaultClientFactory, resetMetaCache, resetValueHelpCache, resolveAttrs, resolveFieldProp, resolveFormProp, resolveGridSpec, resolveOptions, resolveSingularLabel, resolveStatic, resolveValueHelp, setByPath, setDefaultClientFactory, setDefaultValidatorPlugins, setResolver, splitDecimalString, str, unionVariantChanged, valueHelpDictPaths };