Skip to content

Commit

Permalink
Merge pull request #873 from orbitjs/forks
Browse files Browse the repository at this point in the history
Introduce new fork / merge possibilities in MemorySource and MemoryCache
  • Loading branch information
dgeb authored Jul 26, 2021
2 parents 9061328 + f1a249d commit 7803074
Show file tree
Hide file tree
Showing 6 changed files with 609 additions and 94 deletions.
187 changes: 177 additions & 10 deletions packages/@orbit/memory/src/memory-cache.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { clone, Dict } from '@orbit/utils';
import { Orbit } from '@orbit/core';
import {
InitializedRecord,
RecordIdentity,
equalRecordIdentities,
RecordQueryBuilder,
RecordTransformBuilder
} from '@orbit/records';
DefaultRequestOptions,
FullRequestOptions,
FullResponse,
RequestOptions
} from '@orbit/data';
import { ImmutableMap } from '@orbit/immutable';
import {
RecordCacheQueryOptions,
RecordCacheTransformOptions,
Expand All @@ -16,8 +16,24 @@ import {
SyncRecordCache,
SyncRecordCacheSettings
} from '@orbit/record-cache';
import { ImmutableMap } from '@orbit/immutable';
import { RequestOptions } from '@orbit/data';
import {
coalesceRecordOperations,
equalRecordIdentities,
InitializedRecord,
RecordIdentity,
RecordOperation,
RecordQueryBuilder,
RecordTransform,
RecordTransformBuilder,
RecordTransformResult
} from '@orbit/records';
import { clone, Dict, toArray } from '@orbit/utils';

const { assert } = Orbit;

export interface MemoryCacheMergeOptions {
coalesce?: boolean;
}

export interface MemoryCacheSettings<
QO extends RequestOptions = RecordCacheQueryOptions,
Expand All @@ -28,6 +44,7 @@ export interface MemoryCacheSettings<
TRD extends RecordCacheUpdateDetails = RecordCacheUpdateDetails
> extends SyncRecordCacheSettings<QO, TO, QB, TB> {
base?: MemoryCache<QO, TO, QB, TB, QRD, TRD>;
trackUpdateOperations?: boolean;
}

export interface MemoryCacheClass<
Expand Down Expand Up @@ -62,15 +79,133 @@ export class MemoryCache<
QRD = unknown,
TRD extends RecordCacheUpdateDetails = RecordCacheUpdateDetails
> extends SyncRecordCache<QO, TO, QB, TB, QRD, TRD> {
protected _base?: MemoryCache<QO, TO, QB, TB, QRD, TRD>;
protected _records!: Dict<ImmutableMap<string, InitializedRecord>>;
protected _inverseRelationships!: Dict<
ImmutableMap<string, RecordRelationshipIdentity[]>
>;
protected _updateOperations?: RecordOperation[];
protected _isTrackingUpdateOperations: boolean;

constructor(settings: MemoryCacheSettings<QO, TO, QB, TB, QRD, TRD>) {
const { base, trackUpdateOperations } = settings;

super(settings);

this.reset(settings.base);
this._base = base;

// Track update operations if explicitly told to do so, or if a `base`
// cache has been specified.
this._isTrackingUpdateOperations =
trackUpdateOperations ?? base !== undefined;

this.reset(base);
}

get base(): MemoryCache<QO, TO, QB, TB, QRD, TRD> | undefined {
return this._base;
}

/**
* Create a clone, or "fork", from a "base" cache.
*
* The forked cache will have the same `schema` and `keyMap` as its base
* source. The forked cache will start with the same immutable document as the
* base source. Its contents and log will evolve independently.
*
* @returns the forked cache
*/
fork(
settings: Partial<MemoryCacheSettings<QO, TO, QB, TB, QRD, TRD>> = {}
): MemoryCache<QO, TO, QB, TB, QRD, TRD> {
// required settings
settings.base = this;
settings.schema = this.schema;
settings.keyMap = this._keyMap;

// customizable settings
settings.queryBuilder ??= this._queryBuilder;
settings.transformBuilder ??= this._transformBuilder;
settings.validatorFor ??= this._validatorFor;
settings.defaultQueryOptions ??= this._defaultQueryOptions;
settings.defaultTransformOptions ??= this._defaultTransformOptions;

return new MemoryCache(
settings as MemoryCacheSettings<QO, TO, QB, TB, QRD, TRD>
);
}

/**
* Merges the operations from a forked cache back to this cache.
*
* @returns the result of calling `update` with the operations
*/
merge<RequestData extends RecordTransformResult = RecordTransformResult>(
forkedCache: MemoryCache<QO, TO, QB, TB, QRD, TRD>,
options?: DefaultRequestOptions<TO> & MemoryCacheMergeOptions
): RequestData;
merge<RequestData extends RecordTransformResult = RecordTransformResult>(
forkedCache: MemoryCache<QO, TO, QB, TB, QRD, TRD>,
options: FullRequestOptions<TO> & MemoryCacheMergeOptions
): FullResponse<RequestData, TRD, RecordOperation>;
merge<RequestData extends RecordTransformResult = RecordTransformResult>(
forkedCache: MemoryCache<QO, TO, QB, TB, QRD, TRD>,
options?: TO & MemoryCacheMergeOptions
): RequestData | FullResponse<RequestData, TRD, RecordOperation> {
let { coalesce, ...remainingOptions } = options ?? {};

assert(
'MemoryCache#merge can only merge a forked cache that is configured with `trackUpdateOperations: true`.',
forkedCache.isTrackingUpdateOperations
);

let ops = forkedCache.getAllUpdateOperations();

if (coalesce !== false) {
ops = coalesceRecordOperations(ops);
}

if (options?.fullResponse) {
return this.update<RequestData>(
ops,
remainingOptions as FullRequestOptions<TO>
);
} else {
return this.update<RequestData>(
ops,
remainingOptions as DefaultRequestOptions<TO>
);
}
}

/**
* Rebase resets this cache's state to that of its base cache, then re-applies
* any tracked update operations.
*
* Rebasing requires both a `base` cache as well as tracking of update
* operations (which is enabled by default when a `base` cache is assigned).
*/
rebase(): void {
const base = this._base;

assert(
'A `base` cache must be defined for `rebase` to work',
base !== undefined
);

assert(
'MemoryCache#rebase requires that the cache is configured with `trackUpdateOperations: true`.',
this._isTrackingUpdateOperations
);

// get update ops prior to resetting state
let ops = this.getAllUpdateOperations();

// reset the state of the cache to match the base cache
this.reset(base);

// reapply update ops
this.update(ops);
}

getRecordSync(identity: RecordIdentity): InitializedRecord | undefined {
Expand Down Expand Up @@ -222,6 +357,10 @@ export class MemoryCache<
reset(base?: MemoryCache<QO, TO, QB, TB, QRD, TRD>): void {
this._records = {};

if (this._isTrackingUpdateOperations) {
this._updateOperations = [];
}

Object.keys(this._schema.models).forEach((type) => {
let baseRecords = base && base._records[type];

Expand Down Expand Up @@ -254,10 +393,38 @@ export class MemoryCache<
}
}

get isTrackingUpdateOperations(): boolean {
return this._isTrackingUpdateOperations;
}

getAllUpdateOperations(): RecordOperation[] {
assert(
'MemoryCache#getAllUpdateOperations requires that cache be configured with `trackUpdateOperations: true`.',
this._isTrackingUpdateOperations
);

return this._updateOperations as RecordOperation[];
}

/////////////////////////////////////////////////////////////////////////////
// Protected methods
/////////////////////////////////////////////////////////////////////////////

protected _update<
RequestData extends RecordTransformResult = RecordTransformResult
>(
transform: RecordTransform,
options?: TO
): FullResponse<RequestData, TRD, RecordOperation> {
if (this._isTrackingUpdateOperations) {
Array.prototype.push.apply(
this._updateOperations,
toArray(transform.operations)
);
}
return super._update<RequestData>(transform, options);
}

/**
* Override `_getTransformBuffer` on base `SyncRecordCache` to provide a
* `transformBuffer` if a custom one hasn't been provided via the constructor
Expand Down
Loading

0 comments on commit 7803074

Please sign in to comment.