footprintjs 0.2.3 → 0.4.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 +125 -285
- package/dist/esm/index.js +13 -1
- package/dist/esm/lib/builder/FlowChartBuilder.js +19 -1
- package/dist/esm/lib/builder/types.js +1 -1
- package/dist/esm/lib/contract/defineContract.js +32 -0
- package/dist/esm/lib/contract/index.js +18 -0
- package/dist/esm/lib/contract/openapi.js +120 -0
- package/dist/esm/lib/contract/schema.js +199 -0
- package/dist/esm/lib/contract/types.js +9 -0
- package/dist/esm/lib/engine/index.js +12 -1
- package/dist/esm/lib/engine/narrative/FlowRecorderDispatcher.js +187 -0
- package/dist/esm/lib/engine/narrative/NarrativeFlowRecorder.js +89 -0
- package/dist/esm/lib/engine/narrative/index.js +12 -1
- package/dist/esm/lib/engine/narrative/recorders/AdaptiveNarrativeFlowRecorder.js +48 -0
- package/dist/esm/lib/engine/narrative/recorders/MilestoneNarrativeFlowRecorder.js +41 -0
- package/dist/esm/lib/engine/narrative/recorders/ProgressiveNarrativeFlowRecorder.js +54 -0
- package/dist/esm/lib/engine/narrative/recorders/RLENarrativeFlowRecorder.js +74 -0
- package/dist/esm/lib/engine/narrative/recorders/SeparateNarrativeFlowRecorder.js +55 -0
- package/dist/esm/lib/engine/narrative/recorders/SilentNarrativeFlowRecorder.js +50 -0
- package/dist/esm/lib/engine/narrative/recorders/WindowedNarrativeFlowRecorder.js +82 -0
- package/dist/esm/lib/engine/narrative/recorders/index.js +9 -0
- package/dist/esm/lib/engine/narrative/types.js +1 -1
- package/dist/esm/lib/engine/traversal/FlowchartTraverser.js +28 -6
- package/dist/esm/lib/runner/FlowChartExecutor.js +21 -1
- package/dist/esm/types/index.d.ts +13 -0
- package/dist/esm/types/lib/builder/FlowChartBuilder.d.ts +9 -0
- package/dist/esm/types/lib/builder/types.d.ts +6 -0
- package/dist/esm/types/lib/contract/defineContract.d.ts +18 -0
- package/dist/esm/types/lib/contract/index.d.ts +14 -0
- package/dist/esm/types/lib/contract/openapi.d.ts +12 -0
- package/dist/esm/types/lib/contract/schema.d.ts +14 -0
- package/dist/esm/types/lib/contract/types.d.ts +94 -0
- package/dist/esm/types/lib/engine/index.d.ts +10 -0
- package/dist/esm/types/lib/engine/narrative/FlowRecorderDispatcher.d.ts +38 -0
- package/dist/esm/types/lib/engine/narrative/NarrativeFlowRecorder.d.ts +31 -0
- package/dist/esm/types/lib/engine/narrative/index.d.ts +10 -0
- package/dist/esm/types/lib/engine/narrative/recorders/AdaptiveNarrativeFlowRecorder.d.ts +25 -0
- package/dist/esm/types/lib/engine/narrative/recorders/MilestoneNarrativeFlowRecorder.d.ts +24 -0
- package/dist/esm/types/lib/engine/narrative/recorders/ProgressiveNarrativeFlowRecorder.d.ts +30 -0
- package/dist/esm/types/lib/engine/narrative/recorders/RLENarrativeFlowRecorder.d.ts +25 -0
- package/dist/esm/types/lib/engine/narrative/recorders/SeparateNarrativeFlowRecorder.d.ts +32 -0
- package/dist/esm/types/lib/engine/narrative/recorders/SilentNarrativeFlowRecorder.d.ts +25 -0
- package/dist/esm/types/lib/engine/narrative/recorders/WindowedNarrativeFlowRecorder.d.ts +29 -0
- package/dist/esm/types/lib/engine/narrative/recorders/index.d.ts +7 -0
- package/dist/esm/types/lib/engine/narrative/types.d.ts +79 -0
- package/dist/esm/types/lib/engine/traversal/FlowchartTraverser.d.ts +7 -0
- package/dist/esm/types/lib/runner/FlowChartExecutor.d.ts +12 -0
- package/dist/index.js +26 -2
- package/dist/lib/builder/FlowChartBuilder.js +19 -1
- package/dist/lib/builder/types.js +1 -1
- package/dist/lib/contract/defineContract.js +36 -0
- package/dist/lib/contract/index.js +26 -0
- package/dist/lib/contract/openapi.js +124 -0
- package/dist/lib/contract/schema.js +205 -0
- package/dist/lib/contract/types.js +10 -0
- package/dist/lib/engine/index.js +22 -2
- package/dist/lib/engine/narrative/FlowRecorderDispatcher.js +191 -0
- package/dist/lib/engine/narrative/NarrativeFlowRecorder.js +93 -0
- package/dist/lib/engine/narrative/index.js +22 -2
- package/dist/lib/engine/narrative/recorders/AdaptiveNarrativeFlowRecorder.js +52 -0
- package/dist/lib/engine/narrative/recorders/MilestoneNarrativeFlowRecorder.js +45 -0
- package/dist/lib/engine/narrative/recorders/ProgressiveNarrativeFlowRecorder.js +58 -0
- package/dist/lib/engine/narrative/recorders/RLENarrativeFlowRecorder.js +78 -0
- package/dist/lib/engine/narrative/recorders/SeparateNarrativeFlowRecorder.js +59 -0
- package/dist/lib/engine/narrative/recorders/SilentNarrativeFlowRecorder.js +54 -0
- package/dist/lib/engine/narrative/recorders/WindowedNarrativeFlowRecorder.js +86 -0
- package/dist/lib/engine/narrative/recorders/index.js +19 -0
- package/dist/lib/engine/narrative/types.js +1 -1
- package/dist/lib/engine/traversal/FlowchartTraverser.js +28 -6
- package/dist/lib/runner/FlowChartExecutor.js +21 -1
- package/dist/types/index.d.ts +13 -0
- package/dist/types/lib/builder/FlowChartBuilder.d.ts +9 -0
- package/dist/types/lib/builder/types.d.ts +6 -0
- package/dist/types/lib/contract/defineContract.d.ts +18 -0
- package/dist/types/lib/contract/index.d.ts +14 -0
- package/dist/types/lib/contract/openapi.d.ts +12 -0
- package/dist/types/lib/contract/schema.d.ts +14 -0
- package/dist/types/lib/contract/types.d.ts +94 -0
- package/dist/types/lib/engine/index.d.ts +10 -0
- package/dist/types/lib/engine/narrative/FlowRecorderDispatcher.d.ts +38 -0
- package/dist/types/lib/engine/narrative/NarrativeFlowRecorder.d.ts +31 -0
- package/dist/types/lib/engine/narrative/index.d.ts +10 -0
- package/dist/types/lib/engine/narrative/recorders/AdaptiveNarrativeFlowRecorder.d.ts +25 -0
- package/dist/types/lib/engine/narrative/recorders/MilestoneNarrativeFlowRecorder.d.ts +24 -0
- package/dist/types/lib/engine/narrative/recorders/ProgressiveNarrativeFlowRecorder.d.ts +30 -0
- package/dist/types/lib/engine/narrative/recorders/RLENarrativeFlowRecorder.d.ts +25 -0
- package/dist/types/lib/engine/narrative/recorders/SeparateNarrativeFlowRecorder.d.ts +32 -0
- package/dist/types/lib/engine/narrative/recorders/SilentNarrativeFlowRecorder.d.ts +25 -0
- package/dist/types/lib/engine/narrative/recorders/WindowedNarrativeFlowRecorder.d.ts +29 -0
- package/dist/types/lib/engine/narrative/recorders/index.d.ts +7 -0
- package/dist/types/lib/engine/narrative/types.d.ts +79 -0
- package/dist/types/lib/engine/traversal/FlowchartTraverser.d.ts +7 -0
- package/dist/types/lib/runner/FlowChartExecutor.d.ts +12 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<p align="center">
|
|
2
2
|
<h1 align="center">FootPrint</h1>
|
|
3
3
|
<p align="center">
|
|
4
|
-
<strong>
|
|
4
|
+
<strong>The flowchart pattern for backend code — self-explainable systems that AI can reason about.</strong>
|
|
5
5
|
</p>
|
|
6
6
|
</p>
|
|
7
7
|
|
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
|
|
16
16
|
<br>
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
**MVC is a pattern for structuring backends. FootPrint is a different pattern — the flowchart pattern — where your business logic is organized as a graph of functions with transactional state.** The code becomes self-explainable: AI can read the structure, trace every decision, and explain what happened without reconstructing from logs.
|
|
19
|
+
|
|
20
|
+
> FootPrint is **not** a workflow engine, pipeline builder, or orchestrator. It's a code pattern — like how React changed how we build UIs, FootPrint changes how we structure backend logic to be AI-native.
|
|
19
21
|
|
|
20
22
|
```bash
|
|
21
23
|
npm install footprintjs
|
|
@@ -49,18 +51,26 @@ console.log(executor.getNarrative());
|
|
|
49
51
|
|
|
50
52
|
> **[Try it in the browser](https://footprintjs.github.io/footprint-playground/)** — no install needed
|
|
51
53
|
>
|
|
52
|
-
> **[Browse
|
|
54
|
+
> **[Browse 25+ examples](https://github.com/footprintjs/footPrint-samples)** — features, flowchart patterns, flow recorder strategies, and a full loan underwriting demo
|
|
53
55
|
|
|
54
56
|
---
|
|
55
57
|
|
|
56
|
-
## Why
|
|
58
|
+
## Why a new pattern?
|
|
59
|
+
|
|
60
|
+
**MVC** separates concerns into Model, View, Controller. It works, but the code is opaque to AI — an LLM can't trace why a request produced a specific result without parsing scattered logs.
|
|
61
|
+
|
|
62
|
+
**The flowchart pattern** structures the same logic as a graph of named functions with managed state. This gives you two things MVC can't:
|
|
57
63
|
|
|
58
|
-
|
|
64
|
+
1. **Self-describing code** — The structure auto-generates tool descriptions for LLM agents. No hand-written descriptions that drift from reality.
|
|
65
|
+
2. **Self-explaining execution** — Every run produces a causal trace showing what happened and why. An LLM reads the trace and explains decisions accurately — no hallucination.
|
|
66
|
+
|
|
67
|
+
| | MVC / Traditional | Flowchart Pattern (FootPrint) |
|
|
59
68
|
|---|---|---|
|
|
60
|
-
| **
|
|
61
|
-
| **
|
|
62
|
-
| **
|
|
69
|
+
| **Code structure** | Controllers with implicit flow | Explicit graph of named functions |
|
|
70
|
+
| **LLM explains a decision** | Reconstruct from scattered logs | Read the causal trace directly |
|
|
71
|
+
| **Tool descriptions for agents** | Write and maintain by hand | Auto-generated from the graph |
|
|
63
72
|
| **State management** | Global/manual, race-prone | Transactional scope with atomic commits |
|
|
73
|
+
| **Debugging** | `console.log` + guesswork | Time-travel replay to any stage |
|
|
64
74
|
|
|
65
75
|
### Example: Loan rejection
|
|
66
76
|
|
|
@@ -104,7 +114,7 @@ That answer came from the trace — not from the LLM's imagination.
|
|
|
104
114
|
|
|
105
115
|
## The code that produced it
|
|
106
116
|
|
|
107
|
-
No one wrote those trace sentences.
|
|
117
|
+
This is regular backend code — just structured as a flowchart instead of a controller. No one wrote those trace sentences. The functions just read and write scope; the pattern produces the narrative automatically:
|
|
108
118
|
|
|
109
119
|
```typescript
|
|
110
120
|
import {
|
|
@@ -189,15 +199,17 @@ await executor.run();
|
|
|
189
199
|
const narrative = executor.getNarrative(); // ← the trace above
|
|
190
200
|
```
|
|
191
201
|
|
|
192
|
-
|
|
202
|
+
The functions are ordinary TypeScript — no decorators, no special annotations. The flowchart pattern captures stage transitions, decisions, reads, and writes automatically. That's the difference from MVC: in MVC, this trace doesn't exist. In the flowchart pattern, it's a byproduct of the structure.
|
|
193
203
|
|
|
194
204
|
---
|
|
195
205
|
|
|
196
|
-
##
|
|
206
|
+
## What makes it self-explainable
|
|
207
|
+
|
|
208
|
+
The flowchart pattern produces two AI-readable outputs automatically — no extra code needed:
|
|
197
209
|
|
|
198
|
-
### Build-time: tool description for LLM
|
|
210
|
+
### Build-time: tool description for LLM agents
|
|
199
211
|
|
|
200
|
-
When you call `.build()`,
|
|
212
|
+
When you call `.build()`, the structure auto-generates `chart.description` — a complete description of what the code does:
|
|
201
213
|
|
|
202
214
|
```
|
|
203
215
|
FlowChart: ReceiveApplication
|
|
@@ -244,19 +256,19 @@ const tools = [
|
|
|
244
256
|
|
|
245
257
|
### Runtime: causal trace for LLM explanation
|
|
246
258
|
|
|
247
|
-
After `.run()`, the
|
|
259
|
+
After `.run()`, the trace shows what the code *actually did* — every value read, every value written, every decision made. Ship it alongside the result so any LLM can explain what happened and why.
|
|
248
260
|
|
|
249
|
-
**Build-time tells the LLM
|
|
261
|
+
**Build-time tells the LLM what the code does. Runtime tells the LLM what the code did.** That's what makes it self-explainable — the pattern produces both automatically.
|
|
250
262
|
|
|
251
263
|
---
|
|
252
264
|
|
|
253
|
-
## How
|
|
265
|
+
## How the pattern works
|
|
254
266
|
|
|
255
|
-
|
|
267
|
+
The flowchart pattern has three parts — the same way MVC has Model, View, Controller:
|
|
256
268
|
|
|
257
|
-
1. **
|
|
258
|
-
2. **
|
|
259
|
-
3. **Engine** —
|
|
269
|
+
1. **Builder** — Define your business logic as a graph of named functions. Like how a controller defines routes, but the structure is an explicit flowchart.
|
|
270
|
+
2. **Scope** — Transactional state shared across stages. Writes are buffered and committed atomically. This replaces scattered state management.
|
|
271
|
+
3. **Engine** — Executes the graph and auto-generates the causal trace. You write functions; the pattern produces the explanation.
|
|
260
272
|
|
|
261
273
|
```
|
|
262
274
|
┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐
|
|
@@ -273,229 +285,91 @@ FootPrint has three moving parts:
|
|
|
273
285
|
|
|
274
286
|
---
|
|
275
287
|
|
|
276
|
-
##
|
|
288
|
+
## Documentation
|
|
277
289
|
|
|
278
|
-
|
|
290
|
+
| Guide | What it covers |
|
|
291
|
+
|-------|---------------|
|
|
292
|
+
| **[Patterns](docs/guides/patterns.md)** | All 7 flowchart patterns with diagrams |
|
|
293
|
+
| **[Scope](docs/guides/scope.md)** | Typed, raw, and Zod scope; recorders; protection |
|
|
294
|
+
| **[Execution Control](docs/guides/execution.md)** | breakFn, cancellation, timeout, fail-fast, loops |
|
|
295
|
+
| **[Error Handling](docs/guides/error-handling.md)** | Commit-on-error, debug recorder, post-mortem |
|
|
296
|
+
| **[Flow Recorders](docs/guides/flow-recorders.md)** | Pluggable observers for control flow narrative — 7 built-in strategies |
|
|
297
|
+
| **[Contracts](docs/guides/contracts.md)** | defineContract, OpenAPI 3.1, Zod vs JSON Schema |
|
|
298
|
+
| **[Internals](docs/internals/)** | Architecture deep-dives for each library |
|
|
279
299
|
|
|
280
|
-
|
|
281
|
-
import { flowChart } from 'footprintjs';
|
|
300
|
+
---
|
|
282
301
|
|
|
283
|
-
|
|
284
|
-
.addFunction('B', fnB)
|
|
285
|
-
.addFunction('C', fnC)
|
|
286
|
-
.build();
|
|
287
|
-
```
|
|
302
|
+
## Patterns
|
|
288
303
|
|
|
289
|
-
|
|
304
|
+
Seven composition patterns — linear, parallel, conditional, multi-select, subflow, streaming, and loops:
|
|
290
305
|
|
|
291
306
|
```typescript
|
|
307
|
+
// Linear: A → B → C
|
|
308
|
+
flowChart('A', fnA).addFunction('B', fnB).addFunction('C', fnC).build();
|
|
309
|
+
|
|
310
|
+
// Parallel fork
|
|
292
311
|
flowChart('Fetch', fetchFn)
|
|
293
312
|
.addListOfFunction([
|
|
294
313
|
{ id: 'html', name: 'ParseHTML', fn: parseHTML },
|
|
295
314
|
{ id: 'css', name: 'ParseCSS', fn: parseCSS },
|
|
296
|
-
{ id: 'js', name: 'ParseJS', fn: parseJS },
|
|
297
315
|
])
|
|
298
316
|
.addFunction('Merge', mergeFn)
|
|
299
317
|
.build();
|
|
300
|
-
```
|
|
301
|
-
|
|
302
|
-
### Conditional (Decider)
|
|
303
318
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
```typescript
|
|
319
|
+
// Conditional branching (decider)
|
|
307
320
|
flowChart('Classify', classifyFn)
|
|
308
|
-
.addDeciderFunction('Route',
|
|
309
|
-
const type = scope.getValue('fulfillmentType');
|
|
310
|
-
return type === 'digital' ? 'digital' : 'physical';
|
|
311
|
-
})
|
|
321
|
+
.addDeciderFunction('Route', routeFn)
|
|
312
322
|
.addFunctionBranch('digital', 'DigitalDelivery', digitalFn)
|
|
313
323
|
.addFunctionBranch('physical', 'ShipPackage', shipFn)
|
|
314
324
|
.setDefault('physical')
|
|
315
325
|
.end()
|
|
316
326
|
.build();
|
|
317
|
-
```
|
|
318
|
-
|
|
319
|
-
### Subflow Composition
|
|
320
|
-
|
|
321
|
-
Mount entire flowcharts as nodes in a larger workflow:
|
|
322
|
-
|
|
323
|
-
```typescript
|
|
324
|
-
const faqFlow = flowChart('FAQ_Entry', faqEntryFn)
|
|
325
|
-
.addFunction('FAQ_Answer', faqAnswerFn)
|
|
326
|
-
.build();
|
|
327
|
-
|
|
328
|
-
const ragFlow = flowChart('RAG_Entry', ragEntryFn)
|
|
329
|
-
.addFunction('RAG_Retrieve', ragRetrieveFn)
|
|
330
|
-
.addFunction('RAG_Answer', ragAnswerFn)
|
|
331
|
-
.build();
|
|
332
327
|
|
|
333
|
-
|
|
328
|
+
// Subflow composition
|
|
329
|
+
flowChart('Router', routerFn)
|
|
334
330
|
.addSubFlowChart('faq', faqFlow, 'FAQ Handler')
|
|
335
331
|
.addSubFlowChart('rag', ragFlow, 'RAG Handler')
|
|
336
|
-
.addFunction('Aggregate', aggregateFn)
|
|
337
332
|
.build();
|
|
338
|
-
```
|
|
339
333
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
```typescript
|
|
343
|
-
const chart = flowChart('PreparePrompt', prepareFn)
|
|
344
|
-
.addStreamingFunction('AskLLM', 'llm-stream', askLLMFn)
|
|
345
|
-
.onStream((streamId, token) => process.stdout.write(token))
|
|
346
|
-
.onStreamEnd((streamId, fullText) => console.log('\nDone:', fullText))
|
|
347
|
-
.addFunction('ProcessResponse', processFn)
|
|
348
|
-
.build();
|
|
349
|
-
```
|
|
350
|
-
|
|
351
|
-
### Execution Control
|
|
352
|
-
|
|
353
|
-
Every stage receives `(scope, breakFn)`. Three levels of control:
|
|
354
|
-
|
|
355
|
-
```typescript
|
|
356
|
-
// 1. breakFn() — Graceful stop: complete this stage, skip remaining stages
|
|
357
|
-
const validateInput = async (scope: ScopeFacade, breakFn: () => void) => {
|
|
358
|
-
const amount = scope.getValue('loanAmount') as number;
|
|
359
|
-
if (amount > 50_000) {
|
|
360
|
-
scope.setValue('rejection', 'Exceeds maximum loan amount');
|
|
361
|
-
breakFn(); // stage output is returned, no error — pipeline just stops
|
|
362
|
-
}
|
|
363
|
-
};
|
|
364
|
-
|
|
365
|
-
// 2. throw — Hard abort: stop immediately, propagate error to caller
|
|
366
|
-
const callExternalAPI = async (scope: ScopeFacade) => {
|
|
367
|
-
const response = await fetch(scope.getValue('apiUrl') as string);
|
|
368
|
-
if (response.status === 403) {
|
|
369
|
-
throw new Error('Access denied — cannot continue'); // executor.run() rejects
|
|
370
|
-
}
|
|
371
|
-
};
|
|
372
|
-
|
|
373
|
-
// 3. AbortSignal — External cancellation (see Cancellation & Timeout section)
|
|
374
|
-
await executor.run({ timeoutMs: 30_000 });
|
|
375
|
-
```
|
|
376
|
-
|
|
377
|
-
| Mechanism | Trigger | Stage completes? | Returns |
|
|
378
|
-
|-----------|---------|-----------------|---------|
|
|
379
|
-
| `breakFn()` | Inside stage | Yes | Stage output (no error) |
|
|
380
|
-
| `throw` | Inside stage | No | Error propagates |
|
|
381
|
-
| `AbortSignal` | Outside pipeline | Races async | Error propagates |
|
|
382
|
-
|
|
383
|
-
### Loops
|
|
384
|
-
|
|
385
|
-
```typescript
|
|
334
|
+
// Loops
|
|
386
335
|
flowChart('Init', initFn)
|
|
387
|
-
.addFunction('
|
|
388
|
-
.
|
|
389
|
-
|
|
390
|
-
.addFunctionBranch('
|
|
391
|
-
.addFunctionBranch('no', 'Finalize', finalizeFn)
|
|
336
|
+
.addFunction('Retry', retryFn, 'retry')
|
|
337
|
+
.addDeciderFunction('Check', checkFn)
|
|
338
|
+
.addFunctionBranch('again', 'Process', processFn)
|
|
339
|
+
.addFunctionBranch('done', 'Finish', finishFn)
|
|
392
340
|
.end()
|
|
393
|
-
.loopTo('
|
|
341
|
+
.loopTo('retry')
|
|
394
342
|
.build();
|
|
395
343
|
```
|
|
396
344
|
|
|
345
|
+
**[Full patterns guide →](docs/guides/patterns.md)** — all seven patterns with diagrams and composition examples
|
|
346
|
+
|
|
397
347
|
---
|
|
398
348
|
|
|
399
349
|
## Scope
|
|
400
350
|
|
|
401
|
-
Each stage receives a **scope** — a transactional interface to shared state
|
|
402
|
-
|
|
403
|
-
### Typed Scope (Recommended)
|
|
404
|
-
|
|
405
|
-
Extend `ScopeFacade` with domain-specific getters for type-safe reads:
|
|
351
|
+
Each stage receives a **scope** — a transactional interface to shared state:
|
|
406
352
|
|
|
407
353
|
```typescript
|
|
408
|
-
|
|
409
|
-
|
|
354
|
+
// Typed scope (recommended)
|
|
410
355
|
class LoanScope extends ScopeFacade {
|
|
411
|
-
get creditScore(): number {
|
|
412
|
-
return this.getValue('creditScore') as number;
|
|
413
|
-
}
|
|
414
|
-
get riskTier(): string {
|
|
415
|
-
return this.getValue('riskTier') as string;
|
|
416
|
-
}
|
|
417
|
-
get dtiStatus(): string {
|
|
418
|
-
return this.getValue('dtiStatus') as string;
|
|
419
|
-
}
|
|
356
|
+
get creditScore(): number { return this.getValue('creditScore') as number; }
|
|
420
357
|
}
|
|
421
358
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
// In stage functions:
|
|
425
|
-
const assessRisk = async (scope: LoanScope) => {
|
|
426
|
-
if (scope.creditScore < 600 || scope.dtiStatus === 'excessive') {
|
|
427
|
-
scope.setValue('riskTier', 'high'); // writes go through setValue
|
|
428
|
-
}
|
|
429
|
-
};
|
|
430
|
-
```
|
|
431
|
-
|
|
432
|
-
> **Why `getValue`/`setValue` instead of direct properties?** Scope protection blocks `scope.foo = bar` — those writes bypass transactional buffering and recorder hooks. Typed getters give you clean reads; `setValue` gives you tracked writes.
|
|
433
|
-
|
|
434
|
-
### Raw Scope (Low-level)
|
|
435
|
-
|
|
436
|
-
```typescript
|
|
437
|
-
scope.setValue('total', 79.98); // overwrite
|
|
438
|
-
scope.updateValue('config', { retries: 3 }); // deep merge
|
|
439
|
-
const total = scope.getValue('total'); // read
|
|
440
|
-
```
|
|
441
|
-
|
|
442
|
-
### Validated Scope (Zod)
|
|
443
|
-
|
|
444
|
-
```typescript
|
|
445
|
-
import { z } from 'zod';
|
|
446
|
-
import { defineScopeFromZod } from 'footprintjs';
|
|
447
|
-
|
|
448
|
-
const schema = z.object({
|
|
359
|
+
// Validated scope (Zod)
|
|
360
|
+
const scopeFactory = defineScopeFromZod(z.object({
|
|
449
361
|
creditScore: z.number(),
|
|
450
362
|
riskTier: z.string().optional(),
|
|
451
|
-
});
|
|
363
|
+
}));
|
|
452
364
|
|
|
453
|
-
|
|
454
|
-
|
|
365
|
+
// Raw scope
|
|
366
|
+
scope.setValue('total', 79.98);
|
|
367
|
+
scope.updateValue('config', { retries: 3 });
|
|
455
368
|
```
|
|
456
369
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
## Observability
|
|
460
|
-
|
|
461
|
-
### Recorders
|
|
462
|
-
|
|
463
|
-
Recorders observe scope operations without modifying them. Attach multiple for different concerns:
|
|
464
|
-
|
|
465
|
-
```typescript
|
|
466
|
-
import {
|
|
467
|
-
ScopeFacade, DebugRecorder, MetricRecorder,
|
|
468
|
-
} from 'footprintjs';
|
|
469
|
-
|
|
470
|
-
const scopeFactory = (ctx: any, stageName: string) => {
|
|
471
|
-
const scope = new ScopeFacade(ctx, stageName);
|
|
472
|
-
scope.attachRecorder(new DebugRecorder({ verbosity: 'verbose' }));
|
|
473
|
-
scope.attachRecorder(new MetricRecorder());
|
|
474
|
-
return scope;
|
|
475
|
-
};
|
|
476
|
-
```
|
|
477
|
-
|
|
478
|
-
> **Note:** `NarrativeRecorder` is attached automatically when narrative is enabled via `setEnableNarrative()` or `executor.enableNarrative()`. You only need to attach it manually if you need custom options.
|
|
479
|
-
|
|
480
|
-
Error isolation is built in: if a recorder throws, the error is routed to `onError` hooks of other recorders, and the scope operation continues normally.
|
|
481
|
-
|
|
482
|
-
### Custom Recorders
|
|
483
|
-
|
|
484
|
-
Implement any subset of six hooks: `onRead`, `onWrite`, `onCommit`, `onError`, `onStageStart`, `onStageEnd`.
|
|
485
|
-
|
|
486
|
-
```typescript
|
|
487
|
-
import { Recorder, WriteEvent } from 'footprintjs';
|
|
488
|
-
|
|
489
|
-
class AuditRecorder implements Recorder {
|
|
490
|
-
readonly id = 'audit';
|
|
491
|
-
private writes: Array<{ stage: string; key: string; value: unknown }> = [];
|
|
370
|
+
Pluggable recorders observe every operation: `DebugRecorder`, `MetricRecorder`, `NarrativeRecorder`, or bring your own.
|
|
492
371
|
|
|
493
|
-
|
|
494
|
-
this.writes.push({ stage: event.stageName, key: event.key, value: event.value });
|
|
495
|
-
}
|
|
496
|
-
getWrites() { return [...this.writes]; }
|
|
497
|
-
}
|
|
498
|
-
```
|
|
372
|
+
**[Full scope guide →](docs/guides/scope.md)** — typed/raw/Zod scope, recorders, protection, provider system
|
|
499
373
|
|
|
500
374
|
---
|
|
501
375
|
|
|
@@ -513,95 +387,30 @@ class AuditRecorder implements Recorder {
|
|
|
513
387
|
| **Composable Subflows** | Mount entire flowcharts as nodes in larger workflows |
|
|
514
388
|
| **Streaming** | Built-in streaming stages for LLM token emission |
|
|
515
389
|
| **Pluggable Recorders** | DebugRecorder, MetricRecorder, NarrativeRecorder — or bring your own |
|
|
390
|
+
| **Flow Recorders** | 7 narrative strategies for loop summarization — Windowed, Silent, Adaptive, Progressive, Milestone, RLE, Separate — or build custom ([examples](https://github.com/footprintjs/footPrint-samples/tree/main/examples/flow-recorders)) |
|
|
516
391
|
|
|
517
392
|
---
|
|
518
393
|
|
|
519
|
-
##
|
|
520
|
-
|
|
521
|
-
For LLM pipelines where API calls can hang, `FlowChartExecutor.run()` supports cooperative cancellation:
|
|
522
|
-
|
|
523
|
-
```typescript
|
|
524
|
-
// Timeout: auto-abort after 30 seconds
|
|
525
|
-
const result = await executor.run({ timeoutMs: 30_000 });
|
|
526
|
-
|
|
527
|
-
// AbortSignal: cancel from outside
|
|
528
|
-
const controller = new AbortController();
|
|
529
|
-
setTimeout(() => controller.abort(), 10_000); // cancel after 10s
|
|
530
|
-
const result = await executor.run({ signal: controller.signal });
|
|
531
|
-
```
|
|
532
|
-
|
|
533
|
-
The signal is checked before each stage starts and raced against async stage functions. Aborted executions throw with the signal's reason.
|
|
534
|
-
|
|
535
|
-
## Fail-Fast Forks
|
|
536
|
-
|
|
537
|
-
By default, parallel children run to completion even if some fail (errors captured as `{ isError: true }`). For cases where you want immediate failure:
|
|
538
|
-
|
|
539
|
-
```typescript
|
|
540
|
-
flowChart('Fetch', fetchFn)
|
|
541
|
-
.addListOfFunction([
|
|
542
|
-
{ id: 'api1', name: 'CallAPI1', fn: api1Fn },
|
|
543
|
-
{ id: 'api2', name: 'CallAPI2', fn: api2Fn },
|
|
544
|
-
], { failFast: true }) // first child error rejects the whole fork
|
|
545
|
-
.build();
|
|
546
|
-
```
|
|
547
|
-
|
|
548
|
-
## Design: Error Handling
|
|
549
|
-
|
|
550
|
-
FootPrint's error handling is designed around one principle: **the trace must capture everything that happened, including failures**.
|
|
551
|
-
|
|
552
|
-
### Who is responsible for what
|
|
553
|
-
|
|
554
|
-
| Layer | Responsibility |
|
|
555
|
-
|-------|---------------|
|
|
556
|
-
| **Stage function** | Business logic. Throws errors when invariants break. |
|
|
557
|
-
| **Engine** | Infrastructure. Catches errors, commits the trace, records error metadata, then re-throws. |
|
|
558
|
-
| **Consumer** | Wraps `executor.run()` in try/catch. Inspects `getSnapshot()` after failure for debugging. |
|
|
559
|
-
|
|
560
|
-
### Commit-on-error: why it matters
|
|
394
|
+
## Execution Control & Error Handling
|
|
561
395
|
|
|
562
|
-
|
|
396
|
+
Three levels of control: `breakFn()` (graceful stop), `throw` (hard abort), `AbortSignal` (external cancellation):
|
|
563
397
|
|
|
564
398
|
```typescript
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
}
|
|
569
|
-
// The snapshot captures everything up to the failure point
|
|
570
|
-
const snapshot = executor.getSnapshot();
|
|
571
|
-
// commitLog has entries for every stage that ran (including the one that failed)
|
|
572
|
-
// executionTree shows scope writes, error metadata, and flow decisions
|
|
573
|
-
// An LLM can use this to explain WHY the error happened
|
|
574
|
-
}
|
|
575
|
-
```
|
|
576
|
-
|
|
577
|
-
Without commit-on-error, a failed stage's partial writes would be lost. The trace would end at the last successful stage, hiding the context of the failure. Commit-on-error gives consumers complete visibility:
|
|
578
|
-
|
|
579
|
-
- **Scope writes** made before the throw are preserved
|
|
580
|
-
- **Error metadata** (`stageExecutionError`) is recorded in the execution tree
|
|
581
|
-
- **Narrative** includes the error event (`"An error occurred at validate: ..."`)
|
|
582
|
-
- **Commit log** has an entry for the failed stage's state
|
|
583
|
-
|
|
584
|
-
### Error narrative in practice
|
|
585
|
-
|
|
586
|
-
A validation pipeline fails. The trace tells the story:
|
|
399
|
+
// Graceful stop — complete this stage, skip remaining
|
|
400
|
+
const validate = async (scope: ScopeFacade, breakFn: () => void) => {
|
|
401
|
+
if (scope.getValue('amount') > 50_000) { breakFn(); }
|
|
402
|
+
};
|
|
587
403
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
Stage 2: Next, it moved on to Validate.
|
|
592
|
-
Step 1: Read rawPayload = {name: "Bob", age: -5}
|
|
593
|
-
An error occurred at Validate: Validation failed: age must be positive.
|
|
404
|
+
// Timeout / external cancellation
|
|
405
|
+
await executor.run({ timeoutMs: 30_000 });
|
|
406
|
+
await executor.run({ signal: controller.signal });
|
|
594
407
|
```
|
|
595
408
|
|
|
596
|
-
|
|
409
|
+
When a stage throws, the engine commits the trace *before* re-throwing — so `getSnapshot()` captures everything up to the failure point, including partial writes and error metadata.
|
|
597
410
|
|
|
598
|
-
|
|
411
|
+
**[Execution control guide →](docs/guides/execution.md)** — breakFn, cancellation, timeout, fail-fast forks, loops
|
|
599
412
|
|
|
600
|
-
|
|
601
|
-
- **Partial results**: Fork children that succeed still return results (default mode)
|
|
602
|
-
- **Fail-fast**: Opt into `failFast: true` when any child error should abort the whole fork
|
|
603
|
-
- **Timeout/cancel**: Use `timeoutMs` or `AbortSignal` for external cancellation
|
|
604
|
-
- **Post-mortem**: Feed the narrative + snapshot to an LLM for root-cause analysis
|
|
413
|
+
**[Error handling guide →](docs/guides/error-handling.md)** — commit-on-error, debug recorder, error narrative, post-mortem
|
|
605
414
|
|
|
606
415
|
---
|
|
607
416
|
|
|
@@ -634,6 +443,8 @@ An LLM reading this trace can immediately explain: *"The validation failed becau
|
|
|
634
443
|
| `run(options?)` | Execute the flowchart. Options: `{ signal?, timeoutMs? }` |
|
|
635
444
|
| `getNarrative()` | Combined narrative (flow + data) with ScopeFacade; flow-only otherwise |
|
|
636
445
|
| `getFlowNarrative()` | Flow-only narrative sentences |
|
|
446
|
+
| `attachFlowRecorder(recorder)` | Attach a FlowRecorder for pluggable narrative control |
|
|
447
|
+
| `detachFlowRecorder(id)` | Detach a FlowRecorder by id |
|
|
637
448
|
| `getNarrativeEntries()` | Structured `CombinedNarrativeEntry[]` for programmatic use |
|
|
638
449
|
| `getSnapshot()` | Full execution tree + state |
|
|
639
450
|
| `getExtractedResults()` | Extractor results map |
|
|
@@ -645,14 +456,18 @@ An LLM reading this trace can immediately explain: *"The validation failed becau
|
|
|
645
456
|
|
|
646
457
|
## How FootPrint Compares
|
|
647
458
|
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
|
651
|
-
|
|
459
|
+
FootPrint is a **code pattern**, not an orchestrator. It runs in your process, not as a separate service.
|
|
460
|
+
|
|
461
|
+
| Aspect | MVC / async-await | FootPrint (flowchart pattern) | Temporal / Step Functions |
|
|
462
|
+
|--------|-------------------|-------------------------------|--------------------------|
|
|
463
|
+
| **What it is** | Code pattern | Code pattern | External orchestrator |
|
|
464
|
+
| **Runs where** | In your process | In your process | Separate service |
|
|
465
|
+
| **Control flow** | Implicit in code | Explicit graph of functions | External state machine |
|
|
466
|
+
| **State** | Manual / global | Transactional scope | Durable storage |
|
|
467
|
+
| **AI explains decisions** | Parse logs (hallucination-prone) | Read the causal trace (accurate) | Parse event history |
|
|
468
|
+
| **Tool descriptions** | Write by hand | Auto-generated from structure | Write by hand |
|
|
652
469
|
| **Debugging** | Stack traces | Time-travel replay | Event history |
|
|
653
|
-
| **
|
|
654
|
-
| **Tool Descriptions** | Manual | Auto-generated from structure | Manual |
|
|
655
|
-
| **Complexity** | Low | Medium | High |
|
|
470
|
+
| **Complexity** | Low | Low-medium | High |
|
|
656
471
|
|
|
657
472
|
---
|
|
658
473
|
|
|
@@ -681,9 +496,31 @@ Measured on Node v22, Apple Silicon. Run `npm run bench` to reproduce.
|
|
|
681
496
|
|
|
682
497
|
---
|
|
683
498
|
|
|
499
|
+
## Contract & OpenAPI
|
|
500
|
+
|
|
501
|
+
Define I/O schemas (Zod or raw JSON Schema) and auto-generate OpenAPI 3.1 specs:
|
|
502
|
+
|
|
503
|
+
```typescript
|
|
504
|
+
import { flowChart, defineContract } from 'footprintjs';
|
|
505
|
+
import { z } from 'zod';
|
|
506
|
+
|
|
507
|
+
const contract = defineContract(chart, {
|
|
508
|
+
inputSchema: z.object({ applicantName: z.string(), creditScore: z.number() }),
|
|
509
|
+
outputSchema: z.object({ decision: z.enum(['approved', 'rejected']) }),
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
const spec = contract.toOpenAPI({ version: '1.0.0', basePath: '/api' });
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
Zod is an **optional peer dependency** — zero bundle impact if not used.
|
|
516
|
+
|
|
517
|
+
**[Full contracts guide →](docs/guides/contracts.md)** — defineContract, OpenAPI generation, Zod vs JSON Schema, builder-level schemas
|
|
518
|
+
|
|
519
|
+
---
|
|
520
|
+
|
|
684
521
|
## Architecture
|
|
685
522
|
|
|
686
|
-
FootPrint is
|
|
523
|
+
FootPrint is the reference implementation of the flowchart pattern — six independent libraries, each usable standalone:
|
|
687
524
|
|
|
688
525
|
```
|
|
689
526
|
src/lib/
|
|
@@ -691,9 +528,12 @@ src/lib/
|
|
|
691
528
|
├── builder/ Fluent flowchart DSL (FlowChartBuilder, DeciderList, SelectorFnList)
|
|
692
529
|
├── scope/ Scope facades, recorders, protection, Zod integration
|
|
693
530
|
├── engine/ DFS traversal, handlers, narrative generators
|
|
694
|
-
|
|
531
|
+
├── runner/ Execution convenience (FlowChartExecutor, ExecutionRuntime)
|
|
532
|
+
└── contract/ I/O schemas, Zod→JSON Schema, OpenAPI 3.1 generation
|
|
695
533
|
```
|
|
696
534
|
|
|
535
|
+
**[Architecture deep-dives →](docs/internals/)** — each library has its own README with primitives, design decisions, and dependency graphs
|
|
536
|
+
|
|
697
537
|
---
|
|
698
538
|
|
|
699
539
|
## License
|
package/dist/esm/index.js
CHANGED
|
@@ -24,4 +24,16 @@ export { NarrativeRecorder } from './lib/scope';
|
|
|
24
24
|
// Zod-based scope definitions
|
|
25
25
|
export { defineScopeFromZod } from './lib/scope';
|
|
26
26
|
export { CombinedNarrativeBuilder } from './lib/engine';
|
|
27
|
-
|
|
27
|
+
export { NarrativeFlowRecorder } from './lib/engine';
|
|
28
|
+
// Built-in FlowRecorder strategies (tree-shakeable — import only what you use)
|
|
29
|
+
export { AdaptiveNarrativeFlowRecorder } from './lib/engine';
|
|
30
|
+
export { MilestoneNarrativeFlowRecorder } from './lib/engine';
|
|
31
|
+
export { ProgressiveNarrativeFlowRecorder } from './lib/engine';
|
|
32
|
+
export { RLENarrativeFlowRecorder } from './lib/engine';
|
|
33
|
+
export { SeparateNarrativeFlowRecorder } from './lib/engine';
|
|
34
|
+
export { SilentNarrativeFlowRecorder } from './lib/engine';
|
|
35
|
+
export { WindowedNarrativeFlowRecorder } from './lib/engine';
|
|
36
|
+
export { defineContract } from './lib/contract';
|
|
37
|
+
export { normalizeSchema, zodToJsonSchema } from './lib/contract';
|
|
38
|
+
export { generateOpenAPI } from './lib/contract';
|
|
39
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7OztHQVNHO0FBT0gsT0FBTyxFQUFFLFNBQVMsRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUU1RCwrRUFBK0U7QUFDL0UsdUNBQXVDO0FBQ3ZDLCtFQUErRTtBQUUvRSxPQUFPLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSxjQUFjLENBQUM7QUFFakQsK0VBQStFO0FBQy9FLDBDQUEwQztBQUMxQywrRUFBK0U7QUFFL0UsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUUxQyxZQUFZO0FBQ1osT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUM3QyxPQUFPLEVBQUUsYUFBYSxFQUFFLE1BQU0sYUFBYSxDQUFDO0FBQzVDLE9BQU8sRUFBRSxpQkFBaUIsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUtoRCw4QkFBOEI7QUFDOUIsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0sYUFBYSxDQUFDO0FBT2pELE9BQU8sRUFBRSx3QkFBd0IsRUFBRSxNQUFNLGNBQWMsQ0FBQztBQUl4RCxPQUFPLEVBQUUscUJBQXFCLEVBQUUsTUFBTSxjQUFjLENBQUM7QUFFckQsK0VBQStFO0FBQy9FLE9BQU8sRUFBRSw2QkFBNkIsRUFBRSxNQUFNLGNBQWMsQ0FBQztBQUM3RCxPQUFPLEVBQUUsOEJBQThCLEVBQUUsTUFBTSxjQUFjLENBQUM7QUFDOUQsT0FBTyxFQUFFLGdDQUFnQyxFQUFFLE1BQU0sY0FBYyxDQUFDO0FBQ2hFLE9BQU8sRUFBRSx3QkFBd0IsRUFBRSxNQUFNLGNBQWMsQ0FBQztBQUN4RCxPQUFPLEVBQUUsNkJBQTZCLEVBQUUsTUFBTSxjQUFjLENBQUM7QUFDN0QsT0FBTyxFQUFFLDJCQUEyQixFQUFFLE1BQU0sY0FBYyxDQUFDO0FBQzNELE9BQU8sRUFBRSw2QkFBNkIsRUFBRSxNQUFNLGNBQWMsQ0FBQztBQW9CN0QsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBQ2hELE9BQU8sRUFBRSxlQUFlLEVBQUUsZUFBZSxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFDbEUsT0FBTyxFQUFFLGVBQWUsRUFBRSxNQUFNLGdCQUFnQixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBGb290UHJpbnQg4oCUIFB1YmxpYyBBUElcbiAqXG4gKiBDb25uZWN0ZWQgY2F1c2FsIHRyYWNlIGxpYnJhcnkgZm9yIExMTSBwaXBlbGluZXMuXG4gKiBCdWlsZHMgZmxvd2NoYXJ0cywgZXhlY3V0ZXMgdGhlbSB2aWEgREZTIHRyYXZlcnNhbCwgYW5kIGNhcHR1cmVzXG4gKiBldmVyeSBzdGFnZSdzIGNvbnRleHQgKHN0YXRlLCBkZWNpc2lvbnMsIGVycm9ycykgaW4gYW4gYXVkaXRhYmxlIHRyYWNlLlxuICpcbiAqIEZvciBhZHZhbmNlZC9pbnRlcm5hbCBBUElzIChtZW1vcnkgcHJpbWl0aXZlcywgZW5naW5lIGhhbmRsZXJzLCBwcm92aWRlcnMpLFxuICogaW1wb3J0IGZyb20gJ2Zvb3RwcmludC9hZHZhbmNlZCcuXG4gKi9cblxuLy8gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxuLy8gQnVpbGRlciDigJQgRmxvd2NoYXJ0IGNvbnN0cnVjdGlvblxuLy8gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxuXG5leHBvcnQgdHlwZSB7IEZsb3dDaGFydCwgUGlwZWxpbmVTdGFnZUZ1bmN0aW9uIGFzIFN0YWdlSGFuZGxlciwgU3RyZWFtSGFuZGxlcnMgfSBmcm9tICcuL2xpYi9idWlsZGVyJztcbmV4cG9ydCB7IGZsb3dDaGFydCwgRmxvd0NoYXJ0QnVpbGRlciB9IGZyb20gJy4vbGliL2J1aWxkZXInO1xuXG4vLyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09XG4vLyBSdW5uZXIg4oCUIEV4ZWN1dGlvbiBjb252ZW5pZW5jZSBsYXllclxuLy8gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxuXG5leHBvcnQgeyBGbG93Q2hhcnRFeGVjdXRvciB9IGZyb20gJy4vbGliL3J1bm5lcic7XG5cbi8vID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT1cbi8vIFNjb3BlIOKAlCBQZXItc3RhZ2UgZmFjYWRlcyBhbmQgcmVjb3JkZXJzXG4vLyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09XG5cbmV4cG9ydCB7IFNjb3BlRmFjYWRlIH0gZnJvbSAnLi9saWIvc2NvcGUnO1xuXG4vLyBSZWNvcmRlcnNcbmV4cG9ydCB7IE1ldHJpY1JlY29yZGVyIH0gZnJvbSAnLi9saWIvc2NvcGUnO1xuZXhwb3J0IHsgRGVidWdSZWNvcmRlciB9IGZyb20gJy4vbGliL3Njb3BlJztcbmV4cG9ydCB7IE5hcnJhdGl2ZVJlY29yZGVyIH0gZnJvbSAnLi9saWIvc2NvcGUnO1xuXG4vLyBSZWNvcmRlciBpbnRlcmZhY2UgYW5kIGNvcmUgZXZlbnQgdHlwZXMgKG5lZWRlZCB0byBpbXBsZW1lbnQgY3VzdG9tIFJlY29yZGVyKVxuZXhwb3J0IHR5cGUgeyBDb21taXRFdmVudCwgRXJyb3JFdmVudCwgUmVhZEV2ZW50LCBSZWNvcmRlciwgV3JpdGVFdmVudCB9IGZyb20gJy4vbGliL3Njb3BlJztcblxuLy8gWm9kLWJhc2VkIHNjb3BlIGRlZmluaXRpb25zXG5leHBvcnQgeyBkZWZpbmVTY29wZUZyb21ab2QgfSBmcm9tICcuL2xpYi9zY29wZSc7XG5cbi8vID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT1cbi8vIEVuZ2luZSDigJQgTmFycmF0aXZlIChjb21tb25seSB1c2VkKVxuLy8gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxuXG5leHBvcnQgdHlwZSB7IENvbWJpbmVkTmFycmF0aXZlRW50cnkgfSBmcm9tICcuL2xpYi9lbmdpbmUnO1xuZXhwb3J0IHsgQ29tYmluZWROYXJyYXRpdmVCdWlsZGVyIH0gZnJvbSAnLi9saWIvZW5naW5lJztcblxuLy8gRmxvd1JlY29yZGVyIOKAlCBQbHVnZ2FibGUgb2JzZXJ2ZXIgZm9yIGNvbnRyb2wgZmxvdyBldmVudHMgKG1pcnJvcnMgc2NvcGUgUmVjb3JkZXIpXG5leHBvcnQgdHlwZSB7IEZsb3dMb29wRXZlbnQsIEZsb3dSZWNvcmRlciB9IGZyb20gJy4vbGliL2VuZ2luZSc7XG5leHBvcnQgeyBOYXJyYXRpdmVGbG93UmVjb3JkZXIgfSBmcm9tICcuL2xpYi9lbmdpbmUnO1xuXG4vLyBCdWlsdC1pbiBGbG93UmVjb3JkZXIgc3RyYXRlZ2llcyAodHJlZS1zaGFrZWFibGUg4oCUIGltcG9ydCBvbmx5IHdoYXQgeW91IHVzZSlcbmV4cG9ydCB7IEFkYXB0aXZlTmFycmF0aXZlRmxvd1JlY29yZGVyIH0gZnJvbSAnLi9saWIvZW5naW5lJztcbmV4cG9ydCB7IE1pbGVzdG9uZU5hcnJhdGl2ZUZsb3dSZWNvcmRlciB9IGZyb20gJy4vbGliL2VuZ2luZSc7XG5leHBvcnQgeyBQcm9ncmVzc2l2ZU5hcnJhdGl2ZUZsb3dSZWNvcmRlciB9IGZyb20gJy4vbGliL2VuZ2luZSc7XG5leHBvcnQgeyBSTEVOYXJyYXRpdmVGbG93UmVjb3JkZXIgfSBmcm9tICcuL2xpYi9lbmdpbmUnO1xuZXhwb3J0IHsgU2VwYXJhdGVOYXJyYXRpdmVGbG93UmVjb3JkZXIgfSBmcm9tICcuL2xpYi9lbmdpbmUnO1xuZXhwb3J0IHsgU2lsZW50TmFycmF0aXZlRmxvd1JlY29yZGVyIH0gZnJvbSAnLi9saWIvZW5naW5lJztcbmV4cG9ydCB7IFdpbmRvd2VkTmFycmF0aXZlRmxvd1JlY29yZGVyIH0gZnJvbSAnLi9saWIvZW5naW5lJztcblxuLy8gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxuLy8gTWVtb3J5IOKAlCBTY29wZUZhY3RvcnkgdHlwZSAobmVlZGVkIGZvciBGbG93Q2hhcnRFeGVjdXRvciBjb25zdHJ1Y3Rvcilcbi8vID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT1cblxuZXhwb3J0IHR5cGUgeyBSdW5PcHRpb25zIH0gZnJvbSAnLi9saWIvZW5naW5lJztcbmV4cG9ydCB0eXBlIHsgU2NvcGVGYWN0b3J5IH0gZnJvbSAnLi9saWIvbWVtb3J5JztcblxuLy8gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxuLy8gQ29udHJhY3Qg4oCUIEkvTyBib3VuZGFyeSwgc2NoZW1hcywgYW5kIE9wZW5BUEkgZ2VuZXJhdGlvblxuLy8gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxuXG5leHBvcnQgdHlwZSB7XG4gIEZsb3dDaGFydENvbnRyYWN0LFxuICBGbG93Q2hhcnRDb250cmFjdE9wdGlvbnMsXG4gIEpzb25TY2hlbWEsXG4gIE9wZW5BUElPcHRpb25zLFxuICBPcGVuQVBJU3BlYyxcbn0gZnJvbSAnLi9saWIvY29udHJhY3QnO1xuZXhwb3J0IHsgZGVmaW5lQ29udHJhY3QgfSBmcm9tICcuL2xpYi9jb250cmFjdCc7XG5leHBvcnQgeyBub3JtYWxpemVTY2hlbWEsIHpvZFRvSnNvblNjaGVtYSB9IGZyb20gJy4vbGliL2NvbnRyYWN0JztcbmV4cG9ydCB7IGdlbmVyYXRlT3BlbkFQSSB9IGZyb20gJy4vbGliL2NvbnRyYWN0JztcbiJdfQ==
|