Conversation
* Field.derivedFn computes a value from the record's other values, named in the required dependsOn, via a getter on record data - never loaded, parsed or written. * On a CubeField the function also runs on every View row where the field is not aggregated: with an aggregator it derives leaves and rolls up, without one it derives each level from that row's aggregates. A Query including a derived field includes its inputs. * Stores connected to a Cube View are now always projectionOnly - the View sets the flag, and conflicting config throws.
- `Store` and `RowDataGenerator` each hold one `{data, proto}` template, built in a single pass over fields with a per-field `addDerivedGetter`, replacing the shared defaults object that doubled as the dense-record prototype
- Comment the fixpoint loop over level-derived fields in `View.applyDataUpdate`
- Changelog wording
…setup - `Store.parseFields` throws on a `dependsOn` naming an unknown field, covering plain Stores and Cubes alike; `Query.withDependencies` now skips quietly - Inline the single-use derived-getter helper in `RowDataGenerator.buildParentTemplate` - Comment the level-derived `changedFields` pass in `View.applyDataUpdate`
…ates develop's #4723 narrows the leaf diff on a data-only update to the fields named in the producer's changedFields. Derived fields are never stored, so a producer cannot name them - a tick naming only `price` would skip a derived `notional` (qty * price) and leave its SUM stale up the tree. View.withDerivedDependents() closes a set of field names over the derived query fields that read them, transitively. dataOnlyUpdate applies it to the incoming changedFields before diffing leaves, and to the outgoing changed.fields so consumers hear about level-derived values (getters, never diffed) whenever an input moved. Replaces the post-hoc level-derived widening, which after the merge was mutating the producer's own set.
This was referenced Sep 29, 2026
Open
Member
Author
# Conflicts: # CHANGELOG.md
…ers (#4748 items 1, 3, 4) - modifyRecords() throws on a write to a derived field; columns bound to one are never editable. - CubeField throws when a level-derived field sets canAggregateFn. - READMEs: read-by-name, pure/stable-return guidance; replace WeightedAverageAggregator example with the SUM/SUM/ratio decomposition.
- Store.updateData() adds every derived field reading a named input to the changedFields hint, so grids sorted on a derived column re-sort. - Shared withDerivedDependents() util in data/impl; View uses it instead of its own copy.
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.
Adds derived fields: a
Fieldcomputed from the record's other values, read through a getter on recorddataso it is never loaded, parsed or written, and always current with its inputs. One declaration works on a plainStore, the Cube's own store, and a connected store, so apps can share a field dictionary across all three.FieldSpec.derivedFn+ requireddependsOn(may be[]). AdependsOnnaming a field the Store or Cube does not declare throws at construction.Storeinstalls getters on its record-data prototype; raw parsing andmodifyRecordsignore derived fields,getModifiedValuesexcludes them.CubeFieldthe function runs wherever the field is not aggregated. With anaggregatorit derives each leaf and rolls up (market value, notional). Without one it derives every level from that row's aggregates (PnL in bps, margin %, VWAP) - the ratio-of-sums case that previously required a custom aggregator and a full rebuild per tick. Parent rows get a getter rather than a slot. Derived fields may read other derived fields.QuerypullsdependsOnfields in, as it does dimensions. On a data-only tick the View closes the producer'schangedFieldsoverdependsOnbefore diffing leaves (so a tick naming onlypricestill re-sums a derivednotional), and reports level-derived fields as changed to connected stores whenever an input moved.{name: 'priceQty', aggregator: 'SUM', dependsOn: ['price', 'qty'], derivedFn: d => d.price * d.qty}plus{name: 'vwap', dependsOn: ['priceQty', 'qty'], derivedFn: d => d.qty ? d.priceQty / d.qty : null}keeps the View on the incrementaldataOnlypath with built-inSUMs - no custom aggregator, nodependsOnChildrenOnly: false, and no changes to theAggregator/RowUpdateprotocol. Leaf edits viaCube.modifyRecordsAsync()recompute the product automatically, since it is a getter rather than a stored value.projectionOnly- the View sets the flag, andprojectionOnly: falseorprocessRawDataon a connected store throws. This is what lets a shared field definition carry aderivedFnharmlessly into a connected store: projections adopt values and never derive. Pulled forward from Data: calculated fields for Store and Cube View, connected stores via StoreConfig.view (#4620, #4637) #4638.Perf: stores and Views without derived fields are unchanged from develop (benched, dense-record path). Those with them pay ~0.35µs per dense record/parent row created, for a
__proto__-bearing clone that stays in V8 fast-properties mode. Verified with%HasFastPropertieson real rows - the generated-class approach from #4638 produces dictionary-mode parent rows and was not used.v88.0.0 shipped while this PR was open - its CHANGELOG entries now target
89.0.0-SNAPSHOT, with a newdocs/upgrade-notes/v89-upgrade-notes.mdfor the connected-store change.Not in scope: percent-of-total and other result-set-dependent values, which need lazy evaluation (see #4638). Follow-up noted:
'NULL'-aggregated fields still take a parent-row slot where a constant getter would do.Toolbox PR: xh/toolbox#909
Hoist P/R Checklist
developbranch as of last change.breaking-changelabel + CHANGELOG if so.