@legendapp/state 2.2.0-next.75 → 2.2.0-next.76
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/helpers/time.d.ts +2 -2
- package/index.js +7 -5
- package/index.js.map +1 -1
- package/index.mjs +7 -5
- package/index.mjs.map +1 -1
- package/package.json +11 -1
- package/persist.js +82 -87
- package/persist.js.map +1 -1
- package/persist.mjs +82 -87
- package/persist.mjs.map +1 -1
- package/src/batching.ts +2 -0
- package/src/computed.ts +4 -2
- package/src/globals.ts +1 -1
- package/src/helpers.ts +1 -1
- package/src/history/undoRedo.ts +111 -0
- package/src/observableInterfaces.ts +6 -5
- package/src/observe.ts +1 -1
- package/src/sync/activateSyncedNode.ts +2 -20
- package/src/sync/syncObservable.ts +88 -73
- package/src/sync-plugins/crud.ts +109 -98
- package/src/sync-plugins/fetch.ts +56 -26
- package/src/sync-plugins/keel.ts +447 -0
- package/src/sync-plugins/supabase.ts +225 -0
- package/src/syncTypes.ts +10 -4
- package/sync-plugins/crud.d.ts +27 -26
- package/sync-plugins/crud.js +50 -42
- package/sync-plugins/crud.js.map +1 -1
- package/sync-plugins/crud.mjs +50 -42
- package/sync-plugins/crud.mjs.map +1 -1
- package/sync-plugins/fetch.d.ts +8 -7
- package/sync-plugins/fetch.js +33 -11
- package/sync-plugins/fetch.js.map +1 -1
- package/sync-plugins/fetch.mjs +34 -12
- package/sync-plugins/fetch.mjs.map +1 -1
- package/sync-plugins/keel.d.ts +91 -0
- package/sync-plugins/keel.js +278 -0
- package/sync-plugins/keel.js.map +1 -0
- package/sync-plugins/keel.mjs +274 -0
- package/sync-plugins/keel.mjs.map +1 -0
- package/sync-plugins/supabase.d.ts +32 -0
- package/sync-plugins/supabase.js +134 -0
- package/sync-plugins/supabase.js.map +1 -0
- package/sync-plugins/supabase.mjs +131 -0
- package/sync-plugins/supabase.mjs.map +1 -0
- package/sync.js +82 -87
- package/sync.js.map +1 -1
- package/sync.mjs +83 -88
- package/sync.mjs.map +1 -1
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
NodeValue,
|
|
6
6
|
Observable,
|
|
7
7
|
ObservableObject,
|
|
8
|
+
ObservableOnChangeParams,
|
|
8
9
|
ObservableParam,
|
|
9
10
|
ObservablePersistPlugin,
|
|
10
11
|
ObservableSyncClass,
|
|
@@ -15,6 +16,7 @@ import type {
|
|
|
15
16
|
Synced,
|
|
16
17
|
SyncedOptions,
|
|
17
18
|
TypeAtPath,
|
|
19
|
+
UpdateFnParams,
|
|
18
20
|
} from '@legendapp/state';
|
|
19
21
|
import {
|
|
20
22
|
beginBatch,
|
|
@@ -863,6 +865,82 @@ export function syncObservable<T>(
|
|
|
863
865
|
|
|
864
866
|
if (get) {
|
|
865
867
|
const runGet = () => {
|
|
868
|
+
const onChange = async ({ value, mode, lastSync }: UpdateFnParams) => {
|
|
869
|
+
mode = mode || syncOptions.mode || 'set';
|
|
870
|
+
if (value !== undefined) {
|
|
871
|
+
value = transformLoadData(value, syncOptions, true);
|
|
872
|
+
if (isPromise(value)) {
|
|
873
|
+
value = await (value as Promise<T>);
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
const pending = localState.pendingChanges;
|
|
877
|
+
const currentValue = obs$.peek();
|
|
878
|
+
if (pending) {
|
|
879
|
+
let didChangeMetadata = false;
|
|
880
|
+
Object.keys(pending).forEach((key) => {
|
|
881
|
+
const p = key.split('/').filter((p) => p !== '');
|
|
882
|
+
const { v, t } = pending[key];
|
|
883
|
+
|
|
884
|
+
if (t.length === 0 || !value) {
|
|
885
|
+
if (isObject(value) && isObject(v)) {
|
|
886
|
+
Object.assign(value, v);
|
|
887
|
+
} else {
|
|
888
|
+
value = v;
|
|
889
|
+
}
|
|
890
|
+
} else if ((value as any)[p[0]] !== undefined) {
|
|
891
|
+
const curValue = getValueAtPath(currentValue as object, p);
|
|
892
|
+
const newValue = getValueAtPath(value as object, p);
|
|
893
|
+
if (JSON.stringify(curValue) === JSON.stringify(newValue)) {
|
|
894
|
+
delete pending[key];
|
|
895
|
+
didChangeMetadata = true;
|
|
896
|
+
} else {
|
|
897
|
+
(value as any) = setAtPath(
|
|
898
|
+
value as any,
|
|
899
|
+
p,
|
|
900
|
+
t,
|
|
901
|
+
v,
|
|
902
|
+
'merge',
|
|
903
|
+
obs$.peek(),
|
|
904
|
+
(path: string[], value: any) => {
|
|
905
|
+
delete pending[key];
|
|
906
|
+
pending[path.join('/')] = {
|
|
907
|
+
p: null,
|
|
908
|
+
v: value,
|
|
909
|
+
t: t.slice(0, path.length),
|
|
910
|
+
};
|
|
911
|
+
},
|
|
912
|
+
);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
});
|
|
916
|
+
|
|
917
|
+
if (didChangeMetadata) {
|
|
918
|
+
updateMetadata(obs$, localState, syncState, syncOptions, {
|
|
919
|
+
pending,
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
onChangeRemote(() => {
|
|
925
|
+
if (mode === 'assign' && isObject(value)) {
|
|
926
|
+
(obs$ as unknown as Observable<object>).assign(value);
|
|
927
|
+
} else if (mode === 'append' && isArray(value)) {
|
|
928
|
+
(obs$ as unknown as Observable<any[]>).push(...value);
|
|
929
|
+
} else if (mode === 'prepend' && isArray(value)) {
|
|
930
|
+
(obs$ as unknown as Observable<any[]>).splice(0, 0, ...value);
|
|
931
|
+
} else if (mode === 'merge') {
|
|
932
|
+
mergeIntoObservable(obs$, value);
|
|
933
|
+
} else {
|
|
934
|
+
obs$.set(value);
|
|
935
|
+
}
|
|
936
|
+
});
|
|
937
|
+
}
|
|
938
|
+
if (lastSync && syncOptions.persist) {
|
|
939
|
+
updateMetadata(obs$, localState, syncState, syncOptions, {
|
|
940
|
+
lastSync,
|
|
941
|
+
});
|
|
942
|
+
}
|
|
943
|
+
};
|
|
866
944
|
get({
|
|
867
945
|
state: syncState,
|
|
868
946
|
obs: obs$,
|
|
@@ -873,87 +951,24 @@ export function syncObservable<T>(
|
|
|
873
951
|
syncOptions.onGetError?.(error);
|
|
874
952
|
},
|
|
875
953
|
onGet: () => {
|
|
954
|
+
const isFirstLoad = !node.state!.isLoaded.peek();
|
|
876
955
|
node.state!.assign({
|
|
877
956
|
isLoaded: true,
|
|
878
957
|
error: undefined,
|
|
879
958
|
});
|
|
880
|
-
},
|
|
881
|
-
onChange: async ({ value, mode, lastSync }) => {
|
|
882
|
-
mode = mode || syncOptions.mode || 'set';
|
|
883
|
-
if (value !== undefined) {
|
|
884
|
-
value = transformLoadData(value, syncOptions, true);
|
|
885
|
-
if (isPromise(value)) {
|
|
886
|
-
value = await (value as Promise<T>);
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
const pending = localState.pendingChanges;
|
|
890
|
-
const currentValue = obs$.peek();
|
|
891
|
-
if (pending) {
|
|
892
|
-
let didChangeMetadata = false;
|
|
893
|
-
Object.keys(pending).forEach((key) => {
|
|
894
|
-
const p = key.split('/').filter((p) => p !== '');
|
|
895
|
-
const { v, t } = pending[key];
|
|
896
|
-
|
|
897
|
-
if (t.length === 0 || !value) {
|
|
898
|
-
if (isObject(value) && isObject(v)) {
|
|
899
|
-
Object.assign(value, v);
|
|
900
|
-
} else {
|
|
901
|
-
value = v;
|
|
902
|
-
}
|
|
903
|
-
} else if ((value as any)[p[0]] !== undefined) {
|
|
904
|
-
const curValue = getValueAtPath(currentValue as object, p);
|
|
905
|
-
const newValue = getValueAtPath(value as object, p);
|
|
906
|
-
if (JSON.stringify(curValue) === JSON.stringify(newValue)) {
|
|
907
|
-
delete pending[key];
|
|
908
|
-
didChangeMetadata = true;
|
|
909
|
-
} else {
|
|
910
|
-
(value as any) = setAtPath(
|
|
911
|
-
value as any,
|
|
912
|
-
p,
|
|
913
|
-
t,
|
|
914
|
-
v,
|
|
915
|
-
'merge',
|
|
916
|
-
obs$.peek(),
|
|
917
|
-
(path: string[], value: any) => {
|
|
918
|
-
delete pending[key];
|
|
919
|
-
pending[path.join('/')] = {
|
|
920
|
-
p: null,
|
|
921
|
-
v: value,
|
|
922
|
-
t: t.slice(0, path.length),
|
|
923
|
-
};
|
|
924
|
-
},
|
|
925
|
-
);
|
|
926
|
-
}
|
|
927
|
-
}
|
|
928
|
-
});
|
|
929
959
|
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
if (mode === 'assign' && isObject(value)) {
|
|
939
|
-
(obs$ as unknown as Observable<object>).assign(value);
|
|
940
|
-
} else if (mode === 'append' && isArray(value)) {
|
|
941
|
-
(obs$ as unknown as Observable<any[]>).push(...value);
|
|
942
|
-
} else if (mode === 'prepend' && isArray(value)) {
|
|
943
|
-
(obs$ as unknown as Observable<any[]>).splice(0, 0, ...value);
|
|
944
|
-
} else if (mode === 'merge') {
|
|
945
|
-
mergeIntoObservable(obs$, value);
|
|
946
|
-
} else {
|
|
947
|
-
obs$.set(value);
|
|
948
|
-
}
|
|
949
|
-
});
|
|
950
|
-
}
|
|
951
|
-
if (lastSync && syncOptions.persist) {
|
|
952
|
-
updateMetadata(obs$, localState, syncState, syncOptions, {
|
|
953
|
-
lastSync,
|
|
960
|
+
if (isFirstLoad && syncOptions.subscribe) {
|
|
961
|
+
syncOptions.subscribe({
|
|
962
|
+
node,
|
|
963
|
+
update: (params: ObservableOnChangeParams) => {
|
|
964
|
+
params.mode ||= syncOptions.mode || 'merge';
|
|
965
|
+
onChange(params);
|
|
966
|
+
},
|
|
967
|
+
refresh: sync,
|
|
954
968
|
});
|
|
955
969
|
}
|
|
956
970
|
},
|
|
971
|
+
onChange,
|
|
957
972
|
});
|
|
958
973
|
};
|
|
959
974
|
runGet();
|
package/src/sync-plugins/crud.ts
CHANGED
|
@@ -14,54 +14,55 @@ import { synced, diffObjects } from '@legendapp/state/sync';
|
|
|
14
14
|
|
|
15
15
|
const { clone } = internal;
|
|
16
16
|
|
|
17
|
-
export type CrudAsOption = 'Map' | 'object' | 'first'
|
|
17
|
+
export type CrudAsOption = 'Map' | 'object' | 'first';
|
|
18
18
|
|
|
19
19
|
export type CrudResult<T> = T;
|
|
20
20
|
|
|
21
|
-
export interface SyncedCrudPropsSingle<
|
|
22
|
-
get?: (params: SyncedGetParams) => Promise<CrudResult<
|
|
23
|
-
initial?:
|
|
21
|
+
export interface SyncedCrudPropsSingle<TRemote, TLocal> {
|
|
22
|
+
get?: (params: SyncedGetParams) => Promise<CrudResult<TRemote | null>> | CrudResult<TRemote | null>;
|
|
23
|
+
initial?: InitialValue<TLocal, 'first'>;
|
|
24
|
+
as?: never | 'first';
|
|
24
25
|
}
|
|
25
|
-
export interface SyncedCrudPropsMany<
|
|
26
|
-
list?: (params: SyncedGetParams) => Promise<CrudResult<
|
|
27
|
-
as?:
|
|
28
|
-
initial?: InitialValue<
|
|
26
|
+
export interface SyncedCrudPropsMany<TRemote, TLocal, TAsOption extends CrudAsOption> {
|
|
27
|
+
list?: (params: SyncedGetParams) => Promise<CrudResult<TRemote[] | null>> | CrudResult<TRemote[] | null>;
|
|
28
|
+
as?: TAsOption;
|
|
29
|
+
initial?: InitialValue<TLocal, TAsOption>;
|
|
29
30
|
}
|
|
30
|
-
export interface SyncedCrudPropsBase<
|
|
31
|
-
extends Omit<SyncedOptions<
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
export interface SyncedCrudPropsBase<TRemote extends { id: string | number }, TLocal = TRemote>
|
|
32
|
+
extends Omit<SyncedOptions<TLocal>, 'get' | 'set' | 'transform' | 'initial'> {
|
|
33
|
+
create?(input: TRemote, params: SyncedSetParams<TRemote>): Promise<CrudResult<TRemote> | null | undefined>;
|
|
34
|
+
update?(
|
|
35
|
+
input: Partial<TRemote>,
|
|
36
|
+
params: SyncedSetParams<TRemote>,
|
|
37
|
+
): Promise<CrudResult<Partial<TRemote> | null | undefined>>;
|
|
38
|
+
delete?(input: TRemote, params: SyncedSetParams<TRemote>): Promise<CrudResult<any>>;
|
|
39
|
+
onSaved?(saved: TLocal, input: TRemote, isCreate: boolean): Partial<TLocal> | void;
|
|
40
|
+
transform?: SyncTransform<TLocal, TRemote>;
|
|
38
41
|
fieldUpdatedAt?: string;
|
|
42
|
+
fieldCreatedAt?: string;
|
|
39
43
|
updatePartial?: boolean;
|
|
44
|
+
changesSince?: 'all' | 'last-sync';
|
|
40
45
|
}
|
|
41
46
|
|
|
42
|
-
type
|
|
43
|
-
|
|
44
|
-
type InitialValue<T, TOption extends CrudAsOption> = TOption extends 'Map'
|
|
47
|
+
type InitialValue<T, TAsOption extends CrudAsOption> = TAsOption extends 'Map'
|
|
45
48
|
? Map<string, T>
|
|
46
|
-
:
|
|
49
|
+
: TAsOption extends 'object'
|
|
47
50
|
? Record<string, T>
|
|
48
|
-
:
|
|
51
|
+
: TAsOption extends 'first'
|
|
49
52
|
? T
|
|
50
53
|
: T[];
|
|
51
54
|
|
|
52
|
-
export type SyncedCrudReturnType<
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
: TGet[]
|
|
60
|
-
> & {};
|
|
55
|
+
export type SyncedCrudReturnType<TLocal, TAsOption extends CrudAsOption> = TAsOption extends 'Map'
|
|
56
|
+
? Map<string, TLocal>
|
|
57
|
+
: TAsOption extends 'object'
|
|
58
|
+
? Record<string, TLocal>
|
|
59
|
+
: TAsOption extends 'first'
|
|
60
|
+
? TLocal
|
|
61
|
+
: TLocal[];
|
|
61
62
|
|
|
62
63
|
let _asOption: CrudAsOption;
|
|
63
64
|
|
|
64
|
-
function transformOut<
|
|
65
|
+
function transformOut<T1, T2>(data: T1, transform: undefined | ((value: T1) => T2)) {
|
|
65
66
|
return transform ? transform(clone(data)) : data;
|
|
66
67
|
}
|
|
67
68
|
|
|
@@ -132,25 +133,24 @@ export function combineTransforms<T, T2>(
|
|
|
132
133
|
};
|
|
133
134
|
}
|
|
134
135
|
|
|
135
|
-
export function syncedCrud<
|
|
136
|
-
props: SyncedCrudPropsBase<
|
|
137
|
-
): SyncedCrudReturnType<
|
|
136
|
+
export function syncedCrud<TRemote extends { id: string | number }, TLocal = TRemote>(
|
|
137
|
+
props: SyncedCrudPropsBase<TRemote, TLocal> & SyncedCrudPropsSingle<TRemote, TLocal>,
|
|
138
|
+
): SyncedCrudReturnType<TLocal, 'first'>;
|
|
138
139
|
export function syncedCrud<
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
TOption extends CrudAsOption = 'object',
|
|
140
|
+
TRemote extends { id: string | number },
|
|
141
|
+
TLocal = TRemote,
|
|
142
|
+
TAsOption extends CrudAsOption = 'object',
|
|
143
143
|
>(
|
|
144
|
-
props: SyncedCrudPropsBase<
|
|
145
|
-
): SyncedCrudReturnType<
|
|
144
|
+
props: SyncedCrudPropsBase<TRemote, TLocal> & SyncedCrudPropsMany<TRemote, TLocal, TAsOption>,
|
|
145
|
+
): SyncedCrudReturnType<TLocal, Exclude<TAsOption, 'first'>>;
|
|
146
146
|
export function syncedCrud<
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
TOption extends CrudAsOption = 'object',
|
|
147
|
+
TRemote extends { id: string | number },
|
|
148
|
+
TLocal = TRemote,
|
|
149
|
+
TAsOption extends CrudAsOption = 'object',
|
|
151
150
|
>(
|
|
152
|
-
props: SyncedCrudPropsBase<
|
|
153
|
-
|
|
151
|
+
props: SyncedCrudPropsBase<TRemote, TLocal> &
|
|
152
|
+
(SyncedCrudPropsSingle<TRemote, TLocal> & SyncedCrudPropsMany<TRemote, TLocal, TAsOption>),
|
|
153
|
+
): SyncedCrudReturnType<TLocal, TAsOption> {
|
|
154
154
|
const {
|
|
155
155
|
get: getFn,
|
|
156
156
|
list: listFn,
|
|
@@ -158,38 +158,37 @@ export function syncedCrud<
|
|
|
158
158
|
update: updateFn,
|
|
159
159
|
delete: deleteFn,
|
|
160
160
|
transform,
|
|
161
|
+
fieldCreatedAt,
|
|
161
162
|
fieldUpdatedAt,
|
|
162
|
-
generateId,
|
|
163
163
|
updatePartial,
|
|
164
164
|
onSaved,
|
|
165
165
|
mode: modeParam,
|
|
166
|
+
changesSince,
|
|
166
167
|
...rest
|
|
167
168
|
} = props;
|
|
168
169
|
|
|
169
|
-
let asType = props.as;
|
|
170
|
+
let asType = props.as as TAsOption;
|
|
170
171
|
|
|
171
172
|
if (!asType) {
|
|
172
|
-
asType = (getFn ? 'first' : _asOption || '
|
|
173
|
+
asType = (getFn ? 'first' : _asOption || 'object') as CrudAsOption as TAsOption;
|
|
173
174
|
}
|
|
174
175
|
|
|
175
176
|
const asMap = asType === 'Map';
|
|
176
177
|
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
const get: undefined | ((params: SyncedGetParams) => Promise<TOut>) =
|
|
178
|
+
const get: undefined | ((params: SyncedGetParams) => Promise<TLocal>) =
|
|
180
179
|
getFn || listFn
|
|
181
180
|
? async (getParams: SyncedGetParams) => {
|
|
182
181
|
const { updateLastSync, lastSync } = getParams;
|
|
183
182
|
if (listFn) {
|
|
184
|
-
if (lastSync) {
|
|
185
|
-
getParams.mode =
|
|
186
|
-
modeParam || (asType === 'array' ? 'append' : asType === 'first' ? 'set' : 'assign');
|
|
183
|
+
if (changesSince === 'last-sync' && lastSync) {
|
|
184
|
+
getParams.mode = modeParam || (asType === 'first' ? 'set' : 'assign');
|
|
187
185
|
}
|
|
188
186
|
|
|
189
|
-
|
|
187
|
+
const data = (await listFn(getParams)) || [];
|
|
190
188
|
let newLastSync = 0;
|
|
191
189
|
for (let i = 0; i < data.length; i++) {
|
|
192
|
-
const updated =
|
|
190
|
+
const updated =
|
|
191
|
+
(data[i] as any)[fieldUpdatedAt as any] || (data[i] as any)[fieldCreatedAt as any];
|
|
193
192
|
if (updated) {
|
|
194
193
|
newLastSync = Math.max(newLastSync, +new Date(updated));
|
|
195
194
|
}
|
|
@@ -197,35 +196,36 @@ export function syncedCrud<
|
|
|
197
196
|
if (newLastSync && newLastSync !== lastSync) {
|
|
198
197
|
updateLastSync(newLastSync);
|
|
199
198
|
}
|
|
199
|
+
let transformed = data as unknown as TLocal[];
|
|
200
200
|
if (transform?.load) {
|
|
201
|
-
|
|
201
|
+
transformed = await Promise.all(data.map(transform.load));
|
|
202
202
|
}
|
|
203
203
|
if (asType === 'first') {
|
|
204
|
-
return
|
|
205
|
-
} else if (asType === 'array') {
|
|
206
|
-
return data;
|
|
204
|
+
return transformed.length > 0 ? transformed[0] : null;
|
|
207
205
|
} else {
|
|
208
206
|
const out: Record<string, any> = asMap ? new Map() : {};
|
|
209
|
-
|
|
207
|
+
transformed.forEach((result: any) => {
|
|
210
208
|
const value = result.__deleted ? internal.symbolDelete : result;
|
|
211
209
|
asMap ? (out as Map<any, any>).set(result.id, value) : (out[result.id] = value);
|
|
212
210
|
});
|
|
213
211
|
return out;
|
|
214
212
|
}
|
|
215
213
|
} else if (getFn) {
|
|
216
|
-
|
|
214
|
+
const data = await getFn(getParams);
|
|
217
215
|
|
|
216
|
+
let transformed = data as unknown as TLocal;
|
|
218
217
|
if (data) {
|
|
219
|
-
const newLastSync =
|
|
218
|
+
const newLastSync =
|
|
219
|
+
(data as any)[fieldUpdatedAt as any] || (data as any)[fieldCreatedAt as any];
|
|
220
220
|
if (newLastSync && newLastSync !== lastSync) {
|
|
221
221
|
updateLastSync(newLastSync);
|
|
222
222
|
}
|
|
223
223
|
if (transform?.load) {
|
|
224
|
-
|
|
224
|
+
transformed = await transform.load(data);
|
|
225
225
|
}
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
-
return
|
|
228
|
+
return transformed as any;
|
|
229
229
|
}
|
|
230
230
|
}
|
|
231
231
|
: undefined;
|
|
@@ -234,27 +234,30 @@ export function syncedCrud<
|
|
|
234
234
|
createFn || updateFn || deleteFn
|
|
235
235
|
? async (params: SyncedSetParams<any> & { retryAsCreate?: boolean }) => {
|
|
236
236
|
const { value, changes, update, retryAsCreate, valuePrevious } = params;
|
|
237
|
-
const creates = new Map<string,
|
|
237
|
+
const creates = new Map<string, TLocal>();
|
|
238
238
|
const updates = new Map<string, object>();
|
|
239
239
|
const deletes = new Map<string, object>();
|
|
240
240
|
|
|
241
241
|
changes.forEach(({ path, prevAtPath, valueAtPath }) => {
|
|
242
242
|
if (asType === 'first') {
|
|
243
243
|
if (value) {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
244
|
+
const id = value?.id;
|
|
245
|
+
if (id) {
|
|
246
|
+
const isCreate = fieldCreatedAt ? !value[fieldCreatedAt!] : !prevAtPath;
|
|
247
|
+
if (isCreate || retryAsCreate) {
|
|
248
|
+
creates.set(id, value);
|
|
249
|
+
} else if (path.length === 0) {
|
|
250
|
+
if (valueAtPath) {
|
|
251
|
+
updates.set(id, valueAtPath);
|
|
252
|
+
} else if (prevAtPath) {
|
|
253
|
+
deletes.set(prevAtPath?.id, prevAtPath);
|
|
254
|
+
}
|
|
255
|
+
} else {
|
|
256
|
+
const key = path[0];
|
|
257
|
+
updates.set(id, Object.assign(updates.get(id) || { id }, { [key]: value[key] }));
|
|
254
258
|
}
|
|
255
259
|
} else {
|
|
256
|
-
|
|
257
|
-
updates.set(id, Object.assign(updates.get(id) || { id }, { [key]: value[key] }));
|
|
260
|
+
console.error('[legend-state]: added item without an id');
|
|
258
261
|
}
|
|
259
262
|
} else if (path.length === 0) {
|
|
260
263
|
const id = prevAtPath?.id;
|
|
@@ -267,7 +270,7 @@ export function syncedCrud<
|
|
|
267
270
|
let isCreateGuess: boolean;
|
|
268
271
|
if (path.length === 0) {
|
|
269
272
|
isCreateGuess =
|
|
270
|
-
!fieldUpdatedAt &&
|
|
273
|
+
!(fieldCreatedAt || fieldUpdatedAt) &&
|
|
271
274
|
!(
|
|
272
275
|
(asMap
|
|
273
276
|
? Array.from((valueAtPath as Map<any, any>).values())
|
|
@@ -284,7 +287,7 @@ export function syncedCrud<
|
|
|
284
287
|
} else {
|
|
285
288
|
const itemKey = path[0];
|
|
286
289
|
const itemValue = asMap ? value.get(itemKey) : value[itemKey];
|
|
287
|
-
isCreateGuess = !fieldUpdatedAt && path.length === 1 && !prevAtPath;
|
|
290
|
+
isCreateGuess = !(fieldCreatedAt || fieldUpdatedAt) && path.length === 1 && !prevAtPath;
|
|
288
291
|
if (!itemValue) {
|
|
289
292
|
if (path.length === 1 && prevAtPath) {
|
|
290
293
|
if (deleteFn) {
|
|
@@ -298,8 +301,11 @@ export function syncedCrud<
|
|
|
298
301
|
}
|
|
299
302
|
}
|
|
300
303
|
itemsChanged?.forEach((item) => {
|
|
301
|
-
|
|
302
|
-
|
|
304
|
+
const isCreate = fieldCreatedAt
|
|
305
|
+
? !item[fieldCreatedAt!]
|
|
306
|
+
: fieldUpdatedAt
|
|
307
|
+
? !item[fieldUpdatedAt]
|
|
308
|
+
: isCreateGuess;
|
|
303
309
|
if (isCreate) {
|
|
304
310
|
if (createFn) {
|
|
305
311
|
creates.set(item.id, item);
|
|
@@ -319,39 +325,43 @@ export function syncedCrud<
|
|
|
319
325
|
|
|
320
326
|
const saveResult = async (
|
|
321
327
|
itemKey: string,
|
|
322
|
-
input:
|
|
323
|
-
data: CrudResult<
|
|
328
|
+
input: TRemote,
|
|
329
|
+
data: CrudResult<TRemote>,
|
|
324
330
|
isCreate: boolean,
|
|
325
331
|
) => {
|
|
326
332
|
if (data && onSaved) {
|
|
327
|
-
const dataLoaded:
|
|
333
|
+
const dataLoaded: TLocal = (transform?.load ? transform.load(data as any) : data) as any;
|
|
328
334
|
|
|
329
335
|
const savedOut = onSaved(dataLoaded, input, isCreate);
|
|
330
336
|
|
|
331
|
-
|
|
337
|
+
if (savedOut) {
|
|
338
|
+
const createdAt = fieldCreatedAt ? savedOut[fieldCreatedAt as keyof TLocal] : undefined;
|
|
339
|
+
const updatedAt = fieldUpdatedAt ? savedOut[fieldUpdatedAt as keyof TLocal] : undefined;
|
|
332
340
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
341
|
+
const value =
|
|
342
|
+
itemKey !== 'undefined' && asType !== 'first' ? { [itemKey]: savedOut } : savedOut;
|
|
343
|
+
update({
|
|
344
|
+
value,
|
|
345
|
+
lastSync:
|
|
346
|
+
updatedAt || createdAt ? +new Date(updatedAt || (createdAt as any)) : undefined,
|
|
347
|
+
mode: 'merge',
|
|
348
|
+
});
|
|
349
|
+
}
|
|
340
350
|
}
|
|
341
351
|
};
|
|
342
352
|
|
|
343
353
|
return Promise.all([
|
|
344
354
|
...Array.from(creates).map(([itemKey, itemValue]) => {
|
|
345
|
-
const createObj = transformOut(itemValue, transform?.save as
|
|
355
|
+
const createObj = transformOut(itemValue, transform?.save) as TRemote;
|
|
346
356
|
return createFn!(createObj, params).then((result) =>
|
|
347
|
-
saveResult(itemKey, createObj
|
|
357
|
+
saveResult(itemKey, createObj, result as any, true),
|
|
348
358
|
);
|
|
349
359
|
}),
|
|
350
360
|
...Array.from(updates).map(([itemKey, itemValue]) => {
|
|
351
361
|
const toSave = updatePartial
|
|
352
362
|
? diffObjects(asType === 'first' ? valuePrevious : valuePrevious[itemKey], itemValue)
|
|
353
363
|
: itemValue;
|
|
354
|
-
const changed = transformOut(toSave as
|
|
364
|
+
const changed = transformOut(toSave as TLocal, transform?.save) as TRemote;
|
|
355
365
|
|
|
356
366
|
if (Object.keys(changed).length > 0) {
|
|
357
367
|
return updateFn!(changed, params).then((result) =>
|
|
@@ -360,7 +370,7 @@ export function syncedCrud<
|
|
|
360
370
|
}
|
|
361
371
|
}),
|
|
362
372
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
363
|
-
...Array.from(deletes).map(([_, itemValue]) => deleteFn!(itemValue as
|
|
373
|
+
...Array.from(deletes).map(([_, itemValue]) => deleteFn!(itemValue as TRemote, params)),
|
|
364
374
|
]);
|
|
365
375
|
}
|
|
366
376
|
: undefined;
|
|
@@ -368,6 +378,7 @@ export function syncedCrud<
|
|
|
368
378
|
return synced<any>({
|
|
369
379
|
set,
|
|
370
380
|
get,
|
|
381
|
+
mode: modeParam,
|
|
371
382
|
...rest,
|
|
372
383
|
});
|
|
373
384
|
}
|