@contractspec/lib.personalization 6.0.17 → 6.0.18
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 +167 -69
- package/dist/adapter.js +1 -46
- package/dist/analyzer.js +1 -72
- package/dist/browser/adapter.js +1 -46
- package/dist/browser/analyzer.js +1 -72
- package/dist/browser/docs/behavior-tracking.docblock.js +2 -19
- package/dist/browser/docs/index.js +4 -50
- package/dist/browser/docs/overlay-engine.docblock.js +2 -19
- package/dist/browser/docs/workflow-composition.docblock.js +2 -19
- package/dist/browser/index.js +4 -334
- package/dist/browser/store.js +1 -75
- package/dist/browser/tracker.js +1 -94
- package/dist/docs/behavior-tracking.docblock.js +2 -19
- package/dist/docs/index.js +4 -50
- package/dist/docs/overlay-engine.docblock.js +2 -19
- package/dist/docs/workflow-composition.docblock.js +2 -19
- package/dist/index.js +4 -334
- package/dist/node/adapter.js +1 -46
- package/dist/node/analyzer.js +1 -72
- package/dist/node/docs/behavior-tracking.docblock.js +2 -19
- package/dist/node/docs/index.js +4 -50
- package/dist/node/docs/overlay-engine.docblock.js +2 -19
- package/dist/node/docs/workflow-composition.docblock.js +2 -19
- package/dist/node/index.js +4 -334
- package/dist/node/store.js +1 -75
- package/dist/node/tracker.js +1 -94
- package/dist/store.js +1 -75
- package/dist/tracker.js +1 -94
- package/package.json +9 -9
package/dist/browser/index.js
CHANGED
|
@@ -1,128 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
function insightsToOverlaySuggestion(insights, options) {
|
|
3
|
-
const modifications = [];
|
|
4
|
-
insights.suggestedHiddenFields.forEach((field) => {
|
|
5
|
-
modifications.push({
|
|
6
|
-
type: "hideField",
|
|
7
|
-
field,
|
|
8
|
-
reason: "Automatically hidden because usage is near zero"
|
|
9
|
-
});
|
|
10
|
-
});
|
|
11
|
-
if (insights.frequentlyUsedFields.length) {
|
|
12
|
-
modifications.push({
|
|
13
|
-
type: "reorderFields",
|
|
14
|
-
fields: insights.frequentlyUsedFields
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
if (!modifications.length) {
|
|
18
|
-
return null;
|
|
19
|
-
}
|
|
20
|
-
return {
|
|
21
|
-
overlayId: options.overlayId,
|
|
22
|
-
version: options.version ?? "1.0.0",
|
|
23
|
-
appliesTo: {
|
|
24
|
-
tenantId: options.tenantId,
|
|
25
|
-
capability: options.capability,
|
|
26
|
-
userId: options.userId,
|
|
27
|
-
role: options.role
|
|
28
|
-
},
|
|
29
|
-
modifications,
|
|
30
|
-
metadata: {
|
|
31
|
-
generatedAt: new Date().toISOString(),
|
|
32
|
-
automated: true
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
function insightsToWorkflowAdaptations(insights) {
|
|
37
|
-
return insights.workflowBottlenecks.map((bottleneck) => ({
|
|
38
|
-
workflow: bottleneck.workflow,
|
|
39
|
-
step: bottleneck.step,
|
|
40
|
-
note: `High drop rate (${Math.round(bottleneck.dropRate * 100)}%) detected`
|
|
41
|
-
}));
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// src/analyzer.ts
|
|
45
|
-
var DEFAULT_THRESHOLD = 3;
|
|
46
|
-
|
|
47
|
-
class BehaviorAnalyzer {
|
|
48
|
-
store;
|
|
49
|
-
options;
|
|
50
|
-
constructor(store, options = {}) {
|
|
51
|
-
this.store = store;
|
|
52
|
-
this.options = options;
|
|
53
|
-
}
|
|
54
|
-
async analyze(params) {
|
|
55
|
-
const query = {
|
|
56
|
-
tenantId: params.tenantId,
|
|
57
|
-
userId: params.userId,
|
|
58
|
-
role: params.role
|
|
59
|
-
};
|
|
60
|
-
if (params.windowMs) {
|
|
61
|
-
query.since = Date.now() - params.windowMs;
|
|
62
|
-
}
|
|
63
|
-
const summary = await this.store.summarize(query);
|
|
64
|
-
return buildInsights(summary, this.options);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
function buildInsights(summary, options) {
|
|
68
|
-
const threshold = options.fieldInactivityThreshold ?? DEFAULT_THRESHOLD;
|
|
69
|
-
const minSamples = options.minSamples ?? 10;
|
|
70
|
-
const ignoredFields = [];
|
|
71
|
-
const frequentlyUsedFields = [];
|
|
72
|
-
for (const [field, count] of Object.entries(summary.fieldCounts)) {
|
|
73
|
-
if (count <= threshold) {
|
|
74
|
-
ignoredFields.push(field);
|
|
75
|
-
}
|
|
76
|
-
if (count >= threshold * 4) {
|
|
77
|
-
frequentlyUsedFields.push(field);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
const workflowBottlenecks = Object.entries(summary.workflowStepCounts).flatMap(([workflow, steps]) => {
|
|
81
|
-
const total = Object.values(steps).reduce((acc, value) => acc + value, 0);
|
|
82
|
-
if (!total || total < minSamples) {
|
|
83
|
-
return [];
|
|
84
|
-
}
|
|
85
|
-
return Object.entries(steps).filter(([, count]) => count / total < 0.4).map(([step, count]) => ({
|
|
86
|
-
workflow,
|
|
87
|
-
step,
|
|
88
|
-
dropRate: 1 - count / total
|
|
89
|
-
}));
|
|
90
|
-
});
|
|
91
|
-
const layoutPreference = detectLayout(summary);
|
|
92
|
-
return {
|
|
93
|
-
unusedFields: ignoredFields,
|
|
94
|
-
suggestedHiddenFields: ignoredFields.slice(0, 5),
|
|
95
|
-
frequentlyUsedFields: frequentlyUsedFields.slice(0, 10),
|
|
96
|
-
workflowBottlenecks,
|
|
97
|
-
layoutPreference
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
function detectLayout(summary) {
|
|
101
|
-
const fieldCount = Object.keys(summary.fieldCounts).length;
|
|
102
|
-
if (!fieldCount) {
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
if (fieldCount >= 15) {
|
|
106
|
-
return "table";
|
|
107
|
-
}
|
|
108
|
-
if (fieldCount >= 8) {
|
|
109
|
-
return "grid";
|
|
110
|
-
}
|
|
111
|
-
return "form";
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// src/docs/behavior-tracking.docblock.ts
|
|
115
|
-
import { registerDocBlocks } from "@contractspec/lib.contracts-spec/docs";
|
|
116
|
-
var personalization_behavior_tracking_DocBlocks = [
|
|
117
|
-
{
|
|
118
|
-
id: "docs.personalization.behavior-tracking",
|
|
119
|
-
title: "Behavior Tracking",
|
|
120
|
-
summary: "`@contractspec/lib.personalization` provides primitives to observe how tenants/users interact with specs and turn that telemetry into personalization insights.",
|
|
121
|
-
kind: "reference",
|
|
122
|
-
visibility: "public",
|
|
123
|
-
route: "/docs/personalization/behavior-tracking",
|
|
124
|
-
tags: ["personalization", "behavior-tracking"],
|
|
125
|
-
body: `# Behavior Tracking
|
|
1
|
+
function T(e,t){let r=[];if(e.suggestedHiddenFields.forEach((o)=>{r.push({type:"hideField",field:o,reason:"Automatically hidden because usage is near zero"})}),e.frequentlyUsedFields.length)r.push({type:"reorderFields",fields:e.frequentlyUsedFields});if(!r.length)return null;return{overlayId:t.overlayId,version:t.version??"1.0.0",appliesTo:{tenantId:t.tenantId,capability:t.capability,userId:t.userId,role:t.role},modifications:r,metadata:{generatedAt:new Date().toISOString(),automated:!0}}}function O(e){return e.workflowBottlenecks.map((t)=>({workflow:t.workflow,step:t.step,note:`High drop rate (${Math.round(t.dropRate*100)}%) detected`}))}class h{store;options;constructor(e,t={}){this.store=e;this.options=t}async analyze(e){let t={tenantId:e.tenantId,userId:e.userId,role:e.role};if(e.windowMs)t.since=Date.now()-e.windowMs;let r=await this.store.summarize(t);return v(r,this.options)}}function v(e,t){let r=t.fieldInactivityThreshold??3,o=t.minSamples??10,i=[],l=[];for(let[n,s]of Object.entries(e.fieldCounts)){if(s<=r)i.push(n);if(s>=r*4)l.push(n)}let c=Object.entries(e.workflowStepCounts).flatMap(([n,s])=>{let u=Object.values(s).reduce((a,d)=>a+d,0);if(!u||u<o)return[];return Object.entries(s).filter(([,a])=>a/u<0.4).map(([a,d])=>({workflow:n,step:a,dropRate:1-d/u}))}),p=m(e);return{unusedFields:i,suggestedHiddenFields:i.slice(0,5),frequentlyUsedFields:l.slice(0,10),workflowBottlenecks:c,layoutPreference:p}}function m(e){let t=Object.keys(e.fieldCounts).length;if(!t)return;if(t>=15)return"table";if(t>=8)return"grid";return"form"}import{registerDocBlocks as g}from"@contractspec/lib.contracts-spec/docs";var y=[{id:"docs.personalization.behavior-tracking",title:"Behavior Tracking",summary:"`@contractspec/lib.personalization` provides primitives to observe how tenants/users interact with specs and turn that telemetry into personalization insights.",kind:"reference",visibility:"public",route:"/docs/personalization/behavior-tracking",tags:["personalization","behavior-tracking"],body:`# Behavior Tracking
|
|
126
2
|
|
|
127
3
|
\`@contractspec/lib.personalization\` provides primitives to observe how tenants/users interact with specs and turn that telemetry into personalization insights.
|
|
128
4
|
|
|
@@ -201,23 +77,7 @@ When the adapter returns an overlay spec, pass it to the overlay engine to regis
|
|
|
201
77
|
|
|
202
78
|
|
|
203
79
|
|
|
204
|
-
`
|
|
205
|
-
}
|
|
206
|
-
];
|
|
207
|
-
registerDocBlocks(personalization_behavior_tracking_DocBlocks);
|
|
208
|
-
|
|
209
|
-
// src/docs/overlay-engine.docblock.ts
|
|
210
|
-
import { registerDocBlocks as registerDocBlocks2 } from "@contractspec/lib.contracts-spec/docs";
|
|
211
|
-
var personalization_overlay_engine_DocBlocks = [
|
|
212
|
-
{
|
|
213
|
-
id: "docs.personalization.overlay-engine",
|
|
214
|
-
title: "Overlay Engine",
|
|
215
|
-
summary: "`@contractspec/lib.overlay-engine` is the canonical runtime for OverlaySpecs. It validates specs, tracks scope precedence, and exposes hooks for React renderers.",
|
|
216
|
-
kind: "reference",
|
|
217
|
-
visibility: "public",
|
|
218
|
-
route: "/docs/personalization/overlay-engine",
|
|
219
|
-
tags: ["personalization", "overlay-engine"],
|
|
220
|
-
body: `# Overlay Engine
|
|
80
|
+
`}];g(y);import{registerDocBlocks as w}from"@contractspec/lib.contracts-spec/docs";var B=[{id:"docs.personalization.overlay-engine",title:"Overlay Engine",summary:"`@contractspec/lib.overlay-engine` is the canonical runtime for OverlaySpecs. It validates specs, tracks scope precedence, and exposes hooks for React renderers.",kind:"reference",visibility:"public",route:"/docs/personalization/overlay-engine",tags:["personalization","overlay-engine"],body:`# Overlay Engine
|
|
221
81
|
|
|
222
82
|
\`@contractspec/lib.overlay-engine\` is the canonical runtime for OverlaySpecs. It validates specs, tracks scope precedence, and exposes hooks for React renderers.
|
|
223
83
|
|
|
@@ -297,23 +157,7 @@ const result = engine.apply({
|
|
|
297
157
|
|
|
298
158
|
|
|
299
159
|
|
|
300
|
-
`
|
|
301
|
-
}
|
|
302
|
-
];
|
|
303
|
-
registerDocBlocks2(personalization_overlay_engine_DocBlocks);
|
|
304
|
-
|
|
305
|
-
// src/docs/workflow-composition.docblock.ts
|
|
306
|
-
import { registerDocBlocks as registerDocBlocks3 } from "@contractspec/lib.contracts-spec/docs";
|
|
307
|
-
var personalization_workflow_composition_DocBlocks = [
|
|
308
|
-
{
|
|
309
|
-
id: "docs.personalization.workflow-composition",
|
|
310
|
-
title: "Workflow Composition",
|
|
311
|
-
summary: "`@contractspec/lib.workflow-composer` composes base WorkflowSpecs with tenant/role/device-specific extensions, strict validation, deterministic merge ordering, metadata/annotation overlays, and orphan-graph protection for hidden-step rewrites.",
|
|
312
|
-
kind: "reference",
|
|
313
|
-
visibility: "public",
|
|
314
|
-
route: "/docs/personalization/workflow-composition",
|
|
315
|
-
tags: ["personalization", "workflow-composition"],
|
|
316
|
-
body: `# Workflow Composition
|
|
160
|
+
`}];w(B);import{registerDocBlocks as k}from"@contractspec/lib.contracts-spec/docs";var I=[{id:"docs.personalization.workflow-composition",title:"Workflow Composition",summary:"`@contractspec/lib.workflow-composer` composes base WorkflowSpecs with tenant/role/device-specific extensions, strict validation, deterministic merge ordering, metadata/annotation overlays, and orphan-graph protection for hidden-step rewrites.",kind:"reference",visibility:"public",route:"/docs/personalization/workflow-composition",tags:["personalization","workflow-composition"],body:`# Workflow Composition
|
|
317
161
|
|
|
318
162
|
\`@contractspec/lib.workflow-composer\` composes base WorkflowSpecs with tenant/role/device-specific extensions.
|
|
319
163
|
|
|
@@ -365,178 +209,4 @@ workflowRunner.execute(runtimeSpec, ctx);
|
|
|
365
209
|
- \`metadata\` and \`annotations\` overlays are merged into the composed runtime workflow for downstream observability and rollout tracing.
|
|
366
210
|
|
|
367
211
|
This keeps tenant overlays additive, auditable, and replay-safe.
|
|
368
|
-
`
|
|
369
|
-
}
|
|
370
|
-
];
|
|
371
|
-
registerDocBlocks3(personalization_workflow_composition_DocBlocks);
|
|
372
|
-
// src/store.ts
|
|
373
|
-
class InMemoryBehaviorStore {
|
|
374
|
-
events = [];
|
|
375
|
-
async record(event) {
|
|
376
|
-
this.events.push(event);
|
|
377
|
-
}
|
|
378
|
-
async bulkRecord(events) {
|
|
379
|
-
this.events.push(...events);
|
|
380
|
-
}
|
|
381
|
-
async query(query) {
|
|
382
|
-
return filterEvents(this.events, query);
|
|
383
|
-
}
|
|
384
|
-
async summarize(query) {
|
|
385
|
-
const events = await this.query(query);
|
|
386
|
-
const summary = {
|
|
387
|
-
fieldCounts: {},
|
|
388
|
-
featureCounts: {},
|
|
389
|
-
workflowStepCounts: {},
|
|
390
|
-
totalEvents: events.length
|
|
391
|
-
};
|
|
392
|
-
events.forEach((event) => {
|
|
393
|
-
switch (event.type) {
|
|
394
|
-
case "field_access":
|
|
395
|
-
summary.fieldCounts[event.field] = (summary.fieldCounts[event.field] ?? 0) + 1;
|
|
396
|
-
break;
|
|
397
|
-
case "feature_usage":
|
|
398
|
-
summary.featureCounts[event.feature] = (summary.featureCounts[event.feature] ?? 0) + 1;
|
|
399
|
-
break;
|
|
400
|
-
case "workflow_step": {
|
|
401
|
-
const workflow = summary.workflowStepCounts[event.workflow] ??= {};
|
|
402
|
-
workflow[event.step] = (workflow[event.step] ?? 0) + 1;
|
|
403
|
-
break;
|
|
404
|
-
}
|
|
405
|
-
default:
|
|
406
|
-
break;
|
|
407
|
-
}
|
|
408
|
-
});
|
|
409
|
-
return summary;
|
|
410
|
-
}
|
|
411
|
-
async clear() {
|
|
412
|
-
this.events = [];
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
function filterEvents(events, query) {
|
|
416
|
-
return events.filter((event) => {
|
|
417
|
-
if (query.tenantId && event.tenantId !== query.tenantId) {
|
|
418
|
-
return false;
|
|
419
|
-
}
|
|
420
|
-
if (query.userId && event.userId !== query.userId) {
|
|
421
|
-
return false;
|
|
422
|
-
}
|
|
423
|
-
if (query.role && event.role !== query.role) {
|
|
424
|
-
return false;
|
|
425
|
-
}
|
|
426
|
-
if (query.since && event.timestamp < query.since) {
|
|
427
|
-
return false;
|
|
428
|
-
}
|
|
429
|
-
if (query.until && event.timestamp > query.until) {
|
|
430
|
-
return false;
|
|
431
|
-
}
|
|
432
|
-
if (query.operation && event.type === "field_access" && event.operation !== query.operation) {
|
|
433
|
-
return false;
|
|
434
|
-
}
|
|
435
|
-
if (query.feature && event.type === "feature_usage" && event.feature !== query.feature) {
|
|
436
|
-
return false;
|
|
437
|
-
}
|
|
438
|
-
if (query.workflow && event.type === "workflow_step" && event.workflow !== query.workflow) {
|
|
439
|
-
return false;
|
|
440
|
-
}
|
|
441
|
-
return true;
|
|
442
|
-
});
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
// src/tracker.ts
|
|
446
|
-
import { metrics, trace } from "@opentelemetry/api";
|
|
447
|
-
var DEFAULT_BUFFER_SIZE = 25;
|
|
448
|
-
|
|
449
|
-
class BehaviorTracker {
|
|
450
|
-
store;
|
|
451
|
-
context;
|
|
452
|
-
tracer = trace.getTracer("lssm.personalization", "1.0.0");
|
|
453
|
-
counter = metrics.getMeter("lssm.personalization", "1.0.0").createCounter("lssm.personalization.events", {
|
|
454
|
-
description: "Behavior events tracked for personalization"
|
|
455
|
-
});
|
|
456
|
-
buffer = [];
|
|
457
|
-
bufferSize;
|
|
458
|
-
flushTimer;
|
|
459
|
-
constructor(options) {
|
|
460
|
-
this.store = options.store;
|
|
461
|
-
this.context = options.context;
|
|
462
|
-
this.bufferSize = options.bufferSize ?? DEFAULT_BUFFER_SIZE;
|
|
463
|
-
if (options.autoFlushIntervalMs) {
|
|
464
|
-
this.flushTimer = setInterval(() => {
|
|
465
|
-
this.flush();
|
|
466
|
-
}, options.autoFlushIntervalMs);
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
trackFieldAccess(input) {
|
|
470
|
-
const event = {
|
|
471
|
-
type: "field_access",
|
|
472
|
-
operation: input.operation,
|
|
473
|
-
field: input.field,
|
|
474
|
-
timestamp: Date.now(),
|
|
475
|
-
...this.context,
|
|
476
|
-
metadata: { ...this.context.metadata, ...input.metadata }
|
|
477
|
-
};
|
|
478
|
-
this.enqueue(event);
|
|
479
|
-
}
|
|
480
|
-
trackFeatureUsage(input) {
|
|
481
|
-
const event = {
|
|
482
|
-
type: "feature_usage",
|
|
483
|
-
feature: input.feature,
|
|
484
|
-
action: input.action,
|
|
485
|
-
timestamp: Date.now(),
|
|
486
|
-
...this.context,
|
|
487
|
-
metadata: { ...this.context.metadata, ...input.metadata }
|
|
488
|
-
};
|
|
489
|
-
this.enqueue(event);
|
|
490
|
-
}
|
|
491
|
-
trackWorkflowStep(input) {
|
|
492
|
-
const event = {
|
|
493
|
-
type: "workflow_step",
|
|
494
|
-
workflow: input.workflow,
|
|
495
|
-
step: input.step,
|
|
496
|
-
status: input.status,
|
|
497
|
-
timestamp: Date.now(),
|
|
498
|
-
...this.context,
|
|
499
|
-
metadata: { ...this.context.metadata, ...input.metadata }
|
|
500
|
-
};
|
|
501
|
-
this.enqueue(event);
|
|
502
|
-
}
|
|
503
|
-
async flush() {
|
|
504
|
-
if (!this.buffer.length)
|
|
505
|
-
return;
|
|
506
|
-
const events = this.buffer;
|
|
507
|
-
this.buffer = [];
|
|
508
|
-
await this.store.bulkRecord(events);
|
|
509
|
-
}
|
|
510
|
-
async dispose() {
|
|
511
|
-
if (this.flushTimer) {
|
|
512
|
-
clearInterval(this.flushTimer);
|
|
513
|
-
}
|
|
514
|
-
await this.flush();
|
|
515
|
-
}
|
|
516
|
-
enqueue(event) {
|
|
517
|
-
this.buffer.push(event);
|
|
518
|
-
this.counter.add(1, {
|
|
519
|
-
tenantId: this.context.tenantId,
|
|
520
|
-
type: event.type
|
|
521
|
-
});
|
|
522
|
-
this.tracer.startActiveSpan(`personalization.${event.type}`, (span) => {
|
|
523
|
-
span.setAttribute("tenant.id", this.context.tenantId);
|
|
524
|
-
if (this.context.userId)
|
|
525
|
-
span.setAttribute("user.id", this.context.userId);
|
|
526
|
-
span.setAttribute("personalization.event_type", event.type);
|
|
527
|
-
span.end();
|
|
528
|
-
});
|
|
529
|
-
if (this.buffer.length >= this.bufferSize) {
|
|
530
|
-
this.flush();
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
var createBehaviorTracker = (options) => new BehaviorTracker(options);
|
|
535
|
-
export {
|
|
536
|
-
insightsToWorkflowAdaptations,
|
|
537
|
-
insightsToOverlaySuggestion,
|
|
538
|
-
createBehaviorTracker,
|
|
539
|
-
InMemoryBehaviorStore,
|
|
540
|
-
BehaviorTracker,
|
|
541
|
-
BehaviorAnalyzer
|
|
542
|
-
};
|
|
212
|
+
`}];k(I);class x{events=[];async record(e){this.events.push(e)}async bulkRecord(e){this.events.push(...e)}async query(e){return b(this.events,e)}async summarize(e){let t=await this.query(e),r={fieldCounts:{},featureCounts:{},workflowStepCounts:{},totalEvents:t.length};return t.forEach((o)=>{switch(o.type){case"field_access":r.fieldCounts[o.field]=(r.fieldCounts[o.field]??0)+1;break;case"feature_usage":r.featureCounts[o.feature]=(r.featureCounts[o.feature]??0)+1;break;case"workflow_step":{let i=r.workflowStepCounts[o.workflow]??={};i[o.step]=(i[o.step]??0)+1;break}default:break}}),r}async clear(){this.events=[]}}function b(e,t){return e.filter((r)=>{if(t.tenantId&&r.tenantId!==t.tenantId)return!1;if(t.userId&&r.userId!==t.userId)return!1;if(t.role&&r.role!==t.role)return!1;if(t.since&&r.timestamp<t.since)return!1;if(t.until&&r.timestamp>t.until)return!1;if(t.operation&&r.type==="field_access"&&r.operation!==t.operation)return!1;if(t.feature&&r.type==="feature_usage"&&r.feature!==t.feature)return!1;if(t.workflow&&r.type==="workflow_step"&&r.workflow!==t.workflow)return!1;return!0})}import{metrics as S,trace as E}from"@opentelemetry/api";var F=25;class f{store;context;tracer=E.getTracer("lssm.personalization","1.0.0");counter=S.getMeter("lssm.personalization","1.0.0").createCounter("lssm.personalization.events",{description:"Behavior events tracked for personalization"});buffer=[];bufferSize;flushTimer;constructor(e){if(this.store=e.store,this.context=e.context,this.bufferSize=e.bufferSize??F,e.autoFlushIntervalMs)this.flushTimer=setInterval(()=>{this.flush()},e.autoFlushIntervalMs)}trackFieldAccess(e){let t={type:"field_access",operation:e.operation,field:e.field,timestamp:Date.now(),...this.context,metadata:{...this.context.metadata,...e.metadata}};this.enqueue(t)}trackFeatureUsage(e){let t={type:"feature_usage",feature:e.feature,action:e.action,timestamp:Date.now(),...this.context,metadata:{...this.context.metadata,...e.metadata}};this.enqueue(t)}trackWorkflowStep(e){let t={type:"workflow_step",workflow:e.workflow,step:e.step,status:e.status,timestamp:Date.now(),...this.context,metadata:{...this.context.metadata,...e.metadata}};this.enqueue(t)}async flush(){if(!this.buffer.length)return;let e=this.buffer;this.buffer=[],await this.store.bulkRecord(e)}async dispose(){if(this.flushTimer)clearInterval(this.flushTimer);await this.flush()}enqueue(e){if(this.buffer.push(e),this.counter.add(1,{tenantId:this.context.tenantId,type:e.type}),this.tracer.startActiveSpan(`personalization.${e.type}`,(t)=>{if(t.setAttribute("tenant.id",this.context.tenantId),this.context.userId)t.setAttribute("user.id",this.context.userId);t.setAttribute("personalization.event_type",e.type),t.end()}),this.buffer.length>=this.bufferSize)this.flush()}}var D=(e)=>new f(e);export{O as insightsToWorkflowAdaptations,T as insightsToOverlaySuggestion,D as createBehaviorTracker,x as InMemoryBehaviorStore,f as BehaviorTracker,h as BehaviorAnalyzer};
|
package/dist/browser/store.js
CHANGED
|
@@ -1,75 +1 @@
|
|
|
1
|
-
|
|
2
|
-
class InMemoryBehaviorStore {
|
|
3
|
-
events = [];
|
|
4
|
-
async record(event) {
|
|
5
|
-
this.events.push(event);
|
|
6
|
-
}
|
|
7
|
-
async bulkRecord(events) {
|
|
8
|
-
this.events.push(...events);
|
|
9
|
-
}
|
|
10
|
-
async query(query) {
|
|
11
|
-
return filterEvents(this.events, query);
|
|
12
|
-
}
|
|
13
|
-
async summarize(query) {
|
|
14
|
-
const events = await this.query(query);
|
|
15
|
-
const summary = {
|
|
16
|
-
fieldCounts: {},
|
|
17
|
-
featureCounts: {},
|
|
18
|
-
workflowStepCounts: {},
|
|
19
|
-
totalEvents: events.length
|
|
20
|
-
};
|
|
21
|
-
events.forEach((event) => {
|
|
22
|
-
switch (event.type) {
|
|
23
|
-
case "field_access":
|
|
24
|
-
summary.fieldCounts[event.field] = (summary.fieldCounts[event.field] ?? 0) + 1;
|
|
25
|
-
break;
|
|
26
|
-
case "feature_usage":
|
|
27
|
-
summary.featureCounts[event.feature] = (summary.featureCounts[event.feature] ?? 0) + 1;
|
|
28
|
-
break;
|
|
29
|
-
case "workflow_step": {
|
|
30
|
-
const workflow = summary.workflowStepCounts[event.workflow] ??= {};
|
|
31
|
-
workflow[event.step] = (workflow[event.step] ?? 0) + 1;
|
|
32
|
-
break;
|
|
33
|
-
}
|
|
34
|
-
default:
|
|
35
|
-
break;
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
return summary;
|
|
39
|
-
}
|
|
40
|
-
async clear() {
|
|
41
|
-
this.events = [];
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
function filterEvents(events, query) {
|
|
45
|
-
return events.filter((event) => {
|
|
46
|
-
if (query.tenantId && event.tenantId !== query.tenantId) {
|
|
47
|
-
return false;
|
|
48
|
-
}
|
|
49
|
-
if (query.userId && event.userId !== query.userId) {
|
|
50
|
-
return false;
|
|
51
|
-
}
|
|
52
|
-
if (query.role && event.role !== query.role) {
|
|
53
|
-
return false;
|
|
54
|
-
}
|
|
55
|
-
if (query.since && event.timestamp < query.since) {
|
|
56
|
-
return false;
|
|
57
|
-
}
|
|
58
|
-
if (query.until && event.timestamp > query.until) {
|
|
59
|
-
return false;
|
|
60
|
-
}
|
|
61
|
-
if (query.operation && event.type === "field_access" && event.operation !== query.operation) {
|
|
62
|
-
return false;
|
|
63
|
-
}
|
|
64
|
-
if (query.feature && event.type === "feature_usage" && event.feature !== query.feature) {
|
|
65
|
-
return false;
|
|
66
|
-
}
|
|
67
|
-
if (query.workflow && event.type === "workflow_step" && event.workflow !== query.workflow) {
|
|
68
|
-
return false;
|
|
69
|
-
}
|
|
70
|
-
return true;
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
export {
|
|
74
|
-
InMemoryBehaviorStore
|
|
75
|
-
};
|
|
1
|
+
class i{events=[];async record(o){this.events.push(o)}async bulkRecord(o){this.events.push(...o)}async query(o){return s(this.events,o)}async summarize(o){let e=await this.query(o),r={fieldCounts:{},featureCounts:{},workflowStepCounts:{},totalEvents:e.length};return e.forEach((t)=>{switch(t.type){case"field_access":r.fieldCounts[t.field]=(r.fieldCounts[t.field]??0)+1;break;case"feature_usage":r.featureCounts[t.feature]=(r.featureCounts[t.feature]??0)+1;break;case"workflow_step":{let a=r.workflowStepCounts[t.workflow]??={};a[t.step]=(a[t.step]??0)+1;break}default:break}}),r}async clear(){this.events=[]}}function s(o,e){return o.filter((r)=>{if(e.tenantId&&r.tenantId!==e.tenantId)return!1;if(e.userId&&r.userId!==e.userId)return!1;if(e.role&&r.role!==e.role)return!1;if(e.since&&r.timestamp<e.since)return!1;if(e.until&&r.timestamp>e.until)return!1;if(e.operation&&r.type==="field_access"&&r.operation!==e.operation)return!1;if(e.feature&&r.type==="feature_usage"&&r.feature!==e.feature)return!1;if(e.workflow&&r.type==="workflow_step"&&r.workflow!==e.workflow)return!1;return!0})}export{i as InMemoryBehaviorStore};
|
package/dist/browser/tracker.js
CHANGED
|
@@ -1,94 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { metrics, trace } from "@opentelemetry/api";
|
|
3
|
-
var DEFAULT_BUFFER_SIZE = 25;
|
|
4
|
-
|
|
5
|
-
class BehaviorTracker {
|
|
6
|
-
store;
|
|
7
|
-
context;
|
|
8
|
-
tracer = trace.getTracer("lssm.personalization", "1.0.0");
|
|
9
|
-
counter = metrics.getMeter("lssm.personalization", "1.0.0").createCounter("lssm.personalization.events", {
|
|
10
|
-
description: "Behavior events tracked for personalization"
|
|
11
|
-
});
|
|
12
|
-
buffer = [];
|
|
13
|
-
bufferSize;
|
|
14
|
-
flushTimer;
|
|
15
|
-
constructor(options) {
|
|
16
|
-
this.store = options.store;
|
|
17
|
-
this.context = options.context;
|
|
18
|
-
this.bufferSize = options.bufferSize ?? DEFAULT_BUFFER_SIZE;
|
|
19
|
-
if (options.autoFlushIntervalMs) {
|
|
20
|
-
this.flushTimer = setInterval(() => {
|
|
21
|
-
this.flush();
|
|
22
|
-
}, options.autoFlushIntervalMs);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
trackFieldAccess(input) {
|
|
26
|
-
const event = {
|
|
27
|
-
type: "field_access",
|
|
28
|
-
operation: input.operation,
|
|
29
|
-
field: input.field,
|
|
30
|
-
timestamp: Date.now(),
|
|
31
|
-
...this.context,
|
|
32
|
-
metadata: { ...this.context.metadata, ...input.metadata }
|
|
33
|
-
};
|
|
34
|
-
this.enqueue(event);
|
|
35
|
-
}
|
|
36
|
-
trackFeatureUsage(input) {
|
|
37
|
-
const event = {
|
|
38
|
-
type: "feature_usage",
|
|
39
|
-
feature: input.feature,
|
|
40
|
-
action: input.action,
|
|
41
|
-
timestamp: Date.now(),
|
|
42
|
-
...this.context,
|
|
43
|
-
metadata: { ...this.context.metadata, ...input.metadata }
|
|
44
|
-
};
|
|
45
|
-
this.enqueue(event);
|
|
46
|
-
}
|
|
47
|
-
trackWorkflowStep(input) {
|
|
48
|
-
const event = {
|
|
49
|
-
type: "workflow_step",
|
|
50
|
-
workflow: input.workflow,
|
|
51
|
-
step: input.step,
|
|
52
|
-
status: input.status,
|
|
53
|
-
timestamp: Date.now(),
|
|
54
|
-
...this.context,
|
|
55
|
-
metadata: { ...this.context.metadata, ...input.metadata }
|
|
56
|
-
};
|
|
57
|
-
this.enqueue(event);
|
|
58
|
-
}
|
|
59
|
-
async flush() {
|
|
60
|
-
if (!this.buffer.length)
|
|
61
|
-
return;
|
|
62
|
-
const events = this.buffer;
|
|
63
|
-
this.buffer = [];
|
|
64
|
-
await this.store.bulkRecord(events);
|
|
65
|
-
}
|
|
66
|
-
async dispose() {
|
|
67
|
-
if (this.flushTimer) {
|
|
68
|
-
clearInterval(this.flushTimer);
|
|
69
|
-
}
|
|
70
|
-
await this.flush();
|
|
71
|
-
}
|
|
72
|
-
enqueue(event) {
|
|
73
|
-
this.buffer.push(event);
|
|
74
|
-
this.counter.add(1, {
|
|
75
|
-
tenantId: this.context.tenantId,
|
|
76
|
-
type: event.type
|
|
77
|
-
});
|
|
78
|
-
this.tracer.startActiveSpan(`personalization.${event.type}`, (span) => {
|
|
79
|
-
span.setAttribute("tenant.id", this.context.tenantId);
|
|
80
|
-
if (this.context.userId)
|
|
81
|
-
span.setAttribute("user.id", this.context.userId);
|
|
82
|
-
span.setAttribute("personalization.event_type", event.type);
|
|
83
|
-
span.end();
|
|
84
|
-
});
|
|
85
|
-
if (this.buffer.length >= this.bufferSize) {
|
|
86
|
-
this.flush();
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
var createBehaviorTracker = (options) => new BehaviorTracker(options);
|
|
91
|
-
export {
|
|
92
|
-
createBehaviorTracker,
|
|
93
|
-
BehaviorTracker
|
|
94
|
-
};
|
|
1
|
+
import{metrics as a,trace as s}from"@opentelemetry/api";var i=25;class r{store;context;tracer=s.getTracer("lssm.personalization","1.0.0");counter=a.getMeter("lssm.personalization","1.0.0").createCounter("lssm.personalization.events",{description:"Behavior events tracked for personalization"});buffer=[];bufferSize;flushTimer;constructor(e){if(this.store=e.store,this.context=e.context,this.bufferSize=e.bufferSize??i,e.autoFlushIntervalMs)this.flushTimer=setInterval(()=>{this.flush()},e.autoFlushIntervalMs)}trackFieldAccess(e){let t={type:"field_access",operation:e.operation,field:e.field,timestamp:Date.now(),...this.context,metadata:{...this.context.metadata,...e.metadata}};this.enqueue(t)}trackFeatureUsage(e){let t={type:"feature_usage",feature:e.feature,action:e.action,timestamp:Date.now(),...this.context,metadata:{...this.context.metadata,...e.metadata}};this.enqueue(t)}trackWorkflowStep(e){let t={type:"workflow_step",workflow:e.workflow,step:e.step,status:e.status,timestamp:Date.now(),...this.context,metadata:{...this.context.metadata,...e.metadata}};this.enqueue(t)}async flush(){if(!this.buffer.length)return;let e=this.buffer;this.buffer=[],await this.store.bulkRecord(e)}async dispose(){if(this.flushTimer)clearInterval(this.flushTimer);await this.flush()}enqueue(e){if(this.buffer.push(e),this.counter.add(1,{tenantId:this.context.tenantId,type:e.type}),this.tracer.startActiveSpan(`personalization.${e.type}`,(t)=>{if(t.setAttribute("tenant.id",this.context.tenantId),this.context.userId)t.setAttribute("user.id",this.context.userId);t.setAttribute("personalization.event_type",e.type),t.end()}),this.buffer.length>=this.bufferSize)this.flush()}}var n=(e)=>new r(e);export{n as createBehaviorTracker,r as BehaviorTracker};
|
|
@@ -1,16 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
|
|
3
|
-
import { registerDocBlocks } from "@contractspec/lib.contracts-spec/docs";
|
|
4
|
-
var personalization_behavior_tracking_DocBlocks = [
|
|
5
|
-
{
|
|
6
|
-
id: "docs.personalization.behavior-tracking",
|
|
7
|
-
title: "Behavior Tracking",
|
|
8
|
-
summary: "`@contractspec/lib.personalization` provides primitives to observe how tenants/users interact with specs and turn that telemetry into personalization insights.",
|
|
9
|
-
kind: "reference",
|
|
10
|
-
visibility: "public",
|
|
11
|
-
route: "/docs/personalization/behavior-tracking",
|
|
12
|
-
tags: ["personalization", "behavior-tracking"],
|
|
13
|
-
body: `# Behavior Tracking
|
|
2
|
+
import{registerDocBlocks as d}from"@contractspec/lib.contracts-spec/docs";var f=[{id:"docs.personalization.behavior-tracking",title:"Behavior Tracking",summary:"`@contractspec/lib.personalization` provides primitives to observe how tenants/users interact with specs and turn that telemetry into personalization insights.",kind:"reference",visibility:"public",route:"/docs/personalization/behavior-tracking",tags:["personalization","behavior-tracking"],body:`# Behavior Tracking
|
|
14
3
|
|
|
15
4
|
\`@contractspec/lib.personalization\` provides primitives to observe how tenants/users interact with specs and turn that telemetry into personalization insights.
|
|
16
5
|
|
|
@@ -89,10 +78,4 @@ When the adapter returns an overlay spec, pass it to the overlay engine to regis
|
|
|
89
78
|
|
|
90
79
|
|
|
91
80
|
|
|
92
|
-
`
|
|
93
|
-
}
|
|
94
|
-
];
|
|
95
|
-
registerDocBlocks(personalization_behavior_tracking_DocBlocks);
|
|
96
|
-
export {
|
|
97
|
-
personalization_behavior_tracking_DocBlocks
|
|
98
|
-
};
|
|
81
|
+
`}];d(f);export{f as personalization_behavior_tracking_DocBlocks};
|
package/dist/docs/index.js
CHANGED
|
@@ -1,16 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
|
|
3
|
-
import { registerDocBlocks } from "@contractspec/lib.contracts-spec/docs";
|
|
4
|
-
var personalization_behavior_tracking_DocBlocks = [
|
|
5
|
-
{
|
|
6
|
-
id: "docs.personalization.behavior-tracking",
|
|
7
|
-
title: "Behavior Tracking",
|
|
8
|
-
summary: "`@contractspec/lib.personalization` provides primitives to observe how tenants/users interact with specs and turn that telemetry into personalization insights.",
|
|
9
|
-
kind: "reference",
|
|
10
|
-
visibility: "public",
|
|
11
|
-
route: "/docs/personalization/behavior-tracking",
|
|
12
|
-
tags: ["personalization", "behavior-tracking"],
|
|
13
|
-
body: `# Behavior Tracking
|
|
2
|
+
import{registerDocBlocks as m}from"@contractspec/lib.contracts-spec/docs";var d=[{id:"docs.personalization.behavior-tracking",title:"Behavior Tracking",summary:"`@contractspec/lib.personalization` provides primitives to observe how tenants/users interact with specs and turn that telemetry into personalization insights.",kind:"reference",visibility:"public",route:"/docs/personalization/behavior-tracking",tags:["personalization","behavior-tracking"],body:`# Behavior Tracking
|
|
14
3
|
|
|
15
4
|
\`@contractspec/lib.personalization\` provides primitives to observe how tenants/users interact with specs and turn that telemetry into personalization insights.
|
|
16
5
|
|
|
@@ -89,23 +78,7 @@ When the adapter returns an overlay spec, pass it to the overlay engine to regis
|
|
|
89
78
|
|
|
90
79
|
|
|
91
80
|
|
|
92
|
-
`
|
|
93
|
-
}
|
|
94
|
-
];
|
|
95
|
-
registerDocBlocks(personalization_behavior_tracking_DocBlocks);
|
|
96
|
-
|
|
97
|
-
// src/docs/overlay-engine.docblock.ts
|
|
98
|
-
import { registerDocBlocks as registerDocBlocks2 } from "@contractspec/lib.contracts-spec/docs";
|
|
99
|
-
var personalization_overlay_engine_DocBlocks = [
|
|
100
|
-
{
|
|
101
|
-
id: "docs.personalization.overlay-engine",
|
|
102
|
-
title: "Overlay Engine",
|
|
103
|
-
summary: "`@contractspec/lib.overlay-engine` is the canonical runtime for OverlaySpecs. It validates specs, tracks scope precedence, and exposes hooks for React renderers.",
|
|
104
|
-
kind: "reference",
|
|
105
|
-
visibility: "public",
|
|
106
|
-
route: "/docs/personalization/overlay-engine",
|
|
107
|
-
tags: ["personalization", "overlay-engine"],
|
|
108
|
-
body: `# Overlay Engine
|
|
81
|
+
`}];m(d);import{registerDocBlocks as j}from"@contractspec/lib.contracts-spec/docs";var q=[{id:"docs.personalization.overlay-engine",title:"Overlay Engine",summary:"`@contractspec/lib.overlay-engine` is the canonical runtime for OverlaySpecs. It validates specs, tracks scope precedence, and exposes hooks for React renderers.",kind:"reference",visibility:"public",route:"/docs/personalization/overlay-engine",tags:["personalization","overlay-engine"],body:`# Overlay Engine
|
|
109
82
|
|
|
110
83
|
\`@contractspec/lib.overlay-engine\` is the canonical runtime for OverlaySpecs. It validates specs, tracks scope precedence, and exposes hooks for React renderers.
|
|
111
84
|
|
|
@@ -185,23 +158,7 @@ const result = engine.apply({
|
|
|
185
158
|
|
|
186
159
|
|
|
187
160
|
|
|
188
|
-
`
|
|
189
|
-
}
|
|
190
|
-
];
|
|
191
|
-
registerDocBlocks2(personalization_overlay_engine_DocBlocks);
|
|
192
|
-
|
|
193
|
-
// src/docs/workflow-composition.docblock.ts
|
|
194
|
-
import { registerDocBlocks as registerDocBlocks3 } from "@contractspec/lib.contracts-spec/docs";
|
|
195
|
-
var personalization_workflow_composition_DocBlocks = [
|
|
196
|
-
{
|
|
197
|
-
id: "docs.personalization.workflow-composition",
|
|
198
|
-
title: "Workflow Composition",
|
|
199
|
-
summary: "`@contractspec/lib.workflow-composer` composes base WorkflowSpecs with tenant/role/device-specific extensions, strict validation, deterministic merge ordering, metadata/annotation overlays, and orphan-graph protection for hidden-step rewrites.",
|
|
200
|
-
kind: "reference",
|
|
201
|
-
visibility: "public",
|
|
202
|
-
route: "/docs/personalization/workflow-composition",
|
|
203
|
-
tags: ["personalization", "workflow-composition"],
|
|
204
|
-
body: `# Workflow Composition
|
|
161
|
+
`}];j(q);import{registerDocBlocks as u}from"@contractspec/lib.contracts-spec/docs";var x=[{id:"docs.personalization.workflow-composition",title:"Workflow Composition",summary:"`@contractspec/lib.workflow-composer` composes base WorkflowSpecs with tenant/role/device-specific extensions, strict validation, deterministic merge ordering, metadata/annotation overlays, and orphan-graph protection for hidden-step rewrites.",kind:"reference",visibility:"public",route:"/docs/personalization/workflow-composition",tags:["personalization","workflow-composition"],body:`# Workflow Composition
|
|
205
162
|
|
|
206
163
|
\`@contractspec/lib.workflow-composer\` composes base WorkflowSpecs with tenant/role/device-specific extensions.
|
|
207
164
|
|
|
@@ -253,7 +210,4 @@ workflowRunner.execute(runtimeSpec, ctx);
|
|
|
253
210
|
- \`metadata\` and \`annotations\` overlays are merged into the composed runtime workflow for downstream observability and rollout tracing.
|
|
254
211
|
|
|
255
212
|
This keeps tenant overlays additive, auditable, and replay-safe.
|
|
256
|
-
`
|
|
257
|
-
}
|
|
258
|
-
];
|
|
259
|
-
registerDocBlocks3(personalization_workflow_composition_DocBlocks);
|
|
213
|
+
`}];u(x);
|
|
@@ -1,16 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
|
|
3
|
-
import { registerDocBlocks } from "@contractspec/lib.contracts-spec/docs";
|
|
4
|
-
var personalization_overlay_engine_DocBlocks = [
|
|
5
|
-
{
|
|
6
|
-
id: "docs.personalization.overlay-engine",
|
|
7
|
-
title: "Overlay Engine",
|
|
8
|
-
summary: "`@contractspec/lib.overlay-engine` is the canonical runtime for OverlaySpecs. It validates specs, tracks scope precedence, and exposes hooks for React renderers.",
|
|
9
|
-
kind: "reference",
|
|
10
|
-
visibility: "public",
|
|
11
|
-
route: "/docs/personalization/overlay-engine",
|
|
12
|
-
tags: ["personalization", "overlay-engine"],
|
|
13
|
-
body: `# Overlay Engine
|
|
2
|
+
import{registerDocBlocks as b}from"@contractspec/lib.contracts-spec/docs";var d=[{id:"docs.personalization.overlay-engine",title:"Overlay Engine",summary:"`@contractspec/lib.overlay-engine` is the canonical runtime for OverlaySpecs. It validates specs, tracks scope precedence, and exposes hooks for React renderers.",kind:"reference",visibility:"public",route:"/docs/personalization/overlay-engine",tags:["personalization","overlay-engine"],body:`# Overlay Engine
|
|
14
3
|
|
|
15
4
|
\`@contractspec/lib.overlay-engine\` is the canonical runtime for OverlaySpecs. It validates specs, tracks scope precedence, and exposes hooks for React renderers.
|
|
16
5
|
|
|
@@ -90,10 +79,4 @@ const result = engine.apply({
|
|
|
90
79
|
|
|
91
80
|
|
|
92
81
|
|
|
93
|
-
`
|
|
94
|
-
}
|
|
95
|
-
];
|
|
96
|
-
registerDocBlocks(personalization_overlay_engine_DocBlocks);
|
|
97
|
-
export {
|
|
98
|
-
personalization_overlay_engine_DocBlocks
|
|
99
|
-
};
|
|
82
|
+
`}];b(d);export{d as personalization_overlay_engine_DocBlocks};
|