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
17 changes: 12 additions & 5 deletions .github/ci/Dockerfile
Original file line number Diff line number Diff line change
@@ -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/
# ────────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -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) ──────────────────────
Expand All @@ -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
2 changes: 1 addition & 1 deletion .github/ci/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.2.0
1.3.0
29 changes: 9 additions & 20 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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 }}
Expand All @@ -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 }}
Expand All @@ -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 }}
Expand Down Expand Up @@ -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 }}
Expand Down Expand Up @@ -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 }}
Expand Down Expand Up @@ -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 }}
Expand Down Expand Up @@ -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 }}
Expand Down Expand Up @@ -392,19 +391,14 @@ 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 }}
steps:
- 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

Expand All @@ -421,19 +415,14 @@ 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 }}
steps:
- 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

Expand Down
60 changes: 60 additions & 0 deletions .github/workflows/publish-npm.yml
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion .github/workflows/release-preflight.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
6 changes: 3 additions & 3 deletions docs/src/guide/error-handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand All @@ -324,7 +324,7 @@ import {
HashMismatchError,
ObjectError,
StreamingLimitError,
} from '@ecmwf/tensogram';
} from '@ecmwf.int/tensogram';

try {
const { metadata, objects } = decode(buf, { verifyHash: true });
Expand Down Expand Up @@ -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!, {
Expand Down
2 changes: 1 addition & 1 deletion docs/src/guide/nan-inf-handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down
2 changes: 1 addition & 1 deletion docs/src/guide/tensoscope.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
14 changes: 7 additions & 7 deletions docs/src/guide/typescript-api.md
Original file line number Diff line number Diff line change
@@ -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.

Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -192,7 +192,7 @@ import {
HashMismatchError,
InvalidArgumentError,
StreamingLimitError,
} from '@ecmwf/tensogram';
} from '@ecmwf.int/tensogram';

try {
decode(corruptBuffer);
Expand Down Expand Up @@ -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!)) {
Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion docs/src/guide/vocabularies.md
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
2 changes: 1 addition & 1 deletion examples/typescript/01_encode_decode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
init,
type DataObjectDescriptor,
type GlobalMetadata,
} from '@ecmwf/tensogram';
} from '@ecmwf.int/tensogram';

async function main(): Promise<void> {
// ── 1. Initialise WASM (one-time, idempotent) ──────────────────────────
Expand Down
2 changes: 1 addition & 1 deletion examples/typescript/02_mars_metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import {
init,
type DataObjectDescriptor,
type GlobalMetadata,
} from '@ecmwf/tensogram';
} from '@ecmwf.int/tensogram';

async function main(): Promise<void> {
await init();
Expand Down
2 changes: 1 addition & 1 deletion examples/typescript/02b_generic_metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
init,
type DataObjectDescriptor,
type GlobalMetadata,
} from '@ecmwf/tensogram';
} from '@ecmwf.int/tensogram';

async function main(): Promise<void> {
await init();
Expand Down
2 changes: 1 addition & 1 deletion examples/typescript/03_multi_object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<number>(shape.length).fill(1);
Expand Down
Loading
Loading