@whatsaaaa/form-builder-schema 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 CHANGED
@@ -11,12 +11,12 @@ pnpm add @whatsaaaa/form-builder-schema valibot
11
11
  ## Usage
12
12
 
13
13
  ```ts
14
- import { createSchemaBuilder, type JsonFormSchema } from '@whatsaaaa/form-builder-schema'
14
+ import { createSchemaBuilder, type JsonForm } from '@whatsaaaa/form-builder-schema'
15
15
 
16
- const json: JsonFormSchema = {
16
+ const json: JsonForm = {
17
17
  version: '1',
18
18
  fields: [
19
- { name: 'email', type: 'email', required: { message: 'Email is required' } },
19
+ { name: 'email', type: 'email', label: 'Email', required: { message: 'Email is required' } },
20
20
  {
21
21
  name: 'password',
22
22
  type: 'password',
@@ -26,13 +26,62 @@ const json: JsonFormSchema = {
26
26
  ],
27
27
  }
28
28
 
29
- const build = createSchemaBuilder()
29
+ const { build, validate } = createSchemaBuilder()
30
30
  const schema = build(json) // v.ObjectSchema, ready for Formisch or v.parse
31
31
  ```
32
32
 
33
- Built-in field types are `text`, `email`, `password` and `number`. Empty inputs of optional `number` fields (`''` or `undefined`) produce `undefined`.
33
+ Built-in field types are `text`, `email`, `password` and `number`. Built-in rules are `trim`, `minLength`, `maxLength`, `regex`, `email`, `url`, `minValue`, `maxValue` and `integer`. A rule without a `message` uses Valibot's message, so [`@valibot/i18n`](https://valibot.dev/guides/internationalization/) works.
34
34
 
35
- Custom field types and rules can be passed to `createSchemaBuilder({ fieldTypes, rules })`. Use `defineRule` to get typed rule params.
35
+ Empty optional fields output `undefined`. Rules never run on empty values, and a required empty field reports only the required message. `trim` runs before the required check, so `' '` counts as empty.
36
+
37
+ ## Checking definitions
38
+
39
+ `validate(json)` never throws. It returns every problem in the definition, in document order:
40
+
41
+ ```ts
42
+ const result = validate(jsonFromDatabase)
43
+ if (!result.success) {
44
+ for (const issue of result.issues) console.log(formatPath(issue.path), issue.code, issue.message)
45
+ }
46
+ ```
47
+
48
+ `build(json)` runs the same checks and throws one `DefinitionError` with all of them. Issue codes are `invalid_shape`, `unsupported_version`, `unknown_key`, `unknown_type`, `missing_name`, `duplicate_name`, `invalid_props`, `unknown_rule`, `unknown_param`, `invalid_params` and `kind_mismatch`.
49
+
50
+ Keys on a field must be reserved keys (`RESERVED_FIELD_KEYS`) or props of its type. Put renderer settings under `ui`, which is never checked.
51
+
52
+ ## Extending
53
+
54
+ `createSchemaBuilder({ kinds, fieldTypes, rules })` merges your entries over the defaults by key. The merged registries are returned as `kinds`, `fieldTypes` and `rules`.
55
+
56
+ ```ts
57
+ const select = defineFieldType({
58
+ kind: 'string',
59
+ props: v.object({ options: v.array(v.string()) }),
60
+ schema: (field) => v.picklist(field.options), // field.options is typed
61
+ })
62
+
63
+ const even = defineRule({
64
+ kinds: ['number'],
65
+ create: (_, message) => v.check((n: number) => n % 2 === 0, message),
66
+ })
67
+
68
+ const { build } = createSchemaBuilder({ fieldTypes: { select }, rules: { even } })
69
+ ```
70
+
71
+ - A **kind** says what "empty" means for a value (`isEmpty`) and adds an optional extra `required` check. Built-in kinds are `string`, `number`, `boolean`, `date`, `array`, `file`, `object` and `none`. A type with kind `none` has no value, like a heading.
72
+ - A **rule** with `stage: 'normalize'` runs before the required check. Its `params` schema lists the only keys the rule accepts.
73
+ - Mistakes in the registry, such as an unknown kind or a reserved props key, throw a `TypeError` from `createSchemaBuilder`.
74
+
75
+ ## Messages
76
+
77
+ ```ts
78
+ createSchemaBuilder({
79
+ messages: { required: 'Obavezno' },
80
+ resolveMessage: (key) => t(key), // every message from the definition, and messages.required
81
+ })
82
+ ```
83
+
84
+ `resolveMessage` returning `undefined` keeps the text as written.
36
85
 
37
86
  ## License
38
87
 
package/dist/index.d.ts CHANGED
@@ -1,64 +1,178 @@
1
1
  import * as v from "valibot";
2
2
  type AnySchema = v.GenericSchema;
3
3
  type AnyAction = v.GenericPipeAction;
4
- type ValueKind = 'string' | 'number';
5
- type FieldType = 'text' | 'email' | 'password' | 'number';
4
+ /**
5
+ * A `v.object()` schema. The builder reads its `entries` to find unknown keys.
6
+ */
7
+ type AnyObjectSchema = v.ObjectSchema<v.ObjectEntries, v.ErrorMessage<v.ObjectIssue> | undefined>;
8
+ interface KindDef {
9
+ /** True when the input means "no value". */
10
+ isEmpty(input: unknown): boolean;
11
+ /** Extra check for required fieldsd after normalize rules ran. Omit it when isEmpty is enough. */
12
+ required?(message: string): AnyAction | null;
13
+ }
14
+ /**
15
+ * The built-in kinds, plus any string so consumers can register their own.
16
+ */
17
+ type ValueKind = 'string' | 'number' | 'boolean' | 'date' | 'array' | 'file' | 'object' | 'none' | (string & {});
18
+ interface JsonForm {
19
+ version: '1';
20
+ fields: readonly JsonField[];
21
+ }
6
22
  interface JsonField {
7
- name: string;
8
- type: FieldType;
23
+ name?: string;
24
+ type: string;
25
+ label?: string;
26
+ description?: string;
27
+ placeholder?: string;
28
+ default?: unknown;
9
29
  required?: boolean | {
10
30
  message?: string;
11
31
  };
12
32
  rules?: readonly JsonRule[];
33
+ /** For renderers. This package never reads or checks it. */
34
+ ui?: Record<string, unknown>;
35
+ /** Validated by the field type's props schema, */
36
+ [prop: string]: unknown;
13
37
  }
14
38
  interface JsonRule {
15
39
  rule: string;
16
40
  message?: string;
17
41
  [param: string]: unknown;
18
42
  }
19
- interface JsonFormSchema {
20
- version: '1';
21
- fields: readonly JsonField[];
43
+ /** Where something is in a definition, e.g. `['fields', 0, 'rules', 1]`. */
44
+ type DefinitionPath = readonly (string | number)[];
45
+ /** Public API: adding a code is a minor change, renaming one is breaking, */
46
+ type DefinitionIssueCode = 'invalid_shape' | 'unsupported_version' | 'unknown_key' | 'unknown_type' | 'missing_name' | 'duplicate_name' | 'invalid_props' | 'unknown_rule' | 'unknown_param' | 'invalid_params' | 'kind_mismatch';
47
+ interface DefinitionIssue {
48
+ code: DefinitionIssueCode;
49
+ path: DefinitionPath;
50
+ message: string;
22
51
  }
23
- interface RuleDef<TParams extends AnySchema = AnySchema> {
24
- kinds: readonly ValueKind[];
25
- params: TParams;
26
- create(params: v.InferOutput<TParams>, message?: string): AnyAction;
52
+ type ValidateResult = {
53
+ success: true;
54
+ issues: [];
55
+ } | {
56
+ success: false;
57
+ issues: DefinitionIssue[];
58
+ };
59
+ export declare function formatPath(path: DefinitionPath): string;
60
+ /** Thrown by `build()` when the definition has issues. Lists all of them. */
61
+ export declare class DefinitionError extends Error {
62
+ readonly issues: readonly DefinitionIssue[];
63
+ constructor(issues: readonly DefinitionIssue[]);
27
64
  }
28
- interface FieldTypeDef {
65
+ interface FieldTypeDef<TProps extends AnyObjectSchema = AnyObjectSchema> {
29
66
  kind: ValueKind;
30
- base: (message?: string) => AnySchema;
67
+ /** Extra keys this type accepts on a field, e.g. `options` on a select. */
68
+ props?: TProps;
69
+ /**
70
+ * The base schema for the field's value. Required unless `kind` is `'none'`.
71
+ * Method syntax keeps it bivariant, so typed field types fit in a `Record<string, FieldTypeDef>`.
72
+ */
73
+ schema?(field: FieldWithProps<TProps>, ctx: BuildContext): AnySchema;
74
+ /** Rules every field of this type gets. A field rule with the same name replaces one. */
31
75
  defaultRules?: readonly JsonRule[];
32
76
  }
77
+ /** A field with its type's props parsed and typed. */
78
+ type FieldWithProps<TProps extends AnyObjectSchema = AnyObjectSchema> = JsonField & v.InferOutput<TProps>;
79
+ interface BuildContext {
80
+ /** Where the field is in the definition, e.g. `['fields', 2]`. */
81
+ readonly path: DefinitionPath;
82
+ }
83
+ /**
84
+ * `normalize` rules (trim, change case) run before the required check, so `' '` with `trim` counts
85
+ * as empty. `validate` rules run after it.
86
+ */
87
+ type RuleStage = 'normalize' | 'validate';
88
+ interface RuleDef<TParams extends AnyObjectSchema = AnyObjectSchema> {
89
+ kinds: readonly ValueKind[];
90
+ /** Defaults to `'validate'`. */
91
+ stage?: RuleStage;
92
+ /** The params the rule takes. Omit it for rules without params. Unknown params are rejected. */
93
+ params?: TParams;
94
+ /** Method syntax keeps `create` bivariant, so specific rules fit in a `Record<string, RuleDef`>. */
95
+ create(params: v.InferOutput<TParams>, message?: string): AnyAction;
96
+ }
97
+ interface MessageContext {
98
+ field: JsonField;
99
+ rule?: string;
100
+ path: DefinitionPath;
101
+ }
33
102
  interface SchemaBuilderOptions {
103
+ kinds?: Record<string, KindDef>;
34
104
  fieldTypes?: Record<string, FieldTypeDef>;
35
105
  rules?: Record<string, RuleDef>;
106
+ messages?: {
107
+ required?: string;
108
+ };
109
+ /**
110
+ * Called with every message from the definition and with `messages.required`, e.g. to treat
111
+ * them as i18n keys. Return undefined to use the text as written.
112
+ */
113
+ resolveMessage?(key: string, ctx: MessageContext): string | undefined;
36
114
  }
37
- export declare function createSchemaBuilder(opts?: SchemaBuilderOptions): (json: JsonFormSchema) => v.ObjectSchema<v.ObjectEntries, undefined>;
115
+ export declare function createSchemaBuilder(options?: SchemaBuilderOptions): {
116
+ build: (json: JsonForm) => v.ObjectSchema<v.ObjectEntries, undefined>;
117
+ validate: (json: unknown) => ValidateResult;
118
+ kinds: Readonly<Record<string, KindDef>>;
119
+ fieldTypes: Readonly<Record<string, FieldTypeDef<AnyObjectSchema>>>;
120
+ rules: Readonly<Record<string, RuleDef<AnyObjectSchema>>>;
121
+ };
38
122
  export declare const defaultFieldTypes: {
39
123
  text: {
40
124
  kind: "string";
41
- base: (m: string | undefined) => v.StringSchema<string | undefined>;
125
+ schema: () => v.StringSchema<undefined>;
42
126
  };
43
127
  password: {
44
128
  kind: "string";
45
- base: (m: string | undefined) => v.StringSchema<string | undefined>;
129
+ schema: () => v.StringSchema<undefined>;
46
130
  };
47
131
  email: {
48
132
  kind: "string";
49
- base: (m: string | undefined) => v.StringSchema<string | undefined>;
133
+ schema: () => v.StringSchema<undefined>;
50
134
  defaultRules: {
51
135
  rule: string;
52
- message: string;
53
136
  }[];
54
137
  };
55
138
  number: {
56
139
  kind: "number";
57
- base: (m: string | undefined) => v.NumberSchema<string | undefined>;
140
+ schema: () => v.NumberSchema<undefined>;
141
+ };
142
+ };
143
+ export declare const defineFieldType: <T extends AnyObjectSchema>(def: FieldTypeDef<T>) => FieldTypeDef<T>;
144
+ export declare const defaultKinds: {
145
+ string: {
146
+ isEmpty: (input: unknown) => input is "" | null | undefined;
147
+ required: (message: string) => v.NonEmptyAction<any, string>;
148
+ };
149
+ number: {
150
+ isEmpty: (input: unknown) => boolean;
151
+ };
152
+ boolean: {
153
+ isEmpty: (input: unknown) => input is null | undefined;
154
+ required: (message: string) => v.ValueAction<any, true, string>;
155
+ };
156
+ date: {
157
+ isEmpty: (input: unknown) => input is "" | null | undefined;
158
+ required: (message: string) => v.NonEmptyAction<any, string>;
159
+ };
160
+ array: {
161
+ isEmpty: (input: unknown) => boolean;
162
+ required: (message: string) => v.MinLengthAction<any, 1, string>;
163
+ };
164
+ file: {
165
+ isEmpty: (input: unknown) => input is null | undefined;
166
+ };
167
+ object: {
168
+ isEmpty: (input: unknown) => input is null | undefined;
169
+ };
170
+ none: {
171
+ isEmpty: () => true;
58
172
  };
59
173
  };
60
- export declare const defineRule: <T extends AnySchema>(def: RuleDef<T>) => RuleDef<T>;
61
174
  export declare const defaultRules: {
175
+ trim: RuleDef<AnyObjectSchema>;
62
176
  minLength: RuleDef<v.ObjectSchema<{
63
177
  readonly value: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
64
178
  }, undefined>>;
@@ -69,15 +183,24 @@ export declare const defaultRules: {
69
183
  readonly pattern: v.StringSchema<undefined>;
70
184
  readonly flags: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
71
185
  }, undefined>>;
72
- email: RuleDef<v.ObjectSchema<{}, undefined>>;
73
- url: RuleDef<v.ObjectSchema<{}, undefined>>;
186
+ email: RuleDef<AnyObjectSchema>;
187
+ url: RuleDef<AnyObjectSchema>;
74
188
  minValue: RuleDef<v.ObjectSchema<{
75
189
  readonly value: v.NumberSchema<undefined>;
76
190
  }, undefined>>;
77
191
  maxValue: RuleDef<v.ObjectSchema<{
78
192
  readonly value: v.NumberSchema<undefined>;
79
193
  }, undefined>>;
80
- integer: RuleDef<v.ObjectSchema<{}, undefined>>;
194
+ integer: RuleDef<AnyObjectSchema>;
81
195
  };
82
- export type { AnyAction, AnySchema, FieldType, FieldTypeDef, JsonField, JsonFormSchema, JsonRule, RuleDef, SchemaBuilderOptions, ValueKind };
196
+ export declare const defineRule: <T extends AnyObjectSchema>(def: RuleDef<T>) => RuleDef<T>;
197
+ /**
198
+ * Field keys this package owns. A field type's props can't use them.
199
+ */
200
+ export declare const RESERVED_FIELD_KEYS: readonly ["name", "type", "label", "description", "placeholder", "default", "required", "rules", "ui"];
201
+ /**
202
+ * Rule keys this package own. A rule's params can't use them.
203
+ */
204
+ export declare const RESERVED_RULE_KEYS: readonly ["rule", "message"];
205
+ export type { AnyAction, AnyObjectSchema, AnySchema, BuildContext, DefinitionIssue, DefinitionIssueCode, DefinitionPath, FieldTypeDef, FieldWithProps, JsonField, JsonForm, JsonRule, KindDef, MessageContext, RuleDef, RuleStage, SchemaBuilderOptions, ValidateResult, ValueKind };
83
206
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,32 +1,91 @@
1
1
  import * as v from "valibot";
2
- // src/field-types.ts
2
+ // src/builder/build-field-schema.ts
3
+ /**
4
+ * The schema runs in this order: empty check, base schema, normalize rules, required check,
5
+ * then the type's default rules and the field's rules.
6
+ */
7
+ function buildFieldSchema(checked, messages) {
8
+ const { field, path, type, kind } = checked;
9
+ const required = !!field.required;
10
+ const requiredMessage = messages.resolve(typeof field.required === "object" && field.required.message || messages.required, {
11
+ field,
12
+ path
13
+ });
14
+ const normalize = [];
15
+ const validate = [];
16
+ for (const rule of checked.rules) {
17
+ const message = rule.message === void 0 ? void 0 : messages.resolve(rule.message, {
18
+ field,
19
+ rule: rule.name,
20
+ path
21
+ });
22
+ const action = rule.def.create(rule.params, message);
23
+ if (rule.def.stage === "normalize") normalize.push(action);
24
+ else validate.push(action);
25
+ }
26
+ const base = type.schema(field, { path });
27
+ const requiredCheck = required ? kind.required?.(requiredMessage) : void 0;
28
+ const head = pipe(base, requiredCheck ? [...normalize, requiredCheck] : normalize);
29
+ const empty = required ? v.never(requiredMessage) : v.pipe(v.unknown(), v.transform(() => void 0));
30
+ const whenFilled = (schema) => v.lazy((input) => kind.isEmpty(input) ? empty : schema);
31
+ const schema = whenFilled(v.pipe(head, whenFilled(pipe(v.unknown(), validate))));
32
+ return required ? schema : v.optional(schema);
33
+ }
34
+ const pipe = (schema, actions) => actions.reduce((acc, action) => v.pipe(acc, action), schema);
35
+ // src/field-types/default-field-types.ts
3
36
  const defaultFieldTypes = {
4
37
  text: {
5
38
  kind: "string",
6
- base: (m) => v.string(m)
39
+ schema: () => v.string()
7
40
  },
8
41
  password: {
9
42
  kind: "string",
10
- base: (m) => v.string(m)
43
+ schema: () => v.string()
11
44
  },
12
45
  email: {
13
46
  kind: "string",
14
- base: (m) => v.string(m),
15
- defaultRules: [{
16
- rule: "email",
17
- message: "Invalid email"
18
- }]
47
+ schema: () => v.string(),
48
+ defaultRules: [{ rule: "email" }]
19
49
  },
20
50
  number: {
21
51
  kind: "number",
22
- base: (m) => v.number(m)
52
+ schema: () => v.number()
23
53
  }
24
54
  };
25
- // src/rules.ts
55
+ // src/kinds/default-kinds.ts
56
+ const isNil = (input) => input === void 0 || input === null;
57
+ const defaultKinds = {
58
+ string: {
59
+ isEmpty: (input) => isNil(input) || input === "",
60
+ required: (message) => v.nonEmpty(message)
61
+ },
62
+ number: { isEmpty: (input) => isNil(input) || input === "" || Number.isNaN(input) },
63
+ boolean: {
64
+ isEmpty: isNil,
65
+ required: (message) => v.value(true, message)
66
+ },
67
+ date: {
68
+ isEmpty: (input) => isNil(input) || input === "",
69
+ required: (message) => v.nonEmpty(message)
70
+ },
71
+ array: {
72
+ isEmpty: (input) => isNil(input) || Array.isArray(input) && input.length === 0,
73
+ required: (message) => v.minLength(1, message)
74
+ },
75
+ file: { isEmpty: isNil },
76
+ object: { isEmpty: isNil },
77
+ none: { isEmpty: () => true }
78
+ };
79
+ // src/rules/define-rule.ts
26
80
  const defineRule = (def) => def;
81
+ // src/rules/default-rules.ts
27
82
  const nonNegInt = v.pipe(v.number(), v.integer(), v.minValue(0));
28
- const noParams = v.object({});
29
83
  const defaultRules = {
84
+ trim: defineRule({
85
+ kinds: ["string"],
86
+ stage: "normalize",
87
+ create: () => v.trim()
88
+ }),
30
89
  minLength: defineRule({
31
90
  kinds: ["string"],
32
91
  params: v.object({ value: nonNegInt }),
@@ -47,12 +106,10 @@ const defaultRules = {
47
106
  }),
48
107
  email: defineRule({
49
108
  kinds: ["string"],
50
- params: noParams,
51
109
  create: (_, m) => v.email(m)
52
110
  }),
53
111
  url: defineRule({
54
112
  kinds: ["string"],
55
- params: noParams,
56
113
  create: (_, m) => v.url(m)
57
114
  }),
58
115
  minValue: defineRule({
@@ -67,58 +124,280 @@ const defaultRules = {
67
124
  }),
68
125
  integer: defineRule({
69
126
  kinds: ["number"],
70
- params: noParams,
71
127
  create: (_, m) => v.integer(m)
72
128
  })
73
129
  };
74
- // src/create-schema-builder.ts
75
- function createSchemaBuilder(opts = {}) {
76
- const fieldTypes = {
77
- ...defaultFieldTypes,
78
- ...opts.fieldTypes
130
+ // src/builder/registry.ts
131
+ /** Consumer entries replace defaults with the same key. */
132
+ function mergeRegistry(custom) {
133
+ return {
134
+ kinds: Object.freeze({
135
+ ...defaultKinds,
136
+ ...custom.kinds
137
+ }),
138
+ fieldTypes: Object.freeze({
139
+ ...defaultFieldTypes,
140
+ ...custom.fieldTypes
141
+ }),
142
+ rules: Object.freeze({
143
+ ...defaultRules,
144
+ ...custom.rules
145
+ })
79
146
  };
80
- const rules = {
81
- ...defaultRules,
82
- ...opts.rules
147
+ }
148
+ function lookup(record, key) {
149
+ return Object.hasOwn(record, key) ? record[key] : void 0;
150
+ }
151
+ // src/errors/definition-error.ts
152
+ function formatPath(path) {
153
+ return path.reduce((out, key) => {
154
+ if (typeof key === "number") return `${out}[${key}]`;
155
+ return out ? `${out}.${key}` : key;
156
+ }, "");
157
+ }
158
+ /** Thrown by `build()` when the definition has issues. Lists all of them. */
159
+ var DefinitionError = class extends Error {
160
+ issues;
161
+ constructor(issues) {
162
+ const lines = issues.map((issue) => ` ${formatPath(issue.path) || "(root)"}: ${issue.message}`);
163
+ super(`Invalid form definition:\n${lines.join("\n")}`);
164
+ this.name = "DefinitionError";
165
+ this.issues = issues;
166
+ }
167
+ };
168
+ // src/shared/reserved-keys.ts
169
+ /**
170
+ * Field keys this package owns. A field type's props can't use them.
171
+ */
172
+ const RESERVED_FIELD_KEYS = [
173
+ "name",
174
+ "type",
175
+ "label",
176
+ "description",
177
+ "placeholder",
178
+ "default",
179
+ "required",
180
+ "rules",
181
+ "ui"
182
+ ];
183
+ /**
184
+ * Rule keys this package own. A rule's params can't use them.
185
+ */
186
+ const RESERVED_RULE_KEYS = ["rule", "message"];
187
+ const fieldKeys = new Set(RESERVED_FIELD_KEYS);
188
+ const ruleKeys = new Set(RESERVED_RULE_KEYS);
189
+ const isReservedFieldKey = (key) => fieldKeys.has(key);
190
+ const isReservedRuleKey = (key) => ruleKeys.has(key);
191
+ // src/builder/validate-definition.ts
192
+ const formShape = v.object({
193
+ version: v.string(),
194
+ fields: v.array(v.unknown())
195
+ });
196
+ const fieldShape = v.looseObject({
197
+ name: v.optional(v.string()),
198
+ type: v.string(),
199
+ label: v.optional(v.string()),
200
+ description: v.optional(v.string()),
201
+ placeholder: v.optional(v.string()),
202
+ default: v.optional(v.unknown()),
203
+ required: v.optional(v.union([v.boolean(), v.strictObject({ message: v.optional(v.string()) })])),
204
+ rules: v.optional(v.array(v.unknown())),
205
+ ui: v.optional(v.record(v.string(), v.unknown()))
206
+ });
207
+ const ruleShape = v.looseObject({
208
+ rule: v.string(),
209
+ message: v.optional(v.string())
210
+ });
211
+ const noParams = v.object({});
212
+ /**
213
+ * Checks a whole definition and collects every issue, in document order. Never throws.
214
+ */
215
+ function checkDefinition(json, registry) {
216
+ const issues = [];
217
+ const fields = [];
218
+ const form = v.safeParse(formShape, json);
219
+ if (!form.success) return {
220
+ issues: toIssues("invalid_shape", [], form.issues),
221
+ fields
222
+ };
223
+ if (form.output.version !== "1") return {
224
+ issues: [{
225
+ code: "unsupported_version",
226
+ path: ["version"],
227
+ message: `Unsupported version "${form.output.version}". This package reads version '1'.`
228
+ }],
229
+ fields
83
230
  };
84
- function buildAction(rule, kind, at) {
85
- const def = rules[rule.rule];
86
- if (!def) throw new Error(`${at}: unknown rule "${rule.rule}"`);
87
- if (!def.kinds.includes(kind)) throw new Error(`${at}: rule "${rule.rule}" can't be used on ${kind} fields`);
88
- const parsed = v.safeParse(def.params, rule);
89
- if (!parsed.success) throw new Error(`${at}: invalid params for "${rule.rule}"\n${v.summarize(parsed.issues)}`);
90
- return def.create(parsed.output, rule.message);
231
+ const names = /* @__PURE__ */ new Set();
232
+ form.output.fields.forEach((raw, i) => {
233
+ const field = checkField(raw, ["fields", i], registry, names, issues);
234
+ if (field) fields.push(field);
235
+ });
236
+ return {
237
+ issues,
238
+ fields
239
+ };
240
+ }
241
+ function checkField(raw, path, registry, names, issues) {
242
+ const shape = v.safeParse(fieldShape, raw);
243
+ if (!shape.success) {
244
+ issues.push(...toIssues("invalid_shape", path, shape.issues));
245
+ return;
91
246
  }
92
- function buildField(field, i) {
93
- const at = `fields[${i}] "${field.name}"`;
94
- const type = fieldTypes[field.type];
95
- if (!type) throw new Error(`${at}: unknown field type "${field.type}"`);
96
- const userRules = field.rules ?? [];
97
- const overridden = new Set(userRules.map((r) => r.rule));
98
- const allRules = [...(type.defaultRules ?? []).filter((r) => !overridden.has(r.rule)), ...userRules];
99
- const required = !!field.required;
100
- const requiredMsg = typeof field.required === "object" ? field.required.message : void 0;
101
- const actions = [];
102
- if (required && type.kind === "string") actions.push(v.nonEmpty(requiredMsg ?? "Required"));
103
- allRules.forEach((r, j) => {
104
- actions.push(buildAction(r, type.kind, `${at}.rules[${j}]`));
247
+ const field = shape.output;
248
+ const issueCount = issues.length;
249
+ const type = lookup(registry.fieldTypes, field.type);
250
+ const hasValue = type?.kind !== "none";
251
+ if (!field.name) {
252
+ if (type && hasValue) issues.push(issue("missing_name", [...path, "name"], "A field needs a name"));
253
+ } else if (hasValue) {
254
+ if (names.has(field.name)) issues.push(issue("duplicate_name", [...path, "name"], `Duplicate name "${field.name}"`));
255
+ names.add(field.name);
256
+ }
257
+ if (!type) {
258
+ issues.push(issue("unknown_type", [...path, "type"], `Unknown field type "${field.type}"`));
259
+ return;
260
+ }
261
+ const props = checkProps(field, type, path, issues);
262
+ if (!hasValue) {
263
+ for (const key of ["required", "rules"]) if (field[key] !== void 0) {
264
+ const message = `A "${field.type}" field has no value, so it can't have "${key}"`;
265
+ issues.push(issue("invalid_shape", [...path, key], message));
266
+ }
267
+ return;
268
+ }
269
+ const fieldRules = (shape.output.rules ?? []).flatMap((rule, j) => checkRule(rule, type?.kind, [
270
+ ...path,
271
+ "rules",
272
+ j
273
+ ], registry, issues) ?? []);
274
+ if (issues.length > issueCount || !field.name) return void 0;
275
+ const overridden = new Set(fieldRules.map((rule) => rule.name));
276
+ const defaultRules = (type?.defaultRules ?? []).filter((rule) => !overridden.has(rule.rule)).flatMap((rule) => checkRule(rule, type?.kind, path, registry, []) ?? []);
277
+ return {
278
+ name: field.name,
279
+ field: {
280
+ ...field,
281
+ ...props
282
+ },
283
+ path,
284
+ type,
285
+ kind: registry.kinds[type?.kind],
286
+ rules: [...defaultRules, ...fieldRules]
287
+ };
288
+ }
289
+ function checkProps(field, type, path, issues) {
290
+ const propKeys = Object.keys(type.props?.entries ?? {});
291
+ const input = {};
292
+ for (const key of Object.keys(field)) {
293
+ if (isReservedFieldKey(key)) continue;
294
+ if (propKeys.includes(key)) input[key] = field[key];
295
+ else issues.push(issue("unknown_key", [...path, key], `Unknown key "${key}" on a "${field.type}" field`));
296
+ }
297
+ if (!type.props) return {};
298
+ const parsed = v.safeParse(type.props, input);
299
+ if (parsed.success) return parsed.output;
300
+ issues.push(...toIssues("invalid_props", path, parsed.issues));
301
+ return {};
302
+ }
303
+ function checkRule(raw, kind, path, registry, issues) {
304
+ const shape = v.safeParse(ruleShape, raw);
305
+ if (!shape.success) {
306
+ issues.push(...toIssues("invalid_shape", path, shape.issues));
307
+ return;
308
+ }
309
+ const rule = shape.output;
310
+ const def = lookup(registry.rules, rule.rule);
311
+ if (!def) {
312
+ issues.push(issue("unknown_rule", [...path, "rule"], `Unknown rule "${rule.rule}"`));
313
+ return;
314
+ }
315
+ const issueCount = issues.length;
316
+ if (!def.kinds.includes(kind)) {
317
+ const message = `Rule "${rule.rule} can't be used on ${kind} fields`;
318
+ issues.push(issue("kind_mismatch", [...path, "rule"], message));
319
+ }
320
+ const paramKeys = Object.keys(def.params?.entries ?? {});
321
+ const input = {};
322
+ for (const key of Object.keys(rule)) {
323
+ if (isReservedRuleKey(key)) continue;
324
+ if (paramKeys.includes(key)) input[key] = rule[key];
325
+ else issues.push(issue("unknown_param", [...path, key], `Rule "${rule.rule}" has no param "${key}"`));
326
+ }
327
+ const parsed = v.safeParse(def.params ?? noParams, input);
328
+ if (!parsed.success) issues.push(...toIssues("invalid_params", path, parsed.issues));
329
+ if (!parsed.success || issues.length > issueCount) return void 0;
330
+ return {
331
+ name: rule.rule,
332
+ def,
333
+ params: parsed.output,
334
+ message: rule.message
335
+ };
336
+ }
337
+ function checkRegistry(registry) {
338
+ for (const [name, rule] of Object.entries(registry.rules)) for (const key of Object.keys(rule.params?.entries ?? {})) if (isReservedRuleKey(key)) throw new TypeError(`Rule "${name}": params can't use the reserved key "${key}"`);
339
+ for (const [name, type] of Object.entries(registry.fieldTypes)) {
340
+ const at = `Field type "${name}"`;
341
+ if (!lookup(registry.kinds, type.kind)) throw new TypeError(`${at}: kind "${type.kind}" is not registered`);
342
+ if (type.kind !== "none" && !type.schema) throw new TypeError(`${at}: schema() is required unless the kind is 'none'`);
343
+ for (const key of Object.keys(type.props?.entries ?? {})) if (isReservedFieldKey(key)) throw new TypeError(`${at}: props can't use the reserved key "${key}"`);
344
+ const issues = [];
345
+ type.defaultRules?.forEach((rule, j) => {
346
+ checkRule(rule, type.kind, ["defaultRules", j], registry, issues);
105
347
  });
106
- const baseMsg = required && type.kind === "number" ? requiredMsg ?? "Required" : requiredMsg;
107
- const schema = actions.reduce((s, a) => v.pipe(s, a), type.base(baseMsg));
108
- if (required) return schema;
109
- if (type.kind === "string") return v.optional(v.union([v.literal(""), schema]));
110
- const empty = v.pipe(v.literal(""), v.transform(() => void 0));
111
- return v.optional(v.union([empty, schema]));
348
+ const [first] = issues;
349
+ if (first) throw new TypeError(`${at}: ${formatPath(first.path)}: ${first.message}`);
350
+ }
351
+ }
352
+ function issue(code, path, message) {
353
+ return {
354
+ code,
355
+ path,
356
+ message
357
+ };
358
+ }
359
+ function toIssues(code, path, valibotIssues) {
360
+ return valibotIssues.map((valibotIssue) => {
361
+ const keys = valibotIssue.path?.map((item) => item.key) ?? [];
362
+ return issue(code, [...path, ...keys], valibotIssue.message);
363
+ });
364
+ }
365
+ // src/builder/create-schema-builder.ts
366
+ function createSchemaBuilder(options = {}) {
367
+ const registry = mergeRegistry(options);
368
+ checkRegistry(registry);
369
+ const messages = {
370
+ required: options.messages?.required ?? "Required",
371
+ resolve: (text, ctx) => options.resolveMessage?.(text, ctx) ?? text
372
+ };
373
+ /** Every issue in the definition, in document order. Never throws on bad input. */
374
+ function validate(json) {
375
+ const { issues } = checkDefinition(json, registry);
376
+ return issues.length ? {
377
+ success: false,
378
+ issues
379
+ } : {
380
+ success: true,
381
+ issues: []
382
+ };
112
383
  }
113
- return function build(json) {
384
+ function build(json) {
385
+ const { issues, fields } = checkDefinition(json, registry);
386
+ if (issues.length) throw new DefinitionError(issues);
114
387
  const entries = {};
115
- json.fields.forEach((field, i) => {
116
- if (field.name in entries) throw new Error(`fields[${i}]: duplicate name "${field.name}"`);
117
- entries[field.name] = buildField(field, i);
118
- });
388
+ for (const field of fields) entries[field.name] = buildFieldSchema(field, messages);
119
389
  return v.object(entries);
390
+ }
391
+ return {
392
+ build,
393
+ validate,
394
+ kinds: registry.kinds,
395
+ fieldTypes: registry.fieldTypes,
396
+ rules: registry.rules
120
397
  };
121
398
  }
122
- export { createSchemaBuilder, defaultFieldTypes, defaultRules, defineRule };
399
+ // src/field-types/define-field-type.ts
400
+ const defineFieldType = (def) => def;
401
+ export { DefinitionError, RESERVED_FIELD_KEYS, RESERVED_RULE_KEYS, createSchemaBuilder, defaultFieldTypes, defaultKinds, defaultRules, defineFieldType, defineRule, formatPath };
123
402
 
124
403
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/field-types.ts","../src/rules.ts","../src/create-schema-builder.ts"],"sourcesContent":["import * as v from 'valibot'\nimport type { FieldTypeDef } from './types'\n\nexport const defaultFieldTypes = {\n text: { kind: 'string', base: (m) => v.string(m) },\n password: { kind: 'string', base: (m) => v.string(m) },\n email: {\n kind: 'string',\n base: (m) => v.string(m),\n defaultRules: [{ rule: 'email', message: 'Invalid email' }],\n },\n number: { kind: 'number', base: (m) => v.number(m) },\n} satisfies Record<string, FieldTypeDef>\n","import * as v from 'valibot'\nimport type { AnySchema, RuleDef } from './types'\n\nexport const defineRule = <T extends AnySchema>(def: RuleDef<T>): RuleDef<T> => def\n\nconst nonNegInt = v.pipe(v.number(), v.integer(), v.minValue(0))\nconst noParams = v.object({})\n\nexport const defaultRules = {\n minLength: defineRule({\n kinds: ['string'],\n params: v.object({ value: nonNegInt }),\n create: (p, m) => v.minLength(p.value, m),\n }),\n maxLength: defineRule({\n kinds: ['string'],\n params: v.object({ value: nonNegInt }),\n create: (p, m) => v.maxLength(p.value, m),\n }),\n regex: defineRule({\n kinds: ['string'],\n params: v.object({ pattern: v.string(), flags: v.optional(v.string()) }),\n create: (p, m) => v.regex(new RegExp(p.pattern, p.flags), m),\n }),\n email: defineRule({ kinds: ['string'], params: noParams, create: (_, m) => v.email(m) }),\n url: defineRule({ kinds: ['string'], params: noParams, create: (_, m) => v.url(m) }),\n minValue: defineRule({\n kinds: ['number'],\n params: v.object({ value: v.number() }),\n create: (p, m) => v.minValue(p.value, m),\n }),\n maxValue: defineRule({\n kinds: ['number'],\n params: v.object({ value: v.number() }),\n create: (p, m) => v.maxValue(p.value, m),\n }),\n integer: defineRule({ kinds: ['number'], params: noParams, create: (_, m) => v.integer(m) }),\n} satisfies Record<string, RuleDef>\n","import * as v from 'valibot'\nimport { defaultFieldTypes } from './field-types'\nimport { defaultRules } from './rules'\nimport type {\n AnyAction,\n AnySchema,\n FieldTypeDef,\n JsonField,\n JsonFormSchema,\n JsonRule,\n RuleDef,\n ValueKind,\n} from './types'\n\nexport interface SchemaBuilderOptions {\n fieldTypes?: Record<string, FieldTypeDef>\n rules?: Record<string, RuleDef>\n}\n\nexport function createSchemaBuilder(opts: SchemaBuilderOptions = {}) {\n const fieldTypes: Record<string, FieldTypeDef> = { ...defaultFieldTypes, ...opts.fieldTypes }\n const rules: Record<string, RuleDef> = { ...defaultRules, ...opts.rules }\n\n function buildAction(rule: JsonRule, kind: ValueKind, at: string): AnyAction {\n const def = rules[rule.rule]\n if (!def) throw new Error(`${at}: unknown rule \"${rule.rule}\"`)\n if (!def.kinds.includes(kind))\n throw new Error(`${at}: rule \"${rule.rule}\" can't be used on ${kind} fields`)\n\n const parsed = v.safeParse(def.params, rule)\n if (!parsed.success)\n throw new Error(`${at}: invalid params for \"${rule.rule}\"\\n${v.summarize(parsed.issues)}`)\n\n return def.create(parsed.output, rule.message)\n }\n\n function buildField(field: JsonField, i: number): AnySchema {\n const at = `fields[${i}] \"${field.name}\"`\n const type = fieldTypes[field.type]\n if (!type) throw new Error(`${at}: unknown field type \"${field.type}\"`)\n\n const userRules = field.rules ?? []\n const overridden = new Set(userRules.map((r) => r.rule))\n const allRules = [\n ...(type.defaultRules ?? []).filter((r) => !overridden.has(r.rule)),\n ...userRules,\n ]\n\n const required = !!field.required\n const requiredMsg = typeof field.required === 'object' ? field.required.message : undefined\n\n const actions: AnyAction[] = []\n\n if (required && type.kind === 'string') {\n actions.push(v.nonEmpty(requiredMsg ?? 'Required'))\n }\n\n allRules.forEach((r, j) => {\n actions.push(buildAction(r, type.kind, `${at}.rules[${j}]`))\n })\n\n // An empty number input is not a number, so required ones report it via the type check.\n const baseMsg = required && type.kind === 'number' ? (requiredMsg ?? 'Required') : requiredMsg\n const schema = actions.reduce<AnySchema>((s, a) => v.pipe(s, a), type.base(baseMsg))\n\n if (required) return schema\n if (type.kind === 'string') return v.optional(v.union([v.literal(''), schema]))\n\n // A cleared number input holds '', which means \"no value\".\n const empty = v.pipe(\n v.literal(''),\n v.transform(() => undefined),\n )\n return v.optional(v.union([empty, schema]))\n }\n\n return function build(json: JsonFormSchema) {\n const entries: v.ObjectEntries = {}\n\n json.fields.forEach((field, i) => {\n if (field.name in entries) throw new Error(`fields[${i}]: duplicate name \"${field.name}\"`)\n entries[field.name] = buildField(field, i)\n })\n\n return v.object(entries)\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/builder/build-field-schema.ts","../src/field-types/default-field-types.ts","../src/kinds/default-kinds.ts","../src/rules/define-rule.ts","../src/rules/default-rules.ts","../src/builder/registry.ts","../src/errors/definition-error.ts","../src/shared/reserved-keys.ts","../src/builder/validate-definition.ts","../src/builder/create-schema-builder.ts","../src/field-types/define-field-type.ts"],"sourcesContent":["import * as v from 'valibot'\nimport type { JsonField } from '../definition/json-form-schema'\nimport type { DefinitionPath } from '../errors/definition-error'\nimport type { CheckedField } from './validate-definition'\nimport type { AnyAction, AnySchema } from '../shared/valibot-types'\n\nexport interface MessageContext {\n field: JsonField\n rule?: string\n path: DefinitionPath\n}\n\nexport interface Messages {\n required: string\n resolve(text: string, ctx: MessageContext): string\n}\n\n/**\n * The schema runs in this order: empty check, base schema, normalize rules, required check,\n * then the type's default rules and the field's rules.\n */\nexport function buildFieldSchema(checked: CheckedField, messages: Messages): AnySchema {\n const { field, path, type, kind } = checked\n const required = !!field.required\n const requiredMessage = messages.resolve(\n (typeof field.required === 'object' && field.required.message) || messages.required,\n { field, path },\n )\n\n const normalize: AnyAction[] = []\n const validate: AnyAction[] = []\n\n for (const rule of checked.rules) {\n const message =\n rule.message === undefined\n ? undefined\n : messages.resolve(rule.message, { field, rule: rule.name, path })\n const action = rule.def.create(rule.params, message)\n if (rule.def.stage === 'normalize') {\n normalize.push(action)\n } else {\n validate.push(action)\n }\n }\n\n const base = (type.schema as NonNullable<typeof type.schema>)(field, { path })\n const requiredCheck = required ? kind.required?.(requiredMessage) : undefined\n const head = pipe(base, requiredCheck ? [...normalize, requiredCheck] : normalize)\n\n const empty = required\n ? v.never(requiredMessage)\n : v.pipe(\n v.unknown(),\n v.transform(() => undefined),\n )\n\n const whenFilled = (schema: AnySchema): AnySchema =>\n v.lazy((input) => (kind.isEmpty(input) ? empty : schema))\n\n const schema = whenFilled(v.pipe(head, whenFilled(pipe(v.unknown(), validate))))\n\n return required ? schema : v.optional(schema)\n}\n\nconst pipe = (schema: AnySchema, actions: readonly AnyAction[]): AnySchema =>\n actions.reduce<AnySchema>((acc, action) => v.pipe(acc, action), schema)\n","import * as v from 'valibot'\nimport type { FieldTypeDef } from './field-type-def'\n\nexport const defaultFieldTypes = {\n text: { kind: 'string', schema: () => v.string() },\n password: { kind: 'string', schema: () => v.string() },\n email: { kind: 'string', schema: () => v.string(), defaultRules: [{ rule: 'email' }] },\n number: { kind: 'number', schema: () => v.number() },\n} satisfies Record<string, FieldTypeDef>\n","import * as v from 'valibot'\nimport type { KindDef } from './kind-def'\n\nconst isNil = (input: unknown) => input === undefined || input === null\n\nexport const defaultKinds = {\n string: {\n isEmpty: (input) => isNil(input) || input === '',\n required: (message) => v.nonEmpty(message),\n },\n // A cleared <input type=\"number\"/> holds '' or NaN, depending on the adapter.\n number: {\n isEmpty: (input) => isNil(input) || input === '' || Number.isNaN(input),\n },\n // Required means \"must be ticked\", e.g accepting terms.\n boolean: {\n isEmpty: isNil,\n required: (message) => v.value(true, message),\n },\n date: {\n isEmpty: (input) => isNil(input) || input === '',\n required: (message) => v.nonEmpty(message),\n },\n array: {\n isEmpty: (input) => isNil(input) || (Array.isArray(input) && input.length === 0),\n required: (message) => v.minLength(1, message),\n },\n file: { isEmpty: isNil },\n object: { isEmpty: isNil },\n // Display elements (headings, dividers) have no value, so the builder never calls this.\n none: { isEmpty: () => true },\n} satisfies Record<string, KindDef>\n","import type { AnyObjectSchema } from '../shared/valibot-types'\nimport type { RuleDef } from './rule-def'\n\nexport const defineRule = <T extends AnyObjectSchema>(def: RuleDef<T>): RuleDef<T> => def\n","import * as v from 'valibot'\nimport { defineRule } from './define-rule'\nimport type { RuleDef } from './rule-def'\n\nconst nonNegInt = v.pipe(v.number(), v.integer(), v.minValue(0))\n\nexport const defaultRules = {\n trim: defineRule({ kinds: ['string'], stage: 'normalize', create: () => v.trim() }),\n minLength: defineRule({\n kinds: ['string'],\n params: v.object({ value: nonNegInt }),\n create: (p, m) => v.minLength(p.value, m),\n }),\n maxLength: defineRule({\n kinds: ['string'],\n params: v.object({ value: nonNegInt }),\n create: (p, m) => v.maxLength(p.value, m),\n }),\n regex: defineRule({\n kinds: ['string'],\n params: v.object({ pattern: v.string(), flags: v.optional(v.string()) }),\n create: (p, m) => v.regex(new RegExp(p.pattern, p.flags), m),\n }),\n email: defineRule({ kinds: ['string'], create: (_, m) => v.email(m) }),\n url: defineRule({ kinds: ['string'], create: (_, m) => v.url(m) }),\n minValue: defineRule({\n kinds: ['number'],\n params: v.object({ value: v.number() }),\n create: (p, m) => v.minValue(p.value, m),\n }),\n maxValue: defineRule({\n kinds: ['number'],\n params: v.object({ value: v.number() }),\n create: (p, m) => v.maxValue(p.value, m),\n }),\n integer: defineRule({ kinds: ['number'], create: (_, m) => v.integer(m) }),\n} satisfies Record<string, RuleDef>\n","import { defaultFieldTypes } from '../field-types/default-field-types'\nimport type { FieldTypeDef } from '../field-types/field-type-def'\nimport { defaultKinds } from '../kinds/default-kinds'\nimport type { KindDef } from '../kinds/kind-def'\nimport { defaultRules } from '../rules/default-rules'\nimport type { RuleDef } from '../rules/rule-def'\n\nexport interface Registry {\n readonly kinds: Readonly<Record<string, KindDef>>\n readonly fieldTypes: Readonly<Record<string, FieldTypeDef>>\n readonly rules: Readonly<Record<string, RuleDef>>\n}\n\n/** Consumer entries replace defaults with the same key. */\nexport function mergeRegistry(custom: Partial<Registry>): Registry {\n return {\n kinds: Object.freeze({ ...defaultKinds, ...custom.kinds }),\n fieldTypes: Object.freeze({ ...defaultFieldTypes, ...custom.fieldTypes }),\n rules: Object.freeze({ ...defaultRules, ...custom.rules }),\n }\n}\n\nexport function lookup<T>(record: Readonly<Record<string, T>>, key: string): T | undefined {\n return Object.hasOwn(record, key) ? record[key] : undefined\n}\n","/** Where something is in a definition, e.g. `['fields', 0, 'rules', 1]`. */\nexport type DefinitionPath = readonly (string | number)[]\n\n/** Public API: adding a code is a minor change, renaming one is breaking, */\nexport type DefinitionIssueCode =\n | 'invalid_shape'\n | 'unsupported_version'\n | 'unknown_key'\n | 'unknown_type'\n | 'missing_name'\n | 'duplicate_name'\n | 'invalid_props'\n | 'unknown_rule'\n | 'unknown_param'\n | 'invalid_params'\n | 'kind_mismatch'\n\nexport interface DefinitionIssue {\n code: DefinitionIssueCode\n path: DefinitionPath\n message: string\n}\n\nexport type ValidateResult =\n | { success: true; issues: [] }\n | { success: false; issues: DefinitionIssue[] }\n\nexport function formatPath(path: DefinitionPath): string {\n return path.reduce<string>((out, key) => {\n if (typeof key === 'number') return `${out}[${key}]`\n return out ? `${out}.${key}` : key\n }, '')\n}\n\n/** Thrown by `build()` when the definition has issues. Lists all of them. */\nexport class DefinitionError extends Error {\n readonly issues: readonly DefinitionIssue[]\n\n constructor(issues: readonly DefinitionIssue[]) {\n const lines = issues.map(\n (issue) => ` ${formatPath(issue.path) || '(root)'}: ${issue.message}`,\n )\n super(`Invalid form definition:\\n${lines.join('\\n')}`)\n this.name = 'DefinitionError'\n this.issues = issues\n }\n}\n","/**\n * Field keys this package owns. A field type's props can't use them.\n */\nexport const RESERVED_FIELD_KEYS = [\n 'name',\n 'type',\n 'label',\n 'description',\n 'placeholder',\n 'default',\n 'required',\n 'rules',\n 'ui',\n] as const\n\n/**\n * Rule keys this package own. A rule's params can't use them.\n */\nexport const RESERVED_RULE_KEYS = ['rule', 'message'] as const\n\nconst fieldKeys: ReadonlySet<string> = new Set(RESERVED_FIELD_KEYS)\nconst ruleKeys: ReadonlySet<string> = new Set(RESERVED_RULE_KEYS)\n\nexport const isReservedFieldKey = (key: string): boolean => fieldKeys.has(key)\nexport const isReservedRuleKey = (key: string): boolean => ruleKeys.has(key)\n","import * as v from 'valibot'\nimport type { JsonField, JsonRule } from '../definition/json-form-schema'\nimport {\n type DefinitionPath,\n type DefinitionIssue,\n type DefinitionIssueCode,\n formatPath,\n} from '../errors/definition-error'\nimport type { FieldTypeDef } from '../field-types/field-type-def'\nimport type { KindDef } from '../kinds/kind-def'\nimport type { RuleDef } from '../rules/rule-def'\nimport { lookup, type Registry } from './registry'\nimport { isReservedFieldKey, isReservedRuleKey } from '../shared/reserved-keys'\nimport type { ValueKind } from '../shared/value-kind'\n\nexport interface CheckedField {\n name: string\n field: JsonField\n path: DefinitionPath\n type: FieldTypeDef\n kind: KindDef\n rules: CheckedRule[]\n}\n\nexport interface CheckedRule {\n name: string\n def: RuleDef\n params: Record<string, unknown>\n message?: string\n}\n\ninterface CheckResult {\n issues: DefinitionIssue[]\n fields: CheckedField[]\n}\n\nconst formShape = v.object({ version: v.string(), fields: v.array(v.unknown()) })\nconst fieldShape = v.looseObject({\n name: v.optional(v.string()),\n type: v.string(),\n label: v.optional(v.string()),\n description: v.optional(v.string()),\n placeholder: v.optional(v.string()),\n default: v.optional(v.unknown()),\n required: v.optional(v.union([v.boolean(), v.strictObject({ message: v.optional(v.string()) })])),\n rules: v.optional(v.array(v.unknown())),\n ui: v.optional(v.record(v.string(), v.unknown())),\n})\nconst ruleShape = v.looseObject({ rule: v.string(), message: v.optional(v.string()) })\nconst noParams = v.object({})\n\n/**\n * Checks a whole definition and collects every issue, in document order. Never throws.\n */\nexport function checkDefinition(json: unknown, registry: Registry): CheckResult {\n const issues: DefinitionIssue[] = []\n const fields: CheckedField[] = []\n\n const form = v.safeParse(formShape, json)\n if (!form.success) return { issues: toIssues('invalid_shape', [], form.issues), fields }\n\n if (form.output.version !== '1') {\n const message = `Unsupported version \"${form.output.version}\". This package reads version '1'.`\n return { issues: [{ code: 'unsupported_version', path: ['version'], message }], fields }\n }\n\n const names = new Set<string>()\n form.output.fields.forEach((raw, i) => {\n const field = checkField(raw, ['fields', i], registry, names, issues)\n if (field) fields.push(field)\n })\n\n return { issues, fields }\n}\n\nfunction checkField(\n raw: unknown,\n path: DefinitionPath,\n registry: Registry,\n names: Set<string>,\n issues: DefinitionIssue[],\n): CheckedField | undefined {\n const shape = v.safeParse(fieldShape, raw)\n if (!shape.success) {\n issues.push(...toIssues('invalid_shape', path, shape.issues))\n return undefined\n }\n\n const field = shape.output as JsonField\n const issueCount = issues.length\n const type = lookup(registry.fieldTypes, field.type)\n const hasValue = type?.kind !== 'none'\n\n if (!field.name) {\n if (type && hasValue) {\n issues.push(issue('missing_name', [...path, 'name'], 'A field needs a name'))\n }\n } else if (hasValue) {\n if (names.has(field.name)) {\n issues.push(issue('duplicate_name', [...path, 'name'], `Duplicate name \"${field.name}\"`))\n }\n names.add(field.name)\n }\n\n if (!type) {\n issues.push(issue('unknown_type', [...path, 'type'], `Unknown field type \"${field.type}\"`))\n return undefined\n }\n\n const props = checkProps(field, type, path, issues)\n\n if (!hasValue) {\n for (const key of ['required', 'rules'] as const) {\n if (field[key] !== undefined) {\n const message = `A \"${field.type}\" field has no value, so it can't have \"${key}\"`\n issues.push(issue('invalid_shape', [...path, key], message))\n }\n }\n return undefined\n }\n\n const fieldRules = (shape.output.rules ?? []).flatMap(\n (rule, j) => checkRule(rule, type?.kind, [...path, 'rules', j], registry, issues) ?? [],\n )\n if (issues.length > issueCount || !field.name) return undefined\n\n const overridden = new Set(fieldRules.map((rule) => rule.name))\n const defaultRules = (type?.defaultRules ?? [])\n .filter((rule) => !overridden.has(rule.rule))\n .flatMap((rule) => checkRule(rule, type?.kind, path, registry, []) ?? [])\n\n return {\n name: field.name,\n field: { ...field, ...props },\n path,\n type,\n kind: registry.kinds[type?.kind] as KindDef,\n rules: [...defaultRules, ...fieldRules],\n }\n}\n\nfunction checkProps(\n field: JsonField,\n type: FieldTypeDef,\n path: DefinitionPath,\n issues: DefinitionIssue[],\n): Record<string, unknown> {\n const propKeys = Object.keys(type.props?.entries ?? {})\n const input: Record<string, unknown> = {}\n\n for (const key of Object.keys(field)) {\n if (isReservedFieldKey(key)) continue\n if (propKeys.includes(key)) {\n input[key] = field[key]\n } else {\n issues.push(\n issue('unknown_key', [...path, key], `Unknown key \"${key}\" on a \"${field.type}\" field`),\n )\n }\n }\n\n if (!type.props) return {}\n const parsed = v.safeParse(type.props, input)\n if (parsed.success) return parsed.output\n issues.push(...toIssues('invalid_props', path, parsed.issues))\n return {}\n}\n\nexport function checkRule(\n raw: unknown,\n kind: ValueKind,\n path: DefinitionPath,\n registry: Registry,\n issues: DefinitionIssue[],\n): CheckedRule | undefined {\n const shape = v.safeParse(ruleShape, raw)\n if (!shape.success) {\n issues.push(...toIssues('invalid_shape', path, shape.issues))\n return undefined\n }\n\n const rule: JsonRule = shape.output\n const def = lookup(registry.rules, rule.rule)\n if (!def) {\n issues.push(issue('unknown_rule', [...path, 'rule'], `Unknown rule \"${rule.rule}\"`))\n return undefined\n }\n\n const issueCount = issues.length\n if (!def.kinds.includes(kind)) {\n const message = `Rule \"${rule.rule} can't be used on ${kind} fields`\n issues.push(issue('kind_mismatch', [...path, 'rule'], message))\n }\n\n const paramKeys = Object.keys(def.params?.entries ?? {})\n const input: Record<string, unknown> = {}\n for (const key of Object.keys(rule)) {\n if (isReservedRuleKey(key)) continue\n if (paramKeys.includes(key)) {\n input[key] = rule[key]\n } else {\n issues.push(\n issue('unknown_param', [...path, key], `Rule \"${rule.rule}\" has no param \"${key}\"`),\n )\n }\n }\n\n const parsed = v.safeParse(def.params ?? noParams, input)\n if (!parsed.success) issues.push(...toIssues('invalid_params', path, parsed.issues))\n if (!parsed.success || issues.length > issueCount) return undefined\n\n return { name: rule.rule, def, params: parsed.output, message: rule.message }\n}\n\nexport function checkRegistry(registry: Registry): void {\n for (const [name, rule] of Object.entries(registry.rules)) {\n for (const key of Object.keys(rule.params?.entries ?? {})) {\n if (isReservedRuleKey(key)) {\n throw new TypeError(`Rule \"${name}\": params can't use the reserved key \"${key}\"`)\n }\n }\n }\n\n for (const [name, type] of Object.entries(registry.fieldTypes)) {\n const at = `Field type \"${name}\"`\n if (!lookup(registry.kinds, type.kind)) {\n throw new TypeError(`${at}: kind \"${type.kind}\" is not registered`)\n }\n if (type.kind !== 'none' && !type.schema) {\n throw new TypeError(`${at}: schema() is required unless the kind is 'none'`)\n }\n for (const key of Object.keys(type.props?.entries ?? {})) {\n if (isReservedFieldKey(key)) {\n throw new TypeError(`${at}: props can't use the reserved key \"${key}\"`)\n }\n }\n\n const issues: DefinitionIssue[] = []\n type.defaultRules?.forEach((rule, j) => {\n checkRule(rule, type.kind, ['defaultRules', j], registry, issues)\n })\n const [first] = issues\n if (first) throw new TypeError(`${at}: ${formatPath(first.path)}: ${first.message}`)\n }\n}\n\nfunction issue(code: DefinitionIssueCode, path: DefinitionPath, message: string): DefinitionIssue {\n return { code, path, message }\n}\n\nfunction toIssues(\n code: DefinitionIssueCode,\n path: DefinitionPath,\n valibotIssues: readonly v.BaseIssue<unknown>[],\n): DefinitionIssue[] {\n return valibotIssues.map((valibotIssue) => {\n const keys = valibotIssue.path?.map((item) => item.key as string | number) ?? []\n return issue(code, [...path, ...keys], valibotIssue.message)\n })\n}\n","import * as v from 'valibot'\nimport type { KindDef } from '../kinds/kind-def'\nimport type { FieldTypeDef } from '../field-types/field-type-def'\nimport type { RuleDef } from '../rules/rule-def'\nimport { buildFieldSchema, type MessageContext, type Messages } from './build-field-schema'\nimport { mergeRegistry } from './registry'\nimport { checkDefinition, checkRegistry } from './validate-definition'\nimport { DefinitionError, type ValidateResult } from '../errors/definition-error'\nimport type { JsonForm } from '../definition/json-form-schema'\n\nexport interface SchemaBuilderOptions {\n kinds?: Record<string, KindDef>\n fieldTypes?: Record<string, FieldTypeDef>\n rules?: Record<string, RuleDef>\n messages?: { required?: string }\n /**\n * Called with every message from the definition and with `messages.required`, e.g. to treat\n * them as i18n keys. Return undefined to use the text as written.\n */\n resolveMessage?(key: string, ctx: MessageContext): string | undefined\n}\n\nexport function createSchemaBuilder(options: SchemaBuilderOptions = {}) {\n const registry = mergeRegistry(options)\n checkRegistry(registry)\n\n const messages: Messages = {\n required: options.messages?.required ?? 'Required',\n resolve: (text, ctx) => options.resolveMessage?.(text, ctx) ?? text,\n }\n\n /** Every issue in the definition, in document order. Never throws on bad input. */\n function validate(json: unknown): ValidateResult {\n const { issues } = checkDefinition(json, registry)\n return issues.length ? { success: false, issues } : { success: true, issues: [] }\n }\n\n function build(json: JsonForm) {\n const { issues, fields } = checkDefinition(json, registry)\n if (issues.length) throw new DefinitionError(issues)\n\n const entries: v.ObjectEntries = {}\n for (const field of fields) {\n entries[field.name] = buildFieldSchema(field, messages)\n }\n return v.object(entries)\n }\n\n return {\n build,\n validate,\n kinds: registry.kinds,\n fieldTypes: registry.fieldTypes,\n rules: registry.rules,\n }\n}\n","import type { AnyObjectSchema } from '../shared/valibot-types'\nimport type { FieldTypeDef } from './field-type-def'\n\nexport const defineFieldType = <T extends AnyObjectSchema>(def: FieldTypeDef<T>): FieldTypeDef<T> =>\n def\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,15 @@
1
1
  {
2
2
  "name": "@whatsaaaa/form-builder-schema",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Build Formisch-ready Valibot schemas from JSON form definitions.",
5
+ "keywords": [
6
+ "form",
7
+ "formisch",
8
+ "json",
9
+ "schema"
10
+ ],
11
+ "homepage": "https://github.com/whatsaaaa/form-builder",
12
+ "bugs": "https://github.com/whatsaaaa/form-builder/issues",
5
13
  "license": "MIT",
6
14
  "author": "Whatsaaaa",
7
15
  "repository": {
@@ -12,14 +20,6 @@
12
20
  "files": [
13
21
  "dist"
14
22
  ],
15
- "homepage": "https://github.com/whatsaaaa/form-builder",
16
- "bugs": "https://github.com/whatsaaaa/form-builder/issues",
17
- "keywords": [
18
- "formisch",
19
- "form",
20
- "json",
21
- "schema"
22
- ],
23
23
  "type": "module",
24
24
  "sideEffects": false,
25
25
  "exports": {