From b88cd79cd3208e1efdf734a11697b6334ca0483a Mon Sep 17 00:00:00 2001 From: Ashley Anderson Date: Wed, 13 Aug 2025 13:38:05 -0400 Subject: [PATCH 1/7] Add optional chunk caching to 'get()' function --- .../zarrita/__tests__/indexing/get.test.ts | 166 ++++++++++++++++++ packages/zarrita/src/indexing/get.ts | 34 +++- packages/zarrita/src/indexing/types.ts | 6 + packages/zarrita/src/indexing/util.ts | 1 + 4 files changed, 204 insertions(+), 3 deletions(-) diff --git a/packages/zarrita/__tests__/indexing/get.test.ts b/packages/zarrita/__tests__/indexing/get.test.ts index 21b43c02..8be81ff1 100644 --- a/packages/zarrita/__tests__/indexing/get.test.ts +++ b/packages/zarrita/__tests__/indexing/get.test.ts @@ -6,6 +6,7 @@ import { describe, expect, it } from "vitest"; import * as zarr from "../../src/index.js"; import { get } from "../../src/indexing/ops.js"; import { range } from "../../src/indexing/util.js"; +import type { ChunkCache } from "../../src/indexing/types.js"; let __dirname = path.dirname(url.fileURLToPath(import.meta.url)); @@ -661,3 +662,168 @@ describe("get v3", () => { expect(res.stride).toStrictEqual([1, 3, 9]); }); }); + +describe("chunk caching", () => { + async function get_array_with_cache(): Promise> { + let root = path.resolve(__dirname, "../../../../fixtures/v3/data.zarr"); + let store = zarr.root(new FileSystemStore(root)); + return zarr.open.v3(store.resolve("/2d.chunked.i2"), { kind: "array" }) as Promise>; + } + + it("should work without cache (default behavior)", async () => { + let arr = await get_array_with_cache(); + let result = await get(arr, null); + + expect(result.data).toStrictEqual(new Int16Array([1, 2, 3, 4])); + expect(result.shape).toStrictEqual([2, 2]); + }); + + it("should work with Map as cache", async () => { + let arr = await get_array_with_cache(); + let cache = new Map(); + + // First call should populate cache + let result1 = await get(arr, null, { cache }); + expect(result1.data).toStrictEqual(new Int16Array([1, 2, 3, 4])); + expect(cache.size).toBeGreaterThan(0); + + // Second call should use cache + let result2 = await get(arr, null, { cache }); + expect(result2.data).toStrictEqual(new Int16Array([1, 2, 3, 4])); + expect(result2.shape).toStrictEqual([2, 2]); + }); + + it("should cache chunks with proper keys", async () => { + let arr = await get_array_with_cache(); + let cache = new Map(); + + await get(arr, null, { cache }); + + // Check that cache keys are properly formatted + let keys = Array.from(cache.keys()); + expect(keys.length).toBeGreaterThan(0); + + // Keys should contain store ID, array path and chunk coordinates + for (let key of keys) { + expect(key).toMatch(/^store_\d+:\/2d\.chunked\.i2:c[.\\/]\d+([.\\/]\d+)*$/); + } + }); + + it("should reuse cached chunks across multiple calls", async () => { + let arr = await get_array_with_cache(); + let cache = new Map(); + + // Mock getChunk to count calls + let originalGetChunk = arr.getChunk; + let getChunkCallCount = 0; + arr.getChunk = async (...args) => { + getChunkCallCount++; + return originalGetChunk.call(arr, ...args); + }; + + // First call + await get(arr, null, { cache }); + let firstCallCount = getChunkCallCount; + expect(firstCallCount).toBeGreaterThan(0); + + // Second call should not fetch chunks again + await get(arr, null, { cache }); + expect(getChunkCallCount).toBe(firstCallCount); + + // Restore original method + arr.getChunk = originalGetChunk; + }); + + it("should work with custom cache implementation", async () => { + let arr = await get_array_with_cache(); + + // Custom cache that tracks operations + let operations: string[] = []; + let cache: ChunkCache = { + get(key: string) { + operations.push(`get:${key}`); + return undefined; // Always miss for this test + }, + set(key: string, _value: any) { + operations.push(`set:${key}`); + } + }; + + let result = await get(arr, null, { cache }); + + expect(result.data).toStrictEqual(new Int16Array([1, 2, 3, 4])); + expect(operations.length).toBeGreaterThan(0); + expect(operations.some(op => op.startsWith('get:'))).toBe(true); + expect(operations.some(op => op.startsWith('set:'))).toBe(true); + }); + + it("should handle cache hits correctly", async () => { + let arr = await get_array_with_cache(); + + // First, find out what cache keys are needed by doing a real call + let cache = new Map(); + await get(arr, null, { cache }); + let realKeys = Array.from(cache.keys()); + let realValues = Array.from(cache.values()); + + // Clear cache and pre-populate ALL chunks with fake data using the correct keys + cache.clear(); + for (let i = 0; i < realKeys.length; i++) { + let fakeChunkData = { + data: new Int16Array([99, 98, 97, 96]), // Use same fake data for all chunks + shape: realValues[i].shape, // Use original shape + stride: realValues[i].stride // Use original stride + }; + cache.set(realKeys[i], fakeChunkData); + } + + // Mock getChunk to ensure it's not called + let originalGetChunk = arr.getChunk; + arr.getChunk = async () => { + throw new Error("getChunk should not be called when cache hits"); + }; + + try { + let result = await get(arr, null, { cache }); + // Should get some fake data (exact result depends on how chunks are assembled) + expect(result.data).toBeInstanceOf(Int16Array); + expect(result.shape).toStrictEqual([2, 2]); + } finally { + // Restore original method + arr.getChunk = originalGetChunk; + } + }); + + it("should handle different arrays with separate cache entries", async () => { + let root = path.resolve(__dirname, "../../../../fixtures/v3/data.zarr"); + let store = zarr.root(new FileSystemStore(root)); + + let arr1 = await zarr.open.v3(store.resolve("/1d.contiguous.raw.i2"), { kind: "array" }); + let arr2 = await zarr.open.v3(store.resolve("/2d.contiguous.i2"), { kind: "array" }); + + let cache = new Map(); + + await get(arr1, null, { cache }); + await get(arr2, null, { cache }); + + // Should have separate cache entries for different arrays + let keys = Array.from(cache.keys()); + expect(keys.some(k => k.includes('/1d.contiguous.raw.i2:'))).toBe(true); + expect(keys.some(k => k.includes('/2d.contiguous.i2:'))).toBe(true); + }); + + it("should work with sliced access using cache", async () => { + let arr = await get_array_with_cache(); + let cache = new Map(); + + // Access a slice + let result = await get(arr, [zarr.slice(0, 1), null], { cache }); + + expect(result.shape).toStrictEqual([1, 2]); + expect(cache.size).toBeGreaterThan(0); + + // Access another slice that might reuse same chunks + let result2 = await get(arr, [zarr.slice(1, 2), null], { cache }); + expect(result2.shape).toStrictEqual([1, 2]); + }); +}); diff --git a/packages/zarrita/src/indexing/get.ts b/packages/zarrita/src/indexing/get.ts index 56d19e28..a8f53943 100644 --- a/packages/zarrita/src/indexing/get.ts +++ b/packages/zarrita/src/indexing/get.ts @@ -12,6 +12,24 @@ import type { } from "./types.js"; import { create_queue } from "./util.js"; +// WeakMap to assign unique IDs to store instances +const storeIdMap = new WeakMap(); +let storeIdCounter = 0; + +function getStoreId(store: any): string { + if (!storeIdMap.has(store)) { + storeIdMap.set(store, storeIdCounter++); + } + return `store_${storeIdMap.get(store)}`; +} + +function createCacheKey(arr: Array, chunk_coords: number[]): string { + let context = get_context(arr); + let chunkKey = context.encode_chunk_key(chunk_coords); + let storeId = getStoreId(arr.store); + return `${storeId}:${arr.path}:${chunkKey}`; +} + function unwrap( arr: TypedArray, idx: number, @@ -50,11 +68,21 @@ export async function get< ); let queue = opts.create_queue?.() ?? create_queue(); + let cache = opts.cache ?? { get: () => undefined, set: () => {} }; for (const { chunk_coords, mapping } of indexer) { queue.add(async () => { - let { data, shape, stride } = await arr.getChunk(chunk_coords, opts.opts); - let chunk = setter.prepare(data, shape, stride); - setter.set_from_chunk(out, chunk, mapping); + let cacheKey = createCacheKey(arr, chunk_coords); + let cachedChunk = cache.get(cacheKey); + + if (cachedChunk) { + let chunk = setter.prepare(cachedChunk.data as TypedArray, cachedChunk.shape, cachedChunk.stride); + setter.set_from_chunk(out, chunk, mapping); + } else { + let chunkData = await arr.getChunk(chunk_coords, opts.opts); + cache.set(cacheKey, chunkData); + let chunk = setter.prepare(chunkData.data, chunkData.shape, chunkData.stride); + setter.set_from_chunk(out, chunk, mapping); + } }); } diff --git a/packages/zarrita/src/indexing/types.ts b/packages/zarrita/src/indexing/types.ts index 6bb3a1d6..11b3c602 100644 --- a/packages/zarrita/src/indexing/types.ts +++ b/packages/zarrita/src/indexing/types.ts @@ -34,6 +34,11 @@ export type SetFromChunk> = ( proj: Projection[], ) => void; +export interface ChunkCache { + get(key: string): Chunk | undefined; + set(key: string, value: Chunk): any; +} + export type Setter> = { prepare: Prepare; set_from_chunk: SetFromChunk; @@ -42,6 +47,7 @@ export type Setter> = { export type Options = { create_queue?: () => ChunkQueue; + cache?: ChunkCache; }; export type GetOptions = Options & { opts?: O }; diff --git a/packages/zarrita/src/indexing/util.ts b/packages/zarrita/src/indexing/util.ts index cb6c9f10..85f94659 100644 --- a/packages/zarrita/src/indexing/util.ts +++ b/packages/zarrita/src/indexing/util.ts @@ -127,3 +127,4 @@ export function create_queue(): ChunkQueue { onIdle: () => Promise.all(promises), }; } + From 7ac0e2771df200936c6c0e6e79c5541ad2745c89 Mon Sep 17 00:00:00 2001 From: Ashley Anderson Date: Wed, 13 Aug 2025 13:53:33 -0400 Subject: [PATCH 2/7] Fix type and lint errors --- .../zarrita/__tests__/indexing/get.test.ts | 71 +++++++++++-------- packages/zarrita/src/indexing/get.ts | 21 ++++-- packages/zarrita/src/indexing/types.ts | 2 +- packages/zarrita/src/indexing/util.ts | 1 - 4 files changed, 57 insertions(+), 38 deletions(-) diff --git a/packages/zarrita/__tests__/indexing/get.test.ts b/packages/zarrita/__tests__/indexing/get.test.ts index 8be81ff1..3ccb395b 100644 --- a/packages/zarrita/__tests__/indexing/get.test.ts +++ b/packages/zarrita/__tests__/indexing/get.test.ts @@ -5,8 +5,9 @@ import { describe, expect, it } from "vitest"; import * as zarr from "../../src/index.js"; import { get } from "../../src/indexing/ops.js"; -import { range } from "../../src/indexing/util.js"; import type { ChunkCache } from "../../src/indexing/types.js"; +import { range } from "../../src/indexing/util.js"; +import type { Chunk, DataType } from "../../src/metadata.js"; let __dirname = path.dirname(url.fileURLToPath(import.meta.url)); @@ -667,13 +668,15 @@ describe("chunk caching", () => { async function get_array_with_cache(): Promise> { let root = path.resolve(__dirname, "../../../../fixtures/v3/data.zarr"); let store = zarr.root(new FileSystemStore(root)); - return zarr.open.v3(store.resolve("/2d.chunked.i2"), { kind: "array" }) as Promise>; + return zarr.open.v3(store.resolve("/2d.chunked.i2"), { + kind: "array", + }) as Promise>; } it("should work without cache (default behavior)", async () => { let arr = await get_array_with_cache(); let result = await get(arr, null); - + expect(result.data).toStrictEqual(new Int16Array([1, 2, 3, 4])); expect(result.shape).toStrictEqual([2, 2]); }); @@ -681,7 +684,7 @@ describe("chunk caching", () => { it("should work with Map as cache", async () => { let arr = await get_array_with_cache(); let cache = new Map(); - + // First call should populate cache let result1 = await get(arr, null, { cache }); expect(result1.data).toStrictEqual(new Int16Array([1, 2, 3, 4])); @@ -696,23 +699,25 @@ describe("chunk caching", () => { it("should cache chunks with proper keys", async () => { let arr = await get_array_with_cache(); let cache = new Map(); - + await get(arr, null, { cache }); - + // Check that cache keys are properly formatted let keys = Array.from(cache.keys()); expect(keys.length).toBeGreaterThan(0); - - // Keys should contain store ID, array path and chunk coordinates + + // Keys should contain store ID, array path and chunk coordinates for (let key of keys) { - expect(key).toMatch(/^store_\d+:\/2d\.chunked\.i2:c[.\\/]\d+([.\\/]\d+)*$/); + expect(key).toMatch( + /^store_\d+:\/2d\.chunked\.i2:c[.\\/]\d+([.\\/]\d+)*$/, + ); } }); it("should reuse cached chunks across multiple calls", async () => { let arr = await get_array_with_cache(); let cache = new Map(); - + // Mock getChunk to count calls let originalGetChunk = arr.getChunk; let getChunkCallCount = 0; @@ -736,7 +741,7 @@ describe("chunk caching", () => { it("should work with custom cache implementation", async () => { let arr = await get_array_with_cache(); - + // Custom cache that tracks operations let operations: string[] = []; let cache: ChunkCache = { @@ -744,35 +749,35 @@ describe("chunk caching", () => { operations.push(`get:${key}`); return undefined; // Always miss for this test }, - set(key: string, _value: any) { + set(key: string, _value: Chunk) { operations.push(`set:${key}`); - } + }, }; let result = await get(arr, null, { cache }); - + expect(result.data).toStrictEqual(new Int16Array([1, 2, 3, 4])); expect(operations.length).toBeGreaterThan(0); - expect(operations.some(op => op.startsWith('get:'))).toBe(true); - expect(operations.some(op => op.startsWith('set:'))).toBe(true); + expect(operations.some((op) => op.startsWith("get:"))).toBe(true); + expect(operations.some((op) => op.startsWith("set:"))).toBe(true); }); it("should handle cache hits correctly", async () => { let arr = await get_array_with_cache(); - + // First, find out what cache keys are needed by doing a real call let cache = new Map(); await get(arr, null, { cache }); let realKeys = Array.from(cache.keys()); let realValues = Array.from(cache.values()); - + // Clear cache and pre-populate ALL chunks with fake data using the correct keys cache.clear(); for (let i = 0; i < realKeys.length; i++) { let fakeChunkData = { data: new Int16Array([99, 98, 97, 96]), // Use same fake data for all chunks - shape: realValues[i].shape, // Use original shape - stride: realValues[i].stride // Use original stride + shape: realValues[i].shape, // Use original shape + stride: realValues[i].stride, // Use original stride }; cache.set(realKeys[i], fakeChunkData); } @@ -797,31 +802,35 @@ describe("chunk caching", () => { it("should handle different arrays with separate cache entries", async () => { let root = path.resolve(__dirname, "../../../../fixtures/v3/data.zarr"); let store = zarr.root(new FileSystemStore(root)); - - let arr1 = await zarr.open.v3(store.resolve("/1d.contiguous.raw.i2"), { kind: "array" }); - let arr2 = await zarr.open.v3(store.resolve("/2d.contiguous.i2"), { kind: "array" }); - + + let arr1 = await zarr.open.v3(store.resolve("/1d.contiguous.raw.i2"), { + kind: "array", + }); + let arr2 = await zarr.open.v3(store.resolve("/2d.contiguous.i2"), { + kind: "array", + }); + let cache = new Map(); - + await get(arr1, null, { cache }); await get(arr2, null, { cache }); - + // Should have separate cache entries for different arrays let keys = Array.from(cache.keys()); - expect(keys.some(k => k.includes('/1d.contiguous.raw.i2:'))).toBe(true); - expect(keys.some(k => k.includes('/2d.contiguous.i2:'))).toBe(true); + expect(keys.some((k) => k.includes("/1d.contiguous.raw.i2:"))).toBe(true); + expect(keys.some((k) => k.includes("/2d.contiguous.i2:"))).toBe(true); }); it("should work with sliced access using cache", async () => { let arr = await get_array_with_cache(); let cache = new Map(); - + // Access a slice let result = await get(arr, [zarr.slice(0, 1), null], { cache }); - + expect(result.shape).toStrictEqual([1, 2]); expect(cache.size).toBeGreaterThan(0); - + // Access another slice that might reuse same chunks let result2 = await get(arr, [zarr.slice(1, 2), null], { cache }); expect(result2.shape).toStrictEqual([1, 2]); diff --git a/packages/zarrita/src/indexing/get.ts b/packages/zarrita/src/indexing/get.ts index a8f53943..5ea0ce3b 100644 --- a/packages/zarrita/src/indexing/get.ts +++ b/packages/zarrita/src/indexing/get.ts @@ -16,14 +16,17 @@ import { create_queue } from "./util.js"; const storeIdMap = new WeakMap(); let storeIdCounter = 0; -function getStoreId(store: any): string { +function getStoreId(store: Readable): string { if (!storeIdMap.has(store)) { storeIdMap.set(store, storeIdCounter++); } return `store_${storeIdMap.get(store)}`; } -function createCacheKey(arr: Array, chunk_coords: number[]): string { +function createCacheKey( + arr: Array, + chunk_coords: number[], +): string { let context = get_context(arr); let chunkKey = context.encode_chunk_key(chunk_coords); let storeId = getStoreId(arr.store); @@ -73,14 +76,22 @@ export async function get< queue.add(async () => { let cacheKey = createCacheKey(arr, chunk_coords); let cachedChunk = cache.get(cacheKey); - + if (cachedChunk) { - let chunk = setter.prepare(cachedChunk.data as TypedArray, cachedChunk.shape, cachedChunk.stride); + let chunk = setter.prepare( + cachedChunk.data as TypedArray, + cachedChunk.shape, + cachedChunk.stride, + ); setter.set_from_chunk(out, chunk, mapping); } else { let chunkData = await arr.getChunk(chunk_coords, opts.opts); cache.set(cacheKey, chunkData); - let chunk = setter.prepare(chunkData.data, chunkData.shape, chunkData.stride); + let chunk = setter.prepare( + chunkData.data, + chunkData.shape, + chunkData.stride, + ); setter.set_from_chunk(out, chunk, mapping); } }); diff --git a/packages/zarrita/src/indexing/types.ts b/packages/zarrita/src/indexing/types.ts index 11b3c602..24fb43fb 100644 --- a/packages/zarrita/src/indexing/types.ts +++ b/packages/zarrita/src/indexing/types.ts @@ -36,7 +36,7 @@ export type SetFromChunk> = ( export interface ChunkCache { get(key: string): Chunk | undefined; - set(key: string, value: Chunk): any; + set(key: string, value: Chunk): void; } export type Setter> = { diff --git a/packages/zarrita/src/indexing/util.ts b/packages/zarrita/src/indexing/util.ts index 85f94659..cb6c9f10 100644 --- a/packages/zarrita/src/indexing/util.ts +++ b/packages/zarrita/src/indexing/util.ts @@ -127,4 +127,3 @@ export function create_queue(): ChunkQueue { onIdle: () => Promise.all(promises), }; } - From 8a1613e143e60bf97c94bd2cd72f9477331a29c1 Mon Sep 17 00:00:00 2001 From: Ashley Anderson Date: Wed, 13 Aug 2025 14:05:57 -0400 Subject: [PATCH 3/7] Clarify null cache object --- packages/zarrita/src/indexing/get.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/zarrita/src/indexing/get.ts b/packages/zarrita/src/indexing/get.ts index 5ea0ce3b..1608cf16 100644 --- a/packages/zarrita/src/indexing/get.ts +++ b/packages/zarrita/src/indexing/get.ts @@ -4,6 +4,7 @@ import { type Array, get_context } from "../hierarchy.js"; import type { Chunk, DataType, Scalar, TypedArray } from "../metadata.js"; import { BasicIndexer } from "./indexer.js"; import type { + ChunkCache, GetOptions, Prepare, SetFromChunk, @@ -12,6 +13,11 @@ import type { } from "./types.js"; import { create_queue } from "./util.js"; +const NULL_CACHE: ChunkCache = { + get: () => undefined, + set: () => {}, +}; + // WeakMap to assign unique IDs to store instances const storeIdMap = new WeakMap(); let storeIdCounter = 0; @@ -71,7 +77,7 @@ export async function get< ); let queue = opts.create_queue?.() ?? create_queue(); - let cache = opts.cache ?? { get: () => undefined, set: () => {} }; + let cache = opts.cache ?? NULL_CACHE; for (const { chunk_coords, mapping } of indexer) { queue.add(async () => { let cacheKey = createCacheKey(arr, chunk_coords); From 354749e6d80f42087124cc5ed3cc3cefdc3c9396 Mon Sep 17 00:00:00 2001 From: Ashley Anderson Date: Wed, 13 Aug 2025 14:27:26 -0400 Subject: [PATCH 4/7] Update tests --- .../zarrita/__tests__/indexing/get.test.ts | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/packages/zarrita/__tests__/indexing/get.test.ts b/packages/zarrita/__tests__/indexing/get.test.ts index 3ccb395b..5edf37c8 100644 --- a/packages/zarrita/__tests__/indexing/get.test.ts +++ b/packages/zarrita/__tests__/indexing/get.test.ts @@ -688,12 +688,14 @@ describe("chunk caching", () => { // First call should populate cache let result1 = await get(arr, null, { cache }); expect(result1.data).toStrictEqual(new Int16Array([1, 2, 3, 4])); - expect(cache.size).toBeGreaterThan(0); + const cacheSize = cache.size; + expect(cacheSize).toBeGreaterThan(0); // Second call should use cache let result2 = await get(arr, null, { cache }); expect(result2.data).toStrictEqual(new Int16Array([1, 2, 3, 4])); expect(result2.shape).toStrictEqual([2, 2]); + expect(cache.size).toBe(cacheSize); }); it("should cache chunks with proper keys", async () => { @@ -702,7 +704,6 @@ describe("chunk caching", () => { await get(arr, null, { cache }); - // Check that cache keys are properly formatted let keys = Array.from(cache.keys()); expect(keys.length).toBeGreaterThan(0); @@ -718,7 +719,6 @@ describe("chunk caching", () => { let arr = await get_array_with_cache(); let cache = new Map(); - // Mock getChunk to count calls let originalGetChunk = arr.getChunk; let getChunkCallCount = 0; arr.getChunk = async (...args) => { @@ -821,6 +821,31 @@ describe("chunk caching", () => { expect(keys.some((k) => k.includes("/2d.contiguous.i2:"))).toBe(true); }); + it("should handle multiple stores", async () => { + let root1 = path.resolve(__dirname, "../../../../fixtures/v3/data.zarr"); + let store1 = zarr.root(new FileSystemStore(root1)); + let arr1 = await zarr.open.v3(store1.resolve("/2d.chunked.i2"), { + kind: "array", + }); + + let root2 = path.resolve(__dirname, "../../../../fixtures/v2/data.zarr"); + let store2 = zarr.root(new FileSystemStore(root2)); + let arr2 = await zarr.open.v2(store2.resolve("/1d.contiguous.i4"), { + kind: "array", + }); + + let cache = new Map(); + + await get(arr1, null, { cache }); + await get(arr2, null, { cache }); + + expect(cache.size).toBeGreaterThan(0); + const storePrefixes = new Set(Array.from(cache.keys()).map((key) => { + return key.split(":")[0]; // Extract store ID from cache key + })); + expect(storePrefixes.size).toBe(2); // Should have entries for both stores + }); + it("should work with sliced access using cache", async () => { let arr = await get_array_with_cache(); let cache = new Map(); From 6d00af08ffcac7db1bac68a9191b92a9aac25cd7 Mon Sep 17 00:00:00 2001 From: Ashley Anderson Date: Wed, 13 Aug 2025 14:38:41 -0400 Subject: [PATCH 5/7] Fix lint --- packages/zarrita/__tests__/indexing/get.test.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/zarrita/__tests__/indexing/get.test.ts b/packages/zarrita/__tests__/indexing/get.test.ts index 5edf37c8..a6c5db21 100644 --- a/packages/zarrita/__tests__/indexing/get.test.ts +++ b/packages/zarrita/__tests__/indexing/get.test.ts @@ -840,9 +840,11 @@ describe("chunk caching", () => { await get(arr2, null, { cache }); expect(cache.size).toBeGreaterThan(0); - const storePrefixes = new Set(Array.from(cache.keys()).map((key) => { - return key.split(":")[0]; // Extract store ID from cache key - })); + const storePrefixes = new Set( + Array.from(cache.keys()).map((key) => { + return key.split(":")[0]; // Extract store ID from cache key + }), + ); expect(storePrefixes.size).toBe(2); // Should have entries for both stores }); From 503e8cf0d8421a83cf4cece7247ea9a14c91c85e Mon Sep 17 00:00:00 2001 From: Ashley Anderson Date: Fri, 12 Sep 2025 11:07:39 -0400 Subject: [PATCH 6/7] Add changeset (hopefully correct) --- .changeset/vast-things-design.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/vast-things-design.md diff --git a/.changeset/vast-things-design.md b/.changeset/vast-things-design.md new file mode 100644 index 00000000..6d024472 --- /dev/null +++ b/.changeset/vast-things-design.md @@ -0,0 +1,5 @@ +--- +"zarrita": minor +--- + +Added optional chunk cache for use with zarr.get From 7fe18d99a4811fca38cb78a154be9bde85566445 Mon Sep 17 00:00:00 2001 From: Ashley Anderson Date: Fri, 12 Sep 2025 11:13:49 -0400 Subject: [PATCH 7/7] Small test update --- packages/zarrita/__tests__/indexing/get.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/zarrita/__tests__/indexing/get.test.ts b/packages/zarrita/__tests__/indexing/get.test.ts index a6c5db21..b517a194 100644 --- a/packages/zarrita/__tests__/indexing/get.test.ts +++ b/packages/zarrita/__tests__/indexing/get.test.ts @@ -693,8 +693,7 @@ describe("chunk caching", () => { // Second call should use cache let result2 = await get(arr, null, { cache }); - expect(result2.data).toStrictEqual(new Int16Array([1, 2, 3, 4])); - expect(result2.shape).toStrictEqual([2, 2]); + expect(result2).toStrictEqual(result1); expect(cache.size).toBe(cacheSize); });