Skip to content

Commit

Permalink
move findRecord logic into the legacy-handler
Browse files Browse the repository at this point in the history
  • Loading branch information
runspired committed Mar 18, 2023
1 parent 0420507 commit e89e29f
Show file tree
Hide file tree
Showing 17 changed files with 202 additions and 201 deletions.
2 changes: 0 additions & 2 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,6 @@ module.exports = {
'@types/@ember/object/compat.d.ts',
'@types/@ember/debug/index.d.ts',
'packages/store/src/index.ts',
'packages/store/src/-private/utils/promise-record.ts',
'packages/store/src/-private/utils/is-non-empty-string.ts',
'packages/store/src/-private/utils/construct-resource.ts',
'ember-data-types/q/utils.ts',
Expand All @@ -196,7 +195,6 @@ module.exports = {
'packages/store/src/-private/caches/record-data-for.ts',
'packages/store/src/-private/utils/normalize-model-name.ts',
'packages/store/src/-private/legacy-model-support/shim-model-class.ts',
'packages/store/src/-private/network/fetch-manager.ts',
'packages/store/src/-private/store-service.ts',
'packages/store/src/-private/utils/coerce-id.ts',
'packages/store/src/-private/index.ts',
Expand Down
2 changes: 2 additions & 0 deletions packages/-ember-data/addon/-private/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import PromiseProxyMixin from '@ember/object/promise-proxy-mixin';
import ObjectProxy from '@ember/object/proxy';

import { LegacyNetworkHandler } from '@ember-data/legacy-compat';
import { FetchManager } from '@ember-data/legacy-compat/-private';
import { RequestManager } from '@ember-data/request';
import { Fetch } from '@ember-data/request/fetch';
import BaseStore from '@ember-data/store';

export class Store extends BaseStore {
constructor() {
super(...arguments);
this._fetchManager = new FetchManager(this);
this.requestManager = new RequestManager();
this.requestManager.use([LegacyNetworkHandler, Fetch]);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/legacy-compat/rollup.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default {
// You can augment this if you need to.
output: addon.output(),

external: ['@embroider/macros'],
external: ['rsvp', '@embroider/macros', '@ember/runloop', '@ember-data/store/-private'],

plugins: [
// These are the modules that users should be able to import from your
Expand Down
2 changes: 2 additions & 0 deletions packages/legacy-compat/src/-private.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export { default as SnapshotRecordArray } from './legacy-network-handler/snapshot-record-array';
export { SaveOp } from './legacy-network-handler/fetch-manager';
export { default as FetchManager } from './legacy-network-handler/fetch-manager';
10 changes: 8 additions & 2 deletions packages/legacy-compat/src/legacy-network-handler/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ import { resolve } from 'rsvp';

import { DEPRECATE_RSVP_PROMISE } from '@ember-data/private-build-infra/deprecations';

function _guard(promise, test) {
export function _bind(fn, ...args) {
return function () {
return fn.apply(undefined, args);
};
}

export function _guard(promise, test) {
let guarded = promise.finally(() => {
if (!test()) {
guarded._subscribers.length = 0;
Expand All @@ -15,7 +21,7 @@ function _guard(promise, test) {
return guarded;
}

function _objectIsAlive(object) {
export function _objectIsAlive(object) {
return !(object.isDestroyed || object.isDestroying);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
/**
* @module @ember-data/store
*/
import { assert, deprecate, warn } from '@ember/debug';
import { _backburner as emberBackburner } from '@ember/runloop';
import { DEBUG } from '@glimmer/env';
Expand All @@ -10,26 +7,28 @@ import { default as RSVP, resolve } from 'rsvp';

import { HAS_GRAPH_PACKAGE } from '@ember-data/private-build-infra';
import { DEPRECATE_RSVP_PROMISE, DEPRECATE_V1_RECORD_DATA } from '@ember-data/private-build-infra/deprecations';
import type Store from '@ember-data/store';
import { coerceId } from '@ember-data/store/-private';
import type { InstanceCache } from '@ember-data/store/-private/caches/instance-cache';
import type ShimModelClass from '@ember-data/store/-private/legacy-model-support/shim-model-class';
import type RequestCache from '@ember-data/store/-private/network/request-cache';
import type Snapshot from '@ember-data/store/-private/network/snapshot';
import type { CollectionResourceDocument, SingleResourceDocument } from '@ember-data/types/q/ember-data-json-api';
import type { FindRecordQuery, Request, SaveRecordMutation } from '@ember-data/types/q/fetch-manager';
import type {
RecordIdentifier,
StableExistingRecordIdentifier,
StableRecordIdentifier,
} from '@ember-data/types/q/identifier';
import { MinimumAdapterInterface } from '@ember-data/types/q/minimum-adapter-interface';
import { AdapterPayload, MinimumAdapterInterface } from '@ember-data/types/q/minimum-adapter-interface';
import type { MinimumSerializerInterface } from '@ember-data/types/q/minimum-serializer-interface';
import type { FindOptions } from '@ember-data/types/q/store';

import ShimModelClass from '../legacy-model-support/shim-model-class';
import type Store from '../store-service';
import coerceId from '../utils/coerce-id';
import { _bind, _guard, _objectIsAlive, guardDestroyedStore } from '../utils/common';
import { normalizeResponseHelper } from '../utils/serializer-response';
import type RequestCache from './request-cache';
import type Snapshot from './snapshot';
import { _bind, _guard, _objectIsAlive, guardDestroyedStore } from './common';
import { assertIdentifierHasId } from './identifier-has-id';
import { normalizeResponseHelper } from './serializer-response';

function payloadIsNotBlank(adapterPayload): boolean {
function payloadIsNotBlank(adapterPayload: object): boolean {
if (Array.isArray(adapterPayload)) {
return true;
} else {
Expand All @@ -39,7 +38,7 @@ function payloadIsNotBlank(adapterPayload): boolean {

type AdapterErrors = Error & { errors?: string[]; isAdapterError?: true };
type SerializerWithParseErrors = MinimumSerializerInterface & {
extractErrors?(store: Store, modelClass: ShimModelClass, error: AdapterErrors, recordId: string | null): any;
extractErrors?(store: Store, modelClass: ShimModelClass, error: AdapterErrors, recordId: string | null): unknown;
};

export const SaveOp: unique symbol = Symbol('SaveOp');
Expand All @@ -49,26 +48,22 @@ export type FetchMutationOptions = FindOptions & { [SaveOp]: 'createRecord' | 'd
interface PendingFetchItem {
identifier: StableExistingRecordIdentifier;
queryRequest: Request;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
resolver: RSVP.Deferred<any>;
options: FindOptions;
trace?: any;
trace?: unknown;
promise: Promise<StableRecordIdentifier>;
}

interface PendingSaveItem {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
resolver: RSVP.Deferred<any>;
snapshot: Snapshot;
identifier: RecordIdentifier;
options: FetchMutationOptions;
queryRequest: Request;
}

/**
* Manages the state of network requests initiated by the store
*
* @class FetchManager
* @private
*/
export default class FetchManager {
declare isDestroyed: boolean;
declare requestCache: RequestCache;
Expand Down Expand Up @@ -100,8 +95,7 @@ export default class FetchManager {
@internal
*/
scheduleSave(identifier: RecordIdentifier, options: FetchMutationOptions): Promise<null | SingleResourceDocument> {
let promiseLabel = 'DS: Model#save ' + this;
let resolver = RSVP.defer<null | SingleResourceDocument>(promiseLabel);
let resolver = RSVP.defer<null | SingleResourceDocument>(DEBUG ? `DS: Model#save ${identifier.lid}` : '');
let query: SaveRecordMutation = {
op: 'saveRecord',
recordIdentifier: identifier,
Expand All @@ -112,15 +106,16 @@ export default class FetchManager {
data: [query],
};

let snapshot = this._createSnapshot(identifier, options);
let pendingSaveItem = {
const snapshot = this._createSnapshot(identifier, options);
const pendingSaveItem: PendingSaveItem = {
snapshot: snapshot,
resolver: resolver,
identifier,
options,
queryRequest,
};
this._pendingSave.push(pendingSaveItem);
// eslint-disable-next-line @typescript-eslint/unbound-method
emberBackburner.scheduleOnce('actions', this, this._flushPendingSaves);

this.requestCache.enqueue(resolver.promise, pendingSaveItem.queryRequest);
Expand All @@ -132,7 +127,6 @@ export default class FetchManager {
This method is called at the end of the run loop, and
flushes any records passed into `scheduleSave`
@method flushPendingSave
@internal
*/
_flushPendingSaves() {
Expand Down Expand Up @@ -239,6 +233,7 @@ export default class FetchManager {
);

if (this._pendingFetch.size === 0) {
// eslint-disable-next-line @typescript-eslint/unbound-method
emberBackburner.schedule('actions', this, this.flushAllPendingFetches);
}

Expand Down Expand Up @@ -279,11 +274,60 @@ export default class FetchManager {
this._pendingFetch.clear();
}

fetchDataIfNeededForIdentifier(
identifier: StableRecordIdentifier,
options: FindOptions = {}
): Promise<StableRecordIdentifier> {
// pre-loading will change the isEmpty value
const isEmpty = _isEmpty(this._store._instanceCache, identifier);
const isLoading = _isLoading(this._store._instanceCache, identifier);

let promise: Promise<StableRecordIdentifier>;
if (isEmpty) {
assertIdentifierHasId(identifier);

promise = this.scheduleFetch(identifier, options);
} else if (isLoading) {
promise = this.getPendingFetch(identifier, options)!;
assert(`Expected to find a pending request for a record in the loading state, but found none`, promise);
} else {
promise = resolve(identifier);
}

return promise;
}

destroy() {
this.isDestroyed = true;
}
}

function _isEmpty(instanceCache: InstanceCache, identifier: StableRecordIdentifier): boolean {
const cache = DEPRECATE_V1_RECORD_DATA
? instanceCache.__instances.resourceCache.get(identifier)
: instanceCache.cache;
if (!cache) {
return true;
}
const isNew = cache.isNew(identifier);
const isDeleted = cache.isDeleted(identifier);
const isEmpty = cache.isEmpty(identifier);

return (!isNew || isDeleted) && isEmpty;
}

function _isLoading(cache: InstanceCache, identifier: StableRecordIdentifier): boolean {
const req = cache.store.getRequestStateService();
// const fulfilled = req.getLastRequestForRecord(identifier);
const isLoaded = cache.recordIsLoaded(identifier);

return (
!isLoaded &&
// fulfilled === null &&
req.getPendingRequestsForRecord(identifier).some((req) => req.type === 'query')
);
}

// this function helps resolve whether we have a pending request that we should use instead
function isSameRequest(options: FindOptions = {}, existingOptions: FindOptions = {}) {
let includedMatches = !options.include || options.include === existingOptions.include;
Expand All @@ -304,6 +348,7 @@ function _findMany(
`Cannot fetch a record without an id`,
ids.every((v) => v !== null)
);
// eslint-disable-next-line @typescript-eslint/unbound-method
assert(`Expected this adapter to implement findMany for coalescing`, adapter.findMany);
let promise = adapter.findMany(store, modelClass, ids, snapshots);
let label = `DS: Handle Adapter#findMany of '${modelName}'`;
Expand All @@ -312,11 +357,13 @@ function _findMany(
throw new Error('adapter.findMany returned undefined, this was very likely a mistake');
}

promise = guardDestroyedStore(promise, store, label);
promise = guardDestroyedStore(promise, store, label) as Promise<AdapterPayload>;

return promise.then((adapterPayload) => {
assert(
`You made a 'findMany' request for '${modelName}' records with ids '[${ids}]', but the adapter's response did not have any data`,
`You made a 'findMany' request for '${modelName}' records with ids '[${ids.join(
','
)}]', but the adapter's response did not have any data`,
!!payloadIsNotBlank(adapterPayload)
);
let serializer = store.serializerFor(modelName);
Expand All @@ -334,7 +381,9 @@ function rejectFetchedItems(fetchMap: Map<Snapshot, PendingFetchItem>, snapshots
pair.resolver.reject(
error ||
new Error(
`Expected: '<${snapshot.modelName}:${snapshot.id}>' to be present in the adapter provided payload, but it was not found.`
`Expected: '<${
snapshot.modelName
}:${snapshot.id!}>' to be present in the adapter provided payload, but it was not found.`
)
);
}
Expand Down Expand Up @@ -436,7 +485,9 @@ function _fetchRecord(store: Store, fetchItem: PendingFetchItem) {
}),
store,
label
).then((adapterPayload) => {
) as Promise<AdapterPayload>;

promise = promise.then((adapterPayload) => {
assert(
`You made a 'findRecord' request for a '${modelName}' with id '${id}', but the adapter's response did not have any data`,
!!payloadIsNotBlank(adapterPayload)
Expand All @@ -461,7 +512,7 @@ function _fetchRecord(store: Store, fetchItem: PendingFetchItem) {
);

return payload;
});
}) as Promise<AdapterPayload>;

fetchItem.resolver.resolve(promise);
}
Expand Down Expand Up @@ -495,7 +546,7 @@ function _flushPendingFetchForType(store: Store, pendingFetchItems: PendingFetch

if (shouldCoalesce) {
let snapshots = new Array<Snapshot>(totalItems);
let fetchMap = new Map();
let fetchMap = new Map<Snapshot, PendingFetchItem>();
for (let i = 0; i < totalItems; i++) {
let fetchItem = pendingFetchItems[i];
snapshots[i] = store._instanceCache.createSnapshot(fetchItem.identifier, fetchItem.options);
Expand Down Expand Up @@ -534,16 +585,27 @@ function _flushPendingSave(store: Store, pending: PendingSaveItem) {
typeof adapter[operation] === 'function'
);

let promise = resolve().then(() => adapter[operation](store, modelClass, snapshot));
let promise: Promise<AdapterPayload> = resolve().then(() => adapter[operation](store, modelClass, snapshot));
let serializer: SerializerWithParseErrors | null = store.serializerFor(modelName);
let label = `DS: Extract and notify about ${operation} completion of ${identifier}`;

assert(
`Your adapter's '${operation}' method must return a value, but it returned 'undefined'`,
promise !== undefined
);

promise = _guard(guardDestroyedStore(promise, store, label), _bind(_objectIsAlive, record)).then((adapterPayload) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
promise = _guard(
guardDestroyedStore(
promise,
store,
DEBUG ? `DS: Extract and notify about ${operation} completion of ${identifier.lid}` : ''
),
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
_bind(_objectIsAlive, record)
) as Promise<AdapterPayload>;

promise = promise.then((adapterPayload) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
if (!_objectIsAlive(record)) {
if (DEPRECATE_RSVP_PROMISE) {
deprecate(
Expand All @@ -565,6 +627,7 @@ function _flushPendingSave(store: Store, pending: PendingSaveItem) {
if (adapterPayload) {
return normalizeResponseHelper(serializer, store, modelClass, adapterPayload, snapshot.id, operation);
}
});
}) as Promise<AdapterPayload>;

resolver.resolve(promise);
}
Loading

0 comments on commit e89e29f

Please sign in to comment.