{
  "_comment": "Canonical public source-of-truth for Nexus data adapters. Edit here, then run `node build/gen.mjs` to regenerate adapters.html / adapters.json / adapters.md.",
  "adapters": [
    {
      "id": "netsuite-rest",
      "name": "NetSuite (REST / SuiteQL)",
      "version": "2.0.8",
      "category": "erp",
      "auth": "OAuth 1.0a Token-Based Authentication (TBA, HMAC-SHA256) OR OAuth 2.0 client_credentials — selected per connection",
      "capabilities": {
        "read": true,
        "write": true,
        "discover": true,
        "pushdown-filter": "yes (SuiteQL renders WHERE into the query; Record API filters per the SuiteQL surface)",
        "pushdown-aggregation": "yes via SuiteQL (SELECT aggregates / GROUP BY); not via the Record API"
      },
      "operators": [
        "$eq",
        "$ne",
        "$gt",
        "$gte",
        "$lt",
        "$lte",
        "$like",
        "$ilike",
        "$in",
        "$nin",
        "$between",
        "$empty",
        "$contains",
        "$starts_with",
        "$ends_with",
        "$expr"
      ],
      "quirks": [
        "Two surfaces in one adapter: SuiteQL (the /query/v1/suiteql endpoint, full SQL-style read) and the Record API (/record/v1/<collection>, per-record CRUD). The query container selects the surface: NETSUITE/empty → SuiteQL, RECORD → Record API.",
        "SuiteQL has no prepared-statement support, so filter values are inlined into the query text — treat report/query authoring as a trusted surface.",
        "TBA realm is derived from the account ID, uppercased with hyphens converted to underscores.",
        "OAuth 2.0 access tokens are cached and refreshed ~60 seconds before expiry.",
        "Field-level schema discovery is intentionally shallow (returns collections/record types; per-field lists are deferred).",
        "Requests use the SuiteQL 'Prefer: transient' header and paginate via SuiteQL offset/limit."
      ],
      "secrets": [
        "auth_method",
        "account_id",
        "consumer_key",
        "consumer_secret",
        "token_id",
        "token_secret"
      ],
      "notes": "NetSuite ERP over the REST platform: SuiteQL for ad-hoc reads and the Record API for create/update/delete. Best for read-heavy SuiteQL reporting plus targeted record writes."
    },
    {
      "id": "netsuite-connect",
      "name": "NetSuite (SuiteAnalytics Connect)",
      "version": "2.0.8",
      "category": "erp",
      "auth": "OAuth 1.0a TBA (HMAC-SHA256) presented as a signed JDBC connection password",
      "capabilities": {
        "read": true,
        "write": false,
        "discover": "yes (via the Connect SQL surface)",
        "pushdown-filter": "yes — rich SQL pushdown",
        "pushdown-aggregation": "yes — including window functions, CTEs, lateral joins, and regex/JSON functions"
      },
      "operators": [
        "$eq",
        "$ne",
        "$gt",
        "$gte",
        "$lt",
        "$lte",
        "$like",
        "$ilike",
        "$in",
        "$nin",
        "$between",
        "$empty",
        "$contains",
        "$starts_with",
        "$ends_with",
        "$expr"
      ],
      "quirks": [
        "Read-only. Use this mode for heavy analytical SQL; use REST/SuiteQL when you also need to write.",
        "Connects over JDBC through a local driver sidecar (requires the JDBC sidecar to be enabled in the deployment).",
        "Host normalization: the JDBC URL uses the hyphen form of the host while the account realm inside CustomProperties(AccountID=...) uses the underscore form — they intentionally differ.",
        "The JDBC password is a composite TBA-signed token (realm, consumer key, token id, nonce, timestamp, signature, HMAC-SHA256).",
        "Supports a much richer SQL feature set than SuiteQL (windows, CTEs, lateral joins, regex/JSON), making it the stronger reporting/analytics path."
      ],
      "secrets": [
        "account_id",
        "consumer_key",
        "consumer_secret",
        "token_id",
        "token_secret"
      ],
      "notes": "NetSuite analytics via SuiteAnalytics Connect over JDBC — read-only but with full analytical SQL pushdown. Pair with NetSuite REST when writes are required."
    },
    {
      "id": "monday",
      "name": "Monday.com",
      "version": "2.0.6",
      "category": "saas",
      "auth": "API token (sent as a raw Authorization header value, no Bearer prefix)",
      "capabilities": {
        "read": true,
        "write": true,
        "discover": true,
        "pushdown-filter": "yes — UQL WHERE is translated into Monday's ItemsQuery rules",
        "pushdown-aggregation": "no — Monday's GraphQL API has no aggregation surface; aggregate in-engine"
      },
      "operators": [
        "$eq",
        "$ne",
        "$gt",
        "$gte",
        "$lt",
        "$lte",
        "$contains",
        "$not_contains",
        "$starts_with",
        "$ends_with",
        "$empty",
        "$not_empty",
        "$between"
      ],
      "quirks": [
        "Talks GraphQL to api.monday.com/v2; filters become a parameterized ItemsQuery, so values flow through GraphQL variables (no string interpolation).",
        "The query container is the board ID and the collection is an optional group ID (honored as a group filter).",
        "Insert/update/delete are supported; there is no merge/upsert.",
        "Writes map record keys to Monday column IDs and skip non-writable column types.",
        "Operator set differs from the SQL adapters: it adds $not_contains and $not_empty and does not support $in/$nin/$like/$expr.",
        "Known current limits: cursor pagination across very large boards, subitems, and per-call complexity/rate-limit budgeting are not yet handled."
      ],
      "secrets": [
        "api_key",
        "api_version"
      ],
      "notes": "Monday.com work-management boards over GraphQL with read and write. Filtering pushes down to ItemsQuery; aggregation is performed in the query engine."
    },
    {
      "id": "mssql",
      "name": "Microsoft SQL Server",
      "version": "2.0.5",
      "category": "database",
      "auth": "Username / password (ODBC); TLS validated by default",
      "capabilities": {
        "read": true,
        "write": true,
        "discover": true,
        "pushdown-filter": true,
        "pushdown-aggregation": true
      },
      "operators": [
        "$eq",
        "$ne",
        "$gt",
        "$gte",
        "$lt",
        "$lte",
        "$like",
        "$ilike",
        "$in",
        "$nin",
        "$between",
        "$empty",
        "$contains",
        "$starts_with",
        "$ends_with",
        "$expr"
      ],
      "quirks": [
        "T-SQL dialect: identifiers quoted with [ ], paging uses OFFSET ... FETCH, upserts use T-SQL MERGE.",
        "OFFSET/FETCH paging requires an ORDER BY; when a limit is set without an explicit order, a deterministic ORDER BY is injected automatically.",
        "Discovery enumerates user databases (skips system databases) and reads column metadata from INFORMATION_SCHEMA, including identity/computed flags.",
        "TLS certificate validation is on by default; disabling validation or encryption is an explicit, named opt-out.",
        "Connects via ODBC (default: ODBC Driver 18 for SQL Server); a pre-assembled connection string can be supplied instead of discrete fields.",
        "Identifier quoting is charset-validated; raw SQL expressions ($expr, select expressions) remain a trusted-author surface."
      ],
      "secrets": [
        "host",
        "port",
        "username",
        "password",
        "database",
        "tls_mode"
      ],
      "notes": "Microsoft SQL Server over ODBC with full read/write/discover and SQL pushdown for filtering and aggregation."
    },
    {
      "id": "mysql",
      "name": "MySQL",
      "version": "2.0.5",
      "category": "database",
      "auth": "Username / password; TLS cert/hostname validated when ssl_mode is 'required'",
      "capabilities": {
        "read": true,
        "write": true,
        "discover": true,
        "pushdown-filter": true,
        "pushdown-aggregation": true
      },
      "operators": [
        "$eq",
        "$ne",
        "$gt",
        "$gte",
        "$lt",
        "$lte",
        "$like",
        "$ilike",
        "$in",
        "$nin",
        "$between",
        "$empty",
        "$contains",
        "$starts_with",
        "$ends_with",
        "$expr"
      ],
      "quirks": [
        "MySQL dialect: backtick identifier quoting, LIMIT/OFFSET paging, and INSERT ... ON DUPLICATE KEY UPDATE for upserts.",
        "Discovery reads INFORMATION_SCHEMA and filters out system schemas (information_schema, mysql, performance_schema, sys).",
        "TLS: ssl_mode 'required' validates the certificate and hostname; weaker modes do not validate.",
        "Connects with a native async MySQL driver wrapped to the common SQL adapter surface."
      ],
      "secrets": [
        "host",
        "port",
        "username",
        "password",
        "database",
        "charset",
        "ssl_mode"
      ],
      "notes": "MySQL over a native async driver with full read/write/discover and SQL filter + aggregation pushdown."
    },
    {
      "id": "postgres",
      "name": "PostgreSQL",
      "version": "2.0.7",
      "category": "database",
      "auth": "Username / password; TLS required by default (sslmode=require)",
      "capabilities": {
        "read": true,
        "write": true,
        "discover": true,
        "pushdown-filter": true,
        "pushdown-aggregation": true
      },
      "operators": [
        "$eq",
        "$ne",
        "$gt",
        "$gte",
        "$lt",
        "$lte",
        "$like",
        "$ilike",
        "$in",
        "$nin",
        "$between",
        "$empty",
        "$contains",
        "$starts_with",
        "$ends_with",
        "$expr"
      ],
      "quirks": [
        "PostgreSQL dialect: double-quote identifier quoting, LIMIT/OFFSET paging, and INSERT ... ON CONFLICT (...) DO UPDATE / DO NOTHING for upserts.",
        "Discovery reads information_schema and filters out pg_catalog/information_schema system schemas.",
        "TLS is required by default (sslmode=require).",
        "Connects via a synchronous Postgres driver (psycopg2-style %s parameter binding)."
      ],
      "secrets": [
        "host",
        "port",
        "username",
        "password",
        "database"
      ],
      "notes": "PostgreSQL with full read/write/discover and SQL pushdown. The same adapter class also powers the internal workspace store (see internal-postgres-workspace)."
    },
    {
      "id": "internal-postgres-workspace",
      "name": "Internal Postgres Workspace",
      "version": "baked-in",
      "category": "internal",
      "auth": "Azure Managed Identity (token-minted per connection; no stored password)",
      "capabilities": {
        "read": true,
        "write": true,
        "discover": true,
        "pushdown-filter": true,
        "pushdown-aggregation": "yes for read queries; richer grouping via saved reports / the report engine"
      },
      "operators": [
        "$eq",
        "$ne",
        "$gt",
        "$gte",
        "$lt",
        "$lte",
        "$like",
        "$ilike",
        "$in",
        "$nin",
        "$between",
        "$empty",
        "$contains",
        "$starts_with",
        "$ends_with",
        "$expr"
      ],
      "quirks": [
        "A built-in, per-tenant scratch/staging database exposed as the 'workspace' bridge — no credentials to configure; it ships with the platform.",
        "It is a SEPARATE database from the platform's operational tables, and a workspace query connection is pinned to that database. Because PostgreSQL cannot query across databases on one connection, a workspace query structurally cannot read or modify platform internals.",
        "Access uses a least-privilege, non-admin managed identity scoped only to the workspace database.",
        "Table create/add-column/drop are gated by a workspace-manage admin permission; table and column names are validated against a strict identifier pattern and a type allowlist.",
        "Reads without an explicit limit default to 1000 rows; each statement is bounded by a 30-second timeout.",
        "Cross-database joins (workspace data joined to another bridge's database in one SQL query) are not possible — combine results in a pipeline or report instead."
      ],
      "secrets": [],
      "notes": "A built-in per-tenant Postgres workspace for staging, intermediate results, and pipeline scratch data — zero credential setup, structurally isolated from platform internals."
    },
    {
      "id": "quickbooks-online",
      "name": "QuickBooks Online",
      "version": "2.0.1",
      "category": "erp",
      "auth": "OAuth 2.0 with a rotating refresh token",
      "capabilities": {
        "read": true,
        "write": true,
        "discover": true,
        "pushdown-filter": "yes — UQL WHERE renders into the QBO query language",
        "pushdown-aggregation": "no — the QBO query API has no GROUP BY/aggregate; aggregate in-engine"
      },
      "operators": [
        "$eq",
        "$ne",
        "$gt",
        "$gte",
        "$lt",
        "$lte",
        "$like",
        "$ilike",
        "$in",
        "$nin",
        "$between",
        "$empty",
        "$contains",
        "$starts_with",
        "$ends_with",
        "$expr"
      ],
      "quirks": [
        "Targets the QuickBooks Online Accounting API v3 query endpoint; read filters render into the QBO query language and the literal query rides in the URL.",
        "QBO uses ORDERBY as a single word (not ORDER BY) and STARTPOSITION/MAXRESULTS for paging — the adapter emits these forms automatically.",
        "QBO literals differ from standard SQL (booleans render as true/false, single quotes are backslash-escaped), so it does not reuse the generic SQL inliner.",
        "OAuth refresh-token rotation: the token endpoint requires form-encoded bodies, access tokens are cached until ~60s before expiry, and a 401 triggers a single refresh-and-retry. Refreshes are serialized so concurrent calls don't collide.",
        "Writes: insert posts the full object; update is a sparse update (SyncToken auto-fetched when not provided); delete uses Id+SyncToken.",
        "Name-list entities (Customers, Items, Accounts, Vendors) are not hard-deletable — they are deactivated via Active=false; CompanyInfo/Preferences are read-only.",
        "Has both production and sandbox base URLs; a minor-version parameter is sent with queries."
      ],
      "secrets": [
        "client_id",
        "client_secret",
        "refresh_token",
        "realm_id",
        "environment"
      ],
      "notes": "QuickBooks Online accounting data over the v3 REST API with read, write, and discover. Filtering pushes down to the QBO query language; aggregation is done in the engine. (Newer adapter; verify availability in your deployment.)"
    },
    {
      "id": "stripe",
      "name": "Stripe (preview)",
      "version": "0.1.0",
      "status": "preview",
      "category": "saas",
      "auth": "API secret key (Bearer)",
      "capabilities": {
        "read": true,
        "write": "insert / update / delete, declared per collection (some Stripe objects cannot be deleted, only cancelled/voided)",
        "discover": true,
        "pushdown-filter": "no — declared pushdown=false; UQL WHERE is applied in-engine over rows returned by the Stripe API",
        "pushdown-aggregation": "no — aggregation is performed in the query engine"
      },
      "operators": [
        "$eq",
        "$ne",
        "$gt",
        "$gte",
        "$lt",
        "$lte",
        "$like",
        "$ilike",
        "$in",
        "$nin",
        "$between",
        "$empty",
        "$contains",
        "$starts_with",
        "$ends_with"
      ],
      "quirks": [
        "PREVIEW / pre-1.0 (0.1.0): evaluation-grade, not production-hardened; collection coverage is still evolving. Pin behavior in tests before depending on it.",
        "Declares where=False and order=False, so the engine reads rows from the Stripe REST API and applies filtering, ordering, and aggregation in memory (bounded by the post-process row budget).",
        "Writes are gated per collection: some Stripe objects are read-only or can only be cancelled/voided rather than hard-deleted.",
        "No merge/upsert."
      ],
      "secrets": [
        "secret_key"
      ],
      "notes": "PREVIEW connector for Stripe billing/payments data over the Stripe REST API. Read plus per-collection writes; no pushdown yet (filter, order, and aggregation run in the query engine). Treat as evaluation-grade until it reaches 1.0."
    },
    {
      "id": "jdbc-generic",
      "name": "Generic JDBC",
      "version": "2.0.5",
      "category": "generic",
      "auth": "Username / password (per the JDBC connection URL); driver JAR uploaded to the bridge",
      "capabilities": {
        "read": true,
        "write": "insert / update / delete (SQL); merge/upsert is dialect-dependent and not assumed across all sources",
        "discover": true,
        "pushdown-filter": true,
        "pushdown-aggregation": true
      },
      "operators": [
        "$eq",
        "$ne",
        "$gt",
        "$gte",
        "$lt",
        "$lte",
        "$like",
        "$ilike",
        "$in",
        "$nin",
        "$between",
        "$empty",
        "$contains",
        "$starts_with",
        "$ends_with",
        "$expr"
      ],
      "quirks": [
        "Generic path to any JDBC-speaking database (Oracle, Db2, Snowflake, Databricks, MariaDB, Redshift, and more): supply driver_class, connection_url, and upload the vendor JDBC driver JAR.",
        "Pushes a full analytical SQL surface down to the source: WHERE, ORDER BY, GROUP BY, aggregates, DISTINCT, HAVING, joins, and select expressions.",
        "merge/upsert is not assumed uniformly because merge syntax differs by dialect; express upserts as update-then-insert where unsupported.",
        "Requires the JDBC driver sidecar to be enabled in the deployment (same sidecar used by NetSuite SuiteAnalytics Connect).",
        "$expr passes a raw SQL fragment through — a trusted-author surface."
      ],
      "secrets": [
        "driver_class",
        "connection_url",
        "username",
        "password"
      ],
      "notes": "Configurable JDBC connector for any database with a JDBC driver — supply the driver class, connection URL, and driver JAR. Full read/write/discover and SQL filter + aggregation pushdown. Same substrate that powers NetSuite SuiteAnalytics Connect; requires the JDBC sidecar."
    },
    {
      "id": "http-rest-generic",
      "name": "Generic HTTP/REST",
      "version": "built-in",
      "category": "generic",
      "auth": "Bridge-resolved: OAuth 2.0 client-credentials, bearer/static token, or API-key header — credentials held in Key Vault",
      "capabilities": {
        "read": true,
        "write": "author-declared (implement the write branch of build_request where the API supports it)",
        "discover": "author-declared (optional schema discovery script)",
        "pushdown-filter": "author-declared — you declare only the operators your build_request genuinely translates; the engine applies the rest in-memory",
        "pushdown-aggregation": "no — aggregation runs in the query engine"
      },
      "operators": [
        "author-declared subset (whatever build_request translates into the target API's query parameters)"
      ],
      "quirks": [
        "First-class connector substrate for arbitrary REST/GraphQL APIs: author sandbox-compiled build_request and parse_response scripts to map UQL queries to HTTP requests and responses to rows.",
        "Authentication is resolved through the bridge (OAuth2 client-credentials tokens are fetched, cached, and refreshed); scripts read resolved values, never raw secrets.",
        "Scripts run under the pipeline sandbox (import allowlist, no filesystem, no raw sockets) and can only egress through the governed SSRF-checked, allowlisted HTTP client.",
        "Declare only the pushdown you actually implement; anything not pushed down is applied in-engine over returned rows (bounded by the row budget).",
        "Distinct from and more capable than a pipeline's ad-hoc ctx.http calls: a reusable, credentialed, RBAC-scoped, UQL-queryable data source."
      ],
      "secrets": [
        "base_url",
        "auth_config"
      ],
      "notes": "Generic HTTP/REST connector substrate for integrating any REST or GraphQL API without a native adapter. Author build_request/parse_response scripts (sandbox-compiled); OAuth2/auth-header resolution via the bridge; SSRF-checked egress. The supported way to reach an arbitrary API through the same UQL surface, capability model, and RBAC as a native connector."
    }
  ],
  "generated_note": "Canonical, public-safe reference of Nexus data adapters/connectors — auth, read/write/discover and pushdown capabilities, supported UQL filter operators, credential field names, and operational quirks — for human and AI/LLM consumers.",
  "source": "https://stingraytechnologysolutions.com/adapters.html"
}