Skip to content

[email protected]

Pre-release
Pre-release
Compare
Choose a tag to compare
@github-actions github-actions released this 04 Mar 14:14
· 676 commits to main since this release
1e844d4

Major Changes

  • #715 e8155b2 Thanks @NullVoxPopuli! - Drop support for TypeScript < 4.8 in order to support Glint.

  • #778 901ae9a Thanks @NullVoxPopuli! - The map 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.

    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 option noUncheckedIndexedAccess
    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 source data.

    But!, with noUncheckedIndexedAccess, you can only infer so much before TS goes the safe route,
    and makes the returned type X | 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]);
  • #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
    • isError has been renamed to isRejected
    • isLoading has been removed as it was redundant

    other changes:

    • trackedFunction is a wrapper around ember-async-data's TrackedAsyncData
    • 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 by ember-async-data,
    trackedFunction is now more of an example of how to integrated existing tracked utilities in to resources.


    Migration

    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:

    {{#if this.foo.value}}
      {{this.foo.value}}
    {{else}}
      initial displayed content
    {{/if}}

    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>
    }

    Previously, the isResolved property was true for succesful and error states

    Now, isResolved is only true when the function passed to trackedFunction has succesfully
    completed.

    To have behavior similar to the old behavior, you may want to implement your own isFinished getter:

    class Demo {
      foo = trackedFunction(this, async () => {
        /* ... */
      });
    
      get isFinished() {
        return this.foo.isResolved || this.foo.isRejected;
      }
    }

Minor Changes

Patch Changes