@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.
- package/.github/workflows/main-build.yml +17 -0
- package/.github/workflows/npm-upgrade.yml +16 -0
- package/.github/workflows/pr-check.yml +26 -0
- package/.github/workflows/reusable-merge-build.yml +141 -0
- package/.github/workflows/reusable-npm-merge.yml +135 -0
- package/.github/workflows/reusable-npm-pr.yml +153 -0
- package/.github/workflows/reusable-npm-upgrade.yml +92 -0
- package/.github/workflows/reusable-pr-verify.yml +135 -0
- package/AGENTS.md +84 -0
- package/README.md +413 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +24 -0
- package/dist/src/ConfigBase.d.ts +104 -0
- package/dist/src/ConfigBase.js +201 -0
- package/dist/src/DbUtils.d.ts +50 -0
- package/dist/src/DbUtils.js +117 -0
- package/dist/src/DbUtilsNoTelemetry.d.ts +27 -0
- package/dist/src/DbUtilsNoTelemetry.js +89 -0
- package/dist/src/OTelContext.d.ts +35 -0
- package/dist/src/OTelContext.js +42 -0
- package/dist/src/PostgresDbUtils.d.ts +52 -0
- package/dist/src/PostgresDbUtils.js +217 -0
- package/dist/src/SqlDbUtils.d.ts +40 -0
- package/dist/src/SqlDbUtils.js +156 -0
- package/dist/src/SystemCommand.d.ts +9 -0
- package/dist/src/SystemCommand.js +56 -0
- package/dist/src/Timeout.d.ts +6 -0
- package/dist/src/Timeout.js +15 -0
- package/eslint.config.mjs +10 -0
- package/index.ts +8 -0
- package/jest.config.js +13 -0
- package/package.json +50 -0
- package/prettierrc.json +5 -0
- package/src/ConfigBase.spec.ts +108 -0
- package/src/ConfigBase.ts +213 -0
- package/src/DbUtils.spec.ts +23 -0
- package/src/DbUtils.ts +118 -0
- package/src/DbUtilsNoTelemetry.spec.ts +174 -0
- package/src/DbUtilsNoTelemetry.ts +121 -0
- package/src/OTelContext.spec.ts +58 -0
- package/src/OTelContext.ts +65 -0
- package/src/PostgresDbUtils.spec.ts +155 -0
- package/src/PostgresDbUtils.ts +233 -0
- package/src/SqlDbUtils.spec.ts +111 -0
- package/src/SqlDbUtils.ts +153 -0
- package/src/SystemCommand.spec.ts +18 -0
- package/src/SystemCommand.ts +23 -0
- package/src/Timeout.spec.ts +18 -0
- package/src/Timeout.ts +12 -0
- package/tsconfig.json +14 -0
- package/tsconfig.spec.json +7 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
name: "Reusable: PR Verify"
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_call:
|
|
5
|
+
inputs:
|
|
6
|
+
docker_platforms:
|
|
7
|
+
description: "Docker platforms to build for"
|
|
8
|
+
required: false
|
|
9
|
+
type: string
|
|
10
|
+
default: "linux/arm64/v8,linux/amd64"
|
|
11
|
+
node_app_directories:
|
|
12
|
+
description: "JSON array of Node.js app directories to build, lint, and test"
|
|
13
|
+
required: false
|
|
14
|
+
type: string
|
|
15
|
+
default: ""
|
|
16
|
+
node_version:
|
|
17
|
+
description: "Node.js version to use"
|
|
18
|
+
required: false
|
|
19
|
+
type: string
|
|
20
|
+
default: "22"
|
|
21
|
+
secrets:
|
|
22
|
+
DOCKER_HUB_USERNAME:
|
|
23
|
+
required: true
|
|
24
|
+
DOCKER_HUB_ACCESS_TOKEN:
|
|
25
|
+
required: true
|
|
26
|
+
QUALITY_DASHBOARD_TOKEN:
|
|
27
|
+
required: false
|
|
28
|
+
QUALITY_DASHBOARD_URL:
|
|
29
|
+
required: false
|
|
30
|
+
|
|
31
|
+
jobs:
|
|
32
|
+
node-build:
|
|
33
|
+
if: inputs.node_app_directories != ''
|
|
34
|
+
strategy:
|
|
35
|
+
matrix:
|
|
36
|
+
app: ${{ fromJSON(inputs.node_app_directories) }}
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
defaults:
|
|
39
|
+
run:
|
|
40
|
+
working-directory: ${{ matrix.app }}
|
|
41
|
+
steps:
|
|
42
|
+
- uses: actions/checkout@v6
|
|
43
|
+
|
|
44
|
+
- name: Setup Node.js
|
|
45
|
+
uses: actions/setup-node@v4
|
|
46
|
+
with:
|
|
47
|
+
node-version: ${{ inputs.node_version }}
|
|
48
|
+
cache: "npm"
|
|
49
|
+
cache-dependency-path: ${{ matrix.app }}/package-lock.json
|
|
50
|
+
|
|
51
|
+
- name: Install dependencies
|
|
52
|
+
run: npm ci
|
|
53
|
+
|
|
54
|
+
- name: Build
|
|
55
|
+
run: npm run build
|
|
56
|
+
|
|
57
|
+
- name: Lint
|
|
58
|
+
run: npm run lint
|
|
59
|
+
|
|
60
|
+
- name: Test
|
|
61
|
+
run: npm run test
|
|
62
|
+
|
|
63
|
+
- name: Zip coverage and upload to Quality Dashboard
|
|
64
|
+
if: always()
|
|
65
|
+
env:
|
|
66
|
+
QUALITY_DASHBOARD_URL: ${{ secrets.QUALITY_DASHBOARD_URL }}
|
|
67
|
+
QUALITY_DASHBOARD_TOKEN: ${{ secrets.QUALITY_DASHBOARD_TOKEN }}
|
|
68
|
+
run: |
|
|
69
|
+
APP_NAME="${{ matrix.app }}"
|
|
70
|
+
PACKAGE_NAME=$(node -p "require('./package.json').name")
|
|
71
|
+
REPORT_KEY="$(echo "$APP_NAME" | sed 's/[^a-zA-Z0-9._:\-\/]/_/g')_coverage"
|
|
72
|
+
|
|
73
|
+
if [ -d coverage ]; then
|
|
74
|
+
if [ -z "$QUALITY_DASHBOARD_URL" ] || [ -z "$QUALITY_DASHBOARD_TOKEN" ]; then
|
|
75
|
+
echo "Quality Dashboard URL or token not set, skipping upload"
|
|
76
|
+
exit 0
|
|
77
|
+
fi
|
|
78
|
+
# Zip coverage contents at root level so the jest processor
|
|
79
|
+
# finds coverage-final.json / clover.xml directly
|
|
80
|
+
(cd coverage && zip -r "${{ runner.temp }}/coverage.zip" .)
|
|
81
|
+
|
|
82
|
+
META=$(jq -n \
|
|
83
|
+
--arg key "$REPORT_KEY" \
|
|
84
|
+
--arg pkg "$PACKAGE_NAME" \
|
|
85
|
+
'{
|
|
86
|
+
key: $key,
|
|
87
|
+
displayName: "Coverage: \($pkg)",
|
|
88
|
+
processor: "jest"
|
|
89
|
+
}')
|
|
90
|
+
|
|
91
|
+
echo "Uploading coverage: $REPORT_KEY"
|
|
92
|
+
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
|
|
93
|
+
-X POST "$QUALITY_DASHBOARD_URL/api/reports/" \
|
|
94
|
+
-H "x-upload-token: $QUALITY_DASHBOARD_TOKEN" \
|
|
95
|
+
-F "meta=$META" \
|
|
96
|
+
-F "file=@${{ runner.temp }}/coverage.zip" \
|
|
97
|
+
--max-time 30)
|
|
98
|
+
|
|
99
|
+
if [ "$HTTP_CODE" = "201" ]; then
|
|
100
|
+
echo "\u2713 Coverage uploaded ($HTTP_CODE)"
|
|
101
|
+
else
|
|
102
|
+
echo "\u26a0 Upload returned HTTP $HTTP_CODE"
|
|
103
|
+
fi
|
|
104
|
+
else
|
|
105
|
+
echo "No coverage/ directory found, skipping upload"
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
docker-build:
|
|
109
|
+
runs-on: ubuntu-latest
|
|
110
|
+
steps:
|
|
111
|
+
- uses: actions/checkout@v6
|
|
112
|
+
|
|
113
|
+
- name: Set up QEMU
|
|
114
|
+
uses: docker/setup-qemu-action@v4
|
|
115
|
+
|
|
116
|
+
- name: Set up Docker Buildx
|
|
117
|
+
uses: docker/setup-buildx-action@v4
|
|
118
|
+
|
|
119
|
+
- name: Login to Docker Hub
|
|
120
|
+
uses: docker/login-action@v4
|
|
121
|
+
with:
|
|
122
|
+
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
|
123
|
+
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
|
|
124
|
+
|
|
125
|
+
- name: Build and Push Docker Image
|
|
126
|
+
run: |
|
|
127
|
+
set -e
|
|
128
|
+
SERVICE_NAME=$(cat package.json | jq -r '.name')
|
|
129
|
+
echo "Building ${SERVICE_NAME}:beta"
|
|
130
|
+
docker buildx build \
|
|
131
|
+
--platform ${{ inputs.docker_platforms }} \
|
|
132
|
+
--push \
|
|
133
|
+
-f Dockerfile \
|
|
134
|
+
-t ${{ secrets.DOCKER_HUB_USERNAME }}/${SERVICE_NAME}:beta \
|
|
135
|
+
.
|
package/AGENTS.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
This file provides context for AI coding agents working on this repository.
|
|
4
|
+
|
|
5
|
+
## Repository Overview
|
|
6
|
+
|
|
7
|
+
`@devopsplaybook.io/common-utils` is a shared npm library that centralizes utility modules used across devopsplaybook.io server projects. It serves two purposes:
|
|
8
|
+
|
|
9
|
+
1. **Node.js library** (`src/`): Database access (SQLite via `better-sqlite3`, PostgreSQL via `pg.Pool`), configuration loading, OpenTelemetry context management, and small helpers.
|
|
10
|
+
2. **Reusable GitHub Actions workflows** (`.github/workflows/reusable-*.yml`): Standardized CI/CD pipelines adopted by all projects in the organization.
|
|
11
|
+
|
|
12
|
+
## Architecture
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
index.ts # Barrel re-exports for all modules
|
|
16
|
+
src/
|
|
17
|
+
OTelContext.ts # createOTelContext() factory (tracer/meter/logger singletons)
|
|
18
|
+
ConfigBase.ts # Abstract base class for 3-layer config (env > config.json > defaults)
|
|
19
|
+
SqlDbUtils.ts # SQLite operations (better-sqlite3, synchronous)
|
|
20
|
+
PostgresDbUtils.ts # PostgreSQL operations (pg.Pool, async/Promise)
|
|
21
|
+
DbUtils.ts # Unified facade dispatching to Sql or Postgres
|
|
22
|
+
DbUtilsNoTelemetry.ts # Same DB ops without OTel span overhead
|
|
23
|
+
SystemCommand.ts # Promise wrapper around child_process.exec
|
|
24
|
+
Timeout.ts # Promise wrapper around setTimeout
|
|
25
|
+
*.spec.ts # Co-located test files
|
|
26
|
+
.github/workflows/
|
|
27
|
+
main-build.yml # Caller: push to main -> reusable-npm-merge
|
|
28
|
+
pr-check.yml # Caller: PR to main -> reusable-npm-pr
|
|
29
|
+
npm-upgrade.yml # Caller: weekly schedule -> reusable-npm-upgrade
|
|
30
|
+
reusable-npm-merge.yml # Lint + test + build + publish release to npm
|
|
31
|
+
reusable-npm-pr.yml # Lint + test + build + publish beta tag + comment PR
|
|
32
|
+
reusable-npm-upgrade.yml # npm-check-updates + auto PR
|
|
33
|
+
reusable-pr-verify.yml # Matrix Node.js + multi-platform Docker build for PRs
|
|
34
|
+
reusable-merge-build.yml # Matrix Node.js + Docker build with version tags on merge
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Key Conventions
|
|
38
|
+
|
|
39
|
+
- **TypeScript**: Target ES2019, CommonJS output, strict mode enabled, declarations generated.
|
|
40
|
+
- **ESLint**: Uses `typescript-eslint` with `strict` and `stylistic` rule sets. Minimize `eslint-disable` comments; use them only when the type system cannot express the constraint (e.g., `@typescript-eslint/no-explicit-any` for `this as any` dynamic field access in `ConfigBase`).
|
|
41
|
+
- **Tests**: Jest with `ts-jest`. Spec files live next to source (`*.spec.ts`). Run with `npm run test`. The `tsconfig.spec.json` includes jest types.
|
|
42
|
+
- **No default exports**: All modules use named exports only.
|
|
43
|
+
- **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.
|
|
44
|
+
- **ModuleLogger pattern**: `StandardLogger` only exposes `createModuleLogger(name)`. DB modules call `logger.createModuleLogger("ModuleName")` internally. Never call `.info()` or `.error()` directly on a `StandardLogger`.
|
|
45
|
+
- **SQLite-first SQL**: Write SQL with `?` placeholders. The `DbUtils` facade and `DbUtilsNoTelemetry` module auto-convert to `$1, $2, ...` for Postgres via `convertToPostgresPlaceholders()`.
|
|
46
|
+
- **Migration convention**: SQL files named `init-NNNN.sql`. `init-0000.sql` must create the `metadata` table. Subsequent files are applied in lexicographic order; applied versions are tracked in `metadata` for idempotency.
|
|
47
|
+
|
|
48
|
+
## Build and Verification
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm install
|
|
52
|
+
npm run build # tsc -> dist/
|
|
53
|
+
npm run lint # eslint src (must pass with 0 errors)
|
|
54
|
+
npm run test # jest --coverage (all tests must pass)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
All three commands must pass before committing. The CI pipeline (`reusable-npm-merge.yml`) runs the same checks.
|
|
58
|
+
|
|
59
|
+
## Dependencies
|
|
60
|
+
|
|
61
|
+
| Package | Role |
|
|
62
|
+
| ------------------------------- | ------------------------------------------------------------------------------------------ |
|
|
63
|
+
| `@devopsplaybook.io/otel-utils` | `StandardTracer`, `StandardLogger`, `StandardMeter`, `ModuleLogger`, `ConfigOTelInterface` |
|
|
64
|
+
| `better-sqlite3` | Synchronous SQLite driver (NOT the callback-based `sqlite3`) |
|
|
65
|
+
| `pg` | PostgreSQL client with connection pooling |
|
|
66
|
+
| `uuid` | v14+ (ESM -- requires `jest.mock("uuid")` in tests) |
|
|
67
|
+
| `fs-extra` | Async/sync file operations, `readJson`/`ensureDir` |
|
|
68
|
+
|
|
69
|
+
## Known Gotchas
|
|
70
|
+
|
|
71
|
+
- **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'`.
|
|
72
|
+
- **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.
|
|
73
|
+
- **eslint-disable placement**: `eslint-disable-next-line` applies to the **immediately following line only**. When disabling a rule inside a function call argument, place the comment directly before the offending expression, not before the function call.
|
|
74
|
+
- **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.
|
|
75
|
+
|
|
76
|
+
## Adopting in Other Projects
|
|
77
|
+
|
|
78
|
+
See the [README](./README.md) for full adoption instructions. The typical pattern:
|
|
79
|
+
|
|
80
|
+
1. Add `@devopsplaybook.io/common-utils` as a dependency.
|
|
81
|
+
2. Replace the project's `OTelContext.ts` with `createOTelContext()`.
|
|
82
|
+
3. Replace the project's `Config` class to `extend ConfigBase`.
|
|
83
|
+
4. Replace DB utility files with re-exports from `common-utils`.
|
|
84
|
+
5. Update `App.ts` to call `*SetOTel()` and `*Init()` from `common-utils`.
|
package/README.md
ADDED
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
# @devopsplaybook.io/common-utils
|
|
2
|
+
|
|
3
|
+
Shared utility modules for [devopsplaybook.io](https://github.com/devopsplaybook-io) projects. Provides OpenTelemetry-aware database access (SQLite and PostgreSQL), configuration loading, telemetry context management, and reusable GitHub Actions CI/CD workflows.
|
|
4
|
+
|
|
5
|
+
## Contents
|
|
6
|
+
|
|
7
|
+
- [Node.js Library](#nodejs-library)
|
|
8
|
+
- [Installation](#installation)
|
|
9
|
+
- [Modules](#modules)
|
|
10
|
+
- [Quick Start](#quick-start)
|
|
11
|
+
- [Shared GitHub Actions Workflows](#shared-github-actions-workflows)
|
|
12
|
+
- [Reusable Workflows](#reusable-workflows)
|
|
13
|
+
- [Adopting in Your Project](#adopting-in-your-project)
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Node.js Library
|
|
18
|
+
|
|
19
|
+
### Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @devopsplaybook.io/common-utils
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Peer dependencies** (installed automatically):
|
|
26
|
+
|
|
27
|
+
| Package | Purpose |
|
|
28
|
+
| ------------------------------- | --------------------------------------------------- |
|
|
29
|
+
| `@devopsplaybook.io/otel-utils` | `StandardTracer`, `StandardLogger`, `StandardMeter` |
|
|
30
|
+
| `@opentelemetry/api` | OTel API (`SpanStatusCode`) |
|
|
31
|
+
| `@opentelemetry/sdk-trace-base` | `Span` type |
|
|
32
|
+
| `better-sqlite3` | Synchronous SQLite driver |
|
|
33
|
+
| `pg` | PostgreSQL client (`Pool`) |
|
|
34
|
+
| `fs-extra` | File system helpers |
|
|
35
|
+
| `uuid` | UUID generation for JWT keys |
|
|
36
|
+
|
|
37
|
+
### Modules
|
|
38
|
+
|
|
39
|
+
#### `OTelContext` -- Telemetry Singleton Factory
|
|
40
|
+
|
|
41
|
+
Creates an isolated set of OTel singletons (tracer, meter, logger) for a server process.
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { createOTelContext } from "@devopsplaybook.io/common-utils";
|
|
45
|
+
import { StandardTracer, StandardMeter } from "@devopsplaybook.io/otel-utils";
|
|
46
|
+
|
|
47
|
+
const otel = createOTelContext();
|
|
48
|
+
otel.OTelSetTracer(new StandardTracer(config));
|
|
49
|
+
otel.OTelSetMeter(new StandardMeter(config));
|
|
50
|
+
otel.OTelLogger().initOTel(config);
|
|
51
|
+
|
|
52
|
+
// Later:
|
|
53
|
+
const tracer = otel.OTelTracer();
|
|
54
|
+
const span = tracer.startSpan("my-operation");
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
| Export | Description |
|
|
58
|
+
| --------------------- | --------------------------------------------------------------------------------------------- |
|
|
59
|
+
| `createOTelContext()` | Returns `{ OTelTracer, OTelSetTracer, OTelMeter, OTelSetMeter, OTelLogger, OTelRequestSpan }` |
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
#### `ConfigBase` -- Configuration Base Class
|
|
64
|
+
|
|
65
|
+
Abstract class implementing the three-layer override strategy:
|
|
66
|
+
|
|
67
|
+
1. **Environment variable** (highest priority)
|
|
68
|
+
2. **config.json** file value
|
|
69
|
+
3. **Default** declared on the class property
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import { ConfigBase } from "@devopsplaybook.io/common-utils";
|
|
73
|
+
|
|
74
|
+
class MyConfig extends ConfigBase {
|
|
75
|
+
public MY_SETTING = "default";
|
|
76
|
+
|
|
77
|
+
constructor() {
|
|
78
|
+
super("my-service");
|
|
79
|
+
this.addConfigField({ field: "MY_SETTING" });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async reload(): Promise<void> {
|
|
83
|
+
await super.reload((msg) => console.log(msg));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const config = new MyConfig();
|
|
88
|
+
await config.reload();
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Built-in fields** (pre-registered, no `addConfigField` needed):
|
|
92
|
+
|
|
93
|
+
| Field | Default | Sensitive |
|
|
94
|
+
| -------------------------------------- | -------------------- | ----------------------------------- |
|
|
95
|
+
| `API_PORT` | `8080` | No |
|
|
96
|
+
| `JWT_VALIDITY_DURATION` | `8035200` (3 months) | No |
|
|
97
|
+
| `CORS_POLICY_ORIGIN` | `""` | No |
|
|
98
|
+
| `DATA_DIR` | `/data` | No |
|
|
99
|
+
| `JWT_KEY` | `uuidv4()` | Yes |
|
|
100
|
+
| `LOG_LEVEL` | `"info"` | No |
|
|
101
|
+
| `DATABASE_TYPE` | `"sqlite"` | No |
|
|
102
|
+
| `DATABASE_POSTGRES_HOST` | `""` | No |
|
|
103
|
+
| `DATABASE_POSTGRES_PORT` | `5432` | No |
|
|
104
|
+
| `DATABASE_POSTGRES_USER` | `""` | No |
|
|
105
|
+
| `DATABASE_POSTGRES_PASSWORD` | `""` | Yes |
|
|
106
|
+
| `DATABASE_POSTGRES_DATABASE` | `""` | No |
|
|
107
|
+
| All `OPENTELEMETRY_COLLECTOR_*` fields | Various | No (except `_AUTHORIZATION_HEADER`) |
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
#### `SqlDbUtils` -- SQLite Database Access
|
|
112
|
+
|
|
113
|
+
Synchronous database operations using `better-sqlite3`, with OTel tracing on every call.
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
import {
|
|
117
|
+
SqlDbUtilsSetOTel,
|
|
118
|
+
SqlDbUtilsInit,
|
|
119
|
+
SqlDbUtilsExecSQL,
|
|
120
|
+
SqlDbUtilsQuerySQL,
|
|
121
|
+
} from "@devopsplaybook.io/common-utils";
|
|
122
|
+
|
|
123
|
+
// At startup
|
|
124
|
+
SqlDbUtilsSetOTel(tracer, logger);
|
|
125
|
+
await SqlDbUtilsInit(span, config, path.resolve(__dirname, "../sql"));
|
|
126
|
+
|
|
127
|
+
// Read
|
|
128
|
+
const rows = SqlDbUtilsQuerySQL(span, "SELECT * FROM users WHERE id = ?", [
|
|
129
|
+
userId,
|
|
130
|
+
]);
|
|
131
|
+
|
|
132
|
+
// Write
|
|
133
|
+
const changes = SqlDbUtilsExecSQL(
|
|
134
|
+
span,
|
|
135
|
+
"UPDATE users SET name = ? WHERE id = ?",
|
|
136
|
+
[name, userId],
|
|
137
|
+
);
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
| Export | Signature | Description |
|
|
141
|
+
| ----------------------- | ------------------------------ | ------------------------------------------------ |
|
|
142
|
+
| `SqlDbUtilsSetOTel` | `(tracer, logger)` | Inject OTel instances |
|
|
143
|
+
| `SqlDbUtilsInit` | `(span, config, sqlDir)` | Open DB and run migrations |
|
|
144
|
+
| `SqlDbUtilsExecSQL` | `(span, sql, params?)` | Execute write, returns `number` (changes) |
|
|
145
|
+
| `SqlDbUtilsQuerySQL` | `(span, sql, params?, debug?)` | Execute read, returns `any[]` |
|
|
146
|
+
| `SqlDbUtilsExecSQLFile` | `(span, filename)` | Execute an entire SQL file |
|
|
147
|
+
| `SqlDbUtilsGetDatabase` | `()` | Returns the `better-sqlite3` `Database` instance |
|
|
148
|
+
|
|
149
|
+
**Migration convention**: Files named `init-NNNN.sql` in `sqlDir`, applied in order. A `metadata` table tracks applied versions for idempotent re-runs. `init-0000.sql` must exist (creates the `metadata` table).
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
#### `PostgresDbUtils` -- PostgreSQL Database Access
|
|
154
|
+
|
|
155
|
+
Async (Promise-based) database operations using `pg.Pool`, with OTel tracing.
|
|
156
|
+
|
|
157
|
+
| Export | Signature | Description |
|
|
158
|
+
| ---------------------------------- | ------------------------------ | ---------------------------------------- |
|
|
159
|
+
| `PostgresDbUtilsSetOTel` | `(tracer, logger)` | Inject OTel instances |
|
|
160
|
+
| `PostgresDbUtilsInit` | `(span, config, sqlDir)` | Create pool and run migrations |
|
|
161
|
+
| `PostgresDbUtilsExecSQL` | `(span, sql, params?)` | Execute write, returns `Promise<number>` |
|
|
162
|
+
| `PostgresDbUtilsQuerySQL` | `(span, sql, params?, debug?)` | Execute read, returns `Promise<any[]>` |
|
|
163
|
+
| `PostgresDbUtilsExecSQLFile` | `(span, filename)` | Execute an entire SQL file |
|
|
164
|
+
| `PostgresDbUtilsGetPool` | `()` | Returns the `pg.Pool` instance |
|
|
165
|
+
| `PostgresDbUtilsTransactionStart` | `(span)` | Begin a transaction (`BEGIN`) |
|
|
166
|
+
| `PostgresDbUtilsTransactionCommit` | `(span)` | Commit a transaction (`COMMIT`) |
|
|
167
|
+
|
|
168
|
+
Pool defaults: `max: 20`, `idleTimeoutMillis: 30000`, `connectionTimeoutMillis: 10000`.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
#### `DbUtils` -- Unified Database Facade
|
|
173
|
+
|
|
174
|
+
Dispatches to SQLite or Postgres based on `config.DATABASE_TYPE`. Write SQL using SQLite-style `?` placeholders; they are automatically converted to `$1, $2, ...` for Postgres.
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
import {
|
|
178
|
+
DbUtilsSetOTel,
|
|
179
|
+
DbUtilsInit,
|
|
180
|
+
DbUtilsExecSQL,
|
|
181
|
+
DbUtilsQuerySQL,
|
|
182
|
+
} from "@devopsplaybook.io/common-utils";
|
|
183
|
+
|
|
184
|
+
DbUtilsSetOTel(tracer, logger);
|
|
185
|
+
await DbUtilsInit(span, config, sqlDir);
|
|
186
|
+
|
|
187
|
+
// Works with both SQLite and Postgres -- placeholders auto-converted
|
|
188
|
+
const rows = DbUtilsQuerySQL(span, "SELECT * FROM users WHERE id = ?", [
|
|
189
|
+
userId,
|
|
190
|
+
]);
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
| Export | Description |
|
|
194
|
+
| --------------------------------------------- | -------------------------------------------- |
|
|
195
|
+
| `DbUtilsSetOTel(tracer, logger)` | Set OTel on both backends |
|
|
196
|
+
| `DbUtilsInit(span, config, sqlDir)` | Init the active backend |
|
|
197
|
+
| `DbUtilsExecSQL(span, sql, params?)` | Write with auto-conversion |
|
|
198
|
+
| `DbUtilsQuerySQL(span, sql, params?, debug?)` | Read with auto-conversion |
|
|
199
|
+
| `DbUtilsGetDatabase()` | Returns native handle (`Database` or `Pool`) |
|
|
200
|
+
| `DbUtilsGetType()` | Returns `"sqlite"` or `"postgres"` |
|
|
201
|
+
| `convertToPostgresPlaceholders(sql)` | Converts `?` to `$1, $2, ...` |
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
#### `DbUtilsNoTelemetry` -- High-Throughput Path
|
|
206
|
+
|
|
207
|
+
Same SQL operations but **without** creating OTel spans. Use on hot paths where span overhead matters.
|
|
208
|
+
|
|
209
|
+
```ts
|
|
210
|
+
import {
|
|
211
|
+
DbUtilsNoTelemetrySetLogger,
|
|
212
|
+
DbUtilsNoTelemetryExecSQL,
|
|
213
|
+
DbUtilsNoTelemetryBatchInsert,
|
|
214
|
+
} from "@devopsplaybook.io/common-utils";
|
|
215
|
+
|
|
216
|
+
DbUtilsNoTelemetrySetLogger(logger);
|
|
217
|
+
|
|
218
|
+
DbUtilsNoTelemetryExecSQL("INSERT INTO log (msg) VALUES (?)", [message]);
|
|
219
|
+
|
|
220
|
+
// Batch insert: builds multi-row INSERT
|
|
221
|
+
DbUtilsNoTelemetryBatchInsert(
|
|
222
|
+
"INTO prices (token, price, ts)", // table + columns
|
|
223
|
+
3, // number of columns
|
|
224
|
+
[
|
|
225
|
+
["BTC", 65000, "2024-01-01"],
|
|
226
|
+
["ETH", 3200, "2024-01-01"],
|
|
227
|
+
], // rows
|
|
228
|
+
);
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
| Export | Description |
|
|
232
|
+
| --------------------------------------------------------- | --------------------------------- |
|
|
233
|
+
| `DbUtilsNoTelemetrySetLogger(logger)` | Inject logger for error reporting |
|
|
234
|
+
| `DbUtilsNoTelemetryExecSQL(sql, params?)` | Write without spans |
|
|
235
|
+
| `DbUtilsNoTelemetryQuerySQL(sql, params?, debug?)` | Read without spans |
|
|
236
|
+
| `DbUtilsNoTelemetryBatchInsert(tableCols, numCols, rows)` | Optimized multi-row INSERT |
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
#### `SystemCommand` -- Shell Command Execution
|
|
241
|
+
|
|
242
|
+
```ts
|
|
243
|
+
import { SystemCommandExecute } from "@devopsplaybook.io/common-utils";
|
|
244
|
+
|
|
245
|
+
const output = await SystemCommandExecute("ls -la /tmp", { cwd: "/home" });
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
#### `Timeout` -- Promise-based Delay
|
|
249
|
+
|
|
250
|
+
```ts
|
|
251
|
+
import { TimeoutWait } from "@devopsplaybook.io/common-utils";
|
|
252
|
+
|
|
253
|
+
await TimeoutWait(5000); // wait 5 seconds
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Quick Start
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
import {
|
|
260
|
+
createOTelContext,
|
|
261
|
+
ConfigBase,
|
|
262
|
+
DbUtilsSetOTel,
|
|
263
|
+
DbUtilsInit,
|
|
264
|
+
} from "@devopsplaybook.io/common-utils";
|
|
265
|
+
import { StandardTracer, StandardMeter } from "@devopsplaybook.io/otel-utils";
|
|
266
|
+
|
|
267
|
+
// 1. Config
|
|
268
|
+
class AppConfig extends ConfigBase {
|
|
269
|
+
constructor() {
|
|
270
|
+
super("my-app");
|
|
271
|
+
}
|
|
272
|
+
async reload() {
|
|
273
|
+
await super.reload((m) => console.log(m));
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
const config = new AppConfig();
|
|
277
|
+
await config.reload();
|
|
278
|
+
|
|
279
|
+
// 2. OTel
|
|
280
|
+
const otel = createOTelContext();
|
|
281
|
+
otel.OTelSetTracer(new StandardTracer(config));
|
|
282
|
+
otel.OTelSetMeter(new StandardMeter(config));
|
|
283
|
+
otel.OTelLogger().initOTel(config);
|
|
284
|
+
|
|
285
|
+
// 3. Database
|
|
286
|
+
DbUtilsSetOTel(otel.OTelTracer(), otel.OTelLogger());
|
|
287
|
+
const span = otel.OTelTracer().startSpan("init");
|
|
288
|
+
await DbUtilsInit(span, config, path.resolve(__dirname, "../sql"));
|
|
289
|
+
span.end();
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## Shared GitHub Actions Workflows
|
|
295
|
+
|
|
296
|
+
The `.github/workflows/` directory contains **reusable workflows** that other repositories can call to standardize their CI/CD pipelines.
|
|
297
|
+
|
|
298
|
+
### Reusable Workflows
|
|
299
|
+
|
|
300
|
+
| Workflow | File | Trigger | Purpose |
|
|
301
|
+
| --------------- | -------------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
302
|
+
| **NPM Merge** | `reusable-npm-merge.yml` | `workflow_call` | Lint, test, build, and publish a release to npm on merge to main. Uploads coverage to Quality Dashboard. Only publishes if the version doesn't already exist. |
|
|
303
|
+
| **NPM PR** | `reusable-npm-pr.yml` | `workflow_call` | Lint, test, and build on PR. Publishes a **beta** version tagged `beta` and comments the PR with install instructions. |
|
|
304
|
+
| **NPM Upgrade** | `reusable-npm-upgrade.yml` | `workflow_call` | Runs `npm-check-updates -u`, bumps the patch version, and opens a PR. Supports monorepo sub-folders via `npm_services` input. |
|
|
305
|
+
| **PR Verify** | `reusable-pr-verify.yml` | `workflow_call` | Matrix build/lint/test for multiple Node.js apps plus multi-platform Docker build. For monorepos with Docker images. |
|
|
306
|
+
| **Merge Build** | `reusable-merge-build.yml` | `workflow_call` | Same as PR Verify but on merge. Tags Docker images with `latest`, version, major, and minor tags. |
|
|
307
|
+
|
|
308
|
+
### Inputs and Secrets
|
|
309
|
+
|
|
310
|
+
#### NPM Workflows (`reusable-npm-merge`, `reusable-npm-pr`)
|
|
311
|
+
|
|
312
|
+
| Input | Required | Default | Description |
|
|
313
|
+
| ------------------ | -------- | ------- | --------------------------------------------------------- |
|
|
314
|
+
| `node_version` | No | `"18"` | Node.js version |
|
|
315
|
+
| `npm_package_name` | Yes | -- | npm package name (e.g. `@devopsplaybook.io/common-utils`) |
|
|
316
|
+
|
|
317
|
+
| Secret | Required | Description |
|
|
318
|
+
| ------------------------- | -------- | ----------------------------------------- |
|
|
319
|
+
| `NPM_TOKEN` | Yes | npm publish token |
|
|
320
|
+
| `QUALITY_DASHBOARD_URL` | No | Quality Dashboard URL for coverage upload |
|
|
321
|
+
| `QUALITY_DASHBOARD_TOKEN` | No | Quality Dashboard upload token |
|
|
322
|
+
|
|
323
|
+
#### NPM Upgrade (`reusable-npm-upgrade`)
|
|
324
|
+
|
|
325
|
+
| Input | Required | Default | Description |
|
|
326
|
+
| -------------- | -------- | --------------------------------------- | ------------------------------------------------------------- |
|
|
327
|
+
| `npm_services` | Yes | -- | JSON array of sub-folder paths (e.g. `'["server","client"]'`) |
|
|
328
|
+
| `pr_branch` | No | `feature/YYYY.MM.DD-dependency-updates` | Branch name for the PR |
|
|
329
|
+
|
|
330
|
+
#### Docker/Node Workflows (`reusable-pr-verify`, `reusable-merge-build`)
|
|
331
|
+
|
|
332
|
+
| Input | Required | Default | Description |
|
|
333
|
+
| ---------------------- | -------- | ---------------------------- | ------------------------------------- |
|
|
334
|
+
| `docker_platforms` | No | `linux/arm64/v8,linux/amd64` | Docker build platforms |
|
|
335
|
+
| `node_app_directories` | No | `""` | JSON array of Node.js app directories |
|
|
336
|
+
| `node_version` | No | `"22"` | Node.js version |
|
|
337
|
+
|
|
338
|
+
| Secret | Required | Description |
|
|
339
|
+
| ------------------------- | -------- | ------------------------------ |
|
|
340
|
+
| `DOCKER_HUB_USERNAME` | Yes | Docker Hub username |
|
|
341
|
+
| `DOCKER_HUB_ACCESS_TOKEN` | Yes | Docker Hub access token |
|
|
342
|
+
| `QUALITY_DASHBOARD_URL` | No | Quality Dashboard URL |
|
|
343
|
+
| `QUALITY_DASHBOARD_TOKEN` | No | Quality Dashboard upload token |
|
|
344
|
+
|
|
345
|
+
### Adopting in Your Project
|
|
346
|
+
|
|
347
|
+
Create a caller workflow in `.github/workflows/main-build.yml`:
|
|
348
|
+
|
|
349
|
+
```yaml
|
|
350
|
+
name: Main Build
|
|
351
|
+
on:
|
|
352
|
+
push:
|
|
353
|
+
branches: ["main"]
|
|
354
|
+
jobs:
|
|
355
|
+
npm-merge:
|
|
356
|
+
uses: devopsplaybook-io/common-utils/.github/workflows/reusable-npm-merge.yml@main
|
|
357
|
+
with:
|
|
358
|
+
npm_package_name: "@your-scope/your-package"
|
|
359
|
+
secrets:
|
|
360
|
+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
And `.github/workflows/pr-check.yml`:
|
|
364
|
+
|
|
365
|
+
```yaml
|
|
366
|
+
name: PR Check
|
|
367
|
+
on:
|
|
368
|
+
pull_request:
|
|
369
|
+
branches: ["main"]
|
|
370
|
+
permissions:
|
|
371
|
+
contents: read
|
|
372
|
+
pull-requests: write
|
|
373
|
+
issues: write
|
|
374
|
+
jobs:
|
|
375
|
+
npm-pr:
|
|
376
|
+
uses: devopsplaybook-io/common-utils/.github/workflows/reusable-npm-pr.yml@main
|
|
377
|
+
with:
|
|
378
|
+
npm_package_name: "@your-scope/your-package"
|
|
379
|
+
secrets:
|
|
380
|
+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
And `.github/workflows/npm-upgrade.yml` for weekly dependency updates:
|
|
384
|
+
|
|
385
|
+
```yaml
|
|
386
|
+
name: NPM Upgrade
|
|
387
|
+
on:
|
|
388
|
+
schedule:
|
|
389
|
+
- cron: "0 6 * * 1" # Monday 6am UTC
|
|
390
|
+
jobs:
|
|
391
|
+
npm-upgrade:
|
|
392
|
+
uses: devopsplaybook-io/common-utils/.github/workflows/reusable-npm-upgrade.yml@main
|
|
393
|
+
permissions:
|
|
394
|
+
contents: write
|
|
395
|
+
pull-requests: write
|
|
396
|
+
with:
|
|
397
|
+
npm_services: "[]"
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
---
|
|
401
|
+
|
|
402
|
+
## Development
|
|
403
|
+
|
|
404
|
+
```bash
|
|
405
|
+
npm install
|
|
406
|
+
npm run build # TypeScript compilation -> dist/
|
|
407
|
+
npm run lint # ESLint (strict + stylistic)
|
|
408
|
+
npm run test # Jest with coverage
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
## License
|
|
412
|
+
|
|
413
|
+
ISC
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from "./src/OTelContext";
|
|
2
|
+
export * from "./src/ConfigBase";
|
|
3
|
+
export * from "./src/DbUtils";
|
|
4
|
+
export * from "./src/DbUtilsNoTelemetry";
|
|
5
|
+
export * from "./src/SqlDbUtils";
|
|
6
|
+
export * from "./src/PostgresDbUtils";
|
|
7
|
+
export * from "./src/SystemCommand";
|
|
8
|
+
export * from "./src/Timeout";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./src/OTelContext"), exports);
|
|
18
|
+
__exportStar(require("./src/ConfigBase"), exports);
|
|
19
|
+
__exportStar(require("./src/DbUtils"), exports);
|
|
20
|
+
__exportStar(require("./src/DbUtilsNoTelemetry"), exports);
|
|
21
|
+
__exportStar(require("./src/SqlDbUtils"), exports);
|
|
22
|
+
__exportStar(require("./src/PostgresDbUtils"), exports);
|
|
23
|
+
__exportStar(require("./src/SystemCommand"), exports);
|
|
24
|
+
__exportStar(require("./src/Timeout"), exports);
|