Async State Management without the Management
APACHE-2.0 License
Bot releases are visible (Hide)
Published by github-actions[bot] 7 months ago
Release notes and migration guide
#2921 6e55026
Thanks @ntucker! - BREAKING: new AbortOptimistic() -> snapshot.abort
getOptimisticResponse(snapshot, { id }) {
const { data } = snapshot.getResponse(Base.get, { id });
if (!data) throw new AbortOptimistic();
return {
id,
votes: data.votes + 1,
};
}
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
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,
};
},
}),
}));
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
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.
Updated dependencies [2e169b7
, 6e55026
, ca79a62
, 59a407a
, 6e55026
, 6e55026
, 6e55026
, c129a25
, 59a407a
, b738e18
]:
Published by github-actions[bot] 7 months ago
Release notes and migration guide
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
getOptimisticResponse(snapshot, { id }) {
const { data } = snapshot.getResponse(Base.get, { id });
if (!data) throw new AbortOptimistic();
return {
id,
votes: data.votes + 1,
};
}
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()
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)
#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
Published by github-actions[bot] 7 months ago
Published by github-actions[bot] 7 months ago
Published by github-actions[bot] 7 months ago
#2921 6e55026
Thanks @ntucker! - BREAKING: new AbortOptimistic() -> snapshot.abort
getOptimisticResponse(snapshot, { id }) {
const { data } = snapshot.getResponse(Base.get, { id });
if (!data) throw new AbortOptimistic();
return {
id,
votes: data.votes + 1,
};
}
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
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 });
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)
const UserIndex = new Index(User);
const bob = useCache(UserIndex, { username: "bob" });
const bob = useQuery(User, { username: "bob" });
#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;
}
}
Updated dependencies [2e169b7
, 6e55026
, 6e55026
, 73de27f
, 59a407a
, 8377e0a
, c129a25
, 6e55026
, 446f0b9
, bb24601
, 6e55026
, 446f0b9
, f68750f
]:
Published by github-actions[bot] 7 months ago
Release notes and migration guide
#2921 6e55026
Thanks @ntucker! - BREAKING: new AbortOptimistic() -> snapshot.abort
getOptimisticResponse(snapshot, { id }) {
const { data } = snapshot.getResponse(Base.get, { id });
if (!data) throw new AbortOptimistic();
return {
id,
votes: data.votes + 1,
};
}
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
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 });
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)
const UserIndex = new Index(User);
const bob = useCache(UserIndex, { username: "bob" });
const bob = useQuery(User, { username: "bob" });
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]
#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
Published by github-actions[bot] 7 months ago
Release notes and migration guide
#2921 6e55026
Thanks @ntucker! - BREAKING: new AbortOptimistic() -> snapshot.abort
getOptimisticResponse(snapshot, { id }) {
const { data } = snapshot.getResponse(Base.get, { id });
if (!data) throw new AbortOptimistic();
return {
id,
votes: data.votes + 1,
};
}
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
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,
};
},
}),
}));
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
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.
Updated dependencies [2e169b7
, 6e55026
, ca79a62
, f68750f
, f68750f
, 59a407a
, 73de27f
, 446f0b9
, b738e18
, 6e55026
, 446f0b9
, f68750f
, f68750f
, c129a25
, 10432b7
, 59a407a
]:
Published by github-actions[bot] 9 months ago
Published by github-actions[bot] 9 months ago
Published by github-actions[bot] 9 months ago
#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);
}
}
Published by github-actions[bot] 9 months ago
Published by github-actions[bot] 9 months ago
#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.Updated dependencies [4e6a39e
, 922be79
, 69834b5
, 056737e
]:
Published by github-actions[bot] 9 months ago
#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.Published by github-actions[bot] 9 months ago
Published by github-actions[bot] 9 months ago
67f4e0b
, 053e823
, 922be79
]:
Published by github-actions[bot] 9 months ago
#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.Published by github-actions[bot] 9 months ago
#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.Published by github-actions[bot] 9 months ago