diff --git a/config/scripts/generate-rpc-params-catalog.mjs b/config/scripts/generate-rpc-params-catalog.mjs index e42ea39beb1..9810f952e77 100644 --- a/config/scripts/generate-rpc-params-catalog.mjs +++ b/config/scripts/generate-rpc-params-catalog.mjs @@ -51,7 +51,14 @@ function indexableModules() { const source = readFileSync(path.join(RPC_DIR, file), 'utf8') for (const [, specifier] of source.matchAll(/from\s+'(\.[^']+)'/g)) { const resolved = `${path.resolve(path.dirname(path.join(RPC_DIR, file)), specifier)}.ts` - if (resolved.startsWith(`${SHARED_DIR}${path.sep}`) && existsSync(resolved)) { + // Never re-add the generator's own output: a module under RPC_DIR may import the + // catalog for a type-only contract, and bundling a stale catalog makes regeneration + // crash in exactly the state that requires regenerating. + if ( + resolved !== OUTPUT_PATH && + resolved.startsWith(`${SHARED_DIR}${path.sep}`) && + existsSync(resolved) + ) { modules.add(resolved) } } diff --git a/src/main/runtime/rpc/rpc-params-type-parity.ts b/src/main/runtime/rpc/rpc-params-type-parity.ts new file mode 100644 index 00000000000..263a7fe38cd --- /dev/null +++ b/src/main/runtime/rpc/rpc-params-type-parity.ts @@ -0,0 +1,42 @@ +import type { + RpcMethodName, + RpcParams +} from '../../../shared/rpc-contract/rpc-params-catalog.generated' +import type { RpcAnyMethodDeclaration } from './core' +import type { ALL_RPC_METHODS } from './methods' + +type RegisteredMethod = (typeof ALL_RPC_METHODS)[number] + +// These schemas reach into src/main and have no shared catalog entry. +type UncataloguedMethod = 'emulator.install' | 'orchestration.send' | 'orchestration.taskUpdate' + +type IsAny = 0 extends 1 & T ? true : false + +type ParamsMatch = + IsAny extends true + ? false + : IsAny extends true + ? false + : [Host] extends [Catalog] + ? [Catalog] extends [Host] + ? true + : false + : false + +// Distribute over declarations so each handler is checked, including streaming handlers. +type MismatchedMethod = + Method extends RpcAnyMethodDeclaration + ? Method['name'] extends RpcMethodName + ? ParamsMatch[0], RpcParams> extends true + ? never + : Method['name'] + : Exclude + : never + +type AssertNever = T + +// Type-only gates belong in the node typecheck; runtime parsing is a separate contract. +export type RpcParamsTypeParity = AssertNever> +export type RpcParamsUncataloguedMethods = AssertNever< + Exclude> +>