| undefined, ProcessParameters>
{
declare schema: S;
diff --git a/packages/endpoint/src/schemas/__tests__/Collection.test.ts b/packages/endpoint/src/schemas/__tests__/Collection.test.ts
index f3e964683461..27261d1e81f9 100644
--- a/packages/endpoint/src/schemas/__tests__/Collection.test.ts
+++ b/packages/endpoint/src/schemas/__tests__/Collection.test.ts
@@ -251,15 +251,7 @@ describe(`${schema.Collection.name} normalization`, () => {
state.entityMeta,
),
};
- function validate(
- sch: schema.Collection<
- (typeof Todo)[],
- [
- urlParams: globalThis.Record,
- body?: globalThis.Record | undefined,
- ]
- >,
- ) {
+ function validate(sch: schema.Collection<(typeof Todo)[]>) {
expect(
(
denormalize(
@@ -327,7 +319,7 @@ describe(`${schema.Collection.name} normalization`, () => {
([key, value]) =>
key.startsWith('ignored') ||
// strings are canonical form. See pk() above for value transformation
- `${args[0][key]}` === value ||
+ `${args[0]?.[key]}` === value ||
`${args[1]?.[key]}` === value,
),
});
@@ -336,7 +328,10 @@ describe(`${schema.Collection.name} normalization`, () => {
it('should work with function override of nonFilterArgumentKeys', () => {
class MyCollection<
S extends any[] | PolymorphicInterface = any,
- Parent extends any[] = [urlParams: any, body?: any],
+ Parent extends any[] =
+ | []
+ | [urlParams: { [k: string]: any }]
+ | [urlParams: { [k: string]: any }, body: { [k: string]: any }],
> extends schema.Collection {
nonFilterArgumentKeys(key: string) {
return key.startsWith('ignored');
@@ -348,7 +343,10 @@ describe(`${schema.Collection.name} normalization`, () => {
it('should work with function override of createCollectionFilter', () => {
class MyCollection<
S extends any[] | PolymorphicInterface = any,
- Parent extends any[] = [urlParams: any, body?: any],
+ Parent extends any[] =
+ | []
+ | [urlParams: { [k: string]: any }]
+ | [urlParams: { [k: string]: any }, body: { [k: string]: any }],
> extends schema.Collection {
createCollectionFilter(...args: Parent) {
return (collectionKey: { [k: string]: string }) =>
diff --git a/packages/endpoint/src/schemas/__tests__/Query.test.ts b/packages/endpoint/src/schemas/__tests__/Query.test.ts
index 5e4bcdb024a4..48f0afd9d8cf 100644
--- a/packages/endpoint/src/schemas/__tests__/Query.test.ts
+++ b/packages/endpoint/src/schemas/__tests__/Query.test.ts
@@ -131,6 +131,7 @@ describe.each([
const userCountByAdmin = new schema.Query(
usersSchema,
({ results }, { isAdmin }: { isAdmin?: boolean } = {}) => {
+ if (!results) return 0;
if (isAdmin === undefined) return results.length;
return results.filter(user => user.isAdmin === isAdmin).length;
},
diff --git a/packages/react/README.md b/packages/react/README.md
index 8400e486128f..aaaf06bca5e0 100644
--- a/packages/react/README.md
+++ b/packages/react/README.md
@@ -129,7 +129,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)
@@ -138,8 +138,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)
diff --git a/packages/react/src/__tests__/integration-index-endpoint.web.tsx b/packages/react/src/__tests__/integration-index-endpoint.web.tsx
index b185dd948cc6..a3d41414084d 100644
--- a/packages/react/src/__tests__/integration-index-endpoint.web.tsx
+++ b/packages/react/src/__tests__/integration-index-endpoint.web.tsx
@@ -1,5 +1,5 @@
import { act } from '@testing-library/react-hooks';
-import { IndexedUserResource, User } from '__tests__/new';
+import { IndexedUser, IndexedUserResource, User } from '__tests__/new';
import nock from 'nock';
import { useContext } from 'react';
@@ -7,14 +7,12 @@ import { useContext } from 'react';
import { makeRenderDataClient } from '../../../test';
import { CacheProvider } from '../components';
import { StateContext } from '../context';
-import { useCache, useSuspense, useController } from '../hooks';
+import { useSuspense, useController, useQuery } from '../hooks';
import {
payload,
createPayload,
users,
nested,
- paginatedFirstPage,
- paginatedSecondPage,
valuesFixture,
} from '../test-fixtures';
@@ -83,10 +81,10 @@ describe('indexes', () => {
const { fetch } = useController();
useSuspense(IndexedUserResource.getList);
return {
- bob: useCache(IndexedUserResource.getIndex, {
+ bob: useQuery(IndexedUser, {
username: 'bob',
}),
- charlie: useCache(IndexedUserResource.getIndex, {
+ charlie: useQuery(IndexedUser, {
username: 'charlie',
}),
fetch,
diff --git a/packages/redux/README.md b/packages/redux/README.md
index 03f470b15cde..2bc31ca815a5 100644
--- a/packages/redux/README.md
+++ b/packages/redux/README.md
@@ -74,7 +74,7 @@ return price.value;
### [Programmatic queries](https://dataclient.io/rest/api/Query)
```tsx
-const sortedArticles = new Query(
+const sortedArticles = new schema.Query(
new schema.All(Article),
(entries, { asc } = { asc: false }) => {
const sorted = [...entries].sort((a, b) => a.title.localeCompare(b.title));
@@ -83,9 +83,9 @@ const sortedArticles = new Query(
}
);
-const articlesUnsorted = useCache(sortedArticles);
-const articlesAscending = useCache(sortedArticles, { asc: true });
-const articlesDescending = useCache(sortedArticles, { asc: false });
+const articlesUnsorted = useQuery(sortedArticles);
+const articlesAscending = useQuery(sortedArticles, { asc: true });
+const articlesDescending = useQuery(sortedArticles, { asc: false });
```
### ...all typed ...fast ...and consistent
diff --git a/packages/rest/README.md b/packages/rest/README.md
index 446eed2d4c04..5dc1e00a90b3 100644
--- a/packages/rest/README.md
+++ b/packages/rest/README.md
@@ -121,13 +121,13 @@ const deleteTodo = data => ctrl.fetch(TodoResource.delete, { id });
### [Programmatic queries](https://dataclient.io/rest/api/Query)
```tsx
-const queryRemainingTodos = new Query(
+const queryRemainingTodos = new schema.Query(
TodoResource.getList.schema,
(entries) => entries.filter((todo) => !todo.completed).length,
);
-const allRemainingTodos = useCache(queryRemainingTodos);
-const firstUserRemainingTodos = useCache(queryRemainingTodos, { userId: 1 });
+const allRemainingTodos = useQuery(queryRemainingTodos);
+const firstUserRemainingTodos = useQuery(queryRemainingTodos, { userId: 1 });
```
### TypeScript requirements
diff --git a/website/sidebars-endpoint.json b/website/sidebars-endpoint.json
index 55a4bb16191d..ef2e2f3fa6a4 100644
--- a/website/sidebars-endpoint.json
+++ b/website/sidebars-endpoint.json
@@ -3,10 +3,6 @@
"type": "doc",
"id": "api/Endpoint"
},
- {
- "type": "doc",
- "id": "api/Index"
- },
{
"type": "doc",
"id": "api/schema"
@@ -47,6 +43,10 @@
"type": "doc",
"id": "api/All"
},
+ {
+ "type": "doc",
+ "id": "api/Query"
+ },
{
"type": "doc",
"id": "api/validateRequired"
diff --git a/website/sidebars-rest.js b/website/sidebars-rest.js
index 2c9edcecfd1c..d225d449fadb 100644
--- a/website/sidebars-rest.js
+++ b/website/sidebars-rest.js
@@ -79,14 +79,6 @@ module.exports = {
type: 'doc',
id: 'api/hookifyResource',
},
- {
- type: 'doc',
- id: 'api/Query',
- },
- {
- type: 'doc',
- id: 'api/Index',
- },
],
},
{
diff --git a/website/src/components/Playground/editor-types/@data-client/core.d.ts b/website/src/components/Playground/editor-types/@data-client/core.d.ts
index bff9680f8c60..273345d09746 100644
--- a/website/src/components/Playground/editor-types/@data-client/core.d.ts
+++ b/website/src/components/Playground/editor-types/@data-client/core.d.ts
@@ -1,15 +1,18 @@
type Schema = null | string | {
[K: string]: any;
} | Schema[] | SchemaSimple | Serializable;
+type Queryable = {
+ infer(args: readonly any[], indexes: NormalizedIndex, recurse: (...args: any) => any, entities: EntityTable): {};
+};
type Serializable = (value: any) => T;
-interface SchemaSimple {
- normalize(input: any, parent: any, key: any, visit: (...args: any) => any, addEntity: (...args: any) => any, visitedEntities: Record, storeEntities?: any, args?: any[]): any;
- denormalize(input: {}, args: any, unvisit: (input: any, schema: any) => any): T;
- infer(args: readonly any[], indexes: NormalizedIndex, recurse: (...args: any) => any, entities: EntityTable): any;
+interface SchemaSimple {
+ normalize(input: any, parent: any, key: any, visit: (...args: any) => any, addEntity: (...args: any) => any, visitedEntities: Record, storeEntities: any, args: any[]): any;
+ denormalize(input: {}, args: readonly any[], unvisit: (input: any, schema: any) => any): T;
+ infer(args: Args, indexes: NormalizedIndex, recurse: (...args: any) => any, entities: EntityTable): any;
}
interface EntityInterface extends SchemaSimple {
createIfValid(props: any): any;
@@ -36,6 +39,11 @@ interface EntityTable {
} | undefined;
}
+/** Attempts to infer reasonable input type to construct an Entity */
+type EntityFields = {
+ readonly [K in keyof U as U[K] extends (...args: any) => any ? never : K]?: U[K];
+};
+
/** Maps entity dependencies to a value (usually their denormalized form)
*
* Dependencies store `Path` to enable quick traversal using only `State`
@@ -86,16 +94,12 @@ interface NestedSchemaClass {
interface RecordClass extends NestedSchemaClass {
fromJS: (...args: any) => AbstractInstanceType;
}
-interface DenormalizeCache {
- entities: {
- [key: string]: {
- [pk: string]: WeakMap>;
- };
- };
- results: {
- [key: string]: WeakEntityMap