@ambarltd/core 0.1.17

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.
Files changed (46) hide show
  1. package/README.md +10 -0
  2. package/dist/callable.d.ts +1 -0
  3. package/dist/callable.js +6 -0
  4. package/dist/future.d.ts +102 -0
  5. package/dist/future.js +164 -0
  6. package/dist/helpers/object.d.ts +9 -0
  7. package/dist/helpers/object.js +31 -0
  8. package/dist/json/decoder.d.ts +99 -0
  9. package/dist/json/decoder.js +213 -0
  10. package/dist/json/encoder.d.ts +50 -0
  11. package/dist/json/encoder.js +115 -0
  12. package/dist/json/schema.d.ts +78 -0
  13. package/dist/json/schema.js +168 -0
  14. package/dist/json/types.d.ts +6 -0
  15. package/dist/json/types.js +1 -0
  16. package/dist/list.d.ts +35 -0
  17. package/dist/list.js +130 -0
  18. package/dist/maybe.d.ts +104 -0
  19. package/dist/maybe.js +106 -0
  20. package/dist/remote-data.d.ts +117 -0
  21. package/dist/remote-data.js +148 -0
  22. package/dist/result.d.ts +75 -0
  23. package/dist/result.js +126 -0
  24. package/dist/router.d.ts +117 -0
  25. package/dist/router.js +106 -0
  26. package/dist/test.d.ts +63 -0
  27. package/dist/test.js +470 -0
  28. package/dist/time.d.ts +118 -0
  29. package/dist/time.js +387 -0
  30. package/dist/tracing/opentelemetry.d.ts +27 -0
  31. package/dist/tracing/opentelemetry.js +215 -0
  32. package/dist/tracing/proxy.d.ts +20 -0
  33. package/dist/tracing/proxy.js +103 -0
  34. package/dist/tracing/simple.d.ts +10 -0
  35. package/dist/tracing/simple.js +224 -0
  36. package/dist/tracing.d.ts +29 -0
  37. package/dist/tracing.js +55 -0
  38. package/dist/trampoline.d.ts +24 -0
  39. package/dist/trampoline.js +46 -0
  40. package/dist/tree-map.d.ts +73 -0
  41. package/dist/tree-map.js +169 -0
  42. package/dist/tree-set.d.ts +63 -0
  43. package/dist/tree-set.js +114 -0
  44. package/dist/types.d.ts +21 -0
  45. package/dist/types.js +1 -0
  46. package/package.json +49 -0
package/README.md ADDED
@@ -0,0 +1,10 @@
1
+ # Ambar Core
2
+
3
+ Shared utilities for Ambar projects.
4
+
5
+ ## Notes
6
+
7
+ - Core shared modules: Maybe, Future, Result, List, etc.
8
+ - Backend-only: router
9
+ - Frontend-only: RemoteData
10
+ - No Express dependency, only @types/express"
@@ -0,0 +1 @@
1
+ export default function Callable<T extends new (...args: any[]) => any>(classname: T): T & ((...args: ConstructorParameters<T>) => InstanceType<T>);
@@ -0,0 +1,6 @@
1
+ export default function Callable(classname) {
2
+ function apply(target, _, argumentsList) {
3
+ return new target(...argumentsList);
4
+ }
5
+ return new Proxy(classname, { apply });
6
+ }
@@ -0,0 +1,102 @@
1
+ import * as F from "fluture";
2
+ import { Result } from "./result";
3
+ import { FutureInstance } from "fluture";
4
+ type Cancel = () => void;
5
+ type Fut<E, C> = {
6
+ [K in keyof C]: Future<E, C[K]>;
7
+ };
8
+ /**
9
+ * A lazy asynchronous computation that produces either a rejection value
10
+ * of type `E` or a success value of type `T`.
11
+ *
12
+ * Unlike Promises, Futures are not executed until explicitly forked and
13
+ * can be safely cancelled. This is a thin wrapper around Fluture.
14
+ */
15
+ declare class Future<E, T> {
16
+ readonly inner: FutureInstance<E, T>;
17
+ /**
18
+ * Build a cancellable `Future` from an imperative body. Return a cancel
19
+ * function from `f` to clean up resources (timers, subscriptions, etc.).
20
+ *
21
+ * ```ts
22
+ * const f = Future.create<never, number>((reject, resolve) => {
23
+ * const id = setTimeout(() => resolve(42), 1000);
24
+ * return () => clearTimeout(id);
25
+ * });
26
+ * ```
27
+ */
28
+ static create<E, T>(f: (r: F.RejectFunction<E>, a: F.ResolveFunction<T>) => Cancel | void): Future<E, T>;
29
+ /** For inherently uncancellable operations. Prefer `create` when a cancel path exists. */
30
+ static createUncancellable<E, T>(f: (r: F.RejectFunction<E>, a: F.ResolveFunction<T>) => void): Future<E, T>;
31
+ /** Lift a value into an already-resolved `Future`. */
32
+ static resolve<E, T>(x: T): Future<E, T>;
33
+ /** Lift a value into an already-rejected `Future`. */
34
+ static reject<E, T>(x: E): Future<E, T>;
35
+ /**
36
+ * Wrap a `Promise` producer. Always produces `Future<Error, T>` — use
37
+ * `.mapRej()` to narrow the error type. Loses cancellation; use
38
+ * `Future.create` for operations that must be cancellable.
39
+ *
40
+ * Don't double-wrap: pass `() => fn()`, not `async () => { const r = await fn(); return r; }`.
41
+ */
42
+ static attemptP<T>(f: () => Promise<T>): Future<Error, T>;
43
+ /**
44
+ * Guaranteed resource cleanup. `release` runs whether `consume` succeeds or
45
+ * fails — use for locks, connections, file descriptors.
46
+ */
47
+ static bracket<E, A, B, C>(acquire: Future<E, A>, release: (_: A) => Future<E, C>, consume: (_: A) => Future<E, B>): Future<E, B>;
48
+ /** Run `xs` with at most `n` in flight concurrently; results are collected in order. */
49
+ static parallel<E, T>(n: number, xs: Array<Future<E, T>>): Future<E, Array<T>>;
50
+ /**
51
+ * Run a named map of futures concurrently and collect results under the same keys.
52
+ *
53
+ * ```ts
54
+ * Future.concurrently({ user: fetchUser(id), posts: fetchPosts(id) })
55
+ * .fork(handleError, ({ user, posts }) => render(user, posts));
56
+ * ```
57
+ */
58
+ static concurrently<E, C extends {
59
+ [k: string]: any;
60
+ }>(obj: Fut<E, C>): Future<E, C>;
61
+ /** Map `xs` with `f` and run the resulting futures with unbounded concurrency. */
62
+ static mapConcurrently<A, E, T>(f: (_: A) => Future<E, T>, xs: Array<A>): Future<E, Array<T>>;
63
+ /** Run two futures in parallel and pair their results. */
64
+ static both<E, A, B>(x: Future<E, A>, y: Future<E, B>): Future<E, [A, B]>;
65
+ /** First to settle wins — useful for timeouts: `Future.race(request, timeout)`. */
66
+ static race<E, T>(x: Future<E, T>, y: Future<E, T>): Future<E, T>;
67
+ /** Sequential traversal. Use `parallel` or `mapConcurrently` when order doesn't matter. */
68
+ static traverse<A, E, T>(f: (_: A) => Future<E, T>, xs: Array<A>): Future<E, Array<T>>;
69
+ /** Resolve with `value` after `milliseconds`. The timer is cleared if cancelled. */
70
+ static resolveAfter<E, T>(milliseconds: number, value: T): Future<E, T>;
71
+ constructor(inner: FutureInstance<E, T>);
72
+ /** Transform the success value. */
73
+ map<W>(f: (_: T) => W): Future<E, W>;
74
+ /** Transform the error value; the future stays rejected. */
75
+ mapRej<W>(f: (_: E) => W): Future<W, T>;
76
+ /** Transform both branches in one call. */
77
+ bimap<F, W>(f: (_: E) => F, g: (_: T) => W): Future<F, W>;
78
+ /**
79
+ * Sequential async composition. Short-circuits on rejection.
80
+ *
81
+ * ```ts
82
+ * fetchUser(id).chain(u => fetchPosts(u.id).map(posts => ({ u, posts })));
83
+ * ```
84
+ */
85
+ chain<W>(f: (_: T) => Future<E, W>): Future<E, W>;
86
+ /** Recover from rejection by returning a new `Future`. */
87
+ chainRej<F>(f: (_: E) => Future<F, T>): Future<F, T>;
88
+ /** Branch on both rejected and resolved paths into a single continuation. */
89
+ bichain<F, W>(f: (_: E) => Future<F, W>, g: (_: T) => Future<F, W>): Future<F, W>;
90
+ /** Run `act` for its effects regardless of whether `this` settled with success or failure. */
91
+ finally(act: Future<E, void>): Future<E, T>;
92
+ /**
93
+ * Execute the future. Nothing runs until `fork` is called. Returns a `Cancel`
94
+ * function — store it if cancellation is needed.
95
+ */
96
+ fork(f: (_: E) => void, g: (_: T) => void): Cancel;
97
+ /** Convert to a native `Promise`, mapping the error branch through `f` to an `Error`. */
98
+ promise(f: (_: E) => Error): Promise<T>;
99
+ /** Convert to `Promise<Result<E, T>>` — the returned Promise never rejects. */
100
+ promiseR(): Promise<Result<E, T>>;
101
+ }
102
+ export { Future, type Cancel };
package/dist/future.js ADDED
@@ -0,0 +1,164 @@
1
+ import * as F from "fluture";
2
+ import { Success, Failure } from "./result";
3
+ /**
4
+ * A lazy asynchronous computation that produces either a rejection value
5
+ * of type `E` or a success value of type `T`.
6
+ *
7
+ * Unlike Promises, Futures are not executed until explicitly forked and
8
+ * can be safely cancelled. This is a thin wrapper around Fluture.
9
+ */
10
+ class Future {
11
+ inner;
12
+ /**
13
+ * Build a cancellable `Future` from an imperative body. Return a cancel
14
+ * function from `f` to clean up resources (timers, subscriptions, etc.).
15
+ *
16
+ * ```ts
17
+ * const f = Future.create<never, number>((reject, resolve) => {
18
+ * const id = setTimeout(() => resolve(42), 1000);
19
+ * return () => clearTimeout(id);
20
+ * });
21
+ * ```
22
+ */
23
+ static create(f) {
24
+ return new Future(F.Future((r, a) => {
25
+ const cancel = f(r, a);
26
+ return cancel === undefined ? () => { } : cancel;
27
+ }));
28
+ }
29
+ /** For inherently uncancellable operations. Prefer `create` when a cancel path exists. */
30
+ static createUncancellable(f) {
31
+ return new Future(F.Future((r, a) => {
32
+ f(r, a);
33
+ return () => { }; // No-op cancel function
34
+ }));
35
+ }
36
+ /** Lift a value into an already-resolved `Future`. */
37
+ static resolve(x) {
38
+ return new Future(F.resolve(x));
39
+ }
40
+ /** Lift a value into an already-rejected `Future`. */
41
+ static reject(x) {
42
+ return new Future(F.reject(x));
43
+ }
44
+ /**
45
+ * Wrap a `Promise` producer. Always produces `Future<Error, T>` — use
46
+ * `.mapRej()` to narrow the error type. Loses cancellation; use
47
+ * `Future.create` for operations that must be cancellable.
48
+ *
49
+ * Don't double-wrap: pass `() => fn()`, not `async () => { const r = await fn(); return r; }`.
50
+ */
51
+ static attemptP(f) {
52
+ return new Future(F.attemptP(f));
53
+ }
54
+ /**
55
+ * Guaranteed resource cleanup. `release` runs whether `consume` succeeds or
56
+ * fails — use for locks, connections, file descriptors.
57
+ */
58
+ static bracket(acquire, release, consume) {
59
+ return new Future(F.hook(acquire.inner)(x => release(x).inner)(x => consume(x).inner));
60
+ }
61
+ /** Run `xs` with at most `n` in flight concurrently; results are collected in order. */
62
+ static parallel(n, xs) {
63
+ return new Future(F.parallel(n)(xs.map(f => f.inner)));
64
+ }
65
+ /**
66
+ * Run a named map of futures concurrently and collect results under the same keys.
67
+ *
68
+ * ```ts
69
+ * Future.concurrently({ user: fetchUser(id), posts: fetchPosts(id) })
70
+ * .fork(handleError, ({ user, posts }) => render(user, posts));
71
+ * ```
72
+ */
73
+ static concurrently(obj) {
74
+ const futures = [];
75
+ Object.keys(obj).forEach((key) => {
76
+ const fut = obj[key];
77
+ futures.push(fut.map(value => ({ [key]: value })));
78
+ });
79
+ return Future.parallel(Infinity, futures).map(results => results.reduce((acc, x) => Object.assign(acc, x), {}));
80
+ }
81
+ /** Map `xs` with `f` and run the resulting futures with unbounded concurrency. */
82
+ static mapConcurrently(f, xs) {
83
+ return Future.parallel(Infinity, xs.map(f));
84
+ }
85
+ /** Run two futures in parallel and pair their results. */
86
+ static both(x, y) {
87
+ return new Future(F.both(x.inner)(y.inner));
88
+ }
89
+ /** First to settle wins — useful for timeouts: `Future.race(request, timeout)`. */
90
+ static race(x, y) {
91
+ return new Future(F.race(x.inner)(y.inner));
92
+ }
93
+ /** Sequential traversal. Use `parallel` or `mapConcurrently` when order doesn't matter. */
94
+ static traverse(f, xs) {
95
+ return xs.reduce((acc, x) => acc.chain(ys => f(x).map(y => [...ys, y])), Future.resolve([]));
96
+ }
97
+ /** Resolve with `value` after `milliseconds`. The timer is cleared if cancelled. */
98
+ static resolveAfter(milliseconds, value) {
99
+ return Future.create((_, res) => {
100
+ const timer = setTimeout(() => res(value), milliseconds);
101
+ return function cancel() {
102
+ clearTimeout(timer);
103
+ };
104
+ });
105
+ }
106
+ constructor(inner) {
107
+ this.inner = inner;
108
+ }
109
+ /** Transform the success value. */
110
+ map(f) {
111
+ return new Future(F.map(f)(this.inner));
112
+ }
113
+ /** Transform the error value; the future stays rejected. */
114
+ mapRej(f) {
115
+ return new Future(F.mapRej(f)(this.inner));
116
+ }
117
+ /** Transform both branches in one call. */
118
+ bimap(f, g) {
119
+ return this.map(g).mapRej(f);
120
+ }
121
+ /**
122
+ * Sequential async composition. Short-circuits on rejection.
123
+ *
124
+ * ```ts
125
+ * fetchUser(id).chain(u => fetchPosts(u.id).map(posts => ({ u, posts })));
126
+ * ```
127
+ */
128
+ chain(f) {
129
+ const g = (x) => f(x).inner;
130
+ return new Future(F.chain(g)(this.inner));
131
+ }
132
+ /** Recover from rejection by returning a new `Future`. */
133
+ chainRej(f) {
134
+ const g = (x) => f(x).inner;
135
+ return new Future(F.chainRej(g)(this.inner));
136
+ }
137
+ /** Branch on both rejected and resolved paths into a single continuation. */
138
+ bichain(f, g) {
139
+ const h = (x) => f(x).inner;
140
+ const i = (x) => g(x).inner;
141
+ return new Future(F.bichain(h)(i)(this.inner));
142
+ }
143
+ /** Run `act` for its effects regardless of whether `this` settled with success or failure. */
144
+ finally(act) {
145
+ return new Future(F.lastly(act.inner)(this.inner));
146
+ }
147
+ /**
148
+ * Execute the future. Nothing runs until `fork` is called. Returns a `Cancel`
149
+ * function — store it if cancellation is needed.
150
+ */
151
+ fork(f, g) {
152
+ return F.fork(f)(g)(this.inner);
153
+ }
154
+ /** Convert to a native `Promise`, mapping the error branch through `f` to an `Error`. */
155
+ promise(f) {
156
+ return F.promise(this.mapRej(f).inner);
157
+ }
158
+ /** Convert to `Promise<Result<E, T>>` — the returned Promise never rejects. */
159
+ promiseR() {
160
+ const f = this.map(Success).chainRej(e => Future.resolve(Failure(e)));
161
+ return F.promise(f.inner);
162
+ }
163
+ }
164
+ export { Future };
@@ -0,0 +1,9 @@
1
+ export { filterKeys, filterMap, mapValues };
2
+ declare function filterKeys<O extends object, K extends keyof O>(obj: O, pred: <KK extends K>(key: KK, value: O[KK]) => boolean): Partial<O>;
3
+ /** Filter keys and transform values at the same time. */
4
+ declare function filterMap<O extends object, R>(obj: O, fn: <K extends keyof O>(key: K, value: O[K]) => R | undefined): Partial<{
5
+ [K in keyof O]: R;
6
+ }>;
7
+ declare function mapValues<T extends object, R>(obj: T, fn: <K extends keyof T>(key: K, value: T[K]) => R): {
8
+ [K in keyof T]: R;
9
+ };
@@ -0,0 +1,31 @@
1
+ export { filterKeys, filterMap, mapValues };
2
+ function filterKeys(obj, pred) {
3
+ const result = {};
4
+ const keys = Object.keys(obj);
5
+ for (const key of keys) {
6
+ if (pred(key, obj[key])) {
7
+ result[key] = obj[key];
8
+ }
9
+ }
10
+ return result;
11
+ }
12
+ /** Filter keys and transform values at the same time. */
13
+ function filterMap(obj, fn) {
14
+ const result = {};
15
+ const keys = Object.keys(obj);
16
+ for (const key of keys) {
17
+ const mapped = fn(key, obj[key]);
18
+ if (mapped !== undefined) {
19
+ result[key] = mapped;
20
+ }
21
+ }
22
+ return result;
23
+ }
24
+ function mapValues(obj, fn) {
25
+ const out = {};
26
+ const keys = Object.keys(obj);
27
+ for (const k of keys) {
28
+ out[k] = fn(k, obj[k]);
29
+ }
30
+ return out;
31
+ }
@@ -0,0 +1,99 @@
1
+ import { Result } from "../result";
2
+ import { Maybe, Nullable } from "../maybe";
3
+ import { List } from "../list";
4
+ import { Json } from "./types";
5
+ /** Infer the type from a decoder definition. */
6
+ type Infer<A extends Decoder<unknown>> = A extends Decoder<infer B> ? B : never;
7
+ /** A type that can be decoded from JSON. */
8
+ interface FromJSON<T> {
9
+ decoder(): Decoder<T>;
10
+ }
11
+ /**
12
+ * A type-safe monadic decoder combinator.
13
+ *
14
+ * Use like this:
15
+ *
16
+ * ```ts
17
+ * import * as Decoder from "@ambarltd/core/json/decoder";
18
+ *
19
+ * type Test = {
20
+ * one: number,
21
+ * two: boolean,
22
+ * three: {
23
+ * inner: string
24
+ * }
25
+ * };
26
+ *
27
+ * const testDecoder: Decoder<Test> = object({
28
+ * one: Decoder.number,
29
+ * two: Decoder.boolean,
30
+ * three: Decoder.object({
31
+ * inner: Decoder.string
32
+ * })
33
+ * });
34
+ *
35
+ * const decodeJSON = (s: string) => Decoder.decode(JSON.parse(s), testDecoder);
36
+ * ```
37
+ */
38
+ declare class Decoder<T> {
39
+ readonly run: (input: unknown) => DecodeResult<T>;
40
+ constructor(run: (input: unknown) => DecodeResult<T>);
41
+ chain<W>(f: (v: T) => Decoder<W>): Decoder<W>;
42
+ map<W>(f: (v: T) => W): Decoder<W>;
43
+ }
44
+ declare function decode<T>(input: unknown, decoder: Decoder<T>): Result<string, T>;
45
+ type DecodeResult<T> = Result<[Path, string], T>;
46
+ type Path = List<string>;
47
+ declare const failure: <T>(msg: string) => DecodeResult<T>;
48
+ declare const fail: <T>(msg: string) => Decoder<T>;
49
+ declare const always: <T>(v: T) => Decoder<T>;
50
+ declare const succeed: <T>(v: T) => Decoder<T>;
51
+ declare const any: Decoder<unknown>;
52
+ /** Use two decoders on the same input. */
53
+ declare const both: <T, U>(left: Decoder<T>, right: Decoder<U>) => Decoder<[T, U]>;
54
+ declare const string: Decoder<string>;
55
+ declare const number: Decoder<number>;
56
+ declare const stringNumber: Decoder<number>;
57
+ declare const boolean: Decoder<boolean>;
58
+ declare const array: <V>(decodeValue: Decoder<V>) => Decoder<Array<V>>;
59
+ type DecoderDef<A> = {
60
+ [P in keyof A]: Decoder<A[P]> | DecoderOptional<A[P]>;
61
+ };
62
+ /** Ignores extra properties. */
63
+ declare const object: <A>(decoders: DecoderDef<A>) => Decoder<A>;
64
+ type ObjectMap<A> = {
65
+ [x: string]: A;
66
+ };
67
+ declare const objectMap: <A>(decoder: Decoder<A>) => Decoder<ObjectMap<A>>;
68
+ declare const pair: <L, R>(ldecode: Decoder<L>, rdecode: Decoder<R>) => Decoder<[L, R]>;
69
+ declare const triple: <A, B, C>(pA: Decoder<A>, pB: Decoder<B>, pC: Decoder<C>) => Decoder<[A, B, C]>;
70
+ declare const oneOf: <T extends Decoder<any>[]>(decoders: T) => T[number];
71
+ declare const maybe: <V>(decoder: Decoder<V>) => Decoder<Maybe<V>>;
72
+ declare const nullable: <V>(decoder: Decoder<V>) => Decoder<Nullable<V>>;
73
+ declare const nullP: Decoder<null>;
74
+ declare const undefinedP: Decoder<undefined>;
75
+ /** Useful for parsing tag names in discriminated unions. */
76
+ declare const stringLiteral: <T extends string>(str: T) => Decoder<T>;
77
+ /**
78
+ * Decoder for a field that may not be present.
79
+ * If it is absent it will be decoded as `Nothing()`.
80
+ */
81
+ declare class DecoderOptional<A> {
82
+ readonly decoder: Decoder<A>;
83
+ private constructor();
84
+ static from<A>(d: Decoder<A>): DecoderOptional<Maybe<A>>;
85
+ map<W>(f: (v: A) => W): DecoderOptional<W>;
86
+ }
87
+ declare const optionalMaybe: <V>(decoder: Decoder<V>) => DecoderOptional<Maybe<V>>;
88
+ declare const optionalNullable: <V>(decoder: Decoder<NonNullable<V>>) => DecoderOptional<Nullable<V>>;
89
+ /** An object field that may be absent. */
90
+ declare const optional: <V>(decoder: Decoder<V>) => DecoderOptional<V | undefined>;
91
+ /** If the field is absent, the default value will be used. */
92
+ declare const optionalDefault: <V>(def: V, decoder: Decoder<V>) => DecoderOptional<V>;
93
+ /** Define a recursive decoder. */
94
+ declare function recursive<A>(f: (p: Decoder<A>) => Decoder<A>): Decoder<A>;
95
+ declare const json: Decoder<Json>;
96
+ declare const stringEnum: <const T extends string[]>(strs: T) => Decoder<T[number]>;
97
+ /** Decode a value from a string by treating the string as a stringified JSON value. */
98
+ declare const stringified: <T>(inner: Decoder<T>) => Decoder<T>;
99
+ export { type FromJSON, type Infer, Decoder, type DecoderOptional, type DecoderDef, type DecodeResult, decode, object, objectMap, pair, array, string, number, boolean, any, json, nullP, stringNumber, undefinedP, oneOf, maybe, nullable, stringLiteral, stringEnum, triple, always, fail, failure, succeed, both, optional, optionalNullable, optionalMaybe, optionalDefault, stringified, recursive, };
@@ -0,0 +1,213 @@
1
+ import { Success, Failure, traverse } from "../result";
2
+ import { Just, Nothing } from "../maybe";
3
+ import { List } from "../list";
4
+ /**
5
+ * A type-safe monadic decoder combinator.
6
+ *
7
+ * Use like this:
8
+ *
9
+ * ```ts
10
+ * import * as Decoder from "@ambarltd/core/json/decoder";
11
+ *
12
+ * type Test = {
13
+ * one: number,
14
+ * two: boolean,
15
+ * three: {
16
+ * inner: string
17
+ * }
18
+ * };
19
+ *
20
+ * const testDecoder: Decoder<Test> = object({
21
+ * one: Decoder.number,
22
+ * two: Decoder.boolean,
23
+ * three: Decoder.object({
24
+ * inner: Decoder.string
25
+ * })
26
+ * });
27
+ *
28
+ * const decodeJSON = (s: string) => Decoder.decode(JSON.parse(s), testDecoder);
29
+ * ```
30
+ */
31
+ class Decoder {
32
+ run;
33
+ constructor(run) {
34
+ this.run = run;
35
+ }
36
+ chain(f) {
37
+ return new Decoder(u => this.run(u).chain(v => f(v).run(u)));
38
+ }
39
+ map(f) {
40
+ return new Decoder(v => this.run(v).map(f));
41
+ }
42
+ }
43
+ function decode(input, decoder) {
44
+ return decoder.run(input).mapFailure(showPath);
45
+ }
46
+ function showPath([path, error]) {
47
+ return error + ". When parsing: " + Array.from(path).join(".");
48
+ }
49
+ const failure = (msg) => Failure([List.empty(), msg]);
50
+ const fail = (msg) => new Decoder(_ => Failure([List.empty(), msg]));
51
+ const always = (v) => new Decoder(_ => Success(v));
52
+ const succeed = always;
53
+ const any = new Decoder(v => Success(v));
54
+ /** Use two decoders on the same input. */
55
+ const both = (left, right) => new Decoder(u => {
56
+ const l = left.run(u);
57
+ if (l instanceof Failure) {
58
+ return new Failure(l.error);
59
+ }
60
+ const r = right.run(u);
61
+ if (r instanceof Failure) {
62
+ return new Failure(r.error);
63
+ }
64
+ return Success([l.value, r.value]);
65
+ });
66
+ const string = new Decoder(v => typeof v === "string" ? Success(v) : failure("expected string but found " + typeof v));
67
+ const number = new Decoder(v => typeof v === "number" ? Success(v) : failure("expected number but found " + typeof v));
68
+ const stringNumber = string.chain(s => {
69
+ const v = parseInt(s, 10);
70
+ return isNaN(v) ? fail("not a valid number: " + s) : succeed(v);
71
+ });
72
+ const boolean = new Decoder(v => typeof v === "boolean" ? Success(v) : failure("expected boolean but found " + typeof v));
73
+ const array = (decodeValue) => new Decoder(input => {
74
+ if (!Array.isArray(input)) {
75
+ return failure("expected array but found " + typeof input);
76
+ }
77
+ return traverse(List.from(input), decodeValue.run).map(list => Array.from(list));
78
+ });
79
+ /** Ignores extra properties. */
80
+ const object = (decoders) => new Decoder(input => {
81
+ if (typeof input !== "object" || input === null) {
82
+ return failure("expected object but found " + typeof input);
83
+ }
84
+ const obj = input;
85
+ const result = {};
86
+ for (const field in decoders) {
87
+ const decoder = decoders[field];
88
+ const decoded = decoder instanceof DecoderOptional ?
89
+ obj[field] === undefined ?
90
+ decoder.decoder.run({ nothing: {} })
91
+ : decoder.decoder.run({ just: obj[field] })
92
+ : decoder.run(obj[field]);
93
+ switch (true) {
94
+ case decoded instanceof Success:
95
+ result[field] = decoded.value;
96
+ break;
97
+ case decoded instanceof Failure: {
98
+ const [path, msg] = decoded.error;
99
+ return Failure([List.cons(field, path), msg]);
100
+ }
101
+ default:
102
+ return decoded;
103
+ }
104
+ }
105
+ return Success(result);
106
+ });
107
+ const objectMap = (decoder) => new Decoder(input => {
108
+ if (typeof input !== "object" || input === null) {
109
+ return failure("expected object but found " + typeof input);
110
+ }
111
+ // object without a prototype or built-in functions.
112
+ const result = Object.create(null);
113
+ for (const field in input) {
114
+ // @ts-ignore
115
+ const decoded = decoder.run(input[field]);
116
+ switch (true) {
117
+ case decoded instanceof Success:
118
+ result[field] = decoded.value;
119
+ break;
120
+ case decoded instanceof Failure: {
121
+ const [path, msg] = decoded.error;
122
+ return Failure([List.cons(field, path), msg]);
123
+ }
124
+ default:
125
+ return decoded;
126
+ }
127
+ }
128
+ return Success(result);
129
+ });
130
+ const pair = (ldecode, rdecode) => new Decoder(input => {
131
+ if (!Array.isArray(input)) {
132
+ return failure("expected array but found " + typeof input);
133
+ }
134
+ if (input.length !== 2) {
135
+ return failure("expected array with 2 elements but it found " + input.length);
136
+ }
137
+ const [l, r] = input;
138
+ return ldecode.run(l).chain(left => rdecode.run(r).chain(right => Success([left, right])));
139
+ });
140
+ const triple = (pA, pB, pC) => new Decoder(input => {
141
+ if (!Array.isArray(input)) {
142
+ return failure("expected array but found " + typeof input);
143
+ }
144
+ if (input.length !== 3) {
145
+ return failure("expected array with 3 elements but it found " + input.length);
146
+ }
147
+ const [ia, ib, ic] = input;
148
+ return pA.run(ia).chain(a => pB.run(ib).chain(b => pC.run(ic).chain(c => Success([a, b, c]))));
149
+ });
150
+ const oneOf = (decoders) => new Decoder(input => {
151
+ let decoded = failure("no decoders");
152
+ const errors = [];
153
+ for (const decoder of decoders) {
154
+ decoded = decoder.run(input);
155
+ if (decoded instanceof Success) {
156
+ return decoded;
157
+ }
158
+ errors.push(decoded.error);
159
+ }
160
+ return Failure([List.empty(), errors.map(showPath).join("\n")]);
161
+ });
162
+ const maybe = (decoder) => oneOf([object({ nothing: object({}) }).map(_ => Nothing()), object({ just: decoder }).map(v => Just(v.just))]);
163
+ const nullable = (decoder) => oneOf([nullP, decoder]);
164
+ const nullP = new Decoder(v => v === null ? Success(null) : failure("expected null but found " + typeof v));
165
+ const undefinedP = new Decoder(v => v === undefined ? Success(undefined) : failure("expected `undefined` " + typeof v));
166
+ /** Useful for parsing tag names in discriminated unions. */
167
+ const stringLiteral = (str) => new Decoder(v => (v === str ? Success(v) : failure(`expected '${str}' but found '${v}'`)));
168
+ /**
169
+ * Decoder for a field that may not be present.
170
+ * If it is absent it will be decoded as `Nothing()`.
171
+ */
172
+ class DecoderOptional {
173
+ decoder;
174
+ constructor(decoder) {
175
+ this.decoder = decoder;
176
+ }
177
+ static from(d) {
178
+ return new DecoderOptional(maybe(d));
179
+ }
180
+ map(f) {
181
+ return new DecoderOptional(this.decoder.map(f));
182
+ }
183
+ }
184
+ const optionalMaybe = (decoder) => DecoderOptional.from(decoder);
185
+ const optionalNullable = (decoder) => optionalMaybe(decoder).map(v => v.asNullable());
186
+ /** An object field that may be absent. */
187
+ const optional = (decoder) => optionalMaybe(decoder).map(v => (v instanceof Nothing ? undefined : v.value));
188
+ /** If the field is absent, the default value will be used. */
189
+ const optionalDefault = (def, decoder) => {
190
+ return optionalMaybe(decoder).map(v => v.withDefault(def));
191
+ };
192
+ /** Define a recursive decoder. */
193
+ function recursive(f) {
194
+ const base = fail("A recursive decoder cannot immediately call itself.");
195
+ const top = f(base);
196
+ // @ts-expect-error will complain that 'run' is readonly. But we are doing this on purpose here.
197
+ base.run = top.run;
198
+ return top;
199
+ }
200
+ const json = recursive(json => oneOf([nullP, string, number, boolean, array(json), objectMap(json)]));
201
+ const stringEnum = (strs) => oneOf(strs.map(stringLiteral));
202
+ /** Decode a value from a string by treating the string as a stringified JSON value. */
203
+ const stringified = (inner) => string
204
+ .chain(str => {
205
+ try {
206
+ return succeed(JSON.parse(str));
207
+ }
208
+ catch {
209
+ return fail("Invalid JSON string");
210
+ }
211
+ })
212
+ .chain(json => new Decoder(_ => inner.run(json)));
213
+ export { Decoder, decode, object, objectMap, pair, array, string, number, boolean, any, json, nullP, stringNumber, undefinedP, oneOf, maybe, nullable, stringLiteral, stringEnum, triple, always, fail, failure, succeed, both, optional, optionalNullable, optionalMaybe, optionalDefault, stringified, recursive, };