Skip to content

Version Packages#82

Open
github-actions[bot] wants to merge 1 commit intomasterfrom
changeset-release/master
Open

Version Packages#82
github-actions[bot] wants to merge 1 commit intomasterfrom
changeset-release/master

Conversation

@github-actions
Copy link
Copy Markdown

@github-actions github-actions Bot commented Mar 21, 2026

This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to master, this PR will be updated.

Releases

@data-client/core@0.18.0

Minor Changes

  • #3931 959465a - Allow one Collection schema 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
    exposes unvisit, args, and a new argsKey(fn) helper that registers
    a memoization dimension when output varies with endpoint args. Reading
    delegate.args directly does not contribute to cache invalidation —
    schemas that branch on args must call argsKey. The fn reference
    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
    implementing SchemaSimple must update their denormalize signature.

    Schema.normalize() and the visit() callback also gain an optional
    trailing parentEntity argument 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.

    Scalar models 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 Scalar instance can serve both as an Entity.schema field (parent
    entity inferred from the visit) and standalone — inside Values(Scalar),
    [Scalar], or Collection([Scalar]) — for cheap column-only refreshes
    (entity bound explicitly via entity). Cell pks are derived from the map key
    or via Scalar.entityPk(), which defaults to Entity.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 / shares while sharing the same Company row.

    Scalar.queryKey enumerates cells in its table for the current lens, so
    endpoints that use Scalar directly 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.getResults called paths.shift() on a cache hit, mutating
    the journey array stored by reference on the WeakDependencyMap Link
    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
    missed countRef tracking (premature GC of still-referenced entities)
    and incorrect entityExpiresAt calculations. The hit path now returns a
    non-mutating copy.

  • Updated dependencies [959465a, 84078d7, 6e8e499, 396d163, 84078d7]:

    • @data-client/normalizr@0.18.0

@data-client/endpoint@0.18.0

Minor Changes

  • #3931 959465a - Allow one Collection schema 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
    exposes unvisit, args, and a new argsKey(fn) helper that registers
    a memoization dimension when output varies with endpoint args. Reading
    delegate.args directly does not contribute to cache invalidation —
    schemas that branch on args must call argsKey. The fn reference
    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
    implementing SchemaSimple must update their denormalize signature.

    Schema.normalize() and the visit() callback also gain an optional
    trailing parentEntity argument 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 normalize args and recursive visit into the existing normalize delegate passed to schemas.
    Custom Schema.normalize() implementations should migrate from
    normalize(input, parent, key, args, visit, delegate, parentEntity?) to
    normalize(input, parent, key, delegate, parentEntity?), then read
    delegate.args and call delegate.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.

    Scalar models 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 Scalar instance can serve both as an Entity.schema field (parent
    entity inferred from the visit) and standalone — inside Values(Scalar),
    [Scalar], or Collection([Scalar]) — for cheap column-only refreshes
    (entity bound explicitly via entity). Cell pks are derived from the map key
    or via Scalar.entityPk(), which defaults to Entity.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 / shares while sharing the same Company row.

    Scalar.queryKey enumerates cells in its table for the current lens, so
    endpoints that use Scalar directly 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.getResults called paths.shift() on a cache hit, mutating
    the journey array stored by reference on the WeakDependencyMap Link
    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
    missed countRef tracking (premature GC of still-referenced entities)
    and incorrect entityExpiresAt calculations. The hit path now returns a
    non-mutating copy.

@data-client/graphql@0.18.0

Minor Changes

  • #3931 959465a - Allow one Collection schema 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
    exposes unvisit, args, and a new argsKey(fn) helper that registers
    a memoization dimension when output varies with endpoint args. Reading
    delegate.args directly does not contribute to cache invalidation —
    schemas that branch on args must call argsKey. The fn reference
    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
    implementing SchemaSimple must update their denormalize signature.

    Schema.normalize() and the visit() callback also gain an optional
    trailing parentEntity argument 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.

    Scalar models 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 Scalar instance can serve both as an Entity.schema field (parent
    entity inferred from the visit) and standalone — inside Values(Scalar),
    [Scalar], or Collection([Scalar]) — for cheap column-only refreshes
    (entity bound explicitly via entity). Cell pks are derived from the map key
    or via Scalar.entityPk(), which defaults to Entity.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 / shares while sharing the same Company row.

    Scalar.queryKey enumerates cells in its table for the current lens, so
    endpoints that use Scalar directly 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.getResults called paths.shift() on a cache hit, mutating
    the journey array stored by reference on the WeakDependencyMap Link
    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
    missed countRef tracking (premature GC of still-referenced entities)
    and incorrect entityExpiresAt calculations. The hit path now returns a
    non-mutating copy.

  • Updated dependencies [959465a, 84078d7, 6e8e499, 396d163, 84078d7]:

    • @data-client/endpoint@0.18.0

@data-client/normalizr@0.18.0

Minor Changes

  • #3931 959465a - Allow one Collection schema 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
    exposes unvisit, args, and a new argsKey(fn) helper that registers
    a memoization dimension when output varies with endpoint args. Reading
    delegate.args directly does not contribute to cache invalidation —
    schemas that branch on args must call argsKey. The fn reference
    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
    implementing SchemaSimple must update their denormalize signature.

    Schema.normalize() and the visit() callback also gain an optional
    trailing parentEntity argument 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 normalize args and recursive visit into the existing normalize delegate passed to schemas.
    Custom Schema.normalize() implementations should migrate from
    normalize(input, parent, key, args, visit, delegate, parentEntity?) to
    normalize(input, parent, key, delegate, parentEntity?), then read
    delegate.args and call delegate.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.

    Scalar models 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 Scalar instance can serve both as an Entity.schema field (parent
    entity inferred from the visit) and standalone — inside Values(Scalar),
    [Scalar], or Collection([Scalar]) — for cheap column-only refreshes
    (entity bound explicitly via entity). Cell pks are derived from the map key
    or via Scalar.entityPk(), which defaults to Entity.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 / shares while sharing the same Company row.

    Scalar.queryKey enumerates cells in its table for the current lens, so
    endpoints that use Scalar directly 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.getResults called paths.shift() on a cache hit, mutating
    the journey array stored by reference on the WeakDependencyMap Link
    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
    missed countRef tracking (premature GC of still-referenced entities)
    and incorrect entityExpiresAt calculations. The hit path now returns a
    non-mutating copy.

@data-client/react@0.18.0

Minor Changes

  • #3931 959465a - Allow one Collection schema 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
    exposes unvisit, args, and a new argsKey(fn) helper that registers
    a memoization dimension when output varies with endpoint args. Reading
    delegate.args directly does not contribute to cache invalidation —
    schemas that branch on args must call argsKey. The fn reference
    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
    implementing SchemaSimple must update their denormalize signature.

    Schema.normalize() and the visit() callback also gain an optional
    trailing parentEntity argument 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.

    Scalar models 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 Scalar instance can serve both as an Entity.schema field (parent
    entity inferred from the visit) and standalone — inside Values(Scalar),
    [Scalar], or Collection([Scalar]) — for cheap column-only refreshes
    (entity bound explicitly via entity). Cell pks are derived from the map key
    or via Scalar.entityPk(), which defaults to Entity.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 / shares while sharing the same Company row.

    Scalar.queryKey enumerates cells in its table for the current lens, so
    endpoints that use Scalar directly 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.getResults called paths.shift() on a cache hit, mutating
    the journey array stored by reference on the WeakDependencyMap Link
    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
    missed countRef tracking (premature GC of still-referenced entities)
    and incorrect entityExpiresAt calculations. The hit path now returns a
    non-mutating copy.

  • Updated dependencies [959465a, 84078d7, 6e8e499, 84078d7]:

    • @data-client/core@0.18.0

@data-client/rest@0.18.0

Minor Changes

  • #3931 959465a - Allow one Collection schema 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
    exposes unvisit, args, and a new argsKey(fn) helper that registers
    a memoization dimension when output varies with endpoint args. Reading
    delegate.args directly does not contribute to cache invalidation —
    schemas that branch on args must call argsKey. The fn reference
    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
    implementing SchemaSimple must update their denormalize signature.

    Schema.normalize() and the visit() callback also gain an optional
    trailing parentEntity argument 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.

    Scalar models 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 Scalar instance can serve both as an Entity.schema field (parent
    entity inferred from the visit) and standalone — inside Values(Scalar),
    [Scalar], or Collection([Scalar]) — for cheap column-only refreshes
    (entity bound explicitly via entity). Cell pks are derived from the map key
    or via Scalar.entityPk(), which defaults to Entity.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 / shares while sharing the same Company row.

    Scalar.queryKey enumerates cells in its table for the current lens, so
    endpoints that use Scalar directly 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.getResults called paths.shift() on a cache hit, mutating
    the journey array stored by reference on the WeakDependencyMap Link
    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
    missed countRef tracking (premature GC of still-referenced entities)
    and incorrect entityExpiresAt calculations. The hit path now returns a
    non-mutating copy.

  • Updated dependencies [959465a, 84078d7, 6e8e499, 396d163, 84078d7]:

    • @data-client/endpoint@0.18.0

@data-client/vue@0.18.0

Minor Changes

  • #3931 959465a - Allow one Collection schema 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
    exposes unvisit, args, and a new argsKey(fn) helper that registers
    a memoization dimension when output varies with endpoint args. Reading
    delegate.args directly does not contribute to cache invalidation —
    schemas that branch on args must call argsKey. The fn reference
    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
    implementing SchemaSimple must update their denormalize signature.

    Schema.normalize() and the visit() callback also gain an optional
    trailing parentEntity argument 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.

    Scalar models 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 Scalar instance can serve both as an Entity.schema field (parent
    entity inferred from the visit) and standalone — inside Values(Scalar),
    [Scalar], or Collection([Scalar]) — for cheap column-only refreshes
    (entity bound explicitly via entity). Cell pks are derived from the map key
    or via Scalar.entityPk(), which defaults to Entity.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 / shares while sharing the same Company row.

    Scalar.queryKey enumerates cells in its table for the current lens, so
    endpoints that use Scalar directly as their top-level schema reconstruct
    from cache without a network round-trip once the cells are present.

Patch Changes

@data-client/img@0.18.0

Patch Changes

@data-client/test@0.18.0

Patch Changes

  • 89e06d3 - Bump @data-client/react peer dependency range to include ^0.18.0.

example-benchmark@0.4.85

Patch Changes

example-benchmark-react@0.1.10

Patch Changes

coinbase-lite@0.0.25

Patch Changes

normalizr-github-example@0.1.59

Patch Changes

normalizr-redux-example@0.1.57

Patch Changes

normalizr-relationships@0.1.59

Patch Changes

test-bundlesize@0.1.17

Patch Changes

@github-actions github-actions Bot force-pushed the changeset-release/master branch 9 times, most recently from 3b2fccc to 50c9ce5 Compare March 28, 2026 18:39
@github-actions github-actions Bot force-pushed the changeset-release/master branch 4 times, most recently from b3e6cd7 to 291c561 Compare March 31, 2026 11:04
@github-actions github-actions Bot force-pushed the changeset-release/master branch 8 times, most recently from 4578466 to 29fc22c Compare April 13, 2026 04:26
@github-actions github-actions Bot force-pushed the changeset-release/master branch 4 times, most recently from 5c6e2ed to 4d7c751 Compare April 17, 2026 07:57
@github-actions github-actions Bot force-pushed the changeset-release/master branch 3 times, most recently from dde3493 to 9f5ac05 Compare May 1, 2026 03:22
@github-actions github-actions Bot force-pushed the changeset-release/master branch from 9f5ac05 to 6ad1042 Compare May 2, 2026 01:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants