diff --git a/README.md b/README.md index af149b6..d9e1bab 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ -# golem-ts -`golem-ts` is a TypeScript library that provides high-level wrappers for Golem's runtime API, including the [transaction API](https://learn.golem.cloud/docs/transaction-api). It simplifies the process of writing Golem programs by offering a set of utilities and abstractions. +# golem +`golem` is a TypeScript library that provides high-level wrappers for Golem's runtime API, including the [transaction API](https://learn.golem.cloud/docs/transaction-api). It simplifies the process of writing Golem programs by offering a set of utilities and abstractions. ## Installation -To install `golem-ts`, use the following command: +To install `golem`, use the following command: ```bash -npm install golem-ts +npm install @golemcloud/golem-ts ``` ## Features diff --git a/biome.json b/biome.json index faefa44..04bb62c 100644 --- a/biome.json +++ b/biome.json @@ -1,28 +1,28 @@ { - "$schema": "https://biomejs.dev/schemas/1.8.0/schema.json", - "organizeImports": { - "enabled": true - }, - "linter": { - "enabled": true, - "rules": { - "recommended": true, - "style": { - "noUselessElse": "off" - }, - "suspicious": { - "noExplicitAny": "off", - "noShadowRestrictedNames": "off" - } - } - }, - "formatter": { - "enabled": true, - "lineWidth": 100, - "indentStyle": "space", - "indentWidth": 3 - }, - "files": { - "include": ["src/**/*"] - } + "$schema": "https://biomejs.dev/schemas/1.8.0/schema.json", + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "style": { + "noUselessElse": "off" + }, + "suspicious": { + "noExplicitAny": "off", + "noShadowRestrictedNames": "off" + } + } + }, + "formatter": { + "enabled": true, + "lineWidth": 100, + "indentStyle": "space", + "indentWidth": 4 + }, + "files": { + "include": ["src/**/*"] + } } diff --git a/package-lock.json b/package-lock.json index e836444..9595560 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,17 @@ { - "name": "golem-ts", - "version": "0.1.4", + "name": "@golemcloud/golem-ts", + "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "golem-ts", - "version": "0.1.4", + "name": "@golemcloud/golem-ts", + "version": "0.1.0", "license": "Apache-2.0", "devDependencies": { "@biomejs/biome": "1.8.0", "@bytecodealliance/jco": "1.2.4", + "@types/node": "^20.14.2", "typescript": "^4.9.5" } }, @@ -251,6 +252,15 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@types/node": { + "version": "20.14.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.2.tgz", + "integrity": "sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, "node_modules/acorn": { "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", @@ -607,6 +617,12 @@ "engines": { "node": ">=4.2.0" } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true } } } diff --git a/package.json b/package.json index 6c361a6..dba7efc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "golem-ts", - "version": "0.1.4", + "name": "@golemcloud/golem-ts", + "version": "0.1.0", "description": "A library that help writing Golem programs by providing higher level wrappers for Golem's runtime APIs, including functions for defining and performing operations transactionally.", "repository": { "url": "git+https://github.com/golemcloud/golem-ts.git", @@ -25,6 +25,7 @@ "devDependencies": { "@biomejs/biome": "1.8.0", "@bytecodealliance/jco": "1.2.4", + "@types/node": "^20.14.2", "typescript": "^4.9.5" } } diff --git a/src/bindgen/bindgen.d.ts b/src/bindgen/bindgen.d.ts index 517e0e9..aad9109 100644 --- a/src/bindgen/bindgen.d.ts +++ b/src/bindgen/bindgen.d.ts @@ -30,22 +30,22 @@ export function getIdempotenceMode(): boolean; export function setIdempotenceMode(idempotent: boolean): void; export function generateIdempotencyKey(): Uuid; export function updateWorker( - workerId: WorkerId, - targetVersion: ComponentVersion, - mode: UpdateMode, + workerId: WorkerId, + targetVersion: ComponentVersion, + mode: UpdateMode, ): void; export class GetWorkers { - constructor(componentId: ComponentId, filter: WorkerAnyFilter | undefined, precise: boolean); - getNext(): WorkerMetadata[] | undefined; + constructor(componentId: ComponentId, filter: WorkerAnyFilter | undefined, precise: boolean); + getNext(): WorkerMetadata[] | undefined; } export interface Uuid { - highBits: bigint; - lowBits: bigint; + highBits: bigint; + lowBits: bigint; } export interface ComponentId { - uuid: Uuid; + uuid: Uuid; } /** * # Variants @@ -60,8 +60,8 @@ export interface ComponentId { */ export type StringFilterComparator = "equal" | "not-equal" | "like" | "not-like"; export interface WorkerNameFilter { - comparator: StringFilterComparator; - value: string; + comparator: StringFilterComparator; + value: string; } /** * # Variants @@ -79,12 +79,12 @@ export interface WorkerNameFilter { * ## `"less"` */ export type FilterComparator = - | "equal" - | "not-equal" - | "greater-equal" - | "greater" - | "less-equal" - | "less"; + | "equal" + | "not-equal" + | "greater-equal" + | "greater" + | "less-equal" + | "less"; /** * # Variants * @@ -103,101 +103,101 @@ export type FilterComparator = * ## `"exited"` */ export type WorkerStatus = - | "running" - | "idle" - | "suspended" - | "interrupted" - | "retrying" - | "failed" - | "exited"; + | "running" + | "idle" + | "suspended" + | "interrupted" + | "retrying" + | "failed" + | "exited"; export interface WorkerStatusFilter { - comparator: FilterComparator; - value: WorkerStatus; + comparator: FilterComparator; + value: WorkerStatus; } export interface WorkerVersionFilter { - comparator: FilterComparator; - value: bigint; + comparator: FilterComparator; + value: bigint; } export interface WorkerCreatedAtFilter { - comparator: FilterComparator; - value: bigint; + comparator: FilterComparator; + value: bigint; } export interface WorkerEnvFilter { - name: string; - comparator: StringFilterComparator; - value: string; + name: string; + comparator: StringFilterComparator; + value: string; } export type WorkerPropertyFilter = - | WorkerPropertyFilterName - | WorkerPropertyFilterStatus - | WorkerPropertyFilterVersion - | WorkerPropertyFilterCreatedAt - | WorkerPropertyFilterEnv; + | WorkerPropertyFilterName + | WorkerPropertyFilterStatus + | WorkerPropertyFilterVersion + | WorkerPropertyFilterCreatedAt + | WorkerPropertyFilterEnv; export interface WorkerPropertyFilterName { - tag: "name"; - val: WorkerNameFilter; + tag: "name"; + val: WorkerNameFilter; } export interface WorkerPropertyFilterStatus { - tag: "status"; - val: WorkerStatusFilter; + tag: "status"; + val: WorkerStatusFilter; } export interface WorkerPropertyFilterVersion { - tag: "version"; - val: WorkerVersionFilter; + tag: "version"; + val: WorkerVersionFilter; } export interface WorkerPropertyFilterCreatedAt { - tag: "created-at"; - val: WorkerCreatedAtFilter; + tag: "created-at"; + val: WorkerCreatedAtFilter; } export interface WorkerPropertyFilterEnv { - tag: "env"; - val: WorkerEnvFilter; + tag: "env"; + val: WorkerEnvFilter; } export interface WorkerAllFilter { - filters: WorkerPropertyFilter[]; + filters: WorkerPropertyFilter[]; } export interface WorkerAnyFilter { - filters: WorkerAllFilter[]; + filters: WorkerAllFilter[]; } export interface WorkerId { - componentId: ComponentId; - workerName: string; + componentId: ComponentId; + workerName: string; } export interface WorkerMetadata { - workerId: WorkerId; - args: string[]; - env: [string, string][]; - status: WorkerStatus; - componentVersion: bigint; - retryCount: bigint; + workerId: WorkerId; + args: string[]; + env: [string, string][]; + status: WorkerStatus; + componentVersion: bigint; + retryCount: bigint; } export type OplogIndex = bigint; export interface PromiseId { - workerId: WorkerId; - oplogIdx: OplogIndex; + workerId: WorkerId; + oplogIdx: OplogIndex; } export interface Uri { - value: string; + value: string; } export type Duration = bigint; export interface RetryPolicy { - maxAttempts: number; - minDelay: Duration; - maxDelay: Duration; - multiplier: number; + maxAttempts: number; + minDelay: Duration; + maxDelay: Duration; + multiplier: number; } export type PersistenceLevel = - | PersistenceLevelPersistNothing - | PersistenceLevelPersistRemoteSideEffects - | PersistenceLevelSmart; + | PersistenceLevelPersistNothing + | PersistenceLevelPersistRemoteSideEffects + | PersistenceLevelSmart; export interface PersistenceLevelPersistNothing { - tag: "persist-nothing"; + tag: "persist-nothing"; } export interface PersistenceLevelPersistRemoteSideEffects { - tag: "persist-remote-side-effects"; + tag: "persist-remote-side-effects"; } export interface PersistenceLevelSmart { - tag: "smart"; + tag: "smart"; } export type ComponentVersion = bigint; /** diff --git a/src/bindgen/bindgen.js b/src/bindgen/bindgen.js index 41c1566..528d1da 100644 --- a/src/bindgen/bindgen.js +++ b/src/bindgen/bindgen.js @@ -13,97 +13,97 @@ // limitations under the License. import { - GetWorkers, - generateIdempotencyKey as generateIdempotencyKeyImpl, - getIdempotenceMode as getIdempotenceModeImpl, - getOplogIndex as getOplogIndexImpl, - getOplogPersistenceLevel as getOplogPersistenceLevelImpl, - getRetryPolicy as getRetryPolicyImpl, - getSelfUri as getSelfUriImpl, - golemAwaitPromise as golemAwaitPromiseImpl, - golemCompletePromise as golemCompletePromiseImpl, - golemCreatePromise as golemCreatePromiseImpl, - golemDeletePromise as golemDeletePromiseImpl, - markBeginOperation as markBeginOperationImpl, - markEndOperation as markEndOperationImpl, - oplogCommit as oplogCommitImpl, - setIdempotenceMode as setIdempotenceModeImpl, - setOplogIndex as setOplogIndexImpl, - setOplogPersistenceLevel as setOplogPersistenceLevelImpl, - setRetryPolicy as setRetryPolicyImpl, - updateWorker as updateWorkerImpl, + GetWorkers, + generateIdempotencyKey as generateIdempotencyKeyImpl, + getIdempotenceMode as getIdempotenceModeImpl, + getOplogIndex as getOplogIndexImpl, + getOplogPersistenceLevel as getOplogPersistenceLevelImpl, + getRetryPolicy as getRetryPolicyImpl, + getSelfUri as getSelfUriImpl, + golemAwaitPromise as golemAwaitPromiseImpl, + golemCompletePromise as golemCompletePromiseImpl, + golemCreatePromise as golemCreatePromiseImpl, + golemDeletePromise as golemDeletePromiseImpl, + markBeginOperation as markBeginOperationImpl, + markEndOperation as markEndOperationImpl, + oplogCommit as oplogCommitImpl, + setIdempotenceMode as setIdempotenceModeImpl, + setOplogIndex as setOplogIndexImpl, + setOplogPersistenceLevel as setOplogPersistenceLevelImpl, + setRetryPolicy as setRetryPolicyImpl, + updateWorker as updateWorkerImpl, } from "golem:api/host@0.2.0"; export { GetWorkers }; export function golemCreatePromise() { - return golemCreatePromiseImpl(); + return golemCreatePromiseImpl(); } export function golemAwaitPromise(promiseId) { - return golemAwaitPromiseImpl(promiseId); + return golemAwaitPromiseImpl(promiseId); } export function golemCompletePromise(promiseId, data) { - return golemCompletePromiseImpl(promiseId, data); + return golemCompletePromiseImpl(promiseId, data); } export function golemDeletePromise(promiseId) { - return golemDeletePromiseImpl(promiseId); + return golemDeletePromiseImpl(promiseId); } export function getSelfUri(functionName) { - return getSelfUriImpl(functionName); + return getSelfUriImpl(functionName); } export function getOplogIndex() { - return getOplogIndexImpl(); + return getOplogIndexImpl(); } export function setOplogIndex(oplogIdx) { - return setOplogIndexImpl(oplogIdx); + return setOplogIndexImpl(oplogIdx); } export function oplogCommit(replicas) { - return oplogCommitImpl(replicas); + return oplogCommitImpl(replicas); } export function markBeginOperation() { - return markBeginOperationImpl(); + return markBeginOperationImpl(); } export function markEndOperation(begin) { - return markEndOperationImpl(begin); + return markEndOperationImpl(begin); } export function getRetryPolicy() { - return getRetryPolicyImpl(); + return getRetryPolicyImpl(); } export function setRetryPolicy(newRetryPolicy) { - return setRetryPolicyImpl(newRetryPolicy); + return setRetryPolicyImpl(newRetryPolicy); } export function getOplogPersistenceLevel() { - return getOplogPersistenceLevelImpl(); + return getOplogPersistenceLevelImpl(); } export function setOplogPersistenceLevel(newPersistenceLevel) { - return setOplogPersistenceLevelImpl(newPersistenceLevel); + return setOplogPersistenceLevelImpl(newPersistenceLevel); } export function getIdempotenceMode() { - return getIdempotenceModeImpl(); + return getIdempotenceModeImpl(); } export function setIdempotenceMode(idempotent) { - return setIdempotenceModeImpl(idempotent); + return setIdempotenceModeImpl(idempotent); } export function generateIdempotencyKey() { - return generateIdempotencyKeyImpl(); + return generateIdempotencyKeyImpl(); } export function updateWorker(workerId, targetVersion, mode) { - return updateWorkerImpl(workerId, targetVersion, mode); + return updateWorkerImpl(workerId, targetVersion, mode); } diff --git a/src/eventloop.ts b/src/eventloop.ts index 16b535d..2a05874 100644 --- a/src/eventloop.ts +++ b/src/eventloop.ts @@ -27,33 +27,33 @@ export declare const runEventLoopUntilInterest: () => void; * @param promise */ export function asyncToSync(promise: Promise): T { - let success = false; - let done = false; - let result: T; - let error: any; + let success = false; + let done = false; + let result: T; + let error: any; - promise - .then((r) => { - result = r; - success = true; - done = true; - }) - .catch((e) => { - error = e; - done = true; - }); + promise + .then((r) => { + result = r; + success = true; + done = true; + }) + .catch((e) => { + error = e; + done = true; + }); - runEventLoopUntilInterest(); + runEventLoopUntilInterest(); - if (!done) { - throw new Error("asyncToSync: illegal state: not done"); - } + if (!done) { + throw new Error("asyncToSync: illegal state: not done"); + } - if (!success) { - throw error; - } + if (!success) { + throw error; + } - return result; + return result; } /** @@ -61,5 +61,5 @@ export function asyncToSync(promise: Promise): T { * @param promise */ export function asyncToSyncAsResult(promise: Promise): Result { - return Result.tryCatch(() => asyncToSync(promise)); + return Result.tryCatch(() => asyncToSync(promise)); } diff --git a/src/fetch.ts b/src/fetch.ts index b8c3e27..5659731 100644 --- a/src/fetch.ts +++ b/src/fetch.ts @@ -20,8 +20,8 @@ import type { Result } from "./result"; * @param input * @param init */ -function fetchSync(input: RequestInfo | URL, init?: RequestInit): Response { - return asyncToSync(fetch(input, init)); +export function fetchSync(input: string | URL | Request, init?: RequestInit): Response { + return asyncToSync(fetch(input, init)); } /** @@ -29,6 +29,9 @@ function fetchSync(input: RequestInfo | URL, init?: RequestInit): Response { * @param input * @param init */ -function fetchSyncAsResult(input: RequestInfo | URL, init?: RequestInit): Result { - return asyncToSyncAsResult(fetch(input, init)); +export function fetchSyncAsResult( + input: string | URL | Request, + init?: RequestInit, +): Result { + return asyncToSyncAsResult(fetch(input, init)); } diff --git a/src/guard.ts b/src/guard.ts index 8a9069e..f84a7aa 100644 --- a/src/guard.ts +++ b/src/guard.ts @@ -13,17 +13,17 @@ // limitations under the License. import { - type OplogIndex, - type PersistenceLevel, - type RetryPolicy, - getIdempotenceMode, - getOplogPersistenceLevel, - getRetryPolicy, - markBeginOperation, - markEndOperation, - setIdempotenceMode, - setOplogPersistenceLevel, - setRetryPolicy, + type OplogIndex, + type PersistenceLevel, + type RetryPolicy, + getIdempotenceMode, + getOplogPersistenceLevel, + getRetryPolicy, + markBeginOperation, + markEndOperation, + setIdempotenceMode, + setOplogPersistenceLevel, + setRetryPolicy, } from "./bindgen/bindgen"; /** @@ -31,10 +31,10 @@ import { * You must call drop on the guard once you are finished using it. */ export class PersistenceLevelGuard { - constructor(private originalLevel: PersistenceLevel) {} - drop() { - setOplogPersistenceLevel(this.originalLevel); - } + constructor(private originalLevel: PersistenceLevel) {} + drop() { + setOplogPersistenceLevel(this.originalLevel); + } } /** @@ -44,9 +44,9 @@ export class PersistenceLevelGuard { * @returns A PersistenceLevelGuard instance. */ export function usePersistenceLevel(level: PersistenceLevel) { - const originalLevel = getOplogPersistenceLevel(); - setOplogPersistenceLevel(level); - return new PersistenceLevelGuard(originalLevel); + const originalLevel = getOplogPersistenceLevel(); + setOplogPersistenceLevel(level); + return new PersistenceLevelGuard(originalLevel); } /** @@ -56,8 +56,8 @@ export function usePersistenceLevel(level: PersistenceLevel) { * @returns The result of the executed function. */ export function withPersistenceLevel(level: PersistenceLevel, f: () => R): R { - const guard = usePersistenceLevel(level); - return executeWithDrop([guard], f); + const guard = usePersistenceLevel(level); + return executeWithDrop([guard], f); } /** @@ -65,10 +65,10 @@ export function withPersistenceLevel(level: PersistenceLevel, f: () => R): R * You must call drop on the guard once you are finished using it. */ export class IdempotenceModeGuard { - constructor(private original: boolean) {} - drop() { - setIdempotenceMode(this.original); - } + constructor(private original: boolean) {} + drop() { + setIdempotenceMode(this.original); + } } /** @@ -78,9 +78,9 @@ export class IdempotenceModeGuard { * @returns An IdempotenceModeGuard instance. */ export function useIdempotenceMode(mode: boolean): IdempotenceModeGuard { - const original = getIdempotenceMode(); - setIdempotenceMode(mode); - return new IdempotenceModeGuard(original); + const original = getIdempotenceMode(); + setIdempotenceMode(mode); + return new IdempotenceModeGuard(original); } /** @@ -90,8 +90,8 @@ export function useIdempotenceMode(mode: boolean): IdempotenceModeGuard { * @returns The result of the executed function. */ export function withIdempotenceMode(mode: boolean, f: () => R): R { - const guard = useIdempotenceMode(mode); - return executeWithDrop([guard], f); + const guard = useIdempotenceMode(mode); + return executeWithDrop([guard], f); } /** @@ -99,10 +99,10 @@ export function withIdempotenceMode(mode: boolean, f: () => R): R { * You must call drop on the guard once you are finished using it. */ export class RetryPolicyGuard { - constructor(private original: RetryPolicy) {} - drop() { - setRetryPolicy(this.original); - } + constructor(private original: RetryPolicy) {} + drop() { + setRetryPolicy(this.original); + } } /** @@ -112,9 +112,9 @@ export class RetryPolicyGuard { * @returns A RetryPolicyGuard instance. */ export function useRetryPolicy(policy: RetryPolicy): RetryPolicyGuard { - const original = getRetryPolicy(); - setRetryPolicy(policy); - return new RetryPolicyGuard(original); + const original = getRetryPolicy(); + setRetryPolicy(policy); + return new RetryPolicyGuard(original); } /** @@ -124,8 +124,8 @@ export function useRetryPolicy(policy: RetryPolicy): RetryPolicyGuard { * @returns The result of the executed function. */ export function withRetryPolicy(policy: RetryPolicy, f: () => R): R { - const guard = useRetryPolicy(policy); - return executeWithDrop([guard], f); + const guard = useRetryPolicy(policy); + return executeWithDrop([guard], f); } /** @@ -133,10 +133,10 @@ export function withRetryPolicy(policy: RetryPolicy, f: () => R): R { * You must call drop on the guard once you are finished using it. */ export class AtomicOperationGuard { - constructor(private begin: OplogIndex) {} - drop() { - markEndOperation(this.begin); - } + constructor(private begin: OplogIndex) {} + drop() { + markEndOperation(this.begin); + } } /** @@ -145,8 +145,8 @@ export class AtomicOperationGuard { * @returns An AtomicOperationGuard instance. */ export function markAtomicOperation(): AtomicOperationGuard { - const begin = markBeginOperation(); - return new AtomicOperationGuard(begin); + const begin = markBeginOperation(); + return new AtomicOperationGuard(begin); } /** @@ -155,8 +155,8 @@ export function markAtomicOperation(): AtomicOperationGuard { * @returns The result of the executed function. */ export function atomically(f: () => T): T { - const guard = markAtomicOperation(); - return executeWithDrop([guard], f); + const guard = markAtomicOperation(); + return executeWithDrop([guard], f); } /** @@ -166,17 +166,17 @@ export function atomically(f: () => T): T { * @returns The result of the executed function. */ export function executeWithDrop void }, R>( - resources: [Resource], - fn: () => R, + resources: [Resource], + fn: () => R, ): R { - try { - const result = fn(); - dropAll(true, resources); - return result; - } catch (e) { - dropAll(false, resources); - throw e; - } + try { + const result = fn(); + dropAll(true, resources); + return result; + } catch (e) { + dropAll(false, resources); + throw e; + } } /** @@ -185,28 +185,28 @@ export function executeWithDrop void }, R>( * @throws DropError if any errors occur during the dropping process. */ export function dropAll void }>( - throwOnError: boolean, - resources: [Resource], + throwOnError: boolean, + resources: [Resource], ) { - const errors = []; - for (const resource of resources) { - try { - resource.drop(); - } catch (e) { - errors.push(e); - } - } - if (throwOnError && errors.length > 0) { - throw new DropError(errors); - } + const errors = []; + for (const resource of resources) { + try { + resource.drop(); + } catch (e) { + errors.push(e); + } + } + if (throwOnError && errors.length > 0) { + throw new DropError(errors); + } } /** * Custom error class for errors that occur during the dropping of resources. */ class DropError extends Error { - constructor(public errors: Error[]) { - const message = errors.map((e) => e.message).join(", "); - super(`Error dropping resources: ${message}`); - } + constructor(public errors: Error[]) { + const message = errors.map((e) => e.message).join(", "); + super(`Error dropping resources: ${message}`); + } } diff --git a/src/result.ts b/src/result.ts index 9e075b7..9698e6b 100644 --- a/src/result.ts +++ b/src/result.ts @@ -1,231 +1,231 @@ const prototype = { - /** - * Returns `this.value` if `this` is a successful result, otherwise throws a TypeError. - * @example Returns the payload of a successful result. - * Result.ok(123).unwrap() // 123 - * @example Throws the payload of a failed result. - * Result.err('error').unwrap() // throws 'error' - */ - unwrap, + /** + * Returns `this.value` if `this` is a successful result, otherwise throws a TypeError. + * @example Returns the payload of a successful result. + * Result.ok(123).unwrap() // 123 + * @example Throws the payload of a failed result. + * Result.err('error').unwrap() // throws 'error' + */ + unwrap, - /** - * - * Returns `this.error` if `this` is a failed result, otherwise throws a TypeError. - * @example Returns the payload of a failed result. - * Result. - */ - unwrapErr, - /** - * Returns the payload of the result. - * @example Returns the payload of a successful result. - * Result.ok(123).toUnion() // 123 - * @example Returns the payload of a failed result. - * Result.err('error').toUnion() // 'error' - */ - toUnion, - /** - * Return the result of applying one of the given functions to the payload. - * @example - * Result.ok(123).match((x) => x * 2, (x: string) => x + '!') // 246 - * Result.err('error').match((x: number) => x * 2, (x) => x + '!') // 'error!' - */ - match, - /** - * Creates a Result value by modifying the payload of the successful result using the given function. - * @example - * Result.ok(123).map((x) => x * 2) // Result.ok(246) - * Result.err('error').map((x: number) => x * 2) // Result.err('error') - */ - map, - /** - * Creates a Result value by modifying the payload of the failed result using the given function. - * @example - * Result.ok(123).mapError((x: string) => x + '!') // Result.ok(123) - * Result.err('error').mapError((x) => x + '!') // Result.err('error!') - */ - mapError, - /** - * Calls the given function with the payload of the result and returns the result unchanged. - */ - tap, - /** - * Maps the payload of the successful result and flattens the nested Result type. - * @example - * Result.ok(123).flatMap((x) => Result.ok(x * 2)) // Result.ok(246) - * Result.ok(123).flatMap((x) => Result.err('error')) // Result.err('error') - * Result.err('error').flatMap((x: number) => Result.ok(x * 2)) // Result.err('error') - * Result.err('error').flatMap((x) => Result.err('failure')) // Result.err('error') - */ - flatMap, - /** - * Flattens the nested Result type. - * @example - * Result.ok(Result.ok(123)).flatten() // Result.ok(123) - * Result.ok(Result.err('error')).flatten() // Result.err('error') - * Result.err('error').flatten() // Result.err('error') - */ - flatten, - /** - * Perform a safe cast of the error type to the given class. If the payload of the failed result is not instance of constructor, throws TypeError. - * @example - * const result: Result = Result.tryCatch(() => { - * if (Math.random() >= 0) { - * throw new Error('error') - * } else { - * return 123 - * } - * }).assertErrorInstanceOf(Error) - */ - assertErrorInstanceOf, + /** + * + * Returns `this.error` if `this` is a failed result, otherwise throws a TypeError. + * @example Returns the payload of a failed result. + * Result. + */ + unwrapErr, + /** + * Returns the payload of the result. + * @example Returns the payload of a successful result. + * Result.ok(123).toUnion() // 123 + * @example Returns the payload of a failed result. + * Result.err('error').toUnion() // 'error' + */ + toUnion, + /** + * Return the result of applying one of the given functions to the payload. + * @example + * Result.ok(123).match((x) => x * 2, (x: string) => x + '!') // 246 + * Result.err('error').match((x: number) => x * 2, (x) => x + '!') // 'error!' + */ + match, + /** + * Creates a Result value by modifying the payload of the successful result using the given function. + * @example + * Result.ok(123).map((x) => x * 2) // Result.ok(246) + * Result.err('error').map((x: number) => x * 2) // Result.err('error') + */ + map, + /** + * Creates a Result value by modifying the payload of the failed result using the given function. + * @example + * Result.ok(123).mapError((x: string) => x + '!') // Result.ok(123) + * Result.err('error').mapError((x) => x + '!') // Result.err('error!') + */ + mapError, + /** + * Calls the given function with the payload of the result and returns the result unchanged. + */ + tap, + /** + * Maps the payload of the successful result and flattens the nested Result type. + * @example + * Result.ok(123).flatMap((x) => Result.ok(x * 2)) // Result.ok(246) + * Result.ok(123).flatMap((x) => Result.err('error')) // Result.err('error') + * Result.err('error').flatMap((x: number) => Result.ok(x * 2)) // Result.err('error') + * Result.err('error').flatMap((x) => Result.err('failure')) // Result.err('error') + */ + flatMap, + /** + * Flattens the nested Result type. + * @example + * Result.ok(Result.ok(123)).flatten() // Result.ok(123) + * Result.ok(Result.err('error')).flatten() // Result.err('error') + * Result.err('error').flatten() // Result.err('error') + */ + flatten, + /** + * Perform a safe cast of the error type to the given class. If the payload of the failed result is not instance of constructor, throws TypeError. + * @example + * const result: Result = Result.tryCatch(() => { + * if (Math.random() >= 0) { + * throw new Error('error') + * } else { + * return 123 + * } + * }).assertErrorInstanceOf(Error) + */ + assertErrorInstanceOf, } as const; /** Type representing success or failure. */ export type Result = Result.Ok | Result.Err; export namespace Result { - /** - * The type of a successful result. - * @example - * const success: Result.ok = Result.ok(123) - */ - export type Ok = typeof prototype & { - readonly value: T; - readonly error?: never; - readonly isOk: true; - readonly isErr: false; - }; + /** + * The type of a successful result. + * @example + * const success: Result.ok = Result.ok(123) + */ + export type Ok = typeof prototype & { + readonly value: T; + readonly error?: never; + readonly isOk: true; + readonly isErr: false; + }; - /** - * A failed result. - * @example - * const failure: Result.err = Result.err('error') - */ - export type Err = typeof prototype & { - readonly value?: never; - readonly error: E; - readonly isOk: false; - readonly isErr: true; - }; + /** + * A failed result. + * @example + * const failure: Result.err = Result.err('error') + */ + export type Err = typeof prototype & { + readonly value?: never; + readonly error: E; + readonly isOk: false; + readonly isErr: true; + }; - const UNIT = ok(undefined); + const UNIT = ok(undefined); - /** - * @returns A successful result with no payload. - */ - export function unit(): Ok { - return UNIT; - } + /** + * @returns A successful result with no payload. + */ + export function unit(): Ok { + return UNIT; + } - /** - * Create a successful result. - */ - export function ok(value: T): Result.Ok { - return withPrototype({ value, isOk: true, isErr: false }, prototype); - } + /** + * Create a successful result. + */ + export function ok(value: T): Result.Ok { + return withPrototype({ value, isOk: true, isErr: false }, prototype); + } - /** - * Create an error result. - */ - export function err(error: E): Err { - return withPrototype({ error, isOk: false, isErr: true }, prototype); - } + /** + * Create an error result. + */ + export function err(error: E): Err { + return withPrototype({ error, isOk: false, isErr: true }, prototype); + } - /** - * Create a result from a function that may throw an error. - */ - export function tryCatch(f: () => T): Result { - try { - return ok(f()); - } catch (error) { - return err(error); - } - } + /** + * Create a result from a function that may throw an error. + */ + export function tryCatch(f: () => T): Result { + try { + return ok(f()); + } catch (error) { + return err(error); + } + } - export function fromNullish(value: null): Result.Err; - export function fromNullish(value: undefined): Result.Err; - export function fromNullish(value: null | undefined): Result.Err; - export function fromNullish(value: T): Result.Ok; - export function fromNullish(value: T | null): Result; - export function fromNullish(value: T | undefined): Result; - export function fromNullish(value: T | null | undefined): Result; - export function fromNullish(value: T | null | undefined) { - return value != null ? Result.ok(value) : Result.err(value); - } + export function fromNullish(value: null): Result.Err; + export function fromNullish(value: undefined): Result.Err; + export function fromNullish(value: null | undefined): Result.Err; + export function fromNullish(value: T): Result.Ok; + export function fromNullish(value: T | null): Result; + export function fromNullish(value: T | undefined): Result; + export function fromNullish(value: T | null | undefined): Result; + export function fromNullish(value: T | null | undefined) { + return value != null ? Result.ok(value) : Result.err(value); + } - export function all(results: readonly Result.Ok[]): Result.Ok; - export function all(results: readonly Result[]): Result; - export function all(results: readonly Result[]): Result { - const values: T[] = []; - for (const result of results) { - if (result.isErr) return result; - values.push(result.value); - } - return Result.ok(values); - } + export function all(results: readonly Result.Ok[]): Result.Ok; + export function all(results: readonly Result[]): Result; + export function all(results: readonly Result[]): Result { + const values: T[] = []; + for (const result of results) { + if (result.isErr) return result; + values.push(result.value); + } + return Result.ok(values); + } } function withPrototype(target: T, prototype: P): T & Omit { - return Object.assign(Object.create(prototype), target); + return Object.assign(Object.create(prototype), target); } function unwrap(this: Result.Ok): T; function unwrap(this: Result.Err): never; function unwrap(this: Result): T; function unwrap(this: Result): T { - if (this.isOk) return this.value; - else throw new TypeError(`unwrap·called·on·Err·result:·${this.error}`); + if (this.isOk) return this.value; + else throw new TypeError(`unwrap·called·on·Err·result:·${this.error}`); } function unwrapErr(this: Result.Ok): never; function unwrapErr(this: Result.Err): E; function unwrapErr(this: Result): E; function unwrapErr(this: Result): E { - if (this.isOk) throw new TypeError(`unwrapErr·called·on·Ok·result:·${this.value}`); - else return this.error; + if (this.isOk) throw new TypeError(`unwrapErr·called·on·Ok·result:·${this.value}`); + else return this.error; } function toUnion(this: Result.Ok): T; function toUnion(this: Result.Err): E; function toUnion(this: Result): T | E; function toUnion(this: Result): T | E { - if (this.isOk) return this.value; - else return this.error; + if (this.isOk) return this.value; + else return this.error; } function match(this: Result.Ok, f: (value: T) => T2, g: (error: E) => E2): T2; function match(this: Result.Err, f: (value: T) => T2, g: (error: E) => E2): E2; function match(this: Result, f: (value: T) => T2, g: (error: E) => E2): T2 | E2; function match( - this: Result, - f: (value: T) => T2, - g: (error: E) => E2, + this: Result, + f: (value: T) => T2, + g: (error: E) => E2, ): T2 | E2 { - if (this.isOk) return f(this.value); - else return g(this.error); + if (this.isOk) return f(this.value); + else return g(this.error); } function map(this: Result.Ok, f: (value: T) => T2): Result.Ok; function map(this: Result.Err, f: (value: T) => T2): Result.Err; function map(this: Result, f: (value: T) => T2): Result; function map(this: Result, f: (value: T) => T2): Result { - if (this.isErr) return this; - else return Result.ok(f(this.value)); + if (this.isErr) return this; + else return Result.ok(f(this.value)); } function mapError(this: Result.Ok, f: (error: E) => E2): Result.Ok; function mapError(this: Result.Err, f: (error: E) => E2): Result.Err; function mapError(this: Result, f: (error: E) => E2): Result; function mapError(this: Result, f: (error: E) => E2): Result { - if (this.isOk) return this; - else return Result.err(f(this.error)); + if (this.isOk) return this; + else return Result.err(f(this.error)); } function tap(this: Result.Err, f: (error: E) => void): Result.Err; function tap(this: Result.Ok, f: (value: T) => void): Result.Ok; function tap(this: Result, f: (value: T) => void): Result; function tap(this: Result, f: (value: T) => void): Result { - if (this.isOk) f(this.value); - return this; + if (this.isOk) f(this.value); + return this; } function flatMap(this: Result.Ok, f: (value: T) => Result.Ok): Result.Ok; @@ -234,16 +234,16 @@ function flatMap(this: Result.Ok, f: (value: T) => Result) function flatMap(this: Result.Err, f: (value: T) => Result): Result.Err; function flatMap(this: Result, f: (value: T) => Result.Ok): Result; function flatMap( - this: Result, - f: (value: T) => Result.Err, + this: Result, + f: (value: T) => Result.Err, ): Result.Err; function flatMap( - this: Result, - f: (value: T) => Result, + this: Result, + f: (value: T) => Result, ): Result; function flatMap(this: Result, f: (value: T) => Result) { - if (this.isErr) return this; - else return f(this.value); + if (this.isErr) return this; + else return f(this.value); } function flatten(this: Result.Err): Result.Err; @@ -254,31 +254,31 @@ function flatten(this: Result, E>): Result; function flatten(this: Result, E2>): Result.Err; function flatten(this: Result, E2>): Result; function flatten(this: Result, E2>): Result { - if (this.isErr) return this; - else return this.value; + if (this.isErr) return this; + else return this.value; } function assertErrorInstanceOf any>( - this: Result.Ok, - constructor: C, + this: Result.Ok, + constructor: C, ): Result.Ok; function assertErrorInstanceOf any>( - this: Result.Err, - constructor: C, + this: Result.Err, + constructor: C, ): Result.Err>; function assertErrorInstanceOf any>( - this: Result, - constructor: C, + this: Result, + constructor: C, ): Result>; function assertErrorInstanceOf any>( - this: Result, - constructor: C, + this: Result, + constructor: C, ): Result> { - if (this.isOk) return this; + if (this.isOk) return this; - if (this.error instanceof constructor) return this as any; + if (this.error instanceof constructor) return this as any; - throw new TypeError( - `Assertion failed: Expected error to be an instance of ${constructor.name}.`, - ); + throw new TypeError( + `Assertion failed: Expected error to be an instance of ${constructor.name}.`, + ); } diff --git a/src/transaction.ts b/src/transaction.ts index 92e209f..536c95d 100644 --- a/src/transaction.ts +++ b/src/transaction.ts @@ -23,21 +23,21 @@ import { Result } from "./result"; * Operations can also be constructed from closures using `operation`. */ export interface Operation { - /** - * The action to execute. - * @param input - The input to the operation. - * @returns The result of the operation. - */ - execute(input: In): Result; - - /** - * Compensation to perform in case of failure. - * Compensations should not throw errors. - * @param input - The input to the operation. - * @param result - The result of the operation. - * @returns The result of the compensation. - */ - compensate(input: In, result: Out): Result; + /** + * The action to execute. + * @param input - The input to the operation. + * @returns The result of the operation. + */ + execute(input: In): Result; + + /** + * Compensation to perform in case of failure. + * Compensations should not throw errors. + * @param input - The input to the operation. + * @param result - The result of the operation. + * @returns The result of the compensation. + */ + compensate(input: In, result: Out): Result; } /** @@ -47,114 +47,114 @@ export interface Operation { * @returns The created Operation. */ export function operation( - execute: (input: In) => Result, - compensate: (input: In, result: Out) => Result, + execute: (input: In) => Result, + compensate: (input: In, result: Out) => Result, ): Operation { - return new OperationImpl(execute, compensate); + return new OperationImpl(execute, compensate); } class OperationImpl implements Operation { - constructor( - public readonly execute: (input: In) => Result, - public readonly compensate: (input: In, result: Out) => Result, - ) {} + constructor( + public readonly execute: (input: In) => Result, + public readonly compensate: (input: In, result: Out) => Result, + ) {} } class InfallibleTransaction { - private compensations: (() => void)[] = []; - - constructor(private readonly beginOplogIndex: OplogIndex) {} - - /** - * Executes an operation within the infallible transaction. - * @param operation - The operation to execute. - * @param input - The input to the operation. - * @returns The result of the operation. - */ - execute(operation: Operation, input: In): Out { - const result = operation.execute(input); - if (result.isOk) { - this.compensations.push( - // Compensations cannot fail in infallible transactions. - () => { - const compensationResult = operation.compensate(input, result.value); - if (compensationResult.isErr) { - throw new Error("Compensation action failed"); - } - }, - ); - return result.value; - } else { - this.retry(); - throw new Error("Unreachable code"); - } - } - - private retry(): void { - // Rollback all the compensations in reverse order - for (let i = this.compensations.length - 1; i >= 0; i--) { - this.compensations[i](); - } - setOplogIndex(this.beginOplogIndex); - } + private compensations: (() => void)[] = []; + + constructor(private readonly beginOplogIndex: OplogIndex) {} + + /** + * Executes an operation within the infallible transaction. + * @param operation - The operation to execute. + * @param input - The input to the operation. + * @returns The result of the operation. + */ + execute(operation: Operation, input: In): Out { + const result = operation.execute(input); + if (result.isOk) { + this.compensations.push( + // Compensations cannot fail in infallible transactions. + () => { + const compensationResult = operation.compensate(input, result.value); + if (compensationResult.isErr) { + throw new Error("Compensation action failed"); + } + }, + ); + return result.value; + } else { + this.retry(); + throw new Error("Unreachable code"); + } + } + + private retry(): void { + // Rollback all the compensations in reverse order + for (let i = this.compensations.length - 1; i >= 0; i--) { + this.compensations[i](); + } + setOplogIndex(this.beginOplogIndex); + } } class FallibleTransaction { - private compensations: (() => Result)[] = []; - - /** - * Executes an operation within the fallible transaction. - * @param operation - The operation to execute. - * @param input - The input to the operation. - * @returns The result of the operation. - */ - execute( - operation: Operation, - input: In, - ): Result { - const result = operation.execute(input); - if (result.isOk) { - this.compensations.push(() => { - return operation.compensate(input, result.value); - }); - return result; - } else { - return result; - } - } - - /** - * Handles the failure of the fallible transaction. - * @param error - The error that caused the failure. - * @returns The transaction failure result. - */ - onFailure(error: Err): TransactionFailure { - for (let i = this.compensations.length - 1; i >= 0; i--) { - const compensationResult = this.compensations[i](); - if (compensationResult.isErr) { - return { - type: "FailedAndRolledBackPartially", - error, - compensationFailure: compensationResult.error, - }; - } - } - return { - type: "FailedAndRolledBackCompletely", - error, - }; - } + private compensations: (() => Result)[] = []; + + /** + * Executes an operation within the fallible transaction. + * @param operation - The operation to execute. + * @param input - The input to the operation. + * @returns The result of the operation. + */ + execute( + operation: Operation, + input: In, + ): Result { + const result = operation.execute(input); + if (result.isOk) { + this.compensations.push(() => { + return operation.compensate(input, result.value); + }); + return result; + } else { + return result; + } + } + + /** + * Handles the failure of the fallible transaction. + * @param error - The error that caused the failure. + * @returns The transaction failure result. + */ + onFailure(error: Err): TransactionFailure { + for (let i = this.compensations.length - 1; i >= 0; i--) { + const compensationResult = this.compensations[i](); + if (compensationResult.isErr) { + return { + type: "FailedAndRolledBackPartially", + error, + compensationFailure: compensationResult.error, + }; + } + } + return { + type: "FailedAndRolledBackCompletely", + error, + }; + } } export type TransactionResult = Result>; export type TransactionFailure = - | { type: "FailedAndRolledBackCompletely"; error: Err } - | { - type: "FailedAndRolledBackPartially"; - error: Err; - compensationFailure: Err; - }; + | { type: "FailedAndRolledBackCompletely"; error: Err } + | { + type: "FailedAndRolledBackPartially"; + error: Err; + compensationFailure: Err; + }; /** * Executes an infallible transaction. @@ -174,10 +174,10 @@ export type TransactionFailure = * @returns The result of the transaction. */ export function infallibleTransaction(f: (tx: InfallibleTransaction) => Out): Out { - const guard = markAtomicOperation(); - const beginOplogIndex = getOplogIndex(); - const tx = new InfallibleTransaction(beginOplogIndex); - return executeWithDrop([guard], () => f(tx)); + const guard = markAtomicOperation(); + const beginOplogIndex = getOplogIndex(); + const tx = new InfallibleTransaction(beginOplogIndex); + return executeWithDrop([guard], () => f(tx)); } /** @@ -194,19 +194,19 @@ export function infallibleTransaction(f: (tx: InfallibleTransaction) => Out * @returns The result of the transaction. */ export function fallibleTransaction( - f: (tx: FallibleTransaction) => Result, + f: (tx: FallibleTransaction) => Result, ): TransactionResult { - const guard = markAtomicOperation(); - const tx = new FallibleTransaction(); - const execute = () => { - const result = f(tx); - if (result.isOk) { - return Result.ok(result.value); - } else { - return Result.err(tx.onFailure(result.error)); - } - }; - return executeWithDrop([guard], execute); + const guard = markAtomicOperation(); + const tx = new FallibleTransaction(); + const execute = () => { + const result = f(tx); + if (result.isOk) { + return Result.ok(result.value); + } else { + return Result.err(tx.onFailure(result.error)); + } + }; + return executeWithDrop([guard], execute); } /** @@ -231,5 +231,5 @@ export function fallibleTransaction( * */ export type OperationErrors[]> = { - [K in keyof T]: T[K] extends Operation ? Err : never; + [K in keyof T]: T[K] extends Operation ? Err : never; }[number]; diff --git a/tsconfig.json b/tsconfig.json index d97567c..6fc4aaa 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,9 @@ "target": "ES2020", "declaration": true, "outDir": "./out", - "lib": ["es2020", "dom"] + "lib": ["es2020"], + "types": ["node"], + "moduleResolution": "Node" }, - "include": ["src/**/*"] + "include": ["src/**/*.ts"] }