@oatscenter/result 0.100.0-rc.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.
package/README.md ADDED
@@ -0,0 +1,32 @@
1
+ # @oatscenter/result
2
+
3
+ Class-based `Result<T, E>` and `AsyncResult<T, E>` types for TypeScript,
4
+ inspired by Rust.
5
+
6
+ The npm package contains ESM JavaScript and TypeScript declarations. Use
7
+ `import`; CommonJS output and `require()` support have intentionally been
8
+ removed. JSR publication remains supported.
9
+
10
+ Provides explicit error handling with method chaining and the `take()` pattern
11
+ for early returns, without relying on exceptions.
12
+
13
+ ```typescript
14
+ import { AsyncResult, Result } from "@oatscenter/result";
15
+
16
+ const user = await fetchUser(id).take();
17
+ if (Result.isErr(user)) return user;
18
+ ```
19
+
20
+ Key surface area:
21
+
22
+ - `Result.ok(value)` / `Result.err(error)` — construction
23
+ - `Result.try(() => ...)` / `AsyncResult.try(async () => ...)` — wrap throwing
24
+ code
25
+ - `.map()`, `.mapErr()`, `.andThen()` — transforms
26
+ - `.take()` — early return pattern (Rust's `?` operator equivalent)
27
+ - `.match({ ok, err })` — pattern matching
28
+ - `Result.all()` / `Result.any()` — combinators
29
+ - `MaybeAsync<T, E>` — flexible sync/async return type
30
+ - `BaseError` — base class for all Trellis error types
31
+
32
+ See the source and inline JSDoc for full API details.
package/error.d.ts ADDED
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Base error types for use with Result<T, E>
3
+ * @module error
4
+ */
5
+ import Type, { type Static } from "typebox";
6
+ /**
7
+ * Base error serialization schema.
8
+ * All errors serialize to this structure with optional additional fields.
9
+ */
10
+ export declare const BaseErrorSchema: Type.TObject<{
11
+ id: Type.TString;
12
+ type: Type.TString;
13
+ message: Type.TString;
14
+ context: Type.TOptional<Type.TRecord<"^.*$", Type.TUnknown>>;
15
+ traceId: Type.TOptional<Type.TString>;
16
+ }>;
17
+ export type BaseErrorSchema = Static<typeof BaseErrorSchema>;
18
+ export type BaseErrorOptions = ErrorOptions & {
19
+ context?: Record<string, unknown>;
20
+ id?: string;
21
+ traceId?: string;
22
+ };
23
+ /**
24
+ * Base class for all errors used in Result<T, E>.
25
+ * @template TData - The serialized data type for this error
26
+ */
27
+ export declare abstract class BaseError<TData extends BaseErrorSchema = BaseErrorSchema> extends Error {
28
+ #private;
29
+ /**
30
+ * Optional callback to provide a trace ID from ambient context (e.g. OpenTelemetry).
31
+ *
32
+ * Trellis (or other consumers) can set this once at startup so all BaseError
33
+ * instances capture a traceId automatically unless explicitly provided.
34
+ */
35
+ static traceIdGetter: (() => string | undefined) | undefined;
36
+ /** Unique identifier for this error instance */
37
+ readonly id: string;
38
+ /** Error type name (used for discrimination) */
39
+ abstract readonly name: string;
40
+ constructor(message: string, options?: BaseErrorOptions);
41
+ /**
42
+ * Add contextual information to this error.
43
+ * Useful for adding runtime context like request IDs, user IDs, etc.
44
+ *
45
+ * @param context - Key-value pairs to add to the error context
46
+ * @returns This error instance for chaining
47
+ *
48
+ * @example
49
+ * ```typescript
50
+ * const error = new UnauthorizedError({ reason: "Invalid token" })
51
+ * .withContext({ userId: "123", requestId: "req-456" });
52
+ * ```
53
+ */
54
+ withContext(context?: Record<string, unknown>): this;
55
+ /**
56
+ * Get the current context object.
57
+ * @returns The context object
58
+ */
59
+ getContext(): Record<string, unknown>;
60
+ /**
61
+ * Get the trace ID for this error.
62
+ * Returns the trace ID captured at error creation time.
63
+ *
64
+ * @returns The trace ID string or undefined
65
+ */
66
+ protected getTraceId(): string | undefined;
67
+ /**
68
+ * Attach a trace ID if one was not captured when the error was created.
69
+ *
70
+ * @param traceId - Trace ID to attach to serialized error data.
71
+ * @returns This error instance for chaining.
72
+ */
73
+ withTraceId(traceId: string | undefined): this;
74
+ /**
75
+ * Helper method to get base serializable fields.
76
+ * Subclasses should use this to build their complete serializable object.
77
+ *
78
+ * @returns Base error fields (id, type, message, context, traceId)
79
+ */
80
+ protected baseSerializable(): BaseErrorSchema;
81
+ /**
82
+ * Serializes error to a plain object.
83
+ * Subclasses must implement this to return their specific data type.
84
+ *
85
+ * @returns Plain object representation of the error
86
+ */
87
+ abstract toSerializable(): TData;
88
+ /**
89
+ * Serializes error to JSON string.
90
+ *
91
+ * @returns JSON string representation of the error
92
+ */
93
+ toJSON(): string;
94
+ }
95
+ /**
96
+ * Schema for UnexpectedError serialization.
97
+ */
98
+ export declare const UnexpectedErrorDataSchema: Type.TObject<{
99
+ id: Type.TString;
100
+ type: Type.TLiteral<"UnexpectedError">;
101
+ message: Type.TString;
102
+ context: Type.TOptional<Type.TRecord<"^.*$", Type.TUnknown>>;
103
+ traceId: Type.TOptional<Type.TString>;
104
+ }>;
105
+ export type UnexpectedErrorData = Static<typeof UnexpectedErrorDataSchema>;
106
+ /**
107
+ * Represents an unexpected error.
108
+ * Use this for wrapping unknown errors or for truly unexpected conditions.
109
+ */
110
+ export declare class UnexpectedError extends BaseError<UnexpectedErrorData> {
111
+ readonly name: "UnexpectedError";
112
+ constructor(options?: BaseErrorOptions);
113
+ /**
114
+ * Serializes error to a plain object.
115
+ *
116
+ * @returns Plain object representation of the error
117
+ */
118
+ toSerializable(): UnexpectedErrorData;
119
+ }
package/error.js ADDED
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Base error types for use with Result<T, E>
3
+ * @module error
4
+ */
5
+ import { ulid } from "ulid";
6
+ import Type, {} from "typebox";
7
+ /**
8
+ * Base error serialization schema.
9
+ * All errors serialize to this structure with optional additional fields.
10
+ */
11
+ export const BaseErrorSchema = Type.Object({
12
+ id: Type.String(),
13
+ type: Type.String(),
14
+ message: Type.String(),
15
+ context: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
16
+ traceId: Type.Optional(Type.String()),
17
+ });
18
+ /**
19
+ * Base class for all errors used in Result<T, E>.
20
+ * @template TData - The serialized data type for this error
21
+ */
22
+ export class BaseError extends Error {
23
+ /**
24
+ * Optional callback to provide a trace ID from ambient context (e.g. OpenTelemetry).
25
+ *
26
+ * Trellis (or other consumers) can set this once at startup so all BaseError
27
+ * instances capture a traceId automatically unless explicitly provided.
28
+ */
29
+ static traceIdGetter;
30
+ /** Unique identifier for this error instance */
31
+ id;
32
+ /** Runtime contextual information */
33
+ #context;
34
+ /** Trace ID captured at error creation time */
35
+ #traceId;
36
+ constructor(message, options) {
37
+ const { context, id, traceId, ...errorOptions } = options ?? {};
38
+ super(message, errorOptions);
39
+ this.id = id ?? ulid();
40
+ this.#context = context ?? {};
41
+ this.#traceId = traceId ?? BaseError.traceIdGetter?.();
42
+ const constructor = this.constructor;
43
+ if ("prototype" in constructor) {
44
+ Object.setPrototypeOf(this, constructor.prototype);
45
+ }
46
+ const captureStackTrace = Error
47
+ .captureStackTrace;
48
+ if (captureStackTrace) {
49
+ captureStackTrace(this, this.constructor);
50
+ }
51
+ }
52
+ /**
53
+ * Add contextual information to this error.
54
+ * Useful for adding runtime context like request IDs, user IDs, etc.
55
+ *
56
+ * @param context - Key-value pairs to add to the error context
57
+ * @returns This error instance for chaining
58
+ *
59
+ * @example
60
+ * ```typescript
61
+ * const error = new UnauthorizedError({ reason: "Invalid token" })
62
+ * .withContext({ userId: "123", requestId: "req-456" });
63
+ * ```
64
+ */
65
+ withContext(context) {
66
+ if (context) {
67
+ Object.assign(this.#context, context);
68
+ }
69
+ return this;
70
+ }
71
+ /**
72
+ * Get the current context object.
73
+ * @returns The context object
74
+ */
75
+ getContext() {
76
+ return this.#context;
77
+ }
78
+ /**
79
+ * Get the trace ID for this error.
80
+ * Returns the trace ID captured at error creation time.
81
+ *
82
+ * @returns The trace ID string or undefined
83
+ */
84
+ getTraceId() {
85
+ return this.#traceId;
86
+ }
87
+ /**
88
+ * Attach a trace ID if one was not captured when the error was created.
89
+ *
90
+ * @param traceId - Trace ID to attach to serialized error data.
91
+ * @returns This error instance for chaining.
92
+ */
93
+ withTraceId(traceId) {
94
+ if (this.#traceId === undefined && traceId !== undefined) {
95
+ this.#traceId = traceId;
96
+ }
97
+ return this;
98
+ }
99
+ /**
100
+ * Helper method to get base serializable fields.
101
+ * Subclasses should use this to build their complete serializable object.
102
+ *
103
+ * @returns Base error fields (id, type, message, context, traceId)
104
+ */
105
+ baseSerializable() {
106
+ const traceId = this.getTraceId();
107
+ return {
108
+ id: this.id,
109
+ type: this.name,
110
+ message: this.message,
111
+ context: this.#context,
112
+ ...(traceId !== undefined && { traceId }),
113
+ };
114
+ }
115
+ /**
116
+ * Serializes error to JSON string.
117
+ *
118
+ * @returns JSON string representation of the error
119
+ */
120
+ toJSON() {
121
+ return JSON.stringify(this.toSerializable());
122
+ }
123
+ }
124
+ /**
125
+ * Schema for UnexpectedError serialization.
126
+ */
127
+ export const UnexpectedErrorDataSchema = Type.Object({
128
+ id: Type.String(),
129
+ type: Type.Literal("UnexpectedError"),
130
+ message: Type.String(),
131
+ context: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
132
+ traceId: Type.Optional(Type.String()),
133
+ });
134
+ /**
135
+ * Represents an unexpected error.
136
+ * Use this for wrapping unknown errors or for truly unexpected conditions.
137
+ */
138
+ export class UnexpectedError extends BaseError {
139
+ name = "UnexpectedError";
140
+ constructor(options) {
141
+ super("An unexpected error has occurred", options);
142
+ if (options?.cause) {
143
+ let root = options.cause;
144
+ while (root instanceof Error && root.cause) {
145
+ root = root.cause;
146
+ }
147
+ const causeMessage = root instanceof Error
148
+ ? root.message
149
+ : typeof root === "object"
150
+ ? JSON.stringify(root)
151
+ : String(root);
152
+ const causeStack = root instanceof Error ? root.stack : undefined;
153
+ this.withContext({ causeMessage, causeStack });
154
+ }
155
+ }
156
+ /**
157
+ * Serializes error to a plain object.
158
+ *
159
+ * @returns Plain object representation of the error
160
+ */
161
+ toSerializable() {
162
+ return this.baseSerializable();
163
+ }
164
+ }
package/mod.d.ts ADDED
@@ -0,0 +1,60 @@
1
+ /**
2
+ * @oatscenter/result - Class-based Result type for TypeScript/Deno
3
+ *
4
+ * A class-based Result<T, E> system inspired by Rust's Result type,
5
+ * providing elegant error handling with method chaining and the `take()`
6
+ * pattern for early returns.
7
+ *
8
+ * This library provides two main classes:
9
+ * - `Result<T, E>`: Synchronous result type with method chaining
10
+ * - `AsyncResult<T, E>`: Asynchronous result type that implements PromiseLike
11
+ *
12
+ * @example Basic usage with static methods
13
+ * ```typescript
14
+ * import { Result } from "@oatscenter/result";
15
+ *
16
+ * function divide(a: number, b: number): Result<number, ValidationError> {
17
+ * if (b === 0) {
18
+ * return Result.err(new ValidationError("Division by zero"));
19
+ * }
20
+ * return Result.ok(a / b);
21
+ * }
22
+ *
23
+ * const result = divide(10, 2)
24
+ * .map(x => x * 2)
25
+ * .map(x => x + 1);
26
+ *
27
+ * const value = result.take();
28
+ * if (Result.isErr(value)) return value;
29
+ * console.log(value); // 11
30
+ * ```
31
+ *
32
+ * @example Async operations
33
+ * ```typescript
34
+ * import { AsyncResult, Result } from "@oatscenter/result";
35
+ *
36
+ * const user = AsyncResult.try(async () => {
37
+ * const response = await fetch(`/api/users/123`);
38
+ * return await response.json();
39
+ * });
40
+ *
41
+ * const result = user
42
+ * .map(user => user.name)
43
+ * .map(name => name.toUpperCase());
44
+ *
45
+ * const value = await result.take();
46
+ * if (Result.isErr(value)) return value;
47
+ * console.log(value); // "ALICE"
48
+ * ```
49
+ *
50
+ * @module
51
+ */
52
+ import { Result as ResultClass } from "./result.ts";
53
+ export { AsyncResult, Result } from "./result.ts";
54
+ export type { ErrValue, Infer, InferErr, MaybeAsync, OkValue, } from "./result.ts";
55
+ export declare const ok: typeof ResultClass.ok;
56
+ export declare const err: typeof ResultClass.err;
57
+ export declare const isOk: typeof ResultClass.isOk;
58
+ export declare const isErr: typeof ResultClass.isErr;
59
+ export { BaseError, UnexpectedError, UnexpectedErrorDataSchema, } from "./error.ts";
60
+ export type { BaseErrorOptions, BaseErrorSchema, UnexpectedErrorData, } from "./error.ts";
package/mod.js ADDED
@@ -0,0 +1,58 @@
1
+ /**
2
+ * @oatscenter/result - Class-based Result type for TypeScript/Deno
3
+ *
4
+ * A class-based Result<T, E> system inspired by Rust's Result type,
5
+ * providing elegant error handling with method chaining and the `take()`
6
+ * pattern for early returns.
7
+ *
8
+ * This library provides two main classes:
9
+ * - `Result<T, E>`: Synchronous result type with method chaining
10
+ * - `AsyncResult<T, E>`: Asynchronous result type that implements PromiseLike
11
+ *
12
+ * @example Basic usage with static methods
13
+ * ```typescript
14
+ * import { Result } from "@oatscenter/result";
15
+ *
16
+ * function divide(a: number, b: number): Result<number, ValidationError> {
17
+ * if (b === 0) {
18
+ * return Result.err(new ValidationError("Division by zero"));
19
+ * }
20
+ * return Result.ok(a / b);
21
+ * }
22
+ *
23
+ * const result = divide(10, 2)
24
+ * .map(x => x * 2)
25
+ * .map(x => x + 1);
26
+ *
27
+ * const value = result.take();
28
+ * if (Result.isErr(value)) return value;
29
+ * console.log(value); // 11
30
+ * ```
31
+ *
32
+ * @example Async operations
33
+ * ```typescript
34
+ * import { AsyncResult, Result } from "@oatscenter/result";
35
+ *
36
+ * const user = AsyncResult.try(async () => {
37
+ * const response = await fetch(`/api/users/123`);
38
+ * return await response.json();
39
+ * });
40
+ *
41
+ * const result = user
42
+ * .map(user => user.name)
43
+ * .map(name => name.toUpperCase());
44
+ *
45
+ * const value = await result.take();
46
+ * if (Result.isErr(value)) return value;
47
+ * console.log(value); // "ALICE"
48
+ * ```
49
+ *
50
+ * @module
51
+ */
52
+ import { Result as ResultClass } from "./result.js";
53
+ export { AsyncResult, Result } from "./result.js";
54
+ export const ok = ResultClass.ok;
55
+ export const err = ResultClass.err;
56
+ export const isOk = ResultClass.isOk;
57
+ export const isErr = ResultClass.isErr;
58
+ export { BaseError, UnexpectedError, UnexpectedErrorDataSchema, } from "./error.js";
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@oatscenter/result",
3
+ "description": "Class-based Result and AsyncResult types for Trellis TypeScript applications.",
4
+ "exports": {
5
+ ".": {
6
+ "types": "./mod.d.ts",
7
+ "import": "./mod.js"
8
+ }
9
+ },
10
+ "dependencies": {
11
+ "typebox": "^1.1.33",
12
+ "ulid": "^3.0.2"
13
+ },
14
+ "version": "0.100.0-rc.1",
15
+ "type": "module",
16
+ "license": "Apache-2.0",
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/OATS-Center/trellis.git"
23
+ }
24
+ }