@lssm/lib.overlay-engine 0.0.0-canary-20251206160926

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.
package/README.md ADDED
@@ -0,0 +1,71 @@
1
+ # @lssm/lib.overlay-engine
2
+
3
+ Runtime utilities for executing **OverlaySpecs** inside ContractSpec applications. The library tracks signed overlays, validates their safety guarantees, merges multi-scope overlays, and exposes React helpers to render personalized layouts without bespoke code.
4
+
5
+ ## Features
6
+
7
+ - Type-safe OverlaySpec definitions that mirror the docs.
8
+ - Cryptographic signing and verification (Ed25519, RSA-PSS).
9
+ - Registry + validator that enforces policy boundaries.
10
+ - Deterministic merge engine for tenant / role / user overlays.
11
+ - Runtime helpers + React hooks for applying overlays to form/data view field definitions.
12
+
13
+ ## Usage
14
+
15
+ ```ts
16
+ import {
17
+ OverlayEngine,
18
+ OverlayRegistry,
19
+ defineOverlay,
20
+ signOverlay,
21
+ } from '@lssm/lib.overlay-engine';
22
+
23
+ const registry = new OverlayRegistry();
24
+ const engine = new OverlayEngine({ registry });
25
+
26
+ const overlay = defineOverlay({
27
+ overlayId: 'acme-order-form',
28
+ version: '1.0.0',
29
+ appliesTo: {
30
+ capability: 'billing.createOrder',
31
+ tenantId: 'acme',
32
+ },
33
+ modifications: [
34
+ { type: 'hideField', field: 'internalNotes' },
35
+ { type: 'renameLabel', field: 'customerReference', newLabel: 'PO Number' },
36
+ ],
37
+ });
38
+
39
+ const signed = await signOverlay(overlay, PRIVATE_KEY_PEM, { keyId: 'acme' });
40
+ registry.register(signed);
41
+
42
+ const result = engine.apply({
43
+ target: {
44
+ fields: baseFields,
45
+ },
46
+ context: { tenantId: 'acme', userId: 'u_123' },
47
+ capability: 'billing.createOrder',
48
+ });
49
+ ```
50
+
51
+ See `docs/tech/personalization/overlay-engine.md` for additional details.
52
+
53
+
54
+
55
+
56
+
57
+
58
+
59
+
60
+
61
+
62
+
63
+
64
+
65
+
66
+
67
+
68
+
69
+
70
+
71
+
@@ -0,0 +1,8 @@
1
+ import { AddHelpTextModification, HideFieldModification, MakeRequiredModification, OVERLAY_SCOPE_ORDER, OverlayAppliesTo, OverlayFieldModificationBase, OverlayInput, OverlayModification, OverlayScopeContext, OverlayScopeKey, OverlaySignatureAlgorithm, OverlaySignatureBlock, OverlaySpec, OverlayTargetRef, RenameLabelModification, ReorderFieldsModification, SetDefaultModification, SignedOverlaySpec, defineOverlay } from "./spec.js";
2
+ import { OverlayAuditEvent, OverlayLayoutConfig, OverlayMatchContext, OverlayRenderable, OverlayRenderableField } from "./types.js";
3
+ import { OverlayValidationIssue, OverlayValidationResult, OverlayValidator, assertOverlayValid, defaultOverlayValidator, validateOverlaySpec } from "./validator.js";
4
+ import { OverlayLookup, OverlayRegistry, OverlayRegistryOptions } from "./registry.js";
5
+ import { ApplyOverlayOptions, applyOverlayModifications } from "./merger.js";
6
+ import { OverlayApplyParams, OverlayEngine, OverlayEngineOptions, OverlayRuntimeResult } from "./runtime.js";
7
+ import { SignOverlayOptions, canonicalizeOverlay, signOverlay, stripSignature, verifyOverlaySignature } from "./signer.js";
8
+ export { AddHelpTextModification, ApplyOverlayOptions, HideFieldModification, MakeRequiredModification, OVERLAY_SCOPE_ORDER, OverlayAppliesTo, OverlayApplyParams, OverlayAuditEvent, OverlayEngine, OverlayEngineOptions, OverlayFieldModificationBase, OverlayInput, OverlayLayoutConfig, OverlayLookup, OverlayMatchContext, OverlayModification, OverlayRegistry, OverlayRegistryOptions, OverlayRenderable, OverlayRenderableField, OverlayRuntimeResult, OverlayScopeContext, OverlayScopeKey, OverlaySignatureAlgorithm, OverlaySignatureBlock, OverlaySpec, OverlayTargetRef, OverlayValidationIssue, OverlayValidationResult, OverlayValidator, RenameLabelModification, ReorderFieldsModification, SetDefaultModification, SignOverlayOptions, SignedOverlaySpec, applyOverlayModifications, assertOverlayValid, canonicalizeOverlay, defaultOverlayValidator, defineOverlay, signOverlay, stripSignature, validateOverlaySpec, verifyOverlaySignature };
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ import{OVERLAY_SCOPE_ORDER as e,defineOverlay as t}from"./spec.js";import{assertOverlayValid as n,defaultOverlayValidator as r,validateOverlaySpec as i}from"./validator.js";import{OverlayRegistry as a}from"./registry.js";import{applyOverlayModifications as o}from"./merger.js";import{OverlayEngine as s}from"./runtime.js";import{canonicalizeOverlay as c,signOverlay as l,stripSignature as u,verifyOverlaySignature as d}from"./signer.js";export{e as OVERLAY_SCOPE_ORDER,s as OverlayEngine,a as OverlayRegistry,o as applyOverlayModifications,n as assertOverlayValid,c as canonicalizeOverlay,r as defaultOverlayValidator,t as defineOverlay,l as signOverlay,u as stripSignature,i as validateOverlaySpec,d as verifyOverlaySignature};
@@ -0,0 +1,10 @@
1
+ import { SignedOverlaySpec } from "./spec.js";
2
+ import { OverlayRenderable } from "./types.js";
3
+
4
+ //#region src/merger.d.ts
5
+ interface ApplyOverlayOptions {
6
+ strict?: boolean;
7
+ }
8
+ declare function applyOverlayModifications<T extends OverlayRenderable>(target: T, overlays: SignedOverlaySpec[], options?: ApplyOverlayOptions): T;
9
+ //#endregion
10
+ export { ApplyOverlayOptions, applyOverlayModifications };
package/dist/merger.js ADDED
@@ -0,0 +1 @@
1
+ function e(e,r,i={}){if(!r.length)return e;let a=e.fields.map(e=>({key:e.key,field:{...e},hidden:e.visible===!1})),o=new Map(a.map(e=>[e.key,e])),s=e.fields.map(e=>e.key),c=(e,t)=>{if(i.strict)throw Error(`Overlay "${t}" referenced unknown field "${e}".`)};r.forEach(e=>{e.modifications.forEach(r=>{switch(r.type){case`hideField`:{let t=o.get(r.field);if(!t)return c(r.field,e.overlayId);t.hidden=!0,t.field.visible=!1;break}case`renameLabel`:{let t=o.get(r.field);if(!t)return c(r.field,e.overlayId);t.field.label=r.newLabel;break}case`setDefault`:{let t=o.get(r.field);if(!t)return c(r.field,e.overlayId);t.field.defaultValue=r.value;break}case`addHelpText`:{let t=o.get(r.field);if(!t)return c(r.field,e.overlayId);t.field.helpText=r.text;break}case`makeRequired`:{let t=o.get(r.field);if(!t)return c(r.field,e.overlayId);t.field.required=r.required??!0;break}case`reorderFields`:{let{filtered:a,missing:l}=t(r.fields,o);l.length&&i.strict&&l.forEach(t=>c(t,e.overlayId)),s=n(s,a);break}default:break}})});let l=[],u=new Set;return s.forEach(e=>{let t=o.get(e);!t||t.hidden||(u.add(e),l.push(t.field))}),a.forEach(e=>{e.hidden||u.has(e.key)||l.push(e.field)}),l.forEach((e,t)=>{e.order=t,e.visible=!0}),{...e,fields:l}}function t(e,t){let n=[],r=[],i=new Set;return e.forEach(e=>{if(e?.trim()){if(!t.has(e)){r.push(e);return}i.has(e)||(i.add(e),n.push(e))}}),{filtered:n,missing:r}}function n(e,t){if(!t.length)return e;let n=new Set(t),r=e.filter(e=>!n.has(e));return[...t,...r]}export{e as applyOverlayModifications};
@@ -0,0 +1,9 @@
1
+ import { OverlayRenderable } from "./types.js";
2
+ import { OverlayApplyParams, OverlayEngine, OverlayRuntimeResult } from "./runtime.js";
3
+ import { DependencyList } from "react";
4
+
5
+ //#region src/react.d.ts
6
+ declare function useOverlay<T extends OverlayRenderable>(engine: OverlayEngine | undefined, params: OverlayApplyParams<T>, deps?: DependencyList): OverlayRuntimeResult<T>;
7
+ declare function useOverlayFields<T extends OverlayRenderable>(engine: OverlayEngine | undefined, params: OverlayApplyParams<T>, deps?: DependencyList): T['fields'];
8
+ //#endregion
9
+ export { useOverlay, useOverlayFields };
package/dist/react.js ADDED
@@ -0,0 +1 @@
1
+ import{useMemo as e}from"react";function t(t,n,r=[]){return e(()=>t?t.apply(n):{target:n.target,overlaysApplied:[]},[t,n,...r])}function n(e,n,r=[]){return t(e,n,r).target.fields}export{t as useOverlay,n as useOverlayFields};
@@ -0,0 +1,27 @@
1
+ import { OverlayInput, OverlayScopeContext, OverlayTargetRef, SignedOverlaySpec } from "./spec.js";
2
+ import { OverlayValidator } from "./validator.js";
3
+
4
+ //#region src/registry.d.ts
5
+ interface OverlayRegistryOptions {
6
+ validator?: OverlayValidator;
7
+ allowUnsigned?: boolean;
8
+ }
9
+ interface OverlayLookup extends OverlayScopeContext, OverlayTargetRef {}
10
+ declare class OverlayRegistry {
11
+ private readonly options;
12
+ private readonly overlays;
13
+ constructor(options?: OverlayRegistryOptions);
14
+ register(overlay: OverlayInput, options?: {
15
+ skipValidation?: boolean;
16
+ }): SignedOverlaySpec;
17
+ unregister(overlayId: string, version?: string): void;
18
+ list(): SignedOverlaySpec[];
19
+ get(overlayId: string, version: string): SignedOverlaySpec | undefined;
20
+ forContext(query: OverlayLookup): SignedOverlaySpec[];
21
+ clear(): void;
22
+ size(): number;
23
+ private ensureSigned;
24
+ private getKey;
25
+ }
26
+ //#endregion
27
+ export { OverlayLookup, OverlayRegistry, OverlayRegistryOptions };
@@ -0,0 +1 @@
1
+ import{defaultOverlayValidator as e}from"./validator.js";const t=[`capability`,`workflow`,`dataView`,`presentation`,`operation`],n={tenantId:8,role:4,userId:16,device:2,tags:1};var r=class{overlays=new Map;constructor(e={}){this.options=e}register(t,n){if(!n?.skipValidation){let n=(this.options.validator??e)(t);if(!n.valid){let e=n.issues.map(e=>`${e.code}: ${e.message}`).join(`; `);throw Error(`Overlay "${t.overlayId}" failed validation: ${e}`)}}let r=this.ensureSigned(t),i=this.getKey(r.overlayId,r.version),o={overlay:r,specificity:a(r.appliesTo),registeredAt:Date.now()};return this.overlays.set(i,o),r}unregister(e,t){if(t){this.overlays.delete(this.getKey(e,t));return}for(let t of Array.from(this.overlays.keys()))t.startsWith(`${e}@`)&&this.overlays.delete(t)}list(){return Array.from(this.overlays.values()).map(e=>e.overlay)}get(e,t){return this.overlays.get(this.getKey(e,t))?.overlay}forContext(e){return Array.from(this.overlays.values()).filter(t=>o(t.overlay.appliesTo,e)).sort((e,t)=>e.specificity===t.specificity?e.registeredAt-t.registeredAt:e.specificity-t.specificity).map(e=>e.overlay)}clear(){this.overlays.clear()}size(){return this.overlays.size}ensureSigned(e){if(i(e)){if(!e.signature?.signature&&!this.options.allowUnsigned)throw Error(`Overlay "${e.overlayId}" is missing a signature.`);return e}if(!this.options.allowUnsigned)throw Error(`Overlay "${e.overlayId}" must be signed before registration.`);return e}getKey(e,t){return`${e}@${t}`}};function i(e){return!!e.signature}function a(e){let t=0;return Object.keys(n).forEach(r=>{(r===`tags`?Array.isArray(e.tags)&&e.tags.length>0:e[r])&&(t+=n[r])}),t}function o(e,n){for(let r of t){let t=e[r];if(t&&t!==n[r])return!1}if(e.tenantId&&e.tenantId!==n.tenantId||e.role&&e.role!==n.role||e.userId&&e.userId!==n.userId||e.device&&e.device!==n.device)return!1;if(e.tags?.length){if(!n.tags?.length)return!1;let t=new Set(n.tags);if(!e.tags.every(e=>t.has(e)))return!1}return!0}export{r as OverlayRegistry};
@@ -0,0 +1,27 @@
1
+ import { SignedOverlaySpec } from "./spec.js";
2
+ import { OverlayAuditEvent, OverlayRenderable } from "./types.js";
3
+ import { OverlayLookup, OverlayRegistry } from "./registry.js";
4
+ import { ApplyOverlayOptions } from "./merger.js";
5
+
6
+ //#region src/runtime.d.ts
7
+ interface OverlayEngineOptions {
8
+ registry: OverlayRegistry;
9
+ audit?: (event: OverlayAuditEvent) => void;
10
+ }
11
+ interface OverlayApplyParams<T extends OverlayRenderable> extends OverlayLookup {
12
+ target: T;
13
+ overlays?: SignedOverlaySpec[];
14
+ strict?: ApplyOverlayOptions['strict'];
15
+ }
16
+ interface OverlayRuntimeResult<T extends OverlayRenderable> {
17
+ target: T;
18
+ overlaysApplied: SignedOverlaySpec[];
19
+ }
20
+ declare class OverlayEngine {
21
+ private readonly registry;
22
+ private readonly audit?;
23
+ constructor(options: OverlayEngineOptions);
24
+ apply<T extends OverlayRenderable>(params: OverlayApplyParams<T>): OverlayRuntimeResult<T>;
25
+ }
26
+ //#endregion
27
+ export { OverlayApplyParams, OverlayEngine, OverlayEngineOptions, OverlayRuntimeResult };
@@ -0,0 +1 @@
1
+ import{applyOverlayModifications as e}from"./merger.js";var t=class{registry;audit;constructor(e){this.registry=e.registry,this.audit=e.audit}apply(t){let r=t.overlays??this.registry.forContext({capability:t.capability,workflow:t.workflow,dataView:t.dataView,presentation:t.presentation,operation:t.operation,tenantId:t.tenantId,role:t.role,userId:t.userId,device:t.device,tags:t.tags}),i=e(t.target,r,{strict:t.strict}),a=n(t);return r.forEach(e=>{this.audit?.({overlay:{overlayId:e.overlayId,version:e.version},context:a,timestamp:new Date().toISOString()})}),{target:i,overlaysApplied:r}}};function n(e){return{tenantId:e.tenantId,role:e.role,userId:e.userId,device:e.device,tags:e.tags}}export{t as OverlayEngine};
@@ -0,0 +1,18 @@
1
+ import { OverlaySignatureAlgorithm, OverlaySpec, SignedOverlaySpec } from "./spec.js";
2
+ import { KeyLike } from "crypto";
3
+
4
+ //#region src/signer.d.ts
5
+ interface SignOverlayOptions {
6
+ algorithm?: OverlaySignatureAlgorithm;
7
+ keyId?: string;
8
+ issuedAt?: Date | string;
9
+ expiresAt?: Date | string;
10
+ metadata?: Record<string, unknown>;
11
+ publicKey?: string;
12
+ }
13
+ declare function signOverlay(spec: OverlaySpec, privateKey: KeyLike | string | Buffer, options?: SignOverlayOptions): SignedOverlaySpec;
14
+ declare function verifyOverlaySignature(overlay: SignedOverlaySpec): boolean;
15
+ declare function canonicalizeOverlay(spec: OverlaySpec | SignedOverlaySpec): string;
16
+ declare function stripSignature<T extends OverlaySpec | SignedOverlaySpec>(spec: T): OverlaySpec;
17
+ //#endregion
18
+ export { SignOverlayOptions, canonicalizeOverlay, signOverlay, stripSignature, verifyOverlaySignature };
package/dist/signer.js ADDED
@@ -0,0 +1 @@
1
+ import e from"fast-json-stable-stringify";import{constants as t,createPrivateKey as n,createPublicKey as r,sign as i,verify as a}from"crypto";function o(e,a,o={}){let s=o.algorithm??`ed25519`,l=typeof a==`string`||Buffer.isBuffer(a)?n(a):a,d=Buffer.from(c(e),`utf8`),f;if(s===`ed25519`)f=i(null,d,l);else if(s===`rsa-pss-sha256`)f=i(`sha256`,d,{key:l,padding:t.RSA_PKCS1_PSS_PADDING,saltLength:32});else throw Error(`Unsupported overlay signature algorithm: ${s}`);let p=o.publicKey??r(l).export({format:`pem`,type:`spki`}).toString();return{...e,signature:{algorithm:s,signature:f.toString(`base64`),publicKey:p,keyId:o.keyId,issuedAt:u(o.issuedAt)??new Date().toISOString(),expiresAt:u(o.expiresAt),metadata:o.metadata}}}function s(e){if(!e.signature?.signature)throw Error(`Overlay "${e.overlayId}" is missing signature metadata.`);let n=Buffer.from(c(e),`utf8`),i=Buffer.from(e.signature.signature,`base64`),o=r(e.signature.publicKey);if(e.signature.algorithm===`ed25519`)return a(null,n,o,i);if(e.signature.algorithm===`rsa-pss-sha256`)return a(`sha256`,n,{key:o,padding:t.RSA_PKCS1_PSS_PADDING,saltLength:32},i);throw Error(`Unsupported overlay signature algorithm: ${e.signature.algorithm}`)}function c(t){let{signature:n,...r}=t;return e(r)}function l(e){let{signature:t,...n}=e;return{...n}}function u(e){if(e)return typeof e==`string`?new Date(e).toISOString():e.toISOString()}export{c as canonicalizeOverlay,o as signOverlay,l as stripSignature,s as verifyOverlaySignature};
package/dist/spec.d.ts ADDED
@@ -0,0 +1,82 @@
1
+ import { AnyContractSpec } from "@lssm/lib.contracts";
2
+
3
+ //#region src/spec.d.ts
4
+ type OverlayScopeKey = 'tenantId' | 'role' | 'userId' | 'device' | 'tags';
5
+ interface OverlayScopeContext {
6
+ tenantId?: string;
7
+ role?: string;
8
+ userId?: string;
9
+ device?: string;
10
+ tags?: string[];
11
+ }
12
+ interface OverlayTargetRef {
13
+ capability?: string;
14
+ workflow?: string;
15
+ dataView?: string;
16
+ presentation?: string;
17
+ operation?: AnyContractSpec['meta']['name'];
18
+ }
19
+ interface OverlayAppliesTo extends OverlayScopeContext, OverlayTargetRef {
20
+ /**
21
+ * Optional label to describe why this overlay exists (displayed in tooling).
22
+ */
23
+ label?: string;
24
+ }
25
+ type OverlayModification = HideFieldModification | RenameLabelModification | ReorderFieldsModification | SetDefaultModification | AddHelpTextModification | MakeRequiredModification;
26
+ interface OverlayFieldModificationBase {
27
+ field: string;
28
+ description?: string;
29
+ }
30
+ interface HideFieldModification extends OverlayFieldModificationBase {
31
+ type: 'hideField';
32
+ reason?: string;
33
+ }
34
+ interface RenameLabelModification extends OverlayFieldModificationBase {
35
+ type: 'renameLabel';
36
+ newLabel: string;
37
+ }
38
+ interface ReorderFieldsModification {
39
+ type: 'reorderFields';
40
+ fields: string[];
41
+ description?: string;
42
+ }
43
+ interface SetDefaultModification extends OverlayFieldModificationBase {
44
+ type: 'setDefault';
45
+ value: unknown;
46
+ }
47
+ interface AddHelpTextModification extends OverlayFieldModificationBase {
48
+ type: 'addHelpText';
49
+ text: string;
50
+ }
51
+ interface MakeRequiredModification extends OverlayFieldModificationBase {
52
+ type: 'makeRequired';
53
+ required?: boolean;
54
+ }
55
+ interface OverlaySpec {
56
+ overlayId: string;
57
+ version: string;
58
+ description?: string;
59
+ appliesTo: OverlayAppliesTo;
60
+ modifications: OverlayModification[];
61
+ metadata?: Record<string, unknown>;
62
+ createdBy?: string;
63
+ createdAt?: string;
64
+ }
65
+ type OverlaySignatureAlgorithm = 'ed25519' | 'rsa-pss-sha256';
66
+ interface OverlaySignatureBlock {
67
+ algorithm: OverlaySignatureAlgorithm;
68
+ signature: string;
69
+ publicKey: string;
70
+ keyId?: string;
71
+ issuedAt?: string;
72
+ expiresAt?: string;
73
+ metadata?: Record<string, unknown>;
74
+ }
75
+ type SignedOverlaySpec = OverlaySpec & {
76
+ signature: OverlaySignatureBlock;
77
+ };
78
+ type OverlayInput = OverlaySpec | SignedOverlaySpec;
79
+ declare const OVERLAY_SCOPE_ORDER: OverlayScopeKey[];
80
+ declare function defineOverlay<T extends OverlaySpec>(spec: T): T;
81
+ //#endregion
82
+ export { AddHelpTextModification, HideFieldModification, MakeRequiredModification, OVERLAY_SCOPE_ORDER, OverlayAppliesTo, OverlayFieldModificationBase, OverlayInput, OverlayModification, OverlayScopeContext, OverlayScopeKey, OverlaySignatureAlgorithm, OverlaySignatureBlock, OverlaySpec, OverlayTargetRef, RenameLabelModification, ReorderFieldsModification, SetDefaultModification, SignedOverlaySpec, defineOverlay };
package/dist/spec.js ADDED
@@ -0,0 +1 @@
1
+ const e=[`tenantId`,`role`,`userId`,`device`,`tags`];function t(e){return e}export{e as OVERLAY_SCOPE_ORDER,t as defineOverlay};
@@ -0,0 +1,32 @@
1
+ import { OverlayScopeContext, OverlayTargetRef, SignedOverlaySpec } from "./spec.js";
2
+
3
+ //#region src/types.d.ts
4
+ interface OverlayRenderableField {
5
+ key: string;
6
+ label?: string;
7
+ visible?: boolean;
8
+ required?: boolean;
9
+ helpText?: string;
10
+ defaultValue?: unknown;
11
+ metadata?: Record<string, unknown>;
12
+ order?: number;
13
+ }
14
+ interface OverlayLayoutConfig {
15
+ kind?: 'form' | 'list' | 'table' | 'grid';
16
+ columns?: number;
17
+ density?: 'comfortable' | 'compact';
18
+ [key: string]: unknown;
19
+ }
20
+ interface OverlayRenderable<TField extends OverlayRenderableField = OverlayRenderableField> {
21
+ fields: TField[];
22
+ layout?: OverlayLayoutConfig;
23
+ metadata?: Record<string, unknown>;
24
+ }
25
+ interface OverlayMatchContext extends OverlayScopeContext, OverlayTargetRef {}
26
+ interface OverlayAuditEvent {
27
+ overlay: Pick<SignedOverlaySpec, 'overlayId' | 'version'>;
28
+ context: OverlayScopeContext;
29
+ timestamp: string;
30
+ }
31
+ //#endregion
32
+ export { OverlayAuditEvent, OverlayLayoutConfig, OverlayMatchContext, OverlayRenderable, OverlayRenderableField };
@@ -0,0 +1,18 @@
1
+ import { OverlaySpec } from "./spec.js";
2
+
3
+ //#region src/validator.d.ts
4
+ interface OverlayValidationIssue {
5
+ code: string;
6
+ message: string;
7
+ path?: string[];
8
+ }
9
+ interface OverlayValidationResult {
10
+ valid: boolean;
11
+ issues: OverlayValidationIssue[];
12
+ }
13
+ type OverlayValidator = (spec: OverlaySpec) => OverlayValidationResult;
14
+ declare const defaultOverlayValidator: OverlayValidator;
15
+ declare function validateOverlaySpec(spec: OverlaySpec): OverlayValidationResult;
16
+ declare function assertOverlayValid(spec: OverlaySpec, validator?: OverlayValidator): void;
17
+ //#endregion
18
+ export { OverlayValidationIssue, OverlayValidationResult, OverlayValidator, assertOverlayValid, defaultOverlayValidator, validateOverlaySpec };
@@ -0,0 +1 @@
1
+ const e=[`capability`,`workflow`,`dataView`,`presentation`,`operation`],t=e=>n(e);function n(t){let n=[];return t.overlayId?.trim()||n.push({code:`overlay.id`,message:`overlayId is required`,path:[`overlayId`]}),t.version?.trim()||n.push({code:`overlay.version`,message:`version is required`,path:[`version`]}),e.some(e=>{let n=t.appliesTo?.[e];return typeof n==`string`&&n.trim().length>0})||n.push({code:`overlay.target`,message:`Overlay must specify at least one target (capability, workflow, dataView, presentation, or operation).`,path:[`appliesTo`]}),t.modifications?.length?t.modifications.forEach((e,t)=>{r(e,[`modifications`,String(t)],n)}):n.push({code:`overlay.modifications.empty`,message:`Overlay must include at least one modification.`,path:[`modifications`]}),{valid:n.length===0,issues:n}}function r(e,t,n){let r=(e,r,i)=>{n.push({code:e,message:r,path:i?[...t,...i]:t})};switch(i(e)&&(e.field?.trim()||r(`overlay.mod.field`,`field is required for this modification`,[`field`])),e.type){case`renameLabel`:e.newLabel?.trim()||r(`overlay.mod.renameLabel.newLabel`,`newLabel is required`,[`newLabel`]);break;case`reorderFields`:{e.fields?.length||r(`overlay.mod.reorderFields.fields`,`fields list cannot be empty`,[`fields`]);let t=new Set;for(let n of e.fields??[]){if(!n?.trim()){r(`overlay.mod.reorderFields.fields.blank`,`fields entries must be non-empty`);break}if(t.has(n)){r(`overlay.mod.reorderFields.fields.duplicate`,`field "${n}" was listed multiple times`);break}t.add(n)}break}case`setDefault`:e.value===void 0&&r(`overlay.mod.setDefault.value`,`value is required`,[`value`]);break;case`addHelpText`:e.text?.trim()||r(`overlay.mod.addHelpText.text`,`text is required`,[`text`]);break;case`makeRequired`:case`hideField`:break;default:{let t=e;throw Error(`Unsupported overlay modification ${t?.type??`unknown`}`)}}}function i(e){return`field`in e}function a(e,n=t){let r=n(e);if(!r.valid){let t=r.issues.map(e=>`${e.code}: ${e.message}`).join(`; `);throw Error(`Invalid OverlaySpec "${e.overlayId}": ${t}`)}}export{a as assertOverlayValid,t as defaultOverlayValidator,n as validateOverlaySpec};
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@lssm/lib.overlay-engine",
3
+ "version": "0.0.0-canary-20251206160926",
4
+ "description": "Runtime overlay engine for ContractSpec personalization and adaptive UI rendering.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "files": [
10
+ "dist",
11
+ "README.md"
12
+ ],
13
+ "scripts": {
14
+ "publish:pkg": "bun publish --tolerate-republish --ignore-scripts --verbose",
15
+ "build": "bun build:bundle && bun build:types",
16
+ "build:bundle": "tsdown",
17
+ "build:types": "tsc --noEmit",
18
+ "dev": "bun build:bundle --watch",
19
+ "clean": "rimraf dist .turbo",
20
+ "lint": "bun lint:fix",
21
+ "lint:fix": "eslint src --fix",
22
+ "lint:check": "eslint src",
23
+ "test": "bun run"
24
+ },
25
+ "dependencies": {
26
+ "@lssm/lib.contracts": "workspace:*",
27
+ "fast-json-stable-stringify": "^2.1.0"
28
+ },
29
+ "peerDependencies": {
30
+ "react": "^19.0.0"
31
+ },
32
+ "peerDependenciesMeta": {
33
+ "react": {
34
+ "optional": true
35
+ }
36
+ },
37
+ "devDependencies": {
38
+ "@lssm/tool.tsdown": "workspace:*",
39
+ "@lssm/tool.typescript": "workspace:*",
40
+ "tsdown": "^0.17.0",
41
+ "typescript": "^5.9.3"
42
+ },
43
+ "exports": {
44
+ ".": "./dist/index.js",
45
+ "./react": "./dist/react.js",
46
+ "./*": "./*"
47
+ },
48
+ "publishConfig": {
49
+ "access": "public"
50
+ }
51
+ }