@blinkk/root-cms 1.4.4 → 1.4.5

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/plugin.js CHANGED
@@ -447,10 +447,361 @@ var ChatClient = class {
447
447
 
448
448
  // core/client.ts
449
449
  import crypto2 from "node:crypto";
450
+ import {
451
+ FieldValue as FieldValue2,
452
+ Timestamp as Timestamp3
453
+ } from "firebase-admin/firestore";
454
+
455
+ // core/translations-manager.ts
450
456
  import {
451
457
  FieldValue,
452
458
  Timestamp as Timestamp2
453
459
  } from "firebase-admin/firestore";
460
+
461
+ // shared/strings.ts
462
+ import fnv from "fnv-plus";
463
+ function normalizeStr(str) {
464
+ const lines = str.trim().split("\n").map((line) => removeTrailingWhitespace(line));
465
+ return lines.join("\n");
466
+ }
467
+ function removeTrailingWhitespace(str) {
468
+ return str.trimEnd().replace(/ $/, "");
469
+ }
470
+ function hashStr(str) {
471
+ if (!str || typeof str !== "string") {
472
+ throw new Error("input string is invalid");
473
+ }
474
+ return fnv.fast1a52hex(normalizeStr(str));
475
+ }
476
+
477
+ // core/translations-manager.ts
478
+ var TRANSLATIONS_DB_PATH_FORMAT = "/Projects/{project}/TranslationsManager/{mode}/Translations";
479
+ var TRANSLATIONS_LOCALE_DOC_DB_PATH_FORMAT = `${TRANSLATIONS_DB_PATH_FORMAT}/{id}:{locale}`;
480
+ var TranslationsManager = class {
481
+ constructor(cmsClient) {
482
+ this.cmsClient = cmsClient;
483
+ }
484
+ /**
485
+ * Saves draft translations for a translations doc id.
486
+ *
487
+ * Example:
488
+ * ```
489
+ * const strings = {
490
+ * 'one': {es: 'uno', fr: 'un'},
491
+ * 'two': {es: 'dos', fr: 'deux'},
492
+ * };
493
+ * await tm.saveTranslations('Pages/index', strings);
494
+ * ```
495
+ */
496
+ async saveTranslations(id, strings, options) {
497
+ const mode = "draft";
498
+ const localesSet = /* @__PURE__ */ new Set();
499
+ Object.values(strings).forEach((entry) => {
500
+ Object.keys(entry).forEach((locale) => {
501
+ if (locale !== "source") {
502
+ localesSet.add(locale);
503
+ }
504
+ });
505
+ });
506
+ const batch = this.cmsClient.db.batch();
507
+ const locales = Array.from(localesSet);
508
+ locales.forEach((locale) => {
509
+ const updates = {
510
+ id,
511
+ locale,
512
+ sys: {
513
+ modifiedAt: Timestamp2.now(),
514
+ modifiedBy: (options == null ? void 0 : options.modifiedBy) || "root-cms-client"
515
+ },
516
+ strings: {}
517
+ };
518
+ if ((options == null ? void 0 : options.tags) && options.tags.length > 0) {
519
+ updates.tags = FieldValue.arrayUnion(...options.tags);
520
+ }
521
+ let numUpdates = 0;
522
+ const hashMap = this.toLocaleDocHashMap(strings, locale);
523
+ Object.entries(hashMap).forEach(([hash, translations]) => {
524
+ Object.entries(translations).forEach(([locale2, translation]) => {
525
+ if (translation) {
526
+ updates.strings[hash] ??= {};
527
+ updates.strings[hash][locale2] = translation;
528
+ numUpdates += 1;
529
+ }
530
+ });
531
+ });
532
+ if (numUpdates > 0) {
533
+ const localeDocPath = buildTranslationsLocaleDocDbPath({
534
+ project: this.cmsClient.projectId,
535
+ mode,
536
+ id,
537
+ locale
538
+ });
539
+ const localeDocRef = this.cmsClient.db.doc(localeDocPath);
540
+ batch.set(localeDocRef, updates, { merge: true });
541
+ }
542
+ });
543
+ await batch.commit();
544
+ }
545
+ /**
546
+ * Publishes a translations doc.
547
+ */
548
+ async publishTranslations(id, options) {
549
+ const db = this.cmsClient.db;
550
+ const project = this.cmsClient.projectId;
551
+ const draftPath = buildTranslationsDbPath({ project, mode: "draft" });
552
+ const query = db.collection(draftPath).where("id", "==", id);
553
+ const res = await query.get();
554
+ if (res.size === 0) {
555
+ console.warn(`no translations to publish for ${id}`);
556
+ return;
557
+ }
558
+ const batch = (options == null ? void 0 : options.batch) || db.batch();
559
+ res.docs.forEach((doc) => {
560
+ const translationsLocaleDoc = doc.data();
561
+ const sys = {
562
+ ...translationsLocaleDoc.sys,
563
+ publishedAt: Timestamp2.now(),
564
+ publishedBy: (options == null ? void 0 : options.publishedBy) || "root-cms-client"
565
+ };
566
+ batch.update(doc.ref, { sys });
567
+ const publishedDocPath = buildTranslationsLocaleDocDbPath({
568
+ project,
569
+ mode: "published",
570
+ id: translationsLocaleDoc.id,
571
+ locale: translationsLocaleDoc.locale
572
+ });
573
+ const publishedDocRef = db.doc(publishedDocPath);
574
+ batch.set(publishedDocRef, { ...translationsLocaleDoc, sys });
575
+ });
576
+ const shouldCommitBatch = !(options == null ? void 0 : options.batch);
577
+ if (shouldCommitBatch) {
578
+ await batch.commit();
579
+ }
580
+ }
581
+ /**
582
+ * Fetches translations from one or more translations docs in the translations
583
+ * manager.
584
+ *
585
+ * Example:
586
+ * ```
587
+ * await tm.loadTranslations();
588
+ * // =>
589
+ * // {
590
+ * // "one": {"es": "uno", "fr": "un"},
591
+ * // "two": {"es": "dos", "fr": "deux"}
592
+ * // }
593
+ * ```
594
+ *
595
+ * To load a specific set of translations docs by id:
596
+ * ```
597
+ * const translationsToLoad = ['Global/strings', 'Global/header', 'Global/footer', 'Pages/index'];
598
+ * await tm.loadTranslations({ids: translationsToLoad});
599
+ * // =>
600
+ * // {
601
+ * // "one": {"es": "uno", "fr": "un"},
602
+ * // "two": {"es": "dos", "fr": "deux"}
603
+ * // }
604
+ * ```
605
+ *
606
+ * To load a subset of locales (more performant):
607
+ * ```
608
+ * await tm.loadTranslations({locales: ['es']});
609
+ * // =>
610
+ * // {
611
+ * // "one": {"es": "uno"},
612
+ * // "two": {"es": "dos"}
613
+ * // }
614
+ * ```
615
+ */
616
+ async loadTranslations(options) {
617
+ const mode = (options == null ? void 0 : options.mode) || "published";
618
+ const dbPath = buildTranslationsDbPath({
619
+ project: this.cmsClient.projectId,
620
+ mode
621
+ });
622
+ let query = this.cmsClient.db.collection(dbPath);
623
+ if ((options == null ? void 0 : options.ids) && options.ids.length > 0) {
624
+ query = query.where("id", "in", options.ids);
625
+ }
626
+ if ((options == null ? void 0 : options.tags) && options.tags.length > 0) {
627
+ query = query.where("tags", "array-contains", options.tags);
628
+ }
629
+ if ((options == null ? void 0 : options.locales) && options.locales.length > 0) {
630
+ query = query.where("locale", "in", options.locales);
631
+ }
632
+ const results = await query.get();
633
+ const strings = {};
634
+ results.forEach((result) => {
635
+ const localeDoc = result.data();
636
+ Object.values(localeDoc.strings || {}).forEach((item) => {
637
+ strings[item.source] ??= { source: item.source };
638
+ strings[item.source][localeDoc.locale] = item.translation;
639
+ });
640
+ });
641
+ return strings;
642
+ }
643
+ /**
644
+ * Fetches translations for a given locale, with optional fallbacks.
645
+ * The return value is a map of source string to translated string.
646
+ *
647
+ * Example:
648
+ * ```
649
+ * await translationsDoc.loadTranslationsForLocale('es');
650
+ * // =>
651
+ * // {
652
+ * // "one": "uno",
653
+ * // "two": "dos",
654
+ * // }
655
+ * ```
656
+ */
657
+ async loadTranslationsForLocale(locale, options) {
658
+ const localeSet = /* @__PURE__ */ new Set([
659
+ locale,
660
+ ...(options == null ? void 0 : options.fallbackLocales) || []
661
+ ]);
662
+ const fallbackLocales = Array.from(localeSet);
663
+ const multiLocaleStrings = await this.loadTranslations({
664
+ mode: options == null ? void 0 : options.mode,
665
+ locales: fallbackLocales
666
+ });
667
+ return this.toSingleLocaleMap(multiLocaleStrings, fallbackLocales);
668
+ }
669
+ /**
670
+ * Converts a multi-locale translations map to a flat single-locale map,
671
+ * with optional support for fallback locales.
672
+ *
673
+ * ```
674
+ * const multiLocaleStrings = {
675
+ * 'one': {es: 'uno', fr: 'un'},
676
+ * 'two': {es: 'dos', fr: 'deux'}
677
+ * };
678
+ * translationsDoc.toSingleLocaleMap(multiLocaleStrings, ['es']);
679
+ * // =>
680
+ * // {
681
+ * // "one": "uno",
682
+ * // "two": "dos",
683
+ * // }
684
+ * ```
685
+ */
686
+ toSingleLocaleMap(multiLocaleStrings, fallbackLocales) {
687
+ const singleLocaleStrings = {};
688
+ Object.entries(multiLocaleStrings).forEach(([source, translations]) => {
689
+ let translation = source;
690
+ for (const locale of fallbackLocales) {
691
+ if (translations[locale]) {
692
+ translation = translations[locale];
693
+ break;
694
+ }
695
+ }
696
+ singleLocaleStrings[source] = translation;
697
+ });
698
+ return singleLocaleStrings;
699
+ }
700
+ /**
701
+ * Converts a multi-locale translations map to a single-locale hashed version,
702
+ * used for storage in in the DB.
703
+ *
704
+ * ```
705
+ * const multiLocaleStrings = {
706
+ * 'one': {es: 'uno', fr: 'un'},
707
+ * 'two': {es: 'dos', fr: 'deux'}
708
+ * };
709
+ * translationsDoc.toLocaleDocHashMap(multiLocaleStrings, 'es');
710
+ * // =>
711
+ * // {
712
+ * // "<hash1>": {"source": "one", "translation": "uno"},
713
+ * // "<hash2>": {"source": "two", "translation": "dos"},
714
+ * // }
715
+ * ```
716
+ *
717
+ * One reason for using hashes is because the DB has limits on the number of
718
+ * chars that can be used as the "key" in a object map.
719
+ */
720
+ toLocaleDocHashMap(multiLocaleStrings, locale) {
721
+ const hashMap = {};
722
+ Object.entries(multiLocaleStrings).forEach(([source, translations]) => {
723
+ const translation = translations[locale];
724
+ if (translation) {
725
+ const hash = hashStr(source);
726
+ hashMap[hash] = { source, translation };
727
+ }
728
+ });
729
+ return hashMap;
730
+ }
731
+ /**
732
+ * Import translations from the v1 system to the TranslationsManager.
733
+ */
734
+ async importTranslationsFromV1() {
735
+ var _a;
736
+ const projectId = this.cmsClient.projectId;
737
+ const db = this.cmsClient.db;
738
+ const dbPath = `Projects/${projectId}/Translations`;
739
+ const query = db.collection(dbPath);
740
+ const querySnapshot = await query.get();
741
+ if (querySnapshot.size === 0) {
742
+ return;
743
+ }
744
+ console.log(
745
+ "[root cms] importing v1 Translations to v2 TranslationsManager"
746
+ );
747
+ const translationsDocs = {};
748
+ querySnapshot.forEach((doc) => {
749
+ const translation = doc.data();
750
+ const source = this.cmsClient.normalizeString(translation.source);
751
+ delete translation.source;
752
+ const tags = translation.tags || [];
753
+ delete translation.tags;
754
+ for (const tag of tags) {
755
+ if (tag.includes("/")) {
756
+ const translationsId = tag;
757
+ translationsDocs[translationsId] ??= {
758
+ id: translationsId,
759
+ tags,
760
+ strings: {}
761
+ };
762
+ translationsDocs[translationsId].strings[source] = translation;
763
+ }
764
+ }
765
+ });
766
+ if (Object.keys(translationsDocs).length === 0) {
767
+ console.log("[root cms] no v1 translations to save");
768
+ return;
769
+ }
770
+ for (const docId in translationsDocs) {
771
+ const [collection, slug] = docId.split("/");
772
+ if (collection && slug) {
773
+ const doc = await this.cmsClient.getDoc(collection, slug, {
774
+ mode: "draft"
775
+ });
776
+ const linkedSheet = (_a = doc == null ? void 0 : doc.sys) == null ? void 0 : _a.l10nSheet;
777
+ if (linkedSheet) {
778
+ translationsDocs[docId].sys.linkedSheet = linkedSheet;
779
+ }
780
+ }
781
+ }
782
+ Object.entries(translationsDocs).forEach(([translationsId, data]) => {
783
+ const len = Object.keys(data.strings).length;
784
+ console.log(`[root cms] saving ${len} string(s) to ${translationsId}...`);
785
+ this.saveTranslations(translationsId, data.strings, {
786
+ tags: data.tags || [translationsId]
787
+ });
788
+ });
789
+ }
790
+ };
791
+ function buildTranslationsDbPath(options) {
792
+ return TRANSLATIONS_DB_PATH_FORMAT.replace(
793
+ "{project}",
794
+ options.project
795
+ ).replace("{mode}", options.mode);
796
+ }
797
+ function buildTranslationsLocaleDocDbPath(options) {
798
+ return TRANSLATIONS_LOCALE_DOC_DB_PATH_FORMAT.replace(
799
+ "{project}",
800
+ options.project
801
+ ).replace("{mode}", options.mode).replace("{id}", options.id.replaceAll("/", "--")).replace("{locale}", options.locale);
802
+ }
803
+
804
+ // core/client.ts
454
805
  var RootCMSClient = class {
455
806
  constructor(rootConfig) {
456
807
  this.rootConfig = rootConfig;
@@ -504,9 +855,9 @@ var RootCMSClient = class {
504
855
  slug,
505
856
  sys: {
506
857
  ...draftSys,
507
- createdAt: draftSys.createdAt ?? Timestamp2.now(),
858
+ createdAt: draftSys.createdAt ?? Timestamp3.now(),
508
859
  createdBy: draftSys.createdBy ?? modifiedBy,
509
- modifiedAt: Timestamp2.now(),
860
+ modifiedAt: Timestamp3.now(),
510
861
  modifiedBy,
511
862
  locales: (options == null ? void 0 : options.locales) ?? draftSys.locales ?? ["en"]
512
863
  },
@@ -614,7 +965,7 @@ var RootCMSClient = class {
614
965
  const publishedRef = this.db.doc(
615
966
  `${projectCollectionsPath}/${collection}/Published/${slug}`
616
967
  );
617
- const firstPublishedAt = sys.firstPublishedAt ?? FieldValue.serverTimestamp();
968
+ const firstPublishedAt = sys.firstPublishedAt ?? FieldValue2.serverTimestamp();
618
969
  const firstPublishedBy = sys.firstPublishedBy ?? publishedBy;
619
970
  batch.set(publishedRef, {
620
971
  id,
@@ -625,7 +976,7 @@ var RootCMSClient = class {
625
976
  ...sys,
626
977
  firstPublishedAt,
627
978
  firstPublishedBy,
628
- publishedAt: FieldValue.serverTimestamp(),
979
+ publishedAt: FieldValue2.serverTimestamp(),
629
980
  publishedBy
630
981
  }
631
982
  });
@@ -633,11 +984,11 @@ var RootCMSClient = class {
633
984
  batch.delete(scheduledRef);
634
985
  batchCount += 1;
635
986
  batch.update(draftRef, {
636
- "sys.scheduledAt": FieldValue.delete(),
637
- "sys.scheduledBy": FieldValue.delete(),
987
+ "sys.scheduledAt": FieldValue2.delete(),
988
+ "sys.scheduledBy": FieldValue2.delete(),
638
989
  "sys.firstPublishedAt": firstPublishedAt,
639
990
  "sys.firstPublishedBy": firstPublishedBy,
640
- "sys.publishedAt": FieldValue.serverTimestamp(),
991
+ "sys.publishedAt": FieldValue2.serverTimestamp(),
641
992
  "sys.publishedBy": publishedBy
642
993
  });
643
994
  batchCount += 1;
@@ -711,7 +1062,7 @@ var RootCMSClient = class {
711
1062
  ...sys,
712
1063
  firstPublishedAt,
713
1064
  firstPublishedBy,
714
- publishedAt: FieldValue.serverTimestamp(),
1065
+ publishedAt: FieldValue2.serverTimestamp(),
715
1066
  publishedBy: scheduledBy || ""
716
1067
  }
717
1068
  });
@@ -719,11 +1070,11 @@ var RootCMSClient = class {
719
1070
  batch.delete(scheduledRef);
720
1071
  batchCount += 1;
721
1072
  batch.update(draftRef, {
722
- "sys.scheduledAt": FieldValue.delete(),
723
- "sys.scheduledBy": FieldValue.delete(),
1073
+ "sys.scheduledAt": FieldValue2.delete(),
1074
+ "sys.scheduledBy": FieldValue2.delete(),
724
1075
  "sys.firstPublishedAt": firstPublishedAt,
725
1076
  "sys.firstPublishedBy": firstPublishedBy,
726
- "sys.publishedAt": FieldValue.serverTimestamp(),
1077
+ "sys.publishedAt": FieldValue2.serverTimestamp(),
727
1078
  "sys.publishedBy": scheduledBy || "root-cms-client"
728
1079
  });
729
1080
  batchCount += 1;
@@ -746,17 +1097,17 @@ var RootCMSClient = class {
746
1097
  async publishScheduledReleases() {
747
1098
  const releasesPath = `Projects/${this.projectId}/Releases`;
748
1099
  const now = Math.ceil((/* @__PURE__ */ new Date()).getTime());
749
- const query = this.db.collection(releasesPath).where("scheduledAt", "<=", Timestamp2.fromMillis(now));
1100
+ const query = this.db.collection(releasesPath).where("scheduledAt", "<=", Timestamp3.fromMillis(now));
750
1101
  const querySnapshot = await query.get();
751
1102
  for (const snapshot of querySnapshot.docs) {
752
1103
  const release = snapshot.data();
753
1104
  const batch = this.db.batch();
754
1105
  const publishedBy = release.scheduledBy || "root-cms-client";
755
1106
  batch.update(snapshot.ref, {
756
- publishedAt: Timestamp2.now(),
1107
+ publishedAt: Timestamp3.now(),
757
1108
  publishedBy,
758
- scheduledAt: FieldValue.delete(),
759
- scheduledBy: FieldValue.delete()
1109
+ scheduledAt: FieldValue2.delete(),
1110
+ scheduledBy: FieldValue2.delete()
760
1111
  });
761
1112
  await this.publishDocs(release.docIds || [], { publishedBy, batch });
762
1113
  }
@@ -768,7 +1119,7 @@ var RootCMSClient = class {
768
1119
  var _a;
769
1120
  if ((_a = doc.sys) == null ? void 0 : _a.publishingLocked) {
770
1121
  if (doc.sys.publishingLocked.until) {
771
- const now = Timestamp2.now().toMillis();
1122
+ const now = Timestamp3.now().toMillis();
772
1123
  const until = doc.sys.publishingLocked.until.toMillis();
773
1124
  return now < until;
774
1125
  }
@@ -776,6 +1127,30 @@ var RootCMSClient = class {
776
1127
  }
777
1128
  return false;
778
1129
  }
1130
+ /**
1131
+ * Returns a `TranslationsManager` object for managing translations.
1132
+ *
1133
+ * To get translations:
1134
+ * ```
1135
+ * await tm.loadTranslations({
1136
+ * ids: ['Global/strings', 'Pages/index'],
1137
+ * locales: ['es'],
1138
+ * });
1139
+ * ```
1140
+ *
1141
+ * NOTE: The `TranslationsManager` is a v2 feature that will eventually
1142
+ * replace the v1 translations system.
1143
+ */
1144
+ getTranslationsManager() {
1145
+ var _a;
1146
+ const cmsPluginOptions = this.cmsPlugin.getConfig();
1147
+ if ((_a = cmsPluginOptions.experiments) == null ? void 0 : _a.v2TranslationsManager) {
1148
+ throw new Error(
1149
+ "`v2TranslationsManager` is not enabled. update root.config.ts and add: `{experiments: {v2TranslationsManager: true}}`"
1150
+ );
1151
+ }
1152
+ return new TranslationsManager(this);
1153
+ }
779
1154
  /**
780
1155
  * Loads translations saved in the translations collection, optionally
781
1156
  * filtered by tag.
@@ -821,7 +1196,7 @@ var RootCMSClient = class {
821
1196
  source: this.normalizeString(source)
822
1197
  };
823
1198
  if (tags) {
824
- data.tags = FieldValue.arrayUnion(...tags);
1199
+ data.tags = FieldValue2.arrayUnion(...tags);
825
1200
  }
826
1201
  batch.set(translationRef, data, { merge: true });
827
1202
  batchCount += 1;
@@ -892,7 +1267,7 @@ var RootCMSClient = class {
892
1267
  const syncedBy = (options == null ? void 0 : options.syncedBy) || "root-cms-client";
893
1268
  const updatedDataSource = {
894
1269
  ...dataSource,
895
- syncedAt: Timestamp2.now(),
1270
+ syncedAt: Timestamp3.now(),
896
1271
  syncedBy
897
1272
  };
898
1273
  const batch = this.db.batch();
@@ -901,7 +1276,7 @@ var RootCMSClient = class {
901
1276
  data
902
1277
  });
903
1278
  batch.update(dataSourceDocRef, {
904
- syncedAt: Timestamp2.now(),
1279
+ syncedAt: Timestamp3.now(),
905
1280
  syncedBy
906
1281
  });
907
1282
  await batch.commit();
@@ -926,7 +1301,7 @@ var RootCMSClient = class {
926
1301
  const publishedBy = (options == null ? void 0 : options.publishedBy) || "root-cms-client";
927
1302
  const updatedDataSource = {
928
1303
  ...dataSource,
929
- publishedAt: Timestamp2.now(),
1304
+ publishedAt: Timestamp3.now(),
930
1305
  publishedBy
931
1306
  };
932
1307
  const batch = this.db.batch();
@@ -938,7 +1313,7 @@ var RootCMSClient = class {
938
1313
  dataSource: updatedDataSource
939
1314
  });
940
1315
  batch.update(dataSourceDocRef, {
941
- publishedAt: Timestamp2.now(),
1316
+ publishedAt: Timestamp3.now(),
942
1317
  publishedBy
943
1318
  });
944
1319
  await batch.commit();
@@ -1045,7 +1420,7 @@ var RootCMSClient = class {
1045
1420
  }
1046
1421
  const data = {
1047
1422
  action,
1048
- timestamp: Timestamp2.now(),
1423
+ timestamp: Timestamp3.now(),
1049
1424
  by: (options == null ? void 0 : options.by) || "system",
1050
1425
  metadata: (options == null ? void 0 : options.metadata) || {}
1051
1426
  };
@@ -1199,7 +1574,7 @@ function parseDocId(docId) {
1199
1574
 
1200
1575
  // core/versions.ts
1201
1576
  import path from "node:path";
1202
- import { Timestamp as Timestamp3 } from "firebase-admin/firestore";
1577
+ import { Timestamp as Timestamp4 } from "firebase-admin/firestore";
1203
1578
  import glob from "tiny-glob";
1204
1579
  var DOCUMENT_SAVE_OFFSET = 5 * 60 * 1e3;
1205
1580
  var VersionsService = class {
@@ -1218,7 +1593,7 @@ var VersionsService = class {
1218
1593
  const lastRun = await this.getLastRun();
1219
1594
  const lastRunWithOffset = lastRun === 0 ? lastRun : lastRun - DOCUMENT_SAVE_OFFSET;
1220
1595
  const changedDocs = await this.getDocsModifiedAfter(lastRunWithOffset);
1221
- const now = Timestamp3.now().toMillis();
1596
+ const now = Timestamp4.now().toMillis();
1222
1597
  const versions = changedDocs.filter((doc) => {
1223
1598
  const modifiedAt = doc.sys.modifiedAt.toMillis();
1224
1599
  return modifiedAt <= now - DOCUMENT_SAVE_OFFSET;
@@ -1264,7 +1639,7 @@ var VersionsService = class {
1264
1639
  * Saves {versionLastRun: <timestamp>} to the Projects/<projectId> doc.
1265
1640
  */
1266
1641
  async saveLastRun(millis) {
1267
- const ts = Timestamp3.fromMillis(millis);
1642
+ const ts = Timestamp4.fromMillis(millis);
1268
1643
  const projectDocRef = this.db.collection("Projects").doc(this.projectId);
1269
1644
  await projectDocRef.set({ versionsLastRun: ts }, { merge: true });
1270
1645
  }
@@ -1272,7 +1647,7 @@ var VersionsService = class {
1272
1647
  * Returns a list of all docs that were edited after a certain time.
1273
1648
  */
1274
1649
  async getDocsModifiedAfter(millis) {
1275
- const ts = Timestamp3.fromMillis(millis);
1650
+ const ts = Timestamp4.fromMillis(millis);
1276
1651
  const results = [];
1277
1652
  const collectionIds = await this.listCollections();
1278
1653
  for (const collectionId of collectionIds) {
package/dist/project.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { S as Schema } from './schema--Xq_zQlv.js';
1
+ import { S as Schema } from './schema-hlZc-G1f.js';
2
2
  import 'preact';
3
3
 
4
4
  /**
@@ -197,6 +197,17 @@ type Collection = Schema & {
197
197
  src: string;
198
198
  };
199
199
  };
200
+ /**
201
+ * Regular expression used to validate document slugs. Should be provided as a
202
+ * string so it can be serialized to the CMS UI.
203
+ */
204
+ slugRegex?: string;
205
+ /**
206
+ * Automatically add a publishing lock whenever the doc is edited.
207
+ */
208
+ autolock?: boolean;
209
+ /** Reason for the automatic publishing lock. */
210
+ autolockReason?: string;
200
211
  };
201
212
  declare function defineCollection(collection: Omit<Collection, 'id'>): Omit<Collection, 'id'>;
202
213
  declare const collection: typeof defineCollection;