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
@@ -1,3 +1,4 @@
1
+ import { prototypeKebabToSlug } from '../naming.js';
1
2
  import buildNavigation from '../../panel/navigation.js';
2
3
  import { areaFormActions } from '../../panel/pages/area/actions.server.js';
3
4
  import { areaLoad } from '../../panel/pages/area/load.server.js';
@@ -55,10 +56,22 @@ export const routeHandlers = {
55
56
  };
56
57
  export const handleRoutes = async ({ event, resolve }) => {
57
58
  const { rime, user } = event.locals;
58
- const isSignInRoute = event.url.pathname === '/panel/sign-in';
59
- const isPanelRoute = event.url.pathname.startsWith('/panel') && !isSignInRoute;
59
+ const IS_SIGN_IN_ROUTE = event.url.pathname === '/panel/sign-in';
60
+ const IS_API_ROUTE = event.url.pathname.startsWith('/api');
61
+ const IS_PANEL_ROUTE = event.url.pathname.startsWith('/panel') && !IS_SIGN_IN_ROUTE;
62
+ // event.params.slug comes straight from the URL, always kebab-case (see
63
+ // src/params/collection.ts, area.ts). Collection/area slugs themselves are
64
+ // camelCase, so rewrite once here — every downstream handler (panel
65
+ // load/actions, REST) can then keep comparing event.params.slug directly
66
+ // against the config's `slug`. Scoped to routes actually matched via our
67
+ // own [slug=collection]/[slug=area] param matchers (route.id carries the
68
+ // matcher name) — a consumer's own app route can also use a `slug` param
69
+ // (e.g. (front)/pages/[slug]) and must be left untouched.
70
+ if ((IS_API_ROUTE || IS_PANEL_ROUTE) && event.params.slug) {
71
+ event.params.slug = prototypeKebabToSlug(event.params.slug);
72
+ }
60
73
  // build panel navigation
61
- if (isPanelRoute && event.request.method === 'GET') {
74
+ if (IS_PANEL_ROUTE && event.request.method === 'GET') {
62
75
  event.locals.navigation = buildNavigation(rime.config.raw, user);
63
76
  }
64
77
  event.locals.routes = routeHandlers;
@@ -10,6 +10,17 @@ import type { CollectionSlug } from '../types.js';
10
10
  * withDirectoriesSuffix('pages_versions');
11
11
  */
12
12
  export declare const withDirectoriesSuffix: (slug: string) => CollectionSlug;
13
+ /**
14
+ * Remove a _directories suffix to a given name.
15
+ * Used for uploads path slug and tables.
16
+ * Prevent a version table name from being used, force the use of the main one.
17
+ *
18
+ * @example
19
+ * // Returns 'pages'
20
+ * withoutDirectoriesSuffix('pages_directories');
21
+ * withoutDirectoriesSuffix('pages_versions_directories');
22
+ */
23
+ export declare const withoutDirectoriesSuffix: (slug: string) => CollectionSlug;
13
24
  /**
14
25
  * Add a _versions suffix to a given name.
15
26
  * Used for document versioning slug and tables.
@@ -19,6 +30,15 @@ export declare const withDirectoriesSuffix: (slug: string) => CollectionSlug;
19
30
  * withVersionsSuffix('pages');
20
31
  */
21
32
  export declare const withVersionsSuffix: (name: string) => CollectionSlug;
33
+ /**
34
+ * Remove a _versions suffix from a given name.
35
+ * Used for document versioning slug and tables.
36
+ *
37
+ * @example
38
+ * // Returns 'pages'
39
+ * withoutVersionsSuffix('pages_versions');
40
+ */
41
+ export declare const withoutVersionsSuffix: (name: string) => CollectionSlug;
22
42
  /**
23
43
  * Check if a slug is a verioned collection slug
24
44
  * * @example
@@ -46,3 +66,17 @@ export declare const hasDirectoriesSuffix: (slug: string) => boolean;
46
66
  * withLocalesSuffix('pages_versions');
47
67
  */
48
68
  export declare const withLocalesSuffix: (name: string) => string;
69
+ /**
70
+ * Convert a kebab-case prototype slug.
71
+ * Basically used to convert param matchers slug into a collection/area slug.
72
+ * Only a literal trailing `_directories`/`_versions` suffix is preserved as a
73
+ * suffix — everything else, including a plain `-versions`/`-directories`
74
+ * word, is camelCased like any other segment.
75
+ *
76
+ * @example
77
+ * prototypeKebabToSlug('my-collection') // returns 'myCollection'
78
+ * prototypeKebabToSlug('my-collection_directories') // returns 'myCollection_directories'
79
+ * prototypeKebabToSlug('my-collection_versions') // returns 'myCollection_versions'
80
+ * prototypeKebabToSlug('my-collection-versions') // returns 'myCollectionVersions'
81
+ */
82
+ export declare const prototypeKebabToSlug: (kebab: string) => string;
@@ -9,6 +9,17 @@
9
9
  * withDirectoriesSuffix('pages_versions');
10
10
  */
11
11
  export const withDirectoriesSuffix = (slug) => `${slug.replace('_versions', '')}_directories`;
12
+ /**
13
+ * Remove a _directories suffix to a given name.
14
+ * Used for uploads path slug and tables.
15
+ * Prevent a version table name from being used, force the use of the main one.
16
+ *
17
+ * @example
18
+ * // Returns 'pages'
19
+ * withoutDirectoriesSuffix('pages_directories');
20
+ * withoutDirectoriesSuffix('pages_versions_directories');
21
+ */
22
+ export const withoutDirectoriesSuffix = (slug) => slug.replace('_directories', '');
12
23
  /**
13
24
  * Add a _versions suffix to a given name.
14
25
  * Used for document versioning slug and tables.
@@ -18,6 +29,15 @@ export const withDirectoriesSuffix = (slug) => `${slug.replace('_versions', '')}
18
29
  * withVersionsSuffix('pages');
19
30
  */
20
31
  export const withVersionsSuffix = (name) => `${name}_versions`;
32
+ /**
33
+ * Remove a _versions suffix from a given name.
34
+ * Used for document versioning slug and tables.
35
+ *
36
+ * @example
37
+ * // Returns 'pages'
38
+ * withoutVersionsSuffix('pages_versions');
39
+ */
40
+ export const withoutVersionsSuffix = (name) => name.replace('_versions', '');
21
41
  /**
22
42
  * Check if a slug is a verioned collection slug
23
43
  * * @example
@@ -45,3 +65,22 @@ export const hasDirectoriesSuffix = (slug) => slug.endsWith('_directories');
45
65
  * withLocalesSuffix('pages_versions');
46
66
  */
47
67
  export const withLocalesSuffix = (name) => `${name}Locales`;
68
+ /**
69
+ * Convert a kebab-case prototype slug.
70
+ * Basically used to convert param matchers slug into a collection/area slug.
71
+ * Only a literal trailing `_directories`/`_versions` suffix is preserved as a
72
+ * suffix — everything else, including a plain `-versions`/`-directories`
73
+ * word, is camelCased like any other segment.
74
+ *
75
+ * @example
76
+ * prototypeKebabToSlug('my-collection') // returns 'myCollection'
77
+ * prototypeKebabToSlug('my-collection_directories') // returns 'myCollection_directories'
78
+ * prototypeKebabToSlug('my-collection_versions') // returns 'myCollection_versions'
79
+ * prototypeKebabToSlug('my-collection-versions') // returns 'myCollectionVersions'
80
+ */
81
+ export const prototypeKebabToSlug = (kebab) => {
82
+ const suffixMatch = kebab.match(/(_(?:directories|versions))$/);
83
+ const suffix = suffixMatch ? suffixMatch[1] : '';
84
+ const base = suffix ? kebab.slice(0, -suffix.length) : kebab;
85
+ return base.replace(/-([a-z])/g, (_, char) => char.toUpperCase()) + suffix;
86
+ };
@@ -10,7 +10,7 @@ export function defineBlocksDiff({ existingBlocks, incomingBlocks, context }) {
10
10
  // - If a block is not localized than it should keep its id so block is updated
11
11
  if (context.isFallbackLocale) {
12
12
  incomingBlocks = incomingBlocks.map((block) => {
13
- const isLocalized = configMap[block.path]?.localized || false;
13
+ const isLocalized = configMap[block.path]?.get.localized || false;
14
14
  if (isLocalized) {
15
15
  delete block.id;
16
16
  block.locale = context.isFallbackLocale;
@@ -5,7 +5,7 @@ export function extractBlocks({ data, configMap }) {
5
5
  Object.entries(configMap).forEach(([path, config]) => {
6
6
  if (config.type === 'blocks') {
7
7
  const value = getValueAtPath(path, data);
8
- const isEmptyValue = config.isEmpty(value);
8
+ const isEmptyValue = config.run.isEmpty(value);
9
9
  if (value && !isEmptyValue) {
10
10
  value.forEach((block, index) => {
11
11
  if (block.type) {
@@ -1,3 +1,4 @@
1
- import type { TreeFieldRaw } from '../../../fields/tree/index.js';
1
+ import type { TreeBlock } from '../../types/doc.js';
2
+ import type { TreeBuilder } from '../../../fields/tree/index.js';
2
3
  import type { ConfigMap } from './types.js';
3
- export declare function buildTreeFieldsMap(treeConfig: TreeFieldRaw, treeItems: any[], basePath: string): ConfigMap;
4
+ export declare function buildTreeFieldsMap(treeConfig: TreeBuilder, treeItems: TreeBlock[], basePath: string): ConfigMap;
@@ -3,7 +3,7 @@ export function buildTreeFieldsMap(treeConfig, treeItems, basePath) {
3
3
  const treeMap = {};
4
4
  // Helper to add field configs for a specific path
5
5
  const addFieldConfigs = (path) => {
6
- for (const field of treeConfig.fields) {
6
+ for (const field of treeConfig.get.fields) {
7
7
  if (isFormField(field)) {
8
8
  treeMap[`${path}.${field.name}`] = field;
9
9
  }
@@ -1,5 +1,6 @@
1
+ import type { FieldBuilder } from '../../fields/builders/field-builder.js';
1
2
  import type { GenericDoc } from '../../types/doc.js';
2
3
  import type { Field } from '../../../fields/types.js';
3
4
  import type { DeepPartial } from '../../../util/types.js';
4
5
  import type { ConfigMap } from './types.js';
5
- export declare const buildConfigMap: (data: DeepPartial<GenericDoc>, incomingFields: Field[]) => ConfigMap;
6
+ export declare const buildConfigMap: (data: DeepPartial<GenericDoc>, incomingFields: FieldBuilder<Field>[]) => ConfigMap;
@@ -1,8 +1,8 @@
1
1
  import { isFormField } from '../../fields/util.js';
2
- import { isBlocksFieldRaw } from '../../../fields/blocks/index.js';
3
- import { isGroupFieldRaw } from '../../../fields/group/index.js';
4
- import { isTabsFieldRaw } from '../../../fields/tabs/index.js';
5
- import { isTreeFieldRaw } from '../../../fields/tree/index.js';
2
+ import { BlocksBuilder } from '../../../fields/blocks/index.js';
3
+ import { GroupFieldBuilder } from '../../../fields/group/index.js';
4
+ import { TabsBuilder } from '../../../fields/tabs/index.js';
5
+ import { TreeBuilder } from '../../../fields/tree/index.js';
6
6
  import { buildTreeFieldsMap } from './buildTreeMap.js';
7
7
  export const buildConfigMap = (data, incomingFields) => {
8
8
  let map = {};
@@ -11,10 +11,10 @@ export const buildConfigMap = (data, incomingFields) => {
11
11
  return;
12
12
  basePath = basePath === '' ? basePath : `${basePath}.`;
13
13
  for (const field of fields) {
14
- if (isTabsFieldRaw(field)) {
15
- for (const tab of field.tabs) {
14
+ if (field instanceof TabsBuilder) {
15
+ for (const tab of field.get.tabs) {
16
16
  if (tab.name in data) {
17
- traverseData(data[tab.name], tab.fields, tab.name);
17
+ traverseData(data[tab.name], tab.get.fields, tab.name);
18
18
  }
19
19
  }
20
20
  continue;
@@ -26,13 +26,13 @@ export const buildConfigMap = (data, incomingFields) => {
26
26
  const value = data[field.name];
27
27
  const path = `${basePath}${field.name}`;
28
28
  map[path] = field;
29
- if (isBlocksFieldRaw(field) && value && Array.isArray(value)) {
29
+ if (field instanceof BlocksBuilder && value && Array.isArray(value)) {
30
30
  const blocks = value;
31
31
  for (const [index, block] of blocks.entries()) {
32
32
  try {
33
- const blockConfig = field.blocks.find((b) => b.name === block.type);
33
+ const blockConfig = field.get.blocks.find((b) => b.name === block.type);
34
34
  if (blockConfig) {
35
- traverseData(block, blockConfig.fields, `${path}.${index}`);
35
+ traverseData(block, blockConfig.get.fields, `${path}.${index}`);
36
36
  }
37
37
  }
38
38
  catch {
@@ -40,12 +40,12 @@ export const buildConfigMap = (data, incomingFields) => {
40
40
  }
41
41
  }
42
42
  }
43
- else if (isTreeFieldRaw(field) && value && Array.isArray(value)) {
43
+ else if (field instanceof TreeBuilder && value && Array.isArray(value)) {
44
44
  const treeMap = buildTreeFieldsMap(field, value, path);
45
45
  map = { ...map, ...treeMap };
46
46
  }
47
- else if (isGroupFieldRaw(field)) {
48
- traverseData(value, field.fields, path);
47
+ else if (field instanceof GroupFieldBuilder) {
48
+ traverseData(value, field.get.fields, path);
49
49
  }
50
50
  }
51
51
  };
@@ -1,2 +1,2 @@
1
- import type { FormField } from '../../../fields/types.js';
2
- export type ConfigMap = Record<string, FormField>;
1
+ import type { FormFieldBuilder } from '../../fields/builders/form-field-builder.js';
2
+ export type ConfigMap = Record<string, FormFieldBuilder>;
@@ -1,38 +1,35 @@
1
1
  import { logger } from '../../../logger/index.server.js';
2
- import { deleteValueAtPath, getValueAtPath, hasProp, setValueAtPath } from '../../../../util/object.js';
2
+ import { deleteValueAtPath, getValueAtPath, setValueAtPath } from '../../../../util/object.js';
3
3
  import { buildConfigMap } from '../../configMap/index.js';
4
4
  import { getDefaultValue } from '../before-upsert/set-default-values.server.js';
5
5
  import { Hooks } from '../index.server.js';
6
6
  export const processDocumentFields = Hooks.beforeRead(async (args) => {
7
7
  const { event } = args;
8
8
  let doc = args.doc;
9
- const configMap = buildConfigMap(doc, args.config.fields.map((f) => f.compile()));
9
+ const configMap = buildConfigMap(doc, args.config.fields);
10
10
  for (const [key, config] of Object.entries(configMap)) {
11
11
  let value = getValueAtPath(key, doc);
12
12
  let isEmpty;
13
- if (config.access && config.access.read) {
14
- const authorized = config.access.read(event.locals.user);
15
- if (!authorized) {
16
- doc = deleteValueAtPath(doc, key);
17
- continue;
18
- }
13
+ if (!config.run.canRead(event.locals.user)) {
14
+ doc = deleteValueAtPath(doc, key);
15
+ continue;
19
16
  }
20
- if (config.hooks?.beforeRead) {
21
- if (value) {
22
- for (const hook of config.hooks.beforeRead) {
23
- value = await hook(value, { event, config, operation: args.context, documentId: doc.id });
24
- doc = setValueAtPath(key, doc, value);
25
- }
26
- }
17
+ if (value !== undefined && value !== null) {
18
+ value = await config.run.beforeRead(value, {
19
+ event,
20
+ operation: args.context,
21
+ documentId: doc.id
22
+ });
23
+ doc = setValueAtPath(key, doc, value);
27
24
  }
28
25
  try {
29
- isEmpty = config.isEmpty(value);
26
+ isEmpty = config.run.isEmpty(value);
30
27
  }
31
28
  catch {
32
29
  isEmpty = false;
33
30
  logger.warn(`Error in config.isEmpty for field ${key}`);
34
31
  }
35
- if (isEmpty && hasProp('defaultValue', config)) {
32
+ if (isEmpty && config.get.defaultValue !== undefined) {
36
33
  value = await getDefaultValue({ key, config, adapter: args.event.locals.rime.adapter });
37
34
  doc = setValueAtPath(key, doc, value);
38
35
  }
@@ -1,7 +1,7 @@
1
1
  import { buildConfigMap } from '../../configMap/index.js';
2
2
  import { Hooks } from '../index.server.js';
3
3
  export const buildDataConfigMap = Hooks.beforeUpsert(async (args) => {
4
- const configMap = buildConfigMap(args.data, args.config.fields.map((f) => f.compile()));
4
+ const configMap = buildConfigMap(args.data, args.config.fields);
5
5
  return {
6
6
  ...args,
7
7
  context: {
@@ -5,7 +5,7 @@ export const buildOriginalDocConfigMap = Hooks.beforeUpsert(async (args) => {
5
5
  const { originalDoc } = args.context;
6
6
  if (!originalDoc)
7
7
  throw new RimeError(RimeError.OPERATION_ERROR, 'missing originalDoc @buildDataConfigMap');
8
- const originalConfigMap = buildConfigMap(originalDoc, args.config.fields.map((f) => f.compile()));
8
+ const originalConfigMap = buildConfigMap(originalDoc, args.config.fields);
9
9
  return {
10
10
  ...args,
11
11
  context: {
@@ -1,11 +1,9 @@
1
1
  import type { Adapter } from '../../../../adapter-sqlite/index.server.js';
2
- import type { FormField } from '../../../../fields/types.js';
2
+ import type { FormFieldBuilder } from '../../../fields/builders/form-field-builder.js';
3
3
  export declare const setDefaultValues: import("../index.server.js").Hook<"raw", "update" | "create", "before">;
4
4
  type GetDefaultValue = (args: {
5
5
  key: string;
6
- config: FormField & {
7
- defaultValue: any;
8
- };
6
+ config: FormFieldBuilder;
9
7
  adapter: Adapter;
10
8
  }) => Promise<any>;
11
9
  export declare const getDefaultValue: GetDefaultValue;
@@ -1,8 +1,8 @@
1
1
  import { getRequestEvent } from '$app/server';
2
2
  import { RimeError } from '../../../errors/index.js';
3
3
  import { logger } from '../../../logger/index.server.js';
4
- import { isRelationField } from '../../../../fields/relation/index.js';
5
- import { getValueAtPath, hasProp, setValueAtPath } from '../../../../util/object.js';
4
+ import { RelationFieldBuilder } from '../../../../fields/relation/index.js';
5
+ import { getValueAtPath, setValueAtPath } from '../../../../util/object.js';
6
6
  import { eq, inArray } from 'drizzle-orm';
7
7
  import { Hooks } from '../index.server.js';
8
8
  export const setDefaultValues = Hooks.beforeUpsert(async (args) => {
@@ -15,15 +15,15 @@ export const setDefaultValues = Hooks.beforeUpsert(async (args) => {
15
15
  for (const [key, config] of Object.entries(configMap)) {
16
16
  let value = getValueAtPath(key, output);
17
17
  let isEmpty;
18
- const shouldAddDefault = operation === 'create' || (operation === 'update' && config.required);
18
+ const shouldAddDefault = operation === 'create' || (operation === 'update' && config.get.required);
19
19
  try {
20
- isEmpty = config.isEmpty(value);
20
+ isEmpty = config.run.isEmpty(value);
21
21
  }
22
22
  catch {
23
23
  isEmpty = false;
24
24
  logger.warn(`Error in config.isEmpty for field ${key}`);
25
25
  }
26
- if (shouldAddDefault && isEmpty && hasProp('defaultValue', config)) {
26
+ if (shouldAddDefault && isEmpty && config.get.defaultValue !== undefined) {
27
27
  value = await getDefaultValue({ key, config, adapter: rime.adapter });
28
28
  output = setValueAtPath(key, output, value);
29
29
  }
@@ -41,7 +41,7 @@ const defaultRelationValue = async (config, key, adapter) => {
41
41
  const buildRelation = async (defaultValue) => {
42
42
  let condition;
43
43
  //@TODO encapsulate this into adapter.relation.something
44
- const relationTable = adapter.tables[config.relationTo];
44
+ const relationTable = adapter.tables[config.get.relationTo];
45
45
  if (typeof defaultValue === 'string') {
46
46
  condition = eq(relationTable.id, defaultValue);
47
47
  }
@@ -54,22 +54,17 @@ const defaultRelationValue = async (config, key, adapter) => {
54
54
  .where(condition));
55
55
  return existing.map(({ documentId }, index) => ({
56
56
  id: null,
57
- relationTo: config.relationTo,
57
+ relationTo: config.get.relationTo,
58
58
  path: key,
59
59
  position: index,
60
60
  documentId: documentId
61
61
  }));
62
62
  };
63
- return await buildRelation(config.defaultValue);
63
+ return await buildRelation(config.run.defaultValue({ event: getRequestEvent() }));
64
64
  };
65
65
  export const getDefaultValue = async ({ key, config, adapter }) => {
66
- if (isRelationField(config)) {
66
+ if (config instanceof RelationFieldBuilder) {
67
67
  return await defaultRelationValue(config, key, adapter);
68
68
  }
69
- else {
70
- if (typeof config.defaultValue === 'function') {
71
- return config.defaultValue({ event: getRequestEvent() });
72
- }
73
- return config.defaultValue;
74
- }
69
+ return config.run.defaultValue({ event: getRequestEvent() });
75
70
  };
@@ -7,7 +7,13 @@ export const validateFields = Hooks.beforeUpsert(async (args) => {
7
7
  const errors = {};
8
8
  const { event, operation } = args;
9
9
  const { rime, user } = event.locals;
10
- const configMap = args.context.configMap;
10
+ // Own copy — an access-denied field gets deleted from here too, not just
11
+ // from `output`. `updateById` turns this map's keys into `incomingPaths`,
12
+ // which saveRelations/saveBlocks/saveTreeBlocks use to decide which
13
+ // existing rows are even in scope for this request; leaving a denied
14
+ // field's path in there made its silently-dropped write look like an
15
+ // explicit "no relations/blocks here anymore" and delete the existing data.
16
+ const configMap = { ...args.context.configMap };
11
17
  const locale = args.context.params.locale || args.event.locals.locale;
12
18
  const slug = args.config.slug;
13
19
  const isCollection = rime.config.isCollection(slug);
@@ -16,9 +22,15 @@ export const validateFields = Hooks.beforeUpsert(async (args) => {
16
22
  throw new RimeError(RimeError.OPERATION_ERROR, 'missing configMap @validateFields');
17
23
  // Get the skip parameter from the url
18
24
  const paramSkip = event.url.searchParams.get(PARAMS.SKIP_VALIDATION) === 'true' || false;
19
- // Skip validation on locale fallback as the validation has already been done on creation
25
+ // Skip validation/hooks on locale fallback — this data is `omitId(document)`
26
+ // read back from the just-created document (see create.ts's other-locales
27
+ // loop), already fully processed once. skipHooks gates validate() as well
28
+ // as beforeValidate/beforeSave: a non-idempotent hook (e.g. appending a
29
+ // suffix) would otherwise re-apply itself on every other locale and
30
+ // corrupt non-localized fields, which share the same underlying storage
31
+ // across locales.
20
32
  const skipUnique = args.context.isFallbackLocale || paramSkip;
21
- const skipValidate = args.context.isFallbackLocale || paramSkip;
33
+ const skipHooks = args.context.isFallbackLocale || paramSkip;
22
34
  const skipRequired = args.context.isFallbackLocale || paramSkip;
23
35
  const skipAccess = args.context.isFallbackLocale || args.context.isSystemOperation;
24
36
  for (const [key, config] of Object.entries(configMap)) {
@@ -28,7 +40,7 @@ export const validateFields = Hooks.beforeUpsert(async (args) => {
28
40
  /****************************************************/
29
41
  // Unique
30
42
  /** @TODO better unique check like relations, locale,... */
31
- if ('unique' in config && config.unique && isCollection && !skipUnique) {
43
+ if ('unique' in config.get && config.get.unique && isCollection && !skipUnique) {
32
44
  let query;
33
45
  switch (operation) {
34
46
  case 'create':
@@ -50,72 +62,74 @@ export const validateFields = Hooks.beforeUpsert(async (args) => {
50
62
  /****************************************************/
51
63
  /* Field hook before validate
52
64
  /****************************************************/
53
- if (config.hooks?.beforeValidate) {
54
- if (value) {
55
- for (const hook of config.hooks.beforeValidate) {
56
- value = await hook(value, { config, data: args.data });
57
- output = setValueAtPath(key, output, value);
58
- }
59
- }
65
+ if (value !== undefined && value !== null && !skipHooks) {
66
+ value = await config.run.beforeValidate(value, { config, data: args.data });
67
+ output = setValueAtPath(key, output, value);
60
68
  }
61
69
  /****************************************************/
62
70
  /* Validate
63
71
  /****************************************************/
64
- if (config.validate && value && !skipValidate) {
72
+ if (config.get.validate && value !== undefined && value !== null && !skipHooks) {
65
73
  try {
66
- const valid = config.validate(value, {
74
+ const valid = config.run.validate(value, {
67
75
  data: output,
68
76
  operation,
69
77
  id: operation === 'update' ? args.context.originalDoc?.id : undefined,
70
78
  user: user,
71
79
  locale,
72
- config
80
+ config: config.get
73
81
  });
74
82
  if (valid !== true) {
75
83
  errors[key] = valid;
76
84
  }
77
85
  }
78
- catch {
86
+ catch (err) {
79
87
  logger.warn(`Error while validating field ${key}`);
88
+ console.debug(`[validateFields] field "${key}" threw:`, err);
80
89
  errors[key] = RimeFormError.VALIDATION_ERROR;
81
90
  }
82
91
  }
83
- /****************************************************/
92
+ /*
84
93
  /* Field hook before Save
85
- /****************************************************/
86
- if (config.hooks?.beforeSave) {
87
- if (value) {
88
- for (const hook of config.hooks.beforeSave) {
89
- value = await hook(value, { config, event, operation: args.context });
90
- output = setValueAtPath(key, output, value);
91
- }
92
- }
94
+ */
95
+ if (value !== undefined && value !== null && !skipHooks) {
96
+ value = await config.run.beforeSave(value, {
97
+ config: config.get,
98
+ event,
99
+ operation: args.context
100
+ });
101
+ output = setValueAtPath(key, output, value);
93
102
  }
94
103
  /****************************************************/
95
104
  /* Access
96
105
  /****************************************************/
97
- if (config.access && config.access.update && operation === 'update' && !skipAccess) {
98
- const authorizedFieldUpdate = config.access.update(user, {
106
+ if (operation === 'update' && !skipAccess) {
107
+ const authorizedFieldUpdate = config.run.canUpdate(user, {
99
108
  id: args.context.originalDoc?.id
100
109
  });
101
110
  if (!authorizedFieldUpdate) {
102
111
  output = deleteValueAtPath(output, key);
112
+ delete configMap[key];
103
113
  value = undefined;
104
114
  }
105
115
  }
106
- if (config.access && config.access.create && operation === 'create' && !skipAccess) {
107
- const authorizedFieldCreate = config.access.create(user, {
116
+ if (operation === 'create' && !skipAccess) {
117
+ const authorizedFieldCreate = config.run.canCreate(user, {
108
118
  id: undefined
109
119
  });
110
120
  if (!authorizedFieldCreate) {
111
121
  output = deleteValueAtPath(output, key);
122
+ delete configMap[key];
112
123
  value = undefined;
113
124
  }
114
125
  }
115
126
  // Required
116
- if (config.required && config.isEmpty(value)) {
127
+ if (config.get.required && config.run.isEmpty(value)) {
117
128
  if (skipRequired) {
118
- output = setValueAtPath(key, output, '');
129
+ // The field's own type-correct default (falling back to '' only
130
+ // when none is set) — a hardcoded '' here was wrong for every
131
+ // non-string required field (number, checkbox, date, ...).
132
+ output = setValueAtPath(key, output, config.run.defaultValue({ event }) ?? '');
119
133
  }
120
134
  else {
121
135
  errors[key] = RimeFormError.REQUIRED_FIELD;
@@ -127,6 +141,10 @@ export const validateFields = Hooks.beforeUpsert(async (args) => {
127
141
  }
128
142
  return {
129
143
  ...args,
130
- data: output
144
+ data: output,
145
+ context: {
146
+ ...args.context,
147
+ configMap
148
+ }
131
149
  };
132
150
  });
@@ -1,17 +1,17 @@
1
- import { isRelationField } from '../../../fields/relation/index.js';
1
+ import { RelationFieldBuilder } from '../../../fields/relation/index.js';
2
2
  import { getValueAtPath } from '../../../util/object.js';
3
3
  export const extractRelations = ({ ownerId, data, configMap, locale }) => {
4
4
  const relations = [];
5
5
  for (const [path, config] of Object.entries(configMap)) {
6
- if (isRelationField(config)) {
6
+ if (config instanceof RelationFieldBuilder) {
7
7
  const value = getValueAtPath(path, data);
8
- const localized = config.localized;
8
+ const localized = config.get.localized;
9
9
  const relationRawValue = value;
10
10
  let output = [];
11
11
  const relationFromString = ({ value, position = 0 }) => {
12
12
  const result = {
13
13
  position,
14
- relationTo: config.relationTo,
14
+ relationTo: config.get.relationTo,
15
15
  documentId: value,
16
16
  ownerId,
17
17
  path
@@ -25,7 +25,7 @@ export const extractRelations = ({ ownerId, data, configMap, locale }) => {
25
25
  const result = {
26
26
  id: value.id || undefined,
27
27
  position,
28
- relationTo: config.relationTo,
28
+ relationTo: config.get.relationTo,
29
29
  documentId: value.documentId,
30
30
  ownerId,
31
31
  path
@@ -10,13 +10,13 @@ export const fallbackDataFromOriginal = async (args) => {
10
10
  if (ignore.includes(key))
11
11
  continue;
12
12
  // skip if not required and mode is 'required'
13
- const shouldUpdate = (config.required && mode === 'required') || mode === 'all';
13
+ const shouldUpdate = (config.get.required && mode === 'required') || mode === 'all';
14
14
  if (!shouldUpdate)
15
15
  continue;
16
16
  let value = getValueAtPath(key, output);
17
17
  let isEmpty;
18
18
  try {
19
- isEmpty = config.isEmpty(value);
19
+ isEmpty = config.run.isEmpty(value);
20
20
  }
21
21
  catch {
22
22
  isEmpty = false;
@@ -12,7 +12,7 @@ export function defineTreeBlocksDiff({ existingBlocks, incomingBlocks, context }
12
12
  // - If a block is not localized than it should keep its id so block is updated
13
13
  if (context.isFallbackLocale) {
14
14
  incomingBlocks = incomingBlocks.map((block) => {
15
- const isLocalized = configMap[block.path]?.localized || false;
15
+ const isLocalized = configMap[block.path]?.get.localized || false;
16
16
  if (isLocalized) {
17
17
  delete block.id;
18
18
  block.locale = context.isFallbackLocale;
@@ -22,7 +22,7 @@ export function extractTreeBlocks({ data, configMap }) {
22
22
  Object.entries(configMap).forEach(([path, config]) => {
23
23
  if (config.type === 'tree') {
24
24
  const value = getValueAtPath(path, data);
25
- const isEmptyValue = config.isEmpty(value);
25
+ const isEmptyValue = config.run.isEmpty(value);
26
26
  if (value && !isEmptyValue) {
27
27
  value.forEach((item, index) => {
28
28
  processTreeItem(item, index, path, undefined);