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
12 changes: 12 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
root = true

[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true

[*.md]
trim_trailing_whitespace = false
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* @rzcoder
26 changes: 26 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
version: 2
updates:
- package-ecosystem: npm
directory: /
schedule:
interval: weekly
day: monday
open-pull-requests-limit: 5
groups:
dev-dependencies:
dependency-type: development
update-types: [minor, patch]
prod-dependencies:
dependency-type: production
update-types: [minor, patch]
ignore:
- dependency-name: '*'
update-types: [version-update:semver-major]

- package-ecosystem: github-actions
directory: /
schedule:
interval: monthly
groups:
actions:
patterns: ['*']
18 changes: 18 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
## Summary

<!-- Brief description of the change. -->

## Changes

<!-- Bulleted list of notable changes. -->

## Testing

- [ ] `npm test` passes
- [ ] `npm run lint` passes
- [ ] `npm run typecheck` passes
- [ ] (If perf-sensitive) `npm run bench` results attached

## Notes

<!-- Wire-format compatibility, breaking changes, migration notes, etc. -->
40 changes: 40 additions & 0 deletions .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Benchmark

on:
workflow_dispatch:
inputs:
ref:
description: 'Git ref to benchmark'
required: false
default: 'main'

permissions:
contents: read

jobs:
bench:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run build
- run: npm run bench | tee bench-results.txt
- uses: actions/upload-artifact@v4
with:
name: bench-results-${{ inputs.ref }}
path: bench-results.txt
retention-days: 30
- name: Append to job summary
run: |
{
echo '### Benchmark results (`${{ inputs.ref }}`)'
echo '```'
cat bench-results.txt
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
101 changes: 101 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
name: CI

on:
push:
branches: [main, master]
pull_request:
branches: [main, master]

concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

permissions:
contents: read

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run format:check
- run: npm run lint

audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
# Fail only on high/critical advisories in production deps.
# Dev-only moderate advisories (vitest/vite/esbuild dev-server chain) are tolerated.
- run: npm audit --audit-level=high --omit=dev

typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run typecheck

test:
name: test (Node ${{ matrix.node }} / ${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
node: [20, 22, 24]
include:
- os: windows-latest
node: 22
- os: macos-latest
node: 22
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
cache: npm
- run: npm ci
- run: npm run test:coverage
- name: Upload coverage
if: matrix.os == 'ubuntu-latest' && matrix.node == 22
uses: codecov/codecov-action@v4
with:
files: ./coverage/lcov.info
fail_ci_if_error: false

build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run build
- name: Verify dual entrypoints exist
run: |
test -f dist/index.mjs
test -f dist/index.cjs
test -f dist/index.d.ts
test -f dist/index.d.cts
- uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
retention-days: 7
45 changes: 45 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: Release

on:
push:
tags:
- 'v*'

permissions:
contents: write
id-token: write

jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
registry-url: 'https://registry.npmjs.org'

- run: npm install -g npm@latest
- run: npm ci
- run: npm run lint
- run: npm run typecheck
- run: npm test
- run: npm run build

- name: Verify tag matches package.json version
run: |
PKG_VERSION="v$(node -p "require('./package.json').version")"
TAG="${GITHUB_REF#refs/tags/}"
if [ "$PKG_VERSION" != "$TAG" ]; then
echo "Tag $TAG does not match package.json $PKG_VERSION"
exit 1
fi

- name: Publish to npm
run: npm publish --access public

- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: true
12 changes: 10 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
node_modules/
dist/
coverage/
*.tsbuildinfo
.vitest-cache/
.DS_Store
.idea
node_modules/
.idea/
.vscode/
*.log
.env
.env.local
2 changes: 2 additions & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
engine-strict=true
save-exact=false
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
22
57 changes: 57 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Changelog

All notable changes to this project are documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Performance

- Field-iteration path strings (`${path}.${key}`, `${path}[${i}]`) are no longer allocated on the happy path. `struct` / `array` codecs now wrap each child call in `try { ... } catch (e) { rethrowWithPrefix(e, segment) }` and build the error path only when something throws. Measured on Node 22:
- `nested` encode (compiled): -33%
- `list of list` encode (compiled): -59%
- `list of list` decode (compiled): -45%
- `hero` encode / decode (compiled): -17% each

### Changed

- Build: `tsup` now emits minified output. `dist/index.mjs` shrinks from ~15 KB to ~7.5 KB (~-50%); behaviour is unchanged.

### Security

- Decoded structs are now created with a `null` prototype, neutralising prototype-pollution risk if a schema is built from untrusted input that contains a `__proto__` key.
- String decoder now uses `TextDecoder` in `fatal: true` mode and throws `SCHEMA_MISMATCH` on malformed UTF-8 (previously such bytes were silently replaced with `U+FFFD`).
- `struct.encode` now asserts that `write()` emits exactly as many bytes as `measure()` reported. Defends against codec bugs leaking uninitialised memory from `Buffer.allocUnsafe`.
- CI now runs `npm audit --audit-level=high --omit=dev` and fails on high/critical advisories in production dependencies.

## [0.1.0] - 2026-05-16

### Added
- Full TypeScript rewrite.
- New codec-token API: `t.bool`, `t.i8/u8/i16/u16/i32/u32`, `t.f32/f64`, `t.i64/u64`, `t.string`, `t.shortBytes`, `t.bytes`.
- Little-endian variants under `t.le.*`.
- `struct(schema)` factory that compiles a schema once and reuses it across `encode`/`decode`/`sizeOf` calls.
- Functional `encode(value, schema)` and `decode(buf, schema)`.
- `Infer<S>` type helper that derives the TypeScript shape of decoded values from a schema.
- `DataStructError` with codes (`VALUE_OUT_OF_RANGE`, `STRING_TOO_LONG`, `BYTES_TOO_LONG`, `ARRAY_TOO_LONG`, `BUFFER_UNDERFLOW`, `SCHEMA_MISMATCH`, `INVALID_SCHEMA`), `path` (e.g. `$.skills[1].description`), and `offset` (for decode errors).
- Dual ESM + CommonJS build with separate `.d.mts` / `.d.cts` type declarations.
- Vitest test suite (wire-format goldens, roundtrip, error paths) with v8 coverage.
- `tinybench`-based benchmark suite.
- GitHub Actions: CI matrix (Node 20/22/24 on Linux + Node 22 on macOS/Windows), on-demand benchmark workflow, tag-triggered release with npm provenance.
- Dependabot, PR template, CODEOWNERS.

### Changed
- Module format: package is now `"type": "module"`; entry points are `dist/index.mjs` (ESM) and `dist/index.cjs` (CJS).
- Minimum Node.js version: `>=20.9`.
- Encoder now allocates the output `Buffer` exactly once via a two-pass measure/write strategy (no `Buffer.concat`).
- Decoded `shortBytes` / `bytes` are returned as independent copies of the source memory rather than aliased views.
- Replaced deprecated `new Buffer(...)` calls with `Buffer.allocUnsafe` (for output) and `TextEncoder`/`TextDecoder` for string codecs.

### Removed
- Legacy exports `DataTypes`, `DataReader`, `DataWriter` (clean break — see README migration table).
- `grunt`, `grunt-simple-mocha`, `jit-grunt`, `grunt-contrib-jshint`, `chai`, `benchmark`.

### Wire format
- **Byte-identical** with `0.0.x` for the corresponding new codecs. The previous test suite's golden buffers are preserved as goldens in `test/wire-format.test.ts`.
Loading
Loading