From e400aee6055c4d29d7eb1510d6dce04ae2413866 Mon Sep 17 00:00:00 2001 From: Peter Todd Date: Fri, 31 Jul 2026 18:59:47 +0100 Subject: [PATCH 1/4] Remove the unused zarrSchema module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The v2/v3 `.zarray` zod schemas had no callers and were never exported from the package. Routing the tree builder through them was the alternative considered, and it runs against the decision this package just made: `ZarrArrayMetadata` admits an unrecognised record precisely so a store carrying a data type we do not model still opens, and `getArrayDtype` answers `undefined` for such a type rather than throwing. A schema that rejects the whole store at open time is the opposite of that — and zarr v3 extension dtypes, written as objects rather than strings, are exactly the case it would have rejected. `zarrita` already validates array metadata on the real read path. The one useful thing the module did — reconciling v2's `dtype` with v3's `data_type` — is now `normalizeDtype`, which does it in `zarrita`'s own `DataType` vocabulary rather than leaving a numpy typestring sitting in a v3-shaped `data_type` field, as `validateAndConvertV2Zarray` did. `ZarrV3ArrayNode` stays: it is exported, and describes metadata as it arrives from a store rather than anything the deleted validator produced. Its docstring pointed at `validateV3Zarray` for that contrast, so it now points at `ZarrArrayMetadata`, where the rationale lives. Drops `zod` from the package, which nothing else in it imports. Co-Authored-By: Claude Opus 5 --- .changeset/hungry-donkeys-shave.md | 9 + packages/zarrextra/package.json | 3 +- packages/zarrextra/src/types.ts | 4 +- packages/zarrextra/src/zarrSchema.ts | 303 -------------------- packages/zarrextra/tests/zarrSchema.spec.ts | 264 ----------------- pnpm-lock.yaml | 3 - 6 files changed, 12 insertions(+), 574 deletions(-) create mode 100644 .changeset/hungry-donkeys-shave.md delete mode 100644 packages/zarrextra/src/zarrSchema.ts delete mode 100644 packages/zarrextra/tests/zarrSchema.spec.ts diff --git a/.changeset/hungry-donkeys-shave.md b/.changeset/hungry-donkeys-shave.md new file mode 100644 index 00000000..e7310025 --- /dev/null +++ b/.changeset/hungry-donkeys-shave.md @@ -0,0 +1,9 @@ +--- +'zarrextra': patch +--- + +Remove the unused `zarrSchema` module and drop the now-unneeded `zod` dependency. + +The v2/v3 `.zarray` zod schemas had no callers and were never exported. Validating tree metadata on open is a road this package has now deliberately not taken: `ZarrArrayMetadata` admits an unrecognised record precisely so a store carrying a data type we do not model still opens, and `getArrayDtype` answers `undefined` for such a type rather than throwing. A schema that rejects the whole store at open time is the opposite of that, and `zarrita` already validates array metadata on the real read path. + +The one genuinely useful thing the module did — reconciling v2's `dtype` with v3's `data_type` — is now `normalizeDtype`, which does it in `zarrita`'s own `DataType` vocabulary instead of leaving a numpy typestring in a v3-shaped field. diff --git a/packages/zarrextra/package.json b/packages/zarrextra/package.json index 3ad632c0..0c6394b2 100644 --- a/packages/zarrextra/package.json +++ b/packages/zarrextra/package.json @@ -32,8 +32,7 @@ "test:coverage": "vitest run --coverage" }, "dependencies": { - "zarrita": "catalog:", - "zod": "catalog:" + "zarrita": "catalog:" }, "devDependencies": { "@fideus-labs/fizarrita": "^1.4.1", diff --git a/packages/zarrextra/src/types.ts b/packages/zarrextra/src/types.ts index a4616d84..3b4e243b 100644 --- a/packages/zarrextra/src/types.ts +++ b/packages/zarrextra/src/types.ts @@ -70,8 +70,8 @@ export type ZarrV2ArrayNode = { * * Only `shape` and `data_type` are required here: the rest are optional in the * specification or omitted by real writers, and this type describes metadata as - * it arrives from a store rather than metadata that has been through - * {@link validateV3Zarray}. + * it arrives from a store — parsed, but not validated or normalised. Nothing + * validates it on the way in, by design: see {@link ZarrArrayMetadata}. */ export type ZarrV3ArrayNode = { shape: number[]; diff --git a/packages/zarrextra/src/zarrSchema.ts b/packages/zarrextra/src/zarrSchema.ts deleted file mode 100644 index fb8ebff2..00000000 --- a/packages/zarrextra/src/zarrSchema.ts +++ /dev/null @@ -1,303 +0,0 @@ -import { z } from 'zod'; -import type { ZarrV3ArrayNode } from './types'; - -//-------- LLM generated schemas for generic zarr metadata --------// -// low - mid confidence in validity, need more testing -// (since writing the above, has been subject to at least some more human scrutiny) - -/** - * Zod schema for non-negative integer (for shape dimensions) - */ -const nonNegativeInteger = z.number().int().nonnegative(); - -/** - * Zod schema for positive integer (for chunk dimensions) - */ -const positiveInteger = z.number().int().positive(); - -/** - * Zod schema for v2 filter object - */ -const v2FilterSchema = z - .object({ - id: z.string(), - configuration: z.record(z.string(), z.unknown()).optional(), - }) - .catchall(z.unknown()); - -/** - * Zod schema for v2 compressor object - */ -const v2CompressorSchema = z - .object({ - id: z.string(), - configuration: z.record(z.string(), z.unknown()).optional(), - }) - .catchall(z.unknown()); - -/** - * Zod schema for v3 codec - */ -const v3CodecSchema = z.object({ - name: z.string(), - configuration: z.record(z.string(), z.unknown()).optional(), -}); - -/** - * Zod schema for v3 chunk_grid configuration - * Note: chunk_shape can be empty for scalar arrays (0-dimensional) - */ -const v3ChunkGridConfigSchema = z.object({ - chunk_shape: z.array(positiveInteger), -}); - -/** - * Zod schema for v3 chunk_grid - */ -const v3ChunkGridSchema = z.object({ - name: z.string(), - configuration: v3ChunkGridConfigSchema, -}); - -/** - * Zod schema for v3 chunk_key_encoding - */ -const v3ChunkKeyEncodingSchema = z.object({ - name: z.string(), - configuration: z.object({ - separator: z.union([z.literal('/'), z.literal('.')]).default('/'), - }), -}); - -/** v3 datatypes - note - * the listing at https://zarr-specs.readthedocs.io/en/latest/v3/data-types/index.html misses 'string' - * making this fail on the first real object I tried it on. - * We may just allow it to pass more things if errors keep appearing here. - */ -const v3dtypeSchema = z.union([ - z.literal('bool'), - z.literal('int8'), - z.literal('int16'), - z.literal('int32'), - z.literal('int64'), - z.literal('uint8'), - z.literal('uint16'), - z.literal('uint32'), - z.literal('uint64'), - z.literal('float16'), - z.literal('float32'), - z.literal('float64'), - z.literal('complex64'), - z.literal('complex128'), - // "string" not listed in https://zarr-specs.readthedocs.io/en/latest/v3/data-types/index.html - // but appears in some table data, e.g. 'tables/table/obs/_index' in v0.7.0/blobs.zarr, proably also others. - z.literal('string'), - // Raw bits format: "r*" where * is a variable size (multiple of 8) - z - .string() - .regex(/^r\d+$/) - .superRefine((val, ctx) => { - const size = Number.parseInt(val.slice(1), 10); - if (size <= 0 || size % 8 !== 0) { - ctx.addIssue({ - code: 'custom', - message: `'${val}' Raw bits size must be a positive multiple of 8`, - }); - } - }), -]); - -/** - * Base v2 zarray schema (before refines) - * Note: shape and chunks can be empty arrays for scalar arrays (0-dimensional). - * In zarr, a scalar is represented as shape=[] and chunks=[]. - * When not empty, chunks must contain positive integers. - */ -const v2ZarrayBaseSchema = z.object({ - shape: z.array(nonNegativeInteger), - chunks: z.array(positiveInteger), // Empty array allowed for scalars; when not empty, values must be positive - dtype: z.string().min(1), - filters: z.array(v2FilterSchema).optional().nullable(), - compressor: v2CompressorSchema.optional().nullable(), - fill_value: z.union([z.number(), z.string(), z.boolean(), z.null()]).optional(), - dimension_names: z.array(z.string()).optional().nullable(), - zarr_format: z.number().optional(), -}); - -type V2ZarrayInput = z.infer; - -/** - * Zod schema for v2 zarray metadata - * Handles both regular arrays and scalar arrays (empty shape/chunks) - */ -export const v2ZarraySchema = v2ZarrayBaseSchema - .refine((data: V2ZarrayInput) => data.chunks.length === data.shape.length, { - message: 'chunks length must match shape length', - path: ['chunks'], - }) - .refine( - (data: V2ZarrayInput) => - !data.dimension_names || data.dimension_names.length === data.shape.length, - { - message: 'dimension_names length must match shape length', - path: ['dimension_names'], - } - ); -const storageTransformerSchema = z.object({ - name: z.string(), - configuration: z.unknown().optional(), -}); -/** - * Base v3 zarray schema (before refines) - * Note: shape and chunk_shape can be empty arrays for scalar arrays (0-dimensional). - * In zarr, a scalar is represented as shape=[] and chunk_shape=[]. - */ -const v3ZarrayBaseSchema = z.object({ - shape: z.array(nonNegativeInteger), // Empty array allowed for scalars - data_type: v3dtypeSchema, //z.string().min(1), - chunk_grid: v3ChunkGridSchema, - chunk_key_encoding: v3ChunkKeyEncodingSchema.optional(), - fill_value: z.union([z.number(), z.string(), z.boolean()]).optional(), - codecs: z.array(v3CodecSchema).optional().nullable(), - dimension_names: z.array(z.string()).optional().nullable(), - storage_transformers: z.array(storageTransformerSchema).optional().nullable(), - zarr_format: z.number().optional(), - node_type: z.literal('array').optional(), - attributes: z.record(z.string(), z.json()).optional(), -}); - -type V3ZarrayInput = z.infer; - -/** - * Zod schema for v3 zarray metadata (for hybrid formats) - * Handles both regular arrays and scalar arrays (empty shape/chunk_shape) - */ -export const v3ZarraySchema = v3ZarrayBaseSchema - .refine( - (data: V3ZarrayInput) => data.chunk_grid.configuration.chunk_shape.length === data.shape.length, - { - message: 'chunk_shape length must match shape length', - path: ['chunk_grid', 'configuration', 'chunk_shape'], - } - ) - .refine( - (data: V3ZarrayInput) => - !data.dimension_names || data.dimension_names.length === data.shape.length, - { - message: 'dimension_names length must match shape length', - path: ['dimension_names'], - } - ); - -/** - * Validate and convert v2 zarray metadata to v3 format - * Handles both pure v2 format (with chunks, dtype) and hybrid formats - * @throws Error if required fields are missing or invalid - */ -export function validateAndConvertV2Zarray(zarray: unknown, path: string): ZarrV3ArrayNode { - if (!zarray || typeof zarray !== 'object') { - throw new Error(`Invalid .zarray metadata at path '${path}': expected an object`); - } - const obj = zarray as Record; - - // Check if this already has v3 fields (hybrid/partially converted format) - const hasV3Fields = 'data_type' in obj && 'chunk_grid' in obj; - const hasV2Fields = 'shape' in obj && 'chunks' in obj && 'dtype' in obj; - - if (hasV3Fields && !hasV2Fields) { - // Already in v3 format - validate v3 fields - return validateV3Zarray(zarray, path); - } - - // Validate v2 format - const parseResult = v2ZarraySchema.safeParse(zarray); - if (!parseResult.success) { - const errors = parseResult.error.issues - .map((e: z.ZodIssue) => { - const pathStr = e.path.length > 0 ? `'${e.path.join('.')}'` : ''; - return `${pathStr}${pathStr ? ': ' : ''}${e.message}`; - }) - .join('; '); - throw new Error(`Invalid .zarray metadata at path '${path}': ${errors}`); - } - - const v2 = parseResult.data; - - // Convert v2 compressor and filters to v3 codecs - const codecs: ZarrV3ArrayNode['codecs'] = []; - - // Add filters first (if present) - if (v2.filters) { - for (const filter of v2.filters) { - codecs.push({ - name: filter.id, - configuration: filter.configuration, - }); - } - } - - // Add compressor (if present and not null) - if (v2.compressor) { - codecs.push({ - name: v2.compressor.id, - configuration: v2.compressor.configuration, - }); - } - - // Build v3 array node - return { - shape: v2.shape, - data_type: v2.dtype, - chunk_grid: { - name: 'regular', - configuration: { chunk_shape: v2.chunks }, - }, - chunk_key_encoding: { - name: 'default', - configuration: { separator: '/' }, - }, - fill_value: v2.fill_value ?? 0, - codecs, - attributes: {}, - dimension_names: v2.dimension_names ?? [], - zarr_format: 3, - node_type: 'array', - storage_transformers: [], - }; -} - -/** - * Validate v3 zarray metadata (for hybrid formats that already have v3 fields) - * @throws Error if required fields are missing or invalid - */ -export function validateV3Zarray(zarray: unknown, path: string): ZarrV3ArrayNode { - const parseResult = v3ZarraySchema.safeParse(zarray); - if (!parseResult.success) { - const errors = parseResult.error.issues - .map((e: z.core.$ZodIssue) => { - const pathStr = e.path.length > 0 ? `'${e.path.join('.')}'` : ''; - return `${pathStr}${pathStr ? ': ' : ''}${e.message}`; - }) - .join('; '); - throw new Error(`Invalid .zarray metadata at path '${path}': ${errors}`); - } - - const v3 = parseResult.data; - - return { - shape: v3.shape, - data_type: v3.data_type, - chunk_grid: v3.chunk_grid, - chunk_key_encoding: v3.chunk_key_encoding ?? { - name: 'default', - configuration: { separator: '/' }, - }, - fill_value: v3.fill_value ?? 0, - codecs: v3.codecs ?? [], - attributes: {}, - dimension_names: v3.dimension_names ?? [], - zarr_format: 3, - node_type: 'array', - storage_transformers: v3.storage_transformers ?? [], - }; -} diff --git a/packages/zarrextra/tests/zarrSchema.spec.ts b/packages/zarrextra/tests/zarrSchema.spec.ts deleted file mode 100644 index ad626e56..00000000 --- a/packages/zarrextra/tests/zarrSchema.spec.ts +++ /dev/null @@ -1,264 +0,0 @@ -import { describe, expect, it } from 'vitest'; -import { - validateAndConvertV2Zarray, - validateV3Zarray, - v2ZarraySchema, - v3ZarraySchema, -} from '../src/zarrSchema.js'; - -describe('zarrextra - Zarr Schema Validation', () => { - describe('v2ZarraySchema', () => { - it('should validate v2 zarray with filters and compressor', () => { - const zarray = { - shape: [100], - chunks: [10], - dtype: 'float64', - filters: [{ id: 'delta', configuration: { dtype: 'float64' } }], - compressor: { id: 'blosc', configuration: { cname: 'lz4' } }, - }; - - expect(() => v2ZarraySchema.parse(zarray)).not.toThrow(); - const result = v2ZarraySchema.parse(zarray); - expect(result.filters).toHaveLength(1); - expect(result.compressor?.id).toBe('blosc'); - }); - - it('should reject v2 zarray with mismatched shape/chunks length', () => { - const invalidZarray = { - shape: [100, 200], - chunks: [10], // Wrong length - dtype: 'float64', - }; - - expect(() => v2ZarraySchema.parse(invalidZarray)).toThrow(); - }); - - it('should reject v2 zarray with negative shape dimensions', () => { - const invalidZarray = { - shape: [-1, 100], - chunks: [10, 20], - dtype: 'float64', - }; - - expect(() => v2ZarraySchema.parse(invalidZarray)).toThrow(); - }); - - it('should reject v2 zarray with non-positive chunks', () => { - const invalidZarray = { - shape: [100], - chunks: [0], // Must be positive - dtype: 'float64', - }; - - expect(() => v2ZarraySchema.parse(invalidZarray)).toThrow(); - }); - - it('should reject v2 zarray with missing required fields', () => { - const invalidZarray = { - shape: [100], - // Missing chunks and dtype - }; - - expect(() => v2ZarraySchema.parse(invalidZarray)).toThrow(); - }); - - it('should validate v2 zarray with dimension_names matching shape length', () => { - const zarray = { - shape: [100, 200], - chunks: [10, 20], - dtype: 'float64', - dimension_names: ['y', 'x'], - }; - - expect(() => v2ZarraySchema.parse(zarray)).not.toThrow(); - const result = v2ZarraySchema.parse(zarray); - expect(result.dimension_names).toEqual(['y', 'x']); - }); - - it('should reject v2 zarray with dimension_names length mismatch', () => { - const invalidZarray = { - shape: [100, 200], - chunks: [10, 20], - dtype: 'float64', - dimension_names: ['y'], // Wrong length - }; - - expect(() => v2ZarraySchema.parse(invalidZarray)).toThrow(); - }); - - it('should validate v2 zarray with null fill_value', () => { - const zarray = { - shape: [100], - chunks: [10], - dtype: 'float64', - fill_value: null, // Valid for v2 - }; - - expect(() => v2ZarraySchema.parse(zarray)).not.toThrow(); - const result = v2ZarraySchema.parse(zarray); - expect(result.fill_value).toBeNull(); - }); - }); - - describe('v3ZarraySchema', () => { - it('should validate v3 zarray with codecs', () => { - const zarray = { - shape: [100], - data_type: 'float64', - chunk_grid: { - name: 'regular', - configuration: { chunk_shape: [10] }, - }, - codecs: [{ name: 'blosc', configuration: { cname: 'lz4' } }], - }; - - expect(() => v3ZarraySchema.parse(zarray)).not.toThrow(); - const result = v3ZarraySchema.parse(zarray); - expect(result.codecs).toHaveLength(1); - expect(result.codecs?.[0]?.name).toBe('blosc'); - }); - - it('should reject v3 zarray with mismatched shape/chunk_shape length', () => { - const invalidZarray = { - shape: [100, 200], - data_type: 'float64', - chunk_grid: { - name: 'regular', - configuration: { chunk_shape: [10] }, // Wrong length - }, - }; - - expect(() => v3ZarraySchema.parse(invalidZarray)).toThrow(); - }); - - it('should reject v3 zarray with missing required fields', () => { - const invalidZarray = { - shape: [100], - // Missing data_type and chunk_grid - }; - - expect(() => v3ZarraySchema.parse(invalidZarray)).toThrow(); - }); - }); - - describe('validateAndConvertV2Zarray', () => { - it('should convert valid v2 zarray to v3 format', () => { - const v2Zarray = { - shape: [100, 200], - chunks: [10, 20], - dtype: 'float64', - fill_value: 0, - zarr_format: 2, - }; - - const result = validateAndConvertV2Zarray(v2Zarray, 'test/path'); - - expect(result.shape).toEqual([100, 200]); - expect(result.data_type).toBe('float64'); - expect(result.chunk_grid.configuration.chunk_shape).toEqual([10, 20]); - expect(result.chunk_grid.name).toBe('regular'); - expect(result.chunk_key_encoding.name).toBe('default'); - expect(result.zarr_format).toBe(3); - expect(result.node_type).toBe('array'); - }); - - it('should convert v2 filters and compressor to v3 codecs', () => { - const v2Zarray = { - shape: [100], - chunks: [10], - dtype: 'float64', - filters: [{ id: 'delta', configuration: { dtype: 'float64' } }], - compressor: { id: 'blosc', configuration: { cname: 'lz4' } }, - }; - - const result = validateAndConvertV2Zarray(v2Zarray, 'test/path'); - - expect(result.codecs).toHaveLength(2); - expect(result.codecs[0]?.name).toBe('delta'); - expect(result.codecs[1]?.name).toBe('blosc'); - }); - - it('should handle v2 zarray with dimension_names', () => { - const v2Zarray = { - shape: [100, 200], - chunks: [10, 20], - dtype: 'float64', - dimension_names: ['y', 'x'], - }; - - const result = validateAndConvertV2Zarray(v2Zarray, 'test/path'); - - expect(result.dimension_names).toEqual(['y', 'x']); - }); - - it('should handle v2 zarray with null fill_value', () => { - const v2Zarray = { - shape: [100], - chunks: [10], - dtype: 'float64', - fill_value: null, // Valid for v2 - }; - - const result = validateAndConvertV2Zarray(v2Zarray, 'test/path'); - // Note: validateAndConvertV2Zarray converts null fill_value to 0 default - // This is expected behavior for the conversion function - expect(result.fill_value).toBe(0); - }); - - it('should handle v2 zarray that is already in v3 format (hybrid)', () => { - const hybridZarray = { - shape: [100], - data_type: 'float64', - chunk_grid: { - name: 'regular', - configuration: { chunk_shape: [10] }, - }, - // Missing v2 fields (chunks, dtype) - }; - - // Should use v3 validation path - const result = validateAndConvertV2Zarray(hybridZarray, 'test/path'); - expect(result.data_type).toBe('float64'); - expect(result.chunk_grid.configuration.chunk_shape).toEqual([10]); - }); - }); - - describe('validateV3Zarray', () => { - it('should provide default chunk_key_encoding if missing', () => { - const v3Zarray = { - shape: [100], - data_type: 'float64', - chunk_grid: { - name: 'regular', - configuration: { chunk_shape: [10] }, - }, - // Missing chunk_key_encoding - }; - - const result = validateV3Zarray(v3Zarray, 'test/path'); - - expect(result.chunk_key_encoding.name).toBe('default'); - expect(result.chunk_key_encoding.configuration.separator).toBe('/'); - }); - - it('should preserve provided optional fields', () => { - const v3Zarray = { - shape: [100], - data_type: 'float64', - chunk_grid: { - name: 'regular', - configuration: { chunk_shape: [10] }, - }, - fill_value: 42, - codecs: [{ name: 'blosc' }], - dimension_names: ['x'], - }; - - const result = validateV3Zarray(v3Zarray, 'test/path'); - - expect(result.fill_value).toBe(42); - expect(result.codecs).toHaveLength(1); - expect(result.dimension_names).toEqual(['x']); - }); - }); -}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c6d4bcc7..c98a4ffa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -508,9 +508,6 @@ importers: zarrita: specifier: 'catalog:' version: 0.7.1 - zod: - specifier: 'catalog:' - version: 4.1.13 devDependencies: '@types/node': specifier: 'catalog:' From de1ade7a1f514eb3cbccecf78e0574bb3fe63ef1 Mon Sep 17 00:00:00 2001 From: Peter Todd Date: Fri, 31 Jul 2026 19:00:47 +0100 Subject: [PATCH 2/4] Remove the dead v3 group and consolidated-metadata types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `ZarrV3GroupNode` and `ZarrV3Metadata` describe zarr.json's group and consolidated-metadata shapes, but nothing reads them: they reference only each other, are not exported from index.ts, and types.ts is not reachable through the package's exports map. The tree builder does not use them either — `parseStoreContents` works from zarrita's `store.contents()` rather than from a typed view of zarr.json. They predate the zarrSchema removal rather than being orphaned by it; they are going now only because that pass put the file under the light. Co-Authored-By: Claude Opus 5 --- packages/zarrextra/src/types.ts | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/packages/zarrextra/src/types.ts b/packages/zarrextra/src/types.ts index 3b4e243b..99d827b0 100644 --- a/packages/zarrextra/src/types.ts +++ b/packages/zarrextra/src/types.ts @@ -115,37 +115,6 @@ export type ZarrV3ArrayNode = { */ export type ZarrArrayMetadata = ZarrV2ArrayNode | ZarrV3ArrayNode | ZAttrsAny; -/** - * Zarr v3 group node metadata - */ -export type ZarrV3GroupNode = { - attributes: Record; - zarr_format: number; - consolidated_metadata: { - kind: string; - must_understand: boolean; - metadata: Record; - }; - node_type: 'group'; -}; - -/** - * Zarr v3 consolidated metadata structure (zarr.json) - * The actual structure has metadata nested under consolidated_metadata.metadata - * with path keys like "images/blobs_image", "labels/blobs_labels", etc. - * Each entry can be either a group node or an array node. - */ -export type ZarrV3Metadata = { - attributes: Record; - zarr_format: number; - consolidated_metadata: { - kind: string; - must_understand: boolean; - metadata: Record; - }; - node_type: 'group'; -}; - /** * This type is liable to change in future - for now, it has `zarritaStore` which is the `ListableStore` from `zarrita`, * and `tree: ZarrTree` which has the object hierarchy as described in the consolidated metadata as a mostly "Plain Old Javascript Object", From c7cfdda31b969062f47cc538d1d175fdc8e97b70 Mon Sep 17 00:00:00 2001 From: Peter Todd Date: Fri, 31 Jul 2026 21:34:11 +0100 Subject: [PATCH 3/4] Name both metadata files correctly in the changeset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v2 writes `.zarray` and v3 writes `zarr.json` — `metadataKeysForPath` reads both, and the `ZarrV2ArrayNode` / `ZarrV3ArrayNode` docstrings in the same package already say so. The changeset said "v2/v3 `.zarray`", which implies v3 uses `.zarray` too. Changesets become published CHANGELOG entries, so the wording is worth getting right. Co-Authored-By: Claude Opus 5 --- .changeset/hungry-donkeys-shave.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/hungry-donkeys-shave.md b/.changeset/hungry-donkeys-shave.md index e7310025..1ad24a5f 100644 --- a/.changeset/hungry-donkeys-shave.md +++ b/.changeset/hungry-donkeys-shave.md @@ -4,6 +4,6 @@ Remove the unused `zarrSchema` module and drop the now-unneeded `zod` dependency. -The v2/v3 `.zarray` zod schemas had no callers and were never exported. Validating tree metadata on open is a road this package has now deliberately not taken: `ZarrArrayMetadata` admits an unrecognised record precisely so a store carrying a data type we do not model still opens, and `getArrayDtype` answers `undefined` for such a type rather than throwing. A schema that rejects the whole store at open time is the opposite of that, and `zarrita` already validates array metadata on the real read path. +The zod schemas for v2 `.zarray` and v3 `zarr.json` array metadata had no callers and were never exported. Validating tree metadata on open is a road this package has now deliberately not taken: `ZarrArrayMetadata` admits an unrecognised record precisely so a store carrying a data type we do not model still opens, and `getArrayDtype` answers `undefined` for such a type rather than throwing. A schema that rejects the whole store at open time is the opposite of that, and `zarrita` already validates array metadata on the real read path. The one genuinely useful thing the module did — reconciling v2's `dtype` with v3's `data_type` — is now `normalizeDtype`, which does it in `zarrita`'s own `DataType` vocabulary instead of leaving a numpy typestring in a v3-shaped field. From ea48b78bb8c9418427bb42e2d545d43929fe98ad Mon Sep 17 00:00:00 2001 From: Peter Todd Date: Fri, 31 Jul 2026 21:36:09 +0100 Subject: [PATCH 4/4] Trim the changeset to what a consumer needs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nothing here was exported, so the only thing a `zarrextra` consumer can observe is `zod` leaving the dependency list. The argument for deleting rather than wiring the schemas into the tree builder is review material, and already lives in e400aee and the PR — a published CHANGELOG entry is the wrong place for it. Co-Authored-By: Claude Opus 5 --- .changeset/hungry-donkeys-shave.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.changeset/hungry-donkeys-shave.md b/.changeset/hungry-donkeys-shave.md index 1ad24a5f..c32be918 100644 --- a/.changeset/hungry-donkeys-shave.md +++ b/.changeset/hungry-donkeys-shave.md @@ -2,8 +2,6 @@ 'zarrextra': patch --- -Remove the unused `zarrSchema` module and drop the now-unneeded `zod` dependency. +Remove the unused `zarrSchema` module and drop `zod` from the package's dependencies. -The zod schemas for v2 `.zarray` and v3 `zarr.json` array metadata had no callers and were never exported. Validating tree metadata on open is a road this package has now deliberately not taken: `ZarrArrayMetadata` admits an unrecognised record precisely so a store carrying a data type we do not model still opens, and `getArrayDtype` answers `undefined` for such a type rather than throwing. A schema that rejects the whole store at open time is the opposite of that, and `zarrita` already validates array metadata on the real read path. - -The one genuinely useful thing the module did — reconciling v2's `dtype` with v3's `data_type` — is now `normalizeDtype`, which does it in `zarrita`'s own `DataType` vocabulary instead of leaving a numpy typestring in a v3-shaped field. +The v2 `.zarray` / v3 `zarr.json` schemas were never exported, so no public API changes. Array metadata stays unvalidated on the tree by design: `zarrita` validates on the real read path, and `getArrayDtype` already reconciles both generations' dtype spellings.