[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 PLAN.md with Phase 2 status, deviations, and Phase 3 setup

- Added Status table at top: Phase 0/1/2 done, Phase 3 next, 92 tests passing
- Added decision log entries 10-12 capturing surprises from Phase 2:
10. XRPC validation rejects record type \u2014 catch error, use responseBody
11. Dotted-key un-flattening required before sending records to PDS
12. resolveRefProperties returns { properties, required } for accurate
required propagation through ref-flattened sub-fields
- Marked Phase 0/1/2 sections as done with commit refs
- Added a 'Deviations from the plan' note under Phase 2 explaining the
five things that ended up different from the original design
- Expanded Phase 3 with what Phase 2 already set up for it and the
open question about where async blob upload post-processing fits

+48 -6
+48 -6
PLAN.md
··· 1 1 # Implementation Plan 2 2 3 - Everything decided, nothing ambiguous. Pick up from any section. 3 + ## Status 4 + 5 + | Phase | State | Tests | Commit | 6 + |-------|-------|-------|--------| 7 + | Phase 0 — Scaffolding | ✅ Done | n/a | `d9c7329` | 8 + | Phase 1 — Generic CRUD | ✅ Done | 35 | `d9c7329` | 9 + | Phase 2 — Dynamic field mapping | ✅ Done | 57 | `31140d5`, `34a5e16` (review fixes) | 10 + | Phase 3 — Blob support | ⏳ Not started | — | — | 11 + | Distribution | ⏳ Not started | — | — | 12 + 13 + **Current totals:** 92 tests passing, lint clean, build clean. **Pick up at Phase 3.** 4 14 5 15 ## Decisions Log 6 16 7 - All design ambiguities were resolved. Reference these if anything feels unclear during implementation. 17 + All design ambiguities were resolved. Reference these if anything feels unclear during implementation. Entries 10–12 were added during Phase 2 implementation as new surprises emerged. 8 18 9 19 | # | Question | Decision | Rationale | 10 20 |---|----------|----------|-----------| ··· 17 27 | 7 | Nested `ref` types | Recursive resolution for typed sub-fields | Better UX for complex records | 18 28 | 8 | Testing | Mocks (vitest + msw) + manual Bluesky testing | Docker integration deferred to post-Phase 1 | 19 29 | 9 | `createdAt` | Auto-inject if schema requires it; user can override | Same pattern as `$type` but schema-conditional | 30 + | 10 | `@atproto/api` XRPC validation rejects `record` type in `resolveLexicon` response | Call the typed client anyway; on `XRPCInvalidResponseError`, extract `responseBody.schema` and parse it. Future: replace with raw `fetch()` if upstream issue persists | The validation throws on a valid response. Catching the error and using its `responseBody` is pragmatic; the response data is still well-formed | 31 + | 11 | Dotted-key un-flattening | `buildRecordFromNodeParams` runs `unflattenDottedKeys` over the resourceMapper value before sending to the PDS. Empty values are dropped | Refs and inline objects flatten to keys like `reply.root`, `reply.parent` for the UI; the PDS expects nested objects | 32 + | 12 | Ref sub-field required propagation | `resolveRefProperties` returns `{ properties, required }`. A flattened sub-field is required iff the parent ref is required AND the resolved schema lists it as required | Otherwise optional refs would mark their sub-fields as required, confusing the user | 20 33 21 34 ## Tooling 22 35 ··· 89 102 90 103 --- 91 104 92 - ## Phase 0 — Project Scaffolding 105 + ## Phase 0 — Project Scaffolding ✅ 106 + 107 + > Done in `d9c7329`. Reference only; no work left here. 93 108 94 109 Set up from scratch with `@n8n/node-cli` conventions. Files live under `src/` so `n8n-node dev` can watch them. 95 110 ··· 197 212 198 213 --- 199 214 200 - ## Phase 1 — Credentials + Generic CRUD 215 + ## Phase 1 — Credentials + Generic CRUD ✅ 216 + 217 + > Done in `d9c7329`. 35 tests covering TID generation, all 5 CRUD operations, `$type`/`createdAt` injection, and error paths. 201 218 202 219 ### 1.1 — Credential definition 203 220 ··· 331 348 332 349 --- 333 350 334 - ## Phase 2 — Dynamic Field Mapping 351 + ## Phase 2 — Dynamic Field Mapping ✅ 352 + 353 + > Done in `31140d5` + `34a5e16`. 57 tests covering lexicon resolution, type mapping, recursive ref flattening, dotted-key un-flattening, and `buildRecordFromNodeParams`. 354 + > 355 + > **Deviations from the original plan, with reasons:** 356 + > 357 + > 1. **PDS path uses error-body extraction, not the typed client's normal return path.** The `@atproto/api` v0.20.6 XRPC client validates `resolveLexicon` responses against a schema whose `schema` field is a `ref` to a `record` type — the validator rejects this with `XRPCInvalidResponseError` even when the response is well-formed. We call the client anyway, catch the error, and parse `err.responseBody.schema`. See decision log entry 10. 358 + > 359 + > 2. **Added `unflattenDottedKeys` in the execute path.** Field flattening produces dotted keys like `reply.root` in the UI; the resourceMapper returns those literally, so we must un-flatten back to nested objects before the XRPC call. The first review missed this because each module looked correct in isolation — only end-to-end tracing surfaced it. See decision log entry 11. 360 + > 361 + > 3. **`resolveRefProperties` returns `{ properties, required }`** (the `ResolvedRef` type) so flattened ref sub-fields get accurate required status. See decision log entry 12. 362 + > 363 + > 4. **`@atproto/lexicon-resolver` is imported dynamically** with a `@ts-expect-error` for module resolution. The package is ESM-only and our `tsconfig` uses `module: commonjs`; dynamic `import()` works at runtime in Node 22 but TypeScript can't statically resolve the ESM exports under that config. A future tsconfig migration to `node16`/`bundler` resolution would clean this up. 364 + > 365 + > 5. **`loadOptionsDependsOn: ['collection']`, `supportAutoMap: true`, and `noFieldsError`** were added to the resourceMapper config beyond what the plan called for — the first two avoid running `getRecordFields` on every keystroke and let users auto-map; the third gives a useful hint when resolution fails. 335 366 336 367 ### 2.1 — Lexicon resolution 337 368 ··· 423 454 424 455 --- 425 456 426 - ## Phase 3 — Blob Support 457 + ## Phase 3 — Blob Support ⏳ (next) 458 + 459 + > Not started. This is the natural next step. 460 + > 461 + > **Things Phase 2 set up that Phase 3 will use:** 462 + > - `lexiconTypeToFieldType` already maps `blob` → `'string'`, so the field renders as a string input where the user can name a binary property. 463 + > - `buildRecordFromNodeParams` already drops empty values — a blob field left blank won't try to upload anything. 464 + > - `Atproto.node.ts` `execute()` already has access to `this.helpers.getBinaryDataBuffer()` via `IExecuteFunctions`. 465 + > 466 + > **What 3.1 needs to do:** during execute, walk the record looking for fields whose lexicon definition is `type: blob`. For each, read the binary buffer from the input item using the field's value as the binary property name, call `agent.com.atproto.repo.uploadBlob(data, { encoding: mimeType })`, and replace the field value with the returned blob reference. 467 + > 468 + > **Open question for Phase 3:** the current `buildRecordFromNodeParams` runs synchronously and doesn't have access to the agent or input item's binary data. Blob handling needs an async post-processing step that takes the built record + the resolved lexicon schema + the input item and substitutes blob references. Plan that as `applyBlobUploads(record, schema, agent, item)` and call it after `buildRecordFromNodeParams` in the create/put switch cases. 427 469 428 470 ### 3.1 — Blob upload 429 471