slang-ts 0.2.2 → 0.2.3

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/skill/SKILL.md CHANGED
@@ -1,10 +1,19 @@
1
1
  # slang-ts agent skill
2
2
 
3
- This skill tells AI agents how to work with slang-ts: its APIs, patterns, and conventions so agents can write correct TypeScript against it.
3
+ Use this skill to write correct TypeScript with slang-ts: its APIs, the patterns it expects, and
4
+ the conventions to follow.
4
5
 
5
6
  ## What slang-ts is
6
7
 
7
- slang-ts is a zero-dependency functional programming library for TypeScript. It brings Rust- and Erlang-style utilities: `Option`, `Result`, `SafeTry`, pattern matching (`match`/`matchAll`), async control (`SuperPromise`, `SuperRunner`, `taskGroup`, `Signal`, `Channel`), and data helpers (`list`, `struct`, `sets`, `Ok.iterate`, `memoized`, `zip`, `pipe`, `atom`, `to`).
8
+ slang-ts is a zero-dependency functional programming library for TypeScript, with utilities
9
+ brought over from Rust and Erlang:
10
+
11
+ - Failures as values: `Result` (`Ok`, `Err`), `Option`, `safeTry`, `combineResults`, `combineErrors`, `match`, `matchAll`, `Options`, `pipe`, `panic`
12
+ - Data helpers: `list`, `struct`, `sets`, `Ok(value).iterate()`, `memoized`, `zip`, `zipWith`, `unzip`
13
+ - Conversion: `atom`, `.to()` on every wrapper
14
+ - Async work: `superPromise`, `superRunner`, `taskGroup`, `createSignal`, `createChannel`
15
+ - Step history: `createStepper`
16
+ - Output: `println`, `setEnvironment`
8
17
 
9
18
  Import only what you use:
10
19
 
@@ -16,60 +25,210 @@ import { option, Ok, Err, match, superPromise, list } from "slang-ts";
16
25
 
17
26
  ### Option
18
27
 
19
- `option<T>(value): Option<T>` — wraps a value that may or may not be there.
28
+ `option<T>(value): Option<T>` wraps a value which may be missing.
20
29
 
21
- `0` and `false` count as present; `null`, `undefined`, `""`, `NaN`, and `Infinity` are `None`.
30
+ `0` and `false` count as present. `null`, `undefined`, `""`, `NaN`, `Infinity` and `-Infinity`
31
+ become `None`.
22
32
 
23
33
  ```ts
24
- const port = option(process.env.PORT).unwrap().else(3000);
34
+ import { option, println } from "slang-ts";
35
+
36
+ const savedPort = new URLSearchParams("port=8080").get("port");
37
+
38
+ const port = option(savedPort).unwrap().else("3000");
39
+
40
+ println("Port:", port); // "8080"
25
41
  ```
26
42
 
27
- Never read `.value` on `None`. Check `.isSome` / `.isNone` first, or use `.unwrap().else(fallback)`.
43
+ - Check `.isSome` / `.isNone` before reading `.value`, or use `.unwrap().else(fallback)`.
44
+ - `.unwrap()` must be finished with `.else(...)`. `.else` throws if the fallback is a value
45
+ `option()` treats as missing; `0` and `false` are valid fallbacks.
46
+ - `.expect(message?)` returns the value or throws on `None`.
28
47
 
29
48
  ### Result
30
49
 
31
50
  `Result<T, E>` is `Ok<T>` or `Err<E>`.
32
51
 
33
52
  ```ts
34
- Ok<T>(value, message?); // success, optional message
35
- Err<E>(error, raw?); // failure, optional diagnostic cause
53
+ // Signature
54
+ Ok<T>(value, message?); // success, with an optional message for the user
55
+ Err(error, raw?); // failure: a human-readable error, plus an optional cause for logs
56
+ ```
57
+
58
+ `Err(error, raw?)` keeps the original cause on the non-enumerable `.raw` for logs; `.error`
59
+ stays human-readable.
60
+
61
+ ```ts
62
+ import { Ok, Err, type Result, println } from "slang-ts";
63
+
64
+ function splitBill(total: number, people: number): Result<number, string> {
65
+ if (people === 0) {
66
+ return Err("Add at least one person to split the bill");
67
+ }
68
+
69
+ return Ok(total / people);
70
+ }
71
+
72
+ const share = splitBill(120, 4);
73
+
74
+ if (share.isOk) {
75
+ println("Each person pays:", share.value); // 30
76
+ } else {
77
+ println("Could not split the bill:", share.error);
78
+ }
79
+ ```
80
+
81
+ - Branch with `if (result.isOk) { ... } else { ... }`.
82
+ - Get the value with `.unwrap().else(fallback)`, or `.expect(message?)` when a failure should
83
+ throw.
84
+ - `.andThen(fn)` transforms an `Ok` and skips an `Err`. A plain return becomes `Ok`, a
85
+ returned `Result` passes through, `undefined` keeps the original, and a throw or rejection
86
+ becomes `Err(message)`.
87
+ - `.mapErr(fn)` rewrites an `Err`'s message (`fn` receives the message, returns a string) and
88
+ keeps the original `.raw`. Use it at a route or page edge to turn a technical message into
89
+ a human one. Sync only. On `Ok` it returns the same `Ok`.
90
+ - `.orFn(fn)` recovers from an `Err`: `fn(error, raw)` follows the same rules as `andThen`
91
+ (plain value becomes `Ok`, a `Result` passes through, `undefined` keeps the original `Err`,
92
+ a throw or rejection becomes `Err(message)`). On `Ok` it returns the same `Ok`. It is not
93
+ `unwrap().else()`, which leaves the `Result` and returns a plain value.
94
+ - With a sync callback, `andThen` and `orFn` return a `Result`; read it without `await`. With
95
+ an async callback, or one typed as either a value or a promise, or `unknown`, they return
96
+ `Result | Promise<Result>`: `await` it. The skipped side never calls the callback and
97
+ returns the same instance synchronously, so the async case stays a union.
98
+ - A `Result` never nests inside another `Result`: `Ok(Ok(x))` and `Ok(Err(x))` do not compile.
99
+
100
+ ### safeTry
101
+
102
+ `safeTry(fn, options?)` runs a sync or async function and turns every failure (a throw, a
103
+ returned `Err`, a returned `None`) into one `Err`. Sync in, sync out:
104
+
105
+ - A function returning a plain value gets its `Result` directly; read it without `await`.
106
+ - A function returning a promise (or any thenable) gets `Promise<Result>`. `await` it.
107
+ - A function typed as either a value or a promise, or `unknown`, gets the union. `await` it.
108
+ - A sync result is a plain `Result`: `await` on it works, `.then()` does not exist.
109
+ - `{ throw: true }`: a sync throw throws from the call; an async throw rejects the promise.
110
+
111
+ ```ts
112
+ import { safeTry, println } from "slang-ts";
113
+
114
+ type Settings = {
115
+ theme: string;
116
+ };
117
+
118
+ const settings = safeTry(() => {
119
+ return JSON.parse('{"theme":"dark"}') as Settings;
120
+ });
121
+
122
+ if (settings.isOk) {
123
+ println("Theme:", settings.value.theme); // "dark"
124
+ } else {
125
+ println("Could not read the settings:", settings.error);
126
+ }
36
127
  ```
37
128
 
38
- `Err(error, raw?)` keeps the original cause on the non-enumerable `.raw` for logs; `.error` stays human-readable.
129
+ ### combineResults / combineErrors
39
130
 
40
- Branch with `if (result.isOk) { ... } else { ... }`. Finish chains with `.unwrap().else(fallback)` or `.unwrap().panic(message?)`.
131
+ `combineResults(results)` returns `Ok([...values])` typed as a tuple when every result is `Ok`,
132
+ or the first `Err` as the same instance (`.raw` kept). An empty array gives `Ok([])`.
133
+ `combineErrors(results)` returns every error message in input order, `[]` when all are `Ok`.
41
134
 
42
135
  ```ts
43
- function divide(a: number, b: number): Result<number, string> {
44
- return b === 0 ? Err("Cannot divide by zero") : Ok(a / b);
136
+ import { Ok, Err, combineResults, combineErrors, type Result, println } from "slang-ts";
137
+
138
+ function validateEmail(input: string): Result<string, string> {
139
+ if (!input.includes("@")) {
140
+ return Err("Enter a valid email address");
141
+ }
142
+
143
+ return Ok(input.toLowerCase());
144
+ }
145
+
146
+ function validateAge(input: string): Result<number, string> {
147
+ const age = Number(input);
148
+
149
+ if (!Number.isInteger(age) || age < 13) {
150
+ return Err("You must be at least 13 to sign up");
151
+ }
152
+
153
+ return Ok(age);
154
+ }
155
+
156
+ const fields: [Result<string, string>, Result<number, string>] = [
157
+ validateEmail("ada@example.com"),
158
+ validateAge("29"),
159
+ ];
160
+
161
+ const problems = combineErrors(fields);
162
+
163
+ if (problems.length > 0) {
164
+ println("Fix these fields:", problems);
165
+ } else {
166
+ const values = combineResults(fields);
167
+
168
+ if (values.isOk) {
169
+ const [email, age] = values.value; // typed [string, number]
170
+
171
+ println(email, age); // "ada@example.com" 29
172
+ }
45
173
  }
46
174
  ```
47
175
 
48
- `Result` never nests inside another `Result` — `Ok(Ok(x))` and `Ok(Err(x))` won't compile.
176
+ - Arrays of results only, and sync. `await` promises first; run async tasks with
177
+ `superRunner`; chain dependent steps with `pipe`.
178
+ - Pass an array literal or a tuple-typed value to keep the tuple type.
49
179
 
50
180
  ### match / matchAll
51
181
 
52
- `match(value, patterns)` — exhaustive pattern matching for `Option` and `Result`. TypeScript requires both cases.
182
+ `match(value, patterns)` handles every case of an `Option` or `Result`. TypeScript requires both
183
+ cases. Each handler receives the wrapper itself.
53
184
 
54
185
  ```ts
55
- match(result, { Ok: (v) => ..., Err: (e) => ... });
186
+ import { Ok, match, println } from "slang-ts";
187
+
188
+ const saved = Ok({ id: "u_1", name: "Ada" });
189
+
190
+ const greeting = match(saved, {
191
+ Ok: (user) => `Welcome back, ${user.value.name}`,
192
+ Err: (failed) => `Could not load your profile: ${failed.error}`,
193
+ });
194
+
195
+ println(greeting); // "Welcome back, Ada"
56
196
  ```
57
197
 
58
- `matchAll(value, patterns)` — matching for strings, numbers, booleans, symbols, and `Atom`s. Always needs a `_` fallback.
198
+ `matchAll(value, patterns)` matches strings, numbers, booleans and `Atom`s. It always needs a
199
+ `_` fallback.
59
200
 
60
201
  ```ts
61
- const message = matchAll(failed.category, {
202
+ import { list, matchAll, println } from "slang-ts";
203
+
204
+ const failed = list(["a", "b", "c"]).take(5);
205
+
206
+ const message = matchAll(String(failed.category), {
62
207
  OUT_OF_BOUNDS: () => "That page is out of range.",
63
- _: (category) => `Unexpected failure: ${category}`,
208
+ _: () => "Something went wrong.",
64
209
  });
210
+
211
+ println(message); // "That page is out of range."
65
212
  ```
66
213
 
67
214
  ## Collection chains (list, struct, sets, Ok.iterate)
68
215
 
69
- These return a `Fallible<T>`, not a plain `Result`. Finish with `.else(fallback)` or `.panic(message?)`. On failure, `.category` holds a string like `"OUT_OF_BOUNDS"` or `"INVALID_INPUT"`.
216
+ These return a `Fallible<T>`, not a `Result`. Finish with `.else(fallback)` or
217
+ `.panic(message?)`. On failure, `.category` holds a string such as `"OUT_OF_BOUNDS"` or
218
+ `"INVALID_INPUT"`.
219
+
220
+ - `list`, `struct` and `sets` run each method at once and return a new chain. `Ok.iterate` is
221
+ lazy: steps run only when `toArray`, `reduce` or `count` is called.
222
+ - Nothing throws mid-chain. The first failure is kept and later steps are skipped.
223
+ - Inputs are never changed; each step returns new arrays, objects or sets.
224
+ - `list`, `struct` and `sets` accept a raw value or a successful `Ok` holding one; an `Err`
225
+ fails with `INVALID_INPUT`.
70
226
 
71
227
  ```ts
228
+ import { list, println } from "slang-ts";
229
+
72
230
  const failed = list([1, 2, 3]).range(1, 99).take(1);
231
+
73
232
  if (failed.category === "OUT_OF_BOUNDS") {
74
233
  println(failed.else([])); // []
75
234
  } else {
@@ -77,114 +236,241 @@ if (failed.category === "OUT_OF_BOUNDS") {
77
236
  }
78
237
  ```
79
238
 
80
- - `get` already takes a fallback: `get(key, fallback)`. Do not pair `get(x, fallback)` with `.else(fallback)` — that's redundant.
239
+ - `get` takes a fallback: `get(key, fallback)`. Finish it with `.panic()`, or use
240
+ `get(key).else(fallback)`. Never pair `get(key, fallback)` with `.else(fallback)`.
81
241
  - `.panic()` throws; once you panic, no custom message is read afterward.
82
242
 
83
243
  ### list
84
244
 
85
- `list<T>(input): ListChain<T>` — strict indexed arrays with explicit bounds. Out-of-range fails instead of clamping.
245
+ `list<T>(input): ListChain<T>` reads and slices an array with strict bounds. Every index,
246
+ count and bound must be an integer inside the array; negative indices count from the end. An
247
+ out-of-range request fails with `OUT_OF_BOUNDS` instead of returning fewer items.
86
248
 
87
249
  ```ts
88
- const page = list(users).range(start, end).else([]);
250
+ import { list, println } from "slang-ts";
251
+
252
+ const users = ["Ada", "Grace", "Linus"];
253
+
254
+ const page = list(users).range(0, 2).else([]);
255
+
256
+ println(page); // ["Ada","Grace"]
89
257
  ```
90
258
 
91
259
  Methods: `get`, `take`, `skip`, `range`, `chunk`, `count`, `prune`.
92
260
 
93
261
  ### struct
94
262
 
95
- `struct<T>(input): StructChain<T>` — safe record access and shaping.
263
+ `struct<T>(input): StructChain<T>` reads paths through a record and shapes it. `get` reads own
264
+ properties only: an absent key fails, a key holding `undefined` succeeds. `merge` deep merges
265
+ plain objects; arrays and other values in the patch replace the old value.
96
266
 
97
- Methods: `get`, `pick`, `omit`, `merge`, `countBy`, `keyBy`, `prune`.
267
+ Methods: `get`, `pick`, `omit`, `merge`, `count`, `countBy`, `keyBy`, `prune`.
98
268
 
99
269
  ```ts
100
- const port = struct(config).get(["server", "port"], 3000);
270
+ import { struct, println } from "slang-ts";
271
+
272
+ const config = {
273
+ server: { host: "localhost" },
274
+ };
275
+
276
+ const port = struct(config).get(["server", "port"], 3000).panic();
277
+
278
+ println("Port:", port); // 3000
101
279
  ```
102
280
 
103
- - `prune` drops Slang-falsy values (`null`, `undefined`, `""`, `NaN`, `Infinity`, `-Infinity`) but keeps `0` and `false`. Its predicate receives `(value, key)` and returns `true` to keep a value. It needs a plain object.
104
- - Wrong shape for the operation fails with `INVALID_INPUT`. A missing `get` path fails with `OUT_OF_BOUNDS`. Once a chain fails, later steps don't run, and the category survives so you can check `.category`.
281
+ - `prune` drops Slang-falsy values (`null`, `undefined`, `""`, `NaN`, `Infinity`, `-Infinity`)
282
+ and keeps `0` and `false`. Its predicate receives `(value, key)` and returns `true` to keep a
283
+ value. It needs a plain object.
284
+ - The wrong shape for an operation fails with `INVALID_INPUT`. A missing `get` path fails with
285
+ `OUT_OF_BOUNDS`. Once a chain fails, later steps are skipped and the category stays on
286
+ `.category`.
105
287
 
106
288
  ### sets
107
289
 
108
- `sets<T>(input): SetsChain<T>` — membership and set operations.
290
+ `sets<T>(input): SetsChain<T>` handles membership and set operations.
109
291
 
110
- Methods: `has`, `unique`, `union`, `intersection`, `difference`. Objects compare by identity; primitives by value.
292
+ Methods: `has`, `unique`, `union`, `intersection`, `difference`, `count`. An array, string or
293
+ iterable gives its values, a `Map` its keys, a plain object its own keys. Objects compare by
294
+ identity; primitives by value. The other side of an operation accepts the same inputs.
111
295
 
112
296
  ### Ok.iterate
113
297
 
114
- `Ok<T>(value).iterate(): IterateChain<T>` — lazy pipelines over a successful `Ok`.
298
+ `Ok<T>(value).iterate(): IterateChain<T>` builds a lazy pipeline over the iterable in a
299
+ successful `Ok`. Values pass through every step one at a time, so `take(n)` stops reading the
300
+ source early. A throw in a callback or the source becomes a failure with `INVALID_INPUT`. A
301
+ single-use source, such as a generator, can be finished once.
115
302
 
116
303
  Methods: `map`, `filter`, `flatMap`, `take`, `drop`, `chunk`, `reduce`, `count`, `toArray`.
117
304
 
118
305
  ```ts
306
+ import { Ok, println } from "slang-ts";
307
+
308
+ type User = {
309
+ name: string;
310
+ active: boolean;
311
+ };
312
+
313
+ const users: User[] = [
314
+ { name: "Ada", active: true },
315
+ { name: "Grace", active: false },
316
+ ];
317
+
119
318
  const names = Ok(users)
120
319
  .iterate()
121
- .filter((u) => u.active)
122
- .map((u) => u.name)
320
+ .filter((user) => user.active)
321
+ .map((user) => user.name)
123
322
  .toArray()
124
323
  .else([]);
324
+
325
+ println(names); // ["Ada"]
125
326
  ```
126
327
 
127
328
  ## Async
128
329
 
129
330
  ### SuperPromise
130
331
 
131
- `superPromise<T>(fn, options?): SuperPromise<T>` — one async operation with start, retry, timeout, and cancel controls. Your function gets an `AbortSignal`. Tasks end as `Ok` or `Err`.
332
+ `superPromise<T>(fn, options?): SuperPromise<T>` is a native promise plus start, retry, timeout
333
+ and cancel controls. Your function receives an `AbortSignal`. Awaiting it gives `Ok` or `Err`;
334
+ it never rejects.
132
335
 
133
336
  Options: `defer`, `delay`, `timeout`, `retry`, `retryUntil`, `group`.
134
337
 
135
338
  ```ts
339
+ import { superPromise, println } from "slang-ts";
340
+
341
+ type User = {
342
+ id: string;
343
+ name: string;
344
+ };
345
+
136
346
  const user = await superPromise(
137
- async (signal) => {
138
- const response = await fetch(`/api/users/${id}`, { signal });
139
- return response.json();
347
+ async (signal: AbortSignal): Promise<User> => {
348
+ const response = await fetch("https://example.com/api/users/u_1", { signal });
349
+
350
+ if (!response.ok) {
351
+ throw new Error("Could not load the user");
352
+ }
353
+
354
+ return (await response.json()) as User;
140
355
  },
141
- { timeout: 5000, retry: 2 },
356
+ { timeout: 5000, retry: 2 }, // 5 seconds per attempt, up to 2 retries
142
357
  );
358
+
359
+ if (user.isOk) {
360
+ println("User:", user.value.name);
361
+ } else {
362
+ println("Could not load the user:", user.error);
363
+ }
143
364
  ```
144
365
 
145
366
  ### SuperRunner
146
367
 
147
- `superRunner(config): SuperRunner<Value>` — manages several already-created `SuperPromise`s: sequence, parallel, or race. `start()` runs once.
368
+ `superRunner(config): SuperRunner<Value>` runs several already-created `SuperPromise`s in
369
+ sequence, in parallel (`all`), or as a race (`race`, with a `count`). `start()` runs once; later
370
+ calls return the same result.
148
371
 
149
372
  ```ts
150
- const all = superRunner({ type: "all", runners: { a, b } });
151
- const result = await all.start();
373
+ import { superPromise, superRunner, println } from "slang-ts";
374
+
375
+ const profile = superPromise(async (): Promise<string> => {
376
+ return "Ada";
377
+ });
378
+
379
+ const orders = superPromise(async (): Promise<number> => {
380
+ return 3;
381
+ });
382
+
383
+ const dashboard = superRunner({ type: "all", runners: { profile, orders } });
384
+
385
+ const loaded = await dashboard.start();
386
+
387
+ if (loaded.isOk) {
388
+ println(loaded.value); // {"profile":"Ada","orders":3}
389
+ }
152
390
  ```
153
391
 
154
392
  ### taskGroup
155
393
 
156
- `taskGroup(name, options?): TaskGroup` — shared concurrency limit and start spacing across tasks and runners. Retries wait for a new slot.
394
+ `taskGroup(name, options?): TaskGroup` shares a concurrency limit and start spacing across tasks
395
+ and runners. A retry waits for a new slot.
157
396
 
158
397
  ```ts
159
- const api = taskGroup("user-api", { concurrency: 3, throttle: 200 });
160
- const task = superPromise(fetchUsers, { group: api });
398
+ import { taskGroup, superPromise, println } from "slang-ts";
399
+
400
+ const api = taskGroup("user-api", { concurrency: 3, throttle: 200 }); // starts at least 200 ms apart
401
+
402
+ const task = superPromise(async (): Promise<string> => {
403
+ return "Ada";
404
+ }, { group: api });
405
+
406
+ const user = await task;
407
+
408
+ println(user.isOk); // true
161
409
  ```
162
410
 
163
411
  ### Signal / Channel
164
412
 
165
- `createSignal(): Signal` — a trigger that fires once (`fire()`), AbortSignal-compatible.
413
+ `createSignal(): Signal` is a trigger which fires once (`fire()`). It works anywhere an
414
+ `AbortSignal` does.
166
415
 
167
- `createChannel(): Channel` — in-process publish/subscribe. `subscribe(topic, handler)` returns an unsubscribe function.
416
+ `createChannel(): Channel` is in-process publish/subscribe. `subscribe({ topic }, handler)`
417
+ returns an unsubscribe function, `send({ topic, data })` runs every subscriber of the topic, and
418
+ `when({ signal, topic, data })` sends once the signal fires.
419
+
420
+ ```ts
421
+ import { createChannel, createSignal, println } from "slang-ts";
422
+
423
+ const events = createChannel();
424
+
425
+ const unsubscribe = events.subscribe({ topic: "order.placed" }, (event) => {
426
+ println("Order placed:", event.data); // Order placed: {"orderId":"o_42"}
427
+ });
428
+
429
+ events.send({ topic: "order.placed", data: { orderId: "o_42" } });
430
+
431
+ const shutdown = createSignal();
432
+
433
+ events.when({ signal: shutdown, topic: "order.placed", data: { orderId: "o_43" } });
434
+
435
+ shutdown.fire(); // Order placed: {"orderId":"o_43"}
436
+
437
+ unsubscribe();
438
+ ```
168
439
 
169
440
  ## Other utilities
170
441
 
171
- - `atom<T>(name): Atom<T>` — unique symbol with a readable description; matches in `matchAll` by description.
172
- - `to(target)` — convert between `Option`, `Atom`, `Result` (e.g. `option("ready").to("atom")`).
173
- - `pipe(value, ...fns)` — run `Result`-returning steps in order, stopping at the first failure.
174
- - `zip` / `zipWith` / `unzip` — combine parallel collections.
175
- - `memoized(fn)` — cache a function by argument identity.
176
- - `panic(message)` — throw immediately for unrecoverable states.
177
- - `println` — dev logging, silent in production unless `setEnvironment` routes it.
442
+ - `atom<T>(name): Atom<T>`: a unique symbol with a readable description; `matchAll` matches it
443
+ by description.
444
+ - `.to(target)`: converts between `Option`, `Atom` and `Result`, such as
445
+ `option("ready").to("atom")`. `_to(value, target)` is the same conversion as a function.
446
+ - `Options(values, fallback?)`: the first usable value from a mix of raw values, `Option`s and
447
+ `Result`s, as `Ok`, or the fallback (`Err("No value!")` by default).
448
+ - `pipe(initial, ...fns).run(options?)`: runs `Result` steps in order and stops at the first
449
+ `Err`. Each function receives the previous `Result`; `run` returns a promise.
450
+ - `zip(inputs, options?)` / `zipWith` / `unzip`: rows of items at the same position across arrays.
451
+ Stops at the shortest input unless `fillValue` is set. Returns plain arrays, not a `Result`,
452
+ and throws on a non-array input unless `includeValues: true`.
453
+ - `memoized(fn)`: stores results per argument list (primitives by value, objects by identity).
454
+ An async result is stored as its promise, so concurrent callers share one call; a rejected
455
+ promise is removed. A throw stores nothing. No size limit or expiry; `.clear()` empties it.
456
+ - `createStepper(config?)`: a history of named steps with `next`, `prev`, `current` and `size`.
457
+ - `panic(message)`: throws at once, for states the program cannot recover from.
458
+ - `println`: development output, silent in production unless `setEnvironment({ printFn })`
459
+ routes it. Plain objects, arrays, sets and maps print as JSON.
460
+ - `isFalsy(value)`: `true` for the values `option()` treats as `None`.
178
461
 
179
462
  ## Conventions agents should follow
180
463
 
181
- - Named params in an object for multi-arg functions, not positional.
464
+ - Named params in an object for multi-argument functions, not positional.
182
465
  - No classes: use functions and plain objects.
183
466
  - Error categories are strings (`.category`), not numeric codes. Never call them "error codes."
184
- - `get` accepts a fallback; don't chain `.else()` after a `get` that already has one.
185
- - `.panic()` throws: only for unrecoverable states. Use `Result` / `.else()` for expected failures.
467
+ - `get` accepts a fallback; finish it with `.panic()`, not a second `.else()` fallback.
468
+ - `.panic()` throws: only for unrecoverable states. Use `Result` / `.else()` for expected
469
+ failures.
470
+ - Write `if` / `else` with braces; avoid ternaries and `&&` for control flow in examples.
186
471
  - Zero runtime dependencies; ESM only.
187
472
 
188
473
  ## Package contents
189
474
 
190
- The published package ships `dist/` (JS + bundled declarations), `CHANGELOG.md`, and `skill/SKILL.md` (this file, for agents working with slang-ts).
475
+ The published package ships `dist/` (JavaScript and bundled declarations), `CHANGELOG.md`, and
476
+ `skill/SKILL.md` (this file, for agents working with slang-ts).