graphile-settings 4.10.3 → 4.11.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/esm/plugins/blueprint-types/index.d.ts +11 -0
- package/esm/plugins/blueprint-types/index.js +12 -0
- package/esm/plugins/blueprint-types/json-schema-to-graphql.d.ts +64 -0
- package/esm/plugins/blueprint-types/json-schema-to-graphql.js +89 -0
- package/esm/plugins/blueprint-types/plugin.d.ts +95 -0
- package/esm/plugins/blueprint-types/plugin.js +287 -0
- package/esm/plugins/index.d.ts +2 -0
- package/esm/plugins/index.js +5 -0
- package/esm/presets/constructive-preset.js +2 -1
- package/package.json +2 -2
- package/plugins/blueprint-types/index.d.ts +11 -0
- package/plugins/blueprint-types/index.js +18 -0
- package/plugins/blueprint-types/json-schema-to-graphql.d.ts +64 -0
- package/plugins/blueprint-types/json-schema-to-graphql.js +92 -0
- package/plugins/blueprint-types/plugin.d.ts +95 -0
- package/plugins/blueprint-types/plugin.js +292 -0
- package/plugins/index.d.ts +2 -0
- package/plugins/index.js +9 -1
- package/presets/constructive-preset.js +1 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node Type Registry Plugin
|
|
3
|
+
*
|
|
4
|
+
* Exports the gather-phase plugin and preset for generating @oneOf typed
|
|
5
|
+
* input types for blueprint definitions from node_type_registry.
|
|
6
|
+
*
|
|
7
|
+
* The NodeTypeRegistryPlugin queries node_type_registry through the existing
|
|
8
|
+
* pgService connection during the gather phase — no separate pool needed.
|
|
9
|
+
*/
|
|
10
|
+
export { NodeTypeRegistryPlugin, NodeTypeRegistryPreset, createBlueprintTypesPlugin, BlueprintTypesPreset, } from './plugin';
|
|
11
|
+
export type { NodeTypeRegistryEntry } from './plugin';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node Type Registry Plugin
|
|
3
|
+
*
|
|
4
|
+
* Exports the gather-phase plugin and preset for generating @oneOf typed
|
|
5
|
+
* input types for blueprint definitions from node_type_registry.
|
|
6
|
+
*
|
|
7
|
+
* The NodeTypeRegistryPlugin queries node_type_registry through the existing
|
|
8
|
+
* pgService connection during the gather phase — no separate pool needed.
|
|
9
|
+
*/
|
|
10
|
+
export { NodeTypeRegistryPlugin, NodeTypeRegistryPreset,
|
|
11
|
+
// Legacy exports for backward compatibility
|
|
12
|
+
createBlueprintTypesPlugin, BlueprintTypesPreset, } from './plugin';
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON Schema to GraphQL Input Field Spec Converter
|
|
3
|
+
*
|
|
4
|
+
* Converts JSON Schema objects (from node_type_registry.parameter_schema)
|
|
5
|
+
* into field spec objects compatible with PostGraphile v5's fieldWithHooks API.
|
|
6
|
+
*
|
|
7
|
+
* Handles:
|
|
8
|
+
* - string, integer, number, boolean primitives
|
|
9
|
+
* - arrays (items -> GraphQLList)
|
|
10
|
+
* - enums -> GraphQLEnumType
|
|
11
|
+
* - required fields -> GraphQLNonNull
|
|
12
|
+
* - union types (["integer", "string"]) -> JSON scalar fallback
|
|
13
|
+
*/
|
|
14
|
+
import type { GraphQLInputType, GraphQLScalarType, GraphQLNullableType } from 'graphql';
|
|
15
|
+
interface JsonSchema {
|
|
16
|
+
type: string;
|
|
17
|
+
properties?: Record<string, unknown>;
|
|
18
|
+
required?: string[];
|
|
19
|
+
anyOf?: JsonSchema[];
|
|
20
|
+
description?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* A field spec that mirrors GraphQLInputFieldConfig.
|
|
24
|
+
*/
|
|
25
|
+
export interface FieldSpec {
|
|
26
|
+
type: GraphQLInputType;
|
|
27
|
+
description?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Minimal build interface — only what we need from PostGraphile's Build object.
|
|
31
|
+
*
|
|
32
|
+
* Uses permissive constructor signatures to avoid variance issues between
|
|
33
|
+
* GraphQLNullableType and GraphQLInputType in the graphql-js type hierarchy.
|
|
34
|
+
*/
|
|
35
|
+
export interface BuildLike {
|
|
36
|
+
graphql: {
|
|
37
|
+
GraphQLString: GraphQLScalarType;
|
|
38
|
+
GraphQLInt: GraphQLScalarType;
|
|
39
|
+
GraphQLFloat: GraphQLScalarType;
|
|
40
|
+
GraphQLBoolean: GraphQLScalarType;
|
|
41
|
+
GraphQLNonNull: new (type: any) => GraphQLInputType;
|
|
42
|
+
GraphQLList: new (type: any) => GraphQLInputType & GraphQLNullableType;
|
|
43
|
+
GraphQLEnumType: new (config: {
|
|
44
|
+
name: string;
|
|
45
|
+
values: Record<string, {
|
|
46
|
+
value: string;
|
|
47
|
+
}>;
|
|
48
|
+
}) => GraphQLInputType & GraphQLNullableType;
|
|
49
|
+
};
|
|
50
|
+
getTypeByName: (name: string) => unknown;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Convert a full JSON Schema (from parameter_schema) to field specs.
|
|
54
|
+
*
|
|
55
|
+
* Uses the build object from PostGraphile v5 to access GraphQL types,
|
|
56
|
+
* avoiding direct graphql imports (which can cause version mismatches).
|
|
57
|
+
*
|
|
58
|
+
* @param schema - The JSON Schema object
|
|
59
|
+
* @param typeName - The name prefix for generated enum types
|
|
60
|
+
* @param build - The PostGraphile build object
|
|
61
|
+
* @returns Record of field name -> FieldSpec
|
|
62
|
+
*/
|
|
63
|
+
export declare function jsonSchemaToGraphQLFieldSpecs(schema: JsonSchema, typeName: string, build: BuildLike): Record<string, FieldSpec>;
|
|
64
|
+
export {};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON Schema to GraphQL Input Field Spec Converter
|
|
3
|
+
*
|
|
4
|
+
* Converts JSON Schema objects (from node_type_registry.parameter_schema)
|
|
5
|
+
* into field spec objects compatible with PostGraphile v5's fieldWithHooks API.
|
|
6
|
+
*
|
|
7
|
+
* Handles:
|
|
8
|
+
* - string, integer, number, boolean primitives
|
|
9
|
+
* - arrays (items -> GraphQLList)
|
|
10
|
+
* - enums -> GraphQLEnumType
|
|
11
|
+
* - required fields -> GraphQLNonNull
|
|
12
|
+
* - union types (["integer", "string"]) -> JSON scalar fallback
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Convert a JSON Schema type string to a GraphQL scalar type.
|
|
16
|
+
*/
|
|
17
|
+
function jsonTypeToGraphQL(jsonType, graphql, jsonScalar) {
|
|
18
|
+
if (Array.isArray(jsonType)) {
|
|
19
|
+
return jsonScalar;
|
|
20
|
+
}
|
|
21
|
+
switch (jsonType) {
|
|
22
|
+
case 'string':
|
|
23
|
+
return graphql.GraphQLString;
|
|
24
|
+
case 'integer':
|
|
25
|
+
return graphql.GraphQLInt;
|
|
26
|
+
case 'number':
|
|
27
|
+
return graphql.GraphQLFloat;
|
|
28
|
+
case 'boolean':
|
|
29
|
+
return graphql.GraphQLBoolean;
|
|
30
|
+
case 'object':
|
|
31
|
+
return jsonScalar;
|
|
32
|
+
case 'array':
|
|
33
|
+
return new graphql.GraphQLList(jsonScalar);
|
|
34
|
+
default:
|
|
35
|
+
return jsonScalar;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Convert a full JSON Schema (from parameter_schema) to field specs.
|
|
40
|
+
*
|
|
41
|
+
* Uses the build object from PostGraphile v5 to access GraphQL types,
|
|
42
|
+
* avoiding direct graphql imports (which can cause version mismatches).
|
|
43
|
+
*
|
|
44
|
+
* @param schema - The JSON Schema object
|
|
45
|
+
* @param typeName - The name prefix for generated enum types
|
|
46
|
+
* @param build - The PostGraphile build object
|
|
47
|
+
* @returns Record of field name -> FieldSpec
|
|
48
|
+
*/
|
|
49
|
+
export function jsonSchemaToGraphQLFieldSpecs(schema, typeName, build) {
|
|
50
|
+
const fields = {};
|
|
51
|
+
const properties = schema.properties;
|
|
52
|
+
if (!properties) {
|
|
53
|
+
return fields;
|
|
54
|
+
}
|
|
55
|
+
const { graphql } = build;
|
|
56
|
+
const jsonScalar = (build.getTypeByName('JSON') ?? graphql.GraphQLString);
|
|
57
|
+
const required = new Set(schema.required ?? []);
|
|
58
|
+
for (const [propName, prop] of Object.entries(properties)) {
|
|
59
|
+
let fieldType;
|
|
60
|
+
if (prop.enum && prop.enum.length > 0) {
|
|
61
|
+
const values = {};
|
|
62
|
+
for (const val of prop.enum) {
|
|
63
|
+
const safeName = val.replace(/[^_a-zA-Z0-9]/g, '_').toUpperCase();
|
|
64
|
+
values[safeName] = { value: val };
|
|
65
|
+
}
|
|
66
|
+
fieldType = new graphql.GraphQLEnumType({
|
|
67
|
+
name: `${typeName}_${propName}`,
|
|
68
|
+
values,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
else if (prop.type === 'array' && prop.items) {
|
|
72
|
+
const innerType = prop.items.type
|
|
73
|
+
? jsonTypeToGraphQL(prop.items.type, graphql, jsonScalar)
|
|
74
|
+
: jsonScalar;
|
|
75
|
+
fieldType = new graphql.GraphQLList(innerType);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
fieldType = jsonTypeToGraphQL(prop.type ?? 'string', graphql, jsonScalar);
|
|
79
|
+
}
|
|
80
|
+
if (required.has(propName)) {
|
|
81
|
+
fieldType = new graphql.GraphQLNonNull(fieldType);
|
|
82
|
+
}
|
|
83
|
+
fields[propName] = {
|
|
84
|
+
type: fieldType,
|
|
85
|
+
description: prop.description ?? undefined,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return fields;
|
|
89
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node Type Registry Plugin
|
|
3
|
+
*
|
|
4
|
+
* Generic PostGraphile v5 plugin that queries node_type_registry at schema
|
|
5
|
+
* build time through the existing pgService connection (gather phase) and
|
|
6
|
+
* generates @oneOf typed input types with SuperCase node type names as
|
|
7
|
+
* discriminant keys.
|
|
8
|
+
*
|
|
9
|
+
* Architecture (correct PostGraphile v5 pattern):
|
|
10
|
+
* 1. `gather` phase: hooks into pgIntrospection_introspection to query
|
|
11
|
+
* node_type_registry through the existing pgService (no separate pool —
|
|
12
|
+
* uses withPgClientFromPgService like PgIntrospectionPlugin)
|
|
13
|
+
* 2. `init` hook: registers all input type shells from gathered entries
|
|
14
|
+
* 3. `GraphQLInputObjectType_fields` hook: populates fields on those types
|
|
15
|
+
*
|
|
16
|
+
* Generated type hierarchy:
|
|
17
|
+
*
|
|
18
|
+
* BlueprintDefinitionInput
|
|
19
|
+
* +-- tables: [BlueprintTableInput!]
|
|
20
|
+
* | +-- ref: String!
|
|
21
|
+
* | +-- tableName: String!
|
|
22
|
+
* | +-- nodes: [BlueprintNodeInput!]! <-- @oneOf
|
|
23
|
+
* | +-- shorthand: String
|
|
24
|
+
* | +-- DataId: DataIdParams
|
|
25
|
+
* | +-- DataTimestamps: DataTimestampsParams
|
|
26
|
+
* | +-- AuthzEntityMembership: AuthzEntityMembershipParams
|
|
27
|
+
* | +-- ...
|
|
28
|
+
* +-- relations: [BlueprintRelationInput!] <-- @oneOf
|
|
29
|
+
* +-- RelationBelongsTo: RelationBelongsToParams
|
|
30
|
+
* +-- ...
|
|
31
|
+
*
|
|
32
|
+
* When codegen runs, @oneOf types become discriminated union types:
|
|
33
|
+
*
|
|
34
|
+
* type BlueprintNodeInput =
|
|
35
|
+
* | { DataId: DataIdParams }
|
|
36
|
+
* | { DataTimestamps: DataTimestampsParams }
|
|
37
|
+
* | { AuthzEntityMembership: AuthzEntityMembershipParams }
|
|
38
|
+
*/
|
|
39
|
+
import type { GraphileConfig } from 'graphile-config';
|
|
40
|
+
export interface NodeTypeRegistryEntry {
|
|
41
|
+
name: string;
|
|
42
|
+
slug: string;
|
|
43
|
+
category: string;
|
|
44
|
+
display_name: string;
|
|
45
|
+
description: string;
|
|
46
|
+
parameter_schema: Record<string, unknown>;
|
|
47
|
+
tags: string[];
|
|
48
|
+
}
|
|
49
|
+
declare global {
|
|
50
|
+
namespace GraphileBuild {
|
|
51
|
+
interface ScopeInputObject {
|
|
52
|
+
isBlueprintOneOf?: boolean;
|
|
53
|
+
isBlueprintNodeParams?: boolean;
|
|
54
|
+
isBlueprintTable?: boolean;
|
|
55
|
+
isBlueprintDefinition?: boolean;
|
|
56
|
+
isBlueprintRelation?: boolean;
|
|
57
|
+
blueprintNodeTypeName?: string;
|
|
58
|
+
}
|
|
59
|
+
interface BuildInput {
|
|
60
|
+
nodeTypeRegistryEntries?: NodeTypeRegistryEntry[];
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* NodeTypeRegistryPlugin
|
|
66
|
+
*
|
|
67
|
+
* Gather-phase plugin that queries node_type_registry through the existing
|
|
68
|
+
* pgService connection, then generates @oneOf typed input types in the
|
|
69
|
+
* schema phase. No separate PG pool -- uses the same connection PostGraphile
|
|
70
|
+
* already has (same pattern as PgIntrospectionPlugin / PgEnumTablesPlugin).
|
|
71
|
+
*
|
|
72
|
+
* If the table doesn't exist (42P01) or the query fails, the plugin
|
|
73
|
+
* gracefully skips and no types are registered.
|
|
74
|
+
*/
|
|
75
|
+
export declare const NodeTypeRegistryPlugin: GraphileConfig.Plugin;
|
|
76
|
+
/**
|
|
77
|
+
* Preset that includes the NodeTypeRegistryPlugin.
|
|
78
|
+
*
|
|
79
|
+
* Add to ConstructivePreset.extends[] -- the plugin will automatically
|
|
80
|
+
* query node_type_registry through the existing pgService connection
|
|
81
|
+
* during the gather phase. No separate pool, no manual wiring.
|
|
82
|
+
*/
|
|
83
|
+
export declare const NodeTypeRegistryPreset: GraphileConfig.Preset;
|
|
84
|
+
/**
|
|
85
|
+
* @deprecated Use NodeTypeRegistryPlugin directly. The gather-phase plugin
|
|
86
|
+
* queries node_type_registry automatically via pgService. This factory is
|
|
87
|
+
* kept for backward compatibility and tests that pass static entries.
|
|
88
|
+
*/
|
|
89
|
+
export declare function createBlueprintTypesPlugin(nodeTypes: NodeTypeRegistryEntry[]): GraphileConfig.Plugin;
|
|
90
|
+
/**
|
|
91
|
+
* @deprecated Use NodeTypeRegistryPreset directly. The gather-phase plugin
|
|
92
|
+
* queries node_type_registry automatically via pgService. This factory is
|
|
93
|
+
* kept for backward compatibility and tests that pass static entries.
|
|
94
|
+
*/
|
|
95
|
+
export declare function BlueprintTypesPreset(nodeTypes: NodeTypeRegistryEntry[]): GraphileConfig.Preset;
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { withPgClientFromPgService } from 'graphile-build-pg';
|
|
2
|
+
import { jsonSchemaToGraphQLFieldSpecs } from './json-schema-to-graphql';
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// Constants
|
|
5
|
+
// ============================================================================
|
|
6
|
+
const BLUEPRINT_NODE_INPUT = 'BlueprintNodeInput';
|
|
7
|
+
const BLUEPRINT_TABLE_INPUT = 'BlueprintTableInput';
|
|
8
|
+
const BLUEPRINT_RELATION_INPUT = 'BlueprintRelationInput';
|
|
9
|
+
const BLUEPRINT_DEFINITION_INPUT = 'BlueprintDefinitionInput';
|
|
10
|
+
const NODE_TYPE_REGISTRY_QUERY = `
|
|
11
|
+
SELECT
|
|
12
|
+
name,
|
|
13
|
+
slug,
|
|
14
|
+
category,
|
|
15
|
+
COALESCE(display_name, name) as display_name,
|
|
16
|
+
COALESCE(description, '') as description,
|
|
17
|
+
COALESCE(parameter_schema, '{}'::jsonb) as parameter_schema,
|
|
18
|
+
COALESCE(tags, '{}') as tags
|
|
19
|
+
FROM metaschema_public.node_type_registry
|
|
20
|
+
ORDER BY category, name
|
|
21
|
+
`;
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// Shared schema hooks
|
|
24
|
+
// ============================================================================
|
|
25
|
+
/**
|
|
26
|
+
* Builds the schema hooks (init + GraphQLInputObjectType_fields) from
|
|
27
|
+
* a function that retrieves node type entries. Used by both the gather-phase
|
|
28
|
+
* plugin (reads from build.input) and the static/legacy plugin (closure).
|
|
29
|
+
*/
|
|
30
|
+
function buildSchemaHooks(getNodeTypes) {
|
|
31
|
+
return {
|
|
32
|
+
hooks: {
|
|
33
|
+
init(_, build) {
|
|
34
|
+
const nodeTypes = getNodeTypes(build);
|
|
35
|
+
if (nodeTypes.length === 0)
|
|
36
|
+
return _;
|
|
37
|
+
for (const nt of nodeTypes) {
|
|
38
|
+
const paramsTypeName = nt.name + 'Params';
|
|
39
|
+
build.registerInputObjectType(paramsTypeName, {
|
|
40
|
+
isBlueprintNodeParams: true,
|
|
41
|
+
blueprintNodeTypeName: nt.name,
|
|
42
|
+
}, () => ({
|
|
43
|
+
description: nt.description,
|
|
44
|
+
}), 'NodeTypeRegistryPlugin: ' + paramsTypeName);
|
|
45
|
+
}
|
|
46
|
+
build.registerInputObjectType(BLUEPRINT_NODE_INPUT, { isBlueprintOneOf: true }, () => ({
|
|
47
|
+
description: 'A single node in a blueprint definition. Exactly one field must be set. Use the SuperCase node type name as the key with its parameters as the value, or use "shorthand" with just the node type name string.',
|
|
48
|
+
isOneOf: true,
|
|
49
|
+
}), 'NodeTypeRegistryPlugin: BlueprintNodeInput');
|
|
50
|
+
build.registerInputObjectType(BLUEPRINT_RELATION_INPUT, { isBlueprintOneOf: true, isBlueprintRelation: true }, () => ({
|
|
51
|
+
description: 'A relation in a blueprint definition. Exactly one field must be set. Use the SuperCase relation type name as the key.',
|
|
52
|
+
isOneOf: true,
|
|
53
|
+
}), 'NodeTypeRegistryPlugin: BlueprintRelationInput');
|
|
54
|
+
build.registerInputObjectType(BLUEPRINT_TABLE_INPUT, { isBlueprintTable: true }, () => ({
|
|
55
|
+
description: 'A table definition within a blueprint. Specifies the table reference, name, and an array of typed nodes.',
|
|
56
|
+
}), 'NodeTypeRegistryPlugin: BlueprintTableInput');
|
|
57
|
+
build.registerInputObjectType(BLUEPRINT_DEFINITION_INPUT, { isBlueprintDefinition: true }, () => ({
|
|
58
|
+
description: 'The complete blueprint definition. Contains tables with typed nodes and relations.',
|
|
59
|
+
}), 'NodeTypeRegistryPlugin: BlueprintDefinitionInput');
|
|
60
|
+
return _;
|
|
61
|
+
},
|
|
62
|
+
GraphQLInputObjectType_fields(fields, build, context) {
|
|
63
|
+
const nodeTypes = getNodeTypes(build);
|
|
64
|
+
if (nodeTypes.length === 0)
|
|
65
|
+
return fields;
|
|
66
|
+
const nodeTypesByName = new Map();
|
|
67
|
+
const relationNodeTypes = [];
|
|
68
|
+
const nonRelationNodeTypes = [];
|
|
69
|
+
for (const nt of nodeTypes) {
|
|
70
|
+
nodeTypesByName.set(nt.name, nt);
|
|
71
|
+
if (nt.category === 'relation') {
|
|
72
|
+
relationNodeTypes.push(nt);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
nonRelationNodeTypes.push(nt);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const { extend, graphql: { GraphQLString, GraphQLNonNull, GraphQLList }, } = build;
|
|
79
|
+
const { fieldWithHooks, scope: { isBlueprintOneOf, isBlueprintNodeParams, isBlueprintTable, isBlueprintDefinition, isBlueprintRelation, blueprintNodeTypeName, }, } = context;
|
|
80
|
+
// --- Per-node-type params (e.g., DataIdParams) ---
|
|
81
|
+
if (isBlueprintNodeParams && blueprintNodeTypeName) {
|
|
82
|
+
const nt = nodeTypesByName.get(blueprintNodeTypeName);
|
|
83
|
+
if (!nt)
|
|
84
|
+
return fields;
|
|
85
|
+
const schema = nt.parameter_schema;
|
|
86
|
+
const fieldSpecs = jsonSchemaToGraphQLFieldSpecs(schema, nt.name + 'Params', build);
|
|
87
|
+
if (Object.keys(fieldSpecs).length > 0) {
|
|
88
|
+
let result = fields;
|
|
89
|
+
for (const [fieldName, spec] of Object.entries(fieldSpecs)) {
|
|
90
|
+
result = extend(result, {
|
|
91
|
+
[fieldName]: fieldWithHooks({ fieldName }, () => spec),
|
|
92
|
+
}, 'NodeTypeRegistryPlugin: ' + blueprintNodeTypeName + '.' + fieldName);
|
|
93
|
+
}
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
return extend(fields, {
|
|
97
|
+
_: fieldWithHooks({ fieldName: '_' }, () => ({
|
|
98
|
+
type: build.graphql.GraphQLBoolean,
|
|
99
|
+
description: 'No parameters required. Pass true or omit entirely.',
|
|
100
|
+
})),
|
|
101
|
+
}, 'NodeTypeRegistryPlugin: ' + blueprintNodeTypeName + '._');
|
|
102
|
+
}
|
|
103
|
+
// --- BlueprintNodeInput (@oneOf with all non-relation node types) ---
|
|
104
|
+
if (isBlueprintOneOf && !isBlueprintRelation) {
|
|
105
|
+
let result = fields;
|
|
106
|
+
result = extend(result, {
|
|
107
|
+
shorthand: fieldWithHooks({ fieldName: 'shorthand' }, () => ({
|
|
108
|
+
type: GraphQLString,
|
|
109
|
+
description: 'String shorthand: just the SuperCase node type name (e.g., "DataTimestamps"). Use when the node type has no required parameters.',
|
|
110
|
+
})),
|
|
111
|
+
}, 'NodeTypeRegistryPlugin: BlueprintNodeInput.shorthand');
|
|
112
|
+
for (const nt of nonRelationNodeTypes) {
|
|
113
|
+
const paramsTypeName = nt.name + 'Params';
|
|
114
|
+
const ParamsType = build.getTypeByName(paramsTypeName);
|
|
115
|
+
if (!ParamsType)
|
|
116
|
+
continue;
|
|
117
|
+
result = extend(result, {
|
|
118
|
+
[nt.name]: fieldWithHooks({ fieldName: nt.name }, () => ({
|
|
119
|
+
type: ParamsType,
|
|
120
|
+
description: 'Parameters for ' + nt.name + ' (' + nt.display_name + ')',
|
|
121
|
+
})),
|
|
122
|
+
}, 'NodeTypeRegistryPlugin: BlueprintNodeInput.' + nt.name);
|
|
123
|
+
}
|
|
124
|
+
return result;
|
|
125
|
+
}
|
|
126
|
+
// --- BlueprintRelationInput (@oneOf with relation node types) ---
|
|
127
|
+
if (isBlueprintOneOf && isBlueprintRelation) {
|
|
128
|
+
let result = fields;
|
|
129
|
+
for (const nt of relationNodeTypes) {
|
|
130
|
+
const paramsTypeName = nt.name + 'Params';
|
|
131
|
+
const ParamsType = build.getTypeByName(paramsTypeName);
|
|
132
|
+
if (!ParamsType)
|
|
133
|
+
continue;
|
|
134
|
+
result = extend(result, {
|
|
135
|
+
[nt.name]: fieldWithHooks({ fieldName: nt.name }, () => ({
|
|
136
|
+
type: ParamsType,
|
|
137
|
+
description: 'Parameters for ' + nt.name + ' (' + nt.display_name + ')',
|
|
138
|
+
})),
|
|
139
|
+
}, 'NodeTypeRegistryPlugin: BlueprintRelationInput.' + nt.name);
|
|
140
|
+
}
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
// --- BlueprintTableInput ---
|
|
144
|
+
if (isBlueprintTable) {
|
|
145
|
+
const BlueprintNodeInputType = build.getTypeByName(BLUEPRINT_NODE_INPUT);
|
|
146
|
+
return extend(fields, {
|
|
147
|
+
ref: fieldWithHooks({ fieldName: 'ref' }, () => ({
|
|
148
|
+
type: new GraphQLNonNull(GraphQLString),
|
|
149
|
+
description: 'Unique reference key for this table within the blueprint (used for cross-references in relations).',
|
|
150
|
+
})),
|
|
151
|
+
tableName: fieldWithHooks({ fieldName: 'tableName' }, () => ({
|
|
152
|
+
type: new GraphQLNonNull(GraphQLString),
|
|
153
|
+
description: 'The PostgreSQL table name to create.',
|
|
154
|
+
})),
|
|
155
|
+
nodes: fieldWithHooks({ fieldName: 'nodes' }, () => ({
|
|
156
|
+
type: BlueprintNodeInputType
|
|
157
|
+
? new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(BlueprintNodeInputType)))
|
|
158
|
+
: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(GraphQLString))),
|
|
159
|
+
description: 'Array of node type entries (data, authz, field behaviors) to apply to this table.',
|
|
160
|
+
})),
|
|
161
|
+
}, 'NodeTypeRegistryPlugin: BlueprintTableInput fields');
|
|
162
|
+
}
|
|
163
|
+
// --- BlueprintDefinitionInput ---
|
|
164
|
+
if (isBlueprintDefinition) {
|
|
165
|
+
const BlueprintTableInputType = build.getTypeByName(BLUEPRINT_TABLE_INPUT);
|
|
166
|
+
const BlueprintRelationInputType = build.getTypeByName(BLUEPRINT_RELATION_INPUT);
|
|
167
|
+
return extend(fields, {
|
|
168
|
+
tables: fieldWithHooks({ fieldName: 'tables' }, () => ({
|
|
169
|
+
type: BlueprintTableInputType
|
|
170
|
+
? new GraphQLList(new GraphQLNonNull(BlueprintTableInputType))
|
|
171
|
+
: new GraphQLList(GraphQLString),
|
|
172
|
+
description: 'Array of table definitions.',
|
|
173
|
+
})),
|
|
174
|
+
relations: fieldWithHooks({ fieldName: 'relations' }, () => ({
|
|
175
|
+
type: BlueprintRelationInputType
|
|
176
|
+
? new GraphQLList(new GraphQLNonNull(BlueprintRelationInputType))
|
|
177
|
+
: new GraphQLList(GraphQLString),
|
|
178
|
+
description: 'Array of relation definitions.',
|
|
179
|
+
})),
|
|
180
|
+
}, 'NodeTypeRegistryPlugin: BlueprintDefinitionInput fields');
|
|
181
|
+
}
|
|
182
|
+
return fields;
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
// ============================================================================
|
|
188
|
+
// Gather-phase plugin (production -- queries DB through existing pgService)
|
|
189
|
+
// ============================================================================
|
|
190
|
+
/**
|
|
191
|
+
* NodeTypeRegistryPlugin
|
|
192
|
+
*
|
|
193
|
+
* Gather-phase plugin that queries node_type_registry through the existing
|
|
194
|
+
* pgService connection, then generates @oneOf typed input types in the
|
|
195
|
+
* schema phase. No separate PG pool -- uses the same connection PostGraphile
|
|
196
|
+
* already has (same pattern as PgIntrospectionPlugin / PgEnumTablesPlugin).
|
|
197
|
+
*
|
|
198
|
+
* If the table doesn't exist (42P01) or the query fails, the plugin
|
|
199
|
+
* gracefully skips and no types are registered.
|
|
200
|
+
*/
|
|
201
|
+
export const NodeTypeRegistryPlugin = {
|
|
202
|
+
name: 'NodeTypeRegistryPlugin',
|
|
203
|
+
version: '1.0.0',
|
|
204
|
+
description: 'Queries node_type_registry via the existing pgService and generates @oneOf typed input types with SuperCase keys',
|
|
205
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
206
|
+
gather: {
|
|
207
|
+
namespace: 'nodeTypeRegistry',
|
|
208
|
+
version: 1,
|
|
209
|
+
initialState() {
|
|
210
|
+
return { entries: [] };
|
|
211
|
+
},
|
|
212
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
213
|
+
async finalize(info) {
|
|
214
|
+
info.buildInput.nodeTypeRegistryEntries =
|
|
215
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
216
|
+
info.state.entries ?? [];
|
|
217
|
+
},
|
|
218
|
+
hooks: {
|
|
219
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
220
|
+
async pgIntrospection_introspection(info, event) {
|
|
221
|
+
const { serviceName } = event;
|
|
222
|
+
const pgService = info.resolvedPreset.pgServices?.find((svc) => svc.name === serviceName);
|
|
223
|
+
if (!pgService)
|
|
224
|
+
return;
|
|
225
|
+
try {
|
|
226
|
+
const result = await withPgClientFromPgService(pgService, null, (client) => client.query({ text: NODE_TYPE_REGISTRY_QUERY }));
|
|
227
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
228
|
+
const state = info.state;
|
|
229
|
+
if (!state.entries) {
|
|
230
|
+
state.entries = [];
|
|
231
|
+
}
|
|
232
|
+
state.entries.push(...result.rows);
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
const pgError = error;
|
|
236
|
+
if (pgError.code === '42P01') {
|
|
237
|
+
// 42P01 = undefined_table -- expected when DB hasn't been migrated
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
// Warn but don't fail the schema build
|
|
241
|
+
console.warn('[NodeTypeRegistryPlugin] Failed to query node_type_registry:', pgError.message ?? String(error));
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
246
|
+
},
|
|
247
|
+
schema: buildSchemaHooks((build) => build.input.nodeTypeRegistryEntries ?? []),
|
|
248
|
+
};
|
|
249
|
+
// ============================================================================
|
|
250
|
+
// Preset
|
|
251
|
+
// ============================================================================
|
|
252
|
+
/**
|
|
253
|
+
* Preset that includes the NodeTypeRegistryPlugin.
|
|
254
|
+
*
|
|
255
|
+
* Add to ConstructivePreset.extends[] -- the plugin will automatically
|
|
256
|
+
* query node_type_registry through the existing pgService connection
|
|
257
|
+
* during the gather phase. No separate pool, no manual wiring.
|
|
258
|
+
*/
|
|
259
|
+
export const NodeTypeRegistryPreset = {
|
|
260
|
+
plugins: [NodeTypeRegistryPlugin],
|
|
261
|
+
};
|
|
262
|
+
// ============================================================================
|
|
263
|
+
// Legacy exports for backward compatibility
|
|
264
|
+
// ============================================================================
|
|
265
|
+
/**
|
|
266
|
+
* @deprecated Use NodeTypeRegistryPlugin directly. The gather-phase plugin
|
|
267
|
+
* queries node_type_registry automatically via pgService. This factory is
|
|
268
|
+
* kept for backward compatibility and tests that pass static entries.
|
|
269
|
+
*/
|
|
270
|
+
export function createBlueprintTypesPlugin(nodeTypes) {
|
|
271
|
+
return {
|
|
272
|
+
name: 'BlueprintTypesPlugin',
|
|
273
|
+
version: '1.0.0',
|
|
274
|
+
description: 'Generates @oneOf typed input types from static node_type_registry entries',
|
|
275
|
+
schema: buildSchemaHooks(() => nodeTypes),
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* @deprecated Use NodeTypeRegistryPreset directly. The gather-phase plugin
|
|
280
|
+
* queries node_type_registry automatically via pgService. This factory is
|
|
281
|
+
* kept for backward compatibility and tests that pass static entries.
|
|
282
|
+
*/
|
|
283
|
+
export function BlueprintTypesPreset(nodeTypes) {
|
|
284
|
+
return {
|
|
285
|
+
plugins: [createBlueprintTypesPlugin(nodeTypes)],
|
|
286
|
+
};
|
|
287
|
+
}
|
package/esm/plugins/index.d.ts
CHANGED
|
@@ -20,3 +20,5 @@ export { _pgTypeToGqlType, _buildFieldMeta, _cachedTablesMeta } from './meta-sch
|
|
|
20
20
|
export { RequiredInputPlugin, RequiredInputPreset, } from './required-input-plugin';
|
|
21
21
|
export { createUnifiedSearchPlugin, UnifiedSearchPreset, TsvectorCodecPlugin, TsvectorCodecPreset, createTsvectorCodecPlugin, Bm25CodecPlugin, Bm25CodecPreset, bm25IndexStore, VectorCodecPlugin, VectorCodecPreset, createTsvectorAdapter, createBm25Adapter, createTrgmAdapter, createPgvectorAdapter, createMatchesOperatorFactory, createTrgmOperatorFactories, } from 'graphile-search';
|
|
22
22
|
export type { SearchAdapter, SearchableColumn, UnifiedSearchOptions, UnifiedSearchPresetOptions, TsvectorCodecPluginOptions, Bm25IndexInfo, TsvectorAdapterOptions, Bm25AdapterOptions, TrgmAdapterOptions, PgvectorAdapterOptions, } from 'graphile-search';
|
|
23
|
+
export { NodeTypeRegistryPlugin, NodeTypeRegistryPreset, createBlueprintTypesPlugin, BlueprintTypesPreset, } from './blueprint-types';
|
|
24
|
+
export type { NodeTypeRegistryEntry } from './blueprint-types';
|
package/esm/plugins/index.js
CHANGED
|
@@ -37,3 +37,8 @@ TsvectorCodecPlugin, TsvectorCodecPreset, createTsvectorCodecPlugin, Bm25CodecPl
|
|
|
37
37
|
createTsvectorAdapter, createBm25Adapter, createTrgmAdapter, createPgvectorAdapter,
|
|
38
38
|
// Operator factories for connection filter integration
|
|
39
39
|
createMatchesOperatorFactory, createTrgmOperatorFactories, } from 'graphile-search';
|
|
40
|
+
// Node type registry — @oneOf typed input types for blueprint definitions
|
|
41
|
+
// Gather-phase plugin queries node_type_registry through existing pgService
|
|
42
|
+
export { NodeTypeRegistryPlugin, NodeTypeRegistryPreset,
|
|
43
|
+
// Legacy exports for backward compatibility
|
|
44
|
+
createBlueprintTypesPlugin, BlueprintTypesPreset, } from './blueprint-types';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ConnectionFilterPreset } from 'graphile-connection-filter';
|
|
2
|
-
import { MinimalPreset, InflektPreset, ConflictDetectorPreset, InflectorLoggerPreset, NoUniqueLookupPreset, EnableAllFilterColumnsPreset, ManyToManyOptInPreset, MetaSchemaPreset, PgTypeMappingsPreset, RequiredInputPreset, } from '../plugins';
|
|
2
|
+
import { MinimalPreset, InflektPreset, ConflictDetectorPreset, InflectorLoggerPreset, NoUniqueLookupPreset, EnableAllFilterColumnsPreset, ManyToManyOptInPreset, MetaSchemaPreset, PgTypeMappingsPreset, RequiredInputPreset, NodeTypeRegistryPreset, } from '../plugins';
|
|
3
3
|
import { UnifiedSearchPreset, createMatchesOperatorFactory, createTrgmOperatorFactories } from 'graphile-search';
|
|
4
4
|
import { GraphilePostgisPreset, createPostgisOperatorFactory } from 'graphile-postgis';
|
|
5
5
|
import { UploadPreset } from 'graphile-upload-plugin';
|
|
@@ -77,6 +77,7 @@ export const ConstructivePreset = {
|
|
|
77
77
|
SqlExpressionValidatorPreset(),
|
|
78
78
|
PgTypeMappingsPreset,
|
|
79
79
|
RequiredInputPreset,
|
|
80
|
+
NodeTypeRegistryPreset,
|
|
80
81
|
],
|
|
81
82
|
/**
|
|
82
83
|
* Disable PostGraphile core's condition argument entirely.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "graphile-settings",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.11.0",
|
|
4
4
|
"author": "Constructive <developers@constructive.io>",
|
|
5
5
|
"description": "graphile settings",
|
|
6
6
|
"main": "index.js",
|
|
@@ -80,5 +80,5 @@
|
|
|
80
80
|
"constructive",
|
|
81
81
|
"graphql"
|
|
82
82
|
],
|
|
83
|
-
"gitHead": "
|
|
83
|
+
"gitHead": "d7ad4d8a95a9ab73c9eee919e0cdfcc2334845af"
|
|
84
84
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node Type Registry Plugin
|
|
3
|
+
*
|
|
4
|
+
* Exports the gather-phase plugin and preset for generating @oneOf typed
|
|
5
|
+
* input types for blueprint definitions from node_type_registry.
|
|
6
|
+
*
|
|
7
|
+
* The NodeTypeRegistryPlugin queries node_type_registry through the existing
|
|
8
|
+
* pgService connection during the gather phase — no separate pool needed.
|
|
9
|
+
*/
|
|
10
|
+
export { NodeTypeRegistryPlugin, NodeTypeRegistryPreset, createBlueprintTypesPlugin, BlueprintTypesPreset, } from './plugin';
|
|
11
|
+
export type { NodeTypeRegistryEntry } from './plugin';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BlueprintTypesPreset = exports.createBlueprintTypesPlugin = exports.NodeTypeRegistryPreset = exports.NodeTypeRegistryPlugin = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Node Type Registry Plugin
|
|
6
|
+
*
|
|
7
|
+
* Exports the gather-phase plugin and preset for generating @oneOf typed
|
|
8
|
+
* input types for blueprint definitions from node_type_registry.
|
|
9
|
+
*
|
|
10
|
+
* The NodeTypeRegistryPlugin queries node_type_registry through the existing
|
|
11
|
+
* pgService connection during the gather phase — no separate pool needed.
|
|
12
|
+
*/
|
|
13
|
+
var plugin_1 = require("./plugin");
|
|
14
|
+
Object.defineProperty(exports, "NodeTypeRegistryPlugin", { enumerable: true, get: function () { return plugin_1.NodeTypeRegistryPlugin; } });
|
|
15
|
+
Object.defineProperty(exports, "NodeTypeRegistryPreset", { enumerable: true, get: function () { return plugin_1.NodeTypeRegistryPreset; } });
|
|
16
|
+
// Legacy exports for backward compatibility
|
|
17
|
+
Object.defineProperty(exports, "createBlueprintTypesPlugin", { enumerable: true, get: function () { return plugin_1.createBlueprintTypesPlugin; } });
|
|
18
|
+
Object.defineProperty(exports, "BlueprintTypesPreset", { enumerable: true, get: function () { return plugin_1.BlueprintTypesPreset; } });
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON Schema to GraphQL Input Field Spec Converter
|
|
3
|
+
*
|
|
4
|
+
* Converts JSON Schema objects (from node_type_registry.parameter_schema)
|
|
5
|
+
* into field spec objects compatible with PostGraphile v5's fieldWithHooks API.
|
|
6
|
+
*
|
|
7
|
+
* Handles:
|
|
8
|
+
* - string, integer, number, boolean primitives
|
|
9
|
+
* - arrays (items -> GraphQLList)
|
|
10
|
+
* - enums -> GraphQLEnumType
|
|
11
|
+
* - required fields -> GraphQLNonNull
|
|
12
|
+
* - union types (["integer", "string"]) -> JSON scalar fallback
|
|
13
|
+
*/
|
|
14
|
+
import type { GraphQLInputType, GraphQLScalarType, GraphQLNullableType } from 'graphql';
|
|
15
|
+
interface JsonSchema {
|
|
16
|
+
type: string;
|
|
17
|
+
properties?: Record<string, unknown>;
|
|
18
|
+
required?: string[];
|
|
19
|
+
anyOf?: JsonSchema[];
|
|
20
|
+
description?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* A field spec that mirrors GraphQLInputFieldConfig.
|
|
24
|
+
*/
|
|
25
|
+
export interface FieldSpec {
|
|
26
|
+
type: GraphQLInputType;
|
|
27
|
+
description?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Minimal build interface — only what we need from PostGraphile's Build object.
|
|
31
|
+
*
|
|
32
|
+
* Uses permissive constructor signatures to avoid variance issues between
|
|
33
|
+
* GraphQLNullableType and GraphQLInputType in the graphql-js type hierarchy.
|
|
34
|
+
*/
|
|
35
|
+
export interface BuildLike {
|
|
36
|
+
graphql: {
|
|
37
|
+
GraphQLString: GraphQLScalarType;
|
|
38
|
+
GraphQLInt: GraphQLScalarType;
|
|
39
|
+
GraphQLFloat: GraphQLScalarType;
|
|
40
|
+
GraphQLBoolean: GraphQLScalarType;
|
|
41
|
+
GraphQLNonNull: new (type: any) => GraphQLInputType;
|
|
42
|
+
GraphQLList: new (type: any) => GraphQLInputType & GraphQLNullableType;
|
|
43
|
+
GraphQLEnumType: new (config: {
|
|
44
|
+
name: string;
|
|
45
|
+
values: Record<string, {
|
|
46
|
+
value: string;
|
|
47
|
+
}>;
|
|
48
|
+
}) => GraphQLInputType & GraphQLNullableType;
|
|
49
|
+
};
|
|
50
|
+
getTypeByName: (name: string) => unknown;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Convert a full JSON Schema (from parameter_schema) to field specs.
|
|
54
|
+
*
|
|
55
|
+
* Uses the build object from PostGraphile v5 to access GraphQL types,
|
|
56
|
+
* avoiding direct graphql imports (which can cause version mismatches).
|
|
57
|
+
*
|
|
58
|
+
* @param schema - The JSON Schema object
|
|
59
|
+
* @param typeName - The name prefix for generated enum types
|
|
60
|
+
* @param build - The PostGraphile build object
|
|
61
|
+
* @returns Record of field name -> FieldSpec
|
|
62
|
+
*/
|
|
63
|
+
export declare function jsonSchemaToGraphQLFieldSpecs(schema: JsonSchema, typeName: string, build: BuildLike): Record<string, FieldSpec>;
|
|
64
|
+
export {};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* JSON Schema to GraphQL Input Field Spec Converter
|
|
4
|
+
*
|
|
5
|
+
* Converts JSON Schema objects (from node_type_registry.parameter_schema)
|
|
6
|
+
* into field spec objects compatible with PostGraphile v5's fieldWithHooks API.
|
|
7
|
+
*
|
|
8
|
+
* Handles:
|
|
9
|
+
* - string, integer, number, boolean primitives
|
|
10
|
+
* - arrays (items -> GraphQLList)
|
|
11
|
+
* - enums -> GraphQLEnumType
|
|
12
|
+
* - required fields -> GraphQLNonNull
|
|
13
|
+
* - union types (["integer", "string"]) -> JSON scalar fallback
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.jsonSchemaToGraphQLFieldSpecs = jsonSchemaToGraphQLFieldSpecs;
|
|
17
|
+
/**
|
|
18
|
+
* Convert a JSON Schema type string to a GraphQL scalar type.
|
|
19
|
+
*/
|
|
20
|
+
function jsonTypeToGraphQL(jsonType, graphql, jsonScalar) {
|
|
21
|
+
if (Array.isArray(jsonType)) {
|
|
22
|
+
return jsonScalar;
|
|
23
|
+
}
|
|
24
|
+
switch (jsonType) {
|
|
25
|
+
case 'string':
|
|
26
|
+
return graphql.GraphQLString;
|
|
27
|
+
case 'integer':
|
|
28
|
+
return graphql.GraphQLInt;
|
|
29
|
+
case 'number':
|
|
30
|
+
return graphql.GraphQLFloat;
|
|
31
|
+
case 'boolean':
|
|
32
|
+
return graphql.GraphQLBoolean;
|
|
33
|
+
case 'object':
|
|
34
|
+
return jsonScalar;
|
|
35
|
+
case 'array':
|
|
36
|
+
return new graphql.GraphQLList(jsonScalar);
|
|
37
|
+
default:
|
|
38
|
+
return jsonScalar;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Convert a full JSON Schema (from parameter_schema) to field specs.
|
|
43
|
+
*
|
|
44
|
+
* Uses the build object from PostGraphile v5 to access GraphQL types,
|
|
45
|
+
* avoiding direct graphql imports (which can cause version mismatches).
|
|
46
|
+
*
|
|
47
|
+
* @param schema - The JSON Schema object
|
|
48
|
+
* @param typeName - The name prefix for generated enum types
|
|
49
|
+
* @param build - The PostGraphile build object
|
|
50
|
+
* @returns Record of field name -> FieldSpec
|
|
51
|
+
*/
|
|
52
|
+
function jsonSchemaToGraphQLFieldSpecs(schema, typeName, build) {
|
|
53
|
+
const fields = {};
|
|
54
|
+
const properties = schema.properties;
|
|
55
|
+
if (!properties) {
|
|
56
|
+
return fields;
|
|
57
|
+
}
|
|
58
|
+
const { graphql } = build;
|
|
59
|
+
const jsonScalar = (build.getTypeByName('JSON') ?? graphql.GraphQLString);
|
|
60
|
+
const required = new Set(schema.required ?? []);
|
|
61
|
+
for (const [propName, prop] of Object.entries(properties)) {
|
|
62
|
+
let fieldType;
|
|
63
|
+
if (prop.enum && prop.enum.length > 0) {
|
|
64
|
+
const values = {};
|
|
65
|
+
for (const val of prop.enum) {
|
|
66
|
+
const safeName = val.replace(/[^_a-zA-Z0-9]/g, '_').toUpperCase();
|
|
67
|
+
values[safeName] = { value: val };
|
|
68
|
+
}
|
|
69
|
+
fieldType = new graphql.GraphQLEnumType({
|
|
70
|
+
name: `${typeName}_${propName}`,
|
|
71
|
+
values,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
else if (prop.type === 'array' && prop.items) {
|
|
75
|
+
const innerType = prop.items.type
|
|
76
|
+
? jsonTypeToGraphQL(prop.items.type, graphql, jsonScalar)
|
|
77
|
+
: jsonScalar;
|
|
78
|
+
fieldType = new graphql.GraphQLList(innerType);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
fieldType = jsonTypeToGraphQL(prop.type ?? 'string', graphql, jsonScalar);
|
|
82
|
+
}
|
|
83
|
+
if (required.has(propName)) {
|
|
84
|
+
fieldType = new graphql.GraphQLNonNull(fieldType);
|
|
85
|
+
}
|
|
86
|
+
fields[propName] = {
|
|
87
|
+
type: fieldType,
|
|
88
|
+
description: prop.description ?? undefined,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
return fields;
|
|
92
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node Type Registry Plugin
|
|
3
|
+
*
|
|
4
|
+
* Generic PostGraphile v5 plugin that queries node_type_registry at schema
|
|
5
|
+
* build time through the existing pgService connection (gather phase) and
|
|
6
|
+
* generates @oneOf typed input types with SuperCase node type names as
|
|
7
|
+
* discriminant keys.
|
|
8
|
+
*
|
|
9
|
+
* Architecture (correct PostGraphile v5 pattern):
|
|
10
|
+
* 1. `gather` phase: hooks into pgIntrospection_introspection to query
|
|
11
|
+
* node_type_registry through the existing pgService (no separate pool —
|
|
12
|
+
* uses withPgClientFromPgService like PgIntrospectionPlugin)
|
|
13
|
+
* 2. `init` hook: registers all input type shells from gathered entries
|
|
14
|
+
* 3. `GraphQLInputObjectType_fields` hook: populates fields on those types
|
|
15
|
+
*
|
|
16
|
+
* Generated type hierarchy:
|
|
17
|
+
*
|
|
18
|
+
* BlueprintDefinitionInput
|
|
19
|
+
* +-- tables: [BlueprintTableInput!]
|
|
20
|
+
* | +-- ref: String!
|
|
21
|
+
* | +-- tableName: String!
|
|
22
|
+
* | +-- nodes: [BlueprintNodeInput!]! <-- @oneOf
|
|
23
|
+
* | +-- shorthand: String
|
|
24
|
+
* | +-- DataId: DataIdParams
|
|
25
|
+
* | +-- DataTimestamps: DataTimestampsParams
|
|
26
|
+
* | +-- AuthzEntityMembership: AuthzEntityMembershipParams
|
|
27
|
+
* | +-- ...
|
|
28
|
+
* +-- relations: [BlueprintRelationInput!] <-- @oneOf
|
|
29
|
+
* +-- RelationBelongsTo: RelationBelongsToParams
|
|
30
|
+
* +-- ...
|
|
31
|
+
*
|
|
32
|
+
* When codegen runs, @oneOf types become discriminated union types:
|
|
33
|
+
*
|
|
34
|
+
* type BlueprintNodeInput =
|
|
35
|
+
* | { DataId: DataIdParams }
|
|
36
|
+
* | { DataTimestamps: DataTimestampsParams }
|
|
37
|
+
* | { AuthzEntityMembership: AuthzEntityMembershipParams }
|
|
38
|
+
*/
|
|
39
|
+
import type { GraphileConfig } from 'graphile-config';
|
|
40
|
+
export interface NodeTypeRegistryEntry {
|
|
41
|
+
name: string;
|
|
42
|
+
slug: string;
|
|
43
|
+
category: string;
|
|
44
|
+
display_name: string;
|
|
45
|
+
description: string;
|
|
46
|
+
parameter_schema: Record<string, unknown>;
|
|
47
|
+
tags: string[];
|
|
48
|
+
}
|
|
49
|
+
declare global {
|
|
50
|
+
namespace GraphileBuild {
|
|
51
|
+
interface ScopeInputObject {
|
|
52
|
+
isBlueprintOneOf?: boolean;
|
|
53
|
+
isBlueprintNodeParams?: boolean;
|
|
54
|
+
isBlueprintTable?: boolean;
|
|
55
|
+
isBlueprintDefinition?: boolean;
|
|
56
|
+
isBlueprintRelation?: boolean;
|
|
57
|
+
blueprintNodeTypeName?: string;
|
|
58
|
+
}
|
|
59
|
+
interface BuildInput {
|
|
60
|
+
nodeTypeRegistryEntries?: NodeTypeRegistryEntry[];
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* NodeTypeRegistryPlugin
|
|
66
|
+
*
|
|
67
|
+
* Gather-phase plugin that queries node_type_registry through the existing
|
|
68
|
+
* pgService connection, then generates @oneOf typed input types in the
|
|
69
|
+
* schema phase. No separate PG pool -- uses the same connection PostGraphile
|
|
70
|
+
* already has (same pattern as PgIntrospectionPlugin / PgEnumTablesPlugin).
|
|
71
|
+
*
|
|
72
|
+
* If the table doesn't exist (42P01) or the query fails, the plugin
|
|
73
|
+
* gracefully skips and no types are registered.
|
|
74
|
+
*/
|
|
75
|
+
export declare const NodeTypeRegistryPlugin: GraphileConfig.Plugin;
|
|
76
|
+
/**
|
|
77
|
+
* Preset that includes the NodeTypeRegistryPlugin.
|
|
78
|
+
*
|
|
79
|
+
* Add to ConstructivePreset.extends[] -- the plugin will automatically
|
|
80
|
+
* query node_type_registry through the existing pgService connection
|
|
81
|
+
* during the gather phase. No separate pool, no manual wiring.
|
|
82
|
+
*/
|
|
83
|
+
export declare const NodeTypeRegistryPreset: GraphileConfig.Preset;
|
|
84
|
+
/**
|
|
85
|
+
* @deprecated Use NodeTypeRegistryPlugin directly. The gather-phase plugin
|
|
86
|
+
* queries node_type_registry automatically via pgService. This factory is
|
|
87
|
+
* kept for backward compatibility and tests that pass static entries.
|
|
88
|
+
*/
|
|
89
|
+
export declare function createBlueprintTypesPlugin(nodeTypes: NodeTypeRegistryEntry[]): GraphileConfig.Plugin;
|
|
90
|
+
/**
|
|
91
|
+
* @deprecated Use NodeTypeRegistryPreset directly. The gather-phase plugin
|
|
92
|
+
* queries node_type_registry automatically via pgService. This factory is
|
|
93
|
+
* kept for backward compatibility and tests that pass static entries.
|
|
94
|
+
*/
|
|
95
|
+
export declare function BlueprintTypesPreset(nodeTypes: NodeTypeRegistryEntry[]): GraphileConfig.Preset;
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NodeTypeRegistryPreset = exports.NodeTypeRegistryPlugin = void 0;
|
|
4
|
+
exports.createBlueprintTypesPlugin = createBlueprintTypesPlugin;
|
|
5
|
+
exports.BlueprintTypesPreset = BlueprintTypesPreset;
|
|
6
|
+
const graphile_build_pg_1 = require("graphile-build-pg");
|
|
7
|
+
const json_schema_to_graphql_1 = require("./json-schema-to-graphql");
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// Constants
|
|
10
|
+
// ============================================================================
|
|
11
|
+
const BLUEPRINT_NODE_INPUT = 'BlueprintNodeInput';
|
|
12
|
+
const BLUEPRINT_TABLE_INPUT = 'BlueprintTableInput';
|
|
13
|
+
const BLUEPRINT_RELATION_INPUT = 'BlueprintRelationInput';
|
|
14
|
+
const BLUEPRINT_DEFINITION_INPUT = 'BlueprintDefinitionInput';
|
|
15
|
+
const NODE_TYPE_REGISTRY_QUERY = `
|
|
16
|
+
SELECT
|
|
17
|
+
name,
|
|
18
|
+
slug,
|
|
19
|
+
category,
|
|
20
|
+
COALESCE(display_name, name) as display_name,
|
|
21
|
+
COALESCE(description, '') as description,
|
|
22
|
+
COALESCE(parameter_schema, '{}'::jsonb) as parameter_schema,
|
|
23
|
+
COALESCE(tags, '{}') as tags
|
|
24
|
+
FROM metaschema_public.node_type_registry
|
|
25
|
+
ORDER BY category, name
|
|
26
|
+
`;
|
|
27
|
+
// ============================================================================
|
|
28
|
+
// Shared schema hooks
|
|
29
|
+
// ============================================================================
|
|
30
|
+
/**
|
|
31
|
+
* Builds the schema hooks (init + GraphQLInputObjectType_fields) from
|
|
32
|
+
* a function that retrieves node type entries. Used by both the gather-phase
|
|
33
|
+
* plugin (reads from build.input) and the static/legacy plugin (closure).
|
|
34
|
+
*/
|
|
35
|
+
function buildSchemaHooks(getNodeTypes) {
|
|
36
|
+
return {
|
|
37
|
+
hooks: {
|
|
38
|
+
init(_, build) {
|
|
39
|
+
const nodeTypes = getNodeTypes(build);
|
|
40
|
+
if (nodeTypes.length === 0)
|
|
41
|
+
return _;
|
|
42
|
+
for (const nt of nodeTypes) {
|
|
43
|
+
const paramsTypeName = nt.name + 'Params';
|
|
44
|
+
build.registerInputObjectType(paramsTypeName, {
|
|
45
|
+
isBlueprintNodeParams: true,
|
|
46
|
+
blueprintNodeTypeName: nt.name,
|
|
47
|
+
}, () => ({
|
|
48
|
+
description: nt.description,
|
|
49
|
+
}), 'NodeTypeRegistryPlugin: ' + paramsTypeName);
|
|
50
|
+
}
|
|
51
|
+
build.registerInputObjectType(BLUEPRINT_NODE_INPUT, { isBlueprintOneOf: true }, () => ({
|
|
52
|
+
description: 'A single node in a blueprint definition. Exactly one field must be set. Use the SuperCase node type name as the key with its parameters as the value, or use "shorthand" with just the node type name string.',
|
|
53
|
+
isOneOf: true,
|
|
54
|
+
}), 'NodeTypeRegistryPlugin: BlueprintNodeInput');
|
|
55
|
+
build.registerInputObjectType(BLUEPRINT_RELATION_INPUT, { isBlueprintOneOf: true, isBlueprintRelation: true }, () => ({
|
|
56
|
+
description: 'A relation in a blueprint definition. Exactly one field must be set. Use the SuperCase relation type name as the key.',
|
|
57
|
+
isOneOf: true,
|
|
58
|
+
}), 'NodeTypeRegistryPlugin: BlueprintRelationInput');
|
|
59
|
+
build.registerInputObjectType(BLUEPRINT_TABLE_INPUT, { isBlueprintTable: true }, () => ({
|
|
60
|
+
description: 'A table definition within a blueprint. Specifies the table reference, name, and an array of typed nodes.',
|
|
61
|
+
}), 'NodeTypeRegistryPlugin: BlueprintTableInput');
|
|
62
|
+
build.registerInputObjectType(BLUEPRINT_DEFINITION_INPUT, { isBlueprintDefinition: true }, () => ({
|
|
63
|
+
description: 'The complete blueprint definition. Contains tables with typed nodes and relations.',
|
|
64
|
+
}), 'NodeTypeRegistryPlugin: BlueprintDefinitionInput');
|
|
65
|
+
return _;
|
|
66
|
+
},
|
|
67
|
+
GraphQLInputObjectType_fields(fields, build, context) {
|
|
68
|
+
const nodeTypes = getNodeTypes(build);
|
|
69
|
+
if (nodeTypes.length === 0)
|
|
70
|
+
return fields;
|
|
71
|
+
const nodeTypesByName = new Map();
|
|
72
|
+
const relationNodeTypes = [];
|
|
73
|
+
const nonRelationNodeTypes = [];
|
|
74
|
+
for (const nt of nodeTypes) {
|
|
75
|
+
nodeTypesByName.set(nt.name, nt);
|
|
76
|
+
if (nt.category === 'relation') {
|
|
77
|
+
relationNodeTypes.push(nt);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
nonRelationNodeTypes.push(nt);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const { extend, graphql: { GraphQLString, GraphQLNonNull, GraphQLList }, } = build;
|
|
84
|
+
const { fieldWithHooks, scope: { isBlueprintOneOf, isBlueprintNodeParams, isBlueprintTable, isBlueprintDefinition, isBlueprintRelation, blueprintNodeTypeName, }, } = context;
|
|
85
|
+
// --- Per-node-type params (e.g., DataIdParams) ---
|
|
86
|
+
if (isBlueprintNodeParams && blueprintNodeTypeName) {
|
|
87
|
+
const nt = nodeTypesByName.get(blueprintNodeTypeName);
|
|
88
|
+
if (!nt)
|
|
89
|
+
return fields;
|
|
90
|
+
const schema = nt.parameter_schema;
|
|
91
|
+
const fieldSpecs = (0, json_schema_to_graphql_1.jsonSchemaToGraphQLFieldSpecs)(schema, nt.name + 'Params', build);
|
|
92
|
+
if (Object.keys(fieldSpecs).length > 0) {
|
|
93
|
+
let result = fields;
|
|
94
|
+
for (const [fieldName, spec] of Object.entries(fieldSpecs)) {
|
|
95
|
+
result = extend(result, {
|
|
96
|
+
[fieldName]: fieldWithHooks({ fieldName }, () => spec),
|
|
97
|
+
}, 'NodeTypeRegistryPlugin: ' + blueprintNodeTypeName + '.' + fieldName);
|
|
98
|
+
}
|
|
99
|
+
return result;
|
|
100
|
+
}
|
|
101
|
+
return extend(fields, {
|
|
102
|
+
_: fieldWithHooks({ fieldName: '_' }, () => ({
|
|
103
|
+
type: build.graphql.GraphQLBoolean,
|
|
104
|
+
description: 'No parameters required. Pass true or omit entirely.',
|
|
105
|
+
})),
|
|
106
|
+
}, 'NodeTypeRegistryPlugin: ' + blueprintNodeTypeName + '._');
|
|
107
|
+
}
|
|
108
|
+
// --- BlueprintNodeInput (@oneOf with all non-relation node types) ---
|
|
109
|
+
if (isBlueprintOneOf && !isBlueprintRelation) {
|
|
110
|
+
let result = fields;
|
|
111
|
+
result = extend(result, {
|
|
112
|
+
shorthand: fieldWithHooks({ fieldName: 'shorthand' }, () => ({
|
|
113
|
+
type: GraphQLString,
|
|
114
|
+
description: 'String shorthand: just the SuperCase node type name (e.g., "DataTimestamps"). Use when the node type has no required parameters.',
|
|
115
|
+
})),
|
|
116
|
+
}, 'NodeTypeRegistryPlugin: BlueprintNodeInput.shorthand');
|
|
117
|
+
for (const nt of nonRelationNodeTypes) {
|
|
118
|
+
const paramsTypeName = nt.name + 'Params';
|
|
119
|
+
const ParamsType = build.getTypeByName(paramsTypeName);
|
|
120
|
+
if (!ParamsType)
|
|
121
|
+
continue;
|
|
122
|
+
result = extend(result, {
|
|
123
|
+
[nt.name]: fieldWithHooks({ fieldName: nt.name }, () => ({
|
|
124
|
+
type: ParamsType,
|
|
125
|
+
description: 'Parameters for ' + nt.name + ' (' + nt.display_name + ')',
|
|
126
|
+
})),
|
|
127
|
+
}, 'NodeTypeRegistryPlugin: BlueprintNodeInput.' + nt.name);
|
|
128
|
+
}
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
131
|
+
// --- BlueprintRelationInput (@oneOf with relation node types) ---
|
|
132
|
+
if (isBlueprintOneOf && isBlueprintRelation) {
|
|
133
|
+
let result = fields;
|
|
134
|
+
for (const nt of relationNodeTypes) {
|
|
135
|
+
const paramsTypeName = nt.name + 'Params';
|
|
136
|
+
const ParamsType = build.getTypeByName(paramsTypeName);
|
|
137
|
+
if (!ParamsType)
|
|
138
|
+
continue;
|
|
139
|
+
result = extend(result, {
|
|
140
|
+
[nt.name]: fieldWithHooks({ fieldName: nt.name }, () => ({
|
|
141
|
+
type: ParamsType,
|
|
142
|
+
description: 'Parameters for ' + nt.name + ' (' + nt.display_name + ')',
|
|
143
|
+
})),
|
|
144
|
+
}, 'NodeTypeRegistryPlugin: BlueprintRelationInput.' + nt.name);
|
|
145
|
+
}
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
// --- BlueprintTableInput ---
|
|
149
|
+
if (isBlueprintTable) {
|
|
150
|
+
const BlueprintNodeInputType = build.getTypeByName(BLUEPRINT_NODE_INPUT);
|
|
151
|
+
return extend(fields, {
|
|
152
|
+
ref: fieldWithHooks({ fieldName: 'ref' }, () => ({
|
|
153
|
+
type: new GraphQLNonNull(GraphQLString),
|
|
154
|
+
description: 'Unique reference key for this table within the blueprint (used for cross-references in relations).',
|
|
155
|
+
})),
|
|
156
|
+
tableName: fieldWithHooks({ fieldName: 'tableName' }, () => ({
|
|
157
|
+
type: new GraphQLNonNull(GraphQLString),
|
|
158
|
+
description: 'The PostgreSQL table name to create.',
|
|
159
|
+
})),
|
|
160
|
+
nodes: fieldWithHooks({ fieldName: 'nodes' }, () => ({
|
|
161
|
+
type: BlueprintNodeInputType
|
|
162
|
+
? new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(BlueprintNodeInputType)))
|
|
163
|
+
: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(GraphQLString))),
|
|
164
|
+
description: 'Array of node type entries (data, authz, field behaviors) to apply to this table.',
|
|
165
|
+
})),
|
|
166
|
+
}, 'NodeTypeRegistryPlugin: BlueprintTableInput fields');
|
|
167
|
+
}
|
|
168
|
+
// --- BlueprintDefinitionInput ---
|
|
169
|
+
if (isBlueprintDefinition) {
|
|
170
|
+
const BlueprintTableInputType = build.getTypeByName(BLUEPRINT_TABLE_INPUT);
|
|
171
|
+
const BlueprintRelationInputType = build.getTypeByName(BLUEPRINT_RELATION_INPUT);
|
|
172
|
+
return extend(fields, {
|
|
173
|
+
tables: fieldWithHooks({ fieldName: 'tables' }, () => ({
|
|
174
|
+
type: BlueprintTableInputType
|
|
175
|
+
? new GraphQLList(new GraphQLNonNull(BlueprintTableInputType))
|
|
176
|
+
: new GraphQLList(GraphQLString),
|
|
177
|
+
description: 'Array of table definitions.',
|
|
178
|
+
})),
|
|
179
|
+
relations: fieldWithHooks({ fieldName: 'relations' }, () => ({
|
|
180
|
+
type: BlueprintRelationInputType
|
|
181
|
+
? new GraphQLList(new GraphQLNonNull(BlueprintRelationInputType))
|
|
182
|
+
: new GraphQLList(GraphQLString),
|
|
183
|
+
description: 'Array of relation definitions.',
|
|
184
|
+
})),
|
|
185
|
+
}, 'NodeTypeRegistryPlugin: BlueprintDefinitionInput fields');
|
|
186
|
+
}
|
|
187
|
+
return fields;
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
// ============================================================================
|
|
193
|
+
// Gather-phase plugin (production -- queries DB through existing pgService)
|
|
194
|
+
// ============================================================================
|
|
195
|
+
/**
|
|
196
|
+
* NodeTypeRegistryPlugin
|
|
197
|
+
*
|
|
198
|
+
* Gather-phase plugin that queries node_type_registry through the existing
|
|
199
|
+
* pgService connection, then generates @oneOf typed input types in the
|
|
200
|
+
* schema phase. No separate PG pool -- uses the same connection PostGraphile
|
|
201
|
+
* already has (same pattern as PgIntrospectionPlugin / PgEnumTablesPlugin).
|
|
202
|
+
*
|
|
203
|
+
* If the table doesn't exist (42P01) or the query fails, the plugin
|
|
204
|
+
* gracefully skips and no types are registered.
|
|
205
|
+
*/
|
|
206
|
+
exports.NodeTypeRegistryPlugin = {
|
|
207
|
+
name: 'NodeTypeRegistryPlugin',
|
|
208
|
+
version: '1.0.0',
|
|
209
|
+
description: 'Queries node_type_registry via the existing pgService and generates @oneOf typed input types with SuperCase keys',
|
|
210
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
211
|
+
gather: {
|
|
212
|
+
namespace: 'nodeTypeRegistry',
|
|
213
|
+
version: 1,
|
|
214
|
+
initialState() {
|
|
215
|
+
return { entries: [] };
|
|
216
|
+
},
|
|
217
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
218
|
+
async finalize(info) {
|
|
219
|
+
info.buildInput.nodeTypeRegistryEntries =
|
|
220
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
221
|
+
info.state.entries ?? [];
|
|
222
|
+
},
|
|
223
|
+
hooks: {
|
|
224
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
225
|
+
async pgIntrospection_introspection(info, event) {
|
|
226
|
+
const { serviceName } = event;
|
|
227
|
+
const pgService = info.resolvedPreset.pgServices?.find((svc) => svc.name === serviceName);
|
|
228
|
+
if (!pgService)
|
|
229
|
+
return;
|
|
230
|
+
try {
|
|
231
|
+
const result = await (0, graphile_build_pg_1.withPgClientFromPgService)(pgService, null, (client) => client.query({ text: NODE_TYPE_REGISTRY_QUERY }));
|
|
232
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
233
|
+
const state = info.state;
|
|
234
|
+
if (!state.entries) {
|
|
235
|
+
state.entries = [];
|
|
236
|
+
}
|
|
237
|
+
state.entries.push(...result.rows);
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
const pgError = error;
|
|
241
|
+
if (pgError.code === '42P01') {
|
|
242
|
+
// 42P01 = undefined_table -- expected when DB hasn't been migrated
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
// Warn but don't fail the schema build
|
|
246
|
+
console.warn('[NodeTypeRegistryPlugin] Failed to query node_type_registry:', pgError.message ?? String(error));
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
251
|
+
},
|
|
252
|
+
schema: buildSchemaHooks((build) => build.input.nodeTypeRegistryEntries ?? []),
|
|
253
|
+
};
|
|
254
|
+
// ============================================================================
|
|
255
|
+
// Preset
|
|
256
|
+
// ============================================================================
|
|
257
|
+
/**
|
|
258
|
+
* Preset that includes the NodeTypeRegistryPlugin.
|
|
259
|
+
*
|
|
260
|
+
* Add to ConstructivePreset.extends[] -- the plugin will automatically
|
|
261
|
+
* query node_type_registry through the existing pgService connection
|
|
262
|
+
* during the gather phase. No separate pool, no manual wiring.
|
|
263
|
+
*/
|
|
264
|
+
exports.NodeTypeRegistryPreset = {
|
|
265
|
+
plugins: [exports.NodeTypeRegistryPlugin],
|
|
266
|
+
};
|
|
267
|
+
// ============================================================================
|
|
268
|
+
// Legacy exports for backward compatibility
|
|
269
|
+
// ============================================================================
|
|
270
|
+
/**
|
|
271
|
+
* @deprecated Use NodeTypeRegistryPlugin directly. The gather-phase plugin
|
|
272
|
+
* queries node_type_registry automatically via pgService. This factory is
|
|
273
|
+
* kept for backward compatibility and tests that pass static entries.
|
|
274
|
+
*/
|
|
275
|
+
function createBlueprintTypesPlugin(nodeTypes) {
|
|
276
|
+
return {
|
|
277
|
+
name: 'BlueprintTypesPlugin',
|
|
278
|
+
version: '1.0.0',
|
|
279
|
+
description: 'Generates @oneOf typed input types from static node_type_registry entries',
|
|
280
|
+
schema: buildSchemaHooks(() => nodeTypes),
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* @deprecated Use NodeTypeRegistryPreset directly. The gather-phase plugin
|
|
285
|
+
* queries node_type_registry automatically via pgService. This factory is
|
|
286
|
+
* kept for backward compatibility and tests that pass static entries.
|
|
287
|
+
*/
|
|
288
|
+
function BlueprintTypesPreset(nodeTypes) {
|
|
289
|
+
return {
|
|
290
|
+
plugins: [createBlueprintTypesPlugin(nodeTypes)],
|
|
291
|
+
};
|
|
292
|
+
}
|
package/plugins/index.d.ts
CHANGED
|
@@ -20,3 +20,5 @@ export { _pgTypeToGqlType, _buildFieldMeta, _cachedTablesMeta } from './meta-sch
|
|
|
20
20
|
export { RequiredInputPlugin, RequiredInputPreset, } from './required-input-plugin';
|
|
21
21
|
export { createUnifiedSearchPlugin, UnifiedSearchPreset, TsvectorCodecPlugin, TsvectorCodecPreset, createTsvectorCodecPlugin, Bm25CodecPlugin, Bm25CodecPreset, bm25IndexStore, VectorCodecPlugin, VectorCodecPreset, createTsvectorAdapter, createBm25Adapter, createTrgmAdapter, createPgvectorAdapter, createMatchesOperatorFactory, createTrgmOperatorFactories, } from 'graphile-search';
|
|
22
22
|
export type { SearchAdapter, SearchableColumn, UnifiedSearchOptions, UnifiedSearchPresetOptions, TsvectorCodecPluginOptions, Bm25IndexInfo, TsvectorAdapterOptions, Bm25AdapterOptions, TrgmAdapterOptions, PgvectorAdapterOptions, } from 'graphile-search';
|
|
23
|
+
export { NodeTypeRegistryPlugin, NodeTypeRegistryPreset, createBlueprintTypesPlugin, BlueprintTypesPreset, } from './blueprint-types';
|
|
24
|
+
export type { NodeTypeRegistryEntry } from './blueprint-types';
|
package/plugins/index.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* This module exports all custom plugins (consolidated from graphile-misc-plugins).
|
|
6
6
|
*/
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
-
exports.createTrgmOperatorFactories = exports.createMatchesOperatorFactory = exports.createPgvectorAdapter = exports.createTrgmAdapter = exports.createBm25Adapter = exports.createTsvectorAdapter = exports.VectorCodecPreset = exports.VectorCodecPlugin = exports.bm25IndexStore = exports.Bm25CodecPreset = exports.Bm25CodecPlugin = exports.createTsvectorCodecPlugin = exports.TsvectorCodecPreset = exports.TsvectorCodecPlugin = exports.UnifiedSearchPreset = exports.createUnifiedSearchPlugin = exports.RequiredInputPreset = exports.RequiredInputPlugin = exports._cachedTablesMeta = exports._buildFieldMeta = exports._pgTypeToGqlType = exports.PublicKeySignature = exports.PgTypeMappingsPreset = exports.PgTypeMappingsPlugin = exports.MetaSchemaPreset = exports.MetaSchemaPlugin = exports.NoUniqueLookupPreset = exports.PrimaryKeyOnlyPreset = exports.NoUniqueLookupPlugin = exports.PrimaryKeyOnlyPlugin = exports.createUniqueLookupPlugin = exports.ManyToManyOptInPreset = exports.ManyToManyOptInPlugin = exports.EnableAllFilterColumnsPreset = exports.EnableAllFilterColumnsPlugin = exports.InflectorLoggerPreset = exports.InflectorLoggerPlugin = exports.ConflictDetectorPreset = exports.ConflictDetectorPlugin = exports.CustomInflectorPreset = exports.CustomInflectorPlugin = exports.InflektPreset = exports.InflektPlugin = exports.MinimalPreset = void 0;
|
|
8
|
+
exports.BlueprintTypesPreset = exports.createBlueprintTypesPlugin = exports.NodeTypeRegistryPreset = exports.NodeTypeRegistryPlugin = exports.createTrgmOperatorFactories = exports.createMatchesOperatorFactory = exports.createPgvectorAdapter = exports.createTrgmAdapter = exports.createBm25Adapter = exports.createTsvectorAdapter = exports.VectorCodecPreset = exports.VectorCodecPlugin = exports.bm25IndexStore = exports.Bm25CodecPreset = exports.Bm25CodecPlugin = exports.createTsvectorCodecPlugin = exports.TsvectorCodecPreset = exports.TsvectorCodecPlugin = exports.UnifiedSearchPreset = exports.createUnifiedSearchPlugin = exports.RequiredInputPreset = exports.RequiredInputPlugin = exports._cachedTablesMeta = exports._buildFieldMeta = exports._pgTypeToGqlType = exports.PublicKeySignature = exports.PgTypeMappingsPreset = exports.PgTypeMappingsPlugin = exports.MetaSchemaPreset = exports.MetaSchemaPlugin = exports.NoUniqueLookupPreset = exports.PrimaryKeyOnlyPreset = exports.NoUniqueLookupPlugin = exports.PrimaryKeyOnlyPlugin = exports.createUniqueLookupPlugin = exports.ManyToManyOptInPreset = exports.ManyToManyOptInPlugin = exports.EnableAllFilterColumnsPreset = exports.EnableAllFilterColumnsPlugin = exports.InflectorLoggerPreset = exports.InflectorLoggerPlugin = exports.ConflictDetectorPreset = exports.ConflictDetectorPlugin = exports.CustomInflectorPreset = exports.CustomInflectorPlugin = exports.InflektPreset = exports.InflektPlugin = exports.MinimalPreset = void 0;
|
|
9
9
|
// Minimal preset - PostGraphile without Node/Relay features
|
|
10
10
|
var minimal_preset_1 = require("./minimal-preset");
|
|
11
11
|
Object.defineProperty(exports, "MinimalPreset", { enumerable: true, get: function () { return minimal_preset_1.MinimalPreset; } });
|
|
@@ -80,3 +80,11 @@ Object.defineProperty(exports, "createPgvectorAdapter", { enumerable: true, get:
|
|
|
80
80
|
// Operator factories for connection filter integration
|
|
81
81
|
Object.defineProperty(exports, "createMatchesOperatorFactory", { enumerable: true, get: function () { return graphile_search_1.createMatchesOperatorFactory; } });
|
|
82
82
|
Object.defineProperty(exports, "createTrgmOperatorFactories", { enumerable: true, get: function () { return graphile_search_1.createTrgmOperatorFactories; } });
|
|
83
|
+
// Node type registry — @oneOf typed input types for blueprint definitions
|
|
84
|
+
// Gather-phase plugin queries node_type_registry through existing pgService
|
|
85
|
+
var blueprint_types_1 = require("./blueprint-types");
|
|
86
|
+
Object.defineProperty(exports, "NodeTypeRegistryPlugin", { enumerable: true, get: function () { return blueprint_types_1.NodeTypeRegistryPlugin; } });
|
|
87
|
+
Object.defineProperty(exports, "NodeTypeRegistryPreset", { enumerable: true, get: function () { return blueprint_types_1.NodeTypeRegistryPreset; } });
|
|
88
|
+
// Legacy exports for backward compatibility
|
|
89
|
+
Object.defineProperty(exports, "createBlueprintTypesPlugin", { enumerable: true, get: function () { return blueprint_types_1.createBlueprintTypesPlugin; } });
|
|
90
|
+
Object.defineProperty(exports, "BlueprintTypesPreset", { enumerable: true, get: function () { return blueprint_types_1.BlueprintTypesPreset; } });
|
|
@@ -80,6 +80,7 @@ exports.ConstructivePreset = {
|
|
|
80
80
|
(0, graphile_sql_expression_validator_1.SqlExpressionValidatorPreset)(),
|
|
81
81
|
plugins_1.PgTypeMappingsPreset,
|
|
82
82
|
plugins_1.RequiredInputPreset,
|
|
83
|
+
plugins_1.NodeTypeRegistryPreset,
|
|
83
84
|
],
|
|
84
85
|
/**
|
|
85
86
|
* Disable PostGraphile core's condition argument entirely.
|