@atproto/tap 0.1.2 → 0.2.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 +29 -0
- package/LICENSE.txt +1 -1
- package/dist/channel.d.ts +2 -1
- package/dist/channel.d.ts.map +1 -1
- package/dist/channel.js +23 -14
- package/dist/channel.js.map +1 -1
- package/dist/lex-indexer.d.ts.map +1 -1
- package/dist/lex-indexer.js +8 -6
- package/dist/lex-indexer.js.map +1 -1
- package/dist/types.d.ts +107 -258
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +33 -30
- package/dist/types.js.map +1 -1
- package/dist/util.d.ts +1 -0
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +9 -0
- package/dist/util.js.map +1 -1
- package/package.json +8 -9
- package/src/channel.ts +28 -19
- package/src/lex-indexer.ts +14 -8
- package/src/types.ts +41 -37
- package/src/util.ts +9 -0
- package/tests/_util.ts +24 -1
- package/tests/channel.test.ts +40 -48
- package/tests/client.test.ts +6 -5
- package/tests/lex-indexer.test.ts +4 -1
- package/tests/simple-indexer.test.ts +1 -0
- package/tests/util.test.ts +1 -0
- package/tsconfig.json +4 -1
- package/tsconfig.tests.json +9 -0
- package/vitest.config.ts +5 -0
- package/jest.config.js +0 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# @atproto/tap
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#4532](https://github.com/bluesky-social/atproto/pull/4532) [`aaedafc`](https://github.com/bluesky-social/atproto/commit/aaedafc6baef106b85e0954d8474cec21c00c1c2) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Expose `record` data as parsed atproto data (including CIDs and Uint8Arrays)
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- [#4532](https://github.com/bluesky-social/atproto/pull/4532) [`aaedafc`](https://github.com/bluesky-social/atproto/commit/aaedafc6baef106b85e0954d8474cec21c00c1c2) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Replace event validation from "zod" to "@atproto/lex"
|
|
12
|
+
|
|
13
|
+
- [#4562](https://github.com/bluesky-social/atproto/pull/4562) [`7310b97`](https://github.com/bluesky-social/atproto/commit/7310b9704de678a3b389a741784d58bb7f79b10b) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Add validation error as cause when handling invalid records
|
|
14
|
+
|
|
15
|
+
- Updated dependencies [[`99963d0`](https://github.com/bluesky-social/atproto/commit/99963d002a9e030e79aed5fba700e0a68f31e101), [`99963d0`](https://github.com/bluesky-social/atproto/commit/99963d002a9e030e79aed5fba700e0a68f31e101), [`7310b97`](https://github.com/bluesky-social/atproto/commit/7310b9704de678a3b389a741784d58bb7f79b10b), [`99963d0`](https://github.com/bluesky-social/atproto/commit/99963d002a9e030e79aed5fba700e0a68f31e101), [`7310b97`](https://github.com/bluesky-social/atproto/commit/7310b9704de678a3b389a741784d58bb7f79b10b), [`99963d0`](https://github.com/bluesky-social/atproto/commit/99963d002a9e030e79aed5fba700e0a68f31e101), [`7310b97`](https://github.com/bluesky-social/atproto/commit/7310b9704de678a3b389a741784d58bb7f79b10b), [`7310b97`](https://github.com/bluesky-social/atproto/commit/7310b9704de678a3b389a741784d58bb7f79b10b)]:
|
|
16
|
+
- @atproto/syntax@0.4.3
|
|
17
|
+
- @atproto/lex@0.0.12
|
|
18
|
+
- @atproto/common@0.5.9
|
|
19
|
+
|
|
20
|
+
## 0.1.3
|
|
21
|
+
|
|
22
|
+
### Patch Changes
|
|
23
|
+
|
|
24
|
+
- [#4534](https://github.com/bluesky-social/atproto/pull/4534) [`0193467`](https://github.com/bluesky-social/atproto/commit/0193467463a1a49c0c7f17d129c07b687a1f5057) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Make sure the `destroy()` promise resolves, even in case of error
|
|
25
|
+
|
|
26
|
+
- [#4534](https://github.com/bluesky-social/atproto/pull/4534) [`0193467`](https://github.com/bluesky-social/atproto/commit/0193467463a1a49c0c7f17d129c07b687a1f5057) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Make `TapChannel` implement `AsyncDisposable`, allowing use with `using` keyword
|
|
27
|
+
|
|
28
|
+
- Updated dependencies []:
|
|
29
|
+
- @atproto/common@0.5.8
|
|
30
|
+
- @atproto/lex@0.0.11
|
|
31
|
+
|
|
3
32
|
## 0.1.2
|
|
4
33
|
|
|
5
34
|
### Patch Changes
|
package/LICENSE.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Dual MIT/Apache-2.0 License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2022-
|
|
3
|
+
Copyright (c) 2022-2026 Bluesky Social PBC, and Contributors
|
|
4
4
|
|
|
5
5
|
Except as otherwise noted in individual files, this software is licensed under the MIT license (<http://opensource.org/licenses/MIT>), or the Apache License, Version 2.0 (<http://www.apache.org/licenses/LICENSE-2.0>).
|
|
6
6
|
|
package/dist/channel.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export type TapWebsocketOptions = ClientOptions & {
|
|
|
14
14
|
heartbeatIntervalMs?: number;
|
|
15
15
|
onReconnectError?: (error: unknown, n: number, initialSetup: boolean) => void;
|
|
16
16
|
};
|
|
17
|
-
export declare class TapChannel {
|
|
17
|
+
export declare class TapChannel implements AsyncDisposable {
|
|
18
18
|
private ws;
|
|
19
19
|
private handler;
|
|
20
20
|
private readonly abortController;
|
|
@@ -28,5 +28,6 @@ export declare class TapChannel {
|
|
|
28
28
|
start(): Promise<void>;
|
|
29
29
|
private processWsEvent;
|
|
30
30
|
destroy(): Promise<void>;
|
|
31
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
31
32
|
}
|
|
32
33
|
//# sourceMappingURL=channel.d.ts.map
|
package/dist/channel.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,IAAI,CAAA;
|
|
1
|
+
{"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,IAAI,CAAA;AAIlC,OAAO,EAAE,QAAQ,EAAiB,MAAM,SAAS,CAAA;AAGjD,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;gBAGtC,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,UAAU,EACnB,MAAM,GAAE,mBAAwB;IAoB5B,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAY3B,OAAO;YAKP,gBAAgB;YAShB,iBAAiB;IAqBzB,KAAK;YAeG,cAAc;IA6BtB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAKxB,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7C"}
|
package/dist/channel.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.TapChannel = void 0;
|
|
4
4
|
const common_1 = require("@atproto/common");
|
|
5
|
+
const lex_1 = require("@atproto/lex");
|
|
5
6
|
const ws_client_1 = require("@atproto/ws-client");
|
|
6
7
|
const types_1 = require("./types");
|
|
7
8
|
const util_1 = require("./util");
|
|
@@ -90,37 +91,41 @@ class TapChannel {
|
|
|
90
91
|
ack.defer.resolve();
|
|
91
92
|
this.bufferedAcks = this.bufferedAcks.slice(1);
|
|
92
93
|
}
|
|
93
|
-
catch (
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}));
|
|
94
|
+
catch (cause) {
|
|
95
|
+
const error = new Error(`failed to send ack for event ${this.bufferedAcks[0]}`, { cause });
|
|
96
|
+
this.handler.onError(error);
|
|
97
97
|
return;
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
async start() {
|
|
102
|
+
this.abortController.signal.throwIfAborted();
|
|
102
103
|
try {
|
|
103
104
|
for await (const chunk of this.ws) {
|
|
104
105
|
await this.processWsEvent(chunk);
|
|
105
106
|
}
|
|
106
107
|
}
|
|
107
108
|
catch (err) {
|
|
108
|
-
if ((0,
|
|
109
|
-
this.destroyDefer.resolve();
|
|
110
|
-
}
|
|
111
|
-
else {
|
|
109
|
+
if (!(0, util_1.isCausedBySignal)(err, this.abortController.signal)) {
|
|
112
110
|
throw err;
|
|
113
111
|
}
|
|
114
112
|
}
|
|
113
|
+
finally {
|
|
114
|
+
this.destroyDefer.resolve();
|
|
115
|
+
}
|
|
115
116
|
}
|
|
116
117
|
async processWsEvent(chunk) {
|
|
117
118
|
let evt;
|
|
118
119
|
try {
|
|
119
|
-
const data = chunk.toString()
|
|
120
|
-
|
|
120
|
+
const data = (0, lex_1.lexParse)(chunk.toString(), {
|
|
121
|
+
// Reject invalid CIDs and blobs
|
|
122
|
+
strict: true,
|
|
123
|
+
});
|
|
124
|
+
evt = (0, types_1.parseTapEvent)(data);
|
|
121
125
|
}
|
|
122
|
-
catch (
|
|
123
|
-
|
|
126
|
+
catch (cause) {
|
|
127
|
+
const error = new Error(`Failed to parse message`, { cause });
|
|
128
|
+
this.handler.onError(error);
|
|
124
129
|
return;
|
|
125
130
|
}
|
|
126
131
|
try {
|
|
@@ -131,9 +136,10 @@ class TapChannel {
|
|
|
131
136
|
},
|
|
132
137
|
});
|
|
133
138
|
}
|
|
134
|
-
catch (
|
|
139
|
+
catch (cause) {
|
|
135
140
|
// Don't ack on error - let Tap retry
|
|
136
|
-
|
|
141
|
+
const error = new Error(`Failed to process event ${evt.id}`, { cause });
|
|
142
|
+
this.handler.onError(error);
|
|
137
143
|
return;
|
|
138
144
|
}
|
|
139
145
|
}
|
|
@@ -141,6 +147,9 @@ class TapChannel {
|
|
|
141
147
|
this.abortController.abort();
|
|
142
148
|
await this.destroyDefer.complete;
|
|
143
149
|
}
|
|
150
|
+
async [Symbol.asyncDispose]() {
|
|
151
|
+
await this.destroy();
|
|
152
|
+
}
|
|
144
153
|
}
|
|
145
154
|
exports.TapChannel = TapChannel;
|
|
146
155
|
//# sourceMappingURL=channel.js.map
|
package/dist/channel.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"channel.js","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":";;;AACA,
|
|
1
|
+
{"version":3,"file":"channel.js","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":";;;AACA,4CAA8D;AAC9D,sCAAuC;AACvC,kDAAuD;AACvD,mCAAiD;AACjD,iCAAgE;AAwBhE,MAAa,UAAU;IASrB,YACE,GAAW,EACX,OAAmB,EACnB,SAA8B,EAAE;QAX1B;;;;;WAAsB;QACtB;;;;;WAAmB;QAEV;;;;mBAAmC,IAAI,eAAe,EAAE;WAAA;QACxD;;;;mBAA2B,IAAA,yBAAgB,GAAE;WAAA;QAEtD;;;;mBAA8B,EAAE;WAAA;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,KAAP,OAAO,GAAK,EAAE,EAAA;YACd,OAAO,CAAC,eAAe,CAAC,GAAG,IAAA,4BAAqB,EAAC,aAAa,CAAC,CAAA;QACjE,CAAC;QACD,IAAI,CAAC,EAAE,GAAG,IAAI,8BAAkB,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,IAAA,yBAAgB,GAAE,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,IAAA,uBAAgB,EAAC,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,IAAA,cAAQ,EAAC,KAAK,CAAC,QAAQ,EAAE,EAAE;gBACtC,gCAAgC;gBAChC,MAAM,EAAE,IAAI;aACb,CAAC,CAAA;YACF,GAAG,GAAG,IAAA,qBAAa,EAAC,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;AAnID,gCAmIC","sourcesContent":["import { ClientOptions } from 'ws'\nimport { Deferrable, createDeferrable } from '@atproto/common'\nimport { lexParse } from '@atproto/lex'\nimport { WebSocketKeepAlive } from '@atproto/ws-client'\nimport { TapEvent, parseTapEvent } from './types'\nimport { formatAdminAuthHeader, isCausedBySignal } from './util'\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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lex-indexer.d.ts","sourceRoot":"","sources":["../src/lex-indexer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,YAAY,EAAW,MAAM,cAAc,CAAA;AAEjE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AACnD,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAE9D,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,IACvB,aAAa,CAAC,CAAC,CAAC,GAChB,aAAa,CAAC,CAAC,CAAC,GAChB,UAAU,CAAC,CAAC,CAAC,GACb,aAAa,CAAA;AAQjB,qBAAa,UAAW,YAAW,UAAU;IAC3C,OAAO,CAAC,QAAQ,CAAuC;IACvD,OAAO,CAAC,YAAY,CAA4B;IAChD,OAAO,CAAC,eAAe,CAA6B;IACpD,OAAO,CAAC,YAAY,CAA0B;IAE9C,OAAO,CAAC,UAAU;
|
|
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
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.LexIndexer = void 0;
|
|
4
4
|
const lex_1 = require("@atproto/lex");
|
|
5
|
-
const syntax_1 = require("@atproto/syntax");
|
|
6
5
|
class LexIndexer {
|
|
7
6
|
constructor() {
|
|
8
7
|
Object.defineProperty(this, "handlers", {
|
|
@@ -53,7 +52,8 @@ class LexIndexer {
|
|
|
53
52
|
}
|
|
54
53
|
put(ns, handler) {
|
|
55
54
|
this.register('create', ns, handler);
|
|
56
|
-
|
|
55
|
+
this.register('update', ns, handler);
|
|
56
|
+
return this;
|
|
57
57
|
}
|
|
58
58
|
other(fn) {
|
|
59
59
|
if (this.otherHandler) {
|
|
@@ -94,10 +94,12 @@ class LexIndexer {
|
|
|
94
94
|
return;
|
|
95
95
|
}
|
|
96
96
|
if (action === 'create' || action === 'update') {
|
|
97
|
-
const match = registered.schema.
|
|
98
|
-
if (!match) {
|
|
99
|
-
const uriStr =
|
|
100
|
-
throw new Error(`Record validation failed for ${uriStr}
|
|
97
|
+
const match = registered.schema.safeValidate(evt.record);
|
|
98
|
+
if (!match.success) {
|
|
99
|
+
const uriStr = `at://${evt.did}/${evt.collection}/${evt.rkey}`;
|
|
100
|
+
throw new Error(`Record validation failed for ${uriStr}`, {
|
|
101
|
+
cause: match.reason,
|
|
102
|
+
});
|
|
101
103
|
}
|
|
102
104
|
}
|
|
103
105
|
await registered.handler(evt, opts);
|
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,sCAAiE;
|
|
1
|
+
{"version":3,"file":"lex-indexer.js","sourceRoot":"","sources":["../src/lex-indexer.ts"],"names":[],"mappings":"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","sourcesContent":["import { Infer, Main, RecordSchema, getMain } from '@atproto/lex'\nimport { AtUriString, NsidString } from '@atproto/syntax'\nimport { HandlerOpts, TapHandler } from './channel'\nimport { IdentityEvent, RecordEvent, TapEvent } from './types'\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"]}
|