@legendapp/state 3.0.0-alpha.3 → 3.0.0-alpha.30

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.
Files changed (78) hide show
  1. package/CHANGELOG.md +831 -1
  2. package/LICENSE +21 -1
  3. package/README.md +141 -1
  4. package/babel.js +0 -2
  5. package/babel.mjs +0 -2
  6. package/config/enable$GetSet.js +2 -1
  7. package/config/enable$GetSet.mjs +2 -1
  8. package/config/enableReactTracking.js +2 -1
  9. package/config/enableReactTracking.mjs +2 -1
  10. package/config/enableReactUse.js +2 -1
  11. package/config/enableReactUse.mjs +2 -1
  12. package/config/enable_PeekAssign.js +2 -1
  13. package/config/enable_PeekAssign.mjs +2 -1
  14. package/config.d.mts +13 -0
  15. package/config.d.ts +13 -0
  16. package/config.js +2052 -0
  17. package/config.mjs +2050 -0
  18. package/helpers/trackHistory.js +2 -2
  19. package/helpers/trackHistory.mjs +2 -2
  20. package/index.d.mts +21 -302
  21. package/index.d.ts +21 -302
  22. package/index.js +274 -318
  23. package/index.mjs +275 -317
  24. package/observableInterfaces-Dilj6F92.d.mts +282 -0
  25. package/observableInterfaces-Dilj6F92.d.ts +282 -0
  26. package/package.json +11 -1
  27. package/persist-plugins/async-storage.d.mts +6 -3
  28. package/persist-plugins/async-storage.d.ts +6 -3
  29. package/persist-plugins/async-storage.js +12 -4
  30. package/persist-plugins/async-storage.mjs +12 -5
  31. package/persist-plugins/indexeddb.d.mts +6 -4
  32. package/persist-plugins/indexeddb.d.ts +6 -4
  33. package/persist-plugins/indexeddb.js +16 -6
  34. package/persist-plugins/indexeddb.mjs +16 -7
  35. package/persist-plugins/mmkv.d.mts +5 -1
  36. package/persist-plugins/mmkv.d.ts +5 -1
  37. package/persist-plugins/mmkv.js +14 -5
  38. package/persist-plugins/mmkv.mjs +14 -6
  39. package/react.d.mts +18 -14
  40. package/react.d.ts +18 -14
  41. package/react.js +57 -32
  42. package/react.mjs +58 -33
  43. package/sync-plugins/_transformObjectFields.d.mts +31 -0
  44. package/sync-plugins/_transformObjectFields.d.ts +31 -0
  45. package/sync-plugins/_transformObjectFields.js +114 -0
  46. package/sync-plugins/_transformObjectFields.mjs +110 -0
  47. package/sync-plugins/crud.d.mts +15 -23
  48. package/sync-plugins/crud.d.ts +15 -23
  49. package/sync-plugins/crud.js +213 -134
  50. package/sync-plugins/crud.mjs +214 -135
  51. package/sync-plugins/fetch.js +12 -8
  52. package/sync-plugins/fetch.mjs +13 -9
  53. package/sync-plugins/firebase.d.mts +26 -0
  54. package/sync-plugins/firebase.d.ts +26 -0
  55. package/sync-plugins/firebase.js +373 -0
  56. package/sync-plugins/firebase.mjs +368 -0
  57. package/sync-plugins/keel.d.mts +27 -10
  58. package/sync-plugins/keel.d.ts +27 -10
  59. package/sync-plugins/keel.js +40 -21
  60. package/sync-plugins/keel.mjs +40 -21
  61. package/sync-plugins/supabase.d.mts +12 -7
  62. package/sync-plugins/supabase.d.ts +12 -7
  63. package/sync-plugins/supabase.js +24 -13
  64. package/sync-plugins/supabase.mjs +25 -14
  65. package/sync-plugins/tanstack-query.d.mts +2 -2
  66. package/sync-plugins/tanstack-query.d.ts +2 -2
  67. package/sync-plugins/tanstack-query.js +3 -2
  68. package/sync-plugins/tanstack-query.mjs +3 -2
  69. package/sync-plugins/tanstack-react-query.d.mts +1 -1
  70. package/sync-plugins/tanstack-react-query.d.ts +1 -1
  71. package/sync.d.mts +68 -197
  72. package/sync.d.ts +68 -197
  73. package/sync.js +448 -283
  74. package/sync.mjs +454 -289
  75. package/types/babel.d.ts +12 -1
  76. package/.DS_Store +0 -0
  77. /package/config/{enable_GetSet.d.mts → enable$GetSet.d.mts} +0 -0
  78. /package/config/{enable_GetSet.d.ts → enable$GetSet.d.ts} +0 -0
@@ -1,42 +1,38 @@
1
- import { SyncedGetParams, SyncedOptions, SyncedSetParams } from '@legendapp/state/sync';
1
+ import { SyncedGetParams, SyncedOptions, SyncedSetParams, SyncedSubscribeParams } from '@legendapp/state/sync';
2
2
 
3
3
  type CrudAsOption = 'Map' | 'object' | 'value' | 'array';
4
4
  type CrudResult<T> = T;
5
- interface SyncedCrudPropsSingle<TRemote, TLocal> {
6
- get?: (params: SyncedGetParams) => Promise<CrudResult<TRemote | null>> | CrudResult<TRemote | null>;
5
+ interface SyncedCrudPropsSingle<TRemote extends object, TLocal> {
6
+ get?: (params: SyncedGetParams<TRemote>) => Promise<CrudResult<TRemote | null>> | CrudResult<TRemote | null>;
7
7
  initial?: InitialValue<TLocal, 'value'>;
8
8
  as?: never | 'value';
9
9
  }
10
- interface SyncedCrudPropsMany<TRemote, TLocal, TAsOption extends CrudAsOption> {
11
- list?: (params: SyncedGetParams) => Promise<CrudResult<TRemote[] | null>> | CrudResult<TRemote[] | null>;
10
+ interface SyncedCrudPropsMany<TRemote extends object, TLocal, TAsOption extends CrudAsOption> {
11
+ list?: (params: SyncedGetParams<TRemote>) => Promise<CrudResult<TRemote[] | null>> | CrudResult<TRemote[] | null>;
12
12
  as?: TAsOption;
13
13
  initial?: InitialValue<TLocal, TAsOption>;
14
14
  }
15
- interface SyncedCrudOnSavedParams<TRemote extends {
16
- id: string | number;
17
- }, TLocal> {
15
+ interface SyncedCrudOnSavedParams<TRemote extends object, TLocal> {
18
16
  saved: TLocal;
19
17
  input: TRemote;
20
18
  currentValue: TLocal;
21
19
  isCreate: boolean;
22
20
  props: SyncedCrudPropsBase<TRemote, TLocal>;
23
21
  }
24
- interface SyncedCrudPropsBase<TRemote extends {
25
- id: string | number;
26
- }, TLocal = TRemote> extends Omit<SyncedOptions<TRemote, TLocal>, 'get' | 'set' | 'initial'> {
27
- create?(input: TRemote, params: SyncedSetParams<TRemote>): Promise<CrudResult<TRemote> | null | undefined>;
28
- update?(input: Partial<TRemote>, params: SyncedSetParams<TRemote>): Promise<CrudResult<Partial<TRemote> | null | undefined>>;
29
- delete?(input: {
30
- id: TRemote['id'];
31
- }, params: SyncedSetParams<TRemote>): Promise<CrudResult<any>>;
22
+ interface SyncedCrudPropsBase<TRemote extends object, TLocal = TRemote> extends Omit<SyncedOptions<TRemote, TLocal>, 'get' | 'set' | 'initial' | 'subscribe'> {
23
+ create?(input: TRemote, params: SyncedSetParams<TRemote>): Promise<CrudResult<TRemote> | null | undefined | void>;
24
+ update?(input: Partial<TRemote>, params: SyncedSetParams<TRemote>): Promise<CrudResult<Partial<TRemote> | null | undefined | void>>;
25
+ delete?(input: TRemote, params: SyncedSetParams<TRemote>): Promise<any>;
32
26
  onSaved?(params: SyncedCrudOnSavedParams<TRemote, TLocal>): Partial<TLocal> | void;
33
- onSavedUpdate?: 'createdUpdatedAt';
27
+ fieldId?: string;
34
28
  fieldUpdatedAt?: string;
35
29
  fieldCreatedAt?: string;
36
30
  fieldDeleted?: string;
31
+ fieldDeletedList?: string;
37
32
  updatePartial?: boolean;
38
33
  changesSince?: 'all' | 'last-sync';
39
34
  generateId?: () => string | number;
35
+ subscribe?: (params: SyncedSubscribeParams<TRemote[]>) => (() => void) | void;
40
36
  }
41
37
  type InitialValue<TLocal, TAsOption extends CrudAsOption> = TAsOption extends 'Map' ? Map<string | number, TLocal> : TAsOption extends 'object' ? Record<string | number, TLocal> : TAsOption extends 'value' ? TLocal : TLocal[];
42
38
  type SyncedCrudReturnType<TLocal, TAsOption extends CrudAsOption> = TAsOption extends 'Map' ? Map<TLocal extends {
@@ -44,11 +40,7 @@ type SyncedCrudReturnType<TLocal, TAsOption extends CrudAsOption> = TAsOption ex
44
40
  } ? number : string, TLocal> : TAsOption extends 'object' ? Record<TLocal extends {
45
41
  id: number;
46
42
  } ? number : string, TLocal> : TAsOption extends 'value' ? TLocal : TLocal[];
47
- declare function syncedCrud<TRemote extends {
48
- id: string | number;
49
- }, TLocal = TRemote>(props: SyncedCrudPropsBase<TRemote, TLocal> & SyncedCrudPropsSingle<TRemote, TLocal>): SyncedCrudReturnType<TLocal, 'value'>;
50
- declare function syncedCrud<TRemote extends {
51
- id: string | number;
52
- }, TLocal = TRemote, TAsOption extends CrudAsOption = 'object'>(props: SyncedCrudPropsBase<TRemote, TLocal> & SyncedCrudPropsMany<TRemote, TLocal, TAsOption>): SyncedCrudReturnType<TLocal, Exclude<TAsOption, 'value'>>;
43
+ declare function syncedCrud<TRemote extends object, TLocal = TRemote>(props: SyncedCrudPropsBase<TRemote, TLocal> & SyncedCrudPropsSingle<TRemote, TLocal>): SyncedCrudReturnType<TLocal, 'value'>;
44
+ declare function syncedCrud<TRemote extends object, TLocal = TRemote, TAsOption extends CrudAsOption = 'object'>(props: SyncedCrudPropsBase<TRemote, TLocal> & SyncedCrudPropsMany<TRemote, TLocal, TAsOption>): SyncedCrudReturnType<TLocal, Exclude<TAsOption, 'value'>>;
53
45
 
54
46
  export { type CrudAsOption, type CrudResult, type SyncedCrudOnSavedParams, type SyncedCrudPropsBase, type SyncedCrudPropsMany, type SyncedCrudPropsSingle, type SyncedCrudReturnType, syncedCrud };
@@ -1,42 +1,38 @@
1
- import { SyncedGetParams, SyncedOptions, SyncedSetParams } from '@legendapp/state/sync';
1
+ import { SyncedGetParams, SyncedOptions, SyncedSetParams, SyncedSubscribeParams } from '@legendapp/state/sync';
2
2
 
3
3
  type CrudAsOption = 'Map' | 'object' | 'value' | 'array';
4
4
  type CrudResult<T> = T;
5
- interface SyncedCrudPropsSingle<TRemote, TLocal> {
6
- get?: (params: SyncedGetParams) => Promise<CrudResult<TRemote | null>> | CrudResult<TRemote | null>;
5
+ interface SyncedCrudPropsSingle<TRemote extends object, TLocal> {
6
+ get?: (params: SyncedGetParams<TRemote>) => Promise<CrudResult<TRemote | null>> | CrudResult<TRemote | null>;
7
7
  initial?: InitialValue<TLocal, 'value'>;
8
8
  as?: never | 'value';
9
9
  }
10
- interface SyncedCrudPropsMany<TRemote, TLocal, TAsOption extends CrudAsOption> {
11
- list?: (params: SyncedGetParams) => Promise<CrudResult<TRemote[] | null>> | CrudResult<TRemote[] | null>;
10
+ interface SyncedCrudPropsMany<TRemote extends object, TLocal, TAsOption extends CrudAsOption> {
11
+ list?: (params: SyncedGetParams<TRemote>) => Promise<CrudResult<TRemote[] | null>> | CrudResult<TRemote[] | null>;
12
12
  as?: TAsOption;
13
13
  initial?: InitialValue<TLocal, TAsOption>;
14
14
  }
15
- interface SyncedCrudOnSavedParams<TRemote extends {
16
- id: string | number;
17
- }, TLocal> {
15
+ interface SyncedCrudOnSavedParams<TRemote extends object, TLocal> {
18
16
  saved: TLocal;
19
17
  input: TRemote;
20
18
  currentValue: TLocal;
21
19
  isCreate: boolean;
22
20
  props: SyncedCrudPropsBase<TRemote, TLocal>;
23
21
  }
24
- interface SyncedCrudPropsBase<TRemote extends {
25
- id: string | number;
26
- }, TLocal = TRemote> extends Omit<SyncedOptions<TRemote, TLocal>, 'get' | 'set' | 'initial'> {
27
- create?(input: TRemote, params: SyncedSetParams<TRemote>): Promise<CrudResult<TRemote> | null | undefined>;
28
- update?(input: Partial<TRemote>, params: SyncedSetParams<TRemote>): Promise<CrudResult<Partial<TRemote> | null | undefined>>;
29
- delete?(input: {
30
- id: TRemote['id'];
31
- }, params: SyncedSetParams<TRemote>): Promise<CrudResult<any>>;
22
+ interface SyncedCrudPropsBase<TRemote extends object, TLocal = TRemote> extends Omit<SyncedOptions<TRemote, TLocal>, 'get' | 'set' | 'initial' | 'subscribe'> {
23
+ create?(input: TRemote, params: SyncedSetParams<TRemote>): Promise<CrudResult<TRemote> | null | undefined | void>;
24
+ update?(input: Partial<TRemote>, params: SyncedSetParams<TRemote>): Promise<CrudResult<Partial<TRemote> | null | undefined | void>>;
25
+ delete?(input: TRemote, params: SyncedSetParams<TRemote>): Promise<any>;
32
26
  onSaved?(params: SyncedCrudOnSavedParams<TRemote, TLocal>): Partial<TLocal> | void;
33
- onSavedUpdate?: 'createdUpdatedAt';
27
+ fieldId?: string;
34
28
  fieldUpdatedAt?: string;
35
29
  fieldCreatedAt?: string;
36
30
  fieldDeleted?: string;
31
+ fieldDeletedList?: string;
37
32
  updatePartial?: boolean;
38
33
  changesSince?: 'all' | 'last-sync';
39
34
  generateId?: () => string | number;
35
+ subscribe?: (params: SyncedSubscribeParams<TRemote[]>) => (() => void) | void;
40
36
  }
41
37
  type InitialValue<TLocal, TAsOption extends CrudAsOption> = TAsOption extends 'Map' ? Map<string | number, TLocal> : TAsOption extends 'object' ? Record<string | number, TLocal> : TAsOption extends 'value' ? TLocal : TLocal[];
42
38
  type SyncedCrudReturnType<TLocal, TAsOption extends CrudAsOption> = TAsOption extends 'Map' ? Map<TLocal extends {
@@ -44,11 +40,7 @@ type SyncedCrudReturnType<TLocal, TAsOption extends CrudAsOption> = TAsOption ex
44
40
  } ? number : string, TLocal> : TAsOption extends 'object' ? Record<TLocal extends {
45
41
  id: number;
46
42
  } ? number : string, TLocal> : TAsOption extends 'value' ? TLocal : TLocal[];
47
- declare function syncedCrud<TRemote extends {
48
- id: string | number;
49
- }, TLocal = TRemote>(props: SyncedCrudPropsBase<TRemote, TLocal> & SyncedCrudPropsSingle<TRemote, TLocal>): SyncedCrudReturnType<TLocal, 'value'>;
50
- declare function syncedCrud<TRemote extends {
51
- id: string | number;
52
- }, TLocal = TRemote, TAsOption extends CrudAsOption = 'object'>(props: SyncedCrudPropsBase<TRemote, TLocal> & SyncedCrudPropsMany<TRemote, TLocal, TAsOption>): SyncedCrudReturnType<TLocal, Exclude<TAsOption, 'value'>>;
43
+ declare function syncedCrud<TRemote extends object, TLocal = TRemote>(props: SyncedCrudPropsBase<TRemote, TLocal> & SyncedCrudPropsSingle<TRemote, TLocal>): SyncedCrudReturnType<TLocal, 'value'>;
44
+ declare function syncedCrud<TRemote extends object, TLocal = TRemote, TAsOption extends CrudAsOption = 'object'>(props: SyncedCrudPropsBase<TRemote, TLocal> & SyncedCrudPropsMany<TRemote, TLocal, TAsOption>): SyncedCrudReturnType<TLocal, Exclude<TAsOption, 'value'>>;
53
45
 
54
46
  export { type CrudAsOption, type CrudResult, type SyncedCrudOnSavedParams, type SyncedCrudPropsBase, type SyncedCrudPropsMany, type SyncedCrudPropsSingle, type SyncedCrudReturnType, syncedCrud };
@@ -8,30 +8,21 @@ var { clone } = state.internal;
8
8
  function transformOut(data, transform) {
9
9
  return transform ? transform(clone(data)) : data;
10
10
  }
11
- function ensureId(obj, generateId) {
12
- if (!obj.id) {
13
- obj.id = generateId();
11
+ function ensureId(obj, fieldId, generateId) {
12
+ if (!obj[fieldId]) {
13
+ obj[fieldId] = generateId();
14
14
  }
15
- return obj.id;
15
+ return obj[fieldId];
16
16
  }
17
- function onSavedCreatedUpdatedAt(mode, { saved, currentValue, isCreate, props }) {
18
- const savedOut = {};
19
- if (isCreate) {
20
- Object.keys(saved).forEach((key) => {
21
- if (state.isNullOrUndefined(currentValue[key])) {
22
- savedOut[key] = saved[key];
23
- }
24
- });
25
- } else if (mode === "createdUpdatedAt") {
26
- Object.keys(saved).forEach((key) => {
27
- const k = key;
28
- const keyLower = key.toLowerCase();
29
- if ((key === props.fieldCreatedAt || key === props.fieldUpdatedAt || keyLower.endsWith("createdat") || keyLower.endsWith("updatedat") || keyLower.endsWith("created_at") || keyLower.endsWith("updated_at")) && saved[k] instanceof Date) {
30
- savedOut[k] = saved[k];
31
- }
32
- });
17
+ function computeLastSync(data, fieldUpdatedAt, fieldCreatedAt) {
18
+ let newLastSync = 0;
19
+ for (let i = 0; i < data.length; i++) {
20
+ const updated = (fieldUpdatedAt ? data[i][fieldUpdatedAt] : 0) || (fieldCreatedAt ? data[i][fieldCreatedAt] : 0);
21
+ if (updated) {
22
+ newLastSync = Math.max(newLastSync, +new Date(updated));
23
+ }
33
24
  }
34
- return savedOut;
25
+ return newLastSync;
35
26
  }
36
27
  function syncedCrud(props) {
37
28
  const {
@@ -41,165 +32,220 @@ function syncedCrud(props) {
41
32
  update: updateFn,
42
33
  delete: deleteFn,
43
34
  transform,
35
+ fieldId: fieldIdProp,
44
36
  fieldCreatedAt,
45
37
  fieldUpdatedAt,
46
38
  fieldDeleted,
39
+ fieldDeletedList,
47
40
  updatePartial,
41
+ subscribe: subscribeProp,
48
42
  onSaved,
49
- onSavedUpdate,
50
43
  mode: modeParam,
51
44
  changesSince,
52
45
  generateId,
53
46
  ...rest
54
47
  } = props;
48
+ const fieldId = fieldIdProp || "id";
55
49
  let asType = props.as;
56
50
  if (!asType) {
57
51
  asType = getFn ? "value" : "object";
58
52
  }
59
53
  const asMap = asType === "Map";
60
54
  const asArray = asType === "array";
61
- const get = getFn || listFn ? async (getParams) => {
55
+ const resultsToOutType = (results) => {
56
+ if (asType === "value") {
57
+ return results[0];
58
+ }
59
+ const out = asType === "array" ? [] : asMap ? /* @__PURE__ */ new Map() : {};
60
+ for (let i = 0; i < results.length; i++) {
61
+ let result = results[i];
62
+ const isObs = state.isObservable(result);
63
+ const value = isObs ? result.peek() : result;
64
+ if (value) {
65
+ result = !isObs && (fieldDeleted && result[fieldDeleted] || fieldDeletedList && result[fieldDeletedList] || result[state.symbolDelete]) ? state.internal.symbolDelete : result;
66
+ if (asArray) {
67
+ out.push(result);
68
+ } else if (asMap) {
69
+ out.set(value[fieldId], result);
70
+ } else {
71
+ out[value[fieldId]] = result;
72
+ }
73
+ }
74
+ }
75
+ return out;
76
+ };
77
+ const transformRows = (data) => {
78
+ return Promise.all(
79
+ data.map(
80
+ (value) => (
81
+ // Skip transforming any children with symbolDelete or fieldDeleted because they'll get deleted by resultsToOutType
82
+ value[state.symbolDelete] || fieldDeleted && value[fieldDeleted] || fieldDeletedList && value[fieldDeletedList] ? value : transform.load(value, "get")
83
+ )
84
+ )
85
+ );
86
+ };
87
+ const get = getFn || listFn ? (getParams) => {
62
88
  const { updateLastSync, lastSync, value } = getParams;
63
89
  if (listFn) {
64
90
  const isLastSyncMode = changesSince === "last-sync";
65
91
  if (isLastSyncMode && lastSync) {
66
92
  getParams.mode = modeParam || (asType === "array" ? "append" : asType === "value" ? "set" : "assign");
67
93
  }
68
- const data = await listFn(getParams) || [];
69
- let newLastSync = 0;
70
- for (let i = 0; i < data.length; i++) {
71
- const updated = data[i][fieldUpdatedAt] || data[i][fieldCreatedAt];
72
- if (updated) {
73
- newLastSync = Math.max(newLastSync, +new Date(updated));
94
+ const listPromise = listFn(getParams);
95
+ const toOut = (transformed) => {
96
+ var _a;
97
+ if (asType === "value") {
98
+ return transformed.length > 0 ? transformed[0] : (_a = (isLastSyncMode && lastSync || fieldDeleted) && value) != null ? _a : null;
99
+ } else {
100
+ return resultsToOutType(transformed);
74
101
  }
75
- }
76
- if (newLastSync && newLastSync !== lastSync) {
77
- updateLastSync(newLastSync);
78
- }
79
- let transformed = data;
80
- if (transform == null ? void 0 : transform.load) {
81
- transformed = await Promise.all(data.map((value2) => transform.load(value2, "get")));
82
- }
83
- if (asType === "value") {
84
- return transformed.length > 0 ? transformed[0] : isLastSyncMode && lastSync && value || null;
85
- } else {
86
- const results = transformed.map(
87
- (result) => result[fieldDeleted] || result.__deleted ? state.internal.symbolDelete : result
88
- );
89
- const out = asType === "array" ? [] : asMap ? /* @__PURE__ */ new Map() : {};
90
- for (let i = 0; i < results.length; i++) {
91
- let result = results[i];
92
- result = result[fieldDeleted] || result.__deleted ? state.internal.symbolDelete : result;
93
- if (asArray) {
94
- out.push(result);
95
- } else if (asMap) {
96
- out.set(result.id, result);
97
- } else {
98
- out[result.id] = result;
102
+ };
103
+ const processResults = (data) => {
104
+ data || (data = []);
105
+ if (fieldUpdatedAt) {
106
+ const newLastSync = computeLastSync(data, fieldUpdatedAt, fieldCreatedAt);
107
+ if (newLastSync && newLastSync !== lastSync) {
108
+ updateLastSync(newLastSync);
99
109
  }
100
110
  }
101
- return out;
102
- }
103
- } else if (getFn) {
104
- const data = await getFn(getParams);
105
- let transformed = data;
106
- if (data) {
107
- const newLastSync = data[fieldUpdatedAt] || data[fieldCreatedAt];
108
- if (newLastSync && newLastSync !== lastSync) {
109
- updateLastSync(newLastSync);
110
- }
111
+ let transformed = data;
111
112
  if (transform == null ? void 0 : transform.load) {
112
- transformed = await transform.load(data, "get");
113
+ transformed = transformRows(data);
113
114
  }
114
- }
115
- return transformed;
115
+ return state.isPromise(transformed) ? transformed.then(toOut) : toOut(transformed);
116
+ };
117
+ return state.isPromise(listPromise) ? listPromise.then(processResults) : processResults(listPromise);
118
+ } else if (getFn) {
119
+ const dataPromise = getFn(getParams);
120
+ const processData = (data) => {
121
+ let transformed = data;
122
+ if (data) {
123
+ const newLastSync = data[fieldUpdatedAt] || data[fieldCreatedAt];
124
+ if (newLastSync && newLastSync !== lastSync) {
125
+ updateLastSync(newLastSync);
126
+ }
127
+ if (transform == null ? void 0 : transform.load) {
128
+ transformed = transform.load(data, "get");
129
+ }
130
+ }
131
+ return transformed;
132
+ };
133
+ return state.isPromise(dataPromise) ? dataPromise.then(processData) : processData(dataPromise);
116
134
  }
117
135
  } : void 0;
118
136
  const set = createFn || updateFn || deleteFn ? async (params) => {
119
- const { value, changes, update, retryAsCreate, valuePrevious, node } = params;
137
+ const { value, changes, update, retryAsCreate, node } = params;
120
138
  const creates = /* @__PURE__ */ new Map();
121
139
  const updates = /* @__PURE__ */ new Map();
122
140
  const deletes = /* @__PURE__ */ new Set();
123
- changes.forEach(({ path, prevAtPath, valueAtPath }) => {
141
+ const getUpdateValue = (itemValue, prev) => {
142
+ return updatePartial ? Object.assign(
143
+ sync.diffObjects(
144
+ prev,
145
+ itemValue,
146
+ /*deep*/
147
+ true
148
+ ),
149
+ itemValue[fieldId] ? { [fieldId]: itemValue[fieldId] } : {}
150
+ ) : itemValue;
151
+ };
152
+ changes.forEach((change) => {
153
+ const { path, prevAtPath, valueAtPath, pathTypes } = change;
124
154
  if (asType === "value") {
125
155
  if (value) {
126
- let id = value == null ? void 0 : value.id;
156
+ let id = value == null ? void 0 : value[fieldId];
127
157
  const isCreate = fieldCreatedAt ? !value[fieldCreatedAt] : !prevAtPath;
128
158
  if (!id && generateId) {
129
- id = ensureId(value, generateId);
159
+ id = ensureId(value, fieldId, generateId);
130
160
  }
131
161
  if (id) {
132
162
  if (isCreate || retryAsCreate) {
133
163
  creates.set(id, value);
134
164
  } else if (path.length === 0) {
135
165
  if (valueAtPath) {
136
- updates.set(id, valueAtPath);
166
+ updates.set(id, getUpdateValue(valueAtPath, prevAtPath));
137
167
  } else if (prevAtPath) {
138
- deletes.add(prevAtPath == null ? void 0 : prevAtPath.id);
168
+ deletes.add(prevAtPath);
139
169
  }
140
- } else {
141
- updates.set(id, Object.assign(updates.get(id) || { id }, value));
170
+ } else if (!updates.has(id)) {
171
+ const previous = state.applyChanges(
172
+ clone(value),
173
+ changes,
174
+ /*applyPrevious*/
175
+ true
176
+ );
177
+ updates.set(id, getUpdateValue(value, previous));
142
178
  }
143
179
  } else {
144
180
  console.error("[legend-state]: added synced item without an id");
145
181
  }
146
182
  } else if (path.length === 0) {
147
- const id = prevAtPath == null ? void 0 : prevAtPath.id;
148
- if (id) {
149
- deletes.add(id);
150
- }
183
+ deletes.add(prevAtPath);
151
184
  }
152
185
  } else {
153
- let itemsChanged = void 0;
186
+ let itemsChanged = [];
154
187
  if (path.length === 0) {
155
- itemsChanged = (asMap ? Array.from(valueAtPath.entries()) : Object.entries(valueAtPath)).filter(([key, value2]) => {
188
+ const changed = asMap ? Array.from(valueAtPath.entries()) : Object.entries(valueAtPath);
189
+ for (let i = 0; i < changed.length; i++) {
190
+ const [key, value2] = changed[i];
156
191
  const prev = asMap ? prevAtPath.get(key) : prevAtPath[key];
157
- const isDiff = !prevAtPath || !sync.deepEqual(value2, prev);
158
- return isDiff;
159
- });
192
+ if (state.isNullOrUndefined(value2) && !state.isNullOrUndefined(prev)) {
193
+ deletes.add(prev);
194
+ return false;
195
+ } else {
196
+ const isDiff = !prevAtPath || !sync.deepEqual(value2, prev);
197
+ if (isDiff) {
198
+ itemsChanged.push([getUpdateValue(value2, prev), prev]);
199
+ }
200
+ }
201
+ }
160
202
  } else {
161
203
  const itemKey = path[0];
162
204
  const itemValue = asMap ? value.get(itemKey) : value[itemKey];
163
205
  if (!itemValue) {
164
206
  if (path.length === 1 && prevAtPath) {
165
- deletes.add(itemKey);
207
+ deletes.add(prevAtPath);
166
208
  }
167
209
  } else {
168
- itemsChanged = [[itemKey, itemValue]];
210
+ const previous = state.setAtPath(
211
+ clone(itemValue),
212
+ path.slice(1),
213
+ pathTypes.slice(1),
214
+ prevAtPath
215
+ );
216
+ itemsChanged = [[getUpdateValue(itemValue, previous), previous]];
169
217
  }
170
218
  }
171
- itemsChanged == null ? void 0 : itemsChanged.forEach(([itemKey, item]) => {
172
- if (state.isNullOrUndefined(item)) {
173
- deletes.add(itemKey);
219
+ itemsChanged == null ? void 0 : itemsChanged.forEach(([item, prev]) => {
220
+ const isCreate = fieldCreatedAt ? !item[fieldCreatedAt] && !(prev == null ? void 0 : prev[fieldCreatedAt]) : fieldUpdatedAt ? !item[fieldUpdatedAt] && !(prev == null ? void 0 : prev[fieldCreatedAt]) : state.isNullOrUndefined(prev);
221
+ if (isCreate) {
222
+ if (generateId) {
223
+ ensureId(item, fieldId, generateId);
224
+ }
225
+ if (!item.id) {
226
+ console.error("[legend-state]: added item without an id");
227
+ }
228
+ if (createFn) {
229
+ creates.set(item.id, item);
230
+ } else {
231
+ console.log("[legend-state] missing create function");
232
+ }
174
233
  } else {
175
- const prev = asMap ? valuePrevious.get(itemKey) : valuePrevious[itemKey];
176
- const isCreate = fieldCreatedAt ? !item[fieldCreatedAt] : fieldUpdatedAt ? !item[fieldUpdatedAt] : state.isNullOrUndefined(prev);
177
- if (isCreate) {
178
- if (generateId) {
179
- ensureId(item, generateId);
180
- }
181
- if (!item.id) {
182
- console.error("[legend-state]: added item without an id");
183
- }
184
- if (createFn) {
185
- creates.set(item.id, item);
186
- } else {
187
- console.log("[legend-state] missing create function");
188
- }
234
+ if (updateFn) {
235
+ updates.set(
236
+ item.id,
237
+ updates.has(item.id) ? Object.assign(updates.get(item.id), item) : item
238
+ );
189
239
  } else {
190
- if (updateFn) {
191
- updates.set(item.id, item);
192
- } else {
193
- console.log("[legend-state] missing update function");
194
- }
240
+ console.log("[legend-state] missing update function");
195
241
  }
196
242
  }
197
243
  });
198
244
  }
199
245
  });
200
246
  const saveResult = async (itemKey, input, data, isCreate) => {
201
- if (data && (onSaved || onSavedUpdate)) {
202
- const saved = (transform == null ? void 0 : transform.load) ? transform.load(data, "set") : data;
247
+ if (data) {
248
+ const saved = (transform == null ? void 0 : transform.load) ? await transform.load(data, "set") : data;
203
249
  const isChild = itemKey !== "undefined" && asType !== "value";
204
250
  const currentPeeked = state.getNodeValue(node);
205
251
  const currentValue = isChild ? currentPeeked == null ? void 0 : currentPeeked[itemKey] : currentPeeked;
@@ -210,17 +256,26 @@ function syncedCrud(props) {
210
256
  isCreate,
211
257
  props
212
258
  };
213
- let savedOut = void 0;
214
- if (onSavedUpdate) {
215
- savedOut = onSavedCreatedUpdatedAt(onSavedUpdate, dataOnSaved);
216
- }
217
- if (onSaved) {
218
- const ret = onSaved(dataOnSaved);
219
- if (ret) {
220
- savedOut = ret;
259
+ let savedOut = saved;
260
+ if (savedOut && !state.isNullOrUndefined(currentValue)) {
261
+ savedOut = clone(savedOut);
262
+ Object.keys(savedOut).forEach((key) => {
263
+ const i = input[key];
264
+ const c = currentValue[key];
265
+ if (
266
+ // value is already the new value, can ignore
267
+ savedOut[key] === c || // user has changed local value
268
+ key !== "id" && i !== c
269
+ ) {
270
+ delete savedOut[key];
271
+ }
272
+ });
273
+ if (onSaved) {
274
+ const ret = onSaved(dataOnSaved);
275
+ if (ret) {
276
+ savedOut = ret;
277
+ }
221
278
  }
222
- }
223
- if (savedOut) {
224
279
  const createdAt = fieldCreatedAt ? savedOut[fieldCreatedAt] : void 0;
225
280
  const updatedAt = fieldUpdatedAt ? savedOut[fieldUpdatedAt] : void 0;
226
281
  const value2 = itemKey !== "undefined" && asType !== "value" ? { [itemKey]: savedOut } : savedOut;
@@ -233,38 +288,62 @@ function syncedCrud(props) {
233
288
  }
234
289
  };
235
290
  return Promise.all([
236
- ...Array.from(creates).map(([itemKey, itemValue]) => {
237
- const createObj = transformOut(itemValue, transform == null ? void 0 : transform.save);
291
+ ...Array.from(creates).map(async ([itemKey, itemValue]) => {
292
+ const createObj = await transformOut(itemValue, transform == null ? void 0 : transform.save);
238
293
  return createFn(createObj, params).then(
239
294
  (result) => saveResult(itemKey, createObj, result, true)
240
295
  );
241
296
  }),
242
- ...Array.from(updates).map(([itemKey, itemValue]) => {
243
- const toSave = updatePartial ? Object.assign(
244
- sync.diffObjects(asType === "value" ? valuePrevious : valuePrevious[itemKey], itemValue),
245
- { id: itemValue.id }
246
- ) : itemValue;
247
- const changed = transformOut(toSave, transform == null ? void 0 : transform.save);
297
+ ...Array.from(updates).map(async ([itemKey, itemValue]) => {
298
+ const toSave = itemValue;
299
+ const changed = await transformOut(toSave, transform == null ? void 0 : transform.save);
248
300
  if (Object.keys(changed).length > 0) {
249
301
  return updateFn(changed, params).then(
250
302
  (result) => result && saveResult(itemKey, changed, result, false)
251
303
  );
252
304
  }
253
305
  }),
254
- ...Array.from(deletes).map((id) => {
255
- if (deleteFn) {
256
- deleteFn({ id }, params);
257
- } else if (fieldDeleted && updateFn) {
258
- updateFn({ id, [fieldDeleted]: true }, params);
259
- } else {
260
- console.log("[legend-state] missing delete function");
306
+ ...Array.from(deletes).map((valuePrevious) => {
307
+ if (valuePrevious !== state.symbolDelete) {
308
+ if (deleteFn) {
309
+ deleteFn(valuePrevious, params);
310
+ } else if (fieldDeleted && updateFn) {
311
+ const valueId = valuePrevious[fieldId];
312
+ if (valueId) {
313
+ updateFn({ ...{ [fieldId]: valueId }, [fieldDeleted]: true }, params);
314
+ } else {
315
+ console.error("[legend-state]: deleting item without an id");
316
+ }
317
+ } else {
318
+ console.log("[legend-state] missing delete function");
319
+ }
261
320
  }
262
321
  })
263
322
  ]);
264
323
  } : void 0;
324
+ const subscribe = subscribeProp ? (params) => subscribeProp({
325
+ ...params,
326
+ update: async (paramsUpdate) => {
327
+ const paramsForUpdate = paramsUpdate;
328
+ const rows = paramsUpdate.value;
329
+ if (process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test") {
330
+ if (!state.isArray(rows)) {
331
+ console.error("[legend-state] subscribe:update expects an array of changed items");
332
+ }
333
+ }
334
+ const newLastSync = computeLastSync(rows, fieldUpdatedAt, fieldCreatedAt);
335
+ if (newLastSync) {
336
+ paramsForUpdate.lastSync = newLastSync;
337
+ }
338
+ const rowsTransformed = (transform == null ? void 0 : transform.load) ? await transformRows(rows) : rows;
339
+ paramsForUpdate.value = resultsToOutType(rowsTransformed);
340
+ params.update(paramsForUpdate);
341
+ }
342
+ }) : void 0;
265
343
  return sync.synced({
266
344
  set,
267
345
  get,
346
+ subscribe,
268
347
  mode: modeParam,
269
348
  ...rest
270
349
  });