Skip to content

Migrate to libuild, flatten monorepo, add disposal protocol (3.1) - #82

Merged
brainkim merged 14 commits into
masterfrom
3.1
Jun 7, 2026
Merged

Migrate to libuild, flatten monorepo, add disposal protocol (3.1)#82
brainkim merged 14 commits into
masterfrom
3.1

Conversation

@brainkim

@brainkim brainkim commented Jun 3, 2026

Copy link
Copy Markdown
Member

Build/structure:

  • Replace Rollup + Jest with @b9g/libuild; tests via @b9g/libuild/test
  • Flatten packages/* into single package with subpath entry points: repeater/core (class only), repeater/static (race/merge/zip/latest as named fns), repeater/timers, repeater/limiters. Default repeater re-exports the full class with statics attached (no breaking change).
  • Deprecate pubsub (removed) and react-hooks (already gone)
  • Switch to npm lockfile; TypeScript 5.x with isolatedModules

API:

  • Add Symbol.asyncDispose / Symbol.dispose (ES2025) with runtime guards
  • Rename aux exports: delay->createDelay, interval->createInterval, timeout->createTimeout, semaphore->createSemaphore, throttler->createThrottle
  • createStop uses Object.assign; push() float-detection uses Object.create(nextP)

Fixes:

  • index.ts re-exported type-only names as runtime bindings, crashing ESM imports; split into export/export type. isolatedModules now guards this.

Tests: 163/163 green on both node and bun.

Refs #81

brainkim and others added 4 commits June 3, 2026 00:29
Build/structure:
- Replace Rollup + Jest with @b9g/libuild; tests via @b9g/libuild/test
- Flatten packages/* into single package with subpath entry points:
  repeater/core (class only), repeater/static (race/merge/zip/latest as
  named fns), repeater/timers, repeater/limiters. Default `repeater`
  re-exports the full class with statics attached (no breaking change).
- Deprecate pubsub (removed) and react-hooks (already gone)
- Switch to npm lockfile; TypeScript 5.x with isolatedModules

API:
- Add Symbol.asyncDispose / Symbol.dispose (ES2025) with runtime guards
- Rename aux exports: delay->createDelay, interval->createInterval,
  timeout->createTimeout, semaphore->createSemaphore, throttler->createThrottle
- createStop uses Object.assign; push() float-detection uses Object.create(nextP)

Fixes:
- index.ts re-exported type-only names as runtime bindings, crashing ESM
  imports; split into export/export type. isolatedModules now guards this.

Tests: 163/163 green on both node and bun.

Refs #81
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Address code review (two P2s):
- Add root exports map for ./core, ./static, ./timers, ./limiters so
  subpaths resolve for file:/npm link/git installs, not just the
  registry tarball (which ships dist/package.json).
- Add `prepare` script so dist/ is built on install for those same
  non-registry modes, since dist/ is gitignored.

Verified: `npm run prepare` builds all 5 entry points from clean; a
link-style install resolves every subpath via the root exports map.

Refs #81
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Also normalize repository.url to git+https form to silence npm warning.

Refs #81
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reconstruct core.ts from the original repeater.ts so the file keeps its
explanatory JSDoc/rationale (buffer docs, state enum, RepeaterRecord
fields, stop-vs-finish, the AsyncGenerator/implements note, the r.err
rationale, the #49 executor-settle TODO) and the <T, TReturn, TNext>
generics on the internal helpers, rather than the comment-stripped,
any-typed extraction.

Intentional deltas preserved: Object.create(nextP) float detection,
Object.assign in createStop (+ onstop arity wrap), disposal symbols.

tsc clean; 163/163 on node and bun; build emits with type-only names erased.

Refs #81
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@brainkim brainkim left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some general thoughts

Comment thread src/test/_testutils.ts Outdated
Comment thread src/combinators.ts
Comment thread src/test/_testutils.ts Outdated
Comment thread src/core.ts
brainkim and others added 8 commits June 3, 2026 12:59
- Rename src/static.ts -> src/combinators.ts (subpath becomes
  @repeaterjs/repeater/combinators); update index/limiters imports and
  regenerate the exports map.
- Drop the custom rejection() test helper in favor of the built-in
  `expect(p).rejects` matcher, which works on bun for native promises
  (the helper was an over-correction; only the .resolves cases needed
  plain-await).

tsc clean; 163/163 on node and bun.

Refs #81
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Introduce a dependency-free internal util module aligned with crank's
src/_utils.ts (isPromiseLike, isIteratorLike, safeRace) so the
memory-safe race implementation stays in sync across bikeshaving
packages. The underscore prefix keeps it out of libuild's entry points,
so it bundles into importers rather than becoming a public subpath.

Swap the two remaining raw Promise.race sites — limiters' stop/leak race
and combinators' loser-cleanup — for safeRace, closing the last
memory-leak vectors. (The advance/advances hack in combinators stays for
now; a safeRace-based rewrite of race() is a separate, higher-risk pass.)

tsc clean; 163/163 on node and bun; safeRace inlines into the
combinators and limiters bundles.

Refs #81
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the hand-rolled advance/advances deferred coordination in race,
merge, zip, and latest with safeRace([work, stop]). Since stop resolves
to undefined, a safeRace result of undefined doubles as the stop signal,
which lets us drop every stopped flag and stop.then fan-out.

merge/zip/latest are clean structural swaps (one pending next() per loop,
no multi-pending). race keeps a per-next() done-observer so that any
contender finishing still stops the race and records its return value
even when it never wins a value turn — preserving the exact
sample-fastest / stop-on-any-finish semantics. (A naive safeRace-winner
rewrite loses the finisher's return value to a stranded next(); see the
analysis in the PR.)

Verified: 163/163 across 5x node + 3x bun stress runs.

Refs #81
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New coverage for two features this release that had none:
- src/test/dispose.ts: Symbol.asyncDispose / Symbol.dispose, `using` and
  `await using`, and that combinator results are disposable.
- src/test/utils.ts: safeRace (first-settle, rejection, non-thenable and
  already-settled contenders, never-settling, shared promise, undefined
  sentinel) plus isPromiseLike / isIteratorLike.

The dispose tests surfaced a real gap: the disposal methods were installed
on the prototype at runtime but never declared on the Repeater type, so
`await using r = new Repeater(...)` was a type error for consumers. Added
an interface merge declaring [Symbol.asyncDispose]/[Symbol.dispose] so it
type checks, while keeping the feature-detection guards for old runtimes.

178/178 on node and bun.

Refs #81
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Property-based tests (fast-check) for race/merge/zip/latest covering
value-completeness, column/width invariants, sub-multiset output, and
early-break disposal across randomized async interleavings. Add a
node-oriented GitHub Actions workflow (typecheck/build/test on Node
20/22/24, plus a bun-target job) and a `typecheck` npm script.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
timers and limiters pulled Repeater/buffers from ./index.js, which is the
Repeater subclass carrying the static combinators — so every timers bundle
dragged in race/merge/zip/latest. Point them at ./core.js instead; the
timers bundle no longer includes the combinators.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
brainkim and others added 2 commits June 7, 2026 11:27
Three small tests take every source file to 100% line + function coverage:
- latest() rejects when a contender throws on its first pull (the one
  combinator error branch nothing hit)
- createSemaphore / createThrottle throw RangeError for limit < 1

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@brainkim
brainkim merged commit eaf5609 into master Jun 7, 2026
6 checks passed
@brainkim
brainkim deleted the 3.1 branch June 7, 2026 16:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant