diff --git a/app/routes/application.js b/app/routes/application.js index 654a0c4..d4fac6a 100644 --- a/app/routes/application.js +++ b/app/routes/application.js @@ -4,172 +4,25 @@ export default class ApplicationRoute extends Route { model() { return [ { - id: 'generating-files', + id: 'adapters', subsections: [ { - id: 'generating-component', - classicFiles: ['classic.shell'], - octaneFiles: ['octane.shell'], + id: 'general', }, { - id: 'file-structure', - classicFiles: ['classic.text'], - octaneFiles: ['octane.text'], - }, - ], - }, - { - id: 'component-templates', - subsections: [ - { - id: 'angle-brackets', - classicFiles: ['classic.hbs'], - octaneFiles: ['octane.hbs'], - }, - { - id: 'inline-vs-block', - classicFiles: ['classic.hbs'], - octaneFiles: ['octane.hbs'], - }, - { - id: 'angle-brackets-nested', - classicFiles: ['classic.hbs'], - octaneFiles: ['octane.hbs'], - }, - { - id: 'template-named', - classicFiles: ['classic.hbs'], - octaneFiles: ['octane.hbs'], - }, - { - id: 'template-this', - classicFiles: ['classic.hbs'], - octaneFiles: ['octane.hbs'], - }, - { - id: 'template-arguments-named', - classicFiles: ['classic.hbs'], - octaneFiles: ['octane.hbs'], - }, - { - id: 'template-arguments-this', - classicFiles: ['classic.hbs'], - octaneFiles: ['octane.hbs'], - }, - { - id: 'tag-name', - classicFiles: ['classic.hbs', 'classic.html'], - octaneFiles: ['octane.hbs', 'octane.html'], - }, - { - id: 'element-id', - classicFiles: ['classic.js', 'classic.hbs'], - octaneFiles: ['octane.js', 'octane.hbs'], - }, - ], - }, - { - id: 'component-properties', - subsections: [ - { - id: 'js-boilerplate', - classicFiles: ['classic.js'], - octaneFiles: ['octane.js'], - }, - { - id: 'js-properties', - classicFiles: ['classic.js'], - octaneFiles: ['octane.js'], - }, - { - id: 'ddau', - classicFiles: [ - 'classic-parent.js', - 'classic-parent.hbs', - 'classic-child.js', - 'classic-child.hbs', - ], - octaneFiles: [ - 'octane-parent.js', - 'octane-parent.hbs', - 'octane-child.hbs', - ], + id: 'host-and-namespace', + classicFiles: ['old.js'], + octaneFiles: ['new.js'], }, { - id: 'args', - classicFiles: ['classic.js'], - octaneFiles: ['octane.js'], + id: 'path-for-type', + classicFiles: ['old.js'], + octaneFiles: ['new.js', 'utils.js'], }, { - id: 'get-and-set', - classicFiles: ['classic.js'], - octaneFiles: ['octane.js'], - }, - { - id: 'tracked-vs-cp', - classicFiles: ['classic.js'], - octaneFiles: ['octane.js'], - }, - { - id: 'computed-decorator', - classicFiles: ['classic.js'], - octaneFiles: ['octane.js'], - }, - ], - }, - { - id: 'actions', - subsections: [ - { - id: 'actions', - classicFiles: ['classic.js', 'classic.hbs'], - octaneFiles: ['octane.js', 'octane.hbs'], - }, - { - id: 'template-arguments-default', - classicFiles: ['classic.hbs', 'classic.js'], - octaneFiles: ['octane.hbs', 'octane.js'], - }, - { - id: 'mixins', - classicFiles: ['classic.js'], - octaneDescriptionKey: 'actions.mixins.octaneDescription', - }, - ], - }, - { - id: 'component-lifecycle', - subsections: [ - { - id: 'constructors', - classicFiles: ['classic.js'], - octaneFiles: ['octane.js'], - }, - { - id: 'will-destroy', - classicFiles: ['classic.js'], - octaneFiles: ['octane.js'], - }, - { - id: 'render-modifiers', - classicDescriptionKey: - 'component-lifecycle.render-modifiers.classicDescription', - octaneFiles: ['octane.shell'], - }, - { - id: 'did-insert', - classicFiles: ['classic.hbs', 'classic.js'], - octaneFiles: ['octane.hbs', 'octane.js'], - }, - ], - }, - { - id: 'routes', - subsections: [ - { - id: 'model-access', - classicFiles: ['classic.hbs'], - octaneFiles: ['octane.hbs'], + id: 'cache-lifetime', + classicFiles: ['old.js'], + octaneFiles: ['new.js'], }, ], }, diff --git a/package-lock.json b/package-lock.json index 1803fd1..550c3ed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ember-data-request-service-cheat-sheet", - "version": "1.0.0", + "version": "0.0.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "ember-data-request-service-cheat-sheet", - "version": "1.0.0", + "version": "0.0.1", "license": "MIT", "devDependencies": { "@babel/eslint-parser": "^7.18.2", diff --git a/snippets/adapters/cache-lifetime/new.js b/snippets/adapters/cache-lifetime/new.js new file mode 100644 index 0000000..06cf526 --- /dev/null +++ b/snippets/adapters/cache-lifetime/new.js @@ -0,0 +1,43 @@ +import { LifetimesService } from '@ember-data/request-utils'; +import DataStore from '@ember-data/store'; + +export default class Store extends DataStore { + constructor(args) { + super(args); + // This is default configuration that would be set automatically be Ember Data + // this.lifetimes = new LifetimesService(this, { + // apiCacheSoftExpires: 30_000, + // apiCacheHardExpires: 60_000 + // }); + this.lifetimes = { + isHardExpired(identifier) { + const cached = this.store.cache.peekRequest(identifier); + const twentyMinutesInMs = 20 * 60 * 1000; + + function isStale(headers, expirationTime) { + const date = headers.get('date'); + + if (!date) { + return true; + } + + const time = new Date(date).getTime(); + const now = Date.now(); + const deadline = time + expirationTime; + + const result = now > deadline; + + return result; + } + + return !cached || !cached.response || isStale(cached.response.headers, twentyMinutesInMs); + }, + + isSoftExpired(identifier) { + const { downlink, effectiveType } = navigator.connection; + + return downlink > 0 && effectiveType === '4g'; + } + } + } +} diff --git a/snippets/adapters/cache-lifetime/old.js b/snippets/adapters/cache-lifetime/old.js new file mode 100644 index 0000000..58bea6d --- /dev/null +++ b/snippets/adapters/cache-lifetime/old.js @@ -0,0 +1,41 @@ +import JSONAPIAdapter from '@ember-data/adapter/json-api'; + +export default class ApplicationAdapter extends JSONAPIAdapter { + shouldBackgroundReloadAll(store, snapshotArray) { + let { downlink, effectiveType } = navigator.connection; + + return downlink > 0 && effectiveType === '4g'; + } + + shouldBackgroundReloadRecord(store, snapshot) { + let { downlink, effectiveType } = navigator.connection; + + return downlink > 0 && effectiveType === '4g'; + } + + shouldReloadRecord(store, ticketSnapshot) { + let lastAccessedAt = ticketSnapshot.attr('lastAccessedAt'); + let timeDiff = moment().diff(lastAccessedAt, 'minutes'); + + if (timeDiff > 20) { + return true; + } else { + return false; + } + } + + shouldReloadAll(store, snapshotArray) { + let snapshots = snapshotArray.snapshots(); + + return snapshots.any((ticketSnapshot) => { + let lastAccessedAt = ticketSnapshot.attr('lastAccessedAt'); + let timeDiff = moment().diff(lastAccessedAt, 'minutes'); + + if (timeDiff > 20) { + return true; + } else { + return false; + } + }); + } +} diff --git a/snippets/adapters/host-and-namespace/new.js b/snippets/adapters/host-and-namespace/new.js new file mode 100644 index 0000000..08a044f --- /dev/null +++ b/snippets/adapters/host-and-namespace/new.js @@ -0,0 +1,7 @@ +import { setBuildURLConfig } from '@ember-data/request-utils'; +import config from 'my-app/config/environment'; + +setBuildURLConfig({ + host: config.api.host, + namespace: config.api.namespace, +}); diff --git a/snippets/adapters/host-and-namespace/old.js b/snippets/adapters/host-and-namespace/old.js new file mode 100644 index 0000000..e972908 --- /dev/null +++ b/snippets/adapters/host-and-namespace/old.js @@ -0,0 +1,7 @@ +import JSONAPIAdapter from '@ember-data/adapter/json-api'; +import config from 'my-app/config/environment'; + +export default class ApplicationAdapter extends JSONAPIAdapter { + host = config.api.host; + namespace = config.api.namespace; +} diff --git a/snippets/adapters/path-for-type/new.js b/snippets/adapters/path-for-type/new.js new file mode 100644 index 0000000..7ac118e --- /dev/null +++ b/snippets/adapters/path-for-type/new.js @@ -0,0 +1,12 @@ +import { findRecord } from '@ember-data/json-api/request'; +// import { findRecord } from '@ember-data/rest/request'; +// import { findRecord } from '@ember-data/active-record/request'; + +const dynamicPathFor = (identifierType) => { + const collectionName = camelize(pluralize(identifierType)) + return `collections/${collectionName}/records`; +}; + +const options = findRecord('post', '1', { + resourcePath: dynamicPathFor('post'), +}) diff --git a/snippets/adapters/path-for-type/old.js b/snippets/adapters/path-for-type/old.js new file mode 100644 index 0000000..fdfcde7 --- /dev/null +++ b/snippets/adapters/path-for-type/old.js @@ -0,0 +1,6 @@ +export default class ApplicationAdapter extends JSONAPIAdapter { + pathForType(type) { + const collectionName = pluralize(camelize(type)); + return `collections/${collectionName}/records`; + } +} diff --git a/snippets/adapters/path-for-type/utils.js b/snippets/adapters/path-for-type/utils.js new file mode 100644 index 0000000..9616a23 --- /dev/null +++ b/snippets/adapters/path-for-type/utils.js @@ -0,0 +1,60 @@ +import { camelize } from '@ember/string'; +import { pluralize } from 'ember-inflector'; +import { recordIdentifierFor } from '@ember-data/store'; + +import { + findRecord as restFindRecord, + query as restQuery, + createRecord as restCreateRecord, + updateRecord as restUpdateRecord, + deleteRecord as restDeleteRecord, +} from '@ember-data/rest/request'; + +const _customResourcePath = (identifierType) => { + return `collections/${camelize(pluralize(identifierType))}/records`; +}; + +const findRecord = (identifierType, id, options) => { + options = { + ...options, + resourcePath: _customResourcePath(identifierType), + }; + return restFindRecord(identifierType, id, options); +}; + +const query = (identifierType, query, options) => { + options = { + ...options, + resourcePath: _customResourcePath(identifierType), + }; + return restQuery(identifierType, query, options); +}; + +const createRecord = (record, options) => { + const identifier = recordIdentifierFor(record); + options = { + ...options, + resourcePath: _customResourcePath(identifier.type), + }; + return restCreateRecord(record, options); +}; + +const updateRecord = (record, options) => { + const identifier = recordIdentifierFor(record); + options = { + ...options, + resourcePath: _customResourcePath(identifier.type), + }; + return restUpdateRecord(record, options); +}; + +const deleteRecord = (record, options) => { + const identifier = recordIdentifierFor(record); + options = { + ...options, + resourcePath: _customResourcePath(identifier.type), + }; + return restDeleteRecord(record, options); +}; + +export { findRecord, query, createRecord, updateRecord, deleteRecord }; diff --git a/translations/adapters/cache-lifetime/en-us.yaml b/translations/adapters/cache-lifetime/en-us.yaml new file mode 100644 index 0000000..944494e --- /dev/null +++ b/translations/adapters/cache-lifetime/en-us.yaml @@ -0,0 +1,4 @@ +title: Cache lifetime +description: > + In the past, cache lifetimes for single resources were controlled by either supplying the ''reload'' and ''backgroundReload'' options or by the Adapter's hooks for ''shouldReloadRecord'', ''shouldReloadAll'', ''shouldBackgroundReloadRecord'' and ''shouldBackgroundReloadAll''. + This behavior will now be controlled by the combination of either supplying ''cacheOptions'' on the associated ''RequestInfo'' or by supplying a ''lifetimes'' service to the ''Store''. diff --git a/translations/adapters/en-us.yaml b/translations/adapters/en-us.yaml new file mode 100644 index 0000000..d08d5fc --- /dev/null +++ b/translations/adapters/en-us.yaml @@ -0,0 +1 @@ +title: Adapters diff --git a/translations/adapters/general/en-us.yaml b/translations/adapters/general/en-us.yaml new file mode 100644 index 0000000..55a9bbe --- /dev/null +++ b/translations/adapters/general/en-us.yaml @@ -0,0 +1,9 @@ +title: Adapters in general +description: | + In order to provide migration support for Adapters and Serializers, a ''LegacyNetworkHandler'' is provided. This handler takes a request and converts it into the older form, calling the appropriate Adapter and Serializer methods. If no adapter exists for the type (including no application adapter), this handler calls ''next''. In this manner an app can incrementally migrate request-handling to this new paradigm on a per-type basis as desired. +
+ The package ''ember-data'' automatically configures this handler. If not using the ''ember-data'' package, this configuration needs to be done explicitly. +
+ We intend to support this handler through at least the 5.x series -- not deprecating its usage before 6.0. +
+ Similarly, the methods ''adapterFor'' and ''serializerFor'' will not be deprecated until at least 6.0; however, it should no longer be assumed that an application has an adapter or serializer at all. diff --git a/translations/adapters/host-and-namespace/en-us.yaml b/translations/adapters/host-and-namespace/en-us.yaml new file mode 100644 index 0000000..e89daf3 --- /dev/null +++ b/translations/adapters/host-and-namespace/en-us.yaml @@ -0,0 +1,5 @@ +title: Default Host and Namespace +description: > + To set the default ''host'' and ''namespace'' for requests, you can use the ''setBuildURLConfig'' method of ''@ember-data/request-utils'' package. +
+ This is a module-level function, so you can do it anywhere in your application, i.e. in your ''services/request-manager.js'' or ''services/store.js'' file. diff --git a/translations/adapters/path-for-type/en-us.yaml b/translations/adapters/path-for-type/en-us.yaml new file mode 100644 index 0000000..f7060e0 --- /dev/null +++ b/translations/adapters/path-for-type/en-us.yaml @@ -0,0 +1,5 @@ +title: Use request builders instead of pathForType +description: > + To modify a URL for a request you can use the ''resourcePath'' option for every request builder. Default configuration for ''JSON:API'', ''REST'', and ''ActiveRecord'' builders will be provided by EmberData. +
+ We recommend creating your own utility file with request builders that suite your backend's needs. diff --git a/translations/en-us.yaml b/translations/en-us.yaml index b35e43f..a281f89 100644 --- a/translations/en-us.yaml +++ b/translations/en-us.yaml @@ -1,6 +1,6 @@ layout: application: - logo-alt-text: 'Ember Octane: the latest edition from Ember.js' + logo-alt-text: 'EmberData Request Service Cheat Sheet' title: EmberData Request Service Cheat Sheet description-1: > This guide is a cheat sheet for using 'EmberData's Request Service'.