@interactive-inc/claude-funnel 0.57.0 → 0.58.1

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 (49) hide show
  1. package/dist/bin.js +258 -219
  2. package/dist/claude.d.ts +5 -5
  3. package/dist/claude.js +2 -2
  4. package/dist/{connector-adapter-1PxjN-Uk.d.ts → connector-adapter-DGacCppE.d.ts} +1 -1
  5. package/dist/connectors/discord.d.ts +3 -20
  6. package/dist/connectors/discord.js +1 -1
  7. package/dist/connectors/gh.d.ts +4 -4
  8. package/dist/connectors/schedule.d.ts +1 -1
  9. package/dist/connectors/slack.d.ts +2 -2
  10. package/dist/connectors/slack.js +1 -1
  11. package/dist/diagnostics.d.ts +1 -1
  12. package/dist/{discord-connector-schema-CPgcZkXh.d.ts → discord-connector-schema-CQyfDkLD.d.ts} +18 -1
  13. package/dist/{discord-listener-C0MoKdQO.js → discord-listener-CKsZGTnH.js} +1 -1
  14. package/dist/docs.d.ts +1 -1
  15. package/dist/doctor.d.ts +1 -1
  16. package/dist/{file-process-guard-DI1742H5.d.ts → file-process-guard-B3IFCj_G.d.ts} +5 -5
  17. package/dist/{funnel-diagnostics-qWy5tPSq.d.ts → funnel-diagnostics-K-wON25Y.d.ts} +1 -1
  18. package/dist/{funnel-doctor-BF3Rdgk0.d.ts → funnel-doctor-vxO96TCA.d.ts} +2 -2
  19. package/dist/funnel-log-sqlite-sink-B_5_4ybn.js +301 -0
  20. package/dist/{funnel-recovery-BUBsu7WX.d.ts → funnel-recovery-COExL9MD.d.ts} +1 -1
  21. package/dist/gateway/daemon.js +196 -196
  22. package/dist/gateway.d.ts +2 -2
  23. package/dist/gateway.js +1 -1
  24. package/dist/{index-DEeCwhk2.d.ts → index-B9iyugar.d.ts} +49 -14
  25. package/dist/index.d.ts +17 -16
  26. package/dist/index.js +76 -11
  27. package/dist/{local-config-sync-E_t5_fjw.d.ts → local-config-sync--f739oCJ.d.ts} +8 -8
  28. package/dist/local-config.d.ts +2 -2
  29. package/dist/local-config.js +1 -1
  30. package/dist/logger.d.ts +384 -0
  31. package/dist/logger.js +281 -0
  32. package/dist/{memory-diagnostic-log-BbFVqDzz.js → memory-diagnostic-log-5LzwJ_F7.js} +110 -323
  33. package/dist/{memory-token-prompter-DpCC1_Dn.d.ts → memory-token-prompter-BlFwK9k7.d.ts} +2 -2
  34. package/dist/{profiles-EHTeCOqB.d.ts → profiles-g2qGVOWv.d.ts} +3 -3
  35. package/dist/profiles.d.ts +1 -1
  36. package/dist/recovery.d.ts +1 -1
  37. package/dist/{schedule-listener-DKh0hnkK.d.ts → schedule-listener-DoMPjHZj.d.ts} +2 -2
  38. package/dist/{settings-reader-CBrgz01o.d.ts → settings-reader-DPwqOVUm.d.ts} +1 -1
  39. package/dist/{slack-listener-BDyBqatt.js → slack-listener-C4wlZaOq.js} +18 -5
  40. package/dist/{slack-listener-DFlAzMc7.d.ts → slack-listener-Dj9NFbAJ.d.ts} +2 -1
  41. package/dist/{yaml-render-OhUN-qkS.js → yaml-render-C9Hhjk-0.js} +1 -1
  42. package/package.json +6 -1
  43. /package/dist/{diagnostic-log-Bxe7Bbvw.d.ts → diagnostic-log-Cb3v8P7p.d.ts} +0 -0
  44. /package/dist/{file-system-Wub9Nto4.d.ts → file-system-DxpnnUVb.d.ts} +0 -0
  45. /package/dist/{funnel-docs-dXPokzr5.d.ts → funnel-docs-DYBs1-H_.d.ts} +0 -0
  46. /package/dist/{gh-connector-schema-CU1ojfIF.d.ts → gh-connector-schema-CZzwzvqY.d.ts} +0 -0
  47. /package/dist/{memory-token-prompter-vBXxY20-.js → memory-token-prompter-C7vREzCL.js} +0 -0
  48. /package/dist/{process-runner-D5I_jhYQ.d.ts → process-runner-Cx5O_fTf.d.ts} +0 -0
  49. /package/dist/{settings-schema-zhnMIa8I.d.ts → settings-schema-1hh11jnN.d.ts} +0 -0
package/dist/claude.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { a as ProcessGuard, c as ChannelResolver, i as SessionStore, n as FunnelClaude, o as McpInstaller, r as LaunchOptions, s as GatewayController, t as FileProcessGuard } from "./file-process-guard-DI1742H5.js";
2
- import { n as FunnelFileSystem } from "./file-system-Wub9Nto4.js";
3
- import { t as FunnelProfiles } from "./profiles-EHTeCOqB.js";
4
- import { C as profileSpecSchema, S as localConfigSchema, _ as LOCAL_CONFIG_FILENAME, b as channelSpecSchema, g as ConnectorSpec, h as ChannelSpec, i as FunnelTokenPrompter, m as FunnelLocalConfig, n as FunnelLocalConfigSync, r as LocalConfigSyncResult, t as ConnectorSyncOutcome, v as LocalConfig, x as connectorSpecSchema, y as ProfileSpec } from "./local-config-sync-E_t5_fjw.js";
5
- import { i as funnelJsonSchema, n as NodeFunnelTokenPrompter, r as FunnelLocalConfigWriter, t as MemoryFunnelTokenPrompter } from "./memory-token-prompter-DpCC1_Dn.js";
1
+ import { a as ProcessGuard, c as ChannelResolver, i as SessionStore, n as FunnelClaude, o as McpInstaller, r as LaunchOptions, s as GatewayController, t as FileProcessGuard } from "./file-process-guard-B3IFCj_G.js";
2
+ import { n as FunnelFileSystem } from "./file-system-DxpnnUVb.js";
3
+ import { t as FunnelProfiles } from "./profiles-g2qGVOWv.js";
4
+ import { C as profileSpecSchema, S as localConfigSchema, _ as LOCAL_CONFIG_FILENAME, b as channelSpecSchema, g as ConnectorSpec, h as ChannelSpec, i as FunnelTokenPrompter, m as FunnelLocalConfig, n as FunnelLocalConfigSync, r as LocalConfigSyncResult, t as ConnectorSyncOutcome, v as LocalConfig, x as connectorSpecSchema, y as ProfileSpec } from "./local-config-sync--f739oCJ.js";
5
+ import { i as funnelJsonSchema, n as NodeFunnelTokenPrompter, r as FunnelLocalConfigWriter, t as MemoryFunnelTokenPrompter } from "./memory-token-prompter-BlFwK9k7.js";
6
6
 
7
7
  //#region lib/engine/mcp/mcp.d.ts
8
8
  declare const FUNNEL_MCP_COMMAND = "bun";
package/dist/claude.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { f as settingsSchema, s as resolveFunnelPort, t as gatewayLoopbackUrl } from "./gateway-base-url-6foMXfFf.js";
2
- import { a as FunnelMcp, i as FUNNEL_MCP_NAME, n as FUNNEL_MCP_ARGS, o as FileProcessGuard, r as FUNNEL_MCP_COMMAND, s as FunnelClaude, t as renderYaml } from "./yaml-render-OhUN-qkS.js";
2
+ import { a as FunnelMcp, i as FUNNEL_MCP_NAME, n as FUNNEL_MCP_ARGS, o as FileProcessGuard, r as FUNNEL_MCP_COMMAND, s as FunnelClaude, t as renderYaml } from "./yaml-render-C9Hhjk-0.js";
3
3
  import { t as FunnelDocs } from "./funnel-docs-ng5K8w4j.js";
4
4
  import { a as FunnelLocalConfig, c as connectorSpecSchema, i as FunnelTokenPrompter, l as localConfigSchema, n as NodeFunnelTokenPrompter, o as LOCAL_CONFIG_FILENAME, r as FunnelLocalConfigSync, s as channelSpecSchema, t as funnelJsonSchema, u as profileSpecSchema } from "./local-config-json-schema-DE1zkMcb.js";
5
5
  import { t as FunnelProfiles } from "./profiles-MnXvYfZF.js";
6
- import { n as FunnelLocalConfigWriter, t as MemoryFunnelTokenPrompter } from "./memory-token-prompter-vBXxY20-.js";
6
+ import { n as FunnelLocalConfigWriter, t as MemoryFunnelTokenPrompter } from "./memory-token-prompter-C7vREzCL.js";
7
7
  import { join } from "node:path";
8
8
  import { existsSync, readFileSync } from "node:fs";
9
9
  import { homedir } from "node:os";
@@ -22,4 +22,4 @@ declare abstract class FunnelConnectorAdapter {
22
22
  abstract call(input: CallInput): Promise<unknown>;
23
23
  }
24
24
  //#endregion
25
- export { FunnelConnectorAdapter as n, CallInput as t };
25
+ export { FunnelConnectorAdapter as n, JsonValue as r, CallInput as t };
@@ -1,24 +1,7 @@
1
- import { S as FunnelLogger, b as FunnelConnectorListener, o as ConnectorDiagnosticLog, x as NotifyFn } from "../diagnostic-log-Bxe7Bbvw.js";
2
- import { n as FunnelConnectorAdapter, t as CallInput } from "../connector-adapter-1PxjN-Uk.js";
3
- import { n as discordConnectorSchema, t as DiscordConnectorConfig } from "../discord-connector-schema-CPgcZkXh.js";
1
+ import { S as FunnelLogger, b as FunnelConnectorListener, o as ConnectorDiagnosticLog, x as NotifyFn } from "../diagnostic-log-Cb3v8P7p.js";
2
+ import { n as FunnelConnectorAdapter, t as CallInput } from "../connector-adapter-DGacCppE.js";
3
+ import { n as discordConnectorSchema, r as FunnelHttpClient, t as DiscordConnectorConfig } from "../discord-connector-schema-CQyfDkLD.js";
4
4
 
5
- //#region lib/engine/http/http-client.d.ts
6
- type HttpRequest = {
7
- method: string;
8
- url: string;
9
- headers?: Record<string, string>;
10
- body?: string;
11
- };
12
- type HttpResponse = {
13
- status: number;
14
- ok: boolean;
15
- text(): Promise<string>;
16
- json(): Promise<unknown>;
17
- };
18
- declare abstract class FunnelHttpClient {
19
- abstract fetch(request: HttpRequest): Promise<HttpResponse>;
20
- }
21
- //#endregion
22
5
  //#region lib/engine/connectors/discord-adapter.d.ts
23
6
  type Deps$1 = {
24
7
  config: DiscordConnectorConfig; /** Environment used to resolve a `botTokenEnv` reference. Defaults to process.env. */
@@ -1,3 +1,3 @@
1
- import { n as FunnelDiscordEventProcessor, r as FunnelDiscordAdapter, t as FunnelDiscordListener } from "../discord-listener-C0MoKdQO.js";
1
+ import { n as FunnelDiscordEventProcessor, r as FunnelDiscordAdapter, t as FunnelDiscordListener } from "../discord-listener-CKsZGTnH.js";
2
2
  import { t as discordConnectorSchema } from "../discord-connector-schema-B_N6IXLz.js";
3
3
  export { FunnelDiscordAdapter, FunnelDiscordEventProcessor, FunnelDiscordListener, discordConnectorSchema };
@@ -1,7 +1,7 @@
1
- import { S as FunnelLogger, b as FunnelConnectorListener, o as ConnectorDiagnosticLog, x as NotifyFn } from "../diagnostic-log-Bxe7Bbvw.js";
2
- import { r as FunnelProcessRunner } from "../process-runner-D5I_jhYQ.js";
3
- import { n as FunnelConnectorAdapter, t as CallInput } from "../connector-adapter-1PxjN-Uk.js";
4
- import { n as ghConnectorSchema, t as GhConnectorConfig } from "../gh-connector-schema-CU1ojfIF.js";
1
+ import { S as FunnelLogger, b as FunnelConnectorListener, o as ConnectorDiagnosticLog, x as NotifyFn } from "../diagnostic-log-Cb3v8P7p.js";
2
+ import { r as FunnelProcessRunner } from "../process-runner-Cx5O_fTf.js";
3
+ import { n as FunnelConnectorAdapter, t as CallInput } from "../connector-adapter-DGacCppE.js";
4
+ import { n as ghConnectorSchema, t as GhConnectorConfig } from "../gh-connector-schema-CZzwzvqY.js";
5
5
 
6
6
  //#region lib/engine/connectors/gh-adapter.d.ts
7
7
  type Deps$1 = {
@@ -1,4 +1,4 @@
1
- import { a as ScheduleEntry, c as scheduleEntrySchema, i as ScheduleConnectorConfig, l as ScheduleStateStore, n as ScheduleOnFired, o as scheduleCatchupPolicySchema, r as ScheduleCatchupPolicy, s as scheduleConnectorSchema, t as FunnelScheduleListener } from "../schedule-listener-DKh0hnkK.js";
1
+ import { a as ScheduleEntry, c as scheduleEntrySchema, i as ScheduleConnectorConfig, l as ScheduleStateStore, n as ScheduleOnFired, o as scheduleCatchupPolicySchema, r as ScheduleCatchupPolicy, s as scheduleConnectorSchema, t as FunnelScheduleListener } from "../schedule-listener-DoMPjHZj.js";
2
2
 
3
3
  //#region lib/engine/connectors/match-cron.d.ts
4
4
  /**
@@ -1,5 +1,5 @@
1
- import { n as FunnelConnectorAdapter, t as CallInput } from "../connector-adapter-1PxjN-Uk.js";
2
- import { a as slackConnectorSchema, c as SlackProcessedEmit, d as SlackSkipReason, f as SlackEvent, i as SlackConnectorConfig, l as SlackProcessedSkip, m as SlackReactionEvent, n as SlackOnAppCreated, o as FunnelSlackEventProcessor, p as SlackMessageEvent, r as SlackPreprocessEvent, s as SlackProcessed, t as FunnelSlackListener, u as SlackRawEvent } from "../slack-listener-DFlAzMc7.js";
1
+ import { n as FunnelConnectorAdapter, t as CallInput } from "../connector-adapter-DGacCppE.js";
2
+ import { a as slackConnectorSchema, c as SlackProcessedEmit, d as SlackSkipReason, f as SlackEvent, i as SlackConnectorConfig, l as SlackProcessedSkip, m as SlackReactionEvent, n as SlackOnAppCreated, o as FunnelSlackEventProcessor, p as SlackMessageEvent, r as SlackPreprocessEvent, s as SlackProcessed, t as FunnelSlackListener, u as SlackRawEvent } from "../slack-listener-Dj9NFbAJ.js";
3
3
 
4
4
  //#region lib/engine/connectors/slack-adapter.d.ts
5
5
  type SlackWebClientLike = {
@@ -1,3 +1,3 @@
1
- import { n as FunnelSlackEventProcessor, r as FunnelSlackAdapter, t as FunnelSlackListener } from "../slack-listener-BDyBqatt.js";
1
+ import { n as FunnelSlackEventProcessor, r as FunnelSlackAdapter, t as FunnelSlackListener } from "../slack-listener-C4wlZaOq.js";
2
2
  import { t as slackConnectorSchema } from "../slack-connector-schema-C1zEf4TG.js";
3
3
  export { FunnelSlackAdapter, FunnelSlackEventProcessor, FunnelSlackListener, slackConnectorSchema };
@@ -1,2 +1,2 @@
1
- import { a as DiagnosticsGatewayProbe, c as FunnelDiagnostics, d as DiagnosticEvent, f as previewOf, h as toDiagnosticEvent, i as DiagnosticsChannelSource, l as ReplayResult, m as toDiagnosticConnectionError, n as DiagnoseAllReport, o as DiagnosticsPublisher, p as queryRows, r as DiagnosisStatus, s as DiagnosticsTokenReader, t as ChannelDiagnosis, u as DiagnosticConnectionError } from "./funnel-diagnostics-qWy5tPSq.js";
1
+ import { a as DiagnosticsGatewayProbe, c as FunnelDiagnostics, d as DiagnosticEvent, f as previewOf, h as toDiagnosticEvent, i as DiagnosticsChannelSource, l as ReplayResult, m as toDiagnosticConnectionError, n as DiagnoseAllReport, o as DiagnosticsPublisher, p as queryRows, r as DiagnosisStatus, s as DiagnosticsTokenReader, t as ChannelDiagnosis, u as DiagnosticConnectionError } from "./funnel-diagnostics-K-wON25Y.js";
2
2
  export { ChannelDiagnosis, DiagnoseAllReport, DiagnosisStatus, DiagnosticConnectionError, DiagnosticEvent, DiagnosticsChannelSource, DiagnosticsGatewayProbe, DiagnosticsPublisher, DiagnosticsTokenReader, FunnelDiagnostics, ReplayResult, previewOf, queryRows, toDiagnosticConnectionError, toDiagnosticEvent };
@@ -1,5 +1,22 @@
1
1
  import { z } from "zod";
2
2
 
3
+ //#region lib/engine/http/http-client.d.ts
4
+ type HttpRequest = {
5
+ method: string;
6
+ url: string;
7
+ headers?: Record<string, string>;
8
+ body?: string;
9
+ };
10
+ type HttpResponse = {
11
+ status: number;
12
+ ok: boolean;
13
+ text(): Promise<string>;
14
+ json(): Promise<unknown>;
15
+ };
16
+ declare abstract class FunnelHttpClient {
17
+ abstract fetch(request: HttpRequest): Promise<HttpResponse>;
18
+ }
19
+ //#endregion
3
20
  //#region lib/engine/connectors/discord-connector-schema.d.ts
4
21
  /**
5
22
  * Like slack, a discord connector holds either a literal `botToken` or a
@@ -19,4 +36,4 @@ declare const discordConnectorSchema: z.ZodObject<{
19
36
  }, z.core.$strip>;
20
37
  type DiscordConnectorConfig = z.infer<typeof discordConnectorSchema>;
21
38
  //#endregion
22
- export { discordConnectorSchema as n, DiscordConnectorConfig as t };
39
+ export { HttpResponse as a, HttpRequest as i, discordConnectorSchema as n, FunnelHttpClient as r, DiscordConnectorConfig as t };
@@ -224,4 +224,4 @@ const messageOf = (error) => {
224
224
  return error instanceof Error ? error.message : String(error);
225
225
  };
226
226
  //#endregion
227
- export { FunnelDiscordEventProcessor as n, FunnelDiscordAdapter as r, FunnelDiscordListener as t };
227
+ export { FunnelHttpClient as a, NodeFunnelHttpClient as i, FunnelDiscordEventProcessor as n, FunnelDiscordAdapter as r, FunnelDiscordListener as t };
package/dist/docs.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { n as FunnelDocs, t as DocsTopicListing } from "./funnel-docs-dXPokzr5.js";
1
+ import { n as FunnelDocs, t as DocsTopicListing } from "./funnel-docs-DYBs1-H_.js";
2
2
  export { DocsTopicListing, FunnelDocs };
package/dist/doctor.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { n as DoctorReport, r as FunnelDoctor, t as DoctorFixMode } from "./funnel-doctor-BF3Rdgk0.js";
1
+ import { n as DoctorReport, r as FunnelDoctor, t as DoctorFixMode } from "./funnel-doctor-vxO96TCA.js";
2
2
  export { DoctorFixMode, DoctorReport, FunnelDoctor };
@@ -1,8 +1,8 @@
1
- import { t as ChannelConfig } from "./settings-schema-zhnMIa8I.js";
2
- import { n as FunnelIdGenerator } from "./settings-reader-CBrgz01o.js";
3
- import { S as FunnelLogger } from "./diagnostic-log-Bxe7Bbvw.js";
4
- import { r as FunnelProcessRunner } from "./process-runner-D5I_jhYQ.js";
5
- import { n as FunnelFileSystem } from "./file-system-Wub9Nto4.js";
1
+ import { t as ChannelConfig } from "./settings-schema-1hh11jnN.js";
2
+ import { n as FunnelIdGenerator } from "./settings-reader-DPwqOVUm.js";
3
+ import { S as FunnelLogger } from "./diagnostic-log-Cb3v8P7p.js";
4
+ import { r as FunnelProcessRunner } from "./process-runner-Cx5O_fTf.js";
5
+ import { n as FunnelFileSystem } from "./file-system-DxpnnUVb.js";
6
6
 
7
7
  //#region lib/engine/claude/channel-resolver.d.ts
8
8
  type ChannelResolver = {
@@ -1,4 +1,4 @@
1
- import { t as ChannelConfig } from "./settings-schema-zhnMIa8I.js";
1
+ import { t as ChannelConfig } from "./settings-schema-1hh11jnN.js";
2
2
 
3
3
  //#region lib/gateway/diagnostic-log/diagnostic-sql-reader.d.ts
4
4
  type Props$1 = {
@@ -1,5 +1,5 @@
1
- import { c as FunnelDiagnostics, n as DiagnoseAllReport, t as ChannelDiagnosis } from "./funnel-diagnostics-qWy5tPSq.js";
2
- import { n as RecoveryAction, t as FunnelRecovery } from "./funnel-recovery-BUBsu7WX.js";
1
+ import { c as FunnelDiagnostics, n as DiagnoseAllReport, t as ChannelDiagnosis } from "./funnel-diagnostics-K-wON25Y.js";
2
+ import { n as RecoveryAction, t as FunnelRecovery } from "./funnel-recovery-COExL9MD.js";
3
3
 
4
4
  //#region lib/services/doctor/funnel-doctor.d.ts
5
5
  type Props = {
@@ -0,0 +1,301 @@
1
+ import { dirname } from "node:path";
2
+ import { existsSync, mkdirSync } from "node:fs";
3
+ import { Database } from "bun:sqlite";
4
+ //#region lib/logger/funnel-log-sqlite-sink.ts
5
+ /** Conservative whitelist for column names interpolated into SQL. */
6
+ const COLUMN_NAME_RE = /^[a-z_][a-z0-9_]*$/;
7
+ /** How many inserts between on-disk size checks (see insertsSinceByteCheck). */
8
+ const BYTE_CHECK_INTERVAL = 500;
9
+ const RESERVED_COLUMNS = new Set([
10
+ "seq",
11
+ "ts",
12
+ "type",
13
+ "event"
14
+ ]);
15
+ /**
16
+ * Schema versions. Each entry is the list of DDL statements that take the
17
+ * database from version i to version i + 1. Migrations run in a transaction
18
+ * so a partial failure rolls back. Adding a new version is append-only —
19
+ * never edit a published one. Caller-defined index columns are added
20
+ * dynamically on construct (independent of versioned migrations) because
21
+ * they are configuration, not schema evolution.
22
+ */
23
+ const MIGRATIONS = [[
24
+ "CREATE TABLE IF NOT EXISTS logs (seq INTEGER PRIMARY KEY, ts INTEGER NOT NULL, type TEXT, event TEXT NOT NULL)",
25
+ "CREATE INDEX IF NOT EXISTS idx_logs_ts ON logs (ts)",
26
+ "CREATE INDEX IF NOT EXISTS idx_logs_type ON logs (type)"
27
+ ], [
28
+ "DROP TABLE IF EXISTS leuco_log",
29
+ "CREATE TABLE IF NOT EXISTS logs (seq INTEGER PRIMARY KEY, ts INTEGER NOT NULL, type TEXT, event TEXT NOT NULL)",
30
+ "CREATE INDEX IF NOT EXISTS idx_logs_ts ON logs (ts)",
31
+ "CREATE INDEX IF NOT EXISTS idx_logs_type ON logs (type)"
32
+ ]];
33
+ /**
34
+ * SQLite-backed sink built on `bun:sqlite`. Implements both primary and
35
+ * relay roles so the same instance can own seq generation for one bus and
36
+ * mirror records from another (e.g. cross-process replication, restore
37
+ * from a backup stream).
38
+ *
39
+ * Concurrency model: seq is `INTEGER PRIMARY KEY`, so SQLite assigns it
40
+ * atomically via `lastInsertRowid`. Two `FunnelLog` instances pointed
41
+ * at the same database file therefore see one monotonically increasing
42
+ * seq stream without any bus-level coordination — the database itself is
43
+ * the synchronization point.
44
+ *
45
+ * Schema is version-managed via `PRAGMA user_version`. Migrations are
46
+ * append-only and run in a transaction on every construct so a partial
47
+ * upgrade rolls back cleanly. Caller-defined `indexes` are layered on top
48
+ * via `ALTER TABLE ADD COLUMN` + `CREATE INDEX IF NOT EXISTS`, so adding
49
+ * a new index to an existing database is a no-downtime operation.
50
+ *
51
+ * Type safety: the second generic parameter `I` is the literal tuple of
52
+ * index column names. `extractIndexes` and `query({ where })` are
53
+ * both type-checked against this tuple, so a typo at the call site is a
54
+ * compile-time error rather than a silent miss at runtime.
55
+ *
56
+ * Retention is bounded by `maxRows` and/or `maxAgeMs`. Both run on every
57
+ * insert as a single indexed DELETE that no-ops below the cap.
58
+ *
59
+ * Bulk inserts use `insertMany`, which wraps the batch in one transaction
60
+ * for ~10–100x throughput at the cost of one fsync per batch instead of
61
+ * one per row.
62
+ */
63
+ var FunnelLogSqliteSink = class {
64
+ db;
65
+ maxRows;
66
+ maxAgeMs;
67
+ maxBytes;
68
+ targetBytes;
69
+ now;
70
+ indexes;
71
+ extractIndexes;
72
+ insertStmt;
73
+ insertWithSeqStmt;
74
+ maxSeqStmt;
75
+ countStmt;
76
+ trimRowsStmt;
77
+ trimAgeStmt;
78
+ trimOldestStmt;
79
+ insertsSinceByteCheck = 0;
80
+ constructor(props) {
81
+ if (props.path !== ":memory:") {
82
+ const dir = dirname(props.path);
83
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
84
+ }
85
+ this.db = new Database(props.path);
86
+ this.db.run("PRAGMA journal_mode = WAL");
87
+ this.migrate();
88
+ this.maxRows = props.maxRows ?? null;
89
+ this.maxAgeMs = props.maxAgeMs ?? null;
90
+ this.maxBytes = props.maxBytes ?? null;
91
+ this.targetBytes = props.targetBytes ?? (props.maxBytes !== void 0 ? Math.floor(props.maxBytes / 4) : null);
92
+ this.now = props.now ?? (() => Date.now());
93
+ this.indexes = props.indexes ?? [];
94
+ if (this.indexes.length > 0) {
95
+ validateIndexNames(this.indexes);
96
+ this.extractIndexes = props.extractIndexes ?? null;
97
+ this.syncIndexColumns();
98
+ } else this.extractIndexes = null;
99
+ const cols = [
100
+ "ts",
101
+ "type",
102
+ "event",
103
+ ...this.indexes
104
+ ];
105
+ const placeholders = cols.map(() => "?").join(", ");
106
+ this.insertStmt = this.db.prepare(`INSERT INTO logs (${cols.join(", ")}) VALUES (${placeholders})`);
107
+ const colsWithSeq = ["seq", ...cols];
108
+ const placeholdersWithSeq = colsWithSeq.map(() => "?").join(", ");
109
+ this.insertWithSeqStmt = this.db.prepare(`INSERT INTO logs (${colsWithSeq.join(", ")}) VALUES (${placeholdersWithSeq})`);
110
+ this.maxSeqStmt = this.db.prepare("SELECT COALESCE(MAX(seq), 0) AS max FROM logs");
111
+ this.countStmt = this.db.prepare("SELECT COUNT(*) AS n FROM logs");
112
+ this.trimRowsStmt = this.db.prepare("DELETE FROM logs WHERE seq <= (SELECT seq FROM logs ORDER BY seq DESC LIMIT 1 OFFSET ?)");
113
+ this.trimAgeStmt = this.db.prepare("DELETE FROM logs WHERE ts < ?");
114
+ this.trimOldestStmt = this.db.prepare("DELETE FROM logs WHERE seq IN (SELECT seq FROM logs ORDER BY seq ASC LIMIT ?)");
115
+ }
116
+ insert(input) {
117
+ try {
118
+ const params = this.buildInsertParams(input.ts, input.event);
119
+ const result = this.insertStmt.run(...params);
120
+ const seq = Number(result.lastInsertRowid);
121
+ this.trim();
122
+ return {
123
+ seq,
124
+ ts: input.ts,
125
+ event: input.event
126
+ };
127
+ } catch (e) {
128
+ return e instanceof Error ? e : new Error(String(e));
129
+ }
130
+ }
131
+ insertMany(inputs) {
132
+ if (inputs.length === 0) return [];
133
+ try {
134
+ const records = [];
135
+ this.db.transaction((batch) => {
136
+ for (const input of batch) {
137
+ const params = this.buildInsertParams(input.ts, input.event);
138
+ const result = this.insertStmt.run(...params);
139
+ records.push({
140
+ seq: Number(result.lastInsertRowid),
141
+ ts: input.ts,
142
+ event: input.event
143
+ });
144
+ }
145
+ })(inputs);
146
+ this.trim();
147
+ return records;
148
+ } catch (e) {
149
+ return e instanceof Error ? e : new Error(String(e));
150
+ }
151
+ }
152
+ write(record) {
153
+ try {
154
+ const params = [record.seq, ...this.buildInsertParams(record.ts, record.event)];
155
+ this.insertWithSeqStmt.run(...params);
156
+ this.trim();
157
+ } catch (e) {
158
+ return e instanceof Error ? e : new Error(String(e));
159
+ }
160
+ }
161
+ getMaxSeq() {
162
+ const row = this.maxSeqStmt.get();
163
+ return row ? row.max : 0;
164
+ }
165
+ query(props = {}) {
166
+ const conditions = ["seq > ?"];
167
+ const params = [props.sinceSeq ?? 0];
168
+ if (typeof props.type === "string") {
169
+ conditions.push("type = ?");
170
+ params.push(props.type);
171
+ }
172
+ if (props.where) this.appendWhereConditions(props.where, conditions, params);
173
+ const limit = props.limit ?? 1e3;
174
+ params.push(limit);
175
+ const dir = props.order === "desc" ? "DESC" : "ASC";
176
+ const sql = `SELECT seq, ts, type, event FROM logs WHERE ${conditions.join(" AND ")} ORDER BY seq ${dir} LIMIT ?`;
177
+ const rows = this.db.prepare(sql).all(...params);
178
+ if (dir === "DESC") rows.reverse();
179
+ return rows.map(toRecord);
180
+ }
181
+ /**
182
+ * Current schema version. Useful for diagnostics and for tests that want
183
+ * to verify migrations ran. Reads `PRAGMA user_version` once per call.
184
+ */
185
+ getSchemaVersion() {
186
+ return this.db.prepare("PRAGMA user_version").get()?.user_version ?? 0;
187
+ }
188
+ close() {
189
+ this.db.close();
190
+ }
191
+ buildInsertParams(ts, event) {
192
+ const type = extractType(event);
193
+ const json = JSON.stringify(event);
194
+ if (this.indexes.length === 0) return [
195
+ ts,
196
+ type,
197
+ json
198
+ ];
199
+ const values = this.extractIndexes ? this.extractIndexes(event) : null;
200
+ return [
201
+ ts,
202
+ type,
203
+ json,
204
+ ...this.indexes.map((col) => values?.[col] ?? null)
205
+ ];
206
+ }
207
+ appendWhereConditions(where, conditions, params) {
208
+ const widened = where;
209
+ for (const col of this.indexes) {
210
+ const value = widened[col];
211
+ if (value === void 0) continue;
212
+ if (value === null) conditions.push(`${col} IS NULL`);
213
+ else {
214
+ conditions.push(`${col} = ?`);
215
+ params.push(value);
216
+ }
217
+ }
218
+ }
219
+ trim() {
220
+ if (this.maxRows !== null) {
221
+ const row = this.countStmt.get();
222
+ if (row && row.n > this.maxRows) this.trimRowsStmt.run(this.maxRows);
223
+ }
224
+ if (this.maxAgeMs !== null) this.trimAgeStmt.run(this.now() - this.maxAgeMs);
225
+ this.maybeTrimBytes();
226
+ }
227
+ /**
228
+ * Throttled byte-size enforcement. Only every BYTE_CHECK_INTERVAL inserts do
229
+ * we measure the file; on overflow we estimate how many of the oldest rows to
230
+ * drop to land near targetBytes (by the byte/row ratio), delete them in one
231
+ * statement, then VACUUM once to return the freed pages to the filesystem (a
232
+ * plain DELETE only frees pages inside the file). One DELETE + one VACUUM per
233
+ * overflow keeps the expensive rewrite rare — the file must refill the whole
234
+ * maxBytes→targetBytes delta before the next overflow can trigger.
235
+ */
236
+ maybeTrimBytes() {
237
+ if (this.maxBytes === null || this.targetBytes === null) return;
238
+ this.insertsSinceByteCheck += 1;
239
+ if (this.insertsSinceByteCheck < BYTE_CHECK_INTERVAL) return;
240
+ this.insertsSinceByteCheck = 0;
241
+ const bytes = this.byteSize();
242
+ if (bytes <= this.maxBytes) return;
243
+ const rows = this.countStmt.get()?.n ?? 0;
244
+ if (rows === 0) return;
245
+ const bytesToFree = bytes - this.targetBytes;
246
+ const bytesPerRow = bytes / rows;
247
+ const rowsToDrop = Math.min(rows, Math.ceil(bytesToFree / bytesPerRow));
248
+ this.trimOldestStmt.run(rowsToDrop);
249
+ this.db.run("VACUUM");
250
+ }
251
+ byteSize() {
252
+ return (this.db.prepare("PRAGMA page_count").get()?.n ?? 0) * (this.db.prepare("PRAGMA page_size").get()?.n ?? 0);
253
+ }
254
+ /** Drop every row and reclaim the file space. Used by `<log>.clear()`. */
255
+ clear() {
256
+ this.db.run("DELETE FROM logs");
257
+ this.db.run("VACUUM");
258
+ this.insertsSinceByteCheck = 0;
259
+ }
260
+ syncIndexColumns() {
261
+ const existing = new Set(this.db.prepare("PRAGMA table_info(logs)").all().map((r) => r.name));
262
+ for (const col of this.indexes) {
263
+ if (!existing.has(col)) this.db.run(`ALTER TABLE logs ADD COLUMN ${col} TEXT`);
264
+ this.db.run(`CREATE INDEX IF NOT EXISTS idx_logs_${col} ON logs (${col})`);
265
+ }
266
+ }
267
+ migrate() {
268
+ const current = this.db.prepare("PRAGMA user_version").get()?.user_version ?? 0;
269
+ if (current >= MIGRATIONS.length) return;
270
+ const pending = MIGRATIONS.slice(current);
271
+ let version = current;
272
+ for (const stmts of pending) {
273
+ version += 1;
274
+ this.db.transaction(() => {
275
+ for (const stmt of stmts) this.db.run(stmt);
276
+ this.db.run(`PRAGMA user_version = ${version}`);
277
+ })();
278
+ }
279
+ }
280
+ };
281
+ function validateIndexNames(names) {
282
+ for (const name of names) {
283
+ if (!COLUMN_NAME_RE.test(name)) throw new Error(`invalid index column name: ${name}`);
284
+ if (RESERVED_COLUMNS.has(name)) throw new Error(`reserved index column name: ${name}`);
285
+ }
286
+ }
287
+ function extractType(event) {
288
+ if (typeof event !== "object" || event === null) return null;
289
+ if (!("type" in event)) return null;
290
+ const t = event.type;
291
+ return typeof t === "string" ? t : null;
292
+ }
293
+ function toRecord(row) {
294
+ return {
295
+ seq: row.seq,
296
+ ts: row.ts,
297
+ event: JSON.parse(row.event)
298
+ };
299
+ }
300
+ //#endregion
301
+ export { FunnelLogSqliteSink as t };
@@ -1,4 +1,4 @@
1
- import { t as ChannelConfig } from "./settings-schema-zhnMIa8I.js";
1
+ import { t as ChannelConfig } from "./settings-schema-1hh11jnN.js";
2
2
 
3
3
  //#region lib/services/recovery/funnel-recovery.d.ts
4
4
  /** Narrow gateway control — start / stop / restart and a probe. */