Files
workinf_Blender_Wasm/web/protocol/asset-catalog-compatibility.ts
mes123456 5a11045ca5
Some checks are pending
M6 deployable RC / quick (push) Waiting to run
M6 deployable RC / chromium (push) Blocked by required conditions
M6 deployable RC / release (push) Blocked by required conditions
Advance Blender 5.2 web parity through M12-03D
2026-08-17 17:30:27 -04:00

162 lines
5.3 KiB
TypeScript

import { parseAssetLibraryManifest } from "./asset-library-io";
import { ASSET_CATALOG_SCHEMA, parseAssetCatalogManifestV2 } from "./asset-catalog-v2";
import type { ErrorCode } from "./error";
export const ASSET_CATALOG_COMPATIBILITY_SCHEMA = 1 as const;
export type AssetCatalogLegacyOperation = "READ" | "CATALOG_WRITE" | "ASSET_WRITE" | "SAVE";
export interface AssetCatalogLegacySnapshotIR {
revision: number;
catalogs: Array<{ catalogId: string; path: string; simpleName: string }>;
assets: Array<{ assetId: string; relativeAssetIdentifier: string; idType: string; name: string; catalogId: string | null }>;
libraries: Array<{ libraryId: string; name: string; readOnly: boolean }>;
}
export interface AssetCatalogLegacyCompatibilityIR {
schemaVersion: typeof ASSET_CATALOG_COMPATIBILITY_SCHEMA;
task: "M12-01E";
readerSchemaVersion: 1;
documentSchemaVersion: number;
operation: AssetCatalogLegacyOperation;
status: "READY" | "READ_ONLY" | "BLOCKED";
code: ErrorCode | null;
recoverable: boolean;
sourceSha256: string;
snapshot: AssetCatalogLegacySnapshotIR | null;
nextTask: "M12-01F";
}
const encoder = new TextEncoder();
function record(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function stableJSON(value: unknown): string {
if (Array.isArray(value)) return `[${value.map(stableJSON).join(",")}]`;
if (record(value)) return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableJSON(value[key])}`).join(",")}}`;
return JSON.stringify(value);
}
async function sha256(value: unknown): Promise<string> {
const digest = await crypto.subtle.digest("SHA-256", encoder.encode(stableJSON(value)));
return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
}
function operation(value: unknown): AssetCatalogLegacyOperation {
if (value !== "READ" && value !== "CATALOG_WRITE" && value !== "ASSET_WRITE" && value !== "SAVE") {
throw new Error("ASSET_MANIFEST_INVALID: legacy reader operation is invalid");
}
return value;
}
export async function inspectAssetCatalogForLegacyReader(
value: unknown,
operationValue: unknown,
): Promise<AssetCatalogLegacyCompatibilityIR> {
const requestedOperation = operation(operationValue);
const sourceSha256 = await sha256(value);
if (!record(value) || typeof value.schemaVersion !== "number" || !Number.isSafeInteger(value.schemaVersion)) {
return {
schemaVersion: ASSET_CATALOG_COMPATIBILITY_SCHEMA,
task: "M12-01E",
readerSchemaVersion: 1,
documentSchemaVersion: 0,
operation: requestedOperation,
status: "BLOCKED",
code: "PROTOCOL_MISMATCH",
recoverable: false,
sourceSha256,
snapshot: null,
nextTask: "M12-01F",
};
}
if (value.schemaVersion === 1) {
try {
parseAssetLibraryManifest(value);
}
catch {
return {
schemaVersion: ASSET_CATALOG_COMPATIBILITY_SCHEMA,
task: "M12-01E",
readerSchemaVersion: 1,
documentSchemaVersion: 1,
operation: requestedOperation,
status: "BLOCKED",
code: "ASSET_MANIFEST_INVALID",
recoverable: true,
sourceSha256,
snapshot: null,
nextTask: "M12-01F",
};
}
return {
schemaVersion: ASSET_CATALOG_COMPATIBILITY_SCHEMA,
task: "M12-01E",
readerSchemaVersion: 1,
documentSchemaVersion: 1,
operation: requestedOperation,
status: "READY",
code: null,
recoverable: true,
sourceSha256,
snapshot: null,
nextTask: "M12-01F",
};
}
if (value.schemaVersion !== ASSET_CATALOG_SCHEMA) {
return {
schemaVersion: ASSET_CATALOG_COMPATIBILITY_SCHEMA,
task: "M12-01E",
readerSchemaVersion: 1,
documentSchemaVersion: value.schemaVersion,
operation: requestedOperation,
status: "BLOCKED",
code: "PROTOCOL_MISMATCH",
recoverable: false,
sourceSha256,
snapshot: null,
nextTask: "M12-01F",
};
}
let document;
try {
document = await parseAssetCatalogManifestV2(value);
}
catch {
return {
schemaVersion: ASSET_CATALOG_COMPATIBILITY_SCHEMA,
task: "M12-01E",
readerSchemaVersion: 1,
documentSchemaVersion: ASSET_CATALOG_SCHEMA,
operation: requestedOperation,
status: "BLOCKED",
code: "ASSET_MANIFEST_INVALID",
recoverable: true,
sourceSha256,
snapshot: null,
nextTask: "M12-01F",
};
}
const snapshot: AssetCatalogLegacySnapshotIR = {
revision: document.revision,
catalogs: document.catalogs.map(({ catalogId, path, simpleName }) => ({ catalogId, path, simpleName })),
assets: document.assets.map(({ assetId, relativeAssetIdentifier, idType, name, catalogId }) => ({ assetId, relativeAssetIdentifier, idType, name, catalogId })),
libraries: document.libraries.map(({ libraryId, name, readOnly }) => ({ libraryId, name, readOnly })),
};
return {
schemaVersion: ASSET_CATALOG_COMPATIBILITY_SCHEMA,
task: "M12-01E",
readerSchemaVersion: 1,
documentSchemaVersion: ASSET_CATALOG_SCHEMA,
operation: requestedOperation,
status: requestedOperation === "READ" ? "READ_ONLY" : "BLOCKED",
code: "ASSET_SCHEMA_DOWNGRADE_BLOCKED",
recoverable: true,
sourceSha256,
snapshot,
nextTask: "M12-01F",
};
}