diff --git a/.changeset/connector-template-cluster-removed.md b/.changeset/connector-template-cluster-removed.md
new file mode 100644
index 0000000000..20f8d8ade0
--- /dev/null
+++ b/.changeset/connector-template-cluster-removed.md
@@ -0,0 +1,62 @@
+---
+'@objectstack/spec': major
+---
+
+The per-provider connector "template" cluster is removed (#4480, ADR-0049)
+
+`@objectstack/spec/integration` no longer exports the six per-provider
+connector schemas and their sub-schema/type/example clusters (~110 exports,
+2,672 lines):
+
+- `DatabaseConnectorSchema` (+ `DatabaseProviderSchema`, `DatabasePoolConfigSchema`,
+ `SslConfigSchema`, `CdcConfigSchema`, `DatabaseTableSchema`, the three
+ `*ConnectorExample` constants)
+- `FileStorageConnectorSchema` (+ bucket/versioning/multipart/filter configs, examples)
+- `GitHubConnectorSchema` (+ repository/commit/PR/actions/release/issue configs, examples)
+- `MessageQueueConnectorSchema` (+ its queue/topic/consumer configs, examples)
+- `SaasConnectorSchema` (+ examples)
+- `VercelConnectorSchema` (+ its deployment/domain/env configs, examples)
+
+The six generated reference pages under `docs/references/integration/` go with
+them.
+
+**Why removal, not completion.** These files were the losing side of an
+architecture decision the same module's live half already records. ADR-0023
+rejected hand-modelling each external system's shape inside the spec —
+"re-inventing OpenAPI inside this schema" — and ADR-0097's connector protocol
+does the opposite: one `ConnectorSchema`, with provider shapes coming from the
+provider itself (`connector-openapi` materializes instances from an OpenAPI
+document, `connector-mcp` from an MCP server). The templates hardcoded
+Postgres/S3/GitHub/RabbitMQ/Vercel shapes into spec files nothing ever read:
+
+- `engine.registerConnector()` validates against `ConnectorSchema` from
+ `connector.zod.ts` — never the templates
+- the `connectors:` stack collection parses `DeclarativeConnectorEntrySchema` —
+ never the templates
+- nothing else in the monorepo, objectui included, imported any of the six
+
+They were also semantically wrong where they overlapped the live platform:
+`DatabaseConnectorSchema` modelled "tables to sync", CDC, and `readReplicaConfig`
+— a second, independent declaration of read-replica routing (the first,
+`datasource.readReplicas`, was removed in #4468), complete with a `weight`
+field for a load balancer that does not exist. External-database access is
+datasource federation (ADR-0015), which is live and is not a connector.
+
+**Migration.** There is nothing to migrate: these schemas validated no stored
+metadata (the `connectors:` collection never used them) and no runtime read
+their output. If you imported one as a TypeScript type for your own code,
+model your provider config yourself, or — the supported path — declare a
+provider-bound connector instance and let connector-openapi / connector-mcp
+derive the shape:
+
+```ts
+// before (typed against a dead spec export)
+import { DatabaseConnector } from '@objectstack/spec/integration';
+
+// after (the live protocol)
+import { Connector, DeclarativeConnectorEntry } from '@objectstack/spec/integration';
+```
+
+The base protocol — `ConnectorSchema`, `DeclarativeConnectorEntrySchema`, the
+ADR-0097 provider contract, connector-descriptor, connector auth — is
+unchanged.
diff --git a/content/docs/getting-started/quick-reference.mdx b/content/docs/getting-started/quick-reference.mdx
index 6600eb94a7..603d216b20 100644
--- a/content/docs/getting-started/quick-reference.mdx
+++ b/content/docs/getting-started/quick-reference.mdx
@@ -193,19 +193,18 @@ Environments, marketplace, licensing, and multi-tenancy.
| **[Plugin Security](/docs/references/cloud/plugin-security)** | `plugin-security.zod.ts` | PluginSecurityProtocol, SBOM | Plugin security policies |
| **[Tenant](/docs/references/cloud/tenant)** | `tenant.zod.ts` | Tenant | Multi-tenancy isolation |
-## Integration Protocol (7 schemas)
+## Integration Protocol (1 schema)
-External system connectors and adapters.
+External system connectors — one protocol (ADR-0097): a connector entry is
+either a catalog descriptor or a provider-bound instance that a generic
+executor (connector-openapi / connector-mcp) materializes at boot. The
+per-provider schema "templates" (SaaS / database / file-storage /
+message-queue / GitHub / Vercel) were removed in #4480: provider shapes come
+from the provider itself, not from hand-written spec files.
| Protocol | Source File | Key Schemas | Purpose |
|:---------|:-----------|:------------|:--------|
-| **[Connector](/docs/references/integration/connector)** | `connector.zod.ts` | Connector | Generic connector interface |
-| **[SaaS Connector](/docs/references/integration/connector)** | `connector/saas.zod.ts` | SaaSConnector | SaaS integrations |
-| **[Database Connector](/docs/references/integration/connector)** | `connector/database.zod.ts` | DatabaseConnector | Database adapters |
-| **[File Storage](/docs/references/integration/connector)** | `connector/file-storage.zod.ts` | FileStorageConnector | Cloud storage |
-| **[Message Queue](/docs/references/integration/message-queue)** | `connector/message-queue.zod.ts` | MessageQueueConnector | Queue integrations |
-| **[GitHub](/docs/references/integration/connector)** | `connector/github.zod.ts` | GitHubConnector | GitHub API integration |
-| **[Vercel](/docs/references/integration/connector)** | `connector/vercel.zod.ts` | VercelConnector | Vercel deployment |
+| **[Connector](/docs/references/integration/connector)** | `connector.zod.ts` | Connector | The connector protocol — auth, sync, webhooks, rate limiting |
## Shared Protocol (5 schemas)
diff --git a/content/docs/references/index.mdx b/content/docs/references/index.mdx
index 07c912df63..0929d1f3bd 100644
--- a/content/docs/references/index.mdx
+++ b/content/docs/references/index.mdx
@@ -1,6 +1,6 @@
---
title: Protocol Reference
-description: Complete reference of all 139 ObjectStack protocol specifications
+description: Complete reference of all 133 ObjectStack protocol specifications
---
# Protocol Reference
@@ -30,7 +30,7 @@ These reference pages are **auto-generated** from the Zod source files in `packa
| [QA Protocol](#qa-protocol) | 1 | Test Suites and BDD Scenarios |
| [Studio Protocol](#studio-protocol) | 1 | Studio plugin development |
-**Total: 175 Zod schemas** (across 14 protocol modules + 1 root stack schema)
+**Total: 169 Zod schemas** (across 14 protocol modules + 1 root stack schema)
---
@@ -313,19 +313,18 @@ Defines marketplace and multi-tenancy capabilities.
## Integration Protocol
**Location:** `packages/spec/src/integration/`
-**Count:** 7 schemas
+**Count:** 1 schema
-Defines external system connectors and adapters.
+Defines external system connectors — one protocol (ADR-0097). A connector
+entry is either a catalog descriptor or a provider-bound instance that a
+generic executor (connector-openapi / connector-mcp) materializes at boot.
+The per-provider "templates" (`connector/saas.zod.ts` and five siblings) were
+removed in #4480: they hand-modelled each external system's shape inside the
+spec, which ADR-0023 rejected, and nothing ever consumed them.
| File | Schema | Purpose |
| :--- | :--- | :--- |
-| `connector.zod.ts` | `ConnectorSchema` | Generic connector interface |
-| `connector/saas.zod.ts` | `SaaSConnectorSchema` | SaaS platform connectors (Salesforce, HubSpot, etc.) |
-| `connector/database.zod.ts` | `DatabaseConnectorSchema` | Database connection adapters |
-| `connector/file-storage.zod.ts` | `FileStorageConnectorSchema` | Cloud storage connectors (S3, Azure Blob, etc.) |
-| `connector/message-queue.zod.ts` | `MessageQueueConnectorSchema` | Message queue integrations (RabbitMQ, Kafka, etc.) |
-| `connector/github.zod.ts` | `GitHubConnectorSchema` | GitHub API integration |
-| `connector/vercel.zod.ts` | `VercelConnectorSchema` | Vercel deployment integration |
+| `connector.zod.ts` | `ConnectorSchema` | The connector protocol — auth, sync, webhooks, rate limiting |
**Learn more:** [Integration Protocol Reference](/docs/references/integration)
diff --git a/content/docs/references/integration/connector-database.mdx b/content/docs/references/integration/connector-database.mdx
deleted file mode 100644
index b550393cf7..0000000000
--- a/content/docs/references/integration/connector-database.mdx
+++ /dev/null
@@ -1,139 +0,0 @@
----
-title: Connector Database
-description: Connector Database protocol schemas
----
-
-{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
-
-Database Connector Protocol Template
-
-Specialized connector for database systems (PostgreSQL, MySQL, SQL Server, etc.)
-
-Extends the base connector with database-specific features like schema discovery,
-
-CDC (Change Data Capture), and connection pooling.
-
-
-**Source:** `packages/spec/src/integration/connector/database.zod.ts`
-
-
-## TypeScript Usage
-
-```typescript
-import { CdcConfig, DatabaseConnector, DatabasePoolConfig, DatabaseTable, SslConfig } from '@objectstack/spec/integration';
-import type { CdcConfig, DatabaseConnector, DatabasePoolConfig, DatabaseTable, SslConfig } from '@objectstack/spec/integration';
-
-// Validate data
-const result = CdcConfig.parse(data);
-```
-
----
-
-## CdcConfig
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **enabled** | `boolean` | ✅ | Enable CDC |
-| **method** | `Enum<'log_based' \| 'trigger_based' \| 'query_based' \| 'custom'>` | ✅ | CDC method |
-| **slotName** | `string` | optional | Replication slot name (for log-based CDC) |
-| **publicationName** | `string` | optional | Publication name (for PostgreSQL) |
-| **startPosition** | `string` | optional | Starting position/LSN for CDC stream |
-| **batchSize** | `number` | ✅ | CDC batch size |
-| **pollIntervalMs** | `number` | ✅ | CDC polling interval in ms |
-
-
----
-
-## DatabaseConnector
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **name** | `string` | ✅ | Unique connector identifier |
-| **label** | `string` | ✅ | Display label |
-| **type** | `'database'` | ✅ | |
-| **description** | `string` | optional | Connector description |
-| **icon** | `string` | optional | Icon identifier |
-| **authentication** | `{ type: 'oauth2'; authorizationUrl: string; tokenUrl: string; clientId: string; … } \| { type: 'api-key'; key: string; headerName?: string; paramName?: string } \| { type: 'basic'; username: string; password: string } \| { type: 'bearer'; token: string } \| { type: 'none' }` | optional | Authentication configuration (runtime shape with inline secrets). Provider-bound declarative instances use `auth.credentialRef` instead. |
-| **provider** | `Enum<'postgresql' \| 'mysql' \| 'mariadb' \| 'mssql' \| 'oracle' \| 'mongodb' \| 'redis' \| 'cassandra' \| 'snowflake' \| 'bigquery' \| 'redshift' \| 'custom'>` | ✅ | Database provider type |
-| **providerConfig** | `Record` | optional | Provider-specific config validated by the provider factory at boot (e.g. `{ spec, baseUrl }` for openapi, where spec is an inline document, a package-relative file path like './billing-openapi.json', or an http(s) URL). Requires `provider`. |
-| **auth** | `{ type: 'none' } \| { type: 'bearer'; credentialRef: string } \| { type: 'api-key'; credentialRef: string; headerName?: string; paramName?: string } \| { type: 'basic'; username: string; credentialRef: string }` | optional | Declarative instance auth — references credentials via `credentialRef` (resolved at boot), never inline secrets. Requires `provider` (ADR-0097). |
-| **actions** | `{ key: string; label: string; description?: string; inputSchema?: Record; … }[]` | optional | |
-| **triggers** | `{ key: string; label: string; description?: string; type: Enum<'polling' \| 'webhook'>; … }[]` | optional | Trigger definitions (not yet enforced — never read at registration; see #3197) |
-| **syncConfig** | `{ strategy?: Enum<'full' \| 'incremental' \| 'upsert' \| 'append_only'>; direction?: Enum<'import' \| 'export' \| 'bidirectional'>; schedule?: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; realtimeSync?: boolean; … }` | optional | Data sync configuration |
-| **fieldMappings** | `{ source: string; target: string; transform?: { type: 'constant'; value: any } \| { type: 'cast'; targetType: Enum<'string' \| 'number' \| 'boolean' \| 'date'> } \| { type: 'lookup'; table: string; keyField: string; valueField: string } \| { type: 'javascript'; expression: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object } } \| { type: 'map'; mappings: Record }; defaultValue?: any; … }[]` | optional | Field mapping rules |
-| **webhooks** | `{ name: string; label?: string; object?: string; triggers?: Enum<'create' \| 'update' \| 'delete'>[]; … }[]` | optional | Webhook configurations (not yet enforced — never read at registration; see #3197) |
-| **rateLimitConfig** | `{ strategy?: Enum<'fixed_window' \| 'sliding_window' \| 'token_bucket' \| 'leaky_bucket'>; maxRequests: number; windowSeconds: number; burstCapacity?: number; … }` | optional | Rate limiting configuration |
-| **retryConfig** | `{ strategy?: Enum<'exponential_backoff' \| 'linear_backoff' \| 'fixed_delay' \| 'no_retry'>; maxAttempts?: number; initialDelayMs?: number; maxDelayMs?: number; … }` | optional | Retry configuration |
-| **connectionTimeoutMs** | `number` | optional | Connection timeout in ms |
-| **requestTimeoutMs** | `number` | optional | Request timeout in ms |
-| **status** | `Enum<'active' \| 'inactive' \| 'error' \| 'configuring'>` | optional | Connector status |
-| **enabled** | `boolean` | optional | Enable connector. On declarative stack entries, false marks a deliberate catalog-only descriptor (#2612). |
-| **errorMapping** | `{ rules: { sourceCode: string \| number; sourceMessage?: string; targetCode: string; targetCategory: Enum<'validation' \| 'authorization' \| 'not_found' \| 'conflict' \| 'rate_limit' \| 'timeout' \| 'server_error' \| 'integration_error'>; … }[]; defaultCategory?: Enum<'validation' \| 'authorization' \| 'not_found' \| 'conflict' \| 'rate_limit' \| 'timeout' \| 'server_error' \| 'integration_error'>; unmappedBehavior: Enum<'passthrough' \| 'generic_error' \| 'throw'>; logUnmapped?: boolean }` | optional | Error mapping configuration |
-| **health** | `{ healthCheck?: object; circuitBreaker?: object }` | optional | Health and resilience configuration |
-| **metadata** | `Record` | optional | Custom connector metadata |
-| **connectionConfig** | `{ host: string; port: number; database: string; username: string; … }` | ✅ | Database connection configuration |
-| **poolConfig** | `{ min?: number; max?: number; idleTimeoutMs?: number; connectionTimeoutMs?: number; … }` | optional | Connection pool configuration |
-| **sslConfig** | `{ enabled?: boolean; rejectUnauthorized?: boolean; ca?: string; cert?: string; … }` | optional | SSL/TLS configuration |
-| **tables** | `{ name: string; label: string; schema?: string; tableName: string; … }[]` | ✅ | Tables to sync |
-| **cdcConfig** | `{ enabled?: boolean; method: Enum<'log_based' \| 'trigger_based' \| 'query_based' \| 'custom'>; slotName?: string; publicationName?: string; … }` | optional | CDC configuration |
-| **readReplicaConfig** | `{ enabled?: boolean; hosts: { host: string; port: number; weight?: number }[] }` | optional | Read replica configuration |
-| **queryTimeoutMs** | `number` | optional | Query timeout in ms |
-| **enableQueryLogging** | `boolean` | optional | Enable SQL query logging |
-
-
----
-
-## DatabasePoolConfig
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **min** | `number` | ✅ | Minimum connections in pool |
-| **max** | `number` | ✅ | Maximum connections in pool |
-| **idleTimeoutMs** | `number` | ✅ | Idle connection timeout in ms |
-| **connectionTimeoutMs** | `number` | ✅ | Connection establishment timeout in ms |
-| **acquireTimeoutMs** | `number` | ✅ | Connection acquisition timeout in ms |
-| **evictionRunIntervalMs** | `number` | ✅ | Connection eviction check interval in ms |
-| **testOnBorrow** | `boolean` | ✅ | Test connection before use |
-
-
----
-
-## DatabaseTable
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **name** | `string` | ✅ | Table name in ObjectStack (snake_case) |
-| **label** | `string` | ✅ | Display label |
-| **schema** | `string` | optional | Database schema name |
-| **tableName** | `string` | ✅ | Actual table name in database |
-| **primaryKey** | `string` | ✅ | Primary key column |
-| **enabled** | `boolean` | optional | Enable sync for this table |
-| **fieldMappings** | `{ source: string; target: string; transform?: { type: 'constant'; value: any } \| { type: 'cast'; targetType: Enum<'string' \| 'number' \| 'boolean' \| 'date'> } \| { type: 'lookup'; table: string; keyField: string; valueField: string } \| { type: 'javascript'; expression: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object } } \| { type: 'map'; mappings: Record }; defaultValue?: any; … }[]` | optional | Table-specific field mappings |
-| **whereClause** | `string` | optional | SQL WHERE clause for filtering |
-
-
----
-
-## SslConfig
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **enabled** | `boolean` | ✅ | Enable SSL/TLS |
-| **rejectUnauthorized** | `boolean` | ✅ | Reject unauthorized certificates |
-| **ca** | `string` | optional | Certificate Authority certificate |
-| **cert** | `string` | optional | Client certificate |
-| **key** | `string` | optional | Client private key |
-
-
----
-
diff --git a/content/docs/references/integration/connector-file-storage.mdx b/content/docs/references/integration/connector-file-storage.mdx
deleted file mode 100644
index 5235de8e66..0000000000
--- a/content/docs/references/integration/connector-file-storage.mdx
+++ /dev/null
@@ -1,170 +0,0 @@
----
-title: Connector File Storage
-description: Connector File Storage protocol schemas
----
-
-{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
-
-File Storage Connector Protocol Template
-
-Specialized connector for file storage systems (S3, Azure Blob, Google Cloud Storage, etc.)
-
-Extends the base connector with file-specific features like multipart uploads,
-
-versioning, and metadata extraction.
-
-
-**Source:** `packages/spec/src/integration/connector/file-storage.zod.ts`
-
-
-## TypeScript Usage
-
-```typescript
-import { FileAccessPattern, FileFilterConfig, FileMetadataConfig, FileStorageConnector, FileStorageProvider, FileVersioningConfig, StorageBucket } from '@objectstack/spec/integration';
-import type { FileAccessPattern, FileFilterConfig, FileMetadataConfig, FileStorageConnector, FileStorageProvider, FileVersioningConfig, StorageBucket } from '@objectstack/spec/integration';
-
-// Validate data
-const result = FileAccessPattern.parse(data);
-```
-
----
-
-## FileAccessPattern
-
-File access pattern
-
-### Allowed Values
-
-* `public_read`
-* `private`
-* `authenticated_read`
-* `bucket_owner_read`
-* `bucket_owner_full`
-
-
----
-
-## FileFilterConfig
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **includePatterns** | `string[]` | optional | File patterns to include (glob) |
-| **excludePatterns** | `string[]` | optional | File patterns to exclude (glob) |
-| **minFileSize** | `number` | optional | Minimum file size in bytes |
-| **maxFileSize** | `number` | optional | Maximum file size in bytes |
-| **allowedExtensions** | `string[]` | optional | Allowed file extensions |
-| **blockedExtensions** | `string[]` | optional | Blocked file extensions |
-
-
----
-
-## FileMetadataConfig
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **extractMetadata** | `boolean` | ✅ | Extract file metadata |
-| **metadataFields** | `Enum<'content_type' \| 'file_size' \| 'last_modified' \| 'etag' \| 'checksum' \| 'creator' \| 'created_at' \| 'custom'>[]` | optional | Metadata fields to extract |
-| **customMetadata** | `Record` | optional | Custom metadata key-value pairs |
-
-
----
-
-## FileStorageConnector
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **name** | `string` | ✅ | Unique connector identifier |
-| **label** | `string` | ✅ | Display label |
-| **type** | `'file_storage'` | ✅ | |
-| **description** | `string` | optional | Connector description |
-| **icon** | `string` | optional | Icon identifier |
-| **authentication** | `{ type: 'oauth2'; authorizationUrl: string; tokenUrl: string; clientId: string; … } \| { type: 'api-key'; key: string; headerName?: string; paramName?: string } \| { type: 'basic'; username: string; password: string } \| { type: 'bearer'; token: string } \| { type: 'none' }` | optional | Authentication configuration (runtime shape with inline secrets). Provider-bound declarative instances use `auth.credentialRef` instead. |
-| **provider** | `Enum<'s3' \| 'azure_blob' \| 'gcs' \| 'dropbox' \| 'box' \| 'onedrive' \| 'google_drive' \| 'sharepoint' \| 'ftp' \| 'local' \| 'custom'>` | ✅ | File storage provider type |
-| **providerConfig** | `Record` | optional | Provider-specific config validated by the provider factory at boot (e.g. `{ spec, baseUrl }` for openapi, where spec is an inline document, a package-relative file path like './billing-openapi.json', or an http(s) URL). Requires `provider`. |
-| **auth** | `{ type: 'none' } \| { type: 'bearer'; credentialRef: string } \| { type: 'api-key'; credentialRef: string; headerName?: string; paramName?: string } \| { type: 'basic'; username: string; credentialRef: string }` | optional | Declarative instance auth — references credentials via `credentialRef` (resolved at boot), never inline secrets. Requires `provider` (ADR-0097). |
-| **actions** | `{ key: string; label: string; description?: string; inputSchema?: Record; … }[]` | optional | |
-| **triggers** | `{ key: string; label: string; description?: string; type: Enum<'polling' \| 'webhook'>; … }[]` | optional | Trigger definitions (not yet enforced — never read at registration; see #3197) |
-| **syncConfig** | `{ strategy?: Enum<'full' \| 'incremental' \| 'upsert' \| 'append_only'>; direction?: Enum<'import' \| 'export' \| 'bidirectional'>; schedule?: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; realtimeSync?: boolean; … }` | optional | Data sync configuration |
-| **fieldMappings** | `{ source: string; target: string; transform?: { type: 'constant'; value: any } \| { type: 'cast'; targetType: Enum<'string' \| 'number' \| 'boolean' \| 'date'> } \| { type: 'lookup'; table: string; keyField: string; valueField: string } \| { type: 'javascript'; expression: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object } } \| { type: 'map'; mappings: Record }; defaultValue?: any; … }[]` | optional | Field mapping rules |
-| **webhooks** | `{ name: string; label?: string; object?: string; triggers?: Enum<'create' \| 'update' \| 'delete'>[]; … }[]` | optional | Webhook configurations (not yet enforced — never read at registration; see #3197) |
-| **rateLimitConfig** | `{ strategy?: Enum<'fixed_window' \| 'sliding_window' \| 'token_bucket' \| 'leaky_bucket'>; maxRequests: number; windowSeconds: number; burstCapacity?: number; … }` | optional | Rate limiting configuration |
-| **retryConfig** | `{ strategy?: Enum<'exponential_backoff' \| 'linear_backoff' \| 'fixed_delay' \| 'no_retry'>; maxAttempts?: number; initialDelayMs?: number; maxDelayMs?: number; … }` | optional | Retry configuration |
-| **connectionTimeoutMs** | `number` | optional | Connection timeout in ms |
-| **requestTimeoutMs** | `number` | optional | Request timeout in ms |
-| **status** | `Enum<'active' \| 'inactive' \| 'error' \| 'configuring'>` | optional | Connector status |
-| **enabled** | `boolean` | optional | Enable connector. On declarative stack entries, false marks a deliberate catalog-only descriptor (#2612). |
-| **errorMapping** | `{ rules: { sourceCode: string \| number; sourceMessage?: string; targetCode: string; targetCategory: Enum<'validation' \| 'authorization' \| 'not_found' \| 'conflict' \| 'rate_limit' \| 'timeout' \| 'server_error' \| 'integration_error'>; … }[]; defaultCategory?: Enum<'validation' \| 'authorization' \| 'not_found' \| 'conflict' \| 'rate_limit' \| 'timeout' \| 'server_error' \| 'integration_error'>; unmappedBehavior: Enum<'passthrough' \| 'generic_error' \| 'throw'>; logUnmapped?: boolean }` | optional | Error mapping configuration |
-| **health** | `{ healthCheck?: object; circuitBreaker?: object }` | optional | Health and resilience configuration |
-| **metadata** | `Record` | optional | Custom connector metadata |
-| **storageConfig** | `{ endpoint?: string; region?: string; pathStyle?: boolean }` | optional | Storage configuration |
-| **buckets** | `{ name: string; label: string; bucketName: string; region?: string; … }[]` | ✅ | Buckets/containers to sync |
-| **metadataConfig** | `{ extractMetadata?: boolean; metadataFields?: Enum<'content_type' \| 'file_size' \| 'last_modified' \| 'etag' \| 'checksum' \| 'creator' \| 'created_at' \| 'custom'>[]; customMetadata?: Record }` | optional | Metadata extraction configuration |
-| **multipartConfig** | `{ enabled?: boolean; partSize?: number; maxConcurrentParts?: number; threshold?: number }` | optional | Multipart upload configuration |
-| **versioningConfig** | `{ enabled?: boolean; maxVersions?: number; retentionDays?: number }` | optional | File versioning configuration |
-| **encryption** | `{ enabled?: boolean; algorithm?: Enum<'AES256' \| 'aws:kms' \| 'custom'>; kmsKeyId?: string }` | optional | Encryption configuration |
-| **lifecyclePolicy** | `{ enabled?: boolean; deleteAfterDays?: number; archiveAfterDays?: number }` | optional | Lifecycle policy |
-| **contentProcessing** | `{ extractText?: boolean; generateThumbnails?: boolean; thumbnailSizes?: { width: number; height: number }[]; virusScan?: boolean }` | optional | Content processing configuration |
-| **bufferSize** | `number` | optional | Buffer size in bytes |
-| **transferAcceleration** | `boolean` | optional | Enable transfer acceleration |
-
-
----
-
-## FileStorageProvider
-
-File storage provider type
-
-### Allowed Values
-
-* `s3`
-* `azure_blob`
-* `gcs`
-* `dropbox`
-* `box`
-* `onedrive`
-* `google_drive`
-* `sharepoint`
-* `ftp`
-* `local`
-* `custom`
-
-
----
-
-## FileVersioningConfig
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **enabled** | `boolean` | ✅ | Enable file versioning |
-| **maxVersions** | `number` | optional | Maximum versions to retain |
-| **retentionDays** | `number` | optional | Version retention period in days |
-
-
----
-
-## StorageBucket
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **name** | `string` | ✅ | Bucket identifier in ObjectStack (snake_case) |
-| **label** | `string` | ✅ | Display label |
-| **bucketName** | `string` | ✅ | Actual bucket/container name in storage system |
-| **region** | `string` | optional | Storage region |
-| **enabled** | `boolean` | ✅ | Enable sync for this bucket |
-| **prefix** | `string` | optional | Prefix/path within bucket |
-| **accessPattern** | `Enum<'public_read' \| 'private' \| 'authenticated_read' \| 'bucket_owner_read' \| 'bucket_owner_full'>` | optional | Access pattern |
-| **fileFilters** | `{ includePatterns?: string[]; excludePatterns?: string[]; minFileSize?: number; maxFileSize?: number; … }` | optional | File filter configuration |
-
-
----
-
diff --git a/content/docs/references/integration/connector-github.mdx b/content/docs/references/integration/connector-github.mdx
deleted file mode 100644
index df78fc7bbb..0000000000
--- a/content/docs/references/integration/connector-github.mdx
+++ /dev/null
@@ -1,246 +0,0 @@
----
-title: Connector Github
-description: Connector Github protocol schemas
----
-
-{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
-
-GitHub Connector Protocol
-
-Specialized connector for GitHub integration enabling automated
-
-version control operations, CI/CD workflows, and release management.
-
-Use Cases:
-
-- Automated code commits and pull requests
-
-- GitHub Actions workflow management
-
-- Issue and project tracking
-
-- Release and tag management
-
-- Repository administration
-
-@example
-
-```typescript
-
-import \{ GitHubConnector \} from '@objectstack/spec/integration';
-
-const githubConnector: GitHubConnector = \{
-
-name: 'github_enterprise',
-
-label: 'GitHub Enterprise',
-
-type: 'saas',
-
-provider: 'github',
-
-baseUrl: 'https://api.github.com',
-
-authentication: \{
-
-type: 'oauth2',
-
-clientId: '$\{GITHUB_CLIENT_ID\}',
-
-clientSecret: '$\{GITHUB_CLIENT_SECRET\}',
-
-authorizationUrl: 'https://github.com/login/oauth/authorize',
-
-tokenUrl: 'https://github.com/login/oauth/access_token',
-
-grantType: 'authorization_code',
-
-scopes: ['repo', 'workflow', 'admin:org'],
-
-\},
-
-repositories: [
-
-\{
-
-owner: 'objectstack-ai',
-
-name: 'spec',
-
-defaultBranch: 'main',
-
-autoMerge: false,
-
-\},
-
-],
-
-\};
-
-```
-
-
-**Source:** `packages/spec/src/integration/connector/github.zod.ts`
-
-
-## TypeScript Usage
-
-```typescript
-import { GitHubActionsWorkflow, GitHubCommitConfig, GitHubConnector, GitHubIssueTracking, GitHubProvider, GitHubPullRequestConfig, GitHubReleaseConfig, GitHubRepository } from '@objectstack/spec/integration';
-import type { GitHubActionsWorkflow, GitHubCommitConfig, GitHubConnector, GitHubIssueTracking, GitHubProvider, GitHubPullRequestConfig, GitHubReleaseConfig, GitHubRepository } from '@objectstack/spec/integration';
-
-// Validate data
-const result = GitHubActionsWorkflow.parse(data);
-```
-
----
-
-## GitHubActionsWorkflow
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **name** | `string` | ✅ | Workflow name |
-| **path** | `string` | ✅ | Workflow file path (e.g., .github/workflows/ci.yml) |
-| **enabled** | `boolean` | ✅ | Enable workflow |
-| **triggers** | `Enum<'push' \| 'pull_request' \| 'release' \| 'schedule' \| 'workflow_dispatch' \| 'repository_dispatch'>[]` | optional | Workflow triggers |
-| **env** | `Record` | optional | Environment variables |
-| **secrets** | `string[]` | optional | Required secrets |
-
-
----
-
-## GitHubCommitConfig
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **authorName** | `string` | optional | Commit author name |
-| **authorEmail** | `string` | optional | Commit author email |
-| **signCommits** | `boolean` | ✅ | Sign commits with GPG |
-| **messageTemplate** | `string` | optional | Commit message template |
-| **useConventionalCommits** | `boolean` | ✅ | Use conventional commits format |
-
-
----
-
-## GitHubConnector
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **name** | `string` | ✅ | Unique connector identifier |
-| **label** | `string` | ✅ | Display label |
-| **type** | `'saas'` | ✅ | |
-| **description** | `string` | optional | Connector description |
-| **icon** | `string` | optional | Icon identifier |
-| **authentication** | `{ type: 'oauth2'; authorizationUrl: string; tokenUrl: string; clientId: string; … } \| { type: 'api-key'; key: string; headerName?: string; paramName?: string } \| { type: 'basic'; username: string; password: string } \| { type: 'bearer'; token: string } \| { type: 'none' }` | optional | Authentication configuration (runtime shape with inline secrets). Provider-bound declarative instances use `auth.credentialRef` instead. |
-| **provider** | `Enum<'github' \| 'github_enterprise'>` | ✅ | GitHub provider |
-| **providerConfig** | `Record` | optional | Provider-specific config validated by the provider factory at boot (e.g. `{ spec, baseUrl }` for openapi, where spec is an inline document, a package-relative file path like './billing-openapi.json', or an http(s) URL). Requires `provider`. |
-| **auth** | `{ type: 'none' } \| { type: 'bearer'; credentialRef: string } \| { type: 'api-key'; credentialRef: string; headerName?: string; paramName?: string } \| { type: 'basic'; username: string; credentialRef: string }` | optional | Declarative instance auth — references credentials via `credentialRef` (resolved at boot), never inline secrets. Requires `provider` (ADR-0097). |
-| **actions** | `{ key: string; label: string; description?: string; inputSchema?: Record; … }[]` | optional | |
-| **triggers** | `{ key: string; label: string; description?: string; type: Enum<'polling' \| 'webhook'>; … }[]` | optional | Trigger definitions (not yet enforced — never read at registration; see #3197) |
-| **syncConfig** | `{ strategy?: Enum<'full' \| 'incremental' \| 'upsert' \| 'append_only'>; direction?: Enum<'import' \| 'export' \| 'bidirectional'>; schedule?: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; realtimeSync?: boolean; … }` | optional | Data sync configuration |
-| **fieldMappings** | `{ source: string; target: string; transform?: { type: 'constant'; value: any } \| { type: 'cast'; targetType: Enum<'string' \| 'number' \| 'boolean' \| 'date'> } \| { type: 'lookup'; table: string; keyField: string; valueField: string } \| { type: 'javascript'; expression: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object } } \| { type: 'map'; mappings: Record }; defaultValue?: any; … }[]` | optional | Field mapping rules |
-| **webhooks** | `{ name: string; label?: string; object?: string; triggers?: Enum<'create' \| 'update' \| 'delete'>[]; … }[]` | optional | Webhook configurations (not yet enforced — never read at registration; see #3197) |
-| **rateLimitConfig** | `{ strategy?: Enum<'fixed_window' \| 'sliding_window' \| 'token_bucket' \| 'leaky_bucket'>; maxRequests: number; windowSeconds: number; burstCapacity?: number; … }` | optional | Rate limiting configuration |
-| **retryConfig** | `{ strategy?: Enum<'exponential_backoff' \| 'linear_backoff' \| 'fixed_delay' \| 'no_retry'>; maxAttempts?: number; initialDelayMs?: number; maxDelayMs?: number; … }` | optional | Retry configuration |
-| **connectionTimeoutMs** | `number` | optional | Connection timeout in ms |
-| **requestTimeoutMs** | `number` | optional | Request timeout in ms |
-| **status** | `Enum<'active' \| 'inactive' \| 'error' \| 'configuring'>` | optional | Connector status |
-| **enabled** | `boolean` | optional | Enable connector. On declarative stack entries, false marks a deliberate catalog-only descriptor (#2612). |
-| **errorMapping** | `{ rules: { sourceCode: string \| number; sourceMessage?: string; targetCode: string; targetCategory: Enum<'validation' \| 'authorization' \| 'not_found' \| 'conflict' \| 'rate_limit' \| 'timeout' \| 'server_error' \| 'integration_error'>; … }[]; defaultCategory?: Enum<'validation' \| 'authorization' \| 'not_found' \| 'conflict' \| 'rate_limit' \| 'timeout' \| 'server_error' \| 'integration_error'>; unmappedBehavior: Enum<'passthrough' \| 'generic_error' \| 'throw'>; logUnmapped?: boolean }` | optional | Error mapping configuration |
-| **health** | `{ healthCheck?: object; circuitBreaker?: object }` | optional | Health and resilience configuration |
-| **metadata** | `Record` | optional | Custom connector metadata |
-| **baseUrl** | `string` | optional | GitHub API base URL |
-| **repositories** | `{ owner: string; name: string; defaultBranch?: string; autoMerge?: boolean; … }[]` | ✅ | Repositories to manage |
-| **commitConfig** | `{ authorName?: string; authorEmail?: string; signCommits?: boolean; messageTemplate?: string; … }` | optional | Commit configuration |
-| **pullRequestConfig** | `{ titleTemplate?: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; bodyTemplate?: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; defaultReviewers?: string[]; defaultAssignees?: string[]; … }` | optional | Pull request configuration |
-| **workflows** | `{ name: string; path: string; enabled?: boolean; triggers?: Enum<'push' \| 'pull_request' \| 'release' \| 'schedule' \| 'workflow_dispatch' \| 'repository_dispatch'>[]; … }[]` | optional | GitHub Actions workflows |
-| **releaseConfig** | `{ tagPattern?: string; semanticVersioning?: boolean; autoReleaseNotes?: boolean; releaseNameTemplate?: string; … }` | optional | Release configuration |
-| **issueTracking** | `{ enabled?: boolean; defaultLabels?: string[]; templatePaths?: string[]; autoAssign?: boolean; … }` | optional | Issue tracking configuration |
-| **enableWebhooks** | `boolean` | optional | Enable GitHub webhooks |
-| **webhookEvents** | `Enum<'push' \| 'pull_request' \| 'issues' \| 'issue_comment' \| 'release' \| 'workflow_run' \| 'deployment' \| 'deployment_status' \| 'check_run' \| 'check_suite' \| 'status'>[]` | optional | Webhook events to subscribe to |
-
-
----
-
-## GitHubIssueTracking
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **enabled** | `boolean` | ✅ | Enable issue tracking |
-| **defaultLabels** | `string[]` | optional | Default issue labels |
-| **templatePaths** | `string[]` | optional | Issue template paths |
-| **autoAssign** | `boolean` | ✅ | Auto-assign issues |
-| **autoCloseStale** | `{ enabled: boolean; daysBeforeStale: integer; daysBeforeClose: integer; staleLabel: string }` | optional | Auto-close stale issues configuration |
-
-
----
-
-## GitHubProvider
-
-GitHub provider type
-
-### Allowed Values
-
-* `github`
-* `github_enterprise`
-
-
----
-
-## GitHubPullRequestConfig
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **titleTemplate** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }` | optional | PR title template — supports `{{var}`} interpolation |
-| **bodyTemplate** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }` | optional | PR body template — supports `{{var}`} interpolation |
-| **defaultReviewers** | `string[]` | optional | Default reviewers (usernames) |
-| **defaultAssignees** | `string[]` | optional | Default assignees (usernames) |
-| **defaultLabels** | `string[]` | optional | Default labels |
-| **draftByDefault** | `boolean` | optional | Create draft PRs by default |
-| **deleteHeadBranch** | `boolean` | optional | Delete head branch after merge |
-
-
----
-
-## GitHubReleaseConfig
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **tagPattern** | `string` | ✅ | Tag name pattern (e.g., v*, release/*) |
-| **semanticVersioning** | `boolean` | ✅ | Use semantic versioning |
-| **autoReleaseNotes** | `boolean` | ✅ | Generate release notes automatically |
-| **releaseNameTemplate** | `string` | optional | Release name template |
-| **preReleasePattern** | `string` | optional | Pre-release pattern (e.g., *-alpha, *-beta) |
-| **draftByDefault** | `boolean` | ✅ | Create draft releases by default |
-
-
----
-
-## GitHubRepository
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **owner** | `string` | ✅ | Repository owner (organization or username) |
-| **name** | `string` | ✅ | Repository name |
-| **defaultBranch** | `string` | ✅ | Default branch name |
-| **autoMerge** | `boolean` | ✅ | Enable auto-merge for pull requests |
-| **branchProtection** | `{ requiredReviewers: integer; requireStatusChecks: boolean; enforceAdmins: boolean; allowForcePushes: boolean; … }` | optional | Branch protection configuration |
-| **topics** | `string[]` | optional | Repository topics |
-
-
----
-
diff --git a/content/docs/references/integration/connector-message-queue.mdx b/content/docs/references/integration/connector-message-queue.mdx
deleted file mode 100644
index 3af90419ab..0000000000
--- a/content/docs/references/integration/connector-message-queue.mdx
+++ /dev/null
@@ -1,171 +0,0 @@
----
-title: Connector Message Queue
-description: Connector Message Queue protocol schemas
----
-
-{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
-
-Message Queue Connector Protocol Template
-
-Specialized connector for message queue systems (RabbitMQ, Kafka, SQS, etc.)
-
-Extends the base connector with message queue-specific features like topics,
-
-consumer groups, and message acknowledgment patterns.
-
-
-**Source:** `packages/spec/src/integration/connector/message-queue.zod.ts`
-
-
-## TypeScript Usage
-
-```typescript
-import { AckMode, DeliveryGuarantee, DlqConfig, MessageFormat, MessageQueueConnector, ProducerConfig, TopicQueue } from '@objectstack/spec/integration';
-import type { AckMode, DeliveryGuarantee, DlqConfig, MessageFormat, MessageQueueConnector, ProducerConfig, TopicQueue } from '@objectstack/spec/integration';
-
-// Validate data
-const result = AckMode.parse(data);
-```
-
----
-
-## AckMode
-
-Message acknowledgment mode
-
-### Allowed Values
-
-* `auto`
-* `manual`
-* `client`
-
-
----
-
-## DeliveryGuarantee
-
-Message delivery guarantee
-
-### Allowed Values
-
-* `at_most_once`
-* `at_least_once`
-* `exactly_once`
-
-
----
-
-## DlqConfig
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **enabled** | `boolean` | ✅ | Enable DLQ |
-| **queueName** | `string` | ✅ | Dead letter queue/topic name |
-| **maxRetries** | `number` | ✅ | Max retries before DLQ |
-| **retryDelayMs** | `number` | ✅ | Retry delay in ms |
-
-
----
-
-## MessageFormat
-
-Message format/serialization
-
-### Allowed Values
-
-* `json`
-* `xml`
-* `protobuf`
-* `avro`
-* `text`
-* `binary`
-
-
----
-
-## MessageQueueConnector
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **name** | `string` | ✅ | Unique connector identifier |
-| **label** | `string` | ✅ | Display label |
-| **type** | `'message_queue'` | ✅ | |
-| **description** | `string` | optional | Connector description |
-| **icon** | `string` | optional | Icon identifier |
-| **authentication** | `{ type: 'oauth2'; authorizationUrl: string; tokenUrl: string; clientId: string; … } \| { type: 'api-key'; key: string; headerName?: string; paramName?: string } \| { type: 'basic'; username: string; password: string } \| { type: 'bearer'; token: string } \| { type: 'none' }` | optional | Authentication configuration (runtime shape with inline secrets). Provider-bound declarative instances use `auth.credentialRef` instead. |
-| **provider** | `Enum<'rabbitmq' \| 'kafka' \| 'redis_pubsub' \| 'redis_streams' \| 'aws_sqs' \| 'aws_sns' \| 'google_pubsub' \| 'azure_service_bus' \| 'azure_event_hubs' \| 'nats' \| 'pulsar' \| 'activemq' \| 'custom'>` | ✅ | Message queue provider type |
-| **providerConfig** | `Record` | optional | Provider-specific config validated by the provider factory at boot (e.g. `{ spec, baseUrl }` for openapi, where spec is an inline document, a package-relative file path like './billing-openapi.json', or an http(s) URL). Requires `provider`. |
-| **auth** | `{ type: 'none' } \| { type: 'bearer'; credentialRef: string } \| { type: 'api-key'; credentialRef: string; headerName?: string; paramName?: string } \| { type: 'basic'; username: string; credentialRef: string }` | optional | Declarative instance auth — references credentials via `credentialRef` (resolved at boot), never inline secrets. Requires `provider` (ADR-0097). |
-| **actions** | `{ key: string; label: string; description?: string; inputSchema?: Record; … }[]` | optional | |
-| **triggers** | `{ key: string; label: string; description?: string; type: Enum<'polling' \| 'webhook'>; … }[]` | optional | Trigger definitions (not yet enforced — never read at registration; see #3197) |
-| **syncConfig** | `{ strategy?: Enum<'full' \| 'incremental' \| 'upsert' \| 'append_only'>; direction?: Enum<'import' \| 'export' \| 'bidirectional'>; schedule?: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; realtimeSync?: boolean; … }` | optional | Data sync configuration |
-| **fieldMappings** | `{ source: string; target: string; transform?: { type: 'constant'; value: any } \| { type: 'cast'; targetType: Enum<'string' \| 'number' \| 'boolean' \| 'date'> } \| { type: 'lookup'; table: string; keyField: string; valueField: string } \| { type: 'javascript'; expression: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object } } \| { type: 'map'; mappings: Record }; defaultValue?: any; … }[]` | optional | Field mapping rules |
-| **webhooks** | `{ name: string; label?: string; object?: string; triggers?: Enum<'create' \| 'update' \| 'delete'>[]; … }[]` | optional | Webhook configurations (not yet enforced — never read at registration; see #3197) |
-| **rateLimitConfig** | `{ strategy?: Enum<'fixed_window' \| 'sliding_window' \| 'token_bucket' \| 'leaky_bucket'>; maxRequests: number; windowSeconds: number; burstCapacity?: number; … }` | optional | Rate limiting configuration |
-| **retryConfig** | `{ strategy?: Enum<'exponential_backoff' \| 'linear_backoff' \| 'fixed_delay' \| 'no_retry'>; maxAttempts?: number; initialDelayMs?: number; maxDelayMs?: number; … }` | optional | Retry configuration |
-| **connectionTimeoutMs** | `number` | optional | Connection timeout in ms |
-| **requestTimeoutMs** | `number` | optional | Request timeout in ms |
-| **status** | `Enum<'active' \| 'inactive' \| 'error' \| 'configuring'>` | optional | Connector status |
-| **enabled** | `boolean` | optional | Enable connector. On declarative stack entries, false marks a deliberate catalog-only descriptor (#2612). |
-| **errorMapping** | `{ rules: { sourceCode: string \| number; sourceMessage?: string; targetCode: string; targetCategory: Enum<'validation' \| 'authorization' \| 'not_found' \| 'conflict' \| 'rate_limit' \| 'timeout' \| 'server_error' \| 'integration_error'>; … }[]; defaultCategory?: Enum<'validation' \| 'authorization' \| 'not_found' \| 'conflict' \| 'rate_limit' \| 'timeout' \| 'server_error' \| 'integration_error'>; unmappedBehavior: Enum<'passthrough' \| 'generic_error' \| 'throw'>; logUnmapped?: boolean }` | optional | Error mapping configuration |
-| **health** | `{ healthCheck?: object; circuitBreaker?: object }` | optional | Health and resilience configuration |
-| **metadata** | `Record` | optional | Custom connector metadata |
-| **brokerConfig** | `{ brokers: string[]; clientId?: string; connectionTimeoutMs?: number; requestTimeoutMs?: number }` | ✅ | Broker connection configuration |
-| **topics** | `{ name: string; label: string; topicName: string; enabled?: boolean; … }[]` | ✅ | Topics/queues to sync |
-| **deliveryGuarantee** | `Enum<'at_most_once' \| 'at_least_once' \| 'exactly_once'>` | optional | Message delivery guarantee |
-| **sslConfig** | `{ enabled?: boolean; rejectUnauthorized?: boolean; ca?: string; cert?: string; … }` | optional | SSL/TLS configuration |
-| **saslConfig** | `{ mechanism: Enum<'plain' \| 'scram-sha-256' \| 'scram-sha-512' \| 'aws'>; username?: string; password?: string }` | optional | SASL authentication configuration |
-| **schemaRegistry** | `{ url: string; auth?: object }` | optional | Schema registry configuration |
-| **preserveOrder** | `boolean` | optional | Preserve message ordering |
-| **enableMetrics** | `boolean` | optional | Enable message queue metrics |
-| **enableTracing** | `boolean` | optional | Enable distributed tracing |
-
-
----
-
-## ProducerConfig
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **enabled** | `boolean` | ✅ | Enable producer |
-| **acks** | `Enum<'0' \| '1' \| 'all'>` | ✅ | Acknowledgment level |
-| **compressionType** | `Enum<'none' \| 'gzip' \| 'snappy' \| 'lz4' \| 'zstd'>` | ✅ | Compression type |
-| **batchSize** | `number` | ✅ | Batch size in bytes |
-| **lingerMs** | `number` | ✅ | Linger time in ms |
-| **maxInFlightRequests** | `number` | ✅ | Max in-flight requests |
-| **idempotence** | `boolean` | ✅ | Enable idempotent producer |
-| **transactional** | `boolean` | ✅ | Enable transactional producer |
-| **transactionTimeoutMs** | `number` | optional | Transaction timeout in ms |
-
-
----
-
-## TopicQueue
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **name** | `string` | ✅ | Topic/queue identifier in ObjectStack (snake_case) |
-| **label** | `string` | ✅ | Display label |
-| **topicName** | `string` | ✅ | Actual topic/queue name in message queue system |
-| **enabled** | `boolean` | ✅ | Enable sync for this topic/queue |
-| **mode** | `Enum<'consumer' \| 'producer' \| 'both'>` | ✅ | Consumer, producer, or both |
-| **messageFormat** | `Enum<'json' \| 'xml' \| 'protobuf' \| 'avro' \| 'text' \| 'binary'>` | ✅ | Message format/serialization |
-| **partitions** | `number` | optional | Number of partitions (for Kafka) |
-| **replicationFactor** | `number` | optional | Replication factor (for Kafka) |
-| **consumerConfig** | `{ enabled: boolean; consumerGroup?: string; concurrency: number; prefetchCount: number; … }` | optional | Consumer-specific configuration |
-| **producerConfig** | `{ enabled: boolean; acks: Enum<'0' \| '1' \| 'all'>; compressionType: Enum<'none' \| 'gzip' \| 'snappy' \| 'lz4' \| 'zstd'>; batchSize: number; … }` | optional | Producer-specific configuration |
-| **dlqConfig** | `{ enabled: boolean; queueName: string; maxRetries: number; retryDelayMs: number }` | optional | Dead letter queue configuration |
-| **routingKey** | `string` | optional | Routing key pattern |
-| **messageFilter** | `{ headers?: Record; attributes?: Record }` | optional | Message filter criteria |
-
-
----
-
diff --git a/content/docs/references/integration/connector-saas.mdx b/content/docs/references/integration/connector-saas.mdx
deleted file mode 100644
index 2cf29adfae..0000000000
--- a/content/docs/references/integration/connector-saas.mdx
+++ /dev/null
@@ -1,125 +0,0 @@
----
-title: Connector Saas
-description: Connector Saas protocol schemas
----
-
-{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
-
-SaaS Connector Protocol Template
-
-Specialized connector for SaaS applications (Salesforce, HubSpot, Stripe, etc.)
-
-Extends the base connector with SaaS-specific features like OAuth flows,
-
-object type discovery, and API version management.
-
-
-**Source:** `packages/spec/src/integration/connector/saas.zod.ts`
-
-
-## TypeScript Usage
-
-```typescript
-import { ApiVersionConfig, SaasConnector, SaasObjectType, SaasProvider } from '@objectstack/spec/integration';
-import type { ApiVersionConfig, SaasConnector, SaasObjectType, SaasProvider } from '@objectstack/spec/integration';
-
-// Validate data
-const result = ApiVersionConfig.parse(data);
-```
-
----
-
-## ApiVersionConfig
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **version** | `string` | ✅ | API version (e.g., "v2", "2023-10-01") |
-| **isDefault** | `boolean` | ✅ | Is this the default version |
-| **deprecationDate** | `string` | optional | API version deprecation date (ISO 8601) |
-| **sunsetDate** | `string` | optional | API version sunset date (ISO 8601) |
-
-
----
-
-## SaasConnector
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **name** | `string` | ✅ | Unique connector identifier |
-| **label** | `string` | ✅ | Display label |
-| **type** | `'saas'` | ✅ | |
-| **description** | `string` | optional | Connector description |
-| **icon** | `string` | optional | Icon identifier |
-| **authentication** | `{ type: 'oauth2'; authorizationUrl: string; tokenUrl: string; clientId: string; … } \| { type: 'api-key'; key: string; headerName?: string; paramName?: string } \| { type: 'basic'; username: string; password: string } \| { type: 'bearer'; token: string } \| { type: 'none' }` | optional | Authentication configuration (runtime shape with inline secrets). Provider-bound declarative instances use `auth.credentialRef` instead. |
-| **provider** | `Enum<'salesforce' \| 'hubspot' \| 'stripe' \| 'shopify' \| 'zendesk' \| 'intercom' \| 'mailchimp' \| 'slack' \| 'microsoft_dynamics' \| 'servicenow' \| 'netsuite' \| 'custom'>` | ✅ | SaaS provider type |
-| **providerConfig** | `Record` | optional | Provider-specific config validated by the provider factory at boot (e.g. `{ spec, baseUrl }` for openapi, where spec is an inline document, a package-relative file path like './billing-openapi.json', or an http(s) URL). Requires `provider`. |
-| **auth** | `{ type: 'none' } \| { type: 'bearer'; credentialRef: string } \| { type: 'api-key'; credentialRef: string; headerName?: string; paramName?: string } \| { type: 'basic'; username: string; credentialRef: string }` | optional | Declarative instance auth — references credentials via `credentialRef` (resolved at boot), never inline secrets. Requires `provider` (ADR-0097). |
-| **actions** | `{ key: string; label: string; description?: string; inputSchema?: Record; … }[]` | optional | |
-| **triggers** | `{ key: string; label: string; description?: string; type: Enum<'polling' \| 'webhook'>; … }[]` | optional | Trigger definitions (not yet enforced — never read at registration; see #3197) |
-| **syncConfig** | `{ strategy?: Enum<'full' \| 'incremental' \| 'upsert' \| 'append_only'>; direction?: Enum<'import' \| 'export' \| 'bidirectional'>; schedule?: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; realtimeSync?: boolean; … }` | optional | Data sync configuration |
-| **fieldMappings** | `{ source: string; target: string; transform?: { type: 'constant'; value: any } \| { type: 'cast'; targetType: Enum<'string' \| 'number' \| 'boolean' \| 'date'> } \| { type: 'lookup'; table: string; keyField: string; valueField: string } \| { type: 'javascript'; expression: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object } } \| { type: 'map'; mappings: Record }; defaultValue?: any; … }[]` | optional | Field mapping rules |
-| **webhooks** | `{ name: string; label?: string; object?: string; triggers?: Enum<'create' \| 'update' \| 'delete'>[]; … }[]` | optional | Webhook configurations (not yet enforced — never read at registration; see #3197) |
-| **rateLimitConfig** | `{ strategy?: Enum<'fixed_window' \| 'sliding_window' \| 'token_bucket' \| 'leaky_bucket'>; maxRequests: number; windowSeconds: number; burstCapacity?: number; … }` | optional | Rate limiting configuration |
-| **retryConfig** | `{ strategy?: Enum<'exponential_backoff' \| 'linear_backoff' \| 'fixed_delay' \| 'no_retry'>; maxAttempts?: number; initialDelayMs?: number; maxDelayMs?: number; … }` | optional | Retry configuration |
-| **connectionTimeoutMs** | `number` | optional | Connection timeout in ms |
-| **requestTimeoutMs** | `number` | optional | Request timeout in ms |
-| **status** | `Enum<'active' \| 'inactive' \| 'error' \| 'configuring'>` | optional | Connector status |
-| **enabled** | `boolean` | optional | Enable connector. On declarative stack entries, false marks a deliberate catalog-only descriptor (#2612). |
-| **errorMapping** | `{ rules: { sourceCode: string \| number; sourceMessage?: string; targetCode: string; targetCategory: Enum<'validation' \| 'authorization' \| 'not_found' \| 'conflict' \| 'rate_limit' \| 'timeout' \| 'server_error' \| 'integration_error'>; … }[]; defaultCategory?: Enum<'validation' \| 'authorization' \| 'not_found' \| 'conflict' \| 'rate_limit' \| 'timeout' \| 'server_error' \| 'integration_error'>; unmappedBehavior: Enum<'passthrough' \| 'generic_error' \| 'throw'>; logUnmapped?: boolean }` | optional | Error mapping configuration |
-| **health** | `{ healthCheck?: object; circuitBreaker?: object }` | optional | Health and resilience configuration |
-| **metadata** | `Record` | optional | Custom connector metadata |
-| **baseUrl** | `string` | ✅ | API base URL |
-| **apiVersion** | `{ version: string; isDefault?: boolean; deprecationDate?: string; sunsetDate?: string }` | optional | API version configuration |
-| **objectTypes** | `{ name: string; label: string; apiName: string; enabled?: boolean; … }[]` | ✅ | Syncable object types |
-| **oauthSettings** | `{ scopes: string[]; refreshTokenUrl?: string; revokeTokenUrl?: string; autoRefresh?: boolean }` | optional | OAuth-specific configuration |
-| **paginationConfig** | `{ type?: Enum<'cursor' \| 'offset' \| 'page'>; defaultPageSize?: number; maxPageSize?: number }` | optional | Pagination configuration |
-| **sandboxConfig** | `{ enabled?: boolean; baseUrl?: string }` | optional | Sandbox environment configuration |
-| **customHeaders** | `Record` | optional | Custom HTTP headers for all requests |
-
-
----
-
-## SaasObjectType
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **name** | `string` | ✅ | Object type name (snake_case) |
-| **label** | `string` | ✅ | Display label |
-| **apiName** | `string` | ✅ | API name in external system |
-| **enabled** | `boolean` | optional | Enable sync for this object |
-| **supportsCreate** | `boolean` | optional | Supports record creation |
-| **supportsUpdate** | `boolean` | optional | Supports record updates |
-| **supportsDelete** | `boolean` | optional | Supports record deletion |
-| **fieldMappings** | `{ source: string; target: string; transform?: { type: 'constant'; value: any } \| { type: 'cast'; targetType: Enum<'string' \| 'number' \| 'boolean' \| 'date'> } \| { type: 'lookup'; table: string; keyField: string; valueField: string } \| { type: 'javascript'; expression: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object } } \| { type: 'map'; mappings: Record }; defaultValue?: any; … }[]` | optional | Object-specific field mappings |
-
-
----
-
-## SaasProvider
-
-SaaS provider type
-
-### Allowed Values
-
-* `salesforce`
-* `hubspot`
-* `stripe`
-* `shopify`
-* `zendesk`
-* `intercom`
-* `mailchimp`
-* `slack`
-* `microsoft_dynamics`
-* `servicenow`
-* `netsuite`
-* `custom`
-
-
----
-
diff --git a/content/docs/references/integration/connector-vercel.mdx b/content/docs/references/integration/connector-vercel.mdx
deleted file mode 100644
index 43e1b0fd4e..0000000000
--- a/content/docs/references/integration/connector-vercel.mdx
+++ /dev/null
@@ -1,300 +0,0 @@
----
-title: Connector Vercel
-description: Connector Vercel protocol schemas
----
-
-{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
-
-Vercel Connector Protocol
-
-Specialized connector for Vercel deployment platform enabling automated
-
-deployments, preview environments, and production releases.
-
-Use Cases:
-
-- Automated deployments from Git
-
-- Preview deployments for pull requests
-
-- Production releases
-
-- Environment variable management
-
-- Domain and SSL configuration
-
-- Edge function deployment
-
-@example
-
-```typescript
-
-import \{ VercelConnector \} from '@objectstack/spec/integration';
-
-const vercelConnector: VercelConnector = \{
-
-name: 'vercel_production',
-
-label: 'Vercel Production',
-
-type: 'saas',
-
-provider: 'vercel',
-
-baseUrl: 'https://api.vercel.com',
-
-authentication: \{
-
-type: 'bearer',
-
-token: '$\{VERCEL_TOKEN\}',
-
-\},
-
-projects: [
-
-\{
-
-name: 'objectstack-app',
-
-framework: 'nextjs',
-
-gitRepository: \{
-
-type: 'github',
-
-repo: 'objectstack-ai/app',
-
-\},
-
-\},
-
-],
-
-\};
-
-```
-
-
-**Source:** `packages/spec/src/integration/connector/vercel.zod.ts`
-
-
-## TypeScript Usage
-
-```typescript
-import { BuildConfig, DeploymentConfig, DomainConfig, EdgeFunctionConfig, EnvironmentVariables, GitRepositoryConfig, VercelConnector, VercelFramework, VercelMonitoring, VercelProject, VercelProvider, VercelTeam } from '@objectstack/spec/integration';
-import type { BuildConfig, DeploymentConfig, DomainConfig, EdgeFunctionConfig, EnvironmentVariables, GitRepositoryConfig, VercelConnector, VercelFramework, VercelMonitoring, VercelProject, VercelProvider, VercelTeam } from '@objectstack/spec/integration';
-
-// Validate data
-const result = BuildConfig.parse(data);
-```
-
----
-
-## BuildConfig
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **buildCommand** | `string` | optional | Build command (e.g., npm run build) |
-| **outputDirectory** | `string` | optional | Output directory (e.g., .next, dist) |
-| **installCommand** | `string` | optional | Install command (e.g., npm install, pnpm install) |
-| **devCommand** | `string` | optional | Development command (e.g., npm run dev) |
-| **nodeVersion** | `string` | optional | Node.js version (e.g., 18.x, 20.x) |
-| **env** | `Record` | optional | Build environment variables |
-
-
----
-
-## DeploymentConfig
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **autoDeployment** | `boolean` | ✅ | Enable automatic deployments |
-| **regions** | `Enum<'iad1' \| 'sfo1' \| 'gru1' \| 'lhr1' \| 'fra1' \| 'sin1' \| 'syd1' \| 'hnd1' \| 'icn1'>[]` | optional | Deployment regions |
-| **enablePreview** | `boolean` | ✅ | Enable preview deployments |
-| **previewComments** | `boolean` | ✅ | Post preview URLs in PR comments |
-| **productionProtection** | `boolean` | ✅ | Require approval for production deployments |
-| **deployHooks** | `{ name: string; url: string; branch?: string }[]` | optional | Deploy hooks |
-
-
----
-
-## DomainConfig
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **domain** | `string` | ✅ | Domain name (e.g., app.example.com) |
-| **httpsRedirect** | `boolean` | ✅ | Redirect HTTP to HTTPS |
-| **customCertificate** | `{ cert: string; key: string; ca?: string }` | optional | Custom SSL certificate |
-| **gitBranch** | `string` | optional | Git branch to deploy to this domain |
-
-
----
-
-## EdgeFunctionConfig
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **name** | `string` | ✅ | Edge function name |
-| **path** | `string` | ✅ | Function path (e.g., /api/*) |
-| **regions** | `string[]` | optional | Specific regions for this function |
-| **memoryLimit** | `integer` | ✅ | Memory limit in MB |
-| **timeout** | `integer` | ✅ | Timeout in seconds |
-
-
----
-
-## EnvironmentVariables
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **key** | `string` | ✅ | Environment variable name |
-| **value** | `string` | ✅ | Environment variable value |
-| **target** | `Enum<'production' \| 'preview' \| 'development'>[]` | ✅ | Target environments |
-| **isSecret** | `boolean` | ✅ | Encrypt this variable |
-| **gitBranch** | `string` | optional | Specific git branch |
-
-
----
-
-## GitRepositoryConfig
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **type** | `Enum<'github' \| 'gitlab' \| 'bitbucket'>` | ✅ | Git provider |
-| **repo** | `string` | ✅ | Repository identifier (e.g., owner/repo) |
-| **productionBranch** | `string` | ✅ | Production branch name |
-| **autoDeployProduction** | `boolean` | ✅ | Auto-deploy production branch |
-| **autoDeployPreview** | `boolean` | ✅ | Auto-deploy preview branches |
-
-
----
-
-## VercelConnector
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **name** | `string` | ✅ | Unique connector identifier |
-| **label** | `string` | ✅ | Display label |
-| **type** | `'saas'` | ✅ | |
-| **description** | `string` | optional | Connector description |
-| **icon** | `string` | optional | Icon identifier |
-| **authentication** | `{ type: 'oauth2'; authorizationUrl: string; tokenUrl: string; clientId: string; … } \| { type: 'api-key'; key: string; headerName?: string; paramName?: string } \| { type: 'basic'; username: string; password: string } \| { type: 'bearer'; token: string } \| { type: 'none' }` | optional | Authentication configuration (runtime shape with inline secrets). Provider-bound declarative instances use `auth.credentialRef` instead. |
-| **provider** | `Enum<'vercel'>` | ✅ | Vercel provider |
-| **providerConfig** | `Record` | optional | Provider-specific config validated by the provider factory at boot (e.g. `{ spec, baseUrl }` for openapi, where spec is an inline document, a package-relative file path like './billing-openapi.json', or an http(s) URL). Requires `provider`. |
-| **auth** | `{ type: 'none' } \| { type: 'bearer'; credentialRef: string } \| { type: 'api-key'; credentialRef: string; headerName?: string; paramName?: string } \| { type: 'basic'; username: string; credentialRef: string }` | optional | Declarative instance auth — references credentials via `credentialRef` (resolved at boot), never inline secrets. Requires `provider` (ADR-0097). |
-| **actions** | `{ key: string; label: string; description?: string; inputSchema?: Record; … }[]` | optional | |
-| **triggers** | `{ key: string; label: string; description?: string; type: Enum<'polling' \| 'webhook'>; … }[]` | optional | Trigger definitions (not yet enforced — never read at registration; see #3197) |
-| **syncConfig** | `{ strategy?: Enum<'full' \| 'incremental' \| 'upsert' \| 'append_only'>; direction?: Enum<'import' \| 'export' \| 'bidirectional'>; schedule?: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; realtimeSync?: boolean; … }` | optional | Data sync configuration |
-| **fieldMappings** | `{ source: string; target: string; transform?: { type: 'constant'; value: any } \| { type: 'cast'; targetType: Enum<'string' \| 'number' \| 'boolean' \| 'date'> } \| { type: 'lookup'; table: string; keyField: string; valueField: string } \| { type: 'javascript'; expression: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object } } \| { type: 'map'; mappings: Record }; defaultValue?: any; … }[]` | optional | Field mapping rules |
-| **webhooks** | `{ name: string; label?: string; object?: string; triggers?: Enum<'create' \| 'update' \| 'delete'>[]; … }[]` | optional | Webhook configurations (not yet enforced — never read at registration; see #3197) |
-| **rateLimitConfig** | `{ strategy?: Enum<'fixed_window' \| 'sliding_window' \| 'token_bucket' \| 'leaky_bucket'>; maxRequests: number; windowSeconds: number; burstCapacity?: number; … }` | optional | Rate limiting configuration |
-| **retryConfig** | `{ strategy?: Enum<'exponential_backoff' \| 'linear_backoff' \| 'fixed_delay' \| 'no_retry'>; maxAttempts?: number; initialDelayMs?: number; maxDelayMs?: number; … }` | optional | Retry configuration |
-| **connectionTimeoutMs** | `number` | optional | Connection timeout in ms |
-| **requestTimeoutMs** | `number` | optional | Request timeout in ms |
-| **status** | `Enum<'active' \| 'inactive' \| 'error' \| 'configuring'>` | optional | Connector status |
-| **enabled** | `boolean` | optional | Enable connector. On declarative stack entries, false marks a deliberate catalog-only descriptor (#2612). |
-| **errorMapping** | `{ rules: { sourceCode: string \| number; sourceMessage?: string; targetCode: string; targetCategory: Enum<'validation' \| 'authorization' \| 'not_found' \| 'conflict' \| 'rate_limit' \| 'timeout' \| 'server_error' \| 'integration_error'>; … }[]; defaultCategory?: Enum<'validation' \| 'authorization' \| 'not_found' \| 'conflict' \| 'rate_limit' \| 'timeout' \| 'server_error' \| 'integration_error'>; unmappedBehavior: Enum<'passthrough' \| 'generic_error' \| 'throw'>; logUnmapped?: boolean }` | optional | Error mapping configuration |
-| **health** | `{ healthCheck?: object; circuitBreaker?: object }` | optional | Health and resilience configuration |
-| **metadata** | `Record` | optional | Custom connector metadata |
-| **baseUrl** | `string` | optional | Vercel API base URL |
-| **team** | `{ teamId?: string; teamName?: string }` | optional | Vercel team configuration |
-| **projects** | `{ name: string; framework?: Enum<'nextjs' \| 'react' \| 'vue' \| 'nuxtjs' \| 'gatsby' \| 'remix' \| 'astro' \| 'sveltekit' \| 'solid' \| 'angular' \| 'static' \| 'other'>; gitRepository?: object; buildConfig?: object; … }[]` | ✅ | Vercel projects |
-| **monitoring** | `{ enableWebAnalytics?: boolean; enableSpeedInsights?: boolean; logDrains?: { name: string; url: string; headers?: Record; sources?: Enum<'static' \| 'lambda' \| 'edge'>[] }[] }` | optional | Monitoring configuration |
-| **enableWebhooks** | `boolean` | optional | Enable Vercel webhooks |
-| **webhookEvents** | `Enum<'deployment.created' \| 'deployment.succeeded' \| 'deployment.failed' \| 'deployment.ready' \| 'deployment.error' \| 'deployment.canceled' \| 'deployment-checks-completed' \| 'deployment-prepared' \| 'project.created' \| 'project.removed'>[]` | optional | Webhook events to subscribe to |
-
-
----
-
-## VercelFramework
-
-Frontend framework
-
-### Allowed Values
-
-* `nextjs`
-* `react`
-* `vue`
-* `nuxtjs`
-* `gatsby`
-* `remix`
-* `astro`
-* `sveltekit`
-* `solid`
-* `angular`
-* `static`
-* `other`
-
-
----
-
-## VercelMonitoring
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **enableWebAnalytics** | `boolean` | ✅ | Enable Vercel Web Analytics |
-| **enableSpeedInsights** | `boolean` | ✅ | Enable Vercel Speed Insights |
-| **logDrains** | `{ name: string; url: string; headers?: Record; sources?: Enum<'static' \| 'lambda' \| 'edge'>[] }[]` | optional | Log drains configuration |
-
-
----
-
-## VercelProject
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **name** | `string` | ✅ | Vercel project name |
-| **framework** | `Enum<'nextjs' \| 'react' \| 'vue' \| 'nuxtjs' \| 'gatsby' \| 'remix' \| 'astro' \| 'sveltekit' \| 'solid' \| 'angular' \| 'static' \| 'other'>` | optional | Frontend framework |
-| **gitRepository** | `{ type: Enum<'github' \| 'gitlab' \| 'bitbucket'>; repo: string; productionBranch: string; autoDeployProduction: boolean; … }` | optional | Git repository configuration |
-| **buildConfig** | `{ buildCommand?: string; outputDirectory?: string; installCommand?: string; devCommand?: string; … }` | optional | Build configuration |
-| **deploymentConfig** | `{ autoDeployment: boolean; regions?: Enum<'iad1' \| 'sfo1' \| 'gru1' \| 'lhr1' \| 'fra1' \| 'sin1' \| 'syd1' \| 'hnd1' \| 'icn1'>[]; enablePreview: boolean; previewComments: boolean; … }` | optional | Deployment configuration |
-| **domains** | `{ domain: string; httpsRedirect: boolean; customCertificate?: object; gitBranch?: string }[]` | optional | Custom domains |
-| **environmentVariables** | `{ key: string; value: string; target: Enum<'production' \| 'preview' \| 'development'>[]; isSecret: boolean; … }[]` | optional | Environment variables |
-| **edgeFunctions** | `{ name: string; path: string; regions?: string[]; memoryLimit: integer; … }[]` | optional | Edge functions |
-| **rootDirectory** | `string` | optional | Root directory (for monorepos) |
-
-
----
-
-## VercelProvider
-
-Vercel provider type
-
-### Allowed Values
-
-* `vercel`
-
-
----
-
-## VercelTeam
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **teamId** | `string` | optional | Team ID or slug |
-| **teamName** | `string` | optional | Team name |
-
-
----
-
diff --git a/content/docs/references/integration/index.mdx b/content/docs/references/integration/index.mdx
index 2ac0bbf02b..f4cbea5ec3 100644
--- a/content/docs/references/integration/index.mdx
+++ b/content/docs/references/integration/index.mdx
@@ -7,10 +7,4 @@ This section contains all protocol schemas for the integration layer of ObjectSt
-
-
-
-
-
-
diff --git a/content/docs/references/integration/message-queue.mdx b/content/docs/references/integration/message-queue.mdx
deleted file mode 100644
index 8f8aae5f92..0000000000
--- a/content/docs/references/integration/message-queue.mdx
+++ /dev/null
@@ -1,61 +0,0 @@
----
-title: Message Queue
-description: Message Queue protocol schemas
----
-
-{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
-
-## TypeScript Usage
-
-```typescript
-import { ConsumerConfig, MessageQueueProvider } from '@objectstack/spec/integration';
-import type { ConsumerConfig, MessageQueueProvider } from '@objectstack/spec/integration';
-
-// Validate data
-const result = ConsumerConfig.parse(data);
-```
-
----
-
-## ConsumerConfig
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **enabled** | `boolean` | ✅ | Enable consumer |
-| **consumerGroup** | `string` | optional | Consumer group ID |
-| **concurrency** | `number` | ✅ | Number of concurrent consumers |
-| **prefetchCount** | `number` | ✅ | Prefetch count |
-| **ackMode** | `Enum<'auto' \| 'manual' \| 'client'>` | ✅ | Message acknowledgment mode |
-| **autoCommit** | `boolean` | ✅ | Auto-commit offsets |
-| **autoCommitIntervalMs** | `number` | ✅ | Auto-commit interval in ms |
-| **sessionTimeoutMs** | `number` | ✅ | Session timeout in ms |
-| **rebalanceTimeoutMs** | `number` | optional | Rebalance timeout in ms |
-
-
----
-
-## MessageQueueProvider
-
-Message queue provider type
-
-### Allowed Values
-
-* `rabbitmq`
-* `kafka`
-* `redis_pubsub`
-* `redis_streams`
-* `aws_sqs`
-* `aws_sns`
-* `google_pubsub`
-* `azure_service_bus`
-* `azure_event_hubs`
-* `nats`
-* `pulsar`
-* `activemq`
-* `custom`
-
-
----
-
diff --git a/content/docs/references/integration/meta.json b/content/docs/references/integration/meta.json
index ab617bca90..b125c0f212 100644
--- a/content/docs/references/integration/meta.json
+++ b/content/docs/references/integration/meta.json
@@ -7,17 +7,6 @@
"mapping",
"---Transport & Storage---",
"http",
- "message-queue",
- "object-storage",
- "offline",
- "---Tenancy---",
- "tenant",
- "---More---",
- "connector-database",
- "connector-file-storage",
- "connector-github",
- "connector-message-queue",
- "connector-saas",
- "connector-vercel"
+ "offline"
]
}
\ No newline at end of file
diff --git a/content/docs/references/integration/object-storage.mdx b/content/docs/references/integration/object-storage.mdx
deleted file mode 100644
index 8c5c882c5e..0000000000
--- a/content/docs/references/integration/object-storage.mdx
+++ /dev/null
@@ -1,33 +0,0 @@
----
-title: Object Storage
-description: Object Storage protocol schemas
----
-
-{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
-
-## TypeScript Usage
-
-```typescript
-import { MultipartUploadConfig } from '@objectstack/spec/integration';
-import type { MultipartUploadConfig } from '@objectstack/spec/integration';
-
-// Validate data
-const result = MultipartUploadConfig.parse(data);
-```
-
----
-
-## MultipartUploadConfig
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **enabled** | `boolean` | ✅ | Enable multipart uploads |
-| **partSize** | `number` | ✅ | Part size in bytes (min 5MB) |
-| **maxConcurrentParts** | `number` | ✅ | Maximum concurrent part uploads |
-| **threshold** | `number` | ✅ | File size threshold for multipart upload in bytes |
-
-
----
-
diff --git a/content/docs/references/integration/tenant.mdx b/content/docs/references/integration/tenant.mdx
deleted file mode 100644
index e0624b519d..0000000000
--- a/content/docs/references/integration/tenant.mdx
+++ /dev/null
@@ -1,41 +0,0 @@
----
-title: Tenant
-description: Tenant protocol schemas
----
-
-{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
-
-## TypeScript Usage
-
-```typescript
-import { DatabaseProvider } from '@objectstack/spec/integration';
-import type { DatabaseProvider } from '@objectstack/spec/integration';
-
-// Validate data
-const result = DatabaseProvider.parse(data);
-```
-
----
-
-## DatabaseProvider
-
-Database provider type
-
-### Allowed Values
-
-* `postgresql`
-* `mysql`
-* `mariadb`
-* `mssql`
-* `oracle`
-* `mongodb`
-* `redis`
-* `cassandra`
-* `snowflake`
-* `bigquery`
-* `redshift`
-* `custom`
-
-
----
-
diff --git a/content/docs/releases/v17.mdx b/content/docs/releases/v17.mdx
index 2eb5ff6827..6b4a59e7a2 100644
--- a/content/docs/releases/v17.mdx
+++ b/content/docs/releases/v17.mdx
@@ -1024,6 +1024,7 @@ import or the authored key.
| `ChartInteraction.zoom` / `.clickAction` | never implemented (#3752) |
| The `workflow` service slot — `CoreServiceName 'workflow'`, `IWorkflowService`, `WorkflowProtocol`, the `Get/WorkflowState/Config/Transition` schema cluster, discovery `routes.workflow` / `services.workflow` / `features.workflow`, the `RestApiRouteCategory 'workflow'` member and the stray `graphql` provider entry | declared end to end and implemented nowhere: nothing ever registered or resolved the slot (ADR-0115 Evidence 5), no method of `WorkflowProtocol` was ever implemented, no host ever mounted `/api/v1/workflow`. State machines are `state_machine` validation rules; approvals are flow nodes (ADR-0019); record-triggered automation is hooks + `record_change` flows (#4451) |
| `datasource.readReplicas` | replica connections nothing ever opened — no driver reads the key and no query path splits reads from writes, so every statement went to the primary. #4410 had just taught the schema to validate each entry against the declared driver's contract, which made a dead slot look rigorously alive (#4468) |
+| The per-provider connector "template" cluster (`@objectstack/spec/integration` — `DatabaseConnectorSchema`, `FileStorageConnectorSchema`, `GitHubConnectorSchema`, `MessageQueueConnectorSchema`, `SaasConnectorSchema`, `VercelConnectorSchema`, their ~100 sub-schema/type/example exports, and the six generated reference pages) | the losing side of a decided architecture fight, left standing: ADR-0023 rejected hand-modelling each external system's shape inside the spec, and the live ADR-0097 protocol gets provider shapes from the provider itself (connector-openapi / connector-mcp materialize at boot). Zero consumers — `engine.registerConnector()` validates against `ConnectorSchema` from `connector.zod.ts` alone, and nothing referenced the six files, not even their own module's live half. `DatabaseConnectorSchema` also declared read-replica routing a *second* time (`readReplicaConfig`, see the row above), down to a `weight` field for a load balancer that does not exist (#4480) |
| The `kernel` metadata-loader envelope family — `MetadataFormat`, `MetadataStats`, `MetadataLoadOptions`, `MetadataSaveOptions`, `MetadataExportOptions`, `MetadataImportOptions`, `MetadataLoadResult`, `MetadataSaveResult`, `MetadataWatchEvent`, `MetadataCollectionInfo`, `MetadataLoaderContract` (`@objectstack/spec/kernel`) | eleven names that each existed **twice**, with a different shape, on `./kernel` and `./system` — so which type you got depended on your import path. Every consumer imported the `./system` copy; the `./kernel` copies had zero consumers. Import them from `@objectstack/spec/system` (#4411, ADR-0049). `MetadataManagerConfig` / `MetadataFallbackStrategy` are unaffected and still ship from both entries |
The Console side follows: `@object-ui/types` drops its
diff --git a/packages/spec/PROTOCOL_MAP.md b/packages/spec/PROTOCOL_MAP.md
index 6ebe74467c..682dff19db 100644
--- a/packages/spec/PROTOCOL_MAP.md
+++ b/packages/spec/PROTOCOL_MAP.md
@@ -117,13 +117,7 @@ This document serves as the **Grand Map** of the ObjectStack specification. It l
| File | Status | Description |
| :--- | :--- | :--- |
-| [`connector.zod.ts`](src/integration/connector.zod.ts) | ⭐ | **Connector Definition**. Metadata for external API integrations (OpenAPI wrapper). |
-| [`connector/saas.zod.ts`](src/integration/connector/saas.zod.ts) | | **SaaS Connectors**. Specifics for SaaS APIs (Salesforce, Stripe). |
-| [`connector/database.zod.ts`](src/integration/connector/database.zod.ts) | | **DB Connectors**. External database integration. |
-| [`connector/file-storage.zod.ts`](src/integration/connector/file-storage.zod.ts) | | **Storage Connectors**. S3, Blob Storage integrations. |
-| [`connector/message-queue.zod.ts`](src/integration/connector/message-queue.zod.ts) | | **MQ Connectors**. Kafka, RabbitMQ integrations. |
-| [`connector/github.zod.ts`](src/integration/connector/github.zod.ts) | | **GitHub Connector**. Logic for Git integration. |
-| [`connector/vercel.zod.ts`](src/integration/connector/vercel.zod.ts) | | **Vercel Connector**. Deployment integration. |
+| [`connector.zod.ts`](src/integration/connector.zod.ts) | ⭐ | **Connector Protocol** (ADR-0097). One schema; provider shapes come from the provider itself (connector-openapi / connector-mcp), not from per-provider spec files — the six `connector/*.zod.ts` "templates" were removed in #4480 (zero consumers; ADR-0023 rejected that design). |
---
diff --git a/packages/spec/api-surface.json b/packages/spec/api-surface.json
index 48b3c2768e..4e78b190ca 100644
--- a/packages/spec/api-surface.json
+++ b/packages/spec/api-surface.json
@@ -3912,15 +3912,7 @@
"WriteObservabilityOptions (interface)"
],
"./integration": [
- "AckMode (type)",
- "AckModeSchema (const)",
- "ApiVersionConfig (type)",
- "ApiVersionConfigSchema (const)",
- "BuildConfig (type)",
- "BuildConfigSchema (const)",
"CONNECTOR_UPSTREAM_UNAVAILABLE (const)",
- "CdcConfig (type)",
- "CdcConfigSchema (const)",
"CircuitBreakerConfig (type)",
"CircuitBreakerConfigSchema (const)",
"ConflictResolution (type)",
@@ -3955,80 +3947,18 @@
"ConnectorType (type)",
"ConnectorTypeSchema (const)",
"ConnectorUpstreamUnavailableError (class)",
- "ConsumerConfig (type)",
- "ConsumerConfigSchema (const)",
"DataSyncConfig (type)",
"DataSyncConfigSchema (const)",
- "DatabaseConnector (type)",
- "DatabaseConnectorSchema (const)",
- "DatabasePoolConfig (type)",
- "DatabasePoolConfigSchema (const)",
- "DatabaseProvider (type)",
- "DatabaseProviderSchema (const)",
- "DatabaseTable (type)",
- "DatabaseTableSchema (const)",
"DeclarativeConnectorEntry (type)",
"DeclarativeConnectorEntrySchema (const)",
- "DeliveryGuarantee (type)",
- "DeliveryGuaranteeSchema (const)",
- "DeploymentConfig (type)",
- "DeploymentConfigSchema (const)",
- "DlqConfig (type)",
- "DlqConfigSchema (const)",
- "DomainConfig (type)",
- "DomainConfigSchema (const)",
- "EdgeFunctionConfig (type)",
- "EdgeFunctionConfigSchema (const)",
- "EnvironmentVariables (type)",
- "EnvironmentVariablesSchema (const)",
"ErrorMappingConfig (type)",
"ErrorMappingConfigSchema (const)",
"ErrorMappingRule (type)",
"ErrorMappingRuleSchema (const)",
"FieldMapping (type)",
"FieldMappingSchema (const)",
- "FileAccessPattern (type)",
- "FileAccessPatternSchema (const)",
- "FileFilterConfig (type)",
- "FileFilterConfigSchema (const)",
- "FileMetadataConfig (type)",
- "FileMetadataConfigSchema (const)",
- "FileStorageConnector (type)",
- "FileStorageConnectorSchema (const)",
- "FileStorageProvider (type)",
- "FileStorageProviderSchema (const)",
- "FileVersioningConfig (type)",
- "FileVersioningConfigSchema (const)",
- "GitHubActionsWorkflow (type)",
- "GitHubActionsWorkflowSchema (const)",
- "GitHubCommitConfig (type)",
- "GitHubCommitConfigSchema (const)",
- "GitHubConnector (type)",
- "GitHubConnectorSchema (const)",
- "GitHubIssueTracking (type)",
- "GitHubIssueTrackingSchema (const)",
- "GitHubProvider (type)",
- "GitHubProviderSchema (const)",
- "GitHubPullRequestConfig (type)",
- "GitHubPullRequestConfigSchema (const)",
- "GitHubReleaseConfig (type)",
- "GitHubReleaseConfigSchema (const)",
- "GitHubRepository (type)",
- "GitHubRepositorySchema (const)",
- "GitRepositoryConfig (type)",
- "GitRepositoryConfigSchema (const)",
"HealthCheckConfig (type)",
"HealthCheckConfigSchema (const)",
- "MessageFormat (type)",
- "MessageFormatSchema (const)",
- "MessageQueueConnector (type)",
- "MessageQueueConnectorSchema (const)",
- "MessageQueueProvider (type)",
- "MessageQueueProviderSchema (const)",
- "MultipartUploadConfig (type)",
- "MultipartUploadConfigSchema (const)",
- "ProducerConfig (type)",
- "ProducerConfigSchema (const)",
"RateLimitConfig (type)",
"RateLimitConfigSchema (const)",
"RateLimitStrategy (type)",
@@ -4036,57 +3966,16 @@
"ResolvedConnectorAuth (type)",
"RetryConfig (type)",
"RetryConfigSchema (const)",
- "SaaSConnector (type)",
- "SaasConnector (type)",
- "SaasConnectorSchema (const)",
- "SaasObjectType (type)",
- "SaasObjectTypeSchema (const)",
- "SaasProvider (type)",
- "SaasProviderSchema (const)",
- "SslConfig (type)",
- "SslConfigSchema (const)",
- "StorageBucket (type)",
- "StorageBucketSchema (const)",
"SyncStrategy (type)",
"SyncStrategySchema (const)",
- "TopicQueue (type)",
- "TopicQueueSchema (const)",
- "VercelConnector (type)",
- "VercelConnectorSchema (const)",
- "VercelFramework (type)",
- "VercelFrameworkSchema (const)",
- "VercelMonitoring (type)",
- "VercelMonitoringSchema (const)",
- "VercelProject (type)",
- "VercelProjectSchema (const)",
- "VercelProvider (type)",
- "VercelProviderSchema (const)",
- "VercelTeam (type)",
- "VercelTeamSchema (const)",
"WebhookConfig (type)",
"WebhookConfigSchema (const)",
"WebhookEvent (type)",
"WebhookEventSchema (const)",
"WebhookSignatureAlgorithm (type)",
"WebhookSignatureAlgorithmSchema (const)",
- "azureBlobConnectorExample (const)",
"defineConnector (function)",
- "githubEnterpriseConnectorExample (const)",
- "githubPublicConnectorExample (const)",
- "googleDriveConnectorExample (const)",
- "hubspotConnectorExample (const)",
- "isConnectorUpstreamUnavailable (function)",
- "kafkaConnectorExample (const)",
- "mongoConnectorExample (const)",
- "postgresConnectorExample (const)",
- "pubsubConnectorExample (const)",
- "rabbitmqConnectorExample (const)",
- "s3ConnectorExample (const)",
- "salesforceConnectorExample (const)",
- "snowflakeConnectorExample (const)",
- "sqsConnectorExample (const)",
- "vercelNextJsConnectorExample (const)",
- "vercelStaticSiteConnectorExample (const)"
+ "isConnectorUpstreamUnavailable (function)"
],
"./security": [
"AccessMatrix (type)",
diff --git a/packages/spec/authorable-surface.json b/packages/spec/authorable-surface.json
index 5fc17011c1..c8f296470a 100644
--- a/packages/spec/authorable-surface.json
+++ b/packages/spec/authorable-surface.json
@@ -4132,23 +4132,6 @@
"identity/VerificationToken:expires",
"identity/VerificationToken:identifier",
"identity/VerificationToken:token",
- "integration/ApiVersionConfig:deprecationDate",
- "integration/ApiVersionConfig:isDefault",
- "integration/ApiVersionConfig:sunsetDate",
- "integration/ApiVersionConfig:version",
- "integration/BuildConfig:buildCommand",
- "integration/BuildConfig:devCommand",
- "integration/BuildConfig:env",
- "integration/BuildConfig:installCommand",
- "integration/BuildConfig:nodeVersion",
- "integration/BuildConfig:outputDirectory",
- "integration/CdcConfig:batchSize",
- "integration/CdcConfig:enabled",
- "integration/CdcConfig:method",
- "integration/CdcConfig:pollIntervalMs",
- "integration/CdcConfig:publicationName",
- "integration/CdcConfig:slotName",
- "integration/CdcConfig:startPosition",
"integration/CircuitBreakerConfig:enabled",
"integration/CircuitBreakerConfig:failureThreshold",
"integration/CircuitBreakerConfig:fallbackStrategy",
@@ -4200,15 +4183,6 @@
"integration/ConnectorTrigger:key",
"integration/ConnectorTrigger:label",
"integration/ConnectorTrigger:type",
- "integration/ConsumerConfig:ackMode",
- "integration/ConsumerConfig:autoCommit",
- "integration/ConsumerConfig:autoCommitIntervalMs",
- "integration/ConsumerConfig:concurrency",
- "integration/ConsumerConfig:consumerGroup",
- "integration/ConsumerConfig:enabled",
- "integration/ConsumerConfig:prefetchCount",
- "integration/ConsumerConfig:rebalanceTimeoutMs",
- "integration/ConsumerConfig:sessionTimeoutMs",
"integration/DataSyncConfig:batchSize",
"integration/DataSyncConfig:conflictResolution",
"integration/DataSyncConfig:deleteMode",
@@ -4218,52 +4192,6 @@
"integration/DataSyncConfig:schedule",
"integration/DataSyncConfig:strategy",
"integration/DataSyncConfig:timestampField",
- "integration/DatabaseConnector:actions",
- "integration/DatabaseConnector:auth",
- "integration/DatabaseConnector:authentication",
- "integration/DatabaseConnector:cdcConfig",
- "integration/DatabaseConnector:connectionConfig",
- "integration/DatabaseConnector:connectionTimeoutMs",
- "integration/DatabaseConnector:description",
- "integration/DatabaseConnector:enableQueryLogging",
- "integration/DatabaseConnector:enabled",
- "integration/DatabaseConnector:errorMapping",
- "integration/DatabaseConnector:fieldMappings",
- "integration/DatabaseConnector:health",
- "integration/DatabaseConnector:icon",
- "integration/DatabaseConnector:label",
- "integration/DatabaseConnector:metadata",
- "integration/DatabaseConnector:name",
- "integration/DatabaseConnector:poolConfig",
- "integration/DatabaseConnector:provider",
- "integration/DatabaseConnector:providerConfig",
- "integration/DatabaseConnector:queryTimeoutMs",
- "integration/DatabaseConnector:rateLimitConfig",
- "integration/DatabaseConnector:readReplicaConfig",
- "integration/DatabaseConnector:requestTimeoutMs",
- "integration/DatabaseConnector:retryConfig",
- "integration/DatabaseConnector:sslConfig",
- "integration/DatabaseConnector:status",
- "integration/DatabaseConnector:syncConfig",
- "integration/DatabaseConnector:tables",
- "integration/DatabaseConnector:triggers",
- "integration/DatabaseConnector:type",
- "integration/DatabaseConnector:webhooks",
- "integration/DatabasePoolConfig:acquireTimeoutMs",
- "integration/DatabasePoolConfig:connectionTimeoutMs",
- "integration/DatabasePoolConfig:evictionRunIntervalMs",
- "integration/DatabasePoolConfig:idleTimeoutMs",
- "integration/DatabasePoolConfig:max",
- "integration/DatabasePoolConfig:min",
- "integration/DatabasePoolConfig:testOnBorrow",
- "integration/DatabaseTable:enabled",
- "integration/DatabaseTable:fieldMappings",
- "integration/DatabaseTable:label",
- "integration/DatabaseTable:name",
- "integration/DatabaseTable:primaryKey",
- "integration/DatabaseTable:schema",
- "integration/DatabaseTable:tableName",
- "integration/DatabaseTable:whereClause",
"integration/DeclarativeConnectorEntry:actions",
"integration/DeclarativeConnectorEntry:auth",
"integration/DeclarativeConnectorEntry:authentication",
@@ -4287,30 +4215,6 @@
"integration/DeclarativeConnectorEntry:triggers",
"integration/DeclarativeConnectorEntry:type",
"integration/DeclarativeConnectorEntry:webhooks",
- "integration/DeploymentConfig:autoDeployment",
- "integration/DeploymentConfig:deployHooks",
- "integration/DeploymentConfig:enablePreview",
- "integration/DeploymentConfig:previewComments",
- "integration/DeploymentConfig:productionProtection",
- "integration/DeploymentConfig:regions",
- "integration/DlqConfig:enabled",
- "integration/DlqConfig:maxRetries",
- "integration/DlqConfig:queueName",
- "integration/DlqConfig:retryDelayMs",
- "integration/DomainConfig:customCertificate",
- "integration/DomainConfig:domain",
- "integration/DomainConfig:gitBranch",
- "integration/DomainConfig:httpsRedirect",
- "integration/EdgeFunctionConfig:memoryLimit",
- "integration/EdgeFunctionConfig:name",
- "integration/EdgeFunctionConfig:path",
- "integration/EdgeFunctionConfig:regions",
- "integration/EdgeFunctionConfig:timeout",
- "integration/EnvironmentVariables:gitBranch",
- "integration/EnvironmentVariables:isSecret",
- "integration/EnvironmentVariables:key",
- "integration/EnvironmentVariables:target",
- "integration/EnvironmentVariables:value",
"integration/ErrorMappingConfig:defaultCategory",
"integration/ErrorMappingConfig:logUnmapped",
"integration/ErrorMappingConfig:rules",
@@ -4329,123 +4233,6 @@
"integration/FieldMapping:syncMode",
"integration/FieldMapping:target",
"integration/FieldMapping:transform",
- "integration/FileFilterConfig:allowedExtensions",
- "integration/FileFilterConfig:blockedExtensions",
- "integration/FileFilterConfig:excludePatterns",
- "integration/FileFilterConfig:includePatterns",
- "integration/FileFilterConfig:maxFileSize",
- "integration/FileFilterConfig:minFileSize",
- "integration/FileMetadataConfig:customMetadata",
- "integration/FileMetadataConfig:extractMetadata",
- "integration/FileMetadataConfig:metadataFields",
- "integration/FileStorageConnector:actions",
- "integration/FileStorageConnector:auth",
- "integration/FileStorageConnector:authentication",
- "integration/FileStorageConnector:buckets",
- "integration/FileStorageConnector:bufferSize",
- "integration/FileStorageConnector:connectionTimeoutMs",
- "integration/FileStorageConnector:contentProcessing",
- "integration/FileStorageConnector:description",
- "integration/FileStorageConnector:enabled",
- "integration/FileStorageConnector:encryption",
- "integration/FileStorageConnector:errorMapping",
- "integration/FileStorageConnector:fieldMappings",
- "integration/FileStorageConnector:health",
- "integration/FileStorageConnector:icon",
- "integration/FileStorageConnector:label",
- "integration/FileStorageConnector:lifecyclePolicy",
- "integration/FileStorageConnector:metadata",
- "integration/FileStorageConnector:metadataConfig",
- "integration/FileStorageConnector:multipartConfig",
- "integration/FileStorageConnector:name",
- "integration/FileStorageConnector:provider",
- "integration/FileStorageConnector:providerConfig",
- "integration/FileStorageConnector:rateLimitConfig",
- "integration/FileStorageConnector:requestTimeoutMs",
- "integration/FileStorageConnector:retryConfig",
- "integration/FileStorageConnector:status",
- "integration/FileStorageConnector:storageConfig",
- "integration/FileStorageConnector:syncConfig",
- "integration/FileStorageConnector:transferAcceleration",
- "integration/FileStorageConnector:triggers",
- "integration/FileStorageConnector:type",
- "integration/FileStorageConnector:versioningConfig",
- "integration/FileStorageConnector:webhooks",
- "integration/FileVersioningConfig:enabled",
- "integration/FileVersioningConfig:maxVersions",
- "integration/FileVersioningConfig:retentionDays",
- "integration/GitHubActionsWorkflow:enabled",
- "integration/GitHubActionsWorkflow:env",
- "integration/GitHubActionsWorkflow:name",
- "integration/GitHubActionsWorkflow:path",
- "integration/GitHubActionsWorkflow:secrets",
- "integration/GitHubActionsWorkflow:triggers",
- "integration/GitHubCommitConfig:authorEmail",
- "integration/GitHubCommitConfig:authorName",
- "integration/GitHubCommitConfig:messageTemplate",
- "integration/GitHubCommitConfig:signCommits",
- "integration/GitHubCommitConfig:useConventionalCommits",
- "integration/GitHubConnector:actions",
- "integration/GitHubConnector:auth",
- "integration/GitHubConnector:authentication",
- "integration/GitHubConnector:baseUrl",
- "integration/GitHubConnector:commitConfig",
- "integration/GitHubConnector:connectionTimeoutMs",
- "integration/GitHubConnector:description",
- "integration/GitHubConnector:enableWebhooks",
- "integration/GitHubConnector:enabled",
- "integration/GitHubConnector:errorMapping",
- "integration/GitHubConnector:fieldMappings",
- "integration/GitHubConnector:health",
- "integration/GitHubConnector:icon",
- "integration/GitHubConnector:issueTracking",
- "integration/GitHubConnector:label",
- "integration/GitHubConnector:metadata",
- "integration/GitHubConnector:name",
- "integration/GitHubConnector:provider",
- "integration/GitHubConnector:providerConfig",
- "integration/GitHubConnector:pullRequestConfig",
- "integration/GitHubConnector:rateLimitConfig",
- "integration/GitHubConnector:releaseConfig",
- "integration/GitHubConnector:repositories",
- "integration/GitHubConnector:requestTimeoutMs",
- "integration/GitHubConnector:retryConfig",
- "integration/GitHubConnector:status",
- "integration/GitHubConnector:syncConfig",
- "integration/GitHubConnector:triggers",
- "integration/GitHubConnector:type",
- "integration/GitHubConnector:webhookEvents",
- "integration/GitHubConnector:webhooks",
- "integration/GitHubConnector:workflows",
- "integration/GitHubIssueTracking:autoAssign",
- "integration/GitHubIssueTracking:autoCloseStale",
- "integration/GitHubIssueTracking:defaultLabels",
- "integration/GitHubIssueTracking:enabled",
- "integration/GitHubIssueTracking:templatePaths",
- "integration/GitHubPullRequestConfig:bodyTemplate",
- "integration/GitHubPullRequestConfig:defaultAssignees",
- "integration/GitHubPullRequestConfig:defaultLabels",
- "integration/GitHubPullRequestConfig:defaultReviewers",
- "integration/GitHubPullRequestConfig:deleteHeadBranch",
- "integration/GitHubPullRequestConfig:draftByDefault",
- "integration/GitHubPullRequestConfig:titleTemplate",
- "integration/GitHubReleaseConfig:autoReleaseNotes",
- "integration/GitHubReleaseConfig:draftByDefault",
- "integration/GitHubReleaseConfig:preReleasePattern",
- "integration/GitHubReleaseConfig:releaseNameTemplate",
- "integration/GitHubReleaseConfig:semanticVersioning",
- "integration/GitHubReleaseConfig:tagPattern",
- "integration/GitHubRepository:autoMerge",
- "integration/GitHubRepository:branchProtection",
- "integration/GitHubRepository:defaultBranch",
- "integration/GitHubRepository:name",
- "integration/GitHubRepository:owner",
- "integration/GitHubRepository:topics",
- "integration/GitRepositoryConfig:autoDeployPreview",
- "integration/GitRepositoryConfig:autoDeployProduction",
- "integration/GitRepositoryConfig:productionBranch",
- "integration/GitRepositoryConfig:repo",
- "integration/GitRepositoryConfig:type",
"integration/HealthCheckConfig:enabled",
"integration/HealthCheckConfig:endpoint",
"integration/HealthCheckConfig:expectedStatus",
@@ -4454,51 +4241,6 @@
"integration/HealthCheckConfig:method",
"integration/HealthCheckConfig:timeoutMs",
"integration/HealthCheckConfig:unhealthyThreshold",
- "integration/MessageQueueConnector:actions",
- "integration/MessageQueueConnector:auth",
- "integration/MessageQueueConnector:authentication",
- "integration/MessageQueueConnector:brokerConfig",
- "integration/MessageQueueConnector:connectionTimeoutMs",
- "integration/MessageQueueConnector:deliveryGuarantee",
- "integration/MessageQueueConnector:description",
- "integration/MessageQueueConnector:enableMetrics",
- "integration/MessageQueueConnector:enableTracing",
- "integration/MessageQueueConnector:enabled",
- "integration/MessageQueueConnector:errorMapping",
- "integration/MessageQueueConnector:fieldMappings",
- "integration/MessageQueueConnector:health",
- "integration/MessageQueueConnector:icon",
- "integration/MessageQueueConnector:label",
- "integration/MessageQueueConnector:metadata",
- "integration/MessageQueueConnector:name",
- "integration/MessageQueueConnector:preserveOrder",
- "integration/MessageQueueConnector:provider",
- "integration/MessageQueueConnector:providerConfig",
- "integration/MessageQueueConnector:rateLimitConfig",
- "integration/MessageQueueConnector:requestTimeoutMs",
- "integration/MessageQueueConnector:retryConfig",
- "integration/MessageQueueConnector:saslConfig",
- "integration/MessageQueueConnector:schemaRegistry",
- "integration/MessageQueueConnector:sslConfig",
- "integration/MessageQueueConnector:status",
- "integration/MessageQueueConnector:syncConfig",
- "integration/MessageQueueConnector:topics",
- "integration/MessageQueueConnector:triggers",
- "integration/MessageQueueConnector:type",
- "integration/MessageQueueConnector:webhooks",
- "integration/MultipartUploadConfig:enabled",
- "integration/MultipartUploadConfig:maxConcurrentParts",
- "integration/MultipartUploadConfig:partSize",
- "integration/MultipartUploadConfig:threshold",
- "integration/ProducerConfig:acks",
- "integration/ProducerConfig:batchSize",
- "integration/ProducerConfig:compressionType",
- "integration/ProducerConfig:enabled",
- "integration/ProducerConfig:idempotence",
- "integration/ProducerConfig:lingerMs",
- "integration/ProducerConfig:maxInFlightRequests",
- "integration/ProducerConfig:transactionTimeoutMs",
- "integration/ProducerConfig:transactional",
"integration/RateLimitConfig:burstCapacity",
"integration/RateLimitConfig:maxRequests",
"integration/RateLimitConfig:rateLimitHeaders",
@@ -4513,113 +4255,6 @@
"integration/RetryConfig:retryOnNetworkError",
"integration/RetryConfig:retryableStatusCodes",
"integration/RetryConfig:strategy",
- "integration/SaasConnector:actions",
- "integration/SaasConnector:apiVersion",
- "integration/SaasConnector:auth",
- "integration/SaasConnector:authentication",
- "integration/SaasConnector:baseUrl",
- "integration/SaasConnector:connectionTimeoutMs",
- "integration/SaasConnector:customHeaders",
- "integration/SaasConnector:description",
- "integration/SaasConnector:enabled",
- "integration/SaasConnector:errorMapping",
- "integration/SaasConnector:fieldMappings",
- "integration/SaasConnector:health",
- "integration/SaasConnector:icon",
- "integration/SaasConnector:label",
- "integration/SaasConnector:metadata",
- "integration/SaasConnector:name",
- "integration/SaasConnector:oauthSettings",
- "integration/SaasConnector:objectTypes",
- "integration/SaasConnector:paginationConfig",
- "integration/SaasConnector:provider",
- "integration/SaasConnector:providerConfig",
- "integration/SaasConnector:rateLimitConfig",
- "integration/SaasConnector:requestTimeoutMs",
- "integration/SaasConnector:retryConfig",
- "integration/SaasConnector:sandboxConfig",
- "integration/SaasConnector:status",
- "integration/SaasConnector:syncConfig",
- "integration/SaasConnector:triggers",
- "integration/SaasConnector:type",
- "integration/SaasConnector:webhooks",
- "integration/SaasObjectType:apiName",
- "integration/SaasObjectType:enabled",
- "integration/SaasObjectType:fieldMappings",
- "integration/SaasObjectType:label",
- "integration/SaasObjectType:name",
- "integration/SaasObjectType:supportsCreate",
- "integration/SaasObjectType:supportsDelete",
- "integration/SaasObjectType:supportsUpdate",
- "integration/SslConfig:ca",
- "integration/SslConfig:cert",
- "integration/SslConfig:enabled",
- "integration/SslConfig:key",
- "integration/SslConfig:rejectUnauthorized",
- "integration/StorageBucket:accessPattern",
- "integration/StorageBucket:bucketName",
- "integration/StorageBucket:enabled",
- "integration/StorageBucket:fileFilters",
- "integration/StorageBucket:label",
- "integration/StorageBucket:name",
- "integration/StorageBucket:prefix",
- "integration/StorageBucket:region",
- "integration/TopicQueue:consumerConfig",
- "integration/TopicQueue:dlqConfig",
- "integration/TopicQueue:enabled",
- "integration/TopicQueue:label",
- "integration/TopicQueue:messageFilter",
- "integration/TopicQueue:messageFormat",
- "integration/TopicQueue:mode",
- "integration/TopicQueue:name",
- "integration/TopicQueue:partitions",
- "integration/TopicQueue:producerConfig",
- "integration/TopicQueue:replicationFactor",
- "integration/TopicQueue:routingKey",
- "integration/TopicQueue:topicName",
- "integration/VercelConnector:actions",
- "integration/VercelConnector:auth",
- "integration/VercelConnector:authentication",
- "integration/VercelConnector:baseUrl",
- "integration/VercelConnector:connectionTimeoutMs",
- "integration/VercelConnector:description",
- "integration/VercelConnector:enableWebhooks",
- "integration/VercelConnector:enabled",
- "integration/VercelConnector:errorMapping",
- "integration/VercelConnector:fieldMappings",
- "integration/VercelConnector:health",
- "integration/VercelConnector:icon",
- "integration/VercelConnector:label",
- "integration/VercelConnector:metadata",
- "integration/VercelConnector:monitoring",
- "integration/VercelConnector:name",
- "integration/VercelConnector:projects",
- "integration/VercelConnector:provider",
- "integration/VercelConnector:providerConfig",
- "integration/VercelConnector:rateLimitConfig",
- "integration/VercelConnector:requestTimeoutMs",
- "integration/VercelConnector:retryConfig",
- "integration/VercelConnector:status",
- "integration/VercelConnector:syncConfig",
- "integration/VercelConnector:team",
- "integration/VercelConnector:triggers",
- "integration/VercelConnector:type",
- "integration/VercelConnector:webhookEvents",
- "integration/VercelConnector:webhooks",
- "integration/VercelMonitoring:enableSpeedInsights",
- "integration/VercelMonitoring:enableWebAnalytics",
- "integration/VercelMonitoring:logDrains",
- "integration/VercelProject:buildConfig",
- "integration/VercelProject:deploymentConfig",
- "integration/VercelProject:domains",
- "integration/VercelProject:edgeFunctions",
- "integration/VercelProject:environmentVariables",
- "integration/VercelProject:framework",
- "integration/VercelProject:gitRepository",
- "integration/VercelProject:name",
- "integration/VercelProject:rootDirectory",
- "integration/VercelTeam:teamId",
- "integration/VercelTeam:teamName",
"integration/WebhookConfig:description",
"integration/WebhookConfig:events",
"integration/WebhookConfig:headers",
diff --git a/packages/spec/json-schema.manifest.json b/packages/spec/json-schema.manifest.json
index ecdd492db9..5f3fbb5f60 100644
--- a/packages/spec/json-schema.manifest.json
+++ b/packages/spec/json-schema.manifest.json
@@ -880,10 +880,6 @@
"identity/Session",
"identity/User",
"identity/VerificationToken",
- "integration/AckMode",
- "integration/ApiVersionConfig",
- "integration/BuildConfig",
- "integration/CdcConfig",
"integration/CircuitBreakerConfig",
"integration/ConflictResolution",
"integration/Connector",
@@ -899,59 +895,16 @@
"integration/ConnectorStatus",
"integration/ConnectorTrigger",
"integration/ConnectorType",
- "integration/ConsumerConfig",
"integration/DataSyncConfig",
- "integration/DatabaseConnector",
- "integration/DatabasePoolConfig",
- "integration/DatabaseProvider",
- "integration/DatabaseTable",
"integration/DeclarativeConnectorEntry",
- "integration/DeliveryGuarantee",
- "integration/DeploymentConfig",
- "integration/DlqConfig",
- "integration/DomainConfig",
- "integration/EdgeFunctionConfig",
- "integration/EnvironmentVariables",
"integration/ErrorMappingConfig",
"integration/ErrorMappingRule",
"integration/FieldMapping",
- "integration/FileAccessPattern",
- "integration/FileFilterConfig",
- "integration/FileMetadataConfig",
- "integration/FileStorageConnector",
- "integration/FileStorageProvider",
- "integration/FileVersioningConfig",
- "integration/GitHubActionsWorkflow",
- "integration/GitHubCommitConfig",
- "integration/GitHubConnector",
- "integration/GitHubIssueTracking",
- "integration/GitHubProvider",
- "integration/GitHubPullRequestConfig",
- "integration/GitHubReleaseConfig",
- "integration/GitHubRepository",
- "integration/GitRepositoryConfig",
"integration/HealthCheckConfig",
- "integration/MessageFormat",
- "integration/MessageQueueConnector",
- "integration/MessageQueueProvider",
- "integration/MultipartUploadConfig",
- "integration/ProducerConfig",
"integration/RateLimitConfig",
"integration/RateLimitStrategy",
"integration/RetryConfig",
- "integration/SaasConnector",
- "integration/SaasObjectType",
- "integration/SaasProvider",
- "integration/SslConfig",
- "integration/StorageBucket",
"integration/SyncStrategy",
- "integration/TopicQueue",
- "integration/VercelConnector",
- "integration/VercelFramework",
- "integration/VercelMonitoring",
- "integration/VercelProject",
- "integration/VercelProvider",
- "integration/VercelTeam",
"integration/WebhookConfig",
"integration/WebhookEvent",
"integration/WebhookSignatureAlgorithm",
diff --git a/packages/spec/src/integration/connector/database.test.ts b/packages/spec/src/integration/connector/database.test.ts
deleted file mode 100644
index c97d106252..0000000000
--- a/packages/spec/src/integration/connector/database.test.ts
+++ /dev/null
@@ -1,216 +0,0 @@
-import { describe, it, expect } from 'vitest';
-import {
- DatabaseProviderSchema,
- DatabasePoolConfigSchema,
- SslConfigSchema,
- CdcConfigSchema,
- DatabaseTableSchema,
- DatabaseConnectorSchema,
-} from './database.zod';
-
-const baseAuth = { type: 'none' as const };
-
-const minimalTable = {
- name: 'customer',
- label: 'Customer',
- tableName: 'customers',
- primaryKey: 'id',
-};
-
-const minimalConnector = {
- name: 'pg_main',
- label: 'PostgreSQL Main',
- type: 'database' as const,
- provider: 'postgresql' as const,
- authentication: baseAuth,
- connectionConfig: {
- host: 'localhost',
- port: 5432,
- database: 'mydb',
- username: 'user',
- password: 'pass',
- },
- tables: [minimalTable],
-};
-
-describe('DatabaseProviderSchema', () => {
- it('should accept all valid providers', () => {
- const providers = ['postgresql', 'mysql', 'mariadb', 'mssql', 'oracle', 'mongodb', 'redis', 'cassandra', 'snowflake', 'bigquery', 'redshift', 'custom'];
- for (const p of providers) {
- expect(DatabaseProviderSchema.parse(p)).toBe(p);
- }
- });
-
- it('should reject invalid provider', () => {
- expect(() => DatabaseProviderSchema.parse('sqlite')).toThrow();
- });
-});
-
-describe('DatabasePoolConfigSchema', () => {
- it('should apply defaults', () => {
- const result = DatabasePoolConfigSchema.parse({});
- expect(result.min).toBe(2);
- expect(result.max).toBe(10);
- expect(result.idleTimeoutMs).toBe(30000);
- expect(result.testOnBorrow).toBe(true);
- });
-
- it('should accept custom values', () => {
- const result = DatabasePoolConfigSchema.parse({ min: 0, max: 50, idleTimeoutMs: 5000 });
- expect(result.min).toBe(0);
- expect(result.max).toBe(50);
- });
-
- it('should reject max below 1', () => {
- expect(() => DatabasePoolConfigSchema.parse({ max: 0 })).toThrow();
- });
-
- it('should reject idleTimeoutMs below 1000', () => {
- expect(() => DatabasePoolConfigSchema.parse({ idleTimeoutMs: 500 })).toThrow();
- });
-});
-
-describe('SslConfigSchema', () => {
- it('should apply defaults', () => {
- const result = SslConfigSchema.parse({});
- expect(result.enabled).toBe(false);
- expect(result.rejectUnauthorized).toBe(true);
- });
-
- it('should accept full config', () => {
- const result = SslConfigSchema.parse({
- enabled: true,
- rejectUnauthorized: false,
- ca: 'ca-cert',
- cert: 'client-cert',
- key: 'client-key',
- });
- expect(result.enabled).toBe(true);
- expect(result.ca).toBe('ca-cert');
- });
-});
-
-describe('CdcConfigSchema', () => {
- it('should accept valid CDC config', () => {
- const result = CdcConfigSchema.parse({ method: 'log_based' });
- expect(result.enabled).toBe(false);
- expect(result.batchSize).toBe(1000);
- expect(result.pollIntervalMs).toBe(1000);
- });
-
- it('should accept all CDC methods', () => {
- for (const m of ['log_based', 'trigger_based', 'query_based', 'custom']) {
- expect(() => CdcConfigSchema.parse({ method: m })).not.toThrow();
- }
- });
-
- it('should reject missing method', () => {
- expect(() => CdcConfigSchema.parse({ enabled: true })).toThrow();
- });
-
- it('should reject batchSize out of range', () => {
- expect(() => CdcConfigSchema.parse({ method: 'log_based', batchSize: 0 })).toThrow();
- expect(() => CdcConfigSchema.parse({ method: 'log_based', batchSize: 10001 })).toThrow();
- });
-
- it('should accept optional fields', () => {
- const result = CdcConfigSchema.parse({
- method: 'log_based',
- enabled: true,
- slotName: 'slot1',
- publicationName: 'pub1',
- startPosition: '0/1234',
- });
- expect(result.slotName).toBe('slot1');
- });
-});
-
-describe('DatabaseTableSchema', () => {
- it('should accept valid table', () => {
- const result = DatabaseTableSchema.parse(minimalTable);
- expect(result.enabled).toBe(true);
- });
-
- it('should accept table with all optional fields', () => {
- const data = {
- ...minimalTable,
- schema: 'public',
- enabled: false,
- fieldMappings: [{ source: 'ext_id', target: 'id' }],
- whereClause: 'status = \'active\'',
- };
- expect(() => DatabaseTableSchema.parse(data)).not.toThrow();
- });
-
- it('should reject non-snake_case name', () => {
- expect(() => DatabaseTableSchema.parse({ ...minimalTable, name: 'Customer' })).toThrow();
- });
-
- it('should reject missing required fields', () => {
- expect(() => DatabaseTableSchema.parse({ name: 'tbl' })).toThrow();
- });
-});
-
-describe('DatabaseConnectorSchema', () => {
- it('should accept minimal valid connector', () => {
- expect(() => DatabaseConnectorSchema.parse(minimalConnector)).not.toThrow();
- });
-
- it('should apply defaults', () => {
- const result = DatabaseConnectorSchema.parse(minimalConnector);
- expect(result.queryTimeoutMs).toBe(30000);
- expect(result.enableQueryLogging).toBe(false);
- expect(result.enabled).toBe(true);
- });
-
- it('should accept full connector', () => {
- const full = {
- ...minimalConnector,
- poolConfig: { min: 5, max: 25 },
- sslConfig: { enabled: true },
- cdcConfig: { method: 'log_based', enabled: true },
- readReplicaConfig: {
- enabled: true,
- hosts: [{ host: 'replica1', port: 5432, weight: 0.5 }],
- },
- queryTimeoutMs: 60000,
- enableQueryLogging: true,
- };
- expect(() => DatabaseConnectorSchema.parse(full)).not.toThrow();
- });
-
- it('should reject wrong type literal', () => {
- expect(() => DatabaseConnectorSchema.parse({ ...minimalConnector, type: 'saas' })).toThrow();
- });
-
- it('should reject invalid port', () => {
- expect(() => DatabaseConnectorSchema.parse({
- ...minimalConnector,
- connectionConfig: { ...minimalConnector.connectionConfig, port: 0 },
- })).toThrow();
- expect(() => DatabaseConnectorSchema.parse({
- ...minimalConnector,
- connectionConfig: { ...minimalConnector.connectionConfig, port: 70000 },
- })).toThrow();
- });
-
- it('should reject queryTimeoutMs out of range', () => {
- expect(() => DatabaseConnectorSchema.parse({ ...minimalConnector, queryTimeoutMs: 500 })).toThrow();
- expect(() => DatabaseConnectorSchema.parse({ ...minimalConnector, queryTimeoutMs: 500000 })).toThrow();
- });
-
- it('should reject missing tables', () => {
- const { tables: _, ...noTables } = minimalConnector;
- expect(() => DatabaseConnectorSchema.parse(noTables)).toThrow();
- });
-
- it('should reject read replica with invalid weight', () => {
- expect(() => DatabaseConnectorSchema.parse({
- ...minimalConnector,
- readReplicaConfig: {
- enabled: true,
- hosts: [{ host: 'r1', port: 5432, weight: 2 }],
- },
- })).toThrow();
- });
-});
diff --git a/packages/spec/src/integration/connector/database.zod.ts b/packages/spec/src/integration/connector/database.zod.ts
deleted file mode 100644
index a038a7e21e..0000000000
--- a/packages/spec/src/integration/connector/database.zod.ts
+++ /dev/null
@@ -1,344 +0,0 @@
-// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
-
-import { z } from 'zod';
-import {
- ConnectorSchema,
- FieldMappingSchema,
-} from '../connector.zod';
-
-/**
- * Database Connector Protocol Template
- *
- * Specialized connector for database systems (PostgreSQL, MySQL, SQL Server, etc.)
- * Extends the base connector with database-specific features like schema discovery,
- * CDC (Change Data Capture), and connection pooling.
- */
-
-/**
- * Database Provider Types
- */
-import { lazySchema } from '../../shared/lazy-schema';
-export const DatabaseProviderSchema = lazySchema(() => z.enum([
- 'postgresql',
- 'mysql',
- 'mariadb',
- 'mssql',
- 'oracle',
- 'mongodb',
- 'redis',
- 'cassandra',
- 'snowflake',
- 'bigquery',
- 'redshift',
- 'custom',
-]).describe('Database provider type'));
-
-export type DatabaseProvider = z.infer;
-
-/**
- * Database Connection Pool Configuration
- */
-export const DatabasePoolConfigSchema = lazySchema(() => z.object({
- min: z.number().min(0).default(2).describe('Minimum connections in pool'),
- max: z.number().min(1).default(10).describe('Maximum connections in pool'),
- idleTimeoutMs: z.number().min(1000).default(30000).describe('Idle connection timeout in ms'),
- connectionTimeoutMs: z.number().min(1000).default(10000).describe('Connection establishment timeout in ms'),
- acquireTimeoutMs: z.number().min(1000).default(30000).describe('Connection acquisition timeout in ms'),
- evictionRunIntervalMs: z.number().min(1000).default(30000).describe('Connection eviction check interval in ms'),
- testOnBorrow: z.boolean().default(true).describe('Test connection before use'),
-}));
-
-export type DatabasePoolConfig = z.infer;
-
-/**
- * SSL/TLS Configuration
- */
-export const SslConfigSchema = lazySchema(() => z.object({
- enabled: z.boolean().default(false).describe('Enable SSL/TLS'),
- rejectUnauthorized: z.boolean().default(true).describe('Reject unauthorized certificates'),
- ca: z.string().optional().describe('Certificate Authority certificate'),
- cert: z.string().optional().describe('Client certificate'),
- key: z.string().optional().describe('Client private key'),
-}));
-
-export type SslConfig = z.infer;
-
-/**
- * Change Data Capture (CDC) Configuration
- */
-export const CdcConfigSchema = lazySchema(() => z.object({
- enabled: z.boolean().default(false).describe('Enable CDC'),
-
- method: z.enum([
- 'log_based', // Transaction log parsing (e.g., PostgreSQL logical replication)
- 'trigger_based', // Database triggers for change tracking
- 'query_based', // Timestamp-based queries
- 'custom', // Custom CDC implementation
- ]).describe('CDC method'),
-
- slotName: z.string().optional().describe('Replication slot name (for log-based CDC)'),
-
- publicationName: z.string().optional().describe('Publication name (for PostgreSQL)'),
-
- startPosition: z.string().optional().describe('Starting position/LSN for CDC stream'),
-
- batchSize: z.number().min(1).max(10000).default(1000).describe('CDC batch size'),
-
- pollIntervalMs: z.number().min(100).default(1000).describe('CDC polling interval in ms'),
-}));
-
-export type CdcConfig = z.infer;
-
-/**
- * Database Table Configuration
- */
-export const DatabaseTableSchema = lazySchema(() => z.object({
- name: z.string().regex(/^[a-z_][a-z0-9_]*$/).describe('Table name in ObjectStack (snake_case)'),
- label: z.string().describe('Display label'),
- schema: z.string().optional().describe('Database schema name'),
- tableName: z.string().describe('Actual table name in database'),
- primaryKey: z.string().describe('Primary key column'),
- enabled: z.boolean().default(true).describe('Enable sync for this table'),
- fieldMappings: z.array(FieldMappingSchema).optional().describe('Table-specific field mappings'),
- whereClause: z.string().optional().describe('SQL WHERE clause for filtering'),
-}));
-
-export type DatabaseTable = z.infer;
-
-/**
- * Database Connector Configuration Schema
- */
-export const DatabaseConnectorSchema = lazySchema(() => ConnectorSchema.extend({
- type: z.literal('database'),
-
- /**
- * Database provider
- */
- provider: DatabaseProviderSchema.describe('Database provider type'),
-
- /**
- * Connection configuration
- */
- connectionConfig: z.object({
- host: z.string().describe('Database host'),
- port: z.number().min(1).max(65535).describe('Database port'),
- database: z.string().describe('Database name'),
- username: z.string().describe('Database username'),
- password: z.string().describe('Database password (typically from ENV)'),
- options: z.record(z.string(), z.unknown()).optional().describe('Driver-specific connection options'),
- }).describe('Database connection configuration'),
-
- /**
- * Connection pool configuration
- */
- poolConfig: DatabasePoolConfigSchema.optional().describe('Connection pool configuration'),
-
- /**
- * SSL/TLS configuration
- */
- sslConfig: SslConfigSchema.optional().describe('SSL/TLS configuration'),
-
- /**
- * Tables to sync
- */
- tables: z.array(DatabaseTableSchema).describe('Tables to sync'),
-
- /**
- * Change Data Capture configuration
- */
- cdcConfig: CdcConfigSchema.optional().describe('CDC configuration'),
-
- /**
- * Read replica configuration
- */
- readReplicaConfig: z.object({
- enabled: z.boolean().default(false).describe('Use read replicas'),
- hosts: z.array(z.object({
- host: z.string().describe('Replica host'),
- port: z.number().min(1).max(65535).describe('Replica port'),
- weight: z.number().min(0).max(1).default(1).describe('Load balancing weight'),
- })).describe('Read replica hosts'),
- }).optional().describe('Read replica configuration'),
-
- /**
- * Query timeout
- */
- queryTimeoutMs: z.number().min(1000).max(300000).optional().default(30000).describe('Query timeout in ms'),
-
- /**
- * Enable query logging
- */
- enableQueryLogging: z.boolean().optional().default(false).describe('Enable SQL query logging'),
-}));
-
-export type DatabaseConnector = z.infer;
-
-// ============================================================================
-// Helper Functions & Examples
-// ============================================================================
-
-/**
- * Example: PostgreSQL Connector Configuration
- */
-export const postgresConnectorExample = {
- name: 'postgres_production',
- label: 'Production PostgreSQL',
- type: 'database',
- provider: 'postgresql',
- authentication: {
- type: 'basic',
- username: '${DB_USERNAME}',
- password: '${DB_PASSWORD}',
- },
- connectionConfig: {
- host: 'db.example.com',
- port: 5432,
- database: 'production',
- username: '${DB_USERNAME}',
- password: '${DB_PASSWORD}',
- },
- poolConfig: {
- min: 2,
- max: 20,
- idleTimeoutMs: 30000,
- connectionTimeoutMs: 10000,
- acquireTimeoutMs: 30000,
- evictionRunIntervalMs: 30000,
- testOnBorrow: true,
- },
- sslConfig: {
- enabled: true,
- rejectUnauthorized: true,
- },
- tables: [
- {
- name: 'customer',
- label: 'Customer',
- schema: 'public',
- tableName: 'customers',
- primaryKey: 'id',
- enabled: true,
- },
- {
- name: 'order',
- label: 'Order',
- schema: 'public',
- tableName: 'orders',
- primaryKey: 'id',
- enabled: true,
- whereClause: 'status != \'archived\'',
- },
- ],
- cdcConfig: {
- enabled: true,
- method: 'log_based',
- slotName: 'objectstack_replication_slot',
- publicationName: 'objectstack_publication',
- batchSize: 1000,
- pollIntervalMs: 1000,
- },
- syncConfig: {
- strategy: 'incremental',
- direction: 'bidirectional',
- realtimeSync: true,
- conflictResolution: 'latest_wins',
- batchSize: 1000,
- deleteMode: 'soft_delete',
- },
- status: 'active',
- enabled: true,
-};
-
-/**
- * Example: MongoDB Connector Configuration
- */
-export const mongoConnectorExample = {
- name: 'mongodb_analytics',
- label: 'MongoDB Analytics',
- type: 'database',
- provider: 'mongodb',
- authentication: {
- type: 'basic',
- username: '${MONGO_USERNAME}',
- password: '${MONGO_PASSWORD}',
- },
- connectionConfig: {
- host: 'mongodb.example.com',
- port: 27017,
- database: 'analytics',
- username: '${MONGO_USERNAME}',
- password: '${MONGO_PASSWORD}',
- options: {
- authSource: 'admin',
- replicaSet: 'rs0',
- },
- },
- tables: [
- {
- name: 'event',
- label: 'Event',
- tableName: 'events',
- primaryKey: 'id',
- enabled: true,
- },
- ],
- cdcConfig: {
- enabled: true,
- method: 'log_based',
- batchSize: 1000,
- pollIntervalMs: 500,
- },
- syncConfig: {
- strategy: 'incremental',
- direction: 'import',
- batchSize: 1000,
- },
- status: 'active',
- enabled: true,
-};
-
-/**
- * Example: Snowflake Connector Configuration
- */
-export const snowflakeConnectorExample = {
- name: 'snowflake_warehouse',
- label: 'Snowflake Data Warehouse',
- type: 'database',
- provider: 'snowflake',
- authentication: {
- type: 'basic',
- username: '${SNOWFLAKE_USERNAME}',
- password: '${SNOWFLAKE_PASSWORD}',
- },
- connectionConfig: {
- host: 'account.snowflakecomputing.com',
- port: 443,
- database: 'ANALYTICS_DB',
- username: '${SNOWFLAKE_USERNAME}',
- password: '${SNOWFLAKE_PASSWORD}',
- options: {
- warehouse: 'COMPUTE_WH',
- schema: 'PUBLIC',
- role: 'ANALYST',
- },
- },
- tables: [
- {
- name: 'sales_summary',
- label: 'Sales Summary',
- schema: 'PUBLIC',
- tableName: 'SALES_SUMMARY',
- primaryKey: 'ID',
- enabled: true,
- },
- ],
- syncConfig: {
- strategy: 'full',
- direction: 'import',
- schedule: '0 2 * * *', // Daily at 2 AM
- batchSize: 5000,
- },
- queryTimeoutMs: 60000,
- status: 'active',
- enabled: true,
-};
diff --git a/packages/spec/src/integration/connector/file-storage.test.ts b/packages/spec/src/integration/connector/file-storage.test.ts
deleted file mode 100644
index 6f99f3ec3d..0000000000
--- a/packages/spec/src/integration/connector/file-storage.test.ts
+++ /dev/null
@@ -1,209 +0,0 @@
-import { describe, it, expect } from 'vitest';
-import {
- FileStorageProviderSchema,
- FileAccessPatternSchema,
- FileMetadataConfigSchema,
- MultipartUploadConfigSchema,
- FileVersioningConfigSchema,
- FileFilterConfigSchema,
- StorageBucketSchema,
- FileStorageConnectorSchema,
-} from './file-storage.zod';
-
-// Shared base connector fields for FileStorageConnector
-const baseConnector = {
- name: 's3_assets',
- label: 'S3 Assets',
- type: 'file_storage' as const,
- authentication: { type: 'none' as const },
- provider: 's3' as const,
- buckets: [
- { name: 'my_bucket', label: 'My Bucket', bucketName: 'actual-bucket' },
- ],
-};
-
-describe('FileStorageProviderSchema', () => {
- it('should accept valid providers', () => {
- for (const v of ['s3', 'azure_blob', 'gcs', 'dropbox', 'box', 'onedrive', 'google_drive', 'sharepoint', 'ftp', 'local', 'custom']) {
- expect(FileStorageProviderSchema.parse(v)).toBe(v);
- }
- });
-
- it('should reject invalid provider', () => {
- expect(() => FileStorageProviderSchema.parse('invalid')).toThrow();
- });
-});
-
-describe('FileAccessPatternSchema', () => {
- it('should accept valid access patterns', () => {
- for (const v of ['public_read', 'private', 'authenticated_read', 'bucket_owner_read', 'bucket_owner_full']) {
- expect(FileAccessPatternSchema.parse(v)).toBe(v);
- }
- });
-
- it('should reject invalid access pattern', () => {
- expect(() => FileAccessPatternSchema.parse('unknown')).toThrow();
- });
-});
-
-describe('FileMetadataConfigSchema', () => {
- it('should accept valid config with defaults', () => {
- const result = FileMetadataConfigSchema.parse({});
- expect(result.extractMetadata).toBe(true);
- });
-
- it('should accept full config', () => {
- const data = {
- extractMetadata: false,
- metadataFields: ['content_type', 'file_size', 'etag'],
- customMetadata: { env: 'prod' },
- };
- expect(() => FileMetadataConfigSchema.parse(data)).not.toThrow();
- });
-
- it('should reject invalid metadataFields value', () => {
- expect(() => FileMetadataConfigSchema.parse({ metadataFields: ['bad_field'] })).toThrow();
- });
-});
-
-describe('MultipartUploadConfigSchema', () => {
- it('should apply defaults', () => {
- const result = MultipartUploadConfigSchema.parse({});
- expect(result.enabled).toBe(true);
- expect(result.partSize).toBe(5 * 1024 * 1024);
- expect(result.maxConcurrentParts).toBe(5);
- expect(result.threshold).toBe(100 * 1024 * 1024);
- });
-
- it('should reject partSize below minimum', () => {
- expect(() => MultipartUploadConfigSchema.parse({ partSize: 100 })).toThrow();
- });
-
- it('should reject maxConcurrentParts out of range', () => {
- expect(() => MultipartUploadConfigSchema.parse({ maxConcurrentParts: 0 })).toThrow();
- expect(() => MultipartUploadConfigSchema.parse({ maxConcurrentParts: 11 })).toThrow();
- });
-});
-
-describe('FileVersioningConfigSchema', () => {
- it('should apply defaults', () => {
- const result = FileVersioningConfigSchema.parse({});
- expect(result.enabled).toBe(false);
- });
-
- it('should accept valid config', () => {
- const result = FileVersioningConfigSchema.parse({ enabled: true, maxVersions: 10, retentionDays: 30 });
- expect(result.maxVersions).toBe(10);
- });
-
- it('should reject maxVersions out of range', () => {
- expect(() => FileVersioningConfigSchema.parse({ maxVersions: 0 })).toThrow();
- expect(() => FileVersioningConfigSchema.parse({ maxVersions: 101 })).toThrow();
- });
-});
-
-describe('FileFilterConfigSchema', () => {
- it('should accept empty config', () => {
- expect(() => FileFilterConfigSchema.parse({})).not.toThrow();
- });
-
- it('should accept full config', () => {
- const data = {
- includePatterns: ['*.jpg'],
- excludePatterns: ['*.tmp'],
- minFileSize: 0,
- maxFileSize: 1024,
- allowedExtensions: ['.jpg'],
- blockedExtensions: ['.exe'],
- };
- expect(() => FileFilterConfigSchema.parse(data)).not.toThrow();
- });
-
- it('should reject negative minFileSize', () => {
- expect(() => FileFilterConfigSchema.parse({ minFileSize: -1 })).toThrow();
- });
-
- it('should reject maxFileSize less than 1', () => {
- expect(() => FileFilterConfigSchema.parse({ maxFileSize: 0 })).toThrow();
- });
-});
-
-describe('StorageBucketSchema', () => {
- it('should accept valid bucket', () => {
- const data = { name: 'my_bucket', label: 'My Bucket', bucketName: 'actual-bucket-name' };
- const result = StorageBucketSchema.parse(data);
- expect(result.enabled).toBe(true);
- });
-
- it('should accept bucket with all optional fields', () => {
- const data = {
- name: 'docs_bucket',
- label: 'Documents',
- bucketName: 'docs-bucket',
- region: 'us-east-1',
- enabled: false,
- prefix: 'docs/',
- accessPattern: 'private',
- fileFilters: { allowedExtensions: ['.pdf'] },
- };
- expect(() => StorageBucketSchema.parse(data)).not.toThrow();
- });
-
- it('should reject non-snake_case name', () => {
- expect(() => StorageBucketSchema.parse({ name: 'MyBucket', label: 'X', bucketName: 'b' })).toThrow();
- });
-
- it('should reject missing required fields', () => {
- expect(() => StorageBucketSchema.parse({ name: 'b' })).toThrow();
- });
-});
-
-describe('FileStorageConnectorSchema', () => {
- it('should accept minimal valid connector', () => {
- expect(() => FileStorageConnectorSchema.parse(baseConnector)).not.toThrow();
- });
-
- it('should apply defaults', () => {
- const result = FileStorageConnectorSchema.parse(baseConnector);
- expect(result.transferAcceleration).toBe(false);
- expect(result.bufferSize).toBe(64 * 1024);
- expect(result.enabled).toBe(true);
- });
-
- it('should accept full connector config', () => {
- const full = {
- ...baseConnector,
- storageConfig: { endpoint: 'https://s3.example.com', region: 'us-east-1', pathStyle: true },
- metadataConfig: { extractMetadata: true, metadataFields: ['content_type'] },
- multipartConfig: { enabled: true },
- versioningConfig: { enabled: true, maxVersions: 5 },
- encryption: { enabled: true, algorithm: 'AES256' },
- lifecyclePolicy: { enabled: true, deleteAfterDays: 90 },
- contentProcessing: { extractText: true, generateThumbnails: true, thumbnailSizes: [{ width: 100, height: 100 }], virusScan: false },
- bufferSize: 2048,
- transferAcceleration: true,
- };
- expect(() => FileStorageConnectorSchema.parse(full)).not.toThrow();
- });
-
- it('should reject wrong type literal', () => {
- expect(() => FileStorageConnectorSchema.parse({ ...baseConnector, type: 'database' })).toThrow();
- });
-
- it('should reject invalid provider', () => {
- expect(() => FileStorageConnectorSchema.parse({ ...baseConnector, provider: 'invalid' })).toThrow();
- });
-
- it('should reject missing buckets', () => {
- const { buckets: _, ...noBuckets } = baseConnector;
- expect(() => FileStorageConnectorSchema.parse(noBuckets)).toThrow();
- });
-
- it('should reject bufferSize below minimum', () => {
- expect(() => FileStorageConnectorSchema.parse({ ...baseConnector, bufferSize: 100 })).toThrow();
- });
-
- it('should reject invalid storageConfig endpoint', () => {
- expect(() => FileStorageConnectorSchema.parse({ ...baseConnector, storageConfig: { endpoint: 'not-a-url' } })).toThrow();
- });
-});
diff --git a/packages/spec/src/integration/connector/file-storage.zod.ts b/packages/spec/src/integration/connector/file-storage.zod.ts
deleted file mode 100644
index 151f26c92d..0000000000
--- a/packages/spec/src/integration/connector/file-storage.zod.ts
+++ /dev/null
@@ -1,400 +0,0 @@
-// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
-
-import { z } from 'zod';
-import {
- ConnectorSchema,
-} from '../connector.zod';
-
-/**
- * File Storage Connector Protocol Template
- *
- * Specialized connector for file storage systems (S3, Azure Blob, Google Cloud Storage, etc.)
- * Extends the base connector with file-specific features like multipart uploads,
- * versioning, and metadata extraction.
- */
-
-/**
- * File Storage Provider Types
- */
-import { lazySchema } from '../../shared/lazy-schema';
-export const FileStorageProviderSchema = lazySchema(() => z.enum([
- 's3', // Amazon S3
- 'azure_blob', // Azure Blob Storage
- 'gcs', // Google Cloud Storage
- 'dropbox', // Dropbox
- 'box', // Box
- 'onedrive', // Microsoft OneDrive
- 'google_drive', // Google Drive
- 'sharepoint', // SharePoint
- 'ftp', // FTP/SFTP
- 'local', // Local file system
- 'custom', // Custom file storage
-]).describe('File storage provider type'));
-
-export type FileStorageProvider = z.infer;
-
-/**
- * File Access Pattern
- */
-export const FileAccessPatternSchema = lazySchema(() => z.enum([
- 'public_read', // Public read access
- 'private', // Private access
- 'authenticated_read', // Requires authentication
- 'bucket_owner_read', // Bucket owner has read access
- 'bucket_owner_full', // Bucket owner has full control
-]).describe('File access pattern'));
-
-export type FileAccessPattern = z.infer;
-
-/**
- * File Metadata Configuration
- */
-export const FileMetadataConfigSchema = lazySchema(() => z.object({
- extractMetadata: z.boolean().default(true).describe('Extract file metadata'),
-
- metadataFields: z.array(z.enum([
- 'content_type',
- 'file_size',
- 'last_modified',
- 'etag',
- 'checksum',
- 'creator',
- 'created_at',
- 'custom',
- ])).optional().describe('Metadata fields to extract'),
-
- customMetadata: z.record(z.string(), z.string()).optional().describe('Custom metadata key-value pairs'),
-}));
-
-export type FileMetadataConfig = z.infer;
-
-/**
- * Multipart Upload Configuration
- */
-export const MultipartUploadConfigSchema = lazySchema(() => z.object({
- enabled: z.boolean().default(true).describe('Enable multipart uploads'),
-
- partSize: z.number().min(5 * 1024 * 1024).default(5 * 1024 * 1024).describe('Part size in bytes (min 5MB)'),
-
- maxConcurrentParts: z.number().min(1).max(10).default(5).describe('Maximum concurrent part uploads'),
-
- threshold: z.number().min(5 * 1024 * 1024).default(100 * 1024 * 1024).describe('File size threshold for multipart upload in bytes'),
-}));
-
-export type MultipartUploadConfig = z.infer;
-
-/**
- * File Versioning Configuration
- */
-export const FileVersioningConfigSchema = lazySchema(() => z.object({
- enabled: z.boolean().default(false).describe('Enable file versioning'),
-
- maxVersions: z.number().min(1).max(100).optional().describe('Maximum versions to retain'),
-
- retentionDays: z.number().min(1).optional().describe('Version retention period in days'),
-}));
-
-export type FileVersioningConfig = z.infer;
-
-/**
- * File Filter Configuration
- */
-export const FileFilterConfigSchema = lazySchema(() => z.object({
- includePatterns: z.array(z.string()).optional().describe('File patterns to include (glob)'),
-
- excludePatterns: z.array(z.string()).optional().describe('File patterns to exclude (glob)'),
-
- minFileSize: z.number().min(0).optional().describe('Minimum file size in bytes'),
-
- maxFileSize: z.number().min(1).optional().describe('Maximum file size in bytes'),
-
- allowedExtensions: z.array(z.string()).optional().describe('Allowed file extensions'),
-
- blockedExtensions: z.array(z.string()).optional().describe('Blocked file extensions'),
-}));
-
-export type FileFilterConfig = z.infer;
-
-/**
- * File Storage Bucket/Container Configuration
- */
-export const StorageBucketSchema = lazySchema(() => z.object({
- name: z.string().regex(/^[a-z_][a-z0-9_]*$/).describe('Bucket identifier in ObjectStack (snake_case)'),
- label: z.string().describe('Display label'),
- bucketName: z.string().describe('Actual bucket/container name in storage system'),
- region: z.string().optional().describe('Storage region'),
- enabled: z.boolean().default(true).describe('Enable sync for this bucket'),
- prefix: z.string().optional().describe('Prefix/path within bucket'),
- accessPattern: FileAccessPatternSchema.optional().describe('Access pattern'),
- fileFilters: FileFilterConfigSchema.optional().describe('File filter configuration'),
-}));
-
-export type StorageBucket = z.infer;
-
-/**
- * File Storage Connector Configuration Schema
- */
-export const FileStorageConnectorSchema = lazySchema(() => ConnectorSchema.extend({
- type: z.literal('file_storage'),
-
- /**
- * File storage provider
- */
- provider: FileStorageProviderSchema.describe('File storage provider type'),
-
- /**
- * Storage configuration
- */
- storageConfig: z.object({
- endpoint: z.string().url().optional().describe('Custom endpoint URL'),
- region: z.string().optional().describe('Default region'),
- pathStyle: z.boolean().optional().default(false).describe('Use path-style URLs (for S3-compatible)'),
- }).optional().describe('Storage configuration'),
-
- /**
- * Buckets/containers to sync
- */
- buckets: z.array(StorageBucketSchema).describe('Buckets/containers to sync'),
-
- /**
- * File metadata configuration
- */
- metadataConfig: FileMetadataConfigSchema.optional().describe('Metadata extraction configuration'),
-
- /**
- * Multipart upload configuration
- */
- multipartConfig: MultipartUploadConfigSchema.optional().describe('Multipart upload configuration'),
-
- /**
- * File versioning configuration
- */
- versioningConfig: FileVersioningConfigSchema.optional().describe('File versioning configuration'),
-
- /**
- * Enable server-side encryption
- */
- encryption: z.object({
- enabled: z.boolean().default(false).describe('Enable server-side encryption'),
- algorithm: z.enum(['AES256', 'aws:kms', 'custom']).optional().describe('Encryption algorithm'),
- kmsKeyId: z.string().optional().describe('KMS key ID (for aws:kms)'),
- }).optional().describe('Encryption configuration'),
-
- /**
- * Lifecycle policy
- */
- lifecyclePolicy: z.object({
- enabled: z.boolean().default(false).describe('Enable lifecycle policy'),
- deleteAfterDays: z.number().min(1).optional().describe('Delete files after N days'),
- archiveAfterDays: z.number().min(1).optional().describe('Archive files after N days'),
- }).optional().describe('Lifecycle policy'),
-
- /**
- * Content processing configuration
- */
- contentProcessing: z.object({
- extractText: z.boolean().default(false).describe('Extract text from documents'),
- generateThumbnails: z.boolean().default(false).describe('Generate image thumbnails'),
- thumbnailSizes: z.array(z.object({
- width: z.number().min(1),
- height: z.number().min(1),
- })).optional().describe('Thumbnail sizes'),
- virusScan: z.boolean().default(false).describe('Scan for viruses'),
- }).optional().describe('Content processing configuration'),
-
- /**
- * Download/upload buffer size
- */
- bufferSize: z.number().min(1024).default(64 * 1024).describe('Buffer size in bytes'),
-
- /**
- * Enable transfer acceleration (for supported providers)
- */
- transferAcceleration: z.boolean().default(false).describe('Enable transfer acceleration'),
-}));
-
-export type FileStorageConnector = z.infer;
-
-// ============================================================================
-// Helper Functions & Examples
-// ============================================================================
-
-/**
- * Example: Amazon S3 Connector Configuration
- */
-export const s3ConnectorExample = {
- name: 's3_production_assets',
- label: 'Production S3 Assets',
- type: 'file_storage',
- provider: 's3',
- authentication: {
- type: 'api_key',
- apiKey: '${AWS_ACCESS_KEY_ID}:${AWS_SECRET_ACCESS_KEY}',
- headerName: 'Authorization',
- },
- storageConfig: {
- region: 'us-east-1',
- pathStyle: false,
- },
- buckets: [
- {
- name: 'product_images',
- label: 'Product Images',
- bucketName: 'my-company-product-images',
- region: 'us-east-1',
- enabled: true,
- prefix: 'products/',
- accessPattern: 'public_read',
- fileFilters: {
- allowedExtensions: ['.jpg', '.jpeg', '.png', '.webp'],
- maxFileSize: 10 * 1024 * 1024, // 10MB
- },
- },
- {
- name: 'customer_documents',
- label: 'Customer Documents',
- bucketName: 'my-company-customer-docs',
- region: 'us-east-1',
- enabled: true,
- accessPattern: 'private',
- fileFilters: {
- allowedExtensions: ['.pdf', '.docx', '.xlsx'],
- maxFileSize: 50 * 1024 * 1024, // 50MB
- },
- },
- ],
- metadataConfig: {
- extractMetadata: true,
- metadataFields: ['content_type', 'file_size', 'last_modified', 'etag'],
- },
- multipartConfig: {
- enabled: true,
- partSize: 5 * 1024 * 1024, // 5MB
- maxConcurrentParts: 5,
- threshold: 100 * 1024 * 1024, // 100MB
- },
- versioningConfig: {
- enabled: true,
- maxVersions: 10,
- },
- encryption: {
- enabled: true,
- algorithm: 'aws:kms',
- kmsKeyId: '${AWS_KMS_KEY_ID}',
- },
- contentProcessing: {
- extractText: true,
- generateThumbnails: true,
- thumbnailSizes: [
- { width: 150, height: 150 },
- { width: 300, height: 300 },
- { width: 600, height: 600 },
- ],
- virusScan: true,
- },
- syncConfig: {
- strategy: 'incremental',
- direction: 'bidirectional',
- realtimeSync: true,
- conflictResolution: 'latest_wins',
- batchSize: 100,
- },
- transferAcceleration: true,
- status: 'active',
- enabled: true,
-};
-
-/**
- * Example: Google Drive Connector Configuration
- */
-export const googleDriveConnectorExample = {
- name: 'google_drive_team',
- label: 'Google Drive Team Folder',
- type: 'file_storage',
- provider: 'google_drive',
- authentication: {
- type: 'oauth2',
- clientId: '${GOOGLE_CLIENT_ID}',
- clientSecret: '${GOOGLE_CLIENT_SECRET}',
- authorizationUrl: 'https://accounts.google.com/o/oauth2/v2/auth',
- tokenUrl: 'https://oauth2.googleapis.com/token',
- grantType: 'authorization_code',
- scopes: ['https://www.googleapis.com/auth/drive.file'],
- },
- buckets: [
- {
- name: 'team_drive',
- label: 'Team Drive',
- bucketName: 'shared-team-drive',
- enabled: true,
- fileFilters: {
- excludePatterns: ['*.tmp', '~$*'],
- },
- },
- ],
- metadataConfig: {
- extractMetadata: true,
- metadataFields: ['content_type', 'file_size', 'last_modified', 'creator', 'created_at'],
- },
- versioningConfig: {
- enabled: true,
- maxVersions: 5,
- },
- syncConfig: {
- strategy: 'incremental',
- direction: 'bidirectional',
- realtimeSync: true,
- conflictResolution: 'latest_wins',
- batchSize: 50,
- },
- status: 'active',
- enabled: true,
-};
-
-/**
- * Example: Azure Blob Storage Connector Configuration
- */
-export const azureBlobConnectorExample = {
- name: 'azure_blob_storage',
- label: 'Azure Blob Storage',
- type: 'file_storage',
- provider: 'azure_blob',
- authentication: {
- type: 'api_key',
- apiKey: '${AZURE_STORAGE_ACCOUNT_KEY}',
- headerName: 'x-ms-blob-type',
- },
- storageConfig: {
- endpoint: 'https://myaccount.blob.core.windows.net',
- },
- buckets: [
- {
- name: 'archive_container',
- label: 'Archive Container',
- bucketName: 'archive',
- enabled: true,
- accessPattern: 'private',
- },
- ],
- metadataConfig: {
- extractMetadata: true,
- metadataFields: ['content_type', 'file_size', 'last_modified', 'etag'],
- },
- encryption: {
- enabled: true,
- algorithm: 'AES256',
- },
- lifecyclePolicy: {
- enabled: true,
- archiveAfterDays: 90,
- deleteAfterDays: 365,
- },
- syncConfig: {
- strategy: 'incremental',
- direction: 'import',
- schedule: '0 1 * * *', // Daily at 1 AM
- batchSize: 200,
- },
- status: 'active',
- enabled: true,
-};
diff --git a/packages/spec/src/integration/connector/github.test.ts b/packages/spec/src/integration/connector/github.test.ts
deleted file mode 100644
index 778878e585..0000000000
--- a/packages/spec/src/integration/connector/github.test.ts
+++ /dev/null
@@ -1,364 +0,0 @@
-import { describe, it, expect } from 'vitest';
-import {
- GitHubConnectorSchema,
- GitHubRepositorySchema,
- GitHubCommitConfigSchema,
- GitHubPullRequestConfigSchema,
- GitHubActionsWorkflowSchema,
- GitHubReleaseConfigSchema,
- GitHubIssueTrackingSchema,
- githubPublicConnectorExample,
- githubEnterpriseConnectorExample,
- type GitHubConnector,
-} from './github.zod';
-
-describe('GitHubRepositorySchema', () => {
- it('should accept minimal repository config', () => {
- const repo = {
- owner: 'objectstack-ai',
- name: 'spec',
- };
-
- const result = GitHubRepositorySchema.parse(repo);
- expect(result.defaultBranch).toBe('main');
- expect(result.autoMerge).toBe(false);
- });
-
- it('should accept repository with branch protection', () => {
- const repo = {
- owner: 'objectstack-ai',
- name: 'spec',
- defaultBranch: 'main',
- branchProtection: {
- requiredReviewers: 2,
- requireStatusChecks: true,
- enforceAdmins: true,
- },
- };
-
- expect(() => GitHubRepositorySchema.parse(repo)).not.toThrow();
- });
-
- it('should accept repository with topics', () => {
- const repo = {
- owner: 'objectstack-ai',
- name: 'spec',
- topics: ['objectstack', 'low-code', 'metadata'],
- };
-
- expect(() => GitHubRepositorySchema.parse(repo)).not.toThrow();
- });
-});
-
-describe('GitHubCommitConfigSchema', () => {
- it('should accept minimal commit config', () => {
- const config = {};
-
- const result = GitHubCommitConfigSchema.parse(config);
- expect(result.signCommits).toBe(false);
- expect(result.useConventionalCommits).toBe(true);
- });
-
- it('should accept full commit config', () => {
- const config = {
- authorName: 'ObjectStack Bot',
- authorEmail: 'bot@objectstack.ai',
- signCommits: true,
- messageTemplate: '{{type}}: {{message}}',
- useConventionalCommits: true,
- };
-
- expect(() => GitHubCommitConfigSchema.parse(config)).not.toThrow();
- });
-
- it('should validate email format', () => {
- expect(() => GitHubCommitConfigSchema.parse({
- authorEmail: 'invalid-email',
- })).toThrow();
-
- expect(() => GitHubCommitConfigSchema.parse({
- authorEmail: 'valid@email.com',
- })).not.toThrow();
- });
-});
-
-describe('GitHubPullRequestConfigSchema', () => {
- it('should accept minimal PR config', () => {
- const config = {};
-
- const result = GitHubPullRequestConfigSchema.parse(config);
- expect(result.draftByDefault).toBe(false);
- expect(result.deleteHeadBranch).toBe(true);
- });
-
- it('should accept PR config with templates and reviewers', () => {
- const config = {
- titleTemplate: '{{type}}: {{description}}',
- bodyTemplate: '## Changes\n\n{{changes}}',
- defaultReviewers: ['reviewer1', 'reviewer2'],
- defaultAssignees: ['assignee1'],
- defaultLabels: ['automated', 'needs-review'],
- };
-
- expect(() => GitHubPullRequestConfigSchema.parse(config)).not.toThrow();
- });
-});
-
-describe('GitHubActionsWorkflowSchema', () => {
- it('should accept minimal workflow', () => {
- const workflow = {
- name: 'CI',
- path: '.github/workflows/ci.yml',
- };
-
- const result = GitHubActionsWorkflowSchema.parse(workflow);
- expect(result.enabled).toBe(true);
- });
-
- it('should accept all trigger types', () => {
- const triggers = ['push', 'pull_request', 'release', 'schedule', 'workflow_dispatch', 'repository_dispatch'] as const;
-
- triggers.forEach(trigger => {
- const workflow = {
- name: 'Test',
- path: '.github/workflows/test.yml',
- triggers: [trigger],
- };
- expect(() => GitHubActionsWorkflowSchema.parse(workflow)).not.toThrow();
- });
- });
-
- it('should accept workflow with env and secrets', () => {
- const workflow = {
- name: 'Deploy',
- path: '.github/workflows/deploy.yml',
- env: {
- NODE_ENV: 'production',
- API_URL: 'https://api.example.com',
- },
- secrets: ['DEPLOY_TOKEN', 'AWS_SECRET_KEY'],
- };
-
- expect(() => GitHubActionsWorkflowSchema.parse(workflow)).not.toThrow();
- });
-});
-
-describe('GitHubReleaseConfigSchema', () => {
- it('should accept minimal release config', () => {
- const config = {};
-
- const result = GitHubReleaseConfigSchema.parse(config);
- expect(result.tagPattern).toBe('v*');
- expect(result.semanticVersioning).toBe(true);
- expect(result.autoReleaseNotes).toBe(true);
- });
-
- it('should accept full release config', () => {
- const config = {
- tagPattern: 'release/*',
- semanticVersioning: true,
- autoReleaseNotes: true,
- releaseNameTemplate: 'Release {{version}}',
- preReleasePattern: '*-rc*',
- draftByDefault: true,
- };
-
- expect(() => GitHubReleaseConfigSchema.parse(config)).not.toThrow();
- });
-});
-
-describe('GitHubIssueTrackingSchema', () => {
- it('should accept minimal issue tracking config', () => {
- const config = {};
-
- const result = GitHubIssueTrackingSchema.parse(config);
- expect(result.enabled).toBe(true);
- expect(result.autoAssign).toBe(false);
- });
-
- it('should accept auto-close stale issues config', () => {
- const config = {
- autoCloseStale: {
- enabled: true,
- daysBeforeStale: 30,
- daysBeforeClose: 7,
- staleLabel: 'wontfix',
- },
- };
-
- const result = GitHubIssueTrackingSchema.parse(config);
- expect(result.autoCloseStale?.enabled).toBe(true);
- expect(result.autoCloseStale?.daysBeforeStale).toBe(30);
- });
-});
-
-describe('GitHubConnectorSchema', () => {
- describe('Basic Properties', () => {
- it('should accept minimal GitHub connector', () => {
- const connector: GitHubConnector = {
- name: 'github_test',
- label: 'GitHub Test',
- type: 'saas',
- provider: 'github',
- authentication: {
- type: 'oauth2',
- clientId: 'test-client-id',
- clientSecret: 'test-client-secret',
- authorizationUrl: 'https://github.com/login/oauth/authorize',
- tokenUrl: 'https://github.com/login/oauth/access_token',
- grantType: 'authorization_code',
- },
- repositories: [
- {
- owner: 'test-org',
- name: 'test-repo',
- },
- ],
- };
-
- const result = GitHubConnectorSchema.parse(connector);
- expect(result.baseUrl).toBe('https://api.github.com');
- expect(result.enableWebhooks).toBe(true);
- });
-
- it('should enforce snake_case for connector name', () => {
- const validNames = ['github_test', 'github_production', '_internal'];
- validNames.forEach(name => {
- expect(() => GitHubConnectorSchema.parse({
- name,
- label: 'Test',
- type: 'saas',
- provider: 'github',
- authentication: { type: 'oauth2', clientId: 'x', clientSecret: 'y', authorizationUrl: 'https://x.com', tokenUrl: 'https://y.com', grantType: 'authorization_code' },
- repositories: [{ owner: 'x', name: 'y' }],
- })).not.toThrow();
- });
-
- const invalidNames = ['githubTest', 'GitHub-Test', '123github'];
- invalidNames.forEach(name => {
- expect(() => GitHubConnectorSchema.parse({
- name,
- label: 'Test',
- type: 'saas',
- provider: 'github',
- authentication: { type: 'oauth2', clientId: 'x', clientSecret: 'y', authorizationUrl: 'https://x.com', tokenUrl: 'https://y.com', grantType: 'authorization_code' },
- repositories: [{ owner: 'x', name: 'y' }],
- })).toThrow();
- });
- });
-
- it('should accept GitHub Enterprise provider', () => {
- const connector: GitHubConnector = {
- name: 'github_enterprise',
- label: 'GitHub Enterprise',
- type: 'saas',
- provider: 'github_enterprise',
- baseUrl: 'https://github.enterprise.com/api/v3',
- authentication: {
- type: 'oauth2',
- clientId: 'test-client-id',
- clientSecret: 'test-client-secret',
- authorizationUrl: 'https://github.enterprise.com/login/oauth/authorize',
- tokenUrl: 'https://github.enterprise.com/login/oauth/access_token',
- grantType: 'authorization_code',
- },
- repositories: [
- {
- owner: 'enterprise-org',
- name: 'app',
- },
- ],
- };
-
- expect(() => GitHubConnectorSchema.parse(connector)).not.toThrow();
- });
- });
-
- describe('Complete Configuration', () => {
- it('should accept full GitHub connector with all features', () => {
- const connector: GitHubConnector = {
- name: 'github_full',
- label: 'GitHub Full Config',
- type: 'saas',
- provider: 'github',
- baseUrl: 'https://api.github.com',
-
- authentication: {
- type: 'oauth2',
- clientId: '${GITHUB_CLIENT_ID}',
- clientSecret: '${GITHUB_CLIENT_SECRET}',
- authorizationUrl: 'https://github.com/login/oauth/authorize',
- tokenUrl: 'https://github.com/login/oauth/access_token',
- grantType: 'authorization_code',
- scopes: ['repo', 'workflow'],
- },
-
- repositories: [
- {
- owner: 'objectstack-ai',
- name: 'spec',
- defaultBranch: 'main',
- autoMerge: false,
- branchProtection: {
- requiredReviewers: 1,
- requireStatusChecks: true,
- },
- topics: ['objectstack', 'metadata'],
- },
- ],
-
- commitConfig: {
- authorName: 'Bot',
- authorEmail: 'bot@example.com',
- useConventionalCommits: true,
- },
-
- pullRequestConfig: {
- defaultReviewers: ['reviewer'],
- defaultLabels: ['automated'],
- },
-
- workflows: [
- {
- name: 'CI',
- path: '.github/workflows/ci.yml',
- triggers: ['push', 'pull_request'],
- },
- ],
-
- releaseConfig: {
- semanticVersioning: true,
- autoReleaseNotes: true,
- },
-
- issueTracking: {
- enabled: true,
- autoCloseStale: {
- enabled: true,
- daysBeforeStale: 60,
- daysBeforeClose: 7,
- staleLabel: 'stale',
- },
- },
-
- enableWebhooks: true,
- webhookEvents: ['push', 'pull_request', 'release'],
-
- status: 'active',
- enabled: true,
- };
-
- expect(() => GitHubConnectorSchema.parse(connector)).not.toThrow();
- });
- });
-
- describe('Example Configurations', () => {
- it('should accept GitHub.com public connector example', () => {
- expect(() => GitHubConnectorSchema.parse(githubPublicConnectorExample)).not.toThrow();
- });
-
- it('should accept GitHub Enterprise connector example', () => {
- expect(() => GitHubConnectorSchema.parse(githubEnterpriseConnectorExample)).not.toThrow();
- });
- });
-});
diff --git a/packages/spec/src/integration/connector/github.zod.ts b/packages/spec/src/integration/connector/github.zod.ts
deleted file mode 100644
index bc740fd8e9..0000000000
--- a/packages/spec/src/integration/connector/github.zod.ts
+++ /dev/null
@@ -1,530 +0,0 @@
-// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
-
-import { z } from 'zod';
-import { TemplateExpressionInputSchema } from '../../shared/expression.zod';
-import {
- ConnectorSchema,
-} from '../connector.zod';
-
-/**
- * GitHub Connector Protocol
- *
- * Specialized connector for GitHub integration enabling automated
- * version control operations, CI/CD workflows, and release management.
- *
- * Use Cases:
- * - Automated code commits and pull requests
- * - GitHub Actions workflow management
- * - Issue and project tracking
- * - Release and tag management
- * - Repository administration
- *
- * @example
- * ```typescript
- * import { GitHubConnector } from '@objectstack/spec/integration';
- *
- * const githubConnector: GitHubConnector = {
- * name: 'github_enterprise',
- * label: 'GitHub Enterprise',
- * type: 'saas',
- * provider: 'github',
- * baseUrl: 'https://api.github.com',
- * authentication: {
- * type: 'oauth2',
- * clientId: '${GITHUB_CLIENT_ID}',
- * clientSecret: '${GITHUB_CLIENT_SECRET}',
- * authorizationUrl: 'https://github.com/login/oauth/authorize',
- * tokenUrl: 'https://github.com/login/oauth/access_token',
- * grantType: 'authorization_code',
- * scopes: ['repo', 'workflow', 'admin:org'],
- * },
- * repositories: [
- * {
- * owner: 'objectstack-ai',
- * name: 'spec',
- * defaultBranch: 'main',
- * autoMerge: false,
- * },
- * ],
- * };
- * ```
- */
-
-/**
- * GitHub Provider Type
- */
-import { lazySchema } from '../../shared/lazy-schema';
-export const GitHubProviderSchema = lazySchema(() => z.enum([
- 'github', // GitHub.com
- 'github_enterprise', // GitHub Enterprise Server
-]).describe('GitHub provider type'));
-
-export type GitHubProvider = z.infer;
-
-/**
- * GitHub Repository Configuration
- * Defines a repository to integrate with
- */
-export const GitHubRepositorySchema = lazySchema(() => z.object({
- /**
- * Repository owner (organization or user)
- */
- owner: z.string().describe('Repository owner (organization or username)'),
-
- /**
- * Repository name
- */
- name: z.string().describe('Repository name'),
-
- /**
- * Default branch name
- */
- defaultBranch: z.string().optional().default('main').describe('Default branch name'),
-
- /**
- * Enable auto-merge for PRs
- */
- autoMerge: z.boolean().optional().default(false).describe('Enable auto-merge for pull requests'),
-
- /**
- * Branch protection rules
- */
- branchProtection: z.object({
- requiredReviewers: z.number().int().min(0).optional().default(1).describe('Required number of reviewers'),
- requireStatusChecks: z.boolean().optional().default(true).describe('Require status checks to pass'),
- enforceAdmins: z.boolean().optional().default(false).describe('Enforce protections for admins'),
- allowForcePushes: z.boolean().optional().default(false).describe('Allow force pushes'),
- allowDeletions: z.boolean().optional().default(false).describe('Allow branch deletions'),
- }).optional().describe('Branch protection configuration'),
-
- /**
- * Repository topics/tags
- */
- topics: z.array(z.string()).optional().describe('Repository topics'),
-}));
-
-export type GitHubRepository = z.infer;
-
-/**
- * GitHub Commit Configuration
- */
-export const GitHubCommitConfigSchema = lazySchema(() => z.object({
- /**
- * Commit author name
- */
- authorName: z.string().optional().describe('Commit author name'),
-
- /**
- * Commit author email
- */
- authorEmail: z.string().email().optional().describe('Commit author email'),
-
- /**
- * GPG sign commits
- */
- signCommits: z.boolean().optional().default(false).describe('Sign commits with GPG'),
-
- /**
- * Commit message template
- */
- messageTemplate: z.string().optional().describe('Commit message template'),
-
- /**
- * Conventional commits format
- */
- useConventionalCommits: z.boolean().optional().default(true).describe('Use conventional commits format'),
-}));
-
-export type GitHubCommitConfig = z.infer;
-
-/**
- * GitHub Pull Request Configuration
- */
-export const GitHubPullRequestConfigSchema = lazySchema(() => z.object({
- /**
- * Default PR title template
- */
- titleTemplate: TemplateExpressionInputSchema.optional().describe('PR title template — supports {{var}} interpolation'),
-
- /**
- * Default PR body template
- */
- bodyTemplate: TemplateExpressionInputSchema.optional().describe('PR body template — supports {{var}} interpolation'),
-
- /**
- * Default reviewers
- */
- defaultReviewers: z.array(z.string()).optional().describe('Default reviewers (usernames)'),
-
- /**
- * Default assignees
- */
- defaultAssignees: z.array(z.string()).optional().describe('Default assignees (usernames)'),
-
- /**
- * Default labels
- */
- defaultLabels: z.array(z.string()).optional().describe('Default labels'),
-
- /**
- * Enable draft PRs by default
- */
- draftByDefault: z.boolean().optional().default(false).describe('Create draft PRs by default'),
-
- /**
- * Auto-delete head branch after merge
- */
- deleteHeadBranch: z.boolean().optional().default(true).describe('Delete head branch after merge'),
-}));
-
-export type GitHubPullRequestConfig = z.infer;
-
-/**
- * GitHub Actions Workflow Configuration
- */
-export const GitHubActionsWorkflowSchema = lazySchema(() => z.object({
- /**
- * Workflow name
- */
- name: z.string().describe('Workflow name'),
-
- /**
- * Workflow file path
- */
- path: z.string().describe('Workflow file path (e.g., .github/workflows/ci.yml)'),
-
- /**
- * Enable workflow
- */
- enabled: z.boolean().optional().default(true).describe('Enable workflow'),
-
- /**
- * Workflow triggers
- */
- triggers: z.array(z.enum([
- 'push',
- 'pull_request',
- 'release',
- 'schedule',
- 'workflow_dispatch',
- 'repository_dispatch',
- ])).optional().describe('Workflow triggers'),
-
- /**
- * Environment variables
- */
- env: z.record(z.string(), z.string()).optional().describe('Environment variables'),
-
- /**
- * Secrets required
- */
- secrets: z.array(z.string()).optional().describe('Required secrets'),
-}));
-
-export type GitHubActionsWorkflow = z.infer;
-
-/**
- * GitHub Release Configuration
- */
-export const GitHubReleaseConfigSchema = lazySchema(() => z.object({
- /**
- * Tag name pattern
- */
- tagPattern: z.string().optional().default('v*').describe('Tag name pattern (e.g., v*, release/*)'),
-
- /**
- * Use semantic versioning
- */
- semanticVersioning: z.boolean().optional().default(true).describe('Use semantic versioning'),
-
- /**
- * Generate release notes automatically
- */
- autoReleaseNotes: z.boolean().optional().default(true).describe('Generate release notes automatically'),
-
- /**
- * Release name template
- */
- releaseNameTemplate: z.string().optional().describe('Release name template'),
-
- /**
- * Pre-release pattern
- */
- preReleasePattern: z.string().optional().describe('Pre-release pattern (e.g., *-alpha, *-beta)'),
-
- /**
- * Create draft releases
- */
- draftByDefault: z.boolean().optional().default(false).describe('Create draft releases by default'),
-}));
-
-export type GitHubReleaseConfig = z.infer;
-
-/**
- * GitHub Issue Tracking Configuration
- */
-export const GitHubIssueTrackingSchema = lazySchema(() => z.object({
- /**
- * Enable issue tracking
- */
- enabled: z.boolean().optional().default(true).describe('Enable issue tracking'),
-
- /**
- * Default issue labels
- */
- defaultLabels: z.array(z.string()).optional().describe('Default issue labels'),
-
- /**
- * Issue template paths
- */
- templatePaths: z.array(z.string()).optional().describe('Issue template paths'),
-
- /**
- * Auto-assign issues
- */
- autoAssign: z.boolean().optional().default(false).describe('Auto-assign issues'),
-
- /**
- * Auto-close stale issues
- */
- autoCloseStale: z.object({
- enabled: z.boolean().default(false),
- daysBeforeStale: z.number().int().min(1).optional().default(60),
- daysBeforeClose: z.number().int().min(1).optional().default(7),
- staleLabel: z.string().optional().default('stale'),
- }).optional().describe('Auto-close stale issues configuration'),
-}));
-
-export type GitHubIssueTracking = z.infer;
-
-/**
- * GitHub Connector Schema
- * Complete GitHub integration configuration
- */
-export const GitHubConnectorSchema = lazySchema(() => ConnectorSchema.extend({
- type: z.literal('saas'),
-
- /**
- * GitHub provider type
- */
- provider: GitHubProviderSchema.describe('GitHub provider'),
-
- /**
- * GitHub API base URL
- */
- baseUrl: z.string().url().optional().default('https://api.github.com').describe('GitHub API base URL'),
-
- /**
- * Repositories to integrate
- */
- repositories: z.array(GitHubRepositorySchema).describe('Repositories to manage'),
-
- /**
- * Commit configuration
- */
- commitConfig: GitHubCommitConfigSchema.optional().describe('Commit configuration'),
-
- /**
- * Pull request configuration
- */
- pullRequestConfig: GitHubPullRequestConfigSchema.optional().describe('Pull request configuration'),
-
- /**
- * GitHub Actions workflows
- */
- workflows: z.array(GitHubActionsWorkflowSchema).optional().describe('GitHub Actions workflows'),
-
- /**
- * Release configuration
- */
- releaseConfig: GitHubReleaseConfigSchema.optional().describe('Release configuration'),
-
- /**
- * Issue tracking configuration
- */
- issueTracking: GitHubIssueTrackingSchema.optional().describe('Issue tracking configuration'),
-
- /**
- * Enable webhooks
- */
- enableWebhooks: z.boolean().optional().default(true).describe('Enable GitHub webhooks'),
-
- /**
- * Webhook events to subscribe
- */
- webhookEvents: z.array(z.enum([
- 'push',
- 'pull_request',
- 'issues',
- 'issue_comment',
- 'release',
- 'workflow_run',
- 'deployment',
- 'deployment_status',
- 'check_run',
- 'check_suite',
- 'status',
- ])).optional().describe('Webhook events to subscribe to'),
-}));
-
-export type GitHubConnector = z.infer;
-
-// ============================================================================
-// Helper Functions & Examples
-// ============================================================================
-
-/**
- * Example: GitHub.com Connector Configuration
- */
-export const githubPublicConnectorExample = {
- name: 'github_public',
- label: 'GitHub.com',
- type: 'saas',
- provider: 'github',
- baseUrl: 'https://api.github.com',
-
- authentication: {
- type: 'oauth2',
- clientId: '${GITHUB_CLIENT_ID}',
- clientSecret: '${GITHUB_CLIENT_SECRET}',
- authorizationUrl: 'https://github.com/login/oauth/authorize',
- tokenUrl: 'https://github.com/login/oauth/access_token',
- scopes: ['repo', 'workflow', 'write:packages'],
- },
-
- repositories: [
- {
- owner: 'objectstack-ai',
- name: 'spec',
- defaultBranch: 'main',
- autoMerge: false,
- branchProtection: {
- requiredReviewers: 1,
- requireStatusChecks: true,
- enforceAdmins: false,
- allowForcePushes: false,
- allowDeletions: false,
- },
- topics: ['objectstack', 'low-code', 'metadata-driven'],
- },
- ],
-
- commitConfig: {
- authorName: 'ObjectStack Bot',
- authorEmail: 'bot@objectstack.ai',
- signCommits: false,
- useConventionalCommits: true,
- },
-
- pullRequestConfig: {
- titleTemplate: '{{type}}: {{description}}',
- defaultReviewers: ['team-lead'],
- defaultLabels: ['automated', 'ai-generated'],
- draftByDefault: false,
- deleteHeadBranch: true,
- },
-
- workflows: [
- {
- name: 'CI',
- path: '.github/workflows/ci.yml',
- enabled: true,
- triggers: ['push', 'pull_request'],
- },
- {
- name: 'Release',
- path: '.github/workflows/release.yml',
- enabled: true,
- triggers: ['release'],
- },
- ],
-
- releaseConfig: {
- tagPattern: 'v*',
- semanticVersioning: true,
- autoReleaseNotes: true,
- releaseNameTemplate: 'Release {{version}}',
- draftByDefault: false,
- },
-
- issueTracking: {
- enabled: true,
- defaultLabels: ['needs-triage'],
- autoAssign: false,
- autoCloseStale: {
- enabled: true,
- daysBeforeStale: 60,
- daysBeforeClose: 7,
- staleLabel: 'stale',
- },
- },
-
- enableWebhooks: true,
- webhookEvents: ['push', 'pull_request', 'release', 'workflow_run'],
-
- status: 'active',
- enabled: true,
-};
-
-/**
- * Example: GitHub Enterprise Connector Configuration
- */
-export const githubEnterpriseConnectorExample = {
- name: 'github_enterprise',
- label: 'GitHub Enterprise',
- type: 'saas',
- provider: 'github_enterprise',
- baseUrl: 'https://github.enterprise.com/api/v3',
-
- authentication: {
- type: 'oauth2',
- clientId: '${GITHUB_ENTERPRISE_CLIENT_ID}',
- clientSecret: '${GITHUB_ENTERPRISE_CLIENT_SECRET}',
- authorizationUrl: 'https://github.enterprise.com/login/oauth/authorize',
- tokenUrl: 'https://github.enterprise.com/login/oauth/access_token',
- scopes: ['repo', 'admin:org', 'workflow'],
- },
-
- repositories: [
- {
- owner: 'enterprise-org',
- name: 'internal-app',
- defaultBranch: 'develop',
- autoMerge: true,
- branchProtection: {
- requiredReviewers: 2,
- requireStatusChecks: true,
- enforceAdmins: true,
- allowForcePushes: false,
- allowDeletions: false,
- },
- },
- ],
-
- commitConfig: {
- authorName: 'CI Bot',
- authorEmail: 'ci-bot@enterprise.com',
- signCommits: true,
- useConventionalCommits: true,
- },
-
- pullRequestConfig: {
- titleTemplate: '[{{branch}}] {{description}}',
- bodyTemplate: `## Changes\n\n{{changes}}\n\n## Testing\n\n{{testing}}`,
- defaultReviewers: ['tech-lead', 'security-team'],
- defaultLabels: ['automated'],
- draftByDefault: true,
- deleteHeadBranch: true,
- },
-
- releaseConfig: {
- tagPattern: 'release/*',
- semanticVersioning: true,
- autoReleaseNotes: true,
- preReleasePattern: '*-rc*',
- draftByDefault: true,
- },
-
- status: 'active',
- enabled: true,
-};
diff --git a/packages/spec/src/integration/connector/message-queue.test.ts b/packages/spec/src/integration/connector/message-queue.test.ts
deleted file mode 100644
index b3c84ea2d6..0000000000
--- a/packages/spec/src/integration/connector/message-queue.test.ts
+++ /dev/null
@@ -1,258 +0,0 @@
-import { describe, it, expect } from 'vitest';
-import {
- MessageQueueProviderSchema,
- MessageFormatSchema,
- AckModeSchema,
- DeliveryGuaranteeSchema,
- ConsumerConfigSchema,
- ProducerConfigSchema,
- DlqConfigSchema,
- TopicQueueSchema,
- MessageQueueConnectorSchema,
-} from './message-queue.zod';
-
-const baseAuth = { type: 'none' as const };
-
-const minimalTopic = {
- name: 'order_events',
- label: 'Order Events',
- topicName: 'orders',
-};
-
-const minimalConnector = {
- name: 'kafka_main',
- label: 'Kafka Main',
- type: 'message_queue' as const,
- provider: 'kafka' as const,
- authentication: baseAuth,
- brokerConfig: {
- brokers: ['localhost:9092'],
- },
- topics: [minimalTopic],
-};
-
-describe('MessageQueueProviderSchema', () => {
- it('should accept all valid providers', () => {
- const providers = ['rabbitmq', 'kafka', 'redis_pubsub', 'redis_streams', 'aws_sqs', 'aws_sns', 'google_pubsub', 'azure_service_bus', 'azure_event_hubs', 'nats', 'pulsar', 'activemq', 'custom'];
- for (const p of providers) {
- expect(MessageQueueProviderSchema.parse(p)).toBe(p);
- }
- });
-
- it('should reject invalid provider', () => {
- expect(() => MessageQueueProviderSchema.parse('zeromq')).toThrow();
- });
-});
-
-describe('MessageFormatSchema', () => {
- it('should accept valid formats', () => {
- for (const f of ['json', 'xml', 'protobuf', 'avro', 'text', 'binary']) {
- expect(MessageFormatSchema.parse(f)).toBe(f);
- }
- });
-
- it('should reject invalid format', () => {
- expect(() => MessageFormatSchema.parse('yaml')).toThrow();
- });
-});
-
-describe('AckModeSchema', () => {
- it('should accept valid modes', () => {
- for (const m of ['auto', 'manual', 'client']) {
- expect(AckModeSchema.parse(m)).toBe(m);
- }
- });
-
- it('should reject invalid mode', () => {
- expect(() => AckModeSchema.parse('batch')).toThrow();
- });
-});
-
-describe('DeliveryGuaranteeSchema', () => {
- it('should accept valid guarantees', () => {
- for (const g of ['at_most_once', 'at_least_once', 'exactly_once']) {
- expect(DeliveryGuaranteeSchema.parse(g)).toBe(g);
- }
- });
-
- it('should reject invalid guarantee', () => {
- expect(() => DeliveryGuaranteeSchema.parse('best_effort')).toThrow();
- });
-});
-
-describe('ConsumerConfigSchema', () => {
- it('should apply defaults', () => {
- const result = ConsumerConfigSchema.parse({});
- expect(result.enabled).toBe(true);
- expect(result.concurrency).toBe(1);
- expect(result.prefetchCount).toBe(10);
- expect(result.ackMode).toBe('manual');
- expect(result.autoCommit).toBe(false);
- expect(result.autoCommitIntervalMs).toBe(5000);
- expect(result.sessionTimeoutMs).toBe(30000);
- });
-
- it('should accept custom values', () => {
- const result = ConsumerConfigSchema.parse({
- consumerGroup: 'my-group',
- concurrency: 10,
- prefetchCount: 100,
- ackMode: 'auto',
- rebalanceTimeoutMs: 5000,
- });
- expect(result.consumerGroup).toBe('my-group');
- expect(result.concurrency).toBe(10);
- });
-
- it('should reject concurrency out of range', () => {
- expect(() => ConsumerConfigSchema.parse({ concurrency: 0 })).toThrow();
- expect(() => ConsumerConfigSchema.parse({ concurrency: 101 })).toThrow();
- });
-
- it('should reject prefetchCount out of range', () => {
- expect(() => ConsumerConfigSchema.parse({ prefetchCount: 0 })).toThrow();
- expect(() => ConsumerConfigSchema.parse({ prefetchCount: 1001 })).toThrow();
- });
-});
-
-describe('ProducerConfigSchema', () => {
- it('should apply defaults', () => {
- const result = ProducerConfigSchema.parse({});
- expect(result.enabled).toBe(true);
- expect(result.acks).toBe('all');
- expect(result.compressionType).toBe('none');
- expect(result.idempotence).toBe(true);
- expect(result.transactional).toBe(false);
- });
-
- it('should accept custom values', () => {
- const result = ProducerConfigSchema.parse({
- acks: '1',
- compressionType: 'snappy',
- batchSize: 32768,
- lingerMs: 10,
- });
- expect(result.acks).toBe('1');
- expect(result.compressionType).toBe('snappy');
- });
-
- it('should reject invalid acks', () => {
- expect(() => ProducerConfigSchema.parse({ acks: '2' })).toThrow();
- });
-
- it('should reject invalid compressionType', () => {
- expect(() => ProducerConfigSchema.parse({ compressionType: 'brotli' })).toThrow();
- });
-});
-
-describe('DlqConfigSchema', () => {
- it('should accept valid DLQ config', () => {
- const result = DlqConfigSchema.parse({ queueName: 'my-dlq' });
- expect(result.enabled).toBe(false);
- expect(result.maxRetries).toBe(3);
- expect(result.retryDelayMs).toBe(60000);
- });
-
- it('should reject missing queueName', () => {
- expect(() => DlqConfigSchema.parse({})).toThrow();
- });
-
- it('should reject maxRetries out of range', () => {
- expect(() => DlqConfigSchema.parse({ queueName: 'dlq', maxRetries: -1 })).toThrow();
- expect(() => DlqConfigSchema.parse({ queueName: 'dlq', maxRetries: 101 })).toThrow();
- });
-});
-
-describe('TopicQueueSchema', () => {
- it('should accept minimal topic', () => {
- const result = TopicQueueSchema.parse(minimalTopic);
- expect(result.enabled).toBe(true);
- expect(result.mode).toBe('both');
- expect(result.messageFormat).toBe('json');
- });
-
- it('should accept topic with all options', () => {
- const data = {
- ...minimalTopic,
- enabled: false,
- mode: 'consumer',
- messageFormat: 'avro',
- partitions: 10,
- replicationFactor: 3,
- consumerConfig: { consumerGroup: 'grp' },
- producerConfig: { acks: '1' },
- dlqConfig: { queueName: 'dlq' },
- routingKey: 'order.*',
- messageFilter: { headers: { type: 'order' } },
- };
- expect(() => TopicQueueSchema.parse(data)).not.toThrow();
- });
-
- it('should reject non-snake_case name', () => {
- expect(() => TopicQueueSchema.parse({ ...minimalTopic, name: 'OrderEvents' })).toThrow();
- });
-
- it('should reject missing required fields', () => {
- expect(() => TopicQueueSchema.parse({ name: 'topic' })).toThrow();
- });
-});
-
-describe('MessageQueueConnectorSchema', () => {
- it('should accept minimal valid connector', () => {
- expect(() => MessageQueueConnectorSchema.parse(minimalConnector)).not.toThrow();
- });
-
- it('should apply defaults', () => {
- const result = MessageQueueConnectorSchema.parse(minimalConnector);
- expect(result.deliveryGuarantee).toBe('at_least_once');
- expect(result.preserveOrder).toBe(true);
- expect(result.enableMetrics).toBe(true);
- expect(result.enableTracing).toBe(false);
- expect(result.enabled).toBe(true);
- });
-
- it('should accept full connector', () => {
- const full = {
- ...minimalConnector,
- brokerConfig: {
- brokers: ['broker1:9092', 'broker2:9092'],
- clientId: 'my-client',
- connectionTimeoutMs: 5000,
- requestTimeoutMs: 5000,
- },
- deliveryGuarantee: 'exactly_once',
- sslConfig: { enabled: true, rejectUnauthorized: true },
- saslConfig: { mechanism: 'scram-sha-256', username: 'u', password: 'p' },
- schemaRegistry: { url: 'https://registry.example.com' },
- preserveOrder: false,
- enableMetrics: false,
- enableTracing: true,
- };
- expect(() => MessageQueueConnectorSchema.parse(full)).not.toThrow();
- });
-
- it('should reject wrong type literal', () => {
- expect(() => MessageQueueConnectorSchema.parse({ ...minimalConnector, type: 'database' })).toThrow();
- });
-
- it('should reject invalid provider', () => {
- expect(() => MessageQueueConnectorSchema.parse({ ...minimalConnector, provider: 'unknown' })).toThrow();
- });
-
- it('should reject missing brokerConfig', () => {
- const { brokerConfig: _, ...noConfig } = minimalConnector;
- expect(() => MessageQueueConnectorSchema.parse(noConfig)).toThrow();
- });
-
- it('should reject missing topics', () => {
- const { topics: _, ...noTopics } = minimalConnector;
- expect(() => MessageQueueConnectorSchema.parse(noTopics)).toThrow();
- });
-
- it('should reject invalid schemaRegistry url', () => {
- expect(() => MessageQueueConnectorSchema.parse({
- ...minimalConnector,
- schemaRegistry: { url: 'not-a-url' },
- })).toThrow();
- });
-});
diff --git a/packages/spec/src/integration/connector/message-queue.zod.ts b/packages/spec/src/integration/connector/message-queue.zod.ts
deleted file mode 100644
index b285e7fd03..0000000000
--- a/packages/spec/src/integration/connector/message-queue.zod.ts
+++ /dev/null
@@ -1,501 +0,0 @@
-// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
-
-import { z } from 'zod';
-import {
- ConnectorSchema,
-} from '../connector.zod';
-
-/**
- * Message Queue Connector Protocol Template
- *
- * Specialized connector for message queue systems (RabbitMQ, Kafka, SQS, etc.)
- * Extends the base connector with message queue-specific features like topics,
- * consumer groups, and message acknowledgment patterns.
- */
-
-/**
- * Message Queue Provider Types
- */
-import { lazySchema } from '../../shared/lazy-schema';
-export const MessageQueueProviderSchema = lazySchema(() => z.enum([
- 'rabbitmq', // RabbitMQ
- 'kafka', // Apache Kafka
- 'redis_pubsub', // Redis Pub/Sub
- 'redis_streams', // Redis Streams
- 'aws_sqs', // Amazon SQS
- 'aws_sns', // Amazon SNS
- 'google_pubsub', // Google Cloud Pub/Sub
- 'azure_service_bus', // Azure Service Bus
- 'azure_event_hubs', // Azure Event Hubs
- 'nats', // NATS
- 'pulsar', // Apache Pulsar
- 'activemq', // Apache ActiveMQ
- 'custom', // Custom message queue
-]).describe('Message queue provider type'));
-
-export type MessageQueueProvider = z.infer;
-
-/**
- * Message Format
- */
-export const MessageFormatSchema = lazySchema(() => z.enum([
- 'json',
- 'xml',
- 'protobuf',
- 'avro',
- 'text',
- 'binary',
-]).describe('Message format/serialization'));
-
-export type MessageFormat = z.infer;
-
-/**
- * Message Acknowledgment Mode
- */
-export const AckModeSchema = lazySchema(() => z.enum([
- 'auto', // Auto-acknowledge
- 'manual', // Manual acknowledge after processing
- 'client', // Client-controlled acknowledge
-]).describe('Message acknowledgment mode'));
-
-export type AckMode = z.infer;
-
-/**
- * Delivery Guarantee
- */
-export const DeliveryGuaranteeSchema = lazySchema(() => z.enum([
- 'at_most_once', // Fire and forget
- 'at_least_once', // May deliver duplicates
- 'exactly_once', // Guaranteed exactly once delivery
-]).describe('Message delivery guarantee'));
-
-export type DeliveryGuarantee = z.infer;
-
-/**
- * Consumer Configuration
- */
-export const ConsumerConfigSchema = lazySchema(() => z.object({
- enabled: z.boolean().optional().default(true).describe('Enable consumer'),
-
- consumerGroup: z.string().optional().describe('Consumer group ID'),
-
- concurrency: z.number().min(1).max(100).optional().default(1).describe('Number of concurrent consumers'),
-
- prefetchCount: z.number().min(1).max(1000).optional().default(10).describe('Prefetch count'),
-
- ackMode: AckModeSchema.optional().default('manual'),
-
- autoCommit: z.boolean().optional().default(false).describe('Auto-commit offsets'),
-
- autoCommitIntervalMs: z.number().min(100).optional().default(5000).describe('Auto-commit interval in ms'),
-
- sessionTimeoutMs: z.number().min(1000).optional().default(30000).describe('Session timeout in ms'),
-
- rebalanceTimeoutMs: z.number().min(1000).optional().describe('Rebalance timeout in ms'),
-}));
-
-export type ConsumerConfig = z.infer;
-
-/**
- * Producer Configuration
- */
-export const ProducerConfigSchema = lazySchema(() => z.object({
- enabled: z.boolean().optional().default(true).describe('Enable producer'),
-
- acks: z.enum(['0', '1', 'all']).optional().default('all').describe('Acknowledgment level'),
-
- compressionType: z.enum(['none', 'gzip', 'snappy', 'lz4', 'zstd']).optional().default('none').describe('Compression type'),
-
- batchSize: z.number().min(1).optional().default(16384).describe('Batch size in bytes'),
-
- lingerMs: z.number().min(0).optional().default(0).describe('Linger time in ms'),
-
- maxInFlightRequests: z.number().min(1).optional().default(5).describe('Max in-flight requests'),
-
- idempotence: z.boolean().optional().default(true).describe('Enable idempotent producer'),
-
- transactional: z.boolean().optional().default(false).describe('Enable transactional producer'),
-
- transactionTimeoutMs: z.number().min(1000).optional().describe('Transaction timeout in ms'),
-}));
-
-export type ProducerConfig = z.infer;
-
-/**
- * Dead Letter Queue Configuration
- */
-export const DlqConfigSchema = lazySchema(() => z.object({
- enabled: z.boolean().optional().default(false).describe('Enable DLQ'),
-
- queueName: z.string().describe('Dead letter queue/topic name'),
-
- maxRetries: z.number().min(0).max(100).optional().default(3).describe('Max retries before DLQ'),
-
- retryDelayMs: z.number().min(0).optional().default(60000).describe('Retry delay in ms'),
-}));
-
-export type DlqConfig = z.infer;
-
-/**
- * Topic/Queue Configuration
- */
-export const TopicQueueSchema = lazySchema(() => z.object({
- name: z.string().regex(/^[a-z_][a-z0-9_]*$/).describe('Topic/queue identifier in ObjectStack (snake_case)'),
- label: z.string().describe('Display label'),
- topicName: z.string().describe('Actual topic/queue name in message queue system'),
- enabled: z.boolean().optional().default(true).describe('Enable sync for this topic/queue'),
-
- /**
- * Consumer or Producer
- */
- mode: z.enum(['consumer', 'producer', 'both']).optional().default('both').describe('Consumer, producer, or both'),
-
- /**
- * Message format
- */
- messageFormat: MessageFormatSchema.optional().default('json'),
-
- /**
- * Partition/shard configuration
- */
- partitions: z.number().min(1).optional().describe('Number of partitions (for Kafka)'),
-
- /**
- * Replication factor
- */
- replicationFactor: z.number().min(1).optional().describe('Replication factor (for Kafka)'),
-
- /**
- * Consumer configuration
- */
- consumerConfig: ConsumerConfigSchema.optional().describe('Consumer-specific configuration'),
-
- /**
- * Producer configuration
- */
- producerConfig: ProducerConfigSchema.optional().describe('Producer-specific configuration'),
-
- /**
- * Dead letter queue configuration
- */
- dlqConfig: DlqConfigSchema.optional().describe('Dead letter queue configuration'),
-
- /**
- * Message routing key (for RabbitMQ)
- */
- routingKey: z.string().optional().describe('Routing key pattern'),
-
- /**
- * Message filter
- */
- messageFilter: z.object({
- headers: z.record(z.string(), z.string()).optional().describe('Filter by message headers'),
- attributes: z.record(z.string(), z.unknown()).optional().describe('Filter by message attributes'),
- }).optional().describe('Message filter criteria'),
-}));
-
-export type TopicQueue = z.infer;
-
-/**
- * Message Queue Connector Configuration Schema
- */
-export const MessageQueueConnectorSchema = lazySchema(() => ConnectorSchema.extend({
- type: z.literal('message_queue'),
-
- /**
- * Message queue provider
- */
- provider: MessageQueueProviderSchema.describe('Message queue provider type'),
-
- /**
- * Broker configuration
- */
- brokerConfig: z.object({
- brokers: z.array(z.string()).describe('Broker addresses (host:port)'),
- clientId: z.string().optional().describe('Client ID'),
- connectionTimeoutMs: z.number().min(1000).optional().default(30000).describe('Connection timeout in ms'),
- requestTimeoutMs: z.number().min(1000).optional().default(30000).describe('Request timeout in ms'),
- }).describe('Broker connection configuration'),
-
- /**
- * Topics/queues to sync
- */
- topics: z.array(TopicQueueSchema).describe('Topics/queues to sync'),
-
- /**
- * Delivery guarantee
- */
- deliveryGuarantee: DeliveryGuaranteeSchema.optional().default('at_least_once'),
-
- /**
- * SSL/TLS configuration
- */
- sslConfig: z.object({
- enabled: z.boolean().optional().default(false).describe('Enable SSL/TLS'),
- rejectUnauthorized: z.boolean().optional().default(true).describe('Reject unauthorized certificates'),
- ca: z.string().optional().describe('CA certificate'),
- cert: z.string().optional().describe('Client certificate'),
- key: z.string().optional().describe('Client private key'),
- }).optional().describe('SSL/TLS configuration'),
-
- /**
- * SASL authentication (for Kafka)
- */
- saslConfig: z.object({
- mechanism: z.enum(['plain', 'scram-sha-256', 'scram-sha-512', 'aws']).describe('SASL mechanism'),
- username: z.string().optional().describe('SASL username'),
- password: z.string().optional().describe('SASL password'),
- }).optional().describe('SASL authentication configuration'),
-
- /**
- * Schema registry configuration (for Kafka/Avro)
- */
- schemaRegistry: z.object({
- url: z.string().url().describe('Schema registry URL'),
- auth: z.object({
- username: z.string().optional(),
- password: z.string().optional(),
- }).optional(),
- }).optional().describe('Schema registry configuration'),
-
- /**
- * Message ordering
- */
- preserveOrder: z.boolean().optional().default(true).describe('Preserve message ordering'),
-
- /**
- * Enable metrics
- */
- enableMetrics: z.boolean().optional().default(true).describe('Enable message queue metrics'),
-
- /**
- * Enable distributed tracing
- */
- enableTracing: z.boolean().optional().default(false).describe('Enable distributed tracing'),
-}));
-
-export type MessageQueueConnector = z.infer;
-
-// ============================================================================
-// Helper Functions & Examples
-// ============================================================================
-
-/**
- * Example: Apache Kafka Connector Configuration
- */
-export const kafkaConnectorExample = {
- name: 'kafka_production',
- label: 'Production Kafka Cluster',
- type: 'message_queue',
- provider: 'kafka',
- authentication: {
- type: 'none',
- },
- brokerConfig: {
- brokers: ['kafka-1.example.com:9092', 'kafka-2.example.com:9092', 'kafka-3.example.com:9092'],
- clientId: 'objectstack-client',
- connectionTimeoutMs: 30000,
- requestTimeoutMs: 30000,
- },
- topics: [
- {
- name: 'order_events',
- label: 'Order Events',
- topicName: 'orders',
- enabled: true,
- mode: 'consumer',
- messageFormat: 'json',
- partitions: 10,
- replicationFactor: 3,
- consumerConfig: {
- enabled: true,
- consumerGroup: 'objectstack-consumer-group',
- concurrency: 5,
- prefetchCount: 100,
- ackMode: 'manual',
- autoCommit: false,
- sessionTimeoutMs: 30000,
- },
- dlqConfig: {
- enabled: true,
- queueName: 'orders-dlq',
- maxRetries: 3,
- retryDelayMs: 60000,
- },
- },
- {
- name: 'user_activity',
- label: 'User Activity',
- topicName: 'user-activity',
- enabled: true,
- mode: 'producer',
- messageFormat: 'json',
- partitions: 5,
- replicationFactor: 3,
- producerConfig: {
- enabled: true,
- acks: 'all',
- compressionType: 'snappy',
- batchSize: 16384,
- lingerMs: 10,
- maxInFlightRequests: 5,
- idempotence: true,
- },
- },
- ],
- deliveryGuarantee: 'at_least_once',
- saslConfig: {
- mechanism: 'scram-sha-256',
- username: '${KAFKA_USERNAME}',
- password: '${KAFKA_PASSWORD}',
- },
- sslConfig: {
- enabled: true,
- rejectUnauthorized: true,
- },
- preserveOrder: true,
- enableMetrics: true,
- enableTracing: true,
- status: 'active',
- enabled: true,
-};
-
-/**
- * Example: RabbitMQ Connector Configuration
- */
-export const rabbitmqConnectorExample = {
- name: 'rabbitmq_events',
- label: 'RabbitMQ Event Bus',
- type: 'message_queue',
- provider: 'rabbitmq',
- authentication: {
- type: 'basic',
- username: '${RABBITMQ_USERNAME}',
- password: '${RABBITMQ_PASSWORD}',
- },
- brokerConfig: {
- brokers: ['amqp://rabbitmq.example.com:5672'],
- clientId: 'objectstack-rabbitmq-client',
- },
- topics: [
- {
- name: 'notifications',
- label: 'Notifications',
- topicName: 'notifications',
- enabled: true,
- mode: 'both',
- messageFormat: 'json',
- routingKey: 'notification.*',
- consumerConfig: {
- enabled: true,
- prefetchCount: 10,
- ackMode: 'manual',
- },
- producerConfig: {
- enabled: true,
- },
- dlqConfig: {
- enabled: true,
- queueName: 'notifications-dlq',
- maxRetries: 3,
- retryDelayMs: 30000,
- },
- },
- ],
- deliveryGuarantee: 'at_least_once',
- status: 'active',
- enabled: true,
-};
-
-/**
- * Example: AWS SQS Connector Configuration
- */
-export const sqsConnectorExample = {
- name: 'aws_sqs_queue',
- label: 'AWS SQS Queue',
- type: 'message_queue',
- provider: 'aws_sqs',
- authentication: {
- type: 'api_key',
- apiKey: '${AWS_ACCESS_KEY_ID}:${AWS_SECRET_ACCESS_KEY}',
- headerName: 'Authorization',
- },
- brokerConfig: {
- brokers: ['https://sqs.us-east-1.amazonaws.com'],
- },
- topics: [
- {
- name: 'task_queue',
- label: 'Task Queue',
- topicName: 'task-queue',
- enabled: true,
- mode: 'consumer',
- messageFormat: 'json',
- consumerConfig: {
- enabled: true,
- concurrency: 10,
- prefetchCount: 10,
- ackMode: 'manual',
- },
- dlqConfig: {
- enabled: true,
- queueName: 'task-queue-dlq',
- maxRetries: 3,
- retryDelayMs: 120000,
- },
- },
- ],
- deliveryGuarantee: 'at_least_once',
- retryConfig: {
- strategy: 'exponential_backoff',
- maxAttempts: 3,
- initialDelayMs: 1000,
- maxDelayMs: 60000,
- backoffMultiplier: 2,
- },
- status: 'active',
- enabled: true,
-};
-
-/**
- * Example: Google Cloud Pub/Sub Connector Configuration
- */
-export const pubsubConnectorExample = {
- name: 'gcp_pubsub',
- label: 'Google Cloud Pub/Sub',
- type: 'message_queue',
- provider: 'google_pubsub',
- authentication: {
- type: 'oauth2',
- clientId: '${GCP_CLIENT_ID}',
- clientSecret: '${GCP_CLIENT_SECRET}',
- authorizationUrl: 'https://accounts.google.com/o/oauth2/v2/auth',
- tokenUrl: 'https://oauth2.googleapis.com/token',
- grantType: 'client_credentials',
- scopes: ['https://www.googleapis.com/auth/pubsub'],
- },
- brokerConfig: {
- brokers: ['pubsub.googleapis.com'],
- },
- topics: [
- {
- name: 'analytics_events',
- label: 'Analytics Events',
- topicName: 'projects/my-project/topics/analytics-events',
- enabled: true,
- mode: 'both',
- messageFormat: 'json',
- consumerConfig: {
- enabled: true,
- consumerGroup: 'objectstack-subscription',
- concurrency: 5,
- prefetchCount: 100,
- ackMode: 'manual',
- },
- },
- ],
- deliveryGuarantee: 'at_least_once',
- enableMetrics: true,
- status: 'active',
- enabled: true,
-};
diff --git a/packages/spec/src/integration/connector/saas.test.ts b/packages/spec/src/integration/connector/saas.test.ts
deleted file mode 100644
index c0be38fd3e..0000000000
--- a/packages/spec/src/integration/connector/saas.test.ts
+++ /dev/null
@@ -1,164 +0,0 @@
-import { describe, it, expect } from 'vitest';
-import {
- SaasProviderSchema,
- ApiVersionConfigSchema,
- SaasObjectTypeSchema,
- SaasConnectorSchema,
-} from './saas.zod';
-
-const baseAuth = { type: 'none' as const };
-
-const minimalObjectType = {
- name: 'account',
- label: 'Account',
- apiName: 'Account',
-};
-
-const minimalConnector = {
- name: 'sf_prod',
- label: 'Salesforce Prod',
- type: 'saas' as const,
- provider: 'salesforce' as const,
- authentication: baseAuth,
- baseUrl: 'https://api.example.com',
- objectTypes: [minimalObjectType],
-};
-
-describe('SaasProviderSchema', () => {
- it('should accept all valid providers', () => {
- const providers = ['salesforce', 'hubspot', 'stripe', 'shopify', 'zendesk', 'intercom', 'mailchimp', 'slack', 'microsoft_dynamics', 'servicenow', 'netsuite', 'custom'];
- for (const p of providers) {
- expect(SaasProviderSchema.parse(p)).toBe(p);
- }
- });
-
- it('should reject invalid provider', () => {
- expect(() => SaasProviderSchema.parse('quickbooks')).toThrow();
- });
-});
-
-describe('ApiVersionConfigSchema', () => {
- it('should accept valid config', () => {
- const result = ApiVersionConfigSchema.parse({ version: 'v59.0' });
- expect(result.version).toBe('v59.0');
- expect(result.isDefault).toBe(false);
- });
-
- it('should accept full config', () => {
- const data = {
- version: '2023-10-01',
- isDefault: true,
- deprecationDate: '2024-01-01',
- sunsetDate: '2024-06-01',
- };
- const result = ApiVersionConfigSchema.parse(data);
- expect(result.isDefault).toBe(true);
- expect(result.deprecationDate).toBe('2024-01-01');
- });
-
- it('should reject missing version', () => {
- expect(() => ApiVersionConfigSchema.parse({})).toThrow();
- });
-});
-
-describe('SaasObjectTypeSchema', () => {
- it('should accept minimal object type', () => {
- const result = SaasObjectTypeSchema.parse(minimalObjectType);
- expect(result.enabled).toBe(true);
- expect(result.supportsCreate).toBe(true);
- expect(result.supportsUpdate).toBe(true);
- expect(result.supportsDelete).toBe(true);
- });
-
- it('should accept object type with all fields', () => {
- const data = {
- ...minimalObjectType,
- enabled: false,
- supportsCreate: false,
- supportsUpdate: false,
- supportsDelete: false,
- fieldMappings: [{ source: 'Name', target: 'name' }],
- };
- expect(() => SaasObjectTypeSchema.parse(data)).not.toThrow();
- });
-
- it('should reject non-snake_case name', () => {
- expect(() => SaasObjectTypeSchema.parse({ ...minimalObjectType, name: 'Account' })).toThrow();
- });
-
- it('should reject missing required fields', () => {
- expect(() => SaasObjectTypeSchema.parse({ name: 'acct' })).toThrow();
- });
-});
-
-describe('SaasConnectorSchema', () => {
- it('should accept minimal valid connector', () => {
- expect(() => SaasConnectorSchema.parse(minimalConnector)).not.toThrow();
- });
-
- it('should apply defaults', () => {
- const result = SaasConnectorSchema.parse(minimalConnector);
- expect(result.enabled).toBe(true);
- expect(result.status).toBe('inactive');
- });
-
- it('should accept full connector', () => {
- const full = {
- ...minimalConnector,
- apiVersion: { version: 'v59.0', isDefault: true },
- oauthSettings: {
- scopes: ['api', 'refresh_token'],
- refreshTokenUrl: 'https://login.example.com/token',
- revokeTokenUrl: 'https://login.example.com/revoke',
- autoRefresh: true,
- },
- paginationConfig: {
- type: 'cursor',
- defaultPageSize: 50,
- maxPageSize: 500,
- },
- sandboxConfig: {
- enabled: true,
- baseUrl: 'https://sandbox.example.com',
- },
- customHeaders: { 'X-Custom': 'value' },
- };
- expect(() => SaasConnectorSchema.parse(full)).not.toThrow();
- });
-
- it('should reject wrong type literal', () => {
- expect(() => SaasConnectorSchema.parse({ ...minimalConnector, type: 'database' })).toThrow();
- });
-
- it('should reject invalid baseUrl', () => {
- expect(() => SaasConnectorSchema.parse({ ...minimalConnector, baseUrl: 'not-a-url' })).toThrow();
- });
-
- it('should reject invalid provider', () => {
- expect(() => SaasConnectorSchema.parse({ ...minimalConnector, provider: 'unknown' })).toThrow();
- });
-
- it('should reject missing objectTypes', () => {
- const { objectTypes: _, ...noTypes } = minimalConnector;
- expect(() => SaasConnectorSchema.parse(noTypes)).toThrow();
- });
-
- it('should reject missing baseUrl', () => {
- const { baseUrl: _, ...noUrl } = minimalConnector;
- expect(() => SaasConnectorSchema.parse(noUrl)).toThrow();
- });
-
- it('should reject invalid paginationConfig', () => {
- expect(() => SaasConnectorSchema.parse({
- ...minimalConnector,
- paginationConfig: { type: 'cursor', defaultPageSize: 0 },
- })).toThrow();
- });
-
- it('should reject invalid oauthSettings URLs', () => {
- expect(() => SaasConnectorSchema.parse({
- ...minimalConnector,
- oauthSettings: { scopes: ['api'], refreshTokenUrl: 'not-a-url' },
- })).toThrow();
- });
-});
diff --git a/packages/spec/src/integration/connector/saas.zod.ts b/packages/spec/src/integration/connector/saas.zod.ts
deleted file mode 100644
index 2aaac3a5fa..0000000000
--- a/packages/spec/src/integration/connector/saas.zod.ts
+++ /dev/null
@@ -1,252 +0,0 @@
-// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
-
-import { z } from 'zod';
-import {
- ConnectorSchema,
- FieldMappingSchema,
-} from '../connector.zod';
-
-/**
- * SaaS Connector Protocol Template
- *
- * Specialized connector for SaaS applications (Salesforce, HubSpot, Stripe, etc.)
- * Extends the base connector with SaaS-specific features like OAuth flows,
- * object type discovery, and API version management.
- */
-
-/**
- * SaaS Provider Types
- */
-import { lazySchema } from '../../shared/lazy-schema';
-export const SaasProviderSchema = lazySchema(() => z.enum([
- 'salesforce',
- 'hubspot',
- 'stripe',
- 'shopify',
- 'zendesk',
- 'intercom',
- 'mailchimp',
- 'slack',
- 'microsoft_dynamics',
- 'servicenow',
- 'netsuite',
- 'custom',
-]).describe('SaaS provider type'));
-
-export type SaasProvider = z.infer;
-
-/**
- * API Version Configuration
- */
-export const ApiVersionConfigSchema = lazySchema(() => z.object({
- version: z.string().describe('API version (e.g., "v2", "2023-10-01")'),
- isDefault: z.boolean().default(false).describe('Is this the default version'),
- deprecationDate: z.string().optional().describe('API version deprecation date (ISO 8601)'),
- sunsetDate: z.string().optional().describe('API version sunset date (ISO 8601)'),
-}));
-
-export type ApiVersionConfig = z.infer;
-
-/**
- * SaaS Object Type Schema
- * Represents a syncable entity in the SaaS system (e.g., Account, Contact, Deal)
- */
-export const SaasObjectTypeSchema = lazySchema(() => z.object({
- name: z.string().regex(/^[a-z_][a-z0-9_]*$/).describe('Object type name (snake_case)'),
- label: z.string().describe('Display label'),
- apiName: z.string().describe('API name in external system'),
- enabled: z.boolean().default(true).describe('Enable sync for this object'),
- supportsCreate: z.boolean().default(true).describe('Supports record creation'),
- supportsUpdate: z.boolean().default(true).describe('Supports record updates'),
- supportsDelete: z.boolean().default(true).describe('Supports record deletion'),
- fieldMappings: z.array(FieldMappingSchema).optional().describe('Object-specific field mappings'),
-}));
-
-export type SaasObjectType = z.infer;
-
-/**
- * SaaS Connector Configuration Schema
- */
-export const SaasConnectorSchema = lazySchema(() => ConnectorSchema.extend({
- type: z.literal('saas'),
-
- /**
- * SaaS provider
- */
- provider: SaasProviderSchema.describe('SaaS provider type'),
-
- /**
- * Base URL for API requests
- */
- baseUrl: z.string().url().describe('API base URL'),
-
- /**
- * API version configuration
- */
- apiVersion: ApiVersionConfigSchema.optional().describe('API version configuration'),
-
- /**
- * Supported object types to sync
- */
- objectTypes: z.array(SaasObjectTypeSchema).describe('Syncable object types'),
-
- /**
- * OAuth-specific settings
- */
- oauthSettings: z.object({
- scopes: z.array(z.string()).describe('Required OAuth scopes'),
- refreshTokenUrl: z.string().url().optional().describe('Token refresh endpoint'),
- revokeTokenUrl: z.string().url().optional().describe('Token revocation endpoint'),
- autoRefresh: z.boolean().default(true).describe('Automatically refresh expired tokens'),
- }).optional().describe('OAuth-specific configuration'),
-
- /**
- * Pagination settings
- */
- paginationConfig: z.object({
- type: z.enum(['cursor', 'offset', 'page']).default('cursor').describe('Pagination type'),
- defaultPageSize: z.number().min(1).max(1000).default(100).describe('Default page size'),
- maxPageSize: z.number().min(1).max(10000).default(1000).describe('Maximum page size'),
- }).optional().describe('Pagination configuration'),
-
- /**
- * Sandbox/test environment settings
- */
- sandboxConfig: z.object({
- enabled: z.boolean().default(false).describe('Use sandbox environment'),
- baseUrl: z.string().url().optional().describe('Sandbox API base URL'),
- }).optional().describe('Sandbox environment configuration'),
-
- /**
- * Custom request headers
- */
- customHeaders: z.record(z.string(), z.string()).optional().describe('Custom HTTP headers for all requests'),
-}));
-
-export type SaasConnector = z.infer;
-export type SaaSConnector = SaasConnector; // Alias for alternative capitalization
-
-// ============================================================================
-// Helper Functions & Examples
-// ============================================================================
-
-/**
- * Example: Salesforce Connector Configuration
- */
-export const salesforceConnectorExample = {
- name: 'salesforce_production',
- label: 'Salesforce Production',
- type: 'saas',
- provider: 'salesforce',
- baseUrl: 'https://example.my.salesforce.com',
- apiVersion: {
- version: 'v59.0',
- isDefault: true,
- },
- authentication: {
- type: 'oauth2',
- clientId: '${SALESFORCE_CLIENT_ID}',
- clientSecret: '${SALESFORCE_CLIENT_SECRET}',
- authorizationUrl: 'https://login.salesforce.com/services/oauth2/authorize',
- tokenUrl: 'https://login.salesforce.com/services/oauth2/token',
- grantType: 'authorization_code',
- scopes: ['api', 'refresh_token', 'offline_access'],
- },
- objectTypes: [
- {
- name: 'account',
- label: 'Account',
- apiName: 'Account',
- enabled: true,
- supportsCreate: true,
- supportsUpdate: true,
- supportsDelete: true,
- },
- {
- name: 'contact',
- label: 'Contact',
- apiName: 'Contact',
- enabled: true,
- supportsCreate: true,
- supportsUpdate: true,
- supportsDelete: true,
- },
- ],
- syncConfig: {
- strategy: 'incremental',
- direction: 'bidirectional',
- schedule: '0 */6 * * *', // Every 6 hours
- realtimeSync: true,
- conflictResolution: 'latest_wins',
- batchSize: 200,
- deleteMode: 'soft_delete',
- },
- rateLimitConfig: {
- strategy: 'token_bucket',
- maxRequests: 100,
- windowSeconds: 20,
- respectUpstreamLimits: true,
- },
- retryConfig: {
- strategy: 'exponential_backoff',
- maxAttempts: 3,
- initialDelayMs: 1000,
- maxDelayMs: 30000,
- backoffMultiplier: 2,
- retryableStatusCodes: [408, 429, 500, 502, 503, 504],
- retryOnNetworkError: true,
- jitter: true,
- },
- status: 'active',
- enabled: true,
-};
-
-/**
- * Example: HubSpot Connector Configuration
- */
-export const hubspotConnectorExample = {
- name: 'hubspot_crm',
- label: 'HubSpot CRM',
- type: 'saas',
- provider: 'hubspot',
- baseUrl: 'https://api.hubapi.com',
- authentication: {
- type: 'api_key',
- apiKey: '${HUBSPOT_API_KEY}',
- headerName: 'Authorization',
- },
- objectTypes: [
- {
- name: 'company',
- label: 'Company',
- apiName: 'companies',
- enabled: true,
- supportsCreate: true,
- supportsUpdate: true,
- supportsDelete: true,
- },
- {
- name: 'deal',
- label: 'Deal',
- apiName: 'deals',
- enabled: true,
- supportsCreate: true,
- supportsUpdate: true,
- supportsDelete: true,
- },
- ],
- syncConfig: {
- strategy: 'incremental',
- direction: 'import',
- schedule: '0 */4 * * *', // Every 4 hours
- conflictResolution: 'source_wins',
- batchSize: 100,
- },
- rateLimitConfig: {
- strategy: 'token_bucket',
- maxRequests: 100,
- windowSeconds: 10,
- },
- status: 'active',
- enabled: true,
-};
diff --git a/packages/spec/src/integration/connector/vercel.test.ts b/packages/spec/src/integration/connector/vercel.test.ts
deleted file mode 100644
index 43bc7b7bd2..0000000000
--- a/packages/spec/src/integration/connector/vercel.test.ts
+++ /dev/null
@@ -1,416 +0,0 @@
-import { describe, it, expect } from 'vitest';
-import {
- VercelConnectorSchema,
- VercelProjectSchema,
- GitRepositoryConfigSchema,
- BuildConfigSchema,
- DeploymentConfigSchema,
- DomainConfigSchema,
- EnvironmentVariablesSchema,
- EdgeFunctionConfigSchema,
- vercelNextJsConnectorExample,
- vercelStaticSiteConnectorExample,
- type VercelConnector,
-} from './vercel.zod';
-
-describe('GitRepositoryConfigSchema', () => {
- it('should accept minimal git repo config', () => {
- const config = {
- type: 'github' as const,
- repo: 'owner/repo',
- };
-
- const result = GitRepositoryConfigSchema.parse(config);
- expect(result.productionBranch).toBe('main');
- expect(result.autoDeployProduction).toBe(true);
- expect(result.autoDeployPreview).toBe(true);
- });
-
- it('should accept all git provider types', () => {
- const providers = ['github', 'gitlab', 'bitbucket'] as const;
-
- providers.forEach(type => {
- const config = { type, repo: 'owner/repo' };
- expect(() => GitRepositoryConfigSchema.parse(config)).not.toThrow();
- });
- });
-});
-
-describe('BuildConfigSchema', () => {
- it('should accept empty build config', () => {
- const config = {};
- expect(() => BuildConfigSchema.parse(config)).not.toThrow();
- });
-
- it('should accept full build config', () => {
- const config = {
- buildCommand: 'npm run build',
- outputDirectory: '.next',
- installCommand: 'npm ci',
- devCommand: 'npm run dev',
- nodeVersion: '20.x',
- env: {
- NODE_ENV: 'production',
- API_URL: 'https://api.example.com',
- },
- };
-
- expect(() => BuildConfigSchema.parse(config)).not.toThrow();
- });
-});
-
-describe('DeploymentConfigSchema', () => {
- it('should accept minimal deployment config', () => {
- const config = {};
-
- const result = DeploymentConfigSchema.parse(config);
- expect(result.autoDeployment).toBe(true);
- expect(result.enablePreview).toBe(true);
- expect(result.previewComments).toBe(true);
- });
-
- it('should accept deployment with regions', () => {
- const config = {
- regions: ['iad1', 'sfo1', 'fra1'],
- };
-
- expect(() => DeploymentConfigSchema.parse(config)).not.toThrow();
- });
-
- it('should accept deployment with deploy hooks', () => {
- const config = {
- deployHooks: [
- {
- name: 'main-deploy',
- url: 'https://api.vercel.com/v1/integrations/deploy/xxx',
- branch: 'main',
- },
- ],
- };
-
- expect(() => DeploymentConfigSchema.parse(config)).not.toThrow();
- });
-});
-
-describe('DomainConfigSchema', () => {
- it('should accept minimal domain config', () => {
- const config = {
- domain: 'app.example.com',
- };
-
- const result = DomainConfigSchema.parse(config);
- expect(result.httpsRedirect).toBe(true);
- });
-
- it('should accept domain with custom SSL', () => {
- const config = {
- domain: 'secure.example.com',
- customCertificate: {
- cert: '-----BEGIN CERTIFICATE-----',
- key: '-----BEGIN PRIVATE KEY-----',
- ca: '-----BEGIN CERTIFICATE-----',
- },
- };
-
- expect(() => DomainConfigSchema.parse(config)).not.toThrow();
- });
-});
-
-describe('EnvironmentVariablesSchema', () => {
- it('should accept environment variable', () => {
- const envVar = {
- key: 'API_KEY',
- value: 'secret-value',
- target: ['production'] as const,
- };
-
- const result = EnvironmentVariablesSchema.parse(envVar);
- expect(result.isSecret).toBe(false);
- });
-
- it('should accept secret environment variable', () => {
- const envVar = {
- key: 'DATABASE_URL',
- value: 'postgresql://...',
- target: ['production', 'preview'] as const,
- isSecret: true,
- };
-
- expect(() => EnvironmentVariablesSchema.parse(envVar)).not.toThrow();
- });
-
- it('should accept all target environments', () => {
- const targets = ['production', 'preview', 'development'] as const;
-
- targets.forEach(target => {
- const envVar = {
- key: 'TEST',
- value: 'value',
- target: [target],
- };
- expect(() => EnvironmentVariablesSchema.parse(envVar)).not.toThrow();
- });
- });
-});
-
-describe('EdgeFunctionConfigSchema', () => {
- it('should accept minimal edge function', () => {
- const func = {
- name: 'api-handler',
- path: '/api/*',
- };
-
- const result = EdgeFunctionConfigSchema.parse(func);
- expect(result.memoryLimit).toBe(1024);
- expect(result.timeout).toBe(10);
- });
-
- it('should accept edge function with custom limits', () => {
- const func = {
- name: 'heavy-processor',
- path: '/api/process',
- regions: ['iad1', 'sfo1'],
- memoryLimit: 3008,
- timeout: 60,
- };
-
- expect(() => EdgeFunctionConfigSchema.parse(func)).not.toThrow();
- });
-
- it('should enforce memory limits', () => {
- expect(() => EdgeFunctionConfigSchema.parse({
- name: 'test',
- path: '/test',
- memoryLimit: 100, // Too low
- })).toThrow();
-
- expect(() => EdgeFunctionConfigSchema.parse({
- name: 'test',
- path: '/test',
- memoryLimit: 5000, // Too high
- })).toThrow();
- });
-
- it('should enforce timeout limits', () => {
- expect(() => EdgeFunctionConfigSchema.parse({
- name: 'test',
- path: '/test',
- timeout: 0, // Too low
- })).toThrow();
-
- expect(() => EdgeFunctionConfigSchema.parse({
- name: 'test',
- path: '/test',
- timeout: 400, // Too high
- })).toThrow();
- });
-});
-
-describe('VercelProjectSchema', () => {
- it('should accept minimal project', () => {
- const project = {
- name: 'my-app',
- };
-
- expect(() => VercelProjectSchema.parse(project)).not.toThrow();
- });
-
- it('should accept all framework types', () => {
- const frameworks = ['nextjs', 'react', 'vue', 'nuxtjs', 'gatsby', 'remix', 'astro', 'sveltekit', 'solid', 'angular', 'static', 'other'] as const;
-
- frameworks.forEach(framework => {
- const project = {
- name: 'test-app',
- framework,
- };
- expect(() => VercelProjectSchema.parse(project)).not.toThrow();
- });
- });
-
- it('should accept full project configuration', () => {
- const project = {
- name: 'full-app',
- framework: 'nextjs' as const,
- gitRepository: {
- type: 'github' as const,
- repo: 'owner/repo',
- productionBranch: 'main',
- },
- buildConfig: {
- buildCommand: 'npm run build',
- outputDirectory: '.next',
- },
- deploymentConfig: {
- regions: ['iad1', 'sfo1'],
- enablePreview: true,
- },
- domains: [
- { domain: 'app.example.com' },
- ],
- environmentVariables: [
- {
- key: 'API_KEY',
- value: 'test',
- target: ['production'] as const,
- },
- ],
- edgeFunctions: [
- {
- name: 'api',
- path: '/api/*',
- },
- ],
- rootDirectory: 'apps/web',
- };
-
- expect(() => VercelProjectSchema.parse(project)).not.toThrow();
- });
-});
-
-describe('VercelConnectorSchema', () => {
- describe('Basic Properties', () => {
- it('should accept minimal Vercel connector', () => {
- const connector: VercelConnector = {
- name: 'vercel_test',
- label: 'Vercel Test',
- type: 'saas',
- provider: 'vercel',
- authentication: {
- type: 'bearer',
- token: 'test-token',
- },
- projects: [
- {
- name: 'test-project',
- },
- ],
- };
-
- const result = VercelConnectorSchema.parse(connector);
- expect(result.baseUrl).toBe('https://api.vercel.com');
- expect(result.enableWebhooks).toBe(true);
- });
-
- it('should enforce snake_case for connector name', () => {
- const validNames = ['vercel_test', 'vercel_production', '_internal'];
- validNames.forEach(name => {
- expect(() => VercelConnectorSchema.parse({
- name,
- label: 'Test',
- type: 'saas',
- provider: 'vercel',
- authentication: { type: 'bearer', token: 'x' },
- projects: [{ name: 'test' }],
- })).not.toThrow();
- });
-
- const invalidNames = ['vercelTest', 'Vercel-Test', '123vercel'];
- invalidNames.forEach(name => {
- expect(() => VercelConnectorSchema.parse({
- name,
- label: 'Test',
- type: 'saas',
- provider: 'vercel',
- authentication: { type: 'bearer', token: 'x' },
- projects: [{ name: 'test' }],
- })).toThrow();
- });
- });
- });
-
- describe('Team Configuration', () => {
- it('should accept team configuration', () => {
- const connector: VercelConnector = {
- name: 'vercel_team',
- label: 'Vercel Team',
- type: 'saas',
- provider: 'vercel',
- authentication: {
- type: 'bearer',
- token: 'test-token',
- },
- team: {
- teamId: 'team_xxx',
- teamName: 'My Team',
- },
- projects: [
- {
- name: 'team-project',
- },
- ],
- };
-
- expect(() => VercelConnectorSchema.parse(connector)).not.toThrow();
- });
- });
-
- describe('Monitoring Configuration', () => {
- it('should accept monitoring configuration', () => {
- const connector: VercelConnector = {
- name: 'vercel_monitored',
- label: 'Vercel Monitored',
- type: 'saas',
- provider: 'vercel',
- authentication: {
- type: 'bearer',
- token: 'test-token',
- },
- projects: [{ name: 'test' }],
- monitoring: {
- enableWebAnalytics: true,
- enableSpeedInsights: true,
- logDrains: [
- {
- name: 'datadog',
- url: 'https://logs.datadoghq.com',
- headers: { 'DD-API-KEY': 'xxx' },
- sources: ['lambda', 'edge'],
- },
- ],
- },
- };
-
- expect(() => VercelConnectorSchema.parse(connector)).not.toThrow();
- });
- });
-
- describe('Webhook Configuration', () => {
- it('should accept webhook events', () => {
- const events = [
- 'deployment.created',
- 'deployment.succeeded',
- 'deployment.failed',
- 'deployment.ready',
- 'deployment.error',
- 'deployment.canceled',
- 'deployment-checks-completed',
- 'deployment-prepared',
- 'project.created',
- 'project.removed',
- ] as const;
-
- const connector: VercelConnector = {
- name: 'vercel_webhooks',
- label: 'Vercel Webhooks',
- type: 'saas',
- provider: 'vercel',
- authentication: { type: 'bearer', token: 'x' },
- projects: [{ name: 'test' }],
- enableWebhooks: true,
- webhookEvents: [...events],
- };
-
- expect(() => VercelConnectorSchema.parse(connector)).not.toThrow();
- });
- });
-
- describe('Example Configurations', () => {
- it('should accept Next.js connector example', () => {
- expect(() => VercelConnectorSchema.parse(vercelNextJsConnectorExample)).not.toThrow();
- });
-
- it('should accept static site connector example', () => {
- expect(() => VercelConnectorSchema.parse(vercelStaticSiteConnectorExample)).not.toThrow();
- });
- });
-});
diff --git a/packages/spec/src/integration/connector/vercel.zod.ts b/packages/spec/src/integration/connector/vercel.zod.ts
deleted file mode 100644
index 7b0c80133b..0000000000
--- a/packages/spec/src/integration/connector/vercel.zod.ts
+++ /dev/null
@@ -1,645 +0,0 @@
-// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
-
-import { z } from 'zod';
-import {
- ConnectorSchema,
-} from '../connector.zod';
-
-/**
- * Vercel Connector Protocol
- *
- * Specialized connector for Vercel deployment platform enabling automated
- * deployments, preview environments, and production releases.
- *
- * Use Cases:
- * - Automated deployments from Git
- * - Preview deployments for pull requests
- * - Production releases
- * - Environment variable management
- * - Domain and SSL configuration
- * - Edge function deployment
- *
- * @example
- * ```typescript
- * import { VercelConnector } from '@objectstack/spec/integration';
- *
- * const vercelConnector: VercelConnector = {
- * name: 'vercel_production',
- * label: 'Vercel Production',
- * type: 'saas',
- * provider: 'vercel',
- * baseUrl: 'https://api.vercel.com',
- * authentication: {
- * type: 'bearer',
- * token: '${VERCEL_TOKEN}',
- * },
- * projects: [
- * {
- * name: 'objectstack-app',
- * framework: 'nextjs',
- * gitRepository: {
- * type: 'github',
- * repo: 'objectstack-ai/app',
- * },
- * },
- * ],
- * };
- * ```
- */
-
-/**
- * Vercel Provider Type
- */
-import { lazySchema } from '../../shared/lazy-schema';
-export const VercelProviderSchema = lazySchema(() => z.enum([
- 'vercel',
-]).describe('Vercel provider type'));
-
-export type VercelProvider = z.infer;
-
-/**
- * Vercel Framework Types
- */
-export const VercelFrameworkSchema = lazySchema(() => z.enum([
- 'nextjs',
- 'react',
- 'vue',
- 'nuxtjs',
- 'gatsby',
- 'remix',
- 'astro',
- 'sveltekit',
- 'solid',
- 'angular',
- 'static',
- 'other',
-]).describe('Frontend framework'));
-
-export type VercelFramework = z.infer;
-
-/**
- * Git Repository Configuration
- */
-export const GitRepositoryConfigSchema = lazySchema(() => z.object({
- /**
- * Git provider type
- */
- type: z.enum(['github', 'gitlab', 'bitbucket']).describe('Git provider'),
-
- /**
- * Repository identifier (owner/repo)
- */
- repo: z.string().describe('Repository identifier (e.g., owner/repo)'),
-
- /**
- * Production branch
- */
- productionBranch: z.string().optional().default('main').describe('Production branch name'),
-
- /**
- * Auto-deploy production branch
- */
- autoDeployProduction: z.boolean().optional().default(true).describe('Auto-deploy production branch'),
-
- /**
- * Auto-deploy preview branches
- */
- autoDeployPreview: z.boolean().optional().default(true).describe('Auto-deploy preview branches'),
-}));
-
-export type GitRepositoryConfig = z.infer;
-
-/**
- * Build Configuration
- */
-export const BuildConfigSchema = lazySchema(() => z.object({
- /**
- * Build command
- */
- buildCommand: z.string().optional().describe('Build command (e.g., npm run build)'),
-
- /**
- * Output directory
- */
- outputDirectory: z.string().optional().describe('Output directory (e.g., .next, dist)'),
-
- /**
- * Install command
- */
- installCommand: z.string().optional().describe('Install command (e.g., npm install, pnpm install)'),
-
- /**
- * Development command
- */
- devCommand: z.string().optional().describe('Development command (e.g., npm run dev)'),
-
- /**
- * Node.js version
- */
- nodeVersion: z.string().optional().describe('Node.js version (e.g., 18.x, 20.x)'),
-
- /**
- * Environment variables
- */
- env: z.record(z.string(), z.string()).optional().describe('Build environment variables'),
-}));
-
-export type BuildConfig = z.infer;
-
-/**
- * Deployment Configuration
- */
-export const DeploymentConfigSchema = lazySchema(() => z.object({
- /**
- * Enable automatic deployments
- */
- autoDeployment: z.boolean().optional().default(true).describe('Enable automatic deployments'),
-
- /**
- * Deployment regions
- */
- regions: z.array(z.enum([
- 'iad1', // US East (Washington, D.C.)
- 'sfo1', // US West (San Francisco)
- 'gru1', // South America (São Paulo)
- 'lhr1', // Europe West (London)
- 'fra1', // Europe Central (Frankfurt)
- 'sin1', // Asia (Singapore)
- 'syd1', // Australia (Sydney)
- 'hnd1', // Asia (Tokyo)
- 'icn1', // Asia (Seoul)
- ])).optional().describe('Deployment regions'),
-
- /**
- * Enable preview deployments
- */
- enablePreview: z.boolean().optional().default(true).describe('Enable preview deployments'),
-
- /**
- * Preview deployment comments on PRs
- */
- previewComments: z.boolean().optional().default(true).describe('Post preview URLs in PR comments'),
-
- /**
- * Production deployment protection
- */
- productionProtection: z.boolean().optional().default(true).describe('Require approval for production deployments'),
-
- /**
- * Deploy hooks
- */
- deployHooks: z.array(z.object({
- name: z.string().describe('Hook name'),
- url: z.string().url().describe('Deploy hook URL'),
- branch: z.string().optional().describe('Target branch'),
- })).optional().describe('Deploy hooks'),
-}));
-
-export type DeploymentConfig = z.infer;
-
-/**
- * Domain Configuration
- */
-export const DomainConfigSchema = lazySchema(() => z.object({
- /**
- * Domain name
- */
- domain: z.string().describe('Domain name (e.g., app.example.com)'),
-
- /**
- * Enable HTTPS redirect
- */
- httpsRedirect: z.boolean().optional().default(true).describe('Redirect HTTP to HTTPS'),
-
- /**
- * Custom SSL certificate
- */
- customCertificate: z.object({
- cert: z.string().describe('SSL certificate'),
- key: z.string().describe('Private key'),
- ca: z.string().optional().describe('Certificate authority'),
- }).optional().describe('Custom SSL certificate'),
-
- /**
- * Git branch for this domain
- */
- gitBranch: z.string().optional().describe('Git branch to deploy to this domain'),
-}));
-
-export type DomainConfig = z.infer;
-
-/**
- * Environment Variables Configuration
- */
-export const EnvironmentVariablesSchema = lazySchema(() => z.object({
- /**
- * Variable name
- */
- key: z.string().describe('Environment variable name'),
-
- /**
- * Variable value
- */
- value: z.string().describe('Environment variable value'),
-
- /**
- * Target environments
- */
- target: z.array(z.enum(['production', 'preview', 'development'])).describe('Target environments'),
-
- /**
- * Is secret (encrypted)
- */
- isSecret: z.boolean().optional().default(false).describe('Encrypt this variable'),
-
- /**
- * Git branch (for preview/development)
- */
- gitBranch: z.string().optional().describe('Specific git branch'),
-}));
-
-export type EnvironmentVariables = z.infer;
-
-/**
- * Edge Function Configuration
- */
-export const EdgeFunctionConfigSchema = lazySchema(() => z.object({
- /**
- * Function name
- */
- name: z.string().describe('Edge function name'),
-
- /**
- * Function path
- */
- path: z.string().describe('Function path (e.g., /api/*)'),
-
- /**
- * Regions to deploy
- */
- regions: z.array(z.string()).optional().describe('Specific regions for this function'),
-
- /**
- * Memory limit (MB)
- */
- memoryLimit: z.number().int().min(128).max(3008).optional().default(1024).describe('Memory limit in MB'),
-
- /**
- * Timeout (seconds)
- */
- timeout: z.number().int().min(1).max(300).optional().default(10).describe('Timeout in seconds'),
-}));
-
-export type EdgeFunctionConfig = z.infer;
-
-/**
- * Vercel Project Configuration
- */
-export const VercelProjectSchema = lazySchema(() => z.object({
- /**
- * Project name
- */
- name: z.string().describe('Vercel project name'),
-
- /**
- * Framework
- */
- framework: VercelFrameworkSchema.optional().describe('Frontend framework'),
-
- /**
- * Git repository
- */
- gitRepository: GitRepositoryConfigSchema.optional().describe('Git repository configuration'),
-
- /**
- * Build configuration
- */
- buildConfig: BuildConfigSchema.optional().describe('Build configuration'),
-
- /**
- * Deployment configuration
- */
- deploymentConfig: DeploymentConfigSchema.optional().describe('Deployment configuration'),
-
- /**
- * Custom domains
- */
- domains: z.array(DomainConfigSchema).optional().describe('Custom domains'),
-
- /**
- * Environment variables
- */
- environmentVariables: z.array(EnvironmentVariablesSchema).optional().describe('Environment variables'),
-
- /**
- * Edge functions
- */
- edgeFunctions: z.array(EdgeFunctionConfigSchema).optional().describe('Edge functions'),
-
- /**
- * Root directory
- */
- rootDirectory: z.string().optional().describe('Root directory (for monorepos)'),
-}));
-
-export type VercelProject = z.infer;
-
-/**
- * Vercel Monitoring Configuration
- */
-export const VercelMonitoringSchema = lazySchema(() => z.object({
- /**
- * Enable Web Analytics
- */
- enableWebAnalytics: z.boolean().optional().default(false).describe('Enable Vercel Web Analytics'),
-
- /**
- * Enable Speed Insights
- */
- enableSpeedInsights: z.boolean().optional().default(false).describe('Enable Vercel Speed Insights'),
-
- /**
- * Enable Log Drains
- */
- logDrains: z.array(z.object({
- name: z.string().describe('Log drain name'),
- url: z.string().url().describe('Log drain URL'),
- headers: z.record(z.string(), z.string()).optional().describe('Custom headers'),
- sources: z.array(z.enum(['static', 'lambda', 'edge'])).optional().describe('Log sources'),
- })).optional().describe('Log drains configuration'),
-}));
-
-export type VercelMonitoring = z.infer;
-
-/**
- * Vercel Team Configuration
- */
-export const VercelTeamSchema = lazySchema(() => z.object({
- /**
- * Team ID or slug
- */
- teamId: z.string().optional().describe('Team ID or slug'),
-
- /**
- * Team name
- */
- teamName: z.string().optional().describe('Team name'),
-}));
-
-export type VercelTeam = z.infer;
-
-/**
- * Vercel Connector Schema
- * Complete Vercel integration configuration
- */
-export const VercelConnectorSchema = lazySchema(() => ConnectorSchema.extend({
- type: z.literal('saas'),
-
- /**
- * Vercel provider
- */
- provider: VercelProviderSchema.describe('Vercel provider'),
-
- /**
- * Vercel API base URL
- */
- baseUrl: z.string().url().optional().default('https://api.vercel.com').describe('Vercel API base URL'),
-
- /**
- * Team configuration
- */
- team: VercelTeamSchema.optional().describe('Vercel team configuration'),
-
- /**
- * Projects to manage
- */
- projects: z.array(VercelProjectSchema).describe('Vercel projects'),
-
- /**
- * Monitoring configuration
- */
- monitoring: VercelMonitoringSchema.optional().describe('Monitoring configuration'),
-
- /**
- * Enable webhooks
- */
- enableWebhooks: z.boolean().optional().default(true).describe('Enable Vercel webhooks'),
-
- /**
- * Webhook events to subscribe
- */
- webhookEvents: z.array(z.enum([
- 'deployment.created',
- 'deployment.succeeded',
- 'deployment.failed',
- 'deployment.ready',
- 'deployment.error',
- 'deployment.canceled',
- 'deployment-checks-completed',
- 'deployment-prepared',
- 'project.created',
- 'project.removed',
- ])).optional().describe('Webhook events to subscribe to'),
-}));
-
-export type VercelConnector = z.infer;
-
-// ============================================================================
-// Helper Functions & Examples
-// ============================================================================
-
-/**
- * Example: Vercel Next.js Project Configuration
- */
-export const vercelNextJsConnectorExample = {
- name: 'vercel_production',
- label: 'Vercel Production',
- type: 'saas',
- provider: 'vercel',
- baseUrl: 'https://api.vercel.com',
-
- authentication: {
- type: 'bearer',
- token: '${VERCEL_TOKEN}',
- },
-
- projects: [
- {
- name: 'objectstack-app',
- framework: 'nextjs',
-
- gitRepository: {
- type: 'github',
- repo: 'objectstack-ai/app',
- productionBranch: 'main',
- autoDeployProduction: true,
- autoDeployPreview: true,
- },
-
- buildConfig: {
- buildCommand: 'npm run build',
- outputDirectory: '.next',
- installCommand: 'npm ci',
- devCommand: 'npm run dev',
- nodeVersion: '20.x',
- env: {
- NEXT_PUBLIC_API_URL: 'https://api.objectstack.ai',
- },
- },
-
- deploymentConfig: {
- autoDeployment: true,
- regions: ['iad1', 'sfo1', 'fra1'],
- enablePreview: true,
- previewComments: true,
- productionProtection: true,
- },
-
- domains: [
- {
- domain: 'app.objectstack.ai',
- httpsRedirect: true,
- gitBranch: 'main',
- },
- {
- domain: 'staging.objectstack.ai',
- httpsRedirect: true,
- gitBranch: 'develop',
- },
- ],
-
- environmentVariables: [
- {
- key: 'DATABASE_URL',
- value: '${DATABASE_URL}',
- target: ['production', 'preview'],
- isSecret: true,
- },
- {
- key: 'NEXT_PUBLIC_ANALYTICS_ID',
- value: 'UA-XXXXXXXX-X',
- target: ['production'],
- isSecret: false,
- },
- ],
-
- edgeFunctions: [
- {
- name: 'api-middleware',
- path: '/api/*',
- regions: ['iad1', 'sfo1'],
- memoryLimit: 1024,
- timeout: 10,
- },
- ],
- },
- ],
-
- monitoring: {
- enableWebAnalytics: true,
- enableSpeedInsights: true,
- logDrains: [
- {
- name: 'datadog-logs',
- url: 'https://http-intake.logs.datadoghq.com/api/v2/logs',
- headers: {
- 'DD-API-KEY': '${DATADOG_API_KEY}',
- },
- sources: ['lambda', 'edge'],
- },
- ],
- },
-
- enableWebhooks: true,
- webhookEvents: [
- 'deployment.succeeded',
- 'deployment.failed',
- 'deployment.ready',
- ],
-
- status: 'active',
- enabled: true,
-};
-
-/**
- * Example: Vercel Static Site Configuration
- */
-export const vercelStaticSiteConnectorExample = {
- name: 'vercel_docs',
- label: 'Vercel Documentation',
- type: 'saas',
- provider: 'vercel',
- baseUrl: 'https://api.vercel.com',
-
- authentication: {
- type: 'bearer',
- token: '${VERCEL_TOKEN}',
- },
-
- team: {
- teamId: 'team_xxxxxx',
- teamName: 'ObjectStack',
- },
-
- projects: [
- {
- name: 'objectstack-docs',
- framework: 'static',
-
- gitRepository: {
- type: 'github',
- repo: 'objectstack-ai/docs',
- productionBranch: 'main',
- autoDeployProduction: true,
- autoDeployPreview: true,
- },
-
- buildConfig: {
- buildCommand: 'npm run build',
- outputDirectory: 'dist',
- installCommand: 'npm ci',
- nodeVersion: '18.x',
- },
-
- deploymentConfig: {
- autoDeployment: true,
- regions: ['iad1', 'lhr1', 'sin1'],
- enablePreview: true,
- previewComments: true,
- productionProtection: false,
- },
-
- domains: [
- {
- domain: 'docs.objectstack.ai',
- httpsRedirect: true,
- },
- ],
-
- environmentVariables: [
- {
- key: 'ALGOLIA_APP_ID',
- value: '${ALGOLIA_APP_ID}',
- target: ['production', 'preview'],
- isSecret: false,
- },
- {
- key: 'ALGOLIA_API_KEY',
- value: '${ALGOLIA_API_KEY}',
- target: ['production', 'preview'],
- isSecret: true,
- },
- ],
- },
- ],
-
- monitoring: {
- enableWebAnalytics: true,
- enableSpeedInsights: false,
- },
-
- enableWebhooks: false,
-
- status: 'active',
- enabled: true,
-};
diff --git a/packages/spec/src/integration/index.ts b/packages/spec/src/integration/index.ts
index e6d75ceaf3..8dc614a76f 100644
--- a/packages/spec/src/integration/index.ts
+++ b/packages/spec/src/integration/index.ts
@@ -2,14 +2,25 @@
/**
* Integration Protocol Exports
- *
- * External System Connection Protocols
- * - Connector configurations for SaaS, databases, file storage, message queues
- * - GitHub integration (version control, CI/CD)
- * - Vercel integration (deployment, hosting)
+ *
+ * External System Connection Protocols (ADR-0097)
+ * - The connector protocol: one `ConnectorSchema`, provider-bound declarative
+ * instances, and the registry descriptor `GET /automation/connectors` serves
* - Authentication methods (OAuth2, API Key, JWT, SAML)
* - Data synchronization and field mapping
* - Webhooks, rate limiting, and retry strategies
+ *
+ * The per-provider "Connector Templates" (`connector/saas.zod.ts`,
+ * `connector/database.zod.ts`, file-storage, message-queue, github, vercel)
+ * were removed in #4480. They were the losing side of an architecture decision
+ * this module's live half already records: ADR-0023 rejected hand-modelling
+ * each external system's shape inside the spec, and ADR-0097's answer is the
+ * opposite direction — provider shapes come from the provider itself
+ * (connector-openapi materializes from an OpenAPI document, connector-mcp from
+ * an MCP server), while the platform defines only the unified protocol. The
+ * six files had zero consumers: nothing in this module referenced them, no
+ * runtime read them, and `engine.registerConnector()` validates against
+ * `ConnectorSchema` from `./connector.zod` alone.
*/
// Core Connector Protocol
@@ -22,11 +33,3 @@ export * from './connector-provider-errors';
// Connector registry vocabulary — origin/state and the descriptor
// `GET /automation/connectors` serves (ADR-0022, ADR-0097 §4, #3017)
export * from './connector-descriptor';
-
-// Connector Templates
-export * from './connector/saas.zod';
-export * from './connector/database.zod';
-export * from './connector/file-storage.zod';
-export * from './connector/message-queue.zod';
-export * from './connector/github.zod';
-export * from './connector/vercel.zod';