Metadata-Version: 2.5
Name: dander-connector-salesforce
Version: 0.3.2
Summary: Salesforce Bulk API 2.0 connector plugin for Dander.
Project-URL: Homepage, https://github.com/harrisonoconnorhover/dander-connector-salesforce
Project-URL: Issues, https://github.com/harrisonoconnorhover/dander-connector-salesforce/issues
Project-URL: Repository, https://github.com/harrisonoconnorhover/dander-connector-salesforce.git
Author: Harrison O'Connor-Hover
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: bulk-api,dander,etl,salesforce
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Information Technology
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Database
Requires-Python: >=3.12
Requires-Dist: dander-platform<0.10,>=0.6.0rc1
Requires-Dist: httpx>=0.27
Provides-Extra: dev
Requires-Dist: mypy>=1.11; extra == 'dev'
Requires-Dist: pytest>=8.3; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Description-Content-Type: text/markdown

# Dander Salesforce Connector

First-party Salesforce Bulk API 2.0 connector plugin for
[Dander](https://github.com/harrisonoconnorhover/dander).

> **Alpha:** pin both Dander and this plugin exactly. Scheduled ingestion is read-only. Explicit
> connector write commands are experimental and mutate Salesforce. Contact Email and Phone are
> enabled by default and are personal data; deploy them only into an approved project.

## What it reads

The four endpoints stream bounded CSV result pages and commit separate `SystemModstamp` watermarks:

| Endpoint | Raw relation | Salesforce operation | Deletion state |
| --- | --- | --- | --- |
| `accounts` | `raw.salesforce_accounts` | `queryAll` | `IsDeleted` |
| `contacts` | `raw.salesforce_contacts` | `queryAll` | `IsDeleted` |
| `opportunities` | `raw.salesforce_opportunities` | `queryAll` | `IsDeleted` |
| `users` | `raw.salesforce_users` | `query` | `IsActive` |

Inclusive `SystemModstamp >= cursor` filters deliberately reread the cursor boundary. Dander's
SCD1 writer makes that replay duplicate-free. Soft-deleted CRM records remain as tombstones rather
than being physically deleted from BigQuery, and inactive owners remain available for historical
joins. Salesforce records that have already been hard-deleted or purged cannot be recovered by
this connector.

Salesforce's deleted-record feed retains at most 15 days. Dander rejects a wider or non-forward
window instead of silently presenting it as complete.

## Dander compatibility

This release supports `dander-platform>=0.6.0rc1,<0.10` through plugin API v1. Pin both
packages exactly in deployments. CI installs the built connector wheel into a fresh environment
and runs the complete connector test suite with Dander `0.6.0rc1`, `0.7.1`, and `0.9.0rc20`.
The normal development lock uses stable Dander `0.7.1`; release candidates must be selected
explicitly. These checks use synthetic data and do not certify every Salesforce
configuration.

## Install and configure

Declare the exact stable version in `dander.yaml`:

```yaml
version: 1
plugins:
  salesforce:
    distribution: dander-connector-salesforce
    version: 0.3.2
pipelines:
  salesforce_crm:
    source: salesforce
    models: []
    build_models: false
    schedule: "0 7 * * *"
    time_zone: America/New_York
    paused: true
    secrets:
      SALESFORCE_EXTERNAL_CLIENT_APP_ID: salesforce-client-id
      SALESFORCE_EXTERNAL_CLIENT_APP_PRIVATE_KEY: salesforce-private-key
```

Existing installations may keep their current pipeline ID to avoid replacing Cloud Run and
Scheduler resources. Install the manifest's exact package pin:

```console
dander plugins install
```

Copy [`salesforce_jwt.example.yaml`](src/dander_connector_salesforce/templates/salesforce_jwt.example.yaml)
into `connectors/salesforce.yaml`. Replace only the public org URL, OAuth subject, and login-domain
settings. Store the External Client App ID and RSA private key in Dander's secret store; never put
credential values in YAML.

The Salesforce integration user needs API access plus read access to each selected object and
field. Custom fields are opt-in: add each field to both the endpoint's SOQL `SELECT` and its
`raw_schema`. This explicit declaration prevents undeclared source drift from entering BigQuery.

## Runtime contract

- Engine: `salesforce_bulk2`
- Authentication: Dander core's cloud-neutral `oauth2_jwt` strategy
- API: Salesforce Bulk API 2.0 Query
- Publication: Dander's idempotent SCD1 writer
- Memory: CSV pages are streamed and bounded by Salesforce `maxRecords`
- Replay: inclusive server-side `SystemModstamp` filters
- Optional reads: exact aggregate count and one-record lookup by validated Salesforce `Id`
- Optional deleted feed: Accounts, Contacts, and Opportunities within Salesforce's 15-day window
- Explicit writes: create, update, and delete by validated Salesforce `Id`
- Connection check: authenticated REST `/limits` probe that returns no business records

With a supported Dander version, inspect and check an installed pipeline without ingestion:

```console
dander connector inspect salesforce_crm
dander connector check salesforce_crm
```

Dander retains its deprecated built-in Salesforce fallback for compatibility. An explicitly pinned
plugin takes precedence.

## Experimental write-back

Write-back is never invoked by `dander run`. It requires the separate connector command, local JSON
input files, and an explicit `--confirm-write` acknowledgement:

```console
dander connector write salesforce_crm accounts create --record new-account.json --confirm-write
dander connector write salesforce_crm accounts update --identity account-id.json \
  --changes account-changes.json --confirm-write
dander connector write salesforce_crm accounts delete --identity account-id.json --confirm-write
```

Upsert is disabled by default. To enable it for a custom endpoint, declare one Salesforce External
ID field as the endpoint's sole primary key, include it in both the SOQL selection and `raw_schema`,
and name it in `request_body`:

```yaml
primary_key: [Dander_External_ID__c]
request_body:
  operation: queryAll
  query: SELECT Dander_External_ID__c, Name FROM Account
  upsert_external_id_field: Dander_External_ID__c
raw_schema:
  - {name: Dander_External_ID__c, type: STRING, mode: REQUIRED}
  - {name: Name, type: STRING, mode: REQUIRED}
```

Before each upsert, the connector checks Salesforce metadata and refuses fields that are not marked
both External ID and Unique. External-ID values are encoded as one URL path segment. The local
control field is not sent in Bulk query job bodies.

These write capabilities use the same Dander compatibility range as the package. Provider writes
remain explicit CLI operations and are not invoked by normal scheduled ingestion.

## Development

```console
uv sync --extra dev
uv run ruff check .
uv run mypy src tests
uv run pytest
```

Apache-2.0 licensed.
