@cms0/cms0 0.2.11 → 0.2.12

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.
@@ -20,6 +20,10 @@ const fileFieldDescriptors = {
20
20
  const fileDescriptor = {
21
21
  kind: "model",
22
22
  properties: fileFieldDescriptors,
23
+ presentation: {
24
+ kind: "asset",
25
+ assetKind: "file",
26
+ },
23
27
  };
24
28
  const imageDescriptor = {
25
29
  kind: "model",
@@ -29,6 +33,10 @@ const imageDescriptor = {
29
33
  height: primitive("number"),
30
34
  alt: { kind: "primitive", type: "string", optional: true, nullable: false },
31
35
  },
36
+ presentation: {
37
+ kind: "asset",
38
+ assetKind: "image",
39
+ },
32
40
  };
33
41
  const videoDescriptor = {
34
42
  kind: "model",
@@ -39,6 +47,10 @@ const videoDescriptor = {
39
47
  length: primitive("number"),
40
48
  alt: { kind: "primitive", type: "string", optional: true, nullable: false },
41
49
  },
50
+ presentation: {
51
+ kind: "asset",
52
+ assetKind: "video",
53
+ },
42
54
  };
43
55
  const richTextDescriptor = {
44
56
  kind: "primitive",
@@ -1,8 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.toTwitter = exports.toOpenGraph = exports.toNextMetadata = exports.resolveLocalized = void 0;
3
+ exports.toTwitter = exports.toOpenGraph = exports.toNextMetadata = exports.resolveLocalized = exports.unwrapCms0CanvasTransportValue = exports.restoreCms0CanvasTransportMetadata = exports.readCms0ProvenanceValueMeta = exports.readCms0LiveRoots = exports.registerCms0CollectionItemIdentity = exports.registerCms0LiveRoot = exports.readCms0CollectionItemIdentity = exports.readCms0CanvasTransportCollectionItemId = exports.enableCms0ProvenanceTracking = exports.captureCms0Provenance = exports.attachCms0ProvenanceRoot = exports.activateCms0CanvasTransport = void 0;
4
4
  exports.createCmsClient = createCmsClient;
5
5
  exports.cms0 = cms0;
6
+ const provenance_1 = require("@cms0/cms0/provenance");
6
7
  const schema_descriptors_1 = require("@cms0/cms0/schema-descriptors");
7
8
  const shared_1 = require("@cms0/shared");
8
9
  const registry_js_1 = require("./custom-types/registry.cjs");
@@ -513,6 +514,44 @@ function ensureCollectionEnvelope(data, path) {
513
514
  }
514
515
  throw new Error(`${COLLECTION_SHAPE_ERROR} Path: '${path}'.`);
515
516
  }
517
+ async function readCanvasModelRefCollectionIdentities(path, descriptor, context) {
518
+ const cache = context.canvasModelRefCollectionIdentityCache;
519
+ if (!cache)
520
+ return null;
521
+ const cacheKey = [
522
+ path,
523
+ descriptor.model,
524
+ context.options.locale ?? "",
525
+ ].join("::");
526
+ const existing = cache.get(cacheKey);
527
+ if (existing) {
528
+ return existing;
529
+ }
530
+ const pending = (async () => {
531
+ try {
532
+ const rawCollection = await context.requestJson(path, {
533
+ query: {
534
+ raw: 1,
535
+ includeId: 1,
536
+ resolveModelRefs: 0,
537
+ ...(context.options.locale ? { locale: context.options.locale } : {}),
538
+ },
539
+ });
540
+ const envelope = ensureCollectionEnvelope(rawCollection, path);
541
+ return envelope.items.map((row) => ({
542
+ relationId: extractId(row) ?? "",
543
+ modelId: extractModelRefId(row, descriptor.model, {
544
+ allowObjectIdFallback: false,
545
+ }) ?? null,
546
+ }));
547
+ }
548
+ catch {
549
+ return null;
550
+ }
551
+ })();
552
+ cache.set(cacheKey, pending);
553
+ return pending;
554
+ }
516
555
  async function normalizeModelRef(modelName, id, context, trail, isCollectionItem, inlineValue) {
517
556
  if (!id)
518
557
  return null;
@@ -615,7 +654,7 @@ async function normalizeObjectField(descriptor, path, raw, context, trail, isCol
615
654
  ...(context.options.locale ? { locale: context.options.locale } : {}),
616
655
  },
617
656
  });
618
- const normalizedChild = await normalizeArrayField(propertyDescriptor, childPath, childRaw, context, trail);
657
+ const normalizedChild = await normalizeArrayField(propertyDescriptor, childPath, childRaw, context, trail, inlineChildRaw !== undefined);
619
658
  output[propertyName] = coerceCustomTypeValue(propertyDescriptor, normalizedChild, {
620
659
  locale: context.options.locale,
621
660
  defaultLocale: context.options.defaultLocale,
@@ -658,7 +697,7 @@ async function normalizeObjectField(descriptor, path, raw, context, trail, isCol
658
697
  }
659
698
  return maybeAttachAssetUrl(output, context.options.assetUrlBuilder);
660
699
  }
661
- async function normalizeArrayField(descriptor, path, raw, context, trail) {
700
+ async function normalizeArrayField(descriptor, path, raw, context, trail, inlineResolvedItems = false) {
662
701
  const envelope = ensureCollectionEnvelope(raw, path);
663
702
  const itemDescriptor = descriptor.items;
664
703
  if (isScalarDescriptor(itemDescriptor)) {
@@ -678,14 +717,29 @@ async function normalizeArrayField(descriptor, path, raw, context, trail) {
678
717
  }
679
718
  if (isModelRefDescriptor(itemDescriptor)) {
680
719
  const inlineModelDescriptor = context.modelDescriptors.get(itemDescriptor.model);
681
- return mapWithConcurrency(envelope.items, context.options.modelNormalizationConcurrency, async (row) => {
720
+ const collectionIdentities = await readCanvasModelRefCollectionIdentities(path, itemDescriptor, context);
721
+ return mapWithConcurrency(envelope.items, context.options.modelNormalizationConcurrency, async (row, index) => {
682
722
  const inlineModel = inlineModelDescriptor
683
723
  ? extractInlineModelObject(row, inlineModelDescriptor)
684
724
  : null;
685
725
  const refId = extractModelRefId(row, itemDescriptor.model, {
686
726
  allowObjectIdFallback: false,
687
727
  }) ?? (inlineModel ? extractId(inlineModel) : null);
688
- return normalizeModelRef(itemDescriptor.model, refId, context, trail, true, row);
728
+ const normalized = await normalizeModelRef(itemDescriptor.model, refId, context, trail, true, row);
729
+ const collectionItemId = collectionIdentities?.[index]?.relationId ??
730
+ (inlineResolvedItems ? null : extractId(row));
731
+ if (!collectionItemId ||
732
+ !normalized ||
733
+ typeof normalized !== "object" ||
734
+ Array.isArray(normalized)) {
735
+ return normalized;
736
+ }
737
+ const normalizedClone = {
738
+ ...normalized,
739
+ };
740
+ (0, provenance_1.registerCms0CollectionItemIdentity)(normalizedClone, collectionItemId);
741
+ delete normalizedClone.__cms0CanvasCollectionItemId;
742
+ return normalizedClone;
689
743
  });
690
744
  }
691
745
  if (isObjectDescriptor(itemDescriptor)) {
@@ -1073,7 +1127,19 @@ function validateResult(resource, result, descriptor, includeIdMode, zodSchemas,
1073
1127
  throw new Error(formatValidationError(modelName, parsed.error));
1074
1128
  }
1075
1129
  }
1076
- async function runResource(resource, descriptor, baseUrl, apiKey, defaultLocale, assetUrlBuilder, zodSchemas, modelZodSchemas, globalIncludeId, sharedModelInflightCache, options, byId) {
1130
+ function attachResourceProvenance(resource, value) {
1131
+ return attachResourceProvenanceWithOptions(resource, value);
1132
+ }
1133
+ function attachResourceProvenanceWithOptions(resource, value, options) {
1134
+ if (value === null || value === undefined)
1135
+ return value;
1136
+ (0, provenance_1.enableCms0ProvenanceTracking)(true);
1137
+ const rootId = resource.kind === "root"
1138
+ ? resource.key
1139
+ : resource.modelName ?? resource.key;
1140
+ return (0, provenance_1.attachCms0ProvenanceRoot)(value, rootId, options);
1141
+ }
1142
+ async function runResource(resource, descriptor, baseUrl, apiKey, defaultLocale, assetUrlBuilder, zodSchemas, modelZodSchemas, globalIncludeId, sharedModelInflightCache, options, byId, provenanceOptions) {
1077
1143
  const responseMode = options?.response ?? "normalized";
1078
1144
  const includeIdMode = resolveIncludeIdMode(options?.includeId, globalIncludeId);
1079
1145
  const resolveModelRefs = options?.resolveModelRefs !== false;
@@ -1159,6 +1225,9 @@ async function runResource(resource, descriptor, baseUrl, apiKey, defaultLocale,
1159
1225
  modelDescriptors,
1160
1226
  modelCache: new Map(),
1161
1227
  sharedModelInflightCache,
1228
+ canvasModelRefCollectionIdentityCache: useResolvedEndpoint && (0, provenance_1.isCms0CanvasTransportEnabled)()
1229
+ ? new Map()
1230
+ : undefined,
1162
1231
  options: {
1163
1232
  includeIdMode,
1164
1233
  resolveModelRefs,
@@ -1183,16 +1252,16 @@ async function runResource(resource, descriptor, baseUrl, apiKey, defaultLocale,
1183
1252
  const projectedItems = applyFieldProjection(normalizedItems, options?.fields, options?.exclude);
1184
1253
  validateResult(resource, normalizedItems, descriptor, includeIdMode, zodSchemas, modelZodSchemas);
1185
1254
  return {
1186
- items: Array.isArray(projectedItems) ? projectedItems : normalizedItems,
1255
+ items: attachResourceProvenanceWithOptions(resource, Array.isArray(projectedItems) ? projectedItems : normalizedItems, provenanceOptions),
1187
1256
  total: envelope.total,
1188
1257
  };
1189
1258
  }
1190
1259
  validateResult(resource, normalizedItems, descriptor, includeIdMode, zodSchemas, modelZodSchemas);
1191
- return normalizedItems;
1260
+ return attachResourceProvenanceWithOptions(resource, normalizedItems, provenanceOptions);
1192
1261
  }
1193
1262
  const normalized = await normalizeField(resource.descriptor, normalizePath, rawData, context, new Set(), false);
1194
1263
  validateResult(resource, normalized, descriptor, includeIdMode, zodSchemas, modelZodSchemas);
1195
- return applyFieldProjection(normalized, options?.fields, options?.exclude);
1264
+ return attachResourceProvenanceWithOptions(resource, applyFieldProjection(normalized, options?.fields, options?.exclude), provenanceOptions);
1196
1265
  }
1197
1266
  function createCmsClient(descriptor, config) {
1198
1267
  const baseUrl = normalizeBaseUrl(config.apiConfig?.baseUrl);
@@ -1228,6 +1297,29 @@ function createCmsClient(descriptor, config) {
1228
1297
  defaultLocale: config.defaultLocale,
1229
1298
  includeIdDefault: !!config.includeId,
1230
1299
  };
1300
+ const readRoot = async (rootId, options) => {
1301
+ const entry = registry.roots.get(rootId) ??
1302
+ registry.rootsCaseInsensitive.get(rootId.toLowerCase());
1303
+ if (!entry) {
1304
+ throw new Error(`cms0: unknown root '${rootId}' for Canvas read-back.`);
1305
+ }
1306
+ return runResource(entry, descriptor, baseUrl, options?.apiKey ?? apiKey, config.defaultLocale, assetUrlBuilder, zodSchemas, modelZodSchemas, globalIncludeId, sharedModelInflightCache, {
1307
+ includeId: options?.includeId ?? true,
1308
+ locale: options?.locale ?? "all",
1309
+ }, undefined, {
1310
+ registerLiveRoot: false,
1311
+ });
1312
+ };
1313
+ globalThis.__CMS0_CANVAS_CLIENT__ = {
1314
+ apiBaseUrl: baseUrl,
1315
+ locales: clientMeta.locales,
1316
+ defaultLocale: clientMeta.defaultLocale,
1317
+ publishTriggerId: typeof config.canvas?.publishTriggerId === "string" &&
1318
+ config.canvas.publishTriggerId.trim().length > 0
1319
+ ? config.canvas.publishTriggerId.trim()
1320
+ : undefined,
1321
+ readRoot,
1322
+ };
1231
1323
  const rootProxyTarget = {
1232
1324
  models: modelsProxy,
1233
1325
  meta: clientMeta,
@@ -1255,6 +1347,19 @@ function createCmsClient(descriptor, config) {
1255
1347
  function cms0(config) {
1256
1348
  return createCmsClient(schema_descriptors_1.schemaDescriptor, config);
1257
1349
  }
1350
+ var provenance_js_1 = require("./provenance.cjs");
1351
+ Object.defineProperty(exports, "activateCms0CanvasTransport", { enumerable: true, get: function () { return provenance_js_1.activateCms0CanvasTransport; } });
1352
+ Object.defineProperty(exports, "attachCms0ProvenanceRoot", { enumerable: true, get: function () { return provenance_js_1.attachCms0ProvenanceRoot; } });
1353
+ Object.defineProperty(exports, "captureCms0Provenance", { enumerable: true, get: function () { return provenance_js_1.captureCms0Provenance; } });
1354
+ Object.defineProperty(exports, "enableCms0ProvenanceTracking", { enumerable: true, get: function () { return provenance_js_1.enableCms0ProvenanceTracking; } });
1355
+ Object.defineProperty(exports, "readCms0CanvasTransportCollectionItemId", { enumerable: true, get: function () { return provenance_js_1.readCms0CanvasTransportCollectionItemId; } });
1356
+ Object.defineProperty(exports, "readCms0CollectionItemIdentity", { enumerable: true, get: function () { return provenance_js_1.readCms0CollectionItemIdentity; } });
1357
+ Object.defineProperty(exports, "registerCms0LiveRoot", { enumerable: true, get: function () { return provenance_js_1.registerCms0LiveRoot; } });
1358
+ Object.defineProperty(exports, "registerCms0CollectionItemIdentity", { enumerable: true, get: function () { return provenance_js_1.registerCms0CollectionItemIdentity; } });
1359
+ Object.defineProperty(exports, "readCms0LiveRoots", { enumerable: true, get: function () { return provenance_js_1.readCms0LiveRoots; } });
1360
+ Object.defineProperty(exports, "readCms0ProvenanceValueMeta", { enumerable: true, get: function () { return provenance_js_1.readCms0ProvenanceValueMeta; } });
1361
+ Object.defineProperty(exports, "restoreCms0CanvasTransportMetadata", { enumerable: true, get: function () { return provenance_js_1.restoreCms0CanvasTransportMetadata; } });
1362
+ Object.defineProperty(exports, "unwrapCms0CanvasTransportValue", { enumerable: true, get: function () { return provenance_js_1.unwrapCms0CanvasTransportValue; } });
1258
1363
  var seo_js_1 = require("./seo.cjs");
1259
1364
  Object.defineProperty(exports, "resolveLocalized", { enumerable: true, get: function () { return seo_js_1.resolveLocalized; } });
1260
1365
  Object.defineProperty(exports, "toNextMetadata", { enumerable: true, get: function () { return seo_js_1.toNextMetadata; } });
@@ -74,6 +74,65 @@ function collectModelNames(sourceFiles) {
74
74
  });
75
75
  return names;
76
76
  }
77
+ function isDescriptorSourceFile(filePath) {
78
+ if (filePath.includes("node_modules"))
79
+ return false;
80
+ if (filePath.endsWith(".d.ts"))
81
+ return false;
82
+ return true;
83
+ }
84
+ function collectTypeSourceFiles(type, sourceFiles, visited) {
85
+ const { base } = unwrapOptional(type);
86
+ const aliasSymbol = base.getAliasSymbol();
87
+ const symbol = base.getSymbol();
88
+ const key = `${aliasSymbol?.getName() ?? symbol?.getName() ?? "<anon>"}:${base.getText()}`;
89
+ if (visited.has(key))
90
+ return;
91
+ visited.add(key);
92
+ if (base.isUnion()) {
93
+ base
94
+ .getUnionTypes()
95
+ .forEach((entry) => collectTypeSourceFiles(entry, sourceFiles, visited));
96
+ return;
97
+ }
98
+ if (base.isArray()) {
99
+ const elem = base.getArrayElementTypeOrThrow();
100
+ collectTypeSourceFiles(elem, sourceFiles, visited);
101
+ return;
102
+ }
103
+ const customName = resolveCustomTypeName(base);
104
+ if (customName) {
105
+ return;
106
+ }
107
+ const declarations = [
108
+ ...(aliasSymbol?.getDeclarations() ?? []),
109
+ ...(symbol?.getDeclarations() ?? []),
110
+ ];
111
+ declarations.forEach((decl) => {
112
+ const sourceFile = decl.getSourceFile();
113
+ const filePath = path_1.default.resolve(sourceFile.getFilePath());
114
+ if (!isDescriptorSourceFile(filePath))
115
+ return;
116
+ sourceFiles.set(filePath, sourceFile);
117
+ });
118
+ if (base.isObject()) {
119
+ base.getProperties().forEach((prop) => {
120
+ const decl = prop.getDeclarations()[0];
121
+ if (!decl)
122
+ return;
123
+ collectTypeSourceFiles(decl.getType(), sourceFiles, visited);
124
+ });
125
+ }
126
+ }
127
+ function collectRelevantSourceFiles(rootType, entrySourceFile) {
128
+ const sourceFiles = new Map();
129
+ const entryFilePath = path_1.default.resolve(entrySourceFile.getFilePath());
130
+ if (isDescriptorSourceFile(entryFilePath)) {
131
+ sourceFiles.set(entryFilePath, entrySourceFile);
132
+ }
133
+ collectTypeSourceFiles(rootType, sourceFiles, new Set());
134
+ return Array.from(sourceFiles.values());
135
+ }
77
136
  function collectReferencedModelNames(type, names, visited, ctx) {
78
137
  const { base } = unwrapOptional(type);
79
138
  const key = `${ctx}:${base.getText()}`;
@@ -505,19 +564,15 @@ function buildDescriptorAlt(resolved) {
505
564
  fs_1.default.existsSync(resolved.entryFile)) {
506
565
  project.addSourceFileAtPath(resolved.entryFile);
507
566
  }
508
- const sourceFiles = project.getSourceFiles().filter((sf) => {
509
- const filePath = path_1.default.resolve(sf.getFilePath());
510
- if (filePath.includes("node_modules"))
511
- return false;
512
- if (filePath.endsWith(".d.ts"))
513
- return false;
514
- const rel = path_1.default.relative(path_1.default.dirname(resolved.entryFile), filePath);
515
- return rel && !rel.startsWith("..") && !path_1.default.isAbsolute(rel);
516
- });
517
- const invocation = findRootInvocation(sourceFiles);
567
+ const entrySourceFile = project.getSourceFile(resolved.entryFile);
568
+ if (!entrySourceFile) {
569
+ throw new Error(`Could not load cms0 entry file: ${resolved.entryFile}`);
570
+ }
571
+ const invocation = findRootInvocation([entrySourceFile]);
518
572
  if (!invocation)
519
573
  throw new Error("Could not locate cms0<T>() invocation in project sources.");
520
574
  const rootType = invocation.rootType;
575
+ const sourceFiles = collectRelevantSourceFiles(rootType, entrySourceFile);
521
576
  // Restrict models to only those referenced by the root type tree and exported in scope.
522
577
  const exportedModelNames = collectModelNames(sourceFiles);
523
578
  const collisions = Array.from(registry_js_1.customTypeNames).filter((name) => exportedModelNames.has(name));
@@ -1,7 +1,25 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isRetryablePublishStatus = isRetryablePublishStatus;
3
4
  exports.publishDescriptor = publishDescriptor;
4
5
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
6
+ class PublishHttpError extends Error {
7
+ status;
8
+ constructor(status) {
9
+ super(`status ${status}`);
10
+ this.name = "PublishHttpError";
11
+ this.status = status;
12
+ }
13
+ }
14
+ function isRetryablePublishStatus(status) {
15
+ return status === 408 || status === 425 || status === 429 || status >= 500;
16
+ }
17
+ function shouldRetryPublishError(error) {
18
+ if (error instanceof PublishHttpError) {
19
+ return isRetryablePublishStatus(error.status);
20
+ }
21
+ return true;
22
+ }
5
23
  const startSpinner = (label) => {
6
24
  if (!process.stdout.isTTY) {
7
25
  console.log(`${label}...`);
@@ -76,7 +94,7 @@ async function publishDescriptor(resolved, descriptor) {
76
94
  body: JSON.stringify(payload),
77
95
  });
78
96
  if (!res.ok) {
79
- throw new Error(`status ${res.status}`);
97
+ throw new PublishHttpError(res.status);
80
98
  }
81
99
  const ready = await waitForAdminReady(resolved.apiBaseUrl.replace(/\/$/, ""), headers);
82
100
  if (!ready) {
@@ -86,13 +104,15 @@ async function publishDescriptor(resolved, descriptor) {
86
104
  return;
87
105
  }
88
106
  catch (err) {
107
+ const canRetry = attempt < maxAttempts && shouldRetryPublishError(err);
89
108
  const waitMs = Math.min(2000 * attempt, 5000);
90
- console.warn(`cms0: publish attempt ${attempt}/${maxAttempts} failed (${err instanceof Error ? err.message : err}).` + (attempt < maxAttempts ? ` retrying in ${waitMs}ms...` : ""));
91
- if (attempt < maxAttempts) {
109
+ console.warn(`cms0: publish attempt ${attempt}/${maxAttempts} failed (${err instanceof Error ? err.message : err}).` + (canRetry ? ` retrying in ${waitMs}ms...` : ""));
110
+ if (canRetry) {
92
111
  await new Promise((r) => setTimeout(r, waitMs));
93
112
  }
94
113
  else {
95
114
  console.warn("cms0: giving up on publishing descriptor for now.");
115
+ return;
96
116
  }
97
117
  }
98
118
  }