autotel-subscribers 41.0.3 → 41.0.4

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.
Files changed (67) hide show
  1. package/dist/amplitude.cjs +2 -2
  2. package/dist/amplitude.cjs.map +1 -1
  3. package/dist/amplitude.js +2 -2
  4. package/dist/architecture-snapshot.cjs +6 -6
  5. package/dist/architecture-snapshot.cjs.map +1 -1
  6. package/dist/architecture-snapshot.js +3 -3
  7. package/dist/architecture-snapshot.js.map +1 -1
  8. package/dist/{cjs-BS1fE_MP.js → cjs-Bef2t9Tr.js} +348 -109
  9. package/dist/cjs-Bef2t9Tr.js.map +1 -0
  10. package/dist/{cjs-CC1S3xV3.cjs → cjs-C2fugEI8.cjs} +169 -164
  11. package/dist/{cjs-CC1S3xV3.cjs.map → cjs-C2fugEI8.cjs.map} +1 -1
  12. package/dist/{cjs-BVWTUHeD.cjs → cjs-C5tev9Yr.cjs} +458 -219
  13. package/dist/cjs-C5tev9Yr.cjs.map +1 -0
  14. package/dist/{cjs-j3aHAiuq.js → cjs-CY69zXGd.js} +20 -15
  15. package/dist/{cjs-j3aHAiuq.js.map → cjs-CY69zXGd.js.map} +1 -1
  16. package/dist/factories.cjs +2 -2
  17. package/dist/factories.d.cts +1 -1
  18. package/dist/factories.d.ts +1 -1
  19. package/dist/factories.js +1 -1
  20. package/dist/file.cjs +2 -2
  21. package/dist/index.cjs +2 -2
  22. package/dist/index.d.cts +1 -1
  23. package/dist/index.d.ts +1 -1
  24. package/dist/index.js +1 -1
  25. package/dist/{index.node-T6N3xCyo.js → index.node-BF2WkODf.js} +368 -148
  26. package/dist/index.node-BF2WkODf.js.map +1 -0
  27. package/dist/{index.node-CVFSZNnN.cjs → index.node-DOkEUdYV.cjs} +369 -149
  28. package/dist/index.node-DOkEUdYV.cjs.map +1 -0
  29. package/dist/middleware.cjs +1 -1
  30. package/dist/{mixpanel-node-CTaHqgil.cjs → mixpanel-node-BVS2X-h7.cjs} +32 -31
  31. package/dist/{mixpanel-node-CTaHqgil.cjs.map → mixpanel-node-BVS2X-h7.cjs.map} +1 -1
  32. package/dist/{mixpanel-node-2RXHuOWK.js → mixpanel-node-BltEtoG6.js} +5 -4
  33. package/dist/{mixpanel-node-2RXHuOWK.js.map → mixpanel-node-BltEtoG6.js.map} +1 -1
  34. package/dist/mixpanel.cjs +2 -2
  35. package/dist/mixpanel.cjs.map +1 -1
  36. package/dist/mixpanel.js +2 -2
  37. package/dist/{posthog-DTpCMk3r.js → posthog-C9M1c_oG.js} +2 -2
  38. package/dist/{posthog-DTpCMk3r.js.map → posthog-C9M1c_oG.js.map} +1 -1
  39. package/dist/{posthog-DLBf2UJJ.d.ts → posthog-DCV5Arkw.d.ts} +217 -24
  40. package/dist/posthog-DCV5Arkw.d.ts.map +1 -0
  41. package/dist/{posthog-Duh9pZs7.d.cts → posthog-DRYlG5fG.d.cts} +217 -24
  42. package/dist/posthog-DRYlG5fG.d.cts.map +1 -0
  43. package/dist/{posthog-Bz9XTdX5.cjs → posthog-DZfoAW1q.cjs} +4 -4
  44. package/dist/{posthog-Bz9XTdX5.cjs.map → posthog-DZfoAW1q.cjs.map} +1 -1
  45. package/dist/posthog.cjs +2 -2
  46. package/dist/posthog.d.cts +1 -1
  47. package/dist/posthog.d.ts +1 -1
  48. package/dist/posthog.js +1 -1
  49. package/dist/{chunk-CiiB0FCw.js → rolldown-runtime-BHkdefai.js} +9 -2
  50. package/dist/{chunk-BIkA7V7q.cjs → rolldown-runtime-CfAboj1T.cjs} +8 -1
  51. package/dist/security.cjs +1 -1
  52. package/dist/segment.cjs +2 -2
  53. package/dist/segment.cjs.map +1 -1
  54. package/dist/segment.js +2 -2
  55. package/dist/slack.cjs +1 -1
  56. package/dist/{tslib.es6-Ck6tgf4J.cjs → tslib.es6-02HJIdSd.cjs} +4 -4
  57. package/dist/{tslib.es6-Ck6tgf4J.cjs.map → tslib.es6-02HJIdSd.cjs.map} +1 -1
  58. package/dist/{tslib.es6-13Ha9x3r.js → tslib.es6-Dvm0j0am.js} +2 -2
  59. package/dist/{tslib.es6-13Ha9x3r.js.map → tslib.es6-Dvm0j0am.js.map} +1 -1
  60. package/dist/webhook.cjs +1 -1
  61. package/package.json +15 -15
  62. package/dist/cjs-BS1fE_MP.js.map +0 -1
  63. package/dist/cjs-BVWTUHeD.cjs.map +0 -1
  64. package/dist/index.node-CVFSZNnN.cjs.map +0 -1
  65. package/dist/index.node-T6N3xCyo.js.map +0 -1
  66. package/dist/posthog-DLBf2UJJ.d.ts.map +0 -1
  67. package/dist/posthog-Duh9pZs7.d.cts.map +0 -1
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
- const require_chunk = require('./chunk-BIkA7V7q.cjs');
2
+ const require_rolldown_runtime = require('./rolldown-runtime-CfAboj1T.cjs');
3
3
 
4
4
  //#region src/amplitude.ts
5
5
  var AmplitudeSubscriber = class {
@@ -16,7 +16,7 @@ var AmplitudeSubscriber = class {
16
16
  }
17
17
  async initialize() {
18
18
  try {
19
- const amplitude = await Promise.resolve().then(() => /* @__PURE__ */ require_chunk.__toESM(require("./cjs-BVWTUHeD.cjs").default, 1));
19
+ const amplitude = await Promise.resolve().then(() => /* @__PURE__ */ require_rolldown_runtime.__toESM(require("./cjs-C5tev9Yr.cjs").default, 1));
20
20
  amplitude.init(this.config.apiKey);
21
21
  this.amplitudeModule = amplitude;
22
22
  } catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"amplitude.cjs","names":[],"sources":["../src/amplitude.ts"],"sourcesContent":["/**\n * Amplitude Subscriber for autotel\n *\n * Send events to Amplitude for product events.\n *\n * @example\n * ```typescript\n * import { Events } from 'autotel/events';\n * import { AmplitudeSubscriber } from 'autotel-subscribers/amplitude';\n *\n * const events = new Events('checkout', {\n * subscribers: [\n * new AmplitudeSubscriber({\n * apiKey: process.env.AMPLITUDE_API_KEY!\n * })\n * ]\n * });\n *\n * events.trackEvent('order.completed', { userId: '123', amount: 99.99 });\n * ```\n */\n\nimport type {\n EventSubscriber,\n EventAttributes,\n FunnelStatus,\n OutcomeStatus,\n} from 'autotel/event-subscriber';\n\nexport interface AmplitudeConfig {\n /** Amplitude API key */\n apiKey: string;\n /** Enable/disable the subscriber */\n enabled?: boolean;\n}\n\nexport class AmplitudeSubscriber implements EventSubscriber {\n readonly name = 'AmplitudeSubscriber';\n readonly version = '1.0.0';\n\n private amplitudeModule: any = null;\n private enabled: boolean;\n private config: AmplitudeConfig;\n private initPromise: Promise<void> | null = null;\n\n constructor(config: AmplitudeConfig) {\n this.enabled = config.enabled ?? true;\n this.config = config;\n\n if (this.enabled) {\n // Start initialization immediately but don't block constructor\n this.initPromise = this.initialize();\n }\n }\n\n private async initialize(): Promise<void> {\n try {\n // Dynamic import to avoid adding @amplitude/analytics-node as a hard dependency\n // The SDK exports init(), track(), flush() as separate functions\n const amplitude = await import('@amplitude/analytics-node');\n amplitude.init(this.config.apiKey);\n this.amplitudeModule = amplitude;\n } catch (error) {\n console.error(\n 'Amplitude subscriber failed to initialize. Install @amplitude/analytics-node: pnpm add @amplitude/analytics-node',\n error,\n );\n this.enabled = false;\n }\n }\n\n private async ensureInitialized(): Promise<void> {\n if (this.initPromise) {\n await this.initPromise;\n this.initPromise = null;\n }\n }\n\n async trackEvent(name: string, attributes?: EventAttributes): Promise<void> {\n if (!this.enabled) return;\n\n await this.ensureInitialized();\n this.amplitudeModule?.track({\n event_type: name,\n user_id: attributes?.userId || attributes?.user_id || 'anonymous',\n event_properties: attributes,\n });\n }\n\n async trackFunnelStep(\n funnelName: string,\n step: FunnelStatus,\n attributes?: EventAttributes,\n ): Promise<void> {\n if (!this.enabled) return;\n\n await this.ensureInitialized();\n this.amplitudeModule?.track({\n event_type: `${funnelName}.${step}`,\n user_id: attributes?.userId || attributes?.user_id || 'anonymous',\n event_properties: {\n funnel: funnelName,\n step,\n ...attributes,\n },\n });\n }\n\n async trackOutcome(\n operationName: string,\n outcome: OutcomeStatus,\n attributes?: EventAttributes,\n ): Promise<void> {\n if (!this.enabled) return;\n\n await this.ensureInitialized();\n this.amplitudeModule?.track({\n event_type: `${operationName}.${outcome}`,\n user_id: attributes?.userId || attributes?.user_id || 'anonymous',\n event_properties: {\n operation: operationName,\n outcome,\n ...attributes,\n },\n });\n }\n\n async trackValue(name: string, value: number, attributes?: EventAttributes): Promise<void> {\n if (!this.enabled) return;\n\n await this.ensureInitialized();\n this.amplitudeModule?.track({\n event_type: name,\n user_id: attributes?.userId || attributes?.user_id || 'anonymous',\n event_properties: {\n value,\n ...attributes,\n },\n });\n }\n\n /** Flush pending events before shutdown */\n async shutdown(): Promise<void> {\n await this.ensureInitialized();\n if (this.amplitudeModule) {\n await this.amplitudeModule.flush();\n }\n }\n}\n\n"],"mappings":";;;;AAoCA,IAAa,sBAAb,MAA4D;CAC1D,AAAS,OAAO;CAChB,AAAS,UAAU;CAEnB,AAAQ,kBAAuB;CAC/B,AAAQ;CACR,AAAQ;CACR,AAAQ,cAAoC;CAE5C,YAAY,QAAyB;EACnC,KAAK,UAAU,OAAO,WAAW;EACjC,KAAK,SAAS;EAEd,IAAI,KAAK,SAEP,KAAK,cAAc,KAAK,WAAW;CAEvC;CAEA,MAAc,aAA4B;EACxC,IAAI;GAGF,MAAM,YAAY,iFAAM;GACxB,UAAU,KAAK,KAAK,OAAO,MAAM;GACjC,KAAK,kBAAkB;EACzB,SAAS,OAAO;GACd,QAAQ,MACN,oHACA,KACF;GACA,KAAK,UAAU;EACjB;CACF;CAEA,MAAc,oBAAmC;EAC/C,IAAI,KAAK,aAAa;GACpB,MAAM,KAAK;GACX,KAAK,cAAc;EACrB;CACF;CAEA,MAAM,WAAW,MAAc,YAA6C;EAC1E,IAAI,CAAC,KAAK,SAAS;EAEnB,MAAM,KAAK,kBAAkB;EAC7B,KAAK,iBAAiB,MAAM;GAC1B,YAAY;GACZ,SAAS,YAAY,UAAU,YAAY,WAAW;GACtD,kBAAkB;EACpB,CAAC;CACH;CAEA,MAAM,gBACJ,YACA,MACA,YACe;EACf,IAAI,CAAC,KAAK,SAAS;EAEnB,MAAM,KAAK,kBAAkB;EAC7B,KAAK,iBAAiB,MAAM;GAC1B,YAAY,GAAG,WAAW,GAAG;GAC7B,SAAS,YAAY,UAAU,YAAY,WAAW;GACtD,kBAAkB;IAChB,QAAQ;IACR;IACA,GAAG;GACL;EACF,CAAC;CACH;CAEA,MAAM,aACJ,eACA,SACA,YACe;EACf,IAAI,CAAC,KAAK,SAAS;EAEnB,MAAM,KAAK,kBAAkB;EAC7B,KAAK,iBAAiB,MAAM;GAC1B,YAAY,GAAG,cAAc,GAAG;GAChC,SAAS,YAAY,UAAU,YAAY,WAAW;GACtD,kBAAkB;IAChB,WAAW;IACX;IACA,GAAG;GACL;EACF,CAAC;CACH;CAEA,MAAM,WAAW,MAAc,OAAe,YAA6C;EACzF,IAAI,CAAC,KAAK,SAAS;EAEnB,MAAM,KAAK,kBAAkB;EAC7B,KAAK,iBAAiB,MAAM;GAC1B,YAAY;GACZ,SAAS,YAAY,UAAU,YAAY,WAAW;GACtD,kBAAkB;IAChB;IACA,GAAG;GACL;EACF,CAAC;CACH;;CAGA,MAAM,WAA0B;EAC9B,MAAM,KAAK,kBAAkB;EAC7B,IAAI,KAAK,iBACP,MAAM,KAAK,gBAAgB,MAAM;CAErC;AACF"}
1
+ {"version":3,"file":"amplitude.cjs","names":[],"sources":["../src/amplitude.ts"],"sourcesContent":["/**\n * Amplitude Subscriber for autotel\n *\n * Send events to Amplitude for product events.\n *\n * @example\n * ```typescript\n * import { Events } from 'autotel/events';\n * import { AmplitudeSubscriber } from 'autotel-subscribers/amplitude';\n *\n * const events = new Events('checkout', {\n * subscribers: [\n * new AmplitudeSubscriber({\n * apiKey: process.env.AMPLITUDE_API_KEY!\n * })\n * ]\n * });\n *\n * events.trackEvent('order.completed', { userId: '123', amount: 99.99 });\n * ```\n */\n\nimport type {\n EventSubscriber,\n EventAttributes,\n FunnelStatus,\n OutcomeStatus,\n} from 'autotel/event-subscriber';\n\nexport interface AmplitudeConfig {\n /** Amplitude API key */\n apiKey: string;\n /** Enable/disable the subscriber */\n enabled?: boolean;\n}\n\nexport class AmplitudeSubscriber implements EventSubscriber {\n readonly name = 'AmplitudeSubscriber';\n readonly version = '1.0.0';\n\n private amplitudeModule: any = null;\n private enabled: boolean;\n private config: AmplitudeConfig;\n private initPromise: Promise<void> | null = null;\n\n constructor(config: AmplitudeConfig) {\n this.enabled = config.enabled ?? true;\n this.config = config;\n\n if (this.enabled) {\n // Start initialization immediately but don't block constructor\n this.initPromise = this.initialize();\n }\n }\n\n private async initialize(): Promise<void> {\n try {\n // Dynamic import to avoid adding @amplitude/analytics-node as a hard dependency\n // The SDK exports init(), track(), flush() as separate functions\n const amplitude = await import('@amplitude/analytics-node');\n amplitude.init(this.config.apiKey);\n this.amplitudeModule = amplitude;\n } catch (error) {\n console.error(\n 'Amplitude subscriber failed to initialize. Install @amplitude/analytics-node: pnpm add @amplitude/analytics-node',\n error,\n );\n this.enabled = false;\n }\n }\n\n private async ensureInitialized(): Promise<void> {\n if (this.initPromise) {\n await this.initPromise;\n this.initPromise = null;\n }\n }\n\n async trackEvent(name: string, attributes?: EventAttributes): Promise<void> {\n if (!this.enabled) return;\n\n await this.ensureInitialized();\n this.amplitudeModule?.track({\n event_type: name,\n user_id: attributes?.userId || attributes?.user_id || 'anonymous',\n event_properties: attributes,\n });\n }\n\n async trackFunnelStep(\n funnelName: string,\n step: FunnelStatus,\n attributes?: EventAttributes,\n ): Promise<void> {\n if (!this.enabled) return;\n\n await this.ensureInitialized();\n this.amplitudeModule?.track({\n event_type: `${funnelName}.${step}`,\n user_id: attributes?.userId || attributes?.user_id || 'anonymous',\n event_properties: {\n funnel: funnelName,\n step,\n ...attributes,\n },\n });\n }\n\n async trackOutcome(\n operationName: string,\n outcome: OutcomeStatus,\n attributes?: EventAttributes,\n ): Promise<void> {\n if (!this.enabled) return;\n\n await this.ensureInitialized();\n this.amplitudeModule?.track({\n event_type: `${operationName}.${outcome}`,\n user_id: attributes?.userId || attributes?.user_id || 'anonymous',\n event_properties: {\n operation: operationName,\n outcome,\n ...attributes,\n },\n });\n }\n\n async trackValue(name: string, value: number, attributes?: EventAttributes): Promise<void> {\n if (!this.enabled) return;\n\n await this.ensureInitialized();\n this.amplitudeModule?.track({\n event_type: name,\n user_id: attributes?.userId || attributes?.user_id || 'anonymous',\n event_properties: {\n value,\n ...attributes,\n },\n });\n }\n\n /** Flush pending events before shutdown */\n async shutdown(): Promise<void> {\n await this.ensureInitialized();\n if (this.amplitudeModule) {\n await this.amplitudeModule.flush();\n }\n }\n}\n\n"],"mappings":";;;;AAoCA,IAAa,sBAAb,MAA4D;CAC1D,AAAS,OAAO;CAChB,AAAS,UAAU;CAEnB,AAAQ,kBAAuB;CAC/B,AAAQ;CACR,AAAQ;CACR,AAAQ,cAAoC;CAE5C,YAAY,QAAyB;EACnC,KAAK,UAAU,OAAO,WAAW;EACjC,KAAK,SAAS;EAEd,IAAI,KAAK,SAEP,KAAK,cAAc,KAAK,WAAW;CAEvC;CAEA,MAAc,aAA4B;EACxC,IAAI;GAGF,MAAM,YAAY,4FAAM;GACxB,UAAU,KAAK,KAAK,OAAO,MAAM;GACjC,KAAK,kBAAkB;EACzB,SAAS,OAAO;GACd,QAAQ,MACN,oHACA,KACF;GACA,KAAK,UAAU;EACjB;CACF;CAEA,MAAc,oBAAmC;EAC/C,IAAI,KAAK,aAAa;GACpB,MAAM,KAAK;GACX,KAAK,cAAc;EACrB;CACF;CAEA,MAAM,WAAW,MAAc,YAA6C;EAC1E,IAAI,CAAC,KAAK,SAAS;EAEnB,MAAM,KAAK,kBAAkB;EAC7B,KAAK,iBAAiB,MAAM;GAC1B,YAAY;GACZ,SAAS,YAAY,UAAU,YAAY,WAAW;GACtD,kBAAkB;EACpB,CAAC;CACH;CAEA,MAAM,gBACJ,YACA,MACA,YACe;EACf,IAAI,CAAC,KAAK,SAAS;EAEnB,MAAM,KAAK,kBAAkB;EAC7B,KAAK,iBAAiB,MAAM;GAC1B,YAAY,GAAG,WAAW,GAAG;GAC7B,SAAS,YAAY,UAAU,YAAY,WAAW;GACtD,kBAAkB;IAChB,QAAQ;IACR;IACA,GAAG;GACL;EACF,CAAC;CACH;CAEA,MAAM,aACJ,eACA,SACA,YACe;EACf,IAAI,CAAC,KAAK,SAAS;EAEnB,MAAM,KAAK,kBAAkB;EAC7B,KAAK,iBAAiB,MAAM;GAC1B,YAAY,GAAG,cAAc,GAAG;GAChC,SAAS,YAAY,UAAU,YAAY,WAAW;GACtD,kBAAkB;IAChB,WAAW;IACX;IACA,GAAG;GACL;EACF,CAAC;CACH;CAEA,MAAM,WAAW,MAAc,OAAe,YAA6C;EACzF,IAAI,CAAC,KAAK,SAAS;EAEnB,MAAM,KAAK,kBAAkB;EAC7B,KAAK,iBAAiB,MAAM;GAC1B,YAAY;GACZ,SAAS,YAAY,UAAU,YAAY,WAAW;GACtD,kBAAkB;IAChB;IACA,GAAG;GACL;EACF,CAAC;CACH;;CAGA,MAAM,WAA0B;EAC9B,MAAM,KAAK,kBAAkB;EAC7B,IAAI,KAAK,iBACP,MAAM,KAAK,gBAAgB,MAAM;CAErC;AACF"}
package/dist/amplitude.js CHANGED
@@ -1,4 +1,4 @@
1
- import { o as __toESM } from "./chunk-CiiB0FCw.js";
1
+ import { o as __toESM } from "./rolldown-runtime-BHkdefai.js";
2
2
 
3
3
  //#region src/amplitude.ts
4
4
  var AmplitudeSubscriber = class {
@@ -15,7 +15,7 @@ var AmplitudeSubscriber = class {
15
15
  }
16
16
  async initialize() {
17
17
  try {
18
- const amplitude = await import("./cjs-BS1fE_MP.js").then((m) => /* @__PURE__ */ __toESM(m.default, 1));
18
+ const amplitude = await import("./cjs-Bef2t9Tr.js").then((m) => /* @__PURE__ */ __toESM(m.default, 1));
19
19
  amplitude.init(this.config.apiKey);
20
20
  this.amplitudeModule = amplitude;
21
21
  } catch (error) {
@@ -1,10 +1,10 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
- const require_chunk = require('./chunk-BIkA7V7q.cjs');
2
+ const require_rolldown_runtime = require('./rolldown-runtime-CfAboj1T.cjs');
3
3
  const require_event_subscriber_base = require('./event-subscriber-base-C_F3Ycbe.cjs');
4
4
  let node_fs_promises = require("node:fs/promises");
5
- node_fs_promises = require_chunk.__toESM(node_fs_promises, 1);
5
+ node_fs_promises = require_rolldown_runtime.__toESM(node_fs_promises, 1);
6
6
  let node_path = require("node:path");
7
- node_path = require_chunk.__toESM(node_path, 1);
7
+ node_path = require_rolldown_runtime.__toESM(node_path, 1);
8
8
 
9
9
  //#region src/architecture-snapshot.ts
10
10
  /**
@@ -165,7 +165,7 @@ function readAutotelMeta(attrs) {
165
165
  * context, baggage, service metadata). These describe the trace, not the
166
166
  * event payload, so they don't belong in the captured field paths.
167
167
  */
168
- const AUTOTEL_INJECTED_KEYS = new Set([
168
+ const AUTOTEL_INJECTED_KEYS = /* @__PURE__ */ new Set([
169
169
  "_autotel",
170
170
  "traceId",
171
171
  "trace_id",
@@ -263,8 +263,8 @@ function mergeFieldStats(a, b) {
263
263
  merged[path] = bs;
264
264
  continue;
265
265
  }
266
- const types = new Set([...prev.types, ...bs.types]);
267
- const sampleValues = new Set([...prev.sampleValues, ...bs.sampleValues]);
266
+ const types = /* @__PURE__ */ new Set([...prev.types, ...bs.types]);
267
+ const sampleValues = /* @__PURE__ */ new Set([...prev.sampleValues, ...bs.sampleValues]);
268
268
  merged[path] = {
269
269
  types: [...types].toSorted(),
270
270
  sampleValues: [...sampleValues].toSorted(comparePrimitiveValues).slice(0, 20)
@@ -1 +1 @@
1
- {"version":3,"file":"architecture-snapshot.cjs","names":["EventSubscriber","fs","path"],"sources":["../src/architecture-snapshot.ts"],"sourcesContent":["/**\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 * @param options.now Clock used for the snapshot's `generatedAt` field.\n * @param options.freezeTimestamps If supplied, every timestamp in the\n * output (`generatedAt`, and each event's `firstSeen` / `lastSeen`)\n * is replaced with this value. Use when writing a snapshot intended\n * to be committed to a repo as a stable artifact — production code\n * should not pass this.\n */\n toSnapshot(\n options: { now?: () => Date; freezeTimestamps?: string } = {},\n ): ArchitectureSnapshot {\n const { now = () => new Date(), freezeTimestamps } = options;\n\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 firstSeen: freezeTimestamps ?? obs.firstSeen,\n lastSeen: freezeTimestamps ?? obs.lastSeen,\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: freezeTimestamps ?? 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 * See {@link toSnapshot} for option semantics, including `freezeTimestamps`\n * for byte-stable committed artifacts.\n */\n async writeToFile(\n filePath: string,\n options: { now?: () => Date; freezeTimestamps?: string } = {},\n ): Promise<void> {\n await fs.mkdir(path.dirname(filePath), { recursive: true });\n const json = JSON.stringify(this.toSnapshot(options), 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"],"mappings":"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"}
1
+ {"version":3,"file":"architecture-snapshot.cjs","names":["EventSubscriber","fs","path"],"sources":["../src/architecture-snapshot.ts"],"sourcesContent":["/**\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 * @param options.now Clock used for the snapshot's `generatedAt` field.\n * @param options.freezeTimestamps If supplied, every timestamp in the\n * output (`generatedAt`, and each event's `firstSeen` / `lastSeen`)\n * is replaced with this value. Use when writing a snapshot intended\n * to be committed to a repo as a stable artifact — production code\n * should not pass this.\n */\n toSnapshot(\n options: { now?: () => Date; freezeTimestamps?: string } = {},\n ): ArchitectureSnapshot {\n const { now = () => new Date(), freezeTimestamps } = options;\n\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 firstSeen: freezeTimestamps ?? obs.firstSeen,\n lastSeen: freezeTimestamps ?? obs.lastSeen,\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: freezeTimestamps ?? 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 * See {@link toSnapshot} for option semantics, including `freezeTimestamps`\n * for byte-stable committed artifacts.\n */\n async writeToFile(\n filePath: string,\n options: { now?: () => Date; freezeTimestamps?: string } = {},\n ): Promise<void> {\n await fs.mkdir(path.dirname(filePath), { recursive: true });\n const json = JSON.stringify(this.toSnapshot(options), 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"],"mappings":"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"}
@@ -161,7 +161,7 @@ function readAutotelMeta(attrs) {
161
161
  * context, baggage, service metadata). These describe the trace, not the
162
162
  * event payload, so they don't belong in the captured field paths.
163
163
  */
164
- const AUTOTEL_INJECTED_KEYS = new Set([
164
+ const AUTOTEL_INJECTED_KEYS = /* @__PURE__ */ new Set([
165
165
  "_autotel",
166
166
  "traceId",
167
167
  "trace_id",
@@ -259,8 +259,8 @@ function mergeFieldStats(a, b) {
259
259
  merged[path] = bs;
260
260
  continue;
261
261
  }
262
- const types = new Set([...prev.types, ...bs.types]);
263
- const sampleValues = new Set([...prev.sampleValues, ...bs.sampleValues]);
262
+ const types = /* @__PURE__ */ new Set([...prev.types, ...bs.types]);
263
+ const sampleValues = /* @__PURE__ */ new Set([...prev.sampleValues, ...bs.sampleValues]);
264
264
  merged[path] = {
265
265
  types: [...types].toSorted(),
266
266
  sampleValues: [...sampleValues].toSorted(comparePrimitiveValues).slice(0, 20)
@@ -1 +1 @@
1
- {"version":3,"file":"architecture-snapshot.js","names":[],"sources":["../src/architecture-snapshot.ts"],"sourcesContent":["/**\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 * @param options.now Clock used for the snapshot's `generatedAt` field.\n * @param options.freezeTimestamps If supplied, every timestamp in the\n * output (`generatedAt`, and each event's `firstSeen` / `lastSeen`)\n * is replaced with this value. Use when writing a snapshot intended\n * to be committed to a repo as a stable artifact — production code\n * should not pass this.\n */\n toSnapshot(\n options: { now?: () => Date; freezeTimestamps?: string } = {},\n ): ArchitectureSnapshot {\n const { now = () => new Date(), freezeTimestamps } = options;\n\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 firstSeen: freezeTimestamps ?? obs.firstSeen,\n lastSeen: freezeTimestamps ?? obs.lastSeen,\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: freezeTimestamps ?? 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 * See {@link toSnapshot} for option semantics, including `freezeTimestamps`\n * for byte-stable committed artifacts.\n */\n async writeToFile(\n filePath: string,\n options: { now?: () => Date; freezeTimestamps?: string } = {},\n ): Promise<void> {\n await fs.mkdir(path.dirname(filePath), { recursive: true });\n const json = JSON.stringify(this.toSnapshot(options), 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"],"mappings":"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"}
1
+ {"version":3,"file":"architecture-snapshot.js","names":[],"sources":["../src/architecture-snapshot.ts"],"sourcesContent":["/**\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 * @param options.now Clock used for the snapshot's `generatedAt` field.\n * @param options.freezeTimestamps If supplied, every timestamp in the\n * output (`generatedAt`, and each event's `firstSeen` / `lastSeen`)\n * is replaced with this value. Use when writing a snapshot intended\n * to be committed to a repo as a stable artifact — production code\n * should not pass this.\n */\n toSnapshot(\n options: { now?: () => Date; freezeTimestamps?: string } = {},\n ): ArchitectureSnapshot {\n const { now = () => new Date(), freezeTimestamps } = options;\n\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 firstSeen: freezeTimestamps ?? obs.firstSeen,\n lastSeen: freezeTimestamps ?? obs.lastSeen,\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: freezeTimestamps ?? 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 * See {@link toSnapshot} for option semantics, including `freezeTimestamps`\n * for byte-stable committed artifacts.\n */\n async writeToFile(\n filePath: string,\n options: { now?: () => Date; freezeTimestamps?: string } = {},\n ): Promise<void> {\n await fs.mkdir(path.dirname(filePath), { recursive: true });\n const json = JSON.stringify(this.toSnapshot(options), 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"],"mappings":"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"}