Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
be959f7
feat(codegen): add CLI command generator from GraphQL schema
pyramation Feb 17, 2026
922c718
test(codegen): add CLI generator snapshot tests
pyramation Feb 17, 2026
fde91f9
feat(codegen): generate README.md and COMMANDS.md for CLI
pyramation Feb 17, 2026
1fe5255
feat(codegen): add configurable docs generation (README, AGENTS.md, M…
pyramation Feb 17, 2026
0fb16ea
feat(codegen): add docs generation for ORM, React Query, and multi-ta…
pyramation Feb 17, 2026
0b15004
feat(codegen): add unified multi-target CLI architecture
pyramation Feb 17, 2026
5288230
feat(codegen): add multi-target CLI docs generation (README, AGENTS.m…
pyramation Feb 17, 2026
1721182
refactor(codegen): rename infraNames to builtinNames
pyramation Feb 17, 2026
ef638ad
fix(codegen): export generateMulti and fix multi-target executor impo…
pyramation Feb 18, 2026
d05e763
feat(codegen): add shared PGPM ephemeral DB, multi-target cnc codegen…
pyramation Feb 18, 2026
b1396bc
feat(codegen): apiNames auto-expansion to multi-target, --schema-only…
pyramation Feb 18, 2026
6f1eaad
fix(cli): update tests for removed get-graphql-schema and new expandA…
pyramation Feb 18, 2026
5a14718
fix(cli): add explicit type annotations to test mock for strict TS
pyramation Feb 18, 2026
ddcc81d
refactor(codegen): remove deprecations, unify camelizeArgv, clean up …
pyramation Feb 18, 2026
44097ee
feat(codegen): add schemaDir multi-target expansion, unit tests, unif…
pyramation Feb 18, 2026
4825be3
fix(cli): fix spread argument type error in codegen test mock
pyramation Feb 18, 2026
f7c988a
fix(cli): type mock function parameters to fix TS2554 in CI
pyramation Feb 18, 2026
1fef68d
ci: trigger CI run for latest changes
pyramation Feb 18, 2026
4587027
feat: add graphile-schema package, README headers/footers, and --sche…
pyramation Feb 19, 2026
bb1d268
docs: update stale references to get-graphql-schema, add graphile-sch…
pyramation Feb 19, 2026
c519f05
chore(server): remove dead codegen scripts, tsconfig excludes, rimraf…
pyramation Feb 19, 2026
761c274
refactor(server): remove backward-compat schema.ts re-export
pyramation Feb 19, 2026
c61e917
chore: remove --passWithNoTests from 41 packages that have tests
pyramation Feb 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion graphile/graphile-authz/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"build": "makage build",
"build:dev": "makage build --dev",
"lint": "eslint . --fix",
"test": "jest --passWithNoTests",
"test": "jest",
"test:watch": "jest --watch"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion graphile/graphile-query/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"build": "makage build",
"build:dev": "makage build --dev",
"lint": "eslint . --fix",
"test": "jest --passWithNoTests",
"test": "jest",
"test:watch": "jest --watch"
},
"dependencies": {
Expand Down
97 changes: 97 additions & 0 deletions graphile/graphile-schema/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# graphile-schema

<p align="center" width="100%">
<img height="250" src="https://raw.githubusercontent.com/constructive-io/constructive/refs/heads/main/assets/outline-logo.svg" />
</p>

<p align="center" width="100%">
<a href="https://github.com/constructive-io/constructive/actions/workflows/run-tests.yaml">
<img height="20" src="https://github.com/constructive-io/constructive/actions/workflows/run-tests.yaml/badge.svg" />
</a>
<a href="https://github.com/constructive-io/constructive/blob/main/LICENSE">
<img height="20" src="https://img.shields.io/badge/license-MIT-blue.svg"/>
</a>
<a href="https://www.npmjs.com/package/graphile-schema">
<img height="20" src="https://img.shields.io/github/package-json/v/constructive-io/constructive?filename=graphile%2Fgraphile-schema%2Fpackage.json"/>
</a>
</p>

Lightweight GraphQL SDL builder for PostgreSQL using PostGraphile v5. Build schemas directly from a database or fetch them from a running GraphQL endpoint — no server dependencies required.

## Installation

```bash
npm install graphile-schema
```

## Usage

### Build SDL from a PostgreSQL Database

```typescript
import { buildSchemaSDL } from 'graphile-schema';

const sdl = await buildSchemaSDL({
database: 'mydb',
schemas: ['app_public'],
});

console.log(sdl);
```

### Fetch SDL from a GraphQL Endpoint

```typescript
import { fetchEndpointSchemaSDL } from 'graphile-schema';

const sdl = await fetchEndpointSchemaSDL('https://api.example.com/graphql', {
auth: 'Bearer my-token',
headers: { 'X-Custom-Header': 'value' },
});

console.log(sdl);
```

### With Custom Graphile Presets

```typescript
import { buildSchemaSDL } from 'graphile-schema';

const sdl = await buildSchemaSDL({
database: 'mydb',
schemas: ['app_public', 'app_private'],
graphile: {
extends: [MyCustomPreset],
schema: { pgSimplifyPatch: false },
},
});
```

## API

### `buildSchemaSDL(opts)`

Builds a GraphQL SDL string directly from a PostgreSQL database using PostGraphile v5 introspection.

| Option | Type | Description |
|--------|------|-------------|
| `database` | `string` | Database name (default: `'constructive'`) |
| `schemas` | `string[]` | PostgreSQL schemas to introspect |
| `graphile` | `Partial<GraphileConfig.Preset>` | Optional Graphile preset overrides |

### `fetchEndpointSchemaSDL(endpoint, opts?)`

Fetches a GraphQL SDL string from a running GraphQL endpoint via introspection query.

| Option | Type | Description |
|--------|------|-------------|
| `endpoint` | `string` | GraphQL endpoint URL |
| `opts.headerHost` | `string` | Override the `Host` header |
| `opts.auth` | `string` | `Authorization` header value |
| `opts.headers` | `Record<string, string>` | Additional request headers |

## Disclaimer

AS DESCRIBED IN THE LICENSES, THE SOFTWARE IS PROVIDED "AS IS", AT YOUR OWN RISK, AND WITHOUT WARRANTIES OF ANY KIND.

No developer or entity involved in creating this software will be liable for any claims or damages whatsoever associated with your use, inability to use, or your interaction with other users of the code, including any direct, indirect, incidental, special, exemplary, punitive or consequential damages, or loss of profits, cryptocurrencies, tokens, or anything else of value.
109 changes: 109 additions & 0 deletions graphile/graphile-schema/__tests__/fetch-endpoint-schema.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import * as http from 'node:http';
import { getIntrospectionQuery, buildSchema, introspectionFromSchema } from 'graphql';

import { fetchEndpointSchemaSDL } from '../src/fetch-endpoint-schema';

const TEST_SDL = `
type Query {
hello: String
version: Int
}
`;

function createMockServer(handler: (req: http.IncomingMessage, res: http.ServerResponse) => void): Promise<{ server: http.Server; port: number }> {
return new Promise((resolve) => {
const server = http.createServer(handler);
server.listen(0, '127.0.0.1', () => {
const addr = server.address() as { port: number };
resolve({ server, port: addr.port });
});
});
}

function introspectionHandler(_req: http.IncomingMessage, res: http.ServerResponse) {
const schema = buildSchema(TEST_SDL);
const introspection = introspectionFromSchema(schema);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ data: introspection }));
}

describe('fetchEndpointSchemaSDL', () => {
let server: http.Server;
let port: number;

beforeAll(async () => {
({ server, port } = await createMockServer(introspectionHandler));
});

afterAll(() => {
server.close();
});

it('fetches and returns SDL from a live endpoint', async () => {
const sdl = await fetchEndpointSchemaSDL(`http://127.0.0.1:${port}/graphql`);

expect(sdl).toContain('type Query');
expect(sdl).toContain('hello');
expect(sdl).toContain('version');
});

it('throws on HTTP error responses', async () => {
const { server: errServer, port: errPort } = await createMockServer((_req, res) => {
res.writeHead(500);
res.end('Internal Server Error');
});

await expect(
fetchEndpointSchemaSDL(`http://127.0.0.1:${errPort}/graphql`),
).rejects.toThrow('HTTP 500');

errServer.close();
});

it('throws on invalid JSON response', async () => {
const { server: badServer, port: badPort } = await createMockServer((_req, res) => {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end('<html>not json</html>');
});

await expect(
fetchEndpointSchemaSDL(`http://127.0.0.1:${badPort}/graphql`),
).rejects.toThrow('Failed to parse response');

badServer.close();
});

it('throws when introspection returns errors', async () => {
const { server: errServer, port: errPort } = await createMockServer((_req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ errors: [{ message: 'Not allowed' }] }));
});

await expect(
fetchEndpointSchemaSDL(`http://127.0.0.1:${errPort}/graphql`),
).rejects.toThrow('Introspection returned errors');

errServer.close();
});

it('passes custom headers to the endpoint', async () => {
let receivedHeaders: http.IncomingHttpHeaders = {};

const { server: headerServer, port: headerPort } = await createMockServer((req, res) => {
receivedHeaders = req.headers;
introspectionHandler(req, res);
});

await fetchEndpointSchemaSDL(`http://127.0.0.1:${headerPort}/graphql`, {
auth: 'Bearer test-token',
headerHost: 'custom.host.io',
headers: { 'X-Custom': 'value123' },
});

expect(receivedHeaders['authorization']).toBe('Bearer test-token');
expect(receivedHeaders['host']).toBe('custom.host.io');
expect(receivedHeaders['x-custom']).toBe('value123');

headerServer.close();
});
});
18 changes: 18 additions & 0 deletions graphile/graphile-schema/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
transform: {
'^.+\\.tsx?$': [
'ts-jest',
{
babelConfig: false,
tsconfig: 'tsconfig.json',
},
],
},
transformIgnorePatterns: [`/node_modules/*`],
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$',
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
modulePathIgnorePatterns: ['dist/*']
};
53 changes: 53 additions & 0 deletions graphile/graphile-schema/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"name": "graphile-schema",
"version": "1.0.0",
"author": "Constructive <developers@constructive.io>",
"description": "Build GraphQL SDL from PostgreSQL databases using PostGraphile v5",
"main": "index.js",
"module": "esm/index.js",
"types": "index.d.ts",
"homepage": "https://github.com/constructive-io/constructive",
"license": "MIT",
"publishConfig": {
"access": "public",
"directory": "dist"
},
"repository": {
"type": "git",
"url": "https://github.com/constructive-io/constructive"
},
"bugs": {
"url": "https://github.com/constructive-io/constructive/issues"
},
"scripts": {
"clean": "makage clean",
"prepack": "npm run build",
"build": "makage build",
"build:dev": "makage build --dev",
"lint": "eslint . --fix",
"test": "jest",
"test:watch": "jest --watch"
},
"dependencies": {
"deepmerge": "^4.3.1",
"graphile-build": "^5.0.0-rc.3",
"graphile-config": "1.0.0-rc.3",
"graphile-settings": "workspace:^",
"graphql": "^16.9.0",
"pg-cache": "workspace:^",
"pg-env": "workspace:^"
},
"devDependencies": {
"makage": "^0.1.10",
"ts-node": "^10.9.2"
},
"keywords": [
"graphile",
"schema",
"graphql",
"sdl",
"postgraphile",
"introspection",
"constructive"
]
}
44 changes: 44 additions & 0 deletions graphile/graphile-schema/src/build-schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import deepmerge from 'deepmerge'
import { printSchema } from 'graphql'
import { ConstructivePreset, makePgService } from 'graphile-settings'
import { makeSchema } from 'graphile-build'
import { buildConnectionString } from 'pg-cache'
import { getPgEnvOptions } from 'pg-env'
import type { GraphileConfig } from 'graphile-config'

export type BuildSchemaOptions = {
database?: string;
schemas: string[];
graphile?: Partial<GraphileConfig.Preset>;
};

export async function buildSchemaSDL(opts: BuildSchemaOptions): Promise<string> {
const database = opts.database ?? 'constructive'
const schemas = Array.isArray(opts.schemas) ? opts.schemas : []

const config = getPgEnvOptions({ database })
const connectionString = buildConnectionString(
config.user,
config.password,
config.host,
config.port,
config.database,
)

const basePreset: GraphileConfig.Preset = {
extends: [ConstructivePreset],
pgServices: [
makePgService({
connectionString,
schemas,
}),
],
}

const preset: GraphileConfig.Preset = opts.graphile
? deepmerge(basePreset, opts.graphile)
: basePreset

const { schema } = await makeSchema(preset)
return printSchema(schema)
}
Loading