diff --git a/.github/ci/Dockerfile b/.github/ci/Dockerfile index 7d74773d..f4bfa273 100644 --- a/.github/ci/Dockerfile +++ b/.github/ci/Dockerfile @@ -1,9 +1,8 @@ # ──────────────────────────────────────────────────────────────── # tensogram CI image # -# Pre-bakes Rust toolchain + system libs. Python and Node.js are -# installed at CI runtime by uv / curl (adds ~10 s per job that -# needs them, but shrinks the image by ~430 MB). +# Pre-bakes Rust toolchain, wasm-pack, Node.js, and system libs. +# Python is installed at CI runtime by uv. # # docker build -t tensogram-ci:test .github/ci/ # ──────────────────────────────────────────────────────────────── @@ -72,7 +71,7 @@ RUN set -eux \ COPY --from=builder /usr/local/rustup /usr/local/rustup COPY --from=builder /usr/local/cargo /usr/local/cargo -# ── uv (Python + Node installed at CI runtime) ───────────────── +# ── uv (Python installed at CI runtime) ──────────────────────── COPY --from=uv /uv /uvx /usr/local/bin/ # ── sccache (S3-backed compilation cache) ────────────────────── @@ -82,12 +81,20 @@ RUN set -eux \ | tar xz --strip-components=1 -C /usr/local/bin \ "sccache-v${SCCACHE_VERSION}-x86_64-unknown-linux-musl/sccache" +# ── Node.js (full distribution — node, npm, npx) ─────────────── +ARG NODE_VERSION=22.22.2 +RUN set -eux \ + && curl -fsSL "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.xz" \ + | tar xJ --strip-components=1 -C /usr/local \ + && rm -rf /usr/local/share/doc /usr/local/share/man /usr/local/share/systemtap + # ── Smoke test ────────────────────────────────────────────────── RUN set -eux \ && rustc --version && cargo --version \ && cmake --version \ && wasm-pack --version \ && uv --version \ - && sccache --version + && sccache --version \ + && node --version && npm --version WORKDIR /workspace diff --git a/.github/ci/VERSION b/.github/ci/VERSION index 26aaba0e..f0bb29e7 100644 --- a/.github/ci/VERSION +++ b/.github/ci/VERSION @@ -1 +1 @@ -1.2.0 +1.3.0 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 92b2296f..9a1b8e4f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,6 @@ env: CARGO_TERM_COLOR: always CARGO_INCREMENTAL: "0" TMPDIR: ${{ github.workspace }}/.tmp - NODE_VERSION: "22.22.2" # sccache with S3-compatible backend (ECMWF object store). # AWS_* env var names are required by the S3 SDK that sccache uses # internally — no actual AWS service is involved. @@ -38,7 +37,7 @@ jobs: name: Lint runs-on: [self-hosted, Linux, platform-builder-docker-xl, platform-builder-Ubuntu-22.04] container: - image: eccr.ecmwf.int/tensogram/ci:1.2.0 + image: eccr.ecmwf.int/tensogram/ci:1.3.0 credentials: username: ${{ secrets.ECMWF_DOCKER_REGISTRY_USERNAME }} password: ${{ secrets.ECMWF_DOCKER_REGISTRY_ACCESS_TOKEN }} @@ -62,7 +61,7 @@ jobs: name: Test (Rust) runs-on: [self-hosted, Linux, platform-builder-docker-xl, platform-builder-Ubuntu-22.04] container: - image: eccr.ecmwf.int/tensogram/ci:1.2.0 + image: eccr.ecmwf.int/tensogram/ci:1.3.0 credentials: username: ${{ secrets.ECMWF_DOCKER_REGISTRY_USERNAME }} password: ${{ secrets.ECMWF_DOCKER_REGISTRY_ACCESS_TOKEN }} @@ -89,7 +88,7 @@ jobs: name: Test (Python 3.13) runs-on: [self-hosted, Linux, platform-builder-docker-xl, platform-builder-Ubuntu-22.04] container: - image: eccr.ecmwf.int/tensogram/ci:1.2.0 + image: eccr.ecmwf.int/tensogram/ci:1.3.0 credentials: username: ${{ secrets.ECMWF_DOCKER_REGISTRY_USERNAME }} password: ${{ secrets.ECMWF_DOCKER_REGISTRY_ACCESS_TOKEN }} @@ -121,7 +120,7 @@ jobs: name: Test (xarray + zarr) runs-on: [self-hosted, Linux, platform-builder-docker-xl, platform-builder-Ubuntu-22.04] container: - image: eccr.ecmwf.int/tensogram/ci:1.2.0 + image: eccr.ecmwf.int/tensogram/ci:1.3.0 credentials: username: ${{ secrets.ECMWF_DOCKER_REGISTRY_USERNAME }} password: ${{ secrets.ECMWF_DOCKER_REGISTRY_ACCESS_TOKEN }} @@ -154,7 +153,7 @@ jobs: name: Test (Jupyter notebooks) runs-on: [self-hosted, Linux, platform-builder-docker-xl, platform-builder-Ubuntu-22.04] container: - image: eccr.ecmwf.int/tensogram/ci:1.2.0 + image: eccr.ecmwf.int/tensogram/ci:1.3.0 credentials: username: ${{ secrets.ECMWF_DOCKER_REGISTRY_USERNAME }} password: ${{ secrets.ECMWF_DOCKER_REGISTRY_ACCESS_TOKEN }} @@ -213,7 +212,7 @@ jobs: name: Test (Python 3.13t free-threaded) runs-on: [self-hosted, Linux, platform-builder-docker-xl, platform-builder-Ubuntu-22.04] container: - image: eccr.ecmwf.int/tensogram/ci:1.2.0 + image: eccr.ecmwf.int/tensogram/ci:1.3.0 credentials: username: ${{ secrets.ECMWF_DOCKER_REGISTRY_USERNAME }} password: ${{ secrets.ECMWF_DOCKER_REGISTRY_ACCESS_TOKEN }} @@ -327,7 +326,7 @@ jobs: name: C++ (Linux) runs-on: [self-hosted, Linux, platform-builder-docker-xl, platform-builder-Ubuntu-22.04] container: - image: eccr.ecmwf.int/tensogram/ci:1.2.0 + image: eccr.ecmwf.int/tensogram/ci:1.3.0 credentials: username: ${{ secrets.ECMWF_DOCKER_REGISTRY_USERNAME }} password: ${{ secrets.ECMWF_DOCKER_REGISTRY_ACCESS_TOKEN }} @@ -392,7 +391,7 @@ jobs: name: WASM runs-on: [self-hosted, Linux, platform-builder-docker-xl, platform-builder-Ubuntu-22.04] container: - image: eccr.ecmwf.int/tensogram/ci:1.2.0 + image: eccr.ecmwf.int/tensogram/ci:1.3.0 credentials: username: ${{ secrets.ECMWF_DOCKER_REGISTRY_USERNAME }} password: ${{ secrets.ECMWF_DOCKER_REGISTRY_ACCESS_TOKEN }} @@ -400,11 +399,6 @@ jobs: - uses: actions/checkout@v4 - run: mkdir -p "$TMPDIR" - - name: Install Node.js - run: | - curl -fsSL "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.gz" \ - | tar xz --strip-components=1 -C /usr/local "node-v${NODE_VERSION}-linux-x64/bin/node" - - name: Build and test run: make wasm-test @@ -421,7 +415,7 @@ jobs: name: TypeScript runs-on: [self-hosted, Linux, platform-builder-docker-xl, platform-builder-Ubuntu-22.04] container: - image: eccr.ecmwf.int/tensogram/ci:1.2.0 + image: eccr.ecmwf.int/tensogram/ci:1.3.0 credentials: username: ${{ secrets.ECMWF_DOCKER_REGISTRY_USERNAME }} password: ${{ secrets.ECMWF_DOCKER_REGISTRY_ACCESS_TOKEN }} @@ -429,11 +423,6 @@ jobs: - uses: actions/checkout@v4 - run: mkdir -p "$TMPDIR" - - name: Install Node.js (full distribution — needs npm/npx) - run: | - curl -fsSL "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.xz" \ - | tar xJ --strip-components=1 -C /usr/local - - name: Install typescript deps run: make ts-install diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml new file mode 100644 index 00000000..095aec9c --- /dev/null +++ b/.github/workflows/publish-npm.yml @@ -0,0 +1,60 @@ +name: Publish TypeScript package to npm + +on: + workflow_dispatch: + +env: + CARGO_TERM_COLOR: always + TMPDIR: ${{ github.workspace }}/.tmp + +jobs: + publish: + name: Publish @ecmwf.int/tensogram + runs-on: [self-hosted, Linux, platform-builder-docker-xl, platform-builder-Ubuntu-22.04] + environment: npm + container: + image: eccr.ecmwf.int/tensogram/ci:1.3.0 + credentials: + username: ${{ secrets.ECMWF_DOCKER_REGISTRY_USERNAME }} + password: ${{ secrets.ECMWF_DOCKER_REGISTRY_ACCESS_TOKEN }} + steps: + - uses: actions/checkout@v4 + with: + submodules: true + + - run: mkdir -p "$TMPDIR" + + - name: Install typescript deps + run: make ts-install + + - name: Build WASM + TypeScript (wasm-pack → typescript/wasm, tsc → dist) + run: make ts-build + + - name: Check if version already published + id: version-check + run: | + VERSION=$(node -e "process.stdout.write(require('./typescript/package.json').version)") + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \ + "https://registry.npmjs.org/%40ecmwf.int%2Ftensogram/$VERSION") + if [ "$HTTP_STATUS" = "200" ]; then + echo "already_published=true" >> "$GITHUB_OUTPUT" + echo "@ecmwf.int/tensogram@$VERSION is already on npmjs.com — skipping" + else + echo "already_published=false" >> "$GITHUB_OUTPUT" + echo "@ecmwf.int/tensogram@$VERSION not found on npmjs.com — will publish" + fi + + - name: Publish to npm + if: steps.version-check.outputs.already_published == 'false' + working-directory: typescript + env: + NODE_AUTH_TOKEN: ${{ secrets.NPMJS_API_TOKEN }} + run: | + cp ../LICENSE ./LICENSE + echo "//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}" > .npmrc + npm publish + + - name: Cleanup + if: always() + run: rm -f typescript/.npmrc && rm -rf "$TMPDIR" typescript/node_modules typescript/dist typescript/wasm diff --git a/.github/workflows/release-preflight.yml b/.github/workflows/release-preflight.yml index a08e9dcc..26dff0cb 100644 --- a/.github/workflows/release-preflight.yml +++ b/.github/workflows/release-preflight.yml @@ -28,7 +28,7 @@ jobs: name: Validate release readiness runs-on: [self-hosted, Linux, platform-builder-docker-xl, platform-builder-Ubuntu-22.04] container: - image: eccr.ecmwf.int/tensogram/ci:1.2.0 + image: eccr.ecmwf.int/tensogram/ci:1.3.0 credentials: username: ${{ secrets.ECMWF_DOCKER_REGISTRY_USERNAME }} password: ${{ secrets.ECMWF_DOCKER_REGISTRY_ACCESS_TOKEN }} diff --git a/AGENTS.md b/AGENTS.md index b03167f9..c9e740c5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -94,7 +94,7 @@ This project contains Rust, Python, C, C++ and TypeScript code ## Tensoscope (React SPA) The interactive web viewer lives at `tensoscope/`. -It depends on the `@ecmwf/tensogram` WASM package at `typescript/`. +It depends on the `@ecmwf.int/tensogram` WASM package at `typescript/`. ### Prerequisites - Build the WASM package first: `cd typescript && make ts-build` diff --git a/CHANGELOG.md b/CHANGELOG.md index 60ddc20c..6f1dfa23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -572,7 +572,7 @@ and [PyPI](https://pypi.org/project/tensogram/). - FFI signatures gain a `threads` parameter — this is an ABI break from 0.12.0, but downstream code that used option-struct defaults will pick it up naturally. -- **TypeScript wrapper** (`@ecmwf/tensogram` under `typescript/`) +- **TypeScript wrapper** (`@ecmwf.int/tensogram` under `typescript/`) — ergonomic TypeScript bindings over the existing WebAssembly crate, shipped as a separate npm package. Includes a full Vitest suite (smoke, init, encode, decode, metadata, streaming, errors, diff --git a/docs/src/guide/error-handling.md b/docs/src/guide/error-handling.md index 06d971cc..18ebf42a 100644 --- a/docs/src/guide/error-handling.md +++ b/docs/src/guide/error-handling.md @@ -311,7 +311,7 @@ if (!msg) { ### TypeScript -Every error thrown by `@ecmwf/tensogram` is an instance of the abstract +Every error thrown by `@ecmwf.int/tensogram` is an instance of the abstract `TensogramError` base class. The concrete subclasses match the Rust variants one-to-one, plus a TS-specific `InvalidArgumentError` and `StreamingLimitError`. @@ -324,7 +324,7 @@ import { HashMismatchError, ObjectError, StreamingLimitError, -} from '@ecmwf/tensogram'; +} from '@ecmwf.int/tensogram'; try { const { metadata, objects } = decode(buf, { verifyHash: true }); @@ -361,7 +361,7 @@ iterator skips and continues. Register an `onError` callback to observe the skips: ```ts -import { decodeStream, StreamingLimitError } from '@ecmwf/tensogram'; +import { decodeStream, StreamingLimitError } from '@ecmwf.int/tensogram'; try { for await (const frame of decodeStream(res.body!, { diff --git a/docs/src/guide/nan-inf-handling.md b/docs/src/guide/nan-inf-handling.md index 1e58e378..eea65cf9 100644 --- a/docs/src/guide/nan-inf-handling.md +++ b/docs/src/guide/nan-inf-handling.md @@ -75,7 +75,7 @@ decoded = tensogram.decode(msg) ### TypeScript ```ts -import { encode, decode } from '@ecmwf/tensogram'; +import { encode, decode } from '@ecmwf.int/tensogram'; const msg = encode( { version: 2 }, diff --git a/docs/src/guide/tensoscope.md b/docs/src/guide/tensoscope.md index 4c9eb2a1..65769ed7 100644 --- a/docs/src/guide/tensoscope.md +++ b/docs/src/guide/tensoscope.md @@ -1,7 +1,7 @@ # Tensoscope Tensoscope is an interactive web viewer for `.tgm` files. It runs entirely in the -browser — no server-side component — by decoding data via the `@ecmwf/tensogram` +browser — no server-side component — by decoding data via the `@ecmwf.int/tensogram` WebAssembly package. ## Quick start diff --git a/docs/src/guide/typescript-api.md b/docs/src/guide/typescript-api.md index 2bafec49..89c01ba5 100644 --- a/docs/src/guide/typescript-api.md +++ b/docs/src/guide/typescript-api.md @@ -1,6 +1,6 @@ # TypeScript API -Tensogram ships `@ecmwf/tensogram`, a TypeScript package that wraps the +Tensogram ships `@ecmwf.int/tensogram`, a TypeScript package that wraps the WebAssembly build with typed, idiomatic helpers. Use it in any modern browser or Node ≥ 20. @@ -38,7 +38,7 @@ import { init, encode, decode, type DataObjectDescriptor, type GlobalMetadata, -} from '@ecmwf/tensogram'; +} from '@ecmwf.int/tensogram'; // One-time WASM initialisation (idempotent) await init(); @@ -192,7 +192,7 @@ import { HashMismatchError, InvalidArgumentError, StreamingLimitError, -} from '@ecmwf/tensogram'; +} from '@ecmwf.int/tensogram'; try { decode(corruptBuffer); @@ -232,7 +232,7 @@ Use `decodeStream(readable, opts?)` to progressively decode a hand-rolled `ReadableStream`. ```ts -import { decodeStream } from '@ecmwf/tensogram'; +import { decodeStream } from '@ecmwf.int/tensogram'; const res = await fetch('/data.tgm'); for await (const frame of decodeStream(res.body!)) { @@ -271,7 +271,7 @@ whether it lives on the local file system, behind an HTTPS URL, or already in memory. ```ts -import { TensogramFile } from '@ecmwf/tensogram'; +import { TensogramFile } from '@ecmwf.int/tensogram'; // Node: from the local file system const file = await TensogramFile.open('/data/input.tgm'); @@ -493,10 +493,10 @@ that want direct control. The low-level bit-conversion helpers (`halfBitsToFloat`, `floatToHalfBits`, `bfloat16BitsToFloat`, `floatToBfloat16Bits`) and the `isComplexDtype` type-guard are **internal** and are not re-exported -from `@ecmwf/tensogram`. Callers that need bit-level manipulation +from `@ecmwf.int/tensogram`. Callers that need bit-level manipulation should grab the raw storage from a view's `.bits` / `.data` accessor and do the conversion themselves, or import directly from -`@ecmwf/tensogram/float16`, `…/bfloat16`, `…/complex` with the +`@ecmwf.int/tensogram/float16`, `…/bfloat16`, `…/complex` with the understanding that these module paths are not part of the stable API. ## Examples diff --git a/docs/src/guide/vocabularies.md b/docs/src/guide/vocabularies.md index e76798c4..6a83f2b8 100644 --- a/docs/src/guide/vocabularies.md +++ b/docs/src/guide/vocabularies.md @@ -164,7 +164,7 @@ direct nested access instead. ### TypeScript — dotted path ```ts -import { getMetaKey } from '@ecmwf/tensogram'; +import { getMetaKey } from '@ecmwf.int/tensogram'; const param = getMetaKey(meta, 'mars.param'); const subject = getMetaKey(meta, 'bids.subject'); diff --git a/examples/typescript/01_encode_decode.ts b/examples/typescript/01_encode_decode.ts index 98e48584..f30f41c1 100644 --- a/examples/typescript/01_encode_decode.ts +++ b/examples/typescript/01_encode_decode.ts @@ -25,7 +25,7 @@ import { init, type DataObjectDescriptor, type GlobalMetadata, -} from '@ecmwf/tensogram'; +} from '@ecmwf.int/tensogram'; async function main(): Promise { // ── 1. Initialise WASM (one-time, idempotent) ────────────────────────── diff --git a/examples/typescript/02_mars_metadata.ts b/examples/typescript/02_mars_metadata.ts index 36fc9fd5..34cc1d46 100644 --- a/examples/typescript/02_mars_metadata.ts +++ b/examples/typescript/02_mars_metadata.ts @@ -29,7 +29,7 @@ import { init, type DataObjectDescriptor, type GlobalMetadata, -} from '@ecmwf/tensogram'; +} from '@ecmwf.int/tensogram'; async function main(): Promise { await init(); diff --git a/examples/typescript/02b_generic_metadata.ts b/examples/typescript/02b_generic_metadata.ts index 49df6dd8..31c44bca 100644 --- a/examples/typescript/02b_generic_metadata.ts +++ b/examples/typescript/02b_generic_metadata.ts @@ -27,7 +27,7 @@ import { init, type DataObjectDescriptor, type GlobalMetadata, -} from '@ecmwf/tensogram'; +} from '@ecmwf.int/tensogram'; async function main(): Promise { await init(); diff --git a/examples/typescript/03_multi_object.ts b/examples/typescript/03_multi_object.ts index 8f6c5f64..e09641e8 100644 --- a/examples/typescript/03_multi_object.ts +++ b/examples/typescript/03_multi_object.ts @@ -20,7 +20,7 @@ import { encode, init, type DataObjectDescriptor, -} from '@ecmwf/tensogram'; +} from '@ecmwf.int/tensogram'; function desc(shape: number[], dtype: DataObjectDescriptor['dtype']): DataObjectDescriptor { const strides = new Array(shape.length).fill(1); diff --git a/examples/typescript/04_decode_range.ts b/examples/typescript/04_decode_range.ts index e5abf3dc..fbe8a79e 100644 --- a/examples/typescript/04_decode_range.ts +++ b/examples/typescript/04_decode_range.ts @@ -24,7 +24,7 @@ import { encode, init, type DataObjectDescriptor, -} from '@ecmwf/tensogram'; +} from '@ecmwf.int/tensogram'; function describe(shape: number[], dtype: DataObjectDescriptor['dtype']): DataObjectDescriptor { const strides = new Array(shape.length).fill(1); diff --git a/examples/typescript/05_streaming_fetch.ts b/examples/typescript/05_streaming_fetch.ts index 53591291..cd4b8f84 100644 --- a/examples/typescript/05_streaming_fetch.ts +++ b/examples/typescript/05_streaming_fetch.ts @@ -25,7 +25,7 @@ import { init, type DataObjectDescriptor, type StreamDecodeError, -} from '@ecmwf/tensogram'; +} from '@ecmwf.int/tensogram'; function describe(shape: number[], dtype: DataObjectDescriptor['dtype']): DataObjectDescriptor { const strides = new Array(shape.length).fill(1); diff --git a/examples/typescript/06_file_api.ts b/examples/typescript/06_file_api.ts index c6e731a5..54745c93 100644 --- a/examples/typescript/06_file_api.ts +++ b/examples/typescript/06_file_api.ts @@ -26,7 +26,7 @@ import { init, TensogramFile, type DataObjectDescriptor, -} from '@ecmwf/tensogram'; +} from '@ecmwf.int/tensogram'; function describe(shape: number[], dtype: DataObjectDescriptor['dtype']): DataObjectDescriptor { const strides = new Array(shape.length).fill(1); diff --git a/examples/typescript/07_hash_and_errors.ts b/examples/typescript/07_hash_and_errors.ts index 55e27c13..98c3f967 100644 --- a/examples/typescript/07_hash_and_errors.ts +++ b/examples/typescript/07_hash_and_errors.ts @@ -32,7 +32,7 @@ import { TensogramError, decodeObject, type DataObjectDescriptor, -} from '@ecmwf/tensogram'; +} from '@ecmwf.int/tensogram'; function descFor(shape: number[]): DataObjectDescriptor { const strides = new Array(shape.length).fill(1); diff --git a/examples/typescript/08_validate.ts b/examples/typescript/08_validate.ts index fdcc5053..ac682231 100644 --- a/examples/typescript/08_validate.ts +++ b/examples/typescript/08_validate.ts @@ -27,7 +27,7 @@ import { validate, validateFile, type DataObjectDescriptor, -} from '@ecmwf/tensogram'; +} from '@ecmwf.int/tensogram'; function describe(shape: number[], dtype: DataObjectDescriptor['dtype']): DataObjectDescriptor { const strides = new Array(shape.length).fill(1); diff --git a/examples/typescript/11_encode_pre_encoded.ts b/examples/typescript/11_encode_pre_encoded.ts index 915ad7ef..59ebe112 100644 --- a/examples/typescript/11_encode_pre_encoded.ts +++ b/examples/typescript/11_encode_pre_encoded.ts @@ -25,7 +25,7 @@ import { encodePreEncoded, init, type DataObjectDescriptor, -} from '@ecmwf/tensogram'; +} from '@ecmwf.int/tensogram'; function describe(shape: number[], dtype: DataObjectDescriptor['dtype']): DataObjectDescriptor { const strides = new Array(shape.length).fill(1); diff --git a/examples/typescript/12_streaming_encoder.ts b/examples/typescript/12_streaming_encoder.ts index 24f062f8..1ba35448 100644 --- a/examples/typescript/12_streaming_encoder.ts +++ b/examples/typescript/12_streaming_encoder.ts @@ -24,7 +24,7 @@ import { init, StreamingEncoder, type DataObjectDescriptor, -} from '@ecmwf/tensogram'; +} from '@ecmwf.int/tensogram'; function describe(shape: number[], dtype: DataObjectDescriptor['dtype']): DataObjectDescriptor { const strides = new Array(shape.length).fill(1); diff --git a/examples/typescript/13_range_access.ts b/examples/typescript/13_range_access.ts index 5c91ebff..61da99fb 100644 --- a/examples/typescript/13_range_access.ts +++ b/examples/typescript/13_range_access.ts @@ -24,7 +24,7 @@ import { init, TensogramFile, type DataObjectDescriptor, -} from '@ecmwf/tensogram'; +} from '@ecmwf.int/tensogram'; function describe(shape: number[], dtype: DataObjectDescriptor['dtype']): DataObjectDescriptor { const strides = new Array(shape.length).fill(1); diff --git a/examples/typescript/14_streaming_callback.ts b/examples/typescript/14_streaming_callback.ts index 9837a608..22c1e59e 100644 --- a/examples/typescript/14_streaming_callback.ts +++ b/examples/typescript/14_streaming_callback.ts @@ -29,7 +29,7 @@ import { init, StreamingEncoder, type DataObjectDescriptor, -} from '@ecmwf/tensogram'; +} from '@ecmwf.int/tensogram'; function describe(shape: number[], dtype: DataObjectDescriptor['dtype']): DataObjectDescriptor { const strides = new Array(shape.length).fill(1); diff --git a/examples/typescript/package-lock.json b/examples/typescript/package-lock.json index 63fb96aa..9e782508 100644 --- a/examples/typescript/package-lock.json +++ b/examples/typescript/package-lock.json @@ -1,14 +1,14 @@ { "name": "tensogram-typescript-examples", - "version": "0.14.0", + "version": "0.16.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "tensogram-typescript-examples", - "version": "0.14.0", + "version": "0.16.1", "dependencies": { - "@ecmwf/tensogram": "file:../../typescript" + "@ecmwf.int/tensogram": "file:../../typescript" }, "devDependencies": { "@types/node": "^20.11.0", @@ -17,8 +17,8 @@ } }, "../../typescript": { - "name": "@ecmwf/tensogram", - "version": "0.15.0", + "name": "@ecmwf.int/tensogram", + "version": "0.16.1", "license": "Apache-2.0", "devDependencies": { "@types/node": "^20.11.0", @@ -31,7 +31,7 @@ "node": ">=20" } }, - "node_modules/@ecmwf/tensogram": { + "node_modules/@ecmwf.int/tensogram": { "resolved": "../../typescript", "link": true }, diff --git a/examples/typescript/package.json b/examples/typescript/package.json index 4ad32bb8..9d78aae3 100644 --- a/examples/typescript/package.json +++ b/examples/typescript/package.json @@ -2,7 +2,7 @@ "name": "tensogram-typescript-examples", "version": "0.16.1", "private": true, - "description": "Runnable TypeScript examples for @ecmwf/tensogram", + "description": "Runnable TypeScript examples for @ecmwf.int/tensogram", "type": "module", "scripts": { "01": "tsx 01_encode_decode.ts", @@ -15,7 +15,7 @@ "all": "npm run 01 && npm run 02 && npm run 02b && npm run 03 && npm run 05 && npm run 06 && npm run 07" }, "dependencies": { - "@ecmwf/tensogram": "file:../../typescript" + "@ecmwf.int/tensogram": "file:../../typescript" }, "devDependencies": { "@types/node": "^20.11.0", diff --git a/plans/DONE.md b/plans/DONE.md index af06e5ae..2d318476 100644 --- a/plans/DONE.md +++ b/plans/DONE.md @@ -230,7 +230,7 @@ streaming. Compiles to `wasm32-unknown-unknown` via `wasm-pack`. - **Build / test.** `wasm-pack build rust/tensogram-wasm --target web`, `wasm-pack test --node rust/tensogram-wasm`. -## TypeScript wrapper (`@ecmwf/tensogram`) +## TypeScript wrapper (`@ecmwf.int/tensogram`) Ergonomic typed layer over `tensogram-wasm` for browser + Node consumers. Design doc: `plans/TYPESCRIPT_WRAPPER.md`. User guide: @@ -238,7 +238,7 @@ Design doc: `plans/TYPESCRIPT_WRAPPER.md`. User guide: - **Package.** `typescript/` — ESM-only, strict TS, Node ≥ 20, built via `wasm-pack build --target web` + `tsc`. Package name - `@ecmwf/tensogram`. + `@ecmwf.int/tensogram`. - **WASM side-change.** `rust/tensogram-wasm/src/convert.rs::to_js` uses `Serializer::json_compatible()` so CBOR metadata surfaces as plain JS objects rather than ES `Map`. Backwards-compatible at the @@ -669,7 +669,7 @@ comparison against ecCodes. ## Tensoscope (web viewer) - React SPA (`tensoscope/`) for exploring `.tgm` files in the browser. -- Depends on `@ecmwf/tensogram` WASM package (`typescript/`) for all +- Depends on `@ecmwf.int/tensogram` WASM package (`typescript/`) for all decode — no server-side component. - **File loading.** Drag-and-drop or URL fetch; WASM scan builds an in-memory index of messages and objects without decoding payloads. diff --git a/plans/TODO.md b/plans/TODO.md index 60bfa43d..62308962 100644 --- a/plans/TODO.md +++ b/plans/TODO.md @@ -85,7 +85,7 @@ For speculative ideas, see `IDEAS.md`. Three intertwined tasks that all touch the build, pack, and publish pipeline. Best done together so we don't re-open CI config three times. - - **npm publish pipeline.** Choose an npm org (`@ecmwf/tensogram` + - **npm publish pipeline.** Choose an npm org (`@ecmwf.int/tensogram` already exists in `package.json`), wire a GitHub Actions job that publishes on tagged releases and enforces semver lock-step with the root `VERSION` file (add `typescript/package.json` to the @@ -107,15 +107,15 @@ For speculative ideas, see `IDEAS.md`. JavaScript mirror of the `tensogram-zarr` package. Lets downstream JS tooling (browser-based data explorers, notebook viewers) consume Tensogram-backed Zarr stores without re-implementing the - chunk-store contract. Ships as a separate `@ecmwf/tensogram-zarr` - npm package that depends on `@ecmwf/tensogram`. + chunk-store contract. Ships as a separate `@ecmwf.int/tensogram-zarr` + npm package that depends on `@ecmwf.int/tensogram`. - Implement the Zarr v3 chunk-store trait against a `TensogramFile` backend. - 14 bidirectional dtype mappings to match the Python `tensogram-zarr` test matrix. - Smoke-test from a notebook-style fetch scenario (`zarr-js` reading a `.tgm`-backed store over HTTPS). - - Standalone CI job; does not block the main `@ecmwf/tensogram` + - Standalone CI job; does not block the main `@ecmwf.int/tensogram` pipeline. ## Metadata diff --git a/plans/TYPESCRIPT_WRAPPER.md b/plans/TYPESCRIPT_WRAPPER.md index b886ab67..a43aead0 100644 --- a/plans/TYPESCRIPT_WRAPPER.md +++ b/plans/TYPESCRIPT_WRAPPER.md @@ -1,6 +1,6 @@ # TypeScript Wrapper -> Design and implementation plan for `@ecmwf/tensogram` — a TypeScript +> Design and implementation plan for `@ecmwf.int/tensogram` — a TypeScript > package that provides an ergonomic, typed API on top of the existing > `tensogram-wasm` bindings. > @@ -62,7 +62,7 @@ Not included (explicitly): └────────────────┬─────────────────┘ │ ┌────────────────▼─────────────────┐ - │ @ecmwf/tensogram (this pkg) │ + │ @ecmwf.int/tensogram (this pkg) │ │ │ │ typed API · dtype dispatch │ │ file helpers · stream helpers │ @@ -104,7 +104,7 @@ import { init, encode, decode, scan, decodeStream, TensogramFile, getMetaKey, computeCommon, - FramingError, HashMismatchError, /* ... */ } from '@ecmwf/tensogram'; + FramingError, HashMismatchError, /* ... */ } from '@ecmwf.int/tensogram'; await init(); // idempotent, loads + instantiates the WASM blob @@ -137,7 +137,7 @@ const { metadata, objects } = await file.message(0); // Range request ``` typescript/ ← new top-level dir -├── package.json ← "@ecmwf/tensogram", ESM-only +├── package.json ← "@ecmwf.int/tensogram", ESM-only ├── tsconfig.json ← strict, ES2022, node+dom libs ├── vitest.config.ts ├── .gitignore ← dist/, wasm/, node_modules/ @@ -447,7 +447,7 @@ issue the `Range` GET on first access; existing callers add `await`. - **Scope C.3** — npm publish pipeline, browser CI via Vitest-browser + Playwright, `size-limit` bundle budget, vitest bench on hot paths. See `plans/TODO.md`. -- **Scope C.4** — `@ecmwf/tensogram-zarr` mirror of the Python +- **Scope C.4** — `@ecmwf.int/tensogram-zarr` mirror of the Python `tensogram-zarr` package. - Dual `--target web` + `--target nodejs` wasm-pack build for tighter Node compatibility (currently Node ≥ 20 required). diff --git a/rust/tensogram-wasm/src/convert.rs b/rust/tensogram-wasm/src/convert.rs index af0a9e6a..d307cfb2 100644 --- a/rust/tensogram-wasm/src/convert.rs +++ b/rust/tensogram-wasm/src/convert.rs @@ -121,7 +121,7 @@ pub(crate) fn extract_descriptor_data_pairs( /// /// Rust `BTreeMap` / `HashMap` values serialise as plain JS objects /// (not ES `Map`), which is the natural shape TypeScript consumers -/// expect and what the high-level `@ecmwf/tensogram` wrapper relies +/// expect and what the high-level `@ecmwf.int/tensogram` wrapper relies /// on. Safe-range `u64` values come across as `number`; values that /// exceed `Number.MAX_SAFE_INTEGER` come across as `BigInt`. /// diff --git a/tensoscope/Dockerfile b/tensoscope/Dockerfile index 5f15298d..28fb05da 100644 --- a/tensoscope/Dockerfile +++ b/tensoscope/Dockerfile @@ -9,8 +9,8 @@ FROM node:22-alpine AS builder WORKDIR /workspace -# Copy only the pre-built @ecmwf/tensogram artifacts (not the whole tree — -# tensoscope/node_modules/@ecmwf/tensogram is a symlink back to typescript/ which +# Copy only the pre-built @ecmwf.int/tensogram artifacts (not the whole tree — +# tensoscope/node_modules/@ecmwf.int/tensogram is a symlink back to typescript/ which # causes a cycle if we copy the directory recursively). COPY typescript/package.json typescript/package.json COPY typescript/dist/ typescript/dist/ @@ -26,11 +26,11 @@ RUN npm ci COPY tensoscope/ . # Replace the host symlink with the real pre-built package -RUN rm -rf node_modules/@ecmwf/tensogram && \ - mkdir -p node_modules/@ecmwf/tensogram && \ - cp /workspace/typescript/package.json node_modules/@ecmwf/tensogram/ && \ - cp -r /workspace/typescript/dist node_modules/@ecmwf/tensogram/ && \ - cp -r /workspace/typescript/wasm node_modules/@ecmwf/tensogram/ +RUN rm -rf node_modules/@ecmwf.int/tensogram && \ + mkdir -p node_modules/@ecmwf.int/tensogram && \ + cp /workspace/typescript/package.json node_modules/@ecmwf.int/tensogram/ && \ + cp -r /workspace/typescript/dist node_modules/@ecmwf.int/tensogram/ && \ + cp -r /workspace/typescript/wasm node_modules/@ecmwf.int/tensogram/ RUN npm run build diff --git a/tensoscope/README.md b/tensoscope/README.md index 6716f5f9..19903339 100644 --- a/tensoscope/README.md +++ b/tensoscope/README.md @@ -2,7 +2,7 @@ Interactive web viewer for [Tensogram](https://github.com/ecmwf/tensogram) `.tgm` files. -Decodes data directly in the browser via the `@ecmwf/tensogram` WebAssembly package and +Decodes data directly in the browser via the `@ecmwf.int/tensogram` WebAssembly package and renders geospatial fields on an interactive map using deck.gl + MapLibre GL JS. ## Features @@ -52,7 +52,7 @@ Set `BASE_PATH=/subpath` to deploy under a subpath. ## Architecture Tensoscope is a thin UI layer. All heavy lifting is done by the -`@ecmwf/tensogram` WASM package (`typescript/`): +`@ecmwf.int/tensogram` WASM package (`typescript/`): - `src/tensogram/index.ts` — wraps the WASM API into a `TensoscopeViewer` class - `src/store/useAppStore.ts` — Zustand state for selected file, field, and step diff --git a/tensoscope/package-lock.json b/tensoscope/package-lock.json index ec2f3d11..2d48a391 100644 --- a/tensoscope/package-lock.json +++ b/tensoscope/package-lock.json @@ -12,7 +12,7 @@ "@deck.gl/geo-layers": "^9.3.0", "@deck.gl/layers": "^9.3.0", "@deck.gl/react": "^9.3.0", - "@ecmwf/tensogram": "file:../typescript", + "@ecmwf.int/tensogram": "file:../typescript", "@types/d3-scale-chromatic": "^3.1.0", "@types/maplibre-gl": "^1.13.2", "cesium": "^1.140.0", @@ -43,7 +43,7 @@ } }, "../typescript": { - "name": "@ecmwf/tensogram", + "name": "@ecmwf.int/tensogram", "version": "0.16.1", "license": "Apache-2.0", "devDependencies": { @@ -730,7 +730,7 @@ "@luma.gl/core": "~9.3.2" } }, - "node_modules/@ecmwf/tensogram": { + "node_modules/@ecmwf.int/tensogram": { "resolved": "../typescript", "link": true }, @@ -2009,9 +2009,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2028,9 +2025,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2047,9 +2041,6 @@ "cpu": [ "ppc64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2066,9 +2057,6 @@ "cpu": [ "s390x" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2085,9 +2073,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2104,9 +2089,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -6333,9 +6315,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -6356,9 +6335,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -6379,9 +6355,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -6402,9 +6375,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ diff --git a/tensoscope/package.json b/tensoscope/package.json index 66bfee16..c7ff21e7 100644 --- a/tensoscope/package.json +++ b/tensoscope/package.json @@ -14,7 +14,7 @@ "@deck.gl/geo-layers": "^9.3.0", "@deck.gl/layers": "^9.3.0", "@deck.gl/react": "^9.3.0", - "@ecmwf/tensogram": "file:../typescript", + "@ecmwf.int/tensogram": "file:../typescript", "@types/d3-scale-chromatic": "^3.1.0", "@types/maplibre-gl": "^1.13.2", "cesium": "^1.140.0", diff --git a/tensoscope/src/api/types.ts b/tensoscope/src/api/types.ts index 4e2b675b..30b4b102 100644 --- a/tensoscope/src/api/types.ts +++ b/tensoscope/src/api/types.ts @@ -2,7 +2,7 @@ * Shared UI types. * * Data types (ObjectInfo, FileIndex, etc.) are now provided by - * src/tensogram/index.ts via the @ecmwf/tensogram WASM bindings. + * src/tensogram/index.ts via the @ecmwf.int/tensogram WASM bindings. * Only UI-specific types remain here. */ diff --git a/tensoscope/src/tensogram/index.ts b/tensoscope/src/tensogram/index.ts index e46ddf48..dc3d5e91 100644 --- a/tensoscope/src/tensogram/index.ts +++ b/tensoscope/src/tensogram/index.ts @@ -1,7 +1,7 @@ /** * Tensogram wrapper for the viewer. * - * Uses @ecmwf/tensogram (WASM) directly in the browser -- no backend needed. + * Uses @ecmwf.int/tensogram (WASM) directly in the browser -- no backend needed. * Provides file opening, metadata indexing, field decoding, and coordinate * extraction. */ @@ -10,11 +10,11 @@ import { init as tgInit, TensogramFile, decodeObject, -} from '@ecmwf/tensogram'; +} from '@ecmwf.int/tensogram'; import type { BaseEntry, CborValue, -} from '@ecmwf/tensogram'; +} from '@ecmwf.int/tensogram'; // ── Public types ──────────────────────────────────────────────────────── diff --git a/tensoscope/vite.config.ts b/tensoscope/vite.config.ts index 1c062401..072f90ab 100644 --- a/tensoscope/vite.config.ts +++ b/tensoscope/vite.config.ts @@ -5,7 +5,7 @@ import cesium from 'vite-plugin-cesium' import path from 'path' const tensogramPkg = path.resolve( - __dirname, 'node_modules/@ecmwf/tensogram/dist/index.js', + __dirname, 'node_modules/@ecmwf.int/tensogram/dist/index.js', ) /** Vite plugin: proxy /api/proxy?url=... to bypass CORS for remote .tgm files. @@ -71,12 +71,12 @@ export default defineConfig({ plugins: [react(), wasm(), cesium(), corsProxy()], resolve: { alias: { - '@ecmwf/tensogram': tensogramPkg, + '@ecmwf.int/tensogram': tensogramPkg, }, preserveSymlinks: true, }, optimizeDeps: { - exclude: ['@ecmwf/tensogram'], + exclude: ['@ecmwf.int/tensogram'], }, server: { fs: { diff --git a/typescript/README.md b/typescript/README.md index 12774c10..265f8cf7 100644 --- a/typescript/README.md +++ b/typescript/README.md @@ -1,4 +1,4 @@ -# @ecmwf/tensogram +# @ecmwf.int/tensogram TypeScript bindings for [Tensogram](https://github.com/ecmwf/tensogram), a binary message format for N-dimensional scientific tensors. Tensogram is @@ -29,7 +29,7 @@ Requirements: ## Quick start ```ts -import { init, encode, decode } from '@ecmwf/tensogram'; +import { init, encode, decode } from '@ecmwf.int/tensogram'; await init(); diff --git a/typescript/package-lock.json b/typescript/package-lock.json index eb437332..a4a417e9 100644 --- a/typescript/package-lock.json +++ b/typescript/package-lock.json @@ -1,12 +1,12 @@ { - "name": "@ecmwf/tensogram", - "version": "0.15.0", + "name": "@ecmwf.int/tensogram", + "version": "0.16.1", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@ecmwf/tensogram", - "version": "0.15.0", + "name": "@ecmwf.int/tensogram", + "version": "0.16.1", "license": "Apache-2.0", "devDependencies": { "@types/node": "^20.11.0", @@ -651,9 +651,6 @@ "arm" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -668,9 +665,6 @@ "arm" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -685,9 +679,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -702,9 +693,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -719,9 +707,6 @@ "loong64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -736,9 +721,6 @@ "loong64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -753,9 +735,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -770,9 +749,6 @@ "ppc64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -787,9 +763,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -804,9 +777,6 @@ "riscv64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -821,9 +791,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -838,9 +805,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -855,9 +819,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ diff --git a/typescript/package.json b/typescript/package.json index d82f3318..5b1e643d 100644 --- a/typescript/package.json +++ b/typescript/package.json @@ -1,5 +1,5 @@ { - "name": "@ecmwf/tensogram", + "name": "@ecmwf.int/tensogram", "version": "0.16.1", "description": "TypeScript bindings for the Tensogram N-tensor message format", "keywords": [ @@ -54,5 +54,8 @@ }, "engines": { "node": ">=20" + }, + "publishConfig": { + "access": "public" } } diff --git a/typescript/src/index.ts b/typescript/src/index.ts index 16010614..a1ed7d22 100644 --- a/typescript/src/index.ts +++ b/typescript/src/index.ts @@ -7,7 +7,7 @@ // does it submit to any jurisdiction. /** - * `@ecmwf/tensogram` — TypeScript bindings for Tensogram, ECMWF's binary + * `@ecmwf.int/tensogram` — TypeScript bindings for Tensogram, ECMWF's binary * message format for N-dimensional scientific tensors. * * Wraps `rust/tensogram-wasm` (built via `wasm-pack`) with a typed API: diff --git a/typescript/src/types.ts b/typescript/src/types.ts index 9c5ca2b7..28464656 100644 --- a/typescript/src/types.ts +++ b/typescript/src/types.ts @@ -7,7 +7,7 @@ // does it submit to any jurisdiction. /** - * Public type definitions for `@ecmwf/tensogram`. + * Public type definitions for `@ecmwf.int/tensogram`. * * These mirror the Rust types in `tensogram::types` and the wire * format defined in `plans/WIRE_FORMAT.md`. They are hand-written rather