@grupodiariodaregiao/bunstone 0.7.0 → 0.7.2

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.
@@ -86,6 +86,7 @@ export declare class AppStartup {
86
86
  private static buildEffectiveRateLimit;
87
87
  private static registerTimeouts;
88
88
  private static registerCronJobs;
89
+ private static invokeScheduledHandler;
89
90
  private static registerBullMqWorkers;
90
91
  /**
91
92
  * Sets up RabbitMQ consumers for every provider that has `@RabbitSubscribe`
@@ -1,19 +1,36 @@
1
1
  import { SQL } from "bun";
2
- type ConnectionOptions = {
2
+ type Provider = "postgresql" | "mysql" | "sqlite";
3
+ type BunSqlPoolOptions = Pick<SQL.PostgresOrMySQLOptions, "max" | "maxLifetime" | "connectionTimeout" | "idleTimeout" | "connection" | "tls" | "prepare" | "bigint" | "onconnect" | "onclose" | "path">;
4
+ type BunSqliteClientOptions = Pick<SQL.SQLiteOptions, "readonly" | "create" | "safeIntegers" | "strict">;
5
+ /** Options forwarded by Bunstone to the underlying Bun.SQL client. */
6
+ export type BunSqlClientOptions = Partial<BunSqlPoolOptions & BunSqliteClientOptions>;
7
+ /**
8
+ * Options accepted by {@link SqlModule.register}.
9
+ * Only Bunstone-supported settings are allowed.
10
+ */
11
+ export type SqlModuleOptions = BunSqlClientOptions & {
12
+ /**
13
+ * Timezone used for date/time interpretation on the database connection.
14
+ * Mapped to driver `connection` settings when not provided explicitly.
15
+ * Defaults to `UTC`.
16
+ */
17
+ timezone?: string;
18
+ };
19
+ export type SqlConnectionDetails = {
3
20
  host: string;
4
21
  port: number;
5
22
  username: string;
6
23
  password: string;
7
24
  database: string;
8
- provider: "postgresql" | "mysql" | "sqlite";
9
- /**
10
- * Timezone used for date/time interpretation on the database connection.
11
- * Defaults to 'UTC' to ensure consistent, offset-free date handling.
12
- * Set to 'local' to use the process timezone, or any valid tz identifier.
13
- */
14
- timezone?: string;
25
+ provider: Provider;
15
26
  };
16
- type Provider = "postgresql" | "mysql" | "sqlite";
27
+ export type ConnectionOptions = SqlConnectionDetails & SqlModuleOptions;
28
+ /** @deprecated Use {@link ConnectionOptions} */
29
+ export type SqlConnectionOptions = ConnectionOptions;
30
+ /** @deprecated Use {@link SqlModuleOptions} */
31
+ export type SqlRegisterOptions = SqlModuleOptions;
32
+ /** Pool-related subset of {@link SqlModuleOptions}. */
33
+ export type SqlPoolOptions = Pick<SqlModuleOptions, "max" | "maxLifetime" | "connectionTimeout" | "idleTimeout">;
17
34
  export declare class SqlService {
18
35
  query<T = any>(query: string, params?: any[]): Promise<T[]>;
19
36
  transaction(queries: Array<{
@@ -21,13 +38,16 @@ export declare class SqlService {
21
38
  params?: any[];
22
39
  }>): Promise<void>;
23
40
  bulkInsert<T = any>(table: string, values: T[]): Promise<void>;
41
+ private executeQuery;
42
+ private executeTransaction;
43
+ private executeBulkInsert;
24
44
  private getSqlInstance;
25
45
  }
26
46
  export declare class SqlModule {
27
47
  private static sqlInstance;
28
48
  private static provider;
29
49
  static register(connection: ConnectionOptions): typeof SqlModule;
30
- static register(connection: string, timezone?: string): typeof SqlModule;
50
+ static register(connection: string, options?: SqlModuleOptions | string): typeof SqlModule;
31
51
  static getSqlInstance(): SQL;
32
52
  static getProvider(): Provider | undefined;
33
53
  }
@@ -75,10 +75,12 @@ export declare class CqrsError extends BunstoneError {
75
75
  * Codes:
76
76
  * - `BNS-DB-001` – SQL instance not initialised
77
77
  * - `BNS-DB-002` – query / transaction execution failed
78
+ * - `BNS-DB-003` – invalid SqlModule configuration option
78
79
  */
79
80
  export declare class DatabaseError extends BunstoneError {
80
- constructor(message: string, code?: "BNS-DB-001" | "BNS-DB-002", suggestion?: string, context?: Record<string, unknown>, cause?: Error);
81
+ constructor(message: string, code?: "BNS-DB-001" | "BNS-DB-002" | "BNS-DB-003", suggestion?: string, context?: Record<string, unknown>, cause?: Error);
81
82
  static notInitialized(): DatabaseError;
83
+ static invalidConfig(invalidKeys: string[], allowedKeys: readonly string[]): DatabaseError;
82
84
  }
83
85
  /**
84
86
  * Thrown when BullMQ operations fail.
@@ -43,6 +43,68 @@ OR using a connection string:
43
43
  export class AppModule {}
44
44
  ```
45
45
 
46
+ ### Connection options
47
+
48
+ You can pass a second argument with pool and client settings. Bunstone forwards only the supported options to [Bun.SQL](https://bun.sh/docs/api/sql):
49
+
50
+ ```typescript
51
+ @Module({
52
+ imports: [
53
+ SqlModule.register("mysql://user:pass@host:3306/db", {
54
+ maxLifetime: 25200,
55
+ connectionTimeout: 30,
56
+ max: 10,
57
+ timezone: "UTC",
58
+ }),
59
+ ],
60
+ })
61
+ export class AppModule {}
62
+ ```
63
+
64
+ The same options can be combined with the object-style registration:
65
+
66
+ ```typescript
67
+ SqlModule.register({
68
+ provider: "postgresql",
69
+ host: "localhost",
70
+ port: 5432,
71
+ username: "user",
72
+ password: "password",
73
+ database: "my_db",
74
+ max: 20,
75
+ idleTimeout: 30,
76
+ });
77
+ ```
78
+
79
+ For backward compatibility, the second argument may still be a timezone string:
80
+
81
+ ```typescript
82
+ SqlModule.register("mysql://user:pass@host/db", "UTC");
83
+ ```
84
+
85
+ #### Supported options (`SqlModuleOptions`)
86
+
87
+ | Option | Description |
88
+ |---|---|
89
+ | `timezone` | Bunstone helper for date/time handling. Defaults to `UTC`. Mapped to driver `connection` settings. |
90
+ | `max` | Maximum connections in the pool |
91
+ | `maxLifetime` | Maximum connection lifetime in seconds |
92
+ | `connectionTimeout` | Timeout when establishing a connection (seconds) |
93
+ | `idleTimeout` | Close idle connections after N seconds |
94
+ | `connection` | Driver-specific runtime settings (e.g. PostgreSQL `TimeZone`) |
95
+ | `tls` | TLS/SSL configuration |
96
+ | `prepare` | Enable automatic prepared statements |
97
+ | `bigint` | Return out-of-range integers as `BigInt` |
98
+ | `onconnect` | Callback when a connection attempt completes |
99
+ | `onclose` | Callback when a connection closes |
100
+ | `path` | Unix domain socket path |
101
+ | `readonly` | SQLite read-only mode |
102
+ | `create` | SQLite create-if-missing behavior |
103
+ | `safeIntegers` | SQLite safe integer handling |
104
+ | `strict` | SQLite strict mode |
105
+
106
+ Only the options listed above are accepted. Invalid keys are rejected by TypeScript and also throw `DatabaseError` (`BNS-DB-003`) at runtime.
107
+
46
108
  ## Usage
47
109
 
48
110
  Once registered, the `SqlService` is globally available. You can inject it into any controller or provider without needing to import `SqlModule` into subsequent modules.
@@ -43,6 +43,68 @@ OU usando uma string de conexão:
43
43
  export class AppModule {}
44
44
  ```
45
45
 
46
+ ### Opções de conexão
47
+
48
+ Você pode passar um segundo argumento com configurações de pool e do client. O Bunstone repassa apenas as opções suportadas para o [Bun.SQL](https://bun.sh/docs/api/sql):
49
+
50
+ ```typescript
51
+ @Module({
52
+ imports: [
53
+ SqlModule.register("mysql://user:pass@host:3306/db", {
54
+ maxLifetime: 25200,
55
+ connectionTimeout: 30,
56
+ max: 10,
57
+ timezone: "UTC",
58
+ }),
59
+ ],
60
+ })
61
+ export class AppModule {}
62
+ ```
63
+
64
+ As mesmas opções podem ser usadas no registro via objeto:
65
+
66
+ ```typescript
67
+ SqlModule.register({
68
+ provider: "postgresql",
69
+ host: "localhost",
70
+ port: 5432,
71
+ username: "user",
72
+ password: "password",
73
+ database: "my_db",
74
+ max: 20,
75
+ idleTimeout: 30,
76
+ });
77
+ ```
78
+
79
+ Por compatibilidade, o segundo argumento ainda pode ser uma string de timezone:
80
+
81
+ ```typescript
82
+ SqlModule.register("mysql://user:pass@host/db", "UTC");
83
+ ```
84
+
85
+ #### Opções suportadas (`SqlModuleOptions`)
86
+
87
+ | Opção | Descrição |
88
+ |---|---|
89
+ | `timezone` | Helper do Bunstone para datas/horas. Padrão: `UTC`. Mapeado para `connection` do driver. |
90
+ | `max` | Máximo de conexões no pool |
91
+ | `maxLifetime` | Tempo máximo de vida da conexão em segundos |
92
+ | `connectionTimeout` | Timeout ao estabelecer conexão (segundos) |
93
+ | `idleTimeout` | Fecha conexões ociosas após N segundos |
94
+ | `connection` | Configurações runtime do driver (ex.: `TimeZone` no PostgreSQL) |
95
+ | `tls` | Configuração TLS/SSL |
96
+ | `prepare` | Habilita prepared statements automáticos |
97
+ | `bigint` | Retorna inteiros fora do range como `BigInt` |
98
+ | `onconnect` | Callback ao concluir tentativa de conexão |
99
+ | `onclose` | Callback ao fechar conexão |
100
+ | `path` | Caminho do Unix domain socket |
101
+ | `readonly` | Modo somente leitura (SQLite) |
102
+ | `create` | Comportamento de criação do arquivo (SQLite) |
103
+ | `safeIntegers` | Tratamento seguro de inteiros (SQLite) |
104
+ | `strict` | Modo strict (SQLite) |
105
+
106
+ Somente as opções listadas acima são aceitas. Chaves inválidas são rejeitadas pelo TypeScript e também lançam `DatabaseError` (`BNS-DB-003`) em runtime.
107
+
46
108
  ## Uso
47
109
 
48
110
  Depois de registrado, o `SqlService` fica disponível globalmente. Você pode injetá-lo em qualquer controller ou provider sem precisar importar o `SqlModule` nos módulos subsequentes.
@@ -906,7 +906,11 @@ export class AppStartup {
906
906
  `Scheduling timeout for method: ${timeout.methodName} with delay: ${timeout.delay}ms`,
907
907
  );
908
908
  setTimeout(() => {
909
- provider[timeout.methodName]();
909
+ void AppStartup.invokeScheduledHandler(
910
+ provider,
911
+ timeout.methodName,
912
+ "timeout",
913
+ );
910
914
  }, timeout.delay);
911
915
  }
912
916
  }
@@ -935,12 +939,40 @@ export class AppStartup {
935
939
  for (const cron of crons) {
936
940
  AppStartup.logger.log(`Scheduling cron for method: ${cron.methodName}`);
937
941
  scheduler.schedule(cron.expression, () => {
938
- provider[cron.methodName]();
942
+ void AppStartup.invokeScheduledHandler(
943
+ provider,
944
+ cron.methodName,
945
+ "cron",
946
+ );
939
947
  });
940
948
  }
941
949
  }
942
950
  }
943
951
 
952
+ private static async invokeScheduledHandler(
953
+ provider: Record<string, unknown>,
954
+ methodName: string,
955
+ kind: "cron" | "timeout",
956
+ ): Promise<void> {
957
+ const handler = provider[methodName];
958
+ if (typeof handler !== "function") {
959
+ AppStartup.logger.error(
960
+ `${kind === "cron" ? "Cron" : "Timeout"} job "${methodName}" is not a method on the provider`,
961
+ );
962
+ return;
963
+ }
964
+
965
+ try {
966
+ const result = handler.call(provider);
967
+ await Promise.resolve(result);
968
+ } catch (err) {
969
+ AppStartup.logger.error(
970
+ `${kind === "cron" ? "Cron" : "Timeout"} job "${methodName}" failed`,
971
+ err,
972
+ );
973
+ }
974
+ }
975
+
944
976
  private static registerBullMqWorkers(module: any) {
945
977
  const providersBullMq: Map<any, { name?: string; methodName: string }[]> =
946
978
  Reflect.getMetadata("dip:bullmq", module);
@@ -9,22 +9,133 @@ import { DatabaseError } from "../errors";
9
9
  import { Injectable } from "../injectable";
10
10
  import { Module } from "../module";
11
11
 
12
- type ConnectionOptions = {
12
+ type Provider = "postgresql" | "mysql" | "sqlite";
13
+
14
+ type BunSqlPoolOptions = Pick<
15
+ SQL.PostgresOrMySQLOptions,
16
+ | "max"
17
+ | "maxLifetime"
18
+ | "connectionTimeout"
19
+ | "idleTimeout"
20
+ | "connection"
21
+ | "tls"
22
+ | "prepare"
23
+ | "bigint"
24
+ | "onconnect"
25
+ | "onclose"
26
+ | "path"
27
+ >;
28
+
29
+ type BunSqliteClientOptions = Pick<
30
+ SQL.SQLiteOptions,
31
+ "readonly" | "create" | "safeIntegers" | "strict"
32
+ >;
33
+
34
+ /** Options forwarded by Bunstone to the underlying Bun.SQL client. */
35
+ export type BunSqlClientOptions = Partial<
36
+ BunSqlPoolOptions & BunSqliteClientOptions
37
+ >;
38
+
39
+ /**
40
+ * Options accepted by {@link SqlModule.register}.
41
+ * Only Bunstone-supported settings are allowed.
42
+ */
43
+ export type SqlModuleOptions = BunSqlClientOptions & {
44
+ /**
45
+ * Timezone used for date/time interpretation on the database connection.
46
+ * Mapped to driver `connection` settings when not provided explicitly.
47
+ * Defaults to `UTC`.
48
+ */
49
+ timezone?: string;
50
+ };
51
+
52
+ export type SqlConnectionDetails = {
13
53
  host: string;
14
54
  port: number;
15
55
  username: string;
16
56
  password: string;
17
57
  database: string;
18
- provider: "postgresql" | "mysql" | "sqlite";
19
- /**
20
- * Timezone used for date/time interpretation on the database connection.
21
- * Defaults to 'UTC' to ensure consistent, offset-free date handling.
22
- * Set to 'local' to use the process timezone, or any valid tz identifier.
23
- */
24
- timezone?: string;
58
+ provider: Provider;
25
59
  };
26
60
 
27
- type Provider = "postgresql" | "mysql" | "sqlite";
61
+ export type ConnectionOptions = SqlConnectionDetails & SqlModuleOptions;
62
+
63
+ /** @deprecated Use {@link ConnectionOptions} */
64
+ export type SqlConnectionOptions = ConnectionOptions;
65
+
66
+ /** @deprecated Use {@link SqlModuleOptions} */
67
+ export type SqlRegisterOptions = SqlModuleOptions;
68
+
69
+ /** Pool-related subset of {@link SqlModuleOptions}. */
70
+ export type SqlPoolOptions = Pick<
71
+ SqlModuleOptions,
72
+ "max" | "maxLifetime" | "connectionTimeout" | "idleTimeout"
73
+ >;
74
+
75
+ type SqlClientInitOptions = Omit<
76
+ SqlModuleOptions,
77
+ "url" | "filename" | "timezone"
78
+ >;
79
+
80
+ const SQL_MODULE_OPTION_KEYS = [
81
+ "timezone",
82
+ "max",
83
+ "maxLifetime",
84
+ "connectionTimeout",
85
+ "idleTimeout",
86
+ "connection",
87
+ "tls",
88
+ "prepare",
89
+ "bigint",
90
+ "onconnect",
91
+ "onclose",
92
+ "path",
93
+ "readonly",
94
+ "create",
95
+ "safeIntegers",
96
+ "strict",
97
+ ] as const satisfies readonly (keyof SqlModuleOptions)[];
98
+
99
+ const SQL_CONNECTION_DETAIL_KEYS = [
100
+ "host",
101
+ "port",
102
+ "username",
103
+ "password",
104
+ "database",
105
+ "provider",
106
+ ] as const satisfies readonly (keyof SqlConnectionDetails)[];
107
+
108
+ const CONNECTION_CLOSED_CODES = new Set([
109
+ "ERR_MYSQL_CONNECTION_CLOSED",
110
+ "ERR_POSTGRES_CONNECTION_CLOSED",
111
+ ]);
112
+
113
+ function assertOnlyAllowedKeys(
114
+ source: Record<string, unknown>,
115
+ allowedKeys: readonly string[],
116
+ ): void {
117
+ const invalidKeys = Object.keys(source).filter(
118
+ (key) => !allowedKeys.includes(key),
119
+ );
120
+
121
+ if (invalidKeys.length > 0) {
122
+ throw DatabaseError.invalidConfig(invalidKeys, allowedKeys);
123
+ }
124
+ }
125
+
126
+ function pickSqlModuleOptions(
127
+ source: Record<string, unknown>,
128
+ ): SqlModuleOptions {
129
+ assertOnlyAllowedKeys(source, SQL_MODULE_OPTION_KEYS);
130
+
131
+ const picked = {} as SqlModuleOptions;
132
+ for (const key of SQL_MODULE_OPTION_KEYS) {
133
+ if (key in source) {
134
+ (picked as Record<string, unknown>)[key] = source[key];
135
+ }
136
+ }
137
+ return picked;
138
+ }
28
139
 
29
140
  function detectProvider(url: string): Provider {
30
141
  if (url.startsWith("mysql://") || url.startsWith("mysql2://")) {
@@ -45,21 +156,146 @@ function detectProvider(url: string): Provider {
45
156
  function buildConnectionConfig(
46
157
  provider: Provider,
47
158
  timezone: string,
48
- ): Record<string, string | boolean | number> | undefined {
159
+ ): NonNullable<SQL.PostgresOrMySQLOptions["connection"]> | undefined {
49
160
  if (provider === "postgresql") {
50
161
  return { TimeZone: timezone };
51
162
  }
52
163
  if (provider === "mysql") {
53
- // Normalise to MySQL's expected offset format ('+00:00') or named zone
54
164
  const tz = timezone.toLowerCase() === "utc" ? "+00:00" : timezone;
55
165
  return { time_zone: tz };
56
166
  }
57
167
  return undefined;
58
168
  }
59
169
 
170
+ function normalizeRegisterOptions(
171
+ options?: SqlModuleOptions | string,
172
+ ): SqlModuleOptions {
173
+ if (typeof options === "string") {
174
+ return { timezone: options };
175
+ }
176
+ if (!options) {
177
+ return {};
178
+ }
179
+
180
+ return pickSqlModuleOptions(options as Record<string, unknown>);
181
+ }
182
+
183
+ function buildSqlClientOptions(
184
+ provider: Provider,
185
+ timezone: string,
186
+ options: SqlModuleOptions,
187
+ ): SqlClientInitOptions {
188
+ const { timezone: _timezone, connection: userConnection, ...rest } = options;
189
+ const driverConnectionConfig = buildConnectionConfig(provider, timezone);
190
+ const connection = {
191
+ ...driverConnectionConfig,
192
+ ...userConnection,
193
+ };
194
+
195
+ return {
196
+ ...rest,
197
+ ...(Object.keys(connection).length > 0 && { connection }),
198
+ };
199
+ }
200
+
201
+ function resolveConnectionUrl(connection: string | ConnectionOptions): string {
202
+ if (typeof connection === "string") {
203
+ return connection;
204
+ }
205
+
206
+ return `${connection.provider}://${connection.username}:${connection.password}@${connection.host}:${connection.port}/${connection.database}`;
207
+ }
208
+
209
+ function resolveProvider(connection: string | ConnectionOptions): Provider {
210
+ return typeof connection === "string"
211
+ ? detectProvider(connection)
212
+ : connection.provider;
213
+ }
214
+
215
+ function resolveTimezone(
216
+ connection: string | ConnectionOptions,
217
+ options: SqlModuleOptions,
218
+ ): string {
219
+ if (options.timezone) {
220
+ return options.timezone;
221
+ }
222
+
223
+ if (typeof connection === "object" && connection.timezone) {
224
+ return connection.timezone;
225
+ }
226
+
227
+ return "UTC";
228
+ }
229
+
230
+ function resolveRegisterOptions(
231
+ connection: string | ConnectionOptions,
232
+ options?: SqlModuleOptions | string,
233
+ ): SqlModuleOptions {
234
+ const normalized = normalizeRegisterOptions(options);
235
+
236
+ if (typeof connection !== "object") {
237
+ return normalized;
238
+ }
239
+
240
+ assertOnlyAllowedKeys(
241
+ connection as Record<string, unknown>,
242
+ [...SQL_CONNECTION_DETAIL_KEYS, ...SQL_MODULE_OPTION_KEYS],
243
+ );
244
+
245
+ const {
246
+ host: _host,
247
+ port: _port,
248
+ username: _username,
249
+ password: _password,
250
+ database: _database,
251
+ provider: _provider,
252
+ ...connectionSqlOptions
253
+ } = connection;
254
+
255
+ return {
256
+ ...pickSqlModuleOptions(connectionSqlOptions as Record<string, unknown>),
257
+ ...normalized,
258
+ };
259
+ }
260
+
261
+ function isConnectionClosedError(err: unknown): boolean {
262
+ return (
263
+ typeof err === "object" &&
264
+ err !== null &&
265
+ "code" in err &&
266
+ CONNECTION_CLOSED_CODES.has(String((err as { code: unknown }).code))
267
+ );
268
+ }
269
+
270
+ async function withConnectionRetry<T>(fn: () => Promise<T>): Promise<T> {
271
+ try {
272
+ return await fn();
273
+ } catch (err) {
274
+ if (!isConnectionClosedError(err)) {
275
+ throw err;
276
+ }
277
+ }
278
+
279
+ return await fn();
280
+ }
281
+
60
282
  @Injectable()
61
283
  export class SqlService {
62
284
  async query<T = any>(query: string, params?: any[]): Promise<T[]> {
285
+ return withConnectionRetry(() => this.executeQuery<T>(query, params));
286
+ }
287
+
288
+ async transaction(
289
+ queries: Array<{ query: string; params?: any[] }>,
290
+ ): Promise<void> {
291
+ return withConnectionRetry(() => this.executeTransaction(queries));
292
+ }
293
+
294
+ async bulkInsert<T = any>(table: string, values: T[]): Promise<void> {
295
+ return withConnectionRetry(() => this.executeBulkInsert(table, values));
296
+ }
297
+
298
+ private async executeQuery<T>(query: string, params?: any[]): Promise<T[]> {
63
299
  const sql = this.getSqlInstance();
64
300
  const tracer = trace.getTracer("bunstone.db");
65
301
  const operation = detectOperation(query);
@@ -91,7 +327,7 @@ export class SqlService {
91
327
  );
92
328
  }
93
329
 
94
- async transaction(
330
+ private async executeTransaction(
95
331
  queries: Array<{ query: string; params?: any[] }>,
96
332
  ): Promise<void> {
97
333
  const sql = this.getSqlInstance();
@@ -127,7 +363,10 @@ export class SqlService {
127
363
  );
128
364
  }
129
365
 
130
- async bulkInsert<T = any>(table: string, values: T[]): Promise<void> {
366
+ private async executeBulkInsert<T>(
367
+ table: string,
368
+ values: T[],
369
+ ): Promise<void> {
131
370
  const sql = this.getSqlInstance();
132
371
  const tracer = trace.getTracer("bunstone.db");
133
372
  const span = tracer.startSpan("db.INSERT", {
@@ -176,31 +415,30 @@ export class SqlModule {
176
415
  private static provider: Provider | undefined;
177
416
 
178
417
  static register(connection: ConnectionOptions): typeof SqlModule;
179
- static register(connection: string, timezone?: string): typeof SqlModule;
180
- static register(connection: string | ConnectionOptions, timezone?: string) {
181
- const url =
182
- typeof connection === "string"
183
- ? connection
184
- : `${connection.provider}://${connection.username}:${connection.password}@${connection.host}:${connection.port}/${connection.database}`;
185
-
186
- const provider =
187
- typeof connection === "string"
188
- ? detectProvider(connection)
189
- : connection.provider;
418
+ static register(
419
+ connection: string,
420
+ options?: SqlModuleOptions | string,
421
+ ): typeof SqlModule;
422
+ static register(
423
+ connection: string | ConnectionOptions,
424
+ options?: SqlModuleOptions | string,
425
+ ) {
426
+ const resolvedOptions = resolveRegisterOptions(connection, options);
427
+ const url = resolveConnectionUrl(connection);
428
+ const provider = resolveProvider(connection);
429
+ const timezone = resolveTimezone(connection, resolvedOptions);
430
+ const sqlOptions = buildSqlClientOptions(
431
+ provider,
432
+ timezone,
433
+ resolvedOptions,
434
+ );
190
435
 
191
436
  SqlModule.provider = provider;
192
437
 
193
- const tz =
194
- typeof connection === "string"
195
- ? (timezone ?? "UTC")
196
- : (connection.timezone ?? "UTC");
197
-
198
- const connectionConfig = buildConnectionConfig(provider, tz);
199
-
200
438
  SqlModule.sqlInstance = new SQL({
201
439
  url,
202
- ...(connectionConfig && { connection: connectionConfig }),
203
- });
440
+ ...sqlOptions,
441
+ } as SQL.Options);
204
442
 
205
443
  return SqlModule;
206
444
  }
@@ -236,10 +474,6 @@ function detectOperation(query: string): string {
236
474
  return "QUERY";
237
475
  }
238
476
 
239
- /**
240
- * Strips parameter values from SQL for safe inclusion in telemetry.
241
- * Keeps the query structure readable without leaking user data.
242
- */
243
477
  function sanitizeSql(query: string): string {
244
478
  return query
245
479
  .replace(/'[^']*'/g, "'?'")
@@ -244,11 +244,12 @@ export class CqrsError extends BunstoneError {
244
244
  * Codes:
245
245
  * - `BNS-DB-001` – SQL instance not initialised
246
246
  * - `BNS-DB-002` – query / transaction execution failed
247
+ * - `BNS-DB-003` – invalid SqlModule configuration option
247
248
  */
248
249
  export class DatabaseError extends BunstoneError {
249
250
  constructor(
250
251
  message: string,
251
- code: "BNS-DB-001" | "BNS-DB-002" = "BNS-DB-001",
252
+ code: "BNS-DB-001" | "BNS-DB-002" | "BNS-DB-003" = "BNS-DB-001",
252
253
  suggestion?: string,
253
254
  context?: Record<string, unknown>,
254
255
  cause?: Error,
@@ -266,6 +267,18 @@ export class DatabaseError extends BunstoneError {
266
267
  ].join("\n "),
267
268
  );
268
269
  }
270
+
271
+ static invalidConfig(
272
+ invalidKeys: string[],
273
+ allowedKeys: readonly string[],
274
+ ): DatabaseError {
275
+ return new DatabaseError(
276
+ `Invalid SqlModule option(s): ${invalidKeys.join(", ")}.`,
277
+ "BNS-DB-003",
278
+ `Use only supported options: ${allowedKeys.join(", ")}.`,
279
+ { invalidKeys, allowedKeys },
280
+ );
281
+ }
269
282
  }
270
283
 
271
284
  // ─── BullMQ ───────────────────────────────────────────────────────────────────
package/package.json CHANGED
@@ -13,7 +13,7 @@
13
13
  "types": "./dist/*.d.ts"
14
14
  }
15
15
  },
16
- "version": "0.7.0",
16
+ "version": "0.7.2",
17
17
  "homepage": "https://bunstone.diario.one/",
18
18
  "repository": {
19
19
  "url": "https://github.com/diariodaregiao/bunstone.git",