Member Junction
    Preparing search index...

    Fonteva AMS connector.

    Fonteva is a Salesforce managed package (the OrderApi__ and EventApi__ namespaces), so it rides the core Salesforce REST infrastructure verbatim. This connector therefore COMPOSES on SalesforceConnector, reusing — unchanged — its:

    • Salesforce OAuth2 bearer authentication (JWT-bearer / client-credentials),
    • global describe object discovery (/services/data/vXX/sobjects/),
    • per-object /describe field discovery,
    • SOQL incremental fetch with nextRecordsUrl cursor pagination,
    • SystemModstamp watermark incremental sync,
    • generic sObject CRUD (POST/PATCH/DELETE) and the BuildCreatedResult loud-fail-on-empty-ID write contract.

    The Fonteva-specific layer added here is intentionally thin:

    1. Namespace scopingDiscoverObjects filters the inherited Salesforce global describe down to the managed-package namespaces (OrderApi__ / EventApi__), so the connector surfaces ONLY Fonteva AMS objects (the standard SF CRM objects are the SalesforceConnector's job).
    2. IO-name ↔ backing-sObject translation — the friendly IO Name ("Assignment") is mapped to its real sObject API name ("OrderApi__Assignment__c", read from the IO's Configuration) before every inherited Salesforce operation, and the returned records' ObjectType is rewritten back to the IO name for engine identity.
    3. FDService domain services — the /services/apexrest/FDService/* Apex REST services are wired as an opt-in read path with the documented SearchRequest filter/fields/sort/limit query params.

    Discovery is the live global-describe MECHANISM (no baked catalog); the object universe is Declared (docs) + Discovered (runtime describe). Writes use the inherited generic sObject CRUD; FDService write shapes are not idiosyncratic enough to warrant overriding the sObject write path (the metadata routes all writes through the platform sObject endpoints).

    Hierarchy (View Summary)

    Index

    Constructors

    Accessors

    • get DiscoveryIsAuthoritative(): boolean

      AUTHORITATIVE discovery (inherited rationale): the scoped global describe enumerates the COMPLETE set of Fonteva managed-package objects the credentials expose, so absence in a refresh genuinely means the object was removed and the engine may safely deactivate it. Kept explicit so the Fonteva-scoped filter doesn't quietly inherit a non-authoritative default.

      Returns boolean

    • get IntegrationName(): string

      The canonical integration name (e.g., "HubSpot", "Rasa.io"). Used by GetActionGeneratorConfig() and IntegrationActionExecutor to match connectors to action Config.IntegrationName.

      Override in subclasses. Defaults to the class name.

      Returns string

    • get MaxConcurrencyHint(): number

      Highest SAFE per-layer concurrency the source tolerates (plan.md §7 peak parallelization) — the ceiling the engine's adaptive controller ramps toward. null → use configured syncConcurrency.

      Returns number

    • get MonotonicWatermark(): boolean

      Whether the watermark this connector returns (FetchBatchResult.NewWatermarkValue) is a RELIABLE, monotonically-increasing global maximum — i.e. the connector fetches in watermark order so the last batch's value IS the true high-water mark, and an updated record always re-surfaces at a NEW (higher) watermark. When true, the engine uses that watermark to NARROW the next incremental (instead of advancing a full sync to wall-clock "now", and instead of the keyset clear-and-re-scan), so incrementals fetch only what's new.

      Default false — the safe, backwards-compatible choice: a connector whose source returns records out of watermark order (e.g. HubSpot's creation-ordered list API, where the last batch can carry old modstamps) MUST stay false so the engine keeps advancing to "now" and never saves a stale watermark. Override to true ONLY when the source guarantees monotonic ordering.

      Returns boolean

    • get RateLimitPolicy(): RateLimitPolicy

      Token-bucket rate-limit policy for this connector's source API (plan.md §7 peak-aware rate limiting). null → the engine derives a conservative rate from Integration.BatchRequestWaitTime. Override to push to the source's real limits.

      Returns RateLimitPolicy

    • get SupportsBatchWrite(): boolean

      Whether this connector supports batched target writes (plan.md §7 aggressive batching).

      Returns boolean

    • get SupportsCreate(): boolean

      Whether this connector supports creating new records in the external system.

      Returns boolean

    • get SupportsDelete(): boolean

      Whether this connector supports deleting records from the external system.

      Returns boolean

    • get SupportsGet(): boolean

      Whether this connector supports reading/fetching records. Always true.

      Returns boolean

    • get SupportsListing(): boolean

      Whether this connector supports paginated listing of records.

      Returns boolean

    • get SupportsSearch(): boolean

      Whether this connector supports searching/querying records with filters.

      Returns boolean

    • get SupportsUpdate(): boolean

      Whether this connector supports updating existing records in the external system.

      Returns boolean

    • get SupportsUpsert(): boolean

      Whether this connector supports idempotent upserts (create-or-update keyed by a unique business property). Connectors override this AND Upsert to enable it.

      Returns boolean

    Methods

    • Appends default query parameters from the IntegrationObject metadata. DefaultQueryParams is stored as a JSON object like {"key": "value"}. Automatically skips params whose key (case-insensitive) already appears in the URL to avoid duplicates with pagination params.

      Parameters

      Returns string

    • Runs TransformRecord, then RE-ADDS any source key the transform dropped (present in raw, absent from the output) unless declared in ExcludedSourceKeys. This makes the base-fetch path structurally full-record (§0 forward-compat contract): a TransformRecord override may reshape/coerce but cannot SILENTLY drop a source key from ExternalRecord.Fields, so the framework's custom-column capture sees everything the source returned. The default identity transform returns raw unchanged → fast-path no-op (zero overhead, zero behavior change for connectors that don't override TransformRecord).

      Parameters

      Returns Record<string, unknown>

    • Builds a CRUDResult for a record CREATE, failing LOUDLY when the external system returned no usable record ID. A 2xx response with an empty/undefined ID means the create did not durably produce a record we can track — returning Success:true there silently loses the record and causes duplicate creates on the next sync (the HubSpot-association class of bug, fixed in next commit 9f718a7e). This makes that failure explicit at the connector boundary.

      Parameters

      • externalID: string
      • statusCode: number
      • objectName: string

      Returns CRUDResult

    • Build the operation request body per BodyShape:

      • 'flat' → body = attributes verbatim
      • 'wrapped' → body = { [BodyKey]: attributes }
      • 'literal' → connector should have overridden the operation; fall back to flat as safety net
      • null → default to flat (most common shape)

      Parameters

      • attributes: Record<string, unknown>
      • bodyShape: string
      • bodyKey: string

      Returns unknown

    • Appends pagination parameters to a URL based on the object's PaginationType. Override in subclasses to use vendor-specific parameter names.

      Parameters

      • basePath: string
      • obj: MJIntegrationObjectEntity
      • page: number
      • offset: number
      • Optionalcursor: string
      • OptionaleffectivePageSize: number

        When provided by FetchPaginatedLoop, caps the requested page size to the remaining batch capacity. Subclasses should honor this to prevent overshoot.

      Returns string

    • Discovery via the connector's READ PATH (FetchChanges), TIME-BOUNDED — the way to gather a statistically-significant sample when a single DiscoverFields sample is too small to PROVE a key. "Discovery is the sync read path with the save removed."

      Loops FetchChanges as a read-only FULL fetch (WatermarkValue=null, nothing persisted), threading pagination/keyset cursors across batches, and streams every record through the data-informed field + provable-PK inference. It stops at the discovery TIME BUDGET (default 5 min), or a record cap, or source exhaustion — whichever comes first — so the provable-PK decision is made on as much real data as the budget allows. It NEVER fabricates a key: if even this larger sample yields no provable single/composite PK, the field set comes back PK-less and the object is honestly not added.

      Falls back to the single-sample DiscoverFields if the read path can't run for this object (e.g. a connector whose FetchChanges needs an already-persisted IO row that doesn't exist yet).

      Parameters

      Returns Promise<ExternalFieldSchema[]>

    • Stage-2 field discovery for sources WITHOUT a describe/introspection endpoint (file feeds, undocumented JSON list endpoints): stream the source's actual records — READ-ONLY, no save, no ack — and derive the full field set + data-informed PK/uniqueness/nullability from the gathered statistics. The connector supplies whatever read-only fetch yields the records; this helper turns that stream into ExternalFieldSchema[].

      Why data-informed: streaming the real values lets pickPrimaryKeyFromStats pick the PK from evidence (uniqueness/non-null statistics) COMBINED with the naming convention, rather than a name guess alone. The PK is a SOFT key, so the pick is best-available, not strict-significance: a confident unique+non-null column wins outright; otherwise a near-unique / convention-named column is taken as a soft key (a PK-less object would stall CodeGen). The scan is time-bounded — it stops on exhaustion OR opts.Discovery.TimeBudgetMs; more rows simply mean stronger claims.

      Provable-only encoding into the standard flags:

      • IsPrimaryKey — set ONLY on the single statistics-first pick. Multiple equally-ranked unique columns leave PK unset here (ambiguous → the pipeline's SoftPKClassifier LLM tiebreaker decides, fed these same stats). Zero unique columns → no PK is fabricated.
      • IsUniqueKey — set when the column was all-distinct over the scan AND uniqueness was provable (the distinct-cap wasn't hit).
      • AllowsNull — asserted true ONLY when a null/absent value was actually observed; otherwise left undefined (permissive default). Never fabricates NOT NULL — critical under a time-capped partial scan where unseen rows could still be null.

      The PK emitted here is SOFT (it rides additionalSchemaInfo via the persist + DDL path; it is NEVER a hard DB key), so a wrong inference can never reject a valid row — the engine dedupes via the record-map. IsReadOnly defaults to true (stream discovery targets read feeds); a writable source overrides via opts.ReadOnly.

      Parameters

      • records:
            | AsyncIterable<Record<string, unknown>, any, any>
            | Iterable<Record<string, unknown>, any, any>

        A read-only sync/async iterable of source records (the caller's fetch yields them).

      • Optionalopts: { Discovery?: StreamDiscoveryOptions; Pk?: PkPickOptions; ReadOnly?: boolean }

      Returns Promise<ExternalFieldSchema[]>

    • DISCOVERY-ONLY: last-resort SAFETY bound on the recursion depth for multi-level template chains (a parent that is itself a template-var child), so a malformed metadata cycle cannot loop unboundedly. This is only the fallback when the consumer sets no discoverySampleMaxDepth in Configuration and no env override is present — the consumer decides the real value. A recursion cap MUST have some bound, so a conservative default remains here; override the getter to change the fallback.

      Returns number

    • REST override (§sample-discover): a SINGLE-template-var CHILD is sampled with the recursive, record-constrained StreamRecordsForDiscovery. Flat objects fall back to the generic FetchChanges loop; MULTI-var (composition) children are deferred — StreamRecordsForDiscovery adjourns them (declared-only until first sync) rather than fire malformed URLs.

      Parameters

      Returns AsyncGenerator<Record<string, unknown>>

    • The Salesforce attributes blob is the ONLY key any object's TransformRecord removes. Declaring it here makes the removal auditable and excludes it from the base's re-add-dropped-keys safety net (and from change-detection).

      Parameters

      • _objectName: string

      Returns string[]

    • Extract the new record's external ID from a create response per IDLocation.

      Parameters

      Returns string

    • Extract pagination state from the vendor-specific response.

      Parameters

      • rawBody: unknown

        The parsed response body

      • _paginationType: PaginationType

        The pagination strategy for this object

      • _currentPage: number

        Current page number (1-based)

      • _currentOffset: number

        Current record offset

      • _pageSize: number

        Page size used in the request

      Returns PaginationState

      Pagination state indicating whether more data is available

    • Parse a Retry-After / rate-limit signal out of a failed response or thrown error into milliseconds so the engine can back off precisely. Return undefined when the error is not a throttle (or carries no hint).

      Parameters

      • _error: unknown

      Returns number

    • Action-generation hint set. CORRECTION (IMPROVE build): this used to return a baked famous-subset catalog (~9 objects hardcoded in this file). That is a catalog-in-code defect — it froze the object universe to a famous subset.

      It now derives the hint set ENTIRELY from the runtime-cached IntegrationObject / IntegrationObjectField metadata for the Salesforce integration — the FULL Declared (credential-free catalog) + Discovered (live DiscoverObjects describe, custom __c included) gamut. There is NO baked catalog. If the metadata cache is not yet populated (e.g. action generation runs before the integration is seeded), it returns an empty array — the connector NEVER falls back to a hardcoded list.

      The per-tenant object UNIVERSE for sync comes exclusively from DiscoverObjects (live global describe / sObjects endpoint); this method only shapes the cached catalog into the ActionMetadataGenerator's hint structure.

      Returns IntegrationObjectInfo[]

    • Extract the data array from the vendor-specific response envelope.

      Parameters

      • rawBody: unknown

        The parsed response body

      • _responseDataKey: string

        The key to extract data from, or null for root-level arrays

      Returns Record<string, unknown>[]

      Array of raw record objects

    • Type-driven post-processing hook (plan.md §10): a connector may normalize/enforce a record's values to the resolved column formats AFTER transform/normalize and BEFORE write. Default returns the record unchanged. (Named for this system — NOT MCP, not take.) The engine ALSO applies target-type constraint enforcement; this is the connector-side complement.

      Parameters

      Returns ExternalRecord

    • Name of a stable, monotonic ordering key (PK/identity) usable for KEYSET/seek resume on watermark-less objects (plan.md §7 — resume from last-seen key, robust to mid-stream insert/delete). null → keyset resume unavailable for this object.

      Parameters

      • _objectName: string

      Returns string

    • Substitute the ExternalID into a URL path template at runtime. Path templates may use {ID}, {id}, or {ExternalID} as the placeholder; for symmetry with FetchChanges template-var handling, any unsubstituted {var} in the template is left in place (caller's responsibility to ensure consistency).

      For IDLocation='body' or 'header' the ID is not substituted into the path (caller must handle separately); we just return the raw path.

      Parameters

      • path: string
      • externalID: string
      • idLocation: string

      Returns string

    • Per-call cap on how many PARENTS a parent-iterated object processes before yielding a resumable batch (the engine loops on HasMore). Kept conservative so a single FetchChanges call fits the engine's op-timeout even at a slow initial adaptive rate; the AIMD bucket ramps up over batches. Override in a concrete connector if a vendor tolerates larger per-call parent runs.

      Returns number

    • CORRECTION (IMPROVE build): per-record transform that strips Salesforce's attributes metadata blob (e.g. { type, url }) from every record. This is a SANCTIONED removal declared in ExcludedSourceKeys, NOT a silent drop — the attributes key is vendor envelope noise, not customer data, so dropping it can never lose a custom column. Every OTHER source field flows through untouched, so the full-record pass-through contract (custom-column capture) is preserved.

      Used by both the base-fetch path (GetRecord via applyTransformPreservingKeys) and the SOQL override path (RawToExternalRecord routes through StripVendorAttributes, which reuses ExcludedSourceKeys so the two paths agree on what is removed).

      Parameters

      Returns Record<string, unknown>

    • Upserts a record — a single idempotent create-or-update keyed by a unique business property (e.g. email), eliminating the search-then-create race window. Override in subclasses whose external system exposes a keyed upsert primitive. Check SupportsUpsert before calling.

      Parameters

      Returns Promise<CRUDResult>