diff --git a/.github/actions/assert-build/action.yml b/.github/actions/assert-build/action.yml index bce9d0385..28e491834 100644 --- a/.github/actions/assert-build/action.yml +++ b/.github/actions/assert-build/action.yml @@ -15,11 +15,6 @@ runs: core/index.js core/class-based/index.js core/function-based/index.js - util/map.js - util/helper.js - util/debounce.js - util/function.js - util/ember-concurrency.js index.js index.js.map index.d.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 59a2881ea..c3cb7ff33 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -151,29 +151,6 @@ jobs: node_modules/.bin/ember try:one ${{ matrix.try-scenario }} --skip-cleanup - test-ember-concurrency: - name: "w/ ${{ matrix.concurrencyVersion }}" - timeout-minutes: 5 - runs-on: ubuntu-latest - needs: [default_tests] - - strategy: - fail-fast: false - matrix: - concurrencyVersion: - - "test-app-ember-concurrency-2" - - "test-app-ember-concurrency-3" - - steps: - - uses: actions/checkout@v4 - - uses: wyvox/action-setup-pnpm@v3 - with: - node-version: 18.18.1 - - uses: ./.github/actions/download-built-package - - name: Run Tests - run: pnpm test:ember - working-directory: ${{ matrix.concurrencyVersion }} - MeasureAssetSizes: name: Measure Asset Sizes runs-on: ubuntu-latest diff --git a/docs/typedoc.utils.config.json b/docs/typedoc.utils.config.json index abd2b2d2b..226dc9f0f 100644 --- a/docs/typedoc.utils.config.json +++ b/docs/typedoc.utils.config.json @@ -9,14 +9,6 @@ "../ember-resources/src/link.ts", "../ember-resources/src/service.ts", "../ember-resources/src/modifier/index.ts", - "../ember-resources/src/util/debounce.ts", - "../ember-resources/src/util/ember-concurrency.ts", - "../ember-resources/src/util/fps.ts", - "../ember-resources/src/util/function.ts", - "../ember-resources/src/util/helper.ts", - "../ember-resources/src/util/keep-latest.ts", - "../ember-resources/src/util/map.ts", - "../ember-resources/src/util/remote-data.ts" ], "navigationLinks": { "Tutorial": "https://tutorial.glimdown.com", diff --git a/ember-resources/package.json b/ember-resources/package.json index c53a83c60..5fb41db28 100644 --- a/ember-resources/package.json +++ b/ember-resources/package.json @@ -15,16 +15,6 @@ "./link": "./dist/link.js", "./service": "./dist/service.js", "./modifier": "./dist/modifier/index.js", - "./util": "./dist/util/index.js", - "./util/cell": "./dist/core/cell.js", - "./util/keep-latest": "./dist/util/keep-latest.js", - "./util/fps": "./dist/util/fps.js", - "./util/map": "./dist/util/map.js", - "./util/helper": "./dist/util/helper.js", - "./util/remote-data": "./dist/util/remote-data.js", - "./util/debounce": "./dist/util/debounce.js", - "./util/function": "./dist/util/function.js", - "./util/ember-concurrency": "./dist/util/ember-concurrency.js", "./addon-main.js": "./addon-main.cjs" }, "typesVersions": { @@ -43,36 +33,6 @@ ], "modifier": [ "dist/modifier/index.d.ts" - ], - "util": [ - "dist/util/index.d.ts" - ], - "util/cell": [ - "dist/util/cell.d.ts" - ], - "util/keep-latest": [ - "dist/util/keep-latest.d.ts" - ], - "util/function": [ - "dist/util/function.d.ts" - ], - "util/fps": [ - "dist/util/fps.d.ts" - ], - "util/map": [ - "dist/util/map.d.ts" - ], - "util/helper": [ - "dist/util/helper.d.ts" - ], - "util/debounce": [ - "dist/util/debounce.d.ts" - ], - "util/remote-data": [ - "dist/util/remote-data.d.ts" - ], - "util/ember-concurrency": [ - "dist/util/ember-concurrency.d.ts" ] } }, diff --git a/ember-resources/src/util/debounce.ts b/ember-resources/src/util/debounce.ts deleted file mode 100644 index 5ddd0b315..000000000 --- a/ember-resources/src/util/debounce.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { tracked } from '@glimmer/tracking'; -import { deprecate } from '@ember/debug'; - -import { resource } from '../core'; - -deprecate( - `importing from 'ember-resources/util/debounce' is deprecated and will be removed in ember-resources@v7. ` + - `The exact same code and support is available at https://github.com/universal-ember/reactiveweb. ` + - `\`pnpm add reactiveweb\` and then \` import { debounce } from 'reactiveweb/debounce';\`. ` + - `See also: https://github.com/NullVoxPopuli/ember-resources/issues/1061`, - false, - { - id: `ember-resources.util.debounce`, - until: `7.0.0`, - for: `ember-resources`, - url: `https://reactive.nullvoxpopuli.com/functions/debounce.debounce.html`, - since: { - available: '6.4.4', - enabled: '6.4.4', - }, - }, -); - -class TrackedValue { - @tracked value: T | undefined; -} - -/** - *
- * - * This is not a core part of ember-resources, but is an example utility to demonstrate a concept when authoring your own resources. However, this utility is still under the broader library's SemVer policy. - * - * A consuming app will not pay for the bytes of this utility unless imported. - * - *
- * - * A utility for debouncing high-frequency updates. - * The returned value will only be updated every `ms` and is - * initially undefined. - * - * This can be useful when a user's typing is updating a tracked - * property and you want to derive data less frequently than on - * each keystroke. - * - * Note that this utility requires the `@use` decorator - * (debounce could be implemented without the need for the `@use` decorator - * but the current implementation is 8 lines) - * - * @example - * ```js - * import Component from '@glimmer/component'; - * import { tracked } from '@glimmer/tracking'; - * import { debounce } from 'ember-resources/util/debounce'; - * - * const delay = 100; // ms - * - * class Demo extends Component { - * @tracked userInput = ''; - * - * @use debouncedInput = debounce(delay, () => this.userInput); - * } - * ``` - * - * @example - * This could be further composed with RemoteData - * ```js - * import Component from '@glimmer/component'; - * import { tracked } from '@glimmer/tracking'; - * import { debounce } from 'ember-resources/util/debounce'; - * import { RemoteData } from 'ember-resources/util/remote-data'; - * - * const delay = 100; // ms - * - * class Demo extends Component { - * @tracked userInput = ''; - * - * @use debouncedInput = debounce(delay, () => this.userInput); - * - * @use search = RemoteData(() => `https://my.domain/search?q=${this.debouncedInput}`); - * } - * ``` - * - * @param {number} ms delay in milliseconds to wait before updating the returned value - * @param {() => Value} thunk function that returns the value to debounce - */ -export function debounce(ms: number, thunk: () => Value) { - let lastValue: Value; - let timer: number; - let state = new TrackedValue(); - - return resource(({ on }) => { - lastValue = thunk(); - - on.cleanup(() => timer && clearTimeout(timer)); - timer = setTimeout(() => (state.value = lastValue), ms); - - return state.value; - }); -} diff --git a/ember-resources/src/util/ember-concurrency.ts b/ember-resources/src/util/ember-concurrency.ts deleted file mode 100644 index 7eba2b384..000000000 --- a/ember-resources/src/util/ember-concurrency.ts +++ /dev/null @@ -1,265 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable @typescript-eslint/ban-types */ -/* eslint-disable ember/no-get */ -// typed-ember has not publihsed types for this yet -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -import { getValue } from '@glimmer/tracking/primitives/cache'; -import { deprecate } from '@ember/debug'; -import { assert } from '@ember/debug'; -// typed-ember has not publihsed types for this yet -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -import { invokeHelper } from '@ember/helper'; -import { get } from '@ember/object'; - -import { Resource } from '../core/class-based'; -import { DEFAULT_THUNK, normalizeThunk } from '../core/utils'; - -import type { Cache } from '../core/types'; - -deprecate( - `importing from 'ember-resources/util/ember-concurrency' is deprecated and will be removed in ember-resources@v7. ` + - `The exact same code and support is available at https://github.com/universal-ember/reactiveweb. ` + - `\`pnpm add reactiveweb\` and then \` import { task, trackedTask } from 'reactiveweb/ember-concurrency';\`. ` + - `See also: https://github.com/NullVoxPopuli/ember-resources/issues/1061`, - false, - { - id: `ember-resources.util.trackedTask`, - until: `7.0.0`, - for: `ember-resources`, - url: `https://reactive.nullvoxpopuli.com/functions/ember_concurrency.task.html`, - since: { - available: '6.4.4', - enabled: '6.4.4', - }, - }, -); - -/** - *
- * - * This is not a core part of ember-resources, but is an example utility to demonstrate a concept when authoring your own resources. However, this utility is still under the broader library's SemVer policy. - * - * A consuming app will not pay for the bytes of this utility unless imported. - * - *
- * - * uses Resource to make ember-concurrency tasks reactive. - * - * ------------------------- - * - * @note `ember-resources` does not provide or depend on ember-concurrency. - * If you want to use task, you'll need to add ember-concurrency as a dependency - * in your project. - * - * @example - * When `this.id` changes, the task will automatically be re-invoked. - * ```js - * import { tracked } from '@glimmer/tracking'; - * import { restartableTask, timeout } from 'ember-concurrency'; - * import { task as trackedTask } from 'ember-resources/util/ember-concurrency'; - * - * class Demo { - * @tracked id = 1; - * - * searchTask = restartableTask(async () => { - * await timeout(200); - * await fetch('...'); - * return 'the-value'; - * }) - * - * last = trackedTask(this, this.searchTask, () => [this.id]); - * } - * ``` - * ```hbs - * Available Properties: - * {{this.last.value}} - * {{this.last.isFinished}} - * {{this.last.isRunning}} - * ``` - * (and all other properties on a [TaskInstance](https://ember-concurrency.com/api/TaskInstance.html)) - * - * - */ -export function task< - Return = unknown, - Args extends unknown[] = unknown[], - LocalTask extends TaskIsh = TaskIsh, ->(context: object, task: LocalTask, thunk?: () => Args) { - assert(`Task does not have a perform method. Is it actually a task?`, 'perform' in task); - - let target = buildUnproxiedTaskResource(context, task, (thunk || DEFAULT_THUNK) as () => Args); - - // TS can't figure out what the proxy is doing - return proxyClass(target as any) as never as TaskInstance; -} - -export const trackedTask = task; - -const TASK_CACHE = new WeakMap(); - -function buildUnproxiedTaskResource< - ArgsList extends any[], - Return, - LocalTask extends TaskIsh = TaskIsh, ->(context: object, task: LocalTask, thunk: () => ArgsList) { - type LocalResource = TaskResource; - type Klass = new (...args: unknown[]) => LocalResource; - - let resource: Cache; - let klass: Klass; - let existing = TASK_CACHE.get(task); - - if (existing) { - klass = existing; - } else { - klass = class AnonymousTaskRunner extends TaskResource { - [TASK] = task; - } as Klass; - - TASK_CACHE.set(task, klass); - } - - return { - get value(): LocalResource { - if (!resource) { - resource = invokeHelper(context, klass, () => { - return normalizeThunk(thunk); - }) as Cache; - } - - return getValue(resource); - }, - }; -} - -/** - * @private - */ -export function proxyClass< - ArgsList extends any[], - Return, - LocalTask extends TaskIsh, - Instance extends TaskResource = TaskResource< - ArgsList, - Return, - LocalTask - >, ->(target: { value: Instance }) { - /* - * This proxy defaults to returning the underlying data on - * the task runner when '.value' is accessed. - * - * When working with ember-concurrency tasks, users have the expectation - * that they'll be able to inspect the status of the tasks, such as - * `isRunning`, `isFinished`, etc. - * - * To support that, we need to proxy to the `currentTask`. - * - */ - return new Proxy(target, { - get(target, key): unknown { - const taskRunner = target.value; - const instance = taskRunner.currentTask; - - if (typeof key === 'string') { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - get(taskRunner.currentTask, key); - } - - if (key === 'value') { - /** - * getter than falls back to the previous task's value - */ - return taskRunner.value; - } - - /** - * If the key is anything other than value, query on the currentTask - */ - const value = Reflect.get(instance as object, key, instance); - - return typeof value === 'function' ? value.bind(instance) : value; - }, - ownKeys(target): (string | symbol)[] { - return Reflect.ownKeys(target.value); - }, - getOwnPropertyDescriptor(target, key): PropertyDescriptor | undefined { - return Reflect.getOwnPropertyDescriptor(target.value, key); - }, - }) as never as Instance; -} - -export type TaskReturnType = T extends TaskIsh ? Return : unknown; -export type TaskArgsType = T extends TaskIsh ? Args : unknown[]; - -export interface TaskIsh { - perform: (...args: Args) => TaskInstance; - cancelAll: () => void; -} - -/** - * @private - * - * Need to define this ourselves, because between - * ember-concurrency 1, 2, -ts, decorators, etc - * there are 5+ ways the task type is defined - * - * https://github.com/machty/ember-concurrency/blob/f53656876748973cf6638f14aab8a5c0776f5bba/addon/index.d.ts#L280 - */ -export interface TaskInstance extends Promise { - readonly value: Return | null; - readonly error: unknown; - readonly isSuccessful: boolean; - readonly isError: boolean; - readonly isCanceled: boolean; - readonly hasStarted: boolean; - readonly isFinished: boolean; - readonly isRunning: boolean; - readonly isDropped: boolean; - cancel(reason?: string): void | Promise; -} - -/** - * @private - */ -export const TASK = Symbol('TASK'); - -/** - * @private - */ -export class TaskResource< - Args extends any[], - Return, - LocalTask extends TaskIsh, -> extends Resource<{ - positional: Args; -}> { - // Set via useTask - declare [TASK]: LocalTask; - // Set during setup/update - declare currentTask: TaskInstance; - declare lastTask: TaskInstance | undefined; - - get value() { - if (this.currentTask?.isFinished && !this.currentTask.isCanceled) { - return this.currentTask.value; - } - - return this.lastTask?.value; - } - - modify(positional: Args) { - if (this.currentTask) { - this.lastTask = this.currentTask; - } - - this.currentTask = this[TASK].perform(...positional); - } - - teardown() { - this[TASK].cancelAll(); - } -} diff --git a/ember-resources/src/util/fps.ts b/ember-resources/src/util/fps.ts deleted file mode 100644 index 42c2d123c..000000000 --- a/ember-resources/src/util/fps.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { deprecate } from '@ember/debug'; - -import { cell, resource, resourceFactory } from '../index'; - -deprecate( - `importing from 'ember-resources/util/fps' is deprecated and will be removed in ember-resources@v7. ` + - `The exact same code and support is available at https://github.com/universal-ember/reactiveweb. ` + - `\`pnpm add reactiveweb\` and then \` import { FrameRate, UpdateFrequency } from 'reactiveweb/fps';\`. ` + - `See also: https://github.com/NullVoxPopuli/ember-resources/issues/1061`, - false, - { - id: `ember-resources.util.fps`, - until: `7.0.0`, - for: `ember-resources`, - url: `https://reactive.nullvoxpopuli.com/modules/fps.html`, - since: { - available: '6.4.4', - enabled: '6.4.4', - }, - }, -); - -/** - *
- * - * This is not a core part of ember-resources, but is an example utility to demonstrate a concept when authoring your own resources. However, this utility is still under the broader library's SemVer policy. - * - * A consuming app will not pay for the bytes of this utility unless imported. - * - *
- * - * Utility that uses requestAnimationFrame to report - * how many frames per second the current monitor is - * rendering at. - * - * The result is rounded to two decimal places. - * - * ```js - * import { FrameRate } from 'ember-resources/util/fps'; - * - * - * ``` - */ -export const FrameRate = resource(({ on }) => { - let value = cell(0); - let startTime = new Date().getTime(); - let frame: number; - - let update = () => { - // simulate receiving data as fast as possible - frame = requestAnimationFrame(() => { - value.current++; - update(); - }); - }; - - on.cleanup(() => cancelAnimationFrame(frame)); - - // Start the infinite requestAnimationFrame chain - update(); - - return () => { - let elapsed = (new Date().getTime() - startTime) * 0.001; - let fps = value.current * Math.pow(elapsed, -1); - let rounded = Math.round(fps * 100) * 0.01; - // account for https://stackoverflow.com/a/588014/356849 - let formatted = `${rounded}`.substring(0, 5); - - return formatted; - }; -}); - -/** - *
- * - * This is not a core part of ember-resources, but is an example utility to demonstrate a concept when authoring your own resources. However, this utility is still under the broader library's SemVer policy. - * - * A consuming app will not pay for the bytes of this utility unless imported. - * - *
- * - * - * - * Utility that will report the frequency of updates to tracked data. - * - * ```js - * import { UpdateFrequency } from 'ember-resources/util/fps'; - * - * export default class Demo extends Component { - * @tracked someProp; - * - * @use updateFrequency = UpdateFrequency(() => this.someProp); - * - * - * } - * ``` - * - * NOTE: the function passed to UpdateFrequency may not set tracked data. - */ -export const UpdateFrequency = resourceFactory((ofWhat: () => unknown, updateInterval = 500) => { - updateInterval ||= 500; - - let multiplier = 1000 / updateInterval; - let framesSinceUpdate = 0; - - return resource(({ on }) => { - let value = cell(0); - let interval = setInterval(() => { - value.current = framesSinceUpdate * multiplier; - framesSinceUpdate = 0; - }, updateInterval); - - on.cleanup(() => clearInterval(interval)); - - return () => { - ofWhat(); - framesSinceUpdate++; - - return value.current; - }; - }); -}); diff --git a/ember-resources/src/util/function.ts b/ember-resources/src/util/function.ts deleted file mode 100644 index 8bf8b2eaa..000000000 --- a/ember-resources/src/util/function.ts +++ /dev/null @@ -1,325 +0,0 @@ -import { tracked } from '@glimmer/tracking'; -import { deprecate } from '@ember/debug'; -import { assert } from '@ember/debug'; -import { associateDestroyableChild, destroy, isDestroyed, isDestroying } from '@ember/destroyable'; - -import { TrackedAsyncData } from 'ember-async-data'; - -import { resource } from '../core/function-based'; - -deprecate( - `importing from 'ember-resources/util/function' is deprecated and will be removed in ember-resources@v7. ` + - `The exact same code and support is available at https://github.com/universal-ember/reactiveweb. ` + - `\`pnpm add reactiveweb\` and then \` import { trackedFunction } from 'reactiveweb/function';\`. ` + - `See also: https://github.com/NullVoxPopuli/ember-resources/issues/1061`, - false, - { - id: `ember-resources.util.function`, - until: `7.0.0`, - for: `ember-resources`, - url: `https://reactive.nullvoxpopuli.com/functions/function.trackedFunction.html`, - since: { - available: '6.4.4', - enabled: '6.4.4', - }, - }, -); - -/** - *
- * - * This is not a core part of ember-resources, but is an example utility to demonstrate a concept when authoring your own resources. However, this utility is still under the broader library's SemVer policy. - * - * A consuming app will not pay for the bytes of this utility unless imported. - * - *
- * - * _An example utility that uses resource_ - * - * Any tracked data accessed in a tracked function _before_ an `await` - * will "entangle" with the function -- we can call these accessed tracked - * properties, the "tracked prelude". If any properties within the tracked - * payload change, the function will re-run. - * - * ```js - * import Component from '@glimmer/component'; - * import { tracked } from '@glimmer/tracking'; - * import { resourceFactory, resource, use } from 'ember-resources'; - * import { trackedFunction } from 'ember-resources/util/function'; - * import { on } from '@ember/modifier'; - * - * const Request = resourceFactory((idFn) => { - * return resource(({use}) => { - * let trackedRequest = use(trackedFunction(async () => { - * let id = idFn(); - * let response = await fetch(`https://swapi.dev/api/people/${id}`); - * let data = await response.json(); - * - * return data; // { name: 'Luke Skywalker', ... } - * })); - * - * return trackedRequest; - * }); - * }); - * - * class Demo extends Component { - * @tracked id = 1; - * - * updateId = (event) => this.id = event.target.value; - * - * request = use(this, Request(() => this.id)); - * - * // Renders "Luke Skywalker" - * - * } - * ``` - */ -export function trackedFunction(fn: () => Return): State; - -/** - *
- * - * This is not a core part of ember-resources, but is an example utility to demonstrate a concept when authoring your own resources. However, this utility is still under the broader library's SemVer policy. - * - * A consuming app will not pay for the bytes of this utility unless imported. - * - *
- * - * _An example utility that uses resource_ - * - * Any tracked data accessed in a tracked function _before_ an `await` - * will "entangle" with the function -- we can call these accessed tracked - * properties, the "tracked prelude". If any properties within the tracked - * payload change, the function will re-run. - * - * ```js - * import Component from '@glimmer/component'; - * import { tracked } from '@glimmer/tracking'; - * import { trackedFunction } from 'ember-resources/util/function'; - * - * class Demo extends Component { - * @tracked id = 1; - * - * request = trackedFunction(this, async () => { - * let response = await fetch(`https://swapi.dev/api/people/${this.id}`); - * let data = await response.json(); - * - * return data; // { name: 'Luke Skywalker', ... } - * }); - * - * updateId = (event) => this.id = event.target.value; - * - * // Renders "Luke Skywalker" - * - * } - * ``` - * _Note_, this example uses the proposed `