Skip to content

Commit

Permalink
Merge branch 'feat/configure'
Browse files Browse the repository at this point in the history
  • Loading branch information
jmeistrich committed Aug 25, 2024
2 parents 6fac8a9 + 5c7085e commit 6f566be
Show file tree
Hide file tree
Showing 37 changed files with 766 additions and 458 deletions.
1 change: 1 addition & 0 deletions config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { configureLegendState } from './src/configureLegendState';
4 changes: 0 additions & 4 deletions index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
export { isObserved, shouldIgnoreUnobserved } from './src/ObservableObject';
export { batch, beginBatch, endBatch } from './src/batching';
export { computed } from './src/computed';
export { configureLegendState } from './src/config';
export { event } from './src/event';
export { isObservable } from './src/globals';
export {
Expand All @@ -15,7 +14,6 @@ export {
mergeIntoObservable,
opaqueObject,
setAtPath,
setInObservableAtPath,
setSilently,
} from './src/helpers';
export {
Expand Down Expand Up @@ -72,7 +70,6 @@ import {
symbolLinked,
} from './src/globals';
import { deepMerge, getValueAtPath, initializePathType, setAtPath } from './src/helpers';
import { runWithRetry } from './src/retry';
import { tracking } from './src/tracking';

export const internal = {
Expand All @@ -92,7 +89,6 @@ export const internal = {
observableFns,
optimized,
peek,
runWithRetry,
safeParse,
safeStringify,
set,
Expand Down
3 changes: 2 additions & 1 deletion jest.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
"moduleNameMapper": {
"@legendapp/state/sync-plugins/crud": "<rootDir>/src/sync-plugins/crud",
"@legendapp/state/sync": "<rootDir>/sync",
"@legendapp/state/config": "<rootDir>/config",
"@legendapp/state": "<rootDir>/index"
},
"transform": {
"^.+\\.tsx?$": ["ts-jest", {"tsconfig": { "jsx": "react"}}]
"^.+\\.tsx?$": ["ts-jest", { "tsconfig": { "jsx": "react" } }]
}
}
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@legendapp/state",
"version": "3.0.0-alpha.29",
"version": "3.0.0-alpha.30",
"description": "legend-state",
"sideEffects": false,
"private": true,
Expand Down Expand Up @@ -47,6 +47,7 @@
".",
"sync",
"react",
"config",
"trace",
"babel",
"as/*",
Expand Down Expand Up @@ -136,4 +137,4 @@
"@commitlint/config-conventional"
]
}
}
}
8 changes: 4 additions & 4 deletions src/batching.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ export function notify(node: NodeInfo, value: any, prev: any, level: number, whe
computeChangesRecursive(
changesInBatch,
node,
/*loading*/ globalState.isLoadingLocal,
/*remote*/ globalState.isLoadingRemote,
/*loading*/ !!globalState.isLoadingLocal,
/*remote*/ !!globalState.isLoadingRemote,
value,
[],
[],
Expand Down Expand Up @@ -98,8 +98,8 @@ export function notify(node: NodeInfo, value: any, prev: any, level: number, whe
prev,
level,
whenOptimizedOnlyIf,
isFromSync: globalState.isLoadingRemote,
isFromPersist: globalState.isLoadingLocal,
isFromSync: !!globalState.isLoadingRemote,
isFromPersist: !!globalState.isLoadingLocal,
});
}

Expand Down
3 changes: 2 additions & 1 deletion src/config/enable$GetSet.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { configureLegendState, internal } from '@legendapp/state';
import { internal } from '@legendapp/state';
import { configureLegendState } from '@legendapp/state/config';

export function enable$GetSet() {
configureLegendState({
Expand Down
11 changes: 2 additions & 9 deletions src/config/enableReactTracking.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
import {
type GetOptions,
configureLegendState,
internal,
isObject,
tracking,
type NodeInfo,
type TrackingType,
} from '@legendapp/state';
import { type GetOptions, internal, isObject, tracking, type NodeInfo, type TrackingType } from '@legendapp/state';
import { configureLegendState } from '@legendapp/state/config';
import { UseSelectorOptions, useSelector } from '@legendapp/state/react';
import { createContext, useContext } from 'react';
// @ts-expect-error Internals
Expand Down
3 changes: 2 additions & 1 deletion src/config/enableReactUse.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { configureLegendState, internal, NodeInfo } from '@legendapp/state';
import { internal, NodeInfo } from '@legendapp/state';
import { configureLegendState } from '@legendapp/state/config';
import { useSelector, UseSelectorOptions } from '@legendapp/state/react';

// TODO: Deprecated, remove in v4
Expand Down
3 changes: 2 additions & 1 deletion src/config/enable_PeekAssign.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { configureLegendState, internal } from '@legendapp/state';
import { internal } from '@legendapp/state';
import { configureLegendState } from '@legendapp/state/config';

export function enable_PeekAssign() {
configureLegendState({
Expand Down
File renamed without changes.
1 change: 1 addition & 0 deletions src/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export function event(): ObservableEvent {
const obs = observable(0);
const node = getNode(obs);
node.isEvent = true;

return {
fire: function () {
// Notify increments the value so that the observable changes
Expand Down
19 changes: 12 additions & 7 deletions src/globals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@ import { isArray, isChildNode, isDate, isFunction, isMap, isObject, isSet } from
import type { NodeInfo, ObservableEvent, TypeAtPath, UpdateFn } from './observableInterfaces';
import type { Observable, ObservableParam } from './observableTypes';

type GlobalState = {
isLoadingLocal: boolean;
isLoadingRemote: boolean;
activateSyncedNode: (node: NodeInfo, newValue: any) => { update: UpdateFn; value: any };
pendingNodes: Map<NodeInfo, () => void>;
dirtyNodes: Set<NodeInfo>;
replacer: ((this: any, key: string, value: any) => any) | undefined;
reviver: ((this: any, key: string, value: any) => any) | undefined;
};

export const symbolToPrimitive = Symbol.toPrimitive;
export const symbolIterator = Symbol.iterator;
export const symbolGetNode = Symbol('getNode');
Expand All @@ -10,15 +20,10 @@ export const symbolOpaque = Symbol('opaque');
export const optimized = Symbol('optimized');
export const symbolLinked = Symbol('linked');

export const globalState = {
isLoadingLocal: false,
isLoadingRemote: false,
activateSyncedNode: undefined as unknown as (node: NodeInfo, newValue: any) => { update: UpdateFn; value: any },
export const globalState: GlobalState = {
pendingNodes: new Map<NodeInfo, () => void>(),
dirtyNodes: new Set<NodeInfo>(),
replacer: undefined as undefined | ((this: any, key: string, value: any) => any),
reviver: undefined as undefined | ((this: any, key: string, value: any) => any),
};
} as GlobalState;

export function isOpaqueObject(value: any) {
// React elements have $$typeof and should be treated as opaque
Expand Down
68 changes: 24 additions & 44 deletions src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { beginBatch, endBatch } from './batching';
import { getNode, isObservable, setNodeValue, symbolDelete, symbolOpaque } from './globals';
import { hasOwnProperty, isArray, isEmpty, isFunction, isMap, isNumber, isObject, isPrimitive, isSet } from './is';
import type { Change, ObserveEvent, OpaqueObject, Selector, TypeAtPath } from './observableInterfaces';
import type { Observable, ObservableParam } from './observableTypes';
import type { ObservableParam } from './observableTypes';

export function computeSelector<T>(selector: Selector<T>, e?: ObserveEvent<T>, retainObservable?: boolean): T {
let c = selector as any;
Expand Down Expand Up @@ -56,7 +56,7 @@ export function setAtPath<T extends object>(
} else if (o[p] === undefined && value === undefined && i === path.length - 1) {
// If setting undefined and the key is undefined, no need to initialize or set it
return obj;
} else if (o[p] === undefined || o[p] === null) {
} else if (i < path.length - 1 && (o[p] === undefined || o[p] === null)) {
const child = initializePathType(pathTypes[i]);
if (isMap(o)) {
o.set(p, child);
Expand Down Expand Up @@ -93,36 +93,6 @@ export function setAtPath<T extends object>(

return obj;
}
export function setInObservableAtPath(
value$: ObservableParam,
path: string[],
pathTypes: TypeAtPath[],
value: any,
mode: 'assign' | 'set' | 'merge',
) {
let o: any = value$;
let v = value;
for (let i = 0; i < path.length; i++) {
const p = path[i];
if (!o.peek()[p]) {
o[p].set(initializePathType(pathTypes[i]));
}
o = o[p];
v = v[p];
}

if (v === symbolDelete) {
(o as Observable).delete();
}
// Assign if possible, or set otherwise
else if (mode === 'assign' && (o as Observable).assign && isObject(o.peek())) {
(o as Observable<{}>).assign(v);
} else if (mode === 'merge') {
mergeIntoObservable(o, v);
} else {
o.set(v);
}
}
export function mergeIntoObservable<T extends ObservableParam<any>>(target: T, ...sources: any[]): T {
if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test') {
if (!isObservable(target)) {
Expand Down Expand Up @@ -225,12 +195,13 @@ export function initializePathType(pathType: TypeAtPath): any {
switch (pathType) {
case 'array':
return [];
case 'object':
return {};
case 'map':
return new Map();
case 'set':
return new Set();
case 'object':
default:
return {};
}
}
export function applyChange<T extends object>(value: T, change: Change, applyPrevious?: boolean): T {
Expand All @@ -248,21 +219,30 @@ export function deepMerge<T>(target: T, ...sources: any[]): T {
return sources[sources.length - 1];
}

const result: T = (isArray(target) ? [...target] : { ...target }) as T;
let result: T = (isArray(target) ? [...target] : { ...target }) as T;

for (let i = 0; i < sources.length; i++) {
const obj2 = sources[i];
for (const key in obj2) {
if (hasOwnProperty.call(obj2, key)) {
if (obj2[key] instanceof Object && !isObservable(obj2[key]) && Object.keys(obj2[key]).length > 0) {
(result as any)[key] = deepMerge(
(result as any)[key] || (isArray((obj2 as any)[key]) ? [] : {}),
(obj2 as any)[key],
);
} else {
(result as any)[key] = obj2[key];
if (isObject(obj2) || isArray(obj2)) {
const objTarget = obj2 as Record<string, any>;
for (const key in objTarget) {
if (hasOwnProperty.call(objTarget, key)) {
if (
objTarget[key] instanceof Object &&
!isObservable(objTarget[key]) &&
Object.keys(objTarget[key]).length > 0
) {
(result as any)[key] = deepMerge(
(result as any)[key] || (isArray((objTarget as any)[key]) ? [] : {}),
(objTarget as any)[key],
);
} else {
(result as any)[key] = objTarget[key];
}
}
}
} else {
result = obj2;
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/observableInterfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export type ClassConstructor<I, Args extends any[] = any[]> = new (...args: Args
export type ObservableListenerDispose = () => void;

export interface ObservableRoot {
// Observable root value is set on a child of the object so the reference to the root never changes
_: any;
set?: (value: any) => void;
}
Expand Down Expand Up @@ -180,6 +181,7 @@ export interface ObservableSyncStateBase {
}
>
| undefined;
reset: () => Promise<void>;
/* @internal */
numPendingLocalLoads?: number;
numPendingRemoteLoads?: number;
Expand Down
21 changes: 3 additions & 18 deletions src/onChange.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getNodeValue } from './globals';
import { isArray } from './is';
import { deconstructObjectWithPath } from './helpers';
import type { ListenerFn, ListenerParams, NodeInfo, NodeListener, TrackingType } from './observableInterfaces';

export function onChange(
Expand Down Expand Up @@ -106,10 +106,10 @@ export function onChange(

function createCb(linkedFromNode: NodeInfo, path: string[], callback: ListenerFn) {
// Create a callback for a path that calls it with the current value at the path
let { valueAtPath: prevAtPath } = getValueAtPath(getNodeValue(linkedFromNode), path);
let prevAtPath = deconstructObjectWithPath(path, [], getNodeValue(linkedFromNode));

return function ({ value: valueA, isFromPersist, isFromSync }: ListenerParams<any>) {
const { valueAtPath } = getValueAtPath(valueA, path);
const valueAtPath = deconstructObjectWithPath(path, [], valueA);
if (valueAtPath !== prevAtPath) {
callback({
value: valueAtPath,
Expand All @@ -129,18 +129,3 @@ function createCb(linkedFromNode: NodeInfo, path: string[], callback: ListenerFn
prevAtPath = valueAtPath;
};
}

function getValueAtPath(
obj: Record<string, any>,
path: string[],
): { valueAtPath: any; pathTypes: ('object' | 'array')[] } {
let o: Record<string, any> = obj;
const pathTypes: ('object' | 'array')[] = [];
for (let i = 0; o && i < path.length; i++) {
pathTypes.push(isArray(o) ? 'array' : 'object');
const p = path[i];
o = (o as any)[p];
}

return { valueAtPath: o, pathTypes };
}
27 changes: 23 additions & 4 deletions src/persist-plugins/async-storage.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import type { Change } from '@legendapp/state';
import { applyChanges, internal, isArray } from '@legendapp/state';
import type { ObservablePersistPlugin, ObservablePersistPluginOptions, PersistMetadata } from '@legendapp/state/sync';
import type {
ObservablePersistAsyncStoragePluginOptions,
ObservablePersistPlugin,
ObservablePersistPluginOptions,
PersistMetadata,
} from '@legendapp/state/sync';
import type { AsyncStorageStatic } from '@react-native-async-storage/async-storage';

const MetadataSuffix = '__m';
Expand All @@ -11,11 +16,15 @@ const { safeParse, safeStringify } = internal;

export class ObservablePersistAsyncStorage implements ObservablePersistPlugin {
private data: Record<string, any> = {};
private configuration: ObservablePersistAsyncStoragePluginOptions;

constructor(configuration: ObservablePersistAsyncStoragePluginOptions) {
this.configuration = configuration;
}
public async initialize(configOptions: ObservablePersistPluginOptions) {
const storageConfig = this.configuration || configOptions.asyncStorage;

// Init
public async initialize(config: ObservablePersistPluginOptions) {
let tables: readonly string[] = [];
const storageConfig = config.asyncStorage;
if (storageConfig) {
AsyncStorage = storageConfig.AsyncStorage;
const { preload } = storageConfig;
Expand Down Expand Up @@ -93,3 +102,13 @@ export class ObservablePersistAsyncStorage implements ObservablePersistPlugin {
}
}
}

export function configureObservablePersistAsyncStorage(
configuration: ObservablePersistAsyncStoragePluginOptions,
): typeof ObservablePersistAsyncStorage {
return class ObservablePersistAsyncStorageConfigured extends ObservablePersistAsyncStorage {
constructor() {
super(configuration);
}
};
}
Loading

0 comments on commit 6f566be

Please sign in to comment.