@valbuild/core 0.12.0 → 0.13.1

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 (72) hide show
  1. package/README.md +25 -37
  2. package/jest.config.js +4 -0
  3. package/package.json +1 -1
  4. package/src/Json.ts +4 -0
  5. package/src/expr/README.md +193 -0
  6. package/src/expr/eval.test.ts +202 -0
  7. package/src/expr/eval.ts +248 -0
  8. package/src/expr/expr.ts +91 -0
  9. package/src/expr/index.ts +3 -0
  10. package/src/expr/parser.test.ts +158 -0
  11. package/src/expr/parser.ts +229 -0
  12. package/src/expr/repl.ts +93 -0
  13. package/src/expr/tokenizer.test.ts +539 -0
  14. package/src/expr/tokenizer.ts +117 -0
  15. package/src/fetchVal.test.ts +164 -0
  16. package/src/fetchVal.ts +211 -0
  17. package/src/fp/array.ts +30 -0
  18. package/src/fp/index.ts +3 -0
  19. package/src/fp/result.ts +214 -0
  20. package/src/fp/util.ts +52 -0
  21. package/src/index.ts +55 -0
  22. package/src/initSchema.ts +45 -0
  23. package/src/initVal.ts +96 -0
  24. package/src/module.test.ts +170 -0
  25. package/src/module.ts +333 -0
  26. package/src/patch/deref.test.ts +300 -0
  27. package/src/patch/deref.ts +128 -0
  28. package/src/patch/index.ts +11 -0
  29. package/src/patch/json.test.ts +583 -0
  30. package/src/patch/json.ts +304 -0
  31. package/src/patch/operation.ts +74 -0
  32. package/src/patch/ops.ts +83 -0
  33. package/src/patch/parse.test.ts +202 -0
  34. package/src/patch/parse.ts +187 -0
  35. package/src/patch/patch.ts +46 -0
  36. package/src/patch/util.ts +67 -0
  37. package/src/schema/array.ts +52 -0
  38. package/src/schema/boolean.ts +38 -0
  39. package/src/schema/i18n.ts +65 -0
  40. package/src/schema/image.ts +70 -0
  41. package/src/schema/index.ts +46 -0
  42. package/src/schema/literal.ts +42 -0
  43. package/src/schema/number.ts +45 -0
  44. package/src/schema/object.ts +67 -0
  45. package/src/schema/oneOf.ts +60 -0
  46. package/src/schema/richtext.ts +417 -0
  47. package/src/schema/string.ts +49 -0
  48. package/src/schema/union.ts +62 -0
  49. package/src/selector/ExprProxy.test.ts +203 -0
  50. package/src/selector/ExprProxy.ts +209 -0
  51. package/src/selector/SelectorProxy.test.ts +172 -0
  52. package/src/selector/SelectorProxy.ts +237 -0
  53. package/src/selector/array.ts +37 -0
  54. package/src/selector/boolean.ts +4 -0
  55. package/src/selector/file.ts +14 -0
  56. package/src/selector/i18n.ts +13 -0
  57. package/src/selector/index.ts +159 -0
  58. package/src/selector/number.ts +4 -0
  59. package/src/selector/object.ts +22 -0
  60. package/src/selector/primitive.ts +17 -0
  61. package/src/selector/remote.ts +9 -0
  62. package/src/selector/selector.test.ts +453 -0
  63. package/src/selector/selectorOf.ts +7 -0
  64. package/src/selector/string.ts +4 -0
  65. package/src/source/file.ts +45 -0
  66. package/src/source/i18n.ts +60 -0
  67. package/src/source/index.ts +50 -0
  68. package/src/source/remote.ts +54 -0
  69. package/src/val/array.ts +10 -0
  70. package/src/val/index.ts +90 -0
  71. package/src/val/object.ts +13 -0
  72. package/src/val/primitive.ts +8 -0
@@ -0,0 +1,164 @@
1
+ import { initSchema } from "./initSchema";
2
+ import { content } from "./module";
3
+ import { getValPath } from "./val";
4
+ import { serializedValOfSelectorSource, fetchVal } from "./fetchVal";
5
+
6
+ const s = initSchema(["en_US", "no_NB"]);
7
+ // const i18n = initI18n(["en_US", "no_NB"]);
8
+
9
+ describe("serialization of val", () => {
10
+ test("serialized val: string", () => {
11
+ const schema = s.string();
12
+
13
+ const testVal = content("/app", schema, "foo");
14
+
15
+ expect(serializedValOfSelectorSource(testVal)).toStrictEqual({
16
+ val: "foo",
17
+ valPath: "/app",
18
+ });
19
+ });
20
+
21
+ test("serialized val: array", () => {
22
+ const schema = s.array(s.string());
23
+
24
+ const testVal = content("/app", schema, ["foo", "bar"]);
25
+
26
+ expect(serializedValOfSelectorSource(testVal)).toStrictEqual({
27
+ val: [
28
+ {
29
+ val: "foo",
30
+ valPath: "/app.0",
31
+ },
32
+ { val: "bar", valPath: "/app.1" },
33
+ ],
34
+ valPath: "/app",
35
+ });
36
+
37
+ // ^?
38
+ });
39
+
40
+ test("serialized val: object", () => {
41
+ const schema = s.object({ foo: s.object({ bar: s.array(s.string()) }) });
42
+
43
+ const testVal = content("/app", schema, { foo: { bar: ["foo", "bar"] } });
44
+
45
+ expect(serializedValOfSelectorSource(testVal)).toStrictEqual({
46
+ val: {
47
+ foo: {
48
+ val: {
49
+ bar: {
50
+ val: [
51
+ {
52
+ val: "foo",
53
+ valPath: '/app."foo"."bar".0',
54
+ },
55
+ { val: "bar", valPath: '/app."foo"."bar".1' },
56
+ ],
57
+ valPath: '/app."foo"."bar"',
58
+ },
59
+ },
60
+ valPath: '/app."foo"',
61
+ },
62
+ },
63
+ valPath: "/app",
64
+ });
65
+ });
66
+ });
67
+
68
+ describe("fetchVal", () => {
69
+ test("valuate: string", async () => {
70
+ const schema = s.string();
71
+
72
+ const testVal = content("/app", schema, "foo");
73
+
74
+ const test = await fetchVal(testVal);
75
+ // ^? should be Val<string>
76
+ expect(test.val).toBe("foo");
77
+ expect(getValPath(test)).toBe("/app");
78
+ });
79
+
80
+ test("valuate: array", async () => {
81
+ const schema = s.array(s.string());
82
+
83
+ const testVal = content("/app", schema, ["foo", "bar"]);
84
+
85
+ const test = await fetchVal(testVal);
86
+ // ^? should be Val<string[]>
87
+ expect(test.val).toStrictEqual(["foo", "bar"]);
88
+ expect(test[0].val).toStrictEqual("foo");
89
+ expect(test[1].val).toStrictEqual("bar");
90
+ expect(getValPath(test[0])).toStrictEqual("/app.0");
91
+ expect(getValPath(test[1])).toStrictEqual("/app.1");
92
+ });
93
+
94
+ test("valuate: object", async () => {
95
+ const schema = s.object({ foo: s.object({ bar: s.array(s.string()) }) });
96
+
97
+ const testVal = content("/app", schema, { foo: { bar: ["foo", "bar"] } });
98
+
99
+ const test = await fetchVal(testVal);
100
+ // ^? should be Val<{ foo: { bar: string[] } }>
101
+
102
+ expect(test.val).toStrictEqual({ foo: { bar: ["foo", "bar"] } });
103
+ expect(test.foo.val).toStrictEqual({ bar: ["foo", "bar"] });
104
+ expect(test.foo.bar.val).toStrictEqual(["foo", "bar"]);
105
+ expect(test.foo.bar[0].val).toStrictEqual("foo");
106
+ expect(test.foo.bar[1].val).toStrictEqual("bar");
107
+ expect(getValPath(test.foo.bar[0])).toStrictEqual('/app."foo"."bar".0');
108
+ expect(getValPath(test.foo.bar[1])).toStrictEqual('/app."foo"."bar".1');
109
+ });
110
+
111
+ test("valuate: array with map", async () => {
112
+ const schema = s.array(s.string());
113
+
114
+ const testVal = content("/app", schema, ["foo", "bar"]);
115
+
116
+ const test = await fetchVal({
117
+ // ^? should be Val<{ title: string }[]>
118
+ foo: testVal.map((v) => ({ title: v })),
119
+ test: testVal,
120
+ });
121
+ expect(test.val).toStrictEqual({
122
+ foo: [{ title: "foo" }, { title: "bar" }],
123
+ test: ["foo", "bar"],
124
+ });
125
+ });
126
+
127
+ test("valuate: 2 modules with oneOf", async () => {
128
+ const testVal1 = content("/testVal1", s.array(s.string()), [
129
+ "test-val-1-0",
130
+ "test-val-1-1",
131
+ ]);
132
+ const testVal2 = content(
133
+ "/testVal2",
134
+ s.object({ test1: s.oneOf(testVal1), test2: s.string() }),
135
+ {
136
+ test2: "test2 value",
137
+ test1: testVal1[0],
138
+ }
139
+ );
140
+
141
+ const test = await fetchVal({
142
+ // ^?
143
+ testVal1: testVal1.map((v) => ({ title: v, otherModule: testVal2 })),
144
+ testVal2: testVal2,
145
+ });
146
+ expect(test.val).toStrictEqual({
147
+ testVal1: [
148
+ {
149
+ title: "test-val-1-0",
150
+ otherModule: { test2: "test2 value", test1: "test-val-1-0" },
151
+ },
152
+ {
153
+ title: "test-val-1-1",
154
+ otherModule: { test2: "test2 value", test1: "test-val-1-0" },
155
+ },
156
+ ],
157
+ testVal2: { test2: "test2 value", test1: "test-val-1-0" },
158
+ });
159
+ expect(getValPath(test.testVal1[0].otherModule.test1)).toStrictEqual(
160
+ "/testVal1.0"
161
+ );
162
+ expect(getValPath(test.testVal2.test2)).toStrictEqual('/testVal2."test2"');
163
+ });
164
+ });
@@ -0,0 +1,211 @@
1
+ import {
2
+ GenericSelector,
3
+ Path,
4
+ SelectorOf,
5
+ SelectorSource,
6
+ SourceOrExpr,
7
+ } from "./selector";
8
+ import {
9
+ isSerializedVal,
10
+ JsonOfSource,
11
+ SerializedVal,
12
+ SourcePath,
13
+ Val,
14
+ } from "./val";
15
+ import {
16
+ createValPathOfItem,
17
+ isSelector,
18
+ newSelectorProxy,
19
+ } from "./selector/SelectorProxy";
20
+ import { Json } from "./Json";
21
+
22
+ export function fetchVal<T extends SelectorSource>(
23
+ selector: T,
24
+ locale?: string
25
+ ): SelectorOf<T> extends GenericSelector<infer S>
26
+ ? Promise<Val<JsonOfSource<S>>>
27
+ : never {
28
+ return Promise.resolve(
29
+ getVal(selector, locale) as unknown
30
+ ) as SelectorOf<T> extends GenericSelector<infer S>
31
+ ? Promise<Val<JsonOfSource<S>>>
32
+ : never;
33
+ }
34
+
35
+ export function getVal<T extends SelectorSource>(
36
+ selector: T,
37
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
38
+ locale?: string
39
+ ): SelectorOf<T> extends GenericSelector<infer S>
40
+ ? Val<JsonOfSource<S>>
41
+ : never {
42
+ return newValProxy(
43
+ serializedValOfSelectorSource(selector)
44
+ ) as SelectorOf<T> extends GenericSelector<infer S>
45
+ ? Val<JsonOfSource<S>>
46
+ : never;
47
+ }
48
+
49
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
50
+ function isArrayOrArraySelector(child: any) {
51
+ if (isSelector(child)) {
52
+ return (
53
+ typeof child[SourceOrExpr] === "object" &&
54
+ typeof child[SourceOrExpr] !== null &&
55
+ Array.isArray(child[SourceOrExpr])
56
+ );
57
+ }
58
+ return Array.isArray(child);
59
+ }
60
+
61
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
62
+ function isObjectOrObjectSelector(child: any) {
63
+ if (isSelector(child)) {
64
+ return (
65
+ typeof child[SourceOrExpr] === "object" &&
66
+ typeof child[SourceOrExpr] !== null &&
67
+ !Array.isArray(child[SourceOrExpr])
68
+ );
69
+ }
70
+ return typeof child === "object";
71
+ }
72
+
73
+ export function serializedValOfSelectorSource<T extends SelectorSource>(
74
+ selector: T
75
+ ) {
76
+ const wrappedSelector = newSelectorProxy(selector); // NOTE: we do this if call-site uses a literal with selectors inside
77
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
78
+ function rec(child: any): any {
79
+ const isArray = isArrayOrArraySelector(child);
80
+ const isObject = isObjectOrObjectSelector(child);
81
+ if (isArray) {
82
+ const array = (
83
+ SourceOrExpr in child ? child[SourceOrExpr] : child
84
+ ) as Array<unknown>;
85
+ const valPath = Path in child ? (child[Path] as SourcePath) : undefined;
86
+ return {
87
+ val: array.map((item, i) =>
88
+ rec(
89
+ isSelector(item) // NOTE: We do this since selectors currently do not create selectors of items unless specifically required.
90
+ ? item
91
+ : newSelectorProxy(item, createValPathOfItem(valPath, i))
92
+ )
93
+ ),
94
+ valPath,
95
+ };
96
+ } else if (isObject) {
97
+ const obj = (
98
+ SourceOrExpr in child ? child[SourceOrExpr] : child
99
+ ) as object;
100
+ const valPath = Path in child ? (child[Path] as SourcePath) : undefined;
101
+ return {
102
+ val:
103
+ obj !== null &&
104
+ Object.fromEntries(
105
+ Object.entries(obj).map(([key, value]) => [
106
+ key,
107
+ rec(
108
+ isSelector(value) // NOTE: We do this since selectors currently do not create selectors of items unless specifically required.
109
+ ? value
110
+ : newSelectorProxy(value, createValPathOfItem(valPath, key))
111
+ ),
112
+ ])
113
+ ),
114
+ valPath,
115
+ };
116
+ } else if (isSelector(child)) {
117
+ return {
118
+ val: rec(child[SourceOrExpr]),
119
+ valPath: child[Path],
120
+ };
121
+ } else {
122
+ return child;
123
+ }
124
+ }
125
+
126
+ return rec(wrappedSelector);
127
+ }
128
+
129
+ function strip(value: SerializedVal | Json): Json {
130
+ const val = isSerializedVal(value) ? value.val : value;
131
+ switch (typeof val) {
132
+ case "function":
133
+ case "symbol":
134
+ throw Error(`Invalid val type: ${typeof val}`);
135
+ case "object":
136
+ if (val === null) {
137
+ return null;
138
+ } else if (Array.isArray(val)) {
139
+ return val.map(strip);
140
+ } else {
141
+ return Object.fromEntries(
142
+ Object.entries(val).map(([key, value]) => [
143
+ key,
144
+ value && strip(value),
145
+ ])
146
+ ) as Json;
147
+ }
148
+ // intentional fallthrough
149
+ // eslint-disable-next-line no-fallthrough
150
+ default:
151
+ return val;
152
+ }
153
+ }
154
+
155
+ function newValProxy<T extends Json>(val: SerializedVal): Val<T> {
156
+ const source = val.val;
157
+ switch (typeof source) {
158
+ case "function":
159
+ case "symbol":
160
+ throw Error(`Invalid val type: ${typeof source}`);
161
+ case "object":
162
+ if (source !== null) {
163
+ // Handles both objects and arrays!
164
+ return new Proxy(source, {
165
+ has(target, prop: string | symbol) {
166
+ if (prop === "val") {
167
+ return true;
168
+ }
169
+ if (prop === Path) {
170
+ return true;
171
+ }
172
+ return hasOwn(target, prop);
173
+ },
174
+ get(target, prop: string | symbol) {
175
+ if (prop === Path) {
176
+ return val.valPath;
177
+ }
178
+ if (prop === "val") {
179
+ return strip(val);
180
+ }
181
+ if (Array.isArray(target) && prop === "length") {
182
+ return target.length;
183
+ }
184
+ if (hasOwn(source, prop)) {
185
+ return newValProxy({
186
+ val: Reflect.get(target, prop).val,
187
+ valPath:
188
+ Reflect.get(target, prop)?.valPath ??
189
+ createValPathOfItem(
190
+ val.valPath,
191
+ Array.isArray(target) ? Number(prop) : prop
192
+ ),
193
+ } as SerializedVal);
194
+ }
195
+ return Reflect.get(target, prop);
196
+ },
197
+ }) as unknown as Val<T>;
198
+ }
199
+ // intentional fallthrough
200
+ // eslint-disable-next-line no-fallthrough
201
+ default:
202
+ return {
203
+ [Path]: val.valPath,
204
+ val: val.val,
205
+ } as Val<T>;
206
+ }
207
+ }
208
+
209
+ function hasOwn<T extends PropertyKey>(obj: object, prop: T): boolean {
210
+ return Object.prototype.hasOwnProperty.call(obj, prop);
211
+ }
@@ -0,0 +1,30 @@
1
+ export type NonEmptyArray<T> = [T, ...T[]];
2
+ export type ReadonlyNonEmptyArray<T> = readonly [T, ...T[]];
3
+
4
+ export function isNonEmpty<T>(array: Array<T>): array is NonEmptyArray<T>;
5
+ export function isNonEmpty<T>(
6
+ array: ReadonlyArray<T>
7
+ ): array is ReadonlyNonEmptyArray<T> {
8
+ return array.length > 0;
9
+ }
10
+
11
+ export function flatten<T>(
12
+ array: ReadonlyNonEmptyArray<ReadonlyNonEmptyArray<T>>
13
+ ): NonEmptyArray<T>;
14
+ export function flatten<T>(array: ReadonlyArray<ReadonlyArray<T>>): Array<T> {
15
+ return array.flat(1);
16
+ }
17
+
18
+ export function map<T, U>(
19
+ fn: (value: T, index: number) => U
20
+ ): {
21
+ (array: ReadonlyArray<T>): Array<U>;
22
+ (array: ReadonlyNonEmptyArray<T>): NonEmptyArray<U>;
23
+ } {
24
+ function mapFn(array: ReadonlyArray<T>): Array<U>;
25
+ function mapFn(array: ReadonlyNonEmptyArray<T>): NonEmptyArray<U>;
26
+ function mapFn(array: ReadonlyArray<T>): Array<U> {
27
+ return array.map(fn);
28
+ }
29
+ return mapFn;
30
+ }
@@ -0,0 +1,3 @@
1
+ export * as result from "./result";
2
+ export * as array from "./array";
3
+ export * from "./util";
@@ -0,0 +1,214 @@
1
+ import { isNonEmpty, NonEmptyArray } from "./array";
2
+
3
+ export type Ok<T> = {
4
+ readonly kind: "ok";
5
+ readonly value: T;
6
+ };
7
+ export type Err<E> = {
8
+ readonly kind: "err";
9
+ readonly error: E;
10
+ };
11
+
12
+ export type Result<T, E> = Ok<T> | Err<E>;
13
+
14
+ /**
15
+ * Singleton instance of Ok<void>. Used to optimize results whose Ok values are
16
+ * void.
17
+ */
18
+ export const voidOk: Ok<void> = Object.freeze({
19
+ kind: "ok",
20
+ value: undefined,
21
+ });
22
+
23
+ export function ok<T>(value: T): Ok<T> {
24
+ if (value === undefined) return voidOk as Ok<T>;
25
+ return {
26
+ kind: "ok",
27
+ value,
28
+ };
29
+ }
30
+
31
+ export function err<E>(error: E): Err<E> {
32
+ return {
33
+ kind: "err",
34
+ error,
35
+ };
36
+ }
37
+
38
+ export function isOk<T, E>(result: Result<T, E>): result is Ok<T> {
39
+ return result === voidOk || result.kind === "ok";
40
+ }
41
+
42
+ export function isErr<T, E>(result: Result<T, E>): result is Err<E> {
43
+ return result !== voidOk && result.kind === "err";
44
+ }
45
+
46
+ export type OkType<R> = R extends Result<infer T, unknown> ? T : never;
47
+ export type ErrType<R> = R extends Result<unknown, infer E> ? E : never;
48
+
49
+ /**
50
+ * If all results are Ok (or if results is empty), returns Ok with all the Ok
51
+ * values concatenated into an array. If any result is Err, returns Err with all
52
+ * Err values concatenated into an array.
53
+ *
54
+ * @see {@link all} for use with simple array types.
55
+ */
56
+ export function allT<T extends unknown[], E>(results: {
57
+ readonly [P in keyof T]: Result<T[P], E>;
58
+ }): Result<T, NonEmptyArray<E>> {
59
+ const values: T[number][] = [];
60
+ const errors: E[] = [];
61
+ for (const result of results) {
62
+ if (isOk(result)) {
63
+ values.push(result.value);
64
+ } else {
65
+ errors.push(result.error);
66
+ }
67
+ }
68
+ if (isNonEmpty(errors)) {
69
+ return err(errors);
70
+ } else {
71
+ return ok(values as T);
72
+ }
73
+ }
74
+
75
+ /**
76
+ * If all results are Ok (or if results is empty), returns Ok with all the Ok
77
+ * values concatenated into an array. If any result is Err, returns Err with all
78
+ * Err values concatenated into an array.
79
+ *
80
+ * @see {@link allT} for use with tuple types.
81
+ */
82
+ export function all<T, E>(
83
+ results: readonly Result<T, E>[]
84
+ ): Result<T[], NonEmptyArray<E>> {
85
+ return allT<T[], E>(results);
86
+ }
87
+
88
+ /**
89
+ * If all results are Ok (or if results is empty), returns Ok. If any result is
90
+ * Err, returns Err with all Err values concatenated into an array.
91
+ */
92
+ export function allV<E>(
93
+ results: readonly Result<unknown, E>[]
94
+ ): Result<void, NonEmptyArray<E>> {
95
+ const errs: E[] = [];
96
+ for (const result of results) {
97
+ if (isErr(result)) {
98
+ errs.push(result.error);
99
+ }
100
+ }
101
+ if (isNonEmpty(errs)) {
102
+ return err(errs);
103
+ }
104
+ return voidOk;
105
+ }
106
+
107
+ /**
108
+ * Perform a reduction over an array with a Result-returning reducer. If the
109
+ * reducer returns Ok, its value is used as the next value. If the reducer
110
+ * returns Err, it is returned immediately.
111
+ *
112
+ * flatMapReduce is a short-circuiting equivalent to:
113
+ * ```
114
+ * arr.reduce(
115
+ * (accRes, current, currentIndex) =>
116
+ * flatMap((acc) => reducer(acc, current, currentIndex))(accRes),
117
+ * ok(initVal)
118
+ * )
119
+ * ```
120
+ */
121
+ export function flatMapReduce<T, E, A>(
122
+ reducer: (acc: T, current: A, currentIndex: number) => Result<T, E>,
123
+ initVal: T
124
+ ): (arr: readonly A[]) => Result<T, E> {
125
+ return (arr) => {
126
+ let val: Result<T, E> = ok(initVal);
127
+ for (let i = 0; i < arr.length && isOk(val); ++i) {
128
+ val = reducer(val.value, arr[i], i);
129
+ }
130
+ return val;
131
+ };
132
+ }
133
+
134
+ export function map<T0, T1>(
135
+ onOk: (value: T0) => T1
136
+ ): <E>(result: Result<T0, E>) => Result<T1, E> {
137
+ return (result) => {
138
+ if (isOk(result)) {
139
+ return ok(onOk(result.value));
140
+ } else {
141
+ return result;
142
+ }
143
+ };
144
+ }
145
+
146
+ export function flatMap<T0, T1, E1>(
147
+ onOk: (value: T0) => Result<T1, E1>
148
+ ): <E>(result: Result<T0, E>) => Result<T1, E | E1> {
149
+ return (result) => {
150
+ if (isOk(result)) {
151
+ return onOk(result.value);
152
+ } else {
153
+ return result;
154
+ }
155
+ };
156
+ }
157
+
158
+ export function mapErr<E0, E1>(
159
+ onErr: (error: E0) => E1
160
+ ): <T>(result: Result<T, E0>) => Result<T, E1> {
161
+ return (result) => {
162
+ if (isErr(result)) {
163
+ return err(onErr(result.error));
164
+ } else {
165
+ return result;
166
+ }
167
+ };
168
+ }
169
+
170
+ export function fromPredicate<T0, T1 extends T0, E>(
171
+ refinement: (value: T0) => value is T1,
172
+ onFalse: (value: T0) => E
173
+ ): (value: T0) => Result<T1, E>;
174
+ export function fromPredicate<T0, E>(
175
+ refinement: (value: T0) => boolean,
176
+ onFalse: (value: T0) => E
177
+ ): <T1 extends T0>(value: T1) => Result<T1, E> {
178
+ return (value) => {
179
+ if (refinement(value)) {
180
+ return ok(value);
181
+ } else {
182
+ return err(onFalse(value));
183
+ }
184
+ };
185
+ }
186
+
187
+ // NOTE: Function overload resolution seems to fail when declared as overloaded
188
+ // function type, so a value with a callable type is used instead.
189
+ export const filterOrElse: {
190
+ <T0, T1 extends T0, E>(
191
+ refinement: (value: T0) => value is T1,
192
+ onFalse: (value: T0) => E
193
+ ): (result: Result<T0, E>) => Result<T1, E>;
194
+ <T0, E>(refinement: (value: T0) => boolean, onFalse: (value: T0) => E): <
195
+ T1 extends T0
196
+ >(
197
+ result: Result<T1, E>
198
+ ) => Result<T1, E>;
199
+ } = <T0, E>(
200
+ refinement: (value: T0) => boolean,
201
+ onFalse: (value: T0) => E
202
+ ): (<T1 extends T0>(result: Result<T1, E>) => Result<T1, E>) => {
203
+ return (result) => {
204
+ if (isOk(result)) {
205
+ if (refinement(result.value)) {
206
+ return result;
207
+ } else {
208
+ return err(onFalse(result.value));
209
+ }
210
+ } else {
211
+ return result;
212
+ }
213
+ };
214
+ };
package/src/fp/util.ts ADDED
@@ -0,0 +1,52 @@
1
+ export function pipe<A>(a: A): A;
2
+ export function pipe<A, B>(a: A, ab: (a: A) => B): B;
3
+ export function pipe<A, B, C>(a: A, ab: (a: A) => B, bc: (b: B) => C): C;
4
+ export function pipe<A, B, C, D>(
5
+ a: A,
6
+ ab: (a: A) => B,
7
+ bc: (b: B) => C,
8
+ cd: (c: C) => D
9
+ ): D;
10
+ export function pipe<A, B, C, D, E>(
11
+ a: A,
12
+ ab: (a: A) => B,
13
+ bc: (b: B) => C,
14
+ cd: (c: C) => D,
15
+ de: (d: D) => E
16
+ ): E;
17
+ export function pipe<A, B, C, D, E, F>(
18
+ a: A,
19
+ ab: (a: A) => B,
20
+ bc: (b: B) => C,
21
+ cd: (c: C) => D,
22
+ de: (d: D) => E,
23
+ ef: (e: E) => F
24
+ ): F;
25
+ // "Ought to be enough for everybody" -- attributed to E. Åmdal
26
+ export function pipe<A, B, C, D, E, F, G>(
27
+ a: A,
28
+ ab: (a: A) => B,
29
+ bc: (b: B) => C,
30
+ cd: (c: C) => D,
31
+ de: (d: D) => E,
32
+ ef: (e: E) => F,
33
+ fg: (f: F) => G
34
+ ): G;
35
+ export function pipe(a: unknown, ...fns: ((u: unknown) => unknown)[]): unknown {
36
+ let current = a;
37
+ for (const fn of fns) {
38
+ current = fn(current);
39
+ }
40
+ return current;
41
+ }
42
+
43
+ /**
44
+ * Runs the callback with the supplied value, then returns the value. Useful for
45
+ * debugging pipes.
46
+ */
47
+ export function tap<T>(callback: (value: T) => void): (value: T) => T {
48
+ return (value) => {
49
+ callback(value);
50
+ return value;
51
+ };
52
+ }