@constructive-io/graphql-server 5.33.4 → 5.33.5
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/esm/middleware/api.js +12 -19
- package/esm/middleware/auth.js +1 -5
- package/esm/middleware/graphile.js +7 -14
- package/esm/middleware/observability/request-logger.js +2 -2
- package/esm/middleware/routing.js +4 -3
- package/middleware/api.js +11 -18
- package/middleware/auth.js +1 -5
- package/middleware/graphile.js +7 -14
- package/middleware/observability/request-logger.js +2 -2
- package/middleware/routing.d.ts +2 -0
- package/middleware/routing.js +6 -4
- package/package.json +4 -4
package/esm/middleware/api.js
CHANGED
|
@@ -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 { requireApiRole, resolveRouteAcrossPlanes, routeToApiStructure } from './routing';
|
|
9
|
+
import { requireApiRole, resolveRouteAcrossPlanes, routeToApiStructure, servedDatabasePool } from './routing';
|
|
10
10
|
const log = new Logger('api');
|
|
11
11
|
// =============================================================================
|
|
12
12
|
// Module Loader Registry (replaces inline SQL queries for per-db config)
|
|
@@ -23,7 +23,7 @@ const defaultRegistry = createDefaultRegistry();
|
|
|
23
23
|
// `is_published` governs cross-scope route visibility, not internal
|
|
24
24
|
// addressability, so it does not filter this lookup.
|
|
25
25
|
const API_BY_NAME_SQL = `
|
|
26
|
-
SELECT api_id, api_database_id AS database_id,
|
|
26
|
+
SELECT api_id, api_database_id AS database_id, role_name, anon_role, is_public, schemas
|
|
27
27
|
FROM function_resolution.api_by_name($1, $2)
|
|
28
28
|
LIMIT 1
|
|
29
29
|
`;
|
|
@@ -31,18 +31,12 @@ const API_BY_NAME_SQL = `
|
|
|
31
31
|
* Build a LoaderContext from the API row and options.
|
|
32
32
|
* This is used to resolve per-database module settings via the loader registry.
|
|
33
33
|
*/
|
|
34
|
-
const buildLoaderContext = (routingPool, opts, row) => {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
tenantPool: getPgPool({ ...opts.pg, database: dbname }),
|
|
41
|
-
databaseId: row.database_id,
|
|
42
|
-
apiId: row.api_id,
|
|
43
|
-
dbname
|
|
44
|
-
};
|
|
45
|
-
};
|
|
34
|
+
const buildLoaderContext = (routingPool, opts, row) => ({
|
|
35
|
+
routingPool,
|
|
36
|
+
tenantPool: servedDatabasePool(opts),
|
|
37
|
+
databaseId: row.database_id,
|
|
38
|
+
apiId: row.api_id
|
|
39
|
+
});
|
|
46
40
|
/**
|
|
47
41
|
* Resolve all per-database module settings in parallel via the loader registry.
|
|
48
42
|
* Each loader independently caches by databaseId — repeated calls are cheap.
|
|
@@ -129,7 +123,7 @@ export const getSvcKey = (opts, req) => {
|
|
|
129
123
|
};
|
|
130
124
|
const toApiStructure = (row, opts, settings = {}) => ({
|
|
131
125
|
apiId: row.api_id,
|
|
132
|
-
|
|
126
|
+
tenantPool: servedDatabasePool(opts),
|
|
133
127
|
anonRole: requireApiRole('anon_role', row.anon_role, row.api_id),
|
|
134
128
|
roleName: requireApiRole('role_name', row.role_name, row.api_id),
|
|
135
129
|
schema: row.schemas || [],
|
|
@@ -144,7 +138,7 @@ const toApiStructure = (row, opts, settings = {}) => ({
|
|
|
144
138
|
webauthnSettings: settings.webauthnSettings
|
|
145
139
|
});
|
|
146
140
|
const createAdminStructure = (opts, schemas, databaseId) => ({
|
|
147
|
-
|
|
141
|
+
tenantPool: servedDatabasePool(opts),
|
|
148
142
|
anonRole: 'anonymous',
|
|
149
143
|
roleName: 'authenticated',
|
|
150
144
|
schema: schemas,
|
|
@@ -221,13 +215,12 @@ const resolveScopedRoute = async (ctx) => {
|
|
|
221
215
|
error.code = 'CROSS_DATABASE_ROUTE';
|
|
222
216
|
throw error;
|
|
223
217
|
}
|
|
224
|
-
log.debug(`[scoped-routing] resolved host=${host} → api=${structure.apiId}
|
|
218
|
+
log.debug(`[scoped-routing] resolved host=${host} → api=${structure.apiId} databaseId=${structure.databaseId}`);
|
|
225
219
|
if (!structure.databaseId || !structure.apiId)
|
|
226
220
|
return structure;
|
|
227
221
|
const loaderCtx = buildLoaderContext(pool, opts, {
|
|
228
222
|
api_id: structure.apiId,
|
|
229
223
|
database_id: structure.databaseId,
|
|
230
|
-
dbname: structure.dbname,
|
|
231
224
|
role_name: structure.roleName,
|
|
232
225
|
anon_role: structure.anonRole,
|
|
233
226
|
is_public: structure.isPublic ?? false,
|
|
@@ -324,7 +317,7 @@ export const createApiMiddleware = (opts) => {
|
|
|
324
317
|
}
|
|
325
318
|
req.api = apiConfig;
|
|
326
319
|
req.databaseId = apiConfig.databaseId;
|
|
327
|
-
log.debug(`Resolved API:
|
|
320
|
+
log.debug(`Resolved API: databaseId=${apiConfig.databaseId}, schemas=[${apiConfig.schema?.join(', ')}]`);
|
|
328
321
|
next();
|
|
329
322
|
}
|
|
330
323
|
catch (error) {
|
package/esm/middleware/auth.js
CHANGED
|
@@ -2,7 +2,6 @@ import './types'; // for Request type
|
|
|
2
2
|
import { errors } from '@constructive-io/errors';
|
|
3
3
|
import { getNodeEnv } from '@pgpmjs/env';
|
|
4
4
|
import { Logger } from '@pgpmjs/logger';
|
|
5
|
-
import { getPgPool } from 'pg-cache';
|
|
6
5
|
import pgQueryContext from 'pg-query-context';
|
|
7
6
|
import { respondWithGraphQLError } from '../errors/graphql-response';
|
|
8
7
|
const log = new Logger('auth');
|
|
@@ -38,10 +37,7 @@ export const createAuthenticateMiddleware = (opts) => {
|
|
|
38
37
|
res.status(500).send('Missing API info');
|
|
39
38
|
return;
|
|
40
39
|
}
|
|
41
|
-
const pool =
|
|
42
|
-
...opts.pg,
|
|
43
|
-
database: api.dbname,
|
|
44
|
-
});
|
|
40
|
+
const pool = api.tenantPool;
|
|
45
41
|
const rlsModule = api.rlsModule;
|
|
46
42
|
log.info(`[auth] rlsModule=${rlsModule ? 'present' : 'missing'}, ` +
|
|
47
43
|
`authenticate=${rlsModule?.authenticate ?? 'none'}, ` +
|
|
@@ -5,8 +5,6 @@ import { Logger } from '@pgpmjs/logger';
|
|
|
5
5
|
import { cacheGraphileInstance, createGraphileInstance, graphileCache } from 'graphile-cache';
|
|
6
6
|
import { createFunctionBindingsPlugin } from 'graphile-function-bindings';
|
|
7
7
|
import { createConstructivePreset, createGrafastCacheLimitsPreset } from 'graphile-settings';
|
|
8
|
-
import { getPgPool } from 'pg-cache';
|
|
9
|
-
import { getPgEnvOptions } from 'pg-env';
|
|
10
8
|
import { isGraphqlObservabilityEnabled } from '../diagnostics/observability';
|
|
11
9
|
import { HandlerCreationError } from '../errors/api-errors';
|
|
12
10
|
import { respondWithGraphQLError } from '../errors/graphql-response';
|
|
@@ -261,17 +259,18 @@ export const graphile = (opts) => {
|
|
|
261
259
|
respondWithGraphQLError(res, errors.INTERNAL_FAILURE({ details: 'Missing service cache key' }));
|
|
262
260
|
return;
|
|
263
261
|
}
|
|
264
|
-
const {
|
|
262
|
+
const { anonRole, roleName, schema } = api;
|
|
263
|
+
const databaseLabel = api.databaseId ?? 'none';
|
|
265
264
|
const schemaLabel = schema?.join(',') || 'unknown';
|
|
266
265
|
// =========================================================================
|
|
267
266
|
// Phase A: Cache Check (fast path)
|
|
268
267
|
// =========================================================================
|
|
269
268
|
const cached = graphileCache.get(key);
|
|
270
269
|
if (cached) {
|
|
271
|
-
log.debug(`${label} PostGraphile cache hit key=${key}
|
|
270
|
+
log.debug(`${label} PostGraphile cache hit key=${key} databaseId=${databaseLabel} schemas=${schemaLabel}`);
|
|
272
271
|
return cached.handler(req, res, next);
|
|
273
272
|
}
|
|
274
|
-
log.debug(`${label} PostGraphile cache miss key=${key}
|
|
273
|
+
log.debug(`${label} PostGraphile cache miss key=${key} databaseId=${databaseLabel} schemas=${schemaLabel}`);
|
|
275
274
|
// =========================================================================
|
|
276
275
|
// Phase B: In-Flight Check (single-flight coalescing)
|
|
277
276
|
// =========================================================================
|
|
@@ -303,14 +302,8 @@ export const graphile = (opts) => {
|
|
|
303
302
|
const retryInstance = await retryInFlight;
|
|
304
303
|
return retryInstance.handler(req, res, next);
|
|
305
304
|
}
|
|
306
|
-
log.info(`${label} Building PostGraphile v5 handler key=${key}
|
|
307
|
-
const
|
|
308
|
-
...opts.pg,
|
|
309
|
-
database: dbname
|
|
310
|
-
});
|
|
311
|
-
// Route through pg-cache so the pool is tracked and can be cleaned up
|
|
312
|
-
// properly, preventing leaked connections during database teardown.
|
|
313
|
-
const pool = getPgPool(pgConfig);
|
|
305
|
+
log.info(`${label} Building PostGraphile v5 handler key=${key} databaseId=${databaseLabel} schemas=${schemaLabel} role=${roleName} anon=${anonRole}`);
|
|
306
|
+
const pool = api.tenantPool;
|
|
314
307
|
// Register the creation promise before any asynchronous preset work so
|
|
315
308
|
// concurrent requests coalesce while optional plugin modules load.
|
|
316
309
|
const creationPromise = (async () => {
|
|
@@ -333,7 +326,7 @@ export const graphile = (opts) => {
|
|
|
333
326
|
creating.set(key, creationPromise);
|
|
334
327
|
try {
|
|
335
328
|
const instance = await creationPromise;
|
|
336
|
-
log.info(`${label} Cached PostGraphile v5 handler key=${key}
|
|
329
|
+
log.info(`${label} Cached PostGraphile v5 handler key=${key} databaseId=${databaseLabel}`);
|
|
337
330
|
return instance.handler(req, res, next);
|
|
338
331
|
}
|
|
339
332
|
catch (error) {
|
|
@@ -18,7 +18,7 @@ export const createRequestLogger = ({ observabilityEnabled }) => {
|
|
|
18
18
|
finished = true;
|
|
19
19
|
const durationMs = Number(process.hrtime.bigint() - start) / 1e6;
|
|
20
20
|
const apiInfo = req.api
|
|
21
|
-
? `
|
|
21
|
+
? `databaseId=${req.api.databaseId ?? 'none'} schemas=${req.api.schema?.join(',') || 'none'}`
|
|
22
22
|
: 'api=unresolved';
|
|
23
23
|
const authInfo = req.token ? 'auth=token' : 'auth=anon';
|
|
24
24
|
const svcInfo = req.svc_key ? `svc=${req.svc_key}` : 'svc=unset';
|
|
@@ -31,7 +31,7 @@ export const createRequestLogger = ({ observabilityEnabled }) => {
|
|
|
31
31
|
}
|
|
32
32
|
const durationMs = Number(process.hrtime.bigint() - start) / 1e6;
|
|
33
33
|
const apiInfo = req.api
|
|
34
|
-
? `
|
|
34
|
+
? `databaseId=${req.api.databaseId ?? 'none'} schemas=${req.api.schema?.join(',') || 'none'}`
|
|
35
35
|
: 'api=unresolved';
|
|
36
36
|
log.warn(`[${reqId}] connection closed before response completed ` +
|
|
37
37
|
`${req.method} ${req.originalUrl} (${durationMs.toFixed(1)} ms) ${apiInfo}`);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Logger } from '@pgpmjs/logger';
|
|
2
2
|
import { QuoteUtils } from '@pgsql/quotes';
|
|
3
|
+
import { getPgPool } from 'pg-cache';
|
|
3
4
|
const log = new Logger('routing');
|
|
4
5
|
// =============================================================================
|
|
5
6
|
// Scoped routing plane (resolve_route contract)
|
|
@@ -141,6 +142,8 @@ export const resolveRouteAcrossPlanes = async (pool, host) => {
|
|
|
141
142
|
* rest of the middleware chain. Returns null when the route target is not an
|
|
142
143
|
* api surface or its resolved_config lacks the api essentials (→ 404).
|
|
143
144
|
*/
|
|
145
|
+
/** The server always serves its own configured database. */
|
|
146
|
+
export const servedDatabasePool = (opts) => getPgPool({ ...opts.pg });
|
|
144
147
|
export const routeToApiStructure = (route, opts) => {
|
|
145
148
|
if (route.target_module !== 'apis' && route.target_module !== 'api') {
|
|
146
149
|
return null;
|
|
@@ -153,9 +156,7 @@ export const routeToApiStructure = (route, opts) => {
|
|
|
153
156
|
const apiId = config.api_id ?? route.target_source_id ?? undefined;
|
|
154
157
|
return {
|
|
155
158
|
apiId,
|
|
156
|
-
|
|
157
|
-
// database; fall back to the server's own database in that case.
|
|
158
|
-
dbname: config.dbname || opts.pg?.database || '',
|
|
159
|
+
tenantPool: servedDatabasePool(opts),
|
|
159
160
|
anonRole: requireApiRole('anon_role', config.anon_role, apiId),
|
|
160
161
|
roleName: requireApiRole('role_name', config.role_name, apiId),
|
|
161
162
|
schema: config.schemas,
|
package/middleware/api.js
CHANGED
|
@@ -29,7 +29,7 @@ const defaultRegistry = (0, express_context_1.createDefaultRegistry)();
|
|
|
29
29
|
// `is_published` governs cross-scope route visibility, not internal
|
|
30
30
|
// addressability, so it does not filter this lookup.
|
|
31
31
|
const API_BY_NAME_SQL = `
|
|
32
|
-
SELECT api_id, api_database_id AS database_id,
|
|
32
|
+
SELECT api_id, api_database_id AS database_id, role_name, anon_role, is_public, schemas
|
|
33
33
|
FROM function_resolution.api_by_name($1, $2)
|
|
34
34
|
LIMIT 1
|
|
35
35
|
`;
|
|
@@ -37,18 +37,12 @@ const API_BY_NAME_SQL = `
|
|
|
37
37
|
* Build a LoaderContext from the API row and options.
|
|
38
38
|
* This is used to resolve per-database module settings via the loader registry.
|
|
39
39
|
*/
|
|
40
|
-
const buildLoaderContext = (routingPool, opts, row) => {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
tenantPool: (0, pg_cache_1.getPgPool)({ ...opts.pg, database: dbname }),
|
|
47
|
-
databaseId: row.database_id,
|
|
48
|
-
apiId: row.api_id,
|
|
49
|
-
dbname
|
|
50
|
-
};
|
|
51
|
-
};
|
|
40
|
+
const buildLoaderContext = (routingPool, opts, row) => ({
|
|
41
|
+
routingPool,
|
|
42
|
+
tenantPool: (0, routing_1.servedDatabasePool)(opts),
|
|
43
|
+
databaseId: row.database_id,
|
|
44
|
+
apiId: row.api_id
|
|
45
|
+
});
|
|
52
46
|
/**
|
|
53
47
|
* Resolve all per-database module settings in parallel via the loader registry.
|
|
54
48
|
* Each loader independently caches by databaseId — repeated calls are cheap.
|
|
@@ -137,7 +131,7 @@ const getSvcKey = (opts, req) => {
|
|
|
137
131
|
exports.getSvcKey = getSvcKey;
|
|
138
132
|
const toApiStructure = (row, opts, settings = {}) => ({
|
|
139
133
|
apiId: row.api_id,
|
|
140
|
-
|
|
134
|
+
tenantPool: (0, routing_1.servedDatabasePool)(opts),
|
|
141
135
|
anonRole: (0, routing_1.requireApiRole)('anon_role', row.anon_role, row.api_id),
|
|
142
136
|
roleName: (0, routing_1.requireApiRole)('role_name', row.role_name, row.api_id),
|
|
143
137
|
schema: row.schemas || [],
|
|
@@ -152,7 +146,7 @@ const toApiStructure = (row, opts, settings = {}) => ({
|
|
|
152
146
|
webauthnSettings: settings.webauthnSettings
|
|
153
147
|
});
|
|
154
148
|
const createAdminStructure = (opts, schemas, databaseId) => ({
|
|
155
|
-
|
|
149
|
+
tenantPool: (0, routing_1.servedDatabasePool)(opts),
|
|
156
150
|
anonRole: 'anonymous',
|
|
157
151
|
roleName: 'authenticated',
|
|
158
152
|
schema: schemas,
|
|
@@ -229,13 +223,12 @@ const resolveScopedRoute = async (ctx) => {
|
|
|
229
223
|
error.code = 'CROSS_DATABASE_ROUTE';
|
|
230
224
|
throw error;
|
|
231
225
|
}
|
|
232
|
-
log.debug(`[scoped-routing] resolved host=${host} → api=${structure.apiId}
|
|
226
|
+
log.debug(`[scoped-routing] resolved host=${host} → api=${structure.apiId} databaseId=${structure.databaseId}`);
|
|
233
227
|
if (!structure.databaseId || !structure.apiId)
|
|
234
228
|
return structure;
|
|
235
229
|
const loaderCtx = buildLoaderContext(pool, opts, {
|
|
236
230
|
api_id: structure.apiId,
|
|
237
231
|
database_id: structure.databaseId,
|
|
238
|
-
dbname: structure.dbname,
|
|
239
232
|
role_name: structure.roleName,
|
|
240
233
|
anon_role: structure.anonRole,
|
|
241
234
|
is_public: structure.isPublic ?? false,
|
|
@@ -333,7 +326,7 @@ const createApiMiddleware = (opts) => {
|
|
|
333
326
|
}
|
|
334
327
|
req.api = apiConfig;
|
|
335
328
|
req.databaseId = apiConfig.databaseId;
|
|
336
|
-
log.debug(`Resolved API:
|
|
329
|
+
log.debug(`Resolved API: databaseId=${apiConfig.databaseId}, schemas=[${apiConfig.schema?.join(', ')}]`);
|
|
337
330
|
next();
|
|
338
331
|
}
|
|
339
332
|
catch (error) {
|
package/middleware/auth.js
CHANGED
|
@@ -8,7 +8,6 @@ require("./types"); // for Request type
|
|
|
8
8
|
const errors_1 = require("@constructive-io/errors");
|
|
9
9
|
const env_1 = require("@pgpmjs/env");
|
|
10
10
|
const logger_1 = require("@pgpmjs/logger");
|
|
11
|
-
const pg_cache_1 = require("pg-cache");
|
|
12
11
|
const pg_query_context_1 = __importDefault(require("pg-query-context"));
|
|
13
12
|
const graphql_response_1 = require("../errors/graphql-response");
|
|
14
13
|
const log = new logger_1.Logger('auth');
|
|
@@ -45,10 +44,7 @@ const createAuthenticateMiddleware = (opts) => {
|
|
|
45
44
|
res.status(500).send('Missing API info');
|
|
46
45
|
return;
|
|
47
46
|
}
|
|
48
|
-
const pool =
|
|
49
|
-
...opts.pg,
|
|
50
|
-
database: api.dbname,
|
|
51
|
-
});
|
|
47
|
+
const pool = api.tenantPool;
|
|
52
48
|
const rlsModule = api.rlsModule;
|
|
53
49
|
log.info(`[auth] rlsModule=${rlsModule ? 'present' : 'missing'}, ` +
|
|
54
50
|
`authenticate=${rlsModule?.authenticate ?? 'none'}, ` +
|
package/middleware/graphile.js
CHANGED
|
@@ -11,8 +11,6 @@ const logger_1 = require("@pgpmjs/logger");
|
|
|
11
11
|
const graphile_cache_1 = require("graphile-cache");
|
|
12
12
|
const graphile_function_bindings_1 = require("graphile-function-bindings");
|
|
13
13
|
const graphile_settings_1 = require("graphile-settings");
|
|
14
|
-
const pg_cache_1 = require("pg-cache");
|
|
15
|
-
const pg_env_1 = require("pg-env");
|
|
16
14
|
const observability_1 = require("../diagnostics/observability");
|
|
17
15
|
const api_errors_1 = require("../errors/api-errors");
|
|
18
16
|
const graphql_response_1 = require("../errors/graphql-response");
|
|
@@ -267,17 +265,18 @@ const graphile = (opts) => {
|
|
|
267
265
|
(0, graphql_response_1.respondWithGraphQLError)(res, errors_1.errors.INTERNAL_FAILURE({ details: 'Missing service cache key' }));
|
|
268
266
|
return;
|
|
269
267
|
}
|
|
270
|
-
const {
|
|
268
|
+
const { anonRole, roleName, schema } = api;
|
|
269
|
+
const databaseLabel = api.databaseId ?? 'none';
|
|
271
270
|
const schemaLabel = schema?.join(',') || 'unknown';
|
|
272
271
|
// =========================================================================
|
|
273
272
|
// Phase A: Cache Check (fast path)
|
|
274
273
|
// =========================================================================
|
|
275
274
|
const cached = graphile_cache_1.graphileCache.get(key);
|
|
276
275
|
if (cached) {
|
|
277
|
-
log.debug(`${label} PostGraphile cache hit key=${key}
|
|
276
|
+
log.debug(`${label} PostGraphile cache hit key=${key} databaseId=${databaseLabel} schemas=${schemaLabel}`);
|
|
278
277
|
return cached.handler(req, res, next);
|
|
279
278
|
}
|
|
280
|
-
log.debug(`${label} PostGraphile cache miss key=${key}
|
|
279
|
+
log.debug(`${label} PostGraphile cache miss key=${key} databaseId=${databaseLabel} schemas=${schemaLabel}`);
|
|
281
280
|
// =========================================================================
|
|
282
281
|
// Phase B: In-Flight Check (single-flight coalescing)
|
|
283
282
|
// =========================================================================
|
|
@@ -309,14 +308,8 @@ const graphile = (opts) => {
|
|
|
309
308
|
const retryInstance = await retryInFlight;
|
|
310
309
|
return retryInstance.handler(req, res, next);
|
|
311
310
|
}
|
|
312
|
-
log.info(`${label} Building PostGraphile v5 handler key=${key}
|
|
313
|
-
const
|
|
314
|
-
...opts.pg,
|
|
315
|
-
database: dbname
|
|
316
|
-
});
|
|
317
|
-
// Route through pg-cache so the pool is tracked and can be cleaned up
|
|
318
|
-
// properly, preventing leaked connections during database teardown.
|
|
319
|
-
const pool = (0, pg_cache_1.getPgPool)(pgConfig);
|
|
311
|
+
log.info(`${label} Building PostGraphile v5 handler key=${key} databaseId=${databaseLabel} schemas=${schemaLabel} role=${roleName} anon=${anonRole}`);
|
|
312
|
+
const pool = api.tenantPool;
|
|
320
313
|
// Register the creation promise before any asynchronous preset work so
|
|
321
314
|
// concurrent requests coalesce while optional plugin modules load.
|
|
322
315
|
const creationPromise = (async () => {
|
|
@@ -339,7 +332,7 @@ const graphile = (opts) => {
|
|
|
339
332
|
creating.set(key, creationPromise);
|
|
340
333
|
try {
|
|
341
334
|
const instance = await creationPromise;
|
|
342
|
-
log.info(`${label} Cached PostGraphile v5 handler key=${key}
|
|
335
|
+
log.info(`${label} Cached PostGraphile v5 handler key=${key} databaseId=${databaseLabel}`);
|
|
343
336
|
return instance.handler(req, res, next);
|
|
344
337
|
}
|
|
345
338
|
catch (error) {
|
|
@@ -21,7 +21,7 @@ const createRequestLogger = ({ observabilityEnabled }) => {
|
|
|
21
21
|
finished = true;
|
|
22
22
|
const durationMs = Number(process.hrtime.bigint() - start) / 1e6;
|
|
23
23
|
const apiInfo = req.api
|
|
24
|
-
? `
|
|
24
|
+
? `databaseId=${req.api.databaseId ?? 'none'} schemas=${req.api.schema?.join(',') || 'none'}`
|
|
25
25
|
: 'api=unresolved';
|
|
26
26
|
const authInfo = req.token ? 'auth=token' : 'auth=anon';
|
|
27
27
|
const svcInfo = req.svc_key ? `svc=${req.svc_key}` : 'svc=unset';
|
|
@@ -34,7 +34,7 @@ const createRequestLogger = ({ observabilityEnabled }) => {
|
|
|
34
34
|
}
|
|
35
35
|
const durationMs = Number(process.hrtime.bigint() - start) / 1e6;
|
|
36
36
|
const apiInfo = req.api
|
|
37
|
-
? `
|
|
37
|
+
? `databaseId=${req.api.databaseId ?? 'none'} schemas=${req.api.schema?.join(',') || 'none'}`
|
|
38
38
|
: 'api=unresolved';
|
|
39
39
|
log.warn(`[${reqId}] connection closed before response completed ` +
|
|
40
40
|
`${req.method} ${req.originalUrl} (${durationMs.toFixed(1)} ms) ${apiInfo}`);
|
package/middleware/routing.d.ts
CHANGED
|
@@ -75,4 +75,6 @@ export declare const resolveRouteAcrossPlanes: (pool: Pool, host: string) => Pro
|
|
|
75
75
|
* rest of the middleware chain. Returns null when the route target is not an
|
|
76
76
|
* api surface or its resolved_config lacks the api essentials (→ 404).
|
|
77
77
|
*/
|
|
78
|
+
/** The server always serves its own configured database. */
|
|
79
|
+
export declare const servedDatabasePool: (opts: ApiOptions) => Pool;
|
|
78
80
|
export declare const routeToApiStructure: (route: ResolvedRoute, opts: ApiOptions) => ApiStructure | null;
|
package/middleware/routing.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.routeToApiStructure = exports.resolveRouteAcrossPlanes = exports.loadRoutingPlanes = exports.resolveRoute = exports.isValidSchemaName = exports.ROUTING_SCHEMA = exports.requireApiRole = void 0;
|
|
3
|
+
exports.routeToApiStructure = exports.servedDatabasePool = exports.resolveRouteAcrossPlanes = exports.loadRoutingPlanes = exports.resolveRoute = exports.isValidSchemaName = exports.ROUTING_SCHEMA = exports.requireApiRole = void 0;
|
|
4
4
|
const logger_1 = require("@pgpmjs/logger");
|
|
5
5
|
const quotes_1 = require("@pgsql/quotes");
|
|
6
|
+
const pg_cache_1 = require("pg-cache");
|
|
6
7
|
const log = new logger_1.Logger('routing');
|
|
7
8
|
// =============================================================================
|
|
8
9
|
// Scoped routing plane (resolve_route contract)
|
|
@@ -149,6 +150,9 @@ exports.resolveRouteAcrossPlanes = resolveRouteAcrossPlanes;
|
|
|
149
150
|
* rest of the middleware chain. Returns null when the route target is not an
|
|
150
151
|
* api surface or its resolved_config lacks the api essentials (→ 404).
|
|
151
152
|
*/
|
|
153
|
+
/** The server always serves its own configured database. */
|
|
154
|
+
const servedDatabasePool = (opts) => (0, pg_cache_1.getPgPool)({ ...opts.pg });
|
|
155
|
+
exports.servedDatabasePool = servedDatabasePool;
|
|
152
156
|
const routeToApiStructure = (route, opts) => {
|
|
153
157
|
if (route.target_module !== 'apis' && route.target_module !== 'api') {
|
|
154
158
|
return null;
|
|
@@ -161,9 +165,7 @@ const routeToApiStructure = (route, opts) => {
|
|
|
161
165
|
const apiId = config.api_id ?? route.target_source_id ?? undefined;
|
|
162
166
|
return {
|
|
163
167
|
apiId,
|
|
164
|
-
|
|
165
|
-
// database; fall back to the server's own database in that case.
|
|
166
|
-
dbname: config.dbname || opts.pg?.database || '',
|
|
168
|
+
tenantPool: (0, exports.servedDatabasePool)(opts),
|
|
167
169
|
anonRole: (0, exports.requireApiRole)('anon_role', config.anon_role, apiId),
|
|
168
170
|
roleName: (0, exports.requireApiRole)('role_name', config.role_name, apiId),
|
|
169
171
|
schema: config.schemas,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@constructive-io/graphql-server",
|
|
3
|
-
"version": "5.33.
|
|
3
|
+
"version": "5.33.5",
|
|
4
4
|
"author": "Constructive <developers@constructive.io>",
|
|
5
5
|
"description": "Constructive GraphQL Server",
|
|
6
6
|
"main": "index.js",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"@agentic-kit/ollama": "2.16.0",
|
|
44
44
|
"@constructive-io/csrf": "^0.31.0",
|
|
45
45
|
"@constructive-io/errors": "^0.25.0",
|
|
46
|
-
"@constructive-io/express-context": "^0.35.
|
|
46
|
+
"@constructive-io/express-context": "^0.35.4",
|
|
47
47
|
"@constructive-io/graphql-env": "^3.37.2",
|
|
48
48
|
"@constructive-io/graphql-types": "^3.36.1",
|
|
49
49
|
"@constructive-io/llm-env": "^0.16.0",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"graphile-plugin-utils": "^1.14.1",
|
|
73
73
|
"graphile-presigned-url-plugin": "^1.23.4",
|
|
74
74
|
"graphile-scoped-introspection": "^0.6.0",
|
|
75
|
-
"graphile-settings": "^6.28.
|
|
75
|
+
"graphile-settings": "^6.28.4",
|
|
76
76
|
"graphile-utils": "5.0.3",
|
|
77
77
|
"graphql": "16.13.0",
|
|
78
78
|
"graphql-upload": "^13.0.0",
|
|
@@ -98,5 +98,5 @@
|
|
|
98
98
|
"supertest": "^7.2.2",
|
|
99
99
|
"ts-node": "^10.9.2"
|
|
100
100
|
},
|
|
101
|
-
"gitHead": "
|
|
101
|
+
"gitHead": "ed0726630bf8211ff3f58934e0e0b70a180284db"
|
|
102
102
|
}
|