form0-core 0.1.16 → 0.1.17

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 CHANGED
@@ -57,6 +57,9 @@ Record-side utilities follow this contract:
57
57
  - `normalizeStructuredRecord()` consumes and returns canonical stored records
58
58
  - `buildFormRecordSnapshot()` consumes canonical stored records and returns renderer snapshot values
59
59
  - `projectDatasetRowValues()` consumes canonical stored rows
60
+ - Choice `FIELD_SPECS` keep separate validators by context:
61
+ - `valueValidator` validates live engine / renderer values
62
+ - `recordValueValidator` validates canonical stored record values
60
63
 
61
64
  Record status remains top-level as `@status`; it is not stored inside `form_values`.
62
65
 
package/package.json CHANGED
@@ -1,15 +1,14 @@
1
1
  {
2
2
  "name": "form0-core",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org/"
6
6
  },
7
- "description": "Schema-driven engine that powers form0 ecosystem.",
7
+ "description": "Schema-driven engine that powers form0 ecosystem. Looking for the full form0 ecosystem? Install the CLI: `npm install -g form0-cli`",
8
8
  "main": "src/index.js",
9
9
  "type": "module",
10
10
  "scripts": {
11
11
  "test": "node tests/form0-core.test.js && node tests/dataset-descriptors.test.js && node tests/structured-record.test.js && node tests/builtin-metadata.test.js && node tests/calculation-editor.test.js && node tests/form-event-editor.test.js && node tests/security-test.js",
12
- "postinstall": "node scripts/postinstall.cjs",
13
12
  "format": "prettier --write \"**/*.{js,jsx,json,ts,tsx,md}\"",
14
13
  "format:check": "prettier --check \"**/*.{js,jsx,json,ts,tsx,md}\""
15
14
  },
@@ -1,3 +1,7 @@
1
+ // Legacy reference only.
2
+ // This script is intentionally not wired into package.json lifecycle hooks.
3
+ // It is retained as a reference for the previous install-time guidance message.
4
+
1
5
  const PACKAGE_NAME = 'form0-core';
2
6
 
3
7
  const npmUserAgent = process.env.npm_config_user_agent || '';
package/src/index.js CHANGED
@@ -6,6 +6,12 @@ export {
6
6
  buildFormRecordSnapshot,
7
7
  } from './utilities/structured-record.js';
8
8
  export { validateSchema } from './schema/schema-validator.js';
9
+ export {
10
+ validateFieldValue,
11
+ validateRecordFieldValue,
12
+ getFieldValueValidator,
13
+ getRecordFieldValueValidator,
14
+ } from './schema/field-value-registry.js';
9
15
  //export { validateSchema, resolveSupportingImagePath } from './schema/schema-validator.js';
10
16
  export { generateKey } from './utilities/hash.js';
11
17
  export {
@@ -1,4 +1,5 @@
1
1
  import { validateChoiceFieldChoices } from '../utilities/field-helpers.js';
2
+ import { validateStoredChoiceValue } from '../utilities/choice-value-shapes.js';
2
3
  import {
3
4
  isNonEmptyString,
4
5
  validateFormLinkRecordDefaults,
@@ -316,6 +317,10 @@ export const FIELD_SPECS = {
316
317
  }
317
318
  return null;
318
319
  },
320
+ recordValueValidator: (field, value) =>
321
+ validateStoredChoiceValue(field, value, {
322
+ context: 'FIELD_SPECS.recordValueValidator',
323
+ }),
319
324
  defaultProducer: (field) => {
320
325
  if (field.default_value) {
321
326
  const choice = field.choices.find((c) => c.value === field.default_value);
@@ -466,6 +471,10 @@ export const FIELD_SPECS = {
466
471
  }
467
472
  return null;
468
473
  },
474
+ recordValueValidator: (field, value) =>
475
+ validateStoredChoiceValue(field, value, {
476
+ context: 'FIELD_SPECS.recordValueValidator',
477
+ }),
469
478
  defaultProducer: (field) => {
470
479
  if (field.default_value) {
471
480
  const selectedChoices = field.choices
@@ -596,6 +605,10 @@ export const FIELD_SPECS = {
596
605
  }
597
606
  return null;
598
607
  },
608
+ recordValueValidator: (field, value) =>
609
+ validateStoredChoiceValue(field, value, {
610
+ context: 'FIELD_SPECS.recordValueValidator',
611
+ }),
599
612
  defaultProducer: (field) => {
600
613
  if (field.default_value) {
601
614
  const choice = field.choices.find((c) => c.value === field.default_value);
@@ -19,6 +19,29 @@ export function validateFieldValue(field, value) {
19
19
  return null;
20
20
  }
21
21
 
22
+ /**
23
+ * Validates a structured-record field value against its specification
24
+ * @param {object} field - The field definition
25
+ * @param {any} value - The stored record value to validate
26
+ * @returns {string|null} - Error message if validation fails, or null if valid
27
+ */
28
+ export function validateRecordFieldValue(field, value) {
29
+ const spec = FIELD_SPECS[field.type];
30
+ if (!spec) {
31
+ return `Unsupported field type: ${field.type}`;
32
+ }
33
+
34
+ if (typeof spec.recordValueValidator === 'function') {
35
+ return spec.recordValueValidator(field, value);
36
+ }
37
+
38
+ if (typeof spec.valueValidator === 'function') {
39
+ return spec.valueValidator(field, value);
40
+ }
41
+
42
+ return null;
43
+ }
44
+
22
45
  /**
23
46
  * Returns the value validator function for a field type
24
47
  * @param {string} fieldType - The field type
@@ -29,6 +52,25 @@ export function getFieldValueValidator(fieldType) {
29
52
  return spec ? spec.valueValidator : null;
30
53
  }
31
54
 
55
+ /**
56
+ * Returns the structured-record value validator function for a field type
57
+ * Falls back to the live value validator when no record-specific validator exists.
58
+ * @param {string} fieldType - The field type
59
+ * @returns {function|null} - The record value validator function or null if not found
60
+ */
61
+ export function getRecordFieldValueValidator(fieldType) {
62
+ const spec = FIELD_SPECS[fieldType];
63
+ if (!spec) {
64
+ return null;
65
+ }
66
+
67
+ if (typeof spec.recordValueValidator === 'function') {
68
+ return spec.recordValueValidator;
69
+ }
70
+
71
+ return typeof spec.valueValidator === 'function' ? spec.valueValidator : null;
72
+ }
73
+
32
74
  /**
33
75
  * Validates TextField value
34
76
  * @param {object} field - The field definition
@@ -213,6 +213,34 @@ export function normalizeStoredChoiceValue(field, rawValue, options = {}) {
213
213
  return normalized;
214
214
  }
215
215
 
216
+ export function validateStoredChoiceValue(field, rawValue, options = {}) {
217
+ try {
218
+ const normalized = normalizeStoredChoiceValue(field, rawValue, options);
219
+ const kind = getChoiceKind(field);
220
+ const fieldIdentifier = toFieldIdentifier(field);
221
+
222
+ if (normalized && kind === 'single' && toFieldType(field) === 'BooleanField') {
223
+ if (normalized.other_value.length > 0) {
224
+ return `${fieldIdentifier} does not support 'other_value' values`;
225
+ }
226
+ }
227
+
228
+ if (normalized && kind !== null && toFieldType(field) !== 'BooleanField') {
229
+ if (field.allow_other !== true && normalized.other_value.length > 0) {
230
+ return `${fieldIdentifier} does not allow 'other_value' values`;
231
+ }
232
+ }
233
+
234
+ return null;
235
+ } catch (error) {
236
+ if (error instanceof Error) {
237
+ return error.message.replace(/^\[form0\] [^:]+: /, '');
238
+ }
239
+
240
+ return 'stored choice value is invalid';
241
+ }
242
+ }
243
+
216
244
  export function toRendererChoiceValue(field, rawValue, options = {}) {
217
245
  const kind = getChoiceKind(field);
218
246
  if (!kind || rawValue === null || typeof rawValue === 'undefined') {