@az0uz/zarr
Version:
Javascript implementation of Zarr
1 lines • 222 kB
Source Map (JSON)
{"version":3,"file":"core.min.mjs","sources":["../src/compression/registry.ts","../src/mutableMapping.ts","../src/errors.ts","../src/core/slice.ts","../src/core/indexing.ts","../src/util.ts","../src/names.ts","../src/storage/index.ts","../src/metadata.ts","../src/attributes.ts","../src/nestedArray/types.ts","../src/nestedArray/ops.ts","../src/nestedArray/index.ts","../src/rawArray/ops.ts","../src/rawArray/index.ts","../node_modules/eventemitter3/index.js","../node_modules/p-timeout/index.js","../node_modules/p-queue/dist/priority-queue.js","../node_modules/p-queue/dist/lower-bound.js","../node_modules/p-queue/dist/index.js","../src/core/index.ts","../src/storage/memoryStore.ts","../src/storage/httpStore.ts","../src/creation.ts","../src/hierarchy.ts","../src/storage/objectStore.ts"],"sourcesContent":["import type { Codec, CodecConstructor } from 'numcodecs';\n\ntype Config = Record<string, unknown>;\ntype CodecImporter = () => CodecConstructor<Config> | Promise<CodecConstructor<Config>>;\n\nconst registry: Map<string, CodecImporter> = new Map();\n\nexport function addCodec(id: string, importFn: CodecImporter) {\n registry.set(id, importFn);\n}\n\nexport async function getCodec(config: Config & { id: string }): Promise<Codec> {\n if (!registry.has(config.id)) {\n throw new Error(`Compression codec ${config.id} is not supported by Zarr.js yet.`);\n }\n /* eslint-disable @typescript-eslint/no-non-null-assertion */\n const codec = await registry.get(config.id)!();\n return codec.fromConfig(config);\n}\n","/**\n * Closely resembles the functions on the MutableMapping type in Python.\n */\nexport interface MutableMapping<T, O=any> {\n getItem(item: string, opts?: O): T;\n setItem(item: string, value: T): boolean;\n deleteItem(item: string): boolean;\n containsItem(item: string): boolean;\n\n proxy(): MutableMappingProxy<T>;\n\n // length(): number;\n}\n\n/**\n * Closely resembles the functions on the MutableMapping type in Python.\n */\nexport interface AsyncMutableMapping<T, O=any> {\n getItem(item: string, opts?: O): Promise<T>;\n setItem(item: string, value: T): Promise<boolean>;\n deleteItem(item: string): Promise<boolean>;\n containsItem(item: string): Promise<boolean>;\n // length(): number;\n}\n\nexport interface MutableMappingProxy<T> {\n [key: string]: T;\n}\n\nexport interface AsyncMutableMappingProxy<T> {\n [key: string]: T | Promise<T>;\n}\n\n\n/**\n * A proxy allows for accessing, setting and deleting the keys in the mutable mapping using\n * m[\"a\"] or even m.a notation.\n */\nexport function createProxy<S, T>(mapping: S & MutableMapping<T>): (S & MutableMappingProxy<T>);\nexport function createProxy<S, T>(mapping: S & AsyncMutableMapping<T>): (S & AsyncMutableMappingProxy<T>);\nexport function createProxy<S, T>(mapping: (S & MutableMapping<T>) | (S & AsyncMutableMapping<T>)): (S & MutableMappingProxy<T>) | (S & AsyncMutableMappingProxy<T>) {\n return new Proxy(mapping as any, {\n set(target, key, value, _receiver) {\n return target.setItem(key as string, value);\n },\n get(target, key, _receiver) {\n return target.getItem(key as string);\n },\n deleteProperty(target, key) {\n return target.deleteItem(key as string);\n },\n has(target, key) {\n return target.containsItem(key as string);\n }\n });\n}","export interface ZarrError {\n __zarr__: string;\n}\n\nfunction isZarrError(err: unknown): err is ZarrError {\n return typeof err === 'object' && err !== null && '__zarr__' in err;\n}\n\nexport function isKeyError(o: unknown) {\n return isZarrError(o) && o.__zarr__ === 'KeyError';\n}\n\n// Custom error messages, note we have to patch the prototype of the\n// errors to fix `instanceof` calls, see:\n// https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work\nexport class ContainsArrayError extends Error implements ZarrError {\n __zarr__ = 'ContainsArrayError';\n constructor(path: string) {\n super(`path ${path} contains an array`);\n Object.setPrototypeOf(this, ContainsArrayError.prototype);\n }\n}\n\nexport class ContainsGroupError extends Error implements ZarrError {\n __zarr__ = 'ContainsGroupError';\n constructor(path: string) {\n super(`path ${path} contains a group`);\n Object.setPrototypeOf(this, ContainsGroupError.prototype);\n }\n}\n\nexport class ArrayNotFoundError extends Error implements ZarrError {\n __zarr__ = 'ArrayNotFoundError';\n constructor(path: string) {\n super(`array not found at path ${path}`);\n Object.setPrototypeOf(this, ArrayNotFoundError.prototype);\n }\n}\n\nexport class GroupNotFoundError extends Error implements ZarrError {\n __zarr__ = 'GroupNotFoundError';\n constructor(path: string) {\n super(`group not found at path ${path}`);\n Object.setPrototypeOf(this, GroupNotFoundError.prototype);\n }\n}\n\nexport class PathNotFoundError extends Error implements ZarrError {\n __zarr__ = 'PathNotFoundError';\n constructor(path: string) {\n super(`nothing found at path ${path}`);\n Object.setPrototypeOf(this, PathNotFoundError.prototype);\n }\n}\n\nexport class PermissionError extends Error implements ZarrError {\n __zarr__ = 'PermissionError';\n constructor(message: string) {\n super(message);\n Object.setPrototypeOf(this, PermissionError.prototype);\n }\n}\n\nexport class KeyError extends Error implements ZarrError {\n __zarr__ = 'KeyError';\n constructor(key: string) {\n super(`key ${key} not present`);\n Object.setPrototypeOf(this, KeyError.prototype);\n }\n}\n\nexport class TooManyIndicesError extends RangeError implements ZarrError {\n __zarr__ = 'TooManyIndicesError';\n constructor(selection: any[], shape: number[]) {\n super(`too many indices for array; expected ${shape.length}, got ${selection.length}`);\n Object.setPrototypeOf(this, TooManyIndicesError.prototype);\n }\n}\n\nexport class BoundsCheckError extends RangeError implements ZarrError {\n __zarr__ = 'BoundsCheckError';\n constructor(message: string) {\n super(message);\n Object.setPrototypeOf(this, BoundsCheckError.prototype);\n }\n}\n\nexport class InvalidSliceError extends RangeError implements ZarrError {\n __zarr__ = 'InvalidSliceError';\n constructor(from: any, to: any, stepSize: any, reason: any) {\n super(`slice arguments slice(${from}, ${to}, ${stepSize}) invalid: ${reason}`);\n Object.setPrototypeOf(this, InvalidSliceError.prototype);\n }\n}\n\nexport class NegativeStepError extends Error implements ZarrError {\n __zarr__ = 'NegativeStepError';\n constructor() {\n super(`Negative step size is not supported when indexing.`);\n Object.setPrototypeOf(this, NegativeStepError.prototype);\n }\n}\n\nexport class ValueError extends Error implements ZarrError {\n __zarr__ = 'ValueError';\n constructor(message: string) {\n super(message);\n Object.setPrototypeOf(this, ValueError.prototype);\n }\n}\n\nexport class HTTPError extends Error implements ZarrError {\n __zarr__ = 'HTTPError';\n constructor(code: string) {\n super(code);\n Object.setPrototypeOf(this, HTTPError.prototype);\n }\n}\n","\nimport { InvalidSliceError } from '../errors';\nimport { Slice, SliceArgument, SliceIndices } from \"./types\";\n\nexport function slice(start: SliceArgument, stop: SliceArgument | undefined = undefined, step: number | null = null): Slice {\n // tslint:disable-next-line: strict-type-predicates\n if (start === undefined) { // Not possible in typescript\n throw new InvalidSliceError(start, stop, step, \"The first argument must not be undefined\");\n }\n\n if ((typeof start === \"string\" && start !== \":\") || (typeof stop === \"string\" && stop !== \":\")) { // Note in typescript this will never happen with type checking.\n throw new InvalidSliceError(start, stop, step, \"Arguments can only be integers, \\\":\\\" or null\");\n }\n\n // slice(5) === slice(null, 5)\n if (stop === undefined) {\n stop = start;\n start = null;\n }\n\n // if (start !== null && stop !== null && start > stop) {\n // throw new InvalidSliceError(start, stop, step, \"to is higher than from\");\n // }\n\n return {\n start: start === \":\" ? null : start,\n stop: stop === \":\" ? null : stop,\n step,\n _slice: true,\n };\n}\n\n\n/**\n * Port of adjustIndices\n * https://github.com/python/cpython/blob/master/Objects/sliceobject.c#L243\n */\nfunction adjustIndices(start: number, stop: number, step: number, length: number) {\n if (start < 0) {\n start += length;\n if (start < 0) {\n start = (step < 0) ? -1 : 0;\n }\n } else if (start >= length) {\n start = (step < 0) ? length - 1 : length;\n }\n\n if (stop < 0) {\n stop += length;\n if (stop < 0) {\n stop = (step < 0) ? -1 : 0;\n }\n } else if (stop >= length) {\n stop = (step < 0) ? length - 1 : length;\n }\n\n if (step < 0) {\n if (stop < start) {\n const length = Math.floor((start - stop - 1) / (-step) + 1);\n return [start, stop, step, length];\n }\n } else {\n if (start < stop) {\n const length = Math.floor((stop - start - 1) / step + 1);\n return [start, stop, step, length];\n }\n }\n return [start, stop, step, 0];\n}\n\n/**\n * Port of slice.indices(n) and PySlice_Unpack\n * https://github.com/python/cpython/blob/master/Objects/sliceobject.c#L166\n * https://github.com/python/cpython/blob/master/Objects/sliceobject.c#L198 \n * \n * Behaviour might be slightly different as it's a weird hybrid implementation.\n */\nexport function sliceIndices(slice: Slice, length: number): SliceIndices {\n let start: number;\n let stop: number;\n let step: number;\n\n if (slice.step === null) {\n step = 1;\n } else {\n step = slice.step;\n }\n\n if (slice.start === null) {\n start = step < 0 ? Number.MAX_SAFE_INTEGER : 0;\n } else {\n start = slice.start;\n if (start < 0) {\n start += length;\n }\n }\n\n if (slice.stop === null) {\n stop = step < 0 ? -Number.MAX_SAFE_INTEGER : Number.MAX_SAFE_INTEGER;\n } else {\n stop = slice.stop;\n if (stop < 0) {\n stop += length;\n }\n }\n\n // This clips out of bounds slices\n const s = adjustIndices(start, stop, step, length);\n start = s[0];\n stop = s[1];\n step = s[2];\n // The output length\n length = s[3];\n\n\n // With out of bounds slicing these two assertions are not useful.\n // if (stop > length) throw new Error(\"Stop greater than length\");\n // if (start >= length) throw new Error(\"Start greater than or equal to length\");\n\n if (step === 0) throw new Error(\"Step size 0 is invalid\");\n\n return [start, stop, step, length];\n}","import { TooManyIndicesError, BoundsCheckError, NegativeStepError } from '../errors';\nimport { ZarrArray } from './index';\nimport { Slice, ArraySelection, ChunkDimProjection, Indexer, DimIndexer, ChunkProjection, NormalizedArraySelection, SliceIndices, DimensionArraySelection } from './types';\nimport { sliceIndices, slice } from \"./slice\";\n\nfunction ensureArray(selection: ArraySelection): DimensionArraySelection[] {\n if (!Array.isArray(selection)) {\n return [selection];\n }\n return selection;\n}\n\nfunction checkSelectionLength(selection: DimensionArraySelection[], shape: number[]) {\n if (selection.length > shape.length) {\n throw new TooManyIndicesError(selection, shape);\n }\n}\n\n/**\n * Returns both the sliceIndices per dimension and the output shape after slicing.\n */\nexport function selectionToSliceIndices(selection: NormalizedArraySelection, shape: number[]): [(number | SliceIndices)[], number[]] {\n const sliceIndicesResult = [];\n const outShape = [];\n\n for (let i = 0; i < selection.length; i++) {\n const s = selection[i];\n if (typeof s === \"number\") {\n sliceIndicesResult.push(s);\n } else {\n const x = sliceIndices(s, shape[i]);\n const dimLength = x[3];\n\n outShape.push(dimLength);\n sliceIndicesResult.push(x);\n }\n }\n\n return [sliceIndicesResult, outShape];\n}\n\n/**\n * This translates \"...\", \":\", null into a list of slices or non-negative integer selections of length shape\n */\nexport function normalizeArraySelection(selection: ArraySelection | number, shape: number[], convertIntegerSelectionToSlices = false): NormalizedArraySelection {\n selection = replaceEllipsis(selection, shape);\n\n for (let i = 0; i < selection.length; i++) {\n const dimSelection = selection[i];\n\n if (typeof dimSelection === \"number\") {\n if (convertIntegerSelectionToSlices) {\n selection[i] = slice(dimSelection, dimSelection + 1, 1);\n } else {\n selection[i] = normalizeIntegerSelection(dimSelection, shape[i]);\n }\n } else if (isIntegerArray(dimSelection)) {\n throw new TypeError(\"Integer array selections are not supported (yet)\");\n } else if (dimSelection === \":\" || dimSelection === null) {\n selection[i] = slice(null, null, 1);\n }\n }\n\n return selection as NormalizedArraySelection;\n}\n\nexport function replaceEllipsis(selection: ArraySelection | number, shape: number[]) {\n selection = ensureArray(selection);\n\n let ellipsisIndex = -1;\n let numEllipsis = 0;\n for (let i = 0; i < selection.length; i++) {\n if (selection[i] === \"...\") {\n ellipsisIndex = i;\n numEllipsis += 1;\n }\n }\n\n if (numEllipsis > 1) {\n throw new RangeError(\"an index can only have a single ellipsis ('...')\");\n }\n if (numEllipsis === 1) {\n // count how many items to left and right of ellipsis\n const numItemsLeft = ellipsisIndex;\n const numItemsRight = selection.length - (numItemsLeft + 1);\n const numItems = selection.length - 1; // All non-ellipsis items\n if (numItems >= shape.length) {\n // Ellipsis does nothing, just remove it\n selection = selection.filter((x) => x !== \"...\");\n } else {\n // Replace ellipsis with as many slices are needed for number of dims\n const numNewItems = shape.length - numItems;\n let newItem = selection.slice(0, numItemsLeft).concat(new Array(numNewItems).fill(null));\n if (numItemsRight > 0) {\n newItem = newItem.concat(selection.slice(selection.length - numItemsRight));\n }\n selection = newItem;\n }\n }\n // Fill out selection if not completely specified\n if (selection.length < shape.length) {\n const numMissing = shape.length - selection.length;\n selection = selection.concat(new Array(numMissing).fill(null));\n }\n\n checkSelectionLength(selection, shape);\n return selection;\n}\n\nexport function normalizeIntegerSelection(dimSelection: number, dimLength: number): number {\n // Note: Maybe we should convert to integer or warn if dimSelection is not an integer\n\n // handle wraparound\n if (dimSelection < 0) {\n dimSelection = dimLength + dimSelection;\n }\n\n // handle out of bounds\n if (dimSelection >= dimLength || dimSelection < 0) {\n throw new BoundsCheckError(`index out of bounds for dimension with length ${dimLength}`);\n }\n\n return dimSelection;\n}\n\nfunction isInteger(s: any) {\n return typeof s === \"number\";\n}\n\nexport function isIntegerArray(s: any) {\n if (!Array.isArray(s)) {\n return false;\n }\n for (const e of s) {\n if (typeof e !== \"number\") {\n return false;\n }\n }\n return true;\n}\n\nexport function isSlice(s: (Slice | number | number[] | \"...\" | \":\" | null)): boolean {\n if (s !== null && (s as any)[\"_slice\"] === true) {\n return true;\n }\n return false;\n}\n\nfunction isContiguousSlice(s: (Slice | number | number[] | \"...\" | \":\" | null)): boolean {\n return isSlice(s) && ((s as Slice).step === null || (s as Slice).step === 1);\n}\n\nfunction isPositiveSlice(s: (Slice | number | number[] | \"...\" | \":\" | null)): boolean {\n return isSlice(s) && ((s as Slice).step === null || ((s as Slice).step as number) >= 1);\n}\n\nexport function isContiguousSelection(selection: ArraySelection) {\n selection = ensureArray(selection);\n\n for (let i = 0; i < selection.length; i++) {\n const s = selection[i];\n if (!(isIntegerArray(s) || isContiguousSlice(s) || s === \"...\")) {\n return false;\n }\n }\n return true;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nfunction isBasicSelection(selection: ArraySelection): boolean {\n selection = ensureArray(selection);\n\n for (let i = 0; i < selection.length; i++) {\n const s = selection[i];\n if (!(isInteger(s) || isPositiveSlice(s))) {\n return false;\n }\n }\n return true;\n}\nfunction* product<T>(...iterables: (() => IterableIterator<T>)[]): IterableIterator<T[]> {\n if (iterables.length === 0) { return; }\n // make a list of iterators from the iterables\n const iterators = iterables.map(it => it());\n const results = iterators.map(it => it.next());\n\n // Disabled to allow empty inputs\n // if (results.some(r => r.done)) {\n // throw new Error(\"Input contains an empty iterator.\");\n // }\n\n for (let i = 0; ;) {\n if (results[i].done) {\n // reset the current iterator\n iterators[i] = iterables[i]();\n results[i] = iterators[i].next();\n // advance, and exit if we've reached the end\n if (++i >= iterators.length) { return; }\n } else {\n yield results.map(({ value }) => value);\n i = 0;\n }\n results[i] = iterators[i].next();\n }\n}\n\nexport class BasicIndexer implements Indexer {\n dimIndexers: DimIndexer[];\n shape: number[];\n dropAxes: null;\n\n constructor(selection: ArraySelection, array: ZarrArray) {\n selection = normalizeArraySelection(selection, array.shape);\n\n // Setup per-dimension indexers\n this.dimIndexers = [];\n const arrayShape = array.shape;\n for (let i = 0; i < arrayShape.length; i++) {\n let dimSelection = selection[i];\n const dimLength = arrayShape[i];\n const dimChunkLength = array.chunks[i];\n\n if (dimSelection === null) {\n dimSelection = slice(null);\n }\n\n\n if (isInteger(dimSelection)) {\n this.dimIndexers.push(new IntDimIndexer(dimSelection as number, dimLength, dimChunkLength));\n } else if (isSlice(dimSelection)) {\n this.dimIndexers.push(new SliceDimIndexer(dimSelection as Slice, dimLength, dimChunkLength));\n } else {\n throw new RangeError(`Unspported selection item for basic indexing; expected integer or slice, got ${dimSelection}`);\n }\n }\n\n this.shape = [];\n for (const d of this.dimIndexers) {\n if (d instanceof SliceDimIndexer) {\n this.shape.push(d.numItems);\n }\n }\n this.dropAxes = null;\n }\n\n * iter() {\n const dimIndexerIterables = this.dimIndexers.map(x => (() => x.iter()));\n const dimIndexerProduct = product(...dimIndexerIterables);\n\n for (const dimProjections of dimIndexerProduct) {\n // TODO fix this, I think the product outputs too many combinations\n const chunkCoords = [];\n const chunkSelection = [];\n const outSelection = [];\n\n for (const p of dimProjections) {\n chunkCoords.push((p).dimChunkIndex);\n chunkSelection.push((p).dimChunkSelection);\n if ((p).dimOutSelection !== null) {\n outSelection.push((p).dimOutSelection);\n }\n }\n\n yield ({\n chunkCoords,\n chunkSelection,\n outSelection,\n } as ChunkProjection);\n }\n\n }\n}\n\nclass IntDimIndexer implements DimIndexer {\n dimSelection: number;\n dimLength: number;\n dimChunkLength: number;\n numItems: number;\n\n constructor(dimSelection: number, dimLength: number, dimChunkLength: number) {\n dimSelection = normalizeIntegerSelection(dimSelection, dimLength);\n this.dimSelection = dimSelection;\n this.dimLength = dimLength;\n this.dimChunkLength = dimChunkLength;\n this.numItems = 1;\n }\n\n * iter() {\n const dimChunkIndex = Math.floor(this.dimSelection / this.dimChunkLength);\n const dimOffset = dimChunkIndex * this.dimChunkLength;\n const dimChunkSelection = this.dimSelection - dimOffset;\n const dimOutSelection = null;\n yield {\n dimChunkIndex,\n dimChunkSelection,\n dimOutSelection,\n } as ChunkDimProjection;\n }\n}\n\nclass SliceDimIndexer implements DimIndexer {\n dimLength: number;\n dimChunkLength: number;\n numItems: number;\n numChunks: number;\n\n start: number;\n stop: number;\n step: number;\n\n constructor(dimSelection: Slice, dimLength: number, dimChunkLength: number) {\n // Normalize\n const [start, stop, step] = sliceIndices(dimSelection, dimLength);\n this.start = start;\n this.stop = stop;\n this.step = step;\n if (this.step < 1) {\n throw new NegativeStepError();\n }\n\n this.dimLength = dimLength;\n this.dimChunkLength = dimChunkLength;\n this.numItems = Math.max(0, Math.ceil((this.stop - this.start) / this.step));\n this.numChunks = Math.ceil(this.dimLength / this.dimChunkLength);\n }\n\n *iter() {\n const dimChunkIndexFrom = Math.floor(this.start / this.dimChunkLength);\n const dimChunkIndexTo = Math.ceil(this.stop / this.dimChunkLength);\n\n // Iterate over chunks in range\n for (let dimChunkIndex = dimChunkIndexFrom; dimChunkIndex < dimChunkIndexTo; dimChunkIndex++) {\n\n // Compute offsets for chunk within overall array\n const dimOffset = dimChunkIndex * this.dimChunkLength;\n const dimLimit = Math.min(this.dimLength, (dimChunkIndex + 1) * this.dimChunkLength);\n\n // Determine chunk length, accounting for trailing chunk\n const dimChunkLength = dimLimit - dimOffset;\n\n let dimChunkSelStart: number;\n let dimChunkSelStop: number;\n let dimOutOffset: number;\n\n if (this.start < dimOffset) {\n // Selection starts before current chunk\n\n dimChunkSelStart = 0;\n const remainder = (dimOffset - this.start) % this.step;\n if (remainder > 0) {\n dimChunkSelStart += this.step - remainder;\n }\n // Compute number of previous items, provides offset into output array\n dimOutOffset = Math.ceil((dimOffset - this.start) / this.step);\n } else {\n // Selection starts within current chunk\n dimChunkSelStart = this.start - dimOffset;\n dimOutOffset = 0;\n }\n\n if (this.stop > dimLimit) {\n // Selection ends after current chunk\n dimChunkSelStop = dimChunkLength;\n } else {\n // Selection ends within current chunk\n dimChunkSelStop = this.stop - dimOffset;\n }\n\n const dimChunkSelection = slice(dimChunkSelStart, dimChunkSelStop, this.step);\n const dimChunkNumItems = Math.ceil((dimChunkSelStop - dimChunkSelStart) / this.step);\n const dimOutSelection = slice(dimOutOffset, dimOutOffset + dimChunkNumItems);\n yield {\n dimChunkIndex,\n dimChunkSelection,\n dimOutSelection,\n } as ChunkDimProjection;\n }\n\n }\n\n}\n","import { Order, FillType, ChunksArgument, DtypeString } from \"./types\";\n\nimport { DimensionSelection, Slice } from \"./core/types\";\nimport { isSlice } from \"./core/indexing\";\nimport { TypedArray } from \"./nestedArray/types\";\n\n/**\n * This should be true only if this javascript is getting executed in Node.\n */\nexport const IS_NODE = typeof process !== \"undefined\" && process.versions && process.versions.node;\n\n// eslint-disable-next-line @typescript-eslint/no-empty-function\nexport function noop(): void {}\n\nexport function humanReadableSize(size: number) {\n if (size < 2 ** 10) {\n return `${size}`;\n }\n else if (size < 2 ** 20) {\n return `${(size / (2 ** 10)).toFixed(1)}K`;\n }\n else if (size < 2 ** 30) {\n return `${(size / (2 ** 20)).toFixed(1)}M`;\n }\n else if (size < 2 ** 40) {\n return `${(size / (2 ** 30)).toFixed(1)}G`;\n }\n else if (size < 2 ** 50) {\n return `${(size / (2 ** 40)).toFixed(1)}T`;\n }\n return `${(size / (2 ** 50)).toFixed(1)}P`;\n}\n\n// eslint-disable-next-line @typescript-eslint/ban-types\nexport function normalizeStoragePath(path: string | String | null): string {\n if (path === null) {\n return \"\";\n }\n\n if (path instanceof String) {\n path = path.valueOf();\n }\n\n // convert backslash to forward slash\n path = path.replace(/\\\\/g, \"/\");\n\n // ensure no leading slash\n while (path.length > 0 && path[0] === '/') {\n path = path.slice(1);\n }\n\n // ensure no trailing slash\n while (path.length > 0 && path[path.length - 1] === '/') {\n path = path.slice(0, path.length - 1);\n }\n\n\n // collapse any repeated slashes\n path = path.replace(/\\/\\/+/g, \"/\");\n\n // don't allow path segments with just '.' or '..'\n const segments = path.split('/');\n\n for (const s of segments) {\n if (s === \".\" || s === \"..\") {\n throw Error(\"path containing '.' or '..' segment not allowed\");\n }\n }\n return path as string;\n}\n\nexport function normalizeShape(shape: number | number[]): number[] {\n if (typeof shape === \"number\") {\n shape = [shape];\n }\n return shape.map(x => Math.floor(x));\n}\n\nexport function normalizeChunks(chunks: ChunksArgument, shape: number[]): number[] {\n // Assume shape is already normalized\n\n if (chunks === null || chunks === true) {\n throw new Error(\"Chunk guessing is not supported yet\");\n }\n\n if (chunks === false) {\n return shape;\n }\n\n if (typeof chunks === \"number\") {\n chunks = [chunks];\n }\n\n // handle underspecified chunks\n if (chunks.length < shape.length) {\n // assume chunks across remaining dimensions\n chunks = chunks.concat(shape.slice(chunks.length));\n }\n\n return chunks.map((x, idx) => {\n // handle null or -1 in chunks\n if (x === -1 || x === null) {\n return shape[idx];\n } else {\n return Math.floor(x);\n }\n });\n}\n\nexport function normalizeOrder(order: string): Order {\n order = order.toUpperCase();\n return order as Order;\n}\n\nexport function normalizeDtype(dtype: DtypeString): DtypeString {\n return dtype;\n}\n\nexport function normalizeFillValue(fillValue: FillType): FillType {\n return fillValue;\n}\n\n/**\n * Determine whether `item` specifies a complete slice of array with the\n * given `shape`. Used to optimize __setitem__ operations on chunks\n * @param item\n * @param shape\n */\nexport function isTotalSlice(item: DimensionSelection | DimensionSelection[], shape: number[]): boolean {\n if (item === null) {\n return true;\n }\n if (!Array.isArray(item)) {\n item = [item];\n }\n\n for (let i = 0; i < Math.min(item.length, shape.length); i++) {\n const it = item[i];\n if (it === null) continue;\n\n if (isSlice(it)) {\n const s = it as Slice;\n const isStepOne = s.step === 1 || s.step === null;\n\n if (s.start === null && s.stop === null && isStepOne) {\n continue;\n }\n if (((s.stop as number) - (s.start as number)) === shape[i] && isStepOne) {\n continue;\n }\n return false;\n }\n return false;\n\n\n // } else {\n // console.error(`isTotalSlice unexpected non-slice, got ${it}`);\n // return false;\n // }\n }\n return true;\n}\n\n/**\n * Checks for === equality of all elements.\n */\nexport function arrayEquals1D(a: ArrayLike<any>, b: ArrayLike<any>) {\n if (a.length !== b.length) {\n return false;\n }\n\n for (let i = 0; i < a.length; i++) {\n if (a[i] !== b[i]) {\n return false;\n }\n }\n return true;\n}\n\n/*\n * Determines \"C\" order strides for a given shape array.\n * Strides provide integer steps in each dimention to traverse an ndarray.\n *\n * NOTE: - These strides here are distinct from numpy.ndarray.strides, which describe actual byte steps.\n * - Strides are assumed to be contiguous, so initial step is 1. Thus, output will always be [XX, XX, 1].\n */\nexport function getStrides(shape: number[]): number[] {\n // adapted from https://github.com/scijs/ndarray/blob/master/ndarray.js#L326-L330\n const ndim = shape.length;\n const strides = Array(ndim);\n let step = 1; // init step\n for (let i = ndim - 1; i >= 0; i--) {\n strides[i] = step;\n step *= shape[i];\n }\n return strides;\n}\n\nexport function resolveUrl(root: string | URL, path: string): string {\n const base = typeof root === 'string' ? new URL(root) : root;\n if (!base.pathname.endsWith('/')) {\n // ensure trailing slash so that base is resolved as _directory_\n base.pathname += '/';\n }\n const resolved = new URL(path, base);\n // copy search params to new URL\n resolved.search = base.search;\n return resolved.href;\n}\n\n/**\n * Swaps byte order in-place for a given TypedArray.\n * Used to flip endian-ness when getting/setting chunks from/to zarr store.\n * @param src TypedArray\n */\nexport function byteSwapInplace(src: TypedArray): void {\n const b = src.BYTES_PER_ELEMENT;\n if (b === 1) return; // no swapping needed\n if (IS_NODE) {\n // Use builtin methods for swapping if in Node environment\n const bytes = Buffer.from(src.buffer, src.byteOffset, src.length * b);\n if (b === 2) bytes.swap16();\n if (b === 4) bytes.swap32();\n if (b === 8) bytes.swap64();\n return;\n }\n // In browser, need to flip manually\n // Adapted from https://github.com/zbjornson/node-bswap/blob/master/bswap.js\n const flipper = new Uint8Array(src.buffer, src.byteOffset, src.length * b);\n const numFlips = b / 2;\n const endByteIndex = b - 1;\n let t: number;\n for (let i = 0; i < flipper.length; i += b) {\n for (let j = 0; j < numFlips; j++) {\n t = flipper[i + j];\n flipper[i + j] = flipper[i + endByteIndex - j];\n flipper[i + endByteIndex - j] = t;\n }\n }\n}\n\n/**\n * Creates a copy of a TypedArray and swaps bytes.\n * Used to flip endian-ness when getting/setting chunks from/to zarr store.\n * @param src TypedArray\n */\nexport function byteSwap(src: TypedArray): TypedArray {\n const copy = src.slice();\n byteSwapInplace(copy);\n return copy;\n}\n\nfunction convertColMajorToRowMajor2D(src: TypedArray, out: TypedArray, shape: number[]): void {\n let idx = 0;\n const shape0 = shape[0];\n const shape1 = shape[1];\n const stride0 = shape1;\n for (let i1 = 0; i1 < shape1; i1++) {\n for (let i0 = 0; i0 < shape0; i0++) {\n out[i0 * stride0 + i1] = src[idx++];\n }\n }\n}\n\nfunction convertColMajorToRowMajor3D(src: TypedArray, out: TypedArray, shape: number[]): void {\n let idx = 0;\n const shape0 = shape[0];\n const shape1 = shape[1];\n const shape2 = shape[2];\n const stride0 = shape2 * shape1;\n const stride1 = shape2;\n for (let i2 = 0; i2 < shape2; i2++) {\n for (let i1 = 0; i1 < shape1; i1++) {\n for (let i0 = 0; i0 < shape0; i0++) {\n out[i0 * stride0 + i1 * stride1 + i2] = src[idx++];\n }\n }\n }\n}\n\nfunction convertColMajorToRowMajor4D(src: TypedArray, out: TypedArray, shape: number[]): void {\n let idx = 0;\n const shape0 = shape[0];\n const shape1 = shape[1];\n const shape2 = shape[2];\n const shape3 = shape[3];\n const stride0 = shape3 * shape2 * shape1;\n const stride1 = shape3 * shape2;\n const stride2 = shape3;\n for (let i3 = 0; i3 < shape3; i3++) {\n for (let i2 = 0; i2 < shape2; i2++) {\n for (let i1 = 0; i1 < shape1; i1++) {\n for (let i0 = 0; i0 < shape0; i0++) {\n out[i0 * stride0 + i1 * stride1 + i2 * stride2 + i3] = src[idx++];\n }\n }\n }\n }\n}\n\nfunction convertColMajorToRowMajorGeneric(src: TypedArray, out: TypedArray, shape: number[]): void {\n const nDims = shape.length;\n const size = shape.reduce((r, a) => r * a);\n\n const rowMajorStrides = shape.map((_, i) =>\n i + 1 === nDims ? 1 : shape.slice(i + 1).reduce((r, a) => r * a, 1)\n );\n\n const index = Array(nDims).fill(0);\n\n for (let colMajorIdx = 0; colMajorIdx < size; colMajorIdx++) {\n let rowMajorIdx = 0;\n for (let dim = 0; dim < nDims; dim++) {\n rowMajorIdx += index[dim] * rowMajorStrides[dim];\n }\n out[rowMajorIdx] = src[colMajorIdx];\n\n index[0] += 1;\n // Handle carry-over\n for (let dim = 0; dim < nDims; dim++) {\n if (index[dim] === shape[dim]) {\n if (dim + 1 === nDims) {\n return;\n }\n index[dim] = 0;\n index[dim + 1] += 1;\n }\n }\n }\n}\n\nconst colMajorToRowMajorConverters: {\n [dim: number]: (src: TypedArray, out: TypedArray, shape: number[]) => void;\n} = {\n [0]: noop,\n [1]: noop,\n [2]: convertColMajorToRowMajor2D,\n [3]: convertColMajorToRowMajor3D,\n [4]: convertColMajorToRowMajor4D,\n};\n\n/**\n * Rewrites a copy of a TypedArray while converting it from column-major (F-order) to row-major (C-order).\n * @param src TypedArray\n * @param out TypedArray\n * @param shape number[]\n */\nexport function convertColMajorToRowMajor(src: TypedArray, out: TypedArray, shape: number[]): void {\n return (colMajorToRowMajorConverters[shape.length] || convertColMajorToRowMajorGeneric)(\n src,\n out,\n shape\n );\n}\n\nexport function isArrayBufferLike(obj: unknown | null): obj is ArrayBufferLike {\n if (obj === null) {\n return false;\n }\n if (obj instanceof ArrayBuffer) {\n return true;\n }\n if (typeof SharedArrayBuffer === \"function\" && obj instanceof SharedArrayBuffer) {\n return true;\n }\n if (IS_NODE) { // Necessary for Node.js for some reason..\n return (obj as Record<string, unknown>).toString().startsWith(\"[object ArrayBuffer]\")\n || (obj as Record<string, unknown>).toString().startsWith(\"[object SharedArrayBuffer]\");\n }\n return false;\n}\n","export const ARRAY_META_KEY = \".zarray\";\nexport const GROUP_META_KEY = \".zgroup\";\nexport const ATTRS_META_KEY = \".zattrs\";\n","import { normalizeStoragePath, normalizeChunks, normalizeDtype, normalizeShape, normalizeOrder, normalizeFillValue } from '../util';\nimport { Store } from './types';\nimport { ARRAY_META_KEY, GROUP_META_KEY } from '../names';\nimport { FillType, Order, Filter, CompressorConfig, ZarrGroupMetadata, ChunksArgument, DtypeString, ZarrArrayMetadata, FillTypeSerialized } from '../types';\nimport { ContainsArrayError, ContainsGroupError } from '../errors';\n\n\n/**\n * Return true if the store contains an array at the given logical path.\n */\nexport async function containsArray(store: Store, path: string | null = null) {\n path = normalizeStoragePath(path);\n const prefix = pathToPrefix(path);\n const key = prefix + ARRAY_META_KEY;\n return store.containsItem(key);\n}\n\n/**\n * Return true if the store contains a group at the given logical path.\n */\nexport async function containsGroup(store: Store, path: string | null = null) {\n path = normalizeStoragePath(path);\n const prefix = pathToPrefix(path);\n const key = prefix + GROUP_META_KEY;\n return store.containsItem(key);\n}\n\n\nexport function pathToPrefix(path: string): string {\n // assume path already normalized\n if (path.length > 0) {\n return path + '/';\n }\n return '';\n}\n\nasync function listDirFromKeys(store: Store, path: string) {\n // assume path already normalized\n const prefix = pathToPrefix(path);\n const children = new Set<string>();\n\n for (const key in await store.keys()) {\n if (key.startsWith(prefix) && key.length > prefix.length) {\n const suffix = key.slice(prefix.length);\n const child = suffix.split('/')[0];\n children.add(child);\n }\n }\n return Array.from(children).sort();\n}\n\nasync function requireParentGroup(store: Store, path: string, chunkStore: Store | null, overwrite: boolean) {\n // Assume path is normalized\n if (path.length === 0) {\n return;\n }\n\n const segments = path.split(\"/\");\n let p = \"\";\n for (const s of segments.slice(0, segments.length - 1)) {\n p += s;\n if (await containsArray(store, p)) {\n await initGroupMetadata(store, p, overwrite);\n } else if (!await containsGroup(store, p)) {\n await initGroupMetadata(store, p);\n }\n p += \"/\";\n }\n}\n\n/**\n * Obtain a directory listing for the given path. If `store` provides a `listDir`\n * method, this will be called, otherwise will fall back to implementation via the\n * `MutableMapping` interface.\n * @param store \n */\nexport async function listDir(store: Store, path: string | null = null) {\n path = normalizeStoragePath(path);\n if (store.listDir) {\n return store.listDir(path);\n } else {\n return listDirFromKeys(store, path);\n }\n}\n\nasync function initGroupMetadata(store: Store, path: string | null = null, overwrite = false) {\n path = normalizeStoragePath(path);\n\n // Guard conditions\n if (overwrite) {\n throw Error(\"Group overwriting not implemented yet :(\");\n } else if (await containsArray(store, path)) {\n throw new ContainsArrayError(path);\n } else if (await containsGroup(store, path)) {\n throw new ContainsGroupError(path);\n }\n\n const metadata: ZarrGroupMetadata = { zarr_format: 2 };\n const key = pathToPrefix(path) + GROUP_META_KEY;\n await store.setItem(key, JSON.stringify(metadata));\n}\n/**\n * Initialize a group store. Note that this is a low-level function and there should be no\n * need to call this directly from user code.\n */\nexport async function initGroup(store: Store, path: string | null = null, chunkStore: null | Store = null, overwrite = false) {\n path = normalizeStoragePath(path);\n await requireParentGroup(store, path, chunkStore, overwrite);\n await initGroupMetadata(store, path, overwrite);\n}\n\nasync function initArrayMetadata(\n store: Store,\n shape: number | number[],\n chunks: ChunksArgument,\n dtype: DtypeString,\n path: string,\n compressor: null | CompressorConfig,\n fillValue: FillType,\n order: Order,\n overwrite: boolean,\n chunkStore: null | Store,\n filters: null | Filter[],\n dimensionSeparator?: '.' | '/',\n) {\n // Guard conditions\n if (overwrite) {\n throw Error(\"Array overwriting not implemented yet :(\");\n } else if (await containsArray(store, path)) {\n throw new ContainsArrayError(path);\n } else if (await containsGroup(store, path)) {\n throw new ContainsGroupError(path);\n }\n\n // Normalize metadata, does type checking too.\n dtype = normalizeDtype(dtype);\n shape = normalizeShape(shape);\n chunks = normalizeChunks(chunks, shape);\n order = normalizeOrder(order);\n fillValue = normalizeFillValue(fillValue);\n\n if (filters !== null && filters.length > 0) {\n throw Error(\"Filters are not supported yet\");\n }\n\n let serializedFillValue: FillTypeSerialized = fillValue;\n\n if (typeof fillValue === \"number\") {\n if (Number.isNaN(fillValue)) serializedFillValue = \"NaN\";\n if (Number.POSITIVE_INFINITY === fillValue) serializedFillValue = \"Infinity\";\n if (Number.NEGATIVE_INFINITY === fillValue) serializedFillValue = \"-Infinity\";\n }\n\n filters = null;\n\n const metadata: ZarrArrayMetadata = {\n zarr_format: 2,\n shape: shape,\n chunks: chunks as number[],\n dtype: dtype,\n fill_value: serializedFillValue,\n order: order,\n compressor: compressor,\n filters: filters,\n };\n if (dimensionSeparator) {\n metadata.dimension_separator = dimensionSeparator;\n }\n const metaKey = pathToPrefix(path) + ARRAY_META_KEY;\n await store.setItem(metaKey, JSON.stringify(metadata));\n}\n\n/**\n * \n * Initialize an array store with the given configuration. Note that this is a low-level\n * function and there should be no need to call this directly from user code\n */\nexport async function initArray(\n store: Store,\n shape: number | number[],\n chunks: ChunksArgument,\n dtype: DtypeString,\n path: string | null = null,\n compressor: null | CompressorConfig = null,\n fillValue: FillType = null,\n order: Order = \"C\",\n overwrite = false,\n chunkStore: null | Store = null,\n filters: null | Filter[] = null,\n dimensionSeparator?: '.' | '/',\n) {\n\n path = normalizeStoragePath(path);\n await requireParentGroup(store, path, chunkStore, overwrite);\n await initArrayMetadata(store, shape, chunks, dtype, path, compressor, fillValue, order, overwrite, chunkStore, filters, dimensionSeparator);\n}\n","import { ZarrMetadataType, UserAttributes } from './types';\nimport { ValidStoreType } from './storage/types';\nimport { isArrayBufferLike, IS_NODE } from './util';\n\nexport function parseMetadata(\n s: ValidStoreType | ZarrMetadataType\n): ZarrMetadataType | UserAttributes {\n // Here we allow that a store may return an already-parsed metadata object,\n // or a string of JSON that we will parse here. We allow for an already-parsed\n // object to accommodate a consolidated metadata store, where all the metadata for\n // all groups and arrays will already have been parsed from JSON.\n if (typeof s !== 'string') {\n // tslint:disable-next-line: strict-type-predicates\n if (IS_NODE && Buffer.isBuffer(s)) {\n return JSON.parse(s.toString());\n } else if (isArrayBufferLike(s)) {\n const utf8Decoder = new TextDecoder();\n const bytes = new Uint8Array(s);\n return JSON.parse(utf8Decoder.decode(bytes));\n } else {\n return s;\n }\n }\n return JSON.parse(s);\n}\n","import { createProxy, AsyncMutableMapping, AsyncMutableMappingProxy } from './mutableMapping';\nimport { Store } from './storage/types';\nimport { parseMetadata } from './metadata';\nimport { UserAttributes } from './types';\nimport { PermissionError } from './errors';\n\n/**\n * Class providing access to user attributes on an array or group. Should not be\n * instantiated directly, will be available via the `.attrs` property of an array or\n * group.\n */\nexport class Attributes<M extends UserAttributes> implements AsyncMutableMapping<any> {\n store: Store;\n key: string;\n readOnly: boolean;\n cache: boolean;\n private cachedValue: M | null;\n\n constructor(store: Store, key: string, readOnly: boolean, cache = true) {\n this.store = store;\n this.key = key;\n this.readOnly = readOnly;\n this.cache = cache;\n this.cachedValue = null;\n }\n\n /**\n * Retrieve all attributes as a JSON object.\n */\n public async asObject() {\n if (this.cache && this.cachedValue !== null) {\n return this.cachedValue;\n }\n const o = await this.getNoSync();\n if (this.cache) {\n this.cachedValue = o;\n }\n return o;\n }\n\n private async getNoSync(): Promise<M> {\n try {\n const data = await this.store.getItem(this.key);\n // TODO fix typing?\n return parseMetadata(data) as M;\n } catch (error) {\n return {} as M;\n }\n }\n\n private async setNoSync(key: string, value: any) {\n const d = await this.getNoSync();\n (d as any)[key] = value;\n await this.putNoSync(d);\n return true;\n }\n\n private async putNoSync(m: M) {\n await this.store.setItem(this.key, JSON.stringify(m));\n if (this.cache) {\n this.cachedValue = m;\n }\n }\n\n private async delNoSync(key: string): Promise<boolean> {\n const d = await this.getNoSync();\n delete (d as any)[key];\n await this.putNoSync(d);\n return true;\n }\n\n /**\n * Overwrite all attributes with the provided object in a single operation\n */\n async put(d: M) {\n if (this.readOnly) {\n throw new PermissionError(\"attributes are read-only\");\n }\n return this.putNoSync(d);\n }\n\n async setItem(key: string, value: any): Promise<boolean> {\n if (this.readOnly) {\n throw new PermissionError(\"attributes are read-only\");\n }\n return this.setNoSync(key, value);\n }\n\n async getItem(key: string) {\n return ((await this.asObject()) as any)[key];\n }\n\n async deleteItem(key: string) {\n if (this.readOnly) {\n throw new PermissionError(\"attributes are read-only\");\n }\n return this.delNoSync(key);\n }\n\n async containsItem(key: string) {\n return ((await this.asObject()) as any)[key] !== undefined;\n }\n\n proxy(): AsyncMutableMappingProxy<any> {\n return createProxy(this);\n }\n}","import { DtypeString } from '../types';\nimport { ValueError } from '../errors';\n\n// Conditionally get the type for `Float16Array` based on end user TS settings. If not\n// present, then the type if `never` (and thus excluded from unions).\ntype Float16ArrayConstructor = typeof globalThis extends { Float16Array: infer T } ? T : never;\n// eslint-disable-next-line @typescript-eslint/naming-convention\nconst Float16Array = (globalThis as any).Float16Array as Float16ArrayConstructor;\n\nexport type NestedArrayData = TypedArray | NDNestedArrayData;\nexport type NDNestedArrayData =\n | TypedArray[]\n | TypedArray[][]\n | TypedArray[][][]\n | TypedArray[][][][]\n | TypedArray[][][][][]\n | TypedArray[][][][][][];\n\nexport type TypedArray =\n | Uint8Array\n | Int8Array\n | Uint16Array\n | Int16Array\n | Uint32Array\n | Int32Array\n | Float32Array\n | Float64Array\n | InstanceType<Float16ArrayConstructor>;\n\nexport type TypedArrayConstructor<T extends TypedArray> = {\n new(): T;\n // tslint:disable-next-line: unified-signatures\n new(size: number): T;\n // tslint:disable-next-line: unified-signatures\n new(buffer: ArrayBuffer): T;\n BYTES_PER_ELEMENT: number;\n};\n\nconst DTYPE_TYPEDARRAY_MAPPING: { [A in DtypeString]: TypedArrayConstructor<TypedArray> } = {\n '|b': Int8Array,\n '|b1': Uint8Array,\n '|B': Uint8Array,\n '|u1': Uint8Array,\n '|i1': Int8Array,\n '<b': Int8Array,\n '<B': Uint8Array,\n '<u1': Uint8Array,\n '<i1': Int8Array,\n '<u2': Uint16Array,\n '<i2': Int16Array,\n '<u4': Uint32Array,\n '<i4': Int32Array,\n '<f4': Float32Array,\n '<f2': Float16Array,\n '<f8': Float64Array,\n '>b': Int8Array,\n '>B': Uint8Array,\n '>u1': Uint8Array,\n '>i1': Int8Array,\n '>u2': Uint16Array,\n '>i2': Int16Array,\n '>u4': Uint32Array,\n '>i4': Int32Array,\n '>f4': Float32Array,\n '>f2': Float16Array,\n '>f8': Float64Array\n};\n\nexport function getTypedArrayCtr(dtype: DtypeString) {\n const ctr = DTYPE_TYPEDARRAY_MAPPING[dtype];\n if (!ctr) {\n if (dtype.slice(1) === 'f2') {\n throw Error(\n `'${dtype}' is not supported natively in zarr.js. ` +\n `In order to access this dataset you must make Float16Array available as a global. ` +\n `See https://github.com/gzuidhof/zarr.js/issues/127`\n );\n }\n throw Error(`Dtype not recognized or not supported in zarr.js, got ${dtype}.`);\n }\n return ctr;\n}\n\n/*\n * Called by NestedArray and RawArray constructors only.\n * We byte-swap the buffer of a store after decoding\n * since TypedArray views are little endian only.\n *\n * This means NestedArrays and RawArrays will always be little endian,\n * unless a numpy-like library comes around and can handle endianess\n * for buffer views.\n */\nexport function getTypedArrayDtypeString(t: TypedArray): DtypeString {\n // Favour the types below instead of small and big B\n if (t instanceof Uint8Array) return '|u1';\n if