@zhin.js/plugin-runtime 1.1.19 → 1.1.20
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/lib/config-update-declaration.d.ts +5 -0
- package/lib/config-update-declaration.js +15 -0
- package/lib/outbound-host.d.ts +21 -0
- package/lib/plugin.d.ts +5 -0
- package/lib/plugin.js +3 -0
- package/lib/snapshot.d.ts +2 -0
- package/lib/snapshot.js +4 -2
- package/lib/token.d.ts +2 -0
- package/lib/token.js +2 -0
- package/package.json +1 -1
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export interface PluginConfigUpdateDeclaration {
|
|
2
|
+
readonly featurePaths: readonly (readonly string[])[];
|
|
3
|
+
}
|
|
4
|
+
/** @internal Normalize inert ownership metadata at authoring and snapshot boundaries. */
|
|
5
|
+
export declare function freezePluginConfigUpdate(value: PluginConfigUpdateDeclaration): PluginConfigUpdateDeclaration;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** @internal Normalize inert ownership metadata at authoring and snapshot boundaries. */
|
|
2
|
+
export function freezePluginConfigUpdate(value) {
|
|
3
|
+
if (!value || typeof value !== 'object' || Object.keys(value).some(key => key !== 'featurePaths') || !Array.isArray(value.featurePaths)) {
|
|
4
|
+
throw new TypeError('Plugin configUpdate must declare featurePaths');
|
|
5
|
+
}
|
|
6
|
+
return Object.freeze({ featurePaths: Object.freeze(Array.from(value.featurePaths, (path) => {
|
|
7
|
+
if (!Array.isArray(path) || !path.length)
|
|
8
|
+
throw new TypeError('Plugin configUpdate paths must be safe nonempty paths');
|
|
9
|
+
const parts = Array.from(path);
|
|
10
|
+
if (parts.some(part => typeof part !== 'string' || !part || ['__proto__', 'prototype', 'constructor'].includes(part))) {
|
|
11
|
+
throw new TypeError('Plugin configUpdate paths must be safe nonempty paths');
|
|
12
|
+
}
|
|
13
|
+
return Object.freeze(parts);
|
|
14
|
+
})) });
|
|
15
|
+
}
|
package/lib/outbound-host.d.ts
CHANGED
|
@@ -47,8 +47,29 @@ export interface OutboundEndpointInput {
|
|
|
47
47
|
readonly adapter: string;
|
|
48
48
|
readonly endpointKey: string;
|
|
49
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Per-conversation-kind view of an Endpoint's capability profile (ADR 0044). Structurally the same as
|
|
52
|
+
* `EndpointKindCapabilities` in `@zhin.js/im-contract`; a missing kind or field means "not supported".
|
|
53
|
+
*/
|
|
54
|
+
export interface OutboundKindCapabilities {
|
|
55
|
+
readonly feedback?: Readonly<{
|
|
56
|
+
mode: 'typing' | 'reaction' | 'status-message' | 'none';
|
|
57
|
+
emoji?: string;
|
|
58
|
+
text?: string;
|
|
59
|
+
refreshSeconds?: number;
|
|
60
|
+
}>;
|
|
61
|
+
readonly reaction: boolean;
|
|
62
|
+
readonly edit: boolean;
|
|
63
|
+
readonly recall: boolean;
|
|
64
|
+
readonly spool: boolean;
|
|
65
|
+
/** Messages here must quote an inbound message. */
|
|
66
|
+
readonly needsReply: boolean;
|
|
67
|
+
readonly groupInfo: boolean;
|
|
68
|
+
readonly memberProfile: boolean;
|
|
69
|
+
}
|
|
50
70
|
export interface OutboundEndpointCapabilities {
|
|
51
71
|
readonly operations: readonly OutboundEndpointOperation[];
|
|
72
|
+
readonly kinds?: Readonly<Partial<Record<'private' | 'group' | 'channel', OutboundKindCapabilities>>>;
|
|
52
73
|
}
|
|
53
74
|
export interface OutboundEditInput {
|
|
54
75
|
readonly message: OutboundMessage;
|
package/lib/plugin.d.ts
CHANGED
|
@@ -7,6 +7,9 @@ import type { GenerationHandoffRegistry } from './handoff.js';
|
|
|
7
7
|
import type { FeatureId, PluginId } from './identity.js';
|
|
8
8
|
import type { Scope, Token } from './token.js';
|
|
9
9
|
import type { PluginEventSubscriptions } from './plugin-events.js';
|
|
10
|
+
import { type PluginConfigUpdateDeclaration } from './config-update-declaration.js';
|
|
11
|
+
export { freezePluginConfigUpdate } from './config-update-declaration.js';
|
|
12
|
+
export type { PluginConfigUpdateDeclaration } from './config-update-declaration.js';
|
|
10
13
|
export interface PluginMetadata {
|
|
11
14
|
readonly displayName?: string;
|
|
12
15
|
readonly icon?: string;
|
|
@@ -48,6 +51,8 @@ export interface PluginDefinition<TConfig = unknown> {
|
|
|
48
51
|
readonly config?: PluginConfigSchema<TConfig>;
|
|
49
52
|
readonly metadata?: PluginMetadata;
|
|
50
53
|
readonly requires?: readonly Token<unknown>[];
|
|
54
|
+
/** Fields owned by Features; setup must not capture these as mutable business configuration. */
|
|
55
|
+
readonly configUpdate?: PluginConfigUpdateDeclaration;
|
|
51
56
|
setup?(context: PluginSetupContext<TConfig>): void | Dispose | Promise<void | Dispose>;
|
|
52
57
|
}
|
|
53
58
|
/**
|
package/lib/plugin.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { freezePluginConfigUpdate } from './config-update-declaration.js';
|
|
2
|
+
export { freezePluginConfigUpdate } from './config-update-declaration.js';
|
|
1
3
|
const pluginNamePattern = /^[a-z][a-z0-9-]*$/;
|
|
2
4
|
/**
|
|
3
5
|
* Define the canonical `plugin.ts` entry point.
|
|
@@ -28,6 +30,7 @@ export function definePlugin(definition) {
|
|
|
28
30
|
...definition,
|
|
29
31
|
metadata: definition.metadata ? Object.freeze({ ...definition.metadata }) : undefined,
|
|
30
32
|
requires: Object.freeze([...(definition.requires ?? [])]),
|
|
33
|
+
...(definition.configUpdate !== undefined ? { configUpdate: freezePluginConfigUpdate(definition.configUpdate) } : {}),
|
|
31
34
|
});
|
|
32
35
|
}
|
|
33
36
|
// The application facade exposes these Host contracts beside definePlugin.
|
package/lib/snapshot.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { Dispose } from './dispose.js';
|
|
|
4
4
|
import type { GenerationHandoff } from './handoff.js';
|
|
5
5
|
import type { CapabilityId, FeatureId, PluginId, TokenId } from './identity.js';
|
|
6
6
|
import type { PluginMetadata } from './plugin.js';
|
|
7
|
+
import { type PluginConfigUpdateDeclaration } from './config-update-declaration.js';
|
|
7
8
|
export interface PluginNodeSnapshot {
|
|
8
9
|
readonly id: PluginId;
|
|
9
10
|
readonly instanceKey: string;
|
|
@@ -16,6 +17,7 @@ export interface PluginNodeSnapshot {
|
|
|
16
17
|
readonly metadata?: PluginMetadata;
|
|
17
18
|
/** Immutable schema declaration belonging to this committed generation. */
|
|
18
19
|
readonly configSchema?: Readonly<Record<string, unknown>>;
|
|
20
|
+
readonly configUpdate?: PluginConfigUpdateDeclaration;
|
|
19
21
|
}
|
|
20
22
|
export interface RuntimeSnapshot {
|
|
21
23
|
readonly generation: number;
|
package/lib/snapshot.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { collectGenerationAdmissions, replaceGenerationAdmissions, } from './admission.js';
|
|
2
|
+
import { freezePluginConfigUpdate } from './config-update-declaration.js';
|
|
2
3
|
export class GenerationConflictError extends Error {
|
|
3
4
|
expected;
|
|
4
5
|
actual;
|
|
@@ -214,8 +215,9 @@ export function createSnapshotView(generation, state) {
|
|
|
214
215
|
return Object.freeze({
|
|
215
216
|
...state,
|
|
216
217
|
generation,
|
|
217
|
-
tree: readonlyMap([...state.tree].map(([owner, node]) => [owner, node.configSchema
|
|
218
|
-
? Object.freeze({ ...node, configSchema: freezeSchema(structuredClone(node.configSchema)) })
|
|
218
|
+
tree: readonlyMap([...state.tree].map(([owner, node]) => [owner, node.configSchema || node.configUpdate
|
|
219
|
+
? Object.freeze({ ...node, ...(node.configSchema ? { configSchema: freezeSchema(structuredClone(node.configSchema)) } : {}),
|
|
220
|
+
...(node.configUpdate ? { configUpdate: freezePluginConfigUpdate(node.configUpdate) } : {}) })
|
|
219
221
|
: node])),
|
|
220
222
|
config: readonlyMap(state.config),
|
|
221
223
|
resources: readonlyMap([...state.resources].map(([owner, resources]) => [owner, readonlyMap(resources)])),
|
package/lib/token.d.ts
CHANGED
|
@@ -7,6 +7,8 @@ export interface Token<T> {
|
|
|
7
7
|
}
|
|
8
8
|
export declare function createToken<T>(id: string, description?: string): Token<T>;
|
|
9
9
|
export declare function createRootPrivateToken<T>(id: string, description?: string): Token<T>;
|
|
10
|
+
/** @internal Verify token identity before accepting a generation-private binding. */
|
|
11
|
+
export declare function isRootPrivateToken(token: Token<unknown>): boolean;
|
|
10
12
|
/** A Root-private capability readable only while projecting its owning Feature. */
|
|
11
13
|
export declare function createFeaturePrivateToken<T>(id: string, feature: FeatureId, description?: string): Token<T>;
|
|
12
14
|
/** @internal The actual candidate Root Scope never enters the Feature context. */
|
package/lib/token.js
CHANGED
|
@@ -10,6 +10,8 @@ export function createRootPrivateToken(id, description) {
|
|
|
10
10
|
rootPrivateTokens.add(token);
|
|
11
11
|
return Object.freeze(token);
|
|
12
12
|
}
|
|
13
|
+
/** @internal Verify token identity before accepting a generation-private binding. */
|
|
14
|
+
export function isRootPrivateToken(token) { return rootPrivateTokens.has(token); }
|
|
13
15
|
/** A Root-private capability readable only while projecting its owning Feature. */
|
|
14
16
|
export function createFeaturePrivateToken(id, feature, description) {
|
|
15
17
|
const token = createRootPrivateToken(id, description);
|