@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.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +632 -0
  3. package/dist/client.d.ts +6 -0
  4. package/dist/client.d.ts.map +1 -0
  5. package/dist/config/define.d.ts +6 -0
  6. package/dist/config/define.d.ts.map +1 -0
  7. package/dist/config/index.d.ts +5 -0
  8. package/dist/config/index.d.ts.map +1 -0
  9. package/dist/config/index.js +4 -0
  10. package/dist/config/index.js.map +12 -0
  11. package/dist/config/load.d.ts +15 -0
  12. package/dist/config/load.d.ts.map +1 -0
  13. package/dist/config/merge.d.ts +8 -0
  14. package/dist/config/merge.d.ts.map +1 -0
  15. package/dist/config/types.d.ts +22 -0
  16. package/dist/config/types.d.ts.map +1 -0
  17. package/dist/cookies.d.ts +21 -0
  18. package/dist/cookies.d.ts.map +1 -0
  19. package/dist/cookies.js +45 -0
  20. package/dist/cookies.js.map +12 -0
  21. package/dist/engine/engine.d.ts +31 -0
  22. package/dist/engine/engine.d.ts.map +1 -0
  23. package/dist/engine/index.d.ts +2 -0
  24. package/dist/engine/index.d.ts.map +1 -0
  25. package/dist/engine/index.js +6 -0
  26. package/dist/engine/index.js.map +18 -0
  27. package/dist/execute.d.ts +13 -0
  28. package/dist/execute.d.ts.map +1 -0
  29. package/dist/file-loader.d.ts +101 -0
  30. package/dist/file-loader.d.ts.map +1 -0
  31. package/dist/form-data-builder.d.ts +64 -0
  32. package/dist/form-data-builder.d.ts.map +1 -0
  33. package/dist/index.d.ts +9 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +6 -0
  36. package/dist/index.js.map +20 -0
  37. package/dist/interpolate.d.ts +11 -0
  38. package/dist/interpolate.d.ts.map +1 -0
  39. package/dist/parser.d.ts +16 -0
  40. package/dist/parser.d.ts.map +1 -0
  41. package/dist/runtime/auto-transport.d.ts +9 -0
  42. package/dist/runtime/auto-transport.d.ts.map +1 -0
  43. package/dist/runtime/fetch-transport.d.ts +11 -0
  44. package/dist/runtime/fetch-transport.d.ts.map +1 -0
  45. package/dist/runtime/index.d.ts +5 -0
  46. package/dist/runtime/index.d.ts.map +1 -0
  47. package/dist/runtime/index.js +4 -0
  48. package/dist/runtime/index.js.map +12 -0
  49. package/dist/runtime/node-io.d.ts +6 -0
  50. package/dist/runtime/node-io.d.ts.map +1 -0
  51. package/dist/runtime/types.d.ts +62 -0
  52. package/dist/runtime/types.d.ts.map +1 -0
  53. package/dist/types.d.ts +297 -0
  54. package/dist/types.d.ts.map +1 -0
  55. package/dist/utils/optional.d.ts +40 -0
  56. package/dist/utils/optional.d.ts.map +1 -0
  57. 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
+ [![npm version](https://img.shields.io/npm/v/@t-req/core.svg)](https://www.npmjs.com/package/@t-req/core)
6
+ [![npm downloads](https://img.shields.io/npm/dm/@t-req/core.svg)](https://www.npmjs.com/package/@t-req/core)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg)](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
@@ -0,0 +1,6 @@
1
+ import type { Client, ClientConfig } from './types';
2
+ /**
3
+ * Create a high-level HTTP client
4
+ */
5
+ export declare function createClient(config?: ClientConfig): Client;
6
+ //# sourceMappingURL=client.d.ts.map
@@ -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,6 @@
1
+ import type { TreqConfig } from './types';
2
+ /**
3
+ * Typed identity helper for `treq.config.ts`.
4
+ */
5
+ export declare function defineConfig(config: TreqConfig): TreqConfig;
6
+ //# sourceMappingURL=define.d.ts.map
@@ -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