@c9up/eon 0.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.
Files changed (123) hide show
  1. package/README.md +69 -0
  2. package/dist/EonProvider.d.ts +51 -0
  3. package/dist/EonProvider.d.ts.map +1 -0
  4. package/dist/EonProvider.js +154 -0
  5. package/dist/EonProvider.js.map +1 -0
  6. package/dist/connection/EonConnection.d.ts +116 -0
  7. package/dist/connection/EonConnection.d.ts.map +1 -0
  8. package/dist/connection/EonConnection.js +15 -0
  9. package/dist/connection/EonConnection.js.map +1 -0
  10. package/dist/connection/config.d.ts +56 -0
  11. package/dist/connection/config.d.ts.map +1 -0
  12. package/dist/connection/config.js +14 -0
  13. package/dist/connection/config.js.map +1 -0
  14. package/dist/connection/websocket.d.ts +16 -0
  15. package/dist/connection/websocket.d.ts.map +1 -0
  16. package/dist/connection/websocket.js +383 -0
  17. package/dist/connection/websocket.js.map +1 -0
  18. package/dist/decorators/superTable.d.ts +73 -0
  19. package/dist/decorators/superTable.d.ts.map +1 -0
  20. package/dist/decorators/superTable.js +98 -0
  21. package/dist/decorators/superTable.js.map +1 -0
  22. package/dist/index.d.ts +20 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +19 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/ingest/schemaless.d.ts +22 -0
  27. package/dist/ingest/schemaless.d.ts.map +1 -0
  28. package/dist/ingest/schemaless.js +161 -0
  29. package/dist/ingest/schemaless.js.map +1 -0
  30. package/dist/ingest/sql.d.ts +23 -0
  31. package/dist/ingest/sql.d.ts.map +1 -0
  32. package/dist/ingest/sql.js +59 -0
  33. package/dist/ingest/sql.js.map +1 -0
  34. package/dist/ingest/stmt.d.ts +82 -0
  35. package/dist/ingest/stmt.d.ts.map +1 -0
  36. package/dist/ingest/stmt.js +246 -0
  37. package/dist/ingest/stmt.js.map +1 -0
  38. package/dist/metadata-keys.d.ts +17 -0
  39. package/dist/metadata-keys.d.ts.map +1 -0
  40. package/dist/metadata-keys.js +17 -0
  41. package/dist/metadata-keys.js.map +1 -0
  42. package/dist/query/TimeSeriesQuery.d.ts +121 -0
  43. package/dist/query/TimeSeriesQuery.d.ts.map +1 -0
  44. package/dist/query/TimeSeriesQuery.js +291 -0
  45. package/dist/query/TimeSeriesQuery.js.map +1 -0
  46. package/dist/query/native.d.ts +22 -0
  47. package/dist/query/native.d.ts.map +1 -0
  48. package/dist/query/native.js +135 -0
  49. package/dist/query/native.js.map +1 -0
  50. package/dist/repository/SuperTableRepository.d.ts +80 -0
  51. package/dist/repository/SuperTableRepository.d.ts.map +1 -0
  52. package/dist/repository/SuperTableRepository.js +232 -0
  53. package/dist/repository/SuperTableRepository.js.map +1 -0
  54. package/dist/schema/CreateStableSpec.d.ts +140 -0
  55. package/dist/schema/CreateStableSpec.d.ts.map +1 -0
  56. package/dist/schema/CreateStableSpec.js +41 -0
  57. package/dist/schema/CreateStableSpec.js.map +1 -0
  58. package/dist/schema/EonMigrationRunner.d.ts +71 -0
  59. package/dist/schema/EonMigrationRunner.d.ts.map +1 -0
  60. package/dist/schema/EonMigrationRunner.js +295 -0
  61. package/dist/schema/EonMigrationRunner.js.map +1 -0
  62. package/dist/schema/EonSchema.d.ts +128 -0
  63. package/dist/schema/EonSchema.d.ts.map +1 -0
  64. package/dist/schema/EonSchema.js +309 -0
  65. package/dist/schema/EonSchema.js.map +1 -0
  66. package/dist/schema/Migration.d.ts +47 -0
  67. package/dist/schema/Migration.d.ts.map +1 -0
  68. package/dist/schema/Migration.js +55 -0
  69. package/dist/schema/Migration.js.map +1 -0
  70. package/dist/schema/compile.d.ts +33 -0
  71. package/dist/schema/compile.d.ts.map +1 -0
  72. package/dist/schema/compile.js +70 -0
  73. package/dist/schema/compile.js.map +1 -0
  74. package/dist/schema/sync.d.ts +45 -0
  75. package/dist/schema/sync.d.ts.map +1 -0
  76. package/dist/schema/sync.js +102 -0
  77. package/dist/schema/sync.js.map +1 -0
  78. package/dist/services/connection.d.ts +33 -0
  79. package/dist/services/connection.d.ts.map +1 -0
  80. package/dist/services/connection.js +71 -0
  81. package/dist/services/connection.js.map +1 -0
  82. package/dist/testing/Factory.d.ts +43 -0
  83. package/dist/testing/Factory.d.ts.map +1 -0
  84. package/dist/testing/Factory.js +134 -0
  85. package/dist/testing/Factory.js.map +1 -0
  86. package/dist/testing/FakeEonConnection.d.ts +43 -0
  87. package/dist/testing/FakeEonConnection.d.ts.map +1 -0
  88. package/dist/testing/FakeEonConnection.js +315 -0
  89. package/dist/testing/FakeEonConnection.js.map +1 -0
  90. package/dist/testing/index.d.ts +34 -0
  91. package/dist/testing/index.d.ts.map +1 -0
  92. package/dist/testing/index.js +67 -0
  93. package/dist/testing/index.js.map +1 -0
  94. package/index.darwin-arm64.node +0 -0
  95. package/index.darwin-x64.node +0 -0
  96. package/index.linux-arm64-gnu.node +0 -0
  97. package/index.linux-x64-gnu.node +0 -0
  98. package/index.win32-x64-msvc.node +0 -0
  99. package/package.json +65 -0
  100. package/scripts/copy-napi.mjs +72 -0
  101. package/src/EonProvider.ts +222 -0
  102. package/src/connection/EonConnection.ts +146 -0
  103. package/src/connection/config.ts +60 -0
  104. package/src/connection/websocket.ts +436 -0
  105. package/src/decorators/superTable.ts +169 -0
  106. package/src/index.ts +118 -0
  107. package/src/ingest/schemaless.ts +211 -0
  108. package/src/ingest/sql.ts +73 -0
  109. package/src/ingest/stmt.ts +335 -0
  110. package/src/metadata-keys.ts +20 -0
  111. package/src/query/TimeSeriesQuery.ts +480 -0
  112. package/src/query/native.ts +180 -0
  113. package/src/repository/SuperTableRepository.ts +347 -0
  114. package/src/schema/CreateStableSpec.ts +184 -0
  115. package/src/schema/EonMigrationRunner.ts +378 -0
  116. package/src/schema/EonSchema.ts +396 -0
  117. package/src/schema/Migration.ts +66 -0
  118. package/src/schema/compile.ts +109 -0
  119. package/src/schema/sync.ts +142 -0
  120. package/src/services/connection.ts +89 -0
  121. package/src/testing/Factory.ts +186 -0
  122. package/src/testing/FakeEonConnection.ts +351 -0
  123. package/src/testing/index.ts +81 -0
@@ -0,0 +1,222 @@
1
+ /**
2
+ * EonProvider — Ream provider for the Eon time-series layer.
3
+ *
4
+ * On boot it opens the configured ws-first TDengine connection(s) via
5
+ * `connectWsEon` and registers them in the container (`eon` / `eon.connection`
6
+ * / `eon:<name>`), plus the module-level `services/connection` singleton. The
7
+ * transport-independent compiler is registered under `eon.compiler`. Shutdown
8
+ * closes every connection (fail-open) and releases the singletons.
9
+ *
10
+ * Agnostic leaf: consumes the host framework's app context STRUCTURALLY — it
11
+ * never imports `@c9up/ream`. Any context exposing `config.get(key)` and
12
+ * `container.singleton(token, factory)` satisfies the contract.
13
+ */
14
+
15
+ import type { EonConfig, EonConnectionConfig } from "./connection/config.js";
16
+ import type { EonConnection } from "./connection/EonConnection.js";
17
+ import { connectWsEon } from "./connection/websocket.js";
18
+ import {
19
+ type CompiledStatement,
20
+ compileStatementNative,
21
+ type EonDialect,
22
+ } from "./query/native.js";
23
+
24
+ /**
25
+ * Structural slice of the host framework's app context — only the surface
26
+ * EonProvider uses. Mirrors atlas `AtlasAppContext`. The `token` type mirrors
27
+ * ream's `ServiceToken` union so this can grow into Symbol/ctor tokens without
28
+ * re-coupling to `@c9up/ream`; EonProvider uses `string` tokens today.
29
+ */
30
+ export interface EonAppContext {
31
+ container: {
32
+ singleton(
33
+ token: string | symbol | (new (...args: never[]) => unknown),
34
+ factory: () => unknown,
35
+ ): void;
36
+ };
37
+ config: { get<T = unknown>(key: string): T | undefined };
38
+ }
39
+
40
+ /**
41
+ * The transport-independent compiler service registered under `eon.compiler`.
42
+ */
43
+ export interface EonService {
44
+ compile(spec: object, dialect?: EonDialect): CompiledStatement;
45
+ }
46
+
47
+ /**
48
+ * How EonProvider opens a connection. Defaults to `connectWsEon`; tests inject a
49
+ * fake so the boot/shutdown wiring is exercised without a live TDengine server
50
+ * (eon has no in-process transport, unlike atlas's sqlite).
51
+ */
52
+ export type EonConnector = (
53
+ config: EonConnectionConfig,
54
+ ) => Promise<EonConnection>;
55
+
56
+ export class EonProvider {
57
+ readonly #app: EonAppContext;
58
+ readonly #connect: EonConnector;
59
+ /** connection name → open connection. Populated at boot. */
60
+ readonly #connections = new Map<string, EonConnection>();
61
+ /** Set once a full boot opened connections — guards against a leaking re-boot. */
62
+ #booted = false;
63
+
64
+ constructor(app: EonAppContext, connect: EonConnector = connectWsEon) {
65
+ this.#app = app;
66
+ this.#connect = connect;
67
+ }
68
+
69
+ register(): void {}
70
+
71
+ async boot(): Promise<void> {
72
+ if (this.#booted) {
73
+ throw new Error(
74
+ "EonProvider: boot() has already opened connections; a second boot would overwrite and leak them. Call shutdown() first, or construct a new provider.",
75
+ );
76
+ }
77
+ // The compiler is transport-independent — register it unconditionally.
78
+ const service: EonService = {
79
+ compile: (spec, dialect) => compileStatementNative(spec, dialect),
80
+ };
81
+ this.#app.container.singleton("eon.compiler", () => service);
82
+
83
+ const config =
84
+ this.#app.config.get<EonConfig>("timeseries") ??
85
+ this.#app.config.get<EonConfig>("eon");
86
+ if (!config) return;
87
+
88
+ const { connections, defaultName } = this.#resolveConnections(config);
89
+
90
+ // Open every connection in parallel. `allSettled` lets us distinguish
91
+ // successes from failures without leaking the already-opened ones: on any
92
+ // failure we close every success before rethrowing (no partial-boot leak).
93
+ const entries = Object.entries(connections);
94
+ const results = await Promise.allSettled(
95
+ entries.map(([, settings]) => this.#connect(settings)),
96
+ );
97
+ const failures: Array<{ name: string; error: unknown }> = [];
98
+ const successes: Array<{ name: string; conn: EonConnection }> = [];
99
+ results.forEach((result, i) => {
100
+ const entry = entries[i];
101
+ if (!entry) return; // unreachable — allSettled preserves length/order
102
+ const [name] = entry;
103
+ if (result.status === "fulfilled") {
104
+ successes.push({ name, conn: result.value });
105
+ } else {
106
+ failures.push({ name, error: result.reason });
107
+ }
108
+ });
109
+ const first = failures[0];
110
+ if (first) {
111
+ await Promise.allSettled(successes.map((s) => s.conn.close()));
112
+ const others = failures
113
+ .slice(1)
114
+ .map((f) => `${f.name}: ${String(f.error)}`)
115
+ .join("; ");
116
+ throw new Error(
117
+ `EonProvider: failed to open ${failures.length} connection(s) — ` +
118
+ `'${first.name}' failed: ${String(first.error)}` +
119
+ (others ? ` (also: ${others})` : ""),
120
+ );
121
+ }
122
+
123
+ for (const { name, conn } of successes) {
124
+ this.#connections.set(name, conn);
125
+ }
126
+
127
+ const defaultConn = this.#connections.get(defaultName);
128
+ if (!defaultConn) {
129
+ // Same rollback the connect-failure branch does above — close the
130
+ // already-opened sockets before throwing, else they leak (and #booted
131
+ // stays false, so a retry would open even more).
132
+ await Promise.allSettled(
133
+ [...this.#connections.values()].map((conn) => conn.close()),
134
+ );
135
+ this.#connections.clear();
136
+ throw new Error(
137
+ `EonProvider: default connection '${defaultName}' is not defined in config.timeseries.connections`,
138
+ );
139
+ }
140
+ // Register the container singletons only AFTER validation succeeds. The
141
+ // throw path above registers none, so a failed boot never leaves an
142
+ // `eon:<name>` factory resolving to a now-closed connection.
143
+ for (const { name, conn } of successes) {
144
+ this.#app.container.singleton(`eon:${name}`, () => conn);
145
+ }
146
+ this.#app.container.singleton("eon", () => defaultConn);
147
+ this.#app.container.singleton("eon.connection", () => defaultConn);
148
+
149
+ // Populate the `@c9up/eon/services/connection` singleton so apps can
150
+ // `import connection from '@c9up/eon/services/connection'` anywhere. Lazy
151
+ // import so a type-only discovery scan does not pull the module at
152
+ // construction time.
153
+ const { setConnection } = await import("./services/connection.js");
154
+ setConnection(defaultConn);
155
+ this.#booted = true;
156
+ }
157
+
158
+ async shutdown(): Promise<void> {
159
+ // Close every connection in parallel. `allSettled` (fail-open) so a single
160
+ // stuck close doesn't block the rest, but failures are aggregated and
161
+ // rethrown so supervisors see a non-zero shutdown signal. The map + module
162
+ // singleton are cleared unconditionally (never hand out closed handles).
163
+ const named = [...this.#connections.entries()];
164
+ const results = await Promise.allSettled(named.map(([, c]) => c.close()));
165
+ this.#connections.clear();
166
+ this.#booted = false; // allow a fresh boot() after a clean shutdown
167
+
168
+ const { clearConnection } = await import("./services/connection.js");
169
+ for (const [, conn] of named) clearConnection(conn);
170
+
171
+ const errors = results
172
+ .map((result, i) =>
173
+ result.status === "rejected"
174
+ ? { name: named[i]?.[0] ?? "unknown", error: result.reason }
175
+ : null,
176
+ )
177
+ .filter((x): x is { name: string; error: unknown } => x !== null);
178
+ if (errors.length > 0) {
179
+ const summary = errors
180
+ .map((e) => `'${e.name}': ${String(e.error)}`)
181
+ .join("; ");
182
+ throw new AggregateError(
183
+ errors.map((e) => e.error),
184
+ `EonProvider: ${errors.length} connection(s) failed to close — ${summary}`,
185
+ );
186
+ }
187
+ }
188
+
189
+ /** Normalize the config into a `{ name → EonConnectionConfig }` map + default name. */
190
+ #resolveConnections(config: EonConfig): {
191
+ connections: Record<string, EonConnectionConfig>;
192
+ defaultName: string;
193
+ } {
194
+ if (config.connections && Object.keys(config.connections).length > 0) {
195
+ return {
196
+ connections: config.connections,
197
+ defaultName: config.default ?? "primary",
198
+ };
199
+ }
200
+ // Single-connection shape — promote to a one-entry map under "primary"
201
+ // (mirror atlas: copy the connection fields, drop `default`/`connections`).
202
+ return {
203
+ connections: {
204
+ primary: {
205
+ url: config.url,
206
+ user: config.user,
207
+ password: config.password,
208
+ database: config.database,
209
+ token: config.token,
210
+ timeoutMs: config.timeoutMs,
211
+ connectRetries: config.connectRetries,
212
+ connectBackoffMs: config.connectBackoffMs,
213
+ },
214
+ },
215
+ defaultName: "primary",
216
+ };
217
+ }
218
+ }
219
+
220
+ // ream's provider loader constructs `new mod.default(app)` (atlas parity:
221
+ // `export default class AtlasProvider`). The named export stays for tests.
222
+ export default EonProvider;
@@ -0,0 +1,146 @@
1
+ /**
2
+ * `EonConnection` — the transport-agnostic seam every eon transport satisfies.
3
+ *
4
+ * The WebSocket implementation (`connectWsEon`) is ONE implementation. A
5
+ * deferred native `taos` implementation (`connectNativeEon`, opt-in, a later
6
+ * story — memory `project_eon_transport_decision`) satisfies this same
7
+ * interface, and the locked columnar bulk-ingest STMT method (spike 58-0 AC4)
8
+ * is added onto a live connection in story 58.4. Neither is built here, but the
9
+ * seam must not preclude them — keep `exec`/`query`/`ping`/`close`
10
+ * transport-neutral (no ws-specific type leaks through).
11
+ *
12
+ * `exec`/`query` take LITERAL SQL only. TDengine binds positional `?`
13
+ * placeholders exclusively through the STMT path (58.4 ingest), so the 58.1
14
+ * compiler's `params` array is NOT threaded through `exec`: a compiled
15
+ * param-free statement runs as literal SQL, a parameterised one waits for STMT.
16
+ */
17
+ export interface EonConnection {
18
+ /**
19
+ * Which transport backs this connection. A future native impl reports
20
+ * `"native"`; the in-memory test double (`FakeEonConnection`, 58.6) reports
21
+ * `"fake"`.
22
+ */
23
+ readonly transport: "websocket" | "native" | "fake";
24
+ /** Run a literal DDL/DML statement (CREATE / USE / INSERT). */
25
+ exec(sql: string): Promise<{ rowsAffected: number }>;
26
+ /** Run a literal SELECT; rows are mapped to objects keyed by column name. */
27
+ query<T = Record<string, unknown>>(sql: string): Promise<T[]>;
28
+ /** Liveness probe — a cheap round-trip to the server. */
29
+ ping(): Promise<void>;
30
+ /**
31
+ * Columnar STMT bulk ingest (story 58.4) — the default high-throughput write
32
+ * path. Binds each child table's whole-column typed arrays once per batch
33
+ * through the transport's STMT2 API, so no per-row object crosses the
34
+ * boundary (the locked 58.0 columnar contract, kept even on the ws path).
35
+ *
36
+ * It lives on the connection — not as a free function — because only the
37
+ * transport owns the live STMT handle; the `transport` discriminant is a
38
+ * string, never the raw ws socket. A future native impl satisfies this same
39
+ * agnostic contract, so ingest code never imports a ws-specific type.
40
+ */
41
+ ingestColumnar(request: EonColumnarIngest): Promise<{ rowsAffected: number }>;
42
+ /**
43
+ * Schemaless line-protocol ingest (InfluxDB / OpenTSDB). A documented ~8–10×
44
+ * slower helper than STMT — NOT the default bulk path (AC4). Bypasses SQL
45
+ * entirely; the transport maps the agnostic protocol/precision to its native
46
+ * enums.
47
+ */
48
+ schemaless(
49
+ lines: readonly string[],
50
+ options?: EonSchemalessOptions,
51
+ ): Promise<void>;
52
+ /** Close THIS connection (never the process-global connector — D6). */
53
+ close(): Promise<void>;
54
+ }
55
+
56
+ /**
57
+ * How a bound column's values are typed for the STMT columnar bind path. Mirrors
58
+ * the compiler's `ColumnTypeKind` (camelCase) so ingest code carries the column
59
+ * metadata kind straight through; the transport maps each kind to its native
60
+ * columnar setter. No ws-specific type leaks into this seam.
61
+ */
62
+ export type EonBindKind =
63
+ | "timestamp"
64
+ | "bool"
65
+ | "tinyInt"
66
+ | "smallInt"
67
+ | "int"
68
+ | "bigInt"
69
+ | "float"
70
+ | "double"
71
+ | "varchar"
72
+ | "nchar"
73
+ | "varbinary"
74
+ | "json"
75
+ | "decimal";
76
+
77
+ /**
78
+ * One column's values in struct-of-arrays layout plus the kind that selects the
79
+ * transport's typed setter. `values` holds the WHOLE column (N rows) for metric
80
+ * columns, or a single-element array for a child's tag value.
81
+ */
82
+ export interface EonBoundColumn {
83
+ readonly kind: EonBindKind;
84
+ readonly values: unknown[];
85
+ }
86
+
87
+ /** One child table's columnar batch: its name, tag values, and metric columns. */
88
+ export interface EonChildBatch {
89
+ /** The (deterministic) child-table name bound via the STMT `?` table slot. */
90
+ readonly table: string;
91
+ /** Tag values (one single-element column per tag), bound before the columns. */
92
+ readonly tags: readonly EonBoundColumn[];
93
+ /** Value columns in prepared order (timestamp first); each holds the column. */
94
+ readonly columns: readonly EonBoundColumn[];
95
+ }
96
+
97
+ /**
98
+ * A columnar STMT bulk-ingest request: one prepared template reused across every
99
+ * child batch. `sql` is the compiler-produced STMT template
100
+ * (`INSERT INTO ? USING <stable> (…) TAGS (?) VALUES (?)`).
101
+ */
102
+ export interface EonColumnarIngest {
103
+ readonly sql: string;
104
+ readonly children: readonly EonChildBatch[];
105
+ }
106
+
107
+ /** Line-protocol variants for the schemaless path. */
108
+ export type EonLineProtocol = "influxdb" | "opentsdb-telnet" | "opentsdb-json";
109
+
110
+ /** Timestamp precision for the schemaless path. */
111
+ export type EonSchemalessPrecision =
112
+ | "hours"
113
+ | "minutes"
114
+ | "seconds"
115
+ | "milliseconds"
116
+ | "microseconds"
117
+ | "nanoseconds";
118
+
119
+ /** Options for {@link EonConnection.schemaless}. */
120
+ export interface EonSchemalessOptions {
121
+ /** Line-protocol variant. Default `"influxdb"`. */
122
+ readonly protocol?: EonLineProtocol;
123
+ /** Timestamp precision. Default `"milliseconds"`. */
124
+ readonly precision?: EonSchemalessPrecision;
125
+ /** Child-table TTL in days (0 = none). Default `0`. */
126
+ readonly ttl?: number;
127
+ }
128
+
129
+ /**
130
+ * Error thrown by an eon transport. When the failure originated in the TDengine
131
+ * connector, `code` carries the connector's numeric error code verbatim (never
132
+ * swallowed), so callers can branch on it.
133
+ */
134
+ export class EonConnectionError extends Error {
135
+ /** The underlying TDengine numeric error code, when the failure came from the connector. */
136
+ readonly code?: number;
137
+
138
+ constructor(message: string, options?: { cause?: unknown; code?: number }) {
139
+ super(
140
+ message,
141
+ options?.cause !== undefined ? { cause: options.cause } : undefined,
142
+ );
143
+ this.name = "EonConnectionError";
144
+ this.code = options?.code;
145
+ }
146
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Eon connection configuration — mirrors atlas `ConnectionConfig` /
3
+ * `AtlasDatabaseConfig` (`AtlasProvider.ts`), adapted for the ws-first TDengine
4
+ * transport: pool sizing / pragmas / migrations / schema-verification are
5
+ * dropped (no pool — D7); URL + auth + a connect-retry knob remain.
6
+ *
7
+ * No env-var reading happens here: the host `config.get("timeseries")` supplies
8
+ * the resolved object (agnostic leaf — eon never touches `process.env`).
9
+ */
10
+
11
+ /** One TDengine connection's settings. */
12
+ export interface EonConnectionConfig {
13
+ /** taosAdapter WebSocket URL, e.g. `ws://localhost:6041`. */
14
+ url: string;
15
+ /** Auth user — supplied to the connector via a setter, never embedded in the URL. */
16
+ user?: string;
17
+ /** Auth password. */
18
+ password?: string;
19
+ /** Default database to `USE` on connect. */
20
+ database?: string;
21
+ /** Cloud / bearer token auth (taosAdapter) — alternative to user/password. */
22
+ token?: string;
23
+ /**
24
+ * Per-request timeout in ms, passed to `WSConfig.setTimeOut`. Also used as the
25
+ * overall connect deadline (a stalled WS handshake rejects instead of wedging
26
+ * boot). Unset → no JS-side connect deadline.
27
+ */
28
+ timeoutMs?: number;
29
+ /**
30
+ * Retry the INITIAL connection if it fails — covers a server that starts a
31
+ * moment after the app (docker-compose / k8s cold start). Extra attempts
32
+ * beyond the first (default 0 — single attempt). Not a per-query reconnect
33
+ * (D7: one long-lived connection, no pool).
34
+ */
35
+ connectRetries?: number;
36
+ /** Base backoff in ms between connect attempts (exponential, capped 30s; default 200). */
37
+ connectBackoffMs?: number;
38
+ }
39
+
40
+ /**
41
+ * Full eon config — single-connection OR multi-connection (atlas parity).
42
+ *
43
+ * 58.2 ships single-connection end-to-end; the `default` / `connections` shape
44
+ * is structural (mirrors atlas) but not yet exercised — named connections /
45
+ * taosAdapter HA slot behind this same shape in a later story.
46
+ */
47
+ export interface EonConfig extends EonConnectionConfig {
48
+ /** Name of the default connection when `connections` is set. Defaults to `"primary"`. */
49
+ default?: string;
50
+ /**
51
+ * Named connections. When present, the top-level `url` is treated as
52
+ * `connections[default].url`.
53
+ */
54
+ connections?: Record<string, EonConnectionConfig>;
55
+ }
56
+
57
+ /** Identity helper for typed config authoring — mirrors atlas `defineConfig`. */
58
+ export function defineConfig(config: EonConfig): EonConfig {
59
+ return config;
60
+ }