@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 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?.signal,
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
- skipTypename: true,
1460
- enumsAsTypes: true,
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: [{ typescript: {} }, { operations: {} }],
1475
- pluginMap: { typescript, operations }
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: [{ typescript: {} }, { operations: {} }, { typedDocuments: {} }],
1508
- pluginMap: { typescript, operations, typedDocuments }
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;
@@ -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?.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";
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?.signal,
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
- skipTypename: true,
1459
- enumsAsTypes: true,
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: [{ typescript: {} }, { operations: {} }],
1474
- pluginMap: { typescript, operations }
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: [{ typescript: {} }, { operations: {} }, { typedDocuments: {} }],
1507
- pluginMap: { typescript, operations, typedDocuments }
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.0",
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",