slang-ts 0.0.7 → 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
- # Slang
1
+ # slang-ts
2
2
 
3
3
  Functional programming library for TypeScript.
4
4
 
5
- A collection of functional programming utilities and other cool programming stuff from other languages such as rust implemented in TypeScript.
5
+ A collection of utilities and other cool programming stuff brought over from languages like Rust.
6
6
 
7
7
  ## Install
8
8
 
@@ -12,31 +12,49 @@ npm i slang-ts
12
12
 
13
13
  ## Implemented Utilities
14
14
 
15
+ - [x] Option (Some, None)
16
+ - [x] isFalsy (Option truthiness check)
15
17
  - [x] Result (Ok, Err)
16
- - [x] Maybe (Option)
17
- - [x] andThen
18
- - [x] Atom
19
18
  - [x] Expect
20
19
  - [x] Unwrap (on Option)
21
20
  - [x] Else (on unwrap)
22
- - [x] Panic
23
- - [x] Zip, Unzip, zipWith
24
- - [x] SafeTry
21
+ - [x] andThen
25
22
  - [x] Match
26
23
  - [x] MatchAll
24
+ - [x] Options (normalize mixed values)
25
+ - [x] SafeTry
27
26
  - [x] Pipe
28
27
  - [x] To (converters, e.g. `userAtom.to('option')`)
28
+ - [x] Atom
29
+ - [x] Zip, Unzip, zipWith
30
+ - [x] Panic
31
+ - [x] println (environment-aware logging)
32
+ - [x] setEnvironment (logging mode, custom logger)
33
+ - [x] Signal (lifecycle trigger)
34
+ - [x] SuperPromise (abort, reject, resolve, defer)
35
+ - [x] SuperRunner (sequence, all, race)
36
+ - [x] Channel (send, subscribe, when)
37
+ - [x] scheduleMicrotask (internal scheduling helper)
29
38
 
30
- All utilities fully tested, See [tests](https://github.com/Hussseinkizz/slang/tree/main/tests)
39
+ All utilities fully tested. See [tests](https://github.com/Hussseinkizz/slang/tree/main/tests)
31
40
 
32
- ## Others (Planned)
41
+ ## New in v0.0.9
33
42
 
34
- - Pubsub store with state locks
35
- - Promises and async utilities
43
+ - **SafeTry**: unwraps nested Result, Option, and Atom values into one result; nested failures now become the outer `Err`, while plain object properties and array elements stay unchanged.
44
+ - **Package types**: bundled declarations work with strict NodeNext consumers, so importing the package no longer exposes invalid declaration paths.
45
+ - **Build tooling**: tsdown and TypeScript 7 build JavaScript and bundled types together.
46
+
47
+ ## New in v0.0.8
48
+
49
+ - **Signal**: a simple on/off trigger that fires once and stays fired; use it to make something start only after something else finishes.
50
+ - **println now respects the environment**: `setEnvironment("production")` (or `"prod"`) turns it into a no-op, so dev logging never reaches prod logs; short forms `dev`/`prod` accepted, and it appends a real newline. Pass `{ printFn }` to route println through an existing logger instead.
51
+ - **SuperPromise**: native JavaScript promises plus wait, retry, time out, cancel, settling into `Ok`/`Err`; use it for any async operation that needs more than plain promise.
52
+ - **SuperRunner**: `Promise.all` and `Promise.race` grown up: run several SuperPromises in order, in parallel, or take first to finish.
53
+ - **Channel**: a simple event bus between parts of your app that don't need to know each other; use it to broadcast things like "user created" or "workflow completed" without wiring components together.
36
54
 
37
55
  ## How It Works
38
56
 
39
- You can import utilities individually or together:
57
+ Import utilities individually or together:
40
58
 
41
59
  ```ts
42
60
  // Individual imports
@@ -52,18 +70,6 @@ import * as slang from "slang-ts";
52
70
  slang.println("Hello world!");
53
71
  ```
54
72
 
55
- ### println
56
-
57
- Well there's nothing special to slang's println utility, its just who wants console.log, its not fun at all, so we instead println, clean and classic, but latter it can be made environment aware so it doesn't print in prod, but for now its just sugar for console.log.
58
-
59
- ```ts
60
- import { println } from "slang-ts";
61
-
62
- const name = "kizz";
63
- println("name:", name);
64
- println("multiple", "args", "work", { too: true });
65
- ```
66
-
67
73
  ### Option
68
74
 
69
75
  Wraps values that may or may not be present. Returns `Some<T>` for truthy values, `None` for null, undefined, empty strings, NaN, or Infinity. Note that `0` and `false` are truthy as these are usually intentional.
@@ -130,24 +136,76 @@ if (user.isOk) {
130
136
  }
131
137
  ```
132
138
 
133
- ### Atom
139
+ ### Expect
134
140
 
135
- Creates unique, non-interned symbols with semantic descriptions. Each call produces a distinct identity. So ideally define them in one file and import from it everywhere else, great for env variables stuff.
141
+ Unwraps values or throws with custom message. Use when failure is unrecoverable.
136
142
 
137
143
  ```ts
138
- import { atom } from "slang-ts";
144
+ const personAge = option(25).expect("a person must have age!");
145
+ println("person age", personAge); // 25
139
146
 
140
- const userAtom = atom("kizz");
141
- const user2Atom = atom("kizz");
147
+ // This would throw!
148
+ // const personAge2 = option("").expect("a person must have age!");
149
+ ```
142
150
 
143
- println(userAtom === atom("kizz")); // false - non interned ✅
144
- println(userAtom.description); // "kizz"
151
+ ### Unwrap/Else
145
152
 
146
- if (userAtom === user2Atom) {
147
- println("all the same");
148
- } else {
149
- println("not the same"); // This prints!
150
- }
153
+ Chainable unwrapping with mandatory fallback. Must call `.else()` or throws.
154
+
155
+ ```ts
156
+ const port = option(process.env.PORT).unwrap().else(3000);
157
+ println("Using port:", port);
158
+
159
+ // Function fallbacks
160
+ const retries = option(null).unwrap().else(() => 5);
161
+ println("Retries:", retries);
162
+
163
+ // This throws! No .else() chained
164
+ // const nothing = option(null).unwrap();
165
+ ```
166
+
167
+ ### andThen
168
+
169
+ Chainable transformation for `Option`, `Result`, and `Atom`. Transforms the inner value while preserving the wrapper type. Returns original instance if provided transformation function returns `undefined`.
170
+
171
+ ```ts
172
+ // Option - transforms Some, skips None
173
+ option(5).andThen(x => x * 2); // Some(10)
174
+ option(null).andThen(x => x * 2); // None (skipped)
175
+ option(5).andThen(() => undefined); // Some(5) - original
176
+
177
+ // Result - transforms Ok, skips Err
178
+ Ok(10).andThen(x => x + 5); // Ok(15)
179
+ Err("fail").andThen(x => x + 5); // Err("fail") (skipped)
180
+
181
+ // Atom - transforms description (sync only)
182
+ atom("hello").andThen(s => s.toUpperCase()); // Atom("HELLO")
183
+
184
+ // Chained andThen - multiple transformations
185
+ option(5)
186
+ .andThen(x => x + 1)
187
+ .andThen(x => x * 2)
188
+ .andThen(x => x.toString()); // Some("12")
189
+
190
+ Ok(10)
191
+ .andThen(x => x * 2)
192
+ .andThen(x => x + 5)
193
+ .andThen(x => ({ value: x })); // Ok({ value: 25 })
194
+
195
+ atom("hello")
196
+ .andThen(s => s.toUpperCase())
197
+ .andThen(s => s + "!"); // Atom("HELLO!")
198
+
199
+ // Async support for Option and Result
200
+ const data = await option(5).andThen(async x => await fetchData(x));
201
+
202
+ // Error handling
203
+ option(5).andThen(() => { throw "oops" }); // None (caught)
204
+ Ok(5).andThen(() => { throw "oops" }); // Err("oops")
205
+ atom("x").andThen(() => { throw "oops" }); // Panics!
206
+
207
+ // Type transformation
208
+ option(42).andThen(x => x.toString()); // Some("42")
151
209
  ```
152
210
 
153
211
  ### Match
@@ -200,32 +258,123 @@ const label = matchAll(isActive, {
200
258
  println(label); // "Active"
201
259
  ```
202
260
 
203
- ### Expect
261
+ ### Options
204
262
 
205
- Unwraps values or throws with custom message. Use when failure is unrecoverable.
263
+ Normalizes heterogeneous values (raw values, `Option`s, and `Result`s) into a single `Result`. Returns the first usable value as `Ok`, or the provided `Err` fallback if none is usable. A value is usable when its unwrapped inner value is truthy; `None`, `Err`, null, undefined, empty strings, NaN, and Infinity are skipped (note `0` and `false` are usable, matching `option()` semantics).
206
264
 
207
265
  ```ts
208
- const personAge = option(25).expect("a person must have age!");
209
- println("person age", personAge); // 25
266
+ import { Options, option, Ok, Err } from "slang-ts";
267
+
268
+ // First usable value wins
269
+ let a = null;
270
+ let b = option(undefined);
271
+ let c = Ok(true);
272
+ let d = Err("nope!");
273
+
274
+ const something = Options([a, b, c, d], Err("No value!"));
275
+ // Ok(true) - c is usable
276
+
277
+ // Nothing usable -> fallback
278
+ let e = option(null);
279
+ const nothing = Options([a, b, e, d], Err("No value!"));
280
+ // Err("No value!")
281
+
282
+ // Raw (non-wrapped) values work too, just like a single option():
283
+ Options([null, undefined, "hello"]); // Ok("hello") - raw truthy wins
284
+ Options(["", 0, "zero is skipped, 0 used"]); // Ok("...") - skips "" first
285
+ Options([null, undefined, ""]); // Err("No value!") - all raw falsy
286
+ ```
210
287
 
211
- // This would throw!
212
- // const personAge2 = option("").expect("a person must have age!");
288
+ Useful when values come from different functions with inconsistent return types and all you care about is whether the end result is usable.
289
+
290
+ ### SafeTry
291
+
292
+ Runs sync or async functions and captures failures as `Err<string>`. Always await it. Nested Result, Option, and Atom values resolve to one `Result`: `Ok` and `Some` unwrap until a plain value appears, `Err` stops with its error normalized through `String`, `None` becomes `Err("Option was None")`, and Atom becomes its description.
293
+
294
+ ```ts
295
+ import { safeTry, Ok, Err, option, atom, println, type Result } from "slang-ts";
296
+
297
+ // Inferred type is Promise<Result<number, string>>, not nested Results.
298
+ const pending: Promise<Result<number, string>> = safeTry(() => Ok(Ok(42)));
299
+ const nested = await pending; // Ok(42)
300
+ if (nested.isOk) println(nested.value); // 42
301
+
302
+ const failed = await safeTry(() => Ok(Ok(Err("Not found")))); // Err("Not found")
303
+ if (failed.isErr) println(failed.error); // "Not found"
304
+
305
+ const mixed = await safeTry(() => Ok(option(Ok(atom("ready")))));
306
+ // Ok("ready"); inferred success value is the literal "ready".
307
+ if (mixed.isOk) println(mixed.value); // "ready"
308
+
309
+ const missing = await safeTry(() => Ok(option(null))); // Err("Option was None")
310
+ if (missing.isErr) println(missing.error);
311
+
312
+ const caught = await safeTry(async () => {
313
+ throw new Error("Could not load profile");
314
+ }); // Err("Could not load profile")
315
+ if (caught.isErr) println(caught.error);
213
316
  ```
214
317
 
215
- ### Unwrap/Else
318
+ Only the returned wrapper chain unwraps. Plain object properties and array elements stay as supplied. Only the callback's return is awaited; a Promise inside `Ok` or `Some` stays a Promise.
216
319
 
217
- Chainable unwrapping with mandatory fallback. Must call `.else()` or throws.
320
+ ```ts
321
+ import { safeTry, Ok, println } from "slang-ts";
322
+
323
+ const payload = { result: Ok(42), items: [Ok(7)] };
324
+ const plain = await safeTry(() => payload); // Ok(payload), same object
325
+ if (plain.isOk) println(plain.value === payload); // true
326
+
327
+ const innerPromise = Promise.resolve(42);
328
+ const wrapped = await safeTry(() => Ok(innerPromise));
329
+ // Ok(innerPromise), success value stays Promise<number>.
330
+ if (wrapped.isOk) println(wrapped.value === innerPromise); // true
331
+ ```
332
+
333
+ `{ throw: true }` rethrows errors caught while running the callback or reading its returned wrappers. Returned `Err` values still return normally. A circular wrapper chain returns `Err("Cannot resolve a circular value")`, including with `{ throw: true }`.
218
334
 
219
335
  ```ts
220
- const port = option(process.env.PORT).unwrap().else(3000);
221
- println("Using port:", port);
336
+ import { safeTry, Ok, Err, println } from "slang-ts";
222
337
 
223
- // Function fallbacks
224
- const retries = option(null).unwrap().else(() => 5);
225
- println("Retries:", retries);
338
+ const returned = await safeTry(() => Ok(Err("Not found")), { throw: true });
339
+ if (returned.isErr) println(returned.error); // "Not found"
226
340
 
227
- // This throws! No .else() chained
228
- // const nothing = option(null).unwrap();
341
+ await safeTry(() => {
342
+ throw new Error("Critical failure");
343
+ }, { throw: true }).catch((error: Error) => println(error.message));
344
+ // "Critical failure": the call rejects instead of returning Err.
345
+ ```
346
+
347
+ ### Pipe
348
+
349
+ Sequential function composition where each function receives a `Result` and returns a `Result`. Accepts plain values, Option, Result, or Atom as initial input.
350
+
351
+ ```ts
352
+ import { pipe, Ok, Err, option, type Result } from "slang-ts";
353
+
354
+ // Create pipeline functions
355
+ const add = (x: number) => (res: Result<number, string>) =>
356
+ res.isOk ? Ok(res.value + x) : res;
357
+
358
+ const multiply = (x: number) => (res: Result<number, string>) =>
359
+ res.isOk ? Ok(res.value * x) : res;
360
+
361
+ // Basic usage
362
+ const result = await pipe(5, add(3), multiply(2)).run();
363
+ println("Result:", result.value); // 16
364
+
365
+ // With Options as initial value
366
+ const fromOption = await pipe(option(10), add(5)).run();
367
+ println("From option:", fromOption.value); // 15
368
+
369
+ // With callbacks and error handling
370
+ const result = await pipe(5, add(3), multiply(2)).run({
371
+ onEach: ({ currentFn, prevResult }) => {
372
+ println("Executed:", currentFn);
373
+ },
374
+ onSuccess: (value) => println("Done:", value),
375
+ onError: (err) => println("Failed:", err.message),
376
+ allowErrors: false, // stops pipeline on first Err
377
+ });
229
378
  ```
230
379
 
231
380
  ### To
@@ -243,48 +392,24 @@ const errResult = option(null).to("result");
243
392
  println("Result:", errResult.type); // "Err"
244
393
  ```
245
394
 
246
- ### andThen
395
+ ### Atom
247
396
 
248
- Chainable transformation for `Option`, `Result`, and `Atom`. Transforms the inner value while preserving the wrapper type. Returns original instance if provided transformation function returns `undefined`.
397
+ Creates unique, non-interned symbols with semantic descriptions. Each call produces a distinct identity. Define them in one file and import from it everywhere else, handy for env-var style constants.
249
398
 
250
399
  ```ts
251
- // Option - transforms Some, skips None
252
- option(5).andThen(x => x * 2); // Some(10)
253
- option(null).andThen(x => x * 2); // None (skipped)
254
- option(5).andThen(() => undefined); // Some(5) - original
255
-
256
- // Result - transforms Ok, skips Err
257
- Ok(10).andThen(x => x + 5); // Ok(15)
258
- Err("fail").andThen(x => x + 5); // Err("fail") (skipped)
259
-
260
- // Atom - transforms description (sync only)
261
- atom("hello").andThen(s => s.toUpperCase()); // Atom("HELLO")
262
-
263
- // Chained andThen - multiple transformations
264
- option(5)
265
- .andThen(x => x + 1)
266
- .andThen(x => x * 2)
267
- .andThen(x => x.toString()); // Some("12")
268
-
269
- Ok(10)
270
- .andThen(x => x * 2)
271
- .andThen(x => x + 5)
272
- .andThen(x => ({ value: x })); // Ok({ value: 25 })
273
-
274
- atom("hello")
275
- .andThen(s => s.toUpperCase())
276
- .andThen(s => s + "!"); // Atom("HELLO!")
400
+ import { atom } from "slang-ts";
277
401
 
278
- // Async support for Option and Result
279
- const data = await option(5).andThen(async x => await fetchData(x));
402
+ const userAtom = atom("kizz");
403
+ const user2Atom = atom("kizz");
280
404
 
281
- // Error handling
282
- option(5).andThen(() => { throw "oops" }); // None (caught)
283
- Ok(5).andThen(() => { throw "oops" }); // Err("oops")
284
- atom("x").andThen(() => { throw "oops" }); // Panics!
405
+ println(userAtom === atom("kizz")); // false - non interned
406
+ println(userAtom.description); // "kizz"
285
407
 
286
- // Type transformation
287
- option(42).andThen(x => x.toString()); // Some("42")
408
+ if (userAtom === user2Atom) {
409
+ println("all the same");
410
+ } else {
411
+ println("not the same"); // This prints!
412
+ }
288
413
  ```
289
414
 
290
415
  ### Zip
@@ -348,96 +473,218 @@ println(unzip(zipped));
348
473
  // [[1, 2, 3], [4, 5, 6]]
349
474
  ```
350
475
 
351
- ### Pipe
476
+ ### Panic
352
477
 
353
- Sequential function composition where each function receives a `Result` and returns a `Result`. Accepts plain values, Option, Result, or Atom as initial input.
478
+ Throws an error immediately. Use for unrecoverable failures.
354
479
 
355
480
  ```ts
356
- import { pipe, Ok, Err, option, type Result } from "slang-ts";
481
+ import { panic } from "slang-ts";
357
482
 
358
- // Create pipeline functions
359
- const add = (x: number) => (res: Result<number, string>) =>
360
- res.isOk ? Ok(res.value + x) : res;
483
+ function processUser(user: User | null) {
484
+ if (!user) panic("User cannot be null");
485
+ return user.name;
486
+ }
361
487
 
362
- const multiply = (x: number) => (res: Result<number, string>) =>
363
- res.isOk ? Ok(res.value * x) : res;
488
+ // Guard clause pattern
489
+ const config = loadConfig();
490
+ if (!config.apiKey) panic("API key required");
491
+ ```
364
492
 
365
- // Basic usage
366
- const result = await pipe(5, add(3), multiply(2)).run();
367
- println("Result:", result.value); // 16
493
+ ### println
368
494
 
369
- // With Options as initial value
370
- const fromOption = await pipe(option(10), add(5)).run();
371
- println("From option:", fromOption.value); // 15
495
+ console.log sugar that stays out of production. Prints a line (args joined, newline appended) and does nothing once `setEnvironment` puts the app in production mode. Dev chatter never leaks to prod logs. Already have an advanced logger? Hand it to `setEnvironment({ printFn })` and every println routes through it: your logging code stays exactly as it is, in every mode.
372
496
 
373
- // With callbacks and error handling
374
- const result = await pipe(5, add(3), multiply(2)).run({
375
- onEach: ({ currentFn, prevResult }) => {
376
- println("Executed:", currentFn);
377
- },
378
- onSuccess: (value) => println("Done:", value),
379
- onError: (err) => println("Failed:", err.message),
380
- allowErrors: false, // stops pipeline on first Err
381
- });
497
+ ```ts
498
+ import { println, setEnvironment } from "slang-ts";
499
+
500
+ setEnvironment("development"); // "dev" works too (default)
501
+ println("name:", name);
502
+ println("multiple", "args", "work", { too: true });
503
+
504
+ setEnvironment("production"); // "prod" works too
505
+ println("not printed in prod");
506
+
507
+ // Route println through an existing logger instead, even in prod
508
+ import { appLogger } from "./logger";
509
+ setEnvironment({ printFn: (line) => appLogger.info(line.trimEnd()) });
382
510
  ```
383
511
 
384
- ### SafeTry
512
+ ### Signal
513
+
514
+ A trigger (we call them signals) is a switch that fires once and stays fired. Flip it when you're ready, and anything waiting on it reacts. They're interchangeable with the platform's `AbortSignal`, so a signal you create can be handed to anything that already accepts one.
515
+
516
+ ```ts
517
+ import { createSignal } from "slang-ts";
518
+
519
+ const ready = createSignal();
385
520
 
386
- Wraps potentially throwing functions in try-catch, returning a `Result<T, string>`. Always needs to be awaited as its async.
521
+ ready.addEventListener("abort", () => println("ready!")); // fires when the trigger goes off
522
+ ready.fire(); // flip it
523
+ ```
524
+
525
+ The useful part: gate work on a signal, or publish events when a lifecycle completes.
387
526
 
388
527
  ```ts
389
- import { safeTry } from "slang-ts";
528
+ const task = superPromise(loadUser, { defer: ready }); // starts only once ready fires
390
529
 
391
- const result = await safeTry(() => {
392
- if (denom === 0) throw new Error("Cannot divide by zero");
393
- return num / denom;
530
+ channel.when({
531
+ signal: task.done, // task finished, success or not
532
+ topic: "user.loaded",
533
+ data: { id: userId },
394
534
  });
535
+ ```
536
+
537
+ ### SuperPromise
538
+
539
+ Native JavaScript promises plus execution controls: wait before starting, retry when it fails, time out when it hangs, cancel from outside, `done` trigger to wire into other things. Tasks settle into `Ok` or `Err`: just check outcome, no try/catch around awaits. Library exports no short alias: alias locally when the name gets heavy: `import { superPromise as sp }`.
540
+
541
+ ```ts
542
+ import { superPromise } from "slang-ts";
543
+
544
+ // A fetch that gives up after 5 seconds and tries up to 3 more times
545
+ const result = await superPromise(
546
+ (signal) => fetch("/api/user", { signal }),
547
+ { timeout: 5000, retry: 3 }
548
+ );
395
549
 
396
550
  if (result.isOk) {
397
- println("Result:", result.value);
551
+ println("User:", result.value);
398
552
  } else {
399
- println("Error:", result.error);
553
+ println("Failed:", result.error);
400
554
  }
555
+ ```
556
+
557
+ No `await` handy? Chain with `.then`; the callback receives the same `Result`:
401
558
 
402
- // Async functions work the same way
403
- const data = await safeTry(async () => {
404
- const res = await fetch("/api/user");
405
- return res.json();
559
+ ```ts
560
+ superPromise(loadUser, { timeout: 5000 }).then((result) => {
561
+ if (result.isOk) {
562
+ println("User:", result.value);
563
+ } else {
564
+ println("Failed:", result.error);
565
+ }
406
566
  });
567
+ ```
407
568
 
408
- if (data.isOk) {
409
- println("User:", data.value);
410
- }
569
+ Need it to wait for something else first? `defer` holds execution until a signal fires; chain operations so the next starts only when the previous is done:
411
570
 
412
- // Re-throw critical errors instead of capturing
413
- await safeTry(() => {
414
- throw new Error("Critical!");
415
- }, { throw: true });
571
+ ```ts
572
+ const config = superPromise(loadConfig);
573
+ const user = superPromise(loadUser, { defer: config.done });
574
+
575
+ const result = await user; // config runs first, then user
416
576
  ```
417
577
 
418
- ### Panic
578
+ Cancel from outside, or keep retrying until you call it off:
419
579
 
420
- Throws an error immediately. Use for unrecoverable failures.
580
+ ```ts
581
+ task.abort(); // cancel; you get Err("Aborted")
582
+ superPromise(fetchData, {
583
+ retryUntil: { signal: stopSignal, delay: 2000 }, // keep trying until stopSignal fires
584
+ });
585
+ ```
586
+
587
+ One gotcha worth knowing: a task doesn't start until you consume it (`await`, `.then()`, or a runner's `start()`), so you can wire up dependencies first and they'll run in the right order when awaited.
588
+
589
+ Also handy:
421
590
 
422
591
  ```ts
423
- import { panic } from "slang-ts";
592
+ const manual = superPromise<number>(); // no executor; settle it yourself
593
+ manual.resolve(100); // or manual.reject(error)
424
594
 
425
- function processUser(user: User | null) {
426
- if (!user) panic("User cannot be null");
427
- return user.name;
428
- }
595
+ await superPromise(loadUser).tap((user) => println(user.name)); // observe without changing
596
+ ```
429
597
 
430
- // Guard clause pattern
431
- const config = loadConfig();
432
- if (!config.apiKey) panic("API key required");
598
+ ### SuperRunner
599
+
600
+ Same idea as `Promise.all`, `Promise.race`, and plain `await` chain, but for SuperPromises, with cancellation and clean `Result` outcomes. Use it when multiple operations are needed: run several in order, in parallel, or take first to finish. Call `start()` once and read result; calling again just hands back same result without re-running anything.
601
+
602
+ ```ts
603
+ import { superRunner, superPromise as sp } from "slang-ts";
604
+
605
+ // In order: steps that depend on each other; stops at the first failure
606
+ const auth = superRunner({
607
+ type: "sequence",
608
+ runners: [sp(authenticate), sp(loadProfile), sp(loadDashboard)],
609
+ });
610
+ const ordered = await auth.start(); // [authResult, profileResult, dashboardResult]
611
+
612
+ // In parallel: all kicked off at once; result keyed by name
613
+ const parallel = superRunner({
614
+ type: "all",
615
+ runners: {
616
+ user: sp(getUser),
617
+ posts: sp(getPosts),
618
+ settings: sp(getSettings),
619
+ },
620
+ });
621
+ const combined = await parallel.start(); // { user, posts, settings }
622
+
623
+ // First to finish: take whichever source responds first, cancel the rest
624
+ const first = superRunner({
625
+ type: "race",
626
+ count: 1,
627
+ runners: {
628
+ primary: sp(fetchPrimary),
629
+ backup: sp(fetchBackup),
630
+ cache: sp(fetchCache),
631
+ },
632
+ });
633
+ const winner = await first.start(); // { cache: ... } or whichever won
634
+ ```
635
+
636
+ Cancel everything with one call:
637
+
638
+ ```ts
639
+ workflow.abort(); // cancels all active children
640
+ ```
641
+
642
+ ### Channel
643
+
644
+ Channel lets parts of your app that don't know each other talk: one side publishes, another listens, no wiring between them.
645
+
646
+ ```ts
647
+ import { createChannel } from "slang-ts";
648
+
649
+ const channel = createChannel();
650
+
651
+ const unsubscribe = channel.subscribe({ topic: "user.created" }, (event) => {
652
+ println("Created:", event.data);
653
+ });
654
+
655
+ channel.send({ topic: "user.created", data: user });
656
+ unsubscribe(); // stop listening
433
657
  ```
434
658
 
435
- And more are to be implemented in coming versions...
659
+ Publish when a trigger goes off, a task finishing, anything at all:
660
+
661
+ ```ts
662
+ channel.when({
663
+ signal: workflow.done,
664
+ topic: "workflow.completed",
665
+ data: { workflowId },
666
+ });
667
+ ```
436
668
 
437
669
  ## Code Samples
438
670
 
439
671
  See [example.ts](https://github.com/Hussseinkizz/slang/blob/main/example.ts) for usage of currently implemented methods.
440
672
 
673
+ ## Development
674
+
675
+ Install project dependencies with Bun, then build:
676
+
677
+ ```bash
678
+ bun install
679
+ bun run build
680
+ ```
681
+
682
+ The build runs tests, checks types with TypeScript 7.0.2, then uses tsdown 0.23.1 to produce minified ESM at `dist/index.js` and bundled types at `dist/index.d.ts`. Package imports keep the same entry points. tsdown currently marks its TypeScript 7 integration as experimental; the project build passes with this setup.
683
+
684
+ For building, tsdown requires Node.js `^22.18.0 || ^24.11.0 || >=26.0.0`. This is a build-tool requirement, not a new runtime requirement for Slang consumers.
685
+
686
+ Packaged output passes strict consumer checks with TypeScript 5.9.3 and 7.0.2 under both NodeNext and Bundler resolution, including declaration checking. See [migration context](context.md) for verification details.
687
+
441
688
  ## Contributing
442
689
 
443
- Contributions are welcome, I know there a lot of cool things out there we can bring in.
690
+ Contributions are welcome. I know there are a lot of cool things out there we can bring in.