@legendapp/state 3.0.0-alpha.2 → 3.0.0-alpha.20

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