@constructive-io/graphql-query 2.8.0 → 3.0.1
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/ast.d.ts +1 -1
- package/ast.js +6 -5
- package/custom-ast.js +9 -8
- package/esm/ast.js +6 -5
- package/esm/custom-ast.js +9 -8
- package/esm/executor.js +176 -0
- package/esm/index.js +1 -0
- package/executor.d.ts +88 -0
- package/executor.js +181 -0
- package/index.d.ts +1 -0
- package/index.js +4 -1
- package/package.json +12 -6
- package/types.d.ts +24 -0
package/ast.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type DocumentNode, type FieldNode } from 'graphql';
|
|
2
2
|
import type { ASTFunctionParams, FieldSelection, MutationASTParams } from './types';
|
|
3
3
|
export declare const getAll: ({ queryName, operationName, query: _query, selection, }: ASTFunctionParams) => DocumentNode;
|
|
4
4
|
export declare const getCount: ({ queryName, operationName, query, }: Omit<ASTFunctionParams, "selection">) => DocumentNode;
|
package/ast.js
CHANGED
|
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.deleteOne = exports.patchOne = exports.createOne = exports.getOne = exports.getMany = exports.getCount = exports.getAll = void 0;
|
|
37
37
|
exports.getSelections = getSelections;
|
|
38
38
|
const t = __importStar(require("gql-ast"));
|
|
39
|
+
const graphql_1 = require("graphql");
|
|
39
40
|
const inflection_1 = require("inflection");
|
|
40
41
|
const custom_ast_1 = require("./custom-ast");
|
|
41
42
|
const NON_MUTABLE_PROPS = ['createdAt', 'createdBy', 'updatedAt', 'updatedBy'];
|
|
@@ -76,7 +77,7 @@ const createGqlMutation = ({ operationName, mutationName, selectArgs, selections
|
|
|
76
77
|
return t.document({
|
|
77
78
|
definitions: [
|
|
78
79
|
t.operationDefinition({
|
|
79
|
-
operation:
|
|
80
|
+
operation: graphql_1.OperationTypeNode.MUTATION,
|
|
80
81
|
name: mutationName,
|
|
81
82
|
variableDefinitions,
|
|
82
83
|
selectionSet: t.selectionSet({ selections: opSel }),
|
|
@@ -105,7 +106,7 @@ const getAll = ({ queryName, operationName, query: _query, selection, }) => {
|
|
|
105
106
|
const ast = t.document({
|
|
106
107
|
definitions: [
|
|
107
108
|
t.operationDefinition({
|
|
108
|
-
operation:
|
|
109
|
+
operation: graphql_1.OperationTypeNode.QUERY,
|
|
109
110
|
name: queryName,
|
|
110
111
|
selectionSet: t.selectionSet({ selections: opSel }),
|
|
111
112
|
}),
|
|
@@ -149,7 +150,7 @@ const getCount = ({ queryName, operationName, query, }) => {
|
|
|
149
150
|
const ast = t.document({
|
|
150
151
|
definitions: [
|
|
151
152
|
t.operationDefinition({
|
|
152
|
-
operation:
|
|
153
|
+
operation: graphql_1.OperationTypeNode.QUERY,
|
|
153
154
|
name: queryName,
|
|
154
155
|
variableDefinitions,
|
|
155
156
|
selectionSet: t.selectionSet({ selections: opSel }),
|
|
@@ -246,7 +247,7 @@ const getMany = ({ builder, queryName, operationName, query, selection, }) => {
|
|
|
246
247
|
const ast = t.document({
|
|
247
248
|
definitions: [
|
|
248
249
|
t.operationDefinition({
|
|
249
|
-
operation:
|
|
250
|
+
operation: graphql_1.OperationTypeNode.QUERY,
|
|
250
251
|
name: queryName,
|
|
251
252
|
variableDefinitions,
|
|
252
253
|
selectionSet: t.selectionSet({
|
|
@@ -303,7 +304,7 @@ const getOne = ({ queryName, operationName, query, selection, }) => {
|
|
|
303
304
|
const ast = t.document({
|
|
304
305
|
definitions: [
|
|
305
306
|
t.operationDefinition({
|
|
306
|
-
operation:
|
|
307
|
+
operation: graphql_1.OperationTypeNode.QUERY,
|
|
307
308
|
name: queryName,
|
|
308
309
|
variableDefinitions,
|
|
309
310
|
selectionSet: t.selectionSet({ selections: opSel }),
|
package/custom-ast.js
CHANGED
|
@@ -42,6 +42,7 @@ exports.geometryAst = geometryAst;
|
|
|
42
42
|
exports.intervalAst = intervalAst;
|
|
43
43
|
exports.isIntervalType = isIntervalType;
|
|
44
44
|
const t = __importStar(require("gql-ast"));
|
|
45
|
+
const graphql_1 = require("graphql");
|
|
45
46
|
function getCustomAst(fieldDefn) {
|
|
46
47
|
if (!fieldDefn) {
|
|
47
48
|
return null;
|
|
@@ -116,28 +117,28 @@ function geometryPointAst(name) {
|
|
|
116
117
|
function geometryCollectionAst(name) {
|
|
117
118
|
// Manually create inline fragment since gql-ast doesn't support it
|
|
118
119
|
const inlineFragment = {
|
|
119
|
-
kind:
|
|
120
|
+
kind: graphql_1.Kind.INLINE_FRAGMENT,
|
|
120
121
|
typeCondition: {
|
|
121
|
-
kind:
|
|
122
|
+
kind: graphql_1.Kind.NAMED_TYPE,
|
|
122
123
|
name: {
|
|
123
|
-
kind:
|
|
124
|
+
kind: graphql_1.Kind.NAME,
|
|
124
125
|
value: 'GeometryPoint',
|
|
125
126
|
},
|
|
126
127
|
},
|
|
127
128
|
selectionSet: {
|
|
128
|
-
kind:
|
|
129
|
+
kind: graphql_1.Kind.SELECTION_SET,
|
|
129
130
|
selections: [
|
|
130
131
|
{
|
|
131
|
-
kind:
|
|
132
|
+
kind: graphql_1.Kind.FIELD,
|
|
132
133
|
name: {
|
|
133
|
-
kind:
|
|
134
|
+
kind: graphql_1.Kind.NAME,
|
|
134
135
|
value: 'x',
|
|
135
136
|
},
|
|
136
137
|
},
|
|
137
138
|
{
|
|
138
|
-
kind:
|
|
139
|
+
kind: graphql_1.Kind.FIELD,
|
|
139
140
|
name: {
|
|
140
|
-
kind:
|
|
141
|
+
kind: graphql_1.Kind.NAME,
|
|
141
142
|
value: 'y',
|
|
142
143
|
},
|
|
143
144
|
},
|
package/esm/ast.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as t from 'gql-ast';
|
|
2
|
+
import { OperationTypeNode, } from 'graphql';
|
|
2
3
|
import { camelize, singularize } from 'inflection';
|
|
3
4
|
import { getCustomAst } from './custom-ast';
|
|
4
5
|
const NON_MUTABLE_PROPS = ['createdAt', 'createdBy', 'updatedAt', 'updatedBy'];
|
|
@@ -39,7 +40,7 @@ const createGqlMutation = ({ operationName, mutationName, selectArgs, selections
|
|
|
39
40
|
return t.document({
|
|
40
41
|
definitions: [
|
|
41
42
|
t.operationDefinition({
|
|
42
|
-
operation:
|
|
43
|
+
operation: OperationTypeNode.MUTATION,
|
|
43
44
|
name: mutationName,
|
|
44
45
|
variableDefinitions,
|
|
45
46
|
selectionSet: t.selectionSet({ selections: opSel }),
|
|
@@ -68,7 +69,7 @@ export const getAll = ({ queryName, operationName, query: _query, selection, })
|
|
|
68
69
|
const ast = t.document({
|
|
69
70
|
definitions: [
|
|
70
71
|
t.operationDefinition({
|
|
71
|
-
operation:
|
|
72
|
+
operation: OperationTypeNode.QUERY,
|
|
72
73
|
name: queryName,
|
|
73
74
|
selectionSet: t.selectionSet({ selections: opSel }),
|
|
74
75
|
}),
|
|
@@ -111,7 +112,7 @@ export const getCount = ({ queryName, operationName, query, }) => {
|
|
|
111
112
|
const ast = t.document({
|
|
112
113
|
definitions: [
|
|
113
114
|
t.operationDefinition({
|
|
114
|
-
operation:
|
|
115
|
+
operation: OperationTypeNode.QUERY,
|
|
115
116
|
name: queryName,
|
|
116
117
|
variableDefinitions,
|
|
117
118
|
selectionSet: t.selectionSet({ selections: opSel }),
|
|
@@ -207,7 +208,7 @@ export const getMany = ({ builder, queryName, operationName, query, selection, }
|
|
|
207
208
|
const ast = t.document({
|
|
208
209
|
definitions: [
|
|
209
210
|
t.operationDefinition({
|
|
210
|
-
operation:
|
|
211
|
+
operation: OperationTypeNode.QUERY,
|
|
211
212
|
name: queryName,
|
|
212
213
|
variableDefinitions,
|
|
213
214
|
selectionSet: t.selectionSet({
|
|
@@ -263,7 +264,7 @@ export const getOne = ({ queryName, operationName, query, selection, }) => {
|
|
|
263
264
|
const ast = t.document({
|
|
264
265
|
definitions: [
|
|
265
266
|
t.operationDefinition({
|
|
266
|
-
operation:
|
|
267
|
+
operation: OperationTypeNode.QUERY,
|
|
267
268
|
name: queryName,
|
|
268
269
|
variableDefinitions,
|
|
269
270
|
selectionSet: t.selectionSet({ selections: opSel }),
|
package/esm/custom-ast.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as t from 'gql-ast';
|
|
2
|
+
import { Kind } from 'graphql';
|
|
2
3
|
export function getCustomAst(fieldDefn) {
|
|
3
4
|
if (!fieldDefn) {
|
|
4
5
|
return null;
|
|
@@ -73,28 +74,28 @@ export function geometryPointAst(name) {
|
|
|
73
74
|
export function geometryCollectionAst(name) {
|
|
74
75
|
// Manually create inline fragment since gql-ast doesn't support it
|
|
75
76
|
const inlineFragment = {
|
|
76
|
-
kind:
|
|
77
|
+
kind: Kind.INLINE_FRAGMENT,
|
|
77
78
|
typeCondition: {
|
|
78
|
-
kind:
|
|
79
|
+
kind: Kind.NAMED_TYPE,
|
|
79
80
|
name: {
|
|
80
|
-
kind:
|
|
81
|
+
kind: Kind.NAME,
|
|
81
82
|
value: 'GeometryPoint',
|
|
82
83
|
},
|
|
83
84
|
},
|
|
84
85
|
selectionSet: {
|
|
85
|
-
kind:
|
|
86
|
+
kind: Kind.SELECTION_SET,
|
|
86
87
|
selections: [
|
|
87
88
|
{
|
|
88
|
-
kind:
|
|
89
|
+
kind: Kind.FIELD,
|
|
89
90
|
name: {
|
|
90
|
-
kind:
|
|
91
|
+
kind: Kind.NAME,
|
|
91
92
|
value: 'x',
|
|
92
93
|
},
|
|
93
94
|
},
|
|
94
95
|
{
|
|
95
|
-
kind:
|
|
96
|
+
kind: Kind.FIELD,
|
|
96
97
|
name: {
|
|
97
|
-
kind:
|
|
98
|
+
kind: Kind.NAME,
|
|
98
99
|
value: 'y',
|
|
99
100
|
},
|
|
100
101
|
},
|
package/esm/executor.js
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QueryExecutor - Execute GraphQL queries using Grafast/PostGraphile v5
|
|
3
|
+
*
|
|
4
|
+
* This module provides a high-level interface for executing GraphQL queries
|
|
5
|
+
* against a PostgreSQL database using PostGraphile v5's Grafast execution engine.
|
|
6
|
+
*/
|
|
7
|
+
import { execute } from 'grafast';
|
|
8
|
+
import { postgraphile } from 'postgraphile';
|
|
9
|
+
import { ConstructivePreset, makePgService } from 'graphile-settings';
|
|
10
|
+
import { withPgClientFromPgService } from 'graphile-build-pg';
|
|
11
|
+
import { LRUCache } from 'lru-cache';
|
|
12
|
+
/**
|
|
13
|
+
* Global cache for executor instances, keyed by connection string + schemas
|
|
14
|
+
*/
|
|
15
|
+
const executorCache = new LRUCache({
|
|
16
|
+
max: 10,
|
|
17
|
+
dispose: async (entry) => {
|
|
18
|
+
try {
|
|
19
|
+
await entry.pgl.release();
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// Ignore disposal errors
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
/**
|
|
27
|
+
* Build a cache key from connection string and schemas
|
|
28
|
+
*/
|
|
29
|
+
const buildCacheKey = (connectionString, schemas) => {
|
|
30
|
+
return `${connectionString}:${schemas.sort().join(',')}`;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* QueryExecutor - Execute GraphQL queries using Grafast
|
|
34
|
+
*
|
|
35
|
+
* Example usage:
|
|
36
|
+
* ```typescript
|
|
37
|
+
* const executor = new QueryExecutor({
|
|
38
|
+
* connectionString: 'postgres://user:pass@localhost/db',
|
|
39
|
+
* schemas: ['public'],
|
|
40
|
+
* pgSettings: { role: 'authenticated' },
|
|
41
|
+
* });
|
|
42
|
+
*
|
|
43
|
+
* await executor.initialize();
|
|
44
|
+
*
|
|
45
|
+
* const result = await executor.execute<{ allUsers: { nodes: User[] } }>(
|
|
46
|
+
* parse('query { allUsers { nodes { id name } } }')
|
|
47
|
+
* );
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export class QueryExecutor {
|
|
51
|
+
options;
|
|
52
|
+
executor = null;
|
|
53
|
+
cacheKey;
|
|
54
|
+
constructor(options) {
|
|
55
|
+
this.options = options;
|
|
56
|
+
this.cacheKey = buildCacheKey(options.connectionString, options.schemas);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Initialize the executor by building or retrieving the PostGraphile schema
|
|
60
|
+
*
|
|
61
|
+
* This method is called automatically by execute() if not already initialized,
|
|
62
|
+
* but can be called explicitly for eager initialization.
|
|
63
|
+
*/
|
|
64
|
+
async initialize() {
|
|
65
|
+
// Check cache first
|
|
66
|
+
const cached = executorCache.get(this.cacheKey);
|
|
67
|
+
if (cached) {
|
|
68
|
+
this.executor = cached;
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
// Create new PostGraphile instance
|
|
72
|
+
const pgService = makePgService({
|
|
73
|
+
connectionString: this.options.connectionString,
|
|
74
|
+
schemas: this.options.schemas,
|
|
75
|
+
});
|
|
76
|
+
// Note: Using 'as unknown as' to bypass strict type checking
|
|
77
|
+
// because GraphileConfig.Preset doesn't include pgServices in its type definition
|
|
78
|
+
// but postgraphile() accepts it at runtime
|
|
79
|
+
const preset = {
|
|
80
|
+
extends: [ConstructivePreset],
|
|
81
|
+
pgServices: [pgService],
|
|
82
|
+
grafast: {
|
|
83
|
+
context: () => ({
|
|
84
|
+
pgSettings: this.options.pgSettings || {},
|
|
85
|
+
}),
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
const pgl = postgraphile(preset);
|
|
89
|
+
const schema = await pgl.getSchema();
|
|
90
|
+
const resolvedPreset = pgl.getResolvedPreset();
|
|
91
|
+
this.executor = {
|
|
92
|
+
pgl,
|
|
93
|
+
schema,
|
|
94
|
+
resolvedPreset,
|
|
95
|
+
pgService,
|
|
96
|
+
createdAt: Date.now(),
|
|
97
|
+
};
|
|
98
|
+
executorCache.set(this.cacheKey, this.executor);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Execute a GraphQL document against the schema
|
|
102
|
+
*
|
|
103
|
+
* @param document - Parsed GraphQL document (DocumentNode)
|
|
104
|
+
* @param variables - Optional variables for the query
|
|
105
|
+
* @param pgSettings - Optional per-request PostgreSQL settings (overrides constructor settings)
|
|
106
|
+
* @returns ExecutionResult with data and/or errors
|
|
107
|
+
*/
|
|
108
|
+
async execute(document, variables, pgSettings) {
|
|
109
|
+
if (!this.executor) {
|
|
110
|
+
await this.initialize();
|
|
111
|
+
}
|
|
112
|
+
const { schema, resolvedPreset, pgService } = this.executor;
|
|
113
|
+
// Build context with pgSettings and withPgClient
|
|
114
|
+
const contextValue = {
|
|
115
|
+
pgSettings: pgSettings || this.options.pgSettings || {},
|
|
116
|
+
};
|
|
117
|
+
// Add withPgClient function using the pgService's configured key
|
|
118
|
+
const withPgClientKey = pgService.withPgClientKey ?? 'withPgClient';
|
|
119
|
+
contextValue[withPgClientKey] = withPgClientFromPgService.bind(null, pgService);
|
|
120
|
+
return execute({
|
|
121
|
+
schema,
|
|
122
|
+
document,
|
|
123
|
+
variableValues: variables,
|
|
124
|
+
contextValue,
|
|
125
|
+
resolvedPreset,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Get the GraphQL schema for introspection purposes
|
|
130
|
+
*/
|
|
131
|
+
async getSchema() {
|
|
132
|
+
if (!this.executor) {
|
|
133
|
+
await this.initialize();
|
|
134
|
+
}
|
|
135
|
+
return this.executor.schema;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Get cache statistics
|
|
139
|
+
*/
|
|
140
|
+
static getCacheStats() {
|
|
141
|
+
return {
|
|
142
|
+
size: executorCache.size,
|
|
143
|
+
maxSize: executorCache.max,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Clear all cached executors
|
|
148
|
+
*/
|
|
149
|
+
static async clearCache() {
|
|
150
|
+
executorCache.clear();
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Clear a specific executor from the cache
|
|
154
|
+
*/
|
|
155
|
+
static async clearCacheEntry(connectionString, schemas) {
|
|
156
|
+
const key = buildCacheKey(connectionString, schemas);
|
|
157
|
+
return executorCache.delete(key);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Create a QueryExecutor instance with simplified options
|
|
162
|
+
*
|
|
163
|
+
* @param connectionString - PostgreSQL connection string
|
|
164
|
+
* @param schemas - Database schemas to expose
|
|
165
|
+
* @param pgSettings - Optional PostgreSQL settings
|
|
166
|
+
* @returns Initialized QueryExecutor
|
|
167
|
+
*/
|
|
168
|
+
export const createExecutor = async (connectionString, schemas, pgSettings) => {
|
|
169
|
+
const executor = new QueryExecutor({
|
|
170
|
+
connectionString,
|
|
171
|
+
schemas,
|
|
172
|
+
pgSettings,
|
|
173
|
+
});
|
|
174
|
+
await executor.initialize();
|
|
175
|
+
return executor;
|
|
176
|
+
};
|
package/esm/index.js
CHANGED
package/executor.d.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QueryExecutor - Execute GraphQL queries using Grafast/PostGraphile v5
|
|
3
|
+
*
|
|
4
|
+
* This module provides a high-level interface for executing GraphQL queries
|
|
5
|
+
* against a PostgreSQL database using PostGraphile v5's Grafast execution engine.
|
|
6
|
+
*/
|
|
7
|
+
import type { DocumentNode, ExecutionResult, GraphQLSchema } from 'graphql';
|
|
8
|
+
/**
|
|
9
|
+
* Configuration options for QueryExecutor
|
|
10
|
+
*/
|
|
11
|
+
export interface ExecutorOptions {
|
|
12
|
+
/** PostgreSQL connection string */
|
|
13
|
+
connectionString: string;
|
|
14
|
+
/** Database schemas to expose in the GraphQL schema */
|
|
15
|
+
schemas: string[];
|
|
16
|
+
/** PostgreSQL settings to apply (e.g., { role: 'authenticated' }) */
|
|
17
|
+
pgSettings?: Record<string, string>;
|
|
18
|
+
/** Maximum number of cached executor instances (default: 10) */
|
|
19
|
+
maxCacheSize?: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* QueryExecutor - Execute GraphQL queries using Grafast
|
|
23
|
+
*
|
|
24
|
+
* Example usage:
|
|
25
|
+
* ```typescript
|
|
26
|
+
* const executor = new QueryExecutor({
|
|
27
|
+
* connectionString: 'postgres://user:pass@localhost/db',
|
|
28
|
+
* schemas: ['public'],
|
|
29
|
+
* pgSettings: { role: 'authenticated' },
|
|
30
|
+
* });
|
|
31
|
+
*
|
|
32
|
+
* await executor.initialize();
|
|
33
|
+
*
|
|
34
|
+
* const result = await executor.execute<{ allUsers: { nodes: User[] } }>(
|
|
35
|
+
* parse('query { allUsers { nodes { id name } } }')
|
|
36
|
+
* );
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export declare class QueryExecutor {
|
|
40
|
+
private options;
|
|
41
|
+
private executor;
|
|
42
|
+
private cacheKey;
|
|
43
|
+
constructor(options: ExecutorOptions);
|
|
44
|
+
/**
|
|
45
|
+
* Initialize the executor by building or retrieving the PostGraphile schema
|
|
46
|
+
*
|
|
47
|
+
* This method is called automatically by execute() if not already initialized,
|
|
48
|
+
* but can be called explicitly for eager initialization.
|
|
49
|
+
*/
|
|
50
|
+
initialize(): Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
* Execute a GraphQL document against the schema
|
|
53
|
+
*
|
|
54
|
+
* @param document - Parsed GraphQL document (DocumentNode)
|
|
55
|
+
* @param variables - Optional variables for the query
|
|
56
|
+
* @param pgSettings - Optional per-request PostgreSQL settings (overrides constructor settings)
|
|
57
|
+
* @returns ExecutionResult with data and/or errors
|
|
58
|
+
*/
|
|
59
|
+
execute<T = unknown>(document: DocumentNode, variables?: Record<string, unknown>, pgSettings?: Record<string, string>): Promise<ExecutionResult<T>>;
|
|
60
|
+
/**
|
|
61
|
+
* Get the GraphQL schema for introspection purposes
|
|
62
|
+
*/
|
|
63
|
+
getSchema(): Promise<GraphQLSchema>;
|
|
64
|
+
/**
|
|
65
|
+
* Get cache statistics
|
|
66
|
+
*/
|
|
67
|
+
static getCacheStats(): {
|
|
68
|
+
size: number;
|
|
69
|
+
maxSize: number;
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Clear all cached executors
|
|
73
|
+
*/
|
|
74
|
+
static clearCache(): Promise<void>;
|
|
75
|
+
/**
|
|
76
|
+
* Clear a specific executor from the cache
|
|
77
|
+
*/
|
|
78
|
+
static clearCacheEntry(connectionString: string, schemas: string[]): Promise<boolean>;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Create a QueryExecutor instance with simplified options
|
|
82
|
+
*
|
|
83
|
+
* @param connectionString - PostgreSQL connection string
|
|
84
|
+
* @param schemas - Database schemas to expose
|
|
85
|
+
* @param pgSettings - Optional PostgreSQL settings
|
|
86
|
+
* @returns Initialized QueryExecutor
|
|
87
|
+
*/
|
|
88
|
+
export declare const createExecutor: (connectionString: string, schemas: string[], pgSettings?: Record<string, string>) => Promise<QueryExecutor>;
|
package/executor.js
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* QueryExecutor - Execute GraphQL queries using Grafast/PostGraphile v5
|
|
4
|
+
*
|
|
5
|
+
* This module provides a high-level interface for executing GraphQL queries
|
|
6
|
+
* against a PostgreSQL database using PostGraphile v5's Grafast execution engine.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.createExecutor = exports.QueryExecutor = void 0;
|
|
10
|
+
const grafast_1 = require("grafast");
|
|
11
|
+
const postgraphile_1 = require("postgraphile");
|
|
12
|
+
const graphile_settings_1 = require("graphile-settings");
|
|
13
|
+
const graphile_build_pg_1 = require("graphile-build-pg");
|
|
14
|
+
const lru_cache_1 = require("lru-cache");
|
|
15
|
+
/**
|
|
16
|
+
* Global cache for executor instances, keyed by connection string + schemas
|
|
17
|
+
*/
|
|
18
|
+
const executorCache = new lru_cache_1.LRUCache({
|
|
19
|
+
max: 10,
|
|
20
|
+
dispose: async (entry) => {
|
|
21
|
+
try {
|
|
22
|
+
await entry.pgl.release();
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// Ignore disposal errors
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
/**
|
|
30
|
+
* Build a cache key from connection string and schemas
|
|
31
|
+
*/
|
|
32
|
+
const buildCacheKey = (connectionString, schemas) => {
|
|
33
|
+
return `${connectionString}:${schemas.sort().join(',')}`;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* QueryExecutor - Execute GraphQL queries using Grafast
|
|
37
|
+
*
|
|
38
|
+
* Example usage:
|
|
39
|
+
* ```typescript
|
|
40
|
+
* const executor = new QueryExecutor({
|
|
41
|
+
* connectionString: 'postgres://user:pass@localhost/db',
|
|
42
|
+
* schemas: ['public'],
|
|
43
|
+
* pgSettings: { role: 'authenticated' },
|
|
44
|
+
* });
|
|
45
|
+
*
|
|
46
|
+
* await executor.initialize();
|
|
47
|
+
*
|
|
48
|
+
* const result = await executor.execute<{ allUsers: { nodes: User[] } }>(
|
|
49
|
+
* parse('query { allUsers { nodes { id name } } }')
|
|
50
|
+
* );
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
class QueryExecutor {
|
|
54
|
+
options;
|
|
55
|
+
executor = null;
|
|
56
|
+
cacheKey;
|
|
57
|
+
constructor(options) {
|
|
58
|
+
this.options = options;
|
|
59
|
+
this.cacheKey = buildCacheKey(options.connectionString, options.schemas);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Initialize the executor by building or retrieving the PostGraphile schema
|
|
63
|
+
*
|
|
64
|
+
* This method is called automatically by execute() if not already initialized,
|
|
65
|
+
* but can be called explicitly for eager initialization.
|
|
66
|
+
*/
|
|
67
|
+
async initialize() {
|
|
68
|
+
// Check cache first
|
|
69
|
+
const cached = executorCache.get(this.cacheKey);
|
|
70
|
+
if (cached) {
|
|
71
|
+
this.executor = cached;
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
// Create new PostGraphile instance
|
|
75
|
+
const pgService = (0, graphile_settings_1.makePgService)({
|
|
76
|
+
connectionString: this.options.connectionString,
|
|
77
|
+
schemas: this.options.schemas,
|
|
78
|
+
});
|
|
79
|
+
// Note: Using 'as unknown as' to bypass strict type checking
|
|
80
|
+
// because GraphileConfig.Preset doesn't include pgServices in its type definition
|
|
81
|
+
// but postgraphile() accepts it at runtime
|
|
82
|
+
const preset = {
|
|
83
|
+
extends: [graphile_settings_1.ConstructivePreset],
|
|
84
|
+
pgServices: [pgService],
|
|
85
|
+
grafast: {
|
|
86
|
+
context: () => ({
|
|
87
|
+
pgSettings: this.options.pgSettings || {},
|
|
88
|
+
}),
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
const pgl = (0, postgraphile_1.postgraphile)(preset);
|
|
92
|
+
const schema = await pgl.getSchema();
|
|
93
|
+
const resolvedPreset = pgl.getResolvedPreset();
|
|
94
|
+
this.executor = {
|
|
95
|
+
pgl,
|
|
96
|
+
schema,
|
|
97
|
+
resolvedPreset,
|
|
98
|
+
pgService,
|
|
99
|
+
createdAt: Date.now(),
|
|
100
|
+
};
|
|
101
|
+
executorCache.set(this.cacheKey, this.executor);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Execute a GraphQL document against the schema
|
|
105
|
+
*
|
|
106
|
+
* @param document - Parsed GraphQL document (DocumentNode)
|
|
107
|
+
* @param variables - Optional variables for the query
|
|
108
|
+
* @param pgSettings - Optional per-request PostgreSQL settings (overrides constructor settings)
|
|
109
|
+
* @returns ExecutionResult with data and/or errors
|
|
110
|
+
*/
|
|
111
|
+
async execute(document, variables, pgSettings) {
|
|
112
|
+
if (!this.executor) {
|
|
113
|
+
await this.initialize();
|
|
114
|
+
}
|
|
115
|
+
const { schema, resolvedPreset, pgService } = this.executor;
|
|
116
|
+
// Build context with pgSettings and withPgClient
|
|
117
|
+
const contextValue = {
|
|
118
|
+
pgSettings: pgSettings || this.options.pgSettings || {},
|
|
119
|
+
};
|
|
120
|
+
// Add withPgClient function using the pgService's configured key
|
|
121
|
+
const withPgClientKey = pgService.withPgClientKey ?? 'withPgClient';
|
|
122
|
+
contextValue[withPgClientKey] = graphile_build_pg_1.withPgClientFromPgService.bind(null, pgService);
|
|
123
|
+
return (0, grafast_1.execute)({
|
|
124
|
+
schema,
|
|
125
|
+
document,
|
|
126
|
+
variableValues: variables,
|
|
127
|
+
contextValue,
|
|
128
|
+
resolvedPreset,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Get the GraphQL schema for introspection purposes
|
|
133
|
+
*/
|
|
134
|
+
async getSchema() {
|
|
135
|
+
if (!this.executor) {
|
|
136
|
+
await this.initialize();
|
|
137
|
+
}
|
|
138
|
+
return this.executor.schema;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Get cache statistics
|
|
142
|
+
*/
|
|
143
|
+
static getCacheStats() {
|
|
144
|
+
return {
|
|
145
|
+
size: executorCache.size,
|
|
146
|
+
maxSize: executorCache.max,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Clear all cached executors
|
|
151
|
+
*/
|
|
152
|
+
static async clearCache() {
|
|
153
|
+
executorCache.clear();
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Clear a specific executor from the cache
|
|
157
|
+
*/
|
|
158
|
+
static async clearCacheEntry(connectionString, schemas) {
|
|
159
|
+
const key = buildCacheKey(connectionString, schemas);
|
|
160
|
+
return executorCache.delete(key);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
exports.QueryExecutor = QueryExecutor;
|
|
164
|
+
/**
|
|
165
|
+
* Create a QueryExecutor instance with simplified options
|
|
166
|
+
*
|
|
167
|
+
* @param connectionString - PostgreSQL connection string
|
|
168
|
+
* @param schemas - Database schemas to expose
|
|
169
|
+
* @param pgSettings - Optional PostgreSQL settings
|
|
170
|
+
* @returns Initialized QueryExecutor
|
|
171
|
+
*/
|
|
172
|
+
const createExecutor = async (connectionString, schemas, pgSettings) => {
|
|
173
|
+
const executor = new QueryExecutor({
|
|
174
|
+
connectionString,
|
|
175
|
+
schemas,
|
|
176
|
+
pgSettings,
|
|
177
|
+
});
|
|
178
|
+
await executor.initialize();
|
|
179
|
+
return executor;
|
|
180
|
+
};
|
|
181
|
+
exports.createExecutor = createExecutor;
|
package/index.d.ts
CHANGED
package/index.js
CHANGED
|
@@ -36,8 +36,11 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
36
36
|
};
|
|
37
37
|
})();
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.MetaObject = exports.QueryBuilder = void 0;
|
|
39
|
+
exports.MetaObject = exports.createExecutor = exports.QueryExecutor = exports.QueryBuilder = void 0;
|
|
40
40
|
var query_builder_1 = require("./query-builder");
|
|
41
41
|
Object.defineProperty(exports, "QueryBuilder", { enumerable: true, get: function () { return query_builder_1.QueryBuilder; } });
|
|
42
|
+
var executor_1 = require("./executor");
|
|
43
|
+
Object.defineProperty(exports, "QueryExecutor", { enumerable: true, get: function () { return executor_1.QueryExecutor; } });
|
|
44
|
+
Object.defineProperty(exports, "createExecutor", { enumerable: true, get: function () { return executor_1.createExecutor; } });
|
|
42
45
|
__exportStar(require("./types"), exports);
|
|
43
46
|
exports.MetaObject = __importStar(require("./meta-object"));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@constructive-io/graphql-query",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"description": "Constructive GraphQL Query",
|
|
5
5
|
"author": "Constructive <developers@constructive.io>",
|
|
6
6
|
"main": "index.js",
|
|
@@ -30,9 +30,15 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"ajv": "^7.0.4",
|
|
33
|
-
"gql-ast": "^
|
|
34
|
-
"
|
|
35
|
-
"
|
|
33
|
+
"gql-ast": "^3.0.0",
|
|
34
|
+
"grafast": "^1.0.0-rc.4",
|
|
35
|
+
"graphile-build-pg": "^5.0.0-rc.3",
|
|
36
|
+
"graphile-config": "1.0.0-rc.3",
|
|
37
|
+
"graphile-settings": "^4.0.1",
|
|
38
|
+
"graphql": "^16.9.0",
|
|
39
|
+
"inflection": "^3.0.0",
|
|
40
|
+
"lru-cache": "^10.4.3",
|
|
41
|
+
"postgraphile": "^5.0.0-rc.4"
|
|
36
42
|
},
|
|
37
43
|
"keywords": [
|
|
38
44
|
"query",
|
|
@@ -42,7 +48,7 @@
|
|
|
42
48
|
"database"
|
|
43
49
|
],
|
|
44
50
|
"devDependencies": {
|
|
45
|
-
"makage": "^0.1.
|
|
51
|
+
"makage": "^0.1.10"
|
|
46
52
|
},
|
|
47
|
-
"gitHead": "
|
|
53
|
+
"gitHead": "c05d6bd3dfa36690aefe21079042666dfae801a1"
|
|
48
54
|
}
|
package/types.d.ts
CHANGED
|
@@ -137,3 +137,27 @@ export declare function isGraphQLVariables(obj: unknown): obj is GraphQLVariable
|
|
|
137
137
|
export type StrictRecord<K extends PropertyKey, V> = Record<K, V> & {
|
|
138
138
|
[P in PropertyKey]: P extends K ? V : never;
|
|
139
139
|
};
|
|
140
|
+
/**
|
|
141
|
+
* Configuration options for QueryExecutor
|
|
142
|
+
*/
|
|
143
|
+
export interface ExecutorOptions {
|
|
144
|
+
/** PostgreSQL connection string */
|
|
145
|
+
connectionString: string;
|
|
146
|
+
/** Database schemas to expose in the GraphQL schema */
|
|
147
|
+
schemas: string[];
|
|
148
|
+
/** PostgreSQL settings to apply (e.g., { role: 'authenticated' }) */
|
|
149
|
+
pgSettings?: Record<string, string>;
|
|
150
|
+
/** Maximum number of cached executor instances (default: 10) */
|
|
151
|
+
maxCacheSize?: number;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Cache statistics for QueryExecutor
|
|
155
|
+
*/
|
|
156
|
+
export interface ExecutorCacheStats {
|
|
157
|
+
size: number;
|
|
158
|
+
maxSize: number;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Re-export GraphQL execution types for convenience
|
|
162
|
+
*/
|
|
163
|
+
export type { ExecutionResult, GraphQLError, GraphQLSchema, } from 'graphql';
|