@devopsplaybook.io/common-utils 1.0.0-beta.5.18ca754

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 (51) hide show
  1. package/.github/workflows/main-build.yml +17 -0
  2. package/.github/workflows/npm-upgrade.yml +16 -0
  3. package/.github/workflows/pr-check.yml +26 -0
  4. package/.github/workflows/reusable-merge-build.yml +141 -0
  5. package/.github/workflows/reusable-npm-merge.yml +135 -0
  6. package/.github/workflows/reusable-npm-pr.yml +153 -0
  7. package/.github/workflows/reusable-npm-upgrade.yml +92 -0
  8. package/.github/workflows/reusable-pr-verify.yml +135 -0
  9. package/AGENTS.md +84 -0
  10. package/README.md +413 -0
  11. package/dist/index.d.ts +8 -0
  12. package/dist/index.js +24 -0
  13. package/dist/src/ConfigBase.d.ts +104 -0
  14. package/dist/src/ConfigBase.js +201 -0
  15. package/dist/src/DbUtils.d.ts +50 -0
  16. package/dist/src/DbUtils.js +117 -0
  17. package/dist/src/DbUtilsNoTelemetry.d.ts +27 -0
  18. package/dist/src/DbUtilsNoTelemetry.js +89 -0
  19. package/dist/src/OTelContext.d.ts +35 -0
  20. package/dist/src/OTelContext.js +42 -0
  21. package/dist/src/PostgresDbUtils.d.ts +52 -0
  22. package/dist/src/PostgresDbUtils.js +217 -0
  23. package/dist/src/SqlDbUtils.d.ts +40 -0
  24. package/dist/src/SqlDbUtils.js +156 -0
  25. package/dist/src/SystemCommand.d.ts +9 -0
  26. package/dist/src/SystemCommand.js +56 -0
  27. package/dist/src/Timeout.d.ts +6 -0
  28. package/dist/src/Timeout.js +15 -0
  29. package/eslint.config.mjs +10 -0
  30. package/index.ts +8 -0
  31. package/jest.config.js +13 -0
  32. package/package.json +50 -0
  33. package/prettierrc.json +5 -0
  34. package/src/ConfigBase.spec.ts +108 -0
  35. package/src/ConfigBase.ts +213 -0
  36. package/src/DbUtils.spec.ts +23 -0
  37. package/src/DbUtils.ts +118 -0
  38. package/src/DbUtilsNoTelemetry.spec.ts +174 -0
  39. package/src/DbUtilsNoTelemetry.ts +121 -0
  40. package/src/OTelContext.spec.ts +58 -0
  41. package/src/OTelContext.ts +65 -0
  42. package/src/PostgresDbUtils.spec.ts +155 -0
  43. package/src/PostgresDbUtils.ts +233 -0
  44. package/src/SqlDbUtils.spec.ts +111 -0
  45. package/src/SqlDbUtils.ts +153 -0
  46. package/src/SystemCommand.spec.ts +18 -0
  47. package/src/SystemCommand.ts +23 -0
  48. package/src/Timeout.spec.ts +18 -0
  49. package/src/Timeout.ts +12 -0
  50. package/tsconfig.json +14 -0
  51. package/tsconfig.spec.json +7 -0
@@ -0,0 +1,104 @@
1
+ import { ConfigOTelInterface } from "@devopsplaybook.io/otel-utils";
2
+ /**
3
+ * Configuration field descriptor used by {@link ConfigBase.addConfigField}.
4
+ */
5
+ export interface ConfigFieldDef {
6
+ /** Property name on the config instance. */
7
+ field: string;
8
+ /** When `true` the value is masked in log output. */
9
+ sensitive?: boolean;
10
+ }
11
+ /**
12
+ * Database-specific configuration fields shared by every project that
13
+ * supports both SQLite and PostgreSQL backends.
14
+ */
15
+ export interface ConfigDatabaseInterface {
16
+ DATABASE_TYPE: "sqlite" | "postgres";
17
+ DATABASE_POSTGRES_HOST: string;
18
+ DATABASE_POSTGRES_PORT: number;
19
+ DATABASE_POSTGRES_USER: string;
20
+ DATABASE_POSTGRES_PASSWORD: string;
21
+ DATABASE_POSTGRES_DATABASE: string;
22
+ }
23
+ /**
24
+ * Common server configuration fields shared across projects.
25
+ */
26
+ export interface ConfigCommonInterface extends ConfigOTelInterface, ConfigDatabaseInterface {
27
+ CONFIG_FILE: string;
28
+ API_PORT: number;
29
+ JWT_VALIDITY_DURATION: number;
30
+ CORS_POLICY_ORIGIN: string;
31
+ DATA_DIR: string;
32
+ JWT_KEY: string;
33
+ LOG_LEVEL: string;
34
+ }
35
+ /**
36
+ * Abstract base class for project configuration.
37
+ *
38
+ * Implements the three-layer override strategy used across all
39
+ * devopsplaybook.io server projects:
40
+ * 1. **Environment variable** (highest priority)
41
+ * 2. **config.json** file value
42
+ * 3. **Default** declared on the class property
43
+ *
44
+ * Subclasses add project-specific fields and call {@link addConfigField}
45
+ * inside their constructor so that {@link reload} picks them up.
46
+ *
47
+ * @example
48
+ * ```ts
49
+ * class MyConfig extends ConfigBase {
50
+ * public MY_SETTING = "default";
51
+ * constructor() {
52
+ * super("my-service");
53
+ * this.addConfigField({ field: "MY_SETTING" });
54
+ * }
55
+ * }
56
+ * ```
57
+ */
58
+ export declare abstract class ConfigBase implements ConfigCommonInterface {
59
+ SERVICE_ID: string;
60
+ VERSION: string;
61
+ OPENTELEMETRY_COLLECTOR_HTTP_TRACES: string;
62
+ OPENTELEMETRY_COLLECTOR_HTTP_METRICS: string;
63
+ OPENTELEMETRY_COLLECTOR_HTTP_LOGS: string;
64
+ OPENTELEMETRY_COLLECTOR_AWS: boolean;
65
+ OPENTELEMETRY_COLLECTOR_EXPORT_LOGS_INTERVAL_SECONDS: number;
66
+ OPENTELEMETRY_COLLECTOR_EXPORT_METRICS_INTERVAL_SECONDS: number;
67
+ OPENTELEMETRY_COLLECT_AUTHORIZATION_HEADER: string;
68
+ CONFIG_FILE: string;
69
+ API_PORT: number;
70
+ JWT_VALIDITY_DURATION: number;
71
+ CORS_POLICY_ORIGIN: string;
72
+ DATA_DIR: string;
73
+ JWT_KEY: string;
74
+ LOG_LEVEL: string;
75
+ DATABASE_TYPE: "sqlite" | "postgres";
76
+ DATABASE_POSTGRES_HOST: string;
77
+ DATABASE_POSTGRES_PORT: number;
78
+ DATABASE_POSTGRES_USER: string;
79
+ DATABASE_POSTGRES_PASSWORD: string;
80
+ DATABASE_POSTGRES_DATABASE: string;
81
+ /**
82
+ * Fields registered by subclasses (or the base) that {@link reload}
83
+ * should process. Common / DB / OTel fields are pre-registered.
84
+ */
85
+ private _fields;
86
+ /**
87
+ * @param serviceId Unique service identifier (e.g. `"cryptotrader-server"`).
88
+ * @param configFile Optional path to the JSON config file. Defaults to `"config.json"`.
89
+ */
90
+ constructor(serviceId: string, configFile?: string);
91
+ /**
92
+ * Register a configuration field so that {@link reload} processes it.
93
+ * Call this in your subclass constructor for every project-specific field.
94
+ */
95
+ addConfigField(def: ConfigFieldDef): void;
96
+ /**
97
+ * Load (or reload) configuration from the JSON file and environment variables.
98
+ * Environment variables always take precedence over file values.
99
+ *
100
+ * @param logger Optional log callback `(message: string) => void`.
101
+ * When omitted nothing is logged (useful in tests).
102
+ */
103
+ reload(logger?: (message: string) => void): Promise<void>;
104
+ }
@@ -0,0 +1,201 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.ConfigBase = void 0;
40
+ const fse = __importStar(require("fs-extra"));
41
+ const uuid_1 = require("uuid");
42
+ const path_1 = __importDefault(require("path"));
43
+ /**
44
+ * Abstract base class for project configuration.
45
+ *
46
+ * Implements the three-layer override strategy used across all
47
+ * devopsplaybook.io server projects:
48
+ * 1. **Environment variable** (highest priority)
49
+ * 2. **config.json** file value
50
+ * 3. **Default** declared on the class property
51
+ *
52
+ * Subclasses add project-specific fields and call {@link addConfigField}
53
+ * inside their constructor so that {@link reload} picks them up.
54
+ *
55
+ * @example
56
+ * ```ts
57
+ * class MyConfig extends ConfigBase {
58
+ * public MY_SETTING = "default";
59
+ * constructor() {
60
+ * super("my-service");
61
+ * this.addConfigField({ field: "MY_SETTING" });
62
+ * }
63
+ * }
64
+ * ```
65
+ */
66
+ class ConfigBase {
67
+ /**
68
+ * @param serviceId Unique service identifier (e.g. `"cryptotrader-server"`).
69
+ * @param configFile Optional path to the JSON config file. Defaults to `"config.json"`.
70
+ */
71
+ constructor(serviceId, configFile) {
72
+ this.VERSION = "1";
73
+ this.OPENTELEMETRY_COLLECTOR_HTTP_TRACES = "";
74
+ this.OPENTELEMETRY_COLLECTOR_HTTP_METRICS = "";
75
+ this.OPENTELEMETRY_COLLECTOR_HTTP_LOGS = "";
76
+ this.OPENTELEMETRY_COLLECTOR_AWS = false;
77
+ this.OPENTELEMETRY_COLLECTOR_EXPORT_LOGS_INTERVAL_SECONDS = 60;
78
+ this.OPENTELEMETRY_COLLECTOR_EXPORT_METRICS_INTERVAL_SECONDS = 60;
79
+ this.OPENTELEMETRY_COLLECT_AUTHORIZATION_HEADER = "";
80
+ this.API_PORT = 8080;
81
+ this.JWT_VALIDITY_DURATION = 3 * 31 * 24 * 3600;
82
+ this.CORS_POLICY_ORIGIN = "";
83
+ this.DATA_DIR = process.env.DATA_DIR || "/data";
84
+ this.JWT_KEY = (0, uuid_1.v4)();
85
+ this.LOG_LEVEL = "info";
86
+ // -- Database fields --
87
+ this.DATABASE_TYPE = "sqlite";
88
+ this.DATABASE_POSTGRES_HOST = "";
89
+ this.DATABASE_POSTGRES_PORT = 5432;
90
+ this.DATABASE_POSTGRES_USER = "";
91
+ this.DATABASE_POSTGRES_PASSWORD = "";
92
+ this.DATABASE_POSTGRES_DATABASE = "";
93
+ /**
94
+ * Fields registered by subclasses (or the base) that {@link reload}
95
+ * should process. Common / DB / OTel fields are pre-registered.
96
+ */
97
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
98
+ this._fields = [];
99
+ this.SERVICE_ID = serviceId;
100
+ this.CONFIG_FILE = configFile || process.env.CONFIG_FILE || "config.json";
101
+ // Auto-detect version from nearest package.json
102
+ try {
103
+ const pkg = fse.readJsonSync(path_1.default.resolve(__dirname, "../package.json"));
104
+ if (pkg && pkg.version) {
105
+ this.VERSION = pkg.version;
106
+ }
107
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
108
+ }
109
+ catch (_e) {
110
+ // fallback to "1"
111
+ }
112
+ // Pre-register base + DB + OTel fields so reload() handles them
113
+ const baseFields = [
114
+ { field: "JWT_VALIDITY_DURATION" },
115
+ { field: "CORS_POLICY_ORIGIN" },
116
+ { field: "DATA_DIR" },
117
+ { field: "JWT_KEY", sensitive: true },
118
+ { field: "LOG_LEVEL" },
119
+ { field: "DATABASE_TYPE" },
120
+ { field: "DATABASE_POSTGRES_HOST" },
121
+ { field: "DATABASE_POSTGRES_PORT" },
122
+ { field: "DATABASE_POSTGRES_USER" },
123
+ { field: "DATABASE_POSTGRES_PASSWORD", sensitive: true },
124
+ { field: "DATABASE_POSTGRES_DATABASE" },
125
+ { field: "OPENTELEMETRY_COLLECTOR_HTTP_TRACES" },
126
+ { field: "OPENTELEMETRY_COLLECTOR_HTTP_METRICS" },
127
+ { field: "OPENTELEMETRY_COLLECTOR_HTTP_LOGS" },
128
+ { field: "OPENTELEMETRY_COLLECTOR_AWS" },
129
+ {
130
+ field: "OPENTELEMETRY_COLLECTOR_EXPORT_LOGS_INTERVAL_SECONDS",
131
+ },
132
+ {
133
+ field: "OPENTELEMETRY_COLLECTOR_EXPORT_METRICS_INTERVAL_SECONDS",
134
+ },
135
+ {
136
+ field: "OPENTELEMETRY_COLLECT_AUTHORIZATION_HEADER",
137
+ sensitive: true,
138
+ },
139
+ ];
140
+ for (const f of baseFields) {
141
+ this.addConfigField(f);
142
+ }
143
+ }
144
+ /**
145
+ * Register a configuration field so that {@link reload} processes it.
146
+ * Call this in your subclass constructor for every project-specific field.
147
+ */
148
+ addConfigField(def) {
149
+ var _a;
150
+ this._fields.push({
151
+ field: def.field,
152
+ sensitive: (_a = def.sensitive) !== null && _a !== void 0 ? _a : false,
153
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
154
+ defaultValue: this[def.field],
155
+ });
156
+ }
157
+ /**
158
+ * Load (or reload) configuration from the JSON file and environment variables.
159
+ * Environment variables always take precedence over file values.
160
+ *
161
+ * @param logger Optional log callback `(message: string) => void`.
162
+ * When omitted nothing is logged (useful in tests).
163
+ */
164
+ async reload(logger) {
165
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
166
+ const log = logger !== null && logger !== void 0 ? logger : (() => { });
167
+ let content = {};
168
+ try {
169
+ content = await fse.readJson(this.CONFIG_FILE);
170
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
171
+ }
172
+ catch (_e) {
173
+ // config file is optional – fall back to env + defaults
174
+ }
175
+ log(`Configuration Value: CONFIG_FILE: ${this.CONFIG_FILE}`);
176
+ log(`Configuration Value: SERVICE_ID: ${this.SERVICE_ID}`);
177
+ log(`Configuration Value: VERSION: ${this.VERSION}`);
178
+ for (const { field, sensitive } of this._fields) {
179
+ let from = "defaults";
180
+ if (process.env[field] !== undefined) {
181
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
182
+ this[field] = process.env[field];
183
+ from = "environment";
184
+ }
185
+ else if (content[field] !== undefined) {
186
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
187
+ this[field] = content[field];
188
+ from = "config";
189
+ }
190
+ if (sensitive) {
191
+ log(`Configuration Value: ${field}: ******************** (from ${from})`);
192
+ }
193
+ else {
194
+ log(
195
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
196
+ `Configuration Value: ${field}: ${this[field]} (from ${from})`);
197
+ }
198
+ }
199
+ }
200
+ }
201
+ exports.ConfigBase = ConfigBase;
@@ -0,0 +1,50 @@
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
+ * Configuration subset required by the unified DB facade.
7
+ */
8
+ export interface DbUtilsConfig extends SqlDbUtils.SqlDbConfig, PostgresDbUtils.PostgresDbConfig {
9
+ DATABASE_TYPE: "sqlite" | "postgres";
10
+ }
11
+ /**
12
+ * Injects the OTel tracer and logger instances used by the DB layer.
13
+ * Must be called once at startup, before {@link DbUtilsInit}.
14
+ */
15
+ export declare function DbUtilsSetOTel(tracer: StandardTracer, logger: StandardLogger): void;
16
+ /**
17
+ * Initialise the database layer.
18
+ *
19
+ * Dispatches to the SQLite or Postgres backend depending on
20
+ * `config.DATABASE_TYPE` and runs pending migration files from `sqlDir`.
21
+ *
22
+ * @param context Parent OTel span.
23
+ * @param config Server configuration.
24
+ * @param sqlDir Absolute path to the directory containing SQL migration files.
25
+ */
26
+ export declare function DbUtilsInit(context: Span, config: DbUtilsConfig, sqlDir: string): Promise<void>;
27
+ /**
28
+ * Returns the native database handle.
29
+ * - SQLite: `better-sqlite3` `Database` instance
30
+ * - Postgres: `pg` `Pool` instance
31
+ */
32
+ export declare function DbUtilsGetDatabase(): any;
33
+ /** Convert SQLite `?` placeholders to PostgreSQL `$1, $2, ...` numbering. */
34
+ export declare function convertToPostgresPlaceholders(sql: string): string;
35
+ /**
36
+ * Execute a write SQL statement with OTel tracing.
37
+ * Automatically converts `?` placeholders to `$N` when using Postgres.
38
+ *
39
+ * @returns Number of rows changed.
40
+ */
41
+ export declare function DbUtilsExecSQL(context: Span, sql: string, params?: unknown[]): number | Promise<number>;
42
+ /**
43
+ * Execute a read SQL query with OTel tracing.
44
+ * Automatically converts `?` placeholders to `$N` when using Postgres.
45
+ *
46
+ * @returns Array of row objects.
47
+ */
48
+ export declare function DbUtilsQuerySQL(context: Span, sql: string, params?: unknown[], debug?: boolean): any[] | Promise<any[]>;
49
+ /** Returns the active database type (`"sqlite"` or `"postgres"`). */
50
+ export declare function DbUtilsGetType(): "sqlite" | "postgres";
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.DbUtilsSetOTel = DbUtilsSetOTel;
37
+ exports.DbUtilsInit = DbUtilsInit;
38
+ exports.DbUtilsGetDatabase = DbUtilsGetDatabase;
39
+ exports.convertToPostgresPlaceholders = convertToPostgresPlaceholders;
40
+ exports.DbUtilsExecSQL = DbUtilsExecSQL;
41
+ exports.DbUtilsQuerySQL = DbUtilsQuerySQL;
42
+ exports.DbUtilsGetType = DbUtilsGetType;
43
+ const SqlDbUtils = __importStar(require("./SqlDbUtils"));
44
+ const PostgresDbUtils = __importStar(require("./PostgresDbUtils"));
45
+ let databaseType = "sqlite";
46
+ /**
47
+ * Injects the OTel tracer and logger instances used by the DB layer.
48
+ * Must be called once at startup, before {@link DbUtilsInit}.
49
+ */
50
+ function DbUtilsSetOTel(tracer, logger) {
51
+ SqlDbUtils.SqlDbUtilsSetOTel(tracer, logger);
52
+ PostgresDbUtils.PostgresDbUtilsSetOTel(tracer, logger);
53
+ }
54
+ /**
55
+ * Initialise the database layer.
56
+ *
57
+ * Dispatches to the SQLite or Postgres backend depending on
58
+ * `config.DATABASE_TYPE` and runs pending migration files from `sqlDir`.
59
+ *
60
+ * @param context Parent OTel span.
61
+ * @param config Server configuration.
62
+ * @param sqlDir Absolute path to the directory containing SQL migration files.
63
+ */
64
+ async function DbUtilsInit(context, config, sqlDir) {
65
+ databaseType = config.DATABASE_TYPE;
66
+ if (databaseType === "postgres") {
67
+ await PostgresDbUtils.PostgresDbUtilsInit(context, config, sqlDir);
68
+ }
69
+ else {
70
+ await SqlDbUtils.SqlDbUtilsInit(context, config, sqlDir);
71
+ }
72
+ }
73
+ /**
74
+ * Returns the native database handle.
75
+ * - SQLite: `better-sqlite3` `Database` instance
76
+ * - Postgres: `pg` `Pool` instance
77
+ */
78
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
79
+ function DbUtilsGetDatabase() {
80
+ if (databaseType === "postgres") {
81
+ return PostgresDbUtils.PostgresDbUtilsGetPool();
82
+ }
83
+ return SqlDbUtils.SqlDbUtilsGetDatabase();
84
+ }
85
+ /** Convert SQLite `?` placeholders to PostgreSQL `$1, $2, ...` numbering. */
86
+ function convertToPostgresPlaceholders(sql) {
87
+ let paramIndex = 1;
88
+ return sql.replace(/\?/g, () => `$${paramIndex++}`);
89
+ }
90
+ /**
91
+ * Execute a write SQL statement with OTel tracing.
92
+ * Automatically converts `?` placeholders to `$N` when using Postgres.
93
+ *
94
+ * @returns Number of rows changed.
95
+ */
96
+ function DbUtilsExecSQL(context, sql, params = []) {
97
+ if (databaseType === "postgres") {
98
+ return PostgresDbUtils.PostgresDbUtilsExecSQL(context, convertToPostgresPlaceholders(sql), params);
99
+ }
100
+ return SqlDbUtils.SqlDbUtilsExecSQL(context, sql, params);
101
+ }
102
+ /**
103
+ * Execute a read SQL query with OTel tracing.
104
+ * Automatically converts `?` placeholders to `$N` when using Postgres.
105
+ *
106
+ * @returns Array of row objects.
107
+ */
108
+ function DbUtilsQuerySQL(context, sql, params = [], debug = false) {
109
+ if (databaseType === "postgres") {
110
+ return PostgresDbUtils.PostgresDbUtilsQuerySQL(context, convertToPostgresPlaceholders(sql), params, debug);
111
+ }
112
+ return SqlDbUtils.SqlDbUtilsQuerySQL(context, sql, params, debug);
113
+ }
114
+ /** Returns the active database type (`"sqlite"` or `"postgres"`). */
115
+ function DbUtilsGetType() {
116
+ return databaseType;
117
+ }
@@ -0,0 +1,27 @@
1
+ import { StandardLogger } from "@devopsplaybook.io/otel-utils";
2
+ /**
3
+ * Injects the OTel logger instance used by no-telemetry DB operations.
4
+ * Must be called once at startup.
5
+ */
6
+ export declare function DbUtilsNoTelemetrySetLogger(loggerIn: StandardLogger): void;
7
+ /**
8
+ * Execute a multi-row INSERT with a flat parameter array.
9
+ * Builds: INSERT INTO <tableCols> VALUES (?,?...),(?,?...),...
10
+ *
11
+ * @returns Number of rows inserted.
12
+ */
13
+ export declare function DbUtilsNoTelemetryBatchInsert(tableCols: string, numCols: number, rows: any[][]): number | Promise<number>;
14
+ /**
15
+ * Execute a write SQL statement **without** creating an OTel span.
16
+ * Use this on high-throughput paths where span overhead matters.
17
+ *
18
+ * @returns Number of rows changed.
19
+ */
20
+ export declare function DbUtilsNoTelemetryExecSQL(sql: string, params?: unknown[]): number | Promise<number>;
21
+ /**
22
+ * Execute a read SQL query **without** creating an OTel span.
23
+ * Use this on high-throughput paths where span overhead matters.
24
+ *
25
+ * @returns Array of row objects.
26
+ */
27
+ export declare function DbUtilsNoTelemetryQuerySQL(sql: string, params?: unknown[], debug?: boolean): any[] | Promise<any[]>;
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DbUtilsNoTelemetrySetLogger = DbUtilsNoTelemetrySetLogger;
4
+ exports.DbUtilsNoTelemetryBatchInsert = DbUtilsNoTelemetryBatchInsert;
5
+ exports.DbUtilsNoTelemetryExecSQL = DbUtilsNoTelemetryExecSQL;
6
+ exports.DbUtilsNoTelemetryQuerySQL = DbUtilsNoTelemetryQuerySQL;
7
+ const DbUtils_1 = require("./DbUtils");
8
+ let logger;
9
+ /**
10
+ * Injects the OTel logger instance used by no-telemetry DB operations.
11
+ * Must be called once at startup.
12
+ */
13
+ function DbUtilsNoTelemetrySetLogger(loggerIn) {
14
+ logger = loggerIn.createModuleLogger("DbUtilsNoTelemetry");
15
+ }
16
+ /**
17
+ * Execute a multi-row INSERT with a flat parameter array.
18
+ * Builds: INSERT INTO <tableCols> VALUES (?,?...),(?,?...),...
19
+ *
20
+ * @returns Number of rows inserted.
21
+ */
22
+ function DbUtilsNoTelemetryBatchInsert(tableCols, numCols,
23
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
24
+ rows) {
25
+ if (rows.length === 0)
26
+ return 0;
27
+ const rowSQL = `(${Array.from({ length: numCols }, () => "?").join(",")})`;
28
+ const multiValues = Array.from({ length: rows.length }, () => rowSQL).join(",");
29
+ const sql = `INSERT ${tableCols} VALUES ${multiValues}`;
30
+ return DbUtilsNoTelemetryExecSQL(sql, rows.flat());
31
+ }
32
+ /**
33
+ * Execute a write SQL statement **without** creating an OTel span.
34
+ * Use this on high-throughput paths where span overhead matters.
35
+ *
36
+ * @returns Number of rows changed.
37
+ */
38
+ function DbUtilsNoTelemetryExecSQL(sql, params = []) {
39
+ const dbType = (0, DbUtils_1.DbUtilsGetType)();
40
+ if (dbType === "postgres") {
41
+ const pgSql = (0, DbUtils_1.convertToPostgresPlaceholders)(sql);
42
+ return new Promise((resolve, reject) => {
43
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
44
+ (0, DbUtils_1.DbUtilsGetDatabase)().query(pgSql, params, (error, result) => {
45
+ if (error) {
46
+ logger.error(`SQL INSERT ERROR: ${sql.substring(0, 200)}`, error);
47
+ reject(error);
48
+ }
49
+ else {
50
+ resolve(result.rowCount || 0);
51
+ }
52
+ });
53
+ });
54
+ }
55
+ // SQLite (better-sqlite3) – synchronous
56
+ const stmt = (0, DbUtils_1.DbUtilsGetDatabase)().prepare(sql);
57
+ const result = stmt.run(params);
58
+ return result.changes;
59
+ }
60
+ /**
61
+ * Execute a read SQL query **without** creating an OTel span.
62
+ * Use this on high-throughput paths where span overhead matters.
63
+ *
64
+ * @returns Array of row objects.
65
+ */
66
+ function DbUtilsNoTelemetryQuerySQL(sql, params = [], debug = false) {
67
+ if (debug) {
68
+ console.log(sql);
69
+ }
70
+ const dbType = (0, DbUtils_1.DbUtilsGetType)();
71
+ if (dbType === "postgres") {
72
+ const pgSql = (0, DbUtils_1.convertToPostgresPlaceholders)(sql);
73
+ return new Promise((resolve, reject) => {
74
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
75
+ (0, DbUtils_1.DbUtilsGetDatabase)().query(pgSql, params, (error, result) => {
76
+ if (error) {
77
+ logger.error(`SQL ERROR: ${sql}`, error);
78
+ reject(error);
79
+ }
80
+ else {
81
+ resolve(result.rows);
82
+ }
83
+ });
84
+ });
85
+ }
86
+ // SQLite (better-sqlite3) – synchronous
87
+ const stmt = (0, DbUtils_1.DbUtilsGetDatabase)().prepare(sql);
88
+ return stmt.all(params);
89
+ }
@@ -0,0 +1,35 @@
1
+ import { StandardLogger, StandardMeter, StandardTracer } from "@devopsplaybook.io/otel-utils";
2
+ import { Span } from "@opentelemetry/sdk-trace-base";
3
+ /**
4
+ * Result of {@link createOTelContext}.
5
+ * Holds module-level singletons for the OpenTelemetry tracer, meter and logger
6
+ * used throughout a server process.
7
+ */
8
+ export interface OTelContext {
9
+ OTelTracer: () => StandardTracer;
10
+ OTelSetTracer: (tracer: StandardTracer) => void;
11
+ OTelMeter: () => StandardMeter;
12
+ OTelSetMeter: (meter: StandardMeter) => void;
13
+ OTelLogger: () => StandardLogger;
14
+ /**
15
+ * Retrieves the span previously attached to a request object.
16
+ * Equivalent to `req.tracerSpanApi`.
17
+ */
18
+ OTelRequestSpan: (req: any) => Span | undefined;
19
+ }
20
+ /**
21
+ * Creates an isolated OTel context (tracer / meter / logger singletons).
22
+ *
23
+ * Each server process should call this once at startup and pass the returned
24
+ * object to modules that need telemetry access. This avoids polluting the
25
+ * global module scope and makes unit-testing straightforward.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * const otel = createOTelContext();
30
+ * otel.OTelSetTracer(new StandardTracer(config));
31
+ * otel.OTelSetMeter(new StandardMeter(config));
32
+ * otel.OTelLogger().initOTel(config);
33
+ * ```
34
+ */
35
+ export declare function createOTelContext(): OTelContext;
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createOTelContext = createOTelContext;
4
+ const otel_utils_1 = require("@devopsplaybook.io/otel-utils");
5
+ /**
6
+ * Creates an isolated OTel context (tracer / meter / logger singletons).
7
+ *
8
+ * Each server process should call this once at startup and pass the returned
9
+ * object to modules that need telemetry access. This avoids polluting the
10
+ * global module scope and makes unit-testing straightforward.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * const otel = createOTelContext();
15
+ * otel.OTelSetTracer(new StandardTracer(config));
16
+ * otel.OTelSetMeter(new StandardMeter(config));
17
+ * otel.OTelLogger().initOTel(config);
18
+ * ```
19
+ */
20
+ function createOTelContext() {
21
+ let tracer;
22
+ let meter;
23
+ let logger;
24
+ return {
25
+ OTelTracer: () => tracer,
26
+ OTelSetTracer: (t) => {
27
+ tracer = t;
28
+ },
29
+ OTelMeter: () => meter,
30
+ OTelSetMeter: (m) => {
31
+ meter = m;
32
+ },
33
+ OTelLogger: () => {
34
+ if (!logger) {
35
+ logger = new otel_utils_1.StandardLogger();
36
+ }
37
+ return logger;
38
+ },
39
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
40
+ OTelRequestSpan: (req) => req === null || req === void 0 ? void 0 : req.tracerSpanApi,
41
+ };
42
+ }