data-client

Async State Management without the Management

APACHE-2.0 License

Downloads
44.4K
Stars
1.9K
Committers
37

Bot releases are visible (Hide)

data-client - @data-client/[email protected]

Published by github-actions[bot] 7 months ago

Release notes and migration guide

Minor Changes

  • #2921 6e55026 Thanks @ntucker! - BREAKING: new AbortOptimistic() -> snapshot.abort

    Before

    getOptimisticResponse(snapshot, { id }) {
      const { data } = snapshot.getResponse(Base.get, { id });
      if (!data) throw new AbortOptimistic();
      return {
        id,
        votes: data.votes + 1,
      };
    }
    

    After

    getOptimisticResponse(snapshot, { id }) {
      const { data } = snapshot.getResponse(Base.get, { id });
      if (!data) throw snapshot.abort;
      return {
        id,
        votes: data.votes + 1,
      };
    }
    
  • #2963 7580500 Thanks @ntucker! - useCache() accepts Endpoints with sideEffects

  • #2977 59a407a Thanks @ntucker! - BREAKING: Schema.infer -> Schema.queryKey

    class MyEntity extends Entity {
      // highlight-next-line
      static infer(
        args: readonly any[],
        indexes: NormalizedIndex,
        recurse: any,
        entities: any,
      ): any {
        if (SILLYCONDITION) return undefined;
        return super.infer(args, indexes, recurse, entities);
      }
    }
    
    class MyEntity extends Entity {
      // highlight-next-line
      static queryKey(
        args: readonly any[],
        queryKey: (...args: any) => any,
        getEntity: GetEntity,
        getIndex: GetIndex,
      ): any {
        if (SILLYCONDITION) return undefined;
        return super.queryKey(args, queryKey, getEntity, getIndex);
      }
    }
    
  • #2921 6e55026 Thanks @ntucker! - Add useQuery() to render Querable Schemas

    class User extends Entity {
      username = "";
      id = "";
      groupId = "";
      pk() {
        return this.id;
      }
      static index = ["username" as const];
    }
    
    const bob = useQuery(User, { username: "bob" });
    
    const getUserCount = new schema.Query(
      new schema.All(User),
      (entries, { isAdmin } = {}) => {
        if (isAdmin !== undefined)
          return entries.filter((user) => user.isAdmin === isAdmin).length;
        return entries.length;
      },
    );
    
    const userCount = useQuery(getUserCount);
    const adminCount = useQuery(getUserCount, { isAdmin: true });
    
    const UserCollection = new schema.Collection([User], {
      argsKey: (urlParams: { groupId?: string }) => ({
        ...urlParams,
      }),
    });
    
    const usersInGroup = useQuery(UserCollection, { groupId: "5" });
    
  • #2921 6e55026 Thanks @ntucker! - Add controller.get / snapshot.get to directly read Querable Schemas

    Before

    export const PostResource = createResource({
      path: "/posts/:id",
      schema: Post,
    }).extend((Base) => ({
      vote: new RestEndpoint({
        path: "/posts/:id/vote",
        method: "POST",
        body: undefined,
        schema: Post,
        getOptimisticResponse(snapshot, { id }) {
          const { data } = snapshot.getResponse(Base.get, { id });
          if (!data) throw new AbortOptimistic();
          return {
            id,
            votes: data.votes + 1,
          };
        },
      }),
    }));
    

    After

    export const PostResource = createResource({
      path: "/posts/:id",
      schema: Post,
    }).extend("vote", {
      path: "/posts/:id/vote",
      method: "POST",
      body: undefined,
      schema: Post,
      getOptimisticResponse(snapshot, { id }) {
        const post = snapshot.get(Post, { id });
        if (!post) throw new AbortOptimistic();
        return {
          id,
          votes: post.votes + 1,
        };
      },
    });
    
  • #2957 c129a25 Thanks @ntucker! - Add snapshot.abort

    getOptimisticResponse(snapshot, { id }) {
      const { data } = snapshot.getResponse(Base.get, { id });
      if (!data) throw snapshot.abort;
      return {
        id,
        votes: data.votes + 1,
      };
    }
    
  • #2997 8d42ef6 Thanks @ntucker! - useDLE() reactive native focus handling

    When using React Navigation, useDLE() will trigger fetches on focus if the data is considered
    stale.

  • #2971 b738e18 Thanks @ntucker! - BREAKING: Internal state.results -> state.endpoints

Patch Changes

  • 2e169b7 Thanks @ntucker! - Fix schema.All denormalize INVALID case should also work when class name mangling is performed in production builds

    • unvisit() always returns undefined with undefined as input.
    • All returns INVALID from queryKey() to invalidate what was previously a special case in unvisit() (when there is no table entry for the given entity)
  • ca79a62 Thanks @ntucker! - Update description in package.json

  • #2921 6e55026 Thanks @ntucker! - Improve controller.getResponse() type matching

    Uses function overloading to more precisely match argument
    expectations for fetchable Endpoints vs only keyable Endpoints.

  • #2921 6e55026 Thanks @ntucker! - Update README

  • Updated dependencies [2e169b7, 6e55026, ca79a62, 59a407a, 6e55026, 6e55026, 6e55026, c129a25, 59a407a, b738e18]:

data-client - @data-client/[email protected]

Published by github-actions[bot] 7 months ago

Release notes and migration guide

Minor Changes

  • 2e169b7 Thanks @ntucker! - Fix schema.All denormalize INVALID case should also work when class name mangling is performed in production builds

    • unvisit() always returns undefined with undefined as input.
    • All returns INVALID from queryKey() to invalidate what was previously a special case in unvisit() (when there is no table entry for the given entity)
  • #2921 6e55026 Thanks @ntucker! - BREAKING: new AbortOptimistic() -> snapshot.abort

    Before

    getOptimisticResponse(snapshot, { id }) {
      const { data } = snapshot.getResponse(Base.get, { id });
      if (!data) throw new AbortOptimistic();
      return {
        id,
        votes: data.votes + 1,
      };
    }
    

    After

    getOptimisticResponse(snapshot, { id }) {
      const { data } = snapshot.getResponse(Base.get, { id });
      if (!data) throw snapshot.abort;
      return {
        id,
        votes: data.votes + 1,
      };
    }
    
  • #2978 f68750f Thanks @ntucker! - BREAKING CHANGE: buildQueryKey() -> memo.buildQueryKey()

    const results = buildQueryKey(schema, args, state.indexes, state.entities);
    
    const memo = new MemoCached();
    memo.buildQueryKey(key, schema, args, state.entities, state.indexes);
    
  • #2978 f68750f Thanks @ntucker! - Add MemoCache

    MemoCache is a singleton to store the memoization cache for denormalization methods

    const memo = new MemoCache();
    const data = memo.query(key, schema, args, state.entities, state.indexes);
    const { data, paths } = memo.denormalize(input, schema, state.entities, args);
    const queryKey = memo.buildQueryKey(
      key,
      schema,
      args,
      state.entities,
      state.indexes,
    );
    
  • #2977 59a407a Thanks @ntucker! - BREAKING: Schema.infer -> Schema.queryKey

    class MyEntity extends Entity {
      // highlight-next-line
      static infer(
        args: readonly any[],
        indexes: NormalizedIndex,
        recurse: any,
        entities: any,
      ): any {
        if (SILLYCONDITION) return undefined;
        return super.infer(args, indexes, recurse, entities);
      }
    }
    
    class MyEntity extends Entity {
      // highlight-next-line
      static queryKey(
        args: readonly any[],
        queryKey: (...args: any) => any,
        getEntity: GetEntity,
        getIndex: GetIndex,
      ): any {
        if (SILLYCONDITION) return undefined;
        return super.queryKey(args, queryKey, getEntity, getIndex);
      }
    }
    
  • #2971 b738e18 Thanks @ntucker! - type ResultCache -> EndpointCache

  • #2978 f68750f Thanks @ntucker! - BREAKING CHANGE: WeakEntityMap -> WeakDependencyMap

    We generalize this data type so it can be used with other dependencies.

    new WeakEntityMap();
    
    new WeakDependencyMap<EntityPath>();
    
  • #2978 f68750f Thanks @ntucker! - BREAKING CHANGE: denormalizeCached() -> new MemoCache().denormalize()

    const endpointCache = new WeakEntityMap();
    const entityCache = {};
    denormalizeCached(
      input,
      schema,
      state.entities,
      entityCache,
      endpointCache,
      args,
    );
    
    const memo = new MemoCached();
    memo.denormalize(input, schema, state.entities, args);
    
  • #2957 c129a25 Thanks @ntucker! - Add snapshot.abort

    getOptimisticResponse(snapshot, { id }) {
      const { data } = snapshot.getResponse(Base.get, { id });
      if (!data) throw snapshot.abort;
      return {
        id,
        votes: data.votes + 1,
      };
    }
    
  • #2977 59a407a Thanks @ntucker! - BREAKING: inferResults() -> buildQueryKey()

Patch Changes

  • ca79a62 Thanks @ntucker! - Update description in package.json

  • 73de27f Thanks @ntucker! - Use same state meta for each entity, rather than duplicating

  • #2961 446f0b9 Thanks @ntucker! - fix: Missing nested entities should appear once they are present (When nesting pk was a number type)

  • #2921 6e55026 Thanks @ntucker! - Update README

  • #2961 446f0b9 Thanks @ntucker! - Always normalize pk to string type

    Warning: This will affect contents of the store state (some numbers will appear as strings)

    Before:

    {
      "Article": {
        "123": {
          "author": 8472,
          "id": 123,
          "title": "A Great Article"
        }
      },
      "User": {
        "8472": {
          "id": 8472,
          "name": "Paul"
        }
      }
    }
    

    After:

    {
      "Article": {
        "123": {
          "author": "8472",
          "id": 123,
          "title": "A Great Article"
        }
      },
      "User": {
        "8472": {
          "id": 8472,
          "name": "Paul"
        }
      }
    }
    
  • #2956 10432b7 Thanks @ntucker! - fix: Missing nested entities should appear once they are present

data-client - @data-client/[email protected]

Published by github-actions[bot] 7 months ago

Patch Changes

data-client - @data-client/[email protected]

Published by github-actions[bot] 7 months ago

data-client - @data-client/[email protected]

Published by github-actions[bot] 7 months ago

Minor Changes

  • #2921 6e55026 Thanks @ntucker! - BREAKING: new AbortOptimistic() -> snapshot.abort

    Before

    getOptimisticResponse(snapshot, { id }) {
      const { data } = snapshot.getResponse(Base.get, { id });
      if (!data) throw new AbortOptimistic();
      return {
        id,
        votes: data.votes + 1,
      };
    }
    

    After

    getOptimisticResponse(snapshot, { id }) {
      const { data } = snapshot.getResponse(Base.get, { id });
      if (!data) throw snapshot.abort;
      return {
        id,
        votes: data.votes + 1,
      };
    }
    
  • #2921 6e55026 Thanks @ntucker! - BREAKING: new Query -> new schema.Query

    Before

    const getUserCount = new Query(
      new schema.All(User),
      (entries, { isAdmin } = {}) => {
        if (isAdmin !== undefined)
          return entries.filter((user) => user.isAdmin === isAdmin).length;
        return entries.length;
      },
    );
    
    const userCount = useCache(getUserCount);
    const adminCount = useCache(getUserCount, { isAdmin: true });
    

    After

    const getUserCount = new schema.Query(
      new schema.All(User),
      (entries, { isAdmin } = {}) => {
        if (isAdmin !== undefined)
          return entries.filter((user) => user.isAdmin === isAdmin).length;
        return entries.length;
      },
    );
    
    const userCount = useQuery(getUserCount);
    const adminCount = useQuery(getUserCount, { isAdmin: true });
    
  • #2957 c129a25 Thanks @ntucker! - BREAKING CHANGE: Remove new AbortOptimistic() in favor of snapshot.abort

    getOptimisticResponse(snapshot, { id }) {
      const { data } = snapshot.getResponse(Base.get, { id });
      if (!data) throw snapshot.abort;
      return {
        id,
        votes: data.votes + 1,
      };
    }
    
  • #2972 bb24601 Thanks @ntucker! - BREAKING: Entity.useIncoming → Entity.shouldUpdate)

    class MyEntity extends Entity {
      // highlight-next-line
      static useIncoming(
        existingMeta: { date: number },
        incomingMeta: { date: number },
        existing: any,
        incoming: any,
      ) {
        return !deepEquals(existing, incoming);
      }
    }
    
    class MyEntity extends Entity {
      // highlight-next-line
      static shouldUpdate(
        existingMeta: { date: number },
        incomingMeta: { date: number },
        existing: any,
        incoming: any,
      ) {
        return !deepEquals(existing, incoming);
      }
    }
    
  • #2921 6e55026 Thanks @ntucker! - BREAKING: useCache(new Index(MyEntity)) -> useQuery(MyEntity)

    Before

    const UserIndex = new Index(User);
    
    const bob = useCache(UserIndex, { username: "bob" });
    

    After

    const bob = useQuery(User, { username: "bob" });
    

Patch Changes

data-client - @data-client/[email protected]

Published by github-actions[bot] 7 months ago

Release notes and migration guide

Minor Changes

  • #2921 6e55026 Thanks @ntucker! - BREAKING: new AbortOptimistic() -> snapshot.abort

    Before

    getOptimisticResponse(snapshot, { id }) {
      const { data } = snapshot.getResponse(Base.get, { id });
      if (!data) throw new AbortOptimistic();
      return {
        id,
        votes: data.votes + 1,
      };
    }
    

    After

    getOptimisticResponse(snapshot, { id }) {
      const { data } = snapshot.getResponse(Base.get, { id });
      if (!data) throw snapshot.abort;
      return {
        id,
        votes: data.votes + 1,
      };
    }
    
  • #2921 6e55026 Thanks @ntucker! - BREAKING: new Query -> new schema.Query

    Before

    const getUserCount = new Query(
      new schema.All(User),
      (entries, { isAdmin } = {}) => {
        if (isAdmin !== undefined)
          return entries.filter((user) => user.isAdmin === isAdmin).length;
        return entries.length;
      },
    );
    
    const userCount = useCache(getUserCount);
    const adminCount = useCache(getUserCount, { isAdmin: true });
    

    After

    const getUserCount = new schema.Query(
      new schema.All(User),
      (entries, { isAdmin } = {}) => {
        if (isAdmin !== undefined)
          return entries.filter((user) => user.isAdmin === isAdmin).length;
        return entries.length;
      },
    );
    
    const userCount = useQuery(getUserCount);
    const adminCount = useQuery(getUserCount, { isAdmin: true });
    
  • #2977 59a407a Thanks @ntucker! - BREAKING: Schema.infer -> Schema.queryKey

    class MyEntity extends Entity {
      // highlight-next-line
      static infer(
        args: readonly any[],
        indexes: NormalizedIndex,
        recurse: any,
        entities: any,
      ): any {
        if (SILLYCONDITION) return undefined;
        return super.infer(args, indexes, recurse, entities);
      }
    }
    
    class MyEntity extends Entity {
      // highlight-next-line
      static queryKey(
        args: readonly any[],
        queryKey: (...args: any) => any,
        getEntity: GetEntity,
        getIndex: GetIndex,
      ): any {
        if (SILLYCONDITION) return undefined;
        return super.queryKey(args, queryKey, getEntity, getIndex);
      }
    }
    
  • #2957 c129a25 Thanks @ntucker! - BREAKING CHANGE: Remove new AbortOptimistic() in favor of snapshot.abort

    getOptimisticResponse(snapshot, { id }) {
      const { data } = snapshot.getResponse(Base.get, { id });
      if (!data) throw snapshot.abort;
      return {
        id,
        votes: data.votes + 1,
      };
    }
    
  • #2972 bb24601 Thanks @ntucker! - BREAKING: Entity.useIncoming → Entity.shouldUpdate)

    class MyEntity extends Entity {
      // highlight-next-line
      static useIncoming(
        existingMeta: { date: number },
        incomingMeta: { date: number },
        existing: any,
        incoming: any,
      ) {
        return !deepEquals(existing, incoming);
      }
    }
    
    class MyEntity extends Entity {
      // highlight-next-line
      static shouldUpdate(
        existingMeta: { date: number },
        incomingMeta: { date: number },
        existing: any,
        incoming: any,
      ) {
        return !deepEquals(existing, incoming);
      }
    }
    
  • #2921 6e55026 Thanks @ntucker! - BREAKING: useCache(new Index(MyEntity)) -> useQuery(MyEntity)

    Before

    const UserIndex = new Index(User);
    
    const bob = useCache(UserIndex, { username: "bob" });
    

    After

    const bob = useQuery(User, { username: "bob" });
    

Patch Changes

  • 2e169b7 Thanks @ntucker! - Fix schema.All denormalize INVALID case should also work when class name mangling is performed in production builds

    • unvisit() always returns undefined with undefined as input.
    • All returns INVALID from queryKey() to invalidate what was previously a special case in unvisit() (when there is no table entry for the given entity)
  • 73de27f Thanks @ntucker! - Slight code reduction in EntitySchema.normalize

  • 8377e0a Thanks @ntucker! - Default Collection Args type is:

    | []
    | [urlParams: Record<string, any>]
    | [urlParams: Record<string, any>, body: any]
    
  • #2921 6e55026 Thanks @ntucker! - Update README

  • #2961 446f0b9 Thanks @ntucker! - Always normalize pk to string type

    Warning: This will affect contents of the store state (some numbers will appear as strings)

    Before:

    {
      "Article": {
        "123": {
          "author": 8472,
          "id": 123,
          "title": "A Great Article"
        }
      },
      "User": {
        "8472": {
          "id": 8472,
          "name": "Paul"
        }
      }
    }
    

    After:

    {
      "Article": {
        "123": {
          "author": "8472",
          "id": 123,
          "title": "A Great Article"
        }
      },
      "User": {
        "8472": {
          "id": 8472,
          "name": "Paul"
        }
      }
    }
    
  • #2961 446f0b9 Thanks @ntucker! - Allow pk() to return numbers

    Before:

    class MyEntity extends Entity {
      id = 0;
      pk() {
        return `${this.id}`;
      }
    }
    

    After:

    class MyEntity extends Entity {
      id = 0;
      pk() {
        return this.id;
      }
    }
    
  • #2978 f68750f Thanks @ntucker! - More robustly handle Union queries when the schema cannot be found

data-client - @data-client/[email protected]

Published by github-actions[bot] 7 months ago

Release notes and migration guide

Minor Changes

  • #2921 6e55026 Thanks @ntucker! - BREAKING: new AbortOptimistic() -> snapshot.abort

    Before

    getOptimisticResponse(snapshot, { id }) {
      const { data } = snapshot.getResponse(Base.get, { id });
      if (!data) throw new AbortOptimistic();
      return {
        id,
        votes: data.votes + 1,
      };
    }
    

    After

    getOptimisticResponse(snapshot, { id }) {
      const { data } = snapshot.getResponse(Base.get, { id });
      if (!data) throw snapshot.abort;
      return {
        id,
        votes: data.votes + 1,
      };
    }
    
  • #2977 59a407a Thanks @ntucker! - BREAKING: Schema.infer -> Schema.queryKey

    class MyEntity extends Entity {
      // highlight-next-line
      static infer(
        args: readonly any[],
        indexes: NormalizedIndex,
        recurse: any,
        entities: any,
      ): any {
        if (SILLYCONDITION) return undefined;
        return super.infer(args, indexes, recurse, entities);
      }
    }
    
    class MyEntity extends Entity {
      // highlight-next-line
      static queryKey(
        args: readonly any[],
        queryKey: (...args: any) => any,
        getEntity: GetEntity,
        getIndex: GetIndex,
      ): any {
        if (SILLYCONDITION) return undefined;
        return super.queryKey(args, queryKey, getEntity, getIndex);
      }
    }
    
  • #2921 6e55026 Thanks @ntucker! - Add controller.get / snapshot.get to directly read Querable Schemas

    Before

    export const PostResource = createResource({
      path: "/posts/:id",
      schema: Post,
    }).extend((Base) => ({
      vote: new RestEndpoint({
        path: "/posts/:id/vote",
        method: "POST",
        body: undefined,
        schema: Post,
        getOptimisticResponse(snapshot, { id }) {
          const { data } = snapshot.getResponse(Base.get, { id });
          if (!data) throw new AbortOptimistic();
          return {
            id,
            votes: data.votes + 1,
          };
        },
      }),
    }));
    

    After

    export const PostResource = createResource({
      path: "/posts/:id",
      schema: Post,
    }).extend("vote", {
      path: "/posts/:id/vote",
      method: "POST",
      body: undefined,
      schema: Post,
      getOptimisticResponse(snapshot, { id }) {
        const post = snapshot.get(Post, { id });
        if (!post) throw new AbortOptimistic();
        return {
          id,
          votes: post.votes + 1,
        };
      },
    });
    
  • #2957 c129a25 Thanks @ntucker! - Add snapshot.abort

    getOptimisticResponse(snapshot, { id }) {
      const { data } = snapshot.getResponse(Base.get, { id });
      if (!data) throw snapshot.abort;
      return {
        id,
        votes: data.votes + 1,
      };
    }
    
  • #2977 59a407a Thanks @ntucker! - BREAKING: inferResults() -> buildQueryKey()

  • #2971 b738e18 Thanks @ntucker! - BREAKING: Internal state.results -> state.endpoints

Patch Changes

data-client - @data-client/[email protected]

Published by github-actions[bot] 9 months ago

Patch Changes

data-client - @data-client/[email protected]

Published by github-actions[bot] 9 months ago

Patch Changes

data-client - @data-client/[email protected]

Published by github-actions[bot] 9 months ago

Patch Changes

data-client - @data-client/[email protected]

Published by github-actions[bot] 9 months ago

Minor Changes

  • #2919 44f9ec2 Thanks @ntucker! - Add exports getUrlBase, getUrlTokens used to construct URLs

    This enables custom RestEndpoint.url() implementations

  • #2919 44f9ec2 Thanks @ntucker! - Add RestEndpoint.searchToString()

    For example:

    To encode complex objects in the searchParams, you can use the qs library.

    import { RestEndpoint, RestGenerics } from "@data-client/rest";
    import qs from "qs";
    
    class MyEndpoint<O extends RestGenerics = any> extends RestEndpoint<O> {
      searchToString(searchParams) {
        return qs.stringify(searchParams);
      }
    }
    

Patch Changes

data-client - @data-client/[email protected]

Published by github-actions[bot] 9 months ago

Patch Changes

data-client - @data-client/[email protected]

Published by github-actions[bot] 9 months ago

Minor Changes

  • #2912 922be79 Thanks @ntucker! - BREAKING CHANGE: null inputs are no longer filtered from Array or Object

    • [] and schema.Array now behave in the same manner.
    • null values are now consistently handled everywhere (being retained).
    • undefined is still filtered out.

Patch Changes

data-client - @data-client/[email protected]

Published by github-actions[bot] 9 months ago

Minor Changes

  • #2912 922be79 Thanks @ntucker! - BREAKING CHANGE: null inputs are no longer filtered from Array or Object

    • [] and schema.Array now behave in the same manner.
    • null values are now consistently handled everywhere (being retained).
    • undefined is still filtered out.

Patch Changes

data-client - @data-client/[email protected]

Published by github-actions[bot] 9 months ago

Patch Changes

data-client - @data-client/[email protected]

Published by github-actions[bot] 9 months ago

Patch Changes

data-client - @data-client/[email protected]

Published by github-actions[bot] 9 months ago

Minor Changes

  • #2912 922be79 Thanks @ntucker! - BREAKING CHANGE: null inputs are no longer filtered from Array or Object

    • [] and schema.Array now behave in the same manner.
    • null values are now consistently handled everywhere (being retained).
    • undefined is still filtered out.

Patch Changes

data-client - @data-client/[email protected]

Published by github-actions[bot] 9 months ago

Minor Changes

  • #2912 922be79 Thanks @ntucker! - BREAKING CHANGE: null inputs are no longer filtered from Array or Object

    • [] and schema.Array now behave in the same manner.
    • null values are now consistently handled everywhere (being retained).
    • undefined is still filtered out.

Patch Changes

data-client - @data-client/[email protected]

Published by github-actions[bot] 9 months ago

Patch Changes

data-client - @data-client/[email protected]

Published by github-actions[bot] 11 months ago

Patch Changes