@devopsplaybook.io/common-utils 1.10.0-beta.19.2cffff2 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/main-build.yml +1 -0
- package/.github/workflows/pr-check.yml +1 -0
- package/.github/workflows/reusable-npm-merge.yml +1 -1
- package/.github/workflows/reusable-npm-pr.yml +1 -1
- package/AGENTS.md +11 -6
- package/README.md +4 -5
- package/dist/src/ConfigBase.js +5 -16
- package/dist/src/DbUtils.js +0 -1
- package/dist/src/DbUtilsNoTelemetry.js +1 -5
- package/dist/src/LLM.js +0 -2
- package/dist/src/OTelContext.js +0 -1
- package/dist/src/PostgresDbUtils.js +5 -20
- package/dist/src/users/Auth.js +2 -15
- package/dist/src/users/User.js +0 -3
- package/dist/src/users/UserApiToken.js +0 -3
- package/dist/src/users/UsersApiTokensData.js +0 -1
- package/dist/src/users/UsersData.js +0 -1
- package/dist/src/users/UsersRoutes.js +0 -1
- package/jest.config.js +5 -2
- package/package.json +13 -15
- package/src/ConfigBase.spec.ts +0 -33
- package/src/ConfigBase.ts +4 -15
- package/src/DbUtils.ts +0 -2
- package/src/DbUtilsNoTelemetry.spec.ts +0 -6
- package/src/DbUtilsNoTelemetry.ts +0 -4
- package/src/LLM.ts +0 -2
- package/src/OTelContext.ts +0 -2
- package/src/PostgresDbUtils.spec.ts +0 -2
- package/src/PostgresDbUtils.ts +0 -13
- package/src/SqlDbUtils.spec.ts +0 -3
- package/src/SqlDbUtils.ts +0 -1
- package/src/users/Auth.ts +0 -9
- package/src/users/User.ts +0 -3
- package/src/users/UserApiToken.ts +0 -3
- package/src/users/UsersApiTokensData.ts +0 -1
- package/src/users/UsersData.ts +0 -1
- package/src/users/UsersRoutes.ts +0 -1
- package/tsconfig.spec.json +2 -1
- package/eslint.config.mjs +0 -11
|
@@ -11,6 +11,7 @@ jobs:
|
|
|
11
11
|
uses: devopsplaybook-io/common-utils/.github/workflows/reusable-npm-merge.yml@main
|
|
12
12
|
with:
|
|
13
13
|
npm_package_name: "@devopsplaybook.io/common-utils"
|
|
14
|
+
node_version: "22"
|
|
14
15
|
secrets:
|
|
15
16
|
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
16
17
|
QUALITY_DASHBOARD_URL: ${{ secrets.QUALITY_DASHBOARD_URL }}
|
|
@@ -20,6 +20,7 @@ jobs:
|
|
|
20
20
|
uses: devopsplaybook-io/common-utils/.github/workflows/reusable-npm-pr.yml@main
|
|
21
21
|
with:
|
|
22
22
|
npm_package_name: "@devopsplaybook.io/common-utils"
|
|
23
|
+
node_version: "22"
|
|
23
24
|
secrets:
|
|
24
25
|
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
25
26
|
QUALITY_DASHBOARD_URL: ${{ secrets.QUALITY_DASHBOARD_URL }}
|
package/AGENTS.md
CHANGED
|
@@ -36,19 +36,24 @@ src/
|
|
|
36
36
|
.github/workflows/
|
|
37
37
|
main-build.yml # Caller: push to main -> reusable-npm-merge
|
|
38
38
|
pr-check.yml # Caller: PR to main -> reusable-npm-pr
|
|
39
|
-
npm-upgrade.yml # Caller: weekly schedule -> reusable-npm-upgrade
|
|
40
39
|
reusable-npm-merge.yml # Lint + test + build + publish release to npm
|
|
41
40
|
reusable-npm-pr.yml # Lint + test + build + publish beta tag + comment PR
|
|
42
|
-
reusable-npm-upgrade.yml # npm-check-updates + auto PR
|
|
41
|
+
reusable-npm-upgrade.yml # npm-check-updates + auto PR (manual, workflow_call)
|
|
43
42
|
reusable-pr-verify.yml # Matrix Node.js + multi-platform Docker build -> beta-pr-<PR> and beta
|
|
44
43
|
reusable-merge-build.yml # Promotes the PR image to the version tags on merge (no build/lint/test)
|
|
45
44
|
```
|
|
46
45
|
|
|
46
|
+
## Weekly Dependency Updates
|
|
47
|
+
|
|
48
|
+
The scheduled `npm-upgrade.yml` caller was retired: it created dependency upgrade pull requests every week that no one merged. `reusable-npm-upgrade.yml` is kept for manual, on-demand upgrades (`workflow_call`; adopting repositories can trigger it through their own `workflow_dispatch` caller).
|
|
49
|
+
|
|
50
|
+
The weekly update of the shared npm libraries is agent-driven instead: a Kubernetes CronJob (`planner-shared-libraries-update` in the `DidierHoarau/didier-home` GitOps repository) creates a Planner task in the `Projects/xMaintenance` project every Friday, and the planner-llm-agent executes the `update-shared-libraries` skill from `DidierHoarau/planner-llm-agent-config`. The skill updates `otel-utils` first, then `otel-utils-fastify` and `common-utils` once the freshly published `otel-utils` version is visible on npm, then the consumer repositories discovered with GitHub code search. It applies minor/patch updates only (majors are reported, never applied), skips repositories already current against the npm registry, and delivers one squash-merged pull request per repository after green checks. Do not reintroduce a scheduled upgrade workflow in this repository: it would compete with the agent-driven update.
|
|
51
|
+
|
|
47
52
|
## Key Conventions
|
|
48
53
|
|
|
49
54
|
- **TypeScript**: Target ES2019, CommonJS output, strict mode enabled, declarations generated.
|
|
50
|
-
- **
|
|
51
|
-
- **Tests**: Jest with `
|
|
55
|
+
- **Linting**: Uses `oxlint` with its default **recommended** preset. Minimize disable comments (`eslint-disable`-style comments are honored by oxlint); use them only when the rule cannot be satisfied (e.g. `no-explicit-any` is not enabled by the recommended preset, so `any` needs no directive).
|
|
56
|
+
- **Tests**: Jest with `@swc/jest` transform (`jsc.target` matches tsconfig ES2019) and `v8` coverage provider. Spec files live next to source (`*.spec.ts`). Run with `npm run test`. The `tsconfig.spec.json` includes jest types; `npm run build` also type-checks specs (`tsc -p tsconfig.spec.json --noEmit`).
|
|
52
57
|
- **No default exports**: All modules use named exports only.
|
|
53
58
|
- **OTel dependency injection**: Every DB module exposes a `*SetOTel(tracer, logger)` function that must be called before `*Init()`. OTel instances are stored as module-level singletons.
|
|
54
59
|
- **Auth modules**: `AuthSetOTel(tracer)` and `UsersDataSetOTel(tracer)` must be called before `AuthInit`. Application scopes are registered through `AuthInit(context, config, allScopes)`; `UsersRoutes` relies on `req.tracerSpanApi` set by the `otel-utils-fastify` hooks.
|
|
@@ -63,7 +68,7 @@ src/
|
|
|
63
68
|
```bash
|
|
64
69
|
npm install
|
|
65
70
|
npm run build # tsc -> dist/
|
|
66
|
-
npm run lint #
|
|
71
|
+
npm run lint # oxlint src (must pass with 0 errors)
|
|
67
72
|
npm run test # jest --coverage (all tests must pass)
|
|
68
73
|
```
|
|
69
74
|
|
|
@@ -86,7 +91,7 @@ All three commands must pass before committing. The CI pipeline (`reusable-npm-m
|
|
|
86
91
|
|
|
87
92
|
- **uuid ESM**: `uuid` v14+ ships ESM. In any test that transitively imports `uuid`, add `jest.mock("uuid", () => ({ v4: () => "mock-uuid-1234" }))` to avoid `SyntaxError: Unexpected token 'export'`.
|
|
88
93
|
- **better-sqlite3 is synchronous**: `SqlDbUtils` functions return values directly (not Promises). `PostgresDbUtils` functions return Promises. The `DbUtils` facade returns `number | Promise<number>` depending on the active backend.
|
|
89
|
-
- **
|
|
94
|
+
- **Disable-comment placement**: `eslint-disable-next-line` applies to the **immediately following line only** (oxlint honors these comments too). When disabling a rule inside a function call argument, place the comment directly before the offending expression, not before the function call.
|
|
90
95
|
- **pg callback typing**: Always explicitly type pg callback parameters: `(error: Error | null, result: { rowCount: number | null })`. TypeScript cannot infer these from the overloaded `pool.query` signature.
|
|
91
96
|
|
|
92
97
|
## Adopting in Other Projects
|
package/README.md
CHANGED
|
@@ -498,7 +498,7 @@ The `.github/workflows/` directory contains **reusable workflows** that other re
|
|
|
498
498
|
|
|
499
499
|
| Input | Required | Default | Description |
|
|
500
500
|
| ------------------ | -------- | ------- | --------------------------------------------------------- |
|
|
501
|
-
| `node_version` | No | `"
|
|
501
|
+
| `node_version` | No | `"22"` | Node.js version |
|
|
502
502
|
| `npm_package_name` | Yes | -- | npm package name (e.g. `@devopsplaybook.io/common-utils`) |
|
|
503
503
|
|
|
504
504
|
| Secret | Required | Description |
|
|
@@ -607,13 +607,12 @@ jobs:
|
|
|
607
607
|
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
608
608
|
```
|
|
609
609
|
|
|
610
|
-
And `.github/workflows/npm-upgrade.yml` for
|
|
610
|
+
And `.github/workflows/npm-upgrade.yml` for manual dependency upgrades (the weekly update of the shared devopsplaybook.io libraries is agent-driven — see the AGENTS.md of this repository — so no schedule trigger is used):
|
|
611
611
|
|
|
612
612
|
```yaml
|
|
613
613
|
name: NPM Upgrade
|
|
614
614
|
on:
|
|
615
|
-
|
|
616
|
-
- cron: "0 6 * * 1" # Monday 6am UTC
|
|
615
|
+
workflow_dispatch:
|
|
617
616
|
jobs:
|
|
618
617
|
npm-upgrade:
|
|
619
618
|
uses: devopsplaybook-io/common-utils/.github/workflows/reusable-npm-upgrade.yml@main
|
|
@@ -631,7 +630,7 @@ jobs:
|
|
|
631
630
|
```bash
|
|
632
631
|
npm install
|
|
633
632
|
npm run build # TypeScript compilation -> dist/
|
|
634
|
-
npm run lint #
|
|
633
|
+
npm run lint # oxlint (recommended preset)
|
|
635
634
|
npm run test # Jest with coverage
|
|
636
635
|
```
|
|
637
636
|
|
package/dist/src/ConfigBase.js
CHANGED
|
@@ -46,7 +46,6 @@ const path_1 = __importDefault(require("path"));
|
|
|
46
46
|
* array → JSON.parse, etc.). When the default is already a string or
|
|
47
47
|
* there is no default, the original string is returned as-is.
|
|
48
48
|
*/
|
|
49
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
50
49
|
function coerceValue(value, defaultValue) {
|
|
51
50
|
if (defaultValue === undefined || defaultValue === null) {
|
|
52
51
|
return value;
|
|
@@ -139,17 +138,13 @@ class ConfigBase {
|
|
|
139
138
|
this._fields = [];
|
|
140
139
|
this.SERVICE_ID = serviceId;
|
|
141
140
|
this.CONFIG_FILE = configFile || process.env.CONFIG_FILE || "config.json";
|
|
142
|
-
// Auto-detect
|
|
143
|
-
// Services run with their project root as the current working
|
|
144
|
-
// directory, so <cwd>/package.json is the host app's manifest. The
|
|
145
|
-
// installed library's own package.json is deliberately not used: it
|
|
146
|
-
// would report the library version, not the service version.
|
|
141
|
+
// Auto-detect version from nearest package.json
|
|
147
142
|
try {
|
|
148
|
-
const pkg = fse.readJsonSync(path_1.default.resolve(
|
|
143
|
+
const pkg = fse.readJsonSync(path_1.default.resolve(__dirname, "../package.json"));
|
|
149
144
|
if (pkg && pkg.version) {
|
|
150
145
|
this.VERSION = pkg.version;
|
|
151
146
|
}
|
|
152
|
-
// eslint-disable-next-line
|
|
147
|
+
// eslint-disable-next-line no-unused-vars -- catch binding kept so dist/ stays byte-identical to the TS6 build
|
|
153
148
|
}
|
|
154
149
|
catch (_e) {
|
|
155
150
|
// fallback to "1"
|
|
@@ -200,7 +195,6 @@ class ConfigBase {
|
|
|
200
195
|
field: def.field,
|
|
201
196
|
sensitive: (_a = def.sensitive) !== null && _a !== void 0 ? _a : false,
|
|
202
197
|
envAliases: (_b = def.envAliases) !== null && _b !== void 0 ? _b : [],
|
|
203
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
204
198
|
defaultValue: this[def.field],
|
|
205
199
|
});
|
|
206
200
|
}
|
|
@@ -212,12 +206,11 @@ class ConfigBase {
|
|
|
212
206
|
* When omitted nothing is logged (useful in tests).
|
|
213
207
|
*/
|
|
214
208
|
async reload(logger) {
|
|
215
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
216
209
|
const log = logger !== null && logger !== void 0 ? logger : (() => { });
|
|
217
210
|
let content = {};
|
|
218
211
|
try {
|
|
219
212
|
content = await fse.readJson(this.CONFIG_FILE);
|
|
220
|
-
// eslint-disable-next-line
|
|
213
|
+
// eslint-disable-next-line no-unused-vars -- catch binding kept so dist/ stays byte-identical to the TS6 build
|
|
221
214
|
}
|
|
222
215
|
catch (_e) {
|
|
223
216
|
// config file is optional – fall back to env + defaults
|
|
@@ -248,13 +241,11 @@ class ConfigBase {
|
|
|
248
241
|
// 3. Apply environment value (full name or alias) if found,
|
|
249
242
|
// coercing strings to match the default value type
|
|
250
243
|
if (foundValue !== undefined) {
|
|
251
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
252
244
|
this[field] = coerceValue(foundValue, defaultValue);
|
|
253
245
|
}
|
|
254
246
|
// 4. Config file override (environment always wins, but if neither
|
|
255
247
|
// environment nor alias matched, check config file)
|
|
256
248
|
if (foundValue === undefined && content[field] !== undefined) {
|
|
257
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
258
249
|
this[field] = content[field];
|
|
259
250
|
from = "config";
|
|
260
251
|
}
|
|
@@ -262,9 +253,7 @@ class ConfigBase {
|
|
|
262
253
|
log(`Configuration Value: ${field}: ******************** (from ${from})`);
|
|
263
254
|
}
|
|
264
255
|
else {
|
|
265
|
-
log(
|
|
266
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
267
|
-
`Configuration Value: ${field}: ${this[field]} (from ${from}${usedAlias ? ` via alias` : ""})`);
|
|
256
|
+
log(`Configuration Value: ${field}: ${this[field]} (from ${from}${usedAlias ? ` via alias` : ""})`);
|
|
268
257
|
}
|
|
269
258
|
}
|
|
270
259
|
}
|
package/dist/src/DbUtils.js
CHANGED
|
@@ -75,7 +75,6 @@ async function DbUtilsInit(context, config, sqlDir) {
|
|
|
75
75
|
* - SQLite: `better-sqlite3` `Database` instance
|
|
76
76
|
* - Postgres: `pg` `Pool` instance
|
|
77
77
|
*/
|
|
78
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
79
78
|
function DbUtilsGetDatabase() {
|
|
80
79
|
if (databaseType === "postgres") {
|
|
81
80
|
return PostgresDbUtils.PostgresDbUtilsGetPool();
|
|
@@ -19,9 +19,7 @@ function DbUtilsNoTelemetrySetLogger(loggerIn) {
|
|
|
19
19
|
*
|
|
20
20
|
* @returns Number of rows inserted.
|
|
21
21
|
*/
|
|
22
|
-
function DbUtilsNoTelemetryBatchInsert(tableCols, numCols,
|
|
23
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
24
|
-
rows) {
|
|
22
|
+
function DbUtilsNoTelemetryBatchInsert(tableCols, numCols, rows) {
|
|
25
23
|
if (rows.length === 0)
|
|
26
24
|
return 0;
|
|
27
25
|
const rowSQL = `(${Array.from({ length: numCols }, () => "?").join(",")})`;
|
|
@@ -40,7 +38,6 @@ function DbUtilsNoTelemetryExecSQL(sql, params = []) {
|
|
|
40
38
|
if (dbType === "postgres") {
|
|
41
39
|
const pgSql = (0, DbUtils_1.convertToPostgresPlaceholders)(sql);
|
|
42
40
|
return new Promise((resolve, reject) => {
|
|
43
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
44
41
|
(0, DbUtils_1.DbUtilsGetDatabase)().query(pgSql, params, (error, result) => {
|
|
45
42
|
if (error) {
|
|
46
43
|
logger.error(`SQL INSERT ERROR: ${sql.substring(0, 200)}`, error);
|
|
@@ -71,7 +68,6 @@ function DbUtilsNoTelemetryQuerySQL(sql, params = [], debug = false) {
|
|
|
71
68
|
if (dbType === "postgres") {
|
|
72
69
|
const pgSql = (0, DbUtils_1.convertToPostgresPlaceholders)(sql);
|
|
73
70
|
return new Promise((resolve, reject) => {
|
|
74
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
75
71
|
(0, DbUtils_1.DbUtilsGetDatabase)().query(pgSql, params, (error, result) => {
|
|
76
72
|
if (error) {
|
|
77
73
|
logger.error(`SQL ERROR: ${sql}`, error);
|
package/dist/src/LLM.js
CHANGED
|
@@ -87,7 +87,6 @@ class LLMClient {
|
|
|
87
87
|
throw new Error("LLM integration disabled (apiKey, apiUrl or model not set)");
|
|
88
88
|
}
|
|
89
89
|
const model = (options === null || options === void 0 ? void 0 : options.model) || this.model;
|
|
90
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
91
90
|
const body = {
|
|
92
91
|
model,
|
|
93
92
|
messages,
|
|
@@ -122,7 +121,6 @@ class LLMClient {
|
|
|
122
121
|
extractErrorMessage(err) {
|
|
123
122
|
var _a, _b, _c;
|
|
124
123
|
if (axios_1.default.isAxiosError(err)) {
|
|
125
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
126
124
|
const providerMessage = (_c = (_b = (_a = err.response) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.error) === null || _c === void 0 ? void 0 : _c.message;
|
|
127
125
|
if (providerMessage) {
|
|
128
126
|
return providerMessage;
|
package/dist/src/OTelContext.js
CHANGED
|
@@ -166,9 +166,7 @@ class PostgresSchemaDbUtils {
|
|
|
166
166
|
* otherwise use the runtime pool (default).
|
|
167
167
|
* @returns Number of rows changed.
|
|
168
168
|
*/
|
|
169
|
-
execSQL(context, sql,
|
|
170
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
171
|
-
params = [], useSchemaPool = false) {
|
|
169
|
+
execSQL(context, sql, params = [], useSchemaPool = false) {
|
|
172
170
|
const span = tracer.startSpan("PostgresSchemaDbUtilsExecSQL", context);
|
|
173
171
|
const pool = useSchemaPool ? this.schemaPool : this.runtimePool;
|
|
174
172
|
if (!pool) {
|
|
@@ -219,9 +217,7 @@ class PostgresSchemaDbUtils {
|
|
|
219
217
|
* Execute a read SQL query with OTel tracing.
|
|
220
218
|
* @returns Array of row objects.
|
|
221
219
|
*/
|
|
222
|
-
querySQL(context, sql,
|
|
223
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
224
|
-
params = [], useSchemaPool = false) {
|
|
220
|
+
querySQL(context, sql, params = [], useSchemaPool = false) {
|
|
225
221
|
const span = tracer.startSpan("PostgresSchemaDbUtilsQuerySQL", context);
|
|
226
222
|
const pool = useSchemaPool ? this.schemaPool : this.runtimePool;
|
|
227
223
|
if (!pool) {
|
|
@@ -247,9 +243,7 @@ class PostgresSchemaDbUtils {
|
|
|
247
243
|
/**
|
|
248
244
|
* Run a callback inside a transaction.
|
|
249
245
|
*/
|
|
250
|
-
async transaction(context,
|
|
251
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
252
|
-
callback, useSchemaPool = false) {
|
|
246
|
+
async transaction(context, callback, useSchemaPool = false) {
|
|
253
247
|
const span = tracer.startSpan("PostgresSchemaDbUtilsTransaction", context);
|
|
254
248
|
const pool = useSchemaPool ? this.schemaPool : this.runtimePool;
|
|
255
249
|
if (!pool) {
|
|
@@ -294,15 +288,12 @@ class PostgresSchemaDbUtils {
|
|
|
294
288
|
this.moduleLogger.info("All database pools closed");
|
|
295
289
|
}
|
|
296
290
|
// -- Internal helpers (schema pool only) ----------------------------------
|
|
297
|
-
execSQLForSchema(context, sql,
|
|
298
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
299
|
-
params = []) {
|
|
291
|
+
execSQLForSchema(context, sql, params = []) {
|
|
300
292
|
const span = tracer.startSpan("PostgresSchemaDbUtilsExecSQLForSchema", context);
|
|
301
293
|
if (!this.schemaPool) {
|
|
302
294
|
throw new Error(`Pool not initialized for schema: ${this.schemaName}`);
|
|
303
295
|
}
|
|
304
296
|
return new Promise((resolve, reject) => {
|
|
305
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
306
297
|
this.schemaPool.query(sql, params, (error) => {
|
|
307
298
|
span.end();
|
|
308
299
|
if (error) {
|
|
@@ -322,11 +313,9 @@ class PostgresSchemaDbUtils {
|
|
|
322
313
|
throw new Error(`Pool not initialized for schema: ${this.schemaName}`);
|
|
323
314
|
}
|
|
324
315
|
return new Promise((resolve, reject) => {
|
|
325
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
326
316
|
this.schemaPool.query(sql, (error) => {
|
|
327
317
|
span.end();
|
|
328
318
|
if (error) {
|
|
329
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
330
319
|
if (error.code === "ENOENT") {
|
|
331
320
|
resolve();
|
|
332
321
|
}
|
|
@@ -341,22 +330,18 @@ class PostgresSchemaDbUtils {
|
|
|
341
330
|
});
|
|
342
331
|
}
|
|
343
332
|
catch (error) {
|
|
344
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
345
333
|
if (error.code === "ENOENT") {
|
|
346
334
|
return;
|
|
347
335
|
}
|
|
348
336
|
throw error;
|
|
349
337
|
}
|
|
350
338
|
}
|
|
351
|
-
querySQLForSchema(context, sql,
|
|
352
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
353
|
-
params = []) {
|
|
339
|
+
querySQLForSchema(context, sql, params = []) {
|
|
354
340
|
const span = tracer.startSpan("PostgresSchemaDbUtilsQuerySQLForSchema", context);
|
|
355
341
|
if (!this.schemaPool) {
|
|
356
342
|
throw new Error(`Pool not initialized for schema: ${this.schemaName}`);
|
|
357
343
|
}
|
|
358
344
|
return new Promise((resolve, reject) => {
|
|
359
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
360
345
|
this.schemaPool.query(sql, params, (error, result) => {
|
|
361
346
|
span.end();
|
|
362
347
|
if (error) {
|
package/dist/src/users/Auth.js
CHANGED
|
@@ -104,7 +104,6 @@ async function AuthGenerateJWT(user) {
|
|
|
104
104
|
* mirroring the owning user's live role and scopes is built (valid until
|
|
105
105
|
* the token is revoked).
|
|
106
106
|
*/
|
|
107
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
108
107
|
async function jwtDecodeCached(req) {
|
|
109
108
|
if (req._jwtPayload) {
|
|
110
109
|
return req._jwtPayload;
|
|
@@ -131,7 +130,6 @@ async function jwtDecodeCached(req) {
|
|
|
131
130
|
* Permissions are read from the user record at resolution time, so
|
|
132
131
|
* role/scope changes apply to existing tokens immediately.
|
|
133
132
|
*/
|
|
134
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
135
133
|
async function resolveApiToken(authorizationHeader) {
|
|
136
134
|
const token = authorizationHeader.split(" ")[1];
|
|
137
135
|
if (!token) {
|
|
@@ -140,7 +138,6 @@ async function resolveApiToken(authorizationHeader) {
|
|
|
140
138
|
const span = tracer.startSpan("AuthResolveApiToken");
|
|
141
139
|
const tokenHash = (0, crypto_1.createHash)("sha256").update(token).digest("hex");
|
|
142
140
|
const apiToken = await (0, UsersApiTokensData_1.UsersApiTokensDataGetByTokenHash)(span, tokenHash);
|
|
143
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
144
141
|
let payload = null;
|
|
145
142
|
if (apiToken) {
|
|
146
143
|
const user = await (0, UsersData_1.UsersDataGet)(span, apiToken.userId);
|
|
@@ -156,17 +153,12 @@ async function resolveApiToken(authorizationHeader) {
|
|
|
156
153
|
span.end();
|
|
157
154
|
return payload;
|
|
158
155
|
}
|
|
159
|
-
async function AuthMustBeAuthenticated(
|
|
160
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
161
|
-
req,
|
|
162
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
163
|
-
res) {
|
|
156
|
+
async function AuthMustBeAuthenticated(req, res) {
|
|
164
157
|
if (!(await jwtDecodeCached(req))) {
|
|
165
158
|
res.status(403).send({ error: "Access Denied" });
|
|
166
159
|
throw new Error("Access Denied");
|
|
167
160
|
}
|
|
168
161
|
}
|
|
169
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
170
162
|
async function AuthMustBeAdmin(req, res) {
|
|
171
163
|
const info = await jwtDecodeCached(req);
|
|
172
164
|
if ((info === null || info === void 0 ? void 0 : info.role) === "admin") {
|
|
@@ -175,11 +167,7 @@ async function AuthMustBeAdmin(req, res) {
|
|
|
175
167
|
res.status(403).send({ error: "Access Denied" });
|
|
176
168
|
throw new Error("Access Denied");
|
|
177
169
|
}
|
|
178
|
-
async function AuthHasScope(
|
|
179
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
180
|
-
req,
|
|
181
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
182
|
-
res, scope) {
|
|
170
|
+
async function AuthHasScope(req, res, scope) {
|
|
183
171
|
const info = await jwtDecodeCached(req);
|
|
184
172
|
if (!info) {
|
|
185
173
|
res.status(403).send({ error: "Access Denied" });
|
|
@@ -195,7 +183,6 @@ res, scope) {
|
|
|
195
183
|
res.status(403).send({ error: "Access Denied" });
|
|
196
184
|
throw new Error("Access Denied");
|
|
197
185
|
}
|
|
198
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
199
186
|
async function AuthGetUserSession(req) {
|
|
200
187
|
const userSession = { isAuthenticated: false };
|
|
201
188
|
const info = await jwtDecodeCached(req);
|
package/dist/src/users/User.js
CHANGED
|
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.User = void 0;
|
|
4
4
|
const uuid_1 = require("uuid");
|
|
5
5
|
class User {
|
|
6
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
7
6
|
static fromJson(json) {
|
|
8
7
|
if (!json) {
|
|
9
8
|
return null;
|
|
@@ -34,7 +33,6 @@ class User {
|
|
|
34
33
|
this.scopes = [...User.DEFAULT_SCOPES];
|
|
35
34
|
this.id = (0, uuid_1.v4)();
|
|
36
35
|
}
|
|
37
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
38
36
|
toJson() {
|
|
39
37
|
return {
|
|
40
38
|
id: this.id,
|
|
@@ -44,7 +42,6 @@ class User {
|
|
|
44
42
|
scopes: this.scopes,
|
|
45
43
|
};
|
|
46
44
|
}
|
|
47
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
48
45
|
toTransportJson() {
|
|
49
46
|
return {
|
|
50
47
|
id: this.id,
|
|
@@ -12,7 +12,6 @@ const uuid_1 = require("uuid");
|
|
|
12
12
|
*/
|
|
13
13
|
class UserApiToken {
|
|
14
14
|
//
|
|
15
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
16
15
|
static fromJson(json) {
|
|
17
16
|
if (!json) {
|
|
18
17
|
return null;
|
|
@@ -28,7 +27,6 @@ class UserApiToken {
|
|
|
28
27
|
constructor() {
|
|
29
28
|
this.id = (0, uuid_1.v4)();
|
|
30
29
|
}
|
|
31
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
32
30
|
toJson() {
|
|
33
31
|
return {
|
|
34
32
|
id: this.id,
|
|
@@ -39,7 +37,6 @@ class UserApiToken {
|
|
|
39
37
|
};
|
|
40
38
|
}
|
|
41
39
|
// Transport representation: never exposes the token hash.
|
|
42
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
43
40
|
toTransportJson() {
|
|
44
41
|
return {
|
|
45
42
|
id: this.id,
|
|
@@ -71,7 +71,6 @@ async function UsersApiTokensDataDeleteByUser(context, userId) {
|
|
|
71
71
|
span.end();
|
|
72
72
|
}
|
|
73
73
|
// Private Functions
|
|
74
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
75
74
|
function fromRaw(tokenRaw) {
|
|
76
75
|
const apiToken = new UserApiToken_1.UserApiToken();
|
|
77
76
|
apiToken.id = tokenRaw.id;
|
|
@@ -13,7 +13,6 @@ const UsersApiTokensData_1 = require("./UsersApiTokensData");
|
|
|
13
13
|
* Retrieves the OTel span attached to the request by the
|
|
14
14
|
* `@devopsplaybook.io/otel-utils-fastify` hooks.
|
|
15
15
|
*/
|
|
16
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
17
16
|
function requestSpan(req) {
|
|
18
17
|
return req === null || req === void 0 ? void 0 : req.tracerSpanApi;
|
|
19
18
|
}
|
package/jest.config.js
CHANGED
|
@@ -2,12 +2,15 @@ module.exports = {
|
|
|
2
2
|
moduleFileExtensions: ["ts", "js"],
|
|
3
3
|
transform: {
|
|
4
4
|
"^.+\\.(ts|tsx)$": [
|
|
5
|
-
"
|
|
5
|
+
"@swc/jest",
|
|
6
6
|
{
|
|
7
|
-
|
|
7
|
+
jsc: {
|
|
8
|
+
target: "es2019",
|
|
9
|
+
},
|
|
8
10
|
},
|
|
9
11
|
],
|
|
10
12
|
},
|
|
13
|
+
coverageProvider: "v8",
|
|
11
14
|
testMatch: ["/**/src/**/*.spec.(ts|js)"],
|
|
12
15
|
testPathIgnorePatterns: ["/node_modules/", "/dist/"],
|
|
13
16
|
testEnvironment: "node",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devopsplaybook.io/common-utils",
|
|
3
|
-
"version": "1.10.0
|
|
3
|
+
"version": "1.10.0",
|
|
4
4
|
"description": "Shared utility modules for devopsplaybook.io projects (DB, Config, OTel context, auth/users, notifications, LLM, system helpers)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Open Telemetry",
|
|
@@ -21,39 +21,37 @@
|
|
|
21
21
|
"main": "dist/index.js",
|
|
22
22
|
"types": "dist/index.d.ts",
|
|
23
23
|
"scripts": {
|
|
24
|
-
"build": "tsc",
|
|
25
|
-
"lint": "
|
|
24
|
+
"build": "tsc && tsc -p tsconfig.spec.json --noEmit",
|
|
25
|
+
"lint": "oxlint src",
|
|
26
26
|
"test": "jest --coverage"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@devopsplaybook.io/otel-utils": "^1.1.
|
|
29
|
+
"@devopsplaybook.io/otel-utils": "^1.1.5",
|
|
30
30
|
"@opentelemetry/api": "^1.9.1",
|
|
31
|
-
"@opentelemetry/sdk-trace-base": "^2.
|
|
32
|
-
"axios": "^1.
|
|
31
|
+
"@opentelemetry/sdk-trace-base": "^2.11.0",
|
|
32
|
+
"axios": "^1.20.0",
|
|
33
33
|
"bcrypt": "^6.0.0",
|
|
34
34
|
"better-sqlite3": "^13.0.3",
|
|
35
|
-
"fastify": "^5.12.
|
|
35
|
+
"fastify": "^5.12.5",
|
|
36
36
|
"fs-extra": "^11.4.0",
|
|
37
37
|
"jsonwebtoken": "^9.0.3",
|
|
38
38
|
"pg": "^8.23.0",
|
|
39
39
|
"uuid": "^14.0.2"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@
|
|
42
|
+
"@swc/core": "^1.16.2",
|
|
43
|
+
"@swc/jest": "^0.2.39",
|
|
43
44
|
"@types/bcrypt": "^6.0.0",
|
|
44
45
|
"@types/better-sqlite3": "^9.6.0",
|
|
45
46
|
"@types/fs-extra": "^11.0.4",
|
|
46
47
|
"@types/jest": "^30.0.0",
|
|
47
48
|
"@types/jsonwebtoken": "^9.0.10",
|
|
48
|
-
"@types/node": "^26.2
|
|
49
|
+
"@types/node": "^26.6.2",
|
|
49
50
|
"@types/pg": "^8.23.1",
|
|
50
51
|
"@types/uuid": "^11.0.0",
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"ts-node": "^10.9.2",
|
|
55
|
-
"typescript": "^6.0.3",
|
|
56
|
-
"typescript-eslint": "^8.67.0"
|
|
52
|
+
"jest": "^30.5.2",
|
|
53
|
+
"oxlint": "^1.83.0",
|
|
54
|
+
"typescript": "^7.0.2"
|
|
57
55
|
},
|
|
58
56
|
"publishConfig": {
|
|
59
57
|
"access": "public"
|
package/src/ConfigBase.spec.ts
CHANGED
|
@@ -105,37 +105,4 @@ describe("ConfigBase", () => {
|
|
|
105
105
|
await config.reload();
|
|
106
106
|
expect(config.DATABASE_TYPE).toBe("postgres");
|
|
107
107
|
});
|
|
108
|
-
|
|
109
|
-
describe("VERSION detection", () => {
|
|
110
|
-
let cwdSpy: jest.SpyInstance;
|
|
111
|
-
|
|
112
|
-
afterEach(() => {
|
|
113
|
-
cwdSpy.mockRestore();
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it("should detect VERSION from a package.json in the working directory", () => {
|
|
117
|
-
fse.writeJsonSync(path.join(tmpDir, "package.json"), {
|
|
118
|
-
name: "host-app",
|
|
119
|
-
version: "3.2.1",
|
|
120
|
-
});
|
|
121
|
-
cwdSpy = jest.spyOn(process, "cwd").mockReturnValue(tmpDir);
|
|
122
|
-
const config = new TestConfig(configPath);
|
|
123
|
-
expect(config.VERSION).toBe("3.2.1");
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it("should fall back to VERSION '1' when the working directory has no package.json", () => {
|
|
127
|
-
cwdSpy = jest.spyOn(process, "cwd").mockReturnValue(tmpDir);
|
|
128
|
-
const config = new TestConfig(configPath);
|
|
129
|
-
expect(config.VERSION).toBe("1");
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
it("should fall back to VERSION '1' when package.json has no version field", () => {
|
|
133
|
-
fse.writeJsonSync(path.join(tmpDir, "package.json"), {
|
|
134
|
-
name: "host-app",
|
|
135
|
-
});
|
|
136
|
-
cwdSpy = jest.spyOn(process, "cwd").mockReturnValue(tmpDir);
|
|
137
|
-
const config = new TestConfig(configPath);
|
|
138
|
-
expect(config.VERSION).toBe("1");
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
108
|
});
|
package/src/ConfigBase.ts
CHANGED
|
@@ -59,7 +59,6 @@ export interface ConfigCommonInterface
|
|
|
59
59
|
* array → JSON.parse, etc.). When the default is already a string or
|
|
60
60
|
* there is no default, the original string is returned as-is.
|
|
61
61
|
*/
|
|
62
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
63
62
|
function coerceValue(value: string, defaultValue: any): any {
|
|
64
63
|
if (defaultValue === undefined || defaultValue === null) {
|
|
65
64
|
return value;
|
|
@@ -155,7 +154,6 @@ export abstract class ConfigBase implements ConfigCommonInterface {
|
|
|
155
154
|
field: string;
|
|
156
155
|
sensitive: boolean;
|
|
157
156
|
envAliases: string[];
|
|
158
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
159
157
|
defaultValue: any;
|
|
160
158
|
}[] = [];
|
|
161
159
|
|
|
@@ -167,17 +165,13 @@ export abstract class ConfigBase implements ConfigCommonInterface {
|
|
|
167
165
|
this.SERVICE_ID = serviceId;
|
|
168
166
|
this.CONFIG_FILE = configFile || process.env.CONFIG_FILE || "config.json";
|
|
169
167
|
|
|
170
|
-
// Auto-detect
|
|
171
|
-
// Services run with their project root as the current working
|
|
172
|
-
// directory, so <cwd>/package.json is the host app's manifest. The
|
|
173
|
-
// installed library's own package.json is deliberately not used: it
|
|
174
|
-
// would report the library version, not the service version.
|
|
168
|
+
// Auto-detect version from nearest package.json
|
|
175
169
|
try {
|
|
176
|
-
const pkg = fse.readJsonSync(path.resolve(
|
|
170
|
+
const pkg = fse.readJsonSync(path.resolve(__dirname, "../package.json"));
|
|
177
171
|
if (pkg && pkg.version) {
|
|
178
172
|
this.VERSION = pkg.version;
|
|
179
173
|
}
|
|
180
|
-
// eslint-disable-next-line
|
|
174
|
+
// eslint-disable-next-line no-unused-vars -- catch binding kept so dist/ stays byte-identical to the TS6 build
|
|
181
175
|
} catch (_e) {
|
|
182
176
|
// fallback to "1"
|
|
183
177
|
}
|
|
@@ -228,7 +222,6 @@ export abstract class ConfigBase implements ConfigCommonInterface {
|
|
|
228
222
|
field: def.field,
|
|
229
223
|
sensitive: def.sensitive ?? false,
|
|
230
224
|
envAliases: def.envAliases ?? [],
|
|
231
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
232
225
|
defaultValue: (this as any)[def.field],
|
|
233
226
|
});
|
|
234
227
|
}
|
|
@@ -241,12 +234,11 @@ export abstract class ConfigBase implements ConfigCommonInterface {
|
|
|
241
234
|
* When omitted nothing is logged (useful in tests).
|
|
242
235
|
*/
|
|
243
236
|
public async reload(logger?: (message: string) => void): Promise<void> {
|
|
244
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
245
237
|
const log = logger ?? (() => {});
|
|
246
238
|
let content: Record<string, unknown> = {};
|
|
247
239
|
try {
|
|
248
240
|
content = await fse.readJson(this.CONFIG_FILE);
|
|
249
|
-
// eslint-disable-next-line
|
|
241
|
+
// eslint-disable-next-line no-unused-vars -- catch binding kept so dist/ stays byte-identical to the TS6 build
|
|
250
242
|
} catch (_e) {
|
|
251
243
|
// config file is optional – fall back to env + defaults
|
|
252
244
|
}
|
|
@@ -281,14 +273,12 @@ export abstract class ConfigBase implements ConfigCommonInterface {
|
|
|
281
273
|
// 3. Apply environment value (full name or alias) if found,
|
|
282
274
|
// coercing strings to match the default value type
|
|
283
275
|
if (foundValue !== undefined) {
|
|
284
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
285
276
|
(this as any)[field] = coerceValue(foundValue, defaultValue);
|
|
286
277
|
}
|
|
287
278
|
|
|
288
279
|
// 4. Config file override (environment always wins, but if neither
|
|
289
280
|
// environment nor alias matched, check config file)
|
|
290
281
|
if (foundValue === undefined && content[field] !== undefined) {
|
|
291
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
292
282
|
(this as any)[field] = content[field];
|
|
293
283
|
from = "config";
|
|
294
284
|
}
|
|
@@ -299,7 +289,6 @@ export abstract class ConfigBase implements ConfigCommonInterface {
|
|
|
299
289
|
);
|
|
300
290
|
} else {
|
|
301
291
|
log(
|
|
302
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
303
292
|
`Configuration Value: ${field}: ${(this as any)[field]} (from ${from}${usedAlias ? ` via alias` : ""})`,
|
|
304
293
|
);
|
|
305
294
|
}
|
package/src/DbUtils.ts
CHANGED
|
@@ -53,7 +53,6 @@ export async function DbUtilsInit(
|
|
|
53
53
|
* - SQLite: `better-sqlite3` `Database` instance
|
|
54
54
|
* - Postgres: `pg` `Pool` instance
|
|
55
55
|
*/
|
|
56
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
57
56
|
export function DbUtilsGetDatabase(): any {
|
|
58
57
|
if (databaseType === "postgres") {
|
|
59
58
|
return PostgresDbUtils.PostgresDbUtilsGetPool();
|
|
@@ -99,7 +98,6 @@ export function DbUtilsQuerySQL(
|
|
|
99
98
|
sql: string,
|
|
100
99
|
params: unknown[] = [],
|
|
101
100
|
debug = false,
|
|
102
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
103
101
|
): any[] | Promise<any[]> {
|
|
104
102
|
if (databaseType === "postgres") {
|
|
105
103
|
return PostgresDbUtils.PostgresDbUtilsQuerySQL(
|
|
@@ -4,7 +4,6 @@ import {
|
|
|
4
4
|
DbUtilsNoTelemetryQuerySQL,
|
|
5
5
|
} from "./DbUtilsNoTelemetry";
|
|
6
6
|
|
|
7
|
-
/* eslint-disable @typescript-eslint/no-unsafe-function-type */
|
|
8
7
|
|
|
9
8
|
// Shared mock DB handle – defined BEFORE jest.mock factory so it's hoisted correctly.
|
|
10
9
|
// We use jest.fn() at module scope; the mock factory captures the same reference.
|
|
@@ -38,11 +37,9 @@ beforeAll(() => {
|
|
|
38
37
|
error: jest.fn(),
|
|
39
38
|
info: jest.fn(),
|
|
40
39
|
warn: jest.fn(),
|
|
41
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
42
40
|
} as any;
|
|
43
41
|
DbUtilsNoTelemetryModule.DbUtilsNoTelemetrySetLogger({
|
|
44
42
|
createModuleLogger: () => mockLogger,
|
|
45
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
46
43
|
} as any);
|
|
47
44
|
});
|
|
48
45
|
|
|
@@ -62,7 +59,6 @@ describe("DbUtilsNoTelemetryBatchInsert", () => {
|
|
|
62
59
|
mockPrepare.mockReturnValue({
|
|
63
60
|
run: jest.fn().mockReturnValue({ changes: 2 }),
|
|
64
61
|
all: jest.fn(),
|
|
65
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
66
62
|
} as any);
|
|
67
63
|
|
|
68
64
|
const rows = [
|
|
@@ -83,7 +79,6 @@ describe("DbUtilsNoTelemetryExecSQL (sqlite)", () => {
|
|
|
83
79
|
mockPrepare.mockReturnValue({
|
|
84
80
|
run: jest.fn().mockReturnValue({ changes: 3 }),
|
|
85
81
|
all: jest.fn(),
|
|
86
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
87
82
|
} as any);
|
|
88
83
|
|
|
89
84
|
const result = DbUtilsNoTelemetryExecSQL("INSERT INTO t (c) VALUES (?)", [
|
|
@@ -135,7 +130,6 @@ describe("DbUtilsNoTelemetryQuerySQL (sqlite)", () => {
|
|
|
135
130
|
mockPrepare.mockReturnValue({
|
|
136
131
|
run: jest.fn(),
|
|
137
132
|
all: jest.fn().mockReturnValue(expectedRows),
|
|
138
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
139
133
|
} as any);
|
|
140
134
|
|
|
141
135
|
const result = DbUtilsNoTelemetryQuerySQL("SELECT * FROM t");
|
|
@@ -24,7 +24,6 @@ export function DbUtilsNoTelemetrySetLogger(loggerIn: StandardLogger): void {
|
|
|
24
24
|
export function DbUtilsNoTelemetryBatchInsert(
|
|
25
25
|
tableCols: string,
|
|
26
26
|
numCols: number,
|
|
27
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
28
27
|
rows: any[][],
|
|
29
28
|
): number | Promise<number> {
|
|
30
29
|
if (rows.length === 0) return 0;
|
|
@@ -50,7 +49,6 @@ export function DbUtilsNoTelemetryExecSQL(
|
|
|
50
49
|
if (dbType === "postgres") {
|
|
51
50
|
const pgSql = convertToPostgresPlaceholders(sql);
|
|
52
51
|
return new Promise((resolve, reject) => {
|
|
53
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
54
52
|
(DbUtilsGetDatabase() as any).query(
|
|
55
53
|
pgSql,
|
|
56
54
|
params,
|
|
@@ -87,7 +85,6 @@ export function DbUtilsNoTelemetryQuerySQL(
|
|
|
87
85
|
sql: string,
|
|
88
86
|
params: unknown[] = [],
|
|
89
87
|
debug = false,
|
|
90
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
91
88
|
): any[] | Promise<any[]> {
|
|
92
89
|
if (debug) {
|
|
93
90
|
console.log(sql);
|
|
@@ -96,7 +93,6 @@ export function DbUtilsNoTelemetryQuerySQL(
|
|
|
96
93
|
if (dbType === "postgres") {
|
|
97
94
|
const pgSql = convertToPostgresPlaceholders(sql);
|
|
98
95
|
return new Promise((resolve, reject) => {
|
|
99
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
100
96
|
(DbUtilsGetDatabase() as any).query(
|
|
101
97
|
pgSql,
|
|
102
98
|
params,
|
package/src/LLM.ts
CHANGED
|
@@ -159,7 +159,6 @@ export class LLMClient {
|
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
const model = options?.model || this.model;
|
|
162
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
163
162
|
const body: Record<string, any> = {
|
|
164
163
|
model,
|
|
165
164
|
messages,
|
|
@@ -195,7 +194,6 @@ export class LLMClient {
|
|
|
195
194
|
*/
|
|
196
195
|
private extractErrorMessage(err: unknown): string {
|
|
197
196
|
if (axios.isAxiosError(err)) {
|
|
198
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
199
197
|
const providerMessage = (err.response?.data as any)?.error?.message;
|
|
200
198
|
if (providerMessage) {
|
|
201
199
|
return providerMessage;
|
package/src/OTelContext.ts
CHANGED
|
@@ -20,7 +20,6 @@ export interface OTelContext {
|
|
|
20
20
|
* Retrieves the span previously attached to a request object.
|
|
21
21
|
* Equivalent to `req.tracerSpanApi`.
|
|
22
22
|
*/
|
|
23
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
24
23
|
OTelRequestSpan: (req: any) => Span | undefined;
|
|
25
24
|
}
|
|
26
25
|
|
|
@@ -59,7 +58,6 @@ export function createOTelContext(): OTelContext {
|
|
|
59
58
|
}
|
|
60
59
|
return logger;
|
|
61
60
|
},
|
|
62
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
63
61
|
OTelRequestSpan: (req: any) => req?.tracerSpanApi as Span | undefined,
|
|
64
62
|
};
|
|
65
63
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
1
|
|
|
3
2
|
// Mock pg before imports
|
|
4
3
|
const mockQuery = jest.fn();
|
|
@@ -58,7 +57,6 @@ describe("PostgresDbUtils", () => {
|
|
|
58
57
|
beforeEach(() => {
|
|
59
58
|
jest.clearAllMocks();
|
|
60
59
|
jest.isolateModules(() => {
|
|
61
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
62
60
|
PostgresDbUtils = require("./PostgresDbUtils");
|
|
63
61
|
});
|
|
64
62
|
PostgresDbUtils.PostgresDbUtilsSetOTel(
|
package/src/PostgresDbUtils.ts
CHANGED
|
@@ -181,7 +181,6 @@ export class PostgresSchemaDbUtils {
|
|
|
181
181
|
execSQL(
|
|
182
182
|
context: Span,
|
|
183
183
|
sql: string,
|
|
184
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
185
184
|
params: any[] = [],
|
|
186
185
|
useSchemaPool = false,
|
|
187
186
|
): Promise<number> {
|
|
@@ -257,10 +256,8 @@ export class PostgresSchemaDbUtils {
|
|
|
257
256
|
querySQL(
|
|
258
257
|
context: Span,
|
|
259
258
|
sql: string,
|
|
260
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
261
259
|
params: any[] = [],
|
|
262
260
|
useSchemaPool = false,
|
|
263
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
264
261
|
): Promise<any[]> {
|
|
265
262
|
const span = tracer.startSpan("PostgresSchemaDbUtilsQuerySQL", context);
|
|
266
263
|
const pool = useSchemaPool ? this.schemaPool : this.runtimePool;
|
|
@@ -300,7 +297,6 @@ export class PostgresSchemaDbUtils {
|
|
|
300
297
|
*/
|
|
301
298
|
async transaction(
|
|
302
299
|
context: Span,
|
|
303
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
304
300
|
callback: (client: any) => Promise<void>,
|
|
305
301
|
useSchemaPool = false,
|
|
306
302
|
): Promise<void> {
|
|
@@ -370,7 +366,6 @@ export class PostgresSchemaDbUtils {
|
|
|
370
366
|
private execSQLForSchema(
|
|
371
367
|
context: Span,
|
|
372
368
|
sql: string,
|
|
373
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
374
369
|
params: any[] = [],
|
|
375
370
|
): Promise<void> {
|
|
376
371
|
const span = tracer.startSpan(
|
|
@@ -383,7 +378,6 @@ export class PostgresSchemaDbUtils {
|
|
|
383
378
|
}
|
|
384
379
|
|
|
385
380
|
return new Promise((resolve, reject) => {
|
|
386
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
387
381
|
this.schemaPool!.query(sql, params, (error: Error | null) => {
|
|
388
382
|
span.end();
|
|
389
383
|
if (error) {
|
|
@@ -411,11 +405,9 @@ export class PostgresSchemaDbUtils {
|
|
|
411
405
|
}
|
|
412
406
|
|
|
413
407
|
return new Promise((resolve, reject) => {
|
|
414
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
415
408
|
this.schemaPool!.query(sql, (error: Error | null) => {
|
|
416
409
|
span.end();
|
|
417
410
|
if (error) {
|
|
418
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
419
411
|
if ((error as any).code === "ENOENT") {
|
|
420
412
|
resolve();
|
|
421
413
|
} else {
|
|
@@ -427,7 +419,6 @@ export class PostgresSchemaDbUtils {
|
|
|
427
419
|
});
|
|
428
420
|
});
|
|
429
421
|
} catch (error) {
|
|
430
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
431
422
|
if ((error as any).code === "ENOENT") {
|
|
432
423
|
return;
|
|
433
424
|
}
|
|
@@ -438,9 +429,7 @@ export class PostgresSchemaDbUtils {
|
|
|
438
429
|
private querySQLForSchema(
|
|
439
430
|
context: Span,
|
|
440
431
|
sql: string,
|
|
441
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
442
432
|
params: any[] = [],
|
|
443
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
444
433
|
): Promise<any[]> {
|
|
445
434
|
const span = tracer.startSpan(
|
|
446
435
|
"PostgresSchemaDbUtilsQuerySQLForSchema",
|
|
@@ -452,7 +441,6 @@ export class PostgresSchemaDbUtils {
|
|
|
452
441
|
}
|
|
453
442
|
|
|
454
443
|
return new Promise((resolve, reject) => {
|
|
455
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
456
444
|
this.schemaPool!.query(
|
|
457
445
|
sql,
|
|
458
446
|
params,
|
|
@@ -616,7 +604,6 @@ export function PostgresDbUtilsQuerySQL(
|
|
|
616
604
|
sql: string,
|
|
617
605
|
params: unknown[] = [],
|
|
618
606
|
debug = false,
|
|
619
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
620
607
|
): Promise<any[]> {
|
|
621
608
|
const span = tracer.startSpan("PostgresDbUtilsQuerySQL", context);
|
|
622
609
|
if (debug) {
|
package/src/SqlDbUtils.spec.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
1
|
|
|
3
2
|
// Mock dependencies before imports
|
|
4
3
|
jest.mock("better-sqlite3", () => {
|
|
@@ -64,7 +63,6 @@ describe("SqlDbUtils", () => {
|
|
|
64
63
|
jest.clearAllMocks();
|
|
65
64
|
// Re-import to reset module state
|
|
66
65
|
jest.isolateModules(() => {
|
|
67
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
68
66
|
SqlDbUtils = require("./SqlDbUtils");
|
|
69
67
|
});
|
|
70
68
|
SqlDbUtils.SqlDbUtilsSetOTel(mockTracer as any, mockLogger as any);
|
|
@@ -77,7 +75,6 @@ describe("SqlDbUtils", () => {
|
|
|
77
75
|
});
|
|
78
76
|
|
|
79
77
|
it("SqlDbUtilsExecSQL should call prepare and run", () => {
|
|
80
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
81
78
|
const Database = require("better-sqlite3");
|
|
82
79
|
const db = new Database("/tmp/test.db");
|
|
83
80
|
const mockRun = jest.fn().mockReturnValue({ changes: 5 });
|
package/src/SqlDbUtils.ts
CHANGED
|
@@ -133,7 +133,6 @@ export function SqlDbUtilsQuerySQL(
|
|
|
133
133
|
sql: string,
|
|
134
134
|
params: unknown[] = [],
|
|
135
135
|
debug = false,
|
|
136
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
137
136
|
): any[] {
|
|
138
137
|
const span = tracer.startSpan("SqlDbUtilsQuerySQL", context);
|
|
139
138
|
if (debug) {
|
package/src/users/Auth.ts
CHANGED
|
@@ -85,7 +85,6 @@ export async function AuthGenerateJWT(user: User): Promise<string> {
|
|
|
85
85
|
* mirroring the owning user's live role and scopes is built (valid until
|
|
86
86
|
* the token is revoked).
|
|
87
87
|
*/
|
|
88
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
89
88
|
async function jwtDecodeCached(req: any): Promise<any | null> {
|
|
90
89
|
if (req._jwtPayload) {
|
|
91
90
|
return req._jwtPayload;
|
|
@@ -115,7 +114,6 @@ async function jwtDecodeCached(req: any): Promise<any | null> {
|
|
|
115
114
|
* Permissions are read from the user record at resolution time, so
|
|
116
115
|
* role/scope changes apply to existing tokens immediately.
|
|
117
116
|
*/
|
|
118
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
119
117
|
async function resolveApiToken(authorizationHeader: string): Promise<any | null> {
|
|
120
118
|
const token = authorizationHeader.split(" ")[1];
|
|
121
119
|
if (!token) {
|
|
@@ -124,7 +122,6 @@ async function resolveApiToken(authorizationHeader: string): Promise<any | null>
|
|
|
124
122
|
const span = tracer.startSpan("AuthResolveApiToken");
|
|
125
123
|
const tokenHash = createHash("sha256").update(token).digest("hex");
|
|
126
124
|
const apiToken = await UsersApiTokensDataGetByTokenHash(span, tokenHash);
|
|
127
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
128
125
|
let payload: any | null = null;
|
|
129
126
|
if (apiToken) {
|
|
130
127
|
const user = await UsersDataGet(span, apiToken.userId);
|
|
@@ -142,9 +139,7 @@ async function resolveApiToken(authorizationHeader: string): Promise<any | null>
|
|
|
142
139
|
}
|
|
143
140
|
|
|
144
141
|
export async function AuthMustBeAuthenticated(
|
|
145
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
146
142
|
req: any,
|
|
147
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
148
143
|
res: any,
|
|
149
144
|
): Promise<void> {
|
|
150
145
|
if (!(await jwtDecodeCached(req))) {
|
|
@@ -153,7 +148,6 @@ export async function AuthMustBeAuthenticated(
|
|
|
153
148
|
}
|
|
154
149
|
}
|
|
155
150
|
|
|
156
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
157
151
|
export async function AuthMustBeAdmin(req: any, res: any): Promise<void> {
|
|
158
152
|
const info = await jwtDecodeCached(req);
|
|
159
153
|
if (info?.role === "admin") {
|
|
@@ -164,9 +158,7 @@ export async function AuthMustBeAdmin(req: any, res: any): Promise<void> {
|
|
|
164
158
|
}
|
|
165
159
|
|
|
166
160
|
export async function AuthHasScope(
|
|
167
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
168
161
|
req: any,
|
|
169
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
170
162
|
res: any,
|
|
171
163
|
scope: UserScope,
|
|
172
164
|
): Promise<void> {
|
|
@@ -186,7 +178,6 @@ export async function AuthHasScope(
|
|
|
186
178
|
throw new Error("Access Denied");
|
|
187
179
|
}
|
|
188
180
|
|
|
189
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
190
181
|
export async function AuthGetUserSession(req: any): Promise<UserSession> {
|
|
191
182
|
const userSession: UserSession = { isAuthenticated: false };
|
|
192
183
|
const info = await jwtDecodeCached(req);
|
package/src/users/User.ts
CHANGED
|
@@ -19,7 +19,6 @@ export class User {
|
|
|
19
19
|
/** Full scope set of the host application, registered via `AuthInit`. */
|
|
20
20
|
public static ALL_SCOPES: UserScope[] = [];
|
|
21
21
|
|
|
22
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
23
22
|
public static fromJson(json: any): User | null {
|
|
24
23
|
if (!json) {
|
|
25
24
|
return null;
|
|
@@ -55,7 +54,6 @@ export class User {
|
|
|
55
54
|
this.id = uuidv4();
|
|
56
55
|
}
|
|
57
56
|
|
|
58
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
59
57
|
public toJson(): any {
|
|
60
58
|
return {
|
|
61
59
|
id: this.id,
|
|
@@ -66,7 +64,6 @@ export class User {
|
|
|
66
64
|
};
|
|
67
65
|
}
|
|
68
66
|
|
|
69
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
70
67
|
public toTransportJson(): any {
|
|
71
68
|
return {
|
|
72
69
|
id: this.id,
|
|
@@ -10,7 +10,6 @@ import { v4 as uuidv4 } from "uuid";
|
|
|
10
10
|
*/
|
|
11
11
|
export class UserApiToken {
|
|
12
12
|
//
|
|
13
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
14
13
|
public static fromJson(json: any): UserApiToken | null {
|
|
15
14
|
if (!json) {
|
|
16
15
|
return null;
|
|
@@ -34,7 +33,6 @@ export class UserApiToken {
|
|
|
34
33
|
this.id = uuidv4();
|
|
35
34
|
}
|
|
36
35
|
|
|
37
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
38
36
|
public toJson(): any {
|
|
39
37
|
return {
|
|
40
38
|
id: this.id,
|
|
@@ -46,7 +44,6 @@ export class UserApiToken {
|
|
|
46
44
|
}
|
|
47
45
|
|
|
48
46
|
// Transport representation: never exposes the token hash.
|
|
49
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
50
47
|
public toTransportJson(): any {
|
|
51
48
|
return {
|
|
52
49
|
id: this.id,
|
|
@@ -100,7 +100,6 @@ export async function UsersApiTokensDataDeleteByUser(
|
|
|
100
100
|
|
|
101
101
|
// Private Functions
|
|
102
102
|
|
|
103
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
104
103
|
function fromRaw(tokenRaw: any): UserApiToken {
|
|
105
104
|
const apiToken = new UserApiToken();
|
|
106
105
|
apiToken.id = tokenRaw.id;
|
package/src/users/UsersData.ts
CHANGED
package/src/users/UsersRoutes.ts
CHANGED
|
@@ -30,7 +30,6 @@ import {
|
|
|
30
30
|
* Retrieves the OTel span attached to the request by the
|
|
31
31
|
* `@devopsplaybook.io/otel-utils-fastify` hooks.
|
|
32
32
|
*/
|
|
33
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
34
33
|
function requestSpan(req: any): Span | undefined {
|
|
35
34
|
return req?.tracerSpanApi;
|
|
36
35
|
}
|
package/tsconfig.spec.json
CHANGED
package/eslint.config.mjs
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
// @ts-check
|
|
2
|
-
|
|
3
|
-
import eslint from "@eslint/js";
|
|
4
|
-
import tseslint from "typescript-eslint";
|
|
5
|
-
|
|
6
|
-
export default tseslint.config(
|
|
7
|
-
{ ignores: ["dist/", "node_modules/"] },
|
|
8
|
-
eslint.configs.recommended,
|
|
9
|
-
...tseslint.configs.strict,
|
|
10
|
-
...tseslint.configs.stylistic,
|
|
11
|
-
);
|