@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 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 { ClientOptions } from 'ws';
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 = ClientOptions & {
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
- private bufferedAcks;
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 sendAck;
26
- private bufferAndSendAck;
27
- private flushBufferedAcks;
51
+ private flushPendingAcks;
28
52
  start(): Promise<void>;
29
53
  private processWsEvent;
30
54
  destroy(): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,IAAI,CAAA;AAIvC,OAAO,EAAE,KAAK,QAAQ,EAAiB,MAAM,YAAY,CAAA;AAGzD,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,aAAa,GAAG;IAChD,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,KAAK,IAAI,CAAA;CAC9E,CAAA;AAOD,qBAAa,UAAW,YAAW,eAAe;IAChD,OAAO,CAAC,EAAE,CAAoB;IAC9B,OAAO,CAAC,OAAO,CAAY;IAE3B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAyC;IACzE,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAiC;IAE9D,OAAO,CAAC,YAAY,CAAoB;IAExC,YACE,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,UAAU,EACnB,MAAM,GAAE,mBAAwB,EAkBjC;IAEK,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAUxC;YAEa,OAAO;YAKP,gBAAgB;YAShB,iBAAiB;IAqBzB,KAAK,kBAaV;YAEa,cAAc;IA6BtB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAG7B;IAEK,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAE3C;CACF"}
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 { WebSocketKeepAlive } from '@atproto/ws-client';
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
- this.bufferedAcks = [];
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, ...rest } = wsOpts;
13
- let headers = rest.headers;
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 = new WebSocketKeepAlive({
19
- getUrl: async () => url,
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
- if (this.ws.isConnected()) {
30
- try {
31
- await this.sendAck(id);
32
- }
33
- catch {
34
- await this.bufferAndSendAck(id);
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
- else {
38
- await this.bufferAndSendAck(id);
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
- async sendAck(id) {
42
- await this.ws.send(JSON.stringify({ type: 'ack', id }));
43
- }
44
- // resolves after the ack has been actually sent
45
- async bufferAndSendAck(id) {
46
- const defer = createDeferrable();
47
- this.bufferedAcks.push({
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
- const ack = this.bufferedAcks.at(0);
57
- if (!ack) {
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
- const error = new Error(`failed to send ack for event ${this.bufferedAcks[0]}`, { cause });
66
- this.handler.onError(error);
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
- if (!isCausedBySignal(err, this.abortController.signal)) {
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.toString(), {
141
+ const data = lexParse(chunk, {
91
142
  // Reject invalid CIDs and blobs
92
143
  strict: true,
93
144
  });
@@ -1 +1 @@
1
- {"version":3,"file":"channel.js","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"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","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":"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"}
1
+ {"version":3,"file":"lex-indexer.d.ts","sourceRoot":"","sources":["../src/lex-indexer.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
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>\n | UpdateHandler<R>\n | PutHandler<R>\n | DeleteHandler\n\ninterface RegisteredHandler {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\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"]}
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"]}
@@ -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,GAClB,QAAQ,GACR,WAAW,GACX,WAAW,GACX,aAAa,GACb,SAAS,CAAA;AAEb,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"}
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;AAiCF,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,IAAc,EAAY,EAAE;IACxD,MAAM,MAAM,GAAG,cAAc,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IACzC,IAAI,MAAM,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;QAC/B,OAAO;YACL,EAAE,EAAE,MAAM,CAAC,EAAE;YACb,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,GAAG,EAAE,MAAM,CAAC,QAAQ,CAAC,GAAG;YACxB,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM;YAC9B,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,SAAS;YACnC,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM;SAC/B,CAAA;IACH,CAAC;SAAM,CAAC;QACN,OAAO;YACL,EAAE,EAAE,MAAM,CAAC,EAAE;YACb,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM;YAC5B,GAAG,EAAE,MAAM,CAAC,MAAM,CAAC,GAAG;YACtB,GAAG,EAAE,MAAM,CAAC,MAAM,CAAC,GAAG;YACtB,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,UAAU;YACpC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI;YACxB,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM;YAC5B,GAAG,EAAE,MAAM,CAAC,MAAM,CAAC,GAAG;YACtB,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI;SACzB,CAAA;IACH,CAAC;AACH,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,CAAC;IACrC,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE;IACf,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;IAClB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;IACjB,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE;IACf,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE;IACpB,KAAK,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IAC7B,OAAO,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;CACjC,CAAC,CAAA","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'\n | 'takendown'\n | 'suspended'\n | 'deactivated'\n | '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"]}
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.11",
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/syntax": "^0.7.2",
37
- "@atproto/ws-client": "^0.1.7",
38
- "@atproto/common": "^0.7.2",
39
- "@atproto/lex": "^0.3.1"
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.0.16"
46
+ "vitest": "^4.1.10"
48
47
  },
49
48
  "scripts": {
50
49
  "build": "tsgo --build tsconfig.build.json",