Skip to content

Resolve dotted and array paths in request receiver topic_identifier - #34

Open
leggetter wants to merge 3 commits into
mainfrom
claude/receiver-dotted-topic-paths
Open

Resolve dotted and array paths in request receiver topic_identifier#34
leggetter wants to merge 3 commits into
mainfrom
claude/receiver-dotted-topic-paths

Conversation

@leggetter

Copy link
Copy Markdown
Collaborator

The receiver resolved topic_identifier with a flat lookup:

topic = output.headers[identifier] || output.body?.[identifier];

23 providers declare an identifier that nests the event type - data.type, events[].eventType, entry[].changes[].field and so on. None resolve through a flat lookup, so capturing any of them with yarn dev:receiver named every sample untitled-<hash>.

Raised by the Copilot reviewer on #33. The finding was correct about the mechanism but attributed it to that PR; it is pre-existing and affects 22 providers besides the one #33 adds.

Measured, not asserted

One real sample per affected provider, replayed through the receiver into an isolated tree containing only index.json files so every write is observable:

correctly named untitled
before (main) 0 of 22 22
after 19 of 22 2

Passing includes the array forms: okta (data.events[].eventType), whatsapp (entry[].changes[].field), klaviyo (data[].topic), microsoft-graph (value[].changeType), fastspring (events[].type).

Three that still don't resolve, deliberately left alone

These are stale configs, not resolver limitations. Each needs a decision about the right value, which doesn't belong in a mechanical fix:

Provider Declares Sample actually has
mailgun event-data.event event at top level
twitter data.event_type direct_message_events at top level
xero events[].eventType → resolves to CREATE topic is CREDITNOTE/CREATE, a composite of eventCategory/eventType

xero is the interesting one: its topic combines two fields, so no single identifier can ever produce it. That's a schema question, not a lookup bug.

These three are the reason this PR doesn't close the issue entirely.

Supported forms

Form Example Resolves
plain key event body.event, or the header of that name
dotted path data.type body.data.type
array segment events[].eventType first element of body.events
nested arrays entry[].changes[].field first element at each level

A literal key that exists is matched before the value is treated as a path, so a header whose real name contains a dot still resolves. Only a scalar can name a file; anything else falls back to untitled- rather than writing [object Object].

Scope

compile.ts is unaffected - it reads the topic key already stored in each sample file and never consults topic_identifier. No published output changes; yarn compile produces an identical tree.

No new type errors: tsc --noEmit -p tsconfig.json reports the same two pre-existing errors in scripts/ on this branch as on main.

README documents the supported path forms.

Relationship to #33

Independent. #33 is correct as it stands - its samples were written directly rather than through the receiver, and each already carries a correct topic key. This PR is why #33 needs no change.

🤖 Generated with Claude Code

https://claude.ai/code/session_01N3a91JneFov5QaEUrcXXog


Generated by Claude Code

The receiver looked up topic_identifier with a flat lookup:

    topic = output.headers[identifier] || output.body?.[identifier];

23 providers declare an identifier that nests the event type, such as
data.type, events[].eventType or entry[].changes[].field. None of them
resolve through a flat lookup, so capturing any of those providers with
yarn dev:receiver named every sample untitled-<hash>.

Measured against one real sample per affected provider, replayed through
the receiver in an isolated tree:

  before   0 of 22 named correctly, 22 untitled
  after   19 of 22 named correctly

Supported forms are a plain key, a dotted path, and [] array segments
including nested ones. A literal key that exists is matched before the
value is treated as a path, so a header whose real name contains a dot
still resolves. Only a scalar can name a file; anything else falls back
to untitled- rather than writing [object Object].

The three that still do not resolve are stale configs rather than
resolver limitations, and are left alone deliberately since each needs a
decision about the right value:

  mailgun   declares event-data.event, sample has event at top level
  twitter   declares data.event_type, sample has direct_message_events
            at top level
  xero      topic is a composite of eventCategory/eventType, so no
            single identifier can produce CREDITNOTE/CREATE

compile.ts is unaffected: it reads the topic key already stored in each
sample file and never consults topic_identifier. No published output
changes.

README documents the supported path forms.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3a91JneFov5QaEUrcXXog
@vercel

vercel Bot commented Aug 26, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
webhook-samples Ready Ready Preview Aug 28, 2026 4:28pm

Request Review

@leggetter
leggetter marked this pull request as ready for review August 28, 2026 15:57
The resolver has six branches that all fail silently: a wrong answer
becomes a filename, not an exception. The PR's measured before/after
came from a throwaway script, so nothing in the repo would catch a
regression.

requestReceiver.ts calls app.listen() at import, so a test importing it
would hang. The two pure functions move to topic.ts unchanged, with
their original comment verbatim, and requestReceiver.ts imports from
there.

14 tests over plain keys, dotted paths, single and nested array
segments, an array segment under a dotted path, literal keys that
contain a dot, paths landing on objects, empty arrays, broken paths,
null sources, and numeric coercion.

Verified the tests can fail: changing current[0] to current[1] in the
array branch turns 3 of them red. `tsc --noEmit` reports the same two
pre-existing errors in scripts/ as before, and `yarn compile` produces
an identical tree, so the extraction changes no published output.

Uses node:test with the ts-node already in devDependencies, so the repo
gains a `yarn test` script and no new dependency.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3a91JneFov5QaEUrcXXog
The repo had no workflows, so `yarn test` only ever ran if someone
remembered to. A test nobody runs is a comment.

Triggers on pull requests and pushes to main. Node 22 with the yarn
cache, `yarn install --frozen-lockfile` so the lockfile is authoritative,
then `yarn test`.

Verified by running the same sequence against a clean export of this
tree: install from the lockfile, then 14 of 14 passing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3a91JneFov5QaEUrcXXog
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants