@lssm/lib.overlay-engine 0.0.0-canary-20251220002821 → 0.0.0-canary-20251220021406

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.
@@ -1 +1 @@
1
- {"version":3,"file":"merger.js","names":["visibleFields: OverlayRenderableField[]","filtered: string[]","missing: string[]"],"sources":["../src/merger.ts"],"sourcesContent":["import type { SignedOverlaySpec } from './spec';\nimport type { OverlayRenderable, OverlayRenderableField } from './types';\n\nexport interface ApplyOverlayOptions {\n strict?: boolean;\n}\n\ninterface FieldState<TField extends OverlayRenderableField> {\n key: string;\n field: TField;\n hidden: boolean;\n}\n\nexport function applyOverlayModifications<T extends OverlayRenderable>(\n target: T,\n overlays: SignedOverlaySpec[],\n options: ApplyOverlayOptions = {}\n): T {\n if (!overlays.length) {\n return target;\n }\n\n const states = target.fields.map<FieldState<any>>((field) => ({\n key: field.key,\n field: { ...field },\n hidden: field.visible === false,\n }));\n\n const fieldMap = new Map(states.map((state) => [state.key, state]));\n let orderSequence = target.fields.map((field) => field.key);\n\n const handleMissing = (field: string, overlayId: string) => {\n if (options.strict) {\n throw new Error(\n `Overlay \"${overlayId}\" referenced unknown field \"${field}\".`\n );\n }\n };\n\n overlays.forEach((overlay) => {\n overlay.modifications.forEach((modification) => {\n switch (modification.type) {\n case 'hideField': {\n const state = fieldMap.get(modification.field);\n if (!state)\n return handleMissing(modification.field, overlay.overlayId);\n state.hidden = true;\n state.field.visible = false;\n break;\n }\n case 'renameLabel': {\n const state = fieldMap.get(modification.field);\n if (!state)\n return handleMissing(modification.field, overlay.overlayId);\n state.field.label = modification.newLabel;\n break;\n }\n case 'setDefault': {\n const state = fieldMap.get(modification.field);\n if (!state)\n return handleMissing(modification.field, overlay.overlayId);\n state.field.defaultValue = modification.value;\n break;\n }\n case 'addHelpText': {\n const state = fieldMap.get(modification.field);\n if (!state)\n return handleMissing(modification.field, overlay.overlayId);\n state.field.helpText = modification.text;\n break;\n }\n case 'makeRequired': {\n const state = fieldMap.get(modification.field);\n if (!state)\n return handleMissing(modification.field, overlay.overlayId);\n state.field.required = modification.required ?? true;\n break;\n }\n case 'reorderFields': {\n const { filtered, missing } = normalizeOrderList(\n modification.fields,\n fieldMap\n );\n if (missing.length && options.strict) {\n missing.forEach((field) => handleMissing(field, overlay.overlayId));\n }\n orderSequence = applyReorder(orderSequence, filtered);\n break;\n }\n default:\n break;\n }\n });\n });\n\n const visibleFields: OverlayRenderableField[] = [];\n const seen = new Set<string>();\n orderSequence.forEach((key) => {\n const state = fieldMap.get(key);\n if (!state || state.hidden) {\n return;\n }\n seen.add(key);\n visibleFields.push(state.field);\n });\n\n // Handle any fields introduced dynamically that were not part of the original sequence.\n states.forEach((state) => {\n if (state.hidden || seen.has(state.key)) {\n return;\n }\n visibleFields.push(state.field);\n });\n\n visibleFields.forEach((field, index) => {\n field.order = index;\n field.visible = true;\n });\n\n return {\n ...(target as T),\n fields: visibleFields as T['fields'],\n };\n}\n\nfunction normalizeOrderList(\n fields: string[],\n fieldMap: Map<string, FieldState<any>>\n): { filtered: string[]; missing: string[] } {\n const filtered: string[] = [];\n const missing: string[] = [];\n const seen = new Set<string>();\n\n fields.forEach((field) => {\n if (!field?.trim()) {\n return;\n }\n if (!fieldMap.has(field)) {\n missing.push(field);\n return;\n }\n if (seen.has(field)) {\n return;\n }\n seen.add(field);\n filtered.push(field);\n });\n\n return { filtered, missing };\n}\n\nfunction applyReorder(sequence: string[], orderedFields: string[]): string[] {\n if (!orderedFields.length) {\n return sequence;\n }\n const orderedSet = new Set(orderedFields);\n const remainder = sequence.filter((key) => !orderedSet.has(key));\n return [...orderedFields, ...remainder];\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"merger.js","names":["visibleFields: OverlayRenderableField[]","filtered: string[]","missing: string[]"],"sources":["../src/merger.ts"],"sourcesContent":["import type { SignedOverlaySpec } from './spec';\nimport type { OverlayRenderable, OverlayRenderableField } from './types';\n\nexport interface ApplyOverlayOptions {\n strict?: boolean;\n}\n\ninterface FieldState<TField extends OverlayRenderableField> {\n key: string;\n field: TField;\n hidden: boolean;\n}\n\nexport function applyOverlayModifications<T extends OverlayRenderable>(\n target: T,\n overlays: SignedOverlaySpec[],\n options: ApplyOverlayOptions = {}\n): T {\n if (!overlays.length) {\n return target;\n }\n\n const states = target.fields.map<FieldState<OverlayRenderableField>>(\n (field) => ({\n key: field.key,\n field: { ...field },\n hidden: field.visible === false,\n })\n );\n\n const fieldMap = new Map(states.map((state) => [state.key, state]));\n let orderSequence = target.fields.map((field) => field.key);\n\n const handleMissing = (field: string, overlayId: string) => {\n if (options.strict) {\n throw new Error(\n `Overlay \"${overlayId}\" referenced unknown field \"${field}\".`\n );\n }\n };\n\n overlays.forEach((overlay) => {\n overlay.modifications.forEach((modification) => {\n switch (modification.type) {\n case 'hideField': {\n const state = fieldMap.get(modification.field);\n if (!state)\n return handleMissing(modification.field, overlay.overlayId);\n state.hidden = true;\n state.field.visible = false;\n break;\n }\n case 'renameLabel': {\n const state = fieldMap.get(modification.field);\n if (!state)\n return handleMissing(modification.field, overlay.overlayId);\n state.field.label = modification.newLabel;\n break;\n }\n case 'setDefault': {\n const state = fieldMap.get(modification.field);\n if (!state)\n return handleMissing(modification.field, overlay.overlayId);\n state.field.defaultValue = modification.value;\n break;\n }\n case 'addHelpText': {\n const state = fieldMap.get(modification.field);\n if (!state)\n return handleMissing(modification.field, overlay.overlayId);\n state.field.helpText = modification.text;\n break;\n }\n case 'makeRequired': {\n const state = fieldMap.get(modification.field);\n if (!state)\n return handleMissing(modification.field, overlay.overlayId);\n state.field.required = modification.required ?? true;\n break;\n }\n case 'reorderFields': {\n const { filtered, missing } = normalizeOrderList(\n modification.fields,\n fieldMap\n );\n if (missing.length && options.strict) {\n missing.forEach((field) => handleMissing(field, overlay.overlayId));\n }\n orderSequence = applyReorder(orderSequence, filtered);\n break;\n }\n default:\n break;\n }\n });\n });\n\n const visibleFields: OverlayRenderableField[] = [];\n const seen = new Set<string>();\n orderSequence.forEach((key) => {\n const state = fieldMap.get(key);\n if (!state || state.hidden) {\n return;\n }\n seen.add(key);\n visibleFields.push(state.field);\n });\n\n // Handle any fields introduced dynamically that were not part of the original sequence.\n states.forEach((state) => {\n if (state.hidden || seen.has(state.key)) {\n return;\n }\n visibleFields.push(state.field);\n });\n\n visibleFields.forEach((field, index) => {\n field.order = index;\n field.visible = true;\n });\n\n return {\n ...(target as T),\n fields: visibleFields as T['fields'],\n };\n}\n\nfunction normalizeOrderList(\n fields: string[],\n fieldMap: Map<string, FieldState<OverlayRenderableField>>\n): { filtered: string[]; missing: string[] } {\n const filtered: string[] = [];\n const missing: string[] = [];\n const seen = new Set<string>();\n\n fields.forEach((field) => {\n if (!field?.trim()) {\n return;\n }\n if (!fieldMap.has(field)) {\n missing.push(field);\n return;\n }\n if (seen.has(field)) {\n return;\n }\n seen.add(field);\n filtered.push(field);\n });\n\n return { filtered, missing };\n}\n\nfunction applyReorder(sequence: string[], orderedFields: string[]): string[] {\n if (!orderedFields.length) {\n return sequence;\n }\n const orderedSet = new Set(orderedFields);\n const remainder = sequence.filter((key) => !orderedSet.has(key));\n return [...orderedFields, ...remainder];\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"validator.js","names":["defaultOverlayValidator: OverlayValidator","issues: OverlayValidationIssue[]","exhaustive: never"],"sources":["../src/validator.ts"],"sourcesContent":["import type { OverlayModification, OverlaySpec } from './spec';\n\nexport interface OverlayValidationIssue {\n code: string;\n message: string;\n path?: string[];\n}\n\nexport interface OverlayValidationResult {\n valid: boolean;\n issues: OverlayValidationIssue[];\n}\n\nexport type OverlayValidator = (spec: OverlaySpec) => OverlayValidationResult;\n\nconst TARGET_KEYS = [\n 'capability',\n 'workflow',\n 'dataView',\n 'presentation',\n 'operation',\n] as const;\n\nexport const defaultOverlayValidator: OverlayValidator = (spec) =>\n validateOverlaySpec(spec);\n\nexport function validateOverlaySpec(\n spec: OverlaySpec\n): OverlayValidationResult {\n const issues: OverlayValidationIssue[] = [];\n\n if (!spec.overlayId?.trim()) {\n issues.push({\n code: 'overlay.id',\n message: 'overlayId is required',\n path: ['overlayId'],\n });\n }\n\n if (!spec.version?.trim()) {\n issues.push({\n code: 'overlay.version',\n message: 'version is required',\n path: ['version'],\n });\n }\n\n const hasTarget = TARGET_KEYS.some((key) => {\n const value = spec.appliesTo?.[key as keyof typeof spec.appliesTo];\n return typeof value === 'string' && value.trim().length > 0;\n });\n\n if (!hasTarget) {\n issues.push({\n code: 'overlay.target',\n message:\n 'Overlay must specify at least one target (capability, workflow, dataView, presentation, or operation).',\n path: ['appliesTo'],\n });\n }\n\n if (!spec.modifications?.length) {\n issues.push({\n code: 'overlay.modifications.empty',\n message: 'Overlay must include at least one modification.',\n path: ['modifications'],\n });\n } else {\n spec.modifications.forEach((mod, idx) => {\n const path = ['modifications', String(idx)];\n validateModification(mod, path, issues);\n });\n }\n\n return {\n valid: issues.length === 0,\n issues,\n };\n}\n\nfunction validateModification(\n modification: OverlayModification,\n path: string[],\n issues: OverlayValidationIssue[]\n) {\n const push = (code: string, message: string, extraPath?: string[]) => {\n issues.push({\n code,\n message,\n path: extraPath ? [...path, ...extraPath] : path,\n });\n };\n\n if (isFieldModification(modification)) {\n if (!modification.field?.trim()) {\n push('overlay.mod.field', 'field is required for this modification', [\n 'field',\n ]);\n }\n }\n\n switch (modification.type) {\n case 'renameLabel': {\n if (!modification.newLabel?.trim()) {\n push('overlay.mod.renameLabel.newLabel', 'newLabel is required', [\n 'newLabel',\n ]);\n }\n break;\n }\n case 'reorderFields': {\n if (!modification.fields?.length) {\n push(\n 'overlay.mod.reorderFields.fields',\n 'fields list cannot be empty',\n ['fields']\n );\n }\n const seen = new Set<string>();\n for (const field of modification.fields ?? []) {\n if (!field?.trim()) {\n push(\n 'overlay.mod.reorderFields.fields.blank',\n 'fields entries must be non-empty'\n );\n break;\n }\n if (seen.has(field)) {\n push(\n 'overlay.mod.reorderFields.fields.duplicate',\n `field \"${field}\" was listed multiple times`\n );\n break;\n }\n seen.add(field);\n }\n break;\n }\n case 'setDefault': {\n if (modification.value === undefined) {\n push('overlay.mod.setDefault.value', 'value is required', ['value']);\n }\n break;\n }\n case 'addHelpText': {\n if (!modification.text?.trim()) {\n push('overlay.mod.addHelpText.text', 'text is required', ['text']);\n }\n break;\n }\n case 'makeRequired':\n case 'hideField':\n // no extra validation\n break;\n default: {\n const exhaustive: never = modification;\n throw new Error(\n `Unsupported overlay modification ${(exhaustive as any)?.type ?? 'unknown'}`\n );\n }\n }\n}\n\nfunction isFieldModification(\n mod: OverlayModification\n): mod is Extract<OverlayModification, { field: string }> {\n return 'field' in mod;\n}\n\nexport function assertOverlayValid(\n spec: OverlaySpec,\n validator: OverlayValidator = defaultOverlayValidator\n) {\n const result = validator(spec);\n if (!result.valid) {\n const message = result.issues\n .map((issue) => `${issue.code}: ${issue.message}`)\n .join('; ');\n throw new Error(`Invalid OverlaySpec \"${spec.overlayId}\": ${message}`);\n }\n}\n"],"mappings":";AAeA,MAAM,cAAc;CAClB;CACA;CACA;CACA;CACA;CACD;AAED,MAAaA,2BAA6C,SACxD,oBAAoB,KAAK;AAE3B,SAAgB,oBACd,MACyB;CACzB,MAAMC,SAAmC,EAAE;AAE3C,KAAI,CAAC,KAAK,WAAW,MAAM,CACzB,QAAO,KAAK;EACV,MAAM;EACN,SAAS;EACT,MAAM,CAAC,YAAY;EACpB,CAAC;AAGJ,KAAI,CAAC,KAAK,SAAS,MAAM,CACvB,QAAO,KAAK;EACV,MAAM;EACN,SAAS;EACT,MAAM,CAAC,UAAU;EAClB,CAAC;AAQJ,KAAI,CALc,YAAY,MAAM,QAAQ;EAC1C,MAAM,QAAQ,KAAK,YAAY;AAC/B,SAAO,OAAO,UAAU,YAAY,MAAM,MAAM,CAAC,SAAS;GAC1D,CAGA,QAAO,KAAK;EACV,MAAM;EACN,SACE;EACF,MAAM,CAAC,YAAY;EACpB,CAAC;AAGJ,KAAI,CAAC,KAAK,eAAe,OACvB,QAAO,KAAK;EACV,MAAM;EACN,SAAS;EACT,MAAM,CAAC,gBAAgB;EACxB,CAAC;KAEF,MAAK,cAAc,SAAS,KAAK,QAAQ;AAEvC,uBAAqB,KADR,CAAC,iBAAiB,OAAO,IAAI,CAAC,EACX,OAAO;GACvC;AAGJ,QAAO;EACL,OAAO,OAAO,WAAW;EACzB;EACD;;AAGH,SAAS,qBACP,cACA,MACA,QACA;CACA,MAAM,QAAQ,MAAc,SAAiB,cAAyB;AACpE,SAAO,KAAK;GACV;GACA;GACA,MAAM,YAAY,CAAC,GAAG,MAAM,GAAG,UAAU,GAAG;GAC7C,CAAC;;AAGJ,KAAI,oBAAoB,aAAa,EACnC;MAAI,CAAC,aAAa,OAAO,MAAM,CAC7B,MAAK,qBAAqB,2CAA2C,CACnE,QACD,CAAC;;AAIN,SAAQ,aAAa,MAArB;EACE,KAAK;AACH,OAAI,CAAC,aAAa,UAAU,MAAM,CAChC,MAAK,oCAAoC,wBAAwB,CAC/D,WACD,CAAC;AAEJ;EAEF,KAAK,iBAAiB;AACpB,OAAI,CAAC,aAAa,QAAQ,OACxB,MACE,oCACA,+BACA,CAAC,SAAS,CACX;GAEH,MAAM,uBAAO,IAAI,KAAa;AAC9B,QAAK,MAAM,SAAS,aAAa,UAAU,EAAE,EAAE;AAC7C,QAAI,CAAC,OAAO,MAAM,EAAE;AAClB,UACE,0CACA,mCACD;AACD;;AAEF,QAAI,KAAK,IAAI,MAAM,EAAE;AACnB,UACE,8CACA,UAAU,MAAM,6BACjB;AACD;;AAEF,SAAK,IAAI,MAAM;;AAEjB;;EAEF,KAAK;AACH,OAAI,aAAa,UAAU,OACzB,MAAK,gCAAgC,qBAAqB,CAAC,QAAQ,CAAC;AAEtE;EAEF,KAAK;AACH,OAAI,CAAC,aAAa,MAAM,MAAM,CAC5B,MAAK,gCAAgC,oBAAoB,CAAC,OAAO,CAAC;AAEpE;EAEF,KAAK;EACL,KAAK,YAEH;EACF,SAAS;GACP,MAAMC,aAAoB;AAC1B,SAAM,IAAI,MACR,oCAAqC,YAAoB,QAAQ,YAClE;;;;AAKP,SAAS,oBACP,KACwD;AACxD,QAAO,WAAW;;AAGpB,SAAgB,mBACd,MACA,YAA8B,yBAC9B;CACA,MAAM,SAAS,UAAU,KAAK;AAC9B,KAAI,CAAC,OAAO,OAAO;EACjB,MAAM,UAAU,OAAO,OACpB,KAAK,UAAU,GAAG,MAAM,KAAK,IAAI,MAAM,UAAU,CACjD,KAAK,KAAK;AACb,QAAM,IAAI,MAAM,wBAAwB,KAAK,UAAU,KAAK,UAAU"}
1
+ {"version":3,"file":"validator.js","names":["defaultOverlayValidator: OverlayValidator","issues: OverlayValidationIssue[]","exhaustive: never"],"sources":["../src/validator.ts"],"sourcesContent":["import type { OverlayModification, OverlaySpec } from './spec';\n\nexport interface OverlayValidationIssue {\n code: string;\n message: string;\n path?: string[];\n}\n\nexport interface OverlayValidationResult {\n valid: boolean;\n issues: OverlayValidationIssue[];\n}\n\nexport type OverlayValidator = (spec: OverlaySpec) => OverlayValidationResult;\n\nconst TARGET_KEYS = [\n 'capability',\n 'workflow',\n 'dataView',\n 'presentation',\n 'operation',\n] as const;\n\nexport const defaultOverlayValidator: OverlayValidator = (spec) =>\n validateOverlaySpec(spec);\n\nexport function validateOverlaySpec(\n spec: OverlaySpec\n): OverlayValidationResult {\n const issues: OverlayValidationIssue[] = [];\n\n if (!spec.overlayId?.trim()) {\n issues.push({\n code: 'overlay.id',\n message: 'overlayId is required',\n path: ['overlayId'],\n });\n }\n\n if (!spec.version?.trim()) {\n issues.push({\n code: 'overlay.version',\n message: 'version is required',\n path: ['version'],\n });\n }\n\n const hasTarget = TARGET_KEYS.some((key) => {\n const value = spec.appliesTo?.[key as keyof typeof spec.appliesTo];\n return typeof value === 'string' && value.trim().length > 0;\n });\n\n if (!hasTarget) {\n issues.push({\n code: 'overlay.target',\n message:\n 'Overlay must specify at least one target (capability, workflow, dataView, presentation, or operation).',\n path: ['appliesTo'],\n });\n }\n\n if (!spec.modifications?.length) {\n issues.push({\n code: 'overlay.modifications.empty',\n message: 'Overlay must include at least one modification.',\n path: ['modifications'],\n });\n } else {\n spec.modifications.forEach((mod, idx) => {\n const path = ['modifications', String(idx)];\n validateModification(mod, path, issues);\n });\n }\n\n return {\n valid: issues.length === 0,\n issues,\n };\n}\n\nfunction validateModification(\n modification: OverlayModification,\n path: string[],\n issues: OverlayValidationIssue[]\n) {\n const push = (code: string, message: string, extraPath?: string[]) => {\n issues.push({\n code,\n message,\n path: extraPath ? [...path, ...extraPath] : path,\n });\n };\n\n if (isFieldModification(modification)) {\n if (!modification.field?.trim()) {\n push('overlay.mod.field', 'field is required for this modification', [\n 'field',\n ]);\n }\n }\n\n switch (modification.type) {\n case 'renameLabel': {\n if (!modification.newLabel?.trim()) {\n push('overlay.mod.renameLabel.newLabel', 'newLabel is required', [\n 'newLabel',\n ]);\n }\n break;\n }\n case 'reorderFields': {\n if (!modification.fields?.length) {\n push(\n 'overlay.mod.reorderFields.fields',\n 'fields list cannot be empty',\n ['fields']\n );\n }\n const seen = new Set<string>();\n for (const field of modification.fields ?? []) {\n if (!field?.trim()) {\n push(\n 'overlay.mod.reorderFields.fields.blank',\n 'fields entries must be non-empty'\n );\n break;\n }\n if (seen.has(field)) {\n push(\n 'overlay.mod.reorderFields.fields.duplicate',\n `field \"${field}\" was listed multiple times`\n );\n break;\n }\n seen.add(field);\n }\n break;\n }\n case 'setDefault': {\n if (modification.value === undefined) {\n push('overlay.mod.setDefault.value', 'value is required', ['value']);\n }\n break;\n }\n case 'addHelpText': {\n if (!modification.text?.trim()) {\n push('overlay.mod.addHelpText.text', 'text is required', ['text']);\n }\n break;\n }\n case 'makeRequired':\n case 'hideField':\n // no extra validation\n break;\n default: {\n const exhaustive: never = modification;\n throw new Error(\n `Unsupported overlay modification ${(exhaustive as { type: string })?.type ?? 'unknown'}`\n );\n }\n }\n}\n\nfunction isFieldModification(\n mod: OverlayModification\n): mod is Extract<OverlayModification, { field: string }> {\n return 'field' in mod;\n}\n\nexport function assertOverlayValid(\n spec: OverlaySpec,\n validator: OverlayValidator = defaultOverlayValidator\n) {\n const result = validator(spec);\n if (!result.valid) {\n const message = result.issues\n .map((issue) => `${issue.code}: ${issue.message}`)\n .join('; ');\n throw new Error(`Invalid OverlaySpec \"${spec.overlayId}\": ${message}`);\n }\n}\n"],"mappings":";AAeA,MAAM,cAAc;CAClB;CACA;CACA;CACA;CACA;CACD;AAED,MAAaA,2BAA6C,SACxD,oBAAoB,KAAK;AAE3B,SAAgB,oBACd,MACyB;CACzB,MAAMC,SAAmC,EAAE;AAE3C,KAAI,CAAC,KAAK,WAAW,MAAM,CACzB,QAAO,KAAK;EACV,MAAM;EACN,SAAS;EACT,MAAM,CAAC,YAAY;EACpB,CAAC;AAGJ,KAAI,CAAC,KAAK,SAAS,MAAM,CACvB,QAAO,KAAK;EACV,MAAM;EACN,SAAS;EACT,MAAM,CAAC,UAAU;EAClB,CAAC;AAQJ,KAAI,CALc,YAAY,MAAM,QAAQ;EAC1C,MAAM,QAAQ,KAAK,YAAY;AAC/B,SAAO,OAAO,UAAU,YAAY,MAAM,MAAM,CAAC,SAAS;GAC1D,CAGA,QAAO,KAAK;EACV,MAAM;EACN,SACE;EACF,MAAM,CAAC,YAAY;EACpB,CAAC;AAGJ,KAAI,CAAC,KAAK,eAAe,OACvB,QAAO,KAAK;EACV,MAAM;EACN,SAAS;EACT,MAAM,CAAC,gBAAgB;EACxB,CAAC;KAEF,MAAK,cAAc,SAAS,KAAK,QAAQ;AAEvC,uBAAqB,KADR,CAAC,iBAAiB,OAAO,IAAI,CAAC,EACX,OAAO;GACvC;AAGJ,QAAO;EACL,OAAO,OAAO,WAAW;EACzB;EACD;;AAGH,SAAS,qBACP,cACA,MACA,QACA;CACA,MAAM,QAAQ,MAAc,SAAiB,cAAyB;AACpE,SAAO,KAAK;GACV;GACA;GACA,MAAM,YAAY,CAAC,GAAG,MAAM,GAAG,UAAU,GAAG;GAC7C,CAAC;;AAGJ,KAAI,oBAAoB,aAAa,EACnC;MAAI,CAAC,aAAa,OAAO,MAAM,CAC7B,MAAK,qBAAqB,2CAA2C,CACnE,QACD,CAAC;;AAIN,SAAQ,aAAa,MAArB;EACE,KAAK;AACH,OAAI,CAAC,aAAa,UAAU,MAAM,CAChC,MAAK,oCAAoC,wBAAwB,CAC/D,WACD,CAAC;AAEJ;EAEF,KAAK,iBAAiB;AACpB,OAAI,CAAC,aAAa,QAAQ,OACxB,MACE,oCACA,+BACA,CAAC,SAAS,CACX;GAEH,MAAM,uBAAO,IAAI,KAAa;AAC9B,QAAK,MAAM,SAAS,aAAa,UAAU,EAAE,EAAE;AAC7C,QAAI,CAAC,OAAO,MAAM,EAAE;AAClB,UACE,0CACA,mCACD;AACD;;AAEF,QAAI,KAAK,IAAI,MAAM,EAAE;AACnB,UACE,8CACA,UAAU,MAAM,6BACjB;AACD;;AAEF,SAAK,IAAI,MAAM;;AAEjB;;EAEF,KAAK;AACH,OAAI,aAAa,UAAU,OACzB,MAAK,gCAAgC,qBAAqB,CAAC,QAAQ,CAAC;AAEtE;EAEF,KAAK;AACH,OAAI,CAAC,aAAa,MAAM,MAAM,CAC5B,MAAK,gCAAgC,oBAAoB,CAAC,OAAO,CAAC;AAEpE;EAEF,KAAK;EACL,KAAK,YAEH;EACF,SAAS;GACP,MAAMC,aAAoB;AAC1B,SAAM,IAAI,MACR,oCAAqC,YAAiC,QAAQ,YAC/E;;;;AAKP,SAAS,oBACP,KACwD;AACxD,QAAO,WAAW;;AAGpB,SAAgB,mBACd,MACA,YAA8B,yBAC9B;CACA,MAAM,SAAS,UAAU,KAAK;AAC9B,KAAI,CAAC,OAAO,OAAO;EACjB,MAAM,UAAU,OAAO,OACpB,KAAK,UAAU,GAAG,MAAM,KAAK,IAAI,MAAM,UAAU,CACjD,KAAK,KAAK;AACb,QAAM,IAAI,MAAM,wBAAwB,KAAK,UAAU,KAAK,UAAU"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lssm/lib.overlay-engine",
3
- "version": "0.0.0-canary-20251220002821",
3
+ "version": "0.0.0-canary-20251220021406",
4
4
  "description": "Runtime overlay engine for ContractSpec personalization and adaptive UI rendering.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -24,7 +24,7 @@
24
24
  "test": "bun run"
25
25
  },
26
26
  "dependencies": {
27
- "@lssm/lib.contracts": "0.0.0-canary-20251220002821",
27
+ "@lssm/lib.contracts": "0.0.0-canary-20251220021406",
28
28
  "fast-json-stable-stringify": "^2.1.0"
29
29
  },
30
30
  "peerDependencies": {
@@ -36,8 +36,8 @@
36
36
  }
37
37
  },
38
38
  "devDependencies": {
39
- "@lssm/tool.tsdown": "0.0.0-canary-20251220002821",
40
- "@lssm/tool.typescript": "0.0.0-canary-20251220002821",
39
+ "@lssm/tool.tsdown": "0.0.0-canary-20251220021406",
40
+ "@lssm/tool.typescript": "0.0.0-canary-20251220021406",
41
41
  "tsdown": "^0.18.1",
42
42
  "typescript": "^5.9.3"
43
43
  },