@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 +95 -1
- package/env.js +9 -0
- package/esm/env.js +9 -0
- package/esm/scoped-introspection.d.ts +3 -0
- package/esm/scoped-introspection.js +42 -0
- package/package.json +4 -4
- package/scoped-introspection.d.ts +3 -0
- package/scoped-introspection.js +46 -0
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: {
|
|
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.
|
|
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.
|
|
34
|
-
"@pgpmjs/env": "^2.
|
|
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": "
|
|
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;
|