import { isModuleFrozen } from './registry.ts'; const DEFINE_BRAND = Symbol.for('moroutine.define'); const counters = new Map(); /** Registry of defined values by id, populated by module evaluation on each thread. */ export const defineRegistry = new Map(); /** * Tags a value with deterministic module identity so it can cross threads by * reference and be resolved on the receiving thread by evaluating its module — * the same resolution model as task-args, but for plain values. * * Must be called at module scope in a side-effect-free module. The value is * produced by evaluating the module on each thread, so non-shared mutable state * inside it is per-thread; cross-thread state must use shared memory. * * @param importMeta - The `import.meta` of the calling module. * @param value - The value to define. Must be an object or function. * @returns The same value, branded with its module identity. */ export function define(importMeta: ImportMeta, value: T): T { const url = importMeta.url; if (isModuleFrozen(url)) { throw new Error( `Cannot call define() for ${url} after a task or defined value from this module has been dispatched. ` + 'All define() calls must happen at module load time.', ); } if (value === null || (typeof value !== 'object' && typeof value !== 'function')) { throw new TypeError('define() requires an object or function value'); } if (Object.hasOwn(value, DEFINE_BRAND)) { throw new TypeError('define() was already called on this value'); } if (!Object.isExtensible(value)) { throw new TypeError('define() cannot brand a frozen or sealed value'); } const index = counters.get(url) ?? 0; counters.set(url, index + 1); const id = `${url}#${index}`; Object.defineProperty(value, DEFINE_BRAND, { value: id }); defineRegistry.set(id, value); return value; } /** Returns the define id of a branded value, or undefined. */ export function getDefineId(value: unknown): string | undefined { if (value === null || (typeof value !== 'object' && typeof value !== 'function')) return undefined; if (!Object.hasOwn(value, DEFINE_BRAND)) return undefined; return (value as Record)[DEFINE_BRAND]; }