diff --git a/text/0000-decorators.md b/text/0000-decorators.md new file mode 100644 index 0000000000..eb1e5b5bdb --- /dev/null +++ b/text/0000-decorators.md @@ -0,0 +1,1265 @@ +- Start Date: 2018-10-20 +- RFC PR: (leave this empty) +- Ember Issue: (leave this empty) + +# Decorators + +## Summary + +Native classes are now officially supported in Ember, but currently their usage +is very limited. Core Ember features such as computed properties, actions, and +service injections have no publicly supported APIs to be used with native class +syntax. + +Decorators provide a way to abstract functionality and improve the developer +experience of working with native classes. This RFC outlines the implementation +and rollout plan for bringing decorators to Ember's computed properties (and +other behavior) for use in native classes. + +### A Note on Decorator Stability + +[Decorators](https://github.com/tc39/proposal-decorators) are important to +adopting native class syntax. They are a formalization of the patterns we have +been using as a community for years, and it will not be possible to use native +classes ergonomically without them unless a number of major concepts (computed +properties, injections, actions) are rethought. That said, as of today (01/03/19) +decorators are still a [_stage 2_ proposal](https://tc39.github.io/process-document) +in TC39, which means that while they are fully defined as a spec, they are not +yet considered a candidate for inclusion in the language, and may have +incremental changes that could be breaking if/when moved to stage 3. As such, +merging support for them now would pose some risk. Additionally, [class +fields](https://github.com/tc39/proposal-class-fields) are also required for +effective use of decorators, and while they are stage 3 in the process, they +have not yet been fully accepted either. + +Ember cannot guarantee that the spec won't change, and such changes cannot apply +to Ember's normal semver guarantees. But it can make the following guarantees: + +1. If there are changes to the spec, and it ***is*** possible to avoid changing + the public APIs of decorators, then Ember will make the changes necessary to + avoid public API changes. + +2. If there are changes to the spec, and it ***is not*** possible to avoid + breaking changes, Ember will minimize the changes as much as possible, and + will provide a codemod to convert from the previous version of the spec to + the next. + +3. If the spec is dropped from TC39 altogether, Ember would have to continue to + provide support for decorators via babel transforms until they are deprecated + following the standard RFC process, and removed according to SemVer. Reverse + codemods which translate decorators and native class syntax back to classic + class syntax _will_ be made, and alternatives for native class syntax will be + explored. + +4. Classic class syntax will continue to be supported _at least_ until these + features have been stabilized in the JavaScript language, to allow us to + revert these changes if necessary. They will most likely be supported for + longer to allow a smooth transition for users who do not want to adopt native + classes until they are completely stable. + +This RFC is being made with the assumption that decorators will be moved to +stage 3 in the near future, _before_ this RFC is implemented in Ember, +dramatically reducing the risk of adopting decorators. If this RFC is accepted +and decorators are not advanced in a timely manner, a followup RFC should be +made to determine whether or not decorators should be adopted in stage 2, and +what the support for them would look like. + +## Terminology + +For the purposes of this RFC, we'll use the following terminology: + +* The **Octane programming model** refers to the new programming model + established by the Ember Octane edition. It includes _native classes_, + _tracked properties_ and _Glimmer components_, and more generally refers to + features that will be considered _core to Ember_ in the future. +* The **classic programming model** refers to the traditional programming model. + It includes _classic classes_, _computed properties_, _event listeners_, + _observers_, _property notifications_, and _classic components_, and more + generally refers to features that will not be central to Ember Octane. +* **Native classes** are classes defined using the JavaScript `class` keyword +* **Classic classes** are classes defined by subclassing from `EmberObject` + using the static `extend` method. + +## Motivation + +Native JavaScript class syntax has been evolving for the past three years, filling in +the cracks and providing better, more standardized ways to write classes for the +web. They will be a key part of the Octane programming model, and the ES Classes +RFC was the first step toward enabling Ember users to use native class syntax, +but there are still key features of Ember that are not usable with `class` +syntax today, including *computed properties*, *actions*, and *injections*. + +These features cannot be used ergonomically with native classes as it stands. +The only options are to either use Ember's `defineProperty` function directly, +or to define these values in an anonymous class. Both of these options are hard +to read, and will be difficult to codemod in the future: + +```js +// Using define property +import { computed, defineProperty } from '@ember/object'; + +class Person { + constructor() { + this.firstName = 'Melanie'; + this.lastName = 'Sumner'; + } +} + +// define a computed property +defineProperty( + Person.prototype, + 'fullName', + computed('firstName', 'lastName', { + get() { + return `${this.firstName} ${this.lastName}`; + } + }) +); + +// Using intermediate extends +import EmberObject, { computed } from '@ember/object'; + +class Person extends EmberObject.extend({ + fullName: computed('firstName', 'lastName', function() { + return `${this.firstName} ${this.lastName}`; + }) +}) { + constructor() { + super(...arguments); + this.firstName = 'Melanie'; + this.lastName = 'Sumner'; + } +} +``` + +Another method which was used for some time was to assign these values using +class field initializers. This practice is problematic however as it creates a +new instance of the computed property per _instance_ of the class, and does not +work with native getters either: + +```ts +import EmberObject, { computed } from '@ember/object'; + +export default class Person extends EmberObject { + firstName = 'Melanie'; + lastName = 'Sumner'; + + fullName = computed('firstName', 'lastName', function() { + return `${this.firstName} ${this.lastName}`; + }); +} + +new Profile().fullName; // returns the CP instance, not 'Melanie Sumner' +``` + +The missing piece of functionality that we need here are _decorators_, and in +fact that is not a coincidence. The roots of the current TC39 decorator proposal +can be traced back to Ember (Yehuda having worked on the first few drafts of it) +specifically because computed properties, observers, and so on _are_ decorators. +We've been using them for years in Ember, just with a non-standard, +slightly-less-clean syntax. + +```ts +import Component from '@ember/component'; +import { computed } from '@ember/object'; + +export default class Profile extends Component { + firstName = 'Melanie'; + lastName = 'Sumner'; + + @computed('firstName', 'lastName') + get fullName() { + return `${this.firstName} ${this.lastName}`; + } +} +``` + +Native decorators bring a more natural way of declaring computed properties to +native classes than the current computed macros. + +## Prior Art + +The [Ember Decorators](http://ember-decorators.github.io/ember-decorators/) +project has been experimenting with using decorators within Ember for some time +now, with the goal of reaching feature parity with the classic object model, and +the learnings from that project will be used to inform the API design in this +RFC. + +## Detailed design + +This RFC proposes that: + +1. `Ember.computed`, the `inject` macros, and the computed property macros be + updated to return a standard JavaScript decorator. Internally, the classic + object model will be made aware of these decorators, and know how to apply + them. This will allow the same exact imports to continue working as both + native decorators and classic decorators. This decorator _will_ be compatible + with classes that extend from `EmberObject` and classes which do not. +2. A new `@action` decorator be added to allow the definition of actions on + classes. This decorator will _only_ be compatible with classes that are + action handlers. + +This does leave out some features from the classic programming model which are +currently provided by Ember Decorators. This is both to minimize decorators' API +surface area, and because they will not be a major part of Ember Octane's +programming model. Addressing them individually: + +* **Observers and event listeners**, which have long been considered an + antipattern. +* **Classic component** functionality such as `classNames`, `classNameBindings`, + `attributeBindings`, etc. will be unnecessary with Glimmer components. +* **Ember Data** provides computed properties which had to be manually wrapped + in decorators. With the changes proposed in this RFC, however, they should + continue to work without any additional changes. In fact, all computed + property macros will. + +Users who want these features will still be able to rely on addons such as Ember +Decorators, which will provide decorator support for them for the forseeable +future. Moving forward, this RFC breaks down into _computed properties_ and +_actions_. + +### Computed Properties + +As mentioned before, computed properties essentially _are_ decorators. However, +they are not spec compliant. Currently, `computed()` returns an instance of the +`ComputedProperty` class, which contains all of the meta information about the +decorated property. Native decorators, by contrast, are functions which receive +a descriptor and modify it as necessary. + +Unfortunately, there's no way for us to know _ahead of time_ when a computed +property is going to be used as a native decorator in a native class, and when +it is going to be used in a classic class. Consider the following: + +```js +class Person { + @alias('prefix') title; +} +``` + +Really, what's going on there is _not_ that we are invoking the `@alias` +decorator with parameters. We are invoking a function which _returns_ a +decorator, so it desugars to: + +```js +const aliasForPrefix = alias('prefix'); + +class Person { + @aliasForPrefix title; +} +``` + +Therefore, the `alias` function _must_ itself return a decorator function. +However, this conflicts with usage in the classic programming model: + +```js +const Person = EmberObject.extend({ + title: alias('prefix') +}) +``` + +We just established that `alias` must return a decorator function, but here it +is with the exact same arguments, and it needs to return a `ComputedProperty` +instance. There is nothing we can branch on here - in both cases, `alias` only +receives the string `'prefix'`, so it has no context for how it will be used. + +The native class piece of this puzzle is completely inflexible. A decorator must +be a function, there is no choice about it. However, the _Ember_ piece is _very_ +flexible. The classic object model just needs a way to get the meta information +for the property when the class is being finalized. We can either assign the +meta information to the decorator function directly, or we can associate it via +a [`WeakMap`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/WeakMap). + +The benefit of doing this is that the entire Ember ecosystem will get decorator +support with no extra work required. Because standard `computed` definitions +will work as decorators, existing macros will also work as decorators with no +changes to existing code. + +All of Ember's built in computed macros in the `@ember/object/computed` module +will also become decorators with no extra work. However, the injection macros +will require slight updates as they use a subclass of the `ComputedProperty` +class. These updates should be relatively minor, and will follow the same +strategy as `computed()`. + +#### Usage and API + +The API for `computed` will remain mostly the same. The key differences will be: + +1. The result of `computed` will be a decorator which can be applied directly to + native _getters_, _setters_, and _class fields_. +2. The `ComputedPropertyConfig` (the getter/setter functions) argument provided + to `computed` will now be optional when used as a decorator on a native + getter or setter, and the native getter/setter will be used instead. + +```ts +function computed(...args: (string | ComputedPropertyConfig)[]): PropertyDecorator; +``` + +The function signatures of all existing macros, including `inject` macros, would +change in the same way. + +In general usage, these three definitions are equivalent: + +```js +import EmberObject, { computed } from '@ember/object'; + +const Person = EmberObject.extend({ + fullName: computed('firstName', 'lastName', { + get() { + return `${this.firstName} ${this.lastName}`; + } + }) +}); + +class Person { + @computed('firstName', 'lastName') + get fullName() { + return `${this.firstName} ${this.lastName}`; + } +} + +class Person { + @computed('firstName', 'lastName', { + get() { + return `${this.firstName} ${this.lastName}`; + } + }) + fullName; +} +``` + +That last example may seem confusing at first, but this is actually the same as +defining a computed property macro: + +```js +import EmberObject, { computed } from '@ember/object'; + +function join(...dependentKeys) { + return computed(...dependentKeys, { + get() { + return dependentKeys.map(key => this[key]).join(' '); + } + }); +} + +class Person { + @join('firstName', 'lastName') + fullName; +} +``` + +Notably, using `@computed` as a decorator _directly_, without parenthesis, will +not be supported. This is to prevent a parameter check on a critical path: + +```js +class Person { + @computed + get fullName() { + return `${this.firstName} ${this.lastName}`; + } +} +``` + +The most common use case for this form in classic classes was to provide a new +instance of an object or array per instance of the class. This use case is +solved in native classes by class fields: + +```js +const Person = EmberObject.extend({ + cache: computed(function() { + return {}; + }) +}) + +class Person { + cache = {}; +} +``` + +For other use cases, such as lazy evaluation and caching, it will still be +possible to call `@computed` with no arguments: + +```js +class Person { + @computed() + get cache() { + return {}; + }; +} +``` + +#### Preventing Incoherent Usage + +Making the `ComputedPropertyConfig` optional opens up lots of room for +accidents. A computed property without a getter or setter does not make sense, +nor does a computed propery with _two_ getters or setters. The new decorator +will assert at _decorator application_ time to ensure it is being used +correctly: + +```js +import EmberObject, { computed } from '@ember/object'; + +// This will throw because the user attempted to define a CP without a getter +const Person = EmberObject.extend({ + fullName: computed() +}); + +// This will also throw because it is missing a getter +class Person { + @computed('firstName', 'lastName') + fullName; +} + +// This will throw because a getter was already defined +class Person { + @computed('firstName', 'lastName', { + get() { + return `${this.firstName} ${this.lastName}`; + } + }) + get fullName() { + return `${this.firstName} ${this.lastName}`; + } +} +``` + +Effectively, if `computed` is passed a `ComputedPropertyConfig`, it returns a +class field decorator. Otherwise, it returns an accessor (getter/setter) +decorator. + +#### Property Modifiers + +Almost all computed property modifiers have been deprecated at this point, but +they are still in use today and will still be available until Ember v4. As such, +their syntax needs to remain available and unchanged: + +```js +import EmberObject, { computed } from '@ember/object'; + +const Person = EmberObject.extend({ + fullName: computed('firstName', { + get() { + return `${this.firstName} ${this.lastName}`; + } + }).readOnly().volatile().property('lastName') // a very strange combination +}); +``` + +The decorator returned from `computed()` will need to have these chainable +methods available, and they will need to set the state of the decorator. This +should not be too difficult to accomplish. + +Usage in native decorator syntax is a little bit trickier. In the current +proposal, only simple chaining is allowed in a decorator invocation. You may not +chain on the result of a function: + +```js +import { computed } from '@ember/object'; + +const fullName = computed('firstName', 'lastName', function() { + return `${this.firstName} ${this.lastName}`; +}); + +class Person { + @computed('firstName', 'lastName').readOnly() // this is invalid JS decorator syntax + get fullName() { + return `${this.firstName} ${this.lastName}`; + } + + @fullName.readOnly() // this is valid because it's a simple chain + otherFullName; +} +``` + +Luckily, there is one other form of invocation which is available - wrapping the +entire decorator expression in parenthesis: + +```js +import { computed } from '@ember/object'; + +class Person { + @(computed('firstName', 'lastName').readOnly()) // this is valid + get fullName() { + return `${this.firstName} ${this.lastName}`; + } +} +``` + +This is clearly _not_ ideal for commonly used features, and while `volatile()` +and `property()` are not very well known, `readOnly()` is generally considered +best practice, and is used all over the place. However, it _is_ deprecated, and +in the future computed properties will be read only by default. Rather than +attempt to write a different API for decorators, this RFC proposes that we +accept the current syntax, and focus instead on the implementation of Svelte. +This will allow users to enable default read only CPs much sooner, and prevent +the need to use `readOnly()` at all. + +##### A Tale of Two `readOnly`s + +You may be wondering why we can't add more decorators to Ember to take the place +of these modifiers. The crux of the issue is the `readOnly()` modifier, and the +`readOnly()` macro. These share a name, and when macros become decorators as +well they will collide. The only difference would be the import paths, and this +would result in awkward renaming which would likely _not_ be conventional: + +```js +import { readOnly, computed } from '@ember/object'; +import { readOnly as readOnlyAlias } from '@ember/object/computed'; + +class Person { + @readOnly + @computed('firstName', 'lastName') + get fullName() { + return `${this.firstName} ${this.lastName}`; + } + + @readOnlyAlias('fullName') + otherFullName; +} +``` + +Ember Decorators made the decision to attempt renaming the macros themselves, +since `reads`, `readOnly`, and `oneWay` were all poorly named (arguably, `reads` +should have been `readOnly` since it is the more common use case) but this is +not a possibility in Ember, since this would cause collisions and confusion +within the macros namespace. + +All of this would be to support a deprecated feature, which can still be used +(albeit, with a less-than-ideal syntax). Accepting the current syntax would also +prevent us from polluting the decorator namespace - we may want to use +`@readOnly` or `@volatile` in the future with tracked properties instead. + +### Actions + +Actions are special in Ember because they are namespaced within a class on the +`actions` object, despite being able to reference the class directly using +`this` when called, and otherwise behaving like standard methods. The reason for +this stems from the early days of Ember, when users would accidentally name an +action something that conflicted with existing lifecycle and event hooks (e.g. +`destroy`, or `click`). The `actions` namespace was added as a convenience to +prevent these collisions, and to separate them from the rest of the class body +organizationally. + +This namespace is problematic for native classes for a number of reasons: + +1. The namespace cannot be defined using class field syntax, since that would + assign a copy of the object to every instance of the class, and there is no + other native way to easily assign it. +2. Actions must inherit from the parent, which means that the `actions` object + must have its prototype set to the parent class's `actions` object. +3. It is not possible to use `super` within non-class methods, meaning an + alternative would have to be developed specifically for the namespace. + +With these constraints, a decorator would be necessary to maintain the current +namespacing functionality. The options for such a decorator are limited. It +could: + +1. Decorate a class field, placing the value on the class prototype and setting + up inheritance/super functionality. This would necessarily result in either + some amount of repetition in the decorator name and field name, or a + redundant field name: + + ```js + class Foo { + @actions actions = { + onClick() { /* ... */ } + } + } + ``` + +2. Decorate the class itself with the actions object as a parameter. This would + be awkward, since actions would be removed from the class definition: + + ```js + @actions({ + onClick() { /* ... */ } + }) + class Foo {} + ``` + +3. Decorate the class itself, with the expectation that the `actions` class + field exists and is an object. This leads to a disconnect between the + decoration and the definition that is easy to miss, and could be + counterintuitive to newcomers: + + ```js + @actionHandler + class Foo { + actions = { + onClick() { /* ... */ } + } + } + ``` + +None of these options is ergonomic in the least. Instead, it is much cleaner and +easier to decorate method definitions that are directly on the class body: + +```js +class Foo { + @action + onClick() { /* ... */ } +} +``` + +This would be implemented by creating the class's `actions` object and assigning +the method to it, setting up inheritance and such in the process. The decorator +leaves the method definition on the class, where it can be called like a normal +method would, including `super` functionality. This maintains compatibility with +the classic object model, while making the `actions` namespace an +_implementation_ detail rather than something users need to know about. + +This does mean that actions can once again collide with actual lifecycle and +event hooks on the class, since they are no longer namespaced. The decorator +_could_ remove the method from the class definition entirely, but this would be +confusing for users not familiar with the old `actions` namespace - why does +this method disappear from the class? It would also break `super` functionality, +so it would not be ideal to do this. + +The `@action` decorator _could_ warn users when it collides with a lifecycle +hook. However, hooks may vary from class type to class type, which presents a +design challenge. We could either: + +1. Allow classes to specify lifecycle hooks, and throw whenever `@action` + collides with a specified hook. +2. Only throw on hooks that are shared across all classes, such as `init` and + `destroy`. +3. Do nothing, and leave it to user's to know which lifecycle hooks exist. + +Specifying hooks for each class would be time consuming and could fall out of +sync with the implementations. Throwing on "universal" hooks only is +inconsistent, and could lead users to think that they are safe when they are +not. This RFC suggests that we choose option 3 for this reason. When the actions +namespace was introduced, lifecycle hooks like `destroy` and `click` were less +commonly known and used (event listeners were not uncommon). Most Ember users +know they exist now, and will be aware that implementing an action with the same +name is not recommended. In addition, eslint rules can be added to hint against +these collisions. + +### Method Binding + +`@action` will also bind the function to the class instance, allowing it to be +used in templates and elsewhere without having to be bound: + +```js +export default class ButtonComponent extends Component { + @action + onClick() { + // handle click + } +} +``` +```hbs + +``` + +#### Usage and API + +The API for this new decorator would be much simpler than the computed API, +since it is only used as a decorator without parameters. + +```ts +// Technically `action` is a function, but we can't type it transparently that way +const action: MethodDecorator; +``` + +Attempting to pass any parameters to the decorator, or to apply the decorator to +anything other than a class method, will throw an error. + +## How we teach this + +Teaching decorators is intrinsically tied to a wider shift in the Ember +programming model - the Ember Octane edition. From a teaching perspective, this +edition will be completely overhauling the guides and updating all of the best +practices as they stand. New users should see native class syntax with +decorators as the _default_, and should not ever have to write a classic class +or see an example for one. + +With this RFC, the majority of existing examples in the Ember guides will be +updatable to native class syntax. The exception would be examples of classic +components, which would be addressed separately by updating the guides to +Glimmer Components (proposed in a separate RFC). Otherwise, all examples in the +guides should be updated. + +### Updating and Interop + +For existing users, or users who have to interact with classic code from a modern +context, it'll be important to have a reference for the classic object model. +The current section on the object model in the guides can be moved to a classic +section, and a section on updating should be added. Links to relevant codemods, +such as the +[ember-es6-class-codemod](https://github.com/scalvert/ember-es6-class-codemod), +should be included. This section should remain updated and included in the main +guides for as long as `EmberObject` is a part of Ember's public API. + +## Acceptance Commitments + +> This section serves to capture the various commitments accepting this RFC +> would entail. + +* Adds support for decorators and class fields to Ember's public API. Transforms + would be included out of the box as well. +* Allows computed properties to work as a native decorators on native classes. +* Adds the `@action` decorator for defining actions on native classes. + +## Drawbacks + +* The `ComputedProperty` class has long been considered intimate API. Even with + recent changes as part of the native getter RFC to make it more private, these + changes could still cause breakage. +* The strategy for converting `computed` to decorators has one major drawback, + which is that decorator macros cannot easily be customized and will require a + bit of boilerplate in some cases. For instance, currently in Ember Decorators + it is possible to apply the `map` and `reduce` macros directly to a _method_, + which becomes the method to map or reduce by: + + ```js + @map('array') + mappedArray() {} + + @map('array', function() {}) mappedArray; + ``` + + With this method, only the second form would be usable. Likewise, by default + addons like Ember Data would need to write thin decorator wrappers around + macros that may be called _without_ parameters, or which require key + reflection: + + ```js + @attr name; // This would not work OOTB + @attr('string') name; // This would + + @belongsTo user; // This would not work OOTB + @belongsTo('user') user; // This would + ``` + +## Alternatives + +### No Decorators + +We could not have official Ember support for any aspects of the classic +programming model. This essentially means computed properties, since `@action` +and the injection helpers are still needed for the Octane model. This would +leave many users in limbo, unable to update to native class syntax fully because +it would mean rewriting large amounts of classes and components, and would make +libraries like `@ember-decorators` essential. + +### Namespaced Decorators + +We could include decorators as a separate package, such as `@ember/decorators`. +This is not ideal as it would force users to remember more import paths, and it +would make eventual deprecation of the classic form much more difficult. It would +also mean that the wider ecosystem would have to do much more work to adopt +decorator syntax. + +### Full Compatibility Decorators + +We could include decorators for the remaining classic features: Observers, event +listeners, and classic components. These would add extra weight, and may +encourage users to continue using these features, which would not be ideal. + +Instead, we can recommend that users wanting to update to native class syntax +use external packages that implement these features, such as +`@ember-decorators`. The native class codemod will detect and automatically +include these packages if they are necessary. + +### Default Read Only Decorator + +In this proposal, computeds used as decorators match the semantics of computeds +used in classic classes exactly. This is true even in the unfortunate case of +computed overridability: + +```js +class Person { + firstName = 'Stefan'; + lastName = 'Penner'; + + @computed('firstName', 'lastName') + get fullName() { + return `${this.firstName} ${this.lastName}`; + } +} + +let person = new Person; + +person.fullName; // 'Tom Dale' + +person.set('firstName', 'Kris'); +person.set('lastName', 'Selden'); + +person.set('fullName', 'Melanie Sumner'); // overrides the setter + +person.fullName; // 'Melanie Sumner' +``` + +We could, instead, make decorators apply computeds as _readOnly by default_, +since this is a new usage of them and not a breaking change. This would require +us to add a new `overridable()` modifier to opt-out of readOnly behavior by +default, for backwards compatibility, and would add a fair amount of complexity +to codemods. Either way, this behavior will be the default in Ember v4, and +deprecations will begin appearing when this happens soon. + +### Allow `@action` to Rename Actions + +If the namespace collisions caused by actions becoming standard methods are +difficult to refactor around or codemod, we can consider allowing the `@action` +helper to receive an alternative action name: + +```js +export default class ButtonComponent extends GlimmerComponent { + @action('destroy') + destroyAction() { + // handle click + } +} +``` +```hbs + +``` + +This could be added later by a followup RFC, so it is not part of this proposal. +Ideally it won't be necessary. + +## Unresolved questions + +As stated in the introduction, this RFC is being made with the assumption that +decorators will be moved to stage 3 before this RFC is actually implemented. If +they are _not_ moved to stage 3, we will have to decide if decorators should be +supported in while they are in stage 2. + +If they are supported while in stage 2, there are some additional questions: + +* Should stage 2 transforms continue to be supported after decorators move to + stage 3? Would removing stage 2 support require a major version bump? +* Should Typescript's stage 1-like decorators be supported, since Typescript + will not implement new decorator transforms until they reach stage 3? + +## Appendix A + +This appendix contains the list of affected APIs and new APIs for quick reference. + +| `@ember/controller` | +|---------------------| +| `inject` | + +| `@ember/object` | +|-----------------| +| `computed` | +| _`action`_ | + +| `@ember/object/computed` | +|--------------------------| +| `alias` | +| `and` | +| `bool` | +| `collect` | +| `deprecatingAlias` | +| `empty` | +| `equal` | +| `filter` | +| `filterBy` | +| `gt` | +| `gte` | +| `intersect` | +| `lt` | +| `lte` | +| `map` | +| `mapBy` | +| `match` | +| `max` | +| `min` | +| `none` | +| `not` | +| `notEmpty` | +| `oneWay` | +| `or` | +| `readOnly` | +| `reads` | +| `setDiff` | +| `sort` | +| `sum` | +| `union` | +| `uniq` | +| `uniqBy` | + +| `@ember/service` | +|------------------| +| `inject` | + +## Appendix B + +This appendix contains a full list of changed APIs and their old and new signatures. + +### **`@ember/controller`** + +* `inject` + ```ts + // old + function inject(): ComputedProperty; + function inject( + name: K + ): ComputedProperty; + + // new + function inject(): PropertyDecorator; + function inject( + name: K + ): PropertyDecorator; + ``` + +### **`@ember/object`** + +* `computed` + ```ts + // old + function computed(...args: (string | ComputedPropertyConfig)[]): ComputedProperty; + + // new + function computed(...args: (string | ComputedPropertyConfig)[]): PropertyDecorator; + ``` + +* `action` + ```ts + // old + // N/A + + // new + const action: MethodDecorator; + ``` + +### **`@ember/object/computed` + +* `alias` + ```ts + // old + function alias(dependentKey: string): ComputedProperty; + + // new + function alias(dependentKey: string): PropertyDecorator; + ``` + +* `and` + ```ts + // old + function and(...dependentKeys: string[]): ComputedProperty; + + // new + function and(...dependentKeys: string[]): PropertyDecorator; + ``` + +* `bool` + ```ts + // old + function bool(dependentKey: string): ComputedProperty; + + // new + function bool(dependentKey: string): PropertyDecorator; + ``` + +* `collect` + ```ts + // old + function collect(...dependentKeys: string[]): ComputedProperty; + + // new + function collect(...dependentKeys: string[]): PropertyDecorator; + ``` + +* `deprecatingAlias` + ```ts + // old + function deprecatingAlias( + dependentKey: string, + options: { id: string; until: string } + ): ComputedProperty; + + // new + function deprecatingAlias( + dependentKey: string, + options: { id: string; until: string } + ): PropertyDecorator; + ``` + +* `empty` + ```ts + // old + function empty(dependentKey: string): ComputedProperty; + + // new + function empty(dependentKey: string): PropertyDecorator; + ``` + +* `equal` + ```ts + // old + function equal(dependentKey: string, value: any): ComputedProperty; + + // new + function equal(dependentKey: string, value: any): PropertyDecorator; + ``` + +* `filter` + ```ts + // old + function filter( + dependentKey: string, + callback: (value: any, index: number, array: any[]) => boolean + ): ComputedProperty; + + // new + function filter( + dependentKey: string, + callback: (value: any, index: number, array: any[]) => boolean + ): PropertyDecorator; + ``` + +* `filterBy` + ```ts + // old + function filterBy( + dependentKey: string, + propertyKey: string, + value?: any + ): ComputedProperty; + + // new + function filterBy( + dependentKey: string, + propertyKey: string, + value?: any + ): PropertyDecorator; + ``` + +* `gt` + ```ts + // old + function gt(dependentKey: string, value: number): ComputedProperty; + + // new + function gt(dependentKey: string, value: number): PropertyDecorator; + ``` + +* `gte` + ```ts + // old + function gte(dependentKey: string, value: number): ComputedProperty; + + // new + function gte(dependentKey: string, value: number): PropertyDecorator; + ``` + +* `intersect` + ```ts + // old + function intersect(...propertyKeys: string[]): ComputedProperty; + + // new + function intersect(...propertyKeys: string[]): PropertyDecorator; + ``` + +* `lt` + ```ts + // old + function lt(dependentKey: string, value: number): ComputedProperty; + + // new + function lt(dependentKey: string, value: number): PropertyDecorator; + ``` + +* `lte` + ```ts + // old + function lte(dependentKey: string, value: number): ComputedProperty; + + // new + function lte(dependentKey: string, value: number): PropertyDecorator; + ``` + +* `map` + ```ts + // old + function map( + dependentKey: string, + callback: (value: any, index: number, array: any[]) => U + ): ComputedProperty; + + // new + function map( + dependentKey: string, + callback: (value: any, index: number, array: any[]) => U + ): PropertyDecorator; + ``` + +* `mapBy` + ```ts + // old + function mapBy(dependentKey: string, propertyKey: string): ComputedProperty; + + // new + function mapBy(dependentKey: string, propertyKey: string): PropertyDecorator; + ``` + +* `match` + ```ts + // old + function match(dependentKey: string, regexp: RegExp): ComputedProperty; + + // new + function match(dependentKey: string, regexp: RegExp): PropertyDecorator; + ``` + +* `max` + ```ts + // old + function max(dependentKey: string): ComputedProperty; + + // new + function max(dependentKey: string): PropertyDecorator; + ``` + +* `min` + ```ts + // old + function min(dependentKey: string): ComputedProperty; + + // new + function min(dependentKey: string): PropertyDecorator; + ``` + +* `none` + ```ts + // old + function none(dependentKey: string): ComputedProperty; + + // new + function none(dependentKey: string): PropertyDecorator; + ``` + +* `not` + ```ts + // old + function not(dependentKey: string): ComputedProperty; + + // new + function not(dependentKey: string): PropertyDecorator; + ``` + +* `notEmpty` + ```ts + // old + function notEmpty(dependentKey: string): ComputedProperty; + + // new + function notEmpty(dependentKey: string): PropertyDecorator; + ``` + +* `oneWay` + ```ts + // old + function oneWay(dependentKey: string): ComputedProperty; + + // new + function oneWay(dependentKey: string): PropertyDecorator; + ``` + +* `or` + ```ts + // old + function or(...dependentKeys: string[]): ComputedProperty; + + // new + function or(...dependentKeys: string[]): PropertyDecorator; + ``` + +* `readOnly` + ```ts + // old + function readOnly(dependentKey: string): ComputedProperty; + + // new + function readOnly(dependentKey: string): PropertyDecorator; + ``` + +* `reads` + ```ts + // old + function reads(dependentKey: string): ComputedProperty; + + // new + function reads(dependentKey: string): PropertyDecorator; + ``` + +* `setDiff` + ```ts + // old + function setDiff(setAProperty: string, setBProperty: string): ComputedProperty; + + // new + function setDiff(setAProperty: string, setBProperty: string): PropertyDecorator; + ``` + +* `sort` + ```ts + // old + function sort( + itemsKey: string, + sortDefinition: string | ((itemA: any, itemB: any) => number) + ): ComputedProperty; + + // new + function sort( + itemsKey: string, + sortDefinition: string | ((itemA: any, itemB: any) => number) + ): PropertyDecorator; + ``` + +* `sum` + ```ts + // old + function sum(dependentKey: string): ComputedProperty; + + // new + function sum(dependentKey: string): PropertyDecorator; + ``` + +* `union` + ```ts + // old + function union(...propertyKeys: string[]): ComputedProperty; + + // new + function union(...propertyKeys: string[]): PropertyDecorator + ``` + +* `uniq` + ```ts + // old + function uniq(propertyKey: string): ComputedProperty; + + // new + function uniq(propertyKey: string): PropertyDecorator; + ``` + +* `uniqBy` + ```ts + // old + function uniqBy(dependentKey: string, propertyKey: string): ComputedProperty; + + // new + function uniqBy(dependentKey: string, propertyKey: string): PropertyDecorator; + ``` + +### **`@ember/service`** + +* `inject` + ```ts + // old + function inject(): ComputedProperty; + function inject( + name: K + ): ComputedProperty; + + // new + function inject(): PropertyDecorator; + function inject( + name: K + ): PropertyDecorator; + ```