@constructive-io/graphql-server 5.3.3 → 5.4.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/README.md CHANGED
@@ -103,7 +103,7 @@ For the operational workflow, sampler output, and heap snapshot usage, see [docs
103
103
 
104
104
  This is a production-only server: every request is resolved through the scoped-routing plane. There is no static single-tenant mode and no flag to disable routing. For single-database local development without route resolution or a database id, use [`@constructive-io/graphql-dev-server`](../dev-server/README.md).
105
105
 
106
- - The server resolves the request host with a single `resolve_route()` call against the compiled route bindings in the scoped routing schema (`API_SCOPED_ROUTING_SCHEMA`, default `constructive_routing_public`), mapping host → tenant/api/database/role.
106
+ - The server resolves the request host with a single `resolve_route()` call against the compiled route bindings in the scoped routing schema (`API_ROUTING_SCHEMA`, default `routing_public`), mapping host → tenant/api/database/role.
107
107
  - Only APIs where `api.is_public` matches `API_IS_PUBLIC` are served.
108
108
  - In private mode (`API_IS_PUBLIC=false`), you can override with headers:
109
109
  - `X-Api-Name` + `X-Database-Id`
@@ -126,10 +126,10 @@ Configuration is merged from defaults, config files, and env vars via `@construc
126
126
  | `FEATURES_SIMPLE_INFLECTION` | Enable simple inflection | `true` |
127
127
  | `FEATURES_OPPOSITE_BASE_NAMES` | Enable opposite base names | `true` |
128
128
  | `FEATURES_POSTGIS` | Enable PostGIS support | `true` |
129
- | `API_SCOPED_ROUTING_SCHEMA` | Schema containing `resolve_route()` | `constructive_routing_public` |
129
+ | `API_ROUTING_SCHEMA` | Schema containing `resolve_route()` | `routing_public` |
130
130
  | `API_IS_PUBLIC` | Serve public APIs only | `true` |
131
131
  | `API_EXPOSED_SCHEMAS` | Additional schemas to expose | empty |
132
- | `API_META_SCHEMAS` | Meta schemas to query | `constructive_routing_public,metaschema_public,metaschema_modules_public` |
132
+ | `API_META_SCHEMAS` | Meta schemas to query | `routing_public,metaschema_public,metaschema_modules_public` |
133
133
  | `API_ANON_ROLE` | Anonymous role name | `administrator` |
134
134
  | `API_ROLE_NAME` | Authenticated role name | `administrator` |
135
135
  | `GRAPHQL_OBSERVABILITY_ENABLED` | Master switch for debug routes and sampler | `false` |
@@ -6,7 +6,7 @@ import { svcCache } from '@pgpmjs/server-utils';
6
6
  import { getPgPool } from 'pg-cache';
7
7
  import errorPage50x from '../errors/50x';
8
8
  import errorPage404Message from '../errors/404-message';
9
- import { resolveRoute, routeToApiStructure } from './routing';
9
+ import { getRoutingSchema, isValidSchemaName, resolveRoute, routeToApiStructure } from './routing';
10
10
  const log = new Logger('api');
11
11
  // =============================================================================
12
12
  // Module Loader Registry (replaces inline SQL queries for per-db config)
@@ -17,7 +17,7 @@ const defaultRegistry = createDefaultRegistry();
17
17
  // =============================================================================
18
18
  // Private-header X-Api-Name lookup against the scoped routing plane.
19
19
  // `is_published` is the routing-plane analog of the legacy `is_public` column.
20
- const SCOPED_API_NAME_LOOKUP_SQL = `
20
+ const scopedApiNameLookupSql = (routingSchema) => `
21
21
  SELECT
22
22
  a.id as api_id,
23
23
  a.database_id,
@@ -26,8 +26,8 @@ const SCOPED_API_NAME_LOOKUP_SQL = `
26
26
  a.anon_role,
27
27
  a.is_published as is_public,
28
28
  COALESCE(array_agg(s.schema_name) FILTER (WHERE s.schema_name IS NOT NULL), '{}') as schemas
29
- FROM constructive_routing_public.apis a
30
- LEFT JOIN constructive_routing_public.api_schemas aps ON a.id = aps.api_id
29
+ FROM "${routingSchema}".apis a
30
+ LEFT JOIN "${routingSchema}".api_schemas aps ON a.id = aps.api_id
31
31
  LEFT JOIN metaschema_public.schema s ON aps.schema_id = s.id
32
32
  WHERE a.database_id = $1
33
33
  AND a.name = $2
@@ -41,6 +41,7 @@ const SCOPED_API_NAME_LOOKUP_SQL = `
41
41
  */
42
42
  const buildLoaderContext = (routingPool, opts, row) => ({
43
43
  routingPool,
44
+ routingSchema: getRoutingSchema(opts),
44
45
  tenantPool: getPgPool({ ...opts.pg, database: row.dbname }),
45
46
  databaseId: row.database_id,
46
47
  apiId: row.api_id,
@@ -136,7 +137,6 @@ const toApiStructure = (row, opts, settings = {}) => ({
136
137
  anonRole: row.anon_role || 'anon',
137
138
  roleName: row.role_name || 'authenticated',
138
139
  schema: row.schemas || [],
139
- apiModules: [],
140
140
  rlsModule: settings.rlsModule,
141
141
  domains: [],
142
142
  databaseId: row.database_id,
@@ -152,7 +152,6 @@ const createAdminStructure = (opts, schemas, databaseId) => ({
152
152
  anonRole: 'administrator',
153
153
  roleName: 'administrator',
154
154
  schema: schemas,
155
- apiModules: [],
156
155
  domains: [],
157
156
  databaseId,
158
157
  isPublic: false
@@ -164,8 +163,13 @@ const validateSchemata = async (pool, schemas) => {
164
163
  const result = await pool.query(`SELECT schema_name FROM information_schema.schemata WHERE schema_name = ANY($1::text[])`, [schemas]);
165
164
  return result.rows.map((row) => row.schema_name);
166
165
  };
167
- const queryByApiName = async (pool, databaseId, name, isPublic) => {
168
- const result = await pool.query(SCOPED_API_NAME_LOOKUP_SQL, [databaseId, name, isPublic]);
166
+ const queryByApiName = async (pool, opts, databaseId, name, isPublic) => {
167
+ const routingSchema = getRoutingSchema(opts);
168
+ if (!isValidSchemaName(routingSchema)) {
169
+ log.warn(`[api-name-lookup] invalid routing schema name: ${routingSchema}`);
170
+ return null;
171
+ }
172
+ const result = await pool.query(scopedApiNameLookupSql(routingSchema), [databaseId, name, isPublic]);
169
173
  return result.rows[0] ?? null;
170
174
  };
171
175
  // =============================================================================
@@ -193,7 +197,7 @@ const resolveApiNameHeader = async (ctx) => {
193
197
  if (!headers.databaseId)
194
198
  return null;
195
199
  const isPublic = opts.api?.isPublic ?? false;
196
- const row = await queryByApiName(pool, headers.databaseId, headers.apiName, isPublic);
200
+ const row = await queryByApiName(pool, opts, headers.databaseId, headers.apiName, isPublic);
197
201
  if (!row) {
198
202
  log.debug(`[api-name-lookup] No API found for databaseId=${headers.databaseId} name=${headers.apiName}`);
199
203
  return null;
@@ -216,7 +220,7 @@ const resolveMetaSchemaHeader = (ctx, validatedSchemas) => {
216
220
  */
217
221
  const resolveScopedRoute = async (ctx) => {
218
222
  const { opts, pool, host } = ctx;
219
- const schema = opts.api?.scopedRoutingSchema || 'constructive_routing_public';
223
+ const schema = getRoutingSchema(opts);
220
224
  const route = await resolveRoute(pool, schema, host);
221
225
  if (!route)
222
226
  return null;
@@ -7,7 +7,6 @@ import './types'; // for Request type
7
7
  * Feature parity + compatibility:
8
8
  * - Respects a global fallback origin (e.g. from env/CLI) for quick overrides.
9
9
  * - Reads per-API CORS origins from typed cors_settings table (via req.api.corsOrigins).
10
- * - Falls back to legacy api_modules CORS data for backwards compatibility.
11
10
  * - Always allows localhost to ease development.
12
11
  *
13
12
  * Usage:
@@ -32,13 +31,10 @@ export const cors = (fallbackOrigin) => {
32
31
  // createApiMiddleware runs before this in server.ts, so req.api should be set
33
32
  const api = req.api;
34
33
  if (api) {
35
- // Typed cors_settings origins (preferred)
34
+ // Typed cors_settings origins
36
35
  const typedOrigins = api.corsOrigins || [];
37
- // Legacy api_modules CORS data (fallback)
38
- const corsModules = (api.apiModules || []).filter((m) => m.name === 'cors');
39
- const legacyOrigins = corsModules.reduce((m, mod) => [...mod.data.urls, ...m], []);
40
36
  const siteUrls = api.domains || [];
41
- const listOfDomains = [...typedOrigins, ...legacyOrigins, ...siteUrls];
37
+ const listOfDomains = [...typedOrigins, ...siteUrls];
42
38
  if (origin && listOfDomains.includes(origin)) {
43
39
  return callback(null, true);
44
40
  }
@@ -3,6 +3,7 @@ import { Logger } from '@pgpmjs/logger';
3
3
  import { svcCache } from '@pgpmjs/server-utils';
4
4
  import { graphileCache } from 'graphile-cache';
5
5
  import { getPgPool } from 'pg-cache';
6
+ import { getRoutingSchema, isValidSchemaName } from './routing';
6
7
  const log = new Logger('flush');
7
8
  export const flush = async (req, res, next) => {
8
9
  if (req.url === '/flush') {
@@ -28,8 +29,13 @@ export const flushService = async (opts, databaseId) => {
28
29
  }
29
30
  });
30
31
  }
32
+ const routingSchema = getRoutingSchema(opts);
33
+ if (!isValidSchemaName(routingSchema)) {
34
+ log.warn(`[flush] invalid routing schema name: ${routingSchema}`);
35
+ return;
36
+ }
31
37
  const svc = await pgPool.query(`SELECT hostname
32
- FROM constructive_routing_public.domains
38
+ FROM "${routingSchema}".domains
33
39
  WHERE database_id = $1`, [databaseId]);
34
40
  if (svc.rowCount === 0)
35
41
  return;
@@ -1,7 +1,11 @@
1
1
  import { Logger } from '@pgpmjs/logger';
2
2
  const log = new Logger('routing');
3
3
  const RESOLVER_FUNCTION = 'resolve_route';
4
- const isValidSchemaName = (name) => /^[a-z_][a-z0-9_]*$/.test(name);
4
+ /** Published logical name of the database-scope routing plane. */
5
+ export const DEFAULT_ROUTING_SCHEMA = 'routing_public';
6
+ /** The routing-plane schema in effect: configured override or the published default. */
7
+ export const getRoutingSchema = (opts) => opts.api?.routingSchema || DEFAULT_ROUTING_SCHEMA;
8
+ export const isValidSchemaName = (name) => /^[a-z_][a-z0-9_]*$/.test(name);
5
9
  /**
6
10
  * Resolve a hostname through the compiled scoped-routing plane (host-only:
7
11
  * path/method routing belongs to Traefik/Ingress, not the server).
@@ -54,7 +58,6 @@ export const routeToApiStructure = (route, opts) => {
54
58
  anonRole: config.anon_role || 'anon',
55
59
  roleName: config.role_name || 'authenticated',
56
60
  schema: config.schemas,
57
- apiModules: [],
58
61
  domains: [],
59
62
  databaseId: config.database_id,
60
63
  isPublic: config.is_public ?? (opts.api?.isPublic ?? false)
package/esm/server.js CHANGED
@@ -30,6 +30,7 @@ import { createDebugDatabaseMiddleware } from './middleware/observability/debug-
30
30
  import { debugMemory } from './middleware/observability/debug-memory';
31
31
  import { localObservabilityOnly } from './middleware/observability/guard';
32
32
  import { createRequestLogger } from './middleware/observability/request-logger';
33
+ import { getRoutingSchema } from './middleware/routing';
33
34
  const log = new Logger('server');
34
35
  /**
35
36
  * Creates and starts a GraphQL server instance
@@ -85,7 +86,7 @@ class Server {
85
86
  serverHost: effectiveOpts.server?.host,
86
87
  serverPort: effectiveOpts.server?.port,
87
88
  apiIsPublic: apiOpts.isPublic,
88
- scopedRoutingSchema: apiOpts.scopedRoutingSchema,
89
+ routingSchema: apiOpts.routingSchema,
89
90
  metaSchemas: apiOpts.metaSchemas?.join(',') || 'default',
90
91
  observabilityEnabled
91
92
  });
@@ -136,7 +137,11 @@ class Server {
136
137
  app.use(requestLogger);
137
138
  app.use(api);
138
139
  app.use(authenticate);
139
- app.use(createContextMiddleware({ pg: effectiveOpts.pg, loaders: createDefaultRegistry() }));
140
+ app.use(createContextMiddleware({
141
+ pg: effectiveOpts.pg,
142
+ loaders: createDefaultRegistry(),
143
+ routingSchema: getRoutingSchema(effectiveOpts)
144
+ }));
140
145
  app.use(createCaptchaMiddleware());
141
146
  // CSRF protection for cookie-authenticated requests
142
147
  // Skip CSRF for Bearer token auth (not vulnerable to CSRF) and anonymous requests
package/middleware/api.js CHANGED
@@ -23,7 +23,7 @@ const defaultRegistry = (0, express_context_1.createDefaultRegistry)();
23
23
  // =============================================================================
24
24
  // Private-header X-Api-Name lookup against the scoped routing plane.
25
25
  // `is_published` is the routing-plane analog of the legacy `is_public` column.
26
- const SCOPED_API_NAME_LOOKUP_SQL = `
26
+ const scopedApiNameLookupSql = (routingSchema) => `
27
27
  SELECT
28
28
  a.id as api_id,
29
29
  a.database_id,
@@ -32,8 +32,8 @@ const SCOPED_API_NAME_LOOKUP_SQL = `
32
32
  a.anon_role,
33
33
  a.is_published as is_public,
34
34
  COALESCE(array_agg(s.schema_name) FILTER (WHERE s.schema_name IS NOT NULL), '{}') as schemas
35
- FROM constructive_routing_public.apis a
36
- LEFT JOIN constructive_routing_public.api_schemas aps ON a.id = aps.api_id
35
+ FROM "${routingSchema}".apis a
36
+ LEFT JOIN "${routingSchema}".api_schemas aps ON a.id = aps.api_id
37
37
  LEFT JOIN metaschema_public.schema s ON aps.schema_id = s.id
38
38
  WHERE a.database_id = $1
39
39
  AND a.name = $2
@@ -47,6 +47,7 @@ const SCOPED_API_NAME_LOOKUP_SQL = `
47
47
  */
48
48
  const buildLoaderContext = (routingPool, opts, row) => ({
49
49
  routingPool,
50
+ routingSchema: (0, routing_1.getRoutingSchema)(opts),
50
51
  tenantPool: (0, pg_cache_1.getPgPool)({ ...opts.pg, database: row.dbname }),
51
52
  databaseId: row.database_id,
52
53
  apiId: row.api_id,
@@ -144,7 +145,6 @@ const toApiStructure = (row, opts, settings = {}) => ({
144
145
  anonRole: row.anon_role || 'anon',
145
146
  roleName: row.role_name || 'authenticated',
146
147
  schema: row.schemas || [],
147
- apiModules: [],
148
148
  rlsModule: settings.rlsModule,
149
149
  domains: [],
150
150
  databaseId: row.database_id,
@@ -160,7 +160,6 @@ const createAdminStructure = (opts, schemas, databaseId) => ({
160
160
  anonRole: 'administrator',
161
161
  roleName: 'administrator',
162
162
  schema: schemas,
163
- apiModules: [],
164
163
  domains: [],
165
164
  databaseId,
166
165
  isPublic: false
@@ -172,8 +171,13 @@ const validateSchemata = async (pool, schemas) => {
172
171
  const result = await pool.query(`SELECT schema_name FROM information_schema.schemata WHERE schema_name = ANY($1::text[])`, [schemas]);
173
172
  return result.rows.map((row) => row.schema_name);
174
173
  };
175
- const queryByApiName = async (pool, databaseId, name, isPublic) => {
176
- const result = await pool.query(SCOPED_API_NAME_LOOKUP_SQL, [databaseId, name, isPublic]);
174
+ const queryByApiName = async (pool, opts, databaseId, name, isPublic) => {
175
+ const routingSchema = (0, routing_1.getRoutingSchema)(opts);
176
+ if (!(0, routing_1.isValidSchemaName)(routingSchema)) {
177
+ log.warn(`[api-name-lookup] invalid routing schema name: ${routingSchema}`);
178
+ return null;
179
+ }
180
+ const result = await pool.query(scopedApiNameLookupSql(routingSchema), [databaseId, name, isPublic]);
177
181
  return result.rows[0] ?? null;
178
182
  };
179
183
  // =============================================================================
@@ -201,7 +205,7 @@ const resolveApiNameHeader = async (ctx) => {
201
205
  if (!headers.databaseId)
202
206
  return null;
203
207
  const isPublic = opts.api?.isPublic ?? false;
204
- const row = await queryByApiName(pool, headers.databaseId, headers.apiName, isPublic);
208
+ const row = await queryByApiName(pool, opts, headers.databaseId, headers.apiName, isPublic);
205
209
  if (!row) {
206
210
  log.debug(`[api-name-lookup] No API found for databaseId=${headers.databaseId} name=${headers.apiName}`);
207
211
  return null;
@@ -224,7 +228,7 @@ const resolveMetaSchemaHeader = (ctx, validatedSchemas) => {
224
228
  */
225
229
  const resolveScopedRoute = async (ctx) => {
226
230
  const { opts, pool, host } = ctx;
227
- const schema = opts.api?.scopedRoutingSchema || 'constructive_routing_public';
231
+ const schema = (0, routing_1.getRoutingSchema)(opts);
228
232
  const route = await (0, routing_1.resolveRoute)(pool, schema, host);
229
233
  if (!route)
230
234
  return null;
@@ -6,7 +6,6 @@ import './types';
6
6
  * Feature parity + compatibility:
7
7
  * - Respects a global fallback origin (e.g. from env/CLI) for quick overrides.
8
8
  * - Reads per-API CORS origins from typed cors_settings table (via req.api.corsOrigins).
9
- * - Falls back to legacy api_modules CORS data for backwards compatibility.
10
9
  * - Always allows localhost to ease development.
11
10
  *
12
11
  * Usage:
@@ -13,7 +13,6 @@ require("./types"); // for Request type
13
13
  * Feature parity + compatibility:
14
14
  * - Respects a global fallback origin (e.g. from env/CLI) for quick overrides.
15
15
  * - Reads per-API CORS origins from typed cors_settings table (via req.api.corsOrigins).
16
- * - Falls back to legacy api_modules CORS data for backwards compatibility.
17
16
  * - Always allows localhost to ease development.
18
17
  *
19
18
  * Usage:
@@ -38,13 +37,10 @@ const cors = (fallbackOrigin) => {
38
37
  // createApiMiddleware runs before this in server.ts, so req.api should be set
39
38
  const api = req.api;
40
39
  if (api) {
41
- // Typed cors_settings origins (preferred)
40
+ // Typed cors_settings origins
42
41
  const typedOrigins = api.corsOrigins || [];
43
- // Legacy api_modules CORS data (fallback)
44
- const corsModules = (api.apiModules || []).filter((m) => m.name === 'cors');
45
- const legacyOrigins = corsModules.reduce((m, mod) => [...mod.data.urls, ...m], []);
46
42
  const siteUrls = api.domains || [];
47
- const listOfDomains = [...typedOrigins, ...legacyOrigins, ...siteUrls];
43
+ const listOfDomains = [...typedOrigins, ...siteUrls];
48
44
  if (origin && listOfDomains.includes(origin)) {
49
45
  return callback(null, true);
50
46
  }
@@ -6,6 +6,7 @@ const logger_1 = require("@pgpmjs/logger");
6
6
  const server_utils_1 = require("@pgpmjs/server-utils");
7
7
  const graphile_cache_1 = require("graphile-cache");
8
8
  const pg_cache_1 = require("pg-cache");
9
+ const routing_1 = require("./routing");
9
10
  const log = new logger_1.Logger('flush');
10
11
  const flush = async (req, res, next) => {
11
12
  if (req.url === '/flush') {
@@ -32,8 +33,13 @@ const flushService = async (opts, databaseId) => {
32
33
  }
33
34
  });
34
35
  }
36
+ const routingSchema = (0, routing_1.getRoutingSchema)(opts);
37
+ if (!(0, routing_1.isValidSchemaName)(routingSchema)) {
38
+ log.warn(`[flush] invalid routing schema name: ${routingSchema}`);
39
+ return;
40
+ }
35
41
  const svc = await pgPool.query(`SELECT hostname
36
- FROM constructive_routing_public.domains
42
+ FROM "${routingSchema}".domains
37
43
  WHERE database_id = $1`, [databaseId]);
38
44
  if (svc.rowCount === 0)
39
45
  return;
@@ -19,6 +19,15 @@ export interface ResolvedRoute {
19
19
  tls_status: string | null;
20
20
  tls_secret_name: string | null;
21
21
  }
22
+ /** Published logical name of the database-scope routing plane. */
23
+ export declare const DEFAULT_ROUTING_SCHEMA = "routing_public";
24
+ /** The routing-plane schema in effect: configured override or the published default. */
25
+ export declare const getRoutingSchema: (opts: {
26
+ api?: {
27
+ routingSchema?: string;
28
+ };
29
+ }) => string;
30
+ export declare const isValidSchemaName: (name: string) => boolean;
22
31
  /**
23
32
  * Resolve a hostname through the compiled scoped-routing plane (host-only:
24
33
  * path/method routing belongs to Traefik/Ingress, not the server).
@@ -1,10 +1,16 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.routeToApiStructure = exports.resolveRoute = void 0;
3
+ exports.routeToApiStructure = exports.resolveRoute = exports.isValidSchemaName = exports.getRoutingSchema = exports.DEFAULT_ROUTING_SCHEMA = void 0;
4
4
  const logger_1 = require("@pgpmjs/logger");
5
5
  const log = new logger_1.Logger('routing');
6
6
  const RESOLVER_FUNCTION = 'resolve_route';
7
+ /** Published logical name of the database-scope routing plane. */
8
+ exports.DEFAULT_ROUTING_SCHEMA = 'routing_public';
9
+ /** The routing-plane schema in effect: configured override or the published default. */
10
+ const getRoutingSchema = (opts) => opts.api?.routingSchema || exports.DEFAULT_ROUTING_SCHEMA;
11
+ exports.getRoutingSchema = getRoutingSchema;
7
12
  const isValidSchemaName = (name) => /^[a-z_][a-z0-9_]*$/.test(name);
13
+ exports.isValidSchemaName = isValidSchemaName;
8
14
  /**
9
15
  * Resolve a hostname through the compiled scoped-routing plane (host-only:
10
16
  * path/method routing belongs to Traefik/Ingress, not the server).
@@ -13,7 +19,7 @@ const isValidSchemaName = (name) => /^[a-z_][a-z0-9_]*$/.test(name);
13
19
  * treats it as a hard no-match (→ 404); there is no legacy fallback.
14
20
  */
15
21
  const resolveRoute = async (pool, schema, host) => {
16
- if (!isValidSchemaName(schema)) {
22
+ if (!(0, exports.isValidSchemaName)(schema)) {
17
23
  log.warn(`[resolve-route] invalid routing schema name: ${schema}`);
18
24
  return null;
19
25
  }
@@ -58,7 +64,6 @@ const routeToApiStructure = (route, opts) => {
58
64
  anonRole: config.anon_role || 'anon',
59
65
  roleName: config.role_name || 'authenticated',
60
66
  schema: config.schemas,
61
- apiModules: [],
62
67
  domains: [],
63
68
  databaseId: config.database_id,
64
69
  isPublic: config.is_public ?? (opts.api?.isPublic ?? false)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@constructive-io/graphql-server",
3
- "version": "5.3.3",
3
+ "version": "5.4.0",
4
4
  "author": "Constructive <developers@constructive.io>",
5
5
  "description": "Constructive GraphQL Server",
6
6
  "main": "index.js",
@@ -42,9 +42,9 @@
42
42
  ],
43
43
  "dependencies": {
44
44
  "@constructive-io/csrf": "^0.18.2",
45
- "@constructive-io/express-context": "^0.14.2",
46
- "@constructive-io/graphql-env": "^3.19.2",
47
- "@constructive-io/graphql-types": "^3.18.2",
45
+ "@constructive-io/express-context": "^0.15.0",
46
+ "@constructive-io/graphql-env": "^3.20.0",
47
+ "@constructive-io/graphql-types": "^3.19.0",
48
48
  "@constructive-io/query-builder": "^3.1.2",
49
49
  "@constructive-io/s3-utils": "^2.21.2",
50
50
  "@constructive-io/url-domains": "^2.20.2",
@@ -53,7 +53,7 @@
53
53
  "@pgpmjs/logger": "^2.15.2",
54
54
  "@pgpmjs/server-utils": "^3.16.2",
55
55
  "@pgpmjs/types": "^2.37.2",
56
- "agentic-server": "0.12.2",
56
+ "agentic-server": "0.13.0",
57
57
  "cors": "^2.8.6",
58
58
  "deepmerge": "^4.3.1",
59
59
  "express": "^5.2.1",
@@ -64,8 +64,8 @@
64
64
  "graphile-build-pg": "5.0.2",
65
65
  "graphile-cache": "^4.1.2",
66
66
  "graphile-config": "1.0.1",
67
- "graphile-function-bindings": "^1.1.2",
68
- "graphile-settings": "^6.1.3",
67
+ "graphile-function-bindings": "^1.2.0",
68
+ "graphile-settings": "^6.2.0",
69
69
  "graphile-utils": "5.0.1",
70
70
  "graphql": "16.13.0",
71
71
  "graphql-upload": "^13.0.0",
@@ -87,10 +87,10 @@
87
87
  "@types/pg": "^8.20.0",
88
88
  "@types/request-ip": "^0.0.41",
89
89
  "cookie-parser": "^1.4.7",
90
- "graphile-test": "5.1.2",
90
+ "graphile-test": "5.2.0",
91
91
  "makage": "^0.3.0",
92
92
  "nodemon": "^3.1.14",
93
93
  "ts-node": "^10.9.2"
94
94
  },
95
- "gitHead": "0e3ee42c44c9b35ffac4bf1faa6775eb69311ec4"
95
+ "gitHead": "518e7ab2270661c64875258436d90fdf76b4b336"
96
96
  }
package/server.js CHANGED
@@ -36,6 +36,7 @@ const debug_db_1 = require("./middleware/observability/debug-db");
36
36
  const debug_memory_1 = require("./middleware/observability/debug-memory");
37
37
  const guard_1 = require("./middleware/observability/guard");
38
38
  const request_logger_1 = require("./middleware/observability/request-logger");
39
+ const routing_1 = require("./middleware/routing");
39
40
  const log = new logger_1.Logger('server');
40
41
  /**
41
42
  * Creates and starts a GraphQL server instance
@@ -92,7 +93,7 @@ class Server {
92
93
  serverHost: effectiveOpts.server?.host,
93
94
  serverPort: effectiveOpts.server?.port,
94
95
  apiIsPublic: apiOpts.isPublic,
95
- scopedRoutingSchema: apiOpts.scopedRoutingSchema,
96
+ routingSchema: apiOpts.routingSchema,
96
97
  metaSchemas: apiOpts.metaSchemas?.join(',') || 'default',
97
98
  observabilityEnabled
98
99
  });
@@ -143,7 +144,11 @@ class Server {
143
144
  app.use(requestLogger);
144
145
  app.use(api);
145
146
  app.use(authenticate);
146
- app.use((0, express_context_1.createContextMiddleware)({ pg: effectiveOpts.pg, loaders: (0, express_context_1.createDefaultRegistry)() }));
147
+ app.use((0, express_context_1.createContextMiddleware)({
148
+ pg: effectiveOpts.pg,
149
+ loaders: (0, express_context_1.createDefaultRegistry)(),
150
+ routingSchema: (0, routing_1.getRoutingSchema)(effectiveOpts)
151
+ }));
147
152
  app.use((0, captcha_1.createCaptchaMiddleware)());
148
153
  // CSRF protection for cookie-authenticated requests
149
154
  // Skip CSRF for Bearer token auth (not vulnerable to CSRF) and anonymous requests
package/types.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { PgpmOptions } from '@pgpmjs/types';
2
2
  import type { ApiOptions as ApiConfig } from '@constructive-io/graphql-types';
3
- export type { ApiConfigResult, ApiError, ApiModule, ApiStructure, AuthSettings, CorsModuleData, DatabaseSettings, GenericModuleData, PubkeyChallengeSettings, PublicKeyChallengeData, RlsModule, WebauthnSettings, } from '@constructive-io/express-context';
3
+ export type { ApiConfigResult, ApiError, ApiStructure, AuthSettings, DatabaseSettings, PubkeyChallengeSettings, RlsModule, WebauthnSettings, } from '@constructive-io/express-context';
4
4
  export type ApiOptions = PgpmOptions & {
5
5
  api?: ApiConfig;
6
6
  };