@zhin.js/plugin-runtime 1.1.0 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/lib/agent-tools-host.d.ts +9 -0
- package/lib/capability.d.ts +11 -0
- package/lib/database-host.d.ts +46 -1
- package/lib/database-host.js +70 -1
- package/lib/generation-store.d.ts +32 -0
- package/lib/generation-store.js +38 -0
- package/lib/identity.d.ts +8 -0
- package/lib/identity.js +13 -0
- package/lib/index.d.ts +9 -0
- package/lib/index.js +9 -0
- package/lib/plugin.d.ts +6 -1
- package/lib/schedule-host.d.ts +23 -1
- package/lib/schedule-host.js +51 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -12,6 +12,11 @@ import {
|
|
|
12
12
|
} from '@zhin.js/plugin-runtime';
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
+
`PluginSetupContext.addFeature(feature, localName, definition)` 是约定目录之外的内存
|
|
16
|
+
Capability 写入口。它仍由上层 Runtime 交给对应 Feature provider 校验和投影;Feature
|
|
17
|
+
通过 `authoring.setupMethod` 和 module augmentation 提供强类型 `addCommand`、
|
|
18
|
+
`addComponent`、`addTool` 等别名。底座只保存通用注册记录,不理解 IM。
|
|
19
|
+
|
|
15
20
|
## 生命周期
|
|
16
21
|
|
|
17
22
|
```text
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Plugin-contributed Agent tool registered against the Agent Host.
|
|
3
3
|
* Captured at setup() time — execute closures must never call plugin locators.
|
|
4
|
+
*
|
|
5
|
+
* Access vocabulary (platforms / scopes / permissions / hidden) mirrors the
|
|
6
|
+
* `@zhin.js/tool` ToolIndex descriptor and is enforced per message by the
|
|
7
|
+
* Agent runtime via Core `canAccessTool` — one predicate for both paths.
|
|
4
8
|
*/
|
|
5
9
|
export interface AgentToolRegistration {
|
|
6
10
|
/** Runtime tool name exposed to the model (e.g. `lottery_sync`). */
|
|
@@ -10,7 +14,12 @@ export interface AgentToolRegistration {
|
|
|
10
14
|
readonly inputSchema?: unknown;
|
|
11
15
|
readonly keywords?: readonly string[];
|
|
12
16
|
readonly tags?: readonly string[];
|
|
17
|
+
/** Limit to these adapter platforms (e.g. `['icqq']`); empty = all. */
|
|
18
|
+
readonly platforms?: readonly string[];
|
|
19
|
+
/** Limit to these channel scopes; empty = all. */
|
|
20
|
+
readonly scopes?: readonly ('private' | 'group' | 'channel')[];
|
|
13
21
|
readonly permissions?: readonly string[];
|
|
22
|
+
/** Registered but never offered to the model (callable by name only). */
|
|
14
23
|
readonly hidden?: boolean;
|
|
15
24
|
/** Per-tool approval policy; stacks with ExecPolicy. */
|
|
16
25
|
readonly approval?: 'never' | 'always';
|
package/lib/capability.d.ts
CHANGED
|
@@ -6,5 +6,16 @@ export interface CapabilitySlot<T = unknown> {
|
|
|
6
6
|
readonly localName: string;
|
|
7
7
|
readonly source: string;
|
|
8
8
|
readonly definition: T;
|
|
9
|
+
/** Setup-owned slots follow their Plugin Scope instead of file-slot HMR. */
|
|
10
|
+
readonly origin?: 'setup';
|
|
11
|
+
}
|
|
12
|
+
/** A capability declared imperatively while its owning Plugin is assembled. */
|
|
13
|
+
export interface SetupCapabilityRegistration<T = unknown> {
|
|
14
|
+
readonly id: CapabilityId;
|
|
15
|
+
readonly owner: PluginId;
|
|
16
|
+
readonly feature: FeatureId;
|
|
17
|
+
readonly localName: string;
|
|
18
|
+
readonly source: string;
|
|
19
|
+
readonly definition: T;
|
|
9
20
|
}
|
|
10
21
|
export declare function createCapabilitySlot<T>(input: Omit<CapabilitySlot<T>, 'id'>): Readonly<CapabilitySlot<T>>;
|
package/lib/database-host.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type PluginId } from './identity.js';
|
|
1
2
|
export interface DatabaseHostSelection {
|
|
2
3
|
where(query: Record<string, unknown>): DatabaseHostSelection;
|
|
3
4
|
orderBy(field: string, direction?: 'ASC' | 'DESC'): DatabaseHostSelection;
|
|
@@ -17,6 +18,11 @@ export interface DatabaseHostModel {
|
|
|
17
18
|
update(patch: Record<string, unknown>): {
|
|
18
19
|
where(query: Record<string, unknown>): Promise<unknown>;
|
|
19
20
|
};
|
|
21
|
+
/**
|
|
22
|
+
* DB 侧计数(聚合下推,避免为计数整表加载)。
|
|
23
|
+
* 可选:轻量 test/memory host 可不实现,调用方降级为 select 计数。
|
|
24
|
+
*/
|
|
25
|
+
count?(where?: Record<string, unknown>): Promise<number>;
|
|
20
26
|
}
|
|
21
27
|
export type DatabaseHostType = 'related' | 'document' | 'keyvalue';
|
|
22
28
|
export interface DatabaseHostTable {
|
|
@@ -78,4 +84,43 @@ export interface DatabaseHost {
|
|
|
78
84
|
start(): Promise<void>;
|
|
79
85
|
stop(): Promise<void>;
|
|
80
86
|
}
|
|
81
|
-
|
|
87
|
+
/**
|
|
88
|
+
* Persistence surface visible to a plugin. Names are logical to the owner;
|
|
89
|
+
* the Runtime maps them to private physical table names before they reach the
|
|
90
|
+
* process-wide DatabaseHost.
|
|
91
|
+
*/
|
|
92
|
+
export interface PluginDatabaseHost {
|
|
93
|
+
readonly owner: PluginId;
|
|
94
|
+
readonly dialect: string;
|
|
95
|
+
readonly started: boolean;
|
|
96
|
+
define(name: string, definition: Record<string, unknown>): void;
|
|
97
|
+
tables(): readonly string[];
|
|
98
|
+
models: {
|
|
99
|
+
get(name: string): DatabaseHostModel | undefined;
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Maps a plugin's logical resource name to its process-wide physical name.
|
|
104
|
+
* Root keeps its historical bare names so existing projects do not need a
|
|
105
|
+
* database migration merely to adopt scoped child plugins.
|
|
106
|
+
*/
|
|
107
|
+
export declare function qualifyPluginResourceName(owner: PluginId, name: string): string;
|
|
108
|
+
/** Reverse `qualifyPluginResourceName` only when the name belongs to owner. */
|
|
109
|
+
export declare function unqualifyPluginResourceName(owner: PluginId, name: string): string | undefined;
|
|
110
|
+
/** Creates a tenant facade without exposing Console administration or raw DB access. */
|
|
111
|
+
export declare function createPluginDatabaseHost(owner: PluginId, host: DatabaseHost): PluginDatabaseHost;
|
|
112
|
+
/**
|
|
113
|
+
* Recovers a process host from the standard root token or a scoped facade.
|
|
114
|
+
* This keeps custom RootRuntime installers written against the pre-facade
|
|
115
|
+
* token working while child scopes begin receiving tenant boundaries.
|
|
116
|
+
*/
|
|
117
|
+
export declare function unwrapPluginDatabaseHost(host: DatabaseHost | PluginDatabaseHost): DatabaseHost | undefined;
|
|
118
|
+
/**
|
|
119
|
+
* Plugin-facing scoped persistence token. Runtime replaces this binding for
|
|
120
|
+
* every Plugin Scope; plugin code must never receive the process-wide host.
|
|
121
|
+
* The owner encoding keeps `root/a` and `root/a/b` disjoint, so a parent
|
|
122
|
+
* facade cannot accidentally enumerate a descendant's tables.
|
|
123
|
+
*/
|
|
124
|
+
export declare const databaseHostToken: import("./token.js").Token<PluginDatabaseHost>;
|
|
125
|
+
/** Root-only process host for CLI composition and Console administration. */
|
|
126
|
+
export declare const databaseRootHostToken: import("./token.js").Token<DatabaseHost>;
|
package/lib/database-host.js
CHANGED
|
@@ -1,2 +1,71 @@
|
|
|
1
|
+
import { pluginOwnerResourceKey, rootPluginId } from './identity.js';
|
|
1
2
|
import { createToken } from './token.js';
|
|
2
|
-
|
|
3
|
+
const resourcePrefix = '__zhin_plugin__';
|
|
4
|
+
const resourceSeparator = '__';
|
|
5
|
+
const roots = new WeakMap();
|
|
6
|
+
/**
|
|
7
|
+
* Maps a plugin's logical resource name to its process-wide physical name.
|
|
8
|
+
* Root keeps its historical bare names so existing projects do not need a
|
|
9
|
+
* database migration merely to adopt scoped child plugins.
|
|
10
|
+
*/
|
|
11
|
+
export function qualifyPluginResourceName(owner, name) {
|
|
12
|
+
assertLogicalResourceName(name);
|
|
13
|
+
if (owner === rootPluginId())
|
|
14
|
+
return name;
|
|
15
|
+
return `${resourcePrefix}${pluginOwnerResourceKey(owner)}${resourceSeparator}${name}`;
|
|
16
|
+
}
|
|
17
|
+
/** Reverse `qualifyPluginResourceName` only when the name belongs to owner. */
|
|
18
|
+
export function unqualifyPluginResourceName(owner, name) {
|
|
19
|
+
if (owner === rootPluginId())
|
|
20
|
+
return name.startsWith(resourcePrefix) ? undefined : name;
|
|
21
|
+
const prefix = `${resourcePrefix}${pluginOwnerResourceKey(owner)}${resourceSeparator}`;
|
|
22
|
+
return name.startsWith(prefix) ? name.slice(prefix.length) : undefined;
|
|
23
|
+
}
|
|
24
|
+
/** Creates a tenant facade without exposing Console administration or raw DB access. */
|
|
25
|
+
export function createPluginDatabaseHost(owner, host) {
|
|
26
|
+
const facade = Object.freeze({
|
|
27
|
+
owner,
|
|
28
|
+
get dialect() { return host.dialect; },
|
|
29
|
+
get started() { return host.started; },
|
|
30
|
+
define(name, definition) {
|
|
31
|
+
host.define(qualifyPluginResourceName(owner, name), definition);
|
|
32
|
+
},
|
|
33
|
+
tables() {
|
|
34
|
+
return Object.freeze(host.tables().flatMap((name) => {
|
|
35
|
+
const logical = unqualifyPluginResourceName(owner, name);
|
|
36
|
+
return logical === undefined ? [] : [logical];
|
|
37
|
+
}));
|
|
38
|
+
},
|
|
39
|
+
models: Object.freeze({
|
|
40
|
+
get(name) {
|
|
41
|
+
return host.models.get(qualifyPluginResourceName(owner, name));
|
|
42
|
+
},
|
|
43
|
+
}),
|
|
44
|
+
});
|
|
45
|
+
roots.set(facade, host);
|
|
46
|
+
return facade;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Recovers a process host from the standard root token or a scoped facade.
|
|
50
|
+
* This keeps custom RootRuntime installers written against the pre-facade
|
|
51
|
+
* token working while child scopes begin receiving tenant boundaries.
|
|
52
|
+
*/
|
|
53
|
+
export function unwrapPluginDatabaseHost(host) {
|
|
54
|
+
if ('getRawDatabase' in host)
|
|
55
|
+
return host;
|
|
56
|
+
return roots.get(host);
|
|
57
|
+
}
|
|
58
|
+
function assertLogicalResourceName(name) {
|
|
59
|
+
if (!name || name.startsWith(resourcePrefix)) {
|
|
60
|
+
throw new TypeError(`Invalid plugin resource name: ${name}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Plugin-facing scoped persistence token. Runtime replaces this binding for
|
|
65
|
+
* every Plugin Scope; plugin code must never receive the process-wide host.
|
|
66
|
+
* The owner encoding keeps `root/a` and `root/a/b` disjoint, so a parent
|
|
67
|
+
* facade cannot accidentally enumerate a descendant's tables.
|
|
68
|
+
*/
|
|
69
|
+
export const databaseHostToken = createToken('zhin.database.host', 'Plugin Runtime scoped database host');
|
|
70
|
+
/** Root-only process host for CLI composition and Console administration. */
|
|
71
|
+
export const databaseRootHostToken = createToken('zhin.database.root-host', 'Plugin Runtime root database host');
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { DisposeStack, type Dispose } from './dispose.js';
|
|
2
|
+
/**
|
|
3
|
+
* Minimal structural view of a plugin setup context: anything owning a
|
|
4
|
+
* generation-scoped lifecycle (e.g. PluginSetupContext) can provide values.
|
|
5
|
+
*/
|
|
6
|
+
export interface GenerationStoreContext {
|
|
7
|
+
readonly lifecycle: DisposeStack;
|
|
8
|
+
}
|
|
9
|
+
export interface GenerationStore<T> {
|
|
10
|
+
/**
|
|
11
|
+
* Publish `value` as the current generation's value. The registration is
|
|
12
|
+
* removed automatically when `context.lifecycle` disposes (generation end);
|
|
13
|
+
* the returned Dispose unregisters manually and is idempotent.
|
|
14
|
+
*/
|
|
15
|
+
provide(context: GenerationStoreContext, value: T): Dispose;
|
|
16
|
+
/** Latest live value; throws a store-named error when none was provided. */
|
|
17
|
+
use(): T;
|
|
18
|
+
/** Latest live value, or `undefined` when none was provided. */
|
|
19
|
+
tryUse(): T | undefined;
|
|
20
|
+
/** Drop every registration across all generations. Mainly for tests. */
|
|
21
|
+
clear(): void;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Generation-scoped replacement for module-level `let _x` singletons.
|
|
25
|
+
*
|
|
26
|
+
* Provided values form a stack: the newest live registration wins, and when a
|
|
27
|
+
* generation ends its registration is removed via `context.lifecycle`,
|
|
28
|
+
* re-exposing the previous generation's value. This structurally prevents
|
|
29
|
+
* stale references to an already disposed generation (the repeater singleton
|
|
30
|
+
* / rss `_db` class of bugs).
|
|
31
|
+
*/
|
|
32
|
+
export declare function createGenerationStore<T>(name: string): GenerationStore<T>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generation-scoped replacement for module-level `let _x` singletons.
|
|
3
|
+
*
|
|
4
|
+
* Provided values form a stack: the newest live registration wins, and when a
|
|
5
|
+
* generation ends its registration is removed via `context.lifecycle`,
|
|
6
|
+
* re-exposing the previous generation's value. This structurally prevents
|
|
7
|
+
* stale references to an already disposed generation (the repeater singleton
|
|
8
|
+
* / rss `_db` class of bugs).
|
|
9
|
+
*/
|
|
10
|
+
export function createGenerationStore(name) {
|
|
11
|
+
const registrations = [];
|
|
12
|
+
const unregister = (registration) => {
|
|
13
|
+
const index = registrations.lastIndexOf(registration);
|
|
14
|
+
if (index >= 0)
|
|
15
|
+
registrations.splice(index, 1);
|
|
16
|
+
};
|
|
17
|
+
return {
|
|
18
|
+
provide(context, value) {
|
|
19
|
+
const registration = Object.freeze({ value });
|
|
20
|
+
registrations.push(registration);
|
|
21
|
+
context.lifecycle.add(() => unregister(registration));
|
|
22
|
+
return () => unregister(registration);
|
|
23
|
+
},
|
|
24
|
+
use() {
|
|
25
|
+
const latest = registrations[registrations.length - 1];
|
|
26
|
+
if (!latest) {
|
|
27
|
+
throw new Error(`Generation store "${name}" has no live value — call provide(context, value) during plugin setup`);
|
|
28
|
+
}
|
|
29
|
+
return latest.value;
|
|
30
|
+
},
|
|
31
|
+
tryUse() {
|
|
32
|
+
return registrations[registrations.length - 1]?.value;
|
|
33
|
+
},
|
|
34
|
+
clear() {
|
|
35
|
+
registrations.length = 0;
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
package/lib/identity.d.ts
CHANGED
|
@@ -18,5 +18,13 @@ export declare function tokenId(value: string): TokenId;
|
|
|
18
18
|
export declare function featureId(value: string): FeatureId;
|
|
19
19
|
export declare function rootPluginId(): PluginId;
|
|
20
20
|
export declare function childPluginId(parent: PluginId, instanceKey: string): PluginId;
|
|
21
|
+
/**
|
|
22
|
+
* Database-safe owner encoding used by process-wide plugin resources.
|
|
23
|
+
*
|
|
24
|
+
* Each path segment is length-prefixed and the final underscore is followed
|
|
25
|
+
* by a resource separator. A parent owner therefore cannot match a child
|
|
26
|
+
* owner merely because their PluginId has a common path prefix.
|
|
27
|
+
*/
|
|
28
|
+
export declare function pluginOwnerResourceKey(owner: PluginId): string;
|
|
21
29
|
export declare function capabilityId(owner: PluginId, feature: FeatureId, localName: string): CapabilityId;
|
|
22
30
|
export {};
|
package/lib/identity.js
CHANGED
|
@@ -24,6 +24,19 @@ export function childPluginId(parent, instanceKey) {
|
|
|
24
24
|
}
|
|
25
25
|
return `${parent}/${instanceKey}`;
|
|
26
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Database-safe owner encoding used by process-wide plugin resources.
|
|
29
|
+
*
|
|
30
|
+
* Each path segment is length-prefixed and the final underscore is followed
|
|
31
|
+
* by a resource separator. A parent owner therefore cannot match a child
|
|
32
|
+
* owner merely because their PluginId has a common path prefix.
|
|
33
|
+
*/
|
|
34
|
+
export function pluginOwnerResourceKey(owner) {
|
|
35
|
+
return `${String(owner)
|
|
36
|
+
.split('/')
|
|
37
|
+
.map((segment) => `${segment.length}_${segment}`)
|
|
38
|
+
.join('_')}_`;
|
|
39
|
+
}
|
|
27
40
|
export function capabilityId(owner, feature, localName) {
|
|
28
41
|
if (!capabilityLocalNamePattern.test(localName)) {
|
|
29
42
|
throw new TypeError(`Invalid capability local name: ${localName}`);
|
package/lib/index.d.ts
CHANGED
|
@@ -1,16 +1,25 @@
|
|
|
1
|
+
/** @internal Runtime 内部:CapabilitySlot(feature 与 projection 之间的载体)。 */
|
|
1
2
|
export * from './capability.js';
|
|
2
3
|
export * from './dispose.js';
|
|
3
4
|
export * from './identity.js';
|
|
5
|
+
export * from './generation-store.js';
|
|
4
6
|
export * from './handoff.js';
|
|
7
|
+
/** @public 用户侧创作面:`definePlugin`(`plugin.ts` 约定入口,承诺 semver)。 */
|
|
5
8
|
export * from './plugin.js';
|
|
9
|
+
/** @internal Runtime 内部:RootRuntime / RootController。 */
|
|
6
10
|
export * from './root-controller.js';
|
|
7
11
|
export * from './shared-lifetime.js';
|
|
12
|
+
/** @internal Runtime 内部:SnapshotStore / RuntimeSnapshot。 */
|
|
8
13
|
export * from './snapshot.js';
|
|
9
14
|
export * from './token.js';
|
|
15
|
+
/** @public Host token:`scheduleHostToken`(承诺 semver)。 */
|
|
10
16
|
export * from './schedule-host.js';
|
|
17
|
+
/** @public Host token:`databaseHostToken`(承诺 semver)。 */
|
|
11
18
|
export * from './database-host.js';
|
|
19
|
+
/** @public Host token:`agentToolsHostToken`(experimental)。 */
|
|
12
20
|
export * from './agent-tools-host.js';
|
|
13
21
|
export * from './html-renderer.js';
|
|
22
|
+
/** @public Host token:`outboundHostToken`(承诺 semver)。 */
|
|
14
23
|
export * from './outbound-host.js';
|
|
15
24
|
export * from './inbox.js';
|
|
16
25
|
export * from './system-log.js';
|
package/lib/index.js
CHANGED
|
@@ -1,16 +1,25 @@
|
|
|
1
|
+
/** @internal Runtime 内部:CapabilitySlot(feature 与 projection 之间的载体)。 */
|
|
1
2
|
export * from './capability.js';
|
|
2
3
|
export * from './dispose.js';
|
|
3
4
|
export * from './identity.js';
|
|
5
|
+
export * from './generation-store.js';
|
|
4
6
|
export * from './handoff.js';
|
|
7
|
+
/** @public 用户侧创作面:`definePlugin`(`plugin.ts` 约定入口,承诺 semver)。 */
|
|
5
8
|
export * from './plugin.js';
|
|
9
|
+
/** @internal Runtime 内部:RootRuntime / RootController。 */
|
|
6
10
|
export * from './root-controller.js';
|
|
7
11
|
export * from './shared-lifetime.js';
|
|
12
|
+
/** @internal Runtime 内部:SnapshotStore / RuntimeSnapshot。 */
|
|
8
13
|
export * from './snapshot.js';
|
|
9
14
|
export * from './token.js';
|
|
15
|
+
/** @public Host token:`scheduleHostToken`(承诺 semver)。 */
|
|
10
16
|
export * from './schedule-host.js';
|
|
17
|
+
/** @public Host token:`databaseHostToken`(承诺 semver)。 */
|
|
11
18
|
export * from './database-host.js';
|
|
19
|
+
/** @public Host token:`agentToolsHostToken`(experimental)。 */
|
|
12
20
|
export * from './agent-tools-host.js';
|
|
13
21
|
export * from './html-renderer.js';
|
|
22
|
+
/** @public Host token:`outboundHostToken`(承诺 semver)。 */
|
|
14
23
|
export * from './outbound-host.js';
|
|
15
24
|
export * from './inbox.js';
|
|
16
25
|
export * from './system-log.js';
|
package/lib/plugin.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Dispose, DisposeStack } from './dispose.js';
|
|
2
2
|
import type { GenerationHandoffRegistry } from './handoff.js';
|
|
3
|
-
import type { PluginId } from './identity.js';
|
|
3
|
+
import type { FeatureId, PluginId } from './identity.js';
|
|
4
4
|
import type { Scope, Token } from './token.js';
|
|
5
5
|
export interface PluginMetadata {
|
|
6
6
|
readonly displayName?: string;
|
|
@@ -23,6 +23,11 @@ export interface PluginSetupContext<TConfig = unknown> {
|
|
|
23
23
|
readonly resources: Scope;
|
|
24
24
|
readonly lifecycle: DisposeStack;
|
|
25
25
|
readonly handoff: GenerationHandoffRegistry;
|
|
26
|
+
/**
|
|
27
|
+
* Registers an in-memory definition through the same validation and
|
|
28
|
+
* projection transaction used by convention-discovered capabilities.
|
|
29
|
+
*/
|
|
30
|
+
addFeature<TDefinition>(feature: FeatureId | string, localName: string, definition: TDefinition): void;
|
|
26
31
|
}
|
|
27
32
|
export interface PluginDefinition<TConfig = unknown> {
|
|
28
33
|
readonly name: string;
|
package/lib/schedule-host.d.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import { type PluginId } from './identity.js';
|
|
1
2
|
/**
|
|
2
3
|
* Thin Host Resource for Plugin Runtime cron jobs.
|
|
3
4
|
* Implementations typically wrap `@zhin.js/schedule` CalendarScheduler (solar cron).
|
|
4
5
|
*/
|
|
5
6
|
export interface ScheduleJobRegistration {
|
|
6
7
|
readonly id: string;
|
|
8
|
+
/** Runtime-assigned owner for diagnostics and Console inventory. */
|
|
9
|
+
readonly owner?: PluginId;
|
|
7
10
|
/** 6-field solar cron: `秒 分 时 日 月 周` */
|
|
8
11
|
readonly cron: string;
|
|
9
12
|
readonly description?: string;
|
|
@@ -16,6 +19,25 @@ export interface ScheduleHost {
|
|
|
16
19
|
readonly id: string;
|
|
17
20
|
readonly cron: string;
|
|
18
21
|
readonly description?: string;
|
|
22
|
+
readonly owner?: PluginId;
|
|
19
23
|
}[];
|
|
20
24
|
}
|
|
21
|
-
|
|
25
|
+
/** Scoped scheduling surface exposed to a Plugin setup function. */
|
|
26
|
+
export interface PluginScheduleHost {
|
|
27
|
+
readonly owner: PluginId;
|
|
28
|
+
register(job: ScheduleJobRegistration): () => void;
|
|
29
|
+
list(): readonly {
|
|
30
|
+
readonly id: string;
|
|
31
|
+
readonly cron: string;
|
|
32
|
+
readonly description?: string;
|
|
33
|
+
}[];
|
|
34
|
+
}
|
|
35
|
+
export declare function qualifyPluginScheduleId(owner: PluginId, id: string): string;
|
|
36
|
+
export declare function unqualifyPluginScheduleId(owner: PluginId, id: string): string | undefined;
|
|
37
|
+
export declare function createPluginScheduleHost(owner: PluginId, host: ScheduleHost): PluginScheduleHost;
|
|
38
|
+
/** See unwrapPluginDatabaseHost: preserves legacy custom RootResource installers. */
|
|
39
|
+
export declare function unwrapPluginScheduleHost(host: ScheduleHost | PluginScheduleHost): ScheduleHost;
|
|
40
|
+
/** Plugin-facing token; Runtime injects a facade bound to the current owner. */
|
|
41
|
+
export declare const scheduleHostToken: import("./token.js").Token<PluginScheduleHost>;
|
|
42
|
+
/** Root-only process scheduler for CLI composition and Console administration. */
|
|
43
|
+
export declare const scheduleRootHostToken: import("./token.js").Token<ScheduleHost>;
|
package/lib/schedule-host.js
CHANGED
|
@@ -1,2 +1,52 @@
|
|
|
1
|
+
import { pluginOwnerResourceKey, rootPluginId } from './identity.js';
|
|
1
2
|
import { createToken } from './token.js';
|
|
2
|
-
|
|
3
|
+
const jobPrefix = '__zhin_plugin__';
|
|
4
|
+
const jobSeparator = '__';
|
|
5
|
+
const roots = new WeakMap();
|
|
6
|
+
export function qualifyPluginScheduleId(owner, id) {
|
|
7
|
+
assertLogicalJobId(id);
|
|
8
|
+
if (owner === rootPluginId())
|
|
9
|
+
return id;
|
|
10
|
+
return `${jobPrefix}${pluginOwnerResourceKey(owner)}${jobSeparator}${id}`;
|
|
11
|
+
}
|
|
12
|
+
export function unqualifyPluginScheduleId(owner, id) {
|
|
13
|
+
if (owner === rootPluginId())
|
|
14
|
+
return id.startsWith(jobPrefix) ? undefined : id;
|
|
15
|
+
const prefix = `${jobPrefix}${pluginOwnerResourceKey(owner)}${jobSeparator}`;
|
|
16
|
+
return id.startsWith(prefix) ? id.slice(prefix.length) : undefined;
|
|
17
|
+
}
|
|
18
|
+
export function createPluginScheduleHost(owner, host) {
|
|
19
|
+
const facade = Object.freeze({
|
|
20
|
+
owner,
|
|
21
|
+
register(job) {
|
|
22
|
+
return host.register({ ...job, id: qualifyPluginScheduleId(owner, job.id), owner });
|
|
23
|
+
},
|
|
24
|
+
list() {
|
|
25
|
+
return Object.freeze(host.list().flatMap((job) => {
|
|
26
|
+
const id = unqualifyPluginScheduleId(owner, job.id);
|
|
27
|
+
if (id === undefined)
|
|
28
|
+
return [];
|
|
29
|
+
return [Object.freeze({
|
|
30
|
+
id,
|
|
31
|
+
cron: job.cron,
|
|
32
|
+
...(job.description === undefined ? {} : { description: job.description }),
|
|
33
|
+
})];
|
|
34
|
+
}));
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
roots.set(facade, host);
|
|
38
|
+
return facade;
|
|
39
|
+
}
|
|
40
|
+
/** See unwrapPluginDatabaseHost: preserves legacy custom RootResource installers. */
|
|
41
|
+
export function unwrapPluginScheduleHost(host) {
|
|
42
|
+
return roots.get(host) ?? host;
|
|
43
|
+
}
|
|
44
|
+
function assertLogicalJobId(id) {
|
|
45
|
+
if (!id || id.startsWith(jobPrefix)) {
|
|
46
|
+
throw new TypeError(`Invalid plugin schedule id: ${id}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/** Plugin-facing token; Runtime injects a facade bound to the current owner. */
|
|
50
|
+
export const scheduleHostToken = createToken('zhin.schedule.host', 'Plugin Runtime scoped solar cron host');
|
|
51
|
+
/** Root-only process scheduler for CLI composition and Console administration. */
|
|
52
|
+
export const scheduleRootHostToken = createToken('zhin.schedule.root-host', 'Plugin Runtime root solar cron host');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/plugin-runtime",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "Zero-dependency Plugin tree, Scope and atomic generation runtime for Zhin",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"lib"
|
|
17
17
|
],
|
|
18
18
|
"devDependencies": {
|
|
19
|
-
"@types/node": "^26.1.
|
|
19
|
+
"@types/node": "^26.1.2",
|
|
20
20
|
"typescript": "^6.0.3"
|
|
21
21
|
},
|
|
22
22
|
"engines": {
|