@supacloud/compiler 0.13.0 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -0
- package/dist/cli.js +8 -9
- package/dist/graphql-client.d.ts +1 -1
- package/dist/index.js +7 -9
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -72,6 +72,41 @@ Consumers of that optional file must install `@graphql-typed-document-node/core`
|
|
|
72
72
|
it provides `ResultOf` and `VariablesOf` for operation-level inference. The default
|
|
73
73
|
fetch client still needs no GraphQL runtime dependency.
|
|
74
74
|
|
|
75
|
+
Both outputs use the operation plugin as the single owner of referenced enums
|
|
76
|
+
and input objects. Regenerate both files after upgrading the compiler; do not
|
|
77
|
+
deduplicate declarations by editing generated files. Scalar mappings are applied
|
|
78
|
+
directly to operation/input fields, and types unused by queries are not emitted.
|
|
79
|
+
|
|
80
|
+
Consumer acceptance tests cover both SDK and TypedDocumentNode output:
|
|
81
|
+
|
|
82
|
+
```gherkin
|
|
83
|
+
Scenario: Shared enum types
|
|
84
|
+
Given multiple queries share an enum in variables and selected fields
|
|
85
|
+
When the compiler generates the client artifacts
|
|
86
|
+
Then each artifact declares the enum once and passes strict TypeScript checks
|
|
87
|
+
|
|
88
|
+
Scenario: Nested input objects
|
|
89
|
+
Given recursive input objects contain enum lists, defaults and custom scalars
|
|
90
|
+
When the compiler generates the client artifacts
|
|
91
|
+
Then input declarations are unique and valid variables retain their types
|
|
92
|
+
|
|
93
|
+
Scenario: Invalid consumer code
|
|
94
|
+
Given generated query contracts
|
|
95
|
+
When a consumer supplies invalid variables or reads an unselected field
|
|
96
|
+
Then TypeScript rejects the consumer code
|
|
97
|
+
|
|
98
|
+
Scenario: Release package acceptance
|
|
99
|
+
Given an installed compiler package
|
|
100
|
+
When its CLI runs the same consumer acceptance suite
|
|
101
|
+
Then both artifact formats pass without editing generated files
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Run `bun test src/graphql-package.test.ts` from this package. To test an installed
|
|
105
|
+
tarball or registry release with the same suite, set
|
|
106
|
+
`SUPACLOUD_COMPILER_TEST_CLI` to its absolute `dist/cli.js` path.
|
|
107
|
+
`bun run test:package` checks the built CLI and runs automatically after the build
|
|
108
|
+
in `prepublishOnly`, blocking publication when generated consumer types fail.
|
|
109
|
+
|
|
75
110
|
```ts
|
|
76
111
|
import { createGraphqlClient } from "./generated/graphql";
|
|
77
112
|
const queries = createGraphqlClient({
|
package/dist/cli.js
CHANGED
|
@@ -1226,7 +1226,7 @@ export function createGraphqlClient(options: GraphqlClientOptions) {
|
|
|
1226
1226
|
method: "POST",
|
|
1227
1227
|
headers,
|
|
1228
1228
|
body: JSON.stringify({ query, variables }),
|
|
1229
|
-
signal: request
|
|
1229
|
+
...(request?.signal ? { signal: request.signal } : {}),
|
|
1230
1230
|
redirect: "error",
|
|
1231
1231
|
});
|
|
1232
1232
|
if (!response.ok) {
|
|
@@ -1350,7 +1350,6 @@ import {
|
|
|
1350
1350
|
validateSchema
|
|
1351
1351
|
} from "graphql";
|
|
1352
1352
|
import { codegen } from "@graphql-codegen/core";
|
|
1353
|
-
import * as typescript from "@graphql-codegen/typescript";
|
|
1354
1353
|
import * as operations from "@graphql-codegen/typescript-operations";
|
|
1355
1354
|
async function renderGraphql(options) {
|
|
1356
1355
|
const result = { diagnostics: [], files: {} };
|
|
@@ -1456,9 +1455,8 @@ async function renderGraphql(options) {
|
|
|
1456
1455
|
try {
|
|
1457
1456
|
const config = {
|
|
1458
1457
|
useTypeImports: true,
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
onlyOperationTypes: true,
|
|
1458
|
+
nonOptionalTypename: false,
|
|
1459
|
+
enumType: "string-literal",
|
|
1462
1460
|
namingConvention: "keep",
|
|
1463
1461
|
dedupeOperationSuffix: false,
|
|
1464
1462
|
omitOperationSuffix: false,
|
|
@@ -1471,8 +1469,8 @@ async function renderGraphql(options) {
|
|
|
1471
1469
|
schemaAst: schema,
|
|
1472
1470
|
documents,
|
|
1473
1471
|
config,
|
|
1474
|
-
plugins: [{
|
|
1475
|
-
pluginMap: {
|
|
1472
|
+
plugins: [{ operations: {} }],
|
|
1473
|
+
pluginMap: { operations }
|
|
1476
1474
|
});
|
|
1477
1475
|
const separated = separateOperations(combined);
|
|
1478
1476
|
const methods = Object.entries(separated).sort(([a], [b]) => a.localeCompare(b)).map(([name, document]) => {
|
|
@@ -1504,8 +1502,8 @@ ${methods.join(`,
|
|
|
1504
1502
|
schemaAst: schema,
|
|
1505
1503
|
documents,
|
|
1506
1504
|
config,
|
|
1507
|
-
plugins: [{
|
|
1508
|
-
pluginMap: {
|
|
1505
|
+
plugins: [{ operations: {} }, { typedDocuments: {} }],
|
|
1506
|
+
pluginMap: { operations, typedDocuments }
|
|
1509
1507
|
});
|
|
1510
1508
|
}
|
|
1511
1509
|
result.files["graphql.manifest.json"] = JSON.stringify({
|
|
@@ -1576,6 +1574,7 @@ async function pullGraphqlSchema(options) {
|
|
|
1576
1574
|
const content = path.endsWith(".json") ? JSON.stringify(introspectionFromSchema(schema), null, 2) + `
|
|
1577
1575
|
` : `# GENERATED BY supacloud-compiler graphql-schema. DO NOT EDIT.
|
|
1578
1576
|
# Database First: change database declarations, apply migrations, then re-export for the intended role.
|
|
1577
|
+
# Database First: change database declarations, apply migrations, then re-export for the intended role.
|
|
1579
1578
|
` + printSchema2(schema) + `
|
|
1580
1579
|
`;
|
|
1581
1580
|
let previous;
|
package/dist/graphql-client.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Appended to the generated SDK; browser consumers need only the platform fetch API. */
|
|
2
|
-
export declare const GRAPHQL_CLIENT_SOURCE = "\nexport interface GraphqlClientOptions {\n /** Project base URL, not the management API URL. HTTPS is required outside loopback. */\n url: string;\n /** Public project key only. Never put a service-role or management key in a browser. */\n publishableKey?: string;\n /** Resolved on every request so session refresh and logout are observed. */\n getAccessToken?: () => string | null | undefined | Promise<string | null | undefined>;\n fetch?: (url: string, init: RequestInit) => Promise<Response>;\n}\n\nexport interface GraphqlRequestOptions {\n signal?: AbortSignal;\n}\n\nexport class GraphqlRequestError extends Error {\n constructor(\n public readonly code: \"http\" | \"graphql\" | \"invalid-response\",\n message: string,\n public readonly status?: number,\n public readonly errors?: readonly unknown[],\n ) {\n super(message);\n this.name = \"GraphqlRequestError\";\n }\n}\n\nexport function createGraphqlClient(options: GraphqlClientOptions) {\n const endpoint = new URL(options.url);\n const loopback = [\"localhost\", \"127.0.0.1\", \"[::1]\"].includes(endpoint.hostname);\n if (endpoint.protocol !== \"https:\" && !(endpoint.protocol === \"http:\" && loopback)) {\n throw new Error(\"GraphQL requires HTTPS outside loopback development.\");\n }\n if (endpoint.username || endpoint.password || endpoint.search || endpoint.hash) {\n throw new Error(\"GraphQL project URLs must not contain credentials, query parameters or fragments.\");\n }\n endpoint.pathname = endpoint.pathname.replace(/\\/$/, \"\") + \"/graphql/v1\";\n const fetcher = options.fetch ?? globalThis.fetch.bind(globalThis);\n return getSdk<GraphqlRequestOptions>(async <R, V>(\n query: string,\n variables?: V,\n request?: GraphqlRequestOptions,\n ): Promise<R> => {\n const headers = new Headers({ \"Content-Type\": \"application/json\", Accept: \"application/json\" });\n if (options.publishableKey) headers.set(\"apikey\", options.publishableKey);\n const token = await options.getAccessToken?.();\n if (token) headers.set(\"Authorization\", \"Bearer \" + token);\n const response = await fetcher(endpoint.toString(), {\n method: \"POST\",\n headers,\n body: JSON.stringify({ query, variables }),\n signal: request
|
|
2
|
+
export declare const GRAPHQL_CLIENT_SOURCE = "\nexport interface GraphqlClientOptions {\n /** Project base URL, not the management API URL. HTTPS is required outside loopback. */\n url: string;\n /** Public project key only. Never put a service-role or management key in a browser. */\n publishableKey?: string;\n /** Resolved on every request so session refresh and logout are observed. */\n getAccessToken?: () => string | null | undefined | Promise<string | null | undefined>;\n fetch?: (url: string, init: RequestInit) => Promise<Response>;\n}\n\nexport interface GraphqlRequestOptions {\n signal?: AbortSignal;\n}\n\nexport class GraphqlRequestError extends Error {\n constructor(\n public readonly code: \"http\" | \"graphql\" | \"invalid-response\",\n message: string,\n public readonly status?: number,\n public readonly errors?: readonly unknown[],\n ) {\n super(message);\n this.name = \"GraphqlRequestError\";\n }\n}\n\nexport function createGraphqlClient(options: GraphqlClientOptions) {\n const endpoint = new URL(options.url);\n const loopback = [\"localhost\", \"127.0.0.1\", \"[::1]\"].includes(endpoint.hostname);\n if (endpoint.protocol !== \"https:\" && !(endpoint.protocol === \"http:\" && loopback)) {\n throw new Error(\"GraphQL requires HTTPS outside loopback development.\");\n }\n if (endpoint.username || endpoint.password || endpoint.search || endpoint.hash) {\n throw new Error(\"GraphQL project URLs must not contain credentials, query parameters or fragments.\");\n }\n endpoint.pathname = endpoint.pathname.replace(/\\/$/, \"\") + \"/graphql/v1\";\n const fetcher = options.fetch ?? globalThis.fetch.bind(globalThis);\n return getSdk<GraphqlRequestOptions>(async <R, V>(\n query: string,\n variables?: V,\n request?: GraphqlRequestOptions,\n ): Promise<R> => {\n const headers = new Headers({ \"Content-Type\": \"application/json\", Accept: \"application/json\" });\n if (options.publishableKey) headers.set(\"apikey\", options.publishableKey);\n const token = await options.getAccessToken?.();\n if (token) headers.set(\"Authorization\", \"Bearer \" + token);\n const response = await fetcher(endpoint.toString(), {\n method: \"POST\",\n headers,\n body: JSON.stringify({ query, variables }),\n ...(request?.signal ? { signal: request.signal } : {}),\n redirect: \"error\",\n });\n if (!response.ok) {\n throw new GraphqlRequestError(\"http\", \"GraphQL HTTP request failed (\" + response.status + \").\", response.status);\n }\n let envelope: unknown;\n try {\n envelope = await response.json();\n } catch {\n throw new GraphqlRequestError(\"invalid-response\", \"GraphQL returned invalid JSON.\", response.status);\n }\n if (!envelope || typeof envelope !== \"object\" || Array.isArray(envelope)) {\n throw new GraphqlRequestError(\"invalid-response\", \"GraphQL returned an invalid envelope.\", response.status);\n }\n if (\"errors\" in envelope) {\n if (!Array.isArray(envelope.errors)) {\n throw new GraphqlRequestError(\"invalid-response\", \"GraphQL returned invalid errors.\", response.status);\n }\n if (envelope.errors.length > 0) {\n throw new GraphqlRequestError(\"graphql\", \"GraphQL query failed.\", response.status, envelope.errors);\n }\n }\n if (!(\"data\" in envelope) || !envelope.data || typeof envelope.data !== \"object\" || Array.isArray(envelope.data)) {\n throw new GraphqlRequestError(\"invalid-response\", \"GraphQL returned no result object.\", response.status);\n }\n // Operation types describe the schema snapshot, not runtime response validation.\n return envelope.data as R;\n });\n}\n";
|
package/dist/index.js
CHANGED
|
@@ -1225,7 +1225,7 @@ export function createGraphqlClient(options: GraphqlClientOptions) {
|
|
|
1225
1225
|
method: "POST",
|
|
1226
1226
|
headers,
|
|
1227
1227
|
body: JSON.stringify({ query, variables }),
|
|
1228
|
-
signal: request
|
|
1228
|
+
...(request?.signal ? { signal: request.signal } : {}),
|
|
1229
1229
|
redirect: "error",
|
|
1230
1230
|
});
|
|
1231
1231
|
if (!response.ok) {
|
|
@@ -1349,7 +1349,6 @@ import {
|
|
|
1349
1349
|
validateSchema
|
|
1350
1350
|
} from "graphql";
|
|
1351
1351
|
import { codegen } from "@graphql-codegen/core";
|
|
1352
|
-
import * as typescript from "@graphql-codegen/typescript";
|
|
1353
1352
|
import * as operations from "@graphql-codegen/typescript-operations";
|
|
1354
1353
|
async function renderGraphql(options) {
|
|
1355
1354
|
const result = { diagnostics: [], files: {} };
|
|
@@ -1455,9 +1454,8 @@ async function renderGraphql(options) {
|
|
|
1455
1454
|
try {
|
|
1456
1455
|
const config = {
|
|
1457
1456
|
useTypeImports: true,
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
onlyOperationTypes: true,
|
|
1457
|
+
nonOptionalTypename: false,
|
|
1458
|
+
enumType: "string-literal",
|
|
1461
1459
|
namingConvention: "keep",
|
|
1462
1460
|
dedupeOperationSuffix: false,
|
|
1463
1461
|
omitOperationSuffix: false,
|
|
@@ -1470,8 +1468,8 @@ async function renderGraphql(options) {
|
|
|
1470
1468
|
schemaAst: schema,
|
|
1471
1469
|
documents,
|
|
1472
1470
|
config,
|
|
1473
|
-
plugins: [{
|
|
1474
|
-
pluginMap: {
|
|
1471
|
+
plugins: [{ operations: {} }],
|
|
1472
|
+
pluginMap: { operations }
|
|
1475
1473
|
});
|
|
1476
1474
|
const separated = separateOperations(combined);
|
|
1477
1475
|
const methods = Object.entries(separated).sort(([a], [b]) => a.localeCompare(b)).map(([name, document]) => {
|
|
@@ -1503,8 +1501,8 @@ ${methods.join(`,
|
|
|
1503
1501
|
schemaAst: schema,
|
|
1504
1502
|
documents,
|
|
1505
1503
|
config,
|
|
1506
|
-
plugins: [{
|
|
1507
|
-
pluginMap: {
|
|
1504
|
+
plugins: [{ operations: {} }, { typedDocuments: {} }],
|
|
1505
|
+
pluginMap: { operations, typedDocuments }
|
|
1508
1506
|
});
|
|
1509
1507
|
}
|
|
1510
1508
|
result.files["graphql.manifest.json"] = JSON.stringify({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supacloud/compiler",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.1",
|
|
4
4
|
"description": "Static compiler for @supacloud/app metadata: builds the application graph from AST, validates it, and generates reflection-free factory code",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -25,8 +25,9 @@
|
|
|
25
25
|
"build:js": "bun build src/index.ts src/cli.ts --outdir dist --target node --external @typescript/typescript6 --external graphql --external '@graphql-codegen/*'",
|
|
26
26
|
"build:types": "tsc -p tsconfig.json --emitDeclarationOnly",
|
|
27
27
|
"clean": "rm -rf dist",
|
|
28
|
-
"prepublishOnly": "bun run build",
|
|
28
|
+
"prepublishOnly": "bun run build && bun run test:package",
|
|
29
29
|
"test": "bun test",
|
|
30
|
+
"test:package": "SUPACLOUD_COMPILER_TEST_CLI=\"$PWD/dist/cli.js\" bun test src/graphql-package.test.ts",
|
|
30
31
|
"benchmark": "bun run src/benchmark.ts",
|
|
31
32
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
32
33
|
"typecheck:test": "tsc -p tsconfig.test.json --noEmit"
|
|
@@ -47,7 +48,6 @@
|
|
|
47
48
|
"dependencies": {
|
|
48
49
|
"@graphql-codegen/core": "^6.2.0",
|
|
49
50
|
"@graphql-codegen/typed-document-node": "^7.1.0",
|
|
50
|
-
"@graphql-codegen/typescript": "^6.1.0",
|
|
51
51
|
"@graphql-codegen/typescript-operations": "^6.1.6",
|
|
52
52
|
"@graphql-typed-document-node/core": "^3.2.0",
|
|
53
53
|
"@typescript/typescript6": "^6.0.2",
|