includio-cms 0.36.9 → 0.37.1

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 (247) hide show
  1. package/API.md +178 -3
  2. package/CHANGELOG.md +77 -0
  3. package/DOCS.md +28 -3
  4. package/ROADMAP.md +32 -0
  5. package/dist/admin/client/booking/booking-dashboard.svelte +40 -0
  6. package/dist/admin/client/booking/booking-dashboard.svelte.d.ts +3 -0
  7. package/dist/admin/client/booking/customer-panel.svelte +102 -0
  8. package/dist/admin/client/booking/customer-panel.svelte.d.ts +11 -0
  9. package/dist/admin/client/booking/customer-view.d.ts +52 -0
  10. package/dist/admin/client/booking/customer-view.js +134 -0
  11. package/dist/admin/client/booking/participant-data-panel.svelte +330 -0
  12. package/dist/admin/client/booking/participant-data-panel.svelte.d.ts +22 -0
  13. package/dist/admin/client/booking/reservation-detail-page.svelte +637 -0
  14. package/dist/admin/client/booking/reservation-detail-page.svelte.d.ts +3 -0
  15. package/dist/admin/client/booking/reservations-page.svelte +221 -0
  16. package/dist/admin/client/booking/reservations-page.svelte.d.ts +3 -0
  17. package/dist/admin/client/collection/collection-entries.svelte +1 -1
  18. package/dist/admin/client/collection/entry-link.svelte +6 -4
  19. package/dist/admin/client/collection/entry-link.svelte.d.ts +3 -1
  20. package/dist/admin/client/collection/status-badge.svelte +39 -3
  21. package/dist/admin/client/collection/status-badge.svelte.d.ts +5 -3
  22. package/dist/admin/client/entry/entry.svelte +26 -9
  23. package/dist/admin/client/entry/hybrid/hybrid-preview.svelte +73 -10
  24. package/dist/admin/client/entry/hybrid/hybrid-preview.svelte.d.ts +6 -5
  25. package/dist/admin/client/entry/preview-views-context.svelte.d.ts +22 -0
  26. package/dist/admin/client/entry/preview-views-context.svelte.js +58 -0
  27. package/dist/admin/client/entry/preview-views.d.ts +27 -0
  28. package/dist/admin/client/entry/preview-views.js +33 -0
  29. package/dist/admin/client/index.d.ts +3 -0
  30. package/dist/admin/client/index.js +3 -0
  31. package/dist/admin/client/modules/dashboard-registry.d.ts +7 -0
  32. package/dist/admin/client/modules/dashboard-registry.js +11 -0
  33. package/dist/admin/client/modules/module-dashboard-page.svelte +26 -0
  34. package/dist/admin/client/modules/module-dashboard-page.svelte.d.ts +6 -0
  35. package/dist/admin/client/modules/stat-card.svelte +22 -0
  36. package/dist/admin/client/modules/stat-card.svelte.d.ts +10 -0
  37. package/dist/admin/client/shop/shop-dashboard.svelte +29 -0
  38. package/dist/admin/client/shop/shop-dashboard.svelte.d.ts +3 -0
  39. package/dist/admin/components/fields/relation-field.svelte +11 -2
  40. package/dist/admin/components/icons/icon-map.js +8 -0
  41. package/dist/admin/components/layout/app-sidebar.svelte +45 -14
  42. package/dist/admin/components/layout/context-switcher.svelte +90 -0
  43. package/dist/admin/components/layout/context-switcher.svelte.d.ts +3 -0
  44. package/dist/admin/components/layout/lang.d.ts +4 -0
  45. package/dist/admin/components/layout/lang.js +8 -0
  46. package/dist/admin/components/layout/layout-renderer.svelte +2 -1
  47. package/dist/admin/components/layout/layout-tabs.svelte +14 -0
  48. package/dist/admin/components/layout/layout-tabs.svelte.d.ts +2 -0
  49. package/dist/admin/components/layout/nav-collections.svelte +16 -9
  50. package/dist/admin/components/layout/nav-collections.svelte.d.ts +5 -1
  51. package/dist/admin/components/layout/nav-global.svelte +45 -0
  52. package/dist/admin/components/layout/nav-global.svelte.d.ts +3 -0
  53. package/dist/admin/components/layout/nav-main.svelte +8 -32
  54. package/dist/admin/components/layout/nav-main.svelte.d.ts +6 -1
  55. package/dist/admin/components/layout/nav-module.svelte +57 -0
  56. package/dist/admin/components/layout/nav-module.svelte.d.ts +7 -0
  57. package/dist/admin/components/layout/nav-section.svelte +1 -1
  58. package/dist/admin/components/layout/nav-section.svelte.d.ts +1 -1
  59. package/dist/admin/components/layout/nav-singletons.svelte +14 -7
  60. package/dist/admin/components/layout/nav-singletons.svelte.d.ts +5 -1
  61. package/dist/admin/remote/booking.remote.d.ts +247 -0
  62. package/dist/admin/remote/booking.remote.js +286 -0
  63. package/dist/admin/remote/index.d.ts +2 -0
  64. package/dist/admin/remote/index.js +2 -0
  65. package/dist/admin/remote/modules.remote.d.ts +10 -0
  66. package/dist/admin/remote/modules.remote.js +14 -0
  67. package/dist/admin/state/active-context-resolve.d.ts +37 -0
  68. package/dist/admin/state/active-context-resolve.js +66 -0
  69. package/dist/admin/state/active-context.svelte.d.ts +8 -0
  70. package/dist/admin/state/active-context.svelte.js +27 -0
  71. package/dist/booking/adjustments.d.ts +33 -0
  72. package/dist/booking/adjustments.js +26 -0
  73. package/dist/booking/capacity.d.ts +12 -0
  74. package/dist/booking/capacity.js +10 -0
  75. package/dist/booking/client/index.d.ts +42 -0
  76. package/dist/booking/client/index.js +48 -0
  77. package/dist/booking/client/use-booking.svelte.d.ts +24 -0
  78. package/dist/booking/client/use-booking.svelte.js +56 -0
  79. package/dist/booking/config.d.ts +176 -0
  80. package/dist/booking/config.js +37 -0
  81. package/dist/booking/draft.d.ts +96 -0
  82. package/dist/booking/draft.js +67 -0
  83. package/dist/booking/expiry-logic.d.ts +30 -0
  84. package/dist/booking/expiry-logic.js +33 -0
  85. package/dist/booking/index.d.ts +18 -0
  86. package/dist/booking/index.js +52 -0
  87. package/dist/booking/ledger.d.ts +46 -0
  88. package/dist/booking/ledger.js +60 -0
  89. package/dist/booking/number.d.ts +2 -0
  90. package/dist/booking/number.js +11 -0
  91. package/dist/booking/participant-fields/access.d.ts +10 -0
  92. package/dist/booking/participant-fields/access.js +14 -0
  93. package/dist/booking/participant-fields/age.d.ts +12 -0
  94. package/dist/booking/participant-fields/age.js +28 -0
  95. package/dist/booking/participant-fields/completeness.d.ts +8 -0
  96. package/dist/booking/participant-fields/completeness.js +13 -0
  97. package/dist/booking/participant-fields/group.d.ts +6 -0
  98. package/dist/booking/participant-fields/group.js +15 -0
  99. package/dist/booking/participant-fields/index.d.ts +6 -0
  100. package/dist/booking/participant-fields/index.js +6 -0
  101. package/dist/booking/participant-fields/types.d.ts +50 -0
  102. package/dist/booking/participant-fields/types.js +5 -0
  103. package/dist/booking/participant-fields/validate.d.ts +7 -0
  104. package/dist/booking/participant-fields/validate.js +44 -0
  105. package/dist/booking/participant-pricing.d.ts +93 -0
  106. package/dist/booking/participant-pricing.js +91 -0
  107. package/dist/booking/participants-view.d.ts +32 -0
  108. package/dist/booking/participants-view.js +22 -0
  109. package/dist/booking/payment-context.d.ts +19 -0
  110. package/dist/booking/payment-context.js +29 -0
  111. package/dist/booking/pricing-inputs.d.ts +28 -0
  112. package/dist/booking/pricing-inputs.js +35 -0
  113. package/dist/booking/pricing.d.ts +86 -0
  114. package/dist/booking/pricing.js +125 -0
  115. package/dist/booking/recompute.d.ts +22 -0
  116. package/dist/booking/recompute.js +31 -0
  117. package/dist/booking/reminders-schedule.d.ts +11 -0
  118. package/dist/booking/reminders-schedule.js +28 -0
  119. package/dist/booking/server/adjustments.d.ts +27 -0
  120. package/dist/booking/server/adjustments.js +139 -0
  121. package/dist/booking/server/bookings.d.ts +51 -0
  122. package/dist/booking/server/bookings.js +336 -0
  123. package/dist/booking/server/catalog.d.ts +9 -0
  124. package/dist/booking/server/catalog.js +20 -0
  125. package/dist/booking/server/db.d.ts +7 -0
  126. package/dist/booking/server/db.js +18 -0
  127. package/dist/booking/server/email.d.ts +23 -0
  128. package/dist/booking/server/email.js +59 -0
  129. package/dist/booking/server/emailTemplateRegistry.d.ts +29 -0
  130. package/dist/booking/server/emailTemplateRegistry.js +125 -0
  131. package/dist/booking/server/http/create-handler.d.ts +5 -0
  132. package/dist/booking/server/http/create-handler.js +70 -0
  133. package/dist/booking/server/http/index.d.ts +4 -0
  134. package/dist/booking/server/http/index.js +4 -0
  135. package/dist/booking/server/http/maintenance-handler.d.ts +9 -0
  136. package/dist/booking/server/http/maintenance-handler.js +36 -0
  137. package/dist/booking/server/http/portal-handler.d.ts +13 -0
  138. package/dist/booking/server/http/portal-handler.js +103 -0
  139. package/dist/booking/server/http/webhook-handler.d.ts +9 -0
  140. package/dist/booking/server/http/webhook-handler.js +39 -0
  141. package/dist/booking/server/index.d.ts +12 -0
  142. package/dist/booking/server/index.js +14 -0
  143. package/dist/booking/server/maintenance.d.ts +28 -0
  144. package/dist/booking/server/maintenance.js +134 -0
  145. package/dist/booking/server/participants.d.ts +26 -0
  146. package/dist/booking/server/participants.js +92 -0
  147. package/dist/booking/server/payments.d.ts +34 -0
  148. package/dist/booking/server/payments.js +207 -0
  149. package/dist/booking/server/portal-session.d.ts +16 -0
  150. package/dist/booking/server/portal-session.js +42 -0
  151. package/dist/booking/server/portal.d.ts +206 -0
  152. package/dist/booking/server/portal.js +236 -0
  153. package/dist/booking/server/price-source.d.ts +73 -0
  154. package/dist/booking/server/price-source.js +94 -0
  155. package/dist/booking/server/refunds.d.ts +7 -0
  156. package/dist/booking/server/refunds.js +58 -0
  157. package/dist/booking/server/reminders.d.ts +9 -0
  158. package/dist/booking/server/reminders.js +24 -0
  159. package/dist/booking/server/validation.d.ts +17 -0
  160. package/dist/booking/server/validation.js +55 -0
  161. package/dist/booking/state-machine.d.ts +41 -0
  162. package/dist/booking/state-machine.js +73 -0
  163. package/dist/booking/templates/_partials/footer.en.html +4 -0
  164. package/dist/booking/templates/_partials/footer.pl.html +4 -0
  165. package/dist/booking/templates/_partials/header.en.html +4 -0
  166. package/dist/booking/templates/_partials/header.pl.html +4 -0
  167. package/dist/booking/templates/balance-due.en.html +10 -0
  168. package/dist/booking/templates/balance-due.pl.html +10 -0
  169. package/dist/booking/templates/balance-overdue-cancelled.en.html +5 -0
  170. package/dist/booking/templates/balance-overdue-cancelled.pl.html +5 -0
  171. package/dist/booking/templates/booking-received.en.html +9 -0
  172. package/dist/booking/templates/booking-received.pl.html +9 -0
  173. package/dist/booking/templates/deposit-paid.en.html +6 -0
  174. package/dist/booking/templates/deposit-paid.pl.html +6 -0
  175. package/dist/booking/templates/payment-link.en.html +5 -0
  176. package/dist/booking/templates/payment-link.pl.html +5 -0
  177. package/dist/booking/templates/payment-received.en.html +10 -0
  178. package/dist/booking/templates/payment-received.pl.html +10 -0
  179. package/dist/booking/totals.d.ts +38 -0
  180. package/dist/booking/totals.js +61 -0
  181. package/dist/booking/webhook-logic.d.ts +28 -0
  182. package/dist/booking/webhook-logic.js +50 -0
  183. package/dist/cli/create-user.d.ts +7 -1
  184. package/dist/cli/create-user.js +23 -15
  185. package/dist/cli/index.js +55 -7
  186. package/dist/cli/scaffold/admin.d.ts +6 -0
  187. package/dist/cli/scaffold/admin.js +5 -3
  188. package/dist/cli/scaffold/booking.d.ts +12 -0
  189. package/dist/cli/scaffold/booking.js +103 -0
  190. package/dist/core/cms.d.ts +24 -9
  191. package/dist/core/cms.js +72 -34
  192. package/dist/core/config/validatePreviews.d.ts +12 -0
  193. package/dist/core/config/validatePreviews.js +90 -0
  194. package/dist/core/fields/slugPath.d.ts +9 -1
  195. package/dist/core/fields/slugPath.js +16 -2
  196. package/dist/core/server/fields/resolveRelationFields.d.ts +11 -2
  197. package/dist/core/server/fields/resolveRelationFields.js +12 -3
  198. package/dist/core/server/generator/generator.js +7 -2
  199. package/dist/db-postgres/schema/booking/bookingAdjustments.d.ts +186 -0
  200. package/dist/db-postgres/schema/booking/bookingAdjustments.js +22 -0
  201. package/dist/db-postgres/schema/booking/bookingItemPersons.d.ts +117 -0
  202. package/dist/db-postgres/schema/booking/bookingItemPersons.js +22 -0
  203. package/dist/db-postgres/schema/booking/bookingItems.d.ts +181 -0
  204. package/dist/db-postgres/schema/booking/bookingItems.js +22 -0
  205. package/dist/db-postgres/schema/booking/bookingPayments.d.ts +204 -0
  206. package/dist/db-postgres/schema/booking/bookingPayments.js +25 -0
  207. package/dist/db-postgres/schema/booking/bookingPersons.d.ts +99 -0
  208. package/dist/db-postgres/schema/booking/bookingPersons.js +17 -0
  209. package/dist/db-postgres/schema/booking/bookingReminders.d.ts +131 -0
  210. package/dist/db-postgres/schema/booking/bookingReminders.js +18 -0
  211. package/dist/db-postgres/schema/booking/bookings.d.ts +324 -0
  212. package/dist/db-postgres/schema/booking/bookings.js +52 -0
  213. package/dist/db-postgres/schema/booking/index.d.ts +7 -0
  214. package/dist/db-postgres/schema/booking/index.js +7 -0
  215. package/dist/db-postgres/schema/index.d.ts +1 -0
  216. package/dist/db-postgres/schema/index.js +1 -0
  217. package/dist/db-postgres/schema-booking.d.ts +1 -0
  218. package/dist/db-postgres/schema-booking.js +1 -0
  219. package/dist/server/security/rate-limit-handler.d.ts +19 -0
  220. package/dist/server/security/rate-limit-handler.js +34 -0
  221. package/dist/shop/adapters/payu/client.js +3 -1
  222. package/dist/shop/adapters/payu/index.js +37 -17
  223. package/dist/shop/adapters/payu/payload.d.ts +6 -0
  224. package/dist/shop/adapters/payu/payload.js +1 -1
  225. package/dist/shop/adapters/stripe/index.js +36 -21
  226. package/dist/shop/index.d.ts +11 -2
  227. package/dist/shop/index.js +43 -1
  228. package/dist/shop/rate-limit.d.ts +1 -9
  229. package/dist/shop/rate-limit.js +3 -28
  230. package/dist/shop/types.d.ts +19 -0
  231. package/dist/sveltekit/config.d.ts +4 -0
  232. package/dist/sveltekit/config.js +6 -0
  233. package/dist/types/cms.d.ts +15 -1
  234. package/dist/types/cms.schema.d.ts +96 -2
  235. package/dist/types/cms.schema.js +84 -1
  236. package/dist/types/config.d.ts +33 -0
  237. package/dist/types/layout.d.ts +6 -0
  238. package/dist/types/modules.d.ts +133 -0
  239. package/dist/types/modules.js +27 -0
  240. package/dist/updates/0.37.0/index.d.ts +2 -0
  241. package/dist/updates/0.37.0/index.js +52 -0
  242. package/dist/updates/0.37.1/index.d.ts +2 -0
  243. package/dist/updates/0.37.1/index.js +24 -0
  244. package/dist/updates/index.js +5 -1
  245. package/package.json +19 -1
  246. package/dist/admin/components/layout/nav-shop.svelte +0 -33
  247. package/dist/admin/components/layout/nav-shop.svelte.d.ts +0 -3
@@ -1,4 +1,5 @@
1
- export function defineShop(config) {
1
+ /** Resolves a raw {@link ShopConfig} into a fully-defaulted {@link ResolvedShopConfig}. */
2
+ function resolveShopConfig(config) {
2
3
  return {
3
4
  ...config,
4
5
  features: {
@@ -22,6 +23,47 @@ export function defineShop(config) {
22
23
  cartName: config.cartName ?? null
23
24
  };
24
25
  }
26
+ /** Server-only init for the shop module — applies GIN indexes + smoke-tests email templates. */
27
+ async function shopModuleInit({ drizzle, config, defaultLanguage }) {
28
+ const shop = config;
29
+ if (drizzle) {
30
+ const { applyVariantAttributeIndexes } = await import('./server/init.js');
31
+ await applyVariantAttributeIndexes(shop, drizzle);
32
+ }
33
+ // Smoke-test built-in shop email templates — fast lookup-only sweep. Catches
34
+ // corrupt installs (missing dist files) at boot instead of at first send.
35
+ const { REQUIRED_TEMPLATE_NAMES } = await import('./server/email.js');
36
+ const { validateBuiltinTemplates } = await import('./server/emailTemplateRegistry.js');
37
+ validateBuiltinTemplates([...REQUIRED_TEMPLATE_NAMES], defaultLanguage);
38
+ }
39
+ /**
40
+ * Defines the shop module. Returns a {@link CMSModule} for
41
+ * `defineConfig({ modules: [defineShop({...})] })`. The resolved shop config
42
+ * is carried on `module.config` and reachable via `getCMS().getModuleConfig('shop')`
43
+ * (or the back-compat `getCMS().shopConfig`).
44
+ *
45
+ * @public
46
+ */
47
+ export function defineShop(config) {
48
+ const resolved = resolveShopConfig(config);
49
+ return {
50
+ id: 'shop',
51
+ label: { pl: 'Sklep', en: 'Shop' },
52
+ icon: 'building-store',
53
+ context: {
54
+ contextLabel: { pl: 'Sklep', en: 'Shop' },
55
+ landingUrl: '/admin/shop',
56
+ sections: [
57
+ { key: 'products', title: { pl: 'Produkty', en: 'Products' }, url: '/admin/shop/products', icon: 'package' },
58
+ { key: 'orders', title: { pl: 'Zamówienia', en: 'Orders' }, url: '/admin/shop/orders', icon: 'shopping-cart' },
59
+ { key: 'shipping', title: { pl: 'Metody wysyłki', en: 'Shipping methods' }, url: '/admin/shop/shipping-methods', icon: 'truck' },
60
+ { key: 'coupons', title: { pl: 'Kody rabatowe', en: 'Coupon codes' }, url: '/admin/shop/coupons', icon: 'ticket' }
61
+ ]
62
+ },
63
+ config: resolved,
64
+ serverHooks: { init: shopModuleInit }
65
+ };
66
+ }
25
67
  export { InvalidVariantAttributesError } from './variant-attributes.js';
26
68
  export { isVariantExpired, filterUpcoming, VariantExpiredError } from './expiry.js';
27
69
  export { manualAdapter } from './adapters/manual/index.js';
@@ -1,9 +1 @@
1
- import type { RateLimitRule } from './types.js';
2
- export interface RateLimitResult {
3
- allowed: boolean;
4
- remaining: number;
5
- resetAt: number;
6
- }
7
- export declare function checkRateLimit(key: string, rule: RateLimitRule): RateLimitResult;
8
- export declare function resetRateLimit(key?: string): void;
9
- export declare function clientKey(req: Request, prefix: string): string;
1
+ export { checkRateLimit, resetRateLimit, clientKey, type RateLimitResult } from '../server/security/rate-limit-handler.js';
@@ -1,28 +1,3 @@
1
- const buckets = new Map();
2
- export function checkRateLimit(key, rule) {
3
- const now = Date.now();
4
- const windowMs = rule.windowSec * 1000;
5
- const bucket = buckets.get(key);
6
- if (!bucket || bucket.resetAt <= now) {
7
- const fresh = { count: 1, resetAt: now + windowMs };
8
- buckets.set(key, fresh);
9
- return { allowed: true, remaining: rule.limit - 1, resetAt: fresh.resetAt };
10
- }
11
- if (bucket.count >= rule.limit) {
12
- return { allowed: false, remaining: 0, resetAt: bucket.resetAt };
13
- }
14
- bucket.count += 1;
15
- return { allowed: true, remaining: rule.limit - bucket.count, resetAt: bucket.resetAt };
16
- }
17
- export function resetRateLimit(key) {
18
- if (key)
19
- buckets.delete(key);
20
- else
21
- buckets.clear();
22
- }
23
- export function clientKey(req, prefix) {
24
- const ip = req.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ||
25
- req.headers.get('x-real-ip') ||
26
- 'unknown';
27
- return `${prefix}:${ip}`;
28
- }
1
+ // Lifted to core (`$lib/server/security/rate-limit-handler`) so shop + booking
2
+ // share one generic per-handler limiter without a booking→shop dependency.
3
+ export { checkRateLimit, resetRateLimit, clientKey } from '../server/security/rate-limit-handler.js';
@@ -70,6 +70,25 @@ export interface ConsentConfig {
70
70
  export interface PaymentCreateContext {
71
71
  customerIp?: string;
72
72
  language?: string | null;
73
+ /**
74
+ * Fully-resolved absolute return URL. When set, adapters MUST use it verbatim
75
+ * and skip any shop-order hydration — lets non-shop callers (e.g. booking)
76
+ * reuse the adapter without a shop module.
77
+ */
78
+ continueUrl?: string;
79
+ /** Caller-supplied buyer identity (for non-shop callers). */
80
+ buyer?: {
81
+ firstName?: string;
82
+ lastName?: string;
83
+ phone?: string;
84
+ };
85
+ /**
86
+ * Unique external id for THIS payment attempt. Adapters whose provider requires
87
+ * a unique-per-attempt order id (e.g. PayU's `extOrderId`, unique per POS) use
88
+ * it instead of `order.number`. Required when one order/booking spawns multiple
89
+ * provider orders (deposit + balance), otherwise the provider rejects the reuse.
90
+ */
91
+ externalId?: string;
73
92
  }
74
93
  /**
75
94
  * Result of a non-invasive integration connectivity check. Returned by an
@@ -19,6 +19,10 @@ type SingleInput = Omit<SingleConfig, 'fields'> & {
19
19
  * (extends `CmsError`) listing every issue with a path and a fix hint, e.g.
20
20
  * `languages[0].code: must be a 2-letter ISO code (e.g. 'en')`
21
21
  *
22
+ * Also checks preview views: unique names, at most one `default`, `previewUrl`
23
+ * and `previews` are mutually exclusive, and every `tab.preview` points at a
24
+ * declared view.
25
+ *
22
26
  * @param config - The CMSConfig (collections, singles, forms, adapters, languages, ...).
23
27
  * @returns The same config, parsed and frozen against typos.
24
28
  * @throws {ConfigValidationError} when validation fails — message aggregates all issues.
@@ -1,5 +1,6 @@
1
1
  import { cmsConfigSchema } from '../types/cms.schema.js';
2
2
  import { formatConfigError } from '../core/errors.js';
3
+ import { validatePreviews } from '../core/config/validatePreviews.js';
3
4
  /**
4
5
  * Defines the root CMS configuration. Pass to `includioCMS()` in `hooks.server.ts`.
5
6
  *
@@ -7,6 +8,10 @@ import { formatConfigError } from '../core/errors.js';
7
8
  * (extends `CmsError`) listing every issue with a path and a fix hint, e.g.
8
9
  * `languages[0].code: must be a 2-letter ISO code (e.g. 'en')`
9
10
  *
11
+ * Also checks preview views: unique names, at most one `default`, `previewUrl`
12
+ * and `previews` are mutually exclusive, and every `tab.preview` points at a
13
+ * declared view.
14
+ *
10
15
  * @param config - The CMSConfig (collections, singles, forms, adapters, languages, ...).
11
16
  * @returns The same config, parsed and frozen against typos.
12
17
  * @throws {ConfigValidationError} when validation fails — message aggregates all issues.
@@ -26,6 +31,7 @@ export function defineConfig(config) {
26
31
  if (!result.success) {
27
32
  throw formatConfigError(result.error);
28
33
  }
34
+ validatePreviews(config);
29
35
  return config;
30
36
  }
31
37
  /**
@@ -9,6 +9,7 @@ import type { AIAdapter } from './adapters/ai.js';
9
9
  import type { EmailAdapter } from './adapters/email.js';
10
10
  import type { ResolvedShopConfig } from '../shop/types.js';
11
11
  import type { ResolvedCmpConfig } from '../cmp/types.js';
12
+ import type { CMSModule } from './modules.js';
12
13
  export interface VideoTranscodeConfig {
13
14
  /** Enable/disable auto-transcoding on upload (default: true) */
14
15
  transcode?: boolean;
@@ -157,7 +158,12 @@ export interface CMSConfig {
157
158
  apiKeys?: ApiKeyConfig[];
158
159
  typography?: TypographyConfig;
159
160
  sidebarHelp?: boolean;
160
- shop?: ResolvedShopConfig;
161
+ /**
162
+ * Optional modules ("overlays") layered on the CMS core — shop, booking,
163
+ * etc. Each is a {@link CMSModule} (typically from `defineShop()` /
164
+ * `defineBooking()`). Renders a context in the admin switcher.
165
+ */
166
+ modules?: CMSModule[];
161
167
  cmp?: ResolvedCmpConfig;
162
168
  admin?: AdminConfig;
163
169
  }
@@ -180,4 +186,12 @@ export interface ICMS {
180
186
  shopConfig: ResolvedShopConfig | null;
181
187
  cmpConfig: ResolvedCmpConfig | null;
182
188
  adminConfig: AdminConfig | null;
189
+ /** Registered modules ("overlays"), keyed by `module.id`. */
190
+ modules: Map<string, CMSModule>;
191
+ /** Returns a registered module by id, or `undefined`. */
192
+ getModule(id: string): CMSModule | undefined;
193
+ /** Returns a registered module's `config`, narrowed to `T`, or `undefined`. */
194
+ getModuleConfig<T = unknown>(id: string): T | undefined;
195
+ /** Whether a module with the given id is registered. */
196
+ hasModule(id: string): boolean;
183
197
  }
@@ -34,6 +34,12 @@ export declare const cmsConfigSchema: z.ZodObject<{
34
34
  slug: z.ZodString;
35
35
  sidebarIcon: z.ZodOptional<z.ZodString>;
36
36
  previewUrl: z.ZodOptional<z.ZodString>;
37
+ previews: z.ZodOptional<z.ZodArray<z.ZodObject<{
38
+ name: z.ZodString;
39
+ label: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
40
+ url: z.ZodString;
41
+ default: z.ZodOptional<z.ZodBoolean>;
42
+ }, z.core.$loose>>>;
37
43
  fields: z.ZodArray<z.ZodType<FieldDef, unknown, z.core.$ZodTypeInternals<FieldDef, unknown>>>;
38
44
  layout: z.ZodOptional<z.ZodUnknown>;
39
45
  slugField: z.ZodOptional<z.ZodString>;
@@ -44,6 +50,12 @@ export declare const cmsConfigSchema: z.ZodObject<{
44
50
  slug: z.ZodString;
45
51
  sidebarIcon: z.ZodOptional<z.ZodString>;
46
52
  previewUrl: z.ZodOptional<z.ZodString>;
53
+ previews: z.ZodOptional<z.ZodArray<z.ZodObject<{
54
+ name: z.ZodString;
55
+ label: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
56
+ url: z.ZodString;
57
+ default: z.ZodOptional<z.ZodBoolean>;
58
+ }, z.core.$loose>>>;
47
59
  fields: z.ZodArray<z.ZodType<FieldDef, unknown, z.core.$ZodTypeInternals<FieldDef, unknown>>>;
48
60
  layout: z.ZodOptional<z.ZodUnknown>;
49
61
  slugField: z.ZodOptional<z.ZodString>;
@@ -145,7 +157,26 @@ export declare const cmsConfigSchema: z.ZodObject<{
145
157
  fixOrphans: z.ZodOptional<z.ZodBoolean>;
146
158
  }, z.core.$loose>>;
147
159
  sidebarHelp: z.ZodOptional<z.ZodBoolean>;
148
- shop: z.ZodOptional<z.ZodUnknown>;
160
+ modules: z.ZodOptional<z.ZodArray<z.ZodObject<{
161
+ id: z.ZodString;
162
+ label: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>;
163
+ icon: z.ZodString;
164
+ context: z.ZodObject<{
165
+ contextLabel: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
166
+ landingUrl: z.ZodString;
167
+ sections: z.ZodArray<z.ZodObject<{
168
+ key: z.ZodString;
169
+ title: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>;
170
+ url: z.ZodString;
171
+ icon: z.ZodString;
172
+ countSource: z.ZodOptional<z.ZodString>;
173
+ }, z.core.$loose>>;
174
+ surfacedCollections: z.ZodOptional<z.ZodArray<z.ZodString>>;
175
+ surfacedSingles: z.ZodOptional<z.ZodArray<z.ZodString>>;
176
+ }, z.core.$loose>;
177
+ config: z.ZodOptional<z.ZodUnknown>;
178
+ serverHooks: z.ZodOptional<z.ZodUnknown>;
179
+ }, z.core.$loose>>>;
149
180
  cmp: z.ZodOptional<z.ZodUnknown>;
150
181
  }, z.core.$loose>;
151
182
  export type CmsConfigSchema = z.infer<typeof cmsConfigSchema>;
@@ -228,6 +259,12 @@ export declare const _internal: {
228
259
  slug: z.ZodString;
229
260
  sidebarIcon: z.ZodOptional<z.ZodString>;
230
261
  previewUrl: z.ZodOptional<z.ZodString>;
262
+ previews: z.ZodOptional<z.ZodArray<z.ZodObject<{
263
+ name: z.ZodString;
264
+ label: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
265
+ url: z.ZodString;
266
+ default: z.ZodOptional<z.ZodBoolean>;
267
+ }, z.core.$loose>>>;
231
268
  fields: z.ZodArray<z.ZodType<FieldDef, unknown, z.core.$ZodTypeInternals<FieldDef, unknown>>>;
232
269
  layout: z.ZodOptional<z.ZodUnknown>;
233
270
  slugField: z.ZodOptional<z.ZodString>;
@@ -238,6 +275,12 @@ export declare const _internal: {
238
275
  slug: z.ZodString;
239
276
  sidebarIcon: z.ZodOptional<z.ZodString>;
240
277
  previewUrl: z.ZodOptional<z.ZodString>;
278
+ previews: z.ZodOptional<z.ZodArray<z.ZodObject<{
279
+ name: z.ZodString;
280
+ label: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
281
+ url: z.ZodString;
282
+ default: z.ZodOptional<z.ZodBoolean>;
283
+ }, z.core.$loose>>>;
241
284
  fields: z.ZodArray<z.ZodType<FieldDef, unknown, z.core.$ZodTypeInternals<FieldDef, unknown>>>;
242
285
  layout: z.ZodOptional<z.ZodUnknown>;
243
286
  slugField: z.ZodOptional<z.ZodString>;
@@ -314,6 +357,26 @@ export declare const _internal: {
314
357
  label: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>;
315
358
  notificationEmailAddresses: z.ZodOptional<z.ZodArray<z.ZodString>>;
316
359
  }, z.core.$loose>;
360
+ moduleSchema: z.ZodObject<{
361
+ id: z.ZodString;
362
+ label: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>;
363
+ icon: z.ZodString;
364
+ context: z.ZodObject<{
365
+ contextLabel: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
366
+ landingUrl: z.ZodString;
367
+ sections: z.ZodArray<z.ZodObject<{
368
+ key: z.ZodString;
369
+ title: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>;
370
+ url: z.ZodString;
371
+ icon: z.ZodString;
372
+ countSource: z.ZodOptional<z.ZodString>;
373
+ }, z.core.$loose>>;
374
+ surfacedCollections: z.ZodOptional<z.ZodArray<z.ZodString>>;
375
+ surfacedSingles: z.ZodOptional<z.ZodArray<z.ZodString>>;
376
+ }, z.core.$loose>;
377
+ config: z.ZodOptional<z.ZodUnknown>;
378
+ serverHooks: z.ZodOptional<z.ZodUnknown>;
379
+ }, z.core.$loose>;
317
380
  cmsConfigBaseSchema: z.ZodObject<{
318
381
  languages: z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
319
382
  code: z.ZodString;
@@ -331,6 +394,12 @@ export declare const _internal: {
331
394
  slug: z.ZodString;
332
395
  sidebarIcon: z.ZodOptional<z.ZodString>;
333
396
  previewUrl: z.ZodOptional<z.ZodString>;
397
+ previews: z.ZodOptional<z.ZodArray<z.ZodObject<{
398
+ name: z.ZodString;
399
+ label: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
400
+ url: z.ZodString;
401
+ default: z.ZodOptional<z.ZodBoolean>;
402
+ }, z.core.$loose>>>;
334
403
  fields: z.ZodArray<z.ZodType<FieldDef, unknown, z.core.$ZodTypeInternals<FieldDef, unknown>>>;
335
404
  layout: z.ZodOptional<z.ZodUnknown>;
336
405
  slugField: z.ZodOptional<z.ZodString>;
@@ -341,6 +410,12 @@ export declare const _internal: {
341
410
  slug: z.ZodString;
342
411
  sidebarIcon: z.ZodOptional<z.ZodString>;
343
412
  previewUrl: z.ZodOptional<z.ZodString>;
413
+ previews: z.ZodOptional<z.ZodArray<z.ZodObject<{
414
+ name: z.ZodString;
415
+ label: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
416
+ url: z.ZodString;
417
+ default: z.ZodOptional<z.ZodBoolean>;
418
+ }, z.core.$loose>>>;
344
419
  fields: z.ZodArray<z.ZodType<FieldDef, unknown, z.core.$ZodTypeInternals<FieldDef, unknown>>>;
345
420
  layout: z.ZodOptional<z.ZodUnknown>;
346
421
  slugField: z.ZodOptional<z.ZodString>;
@@ -442,7 +517,26 @@ export declare const _internal: {
442
517
  fixOrphans: z.ZodOptional<z.ZodBoolean>;
443
518
  }, z.core.$loose>>;
444
519
  sidebarHelp: z.ZodOptional<z.ZodBoolean>;
445
- shop: z.ZodOptional<z.ZodUnknown>;
520
+ modules: z.ZodOptional<z.ZodArray<z.ZodObject<{
521
+ id: z.ZodString;
522
+ label: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>;
523
+ icon: z.ZodString;
524
+ context: z.ZodObject<{
525
+ contextLabel: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
526
+ landingUrl: z.ZodString;
527
+ sections: z.ZodArray<z.ZodObject<{
528
+ key: z.ZodString;
529
+ title: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>;
530
+ url: z.ZodString;
531
+ icon: z.ZodString;
532
+ countSource: z.ZodOptional<z.ZodString>;
533
+ }, z.core.$loose>>;
534
+ surfacedCollections: z.ZodOptional<z.ZodArray<z.ZodString>>;
535
+ surfacedSingles: z.ZodOptional<z.ZodArray<z.ZodString>>;
536
+ }, z.core.$loose>;
537
+ config: z.ZodOptional<z.ZodUnknown>;
538
+ serverHooks: z.ZodOptional<z.ZodUnknown>;
539
+ }, z.core.$loose>>>;
446
540
  cmp: z.ZodOptional<z.ZodUnknown>;
447
541
  }, z.core.$loose>;
448
542
  FIELD_TYPES: readonly ["text", "content", "number", "boolean", "date", "datetime", "file", "media", "select", "radio", "checkboxes", "relation", "object", "array", "blocks", "slug", "seo", "shop", "url", "icon", "custom"];
@@ -344,10 +344,19 @@ const formFieldSchema = z.discriminatedUnion('type', [
344
344
  /* -------------------------------------------------------------------------- */
345
345
  /* Collections / Singles / Forms */
346
346
  /* -------------------------------------------------------------------------- */
347
+ const previewViewSchema = z
348
+ .object({
349
+ name: z.string().min(1, { message: 'preview view must have a name' }),
350
+ label: localizedSchema.optional(),
351
+ url: z.string().min(1, { message: 'preview view must have a url' }),
352
+ default: z.boolean().optional()
353
+ })
354
+ .passthrough();
347
355
  const configBaseShape = {
348
356
  slug: slugSchema,
349
357
  sidebarIcon: z.string().optional(),
350
358
  previewUrl: z.string().optional(),
359
+ previews: z.array(previewViewSchema).optional(),
351
360
  fields: z.array(fieldSchema).min(1, {
352
361
  message: 'must declare at least one field'
353
362
  }),
@@ -436,6 +445,39 @@ const apiKeySchema = z
436
445
  })
437
446
  .passthrough();
438
447
  /* -------------------------------------------------------------------------- */
448
+ /* Modules */
449
+ /* -------------------------------------------------------------------------- */
450
+ const moduleNavSectionSchema = z
451
+ .object({
452
+ key: z.string().min(1, { message: 'module nav section.key is required' }),
453
+ title: localizedSchema,
454
+ url: z.string().min(1, { message: 'module nav section.url is required' }),
455
+ icon: z.string().min(1, { message: 'module nav section.icon is required' }),
456
+ countSource: z.string().optional()
457
+ })
458
+ .passthrough();
459
+ const moduleContextSchema = z
460
+ .object({
461
+ contextLabel: localizedSchema.optional(),
462
+ landingUrl: z.string().min(1, { message: 'module context.landingUrl is required' }),
463
+ sections: z.array(moduleNavSectionSchema),
464
+ surfacedCollections: z.array(z.string()).optional(),
465
+ surfacedSingles: z.array(z.string()).optional()
466
+ })
467
+ .passthrough();
468
+ const moduleSchema = z
469
+ .object({
470
+ id: slugSchema,
471
+ label: localizedSchema,
472
+ icon: z.string().min(1, { message: 'module.icon is required' }),
473
+ context: moduleContextSchema,
474
+ // `config` holds adapter functions / resolved module config — opaque.
475
+ config: z.unknown().optional(),
476
+ // `serverHooks` holds functions — never validated, never serialized.
477
+ serverHooks: z.unknown().optional()
478
+ })
479
+ .passthrough();
480
+ /* -------------------------------------------------------------------------- */
439
481
  /* CMSConfig */
440
482
  /* -------------------------------------------------------------------------- */
441
483
  const cmsConfigBaseSchema = z
@@ -465,7 +507,7 @@ const cmsConfigBaseSchema = z
465
507
  .passthrough()
466
508
  .optional(),
467
509
  sidebarHelp: z.boolean().optional(),
468
- shop: z.unknown().optional(),
510
+ modules: z.array(moduleSchema).optional(),
469
511
  cmp: z.unknown().optional()
470
512
  })
471
513
  .passthrough();
@@ -625,6 +667,46 @@ export const cmsConfigSchema = cmsConfigBaseSchema.superRefine((data, ctx) => {
625
667
  }
626
668
  }
627
669
  });
670
+ // Module id uniqueness + surfaced collection/single references
671
+ const moduleIds = new Set();
672
+ (data.modules ?? []).forEach((m, i) => {
673
+ const mod = m;
674
+ const id = mod.id;
675
+ if (typeof id === 'string') {
676
+ if (moduleIds.has(id)) {
677
+ ctx.addIssue({
678
+ code: z.ZodIssueCode.custom,
679
+ path: ['modules', i, 'id'],
680
+ message: `duplicate module id '${id}'`
681
+ });
682
+ }
683
+ moduleIds.add(id);
684
+ }
685
+ const surfacedCollections = mod.context?.surfacedCollections;
686
+ if (Array.isArray(surfacedCollections)) {
687
+ surfacedCollections.forEach((slug, j) => {
688
+ if (!collectionSlugs.has(slug)) {
689
+ ctx.addIssue({
690
+ code: z.ZodIssueCode.custom,
691
+ path: ['modules', i, 'context', 'surfacedCollections', j],
692
+ message: `surfaced collection '${slug}' does not reference a declared collection`
693
+ });
694
+ }
695
+ });
696
+ }
697
+ const surfacedSingles = mod.context?.surfacedSingles;
698
+ if (Array.isArray(surfacedSingles)) {
699
+ surfacedSingles.forEach((slug, j) => {
700
+ if (!singleSlugs.has(slug)) {
701
+ ctx.addIssue({
702
+ code: z.ZodIssueCode.custom,
703
+ path: ['modules', i, 'context', 'surfacedSingles', j],
704
+ message: `surfaced single '${slug}' does not reference a declared single`
705
+ });
706
+ }
707
+ });
708
+ }
709
+ });
628
710
  });
629
711
  /** Exposed for tests only. Not part of the public API. */
630
712
  export const _internal = {
@@ -633,6 +715,7 @@ export const _internal = {
633
715
  collectionSchema,
634
716
  singleSchema,
635
717
  formSchema,
718
+ moduleSchema,
636
719
  cmsConfigBaseSchema,
637
720
  FIELD_TYPES,
638
721
  FORM_FIELD_TYPES,
@@ -1,10 +1,43 @@
1
1
  import type { Field } from './fields.js';
2
2
  import type { IconName } from '../admin/components/icons/icon-map.js';
3
3
  import type { Layout } from './layout.js';
4
+ import type { Localized } from './languages.js';
5
+ /**
6
+ * A named preview target. The same entry is often rendered in more than one way
7
+ * on the front end — a full page and a teaser card in a listing — and each way
8
+ * is a view.
9
+ *
10
+ * The admin loads `{url}?preview={entryId}&view={name}` in the preview iframe,
11
+ * so a project may expose one route per view, or a single route that branches
12
+ * on the `view` query param. Views do not change what is fetched from the
13
+ * database — the entry always arrives whole.
14
+ *
15
+ * @public
16
+ * @example
17
+ * ```ts
18
+ * previews: [
19
+ * { name: 'full', label: { pl: 'Pełny wpis' }, url: '/preview/blog-post' },
20
+ * { name: 'teaser', label: { pl: 'Zajawka' }, url: '/preview/blog-post/teaser' }
21
+ * ]
22
+ * ```
23
+ */
24
+ export interface PreviewView {
25
+ /** Stable identifier, referenced by `TabNode.preview`. */
26
+ name: string;
27
+ /** Switcher label. Defaults to `name`. */
28
+ label?: Localized;
29
+ /** Preview route, e.g. '/preview/blog-post/teaser'. */
30
+ url: string;
31
+ /** Start view. Defaults to the first entry of the array. */
32
+ default?: boolean;
33
+ }
4
34
  export interface ConfigBase {
5
35
  sidebarIcon?: IconName;
6
36
  slug: string;
37
+ /** Shorthand for a single preview view. Mutually exclusive with `previews`. */
7
38
  previewUrl?: string;
39
+ /** Named preview views. Mutually exclusive with `previewUrl`. */
40
+ previews?: PreviewView[];
8
41
  fields: Field[];
9
42
  layout?: Layout;
10
43
  slugField?: string;
@@ -81,6 +81,12 @@ export interface TabsNode extends LayoutNodeBase {
81
81
  export interface TabNode extends LayoutNodeBase {
82
82
  type: 'tab';
83
83
  label: Localized;
84
+ /**
85
+ * Name of a view from `previews[]`. Entering this tab switches the preview to
86
+ * that view, unless the user manually picked another view while on this tab.
87
+ * A tab without `preview` leaves the current view alone.
88
+ */
89
+ preview?: string;
84
90
  fields?: string[];
85
91
  children?: LayoutNode[];
86
92
  }
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Module system — shop, booking, and future "overlays" on the CMS core.
3
+ *
4
+ * A module is registered via `defineConfig({ modules: [...] })`. It is split
5
+ * into a **serializable descriptor** (id/label/icon/context — safe to send to
6
+ * the admin client via a remote query) and **server-only `serverHooks`**
7
+ * (init logic that must never cross the wire). The module's dashboard
8
+ * component lives in a separate client-side registry keyed by `id`, never in
9
+ * the config — Svelte components are not serializable and the config is also
10
+ * validated server-side with Zod.
11
+ *
12
+ * @see {@link CMSModule}
13
+ */
14
+ /** A label that is either a plain string or a `{ lang: text }` map. */
15
+ export type LocalizedText = string | Record<string, string>;
16
+ /**
17
+ * One sidebar entry contributed by a module's context. Icons are referenced
18
+ * by **name** (resolved client-side via the admin icon-map), never as a
19
+ * component, so the descriptor stays serializable and userland never needs an
20
+ * icon library as a dependency.
21
+ *
22
+ * @public
23
+ */
24
+ export interface ModuleNavSection {
25
+ /** Stable key for keyed rendering and active-state checks. */
26
+ key: string;
27
+ /** Display title. */
28
+ title: LocalizedText;
29
+ /** Target route, e.g. `/admin/shop/orders`. */
30
+ url: string;
31
+ /** Icon name from the admin icon-map, e.g. `shopping-cart`. */
32
+ icon: string;
33
+ /**
34
+ * Optional remote-query name returning a `number`, rendered as a badge and
35
+ * resolved reactively on the client (same pattern as collection entry
36
+ * counts). Omit for sections without a count.
37
+ */
38
+ countSource?: string;
39
+ }
40
+ /**
41
+ * The curated workspace a module contributes: a landing (dashboard) target,
42
+ * its own nav sections, and optional **surfaced** core collections/singles
43
+ * pulled into the module's context (e.g. shop surfaces the `products`
44
+ * collection as "Products").
45
+ *
46
+ * @public
47
+ */
48
+ export interface ModuleContext {
49
+ /** Switcher label for this context. Defaults to the module's `label`. */
50
+ contextLabel?: LocalizedText;
51
+ /** Where selecting this context navigates, e.g. `/admin/shop`. */
52
+ landingUrl: string;
53
+ /** Static nav sections owned by the module. */
54
+ sections: ModuleNavSection[];
55
+ /** Slugs of core collections surfaced inside this context. */
56
+ surfacedCollections?: string[];
57
+ /** Slugs of core singles surfaced inside this context. */
58
+ surfacedSingles?: string[];
59
+ }
60
+ /** Context passed to a module's `serverHooks.init`. */
61
+ export interface ModuleInitContext {
62
+ /** Raw drizzle handle (`databaseAdapter._drizzle`), or `undefined`. */
63
+ drizzle: unknown;
64
+ /** The module's own resolved config (same reference as `CMSModule.config`). */
65
+ config: unknown;
66
+ /** The CMS default language code (first configured language). */
67
+ defaultLanguage: string;
68
+ }
69
+ /**
70
+ * Server-only module hooks. NEVER serialized or sent to the admin client —
71
+ * stripped from the descriptor returned by `getModules()`.
72
+ *
73
+ * @public
74
+ */
75
+ export interface ModuleServerHooks {
76
+ /**
77
+ * Idempotent boot-time init run by `initCMS` (e.g. create indexes,
78
+ * smoke-test templates). Non-blocking: errors are logged, not thrown.
79
+ *
80
+ * Define this ONLY in a server-only module. A module whose factory (e.g.
81
+ * `defineBooking`) is re-exported from a client-safe barrel MUST NOT inline a
82
+ * server import here — bundlers create a chunk for it and break browser builds.
83
+ * Use {@link initModule} instead.
84
+ */
85
+ init?: (ctx: ModuleInitContext) => Promise<void> | void;
86
+ /**
87
+ * Bare package specifier of a SERVER-ONLY module exporting `serverInit(ctx)`,
88
+ * run by `initCMS` at boot. Prefer this over {@link init} when the module's
89
+ * factory lives in a client-safe barrel: it is a plain string (no import), so
90
+ * the client bundle stays free of server code. `initCMS` (server-only) resolves
91
+ * and imports it at runtime from node_modules. Ignored if {@link init} is set.
92
+ */
93
+ initModule?: string;
94
+ }
95
+ /**
96
+ * A CMS module ("overlay"): shop, booking, etc. Register via
97
+ * `defineConfig({ modules: [defineShop(...), defineBooking(...)] })`.
98
+ *
99
+ * @public
100
+ */
101
+ export interface CMSModule {
102
+ /** Unique slug, e.g. `shop` | `booking`. */
103
+ id: string;
104
+ /** Display label (switcher + sidebar group label). */
105
+ label: LocalizedText;
106
+ /** Icon name from the admin icon-map, e.g. `building-store`. */
107
+ icon: string;
108
+ /** The curated workspace this module contributes. */
109
+ context: ModuleContext;
110
+ /** The module's typed resolved config — opaque to the framework. */
111
+ config?: unknown;
112
+ /** Server-only hooks (not part of the serialized descriptor). */
113
+ serverHooks?: ModuleServerHooks;
114
+ }
115
+ /**
116
+ * The serializable subset of a {@link CMSModule} sent to the admin client.
117
+ * Excludes `config` (may hold adapter functions) and `serverHooks`.
118
+ *
119
+ * @public
120
+ */
121
+ export interface CMSModuleDescriptor {
122
+ id: string;
123
+ label: LocalizedText;
124
+ icon: string;
125
+ context: ModuleContext;
126
+ }
127
+ /**
128
+ * Strips a {@link CMSModule} down to its serializable {@link CMSModuleDescriptor}
129
+ * for transport to the admin client. Drops `config` and `serverHooks`.
130
+ *
131
+ * @public
132
+ */
133
+ export declare function toModuleDescriptor(module: CMSModule): CMSModuleDescriptor;