Skip to content

Commit 3be2036

Browse files
committed
feat: rewrite orm-test to use codegen pipeline + ORM models
- Add codegen-helper.ts: orchestrates introspection → inferTables → generateOrm → compile → load at test runtime - Add graphile-adapter.ts: wraps graphile-test query() as GraphQLAdapter for ORM client - Rewrite orm-m2n.test.ts: uses generated ORM for findMany/create, raw GraphQL for delete (junction PK not inferred) - Rewrite mega-query.test.ts: uses generated ORM for all 7 ConstructivePreset plugins - Fix vectorEmbedding filter shape: VectorNearbyInput is flat {vector, distance}, not nested {nearby: {embedding}} - Fix BM25 assertions: BM25 returns all rows with scores (0 for non-matches), doesn't filter - Each test suite gets isolated __generated__/<name> directory to avoid parallel collisions - Add @constructive-io/graphql-codegen dependency to orm-test package.json - 48 tests pass (12 M:N + 36 mega query)
1 parent 421b4dc commit 3be2036

7 files changed

Lines changed: 884 additions & 767 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__generated__/
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/**
2+
* Codegen Helper for ORM 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 validates the entire codegen -> runtime chain end-to-end.
12+
*/
13+
import fs from 'fs';
14+
import path from 'path';
15+
import ts from 'typescript';
16+
import type { GraphQLQueryFnObj } from 'graphile-test';
17+
import {
18+
SCHEMA_INTROSPECTION_QUERY,
19+
inferTablesFromIntrospection,
20+
transformSchemaToOperations,
21+
} from '@constructive-io/graphql-query';
22+
import type { Table } from '@constructive-io/graphql-query';
23+
24+
// generateOrm is not re-exported from the public barrel — resolve via dist
25+
const codegenRoot = path.dirname(
26+
require.resolve('@constructive-io/graphql-codegen'),
27+
);
28+
const { generateOrm } = require(
29+
path.join(codegenRoot, 'core/codegen/orm/index.js'),
30+
) as { generateOrm: (opts: any) => { files: { path: string; content: string }[] } };
31+
32+
/**
33+
* Run the codegen pipeline against a live graphile-test schema and load the result.
34+
*
35+
* Each test suite must pass a unique `name` to avoid collisions when Jest
36+
* runs suites in parallel — each gets its own `__generated__/<name>` dir.
37+
*
38+
* Returns createClient factory + inferred tables metadata.
39+
*/
40+
export async function runCodegenAndLoad(
41+
query: GraphQLQueryFnObj,
42+
name: string,
43+
) {
44+
const GENERATED_DIR = path.join(__dirname, '..', '__generated__', name);
45+
// 1. Run introspection against live schema
46+
const introspectionResult = await query<{ __schema: any }>({
47+
query: SCHEMA_INTROSPECTION_QUERY,
48+
});
49+
if (introspectionResult.errors?.length) {
50+
throw new Error(
51+
`Introspection failed: ${introspectionResult.errors.map((e) => e.message).join('; ')}`,
52+
);
53+
}
54+
55+
const introspection = introspectionResult.data!;
56+
57+
// 2. Infer tables from introspection (core of the codegen pipeline)
58+
const tables = inferTablesFromIntrospection(introspection);
59+
60+
// 3. Get type registry for input type generation
61+
const { typeRegistry } = transformSchemaToOperations(introspection);
62+
63+
// 4. Generate ORM files
64+
const ormResult = generateOrm({
65+
tables,
66+
customOperations: { queries: [], mutations: [], typeRegistry },
67+
config: {
68+
codegen: { comments: true, condition: true },
69+
} as any,
70+
});
71+
72+
// 5. Write generated files to disk
73+
ensureCleanDir(GENERATED_DIR);
74+
for (const file of ormResult.files) {
75+
const filePath = path.join(GENERATED_DIR, file.path);
76+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
77+
fs.writeFileSync(filePath, file.content);
78+
}
79+
80+
// 6. Compile TypeScript to JavaScript
81+
compileGeneratedFiles(GENERATED_DIR);
82+
83+
// 7. Load the compiled createClient
84+
const indexPath = path.join(GENERATED_DIR, 'index.js');
85+
clearRequireCache(GENERATED_DIR);
86+
const orm = require(indexPath);
87+
88+
return {
89+
createClient: orm.createClient as (config: any) => Record<string, any>,
90+
tables,
91+
ormResult,
92+
};
93+
}
94+
95+
function ensureCleanDir(dir: string) {
96+
if (fs.existsSync(dir)) {
97+
fs.rmSync(dir, { recursive: true, force: true });
98+
}
99+
fs.mkdirSync(dir, { recursive: true });
100+
}
101+
102+
function compileGeneratedFiles(dir: string) {
103+
const tsFiles = collectTsFiles(dir);
104+
for (const filePath of tsFiles) {
105+
const source = fs.readFileSync(filePath, 'utf-8');
106+
const result = ts.transpileModule(source, {
107+
compilerOptions: {
108+
module: ts.ModuleKind.CommonJS,
109+
target: ts.ScriptTarget.ES2020,
110+
esModuleInterop: true,
111+
strict: false,
112+
declaration: false,
113+
skipLibCheck: true,
114+
},
115+
fileName: filePath,
116+
});
117+
const jsPath = filePath.replace(/\.ts$/, '.js');
118+
fs.writeFileSync(jsPath, result.outputText);
119+
}
120+
}
121+
122+
function collectTsFiles(dir: string): string[] {
123+
const files: string[] = [];
124+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
125+
const fullPath = path.join(dir, entry.name);
126+
if (entry.isDirectory()) {
127+
files.push(...collectTsFiles(fullPath));
128+
} else if (entry.name.endsWith('.ts')) {
129+
files.push(fullPath);
130+
}
131+
}
132+
return files;
133+
}
134+
135+
function clearRequireCache(dir: string) {
136+
for (const key of Object.keys(require.cache)) {
137+
if (key.startsWith(dir)) {
138+
delete require.cache[key];
139+
}
140+
}
141+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* GraphQL Adapter for graphile-test
3+
*
4+
* Implements the ORM's GraphQLAdapter interface by wrapping
5+
* graphile-test's query() function. This allows generated ORM
6+
* models to execute queries against a real PostGraphile schema
7+
* running on a live PostgreSQL database.
8+
*/
9+
import type { GraphQLQueryFnObj } from 'graphile-test';
10+
import type {
11+
GraphQLAdapter,
12+
GraphQLError,
13+
QueryResult,
14+
} from '@constructive-io/graphql-types';
15+
16+
export class GraphileTestAdapter implements GraphQLAdapter {
17+
constructor(private queryFn: GraphQLQueryFnObj) {}
18+
19+
async execute<T>(
20+
document: string,
21+
variables?: Record<string, unknown>,
22+
): Promise<QueryResult<T>> {
23+
const result = await this.queryFn<T>({ query: document, variables });
24+
25+
if (result.errors && result.errors.length > 0) {
26+
return {
27+
ok: false,
28+
data: null,
29+
errors: result.errors as unknown as GraphQLError[],
30+
};
31+
}
32+
33+
return {
34+
ok: true,
35+
data: result.data as T,
36+
errors: undefined,
37+
};
38+
}
39+
}

0 commit comments

Comments
 (0)