Release: v2.3.0#245
Merged
Merged
Conversation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F5fwEknfSxpaGCVEME5D4h
Make the existing one-liner ('Null values are omitted from the output')
explicit and complete: null/undefined-valued table keys are dropped
recursively (nested tables, and tables inside objects within arrays)
before validation and serialization, in every binding — restoring the
1.x @iarna/toml drop semantics that the Rust-core cutover regressed
(#232). A null array *element* is a typed error naming the index: TOML
arrays cannot hold nulls, and silently dropping an element (as 1.x did)
shifts sibling indices — a silent data mutation the bytes-authority
refuses. Absent key == cleared optional field is the specced
equivalence; required-field nulls fail validation as missing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F5fwEknfSxpaGCVEME5D4h
Restores the 1.x @iarna/toml drop semantics the Rust-core cutover regressed: a null/undefined (JS) or None (Python) valued table key is dropped at the host->core marshal boundary, recursively — top-level fields, nested tables, and objects inside arrays — so the standard .nullable().optional() + '?? null' consumer pattern serializes byte-identically to never setting the field. A null array ELEMENT is still rejected, now with a typed error naming the index: TOML arrays cannot hold nulls, and dropping an element (as 1.x silently did) shifts sibling indices — a silent data mutation, not a semantics-preserving omission. A null value itself (a whole record, a scalar position) also stays an error. The rejection messages are minted in gitsheets-core (null_array_element_msg / null_value_msg) so the boundary's diagnostics read identically from every binding; each binding's marshal recursion applies the same specced rules (specs/behaviors/normalization.md 'Null / undefined handling'). Merge-patch marshalling, where null is the RFC 7396 delete sentinel, is deliberately untouched. Tests: core unit (message contract), napi boundary (recursive drop, byte-parity with the pre-stripped record, element/scalar rejections, sparse-array holes), Python mirrors, and a new 'nullish' cross-binding byte-parity fixture proving Node and Python land on identical bytes. Closes #232 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F5fwEknfSxpaGCVEME5D4h
Add specs/behaviors/freshness.md: non-transaction Sheet reads resolve through a rebindable read snapshot — rebound automatically after each commit-producing repo.transact (read-your-writes) and explicitly via sheet.refresh() / store.refresh() / repo.refresh(). Spec the streaming read surface: repo.readBlobStream(ref, path) resolved at call time, and sheet.getAttachmentStream(recordOrPath, name) resolved through the snapshot. Align indexing/transactions wording to the rebind model. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F5fwEknfSxpaGCVEME5D4h
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F5fwEknfSxpaGCVEME5D4h
… reads (#184, #235) Implement specs/behaviors/freshness.md host-side (the Rust core is stateless for non-transaction reads, so no core changes): - Sheet: mutable read snapshot + rebindReadTree (drops the memoized config; index builds invalidate via the existing treeHashAtBuild comparison); public sheet.refresh(); TypeError when tx-bound. - Repository: weak sheet registry populated from the Sheet constructor; repo.refresh(); transact rebinds every live sheet to the post-commit HEAD tree before post-commit hooks (read-your-writes). - Store: store.refresh() delegating to repo.refresh(). - repo.readBlobStream(ref, path): call-time-resolved streamed blob read with typed RefError/NotFoundError; sheet.getAttachmentStream(): the snapshot-resolved sibling, null when absent. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F5fwEknfSxpaGCVEME5D4h
…ob reads Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F5fwEknfSxpaGCVEME5D4h
…232) The real-world shape that surfaced the regression: a Standard Schema validator normalizing cleared optionals to explicit nulls ('?? null'). Verifies the write succeeds, the read-back has absent keys (not nulls), the on-disk bytes carry no trace of the cleared fields, re-writing the null-cleared record over the stripped one is a byte-level no-op (no new commit), the drop recurses through nested tables and objects inside arrays, a null-valued required field fails validation as missing, and a null array element is rejected with the index named and no tree mutation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F5fwEknfSxpaGCVEME5D4h
…ion (#233) Enumerates the three breaks that surfaced in the first production 1.x -> 2.x upgrade, each with before/after and the migration step: 1. hologit dropped — BlobObject.write + repo.hologitRepo replaced by repo.writeBlob(buf) + Sheet.setAttachments. 2. null/undefined-valued fields — the initial 2.x releases threw on marshal; fixed in this release to restore the specced 1.x drop semantics (recursive key drop), with the array-element edge case (error naming the index; 1.x silently dropped elements) called out. 3. Canonical byte re-baseline per #196 — the three value-preserving reformat classes and the one-time re-serialize commit recipe. Closes #233 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F5fwEknfSxpaGCVEME5D4h
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F5fwEknfSxpaGCVEME5D4h
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F5fwEknfSxpaGCVEME5D4h
…reaming feat(api): read freshness (refresh + transact auto-refresh) and streaming blob reads
Author
Changelog- feat(api): read freshness (refresh + transact auto-refresh) and streaming blob reads [#239] @themightychris
- feat(api): bytes attachments, repo.withLock, and cast-free Standard Schema typing [#242] @themightychris
- fix(marshal): drop null/undefined-valued keys instead of throwing (1.x parity) [#241] @themightychris
- docs(specs): correct attachment handle naming to shipped types [#246] @themightychris |
… contract (#234, #236, #237) - setAttachment(s) values accept raw Buffer/Uint8Array bytes (and UTF-8 strings) alongside BlobHandle — one-call attachment writes. - repo.withLock(fn): expose the transact write mutex for non-transact git ops; non-reentrant with lock_held deadlock guards (new TransactionError code). - Standard Schema type-level contract: gitsheets' declared StandardSchemaV1 mirrors the published interface exactly, so compliant Zod v4 / Valibot / ArkType schemas assign without casts, regression-guarded by a compile-time test. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F5fwEknfSxpaGCVEME5D4h
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F5fwEknfSxpaGCVEME5D4h
Generated by: npm install -w gitsheets --save-dev zod Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F5fwEknfSxpaGCVEME5D4h
…a typing (#234, #236, #237) - Sheet.setAttachment(s) values widen to string | Buffer | Uint8Array | BlobHandle — raw bytes are written to the object store during staging, making the common attach-these-bytes case one call (#234). - repo.withLock(fn) exposes the transact write mutex for non-transact git ops; FIFO with transactions, released on settle, and explicitly non-reentrant — withLock-in-withLock, withLock-in-transact, and transact-in-withLock all throw the new TransactionError('lock_held') instead of deadlocking, via an AsyncLocalStorage lock context (#236). - The exported StandardSchemaV1 types now mirror the published Standard Schema v1 interface exactly (PropertyKey issue paths, ~standard.types carrier), so compliant Zod v4 schemas assign with no as-cast; the compile-time contract is regression-guarded by store-zod.test.ts against real Zod v4, plus runtime validate/transform/reject coverage through the typed Store (#237). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F5fwEknfSxpaGCVEME5D4h
…tor typing Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F5fwEknfSxpaGCVEME5D4h
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F5fwEknfSxpaGCVEME5D4h
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F5fwEknfSxpaGCVEME5D4h
feat(api): bytes attachments, repo.withLock, and cast-free Standard Schema typing
fix(marshal): drop null/undefined-valued keys instead of throwing (1.x parity)
The writeBlobFromFile example was already superseded by #242's bytes-accepting setAttachment rewrite; this sweeps the residual hologit-era BlobObject references to the shipped BlobHandle / AttachmentBlobHandle types. Deliberately no file-path helper: setAttachment(bytes) + readFile covers the case. Closes #244 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…naming docs(specs): correct attachment handle naming to shipped types
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.
Consumer-hardening release driven by the first production 2.x migration: read-your-writes freshness, streaming attachment reads, one-call byte attachments, an exposed write lock, cast-free Standard Schema typing, and 1.x-parity null handling.
Improvements
Technical