@legendapp/state 2.2.0-next.87 → 2.2.0-next.89
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/config/{enable$get.d.ts → enable$GetSet.d.ts} +2 -2
- package/config/{enable$get.js → enable$GetSet.js} +4 -4
- package/config/enable$GetSet.js.map +1 -0
- package/config/{enable$get.mjs → enable$GetSet.mjs} +4 -4
- package/config/enable$GetSet.mjs.map +1 -0
- package/config/{enable_peek.d.ts → enable_PeekAssign.d.ts} +2 -2
- package/config/{enable_peek.js → enable_PeekAssign.js} +4 -4
- package/config/enable_PeekAssign.js.map +1 -0
- package/config/{enable_peek.mjs → enable_PeekAssign.mjs} +4 -4
- package/config/enable_PeekAssign.mjs.map +1 -0
- package/index.js +8 -1
- package/index.js.map +1 -1
- package/index.mjs +8 -1
- package/index.mjs.map +1 -1
- package/package.json +9 -9
- package/persist.js +2 -1
- package/persist.js.map +1 -1
- package/persist.mjs +2 -1
- package/persist.mjs.map +1 -1
- package/src/ObservableObject.ts +8 -1
- package/src/config/{enable$get.ts → enable$GetSet.ts} +2 -2
- package/src/config/{enable_peek.ts → enable_PeekAssign.ts} +2 -2
- package/src/sync/syncHelpers.ts +121 -1
- package/src/sync/syncObservable.ts +2 -1
- package/src/sync/syncTypes.ts +8 -8
- package/src/sync/synced.ts +3 -1
- package/src/sync-plugins/crud.ts +21 -89
- package/src/sync-plugins/keel.ts +80 -61
- package/src/sync-plugins/supabase.ts +43 -51
- package/sync-plugins/crud.d.ts +4 -9
- package/sync-plugins/crud.js +16 -75
- package/sync-plugins/crud.js.map +1 -1
- package/sync-plugins/crud.mjs +18 -75
- package/sync-plugins/crud.mjs.map +1 -1
- package/sync-plugins/keel.d.ts +26 -13
- package/sync-plugins/keel.js +12 -39
- package/sync-plugins/keel.js.map +1 -1
- package/sync-plugins/keel.mjs +13 -40
- package/sync-plugins/keel.mjs.map +1 -1
- package/sync-plugins/supabase.d.ts +13 -13
- package/sync-plugins/supabase.js +3 -21
- package/sync-plugins/supabase.js.map +1 -1
- package/sync-plugins/supabase.mjs +4 -22
- package/sync-plugins/supabase.mjs.map +1 -1
- package/sync.d.ts +3 -3
- package/sync.js +86 -1
- package/sync.js.map +1 -1
- package/sync.mjs +85 -3
- package/sync.mjs.map +1 -1
- package/config/enable$get.js.map +0 -1
- package/config/enable$get.mjs.map +0 -1
- package/config/enable_peek.js.map +0 -1
- package/config/enable_peek.mjs.map +0 -1
package/src/sync-plugins/crud.ts
CHANGED
|
@@ -1,13 +1,5 @@
|
|
|
1
|
-
import { internal, isArray, isNullOrUndefined
|
|
2
|
-
import {
|
|
3
|
-
synced,
|
|
4
|
-
diffObjects,
|
|
5
|
-
SyncTransform,
|
|
6
|
-
SyncTransformMethod,
|
|
7
|
-
SyncedGetParams,
|
|
8
|
-
SyncedOptions,
|
|
9
|
-
SyncedSetParams,
|
|
10
|
-
} from '@legendapp/state/sync';
|
|
1
|
+
import { internal, isArray, isNullOrUndefined } from '@legendapp/state';
|
|
2
|
+
import { SyncedGetParams, SyncedOptions, SyncedSetParams, diffObjects, synced } from '@legendapp/state/sync';
|
|
11
3
|
|
|
12
4
|
const { clone } = internal;
|
|
13
5
|
|
|
@@ -26,17 +18,17 @@ export interface SyncedCrudPropsMany<TRemote, TLocal, TAsOption extends CrudAsOp
|
|
|
26
18
|
initial?: InitialValue<TLocal, TAsOption>;
|
|
27
19
|
}
|
|
28
20
|
export interface SyncedCrudPropsBase<TRemote extends { id: string | number }, TLocal = TRemote>
|
|
29
|
-
extends Omit<SyncedOptions<TLocal>, 'get' | 'set' | '
|
|
21
|
+
extends Omit<SyncedOptions<TRemote, TLocal>, 'get' | 'set' | 'initial'> {
|
|
30
22
|
create?(input: TRemote, params: SyncedSetParams<TRemote>): Promise<CrudResult<TRemote> | null | undefined>;
|
|
31
23
|
update?(
|
|
32
24
|
input: Partial<TRemote>,
|
|
33
25
|
params: SyncedSetParams<TRemote>,
|
|
34
26
|
): Promise<CrudResult<Partial<TRemote> | null | undefined>>;
|
|
35
27
|
delete?(input: { id: TRemote['id'] }, params: SyncedSetParams<TRemote>): Promise<CrudResult<any>>;
|
|
36
|
-
onSaved?(saved: TLocal, input: TRemote, isCreate: boolean):
|
|
37
|
-
transform?: SyncTransform<TLocal, TRemote>;
|
|
28
|
+
onSaved?(saved: TLocal, input: TRemote, isCreate: boolean): void;
|
|
38
29
|
fieldUpdatedAt?: string;
|
|
39
30
|
fieldCreatedAt?: string;
|
|
31
|
+
fieldDeleted?: string;
|
|
40
32
|
updatePartial?: boolean;
|
|
41
33
|
changesSince?: 'all' | 'last-sync';
|
|
42
34
|
generateId?: () => string | number;
|
|
@@ -64,73 +56,6 @@ function transformOut<T1, T2>(data: T1, transform: undefined | ((value: T1) => T
|
|
|
64
56
|
return transform ? transform(clone(data)) : data;
|
|
65
57
|
}
|
|
66
58
|
|
|
67
|
-
// TODO
|
|
68
|
-
export function createTransform<T extends Record<string, any>, T2 extends Record<string, any>>(
|
|
69
|
-
...keys: (keyof T | { from: keyof T; to: keyof T2 })[]
|
|
70
|
-
): SyncTransform<T2, T> {
|
|
71
|
-
return {
|
|
72
|
-
load: (value: T) => {
|
|
73
|
-
(keys as string[]).forEach((key) => {
|
|
74
|
-
const keyRemote = isObject(key) ? key.from : key;
|
|
75
|
-
const keyLocal = isObject(key) ? key.to : key;
|
|
76
|
-
const v = value[keyRemote];
|
|
77
|
-
if (!isNullOrUndefined(v)) {
|
|
78
|
-
value[keyLocal as keyof T] = isString(v) ? JSON.parse(v as string) : v;
|
|
79
|
-
}
|
|
80
|
-
if (keyLocal !== keyRemote) {
|
|
81
|
-
delete value[keyRemote];
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
return value as unknown as T2;
|
|
85
|
-
},
|
|
86
|
-
save: (value: T2) => {
|
|
87
|
-
(keys as string[]).forEach((key: string) => {
|
|
88
|
-
const keyRemote = isObject(key) ? key.from : key;
|
|
89
|
-
const keyLocal = isObject(key) ? key.to : key;
|
|
90
|
-
let v = (value as any)[keyLocal];
|
|
91
|
-
if (!isNullOrUndefined(v)) {
|
|
92
|
-
if (isArray(v)) {
|
|
93
|
-
v = v.filter((val) => !isNullOrUndefined(val));
|
|
94
|
-
}
|
|
95
|
-
value[keyRemote as keyof T2] =
|
|
96
|
-
isNumber(v) || isObject(v) || isArray(v) ? (JSON.stringify(v) as any) : v;
|
|
97
|
-
}
|
|
98
|
-
if (keyLocal !== keyRemote) {
|
|
99
|
-
delete value[keyLocal];
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
return value as unknown as T;
|
|
103
|
-
},
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// TODO
|
|
108
|
-
export function combineTransforms<T, T2>(
|
|
109
|
-
transform1: SyncTransform<T2, T>,
|
|
110
|
-
...transforms: Partial<SyncTransform<T2, T>>[]
|
|
111
|
-
): SyncTransform<T2, T> {
|
|
112
|
-
return {
|
|
113
|
-
load: (value: T, method: SyncTransformMethod) => {
|
|
114
|
-
let inValue = transform1.load?.(value, method) as any;
|
|
115
|
-
transforms.forEach((transform) => {
|
|
116
|
-
if (transform.load) {
|
|
117
|
-
inValue = transform.load(inValue, method);
|
|
118
|
-
}
|
|
119
|
-
});
|
|
120
|
-
return inValue;
|
|
121
|
-
},
|
|
122
|
-
save: (value: T2) => {
|
|
123
|
-
let outValue = value as any;
|
|
124
|
-
transforms.forEach((transform) => {
|
|
125
|
-
if (transform.save) {
|
|
126
|
-
outValue = transform.save(outValue);
|
|
127
|
-
}
|
|
128
|
-
});
|
|
129
|
-
return transform1.save?.(outValue) ?? outValue;
|
|
130
|
-
},
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
|
|
134
59
|
function ensureId(obj: { id: string | number }, generateId: () => string | number) {
|
|
135
60
|
if (!obj.id) {
|
|
136
61
|
obj.id = generateId();
|
|
@@ -165,6 +90,7 @@ export function syncedCrud<
|
|
|
165
90
|
transform,
|
|
166
91
|
fieldCreatedAt,
|
|
167
92
|
fieldUpdatedAt,
|
|
93
|
+
fieldDeleted,
|
|
168
94
|
updatePartial,
|
|
169
95
|
onSaved,
|
|
170
96
|
mode: modeParam,
|
|
@@ -211,7 +137,8 @@ export function syncedCrud<
|
|
|
211
137
|
} else {
|
|
212
138
|
const out: Record<string, any> = asMap ? new Map() : {};
|
|
213
139
|
transformed.forEach((result: any) => {
|
|
214
|
-
const value =
|
|
140
|
+
const value =
|
|
141
|
+
result[fieldDeleted as any] || result.__deleted ? internal.symbolDelete : result;
|
|
215
142
|
asMap ? (out as Map<any, any>).set(result.id, value) : (out[result.id] = value);
|
|
216
143
|
});
|
|
217
144
|
return out;
|
|
@@ -298,11 +225,7 @@ export function syncedCrud<
|
|
|
298
225
|
isCreateGuess = !(fieldCreatedAt || fieldUpdatedAt) && path.length === 1 && !prevAtPath;
|
|
299
226
|
if (!itemValue) {
|
|
300
227
|
if (path.length === 1 && prevAtPath) {
|
|
301
|
-
|
|
302
|
-
deletes.add(itemKey);
|
|
303
|
-
} else {
|
|
304
|
-
console.log('[legend-state] missing delete function');
|
|
305
|
-
}
|
|
228
|
+
deletes.add(itemKey);
|
|
306
229
|
}
|
|
307
230
|
} else {
|
|
308
231
|
itemsChanged = [[itemKey, itemValue]];
|
|
@@ -352,7 +275,7 @@ export function syncedCrud<
|
|
|
352
275
|
transform?.load ? transform.load(data as any, 'set') : data
|
|
353
276
|
) as any;
|
|
354
277
|
|
|
355
|
-
const savedOut =
|
|
278
|
+
const savedOut = diffObjects(input, dataLoaded as any);
|
|
356
279
|
|
|
357
280
|
if (savedOut) {
|
|
358
281
|
const createdAt = fieldCreatedAt ? savedOut[fieldCreatedAt as keyof TLocal] : undefined;
|
|
@@ -366,6 +289,8 @@ export function syncedCrud<
|
|
|
366
289
|
updatedAt || createdAt ? +new Date(updatedAt || (createdAt as any)) : undefined,
|
|
367
290
|
mode: 'merge',
|
|
368
291
|
});
|
|
292
|
+
|
|
293
|
+
onSaved(dataLoaded, input, isCreate);
|
|
369
294
|
}
|
|
370
295
|
}
|
|
371
296
|
};
|
|
@@ -392,8 +317,15 @@ export function syncedCrud<
|
|
|
392
317
|
);
|
|
393
318
|
}
|
|
394
319
|
}),
|
|
395
|
-
|
|
396
|
-
|
|
320
|
+
...Array.from(deletes).map((id) => {
|
|
321
|
+
if (fieldDeleted && updateFn) {
|
|
322
|
+
updateFn({ id, [fieldDeleted]: true } as any, params);
|
|
323
|
+
} else if (deleteFn) {
|
|
324
|
+
deleteFn({ id }, params);
|
|
325
|
+
} else {
|
|
326
|
+
console.log('[legend-state] missing delete function');
|
|
327
|
+
}
|
|
328
|
+
}),
|
|
397
329
|
]);
|
|
398
330
|
}
|
|
399
331
|
: undefined;
|
package/src/sync-plugins/keel.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { computeSelector, observable, when, internal } from '@legendapp/state';
|
|
1
|
+
import { computeSelector, observable, when, internal, isFunction, mergeIntoObservable } from '@legendapp/state';
|
|
2
2
|
import {
|
|
3
3
|
SyncedOptions,
|
|
4
4
|
removeNullUndefined,
|
|
@@ -44,15 +44,14 @@ type Result<T, U> = NonNullable<Data<T> | Err<U>>;
|
|
|
44
44
|
|
|
45
45
|
// Keel plugin types
|
|
46
46
|
|
|
47
|
-
interface
|
|
48
|
-
refresh: () => void;
|
|
49
|
-
}
|
|
47
|
+
export interface KeelGetParams {}
|
|
50
48
|
|
|
51
|
-
interface
|
|
52
|
-
where: { updatedAt?: { after: Date } };
|
|
53
|
-
refresh?: () => void;
|
|
49
|
+
export interface KeelListParams<Where = {}> {
|
|
50
|
+
where: { updatedAt?: { after: Date } } & Where;
|
|
54
51
|
after?: string;
|
|
55
52
|
first?: number;
|
|
53
|
+
last?: number;
|
|
54
|
+
before?: string;
|
|
56
55
|
}
|
|
57
56
|
|
|
58
57
|
export interface KeelRealtimePlugin {
|
|
@@ -68,9 +67,7 @@ export interface SyncedKeelConfiguration
|
|
|
68
67
|
| 'update'
|
|
69
68
|
| 'delete'
|
|
70
69
|
| 'onSaved'
|
|
71
|
-
| 'transform'
|
|
72
70
|
| 'updatePartial'
|
|
73
|
-
| 'subscribe'
|
|
74
71
|
| 'fieldCreatedAt'
|
|
75
72
|
| 'fieldUpdatedAt'
|
|
76
73
|
> {
|
|
@@ -92,23 +89,60 @@ interface PageInfo {
|
|
|
92
89
|
totalCount: number;
|
|
93
90
|
}
|
|
94
91
|
|
|
95
|
-
interface
|
|
92
|
+
interface SyncedKeelPropsManyBase<TRemote, TLocal, AOption extends CrudAsOption>
|
|
96
93
|
extends Omit<SyncedCrudPropsMany<TRemote, TLocal, AOption>, 'list'> {
|
|
97
|
-
list?: (params: ListGetParams) => Promise<CrudResult<APIResult<{ results: TRemote[]; pageInfo: any }>>>;
|
|
98
94
|
first?: number;
|
|
99
95
|
get?: never;
|
|
100
96
|
}
|
|
97
|
+
interface SyncedKeelPropsManyWhere<TRemote, TLocal, AOption extends CrudAsOption, Where extends Record<string, any>>
|
|
98
|
+
extends SyncedKeelPropsManyBase<TRemote, TLocal, AOption> {
|
|
99
|
+
list?: (params: KeelListParams<NoInfer<Where>>) => Promise<
|
|
100
|
+
CrudResult<
|
|
101
|
+
APIResult<{
|
|
102
|
+
results: TRemote[];
|
|
103
|
+
pageInfo: any;
|
|
104
|
+
}>
|
|
105
|
+
>
|
|
106
|
+
>;
|
|
107
|
+
where?: Where | (() => Where);
|
|
108
|
+
}
|
|
109
|
+
interface SyncedKeelPropsManyNoWhere<TRemote, TLocal, AOption extends CrudAsOption>
|
|
110
|
+
extends SyncedKeelPropsManyBase<TRemote, TLocal, AOption> {
|
|
111
|
+
list?: (params: KeelListParams<{}>) => Promise<
|
|
112
|
+
CrudResult<
|
|
113
|
+
APIResult<{
|
|
114
|
+
results: TRemote[];
|
|
115
|
+
pageInfo: any;
|
|
116
|
+
}>
|
|
117
|
+
>
|
|
118
|
+
>;
|
|
119
|
+
where?: never | {};
|
|
120
|
+
}
|
|
121
|
+
type HasAnyKeys<T> = keyof T extends never ? false : true;
|
|
122
|
+
|
|
123
|
+
type SyncedKeelPropsMany<
|
|
124
|
+
TRemote,
|
|
125
|
+
TLocal,
|
|
126
|
+
AOption extends CrudAsOption,
|
|
127
|
+
Where extends Record<string, any>,
|
|
128
|
+
> = HasAnyKeys<Where> extends true
|
|
129
|
+
? SyncedKeelPropsManyWhere<TRemote, TLocal, AOption, Where>
|
|
130
|
+
: SyncedKeelPropsManyNoWhere<TRemote, TLocal, AOption>;
|
|
101
131
|
|
|
102
132
|
interface SyncedKeelPropsSingle<TRemote, TLocal> extends Omit<SyncedCrudPropsSingle<TRemote, TLocal>, 'get'> {
|
|
103
|
-
get?: (params:
|
|
133
|
+
get?: (params: KeelGetParams) => Promise<APIResult<TRemote>>;
|
|
104
134
|
|
|
105
135
|
first?: never;
|
|
136
|
+
where?: never;
|
|
106
137
|
list?: never;
|
|
107
138
|
as?: never;
|
|
108
139
|
}
|
|
109
140
|
|
|
110
141
|
interface SyncedKeelPropsBase<TRemote extends { id: string }, TLocal = TRemote>
|
|
111
|
-
extends Omit<
|
|
142
|
+
extends Omit<
|
|
143
|
+
SyncedCrudPropsBase<TRemote, TLocal>,
|
|
144
|
+
'create' | 'update' | 'delete' | 'updatePartial' | 'fieldUpdatedAt' | 'fieldCreatedAt'
|
|
145
|
+
> {
|
|
112
146
|
create?: (i: NoInfer<Partial<TRemote>>) => Promise<APIResult<NoInfer<TRemote>>>;
|
|
113
147
|
update?: (params: { where: any; values?: Partial<TRemote> }) => Promise<APIResult<TRemote>>;
|
|
114
148
|
delete?: (params: { id: string }) => Promise<APIResult<string>>;
|
|
@@ -153,13 +187,12 @@ function convertObjectToCreate<TRemote>(item: TRemote): TRemote {
|
|
|
153
187
|
}
|
|
154
188
|
|
|
155
189
|
export function configureSyncedKeel(config: SyncedKeelConfiguration) {
|
|
156
|
-
const { enabled, realtimePlugin, ...rest } = config;
|
|
190
|
+
const { enabled, realtimePlugin, client, ...rest } = config;
|
|
157
191
|
Object.assign(keelConfig, removeNullUndefined(rest));
|
|
158
192
|
|
|
159
193
|
if (enabled !== undefined) {
|
|
160
194
|
isEnabled$.set(enabled);
|
|
161
195
|
}
|
|
162
|
-
const { client } = keelConfig;
|
|
163
196
|
|
|
164
197
|
if (realtimePlugin) {
|
|
165
198
|
keelConfig.realtimePlugin = realtimePlugin;
|
|
@@ -198,13 +231,13 @@ export function configureSyncedKeel(config: SyncedKeelConfiguration) {
|
|
|
198
231
|
|
|
199
232
|
const NumPerPage = 200;
|
|
200
233
|
async function getAllPages<TRemote>(
|
|
201
|
-
listFn: (params:
|
|
234
|
+
listFn: (params: KeelListParams<any>) => Promise<
|
|
202
235
|
APIResult<{
|
|
203
236
|
results: TRemote[];
|
|
204
237
|
pageInfo: any;
|
|
205
238
|
}>
|
|
206
239
|
>,
|
|
207
|
-
params:
|
|
240
|
+
params: KeelListParams,
|
|
208
241
|
): Promise<{ results: TRemote[]; subscribe: (params: { refresh: () => void }) => string }> {
|
|
209
242
|
const allData: TRemote[] = [];
|
|
210
243
|
let pageInfo: PageInfo | undefined = undefined;
|
|
@@ -218,7 +251,7 @@ async function getAllPages<TRemote>(
|
|
|
218
251
|
break;
|
|
219
252
|
}
|
|
220
253
|
const pageEndCursor = pageInfo?.endCursor;
|
|
221
|
-
const paramsWithCursor:
|
|
254
|
+
const paramsWithCursor: KeelListParams = pageEndCursor
|
|
222
255
|
? { first, ...params, after: pageEndCursor }
|
|
223
256
|
: { first, ...params };
|
|
224
257
|
pageInfo = undefined;
|
|
@@ -248,13 +281,26 @@ async function getAllPages<TRemote>(
|
|
|
248
281
|
export function syncedKeel<TRemote extends { id: string }, TLocal = TRemote>(
|
|
249
282
|
props: SyncedKeelPropsBase<TRemote, TLocal> & SyncedKeelPropsSingle<TRemote, TLocal>,
|
|
250
283
|
): SyncedCrudReturnType<TLocal, 'first'>;
|
|
251
|
-
export function syncedKeel<
|
|
252
|
-
|
|
284
|
+
export function syncedKeel<
|
|
285
|
+
TRemote extends { id: string },
|
|
286
|
+
TLocal = TRemote,
|
|
287
|
+
TOption extends CrudAsOption = 'object',
|
|
288
|
+
Where extends Record<string, any> = {},
|
|
289
|
+
>(
|
|
290
|
+
props: SyncedKeelPropsBase<TRemote, TLocal> & SyncedKeelPropsMany<TRemote, TLocal, TOption, Where>,
|
|
253
291
|
): SyncedCrudReturnType<TLocal, Exclude<TOption, 'first'>>;
|
|
254
|
-
export function syncedKeel<
|
|
292
|
+
export function syncedKeel<
|
|
293
|
+
TRemote extends { id: string },
|
|
294
|
+
TLocal = TRemote,
|
|
295
|
+
TOption extends CrudAsOption = 'object',
|
|
296
|
+
Where extends Record<string, any> = {},
|
|
297
|
+
>(
|
|
255
298
|
props: SyncedKeelPropsBase<TRemote, TLocal> &
|
|
256
|
-
(SyncedKeelPropsSingle<TRemote, TLocal> | SyncedKeelPropsMany<TRemote, TLocal, TOption>),
|
|
299
|
+
(SyncedKeelPropsSingle<TRemote, TLocal> | SyncedKeelPropsMany<TRemote, TLocal, TOption, Where>),
|
|
257
300
|
): SyncedCrudReturnType<TLocal, TOption> {
|
|
301
|
+
const { realtimePlugin } = keelConfig;
|
|
302
|
+
mergeIntoObservable(props, keelConfig);
|
|
303
|
+
|
|
258
304
|
const {
|
|
259
305
|
get: getParam,
|
|
260
306
|
list: listParam,
|
|
@@ -262,23 +308,18 @@ export function syncedKeel<TRemote extends { id: string }, TLocal = TRemote, TOp
|
|
|
262
308
|
update: updateParam,
|
|
263
309
|
delete: deleteParam,
|
|
264
310
|
first,
|
|
311
|
+
where: whereParam,
|
|
265
312
|
waitFor,
|
|
266
|
-
waitForSet,
|
|
267
313
|
generateId: generateIdParam,
|
|
268
314
|
...rest
|
|
269
315
|
} = props;
|
|
270
316
|
|
|
271
317
|
const { changesSince } = props;
|
|
272
318
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
if (!asType) {
|
|
276
|
-
asType = (getParam ? 'first' : keelConfig.as || undefined) as TOption;
|
|
277
|
-
}
|
|
319
|
+
const asType: TOption = getParam ? ('first' as TOption) : props.as!;
|
|
278
320
|
|
|
279
321
|
const generateId = generateIdParam || keelConfig.generateId;
|
|
280
322
|
|
|
281
|
-
const realtimePlugin = keelConfig.realtimePlugin;
|
|
282
323
|
let realtimeKeyList: string | undefined = undefined;
|
|
283
324
|
let realtimeKeyGet: string | undefined = undefined;
|
|
284
325
|
|
|
@@ -289,12 +330,12 @@ export function syncedKeel<TRemote extends { id: string }, TLocal = TRemote, TOp
|
|
|
289
330
|
? async (listParams: SyncedGetParams) => {
|
|
290
331
|
const { lastSync, refresh } = listParams;
|
|
291
332
|
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
|
|
294
|
-
const isRawRequest = (listParam || getParam).toString().includes('rawRequest');
|
|
295
333
|
// If querying with lastSync pass it to the "where" parameters
|
|
296
|
-
const where =
|
|
297
|
-
|
|
334
|
+
const where = Object.assign(
|
|
335
|
+
queryBySync ? { updatedAt: { after: new Date(+new Date(lastSync) + 1) } } : {},
|
|
336
|
+
isFunction(whereParam) ? whereParam() : whereParam,
|
|
337
|
+
);
|
|
338
|
+
const params: KeelListParams = { where, first };
|
|
298
339
|
|
|
299
340
|
// TODO: Error?
|
|
300
341
|
const { results, subscribe } = await getAllPages(listParam, params);
|
|
@@ -323,30 +364,11 @@ export function syncedKeel<TRemote extends { id: string }, TLocal = TRemote, TOp
|
|
|
323
364
|
}
|
|
324
365
|
: undefined;
|
|
325
366
|
|
|
326
|
-
const onSaved = (data: TLocal
|
|
327
|
-
if (data) {
|
|
328
|
-
const savedOut: Partial<TLocal> = {};
|
|
329
|
-
if (isCreate) {
|
|
330
|
-
// Update with any fields that were undefined when creating
|
|
331
|
-
Object.keys(data).forEach((key) => {
|
|
332
|
-
if (input[key as keyof TRemote] === undefined) {
|
|
333
|
-
savedOut[key as keyof TLocal] = data[key as keyof TLocal];
|
|
334
|
-
}
|
|
335
|
-
});
|
|
336
|
-
} else {
|
|
337
|
-
// Update with any fields ending in createdAt or updatedAt
|
|
338
|
-
Object.keys(data).forEach((key) => {
|
|
339
|
-
const k = key as keyof TLocal;
|
|
340
|
-
const keyLower = key.toLowerCase();
|
|
341
|
-
if ((keyLower.endsWith('createdat') || keyLower.endsWith('updatedat')) && data[k] instanceof Date) {
|
|
342
|
-
savedOut[k] = data[k];
|
|
343
|
-
}
|
|
344
|
-
});
|
|
345
|
-
}
|
|
346
|
-
|
|
367
|
+
const onSaved = (data: TLocal) => {
|
|
368
|
+
if (realtimePlugin && data) {
|
|
347
369
|
const updatedAt = data[fieldUpdatedAt as keyof TLocal] as Date;
|
|
348
370
|
|
|
349
|
-
if (updatedAt
|
|
371
|
+
if (updatedAt) {
|
|
350
372
|
if (realtimeKeyGet) {
|
|
351
373
|
realtimePlugin.setLatestChange(realtimeKeyGet, updatedAt);
|
|
352
374
|
}
|
|
@@ -354,8 +376,6 @@ export function syncedKeel<TRemote extends { id: string }, TLocal = TRemote, TOp
|
|
|
354
376
|
realtimePlugin.setLatestChange(realtimeKeyList, updatedAt);
|
|
355
377
|
}
|
|
356
378
|
}
|
|
357
|
-
|
|
358
|
-
return savedOut;
|
|
359
379
|
}
|
|
360
380
|
};
|
|
361
381
|
|
|
@@ -421,8 +441,8 @@ export function syncedKeel<TRemote extends { id: string }, TLocal = TRemote, TOp
|
|
|
421
441
|
}
|
|
422
442
|
: undefined;
|
|
423
443
|
const deleteFn = deleteParam
|
|
424
|
-
? async (
|
|
425
|
-
const { data, error } = await deleteParam({ id
|
|
444
|
+
? async ({ id }: { id: string }, params: SyncedSetParams<TRemote>) => {
|
|
445
|
+
const { data, error } = await deleteParam({ id });
|
|
426
446
|
|
|
427
447
|
if (error) {
|
|
428
448
|
handleSetError(error, params, false);
|
|
@@ -440,7 +460,6 @@ export function syncedKeel<TRemote extends { id: string }, TLocal = TRemote, TOp
|
|
|
440
460
|
update,
|
|
441
461
|
delete: deleteFn,
|
|
442
462
|
waitFor: () => isEnabled$.get() && (waitFor ? computeSelector(waitFor) : true),
|
|
443
|
-
waitForSet,
|
|
444
463
|
onSaved,
|
|
445
464
|
fieldCreatedAt,
|
|
446
465
|
fieldUpdatedAt,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Observable, computeSelector, mergeIntoObservable, observable, symbolDelete } from '@legendapp/state';
|
|
1
|
+
import { Observable, computeSelector, isObject, mergeIntoObservable, observable, symbolDelete } from '@legendapp/state';
|
|
2
2
|
import {
|
|
3
3
|
SyncedOptions,
|
|
4
4
|
SyncedOptionsGlobal,
|
|
@@ -22,20 +22,28 @@ import type { SupabaseClient } from '@supabase/supabase-js';
|
|
|
22
22
|
// ? SchemaName
|
|
23
23
|
// : never;
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
export type SupabaseSchemaOf<Client extends SupabaseClient> = Client extends SupabaseClient<
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
27
|
+
infer _,
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
29
|
+
infer __,
|
|
30
|
+
infer Schema
|
|
31
|
+
>
|
|
27
32
|
? Schema
|
|
28
33
|
: never;
|
|
29
|
-
type
|
|
30
|
-
type
|
|
31
|
-
type
|
|
34
|
+
export type SupabaseTableOf<Client extends SupabaseClient> = SupabaseSchemaOf<Client>['Tables'];
|
|
35
|
+
export type SupabaseCollectionOf<Client extends SupabaseClient> = keyof SupabaseTableOf<Client>;
|
|
36
|
+
export type SupabaseRowOf<
|
|
37
|
+
Client extends SupabaseClient,
|
|
38
|
+
Collection extends SupabaseCollectionOf<Client>,
|
|
39
|
+
> = SupabaseTableOf<Client>[Collection]['Row'];
|
|
32
40
|
|
|
33
41
|
export type SyncedSupabaseConfig<T extends { id: string }> = Omit<
|
|
34
42
|
SyncedCrudPropsBase<T>,
|
|
35
|
-
'create' | 'update' | 'delete'
|
|
43
|
+
'create' | 'update' | 'delete'
|
|
36
44
|
>;
|
|
37
45
|
|
|
38
|
-
export interface
|
|
46
|
+
export interface SyncedSupabaseConfiguration
|
|
39
47
|
extends Omit<SyncedSupabaseConfig<{ id: string }>, 'persist' | keyof SyncedOptions> {
|
|
40
48
|
persist?: SyncedOptionsGlobal;
|
|
41
49
|
enabled?: Observable<boolean>;
|
|
@@ -44,46 +52,46 @@ export interface SyncedSupabaseGlobalConfig
|
|
|
44
52
|
|
|
45
53
|
interface SyncedSupabaseProps<
|
|
46
54
|
Client extends SupabaseClient,
|
|
47
|
-
Collection extends
|
|
55
|
+
Collection extends SupabaseCollectionOf<Client>,
|
|
48
56
|
TOption extends CrudAsOption = 'object',
|
|
49
|
-
> extends SyncedSupabaseConfig<
|
|
50
|
-
SyncedCrudPropsMany<
|
|
57
|
+
> extends SyncedSupabaseConfig<SupabaseRowOf<Client, Collection>>,
|
|
58
|
+
SyncedCrudPropsMany<SupabaseRowOf<Client, Collection>, SupabaseRowOf<Client, Collection>, TOption> {
|
|
51
59
|
supabase: Client;
|
|
52
60
|
collection: Collection;
|
|
53
61
|
select?: (
|
|
54
|
-
query: PostgrestQueryBuilder<
|
|
62
|
+
query: PostgrestQueryBuilder<SupabaseSchemaOf<Client>, SupabaseTableOf<Client>[Collection], Collection>,
|
|
55
63
|
) => PostgrestFilterBuilder<
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
64
|
+
SupabaseSchemaOf<Client>,
|
|
65
|
+
SupabaseRowOf<Client, Collection>,
|
|
66
|
+
SupabaseRowOf<Client, Collection>[],
|
|
59
67
|
Collection,
|
|
60
68
|
[]
|
|
61
69
|
>;
|
|
62
70
|
filter?: (
|
|
63
71
|
select: PostgrestFilterBuilder<
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
72
|
+
SupabaseSchemaOf<Client>,
|
|
73
|
+
SupabaseRowOf<Client, Collection>,
|
|
74
|
+
SupabaseRowOf<Client, Collection>[],
|
|
67
75
|
Collection,
|
|
68
76
|
[]
|
|
69
77
|
>,
|
|
70
78
|
params: SyncedGetParams,
|
|
71
79
|
) => PostgrestFilterBuilder<
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
80
|
+
SupabaseSchemaOf<Client>,
|
|
81
|
+
SupabaseRowOf<Client, Collection>,
|
|
82
|
+
SupabaseRowOf<Client, Collection>[],
|
|
75
83
|
Collection,
|
|
76
84
|
[]
|
|
77
85
|
>;
|
|
78
86
|
actions?: ('create' | 'read' | 'update' | 'delete')[];
|
|
79
|
-
realtime?: { schema?: string; filter?: string };
|
|
87
|
+
realtime?: boolean | { schema?: string; filter?: string };
|
|
80
88
|
}
|
|
81
89
|
|
|
82
90
|
let channelNum = 1;
|
|
83
|
-
const supabaseConfig:
|
|
91
|
+
const supabaseConfig: SyncedSupabaseConfiguration = {};
|
|
84
92
|
const isEnabled$ = observable(true);
|
|
85
93
|
|
|
86
|
-
export function configureSyncedSupabase(config:
|
|
94
|
+
export function configureSyncedSupabase(config: SyncedSupabaseConfiguration) {
|
|
87
95
|
const { enabled, ...rest } = config;
|
|
88
96
|
if (enabled !== undefined) {
|
|
89
97
|
isEnabled$.set(enabled);
|
|
@@ -93,9 +101,11 @@ export function configureSyncedSupabase(config: SyncedSupabaseGlobalConfig) {
|
|
|
93
101
|
|
|
94
102
|
export function syncedSupabase<
|
|
95
103
|
Client extends SupabaseClient,
|
|
96
|
-
Collection extends
|
|
104
|
+
Collection extends SupabaseCollectionOf<Client> & string,
|
|
97
105
|
AsOption extends CrudAsOption = 'object',
|
|
98
|
-
>(
|
|
106
|
+
>(
|
|
107
|
+
props: SyncedSupabaseProps<Client, Collection, AsOption>,
|
|
108
|
+
): SyncedCrudReturnType<SupabaseRowOf<Client, Collection>, AsOption> {
|
|
99
109
|
mergeIntoObservable(props, supabaseConfig);
|
|
100
110
|
const {
|
|
101
111
|
supabase: client,
|
|
@@ -122,7 +132,6 @@ export function syncedSupabase<
|
|
|
122
132
|
const from = client.from(collection);
|
|
123
133
|
let select = selectFn ? selectFn(from) : from.select();
|
|
124
134
|
if (changesSince === 'last-sync') {
|
|
125
|
-
select = select.neq('deleted', true);
|
|
126
135
|
if (lastSync) {
|
|
127
136
|
const date = new Date(lastSync).toISOString();
|
|
128
137
|
select = select.or(
|
|
@@ -140,11 +149,11 @@ export function syncedSupabase<
|
|
|
140
149
|
if (error) {
|
|
141
150
|
throw new Error(error?.message);
|
|
142
151
|
}
|
|
143
|
-
return (data! || []) as
|
|
152
|
+
return (data! || []) as SupabaseRowOf<Client, Collection>[];
|
|
144
153
|
}
|
|
145
154
|
: undefined;
|
|
146
155
|
|
|
147
|
-
const upsert = async (input:
|
|
156
|
+
const upsert = async (input: SupabaseRowOf<Client, Collection>) => {
|
|
148
157
|
const res = await client.from(collection).upsert(input).select();
|
|
149
158
|
const { data, error } = res;
|
|
150
159
|
if (data) {
|
|
@@ -158,12 +167,10 @@ export function syncedSupabase<
|
|
|
158
167
|
const update = !actions || actions.includes('update') ? upsert : undefined;
|
|
159
168
|
const deleteFn =
|
|
160
169
|
!actions || actions.includes('delete')
|
|
161
|
-
? async (input: { id:
|
|
170
|
+
? async (input: { id: SupabaseRowOf<Client, Collection>['id'] }) => {
|
|
162
171
|
const id = input.id;
|
|
163
172
|
const from = client.from(collection);
|
|
164
|
-
const res = await (
|
|
165
|
-
.eq('id', id)
|
|
166
|
-
.select();
|
|
173
|
+
const res = await from.delete().eq('id', id).select();
|
|
167
174
|
const { data, error } = res;
|
|
168
175
|
if (data) {
|
|
169
176
|
const created = data[0];
|
|
@@ -175,7 +182,7 @@ export function syncedSupabase<
|
|
|
175
182
|
: undefined;
|
|
176
183
|
const subscribe = realtime
|
|
177
184
|
? ({ node, value$, update }: SyncedSubscribeParams) => {
|
|
178
|
-
const { filter, schema } = realtime;
|
|
185
|
+
const { filter, schema } = (isObject(realtime) ? realtime : {}) as { schema?: string; filter?: string };
|
|
179
186
|
const channel = client
|
|
180
187
|
.channel(`LS_${node.key || ''}${channelNum++}`)
|
|
181
188
|
.on(
|
|
@@ -222,31 +229,16 @@ export function syncedSupabase<
|
|
|
222
229
|
}
|
|
223
230
|
: undefined;
|
|
224
231
|
|
|
225
|
-
return syncedCrud<
|
|
232
|
+
return syncedCrud<SupabaseRowOf<Client, Collection>, SupabaseRowOf<Client, Collection>, AsOption>({
|
|
226
233
|
...rest,
|
|
227
234
|
list,
|
|
228
235
|
create,
|
|
229
236
|
update,
|
|
230
237
|
delete: deleteFn,
|
|
231
|
-
onSaved: (saved) => {
|
|
232
|
-
// Update the local timestamps with server response
|
|
233
|
-
if (fieldCreatedAt || fieldUpdatedAt) {
|
|
234
|
-
const ret: any = {
|
|
235
|
-
id: saved.id,
|
|
236
|
-
};
|
|
237
|
-
if (fieldCreatedAt) {
|
|
238
|
-
ret[fieldCreatedAt] = fieldCreatedAt;
|
|
239
|
-
}
|
|
240
|
-
if (fieldUpdatedAt) {
|
|
241
|
-
ret[fieldUpdatedAt] = fieldUpdatedAt;
|
|
242
|
-
}
|
|
243
|
-
return ret;
|
|
244
|
-
}
|
|
245
|
-
},
|
|
246
238
|
subscribe,
|
|
247
239
|
fieldCreatedAt,
|
|
248
240
|
fieldUpdatedAt,
|
|
249
|
-
updatePartial:
|
|
241
|
+
updatePartial: false,
|
|
250
242
|
generateId,
|
|
251
243
|
waitFor: () => isEnabled$.get() && (waitFor ? computeSelector(waitFor) : true),
|
|
252
244
|
waitForSet,
|