Version Packages#264
Merged
Merged
Conversation
01c93bf to
bdd1613
Compare
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.
This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.
Releases
@nicia-ai/typegraph@0.36.0
Minor Changes
#261
5bc7b53Thanks @pdlug! - Return a receipt fromstore.withRecordedTransaction, and add scoped writemeasurement with
tx.measure.store.withRecordedTransaction(externalTx, fn)now returnsPromise<TransactionOutcome<T>>instead ofPromise<T>. The adopted pathis the only way to get exactly-once cursors and graph writes atomically on a
history store, and it now surfaces the same receipt
transactionWithReceiptdoes:
receipt.writesfor dropped-change detection andreceipt.recordedasthe per-transaction replay anchor (
undefinedfor a read-only callback or anon-history store).
BREAKING: the adopted path now returns the result under
.result. Migrateby destructuring:
Scoped receipts —
tx.measure((scoped) => ...). On the receipt-enabledcontexts (
transactionWithReceipt,withRecordedTransaction),tx.measureruns its callback with a scoped context — a second view over the same
transaction — and returns a
TransactionOutcomewhose receipt counts exactlythe writes made through that scoped context (
scoped.nodes/scoped.edges). So a framework can attribute writes to user code it invoked(e.g. a materializer measuring
project(scoped, change)to detect a droppedchange) while its own bookkeeping — written through the outer
tx— stays outof the count. Attribution is by which context you write through, not by
timing, which makes overlapping and concurrent measures safe by construction
(two scopes racing under
Promise.allnever cross-count). Nesting composes;measured writes still count in the outer receipt; a scoped receipt's
recordedis alwaysundefined. Plainstore.transaction()contexts have nomeasure(that path runs no recorder and stays zero-overhead). New exportedtypes:
MeasurableTransactionContext,MeasurableHistoryTransactionContext,ScopedMeasure<Ctx>.Adopted contexts seal on return. A transaction context retained and
written through after its
withRecordedTransactioncallback resolves nowfails loud on both paths — the history path's capture guard is checked
before the live write (so a swallowed error can no longer commit an
uncaptured row), and the non-history path seals its receipt-tracked
collections (so a post-return write can't persist a row the already-returned
receipt never counted).
#262
34468a0Thanks @pdlug! - Add an opt-incoalesceUnchangedUpsertsstore option for at-least-once /replay materializers.
Idempotent event-log projectors converge live state correctly, but every
re-delivery of a byte-identical value still performed a real write:
upsertByIdon an existing id calledupdateNodeunconditionally, allocatinga fresh recorded instant and a new history row. A full replay of an N-event log
therefore rewrote every row and grew recorded history by N — the recovery /
rebuild workload inflates history the most.
With
createStore(graph, backend, { coalesceUnchangedUpserts: true }), anupsertById(orbulkUpsertByIditem) whose validated props arevalue-identical to the existing live row performs no write at all: no
updateNode, no recorded-time capture, no history row, no revision-anchoradvance, and no
updateoperation hooks. It resolves with the existing node.The dirty-check compares the storage-normalized representation (props run
through the kind's Zod schema, key-order-independent), so it answers exactly
"would the persisted value differ?".
A write still happens (never coalesced) when the row is soft-deleted (an upsert
resurrects it), when an explicit
validFrom/validTois passed, or when anyprop differs. Default off, because some consumers want an audit row per
re-delivery. Covered symmetrically for edge
bulkUpsertById(props only —endpoints are the edge's identity).
Receipt semantics are unchanged and need no new signal: a coalesced upsert
still counts as one write intent (
writes.total) but captures nothing(
recordedstaysundefined) — the same two-signal shape as a no-op delete,which at-least-once consumers already handle by carrying the prior anchor
forward.
#260
35d03aeThanks @pdlug! - Make the store transaction surface tell the truth about raw SQL and historycapture.
New
tx.sqlAvailabilitydiscriminant. Every transaction context nowcarries a required
sqlAvailability: "available" | "history" | "revisionTracking" | "unavailable"field. Branch on it instead oftruthiness-testing
tx.sql: underhistory: true/revisionTracking: truethe raw handle is present-but-throwing (so
if (tx.sql)read truthy and thenthrew), and it is
undefinedonly on the non-transactional fallback."available"means
tx.sqlis a usable raw handle;"history"/"revisionTracking"meanraw SQL is disabled here;
"unavailable"means the backend has no transactions(
tx.sql === undefined, no atomicity).store.withTransaction()on a history-enabled store is now a compile error.It always threw at runtime; the call site now rejects the argument with a
message pointing at
store.withRecordedTransaction(). The runtime guard isunchanged for suppressed calls.
Branchable recorded-capture guard codes. The
ConfigurationErrors theseguards throw carry a stable
details.code(
RECORDED_CAPTURE_REQUIRES_CALLBACK_TRANSACTION,RECORDED_CAPTURE_RAW_SQL_DISABLED,REVISION_TRACKING_RAW_SQL_DISABLED), nowexported as
RECORDED_CAPTURE_GUARD_CODESwith aRecordedCaptureGuardCodetype and an
isRecordedCaptureGuardError(error, code?)type guard — so aportable caller can distinguish "history forbids raw SQL here" from "this
backend has no transactions" without substring-matching the message.
Fixed
withRecordedTransaction's JSDoc, which incorrectly promisedtx.sql; on the adopted path you already hold the pinned connection, so writeyour own relational tables through the external transaction handle you passed
in.