cordis
Frameworkcordiverse/cordis
A meta-framework for spatiotemporal composability in TypeScript.
Overview
Cordis is a meta-framework that introduces a programming paradigm for spatiotemporal composability. It is actively developed, with a plugin-based architecture designed for building modular and extensible applications in Node.js.
README Preview
\n Cordis\n\n\nA Meta-Framework of Spatiotemporal Composability.\n\n**Cordis is under active development. The API is not yet stable and may change without notice.**\n\n- Paper: [_A Programming Paradigm for Spatiotemporal Composability_](https://github.com/cordiverse/paper)\n- Documentation: [cordis-primer](https://deepseek-harness.github.io/deepseek-harness/reference/cordis-primer)\n
FAQ (4)
TroubleshootingHow to fix "Type 'undefined' is not assignable to type 'Timeout'" under TypeScript exactOptionalPropertyTypes?
In @cordisjs/plugin-include 1.0.4, explicitly include undefined in the optional property type. In packages/include/src/index.ts, change:
private writeTask?: NodeJS.Timeout
to:
private writeTask?: NodeJS.Timeout | undefined
This is type-only and does not change runtime behavior. Apply the same T | undefined widening to any other optional properties that are explicitly assigned undefined. Consumers waiting for an upstream release can patch the published type declaration with patch-package.
TroubleshootingWhy does a Cordis plugin still load after its Context is disposed and never trigger its dispose event?
This was a bug in Cordis: calling ctx.plugin() asynchronously after the parent context had been disposed could still install the child plugin, leaving its side effects uncleaned. Upgrade Cordis to a release containing commit fc388fdbabb9642d3b5b1440573d96cda25604a3. After the fix, plugins registered on a disposed context are not activated. As a temporary workaround, guard asynchronous plugin registration yourself:
let disposed = false
ctx.on('dispose', () => {
disposed = true
})
setTimeout(() => {
if (disposed) return
ctx.plugin((ctx) => {
ctx.on('dispose', () => {
// cleanup
})
})
}, 100)TroubleshootingHow do I fix "patch: entry not found" when a Cordis patch targets a row inserted by an earlier patch in the same include list?
This happens because @cordisjs/plugin-include builds entryMap once before applying patches. Rows added by an insert patch are not added to that map, so later patches cannot resolve their IDs. The fix is to upgrade to a release containing PR #116 (or the superseding journal fix), which indexes inserted rows immediately after insertion. If maintaining the loader locally, call/merge buildMap(insert) into the entry map in the insert branch before continuing, including recursive group entries. Temporary workarounds: put the final state directly on the inserted row, e.g. insert: [{ id: 'added', name: 'plugin-c', disabled: true }], or split the insert and subsequent modification into two separate include/patch passes so the map is rebuilt between them. Update with: npm update @cordisjs/plugin-include.
TroubleshootingWhy does cordis fiber.state stay PENDING or UNLOADING forever after calling dispose()?
This is a bug in cordis: state is a cached field only recomputed by _updateState(), and early returns inside _setEpoch() skip that refresh for any fiber that was not ACTIVE at dispose time. This hits exactly two cases: fibers stuck PENDING because a required dependency was never provided, and fibers whose plugin threw during load (stuck UNLOADING). Workarounds: (1) Check fiber.uid === null instead of fiber.state to detect disposal — uid is set to null on dispose, and the internal _getState() already maps a null uid to FiberState.DISPOSED. (2) Or patch fiber.ts so the dispose effect converges state at the end: after the while (this.inertia) { await this.inertia } loop, add a single this._updateState(() => {}) call. With that change all cases settle on DISPOSED, the disposal correctly emits a PENDING -> DISPOSED internal/status event, and the full cordis test suite (246 tests) passes.