feat(api)!: singleton modules, shared cache, direct namespace export (0.9.0, closes #41)#82
Merged
Conversation
…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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #41. Breaking change — ships as 0.9.0. Restores original jsfeat's calling convention. Designed via
/brainstorming; full migration guide added atdocs/migration-0.9.md.What changes for consumers
Three things resolved (all three were rows 1, 4, 5 of the parity audit's Axis 2):
src/index.tsdefault-exports the namespace directly, so the UMD global and ESM default both ARE the namespace.imgproc,math,matmath,linalg,transform,fast_corners,yape,yape06,orb,optical_flow_lk,motion_estimator,affine2d,homography2d,cache) are pre-constructed instances on the namespace; nonew.src/core/core.tscreates a module-levelshared_cacheevery module binds to, replacing per-instance 30-buffer/76.8 KB pools. Falls out naturally from singletons.Unchanged: the data-structure constructors (
matrix_t,keypoint_t,pyramid_t,ransac_params_t) are stillnewed — they're genuine constructors in jsfeat too. Constants stay on the namespace.Why singletons, not a statics-rewrite (the ground truth)
This isn't "match jsfeat for its own sake" — verified against the actual inspirit/jsfeat source:
jsfeat_fast_corners.jsis an IIFE holding closure-level_threshold, exposed as one object, and configured once at load viafast_corners.set_threshold(20). There was never a notion of multiple independently-configured detectors.imgprocborrows from the one globaljsfeat.cacheso buffers freed by one stage feed the next. Per-instance pools defeated that.newa module inside a per-frame loop and silently reallocate pools every frame. With singletons that's impossible by construction.Versioning
Ships as 0.9.0 (pre-1.0 semver allows breaking minors).
1.0.0-alphawas considered and rejected — the release pipeline can't handle prerelease tags yet (filed as #81). Hard cut, no compat shim.Tests & verification
tests/api-shape.test.tspins the namespace shape, singleton identity, shared-cache identity, and constructor surface. 63/63 pass.examples/converted; the 5 non-camera ones executed in Node against the new bundle, all 22 static-scanned, and browser-tested by @kalwalt.browser.html'snew jsfeat.cache()usage rewritten (jsfeat never supported that either).tsc --noEmitclean ·prettier --checkclean · UMD+ESM bundle shapes verified (no double namespace, singletons callable, shared pool public).dist/+types/rebuilt and committed in this PR — a deliberate deviation from the source-only convention, at @kalwalt's request: for an API-breaking change, a stale committed bundle made local browser testing confusing.Docs
docs/migration-0.9.md(full old→new table + motivation), README quick start,AGENTS.md,CLAUDE.md,.github/copilot-instructions.md, and the audit doc (Axis 2 marked resolved with a new status column).Decision log
new X()in aggregatorshared_cachedist/typeshere🤖 Generated with Claude Code