@hatk/hatk 0.0.1-alpha.65 → 0.0.1-alpha.68
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/dist/dev-entry.d.ts.map +1 -1
- package/dist/dev-entry.js +5 -1
- package/dist/indexer.d.ts +20 -0
- package/dist/indexer.d.ts.map +1 -1
- package/dist/indexer.js +53 -3
- package/dist/main.js +8 -1
- package/dist/oauth/discovery.d.ts.map +1 -1
- package/dist/oauth/discovery.js +13 -1
- package/package.json +1 -1
package/dist/dev-entry.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev-entry.d.ts","sourceRoot":"","sources":["../src/dev-entry.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"dev-entry.d.ts","sourceRoot":"","sources":["../src/dev-entry.ts"],"names":[],"mappings":"AAmHA,eAAO,MAAM,OAAO,yCAKlB,CAAA;AAEF,yEAAyE;AACzE,wBAAsB,YAAY,kBAEjC;AAED,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AACpC,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA"}
|
package/dist/dev-entry.js
CHANGED
|
@@ -72,7 +72,11 @@ if (config.oauth) {
|
|
|
72
72
|
}
|
|
73
73
|
// Start indexer
|
|
74
74
|
const collectionSet = new Set(collections);
|
|
75
|
-
|
|
75
|
+
// Same escape hatch as main.ts: boot from live instead of the saved cursor.
|
|
76
|
+
const ignoreSavedCursor = Boolean(process.env.HATK_IGNORE_SAVED_CURSOR);
|
|
77
|
+
if (ignoreSavedCursor)
|
|
78
|
+
log('[dev] HATK_IGNORE_SAVED_CURSOR set — starting indexer from live');
|
|
79
|
+
const cursor = ignoreSavedCursor ? null : await getCursor('relay');
|
|
76
80
|
startIndexer({
|
|
77
81
|
relayUrl: config.relay,
|
|
78
82
|
plcUrl: config.plc,
|
package/dist/indexer.d.ts
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
/** Record the latest firehose sequence seen. Source of truth for cursor persistence and reconnect resume. */
|
|
2
|
+
export declare function noteSeq(seq: number): void;
|
|
3
|
+
/**
|
|
4
|
+
* Persist the latest firehose sequence when it has advanced past the stored
|
|
5
|
+
* cursor. Runs on a timer independent of the write path: an app whose
|
|
6
|
+
* collections rarely appear on the firehose never flushes a batch, so the
|
|
7
|
+
* flush-path cursor write alone leaves the stored cursor frozen — and a frozen
|
|
8
|
+
* cursor makes the relay replay its whole retention window at line rate
|
|
9
|
+
* (~135 GB/hour observed against bsky.network) on every boot and reconnect.
|
|
10
|
+
*/
|
|
11
|
+
export declare function checkpointCursor(): Promise<void>;
|
|
12
|
+
/**
|
|
13
|
+
* The cursor a reconnect should resume from: the latest seq this process has
|
|
14
|
+
* seen, falling back to the boot-time cursor before any message has arrived.
|
|
15
|
+
* Reusing the boot-time cursor on every reconnect would replay everything
|
|
16
|
+
* received since the process started.
|
|
17
|
+
*/
|
|
18
|
+
export declare function resumeCursor(liveSeq: number | null, bootCursor: string | null | undefined): string | null | undefined;
|
|
19
|
+
/** Reset module cursor state between tests. */
|
|
20
|
+
export declare function _resetCursorStateForTests(): void;
|
|
1
21
|
/**
|
|
2
22
|
* Auto-backfill a DID's repo when first seen on the firehose.
|
|
3
23
|
*
|
package/dist/indexer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"indexer.d.ts","sourceRoot":"","sources":["../src/indexer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"indexer.d.ts","sourceRoot":"","sources":["../src/indexer.ts"],"names":[],"mappings":"AA4KA,6GAA6G;AAC7G,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAEzC;AAED;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAStD;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,MAAM,GAAG,IAAI,EACtB,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACpC,MAAM,GAAG,IAAI,GAAG,SAAS,CAE3B;AAED,+CAA+C;AAC/C,wBAAgB,yBAAyB,IAAI,IAAI,CAGhD;AAED;;;;;;;GAOG;AACH,iEAAiE;AACjE,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGxD;AAED,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,SAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CA8EjF;AAED,8CAA8C;AAC9C,UAAU,WAAW;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACxB,iBAAiB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IAC/B,WAAW,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACzB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,YAAY,EAAE,MAAM,CAAA;IACpB,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAC5B;AAyBD;;;;;;;;;GASG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,SAAS,CAAC,CA4DxE"}
|
package/dist/indexer.js
CHANGED
|
@@ -12,8 +12,11 @@ import { validateRecord } from '@bigmoves/lexicon';
|
|
|
12
12
|
let buffer = [];
|
|
13
13
|
let flushTimer = null;
|
|
14
14
|
let lastSeq = null;
|
|
15
|
+
let lastPersistedSeq = null;
|
|
16
|
+
let cursorCheckpointTimer = null;
|
|
15
17
|
const BATCH_SIZE = 100;
|
|
16
18
|
const FLUSH_INTERVAL_MS = 500;
|
|
19
|
+
const CURSOR_CHECKPOINT_INTERVAL_MS = 5_000;
|
|
17
20
|
let writesSinceRebuild = 0;
|
|
18
21
|
let ftsRebuildInterval = 500;
|
|
19
22
|
// Event buffer for DIDs mid-backfill
|
|
@@ -57,8 +60,10 @@ async function flushBuffer() {
|
|
|
57
60
|
}
|
|
58
61
|
}
|
|
59
62
|
if (lastSeq !== null) {
|
|
63
|
+
const seq = lastSeq;
|
|
60
64
|
try {
|
|
61
|
-
await setCursor('relay', String(
|
|
65
|
+
await setCursor('relay', String(seq));
|
|
66
|
+
lastPersistedSeq = seq;
|
|
62
67
|
}
|
|
63
68
|
catch (err) {
|
|
64
69
|
cursorError = err.message;
|
|
@@ -134,6 +139,44 @@ function bufferWrite(item) {
|
|
|
134
139
|
scheduleFlush();
|
|
135
140
|
}
|
|
136
141
|
}
|
|
142
|
+
/** Record the latest firehose sequence seen. Source of truth for cursor persistence and reconnect resume. */
|
|
143
|
+
export function noteSeq(seq) {
|
|
144
|
+
lastSeq = seq;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Persist the latest firehose sequence when it has advanced past the stored
|
|
148
|
+
* cursor. Runs on a timer independent of the write path: an app whose
|
|
149
|
+
* collections rarely appear on the firehose never flushes a batch, so the
|
|
150
|
+
* flush-path cursor write alone leaves the stored cursor frozen — and a frozen
|
|
151
|
+
* cursor makes the relay replay its whole retention window at line rate
|
|
152
|
+
* (~135 GB/hour observed against bsky.network) on every boot and reconnect.
|
|
153
|
+
*/
|
|
154
|
+
export async function checkpointCursor() {
|
|
155
|
+
if (lastSeq === null || lastSeq === lastPersistedSeq)
|
|
156
|
+
return;
|
|
157
|
+
const seq = lastSeq;
|
|
158
|
+
try {
|
|
159
|
+
await setCursor('relay', String(seq));
|
|
160
|
+
lastPersistedSeq = seq;
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
emit('indexer', 'cursor_checkpoint_error', { cursor_seq: seq, error: err.message });
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* The cursor a reconnect should resume from: the latest seq this process has
|
|
168
|
+
* seen, falling back to the boot-time cursor before any message has arrived.
|
|
169
|
+
* Reusing the boot-time cursor on every reconnect would replay everything
|
|
170
|
+
* received since the process started.
|
|
171
|
+
*/
|
|
172
|
+
export function resumeCursor(liveSeq, bootCursor) {
|
|
173
|
+
return liveSeq !== null ? String(liveSeq) : bootCursor;
|
|
174
|
+
}
|
|
175
|
+
/** Reset module cursor state between tests. */
|
|
176
|
+
export function _resetCursorStateForTests() {
|
|
177
|
+
lastSeq = null;
|
|
178
|
+
lastPersistedSeq = null;
|
|
179
|
+
}
|
|
137
180
|
/**
|
|
138
181
|
* Auto-backfill a DID's repo when first seen on the firehose.
|
|
139
182
|
*
|
|
@@ -275,6 +318,13 @@ export async function startIndexer(opts) {
|
|
|
275
318
|
log(`[indexer] Warmed repo status cache with ${statuses.length} entries`);
|
|
276
319
|
}
|
|
277
320
|
// startMemoryDiagnostics()
|
|
321
|
+
// Checkpoint the cursor on a timer regardless of write activity (see
|
|
322
|
+
// checkpointCursor). Guarded so reconnects don't stack intervals; unref'd so
|
|
323
|
+
// it never keeps the process alive.
|
|
324
|
+
if (!cursorCheckpointTimer) {
|
|
325
|
+
cursorCheckpointTimer = setInterval(() => void checkpointCursor(), CURSOR_CHECKPOINT_INTERVAL_MS);
|
|
326
|
+
cursorCheckpointTimer.unref?.();
|
|
327
|
+
}
|
|
278
328
|
let wsUrl = `${relayUrl}/xrpc/com.atproto.sync.subscribeRepos`;
|
|
279
329
|
if (cursor) {
|
|
280
330
|
wsUrl += `?cursor=${cursor}`;
|
|
@@ -299,7 +349,7 @@ export async function startIndexer(opts) {
|
|
|
299
349
|
ws.addEventListener('open', () => log('[indexer] Connected to relay'));
|
|
300
350
|
ws.addEventListener('close', () => {
|
|
301
351
|
log('[indexer] Disconnected, reconnecting in 3s...');
|
|
302
|
-
setTimeout(() => startIndexer(opts), 3000);
|
|
352
|
+
setTimeout(() => startIndexer({ ...opts, cursor: resumeCursor(lastSeq, opts.cursor) }), 3000);
|
|
303
353
|
});
|
|
304
354
|
return ws;
|
|
305
355
|
}
|
|
@@ -380,7 +430,7 @@ function processMessage(bytes, collections) {
|
|
|
380
430
|
return;
|
|
381
431
|
// Track sequence number for cursor
|
|
382
432
|
if (body.value.seq)
|
|
383
|
-
|
|
433
|
+
noteSeq(body.value.seq);
|
|
384
434
|
const did = body.value.repo;
|
|
385
435
|
if (!did)
|
|
386
436
|
return;
|
package/dist/main.js
CHANGED
|
@@ -199,7 +199,14 @@ log(` Feeds: ${listFeeds()
|
|
|
199
199
|
.map((f) => f.name)
|
|
200
200
|
.join(', ')}`);
|
|
201
201
|
// 6. Start indexer with cursor
|
|
202
|
-
|
|
202
|
+
// HATK_IGNORE_SAVED_CURSOR=1 boots from the live firehose instead of the saved
|
|
203
|
+
// cursor — the escape hatch when a stale cursor would make the relay replay its
|
|
204
|
+
// retention window at line rate (see checkpointCursor in indexer.ts). Unset it
|
|
205
|
+
// after one boot so later restarts resume normally.
|
|
206
|
+
const ignoreSavedCursor = Boolean(process.env.HATK_IGNORE_SAVED_CURSOR);
|
|
207
|
+
if (ignoreSavedCursor)
|
|
208
|
+
log('[main] HATK_IGNORE_SAVED_CURSOR set — starting indexer from live');
|
|
209
|
+
const cursor = ignoreSavedCursor ? null : await getCursor('relay');
|
|
203
210
|
startIndexer({
|
|
204
211
|
relayUrl: config.relay,
|
|
205
212
|
plcUrl: config.plc,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../../src/oauth/discovery.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAA;IACd,sBAAsB,EAAE,MAAM,CAAA;IAC9B,cAAc,EAAE,MAAM,CAAA;IACtB,qCAAqC,CAAC,EAAE,MAAM,CAAA;IAC9C,QAAQ,EAAE,MAAM,CAAA;IAChB,iCAAiC,CAAC,EAAE,MAAM,EAAE,CAAA;IAC5C,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAED,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAU1E;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,GAAG,GAAG,MAAM,GAAG,IAAI,CAGzD;AAED,wBAAsB,8BAA8B,CAClD,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC;IAAE,qBAAqB,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAI9C;AAED,wBAAsB,uBAAuB,CAAC,kBAAkB,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAIrG;AAED,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;IACT,WAAW,EAAE,MAAM,CAAA;IACnB,kBAAkB,EAAE,MAAM,CAAA;IAC1B,kBAAkB,EAAE,kBAAkB,CAAA;CACvC,CAAC,CAWD;AAED,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../../src/oauth/discovery.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAA;IACd,sBAAsB,EAAE,MAAM,CAAA;IAC9B,cAAc,EAAE,MAAM,CAAA;IACtB,qCAAqC,CAAC,EAAE,MAAM,CAAA;IAC9C,QAAQ,EAAE,MAAM,CAAA;IAChB,iCAAiC,CAAC,EAAE,MAAM,EAAE,CAAA;IAC5C,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAED,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAU1E;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,GAAG,GAAG,MAAM,GAAG,IAAI,CAGzD;AAED,wBAAsB,8BAA8B,CAClD,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC;IAAE,qBAAqB,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAI9C;AAED,wBAAsB,uBAAuB,CAAC,kBAAkB,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAIrG;AAED,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;IACT,WAAW,EAAE,MAAM,CAAA;IACnB,kBAAkB,EAAE,MAAM,CAAA;IAC1B,kBAAkB,EAAE,kBAAkB,CAAA;CACvC,CAAC,CAWD;AAED,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAgBtF"}
|
package/dist/oauth/discovery.js
CHANGED
|
@@ -41,7 +41,19 @@ export async function discoverAuthServer(did, plcUrl) {
|
|
|
41
41
|
return { pdsEndpoint, authServerEndpoint, authServerMetadata };
|
|
42
42
|
}
|
|
43
43
|
export async function resolveHandle(handle, relayUrl) {
|
|
44
|
-
|
|
44
|
+
// Resolve against the configured network: the localhost PDS in dev, the public
|
|
45
|
+
// AppView in prod, or a self-hosted PDS (e.g. preview environments) derived
|
|
46
|
+
// from its relay/firehose URL — otherwise self-hosted handles 400 against bsky.
|
|
47
|
+
let baseUrl;
|
|
48
|
+
if (relayUrl?.includes('localhost:2583')) {
|
|
49
|
+
baseUrl = 'http://localhost:2583';
|
|
50
|
+
}
|
|
51
|
+
else if (!relayUrl || relayUrl.includes('bsky.network')) {
|
|
52
|
+
baseUrl = 'https://bsky.social';
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
baseUrl = relayUrl.replace(/^ws/, 'http'); // wss://pds.example → https://pds.example
|
|
56
|
+
}
|
|
45
57
|
const res = await fetch(`${baseUrl}/xrpc/com.atproto.identity.resolveHandle?handle=${encodeURIComponent(handle)}`);
|
|
46
58
|
if (!res.ok)
|
|
47
59
|
throw new Error(`resolveHandle failed: ${res.status}`);
|