@procedures/dag 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # @procedures/dag
2
+
3
+ ## 0.1.0
4
+
5
+ The first release on npm. The packages of the client monorepo have one version together.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-2026 Mark Russell
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,499 @@
1
- # Temporary Holding Version
1
+ # @procedures/dag
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![npm](https://img.shields.io/npm/v/@procedures/dag?color=2346c4&label=npm)](https://www.npmjs.com/package/@procedures/dag)
4
+
5
+ ```sh
6
+ npm install @procedures/dag
7
+ ```
8
+
9
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
10
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.9-blue.svg)](https://www.typescriptlang.org/)
11
+ [![Node.js](https://img.shields.io/badge/Node.js-%3E%3D22-green.svg)](https://nodejs.org/)
12
+
13
+ > Generic DAG (Directed Acyclic Graph) utilities for dependency ordering and parallel execution. Powers the ecosystem's build system.
14
+
15
+ ## Table of Contents
16
+
17
+ - [Overview](#overview)
18
+ - [Installation](#installation)
19
+ - [Architecture](#architecture)
20
+ - [Quick Start](#quick-start)
21
+ - [API Reference](#api-reference)
22
+ - [Types](#types)
23
+ - [Functions](#functions)
24
+ - [buildDAG](#builddag)
25
+ - [executeDAG](#executedag)
26
+ - [Use Cases](#use-cases)
27
+ - [Integration](#integration)
28
+ - [Requirements](#requirements)
29
+ - [License](#license)
30
+
31
+ ---
32
+
33
+ ## Overview
34
+
35
+ **client-dag** provides utilities for working with Directed Acyclic Graphs:
36
+
37
+ - **DAG Building** - Topological sorting using Kahn's algorithm
38
+ - **Parallel Execution** - Process nodes level-by-level with configurable concurrency
39
+ - **Traversal Utilities** - Find roots, leaves, dependents, and dependencies
40
+ - **Progress Tracking** - Callbacks for node start/complete events
41
+
42
+ ---
43
+
44
+ ## Installation
45
+
46
+ ```bash
47
+ npm install github:mark1russell7/client-dag#main
48
+ ```
49
+
50
+ ---
51
+
52
+ ## Architecture
53
+
54
+ ### System Overview
55
+
56
+ ```mermaid
57
+ graph TB
58
+ subgraph "Input"
59
+ Nodes[DAGNode Array<br/>id + dependencies]
60
+ end
61
+
62
+ subgraph "client-dag"
63
+ subgraph "DAG Builder"
64
+ Kahn[Kahn's Algorithm]
65
+ Topo[Topological Sort]
66
+ Level[Level Assignment]
67
+ end
68
+
69
+ subgraph "DAG Executor"
70
+ Queue[Level Queue]
71
+ Pool[Concurrency Pool]
72
+ Process[Node Processor]
73
+ end
74
+
75
+ subgraph "Traversal"
76
+ Roots[getRoots]
77
+ Leaves[getLeaves]
78
+ Deps[getDependents]
79
+ Reqs[getDependencies]
80
+ end
81
+ end
82
+
83
+ subgraph "Output"
84
+ DAG[DependencyDAG<br/>nodes + levels]
85
+ Result[DAGResult<br/>success + results]
86
+ end
87
+
88
+ Nodes --> Kahn
89
+ Kahn --> Topo
90
+ Topo --> Level
91
+ Level --> DAG
92
+
93
+ DAG --> Queue
94
+ Queue --> Pool
95
+ Pool --> Process
96
+ Process --> Result
97
+
98
+ DAG --> Roots
99
+ DAG --> Leaves
100
+ DAG --> Deps
101
+ DAG --> Reqs
102
+ ```
103
+
104
+ ### Execution Flow
105
+
106
+ ```mermaid
107
+ sequenceDiagram
108
+ participant App as Application
109
+ participant DAG as client-dag
110
+ participant Build as buildDAG
111
+ participant Exec as executeDAG
112
+ participant Proc as NodeProcessor
113
+
114
+ App->>Build: buildDAG(nodes)
115
+ Build->>Build: Kahn's algorithm
116
+ Build->>Build: Assign levels
117
+ Build-->>App: DependencyDAG
118
+
119
+ App->>Exec: executeDAG(dag, processor, options)
120
+
121
+ loop For each level
122
+ Exec->>Exec: Get nodes at level
123
+ par Parallel execution (concurrency)
124
+ Exec->>Proc: process(node1)
125
+ Proc-->>Exec: NodeResult
126
+ Exec->>Proc: process(node2)
127
+ Proc-->>Exec: NodeResult
128
+ end
129
+ Exec->>Exec: Check failFast
130
+ end
131
+
132
+ Exec-->>App: DAGResult
133
+ ```
134
+
135
+ ### Topological Sorting (Kahn's Algorithm)
136
+
137
+ ```mermaid
138
+ graph LR
139
+ subgraph "Input Graph"
140
+ A1[A depends on B,C]
141
+ B1[B depends on D]
142
+ C1[C depends on D]
143
+ D1[D no deps]
144
+ end
145
+
146
+ subgraph "Processing"
147
+ K[Kahn's Algorithm]
148
+ end
149
+
150
+ subgraph "Output Levels"
151
+ L0[Level 0: D]
152
+ L1[Level 1: B, C]
153
+ L2[Level 2: A]
154
+ end
155
+
156
+ A1 --> K
157
+ B1 --> K
158
+ C1 --> K
159
+ D1 --> K
160
+ K --> L0
161
+ K --> L1
162
+ K --> L2
163
+ ```
164
+
165
+ ### DAG Structure Visualization
166
+
167
+ ```mermaid
168
+ graph TB
169
+ subgraph "Level 2 (Roots)"
170
+ App[app]
171
+ end
172
+
173
+ subgraph "Level 1"
174
+ LibA[lib-a]
175
+ LibB[lib-b]
176
+ LibC[lib-c]
177
+ end
178
+
179
+ subgraph "Level 0 (Leaves)"
180
+ Core[core]
181
+ end
182
+
183
+ App --> LibA
184
+ App --> LibB
185
+ App --> LibC
186
+ LibA --> Core
187
+ LibB --> Core
188
+ LibC --> Core
189
+
190
+ style App fill:#f9f,stroke:#333
191
+ style Core fill:#9f9,stroke:#333
192
+ ```
193
+
194
+ ---
195
+
196
+ ## Quick Start
197
+
198
+ ```typescript
199
+ import {
200
+ buildDAG,
201
+ executeDAG,
202
+ type DAGNode,
203
+ type NodeProcessor,
204
+ } from "@procedures/dag";
205
+
206
+ // Define nodes
207
+ const nodes: DAGNode[] = [
208
+ { id: "app", dependencies: ["lib-a", "lib-b"] },
209
+ { id: "lib-a", dependencies: ["core"] },
210
+ { id: "lib-b", dependencies: ["core"] },
211
+ { id: "core", dependencies: [] },
212
+ ];
213
+
214
+ // Build the DAG
215
+ const dag = buildDAG(nodes);
216
+ // Levels: [["core"], ["lib-a", "lib-b"], ["app"]]
217
+
218
+ // Define a processor
219
+ const processor: NodeProcessor = async (node) => {
220
+ console.log(`Processing ${node.id}`);
221
+ // Do work...
222
+ return {
223
+ node,
224
+ success: true,
225
+ duration: 100,
226
+ logs: [`Built ${node.id}`],
227
+ };
228
+ };
229
+
230
+ // Execute with parallel processing per level
231
+ const result = await executeDAG(dag, processor, {
232
+ concurrency: 4,
233
+ failFast: true,
234
+ onNodeStart: (node) => console.log(`Starting: ${node.id}`),
235
+ onNodeComplete: (result) => console.log(`Done: ${result.node.id}`),
236
+ });
237
+ ```
238
+
239
+ ---
240
+
241
+ ## API Reference
242
+
243
+ ### Types
244
+
245
+ ```typescript
246
+ interface DAGNode<TData = unknown> {
247
+ id: string; // Unique identifier
248
+ dependencies: string[]; // IDs of dependencies
249
+ level?: number; // Topological level (computed)
250
+ data?: TData; // Optional user data
251
+ }
252
+
253
+ interface DependencyDAG<TNode extends DAGNode = DAGNode> {
254
+ nodes: Map<string, TNode>; // All nodes
255
+ levels: TNode[][]; // Nodes by level
256
+ roots: string[]; // No dependents (end nodes)
257
+ leaves: string[]; // No dependencies (start nodes)
258
+ }
259
+
260
+ interface DAGExecutionOptions<TNode extends DAGNode = DAGNode> {
261
+ concurrency?: number; // Max parallel per level (default: 4)
262
+ failFast?: boolean; // Stop on first error (default: true)
263
+ onNodeStart?: (node: TNode) => void;
264
+ onNodeComplete?: (result: NodeResult<TNode>) => void;
265
+ }
266
+
267
+ interface NodeResult<TNode extends DAGNode = DAGNode> {
268
+ node: TNode;
269
+ success: boolean;
270
+ error?: Error;
271
+ duration: number; // ms
272
+ logs: string[];
273
+ output?: unknown;
274
+ }
275
+
276
+ interface DAGResult<TNode extends DAGNode = DAGNode> {
277
+ success: boolean;
278
+ results: Map<string, NodeResult<TNode>>;
279
+ failedNodes: string[];
280
+ totalDuration: number;
281
+ }
282
+
283
+ type NodeProcessor<TNode extends DAGNode = DAGNode> = (
284
+ node: TNode
285
+ ) => Promise<NodeResult<TNode>>;
286
+ ```
287
+
288
+ ### Functions
289
+
290
+ | Function | Description |
291
+ |----------|-------------|
292
+ | `buildDAG(nodes)` | Build a DAG from nodes with dependencies |
293
+ | `executeDAG(dag, processor, options)` | Execute DAG with parallel processing |
294
+ | `getRoots(dag)` | Get nodes with no dependents |
295
+ | `getLeaves(dag)` | Get nodes with no dependencies |
296
+ | `getDependents(dag, id)` | Get nodes that depend on this node |
297
+ | `getDependencies(dag, id)` | Get this node's dependencies |
298
+
299
+ ---
300
+
301
+ ### buildDAG
302
+
303
+ Build a leveled DAG using Kahn's algorithm.
304
+
305
+ ```typescript
306
+ import { buildDAG } from "@procedures/dag";
307
+
308
+ const nodes = [
309
+ { id: "a", dependencies: ["b", "c"] },
310
+ { id: "b", dependencies: ["d"] },
311
+ { id: "c", dependencies: ["d"] },
312
+ { id: "d", dependencies: [] },
313
+ ];
314
+
315
+ const dag = buildDAG(nodes);
316
+ // dag.levels = [
317
+ // [{ id: "d", ... }], // Level 0: process first
318
+ // [{ id: "b" }, { id: "c" }], // Level 1: parallel
319
+ // [{ id: "a", ... }], // Level 2: process last
320
+ // ]
321
+ ```
322
+
323
+ **Cycle Detection:**
324
+ ```typescript
325
+ // Throws if cycles detected
326
+ const cyclic = [
327
+ { id: "a", dependencies: ["b"] },
328
+ { id: "b", dependencies: ["a"] }, // Cycle!
329
+ ];
330
+ buildDAG(cyclic); // Error: Cycle detected
331
+ ```
332
+
333
+ ---
334
+
335
+ ### executeDAG
336
+
337
+ Execute nodes level by level with configurable parallelism.
338
+
339
+ ```typescript
340
+ import { executeDAG } from "@procedures/dag";
341
+
342
+ const result = await executeDAG(dag, processor, {
343
+ concurrency: 4, // Process up to 4 nodes in parallel per level
344
+ failFast: true, // Stop immediately on first failure
345
+ onNodeStart: (node) => console.log(`Starting: ${node.id}`),
346
+ onNodeComplete: (r) => console.log(`${r.node.id}: ${r.success ? "OK" : "FAIL"}`),
347
+ });
348
+
349
+ if (result.success) {
350
+ console.log("All nodes processed successfully");
351
+ } else {
352
+ console.log("Failed nodes:", result.failedNodes);
353
+ }
354
+ ```
355
+
356
+ **Execution Order:**
357
+
358
+ ```mermaid
359
+ graph LR
360
+ subgraph "Level 0"
361
+ D[core]
362
+ end
363
+
364
+ subgraph "Level 1 (parallel)"
365
+ B[lib-a]
366
+ C[lib-b]
367
+ end
368
+
369
+ subgraph "Level 2"
370
+ A[app]
371
+ end
372
+
373
+ D -->|then| B
374
+ D -->|then| C
375
+ B -->|then| A
376
+ C -->|then| A
377
+ ```
378
+
379
+ ---
380
+
381
+ ## Use Cases
382
+
383
+ ### Package Build Order
384
+
385
+ Build packages in dependency order (`dag.traverse` of `client-lib` does this):
386
+
387
+ ```typescript
388
+ const packages = [
389
+ { id: "app", dependencies: ["@org/lib-ui", "@org/lib-core"] },
390
+ { id: "@org/lib-ui", dependencies: ["@org/lib-core"] },
391
+ { id: "@org/lib-core", dependencies: [] },
392
+ ];
393
+
394
+ const dag = buildDAG(packages);
395
+ await executeDAG(dag, async (pkg) => {
396
+ await runBuild(pkg.id);
397
+ return { node: pkg, success: true, duration: 0, logs: [] };
398
+ });
399
+ ```
400
+
401
+ ### Task Scheduling
402
+
403
+ Execute dependent tasks:
404
+
405
+ ```typescript
406
+ const tasks = [
407
+ { id: "deploy", dependencies: ["test", "build"] },
408
+ { id: "test", dependencies: ["build"] },
409
+ { id: "build", dependencies: [] },
410
+ ];
411
+
412
+ const dag = buildDAG(tasks);
413
+ await executeDAG(dag, async (task) => {
414
+ await runTask(task.id);
415
+ return { node: task, success: true, duration: 0, logs: [] };
416
+ });
417
+ ```
418
+
419
+ ### Module Loading
420
+
421
+ Load modules respecting dependencies:
422
+
423
+ ```typescript
424
+ const modules = [
425
+ { id: "app", dependencies: ["router", "store"] },
426
+ { id: "router", dependencies: ["utils"] },
427
+ { id: "store", dependencies: ["utils"] },
428
+ { id: "utils", dependencies: [] },
429
+ ];
430
+
431
+ const dag = buildDAG(modules);
432
+ await executeDAG(dag, async (mod) => {
433
+ await import(mod.id);
434
+ return { node: mod, success: true, duration: 0, logs: [] };
435
+ });
436
+ ```
437
+
438
+ ---
439
+
440
+ ## Integration
441
+
442
+ ### With client-lib
443
+
444
+ `client-lib` uses `client-dag` for ecosystem package management:
445
+
446
+ ```mermaid
447
+ graph TB
448
+ subgraph "client-lib"
449
+ Scan[lib.scan<br/>Discover packages]
450
+ Traverse[dag.traverse<br/>Visit in dependency order]
451
+ end
452
+
453
+ subgraph "client-dag"
454
+ Build[buildDAG]
455
+ Execute[executeDAG]
456
+ end
457
+
458
+ subgraph "Packages"
459
+ P1[Package A]
460
+ P2[Package B]
461
+ P3[Package C]
462
+ end
463
+
464
+ Scan --> Build
465
+ Build --> Execute
466
+ Traverse --> Execute
467
+ Execute --> P1
468
+ Execute --> P2
469
+ Execute --> P3
470
+ ```
471
+
472
+ ### dag.traverse Procedure
473
+
474
+ The `dag.traverse` procedure in `client-lib` wraps this functionality:
475
+
476
+ ```typescript
477
+ // Via procedure
478
+ await client.exec(["dag", "traverse"], {
479
+ visit: ["pnpm", "install"],
480
+ concurrency: 4,
481
+ });
482
+
483
+ // Internally uses
484
+ const dag = buildDAG(packages);
485
+ await executeDAG(dag, processor);
486
+ ```
487
+
488
+ ---
489
+
490
+ ## Requirements
491
+
492
+ - **Node.js** >= 22
493
+ - **Dependencies:** none
494
+
495
+ ---
496
+
497
+ ## License
498
+
499
+ MIT