@atproto/tap 0.3.11 → 0.4.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 +31 -0
- package/dist/channel.d.ts +30 -6
- package/dist/channel.d.ts.map +1 -1
- package/dist/channel.js +98 -47
- package/dist/channel.js.map +1 -1
- package/dist/lex-indexer.d.ts.map +1 -1
- package/dist/lex-indexer.js.map +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +6 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# @atproto/tap
|
|
2
2
|
|
|
3
|
+
## 0.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#5285](https://github.com/bluesky-social/atproto/pull/5285) [`d12f6ac`](https://github.com/bluesky-social/atproto/commit/d12f6ac6e2cb2590446bf9f2051287bac058c092) Thanks [@devinivy](https://github.com/devinivy)! - **BREAKING:** `TapWebsocketOptions` now takes an explicit set of options rather than arbitrary `ws` `ClientOptions`, and adds a `headers` option for the upgrade request.
|
|
8
|
+
|
|
9
|
+
`ackEvent()` now resolves once the ack has been sent — or recorded for the next connection — rather than once it is confirmed delivered. Awaiting confirmed delivery from a handler could deadlock the channel (a bug that predates this change). Acks issued while disconnected are flushed when the next connection comes up, and Tap's at-least-once redelivery covers an ack lost with its connection.
|
|
10
|
+
|
|
11
|
+
`onReconnectError`'s `initialSetup` argument now means "first attempt of this reconnect cycle" rather than "before the first-ever successful connection".
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- [#5295](https://github.com/bluesky-social/atproto/pull/5295) [`6a3d607`](https://github.com/bluesky-social/atproto/commit/6a3d6073cb66c527b5b109242049c85c36b9658c) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Update vitest dependencies
|
|
16
|
+
|
|
17
|
+
- [#5301](https://github.com/bluesky-social/atproto/pull/5301) [`8c07338`](https://github.com/bluesky-social/atproto/commit/8c07338232aa69427aa65322a555f70e0211d6d7) Thanks [@43081j](https://github.com/43081j)! - Switch from destructured default imports to named imports of CommonJS dependencies.
|
|
18
|
+
|
|
19
|
+
- Updated dependencies [[`6a3d607`](https://github.com/bluesky-social/atproto/commit/6a3d6073cb66c527b5b109242049c85c36b9658c), [`8c07338`](https://github.com/bluesky-social/atproto/commit/8c07338232aa69427aa65322a555f70e0211d6d7), [`d12f6ac`](https://github.com/bluesky-social/atproto/commit/d12f6ac6e2cb2590446bf9f2051287bac058c092)]:
|
|
20
|
+
- @atproto/lex@0.3.3
|
|
21
|
+
- @atproto/syntax@0.7.3
|
|
22
|
+
- @atproto/ws-client@0.2.0
|
|
23
|
+
- @atproto/common@0.7.4
|
|
24
|
+
|
|
25
|
+
## 0.3.12
|
|
26
|
+
|
|
27
|
+
### Patch Changes
|
|
28
|
+
|
|
29
|
+
- Updated dependencies []:
|
|
30
|
+
- @atproto/lex@0.3.2
|
|
31
|
+
- @atproto/common@0.7.3
|
|
32
|
+
- @atproto/ws-client@0.1.8
|
|
33
|
+
|
|
3
34
|
## 0.3.11
|
|
4
35
|
|
|
5
36
|
### Patch Changes
|
package/dist/channel.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type HeadersInit } from '@atproto/ws-client';
|
|
2
2
|
import { type TapEvent } from './types.js';
|
|
3
3
|
export interface HandlerOpts {
|
|
4
4
|
signal: AbortSignal;
|
|
@@ -8,10 +8,12 @@ export interface TapHandler {
|
|
|
8
8
|
onEvent: (evt: TapEvent, opts: HandlerOpts) => void | Promise<void>;
|
|
9
9
|
onError: (err: Error) => void;
|
|
10
10
|
}
|
|
11
|
-
export type TapWebsocketOptions =
|
|
11
|
+
export type TapWebsocketOptions = {
|
|
12
12
|
adminPassword?: string;
|
|
13
13
|
maxReconnectSeconds?: number;
|
|
14
14
|
heartbeatIntervalMs?: number;
|
|
15
|
+
/** Applied to the connection's upgrade request (Node.js only). */
|
|
16
|
+
headers?: HeadersInit;
|
|
15
17
|
onReconnectError?: (error: unknown, n: number, initialSetup: boolean) => void;
|
|
16
18
|
};
|
|
17
19
|
export declare class TapChannel implements AsyncDisposable {
|
|
@@ -19,12 +21,34 @@ export declare class TapChannel implements AsyncDisposable {
|
|
|
19
21
|
private handler;
|
|
20
22
|
private readonly abortController;
|
|
21
23
|
private readonly destroyDefer;
|
|
22
|
-
|
|
24
|
+
/** The current connection's sender, or undefined between connections. */
|
|
25
|
+
private sender?;
|
|
26
|
+
/**
|
|
27
|
+
* Acks accepted while there was no connection to send them on, flushed by the
|
|
28
|
+
* `onConnect` hook below.
|
|
29
|
+
*
|
|
30
|
+
* Not a list of promises the caller awaits, deliberately: a handler runs inside
|
|
31
|
+
* the iteration, and the reconnect that would flush it only happens when the
|
|
32
|
+
* iteration advances — so awaiting delivery from a handler would block the very
|
|
33
|
+
* pull that makes delivery possible. Acks are recorded and the handler
|
|
34
|
+
* continues; Tap redelivers anything it never sees acked.
|
|
35
|
+
*/
|
|
36
|
+
private readonly pendingAcks;
|
|
23
37
|
constructor(url: string, handler: TapHandler, wsOpts?: TapWebsocketOptions);
|
|
38
|
+
/**
|
|
39
|
+
* Records an ack for delivery. Resolves once the ack has been sent, or accepted
|
|
40
|
+
* for the next connection — not once the peer has processed it.
|
|
41
|
+
*
|
|
42
|
+
* It can't mean more than that: a handler runs inside the iteration, and a
|
|
43
|
+
* reconnect only happens when the iteration advances, so awaiting confirmed
|
|
44
|
+
* delivery here would block the pull delivery depends on. Tap's own
|
|
45
|
+
* at-least-once redelivery covers an ack lost with its connection.
|
|
46
|
+
*
|
|
47
|
+
* Acks are per-event, idempotent, and order-insensitive server-side (see
|
|
48
|
+
* Outbox.AckEvent), so a duplicate or out-of-order ack is harmless.
|
|
49
|
+
*/
|
|
24
50
|
ackEvent(id: number): Promise<void>;
|
|
25
|
-
private
|
|
26
|
-
private bufferAndSendAck;
|
|
27
|
-
private flushBufferedAcks;
|
|
51
|
+
private flushPendingAcks;
|
|
28
52
|
start(): Promise<void>;
|
|
29
53
|
private processWsEvent;
|
|
30
54
|
destroy(): Promise<void>;
|
package/dist/channel.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,WAAW,EAOjB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,KAAK,QAAQ,EAAiB,MAAM,YAAY,CAAA;AAsBzD,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,WAAW,CAAA;IACnB,GAAG,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CACzB;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,CAAC,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACnE,OAAO,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAA;CAC9B;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,kEAAkE;IAClE,OAAO,CAAC,EAAE,WAAW,CAAA;IACrB,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,KAAK,IAAI,CAAA;CAC9E,CAAA;AAMD,qBAAa,UAAW,YAAW,eAAe;IAChD,OAAO,CAAC,EAAE,CAA2B;IACrC,OAAO,CAAC,OAAO,CAAY;IAE3B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAyC;IACzE,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAiC;IAE9D,yEAAyE;IACzE,OAAO,CAAC,MAAM,CAAC,CAAgB;IAC/B;;;;;;;;;OASG;IACH,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAoB;IAEhD,YACE,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,UAAU,EACnB,MAAM,GAAE,mBAAwB,EAiCjC;IAED;;;;;;;;;;;OAWG;IACG,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAaxC;YAIa,gBAAgB;IAiBxB,KAAK,kBAoBV;YAEa,cAAc;IA6BtB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAG7B;IAEK,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAE3C;CACF"}
|
package/dist/channel.js
CHANGED
|
@@ -1,69 +1,115 @@
|
|
|
1
1
|
import { createDeferrable } from '@atproto/common';
|
|
2
2
|
import { lexParse } from '@atproto/lex';
|
|
3
|
-
import {
|
|
3
|
+
import { CloseCode, CloseError, HeartbeatTimeoutError, IdleTimeoutError, SocketError, websocket, } from '@atproto/ws-client';
|
|
4
4
|
import { parseTapEvent } from './types.js';
|
|
5
5
|
import { formatAdminAuthHeader, isCausedBySignal } from './util.js';
|
|
6
|
+
// Matches the classification the legacy errno-matching client had: reconnect only
|
|
7
|
+
// on a genuine transport failure or an abnormal close (1006). Any other close
|
|
8
|
+
// code, including the synthetic 1005 a bare socket.close() produces, ends the
|
|
9
|
+
// session as before.
|
|
10
|
+
function shouldReconnect(error) {
|
|
11
|
+
if (error instanceof CloseError) {
|
|
12
|
+
return error.code === CloseCode.Abnormal;
|
|
13
|
+
}
|
|
14
|
+
if (error instanceof SocketError ||
|
|
15
|
+
error instanceof HeartbeatTimeoutError ||
|
|
16
|
+
error instanceof IdleTimeoutError) {
|
|
17
|
+
// Defer to each error's own answer so this can't drift from the taxonomy.
|
|
18
|
+
return error.shouldRetry();
|
|
19
|
+
}
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
function ackMessage(id) {
|
|
23
|
+
return JSON.stringify({ type: 'ack', id });
|
|
24
|
+
}
|
|
6
25
|
export class TapChannel {
|
|
7
26
|
constructor(url, handler, wsOpts = {}) {
|
|
8
27
|
this.abortController = new AbortController();
|
|
9
28
|
this.destroyDefer = createDeferrable();
|
|
10
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Acks accepted while there was no connection to send them on, flushed by the
|
|
31
|
+
* `onConnect` hook below.
|
|
32
|
+
*
|
|
33
|
+
* Not a list of promises the caller awaits, deliberately: a handler runs inside
|
|
34
|
+
* the iteration, and the reconnect that would flush it only happens when the
|
|
35
|
+
* iteration advances — so awaiting delivery from a handler would block the very
|
|
36
|
+
* pull that makes delivery possible. Acks are recorded and the handler
|
|
37
|
+
* continues; Tap redelivers anything it never sees acked.
|
|
38
|
+
*/
|
|
39
|
+
this.pendingAcks = new Set();
|
|
11
40
|
this.handler = handler;
|
|
12
|
-
const { adminPassword,
|
|
13
|
-
|
|
41
|
+
const { adminPassword, onReconnectError } = wsOpts;
|
|
42
|
+
const headers = new Headers(wsOpts.headers);
|
|
14
43
|
if (adminPassword) {
|
|
15
|
-
headers
|
|
16
|
-
headers['Authorization'] = formatAdminAuthHeader(adminPassword);
|
|
44
|
+
headers.set('Authorization', formatAdminAuthHeader(adminPassword));
|
|
17
45
|
}
|
|
18
|
-
this.ws =
|
|
19
|
-
|
|
20
|
-
onReconnect: () => {
|
|
21
|
-
this.flushBufferedAcks();
|
|
22
|
-
},
|
|
23
|
-
signal: this.abortController.signal,
|
|
24
|
-
...rest,
|
|
46
|
+
this.ws = websocket(url, {
|
|
47
|
+
dataMode: 'text',
|
|
25
48
|
headers,
|
|
49
|
+
maxReconnectSeconds: wsOpts.maxReconnectSeconds,
|
|
50
|
+
// `{}` means "on at the default interval"; see Subscription.
|
|
51
|
+
heartbeat: wsOpts.heartbeatIntervalMs
|
|
52
|
+
? { intervalMs: wsOpts.heartbeatIntervalMs }
|
|
53
|
+
: {},
|
|
54
|
+
signal: this.abortController.signal,
|
|
55
|
+
shouldReconnect,
|
|
56
|
+
// Fires for every connection, the first included, so an ack recorded before
|
|
57
|
+
// anything ever connected is flushed by whichever connection comes up.
|
|
58
|
+
onConnect: (sender) => {
|
|
59
|
+
this.sender = sender;
|
|
60
|
+
void this.flushPendingAcks();
|
|
61
|
+
},
|
|
62
|
+
onDisconnect: () => {
|
|
63
|
+
this.sender = undefined;
|
|
64
|
+
},
|
|
65
|
+
// Only retries are reported here; a fatal error reaches `start()` as the
|
|
66
|
+
// iterator's rejection instead.
|
|
67
|
+
onReconnect: (error, { attempt }) => {
|
|
68
|
+
onReconnectError?.(error, attempt, attempt === 0);
|
|
69
|
+
},
|
|
26
70
|
});
|
|
27
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Records an ack for delivery. Resolves once the ack has been sent, or accepted
|
|
74
|
+
* for the next connection — not once the peer has processed it.
|
|
75
|
+
*
|
|
76
|
+
* It can't mean more than that: a handler runs inside the iteration, and a
|
|
77
|
+
* reconnect only happens when the iteration advances, so awaiting confirmed
|
|
78
|
+
* delivery here would block the pull delivery depends on. Tap's own
|
|
79
|
+
* at-least-once redelivery covers an ack lost with its connection.
|
|
80
|
+
*
|
|
81
|
+
* Acks are per-event, idempotent, and order-insensitive server-side (see
|
|
82
|
+
* Outbox.AckEvent), so a duplicate or out-of-order ack is harmless.
|
|
83
|
+
*/
|
|
28
84
|
async ackEvent(id) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
85
|
+
const sender = this.sender;
|
|
86
|
+
if (!sender) {
|
|
87
|
+
this.pendingAcks.add(id);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
await sender.send(ackMessage(id));
|
|
36
92
|
}
|
|
37
|
-
|
|
38
|
-
|
|
93
|
+
catch {
|
|
94
|
+
// The connection died under us: hand the ack to the next one rather than
|
|
95
|
+
// reporting a failure the caller can do nothing about.
|
|
96
|
+
this.pendingAcks.add(id);
|
|
39
97
|
}
|
|
40
98
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
this.
|
|
48
|
-
id,
|
|
49
|
-
defer,
|
|
50
|
-
});
|
|
51
|
-
await defer.complete;
|
|
52
|
-
}
|
|
53
|
-
async flushBufferedAcks() {
|
|
54
|
-
while (this.bufferedAcks.length > 0) {
|
|
99
|
+
// Driven by `onConnect`, never by a handler: it runs off the socket's own event,
|
|
100
|
+
// so it doesn't depend on the iteration making progress.
|
|
101
|
+
async flushPendingAcks() {
|
|
102
|
+
const sender = this.sender;
|
|
103
|
+
if (!sender)
|
|
104
|
+
return;
|
|
105
|
+
for (const id of [...this.pendingAcks]) {
|
|
55
106
|
try {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
await this.sendAck(ack.id);
|
|
61
|
-
ack.defer.resolve();
|
|
62
|
-
this.bufferedAcks = this.bufferedAcks.slice(1);
|
|
107
|
+
await sender.send(ackMessage(id));
|
|
108
|
+
this.pendingAcks.delete(id);
|
|
63
109
|
}
|
|
64
110
|
catch (cause) {
|
|
65
|
-
|
|
66
|
-
this.handler.onError(
|
|
111
|
+
// Still unsendable; leave it queued for the connection after this one.
|
|
112
|
+
this.handler.onError(new Error(`failed to send ack for event ${id}`, { cause }));
|
|
67
113
|
return;
|
|
68
114
|
}
|
|
69
115
|
}
|
|
@@ -76,7 +122,12 @@ export class TapChannel {
|
|
|
76
122
|
}
|
|
77
123
|
}
|
|
78
124
|
catch (err) {
|
|
79
|
-
|
|
125
|
+
// A CloseError only reaches here once `shouldReconnect` has declined to
|
|
126
|
+
// retry — i.e. the peer ended the session with an ordinary close frame.
|
|
127
|
+
// That's an expected end, matching the previous client's behavior of
|
|
128
|
+
// ending silently on any non-abnormal close.
|
|
129
|
+
if (!isCausedBySignal(err, this.abortController.signal) &&
|
|
130
|
+
!(err instanceof CloseError)) {
|
|
80
131
|
throw err;
|
|
81
132
|
}
|
|
82
133
|
}
|
|
@@ -87,7 +138,7 @@ export class TapChannel {
|
|
|
87
138
|
async processWsEvent(chunk) {
|
|
88
139
|
let evt;
|
|
89
140
|
try {
|
|
90
|
-
const data = lexParse(chunk
|
|
141
|
+
const data = lexParse(chunk, {
|
|
91
142
|
// Reject invalid CIDs and blobs
|
|
92
143
|
strict: true,
|
|
93
144
|
});
|
package/dist/channel.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"channel.js","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"AACA,OAAO,EAAmB,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AACnE,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AACvC,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AACvD,OAAO,EAAiB,aAAa,EAAE,MAAM,YAAY,CAAA;AACzD,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAwBnE,MAAM,OAAO,UAAU;IASrB,YACE,GAAW,EACX,OAAmB,EACnB,MAAM,GAAwB,EAAE;QARjB,oBAAe,GAAoB,IAAI,eAAe,EAAE,CAAA;QACxD,iBAAY,GAAe,gBAAgB,EAAE,CAAA;QAEtD,iBAAY,GAAkB,EAAE,CAAA;QAOtC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;QACtB,MAAM,EAAE,aAAa,EAAE,GAAG,IAAI,EAAE,GAAG,MAAM,CAAA;QACzC,IAAI,OAAO,GAAG,IAAI,CAAC,OAAO,CAAA;QAC1B,IAAI,aAAa,EAAE,CAAC;YAClB,OAAO,KAAK,EAAE,CAAA;YACd,OAAO,CAAC,eAAe,CAAC,GAAG,qBAAqB,CAAC,aAAa,CAAC,CAAA;QACjE,CAAC;QACD,IAAI,CAAC,EAAE,GAAG,IAAI,kBAAkB,CAAC;YAC/B,MAAM,EAAE,KAAK,IAAI,EAAE,CAAC,GAAG;YACvB,WAAW,EAAE,GAAG,EAAE;gBAChB,IAAI,CAAC,iBAAiB,EAAE,CAAA;YAC1B,CAAC;YACD,MAAM,EAAE,IAAI,CAAC,eAAe,CAAC,MAAM;YACnC,GAAG,IAAI;YACP,OAAO;SACR,CAAC,CAAA;IACJ,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,EAAU;QACvB,IAAI,IAAI,CAAC,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC;YAC1B,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;YACxB,CAAC;YAAC,MAAM,CAAC;gBACP,MAAM,IAAI,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAA;YACjC,CAAC;QACH,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAA;QACjC,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,OAAO,CAAC,EAAU;QAC9B,MAAM,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC,CAAA;IACzD,CAAC;IAED,gDAAgD;IACxC,KAAK,CAAC,gBAAgB,CAAC,EAAU;QACvC,MAAM,KAAK,GAAG,gBAAgB,EAAE,CAAA;QAChC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC;YACrB,EAAE;YACF,KAAK;SACN,CAAC,CAAA;QACF,MAAM,KAAK,CAAC,QAAQ,CAAA;IACtB,CAAC;IAEO,KAAK,CAAC,iBAAiB;QAC7B,OAAO,IAAI,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACpC,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;gBACnC,IAAI,CAAC,GAAG,EAAE,CAAC;oBACT,OAAM;gBACR,CAAC;gBACD,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;gBAC1B,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,CAAA;gBACnB,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;YAChD,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,KAAK,GAAG,IAAI,KAAK,CACrB,gCAAgC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,EACtD,EAAE,KAAK,EAAE,CACV,CAAA;gBACD,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;gBAC3B,OAAM;YACR,CAAC;QACH,CAAC;IACH,CAAC;IAED,KAAK,CAAC,KAAK;QACT,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,cAAc,EAAE,CAAA;QAC5C,IAAI,CAAC;YACH,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC;gBAClC,MAAM,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,CAAA;YAClC,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,EAAE,CAAC;gBACxD,MAAM,GAAG,CAAA;YACX,CAAC;QACH,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,CAAA;QAC7B,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,cAAc,CAAC,KAAiB;QAC5C,IAAI,GAAa,CAAA;QACjB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE;gBACtC,gCAAgC;gBAChC,MAAM,EAAE,IAAI;aACb,CAAC,CAAA;YACF,GAAG,GAAG,aAAa,CAAC,IAAI,CAAC,CAAA;QAC3B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,yBAAyB,EAAE,EAAE,KAAK,EAAE,CAAC,CAAA;YAC7D,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;YAC3B,OAAM;QACR,CAAC;QAED,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE;gBAC9B,MAAM,EAAE,IAAI,CAAC,eAAe,CAAC,MAAM;gBACnC,GAAG,EAAE,KAAK,IAAI,EAAE;oBACd,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;gBAC7B,CAAC;aACF,CAAC,CAAA;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,qCAAqC;YACrC,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,2BAA2B,GAAG,CAAC,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAA;YACvE,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;YAC3B,OAAM;QACR,CAAC;IACH,CAAC;IAED,KAAK,CAAC,OAAO;QACX,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,CAAA;QAC5B,MAAM,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAA;IAClC,CAAC;IAED,KAAK,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC;QACzB,MAAM,IAAI,CAAC,OAAO,EAAE,CAAA;IACtB,CAAC;CACF","sourcesContent":["import type { ClientOptions } from 'ws'\nimport { type Deferrable, createDeferrable } from '@atproto/common'\nimport { lexParse } from '@atproto/lex'\nimport { WebSocketKeepAlive } from '@atproto/ws-client'\nimport { type TapEvent, parseTapEvent } from './types.js'\nimport { formatAdminAuthHeader, isCausedBySignal } from './util.js'\n\nexport interface HandlerOpts {\n signal: AbortSignal\n ack: () => Promise<void>\n}\n\nexport interface TapHandler {\n onEvent: (evt: TapEvent, opts: HandlerOpts) => void | Promise<void>\n onError: (err: Error) => void\n}\n\nexport type TapWebsocketOptions = ClientOptions & {\n adminPassword?: string\n maxReconnectSeconds?: number\n heartbeatIntervalMs?: number\n onReconnectError?: (error: unknown, n: number, initialSetup: boolean) => void\n}\n\ntype BufferedAck = {\n id: number\n defer: Deferrable\n}\n\nexport class TapChannel implements AsyncDisposable {\n private ws: WebSocketKeepAlive\n private handler: TapHandler\n\n private readonly abortController: AbortController = new AbortController()\n private readonly destroyDefer: Deferrable = createDeferrable()\n\n private bufferedAcks: BufferedAck[] = []\n\n constructor(\n url: string,\n handler: TapHandler,\n wsOpts: TapWebsocketOptions = {},\n ) {\n this.handler = handler\n const { adminPassword, ...rest } = wsOpts\n let headers = rest.headers\n if (adminPassword) {\n headers ??= {}\n headers['Authorization'] = formatAdminAuthHeader(adminPassword)\n }\n this.ws = new WebSocketKeepAlive({\n getUrl: async () => url,\n onReconnect: () => {\n this.flushBufferedAcks()\n },\n signal: this.abortController.signal,\n ...rest,\n headers,\n })\n }\n\n async ackEvent(id: number): Promise<void> {\n if (this.ws.isConnected()) {\n try {\n await this.sendAck(id)\n } catch {\n await this.bufferAndSendAck(id)\n }\n } else {\n await this.bufferAndSendAck(id)\n }\n }\n\n private async sendAck(id: number): Promise<void> {\n await this.ws.send(JSON.stringify({ type: 'ack', id }))\n }\n\n // resolves after the ack has been actually sent\n private async bufferAndSendAck(id: number): Promise<void> {\n const defer = createDeferrable()\n this.bufferedAcks.push({\n id,\n defer,\n })\n await defer.complete\n }\n\n private async flushBufferedAcks(): Promise<void> {\n while (this.bufferedAcks.length > 0) {\n try {\n const ack = this.bufferedAcks.at(0)\n if (!ack) {\n return\n }\n await this.sendAck(ack.id)\n ack.defer.resolve()\n this.bufferedAcks = this.bufferedAcks.slice(1)\n } catch (cause) {\n const error = new Error(\n `failed to send ack for event ${this.bufferedAcks[0]}`,\n { cause },\n )\n this.handler.onError(error)\n return\n }\n }\n }\n\n async start() {\n this.abortController.signal.throwIfAborted()\n try {\n for await (const chunk of this.ws) {\n await this.processWsEvent(chunk)\n }\n } catch (err) {\n if (!isCausedBySignal(err, this.abortController.signal)) {\n throw err\n }\n } finally {\n this.destroyDefer.resolve()\n }\n }\n\n private async processWsEvent(chunk: Uint8Array) {\n let evt: TapEvent\n try {\n const data = lexParse(chunk.toString(), {\n // Reject invalid CIDs and blobs\n strict: true,\n })\n evt = parseTapEvent(data)\n } catch (cause) {\n const error = new Error(`Failed to parse message`, { cause })\n this.handler.onError(error)\n return\n }\n\n try {\n await this.handler.onEvent(evt, {\n signal: this.abortController.signal,\n ack: async () => {\n await this.ackEvent(evt.id)\n },\n })\n } catch (cause) {\n // Don't ack on error - let Tap retry\n const error = new Error(`Failed to process event ${evt.id}`, { cause })\n this.handler.onError(error)\n return\n }\n }\n\n async destroy(): Promise<void> {\n this.abortController.abort()\n await this.destroyDefer.complete\n }\n\n async [Symbol.asyncDispose](): Promise<void> {\n await this.destroy()\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"channel.js","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Deferrable, createDeferrable } from '@atproto/common'\nimport { lexParse } from '@atproto/lex'\nimport {\n CloseCode,\n CloseError,\n type HeadersInit,\n HeartbeatTimeoutError,\n IdleTimeoutError,\n type Sender,\n SocketError,\n type WebSocketIterable,\n websocket,\n} from '@atproto/ws-client'\nimport { type TapEvent, parseTapEvent } from './types.js'\nimport { formatAdminAuthHeader, isCausedBySignal } from './util.js'\n\n// Matches the classification the legacy errno-matching client had: reconnect only\n// on a genuine transport failure or an abnormal close (1006). Any other close\n// code, including the synthetic 1005 a bare socket.close() produces, ends the\n// session as before.\nfunction shouldReconnect(error: unknown): boolean {\n if (error instanceof CloseError) {\n return error.code === CloseCode.Abnormal\n }\n if (\n error instanceof SocketError ||\n error instanceof HeartbeatTimeoutError ||\n error instanceof IdleTimeoutError\n ) {\n // Defer to each error's own answer so this can't drift from the taxonomy.\n return error.shouldRetry()\n }\n return false\n}\n\nexport interface HandlerOpts {\n signal: AbortSignal\n ack: () => Promise<void>\n}\n\nexport interface TapHandler {\n onEvent: (evt: TapEvent, opts: HandlerOpts) => void | Promise<void>\n onError: (err: Error) => void\n}\n\nexport type TapWebsocketOptions = {\n adminPassword?: string\n maxReconnectSeconds?: number\n heartbeatIntervalMs?: number\n /** Applied to the connection's upgrade request (Node.js only). */\n headers?: HeadersInit\n onReconnectError?: (error: unknown, n: number, initialSetup: boolean) => void\n}\n\nfunction ackMessage(id: number): string {\n return JSON.stringify({ type: 'ack', id })\n}\n\nexport class TapChannel implements AsyncDisposable {\n private ws: WebSocketIterable<'text'>\n private handler: TapHandler\n\n private readonly abortController: AbortController = new AbortController()\n private readonly destroyDefer: Deferrable = createDeferrable()\n\n /** The current connection's sender, or undefined between connections. */\n private sender?: Sender<'text'>\n /**\n * Acks accepted while there was no connection to send them on, flushed by the\n * `onConnect` hook below.\n *\n * Not a list of promises the caller awaits, deliberately: a handler runs inside\n * the iteration, and the reconnect that would flush it only happens when the\n * iteration advances — so awaiting delivery from a handler would block the very\n * pull that makes delivery possible. Acks are recorded and the handler\n * continues; Tap redelivers anything it never sees acked.\n */\n private readonly pendingAcks = new Set<number>()\n\n constructor(\n url: string,\n handler: TapHandler,\n wsOpts: TapWebsocketOptions = {},\n ) {\n this.handler = handler\n const { adminPassword, onReconnectError } = wsOpts\n const headers = new Headers(wsOpts.headers)\n if (adminPassword) {\n headers.set('Authorization', formatAdminAuthHeader(adminPassword))\n }\n this.ws = websocket(url, {\n dataMode: 'text',\n headers,\n maxReconnectSeconds: wsOpts.maxReconnectSeconds,\n // `{}` means \"on at the default interval\"; see Subscription.\n heartbeat: wsOpts.heartbeatIntervalMs\n ? { intervalMs: wsOpts.heartbeatIntervalMs }\n : {},\n signal: this.abortController.signal,\n shouldReconnect,\n // Fires for every connection, the first included, so an ack recorded before\n // anything ever connected is flushed by whichever connection comes up.\n onConnect: (sender) => {\n this.sender = sender\n void this.flushPendingAcks()\n },\n onDisconnect: () => {\n this.sender = undefined\n },\n // Only retries are reported here; a fatal error reaches `start()` as the\n // iterator's rejection instead.\n onReconnect: (error, { attempt }) => {\n onReconnectError?.(error, attempt, attempt === 0)\n },\n })\n }\n\n /**\n * Records an ack for delivery. Resolves once the ack has been sent, or accepted\n * for the next connection — not once the peer has processed it.\n *\n * It can't mean more than that: a handler runs inside the iteration, and a\n * reconnect only happens when the iteration advances, so awaiting confirmed\n * delivery here would block the pull delivery depends on. Tap's own\n * at-least-once redelivery covers an ack lost with its connection.\n *\n * Acks are per-event, idempotent, and order-insensitive server-side (see\n * Outbox.AckEvent), so a duplicate or out-of-order ack is harmless.\n */\n async ackEvent(id: number): Promise<void> {\n const sender = this.sender\n if (!sender) {\n this.pendingAcks.add(id)\n return\n }\n try {\n await sender.send(ackMessage(id))\n } catch {\n // The connection died under us: hand the ack to the next one rather than\n // reporting a failure the caller can do nothing about.\n this.pendingAcks.add(id)\n }\n }\n\n // Driven by `onConnect`, never by a handler: it runs off the socket's own event,\n // so it doesn't depend on the iteration making progress.\n private async flushPendingAcks(): Promise<void> {\n const sender = this.sender\n if (!sender) return\n for (const id of [...this.pendingAcks]) {\n try {\n await sender.send(ackMessage(id))\n this.pendingAcks.delete(id)\n } catch (cause) {\n // Still unsendable; leave it queued for the connection after this one.\n this.handler.onError(\n new Error(`failed to send ack for event ${id}`, { cause }),\n )\n return\n }\n }\n }\n\n async start() {\n this.abortController.signal.throwIfAborted()\n try {\n for await (const chunk of this.ws) {\n await this.processWsEvent(chunk)\n }\n } catch (err) {\n // A CloseError only reaches here once `shouldReconnect` has declined to\n // retry — i.e. the peer ended the session with an ordinary close frame.\n // That's an expected end, matching the previous client's behavior of\n // ending silently on any non-abnormal close.\n if (\n !isCausedBySignal(err, this.abortController.signal) &&\n !(err instanceof CloseError)\n ) {\n throw err\n }\n } finally {\n this.destroyDefer.resolve()\n }\n }\n\n private async processWsEvent(chunk: string) {\n let evt: TapEvent\n try {\n const data = lexParse(chunk, {\n // Reject invalid CIDs and blobs\n strict: true,\n })\n evt = parseTapEvent(data)\n } catch (cause) {\n const error = new Error(`Failed to parse message`, { cause })\n this.handler.onError(error)\n return\n }\n\n try {\n await this.handler.onEvent(evt, {\n signal: this.abortController.signal,\n ack: async () => {\n await this.ackEvent(evt.id)\n },\n })\n } catch (cause) {\n // Don't ack on error - let Tap retry\n const error = new Error(`Failed to process event ${evt.id}`, { cause })\n this.handler.onError(error)\n return\n }\n }\n\n async destroy(): Promise<void> {\n this.abortController.abort()\n await this.destroyDefer.complete\n }\n\n async [Symbol.asyncDispose](): Promise<void> {\n await this.destroy()\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lex-indexer.d.ts","sourceRoot":"","sources":["../src/lex-indexer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,IAAI,EAAE,KAAK,YAAY,EAAW,MAAM,cAAc,CAAA;AAEhF,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAEtE,KAAK,eAAe,GAAG,IAAI,CAAC,WAAW,EAAE,QAAQ,GAAG,QAAQ,GAAG,KAAK,CAAC,CAAA;AAErE,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI,eAAe,GAAG;IAC7C,MAAM,EAAE,QAAQ,CAAA;IAChB,MAAM,EAAE,CAAC,CAAA;IACT,GAAG,EAAE,MAAM,CAAA;CACZ,CAAA;AAED,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI,eAAe,GAAG;IAC7C,MAAM,EAAE,QAAQ,CAAA;IAChB,MAAM,EAAE,CAAC,CAAA;IACT,GAAG,EAAE,MAAM,CAAA;CACZ,CAAA;AAED,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI,WAAW,CAAC,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAAA;AAEzD,MAAM,MAAM,WAAW,GAAG,eAAe,GAAG;IAC1C,MAAM,EAAE,QAAQ,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI,CAC7B,GAAG,EAAE,WAAW,CAAC,CAAC,CAAC,EACnB,IAAI,EAAE,WAAW,KACd,OAAO,CAAC,IAAI,CAAC,CAAA;AAElB,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI,CAC7B,GAAG,EAAE,WAAW,CAAC,CAAC,CAAC,EACnB,IAAI,EAAE,WAAW,KACd,OAAO,CAAC,IAAI,CAAC,CAAA;AAElB,MAAM,MAAM,UAAU,CAAC,CAAC,IAAI,CAC1B,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,EAChB,IAAI,EAAE,WAAW,KACd,OAAO,CAAC,IAAI,CAAC,CAAA;AAElB,MAAM,MAAM,aAAa,GAAG,CAC1B,GAAG,EAAE,WAAW,EAChB,IAAI,EAAE,WAAW,KACd,OAAO,CAAC,IAAI,CAAC,CAAA;AAElB,MAAM,MAAM,cAAc,GAAG,CAC3B,GAAG,EAAE,WAAW,EAChB,IAAI,EAAE,WAAW,KACd,OAAO,CAAC,IAAI,CAAC,CAAA;AAElB,MAAM,MAAM,eAAe,GAAG,CAC5B,GAAG,EAAE,aAAa,EAClB,IAAI,EAAE,WAAW,KACd,OAAO,CAAC,IAAI,CAAC,CAAA;AAElB,MAAM,MAAM,YAAY,GAAG,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAA;AAE/C,MAAM,MAAM,aAAa,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"lex-indexer.d.ts","sourceRoot":"","sources":["../src/lex-indexer.ts"],"names":[],"mappings":"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"}
|
package/dist/lex-indexer.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lex-indexer.js","sourceRoot":"","sources":["../src/lex-indexer.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4C,OAAO,EAAE,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"lex-indexer.js","sourceRoot":"","sources":["../src/lex-indexer.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Infer, type Main, type RecordSchema, getMain } from '@atproto/lex'\nimport type { AtUriString, NsidString } from '@atproto/syntax'\nimport type { HandlerOpts, TapHandler } from './channel.js'\nimport type { IdentityEvent, RecordEvent, TapEvent } from './types.js'\n\ntype BaseRecordEvent = Omit<RecordEvent, 'record' | 'action' | 'cid'>\n\nexport type CreateEvent<R> = BaseRecordEvent & {\n action: 'create'\n record: R\n cid: string\n}\n\nexport type UpdateEvent<R> = BaseRecordEvent & {\n action: 'update'\n record: R\n cid: string\n}\n\nexport type PutEvent<R> = CreateEvent<R> | UpdateEvent<R>\n\nexport type DeleteEvent = BaseRecordEvent & {\n action: 'delete'\n}\n\nexport type CreateHandler<R> = (\n evt: CreateEvent<R>,\n opts: HandlerOpts,\n) => Promise<void>\n\nexport type UpdateHandler<R> = (\n evt: UpdateEvent<R>,\n opts: HandlerOpts,\n) => Promise<void>\n\nexport type PutHandler<R> = (\n evt: PutEvent<R>,\n opts: HandlerOpts,\n) => Promise<void>\n\nexport type DeleteHandler = (\n evt: DeleteEvent,\n opts: HandlerOpts,\n) => Promise<void>\n\nexport type UntypedHandler = (\n evt: RecordEvent,\n opts: HandlerOpts,\n) => Promise<void>\n\nexport type IdentityHandler = (\n evt: IdentityEvent,\n opts: HandlerOpts,\n) => Promise<void>\n\nexport type ErrorHandler = (err: Error) => void\n\nexport type RecordHandler<R> =\n CreateHandler<R> | UpdateHandler<R> | PutHandler<R> | DeleteHandler\n\ninterface RegisteredHandler {\n handler: RecordHandler<any>\n schema: RecordSchema\n}\n\nexport class LexIndexer implements TapHandler {\n private handlers = new Map<string, RegisteredHandler>()\n private otherHandler: UntypedHandler | undefined\n private identityHandler: IdentityHandler | undefined\n private errorHandler: ErrorHandler | undefined\n\n private handlerKey(\n collection: NsidString,\n action: RecordEvent['action'],\n ): string {\n return `${collection}:${action}`\n }\n\n private register<const T extends RecordSchema>(\n action: RecordEvent['action'],\n ns: Main<T>,\n handler: RecordHandler<Infer<T>>,\n ): this {\n const schema = getMain(ns)\n const key = this.handlerKey(schema.$type, action)\n if (this.handlers.has(key)) {\n throw new Error(`Handler already registered for ${key}`)\n }\n this.handlers.set(key, { schema, handler })\n return this\n }\n\n create<const T extends RecordSchema>(\n ns: Main<T>,\n handler: CreateHandler<Infer<T>>,\n ): this {\n return this.register('create', ns, handler)\n }\n\n update<const T extends RecordSchema>(\n ns: Main<T>,\n handler: UpdateHandler<Infer<T>>,\n ): this {\n return this.register('update', ns, handler)\n }\n\n delete<const T extends RecordSchema>(\n ns: Main<T>,\n handler: DeleteHandler,\n ): this {\n return this.register('delete', ns, handler)\n }\n\n put<const T extends RecordSchema>(\n ns: Main<T>,\n handler: PutHandler<Infer<T>>,\n ): this {\n this.register('create', ns, handler)\n this.register('update', ns, handler)\n return this\n }\n\n other(fn: UntypedHandler): this {\n if (this.otherHandler) {\n throw new Error(`Handler already registered for \"other\"`)\n }\n this.otherHandler = fn\n return this\n }\n\n identity(fn: IdentityHandler): this {\n if (this.identityHandler) {\n throw new Error(`Handler already registered for \"identity\"`)\n }\n this.identityHandler = fn\n return this\n }\n\n error(fn: ErrorHandler): this {\n if (this.errorHandler) {\n throw new Error(`Handler already registered for \"error\"`)\n }\n this.errorHandler = fn\n return this\n }\n\n async onEvent(evt: TapEvent, opts: HandlerOpts): Promise<void> {\n if (evt.type === 'identity') {\n await this.identityHandler?.(evt, opts)\n } else {\n await this.handleRecordEvent(evt, opts)\n }\n await opts.ack()\n }\n\n private async handleRecordEvent(\n evt: RecordEvent,\n opts: HandlerOpts,\n ): Promise<void> {\n const { collection, action } = evt\n const key = this.handlerKey(collection, action)\n const registered = this.handlers.get(key)\n\n if (!registered) {\n await this.otherHandler?.(evt, opts)\n return\n }\n\n if (action === 'create' || action === 'update') {\n const match = registered.schema.safeValidate(evt.record)\n if (!match.success) {\n const uriStr: AtUriString = `at://${evt.did}/${evt.collection}/${evt.rkey}`\n throw new Error(`Record validation failed for ${uriStr}`, {\n cause: match.reason,\n })\n }\n }\n\n await (registered.handler as UntypedHandler)(evt, opts)\n }\n\n onError(err: Error): void {\n if (this.errorHandler) {\n this.errorHandler(err)\n } else {\n throw err\n }\n }\n}\n"]}
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAE,KAAK,QAAQ,EAAE,CAAC,EAAE,MAAM,cAAc,CAAA;AAC5D,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAE1E,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;EAShC,CAAA;AAEF,eAAO,MAAM,uBAAuB;;;;;;;;;EAWlC,CAAA;AAEF,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;EAI5B,CAAA;AAEF,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;EAI9B,CAAA;AAEF,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAGzB,CAAA;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,QAAQ,CAAA;IACd,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAA;IACtC,GAAG,EAAE,SAAS,CAAA;IACd,GAAG,EAAE,MAAM,CAAA;IACX,UAAU,EAAE,UAAU,CAAA;IACtB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,OAAO,CAAA;CACd,CAAA;AAED,MAAM,MAAM,aAAa,GAAG;IAC1B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,UAAU,CAAA;IAChB,GAAG,EAAE,SAAS,CAAA;IACd,MAAM,EAAE,YAAY,CAAA;IACpB,QAAQ,EAAE,OAAO,CAAA;IACjB,MAAM,EAAE,UAAU,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,UAAU,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAE,KAAK,QAAQ,EAAE,CAAC,EAAE,MAAM,cAAc,CAAA;AAC5D,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAE1E,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;EAShC,CAAA;AAEF,eAAO,MAAM,uBAAuB;;;;;;;;;EAWlC,CAAA;AAEF,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;EAI5B,CAAA;AAEF,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;EAI9B,CAAA;AAEF,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAGzB,CAAA;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,QAAQ,CAAA;IACd,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAA;IACtC,GAAG,EAAE,SAAS,CAAA;IACd,GAAG,EAAE,MAAM,CAAA;IACX,UAAU,EAAE,UAAU,CAAA;IACtB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,OAAO,CAAA;CACd,CAAA;AAED,MAAM,MAAM,aAAa,GAAG;IAC1B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,UAAU,CAAA;IAChB,GAAG,EAAE,SAAS,CAAA;IACd,MAAM,EAAE,YAAY,CAAA;IACpB,QAAQ,EAAE,OAAO,CAAA;IACjB,MAAM,EAAE,UAAU,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,UAAU,GACpB,QAAQ,GAAG,WAAW,GAAG,WAAW,GAAG,aAAa,GAAG,SAAS,CAAA;AAElE,MAAM,MAAM,QAAQ,GAAG,aAAa,GAAG,WAAW,CAAA;AAElD,eAAO,MAAM,aAAa,SAAU,QAAQ,KAAG,QAyB9C,CAAA;AAED,eAAO,MAAM,cAAc;;;;;;;;EAQzB,CAAA;AAEF,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAA"}
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAA8B,CAAC,EAAE,MAAM,cAAc,CAAA;AAG5D,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC5C,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAChC,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE;IACf,UAAU,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IACxC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC;IACxC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC9C,MAAM,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IAC9B,GAAG,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IAC5C,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE;CAClB,CAAC,CAAA;AAEF,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9C,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAChC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;IACtC,SAAS,EAAE,CAAC,CAAC,OAAO,EAAE;IACtB,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC;QACb,QAAQ;QACR,WAAW;QACX,WAAW;QACX,aAAa;QACb,SAAS;KACV,CAAC;CACH,CAAC,CAAA;AAEF,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC;IACxC,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE;IACf,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC;IACzB,MAAM,EAAE,qBAAqB;CAC9B,CAAC,CAAA;AAEF,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC1C,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE;IACf,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC;IAC3B,QAAQ,EAAE,uBAAuB;CAClC,CAAC,CAAA;AAEF,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC,kBAAkB,CAAC,MAAM,EAAE;IACzD,iBAAiB;IACjB,mBAAmB;CACpB,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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","sourcesContent":["import { type LexMap, type LexValue, l } from '@atproto/lex'\nimport type { DidString, HandleString, NsidString } from '@atproto/syntax'\n\nexport const recordEventDataSchema = l.object({\n did: l.string({ format: 'did' }),\n rev: l.string(),\n collection: l.string({ format: 'nsid' }),\n rkey: l.string({ format: 'record-key' }),\n action: l.enum(['create', 'update', 'delete']),\n record: l.optional(l.lexMap()),\n cid: l.optional(l.string({ format: 'cid' })),\n live: l.boolean(),\n})\n\nexport const identityEventDataSchema = l.object({\n did: l.string({ format: 'did' }),\n handle: l.string({ format: 'handle' }),\n is_active: l.boolean(),\n status: l.enum([\n 'active',\n 'takendown',\n 'suspended',\n 'deactivated',\n 'deleted',\n ]),\n})\n\nexport const recordEventSchema = l.object({\n id: l.integer(),\n type: l.literal('record'),\n record: recordEventDataSchema,\n})\n\nexport const identityEventSchema = l.object({\n id: l.integer(),\n type: l.literal('identity'),\n identity: identityEventDataSchema,\n})\n\nexport const tapEventSchema = l.discriminatedUnion('type', [\n recordEventSchema,\n identityEventSchema,\n])\n\nexport type RecordEvent = {\n id: number\n type: 'record'\n action: 'create' | 'update' | 'delete'\n did: DidString\n rev: string\n collection: NsidString\n rkey: string\n record?: LexMap\n cid?: string\n live: boolean\n}\n\nexport type IdentityEvent = {\n id: number\n type: 'identity'\n did: DidString\n handle: HandleString\n isActive: boolean\n status: RepoStatus\n}\n\nexport type RepoStatus =\n 'active' | 'takendown' | 'suspended' | 'deactivated' | 'deleted'\n\nexport type TapEvent = IdentityEvent | RecordEvent\n\nexport const parseTapEvent = (data: LexValue): TapEvent => {\n const parsed = tapEventSchema.parse(data)\n if (parsed.type === 'identity') {\n return {\n id: parsed.id,\n type: parsed.type,\n did: parsed.identity.did,\n handle: parsed.identity.handle,\n isActive: parsed.identity.is_active,\n status: parsed.identity.status,\n }\n } else {\n return {\n id: parsed.id,\n type: parsed.type,\n action: parsed.record.action,\n did: parsed.record.did,\n rev: parsed.record.rev,\n collection: parsed.record.collection,\n rkey: parsed.record.rkey,\n record: parsed.record.record,\n cid: parsed.record.cid,\n live: parsed.record.live,\n }\n }\n}\n\nexport const repoInfoSchema = l.object({\n did: l.string(),\n handle: l.string(),\n state: l.string(),\n rev: l.string(),\n records: l.integer(),\n error: l.optional(l.string()),\n retries: l.optional(l.integer()),\n})\n\nexport type RepoInfo = l.Infer<typeof repoInfoSchema>\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atproto/tap",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "atproto tap client",
|
|
6
6
|
"keywords": [
|
|
@@ -33,18 +33,17 @@
|
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"ws": "^8.12.0",
|
|
36
|
-
"@atproto/
|
|
37
|
-
"@atproto/
|
|
38
|
-
"@atproto/
|
|
39
|
-
"@atproto/
|
|
36
|
+
"@atproto/common": "^0.7.4",
|
|
37
|
+
"@atproto/lex": "^0.3.3",
|
|
38
|
+
"@atproto/syntax": "^0.7.3",
|
|
39
|
+
"@atproto/ws-client": "^0.2.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/express": "^4.17.17",
|
|
43
43
|
"@types/ws": "^8.5.4",
|
|
44
|
-
"@vitest/coverage-v8": "4.0.16",
|
|
45
44
|
"express": "^4.18.2",
|
|
46
45
|
"http-terminator": "^3.2.0",
|
|
47
|
-
"vitest": "^4.
|
|
46
|
+
"vitest": "^4.1.10"
|
|
48
47
|
},
|
|
49
48
|
"scripts": {
|
|
50
49
|
"build": "tsgo --build tsconfig.build.json",
|