[READ-ONLY] Mirror of https://github.com/andrioid/n8n-nodes-atproto. atproto node for n8n
0

Configure Feed

Select the types of activity you want to include in your feed.

Update docs for Phase 4: deep resolution, UX, validation

- README: collection picker, hex colors, validation, tips section,
architecture table
- CONTRIBUTING: new files, updated test count, execution pipeline
- DESIGN: type-only lexicons, execution-time processing pipeline,
special field handling, updated file structure
- PLAN: Phase 4 status, decisions 15-21, updated totals
- TODO: Phase 3+4 marked done, 125 tests

+96 -18
+15 -9
CONTRIBUTING.md
··· 48 48 ## Tests 49 49 50 50 ```bash 51 - npm test # 103 tests, runs in ~1s 51 + npm test # 125 tests, runs in ~1s 52 52 npm run test:watch # re-runs on file changes 53 53 ``` 54 54 ··· 58 58 59 59 ``` 60 60 src/nodes/Atproto/ 61 - ├── Atproto.node.ts — Node description + execute method 62 - ├── operations.ts — CRUD wrappers (createRecord, getRecord, etc.) 63 - ├── lexicon.ts — Lexicon schema resolution (PDS + DNS fallback) 64 - ├── fieldMapping.ts — Lexicon types → n8n ResourceMapperFields 65 - ├── blob.ts — Binary upload for blob-typed fields 66 - └── tid.ts — TID (timestamp ID) generation 61 + ├── Atproto.node.ts — Node description, collection picker, execute flow 62 + ├── operations.ts — CRUD wrappers (createRecord, getRecord, etc.) 63 + ├── lexicon.ts — Lexicon schema resolution (PDS + DNS fallback) 64 + ├── fieldMapping.ts — Lexicon types → n8n ResourceMapperFields 65 + ├── typeInjection.ts — Nested $type injection + hex color expansion 66 + ├── validation.ts — Pre-submission schema validation 67 + ├── blob.ts — Binary upload for blob-typed fields 68 + └── tid.ts — TID (timestamp ID) generation 67 69 ``` 68 70 69 71 ### How it works 70 72 71 - **Editor-time:** When the user enters a collection NSID (e.g. `app.bsky.feed.post`), the `resourceMapperMethod` resolves the lexicon schema from the network, maps each property to an n8n field type, and presents a typed form. Ref types are recursively flattened into dotted-path fields (e.g. `reply.root.uri`). 73 + **Editor-time:** When the user picks a collection (via searchable dropdown or typed NSID), the `resourceMapperMethod` resolves the lexicon schema from the network, maps each property to an n8n field type, and presents a typed form. Refs are recursively flattened into dotted-path fields (e.g. `reply.root.uri`). Single-ref unions and type-only lexicons are resolved through cross-document fragment lookup. RGB color objects are collapsed into hex string fields. 72 74 73 - **Execution-time:** The execute method builds a record from the mapped fields, un-flattens dotted keys back into nested objects, uploads any blob-typed fields via `com.atproto.repo.uploadBlob`, auto-injects `$type` and `createdAt`, and makes the XRPC call. 75 + **Execution-time:** The execute method builds a record from the mapped fields, un-flattens dotted keys back into nested objects, uploads any blob-typed fields via `com.atproto.repo.uploadBlob`, injects `$type` on nested ref/union objects, expands hex colors to `{r,g,b}`, validates the record against the schema, auto-injects top-level `$type` and `createdAt`, and makes the XRPC call. 74 76 75 77 ### Key conventions 76 78 ··· 79 81 - **Lexicon resolution** tries the PDS endpoint first, falls back to DNS-based `@atproto/lexicon-resolver`, and falls back to raw JSON mode if both fail. 80 82 - **Session management** — login per execution via `CredentialSession`. No cross-execution token caching. 81 83 - **Blob upload** — top-level blob fields only. The field value in the UI is a binary property name; at execution time, the binary data is read from the input item and uploaded to the PDS. 84 + - **Hex colors** — RGB color objects (from lexicons like `site.standard.theme.color#rgb`) are shown as single hex fields in the UI and expanded to `{ $type, r, g, b }` at execution time. 85 + - **Nested `$type` injection** — `injectNestedTypes` walks the record and adds `$type` discriminators to nested objects from refs and single-ref unions. 86 + - **Schema validation** — `validateRecord` checks required fields, types, `$type` discriminators, and blob references before the PDS call. Errors are surfaced as bullet-point lists in n8n's error UI. 87 + - **Literal record keys** — for lexicons with `key: literal:self` (like `app.bsky.actor.profile`), the rkey auto-resolves when left empty. 82 88 83 89 ## Design docs 84 90
+21
DESIGN.md
··· 163 163 164 164 Complex types (`object`, `union`, `ref`, `array`) render as JSON fields. The user pastes or builds JSON via expressions. Simple types get native n8n inputs. 165 165 166 + **Special handling:** 167 + - **Single-ref unions** (e.g. `{ type: "union", refs: ["site.standard.theme.color#rgb"] }`) are resolved and flattened like regular refs. 168 + - **RGB color objects** (objects with exactly `r`, `g`, `b` integer properties) collapse to a single hex string field (`#3B82F6`) instead of three number fields. 169 + - **Multi-ref unions** show the valid `$type` options in the field label. 170 + - **Arrays** show the item type in the field label. 171 + - **Blob fields** include a hint that the value should be a binary property name from the input. 172 + 166 173 ### Recursive `ref` resolution 167 174 168 175 When a field references another schema (e.g. `app.bsky.richtext.facet`), the node resolves the referenced schema and renders nested typed fields rather than falling back to a raw JSON input. This provides a better UX for complex record types. Resolution follows the same PDS endpoint → fallback chain as the top-level schema. 176 + 177 + **Type-only lexicons** (lexicons with no main record, only named type definitions like `site.standard.theme.color`) are supported — `resolveLexiconSchema` returns a defs-only schema so cross-document fragment resolution (e.g. `#rgb`) works. 178 + 179 + ### Execution-time processing 180 + 181 + After the user's field values are collected and un-flattened into nested objects, the execution pipeline runs: 182 + 183 + 1. **Blob upload** — binary property names → blob references via `uploadBlob` 184 + 2. **Nested `$type` injection** — walks the record + schema and injects `$type` on objects from refs/unions. Hex color strings are expanded to `{ $type, r, g, b }`. 185 + 3. **Schema validation** — checks required fields, type correctness, `$type` discriminators, and blob references. Errors are surfaced as bullet-point lists before the PDS call. 186 + 4. **`$type` + `createdAt` injection** — top-level auto-injection in `operations.ts`. 187 + 5. **XRPC call** — send to PDS. 169 188 170 189 ### Required fields 171 190 ··· 233 252 │ ├── operations.ts # CRUD operation logic 234 253 │ ├── lexicon.ts # Resolve + parse lexicon schemas 235 254 │ ├── fieldMapping.ts # Lexicon schema → ResourceMapperField[] 255 + │ ├── typeInjection.ts # Nested $type injection + hex expansion 256 + │ ├── validation.ts # Pre-submission schema validation 236 257 │ ├── blob.ts # Blob upload for binary fields 237 258 │ ├── tid.ts # TID generation 238 259 │ └── atproto.svg # Node icon
+9 -1
PLAN.md
··· 9 9 | Phase 2 — Dynamic field mapping | ✅ Done | 57 | `31140d5`, `34a5e16` (review fixes) | 10 10 | Phase 3 — Blob support | ✅ Done | 11 | `8f468d7` | 11 11 | Distribution — Bundling | ✅ Done | — | `00a4933`, `976ee7e` | 12 + | Phase 4 — Deep resolution & UX | ✅ Done | 22 | `85beddc`–`b156743` | 12 13 | Distribution — Publish | ⏳ Not started | — | — | 13 14 14 - **Current totals:** 103 tests passing, lint clean, build clean (Vite, ~100ms). 15 + **Current totals:** 125 tests passing, lint clean, build clean (Vite, ~100ms). 15 16 16 17 ## Decisions Log 17 18 ··· 19 20 20 21 | # | Question | Decision | Rationale | 21 22 |---|----------|----------|-----------| 23 + | 15 | Single-ref unions | Treat as resolvable refs via `getResolvableRef()` helper | Common ATProto pattern (`type: "union", refs: ["one.ref"]`); user sees flattened fields instead of raw JSON | 24 + | 16 | Type-only lexicons (no main record) | Return defs-only schema `{ properties: {}, rawDefs }` | Enables fragment resolution for cross-document refs like `site.standard.theme.color#rgb` | 25 + | 17 | RGB color objects | Collapse to single hex string field; expand at execution time | 4 fields instead of 12; hex is universally understood | 26 + | 18 | Nested `$type` injection | Walk record + schema at execution time; inject before PDS call | Users shouldn't need to know about AT Protocol discriminators | 27 + | 19 | Collection picker | `resourceLocator` with `describeRepo` list + free-text NSID mode | Users shouldn't need to memorize NSIDs | 28 + | 20 | Literal record keys | Auto-resolve from schema when rkey is empty | `app.bsky.actor.profile` always uses `self`; users shouldn't need to know | 29 + | 21 | Pre-submission validation | Validate against schema before PDS call; bullet-point errors | PDS errors are opaque (`InvalidRecord`); ours say which field is wrong | 22 30 | 1 | `$type` field handling | Auto-inject always from collection NSID | User never thinks about it | 23 31 | 2 | Repo scope for Get/List | Optional repo field, defaults to authenticated user's DID | Supports reading others' public records | 24 32 | 3 | List pagination | One page per execution + cursor | Standard n8n pattern; user chains/loops |
+27 -4
README.md
··· 14 14 15 15 - **CRUD any AT Protocol record** — create, read, update, delete, and list records in any collection 16 16 - **Dynamic field mapping** — resolves lexicon schemas from the network and presents typed form fields in the n8n editor, so you fill in `title`, `publishedAt`, `tags` rather than crafting raw JSON 17 - - **Handles the hard parts** — PDS resolution, session management, TID generation, and record key conventions 17 + - **Deep schema resolution** — follows `ref` chains, resolves single-ref unions, and handles type-only lexicons (like `site.standard.theme.color#rgb`). Color fields collapse to hex inputs (`#3B82F6`) 18 + - **Smart defaults** — auto-injects `$type` and `createdAt`, auto-resolves literal record keys (e.g. `app.bsky.actor.profile` → `self`), uploads blobs from binary input 19 + - **Validation before submission** — checks required fields, types, and `$type` discriminators at execution time with clear error messages, before the PDS ever sees the record 18 20 - **Works with any PDS** — Bluesky, self-hosted, Blacksky, or any AT Protocol–compatible server 19 21 20 22 ## Use Cases ··· 49 51 1. **Create credentials** — add your AT Protocol handle and an [app password](https://bsky.app/settings/app-passwords) 50 52 2. **Add the node** to a workflow 51 53 3. **Choose an operation** — Create Record, Get Record, etc. 52 - 4. **Enter a collection NSID** — e.g. `site.standard.document` 53 - 5. **Map your fields** — the node resolves the lexicon and shows you the schema's fields 54 - 6. **Run it** 54 + 4. **Pick a collection** — select from the searchable dropdown (lists your repo's collections) or type any NSID manually 55 + 5. **Map your fields** — the node resolves the lexicon and shows you typed form fields. Colors render as hex inputs, blobs explain what to attach, and unions list their valid `$type` options 56 + 6. **Run it** — the node validates your data, injects `$type`/`createdAt`, uploads blobs, and sends the record 57 + 58 + ### Tips 59 + 60 + - **Profiles and other singleton records** — leave the Record Key empty. The node auto-resolves literal keys (e.g. `app.bsky.actor.profile` uses `self`) 61 + - **Theme colors** — enter hex values like `#3B82F6`. They're expanded to `{ "$type": "site.standard.theme.color#rgb", "r": 59, "g": 130, "b": 246 }` automatically 62 + - **Blob fields** — the field label tells you to provide a binary property name. Use an HTTP Request or Read Binary File node upstream to attach the file 55 63 56 64 ## Credentials 57 65 ··· 60 68 | Identifier | Your handle (e.g. `you.bsky.social`) or DID | 61 69 | App Password | Generated at [bsky.app/settings/app-passwords](https://bsky.app/settings/app-passwords) | 62 70 | Service URL | PDS endpoint (default: `https://bsky.social`) | 71 + 72 + ## Architecture 73 + 74 + The node is deliberately thin — it speaks the AT Protocol and defers everything else to lexicon schemas. 75 + 76 + | File | Purpose | 77 + |------|---------| 78 + | `Atproto.node.ts` | Node description, collection picker, execute flow | 79 + | `lexicon.ts` | Schema resolution (PDS endpoint → DNS fallback → null) | 80 + | `fieldMapping.ts` | Lexicon → n8n ResourceMapperFields (ref flattening, hex colors, descriptions) | 81 + | `typeInjection.ts` | Nested `$type` injection + hex color expansion at execution time | 82 + | `validation.ts` | Pre-submission schema validation with friendly errors | 83 + | `blob.ts` | Binary upload via `com.atproto.repo.uploadBlob` | 84 + | `operations.ts` | CRUD wrappers with `$type`/`createdAt` auto-injection | 85 + | `tid.ts` | Timestamp ID generation | 63 86 64 87 ## Contributing 65 88
+24 -4
TODO.md
··· 49 49 - [x] Tests: `tests/fieldMapping.test.ts` (15 tests) 50 50 - [x] Tests: `tests/recursiveRef.test.ts` (8 tests) 51 51 52 - ## Phase 3 — Blob Support 52 + ## ✅ Phase 3 — Blob Support 53 53 54 - - [ ] As a user, I can attach a binary property to a blob field and the node uploads it via `uploadBlob` automatically 55 - - [ ] As a user, the uploaded blob reference is injected into the record in the correct format 54 + - [x] As a user, I can attach a binary property to a blob field and the node uploads it via `uploadBlob` automatically 55 + - [x] As a user, the uploaded blob reference is injected into the record in the correct format 56 + 57 + ## ✅ Phase 4 — Deep Schema Resolution & UX 58 + 59 + - [x] Single-ref unions resolved like refs (common ATProto pattern) 60 + - [x] Type-only lexicons return defs-only schemas for cross-document fragment resolution 61 + - [x] RGB color refs collapsed to hex string fields (`#3B82F6`) 62 + - [x] Hex colors expanded to `{ $type, r, g, b }` at execution time 63 + - [x] Nested `$type` injection on ref/union objects via `injectNestedTypes` 64 + - [x] Descriptions propagated to flattened ref/union fields 65 + - [x] Collection NSID → resourceLocator with searchable list (describeRepo) 66 + - [x] Subtitle showing current operation on canvas 67 + - [x] Swap Commit moved into Options collection 68 + - [x] Blob fields show binary property name hint 69 + - [x] Literal record keys auto-resolve when rkey left empty 70 + - [x] Multi-ref unions show valid `$type` options in field label 71 + - [x] Array fields show item type hint in field label 72 + - [x] Execution-time schema validation with friendly error messages 56 73 57 74 ## Testing 58 75 59 76 - [x] Unit tests for TID generation (valid format, uniqueness, sortability) 60 - - [x] Unit tests for `$type` auto-injection 77 + - [x] Unit tests for `$type` auto-injection + hex color expansion 61 78 - [x] Unit tests for request construction (correct XRPC endpoint, auth headers, body shape) 62 79 - [x] Unit tests for error handling paths (401, 429, not found, malformed response) 63 80 - [x] Unit tests for lexicon schema → ResourceMapperField mapping 64 81 - [x] Unit tests for recursive ref resolution 82 + - [x] Unit tests for single-ref union resolution + type-only lexicons 83 + - [x] Unit tests for schema validation (required fields, types, $type, blobs) 65 84 - [x] Mock XRPC server setup (msw) for all automated tests 85 + - [x] 125 tests passing 66 86 - [ ] Manual testing against real Bluesky PDS during development 67 87 - [ ] Docker-based integration tests deferred 68 88