@arronqzy/blueprint-dsl 1.0.8 → 1.0.9

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.
@@ -1,114 +0,0 @@
1
- export type ScopeAutocompleteState = {
2
- templateStart: number;
3
- cursor: number;
4
- path: string[];
5
- partialKey: string;
6
- };
7
-
8
- function getValueAtPath(scope: unknown, path: string[]): unknown {
9
- let current: unknown = scope;
10
- for (const part of path) {
11
- if (current === null || typeof current !== "object") return undefined;
12
- current = (current as Record<string, unknown>)[part];
13
- }
14
- return current;
15
- }
16
-
17
- function listObjectKeys(value: unknown): string[] {
18
- if (value === null || typeof value !== "object" || Array.isArray(value)) {
19
- return [];
20
- }
21
- return Object.keys(value as Record<string, unknown>);
22
- }
23
-
24
- /** 解析光标处未闭合的 scope 模版表达式 */
25
- export function parseScopeAutocomplete(
26
- text: string,
27
- cursor: number,
28
- scope: unknown
29
- ): ScopeAutocompleteState | null {
30
- const before = text.slice(0, cursor);
31
- const templateStart = before.lastIndexOf("{");
32
- if (templateStart < 0) return null;
33
-
34
- const inner = before.slice(templateStart + 1);
35
- if (inner.includes("}")) return null;
36
- if (!inner.startsWith("scope")) return null;
37
-
38
- let body = inner.slice("scope".length);
39
- if (body === "" || body === "?") {
40
- return { templateStart, cursor, path: [], partialKey: "" };
41
- }
42
- if (!body.startsWith("?.")) return null;
43
-
44
- body = body.slice(2);
45
- if (body === "") {
46
- return { templateStart, cursor, path: [], partialKey: "" };
47
- }
48
-
49
- const segments = body.split("?.");
50
- const endsWithSeparator = inner.endsWith("?.");
51
-
52
- if (endsWithSeparator) {
53
- const path = segments.filter((segment) => segment.length > 0);
54
- return { templateStart, cursor, path, partialKey: "" };
55
- }
56
-
57
- const lastSegment = segments[segments.length - 1] ?? "";
58
- const prefixSegments = segments.slice(0, -1).filter((segment) => segment.length > 0);
59
- const parent = getValueAtPath(scope, prefixSegments);
60
- const keys = listObjectKeys(parent);
61
-
62
- if (lastSegment.length === 0) {
63
- return { templateStart, cursor, path: prefixSegments, partialKey: "" };
64
- }
65
-
66
- if (keys.includes(lastSegment)) {
67
- return {
68
- templateStart,
69
- cursor,
70
- path: [...prefixSegments, lastSegment],
71
- partialKey: "",
72
- };
73
- }
74
-
75
- return {
76
- templateStart,
77
- cursor,
78
- path: prefixSegments,
79
- partialKey: lastSegment,
80
- };
81
- }
82
-
83
- export function getScopeAutocompleteSuggestions(
84
- scope: unknown,
85
- state: ScopeAutocompleteState
86
- ): string[] {
87
- const parent =
88
- state.partialKey.length > 0
89
- ? getValueAtPath(scope, state.path)
90
- : state.path.length > 0
91
- ? getValueAtPath(scope, state.path)
92
- : scope;
93
-
94
- const keys = listObjectKeys(parent);
95
- if (state.partialKey.length === 0) return keys;
96
- return keys.filter((key) => key.startsWith(state.partialKey));
97
- }
98
-
99
- export function buildScopeExpression(path: string[], selectedKey: string): string {
100
- return `scope?.${[...path, selectedKey].join("?.")}`;
101
- }
102
-
103
- export function applyScopeAutocompleteSelection(
104
- text: string,
105
- state: ScopeAutocompleteState,
106
- selectedKey: string
107
- ): { value: string; cursor: number } {
108
- const expr = buildScopeExpression(state.path, selectedKey);
109
- const before = text.slice(0, state.templateStart + 1);
110
- const after = text.slice(state.cursor);
111
- const value = `${before}${expr}${after}`;
112
- const cursor = before.length + expr.length;
113
- return { value, cursor };
114
- }
@@ -1,93 +0,0 @@
1
- /** 仅匹配 `{scope...}`,避免把 JSON 对象 `{ "a": 1 }` 误当成模板 */
2
- const SCOPE_TOKEN_RE = /\{(scope[^}]*)\}/g;
3
-
4
- export function hasScopeTemplate(value: string): boolean {
5
- SCOPE_TOKEN_RE.lastIndex = 0;
6
- return SCOPE_TOKEN_RE.test(value);
7
- }
8
-
9
- export function evaluateScopeExpression(
10
- expression: string,
11
- scope: unknown
12
- ): unknown {
13
- const trimmed = expression.trim();
14
- if (!trimmed) return undefined;
15
- try {
16
- const fn = new Function("scope", `"use strict"; return (${trimmed});`);
17
- return fn(scope);
18
- } catch {
19
- return undefined;
20
- }
21
- }
22
-
23
- export function stringifyScopeValue(value: unknown): string {
24
- if (value === null || value === undefined) return "";
25
- if (typeof value === "string") return value;
26
- if (typeof value === "number" || typeof value === "boolean") {
27
- return String(value);
28
- }
29
- try {
30
- return JSON.stringify(value);
31
- } catch {
32
- return String(value);
33
- }
34
- }
35
-
36
- function isInsideJsonString(before: string): boolean {
37
- let inString = false;
38
- let escape = false;
39
- for (const ch of before) {
40
- if (inString) {
41
- if (escape) {
42
- escape = false;
43
- continue;
44
- }
45
- if (ch === "\\") {
46
- escape = true;
47
- continue;
48
- }
49
- if (ch === '"') inString = false;
50
- continue;
51
- }
52
- if (ch === '"') inString = true;
53
- }
54
- return inString;
55
- }
56
-
57
- function replaceScopeTokens(
58
- template: string,
59
- scope: unknown,
60
- replacer: (value: unknown, offset: number) => string
61
- ): string {
62
- SCOPE_TOKEN_RE.lastIndex = 0;
63
- return template.replace(SCOPE_TOKEN_RE, (_match, rawExpr: string, offset: number) =>
64
- replacer(evaluateScopeExpression(rawExpr, scope), offset)
65
- );
66
- }
67
-
68
- /** 将 `{scope?.a}` 替换为求值结果;无模板时原样返回 */
69
- export function evaluateScopeTemplate(template: string, scope: unknown): string {
70
- if (!hasScopeTemplate(template)) return template;
71
- return replaceScopeTokens(template, scope, (value) => stringifyScopeValue(value));
72
- }
73
-
74
- /**
75
- * 在 JSON 文本中替换 `{scope?...}`:
76
- * - 位于字符串内时写入转义后的内容
77
- * - 位于值位置时写入 JSON 字面量(对象/数组/字符串/数字)
78
- */
79
- export function evaluateScopeTemplateInJson(template: string, scope: unknown): string {
80
- if (!hasScopeTemplate(template)) return template;
81
- return replaceScopeTokens(template, scope, (value, offset) => {
82
- if (isInsideJsonString(template.slice(0, offset))) {
83
- return JSON.stringify(stringifyScopeValue(value)).slice(1, -1);
84
- }
85
- if (value === undefined) return "null";
86
- return JSON.stringify(value);
87
- });
88
- }
89
-
90
- export function looksLikeJsonText(text: string): boolean {
91
- const trimmed = text.trim();
92
- return trimmed.startsWith("{") || trimmed.startsWith("[");
93
- }
package/src/swagger.ts DELETED
@@ -1,192 +0,0 @@
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 DELETED
@@ -1,34 +0,0 @@
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
- }