@player-tools/fluent 0.12.1--canary.241.6077
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/dist/cjs/index.cjs +2396 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/index.legacy-esm.js +2276 -0
- package/dist/index.mjs +2276 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +38 -0
- package/src/core/base-builder/__tests__/fluent-builder-base.test.ts +2423 -0
- package/src/core/base-builder/__tests__/fluent-partial.test.ts +179 -0
- package/src/core/base-builder/__tests__/id-generator.test.ts +658 -0
- package/src/core/base-builder/__tests__/registry.test.ts +534 -0
- package/src/core/base-builder/__tests__/resolution-mixed-arrays.test.ts +319 -0
- package/src/core/base-builder/__tests__/resolution-pipeline.test.ts +416 -0
- package/src/core/base-builder/__tests__/resolution-switches.test.ts +468 -0
- package/src/core/base-builder/__tests__/resolution-templates.test.ts +255 -0
- package/src/core/base-builder/__tests__/switch.test.ts +815 -0
- package/src/core/base-builder/__tests__/template.test.ts +596 -0
- package/src/core/base-builder/__tests__/value-extraction.test.ts +200 -0
- package/src/core/base-builder/__tests__/value-storage.test.ts +459 -0
- package/src/core/base-builder/conditional/index.ts +64 -0
- package/src/core/base-builder/context.ts +152 -0
- package/src/core/base-builder/errors.ts +69 -0
- package/src/core/base-builder/fluent-builder-base.ts +308 -0
- package/src/core/base-builder/guards.ts +137 -0
- package/src/core/base-builder/id/generator.ts +290 -0
- package/src/core/base-builder/id/registry.ts +152 -0
- package/src/core/base-builder/index.ts +72 -0
- package/src/core/base-builder/resolution/path-resolver.ts +116 -0
- package/src/core/base-builder/resolution/pipeline.ts +103 -0
- package/src/core/base-builder/resolution/steps/__tests__/nested-asset-wrappers.test.ts +206 -0
- package/src/core/base-builder/resolution/steps/asset-id.ts +77 -0
- package/src/core/base-builder/resolution/steps/asset-wrappers.ts +64 -0
- package/src/core/base-builder/resolution/steps/builders.ts +84 -0
- package/src/core/base-builder/resolution/steps/mixed-arrays.ts +95 -0
- package/src/core/base-builder/resolution/steps/nested-asset-wrappers.ts +124 -0
- package/src/core/base-builder/resolution/steps/static-values.ts +35 -0
- package/src/core/base-builder/resolution/steps/switches.ts +71 -0
- package/src/core/base-builder/resolution/steps/templates.ts +40 -0
- package/src/core/base-builder/resolution/value-resolver.ts +333 -0
- package/src/core/base-builder/storage/auxiliary-storage.ts +82 -0
- package/src/core/base-builder/storage/value-storage.ts +282 -0
- package/src/core/base-builder/types.ts +266 -0
- package/src/core/base-builder/utils.ts +10 -0
- package/src/core/flow/__tests__/index.test.ts +292 -0
- package/src/core/flow/index.ts +118 -0
- package/src/core/index.ts +8 -0
- package/src/core/mocks/generated/action.builder.ts +92 -0
- package/src/core/mocks/generated/choice-item.builder.ts +120 -0
- package/src/core/mocks/generated/choice.builder.ts +134 -0
- package/src/core/mocks/generated/collection.builder.ts +93 -0
- package/src/core/mocks/generated/field-collection.builder.ts +86 -0
- package/src/core/mocks/generated/index.ts +10 -0
- package/src/core/mocks/generated/info.builder.ts +64 -0
- package/src/core/mocks/generated/input.builder.ts +63 -0
- package/src/core/mocks/generated/overview-collection.builder.ts +65 -0
- package/src/core/mocks/generated/splash-collection.builder.ts +93 -0
- package/src/core/mocks/generated/text.builder.ts +47 -0
- package/src/core/mocks/index.ts +1 -0
- package/src/core/mocks/types/action.ts +92 -0
- package/src/core/mocks/types/choice.ts +129 -0
- package/src/core/mocks/types/collection.ts +140 -0
- package/src/core/mocks/types/info.ts +7 -0
- package/src/core/mocks/types/input.ts +7 -0
- package/src/core/mocks/types/text.ts +5 -0
- package/src/core/schema/__tests__/index.test.ts +127 -0
- package/src/core/schema/index.ts +195 -0
- package/src/core/schema/types.ts +7 -0
- package/src/core/switch/__tests__/index.test.ts +156 -0
- package/src/core/switch/index.ts +81 -0
- package/src/core/tagged-template/README.md +448 -0
- package/src/core/tagged-template/__tests__/extract-bindings-from-schema.test.ts +207 -0
- package/src/core/tagged-template/__tests__/index.test.ts +190 -0
- package/src/core/tagged-template/__tests__/schema-std-integration.test.ts +580 -0
- package/src/core/tagged-template/binding.ts +95 -0
- package/src/core/tagged-template/expression.ts +92 -0
- package/src/core/tagged-template/extract-bindings-from-schema.ts +120 -0
- package/src/core/tagged-template/index.ts +5 -0
- package/src/core/tagged-template/std.ts +472 -0
- package/src/core/tagged-template/types.ts +123 -0
- package/src/core/template/__tests__/index.test.ts +380 -0
- package/src/core/template/index.ts +196 -0
- package/src/core/utils/index.ts +160 -0
- package/src/fp/README.md +411 -0
- package/src/fp/__tests__/index.test.ts +1178 -0
- package/src/fp/index.ts +386 -0
- package/src/gen/common.ts +15 -0
- package/src/index.ts +5 -0
- package/src/types.ts +203 -0
- package/types/core/base-builder/conditional/index.d.ts +21 -0
- package/types/core/base-builder/context.d.ts +39 -0
- package/types/core/base-builder/errors.d.ts +45 -0
- package/types/core/base-builder/fluent-builder-base.d.ts +147 -0
- package/types/core/base-builder/guards.d.ts +58 -0
- package/types/core/base-builder/id/generator.d.ts +69 -0
- package/types/core/base-builder/id/registry.d.ts +93 -0
- package/types/core/base-builder/index.d.ts +9 -0
- package/types/core/base-builder/resolution/path-resolver.d.ts +15 -0
- package/types/core/base-builder/resolution/pipeline.d.ts +27 -0
- package/types/core/base-builder/resolution/steps/asset-id.d.ts +14 -0
- package/types/core/base-builder/resolution/steps/asset-wrappers.d.ts +14 -0
- package/types/core/base-builder/resolution/steps/builders.d.ts +14 -0
- package/types/core/base-builder/resolution/steps/mixed-arrays.d.ts +14 -0
- package/types/core/base-builder/resolution/steps/nested-asset-wrappers.d.ts +14 -0
- package/types/core/base-builder/resolution/steps/static-values.d.ts +14 -0
- package/types/core/base-builder/resolution/steps/switches.d.ts +15 -0
- package/types/core/base-builder/resolution/steps/templates.d.ts +14 -0
- package/types/core/base-builder/resolution/value-resolver.d.ts +62 -0
- package/types/core/base-builder/storage/auxiliary-storage.d.ts +50 -0
- package/types/core/base-builder/storage/value-storage.d.ts +82 -0
- package/types/core/base-builder/types.d.ts +183 -0
- package/types/core/base-builder/utils.d.ts +2 -0
- package/types/core/flow/index.d.ts +23 -0
- package/types/core/index.d.ts +8 -0
- package/types/core/mocks/index.d.ts +2 -0
- package/types/core/mocks/types/action.d.ts +58 -0
- package/types/core/mocks/types/choice.d.ts +95 -0
- package/types/core/mocks/types/collection.d.ts +102 -0
- package/types/core/mocks/types/info.d.ts +7 -0
- package/types/core/mocks/types/input.d.ts +7 -0
- package/types/core/mocks/types/text.d.ts +5 -0
- package/types/core/schema/index.d.ts +34 -0
- package/types/core/schema/types.d.ts +5 -0
- package/types/core/switch/index.d.ts +21 -0
- package/types/core/tagged-template/binding.d.ts +19 -0
- package/types/core/tagged-template/expression.d.ts +11 -0
- package/types/core/tagged-template/extract-bindings-from-schema.d.ts +7 -0
- package/types/core/tagged-template/index.d.ts +6 -0
- package/types/core/tagged-template/std.d.ts +174 -0
- package/types/core/tagged-template/types.d.ts +69 -0
- package/types/core/template/index.d.ts +97 -0
- package/types/core/utils/index.d.ts +47 -0
- package/types/fp/index.d.ts +149 -0
- package/types/gen/common.d.ts +6 -0
- package/types/index.d.ts +3 -0
- package/types/types.d.ts +163 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { isTaggedTemplateValue, TaggedTemplateValue } from "../tagged-template";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Type that can be either a direct value T or a TaggedTemplateValue
|
|
5
|
+
*/
|
|
6
|
+
export type TaggedOr<T> = T | TaggedTemplateValue;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Safely extract a string if TaggedTemplateValue is present
|
|
10
|
+
*/
|
|
11
|
+
export function safeToString<T extends TaggedOr<string | unknown>>(
|
|
12
|
+
value: T,
|
|
13
|
+
): string {
|
|
14
|
+
if (isTaggedTemplateValue(value)) {
|
|
15
|
+
return value.toString();
|
|
16
|
+
}
|
|
17
|
+
return String(value);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Safely extract a boolean if TaggedTemplateValue is present
|
|
22
|
+
*/
|
|
23
|
+
export function safeToBoolean<T extends TaggedOr<boolean | unknown>>(
|
|
24
|
+
value: T,
|
|
25
|
+
): boolean {
|
|
26
|
+
if (isTaggedTemplateValue(value)) {
|
|
27
|
+
return value.toString() === "true";
|
|
28
|
+
}
|
|
29
|
+
return Boolean(value);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Safely extract a number if TaggedTemplateValue is present
|
|
34
|
+
*/
|
|
35
|
+
export function safeToNumber<T extends TaggedOr<number | unknown>>(
|
|
36
|
+
value: T,
|
|
37
|
+
): number {
|
|
38
|
+
if (isTaggedTemplateValue(value)) {
|
|
39
|
+
return Number(value.toString());
|
|
40
|
+
}
|
|
41
|
+
return Number(value);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Type for an item that could be in an array with TaggedTemplate values
|
|
46
|
+
*/
|
|
47
|
+
export type ArrayItem<T> = T extends (infer U)[] ? U : T;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Safely extract an array of values if TaggedTemplateValue is present
|
|
51
|
+
* Preserves element types when possible and handles nested arrays recursively
|
|
52
|
+
*/
|
|
53
|
+
export function safeToArray<T extends unknown[] | unknown>(
|
|
54
|
+
value: TaggedOr<T>,
|
|
55
|
+
): Array<DeepUnwrapTagged<ArrayItem<T>>> {
|
|
56
|
+
if (isTaggedTemplateValue(value)) {
|
|
57
|
+
return [value.toString()] as Array<DeepUnwrapTagged<ArrayItem<T>>>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (Array.isArray(value)) {
|
|
61
|
+
return value.map((item) => {
|
|
62
|
+
if (isTaggedTemplateValue(item)) {
|
|
63
|
+
return item.toString();
|
|
64
|
+
} else if (Array.isArray(item)) {
|
|
65
|
+
return safeToArray(item);
|
|
66
|
+
} else if (
|
|
67
|
+
item &&
|
|
68
|
+
typeof item === "object" &&
|
|
69
|
+
!isTaggedTemplateValue(item)
|
|
70
|
+
) {
|
|
71
|
+
return safeToObject(item as Record<string, unknown>);
|
|
72
|
+
}
|
|
73
|
+
return item;
|
|
74
|
+
}) as Array<DeepUnwrapTagged<ArrayItem<T>>>;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return [value] as Array<DeepUnwrapTagged<ArrayItem<T>>>;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Recursively transforms a type by replacing TaggedTemplateValue with string
|
|
82
|
+
* and handling unions that contain TaggedTemplateValue
|
|
83
|
+
*/
|
|
84
|
+
export type DeepUnwrapTagged<T> =
|
|
85
|
+
// If T is exactly TaggedTemplateValue, convert to string
|
|
86
|
+
T extends TaggedTemplateValue
|
|
87
|
+
? T extends string // Check if TaggedTemplateValue also extends string to avoid conflicts
|
|
88
|
+
? string
|
|
89
|
+
: string
|
|
90
|
+
: // If T is a union that includes TaggedTemplateValue, remove TaggedTemplateValue from the union
|
|
91
|
+
TaggedTemplateValue extends T
|
|
92
|
+
? T extends TaggedTemplateValue
|
|
93
|
+
? string // T is exactly TaggedTemplateValue
|
|
94
|
+
: Exclude<T, TaggedTemplateValue> // T is a union containing TaggedTemplateValue - remove it
|
|
95
|
+
: // Handle arrays
|
|
96
|
+
T extends Array<infer U>
|
|
97
|
+
? Array<DeepUnwrapTagged<U>>
|
|
98
|
+
: // Handle records/objects
|
|
99
|
+
T extends Record<string, unknown>
|
|
100
|
+
? { [K in keyof T]: DeepUnwrapTagged<T[K]> }
|
|
101
|
+
: // Default case - return as is
|
|
102
|
+
T;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Safely extract an object if TaggedTemplateValue is present
|
|
106
|
+
* Recursively handles nested TaggedTemplateValues
|
|
107
|
+
*/
|
|
108
|
+
export function safeToObject<T extends Record<string, unknown>>(
|
|
109
|
+
value: T,
|
|
110
|
+
): DeepUnwrapTagged<T> {
|
|
111
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
112
|
+
return value as DeepUnwrapTagged<T>;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return Object.fromEntries(
|
|
116
|
+
Object.entries(value).map(([key, val]) => {
|
|
117
|
+
if (isTaggedTemplateValue(val)) {
|
|
118
|
+
return [key, val.toString()];
|
|
119
|
+
} else if (Array.isArray(val)) {
|
|
120
|
+
return [key, safeToArray(val)];
|
|
121
|
+
} else if (
|
|
122
|
+
val &&
|
|
123
|
+
typeof val === "object" &&
|
|
124
|
+
!isTaggedTemplateValue(val)
|
|
125
|
+
) {
|
|
126
|
+
return [key, safeToObject(val as Record<string, unknown>)];
|
|
127
|
+
}
|
|
128
|
+
return [key, val];
|
|
129
|
+
}),
|
|
130
|
+
) as DeepUnwrapTagged<T>;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Processes a value that could be a string, TaggedTemplateValue, or
|
|
135
|
+
* a complex object with nested TaggedTemplateValue instances
|
|
136
|
+
*
|
|
137
|
+
* This is useful for handling complex union types like:
|
|
138
|
+
* string | TaggedTemplateValue | Record<string, string | TaggedTemplateValue>
|
|
139
|
+
*/
|
|
140
|
+
export function safeFromMixedType<T>(value: unknown): DeepUnwrapTagged<T> {
|
|
141
|
+
// Handle TaggedTemplateValue directly
|
|
142
|
+
if (isTaggedTemplateValue(value)) {
|
|
143
|
+
return value.toString() as DeepUnwrapTagged<T>;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Handle objects (including records)
|
|
147
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
148
|
+
return safeToObject(
|
|
149
|
+
value as Record<string, unknown>,
|
|
150
|
+
) as DeepUnwrapTagged<T>;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Handle arrays
|
|
154
|
+
if (Array.isArray(value)) {
|
|
155
|
+
return safeToArray(value) as unknown as DeepUnwrapTagged<T>;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Handle primitives
|
|
159
|
+
return value as DeepUnwrapTagged<T>;
|
|
160
|
+
}
|
package/src/fp/README.md
ADDED
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
# Functional Programming Utilities
|
|
2
|
+
|
|
3
|
+
This module provides a collection of functional programming utilities for handling data transformations and error management in a type-safe way. It includes function composition utilities and a Result type system for elegant error handling.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Function Composition](#function-composition)
|
|
8
|
+
- [Result Type System](#result-type-system)
|
|
9
|
+
- [API Reference](#api-reference)
|
|
10
|
+
- [Examples](#examples)
|
|
11
|
+
- [Best Practices](#best-practices)
|
|
12
|
+
|
|
13
|
+
## Function Composition
|
|
14
|
+
|
|
15
|
+
### `pipe`
|
|
16
|
+
|
|
17
|
+
The `pipe` function allows you to compose functions in a readable, left-to-right manner. It takes an initial value and applies a sequence of functions to it.
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { pipe } from "./fp";
|
|
21
|
+
|
|
22
|
+
// Basic usage
|
|
23
|
+
const result = pipe(
|
|
24
|
+
5,
|
|
25
|
+
(x) => x + 1, // 6
|
|
26
|
+
(x) => x * 2, // 12
|
|
27
|
+
(x) => x.toString(), // "12"
|
|
28
|
+
);
|
|
29
|
+
console.log(result); // "12"
|
|
30
|
+
|
|
31
|
+
// Complex data transformation
|
|
32
|
+
const processUser = (userData: any) =>
|
|
33
|
+
pipe(
|
|
34
|
+
userData,
|
|
35
|
+
(data) => ({ ...data, id: Math.random() }),
|
|
36
|
+
(user) => ({ ...user, name: user.name.trim() }),
|
|
37
|
+
(user) => ({ ...user, email: user.email.toLowerCase() }),
|
|
38
|
+
(user) => ({ ...user, createdAt: new Date() }),
|
|
39
|
+
);
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The `pipe` function supports up to 7 functions and maintains type safety throughout the chain.
|
|
43
|
+
|
|
44
|
+
## Result Type System
|
|
45
|
+
|
|
46
|
+
The Result type system provides a way to handle operations that might fail without throwing exceptions. It's inspired by functional programming languages like Rust and Haskell.
|
|
47
|
+
|
|
48
|
+
### Core Types
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
type Result<T, E = Error> = Success<T> | Failure<E>;
|
|
52
|
+
|
|
53
|
+
interface Success<T> {
|
|
54
|
+
success: true;
|
|
55
|
+
value: T;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
interface Failure<E = Error> {
|
|
59
|
+
success: false;
|
|
60
|
+
error: E;
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Creating Results
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import { success, failure } from "./fp";
|
|
68
|
+
|
|
69
|
+
// Create a successful result
|
|
70
|
+
const successResult = success(42);
|
|
71
|
+
// { success: true, value: 42 }
|
|
72
|
+
|
|
73
|
+
// Create a failure result
|
|
74
|
+
const failureResult = failure(new Error("Something went wrong"));
|
|
75
|
+
// { success: false, error: Error("Something went wrong") }
|
|
76
|
+
|
|
77
|
+
// Custom error types
|
|
78
|
+
const customFailure = failure({ code: 404, message: "Not found" });
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Type Guards
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
import { isSuccess, isFailure } from "./fp";
|
|
85
|
+
|
|
86
|
+
const result: Result<number, string> = success(42);
|
|
87
|
+
|
|
88
|
+
if (isSuccess(result)) {
|
|
89
|
+
// TypeScript knows this is Success<number>
|
|
90
|
+
console.log(result.value); // 42
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (isFailure(result)) {
|
|
94
|
+
// TypeScript knows this is Failure<string>
|
|
95
|
+
console.log(result.error); // string
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## API Reference
|
|
100
|
+
|
|
101
|
+
### Function Composition
|
|
102
|
+
|
|
103
|
+
#### `pipe(initialValue, ...functions)`
|
|
104
|
+
|
|
105
|
+
Applies a sequence of functions to an initial value.
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
pipe(value, fn1, fn2, fn3, ...); // fn3(fn2(fn1(value)))
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Result Creation
|
|
112
|
+
|
|
113
|
+
#### `success<T>(value: T): Success<T>`
|
|
114
|
+
|
|
115
|
+
Creates a successful result containing the given value.
|
|
116
|
+
|
|
117
|
+
#### `failure<E>(error: E): Failure<E>`
|
|
118
|
+
|
|
119
|
+
Creates a failure result containing the given error.
|
|
120
|
+
|
|
121
|
+
### Type Guards
|
|
122
|
+
|
|
123
|
+
#### `isSuccess<T, E>(result: Result<T, E>): result is Success<T>`
|
|
124
|
+
|
|
125
|
+
Type guard that checks if a result is successful.
|
|
126
|
+
|
|
127
|
+
#### `isFailure<T, E>(result: Result<T, E>): result is Failure<E>`
|
|
128
|
+
|
|
129
|
+
Type guard that checks if a result is a failure.
|
|
130
|
+
|
|
131
|
+
### Result Transformations
|
|
132
|
+
|
|
133
|
+
#### `map<T, U, E>(result: Result<T, E>, fn: (value: T) => U): Result<U, E>`
|
|
134
|
+
|
|
135
|
+
Transforms the value inside a successful result. Does nothing for failures.
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
const result = success(5);
|
|
139
|
+
const doubled = map(result, (x) => x * 2);
|
|
140
|
+
// Success { value: 10 }
|
|
141
|
+
|
|
142
|
+
const failed = failure("error");
|
|
143
|
+
const stillFailed = map(failed, (x) => x * 2);
|
|
144
|
+
// Failure { error: "error" }
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
#### `flatMap<T, U, E, F>(result: Result<T, E>, fn: (value: T) => Result<U, F>): Result<U, E | F>`
|
|
148
|
+
|
|
149
|
+
Chains results together. Useful for operations that might fail.
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
const parseNumber = (str: string): Result<number, string> => {
|
|
153
|
+
const num = parseInt(str, 10);
|
|
154
|
+
return isNaN(num) ? failure("Invalid number") : success(num);
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const result = success("42");
|
|
158
|
+
const parsed = flatMap(result, parseNumber);
|
|
159
|
+
// Success { value: 42 }
|
|
160
|
+
|
|
161
|
+
const invalid = success("not-a-number");
|
|
162
|
+
const failed = flatMap(invalid, parseNumber);
|
|
163
|
+
// Failure { error: "Invalid number" }
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
#### `recover<T, E>(result: Result<T, E>, fn: (error: E) => T): Success<T>`
|
|
167
|
+
|
|
168
|
+
Recovers from a failure by providing a default value or transformation.
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
const failed = failure("Network error");
|
|
172
|
+
const recovered = recover(failed, (error) => `Default: ${error}`);
|
|
173
|
+
// Success { value: "Default: Network error" }
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Value Extraction
|
|
177
|
+
|
|
178
|
+
#### `getOrThrow<T, E extends Error>(result: Result<T, E>): T`
|
|
179
|
+
|
|
180
|
+
Extracts the value from a successful result or throws the error.
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
const success = success(42);
|
|
184
|
+
const value = getOrThrow(success); // 42
|
|
185
|
+
|
|
186
|
+
const failed = failure(new Error("Oops"));
|
|
187
|
+
const value2 = getOrThrow(failed); // throws Error("Oops")
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
#### `getOrElse<T, E>(result: Result<T, E>, defaultValue: T): T`
|
|
191
|
+
|
|
192
|
+
Extracts the value from a successful result or returns a default value.
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
const success = success(42);
|
|
196
|
+
const value = getOrElse(success, 0); // 42
|
|
197
|
+
|
|
198
|
+
const failed = failure("error");
|
|
199
|
+
const value2 = getOrElse(failed, 0); // 0
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Utility Functions
|
|
203
|
+
|
|
204
|
+
#### `tryResult<T>(fn: () => T): Result<T, Error>`
|
|
205
|
+
|
|
206
|
+
Wraps a function that might throw in a Result.
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
const safeParseJson = (str: string) => tryResult(() => JSON.parse(str));
|
|
210
|
+
|
|
211
|
+
const valid = safeParseJson('{"name": "John"}');
|
|
212
|
+
// Success { value: { name: "John" } }
|
|
213
|
+
|
|
214
|
+
const invalid = safeParseJson("invalid json");
|
|
215
|
+
// Failure { error: SyntaxError(...) }
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
#### `match<T, E, U>(result: Result<T, E>, onSuccess: (value: T) => U, onFailure: (error: E) => U): U`
|
|
219
|
+
|
|
220
|
+
Pattern matching for results. Always returns a value.
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
const result = success(42);
|
|
224
|
+
const message = match(
|
|
225
|
+
result,
|
|
226
|
+
(value) => `Success: ${value}`,
|
|
227
|
+
(error) => `Error: ${error}`,
|
|
228
|
+
);
|
|
229
|
+
// "Success: 42"
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Examples
|
|
233
|
+
|
|
234
|
+
### Basic Error Handling
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
import { success, failure, map, flatMap, getOrElse } from "./fp";
|
|
238
|
+
|
|
239
|
+
// Simulate API calls that might fail
|
|
240
|
+
const fetchUser = (id: number): Result<User, string> => {
|
|
241
|
+
if (id <= 0) return failure("Invalid user ID");
|
|
242
|
+
return success({ id, name: "John Doe", email: "john@example.com" });
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
const validateEmail = (user: User): Result<User, string> => {
|
|
246
|
+
if (!user.email.includes("@")) return failure("Invalid email");
|
|
247
|
+
return success(user);
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
// Chain operations
|
|
251
|
+
const result = pipe(
|
|
252
|
+
1,
|
|
253
|
+
fetchUser,
|
|
254
|
+
(res) => flatMap(res, validateEmail),
|
|
255
|
+
(res) => map(res, (user) => ({ ...user, validated: true })),
|
|
256
|
+
(res) =>
|
|
257
|
+
getOrElse(res, { id: 0, name: "Unknown", email: "", validated: false }),
|
|
258
|
+
);
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Data Processing Pipeline
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
import { pipe, tryResult, map, flatMap, recover } from "./fp";
|
|
265
|
+
|
|
266
|
+
const processData = (input: string) =>
|
|
267
|
+
pipe(
|
|
268
|
+
input,
|
|
269
|
+
// Parse JSON safely
|
|
270
|
+
(str) => tryResult(() => JSON.parse(str)),
|
|
271
|
+
// Validate structure
|
|
272
|
+
(res) =>
|
|
273
|
+
flatMap(res, (data) =>
|
|
274
|
+
typeof data === "object" && data.items
|
|
275
|
+
? success(data)
|
|
276
|
+
: failure("Invalid data structure"),
|
|
277
|
+
),
|
|
278
|
+
// Transform items
|
|
279
|
+
(res) =>
|
|
280
|
+
map(res, (data) => ({
|
|
281
|
+
...data,
|
|
282
|
+
items: data.items.map((item: any) => ({ ...item, processed: true })),
|
|
283
|
+
})),
|
|
284
|
+
// Recover from errors with default
|
|
285
|
+
(res) =>
|
|
286
|
+
recover(res, (error) => ({
|
|
287
|
+
items: [],
|
|
288
|
+
error: error.message || String(error),
|
|
289
|
+
})),
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
const result = processData('{"items": [{"name": "test"}]}');
|
|
293
|
+
// Success with processed data
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Combining with Async Operations
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
import { success, failure, flatMap, map } from "./fp";
|
|
300
|
+
|
|
301
|
+
// Wrap async operations in Results
|
|
302
|
+
const asyncFetchUser = async (id: number): Promise<Result<User, string>> => {
|
|
303
|
+
try {
|
|
304
|
+
const response = await fetch(`/api/users/${id}`);
|
|
305
|
+
if (!response.ok) return failure(`HTTP ${response.status}`);
|
|
306
|
+
const user = await response.json();
|
|
307
|
+
return success(user);
|
|
308
|
+
} catch (error) {
|
|
309
|
+
return failure(error.message);
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
// Use with async/await
|
|
314
|
+
const processUser = async (id: number) => {
|
|
315
|
+
const userResult = await asyncFetchUser(id);
|
|
316
|
+
|
|
317
|
+
return pipe(
|
|
318
|
+
userResult,
|
|
319
|
+
(res) => map(res, (user) => ({ ...user, lastSeen: new Date() })),
|
|
320
|
+
(res) =>
|
|
321
|
+
flatMap(res, (user) =>
|
|
322
|
+
user.active ? success(user) : failure("User is inactive"),
|
|
323
|
+
),
|
|
324
|
+
);
|
|
325
|
+
};
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
## Best Practices
|
|
329
|
+
|
|
330
|
+
### 1. Use Descriptive Error Types
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
// Good: Specific error types
|
|
334
|
+
type ValidationError = "INVALID_EMAIL" | "MISSING_NAME" | "INVALID_AGE";
|
|
335
|
+
const validateUser = (data: any): Result<User, ValidationError> => {
|
|
336
|
+
// ...
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
// Avoid: Generic string errors
|
|
340
|
+
const validateUser = (data: any): Result<User, string> => {
|
|
341
|
+
// ...
|
|
342
|
+
};
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### 2. Chain Operations with flatMap
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
// Good: Chain operations that might fail
|
|
349
|
+
const result = pipe(
|
|
350
|
+
input,
|
|
351
|
+
parseJson,
|
|
352
|
+
(res) => flatMap(res, validateSchema),
|
|
353
|
+
(res) => flatMap(res, transformData),
|
|
354
|
+
(res) => map(res, finalTransform),
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
// Avoid: Nested if statements
|
|
358
|
+
if (isSuccess(parsed)) {
|
|
359
|
+
const validated = validateSchema(parsed.value);
|
|
360
|
+
if (isSuccess(validated)) {
|
|
361
|
+
// ...
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### 3. Use recover for Default Values
|
|
367
|
+
|
|
368
|
+
```typescript
|
|
369
|
+
// Good: Provide meaningful defaults
|
|
370
|
+
const config = pipe(configFile, parseConfig, (res) =>
|
|
371
|
+
recover(res, () => DEFAULT_CONFIG),
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
// Good: Transform errors into user-friendly messages
|
|
375
|
+
const userMessage = pipe(operation, (res) =>
|
|
376
|
+
recover(res, (error) => `Operation failed: ${error}`),
|
|
377
|
+
);
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### 4. Prefer match for Exhaustive Handling
|
|
381
|
+
|
|
382
|
+
```typescript
|
|
383
|
+
// Good: Handle both cases explicitly
|
|
384
|
+
const message = match(
|
|
385
|
+
result,
|
|
386
|
+
(user) => `Welcome, ${user.name}!`,
|
|
387
|
+
(error) => `Login failed: ${error}`,
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
// Avoid: Only handling success case
|
|
391
|
+
if (isSuccess(result)) {
|
|
392
|
+
console.log(`Welcome, ${result.value.name}!`);
|
|
393
|
+
}
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### 5. Use tryResult for Exception-Prone Code
|
|
397
|
+
|
|
398
|
+
```typescript
|
|
399
|
+
// Good: Wrap risky operations
|
|
400
|
+
const safeOperation = (data: string) =>
|
|
401
|
+
tryResult(() => {
|
|
402
|
+
return JSON.parse(data).someProperty.deepValue;
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
// Avoid: Letting exceptions bubble up
|
|
406
|
+
const riskyOperation = (data: string) => {
|
|
407
|
+
return JSON.parse(data).someProperty.deepValue; // Might throw
|
|
408
|
+
};
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
This functional programming module provides a robust foundation for handling data transformations and errors in a type-safe, composable way. The Result type system eliminates the need for exception handling in many cases and makes error states explicit in your type signatures.
|