summaryrefslogtreecommitdiffhomepage
path: root/packages/kernel/src/host/host.ts
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-26 22:03:19 +0900
committerAdam Malczewski <[email protected]>2026-06-26 22:23:39 +0900
commit727c98c9dae516a2070eb950410314380a20c974 (patch)
tree52aa1022c54f11770be7e4e2a324f0a8b8b8deec /packages/kernel/src/host/host.ts
parente59dc11f63b1df51142259bb2c406af8c9c8c2bb (diff)
downloaddispatch-727c98c9dae516a2070eb950410314380a20c974.tar.gz
dispatch-727c98c9dae516a2070eb950410314380a20c974.zip
style: switch from tabs to 2-space indentation
Diffstat (limited to 'packages/kernel/src/host/host.ts')
-rw-r--r--packages/kernel/src/host/host.ts412
1 files changed, 206 insertions, 206 deletions
diff --git a/packages/kernel/src/host/host.ts b/packages/kernel/src/host/host.ts
index 2a262be..f74881f 100644
--- a/packages/kernel/src/host/host.ts
+++ b/packages/kernel/src/host/host.ts
@@ -1,22 +1,22 @@
import type { Bus } from "../bus/bus.js";
import type { AuthContract } from "../contracts/auth.js";
import type {
- ConfigAccess,
- EventsEmitter,
- Extension,
- HostAPI,
- Manifest,
- PermissionGate,
- ScheduledJob,
- SecretsAccess,
- StorageNamespace,
+ ConfigAccess,
+ EventsEmitter,
+ Extension,
+ HostAPI,
+ Manifest,
+ PermissionGate,
+ ScheduledJob,
+ SecretsAccess,
+ StorageNamespace,
} from "../contracts/extension.js";
import type {
- EventHandler,
- EventHookDescriptor,
- FilterDescriptor,
- FilterHandler,
- ServiceHandle,
+ EventHandler,
+ EventHookDescriptor,
+ FilterDescriptor,
+ FilterHandler,
+ ServiceHandle,
} from "../contracts/hooks.js";
import type { LogDeps, Logger, LogSink } from "../contracts/logging.js";
import type { ProviderContract } from "../contracts/provider.js";
@@ -28,210 +28,210 @@ import { isApiVersionCompatible } from "./version.js";
export const KERNEL_API_VERSION = "0.1.0";
export interface DisabledExtension {
- readonly manifest: Manifest;
- readonly reason: string;
+ readonly manifest: Manifest;
+ readonly reason: string;
}
export interface HostDeps {
- readonly logger: Logger;
- readonly config: ConfigAccess;
- readonly storageFactory: (namespace: string) => StorageNamespace;
- readonly secrets: SecretsAccess;
- readonly permissions: PermissionGate;
- readonly scheduler: { readonly register: (job: ScheduledJob) => void };
- readonly bus: Bus;
- readonly events: EventsEmitter;
- readonly logSink: LogSink;
- readonly logDeps: LogDeps;
+ readonly logger: Logger;
+ readonly config: ConfigAccess;
+ readonly storageFactory: (namespace: string) => StorageNamespace;
+ readonly secrets: SecretsAccess;
+ readonly permissions: PermissionGate;
+ readonly scheduler: { readonly register: (job: ScheduledJob) => void };
+ readonly bus: Bus;
+ readonly events: EventsEmitter;
+ readonly logSink: LogSink;
+ readonly logDeps: LogDeps;
}
export interface Host {
- readonly activate: () => Promise<void>;
- readonly deactivate: () => Promise<void>;
- readonly getTools: () => ReadonlyMap<string, ToolContract>;
- readonly getTool: (name: string) => ToolContract | undefined;
- readonly getProviders: () => ReadonlyMap<string, ProviderContract>;
- readonly getProvider: (id: string) => ProviderContract | undefined;
- readonly getAuthProviders: () => ReadonlyMap<string, AuthContract>;
- readonly getAuthProvider: (id: string) => AuthContract | undefined;
- readonly getScheduledJobs: () => readonly ScheduledJob[];
- readonly getMigrations: () => readonly string[];
- readonly getDisabled: () => readonly DisabledExtension[];
- readonly getExtensions: () => readonly Manifest[];
- readonly getHostAPI: () => HostAPI;
+ readonly activate: () => Promise<void>;
+ readonly deactivate: () => Promise<void>;
+ readonly getTools: () => ReadonlyMap<string, ToolContract>;
+ readonly getTool: (name: string) => ToolContract | undefined;
+ readonly getProviders: () => ReadonlyMap<string, ProviderContract>;
+ readonly getProvider: (id: string) => ProviderContract | undefined;
+ readonly getAuthProviders: () => ReadonlyMap<string, AuthContract>;
+ readonly getAuthProvider: (id: string) => AuthContract | undefined;
+ readonly getScheduledJobs: () => readonly ScheduledJob[];
+ readonly getMigrations: () => readonly string[];
+ readonly getDisabled: () => readonly DisabledExtension[];
+ readonly getExtensions: () => readonly Manifest[];
+ readonly getHostAPI: () => HostAPI;
}
export function createHost(extensions: readonly Extension[], deps: HostDeps): Host {
- const tools = new Map<string, ToolContract>();
- const providers = new Map<string, ProviderContract>();
- const authProviders = new Map<string, AuthContract>();
- const scheduledJobs: ScheduledJob[] = [];
- const migrations: string[] = [];
- const disabled: DisabledExtension[] = [];
- const activated: Extension[] = [];
+ const tools = new Map<string, ToolContract>();
+ const providers = new Map<string, ProviderContract>();
+ const authProviders = new Map<string, AuthContract>();
+ const scheduledJobs: ScheduledJob[] = [];
+ const migrations: string[] = [];
+ const disabled: DisabledExtension[] = [];
+ const activated: Extension[] = [];
- const ordered = resolveActivationOrder(extensions.map((e) => e.manifest));
- const extById = new Map<string, Extension>();
- for (const ext of extensions) {
- extById.set(ext.manifest.id, ext);
- }
+ const ordered = resolveActivationOrder(extensions.map((e) => e.manifest));
+ const extById = new Map<string, Extension>();
+ for (const ext of extensions) {
+ extById.set(ext.manifest.id, ext);
+ }
- const compatible: Extension[] = [];
- for (const m of ordered) {
- const ext = extById.get(m.id);
- if (ext === undefined) continue;
- if (isApiVersionCompatible(m.apiVersion, KERNEL_API_VERSION)) {
- compatible.push(ext);
- } else {
- disabled.push({
- manifest: m,
- reason: `apiVersion "${m.apiVersion}" is incompatible with kernel API ${KERNEL_API_VERSION}`,
- });
- deps.logger.warn(`Extension "${m.id}" disabled: apiVersion incompatible`);
- }
- }
+ const compatible: Extension[] = [];
+ for (const m of ordered) {
+ const ext = extById.get(m.id);
+ if (ext === undefined) continue;
+ if (isApiVersionCompatible(m.apiVersion, KERNEL_API_VERSION)) {
+ compatible.push(ext);
+ } else {
+ disabled.push({
+ manifest: m,
+ reason: `apiVersion "${m.apiVersion}" is incompatible with kernel API ${KERNEL_API_VERSION}`,
+ });
+ deps.logger.warn(`Extension "${m.id}" disabled: apiVersion incompatible`);
+ }
+ }
- for (const ext of compatible) {
- const extMigrations = ext.manifest.contributes?.migrations;
- if (extMigrations) {
- for (const migration of extMigrations) {
- migrations.push(migration);
- }
- }
- }
+ for (const ext of compatible) {
+ const extMigrations = ext.manifest.contributes?.migrations;
+ if (extMigrations) {
+ for (const migration of extMigrations) {
+ migrations.push(migration);
+ }
+ }
+ }
- function buildHostAPI(
- extensionId: string,
- opts?: { readonly registrationClosed?: boolean },
- ): HostAPI {
- const closed = opts?.registrationClosed ?? false;
- const extLogger = createLogger({ extensionId }, deps.logSink, deps.logDeps);
- return {
- defineTool(tool: ToolContract) {
- if (closed) throw new Error("Registration not available after activation");
- tools.set(tool.name, tool);
- },
- defineProvider(provider: ProviderContract) {
- if (closed) throw new Error("Registration not available after activation");
- providers.set(provider.id, provider);
- },
- defineAuth(auth: AuthContract) {
- if (closed) throw new Error("Registration not available after activation");
- authProviders.set(auth.id, auth);
- },
- on<TPayload>(hook: EventHookDescriptor<TPayload>, handler: EventHandler<TPayload>) {
- return deps.bus.on(hook, handler);
- },
- emit<TPayload>(hook: EventHookDescriptor<TPayload>, payload: TPayload) {
- deps.bus.emit(hook, payload);
- },
- addFilter<TValue>(hook: FilterDescriptor<TValue>, fn: FilterHandler<TValue>) {
- return deps.bus.addFilter(hook, fn);
- },
- async applyFilters<TValue>(
- hook: FilterDescriptor<TValue>,
- value: TValue,
- opts?: { readonly failClosed?: boolean },
- ): Promise<TValue> {
- return deps.bus.applyFilters(hook, value, opts);
- },
- provideService<T>(handle: ServiceHandle<T>, impl: T) {
- deps.bus.provideService(handle, impl);
- },
- getService<T>(handle: ServiceHandle<T>): T {
- return deps.bus.getService(handle);
- },
- storage(namespace: string): StorageNamespace {
- return deps.storageFactory(namespace);
- },
- config: deps.config,
- secrets: deps.secrets,
- permissions: deps.permissions,
- events: deps.events,
- logger: extLogger,
- getProviders() {
- return providers;
- },
- getTools() {
- return tools;
- },
- getAuthProviders() {
- return authProviders;
- },
- getAuthProvider(id: string) {
- return authProviders.get(id);
- },
- getExtensions() {
- return Object.freeze(activated.map((e) => e.manifest));
- },
- scheduler: {
- register(job: ScheduledJob) {
- scheduledJobs.push(job);
- deps.scheduler.register(job);
- },
- },
- };
- }
+ function buildHostAPI(
+ extensionId: string,
+ opts?: { readonly registrationClosed?: boolean },
+ ): HostAPI {
+ const closed = opts?.registrationClosed ?? false;
+ const extLogger = createLogger({ extensionId }, deps.logSink, deps.logDeps);
+ return {
+ defineTool(tool: ToolContract) {
+ if (closed) throw new Error("Registration not available after activation");
+ tools.set(tool.name, tool);
+ },
+ defineProvider(provider: ProviderContract) {
+ if (closed) throw new Error("Registration not available after activation");
+ providers.set(provider.id, provider);
+ },
+ defineAuth(auth: AuthContract) {
+ if (closed) throw new Error("Registration not available after activation");
+ authProviders.set(auth.id, auth);
+ },
+ on<TPayload>(hook: EventHookDescriptor<TPayload>, handler: EventHandler<TPayload>) {
+ return deps.bus.on(hook, handler);
+ },
+ emit<TPayload>(hook: EventHookDescriptor<TPayload>, payload: TPayload) {
+ deps.bus.emit(hook, payload);
+ },
+ addFilter<TValue>(hook: FilterDescriptor<TValue>, fn: FilterHandler<TValue>) {
+ return deps.bus.addFilter(hook, fn);
+ },
+ async applyFilters<TValue>(
+ hook: FilterDescriptor<TValue>,
+ value: TValue,
+ opts?: { readonly failClosed?: boolean },
+ ): Promise<TValue> {
+ return deps.bus.applyFilters(hook, value, opts);
+ },
+ provideService<T>(handle: ServiceHandle<T>, impl: T) {
+ deps.bus.provideService(handle, impl);
+ },
+ getService<T>(handle: ServiceHandle<T>): T {
+ return deps.bus.getService(handle);
+ },
+ storage(namespace: string): StorageNamespace {
+ return deps.storageFactory(namespace);
+ },
+ config: deps.config,
+ secrets: deps.secrets,
+ permissions: deps.permissions,
+ events: deps.events,
+ logger: extLogger,
+ getProviders() {
+ return providers;
+ },
+ getTools() {
+ return tools;
+ },
+ getAuthProviders() {
+ return authProviders;
+ },
+ getAuthProvider(id: string) {
+ return authProviders.get(id);
+ },
+ getExtensions() {
+ return Object.freeze(activated.map((e) => e.manifest));
+ },
+ scheduler: {
+ register(job: ScheduledJob) {
+ scheduledJobs.push(job);
+ deps.scheduler.register(job);
+ },
+ },
+ };
+ }
- return {
- async activate() {
- for (const ext of compatible) {
- try {
- await ext.activate(buildHostAPI(ext.manifest.id));
- activated.push(ext);
- deps.logger.info(`Extension "${ext.manifest.id}" activated`);
- } catch (err) {
- disabled.push({
- manifest: ext.manifest,
- reason: `Activation failed: ${err instanceof Error ? err.message : String(err)}`,
- });
- deps.logger.error(`Extension "${ext.manifest.id}" failed to activate`, { err });
- }
- }
- },
- async deactivate() {
- for (let i = activated.length - 1; i >= 0; i--) {
- const ext = activated[i];
- if (ext === undefined || ext.deactivate === undefined) continue;
- try {
- await ext.deactivate();
- } catch (err) {
- deps.logger.error(`Extension "${ext.manifest.id}" failed to deactivate`, { err });
- }
- }
- },
- getTools() {
- return tools;
- },
- getTool(name: string) {
- return tools.get(name);
- },
- getProviders() {
- return providers;
- },
- getProvider(id: string) {
- return providers.get(id);
- },
- getAuthProviders() {
- return authProviders;
- },
- getAuthProvider(id: string) {
- return authProviders.get(id);
- },
- getScheduledJobs() {
- return scheduledJobs;
- },
- getMigrations() {
- return migrations;
- },
- getDisabled() {
- return disabled;
- },
- getExtensions() {
- return Object.freeze(activated.map((e) => e.manifest));
- },
- getHostAPI() {
- return buildHostAPI("__host__", { registrationClosed: true });
- },
- };
+ return {
+ async activate() {
+ for (const ext of compatible) {
+ try {
+ await ext.activate(buildHostAPI(ext.manifest.id));
+ activated.push(ext);
+ deps.logger.info(`Extension "${ext.manifest.id}" activated`);
+ } catch (err) {
+ disabled.push({
+ manifest: ext.manifest,
+ reason: `Activation failed: ${err instanceof Error ? err.message : String(err)}`,
+ });
+ deps.logger.error(`Extension "${ext.manifest.id}" failed to activate`, { err });
+ }
+ }
+ },
+ async deactivate() {
+ for (let i = activated.length - 1; i >= 0; i--) {
+ const ext = activated[i];
+ if (ext === undefined || ext.deactivate === undefined) continue;
+ try {
+ await ext.deactivate();
+ } catch (err) {
+ deps.logger.error(`Extension "${ext.manifest.id}" failed to deactivate`, { err });
+ }
+ }
+ },
+ getTools() {
+ return tools;
+ },
+ getTool(name: string) {
+ return tools.get(name);
+ },
+ getProviders() {
+ return providers;
+ },
+ getProvider(id: string) {
+ return providers.get(id);
+ },
+ getAuthProviders() {
+ return authProviders;
+ },
+ getAuthProvider(id: string) {
+ return authProviders.get(id);
+ },
+ getScheduledJobs() {
+ return scheduledJobs;
+ },
+ getMigrations() {
+ return migrations;
+ },
+ getDisabled() {
+ return disabled;
+ },
+ getExtensions() {
+ return Object.freeze(activated.map((e) => e.manifest));
+ },
+ getHostAPI() {
+ return buildHostAPI("__host__", { registrationClosed: true });
+ },
+ };
}