@devopsplaybook.io/common-utils 1.10.1-beta.23.b1906d7 → 1.11.0-beta.24.d8f9aef

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 (69) hide show
  1. package/README.md +73 -27
  2. package/dist/src/ConfigBase.d.ts +31 -0
  3. package/dist/src/ConfigBase.js +58 -16
  4. package/dist/src/DbUtils.d.ts +20 -3
  5. package/dist/src/DbUtils.js +93 -2
  6. package/dist/src/DbUtilsNoTelemetry.d.ts +4 -1
  7. package/dist/src/DbUtilsNoTelemetry.js +62 -6
  8. package/dist/src/PostgresDbUtils.d.ts +41 -10
  9. package/dist/src/PostgresDbUtils.js +357 -304
  10. package/dist/src/SqlDbUtils.d.ts +12 -4
  11. package/dist/src/SqlDbUtils.js +76 -30
  12. package/dist/src/users/Auth.d.ts +11 -1
  13. package/dist/src/users/Auth.js +160 -44
  14. package/dist/src/users/User.d.ts +10 -0
  15. package/dist/src/users/User.js +30 -10
  16. package/dist/src/users/UserApiToken.d.ts +4 -0
  17. package/dist/src/users/UserApiToken.js +11 -0
  18. package/dist/src/users/UsersApiTokensData.d.ts +12 -0
  19. package/dist/src/users/UsersApiTokensData.js +130 -33
  20. package/dist/src/users/UsersData.d.ts +20 -1
  21. package/dist/src/users/UsersData.js +150 -52
  22. package/dist/src/users/UsersRoutes.js +178 -61
  23. package/dist/src/users/index.d.ts +8 -0
  24. package/dist/src/users/index.js +24 -0
  25. package/package.json +58 -1
  26. package/.github/workflows/main-build.yml +0 -18
  27. package/.github/workflows/pr-check.yml +0 -27
  28. package/.github/workflows/reusable-merge-build.yml +0 -197
  29. package/.github/workflows/reusable-npm-merge.yml +0 -135
  30. package/.github/workflows/reusable-npm-pr.yml +0 -183
  31. package/.github/workflows/reusable-npm-upgrade.yml +0 -92
  32. package/.github/workflows/reusable-pr-verify.yml +0 -181
  33. package/AGENTS.md +0 -105
  34. package/index.ts +0 -18
  35. package/jest.config.js +0 -17
  36. package/prettierrc.json +0 -5
  37. package/src/ConfigBase.spec.ts +0 -108
  38. package/src/ConfigBase.ts +0 -297
  39. package/src/DbUtils.spec.ts +0 -23
  40. package/src/DbUtils.ts +0 -116
  41. package/src/DbUtilsNoTelemetry.spec.ts +0 -168
  42. package/src/DbUtilsNoTelemetry.ts +0 -117
  43. package/src/LLM.spec.ts +0 -303
  44. package/src/LLM.ts +0 -204
  45. package/src/Notifications.spec.ts +0 -265
  46. package/src/Notifications.ts +0 -201
  47. package/src/OTelContext.spec.ts +0 -58
  48. package/src/OTelContext.ts +0 -63
  49. package/src/PostgresDbUtils.spec.ts +0 -153
  50. package/src/PostgresDbUtils.ts +0 -666
  51. package/src/SqlDbUtils.spec.ts +0 -108
  52. package/src/SqlDbUtils.ts +0 -152
  53. package/src/SystemCommand.spec.ts +0 -18
  54. package/src/SystemCommand.ts +0 -23
  55. package/src/Timeout.spec.ts +0 -18
  56. package/src/Timeout.ts +0 -12
  57. package/src/users/Auth.spec.ts +0 -268
  58. package/src/users/Auth.ts +0 -202
  59. package/src/users/User.ts +0 -75
  60. package/src/users/UserApiToken.ts +0 -55
  61. package/src/users/UserPassword.spec.ts +0 -28
  62. package/src/users/UserPassword.ts +0 -20
  63. package/src/users/UserSession.ts +0 -9
  64. package/src/users/UsersApiTokensData.spec.ts +0 -158
  65. package/src/users/UsersApiTokensData.ts +0 -125
  66. package/src/users/UsersData.ts +0 -141
  67. package/src/users/UsersRoutes.ts +0 -374
  68. package/tsconfig.json +0 -15
  69. package/tsconfig.spec.json +0 -8
package/src/ConfigBase.ts DELETED
@@ -1,297 +0,0 @@
1
- import { ConfigOTelInterface } from "@devopsplaybook.io/otel-utils";
2
- import * as fse from "fs-extra";
3
- import { v4 as uuidv4 } from "uuid";
4
- import path from "path";
5
-
6
- /**
7
- * Configuration field descriptor used by {@link ConfigBase.addConfigField}.
8
- */
9
- export interface ConfigFieldDef {
10
- /** Property name on the config instance. */
11
- field: string;
12
- /** When `true` the value is masked in log output. */
13
- sensitive?: boolean;
14
- /**
15
- * Alternative environment variable names to check when the primary field
16
- * name is not found in `process.env`. Aliases are tried in order and the
17
- * first match wins. Only checked in the environment layer, never in the
18
- * config-file layer.
19
- *
20
- * @example
21
- * ```ts
22
- * // Look for DATABASE_POSTGRES_HOST first, then fall back to POSTGRES_HOST
23
- * { field: "DATABASE_POSTGRES_HOST", envAliases: ["POSTGRES_HOST"] }
24
- * ```
25
- */
26
- envAliases?: string[];
27
- }
28
-
29
- /**
30
- * Database-specific configuration fields shared by every project that
31
- * supports both SQLite and PostgreSQL backends.
32
- */
33
- export interface ConfigDatabaseInterface {
34
- DATABASE_TYPE: "sqlite" | "postgres";
35
- DATABASE_POSTGRES_HOST: string;
36
- DATABASE_POSTGRES_PORT: number;
37
- DATABASE_POSTGRES_USER: string;
38
- DATABASE_POSTGRES_PASSWORD: string;
39
- DATABASE_POSTGRES_DATABASE: string;
40
- }
41
-
42
- /**
43
- * Common server configuration fields shared across projects.
44
- */
45
- export interface ConfigCommonInterface
46
- extends ConfigOTelInterface, ConfigDatabaseInterface {
47
- CONFIG_FILE: string;
48
- API_PORT: number;
49
- JWT_VALIDITY_DURATION: number;
50
- CORS_POLICY_ORIGIN: string;
51
- DATA_DIR: string;
52
- JWT_KEY: string;
53
- LOG_LEVEL: string;
54
- }
55
-
56
- /**
57
- * Coerce a string value read from an environment variable to match the
58
- * type of the default value (number → parseFloat, boolean → "true"/"1",
59
- * array → JSON.parse, etc.). When the default is already a string or
60
- * there is no default, the original string is returned as-is.
61
- */
62
- function coerceValue(value: string, defaultValue: any): any {
63
- if (defaultValue === undefined || defaultValue === null) {
64
- return value;
65
- }
66
-
67
- switch (typeof defaultValue) {
68
- case "number": {
69
- const parsed = Number(value);
70
- return Number.isNaN(parsed) ? value : parsed;
71
- }
72
- case "boolean": {
73
- return value === "true" || value === "1" || value === "yes";
74
- }
75
- case "object": {
76
- if (Array.isArray(defaultValue)) {
77
- try {
78
- return JSON.parse(value);
79
- } catch {
80
- return value;
81
- }
82
- }
83
- if (defaultValue !== null) {
84
- try {
85
- return JSON.parse(value);
86
- } catch {
87
- return value;
88
- }
89
- }
90
- return value;
91
- }
92
- default:
93
- return value;
94
- }
95
- }
96
-
97
- /**
98
- * Abstract base class for project configuration.
99
- *
100
- * Implements the three-layer override strategy used across all
101
- * devopsplaybook.io server projects:
102
- * 1. **Environment variable** (highest priority)
103
- * 2. **config.json** file value
104
- * 3. **Default** declared on the class property
105
- *
106
- * Subclasses add project-specific fields and call {@link addConfigField}
107
- * inside their constructor so that {@link reload} picks them up.
108
- *
109
- * @example
110
- * ```ts
111
- * class MyConfig extends ConfigBase {
112
- * public MY_SETTING = "default";
113
- * constructor() {
114
- * super("my-service");
115
- * this.addConfigField({ field: "MY_SETTING" });
116
- * }
117
- * }
118
- * ```
119
- */
120
- export abstract class ConfigBase implements ConfigCommonInterface {
121
- // -- OTel fields (ConfigOTelInterface) --
122
- public SERVICE_ID: string;
123
- public VERSION = "1";
124
- public OPENTELEMETRY_COLLECTOR_HTTP_TRACES = "";
125
- public OPENTELEMETRY_COLLECTOR_HTTP_METRICS = "";
126
- public OPENTELEMETRY_COLLECTOR_HTTP_LOGS = "";
127
- public OPENTELEMETRY_COLLECTOR_AWS = false;
128
- public OPENTELEMETRY_COLLECTOR_EXPORT_LOGS_INTERVAL_SECONDS = 60;
129
- public OPENTELEMETRY_COLLECTOR_EXPORT_METRICS_INTERVAL_SECONDS = 60;
130
- public OPENTELEMETRY_COLLECT_AUTHORIZATION_HEADER = "";
131
-
132
- // -- Common server fields --
133
- public CONFIG_FILE: string;
134
- public API_PORT = 8080;
135
- public JWT_VALIDITY_DURATION = 3 * 31 * 24 * 3600;
136
- public CORS_POLICY_ORIGIN = "";
137
- public DATA_DIR = process.env.DATA_DIR || "/data";
138
- public JWT_KEY: string = uuidv4();
139
- public LOG_LEVEL = "info";
140
-
141
- // -- Database fields --
142
- public DATABASE_TYPE: "sqlite" | "postgres" = "sqlite";
143
- public DATABASE_POSTGRES_HOST = "";
144
- public DATABASE_POSTGRES_PORT = 5432;
145
- public DATABASE_POSTGRES_USER = "";
146
- public DATABASE_POSTGRES_PASSWORD = "";
147
- public DATABASE_POSTGRES_DATABASE = "";
148
-
149
- /**
150
- * Fields registered by subclasses (or the base) that {@link reload}
151
- * should process. Common / DB / OTel fields are pre-registered.
152
- */
153
- private _fields: {
154
- field: string;
155
- sensitive: boolean;
156
- envAliases: string[];
157
- defaultValue: any;
158
- }[] = [];
159
-
160
- /**
161
- * @param serviceId Unique service identifier (e.g. `"cryptotrader-server"`).
162
- * @param configFile Optional path to the JSON config file. Defaults to `"config.json"`.
163
- */
164
- constructor(serviceId: string, configFile?: string) {
165
- this.SERVICE_ID = serviceId;
166
- this.CONFIG_FILE = configFile || process.env.CONFIG_FILE || "config.json";
167
-
168
- // Auto-detect version from nearest package.json
169
- try {
170
- const pkg = fse.readJsonSync(path.resolve(__dirname, "../package.json"));
171
- if (pkg && pkg.version) {
172
- this.VERSION = pkg.version;
173
- }
174
- // eslint-disable-next-line no-unused-vars -- catch binding kept so dist/ stays byte-identical to the TS6 build
175
- } catch (_e) {
176
- // fallback to "1"
177
- }
178
-
179
- // Pre-register base + DB + OTel fields so reload() handles them
180
- const baseFields: ConfigFieldDef[] = [
181
- { field: "JWT_VALIDITY_DURATION" },
182
- { field: "CORS_POLICY_ORIGIN" },
183
- { field: "DATA_DIR" },
184
- { field: "JWT_KEY", sensitive: true },
185
- { field: "LOG_LEVEL" },
186
- { field: "DATABASE_TYPE" },
187
- { field: "DATABASE_POSTGRES_HOST", envAliases: ["POSTGRES_HOST"] },
188
- { field: "DATABASE_POSTGRES_PORT", envAliases: ["POSTGRES_PORT"] },
189
- { field: "DATABASE_POSTGRES_USER", envAliases: ["POSTGRES_USER"] },
190
- {
191
- field: "DATABASE_POSTGRES_PASSWORD",
192
- sensitive: true,
193
- envAliases: ["POSTGRES_PASSWORD"],
194
- },
195
- { field: "DATABASE_POSTGRES_DATABASE", envAliases: ["POSTGRES_DB"] },
196
- { field: "OPENTELEMETRY_COLLECTOR_HTTP_TRACES" },
197
- { field: "OPENTELEMETRY_COLLECTOR_HTTP_METRICS" },
198
- { field: "OPENTELEMETRY_COLLECTOR_HTTP_LOGS" },
199
- { field: "OPENTELEMETRY_COLLECTOR_AWS" },
200
- {
201
- field: "OPENTELEMETRY_COLLECTOR_EXPORT_LOGS_INTERVAL_SECONDS",
202
- },
203
- {
204
- field: "OPENTELEMETRY_COLLECTOR_EXPORT_METRICS_INTERVAL_SECONDS",
205
- },
206
- {
207
- field: "OPENTELEMETRY_COLLECT_AUTHORIZATION_HEADER",
208
- sensitive: true,
209
- },
210
- ];
211
- for (const f of baseFields) {
212
- this.addConfigField(f);
213
- }
214
- }
215
-
216
- /**
217
- * Register a configuration field so that {@link reload} processes it.
218
- * Call this in your subclass constructor for every project-specific field.
219
- */
220
- public addConfigField(def: ConfigFieldDef): void {
221
- this._fields.push({
222
- field: def.field,
223
- sensitive: def.sensitive ?? false,
224
- envAliases: def.envAliases ?? [],
225
- defaultValue: (this as any)[def.field],
226
- });
227
- }
228
-
229
- /**
230
- * Load (or reload) configuration from the JSON file and environment variables.
231
- * Environment variables always take precedence over file values.
232
- *
233
- * @param logger Optional log callback `(message: string) => void`.
234
- * When omitted nothing is logged (useful in tests).
235
- */
236
- public async reload(logger?: (message: string) => void): Promise<void> {
237
- const log = logger ?? (() => {});
238
- let content: Record<string, unknown> = {};
239
- try {
240
- content = await fse.readJson(this.CONFIG_FILE);
241
- // eslint-disable-next-line no-unused-vars -- catch binding kept so dist/ stays byte-identical to the TS6 build
242
- } catch (_e) {
243
- // config file is optional – fall back to env + defaults
244
- }
245
-
246
- log(`Configuration Value: CONFIG_FILE: ${this.CONFIG_FILE}`);
247
- log(`Configuration Value: SERVICE_ID: ${this.SERVICE_ID}`);
248
- log(`Configuration Value: VERSION: ${this.VERSION}`);
249
-
250
- for (const { field, sensitive, envAliases, defaultValue } of this._fields) {
251
- let from = "defaults";
252
- let foundValue: string | undefined;
253
- let usedAlias = false;
254
-
255
- // 1. Primary environment variable name
256
- if (process.env[field] !== undefined) {
257
- foundValue = process.env[field];
258
- from = "environment";
259
- }
260
-
261
- // 2. Check aliases if primary env var was not set
262
- if (foundValue === undefined && envAliases.length > 0) {
263
- for (const alias of envAliases) {
264
- if (process.env[alias] !== undefined) {
265
- foundValue = process.env[alias];
266
- from = "environment";
267
- usedAlias = true;
268
- break;
269
- }
270
- }
271
- }
272
-
273
- // 3. Apply environment value (full name or alias) if found,
274
- // coercing strings to match the default value type
275
- if (foundValue !== undefined) {
276
- (this as any)[field] = coerceValue(foundValue, defaultValue);
277
- }
278
-
279
- // 4. Config file override (environment always wins, but if neither
280
- // environment nor alias matched, check config file)
281
- if (foundValue === undefined && content[field] !== undefined) {
282
- (this as any)[field] = content[field];
283
- from = "config";
284
- }
285
-
286
- if (sensitive) {
287
- log(
288
- `Configuration Value: ${field}: ******************** (from ${from})`,
289
- );
290
- } else {
291
- log(
292
- `Configuration Value: ${field}: ${(this as any)[field]} (from ${from}${usedAlias ? ` via alias` : ""})`,
293
- );
294
- }
295
- }
296
- }
297
- }
@@ -1,23 +0,0 @@
1
- import { convertToPostgresPlaceholders } from "./DbUtils";
2
-
3
- describe("convertToPostgresPlaceholders", () => {
4
- it("should convert single ?", () => {
5
- expect(convertToPostgresPlaceholders("SELECT * FROM t WHERE id = ?")).toBe(
6
- "SELECT * FROM t WHERE id = $1",
7
- );
8
- });
9
-
10
- it("should convert multiple ? to $1, $2, ...", () => {
11
- expect(
12
- convertToPostgresPlaceholders("INSERT INTO t (a,b,c) VALUES (?,?,?)"),
13
- ).toBe("INSERT INTO t (a,b,c) VALUES ($1,$2,$3)");
14
- });
15
-
16
- it("should return SQL unchanged when no ? present", () => {
17
- expect(convertToPostgresPlaceholders("SELECT 1")).toBe("SELECT 1");
18
- });
19
-
20
- it("should handle empty string", () => {
21
- expect(convertToPostgresPlaceholders("")).toBe("");
22
- });
23
- });
package/src/DbUtils.ts DELETED
@@ -1,116 +0,0 @@
1
- import { Span } from "@opentelemetry/sdk-trace-base";
2
- import { StandardTracer, StandardLogger } from "@devopsplaybook.io/otel-utils";
3
- import * as SqlDbUtils from "./SqlDbUtils";
4
- import * as PostgresDbUtils from "./PostgresDbUtils";
5
-
6
- /**
7
- * Configuration subset required by the unified DB facade.
8
- */
9
- export interface DbUtilsConfig
10
- extends SqlDbUtils.SqlDbConfig, PostgresDbUtils.PostgresDbConfig {
11
- DATABASE_TYPE: "sqlite" | "postgres";
12
- }
13
-
14
- let databaseType: "sqlite" | "postgres" = "sqlite";
15
-
16
- /**
17
- * Injects the OTel tracer and logger instances used by the DB layer.
18
- * Must be called once at startup, before {@link DbUtilsInit}.
19
- */
20
- export function DbUtilsSetOTel(
21
- tracer: StandardTracer,
22
- logger: StandardLogger,
23
- ): void {
24
- SqlDbUtils.SqlDbUtilsSetOTel(tracer, logger);
25
- PostgresDbUtils.PostgresDbUtilsSetOTel(tracer, logger);
26
- }
27
-
28
- /**
29
- * Initialise the database layer.
30
- *
31
- * Dispatches to the SQLite or Postgres backend depending on
32
- * `config.DATABASE_TYPE` and runs pending migration files from `sqlDir`.
33
- *
34
- * @param context Parent OTel span.
35
- * @param config Server configuration.
36
- * @param sqlDir Absolute path to the directory containing SQL migration files.
37
- */
38
- export async function DbUtilsInit(
39
- context: Span,
40
- config: DbUtilsConfig,
41
- sqlDir: string,
42
- ): Promise<void> {
43
- databaseType = config.DATABASE_TYPE;
44
- if (databaseType === "postgres") {
45
- await PostgresDbUtils.PostgresDbUtilsInit(context, config, sqlDir);
46
- } else {
47
- await SqlDbUtils.SqlDbUtilsInit(context, config, sqlDir);
48
- }
49
- }
50
-
51
- /**
52
- * Returns the native database handle.
53
- * - SQLite: `better-sqlite3` `Database` instance
54
- * - Postgres: `pg` `Pool` instance
55
- */
56
- export function DbUtilsGetDatabase(): any {
57
- if (databaseType === "postgres") {
58
- return PostgresDbUtils.PostgresDbUtilsGetPool();
59
- }
60
- return SqlDbUtils.SqlDbUtilsGetDatabase();
61
- }
62
-
63
- /** Convert SQLite `?` placeholders to PostgreSQL `$1, $2, ...` numbering. */
64
- export function convertToPostgresPlaceholders(sql: string): string {
65
- let paramIndex = 1;
66
- return sql.replace(/\?/g, () => `$${paramIndex++}`);
67
- }
68
-
69
- /**
70
- * Execute a write SQL statement with OTel tracing.
71
- * Automatically converts `?` placeholders to `$N` when using Postgres.
72
- *
73
- * @returns Number of rows changed.
74
- */
75
- export function DbUtilsExecSQL(
76
- context: Span,
77
- sql: string,
78
- params: unknown[] = [],
79
- ): number | Promise<number> {
80
- if (databaseType === "postgres") {
81
- return PostgresDbUtils.PostgresDbUtilsExecSQL(
82
- context,
83
- convertToPostgresPlaceholders(sql),
84
- params,
85
- );
86
- }
87
- return SqlDbUtils.SqlDbUtilsExecSQL(context, sql, params);
88
- }
89
-
90
- /**
91
- * Execute a read SQL query with OTel tracing.
92
- * Automatically converts `?` placeholders to `$N` when using Postgres.
93
- *
94
- * @returns Array of row objects.
95
- */
96
- export function DbUtilsQuerySQL(
97
- context: Span,
98
- sql: string,
99
- params: unknown[] = [],
100
- debug = false,
101
- ): any[] | Promise<any[]> {
102
- if (databaseType === "postgres") {
103
- return PostgresDbUtils.PostgresDbUtilsQuerySQL(
104
- context,
105
- convertToPostgresPlaceholders(sql),
106
- params,
107
- debug,
108
- );
109
- }
110
- return SqlDbUtils.SqlDbUtilsQuerySQL(context, sql, params, debug);
111
- }
112
-
113
- /** Returns the active database type (`"sqlite"` or `"postgres"`). */
114
- export function DbUtilsGetType(): "sqlite" | "postgres" {
115
- return databaseType;
116
- }
@@ -1,168 +0,0 @@
1
- import {
2
- DbUtilsNoTelemetryBatchInsert,
3
- DbUtilsNoTelemetryExecSQL,
4
- DbUtilsNoTelemetryQuerySQL,
5
- } from "./DbUtilsNoTelemetry";
6
-
7
-
8
- // Shared mock DB handle – defined BEFORE jest.mock factory so it's hoisted correctly.
9
- // We use jest.fn() at module scope; the mock factory captures the same reference.
10
- const mockPrepare = jest.fn(() => ({
11
- run: jest.fn().mockReturnValue({ changes: 0 }),
12
- all: jest.fn().mockReturnValue([]),
13
- }));
14
- const mockQuery = jest.fn();
15
- const mockDbHandle = { prepare: mockPrepare, query: mockQuery };
16
-
17
- let currentDbType: "sqlite" | "postgres" = "sqlite";
18
-
19
- jest.mock("./DbUtils", () => ({
20
- DbUtilsGetDatabase: jest.fn(() => mockDbHandle),
21
- DbUtilsGetType: jest.fn(() => currentDbType),
22
- convertToPostgresPlaceholders: jest.fn((sql: string) => {
23
- let idx = 1;
24
- return sql.replace(/\?/g, () => `$${idx++}`);
25
- }),
26
- }));
27
-
28
- jest.mock("@devopsplaybook.io/otel-utils", () => ({
29
- ModuleLogger: jest.fn(),
30
- StandardLogger: jest.fn(),
31
- }));
32
-
33
- import * as DbUtilsNoTelemetryModule from "./DbUtilsNoTelemetry";
34
-
35
- beforeAll(() => {
36
- const mockLogger = {
37
- error: jest.fn(),
38
- info: jest.fn(),
39
- warn: jest.fn(),
40
- } as any;
41
- DbUtilsNoTelemetryModule.DbUtilsNoTelemetrySetLogger({
42
- createModuleLogger: () => mockLogger,
43
- } as any);
44
- });
45
-
46
- beforeEach(() => {
47
- jest.clearAllMocks();
48
- currentDbType = "sqlite";
49
- });
50
-
51
- describe("DbUtilsNoTelemetryBatchInsert", () => {
52
- it("returns 0 for empty rows", () => {
53
- const result = DbUtilsNoTelemetryBatchInsert("INTO t (c)", 1, []);
54
- expect(result).toBe(0);
55
- });
56
-
57
- it("generates correct multi-row VALUES SQL (sqlite)", () => {
58
- currentDbType = "sqlite";
59
- mockPrepare.mockReturnValue({
60
- run: jest.fn().mockReturnValue({ changes: 2 }),
61
- all: jest.fn(),
62
- } as any);
63
-
64
- const rows = [
65
- ["a1", "b1"],
66
- ["a2", "b2"],
67
- ];
68
- const result = DbUtilsNoTelemetryBatchInsert("INTO t (c1,c2)", 2, rows);
69
- expect(result).toBe(2);
70
- });
71
- });
72
-
73
- describe("DbUtilsNoTelemetryExecSQL (sqlite)", () => {
74
- beforeEach(() => {
75
- currentDbType = "sqlite";
76
- });
77
-
78
- it("resolves with changes count on success", () => {
79
- mockPrepare.mockReturnValue({
80
- run: jest.fn().mockReturnValue({ changes: 3 }),
81
- all: jest.fn(),
82
- } as any);
83
-
84
- const result = DbUtilsNoTelemetryExecSQL("INSERT INTO t (c) VALUES (?)", [
85
- "x",
86
- ]);
87
- expect(result).toBe(3);
88
- });
89
- });
90
-
91
- describe("DbUtilsNoTelemetryExecSQL (postgres)", () => {
92
- beforeEach(() => {
93
- currentDbType = "postgres";
94
- });
95
-
96
- it("resolves with rowCount on success", async () => {
97
- mockQuery.mockImplementation(
98
- (_sql: string, _params: unknown[], cb: Function) => {
99
- cb(null, { rowCount: 5 });
100
- },
101
- );
102
-
103
- const result = await DbUtilsNoTelemetryExecSQL(
104
- "INSERT INTO t (c) VALUES (?)",
105
- ["x"],
106
- );
107
- expect(result).toBe(5);
108
- });
109
-
110
- it("rejects on error", async () => {
111
- mockQuery.mockImplementation(
112
- (_sql: string, _params: unknown[], cb: Function) => {
113
- cb(new Error("deadlock detected"));
114
- },
115
- );
116
-
117
- await expect(
118
- DbUtilsNoTelemetryExecSQL("INSERT INTO t (c) VALUES (?)", ["x"]),
119
- ).rejects.toThrow("deadlock detected");
120
- });
121
- });
122
-
123
- describe("DbUtilsNoTelemetryQuerySQL (sqlite)", () => {
124
- beforeEach(() => {
125
- currentDbType = "sqlite";
126
- });
127
-
128
- it("returns rows on success", () => {
129
- const expectedRows = [{ id: 1 }, { id: 2 }];
130
- mockPrepare.mockReturnValue({
131
- run: jest.fn(),
132
- all: jest.fn().mockReturnValue(expectedRows),
133
- } as any);
134
-
135
- const result = DbUtilsNoTelemetryQuerySQL("SELECT * FROM t");
136
- expect(result).toEqual(expectedRows);
137
- });
138
- });
139
-
140
- describe("DbUtilsNoTelemetryQuerySQL (postgres)", () => {
141
- beforeEach(() => {
142
- currentDbType = "postgres";
143
- });
144
-
145
- it("returns rows on success", async () => {
146
- const expectedRows = [{ id: 1 }, { id: 2 }];
147
- mockQuery.mockImplementation(
148
- (_sql: string, _params: unknown[], cb: Function) => {
149
- cb(null, { rows: expectedRows });
150
- },
151
- );
152
-
153
- const result = await DbUtilsNoTelemetryQuerySQL("SELECT * FROM t");
154
- expect(result).toEqual(expectedRows);
155
- });
156
-
157
- it("rejects on error", async () => {
158
- mockQuery.mockImplementation(
159
- (_sql: string, _params: unknown[], cb: Function) => {
160
- cb(new Error("connection lost"));
161
- },
162
- );
163
-
164
- await expect(DbUtilsNoTelemetryQuerySQL("SELECT * FROM t")).rejects.toThrow(
165
- "connection lost",
166
- );
167
- });
168
- });