Skip to content

Commit

Permalink
feat: should only hydrate once
Browse files Browse the repository at this point in the history
  • Loading branch information
acrazing committed Nov 11, 2024
1 parent 86a5f4f commit 338ba7f
Show file tree
Hide file tree
Showing 19 changed files with 99 additions and 43 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@amos/root",
"private": true,
"version": "0.3.0-beta.33",
"version": "0.3.0-beta.34",
"license": "MIT",
"workspaces": [
"packages/*",
Expand Down
2 changes: 1 addition & 1 deletion packages/amos-babel/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "amos-babel",
"version": "0.3.0-beta.33",
"version": "0.3.0-beta.34",
"private": true,
"files": [],
"peerDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/amos-boxes/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "amos-boxes",
"version": "0.3.0-beta.33",
"version": "0.3.0-beta.34",
"private": true,
"files": []
}
1 change: 1 addition & 0 deletions packages/amos-boxes/src/mapBox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export const MapBox = Box.extends<MapBox>({
options: {
table: {
toRows: (state: Map<any, any>) => state.toJSON(),
hasRow: (state: Map<any, any>, key) => state.hasItem(key),
getRow: (state: Map<any, any>, key) => state.getItem(key),
hydrate: (state: Map<any, any>, rows) => state.setAll(state.fromJS(rows).toJSON()),
},
Expand Down
2 changes: 1 addition & 1 deletion packages/amos-core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "amos-core",
"version": "0.3.0-beta.33",
"version": "0.3.0-beta.34",
"private": true,
"files": []
}
7 changes: 6 additions & 1 deletion packages/amos-core/src/box.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,12 @@ export interface TableOptions<S = any> {
/**
* Get a row from current state
*/
getRow: (state: S, rowId: ID) => boolean;
hasRow: (state: S, rowId: ID) => boolean;

/**
* Get a row from current state
*/
getRow: (state: S, rowId: ID) => any;

/**
* Merge the persisted state to current state.
Expand Down
2 changes: 1 addition & 1 deletion packages/amos-io/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "amos-io",
"version": "0.3.0-beta.33",
"version": "0.3.0-beta.34",
"private": true,
"files": []
}
2 changes: 1 addition & 1 deletion packages/amos-persist/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "amos-persist",
"version": "0.3.0-beta.33",
"version": "0.3.0-beta.34",
"private": true,
"files": []
}
28 changes: 28 additions & 0 deletions packages/amos-persist/src/enhancer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
countBox,
darkModeBox,
dispatch,
expectCalled,
expectCalledWith,
Jerry,
Morty,
Expand Down Expand Up @@ -144,4 +145,31 @@ describe('withPersist', () => {
await store.dispatch(hydrate([]));
expect(store.select(userMapBox.getIn(Rick.id, 'firstName'))).toBe('F1');
});

it('should not loop infinite', async () => {
const storage = new MemoryStorage();
await storage.setMulti([
[toKey(countBox), 1, countBox.getInitialState()],
[toKey(userMapBox, Rick.id), 1, Rick],
]);
const store = createStore(void 0, withPersist({ storage, includes: () => true }));
store.subscribe(() => {
store.select(countBox);
store.select(userMapBox.getItem(Rick.id));
store.select(userMapBox.getItem(Morty.id));
store.select(userMapBox.getItem(10000));
});
const getMulti = jest.fn();
append(storage, 'getMulti', getMulti);
store.select(countBox);
store.select(userMapBox.getItem(Rick.id));
store.select(userMapBox.getItem(Morty.id));
store.select(userMapBox.getItem(10000));
store.select(userMapBox.getItem(10001));
await store.dispatch(hydrate([]));
expectCalled(getMulti, 1);
await Promise.resolve();
await store.dispatch(hydrate([]));
expectCalled(getMulti, 0);
});
});
56 changes: 33 additions & 23 deletions packages/amos-persist/src/enhancer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import { Box, type Mutation, type Selectable, type Selector, StoreEnhancer } fro
import { append, isAmosObject, once, override, PartialRequired, StackObserver } from 'amos-utils';
import { createHydrate } from './hydrate';
import { createPersist } from './persist';
import { persistBox, type PersistState } from './state';
import { persistBox } from './state';
import { PersistOptions, type PersistRowKey } from './types';
import { shouldPersist } from './utils';
import { shouldPersist, toKey } from './utils';

export function withPersist(options: PartialRequired<PersistOptions, 'storage'>): StoreEnhancer {
return (next) => (_options) => {
Expand All @@ -22,29 +22,35 @@ export function withPersist(options: PartialRequired<PersistOptions, 'storage'>)

const hydrate = createHydrate(store, finalOptions);
const persist = createPersist(store, finalOptions);

const hydrated = new Set<string>();
const persisted = new Map();
const initial = new Map<string, [any, any]>();
const selecting = new StackObserver();
const initial = new Map<string, any>();

const state: PersistState = {
...finalOptions,
init: once(async () => options.storage.init?.()),
snapshot: new Map(),
select: selecting.observe((s: any) => store.select(s)),
getInitial: (box) => {
if (initial.has(box.key)) {
return initial.get(box.key);
}
state.select(box);
return state.getInitial(box);
},
hydrate,
persist,
const init = once(async () => options.storage.init?.());
const select = selecting.observe((s: any) => store.select(s));
const getInitial = (box: Box) => {
if (!initial.has(box.key)) {
select(box);
}
return initial.get(box.key)!;
};

append(store, 'onInit', () => store.dispatch(persistBox.setState(state)));
append(store, 'onInit', () =>
store.dispatch(
persistBox.setState({
...finalOptions,
init,
select,
getInitial,
hydrated,
persisted,
hydrate,
persist,
}),
),
);
append(store, 'onMount', (box, initialState, preloadedState) => {
initial.set(box.key, initialState);
initial.set(box.key, [initialState, preloadedState]);
});

let dispatchingMutation = 0;
Expand Down Expand Up @@ -80,7 +86,10 @@ export function withPersist(options: PartialRequired<PersistOptions, 'storage'>)
// hydrate rows for multi-row boxes
if (
loadRows &&
r === loadRows[0].table!.getRow(state.getInitial(loadRows[0]), loadRows[1]) &&
(getInitial(loadRows[0])[1] === void 0 ||
!loadRows[0].table!.hasRow(getInitial(loadRows[0])[0], loadRows[1])) &&
!hydrated.has(toKey(loadRows[0])) &&
!hydrated.has(toKey(loadRows[0], loadRows[1])) &&
shouldPersist(finalOptions, loadRows[0])
) {
hydrate(loadRows);
Expand All @@ -91,7 +100,8 @@ export function withPersist(options: PartialRequired<PersistOptions, 'storage'>)
isBox &&
!selectable.table &&
!selectingRows &&
r === state.getInitial(selectable) &&
getInitial(selectable)[1] === void 0 &&
!hydrated.has(toKey(selectable)) &&
shouldPersist(finalOptions, selectable)
) {
hydrate(selectable);
Expand Down
17 changes: 14 additions & 3 deletions packages/amos-persist/src/hydrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,16 @@ export const createHydrate = (store: Store, finalOptions: PersistOptions) => {

const addRow = (box: Box, rowId?: ID) => {
if (rowId === void 0) {
if (state.hydrated.has(toKey(box))) {
return;
}
state.hydrated.add(toKey(box));
targets.set(box.key, box.table ? null : void 0);
} else if (state.hydrated.has(toKey(box)) || state.hydrated.has(toKey(box, rowId))) {
return;
} else {
must(box.table, `${box.key} is not a multi-row box`);
state.hydrated.add(toKey(box, rowId));
if (!targets.has(box.key)) {
targets.set(box.key, new Set());
}
Expand Down Expand Up @@ -65,6 +72,10 @@ export const createHydrate = (store: Store, finalOptions: PersistOptions) => {
}
});

if (prefixBoxes.length === 0 && exactKeys.length === 0) {
return;
}

const [prefixes, exacts] = await Promise.all([
Promise.all(prefixBoxes.map((key) => state.storage.getPrefix(toKey(key, null)))),
exactKeys.length ? state.storage.getMulti(exactKeys.map(([k, i]) => toKey(k, i))) : [],
Expand All @@ -85,10 +96,10 @@ export const createHydrate = (store: Store, finalOptions: PersistOptions) => {
prefixes.push(tablePrefixMap[key]);
}
tablePrefixMap[key].push([toKey(key, rowId), ...value]);
} else if (state.select(box) === state.getInitial(box)) {
} else if (state.select(box) === state.getInitial(box)[0]) {
const js = migrate(box, value[0], '', value[1]);
if (js !== void 0) {
exactEntries.push(box.setState(fromJS(state.getInitial(box), js)));
exactEntries.push(box.setState(fromJS(state.getInitial(box)[0], js)));
}
}
});
Expand All @@ -104,7 +115,7 @@ export const createHydrate = (store: Store, finalOptions: PersistOptions) => {
if (id === void 0) {
continue;
}
if (box.table!.getRow(curr, id) !== box.table!.getRow(state.getInitial(box), id)) {
if (box.table!.getRow(curr, id) !== box.table!.getRow(state.getInitial(box)[0], id)) {
continue;
}
d = migrate(box, v, id, d);
Expand Down
4 changes: 2 additions & 2 deletions packages/amos-persist/src/persist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ export const createPersist = (store: Store, finalOptions: PersistOptions) => {
continue;
}
const curr = snapshot[k];
const prev = state.snapshot.has(k) ? state.snapshot.get(k) : state.getInitial(box);
state.snapshot.set(k, curr);
const prev = state.persisted.has(k) ? state.persisted.get(k) : state.getInitial(box)[0];
state.persisted.set(k, curr);
if (curr === prev) {
continue;
}
Expand Down
5 changes: 3 additions & 2 deletions packages/amos-persist/src/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ import { toKey } from './utils';
export interface PersistState extends PersistOptions {
init: () => Promise<void>;
select: Select;
getInitial: (box: Box) => any;
snapshot: Map<string, any>;
getInitial: (box: Box) => [initial: any, preloaded: any];
hydrated: Set<string>;
persisted: Map<string, any>;
hydrate: NextTicker<PersistKey<any>, void>;
persist: NextTicker<void, void>;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/amos-react/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "amos-react",
"version": "0.3.0-beta.33",
"version": "0.3.0-beta.34",
"private": true,
"files": [],
"peerDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/amos-shapes/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "amos-shapes",
"version": "0.3.0-beta.33",
"version": "0.3.0-beta.34",
"private": true,
"files": []
}
2 changes: 1 addition & 1 deletion packages/amos-testing/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "amos-testing",
"version": "0.3.0-beta.33",
"version": "0.3.0-beta.34",
"private": true,
"files": []
}
2 changes: 1 addition & 1 deletion packages/amos-typescript/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "amos-typescript",
"version": "0.3.0-beta.33",
"version": "0.3.0-beta.34",
"private": true,
"files": [],
"peerDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/amos-utils/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "amos-utils",
"version": "0.3.0-beta.33",
"version": "0.3.0-beta.34",
"private": true,
"files": []
}
2 changes: 1 addition & 1 deletion packages/amos/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "amos",
"version": "0.3.0-beta.33",
"version": "0.3.0-beta.34",
"description": "An out-of-the-box state management library designed for your large-scale projects.",
"keywords": [
"amos",
Expand Down

0 comments on commit 338ba7f

Please sign in to comment.