@atproto/lex 0.1.0 → 0.1.2

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.
Files changed (4) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +185 -17
  3. package/bin/lex +12 -6
  4. package/package.json +5 -5
package/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # @atproto/lex
2
2
 
3
+ ## 0.1.2
4
+
5
+ ### Patch Changes
6
+
7
+ - [#4984](https://github.com/bluesky-social/atproto/pull/4984) [`5bdb4ad`](https://github.com/bluesky-social/atproto/commit/5bdb4addd6a3798bf6c8c391c74044b3e251008a) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Add `default` export of "main" schema in the namespace file
8
+
9
+ - [#4979](https://github.com/bluesky-social/atproto/pull/4979) [`314df62`](https://github.com/bluesky-social/atproto/commit/314df62537bb519231aa375dd3a38360afc79ce0) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Always add `#__PURE__` annotations to function calls
10
+
11
+ - [#4983](https://github.com/bluesky-social/atproto/pull/4983) [`1259646`](https://github.com/bluesky-social/atproto/commit/125964673962a86282a05bf22f410fad8ad06b41) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Make the generation of the `$defs` namespace optional by default
12
+
13
+ - Updated dependencies [[`3aae4fe`](https://github.com/bluesky-social/atproto/commit/3aae4fe43448dca860fc6c4a24d18bfa64de084b), [`5bdb4ad`](https://github.com/bluesky-social/atproto/commit/5bdb4addd6a3798bf6c8c391c74044b3e251008a), [`3aae4fe`](https://github.com/bluesky-social/atproto/commit/3aae4fe43448dca860fc6c4a24d18bfa64de084b), [`314df62`](https://github.com/bluesky-social/atproto/commit/314df62537bb519231aa375dd3a38360afc79ce0), [`1259646`](https://github.com/bluesky-social/atproto/commit/125964673962a86282a05bf22f410fad8ad06b41), [`482767c`](https://github.com/bluesky-social/atproto/commit/482767c4bcce95aa390b2992b028fd8e27d162b2)]:
14
+ - @atproto/lex-builder@0.1.2
15
+ - @atproto/lex-client@0.1.2
16
+
17
+ ## 0.1.1
18
+
19
+ ### Patch Changes
20
+
21
+ - [#4895](https://github.com/bluesky-social/atproto/pull/4895) [`25e0233`](https://github.com/bluesky-social/atproto/commit/25e02339a383740e762c9a9633a701d2fb0cab86) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Add `applyWrites()` method to Lex SDK client class
22
+
23
+ Thank you [@TrySound](https://github.com/TrySound) for the suggestion
24
+
25
+ - Updated dependencies [[`e6c6343`](https://github.com/bluesky-social/atproto/commit/e6c6343bd3727455bd0da12300bb4929a944e4f1), [`e6c6343`](https://github.com/bluesky-social/atproto/commit/e6c6343bd3727455bd0da12300bb4929a944e4f1), [`fb1b403`](https://github.com/bluesky-social/atproto/commit/fb1b40350d46f3c49e512b7e24b071b03902e3b8), [`25e0233`](https://github.com/bluesky-social/atproto/commit/25e02339a383740e762c9a9633a701d2fb0cab86)]:
26
+ - @atproto/lex-client@0.1.1
27
+ - @atproto/lex-data@0.1.1
28
+ - @atproto/lex-builder@0.1.1
29
+
3
30
  ## 0.1.0
4
31
 
5
32
  ### Minor Changes
package/README.md CHANGED
@@ -54,6 +54,14 @@ const posts = await client.list(app.bsky.feed.post, { limit: 10 })
54
54
  - [Type definitions](#type-definitions)
55
55
  - [Building data](#building-data)
56
56
  - [Validation Helpers](#validation-helpers)
57
+ - [`$nsid` - Namespace Identifier](#nsid---namespace-identifier)
58
+ - [`$type` - Type Identifier](#type---type-identifier)
59
+ - [`$check(data)` - Type Guard](#checkdata---type-guard)
60
+ - [`$parse(data)` - Parse and Validate](#parsedata---parse-and-validate)
61
+ - [`$validate(data)` - Validate a value against the schema](#validatedata---validate-a-value-against-the-schema)
62
+ - [`$safeParse(data, options?)` - Parse a value against a schema and get the resulting value](#safeparsedata-options---parse-a-value-against-a-schema-and-get-the-resulting-value)
63
+ - [`$build(data)` - Build with Defaults](#builddata---build-with-defaults)
64
+ - [`$isTypeOf(data)` - Type Discriminator](#istypeofdata---type-discriminator)
57
65
  - [Data Model](#data-model)
58
66
  - [Types](#types)
59
67
  - [JSON Encoding](#json-encoding)
@@ -61,20 +69,56 @@ const posts = await client.list(app.bsky.feed.post, { limit: 10 })
61
69
  - [Making simple XRPC Requests](#making-simple-xrpc-requests)
62
70
  - [Client API](#client-api)
63
71
  - [Creating a Client](#creating-a-client)
72
+ - [Unauthenticated Client](#unauthenticated-client)
73
+ - [Authenticated Client with OAuth](#authenticated-client-with-oauth)
74
+ - [Authenticated Client with Password](#authenticated-client-with-password)
75
+ - [Client with Service Proxy (authenticated only)](#client-with-service-proxy-authenticated-only)
76
+ - [Validation and Strictness Options](#validation-and-strictness-options)
64
77
  - [Core Methods](#core-methods)
78
+ - [`client.call()`](#clientcall)
79
+ - [`client.create()`](#clientcreate)
80
+ - [`client.get()`](#clientget)
81
+ - [`client.put()`](#clientput)
82
+ - [`client.delete()`](#clientdelete)
83
+ - [`client.list()`](#clientlist)
84
+ - [`client.applyWrites()`](#clientapplywrites)
65
85
  - [Error Handling](#error-handling)
86
+ - [Safe Methods](#safe-methods)
87
+ - [XrpcFailure Type](#xrpcfailure-type)
66
88
  - [Authentication Methods](#authentication-methods)
89
+ - [`client.did`](#clientdid)
90
+ - [`client.assertAuthenticated()`](#clientassertauthenticated)
91
+ - [`client.assertDid`](#clientassertdid)
67
92
  - [Labeler Configuration](#labeler-configuration)
68
93
  - [Low-Level XRPC](#low-level-xrpc)
69
94
  - [Utilities](#utilities)
70
95
  - [Datetime Strings](#datetime-strings)
71
96
  - [Advanced Usage](#advanced-usage)
72
97
  - [Workflow Integration](#workflow-integration)
98
+ - [Development Workflow](#development-workflow)
73
99
  - [Tree-Shaking](#tree-shaking)
100
+ - [Namespace notation](#namespace-notation)
101
+ - [Explicit `.main` reference](#explicit-main-reference)
102
+ - [Direct named import from the schema file](#direct-named-import-from-the-schema-file)
103
+ - [Default import (recommended)](#default-import-recommended)
104
+ - [Drawbacks of the default export](#drawbacks-of-the-default-export)
105
+ - [Summary](#summary)
74
106
  - [Blob references](#blob-references)
107
+ - [TypedBlobRef: The Current Standard](#typedblobref-the-current-standard)
108
+ - [LegacyBlobRef: Historical Format](#legacyblobref-historical-format)
109
+ - [Working with Both Formats](#working-with-both-formats)
75
110
  - [Actions](#actions)
111
+ - [What are Actions?](#what-are-actions)
112
+ - [Using Actions](#using-actions)
113
+ - [Composing Multiple Operations](#composing-multiple-operations)
114
+ - [Higher-Order Actions](#higher-order-actions)
76
115
  - [Creating a Client from Another Client](#creating-a-client-from-another-client)
77
116
  - [Building Library-Style APIs with Actions](#building-library-style-apis-with-actions)
117
+ - [Creating Posts](#creating-posts)
118
+ - [Following Users](#following-users)
119
+ - [Updating Profile with Retry Logic](#updating-profile-with-retry-logic)
120
+ - [Packaging Actions as a Library](#packaging-actions-as-a-library)
121
+ - [Best Practices for Actions](#best-practices-for-actions)
78
122
  - [Standard Schema Compatibility](#standard-schema-compatibility)
79
123
  - [License](#license)
80
124
 
@@ -197,6 +241,7 @@ Options:
197
241
  - `--importExt <ext>` - File extension to use for import statements in generated files (default: `.js`). Use `--importExt ""` to generate extension-less imports
198
242
  - `--fileExt <ext>` - File extension to use for generated files (default: `.ts`)
199
243
  - `--indexFile` - Generate an "index" file that re-exports all root-level namespaces (disabled by default)
244
+ - `--no-defaultExport` - Disable generation of a `default` export of the `main` schema in each schema's namespace file (default exports are enabled by default; see [Tree-Shaking](#tree-shaking))
200
245
 
201
246
  ### Generated Schema Structure
202
247
 
@@ -231,7 +276,7 @@ import * as app from './lexicons/app.js'
231
276
  const post = app.bsky.feed.post.$build({
232
277
  // No need to specify $type when using $build
233
278
  text: 'Hello, world!',
234
- createdAt: l.toDatetimeString(new Date()),
279
+ createdAt: l.currentDatetimeString(),
235
280
  })
236
281
 
237
282
  // For runtime validation, use $parse()/$validate() instead
@@ -275,7 +320,7 @@ import * as app from './lexicons/app.js'
275
320
  const data = {
276
321
  $type: 'app.bsky.feed.post',
277
322
  text: 'Hello!',
278
- createdAt: l.toDatetimeString(new Date()),
323
+ createdAt: l.currentDatetimeString(),
279
324
  }
280
325
 
281
326
  if (app.bsky.feed.post.$check(data)) {
@@ -296,7 +341,7 @@ try {
296
341
  const post = app.bsky.feed.post.$main.$parse({
297
342
  $type: 'app.bsky.feed.post',
298
343
  text: 'Hello!',
299
- createdAt: l.toDatetimeString(new Date()),
344
+ createdAt: l.currentDatetimeString(),
300
345
  })
301
346
  // post is now typed and validated
302
347
  console.log(post.text)
@@ -320,7 +365,7 @@ import * as app from './lexicons/app.js'
320
365
  const value = {
321
366
  $type: 'app.bsky.feed.post',
322
367
  text: 'Hello!',
323
- createdAt: l.toDatetimeString(new Date()),
368
+ createdAt: l.currentDatetimeString(),
324
369
  }
325
370
 
326
371
  // Throws if no valid
@@ -340,7 +385,7 @@ import * as app from './lexicons/app.js'
340
385
  const result = app.bsky.feed.post.$safeParse({
341
386
  $type: 'app.bsky.feed.post',
342
387
  text: 'Hello!',
343
- createdAt: l.toDatetimeString(new Date()),
388
+ createdAt: l.currentDatetimeString(),
344
389
  })
345
390
 
346
391
  if (result.success) {
@@ -374,7 +419,7 @@ const like = app.bsky.feed.like.$build({
374
419
  uri: 'at://did:plc:abc/app.bsky.feed.post/123',
375
420
  cid: 'bafyrei...',
376
421
  },
377
- createdAt: l.toDatetimeString(new Date()),
422
+ createdAt: l.currentDatetimeString(),
378
423
  })
379
424
  ```
380
425
 
@@ -655,7 +700,7 @@ import * as app from './lexicons/app.js'
655
700
 
656
701
  const result = await client.create(app.bsky.feed.post, {
657
702
  text: 'Hello, world!',
658
- createdAt: l.toDatetimeString(new Date()),
703
+ createdAt: l.currentDatetimeString(),
659
704
  })
660
705
 
661
706
  console.log(result.uri) // at://did:plc:...
@@ -749,6 +794,49 @@ if (result.cursor) {
749
794
  }
750
795
  ```
751
796
 
797
+ #### `client.applyWrites()`
798
+
799
+ Perform an atomic batch of create, update, and delete operations in a single request.
800
+
801
+ ```typescript
802
+ import { l } from '@atproto/lex'
803
+ import * as app from './lexicons/app.js'
804
+
805
+ const response = await client.applyWrites((op) => [
806
+ // Create a new post
807
+ op.create(app.bsky.feed.post, {
808
+ text: 'Hello, world!',
809
+ createdAt: l.currentDatetimeString(),
810
+ }),
811
+
812
+ // Update profile
813
+ op.update(app.bsky.actor.profile, {
814
+ displayName: 'Alice',
815
+ description: 'Updated bio',
816
+ }),
817
+
818
+ // Delete an existing post by rkey
819
+ op.delete(app.bsky.feed.post, {
820
+ rkey: '3jxf7z2k3q2',
821
+ }),
822
+ ])
823
+
824
+ // Check results
825
+ for (const result of response.body.results) {
826
+ console.log(result.uri, result.cid)
827
+ }
828
+ ```
829
+
830
+ Options:
831
+
832
+ - `repo` - Repository identifier (defaults to authenticated user's DID)
833
+ - `validate` - Asks the PDS to validate records against schema
834
+ - `swapCommit` - CID for optimistic concurrency control
835
+
836
+ > [!NOTE]
837
+ >
838
+ > All operations in an `applyWrites()` call are atomic - they either all succeed or all fail together. This is useful for maintaining consistency when making multiple related changes.
839
+
752
840
  ### Error Handling
753
841
 
754
842
  By default, all client methods throw errors when requests fail. For more ergonomic error handling, the client provides "Safe" variants that return errors instead of throwing them.
@@ -1021,24 +1109,104 @@ This ensures that:
1021
1109
 
1022
1110
  ### Tree-Shaking
1023
1111
 
1024
- The generated TypeScript is optimized for tree-shaking. Import only what you need:
1112
+ The generated TypeScript code is structured to be tree-shakeable, but the way you reference schemas has a meaningful impact on the final bundle size. There are several ways to refer to a generated schema, and each comes with different trade-offs.
1113
+
1114
+ #### Namespace notation
1115
+
1116
+ The most ergonomic style is to use a namespace import and reference schemas through dotted paths:
1025
1117
 
1026
1118
  ```typescript
1027
- // Import specific methods
1028
- import { post } from './lexicons/app/bsky/feed/post.js'
1029
- import { getProfile } from './lexicons/app/bsky/actor/getProfile.js'
1119
+ import * as com from './lexicons/com.js'
1030
1120
 
1031
- // Or use namespace imports (still tree-shakeable)
1032
- import * as app from './lexicons/app.js'
1121
+ await client.call(com.atproto.repo.getRecord, {
1122
+ /* ... */
1123
+ })
1124
+ ```
1125
+
1126
+ This style is convenient and reads naturally as it mirrors the NSID of the schema. However, it produces the largest bundles. From the bundler's point of view, `com.atproto.repo.getRecord` is the whole schema namespace (which contains the `main` schema as well as helpers, and any other definitions). The bundler cannot know that `client.call()` only consumes the `main` schema, so it has to keep the rest of the namespace alive in the bundle.
1127
+
1128
+ #### Explicit `.main` reference
1129
+
1130
+ You can mitigate the bundle-size cost by explicitly naming the `main` definition:
1131
+
1132
+ ```typescript
1133
+ import * as com from './lexicons/com.js'
1134
+
1135
+ await client.call(com.atproto.repo.getRecord.main, {
1136
+ /* ... */
1137
+ })
1033
1138
  ```
1034
1139
 
1035
- For library authors, use `--pure-annotations` when building:
1140
+ This lets the bundler drop the sibling definitions inside `getRecord` that aren't referenced. The drawback is that it leaks an implementation detail: the `main` segment of the path. In Lexicon, `main` is typically implicit:
1141
+
1142
+ - Records use a `$type` of `app.bsky.feed.post` (no `#main`)
1143
+ - XRPC endpoints are exposed as `/xrpc/com.atproto.repo.getRecord` (no `main`)
1144
+
1145
+ So writing `.main` in application code feels verbose compared to how Lexicons are normally referred to.
1146
+
1147
+ #### Direct named import from the schema file
1148
+
1149
+ You can also import the `main` schema directly from the file that defines it:
1150
+
1151
+ ```typescript
1152
+ import { main as getRecord } from './lexicons/com/atproto/repo/getRecord.js'
1153
+
1154
+ await client.call(getRecord, {
1155
+ /* ... */
1156
+ })
1157
+ ```
1158
+
1159
+ This produces equally small bundles as the explicit `.main` reference, but it still surfaces the `main` identifier: you have to know to import `main` and likely rename it.
1160
+
1161
+ #### Default import (recommended)
1162
+
1163
+ To make the small-bundle path also the ergonomic path, every namespace file generated by `lex build` re-exports the `main` schema as its `default` export:
1164
+
1165
+ ```typescript
1166
+ // generated file: ./lexicons/com/atproto/repo/getRecord.js
1167
+ export * from './getRecord.defs.js'
1168
+ export { main as default } from './getRecord.defs.js'
1169
+ ```
1170
+
1171
+ This means you can write:
1172
+
1173
+ ```typescript
1174
+ import getRecord from './lexicons/com/atproto/repo/getRecord.js'
1175
+ import post from './lexicons/app/bsky/feed/post.js'
1176
+
1177
+ await client.call(getRecord, {
1178
+ /* ... */
1179
+ })
1180
+ await client.create(post, {
1181
+ /* ... */
1182
+ })
1183
+ ```
1184
+
1185
+ This is the most bundle-friendly style: the bundler only pulls in the `main` schema, and the import name doesn't have to mention `main` at all. This helps keeping application code aligned with how Lexicons are usually identified.
1186
+
1187
+ #### Drawbacks of the default export
1188
+
1189
+ The `default` re-export is enabled by default but has two minor drawbacks:
1190
+
1191
+ 1. It is one additional property on the namespace module, which can very slightly increase bundle size if you also use the namespace in some places.
1192
+ 2. Any Lexicon document whose path segment is literally `default` (for example a hypothetical `com.example.records.default`) would conflict with the generated `default` export.
1193
+
1194
+ If either of these matters for your use case, you can disable the generation of `default` exports with the `--no-defaultExport` flag:
1036
1195
 
1037
1196
  ```bash
1038
- lex build --pure-annotations
1197
+ lex build --no-defaultExport
1039
1198
  ```
1040
1199
 
1041
- This will make the generated code more easily tree-shakeable from places that import your library.
1200
+ #### Summary
1201
+
1202
+ | Style | Bundle size | Ergonomics |
1203
+ | ------------------------------------------------------ | ----------- | ---------------------------- |
1204
+ | `com.atproto.repo.getRecord` (namespace) | Largest | Best: matches the NSID |
1205
+ | `com.atproto.repo.getRecord.main` | Small | Leaks the `main` identifier |
1206
+ | `import { main as getRecord } from '.../getRecord.js'` | Small | Verbose, leaks `main` |
1207
+ | `import getRecord from '.../getRecord.js'` | Small | Concise, no `main` in source |
1208
+
1209
+ For libraries and applications where bundle size matters (typically anything shipped to a browser), prefer the default-import style. For scripts, tests, and server-side code where the bundle size of generated schemas is not a concern, the namespace style is perfectly fine.
1042
1210
 
1043
1211
  ### Blob references
1044
1212
 
@@ -1174,7 +1342,7 @@ export const likePost: Action<
1174
1342
  app.bsky.feed.like,
1175
1343
  {
1176
1344
  subject: { uri, cid },
1177
- createdAt: l.toDatetimeString(new Date()),
1345
+ createdAt: l.currentDatetimeString(),
1178
1346
  },
1179
1347
  options,
1180
1348
  )
package/bin/lex CHANGED
@@ -51,12 +51,6 @@ yargs(hideBin(process.argv))
51
51
  default: false,
52
52
  describe: 'how to handle errors when processing input files',
53
53
  },
54
- 'pure-annotations': {
55
- type: 'boolean',
56
- default: false,
57
- describe:
58
- 'adds `/*#__PURE__*/` annotations for tree-shaking tools. Set this to true if you are using generated lexicons in a library.',
59
- },
60
54
  exclude: {
61
55
  array: true,
62
56
  type: 'string',
@@ -93,6 +87,18 @@ yargs(hideBin(process.argv))
93
87
  describe:
94
88
  'generate an "index.<fileExt>" file that exports all root-level namespaces',
95
89
  },
90
+ defsExport: {
91
+ type: 'boolean',
92
+ default: false,
93
+ describe:
94
+ 'when some definitions conflict with child namespaces, this option allows to export lexicon definitions under a separate $defs namespace (e.g. com.example.foo.$defs)',
95
+ },
96
+ defaultExport: {
97
+ type: 'boolean',
98
+ default: true,
99
+ describe:
100
+ 'whether to generate a default export for the "main" lexicon definition schema in the parent namespace file',
101
+ },
96
102
  ignoreInvalidLexicons: {
97
103
  type: 'boolean',
98
104
  default: false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atproto/lex",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "engines": {
5
5
  "node": ">=22"
6
6
  },
@@ -37,12 +37,12 @@
37
37
  "dependencies": {
38
38
  "tslib": "^2.8.1",
39
39
  "yargs": "^17.0.0",
40
- "@atproto/lex-builder": "^0.1.0",
41
- "@atproto/lex-client": "^0.1.0",
40
+ "@atproto/lex-builder": "^0.1.2",
41
+ "@atproto/lex-client": "^0.1.2",
42
+ "@atproto/lex-data": "^0.1.1",
42
43
  "@atproto/lex-json": "^0.1.0",
43
- "@atproto/lex-data": "^0.1.0",
44
44
  "@atproto/lex-installer": "^0.1.0",
45
- "@atproto/lex-schema": "^0.1.0"
45
+ "@atproto/lex-schema": "^0.1.1"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@types/yargs": "^17.0.33",