@grupodiariodaregiao/bunstone 0.7.1 → 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.
- package/AGENTS.md +124 -0
- package/dist/index.d.ts +22 -22
- package/dist/index.js +104471 -104399
- package/dist/lib/database/sql-module.d.ts +25 -28
- package/dist/lib/errors/index.d.ts +3 -1
- package/docs/database-sql.md +62 -0
- package/docs/pt-BR/database-sql.md +62 -0
- package/lib/database/sql-module.ts +217 -84
- package/lib/errors/index.ts +14 -1
- package/package.json +1 -1
|
@@ -1,38 +1,36 @@
|
|
|
1
1
|
import { SQL } from "bun";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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 & {
|
|
10
12
|
/**
|
|
11
|
-
*
|
|
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`.
|
|
13
16
|
*/
|
|
14
|
-
|
|
15
|
-
/** Seconds to wait when opening a new connection. Bun default: 30 */
|
|
16
|
-
connectionTimeout?: number;
|
|
17
|
+
timezone?: string;
|
|
17
18
|
};
|
|
18
|
-
export type
|
|
19
|
+
export type SqlConnectionDetails = {
|
|
19
20
|
host: string;
|
|
20
21
|
port: number;
|
|
21
22
|
username: string;
|
|
22
23
|
password: string;
|
|
23
24
|
database: string;
|
|
24
|
-
provider:
|
|
25
|
-
/**
|
|
26
|
-
* Timezone used for date/time interpretation on the database connection.
|
|
27
|
-
* Defaults to 'UTC' to ensure consistent, offset-free date handling.
|
|
28
|
-
* Set to 'local' to use the process timezone, or any valid tz identifier.
|
|
29
|
-
*/
|
|
30
|
-
timezone?: string;
|
|
25
|
+
provider: Provider;
|
|
31
26
|
};
|
|
32
|
-
export type
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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">;
|
|
36
34
|
export declare class SqlService {
|
|
37
35
|
query<T = any>(query: string, params?: any[]): Promise<T[]>;
|
|
38
36
|
transaction(queries: Array<{
|
|
@@ -48,9 +46,8 @@ export declare class SqlService {
|
|
|
48
46
|
export declare class SqlModule {
|
|
49
47
|
private static sqlInstance;
|
|
50
48
|
private static provider;
|
|
51
|
-
static register(connection:
|
|
52
|
-
static register(connection: string,
|
|
53
|
-
static register(connection: string, options: SqlRegisterOptions): typeof SqlModule;
|
|
49
|
+
static register(connection: ConnectionOptions): typeof SqlModule;
|
|
50
|
+
static register(connection: string, options?: SqlModuleOptions | string): typeof SqlModule;
|
|
54
51
|
static getSqlInstance(): SQL;
|
|
55
52
|
static getProvider(): Provider | undefined;
|
|
56
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.
|
package/docs/database-sql.md
CHANGED
|
@@ -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.
|
|
@@ -9,49 +9,134 @@ import { DatabaseError } from "../errors";
|
|
|
9
9
|
import { Injectable } from "../injectable";
|
|
10
10
|
import { Module } from "../module";
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
idleTimeout
|
|
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 & {
|
|
20
44
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
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`.
|
|
23
48
|
*/
|
|
24
|
-
|
|
25
|
-
/** Seconds to wait when opening a new connection. Bun default: 30 */
|
|
26
|
-
connectionTimeout?: number;
|
|
49
|
+
timezone?: string;
|
|
27
50
|
};
|
|
28
51
|
|
|
29
|
-
export type
|
|
52
|
+
export type SqlConnectionDetails = {
|
|
30
53
|
host: string;
|
|
31
54
|
port: number;
|
|
32
55
|
username: string;
|
|
33
56
|
password: string;
|
|
34
57
|
database: string;
|
|
35
|
-
provider:
|
|
36
|
-
/**
|
|
37
|
-
* Timezone used for date/time interpretation on the database connection.
|
|
38
|
-
* Defaults to 'UTC' to ensure consistent, offset-free date handling.
|
|
39
|
-
* Set to 'local' to use the process timezone, or any valid tz identifier.
|
|
40
|
-
*/
|
|
41
|
-
timezone?: string;
|
|
58
|
+
provider: Provider;
|
|
42
59
|
};
|
|
43
60
|
|
|
44
|
-
export type
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
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)[];
|
|
49
107
|
|
|
50
108
|
const CONNECTION_CLOSED_CODES = new Set([
|
|
51
109
|
"ERR_MYSQL_CONNECTION_CLOSED",
|
|
52
110
|
"ERR_POSTGRES_CONNECTION_CLOSED",
|
|
53
111
|
]);
|
|
54
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
|
+
}
|
|
139
|
+
|
|
55
140
|
function detectProvider(url: string): Provider {
|
|
56
141
|
if (url.startsWith("mysql://") || url.startsWith("mysql2://")) {
|
|
57
142
|
return "mysql";
|
|
@@ -71,34 +156,106 @@ function detectProvider(url: string): Provider {
|
|
|
71
156
|
function buildConnectionConfig(
|
|
72
157
|
provider: Provider,
|
|
73
158
|
timezone: string,
|
|
74
|
-
):
|
|
159
|
+
): NonNullable<SQL.PostgresOrMySQLOptions["connection"]> | undefined {
|
|
75
160
|
if (provider === "postgresql") {
|
|
76
161
|
return { TimeZone: timezone };
|
|
77
162
|
}
|
|
78
163
|
if (provider === "mysql") {
|
|
79
|
-
// Normalise to MySQL's expected offset format ('+00:00') or named zone
|
|
80
164
|
const tz = timezone.toLowerCase() === "utc" ? "+00:00" : timezone;
|
|
81
165
|
return { time_zone: tz };
|
|
82
166
|
}
|
|
83
167
|
return undefined;
|
|
84
168
|
}
|
|
85
169
|
|
|
86
|
-
function
|
|
87
|
-
|
|
88
|
-
):
|
|
89
|
-
if (
|
|
90
|
-
return
|
|
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;
|
|
91
204
|
}
|
|
92
205
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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;
|
|
99
221
|
}
|
|
100
222
|
|
|
101
|
-
|
|
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
|
+
};
|
|
102
259
|
}
|
|
103
260
|
|
|
104
261
|
function isConnectionClosedError(err: unknown): boolean {
|
|
@@ -138,10 +295,7 @@ export class SqlService {
|
|
|
138
295
|
return withConnectionRetry(() => this.executeBulkInsert(table, values));
|
|
139
296
|
}
|
|
140
297
|
|
|
141
|
-
private async executeQuery<T>(
|
|
142
|
-
query: string,
|
|
143
|
-
params?: any[],
|
|
144
|
-
): Promise<T[]> {
|
|
298
|
+
private async executeQuery<T>(query: string, params?: any[]): Promise<T[]> {
|
|
145
299
|
const sql = this.getSqlInstance();
|
|
146
300
|
const tracer = trace.getTracer("bunstone.db");
|
|
147
301
|
const operation = detectOperation(query);
|
|
@@ -209,7 +363,10 @@ export class SqlService {
|
|
|
209
363
|
);
|
|
210
364
|
}
|
|
211
365
|
|
|
212
|
-
private async executeBulkInsert<T>(
|
|
366
|
+
private async executeBulkInsert<T>(
|
|
367
|
+
table: string,
|
|
368
|
+
values: T[],
|
|
369
|
+
): Promise<void> {
|
|
213
370
|
const sql = this.getSqlInstance();
|
|
214
371
|
const tracer = trace.getTracer("bunstone.db");
|
|
215
372
|
const span = tracer.startSpan("db.INSERT", {
|
|
@@ -257,51 +414,31 @@ export class SqlModule {
|
|
|
257
414
|
private static sqlInstance: SQL;
|
|
258
415
|
private static provider: Provider | undefined;
|
|
259
416
|
|
|
260
|
-
static register(connection:
|
|
261
|
-
static register(connection: string, timezone?: string): typeof SqlModule;
|
|
417
|
+
static register(connection: ConnectionOptions): typeof SqlModule;
|
|
262
418
|
static register(
|
|
263
419
|
connection: string,
|
|
264
|
-
options
|
|
420
|
+
options?: SqlModuleOptions | string,
|
|
265
421
|
): typeof SqlModule;
|
|
266
422
|
static register(
|
|
267
|
-
connection: string |
|
|
268
|
-
|
|
423
|
+
connection: string | ConnectionOptions,
|
|
424
|
+
options?: SqlModuleOptions | string,
|
|
269
425
|
) {
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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
|
+
);
|
|
279
435
|
|
|
280
436
|
SqlModule.provider = provider;
|
|
281
437
|
|
|
282
|
-
let timezone = "UTC";
|
|
283
|
-
let poolOptions: SqlPoolOptions | undefined;
|
|
284
|
-
|
|
285
|
-
if (typeof connection === "string") {
|
|
286
|
-
if (typeof second === "string") {
|
|
287
|
-
timezone = second;
|
|
288
|
-
} else if (second) {
|
|
289
|
-
timezone = second.timezone ?? "UTC";
|
|
290
|
-
poolOptions = pickPoolOptions(second);
|
|
291
|
-
}
|
|
292
|
-
} else {
|
|
293
|
-
timezone = connection.timezone ?? "UTC";
|
|
294
|
-
poolOptions = pickPoolOptions(connection);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
const connectionConfig = buildConnectionConfig(provider, timezone);
|
|
298
|
-
const pool = poolOptions ?? {};
|
|
299
|
-
|
|
300
438
|
SqlModule.sqlInstance = new SQL({
|
|
301
439
|
url,
|
|
302
|
-
...
|
|
303
|
-
|
|
304
|
-
});
|
|
440
|
+
...sqlOptions,
|
|
441
|
+
} as SQL.Options);
|
|
305
442
|
|
|
306
443
|
return SqlModule;
|
|
307
444
|
}
|
|
@@ -337,10 +474,6 @@ function detectOperation(query: string): string {
|
|
|
337
474
|
return "QUERY";
|
|
338
475
|
}
|
|
339
476
|
|
|
340
|
-
/**
|
|
341
|
-
* Strips parameter values from SQL for safe inclusion in telemetry.
|
|
342
|
-
* Keeps the query structure readable without leaking user data.
|
|
343
|
-
*/
|
|
344
477
|
function sanitizeSql(query: string): string {
|
|
345
478
|
return query
|
|
346
479
|
.replace(/'[^']*'/g, "'?'")
|
package/lib/errors/index.ts
CHANGED
|
@@ -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 ───────────────────────────────────────────────────────────────────
|