From 22437e404fb4d45aa16987aa2c423aca5656bed3 Mon Sep 17 00:00:00 2001 From: Chris Thoburn Date: Thu, 16 May 2024 10:29:08 -0700 Subject: [PATCH 1/7] spike: pagination data utils --- .../ember/src/-private/pagination-state.ts | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 packages/ember/src/-private/pagination-state.ts diff --git a/packages/ember/src/-private/pagination-state.ts b/packages/ember/src/-private/pagination-state.ts new file mode 100644 index 00000000000..acf36e640e1 --- /dev/null +++ b/packages/ember/src/-private/pagination-state.ts @@ -0,0 +1,91 @@ +import { cached, tracked } from '@glimmer/tracking'; + +import type { + Future, + ImmutableRequestInfo, + ResponseInfo, + StructuredDocument, + StructuredErrorDocument, +} from '@ember-data/request'; +import type { Document } from '@ember-data/store'; + +import type { RequestState } from './request-state'; +import { getRequestState } from './request-state'; + +const RequestCache = new WeakMap, PaginationState>(); + +type FirstLink = { + prev: null; + self: RequestState; + next: Link | PlaceholderLink | null; + isVirtual: false; +}; + +type Link = { + prev: Link | PlaceholderLink | FirstLink; + self: RequestState; + next: Link | PlaceholderLink | null; + isVirtual: false; +}; + +type PlaceholderLink = { + prev: Link | FirstLink; + self: null; + next: Link; + isVirtual: true; +}; + +class PaginationState = Document> { + #pageList!: FirstLink; + + @tracked pages: Document[] = []; + @tracked data: T[] = []; + + constructor(request: Future) { + this.#pageList = { + prev: null, + self: getRequestState(request), + next: null, + isVirtual: false, + }; + } + + @cached + get isLoading() { + return this.pages.some((page) => page.isLoading); + } + + @cached + get isSuccess() { + return !this.isError; + } + + @cached + get isError() { + return this.pages.some((page) => page.isError); + } + + #addPage(page: Document) { + this.pages.push(page); + this.data = this.data.concat(page.data!); + } + + async next() { + const page = this.pages.at(-1); + const result = await page?.next(); + if (result) { + this.#addPage(result); + } + } +} + +export function getPaginationState>(future: Future): PaginationState { + let state = RequestCache.get(future) as PaginationState | undefined; + + if (!state) { + state = new PaginationState(future); + RequestCache.set(future, state); + } + + return state; +} From 2e15b54148d40d3ed8275e6296de531a4de99cf5 Mon Sep 17 00:00:00 2001 From: Chris Thoburn Date: Thu, 23 May 2024 15:29:34 -0700 Subject: [PATCH 2/7] upates for pagination --- packages/ember/README.md | 209 +++++++++++++++++++++++++++- tests/example-json-api/package.json | 3 + 2 files changed, 210 insertions(+), 2 deletions(-) diff --git a/packages/ember/README.md b/packages/ember/README.md index 70dcf4fc05c..a9d75a49834 100644 --- a/packages/ember/README.md +++ b/packages/ember/README.md @@ -42,6 +42,9 @@ Documentation - [RequestState](#requeststate) - [getRequestState](#getrequeststate) - [\](#request-) +- [PaginationState](#paginationstate) + - [getPaginationState](#getpaginationstate) + - [\](#paginate-) --- @@ -297,8 +300,15 @@ import { getRequestState } from '@warp-drive/ember'; #### \ -Alternatively, use the `` component. Note: the request component -taps into additional capabilities *beyond* what `RequestState` offers. +To make working with requests in templates even easier, you can use +the `` component. + +The `` component is *layout-less*. It is pure declarative control +flow with built-in state management utilities, and is designed to seamlessly +integrate with preferred patterns for loading data for routes and modals. + +`` taps into additional capabilities *beyond* +what `RequestState` offers. - Completion states and an abort function are available as part of loading state @@ -374,6 +384,45 @@ to the error block to handle. If no error block is present, the cancellation error will be swallowed. +- Idle is an additional state. + +The `<:idle>` state occurs when the request or query passed to the component +is `undefined` or `null`. + +```gjs +import { Request } from '@warp-drive/ember'; + + + +``` + +`<:idle>` states allow you avoid wrapping `` components in `{{#if}}` blocks +when the request isn't ready to be made. E.g. No need do to this: + +```gjs +import { Request } from '@warp-drive/ember'; + + +``` + +> [!IMPORTANT] +> `null` and `undefined` are only valid arguments to `` if an `<:idle>` block is provided. + +An important note is that `<:idle>` is effectively a special-cased error state. If no idle block is +provided, the component *will* throw an error if the argument is `null` or `undefined`. + - retry Cancelled and error'd requests may be retried, @@ -529,6 +578,162 @@ import { Request } from '@warp-drive/ember'; If a matching request is refreshed or reloaded by any other component, the `Request` component will react accordingly. +### PaginationState + +### getPaginationState + +#### \ + +The `` component is *layout-less*. Just like ``, it is pure +declarative control flow with built-in state management utilities. + +It's API mimic's ``, but + +**Render an infinite list** + +```gjs +import { Paginate } from '@warp-drive/ember'; + + +``` + +**Initial Request is loading** + +```diff + ++ <:loading> ++ ++ <:content as |pages|> + + {{item.title}} + ++ <:content> + +``` + +**Initial request errors** + +```diff + + <:loading> + + <:content as |pages|> + + {{item.title}} + + <:content> ++ ++ <:error as |error state|> ++ ++ ++ + +``` + +**Subsequent request is loading** + +```diff + + <:loading> + + <:content as |pages state|> ++ {{#if state.isLoadingPrev}} ++ ++ <:loading> ++ ++ {{/if}} + + {{item.title}} + + <:content> ++ {{#if state.isLoadingNext}} ++ ++ <:loading> ++ ++ {{/if}} + + <:error as |error state|> + + + + +``` + +**Subsequent request errors** + +```gjs +import { Paginate } from '@warp-drive/ember'; + + +``` + +**Render individual pages** + +```gjs +import { Paginate } from '@warp-drive/ember'; + + +``` + +**Page Links** + +```gjs +import { Paginate } from '@warp-drive/ember'; + + +``` --- diff --git a/tests/example-json-api/package.json b/tests/example-json-api/package.json index ef72de5f0b5..cf245da24e4 100644 --- a/tests/example-json-api/package.json +++ b/tests/example-json-api/package.json @@ -53,6 +53,9 @@ "@ember-data/unpublished-test-infra": { "injected": true }, + "@warp-drive/build-config": { + "injected": true + }, "@warp-drive/core-types": { "injected": true }, From 58f78be8c43ea750257847bb87a79bbea25915c9 Mon Sep 17 00:00:00 2001 From: Chris Thoburn Date: Thu, 23 May 2024 17:41:24 -0700 Subject: [PATCH 3/7] updates --- .github/workflows/release_publish-beta.yml | 2 +- CHANGELOG.md | 6 +- README.md | 4 +- ROADMAP.md | 2 +- guides/cookbook/naming-conventions.md | 2 +- .../configuration/2-one-to-many.md | 2 +- guides/requests/index.md | 2 +- packages/-ember-data/README.md | 4 +- packages/-ember-data/src/index.ts | 4 +- packages/adapter/src/rest.ts | 8 +- .../build-config/src/deprecation-versions.ts | 2 +- .../legacy-store-method.ts | 2 +- packages/core-types/src/record.ts | 2 +- .../diagnostic/server/reporters/default.js | 2 +- .../diagnostic/src/-ember/is-component.ts | 2 +- packages/ember/README.md | 187 ++++++++++++++---- packages/request-utils/README.md | 2 +- packages/request/README.md | 2 +- packages/request/src/-private/manager.ts | 2 +- 19 files changed, 174 insertions(+), 65 deletions(-) diff --git a/.github/workflows/release_publish-beta.yml b/.github/workflows/release_publish-beta.yml index 0c4ba5ef81c..8333b974ade 100644 --- a/.github/workflows/release_publish-beta.yml +++ b/.github/workflows/release_publish-beta.yml @@ -72,7 +72,7 @@ jobs: run: echo "value=$(bun --silent release latest beta)" >> $GITHUB_OUTPUT - name: Publish New Release # For beta-cycle we always increment from the branch state - # For mirror we increment from the last beta version, unless it's start of a new cycle. + # For mirror we increment from the last beta version, unless it is start of a new cycle. if: github.event.inputs.kind == 'beta-cycle' || github.event.inputs.is-cycle-start == 'true' run: bun release exec publish beta env: diff --git a/CHANGELOG.md b/CHANGELOG.md index 608ffc4feea..5fa6fb01bce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3515,7 +3515,7 @@ methods. - add documentation for the Store's find method - Do not double include the host when it uses a protocol relative url. - Deprecate RecordArray.pushRecord() -- Wrap the errorThrown in an Error object if it's a string. +- Wrap the errorThrown in an Error object if it is a string. - Use forEach instead of private api for accessing Map values - Disable unknown keys warning by default - remove type check for addCanonicalRecord in belongsto relationship @@ -3617,7 +3617,7 @@ export default DS.RESTSerializer.extend({ ##### `store.metaForType()` has been deprecated -`store.metaForType()` has been deprecated because of it's ambiguous naming. +`store.metaForType()` has been deprecated because of it is ambiguous naming. Please use `store.metadataFor()` to get metadata and `store.setMetadataFor()` to set metadata. @@ -3935,7 +3935,7 @@ correctly will need a shim for Object.create. - Update ember version to 1.4.0 - lock server while compiling assets - [DOC] Fix extractArray -- Don't pass the resolver where it's not needed +- Don't pass the resolver where it is not needed - force jshint on failure - explicitly set a handlebars dependency to a version known to work. - Remove Dead Code from RESTAdapter's Test diff --git a/README.md b/README.md index d4d995398c8..07fa46a06dc 100644 --- a/README.md +++ b/README.md @@ -68,13 +68,13 @@ Wrangle your application's data management with scalable patterns for developer ## 🪜 Architecture -*Ember***Data** is both _resource_ centric and _document_ centric in it's approach to caching, requesting and presenting data. Your application's configuration and usage drives which is important and when. +*Ember***Data** is both _resource_ centric and _document_ centric in its approach to caching, requesting and presenting data. Your application's configuration and usage drives which is important and when. The `Store` is a **coordinator**. When using a `Store` you configure what cache to use, how cache data should be presented to the UI, and where it should look for requested data when it is not available in the cache. This coordination is handled opaquely to the nature of the requests issued and the format of the data being handled. This approach gives applications broad flexibility to configure *Ember***Data** to best suit their needs. This makes *Ember***Data** a powerful solution for applications regardless of their size and complexity. -*Ember***Data** is designed to scale, with a religious focus on performance and asset-size to keep its footprint small but speedy while still being able to handle large complex APIs in huge data-driven applications with no additional code and no added application complexity. It's goal is to prevent applications from writing code to manage data that is difficult to maintain or reason about. +*Ember***Data** is designed to scale, with a religious focus on performance and asset-size to keep its footprint small but speedy while still being able to handle large complex APIs in huge data-driven applications with no additional code and no added application complexity. Its goal is to prevent applications from writing code to manage data that is difficult to maintain or reason about. *Ember***Data**'s power comes not from specific features, data formats, or adherence to specific API specs such as `JSON:API` `trpc` or `GraphQL`, but from solid conventions around requesting and mutating data developed over decades of experience scaling developer productivity. diff --git a/ROADMAP.md b/ROADMAP.md index 807de90b40d..bc418c7b7bf 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -88,7 +88,7 @@ Comprehensive DX around data management should extend to testing. We're thinking Currently, frontend mocking solutions typically rely on either mocking in the browser's ui-thread, or via ServiceWorker. Both of these present major downsides. -The ui-thread approach slows down test suite performance with high alloc, compute and gc costs. It's also much more difficult to debug due to circumventing the browser's built in tooling for inspecting requests, and less accurate because responses don't behave the same way or with the same timing as they would for a real request. The mix of generating similar looking API mocks and client side data in the same test or test module causes many devs to accidentally mix paradigms, and the difficulty in disocvering what you've mocked and what its state is regularly leads to over-mocking. Finally, it allows devs to often accidentally share object state in their test between the ui and the api which leads to difficult to reason about bugs. +The ui-thread approach slows down test suite performance with high alloc, compute and gc costs. it is also much more difficult to debug due to circumventing the browser's built in tooling for inspecting requests, and less accurate because responses don't behave the same way or with the same timing as they would for a real request. The mix of generating similar looking API mocks and client side data in the same test or test module causes many devs to accidentally mix paradigms, and the difficulty in disocvering what you've mocked and what its state is regularly leads to over-mocking. Finally, it allows devs to often accidentally share object state in their test between the ui and the api which leads to difficult to reason about bugs. The MSW approach solves many of the problems of the ui-thread approach, but introduces problems of its own. Developers must now carefully uninstall the service worker when changing between apps they are developing that usually run on the same domain, it intercepts even for dev when it ought to be used only for tests, it interferes with using the ServiceWorker for actual application needs, and it lacks privileged access to the file system to cache state for reuse to optimize performance of repeat test runs. diff --git a/guides/cookbook/naming-conventions.md b/guides/cookbook/naming-conventions.md index e6621172109..2d0cd346bce 100644 --- a/guides/cookbook/naming-conventions.md +++ b/guides/cookbook/naming-conventions.md @@ -44,7 +44,7 @@ What does consistency look like? ### But what about JSON:API spec? -It's pretty simple, JSON:API spec agnostic about the `type` field convention. Here is the quote from the spec: +It is pretty simple, JSON:API spec agnostic about the `type` field convention. Here is the quote from the spec: > Note: This spec is agnostic about inflection rules, so the value of type can be either plural or singular. However, the same value should be used consistently throughout an implementation. diff --git a/guides/relationships/configuration/2-one-to-many.md b/guides/relationships/configuration/2-one-to-many.md index ed5f99c18f5..7fcbc42be9f 100644 --- a/guides/relationships/configuration/2-one-to-many.md +++ b/guides/relationships/configuration/2-one-to-many.md @@ -59,7 +59,7 @@ graph LR; With distinct relationships, we may edit one side without affecting the state of the inverse. This is particularly useful in two situations. -First, it may be the case that the user has thousands or tens of thousands of activities. In this case, you likely don't want whichever individual activities you happen to load to create an incomplete list of the TrailRunner's activities. It's better to load and work with the activities list in isolation, ideally in a paginated manner. +First, it may be the case that the user has thousands or tens of thousands of activities. In this case, you likely don't want whichever individual activities you happen to load to create an incomplete list of the TrailRunner's activities. It is better to load and work with the activities list in isolation, ideally in a paginated manner. Second, it may be the case that runner is able to share the activity data with another runner that forgot to record. By not coupling the relationship, the ActivityData can still be owned by the first runner by included in the second runner's list of activities as well. diff --git a/guides/requests/index.md b/guides/requests/index.md index 4d74ac42a04..174b1cf9361 100644 --- a/guides/requests/index.md +++ b/guides/requests/index.md @@ -24,7 +24,7 @@ const users = userList.content; ### Making Requests -`RequestManager` has a single asynchronous method as it's API: `request` +`RequestManager` has a single asynchronous method as its API: `request` ```ts class RequestManager { diff --git a/packages/-ember-data/README.md b/packages/-ember-data/README.md index ef7a9122d9d..c40b45c4090 100644 --- a/packages/-ember-data/README.md +++ b/packages/-ember-data/README.md @@ -66,13 +66,13 @@ Wrangle your application's data management with scalable patterns for developer ## 🪜 Architecture -*Ember***Data** is both _resource_ centric and _document_ centric in it's approach to caching, requesting and presenting data. Your application's configuration and usage drives which is important and when. +*Ember***Data** is both _resource_ centric and _document_ centric in its approach to caching, requesting and presenting data. Your application's configuration and usage drives which is important and when. The `Store` is a **coordinator**. When using a `Store` you configure what cache to use, how cache data should be presented to the UI, and where it should look for requested data when it is not available in the cache. This coordination is handled opaquely to the nature of the requests issued and the format of the data being handled. This approach gives applications broad flexibility to configure *Ember***Data** to best suite their needs. This makes *Ember***Data** a powerful solution for applications regardless of their size and complexity. -*Ember***Data** is designed to scale, with a religious focus on performance and asset-size to keep its footprint small but speedy while still being able to handle large complex APIs in huge data-driven applications with no additional code and no added application complexity. It's goal is to prevent applications from writing code to manage data that is difficult to maintain or reason about. +*Ember***Data** is designed to scale, with a religious focus on performance and asset-size to keep its footprint small but speedy while still being able to handle large complex APIs in huge data-driven applications with no additional code and no added application complexity. Its goal is to prevent applications from writing code to manage data that is difficult to maintain or reason about. *Ember***Data**'s power comes not from specific features, data formats, or adherence to specific API specs such as `JSON:API` `trpc` or `GraphQL`, but from solid conventions around requesting and mutating data developed over decades of experience scaling developer productivity. diff --git a/packages/-ember-data/src/index.ts b/packages/-ember-data/src/index.ts index 72f4446b516..1b08d14db1b 100644 --- a/packages/-ember-data/src/index.ts +++ b/packages/-ember-data/src/index.ts @@ -44,13 +44,13 @@ Wrangle your application's data management with scalable patterns for developer The core of *Ember*‍**Data** is the `Store`, which coordinates interaction between your application, the `Cache`, and sources of data (such as your `API` or a local persistence layer). Optionally, the Store can be configured to hydrate the response data into rich presentation classes. -*Ember*‍**Data** is both resource centric and document centric in it's approach to caching, requesting and presenting data. Your application's configuration and usage drives which is important and when. +*Ember*‍**Data** is both resource centric and document centric in its approach to caching, requesting and presenting data. Your application's configuration and usage drives which is important and when. The `Store` is a **coordinator**. When using a `Store` you configure what cache to use, how cache data should be presented to the UI, and where it should look for requested data when it is not available in the cache. This coordination is handled opaquely to the nature of the requests issued and the format of the data being handled. This approach gives applications broad flexibility to configure *Ember*‍**Data** to best suite their needs. This makes *Ember*‍**Data** a powerful solution for applications regardless of their size and complexity. -*Ember*‍**Data** is designed to scale, with a religious focus on performance and asset-size to keep its footprint small but speedy while still being able to handle large complex APIs in huge data-driven applications with no additional code and no added application complexity. It's goal is to prevent applications from writing code to manage data that is difficult to maintain or reason about. +*Ember*‍**Data** is designed to scale, with a religious focus on performance and asset-size to keep its footprint small but speedy while still being able to handle large complex APIs in huge data-driven applications with no additional code and no added application complexity. Its goal is to prevent applications from writing code to manage data that is difficult to maintain or reason about. *Ember*‍**Data**'s power comes not from specific features, data formats, or adherence to specific API specs such as `JSON:API` `trpc` or `GraphQL`, but from solid conventions around requesting and mutating data developed over decades of experience scaling developer productivity. diff --git a/packages/adapter/src/rest.ts b/packages/adapter/src/rest.ts index bbb7b62aacb..971f5505c2f 100644 --- a/packages/adapter/src/rest.ts +++ b/packages/adapter/src/rest.ts @@ -203,7 +203,7 @@ declare const jQuery: JQueryStatic | undefined; ``` If the records in the relationship are not known when the response - is serialized it's also possible to represent the relationship as a + is serialized it is also possible to represent the relationship as a URL using the `links` key in the response. Ember Data will fetch this URL to resolve the relationship when it is accessed for the first time. @@ -1468,12 +1468,12 @@ export function fetchOptions( } else { // NOTE: a request's body cannot be an object, so we stringify it if it is. // JSON.stringify removes keys with values of `undefined` (mimics jQuery.ajax). - // If the data is not a POJO (it's a String, FormData, etc), we just set it. - // If the data is a string, we assume it's a stringified object. + // If the data is not a POJO (it is a String, FormData, etc), we just set it. + // If the data is a string, we assume it is a stringified object. /* We check for Objects this way because we want the logic inside the consequent to run * if `options.data` is a POJO, not if it is a data structure whose `typeof` returns "object" - * when it's not (Array, FormData, etc). The reason we don't use `options.data.constructor` + * when it is not (Array, FormData, etc). The reason we don't use `options.data.constructor` * to check is in case `data` is an object with no prototype (e.g. created with null). */ if (Object.prototype.toString.call(options.data) === '[object Object]') { diff --git a/packages/build-config/src/deprecation-versions.ts b/packages/build-config/src/deprecation-versions.ts index 78d21a10057..448ddc4117f 100644 --- a/packages/build-config/src/deprecation-versions.ts +++ b/packages/build-config/src/deprecation-versions.ts @@ -30,7 +30,7 @@ * 3.13 as its minimum version compatibility, any deprecations introduced before * or during 3.13 would be stripped away. * - * An app can use a different version than what it specifies as it's compatibility + * An app can use a different version than what it specifies as its compatibility * version. For instance, an App could be using `3.16` while specifying compatibility * with `3.12`. This would remove any deprecations that were present in or before `3.12` * but keep support for anything deprecated in or above `3.13`. diff --git a/packages/codemods/src/legacy-compat-builders/legacy-store-method.ts b/packages/codemods/src/legacy-compat-builders/legacy-store-method.ts index 5ae7222d13b..300e849064b 100644 --- a/packages/codemods/src/legacy-compat-builders/legacy-store-method.ts +++ b/packages/codemods/src/legacy-compat-builders/legacy-store-method.ts @@ -152,7 +152,7 @@ export function transformLegacyStoreMethod( ], }); } else if (j.ReturnStatement.check(path.parent.parent.value)) { - // It's not assigned to a variable so we don't need to worry about destructuring + // It is not assigned to a variable so we don't need to worry about destructuring // but we do need to make sure the value stays the same, so: // Wrap the whole await expression in a MemberExpression to add `.content` const memberExpression = j.memberExpression.from({ diff --git a/packages/core-types/src/record.ts b/packages/core-types/src/record.ts index ccb83b89518..5ae531118f6 100644 --- a/packages/core-types/src/record.ts +++ b/packages/core-types/src/record.ts @@ -129,7 +129,7 @@ type _ExtractUnion< * to allow for other strategies. * * There's a 90% chance this particular implementation belongs being in the JSON:API package instead - * of core-types, but it's here for now. + * of core-types, but it is here for now. * * @typedoc */ diff --git a/packages/diagnostic/server/reporters/default.js b/packages/diagnostic/server/reporters/default.js index 0c980474f95..baed24eb3c7 100644 --- a/packages/diagnostic/server/reporters/default.js +++ b/packages/diagnostic/server/reporters/default.js @@ -529,7 +529,7 @@ export default class CustomDotReporter { } // Instead of completely removing, we replace the contents with an empty string so that CI will still cache it. -// While this shouldn't ever really be necessary it's a bit more correct to make sure that the log gets cleared +// While this shouldn't ever really be necessary it is a bit more correct to make sure that the log gets cleared // in the cache as well. function remove(filePath) { fs.writeFileSync(filePath, '', { encoding: 'utf-8' }); diff --git a/packages/diagnostic/src/-ember/is-component.ts b/packages/diagnostic/src/-ember/is-component.ts index cab683dbeaa..dd830202170 100644 --- a/packages/diagnostic/src/-ember/is-component.ts +++ b/packages/diagnostic/src/-ember/is-component.ts @@ -10,7 +10,7 @@ export type ComponentLike = object; * (see https://github.com/emberjs/rfcs/pull/785 for more info). * @private * @param {Object} maybeComponent The thing you think might be a component - * @returns {boolean} True if it's a component, false if not + * @returns {boolean} True if it is a component, false if not */ function isComponent(maybeComponent: object): maybeComponent is ComponentLike { return !!(getComponentManager as (c: object, v: boolean) => object)(maybeComponent, true); diff --git a/packages/ember/README.md b/packages/ember/README.md index a9d75a49834..e63dab205dc 100644 --- a/packages/ember/README.md +++ b/packages/ember/README.md @@ -87,7 +87,7 @@ That brings us to our second motivation: performance. Performance is always at the heart of WarpDrive libraries. `@warp-drive/ember` isn't just a library of utilities for working with reactive -asynchronous data in your Ember app. It's *also* a way to optimize your app for +asynchronous data in your Ember app. It is *also* a way to optimize your app for faster, more correct renders. It starts with `setPromiseResult` a simple core primitive provided by the library @@ -298,7 +298,7 @@ import { getRequestState } from '@warp-drive/ember'; ``` -#### \ +### \ To make working with requests in templates even easier, you can use the `` component. @@ -310,7 +310,7 @@ integrate with preferred patterns for loading data for routes and modals. `` taps into additional capabilities *beyond* what `RequestState` offers. -- Completion states and an abort function are available as part of loading state +#### Completion states and an abort function are available as part of loading state ```gjs import { Request } from '@warp-drive/ember'; @@ -337,7 +337,7 @@ When using the Await component, if no error block is provided and the request re the error will be thrown. Cancellation errors are not rethrown if no error block or cancellation block is present. -- Streaming Data +#### Streaming Data The loading state exposes the download `ReadableStream` instance for consumption @@ -357,7 +357,7 @@ import { Request } from '@warp-drive/ember'; ``` -- Cancelled is an additional state. +#### Cancelled is an additional state. ```gjs import { Request } from '@warp-drive/ember'; @@ -384,7 +384,7 @@ to the error block to handle. If no error block is present, the cancellation error will be swallowed. -- Idle is an additional state. +#### Idle is an additional state. The `<:idle>` state occurs when the request or query passed to the component is `undefined` or `null`. @@ -423,7 +423,7 @@ import { Request } from '@warp-drive/ember'; An important note is that `<:idle>` is effectively a special-cased error state. If no idle block is provided, the component *will* throw an error if the argument is `null` or `undefined`. -- retry +#### retry Cancelled and error'd requests may be retried, retry will reuse the error, cancelled and loading @@ -452,7 +452,7 @@ import { on } from '@ember/modifier'; ``` -- Reloading states +#### Reloading states Reload will reset the request state, and so reuse the error, cancelled, and loading blocks as appropriate. @@ -519,7 +519,7 @@ import { Request } from '@warp-drive/ember'; ``` -- Autorefresh behavior +#### Autorefresh behavior Requests can be made to automatically refresh under any combination of three separate conditions by supplying a value to the `@autorefresh` arg. @@ -578,24 +578,69 @@ import { Request } from '@warp-drive/ember'; If a matching request is refreshed or reloaded by any other component, the `Request` component will react accordingly. +--- + ### PaginationState ### getPaginationState -#### \ +### \ The `` component is *layout-less*. Just like ``, it is pure declarative control flow with built-in state management utilities. -It's API mimic's ``, but +Note however, `` works *because* it understands pagination links. Pagination links +are a feature of WarpDrive/EmberData response documents. If your API does not generate pagination +links, you may want consider using a handler to process API responses for paginated queries that +generates pagination links for you. This is useful to do *even if you do not use ``* as +quite a few WarpDrive/EmberData features work best with links. + +The `` Component's API mimics ``, but expands the possibilities to afford an extremely +flexibly toolbox for managing the state of any paginated flow you might want to build. All of the same top-level +states (`idle` `loading` `content` `pending` `error` `cancelled`) are available for use, with `idle`, `loading`, +`error` and `cancelled` specifically applying to the state of the initiating request passed into the component. + +The `content` block alters `result` to a `pages` object that exposes information about all pages but otherwise +has the same semantics as being the `success` block of the primary request. + +Three new blocks are added: +- `<:prev as |request|>`, which is active while a request for a previous link is being performed +- `<:next as |request|>`, which is active while a request for a next link is being performed +- `<:default>`, which allows use of `Paginate` without any other blocks. + +> [!TIP] +> If the `<:default>` block is provided, no other named blocks will ever be utilized. E.g. the use of +> default represents a separate mode for the component in which you have signaled that request state +> management will occur elsewhere + +While the `` component is *layout-less*, named blocks do have to render *somewhere* ;) + +This means that when multiple blocks are capable of being rendered at the same time that insertion +order may matter. To that end, we guarantee that blocks render into the DOM in the following order +with zero wrapping elements. So content placed in one block will be sibling to content placed in +another block if both are rendered. + +- prev +- content +- next + +No other blocks are capable of being rendered simultaneously. It is possible for all three of these +blocks to be shown concurrently if a prev and next requests are both triggered. -**Render an infinite list** +Below, we show a number of example usages. + +#### Render an infinite list + +Here we use `` together with [VerticalCollection](https://github.com/html-next/vertical-collection/) +to provide bidirectional infinite scroll. + +**without error/loading states** ```gjs import { Paginate } from '@warp-drive/ember';