Skip to content

Commit

Permalink
feat: Queries, Queryable, and useQuery
Browse files Browse the repository at this point in the history
  • Loading branch information
ntucker committed Mar 5, 2024
1 parent 3e0e917 commit c8944cb
Show file tree
Hide file tree
Showing 107 changed files with 3,656 additions and 1,081 deletions.
36 changes: 36 additions & 0 deletions .changeset/brave-rockets-bake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
"@data-client/normalizr": minor
"@data-client/endpoint": minor
"@data-client/react": minor
"@data-client/core": minor
"@data-client/rest": minor
"@data-client/graphql": minor
---

BREAKING: new AbortOptimistic() -> [snapshot.abort](https://dataclient/docs/api/Snapshot#abort)

#### Before

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

#### After

```ts
getOptimisticResponse(snapshot, { id }) {
const { data } = snapshot.getResponse(Base.get, { id });
if (!data) throw snapshot.abort;
return {
id,
votes: data.votes + 1,
};
}
```
39 changes: 39 additions & 0 deletions .changeset/fresh-rice-provide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
"@data-client/endpoint": minor
"@data-client/rest": minor
"@data-client/graphql": minor
---

BREAKING: new Query -> [new schema.Query](https://dataclient.io/rest/api/Query)

#### Before

```jsx
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

```jsx
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 });
```
9 changes: 9 additions & 0 deletions .changeset/mean-cougars-confess.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@data-client/react": patch
"@data-client/core": patch
---

Improve controller.getResponse() type matching

Uses function overloading to more precisely match argument
expectations for fetchable Endpoints vs only keyable Endpoints.
43 changes: 43 additions & 0 deletions .changeset/purple-pandas-confess.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
"@data-client/react": minor
---

Add [useQuery()](https://dataclient.io/docs/api/useQuery) to render [Querable Schemas](https://dataclient.io/docs/api/useQuery#queryable)

```ts
class User extends Entity {
username = '';
id = '';
groupId = '';
pk() {
return this.id;
}
static index = ['username' as const];
}

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

```ts
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 });
```

```ts
const UserCollection = new schema.Collection([User], {
argsKey: (urlParams: { groupId?: string }) => ({
...urlParams,
}),
});

const usersInGroup = useQuery(UserCollection, { groupId: '5' });
```
11 changes: 11 additions & 0 deletions .changeset/quick-flies-taste.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@data-client/normalizr": patch
"@data-client/endpoint": patch
"@data-client/react": patch
"@data-client/redux": patch
"@data-client/core": patch
"@data-client/rest": patch
"@data-client/test": patch
---

Update README
54 changes: 54 additions & 0 deletions .changeset/sweet-coins-deliver.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
---
"@data-client/react": minor
"@data-client/core": minor
---

Add [controller.get](https://dataclient.io/docs/api/Controller#get) / [snapshot.get](https://dataclient.io/docs/api/Snapshot#get) to directly read [Querable Schemas](https://dataclient.io/docs/api/useQuery#queryable)

#### Before

```tsx
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

```tsx
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,
};
},
},
);
```
21 changes: 21 additions & 0 deletions .changeset/thirty-ants-grin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
"@data-client/endpoint": minor
"@data-client/rest": minor
"@data-client/graphql": minor
---

BREAKING: useCache(new Index(MyEntity)) -> useQuery(MyEntity)

#### Before

```jsx
const UserIndex = new Index(User)

const bob = useCache(UserIndex, { username: 'bob' });
```

#### After

```jsx
const bob = useQuery(User, { username: 'bob' });
```
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ ctrl.fetch(ArticleResource.get, { id });
### [Programmatic queries](https://dataclient.io/rest/api/Query)

```tsx
const queryTotalVotes = new Query(
const queryTotalVotes = new schema.Query(
new schema.All(Post),
(posts, { userId } = {}) => {
if (userId !== undefined)
Expand All @@ -144,8 +144,8 @@ const queryTotalVotes = new Query(
},
);

const totalVotes = useCache(queryTotalVotes);
const totalVotesForUser = useCache(queryTotalVotes, { userId });
const totalVotes = useQuery(queryTotalVotes);
const totalVotesForUser = useQuery(queryTotalVotes, { userId });
```

### [Powerful Middlewares](https://dataclient.io/docs/concepts/managers)
Expand Down Expand Up @@ -271,5 +271,6 @@ For the small price of 9kb gziped.    [🏁Get started now](https://da
- [Values](https://dataclient.io/rest/api/Values)
- [All](https://dataclient.io/rest/api/All)
- [Collection](https://dataclient.io/rest/api/Collection)
- [Query](https://dataclient.io/rest/api/Query)
- [Union](https://dataclient.io/rest/api/Union)
- [Invalidate](https://dataclient.io/rest/api/Invalidate)
22 changes: 14 additions & 8 deletions __tests__/new.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {
schema,
Endpoint,
Index,
createResource,
RestEndpoint,
Schema,
Expand Down Expand Up @@ -188,6 +187,13 @@ export class Article extends Entity {
author: User,
};
}

export class ArticleWithSlug extends Article {
readonly slug: string = '';

static indexes = ['slug'] as const;
}

class ArticleData {
readonly id: number | undefined = undefined;
readonly title: string = '';
Expand Down Expand Up @@ -265,6 +271,9 @@ function createArticleResource<O extends ArticleGenerics>({
return resource as any;
}
export const ArticleResource = createArticleResource({ schema: Article });
export const ArticleSlugResource = createArticleResource({
schema: ArticleWithSlug,
});

export const AuthContext = createContext('');

Expand Down Expand Up @@ -518,13 +527,10 @@ export const CoolerArticleDetail = new Endpoint(
export class IndexedUser extends User {
static readonly indexes = ['username'];
}
export const IndexedUserResource = {
...createResource({
path: 'http\\://test.com/user/:id',
schema: IndexedUser,
}),
getIndex: new Index(IndexedUser),
};
export const IndexedUserResource = createResource({
path: 'http\\://test.com/user/:id',
schema: IndexedUser,
});

class InvalidIfStaleEndpoint<
O extends RestGenerics = any,
Expand Down
46 changes: 42 additions & 4 deletions docs/core/api/Controller.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class Controller {
subscribe(endpoint, ...args): Promise<void>;
unsubscribe(endpoint, ...args): Promise<void>;
/*************** Data Access ***************/
get(queryable, ...args, state): Denormalized<typeof queryable>;
getResponse(endpoint, ...args, state): { data; expiryStatus; expiresAt };
getError(endpoint, ...args, state): ErrorTypes | undefined;
snapshot(state: State<unknown>, fetchedAt?: number): SnapshotInterface;
Expand Down Expand Up @@ -124,7 +125,10 @@ When using schemas, the denormalized value is returned
// highlight-next-line
import { useController } from '@data-client/react';

const post = await controller.fetch(PostResource.getList.push, createPayload);
const post = await controller.fetch(
PostResource.getList.push,
createPayload,
);
post.title;
post.pk();
```
Expand Down Expand Up @@ -364,7 +368,11 @@ useEffect(() => {
const websocket = new Websocket(url);

websocket.onmessage = event =>
ctrl.setResponse(EndpointLookup[event.endpoint], ...event.args, event.data);
ctrl.setResponse(
EndpointLookup[event.endpoint],
...event.args,
event.data,
);

return () => websocket.close();
});
Expand Down Expand Up @@ -412,6 +420,36 @@ decrement the subscription and if the count reaches 0, more updates won't be rec

[useSubscription](./useSubscription.md) and [useLive](./useLive.md) call this on unmount.

## get(schema, ...args, state) {#get}

Looks up any [Queryable](./useQuery.md#queryable) [Schema](/rest/api/schema#schema-overview) in `state`.

### Example

This is used in [useQuery](./useQuery.md) and can be used in
[Managers](./Manager.md) to safely access the store.

```tsx title="useQuery.ts"
import {
useController,
useCacheState,
type Queryable,
type SchemaArgs,
type DenormalizeNullable,
} from '@data-client/core';

/** Oversimplified useQuery */
function useQuery<S extends Queryable>(
schema: S,
...args: SchemaArgs<S>
): DenormalizeNullable<S> | undefined {
const state = useCacheState();
const controller = useController();

return controller.get(schema, ...args, state);
}
```

## getResponse(endpoint, ...args, state) {#getResponse}

```ts title="returns"
Expand Down Expand Up @@ -487,15 +525,15 @@ import type { EndpointInterface } from '@data-client/endpoint';
export default class MyManager implements Manager {
protected declare middleware: Middleware;
constructor() {
this.middleware = ({ controller, getState }) => {
this.middleware = controller => {
return next => async action => {
if (action.type === actionTypes.FETCH_TYPE) {
console.log('The existing response of the requested fetch');
console.log(
controller.getResponse(
action.endpoint,
...(action.meta.args as Parameters<typeof action.endpoint>),
getState(),
controller.getState(),
).data,
);
}
Expand Down
Loading

1 comment on commit c8944cb

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark

Benchmark suite Current: c8944cb Previous: 72762cf Ratio
normalizeLong 438 ops/sec (±1.62%) 442 ops/sec (±2.04%) 1.01
infer All 9144 ops/sec (±1.73%) 9205 ops/sec (±1.62%) 1.01
denormalizeLong 238 ops/sec (±3.32%) 234 ops/sec (±3.73%) 0.98
denormalizeLong donotcache 832 ops/sec (±0.69%) 853 ops/sec (±0.51%) 1.03
denormalizeShort donotcache 500x 1372 ops/sec (±0.32%) 1363 ops/sec (±0.19%) 0.99
denormalizeShort 500x 728 ops/sec (±2.60%) 721 ops/sec (±2.37%) 0.99
denormalizeShort 500x withCache 4379 ops/sec (±0.34%) 4445 ops/sec (±0.29%) 1.02
denormalizeLong with mixin Entity 234 ops/sec (±2.31%) 231 ops/sec (±2.56%) 0.99
denormalizeLong withCache 7736 ops/sec (±0.16%) 7007 ops/sec (±0.79%) 0.91
denormalizeLongAndShort withEntityCacheOnly 1594 ops/sec (±0.34%) 1585 ops/sec (±0.33%) 0.99
denormalizeLong All withCache 6187 ops/sec (±0.38%) 6137 ops/sec (±0.48%) 0.99
denormalizeLong Query-sorted withCache 6226 ops/sec (±0.27%) 6590 ops/sec (±0.17%) 1.06
getResponse 5168 ops/sec (±1.49%) 4936 ops/sec (±0.91%) 0.96
getResponse (null) 2943302 ops/sec (±0.26%) 2914817 ops/sec (±0.24%) 0.99
getResponse (clear cache) 226 ops/sec (±2.50%) 232 ops/sec (±2.39%) 1.03
getSmallResponse 2382 ops/sec (±0.96%) 2226 ops/sec (±0.26%) 0.93
getSmallInferredResponse 1874 ops/sec (±0.12%) 1784 ops/sec (±0.32%) 0.95
getResponse Query-sorted 1040 ops/sec (±0.36%) 650 ops/sec (±1.14%) 0.63
getResponse Collection 4913 ops/sec (±1.07%) 5242 ops/sec (±1.42%) 1.07
setLong 432 ops/sec (±2.16%) 428 ops/sec (±2.77%) 0.99
setLongWithMerge 184 ops/sec (±0.28%) 186 ops/sec (±0.28%) 1.01
setLongWithSimpleMerge 195 ops/sec (±0.45%) 195 ops/sec (±0.28%) 1

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.