@nodii/db-rls 0.2.1 → 0.3.1
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/dist/configure.d.ts +25 -8
- package/dist/configure.d.ts.map +1 -1
- package/dist/configure.js +48 -17
- package/dist/configure.js.map +1 -1
- package/dist/context.d.ts +8 -6
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +27 -6
- package/dist/context.js.map +1 -1
- package/dist/drizzle-client.d.ts +9 -2
- package/dist/drizzle-client.d.ts.map +1 -1
- package/dist/drizzle-client.js +28 -2
- package/dist/drizzle-client.js.map +1 -1
- package/dist/drizzle-policies.d.ts +23 -0
- package/dist/drizzle-policies.d.ts.map +1 -0
- package/dist/drizzle-policies.js +114 -0
- package/dist/drizzle-policies.js.map +1 -0
- package/dist/index.d.ts +8 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -9
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +14 -5
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +20 -4
- package/dist/init.js.map +1 -1
- package/dist/metrics.d.ts +28 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +263 -0
- package/dist/metrics.js.map +1 -0
- package/dist/migrate-guard.d.ts +14 -0
- package/dist/migrate-guard.d.ts.map +1 -0
- package/dist/migrate-guard.js +33 -0
- package/dist/migrate-guard.js.map +1 -0
- package/dist/pg-pool-adapter.d.ts.map +1 -1
- package/dist/pg-pool-adapter.js +78 -25
- package/dist/pg-pool-adapter.js.map +1 -1
- package/dist/postgres-js-client.d.ts +60 -0
- package/dist/postgres-js-client.d.ts.map +1 -0
- package/dist/postgres-js-client.js +223 -0
- package/dist/postgres-js-client.js.map +1 -0
- package/dist/types.d.ts +29 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +30 -0
- package/dist/types.js.map +1 -1
- package/package.json +20 -5
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,wCAAwC;AACxC,EAAE;AACF,oEAAoE;AACpE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,wCAAwC;AACxC,EAAE;AACF,oEAAoE;AACpE,oEAAoE;AACpE,wDAAwD;AACxD,6CAA6C;AAC7C,EAAE;AACF,yCAAyC;AACzC,sEAAsE;AACtE,wCAAwC;AACxC,sDAAsD;AACtD,+DAA+D;AAC/D,4EAA4E;AAC5E,gDAAgD;AAChD,mEAAmE;AACnE,mDAAmD;AACnD,wEAAwE;AACxE,2EAA2E;AAC3E,+EAA+E;AAC/E,wEAAwE;AACxE,sEAAsE;AACtE,6EAA6E;AAC7E,6EAA6E;AAC7E,sCAAsC;AACtC,uEAAuE;AACvE,0EAA0E;AAC1E,8EAA8E;AAC9E,kFAAkF;AAClF,yEAAyE;AACzE,mEAAmE;AACnE,0EAA0E;AAC1E,qCAAqC;AAErC,MAAM,CAAC,MAAM,QAAQ,GAAG,QAAQ,CAAC;AACjC,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC;AAE/B,OAAO,EACL,SAAS,EACT,YAAY,EACZ,cAAc,EACd,WAAW,EACX,cAAc,GACf,MAAM,QAAQ,CAAC;AAGhB,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,aAAa,CAAC;AAMrB,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGjD,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,gBAAgB,EAChB,YAAY,GACb,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,kBAAkB,CAAC;AAI1B,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAEvD,OAAO,EACL,eAAe,EACf,sBAAsB,EACtB,yBAAyB,EACzB,iBAAiB,GAClB,MAAM,eAAe,CAAC;AAGvB,OAAO,EAAE,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAGhE,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,eAAe,CAAC;AAGvB,qEAAqE;AACrE,oEAAoE;AACpE,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEpE,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAGlD,OAAO,EACL,wBAAwB,EACxB,+BAA+B,GAChC,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,gBAAgB,EAChB,UAAU,EACV,mBAAmB,EACnB,oBAAoB,EACpB,0BAA0B,EAC1B,eAAe,EACf,qBAAqB,GACtB,MAAM,SAAS,CAAC"}
|
package/dist/init.d.ts
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
|
-
import { type PgPool, type PoolKind } from "./types";
|
|
1
|
+
import { type DbRlsMode, type PgPool, type PoolKind } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Process-local config. `services` and `authenticated` are optional ONLY
|
|
4
|
+
* in 2-pool mode; in 3-pool mode (the default) the type guard runs at
|
|
5
|
+
* init time and throws `DbRlsConfigError` if either is missing.
|
|
6
|
+
*/
|
|
2
7
|
export interface InitDbRlsConfig {
|
|
3
8
|
owner: PgPool;
|
|
4
|
-
services
|
|
5
|
-
authenticated
|
|
9
|
+
services?: PgPool;
|
|
10
|
+
authenticated?: PgPool;
|
|
11
|
+
/** Defaults to `"3-pool"`. Set `"2-pool"` for nodii-auth-shape services
|
|
12
|
+
* per 08-rls § 7 — owner + (optional) services only, no authenticated. */
|
|
13
|
+
mode?: DbRlsMode;
|
|
6
14
|
}
|
|
7
15
|
export interface ResolvedDbRls {
|
|
8
16
|
owner: PgPool;
|
|
9
|
-
services: PgPool;
|
|
10
|
-
authenticated: PgPool;
|
|
17
|
+
services: PgPool | null;
|
|
18
|
+
authenticated: PgPool | null;
|
|
19
|
+
mode: DbRlsMode;
|
|
11
20
|
}
|
|
12
21
|
export declare function initDbRls(config: InitDbRlsConfig): void;
|
|
13
22
|
export declare function requireDbRls(): ResolvedDbRls;
|
package/dist/init.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AAUA,OAAO,EAGL,KAAK,SAAS,EACd,KAAK,MAAM,EAEX,KAAK,QAAQ,EACd,MAAM,SAAS,CAAC;AAEjB;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;+EAC2E;IAC3E,IAAI,CAAC,EAAE,SAAS,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,IAAI,EAAE,SAAS,CAAC;CACjB;AAID,wBAAgB,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CA8BvD;AAED,wBAAgB,YAAY,IAAI,aAAa,CAK5C;AAED,wBAAgB,cAAc,IAAI,aAAa,GAAG,IAAI,CAErD;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAOlD;AAED,+DAA+D;AAC/D,wBAAgB,cAAc,IAAI,IAAI,CAErC"}
|
package/dist/init.js
CHANGED
|
@@ -2,18 +2,34 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Spec § 5.2. Caller supplies one PgPool per role. Subsequent calls warn +
|
|
4
4
|
// no-op (first-init wins). Public APIs throw `DbRlsNotInitialized` before init.
|
|
5
|
-
|
|
5
|
+
//
|
|
6
|
+
// v0.2.x adds the 2-pool exception per `08-rls-doctrine § 7` — nodii-auth
|
|
7
|
+
// boots without `services`/`authenticated` until its admin portal lands.
|
|
8
|
+
// Mode is explicit (`"3-pool"` | `"2-pool"`) so a missing pool is never
|
|
9
|
+
// silent.
|
|
10
|
+
import { DbRlsConfigError, DbRlsNotInitialized, PoolUnavailable, } from "./types";
|
|
6
11
|
let resolved = null;
|
|
7
12
|
export function initDbRls(config) {
|
|
8
13
|
if (resolved) {
|
|
9
|
-
// eslint-disable-next-line no-console
|
|
10
14
|
console.warn("[@nodii/db-rls] initDbRls() called more than once; ignoring subsequent call.");
|
|
11
15
|
return;
|
|
12
16
|
}
|
|
17
|
+
const mode = config.mode ?? "3-pool";
|
|
18
|
+
if (mode === "3-pool") {
|
|
19
|
+
if (!config.services || !config.authenticated) {
|
|
20
|
+
throw new DbRlsConfigError("initDbRls: 3-pool mode requires owner + services + authenticated pools. Either supply both, or set mode: '2-pool' (nodii-auth shape per 08-rls § 7).");
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
// 2-pool: services optional, authenticated forbidden (the whole point
|
|
24
|
+
// of the exception is that nodii-auth has no user-context surface yet).
|
|
25
|
+
if (mode === "2-pool" && config.authenticated) {
|
|
26
|
+
throw new DbRlsConfigError("initDbRls: 2-pool mode must not supply an 'authenticated' pool — the 2-pool exception per 08-rls § 7 covers services without user-context. Use mode: '3-pool' if you have an authenticated pool.");
|
|
27
|
+
}
|
|
13
28
|
resolved = {
|
|
14
29
|
owner: config.owner,
|
|
15
|
-
services: config.services,
|
|
16
|
-
authenticated: config.authenticated,
|
|
30
|
+
services: config.services ?? null,
|
|
31
|
+
authenticated: config.authenticated ?? null,
|
|
32
|
+
mode,
|
|
17
33
|
};
|
|
18
34
|
}
|
|
19
35
|
export function requireDbRls() {
|
package/dist/init.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.js","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,EAAE;AACF,2EAA2E;AAC3E,gFAAgF;
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,EAAE;AACF,2EAA2E;AAC3E,gFAAgF;AAChF,EAAE;AACF,0EAA0E;AAC1E,yEAAyE;AACzE,wEAAwE;AACxE,UAAU;AAEV,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EAGnB,eAAe,GAEhB,MAAM,SAAS,CAAC;AAuBjB,IAAI,QAAQ,GAAyB,IAAI,CAAC;AAE1C,MAAM,UAAU,SAAS,CAAC,MAAuB;IAC/C,IAAI,QAAQ,EAAE,CAAC;QACb,OAAO,CAAC,IAAI,CACV,8EAA8E,CAC/E,CAAC;QACF,OAAO;IACT,CAAC;IAED,MAAM,IAAI,GAAc,MAAM,CAAC,IAAI,IAAI,QAAQ,CAAC;IAChD,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;QACtB,IAAI,CAAC,MAAM,CAAC,QAAQ,IAAI,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC;YAC9C,MAAM,IAAI,gBAAgB,CACxB,sJAAsJ,CACvJ,CAAC;QACJ,CAAC;IACH,CAAC;IACD,sEAAsE;IACtE,wEAAwE;IACxE,IAAI,IAAI,KAAK,QAAQ,IAAI,MAAM,CAAC,aAAa,EAAE,CAAC;QAC9C,MAAM,IAAI,gBAAgB,CACxB,kMAAkM,CACnM,CAAC;IACJ,CAAC;IAED,QAAQ,GAAG;QACT,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,QAAQ,EAAE,MAAM,CAAC,QAAQ,IAAI,IAAI;QACjC,aAAa,EAAE,MAAM,CAAC,aAAa,IAAI,IAAI;QAC3C,IAAI;KACL,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,mBAAmB,EAAE,CAAC;IAClC,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,IAAc;IACxC,MAAM,GAAG,GAAG,YAAY,EAAE,CAAC;IAC3B,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,eAAe,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,cAAc;IAC5B,QAAQ,GAAG,IAAI,CAAC;AAClB,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type MetricVocabularyRegistry } from "@nodii/telemetry";
|
|
2
|
+
import type { PoolKind } from "./types";
|
|
3
|
+
export declare const DB_RLS_METRIC_VOCABULARY: MetricVocabularyRegistry;
|
|
4
|
+
export declare function recordAcquisition(pool: PoolKind, outcome: "success" | "failure"): void;
|
|
5
|
+
export declare function recordAcquisitionDuration(pool: PoolKind, durationMs: number): void;
|
|
6
|
+
export declare function recordPoolSize(pool: PoolKind, state: "active" | "idle" | "waiting", size: number): void;
|
|
7
|
+
export declare function recordTxDuration(pool: PoolKind, outcome: "committed" | "rolled_back" | "error", durationMs: number): void;
|
|
8
|
+
export declare function recordCrossPoolNesting(outer: PoolKind, inner: PoolKind, reason: string): void;
|
|
9
|
+
export declare function recordCrossPoolViolation(outer: PoolKind, attemptedInner: PoolKind): void;
|
|
10
|
+
export declare function recordOwnerContextInvocation(outcome: "allowed" | "refused"): void;
|
|
11
|
+
export declare function recordGucSetFailure(guc: string): void;
|
|
12
|
+
/**
|
|
13
|
+
* Wraps `fn` in a `db.rls.tx` span scoped to `pool`. The span stamps
|
|
14
|
+
* the final outcome attribute (`committed` / `rolled_back` / `error`)
|
|
15
|
+
* via the `setOutcome` callback the caller passes through. On thrown
|
|
16
|
+
* exceptions the tracer.span lifecycle marks status=ERROR + records
|
|
17
|
+
* the exception — no extra work needed here.
|
|
18
|
+
*/
|
|
19
|
+
export declare function withTxSpan<T>(pool: PoolKind, fn: (setOutcome: (o: "committed" | "rolled_back" | "error") => void) => Promise<T>): Promise<T>;
|
|
20
|
+
/**
|
|
21
|
+
* Emits a one-shot `db.rls.pool.acquire` span. Caller invokes this only
|
|
22
|
+
* when acquisition time exceeds the slow threshold (default 50ms per
|
|
23
|
+
* § 5.10) — keeps span volume bounded.
|
|
24
|
+
*/
|
|
25
|
+
export declare function emitSlowAcquireSpan(pool: PoolKind, durationMs: number): void;
|
|
26
|
+
/** Test-only: reset the one-shot warn ledger so each test runs clean. */
|
|
27
|
+
export declare function _resetMetricsWarnLedgerForTests(): void;
|
|
28
|
+
//# sourceMappingURL=metrics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../src/metrics.ts"],"names":[],"mappings":"AAmBA,OAAO,EAEL,KAAK,wBAAwB,EAI9B,MAAM,kBAAkB,CAAC;AAE1B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAWxC,eAAO,MAAM,wBAAwB,EAAE,wBAsDtC,CAAC;AAuFF,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,QAAQ,EACd,OAAO,EAAE,SAAS,GAAG,SAAS,GAC7B,IAAI,CAKN;AAED,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,QAAQ,EACd,UAAU,EAAE,MAAM,GACjB,IAAI,CAMN;AAED,wBAAgB,cAAc,CAC5B,IAAI,EAAE,QAAQ,EACd,KAAK,EAAE,QAAQ,GAAG,MAAM,GAAG,SAAS,EACpC,IAAI,EAAE,MAAM,GACX,IAAI,CAMN;AAED,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,QAAQ,EACd,OAAO,EAAE,WAAW,GAAG,aAAa,GAAG,OAAO,EAC9C,UAAU,EAAE,MAAM,GACjB,IAAI,CAUN;AAED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,QAAQ,EACf,KAAK,EAAE,QAAQ,EACf,MAAM,EAAE,MAAM,GACb,IAAI,CAKN;AAED,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,QAAQ,EACf,cAAc,EAAE,QAAQ,GACvB,IAAI,CAKN;AAED,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,SAAS,GAAG,SAAS,GAC7B,IAAI,CAKN;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAKrD;AAQD;;;;;;GAMG;AACH,wBAAsB,UAAU,CAAC,CAAC,EAChC,IAAI,EAAE,QAAQ,EACd,EAAE,EAAE,CACF,UAAU,EAAE,CAAC,CAAC,EAAE,WAAW,GAAG,aAAa,GAAG,OAAO,KAAK,IAAI,KAC3D,OAAO,CAAC,CAAC,CAAC,GACd,OAAO,CAAC,CAAC,CAAC,CA8BZ;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAgB5E;AAKD,yEAAyE;AACzE,wBAAgB,+BAA+B,IAAI,IAAI,CAGtD"}
|
package/dist/metrics.js
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
// 9 locked db-rls metrics + 2 spans per `db-rls.md § 5.10` + doctrine
|
|
2
|
+
// `08-rls-doctrine.md § 12.4`. Wired through `@nodii/telemetry`'s
|
|
3
|
+
// vocabulary-bound `metrics.counter` / `metrics.histogram` API.
|
|
4
|
+
//
|
|
5
|
+
// Behavioral discipline:
|
|
6
|
+
// - Each metric is registered lazily on first emit. If the consumer
|
|
7
|
+
// hasn't merged `DB_RLS_METRIC_VOCABULARY` into their
|
|
8
|
+
// `initTelemetry({ vocabularyRegistry })` call, the registration
|
|
9
|
+
// throws `MetricVocabularyError`. We catch + `console.warn` once per
|
|
10
|
+
// metric so adopters see the gap; subsequent emissions are silently
|
|
11
|
+
// no-op for that name. Mirrors the pattern in
|
|
12
|
+
// `ts/approval/src/telemetry-shim.ts`.
|
|
13
|
+
// - Consumers wire `DB_RLS_METRIC_VOCABULARY` into their telemetry init
|
|
14
|
+
// so the registrations succeed; from then on the metrics emit
|
|
15
|
+
// normally.
|
|
16
|
+
// - Spans are emitted via `@nodii/telemetry`'s `tracer.startSpan`. Same
|
|
17
|
+
// try/swallow pattern — `TelemetryNotInitialized` is caught + the
|
|
18
|
+
// fn runs without a span.
|
|
19
|
+
import { MetricVocabularyError, metrics, tracer, TelemetryNotInitialized, } from "@nodii/telemetry";
|
|
20
|
+
/**
|
|
21
|
+
* Vocabulary registration. Adopters merge this into their `initTelemetry({
|
|
22
|
+
* vocabularyRegistry })` call so the metrics emitted by `@nodii/db-rls`
|
|
23
|
+
* are recognized.
|
|
24
|
+
*/
|
|
25
|
+
// All 9 metrics map to D18 family `pool` + the doctrine § 12.4 decision
|
|
26
|
+
// `D-rls-metrics-v1` (recorded in the locked db-rls.md § 5.10 table).
|
|
27
|
+
const DOCTRINE_DECISION = "D-rls-metrics-v1";
|
|
28
|
+
export const DB_RLS_METRIC_VOCABULARY = {
|
|
29
|
+
"db_rls.pool.acquisitions_total": {
|
|
30
|
+
family: "pool",
|
|
31
|
+
description: "Pool connection acquisitions per pool + outcome (success|failure).",
|
|
32
|
+
decision: DOCTRINE_DECISION,
|
|
33
|
+
},
|
|
34
|
+
"db_rls.pool.acquisition_duration_seconds": {
|
|
35
|
+
family: "pool",
|
|
36
|
+
description: "Duration (s) from pool.connect() request to client ready.",
|
|
37
|
+
decision: DOCTRINE_DECISION,
|
|
38
|
+
},
|
|
39
|
+
"db_rls.pool.size": {
|
|
40
|
+
family: "pool",
|
|
41
|
+
description: "Pool size sampled at tx finish; state ∈ {active, idle, waiting}.",
|
|
42
|
+
decision: DOCTRINE_DECISION,
|
|
43
|
+
},
|
|
44
|
+
"db_rls.tx.duration_seconds": {
|
|
45
|
+
family: "pool",
|
|
46
|
+
description: "Per-transaction duration (s) from BEGIN to COMMIT/ROLLBACK, per pool + outcome.",
|
|
47
|
+
decision: DOCTRINE_DECISION,
|
|
48
|
+
},
|
|
49
|
+
"db_rls.tx.outcomes_total": {
|
|
50
|
+
family: "pool",
|
|
51
|
+
description: "Transaction outcomes per pool. outcome ∈ {committed, rolled_back, error}.",
|
|
52
|
+
decision: DOCTRINE_DECISION,
|
|
53
|
+
},
|
|
54
|
+
"db_rls.cross_pool_nesting_total": {
|
|
55
|
+
family: "pool",
|
|
56
|
+
description: "Sanctioned cross-pool nesting events ({outer}→{inner}, with reason).",
|
|
57
|
+
decision: DOCTRINE_DECISION,
|
|
58
|
+
},
|
|
59
|
+
"db_rls.cross_pool_violations_total": {
|
|
60
|
+
family: "pool",
|
|
61
|
+
description: "Refused cross-pool nesting attempts ({outer}→{attempted_inner}).",
|
|
62
|
+
decision: DOCTRINE_DECISION,
|
|
63
|
+
},
|
|
64
|
+
"db_rls.owner_context_invocations_total": {
|
|
65
|
+
family: "pool",
|
|
66
|
+
description: "withOwnerContext entry events. Should be 0 in non-migration prod runtime.",
|
|
67
|
+
decision: DOCTRINE_DECISION,
|
|
68
|
+
},
|
|
69
|
+
"db_rls.guc_set_failures_total": {
|
|
70
|
+
family: "pool",
|
|
71
|
+
description: "GUC set_config failures by GUC name — indicates auth.* helper drift.",
|
|
72
|
+
decision: DOCTRINE_DECISION,
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* One-shot warn ledger for metric names whose registration failed (e.g.
|
|
77
|
+
* because the consumer didn't merge `DB_RLS_METRIC_VOCABULARY`). Each
|
|
78
|
+
* name warn-logs once; subsequent emissions of that name are silent.
|
|
79
|
+
*/
|
|
80
|
+
const WARNED = new Set();
|
|
81
|
+
let TELEMETRY_WARNED = false;
|
|
82
|
+
function warnOnce(metric, err) {
|
|
83
|
+
if (WARNED.has(metric))
|
|
84
|
+
return;
|
|
85
|
+
WARNED.add(metric);
|
|
86
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
87
|
+
console.warn(`[@nodii/db-rls] metric '${metric}' swallowed: ${msg}. Merge DB_RLS_METRIC_VOCABULARY into your initTelemetry({ vocabularyRegistry }) call so db-rls metrics emit. Further failures for '${metric}' will be silent.`);
|
|
88
|
+
}
|
|
89
|
+
function warnTelemetryOnce(err) {
|
|
90
|
+
if (TELEMETRY_WARNED)
|
|
91
|
+
return;
|
|
92
|
+
TELEMETRY_WARNED = true;
|
|
93
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
94
|
+
console.warn(`[@nodii/db-rls] telemetry not initialized: ${msg}. Call initTelemetry({ vocabularyRegistry: { ...DB_RLS_METRIC_VOCABULARY, ... } }) at process start. Further telemetry failures will be silent.`);
|
|
95
|
+
}
|
|
96
|
+
function inc(args) {
|
|
97
|
+
try {
|
|
98
|
+
const handle = metrics.counter(args.name, {
|
|
99
|
+
family: "pool",
|
|
100
|
+
description: DB_RLS_METRIC_VOCABULARY[args.name]?.description ?? "",
|
|
101
|
+
labelKeys: args.labels ? Object.keys(args.labels) : [],
|
|
102
|
+
});
|
|
103
|
+
handle.inc(args.by ?? 1, args.labels ?? {});
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
if (err instanceof MetricVocabularyError) {
|
|
107
|
+
warnOnce(args.name, err);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (err instanceof TelemetryNotInitialized) {
|
|
111
|
+
warnTelemetryOnce(err);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
// Anything else: also one-shot warn, never throw out of the metric path.
|
|
115
|
+
warnOnce(args.name, err);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function observe(args) {
|
|
119
|
+
try {
|
|
120
|
+
const handle = metrics.histogram(args.name, {
|
|
121
|
+
family: "pool",
|
|
122
|
+
description: DB_RLS_METRIC_VOCABULARY[args.name]?.description ?? "",
|
|
123
|
+
labelKeys: args.labels ? Object.keys(args.labels) : [],
|
|
124
|
+
});
|
|
125
|
+
handle.record(args.value, args.labels ?? {});
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
if (err instanceof MetricVocabularyError) {
|
|
129
|
+
warnOnce(args.name, err);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (err instanceof TelemetryNotInitialized) {
|
|
133
|
+
warnTelemetryOnce(err);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
warnOnce(args.name, err);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
140
|
+
// Public emission helpers — called by PgClientPool / PgPostgresJsPool /
|
|
141
|
+
// context.ts. Each helper is a thin shim so the call sites stay readable.
|
|
142
|
+
export function recordAcquisition(pool, outcome) {
|
|
143
|
+
inc({
|
|
144
|
+
name: "db_rls.pool.acquisitions_total",
|
|
145
|
+
labels: { pool, outcome },
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
export function recordAcquisitionDuration(pool, durationMs) {
|
|
149
|
+
observe({
|
|
150
|
+
name: "db_rls.pool.acquisition_duration_seconds",
|
|
151
|
+
value: durationMs / 1000,
|
|
152
|
+
labels: { pool },
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
export function recordPoolSize(pool, state, size) {
|
|
156
|
+
observe({
|
|
157
|
+
name: "db_rls.pool.size",
|
|
158
|
+
value: size,
|
|
159
|
+
labels: { pool, state },
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
export function recordTxDuration(pool, outcome, durationMs) {
|
|
163
|
+
observe({
|
|
164
|
+
name: "db_rls.tx.duration_seconds",
|
|
165
|
+
value: durationMs / 1000,
|
|
166
|
+
labels: { pool, outcome },
|
|
167
|
+
});
|
|
168
|
+
inc({
|
|
169
|
+
name: "db_rls.tx.outcomes_total",
|
|
170
|
+
labels: { pool, outcome },
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
export function recordCrossPoolNesting(outer, inner, reason) {
|
|
174
|
+
inc({
|
|
175
|
+
name: "db_rls.cross_pool_nesting_total",
|
|
176
|
+
labels: { outer, inner, reason },
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
export function recordCrossPoolViolation(outer, attemptedInner) {
|
|
180
|
+
inc({
|
|
181
|
+
name: "db_rls.cross_pool_violations_total",
|
|
182
|
+
labels: { outer, attempted_inner: attemptedInner },
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
export function recordOwnerContextInvocation(outcome) {
|
|
186
|
+
inc({
|
|
187
|
+
name: "db_rls.owner_context_invocations_total",
|
|
188
|
+
labels: { outcome },
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
export function recordGucSetFailure(guc) {
|
|
192
|
+
inc({
|
|
193
|
+
name: "db_rls.guc_set_failures_total",
|
|
194
|
+
labels: { guc },
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
// ---------------------------------------------------------------------------
|
|
198
|
+
// Spans — `db.rls.tx{pool}` per § 5.10 wraps the tx fn so the span end
|
|
199
|
+
// runs deterministically on success + throw. `db.rls.pool.acquire{pool}`
|
|
200
|
+
// is emitted as a one-shot span only when acquisition exceeds the slow
|
|
201
|
+
// threshold (default 50ms) per the doctrine.
|
|
202
|
+
/**
|
|
203
|
+
* Wraps `fn` in a `db.rls.tx` span scoped to `pool`. The span stamps
|
|
204
|
+
* the final outcome attribute (`committed` / `rolled_back` / `error`)
|
|
205
|
+
* via the `setOutcome` callback the caller passes through. On thrown
|
|
206
|
+
* exceptions the tracer.span lifecycle marks status=ERROR + records
|
|
207
|
+
* the exception — no extra work needed here.
|
|
208
|
+
*/
|
|
209
|
+
export async function withTxSpan(pool, fn) {
|
|
210
|
+
try {
|
|
211
|
+
return await tracer.span("db.rls.tx", async (span) => {
|
|
212
|
+
let outcome = "error";
|
|
213
|
+
try {
|
|
214
|
+
const out = await fn((o) => {
|
|
215
|
+
outcome = o;
|
|
216
|
+
});
|
|
217
|
+
span.setAttribute("outcome", outcome);
|
|
218
|
+
return out;
|
|
219
|
+
}
|
|
220
|
+
catch (err) {
|
|
221
|
+
span.setAttribute("outcome", "error");
|
|
222
|
+
throw err;
|
|
223
|
+
}
|
|
224
|
+
}, { attributes: { pool } });
|
|
225
|
+
}
|
|
226
|
+
catch (err) {
|
|
227
|
+
if (err instanceof TelemetryNotInitialized) {
|
|
228
|
+
warnTelemetryOnce(err);
|
|
229
|
+
// Telemetry not initialized — degrade to a plain pass-through.
|
|
230
|
+
let outcome = "error";
|
|
231
|
+
return await fn((o) => {
|
|
232
|
+
outcome = o;
|
|
233
|
+
}).finally(() => void outcome);
|
|
234
|
+
}
|
|
235
|
+
throw err;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Emits a one-shot `db.rls.pool.acquire` span. Caller invokes this only
|
|
240
|
+
* when acquisition time exceeds the slow threshold (default 50ms per
|
|
241
|
+
* § 5.10) — keeps span volume bounded.
|
|
242
|
+
*/
|
|
243
|
+
export function emitSlowAcquireSpan(pool, durationMs) {
|
|
244
|
+
// Fire-and-forget single-tick span — no fn body to wrap.
|
|
245
|
+
void tracer
|
|
246
|
+
.span("db.rls.pool.acquire", async (_span) => {
|
|
247
|
+
/* attributes set via opts; no inner work. */
|
|
248
|
+
}, { attributes: { pool, "duration.ms": durationMs } })
|
|
249
|
+
.catch((err) => {
|
|
250
|
+
if (err instanceof TelemetryNotInitialized) {
|
|
251
|
+
warnTelemetryOnce(err);
|
|
252
|
+
}
|
|
253
|
+
/* otherwise swallow */
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
// ---------------------------------------------------------------------------
|
|
257
|
+
// Test hooks.
|
|
258
|
+
/** Test-only: reset the one-shot warn ledger so each test runs clean. */
|
|
259
|
+
export function _resetMetricsWarnLedgerForTests() {
|
|
260
|
+
WARNED.clear();
|
|
261
|
+
TELEMETRY_WARNED = false;
|
|
262
|
+
}
|
|
263
|
+
//# sourceMappingURL=metrics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics.js","sourceRoot":"","sources":["../src/metrics.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,kEAAkE;AAClE,gEAAgE;AAChE,EAAE;AACF,yBAAyB;AACzB,sEAAsE;AACtE,0DAA0D;AAC1D,qEAAqE;AACrE,yEAAyE;AACzE,wEAAwE;AACxE,kDAAkD;AAClD,2CAA2C;AAC3C,0EAA0E;AAC1E,kEAAkE;AAClE,gBAAgB;AAChB,0EAA0E;AAC1E,sEAAsE;AACtE,8BAA8B;AAE9B,OAAO,EACL,qBAAqB,EAErB,OAAO,EACP,MAAM,EACN,uBAAuB,GACxB,MAAM,kBAAkB,CAAC;AAI1B;;;;GAIG;AACH,wEAAwE;AACxE,sEAAsE;AACtE,MAAM,iBAAiB,GAAG,kBAAkB,CAAC;AAE7C,MAAM,CAAC,MAAM,wBAAwB,GAA6B;IAChE,gCAAgC,EAAE;QAChC,MAAM,EAAE,MAAM;QACd,WAAW,EACT,oEAAoE;QACtE,QAAQ,EAAE,iBAAiB;KAC5B;IACD,0CAA0C,EAAE;QAC1C,MAAM,EAAE,MAAM;QACd,WAAW,EAAE,2DAA2D;QACxE,QAAQ,EAAE,iBAAiB;KAC5B;IACD,kBAAkB,EAAE;QAClB,MAAM,EAAE,MAAM;QACd,WAAW,EACT,kEAAkE;QACpE,QAAQ,EAAE,iBAAiB;KAC5B;IACD,4BAA4B,EAAE;QAC5B,MAAM,EAAE,MAAM;QACd,WAAW,EACT,iFAAiF;QACnF,QAAQ,EAAE,iBAAiB;KAC5B;IACD,0BAA0B,EAAE;QAC1B,MAAM,EAAE,MAAM;QACd,WAAW,EACT,2EAA2E;QAC7E,QAAQ,EAAE,iBAAiB;KAC5B;IACD,iCAAiC,EAAE;QACjC,MAAM,EAAE,MAAM;QACd,WAAW,EACT,sEAAsE;QACxE,QAAQ,EAAE,iBAAiB;KAC5B;IACD,oCAAoC,EAAE;QACpC,MAAM,EAAE,MAAM;QACd,WAAW,EACT,kEAAkE;QACpE,QAAQ,EAAE,iBAAiB;KAC5B;IACD,wCAAwC,EAAE;QACxC,MAAM,EAAE,MAAM;QACd,WAAW,EACT,2EAA2E;QAC7E,QAAQ,EAAE,iBAAiB;KAC5B;IACD,+BAA+B,EAAE;QAC/B,MAAM,EAAE,MAAM;QACd,WAAW,EACT,sEAAsE;QACxE,QAAQ,EAAE,iBAAiB;KAC5B;CACF,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;AACjC,IAAI,gBAAgB,GAAG,KAAK,CAAC;AAE7B,SAAS,QAAQ,CAAC,MAAc,EAAE,GAAY;IAC5C,IAAI,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC;QAAE,OAAO;IAC/B,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACnB,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC7D,OAAO,CAAC,IAAI,CACV,2BAA2B,MAAM,gBAAgB,GAAG,uIAAuI,MAAM,mBAAmB,CACrN,CAAC;AACJ,CAAC;AAED,SAAS,iBAAiB,CAAC,GAAY;IACrC,IAAI,gBAAgB;QAAE,OAAO;IAC7B,gBAAgB,GAAG,IAAI,CAAC;IACxB,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC7D,OAAO,CAAC,IAAI,CACV,8CAA8C,GAAG,iJAAiJ,CACnM,CAAC;AACJ,CAAC;AAQD,SAAS,GAAG,CAAC,IAAiB;IAC5B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE;YACxC,MAAM,EAAE,MAAM;YACd,WAAW,EAAE,wBAAwB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,WAAW,IAAI,EAAE;YACnE,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE;SACvD,CAAC,CAAC;QACH,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;IAC9C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,qBAAqB,EAAE,CAAC;YACzC,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;YACzB,OAAO;QACT,CAAC;QACD,IAAI,GAAG,YAAY,uBAAuB,EAAE,CAAC;YAC3C,iBAAiB,CAAC,GAAG,CAAC,CAAC;YACvB,OAAO;QACT,CAAC;QACD,yEAAyE;QACzE,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC3B,CAAC;AACH,CAAC;AAQD,SAAS,OAAO,CAAC,IAAmB;IAClC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE;YAC1C,MAAM,EAAE,MAAM;YACd,WAAW,EAAE,wBAAwB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,WAAW,IAAI,EAAE;YACnE,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE;SACvD,CAAC,CAAC;QACH,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;IAC/C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,qBAAqB,EAAE,CAAC;YACzC,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;YACzB,OAAO;QACT,CAAC;QACD,IAAI,GAAG,YAAY,uBAAuB,EAAE,CAAC;YAC3C,iBAAiB,CAAC,GAAG,CAAC,CAAC;YACvB,OAAO;QACT,CAAC;QACD,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC3B,CAAC;AACH,CAAC;AAED,8EAA8E;AAC9E,wEAAwE;AACxE,0EAA0E;AAE1E,MAAM,UAAU,iBAAiB,CAC/B,IAAc,EACd,OAA8B;IAE9B,GAAG,CAAC;QACF,IAAI,EAAE,gCAAgC;QACtC,MAAM,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE;KAC1B,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,yBAAyB,CACvC,IAAc,EACd,UAAkB;IAElB,OAAO,CAAC;QACN,IAAI,EAAE,0CAA0C;QAChD,KAAK,EAAE,UAAU,GAAG,IAAI;QACxB,MAAM,EAAE,EAAE,IAAI,EAAE;KACjB,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,cAAc,CAC5B,IAAc,EACd,KAAoC,EACpC,IAAY;IAEZ,OAAO,CAAC;QACN,IAAI,EAAE,kBAAkB;QACxB,KAAK,EAAE,IAAI;QACX,MAAM,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE;KACxB,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,IAAc,EACd,OAA8C,EAC9C,UAAkB;IAElB,OAAO,CAAC;QACN,IAAI,EAAE,4BAA4B;QAClC,KAAK,EAAE,UAAU,GAAG,IAAI;QACxB,MAAM,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE;KAC1B,CAAC,CAAC;IACH,GAAG,CAAC;QACF,IAAI,EAAE,0BAA0B;QAChC,MAAM,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE;KAC1B,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,sBAAsB,CACpC,KAAe,EACf,KAAe,EACf,MAAc;IAEd,GAAG,CAAC;QACF,IAAI,EAAE,iCAAiC;QACvC,MAAM,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE;KACjC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,wBAAwB,CACtC,KAAe,EACf,cAAwB;IAExB,GAAG,CAAC;QACF,IAAI,EAAE,oCAAoC;QAC1C,MAAM,EAAE,EAAE,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE;KACnD,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,4BAA4B,CAC1C,OAA8B;IAE9B,GAAG,CAAC;QACF,IAAI,EAAE,wCAAwC;QAC9C,MAAM,EAAE,EAAE,OAAO,EAAE;KACpB,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,GAAW;IAC7C,GAAG,CAAC;QACF,IAAI,EAAE,+BAA+B;QACrC,MAAM,EAAE,EAAE,GAAG,EAAE;KAChB,CAAC,CAAC;AACL,CAAC;AAED,8EAA8E;AAC9E,uEAAuE;AACvE,yEAAyE;AACzE,uEAAuE;AACvE,6CAA6C;AAE7C;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,IAAc,EACd,EAEe;IAEf,IAAI,CAAC;QACH,OAAO,MAAM,MAAM,CAAC,IAAI,CACtB,WAAW,EACX,KAAK,EAAE,IAAI,EAAE,EAAE;YACb,IAAI,OAAO,GAA0C,OAAO,CAAC;YAC7D,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE;oBACzB,OAAO,GAAG,CAAC,CAAC;gBACd,CAAC,CAAC,CAAC;gBACH,IAAI,CAAC,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;gBACtC,OAAO,GAAG,CAAC;YACb,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,IAAI,CAAC,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;gBACtC,MAAM,GAAG,CAAC;YACZ,CAAC;QACH,CAAC,EACD,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,EAAE,CACzB,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,uBAAuB,EAAE,CAAC;YAC3C,iBAAiB,CAAC,GAAG,CAAC,CAAC;YACvB,+DAA+D;YAC/D,IAAI,OAAO,GAA0C,OAAO,CAAC;YAC7D,OAAO,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE;gBACpB,OAAO,GAAG,CAAC,CAAC;YACd,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC;QACjC,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAc,EAAE,UAAkB;IACpE,yDAAyD;IACzD,KAAK,MAAM;SACR,IAAI,CACH,qBAAqB,EACrB,KAAK,EAAE,KAAK,EAAE,EAAE;QACd,6CAA6C;IAC/C,CAAC,EACD,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,UAAU,EAAE,EAAE,CACpD;SACA,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;QACb,IAAI,GAAG,YAAY,uBAAuB,EAAE,CAAC;YAC3C,iBAAiB,CAAC,GAAG,CAAC,CAAC;QACzB,CAAC;QACD,uBAAuB;IACzB,CAAC,CAAC,CAAC;AACP,CAAC;AAED,8EAA8E;AAC9E,cAAc;AAEd,yEAAyE;AACzE,MAAM,UAAU,+BAA+B;IAC7C,MAAM,CAAC,KAAK,EAAE,CAAC;IACf,gBAAgB,GAAG,KAAK,CAAC;AAC3B,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface OwnerContextOpts {
|
|
2
|
+
/** Bypass the `NODII_ENV=migrate` guard. Requires `reason` to be a
|
|
3
|
+
* non-empty string so the bypass is at least intentional. v0.2.x:
|
|
4
|
+
* the reason is captured for log lines + future audit emission. */
|
|
5
|
+
allowOutsideMigrate?: boolean;
|
|
6
|
+
/** Operator-attested reason, required iff `allowOutsideMigrate` is true. */
|
|
7
|
+
reason?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Asserts the caller can legitimately enter `withOwnerContext`. Throws
|
|
11
|
+
* `OwnerContextOutsideMigrate` if the guard is not satisfied.
|
|
12
|
+
*/
|
|
13
|
+
export declare function assertMigrateAllowed(opts?: OwnerContextOpts): void;
|
|
14
|
+
//# sourceMappingURL=migrate-guard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrate-guard.d.ts","sourceRoot":"","sources":["../src/migrate-guard.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,gBAAgB;IAC/B;;wEAEoE;IACpE,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,4EAA4E;IAC5E,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,GAAE,gBAAqB,GAAG,IAAI,CAWtE"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// migrate-guard — per `08-rls-doctrine § 10.2`.
|
|
2
|
+
//
|
|
3
|
+
// `withOwnerContext` / `withOwnerContextDb` connect via `nodii_owner`
|
|
4
|
+
// which is BYPASSRLS. Invoking it outside a migration is a tenant-
|
|
5
|
+
// isolation escape: an owner-pool query against the live request path
|
|
6
|
+
// returns ALL tenants' rows. The guard refuses by default unless:
|
|
7
|
+
//
|
|
8
|
+
// 1. `NODII_ENV=migrate` (migration scripts opt in via env), OR
|
|
9
|
+
// 2. The caller explicitly passes `{ allowOutsideMigrate: true,
|
|
10
|
+
// reason: "..." }` with an operator-attested reason.
|
|
11
|
+
//
|
|
12
|
+
// Both paths are first-class — option 2 exists for legitimate ops
|
|
13
|
+
// tooling (DSAR fulfillment, support-ticket data extraction) where the
|
|
14
|
+
// caller has attested authorization. v0.2.x ships the guard; the
|
|
15
|
+
// audit-row-on-bypass extension lands in a follow-up slot.
|
|
16
|
+
import { OwnerContextOutsideMigrate } from "./types";
|
|
17
|
+
/**
|
|
18
|
+
* Asserts the caller can legitimately enter `withOwnerContext`. Throws
|
|
19
|
+
* `OwnerContextOutsideMigrate` if the guard is not satisfied.
|
|
20
|
+
*/
|
|
21
|
+
export function assertMigrateAllowed(opts = {}) {
|
|
22
|
+
if (opts.allowOutsideMigrate) {
|
|
23
|
+
if (!opts.reason || opts.reason.trim() === "") {
|
|
24
|
+
throw new OwnerContextOutsideMigrate(process.env.NODII_ENV ?? "(unset)");
|
|
25
|
+
}
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const env = process.env.NODII_ENV ?? "";
|
|
29
|
+
if (env !== "migrate") {
|
|
30
|
+
throw new OwnerContextOutsideMigrate(env === "" ? "(unset)" : env);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=migrate-guard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrate-guard.js","sourceRoot":"","sources":["../src/migrate-guard.ts"],"names":[],"mappings":"AAAA,gDAAgD;AAChD,EAAE;AACF,sEAAsE;AACtE,mEAAmE;AACnE,sEAAsE;AACtE,kEAAkE;AAClE,EAAE;AACF,kEAAkE;AAClE,kEAAkE;AAClE,0DAA0D;AAC1D,EAAE;AACF,kEAAkE;AAClE,uEAAuE;AACvE,iEAAiE;AACjE,2DAA2D;AAE3D,OAAO,EAAE,0BAA0B,EAAE,MAAM,SAAS,CAAC;AAWrD;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAAyB,EAAE;IAC9D,IAAI,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAC7B,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YAC9C,MAAM,IAAI,0BAA0B,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,SAAS,CAAC,CAAC;QAC3E,CAAC;QACD,OAAO;IACT,CAAC;IACD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,EAAE,CAAC;IACxC,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;QACtB,MAAM,IAAI,0BAA0B,CAAC,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACrE,CAAC;AACH,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pg-pool-adapter.d.ts","sourceRoot":"","sources":["../src/pg-pool-adapter.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,IAAI,EAAc,MAAM,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"pg-pool-adapter.d.ts","sourceRoot":"","sources":["../src/pg-pool-adapter.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,IAAI,EAAc,MAAM,IAAI,CAAC;AAU3C,OAAO,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEtD,MAAM,WAAW,iBAAiB;IAChC,qEAAqE;IACrE,IAAI,EAAE,IAAI,CAAC;IACX,sDAAsD;IACtD,IAAI,EAAE,QAAQ,CAAC;IACf;uDACmD;IACnD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;qDACiD;IACjD,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACvE;qDACiD;IACjD,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE;QAClB,IAAI,EAAE,QAAQ,CAAC;QACf,OAAO,EAAE,WAAW,GAAG,aAAa,GAAG,OAAO,CAAC;QAC/C,UAAU,EAAE,MAAM,CAAC;KACpB,KAAK,IAAI,CAAC;CACZ;AA2BD;;;;GAIG;AACH,qBAAa,YAAa,YAAW,MAAM;IACzC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAO;IAC5B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAqC;IACpE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAkC;gBAElD,IAAI,EAAE,iBAAiB;IAQnC;;;sEAGkE;IAClE,IAAI,OAAO,IAAI,IAAI,CAElB;IAEK,eAAe,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;CA2FnE"}
|
package/dist/pg-pool-adapter.js
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
// Pool-leak guard: client.release() ALWAYS runs in the finally block,
|
|
14
14
|
// regardless of commit/rollback outcome. The acquire-timer fires a
|
|
15
15
|
// telemetry span when acquisition exceeds 50ms (§ 5.10).
|
|
16
|
+
import { emitSlowAcquireSpan, recordAcquisition, recordAcquisitionDuration, recordGucSetFailure, recordPoolSize, recordTxDuration, withTxSpan, } from "./metrics";
|
|
16
17
|
class PgClientTx {
|
|
17
18
|
poolKind;
|
|
18
19
|
client;
|
|
@@ -26,7 +27,13 @@ class PgClientTx {
|
|
|
26
27
|
async setConfig(key, value) {
|
|
27
28
|
// set_config(name, value, is_local=true) — tx-local per § 5.3.
|
|
28
29
|
// Never expose the false variant; library refuses session-scoped GUCs.
|
|
29
|
-
|
|
30
|
+
try {
|
|
31
|
+
await this.client.query("SELECT set_config($1, $2, true)", [key, value]);
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
recordGucSetFailure(key);
|
|
35
|
+
throw err;
|
|
36
|
+
}
|
|
30
37
|
}
|
|
31
38
|
}
|
|
32
39
|
/**
|
|
@@ -56,48 +63,94 @@ export class PgClientPool {
|
|
|
56
63
|
}
|
|
57
64
|
async withTransaction(fn) {
|
|
58
65
|
const acquireStart = Date.now();
|
|
59
|
-
|
|
66
|
+
let client;
|
|
67
|
+
try {
|
|
68
|
+
client = await this.pool.connect();
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
recordAcquisition(this.kind, "failure");
|
|
72
|
+
throw err;
|
|
73
|
+
}
|
|
60
74
|
const acquireMs = Date.now() - acquireStart;
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
75
|
+
recordAcquisition(this.kind, "success");
|
|
76
|
+
recordAcquisitionDuration(this.kind, acquireMs);
|
|
77
|
+
if (acquireMs >= this.slowAcquireMs) {
|
|
78
|
+
emitSlowAcquireSpan(this.kind, acquireMs);
|
|
79
|
+
if (this.onSlowAcquire) {
|
|
80
|
+
try {
|
|
81
|
+
this.onSlowAcquire({ pool: this.kind, durationMs: acquireMs });
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
/* hook errors are non-fatal */
|
|
85
|
+
}
|
|
67
86
|
}
|
|
68
87
|
}
|
|
69
88
|
const txStart = Date.now();
|
|
70
89
|
let outcome = "error";
|
|
71
90
|
try {
|
|
72
|
-
await
|
|
73
|
-
const tx = new PgClientTx(this.kind, client);
|
|
74
|
-
let result;
|
|
75
|
-
try {
|
|
76
|
-
result = await fn(tx);
|
|
77
|
-
}
|
|
78
|
-
catch (err) {
|
|
79
|
-
// Rollback then re-throw. Swallow rollback failure (don't double-throw).
|
|
91
|
+
return await withTxSpan(this.kind, async (setOutcome) => {
|
|
80
92
|
try {
|
|
81
|
-
await client.query("
|
|
93
|
+
await client.query("BEGIN");
|
|
94
|
+
const tx = new PgClientTx(this.kind, client);
|
|
95
|
+
let result;
|
|
96
|
+
try {
|
|
97
|
+
result = await fn(tx);
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
// Rollback then re-throw. Swallow rollback failure (don't
|
|
101
|
+
// double-throw).
|
|
102
|
+
try {
|
|
103
|
+
await client.query("ROLLBACK");
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
/* swallow per § 5.9 */
|
|
107
|
+
}
|
|
108
|
+
outcome = "rolled_back";
|
|
109
|
+
setOutcome("rolled_back");
|
|
110
|
+
throw err;
|
|
111
|
+
}
|
|
112
|
+
await client.query("COMMIT");
|
|
113
|
+
outcome = "committed";
|
|
114
|
+
setOutcome("committed");
|
|
115
|
+
return result;
|
|
82
116
|
}
|
|
83
|
-
catch {
|
|
84
|
-
|
|
117
|
+
catch (err) {
|
|
118
|
+
// BEGIN failure (or other pre-fn throw) — record error path.
|
|
119
|
+
if (outcome === "error") {
|
|
120
|
+
setOutcome("error");
|
|
121
|
+
}
|
|
122
|
+
throw err;
|
|
85
123
|
}
|
|
86
|
-
|
|
87
|
-
throw err;
|
|
88
|
-
}
|
|
89
|
-
await client.query("COMMIT");
|
|
90
|
-
outcome = "committed";
|
|
91
|
-
return result;
|
|
124
|
+
});
|
|
92
125
|
}
|
|
93
126
|
finally {
|
|
94
127
|
const durationMs = Date.now() - txStart;
|
|
128
|
+
recordTxDuration(this.kind, outcome, durationMs);
|
|
95
129
|
try {
|
|
96
130
|
this.onTxFinish?.({ pool: this.kind, outcome, durationMs });
|
|
97
131
|
}
|
|
98
132
|
catch {
|
|
99
133
|
/* hook errors are non-fatal */
|
|
100
134
|
}
|
|
135
|
+
// Sample pool-size gauge at tx end. pg.Pool exposes totalCount /
|
|
136
|
+
// idleCount / waitingCount; if absent (mocked or wrapped pool),
|
|
137
|
+
// skip silently.
|
|
138
|
+
try {
|
|
139
|
+
// biome-ignore lint/suspicious/noExplicitAny: pg.Pool stats
|
|
140
|
+
const stats = this.pool;
|
|
141
|
+
if (typeof stats.totalCount === "number") {
|
|
142
|
+
const total = stats.totalCount;
|
|
143
|
+
const idle = stats.idleCount ?? 0;
|
|
144
|
+
const waiting = stats.waitingCount ?? 0;
|
|
145
|
+
const active = Math.max(0, total - idle);
|
|
146
|
+
recordPoolSize(this.kind, "active", active);
|
|
147
|
+
recordPoolSize(this.kind, "idle", idle);
|
|
148
|
+
recordPoolSize(this.kind, "waiting", waiting);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
/* gauge sampling errors are non-fatal */
|
|
153
|
+
}
|
|
101
154
|
// Pool-leak guard — release ALWAYS runs, even on commit/rollback failure.
|
|
102
155
|
try {
|
|
103
156
|
client.release();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pg-pool-adapter.js","sourceRoot":"","sources":["../src/pg-pool-adapter.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,qEAAqE;AACrE,qEAAqE;AACrE,qBAAqB;AACrB,EAAE;AACF,uBAAuB;AACvB,2EAA2E;AAC3E,aAAa;AACb,wEAAwE;AACxE,8BAA8B;AAC9B,+CAA+C;AAC/C,EAAE;AACF,sEAAsE;AACtE,mEAAmE;AACnE,yDAAyD;
|
|
1
|
+
{"version":3,"file":"pg-pool-adapter.js","sourceRoot":"","sources":["../src/pg-pool-adapter.ts"],"names":[],"mappings":"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"}
|