Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .agents/skills/data-client-rest/references/Entity.vue.md
Original file line number Diff line number Diff line change
Expand Up @@ -685,7 +685,7 @@ static mergeMetaWithStore(

### static queryKey(args, queryKey, getEntity, getIndex): pk? {#queryKey}

This method enables `Entities` to be [Queryable](./schema.md#queryable) - allowing store access without an endpoint.
This method enables `Entities` to be [Queryable](./schema.vue.md#queryable) - allowing store access without an endpoint.

Overriding can allow customization or disabling of this behavior altogether.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ export const getTodo = new RestEndpoint({
export const updateTodo = getTodo.extend({ method: 'PUT' });
```

Using a [Schema](./schema.md) enables [automatic data consistency](https://dataclient.io/vue/concepts/normalization) without the need to hurt performance with [refetching](https://dataclient.io/vue/api/Controller#expireAll).
Using a [Schema](./schema.vue.md) enables [automatic data consistency](https://dataclient.io/vue/concepts/normalization) without the need to hurt performance with [refetching](https://dataclient.io/vue/api/Controller#expireAll).

### Typing

Expand Down Expand Up @@ -826,9 +826,9 @@ const getUserWithId = getUser.extend({

### schema?: Schema {#schema}

[Declarative data lifecycle](./schema.md)
[Declarative data lifecycle](./schema.vue.md)

- Global data consistency and performance with [DRY](https://www.plutora.com/blog/understanding-the-dry-dont-repeat-yourself-principle) state: [where](./schema.md) to expect [Entities](./Entity.vue.md)
- Global data consistency and performance with [DRY](https://www.plutora.com/blog/understanding-the-dry-dont-repeat-yourself-principle) state: [where](./schema.vue.md) to expect [Entities](./Entity.vue.md)
- Functions to [deserialize fields](./network-transform.vue.md#deserializing-fields)
- [Race condition handling](./Entity.vue.md#shouldreorder)
- [Validation](./Entity.vue.md#validate)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ static mergeMetaWithStore(

### static queryKey(args, queryKey, getEntity, getIndex): pk? {#queryKey}

This method enables `Entities` to be [Queryable](./schema.md#queryable) - allowing store access without an endpoint.
This method enables `Entities` to be [Queryable](./schema.vue.md#queryable) - allowing store access without an endpoint.

Overriding can allow customization or disabling of this behavior altogether.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export const getPrice = new RestEndpoint({
### Deserializing Date

In case you want to use legacy [Date](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date),
you can turn the constructor into a function [schema](./schema.md).
you can turn the constructor into a function [schema](./schema.vue.md).

```ts
export class ExchangePrice extends Entity {
Expand Down
2 changes: 1 addition & 1 deletion .agents/skills/data-client-rest/references/resource.vue.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# Resource

`Resources` are a collection of [RestEndpoints](./RestEndpoint.vue.md) that operate on a common
data by sharing a [schema](./schema.md)
data by sharing a [schema](./schema.vue.md)

## Usage

Expand Down
267 changes: 267 additions & 0 deletions .agents/skills/data-client-rest/references/schema.vue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
<!-- Generated by `yarn build:skills` from docs/rest/api/schema.md (vue). Edit the source doc, not this file. -->

# Thinking in Schemas

Consider a typical blog post. The API response for a single post might look something like this:

```json
{
"id": "123",
"author": {
"id": "1",
"name": "Paul"
},
"title": "My awesome blog post",
"comments": [
{
"id": "324",
"createdAt": "2013-05-29T00:00:00-04:00",
"commenter": {
"id": "2",
"name": "Nicole"
}
},
{
"id": "544",
"createdAt": "2013-05-30T00:00:00-04:00",
"commenter": {
"id": "1",
"name": "Paul"
}
}
]
}
```

## Declarative definitions

We have two nested [entity](./Entity.vue.md) types within our `article`: `users` and `comments`. Using various [schema](./Entity.vue.md#schema), we can normalize all three entity types down:

```typescript
import { schema, Entity } from '@data-client/endpoint';
import { Temporal } from 'temporal-polyfill';

class User extends Entity {
id = '';
name = '';
}

class Comment extends Entity {
id = '';
createdAt = Temporal.Instant.fromEpochMilliseconds(0);
commenter = User.fromJS();

static schema = {
commenter: User,
createdAt: Temporal.Instant.from,
};
}

class Article extends Entity {
id = '';
title = '';
author = User.fromJS();
comments: Comment[] = [];

static schema = {
author: User,
comments: [Comment],
};
}
```

```javascript
import { schema, Entity } from '@data-client/endpoint';
import { Temporal } from 'temporal-polyfill';

class User extends Entity { }

class Comment extends Entity {
static schema = {
commenter: User,
createdAt: Temporal.Instant.from,
};
}

class Article extends Entity {
static schema = {
author: User,
comments: [Comment],
};
}
```

## Normalize

```js
import { normalize } from '@data-client/normalizr';

const args = [{ id: '123' }];
const normalizedData = normalize(Article, originalData, args);
```

Now, `normalizedData` will create a single serializable source of truth for all entities:

```js
{
result: "123",
entities: {
articles: {
"123": {
id: "123",
author: "1",
title: "My awesome blog post",
comments: [ "324", "544" ]
}
},
users: {
"1": { "id": "1", "name": "Paul" },
"2": { "id": "2", "name": "Nicole" }
},
comments: {
"324": {
id: "324",
createdAt: "2013-05-29T00:00:00-04:00",
commenter: "2"
},
"544": {
id: "544",
createdAt: "2013-05-30T00:00:00-04:00",
commenter: "1"
}
}
},
// contents excluded for brevity
indexes,
entitiesMeta,
}
```

## Denormalize

```js
import { denormalize } from '@data-client/normalizr';

const denormalizedData = denormalize(
Article,
normalizedData.result,
normalizedData.entities,
args,
);
```

Now, `denormalizedData` will instantiate the classes, ensuring all instances of the same member (like `Paul`) are referentially equal:

```js
Article {
id: '123',
title: 'My awesome blog post',
author: User { id: '1', name: 'Paul' },
comments: [
Comment {
id: '324',
createdAt: Instant [Temporal.Instant] {},
commenter: [User { id: '2', name: 'Nicole' }]
},
Comment {
id: '544',
createdAt: Instant [Temporal.Instant] {},
commenter: [User { id: '1', name: 'Paul' }]
}
]
}
```

### MemoCache

`MemoCache` is a singleton that can be used to maintain referential equality between calls as well
as potentially improved performance by 2000%. Its methods are memoized.

#### memo.denormalize

```js
import { MemoCache } from '@data-client/normalizr';

// you can construct a new memo anytime you want to reset the cache
const memo = new MemoCache();

const { data, paths } = memo.denormalize(
Article,
normalizedData.result,
normalizedData.entities,
args,
);
const { data: data2 } = memo.denormalize(
Article,
normalizedData.result,
normalizedData.entities,
args,
);

// referential equality maintained between calls
assert(data === data2);
```

`memo.denormalize()` is just like [denormalize()](#denormalize) above but includes `paths` as part of the return value. `paths`
is an Array of paths of all entities included in the result.

#### memo.query

`memo.query()` allows denormalizing [Queryable](#queryable) based on args alone, rather than a normalized input.

```ts
const data = memo.query(
Article,
args,
normalizedData,
);
```

## Queryable

`Queryable` Schemas allow store access without an endpoint. They achieve this using the
[queryKey](./Entity.vue.md#queryKey) method that produces the results normally stored in the endpoint cache.

This enables their use in these additional cases:

- [useQuery()](https://dataclient.io/vue/api/useQuery) - Rendering in Vue
- [schema.Query()](https://dataclient.io/rest/api/Query) - As input to produce a computed memoization.
- [ctrl.get](https://dataclient.io/vue/api/Controller#get)/[snap.get](https://dataclient.io/vue/api/Snapshot#get)
- [Managers](https://dataclient.io/vue/concepts/managers)
- Vue with [useController()](https://dataclient.io/vue/api/useController)
- [RestEndpoint.getOptimisticResponse](./RestEndpoint.vue.md#getoptimisticresponse)
- [Unit testing composables](https://dataclient.io/vue/guides/unit-testing-composables) with `renderDataCompose()`
- [memo.query()](#memoquery)
- Improve performance of [useSuspense](https://dataclient.io/vue/api/useSuspense), [useDLE](https://dataclient.io/vue/api/useDLE) by rendering before endpoint resolution

`Querables` include [Entity](./Entity.vue.md), [All](https://dataclient.io/rest/api/All), [Collection](./Collection.vue.md), [Query](https://dataclient.io/rest/api/Query),
[Union](https://dataclient.io/rest/api/Union), and [Scalar](https://dataclient.io/rest/api/Scalar). [Lazy](https://dataclient.io/rest/api/Lazy) fields produce a Queryable via their [`.query`](https://dataclient.io/rest/api/Lazy#query) accessor.

```ts
interface Queryable {
queryKey(
args: readonly any[],
queryKey: (...args: any) => any,
getEntity: GetEntity,
getIndex: GetIndex,
// `{}` means non-void
): {};
}
```

## Schema Overview

| Data Type | Mutable | Schema | Description | [Queryable](./schema.vue.md#queryable) |
| ------------------------------------------------------------------- | ------- | --------------------------------------------------------------- | -------------------------------------------------------- | ---------------------------------- |
| [Object](https://en.wikipedia.org/wiki/Object_\(computer_science\)) | ✅ | [Entity](./Entity.vue.md) | single _unique_ object | ✅ |
| [Object](https://en.wikipedia.org/wiki/Object_\(computer_science\)) | ✅ | [Union(Entity)](https://dataclient.io/rest/api/Union) | polymorphic objects (`A \| B`) | ✅ |
| [Object](https://en.wikipedia.org/wiki/Object_\(computer_science\)) | 🛑 | [Object](https://dataclient.io/rest/api/Object) | statically known keys | 🛑 |
| [Object](https://en.wikipedia.org/wiki/Object_\(computer_science\)) | | [Invalidate(Entity)](https://dataclient.io/rest/api/Invalidate) | [delete an entity](./expiry-policy.vue.md#invalidate-entity) | 🛑 |
| [List](https://en.wikipedia.org/wiki/List_\(abstract_data_type\)) | ✅ | [Collection(Array)](./Collection.vue.md) | growable lists | ✅ |
| [List](https://en.wikipedia.org/wiki/List_\(abstract_data_type\)) | 🛑 | [Array](https://dataclient.io/rest/api/Array) | immutable lists | 🛑 |
| [List](https://en.wikipedia.org/wiki/List_\(abstract_data_type\)) | | [All](https://dataclient.io/rest/api/All) | list of all entities of a kind | ✅ |
| [Map](https://en.wikipedia.org/wiki/Associative_array) | ✅ | [Collection(Values)](./Collection.vue.md) | growable maps | ✅ |
| [Map](https://en.wikipedia.org/wiki/Associative_array) | 🛑 | [Values](https://dataclient.io/rest/api/Values) | immutable maps | 🛑 |
| [Scalar](https://en.wikipedia.org/wiki/Scalar_\(mathematics\)) | ✅ | [Scalar](https://dataclient.io/rest/api/Scalar) | lens-dependent entity fields | ✅ |
| any | | [Query(Queryable)](https://dataclient.io/rest/api/Query) | memoized custom transforms | ✅ |
| any | | [Lazy(Schema)](https://dataclient.io/rest/api/Lazy) | deferred denormalization | ✅ |
2 changes: 1 addition & 1 deletion .agents/skills/data-client-schema/references/Entity.vue.md
Original file line number Diff line number Diff line change
Expand Up @@ -685,7 +685,7 @@ static mergeMetaWithStore(

### static queryKey(args, queryKey, getEntity, getIndex): pk? {#queryKey}

This method enables `Entities` to be [Queryable](./schema.md#queryable) - allowing store access without an endpoint.
This method enables `Entities` to be [Queryable](./schema.vue.md#queryable) - allowing store access without an endpoint.

Overriding can allow customization or disabling of this behavior altogether.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@ static mergeMetaWithStore(

### static queryKey(args, queryKey, getEntity, getIndex): pk? {#queryKey}

This method enables `Entities` to be [Queryable](./schema.md#queryable) - allowing store access without an endpoint.
This method enables `Entities` to be [Queryable](./schema.vue.md#queryable) - allowing store access without an endpoint.

Overriding can allow customization or disabling of this behavior altogether.

Expand Down
2 changes: 1 addition & 1 deletion .agents/skills/data-client-schema/references/Lazy.vue.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ This is useful for:
new Lazy(innerSchema)
```

- `innerSchema`: Any [Schema](./schema.md) — an [Entity](./Entity.vue.md), an array shorthand like `[MyEntity]`, a [Collection](./Collection.vue.md), etc.
- `innerSchema`: Any [Schema](./schema.vue.md) — an [Entity](./Entity.vue.md), an array shorthand like `[MyEntity]`, a [Collection](./Collection.vue.md), etc.

## Usage

Expand Down
4 changes: 2 additions & 2 deletions .agents/skills/data-client-schema/references/Query.vue.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ the same high performance and referential equality guarantees expected of Reacti

### schema

[Schema](./schema.md) used to retrieve/denormalize data from the Reactive Data Client cache.
This accepts any [Queryable](./schema.md#queryable) schema: [Entity](./Entity.vue.md), [All](./All.vue.md), [Collection](./Collection.vue.md), [Query](./Query.vue.md),
[Schema](./schema.vue.md) used to retrieve/denormalize data from the Reactive Data Client cache.
This accepts any [Queryable](./schema.vue.md#queryable) schema: [Entity](./Entity.vue.md), [All](./All.vue.md), [Collection](./Collection.vue.md), [Query](./Query.vue.md),
[Union](./Union.vue.md), [Scalar](./Scalar.vue.md), and [Object](./Object.vue.md) schemas for joining multiple entities.
[Lazy](./Lazy.vue.md) fields produce a Queryable via their [`.query`](./Lazy.vue.md#query) accessor.

Expand Down
6 changes: 3 additions & 3 deletions .agents/skills/data-client-schema/references/Scalar.vue.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ different lens args at the same time, each receiving the correct scalar values.
> **Note**
>
> `Scalar` is for scalar values like numbers, strings, booleans, or date-derived values.
> Use normal nested [schemas](./schema.md) for relationships to other entities.
> Use normal nested [schemas](./schema.vue.md) for relationships to other entities.

## Usage

Expand Down Expand Up @@ -336,7 +336,7 @@ data.

### queryKey {#queryKey}

`Scalar` is a [Queryable](./schema.md#queryable) schema. When used as a
`Scalar` is a [Queryable](./schema.vue.md#queryable) schema. When used as a
top-level endpoint schema — or passed to [useQuery](https://dataclient.io/vue/api/useQuery),
[Controller.get](https://dataclient.io/vue/api/Controller#get), [schema.Query](./Query.vue.md), or any
other Queryable consumer — it reports the cpks of all cells whose lens matches
Expand Down Expand Up @@ -376,4 +376,4 @@ entities['Scalar(portfolio)']['Company|1|portfolioB'] = {
- [Entity](./Entity.vue.md) — defines the base entity that scalar fields attach to
- [Values](./Values.vue.md) — used for column-only endpoints (dictionary keyed by entity pk)
- [Union](./Union.vue.md) — similar wrapper pattern for polymorphic entities
- [Queryable](./schema.md#queryable) — Scalar participates in [useQuery](https://dataclient.io/vue/api/useQuery), [Controller.get](https://dataclient.io/vue/api/Controller#get), and [schema.Query](./Query.vue.md)
- [Queryable](./schema.vue.md#queryable) — Scalar participates in [useQuery](https://dataclient.io/vue/api/useQuery), [Controller.get](https://dataclient.io/vue/api/Controller#get), and [schema.Query](./Query.vue.md)
Loading
Loading