AACsearch
Architecture

Connector lifecycle

The six connector operations — handshake, heartbeat, full-sync, delta-sync, delete, diagnostics — and how they map to the Connector API surface.

A CMS connector (PrestaShop, Bitrix, WordPress, Shopify) talks to AACsearch only through the Connector API mounted at /api/connectors/** and /api/projects/:projectId/**. It never touches Typesense directly. Every call is gated by a ss_connector_* token with the connector_write scope.

Lifecycle

Description

The diagram walks the six CMS-connector phases — handshake, heartbeat, full-sync, delta-sync, delete, diagnostics — each gated by a ss_connector_* token. Sync calls create a SearchConnectorSyncJob row plus N SearchSyncOutbox rows; the worker drains the outbox into the Typesense alias while the CMS module polls the jobId for completion.

sequenceDiagram
    autonumber
    participant Mod as CMS module (PrestaShop / Bitrix / WP / Shopify)
    participant API as packages/api/modules/search/connector-public.ts
    participant Auth as gateConnectorRequest (scope=connector_write)
    participant DB as PostgreSQL (SearchConnectorSyncJob + SearchSyncOutbox)
    participant W as Sync worker
    participant TS as Typesense alias

    rect rgb(220, 252, 231)
        note over Mod,API: Phase 1 — handshake (once per install)
        Mod->>API: POST /api/connectors/handshake { moduleVersion, platform }
        API->>Auth: verify ss_connector_* + connector_write
        API-->>Mod: { projectId, indexSlug, capabilities, syncModes }
    end

    rect rgb(219, 234, 254)
        note over Mod,API: Phase 2 — heartbeat (periodic, every ~5 min)
        Mod->>API: POST /api/connectors/:connectorId/heartbeat
        API-->>Mod: { status: "ok", timestamp }
    end

    rect rgb(254, 243, 199)
        note over Mod,DB: Phase 3 — full sync (initial / on-demand)
        Mod->>API: POST /api/projects/:projectId/sync/full { products[1..1000] }
        API->>DB: createSyncJob(type=full)
        API->>DB: executeSyncJob -> enqueue doc_upsert per product (outbox)
        API-->>Mod: { jobId, total, queued }
        W->>DB: drain outbox doc_upsert rows
        W->>TS: bulkUpsert(batch, action=upsert)
        W->>DB: completeSyncJob(jobId)
    end

    rect rgb(245, 243, 255)
        note over Mod,DB: Phase 4 — delta sync (per change webhook)
        Mod->>API: POST /api/projects/:projectId/sync/delta { products[1..100] }
        API->>DB: createSyncJob(type=delta)
        API->>DB: executeSyncJob -> outbox rows
        API-->>Mod: { jobId, total, queued }
        W->>TS: bulkUpsert(batch)
    end

    rect rgb(254, 226, 226)
        note over Mod,TS: Phase 5 — delete (single or batch)
        Mod->>API: DELETE /api/projects/:projectId/products/:externalId
        API->>DB: enqueue doc_delete
        W->>TS: delete_by_query(externalId)
        Mod->>API: DELETE /api/connector/documents { externalIds[1..500] }
        API->>DB: enqueue doc_delete (batch)
        W->>TS: batch delete
    end

    rect rgb(241, 245, 249)
        note over Mod,DB: Phase 6 — diagnostics (on demand / on error)
        Mod->>API: POST /api/projects/:projectId/diagnostics { moduleVersion, lastFullSync, errors[] }
        API->>DB: recordDiagnostics() (in-memory store + log)
        API-->>Mod: { received: true }
    end

The six operations

PhaseHTTPBody capPurposePersists
handshakePOST /api/connectors/handshakesmallConfirm token, exchange capabilities + sync modesUpdates lastUsedAt
heartbeatPOST /api/connectors/:connectorId/heartbeatnoneModule-alive ping, server time exchangeUpdates lastUsedAt
full-syncPOST /api/projects/:projectId/sync/full1000First-load or reset; bulk-upsert whole catalogSearchConnectorSyncJob + outbox rows
delta-syncPOST /api/projects/:projectId/sync/delta100Per-change webhook from the CMSSearchConnectorSyncJob + outbox rows
deleteDELETE /api/projects/:projectId/products/:externalId
DELETE /api/connector/documents
1 / 500Remove one product / batchdoc_delete outbox row(s)
diagnosticsPOST /api/projects/:projectId/diagnostics4 KBSelf-report PHP version, last sync, error logIn-memory diagnostics store + structured log

What gates every call

gateConnectorRequest runs before any handler logic:

  1. Authorization header must carry Bearer ss_connector_*.
  2. verifySearchApiKey(rawKey, "connector_write") — admin scope is a superset.
  3. Returns { keyId, organizationId, indexId, indexSlug } for the handler.
  4. Sync handlers additionally check that :projectId matches organizationId — a 404 is returned if a token is used against a different project.

Why a job + an outbox

The Connector API never blocks on Typesense. executeSyncJob writes a SearchConnectorSyncJob row for end-to-end visibility, plus N rows into SearchSyncOutbox for the worker to drain. The CMS module gets a jobId it can poll for completion; the worker reconciles each row independently so a single bad SKU does not break the batch.

On this page