graphile-test 3.1.1 → 4.1.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/clean.js CHANGED
@@ -24,7 +24,7 @@ const pruneDates = (row) => mapValues(row, (v, k) => {
24
24
  return v;
25
25
  });
26
26
  exports.pruneDates = pruneDates;
27
- const pruneIds = (row) => mapValues(row, (v, k) => (k === 'id' || (typeof k === 'string' && k.endsWith('_id'))) &&
27
+ const pruneIds = (row) => mapValues(row, (v, k) => (k === 'id' || k === 'rowId' || (typeof k === 'string' && k.endsWith('_id'))) &&
28
28
  (typeof v === 'string' || typeof v === 'number')
29
29
  ? idReplacement(v)
30
30
  : v);
package/context.d.ts CHANGED
@@ -1,17 +1,30 @@
1
- import { DocumentNode, ExecutionResult } from 'graphql';
1
+ import type { DocumentNode, ExecutionResult, GraphQLSchema } from 'graphql';
2
+ import type { GraphileConfig } from 'graphile-config';
3
+ import { makePgService } from 'postgraphile/adaptors/pg';
2
4
  import type { Client, Pool } from 'pg';
3
- import { GetConnectionOpts, GetConnectionResult } from 'pgsql-test';
4
- import { PostGraphileOptions } from 'postgraphile';
5
- import { GetConnectionsInput } from './types';
6
- export declare const runGraphQLInContext: <T = ExecutionResult>({ input, conn, pgPool, schema, options, authRole, query, variables, reqOptions }: {
5
+ import type { GetConnectionOpts, GetConnectionResult } from 'pgsql-test';
6
+ import type { GetConnectionsInput } from './types';
7
+ interface RunGraphQLOptions {
7
8
  input: GetConnectionsInput & GetConnectionOpts;
8
9
  conn: GetConnectionResult;
9
10
  pgPool: Pool;
10
- schema: any;
11
- options: PostGraphileOptions;
11
+ pgService: ReturnType<typeof makePgService>;
12
+ schema: GraphQLSchema;
13
+ resolvedPreset: GraphileConfig.ResolvedPreset;
12
14
  authRole: string;
13
15
  query: string | DocumentNode;
14
16
  variables?: Record<string, any>;
15
- reqOptions?: Record<string, any>;
16
- }) => Promise<T>;
17
+ reqOptions?: Record<string, unknown>;
18
+ }
19
+ /**
20
+ * Execute a GraphQL query in the v5 context using grafast's execute function.
21
+ *
22
+ * This replaces the v4 withPostGraphileContext pattern with grafast execution.
23
+ * Uses execute() with withPgClient to ensure proper connection handling and pgSettings.
24
+ */
25
+ export declare const runGraphQLInContext: <T = ExecutionResult>({ input, conn, schema, resolvedPreset, pgService, authRole, query, variables, reqOptions, }: RunGraphQLOptions) => Promise<T>;
26
+ /**
27
+ * Set the PostgreSQL role and session settings on a client connection.
28
+ */
17
29
  export declare function setContextOnClient(pgClient: Client, pgSettings: Record<string, string>, role: string): Promise<void>;
30
+ export {};
package/context.js CHANGED
@@ -1,60 +1,243 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.runGraphQLInContext = void 0;
7
4
  exports.setContextOnClient = setContextOnClient;
8
5
  const graphql_1 = require("graphql");
9
- // @ts-ignore
10
- const mock_req_1 = __importDefault(require("mock-req"));
11
- const postgraphile_1 = require("postgraphile");
12
- const runGraphQLInContext = async ({ input, conn, pgPool, schema, options, authRole, query, variables, reqOptions = {} }) => {
6
+ const grafast_1 = require("grafast");
7
+ /**
8
+ * Find a field node in the document by following the path.
9
+ * This is used to derive locations when grafast doesn't provide them.
10
+ */
11
+ function findFieldNodeByPath(document, path) {
12
+ if (!path || path.length === 0)
13
+ return null;
14
+ // Find the operation definition
15
+ const operation = document.definitions.find((def) => def.kind === graphql_1.Kind.OPERATION_DEFINITION);
16
+ if (!operation)
17
+ return null;
18
+ // Navigate through the path to find the field
19
+ let selections = operation.selectionSet.selections;
20
+ let fieldNode = null;
21
+ for (const segment of path) {
22
+ if (typeof segment === 'number') {
23
+ // Array index - skip, the field is still the same
24
+ continue;
25
+ }
26
+ // Find the field with this name
27
+ fieldNode = selections.find((sel) => sel.kind === graphql_1.Kind.FIELD && (sel.alias?.value ?? sel.name.value) === segment) ?? null;
28
+ if (!fieldNode)
29
+ return null;
30
+ // Move to nested selections if they exist
31
+ if (fieldNode.selectionSet) {
32
+ selections = fieldNode.selectionSet.selections;
33
+ }
34
+ }
35
+ return fieldNode;
36
+ }
37
+ /**
38
+ * Format a GraphQL error to match the v4 PostGraphile format.
39
+ *
40
+ * v5 grafast errors have different formatting:
41
+ * - They include `extensions: {}` even when empty
42
+ * - They may have `locations: undefined` instead of omitting the field
43
+ * - They don't always include locations even when nodes are available
44
+ *
45
+ * This normalizes errors to match v4 format for backward compatibility.
46
+ *
47
+ * @param error - The GraphQL error to format
48
+ * @param document - The original document (used to derive locations from path)
49
+ */
50
+ function formatErrorToV4(error, document) {
51
+ const formatted = {
52
+ message: error.message,
53
+ };
54
+ // Try to get locations from the error directly first
55
+ let locations = error.locations;
56
+ // If no locations but nodes are available, try to extract from nodes
57
+ if ((!locations || locations.length === 0) && error.nodes && error.nodes.length > 0) {
58
+ const extractedLocations = error.nodes
59
+ .filter(node => node.loc)
60
+ .map(node => {
61
+ const loc = node.loc;
62
+ if (loc && loc.source) {
63
+ // Calculate line and column from the source
64
+ const { startToken } = loc;
65
+ if (startToken) {
66
+ return { line: startToken.line, column: startToken.column };
67
+ }
68
+ }
69
+ return null;
70
+ })
71
+ .filter((loc) => loc !== null);
72
+ if (extractedLocations.length > 0) {
73
+ locations = extractedLocations;
74
+ }
75
+ }
76
+ // If still no locations and we have a path and document, try to derive from path
77
+ if ((!locations || locations.length === 0) && error.path && document) {
78
+ const fieldNode = findFieldNodeByPath(document, error.path);
79
+ if (fieldNode?.loc) {
80
+ const { startToken } = fieldNode.loc;
81
+ if (startToken) {
82
+ locations = [{ line: startToken.line, column: startToken.column }];
83
+ }
84
+ }
85
+ }
86
+ // Only include locations if it's a non-empty array
87
+ if (locations && Array.isArray(locations) && locations.length > 0) {
88
+ formatted.locations = locations;
89
+ }
90
+ // Only include path if it exists
91
+ if (error.path && error.path.length > 0) {
92
+ formatted.path = error.path;
93
+ }
94
+ // Only include extensions if it has non-empty content
95
+ if (error.extensions && Object.keys(error.extensions).length > 0) {
96
+ formatted.extensions = error.extensions;
97
+ }
98
+ return formatted;
99
+ }
100
+ /**
101
+ * Normalize an ExecutionResult to match v4 PostGraphile output format.
102
+ * This ensures backward compatibility with existing tests and consumers.
103
+ *
104
+ * @param result - The execution result from grafast
105
+ * @param document - The original document (used to derive locations from path)
106
+ */
107
+ function normalizeResult(result, document) {
108
+ const normalized = {
109
+ data: result.data,
110
+ };
111
+ // Format errors to match v4 style
112
+ if (result.errors && result.errors.length > 0) {
113
+ normalized.errors = result.errors.map(err => formatErrorToV4(err, document));
114
+ }
115
+ // Only include extensions if present and non-empty
116
+ if (result.extensions && Object.keys(result.extensions).length > 0) {
117
+ normalized.extensions = result.extensions;
118
+ }
119
+ return normalized;
120
+ }
121
+ /**
122
+ * Execute a GraphQL query in the v5 context using grafast's execute function.
123
+ *
124
+ * This replaces the v4 withPostGraphileContext pattern with grafast execution.
125
+ * Uses execute() with withPgClient to ensure proper connection handling and pgSettings.
126
+ */
127
+ const runGraphQLInContext = async ({ input, conn, schema, resolvedPreset, pgService, authRole, query, variables, reqOptions = {}, }) => {
13
128
  if (!conn.pg.client) {
14
129
  throw new Error('pgClient is required and must be provided externally.');
15
130
  }
16
- const { res: reqRes, ...restReqOptions } = reqOptions ?? {};
17
- const req = new mock_req_1.default({
18
- url: options.graphqlRoute || '/graphql',
19
- method: 'POST',
20
- headers: {
21
- Accept: 'application/json',
22
- 'Content-Type': 'application/json'
23
- },
24
- ...restReqOptions
25
- });
26
- const res = reqRes ?? {};
27
- const pgSettingsGenerator = options.pgSettings;
28
- // @ts-ignore
29
- const pgSettings = typeof pgSettingsGenerator === 'function'
30
- ? await pgSettingsGenerator(req)
31
- : pgSettingsGenerator || {};
32
- const contextOptions = { ...options, pgPool, pgSettings, req, res };
33
- // @ts-ignore
34
- return await (0, postgraphile_1.withPostGraphileContext)(contextOptions, async (context) => {
35
- const pgConn = input.useRoot ? conn.pg : conn.db;
36
- const pgClient = pgConn.client;
37
- // IS THIS BAD TO HAVE ROLE HERE
38
- await setContextOnClient(pgClient, pgSettings, authRole);
39
- await pgConn.ctxQuery();
40
- const additionalContext = typeof options.additionalGraphQLContextFromRequest === 'function'
41
- ? await options.additionalGraphQLContextFromRequest(req, res)
42
- : {};
43
- const printed = typeof query === 'string' ? query : (0, graphql_1.print)(query);
44
- const result = await (0, graphql_1.graphql)({
131
+ // Get the appropriate connection (root or database-specific)
132
+ const pgConn = input.useRoot ? conn.pg : conn.db;
133
+ const pgClient = pgConn.client;
134
+ // Build pgSettings by merging:
135
+ // 1. Context from db.setContext() (e.g., role, myapp.user_id)
136
+ // 2. Settings from reqOptions.pgSettings (explicit overrides)
137
+ // The role from getContext() takes precedence over authRole default
138
+ const dbContext = pgConn.getContext();
139
+ const reqPgSettings = reqOptions.pgSettings ?? {};
140
+ // Start with authRole as default, then apply db context, then request overrides
141
+ const pgSettings = {
142
+ role: authRole,
143
+ ...Object.fromEntries(Object.entries(dbContext).filter(([, v]) => v !== null)),
144
+ ...reqPgSettings,
145
+ };
146
+ // Set role and context on the client for direct queries
147
+ await setContextOnClient(pgClient, pgSettings, pgSettings.role);
148
+ await pgConn.ctxQuery();
149
+ // Convert query to DocumentNode if it's a string
150
+ // Also ensure we have location information for error reporting
151
+ let document;
152
+ if (typeof query === 'string') {
153
+ document = (0, graphql_1.parse)(query);
154
+ }
155
+ else {
156
+ // For DocumentNode (from graphql-tag), re-parse from source to get locations
157
+ // graphql-tag doesn't preserve location info by default
158
+ const source = (0, graphql_1.print)(query);
159
+ document = (0, graphql_1.parse)(source);
160
+ }
161
+ // Build context with pgSettings and withPgClient
162
+ // This is the v5 pattern used by executor.ts and api.ts
163
+ const contextValue = {
164
+ pgSettings,
165
+ // Include additional request options (excluding pgSettings to avoid duplication)
166
+ ...Object.fromEntries(Object.entries(reqOptions).filter(([key]) => key !== 'pgSettings')),
167
+ };
168
+ // Provide a custom withPgClient function that uses the test client
169
+ // This ensures GraphQL operations run within the test transaction
170
+ // instead of getting a new connection from the pool
171
+ const withPgClientKey = pgService.withPgClientKey ?? 'withPgClient';
172
+ contextValue[withPgClientKey] = async (_pgSettings, callback) => {
173
+ // Simply use the test client - it's already in a transaction
174
+ // The pgSettings have already been applied above via setContextOnClient
175
+ return callback(pgClient);
176
+ };
177
+ // Check if we're in a transaction by looking at the test client's transaction state
178
+ // When useRoot is true, we might not be in a transaction
179
+ // pgsql-test's `db` client is in a transaction, but `pg` (root) client may not be
180
+ const isInTransaction = !input.useRoot;
181
+ // Wrap the entire query execution in a savepoint if we're in a transaction
182
+ // This matches v4 PostGraphile behavior where each mutation is wrapped in a savepoint
183
+ // allowing the transaction to continue after a database error
184
+ const executionSavepoint = isInTransaction
185
+ ? `graphile_exec_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
186
+ : null;
187
+ if (executionSavepoint) {
188
+ await pgClient.query(`SAVEPOINT ${executionSavepoint}`);
189
+ }
190
+ let rawResult;
191
+ try {
192
+ // Execute using grafast's execute function - the v5 execution engine
193
+ rawResult = await (0, grafast_1.execute)({
45
194
  schema,
46
- source: printed,
47
- contextValue: { ...context, ...additionalContext, pgClient },
48
- variableValues: variables ?? null
195
+ document,
196
+ variableValues: variables ?? undefined,
197
+ contextValue,
198
+ resolvedPreset,
49
199
  });
50
- return result;
51
- });
200
+ }
201
+ catch (error) {
202
+ // Rollback to savepoint on execution error
203
+ if (executionSavepoint) {
204
+ await pgClient.query(`ROLLBACK TO SAVEPOINT ${executionSavepoint}`);
205
+ }
206
+ throw error;
207
+ }
208
+ // Handle streaming results (subscriptions return AsyncGenerator)
209
+ // For normal queries, we get ExecutionResult directly
210
+ if (Symbol.asyncIterator in rawResult) {
211
+ // This is a subscription/streaming result - not supported in test context
212
+ throw new Error('Streaming results (subscriptions) are not supported in test context');
213
+ }
214
+ const result = rawResult;
215
+ // If there were errors, rollback to savepoint to keep transaction usable
216
+ // This matches v4 behavior where database errors don't abort the transaction
217
+ if (executionSavepoint) {
218
+ if (result.errors && result.errors.length > 0) {
219
+ try {
220
+ await pgClient.query(`ROLLBACK TO SAVEPOINT ${executionSavepoint}`);
221
+ }
222
+ catch {
223
+ // Ignore if savepoint doesn't exist
224
+ }
225
+ }
226
+ else {
227
+ // Release the savepoint on success
228
+ await pgClient.query(`RELEASE SAVEPOINT ${executionSavepoint}`);
229
+ }
230
+ }
231
+ // Normalize the result to match v4 PostGraphile format for backward compatibility
232
+ return normalizeResult(result, document);
52
233
  };
53
234
  exports.runGraphQLInContext = runGraphQLInContext;
54
- // IS THIS BAD TO HAVE ROLE HERE
235
+ /**
236
+ * Set the PostgreSQL role and session settings on a client connection.
237
+ */
55
238
  async function setContextOnClient(pgClient, pgSettings, role) {
56
- await pgClient.query(`select set_config('role', $1, true)`, [role]);
239
+ await pgClient.query('SELECT set_config($1, $2, true)', ['role', role]);
57
240
  for (const [key, value] of Object.entries(pgSettings)) {
58
- await pgClient.query(`select set_config($1, $2, true)`, [key, String(value)]);
241
+ await pgClient.query('SELECT set_config($1, $2, true)', [key, String(value)]);
59
242
  }
60
243
  }
package/esm/clean.js CHANGED
@@ -20,7 +20,7 @@ export const pruneDates = (row) => mapValues(row, (v, k) => {
20
20
  }
21
21
  return v;
22
22
  });
23
- export const pruneIds = (row) => mapValues(row, (v, k) => (k === 'id' || (typeof k === 'string' && k.endsWith('_id'))) &&
23
+ export const pruneIds = (row) => mapValues(row, (v, k) => (k === 'id' || k === 'rowId' || (typeof k === 'string' && k.endsWith('_id'))) &&
24
24
  (typeof v === 'string' || typeof v === 'number')
25
25
  ? idReplacement(v)
26
26
  : v);
package/esm/context.js CHANGED
@@ -1,52 +1,238 @@
1
- import { graphql, print } from 'graphql';
2
- // @ts-ignore
3
- import MockReq from 'mock-req';
4
- import { withPostGraphileContext } from 'postgraphile';
5
- export const runGraphQLInContext = async ({ input, conn, pgPool, schema, options, authRole, query, variables, reqOptions = {} }) => {
1
+ import { parse, print, Kind } from 'graphql';
2
+ import { execute } from 'grafast';
3
+ /**
4
+ * Find a field node in the document by following the path.
5
+ * This is used to derive locations when grafast doesn't provide them.
6
+ */
7
+ function findFieldNodeByPath(document, path) {
8
+ if (!path || path.length === 0)
9
+ return null;
10
+ // Find the operation definition
11
+ const operation = document.definitions.find((def) => def.kind === Kind.OPERATION_DEFINITION);
12
+ if (!operation)
13
+ return null;
14
+ // Navigate through the path to find the field
15
+ let selections = operation.selectionSet.selections;
16
+ let fieldNode = null;
17
+ for (const segment of path) {
18
+ if (typeof segment === 'number') {
19
+ // Array index - skip, the field is still the same
20
+ continue;
21
+ }
22
+ // Find the field with this name
23
+ fieldNode = selections.find((sel) => sel.kind === Kind.FIELD && (sel.alias?.value ?? sel.name.value) === segment) ?? null;
24
+ if (!fieldNode)
25
+ return null;
26
+ // Move to nested selections if they exist
27
+ if (fieldNode.selectionSet) {
28
+ selections = fieldNode.selectionSet.selections;
29
+ }
30
+ }
31
+ return fieldNode;
32
+ }
33
+ /**
34
+ * Format a GraphQL error to match the v4 PostGraphile format.
35
+ *
36
+ * v5 grafast errors have different formatting:
37
+ * - They include `extensions: {}` even when empty
38
+ * - They may have `locations: undefined` instead of omitting the field
39
+ * - They don't always include locations even when nodes are available
40
+ *
41
+ * This normalizes errors to match v4 format for backward compatibility.
42
+ *
43
+ * @param error - The GraphQL error to format
44
+ * @param document - The original document (used to derive locations from path)
45
+ */
46
+ function formatErrorToV4(error, document) {
47
+ const formatted = {
48
+ message: error.message,
49
+ };
50
+ // Try to get locations from the error directly first
51
+ let locations = error.locations;
52
+ // If no locations but nodes are available, try to extract from nodes
53
+ if ((!locations || locations.length === 0) && error.nodes && error.nodes.length > 0) {
54
+ const extractedLocations = error.nodes
55
+ .filter(node => node.loc)
56
+ .map(node => {
57
+ const loc = node.loc;
58
+ if (loc && loc.source) {
59
+ // Calculate line and column from the source
60
+ const { startToken } = loc;
61
+ if (startToken) {
62
+ return { line: startToken.line, column: startToken.column };
63
+ }
64
+ }
65
+ return null;
66
+ })
67
+ .filter((loc) => loc !== null);
68
+ if (extractedLocations.length > 0) {
69
+ locations = extractedLocations;
70
+ }
71
+ }
72
+ // If still no locations and we have a path and document, try to derive from path
73
+ if ((!locations || locations.length === 0) && error.path && document) {
74
+ const fieldNode = findFieldNodeByPath(document, error.path);
75
+ if (fieldNode?.loc) {
76
+ const { startToken } = fieldNode.loc;
77
+ if (startToken) {
78
+ locations = [{ line: startToken.line, column: startToken.column }];
79
+ }
80
+ }
81
+ }
82
+ // Only include locations if it's a non-empty array
83
+ if (locations && Array.isArray(locations) && locations.length > 0) {
84
+ formatted.locations = locations;
85
+ }
86
+ // Only include path if it exists
87
+ if (error.path && error.path.length > 0) {
88
+ formatted.path = error.path;
89
+ }
90
+ // Only include extensions if it has non-empty content
91
+ if (error.extensions && Object.keys(error.extensions).length > 0) {
92
+ formatted.extensions = error.extensions;
93
+ }
94
+ return formatted;
95
+ }
96
+ /**
97
+ * Normalize an ExecutionResult to match v4 PostGraphile output format.
98
+ * This ensures backward compatibility with existing tests and consumers.
99
+ *
100
+ * @param result - The execution result from grafast
101
+ * @param document - The original document (used to derive locations from path)
102
+ */
103
+ function normalizeResult(result, document) {
104
+ const normalized = {
105
+ data: result.data,
106
+ };
107
+ // Format errors to match v4 style
108
+ if (result.errors && result.errors.length > 0) {
109
+ normalized.errors = result.errors.map(err => formatErrorToV4(err, document));
110
+ }
111
+ // Only include extensions if present and non-empty
112
+ if (result.extensions && Object.keys(result.extensions).length > 0) {
113
+ normalized.extensions = result.extensions;
114
+ }
115
+ return normalized;
116
+ }
117
+ /**
118
+ * Execute a GraphQL query in the v5 context using grafast's execute function.
119
+ *
120
+ * This replaces the v4 withPostGraphileContext pattern with grafast execution.
121
+ * Uses execute() with withPgClient to ensure proper connection handling and pgSettings.
122
+ */
123
+ export const runGraphQLInContext = async ({ input, conn, schema, resolvedPreset, pgService, authRole, query, variables, reqOptions = {}, }) => {
6
124
  if (!conn.pg.client) {
7
125
  throw new Error('pgClient is required and must be provided externally.');
8
126
  }
9
- const { res: reqRes, ...restReqOptions } = reqOptions ?? {};
10
- const req = new MockReq({
11
- url: options.graphqlRoute || '/graphql',
12
- method: 'POST',
13
- headers: {
14
- Accept: 'application/json',
15
- 'Content-Type': 'application/json'
16
- },
17
- ...restReqOptions
18
- });
19
- const res = reqRes ?? {};
20
- const pgSettingsGenerator = options.pgSettings;
21
- // @ts-ignore
22
- const pgSettings = typeof pgSettingsGenerator === 'function'
23
- ? await pgSettingsGenerator(req)
24
- : pgSettingsGenerator || {};
25
- const contextOptions = { ...options, pgPool, pgSettings, req, res };
26
- // @ts-ignore
27
- return await withPostGraphileContext(contextOptions, async (context) => {
28
- const pgConn = input.useRoot ? conn.pg : conn.db;
29
- const pgClient = pgConn.client;
30
- // IS THIS BAD TO HAVE ROLE HERE
31
- await setContextOnClient(pgClient, pgSettings, authRole);
32
- await pgConn.ctxQuery();
33
- const additionalContext = typeof options.additionalGraphQLContextFromRequest === 'function'
34
- ? await options.additionalGraphQLContextFromRequest(req, res)
35
- : {};
36
- const printed = typeof query === 'string' ? query : print(query);
37
- const result = await graphql({
127
+ // Get the appropriate connection (root or database-specific)
128
+ const pgConn = input.useRoot ? conn.pg : conn.db;
129
+ const pgClient = pgConn.client;
130
+ // Build pgSettings by merging:
131
+ // 1. Context from db.setContext() (e.g., role, myapp.user_id)
132
+ // 2. Settings from reqOptions.pgSettings (explicit overrides)
133
+ // The role from getContext() takes precedence over authRole default
134
+ const dbContext = pgConn.getContext();
135
+ const reqPgSettings = reqOptions.pgSettings ?? {};
136
+ // Start with authRole as default, then apply db context, then request overrides
137
+ const pgSettings = {
138
+ role: authRole,
139
+ ...Object.fromEntries(Object.entries(dbContext).filter(([, v]) => v !== null)),
140
+ ...reqPgSettings,
141
+ };
142
+ // Set role and context on the client for direct queries
143
+ await setContextOnClient(pgClient, pgSettings, pgSettings.role);
144
+ await pgConn.ctxQuery();
145
+ // Convert query to DocumentNode if it's a string
146
+ // Also ensure we have location information for error reporting
147
+ let document;
148
+ if (typeof query === 'string') {
149
+ document = parse(query);
150
+ }
151
+ else {
152
+ // For DocumentNode (from graphql-tag), re-parse from source to get locations
153
+ // graphql-tag doesn't preserve location info by default
154
+ const source = print(query);
155
+ document = parse(source);
156
+ }
157
+ // Build context with pgSettings and withPgClient
158
+ // This is the v5 pattern used by executor.ts and api.ts
159
+ const contextValue = {
160
+ pgSettings,
161
+ // Include additional request options (excluding pgSettings to avoid duplication)
162
+ ...Object.fromEntries(Object.entries(reqOptions).filter(([key]) => key !== 'pgSettings')),
163
+ };
164
+ // Provide a custom withPgClient function that uses the test client
165
+ // This ensures GraphQL operations run within the test transaction
166
+ // instead of getting a new connection from the pool
167
+ const withPgClientKey = pgService.withPgClientKey ?? 'withPgClient';
168
+ contextValue[withPgClientKey] = async (_pgSettings, callback) => {
169
+ // Simply use the test client - it's already in a transaction
170
+ // The pgSettings have already been applied above via setContextOnClient
171
+ return callback(pgClient);
172
+ };
173
+ // Check if we're in a transaction by looking at the test client's transaction state
174
+ // When useRoot is true, we might not be in a transaction
175
+ // pgsql-test's `db` client is in a transaction, but `pg` (root) client may not be
176
+ const isInTransaction = !input.useRoot;
177
+ // Wrap the entire query execution in a savepoint if we're in a transaction
178
+ // This matches v4 PostGraphile behavior where each mutation is wrapped in a savepoint
179
+ // allowing the transaction to continue after a database error
180
+ const executionSavepoint = isInTransaction
181
+ ? `graphile_exec_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
182
+ : null;
183
+ if (executionSavepoint) {
184
+ await pgClient.query(`SAVEPOINT ${executionSavepoint}`);
185
+ }
186
+ let rawResult;
187
+ try {
188
+ // Execute using grafast's execute function - the v5 execution engine
189
+ rawResult = await execute({
38
190
  schema,
39
- source: printed,
40
- contextValue: { ...context, ...additionalContext, pgClient },
41
- variableValues: variables ?? null
191
+ document,
192
+ variableValues: variables ?? undefined,
193
+ contextValue,
194
+ resolvedPreset,
42
195
  });
43
- return result;
44
- });
196
+ }
197
+ catch (error) {
198
+ // Rollback to savepoint on execution error
199
+ if (executionSavepoint) {
200
+ await pgClient.query(`ROLLBACK TO SAVEPOINT ${executionSavepoint}`);
201
+ }
202
+ throw error;
203
+ }
204
+ // Handle streaming results (subscriptions return AsyncGenerator)
205
+ // For normal queries, we get ExecutionResult directly
206
+ if (Symbol.asyncIterator in rawResult) {
207
+ // This is a subscription/streaming result - not supported in test context
208
+ throw new Error('Streaming results (subscriptions) are not supported in test context');
209
+ }
210
+ const result = rawResult;
211
+ // If there were errors, rollback to savepoint to keep transaction usable
212
+ // This matches v4 behavior where database errors don't abort the transaction
213
+ if (executionSavepoint) {
214
+ if (result.errors && result.errors.length > 0) {
215
+ try {
216
+ await pgClient.query(`ROLLBACK TO SAVEPOINT ${executionSavepoint}`);
217
+ }
218
+ catch {
219
+ // Ignore if savepoint doesn't exist
220
+ }
221
+ }
222
+ else {
223
+ // Release the savepoint on success
224
+ await pgClient.query(`RELEASE SAVEPOINT ${executionSavepoint}`);
225
+ }
226
+ }
227
+ // Normalize the result to match v4 PostGraphile format for backward compatibility
228
+ return normalizeResult(result, document);
45
229
  };
46
- // IS THIS BAD TO HAVE ROLE HERE
230
+ /**
231
+ * Set the PostgreSQL role and session settings on a client connection.
232
+ */
47
233
  export async function setContextOnClient(pgClient, pgSettings, role) {
48
- await pgClient.query(`select set_config('role', $1, true)`, [role]);
234
+ await pgClient.query('SELECT set_config($1, $2, true)', ['role', role]);
49
235
  for (const [key, value] of Object.entries(pgSettings)) {
50
- await pgClient.query(`select set_config($1, $2, true)`, [key, String(value)]);
236
+ await pgClient.query('SELECT set_config($1, $2, true)', [key, String(value)]);
51
237
  }
52
238
  }
@@ -28,7 +28,7 @@ const createConnectionsBase = async (input, seedAdapters) => {
28
28
  teardown,
29
29
  baseQuery,
30
30
  baseQueryPositional,
31
- gqlContext
31
+ gqlContext,
32
32
  };
33
33
  };
34
34
  // ============================================================================
@@ -44,7 +44,7 @@ export const getConnectionsObject = async (input, seedAdapters) => {
44
44
  db,
45
45
  teardown,
46
46
  query: baseQuery,
47
- gqlContext
47
+ gqlContext,
48
48
  };
49
49
  };
50
50
  /**
@@ -57,7 +57,7 @@ export const getConnectionsObjectUnwrapped = async (input, seedAdapters) => {
57
57
  pg,
58
58
  db,
59
59
  teardown,
60
- query
60
+ query,
61
61
  };
62
62
  };
63
63
  /**
@@ -75,7 +75,7 @@ export const getConnectionsObjectWithLogging = async (input, seedAdapters) => {
75
75
  pg,
76
76
  db,
77
77
  teardown,
78
- query
78
+ query,
79
79
  };
80
80
  };
81
81
  /**
@@ -94,7 +94,7 @@ export const getConnectionsObjectWithTiming = async (input, seedAdapters) => {
94
94
  pg,
95
95
  db,
96
96
  teardown,
97
- query
97
+ query,
98
98
  };
99
99
  };
100
100
  // ============================================================================
@@ -109,7 +109,7 @@ export const getConnections = async (input, seedAdapters) => {
109
109
  pg,
110
110
  db,
111
111
  teardown,
112
- query: baseQueryPositional
112
+ query: baseQueryPositional,
113
113
  };
114
114
  };
115
115
  /**
@@ -117,12 +117,12 @@ export const getConnections = async (input, seedAdapters) => {
117
117
  */
118
118
  export const getConnectionsUnwrapped = async (input, seedAdapters) => {
119
119
  const { pg, db, teardown, baseQueryPositional } = await createConnectionsBase(input, seedAdapters);
120
- const query = async (query, variables, commit, reqOptions) => unwrap(await baseQueryPositional(query, variables, commit, reqOptions));
120
+ const query = async (queryDoc, variables, commit, reqOptions) => unwrap(await baseQueryPositional(queryDoc, variables, commit, reqOptions));
121
121
  return {
122
122
  pg,
123
123
  db,
124
124
  teardown,
125
- query
125
+ query,
126
126
  };
127
127
  };
128
128
  /**
@@ -130,9 +130,9 @@ export const getConnectionsUnwrapped = async (input, seedAdapters) => {
130
130
  */
131
131
  export const getConnectionsWithLogging = async (input, seedAdapters) => {
132
132
  const { pg, db, teardown, baseQueryPositional } = await createConnectionsBase(input, seedAdapters);
133
- const query = async (query, variables, commit, reqOptions) => {
134
- console.log('Executing positional GraphQL query:', query);
135
- const result = await baseQueryPositional(query, variables, commit, reqOptions);
133
+ const query = async (queryDoc, variables, commit, reqOptions) => {
134
+ console.log('Executing positional GraphQL query:', queryDoc);
135
+ const result = await baseQueryPositional(queryDoc, variables, commit, reqOptions);
136
136
  console.log('GraphQL result:', result);
137
137
  return result;
138
138
  };
@@ -140,7 +140,7 @@ export const getConnectionsWithLogging = async (input, seedAdapters) => {
140
140
  pg,
141
141
  db,
142
142
  teardown,
143
- query
143
+ query,
144
144
  };
145
145
  };
146
146
  /**
@@ -148,9 +148,9 @@ export const getConnectionsWithLogging = async (input, seedAdapters) => {
148
148
  */
149
149
  export const getConnectionsWithTiming = async (input, seedAdapters) => {
150
150
  const { pg, db, teardown, baseQueryPositional } = await createConnectionsBase(input, seedAdapters);
151
- const query = async (query, variables, commit, reqOptions) => {
151
+ const query = async (queryDoc, variables, commit, reqOptions) => {
152
152
  const start = Date.now();
153
- const result = await baseQueryPositional(query, variables, commit, reqOptions);
153
+ const result = await baseQueryPositional(queryDoc, variables, commit, reqOptions);
154
154
  const duration = Date.now() - start;
155
155
  console.log(`Positional GraphQL query took ${duration}ms`);
156
156
  return result;
@@ -159,6 +159,6 @@ export const getConnectionsWithTiming = async (input, seedAdapters) => {
159
159
  pg,
160
160
  db,
161
161
  teardown,
162
- query
162
+ query,
163
163
  };
164
164
  };
@@ -1,42 +1,73 @@
1
- import { createPostGraphileSchema } from 'postgraphile';
1
+ import { makeSchema } from 'graphile-build';
2
+ import { defaultPreset as graphileBuildDefaultPreset } from 'graphile-build';
3
+ import { defaultPreset as graphileBuildPgDefaultPreset } from 'graphile-build-pg';
4
+ import { makePgService } from 'postgraphile/adaptors/pg';
2
5
  import { runGraphQLInContext } from './context';
6
+ /**
7
+ * Minimal preset that provides core functionality without Node/Relay.
8
+ * This matches the pattern from graphile-settings.
9
+ */
10
+ const MinimalPreset = {
11
+ extends: [graphileBuildDefaultPreset, graphileBuildPgDefaultPreset],
12
+ disablePlugins: ['NodePlugin'],
13
+ };
14
+ /**
15
+ * Creates a GraphQL test context using PostGraphile v5 preset-based API.
16
+ *
17
+ * @param input - Configuration including schemas and optional preset
18
+ * @param conn - Database connection result from pgsql-test
19
+ * @returns GraphQL test context with setup, teardown, and query functions
20
+ */
3
21
  export const GraphQLTest = (input, conn) => {
4
- const { schemas, authRole, graphile } = input;
22
+ const { schemas, authRole, preset: userPreset } = input;
5
23
  let schema;
6
- let options;
24
+ let resolvedPreset;
25
+ let pgService;
7
26
  const pgPool = conn.manager.getPool(conn.pg.config);
8
27
  const setup = async () => {
9
- // Bare-bones configuration - no defaults, only use what's explicitly provided
10
- // This gives full control over PostGraphile configuration
11
- options = {
12
- schema: schemas,
13
- // Only apply graphile options if explicitly provided
14
- ...(graphile?.appendPlugins && {
15
- appendPlugins: graphile.appendPlugins
16
- }),
17
- ...(graphile?.graphileBuildOptions && {
18
- graphileBuildOptions: graphile.graphileBuildOptions
19
- }),
20
- // Apply any overrideSettings if provided
21
- ...(graphile?.overrideSettings || {})
28
+ // Create the pgService - this will be used for withPgClient
29
+ pgService = makePgService({
30
+ pool: pgPool,
31
+ schemas,
32
+ });
33
+ // Build the complete preset by extending the minimal preset
34
+ // with user-provided preset configuration
35
+ const completePreset = {
36
+ extends: [
37
+ MinimalPreset,
38
+ ...(userPreset?.extends ?? []),
39
+ ],
40
+ ...(userPreset?.disablePlugins && { disablePlugins: userPreset.disablePlugins }),
41
+ ...(userPreset?.plugins && { plugins: userPreset.plugins }),
42
+ ...(userPreset?.schema && { schema: userPreset.schema }),
43
+ ...(userPreset?.grafast && { grafast: userPreset.grafast }),
44
+ pgServices: [pgService],
22
45
  };
23
- schema = await createPostGraphileSchema(pgPool, schemas, options);
46
+ // Use makeSchema from graphile-build to create the schema
47
+ const result = await makeSchema(completePreset);
48
+ schema = result.schema;
49
+ resolvedPreset = result.resolvedPreset;
50
+ };
51
+ const teardown = async () => {
52
+ // Optional cleanup - schema is garbage collected
24
53
  };
25
- const teardown = async () => { };
26
54
  const query = async (opts) => {
27
55
  return await runGraphQLInContext({
28
56
  input,
29
57
  schema,
30
- options,
31
- authRole,
58
+ resolvedPreset,
59
+ authRole: authRole ?? 'anonymous',
32
60
  pgPool,
61
+ pgService,
33
62
  conn,
34
- ...opts
63
+ query: opts.query,
64
+ variables: opts.variables,
65
+ reqOptions: opts.reqOptions,
35
66
  });
36
67
  };
37
68
  return {
38
69
  setup,
39
70
  teardown,
40
- query
71
+ query,
41
72
  };
42
73
  };
package/esm/index.js CHANGED
@@ -1,5 +1,4 @@
1
- export * from './context';
2
- export * from './get-connections';
3
- export * from './graphile-test';
4
- export * from './types';
1
+ export { runGraphQLInContext, setContextOnClient } from './context';
2
+ export { getConnections, getConnectionsObject, getConnectionsObjectUnwrapped, getConnectionsObjectWithLogging, getConnectionsObjectWithTiming, getConnectionsUnwrapped, getConnectionsWithLogging, getConnectionsWithTiming, } from './get-connections';
3
+ export { GraphQLTest } from './graphile-test';
5
4
  export { seed, snapshot } from 'pgsql-test';
@@ -31,7 +31,7 @@ const createConnectionsBase = async (input, seedAdapters) => {
31
31
  teardown,
32
32
  baseQuery,
33
33
  baseQueryPositional,
34
- gqlContext
34
+ gqlContext,
35
35
  };
36
36
  };
37
37
  // ============================================================================
@@ -47,7 +47,7 @@ const getConnectionsObject = async (input, seedAdapters) => {
47
47
  db,
48
48
  teardown,
49
49
  query: baseQuery,
50
- gqlContext
50
+ gqlContext,
51
51
  };
52
52
  };
53
53
  exports.getConnectionsObject = getConnectionsObject;
@@ -61,7 +61,7 @@ const getConnectionsObjectUnwrapped = async (input, seedAdapters) => {
61
61
  pg,
62
62
  db,
63
63
  teardown,
64
- query
64
+ query,
65
65
  };
66
66
  };
67
67
  exports.getConnectionsObjectUnwrapped = getConnectionsObjectUnwrapped;
@@ -80,7 +80,7 @@ const getConnectionsObjectWithLogging = async (input, seedAdapters) => {
80
80
  pg,
81
81
  db,
82
82
  teardown,
83
- query
83
+ query,
84
84
  };
85
85
  };
86
86
  exports.getConnectionsObjectWithLogging = getConnectionsObjectWithLogging;
@@ -100,7 +100,7 @@ const getConnectionsObjectWithTiming = async (input, seedAdapters) => {
100
100
  pg,
101
101
  db,
102
102
  teardown,
103
- query
103
+ query,
104
104
  };
105
105
  };
106
106
  exports.getConnectionsObjectWithTiming = getConnectionsObjectWithTiming;
@@ -116,7 +116,7 @@ const getConnections = async (input, seedAdapters) => {
116
116
  pg,
117
117
  db,
118
118
  teardown,
119
- query: baseQueryPositional
119
+ query: baseQueryPositional,
120
120
  };
121
121
  };
122
122
  exports.getConnections = getConnections;
@@ -125,12 +125,12 @@ exports.getConnections = getConnections;
125
125
  */
126
126
  const getConnectionsUnwrapped = async (input, seedAdapters) => {
127
127
  const { pg, db, teardown, baseQueryPositional } = await createConnectionsBase(input, seedAdapters);
128
- const query = async (query, variables, commit, reqOptions) => unwrap(await baseQueryPositional(query, variables, commit, reqOptions));
128
+ const query = async (queryDoc, variables, commit, reqOptions) => unwrap(await baseQueryPositional(queryDoc, variables, commit, reqOptions));
129
129
  return {
130
130
  pg,
131
131
  db,
132
132
  teardown,
133
- query
133
+ query,
134
134
  };
135
135
  };
136
136
  exports.getConnectionsUnwrapped = getConnectionsUnwrapped;
@@ -139,9 +139,9 @@ exports.getConnectionsUnwrapped = getConnectionsUnwrapped;
139
139
  */
140
140
  const getConnectionsWithLogging = async (input, seedAdapters) => {
141
141
  const { pg, db, teardown, baseQueryPositional } = await createConnectionsBase(input, seedAdapters);
142
- const query = async (query, variables, commit, reqOptions) => {
143
- console.log('Executing positional GraphQL query:', query);
144
- const result = await baseQueryPositional(query, variables, commit, reqOptions);
142
+ const query = async (queryDoc, variables, commit, reqOptions) => {
143
+ console.log('Executing positional GraphQL query:', queryDoc);
144
+ const result = await baseQueryPositional(queryDoc, variables, commit, reqOptions);
145
145
  console.log('GraphQL result:', result);
146
146
  return result;
147
147
  };
@@ -149,7 +149,7 @@ const getConnectionsWithLogging = async (input, seedAdapters) => {
149
149
  pg,
150
150
  db,
151
151
  teardown,
152
- query
152
+ query,
153
153
  };
154
154
  };
155
155
  exports.getConnectionsWithLogging = getConnectionsWithLogging;
@@ -158,9 +158,9 @@ exports.getConnectionsWithLogging = getConnectionsWithLogging;
158
158
  */
159
159
  const getConnectionsWithTiming = async (input, seedAdapters) => {
160
160
  const { pg, db, teardown, baseQueryPositional } = await createConnectionsBase(input, seedAdapters);
161
- const query = async (query, variables, commit, reqOptions) => {
161
+ const query = async (queryDoc, variables, commit, reqOptions) => {
162
162
  const start = Date.now();
163
- const result = await baseQueryPositional(query, variables, commit, reqOptions);
163
+ const result = await baseQueryPositional(queryDoc, variables, commit, reqOptions);
164
164
  const duration = Date.now() - start;
165
165
  console.log(`Positional GraphQL query took ${duration}ms`);
166
166
  return result;
@@ -169,7 +169,7 @@ const getConnectionsWithTiming = async (input, seedAdapters) => {
169
169
  pg,
170
170
  db,
171
171
  teardown,
172
- query
172
+ query,
173
173
  };
174
174
  };
175
175
  exports.getConnectionsWithTiming = getConnectionsWithTiming;
@@ -1,4 +1,10 @@
1
- import { GetConnectionOpts, GetConnectionResult } from 'pgsql-test';
2
- import type { GraphQLTestContext } from './types';
3
- import { GetConnectionsInput } from './types';
1
+ import type { GetConnectionOpts, GetConnectionResult } from 'pgsql-test';
2
+ import type { GraphQLTestContext, GetConnectionsInput } from './types';
3
+ /**
4
+ * Creates a GraphQL test context using PostGraphile v5 preset-based API.
5
+ *
6
+ * @param input - Configuration including schemas and optional preset
7
+ * @param conn - Database connection result from pgsql-test
8
+ * @returns GraphQL test context with setup, teardown, and query functions
9
+ */
4
10
  export declare const GraphQLTest: (input: GetConnectionsInput & GetConnectionOpts, conn: GetConnectionResult) => GraphQLTestContext;
package/graphile-test.js CHANGED
@@ -1,46 +1,77 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.GraphQLTest = void 0;
4
- const postgraphile_1 = require("postgraphile");
4
+ const graphile_build_1 = require("graphile-build");
5
+ const graphile_build_2 = require("graphile-build");
6
+ const graphile_build_pg_1 = require("graphile-build-pg");
7
+ const pg_1 = require("postgraphile/adaptors/pg");
5
8
  const context_1 = require("./context");
9
+ /**
10
+ * Minimal preset that provides core functionality without Node/Relay.
11
+ * This matches the pattern from graphile-settings.
12
+ */
13
+ const MinimalPreset = {
14
+ extends: [graphile_build_2.defaultPreset, graphile_build_pg_1.defaultPreset],
15
+ disablePlugins: ['NodePlugin'],
16
+ };
17
+ /**
18
+ * Creates a GraphQL test context using PostGraphile v5 preset-based API.
19
+ *
20
+ * @param input - Configuration including schemas and optional preset
21
+ * @param conn - Database connection result from pgsql-test
22
+ * @returns GraphQL test context with setup, teardown, and query functions
23
+ */
6
24
  const GraphQLTest = (input, conn) => {
7
- const { schemas, authRole, graphile } = input;
25
+ const { schemas, authRole, preset: userPreset } = input;
8
26
  let schema;
9
- let options;
27
+ let resolvedPreset;
28
+ let pgService;
10
29
  const pgPool = conn.manager.getPool(conn.pg.config);
11
30
  const setup = async () => {
12
- // Bare-bones configuration - no defaults, only use what's explicitly provided
13
- // This gives full control over PostGraphile configuration
14
- options = {
15
- schema: schemas,
16
- // Only apply graphile options if explicitly provided
17
- ...(graphile?.appendPlugins && {
18
- appendPlugins: graphile.appendPlugins
19
- }),
20
- ...(graphile?.graphileBuildOptions && {
21
- graphileBuildOptions: graphile.graphileBuildOptions
22
- }),
23
- // Apply any overrideSettings if provided
24
- ...(graphile?.overrideSettings || {})
31
+ // Create the pgService - this will be used for withPgClient
32
+ pgService = (0, pg_1.makePgService)({
33
+ pool: pgPool,
34
+ schemas,
35
+ });
36
+ // Build the complete preset by extending the minimal preset
37
+ // with user-provided preset configuration
38
+ const completePreset = {
39
+ extends: [
40
+ MinimalPreset,
41
+ ...(userPreset?.extends ?? []),
42
+ ],
43
+ ...(userPreset?.disablePlugins && { disablePlugins: userPreset.disablePlugins }),
44
+ ...(userPreset?.plugins && { plugins: userPreset.plugins }),
45
+ ...(userPreset?.schema && { schema: userPreset.schema }),
46
+ ...(userPreset?.grafast && { grafast: userPreset.grafast }),
47
+ pgServices: [pgService],
25
48
  };
26
- schema = await (0, postgraphile_1.createPostGraphileSchema)(pgPool, schemas, options);
49
+ // Use makeSchema from graphile-build to create the schema
50
+ const result = await (0, graphile_build_1.makeSchema)(completePreset);
51
+ schema = result.schema;
52
+ resolvedPreset = result.resolvedPreset;
53
+ };
54
+ const teardown = async () => {
55
+ // Optional cleanup - schema is garbage collected
27
56
  };
28
- const teardown = async () => { };
29
57
  const query = async (opts) => {
30
58
  return await (0, context_1.runGraphQLInContext)({
31
59
  input,
32
60
  schema,
33
- options,
34
- authRole,
61
+ resolvedPreset,
62
+ authRole: authRole ?? 'anonymous',
35
63
  pgPool,
64
+ pgService,
36
65
  conn,
37
- ...opts
66
+ query: opts.query,
67
+ variables: opts.variables,
68
+ reqOptions: opts.reqOptions,
38
69
  });
39
70
  };
40
71
  return {
41
72
  setup,
42
73
  teardown,
43
- query
74
+ query,
44
75
  };
45
76
  };
46
77
  exports.GraphQLTest = GraphQLTest;
package/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- export * from './context';
2
- export * from './get-connections';
3
- export * from './graphile-test';
4
- export * from './types';
1
+ export { runGraphQLInContext, setContextOnClient } from './context';
2
+ export { getConnections, getConnectionsObject, getConnectionsObjectUnwrapped, getConnectionsObjectWithLogging, getConnectionsObjectWithTiming, getConnectionsUnwrapped, getConnectionsWithLogging, getConnectionsWithTiming, } from './get-connections';
3
+ export { GraphQLTest } from './graphile-test';
4
+ export type { GetConnectionsInput, GraphQLQueryFn, GraphQLQueryFnObj, GraphQLQueryOptions, GraphQLQueryUnwrappedFn, GraphQLQueryUnwrappedFnObj, GraphQLResponse, GraphQLTestContext, LegacyGraphileOptions, Variables, } from './types';
5
5
  export { seed, snapshot } from 'pgsql-test';
package/index.js CHANGED
@@ -1,24 +1,20 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
- for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
- };
16
2
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.snapshot = exports.seed = void 0;
18
- __exportStar(require("./context"), exports);
19
- __exportStar(require("./get-connections"), exports);
20
- __exportStar(require("./graphile-test"), exports);
21
- __exportStar(require("./types"), exports);
3
+ exports.snapshot = exports.seed = exports.GraphQLTest = exports.getConnectionsWithTiming = exports.getConnectionsWithLogging = exports.getConnectionsUnwrapped = exports.getConnectionsObjectWithTiming = exports.getConnectionsObjectWithLogging = exports.getConnectionsObjectUnwrapped = exports.getConnectionsObject = exports.getConnections = exports.setContextOnClient = exports.runGraphQLInContext = void 0;
4
+ var context_1 = require("./context");
5
+ Object.defineProperty(exports, "runGraphQLInContext", { enumerable: true, get: function () { return context_1.runGraphQLInContext; } });
6
+ Object.defineProperty(exports, "setContextOnClient", { enumerable: true, get: function () { return context_1.setContextOnClient; } });
7
+ var get_connections_1 = require("./get-connections");
8
+ Object.defineProperty(exports, "getConnections", { enumerable: true, get: function () { return get_connections_1.getConnections; } });
9
+ Object.defineProperty(exports, "getConnectionsObject", { enumerable: true, get: function () { return get_connections_1.getConnectionsObject; } });
10
+ Object.defineProperty(exports, "getConnectionsObjectUnwrapped", { enumerable: true, get: function () { return get_connections_1.getConnectionsObjectUnwrapped; } });
11
+ Object.defineProperty(exports, "getConnectionsObjectWithLogging", { enumerable: true, get: function () { return get_connections_1.getConnectionsObjectWithLogging; } });
12
+ Object.defineProperty(exports, "getConnectionsObjectWithTiming", { enumerable: true, get: function () { return get_connections_1.getConnectionsObjectWithTiming; } });
13
+ Object.defineProperty(exports, "getConnectionsUnwrapped", { enumerable: true, get: function () { return get_connections_1.getConnectionsUnwrapped; } });
14
+ Object.defineProperty(exports, "getConnectionsWithLogging", { enumerable: true, get: function () { return get_connections_1.getConnectionsWithLogging; } });
15
+ Object.defineProperty(exports, "getConnectionsWithTiming", { enumerable: true, get: function () { return get_connections_1.getConnectionsWithTiming; } });
16
+ var graphile_test_1 = require("./graphile-test");
17
+ Object.defineProperty(exports, "GraphQLTest", { enumerable: true, get: function () { return graphile_test_1.GraphQLTest; } });
22
18
  var pgsql_test_1 = require("pgsql-test");
23
19
  Object.defineProperty(exports, "seed", { enumerable: true, get: function () { return pgsql_test_1.seed; } });
24
20
  Object.defineProperty(exports, "snapshot", { enumerable: true, get: function () { return pgsql_test_1.snapshot; } });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "graphile-test",
3
- "version": "3.1.1",
3
+ "version": "4.1.0",
4
4
  "author": "Constructive <developers@constructive.io>",
5
5
  "description": "PostGraphile Testing",
6
6
  "main": "index.js",
@@ -25,23 +25,27 @@
25
25
  "build": "makage build",
26
26
  "build:dev": "makage build --dev",
27
27
  "lint": "eslint . --fix",
28
- "test": "jest --passWithNoTests",
28
+ "test": "jest",
29
29
  "test:watch": "jest --watch"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@types/pg": "^8.16.0",
33
33
  "graphql-tag": "2.12.6",
34
- "makage": "^0.1.12"
34
+ "makage": "^0.1.10"
35
35
  },
36
36
  "dependencies": {
37
- "@constructive-io/graphql-env": "^2.10.0",
38
- "@constructive-io/graphql-types": "^2.15.0",
37
+ "@constructive-io/graphql-env": "^3.1.0",
38
+ "@constructive-io/graphql-types": "^3.0.0",
39
39
  "@pgpmjs/types": "^2.16.0",
40
- "graphql": "15.10.1",
40
+ "grafast": "^1.0.0-rc.4",
41
+ "graphile-build": "^5.0.0-rc.3",
42
+ "graphile-build-pg": "^5.0.0-rc.3",
43
+ "graphile-config": "1.0.0-rc.3",
44
+ "graphql": "^16.9.0",
41
45
  "mock-req": "^0.2.0",
42
46
  "pg": "^8.17.1",
43
- "pgsql-test": "^3.1.1",
44
- "postgraphile": "^4.14.1"
47
+ "pgsql-test": "^4.1.0",
48
+ "postgraphile": "^5.0.0-rc.4"
45
49
  },
46
50
  "keywords": [
47
51
  "testing",
@@ -51,5 +55,5 @@
51
55
  "pgpm",
52
56
  "test"
53
57
  ],
54
- "gitHead": "49049ad3ddd762d35625f657cb42fa0862b829a0"
58
+ "gitHead": "6dac0247c675de94b41038ac1e5095dc5df0c753"
55
59
  }
package/types.d.ts CHANGED
@@ -1,33 +1,81 @@
1
- import type { GraphileOptions } from '@constructive-io/graphql-types';
2
- import { DocumentNode, GraphQLError } from 'graphql';
3
- export interface GraphQLQueryOptions<TVariables = Record<string, any>> {
1
+ import type { GraphileConfig } from 'graphile-config';
2
+ import type { DocumentNode, GraphQLError } from 'graphql';
3
+ /**
4
+ * Variables type that accepts plain objects and interfaces without requiring
5
+ * an explicit index signature. Using `Record<string, any>` allows TypeScript
6
+ * to accept typed interfaces like { username: string } since `any` is bi-directionally
7
+ * assignable to all types.
8
+ */
9
+ export type Variables = Record<string, any>;
10
+ export interface GraphQLQueryOptions<TVariables extends Variables = Variables> {
4
11
  query: string | DocumentNode;
5
12
  variables?: TVariables;
6
13
  commit?: boolean;
7
- reqOptions?: Record<string, any>;
14
+ reqOptions?: Record<string, unknown>;
8
15
  }
9
16
  export interface GraphQLTestContext {
10
17
  setup: () => Promise<void>;
11
18
  teardown: () => Promise<void>;
12
- query: <TResult = any, TVariables = Record<string, any>>(opts: GraphQLQueryOptions<TVariables>) => Promise<TResult>;
19
+ query: <TResult = unknown, TVariables extends Variables = Variables>(opts: GraphQLQueryOptions<TVariables>) => Promise<TResult>;
13
20
  }
21
+ /**
22
+ * Legacy v4-style GraphQL options for backward compatibility.
23
+ *
24
+ * @deprecated Use `preset` instead for v5 configuration.
25
+ */
26
+ export interface LegacyGraphileOptions {
27
+ /**
28
+ * V4-style plugins to append.
29
+ * These plugins use the builder.hook() API which is NOT compatible with v5.
30
+ * For v5, convert these to proper v5 plugins and use the `preset` option instead.
31
+ *
32
+ * @deprecated Use preset.plugins for v5 plugins
33
+ */
34
+ appendPlugins?: any[];
35
+ /**
36
+ * V4-style graphile build options.
37
+ *
38
+ * @deprecated Use preset.schema for v5 schema options
39
+ */
40
+ graphileBuildOptions?: Record<string, any>;
41
+ /**
42
+ * V4-style PostGraphile options override.
43
+ *
44
+ * @deprecated Use preset for v5 configuration
45
+ */
46
+ overrideSettings?: Record<string, any>;
47
+ }
48
+ /**
49
+ * Input for GraphQL test connections.
50
+ *
51
+ * Supports both v5 preset-based configuration (recommended) and
52
+ * legacy v4-style configuration (deprecated, for backward compatibility).
53
+ */
14
54
  export interface GetConnectionsInput {
15
55
  useRoot?: boolean;
16
56
  schemas: string[];
17
57
  authRole?: string;
18
- graphile?: GraphileOptions;
19
- }
20
- export interface GraphQLQueryOptions<TVariables = Record<string, any>> {
21
- query: string | DocumentNode;
22
- variables?: TVariables;
23
- commit?: boolean;
24
- reqOptions?: Record<string, any>;
58
+ /**
59
+ * V5 preset configuration (recommended).
60
+ * Can include extends, plugins, schema options, etc.
61
+ */
62
+ preset?: GraphileConfig.Preset;
63
+ /**
64
+ * Legacy v4-style graphile options for backward compatibility.
65
+ *
66
+ * NOTE: v4-style plugins (using builder.hook()) are NOT compatible with v5.
67
+ * If you use appendPlugins with v4 plugins, they will be ignored.
68
+ * Convert your plugins to v5 format and use the `preset` option instead.
69
+ *
70
+ * @deprecated Use preset for v5 configuration
71
+ */
72
+ graphile?: LegacyGraphileOptions;
25
73
  }
26
74
  export interface GraphQLResponse<T> {
27
75
  data?: T;
28
76
  errors?: readonly GraphQLError[];
29
77
  }
30
- export type GraphQLQueryFnObj = <TResult = any, TVariables = Record<string, any>>(opts: GraphQLQueryOptions<TVariables>) => Promise<GraphQLResponse<TResult>>;
31
- export type GraphQLQueryFn = <TResult = any, TVariables = Record<string, any>>(query: string | DocumentNode, variables?: TVariables, commit?: boolean, reqOptions?: Record<string, any>) => Promise<GraphQLResponse<TResult>>;
32
- export type GraphQLQueryUnwrappedFnObj = <TResult = any, TVariables = Record<string, any>>(opts: GraphQLQueryOptions<TVariables>) => Promise<TResult>;
33
- export type GraphQLQueryUnwrappedFn = <TResult = any, TVariables = Record<string, any>>(query: string | DocumentNode, variables?: TVariables, commit?: boolean, reqOptions?: Record<string, any>) => Promise<TResult>;
78
+ export type GraphQLQueryFnObj = <TResult = unknown, TVariables extends Variables = Variables>(opts: GraphQLQueryOptions<TVariables>) => Promise<GraphQLResponse<TResult>>;
79
+ export type GraphQLQueryFn = <TResult = unknown, TVariables extends Variables = Variables>(query: string | DocumentNode, variables?: TVariables, commit?: boolean, reqOptions?: Record<string, unknown>) => Promise<GraphQLResponse<TResult>>;
80
+ export type GraphQLQueryUnwrappedFnObj = <TResult = unknown, TVariables extends Variables = Variables>(opts: GraphQLQueryOptions<TVariables>) => Promise<TResult>;
81
+ export type GraphQLQueryUnwrappedFn = <TResult = unknown, TVariables extends Variables = Variables>(query: string | DocumentNode, variables?: TVariables, commit?: boolean, reqOptions?: Record<string, unknown>) => Promise<TResult>;