n8n-nodes-atproto 0.2.1 → 0.2.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.
@@ -37867,6 +37867,12 @@ async function searchCollections(filter) {
37867
37867
  }
37868
37868
  }
37869
37869
  //#endregion
37870
+ Object.defineProperty(exports, "Agent", {
37871
+ enumerable: true,
37872
+ get: function() {
37873
+ return Agent;
37874
+ }
37875
+ });
37870
37876
  Object.defineProperty(exports, "AtUri", {
37871
37877
  enumerable: true,
37872
37878
  get: function() {
@@ -3,6 +3,16 @@ const require_shared = require("../../_chunks/shared.js");
3
3
  let n8n_workflow = require("n8n-workflow");
4
4
  //#region nodes/Atproto/operations.ts
5
5
  /**
6
+ * CRUD operations for AT Protocol records.
7
+ *
8
+ * Each function wraps an XRPC call via the authenticated Agent.
9
+ * - `$type` is auto-injected from the collection NSID.
10
+ * - `createdAt` is auto-injected as ISO string when the schema requires it
11
+ * (Phase 1: always inject; Phase 2 will make it schema-conditional).
12
+ * - `repo` defaults to the authenticated user's DID for write operations.
13
+ * Get/List accept an optional `repo` to read other users' public records.
14
+ */
15
+ /**
6
16
  * Injects `$type` if not already present.
7
17
  */
8
18
  function ensure$type(record, collection) {
@@ -34,6 +44,53 @@ async function resolveActorToDid(agent, actor) {
34
44
  return (await agent.com.atproto.identity.resolveHandle({ handle })).data.did;
35
45
  }
36
46
  /**
47
+ * Map a DID to the URL of its DID document. Supports did:plc (PLC directory)
48
+ * and did:web.
49
+ */
50
+ function didDocumentUrl(did) {
51
+ if (did.startsWith("did:plc:")) return `https://plc.directory/${did}`;
52
+ if (did.startsWith("did:web:")) {
53
+ const [host, ...path] = did.slice(8).split(":").map(decodeURIComponent);
54
+ return path.length === 0 ? `https://${host}/.well-known/did.json` : `https://${host}/${path.join("/")}/did.json`;
55
+ }
56
+ throw new Error(`Unsupported DID method: ${did}`);
57
+ }
58
+ /**
59
+ * Resolve a DID's hosting PDS endpoint from its DID document.
60
+ */
61
+ async function resolvePdsEndpoint(did) {
62
+ const res = await fetch(didDocumentUrl(did));
63
+ if (!res.ok) throw new Error(`Failed to resolve DID ${did}: HTTP ${res.status}`);
64
+ const endpoint = (await res.json()).service?.find((s) => s.id.endsWith("#atproto_pds"))?.serviceEndpoint;
65
+ if (typeof endpoint !== "string" || endpoint.length === 0) throw new Error(`No PDS endpoint found in DID document for ${did}`);
66
+ return endpoint;
67
+ }
68
+ /**
69
+ * Resolve the repo to read from into a DID plus an Agent pointed at the PDS
70
+ * that hosts it. Repo-hosting reads (getRecord, listRecords, getBlob,
71
+ * listBlobs) must hit that PDS — the authenticated session's PDS only serves
72
+ * its own repos and answers "Could not find repo" for any other.
73
+ *
74
+ * The session agent is reused for the user's own repo (already on the correct
75
+ * PDS); foreign repos get an unauthenticated Agent, since these reads are
76
+ * public.
77
+ */
78
+ async function resolveReadTarget(agent, actor) {
79
+ if (!actor || actor.trim() === "") return {
80
+ did: getOwnDid(agent),
81
+ agent
82
+ };
83
+ const did = await resolveActorToDid(agent, actor);
84
+ if (did === agent.did) return {
85
+ did,
86
+ agent
87
+ };
88
+ return {
89
+ did,
90
+ agent: new require_shared.Agent(await resolvePdsEndpoint(did))
91
+ };
92
+ }
93
+ /**
37
94
  * Walk schema properties and inject `const` values for any field the user
38
95
  * left empty. This ensures constant fields are always set correctly even if
39
96
  * the readOnly UI is bypassed (e.g. via JSON mode or expressions).
@@ -69,9 +126,9 @@ async function createRecord(agent, params) {
69
126
  * Optionally reads from a different repo (defaults to self).
70
127
  */
71
128
  async function getRecord(agent, params) {
72
- const repo = params.repo ?? getOwnDid(agent);
73
- const data = (await agent.com.atproto.repo.getRecord({
74
- repo,
129
+ const { did, agent: reader } = await resolveReadTarget(agent, params.repo);
130
+ const data = (await reader.com.atproto.repo.getRecord({
131
+ repo: did,
75
132
  collection: params.collection,
76
133
  rkey: params.rkey
77
134
  })).data;
@@ -119,9 +176,9 @@ async function deleteRecord(agent, params) {
119
176
  * Returns one page per execution; chain nodes or loop to paginate.
120
177
  */
121
178
  async function listRecords(agent, params) {
122
- const repo = params.repo ?? getOwnDid(agent);
123
- const data = (await agent.com.atproto.repo.listRecords({
124
- repo,
179
+ const { did, agent: reader } = await resolveReadTarget(agent, params.repo);
180
+ const data = (await reader.com.atproto.repo.listRecords({
181
+ repo: did,
125
182
  collection: params.collection,
126
183
  limit: params.limit,
127
184
  cursor: params.cursor
@@ -160,8 +217,9 @@ async function uploadBlob(agent, params) {
160
217
  * MIME type (read from the response headers).
161
218
  */
162
219
  async function getBlob(agent, params) {
163
- const response = await agent.com.atproto.sync.getBlob({
164
- did: params.did,
220
+ const { did, agent: reader } = await resolveReadTarget(agent, params.did);
221
+ const response = await reader.com.atproto.sync.getBlob({
222
+ did,
165
223
  cid: params.cid
166
224
  });
167
225
  const buffer = Buffer.from(response.data);
@@ -179,8 +237,8 @@ async function getBlob(agent, params) {
179
237
  * `did` defaults to the authenticated user.
180
238
  */
181
239
  async function listBlobs(agent, params = {}) {
182
- const did = params.did ?? getOwnDid(agent);
183
- const response = await agent.com.atproto.sync.listBlobs({
240
+ const { did, agent: reader } = await resolveReadTarget(agent, params.did);
241
+ const response = await reader.com.atproto.sync.listBlobs({
184
242
  did,
185
243
  limit: params.limit,
186
244
  cursor: params.cursor,
@@ -1 +1 @@
1
- {"version":3,"file":"Atproto.node.js","names":[],"sources":["../../../nodes/Atproto/operations.ts","../../../nodes/Atproto/tid.ts","../../../nodes/Atproto/lexicon.ts","../../../nodes/Atproto/fieldMapping.ts","../../../nodes/Atproto/blob.ts","../../../nodes/Atproto/blobInput.ts","../../../nodes/Atproto/typeInjection.ts","../../../nodes/Atproto/validation.ts","../../../nodes/Atproto/Atproto.node.ts"],"sourcesContent":["/**\n * CRUD operations for AT Protocol records.\n *\n * Each function wraps an XRPC call via the authenticated Agent.\n * - `$type` is auto-injected from the collection NSID.\n * - `createdAt` is auto-injected as ISO string when the schema requires it\n * (Phase 1: always inject; Phase 2 will make it schema-conditional).\n * - `repo` defaults to the authenticated user's DID for write operations.\n * Get/List accept an optional `repo` to read other users' public records.\n */\n\nimport type { Agent } from '@atproto/api';\nimport type { LexiconSchema } from './lexicon';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface CreateRecordParams {\n collection: string;\n record: Record<string, unknown>;\n rkey?: string;\n swapCommit?: string;\n /** Defaults to authenticated user's DID */\n repo?: string;\n}\n\nexport interface GetRecordParams {\n collection: string;\n rkey: string;\n /** Defaults to authenticated user's DID */\n repo?: string;\n}\n\nexport interface PutRecordParams {\n collection: string;\n rkey: string;\n record: Record<string, unknown>;\n swapCommit?: string;\n /** Defaults to authenticated user's DID */\n repo?: string;\n}\n\nexport interface DeleteRecordParams {\n collection: string;\n rkey: string;\n swapCommit?: string;\n /** Defaults to authenticated user's DID */\n repo?: string;\n}\n\nexport interface ListRecordsParams {\n collection: string;\n cursor?: string;\n limit?: number;\n /** Defaults to authenticated user's DID */\n repo?: string;\n}\n\nexport interface CreateRecordResult {\n uri: string;\n cid: string;\n}\n\nexport interface GetRecordResult {\n uri: string;\n cid: string;\n value: Record<string, unknown>;\n}\n\nexport interface PutRecordResult {\n uri: string;\n cid: string;\n}\n\nexport interface DeleteRecordResult {\n uri?: string;\n cid?: string;\n}\n\nexport interface ListRecordsResult {\n records: Array<{\n uri: string;\n cid: string;\n value: Record<string, unknown>;\n }>;\n cursor?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Blob operation params / results\n// ---------------------------------------------------------------------------\n\nexport interface UploadBlobParams {\n data: Buffer;\n mimeType: string;\n}\n\nexport interface UploadBlobResult {\n blob: {\n $type: 'blob';\n ref: { $link: string };\n mimeType: string;\n size: number;\n };\n}\n\nexport interface GetBlobParams {\n /** DID of the account that owns the blob. */\n did: string;\n /** CID of the blob to fetch. */\n cid: string;\n}\n\nexport interface GetBlobResult {\n /** Raw blob bytes. */\n data: Buffer;\n /** MIME type reported by the server (from Content-Type), or empty string. */\n mimeType: string;\n /** Size of the returned buffer in bytes. */\n size: number;\n}\n\nexport interface ListBlobsParams {\n /** DID of the repo to list blobs for. Defaults to authenticated user. */\n did?: string;\n cursor?: string;\n limit?: number;\n /** Optional repo revision — list only blobs added since this rev. */\n since?: string;\n}\n\nexport interface ListBlobsResult {\n cids: string[];\n cursor?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Injects `$type` if not already present.\n */\nfunction ensure$type(record: Record<string, unknown>, collection: string): void {\n if (!record['$type']) {\n record['$type'] = collection;\n }\n}\n\n/**\n * Injects `createdAt` if not already present (ISO timestamp).\n * Phase 1: always inject. Phase 2 will check the lexicon schema.\n */\nfunction ensureCreatedAt(record: Record<string, unknown>): void {\n if (!record['createdAt']) {\n record['createdAt'] = new Date().toISOString();\n }\n}\n\n/**\n * Returns the authenticated user's DID from the agent's session manager.\n */\nfunction getOwnDid(agent: Agent): string {\n const did = agent.did;\n if (!did) {\n throw new Error('Not authenticated — no DID available');\n }\n return did;\n}\n\n/**\n * Resolve a handle to a DID if needed. Returns DIDs unchanged.\n * Strips a leading `@` from handles. Trims whitespace.\n */\nasync function resolveActorToDid(\n agent: Agent,\n actor: string,\n): Promise<string> {\n const trimmed = actor.trim();\n if (trimmed.startsWith('did:')) return trimmed;\n const handle = trimmed.startsWith('@') ? trimmed.slice(1) : trimmed;\n const res = await agent.com.atproto.identity.resolveHandle({ handle });\n return res.data.did;\n}\n\n// ---------------------------------------------------------------------------\n// Const injection\n// ---------------------------------------------------------------------------\n\n/**\n * Walk schema properties and inject `const` values for any field the user\n * left empty. This ensures constant fields are always set correctly even if\n * the readOnly UI is bypassed (e.g. via JSON mode or expressions).\n */\nexport function applyConstValues(\n record: Record<string, unknown>,\n schema: LexiconSchema | null,\n): void {\n if (!schema) return;\n for (const [name, prop] of Object.entries(schema.properties)) {\n if (prop.const !== undefined && (record[name] === undefined || record[name] === null || record[name] === '')) {\n record[name] = prop.const;\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Operations\n// ---------------------------------------------------------------------------\n\n/**\n * Creates a record in the specified collection.\n * Auto-generates a TID if no `rkey` is provided.\n * Auto-injects `$type` and `createdAt`.\n */\nexport async function createRecord(\n agent: Agent,\n params: CreateRecordParams,\n): Promise<CreateRecordResult> {\n const repo = params.repo ?? getOwnDid(agent);\n\n const record = { ...params.record };\n ensure$type(record, params.collection);\n ensureCreatedAt(record);\n\n const response = await agent.com.atproto.repo.createRecord({\n repo,\n collection: params.collection,\n rkey: params.rkey,\n record,\n swapCommit: params.swapCommit,\n });\n\n const data = response.data;\n return { uri: data.uri, cid: data.cid };\n}\n\n/**\n * Gets a record by collection and record key.\n * Optionally reads from a different repo (defaults to self).\n */\nexport async function getRecord(\n agent: Agent,\n params: GetRecordParams,\n): Promise<GetRecordResult> {\n const repo = params.repo ?? getOwnDid(agent);\n\n const response = await agent.com.atproto.repo.getRecord({\n repo,\n collection: params.collection,\n rkey: params.rkey,\n });\n\n const data = response.data as {\n uri: string;\n cid: string;\n value: Record<string, unknown>;\n };\n return { uri: data.uri, cid: data.cid, value: data.value };\n}\n\n/**\n * Full-replaces a record. Optional `swapCommit` for optimistic concurrency.\n * Auto-injects `$type` and `createdAt`.\n */\nexport async function putRecord(\n agent: Agent,\n params: PutRecordParams,\n): Promise<PutRecordResult> {\n const repo = params.repo ?? getOwnDid(agent);\n\n const record = { ...params.record };\n ensure$type(record, params.collection);\n ensureCreatedAt(record);\n\n const response = await agent.com.atproto.repo.putRecord({\n repo,\n collection: params.collection,\n rkey: params.rkey,\n record,\n swapCommit: params.swapCommit,\n });\n\n const data = response.data;\n return { uri: data.uri, cid: data.cid };\n}\n\n/**\n * Deletes a record by collection and record key.\n */\nexport async function deleteRecord(\n agent: Agent,\n params: DeleteRecordParams,\n): Promise<DeleteRecordResult> {\n const repo = params.repo ?? getOwnDid(agent);\n\n const response = await agent.com.atproto.repo.deleteRecord({\n repo,\n collection: params.collection,\n rkey: params.rkey,\n swapCommit: params.swapCommit,\n });\n\n return response.data as DeleteRecordResult;\n}\n\n/**\n * Lists records in a collection with optional pagination.\n * Returns one page per execution; chain nodes or loop to paginate.\n */\nexport async function listRecords(\n agent: Agent,\n params: ListRecordsParams,\n): Promise<ListRecordsResult> {\n const repo = params.repo ?? getOwnDid(agent);\n\n const response = await agent.com.atproto.repo.listRecords({\n repo,\n collection: params.collection,\n limit: params.limit,\n cursor: params.cursor,\n });\n\n const data = response.data as {\n records: Array<{\n uri: string;\n cid: string;\n value: Record<string, unknown>;\n }>;\n cursor?: string;\n };\n\n return {\n records: data.records.map((r) => ({\n uri: r.uri,\n cid: r.cid,\n value: r.value,\n })),\n cursor: data.cursor,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Blob operations\n// ---------------------------------------------------------------------------\n\n/**\n * Upload a blob to the authenticated user's PDS.\n *\n * Returns the blob reference in the canonical AT Protocol shape, ready to\n * embed in a record:\n *\n * { \"$type\": \"blob\", \"ref\": { \"$link\": \"bafkrei...\" }, \"mimeType\": \"image/jpeg\", \"size\": 12345 }\n *\n * The PDS may reject blobs over a service-defined size limit (commonly\n * ~1 MB on bsky.social). The error is propagated unchanged so the caller\n * can surface it to the user.\n */\nexport async function uploadBlob(\n agent: Agent,\n params: UploadBlobParams,\n): Promise<UploadBlobResult> {\n const response = await agent.com.atproto.repo.uploadBlob(params.data, {\n encoding: params.mimeType,\n });\n\n // BlobRef serializes to the on-the-wire JSON shape via toJSON();\n // calling it explicitly gives us a plain object that's safe to return\n // through n8n's data pipeline.\n const ref = response.data.blob as unknown as { toJSON?: () => unknown };\n const serialized =\n typeof ref.toJSON === 'function'\n ? (ref.toJSON() as UploadBlobResult['blob'])\n : (response.data.blob as unknown as UploadBlobResult['blob']);\n\n return { blob: serialized };\n}\n\n/**\n * Download a blob by CID from a given repo.\n *\n * `did` is required by the XRPC method and must be a DID (callers should\n * resolve handles upfront). Returns the raw bytes plus the server-reported\n * MIME type (read from the response headers).\n */\nexport async function getBlob(\n agent: Agent,\n params: GetBlobParams,\n): Promise<GetBlobResult> {\n const response = await agent.com.atproto.sync.getBlob({\n did: params.did,\n cid: params.cid,\n });\n\n // response.data is a Uint8Array — convert to Buffer for n8n's binary helpers.\n const buffer = Buffer.from(response.data);\n const mimeType =\n (response.headers as Record<string, string> | undefined)?.['content-type'] ??\n '';\n\n return {\n data: buffer,\n mimeType,\n size: buffer.length,\n };\n}\n\n/**\n * List blob CIDs in a repo. Paginated; pass `cursor` from a previous response\n * to fetch the next page. `since` filters to blobs added after a given repo\n * revision (rev) — useful for incremental sync.\n *\n * `did` defaults to the authenticated user.\n */\nexport async function listBlobs(\n agent: Agent,\n params: ListBlobsParams = {},\n): Promise<ListBlobsResult> {\n const did = params.did ?? getOwnDid(agent);\n\n const response = await agent.com.atproto.sync.listBlobs({\n did,\n limit: params.limit,\n cursor: params.cursor,\n since: params.since,\n });\n\n return {\n cids: response.data.cids,\n cursor: response.data.cursor,\n };\n}\n\nexport { resolveActorToDid };\n","/**\n * TID (Timestamp Identifier) generation for AT Protocol.\n *\n * A TID is a 13-character base32-sortable string encoding a 64-bit value:\n * - Top bit: always 0\n * - Bits 62–10: 53-bit microsecond Unix timestamp\n * - Bits 9–0: 10-bit random clock ID\n *\n * Characters use the alphabet `234567abcdefghijklmnopqrstuvwxyz`\n * (base32-sortable — no '1' to avoid confusion with 'l').\n */\n\nconst S32_CHAR = '234567abcdefghijklmnopqrstuvwxyz';\n\nlet clockId: number | null = null;\nlet counter = 0;\nlet lastTimestamp = 0;\n\n/**\n * Encodes a non-negative integer to a base32-sortable string.\n * Returns empty string for 0.\n */\nfunction s32encode(i: number): string {\n let s = '';\n while (i) {\n const c = i % 32;\n i = Math.floor(i / 32);\n s = S32_CHAR.charAt(c) + s;\n }\n return s;\n}\n\n/**\n * Returns the clock ID (10 bits), initialised once and reused.\n */\nfunction getClockId(): number {\n if (clockId === null) {\n clockId = Math.floor(Math.random() * 1024); // 10 bits: 0–1023\n }\n return clockId;\n}\n\n/**\n * Generates a new TID string.\n *\n * The timestamp is derived from `Date.now()` in microseconds, with a\n * per-millisecond counter to guarantee uniqueness within the same ms.\n * The clock ID is a random 10-bit value generated on first call and\n * reused for subsequent TIDs from this process.\n *\n * @returns 13-character base32-sortable TID string.\n */\nexport function generateTid(): string {\n const now = Math.max(Date.now(), lastTimestamp);\n\n if (now === lastTimestamp) {\n counter++;\n } else {\n counter = 0;\n }\n lastTimestamp = now;\n\n // (now in ms) × 1000 + counter gives monotonic microsecond-like values\n const timestamp = now * 1000 + counter;\n\n // 53 bits needs up to 11 base32 chars\n const tsPart = s32encode(timestamp).padStart(11, S32_CHAR[0]);\n // 10 bits needs exactly 2 base32 chars\n const cidPart = s32encode(getClockId()).padStart(2, S32_CHAR[0]);\n\n return tsPart + cidPart;\n}\n","/**\n * Lexicon resolution for AT Protocol record schemas.\n *\n * Resolves a lexicon schema document from an NSID via two paths:\n * 1. PDS endpoint: `com.atproto.lexicon.resolveLexicon` (requires auth)\n * 2. Fallback: `@atproto/lexicon-resolver` DNS-based resolution (no auth)\n *\n * Results are cached in-memory (module-level Map) since calls originate\n * from the n8n editor (resourceMapperMethod), not during execution.\n */\n\nimport type { Agent } from '@atproto/api';\n\n// ---------------------------------------------------------------------------\n// Types — simplified view of what we extract from lexicon documents\n// ---------------------------------------------------------------------------\n\n/** A single property/field definition in a lexicon record. */\nexport interface LexiconProperty {\n type: string;\n format?: string;\n /** For `ref` types: the target NSID or #local-name. */\n ref?: string;\n /** For `union` types: the list of possible ref targets. */\n refs?: string[];\n /** For `array` types: the schema of array items. */\n items?: LexiconProperty;\n /** For `object` types: nested properties. */\n properties?: Record<string, LexiconProperty>;\n /** For `object` types: required sub-field names. */\n required?: string[];\n description?: string;\n /** Whether the field is nullable (from the `nullable` array). */\n nullable?: boolean;\n // --- Value constraints ---\n /** Default value for this field. */\n default?: string | number | boolean;\n /** Constant value — field must always equal this. */\n const?: string | number | boolean;\n /** Closed set of allowed values (dropdown). */\n enum?: (string | number)[];\n /** Suggested values (open set — user can type anything). */\n knownValues?: string[];\n // --- Numeric range ---\n minimum?: number;\n maximum?: number;\n // --- Length constraints (string: UTF-8 bytes; bytes: raw; array: element count) ---\n minLength?: number;\n maxLength?: number;\n // --- Grapheme constraints (string only) ---\n minGraphemes?: number;\n maxGraphemes?: number;\n // --- Blob constraints ---\n /** Accepted MIME types for blob fields (e.g. `['image/*']`). */\n accept?: string[];\n /** Maximum blob size in bytes. */\n maxSize?: number;\n // --- Union flag ---\n /** When true, the union rejects unknown `$type` values. */\n closed?: boolean;\n}\n\n/** Parsed record schema extracted from a lexicon document. */\nexport interface LexiconSchema {\n /** The record's property definitions (from `defs.main.record.properties`). */\n properties: Record<string, LexiconProperty>;\n /** Names of required fields (from `defs.main.record.required`). */\n required: string[];\n /** The record key strategy declared by the lexicon. */\n key?: 'tid' | 'any' | 'literal';\n /** When `key` is `literal`, the literal value (e.g. `self`). */\n literalKey?: string;\n /** The raw lexicon document defs (for local ref resolution like `#someName`). */\n rawDefs?: Record<string, unknown>;\n /** NSID of this lexicon schema. */\n nsid: string;\n}\n\n// ---------------------------------------------------------------------------\n// Cache\n// ---------------------------------------------------------------------------\n\nconst schemaCache = new Map<string, LexiconSchema>();\n\n/** Clear the in-memory cache (useful in tests). */\nexport function clearLexiconCache(): void {\n schemaCache.clear();\n}\n\n// ---------------------------------------------------------------------------\n// Resolution\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve a lexicon schema for the given collection NSID.\n *\n * Resolution chain:\n * 1. Check module-level cache.\n * 2. Try PDS `com.atproto.lexicon.resolveLexicon` endpoint.\n * 3. If that fails, try `@atproto/lexicon-resolver` (DNS-based, dynamic import).\n * 4. If both fail, return `null` (triggers JSON fallback in n8n).\n *\n * @param agent - Authenticated AT Protocol agent (may be null if unavailable).\n * @param nsid - The collection NSID to resolve (e.g. `app.bsky.feed.post`).\n */\nexport async function resolveLexiconSchema(\n agent: Agent | null,\n nsid: string,\n): Promise<LexiconSchema | null> {\n const cached = schemaCache.get(nsid);\n if (cached) return cached;\n\n // Try path A: PDS endpoint.\n // The @atproto/api client's strict XRPC validation rejects `record` types\n // in the resolveLexicon response schema. We call the endpoint via the\n // typed client anyway and extract the response body from the error, which\n // is still populated even when validation fails.\n if (agent) {\n try {\n const response =\n await agent.com.atproto.lexicon.resolveLexicon({ nsid });\n const schema = parseRawLexiconDocument(\n response.data.schema as Record<string, unknown>,\n nsid,\n );\n if (schema) {\n schemaCache.set(nsid, schema);\n return schema;\n }\n } catch (err) {\n // The client throws XRPCInvalidResponseError when the response\n // doesn't match the schema. The response body is still available\n // on the error object.\n const errObj = err as Record<string, unknown>;\n const responseBody = errObj.responseBody as\n | Record<string, unknown>\n | undefined;\n if (responseBody?.schema) {\n const schema = parseRawLexiconDocument(\n responseBody.schema as Record<string, unknown>,\n nsid,\n );\n if (schema) {\n schemaCache.set(nsid, schema);\n return schema;\n }\n }\n // Fall through to path B\n }\n }\n\n // Try path B: @atproto/lexicon-resolver (DNS-based, no auth required).\n // Dynamic import to avoid CJS/ESM interop issues at module load time.\n try {\n const mod: { resolveLexicon: (nsid: string) => Promise<unknown> } =\n // @ts-expect-error Can't resolve package under current moduleResolution\n await import('@atproto/lexicon-resolver');\n const resolution = await mod.resolveLexicon(nsid);\n const lexicon = (resolution as Record<string, unknown>)\n .lexicon as Record<string, unknown>;\n const schema = parseLexiconDocumentRecord(lexicon, nsid);\n if (schema) {\n schemaCache.set(nsid, schema);\n return schema;\n }\n } catch {\n // Both paths failed — return null for JSON fallback\n }\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Parsing helpers — exported for direct use in tests\n// ---------------------------------------------------------------------------\n\n/**\n * Parse a raw lexicon document (without $type or CID validation) into our\n * internal schema. Useful for tests that construct mock lexicons directly.\n */\nexport function parseLexiconDoc(\n doc: Record<string, unknown>,\n nsid: string,\n): LexiconSchema | null {\n return parseRawLexiconDocument(doc, nsid);\n}\n\n/**\n * Parse a raw lexicon document (from PDS endpoint) into our internal schema.\n */\nfunction parseRawLexiconDocument(\n doc: Record<string, unknown>,\n nsid: string,\n): LexiconSchema | null {\n const defs = doc?.defs as Record<string, unknown> | undefined;\n if (!defs) return null;\n\n const mainDef = defs?.main as Record<string, unknown> | undefined;\n if (mainDef) {\n const schema = extractRecordSchema(mainDef, defs, nsid);\n if (schema) return schema;\n }\n\n // Type-only lexicon (no main record) — still useful for cross-document\n // fragment resolution (e.g. site.standard.theme.color#rgb).\n return {\n properties: {},\n required: [],\n rawDefs: defs,\n nsid,\n };\n}\n\n/**\n * Parse a LexiconDocumentRecord (from @atproto/lexicon-resolver) into our\n * internal schema. The shape is the same as the raw document, just\n * wrapped in a typed container.\n */\nfunction parseLexiconDocumentRecord(\n lexicon: Record<string, unknown>,\n nsid: string,\n): LexiconSchema | null {\n const defs = lexicon?.defs as Record<string, unknown> | undefined;\n if (!defs) return null;\n\n const mainDef = defs?.main as Record<string, unknown> | undefined;\n if (mainDef) {\n const schema = extractRecordSchema(mainDef, defs, nsid);\n if (schema) return schema;\n }\n\n // Type-only lexicon (no main record) — still useful for cross-document\n // fragment resolution (e.g. site.standard.theme.color#rgb).\n return {\n properties: {},\n required: [],\n rawDefs: defs,\n nsid,\n };\n}\n\n/**\n * Extract the record schema from a `defs.main` definition that has\n * `type: \"record\"`.\n */\nfunction extractRecordSchema(\n mainDef: Record<string, unknown>,\n defs: Record<string, unknown>,\n nsid: string,\n): LexiconSchema | null {\n if (mainDef.type !== 'record') {\n // Some lexicons have query/procedure as main — not a record schema\n return null;\n }\n\n const record = mainDef.record as Record<string, unknown> | undefined;\n if (!record || record.type !== 'object') return null;\n\n const properties = (record.properties as Record<string, unknown>) ?? {};\n const required = (record.required as string[]) ?? [];\n const nullable = (record.nullable as string[]) ?? [];\n\n // Parse record key strategy\n const key = mainDef.key as string | undefined;\n let keyType: 'tid' | 'any' | 'literal' | undefined;\n let literalKey: string | undefined;\n\n if (key === 'tid') {\n keyType = 'tid';\n } else if (key === 'any') {\n keyType = 'any';\n } else if (key && key.startsWith('literal:')) {\n keyType = 'literal';\n literalKey = key.slice('literal:'.length);\n }\n\n // Convert raw properties to our internal representation\n const parsedProperties: Record<string, LexiconProperty> = {};\n for (const [name, raw] of Object.entries(properties)) {\n const prop = raw as Record<string, unknown>;\n parsedProperties[name] = {\n type: (prop.type as string) ?? 'unknown',\n format: prop.format as string | undefined,\n ref: prop.ref as string | undefined,\n refs: prop.refs as string[] | undefined,\n items: prop.items\n ? parseInlineProperty(prop.items as Record<string, unknown>)\n : undefined,\n properties: prop.properties\n ? parseInlineObjectProperties(\n prop.properties as Record<string, unknown>,\n )\n : undefined,\n required: prop.required as string[] | undefined,\n description: prop.description as string | undefined,\n nullable: nullable.includes(name),\n default: prop.default as string | number | boolean | undefined,\n const: prop.const as string | number | boolean | undefined,\n enum: prop.enum as (string | number)[] | undefined,\n knownValues: prop.knownValues as string[] | undefined,\n minimum: prop.minimum as number | undefined,\n maximum: prop.maximum as number | undefined,\n minLength: prop.minLength as number | undefined,\n maxLength: prop.maxLength as number | undefined,\n minGraphemes: prop.minGraphemes as number | undefined,\n maxGraphemes: prop.maxGraphemes as number | undefined,\n accept: prop.accept as string[] | undefined,\n maxSize: prop.maxSize as number | undefined,\n closed: prop.closed as boolean | undefined,\n };\n }\n\n return {\n properties: parsedProperties,\n required,\n key: keyType,\n literalKey,\n rawDefs: defs,\n nsid,\n };\n}\n\n/**\n * Recursively parse a nested property definition (e.g. for `array` items\n * or inline `object` fields).\n *\n * Handles the AT Protocol shorthand where `type` is inferred from other\n * fields (e.g. `{ \"ref\": \"...\" }` implies `type: \"ref\"`).\n */\nfunction parseInlineProperty(\n raw: Record<string, unknown>,\n): LexiconProperty {\n // Infer type from other fields if not explicitly set\n let type = (raw.type as string) ?? 'unknown';\n if (!raw.type) {\n if (raw.ref) type = 'ref';\n else if (raw.refs) type = 'union';\n else if (raw.properties) type = 'object';\n else if (raw.items) type = 'array';\n }\n\n return {\n type,\n format: raw.format as string | undefined,\n ref: raw.ref as string | undefined,\n refs: raw.refs as string[] | undefined,\n items: raw.items\n ? parseInlineProperty(raw.items as Record<string, unknown>)\n : undefined,\n properties: raw.properties\n ? parseInlineObjectProperties(\n raw.properties as Record<string, unknown>,\n )\n : undefined,\n required: raw.required as string[] | undefined,\n description: raw.description as string | undefined,\n default: raw.default as string | number | boolean | undefined,\n const: raw.const as string | number | boolean | undefined,\n enum: raw.enum as (string | number)[] | undefined,\n knownValues: raw.knownValues as string[] | undefined,\n minimum: raw.minimum as number | undefined,\n maximum: raw.maximum as number | undefined,\n minLength: raw.minLength as number | undefined,\n maxLength: raw.maxLength as number | undefined,\n minGraphemes: raw.minGraphemes as number | undefined,\n maxGraphemes: raw.maxGraphemes as number | undefined,\n accept: raw.accept as string[] | undefined,\n maxSize: raw.maxSize as number | undefined,\n closed: raw.closed as boolean | undefined,\n };\n}\n\n/**\n * Parse the `properties` map of an inline object definition.\n */\nfunction parseInlineObjectProperties(\n raw: Record<string, unknown>,\n): Record<string, LexiconProperty> {\n const result: Record<string, LexiconProperty> = {};\n for (const [name, value] of Object.entries(raw)) {\n result[name] = parseInlineProperty(value as Record<string, unknown>);\n }\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Ref resolution\n// ---------------------------------------------------------------------------\n\n/** Resolved ref: properties plus the names of fields required by the\n * referenced definition. */\nexport interface ResolvedRef {\n properties: Record<string, LexiconProperty>;\n required: string[];\n}\n\n/**\n * Resolve a local ref (`#someName`) or cross-document ref\n * (`app.bsky.richtext.facet` or `app.bsky.feed.post#replyRef`) to\n * its property definitions.\n *\n * For local refs, looks in the `rawDefs` of the given schema.\n * For cross-document refs, attempts to resolve the target NSID.\n *\n * @returns The resolved properties + required-field names, or `null` if\n * resolution fails.\n */\nexport async function resolveRefProperties(\n ref: string,\n currentSchema: LexiconSchema,\n resolveExternal: (nsid: string) => Promise<LexiconSchema | null>,\n): Promise<ResolvedRef | null> {\n if (ref.startsWith('#')) {\n // Local ref — look up in defs\n const localName = ref.slice(1);\n return resolveLocalDef(localName, currentSchema.rawDefs);\n }\n\n // Cross-document ref — may include a fragment like\n // `app.bsky.feed.post#replyRef`\n const hashIdx = ref.indexOf('#');\n const targetNsid = hashIdx >= 0 ? ref.slice(0, hashIdx) : ref;\n const fragment = hashIdx >= 0 ? ref.slice(hashIdx + 1) : undefined;\n\n const resolved = await resolveExternal(targetNsid);\n if (!resolved) return null;\n\n if (fragment) {\n return resolveLocalDef(fragment, resolved.rawDefs);\n }\n\n // No fragment — return the record's top-level properties + required\n return {\n properties: resolved.properties,\n required: resolved.required,\n };\n}\n\n/**\n * Look up a def by name in the raw defs map and extract its properties\n * along with the names of required fields.\n */\nfunction resolveLocalDef(\n name: string,\n rawDefs?: Record<string, unknown>,\n): ResolvedRef | null {\n if (!rawDefs) return null;\n\n const def = rawDefs[name] as Record<string, unknown> | undefined;\n if (!def) return null;\n\n // Direct object definition\n if (def.type === 'object') {\n return extractObjectDef(def);\n }\n\n // Token — no properties\n if (def.type === 'token') {\n return { properties: {}, required: [] };\n }\n\n // Record — unwrap to get the inner object\n if (def.type === 'record') {\n const record = def.record as Record<string, unknown> | undefined;\n if (!record || record.type !== 'object') return null;\n return extractObjectDef(record);\n }\n\n return null;\n}\n\n/** Extract a `{ properties, required }` pair from an `object`-type def. */\nfunction extractObjectDef(\n objDef: Record<string, unknown>,\n): ResolvedRef | null {\n const properties = objDef.properties as\n | Record<string, unknown>\n | undefined;\n if (!properties) return null;\n const required = (objDef.required as string[]) ?? [];\n const nullable = (objDef.nullable as string[]) ?? [];\n const result: Record<string, LexiconProperty> = {};\n for (const [k, v] of Object.entries(properties)) {\n const p = parseInlineProperty(v as Record<string, unknown>);\n p.nullable = nullable.includes(k);\n result[k] = p;\n }\n return { properties: result, required };\n}\n","/**\n * Maps a resolved AT Protocol lexicon schema to n8n `ResourceMapperField[]`.\n *\n * Handles:\n * - Type mapping from lexicon types → n8n FieldType\n * - Recursive `ref` resolution with dotted-path flattening (cap depth 3)\n * - Required vs optional fields\n * - `createdAt` auto-population as default match\n * - Array items hint (shown as `json` for complex, `string` for primitives)\n */\n\nimport type { ResourceMapperField } from 'n8n-workflow';\nimport type { Agent } from '@atproto/api';\nimport type { LexiconProperty, LexiconSchema } from './lexicon';\nimport { resolveLexiconSchema, resolveRefProperties } from './lexicon';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/**\n * Maximum recursion depth for resolving `ref` types in the UI.\n *\n * Set to 1 so that top-level refs are flattened into their immediate\n * properties, but sub-refs within those become single `object` fields\n * rather than being recursively exploded into many dotted sub-fields.\n *\n * This keeps the field count manageable (e.g. a theme with 4 color\n * refs becomes 4 object fields, not 12+ individual number fields).\n *\n * Note: $type injection (typeInjection.ts) and validation (validation.ts)\n * maintain their own depth limits (3) for full recursive processing\n * at execution time.\n */\nconst MAX_REF_DEPTH = 1;\n\n/** Field names that should be auto-populated with default expressions. */\nconst AUTO_DEFAULTS: Record<string, string> = {\n createdAt: '={{ $now }}',\n};\n\n// ---------------------------------------------------------------------------\n// Type mapping\n// ---------------------------------------------------------------------------\n\n/**\n * Map a lexicon property type/format to an n8n FieldType.\n */\nfunction lexiconTypeToFieldType(prop: LexiconProperty): string {\n switch (prop.type) {\n case 'string':\n if (prop.format === 'datetime') return 'dateTime';\n // Note: 'url' is not a valid ResourceMapperField type, so uri/at-uri\n // stay as 'string' with a format hint in displayName instead.\n return 'string';\n case 'integer':\n return 'number';\n case 'boolean':\n return 'boolean';\n case 'array':\n return 'json';\n case 'object':\n // Inline object — flatten or treat as json\n if (prop.properties) return 'json';\n return 'json';\n case 'union':\n return 'json';\n case 'ref':\n return 'json'; // fallback — refs are flattened if resolvable\n case 'blob':\n return 'string';\n case 'cid-link':\n return 'string';\n case 'bytes':\n return 'string';\n case 'token':\n return 'string';\n case 'unknown':\n return 'object';\n default:\n return 'string';\n }\n}\n\n// ---------------------------------------------------------------------------\n// Ref helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Extract a resolvable ref target from a property.\n *\n * Returns the ref string for `ref` types and single-ref `union` types.\n * Single-ref unions are a common AT Protocol pattern for typed sub-objects\n * (e.g. `{ type: \"union\", refs: [\"site.standard.theme.color#rgb\"] }`).\n */\nexport function getResolvableRef(prop: LexiconProperty): string | null {\n if (prop.type === 'ref' && prop.ref) return prop.ref;\n if (prop.type === 'union' && prop.refs?.length === 1) return prop.refs[0];\n return null;\n}\n\n/**\n * Format displayName for multi-ref union fields.\n *\n * Shows the possible `$type` values so users know what to put in the JSON.\n * E.g. `embed (set $type to one of: images, external, record)`\n */\nfunction formatUnionDisplayName(\n name: string,\n prop: LexiconProperty,\n): string {\n const desc = prop.description?.replace(/\\.\\s*$/, '');\n if (prop.refs?.length) {\n const shortNames = prop.refs.map((r) => {\n const frag = r.indexOf('#');\n if (frag >= 0) return r.slice(frag + 1);\n const parts = r.split('.');\n return parts[parts.length - 1];\n });\n const typeList =\n shortNames.length <= 3\n ? shortNames.join(', ')\n : `${shortNames.slice(0, 2).join(', ')}, …`;\n return desc\n ? `${name} (${desc} — set $type to: ${typeList})`\n : `${name} (set $type to: ${typeList})`;\n }\n return desc ? `${name} (${desc})` : name;\n}\n\n/**\n * Format displayName for array fields.\n *\n * Shows the item type and description.\n * E.g. `facets (JSON array of app.bsky.richtext.facet)`\n */\nfunction formatArrayDisplayName(\n name: string,\n prop: LexiconProperty,\n): string {\n const desc = prop.description?.replace(/\\.\\s*$/, '');\n const itemRef =\n prop.items?.ref ?? prop.items?.refs?.[0];\n const itemHint = itemRef\n ? `JSON array of ${itemRef}`\n : prop.items?.type === 'string'\n ? 'list of strings'\n : 'JSON array';\n return desc\n ? `${name} (${desc} — ${itemHint})`\n : `${name} (${itemHint})`;\n}\n\n// ---------------------------------------------------------------------------\n// Main entry point\n// ---------------------------------------------------------------------------\n\n/**\n * Convert a resolved `LexiconSchema` to an array of `ResourceMapperField`.\n *\n * @param schema - The resolved lexicon schema.\n * @param agent - Authenticated agent (for resolving cross-document refs).\n * @param depth - Current recursion depth for ref resolution (starts at 0).\n */\nexport async function lexiconToResourceMapperFields(\n schema: LexiconSchema,\n agent: Agent | null,\n depth: number = 0,\n): Promise<ResourceMapperField[]> {\n const fields: ResourceMapperField[] = [];\n\n for (const [name, prop] of Object.entries(schema.properties)) {\n const isRequired = schema.required.includes(name);\n const subFields = await propToFields(\n name,\n prop,\n isRequired,\n schema,\n agent,\n depth,\n );\n fields.push(...subFields);\n }\n\n return fields;\n}\n\n// ---------------------------------------------------------------------------\n// Property → Field(s) conversion\n// ---------------------------------------------------------------------------\n\n/**\n * Convert a single lexicon property to one or more `ResourceMapperField`s.\n *\n * Simple types produce a single field. `ref` types produce multiple\n * flattened dotted-path fields when the target schema is resolvable.\n */\nasync function propToFields(\n name: string,\n prop: LexiconProperty,\n required: boolean,\n parentSchema: LexiconSchema,\n agent: Agent | null,\n depth: number,\n): Promise<ResourceMapperField[]> {\n // --- const fields: fixed, immutable value → readOnly ---\n if (prop.const !== undefined) {\n const fieldType = lexiconTypeToFieldType(prop);\n const desc = prop.description?.replace(/\\.\\s*$/, '');\n return [{\n id: name,\n displayName: desc\n ? `${name} (${desc} — fixed: ${String(prop.const)})`\n : `${name} (fixed: ${String(prop.const)})`,\n required,\n defaultMatch: false,\n display: true,\n type: fieldType as ResourceMapperField['type'],\n readOnly: true,\n defaultValue: prop.const,\n }];\n }\n\n // --- enum: closed set → options dropdown ---\n if (prop.enum?.length && (prop.type === 'string' || prop.type === 'integer')) {\n const desc = prop.description?.replace(/\\.\\s*$/, '');\n return [{\n id: name,\n displayName: desc ? `${name} (${desc})` : name,\n required,\n defaultMatch: name === 'createdAt',\n display: true,\n type: 'options' as ResourceMapperField['type'],\n options: prop.enum.map(v => ({ name: String(v), value: v })),\n ...(prop.default !== undefined ? { defaultValue: prop.default } : {}),\n }];\n }\n\n // --- ref / single-ref union types: attempt recursive flattening ---\n const resolveTarget = getResolvableRef(prop);\n if (resolveTarget && depth < MAX_REF_DEPTH) {\n const resolved = await resolveRefProperties(\n resolveTarget,\n parentSchema,\n (nsid: string) => resolveLexiconSchema(agent, nsid),\n );\n\n if (resolved !== null && Object.keys(resolved.properties).length > 0) {\n // Flatten the resolved properties with a dotted prefix.\n // A sub-field is required iff the parent ref is required AND the\n // resolved schema lists it as required.\n return flattenRefProperties(\n name,\n resolved.properties,\n resolved.required,\n required,\n parentSchema,\n agent,\n depth + 1,\n );\n }\n\n // Resolution failed — fall back to object field\n const fallbackDisplay = prop.description\n ? `${name} (${prop.description})`\n : name;\n return [\n {\n id: name,\n displayName: fallbackDisplay,\n required,\n defaultMatch: false,\n display: true,\n type: 'object',\n },\n ];\n }\n\n // --- ref / single-ref union that hit the depth limit: show as object ---\n // Still resolve the ref to build a JSON template for the default value,\n // so the user sees the expected structure instead of an empty editor.\n if (resolveTarget) {\n const desc = prop.description?.replace(/\\.\\s*$/, '');\n let template: string | undefined;\n try {\n const resolved = await resolveRefProperties(\n resolveTarget,\n parentSchema,\n (nsid: string) => resolveLexiconSchema(agent, nsid),\n );\n if (resolved && Object.keys(resolved.properties).length > 0) {\n template = JSON.stringify(\n buildDefaultTemplate(resolved.properties),\n null,\n 2,\n );\n }\n } catch {\n // Resolution failed — no template, user gets empty editor\n }\n return [\n {\n id: name,\n displayName: desc ? `${name} (${desc})` : name,\n required,\n defaultMatch: false,\n display: true,\n type: 'object',\n ...(template ? { defaultValue: template } : {}),\n },\n ];\n }\n\n // --- multi-ref union types: always object (too complex for flattening) ---\n if (prop.type === 'union') {\n const displayName = formatUnionDisplayName(name, prop);\n return [\n {\n id: name,\n displayName,\n required,\n defaultMatch: false,\n display: true,\n type: 'object',\n },\n ];\n }\n\n // --- array types ---\n // Use type: 'array' — n8n's tryToParseArray() accepts JSON array strings.\n // Using 'object' would fail validation since [] is not a JSON object.\n if (prop.type === 'array') {\n const displayName = formatArrayDisplayName(name, prop);\n return [\n {\n id: name,\n displayName,\n required,\n defaultMatch: false,\n display: true,\n type: 'array' as ResourceMapperField['type'],\n defaultValue: '[]',\n },\n ];\n }\n\n // --- object types (inline) ---\n if (prop.type === 'object' && prop.properties) {\n // Flatten inline object properties with dotted prefix\n const fields: ResourceMapperField[] = [];\n const subRequired = prop.required ?? [];\n for (const [subName, subProp] of Object.entries(prop.properties)) {\n const isSubRequired = required && subRequired.includes(subName);\n const subFields = await propToFields(\n `${name}.${subName}`,\n subProp,\n isSubRequired,\n parentSchema,\n agent,\n depth,\n );\n fields.push(...subFields);\n }\n return fields;\n }\n\n // --- simple types ---\n const fieldType = lexiconTypeToFieldType(prop);\n const field: ResourceMapperField = {\n id: name,\n displayName: name,\n required,\n defaultMatch: false,\n display: true,\n type: fieldType as ResourceMapperField['type'],\n };\n\n // Attach optional description (with blob hint)\n if (prop.type === 'blob') {\n const desc = prop.description?.replace(/\\.\\s*$/, '');\n field.displayName = desc\n ? `${name} (${desc} — binary property name from input)`\n : `${name} (binary property name from input)`;\n } else if (prop.description) {\n field.displayName = `${name} (${prop.description})`;\n }\n\n // knownValues: open set → show suggestions in displayName\n if (prop.knownValues?.length) {\n const shortNames = prop.knownValues.map(v => {\n const hash = v.indexOf('#');\n return hash >= 0 ? v.slice(hash + 1) : v.split('.').pop() ?? v;\n });\n const hint = shortNames.length <= 4\n ? shortNames.join(', ')\n : `${shortNames.slice(0, 3).join(', ')}, …`;\n const desc = prop.description?.replace(/\\.\\s*$/, '');\n field.displayName = desc\n ? `${name} (${desc} — e.g. ${hint})`\n : `${name} (e.g. ${hint})`;\n }\n\n // String format hints (datetime is already mapped to dateTime type)\n if (prop.type === 'string' && prop.format && prop.format !== 'datetime') {\n field.displayName += ` (${prop.format})`;\n }\n\n // Constraint hints in displayName\n const hints: string[] = [];\n if (prop.maxGraphemes) hints.push(`max ${prop.maxGraphemes} chars`);\n else if (prop.maxLength && prop.type === 'string') hints.push(`max ${prop.maxLength} bytes`);\n if (prop.minimum !== undefined || prop.maximum !== undefined) {\n const parts: string[] = [];\n if (prop.minimum !== undefined) parts.push(`≥${prop.minimum}`);\n if (prop.maximum !== undefined) parts.push(`≤${prop.maximum}`);\n hints.push(parts.join(', '));\n }\n if (hints.length) {\n field.displayName += ` [${hints.join('; ')}]`;\n }\n\n // Apply auto-defaults for known fields, then schema defaults\n if (AUTO_DEFAULTS[name] !== undefined) {\n field.defaultValue = AUTO_DEFAULTS[name];\n if (name === 'createdAt') {\n field.defaultMatch = true;\n }\n } else if (prop.default !== undefined) {\n field.defaultValue = prop.default;\n }\n\n return [field];\n}\n\n// ---------------------------------------------------------------------------\n// Ref flattening\n// ---------------------------------------------------------------------------\n\n/**\n * Flatten a resolved ref's properties into dotted-path fields.\n *\n * E.g. `reply.root.uri`, `reply.root.cid` instead of a single `reply` json field.\n *\n * A sub-field is marked `required` iff the parent ref itself is required\n * (`parentRequired`) AND the resolved schema lists the sub-field as required\n * (`schemaRequired`). This avoids marking optional ref's sub-fields as required\n * just because the schema requires them when the ref is present.\n */\nasync function flattenRefProperties(\n prefix: string,\n properties: Record<string, LexiconProperty>,\n schemaRequired: string[],\n parentRequired: boolean,\n parentSchema: LexiconSchema,\n agent: Agent | null,\n depth: number,\n): Promise<ResourceMapperField[]> {\n const fields: ResourceMapperField[] = [];\n\n for (const [name, prop] of Object.entries(properties)) {\n const dotted = `${prefix}.${name}`;\n const isRequired = parentRequired && schemaRequired.includes(name);\n\n // Recursively handle nested refs and single-ref unions\n const nestedRef = getResolvableRef(prop);\n if (nestedRef && depth < MAX_REF_DEPTH) {\n const resolved = await resolveRefProperties(\n nestedRef,\n parentSchema,\n (nsid) => resolveLexiconSchema(agent, nsid),\n );\n if (resolved !== null && Object.keys(resolved.properties).length > 0) {\n const nested = await flattenRefProperties(\n dotted,\n resolved.properties,\n resolved.required,\n isRequired,\n parentSchema,\n agent,\n depth + 1,\n );\n fields.push(...nested);\n continue;\n }\n }\n\n // For simple types or when refs can't be resolved further,\n // route through propToFields so the field gets all Phase 5 treatment\n // (defaults, enums, format hints, constraint hints).\n const subFields = await propToFields(\n dotted,\n prop,\n isRequired,\n parentSchema,\n agent,\n depth,\n );\n fields.push(...subFields);\n }\n\n return fields;\n}\n\n// ---------------------------------------------------------------------------\n// Default template generation\n// ---------------------------------------------------------------------------\n\n/**\n * Build a JSON-serialisable default template from resolved schema properties.\n *\n * Used for object-type fields where the ref was resolved (for schema info)\n * but not flattened (due to depth limit). The template shows the expected\n * structure so the user isn't staring at an empty editor.\n *\n * E.g. for a color ref: `{ \"r\": 0, \"g\": 0, \"b\": 0 }`\n * E.g. for a strongRef: `{ \"uri\": \"\", \"cid\": \"\" }`\n */\nfunction buildDefaultTemplate(\n properties: Record<string, LexiconProperty>,\n): Record<string, unknown> {\n const template: Record<string, unknown> = {};\n for (const [name, prop] of Object.entries(properties)) {\n template[name] = defaultForPropType(prop);\n }\n return template;\n}\n\n/** Derive a sensible default value for a single property. */\nfunction defaultForPropType(prop: LexiconProperty): unknown {\n if (prop.default !== undefined) return prop.default;\n if (prop.const !== undefined) return prop.const;\n switch (prop.type) {\n case 'string':\n return '';\n case 'integer':\n return prop.minimum ?? 0;\n case 'boolean':\n return false;\n case 'array':\n return [];\n case 'object':\n if (prop.properties) {\n return buildDefaultTemplate(prop.properties);\n }\n return {};\n default:\n return null;\n }\n}\n","/**\n * Blob upload support for AT Protocol nodes.\n *\n * After a record is built from node parameters, this module walks the record\n * looking for fields whose lexicon definition is `type: blob`. For each such\n * field, it reads the binary data from the n8n input item, uploads it via\n * `com.atproto.repo.uploadBlob`, and replaces the field value with the\n * returned blob reference.\n *\n * Only top-level blob fields are handled (not nested inside refs/objects).\n * This covers the common case — a record whose own schema declares a blob field.\n * For Bluesky embeds (images/video), the blob lives in the embed sub-record\n * (e.g. `app.bsky.embed.images`) which the user composes as a union/JSON value;\n * blob upload for those is currently outside scope.\n */\n\nimport type { Agent } from '@atproto/api';\nimport type { IExecuteFunctions } from 'n8n-workflow';\nimport type { LexiconSchema } from './lexicon';\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Walk a built record and upload any blob fields whose values name a binary\n * property on the input item.\n *\n * @param record - The constructed record (from `buildRecordFromNodeParams`).\n * @param schema - The resolved lexicon schema, or `null` if resolution\n * failed. When `null`, the record passes through unchanged\n * (blob fields remain as raw binary property name strings).\n * @param agent - Authenticated AT Protocol agent.\n * @param itemIndex - Index of the current input item in the execution loop.\n * @param executeFunctions - n8n execute functions (for `getBinaryDataBuffer`).\n * @returns A new record with blob fields replaced by blob references\n * `{ $type: 'blob', ref: { $link: cid }, mimeType, size }`.\n * @throws If a binary property named by a blob field doesn't exist or fails to\n * upload. Follows the \"rollback\" pattern — first failure stops processing.\n */\nexport async function applyBlobUploads(\n record: Record<string, unknown>,\n schema: LexiconSchema | null,\n agent: Agent,\n itemIndex: number,\n executeFunctions: IExecuteFunctions,\n): Promise<Record<string, unknown>> {\n if (!schema) {\n // Lexicon unknown — can't determine which fields are blobs.\n // Leave the record as-is; the user may have provided a raw blob ref\n // via JSON mode, or the PDS will reject with a validation error.\n return record;\n }\n\n const result = { ...record };\n\n for (const [key, value] of Object.entries(result)) {\n const propDef = schema.properties[key];\n if (!propDef || propDef.type !== 'blob') {\n continue;\n }\n\n // The field value from resourceMapper is the binary property name.\n // Skip empty/unset fields (they were already dropped by unflattenDottedKeys\n // for empty strings, but guard against edge cases).\n if (typeof value !== 'string' || value.length === 0) {\n continue;\n }\n\n // Pre-upload validation: check accept + maxSize before uploading\n const items = executeFunctions.getInputData();\n const item = items[itemIndex];\n const binaryMeta = item?.binary?.[value];\n\n if (propDef.accept?.length) {\n const mimeType = binaryMeta?.mimeType ?? 'application/octet-stream';\n const accepted = propDef.accept.some(pattern => {\n if (pattern === '*/*') return true;\n if (pattern.endsWith('/*'))\n return mimeType.startsWith(pattern.slice(0, -1));\n return mimeType === pattern;\n });\n if (!accepted) {\n throw new Error(\n `'${key}' accepts ${propDef.accept.join(', ')} but got ${mimeType}`,\n );\n }\n }\n\n // Upload the blob and substitute the reference\n result[key] = await uploadBlobFromBinary(\n value,\n propDef,\n agent,\n itemIndex,\n executeFunctions,\n );\n }\n\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Upload a blob from a named binary property on the current input item.\n *\n * Steps:\n * 1. Read the binary buffer via `getBinaryDataBuffer`.\n * 2. Determine MIME type from the binary item's metadata (falls back to\n * `application/octet-stream`).\n * 3. Call `com.atproto.repo.uploadBlob` on the PDS.\n * 4. Return the blob reference object from the response.\n */\nasync function uploadBlobFromBinary(\n binaryPropertyName: string,\n propDef: import('./lexicon').LexiconProperty,\n agent: Agent,\n itemIndex: number,\n executeFunctions: IExecuteFunctions,\n): Promise<Record<string, unknown>> {\n // 1. Read binary data\n const buffer = await executeFunctions.helpers.getBinaryDataBuffer(\n itemIndex,\n binaryPropertyName,\n );\n\n if (!buffer) {\n throw new Error(\n `Binary property \"${binaryPropertyName}\" not found on input item`,\n );\n }\n\n // 1b. Check maxSize before uploading (saves bandwidth)\n if (propDef.maxSize && buffer.length > propDef.maxSize) {\n throw new Error(\n `'${binaryPropertyName}' max size is ${propDef.maxSize} bytes, file is ${buffer.length} bytes`,\n );\n }\n\n // 2. Determine MIME type from binary metadata\n const items = executeFunctions.getInputData();\n const item = items[itemIndex];\n const binaryMeta = item?.binary?.[binaryPropertyName];\n const mimeType = binaryMeta?.mimeType ?? 'application/octet-stream';\n\n // 3. Upload to PDS\n // The typed XRPC client signature is `uploadBlob(data, opts)` where\n // `data` is the raw binary buffer and `opts.encoding` sets the\n // Content-Type header.\n const response = await agent.com.atproto.repo.uploadBlob(buffer, {\n encoding: mimeType,\n });\n\n // 4. Return the blob reference\n return response.data.blob as unknown as Record<string, unknown>;\n}\n","/**\n * Parse a user-supplied blob reference into a `{ cid, did? }` shape.\n *\n * The Download Blob operation needs a CID (and a DID for the repo). Users\n * paste this from many sources, so we accept whichever shape is convenient:\n *\n * 1. Bare CID — `bafkreig...`\n * 2. Blob-ref JSON — `{\"$link\":\"bafkreig...\"}` or the full BlobRef\n * object `{\"$type\":\"blob\",\"ref\":{\"$link\":\"...\"}}`\n * (so they can paste the output of a previous op\n * or a `getRecord` result without drilling in).\n * 3. bsky CDN URL — `https://cdn.bsky.app/img/<variant>/plain/<did>/<cid>@<ext>`\n * DID is extracted and returned so the Repo field\n * can be left empty in this case.\n *\n * Throws a descriptive error if nothing matches.\n */\n\nexport interface ParsedBlobReference {\n cid: string;\n /** Present only when the input embedded a DID (currently: bsky CDN URLs). */\n did?: string;\n}\n\n// A CID v1 base32 starts with `bafy` or `bafk` (most cases) but the alphabet\n// is base32-lowercase, so we keep this loose. A stricter check would require\n// the multibase + multihash libraries, which is overkill here.\nconst CID_LOOSE = /^[a-z0-9]{40,}$/i;\n\n/**\n * Path shape: `/img/<variant>/plain/<did>/<cid>(@<ext>)?`\n * The DID is everything between `/plain/` and the next `/`; the CID is\n * everything between that `/` and either `@<ext>` or end of path.\n */\nconst BSKY_CDN_PATH = /\\/img\\/[^/]+\\/plain\\/([^/]+)\\/([^/@?#]+)(?:@[^/?#]+)?/;\n\nexport function parseBlobReference(input: unknown): ParsedBlobReference {\n // n8n expression results may not always be strings — accept object inputs\n // (a pasted BlobRef expression) directly without round-tripping through JSON.\n if (input && typeof input === 'object') {\n const cid = extractLinkFromObject(input as Record<string, unknown>);\n if (cid) return { cid };\n throw new Error(\n 'Could not find a CID in the provided object. Expected a BlobRef with `ref.$link` or `$link`.',\n );\n }\n\n if (typeof input !== 'string') {\n throw new Error('Blob reference must be a string, BlobRef object, or URL');\n }\n\n const trimmed = input.trim();\n if (!trimmed) {\n throw new Error('Blob reference is empty');\n }\n\n // 1. bsky CDN URL — try before bare-CID because the URL also contains\n // CID-like strings inside it.\n if (trimmed.startsWith('http://') || trimmed.startsWith('https://')) {\n const url = safeUrl(trimmed);\n if (!url) {\n throw new Error(`Not a valid URL: ${trimmed}`);\n }\n const match = url.pathname.match(BSKY_CDN_PATH);\n if (match) {\n return { did: decodeURIComponent(match[1]), cid: match[2] };\n }\n throw new Error(\n 'URL is not a recognised bsky CDN blob URL (expected /img/<variant>/plain/<did>/<cid>)',\n );\n }\n\n // 2. JSON blob-ref pasted as a string\n if (trimmed.startsWith('{')) {\n try {\n const parsed = JSON.parse(trimmed) as Record<string, unknown>;\n const cid = extractLinkFromObject(parsed);\n if (cid) return { cid };\n } catch {\n // fall through to bare-CID check\n }\n throw new Error(\n 'Could not find a CID in the pasted JSON. Expected `{\"$link\":\"...\"}` or a full BlobRef.',\n );\n }\n\n // 3. Bare CID\n if (CID_LOOSE.test(trimmed)) {\n return { cid: trimmed };\n }\n\n throw new Error(\n `Unrecognised blob reference: \"${trimmed}\". Expected a CID, a blob-ref JSON, or a bsky CDN URL.`,\n );\n}\n\n// ---------------------------------------------------------------------------\n// Internals\n// ---------------------------------------------------------------------------\n\n/**\n * Pull a `$link` value out of common BlobRef shapes:\n * - `{ $link: \"...\" }`\n * - `{ ref: { $link: \"...\" } }`\n * - `{ $type: \"blob\", ref: { $link: \"...\" }, mimeType, size }`\n */\nfunction extractLinkFromObject(obj: Record<string, unknown>): string | null {\n const directLink = obj['$link'];\n if (typeof directLink === 'string' && directLink.length > 0) {\n return directLink;\n }\n const ref = obj['ref'];\n if (ref && typeof ref === 'object') {\n const nestedLink = (ref as Record<string, unknown>)['$link'];\n if (typeof nestedLink === 'string' && nestedLink.length > 0) {\n return nestedLink;\n }\n }\n return null;\n}\n\nfunction safeUrl(input: string): URL | null {\n try {\n return new URL(input);\n } catch {\n return null;\n }\n}\n","/**\n * F4: Nested $type injection for AT Protocol records.\n *\n * STATUS: Planned — not yet wired into the execute flow.\n *\n * PROBLEM\n * -------\n * When the resource mapper flattens refs/single-ref unions into dotted\n * fields (e.g. `theme.background.r`), `unflattenDottedKeys` reconstructs\n * nested objects — but those objects lack the `$type` discriminators that\n * AT Protocol requires for union-typed sub-objects.\n *\n * Currently sent:\n * { \"theme\": { \"background\": { \"r\": 255, \"g\": 255, \"b\": 255 } } }\n *\n * Expected:\n * { \"theme\": { \"$type\": \"site.standard.theme.basic\",\n * \"background\": { \"$type\": \"site.standard.theme.color#rgb\",\n * \"r\": 255, \"g\": 255, \"b\": 255 } } }\n *\n * No special-casing for any value types — only plain objects get $type\n * injected. Strings, numbers, arrays, etc. pass through unchanged.\n *\n * APPROACH\n * -------\n * A new `injectNestedTypes()` function walks the unflattened record\n * alongside the resolved lexicon schema. For each nested object whose\n * schema property is a `ref` or single-ref `union`, it injects `$type`\n * with the ref target NSID, then recurses into the resolved ref's schema.\n *\n * This mirrors the recursive resolution in `flattenRefProperties` but\n * operates on values instead of field definitions.\n *\n * INTEGRATION POINT\n * -----------------\n * Called in the execute flow (Atproto.node.ts) after `applyBlobUploads`:\n *\n * const record = buildRecordFromNodeParams(recordData);\n * const schema = await resolveLexiconSchema(agent, collection);\n * const withBlobs = await applyBlobUploads(record, schema, agent, i, this);\n * const withTypes = await injectNestedTypes(withBlobs, schema, agent);\n * // → use withTypes for createRecord/putRecord\n *\n * EDGE CASES\n * ----------\n * - User-provided $type is preserved (never overwritten)\n * - Array values are skipped (user provides full JSON for arrays of objects)\n * - Inline object properties (not from refs) don't get $type\n * - Recursion capped at MAX_REF_DEPTH (3) to match field generation\n * - Resolution failures are silently skipped (object works without $type\n * on lenient PDS implementations)\n *\n * DEPENDENCIES\n * ------------\n * - `getResolvableRef()` — export from fieldMapping.ts (currently private)\n * - `resolveRefProperties()` + `resolveLexiconSchema()` from lexicon.ts\n * - Schema cache ensures no redundant network calls at runtime\n */\n\nimport type { Agent } from '@atproto/api';\nimport type { LexiconSchema } from './lexicon';\nimport { resolveLexiconSchema, resolveRefProperties } from './lexicon';\nimport { getResolvableRef } from './fieldMapping';\n\nconst MAX_REF_DEPTH = 3;\n\n/**\n * Walk a record and inject `$type` on nested objects that correspond to\n * resolved refs or single-ref unions in the lexicon schema.\n *\n * @param record - The constructed record (after unflatten + blob uploads).\n * @param schema - The resolved lexicon schema (null → pass-through).\n * @param agent - Authenticated agent (for cross-document ref resolution).\n * @returns A new record with `$type` injected on nested ref objects.\n */\nexport async function injectNestedTypes(\n record: Record<string, unknown>,\n schema: LexiconSchema | null,\n agent: Agent,\n): Promise<Record<string, unknown>> {\n if (!schema) return record;\n return walkAndInject(record, schema.properties, schema, agent, 0);\n}\n\n/**\n * Recursive walker — for each property that is a ref/single-ref union,\n * inject `$type` on the value object and recurse into the resolved schema.\n */\nasync function walkAndInject(\n obj: Record<string, unknown>,\n properties: Record<string, import('./lexicon').LexiconProperty>,\n rootSchema: LexiconSchema,\n agent: Agent,\n depth: number,\n): Promise<Record<string, unknown>> {\n if (depth >= MAX_REF_DEPTH) return obj;\n\n const result = { ...obj };\n\n for (const [key, value] of Object.entries(result)) {\n const propDef = properties[key];\n if (!propDef) continue;\n\n const refTarget = getResolvableRef(propDef);\n if (!refTarget) continue;\n\n // Only process plain objects (not arrays, primitives, null)\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n continue;\n }\n\n const nested = { ...(value as Record<string, unknown>) };\n\n // Inject $type if user hasn't already provided one\n if (!nested['$type']) {\n nested['$type'] = refTarget;\n }\n\n // Resolve the ref to get sub-property definitions and recurse\n const resolved = await resolveRefProperties(\n refTarget,\n rootSchema,\n (nsid: string) => resolveLexiconSchema(agent, nsid),\n );\n\n if (resolved && Object.keys(resolved.properties).length > 0) {\n result[key] = await walkAndInject(\n nested,\n resolved.properties,\n rootSchema,\n agent,\n depth + 1,\n );\n } else {\n result[key] = nested;\n }\n }\n\n return result;\n}\n","/**\n * Execution-time record validation against the lexicon schema.\n *\n * Runs before the record is sent to the PDS, providing clear field-level\n * error messages instead of the PDS's opaque \"InvalidRecord\" rejections.\n *\n * Checks:\n * - Required fields are present\n * - Basic type correctness (string, integer, boolean, array, object)\n * - $type discriminator on union-typed sub-objects\n * - Recursion into nested refs (up to depth 3)\n */\n\nimport type { Agent } from '@atproto/api';\nimport type { LexiconProperty, LexiconSchema } from './lexicon';\nimport { resolveLexiconSchema, resolveRefProperties } from './lexicon';\nimport { getResolvableRef } from './fieldMapping';\n\nconst MAX_DEPTH = 3;\n\n/** Auto-injected fields — skip required checks for these. */\nconst AUTO_INJECTED = new Set(['$type', 'createdAt']);\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Validate a record against its lexicon schema.\n *\n * @returns An array of human-readable error strings. Empty = valid.\n */\nexport async function validateRecord(\n record: Record<string, unknown>,\n schema: LexiconSchema | null,\n agent: Agent,\n): Promise<string[]> {\n if (!schema || Object.keys(schema.properties).length === 0) return [];\n\n return walkAndValidate(\n record,\n schema.properties,\n schema.required,\n schema,\n agent,\n '',\n 0,\n );\n}\n\n// ---------------------------------------------------------------------------\n// Recursive walker\n// ---------------------------------------------------------------------------\n\nasync function walkAndValidate(\n obj: Record<string, unknown>,\n properties: Record<string, LexiconProperty>,\n required: string[],\n rootSchema: LexiconSchema,\n agent: Agent,\n prefix: string,\n depth: number,\n): Promise<string[]> {\n const errors: string[] = [];\n\n // 1. Required fields\n for (const name of required) {\n if (AUTO_INJECTED.has(name)) continue;\n const path = prefix ? `${prefix}.${name}` : name;\n const value = obj[name];\n const prop = properties[name];\n // Nullable required fields accept explicit null\n if (value === null && prop?.nullable) continue;\n if (value === undefined || value === null || value === '') {\n errors.push(`Required field '${path}' is missing`);\n }\n }\n\n // 2. Type & structure checks on provided fields\n for (const [name, value] of Object.entries(obj)) {\n if (name === '$type') continue;\n const path = prefix ? `${prefix}.${name}` : name;\n const prop = properties[name];\n if (!prop || value === undefined || value === null) continue;\n\n errors.push(...checkType(path, value, prop));\n\n // 3. Recurse into nested objects that came from refs/unions\n if (\n depth < MAX_DEPTH &&\n typeof value === 'object' &&\n value !== null &&\n !Array.isArray(value)\n ) {\n const refTarget = getResolvableRef(prop);\n if (refTarget) {\n const resolved = await resolveRefProperties(\n refTarget,\n rootSchema,\n (nsid: string) => resolveLexiconSchema(agent, nsid),\n );\n if (resolved && Object.keys(resolved.properties).length > 0) {\n const nested = await walkAndValidate(\n value as Record<string, unknown>,\n resolved.properties,\n resolved.required,\n rootSchema,\n agent,\n path,\n depth + 1,\n );\n errors.push(...nested);\n }\n }\n }\n }\n\n return errors;\n}\n\n// ---------------------------------------------------------------------------\n// Type checks\n// ---------------------------------------------------------------------------\n\nfunction checkType(\n path: string,\n value: unknown,\n prop: LexiconProperty,\n): string[] {\n const errors: string[] = [];\n\n switch (prop.type) {\n case 'string':\n if (typeof value !== 'string') {\n errors.push(`'${path}' must be a string, got ${describeType(value)}`);\n break;\n }\n // UTF-8 byte length constraints\n if (prop.maxLength) {\n const byteLen = Buffer.byteLength(value, 'utf8');\n if (byteLen > prop.maxLength)\n errors.push(`'${path}' is ${byteLen} bytes (max ${prop.maxLength})`);\n }\n if (prop.minLength) {\n const byteLen = Buffer.byteLength(value, 'utf8');\n if (byteLen < prop.minLength)\n errors.push(`'${path}' is ${byteLen} bytes (min ${prop.minLength})`);\n }\n // Grapheme count (Intl.Segmenter, Node 16+)\n if (prop.maxGraphemes || prop.minGraphemes) {\n const segmenter = new Intl.Segmenter();\n const count = [...segmenter.segment(value)].length;\n if (prop.maxGraphemes && count > prop.maxGraphemes)\n errors.push(`'${path}' has ${count} graphemes (max ${prop.maxGraphemes})`);\n if (prop.minGraphemes && count < prop.minGraphemes)\n errors.push(`'${path}' has ${count} graphemes (min ${prop.minGraphemes})`);\n }\n // Closed enum\n if (prop.enum?.length && !prop.enum.includes(value))\n errors.push(`'${path}' must be one of: ${prop.enum.join(', ')}`);\n // Const\n if (prop.const !== undefined && value !== prop.const)\n errors.push(`'${path}' must be '${prop.const}'`);\n break;\n\n case 'integer':\n if (typeof value !== 'number' || !Number.isInteger(value)) {\n errors.push(`'${path}' must be an integer, got ${describeType(value)}`);\n break;\n }\n if (prop.minimum !== undefined && value < prop.minimum)\n errors.push(`'${path}' must be ≥ ${prop.minimum}, got ${value}`);\n if (prop.maximum !== undefined && value > prop.maximum)\n errors.push(`'${path}' must be ≤ ${prop.maximum}, got ${value}`);\n if (prop.enum?.length && !prop.enum.includes(value))\n errors.push(`'${path}' must be one of: ${prop.enum.join(', ')}`);\n if (prop.const !== undefined && value !== prop.const)\n errors.push(`'${path}' must be ${prop.const}`);\n break;\n\n case 'boolean':\n if (typeof value !== 'boolean') {\n errors.push(\n `'${path}' must be true or false, got ${describeType(value)}`,\n );\n break;\n }\n if (prop.const !== undefined && value !== prop.const)\n errors.push(`'${path}' must be ${prop.const}`);\n break;\n\n case 'array':\n if (!Array.isArray(value)) {\n errors.push(\n `'${path}' must be a JSON array, got ${describeType(value)}`,\n );\n break;\n }\n if (prop.maxLength !== undefined && value.length > prop.maxLength)\n errors.push(`'${path}' has ${value.length} items (max ${prop.maxLength})`);\n if (prop.minLength !== undefined && value.length < prop.minLength)\n errors.push(`'${path}' has ${value.length} items (min ${prop.minLength})`);\n // Array item validation\n if (prop.items && prop.items.type !== 'unknown') {\n for (let idx = 0; idx < value.length; idx++) {\n const itemErrors = checkType(`${path}[${idx}]`, value[idx], prop.items);\n errors.push(...itemErrors);\n }\n }\n break;\n\n case 'union':\n if (\n typeof value === 'object' &&\n value !== null &&\n !Array.isArray(value)\n ) {\n const obj = value as Record<string, unknown>;\n if (!obj['$type'] && prop.refs?.length) {\n errors.push(\n `'${path}' requires a $type property (one of: ${prop.refs.join(', ')})`,\n );\n } else if (\n prop.refs?.length &&\n obj['$type'] &&\n !prop.refs.includes(obj['$type'] as string)\n ) {\n // Closed unions: explicit error mentioning \"closed union\"\n if (prop.closed) {\n errors.push(\n `'${path}' has $type '${obj['$type']}' which is not allowed in this closed union (expected: ${prop.refs.join(', ')})`,\n );\n } else {\n errors.push(\n `'${path}' has invalid $type '${obj['$type']}' — expected one of: ${prop.refs.join(', ')}`,\n );\n }\n }\n }\n break;\n\n case 'ref':\n case 'object':\n case 'unknown':\n if (\n typeof value !== 'object' ||\n value === null ||\n Array.isArray(value)\n ) {\n errors.push(\n `'${path}' must be a JSON object, got ${describeType(value)}`,\n );\n }\n break;\n\n case 'blob':\n // After applyBlobUploads, blobs should be { $type: 'blob', ref, ... }.\n // If it's still a string, the upload didn't happen (bad binary property name).\n if (typeof value === 'string') {\n errors.push(\n `'${path}' — binary property '${value}' was not found on the input. Attach a file via an HTTP Request or Read Binary File node.`,\n );\n }\n break;\n }\n\n return errors;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction describeType(value: unknown): string {\n if (value === null) return 'null';\n if (Array.isArray(value)) return 'array';\n return typeof value;\n}\n","import type {\n IDataObject,\n IExecuteFunctions,\n ILoadOptionsFunctions,\n INodeExecutionData,\n INodeType,\n INodeTypeDescription,\n ResourceMapperFields,\n} from 'n8n-workflow';\nimport { ApplicationError, NodeConnectionTypes, NodeOperationError } from 'n8n-workflow';\nimport type { Agent } from '@atproto/api';\n\nimport type { ListRecordsResult } from './operations';\nimport {\n createRecord,\n deleteRecord,\n getRecord,\n getBlob,\n listBlobs,\n listRecords,\n putRecord,\n resolveActorToDid,\n uploadBlob,\n applyConstValues,\n} from './operations';\n\n// Pagination guardrails for Return All. The page sizes match what each\n// XRPC endpoint accepts as a sensible upper bound; MAX_PAGES caps total\n// requests so a mis-configured workflow can't loop forever.\nconst PAGE_SIZE_RECORDS = 100;\nconst PAGE_SIZE_BLOBS = 500;\nconst MAX_PAGES = 1000;\nimport { createAgent, extractCollectionNsid, searchCollections } from './shared';\nimport { generateTid } from './tid';\nimport { resolveLexiconSchema } from './lexicon';\nimport { lexiconToResourceMapperFields } from './fieldMapping';\nimport { applyBlobUploads } from './blob';\nimport { parseBlobReference } from './blobInput';\nimport { injectNestedTypes } from './typeInjection';\nimport { validateRecord } from './validation';\n\n// ---------------------------------------------------------------------------\n// Error handling\n// ---------------------------------------------------------------------------\n\n/**\n * Maps XRPC error messages to user-friendly descriptions.\n */\nfunction friendlyError(error: unknown, context?: Record<string, string>): string {\n const message = error instanceof Error ? error.message : String(error);\n const status =\n error && typeof error === 'object' && 'status' in error\n ? (error as { status: number }).status\n : 0;\n\n if (status === 401 || status === 403 || message.includes('Authentication')) {\n return 'Authentication failed — check your app password';\n }\n if (message.includes('AccountTakedown') || message.includes('takendown')) {\n return 'Account is suspended';\n }\n if (message.includes('RecordNotFound')) {\n const where = context?.rkey\n ? `${context.collection ?? '?'}/${context.rkey}`\n : context?.collection ?? '?';\n return `Record not found at ${where}`;\n }\n if (message.includes('BlobNotFound')) {\n const where =\n context?.cid && context?.did\n ? `${context.did}/${context.cid}`\n : context?.cid ?? '?';\n return `Blob not found at ${where}`;\n }\n if (\n status === 413 ||\n message.includes('PayloadTooLarge') ||\n /blob too large/i.test(message)\n ) {\n return 'Blob too large — the PDS rejected the upload (bsky.social limits blobs to ~1 MB)';\n }\n if (status === 429 || message.includes('RateLimit')) {\n const match = message.match(/retry.*?(\\d+)/i);\n const seconds = match ? match[1] : '?';\n return `Rate limited — retry after ${seconds}s`;\n }\n if (\n message.includes('fetch failed') ||\n message.includes('network') ||\n message.includes('ECONNREFUSED') ||\n message.includes('ENOTFOUND')\n ) {\n const url = context?.serviceUrl ?? 'PDS';\n return `Could not reach ${url}`;\n }\n if (message.includes('InvalidRecord') || message.includes('invalid record')) {\n return `Record validation failed: ${message}`;\n }\n return message;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve the record key for get/put/delete operations.\n *\n * When the user leaves rkey empty, attempts to resolve the lexicon\n * schema and use the literal key (e.g. `self` for `app.bsky.actor.profile`).\n * Throws if the key can’t be determined.\n */\nasync function resolveRkey(\n rkey: string,\n collection: string,\n agent: Agent,\n): Promise<string> {\n if (rkey) return rkey;\n\n const schema = await resolveLexiconSchema(agent, collection);\n if (schema?.key === 'literal' && schema.literalKey) {\n return schema.literalKey;\n }\n\n throw new ApplicationError(\n `Record key is required for ${collection}. The lexicon does not declare a fixed key.`,\n );\n}\n\n// ---------------------------------------------------------------------------\n// Node\n// ---------------------------------------------------------------------------\n\nexport class Atproto implements INodeType {\n description: INodeTypeDescription = {\n displayName: 'AT Protocol',\n name: 'atproto',\n icon: 'file:atproto.svg',\n group: ['transform'],\n version: 1,\n subtitle:\n '={{ $parameter[\"operation\"] + \": \" + $parameter[\"resource\"] }}',\n description: 'CRUD records and manage blobs in any AT Protocol repo',\n defaults: {\n name: 'AT Protocol',\n },\n usableAsTool: true,\n inputs: [NodeConnectionTypes.Main],\n outputs: [NodeConnectionTypes.Main],\n credentials: [\n {\n name: 'atprotoApi',\n required: true,\n },\n ],\n properties: [\n // ------------------------------------------------------------------\n // Resource\n // ------------------------------------------------------------------\n {\n displayName: 'Resource',\n name: 'resource',\n type: 'options',\n noDataExpression: true,\n // Sorted alphabetically per @n8n/community-nodes lint rule.\n options: [\n { name: 'Blob', value: 'blob' },\n { name: 'Record', value: 'record' },\n ],\n default: 'record',\n },\n\n // ------------------------------------------------------------------\n // Operation — Record\n // ------------------------------------------------------------------\n {\n displayName: 'Operation',\n name: 'operation',\n type: 'options',\n noDataExpression: true,\n displayOptions: {\n show: { resource: ['record'] },\n },\n // Sorted alphabetically per @n8n/community-nodes lint rule.\n options: [\n {\n name: 'Create',\n value: 'createRecord',\n description: 'Create a new record in a collection',\n action: 'Create a record',\n },\n {\n name: 'Delete',\n value: 'deleteRecord',\n description: 'Delete a record by collection and record key',\n action: 'Delete a record',\n },\n {\n name: 'Get',\n value: 'getRecord',\n description: 'Get a record by collection and record key',\n action: 'Get a record',\n },\n {\n name: 'List',\n value: 'listRecords',\n description: 'List records in a collection with pagination',\n action: 'List records',\n },\n {\n name: 'Put',\n value: 'putRecord',\n description: 'Full-replace a record',\n action: 'Update a record',\n },\n ],\n default: 'createRecord',\n },\n\n // ------------------------------------------------------------------\n // Operation — Blob\n // ------------------------------------------------------------------\n {\n displayName: 'Operation',\n name: 'operation',\n type: 'options',\n noDataExpression: true,\n displayOptions: {\n show: { resource: ['blob'] },\n },\n // Sorted alphabetically per @n8n/community-nodes lint rule.\n options: [\n {\n name: 'Download',\n value: 'getBlob',\n description: 'Download a blob by CID from a repo',\n action: 'Download a blob',\n },\n {\n name: 'List',\n value: 'listBlobs',\n description: 'List blob CIDs in a repo with pagination',\n action: 'List blobs',\n },\n {\n name: 'Upload',\n value: 'uploadBlob',\n description: 'Upload a binary as a blob to the PDS',\n action: 'Upload a blob',\n },\n ],\n default: 'uploadBlob',\n },\n\n // ------------------------------------------------------------------\n // Collection (resourceLocator — searchable list + free text)\n // ------------------------------------------------------------------\n {\n displayName: 'Collection',\n name: 'collection',\n type: 'resourceLocator',\n required: true,\n description: 'The record collection to operate on',\n default: { mode: 'list', value: '' },\n displayOptions: {\n show: {\n operation: [\n 'createRecord',\n 'getRecord',\n 'putRecord',\n 'deleteRecord',\n 'listRecords',\n ],\n },\n },\n modes: [\n {\n displayName: 'From List',\n name: 'list',\n type: 'list',\n placeholder: 'Select a collection…',\n typeOptions: {\n searchListMethod: 'searchCollections',\n searchable: true,\n },\n },\n {\n displayName: 'By NSID',\n name: 'nsid',\n type: 'string',\n placeholder: 'e.g. app.bsky.feed.post',\n validation: [\n {\n type: 'regex',\n properties: {\n regex: '^[a-z][a-z0-9]*(\\\\.[a-zA-Z][a-zA-Z0-9]*){2,}$',\n errorMessage: 'Must be a valid NSID (e.g. app.bsky.feed.post)',\n },\n },\n ],\n },\n ],\n },\n\n // ------------------------------------------------------------------\n // Repo (for Get/List Record + List Blobs — optional, defaults to self)\n // ------------------------------------------------------------------\n {\n displayName: 'Repo (DID or Handle)',\n name: 'repo',\n type: 'string',\n placeholder: 'did:plc:... or user.bsky.social',\n description:\n 'Optional. The DID or handle of the repo. Defaults to the authenticated user. Useful for reading other users\\' public records or blobs.',\n displayOptions: {\n show: {\n operation: ['getRecord', 'listRecords', 'listBlobs'],\n },\n },\n default: '',\n },\n\n // ------------------------------------------------------------------\n // Repo (for Download Blob — can be DID or handle; optional when the\n // CID field is a bsky CDN URL with the DID embedded)\n // ------------------------------------------------------------------\n {\n displayName: 'Repo (DID or Handle)',\n name: 'repo',\n type: 'string',\n placeholder: 'did:plc:... or user.bsky.social',\n description:\n 'The DID or handle of the repo that owns the blob. Optional when the CID field is a bsky CDN URL — the DID is extracted from the URL.',\n displayOptions: {\n show: {\n operation: ['getBlob'],\n },\n },\n default: '',\n },\n\n // ------------------------------------------------------------------\n // CID / Blob reference (Download Blob only)\n // ------------------------------------------------------------------\n {\n displayName: 'Blob Reference',\n name: 'cid',\n type: 'string',\n required: true,\n placeholder:\n 'bafkreig... or https://cdn.bsky.app/img/.../<did>/<cid>@jpeg',\n description:\n 'A bare CID, a BlobRef JSON (e.g. `{\"$link\":\"bafkreig...\"}`), or a bsky CDN URL. CDN URLs also fill in the Repo.',\n displayOptions: {\n show: {\n operation: ['getBlob'],\n },\n },\n default: '',\n },\n\n // ------------------------------------------------------------------\n // Binary Property (Upload Blob — input, Download Blob — output)\n // ------------------------------------------------------------------\n {\n displayName: 'Input Binary Property',\n name: 'binaryPropertyName',\n type: 'string',\n required: true,\n default: 'data',\n placeholder: 'data',\n description:\n 'Name of the binary property on the incoming item containing the data to upload',\n displayOptions: {\n show: {\n operation: ['uploadBlob'],\n },\n },\n },\n {\n displayName: 'Output Binary Property',\n name: 'binaryPropertyName',\n type: 'string',\n required: true,\n default: 'data',\n placeholder: 'data',\n description:\n 'Name of the binary property to write the downloaded blob to on the output item',\n displayOptions: {\n show: {\n operation: ['getBlob'],\n },\n },\n },\n\n // ------------------------------------------------------------------\n // Upload Blob — advanced options\n // ------------------------------------------------------------------\n {\n displayName: 'Options',\n name: 'options',\n type: 'collection',\n placeholder: 'Add Option',\n default: {},\n displayOptions: {\n show: {\n operation: ['uploadBlob'],\n },\n },\n options: [\n {\n displayName: 'MIME Type Override',\n name: 'mimeTypeOverride',\n type: 'string',\n default: '',\n placeholder: 'image/jpeg',\n description:\n 'Override the MIME type sent to the PDS. Defaults to the binary metadata\\'s mimeType, or application/octet-stream if unset.',\n },\n ],\n },\n\n // ------------------------------------------------------------------\n // List Blobs — advanced options\n // ------------------------------------------------------------------\n {\n displayName: 'Options',\n name: 'options',\n type: 'collection',\n placeholder: 'Add Option',\n default: {},\n displayOptions: {\n show: {\n operation: ['listBlobs'],\n },\n },\n options: [\n {\n displayName: 'Since (Repo Revision)',\n name: 'since',\n type: 'string',\n default: '',\n placeholder: '3jzfc...',\n description:\n 'Only list blobs added after this repo revision. Useful for incremental sync.',\n },\n ],\n },\n\n // ------------------------------------------------------------------\n // Record Key — shown for Get/Put/Delete\n // ------------------------------------------------------------------\n {\n displayName: 'Record Key (Rkey)',\n name: 'rkey',\n type: 'string',\n placeholder: '3jzfcijpj2z2a',\n description:\n 'The record key. Leave empty for collections that use a fixed key (e.g. app.bsky.actor.profile always uses \"self\").',\n displayOptions: {\n show: {\n operation: ['getRecord', 'putRecord', 'deleteRecord'],\n },\n },\n default: '',\n },\n\n // ------------------------------------------------------------------\n // Record Key — Create mode (auto TID or custom)\n // ------------------------------------------------------------------\n {\n displayName: 'Record Key',\n name: 'rkeyMode',\n type: 'options',\n options: [\n {\n name: 'Auto-Generate (TID)',\n value: 'auto',\n description: 'Generate a timestamp-based record key (TID) automatically',\n },\n {\n name: 'Custom',\n value: 'custom',\n description: 'Provide a custom record key',\n },\n ],\n displayOptions: {\n show: {\n operation: ['createRecord'],\n },\n },\n default: 'auto',\n },\n {\n displayName: 'Custom Record Key',\n name: 'rkey',\n type: 'string',\n required: true,\n placeholder: 'my-custom-key',\n description: 'A custom record key (rkey)',\n displayOptions: {\n show: {\n operation: ['createRecord'],\n rkeyMode: ['custom'],\n },\n },\n default: '',\n },\n\n // ------------------------------------------------------------------\n // Record Data — resourceMapper for Create/Put (Phase 2)\n // Falls back to JSON when lexicon cannot be resolved.\n // ------------------------------------------------------------------\n {\n displayName: 'Record Data',\n name: 'recordData',\n type: 'resourceMapper',\n default: {\n mappingMode: 'defineBelow',\n value: null,\n },\n required: true,\n typeOptions: {\n resourceMapper: {\n resourceMapperMethod: 'getRecordFields',\n mode: 'add',\n fieldWords: {\n singular: 'field',\n plural: 'fields',\n },\n supportAutoMap: true,\n noFieldsError:\n 'Could not resolve lexicon for this NSID. Enter record data as JSON using an expression, or check the collection NSID.',\n },\n loadOptionsDependsOn: ['collection.value'],\n },\n displayOptions: {\n show: {\n operation: ['createRecord', 'putRecord'],\n },\n },\n },\n\n // ------------------------------------------------------------------\n // Options (advanced / less-common fields)\n // ------------------------------------------------------------------\n {\n displayName: 'Options',\n name: 'options',\n type: 'collection',\n placeholder: 'Add Option',\n default: {},\n displayOptions: {\n show: {\n operation: ['createRecord', 'putRecord', 'deleteRecord'],\n },\n },\n options: [\n {\n displayName: 'Swap Commit (CID)',\n name: 'swapCommit',\n type: 'string',\n default: '',\n placeholder: 'bafyreia...',\n description:\n 'Compare-and-swap with the current commit CID. The write is rejected if the repo head does not match.',\n },\n ],\n },\n\n // ------------------------------------------------------------------\n // Return All (List Records / List Blobs)\n // ------------------------------------------------------------------\n {\n displayName: 'Return All',\n name: 'returnAll',\n type: 'boolean',\n default: false,\n description: 'Whether to return all results or only up to a given limit',\n displayOptions: {\n show: {\n operation: ['listRecords', 'listBlobs'],\n },\n },\n },\n\n // ------------------------------------------------------------------\n // Limit (only when Return All is off)\n // ------------------------------------------------------------------\n {\n displayName: 'Limit',\n name: 'limit',\n type: 'number',\n typeOptions: {\n minValue: 1,\n maxValue: 1000,\n },\n displayOptions: {\n show: {\n operation: ['listRecords', 'listBlobs'],\n returnAll: [false],\n },\n },\n default: 50,\n description: 'Max number of results to return',\n },\n\n // ------------------------------------------------------------------\n // Cursor (only when Return All is off)\n // ------------------------------------------------------------------\n {\n displayName: 'Cursor',\n name: 'cursor',\n type: 'string',\n placeholder: '...',\n description:\n 'Optional. Cursor for pagination. Pass the cursor from a previous response to get the next page.',\n displayOptions: {\n show: {\n operation: ['listRecords', 'listBlobs'],\n returnAll: [false],\n },\n },\n default: '',\n },\n ],\n };\n\n // -----------------------------------------------------------------------\n // Resource mapper method — called in the n8n editor to resolve fields\n // -----------------------------------------------------------------------\n methods = {\n listSearch: {\n searchCollections,\n },\n resourceMapping: {\n getRecordFields: async function (\n this: ILoadOptionsFunctions,\n ): Promise<ResourceMapperFields> {\n const nsid = extractCollectionNsid(\n this.getNodeParameter('collection'),\n );\n\n if (!nsid) {\n return { fields: [] };\n }\n\n // Try to get credentials for PDS-based resolution\n let agent: Agent | null = null;\n try {\n const credentials = await this.getCredentials('atprotoApi');\n if (credentials) {\n agent = await createAgent(credentials as IDataObject);\n }\n } catch {\n // Credentials not available — fall back to DNS-based resolution\n agent = null;\n }\n\n const schema = await resolveLexiconSchema(agent, nsid);\n\n if (!schema) {\n // Cannot resolve — return empty fields. n8n will show a warning\n // and the user can switch to JSON mode or provide a raw JSON value\n // via an expression.\n return { fields: [] };\n }\n\n const fields = await lexiconToResourceMapperFields(schema, agent);\n return { fields };\n },\n },\n };\n\n // -----------------------------------------------------------------------\n // Execute — called at workflow runtime\n // -----------------------------------------------------------------------\n async execute(this: IExecuteFunctions) {\n const items = this.getInputData();\n const returnData: INodeExecutionData[] = [];\n\n // Get credentials once — shared across all items in this execution\n const credentials = await this.getCredentials('atprotoApi');\n const agent = await createAgent(credentials as IDataObject);\n\n // Process each input item\n for (let i = 0; i < items.length; i++) {\n try {\n const operation = this.getNodeParameter('operation', i) as string;\n // `collection` is hidden via displayOptions for blob operations\n // (uploadBlob / getBlob / listBlobs). Pass an empty resourceLocator\n // default so getNodeParameter doesn't throw \"Could not get parameter\"\n // — blob ops never use the value anyway.\n const collection = extractCollectionNsid(\n this.getNodeParameter('collection', i, { mode: 'list', value: '' }),\n );\n\n let result: IDataObject;\n\n switch (operation) {\n case 'createRecord': {\n const rkeyMode = this.getNodeParameter('rkeyMode', i) as string;\n const rkey =\n rkeyMode === 'custom'\n ? (this.getNodeParameter('rkey', i) as string)\n : generateTid();\n const recordData = this.getNodeParameter('recordData', i);\n const record = buildRecordFromNodeParams(recordData);\n const opts = this.getNodeParameter('options', i, {}) as IDataObject;\n const swapCommit = (opts.swapCommit as string) ?? '';\n\n // Resolve schema once for all downstream steps\n const schema = await resolveLexiconSchema(agent, collection);\n\n // Phase 5: inject const values from schema\n applyConstValues(record, schema);\n\n // Phase 3: upload blobs referenced by binary property names\n const recordWithBlobs = await applyBlobUploads(\n record,\n schema,\n agent,\n i,\n this,\n );\n\n // Phase 4: inject $type on nested ref/union objects\n const recordWithTypes = await injectNestedTypes(\n recordWithBlobs,\n schema,\n agent,\n );\n\n // Phase 5: validate before sending\n const createErrors = await validateRecord(\n recordWithTypes,\n schema,\n agent,\n );\n if (createErrors.length > 0) {\n throw new NodeOperationError(\n this.getNode(),\n `Record validation failed:\\n• ${createErrors.join('\\n• ')}`,\n { itemIndex: i },\n );\n }\n\n const res = await createRecord(agent, {\n collection,\n rkey,\n record: recordWithTypes,\n ...(swapCommit ? { swapCommit } : {}),\n });\n result = res as unknown as IDataObject;\n break;\n }\n\n case 'getRecord': {\n const rkey = await resolveRkey(\n this.getNodeParameter('rkey', i) as string,\n collection,\n agent,\n );\n const repo = this.getNodeParameter('repo', i) as string;\n\n const res = await getRecord(agent, {\n collection,\n rkey,\n ...(repo ? { repo } : {}),\n });\n result = res as unknown as IDataObject;\n break;\n }\n\n case 'putRecord': {\n const rkey = await resolveRkey(\n this.getNodeParameter('rkey', i) as string,\n collection,\n agent,\n );\n const recordData = this.getNodeParameter('recordData', i);\n const record = buildRecordFromNodeParams(recordData);\n const putOpts = this.getNodeParameter('options', i, {}) as IDataObject;\n const swapCommit = (putOpts.swapCommit as string) ?? '';\n\n // Resolve schema once for all downstream steps\n const schema = await resolveLexiconSchema(agent, collection);\n\n // Phase 5: inject const values from schema\n applyConstValues(record, schema);\n\n // Phase 3: upload blobs referenced by binary property names\n const recordWithBlobs = await applyBlobUploads(\n record,\n schema,\n agent,\n i,\n this,\n );\n\n // Phase 4: inject $type on nested ref/union objects\n const recordWithTypes = await injectNestedTypes(\n recordWithBlobs,\n schema,\n agent,\n );\n\n // Phase 5: validate before sending\n const putErrors = await validateRecord(\n recordWithTypes,\n schema,\n agent,\n );\n if (putErrors.length > 0) {\n throw new NodeOperationError(\n this.getNode(),\n `Record validation failed:\\n• ${putErrors.join('\\n• ')}`,\n { itemIndex: i },\n );\n }\n\n const res = await putRecord(agent, {\n collection,\n rkey,\n record: recordWithTypes,\n ...(swapCommit ? { swapCommit } : {}),\n });\n result = res as unknown as IDataObject;\n break;\n }\n\n case 'deleteRecord': {\n const rkey = await resolveRkey(\n this.getNodeParameter('rkey', i) as string,\n collection,\n agent,\n );\n const delOpts = this.getNodeParameter('options', i, {}) as IDataObject;\n const swapCommit = (delOpts.swapCommit as string) ?? '';\n\n const res = await deleteRecord(agent, {\n collection,\n rkey,\n ...(swapCommit ? { swapCommit } : {}),\n });\n result = (res ?? { success: true }) as unknown as IDataObject;\n break;\n }\n\n case 'listRecords': {\n const returnAll = this.getNodeParameter(\n 'returnAll',\n i,\n false,\n ) as boolean;\n const repo = this.getNodeParameter('repo', i) as string;\n\n if (returnAll) {\n // Paginate internally. Concatenate all records across pages.\n const allRecords: ListRecordsResult['records'] = [];\n let pageCursor: string | undefined = undefined;\n for (let page = 0; page < MAX_PAGES; page++) {\n const pageRes = await listRecords(agent, {\n collection,\n limit: PAGE_SIZE_RECORDS,\n ...(pageCursor ? { cursor: pageCursor } : {}),\n ...(repo ? { repo } : {}),\n });\n allRecords.push(...pageRes.records);\n if (!pageRes.cursor) break;\n pageCursor = pageRes.cursor;\n }\n result = { records: allRecords } as unknown as IDataObject;\n } else {\n const limit = this.getNodeParameter('limit', i) as number;\n const cursor = this.getNodeParameter('cursor', i) as string;\n const res = await listRecords(agent, {\n collection,\n limit,\n ...(cursor ? { cursor } : {}),\n ...(repo ? { repo } : {}),\n });\n result = res as unknown as IDataObject;\n }\n break;\n }\n\n case 'uploadBlob': {\n const binaryPropertyName = this.getNodeParameter(\n 'binaryPropertyName',\n i,\n ) as string;\n const opts = this.getNodeParameter('options', i, {}) as {\n mimeTypeOverride?: string;\n };\n\n const buffer = await this.helpers.getBinaryDataBuffer(\n i,\n binaryPropertyName,\n );\n if (!buffer) {\n throw new NodeOperationError(\n this.getNode(),\n `Binary property \"${binaryPropertyName}\" not found on input item`,\n { itemIndex: i },\n );\n }\n\n const binaryMeta = items[i].binary?.[binaryPropertyName];\n const mimeType =\n opts.mimeTypeOverride?.trim() ||\n binaryMeta?.mimeType ||\n 'application/octet-stream';\n\n const res = await uploadBlob(agent, {\n data: buffer,\n mimeType,\n });\n // Flat sibling fields for ergonomic expression access:\n // `{{ $json.cid }}` instead of `{{ $json.blob.ref.$link }}`.\n result = {\n blob: res.blob,\n cid: res.blob.ref.$link,\n mimeType: res.blob.mimeType,\n size: res.blob.size,\n } as unknown as IDataObject;\n break;\n }\n\n case 'getBlob': {\n const repoInput = this.getNodeParameter('repo', i) as string;\n const blobInput = this.getNodeParameter('cid', i) as string;\n const binaryPropertyName = this.getNodeParameter(\n 'binaryPropertyName',\n i,\n ) as string;\n\n // Parse the blob reference (bare CID / JSON ref / CDN URL).\n // CDN URLs may carry a DID that we use when Repo is empty.\n let parsed;\n try {\n parsed = parseBlobReference(blobInput);\n } catch (err) {\n throw new NodeOperationError(\n this.getNode(),\n err instanceof Error ? err.message : String(err),\n { itemIndex: i },\n );\n }\n const { cid } = parsed;\n\n const repoSource = repoInput?.trim() || parsed.did || '';\n if (!repoSource) {\n throw new NodeOperationError(\n this.getNode(),\n 'Repo (DID or handle) is required when the Blob Reference does not contain a DID',\n { itemIndex: i },\n );\n }\n const did = await resolveActorToDid(agent, repoSource);\n\n try {\n const res = await getBlob(agent, { did, cid });\n\n const binaryData = await this.helpers.prepareBinaryData(\n res.data,\n cid,\n res.mimeType || undefined,\n );\n\n returnData.push({\n json: {\n cid,\n did,\n mimeType: res.mimeType,\n size: res.size,\n },\n binary: { [binaryPropertyName]: binaryData },\n pairedItem: { item: i },\n });\n } catch (err) {\n // Re-throw with cid/did context so friendlyError can produce\n // a useful 'Blob not found at <did>/<cid>' message.\n if (this.continueOnFail()) {\n returnData.push({\n json: {\n error: friendlyError(err, { cid, did }),\n ...(err instanceof Error ? { message: err.message } : {}),\n },\n pairedItem: { item: i },\n });\n continue;\n }\n throw new NodeOperationError(\n this.getNode(),\n friendlyError(err, { cid, did }),\n { itemIndex: i },\n );\n }\n continue;\n }\n\n case 'listBlobs': {\n const returnAll = this.getNodeParameter(\n 'returnAll',\n i,\n false,\n ) as boolean;\n const repoInput = this.getNodeParameter('repo', i) as string;\n const listOpts = this.getNodeParameter('options', i, {}) as {\n since?: string;\n };\n\n const did = repoInput\n ? await resolveActorToDid(agent, repoInput)\n : agent.did ?? undefined;\n\n if (returnAll) {\n // Paginate internally; emit one item per CID with no cursor.\n let pageCursor: string | undefined = undefined;\n for (let page = 0; page < MAX_PAGES; page++) {\n const pageRes = await listBlobs(agent, {\n ...(did ? { did } : {}),\n limit: PAGE_SIZE_BLOBS,\n ...(pageCursor ? { cursor: pageCursor } : {}),\n ...(listOpts.since ? { since: listOpts.since } : {}),\n });\n for (const blobCid of pageRes.cids) {\n returnData.push({\n json: {\n cid: blobCid,\n ...(did ? { did } : {}),\n },\n pairedItem: { item: i },\n });\n }\n if (!pageRes.cursor) break;\n pageCursor = pageRes.cursor;\n }\n } else {\n const limit = this.getNodeParameter('limit', i) as number;\n const cursor = this.getNodeParameter('cursor', i) as string;\n const res = await listBlobs(agent, {\n ...(did ? { did } : {}),\n limit,\n ...(cursor ? { cursor } : {}),\n ...(listOpts.since ? { since: listOpts.since } : {}),\n });\n\n // One item per CID; cursor attached to each so any downstream\n // node can drive the next page.\n for (const blobCid of res.cids) {\n returnData.push({\n json: {\n cid: blobCid,\n ...(did ? { did } : {}),\n ...(res.cursor ? { cursor: res.cursor } : {}),\n },\n pairedItem: { item: i },\n });\n }\n }\n continue;\n }\n\n default:\n throw new NodeOperationError(\n this.getNode(),\n `Unknown operation: ${operation}`,\n { itemIndex: i },\n );\n }\n\n returnData.push({\n json: result,\n pairedItem: { item: i },\n });\n } catch (error) {\n if (this.continueOnFail()) {\n returnData.push({\n json: {\n error: friendlyError(error),\n ...(error instanceof Error ? { message: error.message } : {}),\n },\n pairedItem: { item: i },\n });\n continue;\n }\n throw new NodeOperationError(\n this.getNode(),\n error instanceof Error ? error : new Error(String(error)),\n { itemIndex: i },\n );\n }\n }\n\n return [returnData];\n }\n}\n\n// ---------------------------------------------------------------------------\n// Record building helper\n// ---------------------------------------------------------------------------\n\n/**\n * Build a record object from node parameters.\n *\n * Handles three input formats:\n * 1. Raw JSON string (legacy / fallback)\n * 2. `resourceMapper` value object `{ mappingMode, value, ... }`\n * 3. Plain object (when called from an expression)\n *\n * For resourceMapper values, dotted keys produced by ref/object flattening\n * (e.g. `reply.root`, `reply.parent`) are un-flattened into nested objects\n * (`{ reply: { root, parent } }`) so the resulting record matches the\n * lexicon's expected shape.\n *\n * `$type` and `createdAt` are NOT handled here — they're auto-injected in\n * `operations.ts`.\n */\nexport function buildRecordFromNodeParams(\n recordData: unknown,\n): Record<string, unknown> {\n // Format 1: raw JSON string\n if (typeof recordData === 'string') {\n try {\n return JSON.parse(recordData) as Record<string, unknown>;\n } catch {\n return {};\n }\n }\n\n // Format 2: resourceMapper value\n if (\n recordData &&\n typeof recordData === 'object' &&\n 'mappingMode' in recordData &&\n 'value' in recordData\n ) {\n const rm = recordData as {\n mappingMode: string;\n value: Record<string, unknown> | null;\n };\n\n if (!rm.value) return {};\n\n // Both defineBelow and autoMapInputData produce a flat key/value object.\n // Un-flatten dotted keys back into nested objects.\n return unflattenDottedKeys(rm.value);\n }\n\n // Format 3: plain object (e.g. from an expression)\n return (recordData ?? {}) as Record<string, unknown>;\n}\n\n/**\n * Convert a flat object with dotted keys into a nested object.\n *\n * `{ \"reply.root\": \"x\", \"reply.parent\": \"y\", text: \"hi\" }`\n * becomes\n * `{ reply: { root: \"x\", parent: \"y\" }, text: \"hi\" }`.\n *\n * Conflicts (a dotted key whose prefix is also a leaf) prefer the more\n * specific (dotted) value and discard the conflicting leaf.\n */\nexport function unflattenDottedKeys(\n flat: Record<string, unknown>,\n): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(flat)) {\n if (value === undefined || value === '') {\n // Skip empty/undefined values — the user didn't fill in this field.\n // Preserve `null` for nullable fields (the PDS accepts null when\n // the schema declares a field as nullable).\n continue;\n }\n\n if (!key.includes('.')) {\n result[key] = value;\n continue;\n }\n\n const parts = key.split('.');\n let cursor: Record<string, unknown> = result;\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i];\n const existing = cursor[part];\n if (\n existing === undefined ||\n existing === null ||\n typeof existing !== 'object'\n ) {\n cursor[part] = {};\n }\n cursor = cursor[part] as Record<string, unknown>;\n }\n cursor[parts[parts.length - 1]] = value;\n }\n\n return result;\n}\n"],"mappings":"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E,MAAM;MACN,YAAY;OACV,OAAO;OACP,cAAc;MAChB;KACF,CACF;IACF,CACF;GACF;GAKA;IACE,aAAa;IACb,MAAM;IACN,MAAM;IACN,aAAa;IACb,aACE;IACF,gBAAgB,EACd,MAAM,EACJ,WAAW;KAAC;KAAa;KAAe;IAAW,EACrD,EACF;IACA,SAAS;GACX;GAMA;IACE,aAAa;IACb,MAAM;IACN,MAAM;IACN,aAAa;IACb,aACE;IACF,gBAAgB,EACd,MAAM,EACJ,WAAW,CAAC,SAAS,EACvB,EACF;IACA,SAAS;GACX;GAKA;IACE,aAAa;IACb,MAAM;IACN,MAAM;IACN,UAAU;IACV,aACE;IACF,aACE;IACF,gBAAgB,EACd,MAAM,EACJ,WAAW,CAAC,SAAS,EACvB,EACF;IACA,SAAS;GACX;GAKA;IACE,aAAa;IACb,MAAM;IACN,MAAM;IACN,UAAU;IACV,SAAS;IACT,aAAa;IACb,aACE;IACF,gBAAgB,EACd,MAAM,EACJ,WAAW,CAAC,YAAY,EAC1B,EACF;GACF;GACA;IACE,aAAa;IACb,MAAM;IACN,MAAM;IACN,UAAU;IACV,SAAS;IACT,aAAa;IACb,aACE;IACF,gBAAgB,EACd,MAAM,EACJ,WAAW,CAAC,SAAS,EACvB,EACF;GACF;GAKA;IACE,aAAa;IACb,MAAM;IACN,MAAM;IACN,aAAa;IACb,SAAS,CAAC;IACV,gBAAgB,EACd,MAAM,EACJ,WAAW,CAAC,YAAY,EAC1B,EACF;IACA,SAAS,CACP;KACE,aAAa;KACb,MAAM;KACN,MAAM;KACN,SAAS;KACT,aAAa;KACb,aACE;IACJ,CACF;GACF;GAKA;IACE,aAAa;IACb,MAAM;IACN,MAAM;IACN,aAAa;IACb,SAAS,CAAC;IACV,gBAAgB,EACd,MAAM,EACJ,WAAW,CAAC,WAAW,EACzB,EACF;IACA,SAAS,CACP;KACE,aAAa;KACb,MAAM;KACN,MAAM;KACN,SAAS;KACT,aAAa;KACb,aACE;IACJ,CACF;GACF;GAKA;IACE,aAAa;IACb,MAAM;IACN,MAAM;IACN,aAAa;IACb,aACE;IACF,gBAAgB,EACd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"}
1
+ {"version":3,"file":"Atproto.node.js","names":[],"sources":["../../../nodes/Atproto/operations.ts","../../../nodes/Atproto/tid.ts","../../../nodes/Atproto/lexicon.ts","../../../nodes/Atproto/fieldMapping.ts","../../../nodes/Atproto/blob.ts","../../../nodes/Atproto/blobInput.ts","../../../nodes/Atproto/typeInjection.ts","../../../nodes/Atproto/validation.ts","../../../nodes/Atproto/Atproto.node.ts"],"sourcesContent":["/**\n * CRUD operations for AT Protocol records.\n *\n * Each function wraps an XRPC call via the authenticated Agent.\n * - `$type` is auto-injected from the collection NSID.\n * - `createdAt` is auto-injected as ISO string when the schema requires it\n * (Phase 1: always inject; Phase 2 will make it schema-conditional).\n * - `repo` defaults to the authenticated user's DID for write operations.\n * Get/List accept an optional `repo` to read other users' public records.\n */\n\nimport { Agent } from '@atproto/api';\nimport type { LexiconSchema } from './lexicon';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface CreateRecordParams {\n collection: string;\n record: Record<string, unknown>;\n rkey?: string;\n swapCommit?: string;\n /** Defaults to authenticated user's DID */\n repo?: string;\n}\n\nexport interface GetRecordParams {\n collection: string;\n rkey: string;\n /** Defaults to authenticated user's DID */\n repo?: string;\n}\n\nexport interface PutRecordParams {\n collection: string;\n rkey: string;\n record: Record<string, unknown>;\n swapCommit?: string;\n /** Defaults to authenticated user's DID */\n repo?: string;\n}\n\nexport interface DeleteRecordParams {\n collection: string;\n rkey: string;\n swapCommit?: string;\n /** Defaults to authenticated user's DID */\n repo?: string;\n}\n\nexport interface ListRecordsParams {\n collection: string;\n cursor?: string;\n limit?: number;\n /** Defaults to authenticated user's DID */\n repo?: string;\n}\n\nexport interface CreateRecordResult {\n uri: string;\n cid: string;\n}\n\nexport interface GetRecordResult {\n uri: string;\n cid: string;\n value: Record<string, unknown>;\n}\n\nexport interface PutRecordResult {\n uri: string;\n cid: string;\n}\n\nexport interface DeleteRecordResult {\n uri?: string;\n cid?: string;\n}\n\nexport interface ListRecordsResult {\n records: Array<{\n uri: string;\n cid: string;\n value: Record<string, unknown>;\n }>;\n cursor?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Blob operation params / results\n// ---------------------------------------------------------------------------\n\nexport interface UploadBlobParams {\n data: Buffer;\n mimeType: string;\n}\n\nexport interface UploadBlobResult {\n blob: {\n $type: 'blob';\n ref: { $link: string };\n mimeType: string;\n size: number;\n };\n}\n\nexport interface GetBlobParams {\n /** DID of the account that owns the blob. */\n did: string;\n /** CID of the blob to fetch. */\n cid: string;\n}\n\nexport interface GetBlobResult {\n /** Raw blob bytes. */\n data: Buffer;\n /** MIME type reported by the server (from Content-Type), or empty string. */\n mimeType: string;\n /** Size of the returned buffer in bytes. */\n size: number;\n}\n\nexport interface ListBlobsParams {\n /** DID of the repo to list blobs for. Defaults to authenticated user. */\n did?: string;\n cursor?: string;\n limit?: number;\n /** Optional repo revision — list only blobs added since this rev. */\n since?: string;\n}\n\nexport interface ListBlobsResult {\n cids: string[];\n cursor?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Injects `$type` if not already present.\n */\nfunction ensure$type(record: Record<string, unknown>, collection: string): void {\n if (!record['$type']) {\n record['$type'] = collection;\n }\n}\n\n/**\n * Injects `createdAt` if not already present (ISO timestamp).\n * Phase 1: always inject. Phase 2 will check the lexicon schema.\n */\nfunction ensureCreatedAt(record: Record<string, unknown>): void {\n if (!record['createdAt']) {\n record['createdAt'] = new Date().toISOString();\n }\n}\n\n/**\n * Returns the authenticated user's DID from the agent's session manager.\n */\nfunction getOwnDid(agent: Agent): string {\n const did = agent.did;\n if (!did) {\n throw new Error('Not authenticated — no DID available');\n }\n return did;\n}\n\n/**\n * Resolve a handle to a DID if needed. Returns DIDs unchanged.\n * Strips a leading `@` from handles. Trims whitespace.\n */\nasync function resolveActorToDid(\n agent: Agent,\n actor: string,\n): Promise<string> {\n const trimmed = actor.trim();\n if (trimmed.startsWith('did:')) return trimmed;\n const handle = trimmed.startsWith('@') ? trimmed.slice(1) : trimmed;\n const res = await agent.com.atproto.identity.resolveHandle({ handle });\n return res.data.did;\n}\n\n// ---------------------------------------------------------------------------\n// Read routing — direct repo reads to the PDS that hosts the target repo\n// ---------------------------------------------------------------------------\n\ninterface DidDocument {\n service?: Array<{ id: string; type: string; serviceEndpoint: unknown }>;\n}\n\n/**\n * Map a DID to the URL of its DID document. Supports did:plc (PLC directory)\n * and did:web.\n */\nfunction didDocumentUrl(did: string): string {\n if (did.startsWith('did:plc:')) {\n return `https://plc.directory/${did}`;\n }\n if (did.startsWith('did:web:')) {\n const [host, ...path] = did\n .slice('did:web:'.length)\n .split(':')\n .map(decodeURIComponent);\n return path.length === 0\n ? `https://${host}/.well-known/did.json`\n : `https://${host}/${path.join('/')}/did.json`;\n }\n throw new Error(`Unsupported DID method: ${did}`);\n}\n\n/**\n * Resolve a DID's hosting PDS endpoint from its DID document.\n */\nasync function resolvePdsEndpoint(did: string): Promise<string> {\n const res = await fetch(didDocumentUrl(did));\n if (!res.ok) {\n throw new Error(`Failed to resolve DID ${did}: HTTP ${res.status}`);\n }\n const doc = (await res.json()) as DidDocument;\n const endpoint = doc.service?.find((s) =>\n s.id.endsWith('#atproto_pds'),\n )?.serviceEndpoint;\n if (typeof endpoint !== 'string' || endpoint.length === 0) {\n throw new Error(`No PDS endpoint found in DID document for ${did}`);\n }\n return endpoint;\n}\n\n/**\n * Resolve the repo to read from into a DID plus an Agent pointed at the PDS\n * that hosts it. Repo-hosting reads (getRecord, listRecords, getBlob,\n * listBlobs) must hit that PDS — the authenticated session's PDS only serves\n * its own repos and answers \"Could not find repo\" for any other.\n *\n * The session agent is reused for the user's own repo (already on the correct\n * PDS); foreign repos get an unauthenticated Agent, since these reads are\n * public.\n */\nasync function resolveReadTarget(\n agent: Agent,\n actor?: string,\n): Promise<{ did: string; agent: Agent }> {\n if (!actor || actor.trim() === '') {\n return { did: getOwnDid(agent), agent };\n }\n const did = await resolveActorToDid(agent, actor);\n if (did === agent.did) {\n return { did, agent };\n }\n return { did, agent: new Agent(await resolvePdsEndpoint(did)) };\n}\n\n// ---------------------------------------------------------------------------\n// Const injection\n// ---------------------------------------------------------------------------\n\n/**\n * Walk schema properties and inject `const` values for any field the user\n * left empty. This ensures constant fields are always set correctly even if\n * the readOnly UI is bypassed (e.g. via JSON mode or expressions).\n */\nexport function applyConstValues(\n record: Record<string, unknown>,\n schema: LexiconSchema | null,\n): void {\n if (!schema) return;\n for (const [name, prop] of Object.entries(schema.properties)) {\n if (prop.const !== undefined && (record[name] === undefined || record[name] === null || record[name] === '')) {\n record[name] = prop.const;\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Operations\n// ---------------------------------------------------------------------------\n\n/**\n * Creates a record in the specified collection.\n * Auto-generates a TID if no `rkey` is provided.\n * Auto-injects `$type` and `createdAt`.\n */\nexport async function createRecord(\n agent: Agent,\n params: CreateRecordParams,\n): Promise<CreateRecordResult> {\n const repo = params.repo ?? getOwnDid(agent);\n\n const record = { ...params.record };\n ensure$type(record, params.collection);\n ensureCreatedAt(record);\n\n const response = await agent.com.atproto.repo.createRecord({\n repo,\n collection: params.collection,\n rkey: params.rkey,\n record,\n swapCommit: params.swapCommit,\n });\n\n const data = response.data;\n return { uri: data.uri, cid: data.cid };\n}\n\n/**\n * Gets a record by collection and record key.\n * Optionally reads from a different repo (defaults to self).\n */\nexport async function getRecord(\n agent: Agent,\n params: GetRecordParams,\n): Promise<GetRecordResult> {\n const { did, agent: reader } = await resolveReadTarget(agent, params.repo);\n\n const response = await reader.com.atproto.repo.getRecord({\n repo: did,\n collection: params.collection,\n rkey: params.rkey,\n });\n\n const data = response.data as {\n uri: string;\n cid: string;\n value: Record<string, unknown>;\n };\n return { uri: data.uri, cid: data.cid, value: data.value };\n}\n\n/**\n * Full-replaces a record. Optional `swapCommit` for optimistic concurrency.\n * Auto-injects `$type` and `createdAt`.\n */\nexport async function putRecord(\n agent: Agent,\n params: PutRecordParams,\n): Promise<PutRecordResult> {\n const repo = params.repo ?? getOwnDid(agent);\n\n const record = { ...params.record };\n ensure$type(record, params.collection);\n ensureCreatedAt(record);\n\n const response = await agent.com.atproto.repo.putRecord({\n repo,\n collection: params.collection,\n rkey: params.rkey,\n record,\n swapCommit: params.swapCommit,\n });\n\n const data = response.data;\n return { uri: data.uri, cid: data.cid };\n}\n\n/**\n * Deletes a record by collection and record key.\n */\nexport async function deleteRecord(\n agent: Agent,\n params: DeleteRecordParams,\n): Promise<DeleteRecordResult> {\n const repo = params.repo ?? getOwnDid(agent);\n\n const response = await agent.com.atproto.repo.deleteRecord({\n repo,\n collection: params.collection,\n rkey: params.rkey,\n swapCommit: params.swapCommit,\n });\n\n return response.data as DeleteRecordResult;\n}\n\n/**\n * Lists records in a collection with optional pagination.\n * Returns one page per execution; chain nodes or loop to paginate.\n */\nexport async function listRecords(\n agent: Agent,\n params: ListRecordsParams,\n): Promise<ListRecordsResult> {\n const { did, agent: reader } = await resolveReadTarget(agent, params.repo);\n\n const response = await reader.com.atproto.repo.listRecords({\n repo: did,\n collection: params.collection,\n limit: params.limit,\n cursor: params.cursor,\n });\n\n const data = response.data as {\n records: Array<{\n uri: string;\n cid: string;\n value: Record<string, unknown>;\n }>;\n cursor?: string;\n };\n\n return {\n records: data.records.map((r) => ({\n uri: r.uri,\n cid: r.cid,\n value: r.value,\n })),\n cursor: data.cursor,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Blob operations\n// ---------------------------------------------------------------------------\n\n/**\n * Upload a blob to the authenticated user's PDS.\n *\n * Returns the blob reference in the canonical AT Protocol shape, ready to\n * embed in a record:\n *\n * { \"$type\": \"blob\", \"ref\": { \"$link\": \"bafkrei...\" }, \"mimeType\": \"image/jpeg\", \"size\": 12345 }\n *\n * The PDS may reject blobs over a service-defined size limit (commonly\n * ~1 MB on bsky.social). The error is propagated unchanged so the caller\n * can surface it to the user.\n */\nexport async function uploadBlob(\n agent: Agent,\n params: UploadBlobParams,\n): Promise<UploadBlobResult> {\n const response = await agent.com.atproto.repo.uploadBlob(params.data, {\n encoding: params.mimeType,\n });\n\n // BlobRef serializes to the on-the-wire JSON shape via toJSON();\n // calling it explicitly gives us a plain object that's safe to return\n // through n8n's data pipeline.\n const ref = response.data.blob as unknown as { toJSON?: () => unknown };\n const serialized =\n typeof ref.toJSON === 'function'\n ? (ref.toJSON() as UploadBlobResult['blob'])\n : (response.data.blob as unknown as UploadBlobResult['blob']);\n\n return { blob: serialized };\n}\n\n/**\n * Download a blob by CID from a given repo.\n *\n * `did` is required by the XRPC method and must be a DID (callers should\n * resolve handles upfront). Returns the raw bytes plus the server-reported\n * MIME type (read from the response headers).\n */\nexport async function getBlob(\n agent: Agent,\n params: GetBlobParams,\n): Promise<GetBlobResult> {\n const { did, agent: reader } = await resolveReadTarget(agent, params.did);\n\n const response = await reader.com.atproto.sync.getBlob({\n did,\n cid: params.cid,\n });\n\n // response.data is a Uint8Array — convert to Buffer for n8n's binary helpers.\n const buffer = Buffer.from(response.data);\n const mimeType =\n (response.headers as Record<string, string> | undefined)?.['content-type'] ??\n '';\n\n return {\n data: buffer,\n mimeType,\n size: buffer.length,\n };\n}\n\n/**\n * List blob CIDs in a repo. Paginated; pass `cursor` from a previous response\n * to fetch the next page. `since` filters to blobs added after a given repo\n * revision (rev) — useful for incremental sync.\n *\n * `did` defaults to the authenticated user.\n */\nexport async function listBlobs(\n agent: Agent,\n params: ListBlobsParams = {},\n): Promise<ListBlobsResult> {\n const { did, agent: reader } = await resolveReadTarget(agent, params.did);\n\n const response = await reader.com.atproto.sync.listBlobs({\n did,\n limit: params.limit,\n cursor: params.cursor,\n since: params.since,\n });\n\n return {\n cids: response.data.cids,\n cursor: response.data.cursor,\n };\n}\n\nexport { resolveActorToDid };\n","/**\n * TID (Timestamp Identifier) generation for AT Protocol.\n *\n * A TID is a 13-character base32-sortable string encoding a 64-bit value:\n * - Top bit: always 0\n * - Bits 62–10: 53-bit microsecond Unix timestamp\n * - Bits 9–0: 10-bit random clock ID\n *\n * Characters use the alphabet `234567abcdefghijklmnopqrstuvwxyz`\n * (base32-sortable — no '1' to avoid confusion with 'l').\n */\n\nconst S32_CHAR = '234567abcdefghijklmnopqrstuvwxyz';\n\nlet clockId: number | null = null;\nlet counter = 0;\nlet lastTimestamp = 0;\n\n/**\n * Encodes a non-negative integer to a base32-sortable string.\n * Returns empty string for 0.\n */\nfunction s32encode(i: number): string {\n let s = '';\n while (i) {\n const c = i % 32;\n i = Math.floor(i / 32);\n s = S32_CHAR.charAt(c) + s;\n }\n return s;\n}\n\n/**\n * Returns the clock ID (10 bits), initialised once and reused.\n */\nfunction getClockId(): number {\n if (clockId === null) {\n clockId = Math.floor(Math.random() * 1024); // 10 bits: 0–1023\n }\n return clockId;\n}\n\n/**\n * Generates a new TID string.\n *\n * The timestamp is derived from `Date.now()` in microseconds, with a\n * per-millisecond counter to guarantee uniqueness within the same ms.\n * The clock ID is a random 10-bit value generated on first call and\n * reused for subsequent TIDs from this process.\n *\n * @returns 13-character base32-sortable TID string.\n */\nexport function generateTid(): string {\n const now = Math.max(Date.now(), lastTimestamp);\n\n if (now === lastTimestamp) {\n counter++;\n } else {\n counter = 0;\n }\n lastTimestamp = now;\n\n // (now in ms) × 1000 + counter gives monotonic microsecond-like values\n const timestamp = now * 1000 + counter;\n\n // 53 bits needs up to 11 base32 chars\n const tsPart = s32encode(timestamp).padStart(11, S32_CHAR[0]);\n // 10 bits needs exactly 2 base32 chars\n const cidPart = s32encode(getClockId()).padStart(2, S32_CHAR[0]);\n\n return tsPart + cidPart;\n}\n","/**\n * Lexicon resolution for AT Protocol record schemas.\n *\n * Resolves a lexicon schema document from an NSID via two paths:\n * 1. PDS endpoint: `com.atproto.lexicon.resolveLexicon` (requires auth)\n * 2. Fallback: `@atproto/lexicon-resolver` DNS-based resolution (no auth)\n *\n * Results are cached in-memory (module-level Map) since calls originate\n * from the n8n editor (resourceMapperMethod), not during execution.\n */\n\nimport type { Agent } from '@atproto/api';\n\n// ---------------------------------------------------------------------------\n// Types — simplified view of what we extract from lexicon documents\n// ---------------------------------------------------------------------------\n\n/** A single property/field definition in a lexicon record. */\nexport interface LexiconProperty {\n type: string;\n format?: string;\n /** For `ref` types: the target NSID or #local-name. */\n ref?: string;\n /** For `union` types: the list of possible ref targets. */\n refs?: string[];\n /** For `array` types: the schema of array items. */\n items?: LexiconProperty;\n /** For `object` types: nested properties. */\n properties?: Record<string, LexiconProperty>;\n /** For `object` types: required sub-field names. */\n required?: string[];\n description?: string;\n /** Whether the field is nullable (from the `nullable` array). */\n nullable?: boolean;\n // --- Value constraints ---\n /** Default value for this field. */\n default?: string | number | boolean;\n /** Constant value — field must always equal this. */\n const?: string | number | boolean;\n /** Closed set of allowed values (dropdown). */\n enum?: (string | number)[];\n /** Suggested values (open set — user can type anything). */\n knownValues?: string[];\n // --- Numeric range ---\n minimum?: number;\n maximum?: number;\n // --- Length constraints (string: UTF-8 bytes; bytes: raw; array: element count) ---\n minLength?: number;\n maxLength?: number;\n // --- Grapheme constraints (string only) ---\n minGraphemes?: number;\n maxGraphemes?: number;\n // --- Blob constraints ---\n /** Accepted MIME types for blob fields (e.g. `['image/*']`). */\n accept?: string[];\n /** Maximum blob size in bytes. */\n maxSize?: number;\n // --- Union flag ---\n /** When true, the union rejects unknown `$type` values. */\n closed?: boolean;\n}\n\n/** Parsed record schema extracted from a lexicon document. */\nexport interface LexiconSchema {\n /** The record's property definitions (from `defs.main.record.properties`). */\n properties: Record<string, LexiconProperty>;\n /** Names of required fields (from `defs.main.record.required`). */\n required: string[];\n /** The record key strategy declared by the lexicon. */\n key?: 'tid' | 'any' | 'literal';\n /** When `key` is `literal`, the literal value (e.g. `self`). */\n literalKey?: string;\n /** The raw lexicon document defs (for local ref resolution like `#someName`). */\n rawDefs?: Record<string, unknown>;\n /** NSID of this lexicon schema. */\n nsid: string;\n}\n\n// ---------------------------------------------------------------------------\n// Cache\n// ---------------------------------------------------------------------------\n\nconst schemaCache = new Map<string, LexiconSchema>();\n\n/** Clear the in-memory cache (useful in tests). */\nexport function clearLexiconCache(): void {\n schemaCache.clear();\n}\n\n// ---------------------------------------------------------------------------\n// Resolution\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve a lexicon schema for the given collection NSID.\n *\n * Resolution chain:\n * 1. Check module-level cache.\n * 2. Try PDS `com.atproto.lexicon.resolveLexicon` endpoint.\n * 3. If that fails, try `@atproto/lexicon-resolver` (DNS-based, dynamic import).\n * 4. If both fail, return `null` (triggers JSON fallback in n8n).\n *\n * @param agent - Authenticated AT Protocol agent (may be null if unavailable).\n * @param nsid - The collection NSID to resolve (e.g. `app.bsky.feed.post`).\n */\nexport async function resolveLexiconSchema(\n agent: Agent | null,\n nsid: string,\n): Promise<LexiconSchema | null> {\n const cached = schemaCache.get(nsid);\n if (cached) return cached;\n\n // Try path A: PDS endpoint.\n // The @atproto/api client's strict XRPC validation rejects `record` types\n // in the resolveLexicon response schema. We call the endpoint via the\n // typed client anyway and extract the response body from the error, which\n // is still populated even when validation fails.\n if (agent) {\n try {\n const response =\n await agent.com.atproto.lexicon.resolveLexicon({ nsid });\n const schema = parseRawLexiconDocument(\n response.data.schema as Record<string, unknown>,\n nsid,\n );\n if (schema) {\n schemaCache.set(nsid, schema);\n return schema;\n }\n } catch (err) {\n // The client throws XRPCInvalidResponseError when the response\n // doesn't match the schema. The response body is still available\n // on the error object.\n const errObj = err as Record<string, unknown>;\n const responseBody = errObj.responseBody as\n | Record<string, unknown>\n | undefined;\n if (responseBody?.schema) {\n const schema = parseRawLexiconDocument(\n responseBody.schema as Record<string, unknown>,\n nsid,\n );\n if (schema) {\n schemaCache.set(nsid, schema);\n return schema;\n }\n }\n // Fall through to path B\n }\n }\n\n // Try path B: @atproto/lexicon-resolver (DNS-based, no auth required).\n // Dynamic import to avoid CJS/ESM interop issues at module load time.\n try {\n const mod: { resolveLexicon: (nsid: string) => Promise<unknown> } =\n // @ts-expect-error Can't resolve package under current moduleResolution\n await import('@atproto/lexicon-resolver');\n const resolution = await mod.resolveLexicon(nsid);\n const lexicon = (resolution as Record<string, unknown>)\n .lexicon as Record<string, unknown>;\n const schema = parseLexiconDocumentRecord(lexicon, nsid);\n if (schema) {\n schemaCache.set(nsid, schema);\n return schema;\n }\n } catch {\n // Both paths failed — return null for JSON fallback\n }\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Parsing helpers — exported for direct use in tests\n// ---------------------------------------------------------------------------\n\n/**\n * Parse a raw lexicon document (without $type or CID validation) into our\n * internal schema. Useful for tests that construct mock lexicons directly.\n */\nexport function parseLexiconDoc(\n doc: Record<string, unknown>,\n nsid: string,\n): LexiconSchema | null {\n return parseRawLexiconDocument(doc, nsid);\n}\n\n/**\n * Parse a raw lexicon document (from PDS endpoint) into our internal schema.\n */\nfunction parseRawLexiconDocument(\n doc: Record<string, unknown>,\n nsid: string,\n): LexiconSchema | null {\n const defs = doc?.defs as Record<string, unknown> | undefined;\n if (!defs) return null;\n\n const mainDef = defs?.main as Record<string, unknown> | undefined;\n if (mainDef) {\n const schema = extractRecordSchema(mainDef, defs, nsid);\n if (schema) return schema;\n }\n\n // Type-only lexicon (no main record) — still useful for cross-document\n // fragment resolution (e.g. site.standard.theme.color#rgb).\n return {\n properties: {},\n required: [],\n rawDefs: defs,\n nsid,\n };\n}\n\n/**\n * Parse a LexiconDocumentRecord (from @atproto/lexicon-resolver) into our\n * internal schema. The shape is the same as the raw document, just\n * wrapped in a typed container.\n */\nfunction parseLexiconDocumentRecord(\n lexicon: Record<string, unknown>,\n nsid: string,\n): LexiconSchema | null {\n const defs = lexicon?.defs as Record<string, unknown> | undefined;\n if (!defs) return null;\n\n const mainDef = defs?.main as Record<string, unknown> | undefined;\n if (mainDef) {\n const schema = extractRecordSchema(mainDef, defs, nsid);\n if (schema) return schema;\n }\n\n // Type-only lexicon (no main record) — still useful for cross-document\n // fragment resolution (e.g. site.standard.theme.color#rgb).\n return {\n properties: {},\n required: [],\n rawDefs: defs,\n nsid,\n };\n}\n\n/**\n * Extract the record schema from a `defs.main` definition that has\n * `type: \"record\"`.\n */\nfunction extractRecordSchema(\n mainDef: Record<string, unknown>,\n defs: Record<string, unknown>,\n nsid: string,\n): LexiconSchema | null {\n if (mainDef.type !== 'record') {\n // Some lexicons have query/procedure as main — not a record schema\n return null;\n }\n\n const record = mainDef.record as Record<string, unknown> | undefined;\n if (!record || record.type !== 'object') return null;\n\n const properties = (record.properties as Record<string, unknown>) ?? {};\n const required = (record.required as string[]) ?? [];\n const nullable = (record.nullable as string[]) ?? [];\n\n // Parse record key strategy\n const key = mainDef.key as string | undefined;\n let keyType: 'tid' | 'any' | 'literal' | undefined;\n let literalKey: string | undefined;\n\n if (key === 'tid') {\n keyType = 'tid';\n } else if (key === 'any') {\n keyType = 'any';\n } else if (key && key.startsWith('literal:')) {\n keyType = 'literal';\n literalKey = key.slice('literal:'.length);\n }\n\n // Convert raw properties to our internal representation\n const parsedProperties: Record<string, LexiconProperty> = {};\n for (const [name, raw] of Object.entries(properties)) {\n const prop = raw as Record<string, unknown>;\n parsedProperties[name] = {\n type: (prop.type as string) ?? 'unknown',\n format: prop.format as string | undefined,\n ref: prop.ref as string | undefined,\n refs: prop.refs as string[] | undefined,\n items: prop.items\n ? parseInlineProperty(prop.items as Record<string, unknown>)\n : undefined,\n properties: prop.properties\n ? parseInlineObjectProperties(\n prop.properties as Record<string, unknown>,\n )\n : undefined,\n required: prop.required as string[] | undefined,\n description: prop.description as string | undefined,\n nullable: nullable.includes(name),\n default: prop.default as string | number | boolean | undefined,\n const: prop.const as string | number | boolean | undefined,\n enum: prop.enum as (string | number)[] | undefined,\n knownValues: prop.knownValues as string[] | undefined,\n minimum: prop.minimum as number | undefined,\n maximum: prop.maximum as number | undefined,\n minLength: prop.minLength as number | undefined,\n maxLength: prop.maxLength as number | undefined,\n minGraphemes: prop.minGraphemes as number | undefined,\n maxGraphemes: prop.maxGraphemes as number | undefined,\n accept: prop.accept as string[] | undefined,\n maxSize: prop.maxSize as number | undefined,\n closed: prop.closed as boolean | undefined,\n };\n }\n\n return {\n properties: parsedProperties,\n required,\n key: keyType,\n literalKey,\n rawDefs: defs,\n nsid,\n };\n}\n\n/**\n * Recursively parse a nested property definition (e.g. for `array` items\n * or inline `object` fields).\n *\n * Handles the AT Protocol shorthand where `type` is inferred from other\n * fields (e.g. `{ \"ref\": \"...\" }` implies `type: \"ref\"`).\n */\nfunction parseInlineProperty(\n raw: Record<string, unknown>,\n): LexiconProperty {\n // Infer type from other fields if not explicitly set\n let type = (raw.type as string) ?? 'unknown';\n if (!raw.type) {\n if (raw.ref) type = 'ref';\n else if (raw.refs) type = 'union';\n else if (raw.properties) type = 'object';\n else if (raw.items) type = 'array';\n }\n\n return {\n type,\n format: raw.format as string | undefined,\n ref: raw.ref as string | undefined,\n refs: raw.refs as string[] | undefined,\n items: raw.items\n ? parseInlineProperty(raw.items as Record<string, unknown>)\n : undefined,\n properties: raw.properties\n ? parseInlineObjectProperties(\n raw.properties as Record<string, unknown>,\n )\n : undefined,\n required: raw.required as string[] | undefined,\n description: raw.description as string | undefined,\n default: raw.default as string | number | boolean | undefined,\n const: raw.const as string | number | boolean | undefined,\n enum: raw.enum as (string | number)[] | undefined,\n knownValues: raw.knownValues as string[] | undefined,\n minimum: raw.minimum as number | undefined,\n maximum: raw.maximum as number | undefined,\n minLength: raw.minLength as number | undefined,\n maxLength: raw.maxLength as number | undefined,\n minGraphemes: raw.minGraphemes as number | undefined,\n maxGraphemes: raw.maxGraphemes as number | undefined,\n accept: raw.accept as string[] | undefined,\n maxSize: raw.maxSize as number | undefined,\n closed: raw.closed as boolean | undefined,\n };\n}\n\n/**\n * Parse the `properties` map of an inline object definition.\n */\nfunction parseInlineObjectProperties(\n raw: Record<string, unknown>,\n): Record<string, LexiconProperty> {\n const result: Record<string, LexiconProperty> = {};\n for (const [name, value] of Object.entries(raw)) {\n result[name] = parseInlineProperty(value as Record<string, unknown>);\n }\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Ref resolution\n// ---------------------------------------------------------------------------\n\n/** Resolved ref: properties plus the names of fields required by the\n * referenced definition. */\nexport interface ResolvedRef {\n properties: Record<string, LexiconProperty>;\n required: string[];\n}\n\n/**\n * Resolve a local ref (`#someName`) or cross-document ref\n * (`app.bsky.richtext.facet` or `app.bsky.feed.post#replyRef`) to\n * its property definitions.\n *\n * For local refs, looks in the `rawDefs` of the given schema.\n * For cross-document refs, attempts to resolve the target NSID.\n *\n * @returns The resolved properties + required-field names, or `null` if\n * resolution fails.\n */\nexport async function resolveRefProperties(\n ref: string,\n currentSchema: LexiconSchema,\n resolveExternal: (nsid: string) => Promise<LexiconSchema | null>,\n): Promise<ResolvedRef | null> {\n if (ref.startsWith('#')) {\n // Local ref — look up in defs\n const localName = ref.slice(1);\n return resolveLocalDef(localName, currentSchema.rawDefs);\n }\n\n // Cross-document ref — may include a fragment like\n // `app.bsky.feed.post#replyRef`\n const hashIdx = ref.indexOf('#');\n const targetNsid = hashIdx >= 0 ? ref.slice(0, hashIdx) : ref;\n const fragment = hashIdx >= 0 ? ref.slice(hashIdx + 1) : undefined;\n\n const resolved = await resolveExternal(targetNsid);\n if (!resolved) return null;\n\n if (fragment) {\n return resolveLocalDef(fragment, resolved.rawDefs);\n }\n\n // No fragment — return the record's top-level properties + required\n return {\n properties: resolved.properties,\n required: resolved.required,\n };\n}\n\n/**\n * Look up a def by name in the raw defs map and extract its properties\n * along with the names of required fields.\n */\nfunction resolveLocalDef(\n name: string,\n rawDefs?: Record<string, unknown>,\n): ResolvedRef | null {\n if (!rawDefs) return null;\n\n const def = rawDefs[name] as Record<string, unknown> | undefined;\n if (!def) return null;\n\n // Direct object definition\n if (def.type === 'object') {\n return extractObjectDef(def);\n }\n\n // Token — no properties\n if (def.type === 'token') {\n return { properties: {}, required: [] };\n }\n\n // Record — unwrap to get the inner object\n if (def.type === 'record') {\n const record = def.record as Record<string, unknown> | undefined;\n if (!record || record.type !== 'object') return null;\n return extractObjectDef(record);\n }\n\n return null;\n}\n\n/** Extract a `{ properties, required }` pair from an `object`-type def. */\nfunction extractObjectDef(\n objDef: Record<string, unknown>,\n): ResolvedRef | null {\n const properties = objDef.properties as\n | Record<string, unknown>\n | undefined;\n if (!properties) return null;\n const required = (objDef.required as string[]) ?? [];\n const nullable = (objDef.nullable as string[]) ?? [];\n const result: Record<string, LexiconProperty> = {};\n for (const [k, v] of Object.entries(properties)) {\n const p = parseInlineProperty(v as Record<string, unknown>);\n p.nullable = nullable.includes(k);\n result[k] = p;\n }\n return { properties: result, required };\n}\n","/**\n * Maps a resolved AT Protocol lexicon schema to n8n `ResourceMapperField[]`.\n *\n * Handles:\n * - Type mapping from lexicon types → n8n FieldType\n * - Recursive `ref` resolution with dotted-path flattening (cap depth 3)\n * - Required vs optional fields\n * - `createdAt` auto-population as default match\n * - Array items hint (shown as `json` for complex, `string` for primitives)\n */\n\nimport type { ResourceMapperField } from 'n8n-workflow';\nimport type { Agent } from '@atproto/api';\nimport type { LexiconProperty, LexiconSchema } from './lexicon';\nimport { resolveLexiconSchema, resolveRefProperties } from './lexicon';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/**\n * Maximum recursion depth for resolving `ref` types in the UI.\n *\n * Set to 1 so that top-level refs are flattened into their immediate\n * properties, but sub-refs within those become single `object` fields\n * rather than being recursively exploded into many dotted sub-fields.\n *\n * This keeps the field count manageable (e.g. a theme with 4 color\n * refs becomes 4 object fields, not 12+ individual number fields).\n *\n * Note: $type injection (typeInjection.ts) and validation (validation.ts)\n * maintain their own depth limits (3) for full recursive processing\n * at execution time.\n */\nconst MAX_REF_DEPTH = 1;\n\n/** Field names that should be auto-populated with default expressions. */\nconst AUTO_DEFAULTS: Record<string, string> = {\n createdAt: '={{ $now }}',\n};\n\n// ---------------------------------------------------------------------------\n// Type mapping\n// ---------------------------------------------------------------------------\n\n/**\n * Map a lexicon property type/format to an n8n FieldType.\n */\nfunction lexiconTypeToFieldType(prop: LexiconProperty): string {\n switch (prop.type) {\n case 'string':\n if (prop.format === 'datetime') return 'dateTime';\n // Note: 'url' is not a valid ResourceMapperField type, so uri/at-uri\n // stay as 'string' with a format hint in displayName instead.\n return 'string';\n case 'integer':\n return 'number';\n case 'boolean':\n return 'boolean';\n case 'array':\n return 'json';\n case 'object':\n // Inline object — flatten or treat as json\n if (prop.properties) return 'json';\n return 'json';\n case 'union':\n return 'json';\n case 'ref':\n return 'json'; // fallback — refs are flattened if resolvable\n case 'blob':\n return 'string';\n case 'cid-link':\n return 'string';\n case 'bytes':\n return 'string';\n case 'token':\n return 'string';\n case 'unknown':\n return 'object';\n default:\n return 'string';\n }\n}\n\n// ---------------------------------------------------------------------------\n// Ref helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Extract a resolvable ref target from a property.\n *\n * Returns the ref string for `ref` types and single-ref `union` types.\n * Single-ref unions are a common AT Protocol pattern for typed sub-objects\n * (e.g. `{ type: \"union\", refs: [\"site.standard.theme.color#rgb\"] }`).\n */\nexport function getResolvableRef(prop: LexiconProperty): string | null {\n if (prop.type === 'ref' && prop.ref) return prop.ref;\n if (prop.type === 'union' && prop.refs?.length === 1) return prop.refs[0];\n return null;\n}\n\n/**\n * Format displayName for multi-ref union fields.\n *\n * Shows the possible `$type` values so users know what to put in the JSON.\n * E.g. `embed (set $type to one of: images, external, record)`\n */\nfunction formatUnionDisplayName(\n name: string,\n prop: LexiconProperty,\n): string {\n const desc = prop.description?.replace(/\\.\\s*$/, '');\n if (prop.refs?.length) {\n const shortNames = prop.refs.map((r) => {\n const frag = r.indexOf('#');\n if (frag >= 0) return r.slice(frag + 1);\n const parts = r.split('.');\n return parts[parts.length - 1];\n });\n const typeList =\n shortNames.length <= 3\n ? shortNames.join(', ')\n : `${shortNames.slice(0, 2).join(', ')}, …`;\n return desc\n ? `${name} (${desc} — set $type to: ${typeList})`\n : `${name} (set $type to: ${typeList})`;\n }\n return desc ? `${name} (${desc})` : name;\n}\n\n/**\n * Format displayName for array fields.\n *\n * Shows the item type and description.\n * E.g. `facets (JSON array of app.bsky.richtext.facet)`\n */\nfunction formatArrayDisplayName(\n name: string,\n prop: LexiconProperty,\n): string {\n const desc = prop.description?.replace(/\\.\\s*$/, '');\n const itemRef =\n prop.items?.ref ?? prop.items?.refs?.[0];\n const itemHint = itemRef\n ? `JSON array of ${itemRef}`\n : prop.items?.type === 'string'\n ? 'list of strings'\n : 'JSON array';\n return desc\n ? `${name} (${desc} — ${itemHint})`\n : `${name} (${itemHint})`;\n}\n\n// ---------------------------------------------------------------------------\n// Main entry point\n// ---------------------------------------------------------------------------\n\n/**\n * Convert a resolved `LexiconSchema` to an array of `ResourceMapperField`.\n *\n * @param schema - The resolved lexicon schema.\n * @param agent - Authenticated agent (for resolving cross-document refs).\n * @param depth - Current recursion depth for ref resolution (starts at 0).\n */\nexport async function lexiconToResourceMapperFields(\n schema: LexiconSchema,\n agent: Agent | null,\n depth: number = 0,\n): Promise<ResourceMapperField[]> {\n const fields: ResourceMapperField[] = [];\n\n for (const [name, prop] of Object.entries(schema.properties)) {\n const isRequired = schema.required.includes(name);\n const subFields = await propToFields(\n name,\n prop,\n isRequired,\n schema,\n agent,\n depth,\n );\n fields.push(...subFields);\n }\n\n return fields;\n}\n\n// ---------------------------------------------------------------------------\n// Property → Field(s) conversion\n// ---------------------------------------------------------------------------\n\n/**\n * Convert a single lexicon property to one or more `ResourceMapperField`s.\n *\n * Simple types produce a single field. `ref` types produce multiple\n * flattened dotted-path fields when the target schema is resolvable.\n */\nasync function propToFields(\n name: string,\n prop: LexiconProperty,\n required: boolean,\n parentSchema: LexiconSchema,\n agent: Agent | null,\n depth: number,\n): Promise<ResourceMapperField[]> {\n // --- const fields: fixed, immutable value → readOnly ---\n if (prop.const !== undefined) {\n const fieldType = lexiconTypeToFieldType(prop);\n const desc = prop.description?.replace(/\\.\\s*$/, '');\n return [{\n id: name,\n displayName: desc\n ? `${name} (${desc} — fixed: ${String(prop.const)})`\n : `${name} (fixed: ${String(prop.const)})`,\n required,\n defaultMatch: false,\n display: true,\n type: fieldType as ResourceMapperField['type'],\n readOnly: true,\n defaultValue: prop.const,\n }];\n }\n\n // --- enum: closed set → options dropdown ---\n if (prop.enum?.length && (prop.type === 'string' || prop.type === 'integer')) {\n const desc = prop.description?.replace(/\\.\\s*$/, '');\n return [{\n id: name,\n displayName: desc ? `${name} (${desc})` : name,\n required,\n defaultMatch: name === 'createdAt',\n display: true,\n type: 'options' as ResourceMapperField['type'],\n options: prop.enum.map(v => ({ name: String(v), value: v })),\n ...(prop.default !== undefined ? { defaultValue: prop.default } : {}),\n }];\n }\n\n // --- ref / single-ref union types: attempt recursive flattening ---\n const resolveTarget = getResolvableRef(prop);\n if (resolveTarget && depth < MAX_REF_DEPTH) {\n const resolved = await resolveRefProperties(\n resolveTarget,\n parentSchema,\n (nsid: string) => resolveLexiconSchema(agent, nsid),\n );\n\n if (resolved !== null && Object.keys(resolved.properties).length > 0) {\n // Flatten the resolved properties with a dotted prefix.\n // A sub-field is required iff the parent ref is required AND the\n // resolved schema lists it as required.\n return flattenRefProperties(\n name,\n resolved.properties,\n resolved.required,\n required,\n parentSchema,\n agent,\n depth + 1,\n );\n }\n\n // Resolution failed — fall back to object field\n const fallbackDisplay = prop.description\n ? `${name} (${prop.description})`\n : name;\n return [\n {\n id: name,\n displayName: fallbackDisplay,\n required,\n defaultMatch: false,\n display: true,\n type: 'object',\n },\n ];\n }\n\n // --- ref / single-ref union that hit the depth limit: show as object ---\n // Still resolve the ref to build a JSON template for the default value,\n // so the user sees the expected structure instead of an empty editor.\n if (resolveTarget) {\n const desc = prop.description?.replace(/\\.\\s*$/, '');\n let template: string | undefined;\n try {\n const resolved = await resolveRefProperties(\n resolveTarget,\n parentSchema,\n (nsid: string) => resolveLexiconSchema(agent, nsid),\n );\n if (resolved && Object.keys(resolved.properties).length > 0) {\n template = JSON.stringify(\n buildDefaultTemplate(resolved.properties),\n null,\n 2,\n );\n }\n } catch {\n // Resolution failed — no template, user gets empty editor\n }\n return [\n {\n id: name,\n displayName: desc ? `${name} (${desc})` : name,\n required,\n defaultMatch: false,\n display: true,\n type: 'object',\n ...(template ? { defaultValue: template } : {}),\n },\n ];\n }\n\n // --- multi-ref union types: always object (too complex for flattening) ---\n if (prop.type === 'union') {\n const displayName = formatUnionDisplayName(name, prop);\n return [\n {\n id: name,\n displayName,\n required,\n defaultMatch: false,\n display: true,\n type: 'object',\n },\n ];\n }\n\n // --- array types ---\n // Use type: 'array' — n8n's tryToParseArray() accepts JSON array strings.\n // Using 'object' would fail validation since [] is not a JSON object.\n if (prop.type === 'array') {\n const displayName = formatArrayDisplayName(name, prop);\n return [\n {\n id: name,\n displayName,\n required,\n defaultMatch: false,\n display: true,\n type: 'array' as ResourceMapperField['type'],\n defaultValue: '[]',\n },\n ];\n }\n\n // --- object types (inline) ---\n if (prop.type === 'object' && prop.properties) {\n // Flatten inline object properties with dotted prefix\n const fields: ResourceMapperField[] = [];\n const subRequired = prop.required ?? [];\n for (const [subName, subProp] of Object.entries(prop.properties)) {\n const isSubRequired = required && subRequired.includes(subName);\n const subFields = await propToFields(\n `${name}.${subName}`,\n subProp,\n isSubRequired,\n parentSchema,\n agent,\n depth,\n );\n fields.push(...subFields);\n }\n return fields;\n }\n\n // --- simple types ---\n const fieldType = lexiconTypeToFieldType(prop);\n const field: ResourceMapperField = {\n id: name,\n displayName: name,\n required,\n defaultMatch: false,\n display: true,\n type: fieldType as ResourceMapperField['type'],\n };\n\n // Attach optional description (with blob hint)\n if (prop.type === 'blob') {\n const desc = prop.description?.replace(/\\.\\s*$/, '');\n field.displayName = desc\n ? `${name} (${desc} — binary property name from input)`\n : `${name} (binary property name from input)`;\n } else if (prop.description) {\n field.displayName = `${name} (${prop.description})`;\n }\n\n // knownValues: open set → show suggestions in displayName\n if (prop.knownValues?.length) {\n const shortNames = prop.knownValues.map(v => {\n const hash = v.indexOf('#');\n return hash >= 0 ? v.slice(hash + 1) : v.split('.').pop() ?? v;\n });\n const hint = shortNames.length <= 4\n ? shortNames.join(', ')\n : `${shortNames.slice(0, 3).join(', ')}, …`;\n const desc = prop.description?.replace(/\\.\\s*$/, '');\n field.displayName = desc\n ? `${name} (${desc} — e.g. ${hint})`\n : `${name} (e.g. ${hint})`;\n }\n\n // String format hints (datetime is already mapped to dateTime type)\n if (prop.type === 'string' && prop.format && prop.format !== 'datetime') {\n field.displayName += ` (${prop.format})`;\n }\n\n // Constraint hints in displayName\n const hints: string[] = [];\n if (prop.maxGraphemes) hints.push(`max ${prop.maxGraphemes} chars`);\n else if (prop.maxLength && prop.type === 'string') hints.push(`max ${prop.maxLength} bytes`);\n if (prop.minimum !== undefined || prop.maximum !== undefined) {\n const parts: string[] = [];\n if (prop.minimum !== undefined) parts.push(`≥${prop.minimum}`);\n if (prop.maximum !== undefined) parts.push(`≤${prop.maximum}`);\n hints.push(parts.join(', '));\n }\n if (hints.length) {\n field.displayName += ` [${hints.join('; ')}]`;\n }\n\n // Apply auto-defaults for known fields, then schema defaults\n if (AUTO_DEFAULTS[name] !== undefined) {\n field.defaultValue = AUTO_DEFAULTS[name];\n if (name === 'createdAt') {\n field.defaultMatch = true;\n }\n } else if (prop.default !== undefined) {\n field.defaultValue = prop.default;\n }\n\n return [field];\n}\n\n// ---------------------------------------------------------------------------\n// Ref flattening\n// ---------------------------------------------------------------------------\n\n/**\n * Flatten a resolved ref's properties into dotted-path fields.\n *\n * E.g. `reply.root.uri`, `reply.root.cid` instead of a single `reply` json field.\n *\n * A sub-field is marked `required` iff the parent ref itself is required\n * (`parentRequired`) AND the resolved schema lists the sub-field as required\n * (`schemaRequired`). This avoids marking optional ref's sub-fields as required\n * just because the schema requires them when the ref is present.\n */\nasync function flattenRefProperties(\n prefix: string,\n properties: Record<string, LexiconProperty>,\n schemaRequired: string[],\n parentRequired: boolean,\n parentSchema: LexiconSchema,\n agent: Agent | null,\n depth: number,\n): Promise<ResourceMapperField[]> {\n const fields: ResourceMapperField[] = [];\n\n for (const [name, prop] of Object.entries(properties)) {\n const dotted = `${prefix}.${name}`;\n const isRequired = parentRequired && schemaRequired.includes(name);\n\n // Recursively handle nested refs and single-ref unions\n const nestedRef = getResolvableRef(prop);\n if (nestedRef && depth < MAX_REF_DEPTH) {\n const resolved = await resolveRefProperties(\n nestedRef,\n parentSchema,\n (nsid) => resolveLexiconSchema(agent, nsid),\n );\n if (resolved !== null && Object.keys(resolved.properties).length > 0) {\n const nested = await flattenRefProperties(\n dotted,\n resolved.properties,\n resolved.required,\n isRequired,\n parentSchema,\n agent,\n depth + 1,\n );\n fields.push(...nested);\n continue;\n }\n }\n\n // For simple types or when refs can't be resolved further,\n // route through propToFields so the field gets all Phase 5 treatment\n // (defaults, enums, format hints, constraint hints).\n const subFields = await propToFields(\n dotted,\n prop,\n isRequired,\n parentSchema,\n agent,\n depth,\n );\n fields.push(...subFields);\n }\n\n return fields;\n}\n\n// ---------------------------------------------------------------------------\n// Default template generation\n// ---------------------------------------------------------------------------\n\n/**\n * Build a JSON-serialisable default template from resolved schema properties.\n *\n * Used for object-type fields where the ref was resolved (for schema info)\n * but not flattened (due to depth limit). The template shows the expected\n * structure so the user isn't staring at an empty editor.\n *\n * E.g. for a color ref: `{ \"r\": 0, \"g\": 0, \"b\": 0 }`\n * E.g. for a strongRef: `{ \"uri\": \"\", \"cid\": \"\" }`\n */\nfunction buildDefaultTemplate(\n properties: Record<string, LexiconProperty>,\n): Record<string, unknown> {\n const template: Record<string, unknown> = {};\n for (const [name, prop] of Object.entries(properties)) {\n template[name] = defaultForPropType(prop);\n }\n return template;\n}\n\n/** Derive a sensible default value for a single property. */\nfunction defaultForPropType(prop: LexiconProperty): unknown {\n if (prop.default !== undefined) return prop.default;\n if (prop.const !== undefined) return prop.const;\n switch (prop.type) {\n case 'string':\n return '';\n case 'integer':\n return prop.minimum ?? 0;\n case 'boolean':\n return false;\n case 'array':\n return [];\n case 'object':\n if (prop.properties) {\n return buildDefaultTemplate(prop.properties);\n }\n return {};\n default:\n return null;\n }\n}\n","/**\n * Blob upload support for AT Protocol nodes.\n *\n * After a record is built from node parameters, this module walks the record\n * looking for fields whose lexicon definition is `type: blob`. For each such\n * field, it reads the binary data from the n8n input item, uploads it via\n * `com.atproto.repo.uploadBlob`, and replaces the field value with the\n * returned blob reference.\n *\n * Only top-level blob fields are handled (not nested inside refs/objects).\n * This covers the common case — a record whose own schema declares a blob field.\n * For Bluesky embeds (images/video), the blob lives in the embed sub-record\n * (e.g. `app.bsky.embed.images`) which the user composes as a union/JSON value;\n * blob upload for those is currently outside scope.\n */\n\nimport type { Agent } from '@atproto/api';\nimport type { IExecuteFunctions } from 'n8n-workflow';\nimport type { LexiconSchema } from './lexicon';\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Walk a built record and upload any blob fields whose values name a binary\n * property on the input item.\n *\n * @param record - The constructed record (from `buildRecordFromNodeParams`).\n * @param schema - The resolved lexicon schema, or `null` if resolution\n * failed. When `null`, the record passes through unchanged\n * (blob fields remain as raw binary property name strings).\n * @param agent - Authenticated AT Protocol agent.\n * @param itemIndex - Index of the current input item in the execution loop.\n * @param executeFunctions - n8n execute functions (for `getBinaryDataBuffer`).\n * @returns A new record with blob fields replaced by blob references\n * `{ $type: 'blob', ref: { $link: cid }, mimeType, size }`.\n * @throws If a binary property named by a blob field doesn't exist or fails to\n * upload. Follows the \"rollback\" pattern — first failure stops processing.\n */\nexport async function applyBlobUploads(\n record: Record<string, unknown>,\n schema: LexiconSchema | null,\n agent: Agent,\n itemIndex: number,\n executeFunctions: IExecuteFunctions,\n): Promise<Record<string, unknown>> {\n if (!schema) {\n // Lexicon unknown — can't determine which fields are blobs.\n // Leave the record as-is; the user may have provided a raw blob ref\n // via JSON mode, or the PDS will reject with a validation error.\n return record;\n }\n\n const result = { ...record };\n\n for (const [key, value] of Object.entries(result)) {\n const propDef = schema.properties[key];\n if (!propDef || propDef.type !== 'blob') {\n continue;\n }\n\n // The field value from resourceMapper is the binary property name.\n // Skip empty/unset fields (they were already dropped by unflattenDottedKeys\n // for empty strings, but guard against edge cases).\n if (typeof value !== 'string' || value.length === 0) {\n continue;\n }\n\n // Pre-upload validation: check accept + maxSize before uploading\n const items = executeFunctions.getInputData();\n const item = items[itemIndex];\n const binaryMeta = item?.binary?.[value];\n\n if (propDef.accept?.length) {\n const mimeType = binaryMeta?.mimeType ?? 'application/octet-stream';\n const accepted = propDef.accept.some(pattern => {\n if (pattern === '*/*') return true;\n if (pattern.endsWith('/*'))\n return mimeType.startsWith(pattern.slice(0, -1));\n return mimeType === pattern;\n });\n if (!accepted) {\n throw new Error(\n `'${key}' accepts ${propDef.accept.join(', ')} but got ${mimeType}`,\n );\n }\n }\n\n // Upload the blob and substitute the reference\n result[key] = await uploadBlobFromBinary(\n value,\n propDef,\n agent,\n itemIndex,\n executeFunctions,\n );\n }\n\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Upload a blob from a named binary property on the current input item.\n *\n * Steps:\n * 1. Read the binary buffer via `getBinaryDataBuffer`.\n * 2. Determine MIME type from the binary item's metadata (falls back to\n * `application/octet-stream`).\n * 3. Call `com.atproto.repo.uploadBlob` on the PDS.\n * 4. Return the blob reference object from the response.\n */\nasync function uploadBlobFromBinary(\n binaryPropertyName: string,\n propDef: import('./lexicon').LexiconProperty,\n agent: Agent,\n itemIndex: number,\n executeFunctions: IExecuteFunctions,\n): Promise<Record<string, unknown>> {\n // 1. Read binary data\n const buffer = await executeFunctions.helpers.getBinaryDataBuffer(\n itemIndex,\n binaryPropertyName,\n );\n\n if (!buffer) {\n throw new Error(\n `Binary property \"${binaryPropertyName}\" not found on input item`,\n );\n }\n\n // 1b. Check maxSize before uploading (saves bandwidth)\n if (propDef.maxSize && buffer.length > propDef.maxSize) {\n throw new Error(\n `'${binaryPropertyName}' max size is ${propDef.maxSize} bytes, file is ${buffer.length} bytes`,\n );\n }\n\n // 2. Determine MIME type from binary metadata\n const items = executeFunctions.getInputData();\n const item = items[itemIndex];\n const binaryMeta = item?.binary?.[binaryPropertyName];\n const mimeType = binaryMeta?.mimeType ?? 'application/octet-stream';\n\n // 3. Upload to PDS\n // The typed XRPC client signature is `uploadBlob(data, opts)` where\n // `data` is the raw binary buffer and `opts.encoding` sets the\n // Content-Type header.\n const response = await agent.com.atproto.repo.uploadBlob(buffer, {\n encoding: mimeType,\n });\n\n // 4. Return the blob reference\n return response.data.blob as unknown as Record<string, unknown>;\n}\n","/**\n * Parse a user-supplied blob reference into a `{ cid, did? }` shape.\n *\n * The Download Blob operation needs a CID (and a DID for the repo). Users\n * paste this from many sources, so we accept whichever shape is convenient:\n *\n * 1. Bare CID — `bafkreig...`\n * 2. Blob-ref JSON — `{\"$link\":\"bafkreig...\"}` or the full BlobRef\n * object `{\"$type\":\"blob\",\"ref\":{\"$link\":\"...\"}}`\n * (so they can paste the output of a previous op\n * or a `getRecord` result without drilling in).\n * 3. bsky CDN URL — `https://cdn.bsky.app/img/<variant>/plain/<did>/<cid>@<ext>`\n * DID is extracted and returned so the Repo field\n * can be left empty in this case.\n *\n * Throws a descriptive error if nothing matches.\n */\n\nexport interface ParsedBlobReference {\n cid: string;\n /** Present only when the input embedded a DID (currently: bsky CDN URLs). */\n did?: string;\n}\n\n// A CID v1 base32 starts with `bafy` or `bafk` (most cases) but the alphabet\n// is base32-lowercase, so we keep this loose. A stricter check would require\n// the multibase + multihash libraries, which is overkill here.\nconst CID_LOOSE = /^[a-z0-9]{40,}$/i;\n\n/**\n * Path shape: `/img/<variant>/plain/<did>/<cid>(@<ext>)?`\n * The DID is everything between `/plain/` and the next `/`; the CID is\n * everything between that `/` and either `@<ext>` or end of path.\n */\nconst BSKY_CDN_PATH = /\\/img\\/[^/]+\\/plain\\/([^/]+)\\/([^/@?#]+)(?:@[^/?#]+)?/;\n\nexport function parseBlobReference(input: unknown): ParsedBlobReference {\n // n8n expression results may not always be strings — accept object inputs\n // (a pasted BlobRef expression) directly without round-tripping through JSON.\n if (input && typeof input === 'object') {\n const cid = extractLinkFromObject(input as Record<string, unknown>);\n if (cid) return { cid };\n throw new Error(\n 'Could not find a CID in the provided object. Expected a BlobRef with `ref.$link` or `$link`.',\n );\n }\n\n if (typeof input !== 'string') {\n throw new Error('Blob reference must be a string, BlobRef object, or URL');\n }\n\n const trimmed = input.trim();\n if (!trimmed) {\n throw new Error('Blob reference is empty');\n }\n\n // 1. bsky CDN URL — try before bare-CID because the URL also contains\n // CID-like strings inside it.\n if (trimmed.startsWith('http://') || trimmed.startsWith('https://')) {\n const url = safeUrl(trimmed);\n if (!url) {\n throw new Error(`Not a valid URL: ${trimmed}`);\n }\n const match = url.pathname.match(BSKY_CDN_PATH);\n if (match) {\n return { did: decodeURIComponent(match[1]), cid: match[2] };\n }\n throw new Error(\n 'URL is not a recognised bsky CDN blob URL (expected /img/<variant>/plain/<did>/<cid>)',\n );\n }\n\n // 2. JSON blob-ref pasted as a string\n if (trimmed.startsWith('{')) {\n try {\n const parsed = JSON.parse(trimmed) as Record<string, unknown>;\n const cid = extractLinkFromObject(parsed);\n if (cid) return { cid };\n } catch {\n // fall through to bare-CID check\n }\n throw new Error(\n 'Could not find a CID in the pasted JSON. Expected `{\"$link\":\"...\"}` or a full BlobRef.',\n );\n }\n\n // 3. Bare CID\n if (CID_LOOSE.test(trimmed)) {\n return { cid: trimmed };\n }\n\n throw new Error(\n `Unrecognised blob reference: \"${trimmed}\". Expected a CID, a blob-ref JSON, or a bsky CDN URL.`,\n );\n}\n\n// ---------------------------------------------------------------------------\n// Internals\n// ---------------------------------------------------------------------------\n\n/**\n * Pull a `$link` value out of common BlobRef shapes:\n * - `{ $link: \"...\" }`\n * - `{ ref: { $link: \"...\" } }`\n * - `{ $type: \"blob\", ref: { $link: \"...\" }, mimeType, size }`\n */\nfunction extractLinkFromObject(obj: Record<string, unknown>): string | null {\n const directLink = obj['$link'];\n if (typeof directLink === 'string' && directLink.length > 0) {\n return directLink;\n }\n const ref = obj['ref'];\n if (ref && typeof ref === 'object') {\n const nestedLink = (ref as Record<string, unknown>)['$link'];\n if (typeof nestedLink === 'string' && nestedLink.length > 0) {\n return nestedLink;\n }\n }\n return null;\n}\n\nfunction safeUrl(input: string): URL | null {\n try {\n return new URL(input);\n } catch {\n return null;\n }\n}\n","/**\n * F4: Nested $type injection for AT Protocol records.\n *\n * STATUS: Planned — not yet wired into the execute flow.\n *\n * PROBLEM\n * -------\n * When the resource mapper flattens refs/single-ref unions into dotted\n * fields (e.g. `theme.background.r`), `unflattenDottedKeys` reconstructs\n * nested objects — but those objects lack the `$type` discriminators that\n * AT Protocol requires for union-typed sub-objects.\n *\n * Currently sent:\n * { \"theme\": { \"background\": { \"r\": 255, \"g\": 255, \"b\": 255 } } }\n *\n * Expected:\n * { \"theme\": { \"$type\": \"site.standard.theme.basic\",\n * \"background\": { \"$type\": \"site.standard.theme.color#rgb\",\n * \"r\": 255, \"g\": 255, \"b\": 255 } } }\n *\n * No special-casing for any value types — only plain objects get $type\n * injected. Strings, numbers, arrays, etc. pass through unchanged.\n *\n * APPROACH\n * -------\n * A new `injectNestedTypes()` function walks the unflattened record\n * alongside the resolved lexicon schema. For each nested object whose\n * schema property is a `ref` or single-ref `union`, it injects `$type`\n * with the ref target NSID, then recurses into the resolved ref's schema.\n *\n * This mirrors the recursive resolution in `flattenRefProperties` but\n * operates on values instead of field definitions.\n *\n * INTEGRATION POINT\n * -----------------\n * Called in the execute flow (Atproto.node.ts) after `applyBlobUploads`:\n *\n * const record = buildRecordFromNodeParams(recordData);\n * const schema = await resolveLexiconSchema(agent, collection);\n * const withBlobs = await applyBlobUploads(record, schema, agent, i, this);\n * const withTypes = await injectNestedTypes(withBlobs, schema, agent);\n * // → use withTypes for createRecord/putRecord\n *\n * EDGE CASES\n * ----------\n * - User-provided $type is preserved (never overwritten)\n * - Array values are skipped (user provides full JSON for arrays of objects)\n * - Inline object properties (not from refs) don't get $type\n * - Recursion capped at MAX_REF_DEPTH (3) to match field generation\n * - Resolution failures are silently skipped (object works without $type\n * on lenient PDS implementations)\n *\n * DEPENDENCIES\n * ------------\n * - `getResolvableRef()` — export from fieldMapping.ts (currently private)\n * - `resolveRefProperties()` + `resolveLexiconSchema()` from lexicon.ts\n * - Schema cache ensures no redundant network calls at runtime\n */\n\nimport type { Agent } from '@atproto/api';\nimport type { LexiconSchema } from './lexicon';\nimport { resolveLexiconSchema, resolveRefProperties } from './lexicon';\nimport { getResolvableRef } from './fieldMapping';\n\nconst MAX_REF_DEPTH = 3;\n\n/**\n * Walk a record and inject `$type` on nested objects that correspond to\n * resolved refs or single-ref unions in the lexicon schema.\n *\n * @param record - The constructed record (after unflatten + blob uploads).\n * @param schema - The resolved lexicon schema (null → pass-through).\n * @param agent - Authenticated agent (for cross-document ref resolution).\n * @returns A new record with `$type` injected on nested ref objects.\n */\nexport async function injectNestedTypes(\n record: Record<string, unknown>,\n schema: LexiconSchema | null,\n agent: Agent,\n): Promise<Record<string, unknown>> {\n if (!schema) return record;\n return walkAndInject(record, schema.properties, schema, agent, 0);\n}\n\n/**\n * Recursive walker — for each property that is a ref/single-ref union,\n * inject `$type` on the value object and recurse into the resolved schema.\n */\nasync function walkAndInject(\n obj: Record<string, unknown>,\n properties: Record<string, import('./lexicon').LexiconProperty>,\n rootSchema: LexiconSchema,\n agent: Agent,\n depth: number,\n): Promise<Record<string, unknown>> {\n if (depth >= MAX_REF_DEPTH) return obj;\n\n const result = { ...obj };\n\n for (const [key, value] of Object.entries(result)) {\n const propDef = properties[key];\n if (!propDef) continue;\n\n const refTarget = getResolvableRef(propDef);\n if (!refTarget) continue;\n\n // Only process plain objects (not arrays, primitives, null)\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n continue;\n }\n\n const nested = { ...(value as Record<string, unknown>) };\n\n // Inject $type if user hasn't already provided one\n if (!nested['$type']) {\n nested['$type'] = refTarget;\n }\n\n // Resolve the ref to get sub-property definitions and recurse\n const resolved = await resolveRefProperties(\n refTarget,\n rootSchema,\n (nsid: string) => resolveLexiconSchema(agent, nsid),\n );\n\n if (resolved && Object.keys(resolved.properties).length > 0) {\n result[key] = await walkAndInject(\n nested,\n resolved.properties,\n rootSchema,\n agent,\n depth + 1,\n );\n } else {\n result[key] = nested;\n }\n }\n\n return result;\n}\n","/**\n * Execution-time record validation against the lexicon schema.\n *\n * Runs before the record is sent to the PDS, providing clear field-level\n * error messages instead of the PDS's opaque \"InvalidRecord\" rejections.\n *\n * Checks:\n * - Required fields are present\n * - Basic type correctness (string, integer, boolean, array, object)\n * - $type discriminator on union-typed sub-objects\n * - Recursion into nested refs (up to depth 3)\n */\n\nimport type { Agent } from '@atproto/api';\nimport type { LexiconProperty, LexiconSchema } from './lexicon';\nimport { resolveLexiconSchema, resolveRefProperties } from './lexicon';\nimport { getResolvableRef } from './fieldMapping';\n\nconst MAX_DEPTH = 3;\n\n/** Auto-injected fields — skip required checks for these. */\nconst AUTO_INJECTED = new Set(['$type', 'createdAt']);\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Validate a record against its lexicon schema.\n *\n * @returns An array of human-readable error strings. Empty = valid.\n */\nexport async function validateRecord(\n record: Record<string, unknown>,\n schema: LexiconSchema | null,\n agent: Agent,\n): Promise<string[]> {\n if (!schema || Object.keys(schema.properties).length === 0) return [];\n\n return walkAndValidate(\n record,\n schema.properties,\n schema.required,\n schema,\n agent,\n '',\n 0,\n );\n}\n\n// ---------------------------------------------------------------------------\n// Recursive walker\n// ---------------------------------------------------------------------------\n\nasync function walkAndValidate(\n obj: Record<string, unknown>,\n properties: Record<string, LexiconProperty>,\n required: string[],\n rootSchema: LexiconSchema,\n agent: Agent,\n prefix: string,\n depth: number,\n): Promise<string[]> {\n const errors: string[] = [];\n\n // 1. Required fields\n for (const name of required) {\n if (AUTO_INJECTED.has(name)) continue;\n const path = prefix ? `${prefix}.${name}` : name;\n const value = obj[name];\n const prop = properties[name];\n // Nullable required fields accept explicit null\n if (value === null && prop?.nullable) continue;\n if (value === undefined || value === null || value === '') {\n errors.push(`Required field '${path}' is missing`);\n }\n }\n\n // 2. Type & structure checks on provided fields\n for (const [name, value] of Object.entries(obj)) {\n if (name === '$type') continue;\n const path = prefix ? `${prefix}.${name}` : name;\n const prop = properties[name];\n if (!prop || value === undefined || value === null) continue;\n\n errors.push(...checkType(path, value, prop));\n\n // 3. Recurse into nested objects that came from refs/unions\n if (\n depth < MAX_DEPTH &&\n typeof value === 'object' &&\n value !== null &&\n !Array.isArray(value)\n ) {\n const refTarget = getResolvableRef(prop);\n if (refTarget) {\n const resolved = await resolveRefProperties(\n refTarget,\n rootSchema,\n (nsid: string) => resolveLexiconSchema(agent, nsid),\n );\n if (resolved && Object.keys(resolved.properties).length > 0) {\n const nested = await walkAndValidate(\n value as Record<string, unknown>,\n resolved.properties,\n resolved.required,\n rootSchema,\n agent,\n path,\n depth + 1,\n );\n errors.push(...nested);\n }\n }\n }\n }\n\n return errors;\n}\n\n// ---------------------------------------------------------------------------\n// Type checks\n// ---------------------------------------------------------------------------\n\nfunction checkType(\n path: string,\n value: unknown,\n prop: LexiconProperty,\n): string[] {\n const errors: string[] = [];\n\n switch (prop.type) {\n case 'string':\n if (typeof value !== 'string') {\n errors.push(`'${path}' must be a string, got ${describeType(value)}`);\n break;\n }\n // UTF-8 byte length constraints\n if (prop.maxLength) {\n const byteLen = Buffer.byteLength(value, 'utf8');\n if (byteLen > prop.maxLength)\n errors.push(`'${path}' is ${byteLen} bytes (max ${prop.maxLength})`);\n }\n if (prop.minLength) {\n const byteLen = Buffer.byteLength(value, 'utf8');\n if (byteLen < prop.minLength)\n errors.push(`'${path}' is ${byteLen} bytes (min ${prop.minLength})`);\n }\n // Grapheme count (Intl.Segmenter, Node 16+)\n if (prop.maxGraphemes || prop.minGraphemes) {\n const segmenter = new Intl.Segmenter();\n const count = [...segmenter.segment(value)].length;\n if (prop.maxGraphemes && count > prop.maxGraphemes)\n errors.push(`'${path}' has ${count} graphemes (max ${prop.maxGraphemes})`);\n if (prop.minGraphemes && count < prop.minGraphemes)\n errors.push(`'${path}' has ${count} graphemes (min ${prop.minGraphemes})`);\n }\n // Closed enum\n if (prop.enum?.length && !prop.enum.includes(value))\n errors.push(`'${path}' must be one of: ${prop.enum.join(', ')}`);\n // Const\n if (prop.const !== undefined && value !== prop.const)\n errors.push(`'${path}' must be '${prop.const}'`);\n break;\n\n case 'integer':\n if (typeof value !== 'number' || !Number.isInteger(value)) {\n errors.push(`'${path}' must be an integer, got ${describeType(value)}`);\n break;\n }\n if (prop.minimum !== undefined && value < prop.minimum)\n errors.push(`'${path}' must be ≥ ${prop.minimum}, got ${value}`);\n if (prop.maximum !== undefined && value > prop.maximum)\n errors.push(`'${path}' must be ≤ ${prop.maximum}, got ${value}`);\n if (prop.enum?.length && !prop.enum.includes(value))\n errors.push(`'${path}' must be one of: ${prop.enum.join(', ')}`);\n if (prop.const !== undefined && value !== prop.const)\n errors.push(`'${path}' must be ${prop.const}`);\n break;\n\n case 'boolean':\n if (typeof value !== 'boolean') {\n errors.push(\n `'${path}' must be true or false, got ${describeType(value)}`,\n );\n break;\n }\n if (prop.const !== undefined && value !== prop.const)\n errors.push(`'${path}' must be ${prop.const}`);\n break;\n\n case 'array':\n if (!Array.isArray(value)) {\n errors.push(\n `'${path}' must be a JSON array, got ${describeType(value)}`,\n );\n break;\n }\n if (prop.maxLength !== undefined && value.length > prop.maxLength)\n errors.push(`'${path}' has ${value.length} items (max ${prop.maxLength})`);\n if (prop.minLength !== undefined && value.length < prop.minLength)\n errors.push(`'${path}' has ${value.length} items (min ${prop.minLength})`);\n // Array item validation\n if (prop.items && prop.items.type !== 'unknown') {\n for (let idx = 0; idx < value.length; idx++) {\n const itemErrors = checkType(`${path}[${idx}]`, value[idx], prop.items);\n errors.push(...itemErrors);\n }\n }\n break;\n\n case 'union':\n if (\n typeof value === 'object' &&\n value !== null &&\n !Array.isArray(value)\n ) {\n const obj = value as Record<string, unknown>;\n if (!obj['$type'] && prop.refs?.length) {\n errors.push(\n `'${path}' requires a $type property (one of: ${prop.refs.join(', ')})`,\n );\n } else if (\n prop.refs?.length &&\n obj['$type'] &&\n !prop.refs.includes(obj['$type'] as string)\n ) {\n // Closed unions: explicit error mentioning \"closed union\"\n if (prop.closed) {\n errors.push(\n `'${path}' has $type '${obj['$type']}' which is not allowed in this closed union (expected: ${prop.refs.join(', ')})`,\n );\n } else {\n errors.push(\n `'${path}' has invalid $type '${obj['$type']}' — expected one of: ${prop.refs.join(', ')}`,\n );\n }\n }\n }\n break;\n\n case 'ref':\n case 'object':\n case 'unknown':\n if (\n typeof value !== 'object' ||\n value === null ||\n Array.isArray(value)\n ) {\n errors.push(\n `'${path}' must be a JSON object, got ${describeType(value)}`,\n );\n }\n break;\n\n case 'blob':\n // After applyBlobUploads, blobs should be { $type: 'blob', ref, ... }.\n // If it's still a string, the upload didn't happen (bad binary property name).\n if (typeof value === 'string') {\n errors.push(\n `'${path}' — binary property '${value}' was not found on the input. Attach a file via an HTTP Request or Read Binary File node.`,\n );\n }\n break;\n }\n\n return errors;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction describeType(value: unknown): string {\n if (value === null) return 'null';\n if (Array.isArray(value)) return 'array';\n return typeof value;\n}\n","import type {\n IDataObject,\n IExecuteFunctions,\n ILoadOptionsFunctions,\n INodeExecutionData,\n INodeType,\n INodeTypeDescription,\n ResourceMapperFields,\n} from 'n8n-workflow';\nimport { ApplicationError, NodeConnectionTypes, NodeOperationError } from 'n8n-workflow';\nimport type { Agent } from '@atproto/api';\n\nimport type { ListRecordsResult } from './operations';\nimport {\n createRecord,\n deleteRecord,\n getRecord,\n getBlob,\n listBlobs,\n listRecords,\n putRecord,\n resolveActorToDid,\n uploadBlob,\n applyConstValues,\n} from './operations';\n\n// Pagination guardrails for Return All. The page sizes match what each\n// XRPC endpoint accepts as a sensible upper bound; MAX_PAGES caps total\n// requests so a mis-configured workflow can't loop forever.\nconst PAGE_SIZE_RECORDS = 100;\nconst PAGE_SIZE_BLOBS = 500;\nconst MAX_PAGES = 1000;\nimport { createAgent, extractCollectionNsid, searchCollections } from './shared';\nimport { generateTid } from './tid';\nimport { resolveLexiconSchema } from './lexicon';\nimport { lexiconToResourceMapperFields } from './fieldMapping';\nimport { applyBlobUploads } from './blob';\nimport { parseBlobReference } from './blobInput';\nimport { injectNestedTypes } from './typeInjection';\nimport { validateRecord } from './validation';\n\n// ---------------------------------------------------------------------------\n// Error handling\n// ---------------------------------------------------------------------------\n\n/**\n * Maps XRPC error messages to user-friendly descriptions.\n */\nfunction friendlyError(error: unknown, context?: Record<string, string>): string {\n const message = error instanceof Error ? error.message : String(error);\n const status =\n error && typeof error === 'object' && 'status' in error\n ? (error as { status: number }).status\n : 0;\n\n if (status === 401 || status === 403 || message.includes('Authentication')) {\n return 'Authentication failed — check your app password';\n }\n if (message.includes('AccountTakedown') || message.includes('takendown')) {\n return 'Account is suspended';\n }\n if (message.includes('RecordNotFound')) {\n const where = context?.rkey\n ? `${context.collection ?? '?'}/${context.rkey}`\n : context?.collection ?? '?';\n return `Record not found at ${where}`;\n }\n if (message.includes('BlobNotFound')) {\n const where =\n context?.cid && context?.did\n ? `${context.did}/${context.cid}`\n : context?.cid ?? '?';\n return `Blob not found at ${where}`;\n }\n if (\n status === 413 ||\n message.includes('PayloadTooLarge') ||\n /blob too large/i.test(message)\n ) {\n return 'Blob too large — the PDS rejected the upload (bsky.social limits blobs to ~1 MB)';\n }\n if (status === 429 || message.includes('RateLimit')) {\n const match = message.match(/retry.*?(\\d+)/i);\n const seconds = match ? match[1] : '?';\n return `Rate limited — retry after ${seconds}s`;\n }\n if (\n message.includes('fetch failed') ||\n message.includes('network') ||\n message.includes('ECONNREFUSED') ||\n message.includes('ENOTFOUND')\n ) {\n const url = context?.serviceUrl ?? 'PDS';\n return `Could not reach ${url}`;\n }\n if (message.includes('InvalidRecord') || message.includes('invalid record')) {\n return `Record validation failed: ${message}`;\n }\n return message;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve the record key for get/put/delete operations.\n *\n * When the user leaves rkey empty, attempts to resolve the lexicon\n * schema and use the literal key (e.g. `self` for `app.bsky.actor.profile`).\n * Throws if the key can’t be determined.\n */\nasync function resolveRkey(\n rkey: string,\n collection: string,\n agent: Agent,\n): Promise<string> {\n if (rkey) return rkey;\n\n const schema = await resolveLexiconSchema(agent, collection);\n if (schema?.key === 'literal' && schema.literalKey) {\n return schema.literalKey;\n }\n\n throw new ApplicationError(\n `Record key is required for ${collection}. The lexicon does not declare a fixed key.`,\n );\n}\n\n// ---------------------------------------------------------------------------\n// Node\n// ---------------------------------------------------------------------------\n\nexport class Atproto implements INodeType {\n description: INodeTypeDescription = {\n displayName: 'AT Protocol',\n name: 'atproto',\n icon: 'file:atproto.svg',\n group: ['transform'],\n version: 1,\n subtitle:\n '={{ $parameter[\"operation\"] + \": \" + $parameter[\"resource\"] }}',\n description: 'CRUD records and manage blobs in any AT Protocol repo',\n defaults: {\n name: 'AT Protocol',\n },\n usableAsTool: true,\n inputs: [NodeConnectionTypes.Main],\n outputs: [NodeConnectionTypes.Main],\n credentials: [\n {\n name: 'atprotoApi',\n required: true,\n },\n ],\n properties: [\n // ------------------------------------------------------------------\n // Resource\n // ------------------------------------------------------------------\n {\n displayName: 'Resource',\n name: 'resource',\n type: 'options',\n noDataExpression: true,\n // Sorted alphabetically per @n8n/community-nodes lint rule.\n options: [\n { name: 'Blob', value: 'blob' },\n { name: 'Record', value: 'record' },\n ],\n default: 'record',\n },\n\n // ------------------------------------------------------------------\n // Operation — Record\n // ------------------------------------------------------------------\n {\n displayName: 'Operation',\n name: 'operation',\n type: 'options',\n noDataExpression: true,\n displayOptions: {\n show: { resource: ['record'] },\n },\n // Sorted alphabetically per @n8n/community-nodes lint rule.\n options: [\n {\n name: 'Create',\n value: 'createRecord',\n description: 'Create a new record in a collection',\n action: 'Create a record',\n },\n {\n name: 'Delete',\n value: 'deleteRecord',\n description: 'Delete a record by collection and record key',\n action: 'Delete a record',\n },\n {\n name: 'Get',\n value: 'getRecord',\n description: 'Get a record by collection and record key',\n action: 'Get a record',\n },\n {\n name: 'List',\n value: 'listRecords',\n description: 'List records in a collection with pagination',\n action: 'List records',\n },\n {\n name: 'Put',\n value: 'putRecord',\n description: 'Full-replace a record',\n action: 'Update a record',\n },\n ],\n default: 'createRecord',\n },\n\n // ------------------------------------------------------------------\n // Operation — Blob\n // ------------------------------------------------------------------\n {\n displayName: 'Operation',\n name: 'operation',\n type: 'options',\n noDataExpression: true,\n displayOptions: {\n show: { resource: ['blob'] },\n },\n // Sorted alphabetically per @n8n/community-nodes lint rule.\n options: [\n {\n name: 'Download',\n value: 'getBlob',\n description: 'Download a blob by CID from a repo',\n action: 'Download a blob',\n },\n {\n name: 'List',\n value: 'listBlobs',\n description: 'List blob CIDs in a repo with pagination',\n action: 'List blobs',\n },\n {\n name: 'Upload',\n value: 'uploadBlob',\n description: 'Upload a binary as a blob to the PDS',\n action: 'Upload a blob',\n },\n ],\n default: 'uploadBlob',\n },\n\n // ------------------------------------------------------------------\n // Collection (resourceLocator — searchable list + free text)\n // ------------------------------------------------------------------\n {\n displayName: 'Collection',\n name: 'collection',\n type: 'resourceLocator',\n required: true,\n description: 'The record collection to operate on',\n default: { mode: 'list', value: '' },\n displayOptions: {\n show: {\n operation: [\n 'createRecord',\n 'getRecord',\n 'putRecord',\n 'deleteRecord',\n 'listRecords',\n ],\n },\n },\n modes: [\n {\n displayName: 'From List',\n name: 'list',\n type: 'list',\n placeholder: 'Select a collection…',\n typeOptions: {\n searchListMethod: 'searchCollections',\n searchable: true,\n },\n },\n {\n displayName: 'By NSID',\n name: 'nsid',\n type: 'string',\n placeholder: 'e.g. app.bsky.feed.post',\n validation: [\n {\n type: 'regex',\n properties: {\n regex: '^[a-z][a-z0-9]*(\\\\.[a-zA-Z][a-zA-Z0-9]*){2,}$',\n errorMessage: 'Must be a valid NSID (e.g. app.bsky.feed.post)',\n },\n },\n ],\n },\n ],\n },\n\n // ------------------------------------------------------------------\n // Repo (for Get/List Record + List Blobs — optional, defaults to self)\n // ------------------------------------------------------------------\n {\n displayName: 'Repo (DID or Handle)',\n name: 'repo',\n type: 'string',\n placeholder: 'did:plc:... or user.bsky.social',\n description:\n 'Optional. The DID or handle of the repo. Defaults to the authenticated user. Useful for reading other users\\' public records or blobs.',\n displayOptions: {\n show: {\n operation: ['getRecord', 'listRecords', 'listBlobs'],\n },\n },\n default: '',\n },\n\n // ------------------------------------------------------------------\n // Repo (for Download Blob — can be DID or handle; optional when the\n // CID field is a bsky CDN URL with the DID embedded)\n // ------------------------------------------------------------------\n {\n displayName: 'Repo (DID or Handle)',\n name: 'repo',\n type: 'string',\n placeholder: 'did:plc:... or user.bsky.social',\n description:\n 'The DID or handle of the repo that owns the blob. Optional when the CID field is a bsky CDN URL — the DID is extracted from the URL.',\n displayOptions: {\n show: {\n operation: ['getBlob'],\n },\n },\n default: '',\n },\n\n // ------------------------------------------------------------------\n // CID / Blob reference (Download Blob only)\n // ------------------------------------------------------------------\n {\n displayName: 'Blob Reference',\n name: 'cid',\n type: 'string',\n required: true,\n placeholder:\n 'bafkreig... or https://cdn.bsky.app/img/.../<did>/<cid>@jpeg',\n description:\n 'A bare CID, a BlobRef JSON (e.g. `{\"$link\":\"bafkreig...\"}`), or a bsky CDN URL. CDN URLs also fill in the Repo.',\n displayOptions: {\n show: {\n operation: ['getBlob'],\n },\n },\n default: '',\n },\n\n // ------------------------------------------------------------------\n // Binary Property (Upload Blob — input, Download Blob — output)\n // ------------------------------------------------------------------\n {\n displayName: 'Input Binary Property',\n name: 'binaryPropertyName',\n type: 'string',\n required: true,\n default: 'data',\n placeholder: 'data',\n description:\n 'Name of the binary property on the incoming item containing the data to upload',\n displayOptions: {\n show: {\n operation: ['uploadBlob'],\n },\n },\n },\n {\n displayName: 'Output Binary Property',\n name: 'binaryPropertyName',\n type: 'string',\n required: true,\n default: 'data',\n placeholder: 'data',\n description:\n 'Name of the binary property to write the downloaded blob to on the output item',\n displayOptions: {\n show: {\n operation: ['getBlob'],\n },\n },\n },\n\n // ------------------------------------------------------------------\n // Upload Blob — advanced options\n // ------------------------------------------------------------------\n {\n displayName: 'Options',\n name: 'options',\n type: 'collection',\n placeholder: 'Add Option',\n default: {},\n displayOptions: {\n show: {\n operation: ['uploadBlob'],\n },\n },\n options: [\n {\n displayName: 'MIME Type Override',\n name: 'mimeTypeOverride',\n type: 'string',\n default: '',\n placeholder: 'image/jpeg',\n description:\n 'Override the MIME type sent to the PDS. Defaults to the binary metadata\\'s mimeType, or application/octet-stream if unset.',\n },\n ],\n },\n\n // ------------------------------------------------------------------\n // List Blobs — advanced options\n // ------------------------------------------------------------------\n {\n displayName: 'Options',\n name: 'options',\n type: 'collection',\n placeholder: 'Add Option',\n default: {},\n displayOptions: {\n show: {\n operation: ['listBlobs'],\n },\n },\n options: [\n {\n displayName: 'Since (Repo Revision)',\n name: 'since',\n type: 'string',\n default: '',\n placeholder: '3jzfc...',\n description:\n 'Only list blobs added after this repo revision. Useful for incremental sync.',\n },\n ],\n },\n\n // ------------------------------------------------------------------\n // Record Key — shown for Get/Put/Delete\n // ------------------------------------------------------------------\n {\n displayName: 'Record Key (Rkey)',\n name: 'rkey',\n type: 'string',\n placeholder: '3jzfcijpj2z2a',\n description:\n 'The record key. Leave empty for collections that use a fixed key (e.g. app.bsky.actor.profile always uses \"self\").',\n displayOptions: {\n show: {\n operation: ['getRecord', 'putRecord', 'deleteRecord'],\n },\n },\n default: '',\n },\n\n // ------------------------------------------------------------------\n // Record Key — Create mode (auto TID or custom)\n // ------------------------------------------------------------------\n {\n displayName: 'Record Key',\n name: 'rkeyMode',\n type: 'options',\n options: [\n {\n name: 'Auto-Generate (TID)',\n value: 'auto',\n description: 'Generate a timestamp-based record key (TID) automatically',\n },\n {\n name: 'Custom',\n value: 'custom',\n description: 'Provide a custom record key',\n },\n ],\n displayOptions: {\n show: {\n operation: ['createRecord'],\n },\n },\n default: 'auto',\n },\n {\n displayName: 'Custom Record Key',\n name: 'rkey',\n type: 'string',\n required: true,\n placeholder: 'my-custom-key',\n description: 'A custom record key (rkey)',\n displayOptions: {\n show: {\n operation: ['createRecord'],\n rkeyMode: ['custom'],\n },\n },\n default: '',\n },\n\n // ------------------------------------------------------------------\n // Record Data — resourceMapper for Create/Put (Phase 2)\n // Falls back to JSON when lexicon cannot be resolved.\n // ------------------------------------------------------------------\n {\n displayName: 'Record Data',\n name: 'recordData',\n type: 'resourceMapper',\n default: {\n mappingMode: 'defineBelow',\n value: null,\n },\n required: true,\n typeOptions: {\n resourceMapper: {\n resourceMapperMethod: 'getRecordFields',\n mode: 'add',\n fieldWords: {\n singular: 'field',\n plural: 'fields',\n },\n supportAutoMap: true,\n noFieldsError:\n 'Could not resolve lexicon for this NSID. Enter record data as JSON using an expression, or check the collection NSID.',\n },\n loadOptionsDependsOn: ['collection.value'],\n },\n displayOptions: {\n show: {\n operation: ['createRecord', 'putRecord'],\n },\n },\n },\n\n // ------------------------------------------------------------------\n // Options (advanced / less-common fields)\n // ------------------------------------------------------------------\n {\n displayName: 'Options',\n name: 'options',\n type: 'collection',\n placeholder: 'Add Option',\n default: {},\n displayOptions: {\n show: {\n operation: ['createRecord', 'putRecord', 'deleteRecord'],\n },\n },\n options: [\n {\n displayName: 'Swap Commit (CID)',\n name: 'swapCommit',\n type: 'string',\n default: '',\n placeholder: 'bafyreia...',\n description:\n 'Compare-and-swap with the current commit CID. The write is rejected if the repo head does not match.',\n },\n ],\n },\n\n // ------------------------------------------------------------------\n // Return All (List Records / List Blobs)\n // ------------------------------------------------------------------\n {\n displayName: 'Return All',\n name: 'returnAll',\n type: 'boolean',\n default: false,\n description: 'Whether to return all results or only up to a given limit',\n displayOptions: {\n show: {\n operation: ['listRecords', 'listBlobs'],\n },\n },\n },\n\n // ------------------------------------------------------------------\n // Limit (only when Return All is off)\n // ------------------------------------------------------------------\n {\n displayName: 'Limit',\n name: 'limit',\n type: 'number',\n typeOptions: {\n minValue: 1,\n maxValue: 1000,\n },\n displayOptions: {\n show: {\n operation: ['listRecords', 'listBlobs'],\n returnAll: [false],\n },\n },\n default: 50,\n description: 'Max number of results to return',\n },\n\n // ------------------------------------------------------------------\n // Cursor (only when Return All is off)\n // ------------------------------------------------------------------\n {\n displayName: 'Cursor',\n name: 'cursor',\n type: 'string',\n placeholder: '...',\n description:\n 'Optional. Cursor for pagination. Pass the cursor from a previous response to get the next page.',\n displayOptions: {\n show: {\n operation: ['listRecords', 'listBlobs'],\n returnAll: [false],\n },\n },\n default: '',\n },\n ],\n };\n\n // -----------------------------------------------------------------------\n // Resource mapper method — called in the n8n editor to resolve fields\n // -----------------------------------------------------------------------\n methods = {\n listSearch: {\n searchCollections,\n },\n resourceMapping: {\n getRecordFields: async function (\n this: ILoadOptionsFunctions,\n ): Promise<ResourceMapperFields> {\n const nsid = extractCollectionNsid(\n this.getNodeParameter('collection'),\n );\n\n if (!nsid) {\n return { fields: [] };\n }\n\n // Try to get credentials for PDS-based resolution\n let agent: Agent | null = null;\n try {\n const credentials = await this.getCredentials('atprotoApi');\n if (credentials) {\n agent = await createAgent(credentials as IDataObject);\n }\n } catch {\n // Credentials not available — fall back to DNS-based resolution\n agent = null;\n }\n\n const schema = await resolveLexiconSchema(agent, nsid);\n\n if (!schema) {\n // Cannot resolve — return empty fields. n8n will show a warning\n // and the user can switch to JSON mode or provide a raw JSON value\n // via an expression.\n return { fields: [] };\n }\n\n const fields = await lexiconToResourceMapperFields(schema, agent);\n return { fields };\n },\n },\n };\n\n // -----------------------------------------------------------------------\n // Execute — called at workflow runtime\n // -----------------------------------------------------------------------\n async execute(this: IExecuteFunctions) {\n const items = this.getInputData();\n const returnData: INodeExecutionData[] = [];\n\n // Get credentials once — shared across all items in this execution\n const credentials = await this.getCredentials('atprotoApi');\n const agent = await createAgent(credentials as IDataObject);\n\n // Process each input item\n for (let i = 0; i < items.length; i++) {\n try {\n const operation = this.getNodeParameter('operation', i) as string;\n // `collection` is hidden via displayOptions for blob operations\n // (uploadBlob / getBlob / listBlobs). Pass an empty resourceLocator\n // default so getNodeParameter doesn't throw \"Could not get parameter\"\n // — blob ops never use the value anyway.\n const collection = extractCollectionNsid(\n this.getNodeParameter('collection', i, { mode: 'list', value: '' }),\n );\n\n let result: IDataObject;\n\n switch (operation) {\n case 'createRecord': {\n const rkeyMode = this.getNodeParameter('rkeyMode', i) as string;\n const rkey =\n rkeyMode === 'custom'\n ? (this.getNodeParameter('rkey', i) as string)\n : generateTid();\n const recordData = this.getNodeParameter('recordData', i);\n const record = buildRecordFromNodeParams(recordData);\n const opts = this.getNodeParameter('options', i, {}) as IDataObject;\n const swapCommit = (opts.swapCommit as string) ?? '';\n\n // Resolve schema once for all downstream steps\n const schema = await resolveLexiconSchema(agent, collection);\n\n // Phase 5: inject const values from schema\n applyConstValues(record, schema);\n\n // Phase 3: upload blobs referenced by binary property names\n const recordWithBlobs = await applyBlobUploads(\n record,\n schema,\n agent,\n i,\n this,\n );\n\n // Phase 4: inject $type on nested ref/union objects\n const recordWithTypes = await injectNestedTypes(\n recordWithBlobs,\n schema,\n agent,\n );\n\n // Phase 5: validate before sending\n const createErrors = await validateRecord(\n recordWithTypes,\n schema,\n agent,\n );\n if (createErrors.length > 0) {\n throw new NodeOperationError(\n this.getNode(),\n `Record validation failed:\\n• ${createErrors.join('\\n• ')}`,\n { itemIndex: i },\n );\n }\n\n const res = await createRecord(agent, {\n collection,\n rkey,\n record: recordWithTypes,\n ...(swapCommit ? { swapCommit } : {}),\n });\n result = res as unknown as IDataObject;\n break;\n }\n\n case 'getRecord': {\n const rkey = await resolveRkey(\n this.getNodeParameter('rkey', i) as string,\n collection,\n agent,\n );\n const repo = this.getNodeParameter('repo', i) as string;\n\n const res = await getRecord(agent, {\n collection,\n rkey,\n ...(repo ? { repo } : {}),\n });\n result = res as unknown as IDataObject;\n break;\n }\n\n case 'putRecord': {\n const rkey = await resolveRkey(\n this.getNodeParameter('rkey', i) as string,\n collection,\n agent,\n );\n const recordData = this.getNodeParameter('recordData', i);\n const record = buildRecordFromNodeParams(recordData);\n const putOpts = this.getNodeParameter('options', i, {}) as IDataObject;\n const swapCommit = (putOpts.swapCommit as string) ?? '';\n\n // Resolve schema once for all downstream steps\n const schema = await resolveLexiconSchema(agent, collection);\n\n // Phase 5: inject const values from schema\n applyConstValues(record, schema);\n\n // Phase 3: upload blobs referenced by binary property names\n const recordWithBlobs = await applyBlobUploads(\n record,\n schema,\n agent,\n i,\n this,\n );\n\n // Phase 4: inject $type on nested ref/union objects\n const recordWithTypes = await injectNestedTypes(\n recordWithBlobs,\n schema,\n agent,\n );\n\n // Phase 5: validate before sending\n const putErrors = await validateRecord(\n recordWithTypes,\n schema,\n agent,\n );\n if (putErrors.length > 0) {\n throw new NodeOperationError(\n this.getNode(),\n `Record validation failed:\\n• ${putErrors.join('\\n• ')}`,\n { itemIndex: i },\n );\n }\n\n const res = await putRecord(agent, {\n collection,\n rkey,\n record: recordWithTypes,\n ...(swapCommit ? { swapCommit } : {}),\n });\n result = res as unknown as IDataObject;\n break;\n }\n\n case 'deleteRecord': {\n const rkey = await resolveRkey(\n this.getNodeParameter('rkey', i) as string,\n collection,\n agent,\n );\n const delOpts = this.getNodeParameter('options', i, {}) as IDataObject;\n const swapCommit = (delOpts.swapCommit as string) ?? '';\n\n const res = await deleteRecord(agent, {\n collection,\n rkey,\n ...(swapCommit ? { swapCommit } : {}),\n });\n result = (res ?? { success: true }) as unknown as IDataObject;\n break;\n }\n\n case 'listRecords': {\n const returnAll = this.getNodeParameter(\n 'returnAll',\n i,\n false,\n ) as boolean;\n const repo = this.getNodeParameter('repo', i) as string;\n\n if (returnAll) {\n // Paginate internally. Concatenate all records across pages.\n const allRecords: ListRecordsResult['records'] = [];\n let pageCursor: string | undefined = undefined;\n for (let page = 0; page < MAX_PAGES; page++) {\n const pageRes = await listRecords(agent, {\n collection,\n limit: PAGE_SIZE_RECORDS,\n ...(pageCursor ? { cursor: pageCursor } : {}),\n ...(repo ? { repo } : {}),\n });\n allRecords.push(...pageRes.records);\n if (!pageRes.cursor) break;\n pageCursor = pageRes.cursor;\n }\n result = { records: allRecords } as unknown as IDataObject;\n } else {\n const limit = this.getNodeParameter('limit', i) as number;\n const cursor = this.getNodeParameter('cursor', i) as string;\n const res = await listRecords(agent, {\n collection,\n limit,\n ...(cursor ? { cursor } : {}),\n ...(repo ? { repo } : {}),\n });\n result = res as unknown as IDataObject;\n }\n break;\n }\n\n case 'uploadBlob': {\n const binaryPropertyName = this.getNodeParameter(\n 'binaryPropertyName',\n i,\n ) as string;\n const opts = this.getNodeParameter('options', i, {}) as {\n mimeTypeOverride?: string;\n };\n\n const buffer = await this.helpers.getBinaryDataBuffer(\n i,\n binaryPropertyName,\n );\n if (!buffer) {\n throw new NodeOperationError(\n this.getNode(),\n `Binary property \"${binaryPropertyName}\" not found on input item`,\n { itemIndex: i },\n );\n }\n\n const binaryMeta = items[i].binary?.[binaryPropertyName];\n const mimeType =\n opts.mimeTypeOverride?.trim() ||\n binaryMeta?.mimeType ||\n 'application/octet-stream';\n\n const res = await uploadBlob(agent, {\n data: buffer,\n mimeType,\n });\n // Flat sibling fields for ergonomic expression access:\n // `{{ $json.cid }}` instead of `{{ $json.blob.ref.$link }}`.\n result = {\n blob: res.blob,\n cid: res.blob.ref.$link,\n mimeType: res.blob.mimeType,\n size: res.blob.size,\n } as unknown as IDataObject;\n break;\n }\n\n case 'getBlob': {\n const repoInput = this.getNodeParameter('repo', i) as string;\n const blobInput = this.getNodeParameter('cid', i) as string;\n const binaryPropertyName = this.getNodeParameter(\n 'binaryPropertyName',\n i,\n ) as string;\n\n // Parse the blob reference (bare CID / JSON ref / CDN URL).\n // CDN URLs may carry a DID that we use when Repo is empty.\n let parsed;\n try {\n parsed = parseBlobReference(blobInput);\n } catch (err) {\n throw new NodeOperationError(\n this.getNode(),\n err instanceof Error ? err.message : String(err),\n { itemIndex: i },\n );\n }\n const { cid } = parsed;\n\n const repoSource = repoInput?.trim() || parsed.did || '';\n if (!repoSource) {\n throw new NodeOperationError(\n this.getNode(),\n 'Repo (DID or handle) is required when the Blob Reference does not contain a DID',\n { itemIndex: i },\n );\n }\n const did = await resolveActorToDid(agent, repoSource);\n\n try {\n const res = await getBlob(agent, { did, cid });\n\n const binaryData = await this.helpers.prepareBinaryData(\n res.data,\n cid,\n res.mimeType || undefined,\n );\n\n returnData.push({\n json: {\n cid,\n did,\n mimeType: res.mimeType,\n size: res.size,\n },\n binary: { [binaryPropertyName]: binaryData },\n pairedItem: { item: i },\n });\n } catch (err) {\n // Re-throw with cid/did context so friendlyError can produce\n // a useful 'Blob not found at <did>/<cid>' message.\n if (this.continueOnFail()) {\n returnData.push({\n json: {\n error: friendlyError(err, { cid, did }),\n ...(err instanceof Error ? { message: err.message } : {}),\n },\n pairedItem: { item: i },\n });\n continue;\n }\n throw new NodeOperationError(\n this.getNode(),\n friendlyError(err, { cid, did }),\n { itemIndex: i },\n );\n }\n continue;\n }\n\n case 'listBlobs': {\n const returnAll = this.getNodeParameter(\n 'returnAll',\n i,\n false,\n ) as boolean;\n const repoInput = this.getNodeParameter('repo', i) as string;\n const listOpts = this.getNodeParameter('options', i, {}) as {\n since?: string;\n };\n\n const did = repoInput\n ? await resolveActorToDid(agent, repoInput)\n : agent.did ?? undefined;\n\n if (returnAll) {\n // Paginate internally; emit one item per CID with no cursor.\n let pageCursor: string | undefined = undefined;\n for (let page = 0; page < MAX_PAGES; page++) {\n const pageRes = await listBlobs(agent, {\n ...(did ? { did } : {}),\n limit: PAGE_SIZE_BLOBS,\n ...(pageCursor ? { cursor: pageCursor } : {}),\n ...(listOpts.since ? { since: listOpts.since } : {}),\n });\n for (const blobCid of pageRes.cids) {\n returnData.push({\n json: {\n cid: blobCid,\n ...(did ? { did } : {}),\n },\n pairedItem: { item: i },\n });\n }\n if (!pageRes.cursor) break;\n pageCursor = pageRes.cursor;\n }\n } else {\n const limit = this.getNodeParameter('limit', i) as number;\n const cursor = this.getNodeParameter('cursor', i) as string;\n const res = await listBlobs(agent, {\n ...(did ? { did } : {}),\n limit,\n ...(cursor ? { cursor } : {}),\n ...(listOpts.since ? { since: listOpts.since } : {}),\n });\n\n // One item per CID; cursor attached to each so any downstream\n // node can drive the next page.\n for (const blobCid of res.cids) {\n returnData.push({\n json: {\n cid: blobCid,\n ...(did ? { did } : {}),\n ...(res.cursor ? { cursor: res.cursor } : {}),\n },\n pairedItem: { item: i },\n });\n }\n }\n continue;\n }\n\n default:\n throw new NodeOperationError(\n this.getNode(),\n `Unknown operation: ${operation}`,\n { itemIndex: i },\n );\n }\n\n returnData.push({\n json: result,\n pairedItem: { item: i },\n });\n } catch (error) {\n if (this.continueOnFail()) {\n returnData.push({\n json: {\n error: friendlyError(error),\n ...(error instanceof Error ? { message: error.message } : {}),\n },\n pairedItem: { item: i },\n });\n continue;\n }\n throw new NodeOperationError(\n this.getNode(),\n error instanceof Error ? error : new Error(String(error)),\n { itemIndex: i },\n );\n }\n }\n\n return [returnData];\n }\n}\n\n// ---------------------------------------------------------------------------\n// Record building helper\n// ---------------------------------------------------------------------------\n\n/**\n * Build a record object from node parameters.\n *\n * Handles three input formats:\n * 1. Raw JSON string (legacy / fallback)\n * 2. `resourceMapper` value object `{ mappingMode, value, ... }`\n * 3. Plain object (when called from an expression)\n *\n * For resourceMapper values, dotted keys produced by ref/object flattening\n * (e.g. `reply.root`, `reply.parent`) are un-flattened into nested objects\n * (`{ reply: { root, parent } }`) so the resulting record matches the\n * lexicon's expected shape.\n *\n * `$type` and `createdAt` are NOT handled here — they're auto-injected in\n * `operations.ts`.\n */\nexport function buildRecordFromNodeParams(\n recordData: unknown,\n): Record<string, unknown> {\n // Format 1: raw JSON string\n if (typeof recordData === 'string') {\n try {\n return JSON.parse(recordData) as Record<string, unknown>;\n } catch {\n return {};\n }\n }\n\n // Format 2: resourceMapper value\n if (\n recordData &&\n typeof recordData === 'object' &&\n 'mappingMode' in recordData &&\n 'value' in recordData\n ) {\n const rm = recordData as {\n mappingMode: string;\n value: Record<string, unknown> | null;\n };\n\n if (!rm.value) return {};\n\n // Both defineBelow and autoMapInputData produce a flat key/value object.\n // Un-flatten dotted keys back into nested objects.\n return unflattenDottedKeys(rm.value);\n }\n\n // Format 3: plain object (e.g. from an expression)\n return (recordData ?? {}) as Record<string, unknown>;\n}\n\n/**\n * Convert a flat object with dotted keys into a nested object.\n *\n * `{ \"reply.root\": \"x\", \"reply.parent\": \"y\", text: \"hi\" }`\n * becomes\n * `{ reply: { root: \"x\", parent: \"y\" }, text: \"hi\" }`.\n *\n * Conflicts (a dotted key whose prefix is also a leaf) prefer the more\n * specific (dotted) value and discard the conflicting leaf.\n */\nexport function unflattenDottedKeys(\n flat: Record<string, unknown>,\n): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(flat)) {\n if (value === undefined || value === '') {\n // Skip empty/undefined values — the user didn't fill in this field.\n // Preserve `null` for nullable fields (the PDS accepts null when\n // the schema declares a field as nullable).\n continue;\n }\n\n if (!key.includes('.')) {\n result[key] = value;\n continue;\n }\n\n const parts = key.split('.');\n let cursor: Record<string, unknown> = result;\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i];\n const existing = cursor[part];\n if (\n existing === undefined ||\n existing === null ||\n typeof existing !== 'object'\n ) {\n cursor[part] = {};\n }\n cursor = cursor[part] as Record<string, unknown>;\n }\n cursor[parts[parts.length - 1]] = value;\n }\n\n return result;\n}\n"],"mappings":"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;IAAS,CACpC;IACA,SAAS;GACX;GAKA;IACE,aAAa;IACb,MAAM;IACN,MAAM;IACN,kBAAkB;IAClB,gBAAgB,EACd,MAAM,EAAE,UAAU,CAAC,QAAQ,EAAE,EAC/B;IAEA,SAAS;KACP;MACE,MAAM;MACN,OAAO;MACP,aAAa;MACb,QAAQ;KACV;KACA;MACE,MAAM;MACN,OAAO;MACP,aAAa;MACb,QAAQ;KACV;KACA;MACE,MAAM;MACN,OAAO;MACP,aAAa;MACb,QAAQ;KACV;KACA;MACE,MAAM;MACN,OAAO;MACP,aAAa;MACb,QAAQ;KACV;KACA;MACE,MAAM;MACN,OAAO;MACP,aAAa;MACb,QAAQ;KACV;IACF;IACA,SAAS;GACX;GAKA;IACE,aAAa;IACb,MAAM;IACN,MAAM;IACN,kBAAkB;IAClB,gBAAgB,EACd,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,EAC7B;IAEA,SAAS;KACP;MACE,MAAM;MACN,OAAO;MACP,aAAa;MACb,QAAQ;KACV;KACA;MACE,MAAM;MACN,OAAO;MACP,aAAa;MACb,QAAQ;KACV;KACA;MACE,MAAM;MACN,OAAO;MACP,aAAa;MACb,QAAQ;KACV;IACF;IACA,SAAS;GACX;GAKA;IACE,aAAa;IACb,MAAM;IACN,MAAM;IACN,UAAU;IACV,aAAa;IACb,SAAS;KAAE,MAAM;KAAQ,OAAO;IAAG;IACnC,gBAAgB,EACd,MAAM,EACJ,WAAW;KACT;KACA;KACA;KACA;KACA;IACF,EACF,EACF;IACA,OAAO,CACL;KACE,aAAa;KACb,MAAM;KACN,MAAM;KACN,aAAa;KACb,aAAa;MACX,kBAAkB;MAClB,YAAY;KACd;IACF,GACA;KACE,aAAa;KACb,MAAM;KACN,MAAM;KACN,aAAa;KACb,YAAY,CACV;MACE,MAAM;MACN,YAAY;OACV,OAAO;OACP,cAAc;MAChB;KACF,CACF;IACF,CACF;GACF;GAKA;IACE,aAAa;IACb,MAAM;IACN,MAAM;IACN,aAAa;IACb,aACE;IACF,gBAAgB,EACd,MAAM,EACJ,WAAW;KAAC;KAAa;KAAe;IAAW,EACrD,EACF;IACA,SAAS;GACX;GAMA;IACE,aAAa;IACb,MAAM;IACN,MAAM;IACN,aAAa;IACb,aACE;IACF,gBAAgB,EACd,MAAM,EACJ,WAAW,CAAC,SAAS,EACvB,EACF;IACA,SAAS;GACX;GAKA;IACE,aAAa;IACb,MAAM;IACN,MAAM;IACN,UAAU;IACV,aACE;IACF,aACE;IACF,gBAAgB,EACd,MAAM,EACJ,WAAW,CAAC,SAAS,EACvB,EACF;IACA,SAAS;GACX;GAKA;IACE,aAAa;IACb,MAAM;IACN,MAAM;IACN,UAAU;IACV,SAAS;IACT,aAAa;IACb,aACE;IACF,gBAAgB,EACd,MAAM,EACJ,WAAW,CAAC,YAAY,EAC1B,EACF;GACF;GACA;IACE,aAAa;IACb,MAAM;IACN,MAAM;IACN,UAAU;IACV,SAAS;IACT,aAAa;IACb,aACE;IACF,gBAAgB,EACd,MAAM,EACJ,WAAW,CAAC,SAAS,EACvB,EACF;GACF;GAKA;IACE,aAAa;IACb,MAAM;IACN,MAAM;IACN,aAAa;IACb,SAAS,CAAC;IACV,gBAAgB,EACd,MAAM,EACJ,WAAW,CAAC,YAAY,EAC1B,EACF;IACA,SAAS,CACP;KACE,aAAa;KACb,MAAM;KACN,MAAM;KACN,SAAS;KACT,aAAa;KACb,aACE;IACJ,CACF;GACF;GAKA;IACE,aAAa;IACb,MAAM;IACN,MAAM;IACN,aAAa;IACb,SAAS,CAAC;IACV,gBAAgB,EACd,MAAM,EACJ,WAAW,CAAC,WAAW,EACzB,EACF;IACA,SAAS,CACP;KACE,aAAa;KACb,MAAM;KACN,MAAM;KACN,SAAS;KACT,aAAa;KACb,aACE;IACJ,CACF;GACF;GAKA;IACE,aAAa;IACb,MAAM;IACN,MAAM;IACN,aAAa;IACb,aACE;IACF,gBAAgB,EACd,MAAM,EACJ,WAAW;KAAC;KAAa;KAAa;IAAc,EACtD,EACF;IACA,SAAS;GACX;GAKA;IACE,aAAa;IACb,MAAM;IACN,MAAM;IACN,SAAS,CACP;KACE,MAAM;KACN,OAAO;KACP,aAAa;IACf,GACA;KACE,MAAM;KACN,OAAO;KACP,aAAa;IACf,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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-atproto",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Generic AT Protocol node for n8n — CRUD any record in any lexicon",
5
5
  "keywords": [
6
6
  "n8n-community-node-package"