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 }
endThe six operations
| Phase | HTTP | Body cap | Purpose | Persists |
|---|---|---|---|---|
| handshake | POST /api/connectors/handshake | small | Confirm token, exchange capabilities + sync modes | Updates lastUsedAt |
| heartbeat | POST /api/connectors/:connectorId/heartbeat | none | Module-alive ping, server time exchange | Updates lastUsedAt |
| full-sync | POST /api/projects/:projectId/sync/full | 1000 | First-load or reset; bulk-upsert whole catalog | SearchConnectorSyncJob + outbox rows |
| delta-sync | POST /api/projects/:projectId/sync/delta | 100 | Per-change webhook from the CMS | SearchConnectorSyncJob + outbox rows |
| delete | DELETE /api/projects/:projectId/products/:externalIdDELETE /api/connector/documents | 1 / 500 | Remove one product / batch | doc_delete outbox row(s) |
| diagnostics | POST /api/projects/:projectId/diagnostics | 4 KB | Self-report PHP version, last sync, error log | In-memory diagnostics store + structured log |
What gates every call
gateConnectorRequest runs before any handler logic:
- Authorization header must carry
Bearer ss_connector_*. verifySearchApiKey(rawKey, "connector_write")— admin scope is a superset.- Returns
{ keyId, organizationId, indexId, indexSlug }for the handler. - Sync handlers additionally check that
:projectIdmatchesorganizationId— 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.
Related
- Write path — the worker side of the outbox.
- Key types & security model —
the
ss_connector_*token shape and scope rules.
Key types & security model
The four AACsearch key categories — search, connector, scoped, and admin — their ss_* prefixes, hash-only storage, scoped-token HMAC + TTL + filter, and how each one is verified at request time.
Analytics feedback loop
How a search query becomes a SearchUsageEvent, how clicks and conversions are recorded, and how aggregation feeds the dashboard for relevance tuning.