diff --git a/guides/release/typescript/additional-resources/gotchas.md b/guides/release/typescript/additional-resources/gotchas.md
index bb42f460a9..6be2285670 100644
--- a/guides/release/typescript/additional-resources/gotchas.md
+++ b/guides/release/typescript/additional-resources/gotchas.md
@@ -33,10 +33,6 @@ For examples, see:
- [Service][service] registry
- [Controller][controller] registry
-- EmberData [Model][model] registry
-- EmberData [Transform][transform] registry
-- EmberData [Serializer][serializers-and-adapters] registry
-- EmberData [Adapter][serializers-and-adapters] registry
## Decorators
@@ -125,30 +121,6 @@ export default class MyRoute extends Route {
}
```
-## Fixing the EmberData `error TS2344` problem
-
-If you're developing an Ember app or addon and _not_ using EmberData (and accordingly not even have the EmberData types installed), you may see an error like this and be confused:
-
-```text
-node_modules/@types/ember-data/index.d.ts(920,56): error TS2344: Type 'any' does not satisfy the constraint 'never'.
-```
-
-This happens because the types for Ember's _test_ tooling includes the types for EmberData because the `this` value in several of Ember's test types can include a reference to the EmberData `Store` class.
-
-**The fix:** add a declaration like this in a new file named `ember-data.d.ts` in your `types` directory:
-
-```typescript {data-filename="types/ember-data.d.ts"}
-declare module 'ember-data/types/registries/model' {
- export default interface ModelRegistry {
- [key: string]: unknown;
- }
-}
-```
-
-This works because (a) we include things in your types directory automatically and (b) TypeScript will merge this module and interface declaration with the main definitions for EmberData from DefinitelyTyped behind the scenes.
-
-If you're developing an addon and concerned that this might affect consumers, it won't. Your types directory will never be referenced by consumers at all!
-
[controller]: ../../core-concepts/routing/#toc_controller-injections-and-lookups
@@ -156,12 +128,9 @@ If you're developing an addon and concerned that this might affect consumers, it
[model-attr]: ../../core-concepts/ember-data/#toc_attr
[model-belongsto]: ../../core-concepts/ember-data/#toc_belongsto
[model-hasmany]: ../../core-concepts/ember-data/#toc_hasMany
-[model]: ../../core-concepts/ember-data/#toc_models
[owner-lookup]: https://api.emberjs.com/ember/release/classes/Owner/methods/lookup?anchor=lookup
-[serializers-and-adapters]: ../../core-concepts/ember-data/#toc_serializers-and-adapters
[service]: ../../core-concepts/services/#toc_using-services
[signature]: ../../core-concepts/invokables/#toc_signature-basics
-[transform]: ../../core-concepts/ember-data/#toc_transforms
diff --git a/guides/release/typescript/application-development/addons.md b/guides/release/typescript/application-development/addons.md
index e1035498d4..15fb565dc9 100644
--- a/guides/release/typescript/application-development/addons.md
+++ b/guides/release/typescript/application-development/addons.md
@@ -18,24 +18,19 @@ Project files will be generated with `.ts` extensions instead of `.js`.
In addition to the usual packages added with `ember addon`, the following packages will be added at their current "latest" value:
-- `typescript`
-- `@tsconfig/ember`
-- `@typescript-eslint/*`
-- `@types/ember`
-- `@types/ember-data`
-- `@types/ember__*` – `@types/ember__object` for `@ember/object`, etc.
-- `@types/ember-data__*` – `@types/ember-data__model` for `@ember-data/model`, etc.
-- `@types/qunit`
-- `@types/rsvp`
-
-The `typescript` package provides tooling to support TypeScript type checking and compilation. The `@types` packages from [DefinitelyTyped][] provide TypeScript type definitions for all of the Ember and EmberData modules.
+- `typescript` – tooling to support TypeScript type checking and compilation.
+- `@tsconfig/ember` – a shared TypeScript configuration for Ember apps.
+- `@typescript-eslint/*` – ESLint support for TypeScript.
+- `@types/qunit` - TypeScript type definitions for QUnit.
+- `@types/rsvp` - TypeScript type definitions for RSVP.
+- `@warp-drive/core-types` - shared core types, type utilities and constants for the WarpDrive and EmberData packages.
Zoey says...
- Ember also publishes its own native types compiled directly from its source code, as described
in this blog post. For now, we continue to use the
@types
packages by default for the sake of compatibility with EmberData, because EmberData is not yet compatible with Ember's native official types. However, if you do not use EmberData, we
highly recommend following the instructions in that blog post to switch to the native types, which are guaranteed to always be 100% correct and 100% up to date!
+ Ember and EmberData publish their own native types compiled directly from their source code, so you do not need to install any
@types/ember
or
@types/ember-data
packages. These packages should be considered legacy, are only lightly maintained, and will conflict with the native types.
@@ -164,7 +159,6 @@ declare module 'addon/templates/*' {
-[DefinitelyTyped]: https://github.com/DefinitelyTyped/DefinitelyTyped
[dts]: https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html
[ember-addon]: https://cli.emberjs.com/release/writing-addons/
[glint]: https://typed-ember.gitbook.io/glint/
diff --git a/guides/release/typescript/application-development/configuration.md b/guides/release/typescript/application-development/configuration.md
index de891407f1..a7c0c9d31e 100644
--- a/guides/release/typescript/application-development/configuration.md
+++ b/guides/release/typescript/application-development/configuration.md
@@ -11,7 +11,12 @@ If you use the `--typescript` flag when generating your Ember app, we generate a
"my-app/tests/*": ["tests/*"],
"my-app/*": ["app/*"],
"*": ["types/*"]
- }
+ },
+ "types": [
+ "ember-source/types",
+ "./node_modules/ember-data/unstable-preview-types",
+ // ...more ember-data types...
+ "./node_modules/@warp-drive/core-types/unstable-preview-types"
}
}
```
diff --git a/guides/release/typescript/application-development/converting-an-app.md b/guides/release/typescript/application-development/converting-an-app.md
index bf2f3b1639..79b8447caf 100644
--- a/guides/release/typescript/application-development/converting-an-app.md
+++ b/guides/release/typescript/application-development/converting-an-app.md
@@ -2,35 +2,47 @@ These directions are for converting an _existing_ Ember app to TypeScript. If yo
## Enable TypeScript Features
-### A Bit of a Hack
+### Install TypeScript and Related Packages
-Since `ember-cli` _currently_ has no flag to convert your project to TypeScript, we're going to use a bit of a hack and _temporarily_ install the legacy `ember-cli-typescript` addon to complete most of the migration:
+See [Getting Started: Packages to Support TypeScript][packages] for descriptions of these packages.
```shell
-ember install ember-cli-typescript@latest
+npm add --save-dev typescript @tsconfig/ember
+npm add --save-dev @types/qunit @types/rsvp
+npm add --save-dev @typescript-eslint/eslint-plugin @typescript-eslint/parser
+npm remove @babel/plugin-proposal-decorators @babel/eslint-parser
```
-The `ember-cli-typescript` addon will install _most_ of the necessary packages and create or update _most_ of the necessary files as described in [Getting Started][].
+### Add TypeScript Configuration
+
+Add a `tsconfig.json` file to the root of your project. Copy its contents from the [current output from the Ember CLI blueprints][tsconfig.json].
-You can then immediately remove the `ember-cli-typescript` dependency and follow the rest of this guide.
+### Set Up TypeScript for EmberData
-### Manually Enable TypeScript Transpilation
+Follow the instructions in the [EmberData Typescript Guides][ED-ts-guides].
+
+### Enable TypeScript Transpilation for Builds
To enable TypeScript transpilation in your app, simply add the corresponding configuration for Babel to your `ember-cli-build.js` file.
-```javascript {data-filename="ember-cli-build.js" data-diff="+2"}
-const app = new EmberApp(defaults, {
- 'ember-cli-babel': { enableTypeScriptTransform: true },
-});
+```javascript {data-filename="ember-cli-build.js" data-diff="+3"}
+module.exports = function (defaults) {
+ const app = new EmberApp(defaults, {
+ 'ember-cli-babel': { enableTypeScriptTransform: true },
+ // ...
+ });
+
+ return app.toTree();
+};
```
-### Manually Add `lint:types` Script
+### Enable Type Checking in CI
To easily check types with the command line, add the `lint:types` script as shown [here][lint-types].
The default `lint` script generated by Ember CLI will include the `lint:types` script automatically.
-### Manually Force Blueprint Generators to Use TypeScript
+### Configure Blueprint Generators to Use TypeScript
With the following configuration, project files will be generated with `.ts` extensions instead of `.js`:
@@ -41,14 +53,35 @@ With the following configuration, project files will be generated with `.ts` ext
}
```
-### Manually Set Up `@typescript-eslint`
-
-```shell
-npm add --save-dev @typescript-eslint/eslint-plugin @typescript-eslint/parser
+```js {data-filename="config/ember-cli-update.json" data-diff="+12"}
+{
+ // ...
+ "packages": [
+ {
+ "name": "ember-cli",
+ // ...
+ "blueprints": [
+ {
+ // ...
+ "options": [
+ // ...
+ "--typescript"
+ ]
+ }
+ ]
+ }
+ ]
+}
```
+### Configure ESLint
+
Then, update your `.eslintrc.js` to include the [current output from the Ember CLI blueprints][eslintrc]. You might consider using ESLint [overrides][] configuration to separately configure your JavaScript and TypeScript files during the migration.
+### Add Initial Type Declarations
+
+Add types for your `config/environment.js` file by creating a type declaration file at `app/config/environment.d.ts`. You can find an example file in the [current output from the Ember CLI blueprints][environment.d.ts].
+
## Migrate Existing Code to TypeScript
Once you have set up TypeScript following the guides above, you can begin to migrate your files incrementally by changing their extensions from `.js` to `.ts`. Fortunately, TypeScript allows for gradual typing. This means that you can use TypeScript and JavaScript files interchangeably, so you can convert your app piecemeal.
@@ -83,12 +116,16 @@ In general, we recommend migrating to Octane idioms before, or in conjunction wi
## ember-cli-typescript
-If you're migrating from `ember-cli-typescript`, particularly an older version, to Ember's out-of-the-box TypeScript support, you may also need to update your `tsconfig.json`. Current versions of `ember-cli-typescript` generate the correct config at installation. You do _not_ need `ember-cli-typescript` installed for new apps or addons.
+The `ember-cli-typescript` package was used to add TypeScript support to Ember apps before Ember's native TypeScript support was available. You do _not_ need `ember-cli-typescript` installed for new apps or addons.
+
+If you're migrating from `ember-cli-typescript` to Ember's native TypeScript support, most of your existing configuration will still be relevant. Just read through the steps of this guide and ensure that your config matches the expected config as described above.
+[ED-ts-guides]: ../../core-concepts/ember-data/#toc_adding-emberdata-types-to-an-existing-typescript-app
[getting started]: ../../getting-started/
[legacy]: ../../additional-resources/legacy/
+[packages]: ../../getting-started/#toc_packages-to-support-typescript
[strictness]: ../../additional-resources/faq/#toc_strictness
@@ -96,10 +133,13 @@ If you're migrating from `ember-cli-typescript`, particularly an older version,
[allowJs]: https://www.typescriptlang.org/tsconfig/#allowJs
[any]: https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#any
[dts]: https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html
+[environment.d.ts]: https://github.com/ember-cli/editor-output/blob/stackblitz-app-output-typescript/app/config/environment.d.ts
[eslintrc]: https://github.com/ember-cli/editor-output/blob/stackblitz-app-output-typescript/.eslintrc.js
+[global.d.ts]: https://github.com/ember-cli/editor-output/blob/stackblitz-app-output-typescript/types/global.d.ts
[lint-types]: https://github.com/ember-cli/editor-output/blob/stackblitz-app-output-typescript/package.json
[JSDoc]: https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html#handbook-content
[overrides]: https://eslint.org/docs/latest/use/configure/configuration-files#configuration-based-on-glob-patterns
[ts-check]: https://www.typescriptlang.org/docs/handbook/intro-to-js-ts.html#ts-check
[ts-expect-error]: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-9.html
+[tsconfig.json]: https://github.com/ember-cli/editor-output/blob/stackblitz-app-output-typescript/tsconfig.json
[unknown]: https://www.typescriptlang.org/docs/handbook/2/functions.html
diff --git a/guides/release/typescript/core-concepts/ember-data.md b/guides/release/typescript/core-concepts/ember-data.md
index b4e742f8c6..1aca8f0818 100644
--- a/guides/release/typescript/core-concepts/ember-data.md
+++ b/guides/release/typescript/core-concepts/ember-data.md
@@ -8,10 +8,7 @@ We do _not_ cover general usage of EmberData; instead, we assume that as backgro
Zoey says...
- These guides currently assume you are using the EmberData @types
packages in conjunction with the Ember @types
packages.
-
-
- For improved (albeit less stable) types, you can switch to EmberData's alpha native types, documented at this link. Using the EmberData alpha native types will also require switching to the Ember native types, which are guaranteed to always be 100% correct and 100% up to date!
+ The following content applies to the native EmberData types, which are currently considered "unstable" (though in practice, they've been pretty stable as of late). These guides may change as the EmberData types are finalized.
@@ -21,20 +18,33 @@ We do _not_ cover general usage of EmberData; instead, we assume that as backgro
## Models
-EmberData models are normal TypeScript classes, but with properties decorated to define how the model represents an API resource and relationships to other resources. The decorators the library supplies "just work" with TypeScript at runtime, but require type annotations to be useful with TypeScript. Additionally, you must register each model with the [`ModelRegistry`][ED-registry] as shown in the examples below.
+EmberData models are normal TypeScript classes, but with properties decorated to define how the model represents an API resource and relationships to other resources. The decorators the library supplies "just work" with TypeScript at runtime, but require type annotations to be useful with TypeScript. Additionally, you must include the model's ["brand"][brand] to ensure that the EmberData store APIs return the correct types.
+
+For example, here we add the `Type` brand to the `user` model:
+
+```ts {data-filename="app/models/user.ts" data-diff="+2,+5"}
+import Model from '@ember-data/model';
+import type { Type } from '@warp-drive/core-types/symbols';
+
+export default class User extends Model {
+ declare [Type]: 'user';
+}
+```
-### `@attr`
+EmberData will never access Type as an actual value, these brands are _purely_ for type inference.
-The type returned by the `@attr` [decorator][] is whatever [Transform][transform-api-docs] is applied via the invocation. See our [overview of Transforms][transforms] for more information.
+### Attributes
+
+The type returned by the `@attr` [decorator][] is determined by whatever [Transform][transform-api-docs] is applied via the invocation. See our [overview of Transforms][transforms] for more information.
If you supply no argument to `@attr`, the value is passed through without transformation.
If you supply one of the built-in transforms, you will get back a corresponding type:
-- `@attr('string')` → `string`
-- `@attr('number')` → `number`
-- `@attr('boolean')` → `boolean`
-- `@attr('date')` → `Date`
+- `@attr('string')` → `string | null`
+- `@attr('number')` → `number | null`
+- `@attr('boolean')` → `boolean | null`
+- `@attr('date')` → `Date | null`
If you supply a custom transform, you will get back the type returned by your transform.
@@ -42,31 +52,22 @@ So, for example, you might write a class like this:
```typescript {data-filename="app/models/user.ts"}
import Model, { attr } from '@ember-data/model';
-import CustomType from '../transforms/custom-transform';
+import type { Type } from '@warp-drive/core-types/symbols';
+import CustomType from '@my-app/transforms/custom-transform';
export default class User extends Model {
- @attr
- declare name?: string;
+ @attr declare name?: string;
- @attr('number')
- declare age: number;
+ @attr('number') declare age?: number | null;
- @attr('boolean')
- declare isAdmin: boolean;
+ @attr('boolean') declare isAdmin?: boolean | null;
- @attr('custom-transform')
- declare myCustomThing: CustomType;
-}
+ @attr('custom-transform') declare myCustomThing?: CustomType;
-declare module 'ember-data/types/registries/model' {
- export default interface ModelRegistry {
- user: User;
- }
+ declare [Type]: 'user';
}
```
-#### Type Safety for Model Attributes
-
Even more than with decorators in general, you should be careful when deciding whether to mark a property as [optional `?`][optional] or definitely present (no annotation): EmberData will default to leaving a property empty if it is not supplied by the API or by a developer when creating it. That is: the _default_ for EmberData corresponds to an optional field on the model.
The _safest_ type you can write for an EmberData model, therefore, leaves every property optional: this is how models _actually_ behave. If you choose to mark properties as definitely present by leaving off the `?`, you should take care to guarantee that this is a guarantee your API upholds, and that ever time you create a record from within the app, _you_ uphold those guarantees.
@@ -75,201 +76,199 @@ One way to make this safer is to supply a default value using the `defaultValue`
```typescript {data-filename="app/models/user.ts"}
import Model, { attr } from '@ember-data/model';
+import type { Type } from '@warp-drive/core-types/symbols';
+import CustomType from '@my-app/transforms/custom-transform';
export default class User extends Model {
- @attr
- declare name?: string;
+ @attr declare name?: string;
- @attr('number', { defaultValue: 13 })
- declare age: number;
+ @attr('number', { defaultValue: 13 }) declare age: number;
- @attr('boolean', { defaultValue: false })
- declare isAdmin: boolean;
-}
+ @attr('boolean', { defaultValue: false }) declare isAdmin: boolean;
-declare module 'ember-data/types/registries/model' {
- export default interface ModelRegistry {
- user: User;
- }
+ declare [Type]: 'user';
}
```
-### Relationships
+### Async BelongsTo Relationships
-Relationships between models in EmberData rely on importing the related models, like `import User from './user';`. This, naturally, can cause a recursive loop, as `/app/models/post.ts` imports `User` from `/app/models/user.ts`, and `/app/models/user.ts` imports `Post` from `/app/models/post.ts`. Recursive importing triggers an [`import/no-cycle`][import-no-cycle] error from ESLint.
+If the `@belongsTo` is `{ async: true }` (the default), the type is `AsyncBelongsTo
`, where `Model` is the type of the model you are creating a relationship to. Additionally, pass the `Model` type as a generic to the `@belongsTo` decorator to ensure that the inverse relationship is validated.
-To avoid these errors, use [type-only imports][type-only-imports]:
+```ts {data-filename="app/models/user.ts"}
+import Model, { belongsTo, AsyncBelongsTo } from '@ember-data/model';
+import type Address from './address';
+import type { Type } from '@warp-drive/core-types/symbols';
-```typescript
-import type User from './user';
-```
+export default class User extends Model {
+ @belongsTo('address', { async: true, inverse: null })
+ declare address: AsyncBelongsTo;
-#### `@belongsTo`
+ declare [Type]: 'user';
+}
+```
-The type returned by the `@belongsTo` decorator depends on whether the relationship is `{ async: true }` (which it is by default).
+Async BelongsTo relationships are type-safe to define as always present. Accessing an async relationship will _always_ return an `AsyncBelongsTo` object, which itself may or may not ultimately resolve to a value—depending on the API response—but will always be present itself.
-- If the value is `true`, the type you should use is `AsyncBelongsTo`, where `Model` is the type of the model you are creating a relationship to.
-- If the value is `false`, the type is `Model`, where `Model` is the type of the model you are creating a relationship to.
+### Sync BelongsTo Relationships
-So, for example, you might define a class like this:
+If the `@belongsTo` is `{ async: false }`, the type you should use is `Model | null`, where `Model` is the type of the model you are creating a relationship to. Again, you should pass the `Model` type as a generic to the `@belongsTo` decorator to ensure that the inverse relationship is validated.
-```typescript {data-filename="app/models/post.ts"}
-import Model, { belongsTo, type AsyncBelongsTo } from '@ember-data/model';
-import type User from './user';
-import type Site from './site';
+```ts {data-filename="app/models/user.ts"}
+import Model, { belongsTo } from '@ember-data/model';
+import type Address from './address';
+import type { Type } from '@warp-drive/core-types/symbols';
-export default class Post extends Model {
- @belongsTo('user')
- declare user: AsyncBelongsTo;
+export default class User extends Model {
+ @belongsTo('address', { async: false, inverse: null })
+ declare address: Address | null;
- @belongsTo('site', { async: false })
- declare site: Site;
+ declare [Type]: 'user';
}
+```
-declare module 'ember-data/types/registries/model' {
- export default interface ModelRegistry {
- post: Post;
- }
+### Async HasMany Relationships
+
+If the `@hasMany` is `{ async: true }` (the default), the type is `AsyncHasMany`, where `Model` is the type of the model you are creating a relationship to. Additionally, pass the `Model` type as a generic to the `@hasMany` decorator to ensure that the inverse relationship is validated.
+
+```ts {data-filename="app/models/user.ts"}
+import Model, { hasMany, AsyncHasMany } from '@ember-data/model';
+import type Post from './post';
+import type { Type } from '@warp-drive/core-types/symbols';
+
+export default class User extends Model {
+ @hasMany('post', { async: true, inverse: 'author' })
+ declare posts: AsyncHasMany;
+
+ declare [Type]: 'user';
}
```
-These are _type_-safe to define as always present, that is to leave off the `?` optional marker:
+### Sync HasMany Relationships
-- accessing an async relationship will always return an `AsyncBelongsTo` object, which itself may or may not ultimately resolve to a value—depending on the API response—but will always be present itself.
-- accessing a non-async relationship which is known to be associated but has not been loaded will trigger an error, so all access to the property will be safe _if_ it resolves at all.
+If the `@hasMany` is `{ async: false }`, the type is `HasMany`, where `Model` is the type of the model you are creating a relationship to. Additionally, pass the `Model` type as a generic to the `@hasMany` decorator to ensure that the inverse relationship is validated.
-Note, however, that this type-safety is not a guarantee of there being no runtime error: you still need to uphold the contract for non-async relationships (that is: loading the data first, or side-loading it with the request) to avoid throwing an error!
+```ts {data-filename="app/models/user.ts"}
+import Model, { hasMany, HasMany } from '@ember-data/model';
+import type Post from './post';
+import type { Type } from '@warp-drive/core-types/symbols';
+
+export default class User extends Model {
+ @hasMany('post', { async: false, inverse: 'author' })
+ declare posts: HasMany;
-#### `@hasMany`
+ declare [Type]: 'user';
+}
+```
-The type returned by the `@hasMany` decorator depends on whether the relationship is `{ async: true }` (which it is by default).
+### A Note About Recursive Imports
-- If the value is `true`, the type you should use is `AsyncHasMany`, where `Model` is the type of the model you are creating a relationship to.
-- If the value is `false`, the type is `SyncHasMany`, where `Model` is the type of the model you are creating a relationship to.
+Relationships between models in EmberData rely on importing the related models, like `import User from './user';`. This, naturally, can cause a recursive loop, as `/app/models/post.ts` imports `User` from `/app/models/user.ts`, and `/app/models/user.ts` imports `Post` from `/app/models/post.ts`. Recursive importing triggers an [`import/no-cycle`][import-no-cycle] error from ESLint.
-So, for example, you might define a class like this:
+To avoid these errors, use [type-only imports][type-only-imports]:
-```typescript {data-filename="app/models/thread.ts"}
-import Model, {
- hasMany,
- type AsyncHasMany,
- type SyncHasMany,
-} from '@ember-data/model';
-import type Comment from './comment';
+```typescript
import type User from './user';
+```
-export default class Thread extends Model {
- @hasMany('comment')
- declare comments: AsyncHasMany;
+### A Note About Open Types
- @hasMany('user', { async: false })
- declare participants: SyncHasMany;
-}
+When accessing `this.belongsTo` or `this.hasMany` from within a model, you'll need to pass the relationship `Model` type and the string key as generics, like so:
+
+```ts {data-filename="app/models/user.ts"}
+import Model, { hasMany, AsyncHasMany } from '@ember-data/model';
+import type Post from './post';
+import type { Type } from '@warp-drive/core-types/symbols';
-declare module 'ember-data/types/registries/model' {
- export default interface ModelRegistry {
- thread: Thread;
+export default class User extends Model {
+ @hasMany('post', { async: true, inverse: 'author' })
+ declare posts: AsyncHasMany;
+
+ get postIdList(): string[] {
+ return this.hasMany('posts').ids();
}
+
+ declare [Type]: 'user';
}
```
-The same basic rules about the safety of these lookups as with `@belongsTo` apply to these types. The difference is just that in `@hasMany` the resulting types are _arrays_ rather than single objects.
+The reason is that `this.belongsTo` and `this.hasMany` will infer an 'open' type for `this`, meaning that `this` can still be modified. For this reason, it's not able to index the keys of the model. As a workaround, pass in the 'concrete' or `closed` type for proper resolution.
## Transforms
In EmberData, `@attr` defines an [attribute on a Model][model-attrs]. By default, attributes are passed through as-is, however you can specify an optional type to have the value automatically transformed. EmberData ships with four basic transform types: `string`, `number`, `boolean` and `date`.
-You can define your own transforms by sub-classing [Transform][transform-guides]. EmberData transforms are normal TypeScript classes. The return type of `deserialize` method becomes type of the model class property.
+EmberData Transforms[transform-guides] are normal TypeScript classes. The return type of `deserialize` method becomes type of the model class property.
-You may define your own transforms in TypeScript like so:
+Transforms with a `Type` brand will have their type and options validated.
-```typescript {data-filename="app/transforms/coordinate-point.ts"}
-import Transform from '@ember-data/serializer/transform';
+### Example: Typing a Transform
-export type CoordinatePoint = {
- x: number;
- y: number;
-};
+```ts {data-filename="app/transforms/big-int.ts"}
+import type { Type } from '@warp-drive/core-types/symbols';
-export default class CoordinatePointTransform extends Transform {
- deserialize(serialized): CoordinatePoint {
- return { x: value[0], y: value[1] };
+export default class BigIntTransform {
+ deserialize(serialized: string): BigInt | null {
+ return !serialized || serialized === '' ? null : BigInt(serialized + 'n');
}
-
- serialize(value): number {
- return [value.x, value.y];
+ serialize(deserialized: BigInt | null): string | null {
+ return !deserialized ? null : String(deserialized);
}
-}
-declare module 'ember-data/types/registries/transform' {
- export default interface TransformRegistry {
- 'coordinate-point': CoordinatePointTransform;
+ declare [Type]: 'big-int';
+
+ static create() {
+ return new this();
}
}
```
-```typescript {data-filename="app/models/cursor.ts"}
+### Example: Using Transforms
+
+```ts {data-filename="app/models/user.ts"}
import Model, { attr } from '@ember-data/model';
-import { CoordinatePoint } from 'my-app/transforms/coordinate-point';
+import type { StringTransform } from '@ember-data/serializer/transforms';
+import type { Type } from '@warp-drive/core-types/symbols';
-export default class Cursor extends Model {
- @attr('coordinate-point') declare position: CoordinatePoint;
-}
+export default class User extends Model {
+ @attr('string') declare name: string;
-declare module 'ember-data/types/registries/model' {
- export default interface ModelRegistry {
- cursor: Cursor;
- }
+ declare [Type]: 'user';
}
```
-Note that you should declare your own transform under [`TransformRegistry`][ED-registry] to make `@attr` to work with your transform.
-
## Serializers and Adapters
-EmberData serializers and adapters are normal TypeScript classes. The only related gotcha is that you must [register][ED-registry] them with a declaration:
+EmberData serializers and adapters are normal TypeScript classes.
```typescript {data-filename="app/serializers/user-meta.ts"}
import Serializer from '@ember-data/serializer';
export default class UserMeta extends Serializer {}
-
-declare module 'ember-data/types/registries/serializer' {
- export default interface SerializerRegistry {
- 'user-meta': UserMeta;
- }
-}
```
```typescript {data-filename="app/adapters/user.ts"}
import Adapter from '@ember-data/adapter';
export default class User extends Adapter {}
-
-declare module 'ember-data/types/registries/adapter' {
- export default interface AdapterRegistry {
- user: User;
- }
-}
```
-## EmberData Registries
+## Adding EmberData Types to an Existing TypeScript App
-We use [registry][] approach for EmberData type lookups with string keys. As a result, once you add the module and interface definitions for each model, transform, serializer, and adapter in your app, you will automatically get type-checking and autocompletion and the correct return types for functions like `findRecord`, `queryRecord`, `adapterFor`, `serializerFor`, etc. No need to try to write out those types; just write your EmberData calls like normal and everything _should_ just work. That is, writing `this.store.findRecord('user', 1)` will give you back a `Promise`.
+The process for adding EmberData types to an existing TypeScript app is a work in progress. You can find the latest docs in the [EmberData repository][ED-ts-guides].
[decorator]: ../../additional-resources/gotchas/#toc_decorators
[ED-guides]: ../../../models/
-[ED-registry]: ./#toc_emberdata-registries
[model-attrs]: ../../../models/defining-models/
-[registry]: ../../additional-resources/gotchas/#toc_registries
[transforms]: ./#toc_transforms
[transform-guides]: ../../../models/defining-models/#toc_custom-transforms
+[brand]: https://github.com/emberjs/data/blob/main/guides/typescript/2-why-brands.md
[ED-api-docs]: https://api.emberjs.com/ember-data/release
+[ED-ts-guides]: https://github.com/emberjs/data/blob/main/guides/typescript/index.md
[import-no-cycle]: https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-cycle.md
[optional]: https://www.typescriptlang.org/docs/handbook/2/objects.html#optional-properties
[transform-api-docs]: https://api.emberjs.com/ember-data/release/classes/Transform
diff --git a/guides/release/typescript/getting-started.md b/guides/release/typescript/getting-started.md
index 560b96d015..0c40ed87f6 100644
--- a/guides/release/typescript/getting-started.md
+++ b/guides/release/typescript/getting-started.md
@@ -16,17 +16,16 @@ Project files will be generated with `.ts` extensions instead of `.js`.
In addition to the usual packages added with `ember new`, the following packages will be added at their current "latest" value:
-- `typescript`
-- `@tsconfig/ember`
-- `@typescript-eslint/*`
-- `@types/ember`
-- `@types/ember-data`
-- `@types/ember__*` – `@types/ember__object` for `@ember/object`, etc.
-- `@types/ember-data__*` – `@types/ember-data__model` for `@ember-data/model`, etc.
-- `@types/qunit`
-- `@types/rsvp`
-
-The `typescript` package provides tooling to support TypeScript type checking and compilation. The `@types` packages from [DefinitelyTyped][] provide TypeScript type definitions for all of the Ember and EmberData modules.
+- `typescript` – tooling to support TypeScript type checking and compilation.
+
+- `@tsconfig/ember` – a shared TypeScript configuration for Ember apps.
+- `@typescript-eslint/*` – ESLint support for TypeScript.
+- `@types/qunit` - TypeScript type definitions for QUnit.
+- `@types/rsvp` - TypeScript type definitions for RSVP.
+- `@warp-drive/core-types` - shared core types, type utilities and constants for the WarpDrive and EmberData packages.
@@ -34,10 +33,7 @@ The `typescript` package provides tooling to support TypeScript type checking an
Zoey says...
- Ember also publishes its own native types compiled directly from its source code. For now, we continue to use the @types
packages in these guides for the sake of compatibility with EmberData, because the EmberData @types
packages are not compatible with Ember's native official types.
-
-
- If you do not use EmberData, or if you use EmberData's alpha native types, we highly recommend following the instructions in this blog post to switch to the native types, which are guaranteed to always be 100% correct and 100% up to date!
+ Ember and EmberData publish their own native types compiled directly from their source code, so you do not need to install any @types/ember
or @types/ember-data
packages. These packages should be considered legacy, are only lightly maintained, and will conflict with the native types.
@@ -73,5 +69,3 @@ To convert an existing app to TypeScript, you'll need to make the changes descri
[tsconfig]: ../application-development/configuration/#toc_tsconfigjson
-
-[DefinitelyTyped]: https://github.com/DefinitelyTyped/DefinitelyTyped