@legendapp/state 3.0.0-alpha.9 → 3.0.0-beta.0

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 (86) hide show
  1. package/.DS_Store +0 -0
  2. package/config/configureLegendState.d.mts +13 -0
  3. package/config/configureLegendState.d.ts +13 -0
  4. package/config/configureLegendState.js +45 -0
  5. package/config/configureLegendState.mjs +43 -0
  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/helpers/trackHistory.js +2 -2
  15. package/helpers/trackHistory.mjs +2 -2
  16. package/index.d.mts +103 -79
  17. package/index.d.ts +103 -79
  18. package/index.js +326 -316
  19. package/index.mjs +323 -314
  20. package/package.json +36 -1
  21. package/persist-plugins/async-storage.d.mts +6 -3
  22. package/persist-plugins/async-storage.d.ts +6 -3
  23. package/persist-plugins/async-storage.js +8 -4
  24. package/persist-plugins/async-storage.mjs +8 -5
  25. package/persist-plugins/indexeddb.d.mts +6 -4
  26. package/persist-plugins/indexeddb.d.ts +6 -4
  27. package/persist-plugins/indexeddb.js +35 -15
  28. package/persist-plugins/indexeddb.mjs +35 -16
  29. package/persist-plugins/mmkv.d.mts +5 -1
  30. package/persist-plugins/mmkv.d.ts +5 -1
  31. package/persist-plugins/mmkv.js +10 -5
  32. package/persist-plugins/mmkv.mjs +10 -6
  33. package/react-reactive/enableReactComponents.d.mts +9 -0
  34. package/react-reactive/enableReactComponents.d.ts +9 -0
  35. package/react-reactive/enableReactComponents.js +19 -0
  36. package/react-reactive/enableReactComponents.mjs +17 -0
  37. package/react-reactive/enableReactNativeComponents.d.mts +22 -0
  38. package/react-reactive/enableReactNativeComponents.d.ts +22 -0
  39. package/react-reactive/enableReactNativeComponents.js +53 -0
  40. package/react-reactive/enableReactNativeComponents.mjs +51 -0
  41. package/react-reactive/enableReactive.d.mts +5 -0
  42. package/react-reactive/enableReactive.d.ts +5 -0
  43. package/react-reactive/enableReactive.js +24 -0
  44. package/react-reactive/enableReactive.mjs +22 -0
  45. package/react-reactive/enableReactive.native.d.mts +5 -0
  46. package/react-reactive/enableReactive.native.d.ts +5 -0
  47. package/react-reactive/enableReactive.native.js +58 -0
  48. package/react-reactive/enableReactive.native.mjs +56 -0
  49. package/react-reactive/enableReactive.web.d.mts +5 -0
  50. package/react-reactive/enableReactive.web.d.ts +5 -0
  51. package/react-reactive/enableReactive.web.js +58 -0
  52. package/react-reactive/enableReactive.web.mjs +56 -0
  53. package/react.d.mts +39 -34
  54. package/react.d.ts +39 -34
  55. package/react.js +39 -17
  56. package/react.mjs +39 -17
  57. package/sync-plugins/crud.d.mts +21 -23
  58. package/sync-plugins/crud.d.ts +21 -23
  59. package/sync-plugins/crud.js +224 -112
  60. package/sync-plugins/crud.mjs +226 -114
  61. package/sync-plugins/fetch.js +12 -8
  62. package/sync-plugins/fetch.mjs +13 -9
  63. package/sync-plugins/firebase.d.mts +27 -0
  64. package/sync-plugins/firebase.d.ts +27 -0
  65. package/sync-plugins/firebase.js +373 -0
  66. package/sync-plugins/firebase.mjs +368 -0
  67. package/sync-plugins/keel.d.mts +43 -26
  68. package/sync-plugins/keel.d.ts +43 -26
  69. package/sync-plugins/keel.js +145 -99
  70. package/sync-plugins/keel.mjs +147 -99
  71. package/sync-plugins/supabase.d.mts +19 -9
  72. package/sync-plugins/supabase.d.ts +19 -9
  73. package/sync-plugins/supabase.js +52 -21
  74. package/sync-plugins/supabase.mjs +53 -22
  75. package/sync-plugins/tanstack-query.d.mts +2 -2
  76. package/sync-plugins/tanstack-query.d.ts +2 -2
  77. package/sync-plugins/tanstack-query.js +22 -5
  78. package/sync-plugins/tanstack-query.mjs +22 -5
  79. package/sync-plugins/tanstack-react-query.d.mts +1 -1
  80. package/sync-plugins/tanstack-react-query.d.ts +1 -1
  81. package/sync-plugins/tanstack-react-query.js +8 -1
  82. package/sync-plugins/tanstack-react-query.mjs +8 -1
  83. package/sync.d.mts +74 -200
  84. package/sync.d.ts +74 -200
  85. package/sync.js +495 -281
  86. package/sync.mjs +500 -286
@@ -1,88 +1,99 @@
1
- import ksuid from 'ksuid';
2
- import { observable, computeSelector, isFunction, isEmpty, when, internal } from '@legendapp/state';
3
- import { removeNullUndefined } from '@legendapp/state/sync';
1
+ import { observable, isFunction, when, batch, isEmpty } from '@legendapp/state';
4
2
  import { syncedCrud } from '@legendapp/state/sync-plugins/crud';
3
+ import ksuid from 'ksuid';
5
4
 
6
5
  // src/sync-plugins/keel.ts
7
- var { clone } = internal;
8
6
  var KeelKeys = ["createdAt", "updatedAt"];
9
7
  function generateKeelId() {
10
8
  return ksuid.randomSync().string;
11
9
  }
12
- var keelConfig = {};
13
10
  var modifiedClients = /* @__PURE__ */ new WeakSet();
14
- var isEnabled$ = observable(true);
15
- async function ensureAuthToken() {
16
- await when(isEnabled$.get());
17
- let isAuthed = await keelConfig.client.auth.isAuthenticated();
11
+ var isAuthed$ = observable(false);
12
+ var isAuthing$ = observable(false);
13
+ async function ensureAuthToken(props, force) {
14
+ if (!force && isAuthed$.get()) {
15
+ return true;
16
+ }
17
+ const { client, refreshAuth } = props;
18
+ let isAuthed = await client.auth.isAuthenticated().then(({ data }) => data);
18
19
  if (!isAuthed) {
19
- isAuthed = await keelConfig.client.auth.refresh();
20
+ if (!force && isAuthing$.get()) {
21
+ return when(
22
+ () => !isAuthing$.get(),
23
+ () => isAuthed$.get()
24
+ );
25
+ }
26
+ isAuthing$.set(true);
27
+ if (refreshAuth) {
28
+ await refreshAuth();
29
+ }
30
+ isAuthed = await client.auth.isAuthenticated().then(({ data }) => data);
31
+ if (!isAuthed) {
32
+ isAuthed = await client.auth.refresh().then(({ data }) => data);
33
+ }
34
+ }
35
+ if (isAuthed) {
36
+ batch(() => {
37
+ isAuthed$.set(true);
38
+ isAuthing$.set(false);
39
+ });
40
+ } else {
41
+ setTimeout(() => ensureAuthToken(
42
+ props,
43
+ /*force*/
44
+ true
45
+ ), 1e3);
20
46
  }
21
47
  return isAuthed;
22
48
  }
23
- async function handleApiError(error, retry) {
49
+ async function handleApiError(props, error, retry) {
24
50
  if (error.type === "unauthorized" || error.type === "forbidden") {
25
51
  console.warn("Keel token expired, refreshing...");
26
- await ensureAuthToken();
52
+ isAuthed$.set(false);
53
+ await ensureAuthToken(props);
27
54
  }
28
55
  }
29
56
  function convertObjectToCreate(item) {
30
- const cloned = clone(item);
31
- Object.keys(cloned).forEach((key) => {
57
+ const cloned = {};
58
+ Object.keys(item).forEach((key) => {
32
59
  if (key.endsWith("Id")) {
33
- if (cloned[key]) {
34
- cloned[key.slice(0, -2)] = { id: cloned[key] };
60
+ if (item[key]) {
61
+ cloned[key.slice(0, -2)] = { id: item[key] };
62
+ }
63
+ } else if (key !== "createdAt" && key !== "updatedAt") {
64
+ if (item[key] === void 0) {
65
+ cloned[key] = null;
66
+ } else {
67
+ cloned[key] = item[key];
35
68
  }
36
- delete cloned[key];
37
69
  }
38
70
  });
39
- delete cloned.createdAt;
40
- delete cloned.updatedAt;
41
71
  return cloned;
42
72
  }
43
- function getSyncedKeelConfiguration() {
44
- return keelConfig;
45
- }
46
- function configureSyncedKeel(config) {
47
- const { enabled, realtimePlugin, client, ...rest } = config;
48
- Object.assign(keelConfig, removeNullUndefined(rest));
49
- if (enabled !== void 0) {
50
- isEnabled$.set(enabled);
51
- }
52
- if (realtimePlugin) {
53
- keelConfig.realtimePlugin = realtimePlugin;
54
- if (client && !modifiedClients.has(client)) {
55
- modifiedClients.add(client);
56
- const queries = client.api.queries;
57
- Object.keys(queries).forEach((key) => {
58
- if (key.startsWith("list")) {
59
- const oldFn = queries[key];
60
- queries[key] = (i) => {
61
- const realtimeKey = [key, ...Object.values(i.where || {})].filter((value) => value && typeof value !== "object").join("/");
62
- const subscribe = ({ refresh }) => {
63
- if (realtimeKey) {
64
- return realtimePlugin.subscribe(realtimeKey, refresh);
65
- }
66
- };
67
- return oldFn(i).then((ret) => {
68
- if (subscribe) {
69
- ret.subscribe = subscribe;
70
- ret.subscribeKey = realtimeKey;
71
- }
72
- return ret;
73
- });
73
+ var realtimeState = { current: {} };
74
+ function setupRealtime(props) {
75
+ const { client } = props;
76
+ if (client && !modifiedClients.has(client)) {
77
+ modifiedClients.add(client);
78
+ const queries = client.api.queries;
79
+ Object.keys(queries).forEach((key) => {
80
+ if (key.startsWith("list")) {
81
+ const origFn = queries[key];
82
+ queries[key] = (i) => {
83
+ realtimeState.current = {
84
+ lastAction: key,
85
+ lastParams: i
74
86
  };
75
- }
76
- });
77
- }
87
+ return origFn(i);
88
+ };
89
+ }
90
+ });
78
91
  }
79
92
  }
80
93
  var NumPerPage = 200;
81
- async function getAllPages(listFn, params) {
94
+ async function getAllPages(props, listFn, params) {
82
95
  const allData = [];
83
96
  let pageInfo = void 0;
84
- let subscribe_;
85
- let subscribeKey_;
86
97
  const { first: firstParam } = params;
87
98
  do {
88
99
  const first = firstParam ? Math.min(firstParam - allData.length, NumPerPage) : NumPerPage;
@@ -94,13 +105,9 @@ async function getAllPages(listFn, params) {
94
105
  pageInfo = void 0;
95
106
  const ret = await listFn(paramsWithCursor);
96
107
  if (ret) {
97
- const { data, error, subscribe, subscribeKey } = ret;
98
- if (subscribe) {
99
- subscribe_ = subscribe;
100
- subscribeKey_ = subscribeKey;
101
- }
108
+ const { data, error } = ret;
102
109
  if (error) {
103
- await handleApiError(error);
110
+ await handleApiError(props, error);
104
111
  throw new Error(error.message);
105
112
  } else if (data) {
106
113
  pageInfo = data.pageInfo;
@@ -108,21 +115,25 @@ async function getAllPages(listFn, params) {
108
115
  }
109
116
  }
110
117
  } while (pageInfo == null ? void 0 : pageInfo.hasNextPage);
111
- return { results: allData, subscribe: subscribe_, subscribeKey: subscribeKey_ };
118
+ return allData;
112
119
  }
113
120
  function syncedKeel(props) {
114
- const { realtimePlugin } = keelConfig;
115
- props = { ...keelConfig, ...props };
116
121
  const {
117
122
  get: getParam,
118
123
  list: listParam,
119
124
  create: createParam,
120
125
  update: updateParam,
121
126
  delete: deleteParam,
127
+ subscribe: subscribeParam,
122
128
  first,
123
129
  where: whereParam,
124
130
  waitFor,
131
+ waitForSet,
125
132
  fieldDeleted,
133
+ realtime,
134
+ mode,
135
+ onError,
136
+ requireAuth = true,
126
137
  ...rest
127
138
  } = props;
128
139
  const { changesSince } = props;
@@ -131,28 +142,38 @@ function syncedKeel(props) {
131
142
  const subscribeFnKey$ = observable("");
132
143
  const fieldCreatedAt = "createdAt";
133
144
  const fieldUpdatedAt = "updatedAt";
145
+ const setupSubscribe = realtime ? async (getParams) => {
146
+ const { lastAction, lastParams } = realtimeState.current;
147
+ const { path, plugin } = realtime;
148
+ if (lastAction && path && plugin) {
149
+ const key = await path(lastAction, lastParams);
150
+ subscribeFn = () => realtime.plugin.subscribe(key, getParams);
151
+ subscribeFnKey$.set(key);
152
+ }
153
+ } : void 0;
134
154
  const list = listParam ? async (listParams) => {
135
- const { lastSync, refresh } = listParams;
155
+ const { lastSync } = listParams;
136
156
  const queryBySync = !!lastSync && changesSince === "last-sync";
137
157
  const where = Object.assign(
138
158
  queryBySync ? { updatedAt: { after: new Date(lastSync + 1) } } : {},
139
159
  isFunction(whereParam) ? whereParam() : whereParam
140
160
  );
141
161
  const params = { where, first };
142
- const { results, subscribe: subscribe2, subscribeKey } = await getAllPages(listParam, params);
143
- if (subscribe2) {
144
- subscribeFn = () => subscribe2({ refresh });
145
- subscribeFnKey$.set(subscribeKey);
162
+ realtimeState.current = {};
163
+ const promise = getAllPages(props, listParam, params);
164
+ if (realtime) {
165
+ setupSubscribe(listParams);
146
166
  }
147
- return results;
167
+ return promise;
148
168
  } : void 0;
149
169
  const get = getParam ? async (getParams) => {
150
170
  const { refresh } = getParams;
151
- const { data, error, subscribe: subscribe2, subscribeKey } = await getParam({ refresh });
152
- if (subscribe2) {
153
- subscribeFn = () => subscribe2({ refresh });
154
- subscribeFnKey$.set(subscribeKey);
171
+ realtimeState.current = {};
172
+ const promise = getParam({ refresh });
173
+ if (realtime) {
174
+ setupSubscribe(getParams);
155
175
  }
176
+ const { data, error } = await promise;
156
177
  if (error) {
157
178
  throw new Error(error.message);
158
179
  } else {
@@ -161,41 +182,54 @@ function syncedKeel(props) {
161
182
  } : void 0;
162
183
  const onSaved = ({ saved }) => {
163
184
  if (saved) {
164
- const updatedAt = saved[fieldUpdatedAt];
165
- if (updatedAt && realtimePlugin) {
185
+ if (realtime == null ? void 0 : realtime.plugin) {
166
186
  const subscribeFnKey = subscribeFnKey$.get();
167
187
  if (subscribeFnKey) {
168
- realtimePlugin.setLatestChange(subscribeFnKey, updatedAt);
188
+ realtime == null ? void 0 : realtime.plugin.setSaved(subscribeFnKey);
169
189
  }
170
190
  }
171
191
  }
172
192
  };
173
- const handleSetError = async (error, params, isCreate) => {
174
- var _a, _b, _c;
175
- const { retryNum, cancelRetry, update: update2 } = params;
176
- if (isCreate && ((_a = error.message) == null ? void 0 : _a.includes("for the unique")) && ((_b = error.message) == null ? void 0 : _b.includes("must be unique"))) {
193
+ const handleSetError = async (error, params, input, fn, from) => {
194
+ var _a, _b;
195
+ const { retryNum, update: update2 } = params;
196
+ if (from === "create" && ((_a = error.message) == null ? void 0 : _a.includes("for the unique")) && ((_b = error.message) == null ? void 0 : _b.includes("must be unique"))) {
177
197
  if (__DEV__) {
178
198
  console.log("Creating duplicate data already saved, just ignore.");
179
199
  }
200
+ params.cancelRetry = true;
180
201
  update2({
181
202
  value: {},
182
203
  mode: "assign"
183
204
  });
205
+ } else if (from === "delete") {
206
+ if (error.message === "record not found") {
207
+ if (__DEV__) {
208
+ console.log("Deleting non-existing data, just ignore.");
209
+ }
210
+ params.cancelRetry = true;
211
+ }
184
212
  } else if (error.type === "bad_request") {
185
- (_c = keelConfig.onError) == null ? void 0 : _c.call(keelConfig, error);
213
+ onError == null ? void 0 : onError(new Error(error.message), params, {
214
+ error,
215
+ params,
216
+ input,
217
+ type: from,
218
+ action: fn.name || fn.toString()
219
+ });
186
220
  if (retryNum > 4) {
187
- cancelRetry();
221
+ params.cancelRetry = true;
188
222
  }
189
- throw new Error(error.message);
223
+ throw new Error(error.message, { cause: { input } });
190
224
  } else {
191
- await handleApiError(error);
192
- throw new Error(error.message);
225
+ await handleApiError(props, error);
226
+ throw new Error(error.message, { cause: { input } });
193
227
  }
194
228
  };
195
229
  const create = createParam ? async (input, params) => {
196
230
  const { data, error } = await createParam(convertObjectToCreate(input));
197
231
  if (error) {
198
- handleSetError(error, params, true);
232
+ await handleSetError(error, params, input, createParam, "create");
199
233
  }
200
234
  return data;
201
235
  } : void 0;
@@ -203,44 +237,58 @@ function syncedKeel(props) {
203
237
  const id = input.id;
204
238
  const values = convertObjectToCreate(input);
205
239
  delete values.id;
206
- delete values.createdAt;
207
- delete values.updatedAt;
208
240
  if (!isEmpty(values)) {
209
241
  const { data, error } = await updateParam({ where: { id }, values });
210
242
  if (error) {
211
- handleSetError(error, params, false);
243
+ await handleSetError(error, params, input, updateParam, "update");
212
244
  }
213
245
  return data;
214
246
  }
215
247
  } : void 0;
216
- const deleteFn = deleteParam ? async ({ id }, params) => {
217
- const { data, error } = await deleteParam({ id });
248
+ const deleteFn = deleteParam ? async (value, params) => {
249
+ const { data, error } = await deleteParam({ id: value.id });
218
250
  if (error) {
219
- handleSetError(error, params, false);
251
+ await handleSetError(error, params, value, deleteParam, "delete");
220
252
  }
221
253
  return data;
222
254
  } : void 0;
223
- const subscribe = (params) => {
255
+ if (realtime) {
256
+ setupRealtime(props);
257
+ }
258
+ const subscribe = realtime ? (params) => {
224
259
  let unsubscribe = void 0;
225
260
  when(subscribeFnKey$, () => {
226
261
  unsubscribe = subscribeFn(params);
227
262
  });
263
+ const unsubscribeParam = subscribeParam == null ? void 0 : subscribeParam(params);
228
264
  return () => {
229
265
  unsubscribe == null ? void 0 : unsubscribe();
266
+ unsubscribeParam == null ? void 0 : unsubscribeParam();
230
267
  };
231
- };
268
+ } : subscribeParam;
232
269
  return syncedCrud({
233
270
  ...rest,
234
271
  as: asType,
272
+ mode: mode || "merge",
235
273
  list,
236
274
  create,
237
275
  update,
238
276
  delete: deleteFn,
239
- waitFor: () => isEnabled$.get() && (waitFor ? computeSelector(waitFor) : true),
277
+ waitFor: () => {
278
+ ensureAuthToken(props);
279
+ return [requireAuth ? isAuthed$ : true, waitFor || true];
280
+ },
281
+ waitForSet: (params) => {
282
+ ensureAuthToken(props);
283
+ return [
284
+ requireAuth ? isAuthed$ : true,
285
+ () => waitForSet ? isFunction(waitForSet) ? waitForSet(params) : waitForSet : true
286
+ ];
287
+ },
240
288
  onSaved,
241
289
  fieldCreatedAt,
242
290
  fieldUpdatedAt,
243
- fieldDeleted: fieldDeleted || "deleted",
291
+ fieldDeleted,
244
292
  changesSince,
245
293
  updatePartial: true,
246
294
  subscribe,
@@ -250,4 +298,4 @@ function syncedKeel(props) {
250
298
  });
251
299
  }
252
300
 
253
- export { KeelKeys, configureSyncedKeel, generateKeelId, getSyncedKeelConfiguration, syncedKeel };
301
+ export { KeelKeys, generateKeelId, syncedKeel };
@@ -2,12 +2,17 @@ import { Observable } from '@legendapp/state';
2
2
  import { SyncedOptions, SyncedOptionsGlobal, SyncedGetParams } from '@legendapp/state/sync';
3
3
  import { SyncedCrudPropsBase, CrudAsOption, SyncedCrudReturnType, SyncedCrudPropsMany } from '@legendapp/state/sync-plugins/crud';
4
4
  import { PostgrestQueryBuilder, PostgrestFilterBuilder } from '@supabase/postgrest-js';
5
- import { SupabaseClient } from '@supabase/supabase-js';
5
+ import { SupabaseClient, PostgrestSingleResponse } from '@supabase/supabase-js';
6
+ import { FunctionsResponse } from '@supabase/functions-js';
6
7
 
8
+ type DatabaseOf<Client extends SupabaseClient> = Client extends SupabaseClient<infer TDB> ? TDB : never;
9
+ type SchemaNameOf<Client extends SupabaseClient> = keyof DatabaseOf<Client>;
10
+ type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
11
+ type IsUnionOfStrings<T> = [T] extends [string] ? ([T] extends [UnionToIntersection<T>] ? false : true) : false;
7
12
  type SupabaseSchemaOf<Client extends SupabaseClient> = Client extends SupabaseClient<infer _, infer __, infer Schema> ? Schema : never;
8
- type SupabaseTableOf<Client extends SupabaseClient> = SupabaseSchemaOf<Client>['Tables'];
9
- type SupabaseCollectionOf<Client extends SupabaseClient> = keyof SupabaseTableOf<Client>;
10
- type SupabaseRowOf<Client extends SupabaseClient, Collection extends SupabaseCollectionOf<Client>> = SupabaseTableOf<Client>[Collection]['Row'];
13
+ type SupabaseTableOf<Client extends SupabaseClient, SchemaName extends SchemaNameOf<Client>> = DatabaseOf<Client>[SchemaName]['Tables'];
14
+ type SupabaseCollectionOf<Client extends SupabaseClient, SchemaName extends SchemaNameOf<Client>> = keyof SupabaseTableOf<Client, IsUnionOfStrings<SchemaName> extends true ? 'public' : SchemaName>;
15
+ type SupabaseRowOf<Client extends SupabaseClient, Collection extends SupabaseCollectionOf<Client, SchemaName>, SchemaName extends SchemaNameOf<Client>> = SupabaseTableOf<Client, SchemaName>[Collection]['Row'];
11
16
  type SyncedSupabaseConfig<TRemote extends {
12
17
  id: string | number;
13
18
  }, TLocal> = Omit<SyncedCrudPropsBase<TRemote, TLocal>, 'create' | 'update' | 'delete'>;
@@ -20,20 +25,25 @@ interface SyncedSupabaseConfiguration extends Omit<SyncedSupabaseConfig<{
20
25
  enabled?: Observable<boolean>;
21
26
  as?: Exclude<CrudAsOption, 'value'>;
22
27
  }
23
- interface SyncedSupabaseProps<Client extends SupabaseClient, Collection extends SupabaseCollectionOf<Client>, TOption extends CrudAsOption = 'object', TRemote extends SupabaseRowOf<Client, Collection> = SupabaseRowOf<Client, Collection>, TLocal = TRemote> extends SyncedSupabaseConfig<TRemote, TLocal>, SyncedCrudPropsMany<TRemote, TRemote, TOption> {
24
- supabase: Client;
28
+ interface SyncedSupabaseProps<Client extends SupabaseClient<any, any>, Collection extends SupabaseCollectionOf<Client, SchemaName>, SchemaName extends SchemaNameOf<Client> = 'public', TOption extends CrudAsOption = 'object', TRemote extends SupabaseRowOf<Client, Collection, SchemaName> = SupabaseRowOf<Client, Collection, SchemaName>, TLocal = TRemote> extends SyncedSupabaseConfig<TRemote, TLocal>, Omit<SyncedCrudPropsMany<TRemote, TRemote, TOption>, 'list'> {
29
+ supabase?: Client;
25
30
  collection: Collection;
26
- select?: (query: PostgrestQueryBuilder<SupabaseSchemaOf<Client>, SupabaseTableOf<Client>[Collection], Collection>) => PostgrestFilterBuilder<SupabaseSchemaOf<Client>, TRemote, TRemote[], Collection, []>;
27
- filter?: (select: PostgrestFilterBuilder<SupabaseSchemaOf<Client>, TRemote, TRemote[], Collection, []>, params: SyncedGetParams) => PostgrestFilterBuilder<SupabaseSchemaOf<Client>, TRemote, TRemote[], Collection, []>;
31
+ schema?: SchemaName;
32
+ select?: (query: PostgrestQueryBuilder<SupabaseSchemaOf<Client>, SupabaseTableOf<Client, SchemaName>[Collection], Collection>) => PostgrestFilterBuilder<SupabaseSchemaOf<Client>, TRemote, TRemote[], Collection, []>;
33
+ filter?: (select: PostgrestFilterBuilder<SupabaseSchemaOf<Client>, TRemote, TRemote[], Collection, []>, params: SyncedGetParams<TRemote>) => PostgrestFilterBuilder<SupabaseSchemaOf<Client>, TRemote, TRemote[], Collection, []>;
28
34
  actions?: ('create' | 'read' | 'update' | 'delete')[];
29
35
  realtime?: boolean | {
30
36
  schema?: string;
31
37
  filter?: string;
32
38
  };
33
39
  stringifyDates?: boolean;
40
+ list?: (...params: Parameters<Required<SyncedCrudPropsMany<TRemote, TLocal, TOption>>['list']>) => PromiseLike<PostgrestSingleResponse<TRemote[]>> | Promise<FunctionsResponse<NoInfer<TRemote>[]>>;
41
+ create?: (...params: Parameters<Required<SyncedCrudPropsBase<TRemote>>['create']>) => PromiseLike<PostgrestSingleResponse<TRemote>> | Promise<FunctionsResponse<NoInfer<TRemote>>>;
42
+ update?: (...params: Parameters<Required<SyncedCrudPropsBase<TRemote>>['update']>) => PromiseLike<PostgrestSingleResponse<TRemote>> | Promise<FunctionsResponse<NoInfer<TRemote>>>;
43
+ delete?: (...params: Parameters<Required<SyncedCrudPropsBase<TRemote>>['delete']>) => PromiseLike<PostgrestSingleResponse<TRemote>> | Promise<FunctionsResponse<NoInfer<TRemote>>>;
34
44
  }
35
45
  declare function getSyncedSupabaseConfiguration(): SyncedSupabaseConfiguration;
36
46
  declare function configureSyncedSupabase(config: SyncedSupabaseConfiguration): void;
37
- declare function syncedSupabase<Client extends SupabaseClient, Collection extends SupabaseCollectionOf<Client> & string, AsOption extends CrudAsOption = 'object', TRemote extends SupabaseRowOf<Client, Collection> = SupabaseRowOf<Client, Collection>, TLocal = TRemote>(props: SyncedSupabaseProps<Client, Collection, AsOption, TRemote, TLocal>): SyncedCrudReturnType<TLocal, AsOption>;
47
+ declare function syncedSupabase<Client extends SupabaseClient<any, any>, Collection extends SupabaseCollectionOf<Client, SchemaName> & string, SchemaName extends SchemaNameOf<Client> = 'public', AsOption extends CrudAsOption = 'object', TRemote extends SupabaseRowOf<Client, Collection, SchemaName> = SupabaseRowOf<Client, Collection, SchemaName>, TLocal = TRemote>(props: SyncedSupabaseProps<Client, Collection, SchemaName, AsOption, TRemote, TLocal>): SyncedCrudReturnType<TLocal, AsOption>;
38
48
 
39
49
  export { type SupabaseCollectionOf, type SupabaseRowOf, type SupabaseSchemaOf, type SupabaseTableOf, type SyncedSupabaseConfig, type SyncedSupabaseConfiguration, configureSyncedSupabase, getSyncedSupabaseConfiguration, syncedSupabase };
@@ -2,12 +2,17 @@ import { Observable } from '@legendapp/state';
2
2
  import { SyncedOptions, SyncedOptionsGlobal, SyncedGetParams } from '@legendapp/state/sync';
3
3
  import { SyncedCrudPropsBase, CrudAsOption, SyncedCrudReturnType, SyncedCrudPropsMany } from '@legendapp/state/sync-plugins/crud';
4
4
  import { PostgrestQueryBuilder, PostgrestFilterBuilder } from '@supabase/postgrest-js';
5
- import { SupabaseClient } from '@supabase/supabase-js';
5
+ import { SupabaseClient, PostgrestSingleResponse } from '@supabase/supabase-js';
6
+ import { FunctionsResponse } from '@supabase/functions-js';
6
7
 
8
+ type DatabaseOf<Client extends SupabaseClient> = Client extends SupabaseClient<infer TDB> ? TDB : never;
9
+ type SchemaNameOf<Client extends SupabaseClient> = keyof DatabaseOf<Client>;
10
+ type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
11
+ type IsUnionOfStrings<T> = [T] extends [string] ? ([T] extends [UnionToIntersection<T>] ? false : true) : false;
7
12
  type SupabaseSchemaOf<Client extends SupabaseClient> = Client extends SupabaseClient<infer _, infer __, infer Schema> ? Schema : never;
8
- type SupabaseTableOf<Client extends SupabaseClient> = SupabaseSchemaOf<Client>['Tables'];
9
- type SupabaseCollectionOf<Client extends SupabaseClient> = keyof SupabaseTableOf<Client>;
10
- type SupabaseRowOf<Client extends SupabaseClient, Collection extends SupabaseCollectionOf<Client>> = SupabaseTableOf<Client>[Collection]['Row'];
13
+ type SupabaseTableOf<Client extends SupabaseClient, SchemaName extends SchemaNameOf<Client>> = DatabaseOf<Client>[SchemaName]['Tables'];
14
+ type SupabaseCollectionOf<Client extends SupabaseClient, SchemaName extends SchemaNameOf<Client>> = keyof SupabaseTableOf<Client, IsUnionOfStrings<SchemaName> extends true ? 'public' : SchemaName>;
15
+ type SupabaseRowOf<Client extends SupabaseClient, Collection extends SupabaseCollectionOf<Client, SchemaName>, SchemaName extends SchemaNameOf<Client>> = SupabaseTableOf<Client, SchemaName>[Collection]['Row'];
11
16
  type SyncedSupabaseConfig<TRemote extends {
12
17
  id: string | number;
13
18
  }, TLocal> = Omit<SyncedCrudPropsBase<TRemote, TLocal>, 'create' | 'update' | 'delete'>;
@@ -20,20 +25,25 @@ interface SyncedSupabaseConfiguration extends Omit<SyncedSupabaseConfig<{
20
25
  enabled?: Observable<boolean>;
21
26
  as?: Exclude<CrudAsOption, 'value'>;
22
27
  }
23
- interface SyncedSupabaseProps<Client extends SupabaseClient, Collection extends SupabaseCollectionOf<Client>, TOption extends CrudAsOption = 'object', TRemote extends SupabaseRowOf<Client, Collection> = SupabaseRowOf<Client, Collection>, TLocal = TRemote> extends SyncedSupabaseConfig<TRemote, TLocal>, SyncedCrudPropsMany<TRemote, TRemote, TOption> {
24
- supabase: Client;
28
+ interface SyncedSupabaseProps<Client extends SupabaseClient<any, any>, Collection extends SupabaseCollectionOf<Client, SchemaName>, SchemaName extends SchemaNameOf<Client> = 'public', TOption extends CrudAsOption = 'object', TRemote extends SupabaseRowOf<Client, Collection, SchemaName> = SupabaseRowOf<Client, Collection, SchemaName>, TLocal = TRemote> extends SyncedSupabaseConfig<TRemote, TLocal>, Omit<SyncedCrudPropsMany<TRemote, TRemote, TOption>, 'list'> {
29
+ supabase?: Client;
25
30
  collection: Collection;
26
- select?: (query: PostgrestQueryBuilder<SupabaseSchemaOf<Client>, SupabaseTableOf<Client>[Collection], Collection>) => PostgrestFilterBuilder<SupabaseSchemaOf<Client>, TRemote, TRemote[], Collection, []>;
27
- filter?: (select: PostgrestFilterBuilder<SupabaseSchemaOf<Client>, TRemote, TRemote[], Collection, []>, params: SyncedGetParams) => PostgrestFilterBuilder<SupabaseSchemaOf<Client>, TRemote, TRemote[], Collection, []>;
31
+ schema?: SchemaName;
32
+ select?: (query: PostgrestQueryBuilder<SupabaseSchemaOf<Client>, SupabaseTableOf<Client, SchemaName>[Collection], Collection>) => PostgrestFilterBuilder<SupabaseSchemaOf<Client>, TRemote, TRemote[], Collection, []>;
33
+ filter?: (select: PostgrestFilterBuilder<SupabaseSchemaOf<Client>, TRemote, TRemote[], Collection, []>, params: SyncedGetParams<TRemote>) => PostgrestFilterBuilder<SupabaseSchemaOf<Client>, TRemote, TRemote[], Collection, []>;
28
34
  actions?: ('create' | 'read' | 'update' | 'delete')[];
29
35
  realtime?: boolean | {
30
36
  schema?: string;
31
37
  filter?: string;
32
38
  };
33
39
  stringifyDates?: boolean;
40
+ list?: (...params: Parameters<Required<SyncedCrudPropsMany<TRemote, TLocal, TOption>>['list']>) => PromiseLike<PostgrestSingleResponse<TRemote[]>> | Promise<FunctionsResponse<NoInfer<TRemote>[]>>;
41
+ create?: (...params: Parameters<Required<SyncedCrudPropsBase<TRemote>>['create']>) => PromiseLike<PostgrestSingleResponse<TRemote>> | Promise<FunctionsResponse<NoInfer<TRemote>>>;
42
+ update?: (...params: Parameters<Required<SyncedCrudPropsBase<TRemote>>['update']>) => PromiseLike<PostgrestSingleResponse<TRemote>> | Promise<FunctionsResponse<NoInfer<TRemote>>>;
43
+ delete?: (...params: Parameters<Required<SyncedCrudPropsBase<TRemote>>['delete']>) => PromiseLike<PostgrestSingleResponse<TRemote>> | Promise<FunctionsResponse<NoInfer<TRemote>>>;
34
44
  }
35
45
  declare function getSyncedSupabaseConfiguration(): SyncedSupabaseConfiguration;
36
46
  declare function configureSyncedSupabase(config: SyncedSupabaseConfiguration): void;
37
- declare function syncedSupabase<Client extends SupabaseClient, Collection extends SupabaseCollectionOf<Client> & string, AsOption extends CrudAsOption = 'object', TRemote extends SupabaseRowOf<Client, Collection> = SupabaseRowOf<Client, Collection>, TLocal = TRemote>(props: SyncedSupabaseProps<Client, Collection, AsOption, TRemote, TLocal>): SyncedCrudReturnType<TLocal, AsOption>;
47
+ declare function syncedSupabase<Client extends SupabaseClient<any, any>, Collection extends SupabaseCollectionOf<Client, SchemaName> & string, SchemaName extends SchemaNameOf<Client> = 'public', AsOption extends CrudAsOption = 'object', TRemote extends SupabaseRowOf<Client, Collection, SchemaName> = SupabaseRowOf<Client, Collection, SchemaName>, TLocal = TRemote>(props: SyncedSupabaseProps<Client, Collection, SchemaName, AsOption, TRemote, TLocal>): SyncedCrudReturnType<TLocal, AsOption>;
38
48
 
39
49
  export { type SupabaseCollectionOf, type SupabaseRowOf, type SupabaseSchemaOf, type SupabaseTableOf, type SyncedSupabaseConfig, type SyncedSupabaseConfiguration, configureSyncedSupabase, getSyncedSupabaseConfiguration, syncedSupabase };
@@ -18,12 +18,22 @@ function configureSyncedSupabase(config) {
18
18
  }
19
19
  Object.assign(supabaseConfig, sync.removeNullUndefined(rest));
20
20
  }
21
+ function wrapSupabaseFn(fn) {
22
+ return async (...args) => {
23
+ const { data, error } = await fn(...args);
24
+ if (error) {
25
+ throw new Error(error.message);
26
+ }
27
+ return data;
28
+ };
29
+ }
21
30
  function syncedSupabase(props) {
22
31
  props = { ...supabaseConfig, ...props };
23
32
  const {
24
- supabase: client,
33
+ supabase,
25
34
  collection,
26
35
  select: selectFn,
36
+ schema,
27
37
  filter,
28
38
  actions,
29
39
  fieldCreatedAt: fieldCreatedAtParam,
@@ -36,14 +46,21 @@ function syncedSupabase(props) {
36
46
  waitFor,
37
47
  waitForSet,
38
48
  generateId,
49
+ mode,
50
+ list: listParam,
51
+ create: createParam,
52
+ update: updateParam,
53
+ delete: deleteParam,
39
54
  ...rest
40
55
  } = props;
56
+ const client = supabase;
41
57
  const fieldCreatedAt = fieldCreatedAtParam || (changesSince === "last-sync" ? "created_at" : void 0);
42
58
  const fieldUpdatedAt = fieldUpdatedAtParam || (changesSince === "last-sync" ? "updated_at" : void 0);
43
59
  const fieldDeleted = fieldDeletedParam || (changesSince === "last-sync" ? "deleted" : void 0);
44
- const list = !actions || actions.includes("read") ? async (params) => {
60
+ const list = !actions || actions.includes("read") ? listParam ? wrapSupabaseFn(listParam) : async (params) => {
45
61
  const { lastSync } = params;
46
- const from = client.from(collection);
62
+ const clientSchema = schema ? client.schema(schema) : client;
63
+ const from = clientSchema.from(collection);
47
64
  let select = selectFn ? selectFn(from) : from.select();
48
65
  if (changesSince === "last-sync" && lastSync) {
49
66
  const date = new Date(lastSync).toISOString();
@@ -58,8 +75,8 @@ function syncedSupabase(props) {
58
75
  }
59
76
  return data || [];
60
77
  } : void 0;
61
- const upsert = async (input) => {
62
- const res = await client.from(collection).upsert(input).select();
78
+ const create = createParam ? wrapSupabaseFn(createParam) : !actions || actions.includes("create") ? async (input) => {
79
+ const res = await client.from(collection).insert(input).select();
63
80
  const { data, error } = res;
64
81
  if (data) {
65
82
  const created = data[0];
@@ -67,10 +84,18 @@ function syncedSupabase(props) {
67
84
  } else {
68
85
  throw new Error(error == null ? void 0 : error.message);
69
86
  }
70
- };
71
- const create = !actions || actions.includes("create") ? upsert : void 0;
72
- const update = !actions || actions.includes("update") ? upsert : void 0;
73
- const deleteFn = !fieldDeleted && (!actions || actions.includes("delete")) ? async (input) => {
87
+ } : void 0;
88
+ const update = !actions || actions.includes("update") ? updateParam ? wrapSupabaseFn(updateParam) : async (input) => {
89
+ const res = await client.from(collection).update(input).eq("id", input.id).select();
90
+ const { data, error } = res;
91
+ if (data) {
92
+ const created = data[0];
93
+ return created;
94
+ } else {
95
+ throw new Error(error == null ? void 0 : error.message);
96
+ }
97
+ } : void 0;
98
+ const deleteFn = !fieldDeleted && (!actions || actions.includes("delete")) ? deleteParam ? wrapSupabaseFn(deleteParam) : async (input) => {
74
99
  const id = input.id;
75
100
  const res = await client.from(collection).delete().eq("id", id).select();
76
101
  const { data, error } = res;
@@ -82,13 +107,13 @@ function syncedSupabase(props) {
82
107
  }
83
108
  } : void 0;
84
109
  const subscribe = realtime ? ({ node, value$, update: update2 }) => {
85
- const { filter: filter2, schema } = state.isObject(realtime) ? realtime : {};
110
+ const { filter: filter2, schema: schema2 } = state.isObject(realtime) ? realtime : {};
86
111
  const channel = client.channel(`LS_${node.key || ""}${channelNum++}`).on(
87
112
  "postgres_changes",
88
113
  {
89
114
  event: "*",
90
115
  table: collection,
91
- schema: schema || "public",
116
+ schema: schema2 || "public",
92
117
  filter: filter2 || void 0
93
118
  },
94
119
  (payload) => {
@@ -96,25 +121,30 @@ function syncedSupabase(props) {
96
121
  const { eventType, new: value, old } = payload;
97
122
  if (eventType === "INSERT" || eventType === "UPDATE") {
98
123
  const cur = (_a = value$.peek()) == null ? void 0 : _a[value.id];
99
- const curDateStr = cur && (fieldUpdatedAt && cur[fieldUpdatedAt] || fieldCreatedAt || cur[fieldCreatedAt]);
100
- const valueDateStr = fieldUpdatedAt && value[fieldUpdatedAt] || fieldCreatedAt && value[fieldCreatedAt];
101
- const valueDate = +new Date(valueDateStr);
102
- if (valueDateStr && (!curDateStr || valueDate > +new Date(curDateStr))) {
124
+ let isOk = !fieldUpdatedAt;
125
+ let lastSync = void 0;
126
+ if (!isOk) {
127
+ const curDateStr = cur && (fieldUpdatedAt && cur[fieldUpdatedAt] || fieldCreatedAt || cur[fieldCreatedAt]);
128
+ const valueDateStr = fieldUpdatedAt && value[fieldUpdatedAt] || fieldCreatedAt && value[fieldCreatedAt];
129
+ lastSync = +new Date(valueDateStr);
130
+ isOk = valueDateStr && (!curDateStr || lastSync > +new Date(curDateStr));
131
+ }
132
+ if (isOk) {
103
133
  update2({
104
- value: { [value.id]: value },
105
- lastSync: valueDate,
134
+ value: [value],
135
+ lastSync,
106
136
  mode: "merge"
107
137
  });
108
138
  }
109
139
  } else if (eventType === "DELETE") {
110
- const { id } = old;
140
+ old[state.symbolDelete] = true;
111
141
  update2({
112
- value: { [id]: state.symbolDelete }
142
+ value: [old]
113
143
  });
114
144
  }
115
145
  }
116
146
  ).subscribe();
117
- return channel.unsubscribe;
147
+ return () => channel.unsubscribe();
118
148
  } : void 0;
119
149
  let transform = transformParam;
120
150
  if (stringifyDates) {
@@ -123,6 +153,7 @@ function syncedSupabase(props) {
123
153
  }
124
154
  return crud.syncedCrud({
125
155
  ...rest,
156
+ mode: mode || "merge",
126
157
  list,
127
158
  create,
128
159
  update,
@@ -135,7 +166,7 @@ function syncedSupabase(props) {
135
166
  transform,
136
167
  generateId,
137
168
  waitFor: () => isEnabled$.get() && (waitFor ? state.computeSelector(waitFor) : true),
138
- waitForSet
169
+ waitForSet: (params) => isEnabled$.get() && (waitForSet ? state.isFunction(waitForSet) ? waitForSet(params) : waitForSet : true)
139
170
  });
140
171
  }
141
172