Skip to content

Commit

Permalink
MemorySource#merge - merge cache update operations if they are being …
Browse files Browse the repository at this point in the history
…tracked

If a cache is tracking update operations, which is now the default for forked caches, then when the associated source is merged, the cache's operations will be applied in a single transform.

The benefit here is that it allows caches to be manipulated synchronously, and to have all those updates applied during a merge.

If you want to continue to merge only transforms applied to a source, and not its cache, then create the fork with `source.fork({ cacheSettings: { trackUpdateOperations: false } });`.
  • Loading branch information
dgeb committed Jul 26, 2021
1 parent a49beea commit d356f51
Show file tree
Hide file tree
Showing 2 changed files with 154 additions and 37 deletions.
81 changes: 45 additions & 36 deletions packages/@orbit/memory/src/memory-source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,14 @@ export interface MemorySourceSettings<

export interface MemorySourceMergeOptions {
coalesce?: boolean;

/**
* @deprecated since v0.17
*/
sinceTransformId?: string;

/**
* @deprecated
* @deprecated since v0.17, include transform options alongside merge options instead
*/
transformOptions?: RequestOptions;
}
Expand Down Expand Up @@ -95,7 +99,7 @@ export class MemorySource<
protected _transformInverses: Dict<RecordOperation[]>;

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

settings.name = settings.name ?? 'memory';

Expand All @@ -112,14 +116,10 @@ export class MemorySource<
settings.cacheSettings ?? {};
cacheSettings.schema = schema;
cacheSettings.keyMap = keyMap;
cacheSettings.queryBuilder =
cacheSettings.queryBuilder ?? this.queryBuilder;
cacheSettings.transformBuilder =
cacheSettings.transformBuilder ?? this.transformBuilder;
cacheSettings.defaultQueryOptions =
cacheSettings.defaultQueryOptions ?? settings.defaultQueryOptions;
cacheSettings.defaultTransformOptions =
cacheSettings.defaultTransformOptions ?? settings.defaultTransformOptions;
cacheSettings.queryBuilder ??= this.queryBuilder;
cacheSettings.transformBuilder ??= this.transformBuilder;
cacheSettings.defaultQueryOptions ??= this.defaultQueryOptions;
cacheSettings.defaultTransformOptions ??= this.defaultTransformOptions;

if (
cacheSettings.validatorFor === undefined &&
Expand All @@ -128,7 +128,6 @@ export class MemorySource<
cacheSettings.validatorFor = this._validatorFor;
}

const { base } = settings;
if (base) {
this._base = base;
this._forkPoint = base.transformLog.head;
Expand Down Expand Up @@ -267,21 +266,23 @@ export class MemorySource<
* @returns The forked source.
*/
fork(
settings: MemorySourceSettings<QO, TO, QB, TB, QRD, TRD> = {
schema: this.schema
}
settings: Partial<MemorySourceSettings<QO, TO, QB, TB, QRD, TRD>> = {}
): MemorySource<QO, TO, QB, TB, QRD, TRD> {
const schema = this.schema;

settings.schema = schema;
settings.cacheSettings = settings.cacheSettings || { schema };
settings.keyMap = this._keyMap;
settings.queryBuilder = this._queryBuilder;
settings.transformBuilder = this._transformBuilder;
settings.validatorFor = this._validatorFor;
// required settings
settings.base = this;

return new MemorySource<QO, TO, QB, TB, QRD, TRD>(settings);
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 MemorySource<QO, TO, QB, TB, QRD, TRD>(
settings as MemorySourceSettings<QO, TO, QB, TB, QRD, TRD>
);
}

/**
Expand Down Expand Up @@ -327,17 +328,25 @@ export class MemorySource<
requestOptions = (remainingOptions ?? {}) as TO;
}

let transforms: RecordTransform[];
if (sinceTransformId) {
transforms = forkedSource.getTransformsSince(sinceTransformId);
let ops: RecordOperation[] = [];

if (forkedSource.cache.isTrackingUpdateOperations) {
ops = forkedSource.cache.getAllUpdateOperations();
} else {
transforms = forkedSource.getAllTransforms();
}
let transforms: RecordTransform[];
if (sinceTransformId) {
deprecate(
'In MemorySource#merge, passing `sinceTransformId` is deprecated. Instead, call `update` with a custom transform/operations.'
);
transforms = forkedSource.getTransformsSince(sinceTransformId);
} else {
transforms = forkedSource.getAllTransforms();
}

let ops: RecordOperation[] = [];
transforms.forEach((t) => {
Array.prototype.push.apply(ops, toArray(t.operations));
});
transforms.forEach((t) => {
Array.prototype.push.apply(ops, toArray(t.operations));
});
}

if (coalesce !== false) {
ops = coalesceRecordOperations(ops);
Expand Down Expand Up @@ -443,15 +452,15 @@ export class MemorySource<
}

set defaultQueryOptions(options: DefaultRequestOptions<QO> | undefined) {
super.defaultQueryOptions = this.cache.defaultQueryOptions = options;
super.defaultQueryOptions = this._cache.defaultQueryOptions = options;
}

get defaultTransformOptions(): DefaultRequestOptions<TO> | undefined {
return super.defaultTransformOptions;
}

set defaultTransformOptions(options: DefaultRequestOptions<TO> | undefined) {
this._defaultTransformOptions = this.cache.defaultTransformOptions = options;
this._defaultTransformOptions = this._cache.defaultTransformOptions = options;
}

/////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -485,7 +494,7 @@ export class MemorySource<
fullResponse: true
} as FullRequestOptions<TO>);
this._transforms[transform.id] = transform;
this._transformInverses[transform.id] = details?.inverseOperations || [];
this._transformInverses[transform.id] = details?.inverseOperations ?? [];
return data;
}

Expand Down
110 changes: 109 additions & 1 deletion packages/@orbit/memory/test/memory-source-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,7 @@ module('MemorySource', function (hooks) {
);
});

test('#merge - merges transforms from a forked source back into a base source', async function (assert) {
test('#merge - merges changes from a forked source back into a base source', async function (assert) {
const source = new MemorySource({ schema, keyMap });

const jupiter: InitializedRecord = {
Expand Down Expand Up @@ -482,6 +482,114 @@ module('MemorySource', function (hooks) {
);
});

test("#merge - will apply all operations from a forked source's cache, if that cache is tracking update operations", async function (assert) {
const source = new MemorySource({ schema, keyMap });

const jupiter: InitializedRecord = {
type: 'planet',
id: 'jupiter',
attributes: { name: 'Jupiter', classification: 'gas giant' }
};

const earth: InitializedRecord = {
type: 'planet',
id: 'earth',
attributes: { name: 'Jupiter', classification: 'gas giant' }
};

let fork = source.fork();

assert.ok(
fork.cache.isTrackingUpdateOperations,
"forked source's cache is tracking update operations"
);

// apply change to fork.cache
fork.cache.update((t) => t.addRecord(jupiter));

// apply other change to fork directly
await fork.update((t) => t.addRecord(earth));

assert.deepEqual(
fork.cache.getRecordSync({ type: 'planet', id: 'jupiter' }),
jupiter,
'jupiter is present in fork.cache'
);
assert.deepEqual(
fork.cache.getRecordSync({ type: 'planet', id: 'earth' }),
earth,
'earth is present in fork.cache'
);

await source.merge(fork);

assert.deepEqual(
source.cache.getRecordSync({ type: 'planet', id: 'jupiter' }),
jupiter,
'jupiter is present in source.cache'
);

assert.deepEqual(
source.cache.getRecordSync({ type: 'planet', id: 'earth' }),
earth,
'earth is present in source.cache'
);
});

test('#merge - will apply only operations from a forked source, if its cache is NOT tracking update operations', async function (assert) {
const source = new MemorySource({ schema, keyMap });

const jupiter: InitializedRecord = {
type: 'planet',
id: 'jupiter',
attributes: { name: 'Jupiter', classification: 'gas giant' }
};

const earth: InitializedRecord = {
type: 'planet',
id: 'earth',
attributes: { name: 'Jupiter', classification: 'gas giant' }
};

let fork = source.fork({ cacheSettings: { trackUpdateOperations: false } });

assert.notOk(
fork.cache.isTrackingUpdateOperations,
"forked source's cache is NOT tracking update operations"
);

// apply change to fork.cache
fork.cache.update((t) => t.addRecord(jupiter));

// apply other change to fork directly
await fork.update((t) => t.addRecord(earth));

assert.deepEqual(
fork.cache.getRecordSync({ type: 'planet', id: 'jupiter' }),
jupiter,
'jupiter is present in fork.cache'
);
assert.deepEqual(
fork.cache.getRecordSync({ type: 'planet', id: 'earth' }),
earth,
'earth is present in fork.cache'
);

await source.merge(fork);

assert.strictEqual(
source.cache.getRecordSync({ type: 'planet', id: 'jupiter' }),
undefined,
'jupiter is NOT present in source.cache'
);

assert.deepEqual(
source.cache.getRecordSync({ type: 'planet', id: 'earth' }),
earth,
'earth is present in source.cache'
);
});

test('#merge - can accept options in deprecated `transformOptions` object that will be assigned to the resulting transform', async function (assert) {
assert.expect(3);

Expand Down

0 comments on commit d356f51

Please sign in to comment.