@formwright/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
@@ -1,20 +1,53 @@
1
1
  # @formwright/schema
2
2
 
3
- Schema types and a dependency-free runtime validator for [Formwright](../../README.md).
3
+ > Schema types and a dependency-free runtime validator for [Formwright](https://github.com/aliarsalan177/formwright).
4
4
 
5
- The schema is Formwright's public contract: plain, serializable data that both the
6
- runtime renderer and the codegen compiler consume. This package gives you the
7
- TypeScript types plus a validator that produces precise, path-addressed issues —
8
- ideal for repairing LLM-emitted schemas before they reach the runtime.
5
+ The schema is Formwright's public contract: plain, serializable data that both the runtime
6
+ renderer and the codegen compiler consume. This package gives you the TypeScript types plus
7
+ a validator that produces precise, path-addressed issues — ideal for repairing LLM-emitted
8
+ schemas before they reach the runtime.
9
+
10
+ ```bash
11
+ npm i @formwright/schema
12
+ ```
9
13
 
10
14
  ```ts
11
15
  import { validateSchema, parseSchema } from "@formwright/schema";
12
16
 
17
+ // Non-throwing: inspect issues
13
18
  const result = validateSchema(unknownInput);
14
19
  if (!result.ok) {
15
20
  for (const issue of result.issues) console.warn(issue.path, issue.message);
16
21
  }
17
22
 
18
- // or throw on failure:
23
+ // Or throw a single aggregated error on failure:
19
24
  const schema = parseSchema(unknownInput);
20
25
  ```
26
+
27
+ ## Types
28
+
29
+ `FormSchema`, `FieldSchema`, `FieldType`, `FieldOption`, `ValidationSchema`, `Condition`,
30
+ `SubmitSchema`, and more — the full, serializable shape of a form, including nested `group`
31
+ and `collection` fields and the JSONLogic-style `Condition` algebra.
32
+
33
+ ```ts
34
+ import type { FormSchema } from "@formwright/schema";
35
+
36
+ const schema: FormSchema = {
37
+ id: "signup",
38
+ version: "1.0",
39
+ fields: [
40
+ { id: "email", type: "email", validation: { kind: "string", format: "email", required: true } },
41
+ ],
42
+ };
43
+ ```
44
+
45
+ ## Why a runtime validator
46
+
47
+ LLM-emitted schemas can be malformed. `validateSchema` checks the structure (ids present and
48
+ unique, container fields declared, etc.) and returns issues addressed by path
49
+ (`fields[2].type`), so you can repair or reject input before constructing a `Form`.
50
+
51
+ ## License
52
+
53
+ MIT
package/dist/index.d.cts CHANGED
@@ -48,7 +48,25 @@ type Condition = boolean | {
48
48
  readonly or: readonly Condition[];
49
49
  } | FieldValue;
50
50
  /** Built-in field widget kinds. Extensible: any string maps to a registered widget. */
51
- type FieldType = "text" | "number" | "email" | "password" | "textarea" | "select" | "checkbox" | "radio" | "group" | "collection" | (string & {});
51
+ type FieldType = "text" | "number" | "email" | "password" | "textarea" | "select" | "checkbox" | "radio" | "date" | "time" | "datetime" | "daterange" | "color" | "range" | "file" | "group" | "collection" | "heading" | "separator" | "paragraph" | (string & {});
52
+ /**
53
+ * Map a field to your own UI — a custom element, native tag, or a widget you
54
+ * registered by name. The serializable bits (tag/component/valueProp/event/attrs)
55
+ * live here; code-level transforms and framework `mount` functions are attached
56
+ * via `registerWidget` in the renderer.
57
+ */
58
+ type WidgetRef = string | {
59
+ /** A registered widget name (takes precedence over `tag`). */
60
+ readonly component?: string;
61
+ /** A custom element / native tag to render, e.g. "s-select". */
62
+ readonly tag?: string;
63
+ /** Property the value is written to / read from (default "value"). */
64
+ readonly valueProp?: string;
65
+ /** DOM event that signals a change, e.g. "value-change" (default "input"). */
66
+ readonly event?: string;
67
+ /** Static attributes to set on the element. */
68
+ readonly attrs?: Record<string, string>;
69
+ };
52
70
  /** Validation descriptor — declarative, mapped to a Standard Schema validator at runtime. */
53
71
  interface ValidationSchema {
54
72
  readonly kind: "string" | "number" | "boolean" | "array";
@@ -59,20 +77,85 @@ interface ValidationSchema {
59
77
  readonly maxLength?: number;
60
78
  readonly pattern?: string;
61
79
  readonly format?: "email" | "url" | "uuid";
80
+ /** Catch-all override for every rule's message. */
62
81
  readonly message?: Resolvable<string>;
82
+ /** Per-rule message overrides (take precedence over `message` and the defaults). */
83
+ readonly messages?: {
84
+ readonly required?: string;
85
+ readonly min?: string;
86
+ readonly max?: string;
87
+ readonly minLength?: string;
88
+ readonly maxLength?: string;
89
+ readonly pattern?: string;
90
+ readonly format?: string;
91
+ readonly type?: string;
92
+ };
63
93
  }
64
94
  /** A selectable option for `select` / `radio` fields. */
65
95
  interface FieldOption {
66
96
  readonly label: Resolvable<string>;
67
97
  readonly value: FieldValue;
68
98
  }
99
+ /**
100
+ * Per-part class overrides — drop in your own CSS classes or Tailwind utilities
101
+ * to restyle any part of a field without touching the renderer.
102
+ */
103
+ interface FieldClasses {
104
+ readonly field?: string;
105
+ readonly label?: string;
106
+ readonly control?: string;
107
+ readonly help?: string;
108
+ readonly description?: string;
109
+ readonly error?: string;
110
+ }
111
+ /**
112
+ * A slot rendered at the start/end of an input — either decorative text/icon
113
+ * (a string like "$" or "🔍") or a nested field (e.g. a currency `select`) whose
114
+ * value is added to the payload as a sibling key.
115
+ */
116
+ type FieldSlot = string | FieldSchema;
69
117
  /** A single field in the form. Resolved to a widget by `type`, keyed by `id`. */
70
118
  interface FieldSchema {
71
119
  readonly id: string;
72
120
  readonly type: FieldType;
73
121
  readonly label?: Resolvable<string>;
74
122
  readonly placeholder?: Resolvable<string>;
123
+ /** Native `autocomplete` token for the input (e.g. "email", "name", "off"). */
124
+ readonly autocomplete?: string;
75
125
  readonly help?: Resolvable<string>;
126
+ /** Static body text — for `paragraph`/`heading` presentational fields. */
127
+ readonly content?: Resolvable<string>;
128
+ /** An info tooltip shown next to the field's label. */
129
+ readonly tooltip?: Resolvable<string>;
130
+ /**
131
+ * Capture a value per locale → payload `{ en: "...", ar: "..." }`. Requires
132
+ * the form's `locales`. Renders as one input with a language switcher; reshape to
133
+ * `translations: { en: {...} }` in submit if needed.
134
+ */
135
+ readonly localized?: boolean;
136
+ /** The locale shown first for a `localized` field (default: the form's first locale). */
137
+ readonly defaultLocale?: string;
138
+ /** Longer descriptive text, positioned by {@link descriptionPosition}. */
139
+ readonly description?: Resolvable<string>;
140
+ /** Where to render `description` (default `"below-label"`). */
141
+ readonly descriptionPosition?: "below-label" | "below-field";
142
+ /** For check-like fields (checkbox/toggle): label `"start"` (with control at the end) or `"end"` (default). */
143
+ readonly labelPosition?: "start" | "end";
144
+ /** Render content (icon/text or a value-bearing field) before/after the input. */
145
+ readonly slots?: {
146
+ readonly start?: FieldSlot;
147
+ readonly end?: FieldSlot;
148
+ };
149
+ /** Exclude this field's value from the submitted payload (UI-only control). */
150
+ readonly omit?: boolean;
151
+ /** Render this field with your own component/element instead of the built-in for `type`. */
152
+ readonly widget?: WidgetRef;
153
+ /** Columns the field spans in the form's 12-column grid (e.g. 6 = half width, two side by side). */
154
+ readonly colSpan?: number;
155
+ /** Extra class(es) on the field wrapper (e.g. Tailwind utilities). */
156
+ readonly class?: string;
157
+ /** Per-part class overrides (wrapper, label, control, help, description, error). */
158
+ readonly classes?: FieldClasses;
76
159
  readonly defaultValue?: FieldValue;
77
160
  readonly options?: Resolvable<readonly FieldOption[]>;
78
161
  readonly validation?: ValidationSchema;
@@ -110,6 +193,18 @@ interface ProviderDecl {
110
193
  readonly type: "i18n" | "tanstack-query" | "theme" | (string & {});
111
194
  readonly [key: string]: unknown;
112
195
  }
196
+ /** A form-level action button (submit, reset, or a named custom action like "delete"). */
197
+ interface FormAction {
198
+ readonly name: string;
199
+ readonly label?: Resolvable<string>;
200
+ /** `"submit"` triggers submission, `"reset"` resets, `"button"` (default) emits an action event. */
201
+ readonly role?: "submit" | "reset" | "button";
202
+ readonly variant?: "primary" | "secondary" | "danger";
203
+ /** Render this button full-width (stretches to fill the action bar). */
204
+ readonly fullWidth?: boolean;
205
+ /** Name of a handler in `options.handlers`, called with the form on click. */
206
+ readonly handler?: string;
207
+ }
113
208
  /** How the form submits: transform the payload, send it, handle success/error. */
114
209
  interface SubmitSchema {
115
210
  /** Name of a registered transform applied to values before sending. */
@@ -131,6 +226,14 @@ interface FormSchema {
131
226
  readonly providers?: Record<string, ProviderDecl>;
132
227
  readonly fields: readonly FieldSchema[];
133
228
  readonly submit?: SubmitSchema;
229
+ /** Locales for `localized` fields — each captures a value per locale. */
230
+ readonly locales?: readonly string[];
231
+ /** Locales rendered right-to-left (defaults to the common RTL set: ar, he, fa, ur, …). */
232
+ readonly rtlLocales?: readonly string[];
233
+ /** Action buttons rendered at the bottom of the form (defaults to a single Submit). */
234
+ readonly actions?: readonly FormAction[];
235
+ /** How action buttons are aligned: `"start"` (default), `"end"`, or `"between"`. */
236
+ readonly actionsAlign?: "start" | "end" | "between";
134
237
  }
135
238
 
136
239
  /**
@@ -167,4 +270,4 @@ declare function isFormSchema(input: unknown): input is FormSchema;
167
270
  /** Convenience: list the field ids declared by a schema, in order. */
168
271
  declare function fieldIds(schema: FormSchema): string[];
169
272
 
170
- export { type Condition, type FieldOption, type FieldSchema, type FieldType, type FieldValue, type FormSchema, type ProviderDecl, type ProviderRef, type Resolvable, SchemaValidationError, type SubmitSchema, type ValidationIssue, type ValidationResult, type ValidationSchema, fieldIds, isFormSchema, parseSchema, validateSchema };
273
+ export { type Condition, type FieldClasses, type FieldOption, type FieldSchema, type FieldSlot, type FieldType, type FieldValue, type FormAction, type FormSchema, type ProviderDecl, type ProviderRef, type Resolvable, SchemaValidationError, type SubmitSchema, type ValidationIssue, type ValidationResult, type ValidationSchema, type WidgetRef, fieldIds, isFormSchema, parseSchema, validateSchema };
package/dist/index.d.ts CHANGED
@@ -48,7 +48,25 @@ type Condition = boolean | {
48
48
  readonly or: readonly Condition[];
49
49
  } | FieldValue;
50
50
  /** Built-in field widget kinds. Extensible: any string maps to a registered widget. */
51
- type FieldType = "text" | "number" | "email" | "password" | "textarea" | "select" | "checkbox" | "radio" | "group" | "collection" | (string & {});
51
+ type FieldType = "text" | "number" | "email" | "password" | "textarea" | "select" | "checkbox" | "radio" | "date" | "time" | "datetime" | "daterange" | "color" | "range" | "file" | "group" | "collection" | "heading" | "separator" | "paragraph" | (string & {});
52
+ /**
53
+ * Map a field to your own UI — a custom element, native tag, or a widget you
54
+ * registered by name. The serializable bits (tag/component/valueProp/event/attrs)
55
+ * live here; code-level transforms and framework `mount` functions are attached
56
+ * via `registerWidget` in the renderer.
57
+ */
58
+ type WidgetRef = string | {
59
+ /** A registered widget name (takes precedence over `tag`). */
60
+ readonly component?: string;
61
+ /** A custom element / native tag to render, e.g. "s-select". */
62
+ readonly tag?: string;
63
+ /** Property the value is written to / read from (default "value"). */
64
+ readonly valueProp?: string;
65
+ /** DOM event that signals a change, e.g. "value-change" (default "input"). */
66
+ readonly event?: string;
67
+ /** Static attributes to set on the element. */
68
+ readonly attrs?: Record<string, string>;
69
+ };
52
70
  /** Validation descriptor — declarative, mapped to a Standard Schema validator at runtime. */
53
71
  interface ValidationSchema {
54
72
  readonly kind: "string" | "number" | "boolean" | "array";
@@ -59,20 +77,85 @@ interface ValidationSchema {
59
77
  readonly maxLength?: number;
60
78
  readonly pattern?: string;
61
79
  readonly format?: "email" | "url" | "uuid";
80
+ /** Catch-all override for every rule's message. */
62
81
  readonly message?: Resolvable<string>;
82
+ /** Per-rule message overrides (take precedence over `message` and the defaults). */
83
+ readonly messages?: {
84
+ readonly required?: string;
85
+ readonly min?: string;
86
+ readonly max?: string;
87
+ readonly minLength?: string;
88
+ readonly maxLength?: string;
89
+ readonly pattern?: string;
90
+ readonly format?: string;
91
+ readonly type?: string;
92
+ };
63
93
  }
64
94
  /** A selectable option for `select` / `radio` fields. */
65
95
  interface FieldOption {
66
96
  readonly label: Resolvable<string>;
67
97
  readonly value: FieldValue;
68
98
  }
99
+ /**
100
+ * Per-part class overrides — drop in your own CSS classes or Tailwind utilities
101
+ * to restyle any part of a field without touching the renderer.
102
+ */
103
+ interface FieldClasses {
104
+ readonly field?: string;
105
+ readonly label?: string;
106
+ readonly control?: string;
107
+ readonly help?: string;
108
+ readonly description?: string;
109
+ readonly error?: string;
110
+ }
111
+ /**
112
+ * A slot rendered at the start/end of an input — either decorative text/icon
113
+ * (a string like "$" or "🔍") or a nested field (e.g. a currency `select`) whose
114
+ * value is added to the payload as a sibling key.
115
+ */
116
+ type FieldSlot = string | FieldSchema;
69
117
  /** A single field in the form. Resolved to a widget by `type`, keyed by `id`. */
70
118
  interface FieldSchema {
71
119
  readonly id: string;
72
120
  readonly type: FieldType;
73
121
  readonly label?: Resolvable<string>;
74
122
  readonly placeholder?: Resolvable<string>;
123
+ /** Native `autocomplete` token for the input (e.g. "email", "name", "off"). */
124
+ readonly autocomplete?: string;
75
125
  readonly help?: Resolvable<string>;
126
+ /** Static body text — for `paragraph`/`heading` presentational fields. */
127
+ readonly content?: Resolvable<string>;
128
+ /** An info tooltip shown next to the field's label. */
129
+ readonly tooltip?: Resolvable<string>;
130
+ /**
131
+ * Capture a value per locale → payload `{ en: "...", ar: "..." }`. Requires
132
+ * the form's `locales`. Renders as one input with a language switcher; reshape to
133
+ * `translations: { en: {...} }` in submit if needed.
134
+ */
135
+ readonly localized?: boolean;
136
+ /** The locale shown first for a `localized` field (default: the form's first locale). */
137
+ readonly defaultLocale?: string;
138
+ /** Longer descriptive text, positioned by {@link descriptionPosition}. */
139
+ readonly description?: Resolvable<string>;
140
+ /** Where to render `description` (default `"below-label"`). */
141
+ readonly descriptionPosition?: "below-label" | "below-field";
142
+ /** For check-like fields (checkbox/toggle): label `"start"` (with control at the end) or `"end"` (default). */
143
+ readonly labelPosition?: "start" | "end";
144
+ /** Render content (icon/text or a value-bearing field) before/after the input. */
145
+ readonly slots?: {
146
+ readonly start?: FieldSlot;
147
+ readonly end?: FieldSlot;
148
+ };
149
+ /** Exclude this field's value from the submitted payload (UI-only control). */
150
+ readonly omit?: boolean;
151
+ /** Render this field with your own component/element instead of the built-in for `type`. */
152
+ readonly widget?: WidgetRef;
153
+ /** Columns the field spans in the form's 12-column grid (e.g. 6 = half width, two side by side). */
154
+ readonly colSpan?: number;
155
+ /** Extra class(es) on the field wrapper (e.g. Tailwind utilities). */
156
+ readonly class?: string;
157
+ /** Per-part class overrides (wrapper, label, control, help, description, error). */
158
+ readonly classes?: FieldClasses;
76
159
  readonly defaultValue?: FieldValue;
77
160
  readonly options?: Resolvable<readonly FieldOption[]>;
78
161
  readonly validation?: ValidationSchema;
@@ -110,6 +193,18 @@ interface ProviderDecl {
110
193
  readonly type: "i18n" | "tanstack-query" | "theme" | (string & {});
111
194
  readonly [key: string]: unknown;
112
195
  }
196
+ /** A form-level action button (submit, reset, or a named custom action like "delete"). */
197
+ interface FormAction {
198
+ readonly name: string;
199
+ readonly label?: Resolvable<string>;
200
+ /** `"submit"` triggers submission, `"reset"` resets, `"button"` (default) emits an action event. */
201
+ readonly role?: "submit" | "reset" | "button";
202
+ readonly variant?: "primary" | "secondary" | "danger";
203
+ /** Render this button full-width (stretches to fill the action bar). */
204
+ readonly fullWidth?: boolean;
205
+ /** Name of a handler in `options.handlers`, called with the form on click. */
206
+ readonly handler?: string;
207
+ }
113
208
  /** How the form submits: transform the payload, send it, handle success/error. */
114
209
  interface SubmitSchema {
115
210
  /** Name of a registered transform applied to values before sending. */
@@ -131,6 +226,14 @@ interface FormSchema {
131
226
  readonly providers?: Record<string, ProviderDecl>;
132
227
  readonly fields: readonly FieldSchema[];
133
228
  readonly submit?: SubmitSchema;
229
+ /** Locales for `localized` fields — each captures a value per locale. */
230
+ readonly locales?: readonly string[];
231
+ /** Locales rendered right-to-left (defaults to the common RTL set: ar, he, fa, ur, …). */
232
+ readonly rtlLocales?: readonly string[];
233
+ /** Action buttons rendered at the bottom of the form (defaults to a single Submit). */
234
+ readonly actions?: readonly FormAction[];
235
+ /** How action buttons are aligned: `"start"` (default), `"end"`, or `"between"`. */
236
+ readonly actionsAlign?: "start" | "end" | "between";
134
237
  }
135
238
 
136
239
  /**
@@ -167,4 +270,4 @@ declare function isFormSchema(input: unknown): input is FormSchema;
167
270
  /** Convenience: list the field ids declared by a schema, in order. */
168
271
  declare function fieldIds(schema: FormSchema): string[];
169
272
 
170
- export { type Condition, type FieldOption, type FieldSchema, type FieldType, type FieldValue, type FormSchema, type ProviderDecl, type ProviderRef, type Resolvable, SchemaValidationError, type SubmitSchema, type ValidationIssue, type ValidationResult, type ValidationSchema, fieldIds, isFormSchema, parseSchema, validateSchema };
273
+ export { type Condition, type FieldClasses, type FieldOption, type FieldSchema, type FieldSlot, type FieldType, type FieldValue, type FormAction, type FormSchema, type ProviderDecl, type ProviderRef, type Resolvable, SchemaValidationError, type SubmitSchema, type ValidationIssue, type ValidationResult, type ValidationSchema, type WidgetRef, fieldIds, isFormSchema, parseSchema, validateSchema };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@formwright/schema",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Schema types and runtime validator for Formwright.",
5
5
  "license": "MIT",
6
6
  "repository": {