Skip to content

Commit

Permalink
feat: cache-put
Browse files Browse the repository at this point in the history
  • Loading branch information
runspired committed Mar 12, 2023
1 parent 9da3d1b commit 42f2113
Show file tree
Hide file tree
Showing 10 changed files with 229 additions and 40 deletions.
43 changes: 43 additions & 0 deletions ember-data-types/cache/document.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { ImmutableRequestInfo, ResponseInfo as ImmutableResponseInfo } from '@ember-data/request/-private/types';
import { Links, Meta, PaginationLinks } from '@ember-data/types/q/ember-data-json-api';
import { StableExistingRecordIdentifier } from '@ember-data/types/q/identifier';

export type RequestInfo = ImmutableRequestInfo;
export type ResponseInfo = ImmutableResponseInfo;

export interface ResourceMetaDocument {
// the url or cache-key associated with the structured document
lid?: string;
meta: Meta;
links?: Links | PaginationLinks;
}

export interface ResourceDataDocument {
// the url or cache-key associated with the structured document
lid?: string;
links?: Links | PaginationLinks;
meta?: Meta;
data: StableExistingRecordIdentifier | StableExistingRecordIdentifier[] | null;
}

export interface ResourceErrorDocument {
// the url or cache-key associated with the structured document
lid?: string;
links?: Links | PaginationLinks;
meta?: Meta;
error: string | object;
}

export type ResourceDocument = ResourceMetaDocument | ResourceDataDocument | ResourceErrorDocument;

export interface StructuredDataDocument<T> {
request?: RequestInfo;
response?: ResponseInfo;
data: T;
}
export interface StructuredErrorDocument extends Error {
request?: RequestInfo;
response?: ResponseInfo;
error: string | object;
}
export type StructuredDocument<T> = StructuredDataDocument<T> | StructuredErrorDocument;
26 changes: 24 additions & 2 deletions ember-data-types/q/cache.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { LocalRelationshipOperation } from '@ember-data/graph/-private/graph/-operations';

import { ResourceDocument, StructuredDocument } from '../cache/document';
import type { CollectionResourceRelationship, SingleResourceRelationship } from './ember-data-json-api';
import type { RecordIdentifier, StableRecordIdentifier } from './identifier';
import type { JsonApiResource, JsonApiValidationError } from './record-data-json-api';
Expand Down Expand Up @@ -85,8 +86,29 @@ export interface Cache {
*/
version: '2';

// Cache
// =====
/**
* Cache the response to a request
*
* Unlike `store.push` which has UPSERT
* semantics, `put` has `replace` semantics similar to
* the `http` method `PUT`
*
* the individually cacheable resource data it may contain
* should upsert, but the document data surrounding it should
* fully replace any existing information
*
* Note that in order to support inserting arbitrary data
* to the cache that did not originate from a request `put`
* should expect to sometimes encounter a document with only
* a `data` member and therefor must not assume the existence
* of `request` and `response` on the document.
*
* @method put
* @param {StructuredDocument} doc
* @returns {ResourceDocument}
* @public
*/
put(doc: StructuredDocument<unknown>): ResourceDocument;

/**
* Update the "remote" or "canonical" (persisted) state of the Cache
Expand Down
49 changes: 48 additions & 1 deletion packages/json-api/src/-private/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,17 @@ 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 { IdentifierCache } from '@ember-data/store/-private/caches/identifier-cache';
import { ResourceDocument, StructuredDocument } from '@ember-data/types/cache/document';
import type { Cache, ChangedAttributesHash, MergeOperation } from '@ember-data/types/q/cache';
import type { CacheStoreWrapper, V2CacheStoreWrapper } from '@ember-data/types/q/cache-store-wrapper';
import type {
CollectionResourceRelationship,
ExistingResourceObject,
JsonApiDocument,
SingleResourceRelationship,
} from '@ember-data/types/q/ember-data-json-api';
import type { StableRecordIdentifier } from '@ember-data/types/q/identifier';
import type { StableExistingRecordIdentifier, StableRecordIdentifier } from '@ember-data/types/q/identifier';
import type { AttributesHash, JsonApiResource, JsonApiValidationError } from '@ember-data/types/q/record-data-json-api';
import type { AttributeSchema, RelationshipSchema } from '@ember-data/types/q/record-data-schemas';
import type { Dict } from '@ember-data/types/q/utils';
Expand Down Expand Up @@ -86,6 +90,43 @@ export default class SingletonCache implements Cache {
this.__storeWrapper = storeWrapper;
}

put<T extends JsonApiDocument>(doc: StructuredDocument<T>): ResourceDocument {
assert(`Cannot currently cache an ErrorDocument`, !('error' in doc));
const jsonApiDoc = doc.data;
let included = jsonApiDoc.included;
let i: number, length: number;
const { identifierCache } = this.__storeWrapper;

if (included) {
for (i = 0, length = included.length; i < length; i++) {
putOne(this, identifierCache, included[i]);
}
}

if (Array.isArray(jsonApiDoc.data)) {
length = jsonApiDoc.data.length;
let identifiers: StableExistingRecordIdentifier[] = [];

for (i = 0; i < length; i++) {
identifiers.push(putOne(this, identifierCache, jsonApiDoc.data[i]));
}
return { data: identifiers };
}

if (jsonApiDoc.data === null) {
return { data: null };
}

assert(
`Expected an object in the 'data' property in a call to 'push', but was ${typeof jsonApiDoc.data}`,
typeof jsonApiDoc.data === 'object'
);

let identifier: StableExistingRecordIdentifier = identifierCache.getOrCreateRecordIdentifier(jsonApiDoc.data);
this.upsert(identifier, jsonApiDoc.data, false);
return { data: identifier };
}

/**
* Private method used to populate an entry for the identifier
*
Expand Down Expand Up @@ -746,6 +787,12 @@ function patchLocalAttributes(cached: CachedResource): boolean {
return hasAppliedPatch;
}

function putOne(cache: SingletonCache, identifiers: IdentifierCache, resource: ExistingResourceObject) {
let identifier: StableExistingRecordIdentifier = identifiers.getOrCreateRecordIdentifier(resource);
cache.upsert(identifier, resource, false);
return identifier;
}

/*
Iterates over the set of internal models reachable from `this` across exactly one
relationship.
Expand Down
5 changes: 5 additions & 0 deletions packages/store/src/-private/caches/identifier-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type {
RecordIdentifier,
ResetMethod,
ResourceData,
StableExistingRecordIdentifier,
StableRecordIdentifier,
UpdateMethod,
} from '@ember-data/types/q/identifier';
Expand Down Expand Up @@ -342,6 +343,10 @@ export class IdentifierCache {
@returns {StableRecordIdentifier}
@public
*/
getOrCreateRecordIdentifier(resource: ExistingResourceObject): StableExistingRecordIdentifier;
getOrCreateRecordIdentifier(
resource: ResourceIdentifierObject | Identifier | StableRecordIdentifier
): StableRecordIdentifier;
getOrCreateRecordIdentifier(resource: ResourceData | Identifier): StableRecordIdentifier {
return this._getRecordIdentifier(resource, true);
}
Expand Down
90 changes: 88 additions & 2 deletions packages/store/src/-private/managers/cache-manager.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,65 @@
import { deprecate } from '@ember/debug';
import { assert, deprecate } from '@ember/debug';

import type { LocalRelationshipOperation } from '@ember-data/graph/-private/graph/-operations';
import { StructuredDataDocument } from '@ember-data/request/-private/types';
import { ResourceDocument, StructuredDocument } from '@ember-data/types/cache/document';
import type { Cache, CacheV1, ChangedAttributesHash, MergeOperation } from '@ember-data/types/q/cache';
import type {
CollectionResourceRelationship,
JsonApiDocument,
SingleResourceRelationship,
} from '@ember-data/types/q/ember-data-json-api';
import type { StableRecordIdentifier } from '@ember-data/types/q/identifier';
import type { StableExistingRecordIdentifier, StableRecordIdentifier } from '@ember-data/types/q/identifier';
import type { JsonApiResource, JsonApiValidationError } from '@ember-data/types/q/record-data-json-api';
import type { Dict } from '@ember-data/types/q/utils';

import { isStableIdentifier } from '../caches/identifier-cache';
import type Store from '../store-service';

export function legacyCachePut(
store: Store,
doc: StructuredDataDocument<JsonApiDocument> | { data: JsonApiDocument }
): ResourceDocument {
const jsonApiDoc = doc.data;
let ret: ResourceDocument;
store._join(() => {
let included = jsonApiDoc.included;
let i: number, length: number;

if (included) {
for (i = 0, length = included.length; i < length; i++) {
store._instanceCache.loadData(included[i]);
}
}

if (Array.isArray(jsonApiDoc.data)) {
length = jsonApiDoc.data.length;
let identifiers: StableExistingRecordIdentifier[] = [];

for (i = 0; i < length; i++) {
identifiers.push(store._instanceCache.loadData(jsonApiDoc.data[i]));
}
ret = { data: identifiers };
return;
}

if (jsonApiDoc.data === null) {
ret = { data: null };
return;
}

assert(
`Expected an object in the 'data' property in a call to 'push', but was ${typeof jsonApiDoc.data}`,
typeof jsonApiDoc.data === 'object'
);

ret = { data: store._instanceCache.loadData(jsonApiDoc.data) };
return;
});

return ret!;
}

/**
* The CacheManager wraps a Cache
* enforcing that only the public API surface area
Expand Down Expand Up @@ -83,6 +130,41 @@ export class NonSingletonCacheManager implements Cache {
}
}

/**
* Cache the response to a request
*
* Unlike `store.push` which has UPSERT
* semantics, `put` has `replace` semantics similar to
* the `http` method `PUT`
*
* the individually cacheabl
* e resource data it may contain
* should upsert, but the document data surrounding it should
* fully replace any existing information
*
* Note that in order to support inserting arbitrary data
* to the cache that did not originate from a request `put`
* should expect to sometimes encounter a document with only
* a `data` member and therefor must not assume the existence
* of `request` and `response` on the document.
*
* @method put
* @param {StructuredDocument} doc
* @returns {ResourceDocument}
* @public
*/
put<T>(doc: StructuredDocument<T> | { data: T }): ResourceDocument {
const recordData = this.#recordData;
if (this.#isDeprecated(recordData)) {
if (doc instanceof Error) {
// in legacy we don't know how to handle this
throw doc;
}
return legacyCachePut(this.#store, doc as StructuredDataDocument<JsonApiDocument>);
}
return recordData.put(doc);
}

#isDeprecated(recordData: Cache | CacheV1): recordData is CacheV1 {
let version = recordData.version || '1';
return version !== this.version;
Expand Down Expand Up @@ -759,6 +841,10 @@ export class SingletonCacheManager implements Cache {
this.#cache = cache;
}

put<T>(doc: StructuredDocument<T>): ResourceDocument {
return this.#cache.put(doc);
}

// Cache
// =====

Expand Down
42 changes: 7 additions & 35 deletions packages/store/src/-private/store-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
DEPRECATE_JSON_API_FALLBACK,
DEPRECATE_PROMISE_PROXIES,
DEPRECATE_STORE_FIND,
DEPRECATE_V1_RECORD_DATA,
} from '@ember-data/private-build-infra/deprecations';
import type { Cache, CacheV1 } from '@ember-data/types/q/cache';
import type { CacheStoreWrapper } from '@ember-data/types/q/cache-store-wrapper';
Expand Down Expand Up @@ -55,7 +56,7 @@ import RecordReference from './legacy-model-support/record-reference';
import { DSModelSchemaDefinitionService, getModelFactory } from './legacy-model-support/schema-definition-service';
import type ShimModelClass from './legacy-model-support/shim-model-class';
import { getShimClass } from './legacy-model-support/shim-model-class';
import { NonSingletonCacheManager, SingletonCacheManager } from './managers/cache-manager';
import { legacyCachePut, NonSingletonCacheManager, SingletonCacheManager } from './managers/cache-manager';
import NotificationManager from './managers/notification-manager';
import RecordArrayManager from './managers/record-array-manager';
import FetchManager, { SaveOp } from './network/fetch-manager';
Expand Down Expand Up @@ -2153,43 +2154,14 @@ class Store {
}
let ret;
this._join(() => {
let included = jsonApiDoc.included;
let i, length;

if (included) {
for (i = 0, length = included.length; i < length; i++) {
this._instanceCache.loadData(included[i]);
}
}

if (Array.isArray(jsonApiDoc.data)) {
length = jsonApiDoc.data.length;
let identifiers = new Array(length);

for (i = 0; i < length; i++) {
identifiers[i] = this._instanceCache.loadData(jsonApiDoc.data[i]);
}
ret = identifiers;
return;
}

if (jsonApiDoc.data === null) {
ret = null;
return;
if (DEPRECATE_V1_RECORD_DATA) {
ret = legacyCachePut(this, { data: jsonApiDoc });
} else {
ret = this._instanceCache.__cacheManager.put(jsonApiDoc);
}

assert(
`Expected an object in the 'data' property in a call to 'push' for ${
jsonApiDoc.type
}, but was ${typeof jsonApiDoc.data}`,
typeof jsonApiDoc.data === 'object'
);

ret = this._instanceCache.loadData(jsonApiDoc.data);
return;
});

return ret;
return ret.data;
}

/**
Expand Down
2 changes: 2 additions & 0 deletions tests/docs/fixtures/expected.js
Original file line number Diff line number Diff line change
Expand Up @@ -319,9 +319,11 @@ module.exports = {
'(public) @ember-data/store RecordArray#save',
'(public) @ember-data/store RecordArray#type',
'(public) @ember-data/store RecordArray#update',
'(public) @ember-data/store <Interface> Cache#put',
'(public) @ember-data/store <Interface> Cache#patch',
'(public) @ember-data/store <Interface> Cache#upsert',
'(public) @ember-data/store <Interface> Cache#version',
'(public) @ember-data/store CacheManager#put',
'(public) @ember-data/store CacheManager#patch',
'(public) @ember-data/store CacheManager#addToHasMany',
'(public) @ember-data/store CacheManager#changedAttributes',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import Model, { attr } from '@ember-data/model';
import { DEPRECATE_V1_RECORD_DATA } from '@ember-data/private-build-infra/deprecations';
import JSONAPISerializer from '@ember-data/serializer/json-api';
import Store, { recordIdentifierFor } from '@ember-data/store';
import { ResourceDocument, StructuredDocument } from '@ember-data/types/cache/document';
import type { Cache, CacheV1, ChangedAttributesHash, MergeOperation } from '@ember-data/types/q/cache';
import type { CacheStoreWrapper } from '@ember-data/types/q/cache-store-wrapper';
import { DSModel } from '@ember-data/types/q/ds-model';
Expand All @@ -29,6 +30,9 @@ if (!DEPRECATE_V1_RECORD_DATA) {
patch(op: MergeOperation): void {
throw new Error('Method not implemented.');
}
put<T>(doc: StructuredDocument<T>): ResourceDocument {
throw new Error('Method not implemented.');
}
update(operation: LocalRelationshipOperation): void {
throw new Error('Method not implemented.');
}
Expand Down
Loading

0 comments on commit 42f2113

Please sign in to comment.