@donghanh/core 0.1.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/package.json +20 -0
- package/src/execute.ts +30 -0
- package/src/index.ts +34 -0
- package/src/jsx-dev-runtime.ts +12 -0
- package/src/jsx-runtime.ts +24 -0
- package/src/jsx.d.ts +9 -0
- package/src/primitives.test.ts +139 -0
- package/src/primitives.ts +129 -0
- package/src/registry.ts +63 -0
- package/src/render.ts +39 -0
- package/src/types.ts +40 -0
- package/tsconfig.json +15 -0
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@donghanh/core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Custom JSX runtime for conversational UI — primitives, registry, executor",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.ts",
|
|
9
|
+
"./jsx-runtime": "./src/jsx-runtime.ts",
|
|
10
|
+
"./jsx-dev-runtime": "./src/jsx-dev-runtime.ts"
|
|
11
|
+
},
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "https://github.com/rezzahub/donghanh",
|
|
15
|
+
"directory": "packages/core"
|
|
16
|
+
},
|
|
17
|
+
"peerDependencies": {
|
|
18
|
+
"typescript": "^5.0.0"
|
|
19
|
+
}
|
|
20
|
+
}
|
package/src/execute.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ChatNode } from "./primitives";
|
|
2
|
+
import type { Registry } from "./registry";
|
|
3
|
+
import type { Executor, ExecutorContext } from "./types";
|
|
4
|
+
|
|
5
|
+
export interface ExecuteResult {
|
|
6
|
+
data: unknown;
|
|
7
|
+
brief: ChatNode;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function executeOperation(opts: {
|
|
11
|
+
registry: Registry;
|
|
12
|
+
operationId: string;
|
|
13
|
+
variables: Record<string, unknown>;
|
|
14
|
+
executor: Executor;
|
|
15
|
+
context: ExecutorContext;
|
|
16
|
+
}): Promise<ExecuteResult> {
|
|
17
|
+
const { registry, operationId, variables, executor, context } = opts;
|
|
18
|
+
|
|
19
|
+
const operation = registry.get(operationId);
|
|
20
|
+
if (!operation) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
`Unknown operation "${operationId}". Call GET /api/gpt/operations to discover available operations.`,
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const data = await executor(operationId, variables, context);
|
|
27
|
+
const brief = operation({ data, variables });
|
|
28
|
+
|
|
29
|
+
return { data, brief };
|
|
30
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export type { ExecuteResult } from "./execute";
|
|
2
|
+
export { executeOperation } from "./execute";
|
|
3
|
+
export type {
|
|
4
|
+
ActionNode,
|
|
5
|
+
AppNode,
|
|
6
|
+
BriefNode,
|
|
7
|
+
ChatNode,
|
|
8
|
+
ContextNode,
|
|
9
|
+
DisplayNode,
|
|
10
|
+
FieldDef,
|
|
11
|
+
FormNode,
|
|
12
|
+
MessageNode,
|
|
13
|
+
} from "./primitives";
|
|
14
|
+
export {
|
|
15
|
+
Action,
|
|
16
|
+
App,
|
|
17
|
+
Brief,
|
|
18
|
+
Context,
|
|
19
|
+
Display,
|
|
20
|
+
Form,
|
|
21
|
+
Message,
|
|
22
|
+
} from "./primitives";
|
|
23
|
+
export type { CompactOperation, OperationDetail, Registry } from "./registry";
|
|
24
|
+
export { buildRegistry } from "./registry";
|
|
25
|
+
export type { Renderer } from "./render";
|
|
26
|
+
export { renderNode } from "./render";
|
|
27
|
+
export type {
|
|
28
|
+
Executor,
|
|
29
|
+
ExecutorContext,
|
|
30
|
+
OperationComponent,
|
|
31
|
+
OperationConfig,
|
|
32
|
+
OperationProps,
|
|
33
|
+
} from "./types";
|
|
34
|
+
export { registerOperation } from "./types";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ChatNode } from "./primitives";
|
|
2
|
+
|
|
3
|
+
type ComponentFn = (props: Record<string, unknown>) => ChatNode;
|
|
4
|
+
|
|
5
|
+
export function jsxDEV(
|
|
6
|
+
type: ComponentFn,
|
|
7
|
+
props: Record<string, unknown>,
|
|
8
|
+
): ChatNode {
|
|
9
|
+
return type(props);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export { Fragment } from "./jsx-runtime";
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ChatNode } from "./primitives";
|
|
2
|
+
|
|
3
|
+
type ComponentFn = (props: Record<string, unknown>) => ChatNode;
|
|
4
|
+
|
|
5
|
+
export function jsx(
|
|
6
|
+
type: ComponentFn,
|
|
7
|
+
props: Record<string, unknown>,
|
|
8
|
+
): ChatNode {
|
|
9
|
+
return type(props);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function jsxs(
|
|
13
|
+
type: ComponentFn,
|
|
14
|
+
props: Record<string, unknown>,
|
|
15
|
+
): ChatNode {
|
|
16
|
+
return type(props);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function Fragment(props: {
|
|
20
|
+
children?: ChatNode | ChatNode[];
|
|
21
|
+
}): ChatNode[] {
|
|
22
|
+
if (!props.children) return [];
|
|
23
|
+
return Array.isArray(props.children) ? props.children : [props.children];
|
|
24
|
+
}
|
package/src/jsx.d.ts
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import type { ChatNode, Renderer } from "./index";
|
|
3
|
+
import { Fragment, jsx, jsxs } from "./jsx-runtime";
|
|
4
|
+
// Test the JSX runtime + primitives by simulating what the transpiler produces
|
|
5
|
+
import { Action, Brief, Display } from "./primitives";
|
|
6
|
+
import { renderNode } from "./render";
|
|
7
|
+
|
|
8
|
+
// Simple renderer that collects node types for verification
|
|
9
|
+
const collectTypes: Renderer<string[]> = {
|
|
10
|
+
brief(_node, children) {
|
|
11
|
+
return children.flat();
|
|
12
|
+
},
|
|
13
|
+
message(node) {
|
|
14
|
+
return [`message:${node.content}`];
|
|
15
|
+
},
|
|
16
|
+
action(node) {
|
|
17
|
+
return [`action:${node.operation}`];
|
|
18
|
+
},
|
|
19
|
+
form() {
|
|
20
|
+
return ["form"];
|
|
21
|
+
},
|
|
22
|
+
display() {
|
|
23
|
+
return ["display"];
|
|
24
|
+
},
|
|
25
|
+
context() {
|
|
26
|
+
return ["context"];
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
describe("Brief with nested fragments from map", () => {
|
|
31
|
+
test("map returning fragments produces valid children", () => {
|
|
32
|
+
// Simulates what JSX transpiler produces for:
|
|
33
|
+
// <Brief>
|
|
34
|
+
// Some text
|
|
35
|
+
// {items.map(item => (
|
|
36
|
+
// <>
|
|
37
|
+
// <Display data={item.data} />
|
|
38
|
+
// <Action operation={item.op} label={item.label} />
|
|
39
|
+
// </>
|
|
40
|
+
// ))}
|
|
41
|
+
// </Brief>
|
|
42
|
+
|
|
43
|
+
const items = [
|
|
44
|
+
{ data: "balance1", op: "settle-1", label: "Settle 1" },
|
|
45
|
+
{ data: "balance2", op: "settle-2", label: "Settle 2" },
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
// Fragment produces ChatNode[]
|
|
49
|
+
const fragments = items.map((item) =>
|
|
50
|
+
jsx(Fragment as any, {
|
|
51
|
+
children: [
|
|
52
|
+
jsx(Display as any, { data: item.data }),
|
|
53
|
+
jsx(Action as any, { operation: item.op, label: item.label }),
|
|
54
|
+
],
|
|
55
|
+
}),
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
// Brief receives [string, ...ChatNode[][]] as children
|
|
59
|
+
const brief = jsxs(Brief as any, {
|
|
60
|
+
children: ["Some text", ...fragments],
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// This should not throw — all children should be valid ChatNode objects
|
|
64
|
+
expect(brief.type).toBe("brief");
|
|
65
|
+
|
|
66
|
+
const types = renderNode(brief as ChatNode, collectTypes);
|
|
67
|
+
expect(types).toContain("message:Some text");
|
|
68
|
+
expect(types).toContain("display");
|
|
69
|
+
expect(types).toContain("action:settle-1");
|
|
70
|
+
expect(types).toContain("action:settle-2");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("deeply nested arrays from map+filter+map are flattened", () => {
|
|
74
|
+
// Simulates:
|
|
75
|
+
// <Brief>
|
|
76
|
+
// {groups.map(group => (
|
|
77
|
+
// <>
|
|
78
|
+
// <Display data={group.display} />
|
|
79
|
+
// {group.debts.filter(...).map(debt => (
|
|
80
|
+
// <Action operation="settle" label={debt.label} />
|
|
81
|
+
// ))}
|
|
82
|
+
// </>
|
|
83
|
+
// ))}
|
|
84
|
+
// </Brief>
|
|
85
|
+
|
|
86
|
+
const groups = [
|
|
87
|
+
{
|
|
88
|
+
display: "Group 1 balances",
|
|
89
|
+
debts: [{ label: "Debt A" }, { label: "Debt B" }],
|
|
90
|
+
},
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
const fragments = groups.map((group) => {
|
|
94
|
+
const debtActions = group.debts.map((debt) =>
|
|
95
|
+
jsx(Action as any, { operation: "settle", label: debt.label }),
|
|
96
|
+
);
|
|
97
|
+
return jsx(Fragment as any, {
|
|
98
|
+
children: [
|
|
99
|
+
jsx(Display as any, { data: group.display }),
|
|
100
|
+
...debtActions,
|
|
101
|
+
],
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const brief = jsxs(Brief as any, {
|
|
106
|
+
children: [...fragments],
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
expect(brief.type).toBe("brief");
|
|
110
|
+
|
|
111
|
+
const types = renderNode(brief as ChatNode, collectTypes);
|
|
112
|
+
expect(types).toContain("display");
|
|
113
|
+
expect(types).toContain("action:settle");
|
|
114
|
+
expect(types).toHaveLength(3); // 1 display + 2 actions
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("null and undefined children from conditional rendering are filtered", () => {
|
|
118
|
+
// Simulates:
|
|
119
|
+
// <Brief>
|
|
120
|
+
// {condition && <Display data="x" />}
|
|
121
|
+
// <Action operation="a" label="A" />
|
|
122
|
+
// </Brief>
|
|
123
|
+
|
|
124
|
+
const brief = jsxs(Brief as any, {
|
|
125
|
+
children: [
|
|
126
|
+
false, // condition && <Display />
|
|
127
|
+
null,
|
|
128
|
+
undefined,
|
|
129
|
+
jsx(Action as any, { operation: "a", label: "A" }),
|
|
130
|
+
],
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
expect(brief.type).toBe("brief");
|
|
134
|
+
|
|
135
|
+
// Should not throw on null/undefined/false children
|
|
136
|
+
const types = renderNode(brief as ChatNode, collectTypes);
|
|
137
|
+
expect(types).toContain("action:a");
|
|
138
|
+
});
|
|
139
|
+
});
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// --- Node types ---
|
|
2
|
+
|
|
3
|
+
export interface BriefNode {
|
|
4
|
+
type: "brief";
|
|
5
|
+
children: ChatNode[];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface MessageNode {
|
|
9
|
+
type: "message";
|
|
10
|
+
content: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ActionNode {
|
|
14
|
+
type: "action";
|
|
15
|
+
operation: string;
|
|
16
|
+
label: string;
|
|
17
|
+
variables?: Record<string, unknown>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface FormNode {
|
|
21
|
+
type: "form";
|
|
22
|
+
fields: FieldDef[];
|
|
23
|
+
operation: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface DisplayNode {
|
|
27
|
+
type: "display";
|
|
28
|
+
data: unknown;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface ContextNode {
|
|
32
|
+
type: "context";
|
|
33
|
+
value: unknown;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface AppNode {
|
|
37
|
+
type: "app";
|
|
38
|
+
operations: OperationRef[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface FieldDef {
|
|
42
|
+
name: string;
|
|
43
|
+
type: string;
|
|
44
|
+
label?: string;
|
|
45
|
+
required?: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface OperationRef {
|
|
49
|
+
component: OperationComponentAny;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type ChatNode =
|
|
53
|
+
| BriefNode
|
|
54
|
+
| MessageNode
|
|
55
|
+
| ActionNode
|
|
56
|
+
| FormNode
|
|
57
|
+
| DisplayNode
|
|
58
|
+
| ContextNode;
|
|
59
|
+
|
|
60
|
+
// Minimal type for OperationComponent used in primitives (avoids circular dep)
|
|
61
|
+
interface OperationComponentAny {
|
|
62
|
+
operationConfig: { id: string };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// --- Helpers ---
|
|
66
|
+
|
|
67
|
+
function flatChildren(children: Child | Child[]): (ChatNode | string)[] {
|
|
68
|
+
if (children == null || typeof children === "boolean") return [];
|
|
69
|
+
if (Array.isArray(children))
|
|
70
|
+
return (children.flat(Infinity) as unknown[]).filter(
|
|
71
|
+
(c): c is ChatNode | string => c != null && typeof c !== "boolean",
|
|
72
|
+
);
|
|
73
|
+
return [children];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function toNode(child: ChatNode | string): ChatNode {
|
|
77
|
+
if (typeof child === "string") return { type: "message", content: child };
|
|
78
|
+
return child;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// --- Component functions ---
|
|
82
|
+
|
|
83
|
+
type Child = ChatNode | ChatNode[] | string | boolean | undefined | null;
|
|
84
|
+
|
|
85
|
+
export function Brief(props: { children?: Child | Child[] }): BriefNode {
|
|
86
|
+
const flat = flatChildren(props.children);
|
|
87
|
+
return { type: "brief", children: flat.map(toNode) };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function Message(props: { children?: string }): MessageNode {
|
|
91
|
+
return { type: "message", content: props.children ?? "" };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function Action(props: {
|
|
95
|
+
operation: string;
|
|
96
|
+
label: string;
|
|
97
|
+
variables?: Record<string, unknown>;
|
|
98
|
+
}): ActionNode {
|
|
99
|
+
return {
|
|
100
|
+
type: "action",
|
|
101
|
+
operation: props.operation,
|
|
102
|
+
label: props.label,
|
|
103
|
+
variables: props.variables,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function Form(props: {
|
|
108
|
+
fields: FieldDef[];
|
|
109
|
+
operation: string;
|
|
110
|
+
}): FormNode {
|
|
111
|
+
return { type: "form", fields: props.fields, operation: props.operation };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function Display(props: { data: unknown }): DisplayNode {
|
|
115
|
+
return { type: "display", data: props.data };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function Context(props: { value: unknown }): ContextNode {
|
|
119
|
+
return { type: "context", value: props.value };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function App(props: {
|
|
123
|
+
operations: Record<string, OperationComponentAny>;
|
|
124
|
+
}): AppNode {
|
|
125
|
+
const ops = Object.values(props.operations).map((component) => ({
|
|
126
|
+
component,
|
|
127
|
+
}));
|
|
128
|
+
return { type: "app", operations: ops };
|
|
129
|
+
}
|
package/src/registry.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { AppNode } from "./primitives";
|
|
2
|
+
import type { OperationComponent } from "./types";
|
|
3
|
+
|
|
4
|
+
export interface CompactOperation {
|
|
5
|
+
id: string;
|
|
6
|
+
type: "query" | "mutation";
|
|
7
|
+
description: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface OperationDetail {
|
|
11
|
+
id: string;
|
|
12
|
+
type: "query" | "mutation";
|
|
13
|
+
description: string;
|
|
14
|
+
instruction: string;
|
|
15
|
+
input: object;
|
|
16
|
+
response?: object;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface Registry {
|
|
20
|
+
list(): CompactOperation[];
|
|
21
|
+
detail(name: string): OperationDetail | null;
|
|
22
|
+
get(name: string): OperationComponent | undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function buildRegistry(
|
|
26
|
+
appNode: AppNode,
|
|
27
|
+
responseSchemas?: Record<string, object>,
|
|
28
|
+
): Registry {
|
|
29
|
+
const map = new Map<string, OperationComponent>();
|
|
30
|
+
|
|
31
|
+
for (const ref of appNode.operations) {
|
|
32
|
+
const op = ref.component as OperationComponent;
|
|
33
|
+
map.set(op.operationConfig.id, op);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
list(): CompactOperation[] {
|
|
38
|
+
return Array.from(map.values()).map((op) => ({
|
|
39
|
+
id: op.operationConfig.id,
|
|
40
|
+
type: op.operationConfig.type,
|
|
41
|
+
description: op.operationConfig.description,
|
|
42
|
+
}));
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
detail(name: string): OperationDetail | null {
|
|
46
|
+
const op = map.get(name);
|
|
47
|
+
if (!op) return null;
|
|
48
|
+
const config = op.operationConfig;
|
|
49
|
+
return {
|
|
50
|
+
id: config.id,
|
|
51
|
+
type: config.type,
|
|
52
|
+
description: config.description,
|
|
53
|
+
instruction: config.instruction,
|
|
54
|
+
input: config.input,
|
|
55
|
+
response: responseSchemas?.[name],
|
|
56
|
+
};
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
get(name: string): OperationComponent | undefined {
|
|
60
|
+
return map.get(name);
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
package/src/render.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ActionNode,
|
|
3
|
+
BriefNode,
|
|
4
|
+
ChatNode,
|
|
5
|
+
ContextNode,
|
|
6
|
+
DisplayNode,
|
|
7
|
+
FormNode,
|
|
8
|
+
MessageNode,
|
|
9
|
+
} from "./primitives";
|
|
10
|
+
|
|
11
|
+
export interface Renderer<T> {
|
|
12
|
+
brief(node: BriefNode, children: T[]): T;
|
|
13
|
+
message(node: MessageNode): T;
|
|
14
|
+
action(node: ActionNode): T;
|
|
15
|
+
form(node: FormNode): T;
|
|
16
|
+
display(node: DisplayNode): T;
|
|
17
|
+
context(node: ContextNode): T;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function renderNode<T>(node: ChatNode, renderer: Renderer<T>): T {
|
|
21
|
+
switch (node.type) {
|
|
22
|
+
case "brief": {
|
|
23
|
+
const children = node.children.map((child) =>
|
|
24
|
+
renderNode(child, renderer),
|
|
25
|
+
);
|
|
26
|
+
return renderer.brief(node, children);
|
|
27
|
+
}
|
|
28
|
+
case "message":
|
|
29
|
+
return renderer.message(node);
|
|
30
|
+
case "action":
|
|
31
|
+
return renderer.action(node);
|
|
32
|
+
case "form":
|
|
33
|
+
return renderer.form(node);
|
|
34
|
+
case "display":
|
|
35
|
+
return renderer.display(node);
|
|
36
|
+
case "context":
|
|
37
|
+
return renderer.context(node);
|
|
38
|
+
}
|
|
39
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { ChatNode } from "./primitives";
|
|
2
|
+
|
|
3
|
+
export interface OperationProps<T = any> {
|
|
4
|
+
data: T;
|
|
5
|
+
variables: Record<string, unknown>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface OperationConfig {
|
|
9
|
+
id: string;
|
|
10
|
+
type: "query" | "mutation";
|
|
11
|
+
description: string;
|
|
12
|
+
instruction: string;
|
|
13
|
+
input: object;
|
|
14
|
+
responseKey: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface OperationComponent<T = any> {
|
|
18
|
+
(props: OperationProps<T>): ChatNode;
|
|
19
|
+
operationConfig: OperationConfig;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function registerOperation<T = any>(
|
|
23
|
+
component: (props: OperationProps<T>) => ChatNode,
|
|
24
|
+
config: OperationConfig,
|
|
25
|
+
): OperationComponent<T> {
|
|
26
|
+
const op = component as OperationComponent<T>;
|
|
27
|
+
op.operationConfig = config;
|
|
28
|
+
return op;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type Executor = (
|
|
32
|
+
operationId: string,
|
|
33
|
+
variables: Record<string, unknown>,
|
|
34
|
+
context: ExecutorContext,
|
|
35
|
+
) => Promise<unknown>;
|
|
36
|
+
|
|
37
|
+
export interface ExecutorContext {
|
|
38
|
+
userId: string;
|
|
39
|
+
request: Request;
|
|
40
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2017",
|
|
4
|
+
"module": "esnext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"noEmit": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"isolatedModules": true,
|
|
10
|
+
"jsx": "react-jsx",
|
|
11
|
+
"jsxImportSource": "@donghanh/core",
|
|
12
|
+
"skipLibCheck": true
|
|
13
|
+
},
|
|
14
|
+
"include": ["src"]
|
|
15
|
+
}
|