@procedures/playground 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/playground
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,695 @@
1
- # Temporary Holding Version
1
+ # @procedures/playground
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/playground?color=2346c4&label=npm)](https://www.npmjs.com/package/@procedures/playground)
4
+
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+ [![Node.js Version](https://img.shields.io/badge/node-%3E%3D25.0.0-brightgreen)](https://nodejs.org)
7
+
8
+ > Type-safe aggregation building for procedure compositions
9
+
10
+ ## Overview
11
+
12
+ `client-playground` provides a fluent TypeScript API for composing procedure references with full type safety and autocomplete. Write aggregations as executable TypeScript files (`.play.ts`) that can be run directly with `tsx` or `ts-node`. Perfect for testing procedure compositions, building complex workflows, and exploring procedure APIs interactively.
13
+
14
+ ### Key Features
15
+
16
+ - **Type-Safe Builder API**: Fluent interface with full TypeScript autocomplete
17
+ - **Executable Scripts**: Write `.play.ts` files that run directly
18
+ - **Output References**: Reference previous stage outputs with `$ref` syntax
19
+ - **Named Stages**: Name stages for later reference in the execution DAG
20
+ - **Execution Control**: Control when procedures execute (`$immediate`, `$never`, `$parent`)
21
+ - **Local Transport**: Uses in-process transport for fast execution
22
+ - **Zero Configuration**: Auto-discovers registered procedures
23
+
24
+ ## Architecture
25
+
26
+ ```mermaid
27
+ graph TB
28
+ subgraph "Playground File (.play.ts)"
29
+ BUILDER[ProcBuilder API]
30
+ REF[Output References]
31
+ RUN[run() Executor]
32
+ end
33
+
34
+ subgraph "Runtime"
35
+ LOCAL[Local Transport]
36
+ REGISTRY[Procedure Registry]
37
+ EXEC[Execution Engine]
38
+ end
39
+
40
+ subgraph "Procedures"
41
+ PROC1[git.status]
42
+ PROC2[git.add]
43
+ PROC3[pnpm.install]
44
+ end
45
+
46
+ BUILDER --> REF
47
+ REF --> RUN
48
+ RUN --> LOCAL
49
+ LOCAL --> REGISTRY
50
+ REGISTRY --> EXEC
51
+ EXEC --> PROC1
52
+ EXEC --> PROC2
53
+ EXEC --> PROC3
54
+
55
+ style BUILDER fill:#7ed321
56
+ style RUN fill:#f5a623
57
+ style LOCAL fill:#50e3c2
58
+ ```
59
+
60
+ ### Execution Flow
61
+
62
+ ```mermaid
63
+ sequenceDiagram
64
+ participant Script as playground.play.ts
65
+ participant Builder as proc()
66
+ participant Executor as run()
67
+ participant Registry as PROCEDURE_REGISTRY
68
+ participant Proc as Procedure Handler
69
+
70
+ Script->>Builder: proc(["git", "status"]).input({ cwd: "." })
71
+ Builder-->>Script: ProcBuilder with .ref
72
+ Script->>Executor: run(procRef)
73
+ Executor->>Registry: Discover all procedures
74
+ Registry-->>Executor: Registered procedures
75
+ Executor->>Executor: Create local transport
76
+ Executor->>Executor: Sync registry to transport
77
+ Executor->>Proc: Execute procedure
78
+ Proc-->>Executor: Result
79
+ Executor->>Executor: Print result (if print: true)
80
+ Executor-->>Script: Typed result
81
+ ```
82
+
83
+ ## Installation
84
+
85
+ ```bash
86
+ npm install @procedures/playground
87
+ ```
88
+
89
+ Or with pnpm:
90
+
91
+ ```bash
92
+ pnpm add @procedures/playground
93
+ ```
94
+
95
+ ### Dependencies
96
+
97
+ This package depends on:
98
+ - `@procedures/client` - Core client/procedure system (dynamically imported)
99
+
100
+ ### Runtime Requirements
101
+
102
+ - `tsx` or `ts-node` - For executing `.play.ts` files
103
+ - Node.js >= 22.0.0
104
+
105
+ ## Usage
106
+
107
+ ### Quick Start
108
+
109
+ Create a playground file:
110
+
111
+ ```typescript
112
+ // my-workflow.play.ts
113
+ import { proc, ref, run } from "@procedures/playground";
114
+
115
+ // Execute a simple procedure
116
+ run(
117
+ proc(["git", "status"]).input({ cwd: "." }).ref
118
+ );
119
+ ```
120
+
121
+ Run it:
122
+
123
+ ```bash
124
+ npx tsx my-workflow.play.ts
125
+ ```
126
+
127
+ Output:
128
+
129
+ ```json
130
+ {
131
+ "branch": "main",
132
+ "ahead": 0,
133
+ "behind": 0,
134
+ "files": [],
135
+ "clean": true
136
+ }
137
+ ```
138
+
139
+ ### Builder API
140
+
141
+ The `proc()` builder provides a fluent interface:
142
+
143
+ ```typescript
144
+ import { proc } from "@procedures/playground";
145
+
146
+ // Simple procedure reference
147
+ const gitStatus = proc(["git", "status"])
148
+ .input({ cwd: "/path/to/repo" })
149
+ .ref;
150
+
151
+ // Named stage for reference
152
+ const hasChanges = proc(["git", "hasChanges"])
153
+ .input({ cwd: "." })
154
+ .name("changes") // Can be referenced as $ref: "changes"
155
+ .ref;
156
+
157
+ // With execution control
158
+ const conditionalAdd = proc(["git", "add"])
159
+ .input({ all: true })
160
+ .when("$parent") // Execute in parent context
161
+ .ref;
162
+ ```
163
+
164
+ ### Output References
165
+
166
+ Reference outputs from named stages:
167
+
168
+ ```typescript
169
+ import { proc, ref, run } from "@procedures/playground";
170
+
171
+ run(
172
+ proc(["dag", "traverse"]).input({
173
+ visit: [
174
+ // Stage 1: Check for changes
175
+ proc(["git", "hasChanges"])
176
+ .name("changes")
177
+ .ref,
178
+
179
+ // Stage 2: Conditional on stage 1
180
+ proc(["client", "conditional"]).input({
181
+ condition: ref("changes.value"), // Reference "changes" output
182
+ then: proc(["git", "add"]).input({ all: true }).ref,
183
+ }).ref,
184
+ ],
185
+ }).ref
186
+ );
187
+ ```
188
+
189
+ ### Reference Syntax
190
+
191
+ ```typescript
192
+ ref("changes.value") // Reference the .value field of "changes" output
193
+ ref("$last") // Reference the previous step's output
194
+ ref("$last.success") // Reference the .success field of previous output
195
+ ref("install.packages") // Deep property access
196
+ ```
197
+
198
+ ### Complex Workflows
199
+
200
+ ```typescript
201
+ import { proc, ref, run } from "@procedures/playground";
202
+
203
+ // Multi-step build and deploy workflow
204
+ run(
205
+ proc(["dag", "traverse"]).input({
206
+ visit: [
207
+ // 1. Install dependencies
208
+ proc(["pnpm", "install"])
209
+ .name("install")
210
+ .ref,
211
+
212
+ // 2. Run tests
213
+ proc(["shell", "exec"])
214
+ .input({ command: "npm test" })
215
+ .name("test")
216
+ .ref,
217
+
218
+ // 3. Build only if tests pass
219
+ proc(["client", "conditional"]).input({
220
+ condition: ref("test.exitCode"), // 0 = success
221
+ operator: "equals",
222
+ value: 0,
223
+ then: proc(["shell", "exec"])
224
+ .input({ command: "npm run build" })
225
+ .name("build")
226
+ .ref,
227
+ }).ref,
228
+
229
+ // 4. Deploy if build succeeded
230
+ proc(["client", "conditional"]).input({
231
+ condition: ref("build.success"),
232
+ then: proc(["docker", "build"]).input({
233
+ context: ".",
234
+ tag: "my-app:latest",
235
+ }).ref,
236
+ }).ref,
237
+ ],
238
+ }).ref
239
+ );
240
+ ```
241
+
242
+ ### Running Without Printing
243
+
244
+ ```typescript
245
+ import { run } from "@procedures/playground";
246
+
247
+ // Get result without printing
248
+ const result = await run(
249
+ proc(["git", "status"]).ref,
250
+ { print: false }
251
+ );
252
+
253
+ console.log(`Current branch: ${result.branch}`);
254
+
255
+ // Custom formatting
256
+ const data = await run(
257
+ proc(["git", "log"]).input({ count: 5 }).ref,
258
+ { print: true, format: "text" }
259
+ );
260
+ ```
261
+
262
+ ## API Reference
263
+
264
+ ### `proc(path: ProcedurePath): ProcBuilder`
265
+
266
+ Create a procedure reference builder.
267
+
268
+ **Parameters:**
269
+ - `path: string[]` - Procedure path (e.g., `["git", "status"]`)
270
+
271
+ **Returns:** `ProcBuilder` with methods:
272
+
273
+ #### `.input<T>(input: T): ProcBuilder<T>`
274
+
275
+ Set the input for this procedure.
276
+
277
+ ```typescript
278
+ proc(["git", "add"])
279
+ .input({ files: ["file.ts"], all: false })
280
+ .ref
281
+ ```
282
+
283
+ #### `.name(name: string): ProcBuilder`
284
+
285
+ Name this stage for later reference with `$ref`.
286
+
287
+ ```typescript
288
+ proc(["git", "hasChanges"])
289
+ .name("changes")
290
+ .ref
291
+
292
+ // Later:
293
+ ref("changes.value")
294
+ ```
295
+
296
+ #### `.when(when: string): ProcBuilder`
297
+
298
+ Set when this reference should be executed.
299
+
300
+ **Values:**
301
+ - `"$immediate"` - Execute immediately
302
+ - `"$never"` - Never execute (for testing)
303
+ - `"$parent"` - Execute in parent context
304
+ - Custom context name
305
+
306
+ ```typescript
307
+ proc(["git", "add"])
308
+ .when("$parent")
309
+ .ref
310
+ ```
311
+
312
+ #### `.ref: ProcedureRefJson`
313
+
314
+ Get the procedure reference object.
315
+
316
+ ```typescript
317
+ const procRef = proc(["git", "status"])
318
+ .input({ cwd: "." })
319
+ .ref;
320
+
321
+ // procRef is:
322
+ // {
323
+ // $proc: ["git", "status"],
324
+ // input: { cwd: "." }
325
+ // }
326
+ ```
327
+
328
+ ### `ref(path: string): OutputRef`
329
+
330
+ Create an output reference.
331
+
332
+ **Parameters:**
333
+ - `path: string` - Reference path (e.g., `"stageName.value"` or `"$last.value"`)
334
+
335
+ **Returns:** `OutputRef` object
336
+
337
+ ```typescript
338
+ const outputRef = ref("changes.value");
339
+ // { $ref: "changes.value" }
340
+ ```
341
+
342
+ ### `run<T>(procedureRef: ProcedureRefJson, options?: RunOptions): Promise<T>`
343
+
344
+ Execute a procedure reference.
345
+
346
+ **Parameters:**
347
+ - `procedureRef: ProcedureRefJson` - Procedure reference to execute
348
+ - `options?: RunOptions` - Execution options
349
+
350
+ **Options:**
351
+ ```typescript
352
+ interface RunOptions {
353
+ print?: boolean; // Print result (default: true)
354
+ format?: "json" | "text"; // Output format (default: "json")
355
+ }
356
+ ```
357
+
358
+ **Returns:** `Promise<T>` - Typed result
359
+
360
+ ```typescript
361
+ // With printing (default)
362
+ await run(proc(["git", "status"]).ref);
363
+
364
+ // Without printing
365
+ const result = await run(
366
+ proc(["git", "status"]).ref,
367
+ { print: false }
368
+ );
369
+
370
+ // Text format
371
+ await run(
372
+ proc(["shell", "exec"]).input({ command: "ls" }).ref,
373
+ { format: "text" }
374
+ );
375
+ ```
376
+
377
+ ## Types
378
+
379
+ ### ProcBuilder
380
+
381
+ ```typescript
382
+ interface ProcBuilder<TInput = unknown> {
383
+ input<T>(input: T): ProcBuilder<T>;
384
+ name(name: string): ProcBuilder<TInput>;
385
+ when(when: string): ProcBuilder<TInput>;
386
+ readonly ref: ProcedureRefJson<TInput>;
387
+ }
388
+ ```
389
+
390
+ ### ProcedureRefJson
391
+
392
+ ```typescript
393
+ interface ProcedureRefJson<TInput = unknown> {
394
+ $proc: ProcedurePath;
395
+ input: TInput;
396
+ $name?: string;
397
+ $when?: string;
398
+ }
399
+ ```
400
+
401
+ ### OutputRef
402
+
403
+ ```typescript
404
+ interface OutputRef {
405
+ readonly $ref: string;
406
+ }
407
+ ```
408
+
409
+ ## Examples
410
+
411
+ ### Git Workflow
412
+
413
+ ```typescript
414
+ // git-workflow.play.ts
415
+ import { proc, ref, run } from "@procedures/playground";
416
+
417
+ run(
418
+ proc(["dag", "traverse"]).input({
419
+ visit: [
420
+ // Check for changes
421
+ proc(["git", "hasChanges"])
422
+ .name("changes")
423
+ .ref,
424
+
425
+ // Stage all if changes exist
426
+ proc(["client", "conditional"]).input({
427
+ condition: ref("changes.value"),
428
+ then: proc(["git", "add"]).input({ all: true }).ref,
429
+ }).ref,
430
+
431
+ // Commit
432
+ proc(["git", "commit"]).input({
433
+ message: "Automated commit from playground",
434
+ }).ref,
435
+ ],
436
+ }).ref
437
+ );
438
+ ```
439
+
440
+ ### Build Pipeline
441
+
442
+ ```typescript
443
+ // build.play.ts
444
+ import { proc, run } from "@procedures/playground";
445
+
446
+ run(
447
+ proc(["dag", "traverse"]).input({
448
+ visit: [
449
+ proc(["pnpm", "install"]).ref,
450
+ proc(["shell", "exec"]).input({ command: "npm run lint" }).ref,
451
+ proc(["shell", "exec"]).input({ command: "npm test" }).ref,
452
+ proc(["shell", "exec"]).input({ command: "npm run build" }).ref,
453
+ ],
454
+ }).ref
455
+ );
456
+ ```
457
+
458
+ ### Docker Deployment
459
+
460
+ ```typescript
461
+ // deploy.play.ts
462
+ import { proc, ref, run } from "@procedures/playground";
463
+
464
+ run(
465
+ proc(["dag", "traverse"]).input({
466
+ visit: [
467
+ // Build image
468
+ proc(["docker", "build"]).input({
469
+ context: ".",
470
+ tag: "my-app:latest",
471
+ }).name("build").ref,
472
+
473
+ // Run container
474
+ proc(["docker", "run"]).input({
475
+ image: "my-app:latest",
476
+ name: "my-app-container",
477
+ ports: ["3000:3000"],
478
+ detach: true,
479
+ }).ref,
480
+
481
+ // Health check
482
+ proc(["shell", "exec"]).input({
483
+ command: "curl http://localhost:3000/health",
484
+ }).ref,
485
+ ],
486
+ }).ref
487
+ );
488
+ ```
489
+
490
+ ### Testing Procedures
491
+
492
+ ```typescript
493
+ // test-procedures.play.ts
494
+ import { proc, run } from "@procedures/playground";
495
+
496
+ // Test git status
497
+ console.log("=== Testing git.status ===");
498
+ await run(proc(["git", "status"]).input({ cwd: "." }).ref);
499
+
500
+ // Test pnpm store path
501
+ console.log("\n=== Testing pnpm.store.path ===");
502
+ await run(proc(["pnpm", "store", "path"]).ref);
503
+
504
+ // Test shell execution
505
+ console.log("\n=== Testing shell.exec ===");
506
+ await run(proc(["shell", "exec"]).input({ command: "echo 'Hello from playground!'" }).ref);
507
+ ```
508
+
509
+ ### Conditional Logic
510
+
511
+ ```typescript
512
+ // conditional.play.ts
513
+ import { proc, ref, run } from "@procedures/playground";
514
+
515
+ run(
516
+ proc(["dag", "traverse"]).input({
517
+ visit: [
518
+ // Get environment
519
+ proc(["shell", "exec"])
520
+ .input({ command: "echo $NODE_ENV" })
521
+ .name("env")
522
+ .ref,
523
+
524
+ // Install based on environment
525
+ proc(["client", "conditional"]).input({
526
+ condition: ref("env.stdout"),
527
+ operator: "contains",
528
+ value: "production",
529
+ then: proc(["pnpm", "install"]).input({ production: true }).ref,
530
+ else: proc(["pnpm", "install"]).ref,
531
+ }).ref,
532
+ ],
533
+ }).ref
534
+ );
535
+ ```
536
+
537
+ ## Use Cases
538
+
539
+ ### 1. Interactive Procedure Testing
540
+
541
+ Quickly test procedure calls without setting up a full client:
542
+
543
+ ```typescript
544
+ // test-docker.play.ts
545
+ import { proc, run } from "@procedures/playground";
546
+
547
+ await run(proc(["docker", "ps"]).input({ all: true }).ref);
548
+ ```
549
+
550
+ ```bash
551
+ npx tsx test-docker.play.ts
552
+ ```
553
+
554
+ ### 2. Complex Workflow Prototyping
555
+
556
+ Prototype multi-step workflows before integrating into apps:
557
+
558
+ ```typescript
559
+ // prototype-workflow.play.ts
560
+ import { proc, ref, run } from "@procedures/playground";
561
+
562
+ // Test the entire workflow
563
+ run(proc(["dag", "traverse"]).input({ visit: [...] }).ref);
564
+ ```
565
+
566
+ ### 3. CI/CD Scripts
567
+
568
+ Write deployment scripts as executable TypeScript:
569
+
570
+ ```typescript
571
+ // deploy.play.ts
572
+ run(proc(["dag", "traverse"]).input({
573
+ visit: [
574
+ proc(["git", "pull"]).ref,
575
+ proc(["pnpm", "install"]).ref,
576
+ proc(["shell", "exec"]).input({ command: "npm run build" }).ref,
577
+ proc(["docker", "build"]).input({ context: ".", tag: "app:latest" }).ref,
578
+ proc(["docker", "compose", "up"]).input({ detach: true }).ref,
579
+ ],
580
+ }).ref);
581
+ ```
582
+
583
+ ### 4. Development Automation
584
+
585
+ Automate common development tasks:
586
+
587
+ ```typescript
588
+ // setup.play.ts
589
+ // Run: npx tsx setup.play.ts
590
+ import { proc, run } from "@procedures/playground";
591
+
592
+ run(proc(["dag", "traverse"]).input({
593
+ visit: [
594
+ proc(["git", "checkout"]).input({ branch: "main" }).ref,
595
+ proc(["git", "pull"]).ref,
596
+ proc(["pnpm", "install"]).ref,
597
+ proc(["shell", "exec"]).input({ command: "npm run setup:env" }).ref,
598
+ ],
599
+ }).ref);
600
+ ```
601
+
602
+ ## Advanced Usage
603
+
604
+ ### Custom Transport
605
+
606
+ ```typescript
607
+ import { run } from "@procedures/playground";
608
+ import { LocalTransport, PROCEDURE_REGISTRY } from "@procedures/client";
609
+
610
+ // run() uses LocalTransport internally
611
+ // The registry is synced automatically
612
+ ```
613
+
614
+ ### Error Handling
615
+
616
+ ```typescript
617
+ import { proc, run } from "@procedures/playground";
618
+
619
+ try {
620
+ const result = await run(
621
+ proc(["nonexistent", "procedure"]).ref,
622
+ { print: false }
623
+ );
624
+ } catch (error) {
625
+ console.error("Execution failed:", error.message);
626
+ }
627
+ ```
628
+
629
+ ### TypeScript Integration
630
+
631
+ ```typescript
632
+ import { proc, run } from "@procedures/playground";
633
+ import type { GitStatusOutput } from "@procedures/git";
634
+
635
+ // Typed result
636
+ const status = await run<GitStatusOutput>(
637
+ proc(["git", "status"]).input({ cwd: "." }).ref,
638
+ { print: false }
639
+ );
640
+
641
+ console.log(`Branch: ${status.branch}`);
642
+ console.log(`Clean: ${status.clean}`);
643
+ ```
644
+
645
+ ## Performance Considerations
646
+
647
+ - **Local Transport**: Uses in-process transport for minimal overhead
648
+ - **Registry Sync**: Procedures synced once per `run()` call
649
+ - **No Network**: All execution is local - no HTTP/WebSocket overhead
650
+ - **Fast Iteration**: Changes to `.play.ts` files are immediate (no build step)
651
+
652
+ ## Comparison with Alternatives
653
+
654
+ ### vs. Direct Client Usage
655
+
656
+ ```typescript
657
+ // Direct client - more boilerplate
658
+ import { Client, LocalTransport, PROCEDURE_REGISTRY } from "@procedures/client";
659
+ const client = new Client(new LocalTransport({ registry: PROCEDURE_REGISTRY }));
660
+ const result = await client.exec(["git", "status"], { cwd: "." });
661
+
662
+ // Playground - concise
663
+ import { proc, run } from "@procedures/playground";
664
+ const result = await run(proc(["git", "status"]).input({ cwd: "." }).ref);
665
+ ```
666
+
667
+ ### vs. Shell Scripts
668
+
669
+ ```bash
670
+ # Shell script - no type safety
671
+ git status
672
+ pnpm install
673
+ npm test
674
+ ```
675
+
676
+ ```typescript
677
+ // Playground - type-safe, composable
678
+ run(proc(["dag", "traverse"]).input({
679
+ visit: [
680
+ proc(["git", "status"]).ref,
681
+ proc(["pnpm", "install"]).ref,
682
+ proc(["shell", "exec"]).input({ command: "npm test" }).ref,
683
+ ],
684
+ }).ref);
685
+ ```
686
+
687
+ ## License
688
+
689
+ MIT
690
+
691
+ ## Related Packages
692
+
693
+ - `@procedures/client` - Core client/procedure system
694
+ - `@procedures/bundle-dev` - Development bundle with procedures
695
+ - All `client-*` packages - Provide procedures to use in playground