@k8ordo/form 0.1.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/LICENSE +21 -0
- package/README.md +127 -0
- package/dist/async-check.d.mts +22 -0
- package/dist/async-check.mjs +49 -0
- package/dist/derive/attributes.d.mts +32 -0
- package/dist/derive/attributes.mjs +130 -0
- package/dist/derive/form-fields.d.mts +15 -0
- package/dist/derive/form-fields.mjs +83 -0
- package/dist/derive/messages.d.mts +12 -0
- package/dist/derive/messages.mjs +80 -0
- package/dist/hidden-value.d.mts +25 -0
- package/dist/hidden-value.mjs +41 -0
- package/dist/index.d.mts +6 -0
- package/dist/index.mjs +4 -0
- package/dist/parse/parse-form.d.mts +26 -0
- package/dist/parse/parse-form.mjs +118 -0
- package/dist/parse/paths.d.mts +20 -0
- package/dist/parse/paths.mjs +52 -0
- package/dist/paths.d.mts +56 -0
- package/dist/paths.mjs +1 -0
- package/dist/rules/define-form.d.mts +21 -0
- package/dist/rules/define-form.mjs +12 -0
- package/dist/rules/rules.d.mts +41 -0
- package/dist/rules/rules.mjs +35 -0
- package/dist/schema/object-schema.d.mts +33 -0
- package/dist/schema/object-schema.mjs +4 -0
- package/dist/schema/walk.d.mts +48 -0
- package/dist/schema/walk.mjs +128 -0
- package/dist/server.d.mts +6 -0
- package/dist/server.mjs +5 -0
- package/dist/types.d.mts +76 -0
- package/dist/types.mjs +1 -0
- package/dist/use-form.d.mts +44 -0
- package/dist/use-form.mjs +309 -0
- package/docs/GUIDE.md +362 -0
- package/docs/llms.txt +32 -0
- package/package.json +76 -0
package/docs/GUIDE.md
ADDED
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
# @k8ordo/form
|
|
2
|
+
|
|
3
|
+
One zod schema produces the HTML constraint attributes, the messages, and the
|
|
4
|
+
server-side validation. The DOM holds the values; React state holds only what
|
|
5
|
+
the DOM cannot express.
|
|
6
|
+
|
|
7
|
+
Like every k8ordo package it assumes React 19 and Server Components, uses only
|
|
8
|
+
what has reached Baseline newly available, and ships no polyfills or legacy
|
|
9
|
+
fallbacks.
|
|
10
|
+
|
|
11
|
+
## zod, or zod/mini
|
|
12
|
+
|
|
13
|
+
The conversion is taken from zod's shared core, so a schema written with either
|
|
14
|
+
entry works. `zod/mini` is the same validator with a much smaller surface — if
|
|
15
|
+
the schema module is also imported somewhere on the client, that is the one to
|
|
16
|
+
reach for.
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
zod 63 kB gzipped
|
|
20
|
+
zod/mini 10 kB gzipped
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Nothing in this package notices which one you chose. Note that under this
|
|
24
|
+
design the schema is imported only by server code — the Server Component that
|
|
25
|
+
derives the fields, and the Server Action that parses the submission — so zod
|
|
26
|
+
does not reach the browser either way. Reach for `zod/mini` when something on
|
|
27
|
+
the client imports the schema module anyway.
|
|
28
|
+
|
|
29
|
+
## The shape of it
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
server formFields(schema) → { title: { input, messages }, … } plain data
|
|
33
|
+
↓ props (zod does not cross)
|
|
34
|
+
client useForm(fields, state) → attributes to spread, message to show
|
|
35
|
+
↓ submit
|
|
36
|
+
server parseForm(schema, formData) → typed data, or per-field errors
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`formFields` runs on the server — in a Server Component or at module scope. Its
|
|
40
|
+
result is JSON, so it crosses the RSC boundary as props and zod never enters the
|
|
41
|
+
client bundle.
|
|
42
|
+
|
|
43
|
+
## Writing a form
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
// schema.ts
|
|
47
|
+
export const talkSchema = z.object({
|
|
48
|
+
title: z.string().min(1).max(120),
|
|
49
|
+
eventUrl: z.url(),
|
|
50
|
+
blogId: z.coerce.number().int().positive(),
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Type conversion stays in the schema. `parseForm` handles what only it can —
|
|
55
|
+
an unchecked checkbox arrives as a missing key, a repeated name arrives as
|
|
56
|
+
several entries, everything is a string — and `z.coerce` handles `'42'` → `42`.
|
|
57
|
+
That way the schema keeps describing what it actually validates.
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
// page.tsx — Server Component
|
|
61
|
+
const talkFields = formFields(talkSchema); // module scope: derived once
|
|
62
|
+
|
|
63
|
+
export default function Page() {
|
|
64
|
+
return <TalkForm action={createTalk} fields={talkFields} />;
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`useForm` takes the whole result — fields, arrays, rules, and the `dropped`
|
|
69
|
+
report travel together.
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
// talk-form.tsx
|
|
73
|
+
'use client';
|
|
74
|
+
|
|
75
|
+
export const TalkForm = ({ action, fields }: TalkFormProps) => {
|
|
76
|
+
const [state, formAction] = useActionState(action, {});
|
|
77
|
+
const form = useForm(fields, state);
|
|
78
|
+
const title = form.field('title');
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<form {...form.props} action={formAction}>
|
|
82
|
+
<input {...title.input} />
|
|
83
|
+
{title.error !== undefined && <p>{title.error}</p>}
|
|
84
|
+
</form>
|
|
85
|
+
);
|
|
86
|
+
};
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`form.props` attaches to the `<form>` and nowhere else. There is no per-field
|
|
90
|
+
registration to forget.
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
// actions.ts
|
|
94
|
+
'use server';
|
|
95
|
+
|
|
96
|
+
export async function createTalk(_prev: FormState, formData: FormData) {
|
|
97
|
+
const parsed = parseForm(talkSchema, formData);
|
|
98
|
+
if (!parsed.success) return parsed.state;
|
|
99
|
+
|
|
100
|
+
await insertTalk(parsed.data); // typed
|
|
101
|
+
redirect('/talks');
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## What it guarantees
|
|
106
|
+
|
|
107
|
+
**The wording cannot drift.** Client messages are obtained by running the
|
|
108
|
+
schema against a probe value, so the text shown next to the input is the text
|
|
109
|
+
zod itself produces. A custom `min(1, '…')` reaches both sides.
|
|
110
|
+
|
|
111
|
+
**`required` means the same thing on both sides.** JSON Schema's `required`
|
|
112
|
+
means "the key is present", but a form always submits something for every
|
|
113
|
+
control — an empty text field as `''`, an unchecked checkbox as `false` once
|
|
114
|
+
parsed. The attribute is emitted only when the schema rejects that empty
|
|
115
|
+
submission, so the two sides always agree: a plain `z.boolean()` checkbox is
|
|
116
|
+
not required, while `z.literal(true, '…')` — a consent box — is, and shows
|
|
117
|
+
zod's own wording.
|
|
118
|
+
|
|
119
|
+
**Nothing is dropped in silence.** Checks HTML cannot express — `refine`,
|
|
120
|
+
cross-field rules, an exclusive bound on a float, a regex whose flags or
|
|
121
|
+
anchoring the `pattern` attribute would silently reinterpret, a
|
|
122
|
+
`z.iso.datetime()` no `datetime-local` control can ever satisfy — are returned
|
|
123
|
+
in `dropped`. They still run on the server; you are told they do not run on
|
|
124
|
+
the client.
|
|
125
|
+
|
|
126
|
+
**A schema this package cannot express fails at derive time.** `z.record`,
|
|
127
|
+
tuples, a repeat nested inside a repeat, nullable objects, keys containing
|
|
128
|
+
`.` or brackets — `formFields` throws with the reason, instead of deriving a
|
|
129
|
+
form that would silently misparse what the person typed.
|
|
130
|
+
|
|
131
|
+
**A missing name is loud.** If the schema has a field that never arrived in the
|
|
132
|
+
FormData, `parseForm` throws instead of reporting it as a validation failure.
|
|
133
|
+
Forgetting to spread `input` is a wiring mistake, not something the person
|
|
134
|
+
filling in the form did. The one exception is an enum field: a radio group
|
|
135
|
+
with nothing selected submits no entry at all — a state the person can reach —
|
|
136
|
+
so it comes back as that field's validation error instead.
|
|
137
|
+
|
|
138
|
+
**Native validation survives without JavaScript.** `noValidate` is applied
|
|
139
|
+
from JavaScript on mount, never rendered into the markup. With scripts
|
|
140
|
+
disabled or not yet loaded, the browser's own checks stay on; once the hook is
|
|
141
|
+
live, it takes over the message path and the server stays the arbiter.
|
|
142
|
+
|
|
143
|
+
**Secrets are never echoed.** `parseForm` returns the submitted values so a
|
|
144
|
+
retry without JavaScript keeps the input. Fields marked as passwords are
|
|
145
|
+
excluded automatically, and typed as `password` in the markup:
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
z.string().min(8).meta({ input: 'password' }); // zod
|
|
149
|
+
globalRegistry.add(password, { input: 'password' }); // zod or zod/mini
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
`.meta()` is shorthand for the registry and is not available on `zod/mini`;
|
|
153
|
+
the registry route works with either.
|
|
154
|
+
|
|
155
|
+
**`isDirty` costs one boolean.** `form.isDirty` compares each control — text,
|
|
156
|
+
checkbox, `<select>`, a `HiddenValue` — with the value it was rendered with,
|
|
157
|
+
read straight from the DOM; adding or removing a row counts too. It flips at
|
|
158
|
+
most twice, so it never becomes a per-keystroke re-render.
|
|
159
|
+
|
|
160
|
+
## Nested objects and repeated rows
|
|
161
|
+
|
|
162
|
+
A nested field is addressed by its dotted path, which is also the `name` the
|
|
163
|
+
browser submits.
|
|
164
|
+
|
|
165
|
+
```tsx
|
|
166
|
+
const email = form.field('user.email'); // name="user.email"
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
A repeated group is reached through `array()`. React state holds only the
|
|
170
|
+
identity of each row — the values stay in the DOM, so adding or removing a row
|
|
171
|
+
never copies anything into React.
|
|
172
|
+
|
|
173
|
+
```tsx
|
|
174
|
+
const items = form.array('items');
|
|
175
|
+
|
|
176
|
+
{
|
|
177
|
+
items.rows.map((row) => (
|
|
178
|
+
<div key={row.key}>
|
|
179
|
+
<input {...row.field('name').input} /> {/* name="items[0].name" */}
|
|
180
|
+
{items.canRemove && (
|
|
181
|
+
<button onClick={row.remove} type="button">
|
|
182
|
+
削除
|
|
183
|
+
</button>
|
|
184
|
+
)}
|
|
185
|
+
</div>
|
|
186
|
+
));
|
|
187
|
+
}
|
|
188
|
+
{
|
|
189
|
+
items.canAdd && (
|
|
190
|
+
<button onClick={items.add} type="button">
|
|
191
|
+
追加
|
|
192
|
+
</button>
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
`canAdd` and `canRemove` come from the schema's `.min()` / `.max()`, so the
|
|
198
|
+
buttons disappear at exactly the bounds the server enforces. For an array of
|
|
199
|
+
scalars (`z.array(z.string())`) the item has a single unnamed field:
|
|
200
|
+
`row.field()`.
|
|
201
|
+
|
|
202
|
+
`parseForm` reports how many rows arrived in `state.rows`, so a retry without
|
|
203
|
+
JavaScript rebuilds the same number of rows. Row counts are read from the
|
|
204
|
+
submitted names but never trusted beyond the schema's `.max()` (or a hard
|
|
205
|
+
ceiling), so a forged index cannot make the server allocate.
|
|
206
|
+
|
|
207
|
+
A nested object behind `.optional()` / `.default()` keeps its fields — the
|
|
208
|
+
wrapper is peeled the same way on both sides. A repeat nested inside a repeat
|
|
209
|
+
has no unambiguous name; it is a compile error and a derive-time throw.
|
|
210
|
+
|
|
211
|
+
## Checkbox groups
|
|
212
|
+
|
|
213
|
+
An array of enums is a fixed option set the person picks several of — one
|
|
214
|
+
name shared by every box, not repeated rows. It derives as a single field:
|
|
215
|
+
|
|
216
|
+
```tsx
|
|
217
|
+
const prefs = z.object({ tags: z.array(z.enum(['a', 'b', 'c'])).min(2) });
|
|
218
|
+
|
|
219
|
+
const tags = form.field('tags');
|
|
220
|
+
|
|
221
|
+
{
|
|
222
|
+
['a', 'b', 'c'].map((option) => (
|
|
223
|
+
<input key={option} type="checkbox" value={option} {...tags.input} />
|
|
224
|
+
));
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
`parseForm` reads every checked box under the shared name — none checked is
|
|
229
|
+
`[]`, never a wiring error. The `.min(2)` cannot become an HTML attribute (on
|
|
230
|
+
a group, `required` would mean "check every box"), so it is reported in
|
|
231
|
+
`dropped`; declare `minChecked('tags', 2, '…')` to run the same bound in the
|
|
232
|
+
browser. On a no-JS retry the group echoes as an array, so restoring is:
|
|
233
|
+
|
|
234
|
+
```tsx
|
|
235
|
+
defaultChecked={Array.isArray(state.values?.tags) && state.values.tags.includes(option)}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Paths are checked at compile time
|
|
239
|
+
|
|
240
|
+
`formFields` derives the set of valid paths from the schema type, so a typo is
|
|
241
|
+
a build error rather than something you find by clicking. Rules are typed
|
|
242
|
+
against the same paths.
|
|
243
|
+
|
|
244
|
+
```tsx
|
|
245
|
+
form.field('titel'); // error: not a field in the schema
|
|
246
|
+
form.field('items'); // error: an array of objects, reached through array()
|
|
247
|
+
form.array('user'); // error: an object, not an array
|
|
248
|
+
form.array('tags'); // error: a checkbox group is a field
|
|
249
|
+
defineForm(schema, [sameAs('confrim', 'password', '…')]); // error: typo
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## Checks HTML has no attribute for
|
|
253
|
+
|
|
254
|
+
Password confirmation, "tick at least two", "required only when rejected" —
|
|
255
|
+
none of these map to a constraint attribute, and a `.refine()` cannot cross to
|
|
256
|
+
the client because it is a function. Declare them next to the schema instead:
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
export const signup = defineForm(
|
|
260
|
+
z.object({
|
|
261
|
+
password: z.string().min(8),
|
|
262
|
+
confirm: z.string(),
|
|
263
|
+
}),
|
|
264
|
+
[sameAs('confirm', 'password', 'パスワードが一致しません')],
|
|
265
|
+
);
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Pass the definition where you passed the schema — `formFields(signup)` and
|
|
269
|
+
`parseForm(signup, formData)`. The rules are plain data, so they travel with
|
|
270
|
+
the fields, and **both sides run the same evaluator**: the browser against the
|
|
271
|
+
live form, the server against the submission. There is no second
|
|
272
|
+
implementation to drift from the first.
|
|
273
|
+
|
|
274
|
+
On the client a breach is applied with `setCustomValidity`, so it is
|
|
275
|
+
indistinguishable from a built-in check — `:user-invalid` matches and the
|
|
276
|
+
message arrives through the same path as every other one.
|
|
277
|
+
|
|
278
|
+
Available: `sameAs`, `minChecked`, `requiredWhen`. Anything else stays a
|
|
279
|
+
`.refine()`, runs on the server, and is listed in `dropped`.
|
|
280
|
+
|
|
281
|
+
## Asking the server about one field
|
|
282
|
+
|
|
283
|
+
Whether a name is already taken is something only the server knows. `useAsyncCheck`
|
|
284
|
+
runs on blur, keeps the newest answer when replies arrive out of order, and
|
|
285
|
+
applies the result with `setCustomValidity` — so the answer shows up through the
|
|
286
|
+
same path as every other message. A blur that leaves the value unchanged does
|
|
287
|
+
not ask again, and emptying the field clears the last answer with it.
|
|
288
|
+
|
|
289
|
+
```tsx
|
|
290
|
+
const slug = form.field('slug');
|
|
291
|
+
const taken = useAsyncCheck(checkSlugAvailable); // a Server Action
|
|
292
|
+
|
|
293
|
+
<input {...slug.input} {...taken.props} />;
|
|
294
|
+
<button disabled={taken.isChecking} type="submit">
|
|
295
|
+
保存
|
|
296
|
+
</button>;
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
## Components that submit nothing
|
|
300
|
+
|
|
301
|
+
A rich text editor or a third-party combobox renders no `<input name>`, so
|
|
302
|
+
FormData never sees it. Park its value in a hidden input rather than pulling it
|
|
303
|
+
into React state and pushing it back out at submit:
|
|
304
|
+
|
|
305
|
+
```tsx
|
|
306
|
+
<Editor onChange={setBody} value={body} />
|
|
307
|
+
<HiddenValue name="body" value={body} />
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
This is all `Controller` does in react-hook-form, and it is the one place that
|
|
311
|
+
binding silently breaks there. Here the value is in the form, so it submits
|
|
312
|
+
whether or not anything else works. `HiddenValue` is a component rather than a
|
|
313
|
+
props helper because React updates a controlled value without any DOM event —
|
|
314
|
+
the component announces each change with one, so cross-field rules and
|
|
315
|
+
`isDirty` hear it like any keystroke.
|
|
316
|
+
|
|
317
|
+
## Multi-step forms
|
|
318
|
+
|
|
319
|
+
Keep every step mounted and hide the ones you are not on. The values stay in
|
|
320
|
+
the DOM, so moving between steps costs nothing and losing a step is impossible:
|
|
321
|
+
|
|
322
|
+
```tsx
|
|
323
|
+
<div hidden={step !== 1} ref={stepOne}>
|
|
324
|
+
{/* … */}
|
|
325
|
+
</div>
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
Validate one step before advancing by checking only the controls inside that
|
|
329
|
+
step's container — the hidden steps are not filled in yet, so checking the
|
|
330
|
+
whole form would always fail:
|
|
331
|
+
|
|
332
|
+
```tsx
|
|
333
|
+
const stepIsValid = [
|
|
334
|
+
...(stepOne.current?.querySelectorAll('input, select, textarea') ?? []),
|
|
335
|
+
].every((control) => (control as HTMLInputElement).checkValidity());
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Without JavaScript this degrades to one long form that submits in a single
|
|
339
|
+
request — which is the correct behaviour, not a broken one.
|
|
340
|
+
|
|
341
|
+
## What it does not do yet
|
|
342
|
+
|
|
343
|
+
- Input masking. Rewriting `el.value` on input works with the DOM as the source
|
|
344
|
+
of truth, but managing the caret is a separate problem from wiring a form.
|
|
345
|
+
|
|
346
|
+
## Working with @k8ordo/ui
|
|
347
|
+
|
|
348
|
+
`@k8ordo/form` does not depend on `@k8ordo/ui`. Attributes go to the input;
|
|
349
|
+
`FormControl` gets only what it uses, and generates the `id` and `aria-*`
|
|
350
|
+
links itself.
|
|
351
|
+
|
|
352
|
+
```tsx
|
|
353
|
+
const title = form.field('title');
|
|
354
|
+
|
|
355
|
+
<FormControl
|
|
356
|
+
errorText={title.error}
|
|
357
|
+
invalid={title.invalid}
|
|
358
|
+
label="タイトル"
|
|
359
|
+
required={title.required}
|
|
360
|
+
renderInput={(props) => <TextField {...props} {...title.input} />}
|
|
361
|
+
/>;
|
|
362
|
+
```
|
package/docs/llms.txt
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# @k8ordo/form
|
|
2
|
+
|
|
3
|
+
> Derive HTML constraint attributes, error messages, and server-side validation
|
|
4
|
+
> from one zod schema. The DOM holds the values; React state holds only what the
|
|
5
|
+
> DOM cannot express. Forms work with JavaScript disabled or not yet loaded.
|
|
6
|
+
>
|
|
7
|
+
> Like every k8ordo package it assumes React 19 and Server Components, uses only
|
|
8
|
+
> what has reached Baseline newly available, and ships no polyfills or legacy
|
|
9
|
+
> fallbacks.
|
|
10
|
+
|
|
11
|
+
When installed via npm, these docs live under `node_modules/@k8ordo/form/docs/`.
|
|
12
|
+
|
|
13
|
+
## Guide
|
|
14
|
+
|
|
15
|
+
- [Design Guide](GUIDE.md): the server/client split, writing a form end to end,
|
|
16
|
+
what the package guarantees, what it does not do yet, and how it pairs with
|
|
17
|
+
`@k8ordo/ui` without depending on it.
|
|
18
|
+
|
|
19
|
+
## Entry points
|
|
20
|
+
|
|
21
|
+
- `@k8ordo/form` — client. `useForm(fields, state)` takes the whole
|
|
22
|
+
`formFields` result and returns props for the `<form>` element, a
|
|
23
|
+
`field(path)` accessor giving input attributes, the current error, and
|
|
24
|
+
`invalid` / `required`, an `array(path)` accessor for repeated rows, and
|
|
25
|
+
`isDirty`. Also `useAsyncCheck` for server-answered per-field checks and
|
|
26
|
+
`HiddenValue` for components that render no input of their own.
|
|
27
|
+
- `@k8ordo/form/server` — server. `formFields(schema)` derives serializable
|
|
28
|
+
fields plus a `dropped` list of checks HTML cannot express, and throws on a
|
|
29
|
+
schema no form can represent; `parseForm(schema, formData)` validates a
|
|
30
|
+
submission and returns typed data or per-field errors with the submitted
|
|
31
|
+
values. `defineForm(schema, rules)` attaches cross-field rules (`sameAs`,
|
|
32
|
+
`minChecked`, `requiredWhen`) typed against the schema's paths.
|
package/package.json
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@k8ordo/form",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Derive HTML constraint attributes and per-field errors from one zod schema. Works without JavaScript.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"constraint-validation",
|
|
7
|
+
"form",
|
|
8
|
+
"progressive-enhancement",
|
|
9
|
+
"react",
|
|
10
|
+
"server-actions",
|
|
11
|
+
"zod"
|
|
12
|
+
],
|
|
13
|
+
"homepage": "https://ordo.k8o.me",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"author": "k8o <kosakanoki@gmail.com>",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/k35o/k8ordo.git"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist/**",
|
|
22
|
+
"docs/**"
|
|
23
|
+
],
|
|
24
|
+
"type": "module",
|
|
25
|
+
"sideEffects": false,
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"types": "./dist/index.d.mts",
|
|
29
|
+
"default": "./dist/index.mjs"
|
|
30
|
+
},
|
|
31
|
+
"./server": {
|
|
32
|
+
"types": "./dist/server.d.mts",
|
|
33
|
+
"default": "./dist/server.mjs"
|
|
34
|
+
},
|
|
35
|
+
"./package.json": "./package.json"
|
|
36
|
+
},
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public",
|
|
39
|
+
"registry": "https://registry.npmjs.org"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/react": "19.2.18",
|
|
43
|
+
"@vitejs/plugin-react": "6.0.5",
|
|
44
|
+
"@vitest/browser-playwright": "4.1.10",
|
|
45
|
+
"react": "19.2.8",
|
|
46
|
+
"react-dom": "19.2.8",
|
|
47
|
+
"typescript": "7.0.2",
|
|
48
|
+
"vite": "8.2.1",
|
|
49
|
+
"vite-plus": "0.2.9",
|
|
50
|
+
"vitest": "4.1.10",
|
|
51
|
+
"vitest-browser-react": "2.2.0",
|
|
52
|
+
"zod": "4.4.3"
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"@types/react": ">=19.2.18",
|
|
56
|
+
"react": ">=19.2.6",
|
|
57
|
+
"react-dom": ">=19.2.6",
|
|
58
|
+
"typescript": ">=7.0.2",
|
|
59
|
+
"zod": "^4.4.3"
|
|
60
|
+
},
|
|
61
|
+
"peerDependenciesMeta": {
|
|
62
|
+
"@types/react": {
|
|
63
|
+
"optional": true
|
|
64
|
+
},
|
|
65
|
+
"typescript": {
|
|
66
|
+
"optional": true
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
"scripts": {
|
|
70
|
+
"build": "vp pack",
|
|
71
|
+
"test": "vp test",
|
|
72
|
+
"typecheck": "tsc --noEmit",
|
|
73
|
+
"check": "vp check",
|
|
74
|
+
"check:write": "vp check --fix"
|
|
75
|
+
}
|
|
76
|
+
}
|