@blinkk/root-cms 3.0.1-alpha.1 → 3.0.1-beta.3

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.
@@ -1,3 +1,7 @@
1
+ import {
2
+ validateFields
3
+ } from "./chunk-CRK7N6RR.js";
4
+
1
5
  // core/client.ts
2
6
  import crypto from "crypto";
3
7
  import {
@@ -358,301 +362,6 @@ function buildTranslationsLocaleDocDbPath(options) {
358
362
  ).replace("{mode}", options.mode).replace("{id}", normalizeSlug(options.id)).replace("{locale}", options.locale);
359
363
  }
360
364
 
361
- // core/validation.ts
362
- function validateFields(fieldsData, schema) {
363
- if (fieldsData === null || fieldsData === void 0) {
364
- return [];
365
- }
366
- if (typeof fieldsData !== "object" || Array.isArray(fieldsData)) {
367
- return [
368
- {
369
- path: "",
370
- message: "Expected object for fields data",
371
- expected: "object",
372
- received: getType(fieldsData)
373
- }
374
- ];
375
- }
376
- const errors = [];
377
- for (const field of schema.fields) {
378
- if (!field.id) {
379
- continue;
380
- }
381
- if (!(field.id in fieldsData)) {
382
- continue;
383
- }
384
- const value = fieldsData[field.id];
385
- errors.push(...validateValue(value, field, field.id));
386
- }
387
- return errors;
388
- }
389
- function validateValue(value, field, path) {
390
- if (value === void 0) {
391
- return [];
392
- }
393
- switch (field.type) {
394
- case "string":
395
- case "select":
396
- if (typeof value !== "string") {
397
- return [createError(path, "string", value)];
398
- }
399
- return [];
400
- case "number":
401
- if (typeof value !== "number") {
402
- return [createError(path, "number", value)];
403
- }
404
- if (isNaN(value)) {
405
- return [createError(path, "number", value)];
406
- }
407
- return [];
408
- case "boolean":
409
- if (typeof value !== "boolean") {
410
- return [createError(path, "boolean", value)];
411
- }
412
- return [];
413
- case "date":
414
- case "datetime": {
415
- if (typeof value !== "object" || Array.isArray(value)) {
416
- return [createError(path, "object", value)];
417
- }
418
- const errors = [];
419
- const seconds = value.seconds;
420
- const nanoseconds = value.nanoseconds;
421
- if (seconds === void 0) {
422
- errors.push({
423
- path: `${path}.seconds`,
424
- message: "Required",
425
- expected: "number",
426
- received: "undefined"
427
- });
428
- } else if (typeof seconds !== "number") {
429
- errors.push(createError(`${path}.seconds`, "number", seconds));
430
- }
431
- if (nanoseconds === void 0) {
432
- errors.push({
433
- path: `${path}.nanoseconds`,
434
- message: "Required",
435
- expected: "number",
436
- received: "undefined"
437
- });
438
- } else if (typeof nanoseconds !== "number") {
439
- errors.push(createError(`${path}.nanoseconds`, "number", nanoseconds));
440
- }
441
- return errors;
442
- }
443
- case "multiselect":
444
- if (!Array.isArray(value)) {
445
- return [createError(path, "array", value)];
446
- }
447
- return value.flatMap((item, index) => {
448
- if (typeof item !== "string") {
449
- return [createError(`${path}.${index}`, "string", item)];
450
- }
451
- return [];
452
- });
453
- case "image":
454
- case "file": {
455
- if (typeof value !== "object" || Array.isArray(value)) {
456
- return [createError(path, "object", value)];
457
- }
458
- const errors = [];
459
- if (value.src === void 0) {
460
- errors.push({
461
- path: `${path}.src`,
462
- message: "Required",
463
- expected: "string",
464
- received: "undefined"
465
- });
466
- } else if (typeof value.src !== "string") {
467
- errors.push(createError(`${path}.src`, "string", value.src));
468
- }
469
- if (value.alt !== void 0 && typeof value.alt !== "string") {
470
- errors.push(createError(`${path}.alt`, "string", value.alt));
471
- }
472
- return errors;
473
- }
474
- case "object": {
475
- if (typeof value !== "object" || Array.isArray(value)) {
476
- return [createError(path, "object", value)];
477
- }
478
- const objectField = field;
479
- const errors = [];
480
- for (const nestedField of objectField.fields) {
481
- if (!nestedField.id || !(nestedField.id in value)) {
482
- continue;
483
- }
484
- errors.push(
485
- ...validateValue(
486
- value[nestedField.id],
487
- nestedField,
488
- `${path}.${nestedField.id}`
489
- )
490
- );
491
- }
492
- return errors;
493
- }
494
- case "array": {
495
- if (!Array.isArray(value)) {
496
- return [createError(path, "array", value)];
497
- }
498
- const arrayField = field;
499
- const itemField = arrayField.of;
500
- return value.flatMap((item, index) => {
501
- return validateValue(item, itemField, `${path}.${index}`);
502
- });
503
- }
504
- case "oneof": {
505
- if (typeof value !== "object" || Array.isArray(value)) {
506
- return [createError(path, "object", value)];
507
- }
508
- const oneOfField = field;
509
- const typeName = value._type;
510
- const typeMap = /* @__PURE__ */ new Map();
511
- const typeNames = [];
512
- for (const type of oneOfField.types) {
513
- if (typeof type === "string") {
514
- typeNames.push(type);
515
- continue;
516
- }
517
- typeMap.set(type.name, type);
518
- typeNames.push(type.name);
519
- }
520
- if (!typeNames.includes(typeName)) {
521
- const expectedStr = typeNames.map((t) => `'${t}'`).join(" | ");
522
- return [
523
- {
524
- path: `${path}._type`,
525
- message: `Invalid discriminator value. Expected ${expectedStr}`,
526
- expected: "valid discriminator value",
527
- received: typeName
528
- }
529
- ];
530
- }
531
- const matchedSchema = typeMap.get(typeName);
532
- if (!matchedSchema) {
533
- return [];
534
- }
535
- const errors = [];
536
- for (const nestedField of matchedSchema.fields) {
537
- if (!nestedField.id || !(nestedField.id in value)) {
538
- continue;
539
- }
540
- errors.push(
541
- ...validateValue(
542
- value[nestedField.id],
543
- nestedField,
544
- `${path}.${nestedField.id}`
545
- )
546
- );
547
- }
548
- return errors;
549
- }
550
- case "richtext": {
551
- if (typeof value !== "object" || Array.isArray(value)) {
552
- return [createError(path, "object", value)];
553
- }
554
- const errors = [];
555
- if (value.blocks === void 0) {
556
- errors.push({
557
- path: `${path}.blocks`,
558
- message: "Required",
559
- expected: "array",
560
- received: "undefined"
561
- });
562
- } else if (!Array.isArray(value.blocks)) {
563
- errors.push(createError(`${path}.blocks`, "array", value.blocks));
564
- } else {
565
- value.blocks.forEach((block, index) => {
566
- if (typeof block !== "object" || block === null) {
567
- errors.push(
568
- createError(`${path}.blocks.${index}`, "object", block)
569
- );
570
- return;
571
- }
572
- if (block.type === void 0) {
573
- errors.push({
574
- path: `${path}.blocks.${index}.type`,
575
- message: "Required",
576
- expected: "string",
577
- received: "undefined"
578
- });
579
- } else if (typeof block.type !== "string") {
580
- errors.push(
581
- createError(`${path}.blocks.${index}.type`, "string", block.type)
582
- );
583
- }
584
- });
585
- }
586
- return errors;
587
- }
588
- case "reference": {
589
- if (typeof value !== "object" || Array.isArray(value)) {
590
- return [createError(path, "object", value)];
591
- }
592
- const errors = [];
593
- const requiredFields = ["id", "collection", "slug"];
594
- for (const req of requiredFields) {
595
- if (value[req] === void 0) {
596
- errors.push({
597
- path: `${path}.${req}`,
598
- message: "Required",
599
- expected: "string",
600
- received: "undefined"
601
- });
602
- } else if (typeof value[req] !== "string") {
603
- errors.push(createError(`${path}.${req}`, "string", value[req]));
604
- }
605
- }
606
- return errors;
607
- }
608
- case "references": {
609
- if (!Array.isArray(value)) {
610
- return [createError(path, "array", value)];
611
- }
612
- return value.flatMap((item, index) => {
613
- if (typeof item !== "object" || item === null || Array.isArray(item)) {
614
- return [createError(`${path}.${index}`, "object", item)];
615
- }
616
- const errors = [];
617
- const requiredFields = ["id", "collection", "slug"];
618
- for (const req of requiredFields) {
619
- if (item[req] === void 0) {
620
- errors.push({
621
- path: `${path}.${index}.${req}`,
622
- message: "Required",
623
- expected: "string",
624
- received: "undefined"
625
- });
626
- } else if (typeof item[req] !== "string") {
627
- errors.push(
628
- createError(`${path}.${index}.${req}`, "string", item[req])
629
- );
630
- }
631
- }
632
- return errors;
633
- });
634
- }
635
- default:
636
- console.warn(`Unknown field type: ${field.type}`);
637
- return [];
638
- }
639
- }
640
- function getType(value) {
641
- if (value === null) return "null";
642
- if (Array.isArray(value)) return "array";
643
- if (typeof value === "number" && Number.isNaN(value)) return "nan";
644
- return typeof value;
645
- }
646
- function createError(path, expected, receivedValue) {
647
- const received = getType(receivedValue);
648
- return {
649
- path,
650
- message: `Expected ${expected}, received ${received}`,
651
- expected,
652
- received
653
- };
654
- }
655
-
656
365
  // core/values.ts
657
366
  function setValueAtPath(obj, path, value) {
658
367
  const keys = path.split(".");
@@ -1501,10 +1210,41 @@ ${errorMessages}`);
1501
1210
  const docRef = this.db.doc(dbPath);
1502
1211
  const doc = await docRef.get();
1503
1212
  if (doc.exists) {
1504
- return doc.data();
1213
+ const dataSource = doc.data();
1214
+ if (dataSource.archivedAt) {
1215
+ console.warn(
1216
+ `warning: data source "${dataSourceId}" is archived` + (dataSource.archivedBy ? ` (archived by ${dataSource.archivedBy})` : "")
1217
+ );
1218
+ }
1219
+ return dataSource;
1505
1220
  }
1506
1221
  return null;
1507
1222
  }
1223
+ /**
1224
+ * Archives a data source. Archived data sources cannot be synced or published.
1225
+ */
1226
+ async archiveDataSource(dataSourceId, options) {
1227
+ const dbPath = `Projects/${this.projectId}/DataSources/${dataSourceId}`;
1228
+ const docRef = this.db.doc(dbPath);
1229
+ const archivedBy = options?.archivedBy || "root-cms-client";
1230
+ await docRef.update({
1231
+ archivedAt: Timestamp2.now(),
1232
+ archivedBy
1233
+ });
1234
+ console.log(`archived data source: ${dataSourceId}`);
1235
+ }
1236
+ /**
1237
+ * Unarchives a data source.
1238
+ */
1239
+ async unarchiveDataSource(dataSourceId) {
1240
+ const dbPath = `Projects/${this.projectId}/DataSources/${dataSourceId}`;
1241
+ const docRef = this.db.doc(dbPath);
1242
+ await docRef.update({
1243
+ archivedAt: FieldValue2.delete(),
1244
+ archivedBy: FieldValue2.delete()
1245
+ });
1246
+ console.log(`unarchived data source: ${dataSourceId}`);
1247
+ }
1508
1248
  /**
1509
1249
  * Syncs a data source to draft state.
1510
1250
  */
@@ -1513,6 +1253,9 @@ ${errorMessages}`);
1513
1253
  if (!dataSource) {
1514
1254
  throw new Error(`data source not found: ${dataSourceId}`);
1515
1255
  }
1256
+ if (dataSource.archivedAt) {
1257
+ throw new Error(`data source is archived: ${dataSourceId}`);
1258
+ }
1516
1259
  const result = await this.fetchData(dataSource);
1517
1260
  const dataSourceDocRef = this.db.doc(
1518
1261
  `Projects/${this.projectId}/DataSources/${dataSourceId}`
@@ -1545,6 +1288,9 @@ ${errorMessages}`);
1545
1288
  if (!dataSource) {
1546
1289
  throw new Error(`data source not found: ${dataSourceId}`);
1547
1290
  }
1291
+ if (dataSource.archivedAt) {
1292
+ throw new Error(`data source is archived: ${dataSourceId}`);
1293
+ }
1548
1294
  const dataSourceDocRef = this.db.doc(
1549
1295
  `Projects/${this.projectId}/DataSources/${dataSourceId}`
1550
1296
  );
@@ -1578,6 +1324,40 @@ ${errorMessages}`);
1578
1324
  console.log(`published data ${dataSourceId}`);
1579
1325
  console.log(`published by: ${publishedBy}`);
1580
1326
  }
1327
+ /**
1328
+ * Unpublishes a data source. Removes the `publishedAt`/`publishedBy`
1329
+ * metadata from the DataSource doc and deletes the `Data/published` doc.
1330
+ */
1331
+ async unpublishDataSource(dataSourceId) {
1332
+ const dataSource = await this.getDataSource(dataSourceId);
1333
+ if (!dataSource) {
1334
+ throw new Error(`data source not found: ${dataSourceId}`);
1335
+ }
1336
+ const dataSourceDocRef = this.db.doc(
1337
+ `Projects/${this.projectId}/DataSources/${dataSourceId}`
1338
+ );
1339
+ const dataDocRefDraft = this.db.doc(
1340
+ `Projects/${this.projectId}/DataSources/${dataSourceId}/Data/draft`
1341
+ );
1342
+ const dataDocRefPublished = this.db.doc(
1343
+ `Projects/${this.projectId}/DataSources/${dataSourceId}/Data/published`
1344
+ );
1345
+ const batch = this.db.batch();
1346
+ batch.update(dataSourceDocRef, {
1347
+ publishedAt: FieldValue2.delete(),
1348
+ publishedBy: FieldValue2.delete()
1349
+ });
1350
+ const draftSnapshot = await dataDocRefDraft.get();
1351
+ if (draftSnapshot.exists) {
1352
+ batch.update(dataDocRefDraft, {
1353
+ "dataSource.publishedAt": FieldValue2.delete(),
1354
+ "dataSource.publishedBy": FieldValue2.delete()
1355
+ });
1356
+ }
1357
+ batch.delete(dataDocRefPublished);
1358
+ await batch.commit();
1359
+ console.log(`unpublished data source: ${dataSourceId}`);
1360
+ }
1581
1361
  async publishDataSources(dataSourceIds, options) {
1582
1362
  const publishedBy = options?.publishedBy || "root-cms-client";
1583
1363
  const batch = options?.batch || this.db.batch();
@@ -1586,6 +1366,9 @@ ${errorMessages}`);
1586
1366
  if (!dataSource) {
1587
1367
  throw new Error(`data source not found: ${id}`);
1588
1368
  }
1369
+ if (dataSource.archivedAt) {
1370
+ throw new Error(`data source is archived: ${id}`);
1371
+ }
1589
1372
  const dataSourceDocRef = this.db.doc(
1590
1373
  `Projects/${this.projectId}/DataSources/${id}`
1591
1374
  );
@@ -1751,7 +1534,14 @@ ${errorMessages}`);
1751
1534
  const docRef = this.dbDataSourceDataRef(dataSourceId, { mode });
1752
1535
  const doc = await docRef.get();
1753
1536
  if (doc.exists) {
1754
- return doc.data();
1537
+ const dataSourceData = doc.data();
1538
+ if (dataSourceData.dataSource?.archivedAt) {
1539
+ const archivedBy = dataSourceData.dataSource.archivedBy;
1540
+ console.warn(
1541
+ `warning: data source "${dataSourceId}" is archived` + (archivedBy ? ` (archived by ${archivedBy})` : "")
1542
+ );
1543
+ }
1544
+ return dataSourceData;
1755
1545
  }
1756
1546
  return null;
1757
1547
  }
@@ -55,10 +55,10 @@ function testValidCollectionId(id) {
55
55
  function isSchemaPattern(value) {
56
56
  return typeof value === "object" && value !== null && "_schemaPattern" in value && value._schemaPattern === true;
57
57
  }
58
- function buildSchemaNameMap() {
58
+ function buildSchemaNameMap(schemaModules = SCHEMA_MODULES) {
59
59
  const nameMap = {};
60
- for (const fileId in SCHEMA_MODULES) {
61
- const module = SCHEMA_MODULES[fileId];
60
+ for (const fileId in schemaModules) {
61
+ const module = schemaModules[fileId];
62
62
  if (module.default && module.default.name) {
63
63
  nameMap[module.default.name] = module.default;
64
64
  }
@@ -69,16 +69,16 @@ function globToRegex(pattern) {
69
69
  const regexStr = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "{{DOUBLE_STAR}}").replace(/\*/g, "[^/]*").replace(/\{\{DOUBLE_STAR\}\}/g, ".*");
70
70
  return new RegExp(`^${regexStr}$`);
71
71
  }
72
- function resolveSchemaPattern(pattern) {
72
+ function resolveSchemaPattern(pattern, schemaModules = SCHEMA_MODULES) {
73
73
  const regex = globToRegex(pattern.pattern);
74
74
  const excludeSet = new Set(pattern.exclude || []);
75
75
  const names = [];
76
76
  const schemas = {};
77
- for (const fileId in SCHEMA_MODULES) {
77
+ for (const fileId in schemaModules) {
78
78
  if (!regex.test(fileId)) {
79
79
  continue;
80
80
  }
81
- const module = SCHEMA_MODULES[fileId];
81
+ const module = schemaModules[fileId];
82
82
  if (!module.default || !module.default.name) {
83
83
  continue;
84
84
  }
@@ -86,28 +86,25 @@ function resolveSchemaPattern(pattern) {
86
86
  if (excludeSet.has(schemaName)) {
87
87
  continue;
88
88
  }
89
- let schemaObj = module.default;
89
+ const schemaObj = structuredClone(module.default);
90
90
  if (pattern.omitFields && pattern.omitFields.length > 0) {
91
91
  const omitSet = new Set(pattern.omitFields);
92
- schemaObj = {
93
- ...schemaObj,
94
- fields: schemaObj.fields.filter(
95
- (f) => !omitSet.has(f.id || "")
96
- )
97
- };
92
+ schemaObj.fields = schemaObj.fields.filter(
93
+ (f) => !omitSet.has(f.id || "")
94
+ );
98
95
  }
99
96
  names.push(schemaName);
100
97
  schemas[schemaName] = schemaObj;
101
98
  }
102
99
  return { names, schemas };
103
100
  }
104
- function convertOneOfTypes(collection) {
101
+ function convertOneOfTypes(collection, schemaModules = SCHEMA_MODULES) {
105
102
  const clone = structuredClone(collection);
106
103
  const types = clone.types || {};
107
- const schemaNameMap = buildSchemaNameMap();
104
+ const schemaNameMap = buildSchemaNameMap(schemaModules);
108
105
  function handleOneOfField(field) {
109
106
  if (isSchemaPattern(field.types)) {
110
- const resolved = resolveSchemaPattern(field.types);
107
+ const resolved = resolveSchemaPattern(field.types, schemaModules);
111
108
  for (const [name, schemaObj] of Object.entries(resolved.schemas)) {
112
109
  if (!types[name]) {
113
110
  types[name] = schemaObj;
@@ -124,7 +121,7 @@ function convertOneOfTypes(collection) {
124
121
  if (typeof sub === "string") {
125
122
  names.push(sub);
126
123
  if (!types[sub] && schemaNameMap[sub]) {
127
- const resolvedSchema = schemaNameMap[sub];
124
+ const resolvedSchema = structuredClone(schemaNameMap[sub]);
128
125
  types[sub] = resolvedSchema;
129
126
  if (resolvedSchema.fields) {
130
127
  walk(resolvedSchema);
@@ -166,5 +163,6 @@ export {
166
163
  SCHEMA_MODULES,
167
164
  getProjectSchemas,
168
165
  resolveOneOfPatterns,
169
- getCollectionSchema
166
+ getCollectionSchema,
167
+ convertOneOfTypes
170
168
  };