autotel-subscribers 32.1.0 → 34.0.0
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/architecture-snapshot.cjs +100 -5
- package/dist/architecture-snapshot.cjs.map +1 -1
- package/dist/architecture-snapshot.d.cts +18 -2
- package/dist/architecture-snapshot.d.ts +18 -2
- package/dist/architecture-snapshot.js +100 -5
- package/dist/architecture-snapshot.js.map +1 -1
- package/dist/{event-subscriber-base-h285lBsH.d.cts → event-subscriber-base-C5NlyV_O.d.cts} +3 -1
- package/dist/{event-subscriber-base-h285lBsH.d.ts → event-subscriber-base-C5NlyV_O.d.ts} +3 -1
- package/dist/factories.cjs +2 -1
- package/dist/factories.cjs.map +1 -1
- package/dist/factories.d.cts +1 -1
- package/dist/factories.d.ts +1 -1
- package/dist/factories.js +2 -1
- package/dist/factories.js.map +1 -1
- package/dist/file.cjs +262 -0
- package/dist/file.cjs.map +1 -0
- package/dist/file.d.cts +54 -0
- package/dist/file.d.ts +54 -0
- package/dist/file.js +256 -0
- package/dist/file.js.map +1 -0
- package/dist/index.cjs +172 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +173 -33
- package/dist/index.js.map +1 -1
- package/dist/posthog.cjs +2 -1
- package/dist/posthog.cjs.map +1 -1
- package/dist/posthog.d.cts +1 -1
- package/dist/posthog.d.ts +1 -1
- package/dist/posthog.js +2 -1
- package/dist/posthog.js.map +1 -1
- package/dist/slack.cjs +2 -1
- package/dist/slack.cjs.map +1 -1
- package/dist/slack.d.cts +1 -1
- package/dist/slack.d.ts +1 -1
- package/dist/slack.js +2 -1
- package/dist/slack.js.map +1 -1
- package/package.json +8 -3
- package/src/architecture-snapshot.test.ts +41 -1
- package/src/architecture-snapshot.ts +150 -1
- package/src/event-subscriber-base.ts +4 -1
- package/src/file.test.ts +87 -0
- package/src/file.ts +97 -0
- package/src/index.ts +1 -0
|
@@ -79,7 +79,8 @@ var EventSubscriber = class {
|
|
|
79
79
|
name,
|
|
80
80
|
attributes,
|
|
81
81
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
82
|
-
autotel: options?.autotel
|
|
82
|
+
autotel: options?.autotel,
|
|
83
|
+
schema: options?.schema
|
|
83
84
|
};
|
|
84
85
|
await this.send(payload);
|
|
85
86
|
}
|
|
@@ -230,7 +231,9 @@ var ArchitectureSnapshotSubscriber = class extends EventSubscriber {
|
|
|
230
231
|
const traceId = payload.autotel?.trace_id;
|
|
231
232
|
const attrs = payload.attributes ?? {};
|
|
232
233
|
const autotelMeta = readAutotelMeta(attrs);
|
|
233
|
-
const
|
|
234
|
+
const cleanAttrs = stripAutotelMeta(attrs);
|
|
235
|
+
const fieldPaths = extractFieldPaths(cleanAttrs);
|
|
236
|
+
const fieldStats = extractFieldStats(cleanAttrs);
|
|
234
237
|
if (!existing) {
|
|
235
238
|
this.observations.set(payload.name, {
|
|
236
239
|
name: payload.name,
|
|
@@ -240,18 +243,32 @@ var ArchitectureSnapshotSubscriber = class extends EventSubscriber {
|
|
|
240
243
|
fieldPaths,
|
|
241
244
|
sampleTraceIds: traceId ? [traceId] : [],
|
|
242
245
|
channel: autotelMeta.channel,
|
|
243
|
-
producer: autotelMeta.producer
|
|
246
|
+
producer: autotelMeta.producer,
|
|
247
|
+
consumers: autotelMeta.consumers,
|
|
248
|
+
fieldStats,
|
|
249
|
+
schema: payload.schema ? {
|
|
250
|
+
source: payload.schema.source,
|
|
251
|
+
jsonSchema: payload.schema.jsonSchema,
|
|
252
|
+
hash: payload.schema.hash
|
|
253
|
+
} : void 0
|
|
244
254
|
});
|
|
245
255
|
return;
|
|
246
256
|
}
|
|
247
257
|
existing.observedCount += 1;
|
|
248
258
|
existing.lastSeen = now;
|
|
249
259
|
existing.fieldPaths = mergeUnique(existing.fieldPaths, fieldPaths);
|
|
260
|
+
existing.fieldStats = mergeFieldStats(existing.fieldStats ?? {}, fieldStats);
|
|
250
261
|
if (traceId && !existing.sampleTraceIds.includes(traceId) && existing.sampleTraceIds.length < this.maxSampleTraceIds) {
|
|
251
262
|
existing.sampleTraceIds.push(traceId);
|
|
252
263
|
}
|
|
253
264
|
existing.channel ??= autotelMeta.channel;
|
|
254
265
|
existing.producer ??= autotelMeta.producer;
|
|
266
|
+
existing.consumers = mergeUnique(existing.consumers ?? [], autotelMeta.consumers ?? []);
|
|
267
|
+
existing.schema ??= payload.schema ? {
|
|
268
|
+
source: payload.schema.source,
|
|
269
|
+
jsonSchema: payload.schema.jsonSchema,
|
|
270
|
+
hash: payload.schema.hash
|
|
271
|
+
} : void 0;
|
|
255
272
|
}
|
|
256
273
|
/**
|
|
257
274
|
* Build the snapshot in memory. Use this in tests or when you want to
|
|
@@ -267,7 +284,8 @@ var ArchitectureSnapshotSubscriber = class extends EventSubscriber {
|
|
|
267
284
|
events[name] = {
|
|
268
285
|
...obs,
|
|
269
286
|
fieldPaths: obs.fieldPaths.toSorted(),
|
|
270
|
-
sampleTraceIds: obs.sampleTraceIds.toSorted()
|
|
287
|
+
sampleTraceIds: obs.sampleTraceIds.toSorted(),
|
|
288
|
+
fieldStats: sortFieldStats(obs.fieldStats)
|
|
271
289
|
};
|
|
272
290
|
}
|
|
273
291
|
return {
|
|
@@ -297,7 +315,8 @@ function readAutotelMeta(attrs) {
|
|
|
297
315
|
const m = meta;
|
|
298
316
|
return {
|
|
299
317
|
channel: typeof m.channel === "string" ? m.channel : void 0,
|
|
300
|
-
producer: typeof m.producer === "string" ? m.producer : void 0
|
|
318
|
+
producer: typeof m.producer === "string" ? m.producer : void 0,
|
|
319
|
+
consumers: Array.isArray(m.consumers) ? m.consumers.filter((v) => typeof v === "string").toSorted() : void 0
|
|
301
320
|
};
|
|
302
321
|
}
|
|
303
322
|
var AUTOTEL_INJECTED_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -348,6 +367,82 @@ function mergeUnique(a, b) {
|
|
|
348
367
|
for (const v of b) set.add(v);
|
|
349
368
|
return [...set];
|
|
350
369
|
}
|
|
370
|
+
function extractFieldStats(value, prefix = "") {
|
|
371
|
+
const out = /* @__PURE__ */ new Map();
|
|
372
|
+
walkFieldStats(value, prefix, out);
|
|
373
|
+
const obj = {};
|
|
374
|
+
for (const [path2, stats] of out) {
|
|
375
|
+
obj[path2] = {
|
|
376
|
+
types: [...stats.types].toSorted(),
|
|
377
|
+
sampleValues: [...stats.sampleValues].toSorted(comparePrimitiveValues)
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
return obj;
|
|
381
|
+
}
|
|
382
|
+
function walkFieldStats(value, prefix, out) {
|
|
383
|
+
if (value === null || value === void 0) return;
|
|
384
|
+
if (Array.isArray(value)) {
|
|
385
|
+
const arrayPrefix = prefix + "[]";
|
|
386
|
+
for (const item of value) walkFieldStats(item, arrayPrefix, out);
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
if (typeof value === "object") {
|
|
390
|
+
for (const [key, v] of Object.entries(value)) {
|
|
391
|
+
const path2 = prefix === "" ? key : `${prefix}.${key}`;
|
|
392
|
+
addPathValue(path2, v, out);
|
|
393
|
+
walkFieldStats(v, path2, out);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
function addPathValue(path2, value, out) {
|
|
398
|
+
const existing = out.get(path2) ?? { types: /* @__PURE__ */ new Set(), sampleValues: /* @__PURE__ */ new Set() };
|
|
399
|
+
const t = classifyValueType(value);
|
|
400
|
+
existing.types.add(t);
|
|
401
|
+
if ((value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") && existing.sampleValues.size < 20) {
|
|
402
|
+
existing.sampleValues.add(value);
|
|
403
|
+
}
|
|
404
|
+
out.set(path2, existing);
|
|
405
|
+
}
|
|
406
|
+
function classifyValueType(value) {
|
|
407
|
+
if (value === null) return "null";
|
|
408
|
+
if (Array.isArray(value)) return "array";
|
|
409
|
+
return typeof value;
|
|
410
|
+
}
|
|
411
|
+
function mergeFieldStats(a, b) {
|
|
412
|
+
const merged = { ...a };
|
|
413
|
+
for (const [path2, bs] of Object.entries(b)) {
|
|
414
|
+
const prev = merged[path2];
|
|
415
|
+
if (!prev) {
|
|
416
|
+
merged[path2] = bs;
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
const types = /* @__PURE__ */ new Set([...prev.types, ...bs.types]);
|
|
420
|
+
const sampleValues = /* @__PURE__ */ new Set([...prev.sampleValues, ...bs.sampleValues]);
|
|
421
|
+
merged[path2] = {
|
|
422
|
+
types: [...types].toSorted(),
|
|
423
|
+
sampleValues: [...sampleValues].toSorted(comparePrimitiveValues).slice(0, 20)
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
return merged;
|
|
427
|
+
}
|
|
428
|
+
function sortFieldStats(stats) {
|
|
429
|
+
if (!stats) return void 0;
|
|
430
|
+
const out = {};
|
|
431
|
+
for (const path2 of Object.keys(stats).toSorted()) {
|
|
432
|
+
out[path2] = {
|
|
433
|
+
types: [...stats[path2].types].toSorted(),
|
|
434
|
+
sampleValues: [...stats[path2].sampleValues].toSorted(
|
|
435
|
+
comparePrimitiveValues
|
|
436
|
+
)
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
return out;
|
|
440
|
+
}
|
|
441
|
+
function comparePrimitiveValues(a, b) {
|
|
442
|
+
const sa = JSON.stringify(a);
|
|
443
|
+
const sb = JSON.stringify(b);
|
|
444
|
+
return sa.localeCompare(sb);
|
|
445
|
+
}
|
|
351
446
|
|
|
352
447
|
exports.ARCHITECTURE_SNAPSHOT_SPEC = ARCHITECTURE_SNAPSHOT_SPEC;
|
|
353
448
|
exports.ArchitectureSnapshotSubscriber = ArchitectureSnapshotSubscriber;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/event-subscriber-base.ts","../src/architecture-snapshot.ts"],"names":["fs","path"],"mappings":"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","file":"architecture-snapshot.cjs","sourcesContent":["/**\n * EventSubscriber - Standard base class for building custom subscribers\n *\n * This is the recommended base class for creating custom events subscribers.\n * It provides production-ready features out of the box:\n *\n * **Built-in Features:**\n * - **Error Handling**: Automatic error catching with customizable handlers\n * - **Pending Request Tracking**: Ensures all requests complete during shutdown\n * - **Graceful Shutdown**: Drains pending requests before closing\n * - **Enable/Disable**: Runtime control to turn subscriber on/off\n * - **Normalized Payload**: Consistent event structure across all event types\n *\n * **When to use:**\n * - Building custom subscribers for any platform\n * - Production deployments requiring reliability\n * - Need graceful shutdown and error handling\n *\n * @example Basic usage\n * ```typescript\n * import { EventSubscriber, EventPayload } from 'autotel-subscribers';\n *\n * class SnowflakeSubscriber extends EventSubscriber {\n * name = 'SnowflakeSubscriber';\n * version = '1.0.0';\n *\n * protected async sendToDestination(payload: EventPayload): Promise<void> {\n * await snowflakeClient.execute(\n * `INSERT INTO events VALUES (?, ?, ?)`,\n * [payload.type, payload.name, JSON.stringify(payload.attributes)]\n * );\n * }\n * }\n * ```\n *\n * @example With buffering\n * ```typescript\n * class BufferedSubscriber extends EventSubscriber {\n * name = 'BufferedSubscriber';\n * private buffer: EventPayload[] = [];\n *\n * protected async sendToDestination(payload: EventPayload): Promise<void> {\n * this.buffer.push(payload);\n *\n * if (this.buffer.length >= 100) {\n * await this.flush();\n * }\n * }\n *\n * async shutdown(): Promise<void> {\n * await super.shutdown(); // Drain pending requests first\n * await this.flush(); // Then flush buffer\n * }\n *\n * private async flush(): Promise<void> {\n * if (this.buffer.length === 0) return;\n *\n * const batch = [...this.buffer];\n * this.buffer = [];\n *\n * await apiClient.sendBatch(batch);\n * }\n * }\n * ```\n */\n\nimport type {\n EventSubscriber as IEventSubscriber,\n EventAttributes,\n EventAttributesInput,\n FunnelStatus,\n OutcomeStatus,\n AutotelEventContext,\n EventTrackingOptions,\n} from 'autotel/event-subscriber';\n\n// Re-export types for convenience\nexport type { AutotelEventContext, EventTrackingOptions } from 'autotel/event-subscriber';\n\n/**\n * Payload sent to destination\n */\nexport interface EventPayload {\n /** Event type: 'event', 'funnel', 'outcome', or 'value' */\n type: 'event' | 'funnel' | 'outcome' | 'value';\n\n /** Event name or metric name */\n name: string;\n\n /** Optional attributes */\n attributes?: EventAttributes;\n\n /** For funnel events: funnel name */\n funnel?: string;\n\n /** For funnel events: step status (from FunnelStatus enum) */\n step?: FunnelStatus | string;\n\n /** For funnel events: custom step name (from trackFunnelProgression) */\n stepName?: string;\n\n /** For funnel events: numeric position in funnel */\n stepNumber?: number;\n\n /** For outcome events: operation name */\n operation?: string;\n\n /** For outcome events: outcome status */\n outcome?: OutcomeStatus;\n\n /** For value events: numeric value */\n value?: number;\n\n /** Timestamp (ISO 8601) */\n timestamp: string;\n\n /**\n * Autotel trace context (present when events.includeTraceContext is enabled)\n *\n * Subscribers should map these to platform-specific field names:\n * - PostHog: autotel.trace_id → $trace_id\n * - Mixpanel: autotel.trace_id → trace_id\n */\n autotel?: AutotelEventContext;\n}\n\n/**\n * Standard base class for building custom events subscribers\n *\n * **What it provides:**\n * - Consistent payload structure (normalized across all event types)\n * - Enable/disable flag (runtime control)\n * - Automatic error handling (with customizable error handlers)\n * - Pending requests tracking (ensures no lost events during shutdown)\n * - Graceful shutdown (drains pending requests before closing)\n *\n * **Usage:**\n * Extend this class and implement `sendToDestination()`. All other methods\n * (trackEvent, trackFunnelStep, trackOutcome, trackValue, shutdown) are handled automatically.\n *\n * For high-throughput streaming platforms (Kafka, Kinesis, Pub/Sub), use `StreamingEventSubscriber` instead.\n */\nexport abstract class EventSubscriber implements IEventSubscriber {\n /**\n * Subscriber name (required for debugging)\n */\n abstract readonly name: string;\n\n /**\n * Subscriber version (optional)\n */\n readonly version?: string;\n\n /**\n * Enable/disable the subscriber (default: true)\n */\n protected enabled: boolean = true;\n\n /**\n * Track pending requests for graceful shutdown\n */\n private pendingRequests: Set<Promise<void>> = new Set();\n\n /**\n * Send payload to destination\n *\n * Override this method to implement your destination-specific logic.\n * This is called for all event types (event, funnel, outcome, value).\n *\n * @param payload - Normalized event payload\n */\n protected abstract sendToDestination(payload: EventPayload): Promise<void>;\n\n /**\n * Optional: Handle errors\n *\n * Override this to customize error handling (logging, retries, etc.).\n * Default behavior: log to console.error\n *\n * @param error - Error that occurred\n * @param payload - Event payload that failed\n */\n protected handleError(error: Error, payload: EventPayload): void {\n console.error(\n `[${this.name}] Failed to send ${payload.type}:`,\n error,\n payload,\n );\n }\n\n /**\n * Filter out undefined and null values from attributes\n *\n * This improves DX by allowing callers to pass objects with optional properties\n * without having to manually filter them first.\n *\n * @param attributes - Input attributes (may contain undefined/null)\n * @returns Filtered attributes with only defined values, or undefined if empty\n *\n * @example\n * ```typescript\n * const filtered = this.filterAttributes({\n * userId: user.id,\n * email: user.email, // might be undefined\n * plan: null, // will be filtered out\n * });\n * // Result: { userId: 'abc', email: 'test@example.com' } or { userId: 'abc' }\n * ```\n */\n protected filterAttributes(\n attributes?: EventAttributesInput,\n ): EventAttributes | undefined {\n if (!attributes) return undefined;\n\n const filtered: EventAttributes = {};\n for (const [key, value] of Object.entries(attributes)) {\n if (value !== undefined && value !== null) {\n filtered[key] = value;\n }\n }\n\n // Return undefined if no attributes remain after filtering\n return Object.keys(filtered).length > 0 ? filtered : undefined;\n }\n\n /**\n * Track an event\n */\n async trackEvent(\n name: string,\n attributes?: EventAttributes,\n options?: EventTrackingOptions,\n ): Promise<void> {\n if (!this.enabled) return;\n\n const payload: EventPayload = {\n type: 'event',\n name,\n attributes,\n timestamp: new Date().toISOString(),\n autotel: options?.autotel,\n };\n\n await this.send(payload);\n }\n\n /**\n * Track a funnel step\n */\n async trackFunnelStep(\n funnelName: string,\n step: FunnelStatus,\n attributes?: EventAttributes,\n options?: EventTrackingOptions,\n ): Promise<void> {\n if (!this.enabled) return;\n\n const payload: EventPayload = {\n type: 'funnel',\n name: `${funnelName}.${step}`,\n funnel: funnelName,\n step,\n attributes,\n timestamp: new Date().toISOString(),\n autotel: options?.autotel,\n };\n\n await this.send(payload);\n }\n\n /**\n * Track an outcome\n */\n async trackOutcome(\n operationName: string,\n outcome: OutcomeStatus,\n attributes?: EventAttributes,\n options?: EventTrackingOptions,\n ): Promise<void> {\n if (!this.enabled) return;\n\n const payload: EventPayload = {\n type: 'outcome',\n name: `${operationName}.${outcome}`,\n operation: operationName,\n outcome,\n attributes,\n timestamp: new Date().toISOString(),\n autotel: options?.autotel,\n };\n\n await this.send(payload);\n }\n\n /**\n * Track a value/metric\n */\n async trackValue(\n name: string,\n value: number,\n attributes?: EventAttributes,\n options?: EventTrackingOptions,\n ): Promise<void> {\n if (!this.enabled) return;\n\n const payload: EventPayload = {\n type: 'value',\n name,\n value,\n attributes,\n timestamp: new Date().toISOString(),\n autotel: options?.autotel,\n };\n\n await this.send(payload);\n }\n\n /**\n * Track funnel progression with custom step names\n *\n * Unlike trackFunnelStep which uses FunnelStatus enum values,\n * this method allows any string as the step name for flexible funnel tracking.\n *\n * @param funnelName - Name of the funnel (e.g., \"checkout\", \"onboarding\")\n * @param stepName - Custom step name (e.g., \"cart_viewed\", \"payment_entered\")\n * @param stepNumber - Optional numeric position in the funnel\n * @param attributes - Optional event attributes\n * @param options - Optional tracking options including autotel context\n */\n async trackFunnelProgression(\n funnelName: string,\n stepName: string,\n stepNumber?: number,\n attributes?: EventAttributes,\n options?: EventTrackingOptions,\n ): Promise<void> {\n if (!this.enabled) return;\n\n const payload: EventPayload = {\n type: 'funnel',\n name: `${funnelName}.${stepName}`,\n funnel: funnelName,\n step: stepName,\n stepName,\n stepNumber,\n attributes,\n timestamp: new Date().toISOString(),\n autotel: options?.autotel,\n };\n\n await this.send(payload);\n }\n\n /**\n * Flush pending requests and clean up\n *\n * CRITICAL: Prevents race condition during shutdown\n * 1. Disables subscriber to stop new events\n * 2. Drains all pending requests (with retry logic)\n * 3. Ensures flush guarantee\n *\n * Override this if you need custom cleanup logic (close connections, flush buffers, etc.),\n * but ALWAYS call super.shutdown() first to drain pending requests.\n */\n async shutdown(): Promise<void> {\n // 1. Stop accepting new events (prevents race condition)\n this.enabled = false;\n\n // 2. Drain pending requests with retry logic\n // Loop until empty to handle race where new requests added during Promise.allSettled\n const maxDrainAttempts = 10;\n const drainIntervalMs = 50;\n\n for (let attempt = 0; attempt < maxDrainAttempts; attempt++) {\n if (this.pendingRequests.size === 0) {\n break;\n }\n\n // Wait for current batch\n await Promise.allSettled(this.pendingRequests);\n\n // Small delay to catch any stragglers added during allSettled\n if (this.pendingRequests.size > 0 && attempt < maxDrainAttempts - 1) {\n await new Promise((resolve) => setTimeout(resolve, drainIntervalMs));\n }\n }\n\n // 3. Warn if we still have pending requests (shouldn't happen, but be defensive)\n if (this.pendingRequests.size > 0) {\n console.warn(\n `[${this.name}] Shutdown completed with ${this.pendingRequests.size} pending requests still in-flight. ` +\n `This may indicate a bug in the subscriber or extremely slow destination.`\n );\n }\n }\n\n /**\n * Internal: Send payload and track request\n */\n private async send(payload: EventPayload): Promise<void> {\n const request = this.sendWithErrorHandling(payload);\n this.pendingRequests.add(request);\n\n void request.finally(() => {\n this.pendingRequests.delete(request);\n });\n\n return request;\n }\n\n /**\n * Internal: Send with error handling\n */\n private async sendWithErrorHandling(\n payload: EventPayload,\n ): Promise<void> {\n try {\n await this.sendToDestination(payload);\n } catch (error) {\n this.handleError(error as Error, payload);\n }\n }\n}\n\nexport {\n type EventAttributes,\n type EventAttributesInput,\n type FunnelStatus,\n type OutcomeStatus,\n} from 'autotel/event-subscriber';","/**\n * ArchitectureSnapshotSubscriber\n *\n * Captures `track()` events into an in-memory architecture snapshot, then\n * writes it to disk. The snapshot is the input to `autotel-eventcatalog`'s\n * generator and is designed to be deterministic, reviewable, and committable.\n *\n * v0 scope: capture event names, observation counts, first/last-seen, sample\n * trace IDs, and the dotted field paths present in payloads. Producer /\n * consumer / channel attribution is read from a small `_autotel.*` convention\n * inside event attributes — that convention is documented in\n * `apps/example-eventcatalog`.\n *\n * @example\n * ```typescript\n * import { init, track } from 'autotel';\n * import { ArchitectureSnapshotSubscriber } from 'autotel-subscribers/architecture';\n *\n * const snapshot = new ArchitectureSnapshotSubscriber({ service: 'orders' });\n *\n * init({\n * service: 'orders',\n * subscribers: [snapshot],\n * });\n *\n * // ... exercise the system (run integration tests, hit endpoints, etc.) ...\n *\n * await snapshot.writeToFile('./.autotel/snapshot.json');\n * ```\n */\n\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { EventSubscriber, type EventPayload } from './event-subscriber-base';\n\n/**\n * Public, versioned snapshot format. The generator and any downstream tooling\n * target this spec. Bumping the spec version is a breaking change for\n * downstream consumers, so add fields rather than rename existing ones.\n */\nexport const ARCHITECTURE_SNAPSHOT_SPEC = 'autotel-architecture/v0.1.0' as const;\n\nexport type ArchitectureSnapshot = {\n spec: typeof ARCHITECTURE_SNAPSHOT_SPEC;\n generatedAt: string;\n service: string;\n events: Record<string, EventObservation>;\n};\n\nexport type EventObservation = {\n name: string;\n observedCount: number;\n firstSeen: string;\n lastSeen: string;\n /** Dotted field paths observed in any payload (e.g. `items[].sku`). */\n fieldPaths: string[];\n /** Up to 3 trace IDs for click-through from the catalog into the backend. */\n sampleTraceIds: string[];\n /** Channel the event was published on, if the caller provided `_autotel.channel`. */\n channel?: string;\n /** Service that produced the event, if not the snapshot's own service. */\n producer?: string;\n};\n\nexport interface ArchitectureSnapshotConfig {\n /** Service identifier that appears in the snapshot header. */\n service: string;\n /** Maximum number of trace IDs to retain per event (default 3). */\n maxSampleTraceIds?: number;\n}\n\nconst DEFAULT_MAX_SAMPLES = 3;\n\nexport class ArchitectureSnapshotSubscriber extends EventSubscriber {\n readonly name = 'ArchitectureSnapshotSubscriber';\n\n private readonly service: string;\n private readonly maxSampleTraceIds: number;\n private readonly observations = new Map<string, EventObservation>();\n\n constructor(config: ArchitectureSnapshotConfig) {\n super();\n this.service = config.service;\n this.maxSampleTraceIds = config.maxSampleTraceIds ?? DEFAULT_MAX_SAMPLES;\n }\n\n protected async sendToDestination(payload: EventPayload): Promise<void> {\n // Only `track()` events feed the architecture snapshot. Funnels, outcomes,\n // and value metrics belong to product analytics, not the architecture model.\n if (payload.type !== 'event') return;\n\n const existing = this.observations.get(payload.name);\n const now = payload.timestamp;\n const traceId = payload.autotel?.trace_id;\n const attrs = payload.attributes ?? {};\n const autotelMeta = readAutotelMeta(attrs);\n const fieldPaths = extractFieldPaths(stripAutotelMeta(attrs));\n\n if (!existing) {\n this.observations.set(payload.name, {\n name: payload.name,\n observedCount: 1,\n firstSeen: now,\n lastSeen: now,\n fieldPaths,\n sampleTraceIds: traceId ? [traceId] : [],\n channel: autotelMeta.channel,\n producer: autotelMeta.producer,\n });\n return;\n }\n\n existing.observedCount += 1;\n existing.lastSeen = now;\n existing.fieldPaths = mergeUnique(existing.fieldPaths, fieldPaths);\n\n if (\n traceId &&\n !existing.sampleTraceIds.includes(traceId) &&\n existing.sampleTraceIds.length < this.maxSampleTraceIds\n ) {\n existing.sampleTraceIds.push(traceId);\n }\n\n existing.channel ??= autotelMeta.channel;\n existing.producer ??= autotelMeta.producer;\n }\n\n /**\n * Build the snapshot in memory. Use this in tests or when you want to\n * inspect the result before writing it. Field paths and trace IDs are\n * sorted so equal inputs always produce byte-identical snapshots.\n */\n toSnapshot(now: () => Date = () => new Date()): ArchitectureSnapshot {\n const events: Record<string, EventObservation> = {};\n\n const names = [...this.observations.keys()].toSorted();\n for (const name of names) {\n const obs = this.observations.get(name);\n if (!obs) continue;\n events[name] = {\n ...obs,\n fieldPaths: obs.fieldPaths.toSorted(),\n sampleTraceIds: obs.sampleTraceIds.toSorted(),\n };\n }\n\n return {\n spec: ARCHITECTURE_SNAPSHOT_SPEC,\n generatedAt: now().toISOString(),\n service: this.service,\n events,\n };\n }\n\n /**\n * Write the snapshot to disk. Creates parent directories as needed.\n * Files are written with a trailing newline so they diff cleanly in git.\n */\n async writeToFile(\n filePath: string,\n options: { now?: () => Date } = {},\n ): Promise<void> {\n await fs.mkdir(path.dirname(filePath), { recursive: true });\n const json = JSON.stringify(this.toSnapshot(options.now), null, 2);\n await fs.writeFile(filePath, json + '\\n', 'utf8');\n }\n\n /** Reset all accumulated state. Useful between test cases. */\n reset(): void {\n this.observations.clear();\n }\n}\n\ntype AutotelMeta = {\n channel?: string;\n producer?: string;\n};\n\nfunction readAutotelMeta(attrs: Record<string, unknown>): AutotelMeta {\n const meta = attrs._autotel;\n if (!meta || typeof meta !== 'object') return {};\n const m = meta as Record<string, unknown>;\n return {\n channel: typeof m.channel === 'string' ? m.channel : undefined,\n producer: typeof m.producer === 'string' ? m.producer : undefined,\n };\n}\n\n/**\n * Top-level attribute keys that autotel injects automatically (correlation\n * context, baggage, service metadata). These describe the trace, not the\n * event payload, so they don't belong in the captured field paths.\n */\nconst AUTOTEL_INJECTED_KEYS = new Set([\n '_autotel',\n 'traceId',\n 'trace_id',\n 'spanId',\n 'span_id',\n 'parentSpanId',\n 'parent_span_id',\n 'correlationId',\n 'correlation_id',\n 'service',\n 'service.name',\n]);\n\nfunction stripAutotelMeta(attrs: Record<string, unknown>): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(attrs)) {\n if (AUTOTEL_INJECTED_KEYS.has(key)) continue;\n out[key] = value;\n }\n return out;\n}\n\n/**\n * Walk a JSON-like value and produce a sorted list of dotted field paths.\n * Arrays collapse with `[]`, so `items: [{ sku: 'x' }]` yields `items[].sku`.\n */\nexport function extractFieldPaths(value: unknown, prefix = ''): string[] {\n const paths = new Set<string>();\n walk(value, prefix, paths);\n return [...paths].toSorted();\n}\n\nfunction walk(value: unknown, prefix: string, out: Set<string>): void {\n if (value === null || value === undefined) return;\n if (Array.isArray(value)) {\n const arrayPrefix = prefix + '[]';\n for (const item of value) walk(item, arrayPrefix, out);\n return;\n }\n if (typeof value === 'object') {\n for (const [key, v] of Object.entries(value)) {\n const path = prefix === '' ? key : `${prefix}.${key}`;\n out.add(path);\n walk(v, path, out);\n }\n return;\n }\n // Primitives don't add new paths beyond what their key already added.\n}\n\nfunction mergeUnique(a: string[], b: string[]): string[] {\n if (b.length === 0) return a;\n const set = new Set(a);\n for (const v of b) set.add(v);\n return [...set];\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/event-subscriber-base.ts","../src/architecture-snapshot.ts"],"names":["fs","path"],"mappings":"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","file":"architecture-snapshot.cjs","sourcesContent":["/**\n * EventSubscriber - Standard base class for building custom subscribers\n *\n * This is the recommended base class for creating custom events subscribers.\n * It provides production-ready features out of the box:\n *\n * **Built-in Features:**\n * - **Error Handling**: Automatic error catching with customizable handlers\n * - **Pending Request Tracking**: Ensures all requests complete during shutdown\n * - **Graceful Shutdown**: Drains pending requests before closing\n * - **Enable/Disable**: Runtime control to turn subscriber on/off\n * - **Normalized Payload**: Consistent event structure across all event types\n *\n * **When to use:**\n * - Building custom subscribers for any platform\n * - Production deployments requiring reliability\n * - Need graceful shutdown and error handling\n *\n * @example Basic usage\n * ```typescript\n * import { EventSubscriber, EventPayload } from 'autotel-subscribers';\n *\n * class SnowflakeSubscriber extends EventSubscriber {\n * name = 'SnowflakeSubscriber';\n * version = '1.0.0';\n *\n * protected async sendToDestination(payload: EventPayload): Promise<void> {\n * await snowflakeClient.execute(\n * `INSERT INTO events VALUES (?, ?, ?)`,\n * [payload.type, payload.name, JSON.stringify(payload.attributes)]\n * );\n * }\n * }\n * ```\n *\n * @example With buffering\n * ```typescript\n * class BufferedSubscriber extends EventSubscriber {\n * name = 'BufferedSubscriber';\n * private buffer: EventPayload[] = [];\n *\n * protected async sendToDestination(payload: EventPayload): Promise<void> {\n * this.buffer.push(payload);\n *\n * if (this.buffer.length >= 100) {\n * await this.flush();\n * }\n * }\n *\n * async shutdown(): Promise<void> {\n * await super.shutdown(); // Drain pending requests first\n * await this.flush(); // Then flush buffer\n * }\n *\n * private async flush(): Promise<void> {\n * if (this.buffer.length === 0) return;\n *\n * const batch = [...this.buffer];\n * this.buffer = [];\n *\n * await apiClient.sendBatch(batch);\n * }\n * }\n * ```\n */\n\nimport type {\n EventSubscriber as IEventSubscriber,\n EventAttributes,\n EventAttributesInput,\n FunnelStatus,\n OutcomeStatus,\n AutotelEventContext,\n EventTrackingOptions,\n} from 'autotel/event-subscriber';\n\n// Re-export types for convenience\nexport type { AutotelEventContext, EventTrackingOptions } from 'autotel/event-subscriber';\n\n/**\n * Payload sent to destination\n */\nexport interface EventPayload {\n /** Event type: 'event', 'funnel', 'outcome', or 'value' */\n type: 'event' | 'funnel' | 'outcome' | 'value';\n\n /** Event name or metric name */\n name: string;\n\n /** Optional attributes */\n attributes?: EventAttributes;\n\n /** For funnel events: funnel name */\n funnel?: string;\n\n /** For funnel events: step status (from FunnelStatus enum) */\n step?: FunnelStatus | string;\n\n /** For funnel events: custom step name (from trackFunnelProgression) */\n stepName?: string;\n\n /** For funnel events: numeric position in funnel */\n stepNumber?: number;\n\n /** For outcome events: operation name */\n operation?: string;\n\n /** For outcome events: outcome status */\n outcome?: OutcomeStatus;\n\n /** For value events: numeric value */\n value?: number;\n\n /** Timestamp (ISO 8601) */\n timestamp: string;\n\n /**\n * Autotel trace context (present when events.includeTraceContext is enabled)\n *\n * Subscribers should map these to platform-specific field names:\n * - PostHog: autotel.trace_id → $trace_id\n * - Mixpanel: autotel.trace_id → trace_id\n */\n autotel?: AutotelEventContext;\n /** Optional schema metadata for contract-aware subscribers. */\n schema?: EventTrackingOptions['schema'];\n}\n\n/**\n * Standard base class for building custom events subscribers\n *\n * **What it provides:**\n * - Consistent payload structure (normalized across all event types)\n * - Enable/disable flag (runtime control)\n * - Automatic error handling (with customizable error handlers)\n * - Pending requests tracking (ensures no lost events during shutdown)\n * - Graceful shutdown (drains pending requests before closing)\n *\n * **Usage:**\n * Extend this class and implement `sendToDestination()`. All other methods\n * (trackEvent, trackFunnelStep, trackOutcome, trackValue, shutdown) are handled automatically.\n *\n * For high-throughput streaming platforms (Kafka, Kinesis, Pub/Sub), use `StreamingEventSubscriber` instead.\n */\nexport abstract class EventSubscriber implements IEventSubscriber {\n /**\n * Subscriber name (required for debugging)\n */\n abstract readonly name: string;\n\n /**\n * Subscriber version (optional)\n */\n readonly version?: string;\n\n /**\n * Enable/disable the subscriber (default: true)\n */\n protected enabled: boolean = true;\n\n /**\n * Track pending requests for graceful shutdown\n */\n private pendingRequests: Set<Promise<void>> = new Set();\n\n /**\n * Send payload to destination\n *\n * Override this method to implement your destination-specific logic.\n * This is called for all event types (event, funnel, outcome, value).\n *\n * @param payload - Normalized event payload\n */\n protected abstract sendToDestination(payload: EventPayload): Promise<void>;\n\n /**\n * Optional: Handle errors\n *\n * Override this to customize error handling (logging, retries, etc.).\n * Default behavior: log to console.error\n *\n * @param error - Error that occurred\n * @param payload - Event payload that failed\n */\n protected handleError(error: Error, payload: EventPayload): void {\n console.error(\n `[${this.name}] Failed to send ${payload.type}:`,\n error,\n payload,\n );\n }\n\n /**\n * Filter out undefined and null values from attributes\n *\n * This improves DX by allowing callers to pass objects with optional properties\n * without having to manually filter them first.\n *\n * @param attributes - Input attributes (may contain undefined/null)\n * @returns Filtered attributes with only defined values, or undefined if empty\n *\n * @example\n * ```typescript\n * const filtered = this.filterAttributes({\n * userId: user.id,\n * email: user.email, // might be undefined\n * plan: null, // will be filtered out\n * });\n * // Result: { userId: 'abc', email: 'test@example.com' } or { userId: 'abc' }\n * ```\n */\n protected filterAttributes(\n attributes?: EventAttributesInput,\n ): EventAttributes | undefined {\n if (!attributes) return undefined;\n\n const filtered: EventAttributes = {};\n for (const [key, value] of Object.entries(attributes)) {\n if (value !== undefined && value !== null) {\n filtered[key] = value;\n }\n }\n\n // Return undefined if no attributes remain after filtering\n return Object.keys(filtered).length > 0 ? filtered : undefined;\n }\n\n /**\n * Track an event\n */\n async trackEvent(\n name: string,\n attributes?: EventAttributes,\n options?: EventTrackingOptions,\n ): Promise<void> {\n if (!this.enabled) return;\n\n const payload: EventPayload = {\n type: 'event',\n name,\n attributes,\n timestamp: new Date().toISOString(),\n autotel: options?.autotel,\n schema: options?.schema,\n };\n\n await this.send(payload);\n }\n\n /**\n * Track a funnel step\n */\n async trackFunnelStep(\n funnelName: string,\n step: FunnelStatus,\n attributes?: EventAttributes,\n options?: EventTrackingOptions,\n ): Promise<void> {\n if (!this.enabled) return;\n\n const payload: EventPayload = {\n type: 'funnel',\n name: `${funnelName}.${step}`,\n funnel: funnelName,\n step,\n attributes,\n timestamp: new Date().toISOString(),\n autotel: options?.autotel,\n };\n\n await this.send(payload);\n }\n\n /**\n * Track an outcome\n */\n async trackOutcome(\n operationName: string,\n outcome: OutcomeStatus,\n attributes?: EventAttributes,\n options?: EventTrackingOptions,\n ): Promise<void> {\n if (!this.enabled) return;\n\n const payload: EventPayload = {\n type: 'outcome',\n name: `${operationName}.${outcome}`,\n operation: operationName,\n outcome,\n attributes,\n timestamp: new Date().toISOString(),\n autotel: options?.autotel,\n };\n\n await this.send(payload);\n }\n\n /**\n * Track a value/metric\n */\n async trackValue(\n name: string,\n value: number,\n attributes?: EventAttributes,\n options?: EventTrackingOptions,\n ): Promise<void> {\n if (!this.enabled) return;\n\n const payload: EventPayload = {\n type: 'value',\n name,\n value,\n attributes,\n timestamp: new Date().toISOString(),\n autotel: options?.autotel,\n };\n\n await this.send(payload);\n }\n\n /**\n * Track funnel progression with custom step names\n *\n * Unlike trackFunnelStep which uses FunnelStatus enum values,\n * this method allows any string as the step name for flexible funnel tracking.\n *\n * @param funnelName - Name of the funnel (e.g., \"checkout\", \"onboarding\")\n * @param stepName - Custom step name (e.g., \"cart_viewed\", \"payment_entered\")\n * @param stepNumber - Optional numeric position in the funnel\n * @param attributes - Optional event attributes\n * @param options - Optional tracking options including autotel context\n */\n async trackFunnelProgression(\n funnelName: string,\n stepName: string,\n stepNumber?: number,\n attributes?: EventAttributes,\n options?: EventTrackingOptions,\n ): Promise<void> {\n if (!this.enabled) return;\n\n const payload: EventPayload = {\n type: 'funnel',\n name: `${funnelName}.${stepName}`,\n funnel: funnelName,\n step: stepName,\n stepName,\n stepNumber,\n attributes,\n timestamp: new Date().toISOString(),\n autotel: options?.autotel,\n };\n\n await this.send(payload);\n }\n\n /**\n * Flush pending requests and clean up\n *\n * CRITICAL: Prevents race condition during shutdown\n * 1. Disables subscriber to stop new events\n * 2. Drains all pending requests (with retry logic)\n * 3. Ensures flush guarantee\n *\n * Override this if you need custom cleanup logic (close connections, flush buffers, etc.),\n * but ALWAYS call super.shutdown() first to drain pending requests.\n */\n async shutdown(): Promise<void> {\n // 1. Stop accepting new events (prevents race condition)\n this.enabled = false;\n\n // 2. Drain pending requests with retry logic\n // Loop until empty to handle race where new requests added during Promise.allSettled\n const maxDrainAttempts = 10;\n const drainIntervalMs = 50;\n\n for (let attempt = 0; attempt < maxDrainAttempts; attempt++) {\n if (this.pendingRequests.size === 0) {\n break;\n }\n\n // Wait for current batch\n await Promise.allSettled(this.pendingRequests);\n\n // Small delay to catch any stragglers added during allSettled\n if (this.pendingRequests.size > 0 && attempt < maxDrainAttempts - 1) {\n await new Promise((resolve) => setTimeout(resolve, drainIntervalMs));\n }\n }\n\n // 3. Warn if we still have pending requests (shouldn't happen, but be defensive)\n if (this.pendingRequests.size > 0) {\n console.warn(\n `[${this.name}] Shutdown completed with ${this.pendingRequests.size} pending requests still in-flight. ` +\n `This may indicate a bug in the subscriber or extremely slow destination.`\n );\n }\n }\n\n /**\n * Internal: Send payload and track request\n */\n private async send(payload: EventPayload): Promise<void> {\n const request = this.sendWithErrorHandling(payload);\n this.pendingRequests.add(request);\n\n void request.finally(() => {\n this.pendingRequests.delete(request);\n });\n\n return request;\n }\n\n /**\n * Internal: Send with error handling\n */\n private async sendWithErrorHandling(\n payload: EventPayload,\n ): Promise<void> {\n try {\n await this.sendToDestination(payload);\n } catch (error) {\n this.handleError(error as Error, payload);\n }\n }\n}\n\nexport {\n type EventAttributes,\n type EventAttributesInput,\n type FunnelStatus,\n type OutcomeStatus,\n} from 'autotel/event-subscriber';\n","/**\n * ArchitectureSnapshotSubscriber\n *\n * Captures `track()` events into an in-memory architecture snapshot, then\n * writes it to disk. The snapshot is the input to `autotel-eventcatalog`'s\n * generator and is designed to be deterministic, reviewable, and committable.\n *\n * v0 scope: capture event names, observation counts, first/last-seen, sample\n * trace IDs, and the dotted field paths present in payloads. Producer /\n * consumer / channel attribution is read from a small `_autotel.*` convention\n * inside event attributes — that convention is documented in\n * `apps/example-eventcatalog`.\n *\n * @example\n * ```typescript\n * import { init, track } from 'autotel';\n * import { ArchitectureSnapshotSubscriber } from 'autotel-subscribers/architecture';\n *\n * const snapshot = new ArchitectureSnapshotSubscriber({ service: 'orders' });\n *\n * init({\n * service: 'orders',\n * subscribers: [snapshot],\n * });\n *\n * // ... exercise the system (run integration tests, hit endpoints, etc.) ...\n *\n * await snapshot.writeToFile('./.autotel/snapshot.json');\n * ```\n */\n\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { EventSubscriber, type EventPayload } from './event-subscriber-base';\n\n/**\n * Public, versioned snapshot format. The generator and any downstream tooling\n * target this spec. Bumping the spec version is a breaking change for\n * downstream consumers, so add fields rather than rename existing ones.\n */\nexport const ARCHITECTURE_SNAPSHOT_SPEC = 'autotel-architecture/v0.1.0' as const;\n\nexport type ArchitectureSnapshot = {\n spec: typeof ARCHITECTURE_SNAPSHOT_SPEC;\n generatedAt: string;\n service: string;\n events: Record<string, EventObservation>;\n};\n\nexport type EventObservation = {\n name: string;\n observedCount: number;\n firstSeen: string;\n lastSeen: string;\n /** Dotted field paths observed in any payload (e.g. `items[].sku`). */\n fieldPaths: string[];\n /** Up to 3 trace IDs for click-through from the catalog into the backend. */\n sampleTraceIds: string[];\n /** Channel the event was published on, if the caller provided `_autotel.channel`. */\n channel?: string;\n /** Service that produced the event, if not the snapshot's own service. */\n producer?: string;\n /** Services known to consume this event (optional metadata from _autotel.consumers). */\n consumers?: string[];\n /** Observed runtime types and sample primitive values per field path. */\n fieldStats?: Record<string, FieldStats>;\n /** Optional contract schema metadata carried from track() call sites. */\n schema?: {\n source: 'zod';\n jsonSchema: unknown;\n hash: string;\n };\n};\n\nexport type FieldStats = {\n /** Runtime types observed for this field path (e.g. string, number). */\n types: string[];\n /** Small set of observed primitive values (for enum/value drift checks). */\n sampleValues: Array<string | number | boolean | null>;\n};\n\nexport interface ArchitectureSnapshotConfig {\n /** Service identifier that appears in the snapshot header. */\n service: string;\n /** Maximum number of trace IDs to retain per event (default 3). */\n maxSampleTraceIds?: number;\n}\n\nconst DEFAULT_MAX_SAMPLES = 3;\n\nexport class ArchitectureSnapshotSubscriber extends EventSubscriber {\n readonly name = 'ArchitectureSnapshotSubscriber';\n\n private readonly service: string;\n private readonly maxSampleTraceIds: number;\n private readonly observations = new Map<string, EventObservation>();\n\n constructor(config: ArchitectureSnapshotConfig) {\n super();\n this.service = config.service;\n this.maxSampleTraceIds = config.maxSampleTraceIds ?? DEFAULT_MAX_SAMPLES;\n }\n\n protected async sendToDestination(payload: EventPayload): Promise<void> {\n // Only `track()` events feed the architecture snapshot. Funnels, outcomes,\n // and value metrics belong to product analytics, not the architecture model.\n if (payload.type !== 'event') return;\n\n const existing = this.observations.get(payload.name);\n const now = payload.timestamp;\n const traceId = payload.autotel?.trace_id;\n const attrs = payload.attributes ?? {};\n const autotelMeta = readAutotelMeta(attrs);\n const cleanAttrs = stripAutotelMeta(attrs);\n const fieldPaths = extractFieldPaths(cleanAttrs);\n const fieldStats = extractFieldStats(cleanAttrs);\n\n if (!existing) {\n this.observations.set(payload.name, {\n name: payload.name,\n observedCount: 1,\n firstSeen: now,\n lastSeen: now,\n fieldPaths,\n sampleTraceIds: traceId ? [traceId] : [],\n channel: autotelMeta.channel,\n producer: autotelMeta.producer,\n consumers: autotelMeta.consumers,\n fieldStats,\n schema: payload.schema\n ? {\n source: payload.schema.source,\n jsonSchema: payload.schema.jsonSchema,\n hash: payload.schema.hash,\n }\n : undefined,\n });\n return;\n }\n\n existing.observedCount += 1;\n existing.lastSeen = now;\n existing.fieldPaths = mergeUnique(existing.fieldPaths, fieldPaths);\n existing.fieldStats = mergeFieldStats(existing.fieldStats ?? {}, fieldStats);\n\n if (\n traceId &&\n !existing.sampleTraceIds.includes(traceId) &&\n existing.sampleTraceIds.length < this.maxSampleTraceIds\n ) {\n existing.sampleTraceIds.push(traceId);\n }\n\n existing.channel ??= autotelMeta.channel;\n existing.producer ??= autotelMeta.producer;\n existing.consumers = mergeUnique(existing.consumers ?? [], autotelMeta.consumers ?? []);\n existing.schema ??= payload.schema\n ? {\n source: payload.schema.source,\n jsonSchema: payload.schema.jsonSchema,\n hash: payload.schema.hash,\n }\n : undefined;\n }\n\n /**\n * Build the snapshot in memory. Use this in tests or when you want to\n * inspect the result before writing it. Field paths and trace IDs are\n * sorted so equal inputs always produce byte-identical snapshots.\n */\n toSnapshot(now: () => Date = () => new Date()): ArchitectureSnapshot {\n const events: Record<string, EventObservation> = {};\n\n const names = [...this.observations.keys()].toSorted();\n for (const name of names) {\n const obs = this.observations.get(name);\n if (!obs) continue;\n events[name] = {\n ...obs,\n fieldPaths: obs.fieldPaths.toSorted(),\n sampleTraceIds: obs.sampleTraceIds.toSorted(),\n fieldStats: sortFieldStats(obs.fieldStats),\n };\n }\n\n return {\n spec: ARCHITECTURE_SNAPSHOT_SPEC,\n generatedAt: now().toISOString(),\n service: this.service,\n events,\n };\n }\n\n /**\n * Write the snapshot to disk. Creates parent directories as needed.\n * Files are written with a trailing newline so they diff cleanly in git.\n */\n async writeToFile(\n filePath: string,\n options: { now?: () => Date } = {},\n ): Promise<void> {\n await fs.mkdir(path.dirname(filePath), { recursive: true });\n const json = JSON.stringify(this.toSnapshot(options.now), null, 2);\n await fs.writeFile(filePath, json + '\\n', 'utf8');\n }\n\n /** Reset all accumulated state. Useful between test cases. */\n reset(): void {\n this.observations.clear();\n }\n}\n\ntype AutotelMeta = {\n channel?: string;\n producer?: string;\n consumers?: string[];\n};\n\nfunction readAutotelMeta(attrs: Record<string, unknown>): AutotelMeta {\n const meta = attrs._autotel;\n if (!meta || typeof meta !== 'object') return {};\n const m = meta as Record<string, unknown>;\n return {\n channel: typeof m.channel === 'string' ? m.channel : undefined,\n producer: typeof m.producer === 'string' ? m.producer : undefined,\n consumers: Array.isArray(m.consumers)\n ? m.consumers.filter((v): v is string => typeof v === 'string').toSorted()\n : undefined,\n };\n}\n\n/**\n * Top-level attribute keys that autotel injects automatically (correlation\n * context, baggage, service metadata). These describe the trace, not the\n * event payload, so they don't belong in the captured field paths.\n */\nconst AUTOTEL_INJECTED_KEYS = new Set([\n '_autotel',\n 'traceId',\n 'trace_id',\n 'spanId',\n 'span_id',\n 'parentSpanId',\n 'parent_span_id',\n 'correlationId',\n 'correlation_id',\n 'service',\n 'service.name',\n]);\n\nfunction stripAutotelMeta(attrs: Record<string, unknown>): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(attrs)) {\n if (AUTOTEL_INJECTED_KEYS.has(key)) continue;\n out[key] = value;\n }\n return out;\n}\n\n/**\n * Walk a JSON-like value and produce a sorted list of dotted field paths.\n * Arrays collapse with `[]`, so `items: [{ sku: 'x' }]` yields `items[].sku`.\n */\nexport function extractFieldPaths(value: unknown, prefix = ''): string[] {\n const paths = new Set<string>();\n walk(value, prefix, paths);\n return [...paths].toSorted();\n}\n\nfunction walk(value: unknown, prefix: string, out: Set<string>): void {\n if (value === null || value === undefined) return;\n if (Array.isArray(value)) {\n const arrayPrefix = prefix + '[]';\n for (const item of value) walk(item, arrayPrefix, out);\n return;\n }\n if (typeof value === 'object') {\n for (const [key, v] of Object.entries(value)) {\n const path = prefix === '' ? key : `${prefix}.${key}`;\n out.add(path);\n walk(v, path, out);\n }\n return;\n }\n // Primitives don't add new paths beyond what their key already added.\n}\n\nfunction mergeUnique(a: string[], b: string[]): string[] {\n if (b.length === 0) return a;\n const set = new Set(a);\n for (const v of b) set.add(v);\n return [...set];\n}\n\nfunction extractFieldStats(value: unknown, prefix = ''): Record<string, FieldStats> {\n const out = new Map<string, { types: Set<string>; sampleValues: Set<string | number | boolean | null> }>();\n walkFieldStats(value, prefix, out);\n const obj: Record<string, FieldStats> = {};\n for (const [path, stats] of out) {\n obj[path] = {\n types: [...stats.types].toSorted(),\n sampleValues: [...stats.sampleValues].toSorted(comparePrimitiveValues),\n };\n }\n return obj;\n}\n\nfunction walkFieldStats(\n value: unknown,\n prefix: string,\n out: Map<string, { types: Set<string>; sampleValues: Set<string | number | boolean | null> }>,\n): void {\n if (value === null || value === undefined) return;\n if (Array.isArray(value)) {\n const arrayPrefix = prefix + '[]';\n for (const item of value) walkFieldStats(item, arrayPrefix, out);\n return;\n }\n if (typeof value === 'object') {\n for (const [key, v] of Object.entries(value)) {\n const path = prefix === '' ? key : `${prefix}.${key}`;\n addPathValue(path, v, out);\n walkFieldStats(v, path, out);\n }\n }\n}\n\nfunction addPathValue(\n path: string,\n value: unknown,\n out: Map<string, { types: Set<string>; sampleValues: Set<string | number | boolean | null> }>,\n): void {\n const existing = out.get(path) ?? { types: new Set<string>(), sampleValues: new Set<string | number | boolean | null>() };\n const t = classifyValueType(value);\n existing.types.add(t);\n if (\n (value === null ||\n typeof value === 'string' ||\n typeof value === 'number' ||\n typeof value === 'boolean') &&\n existing.sampleValues.size < 20\n ) {\n existing.sampleValues.add(value);\n }\n out.set(path, existing);\n}\n\nfunction classifyValueType(value: unknown): string {\n if (value === null) return 'null';\n if (Array.isArray(value)) return 'array';\n return typeof value;\n}\n\nfunction mergeFieldStats(\n a: Record<string, FieldStats>,\n b: Record<string, FieldStats>,\n): Record<string, FieldStats> {\n const merged: Record<string, FieldStats> = { ...a };\n for (const [path, bs] of Object.entries(b)) {\n const prev = merged[path];\n if (!prev) {\n merged[path] = bs;\n continue;\n }\n const types = new Set([...prev.types, ...bs.types]);\n const sampleValues = new Set([...prev.sampleValues, ...bs.sampleValues]);\n merged[path] = {\n types: [...types].toSorted(),\n sampleValues: [...sampleValues]\n .toSorted(comparePrimitiveValues)\n .slice(0, 20),\n };\n }\n return merged;\n}\n\nfunction sortFieldStats(\n stats: Record<string, FieldStats> | undefined,\n): Record<string, FieldStats> | undefined {\n if (!stats) return undefined;\n const out: Record<string, FieldStats> = {};\n for (const path of Object.keys(stats).toSorted()) {\n out[path] = {\n types: [...stats[path].types].toSorted(),\n sampleValues: [...stats[path].sampleValues].toSorted(\n comparePrimitiveValues,\n ),\n };\n }\n return out;\n}\n\nfunction comparePrimitiveValues(\n a: string | number | boolean | null,\n b: string | number | boolean | null,\n): number {\n const sa = JSON.stringify(a);\n const sb = JSON.stringify(b);\n return sa.localeCompare(sb);\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { E as EventSubscriber, a as EventPayload } from './event-subscriber-base-
|
|
1
|
+
import { E as EventSubscriber, a as EventPayload } from './event-subscriber-base-C5NlyV_O.cjs';
|
|
2
2
|
import 'autotel/event-subscriber';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -57,6 +57,22 @@ type EventObservation = {
|
|
|
57
57
|
channel?: string;
|
|
58
58
|
/** Service that produced the event, if not the snapshot's own service. */
|
|
59
59
|
producer?: string;
|
|
60
|
+
/** Services known to consume this event (optional metadata from _autotel.consumers). */
|
|
61
|
+
consumers?: string[];
|
|
62
|
+
/** Observed runtime types and sample primitive values per field path. */
|
|
63
|
+
fieldStats?: Record<string, FieldStats>;
|
|
64
|
+
/** Optional contract schema metadata carried from track() call sites. */
|
|
65
|
+
schema?: {
|
|
66
|
+
source: 'zod';
|
|
67
|
+
jsonSchema: unknown;
|
|
68
|
+
hash: string;
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
type FieldStats = {
|
|
72
|
+
/** Runtime types observed for this field path (e.g. string, number). */
|
|
73
|
+
types: string[];
|
|
74
|
+
/** Small set of observed primitive values (for enum/value drift checks). */
|
|
75
|
+
sampleValues: Array<string | number | boolean | null>;
|
|
60
76
|
};
|
|
61
77
|
interface ArchitectureSnapshotConfig {
|
|
62
78
|
/** Service identifier that appears in the snapshot header. */
|
|
@@ -93,4 +109,4 @@ declare class ArchitectureSnapshotSubscriber extends EventSubscriber {
|
|
|
93
109
|
*/
|
|
94
110
|
declare function extractFieldPaths(value: unknown, prefix?: string): string[];
|
|
95
111
|
|
|
96
|
-
export { ARCHITECTURE_SNAPSHOT_SPEC, type ArchitectureSnapshot, type ArchitectureSnapshotConfig, ArchitectureSnapshotSubscriber, type EventObservation, extractFieldPaths };
|
|
112
|
+
export { ARCHITECTURE_SNAPSHOT_SPEC, type ArchitectureSnapshot, type ArchitectureSnapshotConfig, ArchitectureSnapshotSubscriber, type EventObservation, type FieldStats, extractFieldPaths };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { E as EventSubscriber, a as EventPayload } from './event-subscriber-base-
|
|
1
|
+
import { E as EventSubscriber, a as EventPayload } from './event-subscriber-base-C5NlyV_O.js';
|
|
2
2
|
import 'autotel/event-subscriber';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -57,6 +57,22 @@ type EventObservation = {
|
|
|
57
57
|
channel?: string;
|
|
58
58
|
/** Service that produced the event, if not the snapshot's own service. */
|
|
59
59
|
producer?: string;
|
|
60
|
+
/** Services known to consume this event (optional metadata from _autotel.consumers). */
|
|
61
|
+
consumers?: string[];
|
|
62
|
+
/** Observed runtime types and sample primitive values per field path. */
|
|
63
|
+
fieldStats?: Record<string, FieldStats>;
|
|
64
|
+
/** Optional contract schema metadata carried from track() call sites. */
|
|
65
|
+
schema?: {
|
|
66
|
+
source: 'zod';
|
|
67
|
+
jsonSchema: unknown;
|
|
68
|
+
hash: string;
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
type FieldStats = {
|
|
72
|
+
/** Runtime types observed for this field path (e.g. string, number). */
|
|
73
|
+
types: string[];
|
|
74
|
+
/** Small set of observed primitive values (for enum/value drift checks). */
|
|
75
|
+
sampleValues: Array<string | number | boolean | null>;
|
|
60
76
|
};
|
|
61
77
|
interface ArchitectureSnapshotConfig {
|
|
62
78
|
/** Service identifier that appears in the snapshot header. */
|
|
@@ -93,4 +109,4 @@ declare class ArchitectureSnapshotSubscriber extends EventSubscriber {
|
|
|
93
109
|
*/
|
|
94
110
|
declare function extractFieldPaths(value: unknown, prefix?: string): string[];
|
|
95
111
|
|
|
96
|
-
export { ARCHITECTURE_SNAPSHOT_SPEC, type ArchitectureSnapshot, type ArchitectureSnapshotConfig, ArchitectureSnapshotSubscriber, type EventObservation, extractFieldPaths };
|
|
112
|
+
export { ARCHITECTURE_SNAPSHOT_SPEC, type ArchitectureSnapshot, type ArchitectureSnapshotConfig, ArchitectureSnapshotSubscriber, type EventObservation, type FieldStats, extractFieldPaths };
|
|
@@ -72,7 +72,8 @@ var EventSubscriber = class {
|
|
|
72
72
|
name,
|
|
73
73
|
attributes,
|
|
74
74
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
75
|
-
autotel: options?.autotel
|
|
75
|
+
autotel: options?.autotel,
|
|
76
|
+
schema: options?.schema
|
|
76
77
|
};
|
|
77
78
|
await this.send(payload);
|
|
78
79
|
}
|
|
@@ -223,7 +224,9 @@ var ArchitectureSnapshotSubscriber = class extends EventSubscriber {
|
|
|
223
224
|
const traceId = payload.autotel?.trace_id;
|
|
224
225
|
const attrs = payload.attributes ?? {};
|
|
225
226
|
const autotelMeta = readAutotelMeta(attrs);
|
|
226
|
-
const
|
|
227
|
+
const cleanAttrs = stripAutotelMeta(attrs);
|
|
228
|
+
const fieldPaths = extractFieldPaths(cleanAttrs);
|
|
229
|
+
const fieldStats = extractFieldStats(cleanAttrs);
|
|
227
230
|
if (!existing) {
|
|
228
231
|
this.observations.set(payload.name, {
|
|
229
232
|
name: payload.name,
|
|
@@ -233,18 +236,32 @@ var ArchitectureSnapshotSubscriber = class extends EventSubscriber {
|
|
|
233
236
|
fieldPaths,
|
|
234
237
|
sampleTraceIds: traceId ? [traceId] : [],
|
|
235
238
|
channel: autotelMeta.channel,
|
|
236
|
-
producer: autotelMeta.producer
|
|
239
|
+
producer: autotelMeta.producer,
|
|
240
|
+
consumers: autotelMeta.consumers,
|
|
241
|
+
fieldStats,
|
|
242
|
+
schema: payload.schema ? {
|
|
243
|
+
source: payload.schema.source,
|
|
244
|
+
jsonSchema: payload.schema.jsonSchema,
|
|
245
|
+
hash: payload.schema.hash
|
|
246
|
+
} : void 0
|
|
237
247
|
});
|
|
238
248
|
return;
|
|
239
249
|
}
|
|
240
250
|
existing.observedCount += 1;
|
|
241
251
|
existing.lastSeen = now;
|
|
242
252
|
existing.fieldPaths = mergeUnique(existing.fieldPaths, fieldPaths);
|
|
253
|
+
existing.fieldStats = mergeFieldStats(existing.fieldStats ?? {}, fieldStats);
|
|
243
254
|
if (traceId && !existing.sampleTraceIds.includes(traceId) && existing.sampleTraceIds.length < this.maxSampleTraceIds) {
|
|
244
255
|
existing.sampleTraceIds.push(traceId);
|
|
245
256
|
}
|
|
246
257
|
existing.channel ??= autotelMeta.channel;
|
|
247
258
|
existing.producer ??= autotelMeta.producer;
|
|
259
|
+
existing.consumers = mergeUnique(existing.consumers ?? [], autotelMeta.consumers ?? []);
|
|
260
|
+
existing.schema ??= payload.schema ? {
|
|
261
|
+
source: payload.schema.source,
|
|
262
|
+
jsonSchema: payload.schema.jsonSchema,
|
|
263
|
+
hash: payload.schema.hash
|
|
264
|
+
} : void 0;
|
|
248
265
|
}
|
|
249
266
|
/**
|
|
250
267
|
* Build the snapshot in memory. Use this in tests or when you want to
|
|
@@ -260,7 +277,8 @@ var ArchitectureSnapshotSubscriber = class extends EventSubscriber {
|
|
|
260
277
|
events[name] = {
|
|
261
278
|
...obs,
|
|
262
279
|
fieldPaths: obs.fieldPaths.toSorted(),
|
|
263
|
-
sampleTraceIds: obs.sampleTraceIds.toSorted()
|
|
280
|
+
sampleTraceIds: obs.sampleTraceIds.toSorted(),
|
|
281
|
+
fieldStats: sortFieldStats(obs.fieldStats)
|
|
264
282
|
};
|
|
265
283
|
}
|
|
266
284
|
return {
|
|
@@ -290,7 +308,8 @@ function readAutotelMeta(attrs) {
|
|
|
290
308
|
const m = meta;
|
|
291
309
|
return {
|
|
292
310
|
channel: typeof m.channel === "string" ? m.channel : void 0,
|
|
293
|
-
producer: typeof m.producer === "string" ? m.producer : void 0
|
|
311
|
+
producer: typeof m.producer === "string" ? m.producer : void 0,
|
|
312
|
+
consumers: Array.isArray(m.consumers) ? m.consumers.filter((v) => typeof v === "string").toSorted() : void 0
|
|
294
313
|
};
|
|
295
314
|
}
|
|
296
315
|
var AUTOTEL_INJECTED_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -341,6 +360,82 @@ function mergeUnique(a, b) {
|
|
|
341
360
|
for (const v of b) set.add(v);
|
|
342
361
|
return [...set];
|
|
343
362
|
}
|
|
363
|
+
function extractFieldStats(value, prefix = "") {
|
|
364
|
+
const out = /* @__PURE__ */ new Map();
|
|
365
|
+
walkFieldStats(value, prefix, out);
|
|
366
|
+
const obj = {};
|
|
367
|
+
for (const [path2, stats] of out) {
|
|
368
|
+
obj[path2] = {
|
|
369
|
+
types: [...stats.types].toSorted(),
|
|
370
|
+
sampleValues: [...stats.sampleValues].toSorted(comparePrimitiveValues)
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
return obj;
|
|
374
|
+
}
|
|
375
|
+
function walkFieldStats(value, prefix, out) {
|
|
376
|
+
if (value === null || value === void 0) return;
|
|
377
|
+
if (Array.isArray(value)) {
|
|
378
|
+
const arrayPrefix = prefix + "[]";
|
|
379
|
+
for (const item of value) walkFieldStats(item, arrayPrefix, out);
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
if (typeof value === "object") {
|
|
383
|
+
for (const [key, v] of Object.entries(value)) {
|
|
384
|
+
const path2 = prefix === "" ? key : `${prefix}.${key}`;
|
|
385
|
+
addPathValue(path2, v, out);
|
|
386
|
+
walkFieldStats(v, path2, out);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
function addPathValue(path2, value, out) {
|
|
391
|
+
const existing = out.get(path2) ?? { types: /* @__PURE__ */ new Set(), sampleValues: /* @__PURE__ */ new Set() };
|
|
392
|
+
const t = classifyValueType(value);
|
|
393
|
+
existing.types.add(t);
|
|
394
|
+
if ((value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") && existing.sampleValues.size < 20) {
|
|
395
|
+
existing.sampleValues.add(value);
|
|
396
|
+
}
|
|
397
|
+
out.set(path2, existing);
|
|
398
|
+
}
|
|
399
|
+
function classifyValueType(value) {
|
|
400
|
+
if (value === null) return "null";
|
|
401
|
+
if (Array.isArray(value)) return "array";
|
|
402
|
+
return typeof value;
|
|
403
|
+
}
|
|
404
|
+
function mergeFieldStats(a, b) {
|
|
405
|
+
const merged = { ...a };
|
|
406
|
+
for (const [path2, bs] of Object.entries(b)) {
|
|
407
|
+
const prev = merged[path2];
|
|
408
|
+
if (!prev) {
|
|
409
|
+
merged[path2] = bs;
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
const types = /* @__PURE__ */ new Set([...prev.types, ...bs.types]);
|
|
413
|
+
const sampleValues = /* @__PURE__ */ new Set([...prev.sampleValues, ...bs.sampleValues]);
|
|
414
|
+
merged[path2] = {
|
|
415
|
+
types: [...types].toSorted(),
|
|
416
|
+
sampleValues: [...sampleValues].toSorted(comparePrimitiveValues).slice(0, 20)
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
return merged;
|
|
420
|
+
}
|
|
421
|
+
function sortFieldStats(stats) {
|
|
422
|
+
if (!stats) return void 0;
|
|
423
|
+
const out = {};
|
|
424
|
+
for (const path2 of Object.keys(stats).toSorted()) {
|
|
425
|
+
out[path2] = {
|
|
426
|
+
types: [...stats[path2].types].toSorted(),
|
|
427
|
+
sampleValues: [...stats[path2].sampleValues].toSorted(
|
|
428
|
+
comparePrimitiveValues
|
|
429
|
+
)
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
return out;
|
|
433
|
+
}
|
|
434
|
+
function comparePrimitiveValues(a, b) {
|
|
435
|
+
const sa = JSON.stringify(a);
|
|
436
|
+
const sb = JSON.stringify(b);
|
|
437
|
+
return sa.localeCompare(sb);
|
|
438
|
+
}
|
|
344
439
|
|
|
345
440
|
export { ARCHITECTURE_SNAPSHOT_SPEC, ArchitectureSnapshotSubscriber, extractFieldPaths };
|
|
346
441
|
//# sourceMappingURL=architecture-snapshot.js.map
|