fenneckit 1.2.0 β†’ 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,547 +1,88 @@
1
- # 🦊 FennecKit: Lab-Based Testing & Development Utility
1
+ # 🦊 FennecKit
2
2
 
3
3
  ![logo](./fenneckit.png)
4
4
 
5
- ```bash
6
- npm install fenneckit@latest --save-dev
7
- npx fenneckit --help
8
- ```
9
-
10
- **English**: A Complete Storage + Security + API Testing System for Practical and Seamless Data Analysis
11
-
12
- **Zero Config** β€’ Sequential Labs β€’ Inter-Lab Data Sharing (within the same file) β€’ Store Management β€’ Encrypted Secrets β€’ Namespaces β€’ Audit Log β€’ HTTP Testing Kit
13
-
14
- **🦊Example** : soon
15
-
16
- ---
17
-
18
- ## 🎯 What is FennecKit?
19
-
20
- FennecKit is a lightweight, **zero-config** testing & development utility built around the concept of **Labs**.
21
-
22
- ### What is a "Lab"?
23
-
24
- A **Lab** is a collection of sequential tasks (a workflow) that together accomplish one objective.
25
-
26
- **Examples**:
27
- - User Registration Lab β†’ create user β†’ validate β†’ save to DB β†’ send email
28
- - Payment Processing Lab β†’ validate β†’ charge β†’ generate invoice
29
- - API Testing Lab β†’ hit endpoints β†’ verify responses β†’ check side effects
30
-
31
- ```typescript
32
- // Lab = Collection of sequential tasks
33
- await newLabs("User Registration Lab", async (kit) => {
34
- await kit.test("Validate Email", async () => {
35
- kit.done("Email validated");
36
- });
37
-
38
- await kit.test("Save to Database", async () => {
39
- kit.done("User saved");
40
- });
41
-
42
- await kit.test("Send Verification Email", async () => {
43
- kit.done("Email sent");
44
- });
45
- });
46
- ```
47
-
48
- ---
49
-
50
- ## ⚑ Zero Config & Runner
51
-
52
- FennecKit requires **no configuration files**.
53
-
54
- ### How to run
55
-
56
- ```bash
57
- # Run a specific lab file
58
- npx fenneckit lab.js
59
-
60
- # Or just
61
- npx fenneckit
62
-
63
- # The runner automatically finds all *.labs.js / *.labs.ts files
64
- # and executes them one after another (sequentially)
65
- ```
66
-
67
- **What the runner does**:
68
- 1. Discovers lab files in the current directory (and subdirectories if configured)
69
- 2. Runs each file **one by one**
70
- 3. Inside each file, Labs run in the order they are written
71
- 4. Generates a report (`fenneckit.md`) after execution
72
-
73
- > **Important limitation**
74
- > Data sharing (`setStore` / `getStore` / secrets / namespaces) only works **inside the same file**.
75
- > Different lab files **cannot** share STORE, TEMP, Secrets or Namespaces with each other.
76
-
77
- ---
78
-
79
- ## 🌳 Data Sharing Hierarchy (Tree Structure)
80
-
81
- ```
82
- πŸ“ FennecKit Execution
83
- β”‚
84
- β”œβ”€ πŸ“„ file1.labs.js (STORE Instance #1 + SecretVault + Namespaces)
85
- β”‚ β”‚
86
- β”‚ β”œβ”€ πŸ”¬ Lab 1 (User Registration)
87
- β”‚ β”‚ β”œβ”€ πŸ“ Test 1: Create User
88
- β”‚ β”‚ β”‚ β”œβ”€ STORE: {"userId": "123"} βœ… Shared with Lab 2
89
- β”‚ β”‚ β”‚ β”œβ”€ SECRET: encrypted token βœ… Shared with Lab 2
90
- β”‚ β”‚ β”‚ β”œβ”€ NAMESPACE "user": {...} βœ… Shared with Lab 2
91
- β”‚ β”‚ β”‚ └─ TEMP: {"token": "abc"} ❌ Only here
92
- β”‚ β”‚ β”‚
93
- β”‚ β”‚ └─ πŸ“ Test 2: Send Email
94
- β”‚ β”‚ └─ Can access STORE / SECRET / Namespaces from Test 1
95
- β”‚ β”‚
96
- β”‚ β”œβ”€ πŸ”¬ Lab 2 (Authentication)
97
- β”‚ β”‚ β”œβ”€ πŸ“ Test 1: Generate Token
98
- β”‚ β”‚ β”‚ β”œβ”€ STORE: {"userId": "123"} βœ… From Lab 1
99
- β”‚ β”‚ β”‚ β”œβ”€ SECRET / Namespace βœ… From Lab 1
100
- β”‚ β”‚ β”‚ └─ TEMP: {"token": "new"} ❌ Only here
101
- β”‚ β”‚ β”‚
102
- β”‚ β”‚ └─ πŸ“ Test 2: Verify Token
103
- β”‚ β”‚ └─ Can access STORE / SECRET / Namespaces from Labs 1 & 2
104
- β”‚ β”‚
105
- β”‚ └─ πŸ”¬ Lab 3 (Cleanup)
106
- β”‚ └─ File STORE + Secrets + Namespaces cleared when execution ends
107
- β”‚
108
- β”œβ”€ πŸ“„ file2.labs.js (Completely ISOLATED)
109
- β”‚ └─ ❌ CANNOT access anything from file1.labs.js
110
- β”‚
111
- └─ πŸ“„ file3.labs.js (Completely ISOLATED)
112
- └─ ❌ Isolated from all other files
113
- ```
114
-
115
- ### Understanding the Hierarchy
116
-
117
- **πŸ”΄ Level 1: Different Files = NO Data Sharing**
118
- Each file gets its own isolated STORE, SecretVault and Namespaces.
119
-
120
- **🟑 Level 2: Same File, Different Labs = STORE + Secrets + Namespaces Sharing**
121
- Data set in Lab 1 is available in Lab 2, Lab 3, etc. (until cleared).
122
-
123
- **🟒 Level 3: Same Lab, Different Tests = STORE + TEMP + Secrets + Namespaces Sharing**
124
- TEMP is automatically cleared when the Lab ends. Everything else remains.
125
-
126
- ---
127
-
128
- ## πŸ”— Data Communication Between Labs (Inter-Lab Communication)
129
-
130
- ### The Problem with Jest / Vitest
131
-
132
- In Jest and Vitest every test is isolated. You cannot pass data from one test to another.
133
-
134
- ### FennecKit Solution – Storage Levels (v1.2.0)
135
-
136
- ```
137
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
138
- β”‚ FennecKit Storage System (Per File) β”‚
139
- β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
140
- β”‚ β”‚
141
- β”‚ LEVEL 1: FILE SCOPE β”‚
142
- β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
143
- β”‚ β”‚ STORE (Global - Persistent within file) β”‚ β”‚
144
- β”‚ β”‚ β”œβ”€ Shared across ALL Labs in this file β”‚ β”‚
145
- β”‚ β”‚ β”œβ”€ Available until file execution ends β”‚ β”‚
146
- β”‚ β”‚ β”œβ”€ Can be manually cleared with clearStore() β”‚ β”‚
147
- β”‚ β”‚ └─ Example: userId, orderData, status β”‚ β”‚
148
- β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
149
- β”‚ β”‚
150
- β”‚ LEVEL 2: ENCRYPTED SECRETS (AES-256-GCM) β”‚
151
- β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
152
- β”‚ β”‚ SECRET VAULT β”‚ β”‚
153
- β”‚ β”‚ β”œβ”€ Encrypted at rest inside the process β”‚ β”‚
154
- β”‚ β”‚ β”œβ”€ Shared across Labs in the same file β”‚ β”‚
155
- β”‚ β”‚ β”œβ”€ setSecret / getSecret / clearSecret β”‚ β”‚
156
- β”‚ β”‚ └─ Example: auth tokens, API keys, passwords β”‚ β”‚
157
- β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
158
- β”‚ β”‚
159
- β”‚ LEVEL 3: NAMESPACES β”‚
160
- β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
161
- β”‚ β”‚ kit.namespace("user") / kit.namespace("order") β”‚ β”‚
162
- β”‚ β”‚ β”œβ”€ Isolated key-value stores β”‚ β”‚
163
- β”‚ β”‚ β”œβ”€ Shared across Labs in the same file β”‚ β”‚
164
- β”‚ β”‚ └─ Perfect for grouping related data β”‚ β”‚
165
- β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
166
- β”‚ β”‚
167
- β”‚ LEVEL 4: LAB-LOCAL SCOPE β”‚
168
- β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
169
- β”‚ β”‚ TEMP (Local - Auto-Cleaned) β”‚ β”‚
170
- β”‚ β”‚ β”œβ”€ Only available inside current Lab β”‚ β”‚
171
- β”‚ β”‚ β”œβ”€ Automatically cleared when Lab ends β”‚ β”‚
172
- β”‚ β”‚ └─ Example: timestamps, temp calculations β”‚ β”‚
173
- β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
174
- β”‚ β”‚
175
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
176
- ```
177
-
178
- ---
179
-
180
- ## πŸ” NEW in v1.2.0: Secret Vault (AES-256-GCM)
181
-
182
- Sensitive data should never live in plain STORE.
183
-
184
- ```typescript
185
- await newLabs("Auth Lab", async (kit) => {
186
- await kit.test("Login", async () => {
187
- // Encrypted storage
188
- kit.setSecret("accessToken", "eyJhbGciOiJIUzI1NiIs...");
189
- kit.setSecret("refreshToken", "rt_abc123");
190
-
191
- kit.done("Tokens stored securely");
192
- });
193
-
194
- await kit.test("Use Token", async () => {
195
- const token = kit.getSecret("accessToken"); // decrypted on the fly
196
- kit.http.setAuth("bearer", token!);
197
- kit.done("Token retrieved");
198
- });
199
-
200
- await kit.test("Cleanup", async () => {
201
- kit.clearSecret("accessToken"); // remove one key
202
- // kit.clearSecret(); // clear entire vault
203
- kit.done("Secrets cleared");
204
- });
205
- });
206
- ```
207
-
208
- **TTL Support for Secrets**
209
- ```typescript
210
- kit.setSecretWithTTL("tempToken", "abc123", 30_000); // auto-delete after 30s
211
- ```
212
-
213
- ---
214
-
215
- ## πŸ—‚οΈ NEW in v1.2.0: Namespaced Store
216
-
217
- Organize data into logical groups.
218
-
219
- ```typescript
220
- await newLabs("E-Commerce Flow", async (kit) => {
221
- const user = kit.namespace("user");
222
- const order = kit.namespace("order");
223
- const cart = kit.namespace("cart");
224
-
225
- await kit.test("Register", async () => {
226
- user.set("id", "usr_123");
227
- user.set("email", "john@example.com");
228
- kit.done("User namespace populated");
229
- });
230
-
231
- await kit.test("Create Order", async () => {
232
- order.set("id", "ord_456");
233
- order.set("total", 299.99);
234
- kit.done("Order namespace populated");
235
- });
236
-
237
- await kit.test("Read Later", async () => {
238
- const userId = kit.getNamespace("user")?.get("id"); // "usr_123"
239
- const total = order.get("total"); // 299.99
240
- kit.done(`User ${userId} ordered ${total}`);
241
- });
242
- });
243
- ```
5
+ <div align="center">
6
+
7
+ [![npm version](https://img.shields.io/npm/v/fenneckit?style=flat-square&color=3178c6&logo=npm)](https://www.npmjs.com/package/fenneckit)
8
+ [![npm downloads](https://img.shields.io/npm/dm/fenneckit?style=flat-square&logo=npm)](https://www.npmjs.com/package/fenneckit)
244
9
 
245
- ---
10
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D20-green?style=flat-square&logo=node.js)](https://nodejs.org/)
11
+ [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-3178c6?style=flat-square&logo=typescript)](https://www.typescriptlang.org/)
12
+ [![License](https://img.shields.io/badge/license-MIT-brightgreen?style=flat-square)](LICENSE)
246
13
 
247
- ## πŸ“‘ NEW in v1.2.0: HTTP Kit (API Testing)
14
+ </div>
15
+ **Lab-based testing utility.** Sequential tests that share data, zero config, encrypted secrets, and a built-in HTTP client.
248
16
 
249
- Built-in HTTP client with auth, retries, history and tracing.
17
+ ## Why FennecKit?
250
18
 
251
- ```typescript
252
- await newLabs("API Smoke Test", async (kit) => {
253
- // Set auth once
254
- kit.http.setAuth("bearer", "your-token");
255
- // or kit.http.setAuth("basic", "base64creds");
256
- // or kit.http.setAuth("api-key", "key123");
19
+ In Jest/Vitest every test starts fresh, so multi-step workflows (create user β†’ read user β†’ delete user) need mocks or duplicated setup. In FennecKit, tests inside one **Lab** run in order and share data.
257
20
 
258
- await kit.test("GET Users", async () => {
259
- const res = await kit.http.get("https://api.example.com/users", {
260
- timeout: 5000,
261
- retry: { max: 3, delay: 1000 }
262
- });
263
-
264
- if (res.status !== 200) {
265
- kit.err(`Expected 200, got ${res.status}`);
266
- }
267
-
268
- kit.setStore("users", res.data);
269
- kit.done(`Fetched ${res.data.length} users`);
270
- });
271
-
272
- await kit.test("POST Order", async () => {
273
- const res = await kit.http.post("https://api.example.com/orders", {
274
- productId: "prod_1",
275
- qty: 2
276
- });
277
-
278
- kit.done(`Order created: ${res.data.id}`);
279
- });
280
-
281
- // Inspect request history
282
- await kit.test("Check History", async () => {
283
- const last = kit.http.getLastRequest();
284
- kit.log(`Last request took ${last?.duration}ms`);
285
- });
286
- });
287
- ```
288
-
289
- **Available methods**:
290
- - `kit.http.get / post / put / patch / delete`
291
- - `kit.http.setAuth(type, credentials)` / `clearAuth()`
292
- - `kit.http.getRequestHistory()` / `getLastRequest()` / `clearHistory()`
293
-
294
- ---
295
-
296
- ## πŸ“‹ NEW in v1.2.0: Audit Log
297
-
298
- Every STORE / SECRET operation is automatically recorded.
299
-
300
- ```typescript
301
- await newLabs("Audit Demo", async (kit) => {
302
- await kit.test("Operations", async () => {
303
- kit.setStore("userId", "123");
304
- kit.setSecret("token", "secret");
305
- kit.getStore("userId");
306
- });
307
-
308
- await kit.test("Inspect Audit", async () => {
309
- const last5 = kit.audit.getLast(5);
310
- const byKey = kit.audit.filterByKey("token");
311
- const json = kit.audit.export("json");
312
- const csv = kit.audit.export("csv");
313
-
314
- kit.log(`Recorded ${last5.length} operations`);
315
- });
316
- });
317
- ```
318
-
319
- ---
320
-
321
- ## 🧹 clearStore() & clearSecret()
322
-
323
- ```typescript
324
- // Clear specific key
325
- kit.clearStore("authToken");
326
- kit.clearSecret("accessToken");
327
-
328
- // Clear everything
329
- kit.clearStore();
330
- kit.clearSecret();
331
- ```
332
-
333
- ---
334
-
335
- ## ⏰ TTL Support (Time-To-Live)
336
-
337
- ```typescript
338
- // Auto-expire after 10 seconds
339
- kit.setStoreWithTTL("sessionId", "sess_abc", 10_000);
340
- kit.setSecretWithTTL("tempKey", "value", 30_000);
341
- ```
342
-
343
- ---
344
-
345
- ## πŸ› οΈ LabContext Methods (v1.2.0)
346
-
347
- ```typescript
348
- interface LabContext {
349
- // Testing & Flow
350
- test(name: string, fn: () => Promise<any>): Promise<any>
351
- done(msg: string): void
352
- err(msg: string): void // stops the Lab
353
- flatErr(msg: string): void // continues
354
- log(msg: string): void
355
- warning(msg: string): void
356
-
357
- // Flow control
358
- out(): void // exit Lab immediately
359
- ret(): void // restart Lab (max 3 times)
360
-
361
- // Persistent STORE
362
- setStore(key: string, value: any): void
363
- getStore(key: string): any
364
- clearStore(key?: string): void
365
- setStoreWithTTL(key: string, value: any, ttlMs: number): void
366
-
367
- // Encrypted Secrets (AES-256-GCM)
368
- setSecret(key: string, value: string): void
369
- getSecret(key: string): string | undefined
370
- clearSecret(key?: string): void
371
- setSecretWithTTL(key: string, value: string, ttlMs: number): void
372
-
373
- // Namespaces
374
- namespace(name: string): NamespacedStore
375
- getNamespace(name: string): NamespacedStore | undefined
376
-
377
- // Temporary (Lab-local only)
378
- setTemp(key: string, value: any): void
379
- getTemp(key: string): any
380
-
381
- // HTTP Kit
382
- http: HttpKit
383
-
384
- // Audit Log
385
- audit: AuditLog
386
- }
387
- ```
388
-
389
- ---
390
-
391
- ## πŸ’‘ Real-World Example (v1.2.0)
392
-
393
- ```typescript
21
+ ```ts
394
22
  import { newLabs } from "fenneckit";
395
23
 
396
- await newLabs("E-Commerce API Test", async (kit) => {
397
- const user = kit.namespace("user");
398
- const order = kit.namespace("order");
399
-
400
- await kit.test("Register User", async () => {
401
- const res = await kit.http.post("https://api.shop.com/auth/register", {
402
- email: "test@example.com",
403
- password: "secure123"
24
+ await newLabs("User API", async (kit) => {
25
+ await kit.test("Create User", async () => {
26
+ const res = await kit.http.post("https://api.example.com/users", {
27
+ name: "John",
404
28
  });
405
-
406
- user.set("id", res.data.id);
407
- kit.setSecret("token", res.data.token); // encrypted
408
- kit.http.setAuth("bearer", res.data.token);
409
-
410
- kit.done("User registered");
411
- });
412
-
413
- await kit.test("Create Order", async () => {
414
- const res = await kit.http.post("https://api.shop.com/orders", {
415
- items: [{ id: "prod_1", qty: 2 }]
416
- });
417
-
418
- order.set("id", res.data.orderId);
419
- order.set("total", res.data.total);
420
- kit.done(`Order created: ${res.data.orderId}`);
421
- });
422
-
423
- await kit.test("Security Cleanup", async () => {
424
- kit.clearSecret("token"); // remove sensitive data
425
- kit.done("Secrets cleared");
29
+ kit.setStore("userId", res.data.id);
30
+ kit.done("User created");
426
31
  });
427
32
 
428
- await kit.test("Audit Check", async () => {
429
- const log = kit.audit.getLast(10);
430
- kit.log(`Audit entries: ${log.length}`);
33
+ await kit.test("Get User", async () => {
34
+ const res = await kit.http.get(
35
+ `https://api.example.com/users/${kit.getStore("userId")}`,
36
+ );
37
+ kit.done(`Status ${res.status}`);
431
38
  });
432
39
  });
433
40
  ```
434
41
 
435
- ---
42
+ ## Install
436
43
 
437
- ## πŸ“Š Storage Lifecycle (per file)
438
-
439
- ```
440
- FILE EXECUTION START
441
- β”‚
442
- β”œβ”€ Lab 1
443
- β”‚ β”œβ”€ setStore / setSecret / namespace.set β†’ kept across Labs
444
- β”‚ β”œβ”€ setTemp β†’ kept only for this Lab
445
- β”‚ β”œβ”€ clearStore / clearSecret β†’ optional cleanup
446
- β”‚ └─ Lab ends β†’ TEMP cleared, everything else remains
447
- β”‚
448
- β”œβ”€ Lab 2
449
- β”‚ β”œβ”€ getStore / getSecret / namespace.get β†’ works
450
- β”‚ └─ Lab ends β†’ TEMP cleared
451
- β”‚
452
- └─ FILE END β†’ STORE + Secrets + Namespaces completely cleared
44
+ ```bash
45
+ npm install fenneckit@latest --save-dev
453
46
  ```
454
47
 
455
- ---
456
-
457
- ## 🎯 Best Practices (v1.2.0)
458
-
459
- 1. **Secrets always go into the Vault**
460
- ```typescript
461
- kit.setSecret("token", token); // βœ…
462
- kit.setStore("token", token); // ❌ avoid
463
- ```
464
-
465
- 2. **Use namespaces for related data**
466
- ```typescript
467
- const user = kit.namespace("user");
468
- user.set("id", id);
469
- user.set("email", email);
470
- ```
471
-
472
- 3. **Clear sensitive data when no longer needed**
473
- ```typescript
474
- kit.clearSecret("accessToken");
475
- kit.clearStore("creditCard");
476
- ```
477
-
478
- 4. **Check before use**
479
- ```typescript
480
- const token = kit.getSecret("token");
481
- if (!token) kit.err("Token missing!");
482
- ```
48
+ Requires Node.js >= 20.
483
49
 
484
- 5. **Never rely on cross-file sharing** – write to disk/DB if needed.
50
+ ## Run
485
51
 
486
- ---
487
-
488
- ## 🎨 Color / Status Legend
489
-
490
- - `βœ…` / `🟒` Success
491
- - `❌` / `πŸ”΄` Failed (Lab continues)
492
- - `πŸ›‘` Error (Lab stops)
493
- - `⚠️` / `🟑` Warning
494
- - `πŸ“` / `βšͺ` Info
495
- - `βš™οΈ` Store operation
496
- - `πŸ”` Secret operation
497
- - `⏱️` Temp operation
498
- - `🧹` Clear operation
499
- - `⏰` TTL expiration
500
-
501
- ---
502
-
503
- ## πŸ’ͺ Perfect For
504
-
505
- - Backend API testing (Express, Fastify, NestJS, etc.)
506
- - Security-conscious test flows (tokens, keys)
507
- - Database migration validation
508
- - Microservice chaining
509
- - Pre-deployment smoke checks
510
- - State management + audit trails
511
- - Data pipeline validation
512
-
513
- ---
514
-
515
- ## πŸ“ Version History
52
+ ```bash
53
+ npx fenneckit # auto-discover and run all *.labs.js files
54
+ npx fenneckit user.labs.js # run one file
55
+ ```
516
56
 
517
- ### v1.2.0 (Current)
518
- - πŸ” **Secret Vault** – AES-256-GCM encryption
519
- - πŸ—‚οΈ **Namespaced Store**
520
- - πŸ“‹ **Audit Log** (JSON / CSV export)
521
- - πŸ“‘ **HTTP Kit** with auth, retry, history
522
- - ⏰ **TTL support** for STORE & Secrets
523
- - Improved LabContext API
57
+ ## Concepts
524
58
 
525
- ### v1.1.0
526
- - ✨ Added `clearStore()` for store management
527
- - 🌳 Improved data sharing hierarchy documentation
59
+ - **Lab**: a group of sequential tests working toward one goal.
60
+ - **Test**: one step inside a lab, run with `kit.test(name, fn)`.
61
+ - **Storage**: data shared between tests of the same lab (see below).
528
62
 
529
- ### v1.0.0
530
- - Core Lab functionality
531
- - STORE & TEMP storage
532
- - Zero config runner
533
- - Report generation
63
+ ## Features
534
64
 
535
- ---
65
+ | Feature | What it does | Docs |
66
+ | ------------ | ------------------------------------------ | ------------------------------------------ |
67
+ | πŸ”¬ Labs | Group tests into workflows | [Getting started](docs/getting-started.md) |
68
+ | πŸ“¦ STORE | Plain shared data | [Storage](docs/storage.md) |
69
+ | πŸ” SECRET | AES-256-GCM encrypted data | [Storage](docs/storage.md#secret) |
70
+ | πŸ—‚οΈ NAMESPACE | Grouped data per entity | [Storage](docs/storage.md#namespace) |
71
+ | ⏱️ TEMP | Lab-local scratch data | [Storage](docs/storage.md#temp) |
72
+ | ⏰ TTL | Auto-expiring STORE/SECRET values | [Storage](docs/storage.md#ttl) |
73
+ | πŸ“‘ HTTP Kit | Client with auth, retry, history | [HTTP Kit](docs/http-kit.md) |
74
+ | πŸ“‹ Audit Log | Trace store/secret access, export JSON/CSV | [Audit log](docs/audit-log.md) |
536
75
 
537
- ## πŸ“ License
76
+ ## Documentation
538
77
 
539
- **Copyright Β© 2026 Lasith Ruwantha Amrwansha**
540
- Written: 2026/09/17
541
- Updated: 2026/09/21
542
- Author: Ruwantha Amrwansha
543
- Library: FennecKit 🦊
78
+ - [Getting started](docs/getting-started.md)
79
+ - [Storage (STORE, SECRET, NAMESPACE, TEMP, TTL)](docs/storage.md)
80
+ - [HTTP Kit](docs/http-kit.md)
81
+ - [Audit log](docs/audit-log.md)
82
+ - [API reference](docs/api-reference.md)
83
+ - [Best practices](docs/best-practices.md)
84
+ - [Changelog](docs/changelog.md)
544
85
 
545
- ---
86
+ ## License
546
87
 
547
- **Happy Testing! 🦊⚑**
88
+ MIT. Copyright Β© 2026 Lasith Ruwantha Amrwansha.
@@ -0,0 +1,72 @@
1
+ # API reference
2
+
3
+ ```ts
4
+ import { newLabs } from "fenneckit";
5
+
6
+ await newLabs(name: string, fn: (kit: LabContext) => void | Promise<void>);
7
+ ```
8
+
9
+ `fn` receives `kit` (the `LabContext`).
10
+
11
+ ## Testing and flow
12
+
13
+ | Method | Description |
14
+ | --- | --- |
15
+ | `kit.test(name, fn)` | Run a named test; returns the test function's result |
16
+ | `kit.done(msg)` | Mark passed |
17
+ | `kit.log(msg)` | Info message |
18
+ | `kit.warning(msg)` | Warning |
19
+ | `kit.flatErr(msg)` | Failure, continue the lab |
20
+ | `kit.err(msg)` | Failure, stop the lab |
21
+ | `kit.out()` | Exit the lab immediately |
22
+ | `kit.ret()` | Restart the lab (max 3 times) |
23
+
24
+ If a test throws an unexpected error it is printed as a bug exception and the lab is marked failed.
25
+
26
+ ## STORE
27
+
28
+ | Method | Description |
29
+ | --- | --- |
30
+ | `setStore(key, value)` | Save |
31
+ | `getStore(key)` | Read |
32
+ | `clearStore(key?)` | Clear one key or all |
33
+ | `setStoreWithTTL(key, value, ms)` | Save with expiry |
34
+
35
+ ## SECRET
36
+
37
+ | Method | Description |
38
+ | --- | --- |
39
+ | `setSecret(key, value)` | Save encrypted |
40
+ | `getSecret(key)` | Read decrypted (`string | undefined`) |
41
+ | `clearSecret(key?)` | Clear one key or all |
42
+ | `setSecretWithTTL(key, value, ms)` | Save with expiry |
43
+
44
+ ## NAMESPACE
45
+
46
+ | Method | Description |
47
+ | --- | --- |
48
+ | `namespace(name)` | Create or get a namespace |
49
+ | `getNamespace(name)` | Get an existing namespace |
50
+ | `ns.set / get / delete / clear / keys / entries / has / toJSON` | Namespace operations |
51
+
52
+ ## TEMP
53
+
54
+ | Method | Description |
55
+ | --- | --- |
56
+ | `setTemp(key, value)` | Save temp value |
57
+ | `getTemp(key)` | Read temp value |
58
+
59
+ ## HTTP
60
+
61
+ See [HTTP Kit](http-kit.md): `get`, `post`, `put`, `patch`, `delete`, `setAuth`, `clearAuth`, `getRequestHistory`, `getLastRequest`, `clearHistory`.
62
+
63
+ ## Audit
64
+
65
+ See [Audit log](audit-log.md): `getLog`, `getLast`, `filterByKey`, `filterByOperation`, `filterByNamespace`, `export`, `clear`.
66
+
67
+ ## CLI
68
+
69
+ ```bash
70
+ npx fenneckit # run all *.labs.js
71
+ npx fenneckit <file> # run one file
72
+ ```
@@ -0,0 +1,47 @@
1
+ # Audit log
2
+
3
+ `kit.audit` records access to STORE and SECRET values so you can trace a workflow.
4
+
5
+ ## What is recorded
6
+
7
+ | Call | Operation |
8
+ | --- | --- |
9
+ | `setStore` | `set` |
10
+ | `getStore` | `get` |
11
+ | `setSecret` | `setSecret` |
12
+ | `getSecret` | `getSecret` |
13
+
14
+ Namespaces, TEMP, and `clear*` calls are not recorded. TTL setters are recorded through their underlying set call.
15
+
16
+ ## Entry fields
17
+
18
+ `timestamp`, `lab`, `test`, `operation`, `key`, `dataType`, `success`, and optionally `namespace`. Secret values are never stored.
19
+
20
+ ## Querying
21
+
22
+ ```ts
23
+ kit.audit.getLog(); // all entries (copy)
24
+ kit.audit.getLast(5); // last 5 entries
25
+ kit.audit.filterByKey("token");
26
+ kit.audit.filterByOperation("setSecret");
27
+ kit.audit.filterByNamespace("user");
28
+ kit.audit.clear();
29
+ ```
30
+
31
+ ## Export
32
+
33
+ ```ts
34
+ const json = kit.audit.export("json"); // pretty-printed JSON
35
+ const csv = kit.audit.export("csv"); // header: timestamp,lab,test,operation,namespace,key,dataType,success
36
+ ```
37
+
38
+ ## Example
39
+
40
+ ```ts
41
+ await kit.test("Review Audit", async () => {
42
+ const last = kit.audit.getLast(5);
43
+ const tokenOps = kit.audit.filterByKey("token");
44
+ kit.log(`${last.length} recent, ${tokenOps.length} on "token"`);
45
+ kit.done("Audit reviewed");
46
+ });
47
+ ```
@@ -0,0 +1,23 @@
1
+ # Best practices
2
+
3
+ 1. **Use STORE for regular data** (IDs, names).
4
+ 2. **Use SECRET for sensitive data** (tokens, passwords, refresh tokens). Never put them in STORE.
5
+ 3. **Use NAMESPACE for organization**: `const user = kit.namespace("user")` instead of `userId`, `userEmail`, ... keys.
6
+ 4. **Check before using**:
7
+ ```ts
8
+ const token = kit.getSecret("token");
9
+ if (!token) kit.err("Token is missing!");
10
+ ```
11
+ 5. **Clean up secrets** in a final test:
12
+ ```ts
13
+ kit.clearSecret("accessToken");
14
+ kit.http.clearAuth();
15
+ ```
16
+ 6. **Check `res.status` yourself.** HTTP error responses are returned, not thrown.
17
+ 7. **Use `flatErr` for non-blocking checks** (e.g. an optional email step) and `err` when later tests cannot continue.
18
+ 8. **Use the audit log** for debugging and traceability, and clear HTTP history before exporting it.
19
+ 9. **One lab, one goal.** Keep labs small and independent, since nothing is shared between labs.
20
+
21
+ ## Good fits
22
+
23
+ API testing, security checks, migration validation, microservice chains, pre-deployment smoke tests, ETL/data pipeline checks, and multi-step integration tests.
@@ -0,0 +1,82 @@
1
+ # Getting started
2
+
3
+ ## Install
4
+
5
+ ```bash
6
+ npm install fenneckit@latest --save-dev
7
+ ```
8
+
9
+ Node.js >= 20 is required. FennecKit is an ES module package (`"type": "module"`).
10
+
11
+ ## Your first lab
12
+
13
+ Create `user.labs.js`:
14
+
15
+ ```ts
16
+ import { newLabs } from "fenneckit";
17
+
18
+ await newLabs("User API Test", async (kit) => {
19
+ await kit.test("Step 1: Create User", async () => {
20
+ kit.setStore("userId", "usr_123");
21
+ kit.done("Stored user ID");
22
+ });
23
+
24
+ await kit.test("Step 2: Read User", async () => {
25
+ const userId = kit.getStore("userId");
26
+ kit.log(`Using user: ${userId}`);
27
+ kit.done("Retrieved user");
28
+ });
29
+ });
30
+ ```
31
+
32
+ ## Run
33
+
34
+ ```bash
35
+ npx fenneckit # run every *.labs.js file
36
+ npx fenneckit user.labs.js # run a single file
37
+ ```
38
+
39
+ ## Core ideas
40
+
41
+ | Term | Meaning |
42
+ | --- | --- |
43
+ | Lab | A collection of sequential tests toward one goal |
44
+ | Test | One step, run with `kit.test(name, fn)` |
45
+ | Storage | Data shared by all tests in the same lab |
46
+
47
+ Example lab layout:
48
+
49
+ ```
50
+ πŸ”¬ Lab: Payment Processing
51
+ β”œβ”€ Test 1: Validate Card
52
+ β”œβ”€ Test 2: Charge Card
53
+ β”œβ”€ Test 3: Generate Invoice
54
+ └─ Test 4: Send Receipt Email
55
+ ```
56
+
57
+ ## Reporting results
58
+
59
+ Inside a test, report status with:
60
+
61
+ | Method | Effect |
62
+ | --- | --- |
63
+ | `kit.done(msg)` | Test passed (🟒) |
64
+ | `kit.log(msg)` | Info message (βšͺ) |
65
+ | `kit.warning(msg)` | Warning (🟑) |
66
+ | `kit.flatErr(msg)` | Failure, lab **continues** (πŸ”΄) |
67
+ | `kit.err(msg)` | Failure, lab **stops** (πŸ”΄) |
68
+
69
+ ## Flow control
70
+
71
+ - `kit.out()` exits the lab immediately.
72
+ - `kit.ret()` restarts the whole lab, up to 3 times. After that it calls `kit.err("Restart limit exceeded (max 3 attempts)")`.
73
+
74
+ ## Isolation
75
+
76
+ Each `newLabs(...)` call has its own isolated STORE, SECRET, NAMESPACE, TEMP, HTTP client and audit log. Nothing is shared between labs or files.
77
+
78
+ ## Next
79
+
80
+ - [Storage](storage.md)
81
+ - [HTTP Kit](http-kit.md)
82
+ - [API reference](api-reference.md)
@@ -0,0 +1,71 @@
1
+ # HTTP Kit
2
+
3
+ `kit.http` is a built-in client (based on `fetch`) with auth, timeout, retry and request history.
4
+
5
+ ## Methods
6
+
7
+ ```ts
8
+ kit.http.get(url, options?)
9
+ kit.http.post(url, body, options?)
10
+ kit.http.put(url, body, options?)
11
+ kit.http.patch(url, body, options?)
12
+ kit.http.delete(url, options?)
13
+ ```
14
+
15
+ Bodies that are not strings are sent as JSON. Default headers: `Content-Type: application/json` and `User-Agent: FennecKit/1.2.0`.
16
+
17
+ ## Response
18
+
19
+ ```ts
20
+ {
21
+ status: number,
22
+ statusText: string,
23
+ headers: Record<string, string>,
24
+ data: any, // parsed JSON if content-type is application/json, otherwise text
25
+ raw: Response
26
+ }
27
+ ```
28
+
29
+ ## Options
30
+
31
+ | Option | Default | Description |
32
+ | --- | --- | --- |
33
+ | `headers` | none | Extra headers, override defaults |
34
+ | `timeout` | `30000` | Timeout in ms (uses `AbortController`) |
35
+ | `retry.max` | `1` | Total number of **attempts** |
36
+ | `retry.delay` | `1000` | Wait in ms between attempts |
37
+ | `followRedirects` | `true` | Set `false` for manual redirects |
38
+
39
+ ```ts
40
+ const res = await kit.http.get("https://api.example.com/users", {
41
+ timeout: 5000,
42
+ retry: { max: 3, delay: 1000 },
43
+ });
44
+ ```
45
+
46
+ Retries happen only on network errors (thrown by `fetch`). An HTTP 4xx/5xx response is returned normally, so check `res.status` yourself.
47
+
48
+ ## Authentication
49
+
50
+ ```ts
51
+ kit.http.setAuth("bearer", token); // Authorization: Bearer <token>
52
+ kit.http.setAuth("basic", base64Creds); // Authorization: Basic <base64Creds>
53
+ kit.http.setAuth("api-key", key); // X-API-Key: <key>
54
+ kit.http.clearAuth();
55
+ ```
56
+
57
+ For `basic`, pass the credentials already base64-encoded.
58
+
59
+ ## Request history
60
+
61
+ ```ts
62
+ kit.http.getRequestHistory(); // TracedRequest[]
63
+ kit.http.getLastRequest(); // TracedRequest | undefined
64
+ kit.http.clearHistory();
65
+ ```
66
+
67
+ Each entry contains: `id`, `timestamp`, `method`, `url`, `status` (0 on failure), `duration`, `headers`, `requestBody`, `responseBody`, and `error` when the request failed.
68
+
69
+ > History stores request headers (including auth headers) and bodies as-is. Call `kit.http.clearHistory()` before exporting or printing it if it may contain credentials.
70
+
71
+ When all attempts fail, the last error is thrown.
@@ -0,0 +1,90 @@
1
+ # Storage
2
+
3
+ Every lab has five kinds of in-memory storage.
4
+
5
+ | Level | Use for | Encrypted | Audited | Lifetime |
6
+ | --- | --- | --- | --- | --- |
7
+ | STORE | IDs, names, plain values | No | Yes (set/get) | Until lab ends |
8
+ | SECRET | Tokens, passwords | Yes | Yes (set/get) | Until lab ends |
9
+ | NAMESPACE | Grouped entity data | No | No | Until lab ends |
10
+ | TEMP | Scratch values | No | No | Until lab ends |
11
+ | TTL variants | Auto-expiring STORE/SECRET | Same as base | Same as base | Until TTL or lab end |
12
+
13
+ Storage is per lab: nothing is shared across labs or files.
14
+
15
+ ## STORE
16
+
17
+ ```ts
18
+ await kit.test("Create User", async () => {
19
+ const res = await kit.http.post("/users", { name: "Alice" });
20
+ kit.setStore("userId", res.data.id);
21
+ kit.done("User created");
22
+ });
23
+
24
+ await kit.test("Get User", async () => {
25
+ const userId = kit.getStore("userId");
26
+ // ...
27
+ });
28
+ ```
29
+
30
+ - `kit.setStore(key, value)`
31
+ - `kit.getStore(key)`
32
+ - `kit.clearStore(key)` removes one key; `kit.clearStore()` removes everything.
33
+
34
+ ## SECRET
35
+
36
+ Use SECRET for anything sensitive. Values are encrypted in memory with AES-256-GCM (random 16-byte IV and auth tag per value, key derived with scrypt) and decrypted on read.
37
+
38
+ ```ts
39
+ kit.setSecret("accessToken", res.data.accessToken);
40
+ const token = kit.getSecret("accessToken"); // string | undefined
41
+ kit.http.setAuth("bearer", token!);
42
+ kit.clearSecret("accessToken");
43
+ ```
44
+
45
+ - `kit.setSecret(key, value)`, `kit.getSecret(key)`
46
+ - `kit.clearSecret(key)` or `kit.clearSecret()` for all
47
+ - Secret **values** are never written to the lab log or audit log. Only the key name is.
48
+ - If decryption fails, `getSecret` logs an error and returns `undefined`.
49
+
50
+ > Note: the vault protects values held in memory. It uses a built-in default password, so treat it as protection against accidental exposure (logs, dumps), not as a security boundary.
51
+
52
+ ## NAMESPACE
53
+
54
+ Group related values instead of using prefixed keys.
55
+
56
+ ```ts
57
+ const user = kit.namespace("user"); // create or get
58
+ const order = kit.namespace("order");
59
+
60
+ user.set("id", "usr_123");
61
+ order.set("total", 299.99);
62
+
63
+ user.get("id");
64
+ kit.getNamespace("user"); // existing namespace, or undefined
65
+ ```
66
+
67
+ Namespace methods: `set`, `get`, `delete`, `clear(key?)`, `keys`, `entries`, `has`, `toJSON`.
68
+
69
+ ## TEMP
70
+
71
+ Scratch values for short-lived data such as timers.
72
+
73
+ ```ts
74
+ kit.setTemp("startTime", Date.now());
75
+ await kit.http.get("/api/heavy-endpoint");
76
+ const elapsed = Date.now() - kit.getTemp("startTime");
77
+ ```
78
+
79
+ TEMP is cleared when the lab finishes. Do not rely on it as per-test storage; use unique keys or STORE for anything you want to control.
80
+
81
+ ## TTL
82
+
83
+ Values that expire automatically.
84
+
85
+ ```ts
86
+ kit.setStoreWithTTL("sessionId", "sess_123", 30_000); // 30 s
87
+ kit.setSecretWithTTL("tempToken", "token", 60_000); // 60 s
88
+ ```
89
+
90
+ Setting the same key again resets its timer. Expiry is written to the lab log. Pending timers are cleared when the lab ends.
package/index.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ import { newLabs } from "./libs/labs.js";
2
+ import { testing } from "./libs/testing.js";
3
+ export { newLabs, testing };
4
+ export type { TracedRequest, HttpResponse, RequestOptions, AuditEntry } from "./types.ts";
5
+ //# sourceMappingURL=index.d.ts.map
package/index.d.ts.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,OAAO,EACR,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAE5C,OAAO,EACL,OAAO,EACP,OAAO,EACR,CAAA;AACD,YAAY,EACV,aAAa,EACb,YAAY,EACZ,cAAc,EACd,UAAU,EACX,MAAM,YAAY,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=fenneckit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fenneckit.d.ts","sourceRoot":"","sources":["../../src/libs/fenneckit.ts"],"names":[],"mappings":""}
package/libs/labs.d.ts ADDED
@@ -0,0 +1,70 @@
1
+ import type { TracedRequest, HttpResponse, RequestOptions, AuditEntry } from "../types.js";
2
+ declare class NamespacedStore {
3
+ private data;
4
+ private name;
5
+ constructor(name: string);
6
+ set(key: string, value: any): void;
7
+ get(key: string): any;
8
+ delete(key: string): void;
9
+ clear(key?: string): void;
10
+ keys(): string[];
11
+ entries(): [string, any][];
12
+ has(key: string): boolean;
13
+ toJSON(): Record<string, any>;
14
+ }
15
+ declare class AuditLog {
16
+ private entries;
17
+ record(entry: AuditEntry): void;
18
+ getLog(): AuditEntry[];
19
+ getLast(n: number): AuditEntry[];
20
+ filterByKey(key: string): AuditEntry[];
21
+ filterByOperation(op: AuditEntry["operation"]): AuditEntry[];
22
+ filterByNamespace(ns: string): AuditEntry[];
23
+ export(format: "json" | "csv"): string;
24
+ clear(): void;
25
+ }
26
+ interface LabContext {
27
+ out: () => void;
28
+ ret: () => void;
29
+ flatErr: (msg: string) => void;
30
+ err: (msg: string) => void;
31
+ done: (msg: string) => void;
32
+ log: (msg: string) => void;
33
+ warning: (msg: string) => void;
34
+ setStore: (key: string, value: any) => void;
35
+ getStore: (key: string) => any;
36
+ clearStore: (key?: string) => void;
37
+ setSecret: (key: string, value: string) => void;
38
+ getSecret: (key: string) => string | undefined;
39
+ clearSecret: (key?: string) => void;
40
+ namespace: (name: string) => NamespacedStore;
41
+ getNamespace: (name: string) => NamespacedStore | undefined;
42
+ setTemp: (key: string, value: any) => void;
43
+ getTemp: (key: string) => any;
44
+ http: HttpKit;
45
+ audit: AuditLog;
46
+ setStoreWithTTL: (key: string, value: any, ttlMs: number) => void;
47
+ setSecretWithTTL: (key: string, value: string, ttlMs: number) => void;
48
+ test: <T>(testName: string, testFn: () => T | Promise<T>) => Promise<T>;
49
+ }
50
+ declare class HttpKit {
51
+ private authHeader;
52
+ private requestHistory;
53
+ private baseHeaders;
54
+ setAuth(type: "bearer" | "basic" | "api-key", credentials: string): void;
55
+ clearAuth(): void;
56
+ private getAuthHeader;
57
+ private makeRequest;
58
+ private recordRequest;
59
+ get(url: string, options?: RequestOptions): Promise<HttpResponse>;
60
+ post(url: string, body: any, options?: RequestOptions): Promise<HttpResponse>;
61
+ put(url: string, body: any, options?: RequestOptions): Promise<HttpResponse>;
62
+ patch(url: string, body: any, options?: RequestOptions): Promise<HttpResponse>;
63
+ delete(url: string, options?: RequestOptions): Promise<HttpResponse>;
64
+ getRequestHistory(): TracedRequest[];
65
+ clearHistory(): void;
66
+ getLastRequest(): TracedRequest | undefined;
67
+ }
68
+ export declare function newLabs(name: string, fn: (kit: LabContext) => void | Promise<void>, retCount?: number): Promise<void>;
69
+ export {};
70
+ //# sourceMappingURL=labs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"labs.d.ts","sourceRoot":"","sources":["../../src/libs/labs.ts"],"names":[],"mappings":"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"}
package/libs/labs.js CHANGED
@@ -7,6 +7,9 @@
7
7
  import { createCipheriv, createDecipheriv, randomBytes, scryptSync, } from "crypto";
8
8
  import report from "../utility/report.js";
9
9
  import path from "path";
10
+ /*===========================
11
+ All calass
12
+ =============================*/
10
13
  class SecretVault {
11
14
  vault = new Map();
12
15
  masterKey;
@@ -291,6 +294,7 @@ class HttpKit {
291
294
  return this.requestHistory[this.requestHistory.length - 1];
292
295
  }
293
296
  }
297
+ // ====================== Lab Context ======================
294
298
  export async function newLabs(name, fn, retCount = 0) {
295
299
  let logs = `## ${name} 🌏\n\n\`\`\`\n`;
296
300
  let currentTest = "";
@@ -0,0 +1,9 @@
1
+ interface TestContext {
2
+ succes: (msg: string) => void;
3
+ err: (msg: string) => never;
4
+ log: (msg: string) => void;
5
+ worrn: (msg: string) => void;
6
+ }
7
+ export declare function testing<T>(fn: (t: TestContext) => T | Promise<T>): Promise<T | void>;
8
+ export {};
9
+ //# sourceMappingURL=testing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../../src/libs/testing.ts"],"names":[],"mappings":"AAiCA,UAAU,WAAW;IACnB,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,KAAK,CAAC;IAC5B,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3B,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9B;AAKD,wBAAsB,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,WAAW,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAc1F"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fenneckit",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "A tiny, fast & adorable testing kit. Lightweight test framework with zero config – easy to hold, easy to run.",
5
5
  "keywords": [
6
6
  "testing",
@@ -29,6 +29,13 @@
29
29
  "author": "lasith ruwantha amrwansha",
30
30
  "type": "module",
31
31
  "main": "index.js",
32
+ "types":"./dist/index.d.ts",
33
+ "exports":{
34
+ ".":{
35
+ "types":"./dist/index.d.ts",
36
+ "import": "./dist/index.js"
37
+ }
38
+ },
32
39
  "scripts": {
33
40
  "eslint": "eslint .",
34
41
  "build": "tsc",
@@ -52,5 +59,8 @@
52
59
  "tsx": "^4.23.13",
53
60
  "typescript": "^6.0.3",
54
61
  "typescript-eslint": "^8.70.0"
62
+ },
63
+ "engines":{
64
+ "node":">=20"
55
65
  }
56
66
  }
package/types.d.ts ADDED
@@ -0,0 +1,39 @@
1
+ export interface HttpResponse<T = any> {
2
+ status: number;
3
+ statusText: string;
4
+ headers: Record<string, string>;
5
+ data: T;
6
+ raw: Response;
7
+ }
8
+ export interface RequestOptions {
9
+ headers?: Record<string, string>;
10
+ timeout?: number;
11
+ retry?: {
12
+ max: number;
13
+ delay: number;
14
+ };
15
+ followRedirects?: boolean;
16
+ }
17
+ export interface TracedRequest {
18
+ id: string;
19
+ timestamp: string;
20
+ method: string;
21
+ url: string;
22
+ status: number;
23
+ duration: number;
24
+ headers: Record<string, string>;
25
+ requestBody?: any;
26
+ responseBody?: any;
27
+ error?: string;
28
+ }
29
+ export interface AuditEntry {
30
+ timestamp: string;
31
+ lab: string;
32
+ test: string;
33
+ operation: "set" | "get" | "delete" | "clear" | "setSecret" | "getSecret";
34
+ namespace?: string;
35
+ key: string;
36
+ dataType: string;
37
+ success: boolean;
38
+ }
39
+ //# sourceMappingURL=types.d.ts.map
package/types.d.ts.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,GAAG;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,EAAE,CAAC,CAAC;IACR,GAAG,EAAE,QAAQ,CAAC;CACf;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IACvC,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,WAAW,CAAC,EAAE,GAAG,CAAC;IAClB,YAAY,CAAC,EAAE,GAAG,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,KAAK,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,GAAG,WAAW,GAAG,WAAW,CAAC;IAC1E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;CAClB"}
package/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ export default function report(data: string): Promise<void>;
2
+ //# sourceMappingURL=report.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../src/utility/report.ts"],"names":[],"mappings":"AAoBA,wBAA8B,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAShE"}