birdclaw 0.5.1 → 0.7.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 +92 -1
- package/README.md +75 -5
- package/package.json +8 -2
- package/scripts/browser-perf.mjs +1 -0
- package/scripts/start-test-server.mjs +16 -3
- package/src/cli.ts +812 -37
- package/src/components/AccountSwitcher.tsx +186 -0
- package/src/components/AppNav.tsx +37 -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 +818 -0
- package/src/components/ProfileAnalysisStream.tsx +428 -0
- package/src/components/ProfilePreview.tsx +120 -9
- package/src/components/SavedTimelineView.tsx +30 -8
- package/src/components/SyncNowButton.tsx +60 -25
- package/src/components/ThemeSlider.tsx +55 -50
- package/src/components/TimelineCard.tsx +240 -92
- package/src/components/TimelineRouteFrame.tsx +38 -8
- package/src/components/TweetMediaGrid.tsx +87 -38
- package/src/components/TweetRichText.tsx +45 -17
- package/src/components/account-selection.ts +64 -0
- package/src/components/useTimelineRouteData.ts +97 -13
- package/src/lib/account-sync-job.ts +666 -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 +1593 -1291
- package/src/lib/authored-live.ts +262 -204
- package/src/lib/avatar-cache.ts +208 -43
- package/src/lib/backup.ts +1536 -954
- package/src/lib/bird-actions.ts +139 -57
- package/src/lib/bird-command.ts +101 -28
- package/src/lib/bird.ts +582 -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/config.ts +35 -2
- package/src/lib/conversation-surface.ts +79 -48
- package/src/lib/data-sources.ts +219 -0
- package/src/lib/db.ts +95 -4
- 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/geocoding.ts +296 -0
- 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/location.ts +137 -0
- package/src/lib/media-fetch.ts +286 -213
- package/src/lib/mention-threads-live.ts +445 -288
- package/src/lib/mentions-live.ts +549 -354
- 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/network-map.ts +382 -0
- package/src/lib/openai.ts +84 -53
- package/src/lib/period-digest.ts +1317 -0
- package/src/lib/profile-affiliation-hydration.ts +93 -54
- package/src/lib/profile-analysis.ts +1272 -0
- package/src/lib/profile-bio-entities.ts +1 -1
- 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 +983 -203
- package/src/lib/research.ts +165 -120
- package/src/lib/search-discussion.ts +1016 -0
- package/src/lib/sqlite.ts +1 -0
- package/src/lib/timeline-collections-live.ts +204 -167
- package/src/lib/timeline-live.ts +325 -51
- package/src/lib/tweet-account-edges.ts +2 -0
- package/src/lib/tweet-lookup.ts +30 -19
- package/src/lib/tweet-render.ts +141 -1
- package/src/lib/tweet-search-live.ts +565 -0
- package/src/lib/types.ts +75 -3
- package/src/lib/ui.ts +31 -8
- package/src/lib/url-expansion.ts +226 -55
- package/src/lib/url-safety.ts +220 -0
- package/src/lib/web-sync.ts +222 -149
- package/src/lib/whois.ts +166 -147
- package/src/lib/x-web.ts +102 -71
- package/src/lib/xurl-rate-limits.ts +267 -0
- package/src/lib/xurl.ts +1185 -405
- package/src/routeTree.gen.ts +273 -0
- package/src/routes/__root.tsx +24 -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/data-sources.tsx +24 -0
- 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/network-map.tsx +55 -0
- package/src/routes/api/period-digest.tsx +133 -0
- package/src/routes/api/profile-analysis.tsx +152 -0
- package/src/routes/api/profile-hydrate.tsx +31 -28
- package/src/routes/api/query.tsx +80 -55
- package/src/routes/api/search-discussion.tsx +169 -0
- package/src/routes/api/status.tsx +18 -10
- package/src/routes/api/sync.tsx +75 -29
- package/src/routes/api/xurl-rate-limits.tsx +24 -0
- package/src/routes/data-sources.tsx +255 -0
- package/src/routes/discuss.tsx +419 -0
- package/src/routes/dms.tsx +95 -28
- package/src/routes/inbox.tsx +32 -19
- package/src/routes/network-map.tsx +1035 -0
- package/src/routes/profile-analyze.tsx +112 -0
- package/src/routes/profiles.$handle.tsx +228 -0
- package/src/routes/rate-limits.tsx +309 -0
- package/src/routes/today.tsx +455 -0
- package/src/styles.css +22 -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" ||
|
|
@@ -113,65 +131,72 @@ function summarizeSteps(steps: WebSyncStep[]) {
|
|
|
113
131
|
const WEB_SYNC_PLANS: Record<WebSyncKind, WebSyncPlan> = {
|
|
114
132
|
timeline: {
|
|
115
133
|
label: "Home timeline",
|
|
116
|
-
accountAware:
|
|
117
|
-
run:
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
134
|
+
accountAware: true,
|
|
135
|
+
run: (account) =>
|
|
136
|
+
Effect.gen(function* () {
|
|
137
|
+
const result = yield* syncHomeTimelineEffect({
|
|
138
|
+
account,
|
|
139
|
+
mode:
|
|
140
|
+
!account || account === resolveDefaultSyncAccountId()
|
|
141
|
+
? "auto"
|
|
142
|
+
: "xurl",
|
|
143
|
+
limit: 100,
|
|
144
|
+
maxPages: 3,
|
|
145
|
+
following: true,
|
|
146
|
+
refresh: true,
|
|
147
|
+
});
|
|
148
|
+
return [
|
|
149
|
+
{
|
|
150
|
+
kind: "timeline",
|
|
151
|
+
label: "Home timeline",
|
|
152
|
+
count: readNumber(result, "count"),
|
|
153
|
+
source: readString(result, "source"),
|
|
154
|
+
},
|
|
155
|
+
];
|
|
156
|
+
}),
|
|
133
157
|
},
|
|
134
158
|
mentions: {
|
|
135
159
|
label: "Mentions",
|
|
136
160
|
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
|
-
|
|
161
|
+
run: (account) =>
|
|
162
|
+
Effect.gen(function* () {
|
|
163
|
+
const mentions = yield* syncMentionsEffect({
|
|
164
|
+
account,
|
|
165
|
+
mode: "auto",
|
|
166
|
+
limit: 100,
|
|
167
|
+
maxPages: 3,
|
|
168
|
+
refresh: true,
|
|
169
|
+
});
|
|
170
|
+
const steps: WebSyncStep[] = [
|
|
171
|
+
{
|
|
172
|
+
kind: "mentions",
|
|
173
|
+
label: "Mentions",
|
|
174
|
+
count: readNumber(mentions, "count"),
|
|
175
|
+
source: readString(mentions, "source"),
|
|
176
|
+
partial: readBoolean(mentions, "partial"),
|
|
177
|
+
},
|
|
178
|
+
];
|
|
179
|
+
|
|
180
|
+
const threads = yield* syncMentionThreadsEffect({
|
|
181
|
+
account,
|
|
182
|
+
mode: "xurl",
|
|
183
|
+
limit: 30,
|
|
184
|
+
delayMs: 1500,
|
|
185
|
+
timeoutMs: 15000,
|
|
186
|
+
});
|
|
187
|
+
steps.push({
|
|
188
|
+
kind: "mention-threads",
|
|
189
|
+
label: "Mention threads",
|
|
190
|
+
count: readNumber(threads, "mergedTweets"),
|
|
191
|
+
source: readString(threads, "source"),
|
|
192
|
+
partial: readBoolean(threads, "partial"),
|
|
193
|
+
warnings:
|
|
194
|
+
Array.isArray(threads.warnings) && threads.warnings.length > 0
|
|
195
|
+
? threads.warnings.map(String)
|
|
196
|
+
: undefined,
|
|
197
|
+
});
|
|
198
|
+
return steps;
|
|
199
|
+
}),
|
|
175
200
|
},
|
|
176
201
|
likes: {
|
|
177
202
|
label: "Likes",
|
|
@@ -186,68 +211,83 @@ const WEB_SYNC_PLANS: Record<WebSyncKind, WebSyncPlan> = {
|
|
|
186
211
|
dms: {
|
|
187
212
|
label: "Direct messages",
|
|
188
213
|
accountAware: false,
|
|
189
|
-
run:
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
214
|
+
run: (account, options) =>
|
|
215
|
+
Effect.gen(function* () {
|
|
216
|
+
const inbox = options.inbox ?? "all";
|
|
217
|
+
const result = yield* syncDirectMessagesViaCachedBirdEffect({
|
|
218
|
+
account,
|
|
219
|
+
inbox,
|
|
220
|
+
limit: options.limit ?? (inbox === "requests" ? 200 : 50),
|
|
221
|
+
...(options.maxPages !== undefined
|
|
222
|
+
? { maxPages: options.maxPages }
|
|
223
|
+
: {}),
|
|
224
|
+
...(options.allPages !== undefined
|
|
225
|
+
? { allPages: options.allPages }
|
|
226
|
+
: {}),
|
|
227
|
+
pageDelayMs: inbox === "requests" ? 750 : 0,
|
|
228
|
+
refresh: true,
|
|
229
|
+
});
|
|
230
|
+
return [
|
|
231
|
+
{
|
|
232
|
+
kind: "dms",
|
|
233
|
+
label: "Direct messages",
|
|
234
|
+
count: readNumber(result, "messages"),
|
|
235
|
+
source: readString(result, "source"),
|
|
236
|
+
},
|
|
237
|
+
];
|
|
238
|
+
}),
|
|
204
239
|
},
|
|
205
240
|
};
|
|
206
241
|
|
|
207
|
-
|
|
242
|
+
function syncSavedCollection(
|
|
208
243
|
kind: "likes" | "bookmarks",
|
|
209
244
|
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
|
-
{
|
|
245
|
+
): Effect.Effect<WebSyncStep[], unknown> {
|
|
246
|
+
return Effect.gen(function* () {
|
|
247
|
+
const isNonDefaultAccount =
|
|
248
|
+
account !== undefined && account !== resolveDefaultSyncAccountId();
|
|
249
|
+
const result = yield* syncTimelineCollectionEffect({
|
|
224
250
|
kind,
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
251
|
+
account,
|
|
252
|
+
mode: isNonDefaultAccount ? "xurl" : "auto",
|
|
253
|
+
limit: 100,
|
|
254
|
+
maxPages: 5,
|
|
255
|
+
refresh: true,
|
|
256
|
+
earlyStop: true,
|
|
257
|
+
});
|
|
258
|
+
return [
|
|
259
|
+
{
|
|
260
|
+
kind,
|
|
261
|
+
label: kind === "likes" ? "Likes" : "Bookmarks",
|
|
262
|
+
count: readNumber(result, "count"),
|
|
263
|
+
source: readString(result, "source"),
|
|
264
|
+
},
|
|
265
|
+
];
|
|
266
|
+
});
|
|
230
267
|
}
|
|
231
268
|
|
|
232
|
-
|
|
269
|
+
export function performWebSyncEffect(
|
|
233
270
|
kind: WebSyncKind,
|
|
234
271
|
accountId?: string,
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
272
|
+
options: WebSyncOptions = {},
|
|
273
|
+
) {
|
|
274
|
+
return Effect.gen(function* () {
|
|
275
|
+
const startedAt = new Date().toISOString();
|
|
276
|
+
const steps = yield* WEB_SYNC_PLANS[kind].run(accountId, options);
|
|
238
277
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
278
|
+
const backup = yield* maybeAutoSyncBackupEffect();
|
|
279
|
+
const finishedAt = new Date().toISOString();
|
|
280
|
+
return {
|
|
281
|
+
ok: true,
|
|
282
|
+
kind,
|
|
283
|
+
...(accountId ? { accountId } : {}),
|
|
284
|
+
startedAt,
|
|
285
|
+
finishedAt,
|
|
286
|
+
summary: summarizeSteps(steps),
|
|
287
|
+
steps,
|
|
288
|
+
backup,
|
|
289
|
+
} satisfies WebSyncResponse;
|
|
290
|
+
});
|
|
251
291
|
}
|
|
252
292
|
|
|
253
293
|
function createWebSyncJobId(kind: WebSyncKind) {
|
|
@@ -281,9 +321,25 @@ function resolveDefaultSyncAccountId() {
|
|
|
281
321
|
}
|
|
282
322
|
}
|
|
283
323
|
|
|
284
|
-
function
|
|
324
|
+
function serializeSyncOptions(kind: WebSyncKind, options: WebSyncOptions) {
|
|
325
|
+
if (kind !== "dms") return "";
|
|
326
|
+
const parts = [
|
|
327
|
+
options.inbox ?? "all",
|
|
328
|
+
options.limit ?? "",
|
|
329
|
+
options.maxPages ?? "",
|
|
330
|
+
options.allPages === undefined ? "" : String(options.allPages),
|
|
331
|
+
];
|
|
332
|
+
return parts.join(":");
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function getRunningSyncKey(
|
|
336
|
+
kind: WebSyncKind,
|
|
337
|
+
accountId: string | undefined,
|
|
338
|
+
options: WebSyncOptions = {},
|
|
339
|
+
) {
|
|
285
340
|
if (!WEB_SYNC_PLANS[kind].accountAware) {
|
|
286
|
-
|
|
341
|
+
const optionKey = serializeSyncOptions(kind, options);
|
|
342
|
+
return optionKey ? `${kind}:${optionKey}` : kind;
|
|
287
343
|
}
|
|
288
344
|
return `${kind}:${accountId ?? resolveDefaultSyncAccountId()}`;
|
|
289
345
|
}
|
|
@@ -326,7 +382,7 @@ function toFailedResponse(
|
|
|
326
382
|
accountId?: string,
|
|
327
383
|
): WebSyncResponse {
|
|
328
384
|
const finishedAt = new Date().toISOString();
|
|
329
|
-
const message = error
|
|
385
|
+
const message = messageFromError(error);
|
|
330
386
|
return {
|
|
331
387
|
ok: false,
|
|
332
388
|
kind,
|
|
@@ -342,9 +398,10 @@ function toFailedResponse(
|
|
|
342
398
|
export function startWebSync(
|
|
343
399
|
kind: WebSyncKind,
|
|
344
400
|
accountId?: string,
|
|
401
|
+
options: WebSyncOptions = {},
|
|
345
402
|
): WebSyncJobSnapshot {
|
|
346
403
|
const effectiveAccountId = getEffectiveAccountId(kind, accountId);
|
|
347
|
-
const syncKey = getRunningSyncKey(kind, effectiveAccountId);
|
|
404
|
+
const syncKey = getRunningSyncKey(kind, effectiveAccountId, options);
|
|
348
405
|
const current = runningSyncs.get(syncKey);
|
|
349
406
|
if (current) {
|
|
350
407
|
return current;
|
|
@@ -363,8 +420,8 @@ export function startWebSync(
|
|
|
363
420
|
webSyncJobKeys.set(job.id, syncKey);
|
|
364
421
|
setJobSnapshot(job);
|
|
365
422
|
|
|
366
|
-
|
|
367
|
-
|
|
423
|
+
runEffectBackground(performWebSyncEffect(kind, effectiveAccountId, options), {
|
|
424
|
+
onSuccess: (result) => {
|
|
368
425
|
setJobSnapshot({
|
|
369
426
|
...job,
|
|
370
427
|
status: "succeeded",
|
|
@@ -373,8 +430,8 @@ export function startWebSync(
|
|
|
373
430
|
inProgress: false,
|
|
374
431
|
result,
|
|
375
432
|
});
|
|
376
|
-
}
|
|
377
|
-
|
|
433
|
+
},
|
|
434
|
+
onFailure: (error) => {
|
|
378
435
|
const result = toFailedResponse(
|
|
379
436
|
kind,
|
|
380
437
|
startedAt,
|
|
@@ -390,7 +447,8 @@ export function startWebSync(
|
|
|
390
447
|
result,
|
|
391
448
|
error: result.error,
|
|
392
449
|
});
|
|
393
|
-
}
|
|
450
|
+
},
|
|
451
|
+
});
|
|
394
452
|
|
|
395
453
|
return job;
|
|
396
454
|
}
|
|
@@ -399,37 +457,52 @@ export function getWebSyncJob(id: string): WebSyncJobSnapshot | null {
|
|
|
399
457
|
return webSyncJobs.get(id) ?? null;
|
|
400
458
|
}
|
|
401
459
|
|
|
402
|
-
export
|
|
460
|
+
export function runWebSyncEffect(
|
|
403
461
|
kind: WebSyncKind,
|
|
404
462
|
accountId?: string,
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
463
|
+
options: WebSyncOptions = {},
|
|
464
|
+
): Effect.Effect<WebSyncResponse, Error> {
|
|
465
|
+
return Effect.gen(function* () {
|
|
466
|
+
const effectiveAccountId = getEffectiveAccountId(kind, accountId);
|
|
467
|
+
const current = runningSyncs.get(
|
|
468
|
+
getRunningSyncKey(kind, effectiveAccountId, options),
|
|
469
|
+
);
|
|
470
|
+
const startedAt = new Date().toISOString();
|
|
471
|
+
if (current) {
|
|
472
|
+
return {
|
|
473
|
+
ok: false,
|
|
474
|
+
kind,
|
|
475
|
+
...(effectiveAccountId ? { accountId: effectiveAccountId } : {}),
|
|
476
|
+
startedAt,
|
|
477
|
+
summary: "Sync already running",
|
|
478
|
+
steps: [],
|
|
479
|
+
inProgress: true,
|
|
480
|
+
} satisfies WebSyncResponse;
|
|
481
|
+
}
|
|
420
482
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
483
|
+
const job = startWebSync(kind, effectiveAccountId, options);
|
|
484
|
+
while (job.inProgress) {
|
|
485
|
+
yield* Effect.sleep(25);
|
|
486
|
+
const latest = getWebSyncJob(job.id);
|
|
487
|
+
if (!latest?.inProgress) {
|
|
488
|
+
if (!latest?.result)
|
|
489
|
+
return yield* Effect.fail(new Error("Sync job disappeared"));
|
|
490
|
+
return latest.result;
|
|
491
|
+
}
|
|
428
492
|
}
|
|
429
|
-
}
|
|
430
493
|
|
|
431
|
-
|
|
432
|
-
|
|
494
|
+
if (!job.result)
|
|
495
|
+
return yield* Effect.fail(new Error("Sync job did not finish"));
|
|
496
|
+
return job.result;
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
export function runWebSync(
|
|
501
|
+
kind: WebSyncKind,
|
|
502
|
+
accountId?: string,
|
|
503
|
+
options: WebSyncOptions = {},
|
|
504
|
+
): Promise<WebSyncResponse> {
|
|
505
|
+
return runEffectPromise(runWebSyncEffect(kind, accountId, options));
|
|
433
506
|
}
|
|
434
507
|
|
|
435
508
|
export function clearWebSyncLocksForTests() {
|