@rsc-kit/press 0.30.50 → 0.30.52

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.
Files changed (61) hide show
  1. package/README.md +3 -2
  2. package/dist/admin/actions.js +6 -3
  3. package/dist/admin/actions.js.map +1 -1
  4. package/dist/admin/panel.js +28 -9
  5. package/dist/admin/panel.js.map +1 -1
  6. package/dist/admin/theme.d.ts +10 -1
  7. package/dist/admin/theme.js.map +1 -1
  8. package/dist/admin/url.d.ts +12 -0
  9. package/dist/admin/url.js +18 -0
  10. package/dist/admin/url.js.map +1 -1
  11. package/dist/conditions.d.ts +32 -0
  12. package/dist/conditions.js +25 -0
  13. package/dist/conditions.js.map +1 -0
  14. package/dist/forms/actions.d.ts +6 -5
  15. package/dist/forms/actions.js +13 -8
  16. package/dist/forms/actions.js.map +1 -1
  17. package/dist/forms/blueprint.d.ts +99 -0
  18. package/dist/forms/blueprint.js +141 -0
  19. package/dist/forms/blueprint.js.map +1 -0
  20. package/dist/forms/brand.d.ts +26 -0
  21. package/dist/forms/brand.js +9 -0
  22. package/dist/forms/brand.js.map +1 -0
  23. package/dist/forms/host.d.ts +12 -2
  24. package/dist/forms/host.js +16 -6
  25. package/dist/forms/host.js.map +1 -1
  26. package/dist/forms/index.d.ts +3 -3
  27. package/dist/forms/index.js +1 -1
  28. package/dist/forms/index.js.map +1 -1
  29. package/dist/forms/registry.d.ts +6 -1
  30. package/dist/forms/registry.js +1 -1
  31. package/dist/forms/registry.js.map +1 -1
  32. package/dist/forms/theme.d.ts +10 -7
  33. package/dist/forms/theme.js.map +1 -1
  34. package/dist/index.d.ts +3 -1
  35. package/dist/index.js +1 -0
  36. package/dist/index.js.map +1 -1
  37. package/dist/installable.js +8 -3
  38. package/dist/installable.js.map +1 -1
  39. package/dist/resource.d.ts +77 -6
  40. package/dist/resource.js +137 -18
  41. package/dist/resource.js.map +1 -1
  42. package/dist/shadcn/blueprint-form.d.ts +8 -0
  43. package/dist/shadcn/{public-form.js → blueprint-form.js} +7 -6
  44. package/dist/shadcn/blueprint-form.js.map +1 -0
  45. package/dist/shadcn/data-table.d.ts +1 -1
  46. package/dist/shadcn/data-table.js +5 -4
  47. package/dist/shadcn/data-table.js.map +1 -1
  48. package/dist/shadcn/fields.d.ts +11 -1
  49. package/dist/shadcn/fields.js +72 -10
  50. package/dist/shadcn/fields.js.map +1 -1
  51. package/dist/shadcn/index.d.ts +1 -1
  52. package/dist/shadcn/index.js +3 -3
  53. package/dist/shadcn/index.js.map +1 -1
  54. package/dist/shadcn/resource-form.js +3 -2
  55. package/dist/shadcn/resource-form.js.map +1 -1
  56. package/package.json +2 -2
  57. package/dist/forms/form.d.ts +0 -61
  58. package/dist/forms/form.js +0 -66
  59. package/dist/forms/form.js.map +0 -1
  60. package/dist/shadcn/public-form.d.ts +0 -8
  61. package/dist/shadcn/public-form.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"url.js","sourceRoot":"","sources":["../../src/admin/url.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The url schemas a panel's route files export. Standard Schemas written by\n * hand, so the panel depends on no validation library: the engine reads a\n * page's params and searchParams through `~standard`, whichever made it.\n */\nimport type { StandardSchemaV1 } from '@rsc-kit/core/standardSchema'\n\ntype Result<T> = { value: T } | { issues: { message: string; path: string[] }[] }\n\nfunction schema<T>(validate: (value: Record<string, unknown>) => Result<T>): StandardSchemaV1<Record<string, unknown>, T> {\n return {\n '~standard': {\n version: 1,\n vendor: '@rsc-kit/press',\n validate: (value) =>\n typeof value === 'object' && value !== null ? validate(value as Record<string, unknown>) : { issues: [{ message: 'Expected an object', path: [] }] },\n types: undefined as never,\n },\n }\n}\n\nconst text = (value: unknown) => (typeof value === 'string' ? value : Array.isArray(value) && typeof value[0] === 'string' ? value[0] : undefined)\n\nexport type ListSearch = {\n q?: string\n sort?: string\n dir: 'asc' | 'desc'\n page: number\n /** A filter: any other key. */\n [filter: string]: string | number | undefined\n}\n\n/** A resource's list page reads these from its url. A filter is any other key. */\nexport const listSearch = schema<ListSearch>((value) => {\n const out: ListSearch = { dir: 'asc', page: 1 }\n\n for (const [key, raw] of Object.entries(value)) {\n const given = text(raw)\n\n if (given === undefined) continue\n\n if (key === 'dir') out.dir = given === 'desc' ? 'desc' : 'asc'\n else if (key === 'page') {\n const page = Number(given)\n\n // A page that is not one is the first, rather than a 400: an old link\n // or a hand-edited url should still show the table.\n out.page = Number.isInteger(page) && page >= 1 ? page : 1\n } else out[key] = given\n }\n\n return { value: out }\n})\n\nconst params = <K extends string>(...keys: K[]) =>\n schema<Record<K, string>>((value) => {\n const missing = keys.filter((key) => text(value[key]) === undefined)\n\n if (missing.length) return { issues: missing.map((key) => ({ message: 'Required', path: [key] })) }\n\n return { value: Object.fromEntries(keys.map((key) => [key, text(value[key])!])) as Record<K, string> }\n })\n\nexport const resourceParams = params('resource')\n\nexport const recordParams = params('resource', 'id')\n\nexport type PluginsSearch = {\n /** A manifest url under review. */\n review?: string\n}\n\n/** The plugins page reads the manifest it is reviewing from its url. */\nexport const pluginsSearch = schema<PluginsSearch>((value) => {\n const review = text(value.review)\n\n return { value: review ? { review } : {} }\n})\n"]}
1
+ {"version":3,"file":"url.js","sourceRoot":"","sources":["../../src/admin/url.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The url schemas a panel's route files export. Standard Schemas written by\n * hand, so the panel depends on no validation library: the engine reads a\n * page's params and searchParams through `~standard`, whichever made it.\n */\nimport type { StandardSchemaV1 } from '@rsc-kit/core/standardSchema'\n\ntype Result<T> = { value: T } | { issues: { message: string; path: string[] }[] }\n\nfunction schema<T>(validate: (value: Record<string, unknown>) => Result<T>): StandardSchemaV1<Record<string, unknown>, T> {\n return {\n '~standard': {\n version: 1,\n vendor: '@rsc-kit/press',\n validate: (value) =>\n typeof value === 'object' && value !== null ? validate(value as Record<string, unknown>) : { issues: [{ message: 'Expected an object', path: [] }] },\n types: undefined as never,\n },\n }\n}\n\nconst text = (value: unknown) => (typeof value === 'string' ? value : Array.isArray(value) && typeof value[0] === 'string' ? value[0] : undefined)\n\nexport type ListSearch = {\n q?: string\n sort?: string\n dir: 'asc' | 'desc'\n page: number\n /** Where a list paged by cursor is. */\n cursor?: string\n /** A filter: any other key. */\n [filter: string]: string | number | undefined\n}\n\n/** A resource's list page reads these from its url. A filter is any other key. */\nexport const listSearch = schema<ListSearch>((value) => {\n const out: ListSearch = { dir: 'asc', page: 1 }\n\n for (const [key, raw] of Object.entries(value)) {\n const given = text(raw)\n\n if (given === undefined) continue\n\n if (key === 'dir') out.dir = given === 'desc' ? 'desc' : 'asc'\n else if (key === 'page') {\n const page = Number(given)\n\n // A page that is not one is the first, rather than a 400: an old link\n // or a hand-edited url should still show the table.\n out.page = Number.isInteger(page) && page >= 1 ? page : 1\n } else out[key] = given\n }\n\n return { value: out }\n})\n\n/**\n * The list's url at a cursor, keeping the search, sort and filters the visitor\n * is on - a cursor is only good for the query that handed it out.\n */\nexport function cursorHref(\n base: string,\n query: { q?: string; sort?: string; dir: 'asc' | 'desc'; filters: Record<string, string> },\n cursor: string,\n): string {\n const search = new URLSearchParams()\n\n if (query.q) search.set('q', query.q)\n if (query.sort) search.set('sort', query.sort)\n if (query.sort && query.dir === 'desc') search.set('dir', 'desc')\n for (const [key, value] of Object.entries(query.filters)) if (value) search.set(key, value)\n search.set('cursor', cursor)\n\n return `${base}?${search}`\n}\n\nconst params = <K extends string>(...keys: K[]) =>\n schema<Record<K, string>>((value) => {\n const missing = keys.filter((key) => text(value[key]) === undefined)\n\n if (missing.length) return { issues: missing.map((key) => ({ message: 'Required', path: [key] })) }\n\n return { value: Object.fromEntries(keys.map((key) => [key, text(value[key])!])) as Record<K, string> }\n })\n\nexport const resourceParams = params('resource')\n\nexport const recordParams = params('resource', 'id')\n\nexport type PluginsSearch = {\n /** A manifest url under review. */\n review?: string\n}\n\n/** The plugins page reads the manifest it is reviewing from its url. */\nexport const pluginsSearch = schema<PluginsSearch>((value) => {\n const review = text(value.review)\n\n return { value: review ? { review } : {} }\n})\n"]}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * What makes a field depend on another, as plain data: the browser applies it
3
+ * while the visitor types, and the server applies it again to what was sent,
4
+ * so the two cannot disagree. No functions - a field crosses to the browser.
5
+ */
6
+ type Scalar = string | number | boolean;
7
+ /**
8
+ * When a field is shown: `{ when: 'type', is: 'business' }`, `is` one value
9
+ * or several. `not` turns it round. With neither, the field shows once `when`
10
+ * is filled in - or switched on.
11
+ */
12
+ export interface Visibility {
13
+ when: string;
14
+ is?: Scalar | Scalar[];
15
+ not?: Scalar | Scalar[];
16
+ }
17
+ /**
18
+ * A value made from another field's: `{ field: 'title' }` fills a slug while
19
+ * the title is typed, until someone edits the slug by hand. Left empty, the
20
+ * server fills it the same way.
21
+ */
22
+ export interface Derivation {
23
+ field: string;
24
+ /** What is made of it. A slug: lowercase letters, digits and dashes. */
25
+ as?: 'slug';
26
+ }
27
+ /** A field's value as a condition reads it: a toggle is on or off, anything else its text. */
28
+ export type Read = (name: string) => string | boolean;
29
+ export declare function shown(visible: Visibility, read: Read): boolean;
30
+ /** "Café & Bar - Downtown" is "cafe-bar-downtown". */
31
+ export declare function slugify(text: string): string;
32
+ export {};
@@ -0,0 +1,25 @@
1
+ /**
2
+ * What makes a field depend on another, as plain data: the browser applies it
3
+ * while the visitor types, and the server applies it again to what was sent,
4
+ * so the two cannot disagree. No functions - a field crosses to the browser.
5
+ */
6
+ export function shown(visible, read) {
7
+ const actual = read(visible.when);
8
+ const text = typeof actual === 'boolean' ? String(actual) : actual;
9
+ const listed = (values) => [].concat(values).map(String).includes(text);
10
+ if (visible.is !== undefined)
11
+ return listed(visible.is);
12
+ if (visible.not !== undefined)
13
+ return !listed(visible.not);
14
+ return actual === true || (typeof actual === 'string' && actual !== '');
15
+ }
16
+ /** "Café & Bar - Downtown" is "cafe-bar-downtown". */
17
+ export function slugify(text) {
18
+ return text
19
+ .normalize('NFKD')
20
+ .replace(/[\u0300-\u036f]/g, '')
21
+ .toLowerCase()
22
+ .replace(/[^a-z0-9]+/g, '-')
23
+ .replace(/^-+|-+$/g, '');
24
+ }
25
+ //# sourceMappingURL=conditions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conditions.js","sourceRoot":"","sources":["../src/conditions.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA6BH,MAAM,UAAU,KAAK,CAAC,OAAmB,EAAE,IAAU;IACnD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;IACjC,MAAM,IAAI,GAAG,OAAO,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAA;IAClE,MAAM,MAAM,GAAG,CAAC,MAAyB,EAAE,EAAE,CAAE,EAAe,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;IAExG,IAAI,OAAO,CAAC,EAAE,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IACvD,IAAI,OAAO,CAAC,GAAG,KAAK,SAAS;QAAE,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IAE1D,OAAO,MAAM,KAAK,IAAI,IAAI,CAAC,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,EAAE,CAAC,CAAA;AACzE,CAAC;AAED,sDAAsD;AACtD,MAAM,UAAU,OAAO,CAAC,IAAY;IAClC,OAAO,IAAI;SACR,SAAS,CAAC,MAAM,CAAC;SACjB,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC;SAC/B,WAAW,EAAE;SACb,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;SAC3B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAA;AAC5B,CAAC","sourcesContent":["/**\n * What makes a field depend on another, as plain data: the browser applies it\n * while the visitor types, and the server applies it again to what was sent,\n * so the two cannot disagree. No functions - a field crosses to the browser.\n */\n\ntype Scalar = string | number | boolean\n\n/**\n * When a field is shown: `{ when: 'type', is: 'business' }`, `is` one value\n * or several. `not` turns it round. With neither, the field shows once `when`\n * is filled in - or switched on.\n */\nexport interface Visibility {\n when: string\n is?: Scalar | Scalar[]\n not?: Scalar | Scalar[]\n}\n\n/**\n * A value made from another field's: `{ field: 'title' }` fills a slug while\n * the title is typed, until someone edits the slug by hand. Left empty, the\n * server fills it the same way.\n */\nexport interface Derivation {\n field: string\n /** What is made of it. A slug: lowercase letters, digits and dashes. */\n as?: 'slug'\n}\n\n/** A field's value as a condition reads it: a toggle is on or off, anything else its text. */\nexport type Read = (name: string) => string | boolean\n\nexport function shown(visible: Visibility, read: Read): boolean {\n const actual = read(visible.when)\n const text = typeof actual === 'boolean' ? String(actual) : actual\n const listed = (values: Scalar | Scalar[]) => ([] as Scalar[]).concat(values).map(String).includes(text)\n\n if (visible.is !== undefined) return listed(visible.is)\n if (visible.not !== undefined) return !listed(visible.not)\n\n return actual === true || (typeof actual === 'string' && actual !== '')\n}\n\n/** \"Café & Bar - Downtown\" is \"cafe-bar-downtown\". */\nexport function slugify(text: string): string {\n return text\n .normalize('NFKD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n}\n\n"]}
@@ -1,7 +1,8 @@
1
1
  /**
2
- * One submit for every form, bound by its component:
3
- * submitForm.bind(null, 'contact'). The form's own schema is the check, on
4
- * the server, every time - the browser's checks are a courtesy to the
5
- * visitor, and anyone can post without them.
2
+ * One submit for every blueprint's form, bound by its component:
3
+ * submitForm.bind(null, 'contact', null) to create, or with the record's key
4
+ * to update it. The blueprint's own schema - or the backend - is the check,
5
+ * on the server, every time: the browser's checks are a courtesy to the
6
+ * visitor, and anyone can post without them, with any key.
6
7
  */
7
- export declare function submitForm(name: string, form: FormData): Promise<undefined>;
8
+ export declare function submitForm(name: string, key: string | null, form: FormData): Promise<undefined>;
@@ -5,21 +5,26 @@ import { parse } from '../resource.js';
5
5
  import { formNamed } from './registry.js';
6
6
  import { HONEYPOT } from './honeypot.js';
7
7
  /**
8
- * One submit for every form, bound by its component:
9
- * submitForm.bind(null, 'contact'). The form's own schema is the check, on
10
- * the server, every time - the browser's checks are a courtesy to the
11
- * visitor, and anyone can post without them.
8
+ * One submit for every blueprint's form, bound by its component:
9
+ * submitForm.bind(null, 'contact', null) to create, or with the record's key
10
+ * to update it. The blueprint's own schema - or the backend - is the check,
11
+ * on the server, every time: the browser's checks are a courtesy to the
12
+ * visitor, and anyone can post without them, with any key.
12
13
  */
13
- export async function submitForm(name, form) {
14
+ export async function submitForm(name, key, form) {
14
15
  const definition = formNamed(name);
15
16
  // Filled in, so not a person. Answered as a success, so a bot learns
16
17
  // nothing from the reply, and dropped.
17
- if (String(form.get(HONEYPOT) ?? '') !== '')
18
+ if (definition.honeypot && String(form.get(HONEYPOT) ?? '') !== '')
18
19
  return;
19
- const parsed = await parse(definition, form);
20
+ const save = key === null ? definition.create : definition.update;
21
+ if (!save)
22
+ throw new Error(`[forms] the blueprint ${name} has no ${key === null ? 'create' : 'update'}`);
23
+ const target = key === null ? definition : { schema: definition.editSchema, fields: definition.editFields };
24
+ const parsed = await parse(target, form);
20
25
  if (!parsed.ok)
21
26
  return fieldErrors(parsed.errors);
22
- await definition.handle(parsed.values);
27
+ await save(parsed.values, key);
23
28
  if (definition.redirect)
24
29
  redirect(definition.redirect);
25
30
  }
@@ -1 +1 @@
1
- {"version":3,"file":"actions.js","sourceRoot":"","sources":["../../src/forms/actions.ts"],"names":[],"mappings":"AAAA,YAAY,CAAA;AAEZ,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAA;AACjD,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAExC;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAAY,EAAE,IAAc;IAC3D,MAAM,UAAU,GAAG,SAAS,CAAC,IAAI,CAAC,CAAA;IAElC,qEAAqE;IACrE,uCAAuC;IACvC,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE;QAAE,OAAM;IAEnD,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,UAAU,EAAE,IAAI,CAAC,CAAA;IAE5C,IAAI,CAAC,MAAM,CAAC,EAAE;QAAE,OAAO,WAAW,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;IAEjD,MAAM,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;IAEtC,IAAI,UAAU,CAAC,QAAQ;QAAE,QAAQ,CAAC,UAAU,CAAC,QAAiB,CAAC,CAAA;AACjE,CAAC","sourcesContent":["'use server'\n\nimport { fieldErrors } from '@rsc-kit/core/action'\nimport { redirect } from '@rsc-kit/core/redirect'\nimport { parse } from '../resource.js'\nimport { formNamed } from './registry.js'\nimport { HONEYPOT } from './honeypot.js'\n\n/**\n * One submit for every form, bound by its component:\n * submitForm.bind(null, 'contact'). The form's own schema is the check, on\n * the server, every time - the browser's checks are a courtesy to the\n * visitor, and anyone can post without them.\n */\nexport async function submitForm(name: string, form: FormData) {\n const definition = formNamed(name)\n\n // Filled in, so not a person. Answered as a success, so a bot learns\n // nothing from the reply, and dropped.\n if (String(form.get(HONEYPOT) ?? '') !== '') return\n\n const parsed = await parse(definition, form)\n\n if (!parsed.ok) return fieldErrors(parsed.errors)\n\n await definition.handle(parsed.values)\n\n if (definition.redirect) redirect(definition.redirect as never)\n}\n"]}
1
+ {"version":3,"file":"actions.js","sourceRoot":"","sources":["../../src/forms/actions.ts"],"names":[],"mappings":"AAAA,YAAY,CAAA;AAEZ,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAA;AACjD,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAExC;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAAY,EAAE,GAAkB,EAAE,IAAc;IAC/E,MAAM,UAAU,GAAG,SAAS,CAAC,IAAI,CAAC,CAAA;IAElC,qEAAqE;IACrE,uCAAuC;IACvC,IAAI,UAAU,CAAC,QAAQ,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE;QAAE,OAAM;IAE1E,MAAM,IAAI,GAAG,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAA;IAEjE,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,yBAAyB,IAAI,WAAW,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAA;IAExG,MAAM,MAAM,GAAG,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,CAAC,UAAU,EAAE,CAAA;IAC3G,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;IAExC,IAAI,CAAC,MAAM,CAAC,EAAE;QAAE,OAAO,WAAW,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;IAEjD,MAAM,IAAI,CAAC,MAAM,CAAC,MAAiC,EAAE,GAAI,CAAC,CAAA;IAE1D,IAAI,UAAU,CAAC,QAAQ;QAAE,QAAQ,CAAC,UAAU,CAAC,QAAiB,CAAC,CAAA;AACjE,CAAC","sourcesContent":["'use server'\n\nimport { fieldErrors } from '@rsc-kit/core/action'\nimport { redirect } from '@rsc-kit/core/redirect'\nimport { parse } from '../resource.js'\nimport { formNamed } from './registry.js'\nimport { HONEYPOT } from './honeypot.js'\n\n/**\n * One submit for every blueprint's form, bound by its component:\n * submitForm.bind(null, 'contact', null) to create, or with the record's key\n * to update it. The blueprint's own schema - or the backend - is the check,\n * on the server, every time: the browser's checks are a courtesy to the\n * visitor, and anyone can post without them, with any key.\n */\nexport async function submitForm(name: string, key: string | null, form: FormData) {\n const definition = formNamed(name)\n\n // Filled in, so not a person. Answered as a success, so a bot learns\n // nothing from the reply, and dropped.\n if (definition.honeypot && String(form.get(HONEYPOT) ?? '') !== '') return\n\n const save = key === null ? definition.create : definition.update\n\n if (!save) throw new Error(`[forms] the blueprint ${name} has no ${key === null ? 'create' : 'update'}`)\n\n const target = key === null ? definition : { schema: definition.editSchema, fields: definition.editFields }\n const parsed = await parse(target, form)\n\n if (!parsed.ok) return fieldErrors(parsed.errors)\n\n await save(parsed.values as Record<string, unknown>, key!)\n\n if (definition.redirect) redirect(definition.redirect as never)\n}\n"]}
@@ -0,0 +1,99 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { Field, FieldOverride, FormSchema, ValuesOf } from '../resource.js';
3
+ import type { BlueprintParts } from './brand.js';
4
+ import type { HostAction, HostFields } from './host.js';
5
+ import type { FormsTheme } from './theme.js';
6
+ interface BlueprintOptions {
7
+ /** Unique in the app: the form is found by it when it is sent. Lowercase letters, digits and dashes. */
8
+ name: string;
9
+ /** What draws `<blueprint.Form />`: import { shadcn } from '@rsc-kit/press/shadcn'. A blueprint only a panel draws needs none. */
10
+ theme?: FormsTheme;
11
+ /** The submit button's words. Send to create and Save to update, unless said. */
12
+ submit?: string;
13
+ /** What the visitor reads in place of the form once it is sent. */
14
+ done?: string;
15
+ /** Where the visitor goes once it is sent, instead of `done`. */
16
+ redirect?: string;
17
+ /**
18
+ * A field people never see and bots fill in, for a form anyone may send.
19
+ * On unless said; a form only signed-in people reach can turn it off.
20
+ */
21
+ honeypot?: boolean;
22
+ }
23
+ export interface SchemaBlueprintDefinition<Schema extends FormSchema> extends BlueprintOptions {
24
+ /** Any Standard Schema of an object: z.object(), v.object(), type({...}). It checks every submission, on the server. */
25
+ form: Schema;
26
+ /** Labels, descriptions and controls the schema cannot say - or every field, for a schema that does not describe itself as JSON Schema. */
27
+ fields?: Partial<Record<keyof ValuesOf<Schema> & string, FieldOverride>>;
28
+ /**
29
+ * What a new submission that passed the schema does: store it, send it,
30
+ * call a backend. Throw an ActionValidationError - or let a backend's 422
31
+ * through - and its messages land under the fields.
32
+ */
33
+ handle?: (values: ValuesOf<Schema>) => unknown;
34
+ /** What a submission of `<blueprint.Form id={...} />` does, with that key. */
35
+ update?: (values: ValuesOf<Schema>, key: string) => unknown;
36
+ }
37
+ export interface HostBlueprintDefinition<Create extends HostAction, Update extends HostAction> extends BlueprintOptions {
38
+ /**
39
+ * The backend actions it saves with, as the build generated them:
40
+ * `import { productsStore, productsUpdate } from '../server-actions.generated'`.
41
+ * The create form is drawn from what `create` takes and the edit form from
42
+ * what `update` takes, and they do all the checking: a Laravel FormRequest,
43
+ * a Go struct. `update` is called with the key after the values.
44
+ */
45
+ actions: {
46
+ create?: Create;
47
+ update?: Update;
48
+ };
49
+ /** Labels, descriptions and controls the backend's types cannot say. */
50
+ fields?: Partial<Record<HostFields<Create> | HostFields<Update>, FieldOverride>>;
51
+ }
52
+ export interface BlueprintFormProps {
53
+ /** The record's current values, for an edit. Only the blueprint's fields reach the browser. */
54
+ values?: object;
55
+ /** The record's key: the form then updates it rather than creating one. */
56
+ id?: string | number;
57
+ }
58
+ /**
59
+ * A form's fields and what saving it does, defined once and drawn anywhere:
60
+ * as `<product.Form />` in a page, and as a panel's create and edit pages
61
+ * with `defineResource({ form: product })`.
62
+ */
63
+ export interface Blueprint extends BlueprintParts {
64
+ /**
65
+ * The form, in any page. Without `id` it creates; with it, it updates that
66
+ * record, starting from `values`:
67
+ *
68
+ * <profile.Form id={user.id} values={user} />
69
+ *
70
+ * The key comes back from the browser like any field, so the update must
71
+ * check it may touch that record: a FormRequest's authorize(), a Go
72
+ * handler's own check.
73
+ */
74
+ Form: (props: BlueprintFormProps) => ReactNode;
75
+ }
76
+ /**
77
+ * A form defined once - its fields, its checks, what saving it does - and
78
+ * drawn wherever it is needed: a page, a panel, or both. Either from a
79
+ * Standard Schema with `handle` and `update`, or from the backend's own
80
+ * actions, which draw the fields and do the checking:
81
+ *
82
+ * export const product = defineBlueprint({
83
+ * name: 'product',
84
+ * theme: shadcn,
85
+ * actions: { create: productsStore, update: productsUpdate },
86
+ * })
87
+ *
88
+ * <product.Form /> // creates
89
+ * <product.Form id={p.id} values={p} /> // updates
90
+ * defineResource<Product>()({ ..., form: product })
91
+ */
92
+ export declare function defineBlueprint<Schema extends FormSchema>(definition: SchemaBlueprintDefinition<Schema>): Blueprint;
93
+ export declare function defineBlueprint<Create extends HostAction, Update extends HostAction>(definition: HostBlueprintDefinition<Create, Update>): Blueprint;
94
+ /**
95
+ * Only the form's own fields go to the browser: a record handed over whole
96
+ * would carry every column it has - a password's hash, a token - into the page.
97
+ */
98
+ export declare function valuesFor(fields: Field[], values: object | undefined): Record<string, unknown>;
99
+ export {};
@@ -0,0 +1,141 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { fieldsOf } from '../resource.js';
3
+ import { submitForm } from './actions.js';
4
+ import { BLUEPRINT } from './brand.js';
5
+ import { hostSchema, requireDrawable } from './host.js';
6
+ import { HONEYPOT } from './honeypot.js';
7
+ import { registerForm } from './registry.js';
8
+ export function defineBlueprint(definition) {
9
+ const { name } = definition;
10
+ if (!/^[a-z0-9-]+$/.test(name)) {
11
+ throw new Error(`[forms] a blueprint's name is lowercase letters, digits and dashes: ${JSON.stringify(name)}`);
12
+ }
13
+ if (definition.done !== undefined && definition.redirect !== undefined) {
14
+ throw new Error(`[forms] the blueprint ${name} has both done and redirect; a sent form shows one or goes to the other`);
15
+ }
16
+ const { actions } = definition;
17
+ if ((actions === undefined) === (definition.form === undefined)) {
18
+ throw new Error(`[forms] the blueprint ${name} needs a form schema, or the backend's actions, and not both`);
19
+ }
20
+ if (actions !== undefined) {
21
+ const given = [actions.create, actions.update].filter((action) => action !== undefined);
22
+ if (given.length === 0 || given.some((action) => typeof action !== 'function')) {
23
+ throw new Error(`[forms] the blueprint ${name}'s actions are { create, update }, either or both, each the action the build generated: ` +
24
+ "import { productsStore, productsUpdate } from '../server-actions.generated'");
25
+ }
26
+ }
27
+ const overrides = (definition.fields ?? {});
28
+ const host = actions;
29
+ const schemaDefinition = definition;
30
+ // A backend's create may be missing - a blueprint that only edits - so the
31
+ // edit form's description stands in for the create's schema, never used.
32
+ const schema = host ? hostSchema((host.create ?? host.update)) : definition.form;
33
+ const editSchema = host ? hostSchema((host.update ?? host.create)) : schema;
34
+ const create = host
35
+ ? host.create
36
+ ? (values) => host.create(values)
37
+ : null
38
+ : schemaDefinition.handle
39
+ ? async (values) => schemaDefinition.handle(values)
40
+ : null;
41
+ const update = host
42
+ ? host.update
43
+ ? (values, key) => host.update(values, key)
44
+ : null
45
+ : schemaDefinition.update
46
+ ? async (values, key) => schemaDefinition.update(values, key)
47
+ : null;
48
+ const drawn = {};
49
+ const trapped = (fields) => {
50
+ if (definition.honeypot !== false && fields.some((field) => field.name === HONEYPOT)) {
51
+ throw new Error(`[forms] the blueprint ${name} has a field named ${HONEYPOT}, which is the name its bot trap uses; rename it, or honeypot: false`);
52
+ }
53
+ return fields;
54
+ };
55
+ function createFields() {
56
+ if (drawn.create)
57
+ return drawn.create;
58
+ if (host) {
59
+ if (!host.create)
60
+ throw new Error(`[forms] the blueprint ${name} has no create action, so it cannot draw a form that creates; pass an id to edit`);
61
+ requireDrawable({ name: `[forms] the blueprint ${name}'s create`, key: false }, host.create, overrides);
62
+ }
63
+ return (drawn.create = trapped(fieldsOf(schema, overrides)));
64
+ }
65
+ function editFields() {
66
+ if (drawn.edit)
67
+ return drawn.edit;
68
+ if (!host)
69
+ return (drawn.edit = createFields());
70
+ if (!host.update)
71
+ throw new Error(`[forms] the blueprint ${name} has no update action, so it cannot draw a form that edits`);
72
+ requireDrawable({ name: `[forms] the blueprint ${name}'s update`, key: true }, host.update, overrides);
73
+ return (drawn.edit = trapped(fieldsOf(editSchema, overrides)));
74
+ }
75
+ // A backend's types arrive with the server entry, which runs after this
76
+ // module: those fields wait for the first render. A schema's fail here.
77
+ if (!host)
78
+ createFields();
79
+ const relations = Object.values(overrides).flatMap((override) => (override?.relation ? [override.relation] : []));
80
+ if (!host)
81
+ for (const field of createFields())
82
+ if (field.relation && !relations.includes(field.relation))
83
+ relations.push(field.relation);
84
+ registerForm({
85
+ name,
86
+ schema,
87
+ editSchema,
88
+ get fields() {
89
+ return createFields();
90
+ },
91
+ get editFields() {
92
+ return editFields();
93
+ },
94
+ create,
95
+ update,
96
+ redirect: definition.redirect ?? null,
97
+ honeypot: definition.honeypot !== false,
98
+ });
99
+ const { theme } = definition;
100
+ function Form({ values, id }) {
101
+ if (!theme) {
102
+ throw new Error(`[forms] the blueprint ${name} has no theme to draw <${name}.Form /> with: theme: shadcn, from '@rsc-kit/press/shadcn'`);
103
+ }
104
+ const editing = id !== undefined && id !== null;
105
+ if (editing && !update)
106
+ throw new Error(`[forms] <${name}.Form id={...} /> edits, and the blueprint ${name} has no update to save it with`);
107
+ if (!editing && !create)
108
+ throw new Error(`[forms] <${name}.Form /> creates, and the blueprint ${name} has no create to save it with; pass the record's id to edit`);
109
+ const fields = editing ? editFields() : createFields();
110
+ const Draw = theme.BlueprintForm;
111
+ const done = definition.redirect === undefined ? (definition.done ?? (editing ? 'Saved.' : 'Thank you. It was sent.')) : undefined;
112
+ return (_jsx(Draw, { action: submitForm.bind(null, name, editing ? String(id) : null), fields: fields, values: valuesFor(fields, values), submit: definition.submit ?? (editing ? 'Save' : 'Send'), ...(done === undefined ? {} : { done }), ...(definition.honeypot === false ? {} : { honeypot: HONEYPOT }) }));
113
+ }
114
+ return {
115
+ [BLUEPRINT]: true,
116
+ name,
117
+ schema,
118
+ editSchema,
119
+ get fields() {
120
+ return createFields();
121
+ },
122
+ get editFields() {
123
+ return editFields();
124
+ },
125
+ relations,
126
+ create,
127
+ update,
128
+ Form,
129
+ };
130
+ }
131
+ /**
132
+ * Only the form's own fields go to the browser: a record handed over whole
133
+ * would carry every column it has - a password's hash, a token - into the page.
134
+ */
135
+ export function valuesFor(fields, values) {
136
+ if (!values)
137
+ return {};
138
+ const record = values;
139
+ return Object.fromEntries(fields.flatMap((field) => (field.name in record ? [[field.name, record[field.name]]] : [])));
140
+ }
141
+ //# sourceMappingURL=blueprint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blueprint.js","sourceRoot":"","sources":["../../src/forms/blueprint.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 { BLUEPRINT } from './brand.js'\nimport type { BlueprintParts } from './brand.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 BlueprintOptions {\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 `<blueprint.Form />`: import { shadcn } from '@rsc-kit/press/shadcn'. A blueprint only a panel draws needs none. */\n theme?: FormsTheme\n /** The submit button's words. Send to create and Save to update, unless said. */\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 * A field people never see and bots fill in, for a form anyone may send.\n * On unless said; a form only signed-in people reach can turn it off.\n */\n honeypot?: boolean\n}\n\nexport interface SchemaBlueprintDefinition<Schema extends FormSchema> extends BlueprintOptions {\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 new submission that passed the schema does: store it, send it,\n * call a backend. Throw an ActionValidationError - or let a backend's 422\n * through - and its messages land under the fields.\n */\n handle?: (values: ValuesOf<Schema>) => unknown\n /** What a submission of `<blueprint.Form id={...} />` does, with that key. */\n update?: (values: ValuesOf<Schema>, key: string) => unknown\n}\n\nexport interface HostBlueprintDefinition<Create extends HostAction, Update extends HostAction> extends BlueprintOptions {\n /**\n * The backend actions it saves with, as the build generated them:\n * `import { productsStore, productsUpdate } from '../server-actions.generated'`.\n * The create form is drawn from what `create` takes and the edit form from\n * what `update` takes, and they do all the checking: a Laravel FormRequest,\n * a Go struct. `update` is called with the key after the values.\n */\n actions: { create?: Create; update?: Update }\n /** Labels, descriptions and controls the backend's types cannot say. */\n fields?: Partial<Record<HostFields<Create> | HostFields<Update>, FieldOverride>>\n}\n\nexport interface BlueprintFormProps {\n /** The record's current values, for an edit. Only the blueprint's fields reach the browser. */\n values?: object\n /** The record's key: the form then updates it rather than creating one. */\n id?: string | number\n}\n\n/**\n * A form's fields and what saving it does, defined once and drawn anywhere:\n * as `<product.Form />` in a page, and as a panel's create and edit pages\n * with `defineResource({ form: product })`.\n */\nexport interface Blueprint extends BlueprintParts {\n /**\n * The form, in any page. Without `id` it creates; with it, it updates that\n * record, starting from `values`:\n *\n * <profile.Form id={user.id} values={user} />\n *\n * The key comes back from the browser like any field, so the update must\n * check it may touch that record: a FormRequest's authorize(), a Go\n * handler's own check.\n */\n Form: (props: BlueprintFormProps) => ReactNode\n}\n\n/**\n * A form defined once - its fields, its checks, what saving it does - and\n * drawn wherever it is needed: a page, a panel, or both. Either from a\n * Standard Schema with `handle` and `update`, or from the backend's own\n * actions, which draw the fields and do the checking:\n *\n * export const product = defineBlueprint({\n * name: 'product',\n * theme: shadcn,\n * actions: { create: productsStore, update: productsUpdate },\n * })\n *\n * <product.Form /> // creates\n * <product.Form id={p.id} values={p} /> // updates\n * defineResource<Product>()({ ..., form: product })\n */\nexport function defineBlueprint<Schema extends FormSchema>(definition: SchemaBlueprintDefinition<Schema>): Blueprint\nexport function defineBlueprint<Create extends HostAction, Update extends HostAction>(definition: HostBlueprintDefinition<Create, Update>): Blueprint\nexport function defineBlueprint(\n definition: (SchemaBlueprintDefinition<FormSchema> | HostBlueprintDefinition<HostAction, HostAction>) & {\n form?: FormSchema\n actions?: { create?: unknown; update?: unknown }\n },\n): Blueprint {\n const { name } = definition\n\n if (!/^[a-z0-9-]+$/.test(name)) {\n throw new Error(`[forms] a blueprint's name is lowercase letters, digits and dashes: ${JSON.stringify(name)}`)\n }\n\n if (definition.done !== undefined && definition.redirect !== undefined) {\n throw new Error(`[forms] the blueprint ${name} has both done and redirect; a sent form shows one or goes to the other`)\n }\n\n const { actions } = definition\n\n if ((actions === undefined) === (definition.form === undefined)) {\n throw new Error(`[forms] the blueprint ${name} needs a form schema, or the backend's actions, and not both`)\n }\n\n if (actions !== undefined) {\n const given = [actions.create, actions.update].filter((action) => action !== undefined)\n\n if (given.length === 0 || given.some((action) => typeof action !== 'function')) {\n throw new Error(\n `[forms] the blueprint ${name}'s actions are { create, update }, either or both, each the action the build generated: ` +\n \"import { productsStore, productsUpdate } from '../server-actions.generated'\",\n )\n }\n }\n\n const overrides = (definition.fields ?? {}) as Record<string, FieldOverride | undefined>\n const host = actions as { create?: HostAction; update?: HostAction } | undefined\n const schemaDefinition = definition as SchemaBlueprintDefinition<FormSchema>\n\n // A backend's create may be missing - a blueprint that only edits - so the\n // edit form's description stands in for the create's schema, never used.\n const schema = host ? hostSchema((host.create ?? host.update)!) : definition.form!\n const editSchema = host ? hostSchema((host.update ?? host.create)!) : schema\n\n const create: BlueprintParts['create'] = host\n ? host.create\n ? (values) => (host.create as (values: unknown) => Promise<unknown>)(values)\n : null\n : schemaDefinition.handle\n ? async (values) => schemaDefinition.handle!(values as never)\n : null\n const update: BlueprintParts['update'] = host\n ? host.update\n ? (values, key) => (host.update as (values: unknown, key: string) => Promise<unknown>)(values, key)\n : null\n : schemaDefinition.update\n ? async (values, key) => schemaDefinition.update!(values as never, key)\n : null\n\n const drawn: { create?: Field[]; edit?: Field[] } = {}\n\n const trapped = (fields: Field[]): Field[] => {\n if (definition.honeypot !== false && fields.some((field) => field.name === HONEYPOT)) {\n throw new Error(`[forms] the blueprint ${name} has a field named ${HONEYPOT}, which is the name its bot trap uses; rename it, or honeypot: false`)\n }\n\n return fields\n }\n\n function createFields(): Field[] {\n if (drawn.create) return drawn.create\n\n if (host) {\n if (!host.create) throw new Error(`[forms] the blueprint ${name} has no create action, so it cannot draw a form that creates; pass an id to edit`)\n\n requireDrawable({ name: `[forms] the blueprint ${name}'s create`, key: false }, host.create, overrides)\n }\n\n return (drawn.create = trapped(fieldsOf(schema, overrides)))\n }\n\n function editFields(): Field[] {\n if (drawn.edit) return drawn.edit\n if (!host) return (drawn.edit = createFields())\n\n if (!host.update) throw new Error(`[forms] the blueprint ${name} has no update action, so it cannot draw a form that edits`)\n\n requireDrawable({ name: `[forms] the blueprint ${name}'s update`, key: true }, host.update, overrides)\n\n return (drawn.edit = trapped(fieldsOf(editSchema, overrides)))\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 (!host) createFields()\n\n const relations = Object.values(overrides).flatMap((override) => (override?.relation ? [override.relation] : []))\n\n if (!host) for (const field of createFields()) if (field.relation && !relations.includes(field.relation)) relations.push(field.relation)\n\n registerForm({\n name,\n schema,\n editSchema,\n get fields() {\n return createFields()\n },\n get editFields() {\n return editFields()\n },\n create,\n update,\n redirect: definition.redirect ?? null,\n honeypot: definition.honeypot !== false,\n })\n\n const { theme } = definition\n\n function Form({ values, id }: BlueprintFormProps) {\n if (!theme) {\n throw new Error(`[forms] the blueprint ${name} has no theme to draw <${name}.Form /> with: theme: shadcn, from '@rsc-kit/press/shadcn'`)\n }\n\n const editing = id !== undefined && id !== null\n\n if (editing && !update) throw new Error(`[forms] <${name}.Form id={...} /> edits, and the blueprint ${name} has no update to save it with`)\n if (!editing && !create) throw new Error(`[forms] <${name}.Form /> creates, and the blueprint ${name} has no create to save it with; pass the record's id to edit`)\n\n const fields = editing ? editFields() : createFields()\n const Draw = theme.BlueprintForm\n const done = definition.redirect === undefined ? (definition.done ?? (editing ? 'Saved.' : 'Thank you. It was sent.')) : undefined\n\n return (\n <Draw\n action={submitForm.bind(null, name, editing ? String(id) : null)}\n fields={fields}\n values={valuesFor(fields, values)}\n submit={definition.submit ?? (editing ? 'Save' : 'Send')}\n {...(done === undefined ? {} : { done })}\n {...(definition.honeypot === false ? {} : { honeypot: HONEYPOT })}\n />\n )\n }\n\n return {\n [BLUEPRINT]: true,\n name,\n schema,\n editSchema,\n get fields() {\n return createFields()\n },\n get editFields() {\n return editFields()\n },\n relations,\n create,\n update,\n Form,\n }\n}\n\n/**\n * Only the form's own fields go to the browser: a record handed over whole\n * would carry every column it has - a password's hash, a token - into the page.\n */\nexport function valuesFor(fields: Field[], values: object | undefined): Record<string, unknown> {\n if (!values) return {}\n\n const record = values as Record<string, unknown>\n\n return Object.fromEntries(fields.flatMap((field) => (field.name in record ? [[field.name, record[field.name]]] : [])))\n}\n"]}
@@ -0,0 +1,26 @@
1
+ import type { Field, FormSchema } from '../resource.js';
2
+ /**
3
+ * What marks a blueprint, so a resource handed one in `form` can tell it from
4
+ * a Standard Schema without importing the module that draws it.
5
+ */
6
+ export declare const BLUEPRINT: unique symbol;
7
+ /** What a resource reads from a blueprint: the fields and the saves, nothing it draws. */
8
+ export interface BlueprintParts {
9
+ readonly [BLUEPRINT]: true;
10
+ name: string;
11
+ /** What a create is checked against. */
12
+ schema: FormSchema;
13
+ /** What an update is checked against: a backend's update may take other fields than its create. */
14
+ editSchema: FormSchema;
15
+ /** The create form's fields. Throws for a blueprint that cannot create. */
16
+ readonly fields: Field[];
17
+ /** The edit form's fields. Throws for a blueprint that cannot update. */
18
+ readonly editFields: Field[];
19
+ /** The resources its relation fields name. */
20
+ relations: string[];
21
+ /** Saves a new record, answering what was saved. Null when the blueprint only updates, or leaves saving to a resource's source. */
22
+ create: ((values: Record<string, unknown>) => Promise<unknown>) | null;
23
+ /** Saves the record with this key. Null when the blueprint only creates, or leaves saving to a resource's source. */
24
+ update: ((values: Record<string, unknown>, key: string) => Promise<unknown>) | null;
25
+ }
26
+ export declare function isBlueprint(value: unknown): value is BlueprintParts;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * What marks a blueprint, so a resource handed one in `form` can tell it from
3
+ * a Standard Schema without importing the module that draws it.
4
+ */
5
+ export const BLUEPRINT = Symbol.for('@rsc-kit/press.blueprint');
6
+ export function isBlueprint(value) {
7
+ return typeof value === 'object' && value !== null && value[BLUEPRINT] === true;
8
+ }
9
+ //# sourceMappingURL=brand.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brand.js","sourceRoot":"","sources":["../../src/forms/brand.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAA;AAsB/D,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAK,KAAiC,CAAC,SAAS,CAAC,KAAK,IAAI,CAAA;AAC9G,CAAC","sourcesContent":["import type { Field, FormSchema } from '../resource.js'\n\n/**\n * What marks a blueprint, so a resource handed one in `form` can tell it from\n * a Standard Schema without importing the module that draws it.\n */\nexport const BLUEPRINT = Symbol.for('@rsc-kit/press.blueprint')\n\n/** What a resource reads from a blueprint: the fields and the saves, nothing it draws. */\nexport interface BlueprintParts {\n readonly [BLUEPRINT]: true\n name: string\n /** What a create is checked against. */\n schema: FormSchema\n /** What an update is checked against: a backend's update may take other fields than its create. */\n editSchema: FormSchema\n /** The create form's fields. Throws for a blueprint that cannot create. */\n readonly fields: Field[]\n /** The edit form's fields. Throws for a blueprint that cannot update. */\n readonly editFields: Field[]\n /** The resources its relation fields name. */\n relations: string[]\n /** Saves a new record, answering what was saved. Null when the blueprint only updates, or leaves saving to a resource's source. */\n create: ((values: Record<string, unknown>) => Promise<unknown>) | null\n /** Saves the record with this key. Null when the blueprint only creates, or leaves saving to a resource's source. */\n update: ((values: Record<string, unknown>, key: string) => Promise<unknown>) | null\n}\n\nexport function isBlueprint(value: unknown): value is BlueprintParts {\n return typeof value === 'object' && value !== null && (value as Record<symbol, unknown>)[BLUEPRINT] === true\n}\n"]}
@@ -1,7 +1,7 @@
1
1
  import type { FormFields } from '@rsc-kit/core/form';
2
2
  import type { FieldOverride, FormSchema } from '../resource.js';
3
3
  /**
4
- * A form with no schema of its own: defineForm({ action: contactSend }), with
4
+ * A form with no schema of its own: defineBlueprint({ actions: { create: contactSend } }), with
5
5
  * the action the build generated for the backend's Contact.send. The backend
6
6
  * said what the function takes when it wrote rsc-host.json, and the build put
7
7
  * that in the server entry, so the fields come from there and every check is
@@ -20,5 +20,15 @@ export declare function targetOf(action: HostAction): string | null;
20
20
  * the form first draws, by which time the server entry has run.
21
21
  */
22
22
  export declare function hostSchema(action: HostAction): FormSchema;
23
+ /**
24
+ * What calls an action: a public form or a panel's create, with the values
25
+ * alone, or a panel's update, with the record's key after them.
26
+ */
27
+ export interface Caller {
28
+ /** How errors name it: "[forms] the form contact", "[admin] products' update". */
29
+ name: string;
30
+ /** Called as update(values, id), so the backend must take the key second. */
31
+ key: boolean;
32
+ }
23
33
  /** A form must be able to draw what it was given, or say so rather than draw a text box. */
24
- export declare function requireDrawable(form: string, action: HostAction, overrides: Record<string, FieldOverride>): void;
34
+ export declare function requireDrawable(caller: string | Caller, action: HostAction, overrides: Record<string, FieldOverride | undefined>): void;
@@ -25,22 +25,32 @@ export function hostSchema(action) {
25
25
  };
26
26
  }
27
27
  /** A form must be able to draw what it was given, or say so rather than draw a text box. */
28
- export function requireDrawable(form, action, overrides) {
28
+ export function requireDrawable(caller, action, overrides) {
29
+ const { name, key } = typeof caller === 'string' ? { name: `[forms] the form ${caller}`, key: false } : caller;
29
30
  const target = targetOf(action);
30
31
  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
+ throw new Error(`${name}'s action ${action.name || 'function'} is not one the build generated for the backend. ` +
32
33
  "Import it from the generated server-actions module: import { contactSend } from '../server-actions.generated'.");
33
34
  }
34
35
  const input = hostForms().inputs[target];
35
36
  if (!input) {
36
- if (Object.keys(overrides).length > 0)
37
+ if (Object.keys(overrides).length > 0 && !key)
37
38
  return;
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.');
39
+ throw new Error(`${name} calls ${target}, and the backend does not say it takes one object with named fields` +
40
+ (key ? " and the record's key after it" : '') +
41
+ '. Take a FormRequest in Laravel or a struct in Go' +
42
+ (key ? ', then the key: update(UpdateProductRequest $request, string $product).' : ', or list every field in fields.'));
43
+ }
44
+ if (key && !input['x-key']) {
45
+ throw new Error(`${name} calls ${target} with the record's key after the values, and it takes only the values. ` +
46
+ 'Take the key second: update(UpdateProductRequest $request, string $product) in Laravel, func(ctx, in ProductInput, id string) in Go.');
47
+ }
48
+ if (!key && input['x-key']) {
49
+ throw new Error(`${name} calls ${target} with the values alone, and it takes a record's key after them. Use it as a panel's update.`);
40
50
  }
41
51
  for (const [field, schema] of Object.entries(input.properties ?? {})) {
42
52
  if (!schema.type && !schema.enum && !schema.anyOf && !overrides[field]?.type) {
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.`);
53
+ throw new Error(`${name} cannot tell what ${target}'s ${field} is, which a file or an open type would be. Say it in fields.`);
44
54
  }
45
55
  }
46
56
  }
@@ -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,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
+ {"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;AAaD,4FAA4F;AAC5F,MAAM,UAAU,eAAe,CAAC,MAAuB,EAAE,MAAkB,EAAE,SAAoD;IAC/H,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,oBAAoB,MAAM,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,MAAM,CAAA;IAC9G,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAA;IAE/B,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CACb,GAAG,IAAI,aAAa,MAAM,CAAC,IAAI,IAAI,UAAU,mDAAmD;YAC9F,gHAAgH,CACnH,CAAA;IACH,CAAC;IAED,MAAM,KAAK,GAAG,SAAS,EAAE,CAAC,MAAM,CAAC,MAAM,CAA4F,CAAA;IAEnI,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,IAAI,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,GAAG;YAAE,OAAM;QAErD,MAAM,IAAI,KAAK,CACb,GAAG,IAAI,UAAU,MAAM,sEAAsE;YAC3F,CAAC,GAAG,CAAC,CAAC,CAAC,gCAAgC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7C,mDAAmD;YACnD,CAAC,GAAG,CAAC,CAAC,CAAC,yEAAyE,CAAC,CAAC,CAAC,kCAAkC,CAAC,CACzH,CAAA;IACH,CAAC;IAED,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CACb,GAAG,IAAI,UAAU,MAAM,yEAAyE;YAC9F,sIAAsI,CACzI,CAAA;IACH,CAAC;IAED,IAAI,CAAC,GAAG,IAAI,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,UAAU,MAAM,6FAA6F,CAAC,CAAA;IACvI,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,GAAG,IAAI,qBAAqB,MAAM,MAAM,KAAK,+DAA+D,CAAC,CAAA;QAC/H,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: defineBlueprint({ actions: { create: 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>>; 'x-key'?: boolean }>\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/**\n * What calls an action: a public form or a panel's create, with the values\n * alone, or a panel's update, with the record's key after them.\n */\nexport interface Caller {\n /** How errors name it: \"[forms] the form contact\", \"[admin] products' update\". */\n name: string\n /** Called as update(values, id), so the backend must take the key second. */\n key: boolean\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(caller: string | Caller, action: HostAction, overrides: Record<string, FieldOverride | undefined>): void {\n const { name, key } = typeof caller === 'string' ? { name: `[forms] the form ${caller}`, key: false } : caller\n const target = targetOf(action)\n\n if (target === null) {\n throw new Error(\n `${name}'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] as { properties?: Record<string, Record<string, unknown>>; 'x-key'?: boolean } | undefined\n\n if (!input) {\n if (Object.keys(overrides).length > 0 && !key) return\n\n throw new Error(\n `${name} calls ${target}, and the backend does not say it takes one object with named fields` +\n (key ? \" and the record's key after it\" : '') +\n '. Take a FormRequest in Laravel or a struct in Go' +\n (key ? ', then the key: update(UpdateProductRequest $request, string $product).' : ', or list every field in fields.'),\n )\n }\n\n if (key && !input['x-key']) {\n throw new Error(\n `${name} calls ${target} with the record's key after the values, and it takes only the values. ` +\n 'Take the key second: update(UpdateProductRequest $request, string $product) in Laravel, func(ctx, in ProductInput, id string) in Go.',\n )\n }\n\n if (!key && input['x-key']) {\n throw new Error(`${name} calls ${target} with the values alone, and it takes a record's key after them. Use it as a panel's update.`)\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(`${name} 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,4 +1,4 @@
1
- export { defineForm } from './form.js';
2
- export type { FormDefinition, HostFormDefinition, PublicForm } from './form.js';
3
- export type { FormsTheme, PublicFormProps } from './theme.js';
1
+ export { defineBlueprint } from './blueprint.js';
2
+ export type { Blueprint, BlueprintFormProps, HostBlueprintDefinition, SchemaBlueprintDefinition } from './blueprint.js';
3
+ export type { FormProps, FormsTheme } from './theme.js';
4
4
  export type { HostAction, HostFields } from './host.js';
@@ -1,2 +1,2 @@
1
- export { defineForm } from './form.js';
1
+ export { defineBlueprint } from './blueprint.js';
2
2
  //# sourceMappingURL=index.js.map
@@ -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'\nexport type { HostAction, HostFields } from './host.js'\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/forms/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA","sourcesContent":["export { defineBlueprint } from './blueprint.js'\nexport type { Blueprint, BlueprintFormProps, HostBlueprintDefinition, SchemaBlueprintDefinition } from './blueprint.js'\nexport type { FormProps, FormsTheme } from './theme.js'\nexport type { HostAction, HostFields } from './host.js'\n"]}