@apifuse/connector-sdk 2.0.0-beta.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.
Files changed (67) hide show
  1. package/README.md +44 -0
  2. package/bin/apifuse-check.ts +408 -0
  3. package/bin/apifuse-dev.ts +222 -0
  4. package/bin/apifuse-init.ts +390 -0
  5. package/bin/apifuse-perf.ts +1101 -0
  6. package/bin/apifuse-record.ts +446 -0
  7. package/bin/apifuse-test.ts +688 -0
  8. package/bin/apifuse.ts +51 -0
  9. package/package.json +64 -0
  10. package/src/__tests__/auth.test.ts +396 -0
  11. package/src/__tests__/browser-auth.test.ts +180 -0
  12. package/src/__tests__/browser.test.ts +632 -0
  13. package/src/__tests__/connectors-yaml.test.ts +135 -0
  14. package/src/__tests__/define.test.ts +225 -0
  15. package/src/__tests__/errors.test.ts +69 -0
  16. package/src/__tests__/executor.test.ts +214 -0
  17. package/src/__tests__/http.test.ts +238 -0
  18. package/src/__tests__/insights.test.ts +210 -0
  19. package/src/__tests__/instrumentation.test.ts +290 -0
  20. package/src/__tests__/otlp.test.ts +141 -0
  21. package/src/__tests__/perf.test.ts +60 -0
  22. package/src/__tests__/proxy.test.ts +359 -0
  23. package/src/__tests__/recipes.test.ts +36 -0
  24. package/src/__tests__/serve.test.ts +233 -0
  25. package/src/__tests__/session.test.ts +231 -0
  26. package/src/__tests__/state.test.ts +100 -0
  27. package/src/__tests__/stealth.test.ts +57 -0
  28. package/src/__tests__/testing.test.ts +97 -0
  29. package/src/__tests__/tls.test.ts +345 -0
  30. package/src/__tests__/types.test.ts +142 -0
  31. package/src/__tests__/utils.test.ts +62 -0
  32. package/src/__tests__/waterfall.test.ts +270 -0
  33. package/src/config/connectors-yaml.ts +373 -0
  34. package/src/config/loader.ts +122 -0
  35. package/src/define.ts +137 -0
  36. package/src/dev.ts +38 -0
  37. package/src/errors.ts +68 -0
  38. package/src/index.test.ts +1 -0
  39. package/src/index.ts +100 -0
  40. package/src/protocol.ts +183 -0
  41. package/src/recipes/gov-api.ts +97 -0
  42. package/src/recipes/rest-api.ts +152 -0
  43. package/src/runtime/auth.ts +245 -0
  44. package/src/runtime/browser.ts +724 -0
  45. package/src/runtime/connector.ts +20 -0
  46. package/src/runtime/executor.ts +51 -0
  47. package/src/runtime/http.ts +248 -0
  48. package/src/runtime/insights.ts +456 -0
  49. package/src/runtime/instrumentation.ts +424 -0
  50. package/src/runtime/otlp.ts +171 -0
  51. package/src/runtime/perf.ts +73 -0
  52. package/src/runtime/session.ts +573 -0
  53. package/src/runtime/state.ts +124 -0
  54. package/src/runtime/tls.ts +410 -0
  55. package/src/runtime/trace.ts +261 -0
  56. package/src/runtime/waterfall.ts +245 -0
  57. package/src/serve.ts +665 -0
  58. package/src/stealth/profiles.ts +391 -0
  59. package/src/testing/helpers.ts +144 -0
  60. package/src/testing/index.ts +2 -0
  61. package/src/testing/run.ts +88 -0
  62. package/src/types/playwright-stealth.d.ts +9 -0
  63. package/src/types.ts +243 -0
  64. package/src/utils/date.ts +163 -0
  65. package/src/utils/parse.ts +66 -0
  66. package/src/utils/text.ts +20 -0
  67. package/src/utils/transform.ts +62 -0
@@ -0,0 +1,245 @@
1
+ import type { Span } from "./trace";
2
+
3
+ export type WaterfallRequest = {
4
+ method: string;
5
+ path: string;
6
+ status: number;
7
+ totalMs: number;
8
+ };
9
+
10
+ export type WaterfallOptions = {
11
+ slowThresholdMs?: number;
12
+ maxBarWidth?: number;
13
+ };
14
+
15
+ type SpanNode = {
16
+ span: Span;
17
+ children: SpanNode[];
18
+ };
19
+
20
+ const ANSI = {
21
+ green: "\x1b[32m",
22
+ red: "\x1b[31m",
23
+ yellow: "\x1b[33m",
24
+ dim: "\x1b[2m",
25
+ bold: "\x1b[1m",
26
+ reset: "\x1b[0m",
27
+ } as const;
28
+
29
+ export function renderWaterfall(
30
+ spans: Span[],
31
+ request: WaterfallRequest,
32
+ options?: WaterfallOptions,
33
+ ): string {
34
+ if (spans.length === 0) {
35
+ return "";
36
+ }
37
+
38
+ const slowThresholdMs = options?.slowThresholdMs ?? 500;
39
+ const maxBarWidth = options?.maxBarWidth ?? 40;
40
+
41
+ const tree = buildTree(spans);
42
+ const bottleneckId = findBottleneck(tree);
43
+ const totalDuration = request.totalMs;
44
+
45
+ const lines: string[] = [];
46
+
47
+ const headerRule = "─".repeat(40);
48
+ lines.push(
49
+ ` ${ANSI.dim}┌─${ANSI.reset} ${request.method} ${request.path} ${ANSI.dim}${headerRule}${ANSI.reset}`,
50
+ );
51
+ lines.push(` ${ANSI.dim}│${ANSI.reset}`);
52
+
53
+ for (const node of tree) {
54
+ const operationDuration = formatDuration(node.span.duration_ms);
55
+ const operationBar = renderBar(
56
+ node.span.duration_ms,
57
+ totalDuration,
58
+ maxBarWidth,
59
+ );
60
+ const operationColor = spanColor(node.span, slowThresholdMs);
61
+
62
+ lines.push(
63
+ ` ${ANSI.dim}│${ANSI.reset} ${operationColor}[${node.span.name}] ${operationDuration} ${operationBar}${ANSI.reset}`,
64
+ );
65
+
66
+ renderChildren(lines, node.children, 1, {
67
+ totalDuration,
68
+ maxBarWidth,
69
+ slowThresholdMs,
70
+ bottleneckId,
71
+ });
72
+ }
73
+
74
+ lines.push(` ${ANSI.dim}│${ANSI.reset}`);
75
+
76
+ const statusColor = request.status >= 400 ? ANSI.red : ANSI.green;
77
+ const totalFormatted = formatDuration(request.totalMs);
78
+ lines.push(
79
+ ` ${ANSI.dim}└─${ANSI.reset} ${statusColor}${request.status} ${statusText(request.status)}${ANSI.reset} (${totalFormatted})`,
80
+ );
81
+
82
+ return lines.join("\n");
83
+ }
84
+
85
+ function renderChildren(
86
+ lines: string[],
87
+ children: SpanNode[],
88
+ depth: number,
89
+ ctx: {
90
+ totalDuration: number;
91
+ maxBarWidth: number;
92
+ slowThresholdMs: number;
93
+ bottleneckId: string | null;
94
+ },
95
+ ): void {
96
+ const indent = " ".repeat(depth);
97
+
98
+ for (let i = 0; i < children.length; i++) {
99
+ const node = children[i];
100
+ if (!node) {
101
+ continue;
102
+ }
103
+ const isLast = i === children.length - 1;
104
+ const connector = isLast ? "└─" : "├─";
105
+ const _childPrefix = isLast ? " " : "│ ";
106
+
107
+ const duration = formatDuration(node.span.duration_ms);
108
+ const bar = renderBar(
109
+ node.span.duration_ms,
110
+ ctx.totalDuration,
111
+ ctx.maxBarWidth,
112
+ );
113
+ const color = spanColor(node.span, ctx.slowThresholdMs);
114
+ const star =
115
+ node.span.id === ctx.bottleneckId ? ` ${ANSI.yellow}★${ANSI.reset}` : "";
116
+ const barSuffix = bar ? ` ${bar}` : "";
117
+
118
+ const nameWidth = 20;
119
+ const paddedName = node.span.name.padEnd(nameWidth);
120
+
121
+ lines.push(
122
+ ` ${ANSI.dim}│${ANSI.reset} ${indent}${ANSI.dim}${connector}${ANSI.reset} ${color}${paddedName}${ANSI.reset} ${duration}${barSuffix}${star}`,
123
+ );
124
+
125
+ if (node.children.length > 0) {
126
+ renderChildren(lines, node.children, depth + 1, ctx);
127
+ }
128
+ }
129
+ }
130
+
131
+ function buildTree(spans: Span[]): SpanNode[] {
132
+ const sorted = [...spans].sort((a, b) => a.startedAt - b.startedAt);
133
+ const nodeMap = new Map<string, SpanNode>();
134
+
135
+ for (const span of sorted) {
136
+ nodeMap.set(span.id, { span, children: [] });
137
+ }
138
+
139
+ const roots: SpanNode[] = [];
140
+
141
+ for (const span of sorted) {
142
+ const node = nodeMap.get(span.id);
143
+ if (!node) {
144
+ continue;
145
+ }
146
+
147
+ if (span.parentId) {
148
+ const parent = nodeMap.get(span.parentId);
149
+ if (parent) {
150
+ parent.children.push(node);
151
+ continue;
152
+ }
153
+ }
154
+
155
+ roots.push(node);
156
+ }
157
+
158
+ return roots;
159
+ }
160
+
161
+ function findBottleneck(roots: SpanNode[]): string | null {
162
+ if (roots.length === 0) {
163
+ return null;
164
+ }
165
+
166
+ const allTopLevel: Span[] = [];
167
+
168
+ for (const root of roots) {
169
+ for (const child of root.children) {
170
+ allTopLevel.push(child.span);
171
+ }
172
+ }
173
+
174
+ if (allTopLevel.length === 0) {
175
+ return null;
176
+ }
177
+
178
+ const first = allTopLevel[0];
179
+ if (!first) {
180
+ return null;
181
+ }
182
+
183
+ let longest = first;
184
+
185
+ for (const span of allTopLevel) {
186
+ if (span.duration_ms > longest.duration_ms) {
187
+ longest = span;
188
+ }
189
+ }
190
+
191
+ return longest.id;
192
+ }
193
+
194
+ function renderBar(
195
+ durationMs: number,
196
+ totalMs: number,
197
+ maxWidth: number,
198
+ ): string {
199
+ if (totalMs <= 0 || durationMs <= 0) {
200
+ return "";
201
+ }
202
+
203
+ const ratio = durationMs / totalMs;
204
+ const width = Math.max(1, Math.round(ratio * maxWidth));
205
+
206
+ return "━".repeat(width);
207
+ }
208
+
209
+ function spanColor(span: Span, slowThresholdMs: number): string {
210
+ if (span.status === "error") {
211
+ return ANSI.red;
212
+ }
213
+
214
+ if (span.duration_ms >= slowThresholdMs) {
215
+ return ANSI.yellow;
216
+ }
217
+
218
+ return ANSI.green;
219
+ }
220
+
221
+ function formatDuration(ms: number): string {
222
+ if (ms >= 1000) {
223
+ return `${(ms / 1000).toFixed(1)}s`;
224
+ }
225
+
226
+ if (ms >= 10) {
227
+ return `${Math.round(ms)}ms`;
228
+ }
229
+
230
+ return `${ms.toFixed(1)}ms`;
231
+ }
232
+
233
+ function statusText(status: number): string {
234
+ const texts: Record<number, string> = {
235
+ 200: "OK",
236
+ 201: "Created",
237
+ 400: "Bad Request",
238
+ 401: "Unauthorized",
239
+ 404: "Not Found",
240
+ 500: "Internal Server Error",
241
+ 502: "Bad Gateway",
242
+ };
243
+
244
+ return texts[status] ?? "";
245
+ }