@ethisyscore/extension-runtime 1.6.0
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 +42 -0
- package/bin/mock-host.cjs +14 -0
- package/dist/host/index.cjs +600 -0
- package/dist/host/index.cjs.map +1 -0
- package/dist/host/index.d.cts +260 -0
- package/dist/host/index.d.ts +260 -0
- package/dist/host/index.js +577 -0
- package/dist/host/index.js.map +1 -0
- package/dist/index.cjs +16 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/mock-host/cli.cjs +800 -0
- package/dist/mock-host/cli.cjs.map +1 -0
- package/dist/mock-host/cli.d.cts +155 -0
- package/dist/mock-host/cli.d.ts +155 -0
- package/dist/mock-host/cli.js +770 -0
- package/dist/mock-host/cli.js.map +1 -0
- package/dist/mock-host/index.cjs +74 -0
- package/dist/mock-host/index.cjs.map +1 -0
- package/dist/mock-host/index.d.cts +95 -0
- package/dist/mock-host/index.d.ts +95 -0
- package/dist/mock-host/index.js +71 -0
- package/dist/mock-host/index.js.map +1 -0
- package/dist/plugin/index.cjs +113 -0
- package/dist/plugin/index.cjs.map +1 -0
- package/dist/plugin/index.d.cts +120 -0
- package/dist/plugin/index.d.ts +120 -0
- package/dist/plugin/index.js +107 -0
- package/dist/plugin/index.js.map +1 -0
- package/dist/registry-DpCx_LxF.d.cts +25 -0
- package/dist/registry-DpCx_LxF.d.ts +25 -0
- package/dist/transport-73otePiw.d.cts +307 -0
- package/dist/transport-73otePiw.d.ts +307 -0
- package/dist/transport-DVn2GVZh.d.cts +32 -0
- package/dist/transport-DVn2GVZh.d.ts +32 -0
- package/package.json +78 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
5
|
+
|
|
6
|
+
// src/plugin/ExtensionRuntimeProvider.tsx
|
|
7
|
+
var ExtensionRuntimeContext = react.createContext(null);
|
|
8
|
+
function ExtensionRuntimeProvider({ transport, children }) {
|
|
9
|
+
const value = react.useMemo(() => transport, [transport]);
|
|
10
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ExtensionRuntimeContext.Provider, { value, children });
|
|
11
|
+
}
|
|
12
|
+
function useExtensionRuntimeTransport(override) {
|
|
13
|
+
const fromContext = react.useContext(ExtensionRuntimeContext);
|
|
14
|
+
const resolved = override ?? fromContext;
|
|
15
|
+
if (!resolved) {
|
|
16
|
+
throw new Error(
|
|
17
|
+
"No McpTransport available. Wrap your plugin in <ExtensionRuntimeProvider transport={...}> or pass `transport` directly to the hook."
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
return resolved;
|
|
21
|
+
}
|
|
22
|
+
function useMcpResource(uri, opts) {
|
|
23
|
+
const transport = useExtensionRuntimeTransport(opts?.transport);
|
|
24
|
+
const [data, setData] = react.useState(void 0);
|
|
25
|
+
const [error, setError] = react.useState(void 0);
|
|
26
|
+
const [loading, setLoading] = react.useState(true);
|
|
27
|
+
const [refetchTick, setRefetchTick] = react.useState(0);
|
|
28
|
+
const controllerRef = react.useRef(null);
|
|
29
|
+
react.useEffect(() => {
|
|
30
|
+
const controller = new AbortController();
|
|
31
|
+
controllerRef.current?.abort();
|
|
32
|
+
controllerRef.current = controller;
|
|
33
|
+
let cancelled = false;
|
|
34
|
+
setLoading(true);
|
|
35
|
+
setError(void 0);
|
|
36
|
+
transport.getResource(uri, controller.signal).then((result) => {
|
|
37
|
+
if (cancelled || controller.signal.aborted) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
setData(result.data);
|
|
41
|
+
setLoading(false);
|
|
42
|
+
}).catch((err) => {
|
|
43
|
+
if (cancelled || controller.signal.aborted) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
47
|
+
setLoading(false);
|
|
48
|
+
});
|
|
49
|
+
return () => {
|
|
50
|
+
cancelled = true;
|
|
51
|
+
controller.abort();
|
|
52
|
+
};
|
|
53
|
+
}, [uri, transport, refetchTick]);
|
|
54
|
+
const refetch = react.useCallback(() => {
|
|
55
|
+
setRefetchTick((tick) => tick + 1);
|
|
56
|
+
}, []);
|
|
57
|
+
return { data, error, loading, refetch };
|
|
58
|
+
}
|
|
59
|
+
function useMcpTool(toolName, opts) {
|
|
60
|
+
const transport = useExtensionRuntimeTransport(opts?.transport);
|
|
61
|
+
const [loading, setLoading] = react.useState(false);
|
|
62
|
+
const [error, setError] = react.useState(void 0);
|
|
63
|
+
const controllerRef = react.useRef(null);
|
|
64
|
+
const mountedRef = react.useRef(true);
|
|
65
|
+
react.useEffect(() => {
|
|
66
|
+
mountedRef.current = true;
|
|
67
|
+
return () => {
|
|
68
|
+
mountedRef.current = false;
|
|
69
|
+
controllerRef.current?.abort();
|
|
70
|
+
};
|
|
71
|
+
}, []);
|
|
72
|
+
const invoke = react.useCallback(
|
|
73
|
+
async (req) => {
|
|
74
|
+
const controller = new AbortController();
|
|
75
|
+
controllerRef.current?.abort();
|
|
76
|
+
controllerRef.current = controller;
|
|
77
|
+
setLoading(true);
|
|
78
|
+
setError(void 0);
|
|
79
|
+
try {
|
|
80
|
+
const result = await transport.invokeTool(
|
|
81
|
+
toolName,
|
|
82
|
+
req,
|
|
83
|
+
controller.signal
|
|
84
|
+
);
|
|
85
|
+
if (mountedRef.current && !controller.signal.aborted) {
|
|
86
|
+
setLoading(false);
|
|
87
|
+
}
|
|
88
|
+
return result;
|
|
89
|
+
} catch (err) {
|
|
90
|
+
const wrapped = err instanceof Error ? err : new Error(String(err));
|
|
91
|
+
if (mountedRef.current && !controller.signal.aborted) {
|
|
92
|
+
setError(wrapped);
|
|
93
|
+
setLoading(false);
|
|
94
|
+
}
|
|
95
|
+
throw wrapped;
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
[transport, toolName]
|
|
99
|
+
);
|
|
100
|
+
return { invoke, loading, error };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// src/plugin/define.ts
|
|
104
|
+
var defineDeclarativePlugin = (cfg) => cfg;
|
|
105
|
+
var defineEthisysPlugin = (cfg) => cfg;
|
|
106
|
+
|
|
107
|
+
exports.ExtensionRuntimeProvider = ExtensionRuntimeProvider;
|
|
108
|
+
exports.defineDeclarativePlugin = defineDeclarativePlugin;
|
|
109
|
+
exports.defineEthisysPlugin = defineEthisysPlugin;
|
|
110
|
+
exports.useMcpResource = useMcpResource;
|
|
111
|
+
exports.useMcpTool = useMcpTool;
|
|
112
|
+
//# sourceMappingURL=index.cjs.map
|
|
113
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/plugin/ExtensionRuntimeProvider.tsx","../../src/plugin/useMcpResource.ts","../../src/plugin/useMcpTool.ts","../../src/plugin/define.ts"],"names":["createContext","useMemo","jsx","useContext","useState","useRef","useEffect","useCallback"],"mappings":"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","file":"index.cjs","sourcesContent":["import { createContext, useContext, useMemo, type ReactNode } from \"react\";\nimport type { McpTransport } from \"./transport\";\n\n/**\n * React context carrying the {@link McpTransport} the plugin should use to\n * reach the host. `null` is the explicit \"not provided\" sentinel so the hooks\n * can disambiguate from a transport that was provided but is incidentally\n * falsy in some other dimension.\n */\nconst ExtensionRuntimeContext = createContext<McpTransport | null>(null);\n\nexport interface ExtensionRuntimeProviderProps\n{\n transport: McpTransport;\n children?: ReactNode;\n}\n\n/**\n * Wrap a plugin's React tree so descendant {@link useMcpResource} and\n * {@link useMcpTool} calls resolve a default transport without having to\n * thread it through every component.\n *\n * Hooks still accept a per-call `transport` override, which takes precedence\n * over the context value — useful for tests and for plugins that want to\n * shard work across multiple hosts.\n */\nexport function ExtensionRuntimeProvider({ transport, children }: ExtensionRuntimeProviderProps): ReactNode\n{\n // Memoise so swapping `children` doesn't churn the context identity.\n const value = useMemo(() => transport, [transport]);\n return (\n <ExtensionRuntimeContext.Provider value={value}>\n {children}\n </ExtensionRuntimeContext.Provider>\n );\n}\n\n/**\n * Internal helper used by the hooks. Returns the explicit override when\n * supplied, otherwise falls back to the context. Throws a deterministic\n * error if neither is available so misconfiguration fails loudly at the\n * first render rather than producing silent no-ops.\n */\nexport function useExtensionRuntimeTransport(override?: McpTransport): McpTransport\n{\n const fromContext = useContext(ExtensionRuntimeContext);\n const resolved = override ?? fromContext;\n if (!resolved)\n {\n throw new Error(\n \"No McpTransport available. Wrap your plugin in <ExtensionRuntimeProvider transport={...}> \"\n + \"or pass `transport` directly to the hook.\",\n );\n }\n return resolved;\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { useExtensionRuntimeTransport } from \"./ExtensionRuntimeProvider\";\nimport type { McpTransport } from \"./transport\";\n\nexport interface UseMcpResourceOptions\n{\n /**\n * Override the transport resolved from {@link ExtensionRuntimeProvider}.\n * Primarily intended for tests and advanced multi-host scenarios.\n */\n transport?: McpTransport;\n}\n\nexport interface UseMcpResourceResult<T>\n{\n data: T | undefined;\n error: Error | undefined;\n loading: boolean;\n /** Re-run the fetch against the current URI. Stable across renders. */\n refetch: () => void;\n}\n\n/**\n * Subscribe to an MCP resource by URI.\n *\n * The hook fetches via the resolved {@link McpTransport} on mount and whenever\n * the URI changes. Each fetch is cancellable: when the component unmounts or\n * the URI changes, the in-flight call is aborted via an {@link AbortController}\n * so stale responses cannot overwrite later state.\n *\n * `refetch` re-runs the fetch against the latest URI. The returned callback is\n * stable across renders so it is safe to include in `useEffect` dependency\n * arrays.\n */\nexport function useMcpResource<T>(\n uri: string,\n opts?: UseMcpResourceOptions,\n): UseMcpResourceResult<T>\n{\n const transport = useExtensionRuntimeTransport(opts?.transport);\n\n const [data, setData] = useState<T | undefined>(undefined);\n const [error, setError] = useState<Error | undefined>(undefined);\n const [loading, setLoading] = useState<boolean>(true);\n\n // Bump this counter to force a re-fetch from `refetch()`.\n const [refetchTick, setRefetchTick] = useState(0);\n\n // Track the active controller so `refetch` and unmount can abort it.\n const controllerRef = useRef<AbortController | null>(null);\n\n useEffect(() =>\n {\n const controller = new AbortController();\n controllerRef.current?.abort();\n controllerRef.current = controller;\n\n let cancelled = false;\n setLoading(true);\n setError(undefined);\n\n transport\n .getResource<T>(uri, controller.signal)\n .then((result) =>\n {\n if (cancelled || controller.signal.aborted)\n {\n return;\n }\n setData(result.data);\n setLoading(false);\n })\n .catch((err: unknown) =>\n {\n if (cancelled || controller.signal.aborted)\n {\n return;\n }\n setError(err instanceof Error ? err : new Error(String(err)));\n setLoading(false);\n });\n\n return () =>\n {\n cancelled = true;\n controller.abort();\n };\n }, [uri, transport, refetchTick]);\n\n const refetch = useCallback(() =>\n {\n setRefetchTick((tick) => tick + 1);\n }, []);\n\n return { data, error, loading, refetch };\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { useExtensionRuntimeTransport } from \"./ExtensionRuntimeProvider\";\nimport type { McpTransport } from \"./transport\";\n\nexport interface UseMcpToolOptions\n{\n /**\n * Override the transport resolved from {@link ExtensionRuntimeProvider}.\n * Primarily intended for tests and advanced multi-host scenarios.\n */\n transport?: McpTransport;\n}\n\nexport interface UseMcpToolResult<TReq, TRes>\n{\n /**\n * Invoke the tool. Each call gets a fresh {@link AbortController} that is\n * aborted on unmount so unresolved promises cannot keep state alive after\n * the component is gone.\n */\n invoke: (req: TReq) => Promise<TRes>;\n loading: boolean;\n error: Error | undefined;\n}\n\n/**\n * Hook returning a callback that invokes an MCP tool by name through the\n * resolved {@link McpTransport}.\n *\n * `loading` and `error` track the most recent in-flight invocation. Callers\n * may also `await` the returned promise directly — failures are both rejected\n * to the caller AND surfaced via `error` so component-level UI can react.\n *\n * `invoke` is a stable reference across renders for the same `toolName` and\n * transport, making it safe to use inside `useEffect`/`useCallback` deps.\n */\nexport function useMcpTool<TReq, TRes>(\n toolName: string,\n opts?: UseMcpToolOptions,\n): UseMcpToolResult<TReq, TRes>\n{\n const transport = useExtensionRuntimeTransport(opts?.transport);\n\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | undefined>(undefined);\n\n // Track the latest controller so unmount can abort an in-flight call.\n const controllerRef = useRef<AbortController | null>(null);\n const mountedRef = useRef(true);\n\n useEffect(() =>\n {\n mountedRef.current = true;\n return () =>\n {\n mountedRef.current = false;\n controllerRef.current?.abort();\n };\n }, []);\n\n const invoke = useCallback(\n async (req: TReq): Promise<TRes> =>\n {\n const controller = new AbortController();\n controllerRef.current?.abort();\n controllerRef.current = controller;\n\n setLoading(true);\n setError(undefined);\n\n try\n {\n const result = await transport.invokeTool<TReq, TRes>(\n toolName,\n req,\n controller.signal,\n );\n if (mountedRef.current && !controller.signal.aborted)\n {\n setLoading(false);\n }\n return result;\n }\n catch (err)\n {\n const wrapped = err instanceof Error ? err : new Error(String(err));\n if (mountedRef.current && !controller.signal.aborted)\n {\n setError(wrapped);\n setLoading(false);\n }\n throw wrapped;\n }\n },\n [transport, toolName],\n );\n\n return { invoke, loading, error };\n}\n","import type { RenderMode, SduiNode } from \"@ethisyscore/protocol\";\n\n/**\n * Configuration accepted by {@link defineDeclarativePlugin}.\n *\n * A Contract A (host-rendered) plugin contributes a map of resource URIs to\n * declarative SDUI trees. The host fetches a resource by URI and renders it\n * against the v1 vocabulary.\n */\nexport interface DeclarativePluginConfig\n{\n resources: Record<string, SduiNode>;\n}\n\n/**\n * Configuration accepted by {@link defineEthisysPlugin}.\n *\n * `renderMode` is constrained to the protocol's {@link RenderMode} enum so\n * authoring mistakes (`\"iframe\"`, `\"webview\"`, …) are caught at compile time.\n * `mount` is generic so Contract B remote-runtime authors can attach their\n * own mount surface without losing type information at the call site.\n */\nexport interface EthisysPluginConfig<TMount>\n{\n renderMode: RenderMode;\n mount?: TMount;\n}\n\n/**\n * Author-facing identity helper for a Contract A (host-rendered) declarative\n * plugin definition.\n *\n * The helper performs no runtime work — it exists purely so authoring sites\n * receive precise type inference and editor tooling against the protocol's\n * {@link SduiNode} contract. The returned value is the exact same reference\n * the caller passed in.\n */\nexport const defineDeclarativePlugin = (\n cfg: DeclarativePluginConfig,\n): DeclarativePluginConfig => cfg;\n\n/**\n * Author-facing identity helper for a generic EthisysCore plugin definition.\n *\n * The helper performs no runtime work. It constrains `renderMode` to the\n * protocol's {@link RenderMode} enum and preserves the inferred type of an\n * optional `mount` surface, so Contract B authors can pass through their own\n * mount object without widening it to `unknown`.\n */\nexport const defineEthisysPlugin = <TMount>(\n cfg: EthisysPluginConfig<TMount>,\n): EthisysPluginConfig<TMount> => cfg;\n"]}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { M as McpTransport } from '../transport-DVn2GVZh.cjs';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { SduiNode, RenderMode } from '@ethisyscore/protocol';
|
|
4
|
+
|
|
5
|
+
interface ExtensionRuntimeProviderProps {
|
|
6
|
+
transport: McpTransport;
|
|
7
|
+
children?: ReactNode;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Wrap a plugin's React tree so descendant {@link useMcpResource} and
|
|
11
|
+
* {@link useMcpTool} calls resolve a default transport without having to
|
|
12
|
+
* thread it through every component.
|
|
13
|
+
*
|
|
14
|
+
* Hooks still accept a per-call `transport` override, which takes precedence
|
|
15
|
+
* over the context value — useful for tests and for plugins that want to
|
|
16
|
+
* shard work across multiple hosts.
|
|
17
|
+
*/
|
|
18
|
+
declare function ExtensionRuntimeProvider({ transport, children }: ExtensionRuntimeProviderProps): ReactNode;
|
|
19
|
+
|
|
20
|
+
interface UseMcpResourceOptions {
|
|
21
|
+
/**
|
|
22
|
+
* Override the transport resolved from {@link ExtensionRuntimeProvider}.
|
|
23
|
+
* Primarily intended for tests and advanced multi-host scenarios.
|
|
24
|
+
*/
|
|
25
|
+
transport?: McpTransport;
|
|
26
|
+
}
|
|
27
|
+
interface UseMcpResourceResult<T> {
|
|
28
|
+
data: T | undefined;
|
|
29
|
+
error: Error | undefined;
|
|
30
|
+
loading: boolean;
|
|
31
|
+
/** Re-run the fetch against the current URI. Stable across renders. */
|
|
32
|
+
refetch: () => void;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Subscribe to an MCP resource by URI.
|
|
36
|
+
*
|
|
37
|
+
* The hook fetches via the resolved {@link McpTransport} on mount and whenever
|
|
38
|
+
* the URI changes. Each fetch is cancellable: when the component unmounts or
|
|
39
|
+
* the URI changes, the in-flight call is aborted via an {@link AbortController}
|
|
40
|
+
* so stale responses cannot overwrite later state.
|
|
41
|
+
*
|
|
42
|
+
* `refetch` re-runs the fetch against the latest URI. The returned callback is
|
|
43
|
+
* stable across renders so it is safe to include in `useEffect` dependency
|
|
44
|
+
* arrays.
|
|
45
|
+
*/
|
|
46
|
+
declare function useMcpResource<T>(uri: string, opts?: UseMcpResourceOptions): UseMcpResourceResult<T>;
|
|
47
|
+
|
|
48
|
+
interface UseMcpToolOptions {
|
|
49
|
+
/**
|
|
50
|
+
* Override the transport resolved from {@link ExtensionRuntimeProvider}.
|
|
51
|
+
* Primarily intended for tests and advanced multi-host scenarios.
|
|
52
|
+
*/
|
|
53
|
+
transport?: McpTransport;
|
|
54
|
+
}
|
|
55
|
+
interface UseMcpToolResult<TReq, TRes> {
|
|
56
|
+
/**
|
|
57
|
+
* Invoke the tool. Each call gets a fresh {@link AbortController} that is
|
|
58
|
+
* aborted on unmount so unresolved promises cannot keep state alive after
|
|
59
|
+
* the component is gone.
|
|
60
|
+
*/
|
|
61
|
+
invoke: (req: TReq) => Promise<TRes>;
|
|
62
|
+
loading: boolean;
|
|
63
|
+
error: Error | undefined;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Hook returning a callback that invokes an MCP tool by name through the
|
|
67
|
+
* resolved {@link McpTransport}.
|
|
68
|
+
*
|
|
69
|
+
* `loading` and `error` track the most recent in-flight invocation. Callers
|
|
70
|
+
* may also `await` the returned promise directly — failures are both rejected
|
|
71
|
+
* to the caller AND surfaced via `error` so component-level UI can react.
|
|
72
|
+
*
|
|
73
|
+
* `invoke` is a stable reference across renders for the same `toolName` and
|
|
74
|
+
* transport, making it safe to use inside `useEffect`/`useCallback` deps.
|
|
75
|
+
*/
|
|
76
|
+
declare function useMcpTool<TReq, TRes>(toolName: string, opts?: UseMcpToolOptions): UseMcpToolResult<TReq, TRes>;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Configuration accepted by {@link defineDeclarativePlugin}.
|
|
80
|
+
*
|
|
81
|
+
* A Contract A (host-rendered) plugin contributes a map of resource URIs to
|
|
82
|
+
* declarative SDUI trees. The host fetches a resource by URI and renders it
|
|
83
|
+
* against the v1 vocabulary.
|
|
84
|
+
*/
|
|
85
|
+
interface DeclarativePluginConfig {
|
|
86
|
+
resources: Record<string, SduiNode>;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Configuration accepted by {@link defineEthisysPlugin}.
|
|
90
|
+
*
|
|
91
|
+
* `renderMode` is constrained to the protocol's {@link RenderMode} enum so
|
|
92
|
+
* authoring mistakes (`"iframe"`, `"webview"`, …) are caught at compile time.
|
|
93
|
+
* `mount` is generic so Contract B remote-runtime authors can attach their
|
|
94
|
+
* own mount surface without losing type information at the call site.
|
|
95
|
+
*/
|
|
96
|
+
interface EthisysPluginConfig<TMount> {
|
|
97
|
+
renderMode: RenderMode;
|
|
98
|
+
mount?: TMount;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Author-facing identity helper for a Contract A (host-rendered) declarative
|
|
102
|
+
* plugin definition.
|
|
103
|
+
*
|
|
104
|
+
* The helper performs no runtime work — it exists purely so authoring sites
|
|
105
|
+
* receive precise type inference and editor tooling against the protocol's
|
|
106
|
+
* {@link SduiNode} contract. The returned value is the exact same reference
|
|
107
|
+
* the caller passed in.
|
|
108
|
+
*/
|
|
109
|
+
declare const defineDeclarativePlugin: (cfg: DeclarativePluginConfig) => DeclarativePluginConfig;
|
|
110
|
+
/**
|
|
111
|
+
* Author-facing identity helper for a generic EthisysCore plugin definition.
|
|
112
|
+
*
|
|
113
|
+
* The helper performs no runtime work. It constrains `renderMode` to the
|
|
114
|
+
* protocol's {@link RenderMode} enum and preserves the inferred type of an
|
|
115
|
+
* optional `mount` surface, so Contract B authors can pass through their own
|
|
116
|
+
* mount object without widening it to `unknown`.
|
|
117
|
+
*/
|
|
118
|
+
declare const defineEthisysPlugin: <TMount>(cfg: EthisysPluginConfig<TMount>) => EthisysPluginConfig<TMount>;
|
|
119
|
+
|
|
120
|
+
export { type DeclarativePluginConfig, type EthisysPluginConfig, ExtensionRuntimeProvider, type ExtensionRuntimeProviderProps, McpTransport, type UseMcpResourceOptions, type UseMcpResourceResult, type UseMcpToolOptions, type UseMcpToolResult, defineDeclarativePlugin, defineEthisysPlugin, useMcpResource, useMcpTool };
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { M as McpTransport } from '../transport-DVn2GVZh.js';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { SduiNode, RenderMode } from '@ethisyscore/protocol';
|
|
4
|
+
|
|
5
|
+
interface ExtensionRuntimeProviderProps {
|
|
6
|
+
transport: McpTransport;
|
|
7
|
+
children?: ReactNode;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Wrap a plugin's React tree so descendant {@link useMcpResource} and
|
|
11
|
+
* {@link useMcpTool} calls resolve a default transport without having to
|
|
12
|
+
* thread it through every component.
|
|
13
|
+
*
|
|
14
|
+
* Hooks still accept a per-call `transport` override, which takes precedence
|
|
15
|
+
* over the context value — useful for tests and for plugins that want to
|
|
16
|
+
* shard work across multiple hosts.
|
|
17
|
+
*/
|
|
18
|
+
declare function ExtensionRuntimeProvider({ transport, children }: ExtensionRuntimeProviderProps): ReactNode;
|
|
19
|
+
|
|
20
|
+
interface UseMcpResourceOptions {
|
|
21
|
+
/**
|
|
22
|
+
* Override the transport resolved from {@link ExtensionRuntimeProvider}.
|
|
23
|
+
* Primarily intended for tests and advanced multi-host scenarios.
|
|
24
|
+
*/
|
|
25
|
+
transport?: McpTransport;
|
|
26
|
+
}
|
|
27
|
+
interface UseMcpResourceResult<T> {
|
|
28
|
+
data: T | undefined;
|
|
29
|
+
error: Error | undefined;
|
|
30
|
+
loading: boolean;
|
|
31
|
+
/** Re-run the fetch against the current URI. Stable across renders. */
|
|
32
|
+
refetch: () => void;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Subscribe to an MCP resource by URI.
|
|
36
|
+
*
|
|
37
|
+
* The hook fetches via the resolved {@link McpTransport} on mount and whenever
|
|
38
|
+
* the URI changes. Each fetch is cancellable: when the component unmounts or
|
|
39
|
+
* the URI changes, the in-flight call is aborted via an {@link AbortController}
|
|
40
|
+
* so stale responses cannot overwrite later state.
|
|
41
|
+
*
|
|
42
|
+
* `refetch` re-runs the fetch against the latest URI. The returned callback is
|
|
43
|
+
* stable across renders so it is safe to include in `useEffect` dependency
|
|
44
|
+
* arrays.
|
|
45
|
+
*/
|
|
46
|
+
declare function useMcpResource<T>(uri: string, opts?: UseMcpResourceOptions): UseMcpResourceResult<T>;
|
|
47
|
+
|
|
48
|
+
interface UseMcpToolOptions {
|
|
49
|
+
/**
|
|
50
|
+
* Override the transport resolved from {@link ExtensionRuntimeProvider}.
|
|
51
|
+
* Primarily intended for tests and advanced multi-host scenarios.
|
|
52
|
+
*/
|
|
53
|
+
transport?: McpTransport;
|
|
54
|
+
}
|
|
55
|
+
interface UseMcpToolResult<TReq, TRes> {
|
|
56
|
+
/**
|
|
57
|
+
* Invoke the tool. Each call gets a fresh {@link AbortController} that is
|
|
58
|
+
* aborted on unmount so unresolved promises cannot keep state alive after
|
|
59
|
+
* the component is gone.
|
|
60
|
+
*/
|
|
61
|
+
invoke: (req: TReq) => Promise<TRes>;
|
|
62
|
+
loading: boolean;
|
|
63
|
+
error: Error | undefined;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Hook returning a callback that invokes an MCP tool by name through the
|
|
67
|
+
* resolved {@link McpTransport}.
|
|
68
|
+
*
|
|
69
|
+
* `loading` and `error` track the most recent in-flight invocation. Callers
|
|
70
|
+
* may also `await` the returned promise directly — failures are both rejected
|
|
71
|
+
* to the caller AND surfaced via `error` so component-level UI can react.
|
|
72
|
+
*
|
|
73
|
+
* `invoke` is a stable reference across renders for the same `toolName` and
|
|
74
|
+
* transport, making it safe to use inside `useEffect`/`useCallback` deps.
|
|
75
|
+
*/
|
|
76
|
+
declare function useMcpTool<TReq, TRes>(toolName: string, opts?: UseMcpToolOptions): UseMcpToolResult<TReq, TRes>;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Configuration accepted by {@link defineDeclarativePlugin}.
|
|
80
|
+
*
|
|
81
|
+
* A Contract A (host-rendered) plugin contributes a map of resource URIs to
|
|
82
|
+
* declarative SDUI trees. The host fetches a resource by URI and renders it
|
|
83
|
+
* against the v1 vocabulary.
|
|
84
|
+
*/
|
|
85
|
+
interface DeclarativePluginConfig {
|
|
86
|
+
resources: Record<string, SduiNode>;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Configuration accepted by {@link defineEthisysPlugin}.
|
|
90
|
+
*
|
|
91
|
+
* `renderMode` is constrained to the protocol's {@link RenderMode} enum so
|
|
92
|
+
* authoring mistakes (`"iframe"`, `"webview"`, …) are caught at compile time.
|
|
93
|
+
* `mount` is generic so Contract B remote-runtime authors can attach their
|
|
94
|
+
* own mount surface without losing type information at the call site.
|
|
95
|
+
*/
|
|
96
|
+
interface EthisysPluginConfig<TMount> {
|
|
97
|
+
renderMode: RenderMode;
|
|
98
|
+
mount?: TMount;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Author-facing identity helper for a Contract A (host-rendered) declarative
|
|
102
|
+
* plugin definition.
|
|
103
|
+
*
|
|
104
|
+
* The helper performs no runtime work — it exists purely so authoring sites
|
|
105
|
+
* receive precise type inference and editor tooling against the protocol's
|
|
106
|
+
* {@link SduiNode} contract. The returned value is the exact same reference
|
|
107
|
+
* the caller passed in.
|
|
108
|
+
*/
|
|
109
|
+
declare const defineDeclarativePlugin: (cfg: DeclarativePluginConfig) => DeclarativePluginConfig;
|
|
110
|
+
/**
|
|
111
|
+
* Author-facing identity helper for a generic EthisysCore plugin definition.
|
|
112
|
+
*
|
|
113
|
+
* The helper performs no runtime work. It constrains `renderMode` to the
|
|
114
|
+
* protocol's {@link RenderMode} enum and preserves the inferred type of an
|
|
115
|
+
* optional `mount` surface, so Contract B authors can pass through their own
|
|
116
|
+
* mount object without widening it to `unknown`.
|
|
117
|
+
*/
|
|
118
|
+
declare const defineEthisysPlugin: <TMount>(cfg: EthisysPluginConfig<TMount>) => EthisysPluginConfig<TMount>;
|
|
119
|
+
|
|
120
|
+
export { type DeclarativePluginConfig, type EthisysPluginConfig, ExtensionRuntimeProvider, type ExtensionRuntimeProviderProps, McpTransport, type UseMcpResourceOptions, type UseMcpResourceResult, type UseMcpToolOptions, type UseMcpToolResult, defineDeclarativePlugin, defineEthisysPlugin, useMcpResource, useMcpTool };
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { createContext, useMemo, useState, useRef, useEffect, useCallback, useContext } from 'react';
|
|
2
|
+
import { jsx } from 'react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
// src/plugin/ExtensionRuntimeProvider.tsx
|
|
5
|
+
var ExtensionRuntimeContext = createContext(null);
|
|
6
|
+
function ExtensionRuntimeProvider({ transport, children }) {
|
|
7
|
+
const value = useMemo(() => transport, [transport]);
|
|
8
|
+
return /* @__PURE__ */ jsx(ExtensionRuntimeContext.Provider, { value, children });
|
|
9
|
+
}
|
|
10
|
+
function useExtensionRuntimeTransport(override) {
|
|
11
|
+
const fromContext = useContext(ExtensionRuntimeContext);
|
|
12
|
+
const resolved = override ?? fromContext;
|
|
13
|
+
if (!resolved) {
|
|
14
|
+
throw new Error(
|
|
15
|
+
"No McpTransport available. Wrap your plugin in <ExtensionRuntimeProvider transport={...}> or pass `transport` directly to the hook."
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
return resolved;
|
|
19
|
+
}
|
|
20
|
+
function useMcpResource(uri, opts) {
|
|
21
|
+
const transport = useExtensionRuntimeTransport(opts?.transport);
|
|
22
|
+
const [data, setData] = useState(void 0);
|
|
23
|
+
const [error, setError] = useState(void 0);
|
|
24
|
+
const [loading, setLoading] = useState(true);
|
|
25
|
+
const [refetchTick, setRefetchTick] = useState(0);
|
|
26
|
+
const controllerRef = useRef(null);
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
const controller = new AbortController();
|
|
29
|
+
controllerRef.current?.abort();
|
|
30
|
+
controllerRef.current = controller;
|
|
31
|
+
let cancelled = false;
|
|
32
|
+
setLoading(true);
|
|
33
|
+
setError(void 0);
|
|
34
|
+
transport.getResource(uri, controller.signal).then((result) => {
|
|
35
|
+
if (cancelled || controller.signal.aborted) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
setData(result.data);
|
|
39
|
+
setLoading(false);
|
|
40
|
+
}).catch((err) => {
|
|
41
|
+
if (cancelled || controller.signal.aborted) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
45
|
+
setLoading(false);
|
|
46
|
+
});
|
|
47
|
+
return () => {
|
|
48
|
+
cancelled = true;
|
|
49
|
+
controller.abort();
|
|
50
|
+
};
|
|
51
|
+
}, [uri, transport, refetchTick]);
|
|
52
|
+
const refetch = useCallback(() => {
|
|
53
|
+
setRefetchTick((tick) => tick + 1);
|
|
54
|
+
}, []);
|
|
55
|
+
return { data, error, loading, refetch };
|
|
56
|
+
}
|
|
57
|
+
function useMcpTool(toolName, opts) {
|
|
58
|
+
const transport = useExtensionRuntimeTransport(opts?.transport);
|
|
59
|
+
const [loading, setLoading] = useState(false);
|
|
60
|
+
const [error, setError] = useState(void 0);
|
|
61
|
+
const controllerRef = useRef(null);
|
|
62
|
+
const mountedRef = useRef(true);
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
mountedRef.current = true;
|
|
65
|
+
return () => {
|
|
66
|
+
mountedRef.current = false;
|
|
67
|
+
controllerRef.current?.abort();
|
|
68
|
+
};
|
|
69
|
+
}, []);
|
|
70
|
+
const invoke = useCallback(
|
|
71
|
+
async (req) => {
|
|
72
|
+
const controller = new AbortController();
|
|
73
|
+
controllerRef.current?.abort();
|
|
74
|
+
controllerRef.current = controller;
|
|
75
|
+
setLoading(true);
|
|
76
|
+
setError(void 0);
|
|
77
|
+
try {
|
|
78
|
+
const result = await transport.invokeTool(
|
|
79
|
+
toolName,
|
|
80
|
+
req,
|
|
81
|
+
controller.signal
|
|
82
|
+
);
|
|
83
|
+
if (mountedRef.current && !controller.signal.aborted) {
|
|
84
|
+
setLoading(false);
|
|
85
|
+
}
|
|
86
|
+
return result;
|
|
87
|
+
} catch (err) {
|
|
88
|
+
const wrapped = err instanceof Error ? err : new Error(String(err));
|
|
89
|
+
if (mountedRef.current && !controller.signal.aborted) {
|
|
90
|
+
setError(wrapped);
|
|
91
|
+
setLoading(false);
|
|
92
|
+
}
|
|
93
|
+
throw wrapped;
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
[transport, toolName]
|
|
97
|
+
);
|
|
98
|
+
return { invoke, loading, error };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// src/plugin/define.ts
|
|
102
|
+
var defineDeclarativePlugin = (cfg) => cfg;
|
|
103
|
+
var defineEthisysPlugin = (cfg) => cfg;
|
|
104
|
+
|
|
105
|
+
export { ExtensionRuntimeProvider, defineDeclarativePlugin, defineEthisysPlugin, useMcpResource, useMcpTool };
|
|
106
|
+
//# sourceMappingURL=index.js.map
|
|
107
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/plugin/ExtensionRuntimeProvider.tsx","../../src/plugin/useMcpResource.ts","../../src/plugin/useMcpTool.ts","../../src/plugin/define.ts"],"names":["useState","useRef","useEffect","useCallback"],"mappings":"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","file":"index.js","sourcesContent":["import { createContext, useContext, useMemo, type ReactNode } from \"react\";\nimport type { McpTransport } from \"./transport\";\n\n/**\n * React context carrying the {@link McpTransport} the plugin should use to\n * reach the host. `null` is the explicit \"not provided\" sentinel so the hooks\n * can disambiguate from a transport that was provided but is incidentally\n * falsy in some other dimension.\n */\nconst ExtensionRuntimeContext = createContext<McpTransport | null>(null);\n\nexport interface ExtensionRuntimeProviderProps\n{\n transport: McpTransport;\n children?: ReactNode;\n}\n\n/**\n * Wrap a plugin's React tree so descendant {@link useMcpResource} and\n * {@link useMcpTool} calls resolve a default transport without having to\n * thread it through every component.\n *\n * Hooks still accept a per-call `transport` override, which takes precedence\n * over the context value — useful for tests and for plugins that want to\n * shard work across multiple hosts.\n */\nexport function ExtensionRuntimeProvider({ transport, children }: ExtensionRuntimeProviderProps): ReactNode\n{\n // Memoise so swapping `children` doesn't churn the context identity.\n const value = useMemo(() => transport, [transport]);\n return (\n <ExtensionRuntimeContext.Provider value={value}>\n {children}\n </ExtensionRuntimeContext.Provider>\n );\n}\n\n/**\n * Internal helper used by the hooks. Returns the explicit override when\n * supplied, otherwise falls back to the context. Throws a deterministic\n * error if neither is available so misconfiguration fails loudly at the\n * first render rather than producing silent no-ops.\n */\nexport function useExtensionRuntimeTransport(override?: McpTransport): McpTransport\n{\n const fromContext = useContext(ExtensionRuntimeContext);\n const resolved = override ?? fromContext;\n if (!resolved)\n {\n throw new Error(\n \"No McpTransport available. Wrap your plugin in <ExtensionRuntimeProvider transport={...}> \"\n + \"or pass `transport` directly to the hook.\",\n );\n }\n return resolved;\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { useExtensionRuntimeTransport } from \"./ExtensionRuntimeProvider\";\nimport type { McpTransport } from \"./transport\";\n\nexport interface UseMcpResourceOptions\n{\n /**\n * Override the transport resolved from {@link ExtensionRuntimeProvider}.\n * Primarily intended for tests and advanced multi-host scenarios.\n */\n transport?: McpTransport;\n}\n\nexport interface UseMcpResourceResult<T>\n{\n data: T | undefined;\n error: Error | undefined;\n loading: boolean;\n /** Re-run the fetch against the current URI. Stable across renders. */\n refetch: () => void;\n}\n\n/**\n * Subscribe to an MCP resource by URI.\n *\n * The hook fetches via the resolved {@link McpTransport} on mount and whenever\n * the URI changes. Each fetch is cancellable: when the component unmounts or\n * the URI changes, the in-flight call is aborted via an {@link AbortController}\n * so stale responses cannot overwrite later state.\n *\n * `refetch` re-runs the fetch against the latest URI. The returned callback is\n * stable across renders so it is safe to include in `useEffect` dependency\n * arrays.\n */\nexport function useMcpResource<T>(\n uri: string,\n opts?: UseMcpResourceOptions,\n): UseMcpResourceResult<T>\n{\n const transport = useExtensionRuntimeTransport(opts?.transport);\n\n const [data, setData] = useState<T | undefined>(undefined);\n const [error, setError] = useState<Error | undefined>(undefined);\n const [loading, setLoading] = useState<boolean>(true);\n\n // Bump this counter to force a re-fetch from `refetch()`.\n const [refetchTick, setRefetchTick] = useState(0);\n\n // Track the active controller so `refetch` and unmount can abort it.\n const controllerRef = useRef<AbortController | null>(null);\n\n useEffect(() =>\n {\n const controller = new AbortController();\n controllerRef.current?.abort();\n controllerRef.current = controller;\n\n let cancelled = false;\n setLoading(true);\n setError(undefined);\n\n transport\n .getResource<T>(uri, controller.signal)\n .then((result) =>\n {\n if (cancelled || controller.signal.aborted)\n {\n return;\n }\n setData(result.data);\n setLoading(false);\n })\n .catch((err: unknown) =>\n {\n if (cancelled || controller.signal.aborted)\n {\n return;\n }\n setError(err instanceof Error ? err : new Error(String(err)));\n setLoading(false);\n });\n\n return () =>\n {\n cancelled = true;\n controller.abort();\n };\n }, [uri, transport, refetchTick]);\n\n const refetch = useCallback(() =>\n {\n setRefetchTick((tick) => tick + 1);\n }, []);\n\n return { data, error, loading, refetch };\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { useExtensionRuntimeTransport } from \"./ExtensionRuntimeProvider\";\nimport type { McpTransport } from \"./transport\";\n\nexport interface UseMcpToolOptions\n{\n /**\n * Override the transport resolved from {@link ExtensionRuntimeProvider}.\n * Primarily intended for tests and advanced multi-host scenarios.\n */\n transport?: McpTransport;\n}\n\nexport interface UseMcpToolResult<TReq, TRes>\n{\n /**\n * Invoke the tool. Each call gets a fresh {@link AbortController} that is\n * aborted on unmount so unresolved promises cannot keep state alive after\n * the component is gone.\n */\n invoke: (req: TReq) => Promise<TRes>;\n loading: boolean;\n error: Error | undefined;\n}\n\n/**\n * Hook returning a callback that invokes an MCP tool by name through the\n * resolved {@link McpTransport}.\n *\n * `loading` and `error` track the most recent in-flight invocation. Callers\n * may also `await` the returned promise directly — failures are both rejected\n * to the caller AND surfaced via `error` so component-level UI can react.\n *\n * `invoke` is a stable reference across renders for the same `toolName` and\n * transport, making it safe to use inside `useEffect`/`useCallback` deps.\n */\nexport function useMcpTool<TReq, TRes>(\n toolName: string,\n opts?: UseMcpToolOptions,\n): UseMcpToolResult<TReq, TRes>\n{\n const transport = useExtensionRuntimeTransport(opts?.transport);\n\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | undefined>(undefined);\n\n // Track the latest controller so unmount can abort an in-flight call.\n const controllerRef = useRef<AbortController | null>(null);\n const mountedRef = useRef(true);\n\n useEffect(() =>\n {\n mountedRef.current = true;\n return () =>\n {\n mountedRef.current = false;\n controllerRef.current?.abort();\n };\n }, []);\n\n const invoke = useCallback(\n async (req: TReq): Promise<TRes> =>\n {\n const controller = new AbortController();\n controllerRef.current?.abort();\n controllerRef.current = controller;\n\n setLoading(true);\n setError(undefined);\n\n try\n {\n const result = await transport.invokeTool<TReq, TRes>(\n toolName,\n req,\n controller.signal,\n );\n if (mountedRef.current && !controller.signal.aborted)\n {\n setLoading(false);\n }\n return result;\n }\n catch (err)\n {\n const wrapped = err instanceof Error ? err : new Error(String(err));\n if (mountedRef.current && !controller.signal.aborted)\n {\n setError(wrapped);\n setLoading(false);\n }\n throw wrapped;\n }\n },\n [transport, toolName],\n );\n\n return { invoke, loading, error };\n}\n","import type { RenderMode, SduiNode } from \"@ethisyscore/protocol\";\n\n/**\n * Configuration accepted by {@link defineDeclarativePlugin}.\n *\n * A Contract A (host-rendered) plugin contributes a map of resource URIs to\n * declarative SDUI trees. The host fetches a resource by URI and renders it\n * against the v1 vocabulary.\n */\nexport interface DeclarativePluginConfig\n{\n resources: Record<string, SduiNode>;\n}\n\n/**\n * Configuration accepted by {@link defineEthisysPlugin}.\n *\n * `renderMode` is constrained to the protocol's {@link RenderMode} enum so\n * authoring mistakes (`\"iframe\"`, `\"webview\"`, …) are caught at compile time.\n * `mount` is generic so Contract B remote-runtime authors can attach their\n * own mount surface without losing type information at the call site.\n */\nexport interface EthisysPluginConfig<TMount>\n{\n renderMode: RenderMode;\n mount?: TMount;\n}\n\n/**\n * Author-facing identity helper for a Contract A (host-rendered) declarative\n * plugin definition.\n *\n * The helper performs no runtime work — it exists purely so authoring sites\n * receive precise type inference and editor tooling against the protocol's\n * {@link SduiNode} contract. The returned value is the exact same reference\n * the caller passed in.\n */\nexport const defineDeclarativePlugin = (\n cfg: DeclarativePluginConfig,\n): DeclarativePluginConfig => cfg;\n\n/**\n * Author-facing identity helper for a generic EthisysCore plugin definition.\n *\n * The helper performs no runtime work. It constrains `renderMode` to the\n * protocol's {@link RenderMode} enum and preserves the inferred type of an\n * optional `mount` surface, so Contract B authors can pass through their own\n * mount object without widening it to `unknown`.\n */\nexport const defineEthisysPlugin = <TMount>(\n cfg: EthisysPluginConfig<TMount>,\n): EthisysPluginConfig<TMount> => cfg;\n"]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ComponentType, ReactNode } from 'react';
|
|
2
|
+
import { PrimitiveName } from '@ethisyscore/protocol';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Props supplied by the interpreter to every registered primitive component.
|
|
6
|
+
*
|
|
7
|
+
* - `children` — already-interpreted child nodes (zero or more React elements).
|
|
8
|
+
* - `props` — opaque per-primitive properties from the SDUI tree. The host
|
|
9
|
+
* component owns interpretation. The interpreter never reads or mutates them.
|
|
10
|
+
*/
|
|
11
|
+
interface PrimitiveProps {
|
|
12
|
+
children?: ReactNode;
|
|
13
|
+
props?: Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Exhaustive primitive-name → component map.
|
|
17
|
+
*
|
|
18
|
+
* `Record<PrimitiveName, ...>` forces TypeScript to fail compilation if any of
|
|
19
|
+
* the closed v1 vocabulary entries are missing when a consumer constructs the
|
|
20
|
+
* registry. This is the mechanism that keeps host registries in lockstep with
|
|
21
|
+
* the protocol-owned primitive set.
|
|
22
|
+
*/
|
|
23
|
+
type ComponentRegistry = Record<PrimitiveName, ComponentType<PrimitiveProps>>;
|
|
24
|
+
|
|
25
|
+
export type { ComponentRegistry as C, PrimitiveProps as P };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ComponentType, ReactNode } from 'react';
|
|
2
|
+
import { PrimitiveName } from '@ethisyscore/protocol';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Props supplied by the interpreter to every registered primitive component.
|
|
6
|
+
*
|
|
7
|
+
* - `children` — already-interpreted child nodes (zero or more React elements).
|
|
8
|
+
* - `props` — opaque per-primitive properties from the SDUI tree. The host
|
|
9
|
+
* component owns interpretation. The interpreter never reads or mutates them.
|
|
10
|
+
*/
|
|
11
|
+
interface PrimitiveProps {
|
|
12
|
+
children?: ReactNode;
|
|
13
|
+
props?: Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Exhaustive primitive-name → component map.
|
|
17
|
+
*
|
|
18
|
+
* `Record<PrimitiveName, ...>` forces TypeScript to fail compilation if any of
|
|
19
|
+
* the closed v1 vocabulary entries are missing when a consumer constructs the
|
|
20
|
+
* registry. This is the mechanism that keeps host registries in lockstep with
|
|
21
|
+
* the protocol-owned primitive set.
|
|
22
|
+
*/
|
|
23
|
+
type ComponentRegistry = Record<PrimitiveName, ComponentType<PrimitiveProps>>;
|
|
24
|
+
|
|
25
|
+
export type { ComponentRegistry as C, PrimitiveProps as P };
|