autotel-subscribers 56.0.0 → 56.1.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.
@@ -13,6 +13,33 @@ node_path = require_rolldown_runtime.__toESM(node_path, 1);
13
13
  * downstream consumers, so add fields rather than rename existing ones.
14
14
  */
15
15
  const ARCHITECTURE_SNAPSHOT_SPEC = "autotel-architecture/v0.1.0";
16
+ /**
17
+ * Order two (producer, channel) pairs, comparing the fields rather than a
18
+ * joined key.
19
+ *
20
+ * Both come from caller-supplied `_autotel` metadata and may contain any
21
+ * character, so joining them with a separator is ambiguous: `('A|B', 'C')` and
22
+ * `('A', 'B|C')` produce the same string and would merge into one source.
23
+ */
24
+ function compareSources(a, b) {
25
+ return (a.producer ?? "").localeCompare(b.producer ?? "") || (a.channel ?? "").localeCompare(b.channel ?? "");
26
+ }
27
+ function recordSource(observation, producer, channel, now) {
28
+ const sources = observation.sources ??= [];
29
+ const existing = sources.find((source) => source.producer === producer && source.channel === channel);
30
+ if (existing) {
31
+ existing.count += 1;
32
+ existing.lastSeen = now;
33
+ return;
34
+ }
35
+ sources.push({
36
+ ...producer !== void 0 && { producer },
37
+ ...channel !== void 0 && { channel },
38
+ count: 1,
39
+ firstSeen: now,
40
+ lastSeen: now
41
+ });
42
+ }
16
43
  const DEFAULT_MAX_SAMPLES = 3;
17
44
  var ArchitectureSnapshotSubscriber = class extends require_event_subscriber_base.EventSubscriber {
18
45
  name = "ArchitectureSnapshotSubscriber";
@@ -50,7 +77,14 @@ var ArchitectureSnapshotSubscriber = class extends require_event_subscriber_base
50
77
  source: payload.schema.source,
51
78
  jsonSchema: payload.schema.jsonSchema,
52
79
  hash: payload.schema.hash
53
- } : void 0
80
+ } : void 0,
81
+ sources: [{
82
+ ...autotelMeta.producer !== void 0 && { producer: autotelMeta.producer },
83
+ ...autotelMeta.channel !== void 0 && { channel: autotelMeta.channel },
84
+ count: 1,
85
+ firstSeen: now,
86
+ lastSeen: now
87
+ }]
54
88
  });
55
89
  return;
56
90
  }
@@ -61,6 +95,7 @@ var ArchitectureSnapshotSubscriber = class extends require_event_subscriber_base
61
95
  if (traceId && !existing.sampleTraceIds.includes(traceId) && existing.sampleTraceIds.length < this.maxSampleTraceIds) existing.sampleTraceIds.push(traceId);
62
96
  existing.channel ??= autotelMeta.channel;
63
97
  existing.producer ??= autotelMeta.producer;
98
+ recordSource(existing, autotelMeta.producer, autotelMeta.channel, now);
64
99
  existing.consumers = mergeUnique(existing.consumers ?? [], autotelMeta.consumers ?? []);
65
100
  existing.schema ??= payload.schema ? {
66
101
  source: payload.schema.source,
@@ -93,7 +128,12 @@ var ArchitectureSnapshotSubscriber = class extends require_event_subscriber_base
93
128
  lastSeen: freezeTimestamps ?? obs.lastSeen,
94
129
  fieldPaths: obs.fieldPaths.toSorted(),
95
130
  sampleTraceIds: obs.sampleTraceIds.toSorted(),
96
- fieldStats: sortFieldStats(obs.fieldStats)
131
+ fieldStats: sortFieldStats(obs.fieldStats),
132
+ ...obs.sources && { sources: obs.sources.map((source) => ({
133
+ ...source,
134
+ firstSeen: freezeTimestamps ?? source.firstSeen,
135
+ lastSeen: freezeTimestamps ?? source.lastSeen
136
+ })).toSorted(compareSources) }
97
137
  };
98
138
  }
99
139
  return {
@@ -27,6 +27,17 @@ type EventObservation = {
27
27
  producer?: string;
28
28
  /** Services known to consume this event (optional metadata from _autotel.consumers). */
29
29
  consumers?: string[];
30
+ /**
31
+ * One entry per distinct (producer, channel) pair this event was seen on,
32
+ * with that pair's own count.
33
+ *
34
+ * `producer` and `channel` above are first-write-wins and `observedCount` is
35
+ * an event-level total, so two services publishing the same event name
36
+ * collapse into one: the second producer disappears and its observations are
37
+ * silently credited to the first. Anything attributing counts to a specific
38
+ * relationship must read this instead.
39
+ */
40
+ sources?: EventSource[];
30
41
  /** Observed runtime types and sample primitive values per field path. */
31
42
  fieldStats?: Record<string, FieldStats>;
32
43
  /** Optional contract schema metadata carried from track() call sites. */
@@ -36,6 +47,14 @@ type EventObservation = {
36
47
  hash: string;
37
48
  };
38
49
  };
50
+ /** One (producer, channel) pair an event was observed on, with its own count. */
51
+ type EventSource = {
52
+ producer?: string;
53
+ channel?: string;
54
+ count: number;
55
+ firstSeen: string;
56
+ lastSeen: string;
57
+ };
39
58
  type FieldStats = {
40
59
  /** Runtime types observed for this field path (e.g. string, number). */
41
60
  types: string[];
@@ -91,4 +110,4 @@ declare class ArchitectureSnapshotSubscriber extends EventSubscriber {
91
110
  */
92
111
  declare function extractFieldPaths(value: unknown, prefix?: string): string[];
93
112
  //#endregion
94
- export { ARCHITECTURE_SNAPSHOT_SPEC, ArchitectureSnapshot, ArchitectureSnapshotConfig, ArchitectureSnapshotSubscriber, EventObservation, FieldStats, extractFieldPaths };
113
+ export { ARCHITECTURE_SNAPSHOT_SPEC, ArchitectureSnapshot, ArchitectureSnapshotConfig, ArchitectureSnapshotSubscriber, EventObservation, EventSource, FieldStats, extractFieldPaths };
@@ -27,6 +27,17 @@ type EventObservation = {
27
27
  producer?: string;
28
28
  /** Services known to consume this event (optional metadata from _autotel.consumers). */
29
29
  consumers?: string[];
30
+ /**
31
+ * One entry per distinct (producer, channel) pair this event was seen on,
32
+ * with that pair's own count.
33
+ *
34
+ * `producer` and `channel` above are first-write-wins and `observedCount` is
35
+ * an event-level total, so two services publishing the same event name
36
+ * collapse into one: the second producer disappears and its observations are
37
+ * silently credited to the first. Anything attributing counts to a specific
38
+ * relationship must read this instead.
39
+ */
40
+ sources?: EventSource[];
30
41
  /** Observed runtime types and sample primitive values per field path. */
31
42
  fieldStats?: Record<string, FieldStats>;
32
43
  /** Optional contract schema metadata carried from track() call sites. */
@@ -36,6 +47,14 @@ type EventObservation = {
36
47
  hash: string;
37
48
  };
38
49
  };
50
+ /** One (producer, channel) pair an event was observed on, with its own count. */
51
+ type EventSource = {
52
+ producer?: string;
53
+ channel?: string;
54
+ count: number;
55
+ firstSeen: string;
56
+ lastSeen: string;
57
+ };
39
58
  type FieldStats = {
40
59
  /** Runtime types observed for this field path (e.g. string, number). */
41
60
  types: string[];
@@ -91,4 +110,4 @@ declare class ArchitectureSnapshotSubscriber extends EventSubscriber {
91
110
  */
92
111
  declare function extractFieldPaths(value: unknown, prefix?: string): string[];
93
112
  //#endregion
94
- export { ARCHITECTURE_SNAPSHOT_SPEC, ArchitectureSnapshot, ArchitectureSnapshotConfig, ArchitectureSnapshotSubscriber, EventObservation, FieldStats, extractFieldPaths };
113
+ export { ARCHITECTURE_SNAPSHOT_SPEC, ArchitectureSnapshot, ArchitectureSnapshotConfig, ArchitectureSnapshotSubscriber, EventObservation, EventSource, FieldStats, extractFieldPaths };
@@ -9,6 +9,33 @@ import path from "node:path";
9
9
  * downstream consumers, so add fields rather than rename existing ones.
10
10
  */
11
11
  const ARCHITECTURE_SNAPSHOT_SPEC = "autotel-architecture/v0.1.0";
12
+ /**
13
+ * Order two (producer, channel) pairs, comparing the fields rather than a
14
+ * joined key.
15
+ *
16
+ * Both come from caller-supplied `_autotel` metadata and may contain any
17
+ * character, so joining them with a separator is ambiguous: `('A|B', 'C')` and
18
+ * `('A', 'B|C')` produce the same string and would merge into one source.
19
+ */
20
+ function compareSources(a, b) {
21
+ return (a.producer ?? "").localeCompare(b.producer ?? "") || (a.channel ?? "").localeCompare(b.channel ?? "");
22
+ }
23
+ function recordSource(observation, producer, channel, now) {
24
+ const sources = observation.sources ??= [];
25
+ const existing = sources.find((source) => source.producer === producer && source.channel === channel);
26
+ if (existing) {
27
+ existing.count += 1;
28
+ existing.lastSeen = now;
29
+ return;
30
+ }
31
+ sources.push({
32
+ ...producer !== void 0 && { producer },
33
+ ...channel !== void 0 && { channel },
34
+ count: 1,
35
+ firstSeen: now,
36
+ lastSeen: now
37
+ });
38
+ }
12
39
  const DEFAULT_MAX_SAMPLES = 3;
13
40
  var ArchitectureSnapshotSubscriber = class extends EventSubscriber {
14
41
  name = "ArchitectureSnapshotSubscriber";
@@ -46,7 +73,14 @@ var ArchitectureSnapshotSubscriber = class extends EventSubscriber {
46
73
  source: payload.schema.source,
47
74
  jsonSchema: payload.schema.jsonSchema,
48
75
  hash: payload.schema.hash
49
- } : void 0
76
+ } : void 0,
77
+ sources: [{
78
+ ...autotelMeta.producer !== void 0 && { producer: autotelMeta.producer },
79
+ ...autotelMeta.channel !== void 0 && { channel: autotelMeta.channel },
80
+ count: 1,
81
+ firstSeen: now,
82
+ lastSeen: now
83
+ }]
50
84
  });
51
85
  return;
52
86
  }
@@ -57,6 +91,7 @@ var ArchitectureSnapshotSubscriber = class extends EventSubscriber {
57
91
  if (traceId && !existing.sampleTraceIds.includes(traceId) && existing.sampleTraceIds.length < this.maxSampleTraceIds) existing.sampleTraceIds.push(traceId);
58
92
  existing.channel ??= autotelMeta.channel;
59
93
  existing.producer ??= autotelMeta.producer;
94
+ recordSource(existing, autotelMeta.producer, autotelMeta.channel, now);
60
95
  existing.consumers = mergeUnique(existing.consumers ?? [], autotelMeta.consumers ?? []);
61
96
  existing.schema ??= payload.schema ? {
62
97
  source: payload.schema.source,
@@ -89,7 +124,12 @@ var ArchitectureSnapshotSubscriber = class extends EventSubscriber {
89
124
  lastSeen: freezeTimestamps ?? obs.lastSeen,
90
125
  fieldPaths: obs.fieldPaths.toSorted(),
91
126
  sampleTraceIds: obs.sampleTraceIds.toSorted(),
92
- fieldStats: sortFieldStats(obs.fieldStats)
127
+ fieldStats: sortFieldStats(obs.fieldStats),
128
+ ...obs.sources && { sources: obs.sources.map((source) => ({
129
+ ...source,
130
+ firstSeen: freezeTimestamps ?? source.firstSeen,
131
+ lastSeen: freezeTimestamps ?? source.lastSeen
132
+ })).toSorted(compareSources) }
93
133
  };
94
134
  }
95
135
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autotel-subscribers",
3
- "version": "56.0.0",
3
+ "version": "56.1.0",
4
4
  "description": "Write Once, Observe Anywhere - Event subscribers for autotel (Mixpanel, Amplitude, Segment, Slack, webhooks, Loki, file)",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -101,7 +101,7 @@
101
101
  "slow-redact": "^0.3.2"
102
102
  },
103
103
  "peerDependencies": {
104
- "autotel": "7.6.0"
104
+ "autotel": "7.6.1"
105
105
  },
106
106
  "peerDependenciesMeta": {
107
107
  "posthog-node": {
@@ -126,7 +126,7 @@
126
126
  "@types/node": "^26.1.2",
127
127
  "@typescript-eslint/eslint-plugin": "^8.65.0",
128
128
  "@typescript-eslint/parser": "^8.65.0",
129
- "autotel": "7.6.0",
129
+ "autotel": "7.6.1",
130
130
  "drizzle-orm": "^0.45.2",
131
131
  "eslint-config-prettier": "^10.1.8",
132
132
  "eslint-plugin-unicorn": "^64.0.0",