prototypey.org - atproto lexicon typescript toolkit - mirror https://github.com/tylersayshi/prototypey

ran formatter and added ci

Tyler 0dc508d7 649c6787

+105 -47
+43
.github/workflows/deno.yml
··· 1 + # This workflow uses actions that are not certified by GitHub. 2 + # They are provided by a third-party and are governed by 3 + # separate terms of service, privacy policy, and support 4 + # documentation. 5 + 6 + # This workflow will install Deno then run `deno lint` and `deno test`. 7 + # For more information see: https://github.com/denoland/setup-deno 8 + 9 + name: Deno 10 + 11 + on: 12 + push: 13 + branches: ["main"] 14 + pull_request: 15 + branches: ["main"] 16 + 17 + permissions: 18 + contents: read 19 + 20 + jobs: 21 + test: 22 + runs-on: ubuntu-latest 23 + 24 + steps: 25 + - name: Setup repo 26 + uses: actions/checkout@v4 27 + 28 + - name: Setup Deno 29 + uses: denoland/setup-deno@v1 30 + with: 31 + deno-version: v2.x 32 + 33 + - name: Verify formatting 34 + run: deno fmt --check 35 + 36 + - name: Run linter 37 + run: deno lint 38 + 39 + - name: Run type checker 40 + run: deno check 41 + 42 + - name: Run tests 43 + run: deno test -A
+16 -6
README.md
··· 1 1 # typed-lexicon 2 2 3 3 > [!WARNING] 4 - > this project is in the middle of active initial development and not ready for use. there will be updates posted [here](https://bsky.app/profile/tylur.dev) if you'd like to follow along! or checkout the [todo.md](./todo.md) 4 + > this project is in the middle of active initial development and not ready for 5 + > use. there will be updates posted [here](https://bsky.app/profile/tylur.dev) 6 + > if you'd like to follow along! or checkout the [todo.md](./todo.md) 5 7 6 - this will be a toolkit for writing lexicon json schema's in typescript and providing types for lexicon data shape. it will: 8 + this will be a toolkit for writing lexicon json schema's in typescript and 9 + providing types for lexicon data shape. it will: 7 10 8 11 - remove boilerplate and improve ergonomics 9 - - type hint for [atproto type parameters](https://atproto.com/specs/lexicon#overview-of-types) 10 - - infer the typescript type definitions for the data shape to avoid duplication and skew 12 + - type hint for 13 + [atproto type parameters](https://atproto.com/specs/lexicon#overview-of-types) 14 + - infer the typescript type definitions for the data shape to avoid duplication 15 + and skew 11 16 - methods and a cli for generating json 12 17 13 - With each of the above finished, i'll plan to write a `validate` method that will be published alongside this that takes any lexicon json definition and validates payloads off that. 18 + With each of the above finished, i'll plan to write a `validate` method that 19 + will be published alongside this that takes any lexicon json definition and 20 + validates payloads off that. 14 21 15 - My working hypothesis: it will be easier to write lexicons in typescript with a single api, then validate based off the json definition, than it would be to start with validation library types (standard-schema style) and attempt to use those as the authoring and validation tools. 22 + My working hypothesis: it will be easier to write lexicons in typescript with a 23 + single api, then validate based off the json definition, than it would be to 24 + start with validation library types (standard-schema style) and attempt to use 25 + those as the authoring and validation tools. 16 26 17 27 **what you'd write:** 18 28
+13 -13
src/lib.ts
··· 158 158 */ 159 159 export const lx = { 160 160 null( 161 - options?: LexiconItemCommonOptions 161 + options?: LexiconItemCommonOptions, 162 162 ): { type: "null" } & LexiconItemCommonOptions { 163 163 return { 164 164 type: "null", ··· 184 184 } as T & { type: "string" }; 185 185 }, 186 186 unknown( 187 - options?: LexiconItemCommonOptions 187 + options?: LexiconItemCommonOptions, 188 188 ): { type: "unknown" } & LexiconItemCommonOptions { 189 189 return { 190 190 type: "unknown", ··· 211 211 }, 212 212 array<Items extends LexiconItem, Options extends ArrayOptions>( 213 213 items: Items, 214 - options?: Options 214 + options?: Options, 215 215 ): Options & { type: "array"; items: Items } { 216 216 return { 217 217 type: "array", ··· 220 220 } as Options & { type: "array"; items: Items }; 221 221 }, 222 222 token<Description extends string>( 223 - description: Description 223 + description: Description, 224 224 ): { type: "token"; description: Description } { 225 225 return { type: "token", description }; 226 226 }, 227 227 ref<Ref extends string>( 228 228 ref: Ref, 229 - options?: LexiconItemCommonOptions 229 + options?: LexiconItemCommonOptions, 230 230 ): LexiconItemCommonOptions & { type: "ref"; ref: Ref } { 231 231 return { 232 232 type: "ref", ··· 236 236 }, 237 237 union<const Refs extends readonly string[], Options extends UnionOptions>( 238 238 refs: Refs, 239 - options?: Options 239 + options?: Options, 240 240 ): Options & { type: "union"; refs: Refs } { 241 241 return { 242 242 type: "union", ··· 252 252 }, 253 253 object<T extends ObjectProperties>(options: T): ObjectResult<T> { 254 254 const required = Object.keys(options).filter( 255 - (key) => options[key].required 255 + (key) => options[key].required, 256 256 ); 257 257 const nullable = Object.keys(options).filter( 258 - (key) => options[key].nullable 258 + (key) => options[key].nullable, 259 259 ); 260 260 const result: ObjectResult<T> = { 261 261 type: "object", ··· 270 270 return result; 271 271 }, 272 272 params<Properties extends ParamsProperties>( 273 - properties: Properties 273 + properties: Properties, 274 274 ): ParamsResult<Properties> { 275 275 const required = Object.keys(properties).filter( 276 - (key) => properties[key].required 276 + (key) => properties[key].required, 277 277 ); 278 278 const result: { 279 279 type: "params"; ··· 295 295 } as T & { type: "query" }; 296 296 }, 297 297 procedure<T extends ProcedureOptions>( 298 - options?: T 298 + options?: T, 299 299 ): T & { type: "procedure" } { 300 300 return { 301 301 type: "procedure", ··· 303 303 } as T & { type: "procedure" }; 304 304 }, 305 305 subscription<T extends SubscriptionOptions>( 306 - options?: T 306 + options?: T, 307 307 ): T & { type: "subscription" } { 308 308 return { 309 309 type: "subscription", ··· 312 312 }, 313 313 namespace<ID extends string, D extends LexiconNamespace["defs"]>( 314 314 id: ID, 315 - defs: D 315 + defs: D, 316 316 ): { lexicon: 1; id: ID; defs: D } { 317 317 return { 318 318 lexicon: 1,
+5 -5
tests/bsky-actor.test.ts
··· 216 216 followedBy: lx.string({ format: "at-uri" }), 217 217 knownFollowers: lx.ref("#knownFollowers"), 218 218 activitySubscription: lx.ref( 219 - "app.bsky.notification.defs#activitySubscription" 219 + "app.bsky.notification.defs#activitySubscription", 220 220 ), 221 221 }); 222 222 ··· 338 338 "#labelersPref", 339 339 "#postInteractionSettingsPref", 340 340 "#verificationPrefs", 341 - ]) 341 + ]), 342 342 ); 343 343 344 344 assertEquals(preferences, { ··· 762 762 "app.bsky.feed.threadgate#followingRule", 763 763 "app.bsky.feed.threadgate#listRule", 764 764 ]), 765 - { maxLength: 5 } 765 + { maxLength: 5 }, 766 766 ), 767 767 postgateEmbeddingRules: lx.array( 768 768 lx.union(["app.bsky.feed.postgate#disableRule"]), 769 - { maxLength: 5 } 769 + { maxLength: 5 }, 770 770 ), 771 771 }); 772 772 ··· 855 855 followedBy: lx.string({ format: "at-uri" }), 856 856 knownFollowers: lx.ref("#knownFollowers"), 857 857 activitySubscription: lx.ref( 858 - "app.bsky.notification.defs#activitySubscription" 858 + "app.bsky.notification.defs#activitySubscription", 859 859 ), 860 860 }), 861 861 });
+15 -15
tests/bsky-feed.test.ts
··· 197 197 post: lx.ref("#postView", { required: true }), 198 198 parent: lx.union(["#threadViewPost", "#notFoundPost", "#blockedPost"]), 199 199 replies: lx.array( 200 - lx.union(["#threadViewPost", "#notFoundPost", "#blockedPost"]) 200 + lx.union(["#threadViewPost", "#notFoundPost", "#blockedPost"]), 201 201 ), 202 202 threadContext: lx.ref("#threadContext"), 203 203 }); ··· 465 465 466 466 Deno.test("app.bsky.feed.defs - requestLess token", () => { 467 467 const requestLess = lx.token( 468 - "Request that less content like the given feed item be shown in the feed" 468 + "Request that less content like the given feed item be shown in the feed", 469 469 ); 470 470 471 471 assertEquals(requestLess, { ··· 477 477 478 478 Deno.test("app.bsky.feed.defs - requestMore token", () => { 479 479 const requestMore = lx.token( 480 - "Request that more content like the given feed item be shown in the feed" 480 + "Request that more content like the given feed item be shown in the feed", 481 481 ); 482 482 483 483 assertEquals(requestMore, { ··· 498 498 499 499 Deno.test("app.bsky.feed.defs - clickthroughAuthor token", () => { 500 500 const clickthroughAuthor = lx.token( 501 - "User clicked through to the author of the feed item" 501 + "User clicked through to the author of the feed item", 502 502 ); 503 503 504 504 assertEquals(clickthroughAuthor, { ··· 509 509 510 510 Deno.test("app.bsky.feed.defs - clickthroughReposter token", () => { 511 511 const clickthroughReposter = lx.token( 512 - "User clicked through to the reposter of the feed item" 512 + "User clicked through to the reposter of the feed item", 513 513 ); 514 514 515 515 assertEquals(clickthroughReposter, { ··· 520 520 521 521 Deno.test("app.bsky.feed.defs - clickthroughEmbed token", () => { 522 522 const clickthroughEmbed = lx.token( 523 - "User clicked through to the embedded content of the feed item" 523 + "User clicked through to the embedded content of the feed item", 524 524 ); 525 525 526 526 assertEquals(clickthroughEmbed, { ··· 532 532 533 533 Deno.test("app.bsky.feed.defs - contentModeUnspecified token", () => { 534 534 const contentModeUnspecified = lx.token( 535 - "Declares the feed generator returns any types of posts." 535 + "Declares the feed generator returns any types of posts.", 536 536 ); 537 537 538 538 assertEquals(contentModeUnspecified, { ··· 543 543 544 544 Deno.test("app.bsky.feed.defs - contentModeVideo token", () => { 545 545 const contentModeVideo = lx.token( 546 - "Declares the feed generator returns posts containing app.bsky.embed.video embeds." 546 + "Declares the feed generator returns posts containing app.bsky.embed.video embeds.", 547 547 ); 548 548 549 549 assertEquals(contentModeVideo, { ··· 643 643 pinned: lx.boolean(), 644 644 }), 645 645 requestLess: lx.token( 646 - "Request that less content like the given feed item be shown in the feed" 646 + "Request that less content like the given feed item be shown in the feed", 647 647 ), 648 648 requestMore: lx.token( 649 - "Request that more content like the given feed item be shown in the feed" 649 + "Request that more content like the given feed item be shown in the feed", 650 650 ), 651 651 clickthroughItem: lx.token("User clicked through to the feed item"), 652 652 clickthroughAuthor: lx.token( 653 - "User clicked through to the author of the feed item" 653 + "User clicked through to the author of the feed item", 654 654 ), 655 655 clickthroughReposter: lx.token( 656 - "User clicked through to the reposter of the feed item" 656 + "User clicked through to the reposter of the feed item", 657 657 ), 658 658 clickthroughEmbed: lx.token( 659 - "User clicked through to the embedded content of the feed item" 659 + "User clicked through to the embedded content of the feed item", 660 660 ), 661 661 contentModeUnspecified: lx.token( 662 - "Declares the feed generator returns any types of posts." 662 + "Declares the feed generator returns any types of posts.", 663 663 ), 664 664 contentModeVideo: lx.token( 665 - "Declares the feed generator returns posts containing app.bsky.embed.video embeds." 665 + "Declares the feed generator returns posts containing app.bsky.embed.video embeds.", 666 666 ), 667 667 interactionSeen: lx.token("Feed item was seen by user"), 668 668 interactionLike: lx.token("User liked the feed item"),
+3 -3
tests/primitives.test.ts
··· 98 98 99 99 Deno.test("lx.cidLink()", () => { 100 100 const result = lx.cidLink( 101 - "bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a" 101 + "bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a", 102 102 ); 103 103 assertEquals(result, { 104 104 type: "cid-link", ··· 182 182 183 183 Deno.test("lx.token() with interaction event", () => { 184 184 const result = lx.token( 185 - "Request that less content like the given feed item be shown in the feed" 185 + "Request that less content like the given feed item be shown in the feed", 186 186 ); 187 187 assertEquals(result, { 188 188 type: "token", ··· 193 193 194 194 Deno.test("lx.token() with content mode", () => { 195 195 const result = lx.token( 196 - "Declares the feed generator returns posts containing app.bsky.embed.video embeds" 196 + "Declares the feed generator returns posts containing app.bsky.embed.video embeds", 197 197 ); 198 198 assertEquals(result, { 199 199 type: "token",
+10 -5
todo.md
··· 5 5 Build a toolkit for writing ATProto lexicon JSON schemas in TypeScript that: 6 6 7 7 - Removes boilerplate and improves ergonomics 8 - - Provides type hints for [atproto type parameters](https://atproto.com/specs/lexicon#overview-of-types) 9 - - Infers TypeScript type definitions for data shapes to avoid duplication and skew 8 + - Provides type hints for 9 + [atproto type parameters](https://atproto.com/specs/lexicon#overview-of-types) 10 + - Infers TypeScript type definitions for data shapes to avoid duplication and 11 + skew 10 12 - Includes methods and a CLI for generating JSON 11 13 12 14 ## Files to Read ··· 25 27 - **Main spec**: https://atproto.com/specs/lexicon#overview-of-types 26 28 - **Data model**: https://atproto.com/specs/data-model 27 29 - **ATProto lexicon examples**: 28 - - https://github.com/bluesky-social/atproto/blob/main/lexicons/app/bsky/actor/defs.json (for `ref` examples) 29 - - https://github.com/bluesky-social/atproto/blob/main/lexicons/app/bsky/feed/defs.json (for `token` examples) 30 + - https://github.com/bluesky-social/atproto/blob/main/lexicons/app/bsky/actor/defs.json 31 + (for `ref` examples) 32 + - https://github.com/bluesky-social/atproto/blob/main/lexicons/app/bsky/feed/defs.json 33 + (for `token` examples) 30 34 31 35 ## Implementation Status 32 36 ··· 34 38 35 39 ## todo 36 40 37 - write two new test files, one for bsky actor and another for bsky feed. i want to see these fully implemented and tested in separate files. then wait. 41 + write two new test files, one for bsky actor and another for bsky feed. i want 42 + to see these fully implemented and tested in separate files. then wait.