autotel-subscribers 33.0.0 → 34.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/file.cjs +262 -0
- package/dist/file.cjs.map +1 -0
- package/dist/file.d.cts +54 -0
- package/dist/file.d.ts +54 -0
- package/dist/file.js +256 -0
- package/dist/file.js.map +1 -0
- package/dist/index.cjs +88 -42
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +89 -44
- package/dist/index.js.map +1 -1
- package/package.json +8 -3
- package/src/file.test.ts +87 -0
- package/src/file.ts +97 -0
- package/src/index.ts +1 -0
package/dist/file.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/event-subscriber-base.ts","../src/file.ts"],"names":[],"mappings":"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","file":"file.js","sourcesContent":["/**\n * EventSubscriber - Standard base class for building custom subscribers\n *\n * This is the recommended base class for creating custom events subscribers.\n * It provides production-ready features out of the box:\n *\n * **Built-in Features:**\n * - **Error Handling**: Automatic error catching with customizable handlers\n * - **Pending Request Tracking**: Ensures all requests complete during shutdown\n * - **Graceful Shutdown**: Drains pending requests before closing\n * - **Enable/Disable**: Runtime control to turn subscriber on/off\n * - **Normalized Payload**: Consistent event structure across all event types\n *\n * **When to use:**\n * - Building custom subscribers for any platform\n * - Production deployments requiring reliability\n * - Need graceful shutdown and error handling\n *\n * @example Basic usage\n * ```typescript\n * import { EventSubscriber, EventPayload } from 'autotel-subscribers';\n *\n * class SnowflakeSubscriber extends EventSubscriber {\n * name = 'SnowflakeSubscriber';\n * version = '1.0.0';\n *\n * protected async sendToDestination(payload: EventPayload): Promise<void> {\n * await snowflakeClient.execute(\n * `INSERT INTO events VALUES (?, ?, ?)`,\n * [payload.type, payload.name, JSON.stringify(payload.attributes)]\n * );\n * }\n * }\n * ```\n *\n * @example With buffering\n * ```typescript\n * class BufferedSubscriber extends EventSubscriber {\n * name = 'BufferedSubscriber';\n * private buffer: EventPayload[] = [];\n *\n * protected async sendToDestination(payload: EventPayload): Promise<void> {\n * this.buffer.push(payload);\n *\n * if (this.buffer.length >= 100) {\n * await this.flush();\n * }\n * }\n *\n * async shutdown(): Promise<void> {\n * await super.shutdown(); // Drain pending requests first\n * await this.flush(); // Then flush buffer\n * }\n *\n * private async flush(): Promise<void> {\n * if (this.buffer.length === 0) return;\n *\n * const batch = [...this.buffer];\n * this.buffer = [];\n *\n * await apiClient.sendBatch(batch);\n * }\n * }\n * ```\n */\n\nimport type {\n EventSubscriber as IEventSubscriber,\n EventAttributes,\n EventAttributesInput,\n FunnelStatus,\n OutcomeStatus,\n AutotelEventContext,\n EventTrackingOptions,\n} from 'autotel/event-subscriber';\n\n// Re-export types for convenience\nexport type { AutotelEventContext, EventTrackingOptions } from 'autotel/event-subscriber';\n\n/**\n * Payload sent to destination\n */\nexport interface EventPayload {\n /** Event type: 'event', 'funnel', 'outcome', or 'value' */\n type: 'event' | 'funnel' | 'outcome' | 'value';\n\n /** Event name or metric name */\n name: string;\n\n /** Optional attributes */\n attributes?: EventAttributes;\n\n /** For funnel events: funnel name */\n funnel?: string;\n\n /** For funnel events: step status (from FunnelStatus enum) */\n step?: FunnelStatus | string;\n\n /** For funnel events: custom step name (from trackFunnelProgression) */\n stepName?: string;\n\n /** For funnel events: numeric position in funnel */\n stepNumber?: number;\n\n /** For outcome events: operation name */\n operation?: string;\n\n /** For outcome events: outcome status */\n outcome?: OutcomeStatus;\n\n /** For value events: numeric value */\n value?: number;\n\n /** Timestamp (ISO 8601) */\n timestamp: string;\n\n /**\n * Autotel trace context (present when events.includeTraceContext is enabled)\n *\n * Subscribers should map these to platform-specific field names:\n * - PostHog: autotel.trace_id → $trace_id\n * - Mixpanel: autotel.trace_id → trace_id\n */\n autotel?: AutotelEventContext;\n /** Optional schema metadata for contract-aware subscribers. */\n schema?: EventTrackingOptions['schema'];\n}\n\n/**\n * Standard base class for building custom events subscribers\n *\n * **What it provides:**\n * - Consistent payload structure (normalized across all event types)\n * - Enable/disable flag (runtime control)\n * - Automatic error handling (with customizable error handlers)\n * - Pending requests tracking (ensures no lost events during shutdown)\n * - Graceful shutdown (drains pending requests before closing)\n *\n * **Usage:**\n * Extend this class and implement `sendToDestination()`. All other methods\n * (trackEvent, trackFunnelStep, trackOutcome, trackValue, shutdown) are handled automatically.\n *\n * For high-throughput streaming platforms (Kafka, Kinesis, Pub/Sub), use `StreamingEventSubscriber` instead.\n */\nexport abstract class EventSubscriber implements IEventSubscriber {\n /**\n * Subscriber name (required for debugging)\n */\n abstract readonly name: string;\n\n /**\n * Subscriber version (optional)\n */\n readonly version?: string;\n\n /**\n * Enable/disable the subscriber (default: true)\n */\n protected enabled: boolean = true;\n\n /**\n * Track pending requests for graceful shutdown\n */\n private pendingRequests: Set<Promise<void>> = new Set();\n\n /**\n * Send payload to destination\n *\n * Override this method to implement your destination-specific logic.\n * This is called for all event types (event, funnel, outcome, value).\n *\n * @param payload - Normalized event payload\n */\n protected abstract sendToDestination(payload: EventPayload): Promise<void>;\n\n /**\n * Optional: Handle errors\n *\n * Override this to customize error handling (logging, retries, etc.).\n * Default behavior: log to console.error\n *\n * @param error - Error that occurred\n * @param payload - Event payload that failed\n */\n protected handleError(error: Error, payload: EventPayload): void {\n console.error(\n `[${this.name}] Failed to send ${payload.type}:`,\n error,\n payload,\n );\n }\n\n /**\n * Filter out undefined and null values from attributes\n *\n * This improves DX by allowing callers to pass objects with optional properties\n * without having to manually filter them first.\n *\n * @param attributes - Input attributes (may contain undefined/null)\n * @returns Filtered attributes with only defined values, or undefined if empty\n *\n * @example\n * ```typescript\n * const filtered = this.filterAttributes({\n * userId: user.id,\n * email: user.email, // might be undefined\n * plan: null, // will be filtered out\n * });\n * // Result: { userId: 'abc', email: 'test@example.com' } or { userId: 'abc' }\n * ```\n */\n protected filterAttributes(\n attributes?: EventAttributesInput,\n ): EventAttributes | undefined {\n if (!attributes) return undefined;\n\n const filtered: EventAttributes = {};\n for (const [key, value] of Object.entries(attributes)) {\n if (value !== undefined && value !== null) {\n filtered[key] = value;\n }\n }\n\n // Return undefined if no attributes remain after filtering\n return Object.keys(filtered).length > 0 ? filtered : undefined;\n }\n\n /**\n * Track an event\n */\n async trackEvent(\n name: string,\n attributes?: EventAttributes,\n options?: EventTrackingOptions,\n ): Promise<void> {\n if (!this.enabled) return;\n\n const payload: EventPayload = {\n type: 'event',\n name,\n attributes,\n timestamp: new Date().toISOString(),\n autotel: options?.autotel,\n schema: options?.schema,\n };\n\n await this.send(payload);\n }\n\n /**\n * Track a funnel step\n */\n async trackFunnelStep(\n funnelName: string,\n step: FunnelStatus,\n attributes?: EventAttributes,\n options?: EventTrackingOptions,\n ): Promise<void> {\n if (!this.enabled) return;\n\n const payload: EventPayload = {\n type: 'funnel',\n name: `${funnelName}.${step}`,\n funnel: funnelName,\n step,\n attributes,\n timestamp: new Date().toISOString(),\n autotel: options?.autotel,\n };\n\n await this.send(payload);\n }\n\n /**\n * Track an outcome\n */\n async trackOutcome(\n operationName: string,\n outcome: OutcomeStatus,\n attributes?: EventAttributes,\n options?: EventTrackingOptions,\n ): Promise<void> {\n if (!this.enabled) return;\n\n const payload: EventPayload = {\n type: 'outcome',\n name: `${operationName}.${outcome}`,\n operation: operationName,\n outcome,\n attributes,\n timestamp: new Date().toISOString(),\n autotel: options?.autotel,\n };\n\n await this.send(payload);\n }\n\n /**\n * Track a value/metric\n */\n async trackValue(\n name: string,\n value: number,\n attributes?: EventAttributes,\n options?: EventTrackingOptions,\n ): Promise<void> {\n if (!this.enabled) return;\n\n const payload: EventPayload = {\n type: 'value',\n name,\n value,\n attributes,\n timestamp: new Date().toISOString(),\n autotel: options?.autotel,\n };\n\n await this.send(payload);\n }\n\n /**\n * Track funnel progression with custom step names\n *\n * Unlike trackFunnelStep which uses FunnelStatus enum values,\n * this method allows any string as the step name for flexible funnel tracking.\n *\n * @param funnelName - Name of the funnel (e.g., \"checkout\", \"onboarding\")\n * @param stepName - Custom step name (e.g., \"cart_viewed\", \"payment_entered\")\n * @param stepNumber - Optional numeric position in the funnel\n * @param attributes - Optional event attributes\n * @param options - Optional tracking options including autotel context\n */\n async trackFunnelProgression(\n funnelName: string,\n stepName: string,\n stepNumber?: number,\n attributes?: EventAttributes,\n options?: EventTrackingOptions,\n ): Promise<void> {\n if (!this.enabled) return;\n\n const payload: EventPayload = {\n type: 'funnel',\n name: `${funnelName}.${stepName}`,\n funnel: funnelName,\n step: stepName,\n stepName,\n stepNumber,\n attributes,\n timestamp: new Date().toISOString(),\n autotel: options?.autotel,\n };\n\n await this.send(payload);\n }\n\n /**\n * Flush pending requests and clean up\n *\n * CRITICAL: Prevents race condition during shutdown\n * 1. Disables subscriber to stop new events\n * 2. Drains all pending requests (with retry logic)\n * 3. Ensures flush guarantee\n *\n * Override this if you need custom cleanup logic (close connections, flush buffers, etc.),\n * but ALWAYS call super.shutdown() first to drain pending requests.\n */\n async shutdown(): Promise<void> {\n // 1. Stop accepting new events (prevents race condition)\n this.enabled = false;\n\n // 2. Drain pending requests with retry logic\n // Loop until empty to handle race where new requests added during Promise.allSettled\n const maxDrainAttempts = 10;\n const drainIntervalMs = 50;\n\n for (let attempt = 0; attempt < maxDrainAttempts; attempt++) {\n if (this.pendingRequests.size === 0) {\n break;\n }\n\n // Wait for current batch\n await Promise.allSettled(this.pendingRequests);\n\n // Small delay to catch any stragglers added during allSettled\n if (this.pendingRequests.size > 0 && attempt < maxDrainAttempts - 1) {\n await new Promise((resolve) => setTimeout(resolve, drainIntervalMs));\n }\n }\n\n // 3. Warn if we still have pending requests (shouldn't happen, but be defensive)\n if (this.pendingRequests.size > 0) {\n console.warn(\n `[${this.name}] Shutdown completed with ${this.pendingRequests.size} pending requests still in-flight. ` +\n `This may indicate a bug in the subscriber or extremely slow destination.`\n );\n }\n }\n\n /**\n * Internal: Send payload and track request\n */\n private async send(payload: EventPayload): Promise<void> {\n const request = this.sendWithErrorHandling(payload);\n this.pendingRequests.add(request);\n\n void request.finally(() => {\n this.pendingRequests.delete(request);\n });\n\n return request;\n }\n\n /**\n * Internal: Send with error handling\n */\n private async sendWithErrorHandling(\n payload: EventPayload,\n ): Promise<void> {\n try {\n await this.sendToDestination(payload);\n } catch (error) {\n this.handleError(error as Error, payload);\n }\n }\n}\n\nexport {\n type EventAttributes,\n type EventAttributesInput,\n type FunnelStatus,\n type OutcomeStatus,\n} from 'autotel/event-subscriber';\n","/**\n * 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/events';\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"]}
|
package/dist/index.cjs
CHANGED
|
@@ -110,8 +110,8 @@ function createGetModuleFromFilename(basePath = process.argv[1] ? path.dirname(p
|
|
|
110
110
|
return decodedFile;
|
|
111
111
|
};
|
|
112
112
|
}
|
|
113
|
-
function normalizeWindowsPath(
|
|
114
|
-
return
|
|
113
|
+
function normalizeWindowsPath(path3) {
|
|
114
|
+
return path3.replace(/^[A-Z]:/, "").replace(/\\/g, "/");
|
|
115
115
|
}
|
|
116
116
|
var init_module_node = __esm({
|
|
117
117
|
"../../node_modules/.pnpm/posthog-node@5.34.1_rxjs@7.8.2/node_modules/posthog-node/dist/extensions/error-tracking/modifiers/module.node.mjs"() {
|
|
@@ -2729,9 +2729,9 @@ async function addSourceContext(frames) {
|
|
|
2729
2729
|
LRU_FILE_CONTENTS_CACHE.reduce();
|
|
2730
2730
|
return frames;
|
|
2731
2731
|
}
|
|
2732
|
-
function getContextLinesFromFile(
|
|
2732
|
+
function getContextLinesFromFile(path3, ranges, output) {
|
|
2733
2733
|
return new Promise((resolve) => {
|
|
2734
|
-
const stream = fs$1.createReadStream(
|
|
2734
|
+
const stream = fs$1.createReadStream(path3);
|
|
2735
2735
|
const lineReaded = readline.createInterface({
|
|
2736
2736
|
input: stream
|
|
2737
2737
|
});
|
|
@@ -2746,7 +2746,7 @@ function getContextLinesFromFile(path2, ranges, output) {
|
|
|
2746
2746
|
let rangeStart = range[0];
|
|
2747
2747
|
let rangeEnd = range[1];
|
|
2748
2748
|
function onStreamError() {
|
|
2749
|
-
LRU_FILE_CONTENTS_FS_READ_FAILED.set(
|
|
2749
|
+
LRU_FILE_CONTENTS_FS_READ_FAILED.set(path3, 1);
|
|
2750
2750
|
lineReaded.close();
|
|
2751
2751
|
lineReaded.removeAllListeners();
|
|
2752
2752
|
destroyStreamAndResolve();
|
|
@@ -2807,8 +2807,8 @@ function clearLineContext(frame) {
|
|
|
2807
2807
|
delete frame.context_line;
|
|
2808
2808
|
delete frame.post_context;
|
|
2809
2809
|
}
|
|
2810
|
-
function shouldSkipContextLinesForFile(
|
|
2811
|
-
return
|
|
2810
|
+
function shouldSkipContextLinesForFile(path3) {
|
|
2811
|
+
return path3.startsWith("node:") || path3.endsWith(".min.js") || path3.endsWith(".min.cjs") || path3.endsWith(".min.mjs") || path3.startsWith("data:");
|
|
2812
2812
|
}
|
|
2813
2813
|
function shouldSkipContextLinesForFrame(frame) {
|
|
2814
2814
|
if (void 0 !== frame.lineno && frame.lineno > MAX_CONTEXTLINES_LINENO) return true;
|
|
@@ -7295,11 +7295,11 @@ var require_flags = __commonJS({
|
|
|
7295
7295
|
params.append(key, value);
|
|
7296
7296
|
}
|
|
7297
7297
|
}
|
|
7298
|
-
const
|
|
7298
|
+
const path3 = `${this.endpoint}?${params.toString()}`;
|
|
7299
7299
|
const requestOptions = {
|
|
7300
7300
|
host: this.providerConfig.api_host,
|
|
7301
7301
|
port: 443,
|
|
7302
|
-
path:
|
|
7302
|
+
path: path3,
|
|
7303
7303
|
method: "GET",
|
|
7304
7304
|
headers: {
|
|
7305
7305
|
...REQUEST_HEADERS,
|
|
@@ -9539,15 +9539,15 @@ var init_context2 = __esm({
|
|
|
9539
9539
|
enumerable: false,
|
|
9540
9540
|
configurable: true
|
|
9541
9541
|
});
|
|
9542
|
-
CoreContext2.prototype.updateEvent = function(
|
|
9542
|
+
CoreContext2.prototype.updateEvent = function(path3, val) {
|
|
9543
9543
|
var _a;
|
|
9544
|
-
if (
|
|
9545
|
-
var integrationName =
|
|
9544
|
+
if (path3.split(".")[0] === "integrations") {
|
|
9545
|
+
var integrationName = path3.split(".")[1];
|
|
9546
9546
|
if (((_a = this.event.integrations) === null || _a === void 0 ? void 0 : _a[integrationName]) === false) {
|
|
9547
9547
|
return this.event;
|
|
9548
9548
|
}
|
|
9549
9549
|
}
|
|
9550
|
-
dset(this.event,
|
|
9550
|
+
dset(this.event, path3, val);
|
|
9551
9551
|
return this.event;
|
|
9552
9552
|
};
|
|
9553
9553
|
CoreContext2.prototype.failedDelivery = function() {
|
|
@@ -10207,8 +10207,8 @@ var stripTrailingSlash, tryCreateFormattedUrl;
|
|
|
10207
10207
|
var init_create_url = __esm({
|
|
10208
10208
|
"../../node_modules/.pnpm/@segment+analytics-node@3.1.0/node_modules/@segment/analytics-node/dist/esm/lib/create-url.js"() {
|
|
10209
10209
|
stripTrailingSlash = (str) => str.replace(/\/$/, "");
|
|
10210
|
-
tryCreateFormattedUrl = (host,
|
|
10211
|
-
return stripTrailingSlash(new URL(
|
|
10210
|
+
tryCreateFormattedUrl = (host, path3) => {
|
|
10211
|
+
return stripTrailingSlash(new URL(path3 || "", host).href);
|
|
10212
10212
|
};
|
|
10213
10213
|
}
|
|
10214
10214
|
});
|
|
@@ -11616,12 +11616,12 @@ var init_publisher = __esm({
|
|
|
11616
11616
|
_rateLimitedUntil;
|
|
11617
11617
|
_rateLimitStartTime;
|
|
11618
11618
|
_abortController = new AbortController();
|
|
11619
|
-
constructor({ host, path:
|
|
11619
|
+
constructor({ host, path: path3, maxRetries, flushAt, flushInterval, writeKey, httpRequestTimeout, httpClient, disable, oauthSettings, maxTotalBackoffDuration, maxRateLimitDuration }, emitter) {
|
|
11620
11620
|
this._emitter = emitter;
|
|
11621
11621
|
this._maxRetries = maxRetries;
|
|
11622
11622
|
this._flushAt = Math.max(flushAt, 1);
|
|
11623
11623
|
this._flushInterval = flushInterval;
|
|
11624
|
-
this._url = tryCreateFormattedUrl(host ?? "https://api.segment.io",
|
|
11624
|
+
this._url = tryCreateFormattedUrl(host ?? "https://api.segment.io", path3 ?? "/v1/batch");
|
|
11625
11625
|
this._httpRequestTimeout = httpRequestTimeout ?? 1e4;
|
|
11626
11626
|
this._disable = Boolean(disable);
|
|
11627
11627
|
this._httpClient = httpClient;
|
|
@@ -13780,12 +13780,12 @@ var init_debug = __esm({
|
|
|
13780
13780
|
};
|
|
13781
13781
|
};
|
|
13782
13782
|
};
|
|
13783
|
-
getValueByStringPath = function(obj,
|
|
13783
|
+
getValueByStringPath = function(obj, path3) {
|
|
13784
13784
|
var e_1, _a;
|
|
13785
|
-
|
|
13786
|
-
|
|
13785
|
+
path3 = path3.replace(/\[(\w+)\]/g, ".$1");
|
|
13786
|
+
path3 = path3.replace(/^\./, "");
|
|
13787
13787
|
try {
|
|
13788
|
-
for (var _b = __values(
|
|
13788
|
+
for (var _b = __values(path3.split(".")), _c = _b.next(); !_c.done; _c = _b.next()) {
|
|
13789
13789
|
var attr = _c.value;
|
|
13790
13790
|
if (attr in obj) {
|
|
13791
13791
|
obj = obj[attr];
|
|
@@ -13810,8 +13810,8 @@ var init_debug = __esm({
|
|
|
13810
13810
|
var res = {};
|
|
13811
13811
|
try {
|
|
13812
13812
|
for (var paths_1 = __values(paths), paths_1_1 = paths_1.next(); !paths_1_1.done; paths_1_1 = paths_1.next()) {
|
|
13813
|
-
var
|
|
13814
|
-
res[
|
|
13813
|
+
var path3 = paths_1_1.value;
|
|
13814
|
+
res[path3] = getValueByStringPath(client, path3);
|
|
13815
13815
|
}
|
|
13816
13816
|
} catch (e_2_1) {
|
|
13817
13817
|
e_2 = { error: e_2_1 };
|
|
@@ -16350,9 +16350,9 @@ function walk(value, prefix, out) {
|
|
|
16350
16350
|
}
|
|
16351
16351
|
if (typeof value === "object") {
|
|
16352
16352
|
for (const [key, v] of Object.entries(value)) {
|
|
16353
|
-
const
|
|
16354
|
-
out.add(
|
|
16355
|
-
walk(v,
|
|
16353
|
+
const path3 = prefix === "" ? key : `${prefix}.${key}`;
|
|
16354
|
+
out.add(path3);
|
|
16355
|
+
walk(v, path3, out);
|
|
16356
16356
|
}
|
|
16357
16357
|
return;
|
|
16358
16358
|
}
|
|
@@ -16367,8 +16367,8 @@ function extractFieldStats(value, prefix = "") {
|
|
|
16367
16367
|
const out = /* @__PURE__ */ new Map();
|
|
16368
16368
|
walkFieldStats(value, prefix, out);
|
|
16369
16369
|
const obj = {};
|
|
16370
|
-
for (const [
|
|
16371
|
-
obj[
|
|
16370
|
+
for (const [path3, stats] of out) {
|
|
16371
|
+
obj[path3] = {
|
|
16372
16372
|
types: [...stats.types].toSorted(),
|
|
16373
16373
|
sampleValues: [...stats.sampleValues].toSorted(comparePrimitiveValues)
|
|
16374
16374
|
};
|
|
@@ -16384,20 +16384,20 @@ function walkFieldStats(value, prefix, out) {
|
|
|
16384
16384
|
}
|
|
16385
16385
|
if (typeof value === "object") {
|
|
16386
16386
|
for (const [key, v] of Object.entries(value)) {
|
|
16387
|
-
const
|
|
16388
|
-
addPathValue(
|
|
16389
|
-
walkFieldStats(v,
|
|
16387
|
+
const path3 = prefix === "" ? key : `${prefix}.${key}`;
|
|
16388
|
+
addPathValue(path3, v, out);
|
|
16389
|
+
walkFieldStats(v, path3, out);
|
|
16390
16390
|
}
|
|
16391
16391
|
}
|
|
16392
16392
|
}
|
|
16393
|
-
function addPathValue(
|
|
16394
|
-
const existing = out.get(
|
|
16393
|
+
function addPathValue(path3, value, out) {
|
|
16394
|
+
const existing = out.get(path3) ?? { types: /* @__PURE__ */ new Set(), sampleValues: /* @__PURE__ */ new Set() };
|
|
16395
16395
|
const t = classifyValueType(value);
|
|
16396
16396
|
existing.types.add(t);
|
|
16397
16397
|
if ((value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") && existing.sampleValues.size < 20) {
|
|
16398
16398
|
existing.sampleValues.add(value);
|
|
16399
16399
|
}
|
|
16400
|
-
out.set(
|
|
16400
|
+
out.set(path3, existing);
|
|
16401
16401
|
}
|
|
16402
16402
|
function classifyValueType(value) {
|
|
16403
16403
|
if (value === null) return "null";
|
|
@@ -16406,15 +16406,15 @@ function classifyValueType(value) {
|
|
|
16406
16406
|
}
|
|
16407
16407
|
function mergeFieldStats(a, b) {
|
|
16408
16408
|
const merged = { ...a };
|
|
16409
|
-
for (const [
|
|
16410
|
-
const prev = merged[
|
|
16409
|
+
for (const [path3, bs] of Object.entries(b)) {
|
|
16410
|
+
const prev = merged[path3];
|
|
16411
16411
|
if (!prev) {
|
|
16412
|
-
merged[
|
|
16412
|
+
merged[path3] = bs;
|
|
16413
16413
|
continue;
|
|
16414
16414
|
}
|
|
16415
16415
|
const types4 = /* @__PURE__ */ new Set([...prev.types, ...bs.types]);
|
|
16416
16416
|
const sampleValues = /* @__PURE__ */ new Set([...prev.sampleValues, ...bs.sampleValues]);
|
|
16417
|
-
merged[
|
|
16417
|
+
merged[path3] = {
|
|
16418
16418
|
types: [...types4].toSorted(),
|
|
16419
16419
|
sampleValues: [...sampleValues].toSorted(comparePrimitiveValues).slice(0, 20)
|
|
16420
16420
|
};
|
|
@@ -16424,10 +16424,10 @@ function mergeFieldStats(a, b) {
|
|
|
16424
16424
|
function sortFieldStats(stats) {
|
|
16425
16425
|
if (!stats) return void 0;
|
|
16426
16426
|
const out = {};
|
|
16427
|
-
for (const
|
|
16428
|
-
out[
|
|
16429
|
-
types: [...stats[
|
|
16430
|
-
sampleValues: [...stats[
|
|
16427
|
+
for (const path3 of Object.keys(stats).toSorted()) {
|
|
16428
|
+
out[path3] = {
|
|
16429
|
+
types: [...stats[path3].types].toSorted(),
|
|
16430
|
+
sampleValues: [...stats[path3].sampleValues].toSorted(
|
|
16431
16431
|
comparePrimitiveValues
|
|
16432
16432
|
)
|
|
16433
16433
|
};
|
|
@@ -16439,6 +16439,51 @@ function comparePrimitiveValues(a, b) {
|
|
|
16439
16439
|
const sb = JSON.stringify(b);
|
|
16440
16440
|
return sa.localeCompare(sb);
|
|
16441
16441
|
}
|
|
16442
|
+
var FileSubscriber = class extends EventSubscriber {
|
|
16443
|
+
name = "FileSubscriber";
|
|
16444
|
+
version = "1.0.0";
|
|
16445
|
+
filePath;
|
|
16446
|
+
pretty;
|
|
16447
|
+
ensureDir;
|
|
16448
|
+
transform;
|
|
16449
|
+
/** Serializes writes so concurrent events never interleave on disk. */
|
|
16450
|
+
writeChain = Promise.resolve();
|
|
16451
|
+
dirEnsured = false;
|
|
16452
|
+
constructor(config) {
|
|
16453
|
+
super();
|
|
16454
|
+
this.filePath = config.path;
|
|
16455
|
+
this.enabled = config.enabled ?? true;
|
|
16456
|
+
this.pretty = config.pretty ?? false;
|
|
16457
|
+
this.ensureDir = config.mkdir ?? true;
|
|
16458
|
+
this.transform = config.transform;
|
|
16459
|
+
}
|
|
16460
|
+
async sendToDestination(payload) {
|
|
16461
|
+
if (!this.enabled) return;
|
|
16462
|
+
const record = this.transform ? this.transform(payload) : payload;
|
|
16463
|
+
if (record === null) return;
|
|
16464
|
+
const json = this.pretty ? JSON.stringify(record, null, 2) : JSON.stringify(record);
|
|
16465
|
+
const line = `${json}
|
|
16466
|
+
`;
|
|
16467
|
+
const run = this.writeChain.then(() => this.write(line));
|
|
16468
|
+
this.writeChain = run.catch(() => {
|
|
16469
|
+
});
|
|
16470
|
+
await run;
|
|
16471
|
+
}
|
|
16472
|
+
async write(line) {
|
|
16473
|
+
if (this.ensureDir && !this.dirEnsured) {
|
|
16474
|
+
const dir = path__default.default.dirname(this.filePath);
|
|
16475
|
+
if (dir && dir !== ".") {
|
|
16476
|
+
await fs.mkdir(dir, { recursive: true });
|
|
16477
|
+
}
|
|
16478
|
+
this.dirEnsured = true;
|
|
16479
|
+
}
|
|
16480
|
+
await fs.appendFile(this.filePath, line, "utf8");
|
|
16481
|
+
}
|
|
16482
|
+
async shutdown() {
|
|
16483
|
+
await this.writeChain;
|
|
16484
|
+
await super.shutdown();
|
|
16485
|
+
}
|
|
16486
|
+
};
|
|
16442
16487
|
|
|
16443
16488
|
// src/streaming-event-subscriber.ts
|
|
16444
16489
|
var StreamingEventSubscriber = class extends EventSubscriber {
|
|
@@ -16596,6 +16641,7 @@ exports.ARCHITECTURE_SNAPSHOT_SPEC = ARCHITECTURE_SNAPSHOT_SPEC;
|
|
|
16596
16641
|
exports.AmplitudeSubscriber = AmplitudeSubscriber;
|
|
16597
16642
|
exports.ArchitectureSnapshotSubscriber = ArchitectureSnapshotSubscriber;
|
|
16598
16643
|
exports.EventSubscriber = EventSubscriber;
|
|
16644
|
+
exports.FileSubscriber = FileSubscriber;
|
|
16599
16645
|
exports.MixpanelSubscriber = MixpanelSubscriber;
|
|
16600
16646
|
exports.PostHogSubscriber = PostHogSubscriber;
|
|
16601
16647
|
exports.SegmentSubscriber = SegmentSubscriber;
|