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/CHANGELOG.md +16 -3
- package/README.md +108 -100
- package/dist/index.d.ts +122 -21
- package/dist/index.js +2 -2
- package/package.json +1 -1
- package/skill/SKILL.md +342 -56
package/skill/SKILL.md
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
# slang-ts agent skill
|
|
2
2
|
|
|
3
|
-
|
|
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
|
|
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>`
|
|
28
|
+
`option<T>(value): Option<T>` wraps a value which may be missing.
|
|
20
29
|
|
|
21
|
-
`0` and `false` count as present
|
|
30
|
+
`0` and `false` count as present. `null`, `undefined`, `""`, `NaN`, `Infinity` and `-Infinity`
|
|
31
|
+
become `None`.
|
|
22
32
|
|
|
23
33
|
```ts
|
|
24
|
-
|
|
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
|
-
|
|
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
|
-
|
|
35
|
-
|
|
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
|
-
|
|
129
|
+
### combineResults / combineErrors
|
|
39
130
|
|
|
40
|
-
|
|
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
|
-
|
|
44
|
-
|
|
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
|
-
|
|
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)`
|
|
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
|
-
|
|
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)`
|
|
198
|
+
`matchAll(value, patterns)` matches strings, numbers, booleans and `Atom`s. It always needs a
|
|
199
|
+
`_` fallback.
|
|
59
200
|
|
|
60
201
|
```ts
|
|
61
|
-
|
|
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
|
-
_: (
|
|
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
|
|
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`
|
|
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>`
|
|
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
|
-
|
|
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>`
|
|
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
|
-
|
|
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`)
|
|
104
|
-
|
|
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>`
|
|
290
|
+
`sets<T>(input): SetsChain<T>` handles membership and set operations.
|
|
109
291
|
|
|
110
|
-
Methods: `has`, `unique`, `union`, `intersection`, `difference`.
|
|
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>`
|
|
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((
|
|
122
|
-
.map((
|
|
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>`
|
|
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(
|
|
139
|
-
|
|
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>`
|
|
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
|
-
|
|
151
|
-
|
|
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`
|
|
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
|
-
|
|
160
|
-
|
|
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`
|
|
413
|
+
`createSignal(): Signal` is a trigger which fires once (`fire()`). It works anywhere an
|
|
414
|
+
`AbortSignal` does.
|
|
166
415
|
|
|
167
|
-
`createChannel(): Channel`
|
|
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
|
|
172
|
-
|
|
173
|
-
-
|
|
174
|
-
|
|
175
|
-
- `
|
|
176
|
-
|
|
177
|
-
- `
|
|
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-
|
|
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;
|
|
185
|
-
- `.panic()` throws: only for unrecoverable states. Use `Result` / `.else()` for expected
|
|
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/` (
|
|
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).
|