Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Supports **Node ≥18**, modern browsers, and Deno/Bun.

```ts
// Modern named export (recommended)
import { load } from "npyjs";
import { load, parse } from "npyjs";

// Back-compatibility class (matches legacy docs/tests)
import npyjs from "npyjs";
Expand All @@ -41,11 +41,14 @@ import npyjs from "npyjs";
### 1. Functional API (preferred)

```ts
import { load } from "npyjs";
import { load, parse } from "npyjs";

const arr = await load("my-array.npy");
// arr has { data, shape, dtype, fortranOrder }
console.log(arr.shape); // e.g., [100, 784]

// Parse bytes synchronously when fetching or reading is handled separately
const parsed = parse(arrayBuffer);
```

### 2. Legacy Class API (still supported)
Expand Down
7 changes: 5 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Supports **Node ≥18**, modern browsers, and Deno/Bun.

```ts
// Modern named export (recommended)
import { load } from "npyjs";
import { load, parse } from "npyjs";

// Back-compatibility class (matches legacy docs/tests)
import npyjs from "npyjs";
Expand All @@ -35,11 +35,14 @@ import npyjs from "npyjs";
### 1. Functional API (preferred)

```ts
import { load } from "npyjs";
import { load, parse } from "npyjs";

const arr = await load("my-array.npy");
// arr has { data, shape, dtype, fortranOrder }
console.log(arr.shape); // e.g., [100, 784]

// Parse bytes synchronously when fetching or reading is handled separately
const parsed = parse(arrayBuffer);
```

### 2. Legacy Class API (still supported)
Expand Down
12 changes: 10 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ function f16toF32(u16: number): number {
}

export async function load(source: string | ArrayBuffer | ArrayBufferView | Blob, opts: Options = {}): Promise<NpyArray> {
let buf: ArrayBufferLike;
let buf: ArrayBuffer;
if (typeof source === "string") {
const res = await fetch(source);
buf = await res.arrayBuffer();
Expand All @@ -152,9 +152,13 @@ export async function load(source: string | ArrayBuffer | ArrayBufferView | Blob
} else if (source instanceof Blob) {
buf = await source.arrayBuffer();
} else {
buf = source.buffer;
buf = source.buffer.slice(source.byteOffset, source.byteOffset + source.byteLength) as ArrayBuffer;
}

return parse(buf, opts);
}

export function parse(buf: ArrayBuffer, opts: Options = {}): NpyArray {
const { headerOffset, headerLen } = readHeader(buf);
const headerBytes = new Uint8Array(buf, headerOffset, headerLen);
const header = textDecoder.decode(headerBytes).trim();
Expand Down Expand Up @@ -384,6 +388,10 @@ export default class N {
async load(source: string | ArrayBuffer | ArrayBufferView) {
return load(source, this.opts);
}

parse(buf: ArrayBuffer) {
return parse(buf, this.opts);
}

static float16ToFloat32(u16: number) {
return f16toF32(u16);
Expand Down
47 changes: 41 additions & 6 deletions test/load.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// tests/load.test.ts
import { describe, it, expect, beforeAll, afterAll } from "vitest";
import { promises as fs } from "fs";
import { promises as fs, readFileSync } from "fs";
import path from "path";
import http from "http";

import N from "../index.js";
import N, { load, parse } from "../src/index.js";

// --- small HTTP file server for fetch() based loader ---
function startServer(root = process.cwd()): Promise<{ server: http.Server; baseUrl: string }> {
Expand Down Expand Up @@ -74,10 +74,45 @@ afterAll(async () => {
});

describe("npyjs parser", () => {
it("parses an ArrayBuffer synchronously", () => {
const file = readFileSync("test/data/10-float32.npy");
const buffer = file.buffer.slice(file.byteOffset, file.byteOffset + file.byteLength);

const data = parse(buffer);

expect(data.shape).toEqual([10]);
expect(data.dtype).toBe("f4");
expect(data.data).toBeInstanceOf(Float32Array);
});

it("returns the same result from load and parse", async () => {
const file = readFileSync("test/data/10-float32.npy");
const buffer = file.buffer.slice(file.byteOffset, file.byteOffset + file.byteLength);

expect(await load(buffer)).toEqual(parse(buffer));
});

it("loads only the bytes in an ArrayBufferView", async () => {
const file = readFileSync("test/data/10-float32.npy");
const padded = Buffer.concat([Buffer.from([0]), file, Buffer.from([0])]);
const view = padded.subarray(1, padded.length - 1);

expect(await load(view)).toEqual(parse(file.buffer.slice(file.byteOffset, file.byteOffset + file.byteLength)));
});

it("exposes synchronous parsing on the class API", () => {
const file = readFileSync("test/data/10-float16.npy");
const buffer = file.buffer.slice(file.byteOffset, file.byteOffset + file.byteLength);

const data = new N({ convertFloat16: false }).parse(buffer);

expect(data.data).toBeInstanceOf(Uint16Array);
});

it("parses npy files and matches tail values", async () => {
// records.json should map filename (no .npy) -> array of the last 5 expected values
const records = JSON.parse(await fs.readFile("test/records.json", "utf8"));
const n = new (N as any)();
const n = new N();

for (const fname of Object.keys(records)) {
const fpath = path.join("test", `${fname}.npy`);
Expand All @@ -93,7 +128,7 @@ describe("npyjs parser", () => {

it("loads from Blob", async () => {
const records = JSON.parse(await fs.readFile("test/records.json", "utf8"));
const n = new (N as any)();
const n = new N();

const firstFile = Object.keys(records)[0];
const fpath = path.join("test", `${firstFile}.npy`);
Expand Down Expand Up @@ -142,11 +177,11 @@ describe("npyjs parser", () => {
it("respects convertFloat16 flag (Float32Array vs Uint16Array)", async () => {
const url = `${baseUrl}/test/data/10-float16.npy`;

const nDefault = new (N as any)(); // conversion enabled by default
const nDefault = new N(); // conversion enabled by default
const converted = await nDefault.load(url);
expect(converted.data instanceof Float32Array).toBe(true);

const nRaw = new (N as any)({ convertFloat16: false });
const nRaw = new N({ convertFloat16: false });
const raw = await nRaw.load(url);
expect(raw.data instanceof Uint16Array).toBe(true);
});
Expand Down
Loading