@delali/sirannon-db 0.1.4 → 0.1.5

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.
@@ -1,12 +1,15 @@
1
- export { B as BackupManager, a as BackupScheduler } from '../index-hXiis3N-.js';
1
+ export { B as BackupManager, a as BackupScheduler } from '../index-CLdNrcPz.js';
2
+ export { C as ChangeTracker, a as ChangeTrackerOptions } from '../change-tracker-CFTQ9TSn.js';
2
3
  import { S as SQLiteDriver, a as SQLiteConnection } from '../types-BFSsG77t.js';
3
4
  export { D as DriverCapabilities, O as OpenOptions, R as RunResult, b as SQLiteStatement } from '../types-BFSsG77t.js';
4
- import { D as Database } from '../sirannon-B1oTfebD.js';
5
- export { H as HookDispose, a as HookEvent, b as HookEventContextMap, c as HookHandler, d as HookRegistry, M as MetricsCollector, S as Sirannon, e as SubscribeHookContext } from '../sirannon-B1oTfebD.js';
6
- import { D as DatabaseOptions, L as LifecycleConfig, P as Params, E as ExecuteResult } from '../types-DtDutWRU.js';
7
- export { A as AfterQueryHook, e as BackupScheduleOptions, h as BeforeConnectHook, f as BeforeQueryHook, B as BeforeSubscribeHook, c as CDCMetrics, k as ChangeEvent, l as ChangeOperation, m as ClientOptions, C as ConnectionHookContext, b as ConnectionMetrics, n as CorsOptions, j as DatabaseCloseHook, i as DatabaseOpenHook, H as HookConfig, M as MetricsConfig, O as OnRequestHook, Q as QueryHookContext, a as QueryMetrics, R as RequestContext, o as RequestDenial, S as ServerOptions, g as SirannonOptions, p as Subscription, d as SubscriptionBuilder, W as WSHandlerOptions } from '../types-DtDutWRU.js';
8
- import { M as Migration, a as MigrationResult, R as RollbackResult } from '../types-DRkJlqex.js';
9
- export { A as AppliedMigration, b as AppliedMigrationEntry, T as Transaction } from '../types-DRkJlqex.js';
5
+ import { D as Database } from '../database-BVY1GqE7.js';
6
+ export { H as HookDispose, a as HookEvent, b as HookEventContextMap, c as HookHandler, d as HookRegistry, M as MetricsCollector, S as SubscribeHookContext } from '../database-BVY1GqE7.js';
7
+ export { B as BackupError, C as CDCError, a as ConnectionPoolError, D as DatabaseAlreadyExistsError, b as DatabaseNotFoundError, E as ExtensionError, H as HookDeniedError, M as MaxDatabasesError, c as MigrationError, Q as QueryError, R as ReadOnlyError, S as SirannonError, T as TransactionError } from '../errors-C00ed08Q.js';
8
+ import { D as DatabaseOptions, L as LifecycleConfig, P as Params, E as ExecuteResult } from '../types-D-74JiXb.js';
9
+ export { A as AfterQueryHook, m as BackupScheduleOptions, a as BeforeConnectHook, B as BeforeQueryHook, g as BeforeSubscribeHook, j as CDCMetrics, C as ChangeEvent, n as ChangeOperation, o as ClientOptions, p as ClusterReadEndpointInfo, q as ClusterStatusInfo, f as ConnectionHookContext, i as ConnectionMetrics, r as CorsOptions, c as DatabaseCloseHook, b as DatabaseOpenHook, H as HookConfig, M as MetricsConfig, O as OnRequestHook, Q as QueryHookContext, h as QueryMetrics, k as QueryOptions, R as ReadConcern, s as ReadConcernLevel, t as ReplicationStatusInfo, u as RequestContext, v as RequestDenial, w as ServerExecutionTarget, x as ServerExecutionTargetResolver, d as ServerOptions, S as SirannonOptions, y as Subscription, l as SubscriptionBuilder, T as Transaction, e as WSHandlerOptions, W as WriteConcern, z as WriteConcernLevel } from '../types-D-74JiXb.js';
10
+ import { M as Migration, a as MigrationResult, R as RollbackResult } from '../types-BeozgNPr.js';
11
+ export { A as AppliedMigration, b as AppliedMigrationEntry } from '../types-BeozgNPr.js';
12
+ export { S as Sirannon } from '../sirannon-Cd-lK6T0.js';
10
13
 
11
14
  interface ConnectionPoolOptions {
12
15
  driver: SQLiteDriver;
@@ -31,106 +34,6 @@ declare class ConnectionPool {
31
34
 
32
35
  declare function defineDriver(config: SQLiteDriver): SQLiteDriver;
33
36
 
34
- /**
35
- * Base class for all sirannon-db errors. Extend this class to create
36
- * domain-specific errors that carry a machine-readable {@link code}.
37
- */
38
- declare class SirannonError extends Error {
39
- readonly code: string;
40
- constructor(message: string, code: string);
41
- }
42
- /**
43
- * Thrown when a database ID cannot be resolved in the registry.
44
- * This typically means the database was never opened or has already been closed.
45
- */
46
- declare class DatabaseNotFoundError extends SirannonError {
47
- constructor(id: string);
48
- }
49
- /**
50
- * Thrown when attempting to register a database with an ID that is already
51
- * in use. Each database ID must be unique within the registry.
52
- */
53
- declare class DatabaseAlreadyExistsError extends SirannonError {
54
- constructor(id: string);
55
- }
56
- /**
57
- * Thrown when a write operation is attempted on a database that was opened
58
- * in read-only mode.
59
- */
60
- declare class ReadOnlyError extends SirannonError {
61
- constructor(id: string);
62
- }
63
- /**
64
- * Thrown when SQLite fails to execute a statement. The {@link sql} property
65
- * holds the original SQL string that caused the failure, which is useful for
66
- * debugging and logging.
67
- */
68
- declare class QueryError extends SirannonError {
69
- readonly sql: string;
70
- constructor(message: string, sql: string);
71
- }
72
- /**
73
- * Thrown when a transaction cannot be committed or is forcibly rolled back.
74
- * Check the message for the underlying cause.
75
- */
76
- declare class TransactionError extends SirannonError {
77
- constructor(message: string);
78
- }
79
- /**
80
- * Thrown when a migration step fails. The {@link version} property identifies
81
- * which schema version triggered the error so the failure can be pinpointed
82
- * in the migration history.
83
- */
84
- declare class MigrationError extends SirannonError {
85
- readonly version: number;
86
- constructor(message: string, version: number, code?: string);
87
- }
88
- /**
89
- * Thrown when a before-hook explicitly rejects an operation. The optional
90
- * `reason` string is surfaced in the message so callers can distinguish
91
- * between different hook policies.
92
- */
93
- declare class HookDeniedError extends SirannonError {
94
- constructor(hookName: string, reason?: string);
95
- }
96
- /**
97
- * Thrown when the change-data-capture pipeline encounters an unrecoverable
98
- * error, such as a failed event dispatch or a corrupt change record.
99
- */
100
- declare class CDCError extends SirannonError {
101
- constructor(message: string);
102
- }
103
- /**
104
- * Thrown when a backup operation fails, whether that is an online backup via
105
- * the SQLite backup API or a file-level copy.
106
- */
107
- declare class BackupError extends SirannonError {
108
- constructor(message: string);
109
- }
110
- /**
111
- * Thrown when the connection pool reaches its limit or is configured with
112
- * invalid parameters such as a minimum size greater than the maximum.
113
- */
114
- declare class ConnectionPoolError extends SirannonError {
115
- constructor(message: string);
116
- }
117
- /**
118
- * Thrown when opening a new database would exceed the configured cap on
119
- * concurrently open databases. Close an existing database before opening
120
- * another one.
121
- */
122
- declare class MaxDatabasesError extends SirannonError {
123
- constructor(max: number);
124
- }
125
- /**
126
- * Thrown when a native SQLite extension cannot be loaded. The `path` argument
127
- * is the filesystem path passed to `load_extension`, and the optional `cause`
128
- * string carries the error detail reported by SQLite.
129
- */
130
- declare class ExtensionError extends SirannonError {
131
- constructor(path: string, cause?: string);
132
- }
133
-
134
37
  interface LifecycleCallbacks {
135
38
  open: (id: string, path: string, options?: DatabaseOptions) => Promise<Database>;
136
39
  close: (id: string) => Promise<void>;
@@ -180,4 +83,4 @@ declare function queryOne<T = Record<string, unknown>>(conn: SQLiteConnection, s
180
83
  declare function execute(conn: SQLiteConnection, sql: string, params?: Params): Promise<ExecuteResult>;
181
84
  declare function executeBatch(conn: SQLiteConnection, sql: string, paramsBatch: Params[]): Promise<ExecuteResult[]>;
182
85
 
183
- export { BackupError, CDCError, ConnectionPool, ConnectionPoolError, type ConnectionPoolOptions, Database, DatabaseAlreadyExistsError, DatabaseNotFoundError, DatabaseOptions, ExecuteResult, ExtensionError, HookDeniedError, type LifecycleCallbacks, LifecycleConfig, LifecycleManager, MaxDatabasesError, Migration, MigrationError, MigrationResult, MigrationRunner, Params, QueryError, ReadOnlyError, RollbackResult, SQLiteConnection, SQLiteDriver, SirannonError, type TenantResolverOptions, TransactionError, createTenantResolver, defineDriver, execute, executeBatch, query, queryOne, sanitizeTenantId, tenantPath };
86
+ export { ConnectionPool, type ConnectionPoolOptions, Database, DatabaseOptions, ExecuteResult, type LifecycleCallbacks, LifecycleConfig, LifecycleManager, Migration, MigrationResult, MigrationRunner, Params, RollbackResult, SQLiteConnection, SQLiteDriver, type TenantResolverOptions, createTenantResolver, defineDriver, execute, executeBatch, query, queryOne, sanitizeTenantId, tenantPath };
@@ -1,8 +1,12 @@
1
- import { SubscriptionBuilderImpl, ChangeTracker, SubscriptionManager, startPolling } from '../chunk-AX66KWBR.mjs';
1
+ import { SubscriptionBuilderImpl, ChangeTracker, SubscriptionManager, startPolling } from '../chunk-UTO3ZAFS.mjs';
2
+ export { ChangeTracker } from '../chunk-UTO3ZAFS.mjs';
2
3
  export { defineDriver } from '../chunk-74UN4DIE.mjs';
3
4
  import { BackupManager, BackupScheduler } from '../chunk-PXKAKK2V.mjs';
4
5
  export { BackupManager, BackupScheduler } from '../chunk-PXKAKK2V.mjs';
5
- import { ConnectionPoolError, QueryError, MigrationError, ReadOnlyError, ExtensionError, SirannonError, MaxDatabasesError, DatabaseAlreadyExistsError, DatabaseNotFoundError } from '../chunk-O7BHI3CF.mjs';
6
+ import { Transaction, query, queryOne, execute, executeBatch } from '../chunk-3MCMONVP.mjs';
7
+ export { Transaction, execute, executeBatch, query, queryOne } from '../chunk-3MCMONVP.mjs';
8
+ import '../chunk-GS7T5YMI.mjs';
9
+ import { ConnectionPoolError, MigrationError, ReadOnlyError, SirannonError, MaxDatabasesError, DatabaseAlreadyExistsError, DatabaseNotFoundError, ExtensionError } from '../chunk-O7BHI3CF.mjs';
6
10
  export { BackupError, CDCError, ConnectionPoolError, DatabaseAlreadyExistsError, DatabaseNotFoundError, ExtensionError, HookDeniedError, MaxDatabasesError, MigrationError, QueryError, ReadOnlyError, SirannonError, TransactionError } from '../chunk-O7BHI3CF.mjs';
7
11
 
8
12
  // src/core/connection-pool.ts
@@ -96,6 +100,81 @@ var ConnectionPool = class _ConnectionPool {
96
100
  }
97
101
  };
98
102
 
103
+ // src/core/cdc/ddl-handler.ts
104
+ var DDL_PREFIX_RE = /^\s*(CREATE\s+TABLE|ALTER\s+TABLE\s+\S+\s+ADD\s+COLUMN|DROP\s+TABLE|CREATE\s+INDEX|DROP\s+INDEX)\b/i;
105
+ var DROP_TABLE_RE = /^\s*DROP\s+TABLE\s+(?:IF\s+EXISTS\s+)?"?([A-Za-z_][A-Za-z0-9_]*)"?\s*;?\s*$/i;
106
+ function isCdcRelevantDdl(sql) {
107
+ return DDL_PREFIX_RE.test(sql);
108
+ }
109
+ function extractDroppedTable(sql) {
110
+ const m = DROP_TABLE_RE.exec(sql);
111
+ return m?.[1] ?? null;
112
+ }
113
+ async function applyDdlSideEffects(tracker, writerConn, sql) {
114
+ if (tracker.watchedTables.size === 0) {
115
+ return;
116
+ }
117
+ await tracker.refreshAllTriggersUsingConnection(writerConn);
118
+ const dropped = extractDroppedTable(sql);
119
+ if (dropped !== null) {
120
+ await tracker.pruneDroppedTables(writerConn, [dropped]);
121
+ }
122
+ }
123
+
124
+ // src/core/cdc/cdc-aware-transaction.ts
125
+ var CdcAwareTransaction = class extends Transaction {
126
+ constructor(txConn, tracker, state) {
127
+ super(txConn);
128
+ this.tracker = tracker;
129
+ this.state = state;
130
+ this.txConn = txConn;
131
+ }
132
+ txConn;
133
+ async execute(sql, params) {
134
+ const isDdl = isCdcRelevantDdl(sql);
135
+ const result = await super.execute(sql, params);
136
+ if (!isDdl) {
137
+ return result;
138
+ }
139
+ this.state.sawDdl = true;
140
+ const dropped = extractDroppedTable(sql);
141
+ if (dropped !== null) {
142
+ this.state.droppedTables.push(dropped);
143
+ }
144
+ if (this.tracker.watchedTables.size > 0) {
145
+ await this.tracker.refreshAllTriggersUsingConnection(this.txConn);
146
+ }
147
+ return result;
148
+ }
149
+ };
150
+
151
+ // src/core/extension-loader.ts
152
+ async function loadExtension(driver, writer, extensionPath) {
153
+ if (!driver.capabilities.extensions) {
154
+ throw new ExtensionError(extensionPath, "Extensions are not supported by the current driver");
155
+ }
156
+ if (!extensionPath || extensionPath.includes("\0")) {
157
+ throw new ExtensionError(extensionPath || "", "Extension path is empty or contains null bytes");
158
+ }
159
+ for (let i = 0; i < extensionPath.length; i++) {
160
+ if (extensionPath.charCodeAt(i) <= 31) {
161
+ throw new ExtensionError(extensionPath, "Extension path contains control characters");
162
+ }
163
+ }
164
+ const segments = extensionPath.split(/[/\\]/);
165
+ if (segments.includes("..")) {
166
+ throw new ExtensionError(extensionPath, "Extension path must not contain directory traversal segments");
167
+ }
168
+ const { resolve } = await import('path');
169
+ const resolved = resolve(extensionPath);
170
+ try {
171
+ const escaped = resolved.replace(/'/g, "''");
172
+ await writer.exec(`SELECT load_extension('${escaped}')`);
173
+ } catch (err) {
174
+ throw new ExtensionError(extensionPath, err instanceof Error ? err.message : String(err));
175
+ }
176
+ }
177
+
99
178
  // src/core/hooks/registry.ts
100
179
  var HOOK_CONFIG_MAP = {
101
180
  onBeforeQuery: "beforeQuery",
@@ -178,118 +257,6 @@ var HookRegistry = class {
178
257
  }
179
258
  };
180
259
 
181
- // src/core/query-executor.ts
182
- var STATEMENT_CACHE_CAPACITY = 128;
183
- var statementCaches = /* @__PURE__ */ new WeakMap();
184
- async function getStatement(conn, sql) {
185
- let cache = statementCaches.get(conn);
186
- if (!cache) {
187
- cache = /* @__PURE__ */ new Map();
188
- statementCaches.set(conn, cache);
189
- }
190
- const cached = cache.get(sql);
191
- if (cached) {
192
- cache.delete(sql);
193
- cache.set(sql, cached);
194
- return cached;
195
- }
196
- const pending = conn.prepare(sql);
197
- cache.set(sql, pending);
198
- if (cache.size > STATEMENT_CACHE_CAPACITY) {
199
- const oldest = cache.keys().next().value;
200
- if (oldest !== void 0) {
201
- cache.delete(oldest);
202
- }
203
- }
204
- try {
205
- return await pending;
206
- } catch (err) {
207
- cache.delete(sql);
208
- throw err;
209
- }
210
- }
211
- function bindParams(params) {
212
- if (params === void 0) return [];
213
- if (Array.isArray(params)) return params;
214
- return [params];
215
- }
216
- async function query(conn, sql, params) {
217
- try {
218
- const stmt = await getStatement(conn, sql);
219
- return await stmt.all(...bindParams(params));
220
- } catch (err) {
221
- throw new QueryError(err instanceof Error ? err.message : String(err), sql);
222
- }
223
- }
224
- async function queryOne(conn, sql, params) {
225
- try {
226
- const stmt = await getStatement(conn, sql);
227
- return await stmt.get(...bindParams(params));
228
- } catch (err) {
229
- throw new QueryError(err instanceof Error ? err.message : String(err), sql);
230
- }
231
- }
232
- async function execute(conn, sql, params) {
233
- try {
234
- const stmt = await getStatement(conn, sql);
235
- const result = await stmt.run(...bindParams(params));
236
- return {
237
- changes: result.changes,
238
- lastInsertRowId: result.lastInsertRowId
239
- };
240
- } catch (err) {
241
- throw new QueryError(err instanceof Error ? err.message : String(err), sql);
242
- }
243
- }
244
- async function executeBatch(conn, sql, paramsBatch) {
245
- try {
246
- const stmt = await getStatement(conn, sql);
247
- const results = [];
248
- for (const params of paramsBatch) {
249
- const result = await stmt.run(...bindParams(params));
250
- results.push({
251
- changes: result.changes,
252
- lastInsertRowId: result.lastInsertRowId
253
- });
254
- }
255
- return results;
256
- } catch (err) {
257
- throw new QueryError(err instanceof Error ? err.message : String(err), sql);
258
- }
259
- }
260
-
261
- // src/core/transaction.ts
262
- var Transaction = class _Transaction {
263
- constructor(conn) {
264
- this.conn = conn;
265
- }
266
- _lastInsertRowId = 0;
267
- async query(sql, params) {
268
- return query(this.conn, sql, params);
269
- }
270
- async execute(sql, params) {
271
- const result = await execute(this.conn, sql, params);
272
- this._lastInsertRowId = result.lastInsertRowId;
273
- return result;
274
- }
275
- async executeBatch(sql, paramsBatch) {
276
- const results = await executeBatch(this.conn, sql, paramsBatch);
277
- if (results.length > 0) {
278
- this._lastInsertRowId = results[results.length - 1].lastInsertRowId;
279
- }
280
- return results;
281
- }
282
- get lastInsertRowId() {
283
- return this._lastInsertRowId;
284
- }
285
- static async run(conn, fn) {
286
- return conn.transaction(async (txConn) => {
287
- const tx = new _Transaction(txConn);
288
- return fn(tx);
289
- });
290
- }
291
- };
292
-
293
260
  // src/core/migrations/runner.ts
294
261
  var CREATE_TRACKING_TABLE = `
295
262
  CREATE TABLE IF NOT EXISTS _sirannon_migrations (
@@ -488,9 +455,9 @@ var Database = class _Database {
488
455
  });
489
456
  return new _Database(id, path, pool, driver, options, internals);
490
457
  }
491
- async query(sql, params) {
458
+ async query(sql, params, options) {
492
459
  this.ensureOpen();
493
- this.fireBeforeQueryHooks(sql, params);
460
+ this.fireBeforeQueryHooks(sql, params, options);
494
461
  const start = performance.now();
495
462
  try {
496
463
  const reader = this.pool.acquireReader();
@@ -505,9 +472,9 @@ var Database = class _Database {
505
472
  this.fireAfterQueryHooks(sql, params, performance.now() - start);
506
473
  }
507
474
  }
508
- async queryOne(sql, params) {
475
+ async queryOne(sql, params, options) {
509
476
  this.ensureOpen();
510
- this.fireBeforeQueryHooks(sql, params);
477
+ this.fireBeforeQueryHooks(sql, params, options);
511
478
  const start = performance.now();
512
479
  try {
513
480
  const reader = this.pool.acquireReader();
@@ -522,39 +489,37 @@ var Database = class _Database {
522
489
  this.fireAfterQueryHooks(sql, params, performance.now() - start);
523
490
  }
524
491
  }
525
- async execute(sql, params) {
492
+ async execute(sql, params, options) {
526
493
  this.ensureOpen();
527
494
  if (this.readOnly) throw new ReadOnlyError(this.id);
528
- this.fireBeforeQueryHooks(sql, params);
495
+ this.fireBeforeQueryHooks(sql, params, options);
529
496
  const start = performance.now();
530
497
  try {
531
498
  const writer = this.pool.acquireWriter();
532
- if (this.metricsCollector) {
533
- return await this.metricsCollector.trackQuery(() => execute(writer, sql, params), {
534
- databaseId: this.id,
535
- sql
536
- });
537
- }
538
- return await execute(writer, sql, params);
499
+ const result = this.metricsCollector ? await this.metricsCollector.trackQuery(() => execute(writer, sql, params), {
500
+ databaseId: this.id,
501
+ sql
502
+ }) : await execute(writer, sql, params);
503
+ await this.maybeApplyDdlSideEffects(writer, sql);
504
+ return result;
539
505
  } finally {
540
506
  this.fireAfterQueryHooks(sql, params, performance.now() - start);
541
507
  }
542
508
  }
543
- async executeBatch(sql, paramsBatch) {
509
+ async executeBatch(sql, paramsBatch, options) {
544
510
  this.ensureOpen();
545
511
  if (this.readOnly) throw new ReadOnlyError(this.id);
546
- this.fireBeforeQueryHooks(sql);
512
+ this.fireBeforeQueryHooks(sql, void 0, options);
547
513
  const start = performance.now();
548
514
  try {
549
515
  const writer = this.pool.acquireWriter();
550
516
  const batchFn = () => writer.transaction(async (txConn) => executeBatch(txConn, sql, paramsBatch));
551
- if (this.metricsCollector) {
552
- return await this.metricsCollector.trackQuery(batchFn, {
553
- databaseId: this.id,
554
- sql
555
- });
556
- }
557
- return await batchFn();
517
+ const results = this.metricsCollector ? await this.metricsCollector.trackQuery(batchFn, {
518
+ databaseId: this.id,
519
+ sql
520
+ }) : await batchFn();
521
+ await this.maybeApplyDdlSideEffects(writer, sql);
522
+ return results;
558
523
  } finally {
559
524
  this.fireAfterQueryHooks(sql, void 0, performance.now() - start);
560
525
  }
@@ -563,7 +528,25 @@ var Database = class _Database {
563
528
  this.ensureOpen();
564
529
  if (this.readOnly) throw new ReadOnlyError(this.id);
565
530
  const writer = this.pool.acquireWriter();
566
- return Transaction.run(writer, fn);
531
+ const tracker = this.changeTracker;
532
+ if (!tracker) {
533
+ return Transaction.run(writer, fn);
534
+ }
535
+ const state = { sawDdl: false, droppedTables: [] };
536
+ const result = await writer.transaction(async (txConn) => {
537
+ const tx = new CdcAwareTransaction(txConn, tracker, state);
538
+ return fn(tx);
539
+ });
540
+ if (state.sawDdl && state.droppedTables.length > 0) {
541
+ await tracker.pruneDroppedTables(writer, state.droppedTables);
542
+ }
543
+ return result;
544
+ }
545
+ async maybeApplyDdlSideEffects(writer, sql) {
546
+ const tracker = this.changeTracker;
547
+ if (!tracker) return;
548
+ if (!isCdcRelevantDdl(sql)) return;
549
+ await applyDdlSideEffects(tracker, writer, sql);
567
550
  }
568
551
  async watch(table) {
569
552
  this.ensureOpen();
@@ -614,30 +597,8 @@ var Database = class _Database {
614
597
  }
615
598
  async loadExtension(extensionPath) {
616
599
  this.ensureOpen();
617
- if (!this.driver.capabilities.extensions) {
618
- throw new ExtensionError(extensionPath, "Extensions are not supported by the current driver");
619
- }
620
- if (!extensionPath || extensionPath.includes("\0")) {
621
- throw new ExtensionError(extensionPath || "", "Extension path is empty or contains null bytes");
622
- }
623
- for (let i = 0; i < extensionPath.length; i++) {
624
- if (extensionPath.charCodeAt(i) <= 31) {
625
- throw new ExtensionError(extensionPath, "Extension path contains control characters");
626
- }
627
- }
628
- const segments = extensionPath.split(/[/\\]/);
629
- if (segments.includes("..")) {
630
- throw new ExtensionError(extensionPath, "Extension path must not contain directory traversal segments");
631
- }
632
- const { resolve } = await import('path');
633
- const resolved = resolve(extensionPath);
634
- try {
635
- const writer = this.pool.acquireWriter();
636
- const escaped = resolved.replace(/'/g, "''");
637
- await writer.exec(`SELECT load_extension('${escaped}')`);
638
- } catch (err) {
639
- throw new ExtensionError(extensionPath, err instanceof Error ? err.message : String(err));
640
- }
600
+ const writer = this.pool.acquireWriter();
601
+ await loadExtension(this.driver, writer, extensionPath);
641
602
  }
642
603
  onBeforeQuery(hook) {
643
604
  this.hookRegistry.register("beforeQuery", hook);
@@ -707,11 +668,17 @@ var Database = class _Database {
707
668
  this.stopCdcPolling = null;
708
669
  }
709
670
  }
710
- fireBeforeQueryHooks(sql, params) {
671
+ fireBeforeQueryHooks(sql, params, options) {
711
672
  const hasParent = this.parentHooks?.has("beforeQuery");
712
673
  const hasLocal = this.hookRegistry.has("beforeQuery");
713
674
  if (!hasParent && !hasLocal) return;
714
- const ctx = { databaseId: this.id, sql, params };
675
+ const ctx = {
676
+ databaseId: this.id,
677
+ sql,
678
+ params,
679
+ writeConcern: options?.writeConcern,
680
+ readConcern: options?.readConcern
681
+ };
715
682
  this.parentHooks?.invokeSync("beforeQuery", ctx);
716
683
  this.hookRegistry.invokeSync("beforeQuery", ctx);
717
684
  }
@@ -745,12 +712,11 @@ var LifecycleManager = class {
745
712
  const timeout = config.idleTimeout;
746
713
  if (timeout && timeout > 0) {
747
714
  const interval = Math.min(Math.max(Math.floor(timeout / 2), 100), 6e4);
748
- this.idleTimer = setInterval(async () => {
715
+ const timer = setInterval(async () => {
749
716
  await this.#runIdleCheck();
750
717
  }, interval);
751
- if (typeof this.idleTimer === "object" && "unref" in this.idleTimer) {
752
- this.idleTimer.unref();
753
- }
718
+ timer.unref?.();
719
+ this.idleTimer = timer;
754
720
  }
755
721
  }
756
722
  async resolve(id) {
@@ -1103,4 +1069,4 @@ var Sirannon = class {
1103
1069
  }
1104
1070
  };
1105
1071
 
1106
- export { ConnectionPool, Database, HookRegistry, LifecycleManager, MetricsCollector, MigrationRunner, Sirannon, Transaction, createTenantResolver, execute, executeBatch, query, queryOne, sanitizeTenantId, tenantPath };
1072
+ export { ConnectionPool, Database, HookRegistry, LifecycleManager, MetricsCollector, MigrationRunner, Sirannon, createTenantResolver, sanitizeTenantId, tenantPath };
@@ -1,6 +1,6 @@
1
1
  import { S as SQLiteDriver } from './types-BFSsG77t.js';
2
- import { Q as QueryHookContext, C as ConnectionHookContext, B as BeforeSubscribeHook, H as HookConfig, M as MetricsConfig, a as QueryMetrics, b as ConnectionMetrics, c as CDCMetrics, D as DatabaseOptions, P as Params, E as ExecuteResult, d as SubscriptionBuilder, e as BackupScheduleOptions, f as BeforeQueryHook, A as AfterQueryHook, g as SirannonOptions, h as BeforeConnectHook, i as DatabaseOpenHook, j as DatabaseCloseHook } from './types-DtDutWRU.js';
3
- import { T as Transaction, M as Migration, a as MigrationResult, R as RollbackResult } from './types-DRkJlqex.js';
2
+ import { Q as QueryHookContext, f as ConnectionHookContext, g as BeforeSubscribeHook, H as HookConfig, M as MetricsConfig, h as QueryMetrics, i as ConnectionMetrics, j as CDCMetrics, D as DatabaseOptions, P as Params, k as QueryOptions, E as ExecuteResult, T as Transaction, l as SubscriptionBuilder, m as BackupScheduleOptions, B as BeforeQueryHook, A as AfterQueryHook } from './types-D-74JiXb.js';
3
+ import { M as Migration, a as MigrationResult, R as RollbackResult } from './types-BeozgNPr.js';
4
4
 
5
5
  type HookEvent = 'beforeQuery' | 'afterQuery' | 'beforeConnect' | 'databaseOpen' | 'databaseClose' | 'beforeSubscribe';
6
6
  type SubscribeHookContext = Parameters<BeforeSubscribeHook>[0];
@@ -64,11 +64,12 @@ declare class Database {
64
64
  private readonly scheduledBackupCancellers;
65
65
  private constructor();
66
66
  static create(id: string, path: string, driver: SQLiteDriver, options?: DatabaseOptions, internals?: DatabaseInternals): Promise<Database>;
67
- query<T = Record<string, unknown>>(sql: string, params?: Params): Promise<T[]>;
68
- queryOne<T = Record<string, unknown>>(sql: string, params?: Params): Promise<T | undefined>;
69
- execute(sql: string, params?: Params): Promise<ExecuteResult>;
70
- executeBatch(sql: string, paramsBatch: Params[]): Promise<ExecuteResult[]>;
67
+ query<T = Record<string, unknown>>(sql: string, params?: Params, options?: QueryOptions): Promise<T[]>;
68
+ queryOne<T = Record<string, unknown>>(sql: string, params?: Params, options?: QueryOptions): Promise<T | undefined>;
69
+ execute(sql: string, params?: Params, options?: QueryOptions): Promise<ExecuteResult>;
70
+ executeBatch(sql: string, paramsBatch: Params[], options?: QueryOptions): Promise<ExecuteResult[]>;
71
71
  transaction<T>(fn: (tx: Transaction) => Promise<T>): Promise<T>;
72
+ private maybeApplyDdlSideEffects;
72
73
  watch(table: string): Promise<void>;
73
74
  unwatch(table: string): Promise<void>;
74
75
  on(table: string): SubscriptionBuilder;
@@ -91,30 +92,4 @@ declare class Database {
91
92
  private fireAfterQueryHooks;
92
93
  }
93
94
 
94
- declare class Sirannon {
95
- readonly options: SirannonOptions;
96
- private readonly dbs;
97
- private readonly opening;
98
- private _shutdown;
99
- private readonly _driver;
100
- private readonly hookRegistry;
101
- private readonly metricsCollector;
102
- private readonly lifecycleManager;
103
- constructor(options: SirannonOptions);
104
- get driver(): SQLiteDriver;
105
- open(id: string, path: string, options?: DatabaseOptions): Promise<Database>;
106
- close(id: string): Promise<void>;
107
- get(id: string): Database | undefined;
108
- resolve(id: string): Promise<Database | undefined>;
109
- has(id: string): boolean;
110
- databases(): Map<string, Database>;
111
- shutdown(): Promise<void>;
112
- onBeforeQuery(hook: BeforeQueryHook): void;
113
- onAfterQuery(hook: AfterQueryHook): void;
114
- onBeforeConnect(hook: BeforeConnectHook): void;
115
- onDatabaseOpen(hook: DatabaseOpenHook): void;
116
- onDatabaseClose(hook: DatabaseCloseHook): void;
117
- private ensureRunning;
118
- }
119
-
120
- export { Database as D, type HookDispose as H, MetricsCollector as M, Sirannon as S, type HookEvent as a, type HookEventContextMap as b, type HookHandler as c, HookRegistry as d, type SubscribeHookContext as e };
95
+ export { Database as D, type HookDispose as H, MetricsCollector as M, type SubscribeHookContext as S, type HookEvent as a, type HookEventContextMap as b, type HookHandler as c, HookRegistry as d };