@constructive-io/graphql-env 3.32.2 → 3.33.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
@@ -44,6 +44,61 @@ In addition to all environment variables supported by `@pgpmjs/env`, this packag
44
44
  ### GraphQL Schema
45
45
  - `GRAPHILE_SCHEMA` - Comma-separated list of PostgreSQL schemas to expose
46
46
 
47
+ Schema-scoped introspection is configured through the Graphile preset in
48
+ `pgpm.json` or runtime options. The map is keyed by the final PostgreSQL
49
+ service name: omit a service or set it to `false` for stock introspection, set
50
+ it to `true` for scoped defaults, or provide `catalogTypes` and
51
+ `capabilityExtensions` explicitly.
52
+
53
+ ### Scoped introspection environment overrides
54
+
55
+ The server maps these variables to
56
+ `graphile.preset.gather.pgScopedIntrospection.main`. `main` is the internal
57
+ PostgreSQL service name, not the routing service name (such as `local`).
58
+
59
+ | Variable | Accepted values |
60
+ | --- | --- |
61
+ | `GRAPHILE_SCOPED_INTROSPECTION` | `true` or `false` |
62
+ | `GRAPHILE_SCOPED_INTROSPECTION_CATALOG_TYPES` | `all` or `dependency-closure` |
63
+ | `GRAPHILE_SCOPED_INTROSPECTION_CAPABILITY_EXTENSIONS` | Comma-separated extension names, e.g. `pg_trgm,vector` |
64
+
65
+ - Unset variables do not override `pgpm.json`. With neither configuration nor
66
+ environment settings, introspection remains stock.
67
+ - `true` enables scoped introspection while preserving advanced options in
68
+ `pgpm.json`; without advanced options it uses Crystal's defaults
69
+ (`catalogTypes: "all"`).
70
+ - Either advanced variable alone enables scoped introspection, including when
71
+ the config file sets `main: false`.
72
+ - Explicit `false` overrides both advanced variables and the config file.
73
+ Advanced variables are ignored, even if malformed, so one switch is enough
74
+ to roll back to stock.
75
+ - Advanced options override only their own field. Extension lists replace the
76
+ configured array, trim names and remove duplicates. An explicitly empty or
77
+ whitespace-only extension variable clears the list to `[]`; unset preserves it.
78
+ - An empty or whitespace-only enable variable means unset. Other enable values
79
+ must be lowercase `true` or `false` (surrounding whitespace is allowed).
80
+ Catalog values must match one of the two lowercase choices exactly; an empty
81
+ catalog value is invalid. Empty CSV items such as `pg_trgm,,vector` are invalid.
82
+ Invalid active settings fail option resolution with the variable name.
83
+ - Precedence remains defaults < config file < environment < explicit runtime
84
+ options. These variables affect only `main`.
85
+
86
+ ```bash
87
+ # Enable default scoped introspection
88
+ export GRAPHILE_SCOPED_INTROSPECTION=true
89
+
90
+ # Optionally customize it
91
+ export GRAPHILE_SCOPED_INTROSPECTION_CATALOG_TYPES=dependency-closure
92
+ export GRAPHILE_SCOPED_INTROSPECTION_CAPABILITY_EXTENSIONS=pg_trgm,vector
93
+
94
+ # Roll back without removing the advanced variables
95
+ export GRAPHILE_SCOPED_INTROSPECTION=false
96
+ ```
97
+
98
+ Restart the server after changing its environment. `capabilityExtensions`
99
+ retains introspection metadata for extension capabilities; it does not install
100
+ PostgreSQL extensions or change the API's exposed schemas.
101
+
47
102
  ### Feature Flags
48
103
  - `FEATURES_SIMPLE_INFLECTION` - Enable simple inflection plugin
49
104
  - `FEATURES_OPPOSITE_BASE_NAMES` - Enable opposite base names
@@ -65,7 +120,11 @@ GraphQL defaults are provided by `@constructive-io/graphql-types`:
65
120
 
66
121
  ```typescript
67
122
  {
68
- graphile: { schema: [] },
123
+ graphile: {
124
+ schema: [],
125
+ extends: [],
126
+ preset: {}
127
+ },
69
128
  features: {
70
129
  simpleInflection: true,
71
130
  oppositeBaseNames: true,
@@ -82,6 +141,41 @@ GraphQL defaults are provided by `@constructive-io/graphql-types`:
82
141
  }
83
142
  ```
84
143
 
144
+ For example, this enables scoped introspection with the defaults for the
145
+ server's default `main` service:
146
+
147
+ ```json
148
+ {
149
+ "graphile": {
150
+ "preset": {
151
+ "gather": {
152
+ "pgScopedIntrospection": { "main": true }
153
+ }
154
+ }
155
+ }
156
+ }
157
+ ```
158
+
159
+ Advanced options can be supplied when a service needs a specific catalog
160
+ policy or extension capability:
161
+
162
+ ```json
163
+ {
164
+ "graphile": {
165
+ "preset": {
166
+ "gather": {
167
+ "pgScopedIntrospection": {
168
+ "main": {
169
+ "catalogTypes": "dependency-closure",
170
+ "capabilityExtensions": ["pg_trgm"]
171
+ }
172
+ }
173
+ }
174
+ }
175
+ }
176
+ }
177
+ ```
178
+
85
179
  ## When to Use
86
180
 
87
181
  - Use `@constructive-io/graphql-env` for Constructive applications that need GraphQL/Graphile configuration
package/env.js CHANGED
@@ -2,10 +2,12 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getGraphQLEnvVars = void 0;
4
4
  const _12factor_env_1 = require("12factor-env");
5
+ const scoped_introspection_1 = require("./scoped-introspection");
5
6
  /**
6
7
  * @param env - Environment object to read from (defaults to process.env for backwards compatibility)
7
8
  */
8
9
  const getGraphQLEnvVars = (env = process.env) => {
10
+ const scopedIntrospection = (0, scoped_introspection_1.getScopedIntrospectionEnv)(env);
9
11
  const { GRAPHILE_SCHEMA, FEATURES_SIMPLE_INFLECTION, FEATURES_OPPOSITE_BASE_NAMES, FEATURES_POSTGIS, API_ROUTING_SCHEMA, API_IS_PUBLIC, API_EXPOSED_SCHEMAS, API_META_SCHEMAS, API_ANON_ROLE, API_ROLE_NAME, API_FLUSH_TOKEN, API_INTROSPECTION_ROLE, EMBEDDER_PROVIDER, EMBEDDER_MODEL, EMBEDDER_BASE_URL, CHAT_PROVIDER, CHAT_MODEL, CHAT_BASE_URL, SMS_PROVIDER, SMS_SENDER_ID, SMS_REQUEST_TIMEOUT_MS, SEND_SMS_DRY_RUN, DEVSMS_BASE_URL } = env;
10
12
  // Keep this function as a partial env-override parser. SMS runtime defaults
11
13
  // belong to the consuming application; injecting them here would incorrectly
@@ -19,6 +21,13 @@ const getGraphQLEnvVars = (env = process.env) => {
19
21
  DEVSMS_BASE_URL);
20
22
  return {
21
23
  graphile: {
24
+ ...(scopedIntrospection !== undefined && {
25
+ preset: {
26
+ gather: {
27
+ pgScopedIntrospection: { main: scopedIntrospection }
28
+ }
29
+ }
30
+ }),
22
31
  ...(GRAPHILE_SCHEMA && {
23
32
  schema: GRAPHILE_SCHEMA.includes(',')
24
33
  ? GRAPHILE_SCHEMA.split(',').map(s => s.trim())
package/esm/env.js CHANGED
@@ -1,8 +1,10 @@
1
1
  import { parseEnvBoolean, parseEnvNumber } from '12factor-env';
2
+ import { getScopedIntrospectionEnv } from './scoped-introspection';
2
3
  /**
3
4
  * @param env - Environment object to read from (defaults to process.env for backwards compatibility)
4
5
  */
5
6
  export const getGraphQLEnvVars = (env = process.env) => {
7
+ const scopedIntrospection = getScopedIntrospectionEnv(env);
6
8
  const { GRAPHILE_SCHEMA, FEATURES_SIMPLE_INFLECTION, FEATURES_OPPOSITE_BASE_NAMES, FEATURES_POSTGIS, API_ROUTING_SCHEMA, API_IS_PUBLIC, API_EXPOSED_SCHEMAS, API_META_SCHEMAS, API_ANON_ROLE, API_ROLE_NAME, API_FLUSH_TOKEN, API_INTROSPECTION_ROLE, EMBEDDER_PROVIDER, EMBEDDER_MODEL, EMBEDDER_BASE_URL, CHAT_PROVIDER, CHAT_MODEL, CHAT_BASE_URL, SMS_PROVIDER, SMS_SENDER_ID, SMS_REQUEST_TIMEOUT_MS, SEND_SMS_DRY_RUN, DEVSMS_BASE_URL } = env;
7
9
  // Keep this function as a partial env-override parser. SMS runtime defaults
8
10
  // belong to the consuming application; injecting them here would incorrectly
@@ -16,6 +18,13 @@ export const getGraphQLEnvVars = (env = process.env) => {
16
18
  DEVSMS_BASE_URL);
17
19
  return {
18
20
  graphile: {
21
+ ...(scopedIntrospection !== undefined && {
22
+ preset: {
23
+ gather: {
24
+ pgScopedIntrospection: { main: scopedIntrospection }
25
+ }
26
+ }
27
+ }),
19
28
  ...(GRAPHILE_SCHEMA && {
20
29
  schema: GRAPHILE_SCHEMA.includes(',')
21
30
  ? GRAPHILE_SCHEMA.split(',').map(s => s.trim())
@@ -0,0 +1,3 @@
1
+ import type { PgScopedIntrospectionServiceConfig } from '@constructive-io/graphql-types';
2
+ /** Partial override for the server-owned `main` service; absence is not a default. */
3
+ export declare const getScopedIntrospectionEnv: (environment: NodeJS.ProcessEnv) => PgScopedIntrospectionServiceConfig | undefined;
@@ -0,0 +1,42 @@
1
+ import { cleanEnv, enumerated, EnvError, makeValidator, parseEnvList, str } from '12factor-env';
2
+ // Unlike the generic list validator, this configuration permits an explicit
3
+ // empty list (clear the configured extensions), but rejects empty CSV items.
4
+ const capabilityExtensions = makeValidator((value) => {
5
+ if (value.trim() === '')
6
+ return [];
7
+ if (value.split(',').some((item) => item.trim() === '')) {
8
+ throw new EnvError('Expected comma-separated extension names without empty items');
9
+ }
10
+ return [...new Set(parseEnvList(value))];
11
+ });
12
+ /** Partial override for the server-owned `main` service; absence is not a default. */
13
+ export const getScopedIntrospectionEnv = (environment) => {
14
+ const enabled = cleanEnv({
15
+ GRAPHILE_SCOPED_INTROSPECTION: environment.GRAPHILE_SCOPED_INTROSPECTION?.trim() || undefined
16
+ }, {
17
+ GRAPHILE_SCOPED_INTROSPECTION: str({
18
+ choices: ['true', 'false'],
19
+ default: undefined
20
+ })
21
+ }).GRAPHILE_SCOPED_INTROSPECTION;
22
+ // The rollback switch works even if stale advanced settings are malformed.
23
+ if (enabled === 'false')
24
+ return false;
25
+ const options = cleanEnv(environment, {
26
+ GRAPHILE_SCOPED_INTROSPECTION_CATALOG_TYPES: enumerated(['all', 'dependency-closure'], { default: undefined }),
27
+ GRAPHILE_SCOPED_INTROSPECTION_CAPABILITY_EXTENSIONS: capabilityExtensions({
28
+ default: undefined
29
+ })
30
+ });
31
+ const catalogTypes = options.GRAPHILE_SCOPED_INTROSPECTION_CATALOG_TYPES;
32
+ const extensions = options.GRAPHILE_SCOPED_INTROSPECTION_CAPABILITY_EXTENSIONS;
33
+ if (enabled === undefined && catalogTypes === undefined && extensions === undefined) {
34
+ return undefined;
35
+ }
36
+ // An object enables scoped introspection while deepmerge preserves advanced
37
+ // fields from pgpm.json. A literal `true` would discard those fields.
38
+ return {
39
+ ...(catalogTypes !== undefined && { catalogTypes }),
40
+ ...(extensions !== undefined && { capabilityExtensions: extensions })
41
+ };
42
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@constructive-io/graphql-env",
3
- "version": "3.32.2",
3
+ "version": "3.33.0",
4
4
  "author": "Constructive <developers@constructive.io>",
5
5
  "description": "Constructive environment configuration with GraphQL/Graphile support",
6
6
  "main": "index.js",
@@ -30,8 +30,8 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "12factor-env": "^1.32.1",
33
- "@constructive-io/graphql-types": "^3.31.2",
34
- "@pgpmjs/env": "^2.43.6",
33
+ "@constructive-io/graphql-types": "^3.32.0",
34
+ "@pgpmjs/env": "^2.44.0",
35
35
  "deepmerge": "^4.3.1"
36
36
  },
37
37
  "keywords": [
@@ -45,5 +45,5 @@
45
45
  "devDependencies": {
46
46
  "makage": "^0.8.0"
47
47
  },
48
- "gitHead": "9bba983b38395277e0400294aaa380e586b049d4"
48
+ "gitHead": "399437801c27b070312125f3f0ee821ce16e183e"
49
49
  }
@@ -0,0 +1,3 @@
1
+ import type { PgScopedIntrospectionServiceConfig } from '@constructive-io/graphql-types';
2
+ /** Partial override for the server-owned `main` service; absence is not a default. */
3
+ export declare const getScopedIntrospectionEnv: (environment: NodeJS.ProcessEnv) => PgScopedIntrospectionServiceConfig | undefined;
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getScopedIntrospectionEnv = void 0;
4
+ const _12factor_env_1 = require("12factor-env");
5
+ // Unlike the generic list validator, this configuration permits an explicit
6
+ // empty list (clear the configured extensions), but rejects empty CSV items.
7
+ const capabilityExtensions = (0, _12factor_env_1.makeValidator)((value) => {
8
+ if (value.trim() === '')
9
+ return [];
10
+ if (value.split(',').some((item) => item.trim() === '')) {
11
+ throw new _12factor_env_1.EnvError('Expected comma-separated extension names without empty items');
12
+ }
13
+ return [...new Set((0, _12factor_env_1.parseEnvList)(value))];
14
+ });
15
+ /** Partial override for the server-owned `main` service; absence is not a default. */
16
+ const getScopedIntrospectionEnv = (environment) => {
17
+ const enabled = (0, _12factor_env_1.cleanEnv)({
18
+ GRAPHILE_SCOPED_INTROSPECTION: environment.GRAPHILE_SCOPED_INTROSPECTION?.trim() || undefined
19
+ }, {
20
+ GRAPHILE_SCOPED_INTROSPECTION: (0, _12factor_env_1.str)({
21
+ choices: ['true', 'false'],
22
+ default: undefined
23
+ })
24
+ }).GRAPHILE_SCOPED_INTROSPECTION;
25
+ // The rollback switch works even if stale advanced settings are malformed.
26
+ if (enabled === 'false')
27
+ return false;
28
+ const options = (0, _12factor_env_1.cleanEnv)(environment, {
29
+ GRAPHILE_SCOPED_INTROSPECTION_CATALOG_TYPES: (0, _12factor_env_1.enumerated)(['all', 'dependency-closure'], { default: undefined }),
30
+ GRAPHILE_SCOPED_INTROSPECTION_CAPABILITY_EXTENSIONS: capabilityExtensions({
31
+ default: undefined
32
+ })
33
+ });
34
+ const catalogTypes = options.GRAPHILE_SCOPED_INTROSPECTION_CATALOG_TYPES;
35
+ const extensions = options.GRAPHILE_SCOPED_INTROSPECTION_CAPABILITY_EXTENSIONS;
36
+ if (enabled === undefined && catalogTypes === undefined && extensions === undefined) {
37
+ return undefined;
38
+ }
39
+ // An object enables scoped introspection while deepmerge preserves advanced
40
+ // fields from pgpm.json. A literal `true` would discard those fields.
41
+ return {
42
+ ...(catalogTypes !== undefined && { catalogTypes }),
43
+ ...(extensions !== undefined && { capabilityExtensions: extensions })
44
+ };
45
+ };
46
+ exports.getScopedIntrospectionEnv = getScopedIntrospectionEnv;