Releases: NullVoxPopuli/ember-resources
[email protected]
Patch Changes
-
#960
77d54e6
Thanks @NullVoxPopuli! - Resolves: #958use
d Resources can now be immediately returned from other resources.const Clock = resource(({ use }) => { return use(Instant({ intervalMs: 1000 }); }); const Stopwatch = resource(({ use }) => { return use(Instant({ intervalMs: 0 }); }); <template> <time>{{Clock}}</time> MS since Epoch: {{Stopwatch}} </template>
[email protected]
Minor Changes
-
#952
1551b33
Thanks @NullVoxPopuli! - Introduce resources as modifiers.
This brings alignment with Starbeam's plans for modifiers as a universal primitive.In ember-resources, using modifiers as resources looks like this:
import { resource } from 'ember-resources'; import { modifier } from 'ember-resources/modifier'; const wiggle = modifier((element, arg1, arg2, namedArgs) => { return resource(({ on }) => { let animation = element.animate([ { transform: `translateX(${arg1}px)` }, { transform: `translateX(-${arg2}px)` }, ], { duration: 100, iterations: Infinity, }); on.cleanup(() => animation.cancel()); }); }); <template> <div {{wiggle 2 5 named="hello"}}>hello</div> </template>
The signature for the modifier here is different from
ember-modifier
, where positional args and named args are grouped together into an array and object respectively.This signature for ember-resource's
modifier
follows the plain function invocation signature.in Starbeam
import { resource } from '@starbeam/universal'; function wiggle(element, arg1, arg2, namedArgs) { return resource(({ on }) => { let animation = element.animate([ { transform: `translateX(${arg1}px)` }, { transform: `translateX(-${arg2}px)` }, ], { duration: 100, iterations: Infinity, }); on.cleanup(() => animation.cancel()); }); } <template> <div {{wiggle 2 5 named="hello"}}>hello</div> </template>
[email protected]
Patch Changes
- #947
16b844e
Thanks @NullVoxPopuli! - Update in-editor docs on the function resource
[email protected]
Patch Changes
-
#941
bfc432b
Thanks @NullVoxPopuli! - Fix an issue with a new (not yet used feature) where Resources could directly return aCell
, and it would have its.current
method automatically called when resolving the value of a Resource.import { resource, cell } from 'ember-resources'; export const Now = resource(({ on }) => { const now = cell(Date.now()); const timer = setInterval(() => now.set(Date.now())); on.cleanup(() => clearInterval(timer)); return now; }); <template> It is: <time>{{Now}}</time> </template>
[email protected]
Minor Changes
-
#936
6246a3c
Thanks @NullVoxPopuli! - Theuse
import fromember-resources
now supports an alternate style of usage.
This is partly to provide consistency across the different kinds of resources (and resource builders), whether or not arguments are provided.The motivation from this change comes from trying to better align with Starbeam's composition capabilities, and "define something once, use it anywhere" approach to that composition.
For example, before, only this was possible:
import { resource, use } from "ember-resources"; const StuckClock = resource(() => 2); class MyClass { @use data = StuckClock; } new MyClass().data === 2;
That looks a little awkward, because it looks like
data
is set to a constant.
InTypeScript
, this still worked out, and the type ofdata
would be anumber
,
but it still didn't look intuitive.Now, we can do this:
import { resource, use } from "ember-resources"; const StuckClock = resource(() => 2); class MyClass { data = use(this, StuckClock); } new MyClass().data.current === 2;
The key difference here is that
data
is now aReactive<number>
, which, like acell
, has a.current
property.
This is a readonly value -- howevercurrent
can still return a mutable data structure.This style of
use
ends up extending nicely to Resources that take arguments:import { tracked } from "@glimmer/tracking"; import { resource, use, resourceFactory } from "ember-resources"; const Clock = resourceFactory((locale) => resource(/* ... */)); class MyClass { @tracked locale = "en-US"; data = use( this, Clock(() => this.locale) ); }
Note
The old way of using@use
as a decorator is still supported, and has no plans of being deprecated.Another approach
I can't recommend this approach for general usage, but it is supported under SemVer (for exploration and feedback).
import { resource, use } from "ember-resources"; const StuckClock = resource(() => 2); class MyClass { @use(StuckClock) declare data: number; } new MyClass().data === 2;
This should feel familiar as it looks like what we're familiar with when it comes to declaring
@tracked
properties as well as@service
s.However, this has the same problems as
@service
-- in TypeScript, it requires you to usedeclare
and specify a type, which may or may not match the actual type ofStuckClock
.Additionally, whenever we want to pass arguments to the resource, like this:
import { tracked } from '@glimmer/tracking'; import { resource, use } from 'ember-resources'; const Clock = resourceFactory((locale) => resource( /* ... */); class MyClass { @tracked locale = 'en-US'; @use(Clock(() => this.locale) declare data: number; }
The arrow function passed to
Clock
would not have the correct this.
This is confusing, because in every other situation where we use classes,
the arrow function has the same context as the instance of the class.
But due to how decorators are configured / transpiled, thethis
is actually the surrounding context aroundMyClass
, because decorators are statically applied.class MyClass { @tracked locale = 'en-US'; @use(Clock( static context here, not instance ) declare data: number; }
So... that's why I want to recommend
property = use(this, Foo)
by default.class MyClass { @tracked locale = 'en-US'; data = use(this, (Clock( instance access )); }
[email protected]
Patch Changes
-
#925
e320cf8
Thanks @NullVoxPopuli! - Fix situation where, when composing with blueprint/factory-creted Resources, the owner was not passed to the tused
d resource.Example from the added testconst Now = resourceFactory((ms = 1000) => resource(({ on }) => { let now = cell(nowDate); let timer = setInterval(() => now.set(Date.now()), ms); on.cleanup(() => clearInterval(timer)); return () => now.current; }) ); const Stopwatch = resourceFactory((ms = 500) => resource(({ use }) => { let time = use(Now(ms)); return () => format(time); }) ); await render(<template><time>{{Stopwatch 250}}</time></template>);
The owner is part of the hooks API for
resource
and an error is thrown when it is undefined - regardless if used.const Demo = resource(({ on, use, owner }) => { // ... });
[email protected]
Minor Changes
-
#866
e1e4f66
Thanks @NullVoxPopuli! - Add the ability to compose function resources.
This is enabled only for function resources as class-based resources could already compose.how function resources compose
let formatter = new Intl.DateTimeFormat("en-US", { hour: "numeric", minute: "numeric", second: "numeric", hour12: false, }); let format = (time) => formatter.format(time.current); // representing the current time. // This could be rendered directly as {{Now}} // but Date does not serialize nicely for humans (Date.now() is a number) const Now = resource(({ on }) => { let now = cell(Date.now()); let timer = setInterval(() => now.set(Date.now()), 1000); on.cleanup(() => clearInterval(timer)); return () => now.current; }); const Clock = resource(({ use }) => { let time = use(Now); return () => format(time); }); // Rendered, Clock is always the formatted time <template> <time>{{ Clock }}</time> </template>;
Patch Changes
- #829
ff776b1
Thanks @NullVoxPopuli! - Move ember-async-data to "dependencies" because users are not required to import from that package ever"
[email protected]
Major Changes
-
#715
e8155b2
Thanks @NullVoxPopuli! - Drop support for TypeScript < 4.8 in order to support Glint. -
#778
901ae9a
Thanks @NullVoxPopuli! - Themap
utility resource has changed its first type-argument for better inference.The utility already supported inference, so this change should not impact too many folks.
Migration and Reasoning
When explicit type-arguments were specified,
class Demo { // previously a = map<Element>(this, { data: () => [ /* ... list of Element(s) ... */ ], map: (element) => { /* some transform */ }, }); // now a = map<Element[]>(this, { data: () => [ /* ... list of Element(s) ... */ ], map: (element) => { /* some transform */ }, }); }
This is advantageous, because with
@tsconfig/ember
, the optionnoUncheckedIndexedAccess
is enabled by default. This is a great strictness / quality option to have enabled,
as arrays in javascript are mutable, and we can't guarantee that they don't change between
index-accesses.However the
map
utility resource explicitly disallows the indicies to get out of sync
with the sourcedata
.But!, with
noUncheckedIndexedAccess
, you can only infer so much before TS goes the safe route,
and makes the returned typeX | undefined
.For example, in these type-tests:
import { map } from "ember-resources/util/map"; import { expectType } from "ts-expect"; const constArray = [1, 2, 3]; b = map(this, { data: () => constArray, map: (element) => { expectType<number>(element); return element; }, }); // index-access here is *safely* `| undefined`, due to `constArray` being mutable. expectType<number | undefined>(b[0]); expectType<number | undefined>(b.values()[0]); // but when we use a const as const array, we define a tuple, // and can correctly infer and return real values via index access const tupleArray = [1, 2, 3] as const; c = map(this, { data: () => tupleArray, map: (element) => { expectType<number>(element); return element; }, }); // No `| undefined` here expectType<number>(c[0]); expectType<number>(c.values()[0]);
-
#815
54e2b50
Thanks @NullVoxPopuli! - TheRemoteData
resource now has the same state changes and semantics astrackedFunction
.Breaking Changes:
isResolved
is only true when the request succeeds. During migration, you may useisFinished
for previous behavior.
-
#779
a471d9b
Thanks @NullVoxPopuli! -trackedFunction
has a new API and thus a major version release is required.Work by @lolmaus
tl;dr: the breaking changes:
- no more manual initial value
isResolved
is only true on success
other changes:
trackedFunction
is a wrapper aroundember-async-data
'sTrackedAsyncData
ember-async-data
will need to be installed in the consumer's app to continue usingtrackedFunction
This keeps installs minimal for folks using ember-resources and are not usingtrackedFunction
- behavior is otherwise the same
NOTE:
trackedFunction
is an example utility of how to use auto-tracking with function invocation,
and abstract away the various states involved with async behavior. Now that the heavy lifting is done byember-async-data
,
trackedFunction
is now more of an example of how to integrated existing tracked utilities in to resources.Migration
Previously, the state's
isResolved
property ontrackedFunction
wastrue
on both success and error.now,
isFinished
can be used instead.
isResolved
is now only true when the function runs to completion without error, aligning with the semantics of promises.class Demo { foo = trackedFunction(this, async () => { /* ... */ }); <template> {{this.foo.isFinished}} = {{this.foo.isResolved}} or {{this.foo.isError}} </template> }
Previously,
trackedFunction
could take an initial value for its second argument.class Demo { foo = trackedFunction(this, "initial value", async () => { /* ... */ }); }
This has been removed, as initial value can be better maintained and made more explicit
in user-space. For example:class Demo { foo = trackedFunction(this, async () => { /* ... */ }); get value() { return this.foo.value ?? "initial value"; } }
Or, in a template:
Or, in gjs/strict mode:
const withDefault = (value) => value ?? 'initial value'; class Demo extends Component { foo = trackedFunction(this, async () => { /* ... */ }); <template> {{withDefault this.foo.value}} </template> }
-
#785
66cee0e
Thanks @NullVoxPopuli! - The import pathember-resources/util/function-resource
has been removed,
as all the relevent exports have been available fromember-resources
since v5.
Minor Changes
-
#797
18adb86
Thanks @NullVoxPopuli! - Add link() and @link, importable fromember-resources/link
.NOTE: for existing users of
ember-resources
, this addition has no impact on your bundle.Example property usage
import { link } from 'ember-resources/link'; class MyClass { ... } export default class Demo extends Component { // This usage does now allow passing args to `MyClass` @link(MyClass) myInstance; }
Example inline usage
import Component from "@glimmer/component"; import { cached } from "@glimmer/tracking"; import { link } from "ember-resources/link"; export default class Demo extends Component { // To pass args to `MyClass`, you must use this form // NOTE though, that `instance` is linked to the `Demo`s lifecycle. // So if @foo is changing frequently, memory pressure will increase rapidly // until the `Demo` instance is destroyed. // // Resources are a better fit for this use case, as they won't add to memory pressure. @cached get myFunction() { let instance = new MyClass(this.args.foo); return link(instance, this); } }
This abstracts away the following boilerplate:
import { getOwner, setOwner } from "@ember/owner"; import { associateDestroyableChild } from "@ember/destroyable"; class MyClass { /* ... */ } export default class Demo extends Component { @cached get myInstance() { let instance = new MyClass(); associateDestroyableChild(this, instance); let owner = getOwner(this); if (owner) { setOwner(instance, owner); } return instance; } }
-
#778
f841a98
Thanks @NullVoxPopuli! - Use strictest possible settings with TypeScript so that consumers can't be stricter than this library -
#776
a99793e
Thanks @NullVoxPopuli! - Glint is now supported starting with 1.0.0-beta.3 -
#818
feeb2db
Thanks @NullVoxPopuli! - RemoteData now checks the response's Content-Type header to decide whether to convert to JSON or Text -
#794
8989bbb
Thanks @NullVoxPopuli! - New Utils: UpdateFreque...
[email protected]
Patch Changes
- #838
acbf03d
Thanks @NullVoxPopuli! - Fixes #835 - resolves regression introduced by PR: #808 which aimed to correctly return the previous task instance's value if the current task hasn't finished yet. The regression described by #835 was that if a task in cancelled (e.g.: dropped), it is considered finished, and that canceled task's value would be used instead of the last compuleted task. In normal ember-concurrency APIs, this is abstracted over via the.lastSuccessful
property on theTaskProperty
. The goal of the.value
ontrackedTask
is to mimic the property chain:taskProperty.lastSuccessful?.value
.
[email protected]
Patch Changes
- #836
ed6828c
Thanks @rpemberton! - Fixes #835 - resolves regression introduced by PR: #808 which aimed to correctly return the previous task instance's value if the current task hasn't finished yet. The regression described by #835 was that if a task in cancelled (e.g.: dropped), it is considered finished, and that canceled task's value would be used instead of the last compuleted task. In normal ember-concurrency APIs, this is abstracted over via the.lastSuccessful
property on theTaskProperty
. The goal of the.value
ontrackedTask
is to mimic the property chain:taskProperty.lastSuccessful?.value
.