@atproto/tap 0.1.3 → 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/dist/types.js CHANGED
@@ -1,22 +1,22 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.repoInfoSchema = exports.parseTapEvent = exports.tapEventSchema = exports.identityEventSchema = exports.recordEventSchema = exports.identityEventDataSchema = exports.recordEventDataSchema = void 0;
4
- const zod_1 = require("zod");
5
- exports.recordEventDataSchema = zod_1.z.object({
6
- did: zod_1.z.string(),
7
- rev: zod_1.z.string(),
8
- collection: zod_1.z.string(),
9
- rkey: zod_1.z.string(),
10
- action: zod_1.z.enum(['create', 'update', 'delete']),
11
- record: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).optional(),
12
- cid: zod_1.z.string().optional(),
13
- live: zod_1.z.boolean(),
4
+ const lex_1 = require("@atproto/lex");
5
+ exports.recordEventDataSchema = lex_1.l.object({
6
+ did: lex_1.l.string({ format: 'did' }),
7
+ rev: lex_1.l.string(),
8
+ collection: lex_1.l.string({ format: 'nsid' }),
9
+ rkey: lex_1.l.string({ format: 'record-key' }),
10
+ action: lex_1.l.enum(['create', 'update', 'delete']),
11
+ record: lex_1.l.optional(lex_1.l.unknownObject()),
12
+ cid: lex_1.l.optional(lex_1.l.string({ format: 'cid' })),
13
+ live: lex_1.l.boolean(),
14
14
  });
15
- exports.identityEventDataSchema = zod_1.z.object({
16
- did: zod_1.z.string(),
17
- handle: zod_1.z.string(),
18
- is_active: zod_1.z.boolean(),
19
- status: zod_1.z.enum([
15
+ exports.identityEventDataSchema = lex_1.l.object({
16
+ did: lex_1.l.string({ format: 'did' }),
17
+ handle: lex_1.l.string({ format: 'handle' }),
18
+ is_active: lex_1.l.boolean(),
19
+ status: lex_1.l.enum([
20
20
  'active',
21
21
  'takendown',
22
22
  'suspended',
@@ -24,17 +24,20 @@ exports.identityEventDataSchema = zod_1.z.object({
24
24
  'deleted',
25
25
  ]),
26
26
  });
27
- exports.recordEventSchema = zod_1.z.object({
28
- id: zod_1.z.number(),
29
- type: zod_1.z.literal('record'),
27
+ exports.recordEventSchema = lex_1.l.object({
28
+ id: lex_1.l.integer(),
29
+ type: lex_1.l.literal('record'),
30
30
  record: exports.recordEventDataSchema,
31
31
  });
32
- exports.identityEventSchema = zod_1.z.object({
33
- id: zod_1.z.number(),
34
- type: zod_1.z.literal('identity'),
32
+ exports.identityEventSchema = lex_1.l.object({
33
+ id: lex_1.l.integer(),
34
+ type: lex_1.l.literal('identity'),
35
35
  identity: exports.identityEventDataSchema,
36
36
  });
37
- exports.tapEventSchema = zod_1.z.union([exports.recordEventSchema, exports.identityEventSchema]);
37
+ exports.tapEventSchema = lex_1.l.discriminatedUnion('type', [
38
+ exports.recordEventSchema,
39
+ exports.identityEventSchema,
40
+ ]);
38
41
  const parseTapEvent = (data) => {
39
42
  const parsed = exports.tapEventSchema.parse(data);
40
43
  if (parsed.type === 'identity') {
@@ -63,13 +66,13 @@ const parseTapEvent = (data) => {
63
66
  }
64
67
  };
65
68
  exports.parseTapEvent = parseTapEvent;
66
- exports.repoInfoSchema = zod_1.z.object({
67
- did: zod_1.z.string(),
68
- handle: zod_1.z.string(),
69
- state: zod_1.z.string(),
70
- rev: zod_1.z.string(),
71
- records: zod_1.z.number(),
72
- error: zod_1.z.string().optional(),
73
- retries: zod_1.z.number().optional(),
69
+ exports.repoInfoSchema = lex_1.l.object({
70
+ did: lex_1.l.string(),
71
+ handle: lex_1.l.string(),
72
+ state: lex_1.l.string(),
73
+ rev: lex_1.l.string(),
74
+ records: lex_1.l.integer(),
75
+ error: lex_1.l.optional(lex_1.l.string()),
76
+ retries: lex_1.l.optional(lex_1.l.integer()),
74
77
  });
75
78
  //# sourceMappingURL=types.js.map
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from 'zod'\n\nexport const recordEventDataSchema = z.object({\n did: z.string(),\n rev: z.string(),\n collection: z.string(),\n rkey: z.string(),\n action: z.enum(['create', 'update', 'delete']),\n record: z.record(z.string(), z.unknown()).optional(),\n cid: z.string().optional(),\n live: z.boolean(),\n})\n\nexport const identityEventDataSchema = z.object({\n did: z.string(),\n handle: z.string(),\n is_active: z.boolean(),\n status: z.enum([\n 'active',\n 'takendown',\n 'suspended',\n 'deactivated',\n 'deleted',\n ]),\n})\n\nexport const recordEventSchema = z.object({\n id: z.number(),\n type: z.literal('record'),\n record: recordEventDataSchema,\n})\n\nexport const identityEventSchema = z.object({\n id: z.number(),\n type: z.literal('identity'),\n identity: identityEventDataSchema,\n})\n\nexport const tapEventSchema = z.union([recordEventSchema, identityEventSchema])\n\nexport type RecordEvent = {\n id: number\n type: 'record'\n action: 'create' | 'update' | 'delete'\n did: string\n rev: string\n collection: string\n rkey: string\n record?: Record<string, unknown>\n cid?: string\n live: boolean\n}\n\nexport type IdentityEvent = {\n id: number\n type: 'identity'\n did: string\n handle: string\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: unknown): 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 = z.object({\n did: z.string(),\n handle: z.string(),\n state: z.string(),\n rev: z.string(),\n records: z.number(),\n error: z.string().optional(),\n retries: z.number().optional(),\n})\n\nexport type RepoInfo = z.infer<typeof repoInfoSchema>\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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","sourcesContent":["import { LexMap, LexValue, l } from '@atproto/lex'\nimport { 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.unknownObject()),\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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atproto/tap",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "license": "MIT",
5
5
  "description": "atproto tap client",
6
6
  "keywords": [
@@ -23,22 +23,21 @@
23
23
  "types": "dist/index.d.ts",
24
24
  "dependencies": {
25
25
  "ws": "^8.12.0",
26
- "zod": "^3.23.8",
27
- "@atproto/common": "^0.5.8",
28
- "@atproto/lex": "^0.0.11",
29
- "@atproto/syntax": "^0.4.2",
26
+ "@atproto/common": "^0.5.9",
27
+ "@atproto/lex": "^0.0.12",
28
+ "@atproto/syntax": "^0.4.3",
30
29
  "@atproto/ws-client": "^0.0.4"
31
30
  },
32
31
  "devDependencies": {
33
32
  "@types/express": "^4.17.17",
34
33
  "@types/ws": "^8.5.4",
35
34
  "express": "^4.18.2",
36
- "get-port": "^6.1.2",
37
- "jest": "^28.1.2",
38
- "typescript": "^5.6.3"
35
+ "typescript": "^5.6.3",
36
+ "@vitest/coverage-v8": "4.0.16",
37
+ "vitest": "^4.0.16"
39
38
  },
40
39
  "scripts": {
41
40
  "build": "tsc --build tsconfig.build.json",
42
- "test": "jest"
41
+ "test": "vitest run"
43
42
  }
44
43
  }
package/src/channel.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { ClientOptions } from 'ws'
2
2
  import { Deferrable, createDeferrable } from '@atproto/common'
3
+ import { lexParse } from '@atproto/lex'
3
4
  import { WebSocketKeepAlive } from '@atproto/ws-client'
4
5
  import { TapEvent, parseTapEvent } from './types'
5
6
  import { formatAdminAuthHeader, isCausedBySignal } from './util'
@@ -94,12 +95,12 @@ export class TapChannel implements AsyncDisposable {
94
95
  await this.sendAck(ack.id)
95
96
  ack.defer.resolve()
96
97
  this.bufferedAcks = this.bufferedAcks.slice(1)
97
- } catch (err) {
98
- this.handler.onError(
99
- new Error(`failed to send ack for event ${this.bufferedAcks[0]}`, {
100
- cause: err,
101
- }),
98
+ } catch (cause) {
99
+ const error = new Error(
100
+ `failed to send ack for event ${this.bufferedAcks[0]}`,
101
+ { cause },
102
102
  )
103
+ this.handler.onError(error)
103
104
  return
104
105
  }
105
106
  }
@@ -123,10 +124,14 @@ export class TapChannel implements AsyncDisposable {
123
124
  private async processWsEvent(chunk: Uint8Array) {
124
125
  let evt: TapEvent
125
126
  try {
126
- const data = chunk.toString()
127
- evt = parseTapEvent(JSON.parse(data))
128
- } catch (err) {
129
- this.handler.onError(new Error('Failed to parse message', { cause: err }))
127
+ const data = lexParse(chunk.toString(), {
128
+ // Reject invalid CIDs and blobs
129
+ strict: true,
130
+ })
131
+ evt = parseTapEvent(data)
132
+ } catch (cause) {
133
+ const error = new Error(`Failed to parse message`, { cause })
134
+ this.handler.onError(error)
130
135
  return
131
136
  }
132
137
 
@@ -137,11 +142,10 @@ export class TapChannel implements AsyncDisposable {
137
142
  await this.ackEvent(evt.id)
138
143
  },
139
144
  })
140
- } catch (err) {
145
+ } catch (cause) {
141
146
  // Don't ack on error - let Tap retry
142
- this.handler.onError(
143
- new Error(`Failed to process event ${evt.id}`, { cause: err }),
144
- )
147
+ const error = new Error(`Failed to process event ${evt.id}`, { cause })
148
+ this.handler.onError(error)
145
149
  return
146
150
  }
147
151
  }
@@ -1,5 +1,5 @@
1
1
  import { Infer, Main, RecordSchema, getMain } from '@atproto/lex'
2
- import { AtUri } from '@atproto/syntax'
2
+ import { AtUriString, NsidString } from '@atproto/syntax'
3
3
  import { HandlerOpts, TapHandler } from './channel'
4
4
  import { IdentityEvent, RecordEvent, TapEvent } from './types'
5
5
 
@@ -73,12 +73,15 @@ export class LexIndexer implements TapHandler {
73
73
  private identityHandler: IdentityHandler | undefined
74
74
  private errorHandler: ErrorHandler | undefined
75
75
 
76
- private handlerKey(collection: string, action: string): string {
76
+ private handlerKey(
77
+ collection: NsidString,
78
+ action: RecordEvent['action'],
79
+ ): string {
77
80
  return `${collection}:${action}`
78
81
  }
79
82
 
80
83
  private register<const T extends RecordSchema>(
81
- action: string,
84
+ action: RecordEvent['action'],
82
85
  ns: Main<T>,
83
86
  handler: RecordHandler<Infer<T>>,
84
87
  ): this {
@@ -117,7 +120,8 @@ export class LexIndexer implements TapHandler {
117
120
  handler: PutHandler<Infer<T>>,
118
121
  ): this {
119
122
  this.register('create', ns, handler)
120
- return this.register('update', ns, handler)
123
+ this.register('update', ns, handler)
124
+ return this
121
125
  }
122
126
 
123
127
  other(fn: UntypedHandler): this {
@@ -167,10 +171,12 @@ export class LexIndexer implements TapHandler {
167
171
  }
168
172
 
169
173
  if (action === 'create' || action === 'update') {
170
- const match = registered.schema.matches(evt.record)
171
- if (!match) {
172
- const uriStr = AtUri.make(evt.did, evt.collection, evt.rkey).toString()
173
- throw new Error(`Record validation failed for ${uriStr}`)
174
+ const match = registered.schema.safeValidate(evt.record)
175
+ if (!match.success) {
176
+ const uriStr: AtUriString = `at://${evt.did}/${evt.collection}/${evt.rkey}`
177
+ throw new Error(`Record validation failed for ${uriStr}`, {
178
+ cause: match.reason,
179
+ })
174
180
  }
175
181
  }
176
182
 
package/src/types.ts CHANGED
@@ -1,21 +1,22 @@
1
- import { z } from 'zod'
1
+ import { LexMap, LexValue, l } from '@atproto/lex'
2
+ import { DidString, HandleString, NsidString } from '@atproto/syntax'
2
3
 
3
- export const recordEventDataSchema = z.object({
4
- did: z.string(),
5
- rev: z.string(),
6
- collection: z.string(),
7
- rkey: z.string(),
8
- action: z.enum(['create', 'update', 'delete']),
9
- record: z.record(z.string(), z.unknown()).optional(),
10
- cid: z.string().optional(),
11
- live: z.boolean(),
4
+ export const recordEventDataSchema = l.object({
5
+ did: l.string({ format: 'did' }),
6
+ rev: l.string(),
7
+ collection: l.string({ format: 'nsid' }),
8
+ rkey: l.string({ format: 'record-key' }),
9
+ action: l.enum(['create', 'update', 'delete']),
10
+ record: l.optional(l.unknownObject()),
11
+ cid: l.optional(l.string({ format: 'cid' })),
12
+ live: l.boolean(),
12
13
  })
13
14
 
14
- export const identityEventDataSchema = z.object({
15
- did: z.string(),
16
- handle: z.string(),
17
- is_active: z.boolean(),
18
- status: z.enum([
15
+ export const identityEventDataSchema = l.object({
16
+ did: l.string({ format: 'did' }),
17
+ handle: l.string({ format: 'handle' }),
18
+ is_active: l.boolean(),
19
+ status: l.enum([
19
20
  'active',
20
21
  'takendown',
21
22
  'suspended',
@@ -24,29 +25,32 @@ export const identityEventDataSchema = z.object({
24
25
  ]),
25
26
  })
26
27
 
27
- export const recordEventSchema = z.object({
28
- id: z.number(),
29
- type: z.literal('record'),
28
+ export const recordEventSchema = l.object({
29
+ id: l.integer(),
30
+ type: l.literal('record'),
30
31
  record: recordEventDataSchema,
31
32
  })
32
33
 
33
- export const identityEventSchema = z.object({
34
- id: z.number(),
35
- type: z.literal('identity'),
34
+ export const identityEventSchema = l.object({
35
+ id: l.integer(),
36
+ type: l.literal('identity'),
36
37
  identity: identityEventDataSchema,
37
38
  })
38
39
 
39
- export const tapEventSchema = z.union([recordEventSchema, identityEventSchema])
40
+ export const tapEventSchema = l.discriminatedUnion('type', [
41
+ recordEventSchema,
42
+ identityEventSchema,
43
+ ])
40
44
 
41
45
  export type RecordEvent = {
42
46
  id: number
43
47
  type: 'record'
44
48
  action: 'create' | 'update' | 'delete'
45
- did: string
49
+ did: DidString
46
50
  rev: string
47
- collection: string
51
+ collection: NsidString
48
52
  rkey: string
49
- record?: Record<string, unknown>
53
+ record?: LexMap
50
54
  cid?: string
51
55
  live: boolean
52
56
  }
@@ -54,8 +58,8 @@ export type RecordEvent = {
54
58
  export type IdentityEvent = {
55
59
  id: number
56
60
  type: 'identity'
57
- did: string
58
- handle: string
61
+ did: DidString
62
+ handle: HandleString
59
63
  isActive: boolean
60
64
  status: RepoStatus
61
65
  }
@@ -69,7 +73,7 @@ export type RepoStatus =
69
73
 
70
74
  export type TapEvent = IdentityEvent | RecordEvent
71
75
 
72
- export const parseTapEvent = (data: unknown): TapEvent => {
76
+ export const parseTapEvent = (data: LexValue): TapEvent => {
73
77
  const parsed = tapEventSchema.parse(data)
74
78
  if (parsed.type === 'identity') {
75
79
  return {
@@ -96,14 +100,14 @@ export const parseTapEvent = (data: unknown): TapEvent => {
96
100
  }
97
101
  }
98
102
 
99
- export const repoInfoSchema = z.object({
100
- did: z.string(),
101
- handle: z.string(),
102
- state: z.string(),
103
- rev: z.string(),
104
- records: z.number(),
105
- error: z.string().optional(),
106
- retries: z.number().optional(),
103
+ export const repoInfoSchema = l.object({
104
+ did: l.string(),
105
+ handle: l.string(),
106
+ state: l.string(),
107
+ rev: l.string(),
108
+ records: l.integer(),
109
+ error: l.optional(l.string()),
110
+ retries: l.optional(l.integer()),
107
111
  })
108
112
 
109
- export type RepoInfo = z.infer<typeof repoInfoSchema>
113
+ export type RepoInfo = l.Infer<typeof repoInfoSchema>
package/tests/_util.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { WebSocketServer } from 'ws'
1
2
  import { HandlerOpts } from '../src/channel'
2
3
  import { IdentityEvent, RecordEvent } from '../src/types'
3
4
 
@@ -25,7 +26,7 @@ export const createRecordEvent = (
25
26
  rkey: 'abc123',
26
27
  action: 'create',
27
28
  record: { text: 'hello' },
28
- cid: 'bafyabc',
29
+ cid: 'bafyreiclp443lavogvhj3d2ob2cxbfuscni2k5jk7bebjzg7khl3esabwq',
29
30
  live: true,
30
31
  ...overrides,
31
32
  })
@@ -38,3 +39,25 @@ export const createIdentityEvent = (): IdentityEvent => ({
38
39
  isActive: true,
39
40
  status: 'active',
40
41
  })
42
+
43
+ export async function createWebSocketServer() {
44
+ return new Promise<WebSocketServer & AsyncDisposable>((resolve, reject) => {
45
+ const server = new WebSocketServer({ port: 0 }, () => {
46
+ server.off('error', reject)
47
+ resolve(
48
+ Object.defineProperty(server, Symbol.asyncDispose, {
49
+ value: disposeWebSocketServer,
50
+ }) as WebSocketServer & AsyncDisposable,
51
+ )
52
+ }).once('error', reject)
53
+ })
54
+ }
55
+
56
+ async function disposeWebSocketServer(this: WebSocketServer) {
57
+ return new Promise<void>((resolve, reject) => {
58
+ this.close((err) => {
59
+ if (err) reject(err)
60
+ else resolve()
61
+ })
62
+ })
63
+ }