From fbd0fb3321106849541c25394f28ca74ffb18f6e Mon Sep 17 00:00:00 2001 From: Kris Zyp Date: Mon, 26 Oct 2026 18:35:16 -0600 Subject: [PATCH 1/7] Document what a tracked update() record writes and how addTo calls combine Covers HarperFast/harper#3586: reads are not writes, Date/Bytes must be reassigned, undeclared properties save on non-sealed tables, repeated addTo/subtractFrom calls compose, and subtractFrom accepts a BigInt. Dispatch-Task: docs-harper-3586 Co-Authored-By: Claude Opus 5.5 --- reference/resources/resource-api.md | 38 ++++++++++++++++++++++++----- release-notes/v5-lincoln/5.4.md | 6 +++++ 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/reference/resources/resource-api.md b/reference/resources/resource-api.md index 7bc962206..d258a8bc2 100644 --- a/reference/resources/resource-api.md +++ b/reference/resources/resource-api.md @@ -216,6 +216,21 @@ product.subtractFrom('quantity', 1); product.save(); ``` + + +Only the properties you assign or change are written. Reading a property, including a sub-object or an array element, is not a write: it is not sent to the source's `patch` on a [write-through caching](#write-through-caching) table, and it adds nothing to the audit history. A sub-object or array you change is written as its whole new value. + +A `Date` or `Bytes` (`Buffer` / `Uint8Array`) value must be reassigned to be saved. Changing it in place, such as `product.when.setTime(...)` or `product.bytes[0] = 9`, is not tracked. Build a new value and assign it: + +```javascript +product.when = new Date(product.when.getTime() + 60_000); +const bytes = Buffer.from(product.bytes); +bytes[0] = 9; +product.bytes = bytes; +``` + +On a table that is not [`@sealed`](../database/schema.md#sealed), assigning a property the schema does not declare (`product.extra = 'x'`) saves it, even when nothing else changed. + --- ### `publish(target: RequestTarget | Id, message: object, context?: Resource | Context): Promise` @@ -834,22 +849,24 @@ Cookie-based sessions are intended for browser clients. For non-browser clients ## Resource Instance Methods -A Resource instance is used to update and interact with a single record/resource. It provides functionality for updating properties, accessing property values, and managing record lifecycle. The Resource instance is normally retrieved from the static `update()` method. An instance from a table has updatable properties that can used to access and update individual properties (for properties declared in the table's schema), as well methods for more advanced updates and saving data. For example: +A Resource instance is used to update and interact with a single record/resource. It provides functionality for updating properties, accessing property values, and managing record lifecycle. The Resource instance is normally retrieved from the static `update()` method. An instance from a table has updatable properties that can be used to access and update individual properties, as well as methods for more advanced updates and saving data. For example: ```javascript const product = await Product.update(32); -product.status = 'active'; // we can directly change properties on the updatable record, if they are declared in the schema +product.status = 'active'; // we can directly change properties on the updatable record product.subtractFrom('quantity', 1); // We can use CRDT incrementation/decrementation to safely update the quantity product.save(); ``` +Properties declared in the schema can be assigned directly. On a table that is not `@sealed`, an undeclared property can be assigned too, and is saved. See [`update()`](#updatetarget-requesttarget--id-updates-object-promiseresource) for what a tracked record writes. + ### `save()` This saves the current state of the resource to the database in the current transaction. This method can be called after making changes to the resource to ensure that those changes have been saved to the current transaction and can be queried within the same transaction. Any pending changes are automatically saved when the transaction commits (if `save()` has not already saved them). This method only saves data when using RocksDB storage engine, and is a no-op when using LMDB. -### `addTo(property: string, value: number)` +### `addTo(property: string, value: number | bigint)` Adds `value` to `property` using CRDT incrementation — safe for concurrent updates across threads and nodes. @@ -863,13 +880,21 @@ static async post(target, data) { // GET /MyTable/test?foo=bar → primary key is 'test?foo=bar' ``` -### `subtractFrom(property: string, value: number)` + Calls on the same property in one transaction combine: + +- Two deltas add up. `record.addTo('n', 5); record.addTo('n', 3)` adds 8 as one commutative addition, which still merges with other writers' additions. +- A delta after an assignment adds to the assigned value. `record.n = 100; record.addTo('n', 7)` stores 107. The write is still an assignment, so it replaces a concurrent writer's change instead of merging with it. +- An assignment after `addTo` replaces the pending delta. + +### `subtractFrom(property: string, value: number | bigint)` + +Subtracts `value` from `property` using CRDT incrementation. It combines with other calls in the transaction the same way as [`addTo`](#addtoproperty-string-value-number--bigint). -Subtracts `value` from `property` using CRDT incrementation. + `value` can be a BigInt, like `addTo`: `record.subtractFrom('total', 35n)`. ### `set(property: string, value: any): void` -Sets a property to `value`. Equivalent to direct property assignment (`record.property = value`), but can be used when the property name is dynamic and not declared in the schema. +Sets a property to `value`. Equivalent to direct property assignment (`record.property = value`), but useful when the property name is dynamic. On a table that is not `@sealed`, the property does not need to be declared in the schema. ```javascript const record = await Table.update(target.id); @@ -954,6 +979,7 @@ A few limitations to design around: - `addTo` and `subtractFrom` return no value, and a read-back immediately after a write is not guaranteed to reflect your own committed result. You cannot use them to read "your position" for an exactly-once claim or a hard limit. - `subtractFrom` has no floor; concurrent decrements can drive a value negative. +- Assigning a property and calling `addTo` on it in the same transaction makes the write an assignment, which does not merge with concurrent writers. See [`addTo`](#addtoproperty-string-value-number--bigint). For non-commutative operations — claim-once, hard caps, inventory floors, state-machine transitions — serialize the operation through a single owning process, or use an external lock or coordinator. This matters more in a cluster: a counter replicates and converges eventually, but a decision made on one node's not-yet-converged view can be wrong during the replication window. A rate limiter built this way, for example, can transiently over-admit when requests are spread across nodes. diff --git a/release-notes/v5-lincoln/5.4.md b/release-notes/v5-lincoln/5.4.md index 8216b8f45..37dfa8dd5 100644 --- a/release-notes/v5-lincoln/5.4.md +++ b/release-notes/v5-lincoln/5.4.md @@ -44,6 +44,12 @@ A durable MQTT session now resumes through the same checked replay. When a recon A static `mcpResources` entry can now name a `subscribe` method, so MCP clients can watch a custom URI such as `memory:///{id}` with `resources/subscribe`. The method is called like the entry's read method, as the session's user, and returns an async iterable; each value it yields sends `notifications/resources/updated` with the URI. Harper releases the stream on unsubscribe, when the GET stream closes, on `DELETE /mcp`, and when the session's token expires or its user is dropped. A subscribe that fails now answers a JSON-RPC error instead of an HTTP 500, for row-backed resources too, and a reconnecting GET stream opens without waiting for its subscriptions to be restored. See [Subscribing to custom resources](/reference/v5/mcp/tools-and-resources#subscribing-to-custom-resources). +## Resource API + +### Tracked Records Write Only What You Change + +A record returned by `update()` no longer writes properties you only read: reading a sub-object or an array element sends nothing to a source's `patch` and adds nothing to the audit history. A `Date` or `Bytes` value must now be reassigned to be saved; changing it in place (`record.when.setTime(...)`, `record.bytes[0] = 9`) used to save and no longer does. On a table that is not `@sealed`, assigning an undeclared property saves it even when nothing else changed. Repeated `addTo` / `subtractFrom` calls on one property in a transaction now add up instead of the last one winning, a delta after an assignment adds to the assigned value, and `subtractFrom` accepts a BigInt. See [`update()`](/reference/v5/resources/resource-api#updatetarget-requesttarget--id-updates-object-promiseresource) and [`addTo`](/reference/v5/resources/resource-api#addtoproperty-string-value-number--bigint). + ## Built-in Agent ### Turning Off or Host-Limiting `http_fetch` From d24851f3864b48403d83f80cbc10b1b315097f23 Mon Sep 17 00:00:00 2001 From: Kris Zyp Date: Mon, 26 Oct 2026 18:46:46 -0600 Subject: [PATCH 2/7] Keep the addTo/subtractFrom anchors and give the Date/Bytes break its own release note Dispatch-Task: docs-harper-3586 Co-Authored-By: Claude Opus 5.5 --- reference/resources/resource-api.md | 12 ++++++------ release-notes/v5-lincoln/5.4.md | 10 +++++++++- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/reference/resources/resource-api.md b/reference/resources/resource-api.md index d258a8bc2..35c82e2c8 100644 --- a/reference/resources/resource-api.md +++ b/reference/resources/resource-api.md @@ -218,7 +218,7 @@ product.save(); -Only the properties you assign or change are written. Reading a property, including a sub-object or an array element, is not a write: it is not sent to the source's `patch` on a [write-through caching](#write-through-caching) table, and it adds nothing to the audit history. A sub-object or array you change is written as its whole new value. +Only the properties you assign or change are written. Reading a property, including a sub-object or an array element, is not a write: nothing is saved for it, nothing is added to the audit history, and on a [write-through caching](#write-through-caching) table nothing is sent to the source's `patch`. A sub-object or array you change is written as its whole new value. A `Date` or `Bytes` (`Buffer` / `Uint8Array`) value must be reassigned to be saved. Changing it in place, such as `product.when.setTime(...)` or `product.bytes[0] = 9`, is not tracked. Build a new value and assign it: @@ -853,7 +853,7 @@ A Resource instance is used to update and interact with a single record/resource ```javascript const product = await Product.update(32); -product.status = 'active'; // we can directly change properties on the updatable record +product.status = 'active'; product.subtractFrom('quantity', 1); // We can use CRDT incrementation/decrementation to safely update the quantity product.save(); ``` @@ -866,7 +866,7 @@ This saves the current state of the resource to the database in the current tran This method only saves data when using RocksDB storage engine, and is a no-op when using LMDB. -### `addTo(property: string, value: number | bigint)` +### `addTo(property: string, value: number | bigint)` {#addtoproperty-string-value-number} Adds `value` to `property` using CRDT incrementation — safe for concurrent updates across threads and nodes. @@ -886,9 +886,9 @@ static async post(target, data) { - A delta after an assignment adds to the assigned value. `record.n = 100; record.addTo('n', 7)` stores 107. The write is still an assignment, so it replaces a concurrent writer's change instead of merging with it. - An assignment after `addTo` replaces the pending delta. -### `subtractFrom(property: string, value: number | bigint)` +### `subtractFrom(property: string, value: number | bigint)` {#subtractfromproperty-string-value-number} -Subtracts `value` from `property` using CRDT incrementation. It combines with other calls in the transaction the same way as [`addTo`](#addtoproperty-string-value-number--bigint). +Subtracts `value` from `property` using CRDT incrementation. It combines with other calls in the transaction the same way as [`addTo`](#addtoproperty-string-value-number). `value` can be a BigInt, like `addTo`: `record.subtractFrom('total', 35n)`. @@ -979,7 +979,7 @@ A few limitations to design around: - `addTo` and `subtractFrom` return no value, and a read-back immediately after a write is not guaranteed to reflect your own committed result. You cannot use them to read "your position" for an exactly-once claim or a hard limit. - `subtractFrom` has no floor; concurrent decrements can drive a value negative. -- Assigning a property and calling `addTo` on it in the same transaction makes the write an assignment, which does not merge with concurrent writers. See [`addTo`](#addtoproperty-string-value-number--bigint). +- Assigning a property and calling `addTo` on it in the same transaction makes the write an assignment, which does not merge with concurrent writers. See [`addTo`](#addtoproperty-string-value-number). For non-commutative operations — claim-once, hard caps, inventory floors, state-machine transitions — serialize the operation through a single owning process, or use an external lock or coordinator. This matters more in a cluster: a counter replicates and converges eventually, but a decision made on one node's not-yet-converged view can be wrong during the replication window. A rate limiter built this way, for example, can transiently over-admit when requests are spread across nodes. diff --git a/release-notes/v5-lincoln/5.4.md b/release-notes/v5-lincoln/5.4.md index 37dfa8dd5..2834e8a5a 100644 --- a/release-notes/v5-lincoln/5.4.md +++ b/release-notes/v5-lincoln/5.4.md @@ -46,9 +46,17 @@ A static `mcpResources` entry can now name a `subscribe` method, so MCP clients ## Resource API +### In-Place `Date` and `Bytes` Changes No Longer Save + +On a record returned by `update()`, a `Date` or `Bytes` value must now be reassigned to be saved. Changing it in place (`record.when.setTime(...)`, `record.bytes[0] = 9`) used to save; it no longer does, and no error is raised. Assign a new value instead, such as `record.when = new Date(...)`. See [`update()`](/reference/v5/resources/resource-api#updatetarget-requesttarget--id-updates-object-promiseresource). + ### Tracked Records Write Only What You Change -A record returned by `update()` no longer writes properties you only read: reading a sub-object or an array element sends nothing to a source's `patch` and adds nothing to the audit history. A `Date` or `Bytes` value must now be reassigned to be saved; changing it in place (`record.when.setTime(...)`, `record.bytes[0] = 9`) used to save and no longer does. On a table that is not `@sealed`, assigning an undeclared property saves it even when nothing else changed. Repeated `addTo` / `subtractFrom` calls on one property in a transaction now add up instead of the last one winning, a delta after an assignment adds to the assigned value, and `subtractFrom` accepts a BigInt. See [`update()`](/reference/v5/resources/resource-api#updatetarget-requesttarget--id-updates-object-promiseresource) and [`addTo`](/reference/v5/resources/resource-api#addtoproperty-string-value-number--bigint). +A record returned by `update()` no longer writes properties you only read: reading a sub-object or an array element saves nothing, sends nothing to a source's `patch` and adds nothing to the audit history. On a table that is not `@sealed`, assigning an undeclared property saves it even when nothing else changed. + +### `addTo` and `subtractFrom` Calls Combine + +Repeated `addTo` / `subtractFrom` calls on one property in a transaction now add up instead of the last one winning, and a delta after an assignment adds to the assigned value. `subtractFrom` accepts a BigInt. See [`addTo`](/reference/v5/resources/resource-api#addtoproperty-string-value-number). ## Built-in Agent From 5f9963fccdef551197be9d79fb5c144553188096 Mon Sep 17 00:00:00 2001 From: Kris Zyp Date: Mon, 26 Oct 2026 18:52:38 -0600 Subject: [PATCH 3/7] Say in the 5.4 note that a delta after an assignment stays an assignment Dispatch-Task: docs-harper-3586 Co-Authored-By: Claude Opus 5.5 --- release-notes/v5-lincoln/5.4.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release-notes/v5-lincoln/5.4.md b/release-notes/v5-lincoln/5.4.md index 2834e8a5a..db6533d40 100644 --- a/release-notes/v5-lincoln/5.4.md +++ b/release-notes/v5-lincoln/5.4.md @@ -56,7 +56,7 @@ A record returned by `update()` no longer writes properties you only read: readi ### `addTo` and `subtractFrom` Calls Combine -Repeated `addTo` / `subtractFrom` calls on one property in a transaction now add up instead of the last one winning, and a delta after an assignment adds to the assigned value. `subtractFrom` accepts a BigInt. See [`addTo`](/reference/v5/resources/resource-api#addtoproperty-string-value-number). +Repeated `addTo` / `subtractFrom` calls on one property in a transaction now add up instead of the last one winning, and a delta after an assignment adds to the assigned value (the write is still an assignment, so it does not merge with concurrent writers). `subtractFrom` accepts a BigInt. See [`addTo`](/reference/v5/resources/resource-api#addtoproperty-string-value-number). ## Built-in Agent From 4302552fb7bfeb38d77749aa48d6f27bd6fea08f Mon Sep 17 00:00:00 2001 From: Kris Zyp Date: Mon, 26 Oct 2026 19:03:21 -0600 Subject: [PATCH 4/7] Place the v5.4.0 badge under its own heading and use plain text inline Dispatch-Task: docs-harper-3586 Co-Authored-By: Claude Opus 5.5 --- reference/resources/resource-api.md | 8 +++++--- release-notes/v5-lincoln/5.4.md | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/reference/resources/resource-api.md b/reference/resources/resource-api.md index 35c82e2c8..df5ac7235 100644 --- a/reference/resources/resource-api.md +++ b/reference/resources/resource-api.md @@ -216,6 +216,8 @@ product.subtractFrom('quantity', 1); product.save(); ``` +#### What a tracked record writes + Only the properties you assign or change are written. Reading a property, including a sub-object or an array element, is not a write: nothing is saved for it, nothing is added to the audit history, and on a [write-through caching](#write-through-caching) table nothing is sent to the source's `patch`. A sub-object or array you change is written as its whole new value. @@ -858,7 +860,7 @@ product.subtractFrom('quantity', 1); // We can use CRDT incrementation/decrement product.save(); ``` -Properties declared in the schema can be assigned directly. On a table that is not `@sealed`, an undeclared property can be assigned too, and is saved. See [`update()`](#updatetarget-requesttarget--id-updates-object-promiseresource) for what a tracked record writes. +Properties declared in the schema can be assigned directly. On a table that is not `@sealed`, an undeclared property can be assigned too, and is saved. See [What a tracked record writes](#what-a-tracked-record-writes). ### `save()` @@ -880,7 +882,7 @@ static async post(target, data) { // GET /MyTable/test?foo=bar → primary key is 'test?foo=bar' ``` - Calls on the same property in one transaction combine: +As of v5.4.0, calls on the same property in one transaction combine: - Two deltas add up. `record.addTo('n', 5); record.addTo('n', 3)` adds 8 as one commutative addition, which still merges with other writers' additions. - A delta after an assignment adds to the assigned value. `record.n = 100; record.addTo('n', 7)` stores 107. The write is still an assignment, so it replaces a concurrent writer's change instead of merging with it. @@ -890,7 +892,7 @@ static async post(target, data) { Subtracts `value` from `property` using CRDT incrementation. It combines with other calls in the transaction the same way as [`addTo`](#addtoproperty-string-value-number). - `value` can be a BigInt, like `addTo`: `record.subtractFrom('total', 35n)`. +As of v5.4.0, `value` can be a BigInt, like `addTo`: `record.subtractFrom('total', 35n)`. ### `set(property: string, value: any): void` diff --git a/release-notes/v5-lincoln/5.4.md b/release-notes/v5-lincoln/5.4.md index db6533d40..f12335562 100644 --- a/release-notes/v5-lincoln/5.4.md +++ b/release-notes/v5-lincoln/5.4.md @@ -48,7 +48,7 @@ A static `mcpResources` entry can now name a `subscribe` method, so MCP clients ### In-Place `Date` and `Bytes` Changes No Longer Save -On a record returned by `update()`, a `Date` or `Bytes` value must now be reassigned to be saved. Changing it in place (`record.when.setTime(...)`, `record.bytes[0] = 9`) used to save; it no longer does, and no error is raised. Assign a new value instead, such as `record.when = new Date(...)`. See [`update()`](/reference/v5/resources/resource-api#updatetarget-requesttarget--id-updates-object-promiseresource). +On a record returned by `update()`, a `Date` or `Bytes` value must now be reassigned to be saved. Changing it in place (`record.when.setTime(...)`, `record.bytes[0] = 9`) used to save; it no longer does, and no error is raised. Assign a new value instead, such as `record.when = new Date(...)`. See [What a tracked record writes](/reference/v5/resources/resource-api#what-a-tracked-record-writes). ### Tracked Records Write Only What You Change From bb711bb8375e1ed6eb97717341626fd3f1ce47a1 Mon Sep 17 00:00:00 2001 From: Kris Zyp Date: Mon, 2 Nov 2026 15:48:47 -0700 Subject: [PATCH 5/7] Match the tracked-update docs to what harper#3586 merged An array element edited in place still saves, and a BigInt delta on an Int, Long, Float or Number property converts to a number. Dispatch-Task: pr-maint-cdb9a84b90693592e84fe856a3e8214f Co-Authored-By: Claude Sonnet 5.5 --- reference/resources/resource-api.md | 8 ++++++-- release-notes/v5-lincoln/5.4.md | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/reference/resources/resource-api.md b/reference/resources/resource-api.md index df5ac7235..cddce82b0 100644 --- a/reference/resources/resource-api.md +++ b/reference/resources/resource-api.md @@ -222,7 +222,7 @@ product.save(); Only the properties you assign or change are written. Reading a property, including a sub-object or an array element, is not a write: nothing is saved for it, nothing is added to the audit history, and on a [write-through caching](#write-through-caching) table nothing is sent to the source's `patch`. A sub-object or array you change is written as its whole new value. -A `Date` or `Bytes` (`Buffer` / `Uint8Array`) value must be reassigned to be saved. Changing it in place, such as `product.when.setTime(...)` or `product.bytes[0] = 9`, is not tracked. Build a new value and assign it: +A `Date` or `Bytes` (`Buffer` / `Uint8Array`) property must be reassigned to be saved. Reading one returns a copy, so changing it in place, such as `product.when.setTime(...)` or `product.bytes[0] = 9`, is not saved. Build a new value and assign it: ```javascript product.when = new Date(product.when.getTime() + 60_000); @@ -231,6 +231,8 @@ bytes[0] = 9; product.bytes = bytes; ``` +Elements of an array property are the exception: changing one in place, such as `product.dates[0].setTime(...)`, saves the array. + On a table that is not [`@sealed`](../database/schema.md#sealed), assigning a property the schema does not declare (`product.extra = 'x'`) saves it, even when nothing else changed. --- @@ -888,11 +890,13 @@ As of v5.4.0, calls on the same property in one transaction combine: - A delta after an assignment adds to the assigned value. `record.n = 100; record.addTo('n', 7)` stores 107. The write is still an assignment, so it replaces a concurrent writer's change instead of merging with it. - An assignment after `addTo` replaces the pending delta. +As of v5.4.0, on a property declared `Int`, `Long`, `Float` or `Number`, a BigInt `value` is converted to a number. One beyond 2^53 in magnitude is rejected with a 400 error. + ### `subtractFrom(property: string, value: number | bigint)` {#subtractfromproperty-string-value-number} Subtracts `value` from `property` using CRDT incrementation. It combines with other calls in the transaction the same way as [`addTo`](#addtoproperty-string-value-number). -As of v5.4.0, `value` can be a BigInt, like `addTo`: `record.subtractFrom('total', 35n)`. +As of v5.4.0, `value` can be a BigInt, like `addTo`: `record.subtractFrom('total', 35n)`. The conversion rule for number-typed properties in [`addTo`](#addtoproperty-string-value-number) applies. ### `set(property: string, value: any): void` diff --git a/release-notes/v5-lincoln/5.4.md b/release-notes/v5-lincoln/5.4.md index f12335562..bd1f1d60c 100644 --- a/release-notes/v5-lincoln/5.4.md +++ b/release-notes/v5-lincoln/5.4.md @@ -48,7 +48,7 @@ A static `mcpResources` entry can now name a `subscribe` method, so MCP clients ### In-Place `Date` and `Bytes` Changes No Longer Save -On a record returned by `update()`, a `Date` or `Bytes` value must now be reassigned to be saved. Changing it in place (`record.when.setTime(...)`, `record.bytes[0] = 9`) used to save; it no longer does, and no error is raised. Assign a new value instead, such as `record.when = new Date(...)`. See [What a tracked record writes](/reference/v5/resources/resource-api#what-a-tracked-record-writes). +On a record returned by `update()`, a `Date` or `Bytes` property must now be reassigned to be saved. Changing it in place (`record.when.setTime(...)`, `record.bytes[0] = 9`) used to save; it no longer does, and no error is raised. Assign a new value instead, such as `record.when = new Date(...)`. Elements of an array property still save when changed in place. See [What a tracked record writes](/reference/v5/resources/resource-api#what-a-tracked-record-writes). ### Tracked Records Write Only What You Change @@ -56,7 +56,7 @@ A record returned by `update()` no longer writes properties you only read: readi ### `addTo` and `subtractFrom` Calls Combine -Repeated `addTo` / `subtractFrom` calls on one property in a transaction now add up instead of the last one winning, and a delta after an assignment adds to the assigned value (the write is still an assignment, so it does not merge with concurrent writers). `subtractFrom` accepts a BigInt. See [`addTo`](/reference/v5/resources/resource-api#addtoproperty-string-value-number). +Repeated `addTo` / `subtractFrom` calls on one property in a transaction now add up instead of the last one winning, and a delta after an assignment adds to the assigned value (the write is still an assignment, so it does not merge with concurrent writers). `subtractFrom` accepts a BigInt, and on an `Int`, `Long`, `Float` or `Number` property a BigInt delta is converted to a number (one beyond 2^53 in magnitude is rejected). See [`addTo`](/reference/v5/resources/resource-api#addtoproperty-string-value-number). ## Built-in Agent From 9fa0511494cd872be50e4b55b885f89300e43783 Mon Sep 17 00:00:00 2001 From: Kris Zyp Date: Mon, 2 Nov 2026 15:53:21 -0700 Subject: [PATCH 6/7] Say any non-plain property value must be reassigned, not only Date and Bytes Dispatch-Task: pr-maint-cdb9a84b90693592e84fe856a3e8214f Co-Authored-By: Claude Sonnet 5.5 --- reference/resources/resource-api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reference/resources/resource-api.md b/reference/resources/resource-api.md index cddce82b0..17212e54c 100644 --- a/reference/resources/resource-api.md +++ b/reference/resources/resource-api.md @@ -222,7 +222,7 @@ product.save(); Only the properties you assign or change are written. Reading a property, including a sub-object or an array element, is not a write: nothing is saved for it, nothing is added to the audit history, and on a [write-through caching](#write-through-caching) table nothing is sent to the source's `patch`. A sub-object or array you change is written as its whole new value. -A `Date` or `Bytes` (`Buffer` / `Uint8Array`) property must be reassigned to be saved. Reading one returns a copy, so changing it in place, such as `product.when.setTime(...)` or `product.bytes[0] = 9`, is not saved. Build a new value and assign it: +A `Date`, `Bytes` (`Buffer` / `Uint8Array`), or other value that is not a plain object or array (such as a `Map`) must be reassigned to be saved. Reading a `Date` or `Bytes` property returns a copy, so changing it in place, such as `product.when.setTime(...)` or `product.bytes[0] = 9`, is not saved. Build a new value and assign it: ```javascript product.when = new Date(product.when.getTime() + 60_000); From 99041d7ac32b890b0088fa0c14035a213ca41b33 Mon Sep 17 00:00:00 2001 From: Kris Zyp Date: Mon, 2 Nov 2026 15:54:57 -0700 Subject: [PATCH 7/7] Limit the array-element exception to Date and Bytes elements Dispatch-Task: pr-maint-cdb9a84b90693592e84fe856a3e8214f Co-Authored-By: Claude Sonnet 5.5 --- reference/resources/resource-api.md | 2 +- release-notes/v5-lincoln/5.4.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/reference/resources/resource-api.md b/reference/resources/resource-api.md index 17212e54c..823026e87 100644 --- a/reference/resources/resource-api.md +++ b/reference/resources/resource-api.md @@ -231,7 +231,7 @@ bytes[0] = 9; product.bytes = bytes; ``` -Elements of an array property are the exception: changing one in place, such as `product.dates[0].setTime(...)`, saves the array. +`Date` and `Bytes` elements of an array property are the exception: changing one in place, such as `product.dates[0].setTime(...)`, saves the array. On a table that is not [`@sealed`](../database/schema.md#sealed), assigning a property the schema does not declare (`product.extra = 'x'`) saves it, even when nothing else changed. diff --git a/release-notes/v5-lincoln/5.4.md b/release-notes/v5-lincoln/5.4.md index bd1f1d60c..1ed92dd7c 100644 --- a/release-notes/v5-lincoln/5.4.md +++ b/release-notes/v5-lincoln/5.4.md @@ -48,7 +48,7 @@ A static `mcpResources` entry can now name a `subscribe` method, so MCP clients ### In-Place `Date` and `Bytes` Changes No Longer Save -On a record returned by `update()`, a `Date` or `Bytes` property must now be reassigned to be saved. Changing it in place (`record.when.setTime(...)`, `record.bytes[0] = 9`) used to save; it no longer does, and no error is raised. Assign a new value instead, such as `record.when = new Date(...)`. Elements of an array property still save when changed in place. See [What a tracked record writes](/reference/v5/resources/resource-api#what-a-tracked-record-writes). +On a record returned by `update()`, a `Date` or `Bytes` property must now be reassigned to be saved. Changing it in place (`record.when.setTime(...)`, `record.bytes[0] = 9`) used to save; it no longer does, and no error is raised. Assign a new value instead, such as `record.when = new Date(...)`. `Date` and `Bytes` elements of an array property still save when changed in place. See [What a tracked record writes](/reference/v5/resources/resource-api#what-a-tracked-record-writes). ### Tracked Records Write Only What You Change