@constructive-io/express-context 0.33.6 → 0.34.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/context.d.ts CHANGED
@@ -24,6 +24,8 @@ export interface ContextMiddlewareOptions {
24
24
  loaders?: LoaderRegistry;
25
25
  /** Routing-plane schema loaders query (defaults to routing_public) */
26
26
  routingSchema?: string;
27
+ /** Role the module loaders run as (`SET LOCAL ROLE`); required with `loaders`. */
28
+ controlRole?: string;
27
29
  }
28
30
  /**
29
31
  * Build the ConstructiveContext from the current request state.
package/context.js CHANGED
@@ -18,6 +18,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
18
18
  exports.buildContext = buildContext;
19
19
  exports.createContextMiddleware = createContextMiddleware;
20
20
  const pg_cache_1 = require("pg-cache");
21
+ const pg_query_context_1 = require("pg-query-context");
21
22
  const billing_client_1 = require("./billing-client");
22
23
  const pg_client_1 = require("./pg-client");
23
24
  const pg_settings_1 = require("./pg-settings");
@@ -64,11 +65,10 @@ function buildContext(req, opts = {}) {
64
65
  // Build loader context (if registry provided and databaseId known)
65
66
  let loaderCtx = null;
66
67
  if (opts.loaders && api.databaseId) {
67
- const routingPool = (0, pg_cache_1.getPgPool)(opts.pg);
68
68
  loaderCtx = {
69
- routingPool,
69
+ routingPool: (0, pg_query_context_1.controlPlanePool)((0, pg_cache_1.getPgPool)(opts.pg), opts.controlRole),
70
70
  routingSchema: opts.routingSchema,
71
- tenantPool,
71
+ tenantPool: (0, pg_query_context_1.controlPlanePool)(tenantPool, opts.controlRole),
72
72
  databaseId: api.databaseId,
73
73
  apiId: api.apiId,
74
74
  dbname: api.dbname
@@ -143,6 +143,9 @@ function buildContext(req, opts = {}) {
143
143
  * ```
144
144
  */
145
145
  function createContextMiddleware(opts = {}) {
146
+ if (opts.loaders && !opts.controlRole) {
147
+ throw new Error('createContextMiddleware: `loaders` need a `controlRole` to run as');
148
+ }
146
149
  return (req, _res, next) => {
147
150
  const ctx = buildContext(req, opts);
148
151
  if (ctx) {
package/esm/context.js CHANGED
@@ -14,6 +14,7 @@
14
14
  * route handler can use for tenant-scoped database operations.
15
15
  */
16
16
  import { getPgPool } from 'pg-cache';
17
+ import { controlPlanePool } from 'pg-query-context';
17
18
  import { createBillingClient } from './billing-client';
18
19
  import { withPgClient as withPgClientFn } from './pg-client';
19
20
  import { buildPgSettings } from './pg-settings';
@@ -60,11 +61,10 @@ export function buildContext(req, opts = {}) {
60
61
  // Build loader context (if registry provided and databaseId known)
61
62
  let loaderCtx = null;
62
63
  if (opts.loaders && api.databaseId) {
63
- const routingPool = getPgPool(opts.pg);
64
64
  loaderCtx = {
65
- routingPool,
65
+ routingPool: controlPlanePool(getPgPool(opts.pg), opts.controlRole),
66
66
  routingSchema: opts.routingSchema,
67
- tenantPool,
67
+ tenantPool: controlPlanePool(tenantPool, opts.controlRole),
68
68
  databaseId: api.databaseId,
69
69
  apiId: api.apiId,
70
70
  dbname: api.dbname
@@ -139,6 +139,9 @@ export function buildContext(req, opts = {}) {
139
139
  * ```
140
140
  */
141
141
  export function createContextMiddleware(opts = {}) {
142
+ if (opts.loaders && !opts.controlRole) {
143
+ throw new Error('createContextMiddleware: `loaders` need a `controlRole` to run as');
144
+ }
142
145
  return (req, _res, next) => {
143
146
  const ctx = buildContext(req, opts);
144
147
  if (ctx) {
package/esm/refusals.js CHANGED
@@ -20,7 +20,7 @@
20
20
  * @module refusals
21
21
  */
22
22
  import { Logger } from '@pgpmjs/logger';
23
- import { withPgClient } from 'pg-query-context';
23
+ import { withControlRole } from 'pg-query-context';
24
24
  import { BoundedCounter, CounterFlusher } from './usage-counter';
25
25
  const log = new Logger('refusals');
26
26
  export const REFUSAL_REASONS = [
@@ -207,8 +207,8 @@ export const createRecordRefusalsSink = (opts) => {
207
207
  if (rows.length === 0)
208
208
  return;
209
209
  const claims = await opts.claims();
210
- await withPgClient(opts.pool, claims, async (client) => {
210
+ await withControlRole(opts.pool, opts.role, async (client) => {
211
211
  await client.query(sql, [JSON.stringify(rows)]);
212
- });
212
+ }, claims);
213
213
  };
214
214
  };
@@ -14,9 +14,9 @@
14
14
  * predates these columns must resolve to the platform defaults, not fail every
15
15
  * request against it with `column ds.… does not exist`.
16
16
  */
17
- import type { Pool } from 'pg';
17
+ import type { ControlPlaneQueryable } from 'pg-query-context';
18
18
  import type { RequestProtection } from '../request-protection';
19
19
  import type { ModuleLoader } from './types';
20
20
  /** Test seam: forget what a pool's routing plane looked like. */
21
- export declare const forgetProtectionColumns: (pool: Pool) => void;
21
+ export declare const forgetProtectionColumns: (pool: ControlPlaneQueryable) => void;
22
22
  export declare const requestProtectionLoader: ModuleLoader<RequestProtection>;
@@ -9,7 +9,7 @@
9
9
  * during context building. The result is a typed modules map on
10
10
  * `req.constructive.modules`.
11
11
  */
12
- import type { Pool } from 'pg';
12
+ import type { ControlPlaneQueryable } from 'pg-query-context';
13
13
  /** Published logical name of the scoped routing plane. */
14
14
  export declare const DEFAULT_ROUTING_SCHEMA = "routing_public";
15
15
  /**
@@ -31,15 +31,16 @@ export declare function requireDatabaseId(databaseId: string | undefined, loader
31
31
  /**
32
32
  * Context passed to every loader's resolve function.
33
33
  * Provides both pool references so the loader can query whichever
34
- * database tier it needs.
34
+ * database tier it needs. Both run every query as the control role
35
+ * (`SET LOCAL ROLE` in its own transaction): the login role can read nothing.
35
36
  */
36
37
  export interface LoaderContext {
37
38
  /** Routing/configuration database pool (for routing-plane lookups) */
38
- routingPool: Pool;
39
+ routingPool: ControlPlaneQueryable;
39
40
  /** Routing-plane schema to query (defaults to the published routing_public) */
40
41
  routingSchema?: string;
41
42
  /** Tenant database pool (for metaschema_modules_public.* lookups) */
42
- tenantPool: Pool;
43
+ tenantPool: ControlPlaneQueryable;
43
44
  /** UUID of the database being resolved */
44
45
  databaseId: string;
45
46
  /** UUID of the API (if resolved from domain/api-name lookup) */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@constructive-io/express-context",
3
- "version": "0.33.6",
3
+ "version": "0.34.0",
4
4
  "description": "Extractable Express middleware for Constructive tenant context — domain resolution, JWT auth, pgSettings, and withPgClient",
5
5
  "author": "Constructive <developers@constructive.io>",
6
6
  "homepage": "https://github.com/constructive-io/constructive",
@@ -30,14 +30,14 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "@constructive-io/url-domains": "^2.31.0",
33
- "@pgpmjs/env": "^2.46.3",
33
+ "@pgpmjs/env": "^2.47.0",
34
34
  "@pgpmjs/logger": "^2.26.0",
35
- "@pgpmjs/server-utils": "^3.29.6",
36
- "@pgpmjs/types": "^2.56.3",
35
+ "@pgpmjs/server-utils": "^3.29.7",
36
+ "@pgpmjs/types": "^2.57.0",
37
37
  "lru-cache": "^11.2.7",
38
38
  "pg": "^8.21.0",
39
- "pg-cache": "^3.29.6",
40
- "pg-query-context": "^2.31.0"
39
+ "pg-cache": "^3.29.7",
40
+ "pg-query-context": "^2.32.0"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/express": "^5.0.6",
@@ -59,5 +59,5 @@
59
59
  "jwt",
60
60
  "pgSettings"
61
61
  ],
62
- "gitHead": "a95137c03998f4ef18aaba770bdeb598077e5b51"
62
+ "gitHead": "2f506a4a4062b4bd92979c02249e0fee14af4043"
63
63
  }
package/refusals.d.ts CHANGED
@@ -136,6 +136,8 @@ export declare class RefusalRecorder {
136
136
  export interface RecordRefusalsSinkOptions {
137
137
  /** The platform database's pool. */
138
138
  pool: Pool;
139
+ /** Role the flush runs as (`SET LOCAL ROLE`); the login role holds nothing. */
140
+ role: string;
139
141
  /**
140
142
  * The transaction-local claims the flush runs under, resolved by the caller
141
143
  * (a service principal plus the platform database id). The function raises
package/refusals.js CHANGED
@@ -215,9 +215,9 @@ const createRecordRefusalsSink = (opts) => {
215
215
  if (rows.length === 0)
216
216
  return;
217
217
  const claims = await opts.claims();
218
- await (0, pg_query_context_1.withPgClient)(opts.pool, claims, async (client) => {
218
+ await (0, pg_query_context_1.withControlRole)(opts.pool, opts.role, async (client) => {
219
219
  await client.query(sql, [JSON.stringify(rows)]);
220
- });
220
+ }, claims);
221
221
  };
222
222
  };
223
223
  exports.createRecordRefusalsSink = createRecordRefusalsSink;