Files
plainleaf/client/service_registry.ts
T

73 lines
2.1 KiB
TypeScript

import type { Config } from "./config.ts";
import type { EventHook } from "./plugos/hooks/event.ts";
export type ServiceMatch = {
id?: string; // uuid, set automatically
priority?: number;
} & Record<string, any>;
export type ServiceSpec = {
selector: string;
match:
| ServiceMatch
| ((data: any) => Promise<ServiceMatch | null | undefined>);
run: (data: any) => Promise<any>;
};
export class ServiceRegistry {
constructor(
private eventHook: EventHook,
private config: Config,
) {}
public define(spec: ServiceSpec): void {
const id = globalThis.crypto.randomUUID();
// Register with discover:* event
this.config.insert(
["eventListeners", `discover:${spec.selector}`],
async (e: any) => {
const matchResult =
typeof spec.match === "function"
? await spec.match(e.data)
: spec.match;
if (matchResult) {
return {
...matchResult,
id,
};
}
},
);
// Register callback when invoked
this.config.insert(["eventListeners", `service:${id}`], (e: any) => {
return spec.run(e.data);
});
}
public async discover(selector: string, opts: any): Promise<ServiceMatch[]> {
const discoveryResults: ServiceMatch[] = await this.eventHook.dispatchEvent(
`discover:${selector}`,
opts,
);
discoveryResults.sort((a, b) => (b.priority || 0) - (a.priority || 0));
return discoveryResults;
}
public async invoke(match: ServiceMatch, data: any): Promise<any> {
const results = await this.eventHook.dispatchEvent(
`service:${match.id}`,
data,
);
// Note: results may be an empty array in case no service actually returned a result (void) case, which is implicitly passed on here
return results[0];
}
public async invokeBestMatch(selector: string, data: any): Promise<any> {
const results = await this.discover(selector, data);
if (results.length === 0) {
throw new Error(`No services matching: ${selector}`);
}
return this.invoke(results[0], data);
}
}