@constructive-io/graphql-server 5.30.0 → 5.31.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/README.md CHANGED
@@ -56,6 +56,13 @@ This starts the server with env defaults from `@constructive-io/graphql-env`.
56
56
 
57
57
  ## What it does
58
58
 
59
+ Platform deployments connect through one pgpm-provisioned `NOSUPERUSER INHERIT
60
+ BYPASSRLS` login with direct metadata privileges, configured only with
61
+ `PGUSER`/`PGPASSWORD`. Startup and routing queries use that pool login directly.
62
+ Requests continue to switch to `authenticated` or `anonymous`, so PostgreSQL
63
+ enforces their RLS policies; the login's BYPASSRLS attribute is not inherited
64
+ after `SET ROLE`.
65
+
59
66
  Runs an Express server that wires CORS, uploads, domain parsing, auth, and PostGraphile into a single GraphQL endpoint. It serves `/graphql` and `/graphiql`, injects per-request `pgSettings`, and flushes cached schemas on demand or via database notifications. When meta API is enabled, it resolves API config (schemas, roles, modules) from the meta schema using the request host and enforces `api.isPublic`, with optional header overrides in private mode; when meta API is disabled, it serves the fixed schemas and roles from `api.exposedSchemas`, `api.anonRole`, and `api.roleName`.
60
67
 
61
68
  ## Key Features
@@ -146,7 +153,6 @@ Configuration is merged from defaults, config files, and env vars via `@construc
146
153
  | `API_ROLE_NAME` | Authenticated role name | `administrator` |
147
154
  | `API_FLUSH_TOKEN` | Bearer token required by `POST /flush`; route is not mounted when unset | empty |
148
155
  | `API_INTROSPECTION_ROLE` | Role PostGraphile introspects as; unset means the pool's connecting role. Naming a role with fewer grants removes fields from the schema | empty |
149
- | `API_CONTROL_ROLE` | Role control-plane queries (routing, API lookups, module loaders, flush, refusal log) run as via `SET LOCAL ROLE`. The login role holds no privileges, so the server refuses to start without it | required |
150
156
  | `GRAPHQL_OBSERVABILITY_ENABLED` | Master switch for debug routes and sampler | `false` |
151
157
  | `GRAPHQL_DEBUG_SAMPLER_ENABLED` | Enables periodic NDJSON sampling when observability is on | `true` |
152
158
  | `GRAPHQL_DEBUG_SAMPLER_INTERVAL_MS` | Sampler interval in milliseconds | `10000` |
@@ -4,7 +4,6 @@ exports.getDebugDatabaseSnapshot = exports.closeDebugDatabasePools = void 0;
4
4
  const pg_1 = require("pg");
5
5
  const pg_cache_1 = require("pg-cache");
6
6
  const pg_env_1 = require("pg-env");
7
- const control_role_1 = require("../control-role");
8
7
  const ACTIVE_ACTIVITY_SQL = `
9
8
  select
10
9
  pid,
@@ -142,7 +141,6 @@ const withDiagnosticsClient = async (opts, fn) => {
142
141
  const client = await diagnosticsPool.connect();
143
142
  try {
144
143
  await client.query('BEGIN');
145
- await client.query('SELECT set_config($1, $2, true)', ['role', (0, control_role_1.getControlRole)(opts)]);
146
144
  await client.query(`SET LOCAL statement_timeout = '${DIAGNOSTICS_STATEMENT_TIMEOUT_MS}ms'`);
147
145
  await client.query(`SET LOCAL lock_timeout = '${DIAGNOSTICS_LOCK_TIMEOUT_MS}ms'`);
148
146
  const result = await fn(client);
@@ -1,7 +1,6 @@
1
1
  import { Pool } from 'pg';
2
2
  import { buildConnectionString, getPgPool } from 'pg-cache';
3
3
  import { getPgEnvOptions } from 'pg-env';
4
- import { getControlRole } from '../control-role';
5
4
  const ACTIVE_ACTIVITY_SQL = `
6
5
  select
7
6
  pid,
@@ -139,7 +138,6 @@ const withDiagnosticsClient = async (opts, fn) => {
139
138
  const client = await diagnosticsPool.connect();
140
139
  try {
141
140
  await client.query('BEGIN');
142
- await client.query('SELECT set_config($1, $2, true)', ['role', getControlRole(opts)]);
143
141
  await client.query(`SET LOCAL statement_timeout = '${DIAGNOSTICS_STATEMENT_TIMEOUT_MS}ms'`);
144
142
  await client.query(`SET LOCAL lock_timeout = '${DIAGNOSTICS_LOCK_TIMEOUT_MS}ms'`);
145
143
  const result = await fn(client);
@@ -4,8 +4,6 @@ import { parseUrl } from '@constructive-io/url-domains';
4
4
  import { Logger } from '@pgpmjs/logger';
5
5
  import { svcCache } from '@pgpmjs/server-utils';
6
6
  import { getPgPool } from 'pg-cache';
7
- import { controlPlanePool } from 'pg-query-context';
8
- import { getControlRole } from '../control-role';
9
7
  import errorPage50x from '../errors/50x';
10
8
  import errorPage404Message from '../errors/404-message';
11
9
  import { getRoutingSchema, isValidSchemaName, requireApiRole, resolveRoute, routeToApiStructure } from './routing';
@@ -50,7 +48,7 @@ const buildLoaderContext = (routingPool, opts, row) => {
50
48
  return {
51
49
  routingPool,
52
50
  routingSchema: getRoutingSchema(opts),
53
- tenantPool: controlPlanePool(getPgPool({ ...opts.pg, database: dbname }), routingPool.role),
51
+ tenantPool: getPgPool({ ...opts.pg, database: dbname }),
54
52
  databaseId: row.database_id,
55
53
  apiId: row.api_id,
56
54
  dbname
@@ -262,7 +260,7 @@ const resolveScopedRoute = async (ctx) => {
262
260
  // Main Resolution Function
263
261
  // =============================================================================
264
262
  export const getApiConfig = async (opts, req) => {
265
- const pool = controlPlanePool(getPgPool(opts.pg), getControlRole(opts));
263
+ const pool = getPgPool(opts.pg);
266
264
  const { domain, subdomains } = getUrlDomains(req);
267
265
  const subdomain = getSubdomain(subdomains);
268
266
  const cacheKey = getSvcKey(opts, req);
@@ -62,7 +62,6 @@ export const createAuthenticateMiddleware = (opts) => {
62
62
  if (effectiveToken) {
63
63
  log.info(`[auth] Processing ${tokenSource} authentication`);
64
64
  const context = {
65
- role: api.anonRole,
66
65
  'jwt.claims.ip_address': req.clientIp,
67
66
  };
68
67
  if (req.get('origin')) {
@@ -4,8 +4,6 @@ import { svcCache } from '@pgpmjs/server-utils';
4
4
  import { createHash, timingSafeEqual } from 'crypto';
5
5
  import { graphileCache } from 'graphile-cache';
6
6
  import { getPgPool } from 'pg-cache';
7
- import { controlPlanePool } from 'pg-query-context';
8
- import { getControlRole } from '../control-role';
9
7
  import { getRoutingSchema, isValidSchemaName } from './routing';
10
8
  const log = new Logger('flush');
11
9
  const bearerToken = (req) => {
@@ -49,7 +47,7 @@ export const createFlushMiddleware = (opts) => {
49
47
  };
50
48
  };
51
49
  export const flushService = async (opts, databaseId) => {
52
- const pgPool = controlPlanePool(getPgPool(opts.pg), getControlRole(opts));
50
+ const pgPool = getPgPool(opts.pg);
53
51
  log.info('flushing db ' + databaseId);
54
52
  const api = new RegExp(`^api:${databaseId}:.*`);
55
53
  const schemata = new RegExp(`^schemata:${databaseId}:.*`);
@@ -7,8 +7,6 @@ import { createFunctionBindingsPlugin } from 'graphile-function-bindings';
7
7
  import { createConstructivePreset, createGrafastCacheLimitsPreset } from 'graphile-settings';
8
8
  import { getPgPool } from 'pg-cache';
9
9
  import { getPgEnvOptions } from 'pg-env';
10
- import { controlPlanePool } from 'pg-query-context';
11
- import { getControlRole } from '../control-role';
12
10
  import { isGraphqlObservabilityEnabled } from '../diagnostics/observability';
13
11
  import { HandlerCreationError } from '../errors/api-errors';
14
12
  import { respondWithGraphQLError } from '../errors/graphql-response';
@@ -58,7 +56,7 @@ const reqLabel = (req) => (req.requestId ? `[${req.requestId}]` : '[req]');
58
56
  * plugin preset. Without settings the default preset is used
59
57
  * (everything on except aggregates).
60
58
  */
61
- const buildPreset = async (pool, schemas, anonRole, roleName, introspectionRole, controlRole, graphileOptions, databaseSettings, apiId, compute, webauthn) => {
59
+ const buildPreset = async (pool, schemas, anonRole, roleName, introspectionRole, graphileOptions, databaseSettings, apiId, compute, webauthn) => {
62
60
  const introspection = await makeIntrospectionWiring(pool, schemas, graphileOptions, undefined, introspectionRole);
63
61
  const configuredPreset = graphileOptions?.preset ?? {};
64
62
  const grafastCachePreset = createGrafastCacheLimitsPreset(graphileOptions?.grafastCache);
@@ -89,7 +87,6 @@ const buildPreset = async (pool, schemas, anonRole, roleName, introspectionRole,
89
87
  ? [
90
88
  createFunctionBindingsPlugin({
91
89
  apiId,
92
- role: controlRole,
93
90
  modules: compute.modules.map((m) => ({
94
91
  computeSchema: m.schemaName,
95
92
  bindingsTable: m.bindingsTableName,
@@ -314,7 +311,7 @@ export const graphile = (opts) => {
314
311
  // concurrent requests coalesce while optional plugin modules load.
315
312
  const creationPromise = (async () => {
316
313
  const compute = api.apiId ? await req.constructive?.useModule('compute') : undefined;
317
- const preset = await buildPreset(pool, schema || [], anonRole, roleName, opts.api?.introspectionRole, getControlRole(opts), opts.graphile, api.databaseSettings, api.apiId, compute, Boolean(api.webauthnSettings));
314
+ const preset = await buildPreset(pool, schema || [], anonRole, roleName, opts.api?.introspectionRole, opts.graphile, api.databaseSettings, api.apiId, compute, Boolean(api.webauthnSettings));
318
315
  return observeGraphileBuild({
319
316
  cacheKey: key,
320
317
  serviceKey: key,
@@ -322,8 +319,7 @@ export const graphile = (opts) => {
322
319
  }, () => createGraphileInstance({
323
320
  preset,
324
321
  cacheKey: key,
325
- enableRealtime: api.databaseSettings?.enableRealtime,
326
- realtimePool: controlPlanePool(pool, getControlRole(opts))
322
+ enableRealtime: api.databaseSettings?.enableRealtime
327
323
  }), { enabled: observabilityEnabled });
328
324
  })();
329
325
  creating.set(key, creationPromise);
@@ -2,8 +2,6 @@ import '../middleware/types'; // for Request type
2
2
  import { clientIpFrom, createRecordRefusalsSink, RefusalRecorder, trustedProxyHops } from '@constructive-io/express-context';
3
3
  import { Logger } from '@pgpmjs/logger';
4
4
  import { getPgPool } from 'pg-cache';
5
- import { controlPlanePool } from 'pg-query-context';
6
- import { getControlRole } from '../control-role';
7
5
  const log = new Logger('refusals');
8
6
  /**
9
7
  * refusals/recorder — the GraphQL lane's `RefusalRecorder`.
@@ -121,17 +119,16 @@ export const platformFlushClaims = (identity) => ({
121
119
  */
122
120
  export const createPlatformRefusalRecorder = (opts, recorderOpts = {}) => {
123
121
  const pool = getPgPool(opts.pg);
124
- const role = getControlRole(opts);
125
122
  let cached = null;
126
123
  const claims = async () => {
127
124
  if (cached)
128
125
  return cached;
129
- cached = platformFlushClaims(await resolvePlatformFlushIdentity(controlPlanePool(pool, role), recorderOpts.principalName));
126
+ cached = platformFlushClaims(await resolvePlatformFlushIdentity(pool, recorderOpts.principalName));
130
127
  log.info(`[refusals] flushing as '${recorderOpts.principalName ?? PLATFORM_BOOTSTRAP_PRINCIPAL}'`);
131
128
  return cached;
132
129
  };
133
130
  return new RefusalRecorder({
134
- sink: createRecordRefusalsSink({ pool, role, claims }),
131
+ sink: createRecordRefusalsSink({ pool, claims }),
135
132
  intervalMs: recorderOpts.intervalMs,
136
133
  jitterMs: recorderOpts.jitterMs,
137
134
  maxKeys: recorderOpts.maxKeys
package/esm/server.js CHANGED
@@ -11,7 +11,6 @@ import graphqlUpload from 'graphql-upload';
11
11
  import { getPgPool } from 'pg-cache';
12
12
  import requestIp from 'request-ip';
13
13
  import { createAgenticRouter } from './agentic';
14
- import { getControlRole } from './control-role';
15
14
  import { closeDebugDatabasePools } from './diagnostics/debug-db-snapshot';
16
15
  import { startDebugSampler } from './diagnostics/debug-sampler';
17
16
  import { isDevelopmentObservabilityMode, isGraphqlObservabilityEnabled, isGraphqlObservabilityRequested, isLoopbackHost } from './diagnostics/observability';
@@ -77,7 +76,6 @@ class Server {
77
76
  constructor(opts) {
78
77
  this.opts = getEnvOptions(opts);
79
78
  const effectiveOpts = this.opts;
80
- const controlRole = getControlRole(effectiveOpts);
81
79
  const observabilityRequested = isGraphqlObservabilityRequested();
82
80
  const observabilityEnabled = isGraphqlObservabilityEnabled(effectiveOpts.server?.host);
83
81
  const app = express();
@@ -147,8 +145,7 @@ class Server {
147
145
  app.use(createContextMiddleware({
148
146
  pg: effectiveOpts.pg,
149
147
  loaders: createDefaultRegistry(),
150
- routingSchema: getRoutingSchema(effectiveOpts),
151
- controlRole
148
+ routingSchema: getRoutingSchema(effectiveOpts)
152
149
  }));
153
150
  // A suspended (or unknown) database is refused before anything is spent on
154
151
  // the request; billing and platform admins set that state, the loader reads it.
package/middleware/api.js CHANGED
@@ -10,8 +10,6 @@ const url_domains_1 = require("@constructive-io/url-domains");
10
10
  const logger_1 = require("@pgpmjs/logger");
11
11
  const server_utils_1 = require("@pgpmjs/server-utils");
12
12
  const pg_cache_1 = require("pg-cache");
13
- const pg_query_context_1 = require("pg-query-context");
14
- const control_role_1 = require("../control-role");
15
13
  const _50x_1 = __importDefault(require("../errors/50x"));
16
14
  const _404_message_1 = __importDefault(require("../errors/404-message"));
17
15
  const routing_1 = require("./routing");
@@ -56,7 +54,7 @@ const buildLoaderContext = (routingPool, opts, row) => {
56
54
  return {
57
55
  routingPool,
58
56
  routingSchema: (0, routing_1.getRoutingSchema)(opts),
59
- tenantPool: (0, pg_query_context_1.controlPlanePool)((0, pg_cache_1.getPgPool)({ ...opts.pg, database: dbname }), routingPool.role),
57
+ tenantPool: (0, pg_cache_1.getPgPool)({ ...opts.pg, database: dbname }),
60
58
  databaseId: row.database_id,
61
59
  apiId: row.api_id,
62
60
  dbname
@@ -270,7 +268,7 @@ const resolveScopedRoute = async (ctx) => {
270
268
  // Main Resolution Function
271
269
  // =============================================================================
272
270
  const getApiConfig = async (opts, req) => {
273
- const pool = (0, pg_query_context_1.controlPlanePool)((0, pg_cache_1.getPgPool)(opts.pg), (0, control_role_1.getControlRole)(opts));
271
+ const pool = (0, pg_cache_1.getPgPool)(opts.pg);
274
272
  const { domain, subdomains } = getUrlDomains(req);
275
273
  const subdomain = (0, exports.getSubdomain)(subdomains);
276
274
  const cacheKey = (0, exports.getSvcKey)(opts, req);
@@ -68,7 +68,6 @@ const createAuthenticateMiddleware = (opts) => {
68
68
  if (effectiveToken) {
69
69
  log.info(`[auth] Processing ${tokenSource} authentication`);
70
70
  const context = {
71
- role: api.anonRole,
72
71
  'jwt.claims.ip_address': req.clientIp,
73
72
  };
74
73
  if (req.get('origin')) {
@@ -7,8 +7,6 @@ const server_utils_1 = require("@pgpmjs/server-utils");
7
7
  const crypto_1 = require("crypto");
8
8
  const graphile_cache_1 = require("graphile-cache");
9
9
  const pg_cache_1 = require("pg-cache");
10
- const pg_query_context_1 = require("pg-query-context");
11
- const control_role_1 = require("../control-role");
12
10
  const routing_1 = require("./routing");
13
11
  const log = new logger_1.Logger('flush');
14
12
  const bearerToken = (req) => {
@@ -53,7 +51,7 @@ const createFlushMiddleware = (opts) => {
53
51
  };
54
52
  exports.createFlushMiddleware = createFlushMiddleware;
55
53
  const flushService = async (opts, databaseId) => {
56
- const pgPool = (0, pg_query_context_1.controlPlanePool)((0, pg_cache_1.getPgPool)(opts.pg), (0, control_role_1.getControlRole)(opts));
54
+ const pgPool = (0, pg_cache_1.getPgPool)(opts.pg);
57
55
  log.info('flushing db ' + databaseId);
58
56
  const api = new RegExp(`^api:${databaseId}:.*`);
59
57
  const schemata = new RegExp(`^schemata:${databaseId}:.*`);
@@ -13,8 +13,6 @@ const graphile_function_bindings_1 = require("graphile-function-bindings");
13
13
  const graphile_settings_1 = require("graphile-settings");
14
14
  const pg_cache_1 = require("pg-cache");
15
15
  const pg_env_1 = require("pg-env");
16
- const pg_query_context_1 = require("pg-query-context");
17
- const control_role_1 = require("../control-role");
18
16
  const observability_1 = require("../diagnostics/observability");
19
17
  const api_errors_1 = require("../errors/api-errors");
20
18
  const graphql_response_1 = require("../errors/graphql-response");
@@ -64,7 +62,7 @@ const reqLabel = (req) => (req.requestId ? `[${req.requestId}]` : '[req]');
64
62
  * plugin preset. Without settings the default preset is used
65
63
  * (everything on except aggregates).
66
64
  */
67
- const buildPreset = async (pool, schemas, anonRole, roleName, introspectionRole, controlRole, graphileOptions, databaseSettings, apiId, compute, webauthn) => {
65
+ const buildPreset = async (pool, schemas, anonRole, roleName, introspectionRole, graphileOptions, databaseSettings, apiId, compute, webauthn) => {
68
66
  const introspection = await (0, graphile_introspection_1.makeIntrospectionWiring)(pool, schemas, graphileOptions, undefined, introspectionRole);
69
67
  const configuredPreset = graphileOptions?.preset ?? {};
70
68
  const grafastCachePreset = (0, graphile_settings_1.createGrafastCacheLimitsPreset)(graphileOptions?.grafastCache);
@@ -95,7 +93,6 @@ const buildPreset = async (pool, schemas, anonRole, roleName, introspectionRole,
95
93
  ? [
96
94
  (0, graphile_function_bindings_1.createFunctionBindingsPlugin)({
97
95
  apiId,
98
- role: controlRole,
99
96
  modules: compute.modules.map((m) => ({
100
97
  computeSchema: m.schemaName,
101
98
  bindingsTable: m.bindingsTableName,
@@ -320,7 +317,7 @@ const graphile = (opts) => {
320
317
  // concurrent requests coalesce while optional plugin modules load.
321
318
  const creationPromise = (async () => {
322
319
  const compute = api.apiId ? await req.constructive?.useModule('compute') : undefined;
323
- const preset = await buildPreset(pool, schema || [], anonRole, roleName, opts.api?.introspectionRole, (0, control_role_1.getControlRole)(opts), opts.graphile, api.databaseSettings, api.apiId, compute, Boolean(api.webauthnSettings));
320
+ const preset = await buildPreset(pool, schema || [], anonRole, roleName, opts.api?.introspectionRole, opts.graphile, api.databaseSettings, api.apiId, compute, Boolean(api.webauthnSettings));
324
321
  return (0, graphile_build_stats_1.observeGraphileBuild)({
325
322
  cacheKey: key,
326
323
  serviceKey: key,
@@ -328,8 +325,7 @@ const graphile = (opts) => {
328
325
  }, () => (0, graphile_cache_1.createGraphileInstance)({
329
326
  preset,
330
327
  cacheKey: key,
331
- enableRealtime: api.databaseSettings?.enableRealtime,
332
- realtimePool: (0, pg_query_context_1.controlPlanePool)(pool, (0, control_role_1.getControlRole)(opts))
328
+ enableRealtime: api.databaseSettings?.enableRealtime
333
329
  }), { enabled: observabilityEnabled });
334
330
  })();
335
331
  creating.set(key, creationPromise);
@@ -1,4 +1,4 @@
1
- import type { ControlPlaneQueryable } from 'pg-query-context';
1
+ import { Pool } from 'pg';
2
2
  import { ApiOptions, ApiStructure } from '../types';
3
3
  /**
4
4
  * The role an API row names is the role the server will SET ROLE to; a row
@@ -41,7 +41,7 @@ export declare const isValidSchemaName: (name: string) => boolean;
41
41
  * resolver is not installed in the target database — in both cases the caller
42
42
  * treats it as a hard no-match (→ 404); there is no legacy fallback.
43
43
  */
44
- export declare const resolveRoute: (pool: ControlPlaneQueryable, schema: string, host: string) => Promise<ResolvedRoute | null>;
44
+ export declare const resolveRoute: (pool: Pool, schema: string, host: string) => Promise<ResolvedRoute | null>;
45
45
  /**
46
46
  * Map a resolved api-target route onto the ApiStructure shape consumed by the
47
47
  * rest of the middleware chain. Returns null when the route target is not an
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@constructive-io/graphql-server",
3
- "version": "5.30.0",
3
+ "version": "5.31.1",
4
4
  "author": "Constructive <developers@constructive.io>",
5
5
  "description": "Constructive GraphQL Server",
6
6
  "main": "index.js",
@@ -43,18 +43,18 @@
43
43
  "@agentic-kit/ollama": "2.15.0",
44
44
  "@constructive-io/csrf": "^0.30.1",
45
45
  "@constructive-io/errors": "^0.24.0",
46
- "@constructive-io/express-context": "^0.34.0",
47
- "@constructive-io/graphql-env": "^3.36.0",
48
- "@constructive-io/graphql-types": "^3.35.0",
46
+ "@constructive-io/express-context": "^0.34.1",
47
+ "@constructive-io/graphql-env": "^3.36.1",
48
+ "@constructive-io/graphql-types": "^3.35.1",
49
49
  "@constructive-io/llm-env": "^0.15.0",
50
- "@constructive-io/query-builder": "^3.16.9",
50
+ "@constructive-io/query-builder": "^3.16.10",
51
51
  "@constructive-io/s3-utils": "^2.34.0",
52
52
  "@constructive-io/url-domains": "^2.31.0",
53
53
  "@graphile-contrib/pg-many-to-many": "2.0.0-rc.2",
54
- "@pgpmjs/env": "^2.47.0",
54
+ "@pgpmjs/env": "^2.47.1",
55
55
  "@pgpmjs/logger": "^2.26.0",
56
- "@pgpmjs/server-utils": "^3.29.7",
57
- "@pgpmjs/types": "^2.57.0",
56
+ "@pgpmjs/server-utils": "^3.29.8",
57
+ "@pgpmjs/types": "^2.57.1",
58
58
  "@pgsql/quotes": "^18.2.4",
59
59
  "@simplewebauthn/server": "13.3.2",
60
60
  "cors": "^2.8.6",
@@ -65,20 +65,20 @@
65
65
  "grafserv": "1.0.1",
66
66
  "graphile-build": "5.1.1",
67
67
  "graphile-build-pg": "5.1.3",
68
- "graphile-cache": "^4.15.0",
68
+ "graphile-cache": "^4.15.1",
69
69
  "graphile-config": "1.1.0",
70
- "graphile-function-bindings": "^1.17.10",
70
+ "graphile-function-bindings": "^1.17.11",
71
71
  "graphile-plugin-utils": "^1.13.0",
72
- "graphile-scoped-introspection": "^0.4.10",
73
- "graphile-settings": "^6.25.2",
72
+ "graphile-scoped-introspection": "^0.4.11",
73
+ "graphile-settings": "^6.25.4",
74
74
  "graphile-utils": "5.0.3",
75
75
  "graphql": "16.13.0",
76
76
  "graphql-upload": "^13.0.0",
77
77
  "lru-cache": "^11.2.7",
78
78
  "pg": "^8.21.0",
79
- "pg-cache": "^3.29.7",
80
- "pg-env": "^1.32.0",
81
- "pg-query-context": "^2.32.0",
79
+ "pg-cache": "^3.29.8",
80
+ "pg-env": "^1.32.1",
81
+ "pg-query-context": "^2.32.1",
82
82
  "pg-sql2": "5.0.1",
83
83
  "postgraphile": "5.1.4",
84
84
  "request-ip": "^3.3.0"
@@ -93,11 +93,11 @@
93
93
  "@types/request-ip": "^0.0.41",
94
94
  "@types/supertest": "^7.2.1",
95
95
  "cookie-parser": "^1.4.7",
96
- "graphile-test": "5.17.10",
96
+ "graphile-test": "5.17.11",
97
97
  "makage": "^0.8.0",
98
98
  "nodemon": "^3.1.14",
99
99
  "supertest": "^7.2.2",
100
100
  "ts-node": "^10.9.2"
101
101
  },
102
- "gitHead": "2f506a4a4062b4bd92979c02249e0fee14af4043"
102
+ "gitHead": "8227c782c98281f7185d257a7f716fbffdcc45c2"
103
103
  }
@@ -3,7 +3,7 @@ import type { RefusalReason, RefusalRecorderStats } from '@constructive-io/expre
3
3
  import { RefusalRecorder } from '@constructive-io/express-context';
4
4
  import type { ConstructiveOptions } from '@constructive-io/graphql-types';
5
5
  import type { Request } from 'express';
6
- import { type ControlPlaneQueryable } from 'pg-query-context';
6
+ import type { Pool } from 'pg';
7
7
  /**
8
8
  * refusals/recorder — the GraphQL lane's `RefusalRecorder`.
9
9
  *
@@ -61,7 +61,7 @@ interface PlatformFlushIdentity {
61
61
  * `platform-bootstrap` service principal's user row. Throws — with the row
62
62
  * that was missing named — rather than returning a partial identity.
63
63
  */
64
- export declare const resolvePlatformFlushIdentity: (pool: ControlPlaneQueryable, principalName?: string) => Promise<PlatformFlushIdentity>;
64
+ export declare const resolvePlatformFlushIdentity: (pool: Pool, principalName?: string) => Promise<PlatformFlushIdentity>;
65
65
  export declare const platformFlushClaims: (identity: PlatformFlushIdentity) => Record<string, string>;
66
66
  export interface PlatformRefusalRecorderOptions {
67
67
  intervalMs?: number;
@@ -5,8 +5,6 @@ require("../middleware/types"); // for Request type
5
5
  const express_context_1 = require("@constructive-io/express-context");
6
6
  const logger_1 = require("@pgpmjs/logger");
7
7
  const pg_cache_1 = require("pg-cache");
8
- const pg_query_context_1 = require("pg-query-context");
9
- const control_role_1 = require("../control-role");
10
8
  const log = new logger_1.Logger('refusals');
11
9
  /**
12
10
  * refusals/recorder — the GraphQL lane's `RefusalRecorder`.
@@ -131,17 +129,16 @@ exports.platformFlushClaims = platformFlushClaims;
131
129
  */
132
130
  const createPlatformRefusalRecorder = (opts, recorderOpts = {}) => {
133
131
  const pool = (0, pg_cache_1.getPgPool)(opts.pg);
134
- const role = (0, control_role_1.getControlRole)(opts);
135
132
  let cached = null;
136
133
  const claims = async () => {
137
134
  if (cached)
138
135
  return cached;
139
- cached = (0, exports.platformFlushClaims)(await (0, exports.resolvePlatformFlushIdentity)((0, pg_query_context_1.controlPlanePool)(pool, role), recorderOpts.principalName));
136
+ cached = (0, exports.platformFlushClaims)(await (0, exports.resolvePlatformFlushIdentity)(pool, recorderOpts.principalName));
140
137
  log.info(`[refusals] flushing as '${recorderOpts.principalName ?? exports.PLATFORM_BOOTSTRAP_PRINCIPAL}'`);
141
138
  return cached;
142
139
  };
143
140
  return new express_context_1.RefusalRecorder({
144
- sink: (0, express_context_1.createRecordRefusalsSink)({ pool, role, claims }),
141
+ sink: (0, express_context_1.createRecordRefusalsSink)({ pool, claims }),
145
142
  intervalMs: recorderOpts.intervalMs,
146
143
  jitterMs: recorderOpts.jitterMs,
147
144
  maxKeys: recorderOpts.maxKeys
package/server.js CHANGED
@@ -17,7 +17,6 @@ const graphql_upload_1 = __importDefault(require("graphql-upload"));
17
17
  const pg_cache_1 = require("pg-cache");
18
18
  const request_ip_1 = __importDefault(require("request-ip"));
19
19
  const agentic_1 = require("./agentic");
20
- const control_role_1 = require("./control-role");
21
20
  const debug_db_snapshot_1 = require("./diagnostics/debug-db-snapshot");
22
21
  const debug_sampler_1 = require("./diagnostics/debug-sampler");
23
22
  const observability_1 = require("./diagnostics/observability");
@@ -84,7 +83,6 @@ class Server {
84
83
  constructor(opts) {
85
84
  this.opts = (0, graphql_env_1.getEnvOptions)(opts);
86
85
  const effectiveOpts = this.opts;
87
- const controlRole = (0, control_role_1.getControlRole)(effectiveOpts);
88
86
  const observabilityRequested = (0, observability_1.isGraphqlObservabilityRequested)();
89
87
  const observabilityEnabled = (0, observability_1.isGraphqlObservabilityEnabled)(effectiveOpts.server?.host);
90
88
  const app = (0, express_1.default)();
@@ -154,8 +152,7 @@ class Server {
154
152
  app.use((0, express_context_1.createContextMiddleware)({
155
153
  pg: effectiveOpts.pg,
156
154
  loaders: (0, express_context_1.createDefaultRegistry)(),
157
- routingSchema: (0, routing_1.getRoutingSchema)(effectiveOpts),
158
- controlRole
155
+ routingSchema: (0, routing_1.getRoutingSchema)(effectiveOpts)
159
156
  }));
160
157
  // A suspended (or unknown) database is refused before anything is spent on
161
158
  // the request; billing and platform admins set that state, the loader reads it.
package/control-role.d.ts DELETED
@@ -1,8 +0,0 @@
1
- import type { ApiOptions } from '@constructive-io/graphql-types';
2
- /**
3
- * The role control-plane queries run as (`api.controlRole` / `API_CONTROL_ROLE`).
4
- * The login role is NOINHERIT and holds no privileges, so there is no default.
5
- */
6
- export declare const getControlRole: (opts: {
7
- api?: ApiOptions;
8
- }) => string;
package/control-role.js DELETED
@@ -1,15 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getControlRole = void 0;
4
- /**
5
- * The role control-plane queries run as (`api.controlRole` / `API_CONTROL_ROLE`).
6
- * The login role is NOINHERIT and holds no privileges, so there is no default.
7
- */
8
- const getControlRole = (opts) => {
9
- const role = opts.api?.controlRole;
10
- if (!role) {
11
- throw new Error('GraphQL server: api.controlRole (API_CONTROL_ROLE) is required — control-plane queries run as that role');
12
- }
13
- return role;
14
- };
15
- exports.getControlRole = getControlRole;
@@ -1,11 +0,0 @@
1
- /**
2
- * The role control-plane queries run as (`api.controlRole` / `API_CONTROL_ROLE`).
3
- * The login role is NOINHERIT and holds no privileges, so there is no default.
4
- */
5
- export const getControlRole = (opts) => {
6
- const role = opts.api?.controlRole;
7
- if (!role) {
8
- throw new Error('GraphQL server: api.controlRole (API_CONTROL_ROLE) is required — control-plane queries run as that role');
9
- }
10
- return role;
11
- };