@lssm/lib.contracts 0.0.0-canary-20251217063201 → 0.0.0-canary-20251217073102
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/dist/app-config/app-config.feature.js +53 -1
- package/dist/app-config/contracts.d.ts +50 -50
- package/dist/app-config/contracts.js +396 -1
- package/dist/app-config/docs/app-config.docblock.js +22 -220
- package/dist/app-config/events.d.ts +27 -27
- package/dist/app-config/events.js +168 -1
- package/dist/app-config/index.js +8 -1
- package/dist/app-config/lifecycle-contracts.d.ts +80 -80
- package/dist/app-config/lifecycle-contracts.js +441 -1
- package/dist/app-config/runtime.js +617 -1
- package/dist/app-config/spec.js +36 -1
- package/dist/app-config/validation.js +538 -1
- package/dist/capabilities/docs/capabilities.docblock.js +22 -1
- package/dist/capabilities/openbanking.js +92 -1
- package/dist/capabilities.js +50 -1
- package/dist/client/index.js +9 -1
- package/dist/client/react/drivers/rn-reusables.js +21 -1
- package/dist/client/react/drivers/shadcn.js +11 -1
- package/dist/client/react/feature-render.js +43 -1
- package/dist/client/react/form-render.js +298 -1
- package/dist/client/react/index.js +8 -1
- package/dist/contract-registry/index.js +3 -1
- package/dist/contract-registry/schemas.js +61 -1
- package/dist/contracts-adapter-hydration.js +41 -1
- package/dist/contracts-adapter-input.js +77 -1
- package/dist/data-views/docs/data-views.docblock.js +22 -1
- package/dist/data-views/query-generator.js +48 -1
- package/dist/data-views/runtime.js +39 -1
- package/dist/data-views.js +35 -1
- package/dist/docs/PUBLISHING.docblock.js +17 -76
- package/dist/docs/accessibility_wcag_compliance_specs.docblock.js +17 -350
- package/dist/docs/index.js +33 -1
- package/dist/docs/meta.docs.js +15 -2
- package/dist/docs/presentations.js +77 -1
- package/dist/docs/registry.js +51 -1
- package/dist/docs/tech/PHASE_1_QUICKSTART.docblock.js +17 -383
- package/dist/docs/tech/PHASE_2_AI_NATIVE_OPERATIONS.docblock.js +17 -68
- package/dist/docs/tech/PHASE_3_AUTO_EVOLUTION.docblock.js +17 -140
- package/dist/docs/tech/PHASE_4_PERSONALIZATION_ENGINE.docblock.js +17 -86
- package/dist/docs/tech/PHASE_5_ZERO_TOUCH_OPERATIONS.docblock.js +17 -1
- package/dist/docs/tech/auth/better-auth-nextjs.docblock.js +25 -2
- package/dist/docs/tech/contracts/README.docblock.js +21 -1
- package/dist/docs/tech/contracts/create-subscription.docblock.js +21 -1
- package/dist/docs/tech/contracts/graphql-typed-outputs.docblock.js +21 -180
- package/dist/docs/tech/contracts/migrations.docblock.js +21 -1
- package/dist/docs/tech/contracts/openapi-export.docblock.js +22 -2
- package/dist/docs/tech/contracts/ops-to-presentation-linking.docblock.js +19 -60
- package/dist/docs/tech/contracts/overlays.docblock.js +21 -68
- package/dist/docs/tech/contracts/tests.docblock.js +21 -132
- package/dist/docs/tech/contracts/themes.docblock.js +21 -1
- package/dist/docs/tech/contracts/vertical-pocket-family-office.docblock.js +21 -106
- package/dist/docs/tech/lifecycle-stage-system.docblock.js +17 -213
- package/dist/docs/tech/llm/llm-integration.docblock.js +74 -5
- package/dist/docs/tech/mcp-endpoints.docblock.js +38 -1
- package/dist/docs/tech/presentation-runtime.docblock.js +17 -1
- package/dist/docs/tech/schema/README.docblock.js +21 -262
- package/dist/docs/tech/studio/learning-events.docblock.js +49 -1
- package/dist/docs/tech/studio/learning-journeys.docblock.js +25 -2
- package/dist/docs/tech/studio/platform-admin-panel.docblock.js +24 -2
- package/dist/docs/tech/studio/project-access-teams.docblock.js +26 -16
- package/dist/docs/tech/studio/project-routing.docblock.js +68 -1
- package/dist/docs/tech/studio/sandbox-unlogged.docblock.js +23 -2
- package/dist/docs/tech/studio/team-invitations.docblock.js +41 -36
- package/dist/docs/tech/studio/workspace-ops.docblock.js +48 -1
- package/dist/docs/tech/studio/workspaces.docblock.js +24 -2
- package/dist/docs/tech/telemetry-ingest.docblock.js +37 -3
- package/dist/docs/tech/templates/runtime.docblock.js +21 -1
- package/dist/docs/tech/vscode-extension.docblock.js +37 -3
- package/dist/docs/tech/workflows/overview.docblock.js +21 -1
- package/dist/docs/tech-contracts.docs.js +19 -2
- package/dist/events.js +12 -1
- package/dist/experiments/docs/experiments.docblock.js +22 -128
- package/dist/experiments/evaluator.js +101 -1
- package/dist/experiments/spec.js +33 -1
- package/dist/features.js +68 -1
- package/dist/forms/docs/forms.docblock.js +22 -1
- package/dist/forms.js +119 -1
- package/dist/index.js +107 -1
- package/dist/install.js +40 -1
- package/dist/integrations/contracts.d.ts +102 -102
- package/dist/integrations/contracts.js +388 -1
- package/dist/integrations/docs/integrations.docblock.js +95 -1
- package/dist/integrations/health.js +69 -1
- package/dist/integrations/index.js +23 -1
- package/dist/integrations/openbanking/contracts/accounts.d.ts +66 -66
- package/dist/integrations/openbanking/contracts/accounts.js +237 -1
- package/dist/integrations/openbanking/contracts/balances.d.ts +34 -34
- package/dist/integrations/openbanking/contracts/balances.js +167 -1
- package/dist/integrations/openbanking/contracts/index.js +12 -1
- package/dist/integrations/openbanking/contracts/transactions.d.ts +48 -48
- package/dist/integrations/openbanking/contracts/transactions.js +218 -1
- package/dist/integrations/openbanking/guards.js +32 -1
- package/dist/integrations/openbanking/models.d.ts +55 -55
- package/dist/integrations/openbanking/models.js +242 -1
- package/dist/integrations/openbanking/openbanking.feature.js +68 -1
- package/dist/integrations/openbanking/telemetry.js +39 -1
- package/dist/integrations/providers/elevenlabs.js +56 -1
- package/dist/integrations/providers/gcs-storage.js +79 -1
- package/dist/integrations/providers/gmail.js +91 -1
- package/dist/integrations/providers/google-calendar.js +70 -1
- package/dist/integrations/providers/impls/elevenlabs-voice.js +95 -1
- package/dist/integrations/providers/impls/gcs-storage.js +88 -1
- package/dist/integrations/providers/impls/gmail-inbound.js +200 -1
- package/dist/integrations/providers/impls/gmail-outbound.js +104 -5
- package/dist/integrations/providers/impls/google-calendar.js +154 -1
- package/dist/integrations/providers/impls/index.js +16 -1
- package/dist/integrations/providers/impls/mistral-embedding.js +41 -1
- package/dist/integrations/providers/impls/mistral-llm.js +247 -1
- package/dist/integrations/providers/impls/postmark-email.js +55 -1
- package/dist/integrations/providers/impls/powens-client.js +171 -1
- package/dist/integrations/providers/impls/powens-openbanking.js +218 -1
- package/dist/integrations/providers/impls/provider-factory.js +142 -1
- package/dist/integrations/providers/impls/qdrant-vector.js +69 -1
- package/dist/integrations/providers/impls/stripe-payments.js +202 -1
- package/dist/integrations/providers/impls/twilio-sms.js +58 -1
- package/dist/integrations/providers/index.js +13 -1
- package/dist/integrations/providers/mistral.js +72 -1
- package/dist/integrations/providers/postmark.js +72 -1
- package/dist/integrations/providers/powens.js +120 -1
- package/dist/integrations/providers/qdrant.js +77 -1
- package/dist/integrations/providers/registry.js +34 -1
- package/dist/integrations/providers/stripe.js +87 -1
- package/dist/integrations/providers/twilio-sms.js +65 -1
- package/dist/integrations/runtime.js +186 -1
- package/dist/integrations/secrets/aws-secret-manager.js +231 -1
- package/dist/integrations/secrets/env-secret-provider.js +81 -1
- package/dist/integrations/secrets/gcp-secret-manager.js +229 -1
- package/dist/integrations/secrets/index.js +8 -1
- package/dist/integrations/secrets/manager.js +103 -1
- package/dist/integrations/secrets/provider.js +58 -1
- package/dist/integrations/secrets/scaleway-secret-manager.js +247 -1
- package/dist/integrations/spec.js +39 -1
- package/dist/jobs/define-job.js +16 -1
- package/dist/jobs/gcp-cloud-tasks.js +53 -1
- package/dist/jobs/gcp-pubsub.js +39 -1
- package/dist/jobs/handlers/gmail-sync-handler.js +9 -1
- package/dist/jobs/handlers/index.js +12 -1
- package/dist/jobs/handlers/ping-handler.js +15 -1
- package/dist/jobs/handlers/storage-document-handler.js +14 -1
- package/dist/jobs/index.js +4 -1
- package/dist/jobs/memory-queue.js +71 -1
- package/dist/jobs/queue.js +33 -1
- package/dist/jobs/scaleway-sqs-queue.js +153 -1
- package/dist/jsonschema.d.ts +3 -3
- package/dist/jsonschema.js +32 -1
- package/dist/knowledge/contracts.d.ts +66 -66
- package/dist/knowledge/contracts.js +317 -1
- package/dist/knowledge/docs/knowledge.docblock.js +22 -138
- package/dist/knowledge/index.js +10 -1
- package/dist/knowledge/ingestion/document-processor.js +54 -1
- package/dist/knowledge/ingestion/embedding-service.js +25 -1
- package/dist/knowledge/ingestion/gmail-adapter.js +50 -5
- package/dist/knowledge/ingestion/index.js +7 -1
- package/dist/knowledge/ingestion/storage-adapter.js +26 -1
- package/dist/knowledge/ingestion/vector-indexer.js +32 -1
- package/dist/knowledge/query/index.js +3 -1
- package/dist/knowledge/query/service.js +64 -2
- package/dist/knowledge/runtime.js +49 -1
- package/dist/knowledge/spaces/email-threads.js +38 -1
- package/dist/knowledge/spaces/financial-docs.js +38 -1
- package/dist/knowledge/spaces/financial-overview.js +42 -1
- package/dist/knowledge/spaces/index.js +8 -1
- package/dist/knowledge/spaces/product-canon.js +38 -1
- package/dist/knowledge/spaces/support-faq.js +41 -1
- package/dist/knowledge/spaces/uploaded-docs.js +38 -1
- package/dist/knowledge/spec.js +39 -1
- package/dist/llm/exporters.js +541 -8
- package/dist/llm/index.js +4 -1
- package/dist/llm/prompts.js +246 -56
- package/dist/markdown.js +116 -3
- package/dist/migrations.js +33 -1
- package/dist/onboarding-base.d.ts +29 -29
- package/dist/onboarding-base.js +196 -1
- package/dist/openapi.js +75 -1
- package/dist/openbanking/docs/openbanking.docblock.js +22 -109
- package/dist/ownership.js +40 -1
- package/dist/policy/docs/policy.docblock.js +22 -1
- package/dist/policy/engine.js +223 -1
- package/dist/policy/opa-adapter.js +71 -1
- package/dist/policy/spec.js +33 -1
- package/dist/presentations/docs/presentations-conventions.docblock.js +21 -7
- package/dist/presentations.backcompat.js +47 -1
- package/dist/presentations.d.ts +3 -3
- package/dist/presentations.js +66 -1
- package/dist/presentations.v2.js +278 -6
- package/dist/prompt.js +10 -1
- package/dist/promptRegistry.js +34 -1
- package/dist/regenerator/docs/regenerator.docblock.js +22 -184
- package/dist/regenerator/executor.js +86 -1
- package/dist/regenerator/index.js +6 -1
- package/dist/regenerator/service.js +92 -1
- package/dist/regenerator/sinks.js +32 -1
- package/dist/regenerator/utils.js +51 -1
- package/dist/registry.js +208 -1
- package/dist/resources.js +47 -1
- package/dist/schema/dist/EnumType.js +2 -1
- package/dist/schema/dist/FieldType.js +49 -1
- package/dist/schema/dist/ScalarTypeEnum.js +236 -1
- package/dist/schema/dist/SchemaModel.js +39 -1
- package/dist/schema/dist/entity/defineEntity.js +1 -1
- package/dist/schema/dist/entity/index.js +2 -1
- package/dist/schema/dist/entity/types.js +1 -1
- package/dist/schema/dist/index.js +6 -1
- package/dist/schema-to-markdown.js +214 -10
- package/dist/server/graphql-pothos.js +128 -1
- package/dist/server/index.js +10 -1
- package/dist/server/mcp/createMcpServer.js +28 -1
- package/dist/server/mcp/registerPresentations.js +151 -1
- package/dist/server/mcp/registerPrompts.js +36 -2
- package/dist/server/mcp/registerResources.js +35 -1
- package/dist/server/mcp/registerTools.js +22 -1
- package/dist/server/provider-mcp.js +3 -1
- package/dist/server/rest-elysia.js +20 -1
- package/dist/server/rest-express.js +39 -1
- package/dist/server/rest-generic.js +125 -1
- package/dist/server/rest-next-app.js +38 -1
- package/dist/server/rest-next-mcp.js +45 -1
- package/dist/server/rest-next-pages.js +25 -1
- package/dist/spec.js +35 -1
- package/dist/telemetry/anomaly.js +48 -1
- package/dist/telemetry/docs/telemetry.docblock.js +22 -139
- package/dist/telemetry/index.js +5 -1
- package/dist/telemetry/spec.js +69 -1
- package/dist/telemetry/tracker.js +76 -1
- package/dist/tests/index.js +4 -1
- package/dist/tests/runner.js +150 -1
- package/dist/tests/spec.js +33 -1
- package/dist/themes.js +39 -1
- package/dist/workflow/adapters/db-adapter.js +83 -1
- package/dist/workflow/adapters/file-adapter.js +11 -1
- package/dist/workflow/adapters/index.js +5 -1
- package/dist/workflow/adapters/memory-store.js +58 -1
- package/dist/workflow/expression.js +98 -1
- package/dist/workflow/index.js +9 -1
- package/dist/workflow/runner.js +337 -1
- package/dist/workflow/sla-monitor.js +47 -1
- package/dist/workflow/spec.js +32 -1
- package/dist/workflow/validation.js +175 -1
- package/package.json +11 -4
|
@@ -1 +1,48 @@
|
|
|
1
|
-
|
|
1
|
+
//#region src/telemetry/anomaly.ts
|
|
2
|
+
var TelemetryAnomalyMonitor = class {
|
|
3
|
+
onAnomaly;
|
|
4
|
+
now;
|
|
5
|
+
samples = /* @__PURE__ */ new Map();
|
|
6
|
+
constructor(options = {}) {
|
|
7
|
+
this.onAnomaly = options.onAnomaly;
|
|
8
|
+
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
9
|
+
}
|
|
10
|
+
observe(dispatch) {
|
|
11
|
+
const anomalyConfig = dispatch.definition.anomalyDetection;
|
|
12
|
+
if (!anomalyConfig?.enabled) return;
|
|
13
|
+
if (!anomalyConfig.thresholds?.length) return;
|
|
14
|
+
const eventKey = `${dispatch.name}.v${dispatch.version}`;
|
|
15
|
+
const newCount = (this.samples.get(eventKey) ?? 0) + 1;
|
|
16
|
+
this.samples.set(eventKey, newCount);
|
|
17
|
+
if (typeof anomalyConfig.minimumSample === "number" && newCount < anomalyConfig.minimumSample) return;
|
|
18
|
+
for (const threshold of anomalyConfig.thresholds) {
|
|
19
|
+
const value = this.extractMetric(dispatch, threshold.metric);
|
|
20
|
+
if (typeof value !== "number") continue;
|
|
21
|
+
if (typeof threshold.min === "number" && value < threshold.min) this.emit(dispatch, anomalyConfig, threshold.metric, value, "min");
|
|
22
|
+
if (typeof threshold.max === "number" && value > threshold.max) this.emit(dispatch, anomalyConfig, threshold.metric, value, "max");
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
extractMetric(dispatch, metric) {
|
|
26
|
+
const value = dispatch.properties[metric];
|
|
27
|
+
if (typeof value === "number") return value;
|
|
28
|
+
if (typeof value === "object" && value !== null && "value" in value) {
|
|
29
|
+
const maybeNumber = value.value;
|
|
30
|
+
return typeof maybeNumber === "number" ? maybeNumber : void 0;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
emit(dispatch, threshold, metric, value, type) {
|
|
34
|
+
this.onAnomaly?.({
|
|
35
|
+
dispatch,
|
|
36
|
+
threshold,
|
|
37
|
+
metric,
|
|
38
|
+
value,
|
|
39
|
+
type
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
reset() {
|
|
43
|
+
this.samples.clear();
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
//#endregion
|
|
48
|
+
export { TelemetryAnomalyMonitor };
|
|
@@ -1,139 +1,22 @@
|
|
|
1
|
-
import{registerDocBlocks
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
### An example
|
|
25
|
-
|
|
26
|
-
\`\`\`ts
|
|
27
|
-
export const SigilTelemetry: TelemetrySpec = {
|
|
28
|
-
meta: {
|
|
29
|
-
name: 'sigil.telemetry',
|
|
30
|
-
version: 1,
|
|
31
|
-
title: 'Sigil telemetry',
|
|
32
|
-
description: 'Core Sigil product telemetry',
|
|
33
|
-
domain: 'sigil',
|
|
34
|
-
owners: ['@team.analytics'],
|
|
35
|
-
tags: ['telemetry'],
|
|
36
|
-
stability: StabilityEnum.Experimental,
|
|
37
|
-
},
|
|
38
|
-
config: {
|
|
39
|
-
defaultRetentionDays: 30,
|
|
40
|
-
defaultSamplingRate: 1,
|
|
41
|
-
providers: [
|
|
42
|
-
{ type: 'posthog', config: { projectApiKey: process.env.POSTHOG_KEY } },
|
|
43
|
-
],
|
|
44
|
-
},
|
|
45
|
-
events: [
|
|
46
|
-
{
|
|
47
|
-
name: 'sigil.telemetry.workflow_step',
|
|
48
|
-
version: 1,
|
|
49
|
-
semantics: {
|
|
50
|
-
what: 'Workflow step executed',
|
|
51
|
-
who: 'Actor executing the workflow',
|
|
52
|
-
},
|
|
53
|
-
privacy: 'internal',
|
|
54
|
-
properties: {
|
|
55
|
-
workflow: { type: 'string', required: true },
|
|
56
|
-
step: { type: 'string', required: true },
|
|
57
|
-
durationMs: { type: 'number' },
|
|
58
|
-
userId: { type: 'string', pii: true, redact: true },
|
|
59
|
-
},
|
|
60
|
-
anomalyDetection: {
|
|
61
|
-
enabled: true,
|
|
62
|
-
minimumSample: 10,
|
|
63
|
-
thresholds: [
|
|
64
|
-
{ metric: 'durationMs', max: 1500 },
|
|
65
|
-
],
|
|
66
|
-
actions: ['alert', 'trigger_regen'],
|
|
67
|
-
},
|
|
68
|
-
},
|
|
69
|
-
],
|
|
70
|
-
};
|
|
71
|
-
\`\`\`
|
|
72
|
-
|
|
73
|
-
### Tracking events at runtime
|
|
74
|
-
|
|
75
|
-
\`TelemetryTracker\` performs sampling, PII redaction, provider dispatch, and anomaly detection.
|
|
76
|
-
|
|
77
|
-
\`\`\`ts
|
|
78
|
-
const tracker = new TelemetryTracker({
|
|
79
|
-
registry: telemetryRegistry,
|
|
80
|
-
providers: [
|
|
81
|
-
{
|
|
82
|
-
id: 'posthog',
|
|
83
|
-
async send(dispatch) {
|
|
84
|
-
posthog.capture({
|
|
85
|
-
event: dispatch.name,
|
|
86
|
-
properties: dispatch.properties,
|
|
87
|
-
distinctId: dispatch.context.userId ?? dispatch.context.sessionId,
|
|
88
|
-
});
|
|
89
|
-
},
|
|
90
|
-
},
|
|
91
|
-
],
|
|
92
|
-
anomalyMonitor: new TelemetryAnomalyMonitor({
|
|
93
|
-
onAnomaly(event) {
|
|
94
|
-
console.warn('Telemetry anomaly detected', event);
|
|
95
|
-
},
|
|
96
|
-
}),
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
await tracker.track('sigil.telemetry.workflow_step', 1, {
|
|
100
|
-
workflow: 'onboarding',
|
|
101
|
-
step: 'verify_email',
|
|
102
|
-
durationMs: 2100,
|
|
103
|
-
userId: 'user-123',
|
|
104
|
-
});
|
|
105
|
-
\`\`\`
|
|
106
|
-
|
|
107
|
-
- Sampling obeys the event-specific rate (fallback to spec defaults)
|
|
108
|
-
- Properties flagged with \`pii\` or \`redact\` are masked before dispatch
|
|
109
|
-
- Anomaly monitor evaluates thresholds and triggers actions (e.g., log, alert, regeneration)
|
|
110
|
-
|
|
111
|
-
### Spec integration
|
|
112
|
-
|
|
113
|
-
- \`ContractSpec.telemetry\` allows operations to emit success/failure events automatically
|
|
114
|
-
- \`SpecRegistry.execute()\` uses the tracker when \`ctx.telemetry\` is provided
|
|
115
|
-
- \`WorkflowRunner\` (Phase 4 follow-up) will emit telemetry during step transitions
|
|
116
|
-
- \`TelemetrySpec\` events should reuse \`EventSpec\` names/versions to keep analytics/contract parity
|
|
117
|
-
|
|
118
|
-
### CLI workflow
|
|
119
|
-
|
|
120
|
-
\`\`\`
|
|
121
|
-
contracts-cli create telemetry
|
|
122
|
-
\`\`\`
|
|
123
|
-
|
|
124
|
-
- Interactive wizard prompts for meta, providers, events, properties, retention, anomaly rules
|
|
125
|
-
- Output: \`*.telemetry.ts\` file using \`TelemetrySpec\`
|
|
126
|
-
|
|
127
|
-
### Best practices
|
|
128
|
-
|
|
129
|
-
- Prefer \`internal\` privacy for non-PII; mark PII properties explicitly with \`pii\` + \`redact\`
|
|
130
|
-
- Keep sampling ≥0.05 except for high-volume events
|
|
131
|
-
- Configure anomaly detection on key metrics (duration, error count, conversion)
|
|
132
|
-
- Check telemetry into source control alongside contracts; regenerate via CLI when specs change
|
|
133
|
-
|
|
134
|
-
### Next steps
|
|
135
|
-
|
|
136
|
-
- Phase 5: Regenerator monitors telemetry anomalies to propose spec improvements
|
|
137
|
-
- Phase 6: Studio surfaces telemetry controls per tenant via \`TenantAppConfig\`
|
|
138
|
-
|
|
139
|
-
`}];e(t);export{t as tech_contracts_telemetry_DocBlocks};
|
|
1
|
+
import { registerDocBlocks } from "../../docs/registry.js";
|
|
2
|
+
import "../../registry.js";
|
|
3
|
+
|
|
4
|
+
//#region src/telemetry/docs/telemetry.docblock.ts
|
|
5
|
+
const tech_contracts_telemetry_DocBlocks = [{
|
|
6
|
+
id: "docs.tech.contracts.telemetry",
|
|
7
|
+
title: "TelemetrySpec",
|
|
8
|
+
summary: "Telemetry specs describe product analytics in a durable, type-safe way. They reference existing `EventSpec`s (same name/version) but layer on privacy classification, retention, sampling, and anomaly detection so instrumentation stays compliant and observable.",
|
|
9
|
+
kind: "reference",
|
|
10
|
+
visibility: "public",
|
|
11
|
+
route: "/docs/tech/contracts/telemetry",
|
|
12
|
+
tags: [
|
|
13
|
+
"tech",
|
|
14
|
+
"contracts",
|
|
15
|
+
"telemetry"
|
|
16
|
+
],
|
|
17
|
+
body: "## TelemetrySpec\n\nTelemetry specs describe product analytics in a durable, type-safe way. They reference existing `EventSpec`s (same name/version) but layer on privacy classification, retention, sampling, and anomaly detection so instrumentation stays compliant and observable.\n\n- **File location**: `packages/libs/contracts/src/telemetry/spec.ts`\n- **Runtime tracker**: `packages/libs/contracts/src/telemetry/tracker.ts`\n- **Anomaly monitor**: `packages/libs/contracts/src/telemetry/anomaly.ts`\n\n### Core concepts\n\n```ts\nexport interface TelemetrySpec {\n meta: TelemetryMeta;\n events: TelemetryEventDef[];\n config?: TelemetryConfig;\n}\n```\n\n- `meta`: ownership + identifiers (`name`, `version`, `domain`)\n- `events`: per-event semantics, property definitions, privacy level, retention, sampling, anomaly rules\n- `config`: defaults and provider configuration\n- `TelemetryRegistry`: registers specs, resolves latest version, finds event definitions by name/version\n\n### An example\n\n```ts\nexport const SigilTelemetry: TelemetrySpec = {\n meta: {\n name: 'sigil.telemetry',\n version: 1,\n title: 'Sigil telemetry',\n description: 'Core Sigil product telemetry',\n domain: 'sigil',\n owners: ['@team.analytics'],\n tags: ['telemetry'],\n stability: StabilityEnum.Experimental,\n },\n config: {\n defaultRetentionDays: 30,\n defaultSamplingRate: 1,\n providers: [\n { type: 'posthog', config: { projectApiKey: process.env.POSTHOG_KEY } },\n ],\n },\n events: [\n {\n name: 'sigil.telemetry.workflow_step',\n version: 1,\n semantics: {\n what: 'Workflow step executed',\n who: 'Actor executing the workflow',\n },\n privacy: 'internal',\n properties: {\n workflow: { type: 'string', required: true },\n step: { type: 'string', required: true },\n durationMs: { type: 'number' },\n userId: { type: 'string', pii: true, redact: true },\n },\n anomalyDetection: {\n enabled: true,\n minimumSample: 10,\n thresholds: [\n { metric: 'durationMs', max: 1500 },\n ],\n actions: ['alert', 'trigger_regen'],\n },\n },\n ],\n};\n```\n\n### Tracking events at runtime\n\n`TelemetryTracker` performs sampling, PII redaction, provider dispatch, and anomaly detection.\n\n```ts\nconst tracker = new TelemetryTracker({\n registry: telemetryRegistry,\n providers: [\n {\n id: 'posthog',\n async send(dispatch) {\n posthog.capture({\n event: dispatch.name,\n properties: dispatch.properties,\n distinctId: dispatch.context.userId ?? dispatch.context.sessionId,\n });\n },\n },\n ],\n anomalyMonitor: new TelemetryAnomalyMonitor({\n onAnomaly(event) {\n console.warn('Telemetry anomaly detected', event);\n },\n }),\n});\n\nawait tracker.track('sigil.telemetry.workflow_step', 1, {\n workflow: 'onboarding',\n step: 'verify_email',\n durationMs: 2100,\n userId: 'user-123',\n});\n```\n\n- Sampling obeys the event-specific rate (fallback to spec defaults)\n- Properties flagged with `pii` or `redact` are masked before dispatch\n- Anomaly monitor evaluates thresholds and triggers actions (e.g., log, alert, regeneration)\n\n### Spec integration\n\n- `ContractSpec.telemetry` allows operations to emit success/failure events automatically\n- `SpecRegistry.execute()` uses the tracker when `ctx.telemetry` is provided\n- `WorkflowRunner` (Phase 4 follow-up) will emit telemetry during step transitions\n- `TelemetrySpec` events should reuse `EventSpec` names/versions to keep analytics/contract parity\n\n### CLI workflow\n\n```\ncontracts-cli create telemetry\n```\n\n- Interactive wizard prompts for meta, providers, events, properties, retention, anomaly rules\n- Output: `*.telemetry.ts` file using `TelemetrySpec`\n\n### Best practices\n\n- Prefer `internal` privacy for non-PII; mark PII properties explicitly with `pii` + `redact`\n- Keep sampling ≥0.05 except for high-volume events\n- Configure anomaly detection on key metrics (duration, error count, conversion)\n- Check telemetry into source control alongside contracts; regenerate via CLI when specs change\n\n### Next steps\n\n- Phase 5: Regenerator monitors telemetry anomalies to propose spec improvements\n- Phase 6: Studio surfaces telemetry controls per tenant via `TenantAppConfig`\n\n"
|
|
18
|
+
}];
|
|
19
|
+
registerDocBlocks(tech_contracts_telemetry_DocBlocks);
|
|
20
|
+
|
|
21
|
+
//#endregion
|
|
22
|
+
export { tech_contracts_telemetry_DocBlocks };
|
package/dist/telemetry/index.js
CHANGED
|
@@ -1 +1,5 @@
|
|
|
1
|
-
import{TelemetryRegistry
|
|
1
|
+
import { TelemetryRegistry, makeTelemetryKey } from "./spec.js";
|
|
2
|
+
import { TelemetryTracker } from "./tracker.js";
|
|
3
|
+
import { TelemetryAnomalyMonitor } from "./anomaly.js";
|
|
4
|
+
|
|
5
|
+
export { TelemetryAnomalyMonitor, TelemetryRegistry, TelemetryTracker, makeTelemetryKey };
|
package/dist/telemetry/spec.js
CHANGED
|
@@ -1 +1,69 @@
|
|
|
1
|
-
|
|
1
|
+
//#region src/telemetry/spec.ts
|
|
2
|
+
const telemetryKey = (meta) => `${meta.name}.v${meta.version}`;
|
|
3
|
+
var TelemetryRegistry = class {
|
|
4
|
+
items = /* @__PURE__ */ new Map();
|
|
5
|
+
eventsByKey = /* @__PURE__ */ new Map();
|
|
6
|
+
specByEventKey = /* @__PURE__ */ new Map();
|
|
7
|
+
register(spec) {
|
|
8
|
+
const key = telemetryKey(spec.meta);
|
|
9
|
+
if (this.items.has(key)) throw new Error(`Duplicate TelemetrySpec registration for ${key}`);
|
|
10
|
+
this.items.set(key, spec);
|
|
11
|
+
for (const event of spec.events) {
|
|
12
|
+
this.eventsByKey.set(`${event.name}.v${event.version}`, event);
|
|
13
|
+
this.specByEventKey.set(`${event.name}.v${event.version}`, spec);
|
|
14
|
+
}
|
|
15
|
+
return this;
|
|
16
|
+
}
|
|
17
|
+
list() {
|
|
18
|
+
return [...this.items.values()];
|
|
19
|
+
}
|
|
20
|
+
get(name, version) {
|
|
21
|
+
if (version != null) return this.items.get(`${name}.v${version}`);
|
|
22
|
+
let latest;
|
|
23
|
+
let maxVersion = -Infinity;
|
|
24
|
+
for (const item of this.items.values()) {
|
|
25
|
+
if (item.meta.name !== name) continue;
|
|
26
|
+
if (item.meta.version > maxVersion) {
|
|
27
|
+
maxVersion = item.meta.version;
|
|
28
|
+
latest = item;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return latest;
|
|
32
|
+
}
|
|
33
|
+
findEventDef(name, version) {
|
|
34
|
+
if (version != null) return this.eventsByKey.get(`${name}.v${version}`);
|
|
35
|
+
let latest;
|
|
36
|
+
let maxVersion = -Infinity;
|
|
37
|
+
for (const [key, event] of this.eventsByKey.entries()) {
|
|
38
|
+
const [eventName, versionPart] = key.split(".v");
|
|
39
|
+
if (eventName !== name) continue;
|
|
40
|
+
const ver = Number(versionPart);
|
|
41
|
+
if (Number.isFinite(ver) && ver > maxVersion) {
|
|
42
|
+
maxVersion = ver;
|
|
43
|
+
latest = event;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return latest;
|
|
47
|
+
}
|
|
48
|
+
getSpecForEvent(name, version) {
|
|
49
|
+
if (version != null) return this.specByEventKey.get(`${name}.v${version}`);
|
|
50
|
+
let latest;
|
|
51
|
+
let maxVersion = -Infinity;
|
|
52
|
+
for (const [key, spec] of this.specByEventKey.entries()) {
|
|
53
|
+
const [eventName, versionPart] = key.split(".v");
|
|
54
|
+
if (eventName !== name) continue;
|
|
55
|
+
const ver = Number(versionPart);
|
|
56
|
+
if (Number.isFinite(ver) && ver > maxVersion) {
|
|
57
|
+
maxVersion = ver;
|
|
58
|
+
latest = spec;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return latest;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
function makeTelemetryKey(meta) {
|
|
65
|
+
return telemetryKey(meta);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
//#endregion
|
|
69
|
+
export { TelemetryRegistry, makeTelemetryKey };
|
|
@@ -1 +1,76 @@
|
|
|
1
|
-
import{randomUUID
|
|
1
|
+
import { randomUUID } from "crypto";
|
|
2
|
+
|
|
3
|
+
//#region src/telemetry/tracker.ts
|
|
4
|
+
const maskValue = (value) => {
|
|
5
|
+
if (value == null) return value;
|
|
6
|
+
if (typeof value === "string") return "REDACTED";
|
|
7
|
+
if (typeof value === "number") return 0;
|
|
8
|
+
if (typeof value === "boolean") return false;
|
|
9
|
+
if (Array.isArray(value)) return value.map(() => "REDACTED");
|
|
10
|
+
if (typeof value === "object") return Object.fromEntries(Object.keys(value).map((key) => [key, "REDACTED"]));
|
|
11
|
+
return "REDACTED";
|
|
12
|
+
};
|
|
13
|
+
var TelemetryTracker = class {
|
|
14
|
+
providers = /* @__PURE__ */ new Map();
|
|
15
|
+
registry;
|
|
16
|
+
anomalyMonitor;
|
|
17
|
+
random;
|
|
18
|
+
clock;
|
|
19
|
+
constructor(options) {
|
|
20
|
+
this.registry = options.registry;
|
|
21
|
+
this.anomalyMonitor = options.anomalyMonitor;
|
|
22
|
+
this.random = options.random ?? Math.random;
|
|
23
|
+
this.clock = options.clock ?? (() => /* @__PURE__ */ new Date());
|
|
24
|
+
for (const provider of options.providers ?? []) this.providers.set(provider.id, provider);
|
|
25
|
+
}
|
|
26
|
+
registerProvider(provider) {
|
|
27
|
+
this.providers.set(provider.id, provider);
|
|
28
|
+
}
|
|
29
|
+
unregisterProvider(providerId) {
|
|
30
|
+
this.providers.delete(providerId);
|
|
31
|
+
}
|
|
32
|
+
async track(name, version, properties, context = {}) {
|
|
33
|
+
const definition = this.registry.findEventDef(name, version);
|
|
34
|
+
if (!definition) return false;
|
|
35
|
+
const spec = this.registry.getSpecForEvent(definition.name, definition.version);
|
|
36
|
+
if (!spec) return false;
|
|
37
|
+
if (!this.shouldSample(definition.sampling ?? spec.config?.defaultSamplingRate)) return false;
|
|
38
|
+
const redactedProperties = this.redactProperties(definition, properties);
|
|
39
|
+
const dispatch = {
|
|
40
|
+
id: randomUUID(),
|
|
41
|
+
name: definition.name,
|
|
42
|
+
version: definition.version,
|
|
43
|
+
occurredAt: this.clock().toISOString(),
|
|
44
|
+
properties: redactedProperties,
|
|
45
|
+
privacy: definition.privacy,
|
|
46
|
+
context,
|
|
47
|
+
tags: definition.tags,
|
|
48
|
+
spec,
|
|
49
|
+
definition
|
|
50
|
+
};
|
|
51
|
+
await Promise.all([...this.providers.values()].map((provider) => provider.send(dispatch)));
|
|
52
|
+
this.anomalyMonitor?.observe(dispatch);
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
shouldSample(sampling) {
|
|
56
|
+
if (typeof sampling === "number") return this.random() < sampling;
|
|
57
|
+
if (!sampling) return true;
|
|
58
|
+
return this.random() < sampling.rate;
|
|
59
|
+
}
|
|
60
|
+
redactProperties(definition, properties) {
|
|
61
|
+
const result = {};
|
|
62
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
63
|
+
const def = definition.properties[key];
|
|
64
|
+
if (!def) {
|
|
65
|
+
result[key] = value;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (def.redact || def.pii || definition.privacy === "sensitive") result[key] = maskValue(value);
|
|
69
|
+
else result[key] = value;
|
|
70
|
+
}
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
//#endregion
|
|
76
|
+
export { TelemetryTracker };
|
package/dist/tests/index.js
CHANGED
|
@@ -1 +1,4 @@
|
|
|
1
|
-
import{TestRegistry
|
|
1
|
+
import { TestRegistry, makeTestKey } from "./spec.js";
|
|
2
|
+
import { TestRunner } from "./runner.js";
|
|
3
|
+
|
|
4
|
+
export { TestRegistry, TestRunner, makeTestKey };
|
package/dist/tests/runner.js
CHANGED
|
@@ -1 +1,150 @@
|
|
|
1
|
-
import"../index.js";
|
|
1
|
+
import "../index.js";
|
|
2
|
+
import { deepStrictEqual } from "node:assert";
|
|
3
|
+
|
|
4
|
+
//#region src/tests/runner.ts
|
|
5
|
+
var TestRunner = class {
|
|
6
|
+
constructor(config) {
|
|
7
|
+
this.config = config;
|
|
8
|
+
}
|
|
9
|
+
async run(spec) {
|
|
10
|
+
const scenarios = [];
|
|
11
|
+
let passed = 0;
|
|
12
|
+
let failed = 0;
|
|
13
|
+
for (const scenario of spec.scenarios) {
|
|
14
|
+
await this.config.beforeEach?.(scenario);
|
|
15
|
+
const result = await this.runScenario(spec, scenario);
|
|
16
|
+
scenarios.push(result);
|
|
17
|
+
if (result.status === "passed") passed += 1;
|
|
18
|
+
else failed += 1;
|
|
19
|
+
await this.config.afterEach?.(scenario, result);
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
spec,
|
|
23
|
+
scenarios,
|
|
24
|
+
passed,
|
|
25
|
+
failed
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
async runScenario(spec, scenario) {
|
|
29
|
+
const assertionResults = [];
|
|
30
|
+
try {
|
|
31
|
+
const context = await this.createContext();
|
|
32
|
+
const fixtures = [...spec.fixtures ?? [], ...scenario.given ?? []];
|
|
33
|
+
const events = [];
|
|
34
|
+
for (const fixture of fixtures) await this.executeOperation(fixture, context, events);
|
|
35
|
+
const actionResult = await this.executeOperation(scenario.when, context, events);
|
|
36
|
+
const assertions = scenario.then ?? [];
|
|
37
|
+
for (const assertion of assertions) {
|
|
38
|
+
const assertionResult = this.evaluateAssertion(assertion, actionResult, events);
|
|
39
|
+
assertionResults.push(assertionResult);
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
scenario,
|
|
43
|
+
status: assertionResults.some((assertion) => assertion.status === "failed") ? "failed" : "passed",
|
|
44
|
+
assertionResults
|
|
45
|
+
};
|
|
46
|
+
} catch (error) {
|
|
47
|
+
return {
|
|
48
|
+
scenario,
|
|
49
|
+
status: "failed",
|
|
50
|
+
error,
|
|
51
|
+
assertionResults
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async createContext() {
|
|
56
|
+
return { ...await this.config.createContext?.() ?? {} };
|
|
57
|
+
}
|
|
58
|
+
async executeOperation(action, baseCtx, recordedEvents) {
|
|
59
|
+
const ctx = {
|
|
60
|
+
...baseCtx,
|
|
61
|
+
eventPublisher: async (event) => {
|
|
62
|
+
recordedEvents.push({
|
|
63
|
+
name: event.name,
|
|
64
|
+
version: event.version,
|
|
65
|
+
payload: event.payload
|
|
66
|
+
});
|
|
67
|
+
await baseCtx.eventPublisher?.(event);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
try {
|
|
71
|
+
return {
|
|
72
|
+
output: await this.config.registry.execute(action.operation.name, action.operation.version, action.input ?? null, ctx),
|
|
73
|
+
events: recordedEvents
|
|
74
|
+
};
|
|
75
|
+
} catch (error) {
|
|
76
|
+
return {
|
|
77
|
+
error,
|
|
78
|
+
events: recordedEvents
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
evaluateAssertion(assertion, result, events) {
|
|
83
|
+
switch (assertion.type) {
|
|
84
|
+
case "expectOutput": return this.evaluateOutputAssertion(assertion, result);
|
|
85
|
+
case "expectError": return this.evaluateErrorAssertion(assertion, result);
|
|
86
|
+
case "expectEvents": return this.evaluateEventsAssertion(assertion, events);
|
|
87
|
+
default: return {
|
|
88
|
+
assertion,
|
|
89
|
+
status: "failed",
|
|
90
|
+
message: `Unknown assertion type ${assertion.type}`
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
evaluateOutputAssertion(assertion, result) {
|
|
95
|
+
if (result.error) return {
|
|
96
|
+
assertion,
|
|
97
|
+
status: "failed",
|
|
98
|
+
message: `Expected output but operation threw error: ${result.error.message}`
|
|
99
|
+
};
|
|
100
|
+
try {
|
|
101
|
+
deepStrictEqual(result.output, assertion.match);
|
|
102
|
+
return {
|
|
103
|
+
assertion,
|
|
104
|
+
status: "passed"
|
|
105
|
+
};
|
|
106
|
+
} catch (error) {
|
|
107
|
+
return {
|
|
108
|
+
assertion,
|
|
109
|
+
status: "failed",
|
|
110
|
+
message: error instanceof Error ? error.message : "Output assertion failed"
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
evaluateErrorAssertion(assertion, result) {
|
|
115
|
+
if (!result.error) return {
|
|
116
|
+
assertion,
|
|
117
|
+
status: "failed",
|
|
118
|
+
message: "Expected an error but operation completed successfully"
|
|
119
|
+
};
|
|
120
|
+
if (assertion.messageIncludes && !result.error.message.includes(assertion.messageIncludes)) return {
|
|
121
|
+
assertion,
|
|
122
|
+
status: "failed",
|
|
123
|
+
message: `Error message "${result.error.message}" did not include expected substring "${assertion.messageIncludes}"`
|
|
124
|
+
};
|
|
125
|
+
return {
|
|
126
|
+
assertion,
|
|
127
|
+
status: "passed"
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
evaluateEventsAssertion(assertion, events) {
|
|
131
|
+
const failures = [];
|
|
132
|
+
for (const expected of assertion.events) {
|
|
133
|
+
const count = events.filter((event) => event.name === expected.name && event.version === expected.version).length;
|
|
134
|
+
if (typeof expected.min === "number" && count < expected.min || typeof expected.max === "number" && count > expected.max) failures.push(`Event ${expected.name}.v${expected.version} occurred ${count} times (expected ${expected.min ?? 0} - ${expected.max ?? "∞"})`);
|
|
135
|
+
else if (typeof expected.min === "undefined" && typeof expected.max === "undefined" && count === 0) failures.push(`Event ${expected.name}.v${expected.version} did not occur`);
|
|
136
|
+
}
|
|
137
|
+
if (failures.length > 0) return {
|
|
138
|
+
assertion,
|
|
139
|
+
status: "failed",
|
|
140
|
+
message: failures.join("; ")
|
|
141
|
+
};
|
|
142
|
+
return {
|
|
143
|
+
assertion,
|
|
144
|
+
status: "passed"
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
//#endregion
|
|
150
|
+
export { TestRunner };
|
package/dist/tests/spec.js
CHANGED
|
@@ -1 +1,33 @@
|
|
|
1
|
-
|
|
1
|
+
//#region src/tests/spec.ts
|
|
2
|
+
const testKey = (meta) => `${meta.name}.v${meta.version}`;
|
|
3
|
+
var TestRegistry = class {
|
|
4
|
+
items = /* @__PURE__ */ new Map();
|
|
5
|
+
register(spec) {
|
|
6
|
+
const key = testKey(spec.meta);
|
|
7
|
+
if (this.items.has(key)) throw new Error(`Duplicate TestSpec registration for ${key}`);
|
|
8
|
+
this.items.set(key, spec);
|
|
9
|
+
return this;
|
|
10
|
+
}
|
|
11
|
+
list() {
|
|
12
|
+
return [...this.items.values()];
|
|
13
|
+
}
|
|
14
|
+
get(name, version) {
|
|
15
|
+
if (version != null) return this.items.get(`${name}.v${version}`);
|
|
16
|
+
let latest;
|
|
17
|
+
let maxVersion = -Infinity;
|
|
18
|
+
for (const spec of this.items.values()) {
|
|
19
|
+
if (spec.meta.name !== name) continue;
|
|
20
|
+
if (spec.meta.version > maxVersion) {
|
|
21
|
+
maxVersion = spec.meta.version;
|
|
22
|
+
latest = spec;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return latest;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
function makeTestKey(meta) {
|
|
29
|
+
return testKey(meta);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
//#endregion
|
|
33
|
+
export { TestRegistry, makeTestKey };
|
package/dist/themes.js
CHANGED
|
@@ -1 +1,39 @@
|
|
|
1
|
-
|
|
1
|
+
//#region src/themes.ts
|
|
2
|
+
const themeKey = (ref) => `${ref.name}.v${ref.version}`;
|
|
3
|
+
var ThemeRegistry = class {
|
|
4
|
+
items = /* @__PURE__ */ new Map();
|
|
5
|
+
register(spec) {
|
|
6
|
+
const key = themeKey(spec.meta);
|
|
7
|
+
if (this.items.has(key)) throw new Error(`Duplicate theme ${key}`);
|
|
8
|
+
this.items.set(key, spec);
|
|
9
|
+
return this;
|
|
10
|
+
}
|
|
11
|
+
list() {
|
|
12
|
+
return [...this.items.values()];
|
|
13
|
+
}
|
|
14
|
+
get(name, version) {
|
|
15
|
+
if (version != null) return this.items.get(themeKey({
|
|
16
|
+
name,
|
|
17
|
+
version
|
|
18
|
+
}));
|
|
19
|
+
let candidate;
|
|
20
|
+
let max = -Infinity;
|
|
21
|
+
for (const spec of this.items.values()) {
|
|
22
|
+
if (spec.meta.name !== name) continue;
|
|
23
|
+
if (spec.meta.version > max) {
|
|
24
|
+
max = spec.meta.version;
|
|
25
|
+
candidate = spec;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return candidate;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
function makeThemeRef(spec) {
|
|
32
|
+
return {
|
|
33
|
+
name: spec.meta.name,
|
|
34
|
+
version: spec.meta.version
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
//#endregion
|
|
39
|
+
export { ThemeRegistry, makeThemeRef };
|