Add file-ref field kind so content-held file references are real references#91
Merged
Merged
Conversation
…re real references (#63) Adds `file-ref` as a scalar FieldDef kind: values must be well-formed SHA-256 hex fileIds, and top-level file-ref content fields are now indexed the same way attachment associations are. `attachmentFileId` query matching, deleteAttachment()'s reference check, and attachment access conveyance under ScopedStack all now see a fileId held in a file-ref field — closing the gap where a fileId in plain content was invisible to the reference checks the spec already promised. A fileId in a plain `string` field still conveys none of this, by design. SQLite-backed adapters track this via a new file_refs index table kept in sync on every content/typeId write; MemoryAdapter checks the record's registered type schema at query time instead.
… re-querying on every write syncFileRefs() was hitting the types table and JSON.parse-ing the full schema on every single createRecord/patchContent/restoreVersion/ commitMigration call, even though most types have no file-ref fields at all. Cache the file-ref field names per typeId (populated in saveType(), lazily filled for types saved before this adapter instance existed) so that lookup happens once per type instead of once per write.
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.
Summary
Closes #63.
The spec's attachment-delete contract promises a
409 Conflictwhen a record's content references a fileId, not just when anattachmentAssociation does — but there was no way to distinguish "content that references a file" from "content that happens to contain 64 hex characters." This addsfile-refas a scalar field kind to close that gap:FieldDefgainskind: 'file-ref'(packages/core/src/types.ts). Values must be well-formed fileIds (SHA-256 hex) — validated at write time, referential existence is not (same stance asrecord-ref).isCompatible()treatsfile-refas exact-match only.file-refcontent fields are now indexed the same wayattachmentAssociations are: theattachmentFileIdquery filter,deleteAttachment()'s reference check (including the atomicdeleteUnreferencedAttachmentRecordspath), andScopedStackattachment-access conveyance all now see a fileId held in afile-reffield.sqlite-shared) track this via a newfile_refsindex table, kept in sync on every write that can change content or typeId (createRecord,patchContent,restoreVersion,commitMigration), and cleaned up on hard delete.MemoryAdaptercomputes the same match at query time against the record's registered type schema (no persisted index needed for the in-memory test double).stringfield continues to work but gets none of this — no delete protection, no access conveyance, no future GC protection. Documented explicitly indocs/spec.md.Scoped to top-level scalar
file-reffields only, matching the existing limitation that only top-level scalars support exact-match content filtering in queries.Test plan
packages/core/tests/validate.test.ts— file-ref format validation (accepts well-formed SHA-256 hex, rejects wrong length/case/non-hex/non-string)packages/core/tests/schema.test.ts— file-ref is exact-match-only inisCompatible()packages/core/tests/stack.test.ts—deleteAttachment()blocked by a content-heldfile-reffield; unaffected by a plainstringfield with the same valuepackages/core/tests/scoped-stack.test.ts— attachment access conveyed by a readable record'sfile-reffield; not conveyed by an equivalentstringfieldpackages/record-adapter-sqlite/tests/record.test.tsandpackages/record-adapter-sqljs/tests/record.test.ts—file_refsindex population/query matching, updates onpatchContent, cleanup on hard delete, and the atomic delete-block path🤖 Generated with Claude Code
Generated by Claude Code