Repository navigation
Conversation
Public Triples reads always resolve a temporal basis before reaching the
KV layer, so the entity-type fast path (basis === undefined) never ran
for them: every match({ entityType }) became a temporal scan of the
whole store, journal included. ConfigStore.load issues five of these,
so each ConfigRuntime.evaluate scanned every fact five times.
Add getByEntityTypeTemporal (and its async twin) to KvTripleStore: read
the TYPE index, then keep the datoms visible at the basis. Retraction
only rewrites META, so the index still names every datom asserted with
the type and visibleAt decides recorded and valid time as before.
On an in-memory store with 16,000 facts, ConfigRuntime.evaluate drops
from 480-985 ms to 1-3 ms. Found while running Triplex in the browser
behind WorldVM's programmable-operations demo.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This was referenced Oct 29, 2026
Draft
bjacobso
added a commit
that referenced
this pull request
Oct 30, 2026
…ory to the entity Entity-type matches at a temporal basis now read the TYPE index (the cherry-picked fix from #46). Attached segments join that path through their own type index, with the same visibility check, so a segment fact retracted later is still visible at an earlier basis. `transactionsForEntity` hydrated an attachment's record with every fact in its segment. It now lists only that entity's facts, found by binary search, so an entity's history page stays small however large the segment is. `transaction(txId)` still derives the complete change list. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This branch has not been deployed
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.
Problem
Public
Triplesreads always resolve a temporal basis before they reach the KV layer (resolveTemporalBasis(basis, yield* runtime.now)). The entity-type fast path inKvTriplesLive.matchonly ran whenbasis === undefined, so it never ran for public reads. Everymatch({ entityType })becamescanCollectTemporalAsync({}, basis): a temporal scan of the whole store, transaction journal included.ConfigStore.loadissues five of these reads (objects, types, revisions, snapshots, refs), so everyConfigRuntime.evaluatescanned every fact five times.Change
KvTripleStore.getByEntityTypeTemporal(and an async twin) read the TYPE index, then keep the datoms visible at the basis. Retraction only rewrites META, so the index still names every datom ever asserted with the type, andvisibleAtdecides recorded and valid time exactly as the full scan did.KvTriplesLive.matchuses them whenever a basis is present.Evidence
KvTriples.store.test.ts: entity-type matches at three bases (before a retraction in recorded time, after it, and in a later valid-time interval) return the right triples. A counting KV backend shows the three reads touch 6 entries. Without the fix they touch 4,548.ConfigRuntime.evaluatetakes 1–3 ms with the fix and 480–985 ms without it.pnpm --filter @triplex-build/triplex typecheck,test(712 tests),pnpm lintandpnpm format:checkpass.Context
Found while running Triplex core in the browser, in memory, behind WorldVM's programmable-operations demo, where requirement statuses become
ConfigRuntimedecisions. WorldVM currently works around this by caching ref resolutions.The same exploration measured the in-memory write path, which this PR does not change.
transactcosts about 165 µs per fact in Chrome and 250–300 µs in Node (16k facts: 2.6 s in Chrome; 82k facts: 14.7 s). The time goes to byte-encoding four or five index keys per triple, a per-keyEffectplus a char-by-char latin1 conversion inside transactions (the backend'ssetAllStrfast path isn't used there), one journal triple per change (which roughly doubles the writes), and GC. That's a candidate for a follow-up.🤖 Generated with Claude Code