@blinkk/root-cms 3.0.1-alpha.0 → 3.0.1-alpha.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.
package/dist/cli.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  RootCMSClient,
6
6
  getCmsPlugin,
7
7
  unmarshalData
8
- } from "./chunk-YMUZ5H5C.js";
8
+ } from "./chunk-SNZ4S4IC.js";
9
9
  import "./chunk-MLKGABMK.js";
10
10
 
11
11
  // cli/cli.ts
@@ -1,7 +1,7 @@
1
1
  import { RootConfig, Plugin, Request } from '@blinkk/root';
2
2
  import { App } from 'firebase-admin/app';
3
3
  import { Firestore, WriteBatch, Timestamp, Query } from 'firebase-admin/firestore';
4
- import { C as Collection } from './schema-D7MOj-YC.js';
4
+ import { C as Collection } from './schema-rjBOZk-j.js';
5
5
 
6
6
  /**
7
7
  * Supported Root AI models. Defaults to 'gemini-3-flash-preview'.
@@ -54,6 +54,105 @@ interface CMSCheck {
54
54
  run: (ctx: CheckContext) => Promise<CheckResult>;
55
55
  }
56
56
 
57
+ /**
58
+ * Base shape for a service registered with the CMS plugin.
59
+ *
60
+ * Services are extension points that let plugins provide capabilities
61
+ * (e.g. email delivery, cache, translations) that root-cms can call into.
62
+ * Every service shares the same identifying fields — `id`, `label`, optional
63
+ * `icon` — and is registered via the `services` option on `cmsPlugin()`.
64
+ *
65
+ * Concrete service interfaces (e.g. `CMSEmailService`,
66
+ * `CMSTranslationService`) extend this base and add the handler functions
67
+ * specific to their capability.
68
+ */
69
+ interface CMSService {
70
+ /** Unique ID for the service (e.g. `'sendgrid'`, `'crowdin'`). */
71
+ id: string;
72
+ /** Human-readable label displayed in the UI (e.g. "SendGrid"). */
73
+ label: string;
74
+ /**
75
+ * Optional icon URL displayed in the UI next to the label. Similar to the
76
+ * sidebar tools `icon` option, this should be a URL to an image.
77
+ */
78
+ icon?: string;
79
+ }
80
+ /**
81
+ * Base context passed to service handler functions. Concrete services may
82
+ * extend this with additional fields specific to the call site (e.g. the
83
+ * translation context adds `docId`, `collectionId`, etc.).
84
+ */
85
+ interface CMSServiceContext {
86
+ /** The Root.js config. */
87
+ rootConfig: RootConfig;
88
+ /** The Root CMS client for accessing the database. */
89
+ cmsClient: RootCMSClient;
90
+ /**
91
+ * Email of the user that triggered the action, if any. Some service calls
92
+ * are initiated by the system rather than a user, in which case this is
93
+ * undefined.
94
+ */
95
+ user?: {
96
+ email: string;
97
+ };
98
+ }
99
+
100
+ /** Result returned by a notification service `onAction` handler. */
101
+ interface NotificationResult {
102
+ /**
103
+ * Delivery status.
104
+ * - `'success'`: the notification was delivered.
105
+ * - `'error'`: delivery failed; `message` should describe why.
106
+ * - `'info'` (default): the service handled the action but did not
107
+ * deliver a notification (e.g. the action was filtered out).
108
+ */
109
+ status?: 'success' | 'info' | 'error';
110
+ /** Optional human-readable message describing the result. */
111
+ message?: string;
112
+ }
113
+ /** Context passed to notification service handler functions. */
114
+ interface NotificationServiceContext extends CMSServiceContext {
115
+ }
116
+ /**
117
+ * Configuration for defining a CMS notification service.
118
+ *
119
+ * Notification services react to actions in the CMS (publishes, schema
120
+ * changes, comments, etc.) and dispatch them to an external channel.
121
+ * Initially this is intended for email, with Slack, webhooks, and other
122
+ * transports planned.
123
+ *
124
+ * Multiple notification services may be registered; each independently
125
+ * decides whether and how to handle a given action.
126
+ *
127
+ * Example:
128
+ * ```ts
129
+ * cmsPlugin({
130
+ * services: {
131
+ * notifications: [
132
+ * {
133
+ * id: 'sendgrid',
134
+ * label: 'SendGrid',
135
+ * onAction: async (ctx, action) => {
136
+ * if (action.action === 'doc.publish') {
137
+ * await sendgrid.send({ ... });
138
+ * }
139
+ * },
140
+ * },
141
+ * ],
142
+ * },
143
+ * });
144
+ * ```
145
+ */
146
+ interface CMSNotificationService extends CMSService {
147
+ /**
148
+ * Async function called when an action occurs in the CMS. Receives the
149
+ * action and may dispatch a notification (e.g. send email, post to Slack)
150
+ * via the service's underlying transport. Can optionally return a
151
+ * `NotificationResult` describing delivery status.
152
+ */
153
+ onAction?: (ctx: NotificationServiceContext, action: Action) => Promise<void | NotificationResult>;
154
+ }
155
+
57
156
  /** A row of translation data keyed by locale. */
58
157
  interface TranslationRow {
59
158
  /** The source string. */
@@ -64,11 +163,7 @@ interface TranslationRow {
64
163
  description?: string;
65
164
  }
66
165
  /** Context passed to translation service import/export functions. */
67
- interface TranslationServiceContext {
68
- /** The Root.js config. */
69
- rootConfig: RootConfig;
70
- /** The Root CMS client for accessing the database. */
71
- cmsClient: RootCMSClient;
166
+ interface TranslationServiceContext extends CMSServiceContext {
72
167
  /** The document ID, e.g. `Pages/index`. */
73
168
  docId: string;
74
169
  /** The collection ID, e.g. `Pages`. */
@@ -125,16 +220,7 @@ interface TranslationImportResult {
125
220
  status?: 'success' | 'info' | 'error';
126
221
  }
127
222
  /** Configuration for defining a CMS translation service. */
128
- interface CMSTranslationService {
129
- /** Unique ID for the translation service. */
130
- id: string;
131
- /** Human-readable label displayed in the UI (e.g. "Crowdin"). */
132
- label: string;
133
- /**
134
- * Optional icon URL displayed in the UI next to the label. Similar to the
135
- * sidebar tools `icon` option, this should be a URL to an image.
136
- */
137
- icon?: string;
223
+ interface CMSTranslationService extends CMSService {
138
224
  /**
139
225
  * Async function to import translations from the service. Should return an
140
226
  * array of translation rows that will be merged into the CMS translations
@@ -166,7 +252,7 @@ declare function translationsCheck(options?: TranslationsCheckOptions): CMSCheck
166
252
  /**
167
253
  * Built-in sidebar tools that can be toggled on/off in the CMS UI.
168
254
  */
169
- type CMSBuiltInSidebarTool = 'home' | 'content' | 'releases' | 'data' | 'assets' | 'translations' | 'ai' | 'settings';
255
+ type CMSBuiltInSidebarTool = 'home' | 'content' | 'tasks' | 'releases' | 'data' | 'assets' | 'translations' | 'ai' | 'settings';
170
256
  interface CMSUser {
171
257
  email: string;
172
258
  }
@@ -367,6 +453,29 @@ type CMSPluginOptions = {
367
453
  * ```
368
454
  */
369
455
  translations?: CMSTranslationService[];
456
+ /**
457
+ * Notification services that react to CMS actions and dispatch them to
458
+ * external channels (email, Slack, webhooks, etc.). Each service defines
459
+ * an optional server-side `onAction` handler.
460
+ *
461
+ * Example:
462
+ * ```ts
463
+ * cmsPlugin({
464
+ * notifications: [
465
+ * {
466
+ * id: 'sendgrid',
467
+ * label: 'SendGrid',
468
+ * onAction: async (ctx, action) => {
469
+ * if (action.action === 'doc.publish') {
470
+ * await sendgrid.send({ ... });
471
+ * }
472
+ * },
473
+ * },
474
+ * ],
475
+ * });
476
+ * ```
477
+ */
478
+ notifications?: CMSNotificationService[];
370
479
  };
371
480
  type CMSPlugin = Plugin & {
372
481
  name: 'root-cms';
@@ -1256,4 +1365,4 @@ declare class BatchResponse {
1256
1365
  private getTranslationsMap;
1257
1366
  }
1258
1367
 
1259
- export { type CMSAIConfig as $, type Action as A, type BatchRequestOptions as B, type CronUnit as C, type DocMode as D, type TranslationsDoc as E, BatchRequest as F, type GetDocOptions as G, type HttpMethod as H, BatchResponse as I, type Locale as J, type SourceString as K, type LoadTranslationsOptions as L, type TranslatedString as M, type TranslationsDocMode as N, type TranslationsLocaleDocHashMap as O, type TranslationsLocaleDocEntry as P, type MultiLocaleTranslationsMap as Q, RootCMSClient as R, type SetDocOptions as S, type TranslationsMap as T, type UserRole as U, type SingleLocaleTranslationsMap as V, TranslationsManager as W, buildTranslationsDbPath as X, buildTranslationsLocaleDocDbPath as Y, type CMSBuiltInSidebarTool as Z, type CMSUser as _, type LocaleTranslations as a, type CMSSidebarTool as a0, type CMSPluginOptions as a1, type CMSPlugin as a2, cmsPlugin as a3, type CMSCheck as a4, type CheckResult as a5, type CheckContext as a6, type CheckStatus as a7, translationsCheck as a8, type TranslationsCheckOptions as a9, type CMSTranslationService as aa, type TranslationExportResult as ab, type TranslationImportResult as ac, type TranslationRow as ad, type TranslationServiceContext as ae, type Doc as b, type DataSourceCron as c, type DataSource as d, type DataSourceData as e, type DataSourceMode as f, type SaveDraftOptions as g, type UpdateDraftOptions as h, type ListDocsOptions as i, type GetCountOptions as j, type Translation as k, type Release as l, type ListActionsOptions as m, isRichTextData as n, getCmsPlugin as o, marshalData as p, normalizeData as q, type ArrayObject as r, marshalArray as s, toArrayObject as t, unmarshalData as u, unmarshalArray as v, translationsForLocale as w, parseDocId as x, type BatchRequestQuery as y, type BatchRequestQueryOptions as z };
1368
+ export { type CMSAIConfig as $, type Action as A, type BatchRequestOptions as B, type CronUnit as C, type DocMode as D, type TranslationsDoc as E, BatchRequest as F, type GetDocOptions as G, type HttpMethod as H, BatchResponse as I, type Locale as J, type SourceString as K, type LoadTranslationsOptions as L, type TranslatedString as M, type TranslationsDocMode as N, type TranslationsLocaleDocHashMap as O, type TranslationsLocaleDocEntry as P, type MultiLocaleTranslationsMap as Q, RootCMSClient as R, type SetDocOptions as S, type TranslationsMap as T, type UserRole as U, type SingleLocaleTranslationsMap as V, TranslationsManager as W, buildTranslationsDbPath as X, buildTranslationsLocaleDocDbPath as Y, type CMSBuiltInSidebarTool as Z, type CMSUser as _, type LocaleTranslations as a, type CMSSidebarTool as a0, type CMSPluginOptions as a1, type CMSPlugin as a2, cmsPlugin as a3, type CMSCheck as a4, type CheckResult as a5, type CheckContext as a6, type CheckStatus as a7, translationsCheck as a8, type TranslationsCheckOptions as a9, type CMSService as aa, type CMSServiceContext as ab, type CMSNotificationService as ac, type NotificationResult as ad, type NotificationServiceContext as ae, type CMSTranslationService as af, type TranslationExportResult as ag, type TranslationImportResult as ah, type TranslationRow as ai, type TranslationServiceContext as aj, type Doc as b, type DataSourceCron as c, type DataSource as d, type DataSourceData as e, type DataSourceMode as f, type SaveDraftOptions as g, type UpdateDraftOptions as h, type ListDocsOptions as i, type GetCountOptions as j, type Translation as k, type Release as l, type ListActionsOptions as m, isRichTextData as n, getCmsPlugin as o, marshalData as p, normalizeData as q, type ArrayObject as r, marshalArray as s, toArrayObject as t, unmarshalData as u, unmarshalArray as v, translationsForLocale as w, parseDocId as x, type BatchRequestQuery as y, type BatchRequestQueryOptions as z };
package/dist/client.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import '@blinkk/root';
2
2
  import 'firebase-admin/app';
3
3
  import 'firebase-admin/firestore';
4
- export { A as Action, r as ArrayObject, F as BatchRequest, B as BatchRequestOptions, y as BatchRequestQuery, z as BatchRequestQueryOptions, I as BatchResponse, C as CronUnit, d as DataSource, c as DataSourceCron, e as DataSourceData, f as DataSourceMode, b as Doc, D as DocMode, j as GetCountOptions, G as GetDocOptions, H as HttpMethod, m as ListActionsOptions, i as ListDocsOptions, L as LoadTranslationsOptions, a as LocaleTranslations, l as Release, R as RootCMSClient, g as SaveDraftOptions, S as SetDocOptions, k as Translation, E as TranslationsDoc, T as TranslationsMap, h as UpdateDraftOptions, U as UserRole, o as getCmsPlugin, n as isRichTextData, s as marshalArray, p as marshalData, q as normalizeData, x as parseDocId, t as toArrayObject, w as translationsForLocale, v as unmarshalArray, u as unmarshalData } from './client-cP6yMgT8.js';
5
- import './schema-D7MOj-YC.js';
4
+ export { A as Action, r as ArrayObject, F as BatchRequest, B as BatchRequestOptions, y as BatchRequestQuery, z as BatchRequestQueryOptions, I as BatchResponse, C as CronUnit, d as DataSource, c as DataSourceCron, e as DataSourceData, f as DataSourceMode, b as Doc, D as DocMode, j as GetCountOptions, G as GetDocOptions, H as HttpMethod, m as ListActionsOptions, i as ListDocsOptions, L as LoadTranslationsOptions, a as LocaleTranslations, l as Release, R as RootCMSClient, g as SaveDraftOptions, S as SetDocOptions, k as Translation, E as TranslationsDoc, T as TranslationsMap, h as UpdateDraftOptions, U as UserRole, o as getCmsPlugin, n as isRichTextData, s as marshalArray, p as marshalData, q as normalizeData, x as parseDocId, t as toArrayObject, w as translationsForLocale, v as unmarshalArray, u as unmarshalData } from './client-DdB4xpM6.js';
5
+ import './schema-rjBOZk-j.js';
package/dist/client.js CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  translationsForLocale,
13
13
  unmarshalArray,
14
14
  unmarshalData
15
- } from "./chunk-YMUZ5H5C.js";
15
+ } from "./chunk-SNZ4S4IC.js";
16
16
  import "./chunk-MLKGABMK.js";
17
17
  export {
18
18
  BatchRequest,
package/dist/core.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { R as RootCMSClient, D as DocMode, L as LoadTranslationsOptions, T as TranslationsMap, a as LocaleTranslations } from './client-cP6yMgT8.js';
2
- export { A as Action, r as ArrayObject, F as BatchRequest, B as BatchRequestOptions, y as BatchRequestQuery, z as BatchRequestQueryOptions, I as BatchResponse, C as CronUnit, d as DataSource, c as DataSourceCron, e as DataSourceData, f as DataSourceMode, b as Doc, j as GetCountOptions, G as GetDocOptions, H as HttpMethod, m as ListActionsOptions, i as ListDocsOptions, J as Locale, Q as MultiLocaleTranslationsMap, l as Release, g as SaveDraftOptions, S as SetDocOptions, V as SingleLocaleTranslationsMap, K as SourceString, M as TranslatedString, k as Translation, E as TranslationsDoc, N as TranslationsDocMode, P as TranslationsLocaleDocEntry, O as TranslationsLocaleDocHashMap, W as TranslationsManager, h as UpdateDraftOptions, U as UserRole, X as buildTranslationsDbPath, Y as buildTranslationsLocaleDocDbPath, o as getCmsPlugin, n as isRichTextData, s as marshalArray, p as marshalData, q as normalizeData, x as parseDocId, t as toArrayObject, w as translationsForLocale, v as unmarshalArray, u as unmarshalData } from './client-cP6yMgT8.js';
1
+ import { R as RootCMSClient, D as DocMode, L as LoadTranslationsOptions, T as TranslationsMap, a as LocaleTranslations } from './client-DdB4xpM6.js';
2
+ export { A as Action, r as ArrayObject, F as BatchRequest, B as BatchRequestOptions, y as BatchRequestQuery, z as BatchRequestQueryOptions, I as BatchResponse, C as CronUnit, d as DataSource, c as DataSourceCron, e as DataSourceData, f as DataSourceMode, b as Doc, j as GetCountOptions, G as GetDocOptions, H as HttpMethod, m as ListActionsOptions, i as ListDocsOptions, J as Locale, Q as MultiLocaleTranslationsMap, l as Release, g as SaveDraftOptions, S as SetDocOptions, V as SingleLocaleTranslationsMap, K as SourceString, M as TranslatedString, k as Translation, E as TranslationsDoc, N as TranslationsDocMode, P as TranslationsLocaleDocEntry, O as TranslationsLocaleDocHashMap, W as TranslationsManager, h as UpdateDraftOptions, U as UserRole, X as buildTranslationsDbPath, Y as buildTranslationsLocaleDocDbPath, o as getCmsPlugin, n as isRichTextData, s as marshalArray, p as marshalData, q as normalizeData, x as parseDocId, t as toArrayObject, w as translationsForLocale, v as unmarshalArray, u as unmarshalData } from './client-DdB4xpM6.js';
3
3
  import { Request, RootConfig, Response, RouteParams, GetStaticProps, GetStaticPaths } from '@blinkk/root';
4
4
  import { Query } from 'firebase-admin/firestore';
5
- export { s as schema } from './schema-D7MOj-YC.js';
5
+ export { s as schema } from './schema-rjBOZk-j.js';
6
6
  import 'firebase-admin/app';
7
7
 
8
8
  /**
package/dist/core.js CHANGED
@@ -16,7 +16,7 @@ import {
16
16
  translationsForLocale,
17
17
  unmarshalArray,
18
18
  unmarshalData
19
- } from "./chunk-YMUZ5H5C.js";
19
+ } from "./chunk-SNZ4S4IC.js";
20
20
  import {
21
21
  __export
22
22
  } from "./chunk-MLKGABMK.js";
@@ -480,6 +480,7 @@ __export(schema_exports, {
480
480
  datetime: () => datetime,
481
481
  define: () => define,
482
482
  defineCollection: () => defineCollection,
483
+ definePreset: () => definePreset,
483
484
  defineSchema: () => defineSchema,
484
485
  file: () => file,
485
486
  glob: () => glob,
@@ -488,6 +489,7 @@ __export(schema_exports, {
488
489
  number: () => number,
489
490
  object: () => object,
490
491
  oneOf: () => oneOf,
492
+ preset: () => preset,
491
493
  reference: () => reference,
492
494
  references: () => references,
493
495
  richtext: () => richtext,
@@ -560,6 +562,10 @@ function defineSchema(schema) {
560
562
  return schema;
561
563
  }
562
564
  var define = defineSchema;
565
+ function definePreset(preset2) {
566
+ return preset2;
567
+ }
568
+ var preset = definePreset;
563
569
  function defineCollection(collection2) {
564
570
  return collection2;
565
571
  }
package/dist/functions.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  runCronJobs
3
- } from "./chunk-H6ZKML2S.js";
4
- import "./chunk-YMUZ5H5C.js";
3
+ } from "./chunk-UTSL2E2P.js";
4
+ import "./chunk-SNZ4S4IC.js";
5
5
  import "./chunk-MLKGABMK.js";
6
6
 
7
7
  // core/functions.ts
package/dist/plugin.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import '@blinkk/root';
2
2
  import 'firebase-admin/app';
3
3
  import 'firebase-admin/firestore';
4
- export { $ as CMSAIConfig, Z as CMSBuiltInSidebarTool, a4 as CMSCheck, a2 as CMSPlugin, a1 as CMSPluginOptions, a0 as CMSSidebarTool, aa as CMSTranslationService, _ as CMSUser, a6 as CheckContext, a5 as CheckResult, a7 as CheckStatus, ab as TranslationExportResult, ac as TranslationImportResult, ad as TranslationRow, ae as TranslationServiceContext, a9 as TranslationsCheckOptions, a3 as cmsPlugin, a8 as translationsCheck } from './client-cP6yMgT8.js';
5
- import './schema-D7MOj-YC.js';
4
+ export { $ as CMSAIConfig, Z as CMSBuiltInSidebarTool, a4 as CMSCheck, ac as CMSNotificationService, a2 as CMSPlugin, a1 as CMSPluginOptions, aa as CMSService, ab as CMSServiceContext, a0 as CMSSidebarTool, af as CMSTranslationService, _ as CMSUser, a6 as CheckContext, a5 as CheckResult, a7 as CheckStatus, ad as NotificationResult, ae as NotificationServiceContext, ag as TranslationExportResult, ah as TranslationImportResult, ai as TranslationRow, aj as TranslationServiceContext, a9 as TranslationsCheckOptions, a3 as cmsPlugin, a8 as translationsCheck } from './client-DdB4xpM6.js';
5
+ import './schema-rjBOZk-j.js';
package/dist/plugin.js CHANGED
@@ -4,15 +4,16 @@ import {
4
4
  } from "./chunk-T5UK2H24.js";
5
5
  import {
6
6
  getServerVersion
7
- } from "./chunk-N4Z3O53K.js";
7
+ } from "./chunk-BBOESYH7.js";
8
8
  import {
9
+ SearchIndexService,
9
10
  runCronJobs
10
- } from "./chunk-H6ZKML2S.js";
11
+ } from "./chunk-UTSL2E2P.js";
11
12
  import {
12
13
  RootCMSClient,
13
14
  parseDocId,
14
15
  unmarshalData
15
- } from "./chunk-YMUZ5H5C.js";
16
+ } from "./chunk-SNZ4S4IC.js";
16
17
  import "./chunk-MLKGABMK.js";
17
18
 
18
19
  // core/plugin.ts
@@ -160,13 +161,96 @@ function api(server, options) {
160
161
  });
161
162
  server.use("/cms/api/cron.run", async (req, res) => {
162
163
  try {
163
- await runCronJobs(req.rootConfig);
164
+ await runCronJobs(req.rootConfig, {
165
+ loadSchema: (collectionId) => getCollectionSchema(req, collectionId)
166
+ });
164
167
  res.status(200).json({ success: true });
165
168
  } catch (err) {
166
169
  console.error(err);
167
170
  res.status(500).json({ success: false });
168
171
  }
169
172
  });
173
+ const searchRebuildJobs = /* @__PURE__ */ new Map();
174
+ server.use("/cms/api/search.query", async (req, res) => {
175
+ if (req.method !== "POST" || !String(req.get("content-type")).startsWith("application/json")) {
176
+ res.status(400).json({ success: false, error: "BAD_REQUEST" });
177
+ return;
178
+ }
179
+ if (!req.user?.email) {
180
+ res.status(401).json({ success: false, error: "UNAUTHORIZED" });
181
+ return;
182
+ }
183
+ const body = req.body || {};
184
+ const q = typeof body.q === "string" ? body.q : "";
185
+ const limit = typeof body.limit === "number" ? body.limit : 25;
186
+ try {
187
+ const service = new SearchIndexService(req.rootConfig);
188
+ const result = await service.search(q, { limit });
189
+ res.setHeader("Cache-Control", "no-store");
190
+ res.status(200).json({ success: true, ...result });
191
+ } catch (err) {
192
+ console.error(err.stack || err);
193
+ res.status(500).json({ success: false, error: "UNKNOWN" });
194
+ }
195
+ });
196
+ server.use("/cms/api/search.status", async (req, res) => {
197
+ if (!req.user?.email) {
198
+ res.status(401).json({ success: false, error: "UNAUTHORIZED" });
199
+ return;
200
+ }
201
+ try {
202
+ const cmsClient = new RootCMSClient(req.rootConfig);
203
+ const service = new SearchIndexService(req.rootConfig);
204
+ const status = await service.getStatus();
205
+ const running = searchRebuildJobs.has(cmsClient.projectId);
206
+ res.setHeader("Cache-Control", "no-store");
207
+ res.status(200).json({ success: true, status, running });
208
+ } catch (err) {
209
+ console.error(err.stack || err);
210
+ res.status(500).json({ success: false, error: "UNKNOWN" });
211
+ }
212
+ });
213
+ server.use("/cms/api/search.rebuild", async (req, res) => {
214
+ if (req.method !== "POST") {
215
+ res.status(400).json({ success: false, error: "BAD_REQUEST" });
216
+ return;
217
+ }
218
+ if (!req.user?.email) {
219
+ res.status(401).json({ success: false, error: "UNAUTHORIZED" });
220
+ return;
221
+ }
222
+ const cmsClient = new RootCMSClient(req.rootConfig);
223
+ try {
224
+ const role = await cmsClient.getUserRole(req.user.email);
225
+ if (role !== "ADMIN") {
226
+ res.status(403).json({ success: false, error: "FORBIDDEN" });
227
+ return;
228
+ }
229
+ } catch (err) {
230
+ console.error(err.stack || err);
231
+ res.status(500).json({ success: false, error: "UNKNOWN" });
232
+ return;
233
+ }
234
+ const body = req.body || {};
235
+ const force = !!body.force;
236
+ const projectId = cmsClient.projectId;
237
+ if (searchRebuildJobs.has(projectId)) {
238
+ res.status(202).json({ success: true, alreadyRunning: true });
239
+ return;
240
+ }
241
+ const service = new SearchIndexService(
242
+ req.rootConfig,
243
+ (collectionId) => getCollectionSchema(req, collectionId)
244
+ );
245
+ const job = service.rebuildIndex({ force }).catch((err) => {
246
+ console.error("search.rebuild failed:", err.stack || err);
247
+ throw err;
248
+ }).finally(() => {
249
+ searchRebuildJobs.delete(projectId);
250
+ });
251
+ searchRebuildJobs.set(projectId, job);
252
+ res.status(202).json({ success: true, started: true, force });
253
+ });
170
254
  server.use("/cms/api/csv.download", (req, res) => {
171
255
  if (req.method !== "POST" || !String(req.get("content-type")).startsWith("application/json")) {
172
256
  res.status(400).json({ success: false, error: "BAD_REQUEST" });
package/dist/project.d.ts CHANGED
@@ -1,10 +1,10 @@
1
- import { S as Schema, C as Collection } from './schema-D7MOj-YC.js';
1
+ import { S as Schema, C as Collection } from './schema-rjBOZk-j.js';
2
2
 
3
3
  /**
4
4
  * Loads various files or configurations from the project.
5
5
  *
6
6
  * NOTE: This file needs to be loaded through vite's ssrLoadModule so that
7
- * `import.meta.glob()` calls are resolved.
7
+ * virtual modules are resolved.
8
8
  */
9
9
 
10
10
  interface SchemaModule {
package/dist/project.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  getCollectionSchema,
4
4
  getProjectSchemas,
5
5
  resolveOneOfPatterns
6
- } from "./chunk-MSJGHSR6.js";
6
+ } from "./chunk-R4LKO3EZ.js";
7
7
  import "./chunk-MLKGABMK.js";
8
8
  export {
9
9
  SCHEMA_MODULES,
@@ -174,6 +174,13 @@ interface SchemaPattern {
174
174
  }
175
175
  type OneOfField = CommonFieldProps & {
176
176
  type: 'oneof';
177
+ /**
178
+ * UI variant for the oneOf field.
179
+ * - `dropdown` (default): A `Select` dropdown for choosing the type.
180
+ * - `picker`: A `Button` that opens a modal with searchable cards. Use this
181
+ * variant when types declare presets or thumbnail images.
182
+ */
183
+ variant?: 'dropdown' | 'picker';
177
184
  /**
178
185
  * Schema types to include in the oneOf field. Can be:
179
186
  * - An array of Schema objects
@@ -226,6 +233,25 @@ type Field = StringField | NumberField | DateField | DateTimeField | BooleanFiel
226
233
  */
227
234
  type FieldWithId = Field;
228
235
  type ObjectLikeField = ImageField | FileField | ObjectField | OneOfField | ReferenceField;
236
+ interface SchemaPreset<T = Record<string, any>> {
237
+ /** Unique identifier for the preset within the schema. */
238
+ id: string;
239
+ /** Display title for the preset. Defaults to the preset id. */
240
+ label?: string;
241
+ /** Description shown below the title in the picker. */
242
+ description?: string;
243
+ /** Thumbnail image (URL or absolute /-path) for the picker. */
244
+ image?: string;
245
+ /**
246
+ * Field values to prefill. Deep-merged into the schema's default value
247
+ * (preset values override defaults). `_type` is set automatically. The
248
+ * preset id is NOT persisted — presets are purely a prefill mechanism.
249
+ *
250
+ * Pass a generated fields type as the generic parameter `T` to
251
+ * type-check the keys against the schema's fields.
252
+ */
253
+ data?: T;
254
+ }
229
255
  interface Schema {
230
256
  /** The name of the content type. Used as the field key. */
231
257
  name: string;
@@ -233,6 +259,19 @@ interface Schema {
233
259
  label?: string;
234
260
  /** The description of the content type. Appears in CMS menus. */
235
261
  description?: string;
262
+ /**
263
+ * Optional thumbnail image (URL or absolute /-path) used to represent this
264
+ * schema in the picker UI for `oneof` fields with `variant: 'picker'`.
265
+ */
266
+ image?: string;
267
+ /**
268
+ * Optional preset prefill configurations for use in the picker UI.
269
+ *
270
+ * Each preset appears in the picker as a separate selectable card alongside
271
+ * the schema's "blank" card. Selecting a preset sets `_type` to the schema's
272
+ * `name` and merges `data` over the schema's default field values.
273
+ */
274
+ presets?: SchemaPreset<any>[];
236
275
  /** Fields describe the structure of the content. */
237
276
  fields: FieldWithId[];
238
277
  /** Defines the preview displayed within the CMS UI. Overrides the `preview` definition for the `array` field. */
@@ -267,6 +306,24 @@ type SchemaWithTypes = Schema & {
267
306
  declare function defineSchema(schema: Schema): Schema;
268
307
  /** Defines the schema for a collection or reusable component. */
269
308
  declare const define: typeof defineSchema;
309
+ /**
310
+ * Defines a preset for use within `schema.define({presets: [...]})`. The
311
+ * optional generic parameter `T` constrains the shape of the preset's `data`
312
+ * field. Pass an auto-generated fields type to enable type-safety:
313
+ *
314
+ * ```ts
315
+ * import type {HeroFields} from './generated/types.d.ts';
316
+ *
317
+ * schema.definePreset<HeroFields>({
318
+ * id: 'big',
319
+ * label: 'Big hero',
320
+ * data: {title: 'Welcome'},
321
+ * });
322
+ * ```
323
+ */
324
+ declare function definePreset<T = Record<string, any>>(preset: SchemaPreset<T>): SchemaPreset<T>;
325
+ /** Alias for {@link definePreset}. */
326
+ declare const preset: typeof definePreset;
270
327
  type Collection = SchemaWithTypes & {
271
328
  /**
272
329
  * The ID of the collection. This comes from the schema filename, e.g
@@ -410,6 +467,7 @@ type schema_ReferencesField = ReferencesField;
410
467
  type schema_RichTextField = RichTextField;
411
468
  type schema_Schema = Schema;
412
469
  type schema_SchemaPattern = SchemaPattern;
470
+ type schema_SchemaPreset<T = Record<string, any>> = SchemaPreset<T>;
413
471
  type schema_SchemaWithTypes = SchemaWithTypes;
414
472
  type schema_SelectField = SelectField;
415
473
  type schema_StringField = StringField;
@@ -420,6 +478,7 @@ declare const schema_date: typeof date;
420
478
  declare const schema_datetime: typeof datetime;
421
479
  declare const schema_define: typeof define;
422
480
  declare const schema_defineCollection: typeof defineCollection;
481
+ declare const schema_definePreset: typeof definePreset;
423
482
  declare const schema_defineSchema: typeof defineSchema;
424
483
  declare const schema_file: typeof file;
425
484
  declare const schema_glob: typeof glob;
@@ -428,13 +487,14 @@ declare const schema_multiselect: typeof multiselect;
428
487
  declare const schema_number: typeof number;
429
488
  declare const schema_object: typeof object;
430
489
  declare const schema_oneOf: typeof oneOf;
490
+ declare const schema_preset: typeof preset;
431
491
  declare const schema_reference: typeof reference;
432
492
  declare const schema_references: typeof references;
433
493
  declare const schema_richtext: typeof richtext;
434
494
  declare const schema_select: typeof select;
435
495
  declare const schema_string: typeof string;
436
496
  declare namespace schema {
437
- export { type schema_ArrayField as ArrayField, type schema_BooleanField as BooleanField, type schema_Collection as Collection, type schema_CommonFieldProps as CommonFieldProps, type schema_DateField as DateField, type schema_DateTimeField as DateTimeField, type schema_Field as Field, type schema_FieldWithId as FieldWithId, type schema_FileField as FileField, type schema_GlobOptions as GlobOptions, type schema_ImageField as ImageField, type schema_MultiSelectField as MultiSelectField, type schema_NumberField as NumberField, type schema_ObjectField as ObjectField, type schema_ObjectLikeField as ObjectLikeField, type schema_OneOfField as OneOfField, type schema_ReferenceField as ReferenceField, type schema_ReferencesField as ReferencesField, type schema_RichTextField as RichTextField, type schema_Schema as Schema, type schema_SchemaPattern as SchemaPattern, type schema_SchemaWithTypes as SchemaWithTypes, type schema_SelectField as SelectField, type schema_StringField as StringField, schema_array as array, schema_boolean as boolean, schema_collection as collection, schema_date as date, schema_datetime as datetime, schema_define as define, schema_defineCollection as defineCollection, schema_defineSchema as defineSchema, schema_file as file, schema_glob as glob, schema_image as image, schema_multiselect as multiselect, schema_number as number, schema_object as object, schema_oneOf as oneOf, schema_reference as reference, schema_references as references, schema_richtext as richtext, schema_select as select, schema_string as string };
497
+ export { type schema_ArrayField as ArrayField, type schema_BooleanField as BooleanField, type schema_Collection as Collection, type schema_CommonFieldProps as CommonFieldProps, type schema_DateField as DateField, type schema_DateTimeField as DateTimeField, type schema_Field as Field, type schema_FieldWithId as FieldWithId, type schema_FileField as FileField, type schema_GlobOptions as GlobOptions, type schema_ImageField as ImageField, type schema_MultiSelectField as MultiSelectField, type schema_NumberField as NumberField, type schema_ObjectField as ObjectField, type schema_ObjectLikeField as ObjectLikeField, type schema_OneOfField as OneOfField, type schema_ReferenceField as ReferenceField, type schema_ReferencesField as ReferencesField, type schema_RichTextField as RichTextField, type schema_Schema as Schema, type schema_SchemaPattern as SchemaPattern, type schema_SchemaPreset as SchemaPreset, type schema_SchemaWithTypes as SchemaWithTypes, type schema_SelectField as SelectField, type schema_StringField as StringField, schema_array as array, schema_boolean as boolean, schema_collection as collection, schema_date as date, schema_datetime as datetime, schema_define as define, schema_defineCollection as defineCollection, schema_definePreset as definePreset, schema_defineSchema as defineSchema, schema_file as file, schema_glob as glob, schema_image as image, schema_multiselect as multiselect, schema_number as number, schema_object as object, schema_oneOf as oneOf, schema_preset as preset, schema_reference as reference, schema_references as references, schema_richtext as richtext, schema_select as select, schema_string as string };
438
498
  }
439
499
 
440
500
  export { type Collection as C, type Schema as S, schema as s };