nalloc 0.2.0 → 0.2.2
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 +61 -28
- package/package.json +17 -9
package/README.md
CHANGED
|
@@ -44,7 +44,7 @@ npm install nalloc
|
|
|
44
44
|
## Quick start
|
|
45
45
|
|
|
46
46
|
```ts
|
|
47
|
-
import { Option, Result, ok, err, none,
|
|
47
|
+
import { Option, Result, ok, err, none, pipe, gen } from 'nalloc';
|
|
48
48
|
|
|
49
49
|
// Option: the value IS the Option
|
|
50
50
|
const port = Option.fromNullable(process.env.PORT);
|
|
@@ -58,12 +58,19 @@ const data = Result.match(
|
|
|
58
58
|
error => ({ fallback: true })
|
|
59
59
|
);
|
|
60
60
|
|
|
61
|
-
//
|
|
62
|
-
const result =
|
|
63
|
-
const a =
|
|
64
|
-
const b =
|
|
61
|
+
// gen: Rust-like ? operator with type-safe errors
|
|
62
|
+
const result = gen(function*($) {
|
|
63
|
+
const a = yield* $(parseNumber('10'));
|
|
64
|
+
const b = yield* $(parseNumber('5'));
|
|
65
65
|
return a + b;
|
|
66
|
-
}); //
|
|
66
|
+
}); // Result<number, ParseError>
|
|
67
|
+
|
|
68
|
+
// pipe: left-to-right composition
|
|
69
|
+
const userId = pipe(
|
|
70
|
+
Result.tryCatch(() => JSON.parse(raw)),
|
|
71
|
+
r => Result.map(r, data => data.userId),
|
|
72
|
+
r => Result.unwrapOr(r, 0),
|
|
73
|
+
);
|
|
67
74
|
```
|
|
68
75
|
|
|
69
76
|
## How it works
|
|
@@ -103,8 +110,8 @@ import * as O from 'fp-ts/Option';
|
|
|
103
110
|
pipe(O.some(42), O.map(x => x * 2));
|
|
104
111
|
|
|
105
112
|
// nalloc - simpler, faster
|
|
106
|
-
import { Option } from 'nalloc';
|
|
107
|
-
Option.map(
|
|
113
|
+
import { Option, pipe } from 'nalloc';
|
|
114
|
+
pipe(42, v => Option.map(v, x => x * 2)); // built-in pipe, value IS the Option
|
|
108
115
|
```
|
|
109
116
|
|
|
110
117
|
### From oxide.ts
|
|
@@ -146,7 +153,7 @@ Option.unwrapOr(42, 0); // value IS the Option
|
|
|
146
153
|
The API mirrors Rust's `Option` and `Result`:
|
|
147
154
|
|
|
148
155
|
```ts
|
|
149
|
-
import { Option, Result, ok, err, none,
|
|
156
|
+
import { Option, Result, ok, err, none, gen } from 'nalloc';
|
|
150
157
|
|
|
151
158
|
// Rust: Some(42).map(|x| x * 2)
|
|
152
159
|
Option.map(42, x => x * 2);
|
|
@@ -157,9 +164,9 @@ Result.andThen(ok(42), x => x > 0 ? ok(x) : err('negative'));
|
|
|
157
164
|
// Rust: result.unwrap_or(0)
|
|
158
165
|
Result.unwrapOr(result, 0);
|
|
159
166
|
|
|
160
|
-
// Rust: let value =
|
|
161
|
-
const value =
|
|
162
|
-
const a =
|
|
167
|
+
// Rust: let value = get_value()?
|
|
168
|
+
const value = gen(function*($) {
|
|
169
|
+
const a = yield* $(getValue());
|
|
163
170
|
return a + 1;
|
|
164
171
|
});
|
|
165
172
|
```
|
|
@@ -205,7 +212,7 @@ const maybeUser = await Option.fromPromise(fetchUserById(id));
|
|
|
205
212
|
A `Result<T, E>` is either `Ok<T>` (the value itself) or `Err<E>` (a wrapper).
|
|
206
213
|
|
|
207
214
|
```ts
|
|
208
|
-
import { Result, ok, err,
|
|
215
|
+
import { Result, ok, err, gen, genAsync, pipe } from 'nalloc';
|
|
209
216
|
|
|
210
217
|
// Wrap throwing functions
|
|
211
218
|
const parsed = Result.tryCatch(
|
|
@@ -213,24 +220,35 @@ const parsed = Result.tryCatch(
|
|
|
213
220
|
e => 'invalid json'
|
|
214
221
|
);
|
|
215
222
|
|
|
216
|
-
//
|
|
217
|
-
const result =
|
|
218
|
-
const config =
|
|
219
|
-
const db =
|
|
223
|
+
// gen: Rust-like ? operator with preserved error types
|
|
224
|
+
const result = gen(function*($) {
|
|
225
|
+
const config = yield* $(parseConfig(raw));
|
|
226
|
+
const db = yield* $(connectDb(config.url));
|
|
220
227
|
return db.query('SELECT 1');
|
|
221
|
-
});
|
|
228
|
+
}); // Result<QueryResult, ConfigError | DbError>
|
|
222
229
|
|
|
223
|
-
// Async
|
|
224
|
-
const data = await
|
|
225
|
-
const res =
|
|
226
|
-
const posts =
|
|
230
|
+
// Async gen
|
|
231
|
+
const data = await genAsync(async function*($) {
|
|
232
|
+
const res = yield* $(await Result.fromPromise(fetchUser(id)));
|
|
233
|
+
const posts = yield* $(await Result.fromPromise(fetchPosts(res.id)));
|
|
227
234
|
return { user: res, posts };
|
|
228
|
-
});
|
|
235
|
+
}); // Promise<Result<{user, posts}, unknown>>
|
|
229
236
|
|
|
230
|
-
//
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
237
|
+
// wrap / toThrowable: ecosystem boundaries
|
|
238
|
+
const safeParse = Result.wrap(JSON.parse); // throwing -> Result
|
|
239
|
+
safeParse('{"a":1}'); // Ok({a: 1})
|
|
240
|
+
const throwingFind = Result.toThrowable(findUser); // Result -> throwing
|
|
241
|
+
throwingFind('123'); // returns User or throws
|
|
242
|
+
|
|
243
|
+
// Standard Schema validation (Zod, Valibot, ArkType, etc.)
|
|
244
|
+
const validated = Result.fromSchema(userSchema, input);
|
|
245
|
+
// Result<User, readonly SchemaIssue[]>
|
|
246
|
+
|
|
247
|
+
// Transform with pipe
|
|
248
|
+
const userId = pipe(
|
|
249
|
+
parsed,
|
|
250
|
+
r => Result.map(r, data => data.userId),
|
|
251
|
+
r => Result.flatMap(r, id => id > 0 ? ok(id) : err('invalid id')),
|
|
234
252
|
);
|
|
235
253
|
|
|
236
254
|
// Pattern match
|
|
@@ -289,6 +307,7 @@ Iter.tryForEach(items, item => processItem(item));
|
|
|
289
307
|
| `flatMap(opt, fn)` | Chain Option-returning functions |
|
|
290
308
|
| `andThen(opt, fn)` | Alias for flatMap |
|
|
291
309
|
| `tap(opt, fn)` | Side effect on Some, return original |
|
|
310
|
+
| `tapNone(opt, fn)` | Side effect on None, return original |
|
|
292
311
|
| `filter(opt, predicate)` | Keep Some if predicate passes |
|
|
293
312
|
| `match(opt, onSome, onNone)` | Pattern match |
|
|
294
313
|
| `unwrap(opt)` | Extract or throw |
|
|
@@ -327,6 +346,11 @@ Iter.tryForEach(items, item => processItem(item));
|
|
|
327
346
|
| `tryCatch(fn, onError?)` | Wrap throwing function |
|
|
328
347
|
| `tryCatchMaybePromise(fn, onError?)` | Wrap sync-or-async, preserving sync |
|
|
329
348
|
| `of(fn)` | Alias for tryCatch (no error mapper) |
|
|
349
|
+
| `wrap(fn, onError?)` | Wrap throwing function once, reuse |
|
|
350
|
+
| `toThrowable(fn)` | Inverse of wrap: Result-returning to throwing |
|
|
351
|
+
| `fromSchema(schema, value)` | Validate via Standard Schema v1 |
|
|
352
|
+
| `gen(fn)` | Generator do-notation with typed errors |
|
|
353
|
+
| `genAsync(fn)` | Async generator do-notation |
|
|
330
354
|
| `safeTry(fn)` | Imperative error handling with unwrap |
|
|
331
355
|
| `safeTryAsync(fn)` | Async version of safeTry |
|
|
332
356
|
| `unwrap(result)` | Extract Ok or throw error value |
|
|
@@ -382,6 +406,12 @@ Iter.tryForEach(items, item => processItem(item));
|
|
|
382
406
|
| `tryFold(source, init, fn)` | Fold with early exit on Err |
|
|
383
407
|
| `tryForEach(source, fn)` | Iterate with early exit on Err |
|
|
384
408
|
|
|
409
|
+
### Utilities
|
|
410
|
+
|
|
411
|
+
| Function | Description |
|
|
412
|
+
|----------|-------------|
|
|
413
|
+
| `pipe(value, ...fns)` | Thread value through functions left-to-right |
|
|
414
|
+
|
|
385
415
|
## Comparison
|
|
386
416
|
|
|
387
417
|
| Feature | nalloc | neverthrow | fp-ts | oxide.ts | ts-results |
|
|
@@ -393,7 +423,10 @@ Iter.tryForEach(items, item => processItem(item));
|
|
|
393
423
|
| Async support | Yes | Yes | Yes | Limited | No |
|
|
394
424
|
| Tree-shakeable | Yes | Yes | Yes | Yes | Yes |
|
|
395
425
|
| Iterator utilities | Yes | No | Yes | No | No |
|
|
396
|
-
|
|
|
426
|
+
| gen (? operator) | Yes | Yes | No | No | No |
|
|
427
|
+
| pipe | Yes | No | Yes | No | No |
|
|
428
|
+
| Schema validation | Yes | No | No | No | No |
|
|
429
|
+
| Ecosystem interop | Yes | No | No | No | No |
|
|
397
430
|
|
|
398
431
|
## Alternatives
|
|
399
432
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nalloc",
|
|
3
3
|
"description": "Rust-like Option and Result for TypeScript with near-zero allocations for extreme performance",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.2",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
7
7
|
"main": "build/index.cjs",
|
|
@@ -60,22 +60,30 @@
|
|
|
60
60
|
"type-safe",
|
|
61
61
|
"monad",
|
|
62
62
|
"functional",
|
|
63
|
-
"fp",
|
|
64
|
-
"adt",
|
|
65
63
|
"algebraic-data-types",
|
|
66
64
|
"discriminated-union",
|
|
65
|
+
"sum-type",
|
|
67
66
|
"error-handling",
|
|
68
|
-
"try-catch",
|
|
69
|
-
"safe",
|
|
70
|
-
"nullable",
|
|
71
67
|
"null-safety",
|
|
72
68
|
"pattern-matching",
|
|
69
|
+
"do-notation",
|
|
70
|
+
"generator",
|
|
71
|
+
"pipe",
|
|
72
|
+
"iterator",
|
|
73
|
+
"railway-oriented-programming",
|
|
74
|
+
"schema-validation",
|
|
73
75
|
"zero-allocation",
|
|
74
|
-
"
|
|
76
|
+
"zero-cost",
|
|
75
77
|
"gc-friendly",
|
|
76
78
|
"performance",
|
|
79
|
+
"lightweight",
|
|
80
|
+
"tree-shakeable",
|
|
77
81
|
"neverthrow-alternative",
|
|
78
|
-
"fp-ts-alternative"
|
|
82
|
+
"fp-ts-alternative",
|
|
83
|
+
"effect-alternative",
|
|
84
|
+
"oxide-ts-alternative",
|
|
85
|
+
"ts-results-alternative",
|
|
86
|
+
"true-myth-alternative"
|
|
79
87
|
],
|
|
80
88
|
"author": "Ivan Zakharchanka",
|
|
81
89
|
"license": "MIT",
|
|
@@ -86,7 +94,7 @@
|
|
|
86
94
|
"devDependencies": {
|
|
87
95
|
"@eslint/js": "^10.0.1",
|
|
88
96
|
"@vitest/coverage-v8": "^4.1.2",
|
|
89
|
-
"eslint": "^10.
|
|
97
|
+
"eslint": "^10.2.0",
|
|
90
98
|
"eslint-plugin-prettier": "^5.5.5",
|
|
91
99
|
"inop": "^0.9.0",
|
|
92
100
|
"overtake": "^2.1.1",
|