@legendapp/state 2.2.0-next.78 → 2.2.0-next.79
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.d.ts +0 -2
- package/index.js +10 -0
- package/index.js.map +1 -1
- package/index.mjs +10 -0
- package/index.mjs.map +1 -1
- package/package.json +1 -1
- package/persist-plugins/async-storage.d.ts +2 -1
- package/persist-plugins/fetch.d.ts +1 -1
- package/persist-plugins/fetch.js.map +1 -1
- package/persist-plugins/fetch.mjs.map +1 -1
- package/persist-plugins/firebase.d.ts +1 -1
- package/persist-plugins/firebase.js.map +1 -1
- package/persist-plugins/firebase.mjs.map +1 -1
- package/persist-plugins/indexeddb.d.ts +2 -1
- package/persist-plugins/indexeddb.js.map +1 -1
- package/persist-plugins/indexeddb.mjs.map +1 -1
- package/persist-plugins/local-storage.d.ts +2 -1
- package/persist-plugins/local-storage.js.map +1 -1
- package/persist-plugins/local-storage.mjs.map +1 -1
- package/persist-plugins/mmkv.d.ts +2 -1
- package/persist-plugins/mmkv.js.map +1 -1
- package/persist-plugins/mmkv.mjs.map +1 -1
- package/persist-plugins/query.d.ts +1 -1
- package/persist-plugins/query.js.map +1 -1
- package/persist-plugins/query.mjs.map +1 -1
- package/persist.d.ts +1 -1
- package/persist.js +4 -6
- package/persist.js.map +1 -1
- package/persist.mjs +4 -6
- package/persist.mjs.map +1 -1
- package/react-hooks/usePersistedObservable.d.ts +2 -1
- package/react-hooks/usePersistedObservable.js.map +1 -1
- package/react-hooks/usePersistedObservable.mjs.map +1 -1
- package/src/ObservableObject.ts +12 -0
- package/src/globals.ts +1 -1
- package/src/observable.ts +1 -1
- package/src/observableInterfaces.ts +2 -20
- package/src/persist/configureObservablePersistence.ts +1 -1
- package/src/persist/fieldTransformer.ts +1 -1
- package/src/persist/observablePersistRemoteFunctionsAdapter.ts +2 -2
- package/src/persist/persistObservable.ts +12 -10
- package/src/persist-plugins/async-storage.ts +2 -2
- package/src/persist-plugins/fetch.ts +2 -1
- package/src/persist-plugins/firebase.ts +8 -6
- package/src/persist-plugins/indexeddb.ts +2 -3
- package/src/persist-plugins/local-storage.ts +2 -1
- package/src/persist-plugins/mmkv.ts +2 -1
- package/src/persist-plugins/query.ts +2 -6
- package/src/react-hooks/usePersistedObservable.ts +2 -1
- package/src/retry.ts +2 -1
- package/src/sync/activateSyncedNode.ts +3 -5
- package/src/sync/configureObservableSync.ts +1 -1
- package/src/{persistTypes.ts → sync/persistTypes.ts} +4 -4
- package/src/sync/syncObservable.ts +20 -20
- package/src/sync/syncObservableAdapter.ts +2 -1
- package/src/{syncTypes.ts → sync/syncTypes.ts} +19 -5
- package/src/sync/synced.ts +1 -1
- package/src/sync-plugins/crud.ts +28 -11
- package/src/sync-plugins/fetch.ts +2 -2
- package/src/sync-plugins/keel.ts +52 -47
- package/src/sync-plugins/supabase.ts +47 -31
- package/sync-plugins/crud.d.ts +2 -1
- package/sync-plugins/crud.js +19 -4
- package/sync-plugins/crud.js.map +1 -1
- package/sync-plugins/crud.mjs +19 -4
- package/sync-plugins/crud.mjs.map +1 -1
- package/sync-plugins/fetch.d.ts +2 -1
- package/sync-plugins/keel.d.ts +5 -5
- package/sync-plugins/keel.js +29 -30
- package/sync-plugins/keel.js.map +1 -1
- package/sync-plugins/keel.mjs +29 -30
- package/sync-plugins/keel.mjs.map +1 -1
- package/sync-plugins/supabase.d.ts +14 -11
- package/sync-plugins/supabase.js +7 -3
- package/sync-plugins/supabase.js.map +1 -1
- package/sync-plugins/supabase.mjs +7 -3
- package/sync-plugins/supabase.mjs.map +1 -1
- package/sync.d.ts +4 -2
- package/sync.js +4 -6
- package/sync.js.map +1 -1
- package/sync.mjs +5 -7
- package/sync.mjs.map +1 -1
|
@@ -1,14 +1,12 @@
|
|
|
1
|
+
import type { NodeValue, Observable, UpdateFn } from '@legendapp/state';
|
|
1
2
|
import type {
|
|
2
|
-
NodeValue,
|
|
3
|
-
Observable,
|
|
4
|
-
ObservablePersistState,
|
|
5
3
|
ObservableSyncFunctions,
|
|
6
4
|
ObservableSyncGetParams,
|
|
7
5
|
ObservableSyncSetParams,
|
|
8
6
|
SyncedGetParams,
|
|
9
7
|
SyncedSetParams,
|
|
10
|
-
|
|
11
|
-
} from '
|
|
8
|
+
} from './syncTypes';
|
|
9
|
+
import type { ObservablePersistState } from './persistTypes';
|
|
12
10
|
import { internal, isFunction, isPromise, mergeIntoObservable, whenReady } from '@legendapp/state';
|
|
13
11
|
import { syncObservable } from './syncObservable';
|
|
14
12
|
const { getProxy, globalState, runWithRetry, symbolLinked, setNodeValue, getNodeValue } = internal;
|
|
@@ -6,18 +6,18 @@ import type { AsyncStorageStatic } from '@react-native-async-storage/async-stora
|
|
|
6
6
|
// @ts-ignore
|
|
7
7
|
import type { DatabaseReference, Query } from 'firebase/database';
|
|
8
8
|
|
|
9
|
-
import type { GetMode, PersistMetadata } from './syncTypes';
|
|
10
9
|
import type {
|
|
11
10
|
ArrayValue,
|
|
12
11
|
Change,
|
|
13
12
|
ClassConstructor,
|
|
13
|
+
GetMode,
|
|
14
14
|
LinkedOptions,
|
|
15
15
|
RecordValue,
|
|
16
|
-
RetryOptions,
|
|
17
16
|
Selector,
|
|
18
17
|
TypeAtPath,
|
|
19
|
-
} from '
|
|
20
|
-
import type { Observable, ObservableParam, ObservableState } from '
|
|
18
|
+
} from '../observableInterfaces';
|
|
19
|
+
import type { Observable, ObservableParam, ObservableState } from '../observableTypes';
|
|
20
|
+
import type { PersistMetadata, RetryOptions } from './syncTypes';
|
|
21
21
|
|
|
22
22
|
export interface PersistTransform<TOrig = any, TSaved = TOrig> {
|
|
23
23
|
load?: (value: TSaved) => TOrig | Promise<TOrig>;
|
|
@@ -5,17 +5,7 @@ import type {
|
|
|
5
5
|
NodeValue,
|
|
6
6
|
Observable,
|
|
7
7
|
ObservableObject,
|
|
8
|
-
ObservableOnChangeParams,
|
|
9
8
|
ObservableParam,
|
|
10
|
-
ObservablePersistPlugin,
|
|
11
|
-
ObservableSyncClass,
|
|
12
|
-
ObservableSyncState,
|
|
13
|
-
PersistMetadata,
|
|
14
|
-
PersistOptions,
|
|
15
|
-
SyncTransform,
|
|
16
|
-
SyncTransformMethod,
|
|
17
|
-
Synced,
|
|
18
|
-
SyncedOptions,
|
|
19
9
|
TypeAtPath,
|
|
20
10
|
UpdateFnParams,
|
|
21
11
|
} from '@legendapp/state';
|
|
@@ -37,10 +27,22 @@ import {
|
|
|
37
27
|
when,
|
|
38
28
|
} from '@legendapp/state';
|
|
39
29
|
import { observableSyncConfiguration } from './configureObservableSync';
|
|
30
|
+
import type { ObservableOnChangeParams } from './persistTypes';
|
|
40
31
|
import { removeNullUndefined } from './syncHelpers';
|
|
41
32
|
import { syncObservableAdapter } from './syncObservableAdapter';
|
|
33
|
+
import type {
|
|
34
|
+
ObservablePersistPlugin,
|
|
35
|
+
ObservableSyncClass,
|
|
36
|
+
ObservableSyncState,
|
|
37
|
+
PersistMetadata,
|
|
38
|
+
PersistOptions,
|
|
39
|
+
SyncTransform,
|
|
40
|
+
SyncTransformMethod,
|
|
41
|
+
Synced,
|
|
42
|
+
SyncedOptions,
|
|
43
|
+
} from './syncTypes';
|
|
42
44
|
|
|
43
|
-
const { createPreviousHandler, getValueAtPath, globalState, symbolLinked, getNode, getNodeValue } = internal;
|
|
45
|
+
const { createPreviousHandler, clone, getValueAtPath, globalState, symbolLinked, getNode, getNodeValue } = internal;
|
|
44
46
|
|
|
45
47
|
export const mapSyncPlugins: WeakMap<
|
|
46
48
|
ClassConstructor<ObservablePersistPlugin | ObservableSyncClass>,
|
|
@@ -832,15 +834,13 @@ export function syncObservable<T>(
|
|
|
832
834
|
const node = getNode(obs$);
|
|
833
835
|
|
|
834
836
|
// Merge remote sync options with global options
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
);
|
|
843
|
-
}
|
|
837
|
+
syncOptions = mergeIntoObservable(
|
|
838
|
+
{
|
|
839
|
+
syncMode: 'auto',
|
|
840
|
+
} as SyncedOptions,
|
|
841
|
+
clone(observableSyncConfiguration),
|
|
842
|
+
removeNullUndefined(syncOptions || {}),
|
|
843
|
+
);
|
|
844
844
|
const localState: LocalState = {};
|
|
845
845
|
let sync: () => Promise<void>;
|
|
846
846
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isPromise } from '@legendapp/state';
|
|
2
|
+
import { ObservableSyncGetParams, SyncedOptions, type ObservableSyncClass } from '@legendapp/state/sync';
|
|
2
3
|
|
|
3
4
|
export function syncObservableAdapter<T = {}>({ get, set }: SyncedOptions<T>): ObservableSyncClass {
|
|
4
5
|
const ret: ObservableSyncClass = {};
|
|
@@ -7,14 +7,14 @@ import type { AsyncStorageStatic } from '@react-native-async-storage/async-stora
|
|
|
7
7
|
import {
|
|
8
8
|
Change,
|
|
9
9
|
ClassConstructor,
|
|
10
|
+
GetMode,
|
|
10
11
|
LinkedOptions,
|
|
11
12
|
NodeValue,
|
|
12
|
-
RetryOptions,
|
|
13
13
|
SetParams,
|
|
14
14
|
UpdateFn,
|
|
15
15
|
UpdateFnParams,
|
|
16
|
-
} from '
|
|
17
|
-
import { Observable, ObservableParam, ObservableState } from '
|
|
16
|
+
} from '../observableInterfaces';
|
|
17
|
+
import { Observable, ObservableParam, ObservableState } from '../observableTypes';
|
|
18
18
|
|
|
19
19
|
export interface PersistOptions<T = any> {
|
|
20
20
|
name: string;
|
|
@@ -54,8 +54,6 @@ export interface SyncedSubscribeParams {
|
|
|
54
54
|
refresh: () => void;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
export type GetMode = 'set' | 'assign' | 'merge' | 'append' | 'prepend';
|
|
58
|
-
|
|
59
57
|
export interface SyncedOptions<T = any> extends Omit<LinkedOptions<T>, 'get' | 'set'> {
|
|
60
58
|
get?: (params: SyncedGetParams) => Promise<T> | T;
|
|
61
59
|
set?: (params: SyncedSetParams<T>) => void | Promise<any>;
|
|
@@ -172,3 +170,19 @@ export interface ObservableSyncFunctions<T = any> {
|
|
|
172
170
|
params: ObservableSyncSetParams<T>,
|
|
173
171
|
): void | Promise<void | { changes?: object | undefined; dateModified?: number; lastSync?: number }>;
|
|
174
172
|
}
|
|
173
|
+
|
|
174
|
+
export interface RetryOptions {
|
|
175
|
+
infinite?: boolean;
|
|
176
|
+
times?: number;
|
|
177
|
+
delay?: number;
|
|
178
|
+
backoff?: 'constant' | 'exponential';
|
|
179
|
+
maxDelay?: number;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export interface SubscribeOptions {
|
|
183
|
+
node: NodeValue;
|
|
184
|
+
update: UpdateFn;
|
|
185
|
+
refresh: () => void;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export type Synced<T> = T;
|
package/src/sync/synced.ts
CHANGED
package/src/sync-plugins/crud.ts
CHANGED
|
@@ -1,17 +1,13 @@
|
|
|
1
|
+
import { internal, isArray, isNullOrUndefined, isNumber, isObject, isString } from '@legendapp/state';
|
|
1
2
|
import {
|
|
3
|
+
synced,
|
|
4
|
+
diffObjects,
|
|
2
5
|
SyncTransform,
|
|
3
6
|
SyncTransformMethod,
|
|
4
7
|
SyncedGetParams,
|
|
5
8
|
SyncedOptions,
|
|
6
9
|
SyncedSetParams,
|
|
7
|
-
|
|
8
|
-
isArray,
|
|
9
|
-
isNullOrUndefined,
|
|
10
|
-
isNumber,
|
|
11
|
-
isObject,
|
|
12
|
-
isString,
|
|
13
|
-
} from '@legendapp/state';
|
|
14
|
-
import { synced, diffObjects } from '@legendapp/state/sync';
|
|
10
|
+
} from '@legendapp/state/sync';
|
|
15
11
|
|
|
16
12
|
const { clone } = internal;
|
|
17
13
|
|
|
@@ -43,6 +39,7 @@ export interface SyncedCrudPropsBase<TRemote extends { id: string | number }, TL
|
|
|
43
39
|
fieldCreatedAt?: string;
|
|
44
40
|
updatePartial?: boolean;
|
|
45
41
|
changesSince?: 'all' | 'last-sync';
|
|
42
|
+
generateId?: () => string | number;
|
|
46
43
|
}
|
|
47
44
|
|
|
48
45
|
type InitialValue<T, TAsOption extends CrudAsOption> = TAsOption extends 'Map'
|
|
@@ -134,6 +131,13 @@ export function combineTransforms<T, T2>(
|
|
|
134
131
|
};
|
|
135
132
|
}
|
|
136
133
|
|
|
134
|
+
function ensureId(obj: { id: string | number }, generateId: () => string | number) {
|
|
135
|
+
if (!obj.id) {
|
|
136
|
+
obj.id = generateId();
|
|
137
|
+
}
|
|
138
|
+
return obj.id;
|
|
139
|
+
}
|
|
140
|
+
|
|
137
141
|
export function syncedCrud<TRemote extends { id: string | number }, TLocal = TRemote>(
|
|
138
142
|
props: SyncedCrudPropsBase<TRemote, TLocal> & SyncedCrudPropsSingle<TRemote, TLocal>,
|
|
139
143
|
): SyncedCrudReturnType<TLocal, 'first'>;
|
|
@@ -165,6 +169,7 @@ export function syncedCrud<
|
|
|
165
169
|
onSaved,
|
|
166
170
|
mode: modeParam,
|
|
167
171
|
changesSince,
|
|
172
|
+
generateId,
|
|
168
173
|
...rest
|
|
169
174
|
} = props;
|
|
170
175
|
|
|
@@ -242,9 +247,12 @@ export function syncedCrud<
|
|
|
242
247
|
changes.forEach(({ path, prevAtPath, valueAtPath }) => {
|
|
243
248
|
if (asType === 'first') {
|
|
244
249
|
if (value) {
|
|
245
|
-
|
|
250
|
+
let id = value?.id;
|
|
251
|
+
const isCreate = fieldCreatedAt ? !value[fieldCreatedAt!] : !prevAtPath;
|
|
252
|
+
if (!id && generateId) {
|
|
253
|
+
id = ensureId(value, generateId);
|
|
254
|
+
}
|
|
246
255
|
if (id) {
|
|
247
|
-
const isCreate = fieldCreatedAt ? !value[fieldCreatedAt!] : !prevAtPath;
|
|
248
256
|
if (isCreate || retryAsCreate) {
|
|
249
257
|
creates.set(id, value);
|
|
250
258
|
} else if (path.length === 0) {
|
|
@@ -308,6 +316,12 @@ export function syncedCrud<
|
|
|
308
316
|
? !item[fieldUpdatedAt]
|
|
309
317
|
: isCreateGuess;
|
|
310
318
|
if (isCreate) {
|
|
319
|
+
if (generateId) {
|
|
320
|
+
ensureId(item, generateId);
|
|
321
|
+
}
|
|
322
|
+
if (!item.id) {
|
|
323
|
+
console.error('[legend-state]: added item without an id');
|
|
324
|
+
}
|
|
311
325
|
if (createFn) {
|
|
312
326
|
creates.set(item.id, item);
|
|
313
327
|
} else {
|
|
@@ -362,7 +376,10 @@ export function syncedCrud<
|
|
|
362
376
|
}),
|
|
363
377
|
...Array.from(updates).map(([itemKey, itemValue]) => {
|
|
364
378
|
const toSave = updatePartial
|
|
365
|
-
?
|
|
379
|
+
? Object.assign(
|
|
380
|
+
diffObjects(asType === 'first' ? valuePrevious : valuePrevious[itemKey], itemValue),
|
|
381
|
+
{ id: (itemValue as any).id },
|
|
382
|
+
)
|
|
366
383
|
: itemValue;
|
|
367
384
|
const changed = transformOut(toSave as TLocal, transform?.save) as TRemote;
|
|
368
385
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Selector,
|
|
2
|
-
import { synced } from '@legendapp/state/sync';
|
|
1
|
+
import { Selector, computeSelector } from '@legendapp/state';
|
|
2
|
+
import { synced, SyncTransform, Synced, SyncedOptions, SyncedSetParams } from '@legendapp/state/sync';
|
|
3
3
|
|
|
4
4
|
export interface SyncedFetchProps<TRemote, TLocal> extends Omit<SyncedOptions, 'get' | 'set' | 'transform'> {
|
|
5
5
|
get: Selector<string>;
|
package/src/sync-plugins/keel.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
+
import { computeSelector, observable, when, internal } from '@legendapp/state';
|
|
1
2
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
internal,
|
|
5
|
-
observable,
|
|
6
|
-
when,
|
|
3
|
+
SyncedOptions,
|
|
4
|
+
removeNullUndefined,
|
|
7
5
|
type SyncedGetParams,
|
|
6
|
+
type SyncedSetParams,
|
|
8
7
|
type SyncedSubscribeParams,
|
|
9
|
-
} from '@legendapp/state';
|
|
8
|
+
} from '@legendapp/state/sync';
|
|
10
9
|
import {
|
|
11
10
|
CrudAsOption,
|
|
12
11
|
CrudResult,
|
|
@@ -54,7 +53,6 @@ interface ListGetParams {
|
|
|
54
53
|
refresh?: () => void;
|
|
55
54
|
after?: string;
|
|
56
55
|
first?: number;
|
|
57
|
-
maxResults?: number;
|
|
58
56
|
}
|
|
59
57
|
|
|
60
58
|
export interface KeelRealtimePlugin {
|
|
@@ -62,7 +60,20 @@ export interface KeelRealtimePlugin {
|
|
|
62
60
|
setLatestChange: (realtimeKey: string, time: Date) => void;
|
|
63
61
|
}
|
|
64
62
|
|
|
65
|
-
interface SyncedKeelConfiguration
|
|
63
|
+
export interface SyncedKeelConfiguration
|
|
64
|
+
extends Omit<
|
|
65
|
+
SyncedCrudPropsBase<any>,
|
|
66
|
+
| keyof SyncedOptions
|
|
67
|
+
| 'create'
|
|
68
|
+
| 'update'
|
|
69
|
+
| 'delete'
|
|
70
|
+
| 'onSaved'
|
|
71
|
+
| 'transform'
|
|
72
|
+
| 'updatePartial'
|
|
73
|
+
| 'subscribe'
|
|
74
|
+
| 'fieldCreatedAt'
|
|
75
|
+
| 'fieldUpdatedAt'
|
|
76
|
+
> {
|
|
66
77
|
client: {
|
|
67
78
|
auth: { refresh: () => Promise<boolean>; isAuthenticated: () => Promise<boolean> };
|
|
68
79
|
api: { queries: Record<string, (i: any) => Promise<any>> };
|
|
@@ -84,14 +95,14 @@ interface PageInfo {
|
|
|
84
95
|
interface SyncedKeelPropsMany<TRemote, TLocal, AOption extends CrudAsOption>
|
|
85
96
|
extends Omit<SyncedCrudPropsMany<TRemote, TLocal, AOption>, 'list'> {
|
|
86
97
|
list?: (params: ListGetParams) => Promise<CrudResult<APIResult<{ results: TRemote[]; pageInfo: any }>>>;
|
|
87
|
-
|
|
98
|
+
first?: number;
|
|
88
99
|
get?: never;
|
|
89
100
|
}
|
|
90
101
|
|
|
91
102
|
interface SyncedKeelPropsSingle<TRemote, TLocal> extends Omit<SyncedCrudPropsSingle<TRemote, TLocal>, 'get'> {
|
|
92
103
|
get?: (params: GetGetParams) => Promise<APIResult<TRemote>>;
|
|
93
104
|
|
|
94
|
-
|
|
105
|
+
first?: never;
|
|
95
106
|
list?: never;
|
|
96
107
|
as?: never;
|
|
97
108
|
}
|
|
@@ -103,18 +114,15 @@ interface SyncedKeelPropsBase<TRemote extends { id: string }, TLocal = TRemote>
|
|
|
103
114
|
delete?: (params: { id: string }) => Promise<APIResult<string>>;
|
|
104
115
|
}
|
|
105
116
|
|
|
106
|
-
|
|
107
|
-
let _asOption: CrudAsOption;
|
|
108
|
-
let _realtimePlugin: KeelRealtimePlugin;
|
|
109
|
-
let _onError: (error: APIResult<any>['error']) => void;
|
|
117
|
+
const keelConfig: SyncedKeelConfiguration = {} as SyncedKeelConfiguration;
|
|
110
118
|
const modifiedClients = new WeakSet<Record<string, any>>();
|
|
111
119
|
const isEnabled$ = observable(true);
|
|
112
120
|
|
|
113
121
|
async function ensureAuthToken() {
|
|
114
122
|
await when(isEnabled$.get());
|
|
115
|
-
let isAuthed = await
|
|
123
|
+
let isAuthed = await keelConfig.client.auth.isAuthenticated();
|
|
116
124
|
if (!isAuthed) {
|
|
117
|
-
isAuthed = await
|
|
125
|
+
isAuthed = await keelConfig.client.auth.refresh();
|
|
118
126
|
}
|
|
119
127
|
|
|
120
128
|
return isAuthed;
|
|
@@ -144,25 +152,17 @@ function convertObjectToCreate<TRemote, TLocal>(item: TRemote) {
|
|
|
144
152
|
return cloned as unknown as TLocal;
|
|
145
153
|
}
|
|
146
154
|
|
|
147
|
-
export function configureSyncedKeel({
|
|
148
|
-
realtimePlugin,
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
enabled,
|
|
152
|
-
onError,
|
|
153
|
-
}: SyncedKeelConfiguration) {
|
|
154
|
-
if (asOption) {
|
|
155
|
-
_asOption = asOption;
|
|
156
|
-
}
|
|
157
|
-
if (client) {
|
|
158
|
-
_client = client;
|
|
159
|
-
}
|
|
155
|
+
export function configureSyncedKeel(config: SyncedKeelConfiguration) {
|
|
156
|
+
const { enabled, realtimePlugin, ...rest } = config;
|
|
157
|
+
Object.assign(keelConfig, removeNullUndefined(rest));
|
|
158
|
+
|
|
160
159
|
if (enabled !== undefined) {
|
|
161
160
|
isEnabled$.set(enabled);
|
|
162
161
|
}
|
|
162
|
+
const { client } = keelConfig;
|
|
163
163
|
|
|
164
164
|
if (realtimePlugin) {
|
|
165
|
-
|
|
165
|
+
keelConfig.realtimePlugin = realtimePlugin;
|
|
166
166
|
if (client && !modifiedClients.has(client)) {
|
|
167
167
|
modifiedClients.add(client);
|
|
168
168
|
const queries = client.api.queries;
|
|
@@ -194,10 +194,6 @@ export function configureSyncedKeel({
|
|
|
194
194
|
});
|
|
195
195
|
}
|
|
196
196
|
}
|
|
197
|
-
|
|
198
|
-
if (onError) {
|
|
199
|
-
_onError = onError;
|
|
200
|
-
}
|
|
201
197
|
}
|
|
202
198
|
|
|
203
199
|
const NumPerPage = 200;
|
|
@@ -214,10 +210,10 @@ async function getAllPages<TRemote>(
|
|
|
214
210
|
let pageInfo: PageInfo | undefined = undefined;
|
|
215
211
|
let subscribe_;
|
|
216
212
|
|
|
217
|
-
const {
|
|
213
|
+
const { first: firstParam } = params;
|
|
218
214
|
|
|
219
215
|
do {
|
|
220
|
-
const first =
|
|
216
|
+
const first = firstParam ? Math.min(firstParam - allData.length, NumPerPage) : NumPerPage;
|
|
221
217
|
if (first < 1) {
|
|
222
218
|
break;
|
|
223
219
|
}
|
|
@@ -265,19 +261,24 @@ export function syncedKeel<TRemote extends { id: string }, TLocal = TRemote, TOp
|
|
|
265
261
|
create: createParam,
|
|
266
262
|
update: updateParam,
|
|
267
263
|
delete: deleteParam,
|
|
268
|
-
|
|
269
|
-
initial,
|
|
264
|
+
first,
|
|
270
265
|
waitFor,
|
|
271
266
|
waitForSet,
|
|
267
|
+
generateId: generateIdParam,
|
|
272
268
|
...rest
|
|
273
269
|
} = props;
|
|
274
270
|
|
|
271
|
+
const { changesSince } = props;
|
|
272
|
+
|
|
275
273
|
let asType = props.as as TOption;
|
|
276
274
|
|
|
277
275
|
if (!asType) {
|
|
278
|
-
asType = (getParam ? 'first' :
|
|
276
|
+
asType = (getParam ? 'first' : keelConfig.as || undefined) as TOption;
|
|
279
277
|
}
|
|
280
278
|
|
|
279
|
+
const generateId = generateIdParam || keelConfig.generateId;
|
|
280
|
+
|
|
281
|
+
const realtimePlugin = keelConfig.realtimePlugin;
|
|
281
282
|
let realtimeKeyList: string | undefined = undefined;
|
|
282
283
|
let realtimeKeyGet: string | undefined = undefined;
|
|
283
284
|
|
|
@@ -287,10 +288,13 @@ export function syncedKeel<TRemote extends { id: string }, TLocal = TRemote, TOp
|
|
|
287
288
|
const list = listParam
|
|
288
289
|
? async (listParams: SyncedGetParams) => {
|
|
289
290
|
const { lastSync, refresh } = listParams;
|
|
290
|
-
const queryBySync = !!lastSync;
|
|
291
|
+
const queryBySync = !!lastSync && changesSince === 'last-sync';
|
|
292
|
+
// If this is one of the customized functions for use with realtime then we need to pass
|
|
293
|
+
// the refresh function to it
|
|
291
294
|
const isRawRequest = (listParam || getParam).toString().includes('rawRequest');
|
|
295
|
+
// If querying with lastSync pass it to the "where" parameters
|
|
292
296
|
const where = queryBySync ? { updatedAt: { after: new Date(+new Date(lastSync) + 1) } } : {};
|
|
293
|
-
const params: ListGetParams = isRawRequest ? { where,
|
|
297
|
+
const params: ListGetParams = isRawRequest ? { where, first } : { where, refresh, first };
|
|
294
298
|
|
|
295
299
|
// TODO: Error?
|
|
296
300
|
const { results, subscribe } = await getAllPages(listParam, params);
|
|
@@ -342,12 +346,12 @@ export function syncedKeel<TRemote extends { id: string }, TLocal = TRemote, TOp
|
|
|
342
346
|
|
|
343
347
|
const updatedAt = data[fieldUpdatedAt as keyof TLocal] as Date;
|
|
344
348
|
|
|
345
|
-
if (updatedAt &&
|
|
349
|
+
if (updatedAt && realtimePlugin) {
|
|
346
350
|
if (realtimeKeyGet) {
|
|
347
|
-
|
|
351
|
+
realtimePlugin.setLatestChange(realtimeKeyGet, updatedAt);
|
|
348
352
|
}
|
|
349
353
|
if (realtimeKeyList) {
|
|
350
|
-
|
|
354
|
+
realtimePlugin.setLatestChange(realtimeKeyList, updatedAt);
|
|
351
355
|
}
|
|
352
356
|
}
|
|
353
357
|
|
|
@@ -372,7 +376,7 @@ export function syncedKeel<TRemote extends { id: string }, TLocal = TRemote, TOp
|
|
|
372
376
|
mode: 'assign',
|
|
373
377
|
});
|
|
374
378
|
} else if (error.type === 'bad_request') {
|
|
375
|
-
|
|
379
|
+
keelConfig.onError?.(error);
|
|
376
380
|
|
|
377
381
|
if (retryNum > 4) {
|
|
378
382
|
cancelRetry();
|
|
@@ -434,13 +438,14 @@ export function syncedKeel<TRemote extends { id: string }, TLocal = TRemote, TOp
|
|
|
434
438
|
create,
|
|
435
439
|
update,
|
|
436
440
|
delete: deleteFn,
|
|
437
|
-
retry: { infinite: true },
|
|
438
441
|
waitFor: () => isEnabled$.get() && (waitFor ? computeSelector(waitFor) : true),
|
|
439
442
|
waitForSet: () => isEnabled$.get() && (waitForSet ? computeSelector(waitForSet) : true),
|
|
440
443
|
onSaved,
|
|
441
444
|
fieldCreatedAt,
|
|
442
445
|
fieldUpdatedAt,
|
|
443
|
-
|
|
446
|
+
changesSince,
|
|
447
|
+
updatePartial: true,
|
|
448
|
+
generateId,
|
|
444
449
|
// @ts-expect-error This errors because of the get/list union type
|
|
445
450
|
get: get as any,
|
|
446
451
|
}) as SyncedCrudReturnType<TLocal, TOption>;
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Observable,
|
|
3
|
-
SyncedOptions,
|
|
4
|
-
SyncedOptionsGlobal,
|
|
5
3
|
computeSelector,
|
|
6
4
|
getNodeValue,
|
|
7
5
|
mergeIntoObservable,
|
|
8
6
|
observable,
|
|
9
7
|
symbolDelete,
|
|
8
|
+
} from '@legendapp/state';
|
|
9
|
+
import {
|
|
10
|
+
SyncedOptions,
|
|
11
|
+
SyncedOptionsGlobal,
|
|
12
|
+
removeNullUndefined,
|
|
10
13
|
type SyncedGetParams,
|
|
11
14
|
type SyncedSubscribeParams,
|
|
12
|
-
} from '@legendapp/state';
|
|
15
|
+
} from '@legendapp/state/sync';
|
|
13
16
|
import {
|
|
14
17
|
CrudAsOption,
|
|
15
18
|
SyncedCrudPropsBase,
|
|
@@ -17,25 +20,22 @@ import {
|
|
|
17
20
|
SyncedCrudReturnType,
|
|
18
21
|
syncedCrud,
|
|
19
22
|
} from '@legendapp/state/sync-plugins/crud';
|
|
20
|
-
import type { PostgrestFilterBuilder } from '@supabase/postgrest-js';
|
|
23
|
+
import type { PostgrestFilterBuilder, PostgrestQueryBuilder } from '@supabase/postgrest-js';
|
|
21
24
|
import type { SupabaseClient } from '@supabase/supabase-js';
|
|
22
25
|
|
|
23
26
|
// Unused types but maybe useful in the future so keeping them for now
|
|
24
|
-
// type DatabaseOf<
|
|
25
|
-
// type SchemaNameOf<
|
|
26
|
-
// ?
|
|
27
|
+
// type DatabaseOf<Client extends SupabaseClient> = Client extends SupabaseClient<infer TDB> ? TDB : never;
|
|
28
|
+
// type SchemaNameOf<Client extends SupabaseClient> = Client extends SupabaseClient<infer _, infer SchemaName>
|
|
29
|
+
// ? SchemaName
|
|
27
30
|
// : never;
|
|
28
31
|
|
|
29
32
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
30
|
-
type SchemaOf<
|
|
31
|
-
?
|
|
33
|
+
type SchemaOf<Client extends SupabaseClient> = Client extends SupabaseClient<infer _, infer __, infer Schema>
|
|
34
|
+
? Schema
|
|
32
35
|
: never;
|
|
33
|
-
type TableOf<
|
|
34
|
-
type CollectionOf<
|
|
35
|
-
type RowOf<
|
|
36
|
-
TClient extends SupabaseClient,
|
|
37
|
-
TCollection extends CollectionOf<TClient>,
|
|
38
|
-
> = TableOf<TClient>[TCollection]['Row'];
|
|
36
|
+
type TableOf<Client extends SupabaseClient> = SchemaOf<Client>['Tables'];
|
|
37
|
+
type CollectionOf<Client extends SupabaseClient> = keyof TableOf<Client>;
|
|
38
|
+
type RowOf<Client extends SupabaseClient, Collection extends CollectionOf<Client>> = TableOf<Client>[Collection]['Row'];
|
|
39
39
|
|
|
40
40
|
export type SyncedSupabaseConfig<T extends { id: string }> = Omit<
|
|
41
41
|
SyncedCrudPropsBase<T>,
|
|
@@ -50,27 +50,36 @@ export interface SyncedSupabaseGlobalConfig
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
interface SyncedSupabaseProps<
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
Client extends SupabaseClient,
|
|
54
|
+
Collection extends CollectionOf<Client>,
|
|
55
55
|
TOption extends CrudAsOption = 'object',
|
|
56
|
-
> extends SyncedSupabaseConfig<RowOf<
|
|
57
|
-
SyncedCrudPropsMany<RowOf<
|
|
58
|
-
supabase:
|
|
59
|
-
collection:
|
|
56
|
+
> extends SyncedSupabaseConfig<RowOf<Client, Collection>>,
|
|
57
|
+
SyncedCrudPropsMany<RowOf<Client, Collection>, RowOf<Client, Collection>, TOption> {
|
|
58
|
+
supabase: Client;
|
|
59
|
+
collection: Collection;
|
|
60
|
+
select?: (
|
|
61
|
+
query: PostgrestQueryBuilder<SchemaOf<Client>, TableOf<Client>[Collection], Collection>,
|
|
62
|
+
) => PostgrestFilterBuilder<
|
|
63
|
+
SchemaOf<Client>,
|
|
64
|
+
RowOf<Client, Collection>,
|
|
65
|
+
RowOf<Client, Collection>[],
|
|
66
|
+
Collection,
|
|
67
|
+
[]
|
|
68
|
+
>;
|
|
60
69
|
filter?: (
|
|
61
70
|
select: PostgrestFilterBuilder<
|
|
62
|
-
SchemaOf<
|
|
63
|
-
RowOf<
|
|
64
|
-
RowOf<
|
|
65
|
-
|
|
71
|
+
SchemaOf<Client>,
|
|
72
|
+
RowOf<Client, Collection>,
|
|
73
|
+
RowOf<Client, Collection>[],
|
|
74
|
+
Collection,
|
|
66
75
|
[]
|
|
67
76
|
>,
|
|
68
77
|
params: SyncedGetParams,
|
|
69
78
|
) => PostgrestFilterBuilder<
|
|
70
|
-
SchemaOf<
|
|
71
|
-
RowOf<
|
|
72
|
-
RowOf<
|
|
73
|
-
|
|
79
|
+
SchemaOf<Client>,
|
|
80
|
+
RowOf<Client, Collection>,
|
|
81
|
+
RowOf<Client, Collection>[],
|
|
82
|
+
Collection,
|
|
74
83
|
[]
|
|
75
84
|
>;
|
|
76
85
|
actions?: ('create' | 'read' | 'update' | 'delete')[];
|
|
@@ -86,7 +95,7 @@ export function configureSyncedSupabase(config: SyncedSupabaseGlobalConfig) {
|
|
|
86
95
|
if (enabled !== undefined) {
|
|
87
96
|
isEnabled$.set(enabled);
|
|
88
97
|
}
|
|
89
|
-
Object.assign(supabaseConfig, rest);
|
|
98
|
+
Object.assign(supabaseConfig, removeNullUndefined(rest));
|
|
90
99
|
}
|
|
91
100
|
|
|
92
101
|
export function syncedSupabase<
|
|
@@ -98,6 +107,7 @@ export function syncedSupabase<
|
|
|
98
107
|
const {
|
|
99
108
|
supabase: client,
|
|
100
109
|
collection,
|
|
110
|
+
select: selectFn,
|
|
101
111
|
filter,
|
|
102
112
|
actions,
|
|
103
113
|
fieldCreatedAt,
|
|
@@ -106,13 +116,18 @@ export function syncedSupabase<
|
|
|
106
116
|
changesSince,
|
|
107
117
|
waitFor,
|
|
108
118
|
waitForSet,
|
|
119
|
+
generateId: generateIdParam,
|
|
109
120
|
...rest
|
|
110
121
|
} = props;
|
|
122
|
+
|
|
123
|
+
const generateId = generateIdParam || supabaseConfig.generateId;
|
|
124
|
+
|
|
111
125
|
const list =
|
|
112
126
|
!actions || actions.includes('read')
|
|
113
127
|
? async (params: SyncedGetParams) => {
|
|
114
128
|
const { lastSync } = params;
|
|
115
|
-
|
|
129
|
+
const from = client.from(collection);
|
|
130
|
+
let select = selectFn ? selectFn(from) : from.select();
|
|
116
131
|
if (changesSince === 'last-sync') {
|
|
117
132
|
select = select.neq('deleted', true);
|
|
118
133
|
if (lastSync) {
|
|
@@ -239,6 +254,7 @@ export function syncedSupabase<
|
|
|
239
254
|
fieldCreatedAt,
|
|
240
255
|
fieldUpdatedAt,
|
|
241
256
|
updatePartial: true,
|
|
257
|
+
generateId,
|
|
242
258
|
waitFor: () => isEnabled$.get() && (waitFor ? computeSelector(waitFor) : true),
|
|
243
259
|
waitForSet: () => isEnabled$.get() && (waitForSet ? computeSelector(waitForSet) : true),
|
|
244
260
|
});
|
package/sync-plugins/crud.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SyncTransform, SyncedGetParams, SyncedOptions, SyncedSetParams } from '@legendapp/state';
|
|
1
|
+
import { SyncTransform, SyncedGetParams, SyncedOptions, SyncedSetParams } from '@legendapp/state/sync';
|
|
2
2
|
export type CrudAsOption = 'Map' | 'object' | 'first';
|
|
3
3
|
export type CrudResult<T> = T;
|
|
4
4
|
export interface SyncedCrudPropsSingle<TRemote, TLocal> {
|
|
@@ -23,6 +23,7 @@ export interface SyncedCrudPropsBase<TRemote extends {
|
|
|
23
23
|
fieldCreatedAt?: string;
|
|
24
24
|
updatePartial?: boolean;
|
|
25
25
|
changesSince?: 'all' | 'last-sync';
|
|
26
|
+
generateId?: () => string | number;
|
|
26
27
|
}
|
|
27
28
|
type InitialValue<T, TAsOption extends CrudAsOption> = TAsOption extends 'Map' ? Map<string, T> : TAsOption extends 'object' ? Record<string, T> : TAsOption extends 'first' ? T : T[];
|
|
28
29
|
export type SyncedCrudReturnType<TLocal, TAsOption extends CrudAsOption> = TAsOption extends 'Map' ? Map<string, TLocal> : TAsOption extends 'object' ? Record<string, TLocal> : TAsOption extends 'first' ? TLocal : TLocal[];
|