feat(plugin-sdk): basic implementation for multi-transport
This commit is contained in:
@@ -18,6 +18,11 @@
|
||||
".": {
|
||||
"types": "./dist/index.d.mts",
|
||||
"default": "./dist/index.mjs"
|
||||
},
|
||||
"./plugin-host": {
|
||||
"types": "./dist/plugin-host/index.d.mts",
|
||||
"node": "./dist/plugin-host/runtimes/node/index.mjs",
|
||||
"default": "./dist/plugin-host/runtimes/web/index.mjs"
|
||||
}
|
||||
},
|
||||
"main": "./dist/index.mjs",
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import type { createContext } from '@moeru/eventa'
|
||||
|
||||
export type ChannelControlPlane = ReturnType<typeof createContext>
|
||||
export type ChannelHost = ReturnType<typeof createContext>
|
||||
|
||||
+7
-5
@@ -4,7 +4,7 @@ import { createContext, defineEventa, defineInvokeHandler } from '@moeru/eventa'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { FileSystemLoader } from '.'
|
||||
import { channels } from '../channels'
|
||||
import { createApis } from '../plugin/apis/client'
|
||||
import { protocolProviders } from '../plugin/apis/protocol'
|
||||
|
||||
describe('for FileSystemPluginHost', () => {
|
||||
@@ -21,10 +21,11 @@ describe('for FileSystemPluginHost', () => {
|
||||
}, { cwd: '' })
|
||||
|
||||
const ctx = createContext()
|
||||
const apis = createApis(ctx)
|
||||
const onVitestCall = vi.fn()
|
||||
ctx.on(defineEventa('vitest-call:init'), onVitestCall)
|
||||
|
||||
await expect(pluginDef.init({ host: ctx })).resolves.not.toThrow()
|
||||
await expect(pluginDef.init?.({ channels: { host: ctx }, apis })).resolves.not.toThrow()
|
||||
expect(onVitestCall).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
@@ -56,13 +57,14 @@ describe('for PluginHost', () => {
|
||||
}, { cwd: '' })
|
||||
|
||||
const ctx = createContext()
|
||||
const apis = createApis(ctx)
|
||||
const onVitestCall = vi.fn()
|
||||
ctx.on(defineEventa('vitest-call:init'), onVitestCall)
|
||||
|
||||
await expect(pluginDef.init({ host: ctx })).resolves.not.toThrow()
|
||||
await expect(pluginDef.init?.({ channels: { host: ctx }, apis })).resolves.not.toThrow()
|
||||
expect(onVitestCall).toHaveBeenCalledTimes(1)
|
||||
|
||||
defineInvokeHandler(channels.data, protocolProviders.listProviders, async () => {
|
||||
defineInvokeHandler(ctx, protocolProviders.listProviders, async () => {
|
||||
return [
|
||||
{ name: 'provider1' },
|
||||
]
|
||||
@@ -70,7 +72,7 @@ describe('for PluginHost', () => {
|
||||
|
||||
const onProviderListCall = vi.fn()
|
||||
ctx.on(protocolProviders.listProviders.sendEvent, onProviderListCall)
|
||||
await expect(pluginDef.setupModules?.()).resolves.not.toThrow()
|
||||
await expect(pluginDef.setupModules?.({ channels: { host: ctx }, apis })).resolves.not.toThrow()
|
||||
expect(onProviderListCall).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,169 @@
|
||||
import type { definePlugin } from '../plugin'
|
||||
import type { Plugin } from '../plugin/shared'
|
||||
|
||||
import { join } from 'node:path'
|
||||
import { cwd } from 'node:process'
|
||||
|
||||
/**
|
||||
* Plugin Host lifecycle overview (transport-aware):
|
||||
*
|
||||
* - The host loads a plugin entrypoint (local or remote).
|
||||
* - The host resolves a per-plugin transport (in-memory, worker, WebSocket, electron).
|
||||
* - The host creates an Eventa context bound to that transport.
|
||||
* - The host binds SDK APIs to the context and passes them into plugin.init.
|
||||
*
|
||||
* This design allows multiple plugins in one host without shared global channels.
|
||||
* Each plugin instance has its own context and transport, so local and remote
|
||||
* plugins share the same API surface while remaining isolated.
|
||||
*/
|
||||
/**
|
||||
* One plugin could contribute multiple modules.
|
||||
*
|
||||
* For plugin itself, there are two ways to implement it, either local plugin, or remote plugin.
|
||||
* Since we have @moeru/eventa as underlying event transmission, we can drive everything in event.
|
||||
*
|
||||
* It's ok that local plugin doesn't implement the remote protocol to handle the remote plugin
|
||||
* RPC if doesn't wish for. Purely local UI manipulation or local resource registration is normal.
|
||||
*
|
||||
* In another word, we could implement the plugin in same eventa definition, while switching
|
||||
* between two different transport.
|
||||
*
|
||||
* For local plugin, local context for in-memory transport will be used.
|
||||
* For remote plugin, server-runtime for WebSocket based transport will be used.
|
||||
*
|
||||
*
|
||||
* The procedure looks like this (regardless to the underlying transport since we will implement
|
||||
* in both):
|
||||
*
|
||||
* 0. Channel Gateway sits on top of all channels
|
||||
* 1. Connect to control plane channel (from plugin-sdk, or any language implementation will impl)
|
||||
* 2. Authenticate with module:authenticate
|
||||
* 3. Negotiate protocol/api compatibility before lifecycle work starts:
|
||||
* 1. Plugin sends module:compatibility:request with:
|
||||
* - plugin protocol version
|
||||
* - plugin sdk api version
|
||||
* - optional supported ranges for backward/forward compatibility
|
||||
* 2. Plugin Host replies module:compatibility:result with:
|
||||
* - accepted version tuple (protocol + api)
|
||||
* - compatibility mode (exact, downgraded, rejected)
|
||||
* - deterministic reason if rejected
|
||||
* 3. If rejected, host MUST stop initialization for that plugin and emit module:status
|
||||
* with incompatible-version details for Configurator visibility.
|
||||
* 4. Plugin Host will send registry:modules:sync, this ensures the auto plugin / dependency discovery
|
||||
* 5. Module will now announce itself to the entire system through module:announce
|
||||
* 6. Module will now sync to Plugin Host that module now preparing, declaring its:
|
||||
* 1. Dependencies to other plugins / modules
|
||||
* 2. Initial Configuration (doesn't relate to capabilities)
|
||||
* Note that for capabilities requires Database configuration, and perhaps Memory manipulation,
|
||||
* plugin should orchestrate itself to contribute many capabilities / features, and the needed
|
||||
* configurations and credentials should be requested and configured for each capabilities
|
||||
* instead.
|
||||
* 7. During this phase, if module failed to find the needed dependency, module:status will be emitted
|
||||
* to allow the Plugin Host to surface errors or notice up to Configurator layer, to display the
|
||||
* needed warning and status.
|
||||
*
|
||||
* It's ok for module to stay online / connected to channels. In this phase, module:announce
|
||||
* could happen multiple times. Module is ok to listen to the sync events and decide whether to enter
|
||||
* the next phases if needed.
|
||||
* 8. During this phase, if plugin successfully configured itself and calculated / computed the possible
|
||||
* contributing capabilities / features, it will emit module:prepared.
|
||||
* 9. During this phase, if module requires more configuration to fill and enable in order to go next
|
||||
* phase, it's ok, it will emit module:configuration:needed.
|
||||
* 10. Module should now emit module:prepared.
|
||||
* 11. Module should now emit module:configuration:needed, for telling the shape to Configurator.
|
||||
* In between, for user side / Configurator side:
|
||||
* - module:configuration:validate:request (static check, zod/valibot or programmatic checks)
|
||||
* - module:configuration:validate:status (with parent event id)
|
||||
* - module:configuration:validate:response
|
||||
* - module:configuration:plan:request (actually dry-run, ensures anything during runtime works)
|
||||
* - module:configuration:plan:status (with parent event id)
|
||||
* - module:configuration:plan:response
|
||||
* - module:configuration:commit
|
||||
* - module:configuration:commit:status (with parent event id)
|
||||
* 12. Module previously configured will get validate, plan, and commit automatically, if failed, status
|
||||
* will surface to the Configurator side for further noticing to user.
|
||||
* 13. Module should now emit module:configuration:configured.
|
||||
* 14. Module should now be able to calculate / compute possible capabilities / features to be able to
|
||||
* contribute to the system / Plugin Host, once calculated, module:contribute:capability:offer will
|
||||
* be emitted in (length of) capabilities times.
|
||||
*
|
||||
* This means for 1 module that offers 5 capabilities, 5 * module:contribute:capability:offer will
|
||||
* be emitted.
|
||||
* 15. Next, module will now enter the capability / feature fill-in phase, during this phase, it's ok
|
||||
* to say that the plugin is running but nothing gets contributed if none of them were configured.
|
||||
*
|
||||
* For any capabilities without further configuration and fill-in from Configurator and User side,
|
||||
* it can be automatically activated now (which is next phase for module:contribute:capability:*
|
||||
* events), module:contribute:capability:configuration:configured,
|
||||
* module:contribute:capability:activated will be emitted.
|
||||
*
|
||||
* If further configuration and actions needed, module:contribute:capability:configuration:needed
|
||||
* will be emitted.
|
||||
*
|
||||
* To configure the capabilities in sequence and correct order,
|
||||
* - module:contribute:capability:configuration:validate:request (static check, zod/valibot or programmatic checks)
|
||||
* - module:contribute:capability:configuration:validate:status (with parent event id)
|
||||
* - module:contribute:capability:configuration:validate:response
|
||||
* - module:contribute:capability:configuration:plan:request (actually dry-run, ensures anything during runtime works)
|
||||
* - module:contribute:capability:configuration:plan:status (with parent event id)
|
||||
* - module:contribute:capability:configuration:plan:response
|
||||
* - module:contribute:capability:configuration:commit
|
||||
* - module:contribute:capability:configuration:commit:status (with parent event id)
|
||||
* similar to module:configuration are accepted.
|
||||
*
|
||||
* 16. No matter what happens, the module:status should emit with ready status now.
|
||||
* 17. Any time the module need to re-calculate / re-compute, or wish to be re-configured, it's ok to
|
||||
* emit module:status:change with needed phase to update, if need to rollback to announced phase,
|
||||
* Plugin Host should treat the Module to be un-prepared status, the needed procedure will be called.
|
||||
*/
|
||||
|
||||
export class PluginHost {
|
||||
constructor() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export interface ManifestV1 {
|
||||
apiVersion: 'v1'
|
||||
kind: 'manifest.plugin.airi.moeru.ai'
|
||||
name: string
|
||||
entrypoints: {
|
||||
electron?: string
|
||||
}
|
||||
}
|
||||
|
||||
export class FileSystemLoader {
|
||||
constructor() {
|
||||
|
||||
}
|
||||
|
||||
async loadLazyPluginFor(manifest: ManifestV1, options?: { cwd?: string }) {
|
||||
const root = options?.cwd ?? cwd()
|
||||
if (!manifest.entrypoints.electron) {
|
||||
throw new Error(''
|
||||
+ 'For locally installed, defined plugin, electron entrypoint is required.'
|
||||
+ 'The value of `entrypoints.electron` should be the relative path to the '
|
||||
+ 'root of app.getPath(\'userData\').',
|
||||
)
|
||||
}
|
||||
|
||||
const entrypoint = join(root, manifest.entrypoints.electron)
|
||||
const pluginModule = await import(entrypoint) as { default: ReturnType<typeof definePlugin> }
|
||||
return pluginModule.default
|
||||
}
|
||||
|
||||
async loadPluginFor(manifest: ManifestV1, options?: { cwd?: string }) {
|
||||
const root = options?.cwd ?? cwd()
|
||||
if (!manifest.entrypoints.electron) {
|
||||
throw new Error(''
|
||||
+ 'For locally installed, defined plugin, electron entrypoint is required.'
|
||||
+ 'The value of `entrypoints.electron` should be the relative path to the '
|
||||
+ 'root of app.getPath(\'userData\').',
|
||||
)
|
||||
}
|
||||
|
||||
const entrypoint = join(root, manifest.entrypoints.electron)
|
||||
const pluginModule = await import(entrypoint) as Plugin
|
||||
return pluginModule
|
||||
}
|
||||
}
|
||||
@@ -1,158 +1,3 @@
|
||||
import type { definePlugin } from '../plugin'
|
||||
import type { Plugin } from '../plugin/shared'
|
||||
|
||||
import { join } from 'node:path'
|
||||
import { cwd } from 'node:process'
|
||||
|
||||
/**
|
||||
* Plugin Host lifecycle overview (transport-aware):
|
||||
*
|
||||
* - The host loads a plugin entrypoint (local or remote).
|
||||
* - The host resolves a per-plugin transport (in-memory, worker, WebSocket, electron).
|
||||
* - The host creates an Eventa context bound to that transport.
|
||||
* - The host binds SDK APIs to the context and passes them into plugin.init.
|
||||
*
|
||||
* This design allows multiple plugins in one host without shared global channels.
|
||||
* Each plugin instance has its own context and transport, so local and remote
|
||||
* plugins share the same API surface while remaining isolated.
|
||||
*/
|
||||
/**
|
||||
* One plugin could contribute multiple modules.
|
||||
*
|
||||
* For plugin itself, there are two ways to implement it, either local plugin, or remote plugin.
|
||||
* Since we have @moeru/eventa as underlying event transmission, we can drive everything in event.
|
||||
*
|
||||
* It's ok that local plugin doesn't implement the remote protocol to handle the remote plugin
|
||||
* RPC if doesn't wish for. Purely local UI manipulation or local resource registration is normal.
|
||||
*
|
||||
* In another word, we could implement the plugin in same eventa definition, while switching
|
||||
* between two different transport.
|
||||
*
|
||||
* For local plugin, local context for in-memory transport will be used.
|
||||
* For remote plugin, server-runtime for WebSocket based transport will be used.
|
||||
*
|
||||
*
|
||||
* The procedure looks like this (regardless to the underlying transport since we will implement
|
||||
* in both):
|
||||
*
|
||||
* 0. Channel Gateway sits on top of all channels
|
||||
* 1. Connect to control plane channel (from plugin-sdk, or any language implementation will impl)
|
||||
* 2. Authenticate with module:authenticate
|
||||
* 3. Plugin Host will send registry:modules:sync, this ensures the auto plugin / dependency discovery
|
||||
* 4. Module will now announce itself to the entire system through module:announce
|
||||
* 5. Module will now sync to Plugin Host that module now preparing, declaring its:
|
||||
* 1. Dependencies to other plugins / modules
|
||||
* 2. Initial Configuration (doesn't relate to capabilities)
|
||||
* Note that for capabilities requires Database configuration, and perhaps Memory manipulation,
|
||||
* plugin should orchestrate itself to contribute many capabilities / features, and the needed
|
||||
* configurations and credentials should be requested and configured for each capabilities
|
||||
* instead.
|
||||
* 6. During this phase, if module failed to find the needed dependency, module:status will be emitted
|
||||
* to allow the Plugin Host to surface errors or notice up to Configurator layer, to display the
|
||||
* needed warning and status.
|
||||
*
|
||||
* It's ok for module to stay online / connected to channels. In this phase, module:announce
|
||||
* could happen multiple times. Module is ok to listen to the sync events and decide whether to enter
|
||||
* the next phases if needed.
|
||||
* 7. During this phase, if plugin successfully configured itself and calculated / computed the possible
|
||||
* contributing capabilities / features, it will emit module:prepared.
|
||||
* 8. During this phase, if module requires more configuration to fill and enable in order to go next
|
||||
* phase, it's ok, it will emit module:configuration:needed.
|
||||
* 8. Module should now emit module:prepared.
|
||||
* 9. Module should now emit module:configuration:needed, for telling the shape to Configurator.
|
||||
* In between, for user side / Configurator side:
|
||||
* - module:configuration:validate:request (static check, zod/valibot or programmatic checks)
|
||||
* - module:configuration:validate:status (with parent event id)
|
||||
* - module:configuration:validate:response
|
||||
* - module:configuration:plan:request (actually dry-run, ensures anything during runtime works)
|
||||
* - module:configuration:plan:status (with parent event id)
|
||||
* - module:configuration:plan:response
|
||||
* - module:configuration:commit
|
||||
* - module:configuration:commit:status (with parent event id)
|
||||
* 9. Module previously configured will get validate, plan, and commit automatically, if failed, status
|
||||
* will surface to the Configurator side for further noticing to user.
|
||||
* 10. Module should now emit module:configuration:configured.
|
||||
* 11. Module should now be able to calculate / compute possible capabilities / features to be able to
|
||||
* contribute to the system / Plugin Host, once calculated, module:contribute:capability:offer will
|
||||
* be emitted in (length of) capabilities times.
|
||||
*
|
||||
* This means for 1 module that offers 5 capabilities, 5 * module:contribute:capability:offer will
|
||||
* be emitted.
|
||||
* 12. Next, module will now enter the capability / feature fill-in phase, during this phase, it's ok
|
||||
* to say that the plugin is running but nothing gets contributed if none of them were configured.
|
||||
*
|
||||
* For any capabilities without further configuration and fill-in from Configurator and User side,
|
||||
* it can be automatically activated now (which is next phase for module:contribute:capability:*
|
||||
* events), module:contribute:capability:configuration:configured,
|
||||
* module:contribute:capability:activated will be emitted.
|
||||
*
|
||||
* If further configuration and actions needed, module:contribute:capability:configuration:needed
|
||||
* will be emitted.
|
||||
*
|
||||
* To configure the capabilities in sequence and correct order,
|
||||
* - module:contribute:capability:configuration:validate:request (static check, zod/valibot or programmatic checks)
|
||||
* - module:contribute:capability:configuration:validate:status (with parent event id)
|
||||
* - module:contribute:capability:configuration:validate:response
|
||||
* - module:contribute:capability:configuration:plan:request (actually dry-run, ensures anything during runtime works)
|
||||
* - module:contribute:capability:configuration:plan:status (with parent event id)
|
||||
* - module:contribute:capability:configuration:plan:response
|
||||
* - module:contribute:capability:configuration:commit
|
||||
* - module:contribute:capability:configuration:commit:status (with parent event id)
|
||||
* similar to module:configuration are accepted.
|
||||
*
|
||||
* 13. No matter what happens, the module:status should emit with ready status now.
|
||||
* 14. Any time the module need to re-calculate / re-compute, or wish to be re-configured, it's ok to
|
||||
* emit module:status:change with needed phase to update, if need to rollback to announced phase,
|
||||
* Plugin Host should treat the Module to be un-prepared status, the needed procedure will be called.
|
||||
*/
|
||||
|
||||
export class PluginHost {
|
||||
constructor() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export interface ManifestV1 {
|
||||
apiVersion: 'v1'
|
||||
kind: 'manifest.plugin.airi.moeru.ai'
|
||||
name: string
|
||||
entrypoints: {
|
||||
electron?: string
|
||||
}
|
||||
}
|
||||
|
||||
export class FileSystemLoader {
|
||||
constructor() {
|
||||
|
||||
}
|
||||
|
||||
async loadLazyPluginFor(manifest: ManifestV1, options?: { cwd?: string }) {
|
||||
const root = options?.cwd ?? cwd()
|
||||
if (!manifest.entrypoints.electron) {
|
||||
throw new Error(''
|
||||
+ 'For locally installed, defined plugin, electron entrypoint is required.'
|
||||
+ 'The value of `entrypoints.electron` should be the relative path to the '
|
||||
+ 'root of app.getPath(\'userData\').',
|
||||
)
|
||||
}
|
||||
|
||||
const entrypoint = join(root, manifest.entrypoints.electron)
|
||||
const pluginModule = await import(entrypoint) as { default: ReturnType<typeof definePlugin> }
|
||||
return pluginModule.default
|
||||
}
|
||||
|
||||
async loadPluginFor(manifest: ManifestV1, options?: { cwd?: string }) {
|
||||
const root = options?.cwd ?? cwd()
|
||||
if (!manifest.entrypoints.electron) {
|
||||
throw new Error(''
|
||||
+ 'For locally installed, defined plugin, electron entrypoint is required.'
|
||||
+ 'The value of `entrypoints.electron` should be the relative path to the '
|
||||
+ 'root of app.getPath(\'userData\').',
|
||||
)
|
||||
}
|
||||
|
||||
const entrypoint = join(root, manifest.entrypoints.electron)
|
||||
const pluginModule = await import(entrypoint) as Plugin
|
||||
return pluginModule
|
||||
}
|
||||
}
|
||||
export * from './core'
|
||||
export { createPluginContext } from './runtimes/node'
|
||||
export * from './transports'
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { EventContext } from '@moeru/eventa'
|
||||
|
||||
import type { PluginTransport } from '../../transports'
|
||||
|
||||
import { createContext } from '@moeru/eventa'
|
||||
|
||||
export * from '../../core'
|
||||
export * from '../../transports'
|
||||
|
||||
export function createPluginContext(transport: PluginTransport): EventContext<any, any> {
|
||||
switch (transport.kind) {
|
||||
case 'in-memory':
|
||||
return createContext()
|
||||
case 'websocket':
|
||||
throw new Error('WebSocket transport is not implemented for node runtime yet.')
|
||||
case 'node-worker':
|
||||
throw new Error('Node worker transport is not implemented yet.')
|
||||
case 'electron':
|
||||
throw new Error('Electron transport is not implemented yet.')
|
||||
case 'web-worker':
|
||||
throw new Error('Web worker transport is not available in node runtime.')
|
||||
default:
|
||||
throw new Error('Unknown plugin transport kind.')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { EventContext } from '@moeru/eventa'
|
||||
|
||||
import type { PluginTransport } from '../../transports'
|
||||
|
||||
import { createContext } from '@moeru/eventa'
|
||||
|
||||
export * from '../../core'
|
||||
export * from '../../transports'
|
||||
|
||||
export function createPluginContext(transport: PluginTransport): EventContext<any, any> {
|
||||
switch (transport.kind) {
|
||||
case 'in-memory':
|
||||
return createContext()
|
||||
case 'websocket':
|
||||
throw new Error('WebSocket transport is not implemented for web runtime yet.')
|
||||
case 'web-worker':
|
||||
throw new Error('Web worker transport is not implemented yet.')
|
||||
case 'node-worker':
|
||||
throw new Error('Node worker transport is not available in web runtime.')
|
||||
case 'electron':
|
||||
throw new Error('Electron transport is not available in web runtime.')
|
||||
default:
|
||||
throw new Error('Unknown plugin transport kind.')
|
||||
}
|
||||
}
|
||||
@@ -2,16 +2,15 @@ import type { ContextInit } from '../../plugin/shared'
|
||||
|
||||
import { defineEventa } from '@moeru/eventa'
|
||||
|
||||
import { channels, providers } from '../../plugin'
|
||||
|
||||
export async function init(initContext: ContextInit): Promise<void | false> {
|
||||
initContext.host.emit(defineEventa('vitest-call:init'), undefined)
|
||||
export async function init({ channels }: ContextInit): Promise<void | false> {
|
||||
channels.host.emit(defineEventa('vitest-call:init'), undefined)
|
||||
}
|
||||
|
||||
export async function configure(): Promise<void> {
|
||||
|
||||
}
|
||||
|
||||
export async function setupModules(): Promise<void> {
|
||||
channels.host.emit(defineEventa('vitest-call:setup-modules'), await providers.listProviders())
|
||||
export async function setupModules({ apis, channels }: ContextInit): Promise<void> {
|
||||
const providerList = await apis.providers.listProviders()
|
||||
channels.host.emit(defineEventa('vitest-call:setup-modules'), providerList)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
export type PluginTransport
|
||||
= | { kind: 'in-memory' }
|
||||
| { kind: 'websocket', url: string, protocols?: string[] }
|
||||
| { kind: 'web-worker', worker: Worker }
|
||||
| { kind: 'node-worker', worker: import('node:worker_threads').Worker }
|
||||
| { kind: 'electron', target: 'main' | 'renderer', webContentsId?: number }
|
||||
@@ -1,2 +1,12 @@
|
||||
export { channels } from '../../../channels'
|
||||
import type { EventContext } from '@moeru/eventa'
|
||||
|
||||
import { createProviders } from './resources'
|
||||
|
||||
export function createApis(ctx: EventContext<any, any>) {
|
||||
return {
|
||||
providers: createProviders(ctx),
|
||||
}
|
||||
}
|
||||
|
||||
export type PluginApis = ReturnType<typeof createApis>
|
||||
export * from './resources'
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { providers } from './providers'
|
||||
export { createProviders } from './providers'
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import type { EventContext } from '@moeru/eventa'
|
||||
|
||||
import { defineInvoke } from '@moeru/eventa'
|
||||
|
||||
import { channels } from '../../../../../channels'
|
||||
import { protocolListProviders } from '../../../protocol/resources/providers'
|
||||
|
||||
export async function listProviders() {
|
||||
const func = defineInvoke(channels.data, protocolListProviders)
|
||||
return func()
|
||||
}
|
||||
|
||||
export const providers = {
|
||||
listProviders,
|
||||
export function createProviders(ctx: EventContext<any, any>) {
|
||||
return {
|
||||
listProviders() {
|
||||
const func = defineInvoke(ctx, protocolListProviders)
|
||||
return func()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import type { ChannelControlPlane } from '../channels/shared'
|
||||
import type { ChannelHost } from '../channels/shared'
|
||||
import type { PluginApis } from './apis/client'
|
||||
|
||||
export interface ContextInit {
|
||||
host: ChannelControlPlane
|
||||
channels: {
|
||||
host: ChannelHost
|
||||
}
|
||||
apis: PluginApis
|
||||
}
|
||||
|
||||
export interface Plugin {
|
||||
@@ -12,5 +16,5 @@ export interface Plugin {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
setupModules?: () => Promise<void | undefined>
|
||||
setupModules?: (initContext: ContextInit) => Promise<void | undefined>
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"lib": [
|
||||
"ESNext"
|
||||
"ESNext",
|
||||
"DOM"
|
||||
],
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
|
||||
@@ -3,6 +3,9 @@ import { defineConfig } from 'tsdown'
|
||||
export default defineConfig({
|
||||
entry: [
|
||||
'src/index.ts',
|
||||
'src/plugin-host/index.ts',
|
||||
'src/plugin-host/runtimes/node/index.ts',
|
||||
'src/plugin-host/runtimes/web/index.ts',
|
||||
],
|
||||
dts: true,
|
||||
format: 'esm',
|
||||
|
||||
Reference in New Issue
Block a user