@constructive-io/graphql-server 2.11.5 → 2.13.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
@@ -33,13 +33,13 @@ import { GraphQLServer } from '@constructive-io/graphql-server';
33
33
  GraphQLServer(
34
34
  getEnvOptions({
35
35
  pg: { database: 'constructive_db' },
36
- server: { host: '0.0.0.0', port: 3000 }
36
+ server: { host: '0.0.0.0', port: 3000 },
37
37
  })
38
38
  );
39
39
  ```
40
40
 
41
41
  > **Tip:** Set `PGHOST`, `PGPORT`, `PGUSER`, `PGPASSWORD`, `PGDATABASE` to control DB connectivity.
42
- See [Configuration](#configuration) for the full list of supported env vars and defaults.
42
+ > See [Configuration](#configuration) for the full list of supported env vars and defaults.
43
43
 
44
44
  ### Local Development (this repo)
45
45
 
@@ -50,9 +50,9 @@ pnpm dev
50
50
  ```
51
51
 
52
52
  This starts the server with env defaults from `@constructive-io/graphql-env`.
53
- > **Tip:** Set `PGHOST`, `PGPORT`, `PGUSER`, `PGPASSWORD`, `PGDATABASE` to control DB connectivity.
54
- See [Configuration](#configuration) for the full list of supported env vars and defaults.
55
53
 
54
+ > **Tip:** Set `PGHOST`, `PGPORT`, `PGUSER`, `PGPASSWORD`, `PGDATABASE` to control DB connectivity.
55
+ > See [Configuration](#configuration) for the full list of supported env vars and defaults.
56
56
 
57
57
  ## What it does
58
58
 
@@ -79,7 +79,7 @@ Runs an Express server that wires CORS, uploads, domain parsing, auth, and PostG
79
79
 
80
80
  When `API_ENABLE_META=true` (default):
81
81
 
82
- - The server resolves APIs from `meta_public.domains` using the request host.
82
+ - The server resolves APIs from `services_public.domains` using the request host.
83
83
  - Only APIs where `api.is_public` matches `API_IS_PUBLIC` are served.
84
84
  - In private mode (`API_IS_PUBLIC=false`), you can override with headers:
85
85
  - `X-Api-Name` + `X-Database-Id`
@@ -95,24 +95,24 @@ When `API_ENABLE_META=false`:
95
95
 
96
96
  Configuration is merged from defaults, config files, and env vars via `@constructive-io/graphql-env`. See `graphql/env/README.md` for the full list and examples.
97
97
 
98
- | Env var | Purpose | Default |
99
- | --- | --- | --- |
100
- | `PGHOST` | Postgres host | `localhost` |
101
- | `PGPORT` | Postgres port | `5432` |
102
- | `PGUSER` | Postgres user | `postgres` |
103
- | `PGPASSWORD` | Postgres password | `password` |
104
- | `PGDATABASE` | Postgres database | `postgres` |
105
- | `GRAPHILE_SCHEMA` | Comma-separated schemas to expose | empty |
106
- | `FEATURES_SIMPLE_INFLECTION` | Enable simple inflection | `true` |
107
- | `FEATURES_OPPOSITE_BASE_NAMES` | Enable opposite base names | `true` |
108
- | `FEATURES_POSTGIS` | Enable PostGIS support | `true` |
109
- | `API_ENABLE_META` | Enable meta API routing | `true` |
110
- | `API_IS_PUBLIC` | Serve public APIs only | `true` |
111
- | `API_EXPOSED_SCHEMAS` | Schemas when meta routing is disabled | empty |
112
- | `API_META_SCHEMAS` | Meta schemas to query | `collections_public,meta_public` |
113
- | `API_ANON_ROLE` | Anonymous role name | `administrator` |
114
- | `API_ROLE_NAME` | Authenticated role name | `administrator` |
115
- | `API_DEFAULT_DATABASE_ID` | Default database ID | `hard-coded` |
98
+ | Env var | Purpose | Default |
99
+ | ------------------------------ | ------------------------------------- | ------------------------------------------------------------- |
100
+ | `PGHOST` | Postgres host | `localhost` |
101
+ | `PGPORT` | Postgres port | `5432` |
102
+ | `PGUSER` | Postgres user | `postgres` |
103
+ | `PGPASSWORD` | Postgres password | `password` |
104
+ | `PGDATABASE` | Postgres database | `postgres` |
105
+ | `GRAPHILE_SCHEMA` | Comma-separated schemas to expose | empty |
106
+ | `FEATURES_SIMPLE_INFLECTION` | Enable simple inflection | `true` |
107
+ | `FEATURES_OPPOSITE_BASE_NAMES` | Enable opposite base names | `true` |
108
+ | `FEATURES_POSTGIS` | Enable PostGIS support | `true` |
109
+ | `API_ENABLE_META` | Enable meta API routing | `true` |
110
+ | `API_IS_PUBLIC` | Serve public APIs only | `true` |
111
+ | `API_EXPOSED_SCHEMAS` | Schemas when meta routing is disabled | empty |
112
+ | `API_META_SCHEMAS` | Meta schemas to query | `services_public,metaschema_public,metaschema_modules_public` |
113
+ | `API_ANON_ROLE` | Anonymous role name | `administrator` |
114
+ | `API_ROLE_NAME` | Authenticated role name | `administrator` |
115
+ | `API_DEFAULT_DATABASE_ID` | Default database ID | `hard-coded` |
116
116
 
117
117
  ## Testing
118
118
 
@@ -1,4 +1,5 @@
1
1
  import { getNodeEnv } from '@constructive-io/graphql-env';
2
+ import { Logger } from '@pgpmjs/logger';
2
3
  import { svcCache } from '@pgpmjs/server-utils';
3
4
  import { getSchema, GraphileQuery } from 'graphile-query';
4
5
  import { getGraphileSettings } from 'graphile-settings';
@@ -7,6 +8,8 @@ import errorPage50x from '../errors/50x';
7
8
  import errorPage404Message from '../errors/404-message';
8
9
  import { ApiByNameQuery, ApiQuery, ListOfAllDomainsOfDb } from './gql';
9
10
  import './types'; // for Request type
11
+ const log = new Logger('api');
12
+ const isDev = () => getNodeEnv() === 'development';
10
13
  const transformServiceToApi = (svc) => {
11
14
  const api = svc.data.api;
12
15
  const schemaNames = api.schemaNamesFromExt?.nodes?.map((n) => n.schemaName) || [];
@@ -92,6 +95,8 @@ export const createApiMiddleware = (opts) => {
92
95
  const api = transformServiceToApi(svc);
93
96
  req.api = api;
94
97
  req.databaseId = api.databaseId;
98
+ if (isDev())
99
+ log.debug(`Resolved API: db=${api.dbname}, schemas=[${api.schema?.join(', ')}]`);
95
100
  next();
96
101
  }
97
102
  catch (e) {
@@ -104,7 +109,7 @@ export const createApiMiddleware = (opts) => {
104
109
  .send(errorPage404Message("The resource you're looking for does not exist."));
105
110
  }
106
111
  else {
107
- console.error(e);
112
+ log.error('API middleware error:', e);
108
113
  res.status(500).send(errorPage50x);
109
114
  }
110
115
  }
@@ -162,7 +167,7 @@ const queryServiceByDomainAndSubdomain = async ({ opts, key, client, domain, sub
162
167
  variables: { domain, subdomain },
163
168
  });
164
169
  if (result.errors?.length) {
165
- console.error(result.errors);
170
+ log.error('GraphQL query errors:', result.errors);
166
171
  return null;
167
172
  }
168
173
  const nodes = result?.data?.domains?.nodes;
@@ -184,7 +189,7 @@ const queryServiceByApiName = async ({ opts, key, client, databaseId, name, }) =
184
189
  variables: { databaseId, name },
185
190
  });
186
191
  if (result.errors?.length) {
187
- console.error(result.errors);
192
+ log.error('GraphQL query errors:', result.errors);
188
193
  return null;
189
194
  }
190
195
  const data = result?.data;
@@ -229,14 +234,21 @@ export const getApiConfig = async (opts, req) => {
229
234
  req.svc_key = key;
230
235
  let svc;
231
236
  if (svcCache.has(key)) {
237
+ if (isDev())
238
+ log.debug(`Cache HIT for key=${key}`);
232
239
  svc = svcCache.get(key);
233
240
  }
234
241
  else {
242
+ if (isDev())
243
+ log.debug(`Cache MISS for key=${key}, looking up API`);
235
244
  const apiOpts = opts.api || {};
236
245
  const allSchemata = apiOpts.metaSchemas || [];
237
246
  const validatedSchemata = await validateSchemata(rootPgPool, allSchemata);
238
247
  if (validatedSchemata.length === 0) {
239
- const message = `No valid schemas found for domain: ${domain}, subdomain: ${subdomain}`;
248
+ const apiOpts2 = opts.api || {};
249
+ const message = `No valid schemas found. Configured metaSchemas: [${(apiOpts2.metaSchemas || []).join(', ')}]`;
250
+ if (isDev())
251
+ log.debug(message);
240
252
  const error = new Error(message);
241
253
  error.code = 'NO_VALID_SCHEMAS';
242
254
  throw error;
@@ -1,6 +1,10 @@
1
+ import { getNodeEnv } from '@constructive-io/graphql-env';
2
+ import { Logger } from '@pgpmjs/logger';
1
3
  import { getPgPool } from 'pg-cache';
2
4
  import pgQueryContext from 'pg-query-context';
3
5
  import './types'; // for Request type
6
+ const log = new Logger('auth');
7
+ const isDev = () => getNodeEnv() === 'development';
4
8
  export const createAuthenticateMiddleware = (opts) => {
5
9
  return async (req, res, next) => {
6
10
  const api = req.api;
@@ -13,8 +17,11 @@ export const createAuthenticateMiddleware = (opts) => {
13
17
  database: api.dbname,
14
18
  });
15
19
  const rlsModule = api.rlsModule;
16
- if (!rlsModule)
20
+ if (!rlsModule) {
21
+ if (isDev())
22
+ log.debug('No RLS module configured, skipping auth');
17
23
  return next();
24
+ }
18
25
  const authFn = opts.server.strictAuth
19
26
  ? rlsModule.authenticateStrict
20
27
  : rlsModule.authenticate;
@@ -46,8 +53,11 @@ export const createAuthenticateMiddleware = (opts) => {
46
53
  return;
47
54
  }
48
55
  token = result.rows[0];
56
+ if (isDev())
57
+ log.debug(`Auth success: role=${token.role}`);
49
58
  }
50
59
  catch (e) {
60
+ log.error('Auth error:', e.message);
51
61
  res.status(200).json({
52
62
  errors: [
53
63
  {
@@ -29,7 +29,7 @@ export const flushService = async (opts, databaseId) => {
29
29
  });
30
30
  }
31
31
  const svc = await pgPool.query(`SELECT *
32
- FROM meta_public.domains
32
+ FROM services_public.domains
33
33
  WHERE database_id = $1`, [databaseId]);
34
34
  if (svc.rowCount === 0)
35
35
  return;
package/esm/server.js CHANGED
@@ -1,4 +1,4 @@
1
- import { getEnvOptions } from '@constructive-io/graphql-env';
1
+ import { getEnvOptions, getNodeEnv } from '@constructive-io/graphql-env';
2
2
  import { Logger } from '@pgpmjs/logger';
3
3
  import { healthz, poweredBy, trustProxy } from '@pgpmjs/server-utils';
4
4
  import { middleware as parseDomains } from '@constructive-io/url-domains';
@@ -13,6 +13,7 @@ import { cors } from './middleware/cors';
13
13
  import { flush, flushService } from './middleware/flush';
14
14
  import { graphile } from './middleware/graphile';
15
15
  const log = new Logger('server');
16
+ const isDev = () => getNodeEnv() === 'development';
16
17
  export const GraphQLServer = (rawOpts = {}) => {
17
18
  const envOptions = getEnvOptions(rawOpts);
18
19
  const app = new Server(envOptions);
@@ -27,6 +28,11 @@ class Server {
27
28
  const app = express();
28
29
  const api = createApiMiddleware(opts);
29
30
  const authenticate = createAuthenticateMiddleware(opts);
31
+ // Log startup config in dev mode
32
+ if (isDev()) {
33
+ log.debug(`Database: ${opts.pg?.database}@${opts.pg?.host}:${opts.pg?.port}`);
34
+ log.debug(`Meta schemas: ${opts.api?.metaSchemas?.join(', ') || 'default'}`);
35
+ }
30
36
  healthz(app);
31
37
  trustProxy(app, opts.server.trustProxy);
32
38
  // Warn if a global CORS override is set in production
package/middleware/api.js CHANGED
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.getApiConfig = exports.createApiMiddleware = exports.getSubdomain = void 0;
7
7
  const graphql_env_1 = require("@constructive-io/graphql-env");
8
+ const logger_1 = require("@pgpmjs/logger");
8
9
  const server_utils_1 = require("@pgpmjs/server-utils");
9
10
  const graphile_query_1 = require("graphile-query");
10
11
  const graphile_settings_1 = require("graphile-settings");
@@ -13,6 +14,8 @@ const _50x_1 = __importDefault(require("../errors/50x"));
13
14
  const _404_message_1 = __importDefault(require("../errors/404-message"));
14
15
  const gql_1 = require("./gql");
15
16
  require("./types"); // for Request type
17
+ const log = new logger_1.Logger('api');
18
+ const isDev = () => (0, graphql_env_1.getNodeEnv)() === 'development';
16
19
  const transformServiceToApi = (svc) => {
17
20
  const api = svc.data.api;
18
21
  const schemaNames = api.schemaNamesFromExt?.nodes?.map((n) => n.schemaName) || [];
@@ -99,6 +102,8 @@ const createApiMiddleware = (opts) => {
99
102
  const api = transformServiceToApi(svc);
100
103
  req.api = api;
101
104
  req.databaseId = api.databaseId;
105
+ if (isDev())
106
+ log.debug(`Resolved API: db=${api.dbname}, schemas=[${api.schema?.join(', ')}]`);
102
107
  next();
103
108
  }
104
109
  catch (e) {
@@ -111,7 +116,7 @@ const createApiMiddleware = (opts) => {
111
116
  .send((0, _404_message_1.default)("The resource you're looking for does not exist."));
112
117
  }
113
118
  else {
114
- console.error(e);
119
+ log.error('API middleware error:', e);
115
120
  res.status(500).send(_50x_1.default);
116
121
  }
117
122
  }
@@ -170,7 +175,7 @@ const queryServiceByDomainAndSubdomain = async ({ opts, key, client, domain, sub
170
175
  variables: { domain, subdomain },
171
176
  });
172
177
  if (result.errors?.length) {
173
- console.error(result.errors);
178
+ log.error('GraphQL query errors:', result.errors);
174
179
  return null;
175
180
  }
176
181
  const nodes = result?.data?.domains?.nodes;
@@ -192,7 +197,7 @@ const queryServiceByApiName = async ({ opts, key, client, databaseId, name, }) =
192
197
  variables: { databaseId, name },
193
198
  });
194
199
  if (result.errors?.length) {
195
- console.error(result.errors);
200
+ log.error('GraphQL query errors:', result.errors);
196
201
  return null;
197
202
  }
198
203
  const data = result?.data;
@@ -237,14 +242,21 @@ const getApiConfig = async (opts, req) => {
237
242
  req.svc_key = key;
238
243
  let svc;
239
244
  if (server_utils_1.svcCache.has(key)) {
245
+ if (isDev())
246
+ log.debug(`Cache HIT for key=${key}`);
240
247
  svc = server_utils_1.svcCache.get(key);
241
248
  }
242
249
  else {
250
+ if (isDev())
251
+ log.debug(`Cache MISS for key=${key}, looking up API`);
243
252
  const apiOpts = opts.api || {};
244
253
  const allSchemata = apiOpts.metaSchemas || [];
245
254
  const validatedSchemata = await validateSchemata(rootPgPool, allSchemata);
246
255
  if (validatedSchemata.length === 0) {
247
- const message = `No valid schemas found for domain: ${domain}, subdomain: ${subdomain}`;
256
+ const apiOpts2 = opts.api || {};
257
+ const message = `No valid schemas found. Configured metaSchemas: [${(apiOpts2.metaSchemas || []).join(', ')}]`;
258
+ if (isDev())
259
+ log.debug(message);
248
260
  const error = new Error(message);
249
261
  error.code = 'NO_VALID_SCHEMAS';
250
262
  throw error;
@@ -4,9 +4,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.createAuthenticateMiddleware = void 0;
7
+ const graphql_env_1 = require("@constructive-io/graphql-env");
8
+ const logger_1 = require("@pgpmjs/logger");
7
9
  const pg_cache_1 = require("pg-cache");
8
10
  const pg_query_context_1 = __importDefault(require("pg-query-context"));
9
11
  require("./types"); // for Request type
12
+ const log = new logger_1.Logger('auth');
13
+ const isDev = () => (0, graphql_env_1.getNodeEnv)() === 'development';
10
14
  const createAuthenticateMiddleware = (opts) => {
11
15
  return async (req, res, next) => {
12
16
  const api = req.api;
@@ -19,8 +23,11 @@ const createAuthenticateMiddleware = (opts) => {
19
23
  database: api.dbname,
20
24
  });
21
25
  const rlsModule = api.rlsModule;
22
- if (!rlsModule)
26
+ if (!rlsModule) {
27
+ if (isDev())
28
+ log.debug('No RLS module configured, skipping auth');
23
29
  return next();
30
+ }
24
31
  const authFn = opts.server.strictAuth
25
32
  ? rlsModule.authenticateStrict
26
33
  : rlsModule.authenticate;
@@ -52,8 +59,11 @@ const createAuthenticateMiddleware = (opts) => {
52
59
  return;
53
60
  }
54
61
  token = result.rows[0];
62
+ if (isDev())
63
+ log.debug(`Auth success: role=${token.role}`);
55
64
  }
56
65
  catch (e) {
66
+ log.error('Auth error:', e.message);
57
67
  res.status(200).json({
58
68
  errors: [
59
69
  {
@@ -33,7 +33,7 @@ const flushService = async (opts, databaseId) => {
33
33
  });
34
34
  }
35
35
  const svc = await pgPool.query(`SELECT *
36
- FROM meta_public.domains
36
+ FROM services_public.domains
37
37
  WHERE database_id = $1`, [databaseId]);
38
38
  if (svc.rowCount === 0)
39
39
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@constructive-io/graphql-server",
3
- "version": "2.11.5",
3
+ "version": "2.13.0",
4
4
  "author": "Constructive <developers@constructive.io>",
5
5
  "description": "Constructive GraphQL Server",
6
6
  "main": "index.js",
@@ -39,8 +39,8 @@
39
39
  "backend"
40
40
  ],
41
41
  "dependencies": {
42
- "@constructive-io/graphql-env": "^2.8.14",
43
- "@constructive-io/graphql-types": "^2.12.10",
42
+ "@constructive-io/graphql-env": "^2.8.15",
43
+ "@constructive-io/graphql-types": "^2.12.11",
44
44
  "@constructive-io/s3-utils": "^2.4.1",
45
45
  "@constructive-io/upload-names": "^2.3.6",
46
46
  "@constructive-io/url-domains": "^2.3.7",
@@ -52,15 +52,15 @@
52
52
  "express": "^5.2.1",
53
53
  "graphile-build": "^4.14.1",
54
54
  "graphile-cache": "^1.6.14",
55
- "graphile-i18n": "^0.2.46",
56
- "graphile-meta-schema": "^0.3.46",
57
- "graphile-plugin-connection-filter": "^2.4.46",
58
- "graphile-plugin-connection-filter-postgis": "^1.1.47",
59
- "graphile-plugin-fulltext-filter": "^2.1.46",
55
+ "graphile-i18n": "^0.3.0",
56
+ "graphile-meta-schema": "^0.4.0",
57
+ "graphile-plugin-connection-filter": "^2.5.0",
58
+ "graphile-plugin-connection-filter-postgis": "^1.2.0",
59
+ "graphile-plugin-fulltext-filter": "^2.2.0",
60
60
  "graphile-query": "^2.4.7",
61
- "graphile-search-plugin": "^0.2.46",
62
- "graphile-settings": "^2.10.5",
63
- "graphile-simple-inflector": "^0.2.46",
61
+ "graphile-search-plugin": "^0.3.0",
62
+ "graphile-settings": "^2.11.0",
63
+ "graphile-simple-inflector": "^0.3.0",
64
64
  "graphile-utils": "^4.14.1",
65
65
  "graphql": "15.10.1",
66
66
  "graphql-tag": "2.12.6",
@@ -83,5 +83,5 @@
83
83
  "nodemon": "^3.1.10",
84
84
  "ts-node": "^10.9.2"
85
85
  },
86
- "gitHead": "5eca40587d2b8e2362a2308bc3fa8c1eecf131da"
86
+ "gitHead": "97528ad4eb2f60c16785ffb84af7b61c52cb5ad8"
87
87
  }
package/server.js CHANGED
@@ -19,6 +19,7 @@ const cors_1 = require("./middleware/cors");
19
19
  const flush_1 = require("./middleware/flush");
20
20
  const graphile_1 = require("./middleware/graphile");
21
21
  const log = new logger_1.Logger('server');
22
+ const isDev = () => (0, graphql_env_1.getNodeEnv)() === 'development';
22
23
  const GraphQLServer = (rawOpts = {}) => {
23
24
  const envOptions = (0, graphql_env_1.getEnvOptions)(rawOpts);
24
25
  const app = new Server(envOptions);
@@ -34,6 +35,11 @@ class Server {
34
35
  const app = (0, express_1.default)();
35
36
  const api = (0, api_1.createApiMiddleware)(opts);
36
37
  const authenticate = (0, auth_1.createAuthenticateMiddleware)(opts);
38
+ // Log startup config in dev mode
39
+ if (isDev()) {
40
+ log.debug(`Database: ${opts.pg?.database}@${opts.pg?.host}:${opts.pg?.port}`);
41
+ log.debug(`Meta schemas: ${opts.api?.metaSchemas?.join(', ') || 'default'}`);
42
+ }
37
43
  (0, server_utils_1.healthz)(app);
38
44
  (0, server_utils_1.trustProxy)(app, opts.server.trustProxy);
39
45
  // Warn if a global CORS override is set in production