@cms0/cms0 0.2.11 → 0.2.13
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/cjs/custom-types/registry.cjs +12 -0
- package/dist/cjs/index.cjs +114 -9
- package/dist/cjs/libs/cli/descriptor-builder-alt.cjs +65 -10
- package/dist/cjs/libs/cli/publisher.cjs +23 -3
- package/dist/cjs/provenance.cjs +517 -0
- package/dist/esm/custom-types/registry.js +12 -0
- package/dist/esm/index.js +101 -8
- package/dist/esm/libs/cli/descriptor-builder-alt.js +65 -10
- package/dist/esm/libs/cli/publisher.js +23 -4
- package/dist/esm/provenance.js +501 -0
- package/dist/types/custom-types/registry.d.ts.map +1 -1
- package/dist/types/index.d.ts +18 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/libs/cli/descriptor-builder-alt.d.ts.map +1 -1
- package/dist/types/libs/cli/publisher.d.ts +2 -1
- package/dist/types/libs/cli/publisher.d.ts.map +1 -1
- package/dist/types/provenance.d.ts +55 -0
- package/dist/types/provenance.d.ts.map +1 -0
- package/package.json +11 -2
package/dist/esm/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { attachCms0ProvenanceRoot, enableCms0ProvenanceTracking, isCms0CanvasTransportEnabled, registerCms0CollectionItemIdentity, } from "@cms0/cms0/provenance";
|
|
1
2
|
import { schemaDescriptor } from "@cms0/cms0/schema-descriptors";
|
|
2
3
|
import { buildZodSchemasFromDescriptor, decodeTaggedUnionValue, getUnionBranchKeys, } from "@cms0/shared";
|
|
3
4
|
import { getCustomInlineTypeMetadata } from "./custom-types/registry.js";
|
|
@@ -508,6 +509,44 @@ function ensureCollectionEnvelope(data, path) {
|
|
|
508
509
|
}
|
|
509
510
|
throw new Error(`${COLLECTION_SHAPE_ERROR} Path: '${path}'.`);
|
|
510
511
|
}
|
|
512
|
+
async function readCanvasModelRefCollectionIdentities(path, descriptor, context) {
|
|
513
|
+
const cache = context.canvasModelRefCollectionIdentityCache;
|
|
514
|
+
if (!cache)
|
|
515
|
+
return null;
|
|
516
|
+
const cacheKey = [
|
|
517
|
+
path,
|
|
518
|
+
descriptor.model,
|
|
519
|
+
context.options.locale ?? "",
|
|
520
|
+
].join("::");
|
|
521
|
+
const existing = cache.get(cacheKey);
|
|
522
|
+
if (existing) {
|
|
523
|
+
return existing;
|
|
524
|
+
}
|
|
525
|
+
const pending = (async () => {
|
|
526
|
+
try {
|
|
527
|
+
const rawCollection = await context.requestJson(path, {
|
|
528
|
+
query: {
|
|
529
|
+
raw: 1,
|
|
530
|
+
includeId: 1,
|
|
531
|
+
resolveModelRefs: 0,
|
|
532
|
+
...(context.options.locale ? { locale: context.options.locale } : {}),
|
|
533
|
+
},
|
|
534
|
+
});
|
|
535
|
+
const envelope = ensureCollectionEnvelope(rawCollection, path);
|
|
536
|
+
return envelope.items.map((row) => ({
|
|
537
|
+
relationId: extractId(row) ?? "",
|
|
538
|
+
modelId: extractModelRefId(row, descriptor.model, {
|
|
539
|
+
allowObjectIdFallback: false,
|
|
540
|
+
}) ?? null,
|
|
541
|
+
}));
|
|
542
|
+
}
|
|
543
|
+
catch {
|
|
544
|
+
return null;
|
|
545
|
+
}
|
|
546
|
+
})();
|
|
547
|
+
cache.set(cacheKey, pending);
|
|
548
|
+
return pending;
|
|
549
|
+
}
|
|
511
550
|
async function normalizeModelRef(modelName, id, context, trail, isCollectionItem, inlineValue) {
|
|
512
551
|
if (!id)
|
|
513
552
|
return null;
|
|
@@ -610,7 +649,7 @@ async function normalizeObjectField(descriptor, path, raw, context, trail, isCol
|
|
|
610
649
|
...(context.options.locale ? { locale: context.options.locale } : {}),
|
|
611
650
|
},
|
|
612
651
|
});
|
|
613
|
-
const normalizedChild = await normalizeArrayField(propertyDescriptor, childPath, childRaw, context, trail);
|
|
652
|
+
const normalizedChild = await normalizeArrayField(propertyDescriptor, childPath, childRaw, context, trail, inlineChildRaw !== undefined);
|
|
614
653
|
output[propertyName] = coerceCustomTypeValue(propertyDescriptor, normalizedChild, {
|
|
615
654
|
locale: context.options.locale,
|
|
616
655
|
defaultLocale: context.options.defaultLocale,
|
|
@@ -653,7 +692,7 @@ async function normalizeObjectField(descriptor, path, raw, context, trail, isCol
|
|
|
653
692
|
}
|
|
654
693
|
return maybeAttachAssetUrl(output, context.options.assetUrlBuilder);
|
|
655
694
|
}
|
|
656
|
-
async function normalizeArrayField(descriptor, path, raw, context, trail) {
|
|
695
|
+
async function normalizeArrayField(descriptor, path, raw, context, trail, inlineResolvedItems = false) {
|
|
657
696
|
const envelope = ensureCollectionEnvelope(raw, path);
|
|
658
697
|
const itemDescriptor = descriptor.items;
|
|
659
698
|
if (isScalarDescriptor(itemDescriptor)) {
|
|
@@ -673,14 +712,29 @@ async function normalizeArrayField(descriptor, path, raw, context, trail) {
|
|
|
673
712
|
}
|
|
674
713
|
if (isModelRefDescriptor(itemDescriptor)) {
|
|
675
714
|
const inlineModelDescriptor = context.modelDescriptors.get(itemDescriptor.model);
|
|
676
|
-
|
|
715
|
+
const collectionIdentities = await readCanvasModelRefCollectionIdentities(path, itemDescriptor, context);
|
|
716
|
+
return mapWithConcurrency(envelope.items, context.options.modelNormalizationConcurrency, async (row, index) => {
|
|
677
717
|
const inlineModel = inlineModelDescriptor
|
|
678
718
|
? extractInlineModelObject(row, inlineModelDescriptor)
|
|
679
719
|
: null;
|
|
680
720
|
const refId = extractModelRefId(row, itemDescriptor.model, {
|
|
681
721
|
allowObjectIdFallback: false,
|
|
682
722
|
}) ?? (inlineModel ? extractId(inlineModel) : null);
|
|
683
|
-
|
|
723
|
+
const normalized = await normalizeModelRef(itemDescriptor.model, refId, context, trail, true, row);
|
|
724
|
+
const collectionItemId = collectionIdentities?.[index]?.relationId ??
|
|
725
|
+
(inlineResolvedItems ? null : extractId(row));
|
|
726
|
+
if (!collectionItemId ||
|
|
727
|
+
!normalized ||
|
|
728
|
+
typeof normalized !== "object" ||
|
|
729
|
+
Array.isArray(normalized)) {
|
|
730
|
+
return normalized;
|
|
731
|
+
}
|
|
732
|
+
const normalizedClone = {
|
|
733
|
+
...normalized,
|
|
734
|
+
};
|
|
735
|
+
registerCms0CollectionItemIdentity(normalizedClone, collectionItemId);
|
|
736
|
+
delete normalizedClone.__cms0CanvasCollectionItemId;
|
|
737
|
+
return normalizedClone;
|
|
684
738
|
});
|
|
685
739
|
}
|
|
686
740
|
if (isObjectDescriptor(itemDescriptor)) {
|
|
@@ -1068,7 +1122,19 @@ function validateResult(resource, result, descriptor, includeIdMode, zodSchemas,
|
|
|
1068
1122
|
throw new Error(formatValidationError(modelName, parsed.error));
|
|
1069
1123
|
}
|
|
1070
1124
|
}
|
|
1071
|
-
|
|
1125
|
+
function attachResourceProvenance(resource, value) {
|
|
1126
|
+
return attachResourceProvenanceWithOptions(resource, value);
|
|
1127
|
+
}
|
|
1128
|
+
function attachResourceProvenanceWithOptions(resource, value, options) {
|
|
1129
|
+
if (value === null || value === undefined)
|
|
1130
|
+
return value;
|
|
1131
|
+
enableCms0ProvenanceTracking(true);
|
|
1132
|
+
const rootId = resource.kind === "root"
|
|
1133
|
+
? resource.key
|
|
1134
|
+
: resource.modelName ?? resource.key;
|
|
1135
|
+
return attachCms0ProvenanceRoot(value, rootId, options);
|
|
1136
|
+
}
|
|
1137
|
+
async function runResource(resource, descriptor, baseUrl, apiKey, defaultLocale, assetUrlBuilder, zodSchemas, modelZodSchemas, globalIncludeId, sharedModelInflightCache, options, byId, provenanceOptions) {
|
|
1072
1138
|
const responseMode = options?.response ?? "normalized";
|
|
1073
1139
|
const includeIdMode = resolveIncludeIdMode(options?.includeId, globalIncludeId);
|
|
1074
1140
|
const resolveModelRefs = options?.resolveModelRefs !== false;
|
|
@@ -1154,6 +1220,9 @@ async function runResource(resource, descriptor, baseUrl, apiKey, defaultLocale,
|
|
|
1154
1220
|
modelDescriptors,
|
|
1155
1221
|
modelCache: new Map(),
|
|
1156
1222
|
sharedModelInflightCache,
|
|
1223
|
+
canvasModelRefCollectionIdentityCache: useResolvedEndpoint && isCms0CanvasTransportEnabled()
|
|
1224
|
+
? new Map()
|
|
1225
|
+
: undefined,
|
|
1157
1226
|
options: {
|
|
1158
1227
|
includeIdMode,
|
|
1159
1228
|
resolveModelRefs,
|
|
@@ -1178,16 +1247,16 @@ async function runResource(resource, descriptor, baseUrl, apiKey, defaultLocale,
|
|
|
1178
1247
|
const projectedItems = applyFieldProjection(normalizedItems, options?.fields, options?.exclude);
|
|
1179
1248
|
validateResult(resource, normalizedItems, descriptor, includeIdMode, zodSchemas, modelZodSchemas);
|
|
1180
1249
|
return {
|
|
1181
|
-
items: Array.isArray(projectedItems) ? projectedItems : normalizedItems,
|
|
1250
|
+
items: attachResourceProvenanceWithOptions(resource, Array.isArray(projectedItems) ? projectedItems : normalizedItems, provenanceOptions),
|
|
1182
1251
|
total: envelope.total,
|
|
1183
1252
|
};
|
|
1184
1253
|
}
|
|
1185
1254
|
validateResult(resource, normalizedItems, descriptor, includeIdMode, zodSchemas, modelZodSchemas);
|
|
1186
|
-
return normalizedItems;
|
|
1255
|
+
return attachResourceProvenanceWithOptions(resource, normalizedItems, provenanceOptions);
|
|
1187
1256
|
}
|
|
1188
1257
|
const normalized = await normalizeField(resource.descriptor, normalizePath, rawData, context, new Set(), false);
|
|
1189
1258
|
validateResult(resource, normalized, descriptor, includeIdMode, zodSchemas, modelZodSchemas);
|
|
1190
|
-
return applyFieldProjection(normalized, options?.fields, options?.exclude);
|
|
1259
|
+
return attachResourceProvenanceWithOptions(resource, applyFieldProjection(normalized, options?.fields, options?.exclude), provenanceOptions);
|
|
1191
1260
|
}
|
|
1192
1261
|
export function createCmsClient(descriptor, config) {
|
|
1193
1262
|
const baseUrl = normalizeBaseUrl(config.apiConfig?.baseUrl);
|
|
@@ -1223,6 +1292,29 @@ export function createCmsClient(descriptor, config) {
|
|
|
1223
1292
|
defaultLocale: config.defaultLocale,
|
|
1224
1293
|
includeIdDefault: !!config.includeId,
|
|
1225
1294
|
};
|
|
1295
|
+
const readRoot = async (rootId, options) => {
|
|
1296
|
+
const entry = registry.roots.get(rootId) ??
|
|
1297
|
+
registry.rootsCaseInsensitive.get(rootId.toLowerCase());
|
|
1298
|
+
if (!entry) {
|
|
1299
|
+
throw new Error(`cms0: unknown root '${rootId}' for Canvas read-back.`);
|
|
1300
|
+
}
|
|
1301
|
+
return runResource(entry, descriptor, baseUrl, options?.apiKey ?? apiKey, config.defaultLocale, assetUrlBuilder, zodSchemas, modelZodSchemas, globalIncludeId, sharedModelInflightCache, {
|
|
1302
|
+
includeId: options?.includeId ?? true,
|
|
1303
|
+
locale: options?.locale ?? "all",
|
|
1304
|
+
}, undefined, {
|
|
1305
|
+
registerLiveRoot: false,
|
|
1306
|
+
});
|
|
1307
|
+
};
|
|
1308
|
+
globalThis.__CMS0_CANVAS_CLIENT__ = {
|
|
1309
|
+
apiBaseUrl: baseUrl,
|
|
1310
|
+
locales: clientMeta.locales,
|
|
1311
|
+
defaultLocale: clientMeta.defaultLocale,
|
|
1312
|
+
publishTriggerId: typeof config.canvas?.publishTriggerId === "string" &&
|
|
1313
|
+
config.canvas.publishTriggerId.trim().length > 0
|
|
1314
|
+
? config.canvas.publishTriggerId.trim()
|
|
1315
|
+
: undefined,
|
|
1316
|
+
readRoot,
|
|
1317
|
+
};
|
|
1226
1318
|
const rootProxyTarget = {
|
|
1227
1319
|
models: modelsProxy,
|
|
1228
1320
|
meta: clientMeta,
|
|
@@ -1250,4 +1342,5 @@ export function createCmsClient(descriptor, config) {
|
|
|
1250
1342
|
export function cms0(config) {
|
|
1251
1343
|
return createCmsClient(schemaDescriptor, config);
|
|
1252
1344
|
}
|
|
1345
|
+
export { activateCms0CanvasTransport, attachCms0ProvenanceRoot, captureCms0Provenance, enableCms0ProvenanceTracking, readCms0CanvasTransportCollectionItemId, readCms0CollectionItemIdentity, registerCms0LiveRoot, registerCms0CollectionItemIdentity, readCms0LiveRoots, readCms0ProvenanceValueMeta, restoreCms0CanvasTransportMetadata, unwrapCms0CanvasTransportValue, } from "./provenance.js";
|
|
1253
1346
|
export { resolveLocalized, toNextMetadata, toOpenGraph, toTwitter, } from "./seo.js";
|
|
@@ -68,6 +68,65 @@ function collectModelNames(sourceFiles) {
|
|
|
68
68
|
});
|
|
69
69
|
return names;
|
|
70
70
|
}
|
|
71
|
+
function isDescriptorSourceFile(filePath) {
|
|
72
|
+
if (filePath.includes("node_modules"))
|
|
73
|
+
return false;
|
|
74
|
+
if (filePath.endsWith(".d.ts"))
|
|
75
|
+
return false;
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
function collectTypeSourceFiles(type, sourceFiles, visited) {
|
|
79
|
+
const { base } = unwrapOptional(type);
|
|
80
|
+
const aliasSymbol = base.getAliasSymbol();
|
|
81
|
+
const symbol = base.getSymbol();
|
|
82
|
+
const key = `${aliasSymbol?.getName() ?? symbol?.getName() ?? "<anon>"}:${base.getText()}`;
|
|
83
|
+
if (visited.has(key))
|
|
84
|
+
return;
|
|
85
|
+
visited.add(key);
|
|
86
|
+
if (base.isUnion()) {
|
|
87
|
+
base
|
|
88
|
+
.getUnionTypes()
|
|
89
|
+
.forEach((entry) => collectTypeSourceFiles(entry, sourceFiles, visited));
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (base.isArray()) {
|
|
93
|
+
const elem = base.getArrayElementTypeOrThrow();
|
|
94
|
+
collectTypeSourceFiles(elem, sourceFiles, visited);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const customName = resolveCustomTypeName(base);
|
|
98
|
+
if (customName) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const declarations = [
|
|
102
|
+
...(aliasSymbol?.getDeclarations() ?? []),
|
|
103
|
+
...(symbol?.getDeclarations() ?? []),
|
|
104
|
+
];
|
|
105
|
+
declarations.forEach((decl) => {
|
|
106
|
+
const sourceFile = decl.getSourceFile();
|
|
107
|
+
const filePath = path.resolve(sourceFile.getFilePath());
|
|
108
|
+
if (!isDescriptorSourceFile(filePath))
|
|
109
|
+
return;
|
|
110
|
+
sourceFiles.set(filePath, sourceFile);
|
|
111
|
+
});
|
|
112
|
+
if (base.isObject()) {
|
|
113
|
+
base.getProperties().forEach((prop) => {
|
|
114
|
+
const decl = prop.getDeclarations()[0];
|
|
115
|
+
if (!decl)
|
|
116
|
+
return;
|
|
117
|
+
collectTypeSourceFiles(decl.getType(), sourceFiles, visited);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function collectRelevantSourceFiles(rootType, entrySourceFile) {
|
|
122
|
+
const sourceFiles = new Map();
|
|
123
|
+
const entryFilePath = path.resolve(entrySourceFile.getFilePath());
|
|
124
|
+
if (isDescriptorSourceFile(entryFilePath)) {
|
|
125
|
+
sourceFiles.set(entryFilePath, entrySourceFile);
|
|
126
|
+
}
|
|
127
|
+
collectTypeSourceFiles(rootType, sourceFiles, new Set());
|
|
128
|
+
return Array.from(sourceFiles.values());
|
|
129
|
+
}
|
|
71
130
|
function collectReferencedModelNames(type, names, visited, ctx) {
|
|
72
131
|
const { base } = unwrapOptional(type);
|
|
73
132
|
const key = `${ctx}:${base.getText()}`;
|
|
@@ -499,19 +558,15 @@ function buildDescriptorAlt(resolved) {
|
|
|
499
558
|
fs.existsSync(resolved.entryFile)) {
|
|
500
559
|
project.addSourceFileAtPath(resolved.entryFile);
|
|
501
560
|
}
|
|
502
|
-
const
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
return false;
|
|
508
|
-
const rel = path.relative(path.dirname(resolved.entryFile), filePath);
|
|
509
|
-
return rel && !rel.startsWith("..") && !path.isAbsolute(rel);
|
|
510
|
-
});
|
|
511
|
-
const invocation = findRootInvocation(sourceFiles);
|
|
561
|
+
const entrySourceFile = project.getSourceFile(resolved.entryFile);
|
|
562
|
+
if (!entrySourceFile) {
|
|
563
|
+
throw new Error(`Could not load cms0 entry file: ${resolved.entryFile}`);
|
|
564
|
+
}
|
|
565
|
+
const invocation = findRootInvocation([entrySourceFile]);
|
|
512
566
|
if (!invocation)
|
|
513
567
|
throw new Error("Could not locate cms0<T>() invocation in project sources.");
|
|
514
568
|
const rootType = invocation.rootType;
|
|
569
|
+
const sourceFiles = collectRelevantSourceFiles(rootType, entrySourceFile);
|
|
515
570
|
// Restrict models to only those referenced by the root type tree and exported in scope.
|
|
516
571
|
const exportedModelNames = collectModelNames(sourceFiles);
|
|
517
572
|
const collisions = Array.from(customTypeNames).filter((name) => exportedModelNames.has(name));
|
|
@@ -1,4 +1,21 @@
|
|
|
1
1
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
2
|
+
class PublishHttpError extends Error {
|
|
3
|
+
status;
|
|
4
|
+
constructor(status) {
|
|
5
|
+
super(`status ${status}`);
|
|
6
|
+
this.name = "PublishHttpError";
|
|
7
|
+
this.status = status;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
function isRetryablePublishStatus(status) {
|
|
11
|
+
return status === 408 || status === 425 || status === 429 || status >= 500;
|
|
12
|
+
}
|
|
13
|
+
function shouldRetryPublishError(error) {
|
|
14
|
+
if (error instanceof PublishHttpError) {
|
|
15
|
+
return isRetryablePublishStatus(error.status);
|
|
16
|
+
}
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
2
19
|
const startSpinner = (label) => {
|
|
3
20
|
if (!process.stdout.isTTY) {
|
|
4
21
|
console.log(`${label}...`);
|
|
@@ -73,7 +90,7 @@ async function publishDescriptor(resolved, descriptor) {
|
|
|
73
90
|
body: JSON.stringify(payload),
|
|
74
91
|
});
|
|
75
92
|
if (!res.ok) {
|
|
76
|
-
throw new
|
|
93
|
+
throw new PublishHttpError(res.status);
|
|
77
94
|
}
|
|
78
95
|
const ready = await waitForAdminReady(resolved.apiBaseUrl.replace(/\/$/, ""), headers);
|
|
79
96
|
if (!ready) {
|
|
@@ -83,13 +100,15 @@ async function publishDescriptor(resolved, descriptor) {
|
|
|
83
100
|
return;
|
|
84
101
|
}
|
|
85
102
|
catch (err) {
|
|
103
|
+
const canRetry = attempt < maxAttempts && shouldRetryPublishError(err);
|
|
86
104
|
const waitMs = Math.min(2000 * attempt, 5000);
|
|
87
|
-
console.warn(`cms0: publish attempt ${attempt}/${maxAttempts} failed (${err instanceof Error ? err.message : err}).` + (
|
|
88
|
-
if (
|
|
105
|
+
console.warn(`cms0: publish attempt ${attempt}/${maxAttempts} failed (${err instanceof Error ? err.message : err}).` + (canRetry ? ` retrying in ${waitMs}ms...` : ""));
|
|
106
|
+
if (canRetry) {
|
|
89
107
|
await new Promise((r) => setTimeout(r, waitMs));
|
|
90
108
|
}
|
|
91
109
|
else {
|
|
92
110
|
console.warn("cms0: giving up on publishing descriptor for now.");
|
|
111
|
+
return;
|
|
93
112
|
}
|
|
94
113
|
}
|
|
95
114
|
}
|
|
@@ -98,4 +117,4 @@ async function publishDescriptor(resolved, descriptor) {
|
|
|
98
117
|
console.warn(`cms0: failed to publish descriptor ${err instanceof Error ? err.message : err}`, err);
|
|
99
118
|
}
|
|
100
119
|
}
|
|
101
|
-
export { publishDescriptor };
|
|
120
|
+
export { isRetryablePublishStatus, publishDescriptor };
|