@cfxlabsinc/b2b-services
    Preparing search index...

    Module @cfxlabsinc/events

    @cfxlabsinc/events

    The central event registries and their publishers. See docs/proposals/EVENT_BUS.md for the design.

    Subpath Contents
    ./sns SnsEventPublisher, the envelope, message attributes
    ./customer The customer-facing registry and CustomerEventPublisher
    ./internal The internal registry and InternalEventPublisher
    ./docs Per-event documentation + the public webhook page generator

    ./docs is separate from ./customer on purpose: every Lambda that publishes an event loads the ./customer barrel, and none of them needs the prose, the ReadMe slugs, or node:fs.

    https://docs.cfx.to/docs/webhook-events is generated from src/customer/registry.ts.

    • Do not edit that page in the ReadMe editor. The next main deploy that touches this package overwrites it.
    • The prose (setup, signature verification, retries, the envelope) lives in src/docs/webhook-events.template.md. Everything below its <!-- events --> marker is generated.
    • The only hand-authored per-event content is in src/docs/customerEventDocs.ts: a one-line summary, the ReadMe ref: slug(s) for the GET that returns the object's current state, and optional notes. Event names, entity scopes, and every field and enum member of data are read from the registry, so they cannot drift.
    • customerEventDocs is typed Record<CustomerEventName, EventDoc>. Registering an event without documenting it fails nx run events:typecheck — that is the mechanism that keeps the page complete.

    Set hiddenReason on its EventDoc and the page omits it — from the summary table, the payload sections, and the count. Five events are withheld today; webhookDocs.test.ts locks that set, so adding to or removing from it is a deliberate edit.

    • It is a reason string, not a boolean. You cannot withhold an event without recording why, and the reason lives next to the event rather than in a changelog.
    • A withheld event still needs a complete entry. The doc-exists check runs before the filter, so hiding is never the cheap way out of writing docs, and unhiding is a one-line change.
    • It changes nothing at runtime. The event is still published on the bus and still delivered to anyone subscribed; this governs only what docs.cfx.to shows.
    • The page therefore claims no exhaustiveness. It says "MoveUSD publishes the following N events", never "the exhaustive list of what you may receive" — that older wording would be false the moment anything is withheld, and a test asserts the word exhaustive never reaches the page.
    Task Command
    Render to dist/docs/readme/ nx run events:gen-webhook-docs
    Check ReadMe accepts it (needs API key) nx run events:validate-webhook-docs
    Upload (CI only) nx run events:publish-webhook-docs

    CI wiring mirrors the OpenAPI specs: pr.yml runs the dry-run validate, main.yml's Publish Specs & Guides job uploads with README_API_KEY. ReadMe's bidirectional git sync mirrors the uploaded page back into CFXLabsInc/cfx-b2b-platform-docs at docs/Conventions/webhook-events.md; nothing in this repo pushes to that repo directly.

    Three behaviours of rdme@10 this wiring depends on, from node_modules/rdme/dist/lib/syncPagePath.js and readPage.js:

    • The page is identified by its slug, which is frontmatter.slug or, absent that, the lowercased file name. The template pins slug: webhook-events explicitly so renaming the file can never orphan the live page and create a second one.
    • It GETs /guides/<slug> first: 404 → POST (create), otherwise PATCH (update). The PATCH body carries only the frontmatter keys present plus the content, so the page's category is untouched — that is why the template declares no category and the page stays under Conventions.
    • Frontmatter that needs an autofix throws under CI rather than uploading, or silently skipping. Don't add --confirm-autofixes to the publish target; a page ReadMe wants to rewrite should fail the build and be fixed in the template.

    The markdown files ReadMe's git sync writes into cfx-b2b-platform-docs are v1 shaped. rdme docs upload PATCHes the v2 API, whose schema is additionalProperties: false and rejects the v1 keys outright. Copying front matter out of the synced repo is the obvious move and it does not work:

    v1 — what git sync exports v2 — what rdme docs upload requires
    hidden: false privacy: { view: public }
    deprecated: false state: current
    metadata: { robots: index } allow_crawlers: enabled

    The v2 root key set is allow_crawlers, appearance, category, content, metadata, parent, position, privacy, slug, state, title, type. metadata survives but holds only title / description / image / keywords / x_import — never robots.

    rdme autofixes hidden and deprecated but not metadata.robots, and it refuses to autofix anything under CI, so the whole file fails the upload with 1 file(s) have issues that should be fixed before uploading. webhookDocs.test.ts asserts the v2 keys are present and that no v1 key survives, so this is caught by nx run events:test — no API key, no network — rather than on a PR.

    To reproduce rdme's validation offline: fetchSchema() reads a bundled OAS (node_modules/rdme/dist/lib/types/openapiDoc.js, path /branches/{branch}/guides/{slug}patch → request body schema), so the same ajv pass runs with no credentials. It is not an exported subpath of the package, which is why the test asserts the key shape directly instead of importing it.

    1. Add it to src/customer/registry.ts.
    2. Add its name to EXPECTED_EVENT_NAMES in test/customerRegistry.test.ts and bump the count.
    3. Add its entry to customerEventDocs. Typecheck fails until you do.

    A ReadMe slug is the markdown filename under reference/ in the docs repo, minus the extension — those files are generated by ReadMe from the OpenAPI specs main.yml uploads, so a slug changes only when an operation's method or path changes.

    customer
    docs
    internal
    sns