enso/app/gui2/shared/util/data/result.ts
Kaz Wesley 343a644051
Syntactic synchronization, automatic parentheses, metadata in Ast (#8893)
- Synchronize Y.Js clients by AST (implements #8237).
- Before committing an edit, insert any parentheses-nodes needed for the concrete syntax to reflect tree structure (fixes #8884).
- Move `externalId` and all node metadata into a Y.Map owned by each `Ast`. This allows including metadata changes in an edit, enables Y.Js merging of changes to different metadata fields, and will enable the use of Y.Js objects in metadata. (Implements #8804.)

### Important Notes

- Metadata is now set and retrieved through accessors on the `Ast` objects.
- Since some metadata edits need to take effect in real time (e.g. node dragging), new lower-overhead APIs (`commitDirect`, `skipTreeRepair`) are provided for careful use in certain cases.
- The client is now bundled as ESM.
- The build script cleans up git-untracked generated files in an outdated location, which fixes lint errors related to `src/generated` that may occur when switching branches.
2024-02-02 10:22:18 +01:00

86 lines
2.3 KiB
TypeScript

/** @file A generic type that can either hold a value representing a successful result,
* or an error. */
import { isSome, type Opt } from './opt'
export type Result<T = undefined, E = string> =
| { ok: true; value: T }
| { ok: false; error: ResultError<E> }
export function Ok<T>(data: T): Result<T, never> {
return { ok: true, value: data }
}
export function Err<E>(error: E): Result<never, E> {
return { ok: false, error: new ResultError(error) }
}
export function okOr<T, E>(data: Opt<T>, error: E): Result<T, E> {
if (isSome(data)) return Ok(data)
else return Err(error)
}
export function unwrap<T, E>(result: Result<T, E>): T {
if (result.ok) return result.value
else throw result.error
}
export function mapOk<T, U, E>(result: Result<T, E>, f: (value: T) => U): Result<U, E> {
if (result.ok) return Ok(f(result.value))
else return result
}
export function isResult(v: unknown): v is Result {
return (
v != null &&
typeof v === 'object' &&
'ok' in v &&
typeof v.ok === 'boolean' &&
('value' in v || ('error' in v && v.error instanceof ResultError))
)
}
export class ResultError<E = string> {
payload: E
context: (() => string)[]
constructor(payload: E) {
this.payload = payload
this.context = []
}
log(preamble: string = 'Error') {
const ctx =
this.context.length > 0 ? `\n${Array.from(this.context, (ctx) => ctx()).join('\n')}` : ''
console.error(`${preamble}: ${this.payload}${ctx}`)
}
}
export function withContext<T, E>(context: () => string, f: () => Result<T, E>): Result<T, E> {
const result = f()
if (result == null) {
throw new Error('withContext: f() returned null or undefined')
}
if (!result.ok) result.error.context.splice(0, 0, context)
return result
}
/**
* Catch promise rejection of provided types and convert them to a Result type.
*/
export function rejectionToResult<ErrorKind extends new (...args: any[]) => any>(
errorKinds: ErrorKind | ErrorKind[],
): <T>(promise: Promise<T>) => Promise<Result<T, InstanceType<ErrorKind>>> {
const errorKindArray = Array.isArray(errorKinds) ? errorKinds : [errorKinds]
return async (promise) => {
try {
return Ok(await promise)
} catch (error) {
for (const errorKind of errorKindArray) {
if (error instanceof errorKind) return Err(error)
}
throw error
}
}
}