@rsc-kit/press 0.30.47 → 0.30.48

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.
@@ -1,31 +1,46 @@
1
- import type { FieldOverride, FormSchema, ValuesOf } from '../resource.js';
1
+ import type { ReactNode } from 'react';
2
+ import type { Field, FieldOverride, FormSchema, ValuesOf } from '../resource.js';
3
+ import type { HostFunction, HostInput } from './host.js';
2
4
  import type { FormsTheme } from './theme.js';
3
- export interface FormDefinition<Schema extends FormSchema> {
5
+ interface FormOptions {
4
6
  /** Unique in the app: the form is found by it when it is sent. Lowercase letters, digits and dashes. */
5
7
  name: string;
8
+ /** What draws it: import { shadcn } from '@rsc-kit/press/shadcn'. */
9
+ theme: FormsTheme;
10
+ /** The submit button's words. Send by default. */
11
+ submit?: string;
12
+ /** What the visitor reads in place of the form once it is sent. */
13
+ done?: string;
14
+ /** Where the visitor goes once it is sent, instead of `done`. */
15
+ redirect?: string;
16
+ }
17
+ export interface FormDefinition<Schema extends FormSchema> extends FormOptions {
6
18
  /** Any Standard Schema of an object: z.object(), v.object(), type({...}). It checks every submission, on the server. */
7
19
  form: Schema;
8
20
  /** Labels, descriptions and controls the schema cannot say - or every field, for a schema that does not describe itself as JSON Schema. */
9
21
  fields?: Partial<Record<keyof ValuesOf<Schema> & string, FieldOverride>>;
10
- /** What draws it: import { shadcn } from '@rsc-kit/press/shadcn'. */
11
- theme: FormsTheme;
12
22
  /**
13
23
  * What a submission that passed the schema does: store it, send it, call a
14
24
  * backend. Throw an ActionValidationError - or let a backend's 422 through -
15
25
  * and its messages land under the fields.
16
26
  */
17
27
  handle: (values: ValuesOf<Schema>) => void | Promise<void>;
18
- /** The submit button's words. Send by default. */
19
- submit?: string;
20
- /** What the visitor reads in place of the form once it is sent. */
21
- done?: string;
22
- /** Where the visitor goes once it is sent, instead of `done`. */
23
- redirect?: string;
28
+ }
29
+ export interface HostFormDefinition<Action extends HostFunction> extends FormOptions {
30
+ /**
31
+ * The backend function the form calls: Contact.send. Its fields are drawn
32
+ * from what the backend says it takes, and it does all the checking: a
33
+ * Laravel FormRequest, a Go function. Its refusals land under the fields.
34
+ */
35
+ action: Action;
36
+ /** Labels, descriptions and controls the backend's type cannot say. */
37
+ fields?: Partial<Record<keyof HostInput<Action> & string, FieldOverride>>;
24
38
  }
25
39
  /**
26
- * A form anyone may fill in - a contact form, a sign-up, a survey - from a
27
- * Standard Schema, checked by the browser first and by the schema on the
28
- * server always. Place it in any page:
40
+ * A form anyone may fill in - a contact form, a sign-up, a survey. Either from
41
+ * a Standard Schema, checked by the browser first and by the schema on the
42
+ * server always, or from a backend function, checked by the browser first and
43
+ * by the backend always. Place it in any page:
29
44
  *
30
45
  * export default function Contact() {
31
46
  * return <main><h1>Contact us</h1><contact.Form /></main>
@@ -34,9 +49,12 @@ export interface FormDefinition<Schema extends FormSchema> {
34
49
  * The form reads nothing from the request, so the page around it can still
35
50
  * be stored by the build.
36
51
  */
37
- export declare function defineForm<Schema extends FormSchema>(definition: FormDefinition<Schema>): {
52
+ export declare function defineForm<Schema extends FormSchema>(definition: FormDefinition<Schema>): PublicFormHandle;
53
+ export declare function defineForm<Action extends HostFunction>(definition: HostFormDefinition<Action>): PublicFormHandle;
54
+ export interface PublicFormHandle {
38
55
  name: string;
39
- fields: import("../resource.js").Field[];
40
- Form: () => import("react").JSX.Element;
41
- };
42
- export type PublicForm = ReturnType<typeof defineForm>;
56
+ fields: Field[];
57
+ Form: () => ReactNode;
58
+ }
59
+ export type PublicForm = PublicFormHandle;
60
+ export {};
@@ -1,20 +1,9 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { fieldsOf } from '../resource.js';
3
3
  import { submitForm } from './actions.js';
4
+ import { callHost, hostSchema, requireDrawable } from './host.js';
4
5
  import { HONEYPOT } from './honeypot.js';
5
6
  import { registerForm } from './registry.js';
6
- /**
7
- * A form anyone may fill in - a contact form, a sign-up, a survey - from a
8
- * Standard Schema, checked by the browser first and by the schema on the
9
- * server always. Place it in any page:
10
- *
11
- * export default function Contact() {
12
- * return <main><h1>Contact us</h1><contact.Form /></main>
13
- * }
14
- *
15
- * The form reads nothing from the request, so the page around it can still
16
- * be stored by the build.
17
- */
18
7
  export function defineForm(definition) {
19
8
  if (!/^[a-z0-9-]+$/.test(definition.name)) {
20
9
  throw new Error(`[forms] a form's name is lowercase letters, digits and dashes: ${JSON.stringify(definition.name)}`);
@@ -22,23 +11,52 @@ export function defineForm(definition) {
22
11
  if (definition.done !== undefined && definition.redirect !== undefined) {
23
12
  throw new Error(`[forms] the form ${definition.name} has both done and redirect; a sent form shows one or goes to the other`);
24
13
  }
25
- const fields = fieldsOf(definition.form, definition.fields ?? {});
26
- if (fields.some((field) => field.name === HONEYPOT)) {
27
- throw new Error(`[forms] the form ${definition.name} has a field named ${HONEYPOT}, which is the name its bot trap uses`);
14
+ const { action } = definition;
15
+ if ((action === undefined) === (definition.form === undefined)) {
16
+ throw new Error(`[forms] the form ${definition.name} needs a form schema with handle, or an action, and not both`);
28
17
  }
18
+ const overrides = (definition.fields ?? {});
19
+ const schema = action === undefined ? definition.form : hostSchema(action);
20
+ let drawn = null;
21
+ function fields() {
22
+ if (drawn)
23
+ return drawn;
24
+ if (action !== undefined)
25
+ requireDrawable(definition.name, action, overrides);
26
+ const fields = fieldsOf(schema, overrides);
27
+ if (fields.some((field) => field.name === HONEYPOT)) {
28
+ throw new Error(`[forms] the form ${definition.name} has a field named ${HONEYPOT}, which is the name its bot trap uses`);
29
+ }
30
+ return (drawn = fields);
31
+ }
32
+ // A backend's types arrive with the server entry, which runs after this
33
+ // module: those fields wait for the first render. A schema's fail here.
34
+ if (action === undefined)
35
+ fields();
36
+ const handle = action === undefined
37
+ ? definition.handle
38
+ : async (values) => void (await callHost(action, values));
29
39
  registerForm({
30
40
  name: definition.name,
31
- schema: definition.form,
32
- fields,
33
- handle: definition.handle,
41
+ schema,
42
+ get fields() {
43
+ return fields();
44
+ },
45
+ handle,
34
46
  redirect: definition.redirect ?? null,
35
47
  });
36
48
  const theme = definition.theme;
37
49
  const submit = definition.submit ?? 'Send';
38
50
  const done = definition.redirect === undefined ? (definition.done ?? 'Thank you. It was sent.') : undefined;
39
51
  function Form() {
40
- return (_jsx(theme.PublicForm, { action: submitForm.bind(null, definition.name), fields: fields, submit: submit, ...(done === undefined ? {} : { done }), honeypot: HONEYPOT }));
52
+ return (_jsx(theme.PublicForm, { action: submitForm.bind(null, definition.name), fields: fields(), submit: submit, ...(done === undefined ? {} : { done }), honeypot: HONEYPOT }));
41
53
  }
42
- return { name: definition.name, fields, Form };
54
+ return {
55
+ name: definition.name,
56
+ get fields() {
57
+ return fields();
58
+ },
59
+ Form,
60
+ };
43
61
  }
44
62
  //# sourceMappingURL=form.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"form.js","sourceRoot":"","sources":["../../src/forms/form.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAEzC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AA0B5C;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,UAAU,CAA4B,UAAkC;IACtF,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QAC1C,MAAM,IAAI,KAAK,CAAC,kEAAkE,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IACtH,CAAC;IAED,IAAI,UAAU,CAAC,IAAI,KAAK,SAAS,IAAI,UAAU,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;QACvE,MAAM,IAAI,KAAK,CAAC,oBAAoB,UAAU,CAAC,IAAI,yEAAyE,CAAC,CAAA;IAC/H,CAAC;IAED,MAAM,MAAM,GAAG,QAAQ,CAAC,UAAU,CAAC,IAAI,EAAE,UAAU,CAAC,MAAM,IAAI,EAAE,CAAC,CAAA;IAEjE,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,KAAK,CAAC,oBAAoB,UAAU,CAAC,IAAI,sBAAsB,QAAQ,uCAAuC,CAAC,CAAA;IAC3H,CAAC;IAED,YAAY,CAAC;QACX,IAAI,EAAE,UAAU,CAAC,IAAI;QACrB,MAAM,EAAE,UAAU,CAAC,IAAI;QACvB,MAAM;QACN,MAAM,EAAE,UAAU,CAAC,MAAmE;QACtF,QAAQ,EAAE,UAAU,CAAC,QAAQ,IAAI,IAAI;KACtC,CAAC,CAAA;IAEF,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAA;IAC9B,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,IAAI,MAAM,CAAA;IAC1C,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,IAAI,yBAAyB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IAE3G,SAAS,IAAI;QACX,OAAO,CACL,KAAC,KAAK,CAAC,UAAU,IACf,MAAM,EAAE,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,IAAI,CAAC,EAC9C,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,KACV,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EACxC,QAAQ,EAAE,QAAQ,GAClB,CACH,CAAA;IACH,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAA;AAChD,CAAC","sourcesContent":["import { fieldsOf } from '../resource.js'\nimport type { FieldOverride, FormSchema, ValuesOf } from '../resource.js'\nimport { submitForm } from './actions.js'\nimport { HONEYPOT } from './honeypot.js'\nimport { registerForm } from './registry.js'\nimport type { FormsTheme } from './theme.js'\n\nexport interface FormDefinition<Schema extends FormSchema> {\n /** Unique in the app: the form is found by it when it is sent. Lowercase letters, digits and dashes. */\n name: string\n /** Any Standard Schema of an object: z.object(), v.object(), type({...}). It checks every submission, on the server. */\n form: Schema\n /** Labels, descriptions and controls the schema cannot say - or every field, for a schema that does not describe itself as JSON Schema. */\n fields?: Partial<Record<keyof ValuesOf<Schema> & string, FieldOverride>>\n /** What draws it: import { shadcn } from '@rsc-kit/press/shadcn'. */\n theme: FormsTheme\n /**\n * What a submission that passed the schema does: store it, send it, call a\n * backend. Throw an ActionValidationError - or let a backend's 422 through -\n * and its messages land under the fields.\n */\n handle: (values: ValuesOf<Schema>) => void | Promise<void>\n /** The submit button's words. Send by default. */\n submit?: string\n /** What the visitor reads in place of the form once it is sent. */\n done?: string\n /** Where the visitor goes once it is sent, instead of `done`. */\n redirect?: string\n}\n\n/**\n * A form anyone may fill in - a contact form, a sign-up, a survey - from a\n * Standard Schema, checked by the browser first and by the schema on the\n * server always. Place it in any page:\n *\n * export default function Contact() {\n * return <main><h1>Contact us</h1><contact.Form /></main>\n * }\n *\n * The form reads nothing from the request, so the page around it can still\n * be stored by the build.\n */\nexport function defineForm<Schema extends FormSchema>(definition: FormDefinition<Schema>) {\n if (!/^[a-z0-9-]+$/.test(definition.name)) {\n throw new Error(`[forms] a form's name is lowercase letters, digits and dashes: ${JSON.stringify(definition.name)}`)\n }\n\n if (definition.done !== undefined && definition.redirect !== undefined) {\n throw new Error(`[forms] the form ${definition.name} has both done and redirect; a sent form shows one or goes to the other`)\n }\n\n const fields = fieldsOf(definition.form, definition.fields ?? {})\n\n if (fields.some((field) => field.name === HONEYPOT)) {\n throw new Error(`[forms] the form ${definition.name} has a field named ${HONEYPOT}, which is the name its bot trap uses`)\n }\n\n registerForm({\n name: definition.name,\n schema: definition.form,\n fields,\n handle: definition.handle as (values: Record<string, unknown>) => void | Promise<void>,\n redirect: definition.redirect ?? null,\n })\n\n const theme = definition.theme\n const submit = definition.submit ?? 'Send'\n const done = definition.redirect === undefined ? (definition.done ?? 'Thank you. It was sent.') : undefined\n\n function Form() {\n return (\n <theme.PublicForm\n action={submitForm.bind(null, definition.name)}\n fields={fields}\n submit={submit}\n {...(done === undefined ? {} : { done })}\n honeypot={HONEYPOT}\n />\n )\n }\n\n return { name: definition.name, fields, Form }\n}\n\nexport type PublicForm = ReturnType<typeof defineForm>\n"]}
1
+ {"version":3,"file":"form.js","sourceRoot":"","sources":["../../src/forms/form.tsx"],"names":[],"mappings":"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","sourcesContent":["import type { ReactNode } from 'react'\nimport { fieldsOf } from '../resource.js'\nimport type { Field, FieldOverride, FormSchema, ValuesOf } from '../resource.js'\nimport { submitForm } from './actions.js'\nimport { callHost, hostSchema, requireDrawable } from './host.js'\nimport type { HostFunction, HostInput } from './host.js'\nimport { HONEYPOT } from './honeypot.js'\nimport { registerForm } from './registry.js'\nimport type { FormsTheme } from './theme.js'\n\ninterface FormOptions {\n /** Unique in the app: the form is found by it when it is sent. Lowercase letters, digits and dashes. */\n name: string\n /** What draws it: import { shadcn } from '@rsc-kit/press/shadcn'. */\n theme: FormsTheme\n /** The submit button's words. Send by default. */\n submit?: string\n /** What the visitor reads in place of the form once it is sent. */\n done?: string\n /** Where the visitor goes once it is sent, instead of `done`. */\n redirect?: string\n}\n\nexport interface FormDefinition<Schema extends FormSchema> extends FormOptions {\n /** Any Standard Schema of an object: z.object(), v.object(), type({...}). It checks every submission, on the server. */\n form: Schema\n /** Labels, descriptions and controls the schema cannot say - or every field, for a schema that does not describe itself as JSON Schema. */\n fields?: Partial<Record<keyof ValuesOf<Schema> & string, FieldOverride>>\n /**\n * What a submission that passed the schema does: store it, send it, call a\n * backend. Throw an ActionValidationError - or let a backend's 422 through -\n * and its messages land under the fields.\n */\n handle: (values: ValuesOf<Schema>) => void | Promise<void>\n}\n\nexport interface HostFormDefinition<Action extends HostFunction> extends FormOptions {\n /**\n * The backend function the form calls: Contact.send. Its fields are drawn\n * from what the backend says it takes, and it does all the checking: a\n * Laravel FormRequest, a Go function. Its refusals land under the fields.\n */\n action: Action\n /** Labels, descriptions and controls the backend's type cannot say. */\n fields?: Partial<Record<keyof HostInput<Action> & string, FieldOverride>>\n}\n\n/**\n * A form anyone may fill in - a contact form, a sign-up, a survey. Either from\n * a Standard Schema, checked by the browser first and by the schema on the\n * server always, or from a backend function, checked by the browser first and\n * by the backend always. Place it in any page:\n *\n * export default function Contact() {\n * return <main><h1>Contact us</h1><contact.Form /></main>\n * }\n *\n * The form reads nothing from the request, so the page around it can still\n * be stored by the build.\n */\nexport function defineForm<Schema extends FormSchema>(definition: FormDefinition<Schema>): PublicFormHandle\nexport function defineForm<Action extends HostFunction>(definition: HostFormDefinition<Action>): PublicFormHandle\nexport function defineForm(definition: (FormDefinition<FormSchema> | HostFormDefinition<string>) & { form?: FormSchema; action?: string }): PublicFormHandle {\n if (!/^[a-z0-9-]+$/.test(definition.name)) {\n throw new Error(`[forms] a form's name is lowercase letters, digits and dashes: ${JSON.stringify(definition.name)}`)\n }\n\n if (definition.done !== undefined && definition.redirect !== undefined) {\n throw new Error(`[forms] the form ${definition.name} has both done and redirect; a sent form shows one or goes to the other`)\n }\n\n const { action } = definition\n\n if ((action === undefined) === (definition.form === undefined)) {\n throw new Error(`[forms] the form ${definition.name} needs a form schema with handle, or an action, and not both`)\n }\n\n const overrides = (definition.fields ?? {}) as Record<string, FieldOverride>\n const schema = action === undefined ? definition.form! : hostSchema(action)\n let drawn: Field[] | null = null\n\n function fields(): Field[] {\n if (drawn) return drawn\n\n if (action !== undefined) requireDrawable(definition.name, action, overrides)\n\n const fields = fieldsOf(schema, overrides)\n\n if (fields.some((field) => field.name === HONEYPOT)) {\n throw new Error(`[forms] the form ${definition.name} has a field named ${HONEYPOT}, which is the name its bot trap uses`)\n }\n\n return (drawn = fields)\n }\n\n // A backend's types arrive with the server entry, which runs after this\n // module: those fields wait for the first render. A schema's fail here.\n if (action === undefined) fields()\n\n const handle = action === undefined\n ? ((definition as FormDefinition<FormSchema>).handle as (values: Record<string, unknown>) => void | Promise<void>)\n : async (values: Record<string, unknown>) => void (await callHost(action, values))\n\n registerForm({\n name: definition.name,\n schema,\n get fields() {\n return fields()\n },\n handle,\n redirect: definition.redirect ?? null,\n })\n\n const theme = definition.theme\n const submit = definition.submit ?? 'Send'\n const done = definition.redirect === undefined ? (definition.done ?? 'Thank you. It was sent.') : undefined\n\n function Form() {\n return (\n <theme.PublicForm\n action={submitForm.bind(null, definition.name)}\n fields={fields()}\n submit={submit}\n {...(done === undefined ? {} : { done })}\n honeypot={HONEYPOT}\n />\n )\n }\n\n return {\n name: definition.name,\n get fields() {\n return fields()\n },\n Form,\n }\n}\n\nexport interface PublicFormHandle {\n name: string\n fields: Field[]\n Form: () => ReactNode\n}\n\nexport type PublicForm = PublicFormHandle\n"]}
@@ -0,0 +1,26 @@
1
+ import type { FieldOverride, FormSchema } from '../resource.js';
2
+ /**
3
+ * A form with no schema of its own: defineForm({ action: 'Contact.send' }).
4
+ * The backend said what the function takes when it wrote rsc-host.json, and
5
+ * the build put that in the server entry, so the fields come from there and
6
+ * every check is the backend's.
7
+ */
8
+ declare global {
9
+ interface RscHostFunctions {
10
+ }
11
+ }
12
+ /** A function the backend has, once the build has typed them; any name before. */
13
+ export type HostFunction = [keyof RscHostFunctions] extends [never] ? string : keyof RscHostFunctions & string;
14
+ /** What a function takes as its one argument. */
15
+ export type HostInput<Action extends string> = Action extends keyof RscHostFunctions ? RscHostFunctions[Action] extends {
16
+ args: [infer Input, ...unknown[]];
17
+ } ? Input : Record<string, unknown> : Record<string, unknown>;
18
+ /**
19
+ * A Standard Schema that describes the backend's input and refuses nothing:
20
+ * the rest of the form is the same as for any schema, and the checking is the
21
+ * function's. The browser's checks come from the same description.
22
+ */
23
+ export declare function hostSchema(action: string): FormSchema;
24
+ /** A form must be able to draw what it was given, or say so rather than draw a text box. */
25
+ export declare function requireDrawable(form: string, action: string, overrides: Record<string, FieldOverride>): void;
26
+ export declare function callHost(action: string, values: Record<string, unknown>): Promise<unknown>;
@@ -0,0 +1,41 @@
1
+ function hostForms() {
2
+ const forms = globalThis[Symbol.for('rsc-kit.host-forms')];
3
+ if (!forms)
4
+ throw new Error('[forms] a form with an action needs the rsc-kit server entry, which says what the backend takes');
5
+ return forms;
6
+ }
7
+ /**
8
+ * A Standard Schema that describes the backend's input and refuses nothing:
9
+ * the rest of the form is the same as for any schema, and the checking is the
10
+ * function's. The browser's checks come from the same description.
11
+ */
12
+ export function hostSchema(action) {
13
+ return {
14
+ '~standard': {
15
+ version: 1,
16
+ vendor: 'rsc-kit',
17
+ validate: (value) => ({ value: value }),
18
+ jsonSchema: { input: () => hostForms().inputs[action] },
19
+ },
20
+ };
21
+ }
22
+ /** A form must be able to draw what it was given, or say so rather than draw a text box. */
23
+ export function requireDrawable(form, action, overrides) {
24
+ const input = hostForms().inputs[action];
25
+ if (!input) {
26
+ if (Object.keys(overrides).length > 0)
27
+ return;
28
+ throw new Error(`[forms] the form ${form} calls ${action}, and the backend does not say it takes one object with named fields. ` +
29
+ 'Check the name, or list every field in fields.');
30
+ }
31
+ for (const [field, schema] of Object.entries(input.properties ?? {})) {
32
+ if (!schema.type && !schema.enum && !schema.anyOf && !overrides[field]?.type) {
33
+ throw new Error(`[forms] the form ${form} cannot tell what ${action}'s ${field} is, which a file or an open type would be. Say it in fields.`);
34
+ }
35
+ }
36
+ }
37
+ export function callHost(action, values) {
38
+ const call = globalThis[hostForms().global];
39
+ return call(action, values);
40
+ }
41
+ //# sourceMappingURL=host.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host.js","sourceRoot":"","sources":["../../src/forms/host.ts"],"names":[],"mappings":"AA4BA,SAAS,SAAS;IAChB,MAAM,KAAK,GAAI,UAAsC,CAAC,MAAM,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAA0B,CAAA;IAEhH,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,iGAAiG,CAAC,CAAA;IAE9H,OAAO,KAAK,CAAA;AACd,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,MAAc;IACvC,OAAO;QACL,WAAW,EAAE;YACX,OAAO,EAAE,CAAC;YACV,MAAM,EAAE,SAAS;YACjB,QAAQ,EAAE,CAAC,KAAc,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,KAAgC,EAAE,CAAC;YAC3E,UAAU,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,SAAS,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE;SACxD;KACuB,CAAA;AAC5B,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,eAAe,CAAC,IAAY,EAAE,MAAc,EAAE,SAAwC;IACpG,MAAM,KAAK,GAAG,SAAS,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;IAExC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,IAAI,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,MAAM,GAAG,CAAC;YAAE,OAAM;QAE7C,MAAM,IAAI,KAAK,CACb,oBAAoB,IAAI,UAAU,MAAM,wEAAwE;YAC9G,gDAAgD,CACnD,CAAA;IACH,CAAC;IAED,KAAK,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,UAAU,IAAI,EAAE,CAAC,EAAE,CAAC;QACrE,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC;YAC7E,MAAM,IAAI,KAAK,CAAC,oBAAoB,IAAI,qBAAqB,MAAM,MAAM,KAAK,+DAA+D,CAAC,CAAA;QAChJ,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,MAAc,EAAE,MAA+B;IACtE,MAAM,IAAI,GAAI,UAAiD,CAAC,SAAS,EAAE,CAAC,MAAM,CAA2D,CAAA;IAE7I,OAAO,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;AAC7B,CAAC","sourcesContent":["import type { FieldOverride, FormSchema } from '../resource.js'\n\n/**\n * A form with no schema of its own: defineForm({ action: 'Contact.send' }).\n * The backend said what the function takes when it wrote rsc-host.json, and\n * the build put that in the server entry, so the fields come from there and\n * every check is the backend's.\n */\ndeclare global {\n interface RscHostFunctions {}\n}\n\n/** A function the backend has, once the build has typed them; any name before. */\nexport type HostFunction = [keyof RscHostFunctions] extends [never] ? string : keyof RscHostFunctions & string\n\n/** What a function takes as its one argument. */\nexport type HostInput<Action extends string> = Action extends keyof RscHostFunctions\n ? RscHostFunctions[Action] extends { args: [infer Input, ...unknown[]] }\n ? Input\n : Record<string, unknown>\n : Record<string, unknown>\n\ninterface HostForms {\n /** The global that calls the backend: rpc, unless rscKit({ hostGlobal }) renamed it. */\n global: string\n inputs: Record<string, { properties?: Record<string, Record<string, unknown>> }>\n}\n\nfunction hostForms(): HostForms {\n const forms = (globalThis as Record<symbol, unknown>)[Symbol.for('rsc-kit.host-forms')] as HostForms | undefined\n\n if (!forms) throw new Error('[forms] a form with an action needs the rsc-kit server entry, which says what the backend takes')\n\n return forms\n}\n\n/**\n * A Standard Schema that describes the backend's input and refuses nothing:\n * the rest of the form is the same as for any schema, and the checking is the\n * function's. The browser's checks come from the same description.\n */\nexport function hostSchema(action: string): FormSchema {\n return {\n '~standard': {\n version: 1,\n vendor: 'rsc-kit',\n validate: (value: unknown) => ({ value: value as Record<string, unknown> }),\n jsonSchema: { input: () => hostForms().inputs[action] },\n },\n } as unknown as FormSchema\n}\n\n/** A form must be able to draw what it was given, or say so rather than draw a text box. */\nexport function requireDrawable(form: string, action: string, overrides: Record<string, FieldOverride>): void {\n const input = hostForms().inputs[action]\n\n if (!input) {\n if (Object.keys(overrides).length > 0) return\n\n throw new Error(\n `[forms] the form ${form} calls ${action}, and the backend does not say it takes one object with named fields. ` +\n 'Check the name, or list every field in fields.',\n )\n }\n\n for (const [field, schema] of Object.entries(input.properties ?? {})) {\n if (!schema.type && !schema.enum && !schema.anyOf && !overrides[field]?.type) {\n throw new Error(`[forms] the form ${form} cannot tell what ${action}'s ${field} is, which a file or an open type would be. Say it in fields.`)\n }\n }\n}\n\nexport function callHost(action: string, values: Record<string, unknown>): Promise<unknown> {\n const call = (globalThis as unknown as Record<string, unknown>)[hostForms().global] as (name: string, ...args: unknown[]) => Promise<unknown>\n\n return call(action, values)\n}\n"]}
@@ -1,3 +1,3 @@
1
1
  export { defineForm } from './form.js';
2
- export type { FormDefinition, PublicForm } from './form.js';
2
+ export type { FormDefinition, HostFormDefinition, PublicForm } from './form.js';
3
3
  export type { FormsTheme, PublicFormProps } from './theme.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/forms/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA","sourcesContent":["export { defineForm } from './form.js'\nexport type { FormDefinition, PublicForm } from './form.js'\nexport type { FormsTheme, PublicFormProps } from './theme.js'\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/forms/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA","sourcesContent":["export { defineForm } from './form.js'\nexport type { FormDefinition, HostFormDefinition, PublicForm } from './form.js'\nexport type { FormsTheme, PublicFormProps } from './theme.js'\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rsc-kit/press",
3
- "version": "0.30.47",
3
+ "version": "0.30.48",
4
4
  "description": "Admin panels, public forms and pages built from blocks for rsc-kit: resources, data sources, tenants and plugins, with the drawing in a theme - shadcn/ui is the one that ships.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -50,7 +50,7 @@
50
50
  "prepack": "bun run build"
51
51
  },
52
52
  "peerDependencies": {
53
- "@rsc-kit/core": "^0.30.47",
53
+ "@rsc-kit/core": "^0.30.48",
54
54
  "react": "^19.2.0",
55
55
  "react-dom": "^19.2.0",
56
56
  "tailwindcss": "^4.0.0"