fenneckit 1.0.3 → 1.2.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/README.md +262 -318
- package/bin/fenneckit +1 -1
- package/index.js +2 -2
- package/libs/fenneckit.js +1 -1
- package/libs/labs.js +393 -46
- package/package.json +4 -3
- package/utility/report.js +26 -0
package/README.md
CHANGED
|
@@ -2,9 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|
|
|
5
|
-
|
|
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
|
|
6
13
|
|
|
7
|
-
**
|
|
14
|
+
**🦊Example** : soon
|
|
8
15
|
|
|
9
16
|
---
|
|
10
17
|
|
|
@@ -64,8 +71,8 @@ npx fenneckit
|
|
|
64
71
|
4. Generates a report (`fenneckit.md`) after execution
|
|
65
72
|
|
|
66
73
|
> **Important limitation**
|
|
67
|
-
> Data sharing (`setStore` / `getStore`) only works **inside the same file**.
|
|
68
|
-
> Different lab files **cannot** share STORE or
|
|
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.
|
|
69
76
|
|
|
70
77
|
---
|
|
71
78
|
|
|
@@ -74,71 +81,47 @@ npx fenneckit
|
|
|
74
81
|
```
|
|
75
82
|
📁 FennecKit Execution
|
|
76
83
|
│
|
|
77
|
-
├─ 📄 file1.labs.js (STORE Instance #1)
|
|
84
|
+
├─ 📄 file1.labs.js (STORE Instance #1 + SecretVault + Namespaces)
|
|
78
85
|
│ │
|
|
79
86
|
│ ├─ 🔬 Lab 1 (User Registration)
|
|
80
87
|
│ │ ├─ 📝 Test 1: Create User
|
|
81
88
|
│ │ │ ├─ STORE: {"userId": "123"} ✅ Shared with Lab 2
|
|
89
|
+
│ │ │ ├─ SECRET: encrypted token ✅ Shared with Lab 2
|
|
90
|
+
│ │ │ ├─ NAMESPACE "user": {...} ✅ Shared with Lab 2
|
|
82
91
|
│ │ │ └─ TEMP: {"token": "abc"} ❌ Only here
|
|
83
92
|
│ │ │
|
|
84
93
|
│ │ └─ 📝 Test 2: Send Email
|
|
85
|
-
│ │ └─ Can access STORE from Test 1
|
|
94
|
+
│ │ └─ Can access STORE / SECRET / Namespaces from Test 1
|
|
86
95
|
│ │
|
|
87
96
|
│ ├─ 🔬 Lab 2 (Authentication)
|
|
88
97
|
│ │ ├─ 📝 Test 1: Generate Token
|
|
89
98
|
│ │ │ ├─ STORE: {"userId": "123"} ✅ From Lab 1
|
|
99
|
+
│ │ │ ├─ SECRET / Namespace ✅ From Lab 1
|
|
90
100
|
│ │ │ └─ TEMP: {"token": "new"} ❌ Only here
|
|
91
101
|
│ │ │
|
|
92
102
|
│ │ └─ 📝 Test 2: Verify Token
|
|
93
|
-
│ │ └─ Can access STORE from Labs 1 & 2
|
|
103
|
+
│ │ └─ Can access STORE / SECRET / Namespaces from Labs 1 & 2
|
|
94
104
|
│ │
|
|
95
105
|
│ └─ 🔬 Lab 3 (Cleanup)
|
|
96
|
-
│ └─ File STORE cleared when execution ends
|
|
106
|
+
│ └─ File STORE + Secrets + Namespaces cleared when execution ends
|
|
97
107
|
│
|
|
98
|
-
├─ 📄 file2.labs.js (
|
|
99
|
-
│
|
|
100
|
-
│ ├─ 🔬 Lab 1
|
|
101
|
-
│ │ └─ ❌ CANNOT access file1.labs.js STORE
|
|
102
|
-
│ │
|
|
103
|
-
│ └─ 🔬 Lab 2
|
|
104
|
-
│ └─ ❌ CANNOT access file1.labs.js STORE
|
|
108
|
+
├─ 📄 file2.labs.js (Completely ISOLATED)
|
|
109
|
+
│ └─ ❌ CANNOT access anything from file1.labs.js
|
|
105
110
|
│
|
|
106
|
-
└─ 📄 file3.labs.js (
|
|
107
|
-
└─ ❌ Isolated from
|
|
111
|
+
└─ 📄 file3.labs.js (Completely ISOLATED)
|
|
112
|
+
└─ ❌ Isolated from all other files
|
|
108
113
|
```
|
|
109
114
|
|
|
110
115
|
### Understanding the Hierarchy
|
|
111
116
|
|
|
112
|
-
**🔴 Level 1: Different Files = NO Data Sharing**
|
|
113
|
-
|
|
114
|
-
file1.labs.js ← STORE Instance #1 (isolated)
|
|
115
|
-
file2.labs.js ← STORE Instance #2 (isolated)
|
|
116
|
-
file3.labs.js ← STORE Instance #3 (isolated)
|
|
117
|
+
**🔴 Level 1: Different Files = NO Data Sharing**
|
|
118
|
+
Each file gets its own isolated STORE, SecretVault and Namespaces.
|
|
117
119
|
|
|
118
|
-
|
|
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).
|
|
120
122
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
file1.labs.js
|
|
124
|
-
├─ Lab 1: setStore("userId", "123")
|
|
125
|
-
├─ Lab 2: getStore("userId") ✅ Can access
|
|
126
|
-
└─ Lab 3: getStore("userId") ✅ Can still access
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
**🟢 Level 3: Same Lab, Different Tests = STORE + TEMP Sharing**
|
|
130
|
-
```
|
|
131
|
-
Lab 1 (User Registration)
|
|
132
|
-
├─ Test 1:
|
|
133
|
-
│ ├─ setStore("userId", "123") ✅ Shared with other tests
|
|
134
|
-
│ └─ setTemp("token", "abc") ✅ Shared with other tests in Lab 1
|
|
135
|
-
│
|
|
136
|
-
├─ Test 2:
|
|
137
|
-
│ ├─ getStore("userId") ✅ Works (from Test 1)
|
|
138
|
-
│ └─ getTemp("token") ✅ Works (from Test 1)
|
|
139
|
-
│
|
|
140
|
-
└─ Lab 1 ends → TEMP cleared, STORE remains
|
|
141
|
-
```
|
|
123
|
+
**🟢 Level 3: Same Lab, Different Tests = STORE + TEMP + Secrets + Namespaces Sharing**
|
|
124
|
+
TEMP is automatically cleared when the Lab ends. Everything else remains.
|
|
142
125
|
|
|
143
126
|
---
|
|
144
127
|
|
|
@@ -148,217 +131,218 @@ Lab 1 (User Registration)
|
|
|
148
131
|
|
|
149
132
|
In Jest and Vitest every test is isolated. You cannot pass data from one test to another.
|
|
150
133
|
|
|
151
|
-
### FennecKit Solution –
|
|
134
|
+
### FennecKit Solution – Storage Levels (v1.2.0)
|
|
152
135
|
|
|
153
136
|
```
|
|
154
137
|
┌─────────────────────────────────────────────────────────────┐
|
|
155
138
|
│ FennecKit Storage System (Per File) │
|
|
156
139
|
├─────────────────────────────────────────────────────────────┤
|
|
157
140
|
│ │
|
|
158
|
-
│ LEVEL 1: FILE SCOPE
|
|
141
|
+
│ LEVEL 1: FILE SCOPE │
|
|
159
142
|
│ ┌──────────────────────────────────────────────────────┐ │
|
|
160
143
|
│ │ STORE (Global - Persistent within file) │ │
|
|
161
144
|
│ │ ├─ Shared across ALL Labs in this file │ │
|
|
162
145
|
│ │ ├─ Available until file execution ends │ │
|
|
163
146
|
│ │ ├─ Can be manually cleared with clearStore() │ │
|
|
164
|
-
│ │ └─ Example: userId,
|
|
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 │ │
|
|
165
157
|
│ └──────────────────────────────────────────────────────┘ │
|
|
166
158
|
│ │
|
|
167
|
-
│ LEVEL
|
|
159
|
+
│ LEVEL 3: NAMESPACES │
|
|
168
160
|
│ ┌──────────────────────────────────────────────────────┐ │
|
|
169
|
-
│ │
|
|
170
|
-
│ │
|
|
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 │ │
|
|
171
165
|
│ └──────────────────────────────────────────────────────┘ │
|
|
172
166
|
│ │
|
|
173
|
-
│ LEVEL
|
|
167
|
+
│ LEVEL 4: LAB-LOCAL SCOPE │
|
|
174
168
|
│ ┌──────────────────────────────────────────────────────┐ │
|
|
175
169
|
│ │ TEMP (Local - Auto-Cleaned) │ │
|
|
176
170
|
│ │ ├─ Only available inside current Lab │ │
|
|
177
171
|
│ │ ├─ Automatically cleared when Lab ends │ │
|
|
178
|
-
│ │
|
|
179
|
-
│ │ └─ Example: timestamps, temp calculations │ │
|
|
172
|
+
│ │ └─ Example: timestamps, temp calculations │ │
|
|
180
173
|
│ └──────────────────────────────────────────────────────┘ │
|
|
181
174
|
│ │
|
|
182
175
|
└─────────────────────────────────────────────────────────────┘
|
|
183
176
|
```
|
|
184
177
|
|
|
185
|
-
|
|
178
|
+
---
|
|
186
179
|
|
|
187
|
-
|
|
188
|
-
// ========== LAB 1: User Registration ==========
|
|
189
|
-
await newLabs("User Registration", async (kit) => {
|
|
190
|
-
await kit.test("Create User", async () => {
|
|
191
|
-
const userId = "user_123";
|
|
192
|
-
const email = "john@example.com";
|
|
180
|
+
## 🔐 NEW in v1.2.0: Secret Vault (AES-256-GCM)
|
|
193
181
|
|
|
194
|
-
|
|
195
|
-
kit.setStore("userId", userId);
|
|
196
|
-
kit.setStore("userEmail", email);
|
|
182
|
+
Sensitive data should never live in plain STORE.
|
|
197
183
|
|
|
198
|
-
|
|
199
|
-
|
|
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");
|
|
200
190
|
|
|
201
|
-
kit.done("
|
|
191
|
+
kit.done("Tokens stored securely");
|
|
202
192
|
});
|
|
203
|
-
});
|
|
204
193
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
const token = kit.getTemp("tempToken"); // ❌ undefined (cleared after Lab 1)
|
|
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
|
+
});
|
|
211
199
|
|
|
212
|
-
|
|
200
|
+
await kit.test("Cleanup", async () => {
|
|
201
|
+
kit.clearSecret("accessToken"); // remove one key
|
|
202
|
+
// kit.clearSecret(); // clear entire vault
|
|
203
|
+
kit.done("Secrets cleared");
|
|
213
204
|
});
|
|
214
205
|
});
|
|
215
206
|
```
|
|
216
207
|
|
|
217
|
-
|
|
208
|
+
**TTL Support for Secrets**
|
|
209
|
+
```typescript
|
|
210
|
+
kit.setSecretWithTTL("tempToken", "abc123", 30_000); // auto-delete after 30s
|
|
211
|
+
```
|
|
218
212
|
|
|
219
|
-
|
|
213
|
+
---
|
|
220
214
|
|
|
221
|
-
|
|
215
|
+
## 🗂️ NEW in v1.2.0: Namespaced Store
|
|
222
216
|
|
|
223
|
-
|
|
217
|
+
Organize data into logical groups.
|
|
224
218
|
|
|
225
|
-
#### 1. Clear specific key (inside Lab)
|
|
226
219
|
```typescript
|
|
227
|
-
await newLabs("
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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");
|
|
231
229
|
});
|
|
232
230
|
|
|
233
|
-
await kit.test("
|
|
234
|
-
|
|
235
|
-
|
|
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}`);
|
|
236
241
|
});
|
|
237
242
|
});
|
|
238
243
|
```
|
|
239
244
|
|
|
240
|
-
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## 📡 NEW in v1.2.0: HTTP Kit (API Testing)
|
|
248
|
+
|
|
249
|
+
Built-in HTTP client with auth, retries, history and tracing.
|
|
250
|
+
|
|
241
251
|
```typescript
|
|
242
|
-
|
|
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");
|
|
257
|
+
|
|
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
|
+
}
|
|
243
267
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
kit.setStore("data1", "value1");
|
|
247
|
-
kit.setStore("data2", "value2");
|
|
248
|
-
kit.done("Stored");
|
|
268
|
+
kit.setStore("users", res.data);
|
|
269
|
+
kit.done(`Fetched ${res.data.length} users`);
|
|
249
270
|
});
|
|
250
|
-
});
|
|
251
271
|
|
|
252
|
-
|
|
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
|
+
});
|
|
253
280
|
|
|
254
|
-
|
|
255
|
-
await kit.test("
|
|
256
|
-
const
|
|
257
|
-
kit.
|
|
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`);
|
|
258
285
|
});
|
|
259
286
|
});
|
|
260
287
|
```
|
|
261
288
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
| Remove sensitive data | `clearStore("token")` | Security |
|
|
267
|
-
| Free memory | `clearStore("largeObject")` | Performance |
|
|
268
|
-
| Test isolation | `clearStore()` | Prevent data leakage |
|
|
269
|
-
| Between phases | `clearStore("tempData")` | Clean state |
|
|
289
|
+
**Available methods**:
|
|
290
|
+
- `kit.http.get / post / put / patch / delete`
|
|
291
|
+
- `kit.http.setAuth(type, credentials)` / `clearAuth()`
|
|
292
|
+
- `kit.http.getRequestHistory()` / `getLastRequest()` / `clearHistory()`
|
|
270
293
|
|
|
271
294
|
---
|
|
272
295
|
|
|
273
|
-
##
|
|
274
|
-
|
|
275
|
-
```
|
|
276
|
-
FILE EXECUTION START
|
|
277
|
-
│
|
|
278
|
-
├─ Lab 1
|
|
279
|
-
│ ├─ setStore(...) → kept across all labs
|
|
280
|
-
│ ├─ setTemp(...) → kept only for this Lab
|
|
281
|
-
│ ├─ clearStore(...) → optionally remove keys
|
|
282
|
-
│ └─ Lab ends → TEMP cleared, STORE remains (unless cleared)
|
|
283
|
-
│
|
|
284
|
-
├─ Lab 2
|
|
285
|
-
│ ├─ getStore(...) → works (if not cleared)
|
|
286
|
-
│ ├─ getTemp(...) → undefined (cleared after Lab 1)
|
|
287
|
-
│ ├─ clearStore(...) → can clear for next labs
|
|
288
|
-
│ └─ Lab ends → TEMP cleared, STORE remains (unless cleared)
|
|
289
|
-
│
|
|
290
|
-
└─ FILE END → STORE completely cleared
|
|
291
|
-
```
|
|
292
|
-
|
|
293
|
-
**Remember**: This lifecycle is **per file**.
|
|
294
|
-
Running `npx fenneckit fileA.labs.js` and then `npx fenneckit fileB.labs.js` gives two completely separate STORE instances.
|
|
295
|
-
|
|
296
|
-
---
|
|
296
|
+
## 📋 NEW in v1.2.0: Audit Log
|
|
297
297
|
|
|
298
|
-
|
|
298
|
+
Every STORE / SECRET operation is automatically recorded.
|
|
299
299
|
|
|
300
300
|
```typescript
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
await newLabs("Order Validation", async (kit) => {
|
|
307
|
-
await kit.test("Check Product Stock", async () => {
|
|
308
|
-
kit.setStore("productId", "prod_456");
|
|
309
|
-
kit.setStore("stockAvailable", 50);
|
|
310
|
-
kit.setTemp("validationTime", Date.now());
|
|
311
|
-
kit.done("Product stock verified");
|
|
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");
|
|
312
306
|
});
|
|
313
|
-
});
|
|
314
307
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
const
|
|
319
|
-
const
|
|
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");
|
|
320
313
|
|
|
321
|
-
kit.
|
|
322
|
-
kit.setStore("orderStatus", "paid");
|
|
323
|
-
kit.setTemp("transactionId", chargeId);
|
|
324
|
-
|
|
325
|
-
kit.done(`Payment charged: ${chargeId}`);
|
|
314
|
+
kit.log(`Recorded ${last5.length} operations`);
|
|
326
315
|
});
|
|
327
316
|
});
|
|
317
|
+
```
|
|
328
318
|
|
|
329
|
-
|
|
330
|
-
clearStore("chargeId");
|
|
319
|
+
---
|
|
331
320
|
|
|
332
|
-
|
|
333
|
-
await newLabs("Shipping & Notification", async (kit) => {
|
|
334
|
-
await kit.test("Create Shipping Label", async () => {
|
|
335
|
-
const status = kit.getStore("orderStatus"); // ✅ Available
|
|
336
|
-
const chargeId = kit.getStore("chargeId"); // ❌ Cleared
|
|
337
|
-
const tempTx = kit.getTemp("transactionId"); // ❌ Undefined
|
|
321
|
+
## 🧹 clearStore() & clearSecret()
|
|
338
322
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
}
|
|
344
|
-
});
|
|
323
|
+
```typescript
|
|
324
|
+
// Clear specific key
|
|
325
|
+
kit.clearStore("authToken");
|
|
326
|
+
kit.clearSecret("accessToken");
|
|
345
327
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
});
|
|
350
|
-
});
|
|
328
|
+
// Clear everything
|
|
329
|
+
kit.clearStore();
|
|
330
|
+
kit.clearSecret();
|
|
351
331
|
```
|
|
352
332
|
|
|
353
|
-
|
|
333
|
+
---
|
|
354
334
|
|
|
355
|
-
|
|
356
|
-
|
|
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);
|
|
357
341
|
```
|
|
358
342
|
|
|
359
343
|
---
|
|
360
344
|
|
|
361
|
-
## 🛠️ LabContext Methods
|
|
345
|
+
## 🛠️ LabContext Methods (v1.2.0)
|
|
362
346
|
|
|
363
347
|
```typescript
|
|
364
348
|
interface LabContext {
|
|
@@ -368,184 +352,136 @@ interface LabContext {
|
|
|
368
352
|
err(msg: string): void // stops the Lab
|
|
369
353
|
flatErr(msg: string): void // continues
|
|
370
354
|
log(msg: string): void
|
|
371
|
-
warning(msg: string): void
|
|
355
|
+
warning(msg: string): void
|
|
372
356
|
|
|
373
357
|
// Flow control
|
|
374
358
|
out(): void // exit Lab immediately
|
|
375
|
-
ret(): void // restart Lab
|
|
359
|
+
ret(): void // restart Lab (max 3 times)
|
|
376
360
|
|
|
377
|
-
// Persistent
|
|
361
|
+
// Persistent STORE
|
|
378
362
|
setStore(key: string, value: any): void
|
|
379
363
|
getStore(key: string): any
|
|
380
|
-
clearStore(key?: string): void
|
|
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
|
|
381
376
|
|
|
382
377
|
// Temporary (Lab-local only)
|
|
383
378
|
setTemp(key: string, value: any): void
|
|
384
379
|
getTemp(key: string): any
|
|
380
|
+
|
|
381
|
+
// HTTP Kit
|
|
382
|
+
http: HttpKit
|
|
383
|
+
|
|
384
|
+
// Audit Log
|
|
385
|
+
audit: AuditLog
|
|
385
386
|
}
|
|
386
387
|
```
|
|
387
388
|
|
|
388
389
|
---
|
|
389
390
|
|
|
390
|
-
##
|
|
391
|
-
|
|
392
|
-
### 1. Create a lab file
|
|
393
|
-
|
|
394
|
-
```bash
|
|
395
|
-
# example.labs.js
|
|
396
|
-
```
|
|
391
|
+
## 💡 Real-World Example (v1.2.0)
|
|
397
392
|
|
|
398
393
|
```typescript
|
|
399
394
|
import { newLabs } from "fenneckit";
|
|
400
395
|
|
|
401
|
-
await newLabs("
|
|
402
|
-
kit.
|
|
396
|
+
await newLabs("E-Commerce API Test", async (kit) => {
|
|
397
|
+
const user = kit.namespace("user");
|
|
398
|
+
const order = kit.namespace("order");
|
|
403
399
|
|
|
404
|
-
await kit.test("
|
|
405
|
-
const
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
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"
|
|
404
|
+
});
|
|
409
405
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
kit.
|
|
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");
|
|
413
411
|
});
|
|
414
|
-
});
|
|
415
|
-
```
|
|
416
412
|
|
|
417
|
-
|
|
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
|
+
});
|
|
418
417
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
418
|
+
order.set("id", res.data.orderId);
|
|
419
|
+
order.set("total", res.data.total);
|
|
420
|
+
kit.done(`Order created: ${res.data.orderId}`);
|
|
421
|
+
});
|
|
422
422
|
|
|
423
|
-
|
|
423
|
+
await kit.test("Security Cleanup", async () => {
|
|
424
|
+
kit.clearSecret("token"); // remove sensitive data
|
|
425
|
+
kit.done("Secrets cleared");
|
|
426
|
+
});
|
|
424
427
|
|
|
425
|
-
|
|
426
|
-
|
|
428
|
+
await kit.test("Audit Check", async () => {
|
|
429
|
+
const log = kit.audit.getLast(10);
|
|
430
|
+
kit.log(`Audit entries: ${log.length}`);
|
|
431
|
+
});
|
|
432
|
+
});
|
|
427
433
|
```
|
|
428
434
|
|
|
429
435
|
---
|
|
430
436
|
|
|
431
|
-
##
|
|
437
|
+
## 📊 Storage Lifecycle (per file)
|
|
432
438
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
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
|
|
453
|
+
```
|
|
442
454
|
|
|
443
455
|
---
|
|
444
456
|
|
|
445
|
-
## 🎯 Best Practices
|
|
457
|
+
## 🎯 Best Practices (v1.2.0)
|
|
446
458
|
|
|
447
|
-
1. **
|
|
459
|
+
1. **Secrets always go into the Vault**
|
|
448
460
|
```typescript
|
|
449
|
-
kit.
|
|
450
|
-
kit.setStore("
|
|
461
|
+
kit.setSecret("token", token); // ✅
|
|
462
|
+
kit.setStore("token", token); // ❌ avoid
|
|
451
463
|
```
|
|
452
464
|
|
|
453
|
-
2. **
|
|
465
|
+
2. **Use namespaces for related data**
|
|
454
466
|
```typescript
|
|
455
|
-
const
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
}
|
|
467
|
+
const user = kit.namespace("user");
|
|
468
|
+
user.set("id", id);
|
|
469
|
+
user.set("email", email);
|
|
459
470
|
```
|
|
460
471
|
|
|
461
|
-
3. **Clear sensitive data**
|
|
472
|
+
3. **Clear sensitive data when no longer needed**
|
|
462
473
|
```typescript
|
|
463
|
-
kit.
|
|
474
|
+
kit.clearSecret("accessToken");
|
|
464
475
|
kit.clearStore("creditCard");
|
|
465
476
|
```
|
|
466
477
|
|
|
467
|
-
4. **
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
---
|
|
474
|
-
|
|
475
|
-
## 🔄 Complete Multi-Lab Example (Payment → Invoice)
|
|
476
|
-
|
|
477
|
-
```typescript
|
|
478
|
-
import { newLabs, clearStore } from "fenneckit";
|
|
479
|
-
|
|
480
|
-
await newLabs("Payment Validation", async (kit) => {
|
|
481
|
-
await kit.test("Validate Payment Details", async () => {
|
|
482
|
-
kit.setStore("customerId", "cust_123");
|
|
483
|
-
kit.setStore("amount", 299.99);
|
|
484
|
-
kit.setStore("currency", "USD");
|
|
485
|
-
kit.setTemp("validatedAt", Date.now());
|
|
486
|
-
kit.done("Payment validated: 299.99 USD");
|
|
487
|
-
});
|
|
488
|
-
});
|
|
489
|
-
|
|
490
|
-
await newLabs("Process Charge", async (kit) => {
|
|
491
|
-
await kit.test("Charge Card", async () => {
|
|
492
|
-
const amount = kit.getStore("amount");
|
|
493
|
-
const chargeId = `charge_${Date.now()}`;
|
|
494
|
-
kit.setStore("chargeId", chargeId);
|
|
495
|
-
kit.setStore("chargedAt", new Date().toISOString());
|
|
496
|
-
kit.done(`Charged: ${chargeId} for ${amount}`);
|
|
497
|
-
});
|
|
498
|
-
});
|
|
499
|
-
|
|
500
|
-
// Clear payment details (security)
|
|
501
|
-
clearStore("chargeId");
|
|
502
|
-
|
|
503
|
-
await newLabs("Generate Invoice", async (kit) => {
|
|
504
|
-
await kit.test("Create Invoice PDF", async () => {
|
|
505
|
-
const invoiceId = `inv_${Date.now()}`;
|
|
506
|
-
kit.setStore("invoiceId", invoiceId);
|
|
507
|
-
kit.done(`Invoice created: ${invoiceId}`);
|
|
508
|
-
});
|
|
509
|
-
|
|
510
|
-
await kit.test("Send Invoice Email", async () => {
|
|
511
|
-
const invoiceId = kit.getStore("invoiceId");
|
|
512
|
-
const customerId = kit.getStore("customerId");
|
|
513
|
-
kit.done(`Invoice ${invoiceId} emailed to ${customerId}`);
|
|
514
|
-
});
|
|
515
|
-
});
|
|
516
|
-
```
|
|
517
|
-
|
|
518
|
-
Run:
|
|
519
|
-
|
|
520
|
-
```bash
|
|
521
|
-
npx fenneckit payment-pipeline.labs.js
|
|
522
|
-
```
|
|
523
|
-
|
|
524
|
-
---
|
|
525
|
-
|
|
526
|
-
## 📞 Troubleshooting
|
|
527
|
-
|
|
528
|
-
**Q: Lab 2 cannot see Lab 1 data?**
|
|
529
|
-
A: You used `setTemp`. Switch to `setStore`.
|
|
530
|
-
|
|
531
|
-
**Q: I cleared data but it's still there?**
|
|
532
|
-
A: Make sure you're using `clearStore()` correctly. Check key name.
|
|
533
|
-
|
|
534
|
-
**Q: Can STORE survive across different files?**
|
|
535
|
-
A: No. Each file execution has its own isolated STORE.
|
|
536
|
-
`npx fenneckit a.labs.js` and `npx fenneckit b.labs.js` do not share data.
|
|
537
|
-
|
|
538
|
-
**Q: How is TEMP cleaned?**
|
|
539
|
-
A: Automatically when the Lab finishes. No manual cleanup needed.
|
|
478
|
+
4. **Check before use**
|
|
479
|
+
```typescript
|
|
480
|
+
const token = kit.getSecret("token");
|
|
481
|
+
if (!token) kit.err("Token missing!");
|
|
482
|
+
```
|
|
540
483
|
|
|
541
|
-
**
|
|
542
|
-
A:
|
|
543
|
-
```bash
|
|
544
|
-
npx fenneckit file1.labs.js
|
|
545
|
-
npx fenneckit file2.labs.js
|
|
546
|
-
# or let the runner discover all *.labs.* files
|
|
547
|
-
npx fenneckit
|
|
548
|
-
```
|
|
484
|
+
5. **Never rely on cross-file sharing** – write to disk/DB if needed.
|
|
549
485
|
|
|
550
486
|
---
|
|
551
487
|
|
|
@@ -557,32 +493,40 @@ npx fenneckit
|
|
|
557
493
|
- `⚠️` / `🟡` Warning
|
|
558
494
|
- `📝` / `⚪` Info
|
|
559
495
|
- `⚙️` Store operation
|
|
496
|
+
- `🔐` Secret operation
|
|
560
497
|
- `⏱️` Temp operation
|
|
561
498
|
- `🧹` Clear operation
|
|
499
|
+
- `⏰` TTL expiration
|
|
562
500
|
|
|
563
501
|
---
|
|
564
502
|
|
|
565
503
|
## 💪 Perfect For
|
|
566
504
|
|
|
567
|
-
- Backend API testing (Express, Fastify, etc.)
|
|
505
|
+
- Backend API testing (Express, Fastify, NestJS, etc.)
|
|
506
|
+
- Security-conscious test flows (tokens, keys)
|
|
568
507
|
- Database migration validation
|
|
569
|
-
- CLI tool workflows
|
|
570
508
|
- Microservice chaining
|
|
571
509
|
- Pre-deployment smoke checks
|
|
572
|
-
-
|
|
573
|
-
- State management testing
|
|
510
|
+
- State management + audit trails
|
|
574
511
|
- Data pipeline validation
|
|
575
512
|
|
|
576
513
|
---
|
|
577
514
|
|
|
578
515
|
## 📝 Version History
|
|
579
516
|
|
|
580
|
-
### v1.
|
|
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
|
|
524
|
+
|
|
525
|
+
### v1.1.0
|
|
581
526
|
- ✨ Added `clearStore()` for store management
|
|
582
527
|
- 🌳 Improved data sharing hierarchy documentation
|
|
583
|
-
- 🧹 Better state cleanup capabilities
|
|
584
528
|
|
|
585
|
-
### v1.0.0
|
|
529
|
+
### v1.0.0
|
|
586
530
|
- Core Lab functionality
|
|
587
531
|
- STORE & TEMP storage
|
|
588
532
|
- Zero config runner
|
|
@@ -594,7 +538,7 @@ npx fenneckit
|
|
|
594
538
|
|
|
595
539
|
**Copyright © 2026 Lasith Ruwantha Amrwansha**
|
|
596
540
|
Written: 2026/09/17
|
|
597
|
-
Updated: 2026/09/
|
|
541
|
+
Updated: 2026/09/21
|
|
598
542
|
Author: Ruwantha Amrwansha
|
|
599
543
|
Library: FennecKit 🦊
|
|
600
544
|
|