From 2f31d0246e0602d69b6a8a9728ab5901e5aea6d0 Mon Sep 17 00:00:00 2001 From: Aravind Date: Tue, 6 Feb 2024 11:48:53 +0800 Subject: [PATCH 1/3] Skip unchanged in shift.js --- src/core/Graffy.js | 2 ++ src/core/shift.js | 57 +++++++++++++++++++++++++++++----------------- 2 files changed, 38 insertions(+), 21 deletions(-) diff --git a/src/core/Graffy.js b/src/core/Graffy.js index 766ac719..b83f148f 100644 --- a/src/core/Graffy.js +++ b/src/core/Graffy.js @@ -16,6 +16,8 @@ import Core from './Core.js'; import { shiftGen, wrapProvider } from './shift.js'; import { validateCall, validateOn } from './validate.js'; +export { unchanged } from './shift.js'; + export default class Graffy { constructor(path = [], core = new Core()) { this.core = core; diff --git a/src/core/shift.js b/src/core/shift.js index b599b76c..6591130d 100644 --- a/src/core/shift.js +++ b/src/core/shift.js @@ -21,12 +21,15 @@ async function mapStream(stream, fn) { } } +export const unchanged = Symbol('Payload or result unchanged by handler'); + export function wrapProvider(fn, decodedPath, isRead) { const decodePayload = isRead ? decodeQuery : decodeGraph; const encodePayload = isRead ? encodeQuery : encodeGraph; const path = encodePath(decodedPath); return async function wrappedProvider(payload, options, next) { let nextCalled = false; + let nextResult; let remainingNextResult; const porcelainPayload = unwrapObject(decodePayload(payload), decodedPath); const remainingPayload = remove(payload, path) || []; @@ -34,11 +37,18 @@ export function wrapProvider(fn, decodedPath, isRead) { // This next function is offered to the provider function. async function shiftedNext(porcelainNextPayload, nextOptions) { nextCalled = true; - const nextPayload = encodePayload( - wrapObject(porcelainNextPayload, decodedPath), - ); - if (remainingPayload.length) merge(nextPayload, remainingPayload); - const nextResult = await next(nextPayload, nextOptions); + + let nextPayload; + if (porcelainNextPayload === unchanged) { + nextPayload = payload; + } else { + nextPayload = encodePayload( + wrapObject(porcelainNextPayload, decodedPath), + ); + if (remainingPayload.length) merge(nextPayload, remainingPayload); + } + + nextResult = await next(nextPayload, nextOptions); // Remember the next() results that are not returned to this provider. // These will be merged into the result later. @@ -47,24 +57,29 @@ export function wrapProvider(fn, decodedPath, isRead) { } const porcelainResult = await fn(porcelainPayload, options, shiftedNext); - let result = encodeGraph(wrapObject(porcelainResult, decodedPath)); - // console.log(result); - - // TODO: Get rid of this special handling by requiring read providers to - // finalize results themselves. - if (isRead && !nextCalled) { - // This does the opposite of "remove"; "keep"? - const appliedQuery = wrap(unwrap(payload, path), path); - result = finalize(result, appliedQuery); - result = wrap(unwrap(result, path), path); - } - if (!nextCalled && remainingPayload.length) { - remainingNextResult = await next(remainingPayload); - } + let result; + if (porcelainResult === unchanged) { + result = nextResult; + } else { + result = encodeGraph(wrapObject(porcelainResult, decodedPath)); + + // TODO: Get rid of this special handling by requiring read providers to + // finalize results themselves. + if (isRead && !nextCalled) { + // This does the opposite of "remove"; "keep"? + const appliedQuery = wrap(unwrap(payload, path), path); + result = finalize(result, appliedQuery); + result = wrap(unwrap(result, path), path); + } - if (remainingNextResult?.length) { - merge(result, remainingNextResult); + if (!nextCalled && remainingPayload.length) { + remainingNextResult = await next(remainingPayload); + } + + if (remainingNextResult?.length) { + merge(result, remainingNextResult); + } } // console.log('Shifted', path, format(payload), format(result)); From 08e42927dd7e76e48fb770953053f100110f6071 Mon Sep 17 00:00:00 2001 From: Aravind Date: Tue, 6 Feb 2024 19:08:50 +0800 Subject: [PATCH 2/3] Add tests --- src/core/test/porcelain.test.js | 60 ++++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/src/core/test/porcelain.test.js b/src/core/test/porcelain.test.js index 609afa40..978f9ab9 100644 --- a/src/core/test/porcelain.test.js +++ b/src/core/test/porcelain.test.js @@ -1,7 +1,7 @@ import GraffyFill from '@graffy/fill'; import { page, ref } from '@graffy/testing'; import { jest } from '@jest/globals'; -import Graffy from '../Graffy.js'; +import Graffy, { unchanged } from '../Graffy.js'; test('Porcelain read', async () => { const store = new Graffy(); @@ -417,3 +417,61 @@ test('modified_next_options', async () => { await store.read('user', query, { foo: 2 }); expect(mockOnRead).toBeCalledWith(query, { bar: true }, expect.any(Function)); }); + +describe('unchanged', () => { + let store; + + const originalQuery = { foo: true }; + const changedQuery = { foo: true, bar: true }; + const originalChange = { foo: 5 }; + const changedChange = { foo: 8, bar: 6 }; + const originalResult = { foo: 10 }; + const changedResult = { foo: 8 }; + + const cases = [ + [false, false], + [false, true], + [true, false], + [true, true], + ]; + + beforeEach(() => { + store = new Graffy(); + }); + + test.each(cases)( + 'read nextChanged:%j retChanged:%j', + async (nextChanged, retChanged) => { + store.onRead('example', async (_query, _options, next) => { + await next(nextChanged ? changedQuery : unchanged); + return retChanged ? changedResult : unchanged; + }); + + store.onRead('example', async (query, _options) => { + expect(query).toEqual(nextChanged ? changedQuery : originalQuery); + return originalResult; + }); + + const result = await store.read('example', originalQuery); + expect(result).toEqual(retChanged ? changedResult : originalResult); + }, + ); + + test.each(cases)( + 'write nextChanged:%j retChanged:%j', + async (nextChanged, retChanged) => { + store.onWrite('example', async (_change, _options, next) => { + await next(nextChanged ? changedChange : unchanged); + return retChanged ? changedResult : unchanged; + }); + + store.onWrite('example', async (change, _options) => { + expect(change).toEqual(nextChanged ? changedChange : originalChange); + return originalResult; + }); + + const result = await store.write('example', originalChange); + expect(result).toEqual(retChanged ? changedResult : originalResult); + }, + ); +}); From 3145296555a1a74e3531b558713c8b3f033fd50b Mon Sep 17 00:00:00 2001 From: Aravind Date: Tue, 6 Feb 2024 21:50:22 +0800 Subject: [PATCH 3/3] Fix packaging issues --- scripts/version.js | 8 +++++++- src/core/Graffy.js | 4 +++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/scripts/version.js b/scripts/version.js index edb52c9b..3232bad1 100644 --- a/scripts/version.js +++ b/scripts/version.js @@ -15,12 +15,16 @@ export default async function version(str) { try { const { stdout } = await git('tag'); + console.log(stdout); + const [major = 0, minor = 0, patch = 0, pre = '', number = 0] = stdout .split('\n') .reduce((latest, vstring) => { const version = vstring .split(/[.-]/) - .map((seg) => (Number.isNaN(seg) ? seg : parseInt(seg))); + .map((seg, i) => (i === 3 ? seg : parseInt(seg))); + + console.log('latest', latest, vstring, version); for (let i = 0; i < 5; i++) { const atPre = i === 3; @@ -32,6 +36,8 @@ export default async function version(str) { return latest; }, []); + console.log({ major, minor, patch, pre, number }); + switch (str) { case 'major': return `${major + 1}.0.0`; diff --git a/src/core/Graffy.js b/src/core/Graffy.js index b83f148f..f3180609 100644 --- a/src/core/Graffy.js +++ b/src/core/Graffy.js @@ -13,7 +13,7 @@ import { } from '@graffy/common'; import { makeStream, mapStream } from '@graffy/stream'; import Core from './Core.js'; -import { shiftGen, wrapProvider } from './shift.js'; +import { shiftGen, unchanged, wrapProvider } from './shift.js'; import { validateCall, validateOn } from './validate.js'; export { unchanged } from './shift.js'; @@ -106,3 +106,5 @@ export default class Graffy { return unwrapObject(decodeGraph(writtenChange), path); } } + +Graffy.unchanged = unchanged;