@blinkk/root-cms 2.4.10 → 2.5.2

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.
@@ -5,6 +5,11 @@ import {
5
5
  Timestamp as Timestamp2
6
6
  } from "firebase-admin/firestore";
7
7
 
8
+ // shared/slug.ts
9
+ function normalizeSlug(slug) {
10
+ return slug.replace(/^[\s/]*/g, "").replace(/[\s/]*$/g, "").replace(/^\/+|\/+$/g, "").replaceAll("/", "--");
11
+ }
12
+
8
13
  // core/translations-manager.ts
9
14
  import {
10
15
  FieldValue,
@@ -350,7 +355,7 @@ function buildTranslationsLocaleDocDbPath(options) {
350
355
  return TRANSLATIONS_LOCALE_DOC_DB_PATH_FORMAT.replace(
351
356
  "{project}",
352
357
  options.project
353
- ).replace("{mode}", options.mode).replace("{id}", options.id.replaceAll("/", "--")).replace("{locale}", options.locale);
358
+ ).replace("{mode}", options.mode).replace("{id}", normalizeSlug(options.id)).replace("{locale}", options.locale);
354
359
  }
355
360
 
356
361
  // core/validation.ts
@@ -675,6 +680,12 @@ var RootCMSClient = class {
675
680
  this.app = this.cmsPlugin.getFirebaseApp();
676
681
  this.db = this.cmsPlugin.getFirestore();
677
682
  }
683
+ /**
684
+ * Converts a doc mode to the Firestore collection name.
685
+ */
686
+ getModeCollection(mode) {
687
+ return mode === "draft" ? "Drafts" : "Published";
688
+ }
678
689
  /**
679
690
  * Retrieves doc data from Root.js CMS.
680
691
  */
@@ -690,9 +701,14 @@ var RootCMSClient = class {
690
701
  * what you are doing.
691
702
  */
692
703
  async getRawDoc(collectionId, slug, options) {
693
- const mode = options.mode;
694
- const modeCollection = mode === "draft" ? "Drafts" : "Published";
695
- slug = slug.replaceAll("/", "--");
704
+ if (!collectionId) {
705
+ throw new Error("collectionId is required");
706
+ }
707
+ if (!slug) {
708
+ throw new Error("slug is required");
709
+ }
710
+ const modeCollection = this.getModeCollection(options.mode);
711
+ slug = normalizeSlug(slug);
696
712
  const dbPath = `Projects/${this.projectId}/Collections/${collectionId}/${modeCollection}/${slug}`;
697
713
  const docRef = this.db.doc(dbPath);
698
714
  const doc = await docRef.get();
@@ -720,7 +736,7 @@ var RootCMSClient = class {
720
736
  */
721
737
  dbDocPath(collectionId, slug, options) {
722
738
  const collectionDocsPath = this.dbCollectionDocsPath(collectionId, options);
723
- const normalizedSlug = slug.replaceAll("/", "--");
739
+ const normalizedSlug = normalizeSlug(slug);
724
740
  return `${collectionDocsPath}/${normalizedSlug}`;
725
741
  }
726
742
  /**
@@ -801,12 +817,98 @@ ${errorMessages}`);
801
817
  });
802
818
  }
803
819
  /**
804
- * Prefer `saveDraftData('Pages/foo', data)`. Only use this if you know what
805
- * you're doing.
820
+ * Sets the raw document data directly in Firestore.
821
+ *
822
+ * CAUTION Prefer using `saveDraftData('Pages/foo', data)` in most cases.
823
+ * Only use this method if you need to manipulate system-level (sys) fields
824
+ * directly or if you're implementing low-level data operations.
825
+ *
826
+ * ## Validation & Normalization
827
+ *
828
+ * This method automatically validates and normalizes `sys` fields to prevent
829
+ * data integrity issues that can cause runtime errors like
830
+ * "e.toMillis is not a function".
831
+ *
832
+ * ### Timestamp Fields (auto-converted)
833
+ * The following fields accept multiple formats and are automatically converted
834
+ * to Firestore Timestamp objects:
835
+ * - `sys.createdAt`
836
+ * - `sys.modifiedAt`
837
+ * - `sys.publishedAt`
838
+ * - `sys.firstPublishedAt`
839
+ *
840
+ * Accepted formats:
841
+ * - Firestore `Timestamp` object (unchanged)
842
+ * - `number` - Interpreted as milliseconds since epoch, converted to Timestamp
843
+ * - `Date` object - Converted to Timestamp
844
+ *
845
+ * ### Required Fields (auto-populated with defaults if missing)
846
+ * - `sys.createdAt` - Defaults to current time if not provided
847
+ * - `sys.modifiedAt` - Defaults to current time if not provided
848
+ * - `sys.createdBy` - Defaults to 'root-cms-client' if not provided
849
+ * - `sys.modifiedBy` - Defaults to 'root-cms-client' if not provided
850
+ * - `sys.locales` - Defaults to ['en'] if not provided
851
+ *
852
+ * ### Optional Fields (validated if present)
853
+ * - `sys.publishedBy` - String identifier
854
+ * - `sys.firstPublishedBy` - String identifier
855
+ * - `sys.publishingLocked` - Object with optional `until` Timestamp
856
+ *
857
+ * ### Document Identity
858
+ * The `id`, `collection`, and `slug` fields are always set to match the
859
+ * provided parameters, overwriting any existing values to prevent data
860
+ * inconsistencies.
861
+ *
862
+ * @param collectionId - The collection ID (e.g., 'Pages')
863
+ * @param slug - The document slug (e.g., 'home')
864
+ * @param data - The complete document data including sys and fields
865
+ * @param options - Options specifying mode ('draft' or 'published')
866
+ *
867
+ * @throws {Error} If sys fields are invalid or missing required fields
868
+ * @throws {Error} If timestamp fields have invalid types
869
+ *
870
+ * @example
871
+ * ```typescript
872
+ * // Minimal example - sys fields use defaults
873
+ * await client.setRawDoc('Pages', 'home', {
874
+ * sys: {}, // All sys fields will be auto-populated with defaults
875
+ * fields: {
876
+ * title: 'Home Page'
877
+ * }
878
+ * }, { mode: 'draft' });
879
+ *
880
+ * // Full example - with number timestamps (auto-converted)
881
+ * await client.setRawDoc('Pages', 'home', {
882
+ * id: 'Pages/home',
883
+ * collection: 'Pages',
884
+ * slug: 'home',
885
+ * sys: {
886
+ * createdAt: Date.now(), // Auto-converted to Timestamp.
887
+ * createdBy: 'user@example.com',
888
+ * modifiedAt: Date.now(), // Auto-converted to Timestamp.
889
+ * modifiedBy: 'user@example.com',
890
+ * locales: ['en', 'es']
891
+ * },
892
+ * fields: {
893
+ * title: 'Home Page'
894
+ * }
895
+ * }, { mode: 'draft' });
896
+ * ```
806
897
  */
807
898
  async setRawDoc(collectionId, slug, data, options) {
808
- const mode = options.mode;
809
- const modeCollection = mode === "draft" ? "Drafts" : "Published";
899
+ if (!collectionId) {
900
+ throw new Error("collectionId is required");
901
+ }
902
+ if (!slug) {
903
+ throw new Error("slug is required");
904
+ }
905
+ slug = normalizeSlug(slug);
906
+ const expectedId = `${collectionId}/${slug}`;
907
+ data.id = expectedId;
908
+ data.collection = collectionId;
909
+ data.slug = slug;
910
+ data.sys = validateSysFields(data.sys || {});
911
+ const modeCollection = this.getModeCollection(options.mode);
810
912
  const dbPath = `Projects/${this.projectId}/Collections/${collectionId}/${modeCollection}/${slug}`;
811
913
  const docRef = this.db.doc(dbPath);
812
914
  await docRef.set(data);
@@ -815,8 +917,10 @@ ${errorMessages}`);
815
917
  * Lists docs from a Root.js CMS collection.
816
918
  */
817
919
  async listDocs(collectionId, options) {
818
- const mode = options.mode;
819
- const modeCollection = mode === "draft" ? "Drafts" : "Published";
920
+ if (!collectionId) {
921
+ throw new Error("collectionId is required");
922
+ }
923
+ const modeCollection = this.getModeCollection(options.mode);
820
924
  const dbPath = `Projects/${this.projectId}/Collections/${collectionId}/${modeCollection}`;
821
925
  let query = this.db.collection(dbPath);
822
926
  if (options.limit) {
@@ -848,8 +952,10 @@ ${errorMessages}`);
848
952
  * Returns the number of docs in a Root.js CMS collection.
849
953
  */
850
954
  async getDocsCount(collectionId, options) {
851
- const mode = options.mode;
852
- const modeCollection = mode === "draft" ? "Drafts" : "Published";
955
+ if (!collectionId) {
956
+ throw new Error("collectionId is required");
957
+ }
958
+ const modeCollection = this.getModeCollection(options.mode);
853
959
  const dbPath = `Projects/${this.projectId}/Collections/${collectionId}/${modeCollection}`;
854
960
  let query = this.db.collection(dbPath);
855
961
  if (options.query) {
@@ -964,6 +1070,67 @@ ${errorMessages}`);
964
1070
  console.log(`published ${publishedDocs.length} docs!`);
965
1071
  return publishedDocs;
966
1072
  }
1073
+ /**
1074
+ * Batch unpublishes a set of docs by id.
1075
+ */
1076
+ async unpublishDocs(docIds, options) {
1077
+ const projectCollectionsPath = `Projects/${this.projectId}/Collections`;
1078
+ const unpublishedBy = options?.unpublishedBy || "root-cms-client";
1079
+ const docRefs = docIds.map((docId) => {
1080
+ const [collection, slug] = docId.split("/");
1081
+ if (!collection || !slug) {
1082
+ throw new Error(`invalid doc id: ${docId}`);
1083
+ }
1084
+ const docRef = this.db.doc(
1085
+ `${projectCollectionsPath}/${collection}/Drafts/${slug}`
1086
+ );
1087
+ return docRef;
1088
+ });
1089
+ const docSnapshots = await this.db.getAll(...docRefs);
1090
+ const docs = docSnapshots.map((snapshot) => snapshot.data()).filter((d) => !!d);
1091
+ if (docs.length === 0) {
1092
+ console.log("no docs to unpublish");
1093
+ return [];
1094
+ }
1095
+ let batchCount = 0;
1096
+ const batch = options?.batch || this.db.batch();
1097
+ const unpublishedDocs = [];
1098
+ for (const doc of docs) {
1099
+ const { id, collection, slug } = doc;
1100
+ const draftRef = this.db.doc(
1101
+ `${projectCollectionsPath}/${collection}/Drafts/${slug}`
1102
+ );
1103
+ const scheduledRef = this.db.doc(
1104
+ `${projectCollectionsPath}/${collection}/Scheduled/${slug}`
1105
+ );
1106
+ const publishedRef = this.db.doc(
1107
+ `${projectCollectionsPath}/${collection}/Published/${slug}`
1108
+ );
1109
+ batch.update(draftRef, {
1110
+ "sys.modifiedAt": FieldValue2.serverTimestamp(),
1111
+ "sys.modifiedBy": unpublishedBy,
1112
+ "sys.publishedAt": FieldValue2.delete(),
1113
+ "sys.publishedBy": FieldValue2.delete(),
1114
+ "sys.firstPublishedAt": FieldValue2.delete(),
1115
+ "sys.firstPublishedBy": FieldValue2.delete()
1116
+ });
1117
+ batchCount += 1;
1118
+ batch.delete(scheduledRef);
1119
+ batchCount += 1;
1120
+ batch.delete(publishedRef);
1121
+ batchCount += 1;
1122
+ unpublishedDocs.push(doc);
1123
+ if (batchCount >= 400) {
1124
+ await batch.commit();
1125
+ batchCount = 0;
1126
+ }
1127
+ }
1128
+ if (batchCount > 0) {
1129
+ await batch.commit();
1130
+ }
1131
+ console.log(`unpublished ${unpublishedDocs.length} docs!`);
1132
+ return unpublishedDocs;
1133
+ }
967
1134
  /**
968
1135
  * Publishes scheduled docs.
969
1136
  */
@@ -1234,7 +1401,7 @@ ${errorMessages}`);
1234
1401
  if (!(mode === "draft" || mode === "published")) {
1235
1402
  throw new Error(`invalid mode: ${mode}`);
1236
1403
  }
1237
- const slug = translationsId.replaceAll("/", "--");
1404
+ const slug = normalizeSlug(translationsId);
1238
1405
  const dbPath = `Projects/${this.projectId}/TranslationsManager/${mode}/Translations/${slug}`;
1239
1406
  return dbPath;
1240
1407
  }
@@ -1578,6 +1745,87 @@ function unmarshalData(data) {
1578
1745
  }
1579
1746
  return result;
1580
1747
  }
1748
+ function isDef(value) {
1749
+ return value !== void 0 && value !== null;
1750
+ }
1751
+ function convertToTimestamp(value, fieldName) {
1752
+ if (value && typeof value === "object" && typeof value.toMillis === "function") {
1753
+ return value;
1754
+ }
1755
+ if (typeof value === "number") {
1756
+ return Timestamp2.fromMillis(value);
1757
+ }
1758
+ if (value instanceof Date) {
1759
+ return Timestamp2.fromDate(value);
1760
+ }
1761
+ throw new Error(
1762
+ `Invalid timestamp for ${fieldName}: expected Timestamp, number, or Date, got ${typeof value}`
1763
+ );
1764
+ }
1765
+ function validateSysFields(sys) {
1766
+ if (!sys || typeof sys !== "object") {
1767
+ throw new Error("sys must be an object");
1768
+ }
1769
+ const result = { ...sys };
1770
+ const now = Timestamp2.now();
1771
+ if (isDef(result.createdAt)) {
1772
+ result.createdAt = convertToTimestamp(result.createdAt, "sys.createdAt");
1773
+ } else {
1774
+ result.createdAt = now;
1775
+ }
1776
+ if (isDef(result.modifiedAt)) {
1777
+ result.modifiedAt = convertToTimestamp(result.modifiedAt, "sys.modifiedAt");
1778
+ } else {
1779
+ result.modifiedAt = now;
1780
+ }
1781
+ const optionalTimestampFields = ["firstPublishedAt", "publishedAt"];
1782
+ for (const field of optionalTimestampFields) {
1783
+ if (isDef(sys[field])) {
1784
+ result[field] = convertToTimestamp(sys[field], `sys.${field}`);
1785
+ }
1786
+ }
1787
+ if (!result.createdBy || typeof result.createdBy !== "string") {
1788
+ result.createdBy = "root-cms-client";
1789
+ }
1790
+ if (!result.modifiedBy || typeof result.modifiedBy !== "string") {
1791
+ result.modifiedBy = "root-cms-client";
1792
+ }
1793
+ const optionalStringFields = ["firstPublishedBy", "publishedBy"];
1794
+ for (const field of optionalStringFields) {
1795
+ if (isDef(sys[field])) {
1796
+ if (typeof sys[field] !== "string") {
1797
+ throw new Error(
1798
+ `Invalid sys.${field}: expected string, got ${typeof sys[field]}`
1799
+ );
1800
+ }
1801
+ }
1802
+ }
1803
+ if (isDef(sys.publishingLocked)) {
1804
+ if (typeof sys.publishingLocked !== "object") {
1805
+ throw new Error("Invalid sys.publishingLocked: expected object or null");
1806
+ }
1807
+ if (isDef(sys.publishingLocked.until)) {
1808
+ result.publishingLocked = {
1809
+ ...sys.publishingLocked,
1810
+ until: convertToTimestamp(
1811
+ sys.publishingLocked.until,
1812
+ "sys.publishingLocked.until"
1813
+ )
1814
+ };
1815
+ }
1816
+ }
1817
+ if (isDef(result.locales)) {
1818
+ if (!Array.isArray(result.locales)) {
1819
+ throw new Error("Invalid sys.locales: expected array");
1820
+ }
1821
+ if (!result.locales.every((locale) => typeof locale === "string")) {
1822
+ throw new Error("Invalid sys.locales: all items must be strings");
1823
+ }
1824
+ } else {
1825
+ result.locales = ["en"];
1826
+ }
1827
+ return result;
1828
+ }
1581
1829
  function normalizeData(data) {
1582
1830
  return unmarshalData(data);
1583
1831
  }
@@ -1652,7 +1900,7 @@ function parseDocId(docId) {
1652
1900
  throw new Error(`invalid doc id: ${docId}`);
1653
1901
  }
1654
1902
  const collection = docId.slice(0, sepIndex);
1655
- const slug = docId.slice(sepIndex + 1).replaceAll("/", "--");
1903
+ const slug = normalizeSlug(docId.slice(sepIndex + 1));
1656
1904
  if (!collection || !slug) {
1657
1905
  throw new Error(`invalid doc id: ${docId}`);
1658
1906
  }
@@ -1864,6 +2112,7 @@ var BatchResponse = class {
1864
2112
  };
1865
2113
 
1866
2114
  export {
2115
+ normalizeSlug,
1867
2116
  TranslationsManager,
1868
2117
  buildTranslationsDbPath,
1869
2118
  buildTranslationsLocaleDocDbPath,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  RootCMSClient,
3
3
  getCmsPlugin
4
- } from "./chunk-62EVNFXB.js";
4
+ } from "./chunk-XMR3KFP5.js";
5
5
 
6
6
  // core/versions.ts
7
7
  import path from "path";
package/dist/cli.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  generateTypes
3
- } from "./chunk-RYF3UTHQ.js";
3
+ } from "./chunk-KDXHFMIH.js";
4
4
  import {
5
5
  getCmsPlugin
6
- } from "./chunk-62EVNFXB.js";
6
+ } from "./chunk-XMR3KFP5.js";
7
7
  import "./chunk-MLKGABMK.js";
8
8
 
9
9
  // cli/cli.ts
@@ -1,7 +1,7 @@
1
1
  import { Plugin, Request, RootConfig } 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-Bux4PrV2.js';
4
+ import { C as Collection } from './schema-BKfPP_s9.js';
5
5
 
6
6
  /**
7
7
  * Supported Root AI models. Defaults to 'gemini-3-flash-preview'.
@@ -539,6 +539,10 @@ declare class RootCMSClient {
539
539
  readonly app: App;
540
540
  readonly db: Firestore;
541
541
  constructor(rootConfig: RootConfig);
542
+ /**
543
+ * Converts a doc mode to the Firestore collection name.
544
+ */
545
+ private getModeCollection;
542
546
  /**
543
547
  * Retrieves doc data from Root.js CMS.
544
548
  */
@@ -591,8 +595,83 @@ declare class RootCMSClient {
591
595
  */
592
596
  updateDraftData(docId: string, path: string, fieldValue: any, options?: UpdateDraftOptions): Promise<void>;
593
597
  /**
594
- * Prefer `saveDraftData('Pages/foo', data)`. Only use this if you know what
595
- * you're doing.
598
+ * Sets the raw document data directly in Firestore.
599
+ *
600
+ * CAUTION Prefer using `saveDraftData('Pages/foo', data)` in most cases.
601
+ * Only use this method if you need to manipulate system-level (sys) fields
602
+ * directly or if you're implementing low-level data operations.
603
+ *
604
+ * ## Validation & Normalization
605
+ *
606
+ * This method automatically validates and normalizes `sys` fields to prevent
607
+ * data integrity issues that can cause runtime errors like
608
+ * "e.toMillis is not a function".
609
+ *
610
+ * ### Timestamp Fields (auto-converted)
611
+ * The following fields accept multiple formats and are automatically converted
612
+ * to Firestore Timestamp objects:
613
+ * - `sys.createdAt`
614
+ * - `sys.modifiedAt`
615
+ * - `sys.publishedAt`
616
+ * - `sys.firstPublishedAt`
617
+ *
618
+ * Accepted formats:
619
+ * - Firestore `Timestamp` object (unchanged)
620
+ * - `number` - Interpreted as milliseconds since epoch, converted to Timestamp
621
+ * - `Date` object - Converted to Timestamp
622
+ *
623
+ * ### Required Fields (auto-populated with defaults if missing)
624
+ * - `sys.createdAt` - Defaults to current time if not provided
625
+ * - `sys.modifiedAt` - Defaults to current time if not provided
626
+ * - `sys.createdBy` - Defaults to 'root-cms-client' if not provided
627
+ * - `sys.modifiedBy` - Defaults to 'root-cms-client' if not provided
628
+ * - `sys.locales` - Defaults to ['en'] if not provided
629
+ *
630
+ * ### Optional Fields (validated if present)
631
+ * - `sys.publishedBy` - String identifier
632
+ * - `sys.firstPublishedBy` - String identifier
633
+ * - `sys.publishingLocked` - Object with optional `until` Timestamp
634
+ *
635
+ * ### Document Identity
636
+ * The `id`, `collection`, and `slug` fields are always set to match the
637
+ * provided parameters, overwriting any existing values to prevent data
638
+ * inconsistencies.
639
+ *
640
+ * @param collectionId - The collection ID (e.g., 'Pages')
641
+ * @param slug - The document slug (e.g., 'home')
642
+ * @param data - The complete document data including sys and fields
643
+ * @param options - Options specifying mode ('draft' or 'published')
644
+ *
645
+ * @throws {Error} If sys fields are invalid or missing required fields
646
+ * @throws {Error} If timestamp fields have invalid types
647
+ *
648
+ * @example
649
+ * ```typescript
650
+ * // Minimal example - sys fields use defaults
651
+ * await client.setRawDoc('Pages', 'home', {
652
+ * sys: {}, // All sys fields will be auto-populated with defaults
653
+ * fields: {
654
+ * title: 'Home Page'
655
+ * }
656
+ * }, { mode: 'draft' });
657
+ *
658
+ * // Full example - with number timestamps (auto-converted)
659
+ * await client.setRawDoc('Pages', 'home', {
660
+ * id: 'Pages/home',
661
+ * collection: 'Pages',
662
+ * slug: 'home',
663
+ * sys: {
664
+ * createdAt: Date.now(), // Auto-converted to Timestamp.
665
+ * createdBy: 'user@example.com',
666
+ * modifiedAt: Date.now(), // Auto-converted to Timestamp.
667
+ * modifiedBy: 'user@example.com',
668
+ * locales: ['en', 'es']
669
+ * },
670
+ * fields: {
671
+ * title: 'Home Page'
672
+ * }
673
+ * }, { mode: 'draft' });
674
+ * ```
596
675
  */
597
676
  setRawDoc(collectionId: string, slug: string, data: any, options: SetDocOptions): Promise<void>;
598
677
  /**
@@ -613,6 +692,13 @@ declare class RootCMSClient {
613
692
  batch?: WriteBatch;
614
693
  releaseId?: string;
615
694
  }): Promise<any[]>;
695
+ /**
696
+ * Batch unpublishes a set of docs by id.
697
+ */
698
+ unpublishDocs(docIds: string[], options?: {
699
+ unpublishedBy?: string;
700
+ batch?: WriteBatch;
701
+ }): Promise<any[]>;
616
702
  /**
617
703
  * Publishes scheduled docs.
618
704
  */
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, q as ArrayObject, C as BatchRequest, B as BatchRequestOptions, x as BatchRequestQuery, y as BatchRequestQueryOptions, E as BatchResponse, c as DataSource, d as DataSourceData, e as DataSourceMode, b as Doc, D as DocMode, i as GetCountOptions, G as GetDocOptions, H as HttpMethod, l as ListActionsOptions, h as ListDocsOptions, L as LoadTranslationsOptions, a as LocaleTranslations, k as Release, R as RootCMSClient, f as SaveDraftOptions, S as SetDocOptions, j as Translation, z as TranslationsDoc, T as TranslationsMap, g as UpdateDraftOptions, U as UserRole, n as getCmsPlugin, m as isRichTextData, r as marshalArray, o as marshalData, p as normalizeData, w as parseDocId, t as toArrayObject, v as translationsForLocale, s as unmarshalArray, u as unmarshalData } from './client-PhodvL2Q.js';
5
- import './schema-Bux4PrV2.js';
4
+ export { A as Action, q as ArrayObject, C as BatchRequest, B as BatchRequestOptions, x as BatchRequestQuery, y as BatchRequestQueryOptions, E as BatchResponse, c as DataSource, d as DataSourceData, e as DataSourceMode, b as Doc, D as DocMode, i as GetCountOptions, G as GetDocOptions, H as HttpMethod, l as ListActionsOptions, h as ListDocsOptions, L as LoadTranslationsOptions, a as LocaleTranslations, k as Release, R as RootCMSClient, f as SaveDraftOptions, S as SetDocOptions, j as Translation, z as TranslationsDoc, T as TranslationsMap, g as UpdateDraftOptions, U as UserRole, n as getCmsPlugin, m as isRichTextData, r as marshalArray, o as marshalData, p as normalizeData, w as parseDocId, t as toArrayObject, v as translationsForLocale, s as unmarshalArray, u as unmarshalData } from './client-BZ03biQh.js';
5
+ import './schema-BKfPP_s9.js';
package/dist/client.js CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  translationsForLocale,
13
13
  unmarshalArray,
14
14
  unmarshalData
15
- } from "./chunk-62EVNFXB.js";
15
+ } from "./chunk-XMR3KFP5.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-PhodvL2Q.js';
2
- export { A as Action, q as ArrayObject, C as BatchRequest, B as BatchRequestOptions, x as BatchRequestQuery, y as BatchRequestQueryOptions, E as BatchResponse, c as DataSource, d as DataSourceData, e as DataSourceMode, b as Doc, i as GetCountOptions, G as GetDocOptions, H as HttpMethod, l as ListActionsOptions, h as ListDocsOptions, F as Locale, O as MultiLocaleTranslationsMap, k as Release, f as SaveDraftOptions, S as SetDocOptions, P as SingleLocaleTranslationsMap, I as SourceString, J as TranslatedString, j as Translation, z as TranslationsDoc, K as TranslationsDocMode, N as TranslationsLocaleDocEntry, M as TranslationsLocaleDocHashMap, Q as TranslationsManager, g as UpdateDraftOptions, U as UserRole, V as buildTranslationsDbPath, W as buildTranslationsLocaleDocDbPath, n as getCmsPlugin, m as isRichTextData, r as marshalArray, o as marshalData, p as normalizeData, w as parseDocId, t as toArrayObject, v as translationsForLocale, s as unmarshalArray, u as unmarshalData } from './client-PhodvL2Q.js';
1
+ import { R as RootCMSClient, D as DocMode, L as LoadTranslationsOptions, T as TranslationsMap, a as LocaleTranslations } from './client-BZ03biQh.js';
2
+ export { A as Action, q as ArrayObject, C as BatchRequest, B as BatchRequestOptions, x as BatchRequestQuery, y as BatchRequestQueryOptions, E as BatchResponse, c as DataSource, d as DataSourceData, e as DataSourceMode, b as Doc, i as GetCountOptions, G as GetDocOptions, H as HttpMethod, l as ListActionsOptions, h as ListDocsOptions, F as Locale, O as MultiLocaleTranslationsMap, k as Release, f as SaveDraftOptions, S as SetDocOptions, P as SingleLocaleTranslationsMap, I as SourceString, J as TranslatedString, j as Translation, z as TranslationsDoc, K as TranslationsDocMode, N as TranslationsLocaleDocEntry, M as TranslationsLocaleDocHashMap, Q as TranslationsManager, g as UpdateDraftOptions, U as UserRole, V as buildTranslationsDbPath, W as buildTranslationsLocaleDocDbPath, n as getCmsPlugin, m as isRichTextData, r as marshalArray, o as marshalData, p as normalizeData, w as parseDocId, t as toArrayObject, v as translationsForLocale, s as unmarshalArray, u as unmarshalData } from './client-BZ03biQh.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-Bux4PrV2.js';
5
+ export { s as schema } from './schema-BKfPP_s9.js';
6
6
  import 'firebase-admin/app';
7
7
 
8
8
  /**
package/dist/core.js CHANGED
@@ -10,12 +10,13 @@ import {
10
10
  marshalArray,
11
11
  marshalData,
12
12
  normalizeData,
13
+ normalizeSlug,
13
14
  parseDocId,
14
15
  toArrayObject,
15
16
  translationsForLocale,
16
17
  unmarshalArray,
17
18
  unmarshalData
18
- } from "./chunk-62EVNFXB.js";
19
+ } from "./chunk-XMR3KFP5.js";
19
20
  import {
20
21
  __export
21
22
  } from "./chunk-MLKGABMK.js";
@@ -48,7 +49,7 @@ function createRoute(options) {
48
49
  const { slug, mode } = routeContext;
49
50
  const translationsTags = [
50
51
  "common",
51
- `${options.collection}/${slug.replaceAll("/", "--")}`
52
+ `${options.collection}/${normalizeSlug(slug)}`
52
53
  ];
53
54
  if (options.translations) {
54
55
  const tags = options.translations(routeContext)?.tags || [];
@@ -102,7 +103,7 @@ function createRoute(options) {
102
103
  };
103
104
  const translationsTags = [
104
105
  "common",
105
- `${options.collection}/${slug.replaceAll("/", "--")}`
106
+ `${options.collection}/${normalizeSlug(slug)}`
106
107
  ];
107
108
  if (options.translations) {
108
109
  const tags = options.translations(routeContext)?.tags || [];
@@ -311,7 +312,7 @@ async function getDoc(rootConfig, collectionId, slug, options) {
311
312
  const mode = options.mode;
312
313
  const modeCollection = mode === "draft" ? "Drafts" : "Published";
313
314
  const db = cmsPlugin.getFirestore();
314
- slug = slug.replaceAll("/", "--");
315
+ slug = normalizeSlug(slug);
315
316
  const dbPath = `Projects/${projectId}/Collections/${collectionId}/${modeCollection}/${slug}`;
316
317
  const docRef = db.doc(dbPath);
317
318
  const doc = await docRef.get();
@@ -484,6 +485,7 @@ __export(schema_exports, {
484
485
  defineCollection: () => defineCollection,
485
486
  defineSchema: () => defineSchema,
486
487
  file: () => file,
488
+ glob: () => glob,
487
489
  image: () => image,
488
490
  multiselect: () => multiselect,
489
491
  number: () => number,
@@ -565,6 +567,14 @@ function defineCollection(collection2) {
565
567
  return collection2;
566
568
  }
567
569
  var collection = defineCollection;
570
+ function glob(pattern, options) {
571
+ return {
572
+ _schemaPattern: true,
573
+ pattern,
574
+ exclude: options?.exclude,
575
+ omitFields: options?.omitFields
576
+ };
577
+ }
568
578
  export {
569
579
  BatchRequest,
570
580
  BatchResponse,
package/dist/functions.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  runCronJobs
3
- } from "./chunk-SI44FG3H.js";
4
- import "./chunk-62EVNFXB.js";
3
+ } from "./chunk-YAQCD6SS.js";
4
+ import "./chunk-XMR3KFP5.js";
5
5
  import "./chunk-MLKGABMK.js";
6
6
 
7
7
  // core/functions.ts
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  generateSchemaDts,
3
3
  generateTypes
4
- } from "./chunk-RYF3UTHQ.js";
4
+ } from "./chunk-KDXHFMIH.js";
5
5
  import "./chunk-MLKGABMK.js";
6
6
  export {
7
7
  generateSchemaDts,
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 { Z as CMSAIConfig, X as CMSBuiltInSidebarTool, a0 as CMSPlugin, $ as CMSPluginOptions, _ as CMSSidebarTool, Y as CMSUser, a1 as cmsPlugin } from './client-PhodvL2Q.js';
5
- import './schema-Bux4PrV2.js';
4
+ export { Z as CMSAIConfig, X as CMSBuiltInSidebarTool, a0 as CMSPlugin, $ as CMSPluginOptions, _ as CMSSidebarTool, Y as CMSUser, a1 as cmsPlugin } from './client-BZ03biQh.js';
5
+ import './schema-BKfPP_s9.js';