diff --git a/graphql/codegen/src/core/codegen/cli/executor-generator.ts b/graphql/codegen/src/core/codegen/cli/executor-generator.ts index 50cfeb007..a4cc2e0ed 100644 --- a/graphql/codegen/src/core/codegen/cli/executor-generator.ts +++ b/graphql/codegen/src/core/codegen/cli/executor-generator.ts @@ -30,21 +30,9 @@ function createImportDeclaration( return decl; } -export interface ExecutorOptions { - /** Enable NodeHttpAdapter for *.localhost subdomain routing */ - nodeHttpAdapter?: boolean; -} - -export function generateExecutorFile(toolName: string, options?: ExecutorOptions): GeneratedFile { +export function generateExecutorFile(toolName: string): GeneratedFile { const statements: t.Statement[] = []; - // Import NodeHttpAdapter for *.localhost subdomain routing - if (options?.nodeHttpAdapter) { - statements.push( - createImportDeclaration('./node-fetch', ['NodeHttpAdapter']), - ); - } - statements.push( createImportDeclaration('appstash', ['createConfigStore']), ); @@ -215,39 +203,20 @@ export function generateExecutorFile(toolName: string, options?: ExecutorOptions ]), ), - // Build createClient config — use NodeHttpAdapter for *.localhost endpoints - ...(options?.nodeHttpAdapter - ? [ - t.returnStatement( - t.callExpression(t.identifier('createClient'), [ - t.objectExpression([ - t.objectProperty( - t.identifier('adapter'), - t.newExpression(t.identifier('NodeHttpAdapter'), [ - t.memberExpression(t.identifier('ctx'), t.identifier('endpoint')), - t.identifier('headers'), - ]), - ), - ]), - ]), + t.returnStatement( + t.callExpression(t.identifier('createClient'), [ + t.objectExpression([ + t.objectProperty( + t.identifier('endpoint'), + t.memberExpression(t.identifier('ctx'), t.identifier('endpoint')), ), - ] - : [ - t.returnStatement( - t.callExpression(t.identifier('createClient'), [ - t.objectExpression([ - t.objectProperty( - t.identifier('endpoint'), - t.memberExpression(t.identifier('ctx'), t.identifier('endpoint')), - ), - t.objectProperty( - t.identifier('headers'), - t.identifier('headers'), - ), - ]), - ]), + t.objectProperty( + t.identifier('headers'), + t.identifier('headers'), ), ]), + ]), + ), ]); const getClientFunc = t.functionDeclaration( @@ -269,17 +238,9 @@ export function generateExecutorFile(toolName: string, options?: ExecutorOptions export function generateMultiTargetExecutorFile( toolName: string, targets: MultiTargetExecutorInput[], - options?: ExecutorOptions, ): GeneratedFile { const statements: t.Statement[] = []; - // Import NodeHttpAdapter for *.localhost subdomain routing - if (options?.nodeHttpAdapter) { - statements.push( - createImportDeclaration('./node-fetch', ['NodeHttpAdapter']), - ); - } - statements.push( createImportDeclaration('appstash', ['createConfigStore']), ); @@ -538,33 +499,14 @@ export function generateMultiTargetExecutorFile( ), ]), ), - // Build createClient config — use NodeHttpAdapter for *.localhost endpoints - ...(options?.nodeHttpAdapter - ? [ - t.returnStatement( - t.callExpression(t.identifier('createFn'), [ - t.objectExpression([ - t.objectProperty( - t.identifier('adapter'), - t.newExpression(t.identifier('NodeHttpAdapter'), [ - t.identifier('endpoint'), - t.identifier('headers'), - ]), - ), - ]), - ]), - ), - ] - : [ - t.returnStatement( - t.callExpression(t.identifier('createFn'), [ - t.objectExpression([ - t.objectProperty(t.identifier('endpoint'), t.identifier('endpoint')), - t.objectProperty(t.identifier('headers'), t.identifier('headers')), - ]), - ]), - ), + t.returnStatement( + t.callExpression(t.identifier('createFn'), [ + t.objectExpression([ + t.objectProperty(t.identifier('endpoint'), t.identifier('endpoint')), + t.objectProperty(t.identifier('headers'), t.identifier('headers')), ]), + ]), + ), ]); const getClientFunc = t.functionDeclaration( diff --git a/graphql/codegen/src/core/codegen/cli/index.ts b/graphql/codegen/src/core/codegen/cli/index.ts index 9e107db1a..fdd48989e 100644 --- a/graphql/codegen/src/core/codegen/cli/index.ts +++ b/graphql/codegen/src/core/codegen/cli/index.ts @@ -14,7 +14,7 @@ import { generateMultiTargetContextCommand, } from './infra-generator'; import { generateTableCommand } from './table-command-generator'; -import { generateUtilsFile, generateNodeFetchFile, generateEntryPointFile, generateEmbedderFile } from './utils-generator'; +import { generateUtilsFile, generateEntryPointFile, generateEmbedderFile } from './utils-generator'; export interface GenerateCliOptions { tables: Table[]; @@ -48,12 +48,7 @@ export function generateCli(options: GenerateCliOptions): GenerateCliResult { ? cliConfig.toolName : 'app'; - // Use top-level nodeHttpAdapter from config (auto-enabled for CLI by generate.ts) - const useNodeHttpAdapter = !!config.nodeHttpAdapter; - - const executorFile = generateExecutorFile(toolName, { - nodeHttpAdapter: useNodeHttpAdapter, - }); + const executorFile = generateExecutorFile(toolName); files.push(executorFile); const utilsFile = generateUtilsFile(); @@ -67,11 +62,6 @@ export function generateCli(options: GenerateCliOptions): GenerateCliResult { files.push(generateEmbedderFile()); } - // Generate node HTTP adapter if configured (for *.localhost subdomain routing) - if (useNodeHttpAdapter) { - files.push(generateNodeFetchFile()); - } - const contextFile = generateContextCommand(toolName); files.push(contextFile); @@ -139,8 +129,6 @@ export interface GenerateMultiTargetCliOptions { toolName: string; builtinNames?: BuiltinNames; targets: MultiTargetCliTarget[]; - /** Enable NodeHttpAdapter for *.localhost subdomain routing */ - nodeHttpAdapter?: boolean; /** Generate a runnable index.ts entry point */ entryPoint?: boolean; } @@ -180,9 +168,7 @@ export function generateMultiTargetCli( endpoint: t.endpoint, ormImportPath: t.ormImportPath, })); - const executorFile = generateMultiTargetExecutorFile(toolName, executorInputs, { - nodeHttpAdapter: !!options.nodeHttpAdapter, - }); + const executorFile = generateMultiTargetExecutorFile(toolName, executorInputs); files.push(executorFile); const utilsFile = generateUtilsFile(); @@ -198,11 +184,6 @@ export function generateMultiTargetCli( files.push(generateEmbedderFile()); } - // Generate node HTTP adapter if configured (for *.localhost subdomain routing) - if (options.nodeHttpAdapter) { - files.push(generateNodeFetchFile()); - } - const contextFile = generateMultiTargetContextCommand( toolName, builtinNames.context, diff --git a/graphql/codegen/src/core/codegen/cli/utils-generator.ts b/graphql/codegen/src/core/codegen/cli/utils-generator.ts index 3860d94a7..eb1fc0274 100644 --- a/graphql/codegen/src/core/codegen/cli/utils-generator.ts +++ b/graphql/codegen/src/core/codegen/cli/utils-generator.ts @@ -60,23 +60,6 @@ export function generateUtilsFile(): GeneratedFile { }; } -/** - * Generate a node-fetch.ts file with NodeHttpAdapter for CLI. - * - * Provides a GraphQLAdapter implementation using node:http/node:https - * instead of the Fetch API. This cleanly handles *.localhost subdomain - * routing (DNS resolution + Host header) without any global patching. - */ -export function generateNodeFetchFile(): GeneratedFile { - return { - fileName: 'node-fetch.ts', - content: readTemplateFile( - 'node-fetch.ts', - 'Node HTTP adapter for localhost subdomain routing', - ), - }; -} - /** * Generate an index.ts entry point file for the CLI. * diff --git a/graphql/codegen/src/core/codegen/orm/client-generator.ts b/graphql/codegen/src/core/codegen/orm/client-generator.ts index efec017ca..3920ec5b3 100644 --- a/graphql/codegen/src/core/codegen/orm/client-generator.ts +++ b/graphql/codegen/src/core/codegen/orm/client-generator.ts @@ -137,7 +137,6 @@ export function generateCreateClientFile( tables: Table[], hasCustomQueries: boolean, hasCustomMutations: boolean, - options?: { nodeHttpAdapter?: boolean }, ): GeneratedClientFile { const statements: t.Statement[] = []; diff --git a/graphql/codegen/src/core/codegen/orm/index.ts b/graphql/codegen/src/core/codegen/orm/index.ts index 2d2c9b866..f9be00d93 100644 --- a/graphql/codegen/src/core/codegen/orm/index.ts +++ b/graphql/codegen/src/core/codegen/orm/index.ts @@ -177,7 +177,6 @@ export function generateOrm(options: GenerateOrmOptions): GenerateOrmResult { tables, hasCustomQueries, hasCustomMutations, - { nodeHttpAdapter: !!options.config.nodeHttpAdapter }, ); files.push({ path: indexFile.fileName, content: indexFile.content }); diff --git a/graphql/codegen/src/core/codegen/templates/node-fetch.ts b/graphql/codegen/src/core/codegen/templates/node-fetch.ts deleted file mode 100644 index a8304b4f5..000000000 --- a/graphql/codegen/src/core/codegen/templates/node-fetch.ts +++ /dev/null @@ -1,198 +0,0 @@ -/** - * Node HTTP Adapter for Node.js applications - * - * Implements the GraphQLAdapter interface using node:http / node:https - * instead of the Fetch API. This solves two Node.js limitations: - * - * 1. DNS: Node.js cannot resolve *.localhost subdomains (ENOTFOUND). - * Browsers handle this automatically, but Node requires manual resolution. - * - * 2. Host header: The Fetch API treats "Host" as a forbidden request header - * and silently drops it. The Constructive GraphQL server uses Host-header - * subdomain routing (enableServicesApi), so this header must be preserved. - * - * By using node:http.request directly, both issues are bypassed cleanly - * without any global patching. - * - * NOTE: This file is read at codegen time and written to output. - * Any changes here will affect all generated CLI node adapters. - */ - -import http from 'node:http'; -import https from 'node:https'; - -import type { - GraphQLAdapter, - GraphQLError, - QueryResult, -} from '@constructive-io/graphql-types'; - -interface HttpResponse { - statusCode: number; - statusMessage: string; - data: string; -} - -/** - * Check if a hostname is a localhost subdomain that needs special handling. - * Returns true for *.localhost (e.g. auth.localhost) but not bare "localhost". - */ -function isLocalhostSubdomain(hostname: string): boolean { - return hostname.endsWith('.localhost') && hostname !== 'localhost'; -} - -/** - * Make an HTTP/HTTPS request using native Node modules. - * Supports optional AbortSignal for request cancellation. - */ -function makeRequest( - url: URL, - options: http.RequestOptions, - body: string, - signal?: AbortSignal, -): Promise { - return new Promise((resolve, reject) => { - if (signal?.aborted) { - reject(new Error('The operation was aborted')); - return; - } - - const protocol = url.protocol === 'https:' ? https : http; - - const req = protocol.request(url, options, (res) => { - let data = ''; - res.setEncoding('utf8'); - res.on('data', (chunk: string) => { - data += chunk; - }); - res.on('end', () => { - resolve({ - statusCode: res.statusCode || 0, - statusMessage: res.statusMessage || '', - data, - }); - }); - }); - - req.on('error', reject); - - if (signal) { - const onAbort = () => { - req.destroy(new Error('The operation was aborted')); - }; - signal.addEventListener('abort', onAbort, { once: true }); - req.on('close', () => { - signal.removeEventListener('abort', onAbort); - }); - } - - req.write(body); - req.end(); - }); -} - -/** - * Options for individual execute calls. - * Allows per-request header overrides and request cancellation. - */ -export interface NodeHttpExecuteOptions { - /** Additional headers to include in this request only */ - headers?: Record; - /** AbortSignal for request cancellation */ - signal?: AbortSignal; -} - -/** - * GraphQL adapter that uses node:http/node:https for requests. - * - * Handles *.localhost subdomains by rewriting the hostname to "localhost" - * and injecting the original Host header for server-side subdomain routing. - */ -export class NodeHttpAdapter implements GraphQLAdapter { - private headers: Record; - private url: URL; - - constructor( - private endpoint: string, - headers?: Record, - ) { - this.headers = headers ?? {}; - this.url = new URL(endpoint); - } - - async execute( - document: string, - variables?: Record, - options?: NodeHttpExecuteOptions, - ): Promise> { - const requestUrl = new URL(this.url.href); - const requestHeaders: Record = { - 'Content-Type': 'application/json', - Accept: 'application/json', - ...this.headers, - ...options?.headers, - }; - - // For *.localhost subdomains, rewrite hostname and inject Host header - if (isLocalhostSubdomain(requestUrl.hostname)) { - requestHeaders['Host'] = requestUrl.host; - requestUrl.hostname = 'localhost'; - } - - const body = JSON.stringify({ - query: document, - variables: variables ?? {}, - }); - - const requestOptions: http.RequestOptions = { - method: 'POST', - headers: requestHeaders, - }; - - const response = await makeRequest( - requestUrl, - requestOptions, - body, - options?.signal, - ); - - if (response.statusCode < 200 || response.statusCode >= 300) { - return { - ok: false, - data: null, - errors: [ - { - message: `HTTP ${response.statusCode}: ${response.statusMessage}`, - }, - ], - }; - } - - const json = JSON.parse(response.data) as { - data?: T; - errors?: GraphQLError[]; - }; - - if (json.errors && json.errors.length > 0) { - return { - ok: false, - data: null, - errors: json.errors, - }; - } - - return { - ok: true, - data: json.data as T, - errors: undefined, - }; - } - - setHeaders(headers: Record): void { - this.headers = { ...this.headers, ...headers }; - } - - getEndpoint(): string { - return this.endpoint; - } -} diff --git a/graphql/codegen/src/core/generate.ts b/graphql/codegen/src/core/generate.ts index f2f760991..ee5a3130f 100644 --- a/graphql/codegen/src/core/generate.ts +++ b/graphql/codegen/src/core/generate.ts @@ -126,11 +126,6 @@ export async function generate( const runOrm = runReactQuery || !!config.cli || (options.orm !== undefined ? !!options.orm : false); - // Auto-enable nodeHttpAdapter when CLI is enabled, unless explicitly set to false - const useNodeHttpAdapter = - options.nodeHttpAdapter === true || - (runCli && options.nodeHttpAdapter !== false); - const schemaEnabled = !!options.schema?.enabled; if (!schemaEnabled && !runReactQuery && !runOrm && !runCli) { @@ -280,7 +275,7 @@ export async function generate( mutations: customOperations.mutations, typeRegistry: customOperations.typeRegistry, }, - config: { ...config, nodeHttpAdapter: useNodeHttpAdapter }, + config, sharedTypesPath: bothEnabled ? '..' : undefined, }); filesToWrite.push( @@ -289,16 +284,6 @@ export async function generate( path: path.posix.join('orm', file.path), })), ); - - // Generate NodeHttpAdapter in ORM output when enabled - if (useNodeHttpAdapter) { - const { generateNodeFetchFile } = await import('./codegen/cli/utils-generator'); - const nodeFetchFile = generateNodeFetchFile(); - filesToWrite.push({ - path: path.posix.join('orm', nodeFetchFile.fileName), - content: nodeFetchFile.content, - }); - } } // Generate CLI commands @@ -310,7 +295,7 @@ export async function generate( queries: customOperations.queries, mutations: customOperations.mutations, }, - config: { ...config, nodeHttpAdapter: useNodeHttpAdapter }, + config, typeRegistry: customOperations.typeRegistry, }); filesToWrite.push( @@ -707,18 +692,11 @@ export async function generateMulti( if (useUnifiedCli && cliTargets.length > 0 && !dryRun) { const cliConfig = typeof unifiedCli === 'object' ? unifiedCli : {}; const toolName = cliConfig.toolName ?? 'app'; - // Auto-enable nodeHttpAdapter for unified CLI unless explicitly disabled - // Check first target config for explicit nodeHttpAdapter setting const firstTargetConfig = configs[names[0]]; - const multiNodeHttpAdapter = - firstTargetConfig?.nodeHttpAdapter === true || - (firstTargetConfig?.nodeHttpAdapter !== false); - const { files } = generateMultiTargetCli({ toolName, builtinNames: cliConfig.builtinNames, targets: cliTargets, - nodeHttpAdapter: multiNodeHttpAdapter, entryPoint: cliConfig.entryPoint, }); diff --git a/graphql/codegen/src/types/config.ts b/graphql/codegen/src/types/config.ts index 790b11be7..962bf78da 100644 --- a/graphql/codegen/src/types/config.ts +++ b/graphql/codegen/src/types/config.ts @@ -365,25 +365,6 @@ export interface GraphQLSDKConfigTarget { */ orm?: boolean; - /** - * Enable NodeHttpAdapter generation for Node.js applications. - * When true, generates a node-fetch.ts with NodeHttpAdapter (implements GraphQLAdapter) - * using node:http/node:https for requests, enabling local development - * with subdomain-based routing (e.g. auth.localhost:3000). - * No global patching — the adapter is passed to createClient via the adapter option. - * - * When CLI generation is enabled (`cli: true`), this is auto-enabled unless - * explicitly set to `false`. - * - * Can also be used standalone with the ORM client for any Node.js application: - * ```ts - * import { NodeHttpAdapter } from './orm/node-fetch'; - * const db = createClient({ adapter: new NodeHttpAdapter(endpoint, headers) }); - * ``` - * @default false - */ - nodeHttpAdapter?: boolean; - /** * Whether to generate React Query hooks * When enabled, generates React Query hooks to {output}/hooks diff --git a/sdk/constructive-cli/src/admin/cli/executor.ts b/sdk/constructive-cli/src/admin/cli/executor.ts index 721e8e68e..fdfdc796d 100644 --- a/sdk/constructive-cli/src/admin/cli/executor.ts +++ b/sdk/constructive-cli/src/admin/cli/executor.ts @@ -3,7 +3,6 @@ * @generated by @constructive-io/graphql-codegen * DO NOT EDIT - changes will be overwritten */ -import { NodeHttpAdapter } from './node-fetch'; import { createConfigStore } from 'appstash'; import { createClient } from '../orm'; const store = createConfigStore('csdk'); @@ -29,6 +28,7 @@ export function getClient(contextName?: string) { } } return createClient({ - adapter: new NodeHttpAdapter(ctx.endpoint, headers), + endpoint: ctx.endpoint, + headers, }); } diff --git a/sdk/constructive-cli/src/admin/cli/node-fetch.ts b/sdk/constructive-cli/src/admin/cli/node-fetch.ts deleted file mode 100644 index 81bb05834..000000000 --- a/sdk/constructive-cli/src/admin/cli/node-fetch.ts +++ /dev/null @@ -1,174 +0,0 @@ -/** - * Node HTTP adapter for localhost subdomain routing - * @generated by @constructive-io/graphql-codegen - * DO NOT EDIT - changes will be overwritten - */ -import http from 'node:http'; -import https from 'node:https'; - -import type { GraphQLAdapter, GraphQLError, QueryResult } from '@constructive-io/graphql-types'; - -interface HttpResponse { - statusCode: number; - statusMessage: string; - data: string; -} - -/** - * Check if a hostname is a localhost subdomain that needs special handling. - * Returns true for *.localhost (e.g. auth.localhost) but not bare "localhost". - */ -function isLocalhostSubdomain(hostname: string): boolean { - return hostname.endsWith('.localhost') && hostname !== 'localhost'; -} - -/** - * Make an HTTP/HTTPS request using native Node modules. - * Supports optional AbortSignal for request cancellation. - */ -function makeRequest( - url: URL, - options: http.RequestOptions, - body: string, - signal?: AbortSignal -): Promise { - return new Promise((resolve, reject) => { - if (signal?.aborted) { - reject(new Error('The operation was aborted')); - return; - } - - const protocol = url.protocol === 'https:' ? https : http; - - const req = protocol.request(url, options, (res) => { - let data = ''; - res.setEncoding('utf8'); - res.on('data', (chunk: string) => { - data += chunk; - }); - res.on('end', () => { - resolve({ - statusCode: res.statusCode || 0, - statusMessage: res.statusMessage || '', - data, - }); - }); - }); - - req.on('error', reject); - - if (signal) { - const onAbort = () => { - req.destroy(new Error('The operation was aborted')); - }; - signal.addEventListener('abort', onAbort, { once: true }); - req.on('close', () => { - signal.removeEventListener('abort', onAbort); - }); - } - - req.write(body); - req.end(); - }); -} - -/** - * Options for individual execute calls. - * Allows per-request header overrides and request cancellation. - */ -export interface NodeHttpExecuteOptions { - /** Additional headers to include in this request only */ - headers?: Record; - /** AbortSignal for request cancellation */ - signal?: AbortSignal; -} - -/** - * GraphQL adapter that uses node:http/node:https for requests. - * - * Handles *.localhost subdomains by rewriting the hostname to "localhost" - * and injecting the original Host header for server-side subdomain routing. - */ -export class NodeHttpAdapter implements GraphQLAdapter { - private headers: Record; - private url: URL; - - constructor( - private endpoint: string, - headers?: Record - ) { - this.headers = headers ?? {}; - this.url = new URL(endpoint); - } - - async execute( - document: string, - variables?: Record, - options?: NodeHttpExecuteOptions - ): Promise> { - const requestUrl = new URL(this.url.href); - const requestHeaders: Record = { - 'Content-Type': 'application/json', - Accept: 'application/json', - ...this.headers, - ...options?.headers, - }; - - // For *.localhost subdomains, rewrite hostname and inject Host header - if (isLocalhostSubdomain(requestUrl.hostname)) { - requestHeaders['Host'] = requestUrl.host; - requestUrl.hostname = 'localhost'; - } - - const body = JSON.stringify({ - query: document, - variables: variables ?? {}, - }); - - const requestOptions: http.RequestOptions = { - method: 'POST', - headers: requestHeaders, - }; - - const response = await makeRequest(requestUrl, requestOptions, body, options?.signal); - - if (response.statusCode < 200 || response.statusCode >= 300) { - return { - ok: false, - data: null, - errors: [ - { - message: `HTTP ${response.statusCode}: ${response.statusMessage}`, - }, - ], - }; - } - - const json = JSON.parse(response.data) as { - data?: T; - errors?: GraphQLError[]; - }; - - if (json.errors && json.errors.length > 0) { - return { - ok: false, - data: null, - errors: json.errors, - }; - } - - return { - ok: true, - data: json.data as T, - errors: undefined, - }; - } - - setHeaders(headers: Record): void { - this.headers = { ...this.headers, ...headers }; - } - - getEndpoint(): string { - return this.endpoint; - } -} diff --git a/sdk/constructive-cli/src/admin/orm/node-fetch.ts b/sdk/constructive-cli/src/admin/orm/node-fetch.ts deleted file mode 100644 index 81bb05834..000000000 --- a/sdk/constructive-cli/src/admin/orm/node-fetch.ts +++ /dev/null @@ -1,174 +0,0 @@ -/** - * Node HTTP adapter for localhost subdomain routing - * @generated by @constructive-io/graphql-codegen - * DO NOT EDIT - changes will be overwritten - */ -import http from 'node:http'; -import https from 'node:https'; - -import type { GraphQLAdapter, GraphQLError, QueryResult } from '@constructive-io/graphql-types'; - -interface HttpResponse { - statusCode: number; - statusMessage: string; - data: string; -} - -/** - * Check if a hostname is a localhost subdomain that needs special handling. - * Returns true for *.localhost (e.g. auth.localhost) but not bare "localhost". - */ -function isLocalhostSubdomain(hostname: string): boolean { - return hostname.endsWith('.localhost') && hostname !== 'localhost'; -} - -/** - * Make an HTTP/HTTPS request using native Node modules. - * Supports optional AbortSignal for request cancellation. - */ -function makeRequest( - url: URL, - options: http.RequestOptions, - body: string, - signal?: AbortSignal -): Promise { - return new Promise((resolve, reject) => { - if (signal?.aborted) { - reject(new Error('The operation was aborted')); - return; - } - - const protocol = url.protocol === 'https:' ? https : http; - - const req = protocol.request(url, options, (res) => { - let data = ''; - res.setEncoding('utf8'); - res.on('data', (chunk: string) => { - data += chunk; - }); - res.on('end', () => { - resolve({ - statusCode: res.statusCode || 0, - statusMessage: res.statusMessage || '', - data, - }); - }); - }); - - req.on('error', reject); - - if (signal) { - const onAbort = () => { - req.destroy(new Error('The operation was aborted')); - }; - signal.addEventListener('abort', onAbort, { once: true }); - req.on('close', () => { - signal.removeEventListener('abort', onAbort); - }); - } - - req.write(body); - req.end(); - }); -} - -/** - * Options for individual execute calls. - * Allows per-request header overrides and request cancellation. - */ -export interface NodeHttpExecuteOptions { - /** Additional headers to include in this request only */ - headers?: Record; - /** AbortSignal for request cancellation */ - signal?: AbortSignal; -} - -/** - * GraphQL adapter that uses node:http/node:https for requests. - * - * Handles *.localhost subdomains by rewriting the hostname to "localhost" - * and injecting the original Host header for server-side subdomain routing. - */ -export class NodeHttpAdapter implements GraphQLAdapter { - private headers: Record; - private url: URL; - - constructor( - private endpoint: string, - headers?: Record - ) { - this.headers = headers ?? {}; - this.url = new URL(endpoint); - } - - async execute( - document: string, - variables?: Record, - options?: NodeHttpExecuteOptions - ): Promise> { - const requestUrl = new URL(this.url.href); - const requestHeaders: Record = { - 'Content-Type': 'application/json', - Accept: 'application/json', - ...this.headers, - ...options?.headers, - }; - - // For *.localhost subdomains, rewrite hostname and inject Host header - if (isLocalhostSubdomain(requestUrl.hostname)) { - requestHeaders['Host'] = requestUrl.host; - requestUrl.hostname = 'localhost'; - } - - const body = JSON.stringify({ - query: document, - variables: variables ?? {}, - }); - - const requestOptions: http.RequestOptions = { - method: 'POST', - headers: requestHeaders, - }; - - const response = await makeRequest(requestUrl, requestOptions, body, options?.signal); - - if (response.statusCode < 200 || response.statusCode >= 300) { - return { - ok: false, - data: null, - errors: [ - { - message: `HTTP ${response.statusCode}: ${response.statusMessage}`, - }, - ], - }; - } - - const json = JSON.parse(response.data) as { - data?: T; - errors?: GraphQLError[]; - }; - - if (json.errors && json.errors.length > 0) { - return { - ok: false, - data: null, - errors: json.errors, - }; - } - - return { - ok: true, - data: json.data as T, - errors: undefined, - }; - } - - setHeaders(headers: Record): void { - this.headers = { ...this.headers, ...headers }; - } - - getEndpoint(): string { - return this.endpoint; - } -} diff --git a/sdk/constructive-cli/src/auth/cli/executor.ts b/sdk/constructive-cli/src/auth/cli/executor.ts index 721e8e68e..fdfdc796d 100644 --- a/sdk/constructive-cli/src/auth/cli/executor.ts +++ b/sdk/constructive-cli/src/auth/cli/executor.ts @@ -3,7 +3,6 @@ * @generated by @constructive-io/graphql-codegen * DO NOT EDIT - changes will be overwritten */ -import { NodeHttpAdapter } from './node-fetch'; import { createConfigStore } from 'appstash'; import { createClient } from '../orm'; const store = createConfigStore('csdk'); @@ -29,6 +28,7 @@ export function getClient(contextName?: string) { } } return createClient({ - adapter: new NodeHttpAdapter(ctx.endpoint, headers), + endpoint: ctx.endpoint, + headers, }); } diff --git a/sdk/constructive-cli/src/auth/cli/node-fetch.ts b/sdk/constructive-cli/src/auth/cli/node-fetch.ts deleted file mode 100644 index 81bb05834..000000000 --- a/sdk/constructive-cli/src/auth/cli/node-fetch.ts +++ /dev/null @@ -1,174 +0,0 @@ -/** - * Node HTTP adapter for localhost subdomain routing - * @generated by @constructive-io/graphql-codegen - * DO NOT EDIT - changes will be overwritten - */ -import http from 'node:http'; -import https from 'node:https'; - -import type { GraphQLAdapter, GraphQLError, QueryResult } from '@constructive-io/graphql-types'; - -interface HttpResponse { - statusCode: number; - statusMessage: string; - data: string; -} - -/** - * Check if a hostname is a localhost subdomain that needs special handling. - * Returns true for *.localhost (e.g. auth.localhost) but not bare "localhost". - */ -function isLocalhostSubdomain(hostname: string): boolean { - return hostname.endsWith('.localhost') && hostname !== 'localhost'; -} - -/** - * Make an HTTP/HTTPS request using native Node modules. - * Supports optional AbortSignal for request cancellation. - */ -function makeRequest( - url: URL, - options: http.RequestOptions, - body: string, - signal?: AbortSignal -): Promise { - return new Promise((resolve, reject) => { - if (signal?.aborted) { - reject(new Error('The operation was aborted')); - return; - } - - const protocol = url.protocol === 'https:' ? https : http; - - const req = protocol.request(url, options, (res) => { - let data = ''; - res.setEncoding('utf8'); - res.on('data', (chunk: string) => { - data += chunk; - }); - res.on('end', () => { - resolve({ - statusCode: res.statusCode || 0, - statusMessage: res.statusMessage || '', - data, - }); - }); - }); - - req.on('error', reject); - - if (signal) { - const onAbort = () => { - req.destroy(new Error('The operation was aborted')); - }; - signal.addEventListener('abort', onAbort, { once: true }); - req.on('close', () => { - signal.removeEventListener('abort', onAbort); - }); - } - - req.write(body); - req.end(); - }); -} - -/** - * Options for individual execute calls. - * Allows per-request header overrides and request cancellation. - */ -export interface NodeHttpExecuteOptions { - /** Additional headers to include in this request only */ - headers?: Record; - /** AbortSignal for request cancellation */ - signal?: AbortSignal; -} - -/** - * GraphQL adapter that uses node:http/node:https for requests. - * - * Handles *.localhost subdomains by rewriting the hostname to "localhost" - * and injecting the original Host header for server-side subdomain routing. - */ -export class NodeHttpAdapter implements GraphQLAdapter { - private headers: Record; - private url: URL; - - constructor( - private endpoint: string, - headers?: Record - ) { - this.headers = headers ?? {}; - this.url = new URL(endpoint); - } - - async execute( - document: string, - variables?: Record, - options?: NodeHttpExecuteOptions - ): Promise> { - const requestUrl = new URL(this.url.href); - const requestHeaders: Record = { - 'Content-Type': 'application/json', - Accept: 'application/json', - ...this.headers, - ...options?.headers, - }; - - // For *.localhost subdomains, rewrite hostname and inject Host header - if (isLocalhostSubdomain(requestUrl.hostname)) { - requestHeaders['Host'] = requestUrl.host; - requestUrl.hostname = 'localhost'; - } - - const body = JSON.stringify({ - query: document, - variables: variables ?? {}, - }); - - const requestOptions: http.RequestOptions = { - method: 'POST', - headers: requestHeaders, - }; - - const response = await makeRequest(requestUrl, requestOptions, body, options?.signal); - - if (response.statusCode < 200 || response.statusCode >= 300) { - return { - ok: false, - data: null, - errors: [ - { - message: `HTTP ${response.statusCode}: ${response.statusMessage}`, - }, - ], - }; - } - - const json = JSON.parse(response.data) as { - data?: T; - errors?: GraphQLError[]; - }; - - if (json.errors && json.errors.length > 0) { - return { - ok: false, - data: null, - errors: json.errors, - }; - } - - return { - ok: true, - data: json.data as T, - errors: undefined, - }; - } - - setHeaders(headers: Record): void { - this.headers = { ...this.headers, ...headers }; - } - - getEndpoint(): string { - return this.endpoint; - } -} diff --git a/sdk/constructive-cli/src/auth/orm/node-fetch.ts b/sdk/constructive-cli/src/auth/orm/node-fetch.ts deleted file mode 100644 index 81bb05834..000000000 --- a/sdk/constructive-cli/src/auth/orm/node-fetch.ts +++ /dev/null @@ -1,174 +0,0 @@ -/** - * Node HTTP adapter for localhost subdomain routing - * @generated by @constructive-io/graphql-codegen - * DO NOT EDIT - changes will be overwritten - */ -import http from 'node:http'; -import https from 'node:https'; - -import type { GraphQLAdapter, GraphQLError, QueryResult } from '@constructive-io/graphql-types'; - -interface HttpResponse { - statusCode: number; - statusMessage: string; - data: string; -} - -/** - * Check if a hostname is a localhost subdomain that needs special handling. - * Returns true for *.localhost (e.g. auth.localhost) but not bare "localhost". - */ -function isLocalhostSubdomain(hostname: string): boolean { - return hostname.endsWith('.localhost') && hostname !== 'localhost'; -} - -/** - * Make an HTTP/HTTPS request using native Node modules. - * Supports optional AbortSignal for request cancellation. - */ -function makeRequest( - url: URL, - options: http.RequestOptions, - body: string, - signal?: AbortSignal -): Promise { - return new Promise((resolve, reject) => { - if (signal?.aborted) { - reject(new Error('The operation was aborted')); - return; - } - - const protocol = url.protocol === 'https:' ? https : http; - - const req = protocol.request(url, options, (res) => { - let data = ''; - res.setEncoding('utf8'); - res.on('data', (chunk: string) => { - data += chunk; - }); - res.on('end', () => { - resolve({ - statusCode: res.statusCode || 0, - statusMessage: res.statusMessage || '', - data, - }); - }); - }); - - req.on('error', reject); - - if (signal) { - const onAbort = () => { - req.destroy(new Error('The operation was aborted')); - }; - signal.addEventListener('abort', onAbort, { once: true }); - req.on('close', () => { - signal.removeEventListener('abort', onAbort); - }); - } - - req.write(body); - req.end(); - }); -} - -/** - * Options for individual execute calls. - * Allows per-request header overrides and request cancellation. - */ -export interface NodeHttpExecuteOptions { - /** Additional headers to include in this request only */ - headers?: Record; - /** AbortSignal for request cancellation */ - signal?: AbortSignal; -} - -/** - * GraphQL adapter that uses node:http/node:https for requests. - * - * Handles *.localhost subdomains by rewriting the hostname to "localhost" - * and injecting the original Host header for server-side subdomain routing. - */ -export class NodeHttpAdapter implements GraphQLAdapter { - private headers: Record; - private url: URL; - - constructor( - private endpoint: string, - headers?: Record - ) { - this.headers = headers ?? {}; - this.url = new URL(endpoint); - } - - async execute( - document: string, - variables?: Record, - options?: NodeHttpExecuteOptions - ): Promise> { - const requestUrl = new URL(this.url.href); - const requestHeaders: Record = { - 'Content-Type': 'application/json', - Accept: 'application/json', - ...this.headers, - ...options?.headers, - }; - - // For *.localhost subdomains, rewrite hostname and inject Host header - if (isLocalhostSubdomain(requestUrl.hostname)) { - requestHeaders['Host'] = requestUrl.host; - requestUrl.hostname = 'localhost'; - } - - const body = JSON.stringify({ - query: document, - variables: variables ?? {}, - }); - - const requestOptions: http.RequestOptions = { - method: 'POST', - headers: requestHeaders, - }; - - const response = await makeRequest(requestUrl, requestOptions, body, options?.signal); - - if (response.statusCode < 200 || response.statusCode >= 300) { - return { - ok: false, - data: null, - errors: [ - { - message: `HTTP ${response.statusCode}: ${response.statusMessage}`, - }, - ], - }; - } - - const json = JSON.parse(response.data) as { - data?: T; - errors?: GraphQLError[]; - }; - - if (json.errors && json.errors.length > 0) { - return { - ok: false, - data: null, - errors: json.errors, - }; - } - - return { - ok: true, - data: json.data as T, - errors: undefined, - }; - } - - setHeaders(headers: Record): void { - this.headers = { ...this.headers, ...headers }; - } - - getEndpoint(): string { - return this.endpoint; - } -} diff --git a/sdk/constructive-cli/src/objects/cli/executor.ts b/sdk/constructive-cli/src/objects/cli/executor.ts index 721e8e68e..fdfdc796d 100644 --- a/sdk/constructive-cli/src/objects/cli/executor.ts +++ b/sdk/constructive-cli/src/objects/cli/executor.ts @@ -3,7 +3,6 @@ * @generated by @constructive-io/graphql-codegen * DO NOT EDIT - changes will be overwritten */ -import { NodeHttpAdapter } from './node-fetch'; import { createConfigStore } from 'appstash'; import { createClient } from '../orm'; const store = createConfigStore('csdk'); @@ -29,6 +28,7 @@ export function getClient(contextName?: string) { } } return createClient({ - adapter: new NodeHttpAdapter(ctx.endpoint, headers), + endpoint: ctx.endpoint, + headers, }); } diff --git a/sdk/constructive-cli/src/objects/cli/node-fetch.ts b/sdk/constructive-cli/src/objects/cli/node-fetch.ts deleted file mode 100644 index 81bb05834..000000000 --- a/sdk/constructive-cli/src/objects/cli/node-fetch.ts +++ /dev/null @@ -1,174 +0,0 @@ -/** - * Node HTTP adapter for localhost subdomain routing - * @generated by @constructive-io/graphql-codegen - * DO NOT EDIT - changes will be overwritten - */ -import http from 'node:http'; -import https from 'node:https'; - -import type { GraphQLAdapter, GraphQLError, QueryResult } from '@constructive-io/graphql-types'; - -interface HttpResponse { - statusCode: number; - statusMessage: string; - data: string; -} - -/** - * Check if a hostname is a localhost subdomain that needs special handling. - * Returns true for *.localhost (e.g. auth.localhost) but not bare "localhost". - */ -function isLocalhostSubdomain(hostname: string): boolean { - return hostname.endsWith('.localhost') && hostname !== 'localhost'; -} - -/** - * Make an HTTP/HTTPS request using native Node modules. - * Supports optional AbortSignal for request cancellation. - */ -function makeRequest( - url: URL, - options: http.RequestOptions, - body: string, - signal?: AbortSignal -): Promise { - return new Promise((resolve, reject) => { - if (signal?.aborted) { - reject(new Error('The operation was aborted')); - return; - } - - const protocol = url.protocol === 'https:' ? https : http; - - const req = protocol.request(url, options, (res) => { - let data = ''; - res.setEncoding('utf8'); - res.on('data', (chunk: string) => { - data += chunk; - }); - res.on('end', () => { - resolve({ - statusCode: res.statusCode || 0, - statusMessage: res.statusMessage || '', - data, - }); - }); - }); - - req.on('error', reject); - - if (signal) { - const onAbort = () => { - req.destroy(new Error('The operation was aborted')); - }; - signal.addEventListener('abort', onAbort, { once: true }); - req.on('close', () => { - signal.removeEventListener('abort', onAbort); - }); - } - - req.write(body); - req.end(); - }); -} - -/** - * Options for individual execute calls. - * Allows per-request header overrides and request cancellation. - */ -export interface NodeHttpExecuteOptions { - /** Additional headers to include in this request only */ - headers?: Record; - /** AbortSignal for request cancellation */ - signal?: AbortSignal; -} - -/** - * GraphQL adapter that uses node:http/node:https for requests. - * - * Handles *.localhost subdomains by rewriting the hostname to "localhost" - * and injecting the original Host header for server-side subdomain routing. - */ -export class NodeHttpAdapter implements GraphQLAdapter { - private headers: Record; - private url: URL; - - constructor( - private endpoint: string, - headers?: Record - ) { - this.headers = headers ?? {}; - this.url = new URL(endpoint); - } - - async execute( - document: string, - variables?: Record, - options?: NodeHttpExecuteOptions - ): Promise> { - const requestUrl = new URL(this.url.href); - const requestHeaders: Record = { - 'Content-Type': 'application/json', - Accept: 'application/json', - ...this.headers, - ...options?.headers, - }; - - // For *.localhost subdomains, rewrite hostname and inject Host header - if (isLocalhostSubdomain(requestUrl.hostname)) { - requestHeaders['Host'] = requestUrl.host; - requestUrl.hostname = 'localhost'; - } - - const body = JSON.stringify({ - query: document, - variables: variables ?? {}, - }); - - const requestOptions: http.RequestOptions = { - method: 'POST', - headers: requestHeaders, - }; - - const response = await makeRequest(requestUrl, requestOptions, body, options?.signal); - - if (response.statusCode < 200 || response.statusCode >= 300) { - return { - ok: false, - data: null, - errors: [ - { - message: `HTTP ${response.statusCode}: ${response.statusMessage}`, - }, - ], - }; - } - - const json = JSON.parse(response.data) as { - data?: T; - errors?: GraphQLError[]; - }; - - if (json.errors && json.errors.length > 0) { - return { - ok: false, - data: null, - errors: json.errors, - }; - } - - return { - ok: true, - data: json.data as T, - errors: undefined, - }; - } - - setHeaders(headers: Record): void { - this.headers = { ...this.headers, ...headers }; - } - - getEndpoint(): string { - return this.endpoint; - } -} diff --git a/sdk/constructive-cli/src/objects/orm/node-fetch.ts b/sdk/constructive-cli/src/objects/orm/node-fetch.ts deleted file mode 100644 index 81bb05834..000000000 --- a/sdk/constructive-cli/src/objects/orm/node-fetch.ts +++ /dev/null @@ -1,174 +0,0 @@ -/** - * Node HTTP adapter for localhost subdomain routing - * @generated by @constructive-io/graphql-codegen - * DO NOT EDIT - changes will be overwritten - */ -import http from 'node:http'; -import https from 'node:https'; - -import type { GraphQLAdapter, GraphQLError, QueryResult } from '@constructive-io/graphql-types'; - -interface HttpResponse { - statusCode: number; - statusMessage: string; - data: string; -} - -/** - * Check if a hostname is a localhost subdomain that needs special handling. - * Returns true for *.localhost (e.g. auth.localhost) but not bare "localhost". - */ -function isLocalhostSubdomain(hostname: string): boolean { - return hostname.endsWith('.localhost') && hostname !== 'localhost'; -} - -/** - * Make an HTTP/HTTPS request using native Node modules. - * Supports optional AbortSignal for request cancellation. - */ -function makeRequest( - url: URL, - options: http.RequestOptions, - body: string, - signal?: AbortSignal -): Promise { - return new Promise((resolve, reject) => { - if (signal?.aborted) { - reject(new Error('The operation was aborted')); - return; - } - - const protocol = url.protocol === 'https:' ? https : http; - - const req = protocol.request(url, options, (res) => { - let data = ''; - res.setEncoding('utf8'); - res.on('data', (chunk: string) => { - data += chunk; - }); - res.on('end', () => { - resolve({ - statusCode: res.statusCode || 0, - statusMessage: res.statusMessage || '', - data, - }); - }); - }); - - req.on('error', reject); - - if (signal) { - const onAbort = () => { - req.destroy(new Error('The operation was aborted')); - }; - signal.addEventListener('abort', onAbort, { once: true }); - req.on('close', () => { - signal.removeEventListener('abort', onAbort); - }); - } - - req.write(body); - req.end(); - }); -} - -/** - * Options for individual execute calls. - * Allows per-request header overrides and request cancellation. - */ -export interface NodeHttpExecuteOptions { - /** Additional headers to include in this request only */ - headers?: Record; - /** AbortSignal for request cancellation */ - signal?: AbortSignal; -} - -/** - * GraphQL adapter that uses node:http/node:https for requests. - * - * Handles *.localhost subdomains by rewriting the hostname to "localhost" - * and injecting the original Host header for server-side subdomain routing. - */ -export class NodeHttpAdapter implements GraphQLAdapter { - private headers: Record; - private url: URL; - - constructor( - private endpoint: string, - headers?: Record - ) { - this.headers = headers ?? {}; - this.url = new URL(endpoint); - } - - async execute( - document: string, - variables?: Record, - options?: NodeHttpExecuteOptions - ): Promise> { - const requestUrl = new URL(this.url.href); - const requestHeaders: Record = { - 'Content-Type': 'application/json', - Accept: 'application/json', - ...this.headers, - ...options?.headers, - }; - - // For *.localhost subdomains, rewrite hostname and inject Host header - if (isLocalhostSubdomain(requestUrl.hostname)) { - requestHeaders['Host'] = requestUrl.host; - requestUrl.hostname = 'localhost'; - } - - const body = JSON.stringify({ - query: document, - variables: variables ?? {}, - }); - - const requestOptions: http.RequestOptions = { - method: 'POST', - headers: requestHeaders, - }; - - const response = await makeRequest(requestUrl, requestOptions, body, options?.signal); - - if (response.statusCode < 200 || response.statusCode >= 300) { - return { - ok: false, - data: null, - errors: [ - { - message: `HTTP ${response.statusCode}: ${response.statusMessage}`, - }, - ], - }; - } - - const json = JSON.parse(response.data) as { - data?: T; - errors?: GraphQLError[]; - }; - - if (json.errors && json.errors.length > 0) { - return { - ok: false, - data: null, - errors: json.errors, - }; - } - - return { - ok: true, - data: json.data as T, - errors: undefined, - }; - } - - setHeaders(headers: Record): void { - this.headers = { ...this.headers, ...headers }; - } - - getEndpoint(): string { - return this.endpoint; - } -} diff --git a/sdk/constructive-cli/src/public/cli/executor.ts b/sdk/constructive-cli/src/public/cli/executor.ts index 721e8e68e..fdfdc796d 100644 --- a/sdk/constructive-cli/src/public/cli/executor.ts +++ b/sdk/constructive-cli/src/public/cli/executor.ts @@ -3,7 +3,6 @@ * @generated by @constructive-io/graphql-codegen * DO NOT EDIT - changes will be overwritten */ -import { NodeHttpAdapter } from './node-fetch'; import { createConfigStore } from 'appstash'; import { createClient } from '../orm'; const store = createConfigStore('csdk'); @@ -29,6 +28,7 @@ export function getClient(contextName?: string) { } } return createClient({ - adapter: new NodeHttpAdapter(ctx.endpoint, headers), + endpoint: ctx.endpoint, + headers, }); } diff --git a/sdk/constructive-cli/src/public/cli/node-fetch.ts b/sdk/constructive-cli/src/public/cli/node-fetch.ts deleted file mode 100644 index 81bb05834..000000000 --- a/sdk/constructive-cli/src/public/cli/node-fetch.ts +++ /dev/null @@ -1,174 +0,0 @@ -/** - * Node HTTP adapter for localhost subdomain routing - * @generated by @constructive-io/graphql-codegen - * DO NOT EDIT - changes will be overwritten - */ -import http from 'node:http'; -import https from 'node:https'; - -import type { GraphQLAdapter, GraphQLError, QueryResult } from '@constructive-io/graphql-types'; - -interface HttpResponse { - statusCode: number; - statusMessage: string; - data: string; -} - -/** - * Check if a hostname is a localhost subdomain that needs special handling. - * Returns true for *.localhost (e.g. auth.localhost) but not bare "localhost". - */ -function isLocalhostSubdomain(hostname: string): boolean { - return hostname.endsWith('.localhost') && hostname !== 'localhost'; -} - -/** - * Make an HTTP/HTTPS request using native Node modules. - * Supports optional AbortSignal for request cancellation. - */ -function makeRequest( - url: URL, - options: http.RequestOptions, - body: string, - signal?: AbortSignal -): Promise { - return new Promise((resolve, reject) => { - if (signal?.aborted) { - reject(new Error('The operation was aborted')); - return; - } - - const protocol = url.protocol === 'https:' ? https : http; - - const req = protocol.request(url, options, (res) => { - let data = ''; - res.setEncoding('utf8'); - res.on('data', (chunk: string) => { - data += chunk; - }); - res.on('end', () => { - resolve({ - statusCode: res.statusCode || 0, - statusMessage: res.statusMessage || '', - data, - }); - }); - }); - - req.on('error', reject); - - if (signal) { - const onAbort = () => { - req.destroy(new Error('The operation was aborted')); - }; - signal.addEventListener('abort', onAbort, { once: true }); - req.on('close', () => { - signal.removeEventListener('abort', onAbort); - }); - } - - req.write(body); - req.end(); - }); -} - -/** - * Options for individual execute calls. - * Allows per-request header overrides and request cancellation. - */ -export interface NodeHttpExecuteOptions { - /** Additional headers to include in this request only */ - headers?: Record; - /** AbortSignal for request cancellation */ - signal?: AbortSignal; -} - -/** - * GraphQL adapter that uses node:http/node:https for requests. - * - * Handles *.localhost subdomains by rewriting the hostname to "localhost" - * and injecting the original Host header for server-side subdomain routing. - */ -export class NodeHttpAdapter implements GraphQLAdapter { - private headers: Record; - private url: URL; - - constructor( - private endpoint: string, - headers?: Record - ) { - this.headers = headers ?? {}; - this.url = new URL(endpoint); - } - - async execute( - document: string, - variables?: Record, - options?: NodeHttpExecuteOptions - ): Promise> { - const requestUrl = new URL(this.url.href); - const requestHeaders: Record = { - 'Content-Type': 'application/json', - Accept: 'application/json', - ...this.headers, - ...options?.headers, - }; - - // For *.localhost subdomains, rewrite hostname and inject Host header - if (isLocalhostSubdomain(requestUrl.hostname)) { - requestHeaders['Host'] = requestUrl.host; - requestUrl.hostname = 'localhost'; - } - - const body = JSON.stringify({ - query: document, - variables: variables ?? {}, - }); - - const requestOptions: http.RequestOptions = { - method: 'POST', - headers: requestHeaders, - }; - - const response = await makeRequest(requestUrl, requestOptions, body, options?.signal); - - if (response.statusCode < 200 || response.statusCode >= 300) { - return { - ok: false, - data: null, - errors: [ - { - message: `HTTP ${response.statusCode}: ${response.statusMessage}`, - }, - ], - }; - } - - const json = JSON.parse(response.data) as { - data?: T; - errors?: GraphQLError[]; - }; - - if (json.errors && json.errors.length > 0) { - return { - ok: false, - data: null, - errors: json.errors, - }; - } - - return { - ok: true, - data: json.data as T, - errors: undefined, - }; - } - - setHeaders(headers: Record): void { - this.headers = { ...this.headers, ...headers }; - } - - getEndpoint(): string { - return this.endpoint; - } -} diff --git a/sdk/constructive-cli/src/public/orm/node-fetch.ts b/sdk/constructive-cli/src/public/orm/node-fetch.ts deleted file mode 100644 index 81bb05834..000000000 --- a/sdk/constructive-cli/src/public/orm/node-fetch.ts +++ /dev/null @@ -1,174 +0,0 @@ -/** - * Node HTTP adapter for localhost subdomain routing - * @generated by @constructive-io/graphql-codegen - * DO NOT EDIT - changes will be overwritten - */ -import http from 'node:http'; -import https from 'node:https'; - -import type { GraphQLAdapter, GraphQLError, QueryResult } from '@constructive-io/graphql-types'; - -interface HttpResponse { - statusCode: number; - statusMessage: string; - data: string; -} - -/** - * Check if a hostname is a localhost subdomain that needs special handling. - * Returns true for *.localhost (e.g. auth.localhost) but not bare "localhost". - */ -function isLocalhostSubdomain(hostname: string): boolean { - return hostname.endsWith('.localhost') && hostname !== 'localhost'; -} - -/** - * Make an HTTP/HTTPS request using native Node modules. - * Supports optional AbortSignal for request cancellation. - */ -function makeRequest( - url: URL, - options: http.RequestOptions, - body: string, - signal?: AbortSignal -): Promise { - return new Promise((resolve, reject) => { - if (signal?.aborted) { - reject(new Error('The operation was aborted')); - return; - } - - const protocol = url.protocol === 'https:' ? https : http; - - const req = protocol.request(url, options, (res) => { - let data = ''; - res.setEncoding('utf8'); - res.on('data', (chunk: string) => { - data += chunk; - }); - res.on('end', () => { - resolve({ - statusCode: res.statusCode || 0, - statusMessage: res.statusMessage || '', - data, - }); - }); - }); - - req.on('error', reject); - - if (signal) { - const onAbort = () => { - req.destroy(new Error('The operation was aborted')); - }; - signal.addEventListener('abort', onAbort, { once: true }); - req.on('close', () => { - signal.removeEventListener('abort', onAbort); - }); - } - - req.write(body); - req.end(); - }); -} - -/** - * Options for individual execute calls. - * Allows per-request header overrides and request cancellation. - */ -export interface NodeHttpExecuteOptions { - /** Additional headers to include in this request only */ - headers?: Record; - /** AbortSignal for request cancellation */ - signal?: AbortSignal; -} - -/** - * GraphQL adapter that uses node:http/node:https for requests. - * - * Handles *.localhost subdomains by rewriting the hostname to "localhost" - * and injecting the original Host header for server-side subdomain routing. - */ -export class NodeHttpAdapter implements GraphQLAdapter { - private headers: Record; - private url: URL; - - constructor( - private endpoint: string, - headers?: Record - ) { - this.headers = headers ?? {}; - this.url = new URL(endpoint); - } - - async execute( - document: string, - variables?: Record, - options?: NodeHttpExecuteOptions - ): Promise> { - const requestUrl = new URL(this.url.href); - const requestHeaders: Record = { - 'Content-Type': 'application/json', - Accept: 'application/json', - ...this.headers, - ...options?.headers, - }; - - // For *.localhost subdomains, rewrite hostname and inject Host header - if (isLocalhostSubdomain(requestUrl.hostname)) { - requestHeaders['Host'] = requestUrl.host; - requestUrl.hostname = 'localhost'; - } - - const body = JSON.stringify({ - query: document, - variables: variables ?? {}, - }); - - const requestOptions: http.RequestOptions = { - method: 'POST', - headers: requestHeaders, - }; - - const response = await makeRequest(requestUrl, requestOptions, body, options?.signal); - - if (response.statusCode < 200 || response.statusCode >= 300) { - return { - ok: false, - data: null, - errors: [ - { - message: `HTTP ${response.statusCode}: ${response.statusMessage}`, - }, - ], - }; - } - - const json = JSON.parse(response.data) as { - data?: T; - errors?: GraphQLError[]; - }; - - if (json.errors && json.errors.length > 0) { - return { - ok: false, - data: null, - errors: json.errors, - }; - } - - return { - ok: true, - data: json.data as T, - errors: undefined, - }; - } - - setHeaders(headers: Record): void { - this.headers = { ...this.headers, ...headers }; - } - - getEndpoint(): string { - return this.endpoint; - } -}