import { createToolcraftModuleDefinition } from "../../contract/module-definition"; import type { ToolcraftModuleRecord } from "../../contract/module-protocol"; import { snapshotToolcraftModuleData } from "../../contract/module-data"; import { toolcraftFileSourceAssetHandler } from "../../../source-assets/file-source-asset-handler"; import { readToolcraftFileBytes } from "../../../source-assets/media-file"; import { createToolcraftMediaResourceRef } from "../../../source-assets/media-resource-ref"; import type { ToolcraftSourceAssetHandler, ToolcraftPreparedSourceAssetRecord } from "../../../source-assets/source-asset-types"; type Palette = Readonly<{ version: 1; colors: readonly string[]; }>; export function decodePalette(bytes: Uint8Array): Palette { const value: unknown = JSON.parse(new TextDecoder().decode(bytes)); if (!value || typeof value !== "object" || !("version" in value) || value.version !== 1 || !("colors" in value) || !Array.isArray(value.colors) || !value.colors.every(color => typeof color === "string" && /^#[0-9a-f]{6}$/i.test(color))) { throw new Error("Invalid palette payload"); } return Object.freeze({ version: 1, colors: Object.freeze([...value.colors]) }); } export const paletteModule = () => createToolcraftModuleDefinition({ id: "palette-source", provides: ["palette.source"], requires: [], defaultProviders: [], portRequirements: [], contributions: [{ id: "palette-source.policy", moduleId: "palette-source", kind: "source-workflow" }], }, snapshotToolcraftModuleData); export function createPaletteHandler(afterStage: () => Promise = async () => { }): ToolcraftSourceAssetHandler<"file", ToolcraftPreparedSourceAssetRecord<"file">> { return { ...toolcraftFileSourceAssetHandler, prepare: async context => { const file = context.batch.files[0]!; const bytes = await readToolcraftFileBytes(file, context.signal); decodePalette(bytes); const resourceRef = createToolcraftMediaResourceRef("file", bytes); await context.stageResource(resourceRef, bytes, { contentType: "application/x-palette", durable: true }); await afterStage(); return { assets: [{ assetKind: "file", fileName: file.name, lifecycle: "ready", mimeType: "application/x-palette", position: { x: 0, y: 0 }, resourceRef }], stagedResourceRefs: [resourceRef], }; }, }; }