@debonet/es6pacts
Version:
Releasable/cancellable and Reporting Promises for Javascript ES6
125 lines (91 loc) • 8.9 kB
Markdown
# es6pacts v3 — Composition Spec
## TL;DR
* Pact remains a one-line composition: `fclassReleasable( Task )`. All release semantics come from es6pledges v3; all reporting semantics come from es6tasks v3 (observer model).
* The executor pass-through in the v3 mixin is what delivers Task's `fReport` as the Pact executor's third parameter: `( fResolve, fReject, fReport ) => xPolicy`.
* README must be rewritten against the v3 API (the current example is a SyntaxError as written and uses the removed second constructor argument); the new example shows multiple observers and chain windowing.
* Version → `3.0.0`, depending on `@debonet/es6pledges ^3.0.0` and `@debonet/es6tasks ^3.0.0` (all three packages ship as 3.0.0; es6tasks skips 2.x for family alignment).
* Test suite expands beyond the single resolve-path test; nothing is deferred — the reporting model is decided.
## Purpose
Pact composes releasability onto reporting by applying the es6pledges mixin to Task.
## Normative References
Shared v3 semantics are owned upstream and are not restated here:
* `../es6pledges/specs/v3-core.md` — constructor, executor, policy, pipeline, dispatch, wrappers, release contract, verbs, `releaseOn`.
* `../es6pledges/specs/v3-chains-combinators.md` — chain and combinator release.
* `../es6tasks/specs/v3-reporting.md` — observer model, microtask delivery, post-settle suppression, windowing, combinator report format.
* `../es6tasks/specs/v3-bugfix-appendix.md` — Task crash-defect history.
(Workspace-relative paths; in published form these are the `specs/` directories of `@debonet/es6pledges` and `@debonet/es6tasks`.)
## Composition Contract
* `src/es6pacts.js` stays exactly: require `fclassReleasable` from `@debonet/es6pledges/fclassReleasable`, require `Task` from `@debonet/es6tasks`, export `fclassReleasable( Task )`.
* Executor pass-through: the mixin forwards all executor parameters beyond `fResolve` / `fReject` from the wrapped class (requirement stated in the core spec). Task passes its report function third, so a Pact executor is `( fResolve, fReject, fReport ) => xPolicy` and the release handler may close over `fReport`.
* `Pact.PactResolve` / `Pact.PactReject` are available as statics via the mixin.
* `then( fOk, fErr )` is standard 2-arg in Task v3 (the `aOpts` third argument is deleted). The mixin's generic extra-argument pass-through in `then` remains harmless and future-proof, but no extra argument is consumed.
* Task's own settle-wrapping (capability bookkeeping around `fOk` / `fErr`) is preserved because the mixin captures the capabilities Task passes into the executor.
* Interlocking guarantees, both enforced upstream and re-verified here at composition level:
* Post-settle report suppression means a released (hence settled) Pact stops reporting — no pacts-specific code needed.
* A REFUSED release leaves the Pact pending, so reports keep flowing during and after refusal.
## README Rewrite Requirements
Rewrite `README.md` for the v3 API. Specific corrections to the original document:
* The example is a SyntaxError as written: stray `;` after `.progress(( x ) => ... )` before `.then( ... )`. Fixed example chains without the semicolon.
* The example computes `let r = ...` and never uses it. Remove or use it.
* The example uses the removed second constructor argument. v3 form: the executor returns the cleanup, e.g. `return () => clearInterval( interval );`.
* The PRIMARY example is the shared cross-library example below, exactly as specified. Usage MUST hold and release the SOURCE pact (cancel-at-source idiom) — releasing a chain tail skips its own handler under scoped release and would make the shown output wrong.
* A SECONDARY example keeps the full windowing walkthrough: a `.progress` attached before a `.then` (sees that stage only) and a `.progress` attached after it (sees the forwarded parent stream plus the handler-returned pact's stream), with the release arriving mid-chain and the output block showing suppression.
* State plainly that progress reports stop when the pact settles — now a normative library guarantee (post-settle suppression in the reporting spec), not executor discipline.
* Document, by summary plus links to the upstream specs: single-argument constructor, executor-returned policy, type dispatch with `PactResolve` / `PactReject`, refusable `resolve` / `reject` / `fulfill`, the release return contract, `releaseOn`, observer-model `.progress` (independent observers, no replay, returns `this`).
* Version references in the README (dependency badges, "Full Semantics" cross-references, install snippets): all three packages are 3.0.0; do not cite an es6tasks 2.x anywhere.
* Style: `*` lists, BLUF, no hyperbole.
### Primary example (shared across the three libraries)
The three READMEs (es6pledges, es6tasks, es6pacts) share ONE running primary example — the interval delay function — so the composition is visible by inspection. THIS repo owns the canonical superset form; the parent libraries' forms are reductions of it (es6pledges drops the reporting lines; es6tasks drops the cleanup line). Use this code verbatim (names and structure are pinned; minor drift here breaks line-compatibility in three READMEs at once):
```javascript
function fpactDelay( dtm, dtmTick = 100 ){
return new Pact(( fResolve, fReject, fReport ) => {
let dtmElapsed = 0;
const interval = setInterval(() => {
dtmElapsed += dtmTick;
fReport( dtmElapsed );
if ( dtmElapsed >= dtm ){
clearInterval( interval );
fResolve( dtm );
}
}, dtmTick );
return () => clearInterval( interval );
});
}
```
Usage (source-held; the release lands on the source and flows downstream):
```javascript
const pactDelay = fpactDelay( 10000 );
pactDelay
.progress(( dtmElapsed ) => console.log( "progress:", dtmElapsed ))
.then(( x ) => console.log( "result:", x ));
setTimeout(() => { pactDelay.resolve( "faster" ); }, 250 );
```
Shown output:
```
progress: 100
progress: 200
result: faster
```
(The release at 250ms sits mid-gap between the 200ms and 300ms ticks, so the shown output is stable; the cleanup clears the interval and post-settle suppression guarantees no report after settlement.)
Diff contract (state it in the README next to the example): remove the `fReport` executor parameter and the `fReport( dtmElapsed );` line and this is `fpledgeDelay` (`@debonet/es6pledges` — releasable, no reporting); remove instead the `return () => clearInterval( interval );` line and this is `ftaskDelay` (`@debonet/es6tasks` — reporting, not releasable). All other lines are identical across the three libraries. Pact is the union, exactly as `fclassReleasable( Task )` is the union of the two libraries.
## Versioning
* `package.json` version → `3.0.0` (breaking, tracks the pledges v3 API and tasks v3 reporting).
* `dependencies`: `@debonet/es6pledges` → `^3.0.0`; `@debonet/es6tasks` → `^3.0.0` (es6tasks ships as 3.0.0 — version-family alignment; semver permits skipping 2.x from 1.0.11).
* Note: the repo's current `2.0.1` is behind the published npm `2.0.3`; v3 supersedes both — publish from this repo at `3.0.0`.
## Build and Test
Interpreted-language project; no build step.
* Test runner: jest `^29` (existing devDependency), command `npm test`.
* Tests: `/Users/jsd/@aios/tmp/es6pacts/test/es6pacts.test.js`, expanded from the single resolve-path test to cover composition-level behavior:
* Executor receives `fReport` as the third parameter; reports reach `.progress` observers with observer-model semantics (same raw value to multiple observers, attachment order).
* `release( xReason )` on a Pact runs the executor-returned cleanup and settles by dispatch; reports issued after settlement are dropped (suppression at composition level).
* Refusal: cleanup that rejects leaves the Pact pending WITH reports still flowing to observers.
* `resolve()` / `reject()` verbs on a Pact chain with `.progress` attached.
* Chain windowing on Pacts: observer attached before `.then` sees only that stage; observer attached after sees the forwarded stream plus a handler-returned Pact's stream (the reporting spec's windowing example, exercised through the mixin).
* `releaseOn` with an AbortController.
* A Pact combinator (`Pact.all`) emits `{ task : n, report : x }` member reports and both report suppression and group release work on the combined pact.
* v2 continuity: `test/v2compat.test.js` per `specs/v3-v2compat.md` (additive; existing suite untouched).
* The test file's existing polyfill shim for running without jest is dropped; jest is the declared runner.
## Dependencies
* `@debonet/es6pledges ^3.0.0`, `@debonet/es6tasks ^3.0.0`.
## Error Handling
Entirely inherited: refusals per the pledges core spec, task rejections and observer-exception isolation per the tasks reporting spec. This module adds no error surface.