From e1893b1a8898e4f5747dbbcc64149290900d9229 Mon Sep 17 00:00:00 2001 From: Trevor Manz Date: Sun, 12 Dec 2021 13:27:34 -0500 Subject: [PATCH] Use conditional types to allow readonly store implementations (#32) --- src/lib/hierarchy.ts | 15 +- src/storage/fetch.ts | 9 +- src/storage/fs.ts | 12 +- src/storage/mem.ts | 11 +- src/types.ts | 47 +++--- src/v2.ts | 189 +++++++++++++----------- src/v3.ts | 337 +++++++++++++++++++++++++------------------ test/common.js | 5 +- 8 files changed, 361 insertions(+), 264 deletions(-) diff --git a/src/lib/hierarchy.ts b/src/lib/hierarchy.ts index 29162ab..2c1b989 100644 --- a/src/lib/hierarchy.ts +++ b/src/lib/hierarchy.ts @@ -6,6 +6,7 @@ import type { Attrs, DataType, Hierarchy, + ReadableStore, Scalar, Store, TypedArray, @@ -13,7 +14,7 @@ import type { } from "../types"; import type { Codec } from "numcodecs"; -export class Node { +export class Node { constructor(public readonly store: S, public readonly path: Path) {} get name() { @@ -22,7 +23,7 @@ export class Node { } export class Group< - S extends Store, + S extends Store | ReadableStore, H extends Hierarchy, P extends AbsolutePath = AbsolutePath, > extends Node { @@ -85,7 +86,7 @@ export class Group< interface ExplicitGroupProps< P extends AbsolutePath, - S extends Store, + S extends Store | ReadableStore, H extends Hierarchy, > { store: S; @@ -95,7 +96,7 @@ interface ExplicitGroupProps< } export class ExplicitGroup< - S extends Store, + S extends Store | ReadableStore, H extends Hierarchy, P extends AbsolutePath = AbsolutePath, > extends Group { @@ -118,7 +119,7 @@ export class ExplicitGroup< } export class ImplicitGroup< - S extends Store, + S extends Store | ReadableStore, H extends Hierarchy, P extends AbsolutePath = AbsolutePath, > extends Group {} @@ -126,7 +127,7 @@ export class ImplicitGroup< export interface ZarrArrayProps< P extends AbsolutePath, D extends DataType, - S extends Store, + S extends ReadableStore, > { store: S; shape: number[]; @@ -141,7 +142,7 @@ export interface ZarrArrayProps< export class ZarrArray< D extends DataType, - S extends Store, + S extends ReadableStore, P extends AbsolutePath = AbsolutePath, > extends Node { readonly shape: number[]; diff --git a/src/storage/fetch.ts b/src/storage/fetch.ts index 4c571b4..8382cee 100644 --- a/src/storage/fetch.ts +++ b/src/storage/fetch.ts @@ -1,5 +1,4 @@ -import ReadOnlyStore from "./readonly"; -import type { AbsolutePath } from "../types"; +import type { AbsolutePath, Async, Readable } from "../types"; function resolve(root: string | URL, path: AbsolutePath): URL { const base = typeof root === "string" ? new URL(root) : root; @@ -13,10 +12,8 @@ function resolve(root: string | URL, path: AbsolutePath): URL { return resolved; } -class FetchStore extends ReadOnlyStore { - constructor(public url: string | URL) { - super(); - } +class FetchStore implements Async> { + constructor(public url: string | URL) {} async get(key: AbsolutePath, opts: RequestInit = {}): Promise { const { href } = resolve(this.url, key); diff --git a/src/storage/fs.ts b/src/storage/fs.ts index fd74ee0..76c2700 100644 --- a/src/storage/fs.ts +++ b/src/storage/fs.ts @@ -1,9 +1,17 @@ import * as fs from "node:fs"; import * as path from "node:path"; -import type { AbsolutePath, AsyncStore, PrefixPath, RootPath } from "../types"; +import type { + AbsolutePath, + Async, + ExtendedReadable, + PrefixPath, + Readable, + RootPath, + Writeable, +} from "../types"; -class FileSystemStore implements AsyncStore { +class FileSystemStore implements Async { constructor(public root: string) {} get(key: AbsolutePath): Promise { diff --git a/src/storage/mem.ts b/src/storage/mem.ts index d23393d..ddbd904 100644 --- a/src/storage/mem.ts +++ b/src/storage/mem.ts @@ -1,6 +1,13 @@ -import type { PrefixPath, RootPath, SyncStore } from "../types"; +import type { + ExtendedReadable, + PrefixPath, + Readable, + RootPath, + Writeable, +} from "../types"; -class MemoryStore extends Map implements SyncStore { +class MemoryStore extends Map + implements Readable, Writeable, ExtendedReadable { list_prefix(prefix: RootPath | PrefixPath) { const items = []; for (const path of super.keys()) { diff --git a/src/types.ts b/src/types.ts index ca8e464..10977c1 100644 --- a/src/types.ts +++ b/src/types.ts @@ -29,32 +29,30 @@ export interface ListDirResult { prefixes: string[]; } -export interface SyncStore { - get(key: AbsolutePath, opts?: GetOptions): Uint8Array | undefined; - has(key: AbsolutePath): boolean; +export type Async> = { + [Key in keyof T]: (...args: Parameters) => Promise>; +}; + +export interface Readable { + get(key: AbsolutePath, opts?: Opts): Uint8Array | undefined; +} + +export interface Writeable { set(key: AbsolutePath, value: Uint8Array): void; - delete(key: AbsolutePath): boolean; - list_prefix(key: RootPath | PrefixPath): string[]; - list_dir(key?: RootPath | PrefixPath): ListDirResult; } -export interface AsyncStore { - get(key: AbsolutePath, opts?: GetOptions): Promise; - has(key: AbsolutePath): Promise; - set(key: AbsolutePath, value: Uint8Array): Promise; - delete(key: AbsolutePath): Promise; - list_prefix(key: RootPath | PrefixPath): Promise; - list_dir(key?: RootPath | PrefixPath): Promise; +export interface ExtendedReadable extends Readable { + list_prefix(key: RootPath | PrefixPath): string[]; + list_dir(key?: RootPath | PrefixPath): ListDirResult; } -export type Store = SyncStore | AsyncStore; export type Attrs = Record; export type ArraySelection = null | (number | null | Slice)[]; -export interface Hierarchy { +export interface Hierarchy> { // read-only - has(path: string): Promise; + has(path: AbsolutePath): Promise; get(path: Path): Promise< | ZarrArray | ExplicitGroup, Path> @@ -68,21 +66,30 @@ export interface Hierarchy { ): Promise, Path>>; get_implicit_group( path: Path, - ): Promise, Path>>; - get_children(path?: AbsolutePath): Promise>; + ): Store extends (ExtendedReadable | Async) + ? Promise, Path>> + : never; + get_children( + path?: AbsolutePath, + ): Store extends (ExtendedReadable | Async) + ? Promise> + : never; // write create_group( path: Path, props?: { attrs?: Attrs }, - ): Promise, Path>>; + ): Store extends (Writeable | Async) + ? Promise, Path>> + : never; create_array< Path extends AbsolutePath, Dtype extends DataType, >( path: Path, props: CreateArrayProps, - ): Promise>; + ): Store extends (Writeable | Async) ? Promise> + : never; } type RequiredArrayProps = { diff --git a/src/v2.ts b/src/v2.ts index 0ab2de8..184298c 100644 --- a/src/v2.ts +++ b/src/v2.ts @@ -1,17 +1,19 @@ import { ExplicitGroup, ZarrArray } from "./lib/hierarchy"; import { registry } from "./lib/codec-registry"; -import { KeyError, NodeNotFoundError } from "./lib/errors"; +import { assert, KeyError, NodeNotFoundError } from "./lib/errors"; // deno-fmt-ignore import { json_decode_object, json_encode_object } from "./lib/util"; import type { AbsolutePath, + Async, Attrs, ChunkKey, CreateArrayProps, DataType, - Hierarchy as HierarchyProtocol, - Store, + Hierarchy as _Hierarchy, + Readable, + Writeable, } from "./types"; import type { Codec } from "numcodecs"; @@ -57,11 +59,86 @@ const array_meta_key = (path: AbsolutePath) => `${key_prefix(path)}.zarray` as c const group_meta_key = (path: AbsolutePath) => `${key_prefix(path)}.zgroup` as const; const attrs_key = (path: AbsolutePath) => `${key_prefix(path)}.zattrs` as const; -const get_attrs = async (store: Store, path: AbsolutePath): Promise => { +const get_attrs = async ( + store: Readable | Async, + path: AbsolutePath, +): Promise => { const attrs = await store.get(attrs_key(path)); return attrs ? json_decode_object(attrs) : {}; }; +async function create_group< + Store extends (Readable & Writeable) | Async, + H extends Hierarchy, + Path extends AbsolutePath, +>(owner: H, path: Path, attrs?: Attrs) { + const meta_doc = json_encode_object({ zarr_format: 2 } as GroupMetadata); + const meta_key = group_meta_key(path); + + await owner.store.set(meta_key, meta_doc); + + if (attrs) { + await owner.store.set( + attrs_key(path), + json_encode_object(attrs), + ); + } + + return new ExplicitGroup({ store: owner.store, owner, path, attrs: attrs ?? {} }); +} + +async function create_array< + S extends Writeable | Async, + Path extends AbsolutePath, + D extends DataType, +>( + store: S, + path: Path, + props: CreateArrayProps, +) { + const shape = props.shape; + const dtype = props.dtype; + const chunk_shape = props.chunk_shape; + const compressor = props.compressor; + const chunk_separator = props.chunk_separator ?? "."; + + const meta: ArrayMetadata = { + zarr_format: 2, + shape, + dtype, + chunks: chunk_shape, + dimension_separator: chunk_separator, + order: "C", + fill_value: props.fill_value ?? null, + filters: [], + compressor: compressor ? encode_codec_metadata(compressor) : null, + }; + + // serialise and store metadata document + const meta_doc = json_encode_object(meta); + const meta_key = array_meta_key(path); + await store.set(meta_key, meta_doc); + + if (props.attrs) { + await store.set( + attrs_key(path), + json_encode_object(props.attrs), + ); + } + + return new ZarrArray({ + store, + path, + shape: meta.shape, + dtype: dtype, + chunk_shape: meta.chunks, + chunk_key: chunk_key(path, chunk_separator), + compressor: compressor, + fill_value: meta.fill_value, + attrs: props.attrs ?? {}, + }); +} + function chunk_key(path: AbsolutePath, chunk_separator: "." | "/"): ChunkKey { const prefix = key_prefix(path); return (chunk_coords) => { @@ -70,13 +147,16 @@ function chunk_key(path: AbsolutePath, chunk_separator: "." | "/"): ChunkKey { }; } -export const create_hierarchy = (store: S) => new Hierarchy({ store }); +export const create_hierarchy = < + S extends Readable & Writeable | Async, +>(store: S) => new Hierarchy({ store }); -export const get_hierarchy = (store: S) => new Hierarchy({ store }); +export const get_hierarchy = >(store: S) => + new Hierarchy({ store }); export const from_meta = async < P extends AbsolutePath, - S extends Store, + S extends Readable | Async, D extends DataType, >( store: S, @@ -97,9 +177,10 @@ export const from_meta = async < }); }; -export class Hierarchy implements HierarchyProtocol { - public store: S; - constructor({ store }: { store: S }) { +export class Hierarchy> + implements _Hierarchy { + public store: Store; + constructor({ store }: { store: Store }) { this.store = store; } @@ -107,81 +188,28 @@ export class Hierarchy implements HierarchyProtocol { return this.get("/"); } - async create_group( + create_group( path: Path, props: { attrs?: Attrs } = {}, - ): Promise, Path>> { - const { attrs } = props; - // sanity checks - // path = normalize_path(path); - - // serialise and store metadata document - const meta_doc = json_encode_object({ zarr_format: 2 } as GroupMetadata); - const meta_key = group_meta_key(path); - await this.store.set(meta_key, meta_doc); - - if (attrs) { - await this.store.set(attrs_key(path), json_encode_object(attrs)); - } - - return new ExplicitGroup({ - store: this.store, - owner: this, - path, - attrs: attrs ?? {}, - }); + ): Store extends (Writeable | Async) + ? Promise, Path>> + : never { + assert("set" in this.store, "Not a writeable store"); + return create_group(this as any, path, props.attrs) as any; } - async create_array( + create_array( path: Path, - props: CreateArrayProps, - ): Promise> { - const shape = props.shape; - const dtype = props.dtype; - const chunk_shape = props.chunk_shape; - const compressor = props.compressor; - const chunk_separator = props.chunk_separator ?? "."; - - const meta: ArrayMetadata = { - zarr_format: 2, - shape, - dtype, - chunks: chunk_shape, - dimension_separator: chunk_separator, - order: "C", - fill_value: props.fill_value ?? null, - filters: [], - compressor: compressor ? encode_codec_metadata(compressor) : null, - }; - - // serialise and store metadata document - const meta_doc = json_encode_object(meta); - const meta_key = array_meta_key(path); - await this.store.set(meta_key, meta_doc); - - if (props.attrs) { - await this.store.set( - attrs_key(path), - json_encode_object(props.attrs), - ); - } - - return new ZarrArray({ - store: this.store, - path, - shape: meta.shape, - dtype: dtype, - chunk_shape: meta.chunks, - chunk_key: chunk_key(path, chunk_separator), - compressor: compressor, - fill_value: meta.fill_value, - attrs: props.attrs ?? {}, - }); + props: CreateArrayProps, + ): Store extends (Writeable | Async) ? Promise> + : never { + assert("set" in this.store, "Not a writeable store"); + return create_array(this.store as any, path, props) as any; } async get_array( path: Path, - ): Promise> { + ): Promise> { // path = normalize_path(path); const meta_key = array_meta_key(path); const meta_doc = await this.store.get(meta_key); @@ -197,7 +225,7 @@ export class Hierarchy implements HierarchyProtocol { async get_group( path: Path, - ): Promise, Path>> { + ): Promise, Path>> { // path = normalize_path(path); const meta_key = group_meta_key(path); @@ -217,7 +245,7 @@ export class Hierarchy implements HierarchyProtocol { } async get(path: Path): Promise< - ZarrArray | ExplicitGroup, Path> + ZarrArray | ExplicitGroup, Path> > { try { return await this.get_array(path); @@ -249,9 +277,8 @@ export class Hierarchy implements HierarchyProtocol { } } - async get_children(_path: AbsolutePath): Promise> { - console.warn("get_children not implemented for v2."); - return new Map(); + get_children(_path: AbsolutePath): never { + throw new Error("get_children not implemented for v2."); } get_implicit_group(_path: AbsolutePath): never { diff --git a/src/v3.ts b/src/v3.ts index 8c0652a..4a04908 100644 --- a/src/v3.ts +++ b/src/v3.ts @@ -6,12 +6,15 @@ import { json_decode_object, json_encode_object } from "./lib/util"; import type { AbsolutePath, + Async, Attrs, CreateArrayProps, DataType, - Hierarchy as HierarchyProtocol, + ExtendedReadable, + Hierarchy as _Hierarchy, + Readable, Scalar, - Store, + Writeable, } from "./types"; import type { Codec } from "numcodecs"; @@ -93,15 +96,82 @@ function meta_key( return `/meta/root${path}.${node}${suffix}` as const; } +async function create_group< + Store extends (Readable & Writeable) | Async, + H extends Hierarchy, + Path extends AbsolutePath, +>(owner: H, path: Path, attrs: Attrs = {}) { + const meta: GroupMetadata = { extensions: [], attributes: attrs }; + + // serialise and store metadata document + const meta_doc = json_encode_object(meta); + const key = meta_key(path, owner.meta_key_suffix, "group"); + await owner.store.set(key, meta_doc); + return new ExplicitGroup({ store: owner.store, owner, path, attrs }); +} + +async function create_array< + Store extends (Readable & Writeable) | Async, + H extends Hierarchy, + Path extends AbsolutePath, + Dtype extends DataType, +>( + owner: H, + path: Path, + props: Omit, "filters">, +): Promise> { + const shape = props.shape; + const dtype = props.dtype; + const chunk_shape = props.chunk_shape; + const compressor = props.compressor; + + const meta: ArrayMetadata = { + shape, + data_type: dtype, + chunk_grid: { + type: "regular", + separator: props.chunk_separator ?? "/", + chunk_shape, + }, + chunk_memory_layout: "C", + fill_value: props.fill_value ?? null, + extensions: [], + attributes: props.attrs ?? {}, + }; + + if (compressor) { + meta.compressor = encode_codec_metadata(compressor); + } + + // serialise and store metadata document + const meta_doc = json_encode_object(meta); + const key = meta_key(path, owner.meta_key_suffix, "array"); + await owner.store.set(key, meta_doc); + + return new ZarrArray({ + store: owner.store, + path, + shape: meta.shape, + dtype: dtype, + chunk_shape: meta.chunk_grid.chunk_shape, + chunk_key: chunk_key(path, meta.chunk_grid.separator), + compressor: compressor, + fill_value: meta.fill_value, + attrs: meta.attributes, + }); +} + const chunk_key = (path: string, chunk_separator: "." | "/") => (chunk_coords: number[]): AbsolutePath => { const chunk_identifier = "c" + chunk_coords.join(chunk_separator); return `/data/root${path}/${chunk_identifier}`; }; -export async function create_hierarchy( - store: S, -): Promise> { +export async function create_hierarchy< + Store extends (Readable & Writeable) | Async, +>( + store: Store, +): Promise> { // create entry point metadata document const meta_key_suffix = ".json"; @@ -121,9 +191,9 @@ export async function create_hierarchy( return new Hierarchy({ store, meta_key_suffix }); } -export async function get_hierarchy( - store: S, -): Promise> { +export async function get_hierarchy>( + store: Store, +): Promise> { // retrieve and parse entry point metadata document const meta_key = "/zarr.json"; const meta_doc = await store.get(meta_key); @@ -174,11 +244,12 @@ export async function get_hierarchy( return new Hierarchy({ store, meta_key_suffix }); } -export class Hierarchy implements HierarchyProtocol { - store: S; +export class Hierarchy> + implements _Hierarchy { + store: Store; meta_key_suffix: string; - constructor({ store, meta_key_suffix }: { store: S; meta_key_suffix: string }) { + constructor({ store, meta_key_suffix }: { store: Store; meta_key_suffix: string }) { this.store = store; this.meta_key_suffix = meta_key_suffix; } @@ -195,75 +266,28 @@ export class Hierarchy implements HierarchyProtocol { return ".group" + this.meta_key_suffix; } - async create_group( + create_group( path: Path, props: { attrs?: Attrs } = {}, - ): Promise, Path>> { - const { attrs = {} } = props; - - const meta: GroupMetadata = { extensions: [], attributes: attrs }; - - // serialise and store metadata document - const meta_doc = json_encode_object(meta); - const key = meta_key(path, this.meta_key_suffix, "group"); - await this.store.set(key, meta_doc); - - return new ExplicitGroup({ - store: this.store, - owner: this, - path, - attrs, - }); + ): Store extends (Writeable | Async) + ? Promise, Path>> + : never { + assert("set" in this.store, "Not a writable store"); + return create_group(this as Hierarchy, path, props.attrs) as any; } - async create_array( + create_array( path: Path, - props: Omit, "filters">, - ): Promise> { - const shape = props.shape; - const dtype = props.dtype; - const chunk_shape = props.chunk_shape; - const compressor = props.compressor; - - const meta: ArrayMetadata = { - shape, - data_type: dtype, - chunk_grid: { - type: "regular", - separator: props.chunk_separator ?? "/", - chunk_shape, - }, - chunk_memory_layout: "C", - fill_value: props.fill_value ?? null, - extensions: [], - attributes: props.attrs ?? {}, - }; - - if (compressor) { - meta.compressor = encode_codec_metadata(compressor); - } - - // serialise and store metadata document - const meta_doc = json_encode_object(meta); - const key = meta_key(path, this.meta_key_suffix, "array"); - await this.store.set(key, meta_doc); - - return new ZarrArray({ - store: this.store, - path, - shape: meta.shape, - dtype: dtype, - chunk_shape: meta.chunk_grid.chunk_shape, - chunk_key: chunk_key(path, meta.chunk_grid.separator), - compressor: compressor, - fill_value: meta.fill_value, - attrs: meta.attributes, - }); + props: Omit, "filters">, + ): Store extends (Writeable | Async) ? Promise> + : never { + assert("set" in this.store, "Not a writable store"); + return create_array(this as Hierarchy, path, props) as any; } async get_array( path: Path, - ): Promise> { + ): Promise> { const key = meta_key(path, this.meta_key_suffix, "array"); const meta_doc = await this.store.get(key); @@ -321,7 +345,7 @@ export class Hierarchy implements HierarchyProtocol { async get_group( path: Path, - ): Promise, Path>> { + ): Promise, Path>> { // retrieve and parse group metadata document const key = meta_key(path, this.meta_key_suffix, "group"); const meta_doc = await this.store.get(key); @@ -341,28 +365,36 @@ export class Hierarchy implements HierarchyProtocol { }); } - async get_implicit_group( + get_implicit_group( path: Path, - ): Promise, Path>> { - // attempt to list directory - const key_prefix = (path as any) === "/" - ? "/meta/root/" - : `/meta/root${path}/` as const; - const result = await this.store.list_dir(key_prefix); - - const { contents, prefixes } = result; - if (contents.length === 0 && prefixes.length === 0) { - throw new NodeNotFoundError(path); - } + ): Store extends ExtendedReadable | Async + ? Promise, Path>> + : never { + assert( + "list_dir" in this.store, + "Not ExtendedReadable, store must implement list_dir", + ); + return (async () => { + // attempt to list directory + const key_prefix = (path as any) === "/" + ? "/meta/root/" + : `/meta/root${path}/` as const; + + const res = await (this.store as ExtendedReadable | Async) + .list_dir(key_prefix); - // instantiate implicit group - return new ImplicitGroup({ store: this.store, path, owner: this }); + if (res.contents.length === 0 && res.prefixes.length === 0) { + throw new NodeNotFoundError(path); + } + + return new ImplicitGroup({ store: this.store, path, owner: this }); + })() as any; } async get(path: Path): Promise< - | ZarrArray - | ExplicitGroup, Path> - | ImplicitGroup, Path> + | ZarrArray + | ExplicitGroup, Path> + | ImplicitGroup, Path> > { try { return await this.get_array(path); @@ -403,68 +435,87 @@ export class Hierarchy implements HierarchyProtocol { } } - async get_nodes(): Promise> { - const nodes: Map = new Map(); - const result = await this.store.list_prefix("/meta/"); - - const lookup = (key: string) => { - if (key.endsWith(this.array_suffix)) { - return { suffix: this.array_suffix, type: "array" }; - } else if (key.endsWith(this.group_suffix)) { - return { suffix: this.group_suffix, type: "explicit_group" }; - } - }; - - for (const key of result) { - if (key === "root.array" + this.meta_key_suffix) { - nodes.set("/", "array"); - } else if (key == "root.group") { - nodes.set("/", "explicit_group"); - } else if (key.startsWith("root/")) { - const m = lookup(key); - if (m) { - const path = key.slice("root".length, -m.suffix.length); - nodes.set(path, m.type); - const segments = path.split("/"); - segments.pop(); - while (segments.length > 1) { - const parent = segments.join("/"); - nodes.set( - parent, - nodes.get(parent) || "implicit_group", - ); + get_nodes(): Store extends ExtendedReadable | Async + ? Promise> + : never { + assert( + "list_prefix" in this.store, + "Not ExtendedReadable, store must implement list_prefix", + ); + return (async () => { + const nodes: Map = new Map(); + const result = await (this.store as ExtendedReadable | Async) + .list_prefix("/meta/"); + const lookup = (key: string) => { + if (key.endsWith(this.array_suffix)) { + return { suffix: this.array_suffix, type: "array" }; + } else if (key.endsWith(this.group_suffix)) { + return { suffix: this.group_suffix, type: "explicit_group" }; + } + }; + + for (const key of result) { + if (key === "root.array" + this.meta_key_suffix) { + nodes.set("/", "array"); + } else if (key == "root.group") { + nodes.set("/", "explicit_group"); + } else if (key.startsWith("root/")) { + const m = lookup(key); + if (m) { + const path = key.slice("root".length, -m.suffix.length); + nodes.set(path, m.type); + const segments = path.split("/"); segments.pop(); + while (segments.length > 1) { + const parent = segments.join("/"); + nodes.set( + parent, + nodes.get(parent) || "implicit_group", + ); + segments.pop(); + } + nodes.set("/", nodes.get("/") || "implicit_group"); } - nodes.set("/", nodes.get("/") || "implicit_group"); } } - } - return nodes; + return nodes; + })() as any; } - async get_children(path: AbsolutePath = "/"): Promise> { - const children: Map = new Map(); - - // attempt to list directory - const key_prefix = path === "/" ? "/meta/root/" : `/meta/root${path}/` as const; - const result = await this.store.list_dir(key_prefix); - - // find explicit children - for (const n of result.contents) { - if (n.endsWith(this.array_suffix)) { - const name = n.slice(0, -this.array_suffix.length); - children.set(name, "array"); - } else if (n.endsWith(this.group_suffix)) { - const name = n.slice(0, -this.group_suffix.length); - children.set(name, "explicit_group"); + get_children( + path: AbsolutePath = "/", + ): Store extends ExtendedReadable | Async + ? Promise> + : never { + assert( + "list_dir" in this.store, + "Not ExtendedReadable, store must implement list_dir", + ); + return (async () => { + const children: Map = new Map(); + + // attempt to list directory + const key_prefix = path === "/" ? "/meta/root/" : `/meta/root${path}/` as const; + const result = await (this.store as ExtendedReadable | Async) + .list_dir(key_prefix); + + // find explicit children + for (const n of result.contents) { + if (n.endsWith(this.array_suffix)) { + const name = n.slice(0, -this.array_suffix.length); + children.set(name, "array"); + } else if (n.endsWith(this.group_suffix)) { + const name = n.slice(0, -this.group_suffix.length); + children.set(name, "explicit_group"); + } } - } - // find implicit children - for (const name of result.prefixes) { - children.set(name, children.get(name) || "implicit_group"); - } + // find implicit children + for (const name of result.prefixes) { + children.set(name, children.get(name) || "implicit_group"); + } - return children; + return children; + })() as any; } } diff --git a/test/common.js b/test/common.js index ad4c19e..37530bb 100644 --- a/test/common.js +++ b/test/common.js @@ -1,12 +1,11 @@ // @ts-check - import { test } from "zora"; import { ExplicitGroup, ImplicitGroup, registry, slice, v3, ZarrArray } from "zarrita"; import { get, set } from "zarrita/ndarray"; import ndarray from "ndarray"; -import GZip from "numcodecs/gzip"; +import GZip from "numcodecs/gzip"; // add dynamic codec to registry // @ts-ignore registry.set("gzip", () => GZip); @@ -17,7 +16,7 @@ function json(bytes) { return JSON.parse(str); } -/** @param {{ name: string, setup: () => Promise }} props */ +/** @param {{ name: string, setup: () => Promise }} props */ export function run_test_suite({ name, setup }) { test(`Zarrita test suite: ${name}.`, async (t) => { const store = await setup();