@contractspec/lib.overlay-engine 3.7.5 → 3.7.7

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 CHANGED
@@ -1,85 +1,73 @@
1
1
  # @contractspec/lib.overlay-engine
2
2
 
3
- Website: https://contractspec.io/
4
-
5
-
6
- 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.
7
-
8
- ## Features
9
-
10
- - Type-safe OverlaySpec definitions that mirror the docs.
11
- - Cryptographic signing and verification (Ed25519, RSA-PSS).
12
- - Registry + validator that enforces policy boundaries.
13
- - Deterministic merge engine for tenant / role / user overlays.
14
- - Runtime helpers + React hooks for applying overlays to form/data view field definitions.
15
-
16
- ## Usage
17
-
18
- ```ts
19
- import {
20
- OverlayEngine,
21
- OverlayRegistry,
22
- defineOverlay,
23
- signOverlay,
24
- } from '@contractspec/lib.overlay-engine';
25
-
26
- const registry = new OverlayRegistry();
27
- const engine = new OverlayEngine({ registry });
28
-
29
- const overlay = defineOverlay({
30
- overlayId: 'acme-order-form',
31
- version: '1.0.0',
32
- appliesTo: {
33
- capability: 'billing.createOrder',
34
- tenantId: 'acme',
35
- },
36
- modifications: [
37
- { type: 'hideField', field: 'internalNotes' },
38
- { type: 'renameLabel', field: 'customerReference', newLabel: 'PO Number' },
39
- ],
40
- });
41
-
42
- const signed = await signOverlay(overlay, PRIVATE_KEY_PEM, { keyId: 'acme' });
43
- registry.register(signed);
44
-
45
- const result = engine.apply({
46
- target: {
47
- fields: baseFields,
48
- },
49
- context: { tenantId: 'acme', userId: 'u_123' },
50
- capability: 'billing.createOrder',
51
- });
52
- ```
53
-
54
- See `docs/tech/personalization/overlay-engine.md` for additional details.
55
-
56
-
57
-
58
-
59
-
60
-
61
-
62
-
63
-
64
-
65
-
66
-
67
-
68
-
69
-
70
-
71
-
72
-
73
-
74
-
75
-
76
-
3
+ Website: https://contractspec.io
77
4
 
5
+ **Runtime overlay engine for ContractSpec personalization and adaptive UI rendering.**
78
6
 
7
+ ## What It Provides
79
8
 
9
+ - **Layer**: lib.
10
+ - **Consumers**: personalization, example-shared-ui, bundles.
11
+ - Related ContractSpec packages include `@contractspec/lib.contracts-spec`, `@contractspec/tool.bun`, `@contractspec/tool.typescript`.
12
+ - Related ContractSpec packages include `@contractspec/lib.contracts-spec`, `@contractspec/tool.bun`, `@contractspec/tool.typescript`.
80
13
 
14
+ ## Installation
81
15
 
16
+ `npm install @contractspec/lib.overlay-engine`
82
17
 
18
+ or
83
19
 
20
+ `bun add @contractspec/lib.overlay-engine`
84
21
 
22
+ ## Usage
85
23
 
24
+ Import the root entrypoint from `@contractspec/lib.overlay-engine`, or choose a documented subpath when you only need one part of the package surface.
25
+
26
+ ## Architecture
27
+
28
+ - `src/index.ts` is the root public barrel and package entrypoint.
29
+ - `src/merger.ts` is part of the package's public or composition surface.
30
+ - `src/react.ts` is part of the package's public or composition surface.
31
+ - `src/registry.ts` is part of the package's public or composition surface.
32
+ - `src/runtime.ts` is part of the package's public or composition surface.
33
+ - `src/signer.ts` is part of the package's public or composition surface.
34
+ - `src/spec.ts` is part of the package's public or composition surface.
35
+ - `src/types.ts` is shared public type definitions.
36
+
37
+ ## Public Entry Points
38
+
39
+ - Export `.` resolves through `./src/index.ts`.
40
+ - Export `./merger` resolves through `./src/merger.ts`.
41
+ - Export `./react` resolves through `./src/react.ts`.
42
+ - Export `./registry` resolves through `./src/registry.ts`.
43
+ - Export `./runtime` resolves through `./src/runtime.ts`.
44
+ - Export `./signer` resolves through `./src/signer.ts`.
45
+ - Export `./spec` resolves through `./src/spec.ts`.
46
+ - Export `./types` resolves through `./src/types.ts`.
47
+ - Export `./validator` resolves through `./src/validator.ts`.
48
+
49
+ ## Local Commands
50
+
51
+ - `bun run dev` — contractspec-bun-build dev
52
+ - `bun run build` — bun run prebuild && bun run build:bundle && bun run build:types
53
+ - `bun run test` — bun test --pass-with-no-tests
54
+ - `bun run lint` — bun lint:fix
55
+ - `bun run lint:check` — biome check .
56
+ - `bun run lint:fix` — biome check --write --unsafe --only=nursery/useSortedClasses . && biome check --write .
57
+ - `bun run typecheck` — tsc --noEmit
58
+ - `bun run publish:pkg` — bun publish --tolerate-republish --ignore-scripts --verbose
59
+ - `bun run publish:pkg:canary` — bun publish:pkg --tag canary
60
+ - `bun run clean` — rimraf dist .turbo
61
+ - `bun run build:bundle` — contractspec-bun-build transpile
62
+ - `bun run build:types` — contractspec-bun-build types
63
+ - `bun run prebuild` — contractspec-bun-build prebuild
64
+
65
+ ## Recent Updates
66
+
67
+ - Replace eslint+prettier by biomejs to optimize speed.
68
+
69
+ ## Notes
70
+
71
+ - Overlay spec schema is a contract — changes are breaking for all consumers.
72
+ - Signer must preserve cryptographic integrity; do not alter signing algorithm without migration.
73
+ - Merger logic must be idempotent — applying the same overlay twice must produce identical results.
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- export * from './spec';
2
- export * from './types';
1
+ export * from './merger';
3
2
  export * from './registry';
4
- export * from './validator';
5
3
  export * from './runtime';
6
- export * from './merger';
7
4
  export * from './signer';
5
+ export * from './spec';
6
+ export * from './types';
7
+ export * from './validator';
package/dist/index.js CHANGED
@@ -1,15 +1,127 @@
1
1
  // @bun
2
- // src/spec.ts
3
- var OVERLAY_SCOPE_ORDER = [
4
- "tenantId",
5
- "role",
6
- "userId",
7
- "device",
8
- "tags"
9
- ];
10
- function defineOverlay(spec) {
11
- return spec;
2
+ // src/merger.ts
3
+ function applyOverlayModifications(target, overlays, options = {}) {
4
+ if (!overlays.length) {
5
+ return target;
6
+ }
7
+ const states = target.fields.map((field) => ({
8
+ key: field.key,
9
+ field: { ...field },
10
+ hidden: field.visible === false
11
+ }));
12
+ const fieldMap = new Map(states.map((state) => [state.key, state]));
13
+ let orderSequence = target.fields.map((field) => field.key);
14
+ const handleMissing = (field, overlayId) => {
15
+ if (options.strict) {
16
+ throw new Error(`Overlay "${overlayId}" referenced unknown field "${field}".`);
17
+ }
18
+ };
19
+ overlays.forEach((overlay) => {
20
+ overlay.modifications.forEach((modification) => {
21
+ switch (modification.type) {
22
+ case "hideField": {
23
+ const state = fieldMap.get(modification.field);
24
+ if (!state)
25
+ return handleMissing(modification.field, overlay.overlayId);
26
+ state.hidden = true;
27
+ state.field.visible = false;
28
+ break;
29
+ }
30
+ case "renameLabel": {
31
+ const state = fieldMap.get(modification.field);
32
+ if (!state)
33
+ return handleMissing(modification.field, overlay.overlayId);
34
+ state.field.label = modification.newLabel;
35
+ break;
36
+ }
37
+ case "setDefault": {
38
+ const state = fieldMap.get(modification.field);
39
+ if (!state)
40
+ return handleMissing(modification.field, overlay.overlayId);
41
+ state.field.defaultValue = modification.value;
42
+ break;
43
+ }
44
+ case "addHelpText": {
45
+ const state = fieldMap.get(modification.field);
46
+ if (!state)
47
+ return handleMissing(modification.field, overlay.overlayId);
48
+ state.field.helpText = modification.text;
49
+ break;
50
+ }
51
+ case "makeRequired": {
52
+ const state = fieldMap.get(modification.field);
53
+ if (!state)
54
+ return handleMissing(modification.field, overlay.overlayId);
55
+ state.field.required = modification.required ?? true;
56
+ break;
57
+ }
58
+ case "reorderFields": {
59
+ const { filtered, missing } = normalizeOrderList(modification.fields, fieldMap);
60
+ if (missing.length && options.strict) {
61
+ missing.forEach((field) => handleMissing(field, overlay.overlayId));
62
+ }
63
+ orderSequence = applyReorder(orderSequence, filtered);
64
+ break;
65
+ }
66
+ default:
67
+ break;
68
+ }
69
+ });
70
+ });
71
+ const visibleFields = [];
72
+ const seen = new Set;
73
+ orderSequence.forEach((key) => {
74
+ const state = fieldMap.get(key);
75
+ if (!state || state.hidden) {
76
+ return;
77
+ }
78
+ seen.add(key);
79
+ visibleFields.push(state.field);
80
+ });
81
+ states.forEach((state) => {
82
+ if (state.hidden || seen.has(state.key)) {
83
+ return;
84
+ }
85
+ visibleFields.push(state.field);
86
+ });
87
+ visibleFields.forEach((field, index) => {
88
+ field.order = index;
89
+ field.visible = true;
90
+ });
91
+ return {
92
+ ...target,
93
+ fields: visibleFields
94
+ };
12
95
  }
96
+ function normalizeOrderList(fields, fieldMap) {
97
+ const filtered = [];
98
+ const missing = [];
99
+ const seen = new Set;
100
+ fields.forEach((field) => {
101
+ if (!field?.trim()) {
102
+ return;
103
+ }
104
+ if (!fieldMap.has(field)) {
105
+ missing.push(field);
106
+ return;
107
+ }
108
+ if (seen.has(field)) {
109
+ return;
110
+ }
111
+ seen.add(field);
112
+ filtered.push(field);
113
+ });
114
+ return { filtered, missing };
115
+ }
116
+ function applyReorder(sequence, orderedFields) {
117
+ if (!orderedFields.length) {
118
+ return sequence;
119
+ }
120
+ const orderedSet = new Set(orderedFields);
121
+ const remainder = sequence.filter((key) => !orderedSet.has(key));
122
+ return [...orderedFields, ...remainder];
123
+ }
124
+
13
125
  // src/validator.ts
14
126
  var TARGET_KEYS = [
15
127
  "capability",
@@ -270,129 +382,6 @@ function matches(appliesTo, ctx) {
270
382
  return true;
271
383
  }
272
384
 
273
- // src/merger.ts
274
- function applyOverlayModifications(target, overlays, options = {}) {
275
- if (!overlays.length) {
276
- return target;
277
- }
278
- const states = target.fields.map((field) => ({
279
- key: field.key,
280
- field: { ...field },
281
- hidden: field.visible === false
282
- }));
283
- const fieldMap = new Map(states.map((state) => [state.key, state]));
284
- let orderSequence = target.fields.map((field) => field.key);
285
- const handleMissing = (field, overlayId) => {
286
- if (options.strict) {
287
- throw new Error(`Overlay "${overlayId}" referenced unknown field "${field}".`);
288
- }
289
- };
290
- overlays.forEach((overlay) => {
291
- overlay.modifications.forEach((modification) => {
292
- switch (modification.type) {
293
- case "hideField": {
294
- const state = fieldMap.get(modification.field);
295
- if (!state)
296
- return handleMissing(modification.field, overlay.overlayId);
297
- state.hidden = true;
298
- state.field.visible = false;
299
- break;
300
- }
301
- case "renameLabel": {
302
- const state = fieldMap.get(modification.field);
303
- if (!state)
304
- return handleMissing(modification.field, overlay.overlayId);
305
- state.field.label = modification.newLabel;
306
- break;
307
- }
308
- case "setDefault": {
309
- const state = fieldMap.get(modification.field);
310
- if (!state)
311
- return handleMissing(modification.field, overlay.overlayId);
312
- state.field.defaultValue = modification.value;
313
- break;
314
- }
315
- case "addHelpText": {
316
- const state = fieldMap.get(modification.field);
317
- if (!state)
318
- return handleMissing(modification.field, overlay.overlayId);
319
- state.field.helpText = modification.text;
320
- break;
321
- }
322
- case "makeRequired": {
323
- const state = fieldMap.get(modification.field);
324
- if (!state)
325
- return handleMissing(modification.field, overlay.overlayId);
326
- state.field.required = modification.required ?? true;
327
- break;
328
- }
329
- case "reorderFields": {
330
- const { filtered, missing } = normalizeOrderList(modification.fields, fieldMap);
331
- if (missing.length && options.strict) {
332
- missing.forEach((field) => handleMissing(field, overlay.overlayId));
333
- }
334
- orderSequence = applyReorder(orderSequence, filtered);
335
- break;
336
- }
337
- default:
338
- break;
339
- }
340
- });
341
- });
342
- const visibleFields = [];
343
- const seen = new Set;
344
- orderSequence.forEach((key) => {
345
- const state = fieldMap.get(key);
346
- if (!state || state.hidden) {
347
- return;
348
- }
349
- seen.add(key);
350
- visibleFields.push(state.field);
351
- });
352
- states.forEach((state) => {
353
- if (state.hidden || seen.has(state.key)) {
354
- return;
355
- }
356
- visibleFields.push(state.field);
357
- });
358
- visibleFields.forEach((field, index) => {
359
- field.order = index;
360
- field.visible = true;
361
- });
362
- return {
363
- ...target,
364
- fields: visibleFields
365
- };
366
- }
367
- function normalizeOrderList(fields, fieldMap) {
368
- const filtered = [];
369
- const missing = [];
370
- const seen = new Set;
371
- fields.forEach((field) => {
372
- if (!field?.trim()) {
373
- return;
374
- }
375
- if (!fieldMap.has(field)) {
376
- missing.push(field);
377
- return;
378
- }
379
- if (seen.has(field)) {
380
- return;
381
- }
382
- seen.add(field);
383
- filtered.push(field);
384
- });
385
- return { filtered, missing };
386
- }
387
- function applyReorder(sequence, orderedFields) {
388
- if (!orderedFields.length) {
389
- return sequence;
390
- }
391
- const orderedSet = new Set(orderedFields);
392
- const remainder = sequence.filter((key) => !orderedSet.has(key));
393
- return [...orderedFields, ...remainder];
394
- }
395
-
396
385
  // src/runtime.ts
397
386
  class OverlayEngine {
398
387
  registry;
@@ -445,7 +434,6 @@ function extractContext(params) {
445
434
  }
446
435
 
447
436
  // src/signer.ts
448
- import stringify from "fast-json-stable-stringify";
449
437
  import {
450
438
  constants,
451
439
  createPrivateKey,
@@ -453,6 +441,7 @@ import {
453
441
  sign,
454
442
  verify
455
443
  } from "crypto";
444
+ import stringify from "fast-json-stable-stringify";
456
445
  function signOverlay(spec, privateKey, options = {}) {
457
446
  const algorithm = options.algorithm ?? "ed25519";
458
447
  const keyObject = typeof privateKey === "string" || Buffer.isBuffer(privateKey) ? createPrivateKey(privateKey) : privateKey;
@@ -519,6 +508,18 @@ function toIso(value) {
519
508
  }
520
509
  return value.toISOString();
521
510
  }
511
+
512
+ // src/spec.ts
513
+ var OVERLAY_SCOPE_ORDER = [
514
+ "tenantId",
515
+ "role",
516
+ "userId",
517
+ "device",
518
+ "tags"
519
+ ];
520
+ function defineOverlay(spec) {
521
+ return spec;
522
+ }
522
523
  export {
523
524
  verifyOverlaySignature,
524
525
  validateOverlaySpec,
@@ -1,14 +1,126 @@
1
- // src/spec.ts
2
- var OVERLAY_SCOPE_ORDER = [
3
- "tenantId",
4
- "role",
5
- "userId",
6
- "device",
7
- "tags"
8
- ];
9
- function defineOverlay(spec) {
10
- return spec;
1
+ // src/merger.ts
2
+ function applyOverlayModifications(target, overlays, options = {}) {
3
+ if (!overlays.length) {
4
+ return target;
5
+ }
6
+ const states = target.fields.map((field) => ({
7
+ key: field.key,
8
+ field: { ...field },
9
+ hidden: field.visible === false
10
+ }));
11
+ const fieldMap = new Map(states.map((state) => [state.key, state]));
12
+ let orderSequence = target.fields.map((field) => field.key);
13
+ const handleMissing = (field, overlayId) => {
14
+ if (options.strict) {
15
+ throw new Error(`Overlay "${overlayId}" referenced unknown field "${field}".`);
16
+ }
17
+ };
18
+ overlays.forEach((overlay) => {
19
+ overlay.modifications.forEach((modification) => {
20
+ switch (modification.type) {
21
+ case "hideField": {
22
+ const state = fieldMap.get(modification.field);
23
+ if (!state)
24
+ return handleMissing(modification.field, overlay.overlayId);
25
+ state.hidden = true;
26
+ state.field.visible = false;
27
+ break;
28
+ }
29
+ case "renameLabel": {
30
+ const state = fieldMap.get(modification.field);
31
+ if (!state)
32
+ return handleMissing(modification.field, overlay.overlayId);
33
+ state.field.label = modification.newLabel;
34
+ break;
35
+ }
36
+ case "setDefault": {
37
+ const state = fieldMap.get(modification.field);
38
+ if (!state)
39
+ return handleMissing(modification.field, overlay.overlayId);
40
+ state.field.defaultValue = modification.value;
41
+ break;
42
+ }
43
+ case "addHelpText": {
44
+ const state = fieldMap.get(modification.field);
45
+ if (!state)
46
+ return handleMissing(modification.field, overlay.overlayId);
47
+ state.field.helpText = modification.text;
48
+ break;
49
+ }
50
+ case "makeRequired": {
51
+ const state = fieldMap.get(modification.field);
52
+ if (!state)
53
+ return handleMissing(modification.field, overlay.overlayId);
54
+ state.field.required = modification.required ?? true;
55
+ break;
56
+ }
57
+ case "reorderFields": {
58
+ const { filtered, missing } = normalizeOrderList(modification.fields, fieldMap);
59
+ if (missing.length && options.strict) {
60
+ missing.forEach((field) => handleMissing(field, overlay.overlayId));
61
+ }
62
+ orderSequence = applyReorder(orderSequence, filtered);
63
+ break;
64
+ }
65
+ default:
66
+ break;
67
+ }
68
+ });
69
+ });
70
+ const visibleFields = [];
71
+ const seen = new Set;
72
+ orderSequence.forEach((key) => {
73
+ const state = fieldMap.get(key);
74
+ if (!state || state.hidden) {
75
+ return;
76
+ }
77
+ seen.add(key);
78
+ visibleFields.push(state.field);
79
+ });
80
+ states.forEach((state) => {
81
+ if (state.hidden || seen.has(state.key)) {
82
+ return;
83
+ }
84
+ visibleFields.push(state.field);
85
+ });
86
+ visibleFields.forEach((field, index) => {
87
+ field.order = index;
88
+ field.visible = true;
89
+ });
90
+ return {
91
+ ...target,
92
+ fields: visibleFields
93
+ };
11
94
  }
95
+ function normalizeOrderList(fields, fieldMap) {
96
+ const filtered = [];
97
+ const missing = [];
98
+ const seen = new Set;
99
+ fields.forEach((field) => {
100
+ if (!field?.trim()) {
101
+ return;
102
+ }
103
+ if (!fieldMap.has(field)) {
104
+ missing.push(field);
105
+ return;
106
+ }
107
+ if (seen.has(field)) {
108
+ return;
109
+ }
110
+ seen.add(field);
111
+ filtered.push(field);
112
+ });
113
+ return { filtered, missing };
114
+ }
115
+ function applyReorder(sequence, orderedFields) {
116
+ if (!orderedFields.length) {
117
+ return sequence;
118
+ }
119
+ const orderedSet = new Set(orderedFields);
120
+ const remainder = sequence.filter((key) => !orderedSet.has(key));
121
+ return [...orderedFields, ...remainder];
122
+ }
123
+
12
124
  // src/validator.ts
13
125
  var TARGET_KEYS = [
14
126
  "capability",
@@ -269,129 +381,6 @@ function matches(appliesTo, ctx) {
269
381
  return true;
270
382
  }
271
383
 
272
- // src/merger.ts
273
- function applyOverlayModifications(target, overlays, options = {}) {
274
- if (!overlays.length) {
275
- return target;
276
- }
277
- const states = target.fields.map((field) => ({
278
- key: field.key,
279
- field: { ...field },
280
- hidden: field.visible === false
281
- }));
282
- const fieldMap = new Map(states.map((state) => [state.key, state]));
283
- let orderSequence = target.fields.map((field) => field.key);
284
- const handleMissing = (field, overlayId) => {
285
- if (options.strict) {
286
- throw new Error(`Overlay "${overlayId}" referenced unknown field "${field}".`);
287
- }
288
- };
289
- overlays.forEach((overlay) => {
290
- overlay.modifications.forEach((modification) => {
291
- switch (modification.type) {
292
- case "hideField": {
293
- const state = fieldMap.get(modification.field);
294
- if (!state)
295
- return handleMissing(modification.field, overlay.overlayId);
296
- state.hidden = true;
297
- state.field.visible = false;
298
- break;
299
- }
300
- case "renameLabel": {
301
- const state = fieldMap.get(modification.field);
302
- if (!state)
303
- return handleMissing(modification.field, overlay.overlayId);
304
- state.field.label = modification.newLabel;
305
- break;
306
- }
307
- case "setDefault": {
308
- const state = fieldMap.get(modification.field);
309
- if (!state)
310
- return handleMissing(modification.field, overlay.overlayId);
311
- state.field.defaultValue = modification.value;
312
- break;
313
- }
314
- case "addHelpText": {
315
- const state = fieldMap.get(modification.field);
316
- if (!state)
317
- return handleMissing(modification.field, overlay.overlayId);
318
- state.field.helpText = modification.text;
319
- break;
320
- }
321
- case "makeRequired": {
322
- const state = fieldMap.get(modification.field);
323
- if (!state)
324
- return handleMissing(modification.field, overlay.overlayId);
325
- state.field.required = modification.required ?? true;
326
- break;
327
- }
328
- case "reorderFields": {
329
- const { filtered, missing } = normalizeOrderList(modification.fields, fieldMap);
330
- if (missing.length && options.strict) {
331
- missing.forEach((field) => handleMissing(field, overlay.overlayId));
332
- }
333
- orderSequence = applyReorder(orderSequence, filtered);
334
- break;
335
- }
336
- default:
337
- break;
338
- }
339
- });
340
- });
341
- const visibleFields = [];
342
- const seen = new Set;
343
- orderSequence.forEach((key) => {
344
- const state = fieldMap.get(key);
345
- if (!state || state.hidden) {
346
- return;
347
- }
348
- seen.add(key);
349
- visibleFields.push(state.field);
350
- });
351
- states.forEach((state) => {
352
- if (state.hidden || seen.has(state.key)) {
353
- return;
354
- }
355
- visibleFields.push(state.field);
356
- });
357
- visibleFields.forEach((field, index) => {
358
- field.order = index;
359
- field.visible = true;
360
- });
361
- return {
362
- ...target,
363
- fields: visibleFields
364
- };
365
- }
366
- function normalizeOrderList(fields, fieldMap) {
367
- const filtered = [];
368
- const missing = [];
369
- const seen = new Set;
370
- fields.forEach((field) => {
371
- if (!field?.trim()) {
372
- return;
373
- }
374
- if (!fieldMap.has(field)) {
375
- missing.push(field);
376
- return;
377
- }
378
- if (seen.has(field)) {
379
- return;
380
- }
381
- seen.add(field);
382
- filtered.push(field);
383
- });
384
- return { filtered, missing };
385
- }
386
- function applyReorder(sequence, orderedFields) {
387
- if (!orderedFields.length) {
388
- return sequence;
389
- }
390
- const orderedSet = new Set(orderedFields);
391
- const remainder = sequence.filter((key) => !orderedSet.has(key));
392
- return [...orderedFields, ...remainder];
393
- }
394
-
395
384
  // src/runtime.ts
396
385
  class OverlayEngine {
397
386
  registry;
@@ -444,7 +433,6 @@ function extractContext(params) {
444
433
  }
445
434
 
446
435
  // src/signer.ts
447
- import stringify from "fast-json-stable-stringify";
448
436
  import {
449
437
  constants,
450
438
  createPrivateKey,
@@ -452,6 +440,7 @@ import {
452
440
  sign,
453
441
  verify
454
442
  } from "crypto";
443
+ import stringify from "fast-json-stable-stringify";
455
444
  function signOverlay(spec, privateKey, options = {}) {
456
445
  const algorithm = options.algorithm ?? "ed25519";
457
446
  const keyObject = typeof privateKey === "string" || Buffer.isBuffer(privateKey) ? createPrivateKey(privateKey) : privateKey;
@@ -518,6 +507,18 @@ function toIso(value) {
518
507
  }
519
508
  return value.toISOString();
520
509
  }
510
+
511
+ // src/spec.ts
512
+ var OVERLAY_SCOPE_ORDER = [
513
+ "tenantId",
514
+ "role",
515
+ "userId",
516
+ "device",
517
+ "tags"
518
+ ];
519
+ function defineOverlay(spec) {
520
+ return spec;
521
+ }
521
522
  export {
522
523
  verifyOverlaySignature,
523
524
  validateOverlaySpec,
@@ -1,5 +1,4 @@
1
1
  // src/signer.ts
2
- import stringify from "fast-json-stable-stringify";
3
2
  import {
4
3
  constants,
5
4
  createPrivateKey,
@@ -7,6 +6,7 @@ import {
7
6
  sign,
8
7
  verify
9
8
  } from "crypto";
9
+ import stringify from "fast-json-stable-stringify";
10
10
  function signOverlay(spec, privateKey, options = {}) {
11
11
  const algorithm = options.algorithm ?? "ed25519";
12
12
  const keyObject = typeof privateKey === "string" || Buffer.isBuffer(privateKey) ? createPrivateKey(privateKey) : privateKey;
package/dist/react.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { type DependencyList } from 'react';
2
- import type { OverlayRenderable } from './types';
3
2
  import type { OverlayApplyParams, OverlayRuntimeResult } from './runtime';
4
3
  import { OverlayEngine } from './runtime';
4
+ import type { OverlayRenderable } from './types';
5
5
  export declare function useOverlay<T extends OverlayRenderable>(engine: OverlayEngine | undefined, params: OverlayApplyParams<T>, deps?: DependencyList): OverlayRuntimeResult<T>;
6
6
  export declare function useOverlayFields<T extends OverlayRenderable>(engine: OverlayEngine | undefined, params: OverlayApplyParams<T>, deps?: DependencyList): T['fields'];
package/dist/runtime.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { type ApplyOverlayOptions } from './merger';
2
- import { OverlayRegistry, type OverlayLookup } from './registry';
2
+ import { type OverlayLookup, OverlayRegistry } from './registry';
3
3
  import type { SignedOverlaySpec } from './spec';
4
- import type { OverlayRenderable, OverlayAuditEvent } from './types';
4
+ import type { OverlayAuditEvent, OverlayRenderable } from './types';
5
5
  export interface OverlayEngineOptions {
6
6
  registry: OverlayRegistry;
7
7
  audit?: (event: OverlayAuditEvent) => void;
package/dist/signer.js CHANGED
@@ -1,6 +1,5 @@
1
1
  // @bun
2
2
  // src/signer.ts
3
- import stringify from "fast-json-stable-stringify";
4
3
  import {
5
4
  constants,
6
5
  createPrivateKey,
@@ -8,6 +7,7 @@ import {
8
7
  sign,
9
8
  verify
10
9
  } from "crypto";
10
+ import stringify from "fast-json-stable-stringify";
11
11
  function signOverlay(spec, privateKey, options = {}) {
12
12
  const algorithm = options.algorithm ?? "ed25519";
13
13
  const keyObject = typeof privateKey === "string" || Buffer.isBuffer(privateKey) ? createPrivateKey(privateKey) : privateKey;
package/dist/types.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { SignedOverlaySpec, OverlayScopeContext, OverlayTargetRef } from './spec';
1
+ import type { OverlayScopeContext, OverlayTargetRef, SignedOverlaySpec } from './spec';
2
2
  export interface OverlayRenderableField {
3
3
  key: string;
4
4
  label?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contractspec/lib.overlay-engine",
3
- "version": "3.7.5",
3
+ "version": "3.7.7",
4
4
  "description": "Runtime overlay engine for ContractSpec personalization and adaptive UI rendering.",
5
5
  "keywords": [
6
6
  "contractspec",
@@ -25,14 +25,14 @@
25
25
  "dev": "contractspec-bun-build dev",
26
26
  "clean": "rimraf dist .turbo",
27
27
  "lint": "bun lint:fix",
28
- "lint:fix": "eslint src --fix",
29
- "lint:check": "eslint src",
28
+ "lint:fix": "biome check --write --unsafe --only=nursery/useSortedClasses . && biome check --write .",
29
+ "lint:check": "biome check .",
30
30
  "test": "bun test --pass-with-no-tests",
31
31
  "prebuild": "contractspec-bun-build prebuild",
32
32
  "typecheck": "tsc --noEmit"
33
33
  },
34
34
  "dependencies": {
35
- "@contractspec/lib.contracts-spec": "3.7.5",
35
+ "@contractspec/lib.contracts-spec": "4.0.0",
36
36
  "fast-json-stable-stringify": "^2.1.0"
37
37
  },
38
38
  "peerDependencies": {
@@ -44,9 +44,9 @@
44
44
  }
45
45
  },
46
46
  "devDependencies": {
47
- "@contractspec/tool.typescript": "3.7.5",
47
+ "@contractspec/tool.typescript": "3.7.6",
48
48
  "typescript": "^5.9.3",
49
- "@contractspec/tool.bun": "3.7.5"
49
+ "@contractspec/tool.bun": "3.7.6"
50
50
  },
51
51
  "exports": {
52
52
  ".": {