Releases: reactive/data-client
@data-client/vue@0.18.0
📝 Read the full release announcement
Minor Changes
-
#3931
959465a- Allow oneCollectionschema to be used both top-level and nested.Before:
const getTodos = new Collection([Todo], { argsKey }); const userTodos = new Collection([Todo], { nestKey });
After:
const userTodos = new Collection([Todo], { argsKey, nestKey });
-
#3887
84078d7- BREAKING:Schema.denormalize()is now(input, delegate)instead
of the previous(input, args, unvisit)3-parameter signature.// before denormalize(input, args, unvisit) { return unvisit(this.schema, input); } // after denormalize(input, delegate) { return delegate.unvisit(this.schema, input); }
The new
IDenormalizeDelegate
exposesunvisit,args, and a newargsKey(fn)helper that registers
a memoization dimension when output varies with endpoint args. Reading
delegate.argsdirectly does not contribute to cache invalidation —
schemas that branch on args must callargsKey. Thefnreference
doubles as the cache path key, so it must be referentially stable
— define it on the instance or at module scope, not inline per call:class LensSchema { constructor({ lens }) { this.lensSelector = lens; // stable reference across calls } denormalize(input, delegate) { const portfolio = delegate.argsKey(this.lensSelector); return this.lookup(input, portfolio); } }
All built-in schemas (
Array,Object,Values,Union,Query,
Invalidate,Lazy,Collection) have been updated. Custom schemas
implementingSchemaSimplemust update theirdenormalizesignature.Schema.normalize()and thevisit()callback also gain an optional
trailingparentEntityargument tracking the nearest enclosing
entity-like schema. This is additive — existing schemas don't need
changes unless they want to use it. -
#3887
84078d7- Add Scalar schema for lens-dependent entity fields.Scalarmodels entity fields whose values vary by a runtime "lens" (such as the
selected portfolio, currency, or locale). Multiple components can render the
same entity through different lenses simultaneously — each sees the correct
values without the entity itself ever being mutated. Lens-dependent values are
stored in a separate cell table and joined at denormalize time from endpoint
args.New exports:
Scalar,schema.Scalar.A single
Scalarinstance can serve both as anEntity.schemafield (parent
entity inferred from the visit) and standalone — insideValues(Scalar),
[Scalar], orCollection([Scalar])— for cheap column-only refreshes
(entity bound explicitly viaentity). Cell pks are derived from the map key
or viaScalar.entityPk(), which defaults toEntity.pk()so custom and
composite primary keys work with no override:import { Collection, Entity, RestEndpoint, Scalar } from '@data-client/rest'; class Company extends Entity { id = ''; price = 0; pct_equity = 0; shares = 0; } const PortfolioScalar = new Scalar({ lens: args => args[0]?.portfolio, key: 'portfolio', entity: Company, }); Company.schema = { pct_equity: PortfolioScalar, shares: PortfolioScalar, }; // Full load — Company rows + scalar cells for the current portfolio export const getCompanies = new RestEndpoint({ path: '/companies', searchParams: {} as { portfolio: string }, schema: new Collection([Company], { argsKey: () => ({}) }), }); // Lens-only refresh — writes to the same Scalar(portfolio) cell table export const getPortfolioColumns = new RestEndpoint({ path: '/companies/columns', searchParams: {} as { portfolio: string }, schema: new Collection([PortfolioScalar], { argsKey: ({ portfolio }) => ({ portfolio }), }), });
useSuspense(getCompanies, { portfolio: 'A' })and
useSuspense(getCompanies, { portfolio: 'B' })resolve to different
pct_equity/shareswhile sharing the sameCompanyrow.Scalar.queryKeyenumerates cells in its table for the current lens, so
endpoints that useScalardirectly as their top-level schema reconstruct
from cache without a network round-trip once the cells are present.
Patch Changes
@data-client/test@0.18.0
📝 Read the full release announcement
Patch Changes
89e06d3- Bump@data-client/reactpeer dependency range to include^0.18.0.
@data-client/rest@0.18.0
📝 Read the full release announcement
Minor Changes
-
#3931
959465a- Allow oneCollectionschema to be used both top-level and nested.Before:
const getTodos = new Collection([Todo], { argsKey }); const userTodos = new Collection([Todo], { nestKey });
After:
const userTodos = new Collection([Todo], { argsKey, nestKey });
-
#3887
84078d7- BREAKING:Schema.denormalize()is now(input, delegate)instead
of the previous(input, args, unvisit)3-parameter signature.// before denormalize(input, args, unvisit) { return unvisit(this.schema, input); } // after denormalize(input, delegate) { return delegate.unvisit(this.schema, input); }
The new
IDenormalizeDelegate
exposesunvisit,args, and a newargsKey(fn)helper that registers
a memoization dimension when output varies with endpoint args. Reading
delegate.argsdirectly does not contribute to cache invalidation —
schemas that branch on args must callargsKey. Thefnreference
doubles as the cache path key, so it must be referentially stable
— define it on the instance or at module scope, not inline per call:class LensSchema { constructor({ lens }) { this.lensSelector = lens; // stable reference across calls } denormalize(input, delegate) { const portfolio = delegate.argsKey(this.lensSelector); return this.lookup(input, portfolio); } }
All built-in schemas (
Array,Object,Values,Union,Query,
Invalidate,Lazy,Collection) have been updated. Custom schemas
implementingSchemaSimplemust update theirdenormalizesignature.Schema.normalize()and thevisit()callback also gain an optional
trailingparentEntityargument tracking the nearest enclosing
entity-like schema. This is additive — existing schemas don't need
changes unless they want to use it. -
#3887
84078d7- Add Scalar schema for lens-dependent entity fields.Scalarmodels entity fields whose values vary by a runtime "lens" (such as the
selected portfolio, currency, or locale). Multiple components can render the
same entity through different lenses simultaneously — each sees the correct
values without the entity itself ever being mutated. Lens-dependent values are
stored in a separate cell table and joined at denormalize time from endpoint
args.New exports:
Scalar,schema.Scalar.A single
Scalarinstance can serve both as anEntity.schemafield (parent
entity inferred from the visit) and standalone — insideValues(Scalar),
[Scalar], orCollection([Scalar])— for cheap column-only refreshes
(entity bound explicitly viaentity). Cell pks are derived from the map key
or viaScalar.entityPk(), which defaults toEntity.pk()so custom and
composite primary keys work with no override:import { Collection, Entity, RestEndpoint, Scalar } from '@data-client/rest'; class Company extends Entity { id = ''; price = 0; pct_equity = 0; shares = 0; } const PortfolioScalar = new Scalar({ lens: args => args[0]?.portfolio, key: 'portfolio', entity: Company, }); Company.schema = { pct_equity: PortfolioScalar, shares: PortfolioScalar, }; // Full load — Company rows + scalar cells for the current portfolio export const getCompanies = new RestEndpoint({ path: '/companies', searchParams: {} as { portfolio: string }, schema: new Collection([Company], { argsKey: () => ({}) }), }); // Lens-only refresh — writes to the same Scalar(portfolio) cell table export const getPortfolioColumns = new RestEndpoint({ path: '/companies/columns', searchParams: {} as { portfolio: string }, schema: new Collection([PortfolioScalar], { argsKey: ({ portfolio }) => ({ portfolio }), }), });
useSuspense(getCompanies, { portfolio: 'A' })and
useSuspense(getCompanies, { portfolio: 'B' })resolve to different
pct_equity/shareswhile sharing the sameCompanyrow.Scalar.queryKeyenumerates cells in its table for the current lens, so
endpoints that useScalardirectly as their top-level schema reconstruct
from cache without a network round-trip once the cells are present.
Patch Changes
-
#3925
6e8e499- Fix cached journey being mutated on repeated result-cache hits.GlobalCache.getResultscalledpaths.shift()on a cache hit, mutating
thejourneyarray stored by reference on theWeakDependencyMapLink
node. After the first hit stripped the placeholder input slot, every
subsequent hit on the same cached entry would shift off a real
EntityPath, progressively losing subscription entries. This could cause
missedcountReftracking (premature GC of still-referenced entities)
and incorrectentityExpiresAtcalculations. The hit path now returns a
non-mutating copy. -
Updated dependencies [
959465a,84078d7,6e8e499,396d163,84078d7]:- @data-client/endpoint@0.18.0
@data-client/react@0.18.0
📝 Read the full release announcement
Minor Changes
-
#3931
959465a- Allow oneCollectionschema to be used both top-level and nested.Before:
const getTodos = new Collection([Todo], { argsKey }); const userTodos = new Collection([Todo], { nestKey });
After:
const userTodos = new Collection([Todo], { argsKey, nestKey });
-
#3887
84078d7- BREAKING:Schema.denormalize()is now(input, delegate)instead
of the previous(input, args, unvisit)3-parameter signature.// before denormalize(input, args, unvisit) { return unvisit(this.schema, input); } // after denormalize(input, delegate) { return delegate.unvisit(this.schema, input); }
The new
IDenormalizeDelegate
exposesunvisit,args, and a newargsKey(fn)helper that registers
a memoization dimension when output varies with endpoint args. Reading
delegate.argsdirectly does not contribute to cache invalidation —
schemas that branch on args must callargsKey. Thefnreference
doubles as the cache path key, so it must be referentially stable
— define it on the instance or at module scope, not inline per call:class LensSchema { constructor({ lens }) { this.lensSelector = lens; // stable reference across calls } denormalize(input, delegate) { const portfolio = delegate.argsKey(this.lensSelector); return this.lookup(input, portfolio); } }
All built-in schemas (
Array,Object,Values,Union,Query,
Invalidate,Lazy,Collection) have been updated. Custom schemas
implementingSchemaSimplemust update theirdenormalizesignature.Schema.normalize()and thevisit()callback also gain an optional
trailingparentEntityargument tracking the nearest enclosing
entity-like schema. This is additive — existing schemas don't need
changes unless they want to use it. -
#3887
84078d7- Add Scalar schema for lens-dependent entity fields.Scalarmodels entity fields whose values vary by a runtime "lens" (such as the
selected portfolio, currency, or locale). Multiple components can render the
same entity through different lenses simultaneously — each sees the correct
values without the entity itself ever being mutated. Lens-dependent values are
stored in a separate cell table and joined at denormalize time from endpoint
args.New exports:
Scalar,schema.Scalar.A single
Scalarinstance can serve both as anEntity.schemafield (parent
entity inferred from the visit) and standalone — insideValues(Scalar),
[Scalar], orCollection([Scalar])— for cheap column-only refreshes
(entity bound explicitly viaentity). Cell pks are derived from the map key
or viaScalar.entityPk(), which defaults toEntity.pk()so custom and
composite primary keys work with no override:import { Collection, Entity, RestEndpoint, Scalar } from '@data-client/rest'; class Company extends Entity { id = ''; price = 0; pct_equity = 0; shares = 0; } const PortfolioScalar = new Scalar({ lens: args => args[0]?.portfolio, key: 'portfolio', entity: Company, }); Company.schema = { pct_equity: PortfolioScalar, shares: PortfolioScalar, }; // Full load — Company rows + scalar cells for the current portfolio export const getCompanies = new RestEndpoint({ path: '/companies', searchParams: {} as { portfolio: string }, schema: new Collection([Company], { argsKey: () => ({}) }), }); // Lens-only refresh — writes to the same Scalar(portfolio) cell table export const getPortfolioColumns = new RestEndpoint({ path: '/companies/columns', searchParams: {} as { portfolio: string }, schema: new Collection([PortfolioScalar], { argsKey: ({ portfolio }) => ({ portfolio }), }), });
useSuspense(getCompanies, { portfolio: 'A' })and
useSuspense(getCompanies, { portfolio: 'B' })resolve to different
pct_equity/shareswhile sharing the sameCompanyrow.Scalar.queryKeyenumerates cells in its table for the current lens, so
endpoints that useScalardirectly as their top-level schema reconstruct
from cache without a network round-trip once the cells are present.
Patch Changes
-
#3925
6e8e499- Fix cached journey being mutated on repeated result-cache hits.GlobalCache.getResultscalledpaths.shift()on a cache hit, mutating
thejourneyarray stored by reference on theWeakDependencyMapLink
node. After the first hit stripped the placeholder input slot, every
subsequent hit on the same cached entry would shift off a real
EntityPath, progressively losing subscription entries. This could cause
missedcountReftracking (premature GC of still-referenced entities)
and incorrectentityExpiresAtcalculations. The hit path now returns a
non-mutating copy. -
Updated dependencies [
959465a,84078d7,6e8e499,84078d7]:- @data-client/core@0.18.0
@data-client/normalizr@0.18.0
📝 Read the full release announcement
Minor Changes
-
#3931
959465a- Allow oneCollectionschema to be used both top-level and nested.Before:
const getTodos = new Collection([Todo], { argsKey }); const userTodos = new Collection([Todo], { nestKey });
After:
const userTodos = new Collection([Todo], { argsKey, nestKey });
-
#3887
84078d7- BREAKING:Schema.denormalize()is now(input, delegate)instead
of the previous(input, args, unvisit)3-parameter signature.// before denormalize(input, args, unvisit) { return unvisit(this.schema, input); } // after denormalize(input, delegate) { return delegate.unvisit(this.schema, input); }
The new
IDenormalizeDelegate
exposesunvisit,args, and a newargsKey(fn)helper that registers
a memoization dimension when output varies with endpoint args. Reading
delegate.argsdirectly does not contribute to cache invalidation —
schemas that branch on args must callargsKey. Thefnreference
doubles as the cache path key, so it must be referentially stable
— define it on the instance or at module scope, not inline per call:class LensSchema { constructor({ lens }) { this.lensSelector = lens; // stable reference across calls } denormalize(input, delegate) { const portfolio = delegate.argsKey(this.lensSelector); return this.lookup(input, portfolio); } }
All built-in schemas (
Array,Object,Values,Union,Query,
Invalidate,Lazy,Collection) have been updated. Custom schemas
implementingSchemaSimplemust update theirdenormalizesignature.Schema.normalize()and thevisit()callback also gain an optional
trailingparentEntityargument tracking the nearest enclosing
entity-like schema. This is additive — existing schemas don't need
changes unless they want to use it. -
#3934
396d163- Move normalizeargsand recursivevisitinto the existing normalize delegate passed to schemas.
CustomSchema.normalize()implementations should migrate from
normalize(input, parent, key, args, visit, delegate, parentEntity?)to
normalize(input, parent, key, delegate, parentEntity?), then read
delegate.argsand calldelegate.visit()for recursive normalization.Before:
class WrapperSchema { normalize(input, parent, key, args, visit, delegate) { const normalized = visit(this.schema, input.value, input, 'value', args); delegate.mergeEntity(this, this.pk(input, parent, key, args), normalized); return normalized; } }
After:
class WrapperSchema { normalize(input, parent, key, delegate) { const { args, visit } = delegate; const normalized = visit(this.schema, input.value, input, 'value'); delegate.mergeEntity(this, this.pk(input, parent, key, args), normalized); return normalized; } }
-
#3887
84078d7- Add Scalar schema for lens-dependent entity fields.Scalarmodels entity fields whose values vary by a runtime "lens" (such as the
selected portfolio, currency, or locale). Multiple components can render the
same entity through different lenses simultaneously — each sees the correct
values without the entity itself ever being mutated. Lens-dependent values are
stored in a separate cell table and joined at denormalize time from endpoint
args.New exports:
Scalar,schema.Scalar.A single
Scalarinstance can serve both as anEntity.schemafield (parent
entity inferred from the visit) and standalone — insideValues(Scalar),
[Scalar], orCollection([Scalar])— for cheap column-only refreshes
(entity bound explicitly viaentity). Cell pks are derived from the map key
or viaScalar.entityPk(), which defaults toEntity.pk()so custom and
composite primary keys work with no override:import { Collection, Entity, RestEndpoint, Scalar } from '@data-client/rest'; class Company extends Entity { id = ''; price = 0; pct_equity = 0; shares = 0; } const PortfolioScalar = new Scalar({ lens: args => args[0]?.portfolio, key: 'portfolio', entity: Company, }); Company.schema = { pct_equity: PortfolioScalar, shares: PortfolioScalar, }; // Full load — Company rows + scalar cells for the current portfolio export const getCompanies = new RestEndpoint({ path: '/companies', searchParams: {} as { portfolio: string }, schema: new Collection([Company], { argsKey: () => ({}) }), }); // Lens-only refresh — writes to the same Scalar(portfolio) cell table export const getPortfolioColumns = new RestEndpoint({ path: '/companies/columns', searchParams: {} as { portfolio: string }, schema: new Collection([PortfolioScalar], { argsKey: ({ portfolio }) => ({ portfolio }), }), });
useSuspense(getCompanies, { portfolio: 'A' })and
useSuspense(getCompanies, { portfolio: 'B' })resolve to different
pct_equity/shareswhile sharing the sameCompanyrow.Scalar.queryKeyenumerates cells in its table for the current lens, so
endpoints that useScalardirectly as their top-level schema reconstruct
from cache without a network round-trip once the cells are present.
Patch Changes
-
#3925
6e8e499- Fix cached journey being mutated on repeated result-cache hits.GlobalCache.getResultscalledpaths.shift()on a cache hit, mutating
thejourneyarray stored by reference on theWeakDependencyMapLink
node. After the first hit stripped the placeholder input slot, every
subsequent hit on the same cached entry would shift off a real
EntityPath, progressively losing subscription entries. This could cause
missedcountReftracking (premature GC of still-referenced entities)
and incorrectentityExpiresAtcalculations. The hit path now returns a
non-mutating copy.
@data-client/img@0.18.0
@data-client/graphql@0.18.0
📝 Read the full release announcement
Minor Changes
-
#3931
959465a- Allow oneCollectionschema to be used both top-level and nested.Before:
const getTodos = new Collection([Todo], { argsKey }); const userTodos = new Collection([Todo], { nestKey });
After:
const userTodos = new Collection([Todo], { argsKey, nestKey });
-
#3887
84078d7- BREAKING:Schema.denormalize()is now(input, delegate)instead
of the previous(input, args, unvisit)3-parameter signature.// before denormalize(input, args, unvisit) { return unvisit(this.schema, input); } // after denormalize(input, delegate) { return delegate.unvisit(this.schema, input); }
The new
IDenormalizeDelegate
exposesunvisit,args, and a newargsKey(fn)helper that registers
a memoization dimension when output varies with endpoint args. Reading
delegate.argsdirectly does not contribute to cache invalidation —
schemas that branch on args must callargsKey. Thefnreference
doubles as the cache path key, so it must be referentially stable
— define it on the instance or at module scope, not inline per call:class LensSchema { constructor({ lens }) { this.lensSelector = lens; // stable reference across calls } denormalize(input, delegate) { const portfolio = delegate.argsKey(this.lensSelector); return this.lookup(input, portfolio); } }
All built-in schemas (
Array,Object,Values,Union,Query,
Invalidate,Lazy,Collection) have been updated. Custom schemas
implementingSchemaSimplemust update theirdenormalizesignature.Schema.normalize()and thevisit()callback also gain an optional
trailingparentEntityargument tracking the nearest enclosing
entity-like schema. This is additive — existing schemas don't need
changes unless they want to use it. -
#3887
84078d7- Add Scalar schema for lens-dependent entity fields.Scalarmodels entity fields whose values vary by a runtime "lens" (such as the
selected portfolio, currency, or locale). Multiple components can render the
same entity through different lenses simultaneously — each sees the correct
values without the entity itself ever being mutated. Lens-dependent values are
stored in a separate cell table and joined at denormalize time from endpoint
args.New exports:
Scalar,schema.Scalar.A single
Scalarinstance can serve both as anEntity.schemafield (parent
entity inferred from the visit) and standalone — insideValues(Scalar),
[Scalar], orCollection([Scalar])— for cheap column-only refreshes
(entity bound explicitly viaentity). Cell pks are derived from the map key
or viaScalar.entityPk(), which defaults toEntity.pk()so custom and
composite primary keys work with no override:import { Collection, Entity, RestEndpoint, Scalar } from '@data-client/rest'; class Company extends Entity { id = ''; price = 0; pct_equity = 0; shares = 0; } const PortfolioScalar = new Scalar({ lens: args => args[0]?.portfolio, key: 'portfolio', entity: Company, }); Company.schema = { pct_equity: PortfolioScalar, shares: PortfolioScalar, }; // Full load — Company rows + scalar cells for the current portfolio export const getCompanies = new RestEndpoint({ path: '/companies', searchParams: {} as { portfolio: string }, schema: new Collection([Company], { argsKey: () => ({}) }), }); // Lens-only refresh — writes to the same Scalar(portfolio) cell table export const getPortfolioColumns = new RestEndpoint({ path: '/companies/columns', searchParams: {} as { portfolio: string }, schema: new Collection([PortfolioScalar], { argsKey: ({ portfolio }) => ({ portfolio }), }), });
useSuspense(getCompanies, { portfolio: 'A' })and
useSuspense(getCompanies, { portfolio: 'B' })resolve to different
pct_equity/shareswhile sharing the sameCompanyrow.Scalar.queryKeyenumerates cells in its table for the current lens, so
endpoints that useScalardirectly as their top-level schema reconstruct
from cache without a network round-trip once the cells are present.
Patch Changes
-
#3925
6e8e499- Fix cached journey being mutated on repeated result-cache hits.GlobalCache.getResultscalledpaths.shift()on a cache hit, mutating
thejourneyarray stored by reference on theWeakDependencyMapLink
node. After the first hit stripped the placeholder input slot, every
subsequent hit on the same cached entry would shift off a real
EntityPath, progressively losing subscription entries. This could cause
missedcountReftracking (premature GC of still-referenced entities)
and incorrectentityExpiresAtcalculations. The hit path now returns a
non-mutating copy. -
Updated dependencies [
959465a,84078d7,6e8e499,396d163,84078d7]:- @data-client/endpoint@0.18.0
@data-client/endpoint@0.18.0
📝 Read the full release announcement
Minor Changes
-
#3931
959465a- Allow oneCollectionschema to be used both top-level and nested.Before:
const getTodos = new Collection([Todo], { argsKey }); const userTodos = new Collection([Todo], { nestKey });
After:
const userTodos = new Collection([Todo], { argsKey, nestKey });
-
#3887
84078d7- BREAKING:Schema.denormalize()is now(input, delegate)instead
of the previous(input, args, unvisit)3-parameter signature.// before denormalize(input, args, unvisit) { return unvisit(this.schema, input); } // after denormalize(input, delegate) { return delegate.unvisit(this.schema, input); }
The new
IDenormalizeDelegate
exposesunvisit,args, and a newargsKey(fn)helper that registers
a memoization dimension when output varies with endpoint args. Reading
delegate.argsdirectly does not contribute to cache invalidation —
schemas that branch on args must callargsKey. Thefnreference
doubles as the cache path key, so it must be referentially stable
— define it on the instance or at module scope, not inline per call:class LensSchema { constructor({ lens }) { this.lensSelector = lens; // stable reference across calls } denormalize(input, delegate) { const portfolio = delegate.argsKey(this.lensSelector); return this.lookup(input, portfolio); } }
All built-in schemas (
Array,Object,Values,Union,Query,
Invalidate,Lazy,Collection) have been updated. Custom schemas
implementingSchemaSimplemust update theirdenormalizesignature.Schema.normalize()and thevisit()callback also gain an optional
trailingparentEntityargument tracking the nearest enclosing
entity-like schema. This is additive — existing schemas don't need
changes unless they want to use it. -
#3934
396d163- Move normalizeargsand recursivevisitinto the existing normalize delegate passed to schemas.
CustomSchema.normalize()implementations should migrate from
normalize(input, parent, key, args, visit, delegate, parentEntity?)to
normalize(input, parent, key, delegate, parentEntity?), then read
delegate.argsand calldelegate.visit()for recursive normalization.Before:
class WrapperSchema { normalize(input, parent, key, args, visit, delegate) { const normalized = visit(this.schema, input.value, input, 'value', args); delegate.mergeEntity(this, this.pk(input, parent, key, args), normalized); return normalized; } }
After:
class WrapperSchema { normalize(input, parent, key, delegate) { const { args, visit } = delegate; const normalized = visit(this.schema, input.value, input, 'value'); delegate.mergeEntity(this, this.pk(input, parent, key, args), normalized); return normalized; } }
-
#3887
84078d7- Add Scalar schema for lens-dependent entity fields.Scalarmodels entity fields whose values vary by a runtime "lens" (such as the
selected portfolio, currency, or locale). Multiple components can render the
same entity through different lenses simultaneously — each sees the correct
values without the entity itself ever being mutated. Lens-dependent values are
stored in a separate cell table and joined at denormalize time from endpoint
args.New exports:
Scalar,schema.Scalar.A single
Scalarinstance can serve both as anEntity.schemafield (parent
entity inferred from the visit) and standalone — insideValues(Scalar),
[Scalar], orCollection([Scalar])— for cheap column-only refreshes
(entity bound explicitly viaentity). Cell pks are derived from the map key
or viaScalar.entityPk(), which defaults toEntity.pk()so custom and
composite primary keys work with no override:import { Collection, Entity, RestEndpoint, Scalar } from '@data-client/rest'; class Company extends Entity { id = ''; price = 0; pct_equity = 0; shares = 0; } const PortfolioScalar = new Scalar({ lens: args => args[0]?.portfolio, key: 'portfolio', entity: Company, }); Company.schema = { pct_equity: PortfolioScalar, shares: PortfolioScalar, }; // Full load — Company rows + scalar cells for the current portfolio export const getCompanies = new RestEndpoint({ path: '/companies', searchParams: {} as { portfolio: string }, schema: new Collection([Company], { argsKey: () => ({}) }), }); // Lens-only refresh — writes to the same Scalar(portfolio) cell table export const getPortfolioColumns = new RestEndpoint({ path: '/companies/columns', searchParams: {} as { portfolio: string }, schema: new Collection([PortfolioScalar], { argsKey: ({ portfolio }) => ({ portfolio }), }), });
useSuspense(getCompanies, { portfolio: 'A' })and
useSuspense(getCompanies, { portfolio: 'B' })resolve to different
pct_equity/shareswhile sharing the sameCompanyrow.Scalar.queryKeyenumerates cells in its table for the current lens, so
endpoints that useScalardirectly as their top-level schema reconstruct
from cache without a network round-trip once the cells are present.
Patch Changes
-
#3925
6e8e499- Fix cached journey being mutated on repeated result-cache hits.GlobalCache.getResultscalledpaths.shift()on a cache hit, mutating
thejourneyarray stored by reference on theWeakDependencyMapLink
node. After the first hit stripped the placeholder input slot, every
subsequent hit on the same cached entry would shift off a real
EntityPath, progressively losing subscription entries. This could cause
missedcountReftracking (premature GC of still-referenced entities)
and incorrectentityExpiresAtcalculations. The hit path now returns a
non-mutating copy.
@data-client/core@0.18.0
📝 Read the full release announcement
Minor Changes
-
#3931
959465a- Allow oneCollectionschema to be used both top-level and nested.Before:
const getTodos = new Collection([Todo], { argsKey }); const userTodos = new Collection([Todo], { nestKey });
After:
const userTodos = new Collection([Todo], { argsKey, nestKey });
-
#3887
84078d7- BREAKING:Schema.denormalize()is now(input, delegate)instead
of the previous(input, args, unvisit)3-parameter signature.// before denormalize(input, args, unvisit) { return unvisit(this.schema, input); } // after denormalize(input, delegate) { return delegate.unvisit(this.schema, input); }
The new
IDenormalizeDelegate
exposesunvisit,args, and a newargsKey(fn)helper that registers
a memoization dimension when output varies with endpoint args. Reading
delegate.argsdirectly does not contribute to cache invalidation —
schemas that branch on args must callargsKey. Thefnreference
doubles as the cache path key, so it must be referentially stable
— define it on the instance or at module scope, not inline per call:class LensSchema { constructor({ lens }) { this.lensSelector = lens; // stable reference across calls } denormalize(input, delegate) { const portfolio = delegate.argsKey(this.lensSelector); return this.lookup(input, portfolio); } }
All built-in schemas (
Array,Object,Values,Union,Query,
Invalidate,Lazy,Collection) have been updated. Custom schemas
implementingSchemaSimplemust update theirdenormalizesignature.Schema.normalize()and thevisit()callback also gain an optional
trailingparentEntityargument tracking the nearest enclosing
entity-like schema. This is additive — existing schemas don't need
changes unless they want to use it. -
#3887
84078d7- Add Scalar schema for lens-dependent entity fields.Scalarmodels entity fields whose values vary by a runtime "lens" (such as the
selected portfolio, currency, or locale). Multiple components can render the
same entity through different lenses simultaneously — each sees the correct
values without the entity itself ever being mutated. Lens-dependent values are
stored in a separate cell table and joined at denormalize time from endpoint
args.New exports:
Scalar,schema.Scalar.A single
Scalarinstance can serve both as anEntity.schemafield (parent
entity inferred from the visit) and standalone — insideValues(Scalar),
[Scalar], orCollection([Scalar])— for cheap column-only refreshes
(entity bound explicitly viaentity). Cell pks are derived from the map key
or viaScalar.entityPk(), which defaults toEntity.pk()so custom and
composite primary keys work with no override:import { Collection, Entity, RestEndpoint, Scalar } from '@data-client/rest'; class Company extends Entity { id = ''; price = 0; pct_equity = 0; shares = 0; } const PortfolioScalar = new Scalar({ lens: args => args[0]?.portfolio, key: 'portfolio', entity: Company, }); Company.schema = { pct_equity: PortfolioScalar, shares: PortfolioScalar, }; // Full load — Company rows + scalar cells for the current portfolio export const getCompanies = new RestEndpoint({ path: '/companies', searchParams: {} as { portfolio: string }, schema: new Collection([Company], { argsKey: () => ({}) }), }); // Lens-only refresh — writes to the same Scalar(portfolio) cell table export const getPortfolioColumns = new RestEndpoint({ path: '/companies/columns', searchParams: {} as { portfolio: string }, schema: new Collection([PortfolioScalar], { argsKey: ({ portfolio }) => ({ portfolio }), }), });
useSuspense(getCompanies, { portfolio: 'A' })and
useSuspense(getCompanies, { portfolio: 'B' })resolve to different
pct_equity/shareswhile sharing the sameCompanyrow.Scalar.queryKeyenumerates cells in its table for the current lens, so
endpoints that useScalardirectly as their top-level schema reconstruct
from cache without a network round-trip once the cells are present.
Patch Changes
-
#3925
6e8e499- Fix cached journey being mutated on repeated result-cache hits.GlobalCache.getResultscalledpaths.shift()on a cache hit, mutating
thejourneyarray stored by reference on theWeakDependencyMapLink
node. After the first hit stripped the placeholder input slot, every
subsequent hit on the same cached entry would shift off a real
EntityPath, progressively losing subscription entries. This could cause
missedcountReftracking (premature GC of still-referenced entities)
and incorrectentityExpiresAtcalculations. The hit path now returns a
non-mutating copy. -
Updated dependencies [
959465a,84078d7,6e8e499,396d163,84078d7]:- @data-client/normalizr@0.18.0
@data-client/vue@0.16.1
📝 Read the full release announcement
Patch Changes
-
fd64b41- Include@data-client/normalizr@0.16.6performance improvements:-
#3875
467a5f6- Fix deepClone to only copy own propertiesdeepClonein the immutable store path now usesObject.keys()instead offor...in, preventing inherited properties from being copied into cloned state. -
#3877
e9e96f1- Replace megamorphic computed dispatch in getDependency with switchgetDependencyuseddelegate[array[index]](...spread)which creates a temporary array, a computed property lookup, and a spread call on every invocation — a megamorphic pattern that prevents V8 from inlining or type-specializing the call site. Replaced with aswitchonpath.lengthfor monomorphic dispatch. -
#3876
7d28629- Improve denormalization performance by pre-allocating the dependency tracking slotReplace
Array.prototype.unshift()inGlobalCache.getResults()with a pre-allocated slot at index 0, avoiding O(n) element shifting on every cache-miss denormalization. -
#3884
7df6a49- Move entity table POJO clone from getNewEntities to setEntityLazy-clone entity and meta tables on first write per entity type instead of eagerly in getNewEntities. This keeps getNewEntities as a pure Map operation, eliminating its V8 Maglev bailout ("Insufficient type feedback for generic named access" on
this.entities). -
#3878
98a7831- Avoid hidden class mutation in normalize() return objectThe normalize result object was constructed with
result: '' as anythen mutated viaret.result = visit(...), causing a V8 hidden class transition when the property type changed from string to the actual result type. Restructured to compute the result first and construct the final object in a single step.
-
-
Updated dependencies [
fd64b41]:- @data-client/core@0.16.7