@rsc-kit/press 0.30.48 → 0.30.49

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,6 +1,6 @@
1
1
  import type { ReactNode } from 'react';
2
2
  import type { Field, FieldOverride, FormSchema, ValuesOf } from '../resource.js';
3
- import type { HostFunction, HostInput } from './host.js';
3
+ import type { HostAction, HostFields } from './host.js';
4
4
  import type { FormsTheme } from './theme.js';
5
5
  interface FormOptions {
6
6
  /** Unique in the app: the form is found by it when it is sent. Lowercase letters, digits and dashes. */
@@ -26,15 +26,16 @@ export interface FormDefinition<Schema extends FormSchema> extends FormOptions {
26
26
  */
27
27
  handle: (values: ValuesOf<Schema>) => void | Promise<void>;
28
28
  }
29
- export interface HostFormDefinition<Action extends HostFunction> extends FormOptions {
29
+ export interface HostFormDefinition<Action extends HostAction> extends FormOptions {
30
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.
31
+ * The backend action the form calls, as the build generated it:
32
+ * `import { contactSend } from '../server-actions.generated'`. Its fields are
33
+ * drawn from what the backend says it takes, and it does all the checking:
34
+ * a Laravel FormRequest, a Go struct. Its refusals land under the fields.
34
35
  */
35
36
  action: Action;
36
37
  /** Labels, descriptions and controls the backend's type cannot say. */
37
- fields?: Partial<Record<keyof HostInput<Action> & string, FieldOverride>>;
38
+ fields?: Partial<Record<HostFields<Action>, FieldOverride>>;
38
39
  }
39
40
  /**
40
41
  * A form anyone may fill in - a contact form, a sign-up, a survey. Either from
@@ -50,7 +51,7 @@ export interface HostFormDefinition<Action extends HostFunction> extends FormOpt
50
51
  * be stored by the build.
51
52
  */
52
53
  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 declare function defineForm<Action extends HostAction>(definition: HostFormDefinition<Action>): PublicFormHandle;
54
55
  export interface PublicFormHandle {
55
56
  name: string;
56
57
  fields: Field[];
@@ -1,7 +1,7 @@
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
+ import { hostSchema, requireDrawable } from './host.js';
5
5
  import { HONEYPOT } from './honeypot.js';
6
6
  import { registerForm } from './registry.js';
7
7
  export function defineForm(definition) {
@@ -12,6 +12,10 @@ export function defineForm(definition) {
12
12
  throw new Error(`[forms] the form ${definition.name} has both done and redirect; a sent form shows one or goes to the other`);
13
13
  }
14
14
  const { action } = definition;
15
+ if (typeof action === 'string') {
16
+ throw new Error(`[forms] the form ${definition.name}'s action is the generated action now, not its name: ` +
17
+ `import { contactSend } from '../server-actions.generated', then action: contactSend, for ${JSON.stringify(action)}`);
18
+ }
15
19
  if ((action === undefined) === (definition.form === undefined)) {
16
20
  throw new Error(`[forms] the form ${definition.name} needs a form schema with handle, or an action, and not both`);
17
21
  }
@@ -35,7 +39,7 @@ export function defineForm(definition) {
35
39
  fields();
36
40
  const handle = action === undefined
37
41
  ? definition.handle
38
- : async (values) => void (await callHost(action, values));
42
+ : async (values) => void (await action(values));
39
43
  registerForm({
40
44
  name: definition.name,
41
45
  schema,
@@ -1 +1 @@
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"]}
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 { hostSchema, requireDrawable } from './host.js'\nimport type { HostAction, HostFields } 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 HostAction> extends FormOptions {\n /**\n * The backend action the form calls, as the build generated it:\n * `import { contactSend } from '../server-actions.generated'`. Its fields are\n * drawn from what the backend says it takes, and it does all the checking:\n * a Laravel FormRequest, a Go struct. 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<HostFields<Action>, 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 HostAction>(definition: HostFormDefinition<Action>): PublicFormHandle\nexport function defineForm(definition: (FormDefinition<FormSchema> | HostFormDefinition<HostAction>) & { form?: FormSchema; action?: HostAction }): 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 (typeof action === 'string') {\n throw new Error(\n `[forms] the form ${definition.name}'s action is the generated action now, not its name: ` +\n `import { contactSend } from '../server-actions.generated', then action: contactSend, for ${JSON.stringify(action)}`,\n )\n }\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 (action as (values: Record<string, unknown>) => Promise<unknown>)(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"]}
@@ -1,26 +1,24 @@
1
+ import type { FormFields } from '@rsc-kit/core/form';
1
2
  import type { FieldOverride, FormSchema } from '../resource.js';
2
3
  /**
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.
4
+ * A form with no schema of its own: defineForm({ action: contactSend }), with
5
+ * the action the build generated for the backend's Contact.send. The backend
6
+ * said what the function takes when it wrote rsc-host.json, and the build put
7
+ * that in the server entry, so the fields come from there and every check is
8
+ * the backend's.
7
9
  */
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>;
10
+ /** A backend action the build generated: import { contactSend } from '../server-actions.generated'. */
11
+ export type HostAction = (...args: never[]) => Promise<unknown>;
12
+ /** The names of the fields an action's form has, as its generated stub declares them. */
13
+ export type HostFields<Action> = Action extends (form: FormFields<infer Name>) => Promise<unknown> ? string extends Name ? string : Name : string;
14
+ /** The backend function a generated action calls, or null for any other function. */
15
+ export declare function targetOf(action: HostAction): string | null;
18
16
  /**
19
17
  * A Standard Schema that describes the backend's input and refuses nothing:
20
18
  * 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.
19
+ * function's. The browser's checks come from the same description. Read when
20
+ * the form first draws, by which time the server entry has run.
22
21
  */
23
- export declare function hostSchema(action: string): FormSchema;
22
+ export declare function hostSchema(action: HostAction): FormSchema;
24
23
  /** 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>;
24
+ export declare function requireDrawable(form: string, action: HostAction, overrides: Record<string, FieldOverride>): void;
@@ -4,10 +4,15 @@ function hostForms() {
4
4
  throw new Error('[forms] a form with an action needs the rsc-kit server entry, which says what the backend takes');
5
5
  return forms;
6
6
  }
7
+ /** The backend function a generated action calls, or null for any other function. */
8
+ export function targetOf(action) {
9
+ return hostForms().actions.get(action) ?? null;
10
+ }
7
11
  /**
8
12
  * A Standard Schema that describes the backend's input and refuses nothing:
9
13
  * 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.
14
+ * function's. The browser's checks come from the same description. Read when
15
+ * the form first draws, by which time the server entry has run.
11
16
  */
12
17
  export function hostSchema(action) {
13
18
  return {
@@ -15,27 +20,28 @@ export function hostSchema(action) {
15
20
  version: 1,
16
21
  vendor: 'rsc-kit',
17
22
  validate: (value) => ({ value: value }),
18
- jsonSchema: { input: () => hostForms().inputs[action] },
23
+ jsonSchema: { input: () => hostForms().inputs[targetOf(action) ?? ''] },
19
24
  },
20
25
  };
21
26
  }
22
27
  /** A form must be able to draw what it was given, or say so rather than draw a text box. */
23
28
  export function requireDrawable(form, action, overrides) {
24
- const input = hostForms().inputs[action];
29
+ const target = targetOf(action);
30
+ if (target === null) {
31
+ throw new Error(`[forms] the form ${form}'s action ${action.name || 'function'} is not one the build generated for the backend. ` +
32
+ "Import it from the generated server-actions module: import { contactSend } from '../server-actions.generated'.");
33
+ }
34
+ const input = hostForms().inputs[target];
25
35
  if (!input) {
26
36
  if (Object.keys(overrides).length > 0)
27
37
  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.');
38
+ throw new Error(`[forms] the form ${form} calls ${target}, and the backend does not say it takes one object with named fields. ` +
39
+ 'Take a FormRequest in Laravel or a struct in Go, or list every field in fields.');
30
40
  }
31
41
  for (const [field, schema] of Object.entries(input.properties ?? {})) {
32
42
  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.`);
43
+ throw new Error(`[forms] the form ${form} cannot tell what ${target}'s ${field} is, which a file or an open type would be. Say it in fields.`);
34
44
  }
35
45
  }
36
46
  }
37
- export function callHost(action, values) {
38
- const call = globalThis[hostForms().global];
39
- return call(action, values);
40
- }
41
47
  //# sourceMappingURL=host.js.map
@@ -1 +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
+ {"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,qFAAqF;AACrF,MAAM,UAAU,QAAQ,CAAC,MAAkB;IACzC,OAAO,SAAS,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,IAAI,CAAA;AAChD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,MAAkB;IAC3C,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,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,EAAE;SACxE;KACuB,CAAA;AAC5B,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,eAAe,CAAC,IAAY,EAAE,MAAkB,EAAE,SAAwC;IACxG,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAA;IAE/B,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CACb,oBAAoB,IAAI,aAAa,MAAM,CAAC,IAAI,IAAI,UAAU,mDAAmD;YAC/G,gHAAgH,CACnH,CAAA;IACH,CAAC;IAED,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,iFAAiF,CACpF,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","sourcesContent":["import type { FormFields } from '@rsc-kit/core/form'\nimport type { FieldOverride, FormSchema } from '../resource.js'\n\n/**\n * A form with no schema of its own: defineForm({ action: contactSend }), with\n * the action the build generated for the backend's Contact.send. The backend\n * said what the function takes when it wrote rsc-host.json, and the build put\n * that in the server entry, so the fields come from there and every check is\n * the backend's.\n */\n\n/** A backend action the build generated: import { contactSend } from '../server-actions.generated'. */\nexport type HostAction = (...args: never[]) => Promise<unknown>\n\n/** The names of the fields an action's form has, as its generated stub declares them. */\nexport type HostFields<Action> = Action extends (form: FormFields<infer Name>) => Promise<unknown>\n ? string extends Name\n ? string\n : Name\n : string\n\ninterface HostForms {\n /** Each generated action, and the backend function it calls. */\n actions: Map<unknown, string>\n /** What each backend function takes, by its name. */\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/** The backend function a generated action calls, or null for any other function. */\nexport function targetOf(action: HostAction): string | null {\n return hostForms().actions.get(action) ?? null\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. Read when\n * the form first draws, by which time the server entry has run.\n */\nexport function hostSchema(action: HostAction): 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[targetOf(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: HostAction, overrides: Record<string, FieldOverride>): void {\n const target = targetOf(action)\n\n if (target === null) {\n throw new Error(\n `[forms] the form ${form}'s action ${action.name || 'function'} is not one the build generated for the backend. ` +\n \"Import it from the generated server-actions module: import { contactSend } from '../server-actions.generated'.\",\n )\n }\n\n const input = hostForms().inputs[target]\n\n if (!input) {\n if (Object.keys(overrides).length > 0) return\n\n throw new Error(\n `[forms] the form ${form} calls ${target}, and the backend does not say it takes one object with named fields. ` +\n 'Take a FormRequest in Laravel or a struct in Go, 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 ${target}'s ${field} is, which a file or an open type would be. Say it in fields.`)\n }\n }\n}\n"]}
@@ -1,3 +1,4 @@
1
1
  export { defineForm } from './form.js';
2
2
  export type { FormDefinition, HostFormDefinition, PublicForm } from './form.js';
3
3
  export type { FormsTheme, PublicFormProps } from './theme.js';
4
+ export type { HostAction, HostFields } from './host.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, HostFormDefinition, 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'\nexport type { HostAction, HostFields } from './host.js'\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rsc-kit/press",
3
- "version": "0.30.48",
3
+ "version": "0.30.49",
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.48",
53
+ "@rsc-kit/core": "^0.30.49",
54
54
  "react": "^19.2.0",
55
55
  "react-dom": "^19.2.0",
56
56
  "tailwindcss": "^4.0.0"