diff --git a/packages/core/src/consolidated.ts b/packages/core/src/consolidated.ts index 0d93b41..acceb71 100644 --- a/packages/core/src/consolidated.ts +++ b/packages/core/src/consolidated.ts @@ -7,7 +7,7 @@ import type { GroupMetadata, GroupMetadataV2, } from "./metadata.js"; -import { json_decode_object, json_encode_object } from "./util.js"; +import { except, json_decode_object, json_encode_object } from "./util.js"; type ConsolidatedMetadata = { metadata: Record; @@ -134,10 +134,8 @@ export async function withConsolidated( export async function tryWithConsolidated( store: Store, ): Promise | Store> { - return withConsolidated(store).catch((e: unknown) => { - if (e instanceof NodeNotFoundError) { - return store; - } - throw e; + return withConsolidated(store).catch((error: unknown) => { + except(error, NodeNotFoundError); + return store; }); } diff --git a/packages/core/src/open.ts b/packages/core/src/open.ts index d4c3173..ff6e8f6 100644 --- a/packages/core/src/open.ts +++ b/packages/core/src/open.ts @@ -9,6 +9,7 @@ import type { } from "./metadata.js"; import { ensure_correct_scalar, + except, json_decode_object, v2_to_v3_array_metadata, v2_to_v3_group_metadata, @@ -64,8 +65,8 @@ async function open_v2( if (options.kind === "array") return open_array_v2(loc, attrs); if (options.kind === "group") return open_group_v2(loc, attrs); return open_array_v2(loc, attrs).catch((err) => { - if (err instanceof NodeNotFoundError) return open_group_v2(loc, attrs); - throw err; + except(err, NodeNotFoundError); + return open_group_v2(loc, attrs); }); } @@ -192,10 +193,8 @@ export async function open( let open_primary = version_max === "v2" ? open.v2 : open.v3; let open_secondary = version_max === "v2" ? open.v3 : open.v2; return open_primary(location, options).catch((err) => { - if (err instanceof NodeNotFoundError) { - return open_secondary(location, options); - } - throw err; + except(err, NodeNotFoundError); + return open_secondary(location, options); }); } diff --git a/packages/core/src/util.ts b/packages/core/src/util.ts index 82e552f..736b4a8 100644 --- a/packages/core/src/util.ts +++ b/packages/core/src/util.ts @@ -292,3 +292,49 @@ export function ensure_correct_scalar( } return metadata.fill_value; } + +// biome-ignore lint/suspicious/noExplicitAny: Necessary for type inference +type InstanceType = T extends new (...args: any[]) => infer R ? R : never; + +// biome-ignore lint/suspicious/noExplicitAny: Necessary for type inference +type UnionInstanceType any)[]> = + InstanceType; + +/** + * Ensures an error matches expected type(s), otherwise rethrows. + * + * Unmatched errors bubble up, like Python's `except`. Narrows error types for + * type-safe property access. + * + * @see {@link https://gist.github.com/manzt/3702f19abb714e21c22ce48851c75abf} + * + * @example + * ```ts + * class DatabaseError extends Error { } + * class NetworkError extends Error { } + * + * try { + * await db.query(); + * } catch (err) { + * except(err, DatabaseError, NetworkError); + * err // DatabaseError | NetworkError + * } + * ``` + * + * @param error - The error to check + * @param errorClasses - Expected error type(s) + * @throws The original error if it doesn't match expected type(s) + */ +export function except< + ErrorClasses extends readonly (new ( + // biome-ignore lint/suspicious/noExplicitAny: Necessary for type inference + ...args: any[] + ) => Error)[], +>( + error: unknown, + ...errorClasses: ErrorClasses +): asserts error is UnionInstanceType { + if (!errorClasses.some((ErrorClass) => error instanceof ErrorClass)) { + throw error; + } +}