wellcrafted 0.15.0

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/index.js ADDED
@@ -0,0 +1,385 @@
1
+ //#region src/result.ts
2
+ /**
3
+ * Constructs an `Ok<T>` variant, representing a successful outcome.
4
+ *
5
+ * This factory function creates the success variant of a `Result`.
6
+ * It wraps the provided `data` (the success value) and ensures the `error` property is `null`.
7
+ *
8
+ * @template T - The type of the success value.
9
+ * @param data - The success value to be wrapped in the `Ok` variant.
10
+ * @returns An `Ok<T>` object with the provided data and `error` set to `null`.
11
+ * @example
12
+ * ```ts
13
+ * const successfulResult = Ok("Operation completed successfully");
14
+ * // successfulResult is { data: "Operation completed successfully", error: null }
15
+ * ```
16
+ */
17
+ const Ok = (data) => ({
18
+ data,
19
+ error: null
20
+ });
21
+ /**
22
+ * Constructs an `Err<E>` variant, representing a failure outcome.
23
+ *
24
+ * This factory function creates the error variant of a `Result`.
25
+ * It wraps the provided `error` (the error value) and ensures the `data` property is `null`.
26
+ *
27
+ * @template E - The type of the error value.
28
+ * @param error - The error value to be wrapped in the `Err` variant. This value represents the specific error that occurred.
29
+ * @returns An `Err<E>` object with the provided error and `data` set to `null`.
30
+ * @example
31
+ * ```ts
32
+ * const failedResult = Err(new TypeError("Invalid input"));
33
+ * // failedResult is { error: TypeError("Invalid input"), data: null }
34
+ * ```
35
+ */
36
+ const Err = (error) => ({
37
+ error,
38
+ data: null
39
+ });
40
+ /**
41
+ * Type guard to runtime check if an unknown value is a valid `Result<T, E>`.
42
+ *
43
+ * A value is considered a valid `Result` if:
44
+ * 1. It is a non-null object.
45
+ * 2. It has both `data` and `error` properties.
46
+ * 3. Exactly one of `data` or `error` is `null`. The other must be non-`null`.
47
+ *
48
+ * This function does not validate the types of `data` or `error` beyond `null` checks.
49
+ *
50
+ * @template T - The expected type of the success value if the value is an `Ok` variant (defaults to `unknown`).
51
+ * @template E - The expected type of the error value if the value is an `Err` variant (defaults to `unknown`).
52
+ * @param value - The value to check.
53
+ * @returns `true` if the value conforms to the `Result` structure, `false` otherwise.
54
+ * If `true`, TypeScript's type system will narrow `value` to `Result<T, E>`.
55
+ * @example
56
+ * ```ts
57
+ * declare const someValue: unknown;
58
+ *
59
+ * if (isResult<string, Error>(someValue)) {
60
+ * // someValue is now typed as Result<string, Error>
61
+ * if (isOk(someValue)) {
62
+ * console.log(someValue.data); // string
63
+ * } else {
64
+ * console.error(someValue.error); // Error
65
+ * }
66
+ * }
67
+ * ```
68
+ */
69
+ function isResult(value) {
70
+ const isNonNullObject = typeof value === "object" && value !== null;
71
+ if (!isNonNullObject) return false;
72
+ const hasDataProperty = "data" in value;
73
+ const hasErrorProperty = "error" in value;
74
+ if (!hasDataProperty || !hasErrorProperty) return false;
75
+ const isBothNull = value.data === null && value.error === null;
76
+ if (isBothNull) return false;
77
+ const isNeitherNull = value.data !== null && value.error !== null;
78
+ if (isNeitherNull) return false;
79
+ return true;
80
+ }
81
+ /**
82
+ * Type guard to runtime check if a `Result<T, E>` is an `Ok<T>` variant.
83
+ *
84
+ * This function narrows the type of a `Result` to `Ok<T>` if it represents a successful outcome.
85
+ * An `Ok<T>` variant is identified by its `error` property being `null`.
86
+ *
87
+ * @template T - The success value type.
88
+ * @template E - The error value type.
89
+ * @param result - The `Result<T, E>` to check.
90
+ * @returns `true` if the `result` is an `Ok<T>` variant, `false` otherwise.
91
+ * If `true`, TypeScript's type system will narrow `result` to `Ok<T>`.
92
+ * @example
93
+ * ```ts
94
+ * declare const myResult: Result<number, string>;
95
+ *
96
+ * if (isOk(myResult)) {
97
+ * // myResult is now typed as Ok<number>
98
+ * console.log("Success value:", myResult.data); // myResult.data is number
99
+ * }
100
+ * ```
101
+ */
102
+ function isOk(result) {
103
+ return result.error === null;
104
+ }
105
+ /**
106
+ * Type guard to runtime check if a `Result<T, E>` is an `Err<E>` variant.
107
+ *
108
+ * This function narrows the type of a `Result` to `Err<E>` if it represents a failure outcome.
109
+ * An `Err<E>` variant is identified by its `error` property being non-`null` (and thus `data` being `null`).
110
+ *
111
+ * @template T - The success value type.
112
+ * @template E - The error value type.
113
+ * @param result - The `Result<T, E>` to check.
114
+ * @returns `true` if the `result` is an `Err<E>` variant, `false` otherwise.
115
+ * If `true`, TypeScript's type system will narrow `result` to `Err<E>`.
116
+ * @example
117
+ * ```ts
118
+ * declare const myResult: Result<number, string>;
119
+ *
120
+ * if (isErr(myResult)) {
121
+ * // myResult is now typed as Err<string>
122
+ * console.error("Error value:", myResult.error); // myResult.error is string
123
+ * }
124
+ * ```
125
+ */
126
+ function isErr(result) {
127
+ return result.error !== null;
128
+ }
129
+ /**
130
+ * Executes a synchronous operation and wraps its outcome (success or failure) in a `Result<T, E>`.
131
+ *
132
+ * This function attempts to execute the `operation`.
133
+ * - If `operation` completes successfully, its return value is wrapped in an `Ok<T>` variant.
134
+ * - If `operation` throws an exception, the caught exception (of type `unknown`) is passed to
135
+ * the `mapError` function. `mapError` is responsible for transforming this `unknown`
136
+ * exception into a well-typed error value of type `E`. This error value is then wrapped
137
+ * in an `Err<E>` variant.
138
+ *
139
+ * @template T - The type of the success value returned by the `operation` if it succeeds.
140
+ * @template E - The type of the error value produced by `mapError` if the `operation` fails.
141
+ * @param options - An object containing the operation and error mapping function.
142
+ * @param options.try - The synchronous operation to execute. This function is expected to return a value of type `T`.
143
+ * @param options.mapError - A function that takes the `unknown` exception caught from `options.try`
144
+ * and transforms it into a specific error value of type `E`. This function
145
+ * is crucial for creating a well-typed error for the `Err<E>` variant.
146
+ * @returns A `Result<T, E>`: `Ok<T>` if `options.try` succeeds, or `Err<E>` if it throws and `options.mapError` provides an error value.
147
+ * @example
148
+ * ```ts
149
+ * function parseJson(jsonString: string): Result<object, SyntaxError> {
150
+ * return trySync({
151
+ * try: () => JSON.parse(jsonString),
152
+ * mapError: (err: unknown) => {
153
+ * if (err instanceof SyntaxError) return err;
154
+ * return new SyntaxError("Unknown parsing error");
155
+ * }
156
+ * });
157
+ * }
158
+ *
159
+ * const validResult = parseJson('{"name":"Result"}'); // Ok<{name: string}>
160
+ * const invalidResult = parseJson('invalid json'); // Err<SyntaxError>
161
+ *
162
+ * if (isOk(validResult)) console.log(validResult.data);
163
+ * if (isErr(invalidResult)) console.error(invalidResult.error.message);
164
+ * ```
165
+ */
166
+ function trySync({ try: operation, mapError }) {
167
+ try {
168
+ const data = operation();
169
+ return Ok(data);
170
+ } catch (error) {
171
+ return Err(mapError(error));
172
+ }
173
+ }
174
+ /**
175
+ * Executes an asynchronous operation (returning a `Promise`) and wraps its outcome in a `Promise<Result<T, E>>`.
176
+ *
177
+ * This function attempts to execute the asynchronous `operation`.
178
+ * - If the `Promise` returned by `operation` resolves successfully, its resolved value is wrapped in an `Ok<T>` variant.
179
+ * - If the `Promise` returned by `operation` rejects, or if `operation` itself throws an exception synchronously,
180
+ * the caught exception/rejection reason (of type `unknown`) is passed to the `mapError` function.
181
+ * `mapError` is responsible for transforming this `unknown` error into a well-typed error value of type `E`.
182
+ * This error value is then wrapped in an `Err<E>` variant.
183
+ *
184
+ * The entire outcome (`Ok<T>` or `Err<E>`) is wrapped in a `Promise`.
185
+ *
186
+ * @template T - The type of the success value the `Promise` from `operation` resolves to.
187
+ * @template E - The type of the error value produced by `mapError` if the `operation` fails or rejects.
188
+ * @param options - An object containing the asynchronous operation and error mapping function.
189
+ * @param options.try - The asynchronous operation to execute. This function must return a `Promise<T>`.
190
+ * @param options.mapError - A function that takes the `unknown` exception/rejection reason caught from `options.try`
191
+ * and transforms it into a specific error value of type `E`. This function
192
+ * is crucial for creating a well-typed error for the `Err<E>` variant.
193
+ * @returns A `Promise` that resolves to a `Result<T, E>`: `Ok<T>` if `options.try`'s `Promise` resolves,
194
+ * or `Err<E>` if it rejects/throws and `options.mapError` provides an error value.
195
+ * @example
196
+ * ```ts
197
+ * async function fetchData(url: string): Promise<Result<Response, Error>> {
198
+ * return tryAsync({
199
+ * try: async () => fetch(url),
200
+ * mapError: (err: unknown) => {
201
+ * if (err instanceof Error) return err;
202
+ * return new Error("Network request failed");
203
+ * }
204
+ * });
205
+ * }
206
+ *
207
+ * async function processData() {
208
+ * const result = await fetchData("/api/data");
209
+ * if (isOk(result)) {
210
+ * const response = result.data;
211
+ * console.log("Data fetched:", await response.json());
212
+ * } else {
213
+ * console.error("Fetch error:", result.error.message);
214
+ * }
215
+ * }
216
+ * processData();
217
+ * ```
218
+ */
219
+ async function tryAsync({ try: operation, mapError }) {
220
+ try {
221
+ const data = await operation();
222
+ return Ok(data);
223
+ } catch (error) {
224
+ return Err(mapError(error));
225
+ }
226
+ }
227
+ /**
228
+ * Unwraps a value if it is a `Result`, otherwise returns the value itself.
229
+ *
230
+ * - If `value` is an `Ok<T>` variant, its `data` (the success value) is returned.
231
+ * - If `value` is an `Err<E>` variant, its `error` (the error value) is thrown.
232
+ * - If `value` is not a `Result` (i.e., it's already a plain value of type `T`),
233
+ * it is returned as-is.
234
+ *
235
+ * This function is useful for situations where an operation might return either a
236
+ * direct value or a `Result` wrapping a value/error, and you want to
237
+ * uniformly access the value or propagate the error via throwing.
238
+ *
239
+ * @template T - The type of the success value (if `value` is `Ok<T>`) or the type of the plain value.
240
+ * @template E - The type of the error value (if `value` is `Err<E>`).
241
+ * @param value - The value to unwrap. It can be a `Result<T, E>` or a plain value of type `T`.
242
+ * @returns The success value of type `T` if `value` is `Ok<T>` or if `value` is a plain `T`.
243
+ * @throws The error value `E` if `value` is an `Err<E>` variant.
244
+ *
245
+ * @example
246
+ * ```ts
247
+ * // Example with an Ok variant
248
+ * const okResult = Ok("success data");
249
+ * const unwrappedOk = unwrapIfResult(okResult); // "success data"
250
+ *
251
+ * // Example with an Err variant
252
+ * const errResult = Err(new Error("failure"));
253
+ * try {
254
+ * unwrapIfResult(errResult);
255
+ * } catch (e) {
256
+ * console.error(e.message); // "failure"
257
+ * }
258
+ *
259
+ * // Example with a plain value
260
+ * const plainValue = "plain data";
261
+ * const unwrappedPlain = unwrapIfResult(plainValue); // "plain data"
262
+ *
263
+ * // Example with a function that might return Result or plain value
264
+ * declare function mightReturnResult(): string | Result<string, Error>;
265
+ * const outcome = mightReturnResult();
266
+ * try {
267
+ * const finalValue = unwrapIfResult(outcome); // handles both cases
268
+ * console.log("Final value:", finalValue);
269
+ * } catch (e) {
270
+ * console.error("Operation failed:", e);
271
+ * }
272
+ * ```
273
+ */
274
+ function unwrapIfResult(value) {
275
+ if (isResult(value)) {
276
+ if (isOk(value)) return value.data;
277
+ throw value.error;
278
+ }
279
+ return value;
280
+ }
281
+
282
+ //#endregion
283
+ //#region src/utils.ts
284
+ /**
285
+ * Partitions an array of Result objects into two separate arrays based on their status.
286
+ *
287
+ * @template T - The success type
288
+ * @template E - The error type
289
+ * @param results - An array of Result objects to partition
290
+ * @returns An object containing two arrays:
291
+ * - `oks`: Array of successful Result objects (Ok<T>[])
292
+ * - `errs`: Array of error Result objects (Err<E>[])
293
+ *
294
+ * @example
295
+ * const results = [Ok(1), Err("error"), Ok(2)];
296
+ * const { oks, errs } = partitionResults(results);
297
+ * // oks = [Ok(1), Ok(2)]
298
+ * // errs = [Err("error")]
299
+ */
300
+ function partitionResults(results) {
301
+ return {
302
+ oks: [],
303
+ errs: [],
304
+ ...Object.groupBy(results, (result) => isOk(result) ? "oks" : "errs")
305
+ };
306
+ }
307
+
308
+ //#endregion
309
+ //#region src/error/utils.ts
310
+ /**
311
+ * Extracts a readable error message from an unknown error value
312
+ *
313
+ * This utility is commonly used in mapError functions when converting
314
+ * unknown errors to typed error objects in the Result system.
315
+ *
316
+ * @param error - The unknown error to extract a message from
317
+ * @returns A string representation of the error
318
+ *
319
+ * @example
320
+ * ```ts
321
+ * // With native Error
322
+ * const error = new Error("Something went wrong");
323
+ * const message = extractErrorMessage(error); // "Something went wrong"
324
+ *
325
+ * // With string error
326
+ * const stringError = "String error";
327
+ * const message2 = extractErrorMessage(stringError); // "String error"
328
+ *
329
+ * // With object error
330
+ * const unknownError = { code: 500, details: "Server error" };
331
+ * const message3 = extractErrorMessage(unknownError); // '{"code":500,"details":"Server error"}'
332
+ *
333
+ * // Used in mapError function
334
+ * const result = await tryAsync({
335
+ * try: () => riskyOperation(),
336
+ * mapError: (error): NetworkError => ({
337
+ * name: "NetworkError",
338
+ * message: extractErrorMessage(error),
339
+ * context: { operation: "riskyOperation" },
340
+ * cause: error,
341
+ * }),
342
+ * });
343
+ * ```
344
+ */
345
+ function extractErrorMessage(error) {
346
+ if (error instanceof Error) return error.message;
347
+ if (typeof error === "string") return error;
348
+ if (typeof error === "object" && error !== null) {
349
+ const errorObj = error;
350
+ if (typeof errorObj.message === "string") return errorObj.message;
351
+ if (typeof errorObj.error === "string") return errorObj.error;
352
+ if (typeof errorObj.description === "string") return errorObj.description;
353
+ try {
354
+ return JSON.stringify(error);
355
+ } catch {
356
+ return String(error);
357
+ }
358
+ }
359
+ return String(error);
360
+ }
361
+
362
+ //#endregion
363
+ //#region src/error/types.ts
364
+ function createTryFns(name) {
365
+ return {
366
+ trySync: ({ try: operation, mapErr }) => trySync({
367
+ try: operation,
368
+ mapErr: (error) => ({
369
+ ...mapErr(error),
370
+ name
371
+ })
372
+ }),
373
+ tryAsync: ({ try: operation, mapErr }) => tryAsync({
374
+ try: operation,
375
+ mapErr: (error) => ({
376
+ ...mapErr(error),
377
+ name
378
+ })
379
+ })
380
+ };
381
+ }
382
+
383
+ //#endregion
384
+ export { Err, Ok, createTryFns, extractErrorMessage, isErr, isOk, isResult, partitionResults, tryAsync, trySync, unwrapIfResult };
385
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["data: T","error: E","value: unknown","result: Result<T, E>","value: T | Result<T, E>","results: Result<T, E>[]","error: unknown","name: TName"],"sources":["../src/result.ts","../src/utils.ts","../src/error/utils.ts","../src/error/types.ts"],"sourcesContent":["/**\n * Represents the successful outcome of an operation, encapsulating the success value.\n *\n * This is the 'Ok' variant of the `Result` type. It holds a `data` property\n * of type `T` (the success value) and an `error` property explicitly set to `null`,\n * signifying no error occurred.\n *\n * Use this type in conjunction with `Err<E>` and `Result<T, E>`.\n *\n * @template T - The type of the success value contained within.\n */\nexport type Ok<T> = { data: T; error: null };\n\n/**\n * Represents the failure outcome of an operation, encapsulating the error value.\n *\n * This is the 'Err' variant of the `Result` type. It holds an `error` property\n * of type `E` (the error value) and a `data` property explicitly set to `null`,\n * signifying that no success value is present due to the failure.\n *\n * Use this type in conjunction with `Ok<T>` and `Result<T, E>`.\n *\n * @template E - The type of the error value contained within.\n */\nexport type Err<E> = { error: E; data: null };\n\n/**\n * A type that represents the outcome of an operation that can either succeed or fail.\n *\n * `Result<T, E>` is a discriminated union type with two possible variants:\n * - `Ok<T>`: Represents a successful outcome, containing a `data` field with the success value of type `T`.\n * In this case, the `error` field is `null`.\n * - `Err<E>`: Represents a failure outcome, containing an `error` field with the error value of type `E`.\n * In this case, the `data` field is `null`.\n *\n * This type promotes explicit error handling by requiring developers to check\n * the variant of the `Result` before accessing its potential value or error.\n * It helps avoid runtime errors often associated with implicit error handling (e.g., relying on `try-catch` for all errors).\n *\n * @template T - The type of the success value if the operation is successful (held in `Ok<T>`).\n * @template E - The type of the error value if the operation fails (held in `Err<E>`).\n * @example\n * ```ts\n * function divide(numerator: number, denominator: number): Result<number, string> {\n * if (denominator === 0) {\n * return Err(\"Cannot divide by zero\");\n * }\n * return Ok(numerator / denominator);\n * }\n *\n * const result1 = divide(10, 2);\n * if (isOk(result1)) {\n * console.log(\"Success:\", result1.data); // Output: Success: 5\n * }\n *\n * const result2 = divide(10, 0);\n * if (isErr(result2)) {\n * console.error(\"Failure:\", result2.error); // Output: Failure: Cannot divide by zero\n * }\n * ```\n */\nexport type Result<T, E> = Ok<T> | Err<E>;\n\n/**\n * Constructs an `Ok<T>` variant, representing a successful outcome.\n *\n * This factory function creates the success variant of a `Result`.\n * It wraps the provided `data` (the success value) and ensures the `error` property is `null`.\n *\n * @template T - The type of the success value.\n * @param data - The success value to be wrapped in the `Ok` variant.\n * @returns An `Ok<T>` object with the provided data and `error` set to `null`.\n * @example\n * ```ts\n * const successfulResult = Ok(\"Operation completed successfully\");\n * // successfulResult is { data: \"Operation completed successfully\", error: null }\n * ```\n */\nexport const Ok = <T>(data: T): Ok<T> => ({ data, error: null });\n\n/**\n * Constructs an `Err<E>` variant, representing a failure outcome.\n *\n * This factory function creates the error variant of a `Result`.\n * It wraps the provided `error` (the error value) and ensures the `data` property is `null`.\n *\n * @template E - The type of the error value.\n * @param error - The error value to be wrapped in the `Err` variant. This value represents the specific error that occurred.\n * @returns An `Err<E>` object with the provided error and `data` set to `null`.\n * @example\n * ```ts\n * const failedResult = Err(new TypeError(\"Invalid input\"));\n * // failedResult is { error: TypeError(\"Invalid input\"), data: null }\n * ```\n */\nexport const Err = <E>(error: E): Err<E> => ({ error, data: null });\n\n/**\n * Utility type to extract the `Ok<T>` variant from a `Result<T, E>` union type.\n *\n * If `R` is a `Result` type (e.g., `Result<string, Error>`), this type will resolve\n * to `Ok<string>`. This can be useful in generic contexts or for type narrowing.\n *\n * @template R - The `Result<T, E>` union type from which to extract the `Ok<T>` variant.\n * Must extend `Result<unknown, unknown>`.\n */\nexport type ExtractOkFromResult<R extends Result<unknown, unknown>> = Extract<\n\tR,\n\t{ error: null }\n>;\n\n/**\n * Utility type to extract the `Err<E>` variant from a `Result<T, E>` union type.\n *\n * If `R` is a `Result` type (e.g., `Result<string, Error>`), this type will resolve\n * to `Err<Error>`. This can be useful in generic contexts or for type narrowing.\n *\n * @template R - The `Result<T, E>` union type from which to extract the `Err<E>` variant.\n * Must extend `Result<unknown, unknown>`.\n */\nexport type ExtractErrFromResult<R extends Result<unknown, unknown>> = Extract<\n\tR,\n\t{ data: null }\n>;\n\n/**\n * Utility type to extract the success value's type `T` from a `Result<T, E>` type.\n *\n * If `R` is an `Ok<T>` variant (or a `Result<T, E>` that could be an `Ok<T>`),\n * this type resolves to `T`. If `R` can only be an `Err<E>` variant, it resolves to `never`.\n * This is useful for obtaining the type of the `data` field when you know you have a success.\n *\n * @template R - The `Result<T, E>` type from which to extract the success value's type.\n * Must extend `Result<unknown, unknown>`.\n * @example\n * ```ts\n * type MyResult = Result<number, string>;\n * type SuccessValueType = UnwrapOk<MyResult>; // SuccessValueType is number\n *\n * type MyErrorResult = Err<string>;\n * type ErrorValueType = UnwrapOk<MyErrorResult>; // ErrorValueType is never\n * ```\n */\nexport type UnwrapOk<R extends Result<unknown, unknown>> = R extends Ok<infer U>\n\t? U\n\t: never;\n\n/**\n * Utility type to extract the error value's type `E` from a `Result<T, E>` type.\n *\n * If `R` is an `Err<E>` variant (or a `Result<T, E>` that could be an `Err<E>`),\n * this type resolves to `E`. If `R` can only be an `Ok<T>` variant, it resolves to `never`.\n * This is useful for obtaining the type of the `error` field when you know you have a failure.\n *\n * @template R - The `Result<T, E>` type from which to extract the error value's type.\n * Must extend `Result<unknown, unknown>`.\n * @example\n * ```ts\n * type MyResult = Result<number, string>;\n * type ErrorValueType = UnwrapErr<MyResult>; // ErrorValueType is string\n *\n * type MySuccessResult = Ok<number>;\n * type SuccessValueType = UnwrapErr<MySuccessResult>; // SuccessValueType is never\n * ```\n */\nexport type UnwrapErr<R extends Result<unknown, unknown>> = R extends Err<\n\tinfer E\n>\n\t? E\n\t: never;\n\n/**\n * Type guard to runtime check if an unknown value is a valid `Result<T, E>`.\n *\n * A value is considered a valid `Result` if:\n * 1. It is a non-null object.\n * 2. It has both `data` and `error` properties.\n * 3. Exactly one of `data` or `error` is `null`. The other must be non-`null`.\n *\n * This function does not validate the types of `data` or `error` beyond `null` checks.\n *\n * @template T - The expected type of the success value if the value is an `Ok` variant (defaults to `unknown`).\n * @template E - The expected type of the error value if the value is an `Err` variant (defaults to `unknown`).\n * @param value - The value to check.\n * @returns `true` if the value conforms to the `Result` structure, `false` otherwise.\n * If `true`, TypeScript's type system will narrow `value` to `Result<T, E>`.\n * @example\n * ```ts\n * declare const someValue: unknown;\n *\n * if (isResult<string, Error>(someValue)) {\n * // someValue is now typed as Result<string, Error>\n * if (isOk(someValue)) {\n * console.log(someValue.data); // string\n * } else {\n * console.error(someValue.error); // Error\n * }\n * }\n * ```\n */\nexport function isResult<T = unknown, E = unknown>(\n\tvalue: unknown,\n): value is Result<T, E> {\n\tconst isNonNullObject = typeof value === \"object\" && value !== null;\n\tif (!isNonNullObject) return false;\n\n\tconst hasDataProperty = \"data\" in value;\n\tconst hasErrorProperty = \"error\" in value;\n\tif (!hasDataProperty || !hasErrorProperty) return false;\n\n\tconst isBothNull = value.data === null && value.error === null;\n\tif (isBothNull) return false;\n\n\tconst isNeitherNull = value.data !== null && value.error !== null;\n\tif (isNeitherNull) return false;\n\n\t// Exactly one property is null\n\treturn true;\n}\n\n/**\n * Type guard to runtime check if a `Result<T, E>` is an `Ok<T>` variant.\n *\n * This function narrows the type of a `Result` to `Ok<T>` if it represents a successful outcome.\n * An `Ok<T>` variant is identified by its `error` property being `null`.\n *\n * @template T - The success value type.\n * @template E - The error value type.\n * @param result - The `Result<T, E>` to check.\n * @returns `true` if the `result` is an `Ok<T>` variant, `false` otherwise.\n * If `true`, TypeScript's type system will narrow `result` to `Ok<T>`.\n * @example\n * ```ts\n * declare const myResult: Result<number, string>;\n *\n * if (isOk(myResult)) {\n * // myResult is now typed as Ok<number>\n * console.log(\"Success value:\", myResult.data); // myResult.data is number\n * }\n * ```\n */\nexport function isOk<T, E>(result: Result<T, E>): result is Ok<T> {\n\treturn result.error === null;\n}\n\n/**\n * Type guard to runtime check if a `Result<T, E>` is an `Err<E>` variant.\n *\n * This function narrows the type of a `Result` to `Err<E>` if it represents a failure outcome.\n * An `Err<E>` variant is identified by its `error` property being non-`null` (and thus `data` being `null`).\n *\n * @template T - The success value type.\n * @template E - The error value type.\n * @param result - The `Result<T, E>` to check.\n * @returns `true` if the `result` is an `Err<E>` variant, `false` otherwise.\n * If `true`, TypeScript's type system will narrow `result` to `Err<E>`.\n * @example\n * ```ts\n * declare const myResult: Result<number, string>;\n *\n * if (isErr(myResult)) {\n * // myResult is now typed as Err<string>\n * console.error(\"Error value:\", myResult.error); // myResult.error is string\n * }\n * ```\n */\nexport function isErr<T, E>(result: Result<T, E>): result is Err<E> {\n\treturn result.error !== null; // Equivalent to result.data === null\n}\n\n/**\n * Executes a synchronous operation and wraps its outcome (success or failure) in a `Result<T, E>`.\n *\n * This function attempts to execute the `operation`.\n * - If `operation` completes successfully, its return value is wrapped in an `Ok<T>` variant.\n * - If `operation` throws an exception, the caught exception (of type `unknown`) is passed to\n * the `mapError` function. `mapError` is responsible for transforming this `unknown`\n * exception into a well-typed error value of type `E`. This error value is then wrapped\n * in an `Err<E>` variant.\n *\n * @template T - The type of the success value returned by the `operation` if it succeeds.\n * @template E - The type of the error value produced by `mapError` if the `operation` fails.\n * @param options - An object containing the operation and error mapping function.\n * @param options.try - The synchronous operation to execute. This function is expected to return a value of type `T`.\n * @param options.mapError - A function that takes the `unknown` exception caught from `options.try`\n * and transforms it into a specific error value of type `E`. This function\n * is crucial for creating a well-typed error for the `Err<E>` variant.\n * @returns A `Result<T, E>`: `Ok<T>` if `options.try` succeeds, or `Err<E>` if it throws and `options.mapError` provides an error value.\n * @example\n * ```ts\n * function parseJson(jsonString: string): Result<object, SyntaxError> {\n * return trySync({\n * try: () => JSON.parse(jsonString),\n * mapError: (err: unknown) => {\n * if (err instanceof SyntaxError) return err;\n * return new SyntaxError(\"Unknown parsing error\");\n * }\n * });\n * }\n *\n * const validResult = parseJson('{\"name\":\"Result\"}'); // Ok<{name: string}>\n * const invalidResult = parseJson('invalid json'); // Err<SyntaxError>\n *\n * if (isOk(validResult)) console.log(validResult.data);\n * if (isErr(invalidResult)) console.error(invalidResult.error.message);\n * ```\n */\nexport function trySync<T, E>({\n\ttry: operation,\n\tmapError,\n}: {\n\ttry: () => T;\n\tmapError: (error: unknown) => E;\n}): Result<T, E> {\n\ttry {\n\t\tconst data = operation();\n\t\treturn Ok(data);\n\t} catch (error) {\n\t\treturn Err(mapError(error));\n\t}\n}\n\n/**\n * Executes an asynchronous operation (returning a `Promise`) and wraps its outcome in a `Promise<Result<T, E>>`.\n *\n * This function attempts to execute the asynchronous `operation`.\n * - If the `Promise` returned by `operation` resolves successfully, its resolved value is wrapped in an `Ok<T>` variant.\n * - If the `Promise` returned by `operation` rejects, or if `operation` itself throws an exception synchronously,\n * the caught exception/rejection reason (of type `unknown`) is passed to the `mapError` function.\n * `mapError` is responsible for transforming this `unknown` error into a well-typed error value of type `E`.\n * This error value is then wrapped in an `Err<E>` variant.\n *\n * The entire outcome (`Ok<T>` or `Err<E>`) is wrapped in a `Promise`.\n *\n * @template T - The type of the success value the `Promise` from `operation` resolves to.\n * @template E - The type of the error value produced by `mapError` if the `operation` fails or rejects.\n * @param options - An object containing the asynchronous operation and error mapping function.\n * @param options.try - The asynchronous operation to execute. This function must return a `Promise<T>`.\n * @param options.mapError - A function that takes the `unknown` exception/rejection reason caught from `options.try`\n * and transforms it into a specific error value of type `E`. This function\n * is crucial for creating a well-typed error for the `Err<E>` variant.\n * @returns A `Promise` that resolves to a `Result<T, E>`: `Ok<T>` if `options.try`'s `Promise` resolves,\n * or `Err<E>` if it rejects/throws and `options.mapError` provides an error value.\n * @example\n * ```ts\n * async function fetchData(url: string): Promise<Result<Response, Error>> {\n * return tryAsync({\n * try: async () => fetch(url),\n * mapError: (err: unknown) => {\n * if (err instanceof Error) return err;\n * return new Error(\"Network request failed\");\n * }\n * });\n * }\n *\n * async function processData() {\n * const result = await fetchData(\"/api/data\");\n * if (isOk(result)) {\n * const response = result.data;\n * console.log(\"Data fetched:\", await response.json());\n * } else {\n * console.error(\"Fetch error:\", result.error.message);\n * }\n * }\n * processData();\n * ```\n */\nexport async function tryAsync<T, E>({\n\ttry: operation,\n\tmapError,\n}: {\n\ttry: () => Promise<T>;\n\tmapError: (error: unknown) => E;\n}): Promise<Result<T, E>> {\n\ttry {\n\t\tconst data = await operation();\n\t\treturn Ok(data);\n\t} catch (error) {\n\t\treturn Err(mapError(error));\n\t}\n}\n\n/**\n * Unwraps a value if it is a `Result`, otherwise returns the value itself.\n *\n * - If `value` is an `Ok<T>` variant, its `data` (the success value) is returned.\n * - If `value` is an `Err<E>` variant, its `error` (the error value) is thrown.\n * - If `value` is not a `Result` (i.e., it's already a plain value of type `T`),\n * it is returned as-is.\n *\n * This function is useful for situations where an operation might return either a\n * direct value or a `Result` wrapping a value/error, and you want to\n * uniformly access the value or propagate the error via throwing.\n *\n * @template T - The type of the success value (if `value` is `Ok<T>`) or the type of the plain value.\n * @template E - The type of the error value (if `value` is `Err<E>`).\n * @param value - The value to unwrap. It can be a `Result<T, E>` or a plain value of type `T`.\n * @returns The success value of type `T` if `value` is `Ok<T>` or if `value` is a plain `T`.\n * @throws The error value `E` if `value` is an `Err<E>` variant.\n *\n * @example\n * ```ts\n * // Example with an Ok variant\n * const okResult = Ok(\"success data\");\n * const unwrappedOk = unwrapIfResult(okResult); // \"success data\"\n *\n * // Example with an Err variant\n * const errResult = Err(new Error(\"failure\"));\n * try {\n * unwrapIfResult(errResult);\n * } catch (e) {\n * console.error(e.message); // \"failure\"\n * }\n *\n * // Example with a plain value\n * const plainValue = \"plain data\";\n * const unwrappedPlain = unwrapIfResult(plainValue); // \"plain data\"\n *\n * // Example with a function that might return Result or plain value\n * declare function mightReturnResult(): string | Result<string, Error>;\n * const outcome = mightReturnResult();\n * try {\n * const finalValue = unwrapIfResult(outcome); // handles both cases\n * console.log(\"Final value:\", finalValue);\n * } catch (e) {\n * console.error(\"Operation failed:\", e);\n * }\n * ```\n */\nexport function unwrapIfResult<T, E>(value: T | Result<T, E>): T {\n\tif (isResult<T, E>(value)) {\n\t\tif (isOk(value)) {\n\t\t\treturn value.data;\n\t\t}\n\t\t// If it's a Result and not Ok, it must be Err.\n\t\t// The type guard isResult<T,E>(value) and isOk(value) already refine the type.\n\t\t// So, 'value' here is known to be Err<E>.\n\t\tthrow value.error;\n\t}\n\n\t// If it's not a Result type, return the value as-is.\n\t// 'value' here is known to be of type T.\n\treturn value;\n}\n","import type { Err, Ok, Result } from \"./result.js\";\nimport { isOk } from \"./result.js\";\n\n/**\n * Partitions an array of Result objects into two separate arrays based on their status.\n *\n * @template T - The success type\n * @template E - The error type\n * @param results - An array of Result objects to partition\n * @returns An object containing two arrays:\n * - `oks`: Array of successful Result objects (Ok<T>[])\n * - `errs`: Array of error Result objects (Err<E>[])\n *\n * @example\n * const results = [Ok(1), Err(\"error\"), Ok(2)];\n * const { oks, errs } = partitionResults(results);\n * // oks = [Ok(1), Ok(2)]\n * // errs = [Err(\"error\")]\n */\nexport function partitionResults<T, E>(results: Result<T, E>[]) {\n\treturn {\n\t\toks: [],\n\t\terrs: [],\n\t\t...Object.groupBy(results, (result) => (isOk(result) ? \"oks\" : \"errs\")),\n\t} as { oks: Ok<T>[]; errs: Err<E>[] };\n}\n","/**\n * Extracts a readable error message from an unknown error value\n *\n * This utility is commonly used in mapError functions when converting\n * unknown errors to typed error objects in the Result system.\n *\n * @param error - The unknown error to extract a message from\n * @returns A string representation of the error\n *\n * @example\n * ```ts\n * // With native Error\n * const error = new Error(\"Something went wrong\");\n * const message = extractErrorMessage(error); // \"Something went wrong\"\n *\n * // With string error\n * const stringError = \"String error\";\n * const message2 = extractErrorMessage(stringError); // \"String error\"\n *\n * // With object error\n * const unknownError = { code: 500, details: \"Server error\" };\n * const message3 = extractErrorMessage(unknownError); // '{\"code\":500,\"details\":\"Server error\"}'\n *\n * // Used in mapError function\n * const result = await tryAsync({\n * try: () => riskyOperation(),\n * mapError: (error): NetworkError => ({\n * name: \"NetworkError\",\n * message: extractErrorMessage(error),\n * context: { operation: \"riskyOperation\" },\n * cause: error,\n * }),\n * });\n * ```\n */\nexport function extractErrorMessage(error: unknown): string {\n\tif (error instanceof Error) {\n\t\treturn error.message;\n\t}\n\n\tif (typeof error === \"string\") {\n\t\treturn error;\n\t}\n\n\tif (typeof error === \"object\" && error !== null) {\n\t\t// Check for common error properties\n\t\tconst errorObj = error as Record<string, unknown>;\n\t\tif (typeof errorObj.message === \"string\") {\n\t\t\treturn errorObj.message;\n\t\t}\n\t\tif (typeof errorObj.error === \"string\") {\n\t\t\treturn errorObj.error;\n\t\t}\n\t\tif (typeof errorObj.description === \"string\") {\n\t\t\treturn errorObj.description;\n\t\t}\n\n\t\t// Fallback to JSON stringification\n\t\ttry {\n\t\t\treturn JSON.stringify(error);\n\t\t} catch {\n\t\t\treturn String(error);\n\t\t}\n\t}\n\n\treturn String(error);\n}\n","import { tryAsync, trySync } from \"../result.js\";\nimport { extractErrorMessage } from \"./utils.js\";\n\nexport type BaseError = Readonly<{\n\tname: string;\n\tmessage: string;\n\tcontext: Record<string, unknown>;\n\tcause: unknown;\n}>;\n\n/**\n * Creates a tagged error type for type-safe error handling.\n * Uses the `name` property as a discriminator for tagged unions.\n *\n * Error types should follow the convention of ending with \"Error\" suffix.\n * The Err data structure wraps these error types in the Result system.\n *\n * @example\n * ```ts\n * type ValidationError = TaggedError<\"ValidationError\">\n * type NetworkError = TaggedError<\"NetworkError\">\n *\n * function handleError(error: ValidationError | NetworkError) {\n * switch (error.name) {\n * case \"ValidationError\": // TypeScript knows this is ValidationError\n * break;\n * case \"NetworkError\": // TypeScript knows this is NetworkError\n * break;\n * }\n * }\n *\n * // Used in Result types:\n * function validate(input: string): Result<string, ValidationError> {\n * if (!input) {\n * return Err({\n * name: \"ValidationError\",\n * message: \"Input is required\",\n * context: { input },\n * cause: null,\n * });\n * }\n * return Ok(input);\n * }\n * ```\n */\nexport type TaggedError<T extends string> = BaseError & {\n\treadonly name: T;\n};\n\nexport function createTryFns<TName extends string>(name: TName) {\n\ttype TError = TaggedError<TName>;\n\treturn {\n\t\ttrySync: <T>({\n\t\t\ttry: operation,\n\t\t\tmapErr,\n\t\t}: {\n\t\t\ttry: () => T;\n\t\t\tmapErr: (error: unknown) => Omit<TError, \"name\">;\n\t\t}) =>\n\t\t\ttrySync<T, TError>({\n\t\t\t\ttry: operation,\n\t\t\t\tmapErr: (error) => ({\n\t\t\t\t\t...mapErr(error),\n\t\t\t\t\tname,\n\t\t\t\t}),\n\t\t\t}),\n\t\ttryAsync: <T>({\n\t\t\ttry: operation,\n\t\t\tmapErr,\n\t\t}: {\n\t\t\ttry: () => Promise<T>;\n\t\t\tmapErr: (error: unknown) => Omit<TError, \"name\">;\n\t\t}) =>\n\t\t\ttryAsync<T, TError>({\n\t\t\t\ttry: operation,\n\t\t\t\tmapErr: (error) => ({ ...mapErr(error), name }),\n\t\t\t}),\n\t};\n}\n"],"mappings":";;;;;;;;;;;;;;;;AA8EA,MAAa,KAAK,CAAIA,UAAoB;CAAE;CAAM,OAAO;AAAM;;;;;;;;;;;;;;;;AAiB/D,MAAa,MAAM,CAAIC,WAAsB;CAAE;CAAO,MAAM;AAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyGlE,SAAgB,SACfC,OACwB;CACxB,MAAM,yBAAyB,UAAU,YAAY,UAAU;AAC/D,MAAK,gBAAiB,QAAO;CAE7B,MAAM,kBAAkB,UAAU;CAClC,MAAM,mBAAmB,WAAW;AACpC,MAAK,oBAAoB,iBAAkB,QAAO;CAElD,MAAM,aAAa,MAAM,SAAS,QAAQ,MAAM,UAAU;AAC1D,KAAI,WAAY,QAAO;CAEvB,MAAM,gBAAgB,MAAM,SAAS,QAAQ,MAAM,UAAU;AAC7D,KAAI,cAAe,QAAO;AAG1B,QAAO;AACP;;;;;;;;;;;;;;;;;;;;;;AAuBD,SAAgB,KAAWC,QAAuC;AACjE,QAAO,OAAO,UAAU;AACxB;;;;;;;;;;;;;;;;;;;;;;AAuBD,SAAgB,MAAYA,QAAwC;AACnE,QAAO,OAAO,UAAU;AACxB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuCD,SAAgB,QAAc,EAC7B,KAAK,WACL,UAIA,EAAgB;AAChB,KAAI;EACH,MAAM,OAAO,WAAW;AACxB,SAAO,GAAG,KAAK;CACf,SAAQ,OAAO;AACf,SAAO,IAAI,SAAS,MAAM,CAAC;CAC3B;AACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+CD,eAAsB,SAAe,EACpC,KAAK,WACL,UAIA,EAAyB;AACzB,KAAI;EACH,MAAM,OAAO,MAAM,WAAW;AAC9B,SAAO,GAAG,KAAK;CACf,SAAQ,OAAO;AACf,SAAO,IAAI,SAAS,MAAM,CAAC;CAC3B;AACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiDD,SAAgB,eAAqBC,OAA4B;AAChE,KAAI,SAAe,MAAM,EAAE;AAC1B,MAAI,KAAK,MAAM,CACd,QAAO,MAAM;AAKd,QAAM,MAAM;CACZ;AAID,QAAO;AACP;;;;;;;;;;;;;;;;;;;;ACxaD,SAAgB,iBAAuBC,SAAyB;AAC/D,QAAO;EACN,KAAK,CAAE;EACP,MAAM,CAAE;EACR,GAAG,OAAO,QAAQ,SAAS,CAAC,WAAY,KAAK,OAAO,GAAG,QAAQ,OAAQ;CACvE;AACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACUD,SAAgB,oBAAoBC,OAAwB;AAC3D,KAAI,iBAAiB,MACpB,QAAO,MAAM;AAGd,YAAW,UAAU,SACpB,QAAO;AAGR,YAAW,UAAU,YAAY,UAAU,MAAM;EAEhD,MAAM,WAAW;AACjB,aAAW,SAAS,YAAY,SAC/B,QAAO,SAAS;AAEjB,aAAW,SAAS,UAAU,SAC7B,QAAO,SAAS;AAEjB,aAAW,SAAS,gBAAgB,SACnC,QAAO,SAAS;AAIjB,MAAI;AACH,UAAO,KAAK,UAAU,MAAM;EAC5B,QAAO;AACP,UAAO,OAAO,MAAM;EACpB;CACD;AAED,QAAO,OAAO,MAAM;AACpB;;;;ACjBD,SAAgB,aAAmCC,MAAa;AAE/D,QAAO;EACN,SAAS,CAAI,EACZ,KAAK,WACL,QAIA,KACA,QAAmB;GAClB,KAAK;GACL,QAAQ,CAAC,WAAW;IACnB,GAAG,OAAO,MAAM;IAChB;GACA;EACD,EAAC;EACH,UAAU,CAAI,EACb,KAAK,WACL,QAIA,KACA,SAAoB;GACnB,KAAK;GACL,QAAQ,CAAC,WAAW;IAAE,GAAG,OAAO,MAAM;IAAE;GAAM;EAC9C,EAAC;CACH;AACD"}
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "wellcrafted",
3
+ "version": "0.15.0",
4
+ "description": "Delightful TypeScript patterns for elegant, type-safe applications",
5
+ "type": "module",
6
+ "files": ["dist", "README.md", "LICENSE"],
7
+ "exports": {
8
+ "./result": {
9
+ "types": "./dist/result.d.ts",
10
+ "import": "./dist/result.js"
11
+ },
12
+ "./error": {
13
+ "types": "./dist/error/index.d.ts",
14
+ "import": "./dist/error/index.js"
15
+ },
16
+ "./brand": {
17
+ "types": "./dist/brand.d.ts",
18
+ "import": "./dist/brand.js"
19
+ }
20
+ },
21
+ "scripts": {
22
+ "build": "tsdown",
23
+ "format": "biome format --write .",
24
+ "lint": "biome lint --write .",
25
+ "release": "pnpm run build && changeset version && changeset publish"
26
+ },
27
+ "keywords": [
28
+ "typescript",
29
+ "delightful",
30
+ "elegant",
31
+ "type-safe",
32
+ "well-crafted",
33
+ "polished",
34
+ "utilities",
35
+ "result",
36
+ "error-handling",
37
+ "brand-types"
38
+ ],
39
+ "author": "",
40
+ "license": "MIT",
41
+ "devDependencies": {
42
+ "@biomejs/biome": "^1.9.4",
43
+ "@changesets/cli": "^2.27.10",
44
+ "tsdown": "^0.12.5",
45
+ "typescript": "^5.7.2"
46
+ }
47
+ }