Skip to content

Commit 3b9eb95

Browse files
kalwaltclaude
andcommitted
feat(api)!: singleton modules, shared cache, direct namespace export (#41)
BREAKING CHANGE: restores original jsfeat's calling convention (ships as 0.9.0). Designed via /brainstorming with every decision user-confirmed; full migration guide in docs/migration-0.9.md. What changed: - The 14 algorithm modules (imgproc, math, matmath, linalg, transform, fast_corners, yape, yape06, orb, optical_flow_lk, motion_estimator, affine2d, homography2d, cache) are now SINGLETON INSTANCES on the namespace: jsfeatNext.imgproc.grayscale(...) -- no `new`. The classes themselves are untouched (kept verbatim so the parity suite still guards unmodified algorithm code); the aggregator instantiates them once at load. - ONE shared buffer pool: src/core/core.ts now creates a module-level shared_cache that every module binds to, replacing the per-instance 30-buffer/76.8KB pools. jsfeatNext.cache is that pool instance (get_buffer/put_buffer), matching original jsfeat's global jsfeat.cache -- which was never a constructor. - The jsfeatNext.jsfeatNext double namespace is gone: src/index.ts default-exports the namespace directly (UMD global and ESM default are now the namespace itself). - Data-structure constructors unchanged: matrix_t, keypoint_t, pyramid_t, ransac_params_t are still `new`ed, as in original jsfeat. Ground truth for the design (verified against inspirit/jsfeat source, not assumed): jsfeat's own modules are singletons even when stateful (fast_corners keeps closure-level _threshold and is configured once via set_threshold(20) at load); imgproc borrows from the single global jsfeat.cache -- per-instance pools defeated the cache's purpose and made new-in-a-frame-loop silently reallocate pools every frame. Also in this change: - tests: parity suite converted to the new convention (63 tests total, including a new tests/api-shape.test.ts pinning the namespace shape, singleton identity, shared-cache identity and constructor surface) - examples: all 22 converted and validated against the new bundle (the 5 non-camera ones executed in Node, all 22 static-scanned, and browser-tested by the user); browser.html's cache usage rewritten (it used new jsfeat.cache()+allocate(), which original jsfeat never supported either); grayscale example simplified to direct jsfeatNext.imgproc.grayscale() calls - docs: docs/migration-0.9.md (full old->new mapping + motivation), README quick start, AGENTS.md architecture/gotchas (also refreshed for the post-#47 layout), CLAUDE.md notes (were stale re: monolith and 'no tests'), copilot-instructions, audit doc Axis 2 marked resolved with a status column on the severity table - dist/ + types/ rebuilt and committed IN THIS PR (deviation from the source-only convention, at user request: an API-breaking change with a stale committed bundle made local example testing confusing) Verified: tsc --noEmit clean; npm test 63/63; prettier clean; UMD and ESM bundle shapes checked (no double namespace, singletons callable, shared pool public); examples validated against the new bundle and manually tested in a browser by the user. Closes #41. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 5159e61 commit 3b9eb95

45 files changed

Lines changed: 633 additions & 438 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/copilot-instructions.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
The full guidance is in **[`AGENTS.md`](../AGENTS.md)** (source of truth) and the roadmap in [`docs/jsfeat-parity-and-refactor-audit.md`](../docs/jsfeat-parity-and-refactor-audit.md). Critical points, inlined because Copilot injects this file directly:
44

55
- **TypeScript port of [jsfeat](https://github.com/inspirit/jsfeat)** for WebARKit. npm: `@webarkit/jsfeat-next`. Node v24.18.0 (npm 11).
6-
- Build: `npm run build-ts``dist/jsfeatNext.js` (UMD) + `types/`. Watch: `npm run dev-ts`. Format: `npm run format` (Prettier). **Tests:** `npm test` (Vitest, parity vs original jsfeat); also verify via `examples/*.html`.
7-
- **Architecture:** most algorithms live **inline in `src/jsfeatNext.ts` (~3,900 lines)**, attached as `jsfeatNext.X = class X extends jsfeatNext`. Edit *there* for `imgproc, fast_corners, math, linalg, orb, yape06, motion_estimator, optical_flow_lk, pyramid_t`.
8-
- **⚠️ Trap:** several `src/<module>/<module>.ts` files are **type-only stubs** that `throw new Error("Method not implemented.")` (e.g. `src/imgproc/imgproc.ts`). Do not treat them as the implementation and never instantiate them.
9-
- **API notes:** consumers use `jsfeatNext.jsfeatNext` (double namespace); algorithm modules require `new` (instance methods), unlike jsfeat's static namespace; each `new` allocates its own cache. `haar` and `bbf` are not ported.
10-
- **Conventions:** keep numeric/behavioral parity with jsfeat (typed-array/bitwise hot loops); preserve the public `jsfeatNext.<module>` API; avoid `any` in new code; LGPL-3.0-or-later.
11-
- **Git workflow:** open PRs against **`dev`**, never `main` (`dev` = integration, `main` = release). Commit messages follow **Conventional Commits** (`feat:`, `fix:`, `chore:`, `docs:`, `test:`, `refactor:`, `ci:` …).
6+
- Build: `npm run build-ts` (Vite) `dist/jsfeatNext.js` (UMD) + `dist/jsfeatNext.mjs` (ESM) + `types/`. Watch: `npm run dev-ts`. Format: `npm run format` (Prettier). **Tests:** `npm test` (Vitest, parity vs original jsfeat); also verify via `examples/*.html`.
7+
- **Architecture:** one real module per algorithm under `src/<module>/`, extending the base in `src/core/core.ts`; `src/jsfeatNext.ts` is a thin aggregator.
8+
- **Calling convention (0.9.0+, #41):** algorithm modules are **singletons** `jsfeatNext.imgproc.grayscale(...)`, **no `new`**. Only the data structures (`matrix_t`, `keypoint_t`, `pyramid_t`, `ransac_params_t`) are constructors. All modules share ONE buffer pool (`jsfeatNext.cache`); balance `get_buffer`/`put_buffer`. See `docs/migration-0.9.md`.
9+
- **Missing vs jsfeat:** `haar` and `bbf` are not ported (#43/#44).
10+
- **Conventions:** keep numeric/behavioral parity with jsfeat (typed-array/bitwise hot loops; the Vitest suite pins outputs against a vendored jsfeat oracle); preserve the public `jsfeatNext.<module>` API; avoid `any` in new code; LGPL-3.0-or-later.
11+
- **Git workflow:** open PRs against **`dev`**, never `main` (`dev` = integration, `main` = release). Commit messages follow **Conventional Commits** (`feat:`, `fix:`, `chore:`, `docs:`, `test:`, `refactor:`, `ci:` …) — the release changelog is generated from them. Release tags are bare `X.Y.Z`.

AGENTS.md

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,34 +5,31 @@
55
66
## What this project is
77

8-
**jsfeatNext** is a TypeScript port of [jsfeat](https://github.com/inspirit/jsfeat) (a JS computer-vision library) for the **WebARKit** project. Published to npm as `@webarkit/jsfeat-next`. It ships a UMD bundle for browsers and TypeScript type declarations.
8+
**jsfeatNext** is a TypeScript port of [jsfeat](https://github.com/inspirit/jsfeat) (a JS computer-vision library) for the **WebARKit** project. Published to npm as `@webarkit/jsfeat-next`. It ships UMD + ESM bundles and TypeScript type declarations.
99

1010
## Environment & commands
1111

1212
- **Node:** v24.18.0 (see `.nvmrc`; npm 11). **Package manager:** npm.
13-
- Install: `npm install`
14-
- Build (prod): `npm run build-ts`runs `tsc` (emits `.d.ts` to `types/`) then webpack → `dist/jsfeatNext.js`
13+
- Install: `npm install` (regenerate the lockfile only with npm 11, e.g. `npx npm@11 install` — older npm writes incomplete cross-platform lockfiles that break CI's `npm ci`)
14+
- Build: `npm run build-ts`Vite library mode → `dist/jsfeatNext.js` (UMD) + `dist/jsfeatNext.mjs` (ESM) + `types/` (via vite-plugin-dts)
1515
- Watch/dev: `npm run dev-ts`
16-
- Format: `npm run format` (write) · `npm run format-check` (verify) — Prettier, config in `.prettierrc.json`
17-
- **Test:** `npm test` (Vitest) runs characterization tests asserting parity against the original `jsfeat` (see `tests/`). Also verify visually via `examples/*.html` after building. Do **not** claim behavior is verified without a real check.
16+
- Format: `npm run format` (write) · `npm run format-check` (verify) — Prettier, config in `.prettierrc.json`. On Windows, verify with `node_modules/.bin/prettier` directly, not bare `npx prettier` (which can silently resolve a different version).
17+
- API docs: `npm run docs` (TypeDoc → `docs/api/`, gitignored)
18+
- **Test:** `npm test` (Vitest) runs characterization tests asserting parity against the original `jsfeat` (see `tests/`, oracle vendored in `tests/vendor/`). Also verify visually via `examples/*.html` after building. Do **not** claim behavior is verified without a real check.
1819

1920
## Architecture — read this before editing
2021

21-
The runtime is centered on one large file: **`src/jsfeatNext.ts` (~3,900 lines)**.
22+
- **One real module per algorithm** under `src/<module>/<module>.ts`, each extending the base class in **`src/core/core.ts`** (constants, data-type helpers, the shared cache). `src/jsfeatNext.ts` is a thin aggregator that only attaches modules to the namespace; `src/index.ts` default-exports the namespace directly.
23+
- **Calling convention (since 0.9.0, issue #41):** the 14 algorithm modules (`imgproc`, `math`, `matmath`, `linalg`, `transform`, `fast_corners`, `yape`, `yape06`, `orb`, `optical_flow_lk`, `motion_estimator`, `affine2d`, `homography2d`, plus the `cache` pool) are **singleton instances** on the namespace — `jsfeatNext.imgproc.grayscale(...)`, no `new` — matching original jsfeat. The data-structure classes (`matrix_t`, `keypoint_t`, `pyramid_t`, `ransac_params_t`) remain constructors.
24+
- **One shared buffer pool:** all modules borrow scratch buffers from the single `shared_cache` exported by `src/core/core.ts` (public as `jsfeatNext.cache`), exactly like jsfeat's global `jsfeat.cache`. Balance every `get_buffer` with a `put_buffer`.
25+
- Full background: `docs/jsfeat-parity-and-refactor-audit.md` (the plan) and `docs/migration-0.9.md` (the 0.9.0 API break and its motivation).
2226

23-
- `src/index.ts` default-exports `{ jsfeatNext }`. The UMD global is `jsfeatNext`, so **consumers write `jsfeatNext.jsfeatNext`** (double namespace — known wart).
24-
- The base `class jsfeatNext` holds `cache`, `data_type`, and all constants. Algorithm modules are attached as **static members**.
25-
- **Two conflicting patterns coexist:**
26-
- **Implemented INLINE in `src/jsfeatNext.ts`** (attached via `jsfeatNext.X = class X extends jsfeatNext {…}`): `imgproc, fast_corners, pyramid_t, math, linalg, orb, yape06, motion_estimator, optical_flow_lk` (plus `motion_model, affine2d, homography2d`).
27-
- **Assigned from a REAL module file**: `cache, transform, matrix_t, keypoint_t, matmath, yape, ransac_params_t`.
27+
### ⚠️ Gotchas
2828

29-
### ⚠️ Critical gotchas (these will bite you)
30-
31-
1. **Type-only stub files.** Several `src/<module>/<module>.ts` files (confirmed: `src/imgproc/imgproc.ts`) are **stubs** whose every method is `throw new Error("Method not implemented.")`. They exist only to type `static X: typeof X`. **The real code is inline in `src/jsfeatNext.ts`.** → When editing an inline-implemented algorithm, **edit `src/jsfeatNext.ts`**, not the stub. Never instantiate a stub class.
32-
2. **Latent trap:** `src/orb/rectify_patch.ts` imports the *stub* `imgproc`.
33-
3. **Instantiation required.** Unlike jsfeat's static namespace (`jsfeat.imgproc.grayscale()`), jsfeatNext modules are instance classes: `new jsfeatNext.jsfeatNext.imgproc().grayscale(...)`. Not drop-in compatible with jsfeat.
34-
4. **Per-instance cache.** Every `new` of a module runs the base ctor `this.cache.allocate(30, 640*4)` — each instance gets its own buffer pool (jsfeat shares one global cache).
35-
5. **Missing vs jsfeat:** `haar` and `bbf` (object/face detection) are **not ported**.
29+
1. **Don't reintroduce `new jsfeatNext.<algorithm>()`** in examples, docs or tests — the slots hold instances, not classes. The classes still exist in their module files (importable for isolated instances if truly needed) and bind to the shared pool.
30+
2. **Missing vs jsfeat:** `haar` and `bbf` (object/face detection) are **not ported** (#43/#44).
31+
3. **`transform` signature divergence:** jsfeatNext's `transform` methods take `matrix_t`; original jsfeat's (never actually shipped in any jsfeat build) took raw arrays. Same math.
32+
4. **The parity suite is the safety net.** Any change to algorithm code must keep `npm test` green — the tests pin outputs bit-for-bit/close-to against a vendored original-jsfeat oracle.
3633

3734
## Conventions
3835

@@ -44,17 +41,18 @@ The runtime is centered on one large file: **`src/jsfeatNext.ts` (~3,900 lines)*
4441
## Git & contribution workflow
4542

4643
- **Open PRs against the `dev` branch — never `main`.** `dev` is the integration branch; `main` is stable/release. Branch your work off `dev`.
47-
- **Commit messages follow [Conventional Commits](https://www.conventionalcommits.org/):** `type(scope): summary`, e.g. `feat:`, `fix:`, `chore:`, `docs:`, `test:`, `refactor:`, `perf:`, `ci:`. Keep the subject imperative and concise.
44+
- **Commit messages follow [Conventional Commits](https://www.conventionalcommits.org/):** `type(scope): summary`, e.g. `feat:`, `fix:`, `chore:`, `docs:`, `test:`, `refactor:`, `perf:`, `ci:`. Keep the subject imperative and concise. The release changelog is generated from these (git-cliff) — non-conforming commits are silently dropped from release notes.
4845
- One feature branch per issue; reference the issue in the PR body.
46+
- Release tags are bare `X.Y.Z` (never `vX.Y.Z`); releases are automated from the tag (see `MAINTAINERS.md`).
4947
- Never commit `.idea/` (JetBrains IDE files).
5048

5149
## Before you make changes
5250

5351
- Small, incremental, reviewable diffs. Match surrounding code style.
54-
- If a change could alter algorithm output, note that there is **no test net** and flag it for manual example verification.
55-
- There is a full audit + refactoring roadmap in **[`docs/jsfeat-parity-and-refactor-audit.md`](docs/jsfeat-parity-and-refactor-audit.md)** — read it before proposing structural refactors, the webpack→Vite migration, or porting `haar`/`bbf`.
52+
- Keep `npm test` green; add parity tests for new algorithm code.
53+
- The audit + roadmap lives in **[`docs/jsfeat-parity-and-refactor-audit.md`](docs/jsfeat-parity-and-refactor-audit.md)**; the release runbook in **[`MAINTAINERS.md`](MAINTAINERS.md)**.
5654

5755
## Roadmap pointers
5856

59-
- **Refactor direction:** de-duplicate stub/monolith → one real class per module, `jsfeatNext.ts` becomes a thin aggregator, shared singleton cache, static-facade API for jsfeat parity. (See audit doc §4.)
60-
- **Build direction:** webpack → **Vite library mode** + `vite-plugin-dts`; ship ESM+UMD; fix the double namespace. (See audit doc §5.)
57+
- **Remaining vs jsfeat:** port `haar` (#43) and `bbf` (#44); exhaustive per-symbol parity audit (#45).
58+
- **Toward 1.0:** prerelease-tag support in the release pipeline (#81); examples modernization (#79); new descriptors like FREAK (#80).

CLAUDE.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ The canonical, tool-agnostic guidance for this repo lives in **AGENTS.md**. It i
66

77
## Claude-specific notes
88

9-
- When editing an algorithm module (`imgproc`, `fast_corners`, `math`, `linalg`, `orb`, `yape06`, `motion_estimator`, `optical_flow_lk`, `pyramid_t`), remember the implementation is **inline in `src/jsfeatNext.ts`**, not in the same-named `src/<module>/<module>.ts` stub. Grep `jsfeatNext.<module> = class` to jump to it.
10-
- There are **no automated tests**. Do not report a change as "verified" unless you actually built (`npm run build-ts`) and checked the relevant `examples/*.html`, or the user confirms.
11-
- The audit/roadmap is in [`docs/jsfeat-parity-and-refactor-audit.md`](docs/jsfeat-parity-and-refactor-audit.md). Consult it before structural refactors, the Vite migration, or porting `haar`/`bbf`.
9+
- Every algorithm lives in its own module under `src/<module>/<module>.ts`, extending the base in `src/core/core.ts`; `src/jsfeatNext.ts` is only the aggregator that attaches the singletons (the old inline monolith and its type-only stubs are gone since #47).
10+
- **Always run `npm test`** (Vitest parity suite vs a vendored original-jsfeat oracle) before claiming an algorithm change is verified; for consumer-facing changes also build (`npm run build-ts`) and check the relevant `examples/*.html`, or ask the user to confirm.
11+
- On this Windows machine: verify formatting with `node_modules/.bin/prettier` directly (bare `npx prettier` can resolve the wrong version) and regenerate `package-lock.json` only with `npx npm@11`.
12+
- The audit/roadmap is in [`docs/jsfeat-parity-and-refactor-audit.md`](docs/jsfeat-parity-and-refactor-audit.md); the 0.9.0 API break is documented in [`docs/migration-0.9.md`](docs/migration-0.9.md); the release runbook is in [`MAINTAINERS.md`](MAINTAINERS.md).
1213
- Keep numeric/behavioral parity with the original jsfeat; preserve the public `jsfeatNext.<module>` API.

README.md

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,29 +18,26 @@ npm install @webarkit/jsfeat-next
1818
```
1919

2020
```js
21-
import pkg from "@webarkit/jsfeat-next";
21+
import jsfeatNext from "@webarkit/jsfeat-next";
2222

23-
// consumers unwrap the double namespace: pkg.jsfeatNext, not pkg directly
24-
// (a known wart, tracked for a future breaking-change cleanup — see #41)
25-
const jsfeatNext = pkg.jsfeatNext;
26-
27-
const imgproc = new jsfeatNext.imgproc();
23+
// algorithm modules are singletons — call them directly, no `new` (since 0.9.0)
2824
const src = new jsfeatNext.matrix_t(width, height, jsfeatNext.U8_t | jsfeatNext.C1_t);
29-
imgproc.grayscale(rgbaPixelData, width, height, src);
25+
jsfeatNext.imgproc.grayscale(rgbaPixelData, width, height, src);
3026
```
3127

32-
In the browser (UMD build), the same shape applies via the global:
28+
In the browser (UMD build), the global is the namespace directly:
3329

3430
```html
3531
<script src="dist/jsfeatNext.js"></script>
3632
<script>
37-
// note: reuse a *different* variable name — `var jsfeatNext = jsfeatNext.jsfeatNext`
38-
// would shadow the global with itself and break.
39-
var jsfeat = jsfeatNext.jsfeatNext;
40-
var imgproc = new jsfeat.imgproc();
33+
jsfeatNext.imgproc.grayscale(rgbaPixelData, width, height, src);
4134
</script>
4235
```
4336

37+
> **Upgrading from ≤ 0.8.x?** The `jsfeatNext.jsfeatNext` double namespace and the
38+
> `new jsfeatNext.imgproc()` calling convention were removed in 0.9.0 — see the
39+
> [migration guide](docs/migration-0.9.md).
40+
4441
## List of features ✨
4542

4643
- TypeScript definitions, with full TSDoc on every public class/method (`npm run docs` to generate a browsable API reference locally)
@@ -73,7 +70,7 @@ npm install @webarkit/jsfeat-next
7370
## Known limitations 🔍
7471

7572
- Not every original jsfeat class is ported yet — `haar` and `bbf` (Haar-cascade / BBF object detection) are not implemented. Tracked in [#43](https://github.com/webarkit/jsfeatNext/issues/43) and [#44](https://github.com/webarkit/jsfeatNext/issues/44).
76-
- jsfeatNext is **not a drop-in replacement** for jsfeat: algorithm modules are instantiated (`new jsfeatNext.imgproc()`) rather than called as static namespace functions, and consumers must unwrap the `jsfeatNext.jsfeatNext` double namespace. Tracked in [#41](https://github.com/webarkit/jsfeatNext/issues/41).
73+
- The `transform` module takes `matrix_t` arguments where original jsfeat's (never-shipped) `transform` module used raw arrays — same math, slightly different calling convention (see the parity audit, Axis 2).
7774

7875
## Examples 🧪
7976

dist/jsfeatNext.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)