@blinkk/root-cms 1.4.6 → 1.4.8

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/core.js CHANGED
@@ -396,6 +396,35 @@ var RootCMSClient = class {
396
396
  }
397
397
  return null;
398
398
  }
399
+ /**
400
+ * Firestore path for a collection.
401
+ */
402
+ dbCollectionDocsPath(collectionId, options) {
403
+ let modeCollection = "";
404
+ if (options.mode === "draft") {
405
+ modeCollection = "Drafts";
406
+ } else if (options.mode === "published") {
407
+ modeCollection = "Published";
408
+ } else {
409
+ throw new Error(`unknown mode: ${options.mode}`);
410
+ }
411
+ return `Projects/${this.projectId}/Collections/${collectionId}/${modeCollection}`;
412
+ }
413
+ /**
414
+ * Firestore path for a content doc.
415
+ */
416
+ dbDocPath(collectionId, slug, options) {
417
+ const collectionDocsPath = this.dbCollectionDocsPath(collectionId, options);
418
+ const normalizedSlug = slug.replaceAll("/", "--");
419
+ return `${collectionDocsPath}/${normalizedSlug}`;
420
+ }
421
+ /**
422
+ * Firestore doc ref for a content doc.
423
+ */
424
+ dbDocRef(collectionId, slug, options) {
425
+ const docPath = this.dbDocPath(collectionId, slug, options);
426
+ return this.db.doc(docPath);
427
+ }
399
428
  /**
400
429
  * Saves draft data to a doc.
401
430
  *
@@ -796,6 +825,25 @@ var RootCMSClient = class {
796
825
  const translationsMap = await this.loadTranslations(options);
797
826
  return translationsForLocale(translationsMap, locale);
798
827
  }
828
+ /**
829
+ * Firestore path for a translations file.
830
+ */
831
+ dbTranslationsPath(translationsId, options) {
832
+ const mode = options.mode;
833
+ if (!(mode === "draft" || mode === "published")) {
834
+ throw new Error(`invalid mode: ${mode}`);
835
+ }
836
+ const slug = translationsId.replaceAll("/", "--");
837
+ const dbPath = `Projects/${this.projectId}/TranslationsManager/${mode}/Translations/${slug}`;
838
+ return dbPath;
839
+ }
840
+ /**
841
+ * Firestore doc ref for a translations file.
842
+ */
843
+ dbTranslationsRef(translationsId, options) {
844
+ const dbPath = this.dbTranslationsPath(translationsId, options);
845
+ return this.db.doc(dbPath);
846
+ }
799
847
  /**
800
848
  * Returns a data source configuration object.
801
849
  */
@@ -866,7 +914,8 @@ var RootCMSClient = class {
866
914
  const batch = this.db.batch();
867
915
  batch.set(dataDocRefPublished, {
868
916
  dataSource: updatedDataSource,
869
- data: (dataRes == null ? void 0 : dataRes.data) || null
917
+ data: (dataRes == null ? void 0 : dataRes.data) || null,
918
+ ...(dataRes == null ? void 0 : dataRes.headers) ? { headers: dataRes.headers } : {}
870
919
  });
871
920
  batch.update(dataDocRefDraft, {
872
921
  dataSource: updatedDataSource
@@ -917,14 +966,34 @@ var RootCMSClient = class {
917
966
  if (!dataSourceId || dataSourceId.includes("/")) {
918
967
  throw new Error(`invalid data source id: ${dataSourceId}`);
919
968
  }
920
- const dbPath = `Projects/${this.projectId}/DataSources/${dataSourceId}/Data/${mode}`;
921
- const docRef = this.db.doc(dbPath);
969
+ const docRef = this.dbDataSourceDataRef(dataSourceId, { mode });
922
970
  const doc = await docRef.get();
923
971
  if (doc.exists) {
924
972
  return doc.data();
925
973
  }
926
974
  return null;
927
975
  }
976
+ /**
977
+ * Firestore path for a datasource data.
978
+ */
979
+ dbDataSourceDataPath(dataSourceId, options) {
980
+ if (!dataSourceId || dataSourceId.includes("/")) {
981
+ throw new Error(`invalid data source id: ${dataSourceId}`);
982
+ }
983
+ const mode = options.mode;
984
+ if (!(mode === "draft" || mode === "published")) {
985
+ throw new Error(`invalid mode: ${mode}`);
986
+ }
987
+ const dbPath = `Projects/${this.projectId}/DataSources/${dataSourceId}/Data/${mode}`;
988
+ return dbPath;
989
+ }
990
+ /**
991
+ * Firestore doc ref for a datasource data.
992
+ */
993
+ dbDataSourceDataRef(dataSourceId, options) {
994
+ const dbPath = this.dbDataSourceDataPath(dataSourceId, options);
995
+ return this.db.doc(dbPath);
996
+ }
928
997
  /**
929
998
  * Gets the user's role from the project's ACL.
930
999
  */
@@ -996,6 +1065,13 @@ var RootCMSClient = class {
996
1065
  }
997
1066
  }
998
1067
  }
1068
+ /**
1069
+ * Creates a batch request that is capable of fetching one or more docs,
1070
+ * corresponding translations, and dataSources.
1071
+ */
1072
+ createBatchRequest(options) {
1073
+ return new BatchRequest(this, options);
1074
+ }
999
1075
  };
1000
1076
  function isRichTextData(data) {
1001
1077
  return Boolean(
@@ -1141,6 +1217,210 @@ function parseDocId(docId) {
1141
1217
  }
1142
1218
  return { collection: collection2, slug };
1143
1219
  }
1220
+ var BatchRequest = class {
1221
+ constructor(cmsClient, options) {
1222
+ this.docIds = [];
1223
+ this.dataSourceIds = [];
1224
+ this.queries = [];
1225
+ this.translationsIds = [];
1226
+ this.cmsClient = cmsClient;
1227
+ this.db = cmsClient.db;
1228
+ this.options = options;
1229
+ }
1230
+ /**
1231
+ * Adds a doc to the batch request.
1232
+ */
1233
+ addDoc(docId) {
1234
+ this.docIds.push(docId);
1235
+ }
1236
+ /**
1237
+ * Adds a data source to the batch request.
1238
+ */
1239
+ addDataSource(dataSourceId) {
1240
+ this.dataSourceIds.push(dataSourceId);
1241
+ }
1242
+ /**
1243
+ * Adds a collection-based query to the batch request.
1244
+ */
1245
+ addQuery(queryId, collectionId, queryOptions) {
1246
+ this.queries.push({
1247
+ queryId,
1248
+ collectionId,
1249
+ queryOptions
1250
+ });
1251
+ }
1252
+ /**
1253
+ * Adds a translation file to the request.
1254
+ */
1255
+ addTranslations(translationsId) {
1256
+ this.translationsIds.push(translationsId);
1257
+ }
1258
+ /**
1259
+ * Fetches data from the DB.
1260
+ */
1261
+ async fetch() {
1262
+ const res = new BatchResponse();
1263
+ const promises = [
1264
+ this.fetchDocs(res),
1265
+ this.fetchQueries(res),
1266
+ this.fetchDataSources(res)
1267
+ ];
1268
+ if (!this.options.translate && this.translationsIds.length > 0) {
1269
+ promises.push(this.fetchTranslations(res));
1270
+ }
1271
+ await Promise.all(promises);
1272
+ if (this.translationsIds.length > 0) {
1273
+ await this.fetchTranslations(res);
1274
+ }
1275
+ return res;
1276
+ }
1277
+ async fetchDocs(res) {
1278
+ if (this.docIds.length === 0) {
1279
+ return;
1280
+ }
1281
+ const docRefs = this.docIds.map((docId) => {
1282
+ const [collectionId, slug] = docId.split("/");
1283
+ return this.cmsClient.dbDocRef(collectionId, slug, {
1284
+ mode: this.options.mode
1285
+ });
1286
+ });
1287
+ const docs = await this.db.getAll(...docRefs);
1288
+ this.docIds.forEach((docId, i) => {
1289
+ const doc = docs[i];
1290
+ if (!doc.exists) {
1291
+ console.warn(`doc "${docId}" does not exist`);
1292
+ return;
1293
+ }
1294
+ const docData = unmarshalData(doc.data());
1295
+ res.docs[docId] = docData;
1296
+ if (this.options.translate) {
1297
+ this.addTranslations(docId);
1298
+ }
1299
+ });
1300
+ }
1301
+ async fetchQueries(res) {
1302
+ if (this.queries.length === 0) {
1303
+ return;
1304
+ }
1305
+ const mode = this.options.mode;
1306
+ const handleQuery = async (queryItem) => {
1307
+ const docsPath = this.cmsClient.dbCollectionDocsPath(
1308
+ queryItem.collectionId,
1309
+ { mode }
1310
+ );
1311
+ const queryOptions = queryItem.queryOptions || {};
1312
+ let query = this.db.collection(docsPath);
1313
+ if (queryOptions.limit) {
1314
+ query = query.limit(queryOptions.limit);
1315
+ }
1316
+ if (queryOptions.offset) {
1317
+ query = query.offset(queryOptions.offset);
1318
+ }
1319
+ if (queryOptions.orderBy) {
1320
+ query = query.orderBy(
1321
+ queryOptions.orderBy,
1322
+ queryOptions.orderByDirection
1323
+ );
1324
+ }
1325
+ if (queryOptions.query) {
1326
+ query = queryOptions.query(query);
1327
+ }
1328
+ const results = await query.get();
1329
+ const docs = [];
1330
+ results.forEach((result) => {
1331
+ const doc = unmarshalData(result.data());
1332
+ docs.push(doc);
1333
+ });
1334
+ res.queries[queryItem.queryId] = docs;
1335
+ };
1336
+ await Promise.all(this.queries.map((queryItem) => handleQuery(queryItem)));
1337
+ }
1338
+ async fetchDataSources(res) {
1339
+ if (this.dataSourceIds.length === 0) {
1340
+ return;
1341
+ }
1342
+ const docRefs = this.dataSourceIds.map((dataSourceId) => {
1343
+ return this.cmsClient.dbDataSourceDataRef(dataSourceId, {
1344
+ mode: this.options.mode
1345
+ });
1346
+ });
1347
+ const docs = await this.db.getAll(...docRefs);
1348
+ this.dataSourceIds.forEach((dataSourceId, i) => {
1349
+ const doc = docs[i];
1350
+ if (!doc.exists) {
1351
+ console.warn(`"data source "${dataSourceId}" does not exist`);
1352
+ return;
1353
+ }
1354
+ res.dataSources[dataSourceId] = doc.data();
1355
+ });
1356
+ }
1357
+ async fetchTranslations(res) {
1358
+ if (this.translationsIds.length === 0) {
1359
+ return;
1360
+ }
1361
+ const docRefs = this.translationsIds.map((translationsId) => {
1362
+ return this.cmsClient.dbTranslationsRef(translationsId, {
1363
+ mode: this.options.mode
1364
+ });
1365
+ });
1366
+ const docs = await this.db.getAll(...docRefs);
1367
+ this.translationsIds.forEach((translationsId, i) => {
1368
+ const doc = docs[i];
1369
+ if (!doc.exists) {
1370
+ return;
1371
+ }
1372
+ res.translations[translationsId] = doc.data();
1373
+ });
1374
+ }
1375
+ };
1376
+ var BatchResponse = class {
1377
+ constructor() {
1378
+ this.docs = {};
1379
+ this.queries = {};
1380
+ this.dataSources = {};
1381
+ this.translations = {};
1382
+ }
1383
+ /**
1384
+ * Returns a map of translations for a given locale or locale fallbacks.
1385
+ *
1386
+ * The input is either a single locale (e.g. "de") or an array of locales
1387
+ * representing the fallback tree, e.g. ["en-CA", "en-GB", "en"].
1388
+ *
1389
+ * TODO(stevenle): support the locale fallback tree.
1390
+ *
1391
+ * The returned value is a flat map of source string to translated string,
1392
+ * e.g.:
1393
+ * {"<source>": "<translation>"}
1394
+ */
1395
+ getTranslations(locale) {
1396
+ const translationsMap = this.getTranslationsMap();
1397
+ const translations = translationsForLocale(translationsMap, locale);
1398
+ return translations;
1399
+ }
1400
+ /**
1401
+ * Merges the strings from all translations files retrieved in the request.
1402
+ * The returned value is a map of string to translations, e.g.:
1403
+ *
1404
+ * {"<hash>": {"source": "<source>", "<locale>": "<translation>"}}
1405
+ */
1406
+ getTranslationsMap() {
1407
+ const translationsDocs = Object.values(this.translations).reverse();
1408
+ const translationsMap = {};
1409
+ for (const translationsDoc of translationsDocs) {
1410
+ const strings = translationsDoc.strings || {};
1411
+ for (const hash in strings) {
1412
+ const translations = strings[hash];
1413
+ translationsMap[hash] ??= { source: translations.source };
1414
+ for (const locale in translations) {
1415
+ if (locale !== "source" && translations[locale]) {
1416
+ translationsMap[hash][locale] = translations[locale];
1417
+ }
1418
+ }
1419
+ }
1420
+ }
1421
+ return translationsMap;
1422
+ }
1423
+ };
1144
1424
 
1145
1425
  // core/runtime.ts
1146
1426
  import { FieldValue as FieldValue3 } from "firebase-admin/firestore";
@@ -1386,6 +1666,8 @@ function defineCollection(collection2) {
1386
1666
  }
1387
1667
  var collection = defineCollection;
1388
1668
  export {
1669
+ BatchRequest,
1670
+ BatchResponse,
1389
1671
  RootCMSClient,
1390
1672
  TranslationsManager,
1391
1673
  buildTranslationsDbPath,
package/dist/functions.js CHANGED
@@ -395,6 +395,35 @@ var RootCMSClient = class {
395
395
  }
396
396
  return null;
397
397
  }
398
+ /**
399
+ * Firestore path for a collection.
400
+ */
401
+ dbCollectionDocsPath(collectionId, options) {
402
+ let modeCollection = "";
403
+ if (options.mode === "draft") {
404
+ modeCollection = "Drafts";
405
+ } else if (options.mode === "published") {
406
+ modeCollection = "Published";
407
+ } else {
408
+ throw new Error(`unknown mode: ${options.mode}`);
409
+ }
410
+ return `Projects/${this.projectId}/Collections/${collectionId}/${modeCollection}`;
411
+ }
412
+ /**
413
+ * Firestore path for a content doc.
414
+ */
415
+ dbDocPath(collectionId, slug, options) {
416
+ const collectionDocsPath = this.dbCollectionDocsPath(collectionId, options);
417
+ const normalizedSlug = slug.replaceAll("/", "--");
418
+ return `${collectionDocsPath}/${normalizedSlug}`;
419
+ }
420
+ /**
421
+ * Firestore doc ref for a content doc.
422
+ */
423
+ dbDocRef(collectionId, slug, options) {
424
+ const docPath = this.dbDocPath(collectionId, slug, options);
425
+ return this.db.doc(docPath);
426
+ }
398
427
  /**
399
428
  * Saves draft data to a doc.
400
429
  *
@@ -795,6 +824,25 @@ var RootCMSClient = class {
795
824
  const translationsMap = await this.loadTranslations(options);
796
825
  return translationsForLocale(translationsMap, locale);
797
826
  }
827
+ /**
828
+ * Firestore path for a translations file.
829
+ */
830
+ dbTranslationsPath(translationsId, options) {
831
+ const mode = options.mode;
832
+ if (!(mode === "draft" || mode === "published")) {
833
+ throw new Error(`invalid mode: ${mode}`);
834
+ }
835
+ const slug = translationsId.replaceAll("/", "--");
836
+ const dbPath = `Projects/${this.projectId}/TranslationsManager/${mode}/Translations/${slug}`;
837
+ return dbPath;
838
+ }
839
+ /**
840
+ * Firestore doc ref for a translations file.
841
+ */
842
+ dbTranslationsRef(translationsId, options) {
843
+ const dbPath = this.dbTranslationsPath(translationsId, options);
844
+ return this.db.doc(dbPath);
845
+ }
798
846
  /**
799
847
  * Returns a data source configuration object.
800
848
  */
@@ -865,7 +913,8 @@ var RootCMSClient = class {
865
913
  const batch = this.db.batch();
866
914
  batch.set(dataDocRefPublished, {
867
915
  dataSource: updatedDataSource,
868
- data: (dataRes == null ? void 0 : dataRes.data) || null
916
+ data: (dataRes == null ? void 0 : dataRes.data) || null,
917
+ ...(dataRes == null ? void 0 : dataRes.headers) ? { headers: dataRes.headers } : {}
869
918
  });
870
919
  batch.update(dataDocRefDraft, {
871
920
  dataSource: updatedDataSource
@@ -916,14 +965,34 @@ var RootCMSClient = class {
916
965
  if (!dataSourceId || dataSourceId.includes("/")) {
917
966
  throw new Error(`invalid data source id: ${dataSourceId}`);
918
967
  }
919
- const dbPath = `Projects/${this.projectId}/DataSources/${dataSourceId}/Data/${mode}`;
920
- const docRef = this.db.doc(dbPath);
968
+ const docRef = this.dbDataSourceDataRef(dataSourceId, { mode });
921
969
  const doc = await docRef.get();
922
970
  if (doc.exists) {
923
971
  return doc.data();
924
972
  }
925
973
  return null;
926
974
  }
975
+ /**
976
+ * Firestore path for a datasource data.
977
+ */
978
+ dbDataSourceDataPath(dataSourceId, options) {
979
+ if (!dataSourceId || dataSourceId.includes("/")) {
980
+ throw new Error(`invalid data source id: ${dataSourceId}`);
981
+ }
982
+ const mode = options.mode;
983
+ if (!(mode === "draft" || mode === "published")) {
984
+ throw new Error(`invalid mode: ${mode}`);
985
+ }
986
+ const dbPath = `Projects/${this.projectId}/DataSources/${dataSourceId}/Data/${mode}`;
987
+ return dbPath;
988
+ }
989
+ /**
990
+ * Firestore doc ref for a datasource data.
991
+ */
992
+ dbDataSourceDataRef(dataSourceId, options) {
993
+ const dbPath = this.dbDataSourceDataPath(dataSourceId, options);
994
+ return this.db.doc(dbPath);
995
+ }
927
996
  /**
928
997
  * Gets the user's role from the project's ACL.
929
998
  */
@@ -995,6 +1064,13 @@ var RootCMSClient = class {
995
1064
  }
996
1065
  }
997
1066
  }
1067
+ /**
1068
+ * Creates a batch request that is capable of fetching one or more docs,
1069
+ * corresponding translations, and dataSources.
1070
+ */
1071
+ createBatchRequest(options) {
1072
+ return new BatchRequest(this, options);
1073
+ }
998
1074
  };
999
1075
  function isRichTextData(data) {
1000
1076
  return Boolean(
@@ -1129,6 +1205,210 @@ function parseDocId(docId) {
1129
1205
  }
1130
1206
  return { collection, slug };
1131
1207
  }
1208
+ var BatchRequest = class {
1209
+ constructor(cmsClient, options) {
1210
+ this.docIds = [];
1211
+ this.dataSourceIds = [];
1212
+ this.queries = [];
1213
+ this.translationsIds = [];
1214
+ this.cmsClient = cmsClient;
1215
+ this.db = cmsClient.db;
1216
+ this.options = options;
1217
+ }
1218
+ /**
1219
+ * Adds a doc to the batch request.
1220
+ */
1221
+ addDoc(docId) {
1222
+ this.docIds.push(docId);
1223
+ }
1224
+ /**
1225
+ * Adds a data source to the batch request.
1226
+ */
1227
+ addDataSource(dataSourceId) {
1228
+ this.dataSourceIds.push(dataSourceId);
1229
+ }
1230
+ /**
1231
+ * Adds a collection-based query to the batch request.
1232
+ */
1233
+ addQuery(queryId, collectionId, queryOptions) {
1234
+ this.queries.push({
1235
+ queryId,
1236
+ collectionId,
1237
+ queryOptions
1238
+ });
1239
+ }
1240
+ /**
1241
+ * Adds a translation file to the request.
1242
+ */
1243
+ addTranslations(translationsId) {
1244
+ this.translationsIds.push(translationsId);
1245
+ }
1246
+ /**
1247
+ * Fetches data from the DB.
1248
+ */
1249
+ async fetch() {
1250
+ const res = new BatchResponse();
1251
+ const promises = [
1252
+ this.fetchDocs(res),
1253
+ this.fetchQueries(res),
1254
+ this.fetchDataSources(res)
1255
+ ];
1256
+ if (!this.options.translate && this.translationsIds.length > 0) {
1257
+ promises.push(this.fetchTranslations(res));
1258
+ }
1259
+ await Promise.all(promises);
1260
+ if (this.translationsIds.length > 0) {
1261
+ await this.fetchTranslations(res);
1262
+ }
1263
+ return res;
1264
+ }
1265
+ async fetchDocs(res) {
1266
+ if (this.docIds.length === 0) {
1267
+ return;
1268
+ }
1269
+ const docRefs = this.docIds.map((docId) => {
1270
+ const [collectionId, slug] = docId.split("/");
1271
+ return this.cmsClient.dbDocRef(collectionId, slug, {
1272
+ mode: this.options.mode
1273
+ });
1274
+ });
1275
+ const docs = await this.db.getAll(...docRefs);
1276
+ this.docIds.forEach((docId, i) => {
1277
+ const doc = docs[i];
1278
+ if (!doc.exists) {
1279
+ console.warn(`doc "${docId}" does not exist`);
1280
+ return;
1281
+ }
1282
+ const docData = unmarshalData(doc.data());
1283
+ res.docs[docId] = docData;
1284
+ if (this.options.translate) {
1285
+ this.addTranslations(docId);
1286
+ }
1287
+ });
1288
+ }
1289
+ async fetchQueries(res) {
1290
+ if (this.queries.length === 0) {
1291
+ return;
1292
+ }
1293
+ const mode = this.options.mode;
1294
+ const handleQuery = async (queryItem) => {
1295
+ const docsPath = this.cmsClient.dbCollectionDocsPath(
1296
+ queryItem.collectionId,
1297
+ { mode }
1298
+ );
1299
+ const queryOptions = queryItem.queryOptions || {};
1300
+ let query = this.db.collection(docsPath);
1301
+ if (queryOptions.limit) {
1302
+ query = query.limit(queryOptions.limit);
1303
+ }
1304
+ if (queryOptions.offset) {
1305
+ query = query.offset(queryOptions.offset);
1306
+ }
1307
+ if (queryOptions.orderBy) {
1308
+ query = query.orderBy(
1309
+ queryOptions.orderBy,
1310
+ queryOptions.orderByDirection
1311
+ );
1312
+ }
1313
+ if (queryOptions.query) {
1314
+ query = queryOptions.query(query);
1315
+ }
1316
+ const results = await query.get();
1317
+ const docs = [];
1318
+ results.forEach((result) => {
1319
+ const doc = unmarshalData(result.data());
1320
+ docs.push(doc);
1321
+ });
1322
+ res.queries[queryItem.queryId] = docs;
1323
+ };
1324
+ await Promise.all(this.queries.map((queryItem) => handleQuery(queryItem)));
1325
+ }
1326
+ async fetchDataSources(res) {
1327
+ if (this.dataSourceIds.length === 0) {
1328
+ return;
1329
+ }
1330
+ const docRefs = this.dataSourceIds.map((dataSourceId) => {
1331
+ return this.cmsClient.dbDataSourceDataRef(dataSourceId, {
1332
+ mode: this.options.mode
1333
+ });
1334
+ });
1335
+ const docs = await this.db.getAll(...docRefs);
1336
+ this.dataSourceIds.forEach((dataSourceId, i) => {
1337
+ const doc = docs[i];
1338
+ if (!doc.exists) {
1339
+ console.warn(`"data source "${dataSourceId}" does not exist`);
1340
+ return;
1341
+ }
1342
+ res.dataSources[dataSourceId] = doc.data();
1343
+ });
1344
+ }
1345
+ async fetchTranslations(res) {
1346
+ if (this.translationsIds.length === 0) {
1347
+ return;
1348
+ }
1349
+ const docRefs = this.translationsIds.map((translationsId) => {
1350
+ return this.cmsClient.dbTranslationsRef(translationsId, {
1351
+ mode: this.options.mode
1352
+ });
1353
+ });
1354
+ const docs = await this.db.getAll(...docRefs);
1355
+ this.translationsIds.forEach((translationsId, i) => {
1356
+ const doc = docs[i];
1357
+ if (!doc.exists) {
1358
+ return;
1359
+ }
1360
+ res.translations[translationsId] = doc.data();
1361
+ });
1362
+ }
1363
+ };
1364
+ var BatchResponse = class {
1365
+ constructor() {
1366
+ this.docs = {};
1367
+ this.queries = {};
1368
+ this.dataSources = {};
1369
+ this.translations = {};
1370
+ }
1371
+ /**
1372
+ * Returns a map of translations for a given locale or locale fallbacks.
1373
+ *
1374
+ * The input is either a single locale (e.g. "de") or an array of locales
1375
+ * representing the fallback tree, e.g. ["en-CA", "en-GB", "en"].
1376
+ *
1377
+ * TODO(stevenle): support the locale fallback tree.
1378
+ *
1379
+ * The returned value is a flat map of source string to translated string,
1380
+ * e.g.:
1381
+ * {"<source>": "<translation>"}
1382
+ */
1383
+ getTranslations(locale) {
1384
+ const translationsMap = this.getTranslationsMap();
1385
+ const translations = translationsForLocale(translationsMap, locale);
1386
+ return translations;
1387
+ }
1388
+ /**
1389
+ * Merges the strings from all translations files retrieved in the request.
1390
+ * The returned value is a map of string to translations, e.g.:
1391
+ *
1392
+ * {"<hash>": {"source": "<source>", "<locale>": "<translation>"}}
1393
+ */
1394
+ getTranslationsMap() {
1395
+ const translationsDocs = Object.values(this.translations).reverse();
1396
+ const translationsMap = {};
1397
+ for (const translationsDoc of translationsDocs) {
1398
+ const strings = translationsDoc.strings || {};
1399
+ for (const hash in strings) {
1400
+ const translations = strings[hash];
1401
+ translationsMap[hash] ??= { source: translations.source };
1402
+ for (const locale in translations) {
1403
+ if (locale !== "source" && translations[locale]) {
1404
+ translationsMap[hash][locale] = translations[locale];
1405
+ }
1406
+ }
1407
+ }
1408
+ }
1409
+ return translationsMap;
1410
+ }
1411
+ };
1132
1412
 
1133
1413
  // core/versions.ts
1134
1414
  import path from "node:path";
package/dist/plugin.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  import '@blinkk/root';
2
2
  import 'firebase-admin/app';
3
3
  import 'firebase-admin/firestore';
4
- export { N as CMSAIConfig, Q as CMSPlugin, P as CMSPluginOptions, O as CMSSidebarTool, K as CMSUser, V as cmsPlugin } from './client-c_QAurxr.js';
4
+ export { X as CMSAIConfig, _ as CMSPlugin, Z as CMSPluginOptions, Y as CMSSidebarTool, W as CMSUser, $ as cmsPlugin } from './client-AE0BYn69.js';