Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 5 additions & 19 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -17,6 +17,7 @@ export {
EventProxy,
GetInterfaceProxyIdentity,
GetResponsibleModule,
InterfaceFunction,
IsInterfaceProxy,
RegisteringProxy,
RunWithResponsibleModule,
Expand Down Expand Up @@ -75,24 +76,6 @@ export function GetMetadata<

type Func<A extends any[] = any[], R = any> = (...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<ReturnType<T>>,
>(identity?: string): (...args: Parameters<T>) => Promise<R> {
const proxy = new AsyncProxy<T, R>(identity);
const func = (...args: Parameters<T>) => proxy.call(...args);
func.proxy = proxy;
return func;
}

type RID<T> = T extends (id: infer P, ...args: any[]) => void ? P : never;

type InterfaceImplType<T> = T extends RegisteringProxy<infer P>
Expand Down Expand Up @@ -328,3 +311,6 @@ export function GetInterfaceInstance(
(connection) => connection.id === connectionID,
);
}

export * from "./modules";
Comment thread
Upd4ting marked this conversation as resolved.
export * from "./runtime";
2 changes: 1 addition & 1 deletion src/modules.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { EventProxy, InterfaceFunction } from ".";
import {
getModuleContext,
internal,
Expand All @@ -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<T>(
Expand Down
11 changes: 11 additions & 0 deletions src/proxies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,17 @@ export class AsyncProxy<T extends Func = Func, R = Awaited<ReturnType<T>>> {
}
}

/** Creates an interface function backed by an asynchronous proxy. */
export function InterfaceFunction<
T extends Func = Func,
R = Awaited<ReturnType<T>>,
>(identity?: string): (...args: Parameters<T>) => Promise<R> {
const proxy = new AsyncProxy<T, R>(identity);
const func = (...args: Parameters<T>) => proxy.call(...args);
func.proxy = proxy;
return func;
}

/** Proxy for provider-aware register and unregister handlers. */
export class RegisteringProxy<T extends RegisterFunction = RegisterFunction> {
public readonly [PROXY_BRAND]: ProxyBrand;
Expand Down
2 changes: 1 addition & 1 deletion src/runtime.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { InterfaceFunction } from ".";
import { InterfaceFunction } from "./proxies";

/**
* Information about the runtime environment of the running Antelope project.
Expand Down
64 changes: 64 additions & 0 deletions src/tests/root-declarations.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
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);
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);
});

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");
});
});
48 changes: 42 additions & 6 deletions test/package-consumer.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,23 @@ 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);
}

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 = {
Expand All @@ -42,13 +53,36 @@ const context: ModuleExecutionContext = {
};
void connection;
void context;
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);
Expand Down Expand Up @@ -100,6 +134,7 @@ modules.RunWithModuleContext(providerContext, () => {
process.exitCode = 1;
});
`;
}

try {
run("corepack", ["pnpm", "run", "build"], repository);
Expand Down Expand Up @@ -135,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 });
}
Loading