diff --git a/README.md b/README.md index 6c121ad0ac6f..055117a571c0 100644 --- a/README.md +++ b/README.md @@ -45,10 +45,6 @@ For more details, see [the Installation docs page](https://dataclient.io/docs/ge class User extends Entity { id = ''; username = ''; - - pk() { - return this.id; - } } class Article extends Entity { @@ -58,10 +54,6 @@ class Article extends Entity { author = User.fromJS(); createdAt = Temporal.Instant.fromEpochSeconds(0); - pk() { - return this.id; - } - static schema = { author: User, createdAt: Temporal.Instant.from, diff --git a/__tests__/new.ts b/__tests__/new.ts index 8e4839c583c8..4bccacd9ba8e 100644 --- a/__tests__/new.ts +++ b/__tests__/new.ts @@ -162,10 +162,6 @@ export class User extends Entity { readonly username: string = ''; readonly email: string = ''; readonly isAdmin: boolean = false; - - pk() { - return this.id?.toString(); - } } export const UserResource = resource({ path: 'http\\://test.com/user/:id', @@ -179,10 +175,6 @@ export class Article extends Entity { readonly author: User | null = null; readonly tags: string[] = []; - pk() { - return this.id?.toString(); - } - static schema = { author: User, }; @@ -621,10 +613,6 @@ export abstract class UnionBase extends Entity { readonly id: string = ''; readonly body: string = ''; readonly type: string = ''; - - pk() { - return this.id; - } } export class FirstUnion extends UnionBase { readonly type = 'first'; diff --git a/docs/core/api/useCache.md b/docs/core/api/useCache.md index 4fe6b6cbe082..1be487cb26bd 100644 --- a/docs/core/api/useCache.md +++ b/docs/core/api/useCache.md @@ -37,9 +37,7 @@ export class User extends Entity { id = ''; name = ''; isAdmin = false; - pk() { - return this.id; - } + static key = 'User'; } export const UserResource = resource({ diff --git a/docs/core/api/useDLE.md b/docs/core/api/useDLE.md index 4d8d11dc3b0d..d81913703112 100644 --- a/docs/core/api/useDLE.md +++ b/docs/core/api/useDLE.md @@ -34,9 +34,6 @@ export class Profile extends Entity { fullName = ''; bio = ''; - pk() { - return this.id?.toString(); - } static key = 'Profile'; } @@ -148,9 +145,6 @@ export class Profile extends Entity { fullName = ''; bio = ''; - pk() { - return this.id?.toString(); - } static key = 'Profile'; } @@ -202,9 +196,6 @@ export class Post extends Entity { title = ''; body = ''; - pk() { - return this.id?.toString(); - } static key = 'Post'; } export const PostResource = resource({ @@ -224,9 +215,6 @@ export class User extends Entity { return `https://i.pravatar.cc/64?img=${this.id + 4}`; } - pk() { - return `${this.id}`; - } static key = 'User'; } export const UserResource = resource({ @@ -272,9 +260,6 @@ export class PaginatedPost extends Entity { title = ''; content = ''; - pk() { - return this.id; - } static key = 'PaginatedPost'; } diff --git a/docs/core/api/useQuery.md b/docs/core/api/useQuery.md index 2f8cae75d236..0786ea1177dd 100644 --- a/docs/core/api/useQuery.md +++ b/docs/core/api/useQuery.md @@ -95,9 +95,7 @@ export class User extends Entity { id = ''; name = ''; isAdmin = false; - pk() { - return this.id; - } + static key = 'User'; } export const UserResource = resource({ diff --git a/docs/core/api/useSubscription.md b/docs/core/api/useSubscription.md index 601c6c82bde1..9423d2320385 100644 --- a/docs/core/api/useSubscription.md +++ b/docs/core/api/useSubscription.md @@ -91,7 +91,6 @@ function useSubscription< ### Only subscribe while element is visible ```tsx title="MasterPrice.tsx" -import { useRef } from 'react'; import { useSuspense, useSubscription } from '@data-client/react'; import { getPrice } from 'api/Price'; diff --git a/docs/core/api/useSuspense.md b/docs/core/api/useSuspense.md index 46d0ca90207a..4c94b6b96ec4 100644 --- a/docs/core/api/useSuspense.md +++ b/docs/core/api/useSuspense.md @@ -52,9 +52,6 @@ export class Profile extends Entity { fullName = ''; bio = ''; - pk() { - return this.id?.toString(); - } static key = 'Profile'; } @@ -204,9 +201,6 @@ export class Profile extends Entity { fullName = ''; bio = ''; - pk() { - return this.id?.toString(); - } static key = 'Profile'; } @@ -276,9 +270,6 @@ export class Post extends Entity { title = ''; body = ''; - pk() { - return this.id?.toString(); - } static key = 'Post'; } export const PostResource = resource({ @@ -298,9 +289,6 @@ export class User extends Entity { return `https://i.pravatar.cc/64?img=${this.id + 4}`; } - pk() { - return `${this.id}`; - } static key = 'User'; } export const UserResource = resource({ @@ -336,15 +324,12 @@ When entities are stored in [nested structures](/rest/guides/relational-data#nes -```typescript title="api/Post" {15-19} +```typescript title="api/Post" {12-16} export class PaginatedPost extends Entity { id = ''; title = ''; content = ''; - pk() { - return this.id; - } static key = 'PaginatedPost'; } diff --git a/docs/core/concepts/error-policy.md b/docs/core/concepts/error-policy.md index 8c795ca69468..23f26312fa10 100644 --- a/docs/core/concepts/error-policy.md +++ b/docs/core/concepts/error-policy.md @@ -49,9 +49,6 @@ delay: () => 150, export class TimedEntity extends Entity { id = ''; updatedAt = Temporal.Instant.fromEpochSeconds(0); - pk() { - return this.id; - } static schema = { updatedAt: Temporal.Instant.from, diff --git a/docs/core/concepts/expiry-policy.md b/docs/core/concepts/expiry-policy.md index f7eeb644b6ae..7ff8d213df1f 100644 --- a/docs/core/concepts/expiry-policy.md +++ b/docs/core/concepts/expiry-policy.md @@ -74,9 +74,6 @@ export class TimedEntity extends Entity { id = ''; updatedAt = Temporal.Instant.fromEpochSeconds(0); - pk() { - return this.id; - } static schema = { updatedAt: Temporal.Instant.from, }; @@ -216,9 +213,6 @@ delay: () => 150, export class TimedEntity extends Entity { id = ''; updatedAt = Temporal.Instant.fromEpochSeconds(0); - pk() { - return this.id; - } static schema = { updatedAt: Temporal.Instant.from, @@ -314,9 +308,6 @@ delay: () => 150, export class TimedEntity extends Entity { id = ''; updatedAt = Temporal.Instant.fromEpochSeconds(0); - pk() { - return this.id; - } static schema = { updatedAt: Temporal.Instant.from, @@ -378,9 +369,6 @@ delay: () => 150, export class TimedEntity extends Entity { id = ''; updatedAt = Temporal.Instant.fromEpochSeconds(0); - pk() { - return this.id; - } static schema = { updatedAt: Temporal.Instant.from, @@ -482,9 +470,6 @@ delay: () => 150, export class TimedEntity extends Entity { id = ''; updatedAt = Temporal.Instant.fromEpochSeconds(0); - pk() { - return this.id; - } static schema = { updatedAt: Temporal.Instant.from, @@ -595,9 +580,6 @@ delay: () => 150, export class TimedEntity extends Entity { id = ''; updatedAt = Temporal.Instant.fromEpochSeconds(0); - pk() { - return this.id; - } static schema = { updatedAt: Temporal.Instant.from, diff --git a/docs/core/concepts/validation.md b/docs/core/concepts/validation.md index 4e29a877e07f..e3d21eb008df 100644 --- a/docs/core/concepts/validation.md +++ b/docs/core/concepts/validation.md @@ -45,12 +45,8 @@ delay: 150, ```typescript title="api/Article" export class Article extends Entity { - readonly id: string = ''; - readonly title: string = ''; - - pk() { - return this.id; - } + id = ''; + title = ''; static validate(processedEntity) { if (!Object.hasOwn(processedEntity, 'title')) return 'missing title field'; @@ -79,7 +75,7 @@ render(); ### All fields check -Here's a recipe for checking that every defined field is present. +[validateRequired()](/rest/api/validateRequired) can be used to check if all defined fields are present. - Object.hasOwn(processedEntity, key), - ) - ) - return 'a field is missing'; + return validateRequired(processedEntity, this.defaults); } } diff --git a/docs/core/getting-started/data-dependency.md b/docs/core/getting-started/data-dependency.md index 6f05dc90be33..42cd80cbbf4e 100644 --- a/docs/core/getting-started/data-dependency.md +++ b/docs/core/getting-started/data-dependency.md @@ -40,9 +40,6 @@ export class User extends Entity { return `https://i.pravatar.cc/64?img=${this.id + 4}`; } - pk() { - return this.id; - } static key = 'User'; } export const UserResource = resource({ @@ -57,9 +54,6 @@ export class Post extends Entity { title = ''; body = ''; - pk() { - return this.id; - } static key = 'Post'; static schema = { @@ -322,9 +316,6 @@ export class Profile extends Entity { fullName = ''; bio = ''; - pk() { - return this.id?.toString(); - } static key = 'Profile'; } diff --git a/docs/core/getting-started/mutations.md b/docs/core/getting-started/mutations.md index 69dd7daa2710..f1c2d52b555e 100644 --- a/docs/core/getting-started/mutations.md +++ b/docs/core/getting-started/mutations.md @@ -35,9 +35,7 @@ export class Todo extends Entity { userId = 0; title = ''; completed = false; - pk() { - return `${this.id}`; - } + static key = 'Todo'; } export const TodoResource = resource({ diff --git a/docs/core/getting-started/resource.md b/docs/core/getting-started/resource.md index 8f03e3b490de..304431e3a7d1 100644 --- a/docs/core/getting-started/resource.md +++ b/docs/core/getting-started/resource.md @@ -49,9 +49,6 @@ export class Todo extends Entity { title = ''; completed = false; - pk() { - return `${this.id}`; - } static key = 'Todo'; } diff --git a/docs/core/guides/storybook.md b/docs/core/guides/storybook.md index 4a69b46cdb25..f21366d6cd4c 100644 --- a/docs/core/guides/storybook.md +++ b/docs/core/guides/storybook.md @@ -36,9 +36,6 @@ export class Article extends Entity { author: number | null = null; contributors: number[] = []; - pk() { - return this.id?.toString(); - } static key = 'Article'; } export const ArticleResource = resource({ diff --git a/docs/core/shared/_VoteDemo.mdx b/docs/core/shared/_VoteDemo.mdx index 97a2f7d27d79..6c9db6f78c97 100644 --- a/docs/core/shared/_VoteDemo.mdx +++ b/docs/core/shared/_VoteDemo.mdx @@ -13,9 +13,6 @@ export class Post extends Entity { body = ''; votes = 0; - pk() { - return this.id; - } static key = 'Post'; static schema = { diff --git a/docs/core/shared/_useCancelling.mdx b/docs/core/shared/_useCancelling.mdx index 222dfbd8f16f..3ba189e5bfdd 100644 --- a/docs/core/shared/_useCancelling.mdx +++ b/docs/core/shared/_useCancelling.mdx @@ -8,9 +8,7 @@ export class Todo extends Entity { userId = 0; title = ''; completed = false; - pk() { - return `${this.id}`; - } + static key = 'Todo'; } export const TodoResource = resource({ diff --git a/docs/core/shared/_useLoading.mdx b/docs/core/shared/_useLoading.mdx index 9e9bc33f21f0..35f57d4b0536 100644 --- a/docs/core/shared/_useLoading.mdx +++ b/docs/core/shared/_useLoading.mdx @@ -13,9 +13,6 @@ export class Post extends Entity { body = ''; votes = 0; - pk() { - return this.id; - } static key = 'Post'; get img() { diff --git a/docs/rest/README.md b/docs/rest/README.md index 393b5163856e..9144e08afc61 100644 --- a/docs/rest/README.md +++ b/docs/rest/README.md @@ -40,6 +40,8 @@ export class User extends Entity { pk() { return this.id; } + + static key = 'User'; } ``` @@ -59,12 +61,12 @@ export class Article extends Entity { return this.id; } + static key = 'Article'; + static schema = { author: User, createdAt: Temporal.Instant.from, }; - - static key = 'Article'; } export const ArticleResource = resource({ @@ -263,14 +265,20 @@ resolves to the new Resource created by the API. It will automatically be added import { useController } from '@data-client/react'; import { Article, ArticleResource } from 'api/article'; -export default function ArticleWithDelete({ article }: { article: Article }) { +export default function ArticleWithDelete({ + article, +}: { + article: Article; +}) { const ctrl = useController(); return (

{article.title}

{article.content}
diff --git a/docs/rest/api/Entity.md b/docs/rest/api/Entity.md index 880c1b882a58..be6c710dc614 100644 --- a/docs/rest/api/Entity.md +++ b/docs/rest/api/Entity.md @@ -35,7 +35,7 @@ import TypeScriptEditor from '@site/src/components/TypeScriptEditor'; performance, data consistency and atomic mutations. `Entities` enable customizing the data processing lifecycle by defining its static members like [schema](#schema) -and overriding its [lifecycle methods](#data-lifecycle). +and overriding its [lifecycle methods](#lifecycle). ## Usage @@ -48,6 +48,7 @@ export class User extends Entity { id = ''; username = ''; + static key = 'User'; pk() { return this.id; } @@ -101,19 +102,15 @@ used to make Entities. Other static members overrides allow customizing the data lifecycle as seen below. -## Data lifecycle - -import Lifecycle from '../diagrams/\_entity_lifecycle.mdx'; - - +## Members -## Methods +### pk(parent?, key?, args?): string | number | undefined {#pk} -### abstract pk(parent?, key?, args?): string? {#pk} +pk stands for [_primary key_](https://www.postgresql.org/docs/current/ddl-constraints.html#DDL-CONSTRAINTS-PRIMARY-KEYS), uniquely identifying an `Entity` instance. +By default this returns the an Entity's `id` field. -PK stands for _primary key_ and is intended to provide a standard means of retrieving -a key identifier for any `Entity`. In many cases there will simply be an 'id' field -member to return. In case of multicolumn you can simply join them together. +Override this method to use other fields, or to for other cases like +multicolumn primary keys. #### undefined value @@ -152,7 +149,7 @@ pk() { ### static key: string {#key} -This defines the key for the Entity itself, rather than an instance. This needs to be a globally +This defines the key for the Entity kind, rather than an instance. This needs to be a globally unique value. :::warning @@ -160,7 +157,7 @@ unique value. This defaults to `this.name`; however this may break in production builds that change class names. This is often know as [class name mangling](https://terser.org/docs/api-reference#mangle-options). -In these cases you can override `key` or disable class mangling. +In these cases you can override `key` or disable class name mangling. ::: @@ -168,15 +165,206 @@ In these cases you can override `key` or disable class mangling. class User extends Entity { id = ''; username = ''; + pk() { return this.id; } - // highlight-next-line static key = 'User'; } ``` +### static schema: \{ [k: keyof this]: Schema } {#schema} + +Defines [related entity](/rest/guides/relational-data) members, or +[field deserialization](/rest/guides/network-transform#deserializing-fields) like Date and BigNumber. + + + +```ts title="User" collapsed +import { Entity } from '@data-client/rest'; + +export class User extends Entity { + id = ''; + name = ''; + + pk() { + return this.id; + } + static key = 'User'; +} +``` + +```ts title="Post" {16-20} +import { Entity } from '@data-client/rest'; +import { User } from './User'; + +export class Post extends Entity { + id = ''; + author = User.fromJS(); + createdAt = Temporal.Instant.fromEpochSeconds(0); + content = ''; + title = ''; + + pk() { + return this.id; + } + static key = 'Post'; + + static schema = { + author: User, + createdAt: Temporal.Instant.from, + }; +} +``` + +```tsx title="PostPage" collapsed +import { Post } from './Post'; + +export const getPost = new RestEndpoint({ + path: '/posts/:id', + schema: Post, +}); +function PostPage() { + const post = useSuspense(getPost, { id: '123' }); + return ( +
+

+ {post.content} - {post.author.name} +

+ +
+ ); +} +render(); +``` + +
+ +#### Optional members + +Entities references here whose default values in the Record definition itself are +considered 'optional' + +```typescript +class User extends Entity { + friend: User | null = null; // this field is optional + lastUpdated = Temporal.Instant.fromEpochSeconds(0); + + static schema = { + friend: User, + lastUpdated: Temporal.Instant.from, + }; +} +``` + +### static indexes?: (keyof this)[] {#indexes} + +Indexes enable increased performance when doing lookups based on those parameters. Add +fieldnames (like `slug`, `username`) to the list that you want to send as params to lookup +later. + +:::note + +Don't add your primary key like `id` to the indexes list, as that will already be optimized. + +::: + +#### useSuspense() + +With [useSuspense()](/docs/api/useSuspense) this will eagerly infer the results from entities table if possible, +rendering without needing to complete the fetch. This is typically helpful when the entities +cache has already been populated by another request like a list request. + +```typescript +export class User extends Entity { + id: number | undefined = undefined; + username = ''; + email = ''; + isAdmin = false; + + // highlight-next-line + static indexes = ['username' as const]; +} +export const UserResource = resource({ + path: '/user/:id', + schema: User, +}); +``` + +```tsx +const user = useSuspense(UserResource.get, { username: 'bob' }); +``` + +#### useQuery() + +With [useQuery()](/docs/api/useQuery), this enables accessing results retrieved inside other requests - even +if there is no endpoint it can be fetched from. + +```typescript +class LatestPrice extends Entity { + id = ''; + symbol = ''; + price = '0.0'; + + static indexes = ['symbol' as const]; +} +``` + +```typescript +class Asset extends Entity { + id = ''; + price = ''; + + static schema = { + price: LatestPrice, + }; +} +const getAssets = new RestEndpoint({ + path: '/assets', + schema: [Asset], +}); +``` + +Some top level component: + +```tsx +const assets = useSuspense(getAssets); +``` + +Nested below: + +```tsx +const price = useQuery(LatestPrice, { symbol: 'BTC' }); +``` + +## Lifecycle + +import Lifecycle from '../diagrams/\_entity_lifecycle.mdx'; + + + +### static fromJS(props): Entity {#fromJS} + +Factory method that copies props to a new instance. Use this instead of `new MyEntity()`, +to ensure default props are overridden. + ### static process(input, parent, key, args): processedEntity {#process} Run at the start of normalization for this entity. Return value is saved in store @@ -444,190 +632,3 @@ By **default** does some basic field existance checks in development mode only. disable or customize. [Using validation for endpoints with incomplete fields](../guides/partial-entities.md) - -### static fromJS(props): Entity {#fromJS} - -Factory method that copies props to a new instance. Use this instead of `new MyEntity()`, -to ensure default props are overridden. - -## Fields - -### static schema: \{ [k: keyof this]: Schema } {#schema} - -Defines [related entity](/rest/guides/relational-data) members, or -[field deserialization](/rest/guides/network-transform#deserializing-fields) like Date and BigNumber. - - - -```ts title="User" collapsed -import { Entity } from '@data-client/rest'; - -export class User extends Entity { - id = ''; - name = ''; - pk() { - return this.id; - } -} -``` - -```ts title="Post" -import { Entity } from '@data-client/rest'; -import { User } from './User'; - -export class Post extends Entity { - id = ''; - author = User.fromJS({}); - createdAt = Temporal.Instant.fromEpochSeconds(0); - content = ''; - title = ''; - - static schema = { - author: User, - createdAt: Temporal.Instant.from, - }; - pk() { - return this.id; - } - static key = 'Post'; -} -``` - -```tsx title="PostPage" collapsed -import { Post } from './Post'; - -export const getPost = new RestEndpoint({ - path: '/posts/:id', - schema: Post, -}); -function PostPage() { - const post = useSuspense(getPost, { id: '123' }); - return ( -
-

- {post.content} - {post.author.name} -

- -
- ); -} -render(); -``` - -
- -#### Optional members - -Entities references here whose default values in the Record definition itself are -considered 'optional' - -```typescript -class User extends Entity { - friend: User | null = null; // this field is optional - lastUpdated = Temporal.Instant.fromEpochSeconds(0); - - static schema = { - friend: User, - lastUpdated: Temporal.Instant.from, - }; -} -``` - -### static indexes?: (keyof this)[] {#indexes} - -Indexes enable increased performance when doing lookups based on those parameters. Add -fieldnames (like `slug`, `username`) to the list that you want to send as params to lookup -later. - -:::note - -Don't add your primary key like `id` to the indexes list, as that will already be optimized. - -::: - -#### useSuspense() - -With [useSuspense()](/docs/api/useSuspense) this will eagerly infer the results from entities table if possible, -rendering without needing to complete the fetch. This is typically helpful when the entities -cache has already been populated by another request like a list request. - -```typescript -export class User extends Entity { - id: number | undefined = undefined; - username = ''; - email = ''; - isAdmin = false; - - pk() { - return this.id?.toString(); - } - - // highlight-next-line - static indexes = ['username' as const]; -} -export const UserResource = resource({ - path: '/user/:id', - schema: User, -}); -``` - -```tsx -const user = useSuspense(UserResource.get, { username: 'bob' }); -``` - -#### useQuery() - -With [useQuery()](/docs/api/useQuery), this enables accessing results retrieved inside other requests - even -if there is no endpoint it can be fetched from. - -```typescript -class LatestPrice extends Entity { - id = ''; - symbol = ''; - price = '0.0'; - static indexes = ['symbol' as const]; -} -``` - -```typescript -class Asset extends Entity { - id = ''; - price = ''; - - static schema = { - price: LatestPrice, - }; -} -const getAssets = new RestEndpoint({ - path: '/assets', - schema: [Asset], -}); -``` - -Some top level component: - -```tsx -const assets = useSuspense(getAssets); -``` - -Nested below: - -```tsx -const price = useQuery(LatestPrice, { symbol: 'BTC' }); -``` diff --git a/docs/rest/api/schema.Entity.md b/docs/rest/api/schema.Entity.md index 87b3b02c3b17..32138e65be7e 100644 --- a/docs/rest/api/schema.Entity.md +++ b/docs/rest/api/schema.Entity.md @@ -94,7 +94,7 @@ Specifies the [Entity.schema](./Entity.md#schema) ## Methods -`schema.Entity` mixin has the same [methods as the Entity](./Entity.md#methods) class. +`schema.Entity` mixin has the same [methods as the Entity](./Entity.md#lifecycle) class. ## const vs class diff --git a/docs/rest/guides/auth.md b/docs/rest/guides/auth.md index b79908cadd85..0c5227b6f8f3 100644 --- a/docs/rest/guides/auth.md +++ b/docs/rest/guides/auth.md @@ -38,9 +38,6 @@ import AuthdEndpoint from './AuthdEndpoint'; class MyEntity extends Entity { id = ''; title = ''; - pk() { - return this.id; - } } export const MyResource = resource({ @@ -120,9 +117,6 @@ import AuthdEndpoint from './AuthdEndpoint'; class MyEntity extends Entity { id = ''; title = ''; - pk() { - return this.id; - } } export const MyResource = resource({ diff --git a/docs/rest/guides/django.md b/docs/rest/guides/django.md index a1a0ffbdfab0..e4b422866a0e 100644 --- a/docs/rest/guides/django.md +++ b/docs/rest/guides/django.md @@ -66,9 +66,6 @@ import DjangoEndpoint from './DjangoEndpoint'; class MyEntity extends Entity { id = ''; title = ''; - pk() { - return this.id; - } } export const MyResource = resource({ diff --git a/docs/rest/guides/mocking-unfinished.md b/docs/rest/guides/mocking-unfinished.md index 42876850a379..3cff027dcd3e 100644 --- a/docs/rest/guides/mocking-unfinished.md +++ b/docs/rest/guides/mocking-unfinished.md @@ -20,9 +20,6 @@ export class Rating extends Entity { author = ''; date = Temporal.Instant.fromEpochSeconds(0); - pk() { - return this.id; - } static key = 'Rating'; static schema = { diff --git a/docs/rest/guides/optimistic-updates.md b/docs/rest/guides/optimistic-updates.md index 8885545f61b6..0fdc258eaaa2 100644 --- a/docs/rest/guides/optimistic-updates.md +++ b/docs/rest/guides/optimistic-updates.md @@ -26,7 +26,7 @@ handles these for you. -```ts title="TodoResource" {18} +```ts title="TodoResource" {16} import { Entity, resource } from '@data-client/rest'; export class Todo extends Entity { @@ -34,9 +34,7 @@ export class Todo extends Entity { userId = 0; title = ''; completed = false; - pk() { - return `${this.id}`; - } + static key = 'Todo'; } export const TodoResource = resource({ diff --git a/docs/rest/guides/pagination.md b/docs/rest/guides/pagination.md index c22cd2fe8c6a..4cd833f61f8b 100644 --- a/docs/rest/guides/pagination.md +++ b/docs/rest/guides/pagination.md @@ -129,10 +129,36 @@ interface Props { } ``` -```tsx title="ValidatorList" {12-16} +```tsx title="LoadMore" {8-11} +import { useController, useLoading } from '@data-client/react'; +import { getValidators } from './Validator'; + +export default function LoadMore({ next_key, limit }) { + const ctrl = useController(); + const [handleLoadMore, isPending] = useLoading( + () => + ctrl.fetch(getValidators.getPage, { + 'pagination.limit': limit, + 'pagination.key': next_key, + }), + [next_key, limit], + ); + if (!next_key) return null; + return ( +
+ +
+ ); +} +``` + +```tsx title="ValidatorList" collapsed import { useSuspense } from '@data-client/react'; import ValidatorItem from './ValidatorItem'; import { getValidators } from './Validator'; +import LoadMore from './LoadMore'; const PAGE_LIMIT = '3'; @@ -140,23 +166,13 @@ export default function ValidatorList() { const { validators, pagination } = useSuspense(getValidators, { 'pagination.limit': PAGE_LIMIT, }); - const ctrl = useController(); - const handleLoadMore = () => - ctrl.fetch(getValidators.getPage, { - 'pagination.limit': PAGE_LIMIT, - 'pagination.key': pagination.next_key, - }); return (
{validators.map(validator => ( ))} - {pagination.next_key ? ( -
- -
- ) : null} +
); } diff --git a/docs/rest/guides/partial-entities.md b/docs/rest/guides/partial-entities.md index f07e706f7248..4c59740a5040 100644 --- a/docs/rest/guides/partial-entities.md +++ b/docs/rest/guides/partial-entities.md @@ -74,9 +74,6 @@ export class ArticleSummary extends Entity { id = ''; title = ''; - pk() { - return this.id; - } // this ensures `Article` maps to the same entity static key = 'Article'; @@ -178,9 +175,6 @@ class ArticleSummary extends Entity { meta: ArticleMeta, }; - pk() { - return this.id; - } // this ensures `Article` maps to the same entity // highlight-next-line static key = 'Article'; diff --git a/docs/rest/guides/relational-data.md b/docs/rest/guides/relational-data.md index ba4e3ee46143..be94ea581c02 100644 --- a/docs/rest/guides/relational-data.md +++ b/docs/rest/guides/relational-data.md @@ -100,10 +100,6 @@ import { Entity } from '@data-client/rest'; export class User extends Entity { id = ''; name = ''; - - pk() { - return this.id; - } } export class Comment extends Entity { @@ -111,10 +107,6 @@ export class Comment extends Entity { content = ''; commenter = User.fromJS(); - pk() { - return this.id; - } - static schema = { commenter: User, }; @@ -126,10 +118,6 @@ export class Post extends Entity { author = User.fromJS(); comments: Comment[] = []; - pk() { - return this.id; - } - static schema = { author: User, comments: [Comment], @@ -193,9 +181,6 @@ export class User extends Entity { name = ''; email = ''; website = ''; - pk() { - return this.id; - } } export const UserResource = resource({ urlPrefix: 'https://jsonplaceholder.typicode.com', @@ -213,9 +198,6 @@ export class Todo extends Entity { user? = User.fromJS({}); title = ''; completed = false; - pk() { - return this.id; - } static schema = { user: User, }; @@ -348,10 +330,6 @@ export class User extends Entity { posts: Post[] = []; comments: Comment[] = []; - pk() { - return this.id; - } - static merge(existing, incoming) { return { ...existing, @@ -382,10 +360,6 @@ export class Comment extends Entity { commenter = User.fromJS(); post = Post.fromJS(); - pk() { - return this.id; - } - static schema: Record = { commenter: User, }; @@ -400,10 +374,6 @@ export class Post extends Entity { author = User.fromJS(); comments: Comment[] = []; - pk() { - return this.id; - } - static schema = { author: User, comments: [Comment], @@ -541,10 +511,6 @@ export class Post extends Entity { title = ''; author = User.fromJS(); - pk() { - return this.id; - } - static schema = { author: User, }; @@ -572,10 +538,6 @@ export class User extends Entity { posts: Post[] = []; createdAt = Temporal.Instant.fromEpochSeconds(0); - pk() { - return this.id; - } - static schema: Record = { createdAt: Temporal.Instant.from, }; diff --git a/docs/rest/shared/_SortDemo.mdx b/docs/rest/shared/_SortDemo.mdx index bf8c9620df12..0c6c66bddeaa 100644 --- a/docs/rest/shared/_SortDemo.mdx +++ b/docs/rest/shared/_SortDemo.mdx @@ -9,16 +9,13 @@ import { ```ts title="getPosts" {20-27} import { Entity, RestEndpoint } from '@data-client/rest'; -class Post extends Entity { +export class Post extends Entity { id = ''; title = ''; group = ''; author = ''; - - pk() { - return this.id; - } } + export const getPosts = new RestEndpoint({ path: '/:group/posts', searchParams: {} as { orderBy?: string; author?: string }, diff --git a/packages/core/src/controller/__tests__/get.ts b/packages/core/src/controller/__tests__/get.ts index ad5f62f3039e..01f7914349d5 100644 --- a/packages/core/src/controller/__tests__/get.ts +++ b/packages/core/src/controller/__tests__/get.ts @@ -7,9 +7,6 @@ describe('Controller.get()', () => { class Tacos extends Entity { type = ''; id = ''; - pk() { - return this.id; - } } const TacoList = new schema.Collection([Tacos]); const entities = { @@ -54,10 +51,6 @@ describe('Controller.get()', () => { id = ''; username = ''; - pk() { - return this.id; - } - static indexes = ['username'] as const; } @@ -223,9 +216,6 @@ describe('Controller.get()', () => { it('Union based on args', () => { class IDEntity extends Entity { id: string = ''; - pk() { - return this.id; - } } class User extends IDEntity { type = 'user'; diff --git a/packages/core/src/controller/__tests__/getResponse.ts b/packages/core/src/controller/__tests__/getResponse.ts index 856ab0bde42a..7f7f76dedc05 100644 --- a/packages/core/src/controller/__tests__/getResponse.ts +++ b/packages/core/src/controller/__tests__/getResponse.ts @@ -10,9 +10,6 @@ describe('Controller.getResponse()', () => { class Tacos extends Entity { type = ''; id = ''; - pk() { - return this.id; - } } const ep = new Endpoint(() => Promise.resolve(), { key() { @@ -129,9 +126,6 @@ describe('Controller.getResponse()', () => { class Tacos extends Entity { type = ''; id = ''; - pk() { - return this.id; - } } const ep = new Endpoint(({ id }: { id: string }) => Promise.resolve(), { key({ id }) { diff --git a/packages/core/src/state/__tests__/reducer.ts b/packages/core/src/state/__tests__/reducer.ts index 1ad2165c0922..688a4cc63229 100644 --- a/packages/core/src/state/__tests__/reducer.ts +++ b/packages/core/src/state/__tests__/reducer.ts @@ -211,9 +211,6 @@ describe('reducer', () => { class Counter extends Entity { id = 0; counter = 0; - pk() { - return this.id; - } static key = 'Counter'; } @@ -241,9 +238,6 @@ describe('reducer', () => { class Counter extends Entity { id = 0; counter = 0; - pk() { - return this.id; - } static key = 'Counter'; } diff --git a/packages/endpoint/src/__tests__/endpoint.ts b/packages/endpoint/src/__tests__/endpoint.ts index 2baa532f6580..d328c61fda28 100644 --- a/packages/endpoint/src/__tests__/endpoint.ts +++ b/packages/endpoint/src/__tests__/endpoint.ts @@ -507,9 +507,6 @@ describe.each([true, false])(`Endpoint (CSP %s)`, mockCSP => { const url = ({ id }: { id: string }) => `/users/${id}`; class User extends Entity { readonly id: string = ''; - pk() { - return this.id; - } } const UserDetail = new Endpoint( function ({ id }: { id: string }) { diff --git a/packages/endpoint/src/schemas/Entity.ts b/packages/endpoint/src/schemas/Entity.ts index 557520919440..fb47ccb15e96 100644 --- a/packages/endpoint/src/schemas/Entity.ts +++ b/packages/endpoint/src/schemas/Entity.ts @@ -16,20 +16,6 @@ const EmptyBase = class {} as any as abstract new (...args: any[]) => { * @see https://dataclient.io/rest/api/Entity */ export default abstract class Entity extends EntitySchema(EmptyBase) { - /** - * A unique identifier for each Entity - * - * @param [parent] When normalizing, the object which included the entity - * @param [key] When normalizing, the key where this entity was found - * @param [args] ...args sent to Endpoint - * @see https://dataclient.io/rest/api/Entity#pk - */ - abstract pk( - parent?: any, - key?: string, - args?: readonly any[], - ): string | number | undefined; - /** Control how automatic schema validation is handled * * `undefined`: Defaults - throw error in worst offense @@ -42,8 +28,8 @@ export default abstract class Entity extends EntitySchema(EmptyBase) { /** Factory method to convert from Plain JS Objects. * - * @param [props] Plain Object of properties to assign. * @see https://dataclient.io/rest/api/Entity#fromJS + * @param [props] Plain Object of properties to assign. */ declare static fromJS: ( this: T, @@ -54,6 +40,7 @@ export default abstract class Entity extends EntitySchema(EmptyBase) { /** * A unique identifier for each Entity * + * @see https://dataclient.io/rest/api/Entity#pk * @param [value] POJO of the entity or subset used * @param [parent] When normalizing, the object which included the entity * @param [key] When normalizing, the key where this entity was found @@ -113,18 +100,3 @@ First three members: ${JSON.stringify(input.slice(0, 3), null, 2)}`; unvisit: (schema: any, input: any) => any, ) => AbstractInstanceType; } - -if (process.env.NODE_ENV !== 'production') { - /* istanbul ignore else */ - const superFrom = Entity.fromJS; - // for those not using TypeScript this is a good catch to ensure they are defining - // the abstract members - Entity.fromJS = function fromJS( - this: T, - props?: Partial>, - ): AbstractInstanceType { - if ((this as any).prototype.pk === Entity.prototype.pk) - throw new Error('cannot construct on abstract types'); - return superFrom.call(this, props) as any; - }; -} diff --git a/packages/endpoint/src/schemas/EntitySchema.ts b/packages/endpoint/src/schemas/EntitySchema.ts index 159f582bfd86..8220ef44ed44 100644 --- a/packages/endpoint/src/schemas/EntitySchema.ts +++ b/packages/endpoint/src/schemas/EntitySchema.ts @@ -39,16 +39,16 @@ export default function EntitySchema( /** * A unique identifier for each Entity * + * @see https://dataclient.io/rest/api/Entity#pk * @param [parent] When normalizing, the object which included the entity * @param [key] When normalizing, the key where this entity was found * @param [args] ...args sent to Endpoint - * @see https://dataclient.io/docs/api/schema.Entity#pk */ - abstract pk( + declare pk: ( parent?: any, key?: string, args?: readonly any[], - ): string | number | undefined; + ) => string | number | undefined; /** Returns the globally unique identifier for the static Entity */ declare static key: string; @@ -60,7 +60,7 @@ export default function EntitySchema( /** * A unique identifier for each Entity * - * @see https://dataclient.io/docs/api/schema.Entity#pk + * @see https://dataclient.io/rest/api/Entity#pk * @param [value] POJO of the entity or subset used * @param [parent] When normalizing, the object which included the entity * @param [key] When normalizing, the key where this entity was found @@ -78,7 +78,7 @@ export default function EntitySchema( /** Return true to merge incoming data; false keeps existing entity * - * @see https://dataclient.io/docs/api/schema.Entity#shouldUpdate + * @see https://dataclient.io/rest/api/Entity#shouldUpdate */ static shouldUpdate( existingMeta: { date: number; fetchedAt: number }, @@ -91,7 +91,7 @@ export default function EntitySchema( /** Determines the order of incoming entity vs entity already in store\ * - * @see https://dataclient.io/docs/api/schema.Entity#shouldReorder + * @see https://dataclient.io/rest/api/Entity#shouldReorder * @returns true if incoming entity should be first argument of merge() */ static shouldReorder( @@ -105,7 +105,7 @@ export default function EntitySchema( /** Creates new instance copying over defined values of arguments * - * @see https://dataclient.io/docs/api/schema.Entity#merge + * @see https://dataclient.io/rest/api/Entity#merge */ static merge(existing: any, incoming: any) { return { @@ -116,7 +116,7 @@ export default function EntitySchema( /** Run when an existing entity is found in the store * - * @see https://dataclient.io/docs/api/schema.Entity#mergeWithStore + * @see https://dataclient.io/rest/api/Entity#mergeWithStore */ static mergeWithStore( existingMeta: { @@ -152,7 +152,7 @@ export default function EntitySchema( /** Run when an existing entity is found in the store * - * @see https://dataclient.io/docs/api/schema.Entity#mergeMetaWithStore + * @see https://dataclient.io/rest/api/Entity#mergeMetaWithStore */ static mergeMetaWithStore( existingMeta: { @@ -190,8 +190,8 @@ export default function EntitySchema( /** Called when denormalizing an entity to create an instance when 'valid' * + * @see https://dataclient.io/rest/api/Entity#createIfValid * @param [props] Plain Object of properties to assign. - * @see https://dataclient.io/docs/api/schema.Entity#createIfValid */ static createIfValid( this: T, @@ -206,7 +206,7 @@ export default function EntitySchema( /** Do any transformations when first receiving input * - * @see https://dataclient.io/docs/api/schema.Entity#process + * @see https://dataclient.io/rest/api/Entity#process */ static process( input: any, @@ -235,12 +235,22 @@ export default function EntitySchema( id = `MISS-${Math.random()}`; // 'creates' conceptually should allow missing PK to make optimistic creates easy if (process.env.NODE_ENV !== 'production' && !visit.creating) { + let why: string; + if ( + !('pk' in options) && + EntityMixin.prototype.pk === this.prototype.pk && + !('id' in processedEntity) + ) { + why = `'id' missing but needed for default pk(). Try defining pk() for your Entity.`; + } else { + why = `This is likely due to a malformed response. + Try inspecting the network response or fetch() return value. + Or use debugging tools: https://dataclient.io/docs/getting-started/debugging`; + } const error = new Error( `Missing usable primary key when normalizing response. - This is likely due to a malformed response. - Try inspecting the network response or fetch() return value. - Or use debugging tools: https://dataclient.io/docs/getting-started/debugging + ${why} Learn more about primary keys: https://dataclient.io/rest/api/Entity#pk Entity: ${this.key} diff --git a/packages/endpoint/src/schemas/__tests__/Entity.test.ts b/packages/endpoint/src/schemas/__tests__/Entity.test.ts index c356cbf39d50..7519d3db99e0 100644 --- a/packages/endpoint/src/schemas/__tests__/Entity.test.ts +++ b/packages/endpoint/src/schemas/__tests__/Entity.test.ts @@ -34,9 +34,6 @@ class ArticleEntity extends Entity { readonly title: string = ''; readonly author: string = ''; readonly content: string = ''; - pk() { - return this.id; - } } class WithOptional extends Entity { @@ -45,10 +42,6 @@ class WithOptional extends Entity { readonly requiredArticle = ArticleEntity.fromJS(); readonly nextPage: string = ''; - pk() { - return this.id; - } - static schema = { article: ArticleEntity, requiredArticle: ArticleEntity, @@ -65,13 +58,20 @@ describe(`${Entity.name} normalization`, () => { ); test('normalizes an entity', () => { - class MyEntity extends IDEntity {} + class MyEntity extends Entity {} expect(normalize(MyEntity, { id: '1' })).toMatchSnapshot(); }); + test('normalize throws error when id missing with no pk', () => { + class MyEntity extends Entity {} + expect(() => + normalize(MyEntity, { slug: '1' }), + ).toThrowErrorMatchingSnapshot(); + }); + test('normalizes already processed entities', () => { - class MyEntity extends IDEntity {} - class Nested extends IDEntity { + class MyEntity extends Entity {} + class Nested extends Entity { title = ''; nest = MyEntity.fromJS(); static schema = { @@ -88,7 +88,7 @@ describe(`${Entity.name} normalization`, () => { }); test('normalizes does not change value when shouldUpdate() returns false', () => { - class MyEntity extends IDEntity { + class MyEntity extends Entity { id = ''; title = ''; static shouldUpdate() { @@ -607,9 +607,9 @@ describe(`${Entity.name} denormalization`, () => { expect(denormalize(Tacos, '1', fromJS(entities))).toMatchSnapshot(); }); - class Food extends IDEntity {} - class Menu extends IDEntity { - readonly food: Food = Food.fromJS(); + class Food extends Entity {} + class Menu extends Entity { + food: Food = Food.fromJS(); static schema = { food: Food }; } diff --git a/packages/endpoint/src/schemas/__tests__/EntitySchema.test.ts b/packages/endpoint/src/schemas/__tests__/EntitySchema.test.ts index f3242d5833d5..b94304efbbcd 100644 --- a/packages/endpoint/src/schemas/__tests__/EntitySchema.test.ts +++ b/packages/endpoint/src/schemas/__tests__/EntitySchema.test.ts @@ -146,6 +146,9 @@ describe(`${schema.Entity.name} construction`, () => { expect(MyEntity.pk({ username: 'bob' })).toBeUndefined(); // @ts-expect-error expect(MyEntity.fromJS({ username: 'bob' }).pk()).toBeUndefined(); + expect(() => + normalize(MyEntity, { username: 'bob' }), + ).toThrowErrorMatchingSnapshot(); }); it('should use id field if no pk specified', () => { class MyData { diff --git a/packages/endpoint/src/schemas/__tests__/Object.test.js b/packages/endpoint/src/schemas/__tests__/Object.test.js index 60a3c5405db1..b1b594b4069e 100644 --- a/packages/endpoint/src/schemas/__tests__/Object.test.js +++ b/packages/endpoint/src/schemas/__tests__/Object.test.js @@ -33,11 +33,7 @@ describe(`${schema.Object.name} normalization`, () => { }); test('filters out undefined and null values', () => { - class User extends Entity { - pk() { - return this.id; - } - } + class User extends Entity {} const users = new schema.Object({ foo: User, bar: User, baz: User }); const oldenv = process.env.NODE_ENV; process.env.NODE_ENV = 'production'; diff --git a/packages/endpoint/src/schemas/__tests__/__snapshots__/Entity.test.ts.snap b/packages/endpoint/src/schemas/__tests__/__snapshots__/Entity.test.ts.snap index a20a36e28867..77b8bd13d374 100644 --- a/packages/endpoint/src/schemas/__tests__/__snapshots__/Entity.test.ts.snap +++ b/packages/endpoint/src/schemas/__tests__/__snapshots__/Entity.test.ts.snap @@ -45,9 +45,7 @@ Menu { exports[`Entity denormalization denormalizes deep entities 2`] = ` Menu { - "food": Food { - "id": undefined, - }, + "food": Food {}, "id": "2", } `; @@ -238,6 +236,19 @@ exports[`Entity normalization mergeStrategy defaults to plain merging 1`] = ` } `; +exports[`Entity normalization normalize throws error when id missing with no pk 1`] = ` +"Missing usable primary key when normalizing response. + + 'id' missing but needed for default pk(). Try defining pk() for your Entity. + Learn more about primary keys: https://dataclient.io/rest/api/Entity#pk + + Entity: MyEntity + Value (processed): { + "slug": "1" +} +" +`; + exports[`Entity normalization normalizes already processed entities 1`] = ` { "entities": {}, diff --git a/packages/endpoint/src/schemas/__tests__/__snapshots__/EntitySchema.test.ts.snap b/packages/endpoint/src/schemas/__tests__/__snapshots__/EntitySchema.test.ts.snap index 70a8c9c08930..98706cc78277 100644 --- a/packages/endpoint/src/schemas/__tests__/__snapshots__/EntitySchema.test.ts.snap +++ b/packages/endpoint/src/schemas/__tests__/__snapshots__/EntitySchema.test.ts.snap @@ -1,5 +1,18 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`EntitySchema construction pk should fail with no id and pk unspecified 1`] = ` +"Missing usable primary key when normalizing response. + + 'id' missing but needed for default pk(). Try defining pk() for your Entity. + Learn more about primary keys: https://dataclient.io/rest/api/Entity#pk + + Entity: MyEntity + Value (processed): { + "username": "bob" +} +" +`; + exports[`EntitySchema denormalization can denormalize already partially denormalized data 1`] = ` Menu { "food": Food { diff --git a/packages/graphql/src/GQLEntity.ts b/packages/graphql/src/GQLEntity.ts index a322856b1658..e48ac3d1e9d2 100644 --- a/packages/graphql/src/GQLEntity.ts +++ b/packages/graphql/src/GQLEntity.ts @@ -2,7 +2,4 @@ import { Entity } from '@data-client/endpoint'; export default class GQLEntity extends Entity { readonly id: string = ''; - pk() { - return this.id; - } } diff --git a/packages/normalizr/src/__tests__/MemoCache.ts b/packages/normalizr/src/__tests__/MemoCache.ts index 15860a37b506..42ab58e4a786 100644 --- a/packages/normalizr/src/__tests__/MemoCache.ts +++ b/packages/normalizr/src/__tests__/MemoCache.ts @@ -634,7 +634,7 @@ describe('MemoCache', () => { {}, ), ).toEqual({ - data: { article: '5' }, + data: { article: 5 }, }); }); @@ -785,7 +785,7 @@ describe('MemoCache', () => { ), ).toEqual({ pagination: { next: '', previous: '' }, - data: '5', + data: 5, }); }); diff --git a/packages/react/README.md b/packages/react/README.md index b527db9dceb5..aebd80bb8e6f 100644 --- a/packages/react/README.md +++ b/packages/react/README.md @@ -47,10 +47,6 @@ For more details, see [the Installation docs page](https://dataclient.io/docs/ge class User extends Entity { id = ''; username = ''; - - pk() { - return this.id; - } } class Article extends Entity { @@ -60,10 +56,6 @@ class Article extends Entity { author = User.fromJS(); createdAt = Temporal.Instant.fromEpochSeconds(0); - pk() { - return this.id; - } - static schema = { author: User, createdAt: Temporal.Instant.from, diff --git a/packages/rest/README.md b/packages/rest/README.md index 6799229b6177..d8c7e5967a54 100644 --- a/packages/rest/README.md +++ b/packages/rest/README.md @@ -51,9 +51,6 @@ class Todo extends Entity { userId = 0; title = ''; completed = false; - pk() { - return `${this.id}`; - } } const TodoResource = resource({ urlPrefix: 'https://jsonplaceholder.typicode.com', @@ -136,7 +133,7 @@ const groupTodoByUser = new schema.Query( todos => Object.groupBy(todos, todo => todo.userId), ); const todosByUser = useQuery(groupTodoByUser); -``` +```\ ### TypeScript requirements diff --git a/packages/rest/src/__tests__/Resource.test.ts b/packages/rest/src/__tests__/Resource.test.ts index 0b22be9ac578..785442757161 100644 --- a/packages/rest/src/__tests__/Resource.test.ts +++ b/packages/rest/src/__tests__/Resource.test.ts @@ -5,8 +5,8 @@ import { CacheProvider } from '@data-client/react'; import nock from 'nock'; import { makeRenderDataClient } from '../../../test'; -import resource from '../resource'; import { ResourcePath } from '../pathTypes'; +import resource from '../resource'; import RestEndpoint from '../RestEndpoint'; import { payload, @@ -25,10 +25,6 @@ export class User extends Entity { readonly username: string = ''; readonly email: string = ''; readonly isAdmin: boolean = false; - - pk() { - return this.id?.toString(); - } } export const UserResource = resource({ path: 'http\\://test.com/user/:id', @@ -41,10 +37,6 @@ export class PaginatedArticle extends Entity { readonly author: number | null = null; readonly tags: string[] = []; - pk() { - return this.id?.toString(); - } - static schema = { author: User, }; diff --git a/packages/rest/src/__tests__/RestEndpoint.ts b/packages/rest/src/__tests__/RestEndpoint.ts index 2f34f84efa9e..17baae05355e 100644 --- a/packages/rest/src/__tests__/RestEndpoint.ts +++ b/packages/rest/src/__tests__/RestEndpoint.ts @@ -26,10 +26,6 @@ export class User extends Entity { readonly username: string = ''; readonly email: string = ''; readonly isAdmin: boolean = false; - - pk() { - return this.id?.toString(); - } } const getUser = new RestEndpoint({ path: 'http\\://test.com/user/:id', @@ -45,10 +41,6 @@ export class PaginatedArticle extends Entity { readonly author: number | null = null; readonly tags: string[] = []; - pk() { - return this.id?.toString(); - } - static schema = { author: User, }; @@ -804,10 +796,6 @@ describe('RestEndpoint', () => { readonly username2: string = ''; readonly email: string = ''; readonly isAdmin: boolean = false; - - pk() { - return this.id?.toString(); - } } const getUserBase = new MyEndpoint({ diff --git a/website/src/components/Demo/code/posts-app/rest/resources.ts b/website/src/components/Demo/code/posts-app/rest/resources.ts index 9eb4f4db3a46..2a03875a510c 100644 --- a/website/src/components/Demo/code/posts-app/rest/resources.ts +++ b/website/src/components/Demo/code/posts-app/rest/resources.ts @@ -6,9 +6,6 @@ export class Post extends Entity { userId = 0; title = ''; body = ''; - pk() { - return `${this.id}`; - } } export const PostResource = resource({ urlPrefix: 'https://jsonplaceholder.typicode.com', @@ -29,10 +26,6 @@ export class User extends Entity { get profileImage() { return `//i.pravatar.cc/64?img=${this.id + 4}`; } - - pk() { - return `${this.id}`; - } } export const UserResource = resource({ urlPrefix: 'https://jsonplaceholder.typicode.com', diff --git a/website/src/components/Demo/code/profile-edit/rest/resources.ts b/website/src/components/Demo/code/profile-edit/rest/resources.ts index a30e228be7f0..aaa84449da69 100644 --- a/website/src/components/Demo/code/profile-edit/rest/resources.ts +++ b/website/src/components/Demo/code/profile-edit/rest/resources.ts @@ -5,9 +5,6 @@ export class Post extends Entity { userId = 0; title = ''; body = ''; - pk() { - return this.id; - } } export const PostResource = resource({ urlPrefix: 'https://jsonplaceholder.typicode.com', @@ -28,10 +25,6 @@ export class User extends Entity { get profileImage() { return `https://i.pravatar.cc/64?img=${this.id + 4}`; } - - pk() { - return this.id; - } } export const UserResource = resource({ urlPrefix: 'https://jsonplaceholder.typicode.com', diff --git a/website/src/components/Demo/code/todo-app/rest/resources.ts b/website/src/components/Demo/code/todo-app/rest/resources.ts index e0962e2dec54..7639e186bef1 100644 --- a/website/src/components/Demo/code/todo-app/rest/resources.ts +++ b/website/src/components/Demo/code/todo-app/rest/resources.ts @@ -5,9 +5,6 @@ export class Todo extends Entity { userId = 0; title = ''; completed = false; - pk() { - return this.id; - } } export const TodoResource = resource({ urlPrefix: 'https://jsonplaceholder.typicode.com', @@ -29,10 +26,6 @@ export class User extends Entity { return `https://i.pravatar.cc/64?img=${this.id + 4}`; } - pk() { - return this.id; - } - static schema = { todos: new schema.Collection([Todo], { nestKey: (parent, key) => ({ diff --git a/website/src/components/Playground/editor-types/@data-client/endpoint.d.ts b/website/src/components/Playground/editor-types/@data-client/endpoint.d.ts index 22ab850d2e93..37b181e6fea8 100644 --- a/website/src/components/Playground/editor-types/@data-client/endpoint.d.ts +++ b/website/src/components/Playground/editor-types/@data-client/endpoint.d.ts @@ -1113,15 +1113,6 @@ declare const Entity_base: IEntityClass { * @see https://dataclient.io/rest/api/Entity */ declare abstract class Entity extends Entity_base { - /** - * A unique identifier for each Entity - * - * @param [parent] When normalizing, the object which included the entity - * @param [key] When normalizing, the key where this entity was found - * @param [args] ...args sent to Endpoint - * @see https://dataclient.io/rest/api/Entity#pk - */ - abstract pk(parent?: any, key?: string, args?: readonly any[]): string | number | undefined; /** Control how automatic schema validation is handled * * `undefined`: Defaults - throw error in worst offense @@ -1133,13 +1124,14 @@ declare abstract class Entity extends Entity_base { protected static automaticValidation?: 'warn' | 'silent'; /** Factory method to convert from Plain JS Objects. * - * @param [props] Plain Object of properties to assign. * @see https://dataclient.io/rest/api/Entity#fromJS + * @param [props] Plain Object of properties to assign. */ static fromJS: (this: T, props?: Partial>) => AbstractInstanceType; /** * A unique identifier for each Entity * + * @see https://dataclient.io/rest/api/Entity#pk * @param [value] POJO of the entity or subset used * @param [parent] When normalizing, the object which included the entity * @param [key] When normalizing, the key where this entity was found diff --git a/website/src/components/Playground/editor-types/@data-client/graphql.d.ts b/website/src/components/Playground/editor-types/@data-client/graphql.d.ts index 915307f776e6..9d52cfbb5fa3 100644 --- a/website/src/components/Playground/editor-types/@data-client/graphql.d.ts +++ b/website/src/components/Playground/editor-types/@data-client/graphql.d.ts @@ -1113,15 +1113,6 @@ declare const Entity_base: IEntityClass { * @see https://dataclient.io/rest/api/Entity */ declare abstract class Entity extends Entity_base { - /** - * A unique identifier for each Entity - * - * @param [parent] When normalizing, the object which included the entity - * @param [key] When normalizing, the key where this entity was found - * @param [args] ...args sent to Endpoint - * @see https://dataclient.io/rest/api/Entity#pk - */ - abstract pk(parent?: any, key?: string, args?: readonly any[]): string | number | undefined; /** Control how automatic schema validation is handled * * `undefined`: Defaults - throw error in worst offense @@ -1133,13 +1124,14 @@ declare abstract class Entity extends Entity_base { protected static automaticValidation?: 'warn' | 'silent'; /** Factory method to convert from Plain JS Objects. * - * @param [props] Plain Object of properties to assign. * @see https://dataclient.io/rest/api/Entity#fromJS + * @param [props] Plain Object of properties to assign. */ static fromJS: (this: T, props?: Partial>) => AbstractInstanceType; /** * A unique identifier for each Entity * + * @see https://dataclient.io/rest/api/Entity#pk * @param [value] POJO of the entity or subset used * @param [parent] When normalizing, the object which included the entity * @param [key] When normalizing, the key where this entity was found @@ -1163,7 +1155,6 @@ type NI = NoInfer; declare class GQLEntity extends Entity { readonly id: string; - pk(): string; } interface GQLOptions extends EndpointOptions<(v: Variables) => Promise, S, M> { diff --git a/website/src/components/Playground/editor-types/@data-client/rest.d.ts b/website/src/components/Playground/editor-types/@data-client/rest.d.ts index 937096963583..224349615e16 100644 --- a/website/src/components/Playground/editor-types/@data-client/rest.d.ts +++ b/website/src/components/Playground/editor-types/@data-client/rest.d.ts @@ -1111,15 +1111,6 @@ declare const Entity_base: IEntityClass { * @see https://dataclient.io/rest/api/Entity */ declare abstract class Entity extends Entity_base { - /** - * A unique identifier for each Entity - * - * @param [parent] When normalizing, the object which included the entity - * @param [key] When normalizing, the key where this entity was found - * @param [args] ...args sent to Endpoint - * @see https://dataclient.io/rest/api/Entity#pk - */ - abstract pk(parent?: any, key?: string, args?: readonly any[]): string | number | undefined; /** Control how automatic schema validation is handled * * `undefined`: Defaults - throw error in worst offense @@ -1131,13 +1122,14 @@ declare abstract class Entity extends Entity_base { protected static automaticValidation?: 'warn' | 'silent'; /** Factory method to convert from Plain JS Objects. * - * @param [props] Plain Object of properties to assign. * @see https://dataclient.io/rest/api/Entity#fromJS + * @param [props] Plain Object of properties to assign. */ static fromJS: (this: T, props?: Partial>) => AbstractInstanceType; /** * A unique identifier for each Entity * + * @see https://dataclient.io/rest/api/Entity#pk * @param [value] POJO of the entity or subset used * @param [parent] When normalizing, the object which included the entity * @param [key] When normalizing, the key where this entity was found diff --git a/website/src/components/Playground/editor-types/globals.d.ts b/website/src/components/Playground/editor-types/globals.d.ts index ad4422a7f900..fa2c3a2ee1f8 100644 --- a/website/src/components/Playground/editor-types/globals.d.ts +++ b/website/src/components/Playground/editor-types/globals.d.ts @@ -1115,15 +1115,6 @@ declare const Entity_base: IEntityClass { * @see https://dataclient.io/rest/api/Entity */ declare abstract class Entity extends Entity_base { - /** - * A unique identifier for each Entity - * - * @param [parent] When normalizing, the object which included the entity - * @param [key] When normalizing, the key where this entity was found - * @param [args] ...args sent to Endpoint - * @see https://dataclient.io/rest/api/Entity#pk - */ - abstract pk(parent?: any, key?: string, args?: readonly any[]): string | number | undefined; /** Control how automatic schema validation is handled * * `undefined`: Defaults - throw error in worst offense @@ -1135,13 +1126,14 @@ declare abstract class Entity extends Entity_base { protected static automaticValidation?: 'warn' | 'silent'; /** Factory method to convert from Plain JS Objects. * - * @param [props] Plain Object of properties to assign. * @see https://dataclient.io/rest/api/Entity#fromJS + * @param [props] Plain Object of properties to assign. */ static fromJS: (this: T, props?: Partial>) => AbstractInstanceType; /** * A unique identifier for each Entity * + * @see https://dataclient.io/rest/api/Entity#pk * @param [value] POJO of the entity or subset used * @param [parent] When normalizing, the object which included the entity * @param [key] When normalizing, the key where this entity was found