diff --git a/guides/index.md b/guides/index.md index 0a7f07f0a53..090d25acb13 100644 --- a/guides/index.md +++ b/guides/index.md @@ -2,5 +2,6 @@ - [Relationships](./relationships/index.md) - [Requests](./requests/index.md) +- [Typescript](./typescript/index.md) - [Terminology](./terminology.md) - [Cookbook](./cookbook/index.md) diff --git a/guides/typescript/0-installation.md b/guides/typescript/0-installation.md new file mode 100644 index 00000000000..809b0fbfefe --- /dev/null +++ b/guides/typescript/0-installation.md @@ -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", +``` diff --git a/guides/typescript/1-configuration.md b/guides/typescript/1-configuration.md new file mode 100644 index 00000000000..28c821f53ef --- /dev/null +++ b/guides/typescript/1-configuration.md @@ -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" ++ ] + } + } +``` diff --git a/guides/typescript/2-why-brands.md b/guides/typescript/2-why-brands.md new file mode 100644 index 00000000000..beea79975d0 --- /dev/null +++ b/guides/typescript/2-why-brands.md @@ -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', '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. diff --git a/guides/typescript/3-typing-models.md b/guides/typescript/3-typing-models.md new file mode 100644 index 00000000000..db92dbabea0 --- /dev/null +++ b/guides/typescript/3-typing-models.md @@ -0,0 +1,155 @@ +# Typing Models + +## ResourceType + +Example: add the `ResourceType` brand to the `user` model. + +```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; +} +``` + +## Transforms + +Transforms with a `TransformName` brand will have their type and options validated. Once we move to stage-3 decorators, the signature of the field would also be validated against the transform. + +Example: Using Transforms + +```ts +import Model, { attr } from '@ember-data/model'; +import type { StringTransform } from '@ember-data/serializer/transforms'; +import { ResourceType } from '@warp-drive/core-types/symbols'; + +export default class User extends Model { + @attr('string') declare name: string; + + [ResourceType] = 'user' as const; +} +``` + +## Sync BelongsTo + +`belongsTo` relationships will have their resource type and options config validated against the passed in type. + +Once we move to stage-3 decorators, explicitly setting the generic would not be required as it could be infered from the field's type. + +```ts +import Model, { belongsTo } from '@ember-data/model'; +import type Address from './address'; +import { ResourceType } from '@warp-drive/core-types/symbols'; + +export default class User extends Model { + @belongsTo
('address', { async: false, inverse: null }) + declare address: Address; + + [ResourceType] = 'user' as const; +} +``` + +## Async BelongsTo + +`belongsTo` relationships will have their resource type and options config validated against the passed in type. + +Once we move to stage-3 decorators, explicitly setting the generic would not be required as it could be infered from the field's type. + +```ts +import Model, { belongsTo, AsyncBelongsTo } from '@ember-data/model'; +import type Address from './address'; +import { ResourceType } from '@warp-drive/core-types/symbols'; + +export default class User extends Model { + @belongsTo
('address', { async: true, inverse: null }) + declare address: AsyncBelongsTo
; + + [ResourceType] = 'user' as const; +} +``` + +## Sync HasMany (data only) + +If you don't need access to meta or links on relationships, you can type the relationship as just an array. + +`hasMany` relationships will have their resource type and options config validated against the passed in type. + +Once we move to stage-3 decorators, explicitly setting the generic would not be required as it could be infered from the field's type. + +```ts +import Model, { hasMany } from '@ember-data/model'; +import type Post from './post'; +import { ResourceType } from '@warp-drive/core-types/symbols'; + +export default class User extends Model { + @hasMany('post', { async: false, inverse: 'author' }) + declare posts: Post[]; + + [ResourceType] = 'user' as const; +} +``` + +## Sync HasMany (with meta, links, etc) + +`hasMany` relationships will have their resource type and options config validated against the passed in type. + +Once we move to stage-3 decorators, explicitly setting the generic would not be required as it could be infered from the field's type. + +```ts +import Model, { hasMany, ManyArray } from '@ember-data/model'; +import type Post from './post'; +import { ResourceType } from '@warp-drive/core-types/symbols'; + +export default class User extends Model { + @hasMany('post', { async: false, inverse: 'author' }) + declare posts: ManyArray; + + [ResourceType] = 'user' as const; +} +``` + +## Async HasMany (restricted) + +If you don't need access to meta, links or template iterations on relationships, you can type the relationship as just a promise resolving to an array. Only use this if the value +will always be awaited before iteration. + +`hasMany` relationships will have their resource type and options config validated against the passed in type. + +Once we move to stage-3 decorators, explicitly setting the generic would not be required as it could be infered from the field's type. + +```ts +import Model, { hasMany, AsyncHasMany } from '@ember-data/model'; +import type Post from './post'; +import { ResourceType } from '@warp-drive/core-types/symbols'; + +export default class User extends Model { + @hasMany('post', { async: true, inverse: 'author' }) + declare posts: Promise; + + [ResourceType] = 'user' as const; +} +``` + +`Promise>` also works. + +## Async HasMany (with links, meta, etc.) + +`hasMany` relationships will have their resource type and options config validated against the passed in type. + +Once we move to stage-3 decorators, explicitly setting the generic would not be required as it could be infered from the field's type. + +```ts +import Model, { hasMany, AsyncHasMany } from '@ember-data/model'; +import type Post from './post'; +import { ResourceType } from '@warp-drive/core-types/symbols'; + +export default class User extends Model { + @hasMany('post', { async: true, inverse: 'author' }) + declare posts: AsyncHasMany; + + [ResourceType] = 'user' as const; +} +``` diff --git a/guides/typescript/index.md b/guides/typescript/index.md new file mode 100644 index 00000000000..3769b20a960 --- /dev/null +++ b/guides/typescript/index.md @@ -0,0 +1,53 @@ +# 💚 TypeScript Guide + +Before getting started, we recommend reading +the following two sections + +- [Notice on Type Maturity](#type-maturity) +- [Contributing Type Fixes](#contributing-type-fixes) + + +--- + +- Installation + - [Using Canary](./0-installation.md#using-canary) + - [Using Types Packages](./0-installation.md#using-types-packages) +- Configuration + - [Using Canary](./1-configuration.md#using-canary) + - [Using Types Packages](./1-configuration.md#using-types-packages) +- Usage + - [Why Brands](./2-why-brands.md) + - [Typing Models](./3-typing-models.md) + - Typing Transforms + - Typing Requests + - Typing Builders + - Typing Handlers + - Using Store APIs + +--- + +## Type Maturity + +We publish types in stages, just like `canary | beta | stable` channels for code. + +- `private` we don't ship types (yet), even if typed in the repo +- `alpha` we expect high churn on type signatures and users must opt-in to use these types. +- `beta` we expect moderate churn on type signatures and users must opt-in to use these types. +- `stable` we feel the types story is robust enough to attempt to follow semver when changing these types. + +Each package in the project can choose its own stage for types. + +> [!TIP] +> TypeScript support for all EmberData and WarpDrive packages is currently `alpha`. +> +> **This means that you must opt-in to be able use EmberData's types.** + +## Contributing Type Fixes + +Even though EmberData is typed, what makes for good types for a project doesn't necessarily make for good types for that project's consumers (your application). + +Currently, TypeScript support is `alpha` largely because we expect to need to improve **a lot** of type signatures to make them more useful and correct for your app. + +Both strategies for installing and consuming types listed in [installation](./0-installation.md) pull their types from the `main` branch (canary). + +Every commit to main can be one-click published by us as a new canary version for both installation strategies, this means we can ship type fixes as quickly as folks contribute them, letting us dogfood our way to robust stable types.