@t-req/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/LICENSE +21 -0
- package/README.md +632 -0
- package/dist/client.d.ts +6 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/config/define.d.ts +6 -0
- package/dist/config/define.d.ts.map +1 -0
- package/dist/config/index.d.ts +5 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +4 -0
- package/dist/config/index.js.map +12 -0
- package/dist/config/load.d.ts +15 -0
- package/dist/config/load.d.ts.map +1 -0
- package/dist/config/merge.d.ts +8 -0
- package/dist/config/merge.d.ts.map +1 -0
- package/dist/config/types.d.ts +22 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/cookies.d.ts +21 -0
- package/dist/cookies.d.ts.map +1 -0
- package/dist/cookies.js +45 -0
- package/dist/cookies.js.map +12 -0
- package/dist/engine/engine.d.ts +31 -0
- package/dist/engine/engine.d.ts.map +1 -0
- package/dist/engine/index.d.ts +2 -0
- package/dist/engine/index.d.ts.map +1 -0
- package/dist/engine/index.js +6 -0
- package/dist/engine/index.js.map +18 -0
- package/dist/execute.d.ts +13 -0
- package/dist/execute.d.ts.map +1 -0
- package/dist/file-loader.d.ts +101 -0
- package/dist/file-loader.d.ts.map +1 -0
- package/dist/form-data-builder.d.ts +64 -0
- package/dist/form-data-builder.d.ts.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +20 -0
- package/dist/interpolate.d.ts +11 -0
- package/dist/interpolate.d.ts.map +1 -0
- package/dist/parser.d.ts +16 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/runtime/auto-transport.d.ts +9 -0
- package/dist/runtime/auto-transport.d.ts.map +1 -0
- package/dist/runtime/fetch-transport.d.ts +11 -0
- package/dist/runtime/fetch-transport.d.ts.map +1 -0
- package/dist/runtime/index.d.ts +5 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +4 -0
- package/dist/runtime/index.js.map +12 -0
- package/dist/runtime/node-io.d.ts +6 -0
- package/dist/runtime/node-io.d.ts.map +1 -0
- package/dist/runtime/types.d.ts +62 -0
- package/dist/runtime/types.d.ts.map +1 -0
- package/dist/types.d.ts +297 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils/optional.d.ts +40 -0
- package/dist/utils/optional.d.ts.map +1 -0
- package/package.json +92 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,632 @@
|
|
|
1
|
+
# @t-req/core
|
|
2
|
+
|
|
3
|
+
HTTP request parsing, execution, and testing. Define requests in `.http` files, test them in isolation.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@t-req/core)
|
|
6
|
+
[](https://www.npmjs.com/package/@t-req/core)
|
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
|
8
|
+
[](https://www.typescriptlang.org/)
|
|
9
|
+
|
|
10
|
+
**Runtimes:** Node (>=18) and Bun. The engine is also renderer-safe for desktop apps (e.g. Tauri) when you execute from in-memory `.http` content via `runString()`.
|
|
11
|
+
|
|
12
|
+
## Features
|
|
13
|
+
|
|
14
|
+
- **Parse `.http` files** - Standard format used by VS Code REST Client, JetBrains HTTP Client
|
|
15
|
+
- **Variable interpolation** - `{{variable}}` syntax with custom resolvers
|
|
16
|
+
- **Native fetch Response** - Returns standard `Response` objects, no wrapper
|
|
17
|
+
- **Cookie management** - Automatic cookie jar with RFC 6265 compliance
|
|
18
|
+
- **Timeout & cancellation** - Built-in timeout and AbortSignal support
|
|
19
|
+
- **TypeScript first** - Full type definitions included
|
|
20
|
+
|
|
21
|
+
## Philosophy
|
|
22
|
+
|
|
23
|
+
**Requests are just code.** No DSL, no hidden state machines. Each `.http` file contains one request, and you orchestrate them with standard JavaScript:
|
|
24
|
+
|
|
25
|
+
> Note: `client.run('./file.http')` reads from disk. In **Node**, pass `io: createNodeIO()` when creating the client. In **Tauri/desktop**, prefer `client.runString()` from the editor buffer, or provide an IO adapter that bridges to your backend.
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
// Login and get token
|
|
29
|
+
const login = await client.run('./auth/login.http');
|
|
30
|
+
const { token } = await login.json();
|
|
31
|
+
|
|
32
|
+
// Use token for subsequent requests
|
|
33
|
+
client.setVariable('token', token);
|
|
34
|
+
|
|
35
|
+
// Fetch profile
|
|
36
|
+
const profile = await client.run('./users/profile.http');
|
|
37
|
+
|
|
38
|
+
// Standard control flow for complex scenarios
|
|
39
|
+
for (const id of userIds) {
|
|
40
|
+
await client.run('./users/get.http', { variables: { userId: id } });
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Installation
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# Runtime: Node (>=18) or Bun
|
|
48
|
+
# npm
|
|
49
|
+
npm install @t-req/core
|
|
50
|
+
|
|
51
|
+
# bun
|
|
52
|
+
bun add @t-req/core
|
|
53
|
+
|
|
54
|
+
#yarn
|
|
55
|
+
yarn add @t-req/core
|
|
56
|
+
|
|
57
|
+
# pnpm
|
|
58
|
+
pnpm add @t-req/core
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Quick Start
|
|
62
|
+
|
|
63
|
+
Create a `.http` file:
|
|
64
|
+
|
|
65
|
+
```http
|
|
66
|
+
# auth/login.http
|
|
67
|
+
POST https://api.example.com/auth/login
|
|
68
|
+
Content-Type: application/json
|
|
69
|
+
|
|
70
|
+
{"email": "{{email}}", "password": "{{password}}"}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Run it:
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
import { createClient } from '@t-req/core';
|
|
77
|
+
import { createNodeIO } from '@t-req/core/runtime';
|
|
78
|
+
|
|
79
|
+
const client = createClient({
|
|
80
|
+
// Required in Node to run from files.
|
|
81
|
+
// In Bun, you can omit this and the library will use Bun's filesystem APIs.
|
|
82
|
+
io: createNodeIO(),
|
|
83
|
+
variables: {
|
|
84
|
+
email: 'user@example.com',
|
|
85
|
+
password: 'secret',
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const response = await client.run('./auth/login.http');
|
|
90
|
+
const { token } = await response.json();
|
|
91
|
+
|
|
92
|
+
console.log('Logged in, token:', token);
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
If you're running inside an editor/desktop app (e.g. Tauri), prefer `runString()` (no filesystem needed):
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
import { createClient } from '@t-req/core';
|
|
99
|
+
|
|
100
|
+
const client = createClient();
|
|
101
|
+
|
|
102
|
+
const response = await client.runString(
|
|
103
|
+
`POST https://api.example.com/auth/login
|
|
104
|
+
Content-Type: application/json
|
|
105
|
+
|
|
106
|
+
{"email":"{{email}}","password":"{{password}}"}
|
|
107
|
+
`,
|
|
108
|
+
{ variables: { email: 'user@example.com', password: 'secret' } }
|
|
109
|
+
);
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## API Reference
|
|
113
|
+
|
|
114
|
+
### Client
|
|
115
|
+
|
|
116
|
+
The primary way to execute requests. Handles variable interpolation, cookies, and request execution.
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
import { createClient } from '@t-req/core';
|
|
120
|
+
import { createNodeIO } from '@t-req/core/runtime';
|
|
121
|
+
import { createCookieJar } from '@t-req/core/cookies';
|
|
122
|
+
|
|
123
|
+
const client = createClient({
|
|
124
|
+
// Required in Node to run from files.
|
|
125
|
+
// In Bun, you can omit this and the library will use Bun's filesystem APIs.
|
|
126
|
+
io: createNodeIO(),
|
|
127
|
+
// Variables available to all requests
|
|
128
|
+
variables: {
|
|
129
|
+
baseUrl: 'https://api.example.com',
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
// Custom resolvers for dynamic values
|
|
133
|
+
resolvers: {
|
|
134
|
+
$env: (key) => process.env[key] || '',
|
|
135
|
+
$timestamp: () => String(Date.now()),
|
|
136
|
+
$uuid: () => crypto.randomUUID(),
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
// Automatic cookie handling
|
|
140
|
+
cookieJar: createCookieJar(),
|
|
141
|
+
|
|
142
|
+
// Default timeout for all requests (ms)
|
|
143
|
+
timeout: 30000,
|
|
144
|
+
|
|
145
|
+
// Default settings
|
|
146
|
+
defaults: {
|
|
147
|
+
headers: { 'User-Agent': 'my-app/1.0' },
|
|
148
|
+
followRedirects: true,
|
|
149
|
+
validateSSL: true,
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Run a request from a .http file
|
|
154
|
+
const response = await client.run('./api/users.http');
|
|
155
|
+
|
|
156
|
+
// Run with additional variables
|
|
157
|
+
const response = await client.run('./api/user.http', {
|
|
158
|
+
variables: { userId: '123' },
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// Run with timeout override
|
|
162
|
+
const response = await client.run('./api/slow.http', {
|
|
163
|
+
timeout: 60000,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Run with AbortSignal for cancellation
|
|
167
|
+
const controller = new AbortController();
|
|
168
|
+
const response = await client.run('./api/users.http', {
|
|
169
|
+
signal: controller.signal,
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// Run from in-memory `.http` content (great for editors/TUI/desktop)
|
|
173
|
+
const res2 = await client.runString(
|
|
174
|
+
`GET {{baseUrl}}/users
|
|
175
|
+
Accept: application/json
|
|
176
|
+
`,
|
|
177
|
+
{ variables: { baseUrl: 'https://api.example.com' } }
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
// Update variables at runtime
|
|
181
|
+
client.setVariable('token', 'new-token');
|
|
182
|
+
client.setVariables({ a: 1, b: 2 });
|
|
183
|
+
console.log(client.getVariables());
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Response
|
|
187
|
+
|
|
188
|
+
`client.run()` returns a native [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) object:
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
const response = await client.run('./api/users.http');
|
|
192
|
+
|
|
193
|
+
// Standard Response properties
|
|
194
|
+
console.log(response.status); // 200
|
|
195
|
+
console.log(response.statusText); // "OK"
|
|
196
|
+
console.log(response.ok); // true
|
|
197
|
+
console.log(response.headers); // Headers object
|
|
198
|
+
|
|
199
|
+
// Standard Response methods (async)
|
|
200
|
+
const json = await response.json();
|
|
201
|
+
const text = await response.text();
|
|
202
|
+
const blob = await response.blob();
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Parsing
|
|
206
|
+
|
|
207
|
+
Parse `.http` file content into structured request objects. Useful for inspection or custom execution.
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
import { parse, parseFileWithIO } from '@t-req/core';
|
|
211
|
+
import { createNodeIO } from '@t-req/core/runtime';
|
|
212
|
+
|
|
213
|
+
// Parse string content
|
|
214
|
+
const requests = parse(`
|
|
215
|
+
### Get Users
|
|
216
|
+
GET https://api.example.com/users
|
|
217
|
+
Authorization: Bearer token
|
|
218
|
+
|
|
219
|
+
### Create User
|
|
220
|
+
POST https://api.example.com/users
|
|
221
|
+
Content-Type: application/json
|
|
222
|
+
|
|
223
|
+
{"name": "John", "email": "john@example.com"}
|
|
224
|
+
`);
|
|
225
|
+
|
|
226
|
+
// Parse from file (Node example)
|
|
227
|
+
const io = createNodeIO();
|
|
228
|
+
const requests = await parseFileWithIO('./api.http', io);
|
|
229
|
+
|
|
230
|
+
// Access parsed request
|
|
231
|
+
console.log(requests[0].name); // "Get Users"
|
|
232
|
+
console.log(requests[0].method); // "GET"
|
|
233
|
+
console.log(requests[0].url); // "https://api.example.com/users"
|
|
234
|
+
console.log(requests[0].headers); // { Authorization: "Bearer token" }
|
|
235
|
+
console.log(requests[0].body); // undefined
|
|
236
|
+
console.log(requests[0].meta); // { } - meta directives from # @key value
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Filesystem (IO adapters)
|
|
240
|
+
|
|
241
|
+
Any feature that reads from disk (like `client.run('./file.http')`, `< ./payload.json`, or `@./file` uploads) requires filesystem access:
|
|
242
|
+
|
|
243
|
+
- **Bun**: works out of the box (uses Bun's filesystem APIs when you don't provide an IO adapter).
|
|
244
|
+
- **Node**: pass `io: createNodeIO()` to `createClient()` or `createEngine()`.
|
|
245
|
+
- **Tauri desktop**: your renderer should use `runString()` for editor buffers; for `runFile()` you must provide an IO adapter that calls your Tauri commands (we recommend enforcing workspace root access in the backend).
|
|
246
|
+
|
|
247
|
+
### Interpolation
|
|
248
|
+
|
|
249
|
+
Replace `{{variables}}` in strings or objects.
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
import { interpolate, createInterpolator } from '@t-req/core';
|
|
253
|
+
|
|
254
|
+
// Simple interpolation
|
|
255
|
+
const result = interpolate('Hello {{name}}!', { name: 'World' });
|
|
256
|
+
// "Hello World!"
|
|
257
|
+
|
|
258
|
+
// Nested object paths
|
|
259
|
+
const result = interpolate('User: {{user.name}}', {
|
|
260
|
+
user: { name: 'John' }
|
|
261
|
+
});
|
|
262
|
+
// "User: John"
|
|
263
|
+
|
|
264
|
+
// Custom resolvers for dynamic values
|
|
265
|
+
const interp = createInterpolator({
|
|
266
|
+
resolvers: {
|
|
267
|
+
$env: (key) => process.env[key] || '',
|
|
268
|
+
$timestamp: () => String(Date.now()),
|
|
269
|
+
$random: (min = '0', max = '100') =>
|
|
270
|
+
String(Math.floor(Math.random() * (Number(max) - Number(min) + 1)) + Number(min)),
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
const result = await interp.interpolate(
|
|
275
|
+
'KEY={{$env(API_KEY)}}&t={{$timestamp()}}&r={{$random(1,10)}}',
|
|
276
|
+
{}
|
|
277
|
+
);
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Cookie Jar
|
|
281
|
+
|
|
282
|
+
Manage cookies across requests with persistence support. Uses [tough-cookie](https://github.com/salesforce/tough-cookie) internally for RFC 6265-ish behavior and edge-case handling.
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
import { createCookieJar, CookieJar } from '@t-req/core/cookies';
|
|
286
|
+
|
|
287
|
+
const jar = createCookieJar();
|
|
288
|
+
|
|
289
|
+
// Set cookies from a Set-Cookie header
|
|
290
|
+
jar.setCookieSync('session=abc123; Path=/', 'https://example.com/');
|
|
291
|
+
|
|
292
|
+
// Read cookies for a URL
|
|
293
|
+
const cookies = jar.getCookiesSync('https://example.com/api');
|
|
294
|
+
console.log(cookies.map((c) => `${c.key}=${c.value}`));
|
|
295
|
+
|
|
296
|
+
// Get the Cookie header string for a request
|
|
297
|
+
const cookieHeader = jar.getCookieStringSync('https://example.com/api');
|
|
298
|
+
// "session=abc123"
|
|
299
|
+
|
|
300
|
+
// Persist the jar (tough-cookie native format)
|
|
301
|
+
const snapshot = jar.serializeSync();
|
|
302
|
+
await Bun.write('./cookies.json', JSON.stringify(snapshot, null, 2)); // Bun example
|
|
303
|
+
|
|
304
|
+
// Node example:
|
|
305
|
+
// import { writeFile, readFile } from 'node:fs/promises';
|
|
306
|
+
// await writeFile('./cookies.json', JSON.stringify(snapshot, null, 2), 'utf8');
|
|
307
|
+
|
|
308
|
+
// Restore into a fresh jar
|
|
309
|
+
const loaded = JSON.parse(await Bun.file('./cookies.json').text());
|
|
310
|
+
const jar2 = CookieJar.deserializeSync(loaded);
|
|
311
|
+
console.log(jar2.getCookieStringSync('https://example.com/api'));
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
#### Security Features
|
|
315
|
+
|
|
316
|
+
- **Domain scope validation**: Cookies can only be set for the request domain or its parent domains
|
|
317
|
+
- **Public suffix protection**: Rejects cookies for public suffixes like `.com`, `.co.uk`, `.github.io`, etc. (enabled by default)
|
|
318
|
+
- **Secure cookie enforcement**: Secure cookies are only accepted from HTTPS origins and only sent over HTTPS
|
|
319
|
+
- **RFC 6265 ordering**: Cookies are sorted by path length (longest first), then by creation time
|
|
320
|
+
|
|
321
|
+
#### Public suffix compatibility mode
|
|
322
|
+
|
|
323
|
+
If you need compatibility with servers that incorrectly set cookies for public suffixes, you can opt out (not recommended):
|
|
324
|
+
|
|
325
|
+
```typescript
|
|
326
|
+
import { createCookieJar } from '@t-req/core/cookies';
|
|
327
|
+
|
|
328
|
+
const jar = createCookieJar({ rejectPublicSuffixes: false });
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
## Engine (advanced)
|
|
332
|
+
|
|
333
|
+
The engine centralizes parsing/interpolation/compilation/execution behind explicit runtime adapters. This is useful for building a TUI/desktop/agent that needs structured events and `runString()`.
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
import { createEngine } from '@t-req/core';
|
|
337
|
+
import { createFetchTransport } from '@t-req/core/runtime';
|
|
338
|
+
|
|
339
|
+
const engine = createEngine({
|
|
340
|
+
transport: createFetchTransport(fetch),
|
|
341
|
+
onEvent: (e) => console.log(e)
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
await engine.runString('GET https://example.com\n');
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
## Config (`treq.config.ts`)
|
|
348
|
+
|
|
349
|
+
Define typed config:
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
// treq.config.ts
|
|
353
|
+
import { defineConfig } from '@t-req/core/config';
|
|
354
|
+
|
|
355
|
+
export default defineConfig({
|
|
356
|
+
variables: { baseUrl: 'https://api.example.com' },
|
|
357
|
+
defaults: { timeoutMs: 30000, headers: { 'User-Agent': 't-req' } }
|
|
358
|
+
});
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
Load and apply it in Node/Bun tooling:
|
|
362
|
+
|
|
363
|
+
```typescript
|
|
364
|
+
import { loadConfig, mergeConfig } from '@t-req/core/config';
|
|
365
|
+
import { createClient } from '@t-req/core';
|
|
366
|
+
import { createNodeIO } from '@t-req/core/runtime';
|
|
367
|
+
|
|
368
|
+
const { config } = await loadConfig({ startDir: process.cwd() });
|
|
369
|
+
const merged = mergeConfig({ file: config });
|
|
370
|
+
|
|
371
|
+
const client = createClient({
|
|
372
|
+
// Required in Node to run from files.
|
|
373
|
+
io: createNodeIO(),
|
|
374
|
+
variables: merged.variables,
|
|
375
|
+
resolvers: merged.resolvers,
|
|
376
|
+
defaults: merged.defaults
|
|
377
|
+
});
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
## Real-World Example: E-Commerce Checkout
|
|
381
|
+
|
|
382
|
+
Run the included demo flow (uses dummyjson.com) to see a realistic multi-step scenario:
|
|
383
|
+
|
|
384
|
+
```bash
|
|
385
|
+
bun examples/e-commerce/checkout-flow.ts
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
See [`examples/e-commerce/`](./examples/e-commerce/) for the `.http` files and the orchestration script.
|
|
389
|
+
|
|
390
|
+
This shows the two patterns:
|
|
391
|
+
- **`setVariable`**: For values extracted from responses that subsequent requests need (`token`, `userId`, `cartId`)
|
|
392
|
+
- **Inline `variables`**: For one-off overrides (`productId`, `quantity`, pagination params)
|
|
393
|
+
|
|
394
|
+
See [`examples/e-commerce/`](./examples/e-commerce/) for a working version using dummyjson.com.
|
|
395
|
+
|
|
396
|
+
## Common Patterns
|
|
397
|
+
|
|
398
|
+
### Retry Logic
|
|
399
|
+
|
|
400
|
+
```typescript
|
|
401
|
+
async function withRetry<T>(
|
|
402
|
+
fn: () => Promise<T>,
|
|
403
|
+
retries = 3,
|
|
404
|
+
delay = 1000
|
|
405
|
+
): Promise<T> {
|
|
406
|
+
for (let i = 0; i < retries; i++) {
|
|
407
|
+
try {
|
|
408
|
+
return await fn();
|
|
409
|
+
} catch (error) {
|
|
410
|
+
if (i === retries - 1) throw error;
|
|
411
|
+
await new Promise(r => setTimeout(r, delay));
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
throw new Error('Unreachable');
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const response = await withRetry(() =>
|
|
418
|
+
client.run('./api/flaky-endpoint.http')
|
|
419
|
+
);
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### Parallel Requests
|
|
423
|
+
|
|
424
|
+
```typescript
|
|
425
|
+
const [users, posts, comments] = await Promise.all([
|
|
426
|
+
client.run('./api/users.http'),
|
|
427
|
+
client.run('./api/posts.http'),
|
|
428
|
+
client.run('./api/comments.http'),
|
|
429
|
+
]);
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
### Cleanup with try/finally
|
|
433
|
+
|
|
434
|
+
```typescript
|
|
435
|
+
try {
|
|
436
|
+
await client.run('./setup.http');
|
|
437
|
+
await client.run('./test.http');
|
|
438
|
+
} finally {
|
|
439
|
+
await client.run('./cleanup.http');
|
|
440
|
+
}
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
### Request Timing
|
|
444
|
+
|
|
445
|
+
```typescript
|
|
446
|
+
const start = performance.now();
|
|
447
|
+
const response = await client.run('./api/users.http');
|
|
448
|
+
const duration = performance.now() - start;
|
|
449
|
+
|
|
450
|
+
console.log(`Request took ${duration.toFixed(0)}ms`);
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
## TypeScript Support
|
|
454
|
+
|
|
455
|
+
All types are exported:
|
|
456
|
+
|
|
457
|
+
```typescript
|
|
458
|
+
import type {
|
|
459
|
+
// Parsing
|
|
460
|
+
ParsedRequest,
|
|
461
|
+
FileReference,
|
|
462
|
+
FormField,
|
|
463
|
+
|
|
464
|
+
// Interpolation
|
|
465
|
+
InterpolateOptions,
|
|
466
|
+
Interpolator,
|
|
467
|
+
Resolver,
|
|
468
|
+
|
|
469
|
+
// Execution
|
|
470
|
+
ExecuteRequest,
|
|
471
|
+
ExecuteOptions,
|
|
472
|
+
|
|
473
|
+
// Client
|
|
474
|
+
Client,
|
|
475
|
+
ClientConfig,
|
|
476
|
+
RunOptions,
|
|
477
|
+
|
|
478
|
+
// File loading
|
|
479
|
+
FileLoaderOptions,
|
|
480
|
+
LoadedFile,
|
|
481
|
+
|
|
482
|
+
// Form data building
|
|
483
|
+
BuildFormDataOptions,
|
|
484
|
+
} from '@t-req/core';
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
Cookie types are exported from `@t-req/core/cookies`:
|
|
488
|
+
|
|
489
|
+
```typescript
|
|
490
|
+
import type { Cookie, CookieJar } from '@t-req/core/cookies';
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
## .http File Format
|
|
494
|
+
|
|
495
|
+
The library supports the standard `.http` file format:
|
|
496
|
+
|
|
497
|
+
```http
|
|
498
|
+
### Request Name
|
|
499
|
+
# @name requestId
|
|
500
|
+
# @description Optional description
|
|
501
|
+
GET https://api.example.com/users/{{userId}}
|
|
502
|
+
Authorization: Bearer {{token}}
|
|
503
|
+
Content-Type: application/json
|
|
504
|
+
|
|
505
|
+
###
|
|
506
|
+
|
|
507
|
+
POST https://api.example.com/users
|
|
508
|
+
Content-Type: application/json
|
|
509
|
+
|
|
510
|
+
{
|
|
511
|
+
"name": "{{name}}",
|
|
512
|
+
"email": "{{email}}"
|
|
513
|
+
}
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
### Format Rules
|
|
517
|
+
|
|
518
|
+
- Requests are separated by `###`
|
|
519
|
+
- Request names can be specified with `### Name` or `# @name name`
|
|
520
|
+
- Comments start with `#` or `//`
|
|
521
|
+
- Meta directives use `# @directive value`
|
|
522
|
+
- Headers follow the request line
|
|
523
|
+
- Body starts after an empty line
|
|
524
|
+
- Variables use `{{variable}}` syntax
|
|
525
|
+
|
|
526
|
+
### File References
|
|
527
|
+
|
|
528
|
+
Load request body from an external file:
|
|
529
|
+
|
|
530
|
+
```http
|
|
531
|
+
POST https://api.example.com/data
|
|
532
|
+
Content-Type: application/json
|
|
533
|
+
|
|
534
|
+
< ./fixtures/payload.json
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
The file path is relative to the `.http` file location. Content-Type is automatically inferred from the file extension if not specified. Binary files (images, PDFs, etc.) are handled correctly.
|
|
538
|
+
|
|
539
|
+
### Form Data
|
|
540
|
+
|
|
541
|
+
Simple syntax for forms and file uploads:
|
|
542
|
+
|
|
543
|
+
```http
|
|
544
|
+
POST https://api.example.com/upload
|
|
545
|
+
|
|
546
|
+
title = Quarterly Report
|
|
547
|
+
description = Q4 2025 summary
|
|
548
|
+
document = @./reports/q4-2025.pdf
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
**Syntax:**
|
|
552
|
+
- `field = value` — text field (spaces around `=` optional)
|
|
553
|
+
- `field = @./path` — file upload
|
|
554
|
+
- `field = @./path | custom.pdf` — file with custom filename
|
|
555
|
+
|
|
556
|
+
**Content-Type is inferred:**
|
|
557
|
+
- Files present → `multipart/form-data`
|
|
558
|
+
- Text only → `application/x-www-form-urlencoded`
|
|
559
|
+
|
|
560
|
+
For URL-encoded login:
|
|
561
|
+
|
|
562
|
+
```http
|
|
563
|
+
POST https://api.example.com/login
|
|
564
|
+
|
|
565
|
+
username = {{user}}
|
|
566
|
+
password = {{pass}}
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
Variables work in field values, file paths, and custom filenames.
|
|
570
|
+
|
|
571
|
+
### Best Practice: One Request Per File
|
|
572
|
+
|
|
573
|
+
For testability and clarity, we recommend one request per file:
|
|
574
|
+
|
|
575
|
+
```
|
|
576
|
+
requests/
|
|
577
|
+
├── auth/
|
|
578
|
+
│ ├── login.http
|
|
579
|
+
│ ├── logout.http
|
|
580
|
+
│ └── refresh.http
|
|
581
|
+
├── users/
|
|
582
|
+
│ ├── create.http
|
|
583
|
+
│ ├── get.http
|
|
584
|
+
│ ├── update.http
|
|
585
|
+
│ └── delete.http
|
|
586
|
+
└── orders/
|
|
587
|
+
├── create.http
|
|
588
|
+
└── list.http
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
This makes each request independently executable and testable.
|
|
592
|
+
|
|
593
|
+
## Error Handling
|
|
594
|
+
|
|
595
|
+
```typescript
|
|
596
|
+
// Parsing errors throw
|
|
597
|
+
try {
|
|
598
|
+
const requests = parse('not valid http');
|
|
599
|
+
} catch (e) {
|
|
600
|
+
// ParseError
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// Network errors throw
|
|
604
|
+
try {
|
|
605
|
+
await client.run('./api/unreachable.http');
|
|
606
|
+
} catch (e) {
|
|
607
|
+
// Network error or timeout
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// Non-2xx is NOT an error - check response.ok
|
|
611
|
+
const response = await client.run('./api/users.http');
|
|
612
|
+
if (!response.ok) {
|
|
613
|
+
console.log('Request failed:', response.status);
|
|
614
|
+
const error = await response.json();
|
|
615
|
+
console.log('Error:', error);
|
|
616
|
+
}
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
## Contributing
|
|
620
|
+
|
|
621
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md) for development setup and guidelines.
|
|
622
|
+
|
|
623
|
+
## Publishing
|
|
624
|
+
|
|
625
|
+
This is a scoped package (`@t-req/core`). The repo is configured to publish it as **public**.
|
|
626
|
+
|
|
627
|
+
- **Local publish**: `bun publish --access public`
|
|
628
|
+
- **CI publish**: push a tag like `v0.1.0` (see `.github/workflows/release.yml`)
|
|
629
|
+
|
|
630
|
+
## License
|
|
631
|
+
|
|
632
|
+
MIT
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAc,MAAM,SAAS,CAAC;AAGhE;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,GAAE,YAAiB,GAAG,MAAM,CAmE9D"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"define.d.ts","sourceRoot":"","sources":["../../src/config/define.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE1C;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,UAAU,CAE3D"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { defineConfig } from './define';
|
|
2
|
+
export { type LoadConfigOptions, loadConfig } from './load';
|
|
3
|
+
export { type MergeConfigInputs, mergeConfig } from './merge';
|
|
4
|
+
export type { LoadedConfig, TreqConfig, TreqDefaults } from './types';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,KAAK,iBAAiB,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAC5D,OAAO,EAAE,KAAK,iBAAiB,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAC9D,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
function q(e){return e}import{access as b}from"node:fs/promises";import*as f from"node:path";import{pathToFileURL as g}from"node:url";var k="treq.config.ts";async function x(e){try{return await b(e),!0}catch{return!1}}async function T(e,l,C){let r=f.resolve(e),v=C?f.resolve(C):void 0;while(!0){let u=f.join(r,l);if(await x(u))return u;if(v&&r===v)return;let s=f.dirname(r);if(s===r)return;r=s}}async function y(e){let r=(await import(g(e).href)).default;if(!r||typeof r!=="object")throw Error(`Invalid config export from ${e}. Expected default object export.`);return r}async function d(e){let l="filename"in e&&e.filename?e.filename:k,C="path"in e?f.resolve(e.path):await T(e.startDir,l,e.stopDir);if(!C)return{config:{}};return{path:C,config:await y(C)}}function m(e){let l=e.defaults??{},C=e.file??{},r=e.overrides??{};return{variables:{...l.variables??{},...C.variables??{},...r.variables??{}},resolvers:{...l.resolvers??{},...C.resolvers??{},...r.resolvers??{}},defaults:{...l.defaults??{},...C.defaults??{},...r.defaults??{},headers:{...l.defaults?.headers??{},...C.defaults?.headers??{},...r.defaults?.headers??{}}},cookies:{...l.cookies??{},...C.cookies??{},...r.cookies??{}}}}export{m as mergeConfig,d as loadConfig,q as defineConfig};
|
|
2
|
+
|
|
3
|
+
//# debugId=52C8953ED3C84A5E64756E2164756E21
|
|
4
|
+
//# sourceMappingURL=index.js.map
|