@voltro/sql-mssql 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.
@@ -0,0 +1,252 @@
1
+ import { CdcConfig } from '@voltro/database';
2
+ import { ChangeEvent } from '@voltro/database';
3
+ import { ChangeStrategy } from '@voltro/database';
4
+ import { ConfigError } from 'effect';
5
+ import { ConnectionConfig } from '@voltro/database';
6
+ import { Context } from 'effect';
7
+ import { DataStore } from '@voltro/database';
8
+ import { DialectReplicationAdapter } from '@voltro/database';
9
+ import { Effect } from 'effect';
10
+ import { EventEmitter } from 'node:events';
11
+ import { Layer } from 'effect';
12
+ import { ManagedRuntime } from 'effect';
13
+ import { MssqlClient } from '@effect/sql-mssql';
14
+ import { Predicate } from '@voltro/database';
15
+ import { QueryDescriptor } from '@voltro/database';
16
+ import { QueryNamespace } from '@voltro/database';
17
+ import { RawSqlFragment } from '@voltro/database/sql';
18
+ import { RetryDecision } from '@voltro/database';
19
+ import { Row } from '@voltro/database';
20
+ import { SqlClient } from '@effect/sql';
21
+ import { SqlClient as SqlClient_2 } from '@effect/sql/SqlClient';
22
+ import { SqlDialect } from '@voltro/database';
23
+ import { SqlError } from '@effect/sql';
24
+ import { TableLike } from '@voltro/database';
25
+ import { TransactionConnection } from '@effect/sql/SqlClient';
26
+
27
+ export declare const CDC_OFFSETS_TABLE = "_voltro_cdc_offsets";
28
+
29
+ export declare interface ChangeTrackingCdcHandle {
30
+ readonly stop: () => Promise<void>;
31
+ /** The last CT version the reader has advanced past (for a final checkpoint). */
32
+ readonly currentVersion: () => string | null;
33
+ }
34
+
35
+ export declare interface ChangeTrackingCdcOptions {
36
+ readonly run: MssqlEffectRunner;
37
+ /** Unqualified table names to tail. Each must have CT enabled (the
38
+ * reader enables them at start via `ensureChangeTracking`). */
39
+ readonly tables: ReadonlyArray<string>;
40
+ /** Poll cadence. Default 500ms. */
41
+ readonly pollIntervalMs?: number;
42
+ /** Resume cursor (decimal string of the CT version). Omit / null →
43
+ * start at the CURRENT version (only future changes). */
44
+ readonly startVersion?: string | null;
45
+ readonly onChange: (event: ChangeEvent) => void;
46
+ /** Called with the CT version after each processed poll (checkpointing). */
47
+ readonly onVersion?: (version: string) => void;
48
+ readonly onError?: (err: unknown) => void;
49
+ /** Called when the reader had to skip forward because the persisted
50
+ * cursor fell below the CT retention floor (auto-cleanup outran us).
51
+ * The caller should treat live state as possibly-stale + re-query. */
52
+ readonly onResync?: () => void;
53
+ }
54
+
55
+ /**
56
+ * Parse a `ConnectionConfig` into the mssql-specific shape.
57
+ *
58
+ * Resolution order:
59
+ * 1. `url` — `mssql://user:pw@server:port/database` (also `sqlserver://`)
60
+ * 2. Discrete `host`/`port`/`username`/`password`/`database`
61
+ * 3. Defaults: server `localhost`, port 1433
62
+ *
63
+ * Sane defaults for dev: `trustServer: true` so the docker SQL Server
64
+ * Developer edition's self-signed cert doesn't trip TLS. Production
65
+ * setups override via env.
66
+ */
67
+ export declare const connectionFromConfig: (config: ConnectionConfig) => MssqlConnection;
68
+
69
+ /**
70
+ * Enable Change Tracking on `tables` (idempotent) + confirm the database
71
+ * itself has CT on. Called by the store at CDC start. Enabling per-table
72
+ * CT is a cheap `ALTER TABLE … ENABLE CHANGE_TRACKING`; re-enabling an
73
+ * already-tracked table is a no-op guarded by `sys.change_tracking_tables`.
74
+ */
75
+ export declare const ensureChangeTracking: (run: MssqlEffectRunner, tables: ReadonlyArray<string>) => Promise<void>;
76
+
77
+ export declare const makeMssqlSqlLayer: (options: MssqlConnection) => ReturnType<typeof MssqlClient.layerConfig>;
78
+
79
+ export declare const makeMssqlSqlLayerFromConfig: (config: ConnectionConfig) => ReturnType<typeof MssqlClient.layerConfig>;
80
+
81
+ export declare const makeMssqlStore: (options: MssqlStoreOptions) => Promise<MssqlStore>;
82
+
83
+ declare interface MssqlAdapterFriend {
84
+ readonly runEffect: <A, E>(effect: Effect.Effect<A, E, SqlClient_2>) => Promise<A>;
85
+ }
86
+
87
+ export { MssqlClient }
88
+
89
+ export declare interface MssqlConnection {
90
+ readonly server: string;
91
+ readonly port?: number;
92
+ readonly database?: string;
93
+ readonly username?: string;
94
+ readonly password?: string;
95
+ readonly encrypt?: boolean;
96
+ readonly trustServer?: boolean;
97
+ readonly maxConnections?: number;
98
+ }
99
+
100
+ export declare const mssqlDialect: SqlDialect;
101
+
102
+ /** Effect runner bound to the store's managed runtime + connection pool
103
+ * (the same `__mssqlReplicationFriend.runEffect` seam the replication
104
+ * adapter uses) — so the CT reader needs no second pool. */
105
+ declare type MssqlEffectRunner = <A, E>(effect: Effect.Effect<A, E, SqlClient_2>) => Promise<A>;
106
+
107
+ export declare const mssqlReplicationAdapter: () => DialectReplicationAdapter;
108
+
109
+ export declare const mssqlRetryFilter: (err: unknown) => RetryDecision;
110
+
111
+ export declare class MssqlStore implements DataStore {
112
+ private readonly sql;
113
+ private readonly runtime;
114
+ private readonly changeStrategy;
115
+ private readonly namespace;
116
+ private readonly emitter;
117
+ private inflightTxns;
118
+ private cdcHandle;
119
+ private cdcCheckpointTimer;
120
+ private cdcPendingVersion;
121
+ private cdcReplicaId;
122
+ private cdcStreamName;
123
+ constructor(sql: SqlClient.SqlClient, runtime: ManagedRuntime.ManagedRuntime<SqlClient.SqlClient | MssqlClient.MssqlClient, never>, changeStrategy?: ChangeStrategy, namespace?: QueryNamespace, sharedEmitter?: EventEmitter);
124
+ /** True when the write path emits its own deltas inline. In 'cdc' mode
125
+ * the CT reader is the sole emitter, so the write path stays silent. */
126
+ private get inlineEmit();
127
+ /**
128
+ * Bind a per-request tenant namespace (a SCHEMA), returning a view that
129
+ * qualifies every table reference to `<schema>.<table>`. Shares this
130
+ * store's pool + emitter + CDC. The schema must already exist — see the
131
+ * migrator's namespace fan-out.
132
+ */
133
+ withNamespace(namespace: string | null): DataStore;
134
+ /** Qualify a table to this view's namespace (no-op when unset). */
135
+ private nsT;
136
+ /**
137
+ * Friend-handle the mssql replication adapter reads via a structural
138
+ * check on `DataStore` (declared in `./replicationAdapter.ts`). The
139
+ * adapter needs raw SQL access against THIS store's managed runtime
140
+ * to query `sys.dm_tran_database_transactions` /
141
+ * `sys.dm_hadr_database_replica_states` — without a friend handle
142
+ * the adapter would need its own connection pool + config, doubling
143
+ * the deployment cost.
144
+ *
145
+ * The handle stays on the store class as a public-but-prefixed
146
+ * property so the type cast in `adapterFriend(store)` resolves
147
+ * without exporting the runtime to the wider API surface.
148
+ */
149
+ readonly __mssqlReplicationFriend: MssqlAdapterFriend;
150
+ private executeQuery;
151
+ private executeInsert;
152
+ private executeUpdate;
153
+ private executeInsertMany;
154
+ private executePatchJson;
155
+ private executeDelete;
156
+ private routeEvent;
157
+ query(d: QueryDescriptor): Promise<readonly Readonly<Record<string, unknown>>[]>;
158
+ raw<T extends object = Row>(fragment: RawSqlFragment, _opts?: {
159
+ dependsOn?: ReadonlyArray<string>;
160
+ }): Promise<ReadonlyArray<T>>;
161
+ private runWithEager;
162
+ /* Excluded from this release type: getInternalRunWithEager */
163
+ insert(t: string, r: Row): Promise<Readonly<Record<string, unknown>>>;
164
+ insertMany(t: string, rows: ReadonlyArray<Row>): Promise<readonly Readonly<Record<string, unknown>>[]>;
165
+ patchJson(t: string, pk: string, path: string, value: unknown): Promise<Readonly<Record<string, unknown>> | null>;
166
+ update(t: string, pk: string, p: Readonly<Record<string, unknown>>): Promise<Readonly<Record<string, unknown>> | null>;
167
+ delete(t: string, pk: string): Promise<boolean>;
168
+ updateMany(table: string, patch: Readonly<Record<string, unknown>>, options: {
169
+ where: Predicate;
170
+ }): Promise<number>;
171
+ deleteMany(table: string, options: {
172
+ where: Predicate;
173
+ }): Promise<number>;
174
+ upsert(t: string, r: Row, options: {
175
+ conflictColumns: ReadonlyArray<string>;
176
+ update?: ReadonlyArray<string> | ((existing: Row) => Readonly<Record<string, unknown>>);
177
+ }): Promise<Readonly<Record<string, unknown>>>;
178
+ insertIgnore(t: string, r: Row, options: {
179
+ conflictColumns: ReadonlyArray<string>;
180
+ }): Promise<Readonly<Record<string, unknown>>>;
181
+ private upsertPatch;
182
+ private executeUpsert;
183
+ /**
184
+ * Single-statement native MERGE upsert. Builds a parameterised
185
+ * `MERGE <t> WITH (HOLDLOCK) AS tgt USING (VALUES (…)) AS src (cols)
186
+ * ON tgt.<ck> = src.<ck>
187
+ * WHEN MATCHED THEN UPDATE SET tgt.<c> = src.<c>
188
+ * WHEN NOT MATCHED THEN INSERT (cols) VALUES (src.cols)
189
+ * OUTPUT $action, INSERTED.*;`
190
+ * — every value binds as a parameter (no interpolation), `$action`
191
+ * distinguishes the insert vs update branch so the change event is
192
+ * emitted with the right `op`, and `INSERTED.*` returns the post-image
193
+ * in the SAME round-trip. Only reached for the column-list / default
194
+ * `update` form; the function form can't express its patch in a MERGE.
195
+ */
196
+ private mergeUpsert;
197
+ private executeInsertIgnore;
198
+ private findByConflict;
199
+ /* Excluded from this release type: emitChange */
200
+ /* Excluded from this release type: startCdcConsumer */
201
+ /* Excluded from this release type: readCdcOffset */
202
+ /* Excluded from this release type: flushCdcOffset */
203
+ /* Excluded from this release type: getInternalExecuteQuery */
204
+ /* Excluded from this release type: getInternalExecuteInsert */
205
+ /* Excluded from this release type: getInternalExecuteInsertMany */
206
+ /* Excluded from this release type: getInternalExecutePatchJson */
207
+ /* Excluded from this release type: getInternalExecuteUpdate */
208
+ /* Excluded from this release type: getInternalExecuteDelete */
209
+ /* Excluded from this release type: getInternalExecuteUpsert */
210
+ /* Excluded from this release type: getInternalExecuteInsertIgnore */
211
+ transactional<T>(work: (tx: DataStore) => Promise<T>): Promise<T>;
212
+ onChange(listener: (event: ChangeEvent) => void): () => void;
213
+ /** 'fleet' under Change Tracking CDC (every replica gets the full
214
+ * stream); 'local' under inline emission. See DataStore.changeScope. */
215
+ get changeScope(): 'local' | 'fleet';
216
+ /** Cross-instance reactivity seam — emit an externally-sourced event to
217
+ * local subscribers without re-persisting. The Change Tracking CDC
218
+ * consumer and `@voltro/plugin-broadcast` route through here. See
219
+ * `DataStore.injectExternalChange`. */
220
+ injectExternalChange(event: ChangeEvent): void;
221
+ run<A, E>(effect: Effect.Effect<A, E, SqlClient.SqlClient>): Promise<A>;
222
+ close(gracePeriodMs?: number): Promise<void>;
223
+ /** Liveness probe — `SELECT 1`. Rejects if the pool can't answer. */
224
+ ping(): Promise<void>;
225
+ }
226
+
227
+ export declare interface MssqlStoreOptions {
228
+ readonly sqlLayer: Layer.Layer<SqlClient.SqlClient | MssqlClient.MssqlClient, ConfigError.ConfigError | SqlError.SqlError, never>;
229
+ readonly tracerLayer?: Layer.Layer<never, never, never>;
230
+ /** `'inline'` (default): the writing instance emits its own deltas.
231
+ * `'cdc'`: a SQL Server Change Tracking reader is the sole emitter, so
232
+ * a write on any replica surfaces on every replica's `onChange`. */
233
+ readonly changeStrategy?: ChangeStrategy;
234
+ /** Change Tracking CDC wiring — required when `changeStrategy: 'cdc'`.
235
+ * `serverId` is unused by mssql (CT has no per-reader identity like a
236
+ * binlog `server_id`); `replicaId` keys the per-pod resume offset and
237
+ * `includeTables` scopes which tables the reader tails. */
238
+ readonly cdcConfig?: CdcConfig;
239
+ }
240
+
241
+ /**
242
+ * Start the Change Tracking reader. Resolves once the initial cursor is
243
+ * resolved + the first poll is armed (so the caller can guarantee
244
+ * "reader attached before subscriptions"). The returned handle stops it.
245
+ */
246
+ export declare const startChangeTrackingCdc: (opts: ChangeTrackingCdcOptions) => Promise<ChangeTrackingCdcHandle>;
247
+
248
+ declare type TxnContext = Context.Tag.Service<typeof TransactionConnection>;
249
+
250
+ export declare const _voltroMssqlCdcOffsetsTable: TableLike;
251
+
252
+ export { }