@arronqzy/blueprint-dsl 0.0.1
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 +31 -0
- package/readme.md +32 -0
- package/src/behaviors/default.ts +44 -0
- package/src/blueprint-signal.ts +14 -0
- package/src/clock-config.ts +232 -0
- package/src/core/behavior-registry.ts +32 -0
- package/src/core/behavior.ts +13 -0
- package/src/core/executor.ts +51 -0
- package/src/core/scheduler-loop.ts +13 -0
- package/src/fetch-config.ts +244 -0
- package/src/index.ts +29 -0
- package/src/json-config.ts +37 -0
- package/src/lifecycle.ts +45 -0
- package/src/logic-config.ts +53 -0
- package/src/node-signal.ts +83 -0
- package/src/nodes/definitions.ts +95 -0
- package/src/runtime/blueprint-cycle.ts +136 -0
- package/src/runtime/clock-active-registry.ts +30 -0
- package/src/runtime/clock-scheduler.ts +49 -0
- package/src/runtime/execution-trace.ts +55 -0
- package/src/runtime/graph-runner.ts +1326 -0
- package/src/swagger.ts +192 -0
- package/src/type.ts +34 -0
package/src/swagger.ts
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import type { FetchHttpMethod } from "./fetch-config.js";
|
|
2
|
+
|
|
3
|
+
export type SwaggerApiEndpoint = {
|
|
4
|
+
method: FetchHttpMethod;
|
|
5
|
+
path: string;
|
|
6
|
+
summary?: string;
|
|
7
|
+
operationId?: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type ParsedSwaggerDocument = {
|
|
11
|
+
apiBaseUrl: string;
|
|
12
|
+
endpoints: SwaggerApiEndpoint[];
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const HTTP_METHODS = new Set([
|
|
16
|
+
"get",
|
|
17
|
+
"post",
|
|
18
|
+
"put",
|
|
19
|
+
"patch",
|
|
20
|
+
"delete",
|
|
21
|
+
"head",
|
|
22
|
+
"options",
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
function normalizeBaseUrl(url: string): string {
|
|
26
|
+
return url.replace(/\/+$/, "");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function joinBaseAndPath(base: string, path: string): string {
|
|
30
|
+
const normalizedBase = normalizeBaseUrl(base);
|
|
31
|
+
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
32
|
+
return `${normalizedBase}${normalizedPath}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function resolveAgainstDocsUrl(relativeOrAbsolute: string, docsUrl: string): string {
|
|
36
|
+
const trimmed = relativeOrAbsolute.trim();
|
|
37
|
+
if (!trimmed) return "";
|
|
38
|
+
if (/^https?:\/\//i.test(trimmed)) {
|
|
39
|
+
return normalizeBaseUrl(trimmed);
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
const docs = new URL(docsUrl);
|
|
43
|
+
if (trimmed.startsWith("/")) {
|
|
44
|
+
return normalizeBaseUrl(`${docs.origin}${trimmed}`);
|
|
45
|
+
}
|
|
46
|
+
return normalizeBaseUrl(new URL(trimmed, `${docs.origin}/`).href);
|
|
47
|
+
} catch {
|
|
48
|
+
return normalizeBaseUrl(trimmed);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function extractEndpointsFromPaths(
|
|
53
|
+
paths: unknown
|
|
54
|
+
): SwaggerApiEndpoint[] {
|
|
55
|
+
if (!paths || typeof paths !== "object") return [];
|
|
56
|
+
|
|
57
|
+
const endpoints: SwaggerApiEndpoint[] = [];
|
|
58
|
+
|
|
59
|
+
for (const [path, item] of Object.entries(paths as Record<string, unknown>)) {
|
|
60
|
+
if (!item || typeof item !== "object") continue;
|
|
61
|
+
|
|
62
|
+
for (const [method, operation] of Object.entries(item as Record<string, unknown>)) {
|
|
63
|
+
if (!HTTP_METHODS.has(method.toLowerCase())) continue;
|
|
64
|
+
if (!operation || typeof operation !== "object") continue;
|
|
65
|
+
|
|
66
|
+
const op = operation as Record<string, unknown>;
|
|
67
|
+
endpoints.push({
|
|
68
|
+
method: method.toUpperCase() as FetchHttpMethod,
|
|
69
|
+
path,
|
|
70
|
+
summary: typeof op.summary === "string" ? op.summary : undefined,
|
|
71
|
+
operationId:
|
|
72
|
+
typeof op.operationId === "string" ? op.operationId : undefined,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return endpoints.sort((a, b) =>
|
|
78
|
+
`${a.path}:${a.method}`.localeCompare(`${b.path}:${b.method}`)
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function parseOpenApiV3(
|
|
83
|
+
spec: Record<string, unknown>,
|
|
84
|
+
docsUrl: string
|
|
85
|
+
): ParsedSwaggerDocument {
|
|
86
|
+
const servers = Array.isArray(spec.servers) ? spec.servers : [];
|
|
87
|
+
const firstServer =
|
|
88
|
+
servers.find((item) => item && typeof item === "object") as
|
|
89
|
+
| Record<string, unknown>
|
|
90
|
+
| undefined;
|
|
91
|
+
const serverUrl =
|
|
92
|
+
typeof firstServer?.url === "string" ? firstServer.url : "";
|
|
93
|
+
const apiBaseUrl = serverUrl
|
|
94
|
+
? resolveAgainstDocsUrl(serverUrl, docsUrl)
|
|
95
|
+
: resolveAgainstDocsUrl(new URL(docsUrl).origin, docsUrl);
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
apiBaseUrl,
|
|
99
|
+
endpoints: extractEndpointsFromPaths(spec.paths),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function parseSwaggerV2(
|
|
104
|
+
spec: Record<string, unknown>,
|
|
105
|
+
docsUrl: string
|
|
106
|
+
): ParsedSwaggerDocument {
|
|
107
|
+
const schemes = Array.isArray(spec.schemes) ? spec.schemes : ["https"];
|
|
108
|
+
const scheme =
|
|
109
|
+
typeof schemes[0] === "string" ? schemes[0] : "https";
|
|
110
|
+
const host = typeof spec.host === "string" ? spec.host : new URL(docsUrl).host;
|
|
111
|
+
const basePath =
|
|
112
|
+
typeof spec.basePath === "string" ? spec.basePath : "";
|
|
113
|
+
const apiBaseUrl = normalizeBaseUrl(`${scheme}://${host}${basePath}`);
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
apiBaseUrl,
|
|
117
|
+
endpoints: extractEndpointsFromPaths(spec.paths),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function parseSwaggerDocument(
|
|
122
|
+
spec: unknown,
|
|
123
|
+
docsUrl: string
|
|
124
|
+
): ParsedSwaggerDocument {
|
|
125
|
+
if (!spec || typeof spec !== "object") {
|
|
126
|
+
throw new Error("Swagger 文档格式无效");
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const document = spec as Record<string, unknown>;
|
|
130
|
+
|
|
131
|
+
if (typeof document.openapi === "string") {
|
|
132
|
+
const parsed = parseOpenApiV3(document, docsUrl);
|
|
133
|
+
if (parsed.endpoints.length === 0) {
|
|
134
|
+
throw new Error("未在 OpenAPI 文档中找到接口");
|
|
135
|
+
}
|
|
136
|
+
return parsed;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (document.swagger === "2.0" || document.swagger === 2) {
|
|
140
|
+
const parsed = parseSwaggerV2(document, docsUrl);
|
|
141
|
+
if (parsed.endpoints.length === 0) {
|
|
142
|
+
throw new Error("未在 Swagger 2.0 文档中找到接口");
|
|
143
|
+
}
|
|
144
|
+
return parsed;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
throw new Error("仅支持 OpenAPI 3.x 与 Swagger 2.0 文档");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function buildEndpointFullUrl(
|
|
151
|
+
apiBaseUrl: string,
|
|
152
|
+
pathOrEndpoint: string | Pick<SwaggerApiEndpoint, "path">
|
|
153
|
+
): string {
|
|
154
|
+
const path =
|
|
155
|
+
typeof pathOrEndpoint === "string" ? pathOrEndpoint : pathOrEndpoint.path;
|
|
156
|
+
return joinBaseAndPath(apiBaseUrl, path);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function buildEndpointSuggestions(
|
|
160
|
+
apiBaseUrl: string,
|
|
161
|
+
endpoints: SwaggerApiEndpoint[]
|
|
162
|
+
) {
|
|
163
|
+
return endpoints.map((endpoint) => {
|
|
164
|
+
const fullUrl = buildEndpointFullUrl(apiBaseUrl, endpoint);
|
|
165
|
+
const labelParts = [
|
|
166
|
+
endpoint.method,
|
|
167
|
+
endpoint.path,
|
|
168
|
+
endpoint.summary ? `· ${endpoint.summary}` : "",
|
|
169
|
+
].filter(Boolean);
|
|
170
|
+
return {
|
|
171
|
+
...endpoint,
|
|
172
|
+
fullUrl,
|
|
173
|
+
label: labelParts.join(" "),
|
|
174
|
+
};
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function filterEndpointSuggestions(
|
|
179
|
+
suggestions: ReturnType<typeof buildEndpointSuggestions>,
|
|
180
|
+
query: string
|
|
181
|
+
) {
|
|
182
|
+
const keyword = query.trim().toLowerCase();
|
|
183
|
+
if (!keyword) return suggestions;
|
|
184
|
+
return suggestions.filter(
|
|
185
|
+
(item) =>
|
|
186
|
+
item.fullUrl.toLowerCase().includes(keyword) ||
|
|
187
|
+
item.path.toLowerCase().includes(keyword) ||
|
|
188
|
+
item.method.toLowerCase().includes(keyword) ||
|
|
189
|
+
item.summary?.toLowerCase().includes(keyword) ||
|
|
190
|
+
item.operationId?.toLowerCase().includes(keyword)
|
|
191
|
+
);
|
|
192
|
+
}
|
package/src/type.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// types.ts
|
|
2
|
+
export type Value = any
|
|
3
|
+
|
|
4
|
+
export type PortSpec = {
|
|
5
|
+
name: string
|
|
6
|
+
kind: "data" | "flow"
|
|
7
|
+
optional?: boolean
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type NodeDefinition = {
|
|
11
|
+
type: string
|
|
12
|
+
inputs: PortSpec[]
|
|
13
|
+
outputs: PortSpec[]
|
|
14
|
+
|
|
15
|
+
behavior: {
|
|
16
|
+
kind: "js" | "blueprint"
|
|
17
|
+
ref: string
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type ExecutionToken = {
|
|
22
|
+
tokenId: string
|
|
23
|
+
nodeId: string
|
|
24
|
+
nodeType: string
|
|
25
|
+
inPort: string
|
|
26
|
+
|
|
27
|
+
scope: Scope
|
|
28
|
+
correlationId?: string
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type Scope = {
|
|
32
|
+
parent?: Scope
|
|
33
|
+
vars: Map<string, Value>
|
|
34
|
+
}
|