Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: add typescript guide #9332

Merged
merged 10 commits into from
Apr 6, 2024
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
1 change: 1 addition & 0 deletions guides/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@

- [Relationships](./relationships/index.md)
- [Requests](./requests/index.md)
- [Typescript](./typescript/index.md)
- [Terminology](./terminology.md)
- [Cookbook](./cookbook/index.md)
184 changes: 184 additions & 0 deletions guides/typescript/0-installation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
# Installation

> [!CAUTION]
> EmberData does not maintain the DefinitelyTyped types for
> EmberData (e.g. the `@types/ember-data__*`). If you were
> previously using these, you should uninstall them first.

> [!IMPORTANT]
> EmberData's Native Types require the use of Ember's
> Native Types.

> [!IMPORTANT]
> Type definitions need to be installed top-level, this means
> you have to install every EmberData package `ember-data`
> depends on.

> [!TIP]
> When installing packages, use the `@canary` dist tag to get the latest
> version. E.g. `pnpm install ember-data@canary`

There are currently two ways to gain access to EmberData's native types.

1) [Use Canary](#using-canary)

2) [Use Official Types Packages](#using-types-packages)
with releases `>= 4.12.*`

---

### Using Canary

Required Packages for Canary Types

| Name | Version |
| ---- | ------- |
| [ember-data](https://github.com/emberjs/data/blob/main/packages/-ember-data/README.md) | ![NPM Canary Version](https://img.shields.io/npm/v/ember-data/canary?label=&color=90EE90) |
| [@ember-data/adapter](https://github.com/emberjs/data/blob/main/packages/adapter/README.md) | ![NPM Canary Version](https://img.shields.io/npm/v/%40ember-data/adapter/canary?label=&color=90EE90) |
| [@ember-data/graph](https://github.com/emberjs/data/blob/main/packages/graph/README.md) | ![NPM Canary Version](https://img.shields.io/npm/v/%40ember-data/graph/canary?label=&color=90EE90) |
| [@ember-data/json-api](https://github.com/emberjs/data/blob/main/packages/json-api/README.md) | ![NPM Canary Version](https://img.shields.io/npm/v/%40ember-data/json-api/canary?label=&color=90EE90) |
| [@ember-data/legacy-compat](https://github.com/emberjs/data/blob/main/packages/legacy-compat/README.md) | ![NPM Canary Version](https://img.shields.io/npm/v/%40ember-data/legacy-compat/canary?label=&color=90EE90) |
| [@ember-data/model](https://github.com/emberjs/data/blob/main/packages/model/README.md) | ![NPM Canary Version](https://img.shields.io/npm/v/%40ember-data/model/canary?label=&color=90EE90) |
| [@ember-data/request](https://github.com/emberjs/data/blob/main/packages/request/README.md) | ![NPM Canary Version](https://img.shields.io/npm/v/%40ember-data/request/canary?label=&color=90EE90) |
| [@ember-data/request-utils](https://github.com/emberjs/data/blob/main/packages/request-utils/README.md) | ![NPM Canary Version](https://img.shields.io/npm/v/%40ember-data/request-utils/canary?label=&color=90EE90) |
| [@ember-data/serializer](https://github.com/emberjs/data/blob/main/packages/serializer/README.md) | ![NPM Canary Version](https://img.shields.io/npm/v/%40ember-data/serializer/canary?label=&color=90EE90) |
| [@ember-data/store](https://github.com/emberjs/data/blob/main/packages/store/README.md) | ![NPM Canary Version](https://img.shields.io/npm/v/%40ember-data/store/canary?label=&color=90EE90) |
| [@ember-data/tracking](https://github.com/emberjs/data/blob/main/packages/tracking/README.md) | ![NPM Canary Version](https://img.shields.io/npm/v/%40ember-data/tracking/canary?label=&color=90EE90) |
| [@warp-drive/core-types](https://github.com/emberjs/data/blob/main/packages/core-types/README.md) | ![NPM Canary Version](https://img.shields.io/npm/v/%40warp-drive/core-types/canary?label=&color=90EE90) |

Here's a single install command for pnpm. Swap pnpm for yarn or npm as needed.

```
pnpm install ember-data@canary @ember-data/adapter@canary @ember-data/graph@canary @ember-data/json-api@canary @ember-data/legacy-compat@canary @ember-data/model@canary @ember-data/request@canary @ember-data/request-utils@canary @ember-data/serializer@canary @ember-data/store@canary @ember-data/tracking@canary @warp-drive/core-types@canary
```

Here's an example change to package.json which drops all use of types from `@types/` for both Ember and EmberData and adds the appropriate canary packages.

```diff
- "@types/ember": "4.0.11",
- "@types/ember-data": "4.4.16",
- "@types/ember-data__adapter": "4.0.6",
- "@types/ember-data__model": "4.0.5",
- "@types/ember-data__serializer": "4.0.6",
- "@types/ember-data__store": "4.0.7",
- "@types/ember__application": "4.0.11",
- "@types/ember__array": "4.0.10",
- "@types/ember__component": "4.0.22",
- "@types/ember__controller": "4.0.12",
- "@types/ember__debug": "4.0.8",
- "@types/ember__destroyable": "4.0.5",
- "@types/ember__engine": "4.0.11",
- "@types/ember__error": "4.0.6",
- "@types/ember__helper": "4.0.7",
- "@types/ember__modifier": "4.0.9",
- "@types/ember__object": "4.0.12",
- "@types/ember__owner": "4.0.9",
- "@types/ember__routing": "4.0.22",
- "@types/ember__runloop": "4.0.10",
- "@types/ember__service": "4.0.9",
- "@types/ember__string": "3.16.3",
- "@types/ember__template": "4.0.7",
- "@types/ember__test": "4.0.6",
- "@types/ember__utils": "4.0.7",
- "ember-data": "~5.3.3",
+ "ember-data": "5.4.0-alpha.52",
+ "@ember-data/store": "5.4.0-alpha.52",
+ "@ember-data/adapter": "5.4.0-alpha.52",
+ "@ember-data/graph": "5.4.0-alpha.52",
+ "@ember-data/json-api": "5.4.0-alpha.52",
+ "@ember-data/legacy-compat": "5.4.0-alpha.52",
+ "@ember-data/request": "5.4.0-alpha.52",
+ "@ember-data/request-utils": "5.4.0-alpha.52",
+ "@ember-data/serializer": "5.4.0-alpha.52",
+ "@ember-data/model": "5.4.0-alpha.52",
+ "@ember-data/tracking": "5.4.0-alpha.52",
+ "@warp-drive/core-types": "0.0.0-alpha.38",
```

> [!TIP]
> If your package manager enables deduping, we recommend deduping types as much as possible.

>[!TIP]
> It is best to ensure no other dependencies are still bringing `@types/*` packages as this will cause weird type bugs.

---

### Using Types Packages

> [!WARNING]
> When consuming types in this way, you may sometimes
> encounter a misalignment between the types and the actual API. These misalignments should be rare for 4.12.* => 5.4.*. Overall, even when these misalignments occur, we suspect there are fewer mistakes or issues with these types than in the DefinitelyTyped types.


Every package in the project that ships types also publishes its types under a second package name.
This enables older releases to consume these types instead of relying on the DefinitelyTyped project.

These types-only packages have the same version number as the version they were published with, and their org or name is suffixed with `-types`.


**Required Packages for Types**


| Name | Types Package | Version |
| ---- | ------- | ------- |
| [ember-data](https://github.com/emberjs/data/blob/main/packages/-ember-data/README.md) | ember-data-types | ![NPM Canary Version](https://img.shields.io/npm/v/ember-data-types/canary?label=&color=90EE90) |
| [@ember-data/adapter](https://github.com/emberjs/data/blob/main/packages/adapter/README.md) | @ember-data-types/adapter | ![NPM Canary Version](https://img.shields.io/npm/v/%40ember-data-types/adapter/canary?label=&color=90EE90) |
| [@ember-data/graph](https://github.com/emberjs/data/blob/main/packages/graph/README.md) | @ember-data-types/graph | ![NPM Canary Version](https://img.shields.io/npm/v/%40ember-data-types/graph/canary?label=&color=90EE90) |
| [@ember-data/json-api](https://github.com/emberjs/data/blob/main/packages/json-api/README.md) | @ember-data-types/json-api | ![NPM Canary Version](https://img.shields.io/npm/v/%40ember-data-types/json-api/canary?label=&color=90EE90) |
| [@ember-data/legacy-compat](https://github.com/emberjs/data/blob/main/packages/legacy-compat/README.md) | @ember-data-types/legacy-compat | ![NPM Canary Version](https://img.shields.io/npm/v/%40ember-data-types/legacy-compat/canary?label=&color=90EE90) |
| [@ember-data/model](https://github.com/emberjs/data/blob/main/packages/model/README.md) | @ember-data-types/model | ![NPM Canary Version](https://img.shields.io/npm/v/%40ember-data-types/model/canary?label=&color=90EE90) |
| [@ember-data/request](https://github.com/emberjs/data/blob/main/packages/request/README.md) | @ember-data-types/request | ![NPM Canary Version](https://img.shields.io/npm/v/%40ember-data-types/request/canary?label=&color=90EE90) |
| [@ember-data/request-utils](https://github.com/emberjs/data/blob/main/packages/request-utils/README.md) | @ember-data-types/request-utils | ![NPM Canary Version](https://img.shields.io/npm/v/%40ember-data-types/request-utils/canary?label=&color=90EE90) |
| [@ember-data/serializer](https://github.com/emberjs/data/blob/main/packages/serializer/README.md) | @ember-data-types/serializer | ![NPM Canary Version](https://img.shields.io/npm/v/%40ember-data-types/serializer/canary?label=&color=90EE90) |
| [@ember-data/store](https://github.com/emberjs/data/blob/main/packages/store/README.md) | @ember-data-types/store | ![NPM Canary Version](https://img.shields.io/npm/v/%40ember-data-types/store/canary?label=&color=90EE90) |
| [@ember-data/tracking](https://github.com/emberjs/data/blob/main/packages/tracking/README.md) | @ember-data-types/tracking | ![NPM Canary Version](https://img.shields.io/npm/v/%40ember-data-types/tracking/canary?label=&color=90EE90) |
| [@warp-drive/core-types](https://github.com/emberjs/data/blob/main/packages/core-types/README.md) | @warp-drive-types/core-types | ![NPM Canary Version](https://img.shields.io/npm/v/%40warp-drive-types/core-types/canary?label=&color=90EE90) |

Here's a single install command for pnpm. Swap pnpm for yarn or npm as needed.

```
pnpm install ember-data-types@canary @ember-data-types/adapter@canary @ember-data-types/graph@canary @ember-data-types/json-api@canary @ember-data-types/legacy-compat@canary @ember-data-types/model@canary @ember-data-types/request@canary @ember-data-types/request-utils@canary @ember-data-types/serializer@canary @ember-data-types/store@canary @ember-data-types/tracking@canary @warp-drive-types/core-types@canary
```

Here's an example change to package.json which drops all use of types from `@types/` for both Ember and EmberData and adds the appropriate canary packages.

```diff
- "@types/ember": "4.0.11",
- "@types/ember-data": "4.4.16",
- "@types/ember-data__adapter": "4.0.6",
- "@types/ember-data__model": "4.0.5",
- "@types/ember-data__serializer": "4.0.6",
- "@types/ember-data__store": "4.0.7",
- "@types/ember__application": "4.0.11",
- "@types/ember__array": "4.0.10",
- "@types/ember__component": "4.0.22",
- "@types/ember__controller": "4.0.12",
- "@types/ember__debug": "4.0.8",
- "@types/ember__destroyable": "4.0.5",
- "@types/ember__engine": "4.0.11",
- "@types/ember__error": "4.0.6",
- "@types/ember__helper": "4.0.7",
- "@types/ember__modifier": "4.0.9",
- "@types/ember__object": "4.0.12",
- "@types/ember__owner": "4.0.9",
- "@types/ember__routing": "4.0.22",
- "@types/ember__runloop": "4.0.10",
- "@types/ember__service": "4.0.9",
- "@types/ember__string": "3.16.3",
- "@types/ember__template": "4.0.7",
- "@types/ember__test": "4.0.6",
- "@types/ember__utils": "4.0.7",
+ "@ember-data-types/adapter": "^5.4.0-alpha.52",
+ "@ember-data-types/model": "^5.4.0-alpha.52",
+ "@ember-data-types/serializer": "^5.4.0-alpha.52",
+ "@ember-data-types/store": "^5.4.0-alpha.52",
+ "@ember-data-types/graph": "^5.4.0-alpha.52",
+ "@ember-data-types/json-api": "^5.4.0-alpha.52",
+ "@ember-data-types/legacy-compat": "^5.4.0-alpha.52",
+ "@ember-data-types/request": "^5.4.0-alpha.52",
+ "@ember-data-types/request-utils": "^5.4.0-alpha.52",
+ "@ember-data-types/tracking": "^5.4.0-alpha.52",
+ "@warp-drive-types/core-types": "^0.0.0-alpha.38",
"ember-data": "^4.12.7",
+ "ember-data-types": "^5.4.0-alpha.52",
```
73 changes: 73 additions & 0 deletions guides/typescript/1-configuration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Configuration

There are currently two ways to gain access to EmberData's native types.
Follow the configuration guide below for the [installation](./0-installation.md)
option you chose.

1) [Use Canary](#using-canary)

2) [Use Official Types Packages](#using-types-packages)
with releases `>= 4.12.*`

> [!IMPORTANT]
> EmberData's Native Types require the use of Ember's
> Native Types, the configuration below will also setup
> Your application to consume Ember's Native Types.

### Using Canary

To consume `alpha` stage types, you must import the types in your project's `tsconfig.json`.

For alpha stage types, we add `unstable-preview-types` to the path to help you remember the
potential volatility.

```diff
{
"compilerOptions": {
+ "types": [
+ "ember-source/types",
+ "ember-data/unstable-preview-types",
+ "@ember-data/store/unstable-preview-types",
+ "@ember-data/adapter/unstable-preview-types",
+ "@ember-data/graph/unstable-preview-types",
+ "@ember-data/json-api/unstable-preview-types",
+ "@ember-data/legacy-compat/unstable-preview-types",
+ "@ember-data/request/unstable-preview-types",
+ "@ember-data/request-utils/unstable-preview-types",
+ "@ember-data/model/unstable-preview-types",
+ "@ember-data/serializer//unstable-preview-types",
+ "@ember-data/tracking/unstable-preview-types",
+ "@warp-drive/core-types/unstable-preview-types"
+ ]
}
}
```

### Using Types Packages

To consume `alpha` stage types, you must import the types in your project's `tsconfig.json`.

For alpha stage types, we add `unstable-preview-types` to the path to help you remember the
potential volatility.

```diff
{
"compilerOptions": {
+ "types": [
+ "ember-source/types",
+ "ember-data-types/unstable-preview-types",
+ "@ember-data-types/store/unstable-preview-types",
+ "@ember-data-types/adapter/unstable-preview-types",
+ "@ember-data-types/graph/unstable-preview-types",
+ "@ember-data-types/json-api/unstable-preview-types",
+ "@ember-data-types/legacy-compat/unstable-preview-types",
+ "@ember-data-types/request/unstable-preview-types",
+ "@ember-data-types/request-utils/unstable-preview-types",
+ "@ember-data-types/model/unstable-preview-types",
+ "@ember-data-types/serializer//unstable-preview-types",
+ "@ember-data-types/tracking/unstable-preview-types",
+ "@warp-drive-types/core-types/unstable-preview-types"
+ ]
}
}
```
73 changes: 73 additions & 0 deletions guides/typescript/2-why-brands.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# EmberData's Types Strategy

If you previously used the EmberData types provided by DefinitelyTyped, one MASSIVE
difference you will notice immediately is that EmberData does not use any registries
for types.

Instead, EmberData uses Symbol keys to brand objects with additional type information.

For example:

```ts
import Model, { attr } from '@ember-data/model';
import { ResourceType } from '@warp-drive/core-types/symbols';

export default class User extends Model {
@attr declare name: string;

[ResourceType] = 'user' as const;
}
```

This means that when calling an API that takes in a resource type, we pass this branded class as a generic instead of relying on registries. For example:

```ts
import type User from 'my-app/models/user';

// ...

const user = await store.findRecord<User>('user', '1');
```

We chose this direction over registries or objects for a number of reasons we'll detail below.

### Why not registries?

We found registries had 5 significant drawbacks.

First, registries have a max number of entries before TypeScript begins resolving unions based on the registry as `any`. This limit is relatively low (in the hundreds) so many applications hit into this relatively quickly.

Second, constructing registries is brittle. Conflicts often arise when attempting to source models from additional libraries, and often result in `never` types due to an empty registry.

Third, we couldn't type EmberData itself using registries without adopting extreme complexity. This is because while DefinitelyTyped could assume one single global registry, EmberData cannot. This arises for a myriad of reasons: EmberData supports multiple stores, multiple sources of schema, and our own test suite defines Models for each test that would conflict with each other if we were forced to use a single global registry.

Fourth, and possibly most importantly, registries assume that for a given resource-type (like `'user'`) that only a single type signature exists. While this has *mostly* been true historically in EmberData, it is no longer true and will become increasingly less true as we roll out additional features we have planned. Supporting different
type signatures for Create/Edit/Delete as well as for partials and actions means if we stuck with registries, we'd need tons of them and things would get complicated quickly.

Fifth, the registry approach prevents static analysis from easily determining where in the application a Model or Schema is in use, making it difficult for bundlers to
optimize while code-splitting.

### Ok, so then why not objects?

A common alternative to registries is to pass classes as tokens into an API. For instance, we could have redesigned
EmberData to take a class instead of a string in the call to `findRecord` below.

```ts
import type User from 'my-app/models/user';

// ...

const user = await store.findRecord(User, '1');
```

There are two significant drawbacks to this approach. The first is one of the same reasons as "why not registries": we expect that lots of type signatures will satisfy a single resource-type in the future.

The second is related and more important: it forces you to use classes or other objects to represent data, which we don't want to do.

In the near future, EmberData will switch the default story for presenting data from `Model` which is a class-per-resource approach to `SchemaRecord`, which is a single class capable of presenting the data for any associated schema. Schema's are defined in `json` and can be loaded into the app in any number of ways. That means when using SchemaRecord, there never would be a class to import and use as a token for such a call.

### Ok, Brands!

Brands solve the various issues mentioned above, and a bit more!

Over time, they should enable us to curate a great experience for working with partials, actions, contrained edit signatures, query syntaxes like GraphQL and more.
Loading
Loading