@constructive-io/graphql-test 4.11.8 → 4.12.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.
@@ -0,0 +1,34 @@
1
+ import type { GraphQLQueryFn, GraphQLQueryFnObj } from 'graphile-test';
2
+ import type { Table } from '@constructive-io/graphql-query';
3
+ export interface CodegenResult {
4
+ /** Factory function to create an ORM client from a GraphQLAdapter */
5
+ createClient: (config: any) => Record<string, any>;
6
+ /** Inferred table metadata from introspection */
7
+ tables: Table[];
8
+ /** Raw codegen output (generated files) */
9
+ ormResult: {
10
+ files: {
11
+ path: string;
12
+ content: string;
13
+ }[];
14
+ };
15
+ }
16
+ /**
17
+ * Run the codegen pipeline against a live graphile-test schema and load the result.
18
+ *
19
+ * Each test suite must pass a unique `name` to avoid collisions when Jest
20
+ * runs suites in parallel — each gets its own `__generated__/<name>` dir.
21
+ *
22
+ * Accepts either positional-style `GraphQLQueryFn` or object-style `GraphQLQueryFnObj`.
23
+ * The generated files are compiled to JavaScript and loaded via require().
24
+ *
25
+ * IMPORTANT: This must be called inside an active transaction (e.g. in beforeEach
26
+ * after db.beforeEach()), because graphile-test's query wrapper uses savepoints
27
+ * which require a transaction block.
28
+ *
29
+ * @param queryFn - The graphile-test query function (positional or object-style)
30
+ * @param name - Unique suite name for generated file isolation
31
+ * @param outputDir - Optional custom output directory (defaults to __generated__/<name> next to caller)
32
+ * @returns CodegenResult with createClient factory, tables, and raw ormResult
33
+ */
34
+ export declare function runCodegenAndLoad(queryFn: GraphQLQueryFn | GraphQLQueryFnObj, name: string, outputDir?: string): Promise<CodegenResult>;
@@ -0,0 +1,171 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.runCodegenAndLoad = runCodegenAndLoad;
7
+ /**
8
+ * Codegen Helper for Integration Tests
9
+ *
10
+ * Runs the full codegen pipeline against a live graphile-test schema:
11
+ * 1. Executes standard GraphQL introspection
12
+ * 2. Infers Table[] via inferTablesFromIntrospection()
13
+ * 3. Generates ORM files via generateOrm()
14
+ * 4. Compiles TypeScript to JavaScript
15
+ * 5. Loads the compiled createClient factory
16
+ *
17
+ * This allows tests with dynamically-provisioned tables (e.g. app_files,
18
+ * data_room_files) to get fully typed ORM models at test time, rather
19
+ * than relying on the pre-built SDK (which only knows about static tables).
20
+ *
21
+ * Usage:
22
+ * import { runCodegenAndLoad, GraphQLTestAdapter } from '@constructive-io/graphql-test';
23
+ *
24
+ * // In beforeEach (must be inside an active transaction for graphile-test):
25
+ * const codegen = await runCodegenAndLoad(query, 'my-suite');
26
+ * const adapter = new GraphQLTestAdapter(query);
27
+ * const orm = codegen.createClient({ adapter });
28
+ * const rows = await orm.myTable.findMany({ select: { id: true } }).execute();
29
+ */
30
+ const fs_1 = __importDefault(require("fs"));
31
+ const path_1 = __importDefault(require("path"));
32
+ const typescript_1 = __importDefault(require("typescript"));
33
+ const graphql_query_1 = require("@constructive-io/graphql-query");
34
+ const orm_1 = require("@constructive-io/graphql-codegen/core/codegen/orm");
35
+ /**
36
+ * Run the codegen pipeline against a live graphile-test schema and load the result.
37
+ *
38
+ * Each test suite must pass a unique `name` to avoid collisions when Jest
39
+ * runs suites in parallel — each gets its own `__generated__/<name>` dir.
40
+ *
41
+ * Accepts either positional-style `GraphQLQueryFn` or object-style `GraphQLQueryFnObj`.
42
+ * The generated files are compiled to JavaScript and loaded via require().
43
+ *
44
+ * IMPORTANT: This must be called inside an active transaction (e.g. in beforeEach
45
+ * after db.beforeEach()), because graphile-test's query wrapper uses savepoints
46
+ * which require a transaction block.
47
+ *
48
+ * @param queryFn - The graphile-test query function (positional or object-style)
49
+ * @param name - Unique suite name for generated file isolation
50
+ * @param outputDir - Optional custom output directory (defaults to __generated__/<name> next to caller)
51
+ * @returns CodegenResult with createClient factory, tables, and raw ormResult
52
+ */
53
+ async function runCodegenAndLoad(queryFn, name, outputDir) {
54
+ const GENERATED_DIR = outputDir ?? path_1.default.join(process.cwd(), '__generated__', name);
55
+ // 1. Run introspection against live schema
56
+ // Support both positional (query, variables) and object-style ({ query }) signatures
57
+ const introspectionResult = await runIntrospection(queryFn);
58
+ if (introspectionResult.errors?.length) {
59
+ throw new Error(`Introspection failed: ${introspectionResult.errors.map((e) => e.message).join('; ')}`);
60
+ }
61
+ const introspection = introspectionResult.data;
62
+ // 2. Infer tables from introspection (core of the codegen pipeline)
63
+ const tables = (0, graphql_query_1.inferTablesFromIntrospection)(introspection);
64
+ // 3. Get type registry for input type generation
65
+ const { typeRegistry } = (0, graphql_query_1.transformSchemaToOperations)(introspection);
66
+ // 4. Generate ORM files
67
+ const ormResult = (0, orm_1.generateOrm)({
68
+ tables,
69
+ customOperations: { queries: [], mutations: [], typeRegistry },
70
+ config: {
71
+ codegen: { comments: true, condition: true },
72
+ },
73
+ });
74
+ // 5. Write generated files to disk
75
+ ensureCleanDir(GENERATED_DIR);
76
+ for (const file of ormResult.files) {
77
+ const filePath = path_1.default.join(GENERATED_DIR, file.path);
78
+ fs_1.default.mkdirSync(path_1.default.dirname(filePath), { recursive: true });
79
+ fs_1.default.writeFileSync(filePath, file.content);
80
+ }
81
+ // 6. Compile TypeScript to JavaScript, then remove .ts sources so
82
+ // Jest's TypeScript transformer doesn't try to type-check them.
83
+ compileGeneratedFiles(GENERATED_DIR);
84
+ // 7. Load the compiled createClient
85
+ const indexPath = path_1.default.join(GENERATED_DIR, 'index.js');
86
+ clearRequireCache(GENERATED_DIR);
87
+ const orm = require(indexPath);
88
+ return {
89
+ createClient: orm.createClient,
90
+ tables,
91
+ ormResult,
92
+ };
93
+ }
94
+ // ---------------------------------------------------------------------------
95
+ // Internal helpers
96
+ // ---------------------------------------------------------------------------
97
+ /**
98
+ * Run introspection, handling both positional and object-style query functions.
99
+ *
100
+ * GraphQLQueryFn (positional): (query, variables?, commit?, reqOptions?) => Promise
101
+ * GraphQLQueryFnObj (object): ({ query, variables, ... }) => Promise
102
+ *
103
+ * We call positional-style since graphile-test's underlying implementation
104
+ * accepts both — the positional wrapper in @constructive-io/graphql-test
105
+ * normalizes to the object form internally.
106
+ */
107
+ async function runIntrospection(queryFn) {
108
+ // Detect signature: object-style functions typically have arity 1
109
+ // while positional have arity >= 1 (query, variables, commit, reqOptions)
110
+ // However, the most reliable way is to check if the first arg would be
111
+ // a string (positional) or object (object-style).
112
+ // Since we can't know at compile time, we support both at runtime.
113
+ const isObjectStyle = queryFn.length <= 1;
114
+ let result;
115
+ if (isObjectStyle) {
116
+ result = await queryFn({
117
+ query: graphql_query_1.SCHEMA_INTROSPECTION_QUERY,
118
+ });
119
+ }
120
+ else {
121
+ result = await queryFn(graphql_query_1.SCHEMA_INTROSPECTION_QUERY);
122
+ }
123
+ return result;
124
+ }
125
+ function ensureCleanDir(dir) {
126
+ if (fs_1.default.existsSync(dir)) {
127
+ fs_1.default.rmSync(dir, { recursive: true, force: true });
128
+ }
129
+ fs_1.default.mkdirSync(dir, { recursive: true });
130
+ }
131
+ function compileGeneratedFiles(dir) {
132
+ const tsFiles = collectTsFiles(dir);
133
+ for (const filePath of tsFiles) {
134
+ const source = fs_1.default.readFileSync(filePath, 'utf-8');
135
+ const result = typescript_1.default.transpileModule(source, {
136
+ compilerOptions: {
137
+ module: typescript_1.default.ModuleKind.CommonJS,
138
+ target: typescript_1.default.ScriptTarget.ES2020,
139
+ esModuleInterop: true,
140
+ strict: false,
141
+ declaration: false,
142
+ skipLibCheck: true,
143
+ },
144
+ fileName: filePath,
145
+ });
146
+ const jsPath = filePath.replace(/\.ts$/, '.js');
147
+ fs_1.default.writeFileSync(jsPath, result.outputText);
148
+ // Remove .ts source so Jest's transformer doesn't type-check it
149
+ fs_1.default.unlinkSync(filePath);
150
+ }
151
+ }
152
+ function collectTsFiles(dir) {
153
+ const files = [];
154
+ for (const entry of fs_1.default.readdirSync(dir, { withFileTypes: true })) {
155
+ const fullPath = path_1.default.join(dir, entry.name);
156
+ if (entry.isDirectory()) {
157
+ files.push(...collectTsFiles(fullPath));
158
+ }
159
+ else if (entry.name.endsWith('.ts')) {
160
+ files.push(fullPath);
161
+ }
162
+ }
163
+ return files;
164
+ }
165
+ function clearRequireCache(dir) {
166
+ for (const key of Object.keys(require.cache)) {
167
+ if (key.startsWith(dir)) {
168
+ delete require.cache[key];
169
+ }
170
+ }
171
+ }
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Codegen Helper for Integration Tests
3
+ *
4
+ * Runs the full codegen pipeline against a live graphile-test schema:
5
+ * 1. Executes standard GraphQL introspection
6
+ * 2. Infers Table[] via inferTablesFromIntrospection()
7
+ * 3. Generates ORM files via generateOrm()
8
+ * 4. Compiles TypeScript to JavaScript
9
+ * 5. Loads the compiled createClient factory
10
+ *
11
+ * This allows tests with dynamically-provisioned tables (e.g. app_files,
12
+ * data_room_files) to get fully typed ORM models at test time, rather
13
+ * than relying on the pre-built SDK (which only knows about static tables).
14
+ *
15
+ * Usage:
16
+ * import { runCodegenAndLoad, GraphQLTestAdapter } from '@constructive-io/graphql-test';
17
+ *
18
+ * // In beforeEach (must be inside an active transaction for graphile-test):
19
+ * const codegen = await runCodegenAndLoad(query, 'my-suite');
20
+ * const adapter = new GraphQLTestAdapter(query);
21
+ * const orm = codegen.createClient({ adapter });
22
+ * const rows = await orm.myTable.findMany({ select: { id: true } }).execute();
23
+ */
24
+ import fs from 'fs';
25
+ import path from 'path';
26
+ import ts from 'typescript';
27
+ import { SCHEMA_INTROSPECTION_QUERY, inferTablesFromIntrospection, transformSchemaToOperations, } from '@constructive-io/graphql-query';
28
+ import { generateOrm } from '@constructive-io/graphql-codegen/core/codegen/orm';
29
+ /**
30
+ * Run the codegen pipeline against a live graphile-test schema and load the result.
31
+ *
32
+ * Each test suite must pass a unique `name` to avoid collisions when Jest
33
+ * runs suites in parallel — each gets its own `__generated__/<name>` dir.
34
+ *
35
+ * Accepts either positional-style `GraphQLQueryFn` or object-style `GraphQLQueryFnObj`.
36
+ * The generated files are compiled to JavaScript and loaded via require().
37
+ *
38
+ * IMPORTANT: This must be called inside an active transaction (e.g. in beforeEach
39
+ * after db.beforeEach()), because graphile-test's query wrapper uses savepoints
40
+ * which require a transaction block.
41
+ *
42
+ * @param queryFn - The graphile-test query function (positional or object-style)
43
+ * @param name - Unique suite name for generated file isolation
44
+ * @param outputDir - Optional custom output directory (defaults to __generated__/<name> next to caller)
45
+ * @returns CodegenResult with createClient factory, tables, and raw ormResult
46
+ */
47
+ export async function runCodegenAndLoad(queryFn, name, outputDir) {
48
+ const GENERATED_DIR = outputDir ?? path.join(process.cwd(), '__generated__', name);
49
+ // 1. Run introspection against live schema
50
+ // Support both positional (query, variables) and object-style ({ query }) signatures
51
+ const introspectionResult = await runIntrospection(queryFn);
52
+ if (introspectionResult.errors?.length) {
53
+ throw new Error(`Introspection failed: ${introspectionResult.errors.map((e) => e.message).join('; ')}`);
54
+ }
55
+ const introspection = introspectionResult.data;
56
+ // 2. Infer tables from introspection (core of the codegen pipeline)
57
+ const tables = inferTablesFromIntrospection(introspection);
58
+ // 3. Get type registry for input type generation
59
+ const { typeRegistry } = transformSchemaToOperations(introspection);
60
+ // 4. Generate ORM files
61
+ const ormResult = generateOrm({
62
+ tables,
63
+ customOperations: { queries: [], mutations: [], typeRegistry },
64
+ config: {
65
+ codegen: { comments: true, condition: true },
66
+ },
67
+ });
68
+ // 5. Write generated files to disk
69
+ ensureCleanDir(GENERATED_DIR);
70
+ for (const file of ormResult.files) {
71
+ const filePath = path.join(GENERATED_DIR, file.path);
72
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
73
+ fs.writeFileSync(filePath, file.content);
74
+ }
75
+ // 6. Compile TypeScript to JavaScript, then remove .ts sources so
76
+ // Jest's TypeScript transformer doesn't try to type-check them.
77
+ compileGeneratedFiles(GENERATED_DIR);
78
+ // 7. Load the compiled createClient
79
+ const indexPath = path.join(GENERATED_DIR, 'index.js');
80
+ clearRequireCache(GENERATED_DIR);
81
+ const orm = require(indexPath);
82
+ return {
83
+ createClient: orm.createClient,
84
+ tables,
85
+ ormResult,
86
+ };
87
+ }
88
+ // ---------------------------------------------------------------------------
89
+ // Internal helpers
90
+ // ---------------------------------------------------------------------------
91
+ /**
92
+ * Run introspection, handling both positional and object-style query functions.
93
+ *
94
+ * GraphQLQueryFn (positional): (query, variables?, commit?, reqOptions?) => Promise
95
+ * GraphQLQueryFnObj (object): ({ query, variables, ... }) => Promise
96
+ *
97
+ * We call positional-style since graphile-test's underlying implementation
98
+ * accepts both — the positional wrapper in @constructive-io/graphql-test
99
+ * normalizes to the object form internally.
100
+ */
101
+ async function runIntrospection(queryFn) {
102
+ // Detect signature: object-style functions typically have arity 1
103
+ // while positional have arity >= 1 (query, variables, commit, reqOptions)
104
+ // However, the most reliable way is to check if the first arg would be
105
+ // a string (positional) or object (object-style).
106
+ // Since we can't know at compile time, we support both at runtime.
107
+ const isObjectStyle = queryFn.length <= 1;
108
+ let result;
109
+ if (isObjectStyle) {
110
+ result = await queryFn({
111
+ query: SCHEMA_INTROSPECTION_QUERY,
112
+ });
113
+ }
114
+ else {
115
+ result = await queryFn(SCHEMA_INTROSPECTION_QUERY);
116
+ }
117
+ return result;
118
+ }
119
+ function ensureCleanDir(dir) {
120
+ if (fs.existsSync(dir)) {
121
+ fs.rmSync(dir, { recursive: true, force: true });
122
+ }
123
+ fs.mkdirSync(dir, { recursive: true });
124
+ }
125
+ function compileGeneratedFiles(dir) {
126
+ const tsFiles = collectTsFiles(dir);
127
+ for (const filePath of tsFiles) {
128
+ const source = fs.readFileSync(filePath, 'utf-8');
129
+ const result = ts.transpileModule(source, {
130
+ compilerOptions: {
131
+ module: ts.ModuleKind.CommonJS,
132
+ target: ts.ScriptTarget.ES2020,
133
+ esModuleInterop: true,
134
+ strict: false,
135
+ declaration: false,
136
+ skipLibCheck: true,
137
+ },
138
+ fileName: filePath,
139
+ });
140
+ const jsPath = filePath.replace(/\.ts$/, '.js');
141
+ fs.writeFileSync(jsPath, result.outputText);
142
+ // Remove .ts source so Jest's transformer doesn't type-check it
143
+ fs.unlinkSync(filePath);
144
+ }
145
+ }
146
+ function collectTsFiles(dir) {
147
+ const files = [];
148
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
149
+ const fullPath = path.join(dir, entry.name);
150
+ if (entry.isDirectory()) {
151
+ files.push(...collectTsFiles(fullPath));
152
+ }
153
+ else if (entry.name.endsWith('.ts')) {
154
+ files.push(fullPath);
155
+ }
156
+ }
157
+ return files;
158
+ }
159
+ function clearRequireCache(dir) {
160
+ for (const key of Object.keys(require.cache)) {
161
+ if (key.startsWith(dir)) {
162
+ delete require.cache[key];
163
+ }
164
+ }
165
+ }
package/esm/index.js CHANGED
@@ -2,5 +2,10 @@
2
2
  export { GraphQLTest } from './graphile-test';
3
3
  export * from './get-connections';
4
4
  export { seed, snapshot } from 'pgsql-test';
5
+ // Re-export low-level DB connection utilities for advanced two-phase patterns
6
+ // (e.g. provision first, then build GraphQL schema over dynamic tables).
7
+ export { getConnections as getDbConnections } from 'pgsql-test';
5
8
  // Export GraphQL test adapter for SDK integration
6
9
  export { GraphQLTestAdapter } from './adapter';
10
+ // Export codegen-at-test-time helper for dynamic table ORM generation
11
+ export { runCodegenAndLoad } from './codegen-helper';
package/index.d.ts CHANGED
@@ -2,4 +2,9 @@ export { type GraphQLQueryOptions, type GraphQLTestContext, type GetConnectionsI
2
2
  export { GraphQLTest } from './graphile-test';
3
3
  export * from './get-connections';
4
4
  export { seed, snapshot } from 'pgsql-test';
5
+ export { getConnections as getDbConnections } from 'pgsql-test';
6
+ export type { GetConnectionResult, GetConnectionOpts } from 'pgsql-test';
7
+ export type { PgTestClient } from 'pgsql-test/test-client';
5
8
  export { GraphQLTestAdapter } from './adapter';
9
+ export { runCodegenAndLoad } from './codegen-helper';
10
+ export type { CodegenResult } from './codegen-helper';
package/index.js CHANGED
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.GraphQLTestAdapter = exports.snapshot = exports.seed = exports.GraphQLTest = void 0;
17
+ exports.runCodegenAndLoad = exports.GraphQLTestAdapter = exports.getDbConnections = exports.snapshot = exports.seed = exports.GraphQLTest = void 0;
18
18
  // Override with our custom implementations that use graphile-settings
19
19
  var graphile_test_1 = require("./graphile-test");
20
20
  Object.defineProperty(exports, "GraphQLTest", { enumerable: true, get: function () { return graphile_test_1.GraphQLTest; } });
@@ -22,6 +22,13 @@ __exportStar(require("./get-connections"), exports);
22
22
  var pgsql_test_1 = require("pgsql-test");
23
23
  Object.defineProperty(exports, "seed", { enumerable: true, get: function () { return pgsql_test_1.seed; } });
24
24
  Object.defineProperty(exports, "snapshot", { enumerable: true, get: function () { return pgsql_test_1.snapshot; } });
25
+ // Re-export low-level DB connection utilities for advanced two-phase patterns
26
+ // (e.g. provision first, then build GraphQL schema over dynamic tables).
27
+ var pgsql_test_2 = require("pgsql-test");
28
+ Object.defineProperty(exports, "getDbConnections", { enumerable: true, get: function () { return pgsql_test_2.getConnections; } });
25
29
  // Export GraphQL test adapter for SDK integration
26
30
  var adapter_1 = require("./adapter");
27
31
  Object.defineProperty(exports, "GraphQLTestAdapter", { enumerable: true, get: function () { return adapter_1.GraphQLTestAdapter; } });
32
+ // Export codegen-at-test-time helper for dynamic table ORM generation
33
+ var codegen_helper_1 = require("./codegen-helper");
34
+ Object.defineProperty(exports, "runCodegenAndLoad", { enumerable: true, get: function () { return codegen_helper_1.runCodegenAndLoad; } });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@constructive-io/graphql-test",
3
- "version": "4.11.8",
3
+ "version": "4.12.1",
4
4
  "author": "Constructive <developers@constructive.io>",
5
5
  "description": "Constructive GraphQL Testing with all plugins loaded",
6
6
  "main": "index.js",
@@ -34,20 +34,23 @@
34
34
  "makage": "^0.3.0"
35
35
  },
36
36
  "dependencies": {
37
+ "@constructive-io/graphql-codegen": "^4.31.1",
37
38
  "@constructive-io/graphql-env": "^3.6.1",
39
+ "@constructive-io/graphql-query": "^3.15.1",
38
40
  "@constructive-io/graphql-types": "^3.5.1",
39
41
  "@pgpmjs/types": "^2.22.0",
40
42
  "grafast": "1.0.0",
41
43
  "graphile-build": "5.0.0",
42
44
  "graphile-build-pg": "5.0.0",
43
45
  "graphile-config": "1.0.0",
44
- "graphile-settings": "^4.21.8",
45
- "graphile-test": "^4.9.3",
46
+ "graphile-settings": "^4.22.1",
47
+ "graphile-test": "^4.9.4",
46
48
  "graphql": "16.13.0",
47
49
  "mock-req": "^0.2.0",
48
50
  "pg": "^8.20.0",
49
- "pgsql-test": "^4.9.3",
50
- "postgraphile": "5.0.0"
51
+ "pgsql-test": "^4.9.4",
52
+ "postgraphile": "5.0.0",
53
+ "typescript": "^5.0.0"
51
54
  },
52
55
  "keywords": [
53
56
  "testing",
@@ -56,5 +59,5 @@
56
59
  "constructive",
57
60
  "test"
58
61
  ],
59
- "gitHead": "a6424a875afd997535ca6b1636542a5158991003"
62
+ "gitHead": "90b15540015a927e45846923843fc1af36f5bed7"
60
63
  }