birdclaw 0.5.1 → 0.6.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.
- package/CHANGELOG.md +44 -1
- package/README.md +50 -5
- package/package.json +3 -2
- package/scripts/browser-perf.mjs +1 -0
- package/scripts/start-test-server.mjs +16 -3
- package/src/cli.ts +376 -13
- package/src/components/AccountSwitcher.tsx +186 -0
- package/src/components/AppNav.tsx +27 -7
- package/src/components/AvatarChip.tsx +9 -3
- package/src/components/DmWorkspace.tsx +18 -8
- package/src/components/LinkPreviewCard.tsx +40 -18
- package/src/components/MarkdownViewer.tsx +452 -0
- package/src/components/SyncNowButton.tsx +57 -25
- package/src/components/ThemeSlider.tsx +55 -50
- package/src/components/TimelineCard.tsx +225 -93
- package/src/components/TimelineRouteFrame.tsx +22 -8
- package/src/components/TweetMediaGrid.tsx +87 -38
- package/src/components/TweetRichText.tsx +15 -11
- package/src/components/account-selection.ts +64 -0
- package/src/components/useTimelineRouteData.ts +23 -7
- package/src/lib/account-sync-job.ts +654 -0
- package/src/lib/actions-transport.ts +216 -146
- package/src/lib/api-client.ts +128 -53
- package/src/lib/archive-finder.ts +78 -63
- package/src/lib/archive-import.ts +1364 -1300
- package/src/lib/authored-live.ts +261 -204
- package/src/lib/avatar-cache.ts +159 -44
- package/src/lib/backup.ts +1532 -951
- package/src/lib/bird-actions.ts +139 -57
- package/src/lib/bird-command.ts +101 -28
- package/src/lib/bird.ts +549 -194
- package/src/lib/blocklist.ts +40 -23
- package/src/lib/blocks-write.ts +129 -80
- package/src/lib/blocks.ts +165 -97
- package/src/lib/bookmark-sync-job.ts +250 -160
- package/src/lib/conversation-surface.ts +79 -48
- package/src/lib/db.ts +33 -3
- package/src/lib/dms-live.ts +720 -66
- package/src/lib/effect-runtime.ts +45 -0
- package/src/lib/follow-graph.ts +224 -180
- package/src/lib/http-effect.ts +222 -0
- package/src/lib/inbox.ts +74 -43
- package/src/lib/link-index.ts +88 -76
- package/src/lib/link-insights.ts +24 -0
- package/src/lib/link-preview-metadata.ts +472 -52
- package/src/lib/media-fetch.ts +286 -213
- package/src/lib/mention-threads-live.ts +352 -288
- package/src/lib/mentions-live.ts +390 -342
- package/src/lib/moderation-target.ts +102 -65
- package/src/lib/moderation-write.ts +77 -18
- package/src/lib/mutes-write.ts +129 -80
- package/src/lib/mutes.ts +8 -1
- package/src/lib/openai.ts +84 -53
- package/src/lib/period-digest.ts +953 -0
- package/src/lib/profile-affiliation-hydration.ts +93 -54
- package/src/lib/profile-hydration.ts +124 -72
- package/src/lib/profile-replies.ts +60 -43
- package/src/lib/profile-resolver.ts +402 -294
- package/src/lib/queries.ts +969 -199
- package/src/lib/research.ts +165 -120
- package/src/lib/sqlite.ts +1 -0
- package/src/lib/timeline-collections-live.ts +204 -167
- package/src/lib/timeline-live.ts +60 -39
- package/src/lib/tweet-lookup.ts +30 -19
- package/src/lib/types.ts +38 -1
- package/src/lib/ui.ts +30 -7
- package/src/lib/url-expansion.ts +226 -55
- package/src/lib/url-safety.ts +220 -0
- package/src/lib/web-sync.ts +216 -148
- package/src/lib/whois.ts +166 -147
- package/src/lib/x-web.ts +102 -71
- package/src/lib/xurl.ts +681 -411
- package/src/routeTree.gen.ts +42 -0
- package/src/routes/__root.tsx +25 -5
- package/src/routes/api/action.tsx +127 -78
- package/src/routes/api/avatar.tsx +39 -30
- package/src/routes/api/blocks.tsx +26 -23
- package/src/routes/api/conversation.tsx +25 -14
- package/src/routes/api/inbox.tsx +27 -21
- package/src/routes/api/link-insights.tsx +31 -25
- package/src/routes/api/link-preview.tsx +25 -21
- package/src/routes/api/period-digest.tsx +123 -0
- package/src/routes/api/profile-hydrate.tsx +31 -28
- package/src/routes/api/query.tsx +79 -55
- package/src/routes/api/status.tsx +18 -10
- package/src/routes/api/sync.tsx +75 -29
- package/src/routes/dms.tsx +95 -28
- package/src/routes/inbox.tsx +32 -19
- package/src/routes/today.tsx +441 -0
package/src/lib/web-sync.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
-
import {
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
import { maybeAutoSyncBackupEffect } from "./backup";
|
|
3
4
|
import { getBirdclawPaths } from "./config";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { syncDirectMessagesViaCachedBirdEffect } from "./dms-live";
|
|
6
|
+
import { runEffectBackground, runEffectPromise } from "./effect-runtime";
|
|
7
|
+
import { syncMentionThreadsEffect } from "./mention-threads-live";
|
|
8
|
+
import { syncMentionsEffect } from "./mentions-live";
|
|
7
9
|
import NativeSqliteDatabase from "./sqlite";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
+
import { syncTimelineCollectionEffect } from "./timeline-collections-live";
|
|
11
|
+
import { syncHomeTimelineEffect } from "./timeline-live";
|
|
10
12
|
|
|
11
13
|
export type WebSyncKind =
|
|
12
14
|
| "timeline"
|
|
@@ -33,7 +35,7 @@ export interface WebSyncResponse {
|
|
|
33
35
|
summary: string;
|
|
34
36
|
steps: WebSyncStep[];
|
|
35
37
|
inProgress?: boolean;
|
|
36
|
-
backup?:
|
|
38
|
+
backup?: Effect.Effect.Success<ReturnType<typeof maybeAutoSyncBackupEffect>>;
|
|
37
39
|
error?: string;
|
|
38
40
|
}
|
|
39
41
|
|
|
@@ -52,10 +54,22 @@ export interface WebSyncJobSnapshot {
|
|
|
52
54
|
error?: string;
|
|
53
55
|
}
|
|
54
56
|
|
|
57
|
+
export type WebSyncDmInbox = "all" | "accepted" | "requests";
|
|
58
|
+
|
|
59
|
+
export interface WebSyncOptions {
|
|
60
|
+
inbox?: WebSyncDmInbox;
|
|
61
|
+
limit?: number;
|
|
62
|
+
maxPages?: number;
|
|
63
|
+
allPages?: boolean;
|
|
64
|
+
}
|
|
65
|
+
|
|
55
66
|
interface WebSyncPlan {
|
|
56
67
|
label: string;
|
|
57
68
|
accountAware: boolean;
|
|
58
|
-
run: (
|
|
69
|
+
run: (
|
|
70
|
+
accountId: string | undefined,
|
|
71
|
+
options: WebSyncOptions,
|
|
72
|
+
) => Effect.Effect<WebSyncStep[], unknown>;
|
|
59
73
|
}
|
|
60
74
|
|
|
61
75
|
const runningSyncs = new Map<string, WebSyncJobSnapshot>();
|
|
@@ -93,6 +107,10 @@ function readBoolean(value: unknown, key: string) {
|
|
|
93
107
|
return typeof raw === "boolean" ? raw : undefined;
|
|
94
108
|
}
|
|
95
109
|
|
|
110
|
+
function messageFromError(error: unknown) {
|
|
111
|
+
return error instanceof Error ? error.message : String(error);
|
|
112
|
+
}
|
|
113
|
+
|
|
96
114
|
export function parseWebSyncKind(value: unknown): WebSyncKind | null {
|
|
97
115
|
return value === "timeline" ||
|
|
98
116
|
value === "mentions" ||
|
|
@@ -114,64 +132,66 @@ const WEB_SYNC_PLANS: Record<WebSyncKind, WebSyncPlan> = {
|
|
|
114
132
|
timeline: {
|
|
115
133
|
label: "Home timeline",
|
|
116
134
|
accountAware: false,
|
|
117
|
-
run:
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
135
|
+
run: (account) =>
|
|
136
|
+
Effect.gen(function* () {
|
|
137
|
+
const result = yield* syncHomeTimelineEffect({
|
|
138
|
+
account,
|
|
139
|
+
limit: 100,
|
|
140
|
+
following: true,
|
|
141
|
+
refresh: true,
|
|
142
|
+
});
|
|
143
|
+
return [
|
|
144
|
+
{
|
|
145
|
+
kind: "timeline",
|
|
146
|
+
label: "Home timeline",
|
|
147
|
+
count: readNumber(result, "count"),
|
|
148
|
+
source: readString(result, "source"),
|
|
149
|
+
},
|
|
150
|
+
];
|
|
151
|
+
}),
|
|
133
152
|
},
|
|
134
153
|
mentions: {
|
|
135
154
|
label: "Mentions",
|
|
136
155
|
accountAware: true,
|
|
137
|
-
run:
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
156
|
+
run: (account) =>
|
|
157
|
+
Effect.gen(function* () {
|
|
158
|
+
const mentions = yield* syncMentionsEffect({
|
|
159
|
+
account,
|
|
160
|
+
mode: "xurl",
|
|
161
|
+
limit: 100,
|
|
162
|
+
maxPages: 3,
|
|
163
|
+
refresh: true,
|
|
164
|
+
});
|
|
165
|
+
const steps: WebSyncStep[] = [
|
|
166
|
+
{
|
|
167
|
+
kind: "mentions",
|
|
168
|
+
label: "Mentions",
|
|
169
|
+
count: readNumber(mentions, "count"),
|
|
170
|
+
source: readString(mentions, "source"),
|
|
171
|
+
partial: readBoolean(mentions, "partial"),
|
|
172
|
+
},
|
|
173
|
+
];
|
|
174
|
+
|
|
175
|
+
const threads = yield* syncMentionThreadsEffect({
|
|
176
|
+
account,
|
|
177
|
+
mode: "xurl",
|
|
178
|
+
limit: 30,
|
|
179
|
+
delayMs: 1500,
|
|
180
|
+
timeoutMs: 15000,
|
|
181
|
+
});
|
|
182
|
+
steps.push({
|
|
183
|
+
kind: "mention-threads",
|
|
184
|
+
label: "Mention threads",
|
|
185
|
+
count: readNumber(threads, "mergedTweets"),
|
|
186
|
+
source: readString(threads, "source"),
|
|
187
|
+
partial: readBoolean(threads, "partial"),
|
|
188
|
+
warnings:
|
|
189
|
+
Array.isArray(threads.warnings) && threads.warnings.length > 0
|
|
190
|
+
? threads.warnings.map(String)
|
|
191
|
+
: undefined,
|
|
192
|
+
});
|
|
193
|
+
return steps;
|
|
194
|
+
}),
|
|
175
195
|
},
|
|
176
196
|
likes: {
|
|
177
197
|
label: "Likes",
|
|
@@ -186,68 +206,83 @@ const WEB_SYNC_PLANS: Record<WebSyncKind, WebSyncPlan> = {
|
|
|
186
206
|
dms: {
|
|
187
207
|
label: "Direct messages",
|
|
188
208
|
accountAware: false,
|
|
189
|
-
run:
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
209
|
+
run: (account, options) =>
|
|
210
|
+
Effect.gen(function* () {
|
|
211
|
+
const inbox = options.inbox ?? "all";
|
|
212
|
+
const result = yield* syncDirectMessagesViaCachedBirdEffect({
|
|
213
|
+
account,
|
|
214
|
+
inbox,
|
|
215
|
+
limit: options.limit ?? (inbox === "requests" ? 200 : 50),
|
|
216
|
+
...(options.maxPages !== undefined
|
|
217
|
+
? { maxPages: options.maxPages }
|
|
218
|
+
: {}),
|
|
219
|
+
...(options.allPages !== undefined
|
|
220
|
+
? { allPages: options.allPages }
|
|
221
|
+
: {}),
|
|
222
|
+
pageDelayMs: inbox === "requests" ? 750 : 0,
|
|
223
|
+
refresh: true,
|
|
224
|
+
});
|
|
225
|
+
return [
|
|
226
|
+
{
|
|
227
|
+
kind: "dms",
|
|
228
|
+
label: "Direct messages",
|
|
229
|
+
count: readNumber(result, "messages"),
|
|
230
|
+
source: readString(result, "source"),
|
|
231
|
+
},
|
|
232
|
+
];
|
|
233
|
+
}),
|
|
204
234
|
},
|
|
205
235
|
};
|
|
206
236
|
|
|
207
|
-
|
|
237
|
+
function syncSavedCollection(
|
|
208
238
|
kind: "likes" | "bookmarks",
|
|
209
239
|
account: string | undefined,
|
|
210
|
-
) {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
account,
|
|
216
|
-
mode: isNonDefaultAccount ? "xurl" : "auto",
|
|
217
|
-
limit: 100,
|
|
218
|
-
maxPages: 5,
|
|
219
|
-
refresh: true,
|
|
220
|
-
earlyStop: true,
|
|
221
|
-
});
|
|
222
|
-
return [
|
|
223
|
-
{
|
|
240
|
+
): Effect.Effect<WebSyncStep[], unknown> {
|
|
241
|
+
return Effect.gen(function* () {
|
|
242
|
+
const isNonDefaultAccount =
|
|
243
|
+
account !== undefined && account !== resolveDefaultSyncAccountId();
|
|
244
|
+
const result = yield* syncTimelineCollectionEffect({
|
|
224
245
|
kind,
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
246
|
+
account,
|
|
247
|
+
mode: isNonDefaultAccount ? "xurl" : "auto",
|
|
248
|
+
limit: 100,
|
|
249
|
+
maxPages: 5,
|
|
250
|
+
refresh: true,
|
|
251
|
+
earlyStop: true,
|
|
252
|
+
});
|
|
253
|
+
return [
|
|
254
|
+
{
|
|
255
|
+
kind,
|
|
256
|
+
label: kind === "likes" ? "Likes" : "Bookmarks",
|
|
257
|
+
count: readNumber(result, "count"),
|
|
258
|
+
source: readString(result, "source"),
|
|
259
|
+
},
|
|
260
|
+
];
|
|
261
|
+
});
|
|
230
262
|
}
|
|
231
263
|
|
|
232
|
-
|
|
264
|
+
export function performWebSyncEffect(
|
|
233
265
|
kind: WebSyncKind,
|
|
234
266
|
accountId?: string,
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
267
|
+
options: WebSyncOptions = {},
|
|
268
|
+
) {
|
|
269
|
+
return Effect.gen(function* () {
|
|
270
|
+
const startedAt = new Date().toISOString();
|
|
271
|
+
const steps = yield* WEB_SYNC_PLANS[kind].run(accountId, options);
|
|
238
272
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
273
|
+
const backup = yield* maybeAutoSyncBackupEffect();
|
|
274
|
+
const finishedAt = new Date().toISOString();
|
|
275
|
+
return {
|
|
276
|
+
ok: true,
|
|
277
|
+
kind,
|
|
278
|
+
...(accountId ? { accountId } : {}),
|
|
279
|
+
startedAt,
|
|
280
|
+
finishedAt,
|
|
281
|
+
summary: summarizeSteps(steps),
|
|
282
|
+
steps,
|
|
283
|
+
backup,
|
|
284
|
+
} satisfies WebSyncResponse;
|
|
285
|
+
});
|
|
251
286
|
}
|
|
252
287
|
|
|
253
288
|
function createWebSyncJobId(kind: WebSyncKind) {
|
|
@@ -281,9 +316,25 @@ function resolveDefaultSyncAccountId() {
|
|
|
281
316
|
}
|
|
282
317
|
}
|
|
283
318
|
|
|
284
|
-
function
|
|
319
|
+
function serializeSyncOptions(kind: WebSyncKind, options: WebSyncOptions) {
|
|
320
|
+
if (kind !== "dms") return "";
|
|
321
|
+
const parts = [
|
|
322
|
+
options.inbox ?? "all",
|
|
323
|
+
options.limit ?? "",
|
|
324
|
+
options.maxPages ?? "",
|
|
325
|
+
options.allPages === undefined ? "" : String(options.allPages),
|
|
326
|
+
];
|
|
327
|
+
return parts.join(":");
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function getRunningSyncKey(
|
|
331
|
+
kind: WebSyncKind,
|
|
332
|
+
accountId: string | undefined,
|
|
333
|
+
options: WebSyncOptions = {},
|
|
334
|
+
) {
|
|
285
335
|
if (!WEB_SYNC_PLANS[kind].accountAware) {
|
|
286
|
-
|
|
336
|
+
const optionKey = serializeSyncOptions(kind, options);
|
|
337
|
+
return optionKey ? `${kind}:${optionKey}` : kind;
|
|
287
338
|
}
|
|
288
339
|
return `${kind}:${accountId ?? resolveDefaultSyncAccountId()}`;
|
|
289
340
|
}
|
|
@@ -326,7 +377,7 @@ function toFailedResponse(
|
|
|
326
377
|
accountId?: string,
|
|
327
378
|
): WebSyncResponse {
|
|
328
379
|
const finishedAt = new Date().toISOString();
|
|
329
|
-
const message = error
|
|
380
|
+
const message = messageFromError(error);
|
|
330
381
|
return {
|
|
331
382
|
ok: false,
|
|
332
383
|
kind,
|
|
@@ -342,9 +393,10 @@ function toFailedResponse(
|
|
|
342
393
|
export function startWebSync(
|
|
343
394
|
kind: WebSyncKind,
|
|
344
395
|
accountId?: string,
|
|
396
|
+
options: WebSyncOptions = {},
|
|
345
397
|
): WebSyncJobSnapshot {
|
|
346
398
|
const effectiveAccountId = getEffectiveAccountId(kind, accountId);
|
|
347
|
-
const syncKey = getRunningSyncKey(kind, effectiveAccountId);
|
|
399
|
+
const syncKey = getRunningSyncKey(kind, effectiveAccountId, options);
|
|
348
400
|
const current = runningSyncs.get(syncKey);
|
|
349
401
|
if (current) {
|
|
350
402
|
return current;
|
|
@@ -363,8 +415,8 @@ export function startWebSync(
|
|
|
363
415
|
webSyncJobKeys.set(job.id, syncKey);
|
|
364
416
|
setJobSnapshot(job);
|
|
365
417
|
|
|
366
|
-
|
|
367
|
-
|
|
418
|
+
runEffectBackground(performWebSyncEffect(kind, effectiveAccountId, options), {
|
|
419
|
+
onSuccess: (result) => {
|
|
368
420
|
setJobSnapshot({
|
|
369
421
|
...job,
|
|
370
422
|
status: "succeeded",
|
|
@@ -373,8 +425,8 @@ export function startWebSync(
|
|
|
373
425
|
inProgress: false,
|
|
374
426
|
result,
|
|
375
427
|
});
|
|
376
|
-
}
|
|
377
|
-
|
|
428
|
+
},
|
|
429
|
+
onFailure: (error) => {
|
|
378
430
|
const result = toFailedResponse(
|
|
379
431
|
kind,
|
|
380
432
|
startedAt,
|
|
@@ -390,7 +442,8 @@ export function startWebSync(
|
|
|
390
442
|
result,
|
|
391
443
|
error: result.error,
|
|
392
444
|
});
|
|
393
|
-
}
|
|
445
|
+
},
|
|
446
|
+
});
|
|
394
447
|
|
|
395
448
|
return job;
|
|
396
449
|
}
|
|
@@ -399,37 +452,52 @@ export function getWebSyncJob(id: string): WebSyncJobSnapshot | null {
|
|
|
399
452
|
return webSyncJobs.get(id) ?? null;
|
|
400
453
|
}
|
|
401
454
|
|
|
402
|
-
export
|
|
455
|
+
export function runWebSyncEffect(
|
|
403
456
|
kind: WebSyncKind,
|
|
404
457
|
accountId?: string,
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
458
|
+
options: WebSyncOptions = {},
|
|
459
|
+
): Effect.Effect<WebSyncResponse, Error> {
|
|
460
|
+
return Effect.gen(function* () {
|
|
461
|
+
const effectiveAccountId = getEffectiveAccountId(kind, accountId);
|
|
462
|
+
const current = runningSyncs.get(
|
|
463
|
+
getRunningSyncKey(kind, effectiveAccountId, options),
|
|
464
|
+
);
|
|
465
|
+
const startedAt = new Date().toISOString();
|
|
466
|
+
if (current) {
|
|
467
|
+
return {
|
|
468
|
+
ok: false,
|
|
469
|
+
kind,
|
|
470
|
+
...(effectiveAccountId ? { accountId: effectiveAccountId } : {}),
|
|
471
|
+
startedAt,
|
|
472
|
+
summary: "Sync already running",
|
|
473
|
+
steps: [],
|
|
474
|
+
inProgress: true,
|
|
475
|
+
} satisfies WebSyncResponse;
|
|
476
|
+
}
|
|
420
477
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
478
|
+
const job = startWebSync(kind, effectiveAccountId, options);
|
|
479
|
+
while (job.inProgress) {
|
|
480
|
+
yield* Effect.sleep(25);
|
|
481
|
+
const latest = getWebSyncJob(job.id);
|
|
482
|
+
if (!latest?.inProgress) {
|
|
483
|
+
if (!latest?.result)
|
|
484
|
+
return yield* Effect.fail(new Error("Sync job disappeared"));
|
|
485
|
+
return latest.result;
|
|
486
|
+
}
|
|
428
487
|
}
|
|
429
|
-
}
|
|
430
488
|
|
|
431
|
-
|
|
432
|
-
|
|
489
|
+
if (!job.result)
|
|
490
|
+
return yield* Effect.fail(new Error("Sync job did not finish"));
|
|
491
|
+
return job.result;
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
export function runWebSync(
|
|
496
|
+
kind: WebSyncKind,
|
|
497
|
+
accountId?: string,
|
|
498
|
+
options: WebSyncOptions = {},
|
|
499
|
+
): Promise<WebSyncResponse> {
|
|
500
|
+
return runEffectPromise(runWebSyncEffect(kind, accountId, options));
|
|
433
501
|
}
|
|
434
502
|
|
|
435
503
|
export function clearWebSyncLocksForTests() {
|