rimecms 0.29.0 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (215) hide show
  1. package/dist/adapter-sqlite/area.server.js +8 -8
  2. package/dist/adapter-sqlite/generate-schema/column.server.d.ts +13 -0
  3. package/dist/adapter-sqlite/generate-schema/column.server.js +77 -0
  4. package/dist/adapter-sqlite/generate-schema/index.server.js +7 -12
  5. package/dist/adapter-sqlite/generate-schema/root.server.js +35 -39
  6. package/dist/adapter-sqlite/generate-schema/templates.server.d.ts +12 -0
  7. package/dist/adapter-sqlite/generate-schema/templates.server.js +19 -0
  8. package/dist/adapter-sqlite/index.server.d.ts +1 -2
  9. package/dist/adapter-sqlite/index.server.js +0 -2
  10. package/dist/adapter-sqlite/relations.server.js +6 -1
  11. package/dist/adapter-sqlite/util.server.d.ts +1 -6
  12. package/dist/adapter-sqlite/util.server.js +6 -0
  13. package/dist/adapter-sqlite/where.server.js +17 -16
  14. package/dist/adapter-sqlite/with.server.js +9 -9
  15. package/dist/core/collections/auth/fields.d.ts +2 -182
  16. package/dist/core/collections/config/augment-auth.server.js +1 -1
  17. package/dist/core/collections/config/augment-nested.server.js +1 -1
  18. package/dist/core/collections/config/augment-upload.js +2 -2
  19. package/dist/core/collections/config/augment-upload.server.js +4 -1
  20. package/dist/core/collections/operations/duplicate.js +30 -11
  21. package/dist/core/collections/operations/updateById.js +6 -5
  22. package/dist/core/collections/rest/duplicate.server.js +7 -3
  23. package/dist/core/collections/upload/disk/delete.server.d.ts +1 -1
  24. package/dist/core/collections/upload/disk/delete.server.js +48 -1
  25. package/dist/core/collections/upload/hooks/clean-up-files.server.js +2 -2
  26. package/dist/core/collections/upload/hooks/extract-path.server.d.ts +1 -1
  27. package/dist/core/collections/upload/hooks/extract-path.server.js +1 -1
  28. package/dist/core/collections/upload/hooks/process-file-upload.server.js +3 -3
  29. package/dist/core/config/server/validate.server.js +50 -30
  30. package/dist/core/config/shared/find-thumbnail.js +12 -12
  31. package/dist/core/config/shared/find-title.js +13 -13
  32. package/dist/core/config/shared/upload-directories.js +1 -1
  33. package/dist/core/constant.server.d.ts +1 -0
  34. package/dist/core/constant.server.js +2 -0
  35. package/dist/core/dev/cache/index.server.js +9 -9
  36. package/dist/core/dev/cli/build/index.js +2 -2
  37. package/dist/core/dev/cli/clear/index.server.js +2 -1
  38. package/dist/core/dev/cli/generate/index.server.js +3 -2
  39. package/dist/core/dev/cli/init/index.server.js +1 -1
  40. package/dist/core/dev/generate/routes/index.server.js +2 -2
  41. package/dist/core/dev/generate/types/index.server.js +17 -17
  42. package/dist/core/dev/vite/index.server.js +21 -5
  43. package/dist/core/fields/builders/boolean-builder.d.ts +3 -1
  44. package/dist/core/fields/builders/boolean-builder.js +3 -0
  45. package/dist/core/fields/builders/field-builder.d.ts +35 -2
  46. package/dist/core/fields/builders/field-builder.js +33 -2
  47. package/dist/core/fields/builders/form-field-builder.d.ts +71 -12
  48. package/dist/core/fields/builders/form-field-builder.js +96 -6
  49. package/dist/core/fields/builders/select-builder.d.ts +4 -1
  50. package/dist/core/fields/builders/select-builder.js +3 -0
  51. package/dist/core/fields/util.d.ts +8 -16
  52. package/dist/core/fields/util.js +49 -83
  53. package/dist/core/handlers/routes.server.js +16 -3
  54. package/dist/core/naming.d.ts +34 -0
  55. package/dist/core/naming.js +39 -0
  56. package/dist/core/operations/blocks/diff.server.js +1 -1
  57. package/dist/core/operations/blocks/extract.server.js +1 -1
  58. package/dist/core/operations/configMap/buildTreeMap.d.ts +3 -2
  59. package/dist/core/operations/configMap/buildTreeMap.js +1 -1
  60. package/dist/core/operations/configMap/index.d.ts +2 -1
  61. package/dist/core/operations/configMap/index.js +13 -13
  62. package/dist/core/operations/configMap/types.d.ts +2 -2
  63. package/dist/core/operations/hooks/before-read/process-document-fields.server.js +14 -17
  64. package/dist/core/operations/hooks/before-upsert/data-config-map.server.js +1 -1
  65. package/dist/core/operations/hooks/before-upsert/original-config-map.server.js +1 -1
  66. package/dist/core/operations/hooks/before-upsert/set-default-values.server.d.ts +2 -4
  67. package/dist/core/operations/hooks/before-upsert/set-default-values.server.js +10 -15
  68. package/dist/core/operations/hooks/before-upsert/validate-fields.server.js +49 -31
  69. package/dist/core/operations/relations/extract.server.js +5 -5
  70. package/dist/core/operations/shared/fallback-data-from-original.js +2 -2
  71. package/dist/core/operations/tree/diff.server.js +1 -1
  72. package/dist/core/operations/tree/extract.server.js +1 -1
  73. package/dist/core/plugins/cache/cache.server.js +3 -3
  74. package/dist/fields/blocks/component/AddBlockButton.svelte +7 -7
  75. package/dist/fields/blocks/component/AddBlockButton.svelte.d.ts +2 -2
  76. package/dist/fields/blocks/component/Blocks.svelte +6 -7
  77. package/dist/fields/blocks/component/props.d.ts +2 -2
  78. package/dist/fields/blocks/index.d.ts +1 -1
  79. package/dist/fields/blocks/index.js +3 -3
  80. package/dist/fields/blocks/index.server.js +1 -1
  81. package/dist/fields/checkbox/component/Checkbox.svelte +2 -2
  82. package/dist/fields/checkbox/component/props.d.ts +2 -2
  83. package/dist/fields/checkbox/index.server.d.ts +1 -2
  84. package/dist/fields/checkbox/index.server.js +1 -10
  85. package/dist/fields/combobox/component/ComboBox.svelte +3 -3
  86. package/dist/fields/combobox/component/props.d.ts +2 -2
  87. package/dist/fields/combobox/index.server.d.ts +1 -2
  88. package/dist/fields/combobox/index.server.js +2 -11
  89. package/dist/fields/component/index.d.ts +1 -1
  90. package/dist/fields/date/component/Date.svelte +4 -4
  91. package/dist/fields/date/component/Date.svelte.d.ts +2 -2
  92. package/dist/fields/date/index.d.ts +3 -1
  93. package/dist/fields/date/index.js +3 -0
  94. package/dist/fields/date/index.server.d.ts +1 -2
  95. package/dist/fields/date/index.server.js +1 -10
  96. package/dist/fields/email/component/Email.svelte +5 -6
  97. package/dist/fields/email/component/props.d.ts +2 -3
  98. package/dist/fields/email/index.d.ts +3 -2
  99. package/dist/fields/email/index.js +4 -1
  100. package/dist/fields/email/index.server.d.ts +0 -3
  101. package/dist/fields/email/index.server.js +0 -9
  102. package/dist/fields/group/component/Group.svelte +9 -15
  103. package/dist/fields/group/component/Group.svelte.d.ts +2 -2
  104. package/dist/fields/group/index.d.ts +4 -2
  105. package/dist/fields/group/index.server.js +2 -2
  106. package/dist/fields/index.server.d.ts +1 -4
  107. package/dist/fields/index.server.js +17 -13
  108. package/dist/fields/link/component/Link.svelte +5 -5
  109. package/dist/fields/link/component/props.d.ts +2 -2
  110. package/dist/fields/link/index.d.ts +4 -1
  111. package/dist/fields/link/index.js +9 -10
  112. package/dist/fields/link/runtime.d.ts +2 -0
  113. package/dist/fields/link/runtime.js +1 -0
  114. package/dist/fields/link/{index.server.d.ts → runtime.server.d.ts} +1 -2
  115. package/dist/fields/link/{index.server.js → runtime.server.js} +2 -11
  116. package/dist/fields/number/component/Number.svelte +6 -6
  117. package/dist/fields/number/component/props.d.ts +2 -2
  118. package/dist/fields/number/index.d.ts +4 -10
  119. package/dist/fields/number/index.js +3 -4
  120. package/dist/fields/number/index.server.d.ts +1 -2
  121. package/dist/fields/number/index.server.js +1 -10
  122. package/dist/fields/radio/component/Radio.svelte +6 -7
  123. package/dist/fields/radio/component/props.d.ts +2 -2
  124. package/dist/fields/radio/index.server.d.ts +1 -2
  125. package/dist/fields/radio/index.server.js +2 -11
  126. package/dist/fields/relation/component/Relation.svelte +18 -14
  127. package/dist/fields/relation/component/Relation.svelte.d.ts +2 -2
  128. package/dist/fields/relation/index.d.ts +7 -4
  129. package/dist/fields/relation/index.js +8 -47
  130. package/dist/fields/relation/runtime.d.ts +5 -0
  131. package/dist/fields/relation/runtime.js +4 -0
  132. package/dist/fields/relation/runtime.server.d.ts +8 -0
  133. package/dist/fields/relation/runtime.server.js +49 -0
  134. package/dist/fields/rich-text/component/RichText.svelte +5 -5
  135. package/dist/fields/rich-text/component/props.d.ts +2 -2
  136. package/dist/fields/rich-text/core/features/fields/fields.svelte +2 -2
  137. package/dist/fields/rich-text/index.d.ts +2 -0
  138. package/dist/fields/rich-text/index.js +3 -0
  139. package/dist/fields/rich-text/index.server.d.ts +1 -2
  140. package/dist/fields/rich-text/index.server.js +1 -10
  141. package/dist/fields/select/component/Select.svelte +16 -16
  142. package/dist/fields/select/component/props.d.ts +2 -2
  143. package/dist/fields/select/index.d.ts +2 -0
  144. package/dist/fields/select/index.js +3 -0
  145. package/dist/fields/select/index.server.d.ts +1 -2
  146. package/dist/fields/select/index.server.js +2 -17
  147. package/dist/fields/slug/component/Slug.svelte +14 -10
  148. package/dist/fields/slug/component/Slug.svelte.d.ts +2 -2
  149. package/dist/fields/slug/index.d.ts +5 -2
  150. package/dist/fields/slug/index.js +3 -0
  151. package/dist/fields/slug/index.server.d.ts +1 -2
  152. package/dist/fields/slug/index.server.js +1 -10
  153. package/dist/fields/tabs/component/Tabs.svelte +15 -15
  154. package/dist/fields/tabs/component/Tabs.svelte.d.ts +2 -2
  155. package/dist/fields/tabs/index.d.ts +4 -16
  156. package/dist/fields/tabs/index.js +1 -8
  157. package/dist/fields/tabs/index.server.js +3 -3
  158. package/dist/fields/text/component/Text.svelte +5 -5
  159. package/dist/fields/text/component/props.d.ts +2 -3
  160. package/dist/fields/text/index.d.ts +4 -10
  161. package/dist/fields/text/index.js +3 -4
  162. package/dist/fields/text/index.server.d.ts +1 -2
  163. package/dist/fields/text/index.server.js +1 -10
  164. package/dist/fields/textarea/component/TextArea.svelte +3 -3
  165. package/dist/fields/textarea/component/props.d.ts +2 -2
  166. package/dist/fields/textarea/index.d.ts +4 -1
  167. package/dist/fields/textarea/index.js +3 -0
  168. package/dist/fields/textarea/index.server.d.ts +1 -2
  169. package/dist/fields/textarea/index.server.js +1 -10
  170. package/dist/fields/time/component/Time.svelte +5 -5
  171. package/dist/fields/time/component/Time.svelte.d.ts +1 -1
  172. package/dist/fields/time/component/props.d.ts +2 -2
  173. package/dist/fields/time/index.d.ts +2 -0
  174. package/dist/fields/time/index.js +3 -0
  175. package/dist/fields/time/index.server.d.ts +1 -2
  176. package/dist/fields/time/index.server.js +1 -10
  177. package/dist/fields/toggle/component/Toggle.svelte +3 -3
  178. package/dist/fields/toggle/component/Toggle.svelte.d.ts +1 -1
  179. package/dist/fields/toggle/component/props.d.ts +2 -2
  180. package/dist/fields/toggle/index.d.ts +2 -1
  181. package/dist/fields/toggle/index.server.d.ts +1 -2
  182. package/dist/fields/toggle/index.server.js +1 -10
  183. package/dist/fields/tree/component/AddItemButton.svelte +1 -2
  184. package/dist/fields/tree/component/Tree.svelte +6 -6
  185. package/dist/fields/tree/component/TreeBlock.svelte +5 -12
  186. package/dist/fields/tree/component/props.d.ts +3 -3
  187. package/dist/fields/tree/index.d.ts +3 -1
  188. package/dist/fields/tree/index.js +2 -2
  189. package/dist/fields/types.d.ts +11 -5
  190. package/dist/panel/components/fields/FieldsPreview.svelte +4 -5
  191. package/dist/panel/components/fields/Hint.svelte +5 -4
  192. package/dist/panel/components/fields/Hint.svelte.d.ts +3 -2
  193. package/dist/panel/components/fields/Label.svelte +6 -11
  194. package/dist/panel/components/fields/Label.svelte.d.ts +3 -2
  195. package/dist/panel/components/fields/LabelFor.svelte +4 -4
  196. package/dist/panel/components/fields/LabelFor.svelte.d.ts +2 -1
  197. package/dist/panel/components/fields/RenderFields.svelte +11 -16
  198. package/dist/panel/components/fields/{root.svelte.d.ts → fieldset.svelte.d.ts} +1 -1
  199. package/dist/panel/components/fields/{root.svelte.js → fieldset.svelte.js} +1 -1
  200. package/dist/panel/components/fields/index.d.ts +5 -5
  201. package/dist/panel/components/fields/index.js +2 -2
  202. package/dist/panel/components/sections/document/AuthFooter.svelte +2 -7
  203. package/dist/panel/components/sections/live/LiveEditPanel.svelte +2 -2
  204. package/dist/panel/context/collection.svelte.js +9 -9
  205. package/dist/panel/context/documentForm.svelte.d.ts +8 -21
  206. package/dist/panel/context/documentForm.svelte.js +45 -52
  207. package/dist/panel/context/form.svelte.d.ts +4 -3
  208. package/dist/panel/context/form.svelte.js +14 -16
  209. package/dist/panel/pages/auth/fields.d.ts +4 -4
  210. package/dist/panel/pages/auth/fields.js +6 -22
  211. package/dist/panel/pages/auth/reset-password/ResetPassword.svelte +3 -10
  212. package/dist/util/doc.js +15 -17
  213. package/package.json +3 -2
  214. package/dist/fields/relation/index.server.d.ts +0 -4
  215. package/dist/fields/relation/index.server.js +0 -13
@@ -3,10 +3,14 @@ export class FieldBuilder {
3
3
  _metaUrl;
4
4
  constructor(type) {
5
5
  this.field = {
6
+ name: '',
6
7
  type,
7
8
  live: true
8
9
  };
9
10
  }
11
+ get name() {
12
+ return this.field.name;
13
+ }
10
14
  className(str) {
11
15
  this.field.className = str;
12
16
  return this;
@@ -25,8 +29,35 @@ export class FieldBuilder {
25
29
  get type() {
26
30
  return this.field.type;
27
31
  }
28
- get raw() {
29
- return this.field;
32
+ /**
33
+ * Raw field data, plus the couple of properties that a bare `this.field`
34
+ * read can't give you: `localized`/`root` default to `false` here since
35
+ * only FormFieldBuilder fields ever set them — container fields like Tabs
36
+ * answer `false` rather than making every caller check
37
+ * `instanceof FormFieldBuilder` first just to ask this one question.
38
+ * Everything else on `T` (Tabs' `tabs`, Blocks' `blocks`, ...) comes
39
+ * through unchanged via the spread — no per-subclass override needed.
40
+ */
41
+ get get() {
42
+ return { ...this.field, localized: false, root: false };
43
+ }
44
+ /** Behavior the builder runs on your behalf (invoking a stored function) —
45
+ * see `.get` for plain data reads. Fields without an access concept
46
+ * (Tabs) default every check to allowed, matching how the field-less case
47
+ * already behaved. Params are unused here but must match FormFieldBuilder's
48
+ * override signature — TS checks generic-constraint satisfaction
49
+ * (`T extends FieldBuilder<any>`) strictly, not with the bivariant
50
+ * leniency normal overrides get, so a param-less base signature breaks
51
+ * assignability everywhere. */
52
+ get run() {
53
+ return {
54
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
55
+ canRead: (..._args) => true,
56
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
57
+ canCreate: (..._args) => true,
58
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
59
+ canUpdate: (..._args) => true
60
+ };
30
61
  }
31
62
  get component() {
32
63
  return null;
@@ -1,16 +1,54 @@
1
1
  import type { FieldPanelTableConfig } from '../../../types.js';
2
2
  import type { Dic } from '../../../util/types.js';
3
- import type { FieldAccess, FieldHook, FieldHookClient, FieldHookShared, FieldValidationFunc, FieldWidth, FormField } from '../../../fields/types.js';
4
- import { FieldBuilder } from './field-builder.js';
5
- type GenerateSchemaFn = (args: {
6
- camel: string;
7
- snake: string;
8
- suffix: string;
9
- }) => string;
10
- export declare class FormFieldBuilder<T extends FormField> extends FieldBuilder<T> {
11
- _generateSchema: null | GenerateSchemaFn;
3
+ import type { RequestEvent } from '@sveltejs/kit';
4
+ import type { FieldAccess, FieldHook, FieldHookClient, FieldHookContext, FieldHookShared, FieldValidationFunc, FieldWidth, FormField } from '../../../fields/types.js';
5
+ import { FieldBuilder, type FieldRun } from './field-builder.js';
6
+ /** Adapter-agnostic storage primitive — column syntax only, not default-value semantics. */
7
+ export type DataType = 'text' | 'boolean' | 'number' | 'timestamp' | 'json';
8
+ /** Method-syntax, same reasoning as `FieldRun` — keeps `run`'s params
9
+ * bivariant across concrete field types so e.g. `SlugFieldBuilder` still
10
+ * satisfies a prop typed `FormFieldBuilder<FormField>`. */
11
+ export type FormFieldRun<T extends FormField> = FieldRun & {
12
+ isEmpty(value: unknown): boolean;
13
+ validate(value: unknown, context: Parameters<FieldValidationFunc<T>>[1]): true | string;
14
+ condition(doc: Dic, siblings: Dic): boolean;
15
+ onChange(value: unknown, context: Parameters<FieldHookClient>[1]): void;
16
+ beforeRead(value: unknown, context: Omit<FieldHookContext<T>, 'config'>): Promise<any>;
17
+ beforeSave(value: unknown, context: FieldHookContext<T>): Promise<any>;
18
+ beforeValidate(value: unknown, context: Parameters<FieldHookShared>[1]): Promise<any>;
19
+ /** Resolves `field.defaultValue` — calling it if it's a `DefaultValueFn`,
20
+ * returning it as-is otherwise. Every field type stores `defaultValue` as
21
+ * plain data (never a fluent-setter name collision on `.field` itself),
22
+ * but several concrete builders (Select, Link, Slug, Relation) *do* expose
23
+ * a `.defaultValue(...)` fluent setter — bare `config.defaultValue` on
24
+ * those would silently return that setter, not the stored value. */
25
+ defaultValue(context?: {
26
+ event?: RequestEvent;
27
+ }): unknown;
28
+ };
29
+ export type ReferentialAction = 'cascade' | 'set null' | 'restrict' | 'no action';
30
+ export type FieldReferenceOptions = {
31
+ onDelete?: ReferentialAction;
32
+ onUpdate?: ReferentialAction;
33
+ /** Referenced table is this field's own table (self-FK); adapters need this
34
+ * to emit a `(): any =>` accessor and avoid a TS circular-declaration error. */
35
+ selfReferencing?: boolean;
36
+ };
37
+ export type FieldReference = FieldReferenceOptions & {
38
+ table: string;
39
+ };
40
+ export declare class FormFieldBuilder<T extends FormField = FormField> extends FieldBuilder<T> {
41
+ /** Every concrete leaf field builder is expected to implement a
42
+ * `get dataType(): DataType` accessor (see e.g. `$lib/fields/text/index.ts`).
43
+ * Not declared here — TS doesn't allow `declare` on accessors in this
44
+ * project's config, and a plain base field would conflict with subclasses
45
+ * that need a computed getter (e.g. `select`, whose dataType depends on
46
+ * `many`). Container builders (Group/Tabs/Blocks/Tree) never get asked,
47
+ * since the adapter's tree-walker special-cases them before reaching the
48
+ * generic column-rendering branch; `column.server.ts` reads `.dataType`
49
+ * via a narrow cast for the same reason it already casts `unique`. */
50
+ _references: FieldReference | null;
12
51
  constructor(name: string, type: string);
13
- get name(): string;
14
52
  label(label: string): this;
15
53
  hidden(): this;
16
54
  localized(): this;
@@ -19,17 +57,38 @@ export declare class FormFieldBuilder<T extends FormField> extends FieldBuilder<
19
57
  table(params?: FieldPanelTableConfig | number): this;
20
58
  width(value: FieldWidth): this;
21
59
  required(bool?: boolean): this;
60
+ /**
61
+ * Force the field to be on the root table — usefull for fields that
62
+ * should not be versioned (ex: _parent for nested structures should
63
+ * always be on the root table to prevent different versions from having
64
+ * different parents).
65
+ */
66
+ _root(): this;
67
+ /** `label`/`localized`/`root`/`required` are the only properties that need
68
+ * anything beyond a plain `this.field` read (a fallback default, or a
69
+ * boolean coercion) — everything else a concrete field type adds
70
+ * (Group's `fields`, Relation's `relationTo`, Select's `options`, ...)
71
+ * comes through unchanged via the spread, no per-subclass override needed. */
72
+ get get(): T & {
73
+ localized: boolean;
74
+ root: boolean;
75
+ label: string;
76
+ required: boolean;
77
+ };
22
78
  access(access: {
23
79
  create?: FieldAccess;
24
80
  read?: FieldAccess;
25
81
  update?: FieldAccess;
26
82
  }): this;
27
83
  onChange(hook: FieldHookClient): this;
84
+ /** Behavior the builder runs on your behalf — invoking a stored function or
85
+ * hook list, not a data read (see `.get` for that). */
86
+ get run(): FormFieldRun<T>;
28
87
  hint(hint: string): this;
29
88
  clone(): typeof this;
30
- $generateSchema(fn: GenerateSchemaFn): this;
89
+ /** Server-side schema concern, same family as $beforeRead/$beforeSave. */
90
+ $references(table: string, options?: FieldReferenceOptions): this;
31
91
  $beforeRead(hook: FieldHook<T>): this;
32
92
  $beforeSave(hook: FieldHook<T>): this;
33
93
  beforeValidate(hook: FieldHookShared): this;
34
94
  }
35
- export {};
@@ -1,7 +1,17 @@
1
+ import { capitalize } from '../../../util/string.js';
1
2
  import cloneDeep from 'clone-deep';
2
3
  import { FieldBuilder } from './field-builder.js';
3
4
  export class FormFieldBuilder extends FieldBuilder {
4
- _generateSchema = null;
5
+ /** Every concrete leaf field builder is expected to implement a
6
+ * `get dataType(): DataType` accessor (see e.g. `$lib/fields/text/index.ts`).
7
+ * Not declared here — TS doesn't allow `declare` on accessors in this
8
+ * project's config, and a plain base field would conflict with subclasses
9
+ * that need a computed getter (e.g. `select`, whose dataType depends on
10
+ * `many`). Container builders (Group/Tabs/Blocks/Tree) never get asked,
11
+ * since the adapter's tree-walker special-cases them before reaching the
12
+ * generic column-rendering branch; `column.server.ts` reads `.dataType`
13
+ * via a narrow cast for the same reason it already casts `unique`. */
14
+ _references = null;
5
15
  constructor(name, type) {
6
16
  super(type);
7
17
  this.field.name = name;
@@ -15,9 +25,6 @@ export class FormFieldBuilder extends FieldBuilder {
15
25
  };
16
26
  return this;
17
27
  }
18
- get name() {
19
- return this.field.name;
20
- }
21
28
  label(label) {
22
29
  this.field.label = label;
23
30
  return this;
@@ -58,6 +65,30 @@ export class FormFieldBuilder extends FieldBuilder {
58
65
  this.field.required = typeof bool === 'undefined' ? true : bool;
59
66
  return this;
60
67
  }
68
+ /**
69
+ * Force the field to be on the root table — usefull for fields that
70
+ * should not be versioned (ex: _parent for nested structures should
71
+ * always be on the root table to prevent different versions from having
72
+ * different parents).
73
+ */
74
+ _root() {
75
+ this.field._root = true;
76
+ return this;
77
+ }
78
+ /** `label`/`localized`/`root`/`required` are the only properties that need
79
+ * anything beyond a plain `this.field` read (a fallback default, or a
80
+ * boolean coercion) — everything else a concrete field type adds
81
+ * (Group's `fields`, Relation's `relationTo`, Select's `options`, ...)
82
+ * comes through unchanged via the spread, no per-subclass override needed. */
83
+ get get() {
84
+ return {
85
+ ...this.field,
86
+ localized: !!this.field.localized,
87
+ root: !!this.field._root,
88
+ label: this.field.label || capitalize(this.field.name),
89
+ required: !!this.field.required
90
+ };
91
+ }
61
92
  access(access) {
62
93
  this.field.access = { ...this.field.access, ...access };
63
94
  return this;
@@ -67,6 +98,64 @@ export class FormFieldBuilder extends FieldBuilder {
67
98
  this.field.hooks.onChange.push(hook);
68
99
  return this;
69
100
  }
101
+ /** Behavior the builder runs on your behalf — invoking a stored function or
102
+ * hook list, not a data read (see `.get` for that). */
103
+ get run() {
104
+ return {
105
+ isEmpty: (value) => this.field.isEmpty(value),
106
+ validate: (value, context) => {
107
+ if (this.field.validate) {
108
+ return this.field.validate(value, { ...context, config: this.field });
109
+ }
110
+ return true;
111
+ },
112
+ condition: (doc, siblings) => {
113
+ if (this.field.condition) {
114
+ try {
115
+ return this.field.condition(doc, siblings);
116
+ }
117
+ catch (err) {
118
+ console.error(err.message);
119
+ return false;
120
+ }
121
+ }
122
+ return true;
123
+ },
124
+ canRead: (...args) => !!this.field.access?.read?.(...args),
125
+ canCreate: (...args) => !!this.field.access?.create?.(...args),
126
+ canUpdate: (...args) => !!this.field.access?.update?.(...args),
127
+ onChange: (value, context) => {
128
+ for (const hook of this.field.hooks?.onChange ?? []) {
129
+ hook(value, context);
130
+ }
131
+ },
132
+ beforeRead: async (value, context) => {
133
+ let result = value;
134
+ for (const hook of this.field.hooks?.beforeRead ?? []) {
135
+ result = await hook(result, { ...context, config: this.field });
136
+ }
137
+ return result;
138
+ },
139
+ beforeSave: async (value, context) => {
140
+ let result = value;
141
+ for (const hook of this.field.hooks?.beforeSave ?? []) {
142
+ result = await hook(result, { ...context, config: this.field });
143
+ }
144
+ return result;
145
+ },
146
+ beforeValidate: async (value, context) => {
147
+ let result = value;
148
+ for (const hook of this.field.hooks?.beforeValidate ?? []) {
149
+ result = await hook(result, { ...context, config: this.field });
150
+ }
151
+ return result;
152
+ },
153
+ defaultValue: (context = {}) => {
154
+ const value = this.field.defaultValue;
155
+ return typeof value === 'function' ? value(context) : value;
156
+ }
157
+ };
158
+ }
70
159
  hint(hint) {
71
160
  this.field.hint = hint;
72
161
  return this;
@@ -83,8 +172,9 @@ export class FormFieldBuilder extends FieldBuilder {
83
172
  clone.field = cloneDeep(this.field);
84
173
  return clone;
85
174
  }
86
- $generateSchema(fn) {
87
- this._generateSchema = fn;
175
+ /** Server-side schema concern, same family as $beforeRead/$beforeSave. */
176
+ $references(table, options) {
177
+ this._references = { table, ...options };
88
178
  return this;
89
179
  }
90
180
  $beforeRead(hook) {
@@ -1,9 +1,11 @@
1
+ import type { DataType } from './form-field-builder.js';
1
2
  import { FormFieldBuilder } from './form-field-builder.js';
2
- import type { DefaultValueFn, FormField, Option } from '../../../fields/types.js';
3
+ import type { DefaultValueFn, FieldValidationFunc, FormField, Option } from '../../../fields/types.js';
3
4
  type FieldWithOptions = FormField & {
4
5
  options: Option[];
5
6
  defaultValue?: (string | DefaultValueFn<string>) | (string[] | DefaultValueFn<string[]>);
6
7
  many?: boolean;
8
+ validate?: FieldValidationFunc<FieldWithOptions>;
7
9
  };
8
10
  declare class PickFieldBuilder<T extends FieldWithOptions = FieldWithOptions> extends FormFieldBuilder<T> {
9
11
  constructor(name: string, type: string);
@@ -17,6 +19,7 @@ declare class PickFieldBuilder<T extends FieldWithOptions = FieldWithOptions> ex
17
19
  }
18
20
  export declare class PickOneFieldBuilder<T extends FieldWithOptions = FieldWithOptions> extends PickFieldBuilder<T> {
19
21
  defaultValue(value: string | DefaultValueFn<string>): this;
22
+ get dataType(): DataType;
20
23
  }
21
24
  export declare class PickManyFieldBuilder<T extends FieldWithOptions = FieldWithOptions> extends PickFieldBuilder<T> {
22
25
  defaultValue(value: string[] | DefaultValueFn<string[]>): this;
@@ -61,6 +61,9 @@ export class PickOneFieldBuilder extends PickFieldBuilder {
61
61
  this.field.defaultValue = value;
62
62
  return this;
63
63
  }
64
+ get dataType() {
65
+ return 'text';
66
+ }
64
67
  }
65
68
  export class PickManyFieldBuilder extends PickFieldBuilder {
66
69
  defaultValue(value) {
@@ -1,5 +1,6 @@
1
1
  import type { Field, FormField, SeparatorField } from '../../fields/types.js';
2
2
  import type { Dic } from '../../util/types.js';
3
+ import type { FormFieldBuilder } from './builders/form-field-builder.js';
3
4
  import type { FieldBuilder } from './builders/index.js';
4
5
  /**
5
6
  * Checks if a field is a presentative field (currently only separator fields).
@@ -10,24 +11,15 @@ export declare const isPresentative: (field: Field) => field is SeparatorField;
10
11
  * Checks if a field is a form field (has a name property).
11
12
  * Form fields are fields that can store data in documents.
12
13
  */
13
- export declare const isFormField: (field: Field) => field is FormField;
14
+ export declare const isFormField: <T extends Field>(field: FieldBuilder<T>) => field is FormFieldBuilder<T & FormField>;
14
15
  /**
15
16
  * Checks if a form field is not hidden.
16
17
  */
17
- export declare const isNotHidden: (field: FormField) => boolean;
18
+ export declare const isNotHidden: <T extends FormField>(field: FormFieldBuilder<T>) => boolean;
18
19
  /**
19
20
  * Checks if a field has live updates enabled.
20
21
  */
21
22
  export declare const isLiveField: (field: Field) => boolean | undefined;
22
- /**
23
- * Flattens a nested field structure into an array of form fields.
24
- * Handles special field types like tabs, tree, and blocks.
25
- *
26
- * @example
27
- * // Flattens a tabs field into its constituent form fields
28
- * const fields = [tabsField].reduce(toFormFields, []);
29
- */
30
- export declare function toFormFields(prev: any[], curr: any): any;
31
23
  /**
32
24
  * Creates an object with empty values based on field configurations.
33
25
  * Uses defaultValue if specified, otherwise undefined.
@@ -43,7 +35,7 @@ export declare function toFormFields(prev: any[], curr: any): any;
43
35
  * ]}
44
36
  * ]);
45
37
  */
46
- export declare const emptyValuesFromFieldConfig: <T extends FormField>(arr: T[]) => Dic;
38
+ export declare const emptyValuesFromFieldConfig: <T extends FormFieldBuilder>(arr: T[]) => Dic;
47
39
  /**
48
40
  * Converts a path with numeric indices to a regex pattern
49
41
  * Numbers between dots or between dot and colon are converted to \d+
@@ -59,13 +51,13 @@ export declare function pathToRegex(path: string): RegExp;
59
51
  * Retrieves a field configuration by its dot-notation path
60
52
  * @example
61
53
  * // Get the title field in the attributes group
62
- * const titleField = getFieldConfigByPath('attributes.title', collection.fields);
54
+ * const titleField = getFieldAtPath('attributes.title', collection.fields);
63
55
  *
64
56
  * // Get the title field in a specific block
65
- * const titleField = getFieldConfigByPath('attributes.layout.2:blockType.title', collection.fields);
57
+ * const titleField = getFieldAtPath('attributes.layout.2:blockType.title', collection.fields);
66
58
  *
67
59
  */
68
- export declare const getFieldConfigByPath: (path: string, fields: Field[]) => FormField | undefined;
60
+ export declare const getFieldAtPath: (path: string, fields: FieldBuilder[]) => FormFieldBuilder<FormField> | undefined;
69
61
  /**
70
62
  * Traverses a FieldBuilder[] tree and returns the subset of builders to pass to
71
63
  * RenderFields, along with the path prefix those builders should be rendered at.
@@ -74,7 +66,7 @@ export declare const getFieldConfigByPath: (path: string, fields: Field[]) => Fo
74
66
  * Blocks are treated as endpoints — the blocks builder itself is returned
75
67
  * regardless of any index:blockType suffix in the path.
76
68
  */
77
- export declare function getFieldBuildersAtPath(fieldPath: string, builders: FieldBuilder[], parentPath?: string): {
69
+ export declare function getFieldListAtPath(fieldPath: string, fields: FieldBuilder[], parentPath?: string): {
78
70
  fields: FieldBuilder[];
79
71
  path: string;
80
72
  };
@@ -1,7 +1,7 @@
1
- import { BlocksBuilder, isBlocksFieldRaw } from '../../fields/blocks/index.js';
2
- import { GroupFieldBuilder, isGroupFieldRaw } from '../../fields/group/index.js';
3
- import { isTabsFieldRaw, TabsBuilder } from '../../fields/tabs/index.js';
4
- import { isTreeFieldRaw, TreeBuilder } from '../../fields/tree/index.js';
1
+ import { BlocksBuilder } from '../../fields/blocks/index.js';
2
+ import { GroupFieldBuilder } from '../../fields/group/index.js';
3
+ import { TabsBuilder } from '../../fields/tabs/index.js';
4
+ import { TreeBuilder } from '../../fields/tree/index.js';
5
5
  import { normalizeFieldPath } from '../../util/doc.js';
6
6
  /**
7
7
  * Checks if a field is a presentative field (currently only separator fields).
@@ -12,48 +12,15 @@ export const isPresentative = (field) => ['separator'].includes(field.type);
12
12
  * Checks if a field is a form field (has a name property).
13
13
  * Form fields are fields that can store data in documents.
14
14
  */
15
- export const isFormField = (field) => 'name' in field;
15
+ export const isFormField = (field) => field.name !== '';
16
16
  /**
17
17
  * Checks if a form field is not hidden.
18
18
  */
19
- export const isNotHidden = (field) => !field.hidden;
19
+ export const isNotHidden = (field) => !field.get.hidden;
20
20
  /**
21
21
  * Checks if a field has live updates enabled.
22
22
  */
23
23
  export const isLiveField = (field) => field.live;
24
- /**
25
- * Flattens a nested field structure into an array of form fields.
26
- * Handles special field types like tabs, tree, and blocks.
27
- *
28
- * @example
29
- * // Flattens a tabs field into its constituent form fields
30
- * const fields = [tabsField].reduce(toFormFields, []);
31
- */
32
- export function toFormFields(prev, curr) {
33
- if (curr.type === 'tabs') {
34
- return curr.tabs.reduce(toFormFields, prev);
35
- }
36
- else if (curr.type === 'tree') {
37
- curr = {
38
- ...curr,
39
- fields: curr.fields.reduce(toFormFields, [])
40
- };
41
- }
42
- else if (curr.type === 'blocks') {
43
- curr = {
44
- ...curr,
45
- blocks: curr.blocks.map((b) => ({
46
- ...b,
47
- fields: b.fields.reduce(toFormFields, [])
48
- }))
49
- };
50
- }
51
- else if ('fields' in curr) {
52
- return curr.fields.reduce(toFormFields, prev);
53
- }
54
- prev.push(curr);
55
- return prev;
56
- }
57
24
  /**
58
25
  * Creates an object with empty values based on field configurations.
59
26
  * Uses defaultValue if specified, otherwise undefined.
@@ -73,24 +40,23 @@ export const emptyValuesFromFieldConfig = (arr) => {
73
40
  return Object.fromEntries(arr.map((config) => {
74
41
  let emptyValue;
75
42
  // Handle group fields - create nested object structure
76
- if (isGroupFieldRaw(config)) {
77
- emptyValue = emptyValuesFromFieldConfig(config.fields.filter(isFormField));
43
+ if (config instanceof GroupFieldBuilder) {
44
+ emptyValue = emptyValuesFromFieldConfig(config.get.fields.filter(isFormField));
78
45
  }
79
46
  // Handle tabs fields - create nested object structure for each tab
80
- else if (isTabsFieldRaw(config)) {
47
+ else if (config instanceof TabsBuilder) {
81
48
  const tabsValue = {};
82
- const tabs = config.tabs;
49
+ const tabs = config.get.tabs;
83
50
  for (const tab of tabs) {
84
51
  if ('fields' in tab) {
85
- tabsValue[tab.name] = emptyValuesFromFieldConfig(tab.fields.filter(isFormField));
52
+ tabsValue[tab.name] = emptyValuesFromFieldConfig(tab.get.fields.filter(isFormField));
86
53
  }
87
54
  }
88
55
  emptyValue = tabsValue;
89
56
  }
90
57
  // Handle default values
91
- else if ('defaultValue' in config) {
92
- emptyValue =
93
- typeof config.defaultValue === 'function' ? config.defaultValue() : config.defaultValue;
58
+ else {
59
+ emptyValue = config.run.defaultValue();
94
60
  }
95
61
  return [config.name, emptyValue];
96
62
  }));
@@ -118,13 +84,13 @@ export function pathToRegex(path) {
118
84
  * Retrieves a field configuration by its dot-notation path
119
85
  * @example
120
86
  * // Get the title field in the attributes group
121
- * const titleField = getFieldConfigByPath('attributes.title', collection.fields);
87
+ * const titleField = getFieldAtPath('attributes.title', collection.fields);
122
88
  *
123
89
  * // Get the title field in a specific block
124
- * const titleField = getFieldConfigByPath('attributes.layout.2:blockType.title', collection.fields);
90
+ * const titleField = getFieldAtPath('attributes.layout.2:blockType.title', collection.fields);
125
91
  *
126
92
  */
127
- export const getFieldConfigByPath = (path, fields) => {
93
+ export const getFieldAtPath = (path, fields) => {
128
94
  const parts = path.split('.');
129
95
  const findInFields = (currentFields, remainingParts) => {
130
96
  if (remainingParts.length === 0)
@@ -132,10 +98,10 @@ export const getFieldConfigByPath = (path, fields) => {
132
98
  const currentPart = remainingParts[0];
133
99
  for (const field of currentFields) {
134
100
  // Handle tabs
135
- if (isTabsFieldRaw(field)) {
136
- const tab = field.tabs.find((t) => t.name === currentPart);
101
+ if (field instanceof TabsBuilder) {
102
+ const tab = field.get.tabs.find((t) => t.name === currentPart);
137
103
  if (tab) {
138
- return findInFields(tab.fields, remainingParts.slice(1));
104
+ return findInFields(tab.get.fields, remainingParts.slice(1));
139
105
  }
140
106
  continue;
141
107
  }
@@ -145,22 +111,22 @@ export const getFieldConfigByPath = (path, fields) => {
145
111
  if (remainingParts.length === 1) {
146
112
  return field;
147
113
  }
148
- if (isGroupFieldRaw(field)) {
149
- return findInFields(field.fields, remainingParts.slice(1));
114
+ if (field instanceof GroupFieldBuilder) {
115
+ return findInFields(field.get.fields, remainingParts.slice(1));
150
116
  }
151
117
  // Handle blocks
152
- if (isBlocksFieldRaw(field) && remainingParts.length > 1) {
118
+ if (field instanceof BlocksBuilder && remainingParts.length > 1) {
153
119
  // const blockPartPattern = /:[a-zA-Z0-9]+/
154
120
  const blockType = remainingParts[1].split(':')[1];
155
121
  if (blockType) {
156
- const block = field.blocks.find((b) => b.name === blockType);
122
+ const block = field.get.blocks.find((b) => b.name === blockType);
157
123
  if (block) {
158
- return findInFields(block.fields, remainingParts.slice(2));
124
+ return findInFields(block.get.fields, remainingParts.slice(2));
159
125
  }
160
126
  }
161
127
  }
162
- if (isTreeFieldRaw(field)) {
163
- return findInFields(field.fields, remainingParts.slice(2));
128
+ if (field instanceof TreeBuilder) {
129
+ return findInFields(field.get.fields, remainingParts.slice(2));
164
130
  }
165
131
  }
166
132
  }
@@ -177,67 +143,67 @@ export const getFieldConfigByPath = (path, fields) => {
177
143
  * Blocks are treated as endpoints — the blocks builder itself is returned
178
144
  * regardless of any index:blockType suffix in the path.
179
145
  */
180
- export function getFieldBuildersAtPath(fieldPath, builders, parentPath = '') {
146
+ export function getFieldListAtPath(fieldPath, fields, parentPath = '') {
181
147
  if (!fieldPath)
182
- return { fields: builders, path: parentPath };
148
+ return { fields, path: parentPath };
183
149
  const dotIndex = fieldPath.indexOf('.');
184
150
  const head = dotIndex === -1 ? fieldPath : fieldPath.slice(0, dotIndex);
185
151
  const tail = dotIndex === -1 ? '' : fieldPath.slice(dotIndex + 1);
186
152
  const isEndpoint = fieldPath.split('.').length === 1;
187
153
  const nextParentPath = normalizeFieldPath(`${parentPath}${parentPath ? '.' : ''}${head}`);
188
- for (const builder of builders) {
154
+ for (const field of fields) {
189
155
  // ── Tabs container: navigate into the matching tab
190
- if (builder instanceof TabsBuilder) {
191
- const tab = builder.field.tabs.find((t) => t.name === head);
156
+ if (field instanceof TabsBuilder) {
157
+ const tab = field.field.tabs.find((t) => t.name === head);
192
158
  if (!tab)
193
159
  continue;
194
160
  if (isEndpoint) {
195
161
  // Path targets the tab → return all its inner fields
196
- return { fields: tab.raw.fields, path: nextParentPath };
162
+ return { fields: tab.get.fields, path: nextParentPath };
197
163
  }
198
- return getFieldBuildersAtPath(tail, tab.raw.fields, nextParentPath);
164
+ return getFieldListAtPath(tail, tab.get.fields, nextParentPath);
199
165
  }
200
- if (!isFormField(builder.raw))
166
+ if (!isFormField(field))
201
167
  continue;
202
168
  // ── Group field: navigate into children
203
- if (builder instanceof GroupFieldBuilder && builder.raw.name === head) {
204
- const children = builder.field.fields;
169
+ if (field instanceof GroupFieldBuilder && field.name === head) {
170
+ const children = field.get.fields;
205
171
  if (isEndpoint)
206
172
  return { fields: children, path: nextParentPath };
207
- return getFieldBuildersAtPath(tail, children, nextParentPath);
173
+ return getFieldListAtPath(tail, children, nextParentPath);
208
174
  }
209
175
  // —— Blocks
210
- if (builder instanceof BlocksBuilder && builder.raw.name === head) {
176
+ if (field instanceof BlocksBuilder && field.name === head) {
211
177
  const blockType = tail.split('.')[0]?.split(':')[1];
212
178
  const isInnerBlockLookup = tail.split('.').length > 1;
213
179
  const nextParentPathWithBlockIndex = `${nextParentPath}.${tail.split('.')[0]}`;
214
180
  if (blockType) {
215
- const block = builder.raw.blocks.find((b) => b.name === blockType);
216
- if (!isInnerBlockLookup && block?.raw.fields) {
181
+ const block = field.get.blocks.find((b) => b.name === blockType);
182
+ if (!isInnerBlockLookup && block?.get.fields) {
217
183
  return {
218
- fields: block?.raw.fields,
184
+ fields: block.get.fields,
219
185
  path: normalizeFieldPath(nextParentPathWithBlockIndex)
220
186
  };
221
187
  }
222
188
  if (block) {
223
- return getFieldBuildersAtPath(tail.split('.').slice(1).join('.'), block?.raw.fields, normalizeFieldPath(nextParentPathWithBlockIndex));
189
+ return getFieldListAtPath(tail.split('.').slice(1).join('.'), block.get.fields, normalizeFieldPath(nextParentPathWithBlockIndex));
224
190
  }
225
191
  }
226
- return { fields: [builder], path: parentPath };
192
+ return { fields: [field], path: parentPath };
227
193
  }
228
194
  // Tree
229
- if (builder instanceof TreeBuilder && builder.raw.name === head) {
230
- const children = builder.raw.fields;
195
+ if (field instanceof TreeBuilder && field.name === head) {
196
+ const children = field.get.fields;
231
197
  const nextParentPathWithIndex = `${nextParentPath}.${tail.split('.')[0]}`;
232
198
  if (isEndpoint)
233
199
  return { fields: children, path: nextParentPath };
234
- return getFieldBuildersAtPath(tail.split('.').slice(1).join('.'), children, nextParentPathWithIndex);
200
+ return getFieldListAtPath(tail.split('.').slice(1).join('.'), children, nextParentPathWithIndex);
235
201
  }
236
202
  // Direct unique field match
237
- if (isEndpoint && isFormField(builder.raw) && builder.raw.name === fieldPath) {
238
- return { fields: [builder], path: parentPath };
203
+ if (isEndpoint && isFormField(field) && field.name === fieldPath) {
204
+ return { fields: [field], path: parentPath };
239
205
  }
240
206
  }
241
207
  console.warn(`[LiveEditPanel] fieldPath "${fieldPath}" not found in config fields`);
242
- return { fields: builders, path: parentPath };
208
+ return { fields, path: parentPath };
243
209
  }