@legendapp/state 3.0.0-beta.4 → 3.0.0-beta.41

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 (79) hide show
  1. package/.DS_Store +0 -0
  2. package/README.md +2 -2
  3. package/config/enableReactComponents.js +3 -1
  4. package/config/enableReactComponents.mjs +3 -1
  5. package/config/enableReactTracking.d.mts +2 -1
  6. package/config/enableReactTracking.d.ts +2 -1
  7. package/config/enableReactTracking.js +32 -13
  8. package/config/enableReactTracking.mjs +32 -13
  9. package/index.d.mts +46 -8
  10. package/index.d.ts +46 -8
  11. package/index.js +267 -75
  12. package/index.mjs +267 -75
  13. package/package.json +35 -1
  14. package/persist-plugins/async-storage.js +17 -9
  15. package/persist-plugins/async-storage.mjs +17 -9
  16. package/persist-plugins/expo-sqlite.d.mts +19 -0
  17. package/persist-plugins/expo-sqlite.d.ts +19 -0
  18. package/persist-plugins/expo-sqlite.js +72 -0
  19. package/persist-plugins/expo-sqlite.mjs +69 -0
  20. package/persist-plugins/indexeddb.js +13 -3
  21. package/persist-plugins/indexeddb.mjs +13 -3
  22. package/react-native.d.mts +4 -0
  23. package/react-native.d.ts +4 -0
  24. package/react-native.js +53 -0
  25. package/react-native.mjs +40 -0
  26. package/react-reactive/Components.d.mts +19 -0
  27. package/react-reactive/Components.d.ts +19 -0
  28. package/react-reactive/Components.js +53 -0
  29. package/react-reactive/Components.mjs +40 -0
  30. package/react-reactive/enableReactComponents.d.mts +3 -2
  31. package/react-reactive/enableReactComponents.d.ts +3 -2
  32. package/react-reactive/enableReactComponents.js +10 -3
  33. package/react-reactive/enableReactComponents.mjs +10 -3
  34. package/react-reactive/enableReactNativeComponents.d.mts +3 -20
  35. package/react-reactive/enableReactNativeComponents.d.ts +3 -20
  36. package/react-reactive/enableReactNativeComponents.js +8 -3
  37. package/react-reactive/enableReactNativeComponents.mjs +8 -3
  38. package/react-reactive/enableReactive.js +10 -3
  39. package/react-reactive/enableReactive.mjs +10 -3
  40. package/react-reactive/enableReactive.native.js +8 -3
  41. package/react-reactive/enableReactive.native.mjs +8 -3
  42. package/react-reactive/enableReactive.web.js +8 -3
  43. package/react-reactive/enableReactive.web.mjs +8 -3
  44. package/react-web.d.mts +7 -0
  45. package/react-web.d.ts +7 -0
  46. package/react-web.js +39 -0
  47. package/react-web.mjs +37 -0
  48. package/react.d.mts +59 -26
  49. package/react.d.ts +59 -26
  50. package/react.js +136 -87
  51. package/react.mjs +135 -89
  52. package/sync-plugins/crud.d.mts +24 -9
  53. package/sync-plugins/crud.d.ts +24 -9
  54. package/sync-plugins/crud.js +267 -123
  55. package/sync-plugins/crud.mjs +268 -124
  56. package/sync-plugins/firebase.d.mts +7 -3
  57. package/sync-plugins/firebase.d.ts +7 -3
  58. package/sync-plugins/firebase.js +214 -64
  59. package/sync-plugins/firebase.mjs +215 -65
  60. package/sync-plugins/keel.d.mts +12 -13
  61. package/sync-plugins/keel.d.ts +12 -13
  62. package/sync-plugins/keel.js +60 -52
  63. package/sync-plugins/keel.mjs +61 -48
  64. package/sync-plugins/supabase.d.mts +10 -5
  65. package/sync-plugins/supabase.d.ts +10 -5
  66. package/sync-plugins/supabase.js +90 -33
  67. package/sync-plugins/supabase.mjs +91 -34
  68. package/sync-plugins/tanstack-query.d.mts +3 -3
  69. package/sync-plugins/tanstack-query.d.ts +3 -3
  70. package/sync-plugins/tanstack-query.js +1 -1
  71. package/sync-plugins/tanstack-query.mjs +1 -1
  72. package/sync.d.mts +17 -8
  73. package/sync.d.ts +17 -8
  74. package/sync.js +448 -307
  75. package/sync.mjs +446 -307
  76. package/trace.js +5 -6
  77. package/trace.mjs +5 -6
  78. package/types/reactive-native.d.ts +19 -0
  79. package/types/reactive-web.d.ts +7 -0
@@ -103,6 +103,61 @@ if (process.env.NODE_ENV === "development") {
103
103
  };
104
104
  }
105
105
 
106
+ // src/is.ts
107
+ function isMap(obj) {
108
+ return obj instanceof Map || obj instanceof WeakMap;
109
+ }
110
+ var globalState = {
111
+ pendingNodes: /* @__PURE__ */ new Map(),
112
+ dirtyNodes: /* @__PURE__ */ new Set()
113
+ };
114
+ function replacer(key, value) {
115
+ if (isMap(value)) {
116
+ return {
117
+ __LSType: "Map",
118
+ value: Array.from(value.entries())
119
+ // or with spread: value: [...value]
120
+ };
121
+ } else if (value instanceof Set) {
122
+ return {
123
+ __LSType: "Set",
124
+ value: Array.from(value)
125
+ // or with spread: value: [...value]
126
+ };
127
+ } else if (globalState.replacer) {
128
+ value = globalState.replacer(key, value);
129
+ }
130
+ return value;
131
+ }
132
+ var ISO8601 = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/;
133
+ function reviver(key, value) {
134
+ if (value) {
135
+ if (typeof value === "string" && ISO8601.test(value)) {
136
+ return new Date(value);
137
+ }
138
+ if (typeof value === "object") {
139
+ if (value.__LSType === "Map") {
140
+ return new Map(value.value);
141
+ } else if (value.__LSType === "Set") {
142
+ return new Set(value.value);
143
+ }
144
+ }
145
+ if (globalState.reviver) {
146
+ value = globalState.reviver(key, value);
147
+ }
148
+ }
149
+ return value;
150
+ }
151
+ function safeStringify(value) {
152
+ return value ? JSON.stringify(value, replacer) : value;
153
+ }
154
+ function safeParse(value) {
155
+ return value ? JSON.parse(value, reviver) : value;
156
+ }
157
+ function clone(value) {
158
+ return safeParse(safeStringify(value));
159
+ }
160
+
106
161
  // src/sync-plugins/firebase.ts
107
162
  var isEnabled$ = state.observable(true);
108
163
  var firebaseConfig = {};
@@ -154,9 +209,6 @@ var fns = {
154
209
  };
155
210
  function syncedFirebase(props) {
156
211
  props = { ...firebaseConfig, ...props };
157
- const saving$ = state.observable({});
158
- const pendingOutgoing$ = state.observable({});
159
- const pendingIncoming$ = state.observable({});
160
212
  let didList = false;
161
213
  const {
162
214
  refPath,
@@ -174,6 +226,102 @@ function syncedFirebase(props) {
174
226
  const { fieldCreatedAt, changesSince } = props;
175
227
  const asType = props.as || "value";
176
228
  const fieldUpdatedAt = props.fieldUpdatedAt || "@";
229
+ const isRealtime = realtime !== false;
230
+ const pendingWrites = /* @__PURE__ */ new Map();
231
+ const enqueuePendingWrite = (key) => {
232
+ let resolveFn;
233
+ let rejectFn;
234
+ const promise = new Promise((resolve, reject) => {
235
+ resolveFn = resolve;
236
+ rejectFn = reject;
237
+ });
238
+ const entry = {
239
+ resolve: resolveFn,
240
+ reject: rejectFn
241
+ };
242
+ const state = pendingWrites.get(key);
243
+ if (state) {
244
+ state.waiting.push(entry);
245
+ state.pendingCount += 1;
246
+ } else {
247
+ pendingWrites.set(key, {
248
+ waiting: [entry],
249
+ ready: [],
250
+ pendingCount: 1
251
+ });
252
+ }
253
+ return { promise, entry };
254
+ };
255
+ const flushPending = (key) => {
256
+ const state = pendingWrites.get(key);
257
+ if (!state) {
258
+ return;
259
+ }
260
+ if (state.pendingCount === 0 && state.staged) {
261
+ const { value, apply } = state.staged;
262
+ state.staged = void 0;
263
+ while (state.ready.length) {
264
+ const entry = state.ready.shift();
265
+ entry.resolve(value);
266
+ }
267
+ if (!state.waiting.length && !state.ready.length) {
268
+ pendingWrites.delete(key);
269
+ }
270
+ apply == null ? void 0 : apply(value);
271
+ }
272
+ };
273
+ const resolvePendingWrite = (key, entry) => {
274
+ const state = pendingWrites.get(key);
275
+ if (!state) {
276
+ return;
277
+ }
278
+ const waitingIndex = state.waiting.indexOf(entry);
279
+ if (waitingIndex >= 0) {
280
+ state.waiting.splice(waitingIndex, 1);
281
+ state.pendingCount = Math.max(0, state.pendingCount - 1);
282
+ }
283
+ state.ready.push(entry);
284
+ flushPending(key);
285
+ };
286
+ const rejectPendingWrite = (key, entry, error) => {
287
+ const state = pendingWrites.get(key);
288
+ if (state) {
289
+ const waitingIndex = state.waiting.indexOf(entry);
290
+ if (waitingIndex >= 0) {
291
+ state.waiting.splice(waitingIndex, 1);
292
+ state.pendingCount = Math.max(0, state.pendingCount - 1);
293
+ } else {
294
+ const readyIndex = state.ready.indexOf(entry);
295
+ if (readyIndex >= 0) {
296
+ state.ready.splice(readyIndex, 1);
297
+ }
298
+ }
299
+ if (!state.waiting.length && !state.ready.length) {
300
+ pendingWrites.delete(key);
301
+ }
302
+ }
303
+ entry.reject(error);
304
+ };
305
+ const handleServerValue = (key, value, apply) => {
306
+ var _a;
307
+ const state = pendingWrites.get(key);
308
+ if (!state || !state.waiting.length && !state.ready.length) {
309
+ pendingWrites.delete(key);
310
+ apply == null ? void 0 : apply(value);
311
+ } else {
312
+ state.staged = {
313
+ value: value && typeof value === "object" ? clone(value) : value,
314
+ apply: apply != null ? apply : (_a = state.staged) == null ? void 0 : _a.apply
315
+ };
316
+ flushPending(key);
317
+ }
318
+ };
319
+ const ensureFieldId = (key, value) => {
320
+ if (fieldId && key && value && typeof value === "object" && !value[fieldId]) {
321
+ value[fieldId] = key;
322
+ }
323
+ return value;
324
+ };
177
325
  const computeRef = (lastSync) => {
178
326
  const pathFirebase = refPath(fns.getCurrentUser());
179
327
  let ref = fns.ref(pathFirebase);
@@ -185,7 +333,8 @@ function syncedFirebase(props) {
185
333
  }
186
334
  return ref;
187
335
  };
188
- const list = async ({ lastSync, onError }) => {
336
+ const list = async (getParams) => {
337
+ const { lastSync, onError } = getParams;
189
338
  const ref = computeRef(lastSync);
190
339
  return new Promise((resolve) => {
191
340
  fns.once(
@@ -195,20 +344,17 @@ function syncedFirebase(props) {
195
344
  let values = [];
196
345
  if (!state.isNullOrUndefined(val)) {
197
346
  values = asType === "value" ? [val] : Object.entries(val).map(([key, value]) => {
198
- if (fieldId && !value[fieldId]) {
199
- value[fieldId] = key;
200
- }
201
- return value;
347
+ return ensureFieldId(key, value);
202
348
  });
203
349
  }
204
350
  didList = true;
205
351
  resolve(values);
206
352
  },
207
- onError
353
+ (error) => onError(error, { source: "list", type: "get", retry: getParams })
208
354
  );
209
355
  });
210
356
  };
211
- const subscribe = realtime ? ({ lastSync, update: update2, onError }) => {
357
+ const subscribe = isRealtime ? ({ lastSync, update: update2, onError }) => {
212
358
  const ref = computeRef(lastSync);
213
359
  let unsubscribes;
214
360
  if (asType === "value") {
@@ -216,14 +362,12 @@ function syncedFirebase(props) {
216
362
  if (!didList)
217
363
  return;
218
364
  const val = snap.val();
219
- if (saving$[""].get()) {
220
- pendingIncoming$[""].set(val);
221
- } else {
365
+ handleServerValue("", val, (resolvedValue) => {
222
366
  update2({
223
- value: [val],
367
+ value: [resolvedValue],
224
368
  mode: "set"
225
369
  });
226
- }
370
+ });
227
371
  };
228
372
  unsubscribes = [fns.onValue(ref, onValue2, onError)];
229
373
  } else {
@@ -231,31 +375,26 @@ function syncedFirebase(props) {
231
375
  if (!didList)
232
376
  return;
233
377
  const key = snap.key;
234
- const val = snap.val();
235
- if (fieldId && !val[fieldId]) {
236
- val[fieldId] = key;
237
- }
238
- if (saving$[key].get()) {
239
- pendingIncoming$[key].set(val);
240
- } else {
378
+ const val = ensureFieldId(key, snap.val());
379
+ handleServerValue(key, val, (resolvedValue) => {
241
380
  update2({
242
- value: [val],
243
- mode: "assign"
381
+ value: [resolvedValue],
382
+ mode: "merge"
244
383
  });
245
- }
384
+ });
246
385
  };
247
386
  const onChildDelete = (snap) => {
248
387
  if (!didList)
249
388
  return;
250
389
  const key = snap.key;
251
- const val = snap.val();
252
- if (fieldId && !val[fieldId]) {
253
- val[fieldId] = key;
254
- }
255
- val[state.symbolDelete] = true;
256
- update2({
257
- value: [val],
258
- mode: "assign"
390
+ const valueRaw = snap.val();
391
+ const valueWithId = ensureFieldId(key, state.isNullOrUndefined(valueRaw) ? {} : valueRaw);
392
+ valueWithId[state.symbolDelete] = true;
393
+ handleServerValue(key, valueWithId, (resolvedValue) => {
394
+ update2({
395
+ value: [resolvedValue],
396
+ mode: "merge"
397
+ });
259
398
  });
260
399
  };
261
400
  unsubscribes = [
@@ -279,42 +418,52 @@ function syncedFirebase(props) {
279
418
  }
280
419
  return addUpdatedAt(input);
281
420
  };
282
- const upsert = async (input) => {
283
- const id = fieldId ? input[fieldId] : "";
284
- if (saving$[id].get()) {
285
- pendingOutgoing$[id].set(input);
286
- } else {
287
- saving$[id].set(true);
288
- const path = joinPaths(refPath(fns.getCurrentUser()), fieldId ? id : "");
289
- await fns.update(fns.ref(path), input);
290
- saving$[id].set(false);
291
- flushAfterSave();
292
- }
293
- return state.when(
294
- () => !pendingOutgoing$[id].get(),
295
- () => {
296
- const value = pendingIncoming$[id].get();
297
- if (value) {
298
- pendingIncoming$[id].delete();
299
- return value;
300
- }
301
- }
302
- );
303
- };
304
- const flushAfterSave = () => {
305
- const outgoing = pendingOutgoing$.get();
306
- Object.values(outgoing).forEach((value) => {
307
- upsert(value);
421
+ const upsert = (input, params) => {
422
+ const id = fieldId && asType !== "value" ? input[fieldId] : "";
423
+ const pendingKey = fieldId && asType !== "value" ? String(id != null ? id : "") : "";
424
+ const { promise, entry } = enqueuePendingWrite(pendingKey);
425
+ const userId = fns.getCurrentUser();
426
+ const basePath = refPath(userId);
427
+ const childPath = fieldId && asType !== "value" ? pendingKey : "";
428
+ const path = joinPaths(basePath, childPath);
429
+ const ref = fns.ref(path);
430
+ const updatePromise = fns.update(ref, input);
431
+ updatePromise.then(() => {
432
+ resolvePendingWrite(pendingKey, entry);
433
+ }).catch((error) => {
434
+ rejectPendingWrite(pendingKey, entry, error);
308
435
  });
309
- pendingOutgoing$.set({});
436
+ if (!isRealtime) {
437
+ updatePromise.then(() => {
438
+ const onceRef = fieldId && asType !== "value" ? ref : fns.ref(basePath);
439
+ fns.once(
440
+ onceRef,
441
+ (snap) => {
442
+ const rawValue = snap.val();
443
+ const value = fieldId && asType !== "value" ? ensureFieldId(pendingKey, state.isNullOrUndefined(rawValue) ? {} : rawValue) : rawValue;
444
+ handleServerValue(pendingKey, value, (resolvedValue) => {
445
+ params.update({
446
+ value: resolvedValue,
447
+ mode: "merge"
448
+ });
449
+ });
450
+ },
451
+ (error) => {
452
+ rejectPendingWrite(pendingKey, entry, error);
453
+ }
454
+ );
455
+ }).catch(() => {
456
+ });
457
+ }
458
+ return promise;
310
459
  };
311
- const create = readonly ? void 0 : (input) => {
460
+ const create = readonly ? void 0 : (input, params) => {
312
461
  addCreatedAt(input);
313
- return upsert(input);
462
+ return upsert(input, params);
314
463
  };
315
- const update = readonly ? void 0 : (input) => {
464
+ const update = readonly ? void 0 : (input, params) => {
316
465
  addUpdatedAt(input);
317
- return upsert(input);
466
+ return upsert(input, params);
318
467
  };
319
468
  const deleteFn = readonly ? void 0 : (input) => {
320
469
  const path = joinPaths(
@@ -354,6 +503,7 @@ function syncedFirebase(props) {
354
503
  }
355
504
  return crud.syncedCrud({
356
505
  ...rest,
506
+ // Workaround for type errors
357
507
  list,
358
508
  subscribe,
359
509
  create,