Skip to content

Commit

Permalink
all the, nice things
Browse files Browse the repository at this point in the history
  • Loading branch information
runspired committed Mar 15, 2023
1 parent 9c39666 commit 2e49803
Show file tree
Hide file tree
Showing 11 changed files with 230 additions and 30 deletions.
6 changes: 3 additions & 3 deletions ember-data-types/cache/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,13 @@ export type ResourceDocument =

export interface StructuredDataDocument<T> {
request?: RequestInfo;
response?: ResponseInfo;
response?: ResponseInfo | Response | null;
content: T;
}
export interface StructuredErrorDocument extends Error {
request?: RequestInfo;
response?: ResponseInfo;
response?: ResponseInfo | Response | null;
error: string | object;
content?: ResourceErrorDocument;
content?: unknown;
}
export type StructuredDocument<T> = StructuredDataDocument<T> | StructuredErrorDocument;
2 changes: 1 addition & 1 deletion ember-data-types/q/ember-data-json-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ interface Document {
meta?: Dict<JSONValue>;
included?: ExistingResourceObject[];
jsonapi?: Dict<JSONValue>;
links?: Dict<string | JSONValue>;
links?: Links | PaginationLinks;
errors?: JSONValue[];
}

Expand Down
14 changes: 7 additions & 7 deletions packages/json-api/src/-private/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,22 @@ import type { ImplicitRelationship } from '@ember-data/graph/-private/graph/inde
import type BelongsToRelationship from '@ember-data/graph/-private/relationships/state/belongs-to';
import type ManyRelationship from '@ember-data/graph/-private/relationships/state/has-many';
import { LOG_MUTATIONS, LOG_OPERATIONS } from '@ember-data/private-build-infra/debugging';
import { StructuredDataDocument } from '@ember-data/request/-private/types';
import { IdentifierCache } from '@ember-data/store/-private/caches/identifier-cache';
import { ResourceBlob } from '@ember-data/types/cache/aliases';
import { Change } from '@ember-data/types/cache/change';
import {
import type { IdentifierCache } from '@ember-data/store/-private/caches/identifier-cache';
import type { ResourceBlob } from '@ember-data/types/cache/aliases';
import type { Change } from '@ember-data/types/cache/change';
import type {
CollectionResourceDataDocument,
ResourceDocument,
ResourceErrorDocument,
ResourceMetaDocument,
SingleResourceDataDocument,
StructuredDataDocument,
StructuredDocument,
} from '@ember-data/types/cache/document';
import { StableDocumentIdentifier } from '@ember-data/types/cache/identifier';
import type { StableDocumentIdentifier } from '@ember-data/types/cache/identifier';
import type { Cache, ChangedAttributesHash, MergeOperation } from '@ember-data/types/q/cache';
import type { CacheStoreWrapper, V2CacheStoreWrapper } from '@ember-data/types/q/cache-store-wrapper';
import {
import type {
CollectionResourceDocument,
CollectionResourceRelationship,
ExistingResourceObject,
Expand Down
2 changes: 1 addition & 1 deletion packages/model/src/-private/many-array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ export default class RelatedCollection extends RecordArray {
*/
createRecord(hash: CreateRecordProperties): RecordInstance {
const { store } = this;

assert(`Expected modelName to be set`, this.modelName);
const record = store.createRecord(this.modelName, hash);
this.push(record);

Expand Down
32 changes: 30 additions & 2 deletions packages/request/src/-private/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -400,11 +400,39 @@ import { executeNextHandler } from './utils';
*/
export class RequestManager {
#handlers: Handler[] = [];
declare _hasCacheHandler: boolean;

constructor(options?: GenericCreateArgs) {
Object.assign(this, options);
}

/**
* Register a handler to use for primary cache intercept.
*
* Only one such handler may exist. If using the same
* RequestManager as the Store instance the Store
* registers itself as a Cache handler.
*
* @method useCache
* @public
* @param {Handler[]} cacheHandler
* @returns {void}
*/
useCache(cacheHandler: Handler): void {
if (macroCondition(isDevelopingApp())) {
if (this._hasCacheHandler) {
throw new Error(`\`RequestManager.useCache(<handler>)\` May only be invoked once.`);
}
if (Object.isFrozen(this.#handlers)) {
throw new Error(
`\`RequestManager.useCache(<handler>)\` May only be invoked prior to any request having been made.`
);
}
this._hasCacheHandler = true;
}
this.#handlers.unshift(cacheHandler);
}

/**
* Register handler(s) to use when a request is issued.
*
Expand All @@ -414,10 +442,10 @@ export class RequestManager {
*
* @method use
* @public
* @param {Hanlder[]} newHandlers
* @param {Handler[]} newHandlers
* @returns {void}
*/
use(newHandlers: Handler[]) {
use(newHandlers: Handler[]): void {
const handlers = this.#handlers;
if (macroCondition(isDevelopingApp())) {
if (Object.isFrozen(handlers)) {
Expand Down
5 changes: 3 additions & 2 deletions packages/request/src/-private/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,15 @@ export interface GodContext {
}

export interface StructuredDataDocument<T> {
request: RequestInfo;
request: ImmutableRequestInfo;
response: Response | ResponseInfo | null;
content: T;
}
export interface StructuredErrorDocument extends Error {
request: RequestInfo;
request: ImmutableRequestInfo;
response: Response | ResponseInfo | null;
error: string | object;
content?: unknown;
}

export type Deferred<T> = {
Expand Down
2 changes: 1 addition & 1 deletion packages/request/src/-private/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import type {
StructuredErrorDocument,
} from './types';

const STRUCTURED = Symbol('DOC');
export const STRUCTURED = Symbol('DOC');

export function curryFuture<T>(owner: ContextOwner, inbound: Future<T>, outbound: DeferredFuture<T>): Future<T> {
owner.setStream(inbound.getStream());
Expand Down
116 changes: 116 additions & 0 deletions packages/store/src/-private/cache-handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import type {
Future,
Handler,
ImmutableRequestInfo,
NextFn,
RequestContext,
StructuredErrorDocument,
} from '@ember-data/request/-private/types';
import type Store from '@ember-data/store';
import { CollectionResourceDataDocument, ResourceDataDocument } from '@ember-data/types/cache/document';
import type { StableRecordIdentifier } from '@ember-data/types/q/identifier';

export type HTTPMethod = 'GET' | 'OPTIONS' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';

export interface LifetimesService {
isHardExpired(key: string, url: string, method: HTTPMethod): boolean;
isSoftExpired(key: string, url: string, method: HTTPMethod): boolean;
}

export interface StoreRequestInfo extends ImmutableRequestInfo {
cacheOptions?: { key?: string; reload?: boolean; backgroundReload?: boolean };
store: Store;

op?:
| 'findRecord'
| 'updateRecord'
| 'query'
| 'queryRecord'
| 'findBelongsTo'
| 'findHasMany'
| 'createRecord'
| 'deleteRecord';
records?: StableRecordIdentifier[];
}

export interface StoreRequestContext extends RequestContext {
request: StoreRequestInfo;
}

function getHydratedContent<T>(store: Store, request: ImmutableRequestInfo, document: ResourceDataDocument): T {
if (Array.isArray(document.data)) {
const { lid } = document;
const { recordArrayManager } = store;
if (!lid) {
return recordArrayManager.createArray({
identifiers: document.data,
doc: document as CollectionResourceDataDocument,
query: request,
}) as T;
}
let managed = recordArrayManager._keyedArrays.get(lid);
if (!managed) {
managed = recordArrayManager.createArray({
identifiers: document.data,
doc: document as CollectionResourceDataDocument,
});
recordArrayManager._keyedArrays.set(lid, managed);
} else {
recordArrayManager.populateManagedArray(managed, document.data, document as CollectionResourceDataDocument);
}
return managed as T;
} else {
return (document.data ? store.peekRecord(document.data) : null) as T;
}
}

export const CacheHandler: Handler = {
request<T>(context: StoreRequestContext, next: NextFn<T>): Promise<T> | Future<T> {
const { store } = context.request;
const { cacheOptions, url, method } = context.request;
const lid = cacheOptions?.key || (method === 'GET' && url) ? url : false;
const peeked = lid ? store.cache.peekRequest({ lid }) : null;

// determine if we should skip cache
const shouldFetch =
cacheOptions?.reload || !peeked || (lid && url && method)
? store.lifetimes?.isHardExpired(lid as string, url as string, method as HTTPMethod)
: false;

// if we have not skipped cache, determine if we should update behind the scenes
const shouldBackgroundFetch =
!shouldFetch &&
(cacheOptions?.backgroundReload || (lid && url && method)
? store.lifetimes?.isSoftExpired(lid as string, url as string, method as HTTPMethod)
: false);

let promise: Promise<T>;
if (shouldFetch || shouldBackgroundFetch) {
promise = next(context.request).then(
(document) => {
const response = store.cache.put(document);

if (shouldFetch) {
return getHydratedContent(store, context.request, response as ResourceDataDocument);
}
},
(error: StructuredErrorDocument) => {
store.cache.put(error);
// TODO @runspired this is probably not the right thing to throw so make sure we add a test
if (!shouldBackgroundFetch) {
throw error;
}
}
) as Promise<T>;
}

if (shouldFetch) {
return promise!;
}

if ('error' in peeked!) {
throw peeked.error;
}
return Promise.resolve(getHydratedContent<T>(store, context.request, peeked!.content as ResourceDataDocument));
},
};
6 changes: 4 additions & 2 deletions packages/store/src/-private/managers/record-array-manager.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/**
@module @ember-data/store
*/
import { ImmutableRequestInfo } from '@ember-data/request/-private/types';
import { addTransactionCB } from '@ember-data/tracking/-private';
import type { CollectionResourceDocument } from '@ember-data/types/q/ember-data-json-api';
import type { StableRecordIdentifier } from '@ember-data/types/q/identifier';
Expand Down Expand Up @@ -87,6 +88,7 @@ class RecordArrayManager {
declare _identifiers: Map<StableRecordIdentifier, Set<Collection>>;
declare _staged: Map<string, ChangeSet>;
declare _subscription: UnsubscribeToken;
declare _keyedArrays: Map<string, Collection>;

constructor(options: { store: Store }) {
this.store = options.store;
Expand Down Expand Up @@ -160,8 +162,8 @@ class RecordArrayManager {
}

createArray(config: {
type: string;
query?: Dict<unknown>;
type?: string;
query?: ImmutableRequestInfo | Dict<unknown>;
identifiers?: StableRecordIdentifier[];
doc?: CollectionResourceDocument;
}): Collection {
Expand Down
11 changes: 7 additions & 4 deletions packages/store/src/-private/record-arrays/identifier-array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
DEPRECATE_PROMISE_PROXIES,
DEPRECATE_SNAPSHOT_MODEL_CLASS_ACCESS,
} from '@ember-data/private-build-infra/deprecations';
import { ImmutableRequestInfo } from '@ember-data/request/-private/types';
import { addToTransaction, subscribe } from '@ember-data/tracking/-private';
import { Links, PaginationLinks } from '@ember-data/types/q/ember-data-json-api';
import type { StableRecordIdentifier } from '@ember-data/types/q/identifier';
Expand Down Expand Up @@ -114,7 +115,7 @@ declare global {

export type IdentifierArrayCreateOptions = {
identifiers: StableRecordIdentifier[];
type: string;
type?: string;
store: Store;
allowMutation: boolean;
manager: RecordArrayManager;
Expand Down Expand Up @@ -220,7 +221,7 @@ class IdentifierArray {
@deprecated
@type {subclass of Model}
*/
declare modelName: string;
declare modelName?: string;
/**
The store that created this record array.
Expand Down Expand Up @@ -529,6 +530,7 @@ class IdentifierArray {
is finished.
*/
_update(): PromiseArray<RecordInstance, IdentifierArray> | Promise<IdentifierArray> {
assert(`_update cannot be used with this array`, this.modelName);
return this.store.findAll(this.modelName, { reload: true });
}

Expand Down Expand Up @@ -587,11 +589,11 @@ if (DEPRECATE_SNAPSHOT_MODEL_CLASS_ACCESS) {
}

export type CollectionCreateOptions = IdentifierArrayCreateOptions & {
query: Dict<unknown> | null;
query: ImmutableRequestInfo | Dict<unknown> | null;
isLoaded: boolean;
};
export class Collection extends IdentifierArray {
query: Dict<unknown> | null = null;
query: ImmutableRequestInfo | Dict<unknown> | null = null;

constructor(options: CollectionCreateOptions) {
super(options as IdentifierArrayCreateOptions);
Expand All @@ -603,6 +605,7 @@ export class Collection extends IdentifierArray {
const { store, query } = this;

// TODO save options from initial request?
assert(`_update cannot be used with this array`, this.modelName);
const promise = store.query(this.modelName, query, { _recordArray: this });

if (DEPRECATE_PROMISE_PROXIES) {
Expand Down
Loading

0 comments on commit 2e49803

Please sign in to comment.