refactor: introduce explicit query error seam contracts
This commit is contained in:
@@ -8,6 +8,7 @@ import { QueryNativeDirectAdapter } from './query-native-direct-adapter.js';
|
||||
import { QueryNativeHotpathAdapter } from './query-native-hotpath-adapter.js';
|
||||
import { formatQueryRawOutput } from './query-raw-output-projection.js';
|
||||
import { failureToolsError, timeoutToolsError } from './query-tools-error-factory.js';
|
||||
import type { QueryNativeErrorFactory, QueryToolsErrorFactory } from './query-tools-error-seam.js';
|
||||
|
||||
export interface GSDToolsRuntime {
|
||||
registry: ReturnType<typeof createRegistry>;
|
||||
@@ -28,24 +29,32 @@ export function createGSDToolsRuntime(opts: {
|
||||
}): GSDToolsRuntime {
|
||||
const registry = createRegistry(opts.eventStream, opts.sessionId);
|
||||
|
||||
const queryToolsErrorFactory: QueryToolsErrorFactory = {
|
||||
createTimeoutError: (message, command, args, stderr, timeoutMs) =>
|
||||
timeoutToolsError(message, command, args, stderr, timeoutMs),
|
||||
createFailureError: (message, command, args, exitCode, stderr) =>
|
||||
failureToolsError(message, command, args, exitCode, stderr),
|
||||
};
|
||||
|
||||
const subprocessAdapter = new QuerySubprocessAdapter({
|
||||
projectDir: opts.projectDir,
|
||||
gsdToolsPath: opts.gsdToolsPath,
|
||||
timeoutMs: opts.timeoutMs,
|
||||
workstream: opts.workstream,
|
||||
createTimeoutError: (message, command, args, stderr, timeoutMs) =>
|
||||
timeoutToolsError(message, command, args, stderr, timeoutMs),
|
||||
createFailureError: (message, command, args, exitCode, stderr) =>
|
||||
failureToolsError(message, command, args, exitCode, stderr),
|
||||
...queryToolsErrorFactory,
|
||||
});
|
||||
|
||||
const nativeErrorFactory: QueryNativeErrorFactory = {
|
||||
createNativeTimeoutError: (message, command, args) =>
|
||||
timeoutToolsError(message, command, args, '', opts.timeoutMs),
|
||||
createNativeFailureError: (message, command, args, cause) =>
|
||||
failureToolsError(message, command, args, 1, '', cause),
|
||||
};
|
||||
|
||||
const nativeDirectAdapter = new QueryNativeDirectAdapter({
|
||||
timeoutMs: opts.timeoutMs,
|
||||
dispatch: (registryCommand, registryArgs) => registry.dispatch(registryCommand, registryArgs, opts.projectDir),
|
||||
createTimeoutError: (message, command, args) =>
|
||||
timeoutToolsError(message, command, args, '', opts.timeoutMs),
|
||||
createFailureError: (message, command, args, cause) =>
|
||||
failureToolsError(message, command, args, 1, '', cause),
|
||||
...nativeErrorFactory,
|
||||
});
|
||||
|
||||
const transport = new GSDTransport(registry, {
|
||||
|
||||
@@ -9,8 +9,8 @@ describe('QueryNativeDirectAdapter', () => {
|
||||
dispatch: async () => {
|
||||
throw new Error('boom');
|
||||
},
|
||||
createTimeoutError: (message, command, args) => GSDToolsError.timeout(message, command, args),
|
||||
createFailureError: (message, command, args, cause) => GSDToolsError.failure(message, command, args, 1, '', { cause }),
|
||||
createNativeTimeoutError: (message, command, args) => GSDToolsError.timeout(message, command, args),
|
||||
createNativeFailureError: (message, command, args, cause) => GSDToolsError.failure(message, command, args, 1, '', { cause }),
|
||||
});
|
||||
|
||||
await expect(adapter.dispatchJson('state', ['load'], 'state.load', [])).rejects.toMatchObject({
|
||||
@@ -26,8 +26,8 @@ describe('QueryNativeDirectAdapter', () => {
|
||||
dispatch: async () => {
|
||||
throw timeoutErr;
|
||||
},
|
||||
createTimeoutError: (message, command, args) => GSDToolsError.timeout(message, command, args),
|
||||
createFailureError: (message, command, args, cause) => GSDToolsError.failure(message, command, args, 1, '', { cause }),
|
||||
createNativeTimeoutError: (message, command, args) => GSDToolsError.timeout(message, command, args),
|
||||
createNativeFailureError: (message, command, args, cause) => GSDToolsError.failure(message, command, args, 1, '', { cause }),
|
||||
});
|
||||
|
||||
await expect(adapter.dispatchJson('state', ['load'], 'state.load', [])).rejects.toBe(timeoutErr);
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { formatQueryRawOutput } from './query-raw-output-projection.js';
|
||||
import { GSDToolsError } from './gsd-tools-error.js';
|
||||
import { errorMessage, timeoutMessage } from './query-failure-classification.js';
|
||||
import type { QueryNativeErrorFactory } from './query-tools-error-seam.js';
|
||||
import type { QueryResult } from './query/utils.js';
|
||||
|
||||
export interface QueryNativeDirectAdapterDeps {
|
||||
export interface QueryNativeDirectAdapterDeps extends QueryNativeErrorFactory {
|
||||
timeoutMs: number;
|
||||
dispatch: (registryCommand: string, registryArgs: string[]) => Promise<QueryResult>;
|
||||
createTimeoutError: (message: string, command: string, args: string[]) => GSDToolsError;
|
||||
createFailureError: (message: string, command: string, args: string[], cause: unknown) => GSDToolsError;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -21,7 +20,7 @@ export class QueryNativeDirectAdapter {
|
||||
return await this.withTimeout(legacyCommand, legacyArgs, this.deps.dispatch(registryCommand, registryArgs));
|
||||
} catch (error) {
|
||||
if (error instanceof GSDToolsError) throw error;
|
||||
throw this.deps.createFailureError(errorMessage(error), legacyCommand, legacyArgs, error);
|
||||
throw this.deps.createNativeFailureError(errorMessage(error), legacyCommand, legacyArgs, error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +39,7 @@ export class QueryNativeDirectAdapter {
|
||||
const timeoutPromise = new Promise<never>((_, reject) => {
|
||||
timeoutId = setTimeout(() => {
|
||||
reject(
|
||||
this.deps.createTimeoutError(
|
||||
this.deps.createNativeTimeoutError(
|
||||
timeoutMessage(legacyCommand, legacyArgs, this.deps.timeoutMs),
|
||||
legacyCommand,
|
||||
legacyArgs,
|
||||
|
||||
@@ -1,27 +1,13 @@
|
||||
import { execFile } from 'node:child_process';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { timeoutMessage } from './query-failure-classification.js';
|
||||
import type { GSDToolsError } from './gsd-tools-error.js';
|
||||
import type { QueryToolsErrorFactory } from './query-tools-error-seam.js';
|
||||
|
||||
export interface QuerySubprocessAdapterDeps {
|
||||
export interface QuerySubprocessAdapterDeps extends QueryToolsErrorFactory {
|
||||
projectDir: string;
|
||||
gsdToolsPath: string;
|
||||
timeoutMs: number;
|
||||
workstream?: string;
|
||||
createTimeoutError: (
|
||||
message: string,
|
||||
command: string,
|
||||
args: string[],
|
||||
stderr: string,
|
||||
timeoutMs: number,
|
||||
) => GSDToolsError;
|
||||
createFailureError: (
|
||||
message: string,
|
||||
command: string,
|
||||
args: string[],
|
||||
exitCode: number | null,
|
||||
stderr: string,
|
||||
) => GSDToolsError;
|
||||
}
|
||||
|
||||
export class QuerySubprocessAdapter {
|
||||
|
||||
28
sdk/src/query-tools-error-seam.ts
Normal file
28
sdk/src/query-tools-error-seam.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import type { GSDToolsError } from './gsd-tools-error.js';
|
||||
|
||||
export interface QueryTimeoutErrorFactory {
|
||||
createTimeoutError: (
|
||||
message: string,
|
||||
command: string,
|
||||
args: string[],
|
||||
stderr: string,
|
||||
timeoutMs: number,
|
||||
) => GSDToolsError;
|
||||
}
|
||||
|
||||
export interface QueryFailureErrorFactory {
|
||||
createFailureError: (
|
||||
message: string,
|
||||
command: string,
|
||||
args: string[],
|
||||
exitCode: number | null,
|
||||
stderr: string,
|
||||
) => GSDToolsError;
|
||||
}
|
||||
|
||||
export type QueryToolsErrorFactory = QueryTimeoutErrorFactory & QueryFailureErrorFactory;
|
||||
|
||||
export interface QueryNativeErrorFactory {
|
||||
createNativeTimeoutError: (message: string, command: string, args: string[]) => GSDToolsError;
|
||||
createNativeFailureError: (message: string, command: string, args: string[], cause: unknown) => GSDToolsError;
|
||||
}
|
||||
Reference in New Issue
Block a user