node-firebird 2.8.1 → 2.10.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 (53) hide show
  1. package/README.md +268 -7
  2. package/lib/index.d.ts +17 -9
  3. package/lib/index.js +42 -3
  4. package/lib/named-params.d.ts +42 -0
  5. package/lib/named-params.js +133 -0
  6. package/lib/pool.js +1 -1
  7. package/lib/srp.d.ts +3 -3
  8. package/lib/types.d.ts +185 -25
  9. package/lib/uri.d.ts +57 -0
  10. package/lib/uri.js +193 -0
  11. package/lib/wire/connection.d.ts +92 -59
  12. package/lib/wire/connection.js +286 -55
  13. package/lib/wire/const.d.ts +9 -1
  14. package/lib/wire/const.js +21 -9
  15. package/lib/wire/database.d.ts +51 -26
  16. package/lib/wire/database.js +26 -8
  17. package/lib/wire/eventConnection.js +5 -3
  18. package/lib/wire/query-stream.d.ts +18 -0
  19. package/lib/wire/query-stream.js +73 -0
  20. package/lib/wire/serialize.d.ts +18 -2
  21. package/lib/wire/serialize.js +7 -0
  22. package/lib/wire/service.d.ts +42 -0
  23. package/lib/wire/service.js +145 -0
  24. package/lib/wire/socket.d.ts +3 -1
  25. package/lib/wire/socket.js +5 -2
  26. package/lib/wire/statement.d.ts +40 -20
  27. package/lib/wire/statement.js +26 -6
  28. package/lib/wire/transaction.d.ts +32 -18
  29. package/lib/wire/transaction.js +50 -8
  30. package/lib/wire/wire-types.d.ts +116 -0
  31. package/lib/wire/wire-types.js +10 -0
  32. package/lib/wire/xsqlvar.d.ts +18 -18
  33. package/package.json +1 -1
  34. package/src/index.ts +54 -15
  35. package/src/messages.ts +1 -1
  36. package/src/named-params.ts +145 -0
  37. package/src/pool.ts +1 -1
  38. package/src/srp.ts +6 -6
  39. package/src/types.ts +183 -25
  40. package/src/unix-crypt.ts +9 -9
  41. package/src/uri.ts +204 -0
  42. package/src/wire/connection.ts +481 -234
  43. package/src/wire/const.ts +21 -9
  44. package/src/wire/database.ts +75 -43
  45. package/src/wire/eventConnection.ts +8 -5
  46. package/src/wire/query-stream.ts +80 -0
  47. package/src/wire/serialize.ts +29 -0
  48. package/src/wire/service.ts +188 -6
  49. package/src/wire/socket.ts +17 -8
  50. package/src/wire/statement.ts +68 -31
  51. package/src/wire/transaction.ts +85 -33
  52. package/src/wire/wire-types.ts +127 -0
  53. package/src/wire/xsqlvar.ts +9 -7
package/lib/srp.d.ts CHANGED
@@ -22,7 +22,7 @@ export declare function clientSeed(a?: bigint): KeyPair;
22
22
  * @param b BigInt Server private key.
23
23
  * @returns {{private: BigInt, public: BigInt}}
24
24
  */
25
- export declare function serverSeed(user: string, password: string, salt: Buffer, b?: bigint | string, hashAlgo?: string): KeyPair;
25
+ export declare function serverSeed(user: string, password: string, salt: Buffer | string, b?: bigint | string, hashAlgo?: string): KeyPair;
26
26
  /**
27
27
  * Server session secret.
28
28
  *
@@ -34,11 +34,11 @@ export declare function serverSeed(user: string, password: string, salt: Buffer,
34
34
  * @param b BigInt Server private key.
35
35
  * @returns {BigInt}
36
36
  */
37
- export declare function serverSession(user: string, password: string, salt: Buffer, A: bigint, B: bigint, b: bigint, hashAlgo?: string): bigint;
37
+ export declare function serverSession(user: string, password: string, salt: Buffer | string, A: bigint, B: bigint, b: bigint, hashAlgo?: string): bigint;
38
38
  /**
39
39
  * M = H(H(N) xor H(g), H(I), s, A, B, K)
40
40
  */
41
- export declare function clientProof(user: string, password: string, salt: Buffer, A: bigint, B: bigint, a: bigint, hashAlgo?: string): ClientProof;
41
+ export declare function clientProof(user: string, password: string, salt: Buffer | string, A: bigint, B: bigint, a: bigint, hashAlgo?: string): ClientProof;
42
42
  /**
43
43
  * Pad hex string.
44
44
  */
package/lib/types.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { Readable } from 'stream';
1
2
  export type DatabaseCallback = (err: any, db: Database) => void;
2
3
  export type TransactionCallback = (err: any, transaction: Transaction) => void;
3
4
  export type QueryCallback = (err: any, result: any[]) => void;
@@ -90,9 +91,21 @@ export type BatchOptions = {
90
91
  /** Rows per op_batch_msg packet (default 500). */
91
92
  chunkSize?: number;
92
93
  };
94
+ /**
95
+ * Positional query parameters (array), or — when named placeholders are
96
+ * enabled via the `namedPlaceholders` connection/query option — values by
97
+ * placeholder name.
98
+ */
99
+ export type QueryParams = any[] | Record<string, any>;
93
100
  export type QueryOptions = {
94
101
  timeout?: number;
95
102
  scrollable?: boolean;
103
+ /**
104
+ * Per-query override of the `namedPlaceholders` connection option
105
+ * (e.g. disable it for one EXECUTE BLOCK statement whose body uses
106
+ * `:variable` PSQL references).
107
+ */
108
+ namedPlaceholders?: boolean;
96
109
  /**
97
110
  * Abort the query when the signal fires (Firebird 2.5+ / protocol 12+).
98
111
  * If the signal is already aborted the query is not sent at all and the
@@ -103,15 +116,31 @@ export type QueryOptions = {
103
116
  */
104
117
  signal?: AbortSignal;
105
118
  };
119
+ export type QueryStreamOptions = QueryOptions & {
120
+ /**
121
+ * Rows buffered internally before fetching pauses (object-mode
122
+ * Readable highWaterMark, default 16).
123
+ */
124
+ highWaterMark?: number;
125
+ /** Emit array rows instead of objects (like db.execute). */
126
+ asObject?: boolean;
127
+ };
106
128
  export interface Database {
107
129
  detach(callback?: SimpleCallback): Database;
108
130
  transaction(options: TransactionOptions | Isolation | TransactionCallback, callback?: TransactionCallback): Database;
109
131
  newStatement(query: string, callback: (err: Error | null, statement: Statement) => void): Database;
110
- query(query: string, params: any[], callback: QueryCallback, options?: QueryOptions): Database;
111
- execute(query: string, params: any[], callback: QueryCallback, options?: QueryOptions): Database;
132
+ query(query: string, params: QueryParams, callback: QueryCallback, options?: QueryOptions): Database;
133
+ execute(query: string, params: QueryParams, callback: QueryCallback, options?: QueryOptions): Database;
112
134
  /** Bulk-execute in its own transaction, all-or-nothing (Firebird 4.0+). */
113
- executeBatch(query: string, rows: any[][], callback?: (err: any, result: BatchResult) => void, options?: BatchOptions): Database;
114
- sequentially(query: string, params: any[], rowCallback: SequentialCallback, callback: SimpleCallback, options?: QueryOptions | boolean): Database;
135
+ executeBatch(query: string, rows: QueryParams[], callback?: (err: any, result: BatchResult) => void, options?: BatchOptions): Database;
136
+ sequentially(query: string, params: QueryParams, rowCallback: SequentialCallback, callback: SimpleCallback, options?: QueryOptions | boolean): Database;
137
+ /**
138
+ * Run `query` and return an object-mode Readable emitting one row per
139
+ * chunk, with backpressure (fetching pauses while the buffer is full).
140
+ * Runs in its own transaction. Destroying the stream early aborts the
141
+ * fetch and releases the statement.
142
+ */
143
+ queryStream(query: string, params?: QueryParams, options?: QueryStreamOptions): Readable;
115
144
  drop(callback: SimpleCallback): void;
116
145
  escape(value: any): string;
117
146
  attachEvent(callback: any): this;
@@ -119,10 +148,10 @@ export interface Database {
119
148
  alterTablespace(name: string, filePath: string, callback?: QueryCallback): Database;
120
149
  dropTablespace(name: string, callback?: QueryCallback): Database;
121
150
  createSchema(schemaName: string, tablespaceName?: string | QueryCallback, callback?: QueryCallback): Database;
122
- queryAsync<T = any>(query: string, params?: any[], options?: QueryOptions): Promise<T[]>;
123
- executeAsync<T = any>(query: string, params?: any[], options?: QueryOptions): Promise<T[]>;
124
- executeBatchAsync(query: string, rows: any[][], options?: BatchOptions): Promise<BatchResult>;
125
- sequentiallyAsync(query: string, params: any[] | undefined, rowCallback: SequentialCallback, options?: QueryOptions | boolean): Promise<void>;
151
+ queryAsync<T = any>(query: string, params?: QueryParams, options?: QueryOptions): Promise<T[]>;
152
+ executeAsync<T = any>(query: string, params?: QueryParams, options?: QueryOptions): Promise<T[]>;
153
+ executeBatchAsync(query: string, rows: QueryParams[], options?: BatchOptions): Promise<BatchResult>;
154
+ sequentiallyAsync(query: string, params: QueryParams | undefined, rowCallback: SequentialCallback, options?: QueryOptions | boolean): Promise<void>;
126
155
  sequentiallyAsync(query: string, rowCallback: SequentialCallback, options?: QueryOptions | boolean): Promise<void>;
127
156
  transactionAsync(options?: TransactionOptions | Isolation): Promise<Transaction>;
128
157
  startTransactionAsync(options?: TransactionOptions | Isolation): Promise<Transaction>;
@@ -143,19 +172,25 @@ export interface Database {
143
172
  }
144
173
  export interface Transaction {
145
174
  newStatement(query: string, callback: (err: Error | null, statement: Statement) => void): void;
146
- query(query: string, params: any[], callback: QueryCallback, options?: QueryOptions): void;
147
- execute(query: string, params: any[], callback: QueryCallback, options?: QueryOptions): void;
175
+ query(query: string, params: QueryParams, callback: QueryCallback, options?: QueryOptions): void;
176
+ execute(query: string, params: QueryParams, callback: QueryCallback, options?: QueryOptions): void;
148
177
  /** Bulk-execute within this transaction; per-record failures do not roll back (Firebird 4.0+). */
149
- executeBatch(query: string, rows: any[][], callback?: (err: any, result: BatchResult) => void, options?: BatchOptions): void;
150
- sequentially(query: string, params: any[], rowCallback: SequentialCallback, callback: SimpleCallback, options?: QueryOptions | boolean): Database;
178
+ executeBatch(query: string, rows: QueryParams[], callback?: (err: any, result: BatchResult) => void, options?: BatchOptions): void;
179
+ sequentially(query: string, params: QueryParams, rowCallback: SequentialCallback, callback: SimpleCallback, options?: QueryOptions | boolean): Database;
180
+ /**
181
+ * Run `query` inside this transaction and return an object-mode
182
+ * Readable emitting one row per chunk, with backpressure. The
183
+ * transaction is NOT committed when the stream ends.
184
+ */
185
+ queryStream(query: string, params?: QueryParams, options?: QueryStreamOptions): Readable;
151
186
  commit(callback?: SimpleCallback): void;
152
187
  commitRetaining(callback?: SimpleCallback): void;
153
188
  rollback(callback?: SimpleCallback): void;
154
189
  rollbackRetaining(callback?: SimpleCallback): void;
155
- queryAsync<T = any>(query: string, params?: any[], options?: QueryOptions): Promise<T[]>;
156
- executeAsync<T = any>(query: string, params?: any[], options?: QueryOptions): Promise<T[]>;
157
- executeBatchAsync(query: string, rows: any[][], options?: BatchOptions): Promise<BatchResult>;
158
- sequentiallyAsync(query: string, params: any[] | undefined, rowCallback: SequentialCallback, options?: QueryOptions | boolean): Promise<void>;
190
+ queryAsync<T = any>(query: string, params?: QueryParams, options?: QueryOptions): Promise<T[]>;
191
+ executeAsync<T = any>(query: string, params?: QueryParams, options?: QueryOptions): Promise<T[]>;
192
+ executeBatchAsync(query: string, rows: QueryParams[], options?: BatchOptions): Promise<BatchResult>;
193
+ sequentiallyAsync(query: string, params: QueryParams | undefined, rowCallback: SequentialCallback, options?: QueryOptions | boolean): Promise<void>;
159
194
  sequentiallyAsync(query: string, rowCallback: SequentialCallback, options?: QueryOptions | boolean): Promise<void>;
160
195
  newStatementAsync(query: string): Promise<Statement>;
161
196
  commitAsync(): Promise<void>;
@@ -167,14 +202,14 @@ export interface Statement {
167
202
  close(callback?: SimpleCallback): void;
168
203
  drop(callback?: SimpleCallback): void;
169
204
  release(callback?: SimpleCallback): void;
170
- execute(transaction: Transaction, params: any[], callback: QueryCallback, options?: QueryOptions): void;
205
+ execute(transaction: Transaction, params: QueryParams, callback: QueryCallback, options?: QueryOptions): void;
171
206
  fetch(transaction: Transaction, count: number, callback: QueryCallback): void;
172
207
  fetchScroll(transaction: Transaction, direction: 'NEXT' | 'PRIOR' | 'FIRST' | 'LAST' | 'ABSOLUTE' | 'RELATIVE' | number, offset: number, count: number, callback: QueryCallback): void;
173
208
  fetchAll(transaction: Transaction, callback: QueryCallback): void;
174
209
  /** Execute this prepared statement once per row (Firebird 4.0+ batch API). */
175
- executeBatch(transaction: Transaction, rows: any[][], callback?: (err: any, result: BatchResult) => void, options?: BatchOptions): void;
176
- executeAsync(transaction: Transaction, params?: any[], options?: QueryOptions): Promise<any>;
177
- executeBatchAsync(transaction: Transaction, rows: any[][], options?: BatchOptions): Promise<BatchResult>;
210
+ executeBatch(transaction: Transaction, rows: QueryParams[], callback?: (err: any, result: BatchResult) => void, options?: BatchOptions): void;
211
+ executeAsync(transaction: Transaction, params?: QueryParams, options?: QueryOptions): Promise<any>;
212
+ executeBatchAsync(transaction: Transaction, rows: QueryParams[], options?: BatchOptions): Promise<BatchResult>;
178
213
  fetchAsync(transaction: Transaction, count: number | 'all'): Promise<any>;
179
214
  fetchScrollAsync(transaction: Transaction, direction: 'NEXT' | 'PRIOR' | 'FIRST' | 'LAST' | 'ABSOLUTE' | 'RELATIVE' | number, offset?: number, count?: number): Promise<any>;
180
215
  fetchAllAsync(transaction: Transaction): Promise<any>;
@@ -209,6 +244,26 @@ export interface Options {
209
244
  blobReadChunkSize?: number;
210
245
  wireCrypt?: number;
211
246
  wireCompression?: boolean;
247
+ /**
248
+ * Enable named placeholders: SQL may use `:name` markers and params may
249
+ * be a values-by-name object (`db.query('... WHERE id = :id', { id: 1 })`).
250
+ * Placeholders are rewritten client-side to positional `?` before
251
+ * preparing; positional arrays keep working unchanged. Off by default
252
+ * because `EXECUTE BLOCK` bodies use `:variable` for PSQL references —
253
+ * with this option on, run such statements with positional params or a
254
+ * per-query `namedPlaceholders: false` override.
255
+ */
256
+ namedPlaceholders?: boolean;
257
+ /**
258
+ * TCP keepalive probing to detect dead/stale connections (same option
259
+ * names as mysql2). On by default; set false to disable.
260
+ */
261
+ enableKeepAlive?: boolean;
262
+ /**
263
+ * Milliseconds a socket must be idle before the first TCP keepalive
264
+ * probe is sent (default 60000). Ignored when enableKeepAlive is false.
265
+ */
266
+ keepAliveInitialDelay?: number;
212
267
  pluginName?: string;
213
268
  parallelWorkers?: number;
214
269
  maxInlineBlobSize?: number;
@@ -240,11 +295,11 @@ export interface Options {
240
295
  /**
241
296
  * **Firebird 6.0+ only (Protocol 20+)**
242
297
  *
243
- * Sets the session's current schema at connection time. Equivalent to
244
- * executing `SET SCHEMA <name>` immediately after connecting.
245
- *
246
- * Unqualified object references (tables, procedures, etc.) that do not
247
- * match any schema in the `searchPath` fall back to `PUBLIC`.
298
+ * Sets the session's current schema at connection time. `CURRENT_SCHEMA`
299
+ * in Firebird is the first existing schema of the search path, so this
300
+ * option is implemented by putting the schema at the front of the
301
+ * `searchPath` sent to the server (with `PUBLIC` kept as a fallback when
302
+ * no explicit `searchPath` is given).
248
303
  *
249
304
  * Example: `defaultSchema: 'myapp'`
250
305
  */
@@ -266,6 +321,18 @@ export interface Options {
266
321
  * (typically `PUBLIC` then `SYSTEM`).
267
322
  */
268
323
  searchPath?: string | string[];
324
+ /**
325
+ * **Firebird 6.0+ only**
326
+ *
327
+ * Owner of a newly created database (`isc_dpb_owner`), allowing a
328
+ * superuser to create a database owned by another user
329
+ * ([firebird#7718](https://github.com/FirebirdSQL/firebird/issues/7718)).
330
+ * Only honored by `create`/`attachOrCreate` when the database is
331
+ * created; ignored on plain attach and by older servers.
332
+ *
333
+ * Example: `owner: 'APP_OWNER'`
334
+ */
335
+ owner?: string;
269
336
  /**
270
337
  * **Firebird 6.0+ only (Protocol 20+)**
271
338
  *
@@ -274,7 +341,55 @@ export interface Options {
274
341
  * text/BLOB columns back into JavaScript objects/arrays.
275
342
  */
276
343
  jsonAsObject?: boolean;
344
+ /**
345
+ * Custom type parser (mysql2-style). Called for every column value of
346
+ * every result row (including NULLs); whatever it returns becomes the
347
+ * value in the row. Call `next()` to get the value the driver would
348
+ * produce by default (after `blobAsText`/`jsonAsObject` are applied).
349
+ *
350
+ * ```js
351
+ * typeCast: (column, next) =>
352
+ * column.typeName === 'INT64' ? Number(next()) : next()
353
+ * ```
354
+ *
355
+ * Non-text BLOB columns reach the hook as the usual fetch function;
356
+ * text BLOBs with `blobAsText` reach it as the resolved string. The
357
+ * hook must be a pure function: a row can be decoded more than once
358
+ * when a response spans TCP packets.
359
+ */
360
+ typeCast?: TypeCastFunction;
361
+ /**
362
+ * Per-connection LRU cache of prepared statements (like mysql2's
363
+ * statement cache). `db.query`/`tx.query` and friends transparently
364
+ * reuse the prepared handle for a repeated SQL string, skipping the
365
+ * prepare round-trip on hot paths. The number is the maximum of idle
366
+ * cached statements; least-recently-used ones are dropped over the
367
+ * limit. 0 / unset = disabled. Statements that failed and DDL are
368
+ * never cached; concurrent runs of the same SQL never share a
369
+ * statement (extra preparations are simply not cached).
370
+ */
371
+ statementCacheSize?: number;
277
372
  }
373
+ /** Column metadata passed to the {@link Options.typeCast} hook. */
374
+ export interface TypeCastColumn {
375
+ /** Firebird SQL type code (see the exported `SQL_TYPES` map). */
376
+ type: number;
377
+ /** Friendly name of the type code: 'VARYING', 'INT64', 'BLOB', ... */
378
+ typeName: string;
379
+ /** Column subtype (e.g. 1 = text for BLOBs; charset id for strings). */
380
+ subType?: number;
381
+ /** Negative decimal scale for NUMERIC/DECIMAL columns (e.g. -2). */
382
+ scale?: number;
383
+ /** Declared length in bytes. */
384
+ length?: number;
385
+ /** Column name in the table. */
386
+ field?: string;
387
+ /** Table (relation) name. */
388
+ relation?: string;
389
+ /** Alias used in the SELECT list (the row key for object rows). */
390
+ alias?: string;
391
+ }
392
+ export type TypeCastFunction = (column: TypeCastColumn, next: () => any) => any;
278
393
  export interface SvcMgrOptions extends Options {
279
394
  manager: true;
280
395
  }
@@ -484,4 +599,49 @@ export interface ServiceManager {
484
599
  hasRunningAction(options: ReadableOptions, callback: ReadableCallback): void;
485
600
  readusers(options: ReadableOptions, callback: ReadableCallback): void;
486
601
  readlimbo(options: ReadableOptions, callback: ReadableCallback): void;
602
+ detachAsync(force?: boolean): Promise<void>;
603
+ backupAsync(options: BackupOptions): Promise<NodeJS.ReadableStream>;
604
+ nbackupAsync(options: BackupOptions): Promise<NodeJS.ReadableStream>;
605
+ restoreAsync(options: RestoreOptions): Promise<NodeJS.ReadableStream>;
606
+ nrestoreAsync(options: NRestoreOptions): Promise<NodeJS.ReadableStream>;
607
+ setDialectAsync(db: string, dialect: 1 | 3): Promise<NodeJS.ReadableStream>;
608
+ setSweepintervalAsync(db: string, interval: number): Promise<any>;
609
+ setCachebufferAsync(db: string, nbpages: any): Promise<NodeJS.ReadableStream>;
610
+ BringOnlineAsync(db: string): Promise<NodeJS.ReadableStream>;
611
+ ShutdownAsync(db: string, kind: ShutdownKind, delay: number, mode?: ShutdownMode): Promise<NodeJS.ReadableStream>;
612
+ setShadowAsync(db: string, val: boolean): Promise<NodeJS.ReadableStream>;
613
+ setForcewriteAsync(db: string, val: boolean): Promise<NodeJS.ReadableStream>;
614
+ setReservespaceAsync(db: string, val: boolean): Promise<NodeJS.ReadableStream>;
615
+ setReadonlyModeAsync(db: string): Promise<NodeJS.ReadableStream>;
616
+ setReadwriteModeAsync(db: string): Promise<NodeJS.ReadableStream>;
617
+ validateAsync(options: ValidateOptions): Promise<NodeJS.ReadableStream>;
618
+ commitAsync(db: string, transactid: number): Promise<NodeJS.ReadableStream>;
619
+ rollbackAsync(db: string, transactid: number): Promise<NodeJS.ReadableStream>;
620
+ recoverAsync(db: string, transactid: number): Promise<NodeJS.ReadableStream>;
621
+ getStatsAsync(options: StatsOptions): Promise<NodeJS.ReadableStream>;
622
+ getLogAsync(options: ReadableOptions): Promise<NodeJS.ReadableStream>;
623
+ getUsersAsync(username?: string | null): Promise<ServerInfo>;
624
+ addUserAsync(username: string, password: string, info?: UserInfo): Promise<NodeJS.ReadableStream>;
625
+ editUserAsync(username: string, info: UserInfo): Promise<NodeJS.ReadableStream>;
626
+ removeUserAsync(username: string, rolename?: string | null): Promise<NodeJS.ReadableStream>;
627
+ getFbserverInfosAsync(infos?: ServerInfoReq, options?: {
628
+ buffersize?: number;
629
+ timeout?: number;
630
+ }): Promise<ServerInfo>;
631
+ startTraceAsync(options: TraceOptions): Promise<NodeJS.ReadableStream>;
632
+ suspendTraceAsync(options: TraceOptions): Promise<NodeJS.ReadableStream>;
633
+ resumeTraceAsync(options: TraceOptions): Promise<NodeJS.ReadableStream>;
634
+ stopTraceAsync(options: TraceOptions): Promise<NodeJS.ReadableStream>;
635
+ getTraceListAsync(options?: ReadableOptions): Promise<NodeJS.ReadableStream>;
636
+ readlineAsync(options?: ReadableOptions): Promise<{
637
+ result: number;
638
+ line: string;
639
+ }>;
640
+ readeofAsync(options?: ReadableOptions): Promise<{
641
+ result: number;
642
+ line: string;
643
+ }>;
644
+ hasRunningActionAsync(options?: ReadableOptions): Promise<any>;
645
+ readusersAsync(options?: ReadableOptions): Promise<any>;
646
+ readlimboAsync(options?: ReadableOptions): Promise<any>;
487
647
  }
package/lib/uri.d.ts ADDED
@@ -0,0 +1,57 @@
1
+ /***************************************
2
+ *
3
+ * Connection URI strings
4
+ *
5
+ ***************************************/
6
+ import type { Options } from './types';
7
+ /**
8
+ * Parse a firebird:// connection URI into an options object.
9
+ *
10
+ * firebird://user:password@host:port/database?option=value&...
11
+ *
12
+ * The database part:
13
+ * firebird://host/employee → alias "employee"
14
+ * firebird://host//var/db/prod.fdb → absolute path "/var/db/prod.fdb"
15
+ * firebird://host/var/db/prod.fdb → "/var/db/prod.fdb" (a database
16
+ * part with slashes is a path —
17
+ * aliases cannot contain "/")
18
+ * firebird://host/C:/db/prod.fdb → Windows path "C:/db/prod.fdb"
19
+ *
20
+ * Credentials and the database path are URL-decoded, so reserved characters
21
+ * can be percent-encoded (e.g. p%40ss for "p@ss"). Query parameters map
22
+ * 1:1 to option keys and are coerced to the option's type (booleans accept
23
+ * 1/true/yes/on). `user` and `password` may be given as query parameters
24
+ * instead of in the authority.
25
+ */
26
+ export declare function parseConnectionUri(uri: string): Options;
27
+ /**
28
+ * Parse a traditional ("old style") Firebird connection string:
29
+ *
30
+ * [host[/port]:]{path | alias}
31
+ *
32
+ * employee → alias "employee" (default host)
33
+ * /var/fb/prod.fdb → local path (default host)
34
+ * C:\fbdata\prod.fdb → Windows path — a single character
35
+ * before ":" is a drive letter, not
36
+ * a host (same rule as Firebird)
37
+ * db.example.com:employee → host + alias
38
+ * db.example.com/3051:/var/fb/prod.fdb → host + port + path
39
+ * myserver:C:\fbdata\prod.fdb → host + Windows path
40
+ * [::1]/3050:employee → IPv6 host + port + alias
41
+ *
42
+ * Unlike firebird:// URIs, traditional strings carry no credentials or
43
+ * options — the driver defaults apply (SYSDBA/masterkey, port 3050).
44
+ * The port must be numeric; /etc/services names are not resolved.
45
+ */
46
+ export declare function parseOldStyleConnectionString(str: string): Options;
47
+ /**
48
+ * Parse any connection string the driver accepts: a firebird:// URI, or a
49
+ * traditional [host[/port]:]database string when there is no scheme.
50
+ */
51
+ export declare function parseConnectionString(str: string): Options;
52
+ /**
53
+ * Accept either an options object or a connection string (firebird:// URI
54
+ * or traditional host[/port]:database) everywhere options are taken.
55
+ * Strings are parsed; objects pass through unchanged.
56
+ */
57
+ export declare function normalizeOptions<T>(options: T | string): T;
package/lib/uri.js ADDED
@@ -0,0 +1,193 @@
1
+ "use strict";
2
+ /***************************************
3
+ *
4
+ * Connection URI strings
5
+ *
6
+ ***************************************/
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.parseConnectionUri = parseConnectionUri;
9
+ exports.parseOldStyleConnectionString = parseOldStyleConnectionString;
10
+ exports.parseConnectionString = parseConnectionString;
11
+ exports.normalizeOptions = normalizeOptions;
12
+ /**
13
+ * Option keys coerced to boolean when they arrive as URI query parameters.
14
+ * "1"/"true"/"yes"/"on" (case-insensitive) → true, everything else → false.
15
+ */
16
+ const BOOLEAN_KEYS = new Set([
17
+ 'lowercase_keys', 'blobAsText', 'wireCompression', 'manager',
18
+ 'namedPlaceholders', 'enableKeepAlive',
19
+ ]);
20
+ /** Option keys coerced to number when they arrive as URI query parameters. */
21
+ const NUMBER_KEYS = new Set([
22
+ 'port', 'pageSize', 'timeout', 'retryConnectionInterval',
23
+ 'blobChunkSize', 'blobReadChunkSize', 'wireCrypt', 'parallelWorkers',
24
+ 'maxInlineBlobSize', 'maxNegotiatedProtocols', 'connectTimeout',
25
+ 'min', 'idleTimeoutMillis', 'keepAliveInitialDelay',
26
+ ]);
27
+ function coerce(key, value) {
28
+ if (BOOLEAN_KEYS.has(key)) {
29
+ return /^(1|true|yes|on)$/i.test(value);
30
+ }
31
+ if (NUMBER_KEYS.has(key)) {
32
+ var n = Number(value);
33
+ if (Number.isNaN(n)) {
34
+ throw new Error('Invalid numeric value for connection URI option "' + key + '": ' + value);
35
+ }
36
+ return n;
37
+ }
38
+ return value;
39
+ }
40
+ /**
41
+ * Parse a firebird:// connection URI into an options object.
42
+ *
43
+ * firebird://user:password@host:port/database?option=value&...
44
+ *
45
+ * The database part:
46
+ * firebird://host/employee → alias "employee"
47
+ * firebird://host//var/db/prod.fdb → absolute path "/var/db/prod.fdb"
48
+ * firebird://host/var/db/prod.fdb → "/var/db/prod.fdb" (a database
49
+ * part with slashes is a path —
50
+ * aliases cannot contain "/")
51
+ * firebird://host/C:/db/prod.fdb → Windows path "C:/db/prod.fdb"
52
+ *
53
+ * Credentials and the database path are URL-decoded, so reserved characters
54
+ * can be percent-encoded (e.g. p%40ss for "p@ss"). Query parameters map
55
+ * 1:1 to option keys and are coerced to the option's type (booleans accept
56
+ * 1/true/yes/on). `user` and `password` may be given as query parameters
57
+ * instead of in the authority.
58
+ */
59
+ function parseConnectionUri(uri) {
60
+ var url;
61
+ try {
62
+ url = new URL(uri);
63
+ }
64
+ catch (e) {
65
+ throw new Error('Invalid connection URI: ' + uri);
66
+ }
67
+ if (url.protocol !== 'firebird:') {
68
+ throw new Error('Unsupported connection URI scheme "' + url.protocol.replace(/:$/, '') +
69
+ '" (expected firebird://...)');
70
+ }
71
+ var options = {};
72
+ if (url.hostname) {
73
+ // URL keeps IPv6 hostnames bracketed ([::1]); net.connect wants them bare
74
+ options.host = url.hostname.replace(/^\[(.*)\]$/, '$1');
75
+ }
76
+ if (url.port) {
77
+ options.port = Number(url.port);
78
+ }
79
+ if (url.username) {
80
+ options.user = decodeURIComponent(url.username);
81
+ }
82
+ if (url.password) {
83
+ options.password = decodeURIComponent(url.password);
84
+ }
85
+ var database = decodeURIComponent(url.pathname || '');
86
+ if (database.startsWith('/')) {
87
+ database = database.slice(1);
88
+ }
89
+ // A database part with path separators is a filesystem path, not an
90
+ // alias (aliases cannot contain "/") — restore the leading slash unless
91
+ // it is a Windows drive path or already absolute (double-slash form).
92
+ if (database.includes('/') && !database.startsWith('/') && !/^[A-Za-z]:\//.test(database)) {
93
+ database = '/' + database;
94
+ }
95
+ if (database) {
96
+ options.database = database;
97
+ }
98
+ url.searchParams.forEach(function (value, key) {
99
+ options[key] = coerce(key, value);
100
+ });
101
+ return options;
102
+ }
103
+ /**
104
+ * Parse a traditional ("old style") Firebird connection string:
105
+ *
106
+ * [host[/port]:]{path | alias}
107
+ *
108
+ * employee → alias "employee" (default host)
109
+ * /var/fb/prod.fdb → local path (default host)
110
+ * C:\fbdata\prod.fdb → Windows path — a single character
111
+ * before ":" is a drive letter, not
112
+ * a host (same rule as Firebird)
113
+ * db.example.com:employee → host + alias
114
+ * db.example.com/3051:/var/fb/prod.fdb → host + port + path
115
+ * myserver:C:\fbdata\prod.fdb → host + Windows path
116
+ * [::1]/3050:employee → IPv6 host + port + alias
117
+ *
118
+ * Unlike firebird:// URIs, traditional strings carry no credentials or
119
+ * options — the driver defaults apply (SYSDBA/masterkey, port 3050).
120
+ * The port must be numeric; /etc/services names are not resolved.
121
+ */
122
+ function parseOldStyleConnectionString(str) {
123
+ var options = {};
124
+ var host = null;
125
+ var port = null;
126
+ var database = str;
127
+ var ipv6 = /^\[([^\]]+)\](?:\/([^:]*))?:(.*)$/.exec(str);
128
+ if (ipv6) {
129
+ host = ipv6[1];
130
+ port = ipv6[2] !== undefined ? ipv6[2] : null;
131
+ database = ipv6[3];
132
+ }
133
+ else {
134
+ var colon = str.indexOf(':');
135
+ if (colon === 0) {
136
+ throw new Error('Invalid connection string (empty host): ' + str);
137
+ }
138
+ // colon === 1 → single character before ":" is a drive letter;
139
+ // colon === -1 → no host part. Both leave the whole string as database.
140
+ if (colon > 1) {
141
+ var hostPart = str.slice(0, colon);
142
+ database = str.slice(colon + 1);
143
+ var slash = hostPart.indexOf('/');
144
+ if (slash !== -1) {
145
+ host = hostPart.slice(0, slash);
146
+ port = hostPart.slice(slash + 1);
147
+ if (!host) {
148
+ throw new Error('Invalid connection string (empty host): ' + str);
149
+ }
150
+ }
151
+ else {
152
+ host = hostPart;
153
+ }
154
+ }
155
+ }
156
+ if (!database) {
157
+ throw new Error('Invalid connection string (empty database): ' + str);
158
+ }
159
+ if (host) {
160
+ options.host = host;
161
+ }
162
+ if (port !== null) {
163
+ var n = Number(port);
164
+ if (!/^\d+$/.test(port) || n < 1 || n > 65535) {
165
+ throw new Error('Invalid port in connection string "' + str +
166
+ '" (service names are not supported — use a numeric port)');
167
+ }
168
+ options.port = n;
169
+ }
170
+ options.database = database;
171
+ return options;
172
+ }
173
+ const URI_SCHEME = /^[A-Za-z][A-Za-z0-9+.-]*:\/\//;
174
+ /**
175
+ * Parse any connection string the driver accepts: a firebird:// URI, or a
176
+ * traditional [host[/port]:]database string when there is no scheme.
177
+ */
178
+ function parseConnectionString(str) {
179
+ return URI_SCHEME.test(str)
180
+ ? parseConnectionUri(str)
181
+ : parseOldStyleConnectionString(str);
182
+ }
183
+ /**
184
+ * Accept either an options object or a connection string (firebird:// URI
185
+ * or traditional host[/port]:database) everywhere options are taken.
186
+ * Strings are parsed; objects pass through unchanged.
187
+ */
188
+ function normalizeOptions(options) {
189
+ if (typeof options === 'string') {
190
+ return parseConnectionString(options);
191
+ }
192
+ return options;
193
+ }