From 7896665a26df8c776ab3b39cb64bffba7dc6ef7e Mon Sep 17 00:00:00 2001 From: Upd4ting Date: Fri, 21 Aug 2026 16:32:08 +0000 Subject: [PATCH 1/2] fix(runtime): export core interface declarations --- src/index.ts | 3 +++ src/tests/root-declarations.test.ts | 17 +++++++++++++++++ test/package-consumer.mjs | 13 ++++++++++++- 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 src/tests/root-declarations.test.ts diff --git a/src/index.ts b/src/index.ts index b0dc34a..20c229e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -328,3 +328,6 @@ export function GetInterfaceInstance( (connection) => connection.id === connectionID, ); } + +export * from "./modules"; +export * from "./runtime"; diff --git a/src/tests/root-declarations.test.ts b/src/tests/root-declarations.test.ts new file mode 100644 index 0000000..70c3fa5 --- /dev/null +++ b/src/tests/root-declarations.test.ts @@ -0,0 +1,17 @@ +import { expect } from "chai"; +import * as declarations from ".."; +import * as modules from "../modules"; +import * as runtime from "../runtime"; + +describe("root interface declarations", () => { + it("exports the canonical runtime proxies", () => { + expect(declarations.GetRuntimeInfo).to.equal(runtime.GetRuntimeInfo); + expect(declarations.RegisterDevServer).to.equal(runtime.RegisterDevServer); + }); + + it("exports the canonical module proxies", () => { + expect(declarations.Events).to.equal(modules.Events); + expect(declarations.ListModules).to.equal(modules.ListModules); + expect(declarations.GetModuleInfo).to.equal(modules.GetModuleInfo); + }); +}); diff --git a/test/package-consumer.mjs b/test/package-consumer.mjs index f7668dc..92e10c4 100644 --- a/test/package-consumer.mjs +++ b/test/package-consumer.mjs @@ -28,7 +28,11 @@ function createConsumer(tarball) { } const typeConsumerSource = ` -import type { InterfaceConnection } from "@antelopejs/interface-core"; +import { + GetRuntimeInfo, + ListModules, + type InterfaceConnection, +} from "@antelopejs/interface-core"; import type { ModuleExecutionContext } from "@antelopejs/interface-core/modules"; const connection: InterfaceConnection = { @@ -42,6 +46,8 @@ const context: ModuleExecutionContext = { }; void connection; void context; +void GetRuntimeInfo; +void ListModules; `; const consumerSource = ` @@ -49,6 +55,11 @@ const assert = require("node:assert/strict"); const core = require("@antelopejs/interface-core"); const { internal } = require("@antelopejs/interface-core/internal"); const modules = require("@antelopejs/interface-core/modules"); +const runtime = require("@antelopejs/interface-core/runtime"); + +assert.equal(core.GetRuntimeInfo, runtime.GetRuntimeInfo); +assert.equal(core.RegisterDevServer, runtime.RegisterDevServer); +assert.equal(core.ListModules, modules.ListModules); const proxy = core.InterfaceFunction("package-consumer.context"); const identity = core.GetInterfaceProxyIdentity(proxy.proxy); From 065c73b58029d003de159e7cd72e12ea56be2ca6 Mon Sep 17 00:00:00 2001 From: Upd4ting Date: Fri, 21 Aug 2026 16:39:42 +0000 Subject: [PATCH 2/2] fix(runtime): remove declaration import cycle --- src/index.ts | 21 ++----------- src/modules.ts | 2 +- src/proxies.ts | 11 +++++++ src/runtime.ts | 2 +- src/tests/root-declarations.test.ts | 47 +++++++++++++++++++++++++++++ test/package-consumer.mjs | 35 ++++++++++++++++++--- 6 files changed, 92 insertions(+), 26 deletions(-) diff --git a/src/index.ts b/src/index.ts index 20c229e..e99a8ab 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,7 +3,7 @@ import type { Class } from "./decorators"; import { type InterfaceConnection, internal } from "./internal"; import { Logging } from "./logging"; import { - AsyncProxy, + type AsyncProxy, type EventProxy, GetResponsibleModule, IsInterfaceProxy, @@ -17,6 +17,7 @@ export { EventProxy, GetInterfaceProxyIdentity, GetResponsibleModule, + InterfaceFunction, IsInterfaceProxy, RegisteringProxy, RunWithResponsibleModule, @@ -75,24 +76,6 @@ export function GetMetadata< type Func = (...args: A) => R; -/** - * Creates an interface function proxy. - * - * Returns a function that routes calls through an AsyncProxy, allowing for module-aware - * asynchronous function calls that can be implemented by other modules. - * - * @returns A function that proxies calls to the implementation when available - */ -export function InterfaceFunction< - T extends Func = Func, - R = Awaited>, ->(identity?: string): (...args: Parameters) => Promise { - const proxy = new AsyncProxy(identity); - const func = (...args: Parameters) => proxy.call(...args); - func.proxy = proxy; - return func; -} - type RID = T extends (id: infer P, ...args: any[]) => void ? P : never; type InterfaceImplType = T extends RegisteringProxy diff --git a/src/modules.ts b/src/modules.ts index a708a05..33c3af1 100644 --- a/src/modules.ts +++ b/src/modules.ts @@ -1,4 +1,3 @@ -import { EventProxy, InterfaceFunction } from "."; import { getModuleContext, internal, @@ -8,6 +7,7 @@ import { type RuntimeCleanup, runWithModuleContext, } from "./internal"; +import { EventProxy, InterfaceFunction } from "./proxies"; /** Runs work with module ownership and an optional provider route across awaits. */ export function RunWithModuleContext( diff --git a/src/proxies.ts b/src/proxies.ts index 212aa6c..1db7b2f 100644 --- a/src/proxies.ts +++ b/src/proxies.ts @@ -339,6 +339,17 @@ export class AsyncProxy>> { } } +/** Creates an interface function backed by an asynchronous proxy. */ +export function InterfaceFunction< + T extends Func = Func, + R = Awaited>, +>(identity?: string): (...args: Parameters) => Promise { + const proxy = new AsyncProxy(identity); + const func = (...args: Parameters) => proxy.call(...args); + func.proxy = proxy; + return func; +} + /** Proxy for provider-aware register and unregister handlers. */ export class RegisteringProxy { public readonly [PROXY_BRAND]: ProxyBrand; diff --git a/src/runtime.ts b/src/runtime.ts index 0de93aa..50da403 100644 --- a/src/runtime.ts +++ b/src/runtime.ts @@ -1,4 +1,4 @@ -import { InterfaceFunction } from "."; +import { InterfaceFunction } from "./proxies"; /** * Information about the runtime environment of the running Antelope project. diff --git a/src/tests/root-declarations.test.ts b/src/tests/root-declarations.test.ts index 70c3fa5..5ef9aa7 100644 --- a/src/tests/root-declarations.test.ts +++ b/src/tests/root-declarations.test.ts @@ -1,8 +1,47 @@ +import { execFileSync } from "node:child_process"; +import { resolve } from "node:path"; import { expect } from "chai"; import * as declarations from ".."; import * as modules from "../modules"; import * as runtime from "../runtime"; +const PACKAGE_ROOT = resolve(__dirname, "..", ".."); +const FRESH_PROCESS_CONTRACT = ` +const assert = require("node:assert/strict"); +const path = require("node:path"); +const root = process.env.INTERFACE_CORE_ROOT; +const entries = { + root: path.join(root, "dist"), + modules: path.join(root, "dist", "modules.js"), + runtime: path.join(root, "dist", "runtime.js"), +}; +const loaded = Object.fromEntries( + process.env.INTERFACE_CORE_ORDER.split(",").map((entry) => [entry, require(entries[entry])]), +); +const core = loaded.root; +const modules = loaded.modules; +const runtime = loaded.runtime; +assert.equal(core.Events, modules.Events); +assert.equal(core.ListModules, modules.ListModules); +assert.equal(core.GetModuleInfo, modules.GetModuleInfo); +assert.equal(core.GetRuntimeInfo, runtime.GetRuntimeInfo); +assert.equal(core.RegisterDevServer, runtime.RegisterDevServer); +assert.equal(core.IsInterfaceProxy(core.ListModules.proxy), true); +assert.equal(core.IsInterfaceProxy(core.GetRuntimeInfo.proxy), true); +assert.equal(core.GetInterfaceProxyIdentity(core.ListModules.proxy), "async:modules.ListModules"); +assert.equal(core.GetInterfaceProxyIdentity(core.GetRuntimeInfo.proxy), "async:runtime.GetRuntimeInfo"); +`; + +function runFreshProcess(order: string): void { + execFileSync(process.execPath, ["-e", FRESH_PROCESS_CONTRACT], { + env: { + ...process.env, + INTERFACE_CORE_ORDER: order, + INTERFACE_CORE_ROOT: PACKAGE_ROOT, + }, + }); +} + describe("root interface declarations", () => { it("exports the canonical runtime proxies", () => { expect(declarations.GetRuntimeInfo).to.equal(runtime.GetRuntimeInfo); @@ -14,4 +53,12 @@ describe("root interface declarations", () => { expect(declarations.ListModules).to.equal(modules.ListModules); expect(declarations.GetModuleInfo).to.equal(modules.GetModuleInfo); }); + + it("loads complete canonical declarations when the root loads first", () => { + runFreshProcess("root,runtime,modules"); + }); + + it("loads complete canonical declarations when subpaths load first", () => { + runFreshProcess("modules,runtime,root"); + }); }); diff --git a/test/package-consumer.mjs b/test/package-consumer.mjs index 92e10c4..00710bd 100644 --- a/test/package-consumer.mjs +++ b/test/package-consumer.mjs @@ -23,7 +23,14 @@ function createConsumer(tarball) { }, }), ); - writeFileSync(join(temporary, "contract.cjs"), consumerSource); + writeFileSync( + join(temporary, "contract-root-first.cjs"), + createConsumerSource(rootFirstImports), + ); + writeFileSync( + join(temporary, "contract-subpaths-first.cjs"), + createConsumerSource(subpathsFirstImports), + ); writeFileSync(join(temporary, "contract.ts"), typeConsumerSource); } @@ -50,16 +57,32 @@ void GetRuntimeInfo; void ListModules; `; -const consumerSource = ` -const assert = require("node:assert/strict"); +const rootFirstImports = ` const core = require("@antelopejs/interface-core"); -const { internal } = require("@antelopejs/interface-core/internal"); const modules = require("@antelopejs/interface-core/modules"); const runtime = require("@antelopejs/interface-core/runtime"); +`; + +const subpathsFirstImports = ` +const modules = require("@antelopejs/interface-core/modules"); +const runtime = require("@antelopejs/interface-core/runtime"); +const core = require("@antelopejs/interface-core"); +`; + +function createConsumerSource(imports) { + return ` +const assert = require("node:assert/strict"); +${imports} +const { internal } = require("@antelopejs/interface-core/internal"); assert.equal(core.GetRuntimeInfo, runtime.GetRuntimeInfo); assert.equal(core.RegisterDevServer, runtime.RegisterDevServer); assert.equal(core.ListModules, modules.ListModules); +assert.equal(core.Events, modules.Events); +assert.equal(core.IsInterfaceProxy(core.GetRuntimeInfo.proxy), true); +assert.equal(core.IsInterfaceProxy(core.ListModules.proxy), true); +assert.equal(core.GetInterfaceProxyIdentity(core.GetRuntimeInfo.proxy), "async:runtime.GetRuntimeInfo"); +assert.equal(core.GetInterfaceProxyIdentity(core.ListModules.proxy), "async:modules.ListModules"); const proxy = core.InterfaceFunction("package-consumer.context"); const identity = core.GetInterfaceProxyIdentity(proxy.proxy); @@ -111,6 +134,7 @@ modules.RunWithModuleContext(providerContext, () => { process.exitCode = 1; }); `; +} try { run("corepack", ["pnpm", "run", "build"], repository); @@ -146,7 +170,8 @@ try { ], temporary, ); - run(process.execPath, ["contract.cjs"], temporary); + run(process.execPath, ["contract-root-first.cjs"], temporary); + run(process.execPath, ["contract-subpaths-first.cjs"], temporary); } finally { rmSync(temporary, { force: true, recursive: true }); }