@yiaany/ghostapi 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/CHANGELOG.md +29 -0
- package/CONTRIBUTING.md +117 -0
- package/LICENSE +21 -0
- package/README.md +354 -0
- package/SECURITY.md +37 -0
- package/dist/agents/agentPrompt.d.ts +5 -0
- package/dist/agents/agentPrompt.js +30 -0
- package/dist/agents/agentPrompt.js.map +1 -0
- package/dist/ai/aiClient.d.ts +7 -0
- package/dist/ai/aiClient.js +30 -0
- package/dist/ai/aiClient.js.map +1 -0
- package/dist/ai/aiGenerator.d.ts +10 -0
- package/dist/ai/aiGenerator.js +101 -0
- package/dist/ai/aiGenerator.js.map +1 -0
- package/dist/ai/genericInference.d.ts +5 -0
- package/dist/ai/genericInference.js +80 -0
- package/dist/ai/genericInference.js.map +1 -0
- package/dist/ai/index.d.ts +3 -0
- package/dist/ai/index.js +2 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/ai/jsonRepair.d.ts +2 -0
- package/dist/ai/jsonRepair.js +31 -0
- package/dist/ai/jsonRepair.js.map +1 -0
- package/dist/ai/prompts.d.ts +4 -0
- package/dist/ai/prompts.js +81 -0
- package/dist/ai/prompts.js.map +1 -0
- package/dist/behavior/behaviorStore.d.ts +12 -0
- package/dist/behavior/behaviorStore.js +72 -0
- package/dist/behavior/behaviorStore.js.map +1 -0
- package/dist/cache/index.d.ts +9 -0
- package/dist/cache/index.js +34 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/cli/errors.d.ts +4 -0
- package/dist/cli/errors.js +9 -0
- package/dist/cli/errors.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +277 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/parser.d.ts +56 -0
- package/dist/cli/parser.js +169 -0
- package/dist/cli/parser.js.map +1 -0
- package/dist/config/localConfig.d.ts +16 -0
- package/dist/config/localConfig.js +58 -0
- package/dist/config/localConfig.js.map +1 -0
- package/dist/config/serverConfig.d.ts +13 -0
- package/dist/config/serverConfig.js +44 -0
- package/dist/config/serverConfig.js.map +1 -0
- package/dist/dashboard/app.js +615 -0
- package/dist/dashboard/dashboard.d.ts +4 -0
- package/dist/dashboard/dashboard.js +20 -0
- package/dist/dashboard/dashboard.js.map +1 -0
- package/dist/dashboard/index.d.ts +1 -0
- package/dist/dashboard/index.html +298 -0
- package/dist/dashboard/index.js +2 -0
- package/dist/dashboard/index.js.map +1 -0
- package/dist/dashboard/styles.css +349 -0
- package/dist/errors/errorEngine.d.ts +3 -0
- package/dist/errors/errorEngine.js +65 -0
- package/dist/errors/errorEngine.js.map +1 -0
- package/dist/errors/index.d.ts +3 -0
- package/dist/errors/index.js +3 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/errors/providerErrors.d.ts +3 -0
- package/dist/errors/providerErrors.js +6 -0
- package/dist/errors/providerErrors.js.map +1 -0
- package/dist/fault/faultLab.d.ts +27 -0
- package/dist/fault/faultLab.js +89 -0
- package/dist/fault/faultLab.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/landing/assets/index-BXR41fAD.css +1 -0
- package/dist/landing/assets/index-D8_ggMRE.js +55 -0
- package/dist/landing/index.html +15 -0
- package/dist/landing/landing.d.ts +3 -0
- package/dist/landing/landing.js +12 -0
- package/dist/landing/landing.js.map +1 -0
- package/dist/mcp/server.d.ts +1 -0
- package/dist/mcp/server.js +52 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/providers/discord.d.ts +2 -0
- package/dist/providers/discord.js +11 -0
- package/dist/providers/discord.js.map +1 -0
- package/dist/providers/generic.d.ts +2 -0
- package/dist/providers/generic.js +13 -0
- package/dist/providers/generic.js.map +1 -0
- package/dist/providers/github.d.ts +2 -0
- package/dist/providers/github.js +11 -0
- package/dist/providers/github.js.map +1 -0
- package/dist/providers/index.d.ts +2 -0
- package/dist/providers/index.js +2 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/openai.d.ts +2 -0
- package/dist/providers/openai.js +15 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/providers/registry.d.ts +12 -0
- package/dist/providers/registry.js +23 -0
- package/dist/providers/registry.js.map +1 -0
- package/dist/providers/resend.d.ts +2 -0
- package/dist/providers/resend.js +12 -0
- package/dist/providers/resend.js.map +1 -0
- package/dist/providers/stripe.d.ts +2 -0
- package/dist/providers/stripe.js +15 -0
- package/dist/providers/stripe.js.map +1 -0
- package/dist/providers/twilio.d.ts +2 -0
- package/dist/providers/twilio.js +14 -0
- package/dist/providers/twilio.js.map +1 -0
- package/dist/providers/types.d.ts +13 -0
- package/dist/providers/types.js +2 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/proxy/cacheKey.d.ts +2 -0
- package/dist/proxy/cacheKey.js +37 -0
- package/dist/proxy/cacheKey.js.map +1 -0
- package/dist/proxy/providerDetector.d.ts +11 -0
- package/dist/proxy/providerDetector.js +74 -0
- package/dist/proxy/providerDetector.js.map +1 -0
- package/dist/proxy/proxyHandler.d.ts +3 -0
- package/dist/proxy/proxyHandler.js +114 -0
- package/dist/proxy/proxyHandler.js.map +1 -0
- package/dist/proxy/requestNormalizer.d.ts +11 -0
- package/dist/proxy/requestNormalizer.js +30 -0
- package/dist/proxy/requestNormalizer.js.map +1 -0
- package/dist/report/safetyReport.d.ts +13 -0
- package/dist/report/safetyReport.js +65 -0
- package/dist/report/safetyReport.js.map +1 -0
- package/dist/rules/aiRules.d.ts +7 -0
- package/dist/rules/aiRules.js +31 -0
- package/dist/rules/aiRules.js.map +1 -0
- package/dist/scenarios/scenarioStore.d.ts +37 -0
- package/dist/scenarios/scenarioStore.js +201 -0
- package/dist/scenarios/scenarioStore.js.map +1 -0
- package/dist/security/headerSanitizer.d.ts +3 -0
- package/dist/security/headerSanitizer.js +12 -0
- package/dist/security/headerSanitizer.js.map +1 -0
- package/dist/security/maskSecrets.d.ts +1 -0
- package/dist/security/maskSecrets.js +2 -0
- package/dist/security/maskSecrets.js.map +1 -0
- package/dist/security/secrets.d.ts +4 -0
- package/dist/security/secrets.js +61 -0
- package/dist/security/secrets.js.map +1 -0
- package/dist/server/createServer.d.ts +8 -0
- package/dist/server/createServer.js +42 -0
- package/dist/server/createServer.js.map +1 -0
- package/dist/server/eventsStore.d.ts +17 -0
- package/dist/server/eventsStore.js +31 -0
- package/dist/server/eventsStore.js.map +1 -0
- package/dist/server/routes.d.ts +3 -0
- package/dist/server/routes.js +189 -0
- package/dist/server/routes.js.map +1 -0
- package/dist/server/sse.d.ts +5 -0
- package/dist/server/sse.js +32 -0
- package/dist/server/sse.js.map +1 -0
- package/dist/setup/sdkDetector.d.ts +9 -0
- package/dist/setup/sdkDetector.js +31 -0
- package/dist/setup/sdkDetector.js.map +1 -0
- package/dist/setup/setupGenerator.d.ts +28 -0
- package/dist/setup/setupGenerator.js +146 -0
- package/dist/setup/setupGenerator.js.map +1 -0
- package/dist/state/index.d.ts +1 -0
- package/dist/state/index.js +2 -0
- package/dist/state/index.js.map +1 -0
- package/dist/state/stateExtractor.d.ts +3 -0
- package/dist/state/stateExtractor.js +36 -0
- package/dist/state/stateExtractor.js.map +1 -0
- package/dist/state/stateResolver.d.ts +8 -0
- package/dist/state/stateResolver.js +95 -0
- package/dist/state/stateResolver.js.map +1 -0
- package/dist/state/stateStore.d.ts +4 -0
- package/dist/state/stateStore.js +72 -0
- package/dist/state/stateStore.js.map +1 -0
- package/dist/tests/testGenerator.d.ts +6 -0
- package/dist/tests/testGenerator.js +33 -0
- package/dist/tests/testGenerator.js.map +1 -0
- package/dist/utils/json.d.ts +2 -0
- package/dist/utils/json.js +10 -0
- package/dist/utils/json.js.map +1 -0
- package/docs/release-checklist.md +71 -0
- package/examples/agent-instructions/README.md +39 -0
- package/examples/generic-rest/README.md +27 -0
- package/examples/github-fetch/README.md +35 -0
- package/examples/resend-node/README.md +22 -0
- package/examples/stripe-node/README.md +29 -0
- package/examples/twilio-node/README.md +21 -0
- package/package.json +67 -0
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap');
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
--background: #09090b; /* zinc-950 */
|
|
5
|
+
--foreground: #fafafa;
|
|
6
|
+
--card: #09090b;
|
|
7
|
+
--card-foreground: #fafafa;
|
|
8
|
+
--popover: #09090b;
|
|
9
|
+
--popover-foreground: #fafafa;
|
|
10
|
+
--primary: #fafafa;
|
|
11
|
+
--primary-foreground: #18181b;
|
|
12
|
+
--secondary: #27272a;
|
|
13
|
+
--secondary-foreground: #fafafa;
|
|
14
|
+
--muted: #27272a;
|
|
15
|
+
--muted-foreground: #a1a1aa; /* zinc-400 */
|
|
16
|
+
--accent: #27272a;
|
|
17
|
+
--accent-foreground: #fafafa;
|
|
18
|
+
--border: rgba(255, 255, 255, 0.1);
|
|
19
|
+
--input: #27272a;
|
|
20
|
+
--ring: #d4d4d8;
|
|
21
|
+
|
|
22
|
+
--radius: 0.5rem;
|
|
23
|
+
|
|
24
|
+
--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
25
|
+
--font-mono: 'JetBrains Mono', "Geist Mono", "Fira Code", monospace;
|
|
26
|
+
|
|
27
|
+
--sys-green: #4ade80;
|
|
28
|
+
--sys-blue: #60a5fa;
|
|
29
|
+
--sys-red: #f87171;
|
|
30
|
+
--sys-orange: #fbbf24;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
* {
|
|
34
|
+
box-sizing: border-box;
|
|
35
|
+
margin: 0;
|
|
36
|
+
padding: 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
body {
|
|
40
|
+
background-color: var(--background);
|
|
41
|
+
color: var(--foreground);
|
|
42
|
+
font-family: var(--font-sans);
|
|
43
|
+
-webkit-font-smoothing: antialiased;
|
|
44
|
+
font-size: 13px;
|
|
45
|
+
overflow: hidden;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* Layout Wrappers */
|
|
49
|
+
.app-wrapper {
|
|
50
|
+
display: flex;
|
|
51
|
+
width: 100vw;
|
|
52
|
+
height: 100vh;
|
|
53
|
+
background: var(--background);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* Sidebar */
|
|
57
|
+
.sidebar {
|
|
58
|
+
width: 260px;
|
|
59
|
+
height: 100%;
|
|
60
|
+
background-color: rgba(255, 255, 255, 0.02);
|
|
61
|
+
border-right: 1px solid var(--border);
|
|
62
|
+
padding: 12px;
|
|
63
|
+
display: flex;
|
|
64
|
+
flex-direction: column;
|
|
65
|
+
flex-shrink: 0;
|
|
66
|
+
transition: width 0.3s ease, opacity 0.3s ease;
|
|
67
|
+
overflow: hidden;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.sidebar.closed {
|
|
71
|
+
width: 0;
|
|
72
|
+
padding-left: 0;
|
|
73
|
+
padding-right: 0;
|
|
74
|
+
opacity: 0;
|
|
75
|
+
border-right: none;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* Workspace Switcher */
|
|
79
|
+
.ws-switcher {
|
|
80
|
+
position: relative;
|
|
81
|
+
margin-bottom: 24px;
|
|
82
|
+
}
|
|
83
|
+
.ws-btn {
|
|
84
|
+
display: flex;
|
|
85
|
+
align-items: center;
|
|
86
|
+
justify-content: space-between;
|
|
87
|
+
padding: 8px;
|
|
88
|
+
border-radius: var(--radius);
|
|
89
|
+
user-select: none;
|
|
90
|
+
}
|
|
91
|
+
.ws-btn-content { display: flex; align-items: center; gap: 12px; }
|
|
92
|
+
.ws-avatar {
|
|
93
|
+
width: 32px; height: 32px;
|
|
94
|
+
border-radius: 6px;
|
|
95
|
+
background: var(--primary);
|
|
96
|
+
color: var(--primary-foreground);
|
|
97
|
+
display: flex; align-items: center; justify-content: center;
|
|
98
|
+
font-weight: 600; font-size: 13px;
|
|
99
|
+
box-shadow: 0 1px 2px rgba(0,0,0,0.1);
|
|
100
|
+
}
|
|
101
|
+
.ws-info { display: flex; flex-direction: column; overflow: hidden; }
|
|
102
|
+
.ws-title { font-size: 13px; font-weight: 500; line-height: 1; margin-bottom: 4px; color: var(--foreground); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 120px; }
|
|
103
|
+
.ws-subtitle { font-size: 11px; color: var(--muted-foreground); line-height: 1; }
|
|
104
|
+
.ws-icon { color: rgba(161, 161, 170, 0.5); width: 16px; height: 16px; transition: color 0.2s; }
|
|
105
|
+
.ws-btn:hover .ws-icon { color: rgba(250, 250, 250, 0.7); }
|
|
106
|
+
|
|
107
|
+
/* Sidebar Nav Groups */
|
|
108
|
+
.nav-scroll-area {
|
|
109
|
+
flex: 1;
|
|
110
|
+
overflow-y: auto;
|
|
111
|
+
display: flex;
|
|
112
|
+
flex-direction: column;
|
|
113
|
+
gap: 16px;
|
|
114
|
+
margin-top: 8px;
|
|
115
|
+
}
|
|
116
|
+
.nav-scroll-area::-webkit-scrollbar { display: none; }
|
|
117
|
+
.nav-group { display: flex; flex-direction: column; gap: 2px; }
|
|
118
|
+
.nav-heading {
|
|
119
|
+
padding: 0 10px; margin-bottom: 4px;
|
|
120
|
+
font-size: 11px; font-weight: 600; letter-spacing: 0.05em;
|
|
121
|
+
color: rgba(161, 161, 170, 0.5); text-transform: uppercase;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/* Nav Item */
|
|
125
|
+
.nav-item {
|
|
126
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
127
|
+
padding: 7px 10px;
|
|
128
|
+
border-radius: 6px;
|
|
129
|
+
cursor: pointer;
|
|
130
|
+
transition: all 0.2s;
|
|
131
|
+
user-select: none;
|
|
132
|
+
color: var(--muted-foreground);
|
|
133
|
+
}
|
|
134
|
+
.nav-item:hover { color: rgba(250, 250, 250, 0.9); background: rgba(255, 255, 255, 0.05); }
|
|
135
|
+
.nav-item.active { background: rgba(255, 255, 255, 0.1); color: var(--foreground); font-weight: 500; }
|
|
136
|
+
.nav-item-left { display: flex; align-items: center; gap: 10px; }
|
|
137
|
+
.nav-item-icon { width: 16px; height: 16px; color: rgba(161, 161, 170, 0.7); transition: color 0.2s; }
|
|
138
|
+
.nav-item:hover .nav-item-icon { color: rgba(250, 250, 250, 0.7); }
|
|
139
|
+
.nav-item.active .nav-item-icon { color: var(--foreground); }
|
|
140
|
+
.nav-item-title { font-size: 13px; tracking-wide; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
141
|
+
.nav-item-right { display: flex; align-items: center; gap: 8px; }
|
|
142
|
+
|
|
143
|
+
/* Status Badge/Shortcuts injected via JS natively */
|
|
144
|
+
.sc-badge { display: none; align-items: center; justify-content: center; height: 20px; padding: 0 6px; font-size: 10px; font-weight: 500; font-family: var(--font-mono); color: rgba(161, 161, 170, 0.6); background: rgba(0, 0, 0, 0.5); border: 1px solid var(--border); border-radius: 4px; }
|
|
145
|
+
.nav-item:hover .sc-badge { display: flex; }
|
|
146
|
+
.num-badge { display: flex; align-items: center; justify-content: center; min-width: 20px; height: 20px; padding: 0 6px; font-size: 10px; font-weight: 500; border-radius: 99px; background: rgba(250, 250, 250, 0.1); color: var(--primary); }
|
|
147
|
+
.fault-toggle.active { color: #fef3c7; background: rgba(251, 191, 36, 0.1); }
|
|
148
|
+
.fault-toggle.active .nav-item-icon { color: var(--sys-orange); }
|
|
149
|
+
.fault-state { font-family: var(--font-mono); font-size: 10px; color: var(--muted-foreground); border: 1px solid var(--border); border-radius: 99px; padding: 2px 6px; }
|
|
150
|
+
.fault-toggle.active .fault-state { color: var(--sys-orange); border-color: rgba(251, 191, 36, 0.25); background: rgba(251, 191, 36, 0.08); }
|
|
151
|
+
|
|
152
|
+
.nav-bottom {
|
|
153
|
+
margin-top: auto;
|
|
154
|
+
padding-top: 16px;
|
|
155
|
+
border-top: 1px solid var(--border);
|
|
156
|
+
display: flex;
|
|
157
|
+
flex-direction: column;
|
|
158
|
+
gap: 2px;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/* Main Content Area */
|
|
162
|
+
.main {
|
|
163
|
+
flex: 1;
|
|
164
|
+
display: flex;
|
|
165
|
+
flex-direction: column;
|
|
166
|
+
background: rgba(255, 255, 255, 0.01);
|
|
167
|
+
min-width: 0;
|
|
168
|
+
transition: all 0.3s;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/* Topbar */
|
|
172
|
+
.topbar {
|
|
173
|
+
height: 56px;
|
|
174
|
+
border-bottom: 1px solid var(--border);
|
|
175
|
+
display: flex;
|
|
176
|
+
align-items: center;
|
|
177
|
+
justify-content: space-between;
|
|
178
|
+
padding: 0 16px;
|
|
179
|
+
background: var(--card);
|
|
180
|
+
flex-shrink: 0;
|
|
181
|
+
}
|
|
182
|
+
.topbar-left { display: flex; align-items: center; gap: 12px; }
|
|
183
|
+
.toggle-btn {
|
|
184
|
+
padding: 6px; border-radius: 6px; color: var(--muted-foreground); cursor: pointer; transition: all 0.2s; background: transparent; border: none; outline: none;
|
|
185
|
+
}
|
|
186
|
+
.toggle-btn:hover { background: rgba(255,255,255,0.05); color: var(--foreground); }
|
|
187
|
+
|
|
188
|
+
.breadcrumb { display: flex; align-items: center; gap: 8px; font-size: 14px; color: var(--muted-foreground); }
|
|
189
|
+
.breadcrumb-active { font-weight: 500; color: var(--foreground); }
|
|
190
|
+
|
|
191
|
+
.topbar-right { display: flex; align-items: center; gap: 12px; }
|
|
192
|
+
.status-indicator {
|
|
193
|
+
display: flex; align-items: center; gap: 6px; padding: 4px 10px; border-radius: 6px; font-size: 12px; background: rgba(255,255,255,0.03); color: var(--muted-foreground); border: 1px solid var(--border);
|
|
194
|
+
}
|
|
195
|
+
.status-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--muted-foreground); }
|
|
196
|
+
.status-indicator.connected .status-dot { background: var(--sys-green); box-shadow: 0 0 10px rgba(74, 222, 128, 0.4); }
|
|
197
|
+
.status-indicator.connected { color: var(--foreground); }
|
|
198
|
+
|
|
199
|
+
/* Main Split View: Event List (Left) + Event Details (Right) */
|
|
200
|
+
.content-wrapper {
|
|
201
|
+
flex: 1;
|
|
202
|
+
display: flex;
|
|
203
|
+
overflow: hidden;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/* Event List Pane */
|
|
207
|
+
.event-list-pane {
|
|
208
|
+
width: 340px;
|
|
209
|
+
border-right: 1px solid var(--border);
|
|
210
|
+
display: flex;
|
|
211
|
+
flex-direction: column;
|
|
212
|
+
background: var(--background);
|
|
213
|
+
}
|
|
214
|
+
.event-list-header {
|
|
215
|
+
padding: 12px 16px;
|
|
216
|
+
border-bottom: 1px solid var(--border);
|
|
217
|
+
display: flex; justify-content: space-between; align-items: center;
|
|
218
|
+
}
|
|
219
|
+
.event-list-header h2 { font-size: 13px; font-weight: 600; color: var(--foreground); }
|
|
220
|
+
.event-count { font-family: var(--font-mono); font-size: 11px; color: var(--muted-foreground); background: rgba(255,255,255,0.05); padding: 2px 6px; border-radius: 4px;}
|
|
221
|
+
|
|
222
|
+
.events-scroll { flex: 1; overflow-y: auto; }
|
|
223
|
+
.events-scroll::-webkit-scrollbar { display: none; }
|
|
224
|
+
|
|
225
|
+
.event-card {
|
|
226
|
+
padding: 12px 16px; border-bottom: 1px solid var(--border); cursor: pointer; transition: background 0.2s;
|
|
227
|
+
}
|
|
228
|
+
.event-card:hover { background: rgba(255,255,255,0.02); }
|
|
229
|
+
.event-card.active { background: rgba(255,255,255,0.04); border-left: 2px solid var(--sys-blue); padding-left: 14px; }
|
|
230
|
+
|
|
231
|
+
.ec-top { display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px; }
|
|
232
|
+
.ec-method { font-family: var(--font-mono); font-size: 11px; font-weight: 600; padding: 2px 6px; border-radius: 4px; background: rgba(255,255,255,0.05); color: var(--foreground);}
|
|
233
|
+
.ec-time { font-size: 11px; color: var(--muted-foreground); }
|
|
234
|
+
.ec-path { font-family: var(--font-mono); font-size: 12px; color: var(--foreground); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-bottom: 8px;}
|
|
235
|
+
.ec-bottom { display: flex; align-items: center; gap: 8px; font-size: 11px; }
|
|
236
|
+
.provider-pill { margin-left: auto; }
|
|
237
|
+
|
|
238
|
+
.pill { padding: 2px 6px; border-radius: 4px; background: rgba(255,255,255,0.05); color: var(--muted-foreground); border: 1px solid var(--border); font-family: var(--font-mono); }
|
|
239
|
+
.pill-status-200 { color: var(--sys-green); border-color: rgba(74, 222, 128, 0.2); background: rgba(74, 222, 128, 0.05); }
|
|
240
|
+
.pill-status-400, .pill-status-500 { color: var(--sys-red); border-color: rgba(248, 113, 113, 0.2); background: rgba(248, 113, 113, 0.05); }
|
|
241
|
+
|
|
242
|
+
/* Details Pane */
|
|
243
|
+
.details-pane {
|
|
244
|
+
flex: 1;
|
|
245
|
+
display: flex;
|
|
246
|
+
flex-direction: column;
|
|
247
|
+
background: var(--background);
|
|
248
|
+
min-width: 0;
|
|
249
|
+
}
|
|
250
|
+
.empty-view {
|
|
251
|
+
flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; color: var(--muted-foreground); gap: 12px;
|
|
252
|
+
}
|
|
253
|
+
.empty-view svg { opacity: 0.3; }
|
|
254
|
+
|
|
255
|
+
.details-content { flex: 1; display: flex; flex-direction: column; display: none; }
|
|
256
|
+
.details-content.visible { display: flex; }
|
|
257
|
+
|
|
258
|
+
.dep-header {
|
|
259
|
+
padding: 16px 24px; border-bottom: 1px solid var(--border); display: flex; align-items: center; justify-content: space-between; background: rgba(255,255,255,0.01);
|
|
260
|
+
}
|
|
261
|
+
.dep-title { display: flex; align-items: center; gap: 12px; font-family: var(--font-mono); font-size: 14px; }
|
|
262
|
+
.dep-badges { display: flex; gap: 8px; }
|
|
263
|
+
.mini-action { border: 1px solid var(--border); background: rgba(255,255,255,0.04); color: var(--foreground); border-radius: 6px; padding: 3px 8px; font: 500 11px var(--font-sans); cursor: pointer; }
|
|
264
|
+
.mini-action:hover { background: rgba(255,255,255,0.08); }
|
|
265
|
+
|
|
266
|
+
.json-split {
|
|
267
|
+
flex: 1; display: flex; overflow: hidden;
|
|
268
|
+
}
|
|
269
|
+
.json-pane {
|
|
270
|
+
flex: 1; display: flex; flex-direction: column; border-right: 1px solid var(--border); min-width: 0;
|
|
271
|
+
}
|
|
272
|
+
.json-pane:last-child { border-right: none; }
|
|
273
|
+
|
|
274
|
+
.json-header {
|
|
275
|
+
padding: 8px 16px; border-bottom: 1px solid var(--border); display: flex; align-items: center; justify-content: space-between;
|
|
276
|
+
font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; font-weight: 600; color: var(--muted-foreground); background: var(--background);
|
|
277
|
+
}
|
|
278
|
+
.json-body {
|
|
279
|
+
flex: 1; overflow: auto; padding: 16px; background: rgba(255,255,255,0.01);
|
|
280
|
+
}
|
|
281
|
+
.json-body pre { font-family: var(--font-mono); font-size: 12px; line-height: 1.6; color: var(--foreground); white-space: pre-wrap; word-wrap: break-word; }
|
|
282
|
+
|
|
283
|
+
/* Modal / Search */
|
|
284
|
+
.modal-overlay {
|
|
285
|
+
position: fixed; inset: 0; z-index: 50; display: none; align-items: flex-start; justify-content: center; padding: 12vh 16px 24px; background: rgba(0,0,0,0.4); backdrop-filter: blur(2px); overflow: auto;
|
|
286
|
+
}
|
|
287
|
+
.modal-overlay.open { display: flex; }
|
|
288
|
+
.modal-content {
|
|
289
|
+
position: relative; width: 100%; max-width: 600px; max-height: calc(100vh - 48px); background: var(--card); border: 1px solid var(--border); border-radius: 12px; box-shadow: 0 24px 48px rgba(0,0,0,0.4); overflow: hidden; animation: zoomIn 0.15s ease-out;
|
|
290
|
+
}
|
|
291
|
+
.modal-header {
|
|
292
|
+
display: flex; align-items: center; padding: 0 16px; border-bottom: 1px solid var(--border);
|
|
293
|
+
}
|
|
294
|
+
.modal-header svg { color: var(--muted-foreground); margin-right: 12px; }
|
|
295
|
+
.modal-input {
|
|
296
|
+
flex: 1; background: transparent; border: none; padding: 16px 0; outline: none; font-size: 14px; color: var(--foreground); font-family: var(--font-sans);
|
|
297
|
+
}
|
|
298
|
+
.modal-input::placeholder { color: rgba(161, 161, 170, 0.5); }
|
|
299
|
+
.modal-body { padding: 32px 16px; display: flex; flex-direction: column; align-items: center; justify-content: center; color: var(--muted-foreground); gap: 8px; }
|
|
300
|
+
.rules-modal-content { max-width: 860px; }
|
|
301
|
+
.rules-modal-header { gap: 12px; padding: 12px 16px; }
|
|
302
|
+
.rules-title-block { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
|
|
303
|
+
.rules-title-block strong { font-size: 13px; color: var(--foreground); }
|
|
304
|
+
.rules-title-block span { font-size: 11px; color: var(--muted-foreground); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
305
|
+
.modal-action { border: 1px solid rgba(250,250,250,0.16); background: rgba(250,250,250,0.92); color: #18181b; border-radius: 6px; padding: 6px 10px; font: 500 12px var(--font-sans); cursor: pointer; }
|
|
306
|
+
.modal-action.ghost { background: rgba(255,255,255,0.04); color: var(--muted-foreground); }
|
|
307
|
+
.modal-action:hover { filter: brightness(1.08); }
|
|
308
|
+
.rules-body { max-height: 70vh; overflow: auto; padding: 16px; background: radial-gradient(circle at top left, rgba(96,165,250,0.08), transparent 28%), rgba(255,255,255,0.01); }
|
|
309
|
+
.rules-body pre { font-family: var(--font-mono); font-size: 12px; line-height: 1.7; white-space: pre-wrap; color: var(--foreground); }
|
|
310
|
+
.setup-modal-content { max-width: 980px; }
|
|
311
|
+
.setup-grid, .scenario-shell { display: grid; grid-template-columns: 300px minmax(0, 1fr); min-height: 560px; max-height: 74vh; }
|
|
312
|
+
.setup-list, .scenario-list { border-right: 1px solid var(--border); overflow: auto; padding: 10px; background: rgba(255,255,255,0.01); }
|
|
313
|
+
.setup-output, .scenario-output { max-height: none; height: 100%; }
|
|
314
|
+
.setup-item { width: 100%; border: 1px solid transparent; background: transparent; color: var(--muted-foreground); border-radius: 8px; padding: 10px; text-align: left; cursor: pointer; display: flex; flex-direction: column; gap: 4px; }
|
|
315
|
+
.setup-item:hover, .setup-item.active { background: rgba(255,255,255,0.05); border-color: var(--border); color: var(--foreground); }
|
|
316
|
+
.setup-item strong { font: 600 12px var(--font-sans); color: inherit; }
|
|
317
|
+
.setup-item span { font-size: 11px; line-height: 1.4; color: var(--muted-foreground); }
|
|
318
|
+
.scenario-card { border: 1px solid var(--border); border-radius: 10px; padding: 12px; margin-bottom: 10px; background: rgba(255,255,255,0.02); }
|
|
319
|
+
.scenario-card-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 8px; }
|
|
320
|
+
.scenario-card strong { font-size: 13px; color: var(--foreground); }
|
|
321
|
+
.scenario-card p { font-size: 12px; line-height: 1.5; color: var(--muted-foreground); margin-bottom: 12px; }
|
|
322
|
+
.scenario-actions { display: flex; gap: 6px; }
|
|
323
|
+
.scenario-actions button { flex: 1; border: 1px solid var(--border); background: rgba(255,255,255,0.04); color: var(--foreground); border-radius: 6px; padding: 6px; font: 500 11px var(--font-sans); cursor: pointer; }
|
|
324
|
+
.scenario-actions button:hover { background: rgba(255,255,255,0.08); }
|
|
325
|
+
|
|
326
|
+
@media (max-width: 900px) {
|
|
327
|
+
.modal-overlay { padding-top: 24px; }
|
|
328
|
+
.setup-grid, .scenario-shell { grid-template-columns: 1fr; min-height: auto; }
|
|
329
|
+
.setup-list, .scenario-list { border-right: none; border-bottom: 1px solid var(--border); max-height: 260px; }
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
@media (max-height: 720px) {
|
|
333
|
+
.setup-grid, .scenario-shell { min-height: 0; }
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
@keyframes zoomIn { from { opacity: 0; transform: scale(0.96); } to { opacity: 1; transform: scale(1); } }
|
|
337
|
+
|
|
338
|
+
/* Method Colors */
|
|
339
|
+
.m-get { color: var(--sys-blue); }
|
|
340
|
+
.m-post { color: var(--sys-green); }
|
|
341
|
+
.m-delete { color: var(--sys-red); }
|
|
342
|
+
.m-put, .m-patch { color: var(--sys-orange); }
|
|
343
|
+
|
|
344
|
+
/* Syntax Highlight */
|
|
345
|
+
.j-key { color: #86efac; opacity: 0.9; }
|
|
346
|
+
.j-str { color: #fca5a5; opacity: 0.9; }
|
|
347
|
+
.j-num { color: #7dd3fc; opacity: 0.9; }
|
|
348
|
+
.j-bool { color: #c4b5fd; opacity: 0.9; }
|
|
349
|
+
.j-null { color: var(--muted-foreground); font-style: italic; }
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { NormalizedRequest } from "../proxy/requestNormalizer.js";
|
|
2
|
+
import type { ProviderName, ProviderErrorDetails } from "../providers/types.js";
|
|
3
|
+
export declare function validateRequest(request: NormalizedRequest, provider: ProviderName): ProviderErrorDetails | null;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { isJsonObject } from "../utils/json.js";
|
|
2
|
+
export function validateRequest(request, provider) {
|
|
3
|
+
if (provider === "stripe") {
|
|
4
|
+
return validateStripe(request);
|
|
5
|
+
}
|
|
6
|
+
if (provider === "twilio") {
|
|
7
|
+
return validateTwilio(request);
|
|
8
|
+
}
|
|
9
|
+
if (provider === "resend") {
|
|
10
|
+
return validateResend(request);
|
|
11
|
+
}
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
function validateStripe(request) {
|
|
15
|
+
if (request.method !== "POST")
|
|
16
|
+
return null;
|
|
17
|
+
const body = isJsonObject(request.body) ? request.body : Object.fromEntries(new URLSearchParams(String(request.body)));
|
|
18
|
+
if (request.path.includes("/charges") || request.path.includes("/payment_intents")) {
|
|
19
|
+
if (!body["amount"]) {
|
|
20
|
+
return { status: 400, message: "Missing required param: amount.", type: "invalid_request_error", code: "parameter_missing", param: "amount" };
|
|
21
|
+
}
|
|
22
|
+
if (!body["currency"]) {
|
|
23
|
+
return { status: 400, message: "Missing required param: currency.", type: "invalid_request_error", code: "parameter_missing", param: "currency" };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (request.path.includes("/customers") && Object.keys(body).length === 0) {
|
|
27
|
+
return { status: 400, message: "Missing required param: email or description.", type: "invalid_request_error", code: "parameter_missing", param: "email" };
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
function validateTwilio(request) {
|
|
32
|
+
if (request.method !== "POST")
|
|
33
|
+
return null;
|
|
34
|
+
const body = Array.isArray(request.body) ? {} : isJsonObject(request.body) ? request.body : Object.fromEntries(new URLSearchParams(String(request.body)));
|
|
35
|
+
if (request.path.includes("/Messages.json")) {
|
|
36
|
+
if (!body["To"]) {
|
|
37
|
+
return { status: 400, message: "A 'To' phone number is required.", code: 21604 };
|
|
38
|
+
}
|
|
39
|
+
if (!body["From"] && !body["MessagingServiceSid"]) {
|
|
40
|
+
return { status: 400, message: "A 'From' phone number is required.", code: 21603 };
|
|
41
|
+
}
|
|
42
|
+
if (!body["Body"] && !body["MediaUrl"]) {
|
|
43
|
+
return { status: 400, message: "Message body is required.", code: 21602 };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
function validateResend(request) {
|
|
49
|
+
if (request.method !== "POST")
|
|
50
|
+
return null;
|
|
51
|
+
const body = isJsonObject(request.body) ? request.body : {};
|
|
52
|
+
if (request.path === "/emails" || request.path.startsWith("/emails/")) {
|
|
53
|
+
if (!body["from"]) {
|
|
54
|
+
return { status: 400, message: "Missing required field: from", type: "validation_error" };
|
|
55
|
+
}
|
|
56
|
+
if (!body["to"]) {
|
|
57
|
+
return { status: 400, message: "Missing required field: to", type: "validation_error" };
|
|
58
|
+
}
|
|
59
|
+
if (!body["subject"]) {
|
|
60
|
+
return { status: 400, message: "Missing required field: subject", type: "validation_error" };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=errorEngine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errorEngine.js","sourceRoot":"","sources":["../../src/errors/errorEngine.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAEhD,MAAM,UAAU,eAAe,CAAC,OAA0B,EAAE,QAAsB;IAChF,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC1B,OAAO,cAAc,CAAC,OAAO,CAAC,CAAC;IACjC,CAAC;IAED,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC1B,OAAO,cAAc,CAAC,OAAO,CAAC,CAAC;IACjC,CAAC;IAED,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC1B,OAAO,cAAc,CAAC,OAAO,CAAC,CAAC;IACjC,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,cAAc,CAAC,OAA0B;IAChD,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC;IAE3C,MAAM,IAAI,GAAG,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,eAAe,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAEvH,IAAI,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC;QACnF,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;YACpB,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,iCAAiC,EAAE,IAAI,EAAE,uBAAuB,EAAE,IAAI,EAAE,mBAAmB,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;QAChJ,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;YACtB,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,mCAAmC,EAAE,IAAI,EAAE,uBAAuB,EAAE,IAAI,EAAE,mBAAmB,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC;QACpJ,CAAC;IACH,CAAC;IAED,IAAI,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1E,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,+CAA+C,EAAE,IAAI,EAAE,uBAAuB,EAAE,IAAI,EAAE,mBAAmB,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;IAC7J,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,cAAc,CAAC,OAA0B;IAChD,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC;IAE3C,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,eAAe,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAE1J,IAAI,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC;QAC5C,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAChB,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,kCAAkC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;QACnF,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,qBAAqB,CAAC,EAAE,CAAC;YAClD,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,oCAAoC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;QACrF,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;YACvC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,2BAA2B,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;QAC5E,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,cAAc,CAAC,OAA0B;IAChD,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC;IAE3C,MAAM,IAAI,GAAG,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;IAE5D,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QACtE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YAClB,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,8BAA8B,EAAE,IAAI,EAAE,kBAAkB,EAAE,CAAC;QAC5F,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAChB,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,4BAA4B,EAAE,IAAI,EAAE,kBAAkB,EAAE,CAAC;QAC1F,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;YACrB,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,iCAAiC,EAAE,IAAI,EAAE,kBAAkB,EAAE,CAAC;QAC/F,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/errors/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"providerErrors.js","sourceRoot":"","sources":["../../src/errors/providerErrors.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAG9D,MAAM,UAAU,mBAAmB,CAAC,QAAsB,EAAE,OAA6B;IACvF,MAAM,OAAO,GAAG,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IAC7C,OAAO,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;AACtC,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ProviderName } from "../providers/types.js";
|
|
2
|
+
export type FaultLabConfig = {
|
|
3
|
+
enabled: boolean;
|
|
4
|
+
latencyMs: number;
|
|
5
|
+
latencyMinMs: number;
|
|
6
|
+
latencyMaxMs: number;
|
|
7
|
+
errorRate: number;
|
|
8
|
+
statusCode: 429 | 500 | 502 | 503;
|
|
9
|
+
retryAfterSeconds: number;
|
|
10
|
+
};
|
|
11
|
+
export type FaultLabDecision = {
|
|
12
|
+
type: "off";
|
|
13
|
+
} | {
|
|
14
|
+
type: "delay";
|
|
15
|
+
latencyMs: number;
|
|
16
|
+
} | {
|
|
17
|
+
type: "error";
|
|
18
|
+
latencyMs: number;
|
|
19
|
+
statusCode: FaultLabConfig["statusCode"];
|
|
20
|
+
retryAfterSeconds: number;
|
|
21
|
+
body: unknown;
|
|
22
|
+
};
|
|
23
|
+
export declare function getFaultLabConfig(): FaultLabConfig;
|
|
24
|
+
export declare function updateFaultLabConfig(input: unknown): FaultLabConfig;
|
|
25
|
+
export declare function resetFaultLabForTests(): void;
|
|
26
|
+
export declare function decideFault(provider: ProviderName, random?: () => number): FaultLabDecision;
|
|
27
|
+
export declare function waitForFault(latencyMs: number): Promise<void>;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { createProviderError } from "../errors/index.js";
|
|
2
|
+
const DEFAULT_CONFIG = {
|
|
3
|
+
enabled: false,
|
|
4
|
+
latencyMs: 0,
|
|
5
|
+
latencyMinMs: 2_000,
|
|
6
|
+
latencyMaxMs: 5_000,
|
|
7
|
+
errorRate: 15,
|
|
8
|
+
statusCode: 429,
|
|
9
|
+
retryAfterSeconds: 2
|
|
10
|
+
};
|
|
11
|
+
let config = { ...DEFAULT_CONFIG };
|
|
12
|
+
export function getFaultLabConfig() {
|
|
13
|
+
return { ...config };
|
|
14
|
+
}
|
|
15
|
+
export function updateFaultLabConfig(input) {
|
|
16
|
+
if (input === null || typeof input !== "object" || Array.isArray(input)) {
|
|
17
|
+
throw new Error("Fault Lab config must be a JSON object.");
|
|
18
|
+
}
|
|
19
|
+
const record = input;
|
|
20
|
+
const nextConfig = {
|
|
21
|
+
enabled: readBoolean(record.enabled, config.enabled),
|
|
22
|
+
latencyMs: readInteger(record.latencyMs, config.latencyMs, 0, 10_000),
|
|
23
|
+
latencyMinMs: readInteger(record.latencyMinMs, config.latencyMinMs, 0, 10_000),
|
|
24
|
+
latencyMaxMs: readInteger(record.latencyMaxMs, config.latencyMaxMs, 0, 10_000),
|
|
25
|
+
errorRate: readInteger(record.errorRate, config.errorRate, 0, 100),
|
|
26
|
+
statusCode: readStatusCode(record.statusCode, config.statusCode),
|
|
27
|
+
retryAfterSeconds: readInteger(record.retryAfterSeconds, config.retryAfterSeconds, 0, 120)
|
|
28
|
+
};
|
|
29
|
+
if (nextConfig.latencyMaxMs < nextConfig.latencyMinMs) {
|
|
30
|
+
throw new Error("Fault Lab latencyMaxMs must be greater than or equal to latencyMinMs.");
|
|
31
|
+
}
|
|
32
|
+
config = nextConfig;
|
|
33
|
+
return getFaultLabConfig();
|
|
34
|
+
}
|
|
35
|
+
export function resetFaultLabForTests() {
|
|
36
|
+
config = { ...DEFAULT_CONFIG };
|
|
37
|
+
}
|
|
38
|
+
export function decideFault(provider, random = Math.random) {
|
|
39
|
+
if (!config.enabled)
|
|
40
|
+
return { type: "off" };
|
|
41
|
+
const shouldInjectChaos = config.errorRate > 0 && random() * 100 < config.errorRate;
|
|
42
|
+
if (!shouldInjectChaos)
|
|
43
|
+
return { type: "off" };
|
|
44
|
+
const action = Math.floor(random() * 3);
|
|
45
|
+
if (action === 2)
|
|
46
|
+
return { type: "delay", latencyMs: pickLatency(random) };
|
|
47
|
+
const statusCode = action === 0 ? 429 : config.statusCode === 429 ? 503 : config.statusCode;
|
|
48
|
+
return {
|
|
49
|
+
type: "error",
|
|
50
|
+
latencyMs: config.latencyMs,
|
|
51
|
+
statusCode,
|
|
52
|
+
retryAfterSeconds: config.retryAfterSeconds,
|
|
53
|
+
body: createProviderError(provider, createFaultDetails(statusCode))
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export function waitForFault(latencyMs) {
|
|
57
|
+
return latencyMs > 0 ? new Promise((resolve) => setTimeout(resolve, latencyMs)) : Promise.resolve();
|
|
58
|
+
}
|
|
59
|
+
function createFaultDetails(statusCode) {
|
|
60
|
+
if (statusCode === 429) {
|
|
61
|
+
return { status: 429, message: "Chaos Mode simulated rate limit. Please retry later.", type: "rate_limit_error", code: "rate_limit" };
|
|
62
|
+
}
|
|
63
|
+
return { status: statusCode, message: `Chaos Mode simulated upstream ${statusCode} response.`, type: "api_error", code: statusCode };
|
|
64
|
+
}
|
|
65
|
+
function pickLatency(random) {
|
|
66
|
+
if (config.latencyMs > 0)
|
|
67
|
+
return config.latencyMs;
|
|
68
|
+
const range = config.latencyMaxMs - config.latencyMinMs;
|
|
69
|
+
return config.latencyMinMs + Math.round(random() * range);
|
|
70
|
+
}
|
|
71
|
+
function readBoolean(value, fallback) {
|
|
72
|
+
return typeof value === "boolean" ? value : fallback;
|
|
73
|
+
}
|
|
74
|
+
function readInteger(value, fallback, min, max) {
|
|
75
|
+
if (value === undefined)
|
|
76
|
+
return fallback;
|
|
77
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < min || value > max) {
|
|
78
|
+
throw new Error(`Expected integer between ${min} and ${max}.`);
|
|
79
|
+
}
|
|
80
|
+
return value;
|
|
81
|
+
}
|
|
82
|
+
function readStatusCode(value, fallback) {
|
|
83
|
+
if (value === undefined)
|
|
84
|
+
return fallback;
|
|
85
|
+
if (value === 429 || value === 500 || value === 502 || value === 503)
|
|
86
|
+
return value;
|
|
87
|
+
throw new Error("Fault Lab statusCode must be one of 429, 500, 502, or 503.");
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=faultLab.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"faultLab.js","sourceRoot":"","sources":["../../src/fault/faultLab.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@import"https://fonts.googleapis.com/css2?family=Inter:wght@400;500&family=Source+Code+Pro:wght@400&display=swap";:root{--color-phosphor-green: #3ecf8e;--color-mint-pulse: #00c573;--color-forest-depth: #1f4b37;--color-midnight-emerald: #006239;--color-snow: #fafafa;--color-silver-mist: #b4b4b4;--color-smoke: #898989;--color-graphite: #4d4d4d;--color-slate: #393939;--color-charcoal: #2e2e2e;--color-ash: #242424;--color-obsidian: #121212;--font-circular: "Circular", Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;--font-source-code-pro: "Source Code Pro", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;--text-caption: 12px;--leading-caption: 1.5;--tracking-caption: -.084px;--text-body-sm: 14px;--leading-body-sm: 1.43;--tracking-body-sm: -.098px;--text-body: 16px;--leading-body: 1.5;--tracking-body: -.112px;--text-subheading: 18px;--leading-subheading: 1.38;--tracking-subheading: -.126px;--text-heading-sm: 24px;--leading-heading-sm: 1.33;--tracking-heading-sm: -.168px;--text-heading: 36px;--leading-heading: 1.2;--tracking-heading: -.252px;--text-display: 72px;--leading-display: 1;--tracking-display: -.504px;--font-weight-regular: 400;--font-weight-medium: 500;--spacing-unit: 8px;--spacing-8: 8px;--spacing-16: 16px;--spacing-24: 24px;--spacing-32: 32px;--spacing-40: 40px;--spacing-48: 48px;--spacing-64: 64px;--spacing-80: 80px;--spacing-96: 96px;--spacing-112: 112px;--spacing-128: 128px;--spacing-224: 224px;--page-max-width: 1200px;--section-gap: 64px;--card-padding: 24px;--element-gap: 16px;--radius-lg: 8px;--radius-lg-2: 11px;--radius-2xl: 16px;--radius-full: 9999px;--radius-tags: 9999px;--radius-cards: 16px;--radius-inputs: 8px;--radius-buttons: 9999px;--shadow-sm: rgba(0, 0, 0, .1) 0px 4px 6px -1px, rgba(0, 0, 0, .1) 0px 2px 4px -2px;--surface-obsidian-canvas: #121212;--surface-ash-card: #242424;--surface-charcoal-border: #2e2e2e}@theme{ --color-phosphor-green: #3ecf8e; --color-mint-pulse: #00c573; --color-forest-depth: #1f4b37; --color-midnight-emerald: #006239; --color-snow: #fafafa; --color-silver-mist: #b4b4b4; --color-smoke: #898989; --color-graphite: #4d4d4d; --color-slate: #393939; --color-charcoal: #2e2e2e; --color-ash: #242424; --color-obsidian: #121212; --font-circular: "Circular", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; --font-source-code-pro: "Source Code Pro", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; --text-caption: 12px; --leading-caption: 1.5; --tracking-caption: -.084px; --text-body-sm: 14px; --leading-body-sm: 1.43; --tracking-body-sm: -.098px; --text-body: 16px; --leading-body: 1.5; --tracking-body: -.112px; --text-subheading: 18px; --leading-subheading: 1.38; --tracking-subheading: -.126px; --text-heading-sm: 24px; --leading-heading-sm: 1.33; --tracking-heading-sm: -.168px; --text-heading: 36px; --leading-heading: 1.2; --tracking-heading: -.252px; --text-display: 72px; --leading-display: 1; --tracking-display: -.504px; --spacing-8: 8px; --spacing-16: 16px; --spacing-24: 24px; --spacing-32: 32px; --spacing-40: 40px; --spacing-48: 48px; --spacing-64: 64px; --spacing-80: 80px; --spacing-96: 96px; --spacing-112: 112px; --spacing-128: 128px; --spacing-224: 224px; --radius-lg: 8px; --radius-lg-2: 11px; --radius-2xl: 16px; --radius-full: 9999px; --shadow-sm: rgba(0, 0, 0, .1) 0px 4px 6px -1px, rgba(0, 0, 0, .1) 0px 2px 4px -2px; }*{box-sizing:border-box}html{background:var(--color-obsidian);color:var(--color-snow);scroll-behavior:smooth}body{margin:0;background:var(--color-obsidian);color:var(--color-snow);font-family:var(--font-circular);font-size:var(--text-body);font-weight:var(--font-weight-regular);letter-spacing:var(--tracking-body);line-height:var(--leading-body);-webkit-font-smoothing:antialiased}a{color:inherit;text-decoration:none}button,a{-webkit-tap-highlight-color:transparent}:focus-visible{outline:1px solid var(--color-phosphor-green);outline-offset:4px}.site-shell{min-height:100vh;overflow:hidden;background:linear-gradient(var(--color-charcoal) 1px,transparent 1px),linear-gradient(90deg,var(--color-charcoal) 1px,transparent 1px),var(--color-obsidian);background-size:96px 96px;background-position:center top}.site-shell:before{content:"";position:fixed;top:0;right:0;bottom:0;left:0;pointer-events:none;background:radial-gradient(circle at 50% 0%,rgba(62,207,142,.1),transparent 28%);opacity:.8}.nav{position:fixed;top:0;left:0;right:0;z-index:20;display:flex;align-items:center;justify-content:space-between;height:64px;padding:0 max(24px,calc((100vw - var(--page-max-width)) / 2));background:#121212e0;border-bottom:1px solid var(--color-charcoal);-webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px)}.brand,.nav-links,.nav-actions,.hero-actions,.visual-topline,.control-row,.footer{display:flex;align-items:center}.brand{gap:10px;font-size:var(--text-body-sm);font-weight:var(--font-weight-medium);letter-spacing:var(--tracking-body-sm)}.logo-mark{width:24px;height:24px;color:var(--color-phosphor-green);fill:currentColor}.logo-mark circle{fill:var(--color-obsidian)}.nav-links{gap:24px;color:var(--color-silver-mist);font-size:var(--text-body-sm);line-height:var(--leading-body-sm)}.nav-links a:hover{color:var(--color-snow)}.nav-actions{gap:8px}.stars-pill,.announcement{border:1px solid var(--color-charcoal);border-radius:var(--radius-full);background:var(--color-ash);color:var(--color-snow)}.stars-pill{padding:6px 10px;font-size:var(--text-caption);line-height:var(--leading-caption)}.btn{position:relative;display:inline-flex;align-items:center;justify-content:center;min-height:38px;padding:8px 16px;border:1px solid var(--color-slate);border-radius:var(--radius-buttons);font-size:var(--text-body-sm);font-weight:var(--font-weight-medium);line-height:var(--leading-body-sm);letter-spacing:var(--tracking-body-sm);transition:border-color .18s ease,background-color .18s ease,transform .18s ease}.btn:hover{transform:translateY(-1px)}.btn-primary{border-color:var(--color-phosphor-green);background:var(--color-phosphor-green);color:var(--color-snow)}.btn-primary:hover{border-color:var(--color-forest-depth)}.btn-ghost{background:transparent;color:var(--color-snow)}.btn-ghost:hover{border-color:var(--color-graphite);background:#ffffff0a}.section{position:relative;z-index:1;width:min(var(--page-max-width),calc(100vw - 48px));margin:0 auto;padding:96px 0}.hero{min-height:100vh;padding-top:160px;text-align:center}.announcement{display:inline-flex;align-items:center;gap:8px;padding:6px 16px;color:var(--color-silver-mist);font-size:var(--text-caption);line-height:var(--leading-caption);animation:fade-up .5s ease both}.announcement span{color:var(--color-silver-mist)}.hero h1{max-width:970px;margin:28px auto 0;color:var(--color-snow);font-size:clamp(48px,7vw,var(--text-display));font-weight:var(--font-weight-regular);letter-spacing:var(--tracking-display);line-height:var(--leading-display);animation:fade-up .62s 80ms ease both}.hero h1 span{display:block;color:var(--color-phosphor-green)}.hero-subline{max-width:760px;margin:24px auto 0;color:var(--color-silver-mist);font-size:var(--text-body);line-height:var(--leading-body);animation:fade-up .62s .16s ease both}.hero-actions{justify-content:center;gap:12px;margin-top:32px;animation:fade-up .62s .24s ease both}.hero-command{width:min(460px,100%);margin:24px auto 0;animation:fade-up .62s .32s ease both}.code{margin:0;overflow:auto;border:1px solid var(--color-charcoal);border-radius:var(--radius-inputs);background:#0d0d0d;color:var(--color-snow);padding:12px 14px;text-align:left;font-family:var(--font-source-code-pro);font-size:var(--text-caption);font-weight:var(--font-weight-regular);letter-spacing:.1em;line-height:1.33}.product-visual{position:relative;max-width:1060px;margin:56px auto 0;padding:24px;border:1px solid var(--color-charcoal);border-radius:var(--radius-cards);background:var(--color-obsidian);text-align:left;animation:fade-up .62s .4s ease both}.product-visual:before,.product-visual:after{content:"";position:absolute;width:8px;height:8px;border-radius:9999px;background:var(--color-phosphor-green);box-shadow:0 0 24px #3ecf8e80}.product-visual:before{top:18px;right:24px}.product-visual:after{right:17%;bottom:28%;animation:pulse-dot 1.6s ease-in-out infinite}.visual-topline{justify-content:space-between;padding-bottom:16px;color:var(--color-smoke);font-family:var(--font-source-code-pro);font-size:var(--text-caption);letter-spacing:.1em}.live-dot{color:var(--color-phosphor-green)}.visual-grid{display:grid;grid-template-columns:1.1fr 1fr;grid-template-rows:auto auto;gap:16px}.panel{min-height:214px;border:1px solid var(--color-charcoal);border-radius:var(--radius-cards);background:linear-gradient(var(--color-charcoal) 1px,transparent 1px),linear-gradient(90deg,var(--color-charcoal) 1px,transparent 1px),#0d0d0d;background-size:32px 32px;padding:20px}.traffic-panel{grid-row:span 2}.panel-label,.eyebrow{display:inline-flex;margin-bottom:16px;color:var(--color-phosphor-green);font-size:var(--text-caption);font-weight:var(--font-weight-medium);letter-spacing:var(--tracking-caption);line-height:var(--leading-caption)}.request{display:grid;grid-template-columns:58px minmax(0,1fr) 72px;align-items:center;gap:12px;margin-top:10px;padding:12px;border:1px solid var(--color-charcoal);border-radius:var(--radius-inputs);color:var(--color-silver-mist);animation:scan-row 4.8s linear infinite}.request.active{border-color:var(--color-phosphor-green)}.request span,.request em,.control-row b{font-family:var(--font-source-code-pro);font-size:var(--text-caption);font-style:normal;letter-spacing:.1em}.request strong{overflow:hidden;color:var(--color-snow);font-size:var(--text-body-sm);font-weight:var(--font-weight-medium);text-overflow:ellipsis;white-space:nowrap}.failure-panel .code{height:148px;border-color:var(--color-slate);color:var(--color-silver-mist)}.control-panel{min-height:180px}.control-row{justify-content:space-between;padding:10px 0;border-bottom:1px solid var(--color-charcoal);color:var(--color-silver-mist);font-size:var(--text-body-sm)}.control-row:last-child{border-bottom:0}.control-row b{color:var(--color-phosphor-green);font-weight:var(--font-weight-regular)}.product-visual p,.killer-use-case,.section-copy p,.feature-card p,.step-card p,.audience-grid p,.faq-grid p,.install-note,.footer p{color:var(--color-silver-mist)}.product-visual p{margin:18px 0 0;text-align:center;font-size:var(--text-body-sm)}.killer-use-case{max-width:760px;margin:24px auto 0;font-size:var(--text-body-sm);line-height:var(--leading-body-sm)}.section-copy{max-width:760px}.section-copy.centered{margin:0 auto 48px;text-align:center}.section-copy h2,.proof-card h2{margin:0;color:var(--color-snow);font-size:clamp(30px,4vw,var(--text-heading));font-weight:var(--font-weight-regular);letter-spacing:var(--tracking-heading);line-height:var(--leading-heading)}.section-copy p{margin:16px 0 0}.problem{display:grid;grid-template-columns:1fr 1fr;gap:64px;align-items:start}.problem-grid,.audience-grid,.faq-grid{display:grid;gap:16px}.problem-grid article,.audience-grid article,.faq-grid article,.step-card,.feature-card,.install-card,.proof-card{border:1px solid var(--color-charcoal);border-radius:var(--radius-cards);background:var(--color-obsidian);padding:var(--card-padding)}.problem-grid article{color:var(--color-silver-mist)}.steps-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px}.step-number{color:var(--color-smoke);font-family:var(--font-source-code-pro);font-size:var(--text-caption);letter-spacing:.1em}.step-card h3,.feature-card h3,.audience-grid h3,.install-card h3,.faq-grid h3{margin:14px 0 8px;color:var(--color-snow);font-size:var(--text-subheading);font-weight:var(--font-weight-medium);letter-spacing:var(--tracking-subheading);line-height:var(--leading-subheading)}.step-card .code{margin:18px 0}.feature-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px}.feature-card{display:flex;min-height:356px;flex-direction:column}.feature-icon .logo-mark{width:26px;height:26px}.line-art{position:relative;display:grid;grid-template-columns:repeat(3,1fr);gap:10px;min-height:116px;margin-top:auto;overflow:hidden;border:1px solid var(--color-charcoal);border-radius:var(--radius-inputs);background:#0d0d0d;padding:18px}.line-art span{border:1px solid var(--color-graphite);border-radius:var(--radius-inputs);transform:skewY(-8deg)}.line-art span:nth-child(2){border-color:var(--color-phosphor-green);animation:pulse-border 1.8s ease-in-out infinite}.feature-card .code{margin-top:16px}.audience-section{display:grid;grid-template-columns:.9fr 1.1fr;gap:48px;align-items:start}.install-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:16px}.install-card h3{margin-top:0}.install-card .code{min-height:74px}.install-note{margin:16px 0 0;text-align:center;font-size:var(--text-body-sm)}.proof-card{display:grid;grid-template-columns:.9fr 1.1fr;gap:48px;align-items:center;padding:40px}.storyboard{display:grid;gap:10px}.storyboard span{position:relative;display:block;padding:12px 14px 12px 36px;border:1px solid var(--color-charcoal);border-radius:var(--radius-inputs);color:var(--color-silver-mist);font-size:var(--text-body-sm)}.storyboard span:before{content:"";position:absolute;top:17px;left:14px;width:8px;height:8px;border-radius:9999px;background:var(--color-phosphor-green)}.faq-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.footer{position:relative;z-index:1;justify-content:space-between;width:min(var(--page-max-width),calc(100vw - 48px));margin:0 auto;padding:48px 0 64px;border-top:1px solid var(--color-charcoal)}.footer p{margin:0;font-size:var(--text-body-sm)}@keyframes fade-up{0%{opacity:0;transform:translateY(18px)}to{opacity:1;transform:translateY(0)}}@keyframes pulse-dot{0%,to{opacity:.45;transform:scale(1)}50%{opacity:1;transform:scale(1.6)}}@keyframes pulse-border{0%,to{opacity:.4}50%{opacity:1}}@keyframes scan-row{0%,38%,to{background:transparent}42%,50%{background:#3ecf8e0a}}@media (prefers-reduced-motion: reduce){*,*:before,*:after{animation-duration:1ms!important;animation-iteration-count:1!important;scroll-behavior:auto!important;transition-duration:1ms!important}}@media (max-width: 1060px){.nav-links,.stars-pill{display:none}.feature-grid,.steps-grid,.install-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.problem,.audience-section,.proof-card{grid-template-columns:1fr}}@media (max-width: 760px){.nav{height:auto;min-height:64px;padding:10px 16px}.nav-actions .btn-ghost{display:none}.section{width:min(100% - 32px,var(--page-max-width));padding:72px 0}.hero{padding-top:128px}.hero-actions{flex-direction:column}.hero-actions .btn{width:100%}.visual-grid,.feature-grid,.steps-grid,.install-grid,.faq-grid{grid-template-columns:1fr}.request{grid-template-columns:54px minmax(0,1fr)}.request em{display:none}.product-visual,.proof-card{padding:16px}.footer{align-items:flex-start;flex-direction:column;gap:18px;width:min(100% - 32px,var(--page-max-width))}}@media (max-width: 440px){.brand span{display:none}.nav-actions .btn{min-height:34px;padding:7px 12px}.hero h1{font-size:44px}}
|