slang-ts 0.0.6 → 0.0.8

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,43 @@ 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.8
33
42
 
34
- - Pubsub store with state locks
35
- - Promises and async utilities
43
+ - **Signal**: a simple on/off trigger that fires once and stays fired; use it to make something start only after something else finishes.
44
+ - **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.
45
+ - **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.
46
+ - **SuperRunner**: `Promise.all` and `Promise.race` grown up: run several SuperPromises in order, in parallel, or take first to finish.
47
+ - **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
48
 
37
49
  ## How It Works
38
50
 
39
- You can import utilities individually or together:
51
+ Import utilities individually or together:
40
52
 
41
53
  ```ts
42
54
  // Individual imports
@@ -52,18 +64,6 @@ import * as slang from "slang-ts";
52
64
  slang.println("Hello world!");
53
65
  ```
54
66
 
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
67
  ### Option
68
68
 
69
69
  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,72 +130,6 @@ if (user.isOk) {
130
130
  }
131
131
  ```
132
132
 
133
- ### Atom
134
-
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.
136
-
137
- ```ts
138
- import { atom } from "slang-ts";
139
-
140
- const userAtom = atom("kizz");
141
- const user2Atom = atom("kizz");
142
-
143
- println(userAtom === atom("kizz")); // false - non interned ✅
144
- println(userAtom.description); // "kizz"
145
-
146
- if (userAtom === user2Atom) {
147
- println("all the same");
148
- } else {
149
- println("not the same"); // This prints!
150
- }
151
- ```
152
-
153
- ### Match
154
-
155
- Exhaustive pattern matching for `Option` and `Result` types. Forces you to handle all cases.
156
-
157
- ```ts
158
- import { match } from "slang-ts";
159
-
160
- // Matching Results
161
- const result = divide(10, 0);
162
- match(result, {
163
- Ok: (v) => println("Success:", v.value),
164
- Err: (e) => println("Failed:", e.error),
165
- });
166
-
167
- // Matching Options
168
- const maybePort = option(process.env.PORT);
169
- match(maybePort, {
170
- Some: (v) => println("Port:", v.value),
171
- None: () => println("No port configured"),
172
- });
173
- ```
174
-
175
- ### MatchAll
176
-
177
- Pattern matching for primitives and atoms with required `_` fallback.
178
-
179
- ```ts
180
- import { matchAll } from "slang-ts";
181
-
182
- // Match atoms
183
- const ready = atom("ready");
184
- matchAll(ready, {
185
- ready: () => println("Ready!"),
186
- failed: () => println("Failed!"),
187
- _: () => println("Unknown"),
188
- });
189
-
190
- // Match booleans
191
- const isActive = true;
192
- matchAll(isActive, {
193
- true: () => println("Active"),
194
- false: () => println("Inactive"),
195
- _: () => println("Unknown"),
196
- });
197
- ```
198
-
199
133
  ### Expect
200
134
 
201
135
  Unwraps values or throws with custom message. Use when failure is unrecoverable.
@@ -224,21 +158,6 @@ println("Retries:", retries);
224
158
  // const nothing = option(null).unwrap();
225
159
  ```
226
160
 
227
- ### To
228
-
229
- Converts between Slang types.
230
-
231
- ```ts
232
- const statusAtom = atom("active").to("option");
233
- println("Option:", statusAtom); // Some("active")
234
-
235
- const stateOption = option("ready").to("atom");
236
- println("Atom:", stateOption.description); // "ready"
237
-
238
- const errResult = option(null).to("result");
239
- println("Result:", errResult.type); // "Err"
240
- ```
241
-
242
161
  ### andThen
243
162
 
244
163
  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`.
@@ -283,6 +202,187 @@ atom("x").andThen(() => { throw "oops" }); // Panics!
283
202
  option(42).andThen(x => x.toString()); // Some("42")
284
203
  ```
285
204
 
205
+ ### Match
206
+
207
+ Exhaustive pattern matching for `Option` and `Result` types. Forces you to handle all cases. Returns the value from the matched handler.
208
+
209
+ ```ts
210
+ import { match } from "slang-ts";
211
+
212
+ // Matching Results - returns handler result
213
+ const result = divide(10, 0);
214
+ const message = match(result, {
215
+ Ok: (v) => `Success: ${v.value}`,
216
+ Err: (e) => `Failed: ${e.error}`,
217
+ });
218
+ println(message); // "Failed: Cannot divide by zero"
219
+
220
+ // Matching Options - returns handler result
221
+ const maybePort = option(process.env.PORT);
222
+ const port = match(maybePort, {
223
+ Some: (v) => parseInt(v.value),
224
+ None: () => 3000,
225
+ });
226
+ println("Using port:", port); // Uses parsed port or default 3000
227
+ ```
228
+
229
+ ### MatchAll
230
+
231
+ Pattern matching for primitives and atoms with required `_` fallback. Returns the value from the matched handler.
232
+
233
+ ```ts
234
+ import { matchAll } from "slang-ts";
235
+
236
+ // Match atoms - returns handler result
237
+ const ready = atom("ready");
238
+ const status = matchAll(ready, {
239
+ ready: () => "System is ready",
240
+ failed: () => "System failed",
241
+ _: () => "Unknown state",
242
+ });
243
+ println(status); // "System is ready"
244
+
245
+ // Match booleans - returns handler result
246
+ const isActive = true;
247
+ const label = matchAll(isActive, {
248
+ true: () => "Active",
249
+ false: () => "Inactive",
250
+ _: () => "Unknown",
251
+ });
252
+ println(label); // "Active"
253
+ ```
254
+
255
+ ### Options
256
+
257
+ 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).
258
+
259
+ ```ts
260
+ import { Options, option, Ok, Err } from "slang-ts";
261
+
262
+ // First usable value wins
263
+ let a = null;
264
+ let b = option(undefined);
265
+ let c = Ok(true);
266
+ let d = Err("nope!");
267
+
268
+ const something = Options([a, b, c, d], Err("No value!"));
269
+ // Ok(true) - c is usable
270
+
271
+ // Nothing usable -> fallback
272
+ let e = option(null);
273
+ const nothing = Options([a, b, e, d], Err("No value!"));
274
+ // Err("No value!")
275
+
276
+ // Raw (non-wrapped) values work too, just like a single option():
277
+ Options([null, undefined, "hello"]); // Ok("hello") - raw truthy wins
278
+ Options(["", 0, "zero is skipped, 0 used"]); // Ok("...") - skips "" first
279
+ Options([null, undefined, ""]); // Err("No value!") - all raw falsy
280
+ ```
281
+
282
+ Useful when values come from different functions with inconsistent return types and all you care about is whether the end result is usable.
283
+
284
+ ### SafeTry
285
+
286
+ Wraps potentially throwing functions in try-catch, returning a `Result<T, string>`. Always needs to be awaited as its async.
287
+
288
+ ```ts
289
+ import { safeTry } from "slang-ts";
290
+
291
+ const result = await safeTry(() => {
292
+ if (denom === 0) throw new Error("Cannot divide by zero");
293
+ return num / denom;
294
+ });
295
+
296
+ if (result.isOk) {
297
+ println("Result:", result.value);
298
+ } else {
299
+ println("Error:", result.error);
300
+ }
301
+
302
+ // Async functions work the same way
303
+ const data = await safeTry(async () => {
304
+ const res = await fetch("/api/user");
305
+ return res.json();
306
+ });
307
+
308
+ if (data.isOk) {
309
+ println("User:", data.value);
310
+ }
311
+
312
+ // Re-throw critical errors instead of capturing
313
+ await safeTry(() => {
314
+ throw new Error("Critical!");
315
+ }, { throw: true });
316
+ ```
317
+
318
+ ### Pipe
319
+
320
+ Sequential function composition where each function receives a `Result` and returns a `Result`. Accepts plain values, Option, Result, or Atom as initial input.
321
+
322
+ ```ts
323
+ import { pipe, Ok, Err, option, type Result } from "slang-ts";
324
+
325
+ // Create pipeline functions
326
+ const add = (x: number) => (res: Result<number, string>) =>
327
+ res.isOk ? Ok(res.value + x) : res;
328
+
329
+ const multiply = (x: number) => (res: Result<number, string>) =>
330
+ res.isOk ? Ok(res.value * x) : res;
331
+
332
+ // Basic usage
333
+ const result = await pipe(5, add(3), multiply(2)).run();
334
+ println("Result:", result.value); // 16
335
+
336
+ // With Options as initial value
337
+ const fromOption = await pipe(option(10), add(5)).run();
338
+ println("From option:", fromOption.value); // 15
339
+
340
+ // With callbacks and error handling
341
+ const result = await pipe(5, add(3), multiply(2)).run({
342
+ onEach: ({ currentFn, prevResult }) => {
343
+ println("Executed:", currentFn);
344
+ },
345
+ onSuccess: (value) => println("Done:", value),
346
+ onError: (err) => println("Failed:", err.message),
347
+ allowErrors: false, // stops pipeline on first Err
348
+ });
349
+ ```
350
+
351
+ ### To
352
+
353
+ Converts between Slang types.
354
+
355
+ ```ts
356
+ const statusAtom = atom("active").to("option");
357
+ println("Option:", statusAtom); // Some("active")
358
+
359
+ const stateOption = option("ready").to("atom");
360
+ println("Atom:", stateOption.description); // "ready"
361
+
362
+ const errResult = option(null).to("result");
363
+ println("Result:", errResult.type); // "Err"
364
+ ```
365
+
366
+ ### Atom
367
+
368
+ 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.
369
+
370
+ ```ts
371
+ import { atom } from "slang-ts";
372
+
373
+ const userAtom = atom("kizz");
374
+ const user2Atom = atom("kizz");
375
+
376
+ println(userAtom === atom("kizz")); // false - non interned
377
+ println(userAtom.description); // "kizz"
378
+
379
+ if (userAtom === user2Atom) {
380
+ println("all the same");
381
+ } else {
382
+ println("not the same"); // This prints!
383
+ }
384
+ ```
385
+
286
386
  ### Zip
287
387
 
288
388
  Combines multiple collections element-wise into tuples.
@@ -344,91 +444,198 @@ println(unzip(zipped));
344
444
  // [[1, 2, 3], [4, 5, 6]]
345
445
  ```
346
446
 
347
- ### Pipe
447
+ ### Panic
348
448
 
349
- Sequential function composition where each function receives a `Result` and returns a `Result`. Accepts plain values, Option, Result, or Atom as initial input.
449
+ Throws an error immediately. Use for unrecoverable failures.
350
450
 
351
451
  ```ts
352
- import { pipe, Ok, Err, option, type Result } from "slang-ts";
452
+ import { panic } from "slang-ts";
353
453
 
354
- // Create pipeline functions
355
- const add = (x: number) => (res: Result<number, string>) =>
356
- res.isOk ? Ok(res.value + x) : res;
454
+ function processUser(user: User | null) {
455
+ if (!user) panic("User cannot be null");
456
+ return user.name;
457
+ }
357
458
 
358
- const multiply = (x: number) => (res: Result<number, string>) =>
359
- res.isOk ? Ok(res.value * x) : res;
459
+ // Guard clause pattern
460
+ const config = loadConfig();
461
+ if (!config.apiKey) panic("API key required");
462
+ ```
360
463
 
361
- // Basic usage
362
- const result = await pipe(5, add(3), multiply(2)).run();
363
- println("Result:", result.value); // 16
464
+ ### println
364
465
 
365
- // With Options as initial value
366
- const fromOption = await pipe(option(10), add(5)).run();
367
- println("From option:", fromOption.value); // 15
466
+ 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.
368
467
 
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
- });
468
+ ```ts
469
+ import { println, setEnvironment } from "slang-ts";
470
+
471
+ setEnvironment("development"); // "dev" works too (default)
472
+ println("name:", name);
473
+ println("multiple", "args", "work", { too: true });
474
+
475
+ setEnvironment("production"); // "prod" works too
476
+ println("not printed in prod");
477
+
478
+ // Route println through an existing logger instead, even in prod
479
+ import { appLogger } from "./logger";
480
+ setEnvironment({ printFn: (line) => appLogger.info(line.trimEnd()) });
378
481
  ```
379
482
 
380
- ### SafeTry
483
+ ### Signal
381
484
 
382
- Wraps potentially throwing functions in try-catch, returning a `Result<T, string>`. Always needs to be awaited as its async.
485
+ 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.
383
486
 
384
487
  ```ts
385
- import { safeTry } from "slang-ts";
488
+ import { createSignal } from "slang-ts";
386
489
 
387
- const result = await safeTry(() => {
388
- if (denom === 0) throw new Error("Cannot divide by zero");
389
- return num / denom;
490
+ const ready = createSignal();
491
+
492
+ ready.addEventListener("abort", () => println("ready!")); // fires when the trigger goes off
493
+ ready.fire(); // flip it
494
+ ```
495
+
496
+ The useful part: gate work on a signal, or publish events when a lifecycle completes.
497
+
498
+ ```ts
499
+ const task = superPromise(loadUser, { defer: ready }); // starts only once ready fires
500
+
501
+ channel.when({
502
+ signal: task.done, // task finished, success or not
503
+ topic: "user.loaded",
504
+ data: { id: userId },
390
505
  });
506
+ ```
507
+
508
+ ### SuperPromise
509
+
510
+ 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 }`.
511
+
512
+ ```ts
513
+ import { superPromise } from "slang-ts";
514
+
515
+ // A fetch that gives up after 5 seconds and tries up to 3 more times
516
+ const result = await superPromise(
517
+ (signal) => fetch("/api/user", { signal }),
518
+ { timeout: 5000, retry: 3 }
519
+ );
391
520
 
392
521
  if (result.isOk) {
393
- println("Result:", result.value);
522
+ println("User:", result.value);
394
523
  } else {
395
- println("Error:", result.error);
524
+ println("Failed:", result.error);
396
525
  }
526
+ ```
397
527
 
398
- // Async functions work the same way
399
- const data = await safeTry(async () => {
400
- const res = await fetch("/api/user");
401
- return res.json();
528
+ No `await` handy? Chain with `.then`; the callback receives the same `Result`:
529
+
530
+ ```ts
531
+ superPromise(loadUser, { timeout: 5000 }).then((result) => {
532
+ if (result.isOk) {
533
+ println("User:", result.value);
534
+ } else {
535
+ println("Failed:", result.error);
536
+ }
402
537
  });
538
+ ```
403
539
 
404
- if (data.isOk) {
405
- println("User:", data.value);
406
- }
540
+ 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:
407
541
 
408
- // Re-throw critical errors instead of capturing
409
- await safeTry(() => {
410
- throw new Error("Critical!");
411
- }, { throw: true });
542
+ ```ts
543
+ const config = superPromise(loadConfig);
544
+ const user = superPromise(loadUser, { defer: config.done });
545
+
546
+ const result = await user; // config runs first, then user
412
547
  ```
413
548
 
414
- ### Panic
549
+ Cancel from outside, or keep retrying until you call it off:
415
550
 
416
- Throws an error immediately. Use for unrecoverable failures.
551
+ ```ts
552
+ task.abort(); // cancel; you get Err("Aborted")
553
+ superPromise(fetchData, {
554
+ retryUntil: { signal: stopSignal, delay: 2000 }, // keep trying until stopSignal fires
555
+ });
556
+ ```
557
+
558
+ 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.
559
+
560
+ Also handy:
417
561
 
418
562
  ```ts
419
- import { panic } from "slang-ts";
563
+ const manual = superPromise<number>(); // no executor; settle it yourself
564
+ manual.resolve(100); // or manual.reject(error)
420
565
 
421
- function processUser(user: User | null) {
422
- if (!user) panic("User cannot be null");
423
- return user.name;
424
- }
566
+ await superPromise(loadUser).tap((user) => println(user.name)); // observe without changing
567
+ ```
425
568
 
426
- // Guard clause pattern
427
- const config = loadConfig();
428
- if (!config.apiKey) panic("API key required");
569
+ ### SuperRunner
570
+
571
+ 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.
572
+
573
+ ```ts
574
+ import { superRunner, superPromise as sp } from "slang-ts";
575
+
576
+ // In order: steps that depend on each other; stops at the first failure
577
+ const auth = superRunner({
578
+ type: "sequence",
579
+ runners: [sp(authenticate), sp(loadProfile), sp(loadDashboard)],
580
+ });
581
+ const ordered = await auth.start(); // [authResult, profileResult, dashboardResult]
582
+
583
+ // In parallel: all kicked off at once; result keyed by name
584
+ const parallel = superRunner({
585
+ type: "all",
586
+ runners: {
587
+ user: sp(getUser),
588
+ posts: sp(getPosts),
589
+ settings: sp(getSettings),
590
+ },
591
+ });
592
+ const combined = await parallel.start(); // { user, posts, settings }
593
+
594
+ // First to finish: take whichever source responds first, cancel the rest
595
+ const first = superRunner({
596
+ type: "race",
597
+ count: 1,
598
+ runners: {
599
+ primary: sp(fetchPrimary),
600
+ backup: sp(fetchBackup),
601
+ cache: sp(fetchCache),
602
+ },
603
+ });
604
+ const winner = await first.start(); // { cache: ... } or whichever won
605
+ ```
606
+
607
+ Cancel everything with one call:
608
+
609
+ ```ts
610
+ workflow.abort(); // cancels all active children
611
+ ```
612
+
613
+ ### Channel
614
+
615
+ Channel lets parts of your app that don't know each other talk: one side publishes, another listens, no wiring between them.
616
+
617
+ ```ts
618
+ import { createChannel } from "slang-ts";
619
+
620
+ const channel = createChannel();
621
+
622
+ const unsubscribe = channel.subscribe({ topic: "user.created" }, (event) => {
623
+ println("Created:", event.data);
624
+ });
625
+
626
+ channel.send({ topic: "user.created", data: user });
627
+ unsubscribe(); // stop listening
429
628
  ```
430
629
 
431
- And more are to be implemented in coming versions...
630
+ Publish when a trigger goes off, a task finishing, anything at all:
631
+
632
+ ```ts
633
+ channel.when({
634
+ signal: workflow.done,
635
+ topic: "workflow.completed",
636
+ data: { workflowId },
637
+ });
638
+ ```
432
639
 
433
640
  ## Code Samples
434
641
 
@@ -436,4 +643,4 @@ See [example.ts](https://github.com/Hussseinkizz/slang/blob/main/example.ts) for
436
643
 
437
644
  ## Contributing
438
645
 
439
- Contributions are welcome, I know there a lot of cool things out there we can bring in.
646
+ Contributions are welcome. I know there are a lot of cool things out there we can bring in.
package/dist/index.js CHANGED
@@ -1 +1,2 @@
1
- var q=(G)=>{let B=globalThis?.queueMicrotask;if(typeof B==="function")B(G);else Promise.resolve().then(G)},P=(G)=>G!=null&&typeof G==="object"&&("type"in G)&&(G.type==="Ok"||G.type==="Err"),M=(G)=>G!=null&&typeof G==="object"&&("isSome"in G)&&("isNone"in G),O=(G)=>G!=null&&typeof G==="object"&&typeof G.valueOf?.()==="symbol";function N(G){if(O(G))return{ok:!0,value:G.valueOf().description};if(P(G)){if(G.isOk)return{ok:!0,value:G.value};return{ok:!1,error:typeof G.error==="string"?G.error:String(G.error)}}if(M(G)){if(G.isSome)return{ok:!0,value:G.value};return{ok:!1,error:"Option was None"}}return{ok:!0,value:G}}var R=(...G)=>{globalThis?.console?.log?.(...G)};function Z(G){throw Error(G)}function L(G){let B=Object.freeze({type:"Ok",value:G,isOk:!0,isErr:!1}),Y={...B,expect:(D)=>B.value,unwrap:()=>{let D=!1;return q(()=>{}),{else(X){return D=!0,B.value}}},andThen:(D)=>{try{let X=D(B.value);if(X instanceof Promise)return X.then(($)=>{if($===void 0)return Y;return L($)}).catch(($)=>{let J=$ instanceof Error?$.message:String($);return I(J)});if(X===void 0)return Y;return L(X)}catch(X){let $=X instanceof Error?X.message:String(X);return I($)}}};return Y}function I(G){let Y={...Object.freeze({type:"Err",error:G,isOk:!1,isErr:!0}),expect:(D)=>{throw Error(D??G)},unwrap:()=>{let D=!1;return q(()=>{if(!D)throw Error(G)}),{else(X){if(D=!0,typeof X==="function")return X(G);return X}}},andThen:(D)=>{return Y}};return Y}var K=(G)=>{return G===null||G===void 0||G===""||Number.isNaN(G)||G===1/0||G===-1/0};function b(G){if(K(G))throw Error("Cannot wrap null, undefined, NaN, or empty string in Some");return Object.freeze({type:"Some",value:G,isSome:!0,isNone:!1})}var g=Object.freeze({type:"None",isSome:!1,isNone:!0}),S=null;function A(G){S=G}function W(G){let B=K(G)?g:b(G),$={...B,to:(J)=>{if(!S)throw Error("Converter not initialized");return S(B,J)},expect:(J)=>{if(B.isSome)return B.value;throw Error(J??"Expected Some, got None")},unwrap:()=>{let J=!1,C=B;return q(()=>{if(!J)throw Error("Expected else")}),{else(j){if(J=!0,C.isSome)return C.value;let H=typeof j==="function"?j(void 0):j;if(K(H))throw Error("Fallback must be truthy");return H}}},andThen:(J)=>{let C=B;if(C.isNone)return $;let j=C.value;try{let H=J(j);if(H instanceof Promise)return H.then((x)=>{if(x===void 0)return $;return W(x)}).catch(()=>W(null));if(H===void 0)return $;return W(H)}catch{return W(null)}}};return $}var T=null;function F(G){T=G}function U(G){let B=Symbol(G),Q=Object(B),Y=(X)=>{if(!T)throw Error("Converter not initialized");return T(B,X)},D=(X)=>{let $;try{$=X(G)}catch(J){let C=J instanceof Error?J.message:String(J);Z(`Atom.andThen: fn threw an error: ${C}`)}if($===void 0)return Q;if(typeof $!=="string")Z(`Atom.andThen: fn must return a string, got ${typeof $}`);return U($)};return Q.to=Y,Q.andThen=D,Q}var V=(G)=>G!=null&&typeof G==="object"&&("isSome"in G)&&("isNone"in G);function _(G,B){if(G&&(G.type==="Ok"||G.type==="Err"))throw Error("Cannot convert a Result to any other type");switch(B){case"option":{if(V(G))return G;if(typeof G==="symbol")return W(G.description);return W(G)}case"atom":{if(V(G)){if(G.isNone)throw Error("Cannot convert None to Atom");if(typeof G.value!=="string")throw Error("Only string values can be converted to Atom");return U(G.value)}if(typeof G==="symbol")return G;throw Error(`Cannot convert type ${typeof G} to Atom`)}case"result":{if(V(G))return G.isSome?L(G.value):I("Value is None");if(typeof G==="symbol")return L(G.description);return L(G)}default:throw Error(`Invalid target: ${B}`)}}A(_);F(_);function w(G,B){let Q=B[G.type];if(!Q)throw Error(`Non-exhaustive match — missing handler for '${G.type}'`);return Q(G)}var k=(G)=>{return typeof G==="string"||typeof G==="number"||typeof G==="boolean"||typeof G==="symbol"};function y(G,B){let Q=(J)=>typeof J?.valueOf==="function"?J.valueOf():J,Y=(J)=>typeof J==="symbol"?J.description:void 0,D=Q(G);if(!k(D))throw Error(`Unsupported match all value type: ${typeof D}`);let X=Y(D)??D,$=typeof X==="boolean"||typeof X==="number"?String(X):X;if($!=null&&$ in B)return B[$](G);return B._()}function h(G,B=!1){if(!B&&!Array.isArray(G))throw Error("Only arrays allowed when includeValues=false");if(Array.isArray(G))return G;if(G instanceof Set)return Array.from(G);return Object.values(G)}function z(G,B){let{fillValue:Q,includeValues:Y=!1}=B||{};if(G.length===0)return[];let D=G.map((j)=>h(j,Y)),X=Math.max(...D.map((j)=>j.length)),$=Math.min(...D.map((j)=>j.length)),J=Q===void 0?$:X,C=[];for(let j=0;j<J;j++)C.push(D.map((H)=>j<H.length?H[j]:Q));return C}function m(G,B,Q){return z(G,Q).map(B)}function f(G){if(G.length===0)return[];let B=G[0]?.length??0,Q=Array.from({length:B},()=>[]);for(let Y of G)Y.forEach((D,X)=>Q[X]?.push(D));return Q}async function c(G,B){let Q=B?.throw??!1;try{let Y=await G(),D=N(Y);if(D.ok)return L(D.value);return I(D.error)}catch(Y){let D=Y instanceof Error?Y.message:String(Y);if(Q)throw Y instanceof Error?Y:Error(D);return I(D)}}function E(G){return G.error??"Unknown error"}function d(G){let B=N(G);if(B.ok)return L(B.value);return I(B.error)}async function p(G,B){try{let Q=await G(B);if(!P(Q))return I("Pipeline function must return a Result");return Q}catch(Q){let Y=Q instanceof Error?Q.message:String(Q);return I(Y)}}function v(G,...B){return{async run(Q){let{onEach:Y,onSuccess:D,onError:X,allowErrors:$=!1}=Q??{},J=d(G);for(let C=0;C<B.length;C++){let j=B[C],H=B[C+1];if(J.isErr&&!$)return X?.(Error(E(J))),J;J=await p(j,J),Y?.({prevResult:J,currentFn:j.name||`fn${C+1}`,nextFn:H?H.name||`fn${C+2}`:void 0})}if(J.isOk)D?.(J.value);else X?.(Error(E(J)));return J}}}export{m as zipWith,z as zip,f as unzip,q as scheduleMicrotask,c as safeTry,R as println,v as pipe,Z as panic,W as option,y as matchAll,w as match,K as isFalsy,U as atom,_ as _to,L as Ok,I as Err};
1
+ var R=(G)=>{let B=globalThis?.queueMicrotask;if(typeof B==="function")B(G);else Promise.resolve().then(G)},h=(G)=>G!=null&&typeof G==="object"&&("type"in G)&&(G.type==="Ok"||G.type==="Err"),O=Symbol("slang.activate"),r=(G)=>G!=null&&typeof G==="object"&&("isSome"in G)&&("isNone"in G),e=(G)=>G!=null&&typeof G==="object"&&typeof G.valueOf?.()==="symbol";function x(G){if(e(G))return{ok:!0,value:G.valueOf().description};if(h(G)){if(G.isOk)return{ok:!0,value:G.value};return{ok:!1,error:typeof G.error==="string"?G.error:String(G.error)}}if(r(G)){if(G.isSome)return{ok:!0,value:G.value};return{ok:!1,error:"Option was None"}}return{ok:!0,value:G}}var v="development",m=null,GG=(G)=>{if(typeof G==="object"&&G!==null){m=G.printFn??null;return}v=G==="production"||G==="prod"?"production":"development"},BG=(G)=>{if(typeof G==="string")return G;if(typeof G==="symbol")return String(G);if(typeof G==="function")return`[Function ${G.name}]`;try{return String(G)}catch{try{return JSON.stringify(G)??"null"}catch{return Object.prototype.toString.call(G)}}},g=(...G)=>{let B=G.map(BG).join(" ").concat(`
2
+ `);if(m){m(B);return}if(v==="production")return;globalThis.console?.log?.(B)};function E(G){throw Error(G)}function L(G){let B=Object.freeze({type:"Ok",value:G,isOk:!0,isErr:!1}),D={...B,expect:(X)=>B.value,unwrap:()=>{let X=!1;return R(()=>{}),{else(Z){return X=!0,B.value}}},andThen:(X)=>{try{let Z=X(B.value);if(Z instanceof Promise)return Z.then((q)=>{if(q===void 0)return D;return L(q)}).catch((q)=>{let Y=q instanceof Error?q.message:String(q);return $(Y)});if(Z===void 0)return D;return L(Z)}catch(Z){let q=Z instanceof Error?Z.message:String(Z);return $(q)}}};return D}function $(G){let D={...Object.freeze({type:"Err",error:G,isOk:!1,isErr:!0}),expect:(X)=>{throw Error(X??G)},unwrap:()=>{let X=!1;return R(()=>{if(!X)throw Error(G)}),{else(Z){if(X=!0,typeof Z==="function")return Z(G);return Z}}},andThen:(X)=>{return D}};return D}var z=(G)=>{return G===null||G===void 0||G===""||Number.isNaN(G)||G===1/0||G===-1/0};function JG(G){if(z(G))throw Error("Cannot wrap null, undefined, NaN, or empty string in Some");return Object.freeze({type:"Some",value:G,isSome:!0,isNone:!1})}var QG=Object.freeze({type:"None",isSome:!1,isNone:!0}),f=null;function u(G){f=G}function T(G){let B=z(G)?QG:JG(G),q={...B,to:(Y)=>{if(!f)throw Error("Converter not initialized");return f(B,Y)},expect:(Y)=>{if(B.isSome)return B.value;throw Error(Y??"Expected Some, got None")},unwrap:()=>{let Y=!1,Q=B;return R(()=>{if(!Y)throw Error("Expected else")}),{else(K){if(Y=!0,Q.isSome)return Q.value;let I=typeof K==="function"?K(void 0):K;if(z(I))throw Error("Fallback must be truthy");return I}}},andThen:(Y)=>{let Q=B;if(Q.isNone)return q;let K=Q.value;try{let I=Y(K);if(I instanceof Promise)return I.then((M)=>{if(M===void 0)return q;return T(M)}).catch(()=>T(null));if(I===void 0)return q;return T(I)}catch{return T(null)}}};return q}var i=(G)=>{if(typeof G!=="object"||G===null)return G;let B=G;if(B.isSome===!0||B.isOk===!0)return B.value;return G},XG=(G)=>{if(typeof G==="object"&&G!==null){let B=G;if(B.isNone===!0||B.isErr===!0)return!0}return z(i(G))};function YG(G,B=$("No value!")){for(let J of G){if(XG(J))continue;return L(i(J))}return B}var d=null;function s(G){d=G}function b(G){let B=Symbol(G),J=Object(B),D=(Z)=>{if(!d)throw Error("Converter not initialized");return d(B,Z)},X=(Z)=>{let q;try{q=Z(G)}catch(Y){let Q=Y instanceof Error?Y.message:String(Y);E(`Atom.andThen: fn threw an error: ${Q}`)}if(q===void 0)return J;if(typeof q!=="string")E(`Atom.andThen: fn must return a string, got ${typeof q}`);return b(q)};return J.to=D,J.andThen=X,J}var c=(G)=>G!=null&&typeof G==="object"&&("isSome"in G)&&("isNone"in G);function p(G,B){if(G&&(G.type==="Ok"||G.type==="Err"))throw Error("Cannot convert a Result to any other type");switch(B){case"option":{if(c(G))return G;if(typeof G==="symbol")return T(G.description);return T(G)}case"atom":{if(c(G)){if(G.isNone)throw Error("Cannot convert None to Atom");if(typeof G.value!=="string")throw Error("Only string values can be converted to Atom");return b(G.value)}if(typeof G==="symbol")return G;throw Error(`Cannot convert type ${typeof G} to Atom`)}case"result":{if(c(G))return G.isSome?L(G.value):$("Value is None");if(typeof G==="symbol")return L(G.description);return L(G)}default:throw Error(`Invalid target: ${B}`)}}u(p);s(p);function $G(G,B){let J=B[G.type];if(!J)throw Error(`Non-exhaustive match — missing handler for '${G.type}'`);return J(G)}var ZG=(G)=>{return typeof G==="string"||typeof G==="number"||typeof G==="boolean"||typeof G==="symbol"};function qG(G,B){let J=(Y)=>typeof Y?.valueOf==="function"?Y.valueOf():Y,D=(Y)=>typeof Y==="symbol"?Y.description:void 0,X=J(G);if(!ZG(X))throw Error(`Unsupported match all value type: ${typeof X}`);let Z=D(X)??X,q=typeof Z==="boolean"||typeof Z==="number"?String(Z):Z;if(q!=null&&q in B)return B[q](G);return B._()}function DG(G,B=!1){if(!B&&!Array.isArray(G))throw Error("Only arrays allowed when includeValues=false");if(Array.isArray(G))return G;if(G instanceof Set)return Array.from(G);return Object.values(G)}function o(G,B){let{fillValue:J,includeValues:D=!1}=B||{};if(G.length===0)return[];let X=G.map((K)=>DG(K,D)),Z=Math.max(...X.map((K)=>K.length)),q=Math.min(...X.map((K)=>K.length)),Y=J===void 0?q:Z,Q=[];for(let K=0;K<Y;K++)Q.push(X.map((I)=>K<I.length?I[K]:J));return Q}function WG(G,B,J){return o(G,J).map(B)}function HG(G){if(G.length===0)return[];let B=G[0]?.length??0,J=Array.from({length:B},()=>[]);for(let D of G)D.forEach((X,Z)=>J[Z]?.push(X));return J}async function jG(G,B){let J=B?.throw??!1;try{let D=await G(),X=x(D);if(X.ok)return L(X.value);return $(X.error)}catch(D){let X=D instanceof Error?D.message:String(D);if(J)throw D instanceof Error?D:Error(X);return $(X)}}function n(G){return G.error??"Unknown error"}function KG(G){let B=x(G);if(B.ok)return L(B.value);return $(B.error)}async function LG(G,B){try{let J=await G(B);if(!h(J))return $("Pipeline function must return a Result");return J}catch(J){let D=J instanceof Error?J.message:String(J);return $(D)}}function IG(G,...B){return{async run(J){let{onEach:D,onSuccess:X,onError:Z,allowErrors:q=!1}=J??{},Y=KG(G);for(let Q=0;Q<B.length;Q++){let K=B[Q],I=B[Q+1];if(Y.isErr&&!q)return Z?.(Error(n(Y))),Y;Y=await LG(K,Y),D?.({prevResult:Y,currentFn:K.name||`fn${Q+1}`,nextFn:I?I.name||`fn${Q+2}`:void 0})}if(Y.isOk)X?.(Y.value);else Z?.(Error(n(Y)));return Y}}}function w(){let G=new AbortController;return Object.assign(G.signal,{fire:()=>G.abort()})}var l=(G)=>G instanceof Error?G.message:String(G),a=(G,B)=>{if(G.aborted)return B.abort(),()=>{};let J=()=>B.abort();return G.addEventListener("abort",J,{once:!0}),()=>G.removeEventListener("abort",J)};function t(G,B={}){let J=new AbortController,D=w(),X=()=>{},Z=new Promise((W)=>{X=W}),q=!1,Y=!1,Q=(W)=>{if(Y)return;Y=!0,X(W),D.fire()},K=async(W,H,j)=>{let U=!1,N,S=j!==void 0?a(j,H):()=>{},F=a(J.signal,H);if(B.timeout!==void 0)N=setTimeout(()=>{U=!0,H.abort()},B.timeout);let P=(V)=>{if(N!==void 0)clearTimeout(N);return S(),F(),V};try{let V=await W(H.signal),_=x(V);if(H.signal.aborted){if(J.signal.aborted)return P({kind:"aborted"});if(j?.aborted)return P({kind:"stopped"});return P(U?{kind:"timedOut"}:{kind:"aborted"})}if(!_.ok)return P({kind:"error",error:_.error});return P({kind:"value",value:_.value})}catch(V){if(J.signal.aborted)return P({kind:"aborted"});if(j?.aborted)return P({kind:"stopped"});if(H.signal.aborted)return P(U?{kind:"timedOut"}:{kind:"aborted"});return P({kind:"error",error:l(V)})}},I=(W)=>new Promise((H)=>{if(W.aborted){H();return}W[O]?.();let j=()=>{U(),H()},U=()=>{W.removeEventListener("abort",j),J.signal.removeEventListener("abort",j)};W.addEventListener("abort",j,{once:!0}),J.signal.addEventListener("abort",j,{once:!0})}),M=(W)=>new Promise((H)=>{if(J.signal.aborted){H();return}let j=()=>{clearTimeout(N),U(),H()},U=()=>J.signal.removeEventListener("abort",j),N=setTimeout(j,W);J.signal.addEventListener("abort",j,{once:!0})}),k=async()=>{let{defer:W,delay:H,retry:j,retryUntil:U}=B,N=G;if(N===void 0)return;if(J.signal.aborted)return Q($("Aborted"));if(W!==void 0){if(await I(W),J.signal.aborted)return Q($("Aborted"))}if(U!==void 0){let P=U.signal;for(;;){if(J.signal.aborted)return Q($("Aborted"));if(P.aborted)return Q($("Stopped"));let V=new AbortController,_=await K(N,V,P);switch(_.kind){case"value":return Q(L(_.value));case"aborted":return Q($("Aborted"));case"stopped":return Q($("Stopped"));case"timedOut":case"error":{if(J.signal.aborted)return Q($("Aborted"));if(P.aborted)return Q($("Stopped"));if(U.delay!==void 0)await M(U.delay)}}}}if(j!==void 0&&j>0){let P=j;for(;;){if(J.signal.aborted)return Q($("Aborted"));if(H!==void 0)await M(H);if(J.signal.aborted)return Q($("Aborted"));let V=new AbortController,_=await K(N,V,void 0);switch(_.kind){case"value":return Q(L(_.value));case"aborted":return Q($("Aborted"));case"timedOut":return Q($("Timed out"));case"stopped":return Q($("Stopped"));case"error":if(P>0){P-=1;continue}return Q($(_.error))}}}if(H!==void 0)await M(H);if(J.signal.aborted)return Q($("Aborted"));let S=new AbortController,F=await K(N,S,void 0);switch(F.kind){case"value":return Q(L(F.value));case"aborted":return Q($("Aborted"));case"timedOut":return Q($("Timed out"));case"stopped":return Q($("Stopped"));case"error":return Q($(F.error))}},A=()=>{if(q)return;if(q=!0,G===void 0)return;k()};D[O]=A;let C={done:D,abort:()=>{J.abort(),Q($("Aborted"))},resolve:(W)=>Q(L(W)),reject:(W)=>Q($(l(W))),tap:(W)=>t(async()=>{let H=await C;if(H.isOk)await W(H.value);return H}),then:(W,H)=>{return A(),Z.then(W,H)},catch:(W)=>{return A(),Z.catch(W)},finally:(W)=>{return A(),Z.finally(W)}};return C}function PG(G){let{type:B}=G;if(B==="race"&&G.count<1)throw Error("superRunner race count must be at least 1");let J=new AbortController,D=w(),X=()=>{},Z=new Promise((C)=>{X=C}),q=!1,Y=!1,Q=(C)=>{if(Y)return;Y=!0,X(C),D.fire()},K=B==="sequence"?G.runners:Object.values(G.runners),I=()=>{for(let C of K)C.abort()},M=async(C)=>{let W=[];for(let H of C){if(J.signal.aborted)return Q($("Aborted"));let j=await H;if(J.signal.aborted)return Q($("Aborted"));if(j.isErr)return Q($(j.error));W.push(j.value)}Q(L(W))},k=async(C)=>{let W=Object.entries(C),H={},j=0;for(let[U,N]of W)N.then((S)=>{if(j+=1,S.isOk)H[U]=S.value;else if(!J.signal.aborted&&!Y){I(),Q($(S.error));return}if(j===W.length)Q(L(H))});if(W.length===0)Q(L(H))},A=async(C,W)=>{let H=Object.entries(C),j={},U=0,N=0;for(let[S,F]of H)F.then((P)=>{if(N+=1,P.isOk){if(j[S]=P.value,U+=1,U>=W){I(),Q(L(j));return}}if(N===H.length)Q($(U===0?"All runners failed":"Not enough runners succeeded"))});if(H.length===0)Q($("All runners failed"))},y=()=>{if(!q)if(q=!0,B==="sequence")M(G.runners);else if(B==="all")k(G.runners);else A(G.runners,G.count);return Z};return D[O]=y,{done:D,start:y,abort:()=>{J.abort(),I(),Q($("Aborted"))}}}function UG(){let G=new Map,B=(X)=>{let Z=G.get(X.topic);if(Z===void 0)return;for(let q of Z)try{q(X)}catch(Y){g("channel subscriber threw:",Y)}};return{send:B,subscribe:(X,Z)=>{let q=G.get(X.topic);if(q===void 0)q=new Set,G.set(X.topic,q);return q.add(Z),()=>{let Y=G.get(X.topic);if(Y!==void 0)Y.delete(Z)}},when:(X)=>{if(X.signal.aborted){B({topic:X.topic,data:X.data});return}X.signal.addEventListener("abort",()=>B({topic:X.topic,data:X.data}),{once:!0})}}}export{WG as zipWith,o as zip,HG as unzip,PG as superRunner,t as superPromise,GG as setEnvironment,R as scheduleMicrotask,jG as safeTry,g as println,IG as pipe,E as panic,T as option,qG as matchAll,$G as match,z as isFalsy,w as createSignal,UG as createChannel,b as atom,p as _to,YG as Options,L as Ok,$ as Err};
@@ -0,0 +1,39 @@
1
+ /** Event published on a Channel. */
2
+ export type ChannelEvent = {
3
+ topic: string;
4
+ data: unknown;
5
+ };
6
+ /** Subscriber handler receiving published events. */
7
+ export type ChannelSubscriber = (event: ChannelEvent) => void;
8
+ /** Event communication between independent parts of an application. */
9
+ export type Channel = {
10
+ /** Immediately publishes an event to subscribers of its topic. */
11
+ send(event: ChannelEvent): void;
12
+ /**
13
+ * Subscribes to events of a specific topic.
14
+ * Returns an unsubscribe function; after calling it, no further events are received.
15
+ */
16
+ subscribe(filter: {
17
+ topic: string;
18
+ }, handler: ChannelSubscriber): () => void;
19
+ /** Publishes an event when a signal fires. Accepts any compatible lifecycle signal. */
20
+ when(config: {
21
+ signal: AbortSignal;
22
+ topic: string;
23
+ data: unknown;
24
+ }): void;
25
+ };
26
+ /**
27
+ * Creates a Channel — completely independent of SuperPromise/SuperRunner.
28
+ *
29
+ * @example
30
+ * const channel = createChannel();
31
+ *
32
+ * const unsubscribe = channel.subscribe({ topic: "user.created" }, event => {
33
+ * println(event.data);
34
+ * });
35
+ *
36
+ * channel.send({ topic: "user.created", data: user });
37
+ * unsubscribe(); // stop receiving events
38
+ */
39
+ export declare function createChannel(): Channel;
@@ -1,10 +1,13 @@
1
1
  export { scheduleMicrotask } from "./internals";
2
- export { println } from "./println";
2
+ export { println, setEnvironment } from "./println";
3
+ export type { EnvironmentMode, EnvironmentOptions, PrintFn } from "./println";
3
4
  export { panic } from "./panic";
4
5
  export { Ok, Err } from "./result";
5
6
  export type { Result, ResultMethods, Ok as OkType, Err as ErrType } from "./result";
6
7
  export { option, isFalsy } from "./option";
7
8
  export type { Option, OptionMethods, Some, None, NonTruthy } from "./option";
9
+ export { Options } from "./options";
10
+ export type { OptionsValue } from "./options";
8
11
  export { atom } from "./atom";
9
12
  export type { Atom, AtomMethods } from "./atom";
10
13
  import "./to";
@@ -14,3 +17,11 @@ export { zip, zipWith, unzip } from "./zip";
14
17
  export { safeTry } from "./safe-try";
15
18
  export { pipe } from "./pipe";
16
19
  export type { PipeFn, PipeEachContext, PipeRunOptions, Pipeline } from "./pipe";
20
+ export { superPromise } from "./super-promise";
21
+ export type { SuperPromise, SuperPromiseOptions, Executor, RetryUntilOptions } from "./super-promise";
22
+ export { superRunner } from "./super-runner";
23
+ export type { SuperRunner } from "./super-runner";
24
+ export { createChannel } from "./channel";
25
+ export type { Channel, ChannelEvent, ChannelSubscriber } from "./channel";
26
+ export { createSignal } from "./signal";
27
+ export type { Signal } from "./signal";
@@ -7,6 +7,14 @@ import type { Option } from "./option";
7
7
  export declare const scheduleMicrotask: (fn: () => void) => void;
8
8
  /** Type guard for Result */
9
9
  export declare const isResult: (value: unknown) => value is Result<unknown, unknown>;
10
+ /**
11
+ * Hidden activation hook carried by lifecycle signals (task.done, runner.done).
12
+ * Lets defer chains activate their source task when awaited.
13
+ */
14
+ export declare const ACTIVATE_HOOK: unique symbol;
15
+ export type Activatable = {
16
+ [ACTIVATE_HOOK]?: () => void;
17
+ };
10
18
  /** Type guard for Option */
11
19
  export declare const isOption: (value: unknown) => value is Option<unknown>;
12
20
  /** Type guard for Atom (boxed symbol) */
@@ -23,11 +23,18 @@ type MatchKey = string | number | symbol;
23
23
  /**
24
24
  * Type-safe pattern object: must always have `_` fallback.
25
25
  */
26
- type MatchPatterns<V, R> = {
27
- [K in MatchKey]?: (v: V) => R;
26
+ type MatchPatterns<V> = {
27
+ [K in MatchKey]?: (v: V) => unknown;
28
28
  } & {
29
- _: () => R;
29
+ _: () => unknown;
30
30
  };
31
+ /**
32
+ * Extracts return types from all handlers in a patterns object as a union.
33
+ * Allows each arm to return a different type.
34
+ */
35
+ type InferReturnTypes<P> = {
36
+ [K in keyof P]: P[K] extends (...args: any[]) => infer R ? R : never;
37
+ }[keyof P];
31
38
  /**
32
39
  * Matches a value against literal or Atom cases by *semantic name*.
33
40
  * - Supports string, number, booleans and Atom (symbol) values.
@@ -47,5 +54,5 @@ type MatchPatterns<V, R> = {
47
54
  * _: () => println("Unknown!"),
48
55
  * });
49
56
  */
50
- export declare function matchAll<T extends MatchKey | boolean, R>(value: T, patterns: MatchPatterns<T, R>): R;
57
+ export declare function matchAll<T extends MatchKey | boolean, P extends MatchPatterns<T>>(value: T, patterns: P): InferReturnTypes<P>;
51
58
  export {};
@@ -0,0 +1,41 @@
1
+ import { Err, type Result, type ResultMethods } from "./result";
2
+ /**
3
+ * Resolves the usable inner type of a value.
4
+ * - `Some<U>` / `Ok<U>` contribute `U`.
5
+ * - `None`, `Err`, null, undefined, and empty string contribute nothing.
6
+ * - Raw values contribute themselves.
7
+ */
8
+ export type OptionsValue<T> = T extends {
9
+ isErr: true;
10
+ } | {
11
+ isNone: true;
12
+ } | null | undefined | "" ? never : T extends {
13
+ isSome: true;
14
+ value: infer U;
15
+ } | {
16
+ isOk: true;
17
+ value: infer U;
18
+ } ? U : T;
19
+ /**
20
+ * Returns the first usable value from a list as an `Ok`, or the fallback
21
+ * `Err` if none is usable.
22
+ *
23
+ * Normalizes heterogeneous values — raw values, `Option`s, and `Result`s
24
+ * from different sources — when all you care about is whether any of them
25
+ * is usable. A value is usable when its unwrapped inner value is truthy;
26
+ * `None`, `Err`, null, undefined, `""`, NaN, and Infinity are skipped.
27
+ * Note that `0` and `false` are usable, matching `option()` semantics.
28
+ *
29
+ * @param values - Values to inspect in order; first usable one wins.
30
+ * @param fallback - `Err` returned when no value is usable (default `Err("No value!")`).
31
+ * @returns `Ok` of the first usable inner value, or the fallback.
32
+ * @example
33
+ * const a = null;
34
+ * const b = option(undefined);
35
+ * const c = Ok(true);
36
+ * const d = Err("nope!");
37
+ * Options([a, b, c, d]); // Ok(true) - c is usable
38
+ *
39
+ * const nothing = Options([a, option(null), Err("nope!")]); // Err("No value!")
40
+ */
41
+ export declare function Options<T extends readonly unknown[]>(values: T, fallback?: Err<string> & ResultMethods<never>): Result<OptionsValue<T[number]>, string>;
@@ -1,7 +1,20 @@
1
+ export type EnvironmentMode = "development" | "production" | "dev" | "prod";
2
+ export type PrintFn = (line: string) => void;
3
+ export type EnvironmentOptions = {
4
+ printFn?: PrintFn | null;
5
+ };
1
6
  /**
2
- * Logs the provided arguments to the console.
3
- * - Uses `globalThis.console.log` if available.
4
- * - Supports variadic arguments.
5
- * @param args - The arguments to log. Can be of any type.
7
+ * Sets the runtime environment for dev-only utilities.
8
+ * Idempotent — safe to call any number of times, in any order; the last call wins.
9
+ * Accepts a mode (full words or short forms: "development"/"dev", "production"/"prod")
10
+ * or options `{ printFn }` to route println through an existing logger instead of
11
+ * the console — useful in production, where logs go somewhere real.
12
+ * Pass `{ printFn: null }` to switch back to the console.
13
+ */
14
+ export declare const setEnvironment: (next: EnvironmentMode | EnvironmentOptions) => void;
15
+ /**
16
+ * Prints a line to the console — args joined with spaces, newline appended.
17
+ * When a custom printFn is set, the line goes to it instead, in every mode.
18
+ * Otherwise production mode is silent, so dev chatter never leaks to prod logs.
6
19
  */
7
20
  export declare const println: (...args: unknown[]) => void;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * A lifecycle Signal — a trigger that fires once and stays fired.
3
+ *
4
+ * Abort-compatible: structurally an `AbortSignal` plus a `fire()` method,
5
+ * so it can be passed anywhere a native `AbortSignal` is expected
6
+ * (and vice-versa). Wraps AbortController semantics.
7
+ *
8
+ * Consumed by `defer`, `retryUntil`, `channel.when`, and `task.done`.
9
+ */
10
+ export type Signal = AbortSignal & {
11
+ /** Fires the signal once. Subsequent calls are no-ops. */
12
+ fire(): void;
13
+ };
14
+ /**
15
+ * Creates a lifecycle signal.
16
+ *
17
+ * ```ts
18
+ * const signal = createSignal();
19
+ *
20
+ * signal.addEventListener("abort", () => {
21
+ * println("fired");
22
+ * });
23
+ *
24
+ * signal.fire(); // "fired"
25
+ * signal.aborted; // true
26
+ * ```
27
+ *
28
+ * Listeners are NOT replayed after firing — consumers must check
29
+ * `signal.aborted` synchronously to detect an already-fired signal.
30
+ */
31
+ export declare function createSignal(): Signal;
@@ -0,0 +1,60 @@
1
+ import type { Result } from "./result";
2
+ /** The operation itself. Receives the attempt's AbortSignal. */
3
+ export type Executor<T> = (signal: AbortSignal) => T | Promise<T> | Result<T, string> | Promise<Result<T, string>>;
4
+ /** Stop signal for `retryUntil`. */
5
+ export type RetryUntilOptions = {
6
+ /** When this fires, retrying ends — mid-attempt firings cancel the in-flight attempt. */
7
+ signal: AbortSignal;
8
+ /** Wait between attempts. No wait applies before the first attempt. */
9
+ delay?: number;
10
+ };
11
+ /** Execution policies, configured at construction time. */
12
+ export type SuperPromiseOptions = {
13
+ /** Do not execute until this signal fires (accepts SuperPromise signals and AbortSignal). */
14
+ defer?: AbortSignal;
15
+ /** Wait before initial execution; normal retries also wait this long. */
16
+ delay?: number;
17
+ /** Abort the operation after this many milliseconds. Timeouts are never retried. */
18
+ timeout?: number;
19
+ /** `n` retries after the first attempt — `n + 1` attempts total. Aborts and timeouts are never retried. */
20
+ retry?: number;
21
+ /** Keep retrying until the stop signal fires. Overrides `retry` and `delay`. */
22
+ retryUntil?: RetryUntilOptions;
23
+ };
24
+ /** A single async operation with lifecycle ergonomics. Promise-compatible. */
25
+ export type SuperPromise<T> = {
26
+ /** Lifecycle signal: fires once when the task reaches a terminal state. */
27
+ done: AbortSignal;
28
+ /** Cancels the operation; settles with `Err("Aborted")` when not yet settled. */
29
+ abort(): void;
30
+ /** Manually settles an externally controlled task with a value. First settlement wins. */
31
+ resolve(value: T): void;
32
+ /** Manually settles an externally controlled task with a failure. First settlement wins. */
33
+ reject(error: unknown): void;
34
+ /** Observes a successful value without transforming it. Callback throws settle as `Err`. */
35
+ tap(fn: (value: T) => unknown | Promise<unknown>): SuperPromise<T>;
36
+ /** Promise-compatible. The callback receives the settled `Result`; the task never rejects. */
37
+ then<TR1 = Result<T, string>, TR2 = never>(onfulfilled?: ((value: Result<T, string>) => TR1 | PromiseLike<TR1>) | null, onrejected?: ((reason: unknown) => TR2 | PromiseLike<TR2>) | null): Promise<TR1 | TR2>;
38
+ /** Promise-compatible. Effectively dead — failures are captured into `Err`, never rejections. */
39
+ catch<TR = never>(onrejected?: ((reason: unknown) => TR | PromiseLike<TR>) | null): Promise<Result<T, string> | TR>;
40
+ /** Promise-compatible. Native behavior. */
41
+ finally(onfinally?: (() => void) | null): Promise<Result<T, string>>;
42
+ };
43
+ /**
44
+ * Creates a SuperPromise — one async operation plus lifecycle/execution ergonomics.
45
+ *
46
+ * Lazy activation: the executor starts on first consumption (`await`/`.then()`,
47
+ * or a runner's `start()`), not at creation. Defer chains activate recursively,
48
+ * so awaiting a deferred task runs its dependency first.
49
+ *
50
+ * @example
51
+ * const task = superPromise(async signal => {
52
+ * const response = await fetch(url, { signal });
53
+ * return response.json();
54
+ * });
55
+ *
56
+ * const result = await task; // Result<T, string>
57
+ */
58
+ export declare function superPromise<T>(fn: Executor<T>, options?: SuperPromiseOptions): SuperPromise<T>;
59
+ /** Creates an externally controlled SuperPromise, settled via `resolve`/`reject`. */
60
+ export declare function superPromise<T>(fn?: undefined, options?: SuperPromiseOptions): SuperPromise<T>;
@@ -0,0 +1,42 @@
1
+ import type { Result } from "./result";
2
+ import type { SuperPromise } from "./super-promise";
3
+ /** Sequence result: positional tuple of the runners' Ok values. */
4
+ type TupleValues<R extends readonly SuperPromise<unknown>[]> = {
5
+ -readonly [K in keyof R]: R[K] extends SuperPromise<infer T> ? T : never;
6
+ };
7
+ /** all/race result: named object of the runners' Ok values. */
8
+ type NamedValues<R extends Record<string, SuperPromise<unknown>>> = {
9
+ [K in keyof R]: R[K] extends SuperPromise<infer T> ? T : never;
10
+ };
11
+ /** race(count: 1) result: union of single-winner objects. */
12
+ type RaceValues<C extends number, R extends Record<string, SuperPromise<unknown>>> = C extends 1 ? {
13
+ [K in keyof R]: {
14
+ [P in K]: R[K] extends SuperPromise<infer T> ? T : never;
15
+ };
16
+ }[keyof R] : Partial<NamedValues<R>>;
17
+ /** Orchestrates multiple already-created SuperPromises. */
18
+ export type SuperRunner<Value> = {
19
+ /** Lifecycle signal: fires once when the runner reaches a terminal state. */
20
+ done: AbortSignal;
21
+ /** Starts execution. Idempotent: repeated calls return the same result without re-executing children. */
22
+ start(): Promise<Result<Value, string>>;
23
+ /** Cancels the runner and all active children; settles with `Err("Aborted")`. */
24
+ abort(): void;
25
+ };
26
+ /** Runs runners in order; stops at the first failure. */
27
+ export declare function superRunner<const R extends readonly SuperPromise<unknown>[]>(config: {
28
+ type: "sequence";
29
+ runners: R;
30
+ }): SuperRunner<TupleValues<R>>;
31
+ /** Runs runners concurrently; fails fast on the first failure, aborting remaining children. */
32
+ export declare function superRunner<const R extends Record<string, SuperPromise<unknown>>>(config: {
33
+ type: "all";
34
+ runners: R;
35
+ }): SuperRunner<NamedValues<R>>;
36
+ /** Runs runners concurrently; first `count` successful completions win, remaining children are aborted. */
37
+ export declare function superRunner<const C extends number, const R extends Record<string, SuperPromise<unknown>>>(config: {
38
+ type: "race";
39
+ count: C;
40
+ runners: R;
41
+ }): SuperRunner<RaceValues<C, R>>;
42
+ export {};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "slang-ts",
3
- "version": "0.0.6",
4
- "description": "Functional programming library for TypeScript",
3
+ "version": "0.0.8",
4
+ "description": "Functional programming library for TypeScript: Option, Result, pattern matching, and async primitives",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.js",
@@ -12,13 +12,6 @@
12
12
  "files": [
13
13
  "dist"
14
14
  ],
15
- "scripts": {
16
- "test": "vitest run",
17
- "test:watch": "vitest",
18
- "test:ui": "vitest --ui",
19
- "typecheck": "tsc --noEmit",
20
- "build": "bun run test && bun run typecheck && bun run bun-build.ts && tsc -p tsconfig.build.json"
21
- },
22
15
  "homepage": "https://github.com/Hussseinkizz/slang#readme",
23
16
  "repository": {
24
17
  "type": "git",
@@ -31,14 +24,26 @@
31
24
  "functional",
32
25
  "typescript",
33
26
  "option",
27
+ "maybe",
34
28
  "result",
29
+ "safe-try",
35
30
  "pattern-matching",
31
+ "match",
36
32
  "monad",
37
- "ts-pattern",
33
+ "pipe",
34
+ "zip",
35
+ "async",
36
+ "promise",
37
+ "superpromise",
38
+ "superrunner",
39
+ "cancellation",
40
+ "abort",
41
+ "signal",
42
+ "channel",
43
+ "event-bus",
38
44
  "defensive programming",
39
45
  "fp-ts",
40
46
  "ramda",
41
- "rameda",
42
47
  "neverthrow",
43
48
  "lodash"
44
49
  ],
@@ -47,10 +52,17 @@
47
52
  "sideEffects": false,
48
53
  "devDependencies": {
49
54
  "@types/bun": "latest",
50
- "@vitest/ui": "^4.0.15",
51
- "vitest": "^4.0.15"
55
+ "@vitest/ui": "^4.1.10",
56
+ "vitest": "^4.1.10"
52
57
  },
53
58
  "peerDependencies": {
54
- "typescript": "^5"
59
+ "typescript": "^5.9.3"
60
+ },
61
+ "scripts": {
62
+ "test": "vitest run",
63
+ "test:watch": "vitest",
64
+ "test:ui": "vitest --ui",
65
+ "typecheck": "tsc --noEmit",
66
+ "build": "bun run test && bun run typecheck && bun run bun-build.ts && tsc -p tsconfig.build.json"
55
67
  }
56
- }
68
+ }