autotel-subscribers 45.0.0 → 46.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.
Files changed (113) hide show
  1. package/dist/amplitude.cjs +1 -2
  2. package/dist/amplitude.d.cts +1 -2
  3. package/dist/amplitude.d.ts +1 -2
  4. package/dist/amplitude.js +1 -2
  5. package/dist/architecture-snapshot.cjs +1 -2
  6. package/dist/architecture-snapshot.d.cts +1 -2
  7. package/dist/architecture-snapshot.d.ts +1 -2
  8. package/dist/architecture-snapshot.js +1 -2
  9. package/dist/event-subscriber-base-C_F3Ycbe.cjs +1 -2
  10. package/dist/event-subscriber-base-Day2ml6V.js +1 -2
  11. package/dist/event-subscriber-base-DpZclJM4.d.cts +1 -2
  12. package/dist/event-subscriber-base-DpZclJM4.d.ts +1 -2
  13. package/dist/factories.cjs +1 -2
  14. package/dist/factories.d.cts +1 -2
  15. package/dist/factories.d.ts +1 -2
  16. package/dist/factories.js +1 -2
  17. package/dist/file.cjs +1 -2
  18. package/dist/file.d.cts +1 -2
  19. package/dist/file.d.ts +1 -2
  20. package/dist/file.js +1 -2
  21. package/dist/index.cjs +3 -2
  22. package/dist/index.d.cts +2 -2
  23. package/dist/index.d.ts +2 -2
  24. package/dist/index.js +2 -2
  25. package/dist/loki.cjs +229 -0
  26. package/dist/loki.d.cts +119 -0
  27. package/dist/loki.d.ts +119 -0
  28. package/dist/loki.js +221 -0
  29. package/dist/middleware.cjs +1 -2
  30. package/dist/middleware.d.cts +1 -2
  31. package/dist/middleware.d.ts +1 -2
  32. package/dist/middleware.js +1 -2
  33. package/dist/mixpanel.cjs +1 -2
  34. package/dist/mixpanel.d.cts +1 -2
  35. package/dist/mixpanel.d.ts +1 -2
  36. package/dist/mixpanel.js +1 -2
  37. package/dist/posthog-ByP8BmHb.js +1 -2
  38. package/dist/posthog-DOOBZanH.cjs +1 -2
  39. package/dist/posthog.d.cts +1 -2
  40. package/dist/posthog.d.ts +1 -2
  41. package/dist/security.cjs +1 -2
  42. package/dist/security.d.cts +1 -2
  43. package/dist/security.d.ts +1 -2
  44. package/dist/security.js +1 -2
  45. package/dist/segment.cjs +1 -2
  46. package/dist/segment.d.cts +1 -2
  47. package/dist/segment.d.ts +1 -2
  48. package/dist/segment.js +1 -2
  49. package/dist/slack.cjs +1 -2
  50. package/dist/slack.d.cts +1 -2
  51. package/dist/slack.d.ts +1 -2
  52. package/dist/slack.js +1 -2
  53. package/dist/webhook-delivery-D3v50C81.js +1 -2
  54. package/dist/webhook-delivery-DZeUlZhi.cjs +1 -2
  55. package/dist/webhook.cjs +1 -2
  56. package/dist/webhook.d.cts +1 -2
  57. package/dist/webhook.d.ts +1 -2
  58. package/dist/webhook.js +1 -2
  59. package/package.json +17 -10
  60. package/dist/amplitude.cjs.map +0 -1
  61. package/dist/amplitude.d.cts.map +0 -1
  62. package/dist/amplitude.d.ts.map +0 -1
  63. package/dist/amplitude.js.map +0 -1
  64. package/dist/architecture-snapshot.cjs.map +0 -1
  65. package/dist/architecture-snapshot.d.cts.map +0 -1
  66. package/dist/architecture-snapshot.d.ts.map +0 -1
  67. package/dist/architecture-snapshot.js.map +0 -1
  68. package/dist/event-subscriber-base-C_F3Ycbe.cjs.map +0 -1
  69. package/dist/event-subscriber-base-Day2ml6V.js.map +0 -1
  70. package/dist/event-subscriber-base-DpZclJM4.d.cts.map +0 -1
  71. package/dist/event-subscriber-base-DpZclJM4.d.ts.map +0 -1
  72. package/dist/factories.cjs.map +0 -1
  73. package/dist/factories.d.cts.map +0 -1
  74. package/dist/factories.d.ts.map +0 -1
  75. package/dist/factories.js.map +0 -1
  76. package/dist/file.cjs.map +0 -1
  77. package/dist/file.d.cts.map +0 -1
  78. package/dist/file.d.ts.map +0 -1
  79. package/dist/file.js.map +0 -1
  80. package/dist/index.cjs.map +0 -1
  81. package/dist/index.d.cts.map +0 -1
  82. package/dist/index.d.ts.map +0 -1
  83. package/dist/index.js.map +0 -1
  84. package/dist/middleware.cjs.map +0 -1
  85. package/dist/middleware.d.cts.map +0 -1
  86. package/dist/middleware.d.ts.map +0 -1
  87. package/dist/middleware.js.map +0 -1
  88. package/dist/mixpanel.cjs.map +0 -1
  89. package/dist/mixpanel.d.cts.map +0 -1
  90. package/dist/mixpanel.d.ts.map +0 -1
  91. package/dist/mixpanel.js.map +0 -1
  92. package/dist/posthog-ByP8BmHb.js.map +0 -1
  93. package/dist/posthog-DOOBZanH.cjs.map +0 -1
  94. package/dist/posthog.d.cts.map +0 -1
  95. package/dist/posthog.d.ts.map +0 -1
  96. package/dist/security.cjs.map +0 -1
  97. package/dist/security.d.cts.map +0 -1
  98. package/dist/security.d.ts.map +0 -1
  99. package/dist/security.js.map +0 -1
  100. package/dist/segment.cjs.map +0 -1
  101. package/dist/segment.d.cts.map +0 -1
  102. package/dist/segment.d.ts.map +0 -1
  103. package/dist/segment.js.map +0 -1
  104. package/dist/slack.cjs.map +0 -1
  105. package/dist/slack.d.cts.map +0 -1
  106. package/dist/slack.d.ts.map +0 -1
  107. package/dist/slack.js.map +0 -1
  108. package/dist/webhook-delivery-D3v50C81.js.map +0 -1
  109. package/dist/webhook-delivery-DZeUlZhi.cjs.map +0 -1
  110. package/dist/webhook.cjs.map +0 -1
  111. package/dist/webhook.d.cts.map +0 -1
  112. package/dist/webhook.d.ts.map +0 -1
  113. package/dist/webhook.js.map +0 -1
@@ -1 +0,0 @@
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 { Event } from 'autotel/event';\n * import { AmplitudeSubscriber } from 'autotel-subscribers/amplitude';\n *\n * const events = new Event('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(\n name: string,\n value: number,\n attributes?: EventAttributes,\n ): 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"],"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,MAAM,OAAO;GAC/B,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,WACJ,MACA,OACA,YACe;EACf,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 +0,0 @@
1
- {"version":3,"file":"amplitude.d.cts","names":[],"sources":["../src/amplitude.ts"],"mappings":";;UA6BiB;;EAEf;;EAEA;;cAGW,+BAA+B;WACjC;WACA;UAED;UACA;UACA;UACA;EAEI,YAAA,QAAQ;UAUN;UAgBA;EAOR,WAAW,cAAc,aAAa,kBAAkB;EAWxD,gBACJ,oBACA,MAAM,cACN,aAAa,kBACZ;EAeG,aACJ,uBACA,SAAS,eACT,aAAa,kBACZ;EAeG,WACJ,cACA,eACA,aAAa,kBACZ;;EAeG,YAAY"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"amplitude.d.ts","names":[],"sources":["../src/amplitude.ts"],"mappings":";;UA6BiB;;EAEf;;EAEA;;cAGW,+BAA+B;WACjC;WACA;UAED;UACA;UACA;UACA;EAEI,YAAA,QAAQ;UAUN;UAgBA;EAOR,WAAW,cAAc,aAAa,kBAAkB;EAWxD,gBACJ,oBACA,MAAM,cACN,aAAa,kBACZ;EAeG,aACJ,uBACA,SAAS,eACT,aAAa,kBACZ;EAeG,WACJ,cACA,eACA,aAAa,kBACZ;;EAeG,YAAY"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"amplitude.js","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 { Event } from 'autotel/event';\n * import { AmplitudeSubscriber } from 'autotel-subscribers/amplitude';\n *\n * const events = new Event('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(\n name: string,\n value: number,\n attributes?: EventAttributes,\n ): 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"],"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,MAAM,OAAO;GAC/B,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,WACJ,MACA,OACA,YACe;EACf,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 +0,0 @@
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 =\n '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(\n existing.fieldStats ?? {},\n fieldStats,\n );\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(\n existing.consumers ?? [],\n autotelMeta.consumers ?? [],\n );\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(\n attrs: Record<string, unknown>,\n): 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(\n value: unknown,\n prefix = '',\n): Record<string, FieldStats> {\n const out = new Map<\n string,\n { types: Set<string>; sampleValues: Set<string | number | boolean | null> }\n >();\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<\n string,\n { types: Set<string>; sampleValues: Set<string | number | boolean | null> }\n >,\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<\n string,\n { types: Set<string>; sampleValues: Set<string | number | boolean | null> }\n >,\n): void {\n const existing = out.get(path) ?? {\n types: new Set<string>(),\n sampleValues: new Set<string | number | boolean | null>(),\n };\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 +0,0 @@
1
- {"version":3,"file":"architecture-snapshot.d.cts","names":[],"sources":["../src/architecture-snapshot.ts"],"mappings":";;;;;;;cAwCa;KAGD;EACV,aAAa;EACb;EACA;EACA,QAAQ,eAAe;;KAGb;EACV;EACA;EACA;EACA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA,aAAa,eAAe;;EAE5B;IACE;IACA;IACA;;;KAIQ;;EAEV;;EAEA,cAAc;;UAGC;;EAEf;;EAEA;;cAKW,uCAAuC;WACzC;mBAEQ;mBACA;mBACA;EAEL,YAAA,QAAQ;YAMJ,kBAAkB,SAAS,eAAe;;;;;;;;;;;;;EAgF1D,WACE;IAAW,YAAY;IAAM;MAC5B;;;;;;;;EAkCG,YACJ,kBACA;IAAW,YAAY;IAAM;MAC5B;;EAOH;;;;;;iBA0Dc,kBAAkB,gBAAgB"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"architecture-snapshot.d.ts","names":[],"sources":["../src/architecture-snapshot.ts"],"mappings":";;;;;;;cAwCa;KAGD;EACV,aAAa;EACb;EACA;EACA,QAAQ,eAAe;;KAGb;EACV;EACA;EACA;EACA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA,aAAa,eAAe;;EAE5B;IACE;IACA;IACA;;;KAIQ;;EAEV;;EAEA,cAAc;;UAGC;;EAEf;;EAEA;;cAKW,uCAAuC;WACzC;mBAEQ;mBACA;mBACA;EAEL,YAAA,QAAQ;YAMJ,kBAAkB,SAAS,eAAe;;;;;;;;;;;;;EAgF1D,WACE;IAAW,YAAY;IAAM;MAC5B;;;;;;;;EAkCG,YACJ,kBACA;IAAW,YAAY;IAAM;MAC5B;;EAOH;;;;;;iBA0Dc,kBAAkB,gBAAgB"}
@@ -1 +0,0 @@
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 =\n '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(\n existing.fieldStats ?? {},\n fieldStats,\n );\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(\n existing.consumers ?? [],\n autotelMeta.consumers ?? [],\n );\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(\n attrs: Record<string, unknown>,\n): 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(\n value: unknown,\n prefix = '',\n): Record<string, FieldStats> {\n const out = new Map<\n string,\n { types: Set<string>; sampleValues: Set<string | number | boolean | null> }\n >();\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<\n string,\n { types: Set<string>; sampleValues: Set<string | number | boolean | null> }\n >,\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<\n string,\n { types: Set<string>; sampleValues: Set<string | number | boolean | null> }\n >,\n): void {\n const existing = out.get(path) ?? {\n types: new Set<string>(),\n sampleValues: new Set<string | number | boolean | null>(),\n };\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwCA,MAAa,6BACX;AAgDF,MAAM,sBAAsB;AAE5B,IAAa,iCAAb,cAAoD,gBAAgB;CAClE,AAAS,OAAO;CAEhB,AAAiB;CACjB,AAAiB;CACjB,AAAiB,+BAAe,IAAI,IAA8B;CAElE,YAAY,QAAoC;EAC9C,MAAM;EACN,KAAK,UAAU,OAAO;EACtB,KAAK,oBAAoB,OAAO,qBAAqB;CACvD;CAEA,MAAgB,kBAAkB,SAAsC;EAGtE,IAAI,QAAQ,SAAS,SAAS;EAE9B,MAAM,WAAW,KAAK,aAAa,IAAI,QAAQ,IAAI;EACnD,MAAM,MAAM,QAAQ;EACpB,MAAM,UAAU,QAAQ,SAAS;EACjC,MAAM,QAAQ,QAAQ,cAAc,CAAC;EACrC,MAAM,cAAc,gBAAgB,KAAK;EACzC,MAAM,aAAa,iBAAiB,KAAK;EACzC,MAAM,aAAa,kBAAkB,UAAU;EAC/C,MAAM,aAAa,kBAAkB,UAAU;EAE/C,IAAI,CAAC,UAAU;GACb,KAAK,aAAa,IAAI,QAAQ,MAAM;IAClC,MAAM,QAAQ;IACd,eAAe;IACf,WAAW;IACX,UAAU;IACV;IACA,gBAAgB,UAAU,CAAC,OAAO,IAAI,CAAC;IACvC,SAAS,YAAY;IACrB,UAAU,YAAY;IACtB,WAAW,YAAY;IACvB;IACA,QAAQ,QAAQ,SACZ;KACE,QAAQ,QAAQ,OAAO;KACvB,YAAY,QAAQ,OAAO;KAC3B,MAAM,QAAQ,OAAO;IACvB,IACA;GACN,CAAC;GACD;EACF;EAEA,SAAS,iBAAiB;EAC1B,SAAS,WAAW;EACpB,SAAS,aAAa,YAAY,SAAS,YAAY,UAAU;EACjE,SAAS,aAAa,gBACpB,SAAS,cAAc,CAAC,GACxB,UACF;EAEA,IACE,WACA,CAAC,SAAS,eAAe,SAAS,OAAO,KACzC,SAAS,eAAe,SAAS,KAAK,mBAEtC,SAAS,eAAe,KAAK,OAAO;EAGtC,SAAS,YAAY,YAAY;EACjC,SAAS,aAAa,YAAY;EAClC,SAAS,YAAY,YACnB,SAAS,aAAa,CAAC,GACvB,YAAY,aAAa,CAAC,CAC5B;EACA,SAAS,WAAW,QAAQ,SACxB;GACE,QAAQ,QAAQ,OAAO;GACvB,YAAY,QAAQ,OAAO;GAC3B,MAAM,QAAQ,OAAO;EACvB,IACA;CACN;;;;;;;;;;;;;CAcA,WACE,UAA2D,CAAC,GACtC;EACtB,MAAM,EAAE,4BAAY,IAAI,KAAK,GAAG,qBAAqB;EAErD,MAAM,SAA2C,CAAC;EAElD,MAAM,QAAQ,CAAC,GAAG,KAAK,aAAa,KAAK,CAAC,CAAC,CAAC,SAAS;EACrD,KAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,MAAM,KAAK,aAAa,IAAI,IAAI;GACtC,IAAI,CAAC,KAAK;GACV,OAAO,QAAQ;IACb,GAAG;IACH,WAAW,oBAAoB,IAAI;IACnC,UAAU,oBAAoB,IAAI;IAClC,YAAY,IAAI,WAAW,SAAS;IACpC,gBAAgB,IAAI,eAAe,SAAS;IAC5C,YAAY,eAAe,IAAI,UAAU;GAC3C;EACF;EAEA,OAAO;GACL,MAAM;GACN,aAAa,oBAAoB,IAAI,CAAC,CAAC,YAAY;GACnD,SAAS,KAAK;GACd;EACF;CACF;;;;;;;;CASA,MAAM,YACJ,UACA,UAA2D,CAAC,GAC7C;EACf,MAAM,GAAG,MAAM,KAAK,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;EAC1D,MAAM,OAAO,KAAK,UAAU,KAAK,WAAW,OAAO,GAAG,MAAM,CAAC;EAC7D,MAAM,GAAG,UAAU,UAAU,OAAO,MAAM,MAAM;CAClD;;CAGA,QAAc;EACZ,KAAK,aAAa,MAAM;CAC1B;AACF;AAQA,SAAS,gBAAgB,OAA6C;CACpE,MAAM,OAAO,MAAM;CACnB,IAAI,CAAC,QAAQ,OAAO,SAAS,UAAU,OAAO,CAAC;CAC/C,MAAM,IAAI;CACV,OAAO;EACL,SAAS,OAAO,EAAE,YAAY,WAAW,EAAE,UAAU;EACrD,UAAU,OAAO,EAAE,aAAa,WAAW,EAAE,WAAW;EACxD,WAAW,MAAM,QAAQ,EAAE,SAAS,IAChC,EAAE,UAAU,QAAQ,MAAmB,OAAO,MAAM,QAAQ,CAAC,CAAC,SAAS,IACvE;CACN;AACF;;;;;;AAOA,MAAM,wCAAwB,IAAI,IAAI;CACpC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAED,SAAS,iBACP,OACyB;CACzB,MAAM,MAA+B,CAAC;CACtC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,KAAK,GAAG;EAChD,IAAI,sBAAsB,IAAI,GAAG,GAAG;EACpC,IAAI,OAAO;CACb;CACA,OAAO;AACT;;;;;AAMA,SAAgB,kBAAkB,OAAgB,SAAS,IAAc;CACvE,MAAM,wBAAQ,IAAI,IAAY;CAC9B,KAAK,OAAO,QAAQ,KAAK;CACzB,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC,SAAS;AAC7B;AAEA,SAAS,KAAK,OAAgB,QAAgB,KAAwB;CACpE,IAAI,UAAU,QAAQ,UAAU,QAAW;CAC3C,IAAI,MAAM,QAAQ,KAAK,GAAG;EACxB,MAAM,cAAc,SAAS;EAC7B,KAAK,MAAM,QAAQ,OAAO,KAAK,MAAM,aAAa,GAAG;EACrD;CACF;CACA,IAAI,OAAO,UAAU,UAAU;EAC7B,KAAK,MAAM,CAAC,KAAK,MAAM,OAAO,QAAQ,KAAK,GAAG;GAC5C,MAAM,OAAO,WAAW,KAAK,MAAM,GAAG,OAAO,GAAG;GAChD,IAAI,IAAI,IAAI;GACZ,KAAK,GAAG,MAAM,GAAG;EACnB;EACA;CACF;AAEF;AAEA,SAAS,YAAY,GAAa,GAAuB;CACvD,IAAI,EAAE,WAAW,GAAG,OAAO;CAC3B,MAAM,MAAM,IAAI,IAAI,CAAC;CACrB,KAAK,MAAM,KAAK,GAAG,IAAI,IAAI,CAAC;CAC5B,OAAO,CAAC,GAAG,GAAG;AAChB;AAEA,SAAS,kBACP,OACA,SAAS,IACmB;CAC5B,MAAM,sBAAM,IAAI,IAGd;CACF,eAAe,OAAO,QAAQ,GAAG;CACjC,MAAM,MAAkC,CAAC;CACzC,KAAK,MAAM,CAAC,MAAM,UAAU,KAC1B,IAAI,QAAQ;EACV,OAAO,CAAC,GAAG,MAAM,KAAK,CAAC,CAAC,SAAS;EACjC,cAAc,CAAC,GAAG,MAAM,YAAY,CAAC,CAAC,SAAS,sBAAsB;CACvE;CAEF,OAAO;AACT;AAEA,SAAS,eACP,OACA,QACA,KAIM;CACN,IAAI,UAAU,QAAQ,UAAU,QAAW;CAC3C,IAAI,MAAM,QAAQ,KAAK,GAAG;EACxB,MAAM,cAAc,SAAS;EAC7B,KAAK,MAAM,QAAQ,OAAO,eAAe,MAAM,aAAa,GAAG;EAC/D;CACF;CACA,IAAI,OAAO,UAAU,UACnB,KAAK,MAAM,CAAC,KAAK,MAAM,OAAO,QAAQ,KAAK,GAAG;EAC5C,MAAM,OAAO,WAAW,KAAK,MAAM,GAAG,OAAO,GAAG;EAChD,aAAa,MAAM,GAAG,GAAG;EACzB,eAAe,GAAG,MAAM,GAAG;CAC7B;AAEJ;AAEA,SAAS,aACP,MACA,OACA,KAIM;CACN,MAAM,WAAW,IAAI,IAAI,IAAI,KAAK;EAChC,uBAAO,IAAI,IAAY;EACvB,8BAAc,IAAI,IAAsC;CAC1D;CACA,MAAM,IAAI,kBAAkB,KAAK;CACjC,SAAS,MAAM,IAAI,CAAC;CACpB,KACG,UAAU,QACT,OAAO,UAAU,YACjB,OAAO,UAAU,YACjB,OAAO,UAAU,cACnB,SAAS,aAAa,OAAO,IAE7B,SAAS,aAAa,IAAI,KAAK;CAEjC,IAAI,IAAI,MAAM,QAAQ;AACxB;AAEA,SAAS,kBAAkB,OAAwB;CACjD,IAAI,UAAU,MAAM,OAAO;CAC3B,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO;CACjC,OAAO,OAAO;AAChB;AAEA,SAAS,gBACP,GACA,GAC4B;CAC5B,MAAM,SAAqC,EAAE,GAAG,EAAE;CAClD,KAAK,MAAM,CAAC,MAAM,OAAO,OAAO,QAAQ,CAAC,GAAG;EAC1C,MAAM,OAAO,OAAO;EACpB,IAAI,CAAC,MAAM;GACT,OAAO,QAAQ;GACf;EACF;EACA,MAAM,wBAAQ,IAAI,IAAI,CAAC,GAAG,KAAK,OAAO,GAAG,GAAG,KAAK,CAAC;EAClD,MAAM,+BAAe,IAAI,IAAI,CAAC,GAAG,KAAK,cAAc,GAAG,GAAG,YAAY,CAAC;EACvE,OAAO,QAAQ;GACb,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC,SAAS;GAC3B,cAAc,CAAC,GAAG,YAAY,CAAC,CAC5B,SAAS,sBAAsB,CAAC,CAChC,MAAM,GAAG,EAAE;EAChB;CACF;CACA,OAAO;AACT;AAEA,SAAS,eACP,OACwC;CACxC,IAAI,CAAC,OAAO,OAAO;CACnB,MAAM,MAAkC,CAAC;CACzC,KAAK,MAAM,QAAQ,OAAO,KAAK,KAAK,CAAC,CAAC,SAAS,GAC7C,IAAI,QAAQ;EACV,OAAO,CAAC,GAAG,MAAM,KAAK,CAAC,KAAK,CAAC,CAAC,SAAS;EACvC,cAAc,CAAC,GAAG,MAAM,KAAK,CAAC,YAAY,CAAC,CAAC,SAC1C,sBACF;CACF;CAEF,OAAO;AACT;AAEA,SAAS,uBACP,GACA,GACQ;CACR,MAAM,KAAK,KAAK,UAAU,CAAC;CAC3B,MAAM,KAAK,KAAK,UAAU,CAAC;CAC3B,OAAO,GAAG,cAAc,EAAE;AAC5B"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"event-subscriber-base-C_F3Ycbe.cjs","names":[],"sources":["../src/event-subscriber-base.ts"],"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 {\n AutotelEventContext,\n EventTrackingOptions,\n} 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(payload: EventPayload): 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"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"event-subscriber-base-Day2ml6V.js","names":[],"sources":["../src/event-subscriber-base.ts"],"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 {\n AutotelEventContext,\n EventTrackingOptions,\n} 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(payload: EventPayload): 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"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"event-subscriber-base-DpZclJM4.d.cts","names":[],"sources":["../src/event-subscriber-base.ts"],"mappings":";;;;;UAqFiB;;EAEf;;EAGA;;EAGA,aAAa;;EAGb;;EAGA,OAAO;;EAGP;;EAGA;;EAGA;;EAGA,UAAU;;EAGV;;EAGA;;;;;;;;EASA,UAAU;;EAEV,SAAS;;;;;;;;;;;;;;;;;;uBAmBW,6BAA2B;;;;oBAI7B;;;;WAKT;;;;YAKC;;;;UAKF;;;;;;;;;qBAUW,kBAAkB,SAAS,eAAe;;;;;;;;;;YAWnD,YAAY,OAAO,OAAO,SAAS;;;;;;;;;;;;;;;;;;;;YA2BnC,iBACR,aAAa,uBACZ;;;;EAiBG,WACJ,cACA,aAAa,iBACb,UAAU,uBACT;;;;EAkBG,gBACJ,oBACA,MAAM,cACN,aAAa,iBACb,UAAU,uBACT;;;;EAmBG,aACJ,uBACA,SAAS,eACT,aAAa,iBACb,UAAU,uBACT;;;;EAmBG,WACJ,cACA,eACA,aAAa,iBACb,UAAU,uBACT;;;;;;;;;;;;;EA2BG,uBACJ,oBACA,kBACA,qBACA,aAAa,iBACb,UAAU,uBACT;;;;;;;;;;;;EA6BG,YAAY;;;;UAmCJ;;;;UAcA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"event-subscriber-base-DpZclJM4.d.ts","names":[],"sources":["../src/event-subscriber-base.ts"],"mappings":";;;;;UAqFiB;;EAEf;;EAGA;;EAGA,aAAa;;EAGb;;EAGA,OAAO;;EAGP;;EAGA;;EAGA;;EAGA,UAAU;;EAGV;;EAGA;;;;;;;;EASA,UAAU;;EAEV,SAAS;;;;;;;;;;;;;;;;;;uBAmBW,6BAA2B;;;;oBAI7B;;;;WAKT;;;;YAKC;;;;UAKF;;;;;;;;;qBAUW,kBAAkB,SAAS,eAAe;;;;;;;;;;YAWnD,YAAY,OAAO,OAAO,SAAS;;;;;;;;;;;;;;;;;;;;YA2BnC,iBACR,aAAa,uBACZ;;;;EAiBG,WACJ,cACA,aAAa,iBACb,UAAU,uBACT;;;;EAkBG,gBACJ,oBACA,MAAM,cACN,aAAa,iBACb,UAAU,uBACT;;;;EAmBG,aACJ,uBACA,SAAS,eACT,aAAa,iBACb,UAAU,uBACT;;;;EAmBG,WACJ,cACA,eACA,aAAa,iBACb,UAAU,uBACT;;;;;;;;;;;;;EA2BG,uBACJ,oBACA,kBACA,qBACA,aAAa,iBACb,UAAU,uBACT;;;;;;;;;;;;EA6BG,YAAY;;;;UAmCJ;;;;UAcA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"factories.cjs","names":["PostHogSubscriber","MixpanelSubscriber","AmplitudeSubscriber","SegmentSubscriber","WebhookSubscriber","SlackSubscriber"],"sources":["../src/mock-event-subscriber.ts","../src/factories.ts"],"sourcesContent":["/**\n * Mock Events Subscriber for Testing\n *\n * In-memory subscriber that captures all events events for testing assertions.\n * Useful for unit testing code that uses Events without making real API calls.\n *\n * @example Basic testing\n * ```typescript\n * import { Event } from 'autotel/event';\n * import { MockEventSubscriber } from 'autotel-subscribers/mock-event-subscriber';\n * import { describe, it, expect, beforeEach } from 'vitest';\n *\n * describe('CheckoutService', () => {\n * let mockSubscriber: MockEventSubscriber;\n * let events: Events;\n *\n * beforeEach(() => {\n * mockSubscriber = new MockEventSubscriber();\n * events = new Event('checkout', { subscribers: [mockSubscriber] });\n * });\n *\n * it('should track order completion', async () => {\n * const service = new CheckoutService(events);\n * await service.completeOrder('ord_123', 99.99);\n *\n * expect(mockSubscriber.events).toHaveLength(1);\n * expect(mockSubscriber.events[0]).toMatchObject({\n * type: 'event',\n * name: 'order.completed',\n * attributes: { orderId: 'ord_123', amount: 99.99 }\n * });\n * });\n *\n * it('should track checkout funnel', () => {\n * events.trackFunnelStep('checkout', 'started', { cartValue: 99.99 });\n * events.trackFunnelStep('checkout', 'completed', { cartValue: 99.99 });\n *\n * const funnelEvents = mockSubscriber.getFunnelEvents('checkout');\n * expect(funnelEvents).toHaveLength(2);\n * expect(funnelEvents[0].step).toBe('started');\n * expect(funnelEvents[1].step).toBe('completed');\n * });\n * });\n * ```\n *\n * @example With Outbox Pattern\n * ```typescript\n * import { createPublisher } from 'autotel-outbox';\n * import { MockOutboxStorage } from 'autotel-outbox/testing';\n *\n * it('should broadcast events to all subscribers', async () => {\n * const mockOutbox = new MockOutboxStorage();\n * const mockSubscriber = new MockEventSubscriber();\n *\n * // Add events to outbox\n * await mockOutbox.writeEvent({\n * id: '1',\n * type: 'order.completed',\n * aggregateId: 'ord_123',\n * aggregateType: 'Order',\n * payload: { amount: 99.99 },\n * createdAt: new Date().toISOString(),\n * publishedAt: null\n * });\n *\n * // Run publisher\n * const publisher = createPublisher(mockOutbox, [mockSubscriber]);\n * await publisher();\n *\n * // Assert event was broadcast\n * expect(mockSubscriber.events).toHaveLength(1);\n * expect(mockSubscriber.events[0].name).toBe('order.completed');\n * });\n * ```\n */\n\nimport type {\n EventSubscriber,\n EventAttributes,\n FunnelStatus,\n OutcomeStatus,\n} from 'autotel/event-subscriber';\n\n/**\n * Captured event data\n */\nexport interface CapturedEvent {\n type: 'event' | 'funnel' | 'outcome' | 'value';\n name: string;\n attributes?: EventAttributes;\n funnel?: string;\n step?: FunnelStatus;\n operation?: string;\n outcome?: OutcomeStatus;\n value?: number;\n timestamp: string;\n}\n\n/**\n * Mock subscriber for testing\n *\n * Captures all events calls in memory for test assertions.\n * Does not make any real API calls.\n */\nexport class MockEventSubscriber implements EventSubscriber {\n readonly name = 'MockEventSubscriber';\n readonly version = '1.0.0';\n\n /**\n * All captured events\n */\n public events: CapturedEvent[] = [];\n\n /**\n * Track if shutdown was called\n */\n public shutdownCalled = false;\n\n async trackEvent(name: string, attributes?: EventAttributes): Promise<void> {\n this.events.push({\n type: 'event',\n name,\n attributes,\n timestamp: new Date().toISOString(),\n });\n }\n\n async trackFunnelStep(\n funnelName: string,\n step: FunnelStatus,\n attributes?: EventAttributes,\n ): Promise<void> {\n this.events.push({\n type: 'funnel',\n name: `${funnelName}.${step}`,\n funnel: funnelName,\n step,\n attributes,\n timestamp: new Date().toISOString(),\n });\n }\n\n async trackOutcome(\n operationName: string,\n outcome: OutcomeStatus,\n attributes?: EventAttributes,\n ): Promise<void> {\n this.events.push({\n type: 'outcome',\n name: `${operationName}.${outcome}`,\n operation: operationName,\n outcome,\n attributes,\n timestamp: new Date().toISOString(),\n });\n }\n\n async trackValue(\n name: string,\n value: number,\n attributes?: EventAttributes,\n ): Promise<void> {\n this.events.push({\n type: 'value',\n name,\n value,\n attributes,\n timestamp: new Date().toISOString(),\n });\n }\n\n async shutdown(): Promise<void> {\n this.shutdownCalled = true;\n }\n\n /**\n * Reset captured events (useful between tests)\n */\n reset(): void {\n this.events = [];\n this.shutdownCalled = false;\n }\n\n /**\n * Get events by type\n */\n getEventsByType(\n type: 'event' | 'funnel' | 'outcome' | 'value',\n ): CapturedEvent[] {\n return this.events.filter((e) => e.type === type);\n }\n\n /**\n * Get events by name\n */\n getEventsByName(name: string): CapturedEvent[] {\n return this.events.filter((e) => e.name === name);\n }\n\n /**\n * Get funnel events for a specific funnel\n */\n getFunnelEvents(funnelName: string): CapturedEvent[] {\n return this.events.filter(\n (e) => e.type === 'funnel' && e.funnel === funnelName,\n );\n }\n\n /**\n * Get outcome events for a specific operation\n */\n getOutcomeEvents(operationName: string): CapturedEvent[] {\n return this.events.filter(\n (e) => e.type === 'outcome' && e.operation === operationName,\n );\n }\n\n /**\n * Get value events by metric name\n */\n getValueEvents(metricName: string): CapturedEvent[] {\n return this.events.filter(\n (e) => e.type === 'value' && e.name === metricName,\n );\n }\n\n /**\n * Assert that an event was tracked\n */\n assertEventTracked(\n name: string,\n attributes?: Partial<EventAttributes>,\n ): void {\n const events = this.getEventsByName(name);\n\n if (events.length === 0) {\n throw new Error(`No events found with name: ${name}`);\n }\n\n if (attributes) {\n const matchingEvent = events.find((event) => {\n if (!event.attributes) return false;\n\n return Object.entries(attributes).every(\n ([key, value]) => event.attributes![key] === value,\n );\n });\n\n if (!matchingEvent) {\n throw new Error(\n `Event \"${name}\" found, but no matching attributes: ${JSON.stringify(attributes)}`,\n );\n }\n }\n }\n\n /**\n * Assert that a funnel step was tracked\n */\n assertFunnelStepTracked(\n funnelName: string,\n step: FunnelStatus,\n attributes?: Partial<EventAttributes>,\n ): void {\n const events = this.getFunnelEvents(funnelName);\n const matchingEvent = events.find((e) => e.step === step);\n\n if (!matchingEvent) {\n throw new Error(`Funnel \"${funnelName}\" step \"${step}\" was not tracked`);\n }\n\n if (attributes) {\n const hasMatchingAttributes = Object.entries(attributes).every(\n ([key, value]) => matchingEvent.attributes?.[key] === value,\n );\n\n if (!hasMatchingAttributes) {\n throw new Error(\n `Funnel step tracked, but attributes don't match: ${JSON.stringify(attributes)}`,\n );\n }\n }\n }\n\n /**\n * Assert that an outcome was tracked\n */\n assertOutcomeTracked(\n operationName: string,\n outcome: OutcomeStatus,\n attributes?: Partial<EventAttributes>,\n ): void {\n const events = this.getOutcomeEvents(operationName);\n const matchingEvent = events.find((e) => e.outcome === outcome);\n\n if (!matchingEvent) {\n throw new Error(`Outcome \"${operationName}.${outcome}\" was not tracked`);\n }\n\n if (attributes) {\n const hasMatchingAttributes = Object.entries(attributes).every(\n ([key, value]) => matchingEvent.attributes?.[key] === value,\n );\n\n if (!hasMatchingAttributes) {\n throw new Error(\n `Outcome tracked, but attributes don't match: ${JSON.stringify(attributes)}`,\n );\n }\n }\n }\n\n /**\n * Pretty print all captured events (useful for debugging)\n */\n printEvents(): void {\n console.log(`\\n[${this.name}] Captured ${this.events.length} events:\\n`);\n for (const [index, event] of this.events.entries()) {\n console.log(`${index + 1}. [${event.type}] ${event.name}`);\n if (event.attributes) {\n console.log(` Attributes:`, event.attributes);\n }\n if (event.value !== undefined) {\n console.log(` Value: ${event.value}`);\n }\n console.log(` Timestamp: ${event.timestamp}\\n`);\n }\n }\n}\n","/**\n * Factory functions for creating events subscribers\n *\n * Function-based alternatives to `new SubscriberClass()` pattern.\n * Provides a consistent API and better tree-shaking.\n *\n * @example\n * ```typescript\n * import { createPostHogSubscriber, createWebhookSubscriber } from 'autotel-subscribers/factories'\n *\n * const events = new Event('my-service', {\n * subscribers: [\n * createPostHogSubscriber({ apiKey: 'phc_...' }),\n * createWebhookSubscriber({ url: 'https://...' })\n * ]\n * })\n * ```\n */\n\nimport { PostHogSubscriber } from './posthog';\nimport { MixpanelSubscriber } from './mixpanel';\nimport { AmplitudeSubscriber } from './amplitude';\nimport { SegmentSubscriber } from './segment';\nimport { WebhookSubscriber } from './webhook';\nimport { SlackSubscriber } from './slack';\nimport { MockEventSubscriber } from './mock-event-subscriber';\n\nimport type {\n EventSubscriber,\n EventAttributes,\n OutcomeStatus,\n FunnelStatus,\n EventTrackingOptions,\n} from 'autotel/event-subscriber';\n\nexport type { PostHogConfig } from './posthog';\nexport type { MixpanelConfig } from './mixpanel';\nexport type { AmplitudeConfig } from './amplitude';\nexport type { SegmentConfig } from './segment';\nexport type { WebhookConfig } from './webhook';\nexport type { SlackSubscriberConfig } from './slack';\n\n/** Create a PostHog events subscriber */\nexport function createPostHogSubscriber(config: {\n apiKey: string;\n host?: string;\n enabled?: boolean;\n}): EventSubscriber {\n return new PostHogSubscriber(config);\n}\n\n/** Create a Mixpanel events subscriber */\nexport function createMixpanelSubscriber(config: {\n token: string;\n enabled?: boolean;\n}): EventSubscriber {\n return new MixpanelSubscriber(config);\n}\n\n/** Create an Amplitude events subscriber */\nexport function createAmplitudeSubscriber(config: {\n apiKey: string;\n enabled?: boolean;\n}): EventSubscriber {\n return new AmplitudeSubscriber(config);\n}\n\n/** Create a Segment events subscriber */\nexport function createSegmentSubscriber(config: {\n writeKey: string;\n enabled?: boolean;\n}): EventSubscriber {\n return new SegmentSubscriber(config);\n}\n\n/** Create a Webhook events subscriber with retry and timeout support */\nexport function createWebhookSubscriber(config: {\n url: string;\n method?: 'POST' | 'PUT';\n headers?: Record<string, string>;\n enabled?: boolean;\n maxRetries?: number;\n timeoutMs?: number;\n retryDelayMs?: number;\n}): EventSubscriber {\n return new WebhookSubscriber(config);\n}\n\n/** Create a Slack events subscriber */\nexport function createSlackSubscriber(config: {\n webhookUrl: string;\n channel?: string;\n enabled?: boolean;\n}): EventSubscriber {\n return new SlackSubscriber(config);\n}\n\n/** Create a mock events subscriber for testing */\nexport function createMockSubscriber(): MockEventSubscriber {\n return new MockEventSubscriber();\n}\n\n/**\n * Strategy for composing multiple subscribers\n *\n * - `parallel`: Send to all subscribers concurrently, fail if any fails\n * - `failover`: Try subscribers in order until one succeeds\n * - `round-robin`: Cycle through subscribers sequentially\n * - `random`: Pick a random subscriber each time\n * - `race`: Send to all, succeed when any succeeds\n * - `mirrored`: Send to primary, mirror to others (primary failure fails all)\n */\nexport type ComposeSubscriberStrategy =\n 'parallel' | 'failover' | 'round-robin' | 'random' | 'race' | 'mirrored';\n\n/** Configuration options for composing multiple subscribers */\nexport type ComposeSubscribersOptions = {\n name?: string;\n strategy?: ComposeSubscriberStrategy;\n maxAttemptsPerSubscriber?: number;\n initialRetryDelayMs?: number;\n maxRetryDelayMs?: number;\n isRetriable?: (error: unknown) => boolean;\n logger?: Pick<Console, 'debug' | 'warn' | 'error'>;\n};\n\ntype SubscriberMethod =\n 'trackEvent' | 'trackFunnelStep' | 'trackOutcome' | 'trackValue';\n\ntype MethodCall = {\n method: SubscriberMethod;\n args: unknown[];\n};\n\nfunction backoffDelay(\n attempt: number,\n initialMs: number,\n maxMs: number,\n): number {\n return Math.min(maxMs, initialMs * 2 ** (attempt - 1));\n}\n\nasync function callSubscriber(\n subscriber: EventSubscriber,\n call: MethodCall,\n): Promise<void> {\n switch (call.method) {\n case 'trackEvent': {\n await subscriber.trackEvent(\n call.args[0] as string,\n call.args[1] as EventAttributes | undefined,\n call.args[2] as EventTrackingOptions | undefined,\n );\n return;\n }\n case 'trackFunnelStep': {\n await subscriber.trackFunnelStep(\n call.args[0] as string,\n call.args[1] as FunnelStatus,\n call.args[2] as EventAttributes | undefined,\n call.args[3] as EventTrackingOptions | undefined,\n );\n return;\n }\n case 'trackOutcome': {\n await subscriber.trackOutcome(\n call.args[0] as string,\n call.args[1] as OutcomeStatus,\n call.args[2] as EventAttributes | undefined,\n call.args[3] as EventTrackingOptions | undefined,\n );\n return;\n }\n case 'trackValue': {\n await subscriber.trackValue(\n call.args[0] as string,\n call.args[1] as number,\n call.args[2] as EventAttributes | undefined,\n call.args[3] as EventTrackingOptions | undefined,\n );\n }\n }\n}\n\n/**\n * Compose multiple subscribers into one with a specified strategy\n *\n * @example\n * ```typescript\n * const multiSubscriber = composeSubscribers(\n * [\n * createPostHogSubscriber({ apiKey: '...' }),\n * createWebhookSubscriber({ url: '...' })\n * ],\n * { strategy: 'parallel' }\n * )\n * ```\n *\n * @param subscribers - Array of subscribers to compose\n * @param options - Configuration for composition strategy and retry behavior\n * @returns A composite EventSubscriber that applies the strategy\n */\nexport function composeSubscribers(\n subscribers: EventSubscriber[],\n options: ComposeSubscribersOptions = {},\n): EventSubscriber {\n const strategy = options.strategy ?? 'parallel';\n const name = options.name ?? `ComposedSubscriber(${strategy})`;\n const maxAttempts = options.maxAttemptsPerSubscriber ?? 1;\n const initialRetryDelayMs = options.initialRetryDelayMs ?? 250;\n const maxRetryDelayMs = options.maxRetryDelayMs ?? 10_000;\n const isRetriable = options.isRetriable ?? (() => true);\n const logger = options.logger ?? console;\n let rrCounter = 0;\n\n const sendOne = async (\n subscriber: EventSubscriber,\n call: MethodCall,\n ): Promise<void> => {\n let lastError: unknown;\n\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n await callSubscriber(subscriber, call);\n return;\n } catch (error) {\n lastError = error;\n const retryable = isRetriable(error);\n logger.warn?.('composeSubscribers attempt failed', {\n subscriber: subscriber.name,\n attempt,\n retryable,\n strategy,\n error,\n });\n\n if (!retryable || attempt >= maxAttempts) break;\n await new Promise((resolve) =>\n setTimeout(\n resolve,\n backoffDelay(attempt, initialRetryDelayMs, maxRetryDelayMs),\n ),\n );\n }\n }\n\n throw lastError instanceof Error ? lastError : new Error(String(lastError));\n };\n\n const orderForSequential = (): number[] => {\n const n = subscribers.length;\n if (n === 0) return [];\n\n if (strategy === 'failover') {\n return Array.from({ length: n }, (_, i) => i);\n }\n\n const start =\n strategy === 'random' ? Math.floor(Math.random() * n) : rrCounter++ % n;\n\n return Array.from({ length: n }, (_, i) => (start + i) % n);\n };\n\n const execute = async (call: MethodCall): Promise<void> => {\n if (subscribers.length === 0) return;\n\n if (strategy === 'parallel') {\n await Promise.all(\n subscribers.map((subscriber) => sendOne(subscriber, call)),\n );\n return;\n }\n\n if (strategy === 'race') {\n const attempts = subscribers.map(async (subscriber) => {\n await sendOne(subscriber, call);\n return subscriber.name ?? 'unknown';\n });\n\n try {\n await Promise.any(attempts);\n } catch (error) {\n if (error instanceof AggregateError && error.errors.length > 0) {\n throw error.errors.at(-1);\n }\n throw error;\n }\n return;\n }\n\n if (strategy === 'mirrored') {\n const primary = subscribers[0];\n if (!primary) return;\n await sendOne(primary, call);\n\n for (const mirror of subscribers.slice(1)) {\n void sendOne(mirror, call).catch((error) => {\n logger.warn?.('composeSubscribers mirror failed', {\n subscriber: mirror.name,\n error,\n });\n });\n }\n return;\n }\n\n const order = orderForSequential();\n let lastError: unknown;\n\n for (const index of order) {\n const subscriber = subscribers[index];\n if (!subscriber) continue;\n\n try {\n await sendOne(subscriber, call);\n return;\n } catch (error) {\n lastError = error;\n }\n }\n\n throw lastError instanceof Error ? lastError : new Error(String(lastError));\n };\n\n return {\n name,\n async trackEvent(name_, attributes, options_) {\n await execute({\n method: 'trackEvent',\n args: [name_, attributes, options_],\n });\n },\n async trackFunnelStep(funnel, step, attributes, options_) {\n await execute({\n method: 'trackFunnelStep',\n args: [funnel, step, attributes, options_],\n });\n },\n async trackOutcome(operation, outcome, attributes, options_) {\n await execute({\n method: 'trackOutcome',\n args: [operation, outcome, attributes, options_],\n });\n },\n async trackValue(name_, value, attributes, options_) {\n await execute({\n method: 'trackValue',\n args: [name_, value, attributes, options_],\n });\n },\n async shutdown() {\n await Promise.all(\n subscribers.map(async (subscriber) => subscriber.shutdown?.()),\n );\n },\n };\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"factories.d.cts","names":[],"sources":["../src/mock-event-subscriber.ts","../src/factories.ts"],"mappings":";;;;;;;;;;;UAsFiB;EACf;EACA;EACA,aAAa;EACb;EACA,OAAO;EACP;EACA,UAAU;EACV;EACA;;;;;;;;cASW,+BAA+B;WACjC;WACA;;;;EAKF,QAAQ;;;;EAKR;EAED,WAAW,cAAc,aAAa,kBAAkB;EASxD,gBACJ,oBACA,MAAM,cACN,aAAa,kBACZ;EAWG,aACJ,uBACA,SAAS,eACT,aAAa,kBACZ;EAWG,WACJ,cACA,eACA,aAAa,kBACZ;EAUG,YAAY;;;;EAOlB;;;;EAQA,gBACE,iDACC;;;;EAOH,gBAAgB,eAAe;;;;EAO/B,gBAAgB,qBAAqB;;;;EASrC,iBAAiB,wBAAwB;;;;EASzC,eAAe,qBAAqB;;;;EASpC,mBACE,cACA,aAAa,QAAQ;;;;EA4BvB,wBACE,oBACA,MAAM,cACN,aAAa,QAAQ;;;;EAyBvB,qBACE,uBACA,SAAS,eACT,aAAa,QAAQ;;;;EAyBvB;;;;;iBChRc,wBAAwB;EACtC;EACA;EACA;IACE;;iBAKY,yBAAyB;EACvC;EACA;IACE;;iBAKY,0BAA0B;EACxC;EACA;IACE;;iBAKY,wBAAwB;EACtC;EACA;IACE;;iBAKY,wBAAwB;EACtC;EACA;EACA,UAAU;EACV;EACA;EACA;EACA;IACE;;iBAKY,sBAAsB;EACpC;EACA;EACA;IACE;;iBAKY,wBAAwB;;;;;;;;;;;KAc5B;;KAIA;EACV;EACA,WAAW;EACX;EACA;EACA;EACA,eAAe;EACf,SAAS,KAAK;;;;;;;;;;;;;;;;;;;;iBA+EA,mBACd,aAAa,mBACb,UAAS,4BACR"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"factories.d.ts","names":[],"sources":["../src/mock-event-subscriber.ts","../src/factories.ts"],"mappings":";;;;;;;;;;;UAsFiB;EACf;EACA;EACA,aAAa;EACb;EACA,OAAO;EACP;EACA,UAAU;EACV;EACA;;;;;;;;cASW,+BAA+B;WACjC;WACA;;;;EAKF,QAAQ;;;;EAKR;EAED,WAAW,cAAc,aAAa,kBAAkB;EASxD,gBACJ,oBACA,MAAM,cACN,aAAa,kBACZ;EAWG,aACJ,uBACA,SAAS,eACT,aAAa,kBACZ;EAWG,WACJ,cACA,eACA,aAAa,kBACZ;EAUG,YAAY;;;;EAOlB;;;;EAQA,gBACE,iDACC;;;;EAOH,gBAAgB,eAAe;;;;EAO/B,gBAAgB,qBAAqB;;;;EASrC,iBAAiB,wBAAwB;;;;EASzC,eAAe,qBAAqB;;;;EASpC,mBACE,cACA,aAAa,QAAQ;;;;EA4BvB,wBACE,oBACA,MAAM,cACN,aAAa,QAAQ;;;;EAyBvB,qBACE,uBACA,SAAS,eACT,aAAa,QAAQ;;;;EAyBvB;;;;;iBChRc,wBAAwB;EACtC;EACA;EACA;IACE;;iBAKY,yBAAyB;EACvC;EACA;IACE;;iBAKY,0BAA0B;EACxC;EACA;IACE;;iBAKY,wBAAwB;EACtC;EACA;IACE;;iBAKY,wBAAwB;EACtC;EACA;EACA,UAAU;EACV;EACA;EACA;EACA;IACE;;iBAKY,sBAAsB;EACpC;EACA;EACA;IACE;;iBAKY,wBAAwB;;;;;;;;;;;KAc5B;;KAIA;EACV;EACA,WAAW;EACX;EACA;EACA;EACA,eAAe;EACf,SAAS,KAAK;;;;;;;;;;;;;;;;;;;;iBA+EA,mBACd,aAAa,mBACb,UAAS,4BACR"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"factories.js","names":[],"sources":["../src/mock-event-subscriber.ts","../src/factories.ts"],"sourcesContent":["/**\n * Mock Events Subscriber for Testing\n *\n * In-memory subscriber that captures all events events for testing assertions.\n * Useful for unit testing code that uses Events without making real API calls.\n *\n * @example Basic testing\n * ```typescript\n * import { Event } from 'autotel/event';\n * import { MockEventSubscriber } from 'autotel-subscribers/mock-event-subscriber';\n * import { describe, it, expect, beforeEach } from 'vitest';\n *\n * describe('CheckoutService', () => {\n * let mockSubscriber: MockEventSubscriber;\n * let events: Events;\n *\n * beforeEach(() => {\n * mockSubscriber = new MockEventSubscriber();\n * events = new Event('checkout', { subscribers: [mockSubscriber] });\n * });\n *\n * it('should track order completion', async () => {\n * const service = new CheckoutService(events);\n * await service.completeOrder('ord_123', 99.99);\n *\n * expect(mockSubscriber.events).toHaveLength(1);\n * expect(mockSubscriber.events[0]).toMatchObject({\n * type: 'event',\n * name: 'order.completed',\n * attributes: { orderId: 'ord_123', amount: 99.99 }\n * });\n * });\n *\n * it('should track checkout funnel', () => {\n * events.trackFunnelStep('checkout', 'started', { cartValue: 99.99 });\n * events.trackFunnelStep('checkout', 'completed', { cartValue: 99.99 });\n *\n * const funnelEvents = mockSubscriber.getFunnelEvents('checkout');\n * expect(funnelEvents).toHaveLength(2);\n * expect(funnelEvents[0].step).toBe('started');\n * expect(funnelEvents[1].step).toBe('completed');\n * });\n * });\n * ```\n *\n * @example With Outbox Pattern\n * ```typescript\n * import { createPublisher } from 'autotel-outbox';\n * import { MockOutboxStorage } from 'autotel-outbox/testing';\n *\n * it('should broadcast events to all subscribers', async () => {\n * const mockOutbox = new MockOutboxStorage();\n * const mockSubscriber = new MockEventSubscriber();\n *\n * // Add events to outbox\n * await mockOutbox.writeEvent({\n * id: '1',\n * type: 'order.completed',\n * aggregateId: 'ord_123',\n * aggregateType: 'Order',\n * payload: { amount: 99.99 },\n * createdAt: new Date().toISOString(),\n * publishedAt: null\n * });\n *\n * // Run publisher\n * const publisher = createPublisher(mockOutbox, [mockSubscriber]);\n * await publisher();\n *\n * // Assert event was broadcast\n * expect(mockSubscriber.events).toHaveLength(1);\n * expect(mockSubscriber.events[0].name).toBe('order.completed');\n * });\n * ```\n */\n\nimport type {\n EventSubscriber,\n EventAttributes,\n FunnelStatus,\n OutcomeStatus,\n} from 'autotel/event-subscriber';\n\n/**\n * Captured event data\n */\nexport interface CapturedEvent {\n type: 'event' | 'funnel' | 'outcome' | 'value';\n name: string;\n attributes?: EventAttributes;\n funnel?: string;\n step?: FunnelStatus;\n operation?: string;\n outcome?: OutcomeStatus;\n value?: number;\n timestamp: string;\n}\n\n/**\n * Mock subscriber for testing\n *\n * Captures all events calls in memory for test assertions.\n * Does not make any real API calls.\n */\nexport class MockEventSubscriber implements EventSubscriber {\n readonly name = 'MockEventSubscriber';\n readonly version = '1.0.0';\n\n /**\n * All captured events\n */\n public events: CapturedEvent[] = [];\n\n /**\n * Track if shutdown was called\n */\n public shutdownCalled = false;\n\n async trackEvent(name: string, attributes?: EventAttributes): Promise<void> {\n this.events.push({\n type: 'event',\n name,\n attributes,\n timestamp: new Date().toISOString(),\n });\n }\n\n async trackFunnelStep(\n funnelName: string,\n step: FunnelStatus,\n attributes?: EventAttributes,\n ): Promise<void> {\n this.events.push({\n type: 'funnel',\n name: `${funnelName}.${step}`,\n funnel: funnelName,\n step,\n attributes,\n timestamp: new Date().toISOString(),\n });\n }\n\n async trackOutcome(\n operationName: string,\n outcome: OutcomeStatus,\n attributes?: EventAttributes,\n ): Promise<void> {\n this.events.push({\n type: 'outcome',\n name: `${operationName}.${outcome}`,\n operation: operationName,\n outcome,\n attributes,\n timestamp: new Date().toISOString(),\n });\n }\n\n async trackValue(\n name: string,\n value: number,\n attributes?: EventAttributes,\n ): Promise<void> {\n this.events.push({\n type: 'value',\n name,\n value,\n attributes,\n timestamp: new Date().toISOString(),\n });\n }\n\n async shutdown(): Promise<void> {\n this.shutdownCalled = true;\n }\n\n /**\n * Reset captured events (useful between tests)\n */\n reset(): void {\n this.events = [];\n this.shutdownCalled = false;\n }\n\n /**\n * Get events by type\n */\n getEventsByType(\n type: 'event' | 'funnel' | 'outcome' | 'value',\n ): CapturedEvent[] {\n return this.events.filter((e) => e.type === type);\n }\n\n /**\n * Get events by name\n */\n getEventsByName(name: string): CapturedEvent[] {\n return this.events.filter((e) => e.name === name);\n }\n\n /**\n * Get funnel events for a specific funnel\n */\n getFunnelEvents(funnelName: string): CapturedEvent[] {\n return this.events.filter(\n (e) => e.type === 'funnel' && e.funnel === funnelName,\n );\n }\n\n /**\n * Get outcome events for a specific operation\n */\n getOutcomeEvents(operationName: string): CapturedEvent[] {\n return this.events.filter(\n (e) => e.type === 'outcome' && e.operation === operationName,\n );\n }\n\n /**\n * Get value events by metric name\n */\n getValueEvents(metricName: string): CapturedEvent[] {\n return this.events.filter(\n (e) => e.type === 'value' && e.name === metricName,\n );\n }\n\n /**\n * Assert that an event was tracked\n */\n assertEventTracked(\n name: string,\n attributes?: Partial<EventAttributes>,\n ): void {\n const events = this.getEventsByName(name);\n\n if (events.length === 0) {\n throw new Error(`No events found with name: ${name}`);\n }\n\n if (attributes) {\n const matchingEvent = events.find((event) => {\n if (!event.attributes) return false;\n\n return Object.entries(attributes).every(\n ([key, value]) => event.attributes![key] === value,\n );\n });\n\n if (!matchingEvent) {\n throw new Error(\n `Event \"${name}\" found, but no matching attributes: ${JSON.stringify(attributes)}`,\n );\n }\n }\n }\n\n /**\n * Assert that a funnel step was tracked\n */\n assertFunnelStepTracked(\n funnelName: string,\n step: FunnelStatus,\n attributes?: Partial<EventAttributes>,\n ): void {\n const events = this.getFunnelEvents(funnelName);\n const matchingEvent = events.find((e) => e.step === step);\n\n if (!matchingEvent) {\n throw new Error(`Funnel \"${funnelName}\" step \"${step}\" was not tracked`);\n }\n\n if (attributes) {\n const hasMatchingAttributes = Object.entries(attributes).every(\n ([key, value]) => matchingEvent.attributes?.[key] === value,\n );\n\n if (!hasMatchingAttributes) {\n throw new Error(\n `Funnel step tracked, but attributes don't match: ${JSON.stringify(attributes)}`,\n );\n }\n }\n }\n\n /**\n * Assert that an outcome was tracked\n */\n assertOutcomeTracked(\n operationName: string,\n outcome: OutcomeStatus,\n attributes?: Partial<EventAttributes>,\n ): void {\n const events = this.getOutcomeEvents(operationName);\n const matchingEvent = events.find((e) => e.outcome === outcome);\n\n if (!matchingEvent) {\n throw new Error(`Outcome \"${operationName}.${outcome}\" was not tracked`);\n }\n\n if (attributes) {\n const hasMatchingAttributes = Object.entries(attributes).every(\n ([key, value]) => matchingEvent.attributes?.[key] === value,\n );\n\n if (!hasMatchingAttributes) {\n throw new Error(\n `Outcome tracked, but attributes don't match: ${JSON.stringify(attributes)}`,\n );\n }\n }\n }\n\n /**\n * Pretty print all captured events (useful for debugging)\n */\n printEvents(): void {\n console.log(`\\n[${this.name}] Captured ${this.events.length} events:\\n`);\n for (const [index, event] of this.events.entries()) {\n console.log(`${index + 1}. [${event.type}] ${event.name}`);\n if (event.attributes) {\n console.log(` Attributes:`, event.attributes);\n }\n if (event.value !== undefined) {\n console.log(` Value: ${event.value}`);\n }\n console.log(` Timestamp: ${event.timestamp}\\n`);\n }\n }\n}\n","/**\n * Factory functions for creating events subscribers\n *\n * Function-based alternatives to `new SubscriberClass()` pattern.\n * Provides a consistent API and better tree-shaking.\n *\n * @example\n * ```typescript\n * import { createPostHogSubscriber, createWebhookSubscriber } from 'autotel-subscribers/factories'\n *\n * const events = new Event('my-service', {\n * subscribers: [\n * createPostHogSubscriber({ apiKey: 'phc_...' }),\n * createWebhookSubscriber({ url: 'https://...' })\n * ]\n * })\n * ```\n */\n\nimport { PostHogSubscriber } from './posthog';\nimport { MixpanelSubscriber } from './mixpanel';\nimport { AmplitudeSubscriber } from './amplitude';\nimport { SegmentSubscriber } from './segment';\nimport { WebhookSubscriber } from './webhook';\nimport { SlackSubscriber } from './slack';\nimport { MockEventSubscriber } from './mock-event-subscriber';\n\nimport type {\n EventSubscriber,\n EventAttributes,\n OutcomeStatus,\n FunnelStatus,\n EventTrackingOptions,\n} from 'autotel/event-subscriber';\n\nexport type { PostHogConfig } from './posthog';\nexport type { MixpanelConfig } from './mixpanel';\nexport type { AmplitudeConfig } from './amplitude';\nexport type { SegmentConfig } from './segment';\nexport type { WebhookConfig } from './webhook';\nexport type { SlackSubscriberConfig } from './slack';\n\n/** Create a PostHog events subscriber */\nexport function createPostHogSubscriber(config: {\n apiKey: string;\n host?: string;\n enabled?: boolean;\n}): EventSubscriber {\n return new PostHogSubscriber(config);\n}\n\n/** Create a Mixpanel events subscriber */\nexport function createMixpanelSubscriber(config: {\n token: string;\n enabled?: boolean;\n}): EventSubscriber {\n return new MixpanelSubscriber(config);\n}\n\n/** Create an Amplitude events subscriber */\nexport function createAmplitudeSubscriber(config: {\n apiKey: string;\n enabled?: boolean;\n}): EventSubscriber {\n return new AmplitudeSubscriber(config);\n}\n\n/** Create a Segment events subscriber */\nexport function createSegmentSubscriber(config: {\n writeKey: string;\n enabled?: boolean;\n}): EventSubscriber {\n return new SegmentSubscriber(config);\n}\n\n/** Create a Webhook events subscriber with retry and timeout support */\nexport function createWebhookSubscriber(config: {\n url: string;\n method?: 'POST' | 'PUT';\n headers?: Record<string, string>;\n enabled?: boolean;\n maxRetries?: number;\n timeoutMs?: number;\n retryDelayMs?: number;\n}): EventSubscriber {\n return new WebhookSubscriber(config);\n}\n\n/** Create a Slack events subscriber */\nexport function createSlackSubscriber(config: {\n webhookUrl: string;\n channel?: string;\n enabled?: boolean;\n}): EventSubscriber {\n return new SlackSubscriber(config);\n}\n\n/** Create a mock events subscriber for testing */\nexport function createMockSubscriber(): MockEventSubscriber {\n return new MockEventSubscriber();\n}\n\n/**\n * Strategy for composing multiple subscribers\n *\n * - `parallel`: Send to all subscribers concurrently, fail if any fails\n * - `failover`: Try subscribers in order until one succeeds\n * - `round-robin`: Cycle through subscribers sequentially\n * - `random`: Pick a random subscriber each time\n * - `race`: Send to all, succeed when any succeeds\n * - `mirrored`: Send to primary, mirror to others (primary failure fails all)\n */\nexport type ComposeSubscriberStrategy =\n 'parallel' | 'failover' | 'round-robin' | 'random' | 'race' | 'mirrored';\n\n/** Configuration options for composing multiple subscribers */\nexport type ComposeSubscribersOptions = {\n name?: string;\n strategy?: ComposeSubscriberStrategy;\n maxAttemptsPerSubscriber?: number;\n initialRetryDelayMs?: number;\n maxRetryDelayMs?: number;\n isRetriable?: (error: unknown) => boolean;\n logger?: Pick<Console, 'debug' | 'warn' | 'error'>;\n};\n\ntype SubscriberMethod =\n 'trackEvent' | 'trackFunnelStep' | 'trackOutcome' | 'trackValue';\n\ntype MethodCall = {\n method: SubscriberMethod;\n args: unknown[];\n};\n\nfunction backoffDelay(\n attempt: number,\n initialMs: number,\n maxMs: number,\n): number {\n return Math.min(maxMs, initialMs * 2 ** (attempt - 1));\n}\n\nasync function callSubscriber(\n subscriber: EventSubscriber,\n call: MethodCall,\n): Promise<void> {\n switch (call.method) {\n case 'trackEvent': {\n await subscriber.trackEvent(\n call.args[0] as string,\n call.args[1] as EventAttributes | undefined,\n call.args[2] as EventTrackingOptions | undefined,\n );\n return;\n }\n case 'trackFunnelStep': {\n await subscriber.trackFunnelStep(\n call.args[0] as string,\n call.args[1] as FunnelStatus,\n call.args[2] as EventAttributes | undefined,\n call.args[3] as EventTrackingOptions | undefined,\n );\n return;\n }\n case 'trackOutcome': {\n await subscriber.trackOutcome(\n call.args[0] as string,\n call.args[1] as OutcomeStatus,\n call.args[2] as EventAttributes | undefined,\n call.args[3] as EventTrackingOptions | undefined,\n );\n return;\n }\n case 'trackValue': {\n await subscriber.trackValue(\n call.args[0] as string,\n call.args[1] as number,\n call.args[2] as EventAttributes | undefined,\n call.args[3] as EventTrackingOptions | undefined,\n );\n }\n }\n}\n\n/**\n * Compose multiple subscribers into one with a specified strategy\n *\n * @example\n * ```typescript\n * const multiSubscriber = composeSubscribers(\n * [\n * createPostHogSubscriber({ apiKey: '...' }),\n * createWebhookSubscriber({ url: '...' })\n * ],\n * { strategy: 'parallel' }\n * )\n * ```\n *\n * @param subscribers - Array of subscribers to compose\n * @param options - Configuration for composition strategy and retry behavior\n * @returns A composite EventSubscriber that applies the strategy\n */\nexport function composeSubscribers(\n subscribers: EventSubscriber[],\n options: ComposeSubscribersOptions = {},\n): EventSubscriber {\n const strategy = options.strategy ?? 'parallel';\n const name = options.name ?? `ComposedSubscriber(${strategy})`;\n const maxAttempts = options.maxAttemptsPerSubscriber ?? 1;\n const initialRetryDelayMs = options.initialRetryDelayMs ?? 250;\n const maxRetryDelayMs = options.maxRetryDelayMs ?? 10_000;\n const isRetriable = options.isRetriable ?? (() => true);\n const logger = options.logger ?? console;\n let rrCounter = 0;\n\n const sendOne = async (\n subscriber: EventSubscriber,\n call: MethodCall,\n ): Promise<void> => {\n let lastError: unknown;\n\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n await callSubscriber(subscriber, call);\n return;\n } catch (error) {\n lastError = error;\n const retryable = isRetriable(error);\n logger.warn?.('composeSubscribers attempt failed', {\n subscriber: subscriber.name,\n attempt,\n retryable,\n strategy,\n error,\n });\n\n if (!retryable || attempt >= maxAttempts) break;\n await new Promise((resolve) =>\n setTimeout(\n resolve,\n backoffDelay(attempt, initialRetryDelayMs, maxRetryDelayMs),\n ),\n );\n }\n }\n\n throw lastError instanceof Error ? lastError : new Error(String(lastError));\n };\n\n const orderForSequential = (): number[] => {\n const n = subscribers.length;\n if (n === 0) return [];\n\n if (strategy === 'failover') {\n return Array.from({ length: n }, (_, i) => i);\n }\n\n const start =\n strategy === 'random' ? Math.floor(Math.random() * n) : rrCounter++ % n;\n\n return Array.from({ length: n }, (_, i) => (start + i) % n);\n };\n\n const execute = async (call: MethodCall): Promise<void> => {\n if (subscribers.length === 0) return;\n\n if (strategy === 'parallel') {\n await Promise.all(\n subscribers.map((subscriber) => sendOne(subscriber, call)),\n );\n return;\n }\n\n if (strategy === 'race') {\n const attempts = subscribers.map(async (subscriber) => {\n await sendOne(subscriber, call);\n return subscriber.name ?? 'unknown';\n });\n\n try {\n await Promise.any(attempts);\n } catch (error) {\n if (error instanceof AggregateError && error.errors.length > 0) {\n throw error.errors.at(-1);\n }\n throw error;\n }\n return;\n }\n\n if (strategy === 'mirrored') {\n const primary = subscribers[0];\n if (!primary) return;\n await sendOne(primary, call);\n\n for (const mirror of subscribers.slice(1)) {\n void sendOne(mirror, call).catch((error) => {\n logger.warn?.('composeSubscribers mirror failed', {\n subscriber: mirror.name,\n error,\n });\n });\n }\n return;\n }\n\n const order = orderForSequential();\n let lastError: unknown;\n\n for (const index of order) {\n const subscriber = subscribers[index];\n if (!subscriber) continue;\n\n try {\n await sendOne(subscriber, call);\n return;\n } catch (error) {\n lastError = error;\n }\n }\n\n throw lastError instanceof Error ? lastError : new Error(String(lastError));\n };\n\n return {\n name,\n async trackEvent(name_, attributes, options_) {\n await execute({\n method: 'trackEvent',\n args: [name_, attributes, options_],\n });\n },\n async trackFunnelStep(funnel, step, attributes, options_) {\n await execute({\n method: 'trackFunnelStep',\n args: [funnel, step, attributes, options_],\n });\n },\n async trackOutcome(operation, outcome, attributes, options_) {\n await execute({\n method: 'trackOutcome',\n args: [operation, outcome, attributes, options_],\n });\n },\n async trackValue(name_, value, attributes, options_) {\n await execute({\n method: 'trackValue',\n args: [name_, value, attributes, options_],\n });\n },\n async shutdown() {\n await Promise.all(\n subscribers.map(async (subscriber) => subscriber.shutdown?.()),\n );\n },\n };\n}\n"],"mappings":"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"}
package/dist/file.cjs.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"file.cjs","names":["EventSubscriber","path"],"sources":["../src/file.ts"],"sourcesContent":["/**\n * File subscriber for autotel.\n *\n * Appends each tracked event to a file as newline-delimited JSON (NDJSON).\n * Useful for AI agents, scripts, evals, and local debugging that want\n * structured events on disk without a hosted backend. Query the file with\n * `jq`, load it into a notebook, or feed it to an agent.\n *\n * @example\n * ```typescript\n * import { Event } from 'autotel/event';\n * import { FileSubscriber } from 'autotel-subscribers/file';\n *\n * const events = new Event('worker', {\n * subscribers: [new FileSubscriber({ path: './telemetry/events.ndjson' })],\n * });\n * ```\n */\n\nimport { appendFile, mkdir } from 'node:fs/promises';\nimport path from 'node:path';\nimport { EventSubscriber, type EventPayload } from './event-subscriber-base';\n\nexport interface FileSubscriberConfig {\n /** File path to append newline-delimited JSON events to. */\n path: string;\n /** Enable or disable the subscriber. Default `true`. */\n enabled?: boolean;\n /** Pretty-print each event as indented JSON instead of one line. Default `false`. */\n pretty?: boolean;\n /** Create parent directories if they do not exist. Default `true`. */\n mkdir?: boolean;\n /**\n * Transform a payload before writing. Return `null` to skip the event.\n * Defaults to writing the normalized payload unchanged.\n */\n transform?: (payload: EventPayload) => Record<string, unknown> | null;\n}\n\nexport class FileSubscriber extends EventSubscriber {\n readonly name = 'FileSubscriber';\n readonly version = '1.0.0';\n\n private readonly filePath: string;\n private readonly pretty: boolean;\n private readonly ensureDir: boolean;\n private readonly transform?: (\n payload: EventPayload,\n ) => Record<string, unknown> | null;\n\n /** Serializes writes so concurrent events never interleave on disk. */\n private writeChain: Promise<void> = Promise.resolve();\n private dirEnsured = false;\n\n constructor(config: FileSubscriberConfig) {\n super();\n this.filePath = config.path;\n this.enabled = config.enabled ?? true;\n this.pretty = config.pretty ?? false;\n this.ensureDir = config.mkdir ?? true;\n this.transform = config.transform;\n }\n\n protected async sendToDestination(payload: EventPayload): Promise<void> {\n if (!this.enabled) return;\n\n const record = this.transform ? this.transform(payload) : payload;\n if (record === null) return;\n\n const json = this.pretty\n ? JSON.stringify(record, null, 2)\n : JSON.stringify(record);\n const line = `${json}\\n`;\n\n const run = this.writeChain.then(() => this.write(line));\n // Keep the chain ordered and alive even if one write rejects; the failed\n // write still rejects `run` so the base class can report it.\n this.writeChain = run.catch(() => {});\n await run;\n }\n\n private async write(line: string): Promise<void> {\n if (this.ensureDir && !this.dirEnsured) {\n const dir = path.dirname(this.filePath);\n if (dir && dir !== '.') {\n await mkdir(dir, { recursive: true });\n }\n this.dirEnsured = true;\n }\n await appendFile(this.filePath, line, 'utf8');\n }\n\n override async shutdown(): Promise<void> {\n await this.writeChain;\n await super.shutdown();\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAuCA,IAAa,iBAAb,cAAoCA,8CAAgB;CAClD,AAAS,OAAO;CAChB,AAAS,UAAU;CAEnB,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CACjB,AAAiB;;CAKjB,AAAQ,aAA4B,QAAQ,QAAQ;CACpD,AAAQ,aAAa;CAErB,YAAY,QAA8B;EACxC,MAAM;EACN,KAAK,WAAW,OAAO;EACvB,KAAK,UAAU,OAAO,WAAW;EACjC,KAAK,SAAS,OAAO,UAAU;EAC/B,KAAK,YAAY,OAAO,SAAS;EACjC,KAAK,YAAY,OAAO;CAC1B;CAEA,MAAgB,kBAAkB,SAAsC;EACtE,IAAI,CAAC,KAAK,SAAS;EAEnB,MAAM,SAAS,KAAK,YAAY,KAAK,UAAU,OAAO,IAAI;EAC1D,IAAI,WAAW,MAAM;EAKrB,MAAM,OAAO,GAHA,KAAK,SACd,KAAK,UAAU,QAAQ,MAAM,CAAC,IAC9B,KAAK,UAAU,MAAM,EACJ;EAErB,MAAM,MAAM,KAAK,WAAW,WAAW,KAAK,MAAM,IAAI,CAAC;EAGvD,KAAK,aAAa,IAAI,YAAY,CAAC,CAAC;EACpC,MAAM;CACR;CAEA,MAAc,MAAM,MAA6B;EAC/C,IAAI,KAAK,aAAa,CAAC,KAAK,YAAY;GACtC,MAAM,MAAMC,kBAAK,QAAQ,KAAK,QAAQ;GACtC,IAAI,OAAO,QAAQ,KACjB,kCAAY,KAAK,EAAE,WAAW,KAAK,CAAC;GAEtC,KAAK,aAAa;EACpB;EACA,uCAAiB,KAAK,UAAU,MAAM,MAAM;CAC9C;CAEA,MAAe,WAA0B;EACvC,MAAM,KAAK;EACX,MAAM,MAAM,SAAS;CACvB;AACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"file.d.cts","names":[],"sources":["../src/file.ts"],"mappings":";;UAuBiB;;EAEf;;EAEA;;EAEA;;EAEA;;;;;EAKA,aAAa,SAAS,iBAAiB;;cAG5B,uBAAuB;WACzB;WACA;mBAEQ;mBACA;mBACA;mBACA;;UAKT;UACA;EAEI,YAAA,QAAQ;YASJ,kBAAkB,SAAS,eAAe;UAkB5C;EAWC,YAAY"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"file.d.ts","names":[],"sources":["../src/file.ts"],"mappings":";;UAuBiB;;EAEf;;EAEA;;EAEA;;EAEA;;;;;EAKA,aAAa,SAAS,iBAAiB;;cAG5B,uBAAuB;WACzB;WACA;mBAEQ;mBACA;mBACA;mBACA;;UAKT;UACA;EAEI,YAAA,QAAQ;YASJ,kBAAkB,SAAS,eAAe;UAkB5C;EAWC,YAAY"}
package/dist/file.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"file.js","names":[],"sources":["../src/file.ts"],"sourcesContent":["/**\n * File subscriber for autotel.\n *\n * Appends each tracked event to a file as newline-delimited JSON (NDJSON).\n * Useful for AI agents, scripts, evals, and local debugging that want\n * structured events on disk without a hosted backend. Query the file with\n * `jq`, load it into a notebook, or feed it to an agent.\n *\n * @example\n * ```typescript\n * import { Event } from 'autotel/event';\n * import { FileSubscriber } from 'autotel-subscribers/file';\n *\n * const events = new Event('worker', {\n * subscribers: [new FileSubscriber({ path: './telemetry/events.ndjson' })],\n * });\n * ```\n */\n\nimport { appendFile, mkdir } from 'node:fs/promises';\nimport path from 'node:path';\nimport { EventSubscriber, type EventPayload } from './event-subscriber-base';\n\nexport interface FileSubscriberConfig {\n /** File path to append newline-delimited JSON events to. */\n path: string;\n /** Enable or disable the subscriber. Default `true`. */\n enabled?: boolean;\n /** Pretty-print each event as indented JSON instead of one line. Default `false`. */\n pretty?: boolean;\n /** Create parent directories if they do not exist. Default `true`. */\n mkdir?: boolean;\n /**\n * Transform a payload before writing. Return `null` to skip the event.\n * Defaults to writing the normalized payload unchanged.\n */\n transform?: (payload: EventPayload) => Record<string, unknown> | null;\n}\n\nexport class FileSubscriber extends EventSubscriber {\n readonly name = 'FileSubscriber';\n readonly version = '1.0.0';\n\n private readonly filePath: string;\n private readonly pretty: boolean;\n private readonly ensureDir: boolean;\n private readonly transform?: (\n payload: EventPayload,\n ) => Record<string, unknown> | null;\n\n /** Serializes writes so concurrent events never interleave on disk. */\n private writeChain: Promise<void> = Promise.resolve();\n private dirEnsured = false;\n\n constructor(config: FileSubscriberConfig) {\n super();\n this.filePath = config.path;\n this.enabled = config.enabled ?? true;\n this.pretty = config.pretty ?? false;\n this.ensureDir = config.mkdir ?? true;\n this.transform = config.transform;\n }\n\n protected async sendToDestination(payload: EventPayload): Promise<void> {\n if (!this.enabled) return;\n\n const record = this.transform ? this.transform(payload) : payload;\n if (record === null) return;\n\n const json = this.pretty\n ? JSON.stringify(record, null, 2)\n : JSON.stringify(record);\n const line = `${json}\\n`;\n\n const run = this.writeChain.then(() => this.write(line));\n // Keep the chain ordered and alive even if one write rejects; the failed\n // write still rejects `run` so the base class can report it.\n this.writeChain = run.catch(() => {});\n await run;\n }\n\n private async write(line: string): Promise<void> {\n if (this.ensureDir && !this.dirEnsured) {\n const dir = path.dirname(this.filePath);\n if (dir && dir !== '.') {\n await mkdir(dir, { recursive: true });\n }\n this.dirEnsured = true;\n }\n await appendFile(this.filePath, line, 'utf8');\n }\n\n override async shutdown(): Promise<void> {\n await this.writeChain;\n await super.shutdown();\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAuCA,IAAa,iBAAb,cAAoC,gBAAgB;CAClD,AAAS,OAAO;CAChB,AAAS,UAAU;CAEnB,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CACjB,AAAiB;;CAKjB,AAAQ,aAA4B,QAAQ,QAAQ;CACpD,AAAQ,aAAa;CAErB,YAAY,QAA8B;EACxC,MAAM;EACN,KAAK,WAAW,OAAO;EACvB,KAAK,UAAU,OAAO,WAAW;EACjC,KAAK,SAAS,OAAO,UAAU;EAC/B,KAAK,YAAY,OAAO,SAAS;EACjC,KAAK,YAAY,OAAO;CAC1B;CAEA,MAAgB,kBAAkB,SAAsC;EACtE,IAAI,CAAC,KAAK,SAAS;EAEnB,MAAM,SAAS,KAAK,YAAY,KAAK,UAAU,OAAO,IAAI;EAC1D,IAAI,WAAW,MAAM;EAKrB,MAAM,OAAO,GAHA,KAAK,SACd,KAAK,UAAU,QAAQ,MAAM,CAAC,IAC9B,KAAK,UAAU,MAAM,EACJ;EAErB,MAAM,MAAM,KAAK,WAAW,WAAW,KAAK,MAAM,IAAI,CAAC;EAGvD,KAAK,aAAa,IAAI,YAAY,CAAC,CAAC;EACpC,MAAM;CACR;CAEA,MAAc,MAAM,MAA6B;EAC/C,IAAI,KAAK,aAAa,CAAC,KAAK,YAAY;GACtC,MAAM,MAAM,KAAK,QAAQ,KAAK,QAAQ;GACtC,IAAI,OAAO,QAAQ,KACjB,MAAM,MAAM,KAAK,EAAE,WAAW,KAAK,CAAC;GAEtC,KAAK,aAAa;EACpB;EACA,MAAM,WAAW,KAAK,UAAU,MAAM,MAAM;CAC9C;CAEA,MAAe,WAA0B;EACvC,MAAM,KAAK;EACX,MAAM,MAAM,SAAS;CACvB;AACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.cjs","names":["EventSubscriber"],"sources":["../src/streaming-event-subscriber.ts"],"sourcesContent":["/**\n * Streaming Events Subscriber Base Class\n *\n * Specialized base class for high-throughput streaming platforms like\n * Kafka, Kinesis, Pub/Sub, etc.\n *\n * Extends EventSubscriber with streaming-specific features:\n * - Partitioning strategy for ordered delivery\n * - Buffer overflow handling (drop/block/disk)\n * - High-throughput optimizations\n * - Backpressure signaling\n *\n * @example Kafka Streaming Subscriber\n * ```typescript\n * import { StreamingEventSubscriber } from 'autotel-subscribers/streaming-event-subscriber';\n *\n * class KafkaSubscriber extends StreamingEventSubscriber {\n * name = 'KafkaSubscriber';\n * version = '1.0.0';\n *\n * constructor(config: KafkaConfig) {\n * super({\n * maxBufferSize: 10000,\n * bufferOverflowStrategy: 'block',\n * maxBatchSize: 500\n * });\n * }\n *\n * protected getPartitionKey(payload: EventPayload): string {\n * // Partition by userId for ordered events per user\n * return payload.attributes?.userId || 'default';\n * }\n *\n * protected async sendBatch(events: EventPayload[]): Promise<void> {\n * await this.producer.send({\n * topic: this.topic,\n * messages: events.map(e => ({\n * key: this.getPartitionKey(e),\n * value: JSON.stringify(e)\n * }))\n * });\n * }\n * }\n * ```\n */\n\nimport { EventSubscriber, type EventPayload } from './event-subscriber-base';\n\n/**\n * Buffer overflow strategy\n *\n * - 'drop': Drop new events when buffer is full (prevents blocking, but loses data)\n * - 'block': Wait for space in buffer (backpressure, may slow application)\n * - 'disk': Spill to disk when memory buffer full (reliable, but complex - not implemented yet)\n */\nexport type BufferOverflowStrategy = 'drop' | 'block' | 'disk';\n\n/**\n * Streaming subscriber configuration\n */\nexport interface StreamingSubscriberConfig {\n /** Maximum buffer size before triggering overflow strategy (default: 10000) */\n maxBufferSize?: number;\n\n /** Strategy when buffer is full (default: 'block') */\n bufferOverflowStrategy?: BufferOverflowStrategy;\n\n /** Maximum batch size for sending (default: 500) */\n maxBatchSize?: number;\n\n /** Flush interval in milliseconds (default: 1000) */\n flushIntervalMs?: number;\n\n /** Enable compression (default: false) */\n compressionEnabled?: boolean;\n}\n\n/**\n * Buffer status for monitoring\n */\nexport interface BufferStatus {\n /** Current number of events in buffer */\n size: number;\n\n /** Maximum capacity */\n capacity: number;\n\n /** Utilization percentage (0-100) */\n utilization: number;\n\n /** Is buffer near full (>80%) */\n isNearFull: boolean;\n\n /** Is buffer full (100%) */\n isFull: boolean;\n}\n\n/**\n * Streaming Events Subscriber Base Class\n *\n * Provides streaming-specific patterns on top of EventSubscriber.\n */\nexport abstract class StreamingEventSubscriber extends EventSubscriber {\n protected config: Required<StreamingSubscriberConfig>;\n protected buffer: EventPayload[] = [];\n protected flushIntervalHandle: NodeJS.Timeout | null = null;\n private isShuttingDown = false;\n\n constructor(config: StreamingSubscriberConfig = {}) {\n super();\n\n // Set defaults\n this.config = {\n maxBufferSize: config.maxBufferSize ?? 10_000,\n bufferOverflowStrategy: config.bufferOverflowStrategy ?? 'block',\n maxBatchSize: config.maxBatchSize ?? 500,\n flushIntervalMs: config.flushIntervalMs ?? 1000,\n compressionEnabled: config.compressionEnabled ?? false,\n };\n\n // Start periodic flushing\n this.startFlushInterval();\n }\n\n /**\n * Get partition key for event\n *\n * Override this to implement your partitioning strategy.\n * Events with the same partition key go to the same partition/shard.\n *\n * Common strategies:\n * - By userId: Ordered events per user\n * - By tenantId: Isolate tenants\n * - By eventType: Group similar events\n * - Round-robin: Load balancing\n *\n * @param payload - Event payload\n * @returns Partition key (string)\n *\n * @example Partition by userId\n * ```typescript\n * protected getPartitionKey(payload: EventPayload): string {\n * return payload.attributes?.userId || 'default';\n * }\n * ```\n */\n protected abstract getPartitionKey(payload: EventPayload): string;\n\n /**\n * Send batch of events to streaming platform\n *\n * Override this to implement platform-specific batch sending.\n * Called when buffer reaches maxBatchSize or flush interval triggers.\n *\n * @param events - Batch of events to send\n */\n protected abstract sendBatch(events: EventPayload[]): Promise<void>;\n\n /**\n * Send single event to destination (from EventSubscriber)\n *\n * This buffers events and sends in batches for performance.\n * Override sendBatch() instead of this method.\n */\n protected async sendToDestination(payload: EventPayload): Promise<void> {\n // Check buffer capacity before adding\n await this.ensureBufferCapacity();\n\n // Add to buffer\n this.buffer.push(payload);\n\n // Auto-flush if batch size reached\n if (this.buffer.length >= this.config.maxBatchSize) {\n await this.flushBuffer();\n }\n }\n\n /**\n * Ensure buffer has capacity for new event\n *\n * Implements buffer overflow strategy:\n * - 'drop': Returns immediately (event will be added, oldest may be dropped)\n * - 'block': Waits until space available (backpressure)\n * - 'disk': Not implemented yet (would spill to disk)\n */\n private async ensureBufferCapacity(): Promise<void> {\n if (this.buffer.length < this.config.maxBufferSize) {\n return; // Has space\n }\n\n // Buffer is full - apply overflow strategy\n switch (this.config.bufferOverflowStrategy) {\n case 'drop': {\n // Drop oldest event to make space\n this.buffer.shift();\n console.warn(\n `[${this.name}] Buffer full (${this.config.maxBufferSize}), dropped oldest event`,\n );\n break;\n }\n\n case 'block': {\n // Wait for flush to complete (backpressure)\n console.warn(\n `[${this.name}] Buffer full (${this.config.maxBufferSize}), blocking until space available`,\n );\n await this.flushBuffer();\n\n // If still full after flush, wait a bit and retry\n if (this.buffer.length >= this.config.maxBufferSize) {\n await new Promise((resolve) => setTimeout(resolve, 100));\n await this.ensureBufferCapacity(); // Recursive retry\n }\n break;\n }\n\n case 'disk': {\n throw new Error(\n `[${this.name}] Disk overflow strategy not implemented yet`,\n );\n }\n }\n }\n\n /**\n * Flush buffer to destination\n */\n private async flushBuffer(): Promise<void> {\n if (this.buffer.length === 0) return;\n\n const batch = [...this.buffer];\n this.buffer = [];\n\n try {\n await this.sendBatch(batch);\n } catch (error) {\n console.error(\n `[${this.name}] Failed to send batch of ${batch.length} events:`,\n error,\n );\n\n // On failure, put events back in buffer (at front)\n this.buffer.unshift(...batch);\n\n // If we're near capacity, we need to make a decision\n if (\n this.buffer.length >= this.config.maxBufferSize * 0.9 &&\n this.config.bufferOverflowStrategy === 'drop'\n ) {\n // Drop oldest to prevent runaway growth\n const toDrop = Math.floor(this.config.maxBufferSize * 0.1);\n this.buffer.splice(0, toDrop);\n console.warn(\n `[${this.name}] After failed flush, dropped ${toDrop} oldest events to prevent overflow`,\n );\n }\n }\n }\n\n /**\n * Start periodic flushing\n */\n private startFlushInterval(): void {\n this.flushIntervalHandle = setInterval(() => {\n if (!this.isShuttingDown) {\n void this.flushBuffer();\n }\n }, this.config.flushIntervalMs);\n }\n\n /**\n * Get current buffer status (for monitoring/observability)\n */\n public getBufferStatus(): BufferStatus {\n const size = this.buffer.length;\n const capacity = this.config.maxBufferSize;\n const utilization = Math.round((size / capacity) * 100);\n\n return {\n size,\n capacity,\n utilization,\n isNearFull: utilization > 80,\n isFull: utilization >= 100,\n };\n }\n\n /**\n * Shutdown with proper buffer draining\n */\n async shutdown(): Promise<void> {\n this.isShuttingDown = true;\n\n // Stop flush interval (no more automatic flushes)\n if (this.flushIntervalHandle) {\n clearInterval(this.flushIntervalHandle);\n this.flushIntervalHandle = null;\n }\n\n // Call parent shutdown FIRST to:\n // 1. Set enabled = false (stop accepting new events)\n // 2. Drain any pending sendToDestination() calls\n await super.shutdown();\n\n // THEN flush remaining buffer\n // (no new events can arrive after super.shutdown() disabled the subscriber)\n await this.flushBuffer();\n }\n\n /**\n * Optional: Compress payload before sending\n *\n * Override this if your streaming platform supports compression.\n * Only called if compressionEnabled = true.\n */\n protected async compressPayload(payload: string): Promise<Buffer | string> {\n // Default: no compression\n // Override with gzip, snappy, lz4, etc.\n return payload;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsGA,IAAsB,2BAAtB,cAAuDA,8CAAgB;CACrE,AAAU;CACV,AAAU,SAAyB,CAAC;CACpC,AAAU,sBAA6C;CACvD,AAAQ,iBAAiB;CAEzB,YAAY,SAAoC,CAAC,GAAG;EAClD,MAAM;EAGN,KAAK,SAAS;GACZ,eAAe,OAAO,iBAAiB;GACvC,wBAAwB,OAAO,0BAA0B;GACzD,cAAc,OAAO,gBAAgB;GACrC,iBAAiB,OAAO,mBAAmB;GAC3C,oBAAoB,OAAO,sBAAsB;EACnD;EAGA,KAAK,mBAAmB;CAC1B;;;;;;;CA0CA,MAAgB,kBAAkB,SAAsC;EAEtE,MAAM,KAAK,qBAAqB;EAGhC,KAAK,OAAO,KAAK,OAAO;EAGxB,IAAI,KAAK,OAAO,UAAU,KAAK,OAAO,cACpC,MAAM,KAAK,YAAY;CAE3B;;;;;;;;;CAUA,MAAc,uBAAsC;EAClD,IAAI,KAAK,OAAO,SAAS,KAAK,OAAO,eACnC;EAIF,QAAQ,KAAK,OAAO,wBAApB;GACE,KAAK;IAEH,KAAK,OAAO,MAAM;IAClB,QAAQ,KACN,IAAI,KAAK,KAAK,iBAAiB,KAAK,OAAO,cAAc,wBAC3D;IACA;GAGF,KAAK;IAEH,QAAQ,KACN,IAAI,KAAK,KAAK,iBAAiB,KAAK,OAAO,cAAc,kCAC3D;IACA,MAAM,KAAK,YAAY;IAGvB,IAAI,KAAK,OAAO,UAAU,KAAK,OAAO,eAAe;KACnD,MAAM,IAAI,SAAS,YAAY,WAAW,SAAS,GAAG,CAAC;KACvD,MAAM,KAAK,qBAAqB;IAClC;IACA;GAGF,KAAK,QACH,MAAM,IAAI,MACR,IAAI,KAAK,KAAK,6CAChB;EAEJ;CACF;;;;CAKA,MAAc,cAA6B;EACzC,IAAI,KAAK,OAAO,WAAW,GAAG;EAE9B,MAAM,QAAQ,CAAC,GAAG,KAAK,MAAM;EAC7B,KAAK,SAAS,CAAC;EAEf,IAAI;GACF,MAAM,KAAK,UAAU,KAAK;EAC5B,SAAS,OAAO;GACd,QAAQ,MACN,IAAI,KAAK,KAAK,4BAA4B,MAAM,OAAO,WACvD,KACF;GAGA,KAAK,OAAO,QAAQ,GAAG,KAAK;GAG5B,IACE,KAAK,OAAO,UAAU,KAAK,OAAO,gBAAgB,MAClD,KAAK,OAAO,2BAA2B,QACvC;IAEA,MAAM,SAAS,KAAK,MAAM,KAAK,OAAO,gBAAgB,EAAG;IACzD,KAAK,OAAO,OAAO,GAAG,MAAM;IAC5B,QAAQ,KACN,IAAI,KAAK,KAAK,gCAAgC,OAAO,mCACvD;GACF;EACF;CACF;;;;CAKA,AAAQ,qBAA2B;EACjC,KAAK,sBAAsB,kBAAkB;GAC3C,IAAI,CAAC,KAAK,gBACR,AAAK,KAAK,YAAY;EAE1B,GAAG,KAAK,OAAO,eAAe;CAChC;;;;CAKA,AAAO,kBAAgC;EACrC,MAAM,OAAO,KAAK,OAAO;EACzB,MAAM,WAAW,KAAK,OAAO;EAC7B,MAAM,cAAc,KAAK,MAAO,OAAO,WAAY,GAAG;EAEtD,OAAO;GACL;GACA;GACA;GACA,YAAY,cAAc;GAC1B,QAAQ,eAAe;EACzB;CACF;;;;CAKA,MAAM,WAA0B;EAC9B,KAAK,iBAAiB;EAGtB,IAAI,KAAK,qBAAqB;GAC5B,cAAc,KAAK,mBAAmB;GACtC,KAAK,sBAAsB;EAC7B;EAKA,MAAM,MAAM,SAAS;EAIrB,MAAM,KAAK,YAAY;CACzB;;;;;;;CAQA,MAAgB,gBAAgB,SAA2C;EAGzE,OAAO;CACT;AACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../src/streaming-event-subscriber.ts"],"mappings":";;;;;;;;;;;;;;;;;;KAuDY;;;;UAKK;;EAEf;;EAGA,yBAAyB;;EAGzB;;EAGA;;EAGA;;;;;UAMe;;EAEf;;EAGA;;EAGA;;EAGA;;EAGA;;;;;;;uBAQoB,iCAAiC;YAC3C,QAAQ,SAAS;YACjB,QAAQ;YACR,qBAAqB,OAAO;UAC9B;EAEI,YAAA,SAAQ;;;;;;;;;;;;;;;;;;;;;;;qBAsCD,gBAAgB,SAAS;;;;;;;;;qBAUzB,UAAU,QAAQ,iBAAiB;;;;;;;YAQtC,kBAAkB,SAAS,eAAe;;;;;;;;;UAqB5C;;;;UA0CA;;;;UAmCN;;;;EAWD,mBAAmB;;;;EAiBpB,YAAY;;;;;;;YAyBF,gBAAgB,kBAAkB,QAAQ"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/streaming-event-subscriber.ts"],"mappings":";;;;;;;;;;;;;;;;;;KAuDY;;;;UAKK;;EAEf;;EAGA,yBAAyB;;EAGzB;;EAGA;;EAGA;;;;;UAMe;;EAEf;;EAGA;;EAGA;;EAGA;;EAGA;;;;;;;uBAQoB,iCAAiC;YAC3C,QAAQ,SAAS;YACjB,QAAQ;YACR,qBAAqB,OAAO;UAC9B;EAEI,YAAA,SAAQ;;;;;;;;;;;;;;;;;;;;;;;qBAsCD,gBAAgB,SAAS;;;;;;;;;qBAUzB,UAAU,QAAQ,iBAAiB;;;;;;;YAQtC,kBAAkB,SAAS,eAAe;;;;;;;;;UAqB5C;;;;UA0CA;;;;UAmCN;;;;EAWD,mBAAmB;;;;EAiBpB,YAAY;;;;;;;YAyBF,gBAAgB,kBAAkB,QAAQ"}
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/streaming-event-subscriber.ts"],"sourcesContent":["/**\n * Streaming Events Subscriber Base Class\n *\n * Specialized base class for high-throughput streaming platforms like\n * Kafka, Kinesis, Pub/Sub, etc.\n *\n * Extends EventSubscriber with streaming-specific features:\n * - Partitioning strategy for ordered delivery\n * - Buffer overflow handling (drop/block/disk)\n * - High-throughput optimizations\n * - Backpressure signaling\n *\n * @example Kafka Streaming Subscriber\n * ```typescript\n * import { StreamingEventSubscriber } from 'autotel-subscribers/streaming-event-subscriber';\n *\n * class KafkaSubscriber extends StreamingEventSubscriber {\n * name = 'KafkaSubscriber';\n * version = '1.0.0';\n *\n * constructor(config: KafkaConfig) {\n * super({\n * maxBufferSize: 10000,\n * bufferOverflowStrategy: 'block',\n * maxBatchSize: 500\n * });\n * }\n *\n * protected getPartitionKey(payload: EventPayload): string {\n * // Partition by userId for ordered events per user\n * return payload.attributes?.userId || 'default';\n * }\n *\n * protected async sendBatch(events: EventPayload[]): Promise<void> {\n * await this.producer.send({\n * topic: this.topic,\n * messages: events.map(e => ({\n * key: this.getPartitionKey(e),\n * value: JSON.stringify(e)\n * }))\n * });\n * }\n * }\n * ```\n */\n\nimport { EventSubscriber, type EventPayload } from './event-subscriber-base';\n\n/**\n * Buffer overflow strategy\n *\n * - 'drop': Drop new events when buffer is full (prevents blocking, but loses data)\n * - 'block': Wait for space in buffer (backpressure, may slow application)\n * - 'disk': Spill to disk when memory buffer full (reliable, but complex - not implemented yet)\n */\nexport type BufferOverflowStrategy = 'drop' | 'block' | 'disk';\n\n/**\n * Streaming subscriber configuration\n */\nexport interface StreamingSubscriberConfig {\n /** Maximum buffer size before triggering overflow strategy (default: 10000) */\n maxBufferSize?: number;\n\n /** Strategy when buffer is full (default: 'block') */\n bufferOverflowStrategy?: BufferOverflowStrategy;\n\n /** Maximum batch size for sending (default: 500) */\n maxBatchSize?: number;\n\n /** Flush interval in milliseconds (default: 1000) */\n flushIntervalMs?: number;\n\n /** Enable compression (default: false) */\n compressionEnabled?: boolean;\n}\n\n/**\n * Buffer status for monitoring\n */\nexport interface BufferStatus {\n /** Current number of events in buffer */\n size: number;\n\n /** Maximum capacity */\n capacity: number;\n\n /** Utilization percentage (0-100) */\n utilization: number;\n\n /** Is buffer near full (>80%) */\n isNearFull: boolean;\n\n /** Is buffer full (100%) */\n isFull: boolean;\n}\n\n/**\n * Streaming Events Subscriber Base Class\n *\n * Provides streaming-specific patterns on top of EventSubscriber.\n */\nexport abstract class StreamingEventSubscriber extends EventSubscriber {\n protected config: Required<StreamingSubscriberConfig>;\n protected buffer: EventPayload[] = [];\n protected flushIntervalHandle: NodeJS.Timeout | null = null;\n private isShuttingDown = false;\n\n constructor(config: StreamingSubscriberConfig = {}) {\n super();\n\n // Set defaults\n this.config = {\n maxBufferSize: config.maxBufferSize ?? 10_000,\n bufferOverflowStrategy: config.bufferOverflowStrategy ?? 'block',\n maxBatchSize: config.maxBatchSize ?? 500,\n flushIntervalMs: config.flushIntervalMs ?? 1000,\n compressionEnabled: config.compressionEnabled ?? false,\n };\n\n // Start periodic flushing\n this.startFlushInterval();\n }\n\n /**\n * Get partition key for event\n *\n * Override this to implement your partitioning strategy.\n * Events with the same partition key go to the same partition/shard.\n *\n * Common strategies:\n * - By userId: Ordered events per user\n * - By tenantId: Isolate tenants\n * - By eventType: Group similar events\n * - Round-robin: Load balancing\n *\n * @param payload - Event payload\n * @returns Partition key (string)\n *\n * @example Partition by userId\n * ```typescript\n * protected getPartitionKey(payload: EventPayload): string {\n * return payload.attributes?.userId || 'default';\n * }\n * ```\n */\n protected abstract getPartitionKey(payload: EventPayload): string;\n\n /**\n * Send batch of events to streaming platform\n *\n * Override this to implement platform-specific batch sending.\n * Called when buffer reaches maxBatchSize or flush interval triggers.\n *\n * @param events - Batch of events to send\n */\n protected abstract sendBatch(events: EventPayload[]): Promise<void>;\n\n /**\n * Send single event to destination (from EventSubscriber)\n *\n * This buffers events and sends in batches for performance.\n * Override sendBatch() instead of this method.\n */\n protected async sendToDestination(payload: EventPayload): Promise<void> {\n // Check buffer capacity before adding\n await this.ensureBufferCapacity();\n\n // Add to buffer\n this.buffer.push(payload);\n\n // Auto-flush if batch size reached\n if (this.buffer.length >= this.config.maxBatchSize) {\n await this.flushBuffer();\n }\n }\n\n /**\n * Ensure buffer has capacity for new event\n *\n * Implements buffer overflow strategy:\n * - 'drop': Returns immediately (event will be added, oldest may be dropped)\n * - 'block': Waits until space available (backpressure)\n * - 'disk': Not implemented yet (would spill to disk)\n */\n private async ensureBufferCapacity(): Promise<void> {\n if (this.buffer.length < this.config.maxBufferSize) {\n return; // Has space\n }\n\n // Buffer is full - apply overflow strategy\n switch (this.config.bufferOverflowStrategy) {\n case 'drop': {\n // Drop oldest event to make space\n this.buffer.shift();\n console.warn(\n `[${this.name}] Buffer full (${this.config.maxBufferSize}), dropped oldest event`,\n );\n break;\n }\n\n case 'block': {\n // Wait for flush to complete (backpressure)\n console.warn(\n `[${this.name}] Buffer full (${this.config.maxBufferSize}), blocking until space available`,\n );\n await this.flushBuffer();\n\n // If still full after flush, wait a bit and retry\n if (this.buffer.length >= this.config.maxBufferSize) {\n await new Promise((resolve) => setTimeout(resolve, 100));\n await this.ensureBufferCapacity(); // Recursive retry\n }\n break;\n }\n\n case 'disk': {\n throw new Error(\n `[${this.name}] Disk overflow strategy not implemented yet`,\n );\n }\n }\n }\n\n /**\n * Flush buffer to destination\n */\n private async flushBuffer(): Promise<void> {\n if (this.buffer.length === 0) return;\n\n const batch = [...this.buffer];\n this.buffer = [];\n\n try {\n await this.sendBatch(batch);\n } catch (error) {\n console.error(\n `[${this.name}] Failed to send batch of ${batch.length} events:`,\n error,\n );\n\n // On failure, put events back in buffer (at front)\n this.buffer.unshift(...batch);\n\n // If we're near capacity, we need to make a decision\n if (\n this.buffer.length >= this.config.maxBufferSize * 0.9 &&\n this.config.bufferOverflowStrategy === 'drop'\n ) {\n // Drop oldest to prevent runaway growth\n const toDrop = Math.floor(this.config.maxBufferSize * 0.1);\n this.buffer.splice(0, toDrop);\n console.warn(\n `[${this.name}] After failed flush, dropped ${toDrop} oldest events to prevent overflow`,\n );\n }\n }\n }\n\n /**\n * Start periodic flushing\n */\n private startFlushInterval(): void {\n this.flushIntervalHandle = setInterval(() => {\n if (!this.isShuttingDown) {\n void this.flushBuffer();\n }\n }, this.config.flushIntervalMs);\n }\n\n /**\n * Get current buffer status (for monitoring/observability)\n */\n public getBufferStatus(): BufferStatus {\n const size = this.buffer.length;\n const capacity = this.config.maxBufferSize;\n const utilization = Math.round((size / capacity) * 100);\n\n return {\n size,\n capacity,\n utilization,\n isNearFull: utilization > 80,\n isFull: utilization >= 100,\n };\n }\n\n /**\n * Shutdown with proper buffer draining\n */\n async shutdown(): Promise<void> {\n this.isShuttingDown = true;\n\n // Stop flush interval (no more automatic flushes)\n if (this.flushIntervalHandle) {\n clearInterval(this.flushIntervalHandle);\n this.flushIntervalHandle = null;\n }\n\n // Call parent shutdown FIRST to:\n // 1. Set enabled = false (stop accepting new events)\n // 2. Drain any pending sendToDestination() calls\n await super.shutdown();\n\n // THEN flush remaining buffer\n // (no new events can arrive after super.shutdown() disabled the subscriber)\n await this.flushBuffer();\n }\n\n /**\n * Optional: Compress payload before sending\n *\n * Override this if your streaming platform supports compression.\n * Only called if compressionEnabled = true.\n */\n protected async compressPayload(payload: string): Promise<Buffer | string> {\n // Default: no compression\n // Override with gzip, snappy, lz4, etc.\n return payload;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsGA,IAAsB,2BAAtB,cAAuD,gBAAgB;CACrE,AAAU;CACV,AAAU,SAAyB,CAAC;CACpC,AAAU,sBAA6C;CACvD,AAAQ,iBAAiB;CAEzB,YAAY,SAAoC,CAAC,GAAG;EAClD,MAAM;EAGN,KAAK,SAAS;GACZ,eAAe,OAAO,iBAAiB;GACvC,wBAAwB,OAAO,0BAA0B;GACzD,cAAc,OAAO,gBAAgB;GACrC,iBAAiB,OAAO,mBAAmB;GAC3C,oBAAoB,OAAO,sBAAsB;EACnD;EAGA,KAAK,mBAAmB;CAC1B;;;;;;;CA0CA,MAAgB,kBAAkB,SAAsC;EAEtE,MAAM,KAAK,qBAAqB;EAGhC,KAAK,OAAO,KAAK,OAAO;EAGxB,IAAI,KAAK,OAAO,UAAU,KAAK,OAAO,cACpC,MAAM,KAAK,YAAY;CAE3B;;;;;;;;;CAUA,MAAc,uBAAsC;EAClD,IAAI,KAAK,OAAO,SAAS,KAAK,OAAO,eACnC;EAIF,QAAQ,KAAK,OAAO,wBAApB;GACE,KAAK;IAEH,KAAK,OAAO,MAAM;IAClB,QAAQ,KACN,IAAI,KAAK,KAAK,iBAAiB,KAAK,OAAO,cAAc,wBAC3D;IACA;GAGF,KAAK;IAEH,QAAQ,KACN,IAAI,KAAK,KAAK,iBAAiB,KAAK,OAAO,cAAc,kCAC3D;IACA,MAAM,KAAK,YAAY;IAGvB,IAAI,KAAK,OAAO,UAAU,KAAK,OAAO,eAAe;KACnD,MAAM,IAAI,SAAS,YAAY,WAAW,SAAS,GAAG,CAAC;KACvD,MAAM,KAAK,qBAAqB;IAClC;IACA;GAGF,KAAK,QACH,MAAM,IAAI,MACR,IAAI,KAAK,KAAK,6CAChB;EAEJ;CACF;;;;CAKA,MAAc,cAA6B;EACzC,IAAI,KAAK,OAAO,WAAW,GAAG;EAE9B,MAAM,QAAQ,CAAC,GAAG,KAAK,MAAM;EAC7B,KAAK,SAAS,CAAC;EAEf,IAAI;GACF,MAAM,KAAK,UAAU,KAAK;EAC5B,SAAS,OAAO;GACd,QAAQ,MACN,IAAI,KAAK,KAAK,4BAA4B,MAAM,OAAO,WACvD,KACF;GAGA,KAAK,OAAO,QAAQ,GAAG,KAAK;GAG5B,IACE,KAAK,OAAO,UAAU,KAAK,OAAO,gBAAgB,MAClD,KAAK,OAAO,2BAA2B,QACvC;IAEA,MAAM,SAAS,KAAK,MAAM,KAAK,OAAO,gBAAgB,EAAG;IACzD,KAAK,OAAO,OAAO,GAAG,MAAM;IAC5B,QAAQ,KACN,IAAI,KAAK,KAAK,gCAAgC,OAAO,mCACvD;GACF;EACF;CACF;;;;CAKA,AAAQ,qBAA2B;EACjC,KAAK,sBAAsB,kBAAkB;GAC3C,IAAI,CAAC,KAAK,gBACR,AAAK,KAAK,YAAY;EAE1B,GAAG,KAAK,OAAO,eAAe;CAChC;;;;CAKA,AAAO,kBAAgC;EACrC,MAAM,OAAO,KAAK,OAAO;EACzB,MAAM,WAAW,KAAK,OAAO;EAC7B,MAAM,cAAc,KAAK,MAAO,OAAO,WAAY,GAAG;EAEtD,OAAO;GACL;GACA;GACA;GACA,YAAY,cAAc;GAC1B,QAAQ,eAAe;EACzB;CACF;;;;CAKA,MAAM,WAA0B;EAC9B,KAAK,iBAAiB;EAGtB,IAAI,KAAK,qBAAqB;GAC5B,cAAc,KAAK,mBAAmB;GACtC,KAAK,sBAAsB;EAC7B;EAKA,MAAM,MAAM,SAAS;EAIrB,MAAM,KAAK,YAAY;CACzB;;;;;;;CAQA,MAAgB,gBAAgB,SAA2C;EAGzE,OAAO;CACT;AACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"middleware.cjs","names":[],"sources":["../src/middleware.ts"],"sourcesContent":["/**\n * Subscriber middleware system for transforming, filtering, and enriching events\n *\n * Provides composable middleware for adding cross-cutting concerns:\n * - Retry logic, rate limiting, and circuit breaker patterns\n * - Event filtering, transformation, and enrichment\n * - Idempotency and batching\n * - Event logging and observability\n *\n * @example\n * ```typescript\n * import { applyMiddleware, retryMiddleware, rateLimitMiddleware } from 'autotel-subscribers/middleware'\n *\n * const subscriber = applyMiddleware(\n * createPostHogSubscriber({ apiKey: '...' }),\n * [\n * retryMiddleware({ maxRetries: 3 }),\n * rateLimitMiddleware({ requestsPerSecond: 100 })\n * ]\n * )\n * ```\n */\n\nimport type {\n EventSubscriber,\n EventAttributes,\n FunnelStatus,\n OutcomeStatus,\n EventTrackingOptions,\n} from 'autotel/event-subscriber';\n\n/** Normalized event for middleware processing */\nexport type EventsEvent =\n | {\n type: 'event';\n name: string;\n attributes?: EventAttributes;\n options?: EventTrackingOptions;\n }\n | {\n type: 'funnel';\n funnel: string;\n step: FunnelStatus;\n attributes?: EventAttributes;\n options?: EventTrackingOptions;\n }\n | {\n type: 'outcome';\n operation: string;\n outcome: OutcomeStatus;\n attributes?: EventAttributes;\n options?: EventTrackingOptions;\n }\n | {\n type: 'value';\n name: string;\n value: number;\n attributes?: EventAttributes;\n options?: EventTrackingOptions;\n };\n\nexport type SendEventRecord = {\n subscriberName: string;\n eventType: EventsEvent['type'];\n eventName: string;\n status: 'success' | 'error';\n durationMs: number;\n startedAt: Date;\n endedAt: Date;\n error?: { name: string; message: string; code?: string };\n};\n\nexport type SendEventSink = {\n write(event: SendEventRecord): Promise<void>;\n};\n\nexport type RateLimitAlgorithm = 'fixed' | 'sliding';\nexport type RateLimitRecord = { count: number; resetAtMs: number };\n\nexport type RateLimitStore = {\n record(\n key: string,\n windowMs: number,\n algorithm: RateLimitAlgorithm,\n ): Promise<RateLimitRecord>;\n};\n\nexport type IdempotencyStore<TResult = unknown> = {\n get(key: string): Promise<TResult | null>;\n set(key: string, result: TResult, ttlMs: number): Promise<void>;\n};\n\nexport type SubscriberMiddleware<\n TCtxIn = Record<string, unknown>,\n TCtxOut = TCtxIn,\n> = (params: {\n event: EventsEvent;\n ctx: TCtxIn;\n subscriber: Pick<EventSubscriber, 'name' | 'version'>;\n next: (update?: {\n event?: EventsEvent;\n ctxPatch?: Partial<TCtxOut>;\n }) => Promise<void>;\n}) => Promise<void>;\n\n/** Type-safe middleware factory helper */\nexport const createMiddleware = <\n TCtxIn = Record<string, unknown>,\n TCtxOut = TCtxIn,\n>(\n fn: SubscriberMiddleware<TCtxIn, TCtxOut>,\n): SubscriberMiddleware<TCtxIn, TCtxOut> => fn;\n\nfunction eventNameOf(event: EventsEvent): string {\n switch (event.type) {\n case 'event':\n case 'value': {\n return event.name;\n }\n case 'funnel': {\n return `${event.funnel}.${event.step}`;\n }\n case 'outcome': {\n return `${event.operation}.${event.outcome}`;\n }\n }\n}\n\nfunction defaultContextFactory() {\n return {} as Record<string, unknown>;\n}\n\nasync function dispatchEvent(\n subscriber: EventSubscriber,\n event: EventsEvent,\n): Promise<void> {\n switch (event.type) {\n case 'event': {\n await subscriber.trackEvent(event.name, event.attributes, event.options);\n return;\n }\n case 'funnel': {\n await subscriber.trackFunnelStep(\n event.funnel,\n event.step,\n event.attributes,\n event.options,\n );\n return;\n }\n case 'outcome': {\n await subscriber.trackOutcome(\n event.operation,\n event.outcome,\n event.attributes,\n event.options,\n );\n return;\n }\n case 'value': {\n await subscriber.trackValue(\n event.name,\n event.value,\n event.attributes,\n event.options,\n );\n }\n }\n}\n\n/**\n * Apply middleware to a subscriber\n *\n * Chains middleware in order, each can transform events or context before passing to next\n *\n * @example\n * ```typescript\n * const enriched = applyMiddleware(\n * subscriber,\n * [enrichmentMiddleware(event => ({ ...event, timestamp: Date.now() }))]\n * )\n * ```\n */\nexport function applyMiddleware<TCtx = Record<string, unknown>>(\n subscriber: EventSubscriber,\n middlewares: Array<SubscriberMiddleware<TCtx, TCtx>>,\n options?: { initialContext?: () => TCtx },\n): EventSubscriber {\n const runChain = async (initialEvent: EventsEvent): Promise<void> => {\n const baseCtx = (\n options?.initialContext ?? defaultContextFactory\n )() as TCtx;\n\n const execute = async (\n index: number,\n event: EventsEvent,\n ctx: TCtx,\n ): Promise<void> => {\n const middleware = middlewares[index];\n if (!middleware) {\n await dispatchEvent(subscriber, event);\n return;\n }\n\n await middleware({\n event,\n ctx,\n subscriber,\n next: async (update) => {\n const nextEvent = update?.event ?? event;\n const nextCtx = update?.ctxPatch\n ? ({ ...ctx, ...update.ctxPatch } as TCtx)\n : ctx;\n await execute(index + 1, nextEvent, nextCtx);\n },\n });\n };\n\n await execute(0, initialEvent, baseCtx);\n };\n\n return {\n name: `${subscriber.name}(middleware)`,\n version: subscriber.version,\n trackEvent: async (name, attributes, options_) =>\n runChain({ type: 'event', name, attributes, options: options_ }),\n trackFunnelStep: async (funnel, step, attributes, options_) =>\n runChain({ type: 'funnel', funnel, step, attributes, options: options_ }),\n trackOutcome: async (operation, outcome, attributes, options_) =>\n runChain({\n type: 'outcome',\n operation,\n outcome,\n attributes,\n options: options_,\n }),\n trackValue: async (name, value, attributes, options_) =>\n runChain({ type: 'value', name, value, attributes, options: options_ }),\n shutdown: async () => {\n await subscriber.shutdown?.();\n },\n };\n}\n\n/**\n * Retry middleware with exponential backoff\n *\n * Automatically retries failed events with exponential backoff delay\n */\nexport function retryMiddleware(options: {\n maxRetries?: number;\n delayMs?: number;\n onRetry?: (attempt: number, error: Error, event: EventsEvent) => void;\n}): SubscriberMiddleware {\n const { maxRetries = 3, delayMs = 1000, onRetry } = options;\n\n return async ({ event, next }) => {\n let lastError: Error | undefined;\n\n for (let attempt = 1; attempt <= maxRetries; attempt++) {\n try {\n await next();\n return;\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n if (attempt < maxRetries) {\n onRetry?.(attempt, lastError, event);\n await new Promise((resolve) =>\n setTimeout(resolve, delayMs * 2 ** (attempt - 1)),\n );\n }\n }\n }\n\n throw lastError;\n };\n}\n\n/**\n * Sampling middleware to reduce event volume\n *\n * Only passes through a percentage of events based on the rate (0.0 to 1.0)\n */\nexport function samplingMiddleware(rate: number): SubscriberMiddleware {\n if (rate < 0 || rate > 1)\n throw new Error('Sample rate must be between 0 and 1');\n\n return async ({ next }) => {\n if (Math.random() < rate) await next();\n };\n}\n\n/**\n * Enrichment middleware to add or modify event data\n *\n * Apply a transformation function to each event before sending\n */\nexport function enrichmentMiddleware(\n enricher: (event: EventsEvent) => EventsEvent,\n): SubscriberMiddleware {\n return async ({ event, next }) => {\n await next({ event: enricher(event) });\n };\n}\n\n/**\n * Logging middleware for debugging event flow\n *\n * Logs event type and name (optionally full event) to console\n */\nexport function loggingMiddleware(\n options: { prefix?: string; logAttributes?: boolean } = {},\n): SubscriberMiddleware {\n const { prefix = '[Events]', logAttributes = false } = options;\n\n return async ({ event, next }) => {\n if (logAttributes) {\n console.log(prefix, event.type, event);\n } else {\n console.log(prefix, event.type, eventNameOf(event));\n }\n await next();\n };\n}\n\n/**\n * Filter middleware to selectively process events\n *\n * Only forwards events that match the predicate\n */\nexport function filterMiddleware(\n predicate: (event: EventsEvent) => boolean,\n): SubscriberMiddleware {\n return async ({ event, next }) => {\n if (predicate(event)) await next();\n };\n}\n\n/**\n * Transform middleware to modify event structure\n *\n * Similar to enrichment but replaces entire event\n */\nexport function transformMiddleware(\n transformer: (event: EventsEvent) => EventsEvent,\n): SubscriberMiddleware {\n return async ({ event, next }) => {\n await next({ event: transformer(event) });\n };\n}\n\n/**\n * Batching middleware to group events for bulk sending\n *\n * Collects events into batches before forwarding\n */\nexport function batchingMiddleware(options: {\n batchSize?: number;\n flushInterval?: number;\n}): SubscriberMiddleware {\n const { batchSize = 100, flushInterval = 5000 } = options;\n const queue: Array<() => Promise<void>> = [];\n let timer: NodeJS.Timeout | null = null;\n\n const flush = async () => {\n const pending = queue.splice(0);\n await Promise.all(pending.map((run) => run()));\n };\n\n return async ({ next }) => {\n await new Promise<void>((resolve, reject) => {\n queue.push(async () => {\n try {\n await next();\n resolve();\n } catch (error) {\n reject(error);\n }\n });\n\n if (queue.length >= batchSize) {\n if (timer) clearTimeout(timer);\n timer = null;\n void flush().catch(reject);\n return;\n }\n\n if (!timer) {\n timer = setTimeout(() => {\n timer = null;\n void flush().catch(reject);\n }, flushInterval);\n }\n });\n };\n}\n\n/**\n * Rate limit middleware to control event sending rate\n *\n * Enforces a maximum rate of events sent per second\n */\nexport function rateLimitMiddleware(options: {\n requestsPerSecond: number;\n}): SubscriberMiddleware {\n const intervalMs = 1000 / options.requestsPerSecond;\n let lastAt = 0;\n\n return async ({ next }) => {\n const now = Date.now();\n const waitMs = Math.max(0, intervalMs - (now - lastAt));\n if (waitMs > 0) {\n await new Promise((resolve) => setTimeout(resolve, waitMs));\n }\n lastAt = Date.now();\n await next();\n };\n}\n\n/**\n * Circuit breaker middleware for fault tolerance\n *\n * Prevents cascading failures by stopping requests when error rate exceeds threshold\n */\nexport function circuitBreakerMiddleware(options: {\n failureThreshold?: number;\n timeout?: number;\n onOpen?: () => void;\n onClose?: () => void;\n}): SubscriberMiddleware {\n const { failureThreshold = 5, timeout = 60_000, onOpen, onClose } = options;\n let failureCount = 0;\n let lastFailureAt = 0;\n let open = false;\n\n return async ({ next }) => {\n if (open) {\n if (Date.now() - lastFailureAt > timeout) {\n open = false;\n failureCount = 0;\n onClose?.();\n } else {\n throw new Error('Circuit breaker is open');\n }\n }\n\n try {\n await next();\n failureCount = 0;\n } catch (error) {\n failureCount += 1;\n lastFailureAt = Date.now();\n if (failureCount >= failureThreshold) {\n open = true;\n onOpen?.();\n }\n throw error;\n }\n };\n}\n\n/**\n * Timeout middleware to prevent hanging requests\n *\n * Rejects the event if processing exceeds the specified timeout\n */\nexport function timeoutMiddleware(options: {\n timeoutMs: number;\n}): SubscriberMiddleware {\n return async ({ next }) => {\n const timeoutPromise = new Promise<never>((_, reject) => {\n setTimeout(\n () => reject(new Error(`Timeout after ${options.timeoutMs}ms`)),\n options.timeoutMs,\n );\n });\n\n await Promise.race([next(), timeoutPromise]);\n };\n}\n\n/**\n * Create an idempotency store with custom get/set operations\n *\n * Used with withIdempotency middleware to prevent duplicate event processing\n */\nexport function createIdempotencyStore<TResult = unknown>(options: {\n get: (key: string) => Promise<TResult | null>;\n set: (key: string, result: TResult, ttlMs: number) => Promise<void>;\n}): IdempotencyStore<TResult> {\n return { get: options.get, set: options.set };\n}\n\n/**\n * In-memory idempotency store\n *\n * Useful for testing or single-process applications\n */\nexport function inMemoryIdempotencyStore<\n TResult = unknown,\n>(): IdempotencyStore<TResult> {\n type Entry = { result: TResult; expiresAtMs: number };\n const map = new Map<string, Entry>();\n\n return createIdempotencyStore<TResult>({\n get: async (key) => {\n const entry = map.get(key);\n if (!entry) return null;\n if (entry.expiresAtMs <= Date.now()) {\n map.delete(key);\n return null;\n }\n return entry.result;\n },\n set: async (key, result, ttlMs) => {\n map.set(key, { result, expiresAtMs: Date.now() + ttlMs });\n },\n });\n}\n\n/**\n * Create a rate limit store with custom record operation\n *\n * Used with withRateLimit middleware for distributed rate limiting\n */\nexport function createRateLimitStore(options: {\n record: (\n key: string,\n windowMs: number,\n algorithm: RateLimitAlgorithm,\n ) => Promise<RateLimitRecord>;\n}): RateLimitStore {\n return { record: options.record };\n}\n\n/**\n * In-memory rate limit store\n *\n * Supports fixed and sliding window algorithms\n */\nexport function inMemoryRateLimitStore(): RateLimitStore {\n type FixedEntry = { count: number; expireAtMs: number };\n const fixed = new Map<string, FixedEntry>();\n const sliding = new Map<string, number[]>();\n\n return {\n async record(key, windowMs, algorithm) {\n const now = Date.now();\n if (algorithm === 'fixed') {\n const entry = fixed.get(key);\n if (!entry || entry.expireAtMs <= now) {\n const next = { count: 1, expireAtMs: now + windowMs };\n fixed.set(key, next);\n return { count: 1, resetAtMs: next.expireAtMs };\n }\n entry.count += 1;\n return { count: entry.count, resetAtMs: entry.expireAtMs };\n }\n\n const cutoff = now - windowMs;\n const points = (sliding.get(key) ?? []).filter((ts) => ts > cutoff);\n points.push(now);\n sliding.set(key, points);\n return { count: points.length, resetAtMs: points[0]! + windowMs };\n },\n };\n}\n\n/**\n * Idempotency middleware to prevent duplicate processing\n *\n * Skips processing if the same event (by key) was processed within TTL\n *\n * @example\n * ```typescript\n * const middleware = withIdempotency({\n * store: inMemoryIdempotencyStore(),\n * key: event => event.name,\n * ttlMs: 60000\n * })\n * ```\n */\nexport function withIdempotency(options: {\n store: IdempotencyStore<boolean>;\n key: string | ((event: EventsEvent) => string);\n ttlMs: number;\n}): SubscriberMiddleware {\n return async ({ event, next }) => {\n const key =\n typeof options.key === 'function' ? options.key(event) : options.key;\n const cached = await options.store.get(key);\n if (cached) return;\n\n await next();\n await options.store.set(key, true, options.ttlMs);\n };\n}\n\n/**\n * Rate limiting middleware with store-based tracking\n *\n * Enforces rate limits across distributed systems using a store backend\n *\n * @example\n * ```typescript\n * const middleware = withRateLimit({\n * store: inMemoryRateLimitStore(),\n * key: event => 'global',\n * max: 1000,\n * windowMs: 60000\n * })\n * ```\n */\nexport function withRateLimit(options: {\n store: RateLimitStore;\n key: string | ((event: EventsEvent) => string);\n max: number;\n windowMs: number;\n algorithm?: RateLimitAlgorithm;\n}): SubscriberMiddleware {\n const algorithm = options.algorithm ?? 'fixed';\n\n return async ({ event, next }) => {\n const key =\n typeof options.key === 'function' ? options.key(event) : options.key;\n const { count, resetAtMs } = await options.store.record(\n key,\n options.windowMs,\n algorithm,\n );\n if (count > options.max) {\n const retryAfterMs = Math.max(0, resetAtMs - Date.now());\n throw new Error(\n `Rate limited for key \"${key}\". Retry after ${retryAfterMs}ms.`,\n );\n }\n await next();\n };\n}\n\n/**\n * Event logger middleware for observability\n *\n * Writes event records to a sink for monitoring, audit logging, or analytics\n *\n * @example\n * ```typescript\n * const middleware = withEventLogger({\n * sink: {\n * write: async (record) => {\n * await db.eventLogs.insert(record)\n * }\n * }\n * })\n * ```\n */\nexport function withEventLogger(options: {\n sink: SendEventSink;\n}): SubscriberMiddleware {\n return async ({ event, next, subscriber }) => {\n const startedAt = new Date();\n const start = performance.now();\n\n try {\n await next();\n const endedAt = new Date();\n await options.sink.write({\n subscriberName: subscriber.name ?? 'unknown',\n eventType: event.type,\n eventName: eventNameOf(event),\n status: 'success',\n durationMs: performance.now() - start,\n startedAt,\n endedAt,\n });\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n const endedAt = new Date();\n const maybeCode = (err as { code?: unknown }).code;\n\n await options.sink.write({\n subscriberName: subscriber.name ?? 'unknown',\n eventType: event.type,\n eventName: eventNameOf(event),\n status: 'error',\n durationMs: performance.now() - start,\n startedAt,\n endedAt,\n error: {\n name: err.name,\n message: err.message,\n code: typeof maybeCode === 'string' ? maybeCode : undefined,\n },\n });\n throw err;\n }\n };\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"middleware.d.cts","names":[],"sources":["../src/middleware.ts"],"mappings":";;;KAgCY;EAEN;EACA;EACA,aAAa;EACb,UAAU;;EAGV;EACA;EACA,MAAM;EACN,aAAa;EACb,UAAU;;EAGV;EACA;EACA,SAAS;EACT,aAAa;EACb,UAAU;;EAGV;EACA;EACA;EACA,aAAa;EACb,UAAU;;KAGJ;EACV;EACA,WAAW;EACX;EACA;EACA;EACA,WAAW;EACX,SAAS;EACT;IAAU;IAAc;IAAiB;;;KAG/B;EACV,MAAM,OAAO,kBAAkB;;KAGrB;KACA;EAAoB;EAAe;;KAEnC;EACV,OACE,aACA,kBACA,WAAW,qBACV,QAAQ;;KAGD,iBAAiB;EAC3B,IAAI,cAAc,QAAQ;EAC1B,IAAI,aAAa,QAAQ,SAAS,gBAAgB;;KAGxC,qBACV,SAAS,yBACT,UAAU,WACP;EACH,OAAO;EACP,KAAK;EACL,YAAY,KAAK;EACjB,OAAO;IACL,QAAQ;IACR,WAAW,QAAQ;QACf;MACF;;cAGO,mBACX,SAAS,yBACT,UAAU,QAEV,IAAI,qBAAqB,QAAQ,aAChC,qBAAqB,QAAQ;;;;;;;;;;;;;;iBAwEhB,gBAAgB,OAAO,yBACrC,YAAY,iBACZ,aAAa,MAAM,qBAAqB,MAAM,QAC9C;EAAY,uBAAuB;IAClC;;;;;;iBA8Da,gBAAgB;EAC9B;EACA;EACA,WAAW,iBAAiB,OAAO,OAAO,OAAO;IAC/C;;;;;;iBA8BY,mBAAmB,eAAe;;;;;;iBAclC,qBACd,WAAW,OAAO,gBAAgB,cACjC;;;;;;iBAWa,kBACd;EAAW;EAAiB;IAC3B;;;;;;iBAkBa,iBACd,YAAY,OAAO,0BAClB;;;;;;iBAWa,oBACd,cAAc,OAAO,gBAAgB,cACpC;;;;;;iBAWa,mBAAmB;EACjC;EACA;IACE;;;;;;iBA2CY,oBAAoB;EAClC;IACE;;;;;;iBAoBY,yBAAyB;EACvC;EACA;EACA;EACA;IACE;;;;;;iBAqCY,kBAAkB;EAChC;IACE;;;;;;iBAkBY,uBAAuB,mBAAmB;EACxD,MAAM,gBAAgB,QAAQ;EAC9B,MAAM,aAAa,QAAQ,SAAS,kBAAkB;IACpD,iBAAiB;;;;;;iBASL,yBACd,sBACG,iBAAiB;;;;;;iBAyBN,qBAAqB;EACnC,SACE,aACA,kBACA,WAAW,uBACR,QAAQ;IACX;;;;;;iBASY,0BAA0B;;;;;;;;;;;;;;;iBA0C1B,gBAAgB;EAC9B,OAAO;EACP,gBAAgB,OAAO;EACvB;IACE;;;;;;;;;;;;;;;;iBA2BY,cAAc;EAC5B,OAAO;EACP,gBAAgB,OAAO;EACvB;EACA;EACA,YAAY;IACV;;;;;;;;;;;;;;;;;iBAqCY,gBAAgB;EAC9B,MAAM;IACJ"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"middleware.d.ts","names":[],"sources":["../src/middleware.ts"],"mappings":";;;KAgCY;EAEN;EACA;EACA,aAAa;EACb,UAAU;;EAGV;EACA;EACA,MAAM;EACN,aAAa;EACb,UAAU;;EAGV;EACA;EACA,SAAS;EACT,aAAa;EACb,UAAU;;EAGV;EACA;EACA;EACA,aAAa;EACb,UAAU;;KAGJ;EACV;EACA,WAAW;EACX;EACA;EACA;EACA,WAAW;EACX,SAAS;EACT;IAAU;IAAc;IAAiB;;;KAG/B;EACV,MAAM,OAAO,kBAAkB;;KAGrB;KACA;EAAoB;EAAe;;KAEnC;EACV,OACE,aACA,kBACA,WAAW,qBACV,QAAQ;;KAGD,iBAAiB;EAC3B,IAAI,cAAc,QAAQ;EAC1B,IAAI,aAAa,QAAQ,SAAS,gBAAgB;;KAGxC,qBACV,SAAS,yBACT,UAAU,WACP;EACH,OAAO;EACP,KAAK;EACL,YAAY,KAAK;EACjB,OAAO;IACL,QAAQ;IACR,WAAW,QAAQ;QACf;MACF;;cAGO,mBACX,SAAS,yBACT,UAAU,QAEV,IAAI,qBAAqB,QAAQ,aAChC,qBAAqB,QAAQ;;;;;;;;;;;;;;iBAwEhB,gBAAgB,OAAO,yBACrC,YAAY,iBACZ,aAAa,MAAM,qBAAqB,MAAM,QAC9C;EAAY,uBAAuB;IAClC;;;;;;iBA8Da,gBAAgB;EAC9B;EACA;EACA,WAAW,iBAAiB,OAAO,OAAO,OAAO;IAC/C;;;;;;iBA8BY,mBAAmB,eAAe;;;;;;iBAclC,qBACd,WAAW,OAAO,gBAAgB,cACjC;;;;;;iBAWa,kBACd;EAAW;EAAiB;IAC3B;;;;;;iBAkBa,iBACd,YAAY,OAAO,0BAClB;;;;;;iBAWa,oBACd,cAAc,OAAO,gBAAgB,cACpC;;;;;;iBAWa,mBAAmB;EACjC;EACA;IACE;;;;;;iBA2CY,oBAAoB;EAClC;IACE;;;;;;iBAoBY,yBAAyB;EACvC;EACA;EACA;EACA;IACE;;;;;;iBAqCY,kBAAkB;EAChC;IACE;;;;;;iBAkBY,uBAAuB,mBAAmB;EACxD,MAAM,gBAAgB,QAAQ;EAC9B,MAAM,aAAa,QAAQ,SAAS,kBAAkB;IACpD,iBAAiB;;;;;;iBASL,yBACd,sBACG,iBAAiB;;;;;;iBAyBN,qBAAqB;EACnC,SACE,aACA,kBACA,WAAW,uBACR,QAAQ;IACX;;;;;;iBASY,0BAA0B;;;;;;;;;;;;;;;iBA0C1B,gBAAgB;EAC9B,OAAO;EACP,gBAAgB,OAAO;EACvB;IACE;;;;;;;;;;;;;;;;iBA2BY,cAAc;EAC5B,OAAO;EACP,gBAAgB,OAAO;EACvB;EACA;EACA,YAAY;IACV;;;;;;;;;;;;;;;;;iBAqCY,gBAAgB;EAC9B,MAAM;IACJ"}