162 lines
5.3 KiB
TypeScript
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",
|
|
};
|
|
}
|