From abef6ca1dd862c895569dcdbd302ab0e1f500eb6 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 30 Jan 2026 09:42:40 +0000
Subject: [PATCH 1/2] Initial plan
From 62a621d805fbb0a05e0b064553708b62f16ec650 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 30 Jan 2026 09:52:25 +0000
Subject: [PATCH 2/2] Add P0/P1/P2 ObjectStack kernel API requirements
- Added batch operations API (batch.zod.ts) for updateMany/deleteMany
- Added metadata cache control with ETag support (cache.zod.ts)
- Added standardized error codes (errors.zod.ts)
- Added full-text search support to query.zod.ts
- Added view storage API for UI configuration persistence (view-storage.zod.ts)
- Added comprehensive test coverage for all new features
- All tests passing (2164 tests)
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
---
content/docs/references/api/batch.mdx | 122 ++
content/docs/references/api/cache.mdx | 123 ++
content/docs/references/api/connector.mdx | 32 +
content/docs/references/api/contract.mdx | 1 +
content/docs/references/api/errors.mdx | 144 +++
content/docs/references/api/index.mdx | 4 +
content/docs/references/api/meta.json | 4 +
content/docs/references/api/view-storage.mdx | 194 +++
content/docs/references/data/query.mdx | 22 +-
content/docs/references/hub/license.mdx | 14 +-
content/docs/references/hub/metrics.mdx | 31 +
content/docs/references/system/index.mdx | 3 +
content/docs/references/system/logging.mdx | 209 +++
content/docs/references/system/meta.json | 3 +
content/docs/references/system/metrics.mdx | 268 ++++
content/docs/references/system/tracing.mdx | 280 ++++
.../json-schema/api/BatchOperationResult.json | 57 +
.../json-schema/api/BatchOperationType.json | 15 +
.../spec/json-schema/api/BatchOptions.json | 32 +
.../spec/json-schema/api/BatchRecord.json | 25 +
.../json-schema/api/BatchUpdateRequest.json | 78 ++
.../json-schema/api/BatchUpdateResponse.json | 144 +++
.../spec/json-schema/api/CacheControl.json | 42 +
.../spec/json-schema/api/CacheDirective.json | 17 +
.../api/CacheInvalidationRequest.json | 43 +
.../api/CacheInvalidationResponse.json | 31 +
.../api/CacheInvalidationTarget.json | 17 +
.../json-schema/api/CreateViewRequest.json | 1042 +++++++++++++++
.../json-schema/api/DeleteManyRequest.json | 51 +
packages/spec/json-schema/api/ETag.json | 24 +
.../json-schema/api/EnhancedApiError.json | 226 ++++
.../spec/json-schema/api/ErrorCategory.json | 20 +
.../spec/json-schema/api/ErrorResponse.json | 259 ++++
.../spec/json-schema/api/ExportRequest.json | 110 ++
packages/spec/json-schema/api/FieldError.json | 88 ++
.../json-schema/api/ListViewsRequest.json | 58 +
.../json-schema/api/ListViewsResponse.json | 1132 +++++++++++++++++
.../json-schema/api/MetadataCacheRequest.json | 58 +
.../api/MetadataCacheResponse.json | 85 ++
.../spec/json-schema/api/RetryStrategy.json | 15 +
packages/spec/json-schema/api/SavedView.json | 1074 ++++++++++++++++
.../json-schema/api/StandardErrorCode.json | 62 +
.../json-schema/api/UpdateManyRequest.json | 67 +
.../json-schema/api/UpdateViewRequest.json | 1041 +++++++++++++++
packages/spec/json-schema/api/ViewColumn.json | 58 +
packages/spec/json-schema/api/ViewLayout.json | 125 ++
.../spec/json-schema/api/ViewResponse.json | 1103 ++++++++++++++++
packages/spec/json-schema/api/ViewType.json | 19 +
.../spec/json-schema/api/ViewVisibility.json | 15 +
.../spec/json-schema/data/FullTextSearch.json | 60 +
packages/spec/json-schema/data/JoinNode.json | 55 +
packages/spec/json-schema/data/Query.json | 55 +
packages/spec/src/api/batch.test.ts | 337 +++++
packages/spec/src/api/batch.zod.ts | 224 ++++
packages/spec/src/api/cache.test.ts | 264 ++++
packages/spec/src/api/cache.zod.ts | 230 ++++
packages/spec/src/api/errors.test.ts | 278 ++++
packages/spec/src/api/errors.zod.ts | 332 +++++
packages/spec/src/api/index.ts | 7 +
packages/spec/src/api/view-storage.zod.ts | 286 +++++
packages/spec/src/data/query.test.ts | 177 +++
packages/spec/src/data/query.zod.ts | 50 +
62 files changed, 11028 insertions(+), 14 deletions(-)
create mode 100644 content/docs/references/api/batch.mdx
create mode 100644 content/docs/references/api/cache.mdx
create mode 100644 content/docs/references/api/connector.mdx
create mode 100644 content/docs/references/api/errors.mdx
create mode 100644 content/docs/references/api/view-storage.mdx
create mode 100644 content/docs/references/hub/metrics.mdx
create mode 100644 content/docs/references/system/logging.mdx
create mode 100644 content/docs/references/system/metrics.mdx
create mode 100644 content/docs/references/system/tracing.mdx
create mode 100644 packages/spec/json-schema/api/BatchOperationResult.json
create mode 100644 packages/spec/json-schema/api/BatchOperationType.json
create mode 100644 packages/spec/json-schema/api/BatchOptions.json
create mode 100644 packages/spec/json-schema/api/BatchRecord.json
create mode 100644 packages/spec/json-schema/api/BatchUpdateRequest.json
create mode 100644 packages/spec/json-schema/api/BatchUpdateResponse.json
create mode 100644 packages/spec/json-schema/api/CacheControl.json
create mode 100644 packages/spec/json-schema/api/CacheDirective.json
create mode 100644 packages/spec/json-schema/api/CacheInvalidationRequest.json
create mode 100644 packages/spec/json-schema/api/CacheInvalidationResponse.json
create mode 100644 packages/spec/json-schema/api/CacheInvalidationTarget.json
create mode 100644 packages/spec/json-schema/api/CreateViewRequest.json
create mode 100644 packages/spec/json-schema/api/DeleteManyRequest.json
create mode 100644 packages/spec/json-schema/api/ETag.json
create mode 100644 packages/spec/json-schema/api/EnhancedApiError.json
create mode 100644 packages/spec/json-schema/api/ErrorCategory.json
create mode 100644 packages/spec/json-schema/api/ErrorResponse.json
create mode 100644 packages/spec/json-schema/api/FieldError.json
create mode 100644 packages/spec/json-schema/api/ListViewsRequest.json
create mode 100644 packages/spec/json-schema/api/ListViewsResponse.json
create mode 100644 packages/spec/json-schema/api/MetadataCacheRequest.json
create mode 100644 packages/spec/json-schema/api/MetadataCacheResponse.json
create mode 100644 packages/spec/json-schema/api/RetryStrategy.json
create mode 100644 packages/spec/json-schema/api/SavedView.json
create mode 100644 packages/spec/json-schema/api/StandardErrorCode.json
create mode 100644 packages/spec/json-schema/api/UpdateManyRequest.json
create mode 100644 packages/spec/json-schema/api/UpdateViewRequest.json
create mode 100644 packages/spec/json-schema/api/ViewColumn.json
create mode 100644 packages/spec/json-schema/api/ViewLayout.json
create mode 100644 packages/spec/json-schema/api/ViewResponse.json
create mode 100644 packages/spec/json-schema/api/ViewType.json
create mode 100644 packages/spec/json-schema/api/ViewVisibility.json
create mode 100644 packages/spec/json-schema/data/FullTextSearch.json
create mode 100644 packages/spec/src/api/batch.test.ts
create mode 100644 packages/spec/src/api/batch.zod.ts
create mode 100644 packages/spec/src/api/cache.test.ts
create mode 100644 packages/spec/src/api/cache.zod.ts
create mode 100644 packages/spec/src/api/errors.test.ts
create mode 100644 packages/spec/src/api/errors.zod.ts
create mode 100644 packages/spec/src/api/view-storage.zod.ts
diff --git a/content/docs/references/api/batch.mdx b/content/docs/references/api/batch.mdx
new file mode 100644
index 000000000..2b12e3898
--- /dev/null
+++ b/content/docs/references/api/batch.mdx
@@ -0,0 +1,122 @@
+---
+title: Batch
+description: Batch protocol schemas
+---
+
+# Batch
+
+
+**Source:** `packages/spec/src/api/batch.zod.ts`
+
+
+## TypeScript Usage
+
+```typescript
+import { BatchOperationResultSchema, BatchOperationTypeSchema, BatchOptionsSchema, BatchRecordSchema, BatchUpdateRequestSchema, BatchUpdateResponseSchema, DeleteManyRequestSchema, UpdateManyRequestSchema } from '@objectstack/spec/api';
+import type { BatchOperationResult, BatchOperationType, BatchOptions, BatchRecord, BatchUpdateRequest, BatchUpdateResponse, DeleteManyRequest, UpdateManyRequest } from '@objectstack/spec/api';
+
+// Validate data
+const result = BatchOperationResultSchema.parse(data);
+```
+
+---
+
+## BatchOperationResult
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **id** | `string` | optional | Record ID if operation succeeded |
+| **success** | `boolean` | ✅ | Whether this record was processed successfully |
+| **errors** | `object[]` | optional | Array of errors if operation failed |
+| **data** | `Record` | optional | Full record data (if returnRecords=true) |
+| **index** | `number` | optional | Index of the record in the request array |
+
+---
+
+## BatchOperationType
+
+### Allowed Values
+
+* `create`
+* `update`
+* `upsert`
+* `delete`
+
+---
+
+## BatchOptions
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **atomic** | `boolean` | optional | If true, rollback entire batch on any failure (transaction mode) |
+| **returnRecords** | `boolean` | optional | If true, return full record data in response |
+| **continueOnError** | `boolean` | optional | If true (and atomic=false), continue processing remaining records after errors |
+| **validateOnly** | `boolean` | optional | If true, validate records without persisting changes (dry-run mode) |
+
+---
+
+## BatchRecord
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **id** | `string` | optional | Record ID (required for update/delete) |
+| **data** | `Record` | optional | Record data (required for create/update/upsert) |
+| **externalId** | `string` | optional | External ID for upsert matching |
+
+---
+
+## BatchUpdateRequest
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **operation** | `Enum<'create' \| 'update' \| 'upsert' \| 'delete'>` | ✅ | Type of batch operation |
+| **records** | `object[]` | ✅ | Array of records to process (max 200 per batch) |
+| **options** | `object` | optional | Batch operation options |
+
+---
+
+## BatchUpdateResponse
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **success** | `boolean` | ✅ | Operation success status |
+| **error** | `object` | optional | Error details if success is false |
+| **meta** | `object` | optional | Response metadata |
+| **operation** | `Enum<'create' \| 'update' \| 'upsert' \| 'delete'>` | optional | Operation type that was performed |
+| **total** | `number` | ✅ | Total number of records in the batch |
+| **succeeded** | `number` | ✅ | Number of records that succeeded |
+| **failed** | `number` | ✅ | Number of records that failed |
+| **results** | `object[]` | ✅ | Detailed results for each record |
+
+---
+
+## DeleteManyRequest
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **ids** | `string[]` | ✅ | Array of record IDs to delete (max 200) |
+| **options** | `object` | optional | Delete options |
+
+---
+
+## UpdateManyRequest
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **records** | `object[]` | ✅ | Array of records to update (max 200 per batch) |
+| **options** | `object` | optional | Update options |
+
diff --git a/content/docs/references/api/cache.mdx b/content/docs/references/api/cache.mdx
new file mode 100644
index 000000000..ebfe67ca3
--- /dev/null
+++ b/content/docs/references/api/cache.mdx
@@ -0,0 +1,123 @@
+---
+title: Cache
+description: Cache protocol schemas
+---
+
+# Cache
+
+
+**Source:** `packages/spec/src/api/cache.zod.ts`
+
+
+## TypeScript Usage
+
+```typescript
+import { CacheControlSchema, CacheDirectiveSchema, CacheInvalidationRequestSchema, CacheInvalidationResponseSchema, CacheInvalidationTargetSchema, ETagSchema, MetadataCacheRequestSchema, MetadataCacheResponseSchema } from '@objectstack/spec/api';
+import type { CacheControl, CacheDirective, CacheInvalidationRequest, CacheInvalidationResponse, CacheInvalidationTarget, ETag, MetadataCacheRequest, MetadataCacheResponse } from '@objectstack/spec/api';
+
+// Validate data
+const result = CacheControlSchema.parse(data);
+```
+
+---
+
+## CacheControl
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **directives** | `Enum<'public' \| 'private' \| 'no-cache' \| 'no-store' \| 'must-revalidate' \| 'max-age'>[]` | ✅ | Cache control directives |
+| **maxAge** | `number` | optional | Maximum cache age in seconds |
+| **staleWhileRevalidate** | `number` | optional | Allow serving stale content while revalidating (seconds) |
+| **staleIfError** | `number` | optional | Allow serving stale content on error (seconds) |
+
+---
+
+## CacheDirective
+
+### Allowed Values
+
+* `public`
+* `private`
+* `no-cache`
+* `no-store`
+* `must-revalidate`
+* `max-age`
+
+---
+
+## CacheInvalidationRequest
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **target** | `Enum<'all' \| 'object' \| 'field' \| 'permission' \| 'layout' \| 'custom'>` | ✅ | What to invalidate |
+| **identifiers** | `string[]` | optional | Specific resources to invalidate (e.g., object names) |
+| **cascade** | `boolean` | optional | If true, invalidate dependent resources |
+| **pattern** | `string` | optional | Pattern for custom invalidation (supports wildcards) |
+
+---
+
+## CacheInvalidationResponse
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **success** | `boolean` | ✅ | Whether invalidation succeeded |
+| **invalidated** | `number` | ✅ | Number of cache entries invalidated |
+| **targets** | `string[]` | optional | List of invalidated resources |
+
+---
+
+## CacheInvalidationTarget
+
+### Allowed Values
+
+* `all`
+* `object`
+* `field`
+* `permission`
+* `layout`
+* `custom`
+
+---
+
+## ETag
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **value** | `string` | ✅ | ETag value (hash or version identifier) |
+| **weak** | `boolean` | optional | Whether this is a weak ETag |
+
+---
+
+## MetadataCacheRequest
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **ifNoneMatch** | `string` | optional | ETag value for conditional request (If-None-Match header) |
+| **ifModifiedSince** | `string` | optional | Timestamp for conditional request (If-Modified-Since header) |
+| **cacheControl** | `object` | optional | Client cache control preferences |
+
+---
+
+## MetadataCacheResponse
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **data** | `any` | optional | Metadata payload (omitted for 304 Not Modified) |
+| **etag** | `object` | optional | ETag for this resource version |
+| **lastModified** | `string` | optional | Last modification timestamp |
+| **cacheControl** | `object` | optional | Cache control directives |
+| **notModified** | `boolean` | optional | True if resource has not been modified (304 response) |
+| **version** | `string` | optional | Metadata version identifier |
+
diff --git a/content/docs/references/api/connector.mdx b/content/docs/references/api/connector.mdx
new file mode 100644
index 000000000..b0f324fc8
--- /dev/null
+++ b/content/docs/references/api/connector.mdx
@@ -0,0 +1,32 @@
+---
+title: Connector
+description: Connector protocol schemas
+---
+
+# Connector
+
+
+**Source:** `packages/spec/src/api/connector.zod.ts`
+
+
+## TypeScript Usage
+
+```typescript
+import { RetryStrategySchema } from '@objectstack/spec/api';
+import type { RetryStrategy } from '@objectstack/spec/api';
+
+// Validate data
+const result = RetryStrategySchema.parse(data);
+```
+
+---
+
+## RetryStrategy
+
+### Allowed Values
+
+* `no_retry`
+* `retry_immediate`
+* `retry_backoff`
+* `retry_after`
+
diff --git a/content/docs/references/api/contract.mdx b/content/docs/references/api/contract.mdx
index 7f0cd9cc2..031076ee4 100644
--- a/content/docs/references/api/contract.mdx
+++ b/content/docs/references/api/contract.mdx
@@ -101,6 +101,7 @@ const result = ApiErrorSchema.parse(data);
| **object** | `string` | ✅ | Object name (e.g. account) |
| **fields** | `string \| object[]` | optional | Fields to retrieve |
| **where** | `any` | optional | Filtering criteria (WHERE) |
+| **search** | `object` | optional | Full-text search configuration ($search parameter) |
| **orderBy** | `object[]` | optional | Sorting instructions (ORDER BY) |
| **limit** | `number` | optional | Max records to return (LIMIT) |
| **offset** | `number` | optional | Records to skip (OFFSET) |
diff --git a/content/docs/references/api/errors.mdx b/content/docs/references/api/errors.mdx
new file mode 100644
index 000000000..0d75dca58
--- /dev/null
+++ b/content/docs/references/api/errors.mdx
@@ -0,0 +1,144 @@
+---
+title: Errors
+description: Errors protocol schemas
+---
+
+# Errors
+
+
+**Source:** `packages/spec/src/api/errors.zod.ts`
+
+
+## TypeScript Usage
+
+```typescript
+import { EnhancedApiErrorSchema, ErrorCategorySchema, ErrorResponseSchema, FieldErrorSchema, StandardErrorCodeSchema } from '@objectstack/spec/api';
+import type { EnhancedApiError, ErrorCategory, ErrorResponse, FieldError, StandardErrorCode } from '@objectstack/spec/api';
+
+// Validate data
+const result = EnhancedApiErrorSchema.parse(data);
+```
+
+---
+
+## EnhancedApiError
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **code** | `Enum<'validation_error' \| 'invalid_field' \| 'missing_required_field' \| 'invalid_format' \| 'value_too_long' \| 'value_too_short' \| 'value_out_of_range' \| 'invalid_reference' \| 'duplicate_value' \| 'invalid_query' \| 'invalid_filter' \| 'invalid_sort' \| 'max_records_exceeded' \| 'unauthenticated' \| 'invalid_credentials' \| 'expired_token' \| 'invalid_token' \| 'session_expired' \| 'mfa_required' \| 'email_not_verified' \| 'permission_denied' \| 'insufficient_privileges' \| 'field_not_accessible' \| 'record_not_accessible' \| 'license_required' \| 'ip_restricted' \| 'time_restricted' \| 'resource_not_found' \| 'object_not_found' \| 'record_not_found' \| 'field_not_found' \| 'endpoint_not_found' \| 'resource_conflict' \| 'concurrent_modification' \| 'delete_restricted' \| 'duplicate_record' \| 'lock_conflict' \| 'rate_limit_exceeded' \| 'quota_exceeded' \| 'concurrent_limit_exceeded' \| 'internal_error' \| 'database_error' \| 'timeout' \| 'service_unavailable' \| 'not_implemented' \| 'external_service_error' \| 'integration_error' \| 'webhook_delivery_failed' \| 'batch_partial_failure' \| 'batch_complete_failure' \| 'transaction_failed'>` | ✅ | Machine-readable error code |
+| **message** | `string` | ✅ | Human-readable error message |
+| **category** | `Enum<'validation' \| 'authentication' \| 'authorization' \| 'not_found' \| 'conflict' \| 'rate_limit' \| 'server' \| 'external' \| 'maintenance'>` | optional | Error category |
+| **httpStatus** | `number` | optional | HTTP status code |
+| **retryable** | `boolean` | optional | Whether the request can be retried |
+| **retryStrategy** | `Enum<'no_retry' \| 'retry_immediate' \| 'retry_backoff' \| 'retry_after'>` | optional | Recommended retry strategy |
+| **retryAfter** | `number` | optional | Seconds to wait before retrying |
+| **details** | `any` | optional | Additional error context |
+| **fieldErrors** | `object[]` | optional | Field-specific validation errors |
+| **timestamp** | `string` | optional | When the error occurred |
+| **requestId** | `string` | optional | Request ID for tracking |
+| **traceId** | `string` | optional | Distributed trace ID |
+| **documentation** | `string` | optional | URL to error documentation |
+| **helpText** | `string` | optional | Suggested actions to resolve the error |
+
+---
+
+## ErrorCategory
+
+### Allowed Values
+
+* `validation`
+* `authentication`
+* `authorization`
+* `not_found`
+* `conflict`
+* `rate_limit`
+* `server`
+* `external`
+* `maintenance`
+
+---
+
+## ErrorResponse
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **success** | `boolean` | ✅ | Always false for error responses |
+| **error** | `object` | ✅ | Error details |
+| **meta** | `object` | optional | Response metadata |
+
+---
+
+## FieldError
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **field** | `string` | ✅ | Field path (supports dot notation) |
+| **code** | `Enum<'validation_error' \| 'invalid_field' \| 'missing_required_field' \| 'invalid_format' \| 'value_too_long' \| 'value_too_short' \| 'value_out_of_range' \| 'invalid_reference' \| 'duplicate_value' \| 'invalid_query' \| 'invalid_filter' \| 'invalid_sort' \| 'max_records_exceeded' \| 'unauthenticated' \| 'invalid_credentials' \| 'expired_token' \| 'invalid_token' \| 'session_expired' \| 'mfa_required' \| 'email_not_verified' \| 'permission_denied' \| 'insufficient_privileges' \| 'field_not_accessible' \| 'record_not_accessible' \| 'license_required' \| 'ip_restricted' \| 'time_restricted' \| 'resource_not_found' \| 'object_not_found' \| 'record_not_found' \| 'field_not_found' \| 'endpoint_not_found' \| 'resource_conflict' \| 'concurrent_modification' \| 'delete_restricted' \| 'duplicate_record' \| 'lock_conflict' \| 'rate_limit_exceeded' \| 'quota_exceeded' \| 'concurrent_limit_exceeded' \| 'internal_error' \| 'database_error' \| 'timeout' \| 'service_unavailable' \| 'not_implemented' \| 'external_service_error' \| 'integration_error' \| 'webhook_delivery_failed' \| 'batch_partial_failure' \| 'batch_complete_failure' \| 'transaction_failed'>` | ✅ | Error code for this field |
+| **message** | `string` | ✅ | Human-readable error message |
+| **value** | `any` | optional | The invalid value that was provided |
+| **constraint** | `any` | optional | The constraint that was violated (e.g., max length) |
+
+---
+
+## StandardErrorCode
+
+### Allowed Values
+
+* `validation_error`
+* `invalid_field`
+* `missing_required_field`
+* `invalid_format`
+* `value_too_long`
+* `value_too_short`
+* `value_out_of_range`
+* `invalid_reference`
+* `duplicate_value`
+* `invalid_query`
+* `invalid_filter`
+* `invalid_sort`
+* `max_records_exceeded`
+* `unauthenticated`
+* `invalid_credentials`
+* `expired_token`
+* `invalid_token`
+* `session_expired`
+* `mfa_required`
+* `email_not_verified`
+* `permission_denied`
+* `insufficient_privileges`
+* `field_not_accessible`
+* `record_not_accessible`
+* `license_required`
+* `ip_restricted`
+* `time_restricted`
+* `resource_not_found`
+* `object_not_found`
+* `record_not_found`
+* `field_not_found`
+* `endpoint_not_found`
+* `resource_conflict`
+* `concurrent_modification`
+* `delete_restricted`
+* `duplicate_record`
+* `lock_conflict`
+* `rate_limit_exceeded`
+* `quota_exceeded`
+* `concurrent_limit_exceeded`
+* `internal_error`
+* `database_error`
+* `timeout`
+* `service_unavailable`
+* `not_implemented`
+* `external_service_error`
+* `integration_error`
+* `webhook_delivery_failed`
+* `batch_partial_failure`
+* `batch_complete_failure`
+* `transaction_failed`
+
diff --git a/content/docs/references/api/index.mdx b/content/docs/references/api/index.mdx
index 782c27f56..a5d208693 100644
--- a/content/docs/references/api/index.mdx
+++ b/content/docs/references/api/index.mdx
@@ -8,13 +8,17 @@ description: Complete reference for all api protocol schemas
This section contains all protocol schemas for the api layer of ObjectStack.
+
+
+
+
diff --git a/content/docs/references/api/meta.json b/content/docs/references/api/meta.json
index b72377cec..cd9ded353 100644
--- a/content/docs/references/api/meta.json
+++ b/content/docs/references/api/meta.json
@@ -1,13 +1,17 @@
{
"title": "API Protocol",
"pages": [
+ "batch",
+ "cache",
"contract",
"discovery",
"endpoint",
+ "errors",
"graphql",
"odata",
"realtime",
"router",
+ "view-storage",
"websocket"
]
}
\ No newline at end of file
diff --git a/content/docs/references/api/view-storage.mdx b/content/docs/references/api/view-storage.mdx
new file mode 100644
index 000000000..cefc8c0ae
--- /dev/null
+++ b/content/docs/references/api/view-storage.mdx
@@ -0,0 +1,194 @@
+---
+title: View Storage
+description: View Storage protocol schemas
+---
+
+# View Storage
+
+
+**Source:** `packages/spec/src/api/view-storage.zod.ts`
+
+
+## TypeScript Usage
+
+```typescript
+import { CreateViewRequestSchema, ListViewsRequestSchema, ListViewsResponseSchema, SavedViewSchema, UpdateViewRequestSchema, ViewColumnSchema, ViewLayoutSchema, ViewResponseSchema, ViewTypeSchema, ViewVisibilitySchema } from '@objectstack/spec/api';
+import type { CreateViewRequest, ListViewsRequest, ListViewsResponse, SavedView, UpdateViewRequest, ViewColumn, ViewLayout, ViewResponse, ViewType, ViewVisibility } from '@objectstack/spec/api';
+
+// Validate data
+const result = CreateViewRequestSchema.parse(data);
+```
+
+---
+
+## CreateViewRequest
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **name** | `string` | ✅ | View machine name |
+| **label** | `string` | ✅ | Display label |
+| **description** | `string` | optional | |
+| **object** | `string` | ✅ | Object name |
+| **type** | `Enum<'list' \| 'kanban' \| 'calendar' \| 'gantt' \| 'timeline' \| 'chart' \| 'pivot' \| 'custom'>` | ✅ | View type |
+| **visibility** | `Enum<'private' \| 'shared' \| 'public' \| 'organization'>` | ✅ | View visibility |
+| **query** | `object` | ✅ | Query configuration |
+| **layout** | `object` | optional | Layout configuration |
+| **sharedWith** | `string[]` | optional | Users/teams to share with |
+| **isDefault** | `boolean` | optional | Set as default view |
+| **settings** | `Record` | optional | |
+
+---
+
+## ListViewsRequest
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **object** | `string` | optional | Filter by object name |
+| **type** | `Enum<'list' \| 'kanban' \| 'calendar' \| 'gantt' \| 'timeline' \| 'chart' \| 'pivot' \| 'custom'>` | optional | Filter by view type |
+| **visibility** | `Enum<'private' \| 'shared' \| 'public' \| 'organization'>` | optional | Filter by visibility |
+| **createdBy** | `string` | optional | Filter by creator user ID |
+| **isDefault** | `boolean` | optional | Filter for default views |
+| **limit** | `number` | optional | Max results |
+| **offset** | `number` | optional | Offset for pagination |
+
+---
+
+## ListViewsResponse
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **success** | `boolean` | ✅ | |
+| **data** | `object[]` | ✅ | Array of saved views |
+| **pagination** | `object` | ✅ | |
+| **error** | `object` | optional | |
+
+---
+
+## SavedView
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **id** | `string` | ✅ | Unique view identifier |
+| **name** | `string` | ✅ | View machine name (snake_case) |
+| **label** | `string` | ✅ | Display label |
+| **description** | `string` | optional | View description |
+| **object** | `string` | ✅ | Object/table this view is for |
+| **type** | `Enum<'list' \| 'kanban' \| 'calendar' \| 'gantt' \| 'timeline' \| 'chart' \| 'pivot' \| 'custom'>` | ✅ | View type |
+| **visibility** | `Enum<'private' \| 'shared' \| 'public' \| 'organization'>` | ✅ | Who can access this view |
+| **query** | `object` | ✅ | Query configuration (filters, sorting, etc.) |
+| **layout** | `object` | optional | Layout configuration |
+| **sharedWith** | `string[]` | optional | User/team IDs this view is shared with |
+| **isDefault** | `boolean` | optional | Is this the default view for this object? |
+| **isSystem** | `boolean` | optional | Is this a system-defined view? |
+| **createdBy** | `string` | ✅ | User ID who created this view |
+| **createdAt** | `string` | ✅ | When the view was created |
+| **updatedBy** | `string` | optional | User ID who last updated this view |
+| **updatedAt** | `string` | optional | When the view was last updated |
+| **settings** | `Record` | optional | Additional view-specific settings |
+
+---
+
+## UpdateViewRequest
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **name** | `string` | optional | View machine name |
+| **label** | `string` | optional | Display label |
+| **description** | `string` | optional | |
+| **object** | `string` | optional | Object name |
+| **type** | `Enum<'list' \| 'kanban' \| 'calendar' \| 'gantt' \| 'timeline' \| 'chart' \| 'pivot' \| 'custom'>` | optional | View type |
+| **visibility** | `Enum<'private' \| 'shared' \| 'public' \| 'organization'>` | optional | View visibility |
+| **query** | `object` | optional | Query configuration |
+| **layout** | `object` | optional | Layout configuration |
+| **sharedWith** | `string[]` | optional | Users/teams to share with |
+| **isDefault** | `boolean` | optional | Set as default view |
+| **settings** | `Record` | optional | |
+| **id** | `string` | ✅ | View ID to update |
+
+---
+
+## ViewColumn
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **field** | `string` | ✅ | Field name |
+| **label** | `string` | optional | Custom column label |
+| **width** | `number` | optional | Column width in pixels |
+| **sortable** | `boolean` | optional | Whether column is sortable |
+| **filterable** | `boolean` | optional | Whether column is filterable |
+| **visible** | `boolean` | optional | Whether column is visible |
+| **pinned** | `Enum<'left' \| 'right'>` | optional | Pin column to left or right |
+| **formatter** | `string` | optional | Custom formatter name |
+| **aggregation** | `string` | optional | Aggregation function for column (sum, avg, etc.) |
+
+---
+
+## ViewLayout
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **columns** | `object[]` | optional | Column configuration for list views |
+| **rowHeight** | `number` | optional | Row height in pixels |
+| **groupByField** | `string` | optional | Field to group by (for kanban) |
+| **cardFields** | `string[]` | optional | Fields to display on cards |
+| **dateField** | `string` | optional | Date field for calendar view |
+| **startDateField** | `string` | optional | Start date field for event ranges |
+| **endDateField** | `string` | optional | End date field for event ranges |
+| **titleField** | `string` | optional | Field to use as event title |
+| **chartType** | `Enum<'bar' \| 'line' \| 'pie' \| 'scatter' \| 'area'>` | optional | Chart type |
+| **xAxis** | `string` | optional | X-axis field |
+| **yAxis** | `string` | optional | Y-axis field |
+| **series** | `string[]` | optional | Series fields for multi-series charts |
+
+---
+
+## ViewResponse
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **success** | `boolean` | ✅ | |
+| **data** | `object` | optional | The saved view |
+| **error** | `object` | optional | |
+
+---
+
+## ViewType
+
+### Allowed Values
+
+* `list`
+* `kanban`
+* `calendar`
+* `gantt`
+* `timeline`
+* `chart`
+* `pivot`
+* `custom`
+
+---
+
+## ViewVisibility
+
+### Allowed Values
+
+* `private`
+* `shared`
+* `public`
+* `organization`
+
diff --git a/content/docs/references/data/query.mdx b/content/docs/references/data/query.mdx
index aabc1e68b..3b085f3d7 100644
--- a/content/docs/references/data/query.mdx
+++ b/content/docs/references/data/query.mdx
@@ -12,8 +12,8 @@ description: Query protocol schemas
## TypeScript Usage
```typescript
-import { AggregationFunctionSchema, AggregationNodeSchema, FieldNodeSchema, JoinNodeSchema, JoinStrategySchema, JoinTypeSchema, QuerySchema, SortNodeSchema, WindowFunctionSchema, WindowFunctionNodeSchema, WindowSpecSchema } from '@objectstack/spec/data';
-import type { AggregationFunction, AggregationNode, FieldNode, JoinNode, JoinStrategy, JoinType, Query, SortNode, WindowFunction, WindowFunctionNode, WindowSpec } from '@objectstack/spec/data';
+import { AggregationFunctionSchema, AggregationNodeSchema, FieldNodeSchema, FullTextSearchSchema, JoinNodeSchema, JoinStrategySchema, JoinTypeSchema, QuerySchema, SortNodeSchema, WindowFunctionSchema, WindowFunctionNodeSchema, WindowSpecSchema } from '@objectstack/spec/data';
+import type { AggregationFunction, AggregationNode, FieldNode, FullTextSearch, JoinNode, JoinStrategy, JoinType, Query, SortNode, WindowFunction, WindowFunctionNode, WindowSpec } from '@objectstack/spec/data';
// Validate data
const result = AggregationFunctionSchema.parse(data);
@@ -54,6 +54,23 @@ const result = AggregationFunctionSchema.parse(data);
---
+## FullTextSearch
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **query** | `string` | ✅ | Search query text |
+| **fields** | `string[]` | optional | Fields to search in (if not specified, searches all text fields) |
+| **fuzzy** | `boolean` | optional | Enable fuzzy matching (tolerates typos) |
+| **operator** | `Enum<'and' \| 'or'>` | optional | Logical operator between terms |
+| **boost** | `Record` | optional | Field-specific relevance boosting (field name -> boost factor) |
+| **minScore** | `number` | optional | Minimum relevance score threshold |
+| **language** | `string` | optional | Language for text analysis (e.g., "en", "zh", "es") |
+| **highlight** | `boolean` | optional | Enable search result highlighting |
+
+---
+
## JoinNode
### Properties
@@ -100,6 +117,7 @@ const result = AggregationFunctionSchema.parse(data);
| **object** | `string` | ✅ | Object name (e.g. account) |
| **fields** | `string \| object[]` | optional | Fields to retrieve |
| **where** | `any` | optional | Filtering criteria (WHERE) |
+| **search** | `object` | optional | Full-text search configuration ($search parameter) |
| **orderBy** | `object[]` | optional | Sorting instructions (ORDER BY) |
| **limit** | `number` | optional | Max records to return (LIMIT) |
| **offset** | `number` | optional | Records to skip (OFFSET) |
diff --git a/content/docs/references/hub/license.mdx b/content/docs/references/hub/license.mdx
index d91295909..d1a4472d6 100644
--- a/content/docs/references/hub/license.mdx
+++ b/content/docs/references/hub/license.mdx
@@ -12,8 +12,8 @@ description: License protocol schemas
## TypeScript Usage
```typescript
-import { FeatureSchema, LicenseSchema, MetricTypeSchema, PlanSchema } from '@objectstack/spec/hub';
-import type { Feature, License, MetricType, Plan } from '@objectstack/spec/hub';
+import { FeatureSchema, LicenseSchema, PlanSchema } from '@objectstack/spec/hub';
+import type { Feature, License, Plan } from '@objectstack/spec/hub';
// Validate data
const result = FeatureSchema.parse(data);
@@ -54,16 +54,6 @@ const result = FeatureSchema.parse(data);
---
-## MetricType
-
-### Allowed Values
-
-* `boolean`
-* `counter`
-* `gauge`
-
----
-
## Plan
### Properties
diff --git a/content/docs/references/hub/metrics.mdx b/content/docs/references/hub/metrics.mdx
new file mode 100644
index 000000000..903758fad
--- /dev/null
+++ b/content/docs/references/hub/metrics.mdx
@@ -0,0 +1,31 @@
+---
+title: Metrics
+description: Metrics protocol schemas
+---
+
+# Metrics
+
+
+**Source:** `packages/spec/src/hub/metrics.zod.ts`
+
+
+## TypeScript Usage
+
+```typescript
+import { MetricTypeSchema } from '@objectstack/spec/hub';
+import type { MetricType } from '@objectstack/spec/hub';
+
+// Validate data
+const result = MetricTypeSchema.parse(data);
+```
+
+---
+
+## MetricType
+
+### Allowed Values
+
+* `boolean`
+* `counter`
+* `gauge`
+
diff --git a/content/docs/references/system/index.mdx b/content/docs/references/system/index.mdx
index 9fcdb2f8f..7d17cb63c 100644
--- a/content/docs/references/system/index.mdx
+++ b/content/docs/references/system/index.mdx
@@ -18,10 +18,13 @@ This section contains all protocol schemas for the system layer of ObjectStack.
+
+
+
diff --git a/content/docs/references/system/logging.mdx b/content/docs/references/system/logging.mdx
new file mode 100644
index 000000000..102d166da
--- /dev/null
+++ b/content/docs/references/system/logging.mdx
@@ -0,0 +1,209 @@
+---
+title: Logging
+description: Logging protocol schemas
+---
+
+# Logging
+
+
+**Source:** `packages/spec/src/system/logging.zod.ts`
+
+
+## TypeScript Usage
+
+```typescript
+import { ConsoleDestinationConfigSchema, ExtendedLogLevelSchema, ExternalServiceDestinationConfigSchema, FileDestinationConfigSchema, HttpDestinationConfigSchema, LogDestinationSchema, LogDestinationTypeSchema, LogEnrichmentConfigSchema, LoggingConfigSchema, StructuredLogEntrySchema } from '@objectstack/spec/system';
+import type { ConsoleDestinationConfig, ExtendedLogLevel, ExternalServiceDestinationConfig, FileDestinationConfig, HttpDestinationConfig, LogDestination, LogDestinationType, LogEnrichmentConfig, LoggingConfig, StructuredLogEntry } from '@objectstack/spec/system';
+
+// Validate data
+const result = ConsoleDestinationConfigSchema.parse(data);
+```
+
+---
+
+## ConsoleDestinationConfig
+
+Console destination configuration
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **stream** | `Enum<'stdout' \| 'stderr'>` | optional | |
+| **colors** | `boolean` | optional | |
+| **prettyPrint** | `boolean` | optional | |
+
+---
+
+## ExtendedLogLevel
+
+Extended log severity level
+
+### Allowed Values
+
+* `trace`
+* `debug`
+* `info`
+* `warn`
+* `error`
+* `fatal`
+
+---
+
+## ExternalServiceDestinationConfig
+
+External service destination configuration
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **endpoint** | `string` | optional | |
+| **region** | `string` | optional | |
+| **credentials** | `object` | optional | |
+| **logGroup** | `string` | optional | |
+| **logStream** | `string` | optional | |
+| **index** | `string` | optional | |
+| **config** | `Record` | optional | |
+
+---
+
+## FileDestinationConfig
+
+File destination configuration
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **path** | `string` | ✅ | Log file path |
+| **rotation** | `object` | optional | |
+| **encoding** | `string` | optional | |
+| **append** | `boolean` | optional | |
+
+---
+
+## HttpDestinationConfig
+
+HTTP destination configuration
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **url** | `string` | ✅ | HTTP endpoint URL |
+| **method** | `Enum<'POST' \| 'PUT'>` | optional | |
+| **headers** | `Record` | optional | |
+| **auth** | `object` | optional | |
+| **batch** | `object` | optional | |
+| **retry** | `object` | optional | |
+| **timeout** | `integer` | optional | |
+
+---
+
+## LogDestination
+
+Log destination configuration
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **name** | `string` | ✅ | Destination name (snake_case) |
+| **type** | `Enum<'console' \| 'file' \| 'syslog' \| 'elasticsearch' \| 'cloudwatch' \| 'stackdriver' \| 'azure_monitor' \| 'datadog' \| 'splunk' \| 'loki' \| 'http' \| 'kafka' \| 'redis' \| 'custom'>` | ✅ | Destination type |
+| **level** | `Enum<'trace' \| 'debug' \| 'info' \| 'warn' \| 'error' \| 'fatal'>` | optional | Extended log severity level |
+| **enabled** | `boolean` | optional | |
+| **console** | `object` | optional | Console destination configuration |
+| **file** | `object` | optional | File destination configuration |
+| **http** | `object` | optional | HTTP destination configuration |
+| **externalService** | `object` | optional | External service destination configuration |
+| **format** | `Enum<'json' \| 'text' \| 'pretty'>` | optional | |
+| **filterId** | `string` | optional | Filter function identifier |
+
+---
+
+## LogDestinationType
+
+Log destination type
+
+### Allowed Values
+
+* `console`
+* `file`
+* `syslog`
+* `elasticsearch`
+* `cloudwatch`
+* `stackdriver`
+* `azure_monitor`
+* `datadog`
+* `splunk`
+* `loki`
+* `http`
+* `kafka`
+* `redis`
+* `custom`
+
+---
+
+## LogEnrichmentConfig
+
+Log enrichment configuration
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **staticFields** | `Record` | optional | Static fields added to every log |
+| **dynamicEnrichers** | `string[]` | optional | Dynamic enricher function IDs |
+| **addHostname** | `boolean` | optional | |
+| **addProcessId** | `boolean` | optional | |
+| **addEnvironment** | `boolean` | optional | |
+| **addTimestampFormats** | `object` | optional | |
+| **addCaller** | `boolean` | optional | |
+| **addCorrelationIds** | `boolean` | optional | |
+
+---
+
+## LoggingConfig
+
+Logging configuration
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **name** | `string` | ✅ | Configuration name (snake_case, max 64 chars) |
+| **label** | `string` | ✅ | Display label |
+| **enabled** | `boolean` | optional | |
+| **level** | `Enum<'trace' \| 'debug' \| 'info' \| 'warn' \| 'error' \| 'fatal'>` | optional | Extended log severity level |
+| **destinations** | `object[]` | ✅ | Log destinations |
+| **enrichment** | `object` | optional | Log enrichment configuration |
+| **redact** | `string[]` | optional | Fields to redact |
+| **sampling** | `object` | optional | |
+| **buffer** | `object` | optional | |
+| **performance** | `object` | optional | |
+
+---
+
+## StructuredLogEntry
+
+Structured log entry
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **timestamp** | `string` | ✅ | ISO 8601 timestamp |
+| **level** | `Enum<'trace' \| 'debug' \| 'info' \| 'warn' \| 'error' \| 'fatal'>` | ✅ | Log severity level |
+| **message** | `string` | ✅ | Log message |
+| **context** | `Record` | optional | Structured context |
+| **error** | `object` | optional | Error details |
+| **trace** | `object` | optional | Distributed tracing context |
+| **source** | `object` | optional | Source information |
+| **host** | `object` | optional | Host information |
+| **environment** | `string` | optional | Environment (e.g., production, staging) |
+| **user** | `object` | optional | User context |
+| **request** | `object` | optional | Request context |
+| **labels** | `Record` | optional | Custom labels |
+| **metadata** | `Record` | optional | Additional metadata |
+
diff --git a/content/docs/references/system/meta.json b/content/docs/references/system/meta.json
index d4a87c4ba..64790a4f5 100644
--- a/content/docs/references/system/meta.json
+++ b/content/docs/references/system/meta.json
@@ -11,10 +11,13 @@
"feature",
"job",
"logger",
+ "logging",
"manifest",
+ "metrics",
"plugin",
"plugin-capability",
"scoped-storage",
+ "tracing",
"translation"
]
}
\ No newline at end of file
diff --git a/content/docs/references/system/metrics.mdx b/content/docs/references/system/metrics.mdx
new file mode 100644
index 000000000..9dc001548
--- /dev/null
+++ b/content/docs/references/system/metrics.mdx
@@ -0,0 +1,268 @@
+---
+title: Metrics
+description: Metrics protocol schemas
+---
+
+# Metrics
+
+
+**Source:** `packages/spec/src/system/metrics.zod.ts`
+
+
+## TypeScript Usage
+
+```typescript
+import { HistogramBucketConfigSchema, MetricAggregationConfigSchema, MetricAggregationTypeSchema, MetricDataPointSchema, MetricDefinitionSchema, MetricExportConfigSchema, MetricLabelsSchema, MetricTypeSchema, MetricUnitSchema, MetricsConfigSchema, ServiceLevelIndicatorSchema, ServiceLevelObjectiveSchema, TimeSeriesSchema, TimeSeriesDataPointSchema } from '@objectstack/spec/system';
+import type { HistogramBucketConfig, MetricAggregationConfig, MetricAggregationType, MetricDataPoint, MetricDefinition, MetricExportConfig, MetricLabels, MetricType, MetricUnit, MetricsConfig, ServiceLevelIndicator, ServiceLevelObjective, TimeSeries, TimeSeriesDataPoint } from '@objectstack/spec/system';
+
+// Validate data
+const result = HistogramBucketConfigSchema.parse(data);
+```
+
+---
+
+## HistogramBucketConfig
+
+Histogram bucket configuration
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **type** | `Enum<'linear' \| 'exponential' \| 'explicit'>` | ✅ | Bucket type |
+| **linear** | `object` | optional | |
+| **exponential** | `object` | optional | |
+| **explicit** | `object` | optional | |
+
+---
+
+## MetricAggregationConfig
+
+Metric aggregation configuration
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **type** | `Enum<'sum' \| 'avg' \| 'min' \| 'max' \| 'count' \| 'p50' \| 'p75' \| 'p90' \| 'p95' \| 'p99' \| 'p999' \| 'rate' \| 'stddev'>` | ✅ | Aggregation type |
+| **window** | `object` | optional | |
+| **groupBy** | `string[]` | optional | Group by label names |
+| **filters** | `Record` | optional | Filter criteria |
+
+---
+
+## MetricAggregationType
+
+Metric aggregation type
+
+### Allowed Values
+
+* `sum`
+* `avg`
+* `min`
+* `max`
+* `count`
+* `p50`
+* `p75`
+* `p90`
+* `p95`
+* `p99`
+* `p999`
+* `rate`
+* `stddev`
+
+---
+
+## MetricDataPoint
+
+Metric data point
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **name** | `string` | ✅ | Metric name |
+| **type** | `Enum<'counter' \| 'gauge' \| 'histogram' \| 'summary'>` | ✅ | Metric type |
+| **timestamp** | `string` | ✅ | Observation timestamp |
+| **value** | `number` | optional | Metric value |
+| **labels** | `Record` | optional | Metric labels |
+| **histogram** | `object` | optional | |
+| **summary** | `object` | optional | |
+
+---
+
+## MetricDefinition
+
+Metric definition
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **name** | `string` | ✅ | Metric name (snake_case) |
+| **label** | `string` | optional | Display label |
+| **type** | `Enum<'counter' \| 'gauge' \| 'histogram' \| 'summary'>` | ✅ | Metric type |
+| **unit** | `Enum<'nanoseconds' \| 'microseconds' \| 'milliseconds' \| 'seconds' \| 'minutes' \| 'hours' \| 'days' \| 'bytes' \| 'kilobytes' \| 'megabytes' \| 'gigabytes' \| 'terabytes' \| 'requests_per_second' \| 'events_per_second' \| 'bytes_per_second' \| 'percent' \| 'ratio' \| 'count' \| 'operations' \| 'custom'>` | optional | Metric unit |
+| **description** | `string` | optional | Metric description |
+| **labelNames** | `string[]` | optional | Label names |
+| **histogram** | `object` | optional | Histogram bucket configuration |
+| **summary** | `object` | optional | |
+| **enabled** | `boolean` | optional | |
+
+---
+
+## MetricExportConfig
+
+Metric export configuration
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **type** | `Enum<'prometheus' \| 'openmetrics' \| 'graphite' \| 'statsd' \| 'influxdb' \| 'datadog' \| 'cloudwatch' \| 'stackdriver' \| 'azure_monitor' \| 'http' \| 'custom'>` | ✅ | Export type |
+| **endpoint** | `string` | optional | Export endpoint |
+| **interval** | `integer` | optional | |
+| **batch** | `object` | optional | |
+| **auth** | `object` | optional | |
+| **config** | `Record` | optional | Additional configuration |
+
+---
+
+## MetricLabels
+
+Metric labels
+
+---
+
+## MetricType
+
+Metric type
+
+### Allowed Values
+
+* `counter`
+* `gauge`
+* `histogram`
+* `summary`
+
+---
+
+## MetricUnit
+
+Metric unit
+
+### Allowed Values
+
+* `nanoseconds`
+* `microseconds`
+* `milliseconds`
+* `seconds`
+* `minutes`
+* `hours`
+* `days`
+* `bytes`
+* `kilobytes`
+* `megabytes`
+* `gigabytes`
+* `terabytes`
+* `requests_per_second`
+* `events_per_second`
+* `bytes_per_second`
+* `percent`
+* `ratio`
+* `count`
+* `operations`
+* `custom`
+
+---
+
+## MetricsConfig
+
+Metrics configuration
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **name** | `string` | ✅ | Configuration name (snake_case, max 64 chars) |
+| **label** | `string` | ✅ | Display label |
+| **enabled** | `boolean` | optional | |
+| **metrics** | `object[]` | optional | |
+| **defaultLabels** | `Record` | optional | Metric labels |
+| **aggregations** | `object[]` | optional | |
+| **slis** | `object[]` | optional | |
+| **slos** | `object[]` | optional | |
+| **exports** | `object[]` | optional | |
+| **collectionInterval** | `integer` | optional | |
+| **retention** | `object` | optional | |
+| **cardinalityLimits** | `object` | optional | |
+
+---
+
+## ServiceLevelIndicator
+
+Service Level Indicator
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **name** | `string` | ✅ | SLI name (snake_case) |
+| **label** | `string` | ✅ | Display label |
+| **description** | `string` | optional | SLI description |
+| **metric** | `string` | ✅ | Base metric name |
+| **type** | `Enum<'availability' \| 'latency' \| 'throughput' \| 'error_rate' \| 'saturation' \| 'custom'>` | ✅ | SLI type |
+| **successCriteria** | `object` | ✅ | Success criteria |
+| **window** | `object` | ✅ | Measurement window |
+| **enabled** | `boolean` | optional | |
+
+---
+
+## ServiceLevelObjective
+
+Service Level Objective
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **name** | `string` | ✅ | SLO name (snake_case) |
+| **label** | `string` | ✅ | Display label |
+| **description** | `string` | optional | SLO description |
+| **sli** | `string` | ✅ | SLI name |
+| **target** | `number` | ✅ | Target percentage |
+| **period** | `object` | ✅ | Time period |
+| **errorBudget** | `object` | optional | |
+| **alerts** | `object[]` | optional | |
+| **enabled** | `boolean` | optional | |
+
+---
+
+## TimeSeries
+
+Time series
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **name** | `string` | ✅ | Series name |
+| **labels** | `Record` | optional | Series labels |
+| **dataPoints** | `object[]` | ✅ | Data points |
+| **startTime** | `string` | optional | Start time |
+| **endTime** | `string` | optional | End time |
+
+---
+
+## TimeSeriesDataPoint
+
+Time series data point
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **timestamp** | `string` | ✅ | Timestamp |
+| **value** | `number` | ✅ | Value |
+| **labels** | `Record` | optional | Labels |
+
diff --git a/content/docs/references/system/tracing.mdx b/content/docs/references/system/tracing.mdx
new file mode 100644
index 000000000..f5b5df178
--- /dev/null
+++ b/content/docs/references/system/tracing.mdx
@@ -0,0 +1,280 @@
+---
+title: Tracing
+description: Tracing protocol schemas
+---
+
+# Tracing
+
+
+**Source:** `packages/spec/src/system/tracing.zod.ts`
+
+
+## TypeScript Usage
+
+```typescript
+import { OpenTelemetryCompatibilitySchema, OtelExporterTypeSchema, SamplingDecisionSchema, SamplingStrategyTypeSchema, SpanSchema, SpanAttributeValueSchema, SpanAttributesSchema, SpanEventSchema, SpanKindSchema, SpanLinkSchema, SpanStatusSchema, TraceContextSchema, TraceContextPropagationSchema, TraceFlagsSchema, TracePropagationFormatSchema, TraceSamplingConfigSchema, TraceStateSchema, TracingConfigSchema } from '@objectstack/spec/system';
+import type { OpenTelemetryCompatibility, OtelExporterType, SamplingDecision, SamplingStrategyType, Span, SpanAttributeValue, SpanAttributes, SpanEvent, SpanKind, SpanLink, SpanStatus, TraceContext, TraceContextPropagation, TraceFlags, TracePropagationFormat, TraceSamplingConfig, TraceState, TracingConfig } from '@objectstack/spec/system';
+
+// Validate data
+const result = OpenTelemetryCompatibilitySchema.parse(data);
+```
+
+---
+
+## OpenTelemetryCompatibility
+
+OpenTelemetry compatibility configuration
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **sdkVersion** | `string` | optional | OTel SDK version |
+| **exporter** | `object` | ✅ | Exporter configuration |
+| **resource** | `object` | ✅ | Resource attributes |
+| **instrumentation** | `object` | optional | |
+| **semanticConventionsVersion** | `string` | optional | Semantic conventions version |
+
+---
+
+## OtelExporterType
+
+OpenTelemetry exporter type
+
+### Allowed Values
+
+* `otlp_http`
+* `otlp_grpc`
+* `jaeger`
+* `zipkin`
+* `console`
+* `datadog`
+* `honeycomb`
+* `lightstep`
+* `newrelic`
+* `custom`
+
+---
+
+## SamplingDecision
+
+Sampling decision
+
+### Allowed Values
+
+* `drop`
+* `record_only`
+* `record_and_sample`
+
+---
+
+## SamplingStrategyType
+
+Sampling strategy type
+
+### Allowed Values
+
+* `always_on`
+* `always_off`
+* `trace_id_ratio`
+* `rate_limiting`
+* `parent_based`
+* `probability`
+* `composite`
+* `custom`
+
+---
+
+## Span
+
+OpenTelemetry span
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **context** | `object` | ✅ | Trace context |
+| **name** | `string` | ✅ | Span name |
+| **kind** | `Enum<'internal' \| 'server' \| 'client' \| 'producer' \| 'consumer'>` | optional | Span kind |
+| **startTime** | `string` | ✅ | Span start time |
+| **endTime** | `string` | optional | Span end time |
+| **duration** | `number` | optional | Duration in milliseconds |
+| **status** | `object` | optional | |
+| **attributes** | `Record` | optional | Span attributes |
+| **events** | `object[]` | optional | |
+| **links** | `object[]` | optional | |
+| **resource** | `Record` | optional | Resource attributes |
+| **instrumentationLibrary** | `object` | optional | |
+
+---
+
+## SpanAttributeValue
+
+Span attribute value
+
+---
+
+## SpanAttributes
+
+Span attributes
+
+---
+
+## SpanEvent
+
+Span event
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **name** | `string` | ✅ | Event name |
+| **timestamp** | `string` | ✅ | Event timestamp |
+| **attributes** | `Record` | optional | Event attributes |
+
+---
+
+## SpanKind
+
+Span kind
+
+### Allowed Values
+
+* `internal`
+* `server`
+* `client`
+* `producer`
+* `consumer`
+
+---
+
+## SpanLink
+
+Span link
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **context** | `object` | ✅ | Linked trace context |
+| **attributes** | `Record` | optional | Link attributes |
+
+---
+
+## SpanStatus
+
+Span status
+
+### Allowed Values
+
+* `unset`
+* `ok`
+* `error`
+
+---
+
+## TraceContext
+
+Trace context (W3C Trace Context)
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **traceId** | `string` | ✅ | Trace ID (32 hex chars) |
+| **spanId** | `string` | ✅ | Span ID (16 hex chars) |
+| **traceFlags** | `integer` | optional | Trace flags bitmap |
+| **traceState** | `object` | optional | Trace state |
+| **parentSpanId** | `string` | optional | Parent span ID (16 hex chars) |
+| **sampled** | `boolean` | optional | |
+| **remote** | `boolean` | optional | |
+
+---
+
+## TraceContextPropagation
+
+Trace context propagation
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **formats** | `Enum<'w3c' \| 'b3' \| 'b3_multi' \| 'jaeger' \| 'xray' \| 'ottrace' \| 'custom'>[]` | optional | |
+| **extract** | `boolean` | optional | |
+| **inject** | `boolean` | optional | |
+| **headers** | `object` | optional | |
+| **baggage** | `object` | optional | |
+
+---
+
+## TraceFlags
+
+Trace flags bitmap
+
+---
+
+## TracePropagationFormat
+
+Trace propagation format
+
+### Allowed Values
+
+* `w3c`
+* `b3`
+* `b3_multi`
+* `jaeger`
+* `xray`
+* `ottrace`
+* `custom`
+
+---
+
+## TraceSamplingConfig
+
+Trace sampling configuration
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **type** | `Enum<'always_on' \| 'always_off' \| 'trace_id_ratio' \| 'rate_limiting' \| 'parent_based' \| 'probability' \| 'composite' \| 'custom'>` | ✅ | Sampling strategy |
+| **ratio** | `number` | optional | Sample ratio (0-1) |
+| **rateLimit** | `number` | optional | Traces per second |
+| **parentBased** | `object` | optional | |
+| **composite** | `object[]` | optional | |
+| **rules** | `object[]` | optional | |
+| **customSamplerId** | `string` | optional | Custom sampler identifier |
+
+---
+
+## TraceState
+
+Trace state
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **entries** | `Record` | ✅ | Trace state entries |
+
+---
+
+## TracingConfig
+
+Tracing configuration
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **name** | `string` | ✅ | Configuration name (snake_case, max 64 chars) |
+| **label** | `string` | ✅ | Display label |
+| **enabled** | `boolean` | optional | |
+| **sampling** | `object` | optional | Trace sampling configuration |
+| **propagation** | `object` | optional | Trace context propagation |
+| **openTelemetry** | `object` | optional | OpenTelemetry compatibility configuration |
+| **spanLimits** | `object` | optional | |
+| **traceIdGenerator** | `Enum<'random' \| 'uuid' \| 'custom'>` | optional | |
+| **customTraceIdGeneratorId** | `string` | optional | Custom generator identifier |
+| **performance** | `object` | optional | |
+
diff --git a/packages/spec/json-schema/api/BatchOperationResult.json b/packages/spec/json-schema/api/BatchOperationResult.json
new file mode 100644
index 000000000..1d80b885e
--- /dev/null
+++ b/packages/spec/json-schema/api/BatchOperationResult.json
@@ -0,0 +1,57 @@
+{
+ "$ref": "#/definitions/BatchOperationResult",
+ "definitions": {
+ "BatchOperationResult": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "description": "Record ID if operation succeeded"
+ },
+ "success": {
+ "type": "boolean",
+ "description": "Whether this record was processed successfully"
+ },
+ "errors": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "code": {
+ "type": "string",
+ "description": "Error code (e.g. validation_error)"
+ },
+ "message": {
+ "type": "string",
+ "description": "Readable error message"
+ },
+ "details": {
+ "description": "Additional error context (e.g. field validation errors)"
+ }
+ },
+ "required": [
+ "code",
+ "message"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Array of errors if operation failed"
+ },
+ "data": {
+ "type": "object",
+ "additionalProperties": {},
+ "description": "Full record data (if returnRecords=true)"
+ },
+ "index": {
+ "type": "number",
+ "description": "Index of the record in the request array"
+ }
+ },
+ "required": [
+ "success"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/BatchOperationType.json b/packages/spec/json-schema/api/BatchOperationType.json
new file mode 100644
index 000000000..9497b773f
--- /dev/null
+++ b/packages/spec/json-schema/api/BatchOperationType.json
@@ -0,0 +1,15 @@
+{
+ "$ref": "#/definitions/BatchOperationType",
+ "definitions": {
+ "BatchOperationType": {
+ "type": "string",
+ "enum": [
+ "create",
+ "update",
+ "upsert",
+ "delete"
+ ]
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/BatchOptions.json b/packages/spec/json-schema/api/BatchOptions.json
new file mode 100644
index 000000000..7e8ac0ad1
--- /dev/null
+++ b/packages/spec/json-schema/api/BatchOptions.json
@@ -0,0 +1,32 @@
+{
+ "$ref": "#/definitions/BatchOptions",
+ "definitions": {
+ "BatchOptions": {
+ "type": "object",
+ "properties": {
+ "atomic": {
+ "type": "boolean",
+ "default": true,
+ "description": "If true, rollback entire batch on any failure (transaction mode)"
+ },
+ "returnRecords": {
+ "type": "boolean",
+ "default": false,
+ "description": "If true, return full record data in response"
+ },
+ "continueOnError": {
+ "type": "boolean",
+ "default": false,
+ "description": "If true (and atomic=false), continue processing remaining records after errors"
+ },
+ "validateOnly": {
+ "type": "boolean",
+ "default": false,
+ "description": "If true, validate records without persisting changes (dry-run mode)"
+ }
+ },
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/BatchRecord.json b/packages/spec/json-schema/api/BatchRecord.json
new file mode 100644
index 000000000..9a5a8e480
--- /dev/null
+++ b/packages/spec/json-schema/api/BatchRecord.json
@@ -0,0 +1,25 @@
+{
+ "$ref": "#/definitions/BatchRecord",
+ "definitions": {
+ "BatchRecord": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "description": "Record ID (required for update/delete)"
+ },
+ "data": {
+ "type": "object",
+ "additionalProperties": {},
+ "description": "Record data (required for create/update/upsert)"
+ },
+ "externalId": {
+ "type": "string",
+ "description": "External ID for upsert matching"
+ }
+ },
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/BatchUpdateRequest.json b/packages/spec/json-schema/api/BatchUpdateRequest.json
new file mode 100644
index 000000000..5d7559931
--- /dev/null
+++ b/packages/spec/json-schema/api/BatchUpdateRequest.json
@@ -0,0 +1,78 @@
+{
+ "$ref": "#/definitions/BatchUpdateRequest",
+ "definitions": {
+ "BatchUpdateRequest": {
+ "type": "object",
+ "properties": {
+ "operation": {
+ "type": "string",
+ "enum": [
+ "create",
+ "update",
+ "upsert",
+ "delete"
+ ],
+ "description": "Type of batch operation"
+ },
+ "records": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "description": "Record ID (required for update/delete)"
+ },
+ "data": {
+ "type": "object",
+ "additionalProperties": {},
+ "description": "Record data (required for create/update/upsert)"
+ },
+ "externalId": {
+ "type": "string",
+ "description": "External ID for upsert matching"
+ }
+ },
+ "additionalProperties": false
+ },
+ "minItems": 1,
+ "maxItems": 200,
+ "description": "Array of records to process (max 200 per batch)"
+ },
+ "options": {
+ "type": "object",
+ "properties": {
+ "atomic": {
+ "type": "boolean",
+ "default": true,
+ "description": "If true, rollback entire batch on any failure (transaction mode)"
+ },
+ "returnRecords": {
+ "type": "boolean",
+ "default": false,
+ "description": "If true, return full record data in response"
+ },
+ "continueOnError": {
+ "type": "boolean",
+ "default": false,
+ "description": "If true (and atomic=false), continue processing remaining records after errors"
+ },
+ "validateOnly": {
+ "type": "boolean",
+ "default": false,
+ "description": "If true, validate records without persisting changes (dry-run mode)"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Batch operation options"
+ }
+ },
+ "required": [
+ "operation",
+ "records"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/BatchUpdateResponse.json b/packages/spec/json-schema/api/BatchUpdateResponse.json
new file mode 100644
index 000000000..07f09666e
--- /dev/null
+++ b/packages/spec/json-schema/api/BatchUpdateResponse.json
@@ -0,0 +1,144 @@
+{
+ "$ref": "#/definitions/BatchUpdateResponse",
+ "definitions": {
+ "BatchUpdateResponse": {
+ "type": "object",
+ "properties": {
+ "success": {
+ "type": "boolean",
+ "description": "Operation success status"
+ },
+ "error": {
+ "type": "object",
+ "properties": {
+ "code": {
+ "type": "string",
+ "description": "Error code (e.g. validation_error)"
+ },
+ "message": {
+ "type": "string",
+ "description": "Readable error message"
+ },
+ "details": {
+ "description": "Additional error context (e.g. field validation errors)"
+ }
+ },
+ "required": [
+ "code",
+ "message"
+ ],
+ "additionalProperties": false,
+ "description": "Error details if success is false"
+ },
+ "meta": {
+ "type": "object",
+ "properties": {
+ "timestamp": {
+ "type": "string"
+ },
+ "duration": {
+ "type": "number"
+ },
+ "requestId": {
+ "type": "string"
+ },
+ "traceId": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "timestamp"
+ ],
+ "additionalProperties": false,
+ "description": "Response metadata"
+ },
+ "operation": {
+ "type": "string",
+ "enum": [
+ "create",
+ "update",
+ "upsert",
+ "delete"
+ ],
+ "description": "Operation type that was performed"
+ },
+ "total": {
+ "type": "number",
+ "description": "Total number of records in the batch"
+ },
+ "succeeded": {
+ "type": "number",
+ "description": "Number of records that succeeded"
+ },
+ "failed": {
+ "type": "number",
+ "description": "Number of records that failed"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "description": "Record ID if operation succeeded"
+ },
+ "success": {
+ "type": "boolean",
+ "description": "Whether this record was processed successfully"
+ },
+ "errors": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "code": {
+ "type": "string",
+ "description": "Error code (e.g. validation_error)"
+ },
+ "message": {
+ "type": "string",
+ "description": "Readable error message"
+ },
+ "details": {
+ "description": "Additional error context (e.g. field validation errors)"
+ }
+ },
+ "required": [
+ "code",
+ "message"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Array of errors if operation failed"
+ },
+ "data": {
+ "type": "object",
+ "additionalProperties": {},
+ "description": "Full record data (if returnRecords=true)"
+ },
+ "index": {
+ "type": "number",
+ "description": "Index of the record in the request array"
+ }
+ },
+ "required": [
+ "success"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Detailed results for each record"
+ }
+ },
+ "required": [
+ "success",
+ "total",
+ "succeeded",
+ "failed",
+ "results"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/CacheControl.json b/packages/spec/json-schema/api/CacheControl.json
new file mode 100644
index 000000000..5048b0ac9
--- /dev/null
+++ b/packages/spec/json-schema/api/CacheControl.json
@@ -0,0 +1,42 @@
+{
+ "$ref": "#/definitions/CacheControl",
+ "definitions": {
+ "CacheControl": {
+ "type": "object",
+ "properties": {
+ "directives": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "enum": [
+ "public",
+ "private",
+ "no-cache",
+ "no-store",
+ "must-revalidate",
+ "max-age"
+ ]
+ },
+ "description": "Cache control directives"
+ },
+ "maxAge": {
+ "type": "number",
+ "description": "Maximum cache age in seconds"
+ },
+ "staleWhileRevalidate": {
+ "type": "number",
+ "description": "Allow serving stale content while revalidating (seconds)"
+ },
+ "staleIfError": {
+ "type": "number",
+ "description": "Allow serving stale content on error (seconds)"
+ }
+ },
+ "required": [
+ "directives"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/CacheDirective.json b/packages/spec/json-schema/api/CacheDirective.json
new file mode 100644
index 000000000..3eb523129
--- /dev/null
+++ b/packages/spec/json-schema/api/CacheDirective.json
@@ -0,0 +1,17 @@
+{
+ "$ref": "#/definitions/CacheDirective",
+ "definitions": {
+ "CacheDirective": {
+ "type": "string",
+ "enum": [
+ "public",
+ "private",
+ "no-cache",
+ "no-store",
+ "must-revalidate",
+ "max-age"
+ ]
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/CacheInvalidationRequest.json b/packages/spec/json-schema/api/CacheInvalidationRequest.json
new file mode 100644
index 000000000..203a39086
--- /dev/null
+++ b/packages/spec/json-schema/api/CacheInvalidationRequest.json
@@ -0,0 +1,43 @@
+{
+ "$ref": "#/definitions/CacheInvalidationRequest",
+ "definitions": {
+ "CacheInvalidationRequest": {
+ "type": "object",
+ "properties": {
+ "target": {
+ "type": "string",
+ "enum": [
+ "all",
+ "object",
+ "field",
+ "permission",
+ "layout",
+ "custom"
+ ],
+ "description": "What to invalidate"
+ },
+ "identifiers": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Specific resources to invalidate (e.g., object names)"
+ },
+ "cascade": {
+ "type": "boolean",
+ "default": false,
+ "description": "If true, invalidate dependent resources"
+ },
+ "pattern": {
+ "type": "string",
+ "description": "Pattern for custom invalidation (supports wildcards)"
+ }
+ },
+ "required": [
+ "target"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/CacheInvalidationResponse.json b/packages/spec/json-schema/api/CacheInvalidationResponse.json
new file mode 100644
index 000000000..cdb542066
--- /dev/null
+++ b/packages/spec/json-schema/api/CacheInvalidationResponse.json
@@ -0,0 +1,31 @@
+{
+ "$ref": "#/definitions/CacheInvalidationResponse",
+ "definitions": {
+ "CacheInvalidationResponse": {
+ "type": "object",
+ "properties": {
+ "success": {
+ "type": "boolean",
+ "description": "Whether invalidation succeeded"
+ },
+ "invalidated": {
+ "type": "number",
+ "description": "Number of cache entries invalidated"
+ },
+ "targets": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "List of invalidated resources"
+ }
+ },
+ "required": [
+ "success",
+ "invalidated"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/CacheInvalidationTarget.json b/packages/spec/json-schema/api/CacheInvalidationTarget.json
new file mode 100644
index 000000000..9c174cdbc
--- /dev/null
+++ b/packages/spec/json-schema/api/CacheInvalidationTarget.json
@@ -0,0 +1,17 @@
+{
+ "$ref": "#/definitions/CacheInvalidationTarget",
+ "definitions": {
+ "CacheInvalidationTarget": {
+ "type": "string",
+ "enum": [
+ "all",
+ "object",
+ "field",
+ "permission",
+ "layout",
+ "custom"
+ ]
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/CreateViewRequest.json b/packages/spec/json-schema/api/CreateViewRequest.json
new file mode 100644
index 000000000..01f22609b
--- /dev/null
+++ b/packages/spec/json-schema/api/CreateViewRequest.json
@@ -0,0 +1,1042 @@
+{
+ "$ref": "#/definitions/CreateViewRequest",
+ "definitions": {
+ "CreateViewRequest": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "pattern": "^[a-z_][a-z0-9_]*$",
+ "description": "View machine name"
+ },
+ "label": {
+ "type": "string",
+ "description": "Display label"
+ },
+ "description": {
+ "type": "string"
+ },
+ "object": {
+ "type": "string",
+ "description": "Object name"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "list",
+ "kanban",
+ "calendar",
+ "gantt",
+ "timeline",
+ "chart",
+ "pivot",
+ "custom"
+ ],
+ "description": "View type"
+ },
+ "visibility": {
+ "type": "string",
+ "enum": [
+ "private",
+ "shared",
+ "public",
+ "organization"
+ ],
+ "description": "View visibility"
+ },
+ "query": {
+ "type": "object",
+ "properties": {
+ "object": {
+ "type": "string",
+ "description": "Object name (e.g. account)"
+ },
+ "fields": {
+ "type": "array",
+ "items": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string"
+ },
+ "fields": {
+ "type": "array",
+ "items": {}
+ },
+ "alias": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "field"
+ ],
+ "additionalProperties": false
+ }
+ ]
+ },
+ "description": "Fields to retrieve"
+ },
+ "where": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {}
+ },
+ "$or": {
+ "type": "array",
+ "items": {}
+ },
+ "$not": {}
+ }
+ }
+ ],
+ "description": "Filtering criteria (WHERE)"
+ },
+ "search": {
+ "type": "object",
+ "properties": {
+ "query": {
+ "type": "string",
+ "description": "Search query text"
+ },
+ "fields": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Fields to search in (if not specified, searches all text fields)"
+ },
+ "fuzzy": {
+ "type": "boolean",
+ "default": false,
+ "description": "Enable fuzzy matching (tolerates typos)"
+ },
+ "operator": {
+ "type": "string",
+ "enum": [
+ "and",
+ "or"
+ ],
+ "default": "or",
+ "description": "Logical operator between terms"
+ },
+ "boost": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "number"
+ },
+ "description": "Field-specific relevance boosting (field name -> boost factor)"
+ },
+ "minScore": {
+ "type": "number",
+ "description": "Minimum relevance score threshold"
+ },
+ "language": {
+ "type": "string",
+ "description": "Language for text analysis (e.g., \"en\", \"zh\", \"es\")"
+ },
+ "highlight": {
+ "type": "boolean",
+ "default": false,
+ "description": "Enable search result highlighting"
+ }
+ },
+ "required": [
+ "query"
+ ],
+ "additionalProperties": false,
+ "description": "Full-text search configuration ($search parameter)"
+ },
+ "orderBy": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string"
+ },
+ "order": {
+ "type": "string",
+ "enum": [
+ "asc",
+ "desc"
+ ],
+ "default": "asc"
+ }
+ },
+ "required": [
+ "field"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Sorting instructions (ORDER BY)"
+ },
+ "limit": {
+ "type": "number",
+ "description": "Max records to return (LIMIT)"
+ },
+ "offset": {
+ "type": "number",
+ "description": "Records to skip (OFFSET)"
+ },
+ "cursor": {
+ "type": "object",
+ "additionalProperties": {},
+ "description": "Cursor for keyset pagination"
+ },
+ "joins": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "inner",
+ "left",
+ "right",
+ "full"
+ ],
+ "description": "Join type"
+ },
+ "strategy": {
+ "type": "string",
+ "enum": [
+ "auto",
+ "database",
+ "hash",
+ "loop"
+ ],
+ "description": "Execution strategy hint"
+ },
+ "object": {
+ "type": "string",
+ "description": "Object/table to join"
+ },
+ "alias": {
+ "type": "string",
+ "description": "Table alias"
+ },
+ "on": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {}
+ },
+ "$or": {
+ "type": "array",
+ "items": {}
+ },
+ "$not": {}
+ }
+ }
+ ]
+ }
+ },
+ "$or": {
+ "type": "array",
+ "items": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {}
+ },
+ "$or": {
+ "type": "array",
+ "items": {}
+ },
+ "$not": {}
+ }
+ }
+ ]
+ }
+ },
+ "$not": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {}
+ },
+ "$or": {
+ "type": "array",
+ "items": {}
+ },
+ "$not": {}
+ }
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "description": "Join condition"
+ },
+ "subquery": {
+ "type": "object",
+ "properties": {
+ "object": {
+ "type": "string",
+ "description": "Object name (e.g. account)"
+ },
+ "fields": {
+ "type": "array",
+ "items": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string"
+ },
+ "fields": {
+ "type": "array",
+ "items": {}
+ },
+ "alias": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "field"
+ ],
+ "additionalProperties": false
+ }
+ ]
+ },
+ "description": "Fields to retrieve"
+ },
+ "where": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {}
+ },
+ "$or": {
+ "type": "array",
+ "items": {}
+ },
+ "$not": {}
+ }
+ }
+ ],
+ "description": "Filtering criteria (WHERE)"
+ },
+ "search": {
+ "type": "object",
+ "properties": {
+ "query": {
+ "type": "string",
+ "description": "Search query text"
+ },
+ "fields": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Fields to search in (if not specified, searches all text fields)"
+ },
+ "fuzzy": {
+ "type": "boolean",
+ "default": false,
+ "description": "Enable fuzzy matching (tolerates typos)"
+ },
+ "operator": {
+ "type": "string",
+ "enum": [
+ "and",
+ "or"
+ ],
+ "default": "or",
+ "description": "Logical operator between terms"
+ },
+ "boost": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "number"
+ },
+ "description": "Field-specific relevance boosting (field name -> boost factor)"
+ },
+ "minScore": {
+ "type": "number",
+ "description": "Minimum relevance score threshold"
+ },
+ "language": {
+ "type": "string",
+ "description": "Language for text analysis (e.g., \"en\", \"zh\", \"es\")"
+ },
+ "highlight": {
+ "type": "boolean",
+ "default": false,
+ "description": "Enable search result highlighting"
+ }
+ },
+ "required": [
+ "query"
+ ],
+ "additionalProperties": false,
+ "description": "Full-text search configuration ($search parameter)"
+ },
+ "orderBy": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string"
+ },
+ "order": {
+ "type": "string",
+ "enum": [
+ "asc",
+ "desc"
+ ],
+ "default": "asc"
+ }
+ },
+ "required": [
+ "field"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Sorting instructions (ORDER BY)"
+ },
+ "limit": {
+ "type": "number",
+ "description": "Max records to return (LIMIT)"
+ },
+ "offset": {
+ "type": "number",
+ "description": "Records to skip (OFFSET)"
+ },
+ "cursor": {
+ "type": "object",
+ "additionalProperties": {},
+ "description": "Cursor for keyset pagination"
+ },
+ "joins": {},
+ "aggregations": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "function": {
+ "type": "string",
+ "enum": [
+ "count",
+ "sum",
+ "avg",
+ "min",
+ "max",
+ "count_distinct",
+ "array_agg",
+ "string_agg"
+ ],
+ "description": "Aggregation function"
+ },
+ "field": {
+ "type": "string",
+ "description": "Field to aggregate (optional for COUNT(*))"
+ },
+ "alias": {
+ "type": "string",
+ "description": "Result column alias"
+ },
+ "distinct": {
+ "type": "boolean",
+ "description": "Apply DISTINCT before aggregation"
+ },
+ "filter": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {}
+ },
+ "$or": {
+ "type": "array",
+ "items": {}
+ },
+ "$not": {}
+ }
+ }
+ ],
+ "description": "Filter/Condition to apply to the aggregation (FILTER WHERE clause)"
+ }
+ },
+ "required": [
+ "function",
+ "alias"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Aggregation functions"
+ },
+ "groupBy": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "GROUP BY fields"
+ },
+ "having": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {}
+ },
+ "$or": {
+ "type": "array",
+ "items": {}
+ },
+ "$not": {}
+ }
+ }
+ ],
+ "description": "HAVING clause for aggregation filtering"
+ },
+ "windowFunctions": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "function": {
+ "type": "string",
+ "enum": [
+ "row_number",
+ "rank",
+ "dense_rank",
+ "percent_rank",
+ "lag",
+ "lead",
+ "first_value",
+ "last_value",
+ "sum",
+ "avg",
+ "count",
+ "min",
+ "max"
+ ],
+ "description": "Window function name"
+ },
+ "field": {
+ "type": "string",
+ "description": "Field to operate on (for aggregate window functions)"
+ },
+ "alias": {
+ "type": "string",
+ "description": "Result column alias"
+ },
+ "over": {
+ "type": "object",
+ "properties": {
+ "partitionBy": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "PARTITION BY fields"
+ },
+ "orderBy": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string"
+ },
+ "order": {
+ "type": "string",
+ "enum": [
+ "asc",
+ "desc"
+ ],
+ "default": "asc"
+ }
+ },
+ "required": [
+ "field"
+ ],
+ "additionalProperties": false
+ },
+ "description": "ORDER BY specification"
+ },
+ "frame": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "rows",
+ "range"
+ ]
+ },
+ "start": {
+ "type": "string",
+ "description": "Frame start (e.g., \"UNBOUNDED PRECEDING\", \"1 PRECEDING\")"
+ },
+ "end": {
+ "type": "string",
+ "description": "Frame end (e.g., \"CURRENT ROW\", \"1 FOLLOWING\")"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Window frame specification"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Window specification (OVER clause)"
+ }
+ },
+ "required": [
+ "function",
+ "alias",
+ "over"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Window functions with OVER clause"
+ },
+ "distinct": {
+ "type": "boolean",
+ "description": "SELECT DISTINCT flag"
+ }
+ },
+ "required": [
+ "object"
+ ],
+ "additionalProperties": false,
+ "description": "Subquery instead of object"
+ }
+ },
+ "required": [
+ "type",
+ "object",
+ "on"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Explicit Table Joins"
+ },
+ "aggregations": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "function": {
+ "type": "string",
+ "enum": [
+ "count",
+ "sum",
+ "avg",
+ "min",
+ "max",
+ "count_distinct",
+ "array_agg",
+ "string_agg"
+ ],
+ "description": "Aggregation function"
+ },
+ "field": {
+ "type": "string",
+ "description": "Field to aggregate (optional for COUNT(*))"
+ },
+ "alias": {
+ "type": "string",
+ "description": "Result column alias"
+ },
+ "distinct": {
+ "type": "boolean",
+ "description": "Apply DISTINCT before aggregation"
+ },
+ "filter": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {}
+ },
+ "$or": {
+ "type": "array",
+ "items": {}
+ },
+ "$not": {}
+ }
+ }
+ ],
+ "description": "Filter/Condition to apply to the aggregation (FILTER WHERE clause)"
+ }
+ },
+ "required": [
+ "function",
+ "alias"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Aggregation functions"
+ },
+ "groupBy": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "GROUP BY fields"
+ },
+ "having": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {}
+ },
+ "$or": {
+ "type": "array",
+ "items": {}
+ },
+ "$not": {}
+ }
+ }
+ ],
+ "description": "HAVING clause for aggregation filtering"
+ },
+ "windowFunctions": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "function": {
+ "type": "string",
+ "enum": [
+ "row_number",
+ "rank",
+ "dense_rank",
+ "percent_rank",
+ "lag",
+ "lead",
+ "first_value",
+ "last_value",
+ "sum",
+ "avg",
+ "count",
+ "min",
+ "max"
+ ],
+ "description": "Window function name"
+ },
+ "field": {
+ "type": "string",
+ "description": "Field to operate on (for aggregate window functions)"
+ },
+ "alias": {
+ "type": "string",
+ "description": "Result column alias"
+ },
+ "over": {
+ "type": "object",
+ "properties": {
+ "partitionBy": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "PARTITION BY fields"
+ },
+ "orderBy": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string"
+ },
+ "order": {
+ "type": "string",
+ "enum": [
+ "asc",
+ "desc"
+ ],
+ "default": "asc"
+ }
+ },
+ "required": [
+ "field"
+ ],
+ "additionalProperties": false
+ },
+ "description": "ORDER BY specification"
+ },
+ "frame": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "rows",
+ "range"
+ ]
+ },
+ "start": {
+ "type": "string",
+ "description": "Frame start (e.g., \"UNBOUNDED PRECEDING\", \"1 PRECEDING\")"
+ },
+ "end": {
+ "type": "string",
+ "description": "Frame end (e.g., \"CURRENT ROW\", \"1 FOLLOWING\")"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Window frame specification"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Window specification (OVER clause)"
+ }
+ },
+ "required": [
+ "function",
+ "alias",
+ "over"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Window functions with OVER clause"
+ },
+ "distinct": {
+ "type": "boolean",
+ "description": "SELECT DISTINCT flag"
+ }
+ },
+ "required": [
+ "object"
+ ],
+ "additionalProperties": false,
+ "description": "Query configuration"
+ },
+ "layout": {
+ "type": "object",
+ "properties": {
+ "columns": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string",
+ "description": "Field name"
+ },
+ "label": {
+ "type": "string",
+ "description": "Custom column label"
+ },
+ "width": {
+ "type": "number",
+ "description": "Column width in pixels"
+ },
+ "sortable": {
+ "type": "boolean",
+ "default": true,
+ "description": "Whether column is sortable"
+ },
+ "filterable": {
+ "type": "boolean",
+ "default": true,
+ "description": "Whether column is filterable"
+ },
+ "visible": {
+ "type": "boolean",
+ "default": true,
+ "description": "Whether column is visible"
+ },
+ "pinned": {
+ "type": "string",
+ "enum": [
+ "left",
+ "right"
+ ],
+ "description": "Pin column to left or right"
+ },
+ "formatter": {
+ "type": "string",
+ "description": "Custom formatter name"
+ },
+ "aggregation": {
+ "type": "string",
+ "description": "Aggregation function for column (sum, avg, etc.)"
+ }
+ },
+ "required": [
+ "field"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Column configuration for list views"
+ },
+ "rowHeight": {
+ "type": "number",
+ "description": "Row height in pixels"
+ },
+ "groupByField": {
+ "type": "string",
+ "description": "Field to group by (for kanban)"
+ },
+ "cardFields": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Fields to display on cards"
+ },
+ "dateField": {
+ "type": "string",
+ "description": "Date field for calendar view"
+ },
+ "startDateField": {
+ "type": "string",
+ "description": "Start date field for event ranges"
+ },
+ "endDateField": {
+ "type": "string",
+ "description": "End date field for event ranges"
+ },
+ "titleField": {
+ "type": "string",
+ "description": "Field to use as event title"
+ },
+ "chartType": {
+ "type": "string",
+ "enum": [
+ "bar",
+ "line",
+ "pie",
+ "scatter",
+ "area"
+ ],
+ "description": "Chart type"
+ },
+ "xAxis": {
+ "type": "string",
+ "description": "X-axis field"
+ },
+ "yAxis": {
+ "type": "string",
+ "description": "Y-axis field"
+ },
+ "series": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Series fields for multi-series charts"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Layout configuration"
+ },
+ "sharedWith": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Users/teams to share with"
+ },
+ "isDefault": {
+ "type": "boolean",
+ "default": false,
+ "description": "Set as default view"
+ },
+ "settings": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name",
+ "label",
+ "object",
+ "type",
+ "visibility",
+ "query"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/DeleteManyRequest.json b/packages/spec/json-schema/api/DeleteManyRequest.json
new file mode 100644
index 000000000..c3fee66fe
--- /dev/null
+++ b/packages/spec/json-schema/api/DeleteManyRequest.json
@@ -0,0 +1,51 @@
+{
+ "$ref": "#/definitions/DeleteManyRequest",
+ "definitions": {
+ "DeleteManyRequest": {
+ "type": "object",
+ "properties": {
+ "ids": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "minItems": 1,
+ "maxItems": 200,
+ "description": "Array of record IDs to delete (max 200)"
+ },
+ "options": {
+ "type": "object",
+ "properties": {
+ "atomic": {
+ "type": "boolean",
+ "default": true,
+ "description": "If true, rollback entire batch on any failure (transaction mode)"
+ },
+ "returnRecords": {
+ "type": "boolean",
+ "default": false,
+ "description": "If true, return full record data in response"
+ },
+ "continueOnError": {
+ "type": "boolean",
+ "default": false,
+ "description": "If true (and atomic=false), continue processing remaining records after errors"
+ },
+ "validateOnly": {
+ "type": "boolean",
+ "default": false,
+ "description": "If true, validate records without persisting changes (dry-run mode)"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Delete options"
+ }
+ },
+ "required": [
+ "ids"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/ETag.json b/packages/spec/json-schema/api/ETag.json
new file mode 100644
index 000000000..116c725eb
--- /dev/null
+++ b/packages/spec/json-schema/api/ETag.json
@@ -0,0 +1,24 @@
+{
+ "$ref": "#/definitions/ETag",
+ "definitions": {
+ "ETag": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "type": "string",
+ "description": "ETag value (hash or version identifier)"
+ },
+ "weak": {
+ "type": "boolean",
+ "default": false,
+ "description": "Whether this is a weak ETag"
+ }
+ },
+ "required": [
+ "value"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/EnhancedApiError.json b/packages/spec/json-schema/api/EnhancedApiError.json
new file mode 100644
index 000000000..1914271ec
--- /dev/null
+++ b/packages/spec/json-schema/api/EnhancedApiError.json
@@ -0,0 +1,226 @@
+{
+ "$ref": "#/definitions/EnhancedApiError",
+ "definitions": {
+ "EnhancedApiError": {
+ "type": "object",
+ "properties": {
+ "code": {
+ "type": "string",
+ "enum": [
+ "validation_error",
+ "invalid_field",
+ "missing_required_field",
+ "invalid_format",
+ "value_too_long",
+ "value_too_short",
+ "value_out_of_range",
+ "invalid_reference",
+ "duplicate_value",
+ "invalid_query",
+ "invalid_filter",
+ "invalid_sort",
+ "max_records_exceeded",
+ "unauthenticated",
+ "invalid_credentials",
+ "expired_token",
+ "invalid_token",
+ "session_expired",
+ "mfa_required",
+ "email_not_verified",
+ "permission_denied",
+ "insufficient_privileges",
+ "field_not_accessible",
+ "record_not_accessible",
+ "license_required",
+ "ip_restricted",
+ "time_restricted",
+ "resource_not_found",
+ "object_not_found",
+ "record_not_found",
+ "field_not_found",
+ "endpoint_not_found",
+ "resource_conflict",
+ "concurrent_modification",
+ "delete_restricted",
+ "duplicate_record",
+ "lock_conflict",
+ "rate_limit_exceeded",
+ "quota_exceeded",
+ "concurrent_limit_exceeded",
+ "internal_error",
+ "database_error",
+ "timeout",
+ "service_unavailable",
+ "not_implemented",
+ "external_service_error",
+ "integration_error",
+ "webhook_delivery_failed",
+ "batch_partial_failure",
+ "batch_complete_failure",
+ "transaction_failed"
+ ],
+ "description": "Machine-readable error code"
+ },
+ "message": {
+ "type": "string",
+ "description": "Human-readable error message"
+ },
+ "category": {
+ "type": "string",
+ "enum": [
+ "validation",
+ "authentication",
+ "authorization",
+ "not_found",
+ "conflict",
+ "rate_limit",
+ "server",
+ "external",
+ "maintenance"
+ ],
+ "description": "Error category"
+ },
+ "httpStatus": {
+ "type": "number",
+ "description": "HTTP status code"
+ },
+ "retryable": {
+ "type": "boolean",
+ "default": false,
+ "description": "Whether the request can be retried"
+ },
+ "retryStrategy": {
+ "type": "string",
+ "enum": [
+ "no_retry",
+ "retry_immediate",
+ "retry_backoff",
+ "retry_after"
+ ],
+ "description": "Recommended retry strategy"
+ },
+ "retryAfter": {
+ "type": "number",
+ "description": "Seconds to wait before retrying"
+ },
+ "details": {
+ "description": "Additional error context"
+ },
+ "fieldErrors": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string",
+ "description": "Field path (supports dot notation)"
+ },
+ "code": {
+ "type": "string",
+ "enum": [
+ "validation_error",
+ "invalid_field",
+ "missing_required_field",
+ "invalid_format",
+ "value_too_long",
+ "value_too_short",
+ "value_out_of_range",
+ "invalid_reference",
+ "duplicate_value",
+ "invalid_query",
+ "invalid_filter",
+ "invalid_sort",
+ "max_records_exceeded",
+ "unauthenticated",
+ "invalid_credentials",
+ "expired_token",
+ "invalid_token",
+ "session_expired",
+ "mfa_required",
+ "email_not_verified",
+ "permission_denied",
+ "insufficient_privileges",
+ "field_not_accessible",
+ "record_not_accessible",
+ "license_required",
+ "ip_restricted",
+ "time_restricted",
+ "resource_not_found",
+ "object_not_found",
+ "record_not_found",
+ "field_not_found",
+ "endpoint_not_found",
+ "resource_conflict",
+ "concurrent_modification",
+ "delete_restricted",
+ "duplicate_record",
+ "lock_conflict",
+ "rate_limit_exceeded",
+ "quota_exceeded",
+ "concurrent_limit_exceeded",
+ "internal_error",
+ "database_error",
+ "timeout",
+ "service_unavailable",
+ "not_implemented",
+ "external_service_error",
+ "integration_error",
+ "webhook_delivery_failed",
+ "batch_partial_failure",
+ "batch_complete_failure",
+ "transaction_failed"
+ ],
+ "description": "Error code for this field"
+ },
+ "message": {
+ "type": "string",
+ "description": "Human-readable error message"
+ },
+ "value": {
+ "description": "The invalid value that was provided"
+ },
+ "constraint": {
+ "description": "The constraint that was violated (e.g., max length)"
+ }
+ },
+ "required": [
+ "field",
+ "code",
+ "message"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Field-specific validation errors"
+ },
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "description": "When the error occurred"
+ },
+ "requestId": {
+ "type": "string",
+ "description": "Request ID for tracking"
+ },
+ "traceId": {
+ "type": "string",
+ "description": "Distributed trace ID"
+ },
+ "documentation": {
+ "type": "string",
+ "format": "uri",
+ "description": "URL to error documentation"
+ },
+ "helpText": {
+ "type": "string",
+ "description": "Suggested actions to resolve the error"
+ }
+ },
+ "required": [
+ "code",
+ "message"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/ErrorCategory.json b/packages/spec/json-schema/api/ErrorCategory.json
new file mode 100644
index 000000000..699310f72
--- /dev/null
+++ b/packages/spec/json-schema/api/ErrorCategory.json
@@ -0,0 +1,20 @@
+{
+ "$ref": "#/definitions/ErrorCategory",
+ "definitions": {
+ "ErrorCategory": {
+ "type": "string",
+ "enum": [
+ "validation",
+ "authentication",
+ "authorization",
+ "not_found",
+ "conflict",
+ "rate_limit",
+ "server",
+ "external",
+ "maintenance"
+ ]
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/ErrorResponse.json b/packages/spec/json-schema/api/ErrorResponse.json
new file mode 100644
index 000000000..0eb5099d8
--- /dev/null
+++ b/packages/spec/json-schema/api/ErrorResponse.json
@@ -0,0 +1,259 @@
+{
+ "$ref": "#/definitions/ErrorResponse",
+ "definitions": {
+ "ErrorResponse": {
+ "type": "object",
+ "properties": {
+ "success": {
+ "type": "boolean",
+ "const": false,
+ "description": "Always false for error responses"
+ },
+ "error": {
+ "type": "object",
+ "properties": {
+ "code": {
+ "type": "string",
+ "enum": [
+ "validation_error",
+ "invalid_field",
+ "missing_required_field",
+ "invalid_format",
+ "value_too_long",
+ "value_too_short",
+ "value_out_of_range",
+ "invalid_reference",
+ "duplicate_value",
+ "invalid_query",
+ "invalid_filter",
+ "invalid_sort",
+ "max_records_exceeded",
+ "unauthenticated",
+ "invalid_credentials",
+ "expired_token",
+ "invalid_token",
+ "session_expired",
+ "mfa_required",
+ "email_not_verified",
+ "permission_denied",
+ "insufficient_privileges",
+ "field_not_accessible",
+ "record_not_accessible",
+ "license_required",
+ "ip_restricted",
+ "time_restricted",
+ "resource_not_found",
+ "object_not_found",
+ "record_not_found",
+ "field_not_found",
+ "endpoint_not_found",
+ "resource_conflict",
+ "concurrent_modification",
+ "delete_restricted",
+ "duplicate_record",
+ "lock_conflict",
+ "rate_limit_exceeded",
+ "quota_exceeded",
+ "concurrent_limit_exceeded",
+ "internal_error",
+ "database_error",
+ "timeout",
+ "service_unavailable",
+ "not_implemented",
+ "external_service_error",
+ "integration_error",
+ "webhook_delivery_failed",
+ "batch_partial_failure",
+ "batch_complete_failure",
+ "transaction_failed"
+ ],
+ "description": "Machine-readable error code"
+ },
+ "message": {
+ "type": "string",
+ "description": "Human-readable error message"
+ },
+ "category": {
+ "type": "string",
+ "enum": [
+ "validation",
+ "authentication",
+ "authorization",
+ "not_found",
+ "conflict",
+ "rate_limit",
+ "server",
+ "external",
+ "maintenance"
+ ],
+ "description": "Error category"
+ },
+ "httpStatus": {
+ "type": "number",
+ "description": "HTTP status code"
+ },
+ "retryable": {
+ "type": "boolean",
+ "default": false,
+ "description": "Whether the request can be retried"
+ },
+ "retryStrategy": {
+ "type": "string",
+ "enum": [
+ "no_retry",
+ "retry_immediate",
+ "retry_backoff",
+ "retry_after"
+ ],
+ "description": "Recommended retry strategy"
+ },
+ "retryAfter": {
+ "type": "number",
+ "description": "Seconds to wait before retrying"
+ },
+ "details": {
+ "description": "Additional error context"
+ },
+ "fieldErrors": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string",
+ "description": "Field path (supports dot notation)"
+ },
+ "code": {
+ "type": "string",
+ "enum": [
+ "validation_error",
+ "invalid_field",
+ "missing_required_field",
+ "invalid_format",
+ "value_too_long",
+ "value_too_short",
+ "value_out_of_range",
+ "invalid_reference",
+ "duplicate_value",
+ "invalid_query",
+ "invalid_filter",
+ "invalid_sort",
+ "max_records_exceeded",
+ "unauthenticated",
+ "invalid_credentials",
+ "expired_token",
+ "invalid_token",
+ "session_expired",
+ "mfa_required",
+ "email_not_verified",
+ "permission_denied",
+ "insufficient_privileges",
+ "field_not_accessible",
+ "record_not_accessible",
+ "license_required",
+ "ip_restricted",
+ "time_restricted",
+ "resource_not_found",
+ "object_not_found",
+ "record_not_found",
+ "field_not_found",
+ "endpoint_not_found",
+ "resource_conflict",
+ "concurrent_modification",
+ "delete_restricted",
+ "duplicate_record",
+ "lock_conflict",
+ "rate_limit_exceeded",
+ "quota_exceeded",
+ "concurrent_limit_exceeded",
+ "internal_error",
+ "database_error",
+ "timeout",
+ "service_unavailable",
+ "not_implemented",
+ "external_service_error",
+ "integration_error",
+ "webhook_delivery_failed",
+ "batch_partial_failure",
+ "batch_complete_failure",
+ "transaction_failed"
+ ],
+ "description": "Error code for this field"
+ },
+ "message": {
+ "type": "string",
+ "description": "Human-readable error message"
+ },
+ "value": {
+ "description": "The invalid value that was provided"
+ },
+ "constraint": {
+ "description": "The constraint that was violated (e.g., max length)"
+ }
+ },
+ "required": [
+ "field",
+ "code",
+ "message"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Field-specific validation errors"
+ },
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "description": "When the error occurred"
+ },
+ "requestId": {
+ "type": "string",
+ "description": "Request ID for tracking"
+ },
+ "traceId": {
+ "type": "string",
+ "description": "Distributed trace ID"
+ },
+ "documentation": {
+ "type": "string",
+ "format": "uri",
+ "description": "URL to error documentation"
+ },
+ "helpText": {
+ "type": "string",
+ "description": "Suggested actions to resolve the error"
+ }
+ },
+ "required": [
+ "code",
+ "message"
+ ],
+ "additionalProperties": false,
+ "description": "Error details"
+ },
+ "meta": {
+ "type": "object",
+ "properties": {
+ "timestamp": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "requestId": {
+ "type": "string"
+ },
+ "traceId": {
+ "type": "string"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Response metadata"
+ }
+ },
+ "required": [
+ "success",
+ "error"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/ExportRequest.json b/packages/spec/json-schema/api/ExportRequest.json
index 8b0bc0b14..36abcf882 100644
--- a/packages/spec/json-schema/api/ExportRequest.json
+++ b/packages/spec/json-schema/api/ExportRequest.json
@@ -61,6 +61,61 @@
],
"description": "Filtering criteria (WHERE)"
},
+ "search": {
+ "type": "object",
+ "properties": {
+ "query": {
+ "type": "string",
+ "description": "Search query text"
+ },
+ "fields": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Fields to search in (if not specified, searches all text fields)"
+ },
+ "fuzzy": {
+ "type": "boolean",
+ "default": false,
+ "description": "Enable fuzzy matching (tolerates typos)"
+ },
+ "operator": {
+ "type": "string",
+ "enum": [
+ "and",
+ "or"
+ ],
+ "default": "or",
+ "description": "Logical operator between terms"
+ },
+ "boost": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "number"
+ },
+ "description": "Field-specific relevance boosting (field name -> boost factor)"
+ },
+ "minScore": {
+ "type": "number",
+ "description": "Minimum relevance score threshold"
+ },
+ "language": {
+ "type": "string",
+ "description": "Language for text analysis (e.g., \"en\", \"zh\", \"es\")"
+ },
+ "highlight": {
+ "type": "boolean",
+ "default": false,
+ "description": "Enable search result highlighting"
+ }
+ },
+ "required": [
+ "query"
+ ],
+ "additionalProperties": false,
+ "description": "Full-text search configuration ($search parameter)"
+ },
"orderBy": {
"type": "array",
"items": {
@@ -277,6 +332,61 @@
],
"description": "Filtering criteria (WHERE)"
},
+ "search": {
+ "type": "object",
+ "properties": {
+ "query": {
+ "type": "string",
+ "description": "Search query text"
+ },
+ "fields": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Fields to search in (if not specified, searches all text fields)"
+ },
+ "fuzzy": {
+ "type": "boolean",
+ "default": false,
+ "description": "Enable fuzzy matching (tolerates typos)"
+ },
+ "operator": {
+ "type": "string",
+ "enum": [
+ "and",
+ "or"
+ ],
+ "default": "or",
+ "description": "Logical operator between terms"
+ },
+ "boost": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "number"
+ },
+ "description": "Field-specific relevance boosting (field name -> boost factor)"
+ },
+ "minScore": {
+ "type": "number",
+ "description": "Minimum relevance score threshold"
+ },
+ "language": {
+ "type": "string",
+ "description": "Language for text analysis (e.g., \"en\", \"zh\", \"es\")"
+ },
+ "highlight": {
+ "type": "boolean",
+ "default": false,
+ "description": "Enable search result highlighting"
+ }
+ },
+ "required": [
+ "query"
+ ],
+ "additionalProperties": false,
+ "description": "Full-text search configuration ($search parameter)"
+ },
"orderBy": {
"type": "array",
"items": {
diff --git a/packages/spec/json-schema/api/FieldError.json b/packages/spec/json-schema/api/FieldError.json
new file mode 100644
index 000000000..82f18fbc4
--- /dev/null
+++ b/packages/spec/json-schema/api/FieldError.json
@@ -0,0 +1,88 @@
+{
+ "$ref": "#/definitions/FieldError",
+ "definitions": {
+ "FieldError": {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string",
+ "description": "Field path (supports dot notation)"
+ },
+ "code": {
+ "type": "string",
+ "enum": [
+ "validation_error",
+ "invalid_field",
+ "missing_required_field",
+ "invalid_format",
+ "value_too_long",
+ "value_too_short",
+ "value_out_of_range",
+ "invalid_reference",
+ "duplicate_value",
+ "invalid_query",
+ "invalid_filter",
+ "invalid_sort",
+ "max_records_exceeded",
+ "unauthenticated",
+ "invalid_credentials",
+ "expired_token",
+ "invalid_token",
+ "session_expired",
+ "mfa_required",
+ "email_not_verified",
+ "permission_denied",
+ "insufficient_privileges",
+ "field_not_accessible",
+ "record_not_accessible",
+ "license_required",
+ "ip_restricted",
+ "time_restricted",
+ "resource_not_found",
+ "object_not_found",
+ "record_not_found",
+ "field_not_found",
+ "endpoint_not_found",
+ "resource_conflict",
+ "concurrent_modification",
+ "delete_restricted",
+ "duplicate_record",
+ "lock_conflict",
+ "rate_limit_exceeded",
+ "quota_exceeded",
+ "concurrent_limit_exceeded",
+ "internal_error",
+ "database_error",
+ "timeout",
+ "service_unavailable",
+ "not_implemented",
+ "external_service_error",
+ "integration_error",
+ "webhook_delivery_failed",
+ "batch_partial_failure",
+ "batch_complete_failure",
+ "transaction_failed"
+ ],
+ "description": "Error code for this field"
+ },
+ "message": {
+ "type": "string",
+ "description": "Human-readable error message"
+ },
+ "value": {
+ "description": "The invalid value that was provided"
+ },
+ "constraint": {
+ "description": "The constraint that was violated (e.g., max length)"
+ }
+ },
+ "required": [
+ "field",
+ "code",
+ "message"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/ListViewsRequest.json b/packages/spec/json-schema/api/ListViewsRequest.json
new file mode 100644
index 000000000..1edd30a69
--- /dev/null
+++ b/packages/spec/json-schema/api/ListViewsRequest.json
@@ -0,0 +1,58 @@
+{
+ "$ref": "#/definitions/ListViewsRequest",
+ "definitions": {
+ "ListViewsRequest": {
+ "type": "object",
+ "properties": {
+ "object": {
+ "type": "string",
+ "description": "Filter by object name"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "list",
+ "kanban",
+ "calendar",
+ "gantt",
+ "timeline",
+ "chart",
+ "pivot",
+ "custom"
+ ],
+ "description": "Filter by view type"
+ },
+ "visibility": {
+ "type": "string",
+ "enum": [
+ "private",
+ "shared",
+ "public",
+ "organization"
+ ],
+ "description": "Filter by visibility"
+ },
+ "createdBy": {
+ "type": "string",
+ "description": "Filter by creator user ID"
+ },
+ "isDefault": {
+ "type": "boolean",
+ "description": "Filter for default views"
+ },
+ "limit": {
+ "type": "number",
+ "default": 50,
+ "description": "Max results"
+ },
+ "offset": {
+ "type": "number",
+ "default": 0,
+ "description": "Offset for pagination"
+ }
+ },
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/ListViewsResponse.json b/packages/spec/json-schema/api/ListViewsResponse.json
new file mode 100644
index 000000000..55612f116
--- /dev/null
+++ b/packages/spec/json-schema/api/ListViewsResponse.json
@@ -0,0 +1,1132 @@
+{
+ "$ref": "#/definitions/ListViewsResponse",
+ "definitions": {
+ "ListViewsResponse": {
+ "type": "object",
+ "properties": {
+ "success": {
+ "type": "boolean"
+ },
+ "data": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "description": "Unique view identifier"
+ },
+ "name": {
+ "type": "string",
+ "pattern": "^[a-z_][a-z0-9_]*$",
+ "description": "View machine name (snake_case)"
+ },
+ "label": {
+ "type": "string",
+ "description": "Display label"
+ },
+ "description": {
+ "type": "string",
+ "description": "View description"
+ },
+ "object": {
+ "type": "string",
+ "description": "Object/table this view is for"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "list",
+ "kanban",
+ "calendar",
+ "gantt",
+ "timeline",
+ "chart",
+ "pivot",
+ "custom"
+ ],
+ "description": "View type"
+ },
+ "visibility": {
+ "type": "string",
+ "enum": [
+ "private",
+ "shared",
+ "public",
+ "organization"
+ ],
+ "description": "Who can access this view"
+ },
+ "query": {
+ "type": "object",
+ "properties": {
+ "object": {
+ "type": "string",
+ "description": "Object name (e.g. account)"
+ },
+ "fields": {
+ "type": "array",
+ "items": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string"
+ },
+ "fields": {
+ "type": "array",
+ "items": {}
+ },
+ "alias": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "field"
+ ],
+ "additionalProperties": false
+ }
+ ]
+ },
+ "description": "Fields to retrieve"
+ },
+ "where": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {}
+ },
+ "$or": {
+ "type": "array",
+ "items": {}
+ },
+ "$not": {}
+ }
+ }
+ ],
+ "description": "Filtering criteria (WHERE)"
+ },
+ "search": {
+ "type": "object",
+ "properties": {
+ "query": {
+ "type": "string",
+ "description": "Search query text"
+ },
+ "fields": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Fields to search in (if not specified, searches all text fields)"
+ },
+ "fuzzy": {
+ "type": "boolean",
+ "default": false,
+ "description": "Enable fuzzy matching (tolerates typos)"
+ },
+ "operator": {
+ "type": "string",
+ "enum": [
+ "and",
+ "or"
+ ],
+ "default": "or",
+ "description": "Logical operator between terms"
+ },
+ "boost": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "number"
+ },
+ "description": "Field-specific relevance boosting (field name -> boost factor)"
+ },
+ "minScore": {
+ "type": "number",
+ "description": "Minimum relevance score threshold"
+ },
+ "language": {
+ "type": "string",
+ "description": "Language for text analysis (e.g., \"en\", \"zh\", \"es\")"
+ },
+ "highlight": {
+ "type": "boolean",
+ "default": false,
+ "description": "Enable search result highlighting"
+ }
+ },
+ "required": [
+ "query"
+ ],
+ "additionalProperties": false,
+ "description": "Full-text search configuration ($search parameter)"
+ },
+ "orderBy": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string"
+ },
+ "order": {
+ "type": "string",
+ "enum": [
+ "asc",
+ "desc"
+ ],
+ "default": "asc"
+ }
+ },
+ "required": [
+ "field"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Sorting instructions (ORDER BY)"
+ },
+ "limit": {
+ "type": "number",
+ "description": "Max records to return (LIMIT)"
+ },
+ "offset": {
+ "type": "number",
+ "description": "Records to skip (OFFSET)"
+ },
+ "cursor": {
+ "type": "object",
+ "additionalProperties": {},
+ "description": "Cursor for keyset pagination"
+ },
+ "joins": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "inner",
+ "left",
+ "right",
+ "full"
+ ],
+ "description": "Join type"
+ },
+ "strategy": {
+ "type": "string",
+ "enum": [
+ "auto",
+ "database",
+ "hash",
+ "loop"
+ ],
+ "description": "Execution strategy hint"
+ },
+ "object": {
+ "type": "string",
+ "description": "Object/table to join"
+ },
+ "alias": {
+ "type": "string",
+ "description": "Table alias"
+ },
+ "on": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {}
+ },
+ "$or": {
+ "type": "array",
+ "items": {}
+ },
+ "$not": {}
+ }
+ }
+ ]
+ }
+ },
+ "$or": {
+ "type": "array",
+ "items": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {}
+ },
+ "$or": {
+ "type": "array",
+ "items": {}
+ },
+ "$not": {}
+ }
+ }
+ ]
+ }
+ },
+ "$not": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {}
+ },
+ "$or": {
+ "type": "array",
+ "items": {}
+ },
+ "$not": {}
+ }
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "description": "Join condition"
+ },
+ "subquery": {
+ "type": "object",
+ "properties": {
+ "object": {
+ "type": "string",
+ "description": "Object name (e.g. account)"
+ },
+ "fields": {
+ "type": "array",
+ "items": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string"
+ },
+ "fields": {
+ "type": "array",
+ "items": {}
+ },
+ "alias": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "field"
+ ],
+ "additionalProperties": false
+ }
+ ]
+ },
+ "description": "Fields to retrieve"
+ },
+ "where": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {}
+ },
+ "$or": {
+ "type": "array",
+ "items": {}
+ },
+ "$not": {}
+ }
+ }
+ ],
+ "description": "Filtering criteria (WHERE)"
+ },
+ "search": {
+ "type": "object",
+ "properties": {
+ "query": {
+ "type": "string",
+ "description": "Search query text"
+ },
+ "fields": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Fields to search in (if not specified, searches all text fields)"
+ },
+ "fuzzy": {
+ "type": "boolean",
+ "default": false,
+ "description": "Enable fuzzy matching (tolerates typos)"
+ },
+ "operator": {
+ "type": "string",
+ "enum": [
+ "and",
+ "or"
+ ],
+ "default": "or",
+ "description": "Logical operator between terms"
+ },
+ "boost": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "number"
+ },
+ "description": "Field-specific relevance boosting (field name -> boost factor)"
+ },
+ "minScore": {
+ "type": "number",
+ "description": "Minimum relevance score threshold"
+ },
+ "language": {
+ "type": "string",
+ "description": "Language for text analysis (e.g., \"en\", \"zh\", \"es\")"
+ },
+ "highlight": {
+ "type": "boolean",
+ "default": false,
+ "description": "Enable search result highlighting"
+ }
+ },
+ "required": [
+ "query"
+ ],
+ "additionalProperties": false,
+ "description": "Full-text search configuration ($search parameter)"
+ },
+ "orderBy": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string"
+ },
+ "order": {
+ "type": "string",
+ "enum": [
+ "asc",
+ "desc"
+ ],
+ "default": "asc"
+ }
+ },
+ "required": [
+ "field"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Sorting instructions (ORDER BY)"
+ },
+ "limit": {
+ "type": "number",
+ "description": "Max records to return (LIMIT)"
+ },
+ "offset": {
+ "type": "number",
+ "description": "Records to skip (OFFSET)"
+ },
+ "cursor": {
+ "type": "object",
+ "additionalProperties": {},
+ "description": "Cursor for keyset pagination"
+ },
+ "joins": {},
+ "aggregations": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "function": {
+ "type": "string",
+ "enum": [
+ "count",
+ "sum",
+ "avg",
+ "min",
+ "max",
+ "count_distinct",
+ "array_agg",
+ "string_agg"
+ ],
+ "description": "Aggregation function"
+ },
+ "field": {
+ "type": "string",
+ "description": "Field to aggregate (optional for COUNT(*))"
+ },
+ "alias": {
+ "type": "string",
+ "description": "Result column alias"
+ },
+ "distinct": {
+ "type": "boolean",
+ "description": "Apply DISTINCT before aggregation"
+ },
+ "filter": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {}
+ },
+ "$or": {
+ "type": "array",
+ "items": {}
+ },
+ "$not": {}
+ }
+ }
+ ],
+ "description": "Filter/Condition to apply to the aggregation (FILTER WHERE clause)"
+ }
+ },
+ "required": [
+ "function",
+ "alias"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Aggregation functions"
+ },
+ "groupBy": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "GROUP BY fields"
+ },
+ "having": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {}
+ },
+ "$or": {
+ "type": "array",
+ "items": {}
+ },
+ "$not": {}
+ }
+ }
+ ],
+ "description": "HAVING clause for aggregation filtering"
+ },
+ "windowFunctions": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "function": {
+ "type": "string",
+ "enum": [
+ "row_number",
+ "rank",
+ "dense_rank",
+ "percent_rank",
+ "lag",
+ "lead",
+ "first_value",
+ "last_value",
+ "sum",
+ "avg",
+ "count",
+ "min",
+ "max"
+ ],
+ "description": "Window function name"
+ },
+ "field": {
+ "type": "string",
+ "description": "Field to operate on (for aggregate window functions)"
+ },
+ "alias": {
+ "type": "string",
+ "description": "Result column alias"
+ },
+ "over": {
+ "type": "object",
+ "properties": {
+ "partitionBy": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "PARTITION BY fields"
+ },
+ "orderBy": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string"
+ },
+ "order": {
+ "type": "string",
+ "enum": [
+ "asc",
+ "desc"
+ ],
+ "default": "asc"
+ }
+ },
+ "required": [
+ "field"
+ ],
+ "additionalProperties": false
+ },
+ "description": "ORDER BY specification"
+ },
+ "frame": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "rows",
+ "range"
+ ]
+ },
+ "start": {
+ "type": "string",
+ "description": "Frame start (e.g., \"UNBOUNDED PRECEDING\", \"1 PRECEDING\")"
+ },
+ "end": {
+ "type": "string",
+ "description": "Frame end (e.g., \"CURRENT ROW\", \"1 FOLLOWING\")"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Window frame specification"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Window specification (OVER clause)"
+ }
+ },
+ "required": [
+ "function",
+ "alias",
+ "over"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Window functions with OVER clause"
+ },
+ "distinct": {
+ "type": "boolean",
+ "description": "SELECT DISTINCT flag"
+ }
+ },
+ "required": [
+ "object"
+ ],
+ "additionalProperties": false,
+ "description": "Subquery instead of object"
+ }
+ },
+ "required": [
+ "type",
+ "object",
+ "on"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Explicit Table Joins"
+ },
+ "aggregations": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "function": {
+ "type": "string",
+ "enum": [
+ "count",
+ "sum",
+ "avg",
+ "min",
+ "max",
+ "count_distinct",
+ "array_agg",
+ "string_agg"
+ ],
+ "description": "Aggregation function"
+ },
+ "field": {
+ "type": "string",
+ "description": "Field to aggregate (optional for COUNT(*))"
+ },
+ "alias": {
+ "type": "string",
+ "description": "Result column alias"
+ },
+ "distinct": {
+ "type": "boolean",
+ "description": "Apply DISTINCT before aggregation"
+ },
+ "filter": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {}
+ },
+ "$or": {
+ "type": "array",
+ "items": {}
+ },
+ "$not": {}
+ }
+ }
+ ],
+ "description": "Filter/Condition to apply to the aggregation (FILTER WHERE clause)"
+ }
+ },
+ "required": [
+ "function",
+ "alias"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Aggregation functions"
+ },
+ "groupBy": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "GROUP BY fields"
+ },
+ "having": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {}
+ },
+ "$or": {
+ "type": "array",
+ "items": {}
+ },
+ "$not": {}
+ }
+ }
+ ],
+ "description": "HAVING clause for aggregation filtering"
+ },
+ "windowFunctions": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "function": {
+ "type": "string",
+ "enum": [
+ "row_number",
+ "rank",
+ "dense_rank",
+ "percent_rank",
+ "lag",
+ "lead",
+ "first_value",
+ "last_value",
+ "sum",
+ "avg",
+ "count",
+ "min",
+ "max"
+ ],
+ "description": "Window function name"
+ },
+ "field": {
+ "type": "string",
+ "description": "Field to operate on (for aggregate window functions)"
+ },
+ "alias": {
+ "type": "string",
+ "description": "Result column alias"
+ },
+ "over": {
+ "type": "object",
+ "properties": {
+ "partitionBy": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "PARTITION BY fields"
+ },
+ "orderBy": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string"
+ },
+ "order": {
+ "type": "string",
+ "enum": [
+ "asc",
+ "desc"
+ ],
+ "default": "asc"
+ }
+ },
+ "required": [
+ "field"
+ ],
+ "additionalProperties": false
+ },
+ "description": "ORDER BY specification"
+ },
+ "frame": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "rows",
+ "range"
+ ]
+ },
+ "start": {
+ "type": "string",
+ "description": "Frame start (e.g., \"UNBOUNDED PRECEDING\", \"1 PRECEDING\")"
+ },
+ "end": {
+ "type": "string",
+ "description": "Frame end (e.g., \"CURRENT ROW\", \"1 FOLLOWING\")"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Window frame specification"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Window specification (OVER clause)"
+ }
+ },
+ "required": [
+ "function",
+ "alias",
+ "over"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Window functions with OVER clause"
+ },
+ "distinct": {
+ "type": "boolean",
+ "description": "SELECT DISTINCT flag"
+ }
+ },
+ "required": [
+ "object"
+ ],
+ "additionalProperties": false,
+ "description": "Query configuration (filters, sorting, etc.)"
+ },
+ "layout": {
+ "type": "object",
+ "properties": {
+ "columns": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string",
+ "description": "Field name"
+ },
+ "label": {
+ "type": "string",
+ "description": "Custom column label"
+ },
+ "width": {
+ "type": "number",
+ "description": "Column width in pixels"
+ },
+ "sortable": {
+ "type": "boolean",
+ "default": true,
+ "description": "Whether column is sortable"
+ },
+ "filterable": {
+ "type": "boolean",
+ "default": true,
+ "description": "Whether column is filterable"
+ },
+ "visible": {
+ "type": "boolean",
+ "default": true,
+ "description": "Whether column is visible"
+ },
+ "pinned": {
+ "type": "string",
+ "enum": [
+ "left",
+ "right"
+ ],
+ "description": "Pin column to left or right"
+ },
+ "formatter": {
+ "type": "string",
+ "description": "Custom formatter name"
+ },
+ "aggregation": {
+ "type": "string",
+ "description": "Aggregation function for column (sum, avg, etc.)"
+ }
+ },
+ "required": [
+ "field"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Column configuration for list views"
+ },
+ "rowHeight": {
+ "type": "number",
+ "description": "Row height in pixels"
+ },
+ "groupByField": {
+ "type": "string",
+ "description": "Field to group by (for kanban)"
+ },
+ "cardFields": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Fields to display on cards"
+ },
+ "dateField": {
+ "type": "string",
+ "description": "Date field for calendar view"
+ },
+ "startDateField": {
+ "type": "string",
+ "description": "Start date field for event ranges"
+ },
+ "endDateField": {
+ "type": "string",
+ "description": "End date field for event ranges"
+ },
+ "titleField": {
+ "type": "string",
+ "description": "Field to use as event title"
+ },
+ "chartType": {
+ "type": "string",
+ "enum": [
+ "bar",
+ "line",
+ "pie",
+ "scatter",
+ "area"
+ ],
+ "description": "Chart type"
+ },
+ "xAxis": {
+ "type": "string",
+ "description": "X-axis field"
+ },
+ "yAxis": {
+ "type": "string",
+ "description": "Y-axis field"
+ },
+ "series": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Series fields for multi-series charts"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Layout configuration"
+ },
+ "sharedWith": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "User/team IDs this view is shared with"
+ },
+ "isDefault": {
+ "type": "boolean",
+ "default": false,
+ "description": "Is this the default view for this object?"
+ },
+ "isSystem": {
+ "type": "boolean",
+ "default": false,
+ "description": "Is this a system-defined view?"
+ },
+ "createdBy": {
+ "type": "string",
+ "description": "User ID who created this view"
+ },
+ "createdAt": {
+ "type": "string",
+ "format": "date-time",
+ "description": "When the view was created"
+ },
+ "updatedBy": {
+ "type": "string",
+ "description": "User ID who last updated this view"
+ },
+ "updatedAt": {
+ "type": "string",
+ "format": "date-time",
+ "description": "When the view was last updated"
+ },
+ "settings": {
+ "type": "object",
+ "additionalProperties": {},
+ "description": "Additional view-specific settings"
+ }
+ },
+ "required": [
+ "id",
+ "name",
+ "label",
+ "object",
+ "type",
+ "visibility",
+ "query",
+ "createdBy",
+ "createdAt"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Array of saved views"
+ },
+ "pagination": {
+ "type": "object",
+ "properties": {
+ "total": {
+ "type": "number"
+ },
+ "limit": {
+ "type": "number"
+ },
+ "offset": {
+ "type": "number"
+ },
+ "hasMore": {
+ "type": "boolean"
+ }
+ },
+ "required": [
+ "total",
+ "limit",
+ "offset",
+ "hasMore"
+ ],
+ "additionalProperties": false
+ },
+ "error": {
+ "type": "object",
+ "properties": {
+ "code": {
+ "type": "string"
+ },
+ "message": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "code",
+ "message"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "required": [
+ "success",
+ "data",
+ "pagination"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/MetadataCacheRequest.json b/packages/spec/json-schema/api/MetadataCacheRequest.json
new file mode 100644
index 000000000..84a7d4045
--- /dev/null
+++ b/packages/spec/json-schema/api/MetadataCacheRequest.json
@@ -0,0 +1,58 @@
+{
+ "$ref": "#/definitions/MetadataCacheRequest",
+ "definitions": {
+ "MetadataCacheRequest": {
+ "type": "object",
+ "properties": {
+ "ifNoneMatch": {
+ "type": "string",
+ "description": "ETag value for conditional request (If-None-Match header)"
+ },
+ "ifModifiedSince": {
+ "type": "string",
+ "format": "date-time",
+ "description": "Timestamp for conditional request (If-Modified-Since header)"
+ },
+ "cacheControl": {
+ "type": "object",
+ "properties": {
+ "directives": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "enum": [
+ "public",
+ "private",
+ "no-cache",
+ "no-store",
+ "must-revalidate",
+ "max-age"
+ ]
+ },
+ "description": "Cache control directives"
+ },
+ "maxAge": {
+ "type": "number",
+ "description": "Maximum cache age in seconds"
+ },
+ "staleWhileRevalidate": {
+ "type": "number",
+ "description": "Allow serving stale content while revalidating (seconds)"
+ },
+ "staleIfError": {
+ "type": "number",
+ "description": "Allow serving stale content on error (seconds)"
+ }
+ },
+ "required": [
+ "directives"
+ ],
+ "additionalProperties": false,
+ "description": "Client cache control preferences"
+ }
+ },
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/MetadataCacheResponse.json b/packages/spec/json-schema/api/MetadataCacheResponse.json
new file mode 100644
index 000000000..ff648f2ec
--- /dev/null
+++ b/packages/spec/json-schema/api/MetadataCacheResponse.json
@@ -0,0 +1,85 @@
+{
+ "$ref": "#/definitions/MetadataCacheResponse",
+ "definitions": {
+ "MetadataCacheResponse": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "description": "Metadata payload (omitted for 304 Not Modified)"
+ },
+ "etag": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "type": "string",
+ "description": "ETag value (hash or version identifier)"
+ },
+ "weak": {
+ "type": "boolean",
+ "default": false,
+ "description": "Whether this is a weak ETag"
+ }
+ },
+ "required": [
+ "value"
+ ],
+ "additionalProperties": false,
+ "description": "ETag for this resource version"
+ },
+ "lastModified": {
+ "type": "string",
+ "format": "date-time",
+ "description": "Last modification timestamp"
+ },
+ "cacheControl": {
+ "type": "object",
+ "properties": {
+ "directives": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "enum": [
+ "public",
+ "private",
+ "no-cache",
+ "no-store",
+ "must-revalidate",
+ "max-age"
+ ]
+ },
+ "description": "Cache control directives"
+ },
+ "maxAge": {
+ "type": "number",
+ "description": "Maximum cache age in seconds"
+ },
+ "staleWhileRevalidate": {
+ "type": "number",
+ "description": "Allow serving stale content while revalidating (seconds)"
+ },
+ "staleIfError": {
+ "type": "number",
+ "description": "Allow serving stale content on error (seconds)"
+ }
+ },
+ "required": [
+ "directives"
+ ],
+ "additionalProperties": false,
+ "description": "Cache control directives"
+ },
+ "notModified": {
+ "type": "boolean",
+ "default": false,
+ "description": "True if resource has not been modified (304 response)"
+ },
+ "version": {
+ "type": "string",
+ "description": "Metadata version identifier"
+ }
+ },
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/RetryStrategy.json b/packages/spec/json-schema/api/RetryStrategy.json
new file mode 100644
index 000000000..da1636a6a
--- /dev/null
+++ b/packages/spec/json-schema/api/RetryStrategy.json
@@ -0,0 +1,15 @@
+{
+ "$ref": "#/definitions/RetryStrategy",
+ "definitions": {
+ "RetryStrategy": {
+ "type": "string",
+ "enum": [
+ "no_retry",
+ "retry_immediate",
+ "retry_backoff",
+ "retry_after"
+ ]
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/SavedView.json b/packages/spec/json-schema/api/SavedView.json
new file mode 100644
index 000000000..bb7b3c222
--- /dev/null
+++ b/packages/spec/json-schema/api/SavedView.json
@@ -0,0 +1,1074 @@
+{
+ "$ref": "#/definitions/SavedView",
+ "definitions": {
+ "SavedView": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "description": "Unique view identifier"
+ },
+ "name": {
+ "type": "string",
+ "pattern": "^[a-z_][a-z0-9_]*$",
+ "description": "View machine name (snake_case)"
+ },
+ "label": {
+ "type": "string",
+ "description": "Display label"
+ },
+ "description": {
+ "type": "string",
+ "description": "View description"
+ },
+ "object": {
+ "type": "string",
+ "description": "Object/table this view is for"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "list",
+ "kanban",
+ "calendar",
+ "gantt",
+ "timeline",
+ "chart",
+ "pivot",
+ "custom"
+ ],
+ "description": "View type"
+ },
+ "visibility": {
+ "type": "string",
+ "enum": [
+ "private",
+ "shared",
+ "public",
+ "organization"
+ ],
+ "description": "Who can access this view"
+ },
+ "query": {
+ "type": "object",
+ "properties": {
+ "object": {
+ "type": "string",
+ "description": "Object name (e.g. account)"
+ },
+ "fields": {
+ "type": "array",
+ "items": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string"
+ },
+ "fields": {
+ "type": "array",
+ "items": {}
+ },
+ "alias": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "field"
+ ],
+ "additionalProperties": false
+ }
+ ]
+ },
+ "description": "Fields to retrieve"
+ },
+ "where": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {}
+ },
+ "$or": {
+ "type": "array",
+ "items": {}
+ },
+ "$not": {}
+ }
+ }
+ ],
+ "description": "Filtering criteria (WHERE)"
+ },
+ "search": {
+ "type": "object",
+ "properties": {
+ "query": {
+ "type": "string",
+ "description": "Search query text"
+ },
+ "fields": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Fields to search in (if not specified, searches all text fields)"
+ },
+ "fuzzy": {
+ "type": "boolean",
+ "default": false,
+ "description": "Enable fuzzy matching (tolerates typos)"
+ },
+ "operator": {
+ "type": "string",
+ "enum": [
+ "and",
+ "or"
+ ],
+ "default": "or",
+ "description": "Logical operator between terms"
+ },
+ "boost": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "number"
+ },
+ "description": "Field-specific relevance boosting (field name -> boost factor)"
+ },
+ "minScore": {
+ "type": "number",
+ "description": "Minimum relevance score threshold"
+ },
+ "language": {
+ "type": "string",
+ "description": "Language for text analysis (e.g., \"en\", \"zh\", \"es\")"
+ },
+ "highlight": {
+ "type": "boolean",
+ "default": false,
+ "description": "Enable search result highlighting"
+ }
+ },
+ "required": [
+ "query"
+ ],
+ "additionalProperties": false,
+ "description": "Full-text search configuration ($search parameter)"
+ },
+ "orderBy": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string"
+ },
+ "order": {
+ "type": "string",
+ "enum": [
+ "asc",
+ "desc"
+ ],
+ "default": "asc"
+ }
+ },
+ "required": [
+ "field"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Sorting instructions (ORDER BY)"
+ },
+ "limit": {
+ "type": "number",
+ "description": "Max records to return (LIMIT)"
+ },
+ "offset": {
+ "type": "number",
+ "description": "Records to skip (OFFSET)"
+ },
+ "cursor": {
+ "type": "object",
+ "additionalProperties": {},
+ "description": "Cursor for keyset pagination"
+ },
+ "joins": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "inner",
+ "left",
+ "right",
+ "full"
+ ],
+ "description": "Join type"
+ },
+ "strategy": {
+ "type": "string",
+ "enum": [
+ "auto",
+ "database",
+ "hash",
+ "loop"
+ ],
+ "description": "Execution strategy hint"
+ },
+ "object": {
+ "type": "string",
+ "description": "Object/table to join"
+ },
+ "alias": {
+ "type": "string",
+ "description": "Table alias"
+ },
+ "on": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {}
+ },
+ "$or": {
+ "type": "array",
+ "items": {}
+ },
+ "$not": {}
+ }
+ }
+ ]
+ }
+ },
+ "$or": {
+ "type": "array",
+ "items": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {}
+ },
+ "$or": {
+ "type": "array",
+ "items": {}
+ },
+ "$not": {}
+ }
+ }
+ ]
+ }
+ },
+ "$not": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {}
+ },
+ "$or": {
+ "type": "array",
+ "items": {}
+ },
+ "$not": {}
+ }
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "description": "Join condition"
+ },
+ "subquery": {
+ "type": "object",
+ "properties": {
+ "object": {
+ "type": "string",
+ "description": "Object name (e.g. account)"
+ },
+ "fields": {
+ "type": "array",
+ "items": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string"
+ },
+ "fields": {
+ "type": "array",
+ "items": {}
+ },
+ "alias": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "field"
+ ],
+ "additionalProperties": false
+ }
+ ]
+ },
+ "description": "Fields to retrieve"
+ },
+ "where": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {}
+ },
+ "$or": {
+ "type": "array",
+ "items": {}
+ },
+ "$not": {}
+ }
+ }
+ ],
+ "description": "Filtering criteria (WHERE)"
+ },
+ "search": {
+ "type": "object",
+ "properties": {
+ "query": {
+ "type": "string",
+ "description": "Search query text"
+ },
+ "fields": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Fields to search in (if not specified, searches all text fields)"
+ },
+ "fuzzy": {
+ "type": "boolean",
+ "default": false,
+ "description": "Enable fuzzy matching (tolerates typos)"
+ },
+ "operator": {
+ "type": "string",
+ "enum": [
+ "and",
+ "or"
+ ],
+ "default": "or",
+ "description": "Logical operator between terms"
+ },
+ "boost": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "number"
+ },
+ "description": "Field-specific relevance boosting (field name -> boost factor)"
+ },
+ "minScore": {
+ "type": "number",
+ "description": "Minimum relevance score threshold"
+ },
+ "language": {
+ "type": "string",
+ "description": "Language for text analysis (e.g., \"en\", \"zh\", \"es\")"
+ },
+ "highlight": {
+ "type": "boolean",
+ "default": false,
+ "description": "Enable search result highlighting"
+ }
+ },
+ "required": [
+ "query"
+ ],
+ "additionalProperties": false,
+ "description": "Full-text search configuration ($search parameter)"
+ },
+ "orderBy": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string"
+ },
+ "order": {
+ "type": "string",
+ "enum": [
+ "asc",
+ "desc"
+ ],
+ "default": "asc"
+ }
+ },
+ "required": [
+ "field"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Sorting instructions (ORDER BY)"
+ },
+ "limit": {
+ "type": "number",
+ "description": "Max records to return (LIMIT)"
+ },
+ "offset": {
+ "type": "number",
+ "description": "Records to skip (OFFSET)"
+ },
+ "cursor": {
+ "type": "object",
+ "additionalProperties": {},
+ "description": "Cursor for keyset pagination"
+ },
+ "joins": {},
+ "aggregations": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "function": {
+ "type": "string",
+ "enum": [
+ "count",
+ "sum",
+ "avg",
+ "min",
+ "max",
+ "count_distinct",
+ "array_agg",
+ "string_agg"
+ ],
+ "description": "Aggregation function"
+ },
+ "field": {
+ "type": "string",
+ "description": "Field to aggregate (optional for COUNT(*))"
+ },
+ "alias": {
+ "type": "string",
+ "description": "Result column alias"
+ },
+ "distinct": {
+ "type": "boolean",
+ "description": "Apply DISTINCT before aggregation"
+ },
+ "filter": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {}
+ },
+ "$or": {
+ "type": "array",
+ "items": {}
+ },
+ "$not": {}
+ }
+ }
+ ],
+ "description": "Filter/Condition to apply to the aggregation (FILTER WHERE clause)"
+ }
+ },
+ "required": [
+ "function",
+ "alias"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Aggregation functions"
+ },
+ "groupBy": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "GROUP BY fields"
+ },
+ "having": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {}
+ },
+ "$or": {
+ "type": "array",
+ "items": {}
+ },
+ "$not": {}
+ }
+ }
+ ],
+ "description": "HAVING clause for aggregation filtering"
+ },
+ "windowFunctions": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "function": {
+ "type": "string",
+ "enum": [
+ "row_number",
+ "rank",
+ "dense_rank",
+ "percent_rank",
+ "lag",
+ "lead",
+ "first_value",
+ "last_value",
+ "sum",
+ "avg",
+ "count",
+ "min",
+ "max"
+ ],
+ "description": "Window function name"
+ },
+ "field": {
+ "type": "string",
+ "description": "Field to operate on (for aggregate window functions)"
+ },
+ "alias": {
+ "type": "string",
+ "description": "Result column alias"
+ },
+ "over": {
+ "type": "object",
+ "properties": {
+ "partitionBy": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "PARTITION BY fields"
+ },
+ "orderBy": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string"
+ },
+ "order": {
+ "type": "string",
+ "enum": [
+ "asc",
+ "desc"
+ ],
+ "default": "asc"
+ }
+ },
+ "required": [
+ "field"
+ ],
+ "additionalProperties": false
+ },
+ "description": "ORDER BY specification"
+ },
+ "frame": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "rows",
+ "range"
+ ]
+ },
+ "start": {
+ "type": "string",
+ "description": "Frame start (e.g., \"UNBOUNDED PRECEDING\", \"1 PRECEDING\")"
+ },
+ "end": {
+ "type": "string",
+ "description": "Frame end (e.g., \"CURRENT ROW\", \"1 FOLLOWING\")"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Window frame specification"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Window specification (OVER clause)"
+ }
+ },
+ "required": [
+ "function",
+ "alias",
+ "over"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Window functions with OVER clause"
+ },
+ "distinct": {
+ "type": "boolean",
+ "description": "SELECT DISTINCT flag"
+ }
+ },
+ "required": [
+ "object"
+ ],
+ "additionalProperties": false,
+ "description": "Subquery instead of object"
+ }
+ },
+ "required": [
+ "type",
+ "object",
+ "on"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Explicit Table Joins"
+ },
+ "aggregations": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "function": {
+ "type": "string",
+ "enum": [
+ "count",
+ "sum",
+ "avg",
+ "min",
+ "max",
+ "count_distinct",
+ "array_agg",
+ "string_agg"
+ ],
+ "description": "Aggregation function"
+ },
+ "field": {
+ "type": "string",
+ "description": "Field to aggregate (optional for COUNT(*))"
+ },
+ "alias": {
+ "type": "string",
+ "description": "Result column alias"
+ },
+ "distinct": {
+ "type": "boolean",
+ "description": "Apply DISTINCT before aggregation"
+ },
+ "filter": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {}
+ },
+ "$or": {
+ "type": "array",
+ "items": {}
+ },
+ "$not": {}
+ }
+ }
+ ],
+ "description": "Filter/Condition to apply to the aggregation (FILTER WHERE clause)"
+ }
+ },
+ "required": [
+ "function",
+ "alias"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Aggregation functions"
+ },
+ "groupBy": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "GROUP BY fields"
+ },
+ "having": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {}
+ },
+ "$or": {
+ "type": "array",
+ "items": {}
+ },
+ "$not": {}
+ }
+ }
+ ],
+ "description": "HAVING clause for aggregation filtering"
+ },
+ "windowFunctions": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "function": {
+ "type": "string",
+ "enum": [
+ "row_number",
+ "rank",
+ "dense_rank",
+ "percent_rank",
+ "lag",
+ "lead",
+ "first_value",
+ "last_value",
+ "sum",
+ "avg",
+ "count",
+ "min",
+ "max"
+ ],
+ "description": "Window function name"
+ },
+ "field": {
+ "type": "string",
+ "description": "Field to operate on (for aggregate window functions)"
+ },
+ "alias": {
+ "type": "string",
+ "description": "Result column alias"
+ },
+ "over": {
+ "type": "object",
+ "properties": {
+ "partitionBy": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "PARTITION BY fields"
+ },
+ "orderBy": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string"
+ },
+ "order": {
+ "type": "string",
+ "enum": [
+ "asc",
+ "desc"
+ ],
+ "default": "asc"
+ }
+ },
+ "required": [
+ "field"
+ ],
+ "additionalProperties": false
+ },
+ "description": "ORDER BY specification"
+ },
+ "frame": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "rows",
+ "range"
+ ]
+ },
+ "start": {
+ "type": "string",
+ "description": "Frame start (e.g., \"UNBOUNDED PRECEDING\", \"1 PRECEDING\")"
+ },
+ "end": {
+ "type": "string",
+ "description": "Frame end (e.g., \"CURRENT ROW\", \"1 FOLLOWING\")"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Window frame specification"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Window specification (OVER clause)"
+ }
+ },
+ "required": [
+ "function",
+ "alias",
+ "over"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Window functions with OVER clause"
+ },
+ "distinct": {
+ "type": "boolean",
+ "description": "SELECT DISTINCT flag"
+ }
+ },
+ "required": [
+ "object"
+ ],
+ "additionalProperties": false,
+ "description": "Query configuration (filters, sorting, etc.)"
+ },
+ "layout": {
+ "type": "object",
+ "properties": {
+ "columns": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string",
+ "description": "Field name"
+ },
+ "label": {
+ "type": "string",
+ "description": "Custom column label"
+ },
+ "width": {
+ "type": "number",
+ "description": "Column width in pixels"
+ },
+ "sortable": {
+ "type": "boolean",
+ "default": true,
+ "description": "Whether column is sortable"
+ },
+ "filterable": {
+ "type": "boolean",
+ "default": true,
+ "description": "Whether column is filterable"
+ },
+ "visible": {
+ "type": "boolean",
+ "default": true,
+ "description": "Whether column is visible"
+ },
+ "pinned": {
+ "type": "string",
+ "enum": [
+ "left",
+ "right"
+ ],
+ "description": "Pin column to left or right"
+ },
+ "formatter": {
+ "type": "string",
+ "description": "Custom formatter name"
+ },
+ "aggregation": {
+ "type": "string",
+ "description": "Aggregation function for column (sum, avg, etc.)"
+ }
+ },
+ "required": [
+ "field"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Column configuration for list views"
+ },
+ "rowHeight": {
+ "type": "number",
+ "description": "Row height in pixels"
+ },
+ "groupByField": {
+ "type": "string",
+ "description": "Field to group by (for kanban)"
+ },
+ "cardFields": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Fields to display on cards"
+ },
+ "dateField": {
+ "type": "string",
+ "description": "Date field for calendar view"
+ },
+ "startDateField": {
+ "type": "string",
+ "description": "Start date field for event ranges"
+ },
+ "endDateField": {
+ "type": "string",
+ "description": "End date field for event ranges"
+ },
+ "titleField": {
+ "type": "string",
+ "description": "Field to use as event title"
+ },
+ "chartType": {
+ "type": "string",
+ "enum": [
+ "bar",
+ "line",
+ "pie",
+ "scatter",
+ "area"
+ ],
+ "description": "Chart type"
+ },
+ "xAxis": {
+ "type": "string",
+ "description": "X-axis field"
+ },
+ "yAxis": {
+ "type": "string",
+ "description": "Y-axis field"
+ },
+ "series": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Series fields for multi-series charts"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Layout configuration"
+ },
+ "sharedWith": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "User/team IDs this view is shared with"
+ },
+ "isDefault": {
+ "type": "boolean",
+ "default": false,
+ "description": "Is this the default view for this object?"
+ },
+ "isSystem": {
+ "type": "boolean",
+ "default": false,
+ "description": "Is this a system-defined view?"
+ },
+ "createdBy": {
+ "type": "string",
+ "description": "User ID who created this view"
+ },
+ "createdAt": {
+ "type": "string",
+ "format": "date-time",
+ "description": "When the view was created"
+ },
+ "updatedBy": {
+ "type": "string",
+ "description": "User ID who last updated this view"
+ },
+ "updatedAt": {
+ "type": "string",
+ "format": "date-time",
+ "description": "When the view was last updated"
+ },
+ "settings": {
+ "type": "object",
+ "additionalProperties": {},
+ "description": "Additional view-specific settings"
+ }
+ },
+ "required": [
+ "id",
+ "name",
+ "label",
+ "object",
+ "type",
+ "visibility",
+ "query",
+ "createdBy",
+ "createdAt"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/StandardErrorCode.json b/packages/spec/json-schema/api/StandardErrorCode.json
new file mode 100644
index 000000000..d0c67c499
--- /dev/null
+++ b/packages/spec/json-schema/api/StandardErrorCode.json
@@ -0,0 +1,62 @@
+{
+ "$ref": "#/definitions/StandardErrorCode",
+ "definitions": {
+ "StandardErrorCode": {
+ "type": "string",
+ "enum": [
+ "validation_error",
+ "invalid_field",
+ "missing_required_field",
+ "invalid_format",
+ "value_too_long",
+ "value_too_short",
+ "value_out_of_range",
+ "invalid_reference",
+ "duplicate_value",
+ "invalid_query",
+ "invalid_filter",
+ "invalid_sort",
+ "max_records_exceeded",
+ "unauthenticated",
+ "invalid_credentials",
+ "expired_token",
+ "invalid_token",
+ "session_expired",
+ "mfa_required",
+ "email_not_verified",
+ "permission_denied",
+ "insufficient_privileges",
+ "field_not_accessible",
+ "record_not_accessible",
+ "license_required",
+ "ip_restricted",
+ "time_restricted",
+ "resource_not_found",
+ "object_not_found",
+ "record_not_found",
+ "field_not_found",
+ "endpoint_not_found",
+ "resource_conflict",
+ "concurrent_modification",
+ "delete_restricted",
+ "duplicate_record",
+ "lock_conflict",
+ "rate_limit_exceeded",
+ "quota_exceeded",
+ "concurrent_limit_exceeded",
+ "internal_error",
+ "database_error",
+ "timeout",
+ "service_unavailable",
+ "not_implemented",
+ "external_service_error",
+ "integration_error",
+ "webhook_delivery_failed",
+ "batch_partial_failure",
+ "batch_complete_failure",
+ "transaction_failed"
+ ]
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/UpdateManyRequest.json b/packages/spec/json-schema/api/UpdateManyRequest.json
new file mode 100644
index 000000000..bfd63c1b4
--- /dev/null
+++ b/packages/spec/json-schema/api/UpdateManyRequest.json
@@ -0,0 +1,67 @@
+{
+ "$ref": "#/definitions/UpdateManyRequest",
+ "definitions": {
+ "UpdateManyRequest": {
+ "type": "object",
+ "properties": {
+ "records": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "description": "Record ID (required for update/delete)"
+ },
+ "data": {
+ "type": "object",
+ "additionalProperties": {},
+ "description": "Record data (required for create/update/upsert)"
+ },
+ "externalId": {
+ "type": "string",
+ "description": "External ID for upsert matching"
+ }
+ },
+ "additionalProperties": false
+ },
+ "minItems": 1,
+ "maxItems": 200,
+ "description": "Array of records to update (max 200 per batch)"
+ },
+ "options": {
+ "type": "object",
+ "properties": {
+ "atomic": {
+ "type": "boolean",
+ "default": true,
+ "description": "If true, rollback entire batch on any failure (transaction mode)"
+ },
+ "returnRecords": {
+ "type": "boolean",
+ "default": false,
+ "description": "If true, return full record data in response"
+ },
+ "continueOnError": {
+ "type": "boolean",
+ "default": false,
+ "description": "If true (and atomic=false), continue processing remaining records after errors"
+ },
+ "validateOnly": {
+ "type": "boolean",
+ "default": false,
+ "description": "If true, validate records without persisting changes (dry-run mode)"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Update options"
+ }
+ },
+ "required": [
+ "records"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/UpdateViewRequest.json b/packages/spec/json-schema/api/UpdateViewRequest.json
new file mode 100644
index 000000000..392206f21
--- /dev/null
+++ b/packages/spec/json-schema/api/UpdateViewRequest.json
@@ -0,0 +1,1041 @@
+{
+ "$ref": "#/definitions/UpdateViewRequest",
+ "definitions": {
+ "UpdateViewRequest": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "pattern": "^[a-z_][a-z0-9_]*$",
+ "description": "View machine name"
+ },
+ "label": {
+ "type": "string",
+ "description": "Display label"
+ },
+ "description": {
+ "type": "string"
+ },
+ "object": {
+ "type": "string",
+ "description": "Object name"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "list",
+ "kanban",
+ "calendar",
+ "gantt",
+ "timeline",
+ "chart",
+ "pivot",
+ "custom"
+ ],
+ "description": "View type"
+ },
+ "visibility": {
+ "type": "string",
+ "enum": [
+ "private",
+ "shared",
+ "public",
+ "organization"
+ ],
+ "description": "View visibility"
+ },
+ "query": {
+ "type": "object",
+ "properties": {
+ "object": {
+ "type": "string",
+ "description": "Object name (e.g. account)"
+ },
+ "fields": {
+ "type": "array",
+ "items": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string"
+ },
+ "fields": {
+ "type": "array",
+ "items": {}
+ },
+ "alias": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "field"
+ ],
+ "additionalProperties": false
+ }
+ ]
+ },
+ "description": "Fields to retrieve"
+ },
+ "where": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {}
+ },
+ "$or": {
+ "type": "array",
+ "items": {}
+ },
+ "$not": {}
+ }
+ }
+ ],
+ "description": "Filtering criteria (WHERE)"
+ },
+ "search": {
+ "type": "object",
+ "properties": {
+ "query": {
+ "type": "string",
+ "description": "Search query text"
+ },
+ "fields": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Fields to search in (if not specified, searches all text fields)"
+ },
+ "fuzzy": {
+ "type": "boolean",
+ "default": false,
+ "description": "Enable fuzzy matching (tolerates typos)"
+ },
+ "operator": {
+ "type": "string",
+ "enum": [
+ "and",
+ "or"
+ ],
+ "default": "or",
+ "description": "Logical operator between terms"
+ },
+ "boost": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "number"
+ },
+ "description": "Field-specific relevance boosting (field name -> boost factor)"
+ },
+ "minScore": {
+ "type": "number",
+ "description": "Minimum relevance score threshold"
+ },
+ "language": {
+ "type": "string",
+ "description": "Language for text analysis (e.g., \"en\", \"zh\", \"es\")"
+ },
+ "highlight": {
+ "type": "boolean",
+ "default": false,
+ "description": "Enable search result highlighting"
+ }
+ },
+ "required": [
+ "query"
+ ],
+ "additionalProperties": false,
+ "description": "Full-text search configuration ($search parameter)"
+ },
+ "orderBy": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string"
+ },
+ "order": {
+ "type": "string",
+ "enum": [
+ "asc",
+ "desc"
+ ],
+ "default": "asc"
+ }
+ },
+ "required": [
+ "field"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Sorting instructions (ORDER BY)"
+ },
+ "limit": {
+ "type": "number",
+ "description": "Max records to return (LIMIT)"
+ },
+ "offset": {
+ "type": "number",
+ "description": "Records to skip (OFFSET)"
+ },
+ "cursor": {
+ "type": "object",
+ "additionalProperties": {},
+ "description": "Cursor for keyset pagination"
+ },
+ "joins": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "inner",
+ "left",
+ "right",
+ "full"
+ ],
+ "description": "Join type"
+ },
+ "strategy": {
+ "type": "string",
+ "enum": [
+ "auto",
+ "database",
+ "hash",
+ "loop"
+ ],
+ "description": "Execution strategy hint"
+ },
+ "object": {
+ "type": "string",
+ "description": "Object/table to join"
+ },
+ "alias": {
+ "type": "string",
+ "description": "Table alias"
+ },
+ "on": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {}
+ },
+ "$or": {
+ "type": "array",
+ "items": {}
+ },
+ "$not": {}
+ }
+ }
+ ]
+ }
+ },
+ "$or": {
+ "type": "array",
+ "items": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {}
+ },
+ "$or": {
+ "type": "array",
+ "items": {}
+ },
+ "$not": {}
+ }
+ }
+ ]
+ }
+ },
+ "$not": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {}
+ },
+ "$or": {
+ "type": "array",
+ "items": {}
+ },
+ "$not": {}
+ }
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "description": "Join condition"
+ },
+ "subquery": {
+ "type": "object",
+ "properties": {
+ "object": {
+ "type": "string",
+ "description": "Object name (e.g. account)"
+ },
+ "fields": {
+ "type": "array",
+ "items": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string"
+ },
+ "fields": {
+ "type": "array",
+ "items": {}
+ },
+ "alias": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "field"
+ ],
+ "additionalProperties": false
+ }
+ ]
+ },
+ "description": "Fields to retrieve"
+ },
+ "where": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {}
+ },
+ "$or": {
+ "type": "array",
+ "items": {}
+ },
+ "$not": {}
+ }
+ }
+ ],
+ "description": "Filtering criteria (WHERE)"
+ },
+ "search": {
+ "type": "object",
+ "properties": {
+ "query": {
+ "type": "string",
+ "description": "Search query text"
+ },
+ "fields": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Fields to search in (if not specified, searches all text fields)"
+ },
+ "fuzzy": {
+ "type": "boolean",
+ "default": false,
+ "description": "Enable fuzzy matching (tolerates typos)"
+ },
+ "operator": {
+ "type": "string",
+ "enum": [
+ "and",
+ "or"
+ ],
+ "default": "or",
+ "description": "Logical operator between terms"
+ },
+ "boost": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "number"
+ },
+ "description": "Field-specific relevance boosting (field name -> boost factor)"
+ },
+ "minScore": {
+ "type": "number",
+ "description": "Minimum relevance score threshold"
+ },
+ "language": {
+ "type": "string",
+ "description": "Language for text analysis (e.g., \"en\", \"zh\", \"es\")"
+ },
+ "highlight": {
+ "type": "boolean",
+ "default": false,
+ "description": "Enable search result highlighting"
+ }
+ },
+ "required": [
+ "query"
+ ],
+ "additionalProperties": false,
+ "description": "Full-text search configuration ($search parameter)"
+ },
+ "orderBy": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string"
+ },
+ "order": {
+ "type": "string",
+ "enum": [
+ "asc",
+ "desc"
+ ],
+ "default": "asc"
+ }
+ },
+ "required": [
+ "field"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Sorting instructions (ORDER BY)"
+ },
+ "limit": {
+ "type": "number",
+ "description": "Max records to return (LIMIT)"
+ },
+ "offset": {
+ "type": "number",
+ "description": "Records to skip (OFFSET)"
+ },
+ "cursor": {
+ "type": "object",
+ "additionalProperties": {},
+ "description": "Cursor for keyset pagination"
+ },
+ "joins": {},
+ "aggregations": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "function": {
+ "type": "string",
+ "enum": [
+ "count",
+ "sum",
+ "avg",
+ "min",
+ "max",
+ "count_distinct",
+ "array_agg",
+ "string_agg"
+ ],
+ "description": "Aggregation function"
+ },
+ "field": {
+ "type": "string",
+ "description": "Field to aggregate (optional for COUNT(*))"
+ },
+ "alias": {
+ "type": "string",
+ "description": "Result column alias"
+ },
+ "distinct": {
+ "type": "boolean",
+ "description": "Apply DISTINCT before aggregation"
+ },
+ "filter": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {}
+ },
+ "$or": {
+ "type": "array",
+ "items": {}
+ },
+ "$not": {}
+ }
+ }
+ ],
+ "description": "Filter/Condition to apply to the aggregation (FILTER WHERE clause)"
+ }
+ },
+ "required": [
+ "function",
+ "alias"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Aggregation functions"
+ },
+ "groupBy": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "GROUP BY fields"
+ },
+ "having": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {}
+ },
+ "$or": {
+ "type": "array",
+ "items": {}
+ },
+ "$not": {}
+ }
+ }
+ ],
+ "description": "HAVING clause for aggregation filtering"
+ },
+ "windowFunctions": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "function": {
+ "type": "string",
+ "enum": [
+ "row_number",
+ "rank",
+ "dense_rank",
+ "percent_rank",
+ "lag",
+ "lead",
+ "first_value",
+ "last_value",
+ "sum",
+ "avg",
+ "count",
+ "min",
+ "max"
+ ],
+ "description": "Window function name"
+ },
+ "field": {
+ "type": "string",
+ "description": "Field to operate on (for aggregate window functions)"
+ },
+ "alias": {
+ "type": "string",
+ "description": "Result column alias"
+ },
+ "over": {
+ "type": "object",
+ "properties": {
+ "partitionBy": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "PARTITION BY fields"
+ },
+ "orderBy": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string"
+ },
+ "order": {
+ "type": "string",
+ "enum": [
+ "asc",
+ "desc"
+ ],
+ "default": "asc"
+ }
+ },
+ "required": [
+ "field"
+ ],
+ "additionalProperties": false
+ },
+ "description": "ORDER BY specification"
+ },
+ "frame": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "rows",
+ "range"
+ ]
+ },
+ "start": {
+ "type": "string",
+ "description": "Frame start (e.g., \"UNBOUNDED PRECEDING\", \"1 PRECEDING\")"
+ },
+ "end": {
+ "type": "string",
+ "description": "Frame end (e.g., \"CURRENT ROW\", \"1 FOLLOWING\")"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Window frame specification"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Window specification (OVER clause)"
+ }
+ },
+ "required": [
+ "function",
+ "alias",
+ "over"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Window functions with OVER clause"
+ },
+ "distinct": {
+ "type": "boolean",
+ "description": "SELECT DISTINCT flag"
+ }
+ },
+ "required": [
+ "object"
+ ],
+ "additionalProperties": false,
+ "description": "Subquery instead of object"
+ }
+ },
+ "required": [
+ "type",
+ "object",
+ "on"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Explicit Table Joins"
+ },
+ "aggregations": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "function": {
+ "type": "string",
+ "enum": [
+ "count",
+ "sum",
+ "avg",
+ "min",
+ "max",
+ "count_distinct",
+ "array_agg",
+ "string_agg"
+ ],
+ "description": "Aggregation function"
+ },
+ "field": {
+ "type": "string",
+ "description": "Field to aggregate (optional for COUNT(*))"
+ },
+ "alias": {
+ "type": "string",
+ "description": "Result column alias"
+ },
+ "distinct": {
+ "type": "boolean",
+ "description": "Apply DISTINCT before aggregation"
+ },
+ "filter": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {}
+ },
+ "$or": {
+ "type": "array",
+ "items": {}
+ },
+ "$not": {}
+ }
+ }
+ ],
+ "description": "Filter/Condition to apply to the aggregation (FILTER WHERE clause)"
+ }
+ },
+ "required": [
+ "function",
+ "alias"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Aggregation functions"
+ },
+ "groupBy": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "GROUP BY fields"
+ },
+ "having": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {}
+ },
+ "$or": {
+ "type": "array",
+ "items": {}
+ },
+ "$not": {}
+ }
+ }
+ ],
+ "description": "HAVING clause for aggregation filtering"
+ },
+ "windowFunctions": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "function": {
+ "type": "string",
+ "enum": [
+ "row_number",
+ "rank",
+ "dense_rank",
+ "percent_rank",
+ "lag",
+ "lead",
+ "first_value",
+ "last_value",
+ "sum",
+ "avg",
+ "count",
+ "min",
+ "max"
+ ],
+ "description": "Window function name"
+ },
+ "field": {
+ "type": "string",
+ "description": "Field to operate on (for aggregate window functions)"
+ },
+ "alias": {
+ "type": "string",
+ "description": "Result column alias"
+ },
+ "over": {
+ "type": "object",
+ "properties": {
+ "partitionBy": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "PARTITION BY fields"
+ },
+ "orderBy": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string"
+ },
+ "order": {
+ "type": "string",
+ "enum": [
+ "asc",
+ "desc"
+ ],
+ "default": "asc"
+ }
+ },
+ "required": [
+ "field"
+ ],
+ "additionalProperties": false
+ },
+ "description": "ORDER BY specification"
+ },
+ "frame": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "rows",
+ "range"
+ ]
+ },
+ "start": {
+ "type": "string",
+ "description": "Frame start (e.g., \"UNBOUNDED PRECEDING\", \"1 PRECEDING\")"
+ },
+ "end": {
+ "type": "string",
+ "description": "Frame end (e.g., \"CURRENT ROW\", \"1 FOLLOWING\")"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Window frame specification"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Window specification (OVER clause)"
+ }
+ },
+ "required": [
+ "function",
+ "alias",
+ "over"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Window functions with OVER clause"
+ },
+ "distinct": {
+ "type": "boolean",
+ "description": "SELECT DISTINCT flag"
+ }
+ },
+ "required": [
+ "object"
+ ],
+ "additionalProperties": false,
+ "description": "Query configuration"
+ },
+ "layout": {
+ "type": "object",
+ "properties": {
+ "columns": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string",
+ "description": "Field name"
+ },
+ "label": {
+ "type": "string",
+ "description": "Custom column label"
+ },
+ "width": {
+ "type": "number",
+ "description": "Column width in pixels"
+ },
+ "sortable": {
+ "type": "boolean",
+ "default": true,
+ "description": "Whether column is sortable"
+ },
+ "filterable": {
+ "type": "boolean",
+ "default": true,
+ "description": "Whether column is filterable"
+ },
+ "visible": {
+ "type": "boolean",
+ "default": true,
+ "description": "Whether column is visible"
+ },
+ "pinned": {
+ "type": "string",
+ "enum": [
+ "left",
+ "right"
+ ],
+ "description": "Pin column to left or right"
+ },
+ "formatter": {
+ "type": "string",
+ "description": "Custom formatter name"
+ },
+ "aggregation": {
+ "type": "string",
+ "description": "Aggregation function for column (sum, avg, etc.)"
+ }
+ },
+ "required": [
+ "field"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Column configuration for list views"
+ },
+ "rowHeight": {
+ "type": "number",
+ "description": "Row height in pixels"
+ },
+ "groupByField": {
+ "type": "string",
+ "description": "Field to group by (for kanban)"
+ },
+ "cardFields": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Fields to display on cards"
+ },
+ "dateField": {
+ "type": "string",
+ "description": "Date field for calendar view"
+ },
+ "startDateField": {
+ "type": "string",
+ "description": "Start date field for event ranges"
+ },
+ "endDateField": {
+ "type": "string",
+ "description": "End date field for event ranges"
+ },
+ "titleField": {
+ "type": "string",
+ "description": "Field to use as event title"
+ },
+ "chartType": {
+ "type": "string",
+ "enum": [
+ "bar",
+ "line",
+ "pie",
+ "scatter",
+ "area"
+ ],
+ "description": "Chart type"
+ },
+ "xAxis": {
+ "type": "string",
+ "description": "X-axis field"
+ },
+ "yAxis": {
+ "type": "string",
+ "description": "Y-axis field"
+ },
+ "series": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Series fields for multi-series charts"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Layout configuration"
+ },
+ "sharedWith": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Users/teams to share with"
+ },
+ "isDefault": {
+ "type": "boolean",
+ "default": false,
+ "description": "Set as default view"
+ },
+ "settings": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "id": {
+ "type": "string",
+ "description": "View ID to update"
+ }
+ },
+ "required": [
+ "id"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/ViewColumn.json b/packages/spec/json-schema/api/ViewColumn.json
new file mode 100644
index 000000000..c88b78ab8
--- /dev/null
+++ b/packages/spec/json-schema/api/ViewColumn.json
@@ -0,0 +1,58 @@
+{
+ "$ref": "#/definitions/ViewColumn",
+ "definitions": {
+ "ViewColumn": {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string",
+ "description": "Field name"
+ },
+ "label": {
+ "type": "string",
+ "description": "Custom column label"
+ },
+ "width": {
+ "type": "number",
+ "description": "Column width in pixels"
+ },
+ "sortable": {
+ "type": "boolean",
+ "default": true,
+ "description": "Whether column is sortable"
+ },
+ "filterable": {
+ "type": "boolean",
+ "default": true,
+ "description": "Whether column is filterable"
+ },
+ "visible": {
+ "type": "boolean",
+ "default": true,
+ "description": "Whether column is visible"
+ },
+ "pinned": {
+ "type": "string",
+ "enum": [
+ "left",
+ "right"
+ ],
+ "description": "Pin column to left or right"
+ },
+ "formatter": {
+ "type": "string",
+ "description": "Custom formatter name"
+ },
+ "aggregation": {
+ "type": "string",
+ "description": "Aggregation function for column (sum, avg, etc.)"
+ }
+ },
+ "required": [
+ "field"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/ViewLayout.json b/packages/spec/json-schema/api/ViewLayout.json
new file mode 100644
index 000000000..1de9023a9
--- /dev/null
+++ b/packages/spec/json-schema/api/ViewLayout.json
@@ -0,0 +1,125 @@
+{
+ "$ref": "#/definitions/ViewLayout",
+ "definitions": {
+ "ViewLayout": {
+ "type": "object",
+ "properties": {
+ "columns": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string",
+ "description": "Field name"
+ },
+ "label": {
+ "type": "string",
+ "description": "Custom column label"
+ },
+ "width": {
+ "type": "number",
+ "description": "Column width in pixels"
+ },
+ "sortable": {
+ "type": "boolean",
+ "default": true,
+ "description": "Whether column is sortable"
+ },
+ "filterable": {
+ "type": "boolean",
+ "default": true,
+ "description": "Whether column is filterable"
+ },
+ "visible": {
+ "type": "boolean",
+ "default": true,
+ "description": "Whether column is visible"
+ },
+ "pinned": {
+ "type": "string",
+ "enum": [
+ "left",
+ "right"
+ ],
+ "description": "Pin column to left or right"
+ },
+ "formatter": {
+ "type": "string",
+ "description": "Custom formatter name"
+ },
+ "aggregation": {
+ "type": "string",
+ "description": "Aggregation function for column (sum, avg, etc.)"
+ }
+ },
+ "required": [
+ "field"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Column configuration for list views"
+ },
+ "rowHeight": {
+ "type": "number",
+ "description": "Row height in pixels"
+ },
+ "groupByField": {
+ "type": "string",
+ "description": "Field to group by (for kanban)"
+ },
+ "cardFields": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Fields to display on cards"
+ },
+ "dateField": {
+ "type": "string",
+ "description": "Date field for calendar view"
+ },
+ "startDateField": {
+ "type": "string",
+ "description": "Start date field for event ranges"
+ },
+ "endDateField": {
+ "type": "string",
+ "description": "End date field for event ranges"
+ },
+ "titleField": {
+ "type": "string",
+ "description": "Field to use as event title"
+ },
+ "chartType": {
+ "type": "string",
+ "enum": [
+ "bar",
+ "line",
+ "pie",
+ "scatter",
+ "area"
+ ],
+ "description": "Chart type"
+ },
+ "xAxis": {
+ "type": "string",
+ "description": "X-axis field"
+ },
+ "yAxis": {
+ "type": "string",
+ "description": "Y-axis field"
+ },
+ "series": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Series fields for multi-series charts"
+ }
+ },
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/ViewResponse.json b/packages/spec/json-schema/api/ViewResponse.json
new file mode 100644
index 000000000..794bd3f43
--- /dev/null
+++ b/packages/spec/json-schema/api/ViewResponse.json
@@ -0,0 +1,1103 @@
+{
+ "$ref": "#/definitions/ViewResponse",
+ "definitions": {
+ "ViewResponse": {
+ "type": "object",
+ "properties": {
+ "success": {
+ "type": "boolean"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "description": "Unique view identifier"
+ },
+ "name": {
+ "type": "string",
+ "pattern": "^[a-z_][a-z0-9_]*$",
+ "description": "View machine name (snake_case)"
+ },
+ "label": {
+ "type": "string",
+ "description": "Display label"
+ },
+ "description": {
+ "type": "string",
+ "description": "View description"
+ },
+ "object": {
+ "type": "string",
+ "description": "Object/table this view is for"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "list",
+ "kanban",
+ "calendar",
+ "gantt",
+ "timeline",
+ "chart",
+ "pivot",
+ "custom"
+ ],
+ "description": "View type"
+ },
+ "visibility": {
+ "type": "string",
+ "enum": [
+ "private",
+ "shared",
+ "public",
+ "organization"
+ ],
+ "description": "Who can access this view"
+ },
+ "query": {
+ "type": "object",
+ "properties": {
+ "object": {
+ "type": "string",
+ "description": "Object name (e.g. account)"
+ },
+ "fields": {
+ "type": "array",
+ "items": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string"
+ },
+ "fields": {
+ "type": "array",
+ "items": {}
+ },
+ "alias": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "field"
+ ],
+ "additionalProperties": false
+ }
+ ]
+ },
+ "description": "Fields to retrieve"
+ },
+ "where": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {}
+ },
+ "$or": {
+ "type": "array",
+ "items": {}
+ },
+ "$not": {}
+ }
+ }
+ ],
+ "description": "Filtering criteria (WHERE)"
+ },
+ "search": {
+ "type": "object",
+ "properties": {
+ "query": {
+ "type": "string",
+ "description": "Search query text"
+ },
+ "fields": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Fields to search in (if not specified, searches all text fields)"
+ },
+ "fuzzy": {
+ "type": "boolean",
+ "default": false,
+ "description": "Enable fuzzy matching (tolerates typos)"
+ },
+ "operator": {
+ "type": "string",
+ "enum": [
+ "and",
+ "or"
+ ],
+ "default": "or",
+ "description": "Logical operator between terms"
+ },
+ "boost": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "number"
+ },
+ "description": "Field-specific relevance boosting (field name -> boost factor)"
+ },
+ "minScore": {
+ "type": "number",
+ "description": "Minimum relevance score threshold"
+ },
+ "language": {
+ "type": "string",
+ "description": "Language for text analysis (e.g., \"en\", \"zh\", \"es\")"
+ },
+ "highlight": {
+ "type": "boolean",
+ "default": false,
+ "description": "Enable search result highlighting"
+ }
+ },
+ "required": [
+ "query"
+ ],
+ "additionalProperties": false,
+ "description": "Full-text search configuration ($search parameter)"
+ },
+ "orderBy": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string"
+ },
+ "order": {
+ "type": "string",
+ "enum": [
+ "asc",
+ "desc"
+ ],
+ "default": "asc"
+ }
+ },
+ "required": [
+ "field"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Sorting instructions (ORDER BY)"
+ },
+ "limit": {
+ "type": "number",
+ "description": "Max records to return (LIMIT)"
+ },
+ "offset": {
+ "type": "number",
+ "description": "Records to skip (OFFSET)"
+ },
+ "cursor": {
+ "type": "object",
+ "additionalProperties": {},
+ "description": "Cursor for keyset pagination"
+ },
+ "joins": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "inner",
+ "left",
+ "right",
+ "full"
+ ],
+ "description": "Join type"
+ },
+ "strategy": {
+ "type": "string",
+ "enum": [
+ "auto",
+ "database",
+ "hash",
+ "loop"
+ ],
+ "description": "Execution strategy hint"
+ },
+ "object": {
+ "type": "string",
+ "description": "Object/table to join"
+ },
+ "alias": {
+ "type": "string",
+ "description": "Table alias"
+ },
+ "on": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {}
+ },
+ "$or": {
+ "type": "array",
+ "items": {}
+ },
+ "$not": {}
+ }
+ }
+ ]
+ }
+ },
+ "$or": {
+ "type": "array",
+ "items": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {}
+ },
+ "$or": {
+ "type": "array",
+ "items": {}
+ },
+ "$not": {}
+ }
+ }
+ ]
+ }
+ },
+ "$not": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {}
+ },
+ "$or": {
+ "type": "array",
+ "items": {}
+ },
+ "$not": {}
+ }
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "description": "Join condition"
+ },
+ "subquery": {
+ "type": "object",
+ "properties": {
+ "object": {
+ "type": "string",
+ "description": "Object name (e.g. account)"
+ },
+ "fields": {
+ "type": "array",
+ "items": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string"
+ },
+ "fields": {
+ "type": "array",
+ "items": {}
+ },
+ "alias": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "field"
+ ],
+ "additionalProperties": false
+ }
+ ]
+ },
+ "description": "Fields to retrieve"
+ },
+ "where": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {}
+ },
+ "$or": {
+ "type": "array",
+ "items": {}
+ },
+ "$not": {}
+ }
+ }
+ ],
+ "description": "Filtering criteria (WHERE)"
+ },
+ "search": {
+ "type": "object",
+ "properties": {
+ "query": {
+ "type": "string",
+ "description": "Search query text"
+ },
+ "fields": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Fields to search in (if not specified, searches all text fields)"
+ },
+ "fuzzy": {
+ "type": "boolean",
+ "default": false,
+ "description": "Enable fuzzy matching (tolerates typos)"
+ },
+ "operator": {
+ "type": "string",
+ "enum": [
+ "and",
+ "or"
+ ],
+ "default": "or",
+ "description": "Logical operator between terms"
+ },
+ "boost": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "number"
+ },
+ "description": "Field-specific relevance boosting (field name -> boost factor)"
+ },
+ "minScore": {
+ "type": "number",
+ "description": "Minimum relevance score threshold"
+ },
+ "language": {
+ "type": "string",
+ "description": "Language for text analysis (e.g., \"en\", \"zh\", \"es\")"
+ },
+ "highlight": {
+ "type": "boolean",
+ "default": false,
+ "description": "Enable search result highlighting"
+ }
+ },
+ "required": [
+ "query"
+ ],
+ "additionalProperties": false,
+ "description": "Full-text search configuration ($search parameter)"
+ },
+ "orderBy": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string"
+ },
+ "order": {
+ "type": "string",
+ "enum": [
+ "asc",
+ "desc"
+ ],
+ "default": "asc"
+ }
+ },
+ "required": [
+ "field"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Sorting instructions (ORDER BY)"
+ },
+ "limit": {
+ "type": "number",
+ "description": "Max records to return (LIMIT)"
+ },
+ "offset": {
+ "type": "number",
+ "description": "Records to skip (OFFSET)"
+ },
+ "cursor": {
+ "type": "object",
+ "additionalProperties": {},
+ "description": "Cursor for keyset pagination"
+ },
+ "joins": {},
+ "aggregations": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "function": {
+ "type": "string",
+ "enum": [
+ "count",
+ "sum",
+ "avg",
+ "min",
+ "max",
+ "count_distinct",
+ "array_agg",
+ "string_agg"
+ ],
+ "description": "Aggregation function"
+ },
+ "field": {
+ "type": "string",
+ "description": "Field to aggregate (optional for COUNT(*))"
+ },
+ "alias": {
+ "type": "string",
+ "description": "Result column alias"
+ },
+ "distinct": {
+ "type": "boolean",
+ "description": "Apply DISTINCT before aggregation"
+ },
+ "filter": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {}
+ },
+ "$or": {
+ "type": "array",
+ "items": {}
+ },
+ "$not": {}
+ }
+ }
+ ],
+ "description": "Filter/Condition to apply to the aggregation (FILTER WHERE clause)"
+ }
+ },
+ "required": [
+ "function",
+ "alias"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Aggregation functions"
+ },
+ "groupBy": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "GROUP BY fields"
+ },
+ "having": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {}
+ },
+ "$or": {
+ "type": "array",
+ "items": {}
+ },
+ "$not": {}
+ }
+ }
+ ],
+ "description": "HAVING clause for aggregation filtering"
+ },
+ "windowFunctions": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "function": {
+ "type": "string",
+ "enum": [
+ "row_number",
+ "rank",
+ "dense_rank",
+ "percent_rank",
+ "lag",
+ "lead",
+ "first_value",
+ "last_value",
+ "sum",
+ "avg",
+ "count",
+ "min",
+ "max"
+ ],
+ "description": "Window function name"
+ },
+ "field": {
+ "type": "string",
+ "description": "Field to operate on (for aggregate window functions)"
+ },
+ "alias": {
+ "type": "string",
+ "description": "Result column alias"
+ },
+ "over": {
+ "type": "object",
+ "properties": {
+ "partitionBy": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "PARTITION BY fields"
+ },
+ "orderBy": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string"
+ },
+ "order": {
+ "type": "string",
+ "enum": [
+ "asc",
+ "desc"
+ ],
+ "default": "asc"
+ }
+ },
+ "required": [
+ "field"
+ ],
+ "additionalProperties": false
+ },
+ "description": "ORDER BY specification"
+ },
+ "frame": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "rows",
+ "range"
+ ]
+ },
+ "start": {
+ "type": "string",
+ "description": "Frame start (e.g., \"UNBOUNDED PRECEDING\", \"1 PRECEDING\")"
+ },
+ "end": {
+ "type": "string",
+ "description": "Frame end (e.g., \"CURRENT ROW\", \"1 FOLLOWING\")"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Window frame specification"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Window specification (OVER clause)"
+ }
+ },
+ "required": [
+ "function",
+ "alias",
+ "over"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Window functions with OVER clause"
+ },
+ "distinct": {
+ "type": "boolean",
+ "description": "SELECT DISTINCT flag"
+ }
+ },
+ "required": [
+ "object"
+ ],
+ "additionalProperties": false,
+ "description": "Subquery instead of object"
+ }
+ },
+ "required": [
+ "type",
+ "object",
+ "on"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Explicit Table Joins"
+ },
+ "aggregations": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "function": {
+ "type": "string",
+ "enum": [
+ "count",
+ "sum",
+ "avg",
+ "min",
+ "max",
+ "count_distinct",
+ "array_agg",
+ "string_agg"
+ ],
+ "description": "Aggregation function"
+ },
+ "field": {
+ "type": "string",
+ "description": "Field to aggregate (optional for COUNT(*))"
+ },
+ "alias": {
+ "type": "string",
+ "description": "Result column alias"
+ },
+ "distinct": {
+ "type": "boolean",
+ "description": "Apply DISTINCT before aggregation"
+ },
+ "filter": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {}
+ },
+ "$or": {
+ "type": "array",
+ "items": {}
+ },
+ "$not": {}
+ }
+ }
+ ],
+ "description": "Filter/Condition to apply to the aggregation (FILTER WHERE clause)"
+ }
+ },
+ "required": [
+ "function",
+ "alias"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Aggregation functions"
+ },
+ "groupBy": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "GROUP BY fields"
+ },
+ "having": {
+ "allOf": [
+ {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$and": {
+ "type": "array",
+ "items": {}
+ },
+ "$or": {
+ "type": "array",
+ "items": {}
+ },
+ "$not": {}
+ }
+ }
+ ],
+ "description": "HAVING clause for aggregation filtering"
+ },
+ "windowFunctions": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "function": {
+ "type": "string",
+ "enum": [
+ "row_number",
+ "rank",
+ "dense_rank",
+ "percent_rank",
+ "lag",
+ "lead",
+ "first_value",
+ "last_value",
+ "sum",
+ "avg",
+ "count",
+ "min",
+ "max"
+ ],
+ "description": "Window function name"
+ },
+ "field": {
+ "type": "string",
+ "description": "Field to operate on (for aggregate window functions)"
+ },
+ "alias": {
+ "type": "string",
+ "description": "Result column alias"
+ },
+ "over": {
+ "type": "object",
+ "properties": {
+ "partitionBy": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "PARTITION BY fields"
+ },
+ "orderBy": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string"
+ },
+ "order": {
+ "type": "string",
+ "enum": [
+ "asc",
+ "desc"
+ ],
+ "default": "asc"
+ }
+ },
+ "required": [
+ "field"
+ ],
+ "additionalProperties": false
+ },
+ "description": "ORDER BY specification"
+ },
+ "frame": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "rows",
+ "range"
+ ]
+ },
+ "start": {
+ "type": "string",
+ "description": "Frame start (e.g., \"UNBOUNDED PRECEDING\", \"1 PRECEDING\")"
+ },
+ "end": {
+ "type": "string",
+ "description": "Frame end (e.g., \"CURRENT ROW\", \"1 FOLLOWING\")"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Window frame specification"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Window specification (OVER clause)"
+ }
+ },
+ "required": [
+ "function",
+ "alias",
+ "over"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Window functions with OVER clause"
+ },
+ "distinct": {
+ "type": "boolean",
+ "description": "SELECT DISTINCT flag"
+ }
+ },
+ "required": [
+ "object"
+ ],
+ "additionalProperties": false,
+ "description": "Query configuration (filters, sorting, etc.)"
+ },
+ "layout": {
+ "type": "object",
+ "properties": {
+ "columns": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string",
+ "description": "Field name"
+ },
+ "label": {
+ "type": "string",
+ "description": "Custom column label"
+ },
+ "width": {
+ "type": "number",
+ "description": "Column width in pixels"
+ },
+ "sortable": {
+ "type": "boolean",
+ "default": true,
+ "description": "Whether column is sortable"
+ },
+ "filterable": {
+ "type": "boolean",
+ "default": true,
+ "description": "Whether column is filterable"
+ },
+ "visible": {
+ "type": "boolean",
+ "default": true,
+ "description": "Whether column is visible"
+ },
+ "pinned": {
+ "type": "string",
+ "enum": [
+ "left",
+ "right"
+ ],
+ "description": "Pin column to left or right"
+ },
+ "formatter": {
+ "type": "string",
+ "description": "Custom formatter name"
+ },
+ "aggregation": {
+ "type": "string",
+ "description": "Aggregation function for column (sum, avg, etc.)"
+ }
+ },
+ "required": [
+ "field"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Column configuration for list views"
+ },
+ "rowHeight": {
+ "type": "number",
+ "description": "Row height in pixels"
+ },
+ "groupByField": {
+ "type": "string",
+ "description": "Field to group by (for kanban)"
+ },
+ "cardFields": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Fields to display on cards"
+ },
+ "dateField": {
+ "type": "string",
+ "description": "Date field for calendar view"
+ },
+ "startDateField": {
+ "type": "string",
+ "description": "Start date field for event ranges"
+ },
+ "endDateField": {
+ "type": "string",
+ "description": "End date field for event ranges"
+ },
+ "titleField": {
+ "type": "string",
+ "description": "Field to use as event title"
+ },
+ "chartType": {
+ "type": "string",
+ "enum": [
+ "bar",
+ "line",
+ "pie",
+ "scatter",
+ "area"
+ ],
+ "description": "Chart type"
+ },
+ "xAxis": {
+ "type": "string",
+ "description": "X-axis field"
+ },
+ "yAxis": {
+ "type": "string",
+ "description": "Y-axis field"
+ },
+ "series": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Series fields for multi-series charts"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Layout configuration"
+ },
+ "sharedWith": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "User/team IDs this view is shared with"
+ },
+ "isDefault": {
+ "type": "boolean",
+ "default": false,
+ "description": "Is this the default view for this object?"
+ },
+ "isSystem": {
+ "type": "boolean",
+ "default": false,
+ "description": "Is this a system-defined view?"
+ },
+ "createdBy": {
+ "type": "string",
+ "description": "User ID who created this view"
+ },
+ "createdAt": {
+ "type": "string",
+ "format": "date-time",
+ "description": "When the view was created"
+ },
+ "updatedBy": {
+ "type": "string",
+ "description": "User ID who last updated this view"
+ },
+ "updatedAt": {
+ "type": "string",
+ "format": "date-time",
+ "description": "When the view was last updated"
+ },
+ "settings": {
+ "type": "object",
+ "additionalProperties": {},
+ "description": "Additional view-specific settings"
+ }
+ },
+ "required": [
+ "id",
+ "name",
+ "label",
+ "object",
+ "type",
+ "visibility",
+ "query",
+ "createdBy",
+ "createdAt"
+ ],
+ "additionalProperties": false,
+ "description": "The saved view"
+ },
+ "error": {
+ "type": "object",
+ "properties": {
+ "code": {
+ "type": "string"
+ },
+ "message": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "code",
+ "message"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "required": [
+ "success"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/ViewType.json b/packages/spec/json-schema/api/ViewType.json
new file mode 100644
index 000000000..0ac384fcf
--- /dev/null
+++ b/packages/spec/json-schema/api/ViewType.json
@@ -0,0 +1,19 @@
+{
+ "$ref": "#/definitions/ViewType",
+ "definitions": {
+ "ViewType": {
+ "type": "string",
+ "enum": [
+ "list",
+ "kanban",
+ "calendar",
+ "gantt",
+ "timeline",
+ "chart",
+ "pivot",
+ "custom"
+ ]
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/ViewVisibility.json b/packages/spec/json-schema/api/ViewVisibility.json
new file mode 100644
index 000000000..679625540
--- /dev/null
+++ b/packages/spec/json-schema/api/ViewVisibility.json
@@ -0,0 +1,15 @@
+{
+ "$ref": "#/definitions/ViewVisibility",
+ "definitions": {
+ "ViewVisibility": {
+ "type": "string",
+ "enum": [
+ "private",
+ "shared",
+ "public",
+ "organization"
+ ]
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/data/FullTextSearch.json b/packages/spec/json-schema/data/FullTextSearch.json
new file mode 100644
index 000000000..7974fefd3
--- /dev/null
+++ b/packages/spec/json-schema/data/FullTextSearch.json
@@ -0,0 +1,60 @@
+{
+ "$ref": "#/definitions/FullTextSearch",
+ "definitions": {
+ "FullTextSearch": {
+ "type": "object",
+ "properties": {
+ "query": {
+ "type": "string",
+ "description": "Search query text"
+ },
+ "fields": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Fields to search in (if not specified, searches all text fields)"
+ },
+ "fuzzy": {
+ "type": "boolean",
+ "default": false,
+ "description": "Enable fuzzy matching (tolerates typos)"
+ },
+ "operator": {
+ "type": "string",
+ "enum": [
+ "and",
+ "or"
+ ],
+ "default": "or",
+ "description": "Logical operator between terms"
+ },
+ "boost": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "number"
+ },
+ "description": "Field-specific relevance boosting (field name -> boost factor)"
+ },
+ "minScore": {
+ "type": "number",
+ "description": "Minimum relevance score threshold"
+ },
+ "language": {
+ "type": "string",
+ "description": "Language for text analysis (e.g., \"en\", \"zh\", \"es\")"
+ },
+ "highlight": {
+ "type": "boolean",
+ "default": false,
+ "description": "Enable search result highlighting"
+ }
+ },
+ "required": [
+ "query"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/data/JoinNode.json b/packages/spec/json-schema/data/JoinNode.json
index 13b5210a7..8c1fd80b8 100644
--- a/packages/spec/json-schema/data/JoinNode.json
+++ b/packages/spec/json-schema/data/JoinNode.json
@@ -178,6 +178,61 @@
],
"description": "Filtering criteria (WHERE)"
},
+ "search": {
+ "type": "object",
+ "properties": {
+ "query": {
+ "type": "string",
+ "description": "Search query text"
+ },
+ "fields": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Fields to search in (if not specified, searches all text fields)"
+ },
+ "fuzzy": {
+ "type": "boolean",
+ "default": false,
+ "description": "Enable fuzzy matching (tolerates typos)"
+ },
+ "operator": {
+ "type": "string",
+ "enum": [
+ "and",
+ "or"
+ ],
+ "default": "or",
+ "description": "Logical operator between terms"
+ },
+ "boost": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "number"
+ },
+ "description": "Field-specific relevance boosting (field name -> boost factor)"
+ },
+ "minScore": {
+ "type": "number",
+ "description": "Minimum relevance score threshold"
+ },
+ "language": {
+ "type": "string",
+ "description": "Language for text analysis (e.g., \"en\", \"zh\", \"es\")"
+ },
+ "highlight": {
+ "type": "boolean",
+ "default": false,
+ "description": "Enable search result highlighting"
+ }
+ },
+ "required": [
+ "query"
+ ],
+ "additionalProperties": false,
+ "description": "Full-text search configuration ($search parameter)"
+ },
"orderBy": {
"type": "array",
"items": {
diff --git a/packages/spec/json-schema/data/Query.json b/packages/spec/json-schema/data/Query.json
index 16b7617a7..0148afa84 100644
--- a/packages/spec/json-schema/data/Query.json
+++ b/packages/spec/json-schema/data/Query.json
@@ -61,6 +61,61 @@
],
"description": "Filtering criteria (WHERE)"
},
+ "search": {
+ "type": "object",
+ "properties": {
+ "query": {
+ "type": "string",
+ "description": "Search query text"
+ },
+ "fields": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Fields to search in (if not specified, searches all text fields)"
+ },
+ "fuzzy": {
+ "type": "boolean",
+ "default": false,
+ "description": "Enable fuzzy matching (tolerates typos)"
+ },
+ "operator": {
+ "type": "string",
+ "enum": [
+ "and",
+ "or"
+ ],
+ "default": "or",
+ "description": "Logical operator between terms"
+ },
+ "boost": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "number"
+ },
+ "description": "Field-specific relevance boosting (field name -> boost factor)"
+ },
+ "minScore": {
+ "type": "number",
+ "description": "Minimum relevance score threshold"
+ },
+ "language": {
+ "type": "string",
+ "description": "Language for text analysis (e.g., \"en\", \"zh\", \"es\")"
+ },
+ "highlight": {
+ "type": "boolean",
+ "default": false,
+ "description": "Enable search result highlighting"
+ }
+ },
+ "required": [
+ "query"
+ ],
+ "additionalProperties": false,
+ "description": "Full-text search configuration ($search parameter)"
+ },
"orderBy": {
"type": "array",
"items": {
diff --git a/packages/spec/src/api/batch.test.ts b/packages/spec/src/api/batch.test.ts
new file mode 100644
index 000000000..91e461faa
--- /dev/null
+++ b/packages/spec/src/api/batch.test.ts
@@ -0,0 +1,337 @@
+import { describe, it, expect } from 'vitest';
+import {
+ BatchOperationType,
+ BatchRecordSchema,
+ BatchOptionsSchema,
+ BatchUpdateRequestSchema,
+ UpdateManyRequestSchema,
+ BatchOperationResultSchema,
+ BatchUpdateResponseSchema,
+ DeleteManyRequestSchema,
+ BatchApiContracts,
+} from './batch.zod';
+
+describe('BatchOperationType', () => {
+ it('should accept valid operation types', () => {
+ expect(BatchOperationType.parse('create')).toBe('create');
+ expect(BatchOperationType.parse('update')).toBe('update');
+ expect(BatchOperationType.parse('upsert')).toBe('upsert');
+ expect(BatchOperationType.parse('delete')).toBe('delete');
+ });
+
+ it('should reject invalid operation types', () => {
+ expect(() => BatchOperationType.parse('invalid')).toThrow();
+ });
+});
+
+describe('BatchRecordSchema', () => {
+ it('should accept valid batch record for update', () => {
+ const record = BatchRecordSchema.parse({
+ id: '123',
+ data: { name: 'Updated Name', status: 'active' },
+ });
+
+ expect(record.id).toBe('123');
+ expect(record.data).toEqual({ name: 'Updated Name', status: 'active' });
+ });
+
+ it('should accept record with external ID for upsert', () => {
+ const record = BatchRecordSchema.parse({
+ data: { name: 'New Record' },
+ externalId: 'ext_123',
+ });
+
+ expect(record.externalId).toBe('ext_123');
+ });
+
+ it('should accept minimal record', () => {
+ const record = BatchRecordSchema.parse({});
+ expect(record).toBeDefined();
+ });
+});
+
+describe('BatchOptionsSchema', () => {
+ it('should use default values', () => {
+ const options = BatchOptionsSchema.parse({});
+
+ expect(options.atomic).toBe(true);
+ expect(options.returnRecords).toBe(false);
+ expect(options.continueOnError).toBe(false);
+ expect(options.validateOnly).toBe(false);
+ });
+
+ it('should accept custom options', () => {
+ const options = BatchOptionsSchema.parse({
+ atomic: false,
+ returnRecords: true,
+ continueOnError: true,
+ validateOnly: true,
+ });
+
+ expect(options.atomic).toBe(false);
+ expect(options.returnRecords).toBe(true);
+ expect(options.continueOnError).toBe(true);
+ expect(options.validateOnly).toBe(true);
+ });
+});
+
+describe('BatchUpdateRequestSchema', () => {
+ it('should accept valid batch update request', () => {
+ const request = BatchUpdateRequestSchema.parse({
+ operation: 'update',
+ records: [
+ { id: '1', data: { name: 'Name 1' } },
+ { id: '2', data: { name: 'Name 2' } },
+ ],
+ options: {
+ atomic: true,
+ returnRecords: true,
+ },
+ });
+
+ expect(request.operation).toBe('update');
+ expect(request.records).toHaveLength(2);
+ expect(request.options?.atomic).toBe(true);
+ });
+
+ it('should require at least one record', () => {
+ expect(() =>
+ BatchUpdateRequestSchema.parse({
+ operation: 'create',
+ records: [],
+ })
+ ).toThrow();
+ });
+
+ it('should limit to 200 records', () => {
+ const records = Array(201)
+ .fill(null)
+ .map((_, i) => ({ id: String(i), data: {} }));
+
+ expect(() =>
+ BatchUpdateRequestSchema.parse({
+ operation: 'update',
+ records,
+ })
+ ).toThrow();
+ });
+
+ it('should accept exactly 200 records', () => {
+ const records = Array(200)
+ .fill(null)
+ .map((_, i) => ({ id: String(i), data: {} }));
+
+ const request = BatchUpdateRequestSchema.parse({
+ operation: 'update',
+ records,
+ });
+
+ expect(request.records).toHaveLength(200);
+ });
+});
+
+describe('UpdateManyRequestSchema', () => {
+ it('should accept valid updateMany request', () => {
+ const request = UpdateManyRequestSchema.parse({
+ records: [
+ { id: '1', data: { name: 'Updated 1' } },
+ { id: '2', data: { name: 'Updated 2' } },
+ ],
+ options: { atomic: true },
+ });
+
+ expect(request.records).toHaveLength(2);
+ expect(request.options?.atomic).toBe(true);
+ });
+
+ it('should work without options', () => {
+ const request = UpdateManyRequestSchema.parse({
+ records: [{ id: '1', data: { name: 'Updated' } }],
+ });
+
+ expect(request.records).toHaveLength(1);
+ expect(request.options).toBeUndefined();
+ });
+});
+
+describe('BatchOperationResultSchema', () => {
+ it('should accept successful result', () => {
+ const result = BatchOperationResultSchema.parse({
+ id: '123',
+ success: true,
+ index: 0,
+ });
+
+ expect(result.success).toBe(true);
+ expect(result.id).toBe('123');
+ expect(result.errors).toBeUndefined();
+ });
+
+ it('should accept failed result with errors', () => {
+ const result = BatchOperationResultSchema.parse({
+ success: false,
+ index: 1,
+ errors: [
+ {
+ code: 'validation_error',
+ message: 'Invalid email format',
+ },
+ ],
+ });
+
+ expect(result.success).toBe(false);
+ expect(result.errors).toHaveLength(1);
+ expect(result.errors?.[0].code).toBe('validation_error');
+ });
+
+ it('should accept result with full record data', () => {
+ const result = BatchOperationResultSchema.parse({
+ id: '123',
+ success: true,
+ data: { id: '123', name: 'Test Record', status: 'active' },
+ index: 0,
+ });
+
+ expect(result.data).toBeDefined();
+ expect(result.data?.name).toBe('Test Record');
+ });
+});
+
+describe('BatchUpdateResponseSchema', () => {
+ it('should accept successful batch response', () => {
+ const response = BatchUpdateResponseSchema.parse({
+ success: true,
+ operation: 'update',
+ total: 2,
+ succeeded: 2,
+ failed: 0,
+ results: [
+ { id: '1', success: true, index: 0 },
+ { id: '2', success: true, index: 1 },
+ ],
+ meta: {
+ timestamp: '2026-01-29T12:00:00Z',
+ duration: 150,
+ },
+ });
+
+ expect(response.success).toBe(true);
+ expect(response.total).toBe(2);
+ expect(response.succeeded).toBe(2);
+ expect(response.failed).toBe(0);
+ });
+
+ it('should accept partial success response', () => {
+ const response = BatchUpdateResponseSchema.parse({
+ success: false,
+ operation: 'update',
+ total: 2,
+ succeeded: 1,
+ failed: 1,
+ results: [
+ { id: '1', success: true, index: 0 },
+ {
+ success: false,
+ index: 1,
+ errors: [{ code: 'validation_error', message: 'Invalid data' }],
+ },
+ ],
+ });
+
+ expect(response.success).toBe(false);
+ expect(response.succeeded).toBe(1);
+ expect(response.failed).toBe(1);
+ });
+
+ it('should accept response with error details', () => {
+ const response = BatchUpdateResponseSchema.parse({
+ success: false,
+ operation: 'create',
+ total: 1,
+ succeeded: 0,
+ failed: 1,
+ results: [
+ {
+ success: false,
+ index: 0,
+ errors: [
+ {
+ code: 'duplicate_value',
+ message: 'Record already exists',
+ details: { field: 'email', value: 'test@example.com' },
+ },
+ ],
+ },
+ ],
+ error: {
+ code: 'batch_partial_failure',
+ message: 'Batch operation failed',
+ },
+ });
+
+ expect(response.failed).toBe(1);
+ expect(response.error?.code).toBe('batch_partial_failure');
+ });
+});
+
+describe('DeleteManyRequestSchema', () => {
+ it('should accept valid delete request', () => {
+ const request = DeleteManyRequestSchema.parse({
+ ids: ['1', '2', '3'],
+ options: { atomic: true },
+ });
+
+ expect(request.ids).toHaveLength(3);
+ expect(request.options?.atomic).toBe(true);
+ });
+
+ it('should require at least one ID', () => {
+ expect(() =>
+ DeleteManyRequestSchema.parse({
+ ids: [],
+ })
+ ).toThrow();
+ });
+
+ it('should limit to 200 IDs', () => {
+ const ids = Array(201)
+ .fill(null)
+ .map((_, i) => String(i));
+
+ expect(() =>
+ DeleteManyRequestSchema.parse({
+ ids,
+ })
+ ).toThrow();
+ });
+});
+
+describe('BatchApiContracts', () => {
+ it('should have correct contract structure', () => {
+ expect(BatchApiContracts.batchOperation).toBeDefined();
+ expect(BatchApiContracts.batchOperation.input).toBeDefined();
+ expect(BatchApiContracts.batchOperation.output).toBeDefined();
+
+ expect(BatchApiContracts.updateMany).toBeDefined();
+ expect(BatchApiContracts.deleteMany).toBeDefined();
+ });
+
+ it('should validate batchOperation contract', () => {
+ const input = {
+ operation: 'update',
+ records: [{ id: '1', data: { name: 'Test' } }],
+ };
+
+ const parsedInput = BatchApiContracts.batchOperation.input.parse(input);
+ expect(parsedInput.operation).toBe('update');
+ });
+
+ it('should validate updateMany contract', () => {
+ const input = {
+ records: [{ id: '1', data: { name: 'Test' } }],
+ };
+
+ const parsedInput = BatchApiContracts.updateMany.input.parse(input);
+ expect(parsedInput.records).toHaveLength(1);
+ });
+});
diff --git a/packages/spec/src/api/batch.zod.ts b/packages/spec/src/api/batch.zod.ts
new file mode 100644
index 000000000..a50698e17
--- /dev/null
+++ b/packages/spec/src/api/batch.zod.ts
@@ -0,0 +1,224 @@
+import { z } from 'zod';
+import { ApiErrorSchema, BaseResponseSchema, RecordDataSchema } from './contract.zod';
+
+/**
+ * Batch Operations API
+ *
+ * Provides efficient bulk data operations with transaction support.
+ * Implements P0/P1 requirements for ObjectStack kernel.
+ *
+ * Features:
+ * - Batch create/update/delete operations
+ * - Atomic transaction support (all-or-none)
+ * - Partial success handling
+ * - Detailed error reporting per record
+ *
+ * Industry alignment: Salesforce Bulk API, Microsoft Dynamics Bulk Operations
+ */
+
+// ==========================================
+// Batch Operation Types
+// ==========================================
+
+/**
+ * Batch Operation Type Enum
+ * Defines the type of batch operation to perform
+ */
+export const BatchOperationType = z.enum([
+ 'create', // Batch insert
+ 'update', // Batch update
+ 'upsert', // Batch upsert (insert or update based on external ID)
+ 'delete', // Batch delete
+]);
+
+export type BatchOperationType = z.infer;
+
+// ==========================================
+// Batch Request Schemas
+// ==========================================
+
+/**
+ * Batch Record Schema
+ * Individual record in a batch operation
+ */
+export const BatchRecordSchema = z.object({
+ id: z.string().optional().describe('Record ID (required for update/delete)'),
+ data: RecordDataSchema.optional().describe('Record data (required for create/update/upsert)'),
+ externalId: z.string().optional().describe('External ID for upsert matching'),
+});
+
+export type BatchRecord = z.infer;
+
+/**
+ * Batch Operation Options Schema
+ * Configuration options for batch operations
+ */
+export const BatchOptionsSchema = z.object({
+ atomic: z.boolean().optional().default(true).describe('If true, rollback entire batch on any failure (transaction mode)'),
+ returnRecords: z.boolean().optional().default(false).describe('If true, return full record data in response'),
+ continueOnError: z.boolean().optional().default(false).describe('If true (and atomic=false), continue processing remaining records after errors'),
+ validateOnly: z.boolean().optional().default(false).describe('If true, validate records without persisting changes (dry-run mode)'),
+});
+
+export type BatchOptions = z.infer;
+
+/**
+ * Batch Update Request Schema
+ * Request payload for batch update operations
+ *
+ * @example
+ * // POST /api/v1/data/{object}/batch
+ * {
+ * "operation": "update",
+ * "records": [
+ * { "id": "1", "data": { "name": "Updated Name 1", "status": "active" } },
+ * { "id": "2", "data": { "name": "Updated Name 2", "status": "active" } }
+ * ],
+ * "options": {
+ * "atomic": true,
+ * "returnRecords": true
+ * }
+ * }
+ */
+export const BatchUpdateRequestSchema = z.object({
+ operation: BatchOperationType.describe('Type of batch operation'),
+ records: z.array(BatchRecordSchema).min(1).max(200).describe('Array of records to process (max 200 per batch)'),
+ options: BatchOptionsSchema.optional().describe('Batch operation options'),
+});
+
+export type BatchUpdateRequest = z.infer;
+
+/**
+ * Simplified Batch Update Request (for updateMany API)
+ * Simplified request for batch updates without operation field
+ *
+ * @example
+ * // POST /api/v1/data/{object}/updateMany
+ * {
+ * "records": [
+ * { "id": "1", "data": { "name": "Updated Name 1" } },
+ * { "id": "2", "data": { "name": "Updated Name 2" } }
+ * ],
+ * "options": { "atomic": true }
+ * }
+ */
+export const UpdateManyRequestSchema = z.object({
+ records: z.array(BatchRecordSchema).min(1).max(200).describe('Array of records to update (max 200 per batch)'),
+ options: BatchOptionsSchema.optional().describe('Update options'),
+});
+
+export type UpdateManyRequest = z.infer;
+
+// ==========================================
+// Batch Response Schemas
+// ==========================================
+
+/**
+ * Batch Operation Result Schema
+ * Result for a single record in a batch operation
+ */
+export const BatchOperationResultSchema = z.object({
+ id: z.string().optional().describe('Record ID if operation succeeded'),
+ success: z.boolean().describe('Whether this record was processed successfully'),
+ errors: z.array(ApiErrorSchema).optional().describe('Array of errors if operation failed'),
+ data: RecordDataSchema.optional().describe('Full record data (if returnRecords=true)'),
+ index: z.number().optional().describe('Index of the record in the request array'),
+});
+
+export type BatchOperationResult = z.infer;
+
+/**
+ * Batch Update Response Schema
+ * Response payload for batch operations
+ *
+ * @example Success Response
+ * {
+ * "success": true,
+ * "operation": "update",
+ * "total": 2,
+ * "succeeded": 2,
+ * "failed": 0,
+ * "results": [
+ * { "id": "1", "success": true, "index": 0 },
+ * { "id": "2", "success": true, "index": 1 }
+ * ],
+ * "meta": {
+ * "timestamp": "2026-01-29T12:00:00Z",
+ * "duration": 150
+ * }
+ * }
+ *
+ * @example Partial Success Response (atomic=false)
+ * {
+ * "success": false,
+ * "operation": "update",
+ * "total": 2,
+ * "succeeded": 1,
+ * "failed": 1,
+ * "results": [
+ * { "id": "1", "success": true, "index": 0 },
+ * {
+ * "success": false,
+ * "index": 1,
+ * "errors": [{ "code": "validation_error", "message": "Invalid email format" }]
+ * }
+ * ],
+ * "meta": {
+ * "timestamp": "2026-01-29T12:00:00Z"
+ * }
+ * }
+ */
+export const BatchUpdateResponseSchema = BaseResponseSchema.extend({
+ operation: BatchOperationType.optional().describe('Operation type that was performed'),
+ total: z.number().describe('Total number of records in the batch'),
+ succeeded: z.number().describe('Number of records that succeeded'),
+ failed: z.number().describe('Number of records that failed'),
+ results: z.array(BatchOperationResultSchema).describe('Detailed results for each record'),
+});
+
+export type BatchUpdateResponse = z.infer;
+
+// ==========================================
+// Batch Delete Schemas
+// ==========================================
+
+/**
+ * Batch Delete Request Schema
+ * Simplified request for batch delete operations
+ *
+ * @example
+ * // POST /api/v1/data/{object}/deleteMany
+ * {
+ * "ids": ["1", "2", "3"],
+ * "options": { "atomic": true }
+ * }
+ */
+export const DeleteManyRequestSchema = z.object({
+ ids: z.array(z.string()).min(1).max(200).describe('Array of record IDs to delete (max 200)'),
+ options: BatchOptionsSchema.optional().describe('Delete options'),
+});
+
+export type DeleteManyRequest = z.infer;
+
+// ==========================================
+// API Contract Exports
+// ==========================================
+
+/**
+ * Batch API Contracts
+ * Standardized contracts for batch operations
+ */
+export const BatchApiContracts = {
+ batchOperation: {
+ input: BatchUpdateRequestSchema,
+ output: BatchUpdateResponseSchema,
+ },
+ updateMany: {
+ input: UpdateManyRequestSchema,
+ output: BatchUpdateResponseSchema,
+ },
+ deleteMany: {
+ input: DeleteManyRequestSchema,
+ output: BatchUpdateResponseSchema,
+ },
+};
diff --git a/packages/spec/src/api/cache.test.ts b/packages/spec/src/api/cache.test.ts
new file mode 100644
index 000000000..204b2e5e6
--- /dev/null
+++ b/packages/spec/src/api/cache.test.ts
@@ -0,0 +1,264 @@
+import { describe, it, expect } from 'vitest';
+import {
+ CacheDirective,
+ CacheControlSchema,
+ ETagSchema,
+ MetadataCacheRequestSchema,
+ MetadataCacheResponseSchema,
+ CacheInvalidationTarget,
+ CacheInvalidationRequestSchema,
+ CacheInvalidationResponseSchema,
+ MetadataCacheApi,
+} from './cache.zod';
+
+describe('CacheDirective', () => {
+ it('should accept valid cache directives', () => {
+ expect(CacheDirective.parse('public')).toBe('public');
+ expect(CacheDirective.parse('private')).toBe('private');
+ expect(CacheDirective.parse('no-cache')).toBe('no-cache');
+ expect(CacheDirective.parse('no-store')).toBe('no-store');
+ expect(CacheDirective.parse('must-revalidate')).toBe('must-revalidate');
+ expect(CacheDirective.parse('max-age')).toBe('max-age');
+ });
+});
+
+describe('CacheControlSchema', () => {
+ it('should accept basic cache control', () => {
+ const control = CacheControlSchema.parse({
+ directives: ['public', 'max-age'],
+ maxAge: 3600,
+ });
+
+ expect(control.directives).toContain('public');
+ expect(control.maxAge).toBe(3600);
+ });
+
+ it('should accept cache control with stale options', () => {
+ const control = CacheControlSchema.parse({
+ directives: ['public'],
+ maxAge: 3600,
+ staleWhileRevalidate: 86400,
+ staleIfError: 604800,
+ });
+
+ expect(control.staleWhileRevalidate).toBe(86400);
+ expect(control.staleIfError).toBe(604800);
+ });
+});
+
+describe('ETagSchema', () => {
+ it('should accept strong ETag', () => {
+ const etag = ETagSchema.parse({
+ value: '686897696a7c876b7e',
+ weak: false,
+ });
+
+ expect(etag.value).toBe('686897696a7c876b7e');
+ expect(etag.weak).toBe(false);
+ });
+
+ it('should accept weak ETag', () => {
+ const etag = ETagSchema.parse({
+ value: 'W/"686897696a7c876b7e"',
+ weak: true,
+ });
+
+ expect(etag.weak).toBe(true);
+ });
+
+ it('should default to strong ETag', () => {
+ const etag = ETagSchema.parse({
+ value: 'abc123',
+ });
+
+ expect(etag.weak).toBe(false);
+ });
+});
+
+describe('MetadataCacheRequestSchema', () => {
+ it('should accept request with If-None-Match', () => {
+ const request = MetadataCacheRequestSchema.parse({
+ ifNoneMatch: '"686897696a7c876b7e"',
+ });
+
+ expect(request.ifNoneMatch).toBe('"686897696a7c876b7e"');
+ });
+
+ it('should accept request with If-Modified-Since', () => {
+ const request = MetadataCacheRequestSchema.parse({
+ ifModifiedSince: '2026-01-29T12:00:00Z',
+ });
+
+ expect(request.ifModifiedSince).toBe('2026-01-29T12:00:00Z');
+ });
+
+ it('should accept request with both headers', () => {
+ const request = MetadataCacheRequestSchema.parse({
+ ifNoneMatch: '"abc123"',
+ ifModifiedSince: '2026-01-29T12:00:00Z',
+ cacheControl: {
+ directives: ['no-cache'],
+ },
+ });
+
+ expect(request.ifNoneMatch).toBeDefined();
+ expect(request.ifModifiedSince).toBeDefined();
+ expect(request.cacheControl).toBeDefined();
+ });
+
+ it('should accept empty request', () => {
+ const request = MetadataCacheRequestSchema.parse({});
+ expect(request).toBeDefined();
+ });
+});
+
+describe('MetadataCacheResponseSchema', () => {
+ it('should accept successful response with metadata', () => {
+ const response = MetadataCacheResponseSchema.parse({
+ data: { object: 'account', fields: [] },
+ etag: {
+ value: '686897696a7c876b7e',
+ weak: false,
+ },
+ lastModified: '2026-01-29T12:00:00Z',
+ cacheControl: {
+ directives: ['public', 'max-age'],
+ maxAge: 3600,
+ },
+ });
+
+ expect(response.data).toBeDefined();
+ expect(response.etag?.value).toBe('686897696a7c876b7e');
+ expect(response.cacheControl?.maxAge).toBe(3600);
+ });
+
+ it('should accept 304 Not Modified response', () => {
+ const response = MetadataCacheResponseSchema.parse({
+ notModified: true,
+ etag: {
+ value: '686897696a7c876b7e',
+ },
+ });
+
+ expect(response.notModified).toBe(true);
+ expect(response.data).toBeUndefined();
+ });
+
+ it('should accept response with version', () => {
+ const response = MetadataCacheResponseSchema.parse({
+ data: { object: 'contact' },
+ version: '2.5.0',
+ etag: {
+ value: 'version_2.5.0',
+ },
+ });
+
+ expect(response.version).toBe('2.5.0');
+ });
+
+ it('should default notModified to false', () => {
+ const response = MetadataCacheResponseSchema.parse({
+ data: {},
+ });
+
+ expect(response.notModified).toBe(false);
+ });
+});
+
+describe('CacheInvalidationTarget', () => {
+ it('should accept valid invalidation targets', () => {
+ expect(CacheInvalidationTarget.parse('all')).toBe('all');
+ expect(CacheInvalidationTarget.parse('object')).toBe('object');
+ expect(CacheInvalidationTarget.parse('field')).toBe('field');
+ expect(CacheInvalidationTarget.parse('permission')).toBe('permission');
+ expect(CacheInvalidationTarget.parse('layout')).toBe('layout');
+ expect(CacheInvalidationTarget.parse('custom')).toBe('custom');
+ });
+});
+
+describe('CacheInvalidationRequestSchema', () => {
+ it('should accept invalidate all request', () => {
+ const request = CacheInvalidationRequestSchema.parse({
+ target: 'all',
+ });
+
+ expect(request.target).toBe('all');
+ expect(request.cascade).toBe(false);
+ });
+
+ it('should accept specific object invalidation', () => {
+ const request = CacheInvalidationRequestSchema.parse({
+ target: 'object',
+ identifiers: ['account', 'contact'],
+ cascade: true,
+ });
+
+ expect(request.target).toBe('object');
+ expect(request.identifiers).toContain('account');
+ expect(request.cascade).toBe(true);
+ });
+
+ it('should accept custom pattern invalidation', () => {
+ const request = CacheInvalidationRequestSchema.parse({
+ target: 'custom',
+ pattern: 'metadata:object:*',
+ });
+
+ expect(request.pattern).toBe('metadata:object:*');
+ });
+});
+
+describe('CacheInvalidationResponseSchema', () => {
+ it('should accept successful invalidation response', () => {
+ const response = CacheInvalidationResponseSchema.parse({
+ success: true,
+ invalidated: 5,
+ targets: ['account', 'contact', 'opportunity'],
+ });
+
+ expect(response.success).toBe(true);
+ expect(response.invalidated).toBe(5);
+ expect(response.targets).toHaveLength(3);
+ });
+
+ it('should accept response without targets', () => {
+ const response = CacheInvalidationResponseSchema.parse({
+ success: true,
+ invalidated: 0,
+ });
+
+ expect(response.invalidated).toBe(0);
+ expect(response.targets).toBeUndefined();
+ });
+});
+
+describe('MetadataCacheApi', () => {
+ it('should have correct API structure', () => {
+ expect(MetadataCacheApi.getCached).toBeDefined();
+ expect(MetadataCacheApi.getCached.input).toBeDefined();
+ expect(MetadataCacheApi.getCached.output).toBeDefined();
+
+ expect(MetadataCacheApi.invalidate).toBeDefined();
+ expect(MetadataCacheApi.invalidate.input).toBeDefined();
+ expect(MetadataCacheApi.invalidate.output).toBeDefined();
+ });
+
+ it('should validate getCached contract', () => {
+ const input = {
+ ifNoneMatch: '"abc123"',
+ };
+
+ const parsedInput = MetadataCacheApi.getCached.input.parse(input);
+ expect(parsedInput.ifNoneMatch).toBe('"abc123"');
+ });
+
+ it('should validate invalidate contract', () => {
+ const input = {
+ target: 'object' as const,
+ identifiers: ['account'],
+ };
+
+ const parsedInput = MetadataCacheApi.invalidate.input.parse(input);
+ expect(parsedInput.target).toBe('object');
+ });
+});
diff --git a/packages/spec/src/api/cache.zod.ts b/packages/spec/src/api/cache.zod.ts
new file mode 100644
index 000000000..a4cfab70b
--- /dev/null
+++ b/packages/spec/src/api/cache.zod.ts
@@ -0,0 +1,230 @@
+import { z } from 'zod';
+
+/**
+ * Metadata Cache Control Protocol
+ *
+ * Implements efficient metadata caching with ETag support.
+ * Implements P0 requirement for ObjectStack kernel.
+ *
+ * Features:
+ * - ETag-based conditional requests (HTTP 304 Not Modified)
+ * - Cache-Control directives
+ * - Metadata versioning
+ * - Selective cache invalidation
+ *
+ * Industry alignment: HTTP Caching (RFC 7234), Salesforce Metadata API
+ */
+
+// ==========================================
+// Cache Control Headers
+// ==========================================
+
+/**
+ * Cache Control Directive Enum
+ * Standard HTTP cache control directives
+ */
+export const CacheDirective = z.enum([
+ 'public', // Cacheable by any cache
+ 'private', // Cacheable only by user-agent
+ 'no-cache', // Must revalidate with server
+ 'no-store', // Never cache
+ 'must-revalidate', // Must revalidate stale responses
+ 'max-age', // Maximum cache age in seconds
+]);
+
+export type CacheDirective = z.infer;
+
+/**
+ * Cache Control Schema
+ * HTTP cache control configuration
+ *
+ * @example
+ * {
+ * "directives": ["public", "max-age"],
+ * "maxAge": 3600,
+ * "staleWhileRevalidate": 86400
+ * }
+ */
+export const CacheControlSchema = z.object({
+ directives: z.array(CacheDirective).describe('Cache control directives'),
+ maxAge: z.number().optional().describe('Maximum cache age in seconds'),
+ staleWhileRevalidate: z.number().optional().describe('Allow serving stale content while revalidating (seconds)'),
+ staleIfError: z.number().optional().describe('Allow serving stale content on error (seconds)'),
+});
+
+export type CacheControl = z.infer;
+
+// ==========================================
+// ETag Support
+// ==========================================
+
+/**
+ * ETag Schema
+ * Entity tag for cache validation
+ *
+ * ETags can be:
+ * - Strong: Exact match required (e.g., "686897696a7c876b7e")
+ * - Weak: Semantic equivalence (e.g., W/"686897696a7c876b7e")
+ */
+export const ETagSchema = z.object({
+ value: z.string().describe('ETag value (hash or version identifier)'),
+ weak: z.boolean().optional().default(false).describe('Whether this is a weak ETag'),
+});
+
+export type ETag = z.infer;
+
+// ==========================================
+// Metadata Cache Request
+// ==========================================
+
+/**
+ * Metadata Cache Request Schema
+ * Request with cache validation headers
+ *
+ * @example
+ * // GET /api/v1/metadata/objects/account
+ * // Headers:
+ * // If-None-Match: "686897696a7c876b7e"
+ * // If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT
+ */
+export const MetadataCacheRequestSchema = z.object({
+ ifNoneMatch: z.string().optional().describe('ETag value for conditional request (If-None-Match header)'),
+ ifModifiedSince: z.string().datetime().optional().describe('Timestamp for conditional request (If-Modified-Since header)'),
+ cacheControl: CacheControlSchema.optional().describe('Client cache control preferences'),
+});
+
+export type MetadataCacheRequest = z.infer;
+
+// ==========================================
+// Metadata Cache Response
+// ==========================================
+
+/**
+ * Metadata Cache Response Schema
+ * Response with cache control headers
+ *
+ * @example Success Response (200 OK)
+ * {
+ * "data": { "object": "account" },
+ * "etag": {
+ * "value": "686897696a7c876b7e",
+ * "weak": false
+ * },
+ * "lastModified": "2026-01-29T12:00:00Z",
+ * "cacheControl": {
+ * "directives": ["public", "max-age"],
+ * "maxAge": 3600
+ * }
+ * }
+ *
+ * @example Not Modified Response (304 Not Modified)
+ * {
+ * "notModified": true,
+ * "etag": {
+ * "value": "686897696a7c876b7e"
+ * }
+ * }
+ */
+export const MetadataCacheResponseSchema = z.object({
+ data: z.any().optional().describe('Metadata payload (omitted for 304 Not Modified)'),
+ etag: ETagSchema.optional().describe('ETag for this resource version'),
+ lastModified: z.string().datetime().optional().describe('Last modification timestamp'),
+ cacheControl: CacheControlSchema.optional().describe('Cache control directives'),
+ notModified: z.boolean().optional().default(false).describe('True if resource has not been modified (304 response)'),
+ version: z.string().optional().describe('Metadata version identifier'),
+});
+
+export type MetadataCacheResponse = z.infer;
+
+// ==========================================
+// Metadata Cache Invalidation
+// ==========================================
+
+/**
+ * Cache Invalidation Target Enum
+ * Specifies what to invalidate
+ */
+export const CacheInvalidationTarget = z.enum([
+ 'all', // Invalidate all cached metadata
+ 'object', // Invalidate specific object metadata
+ 'field', // Invalidate specific field metadata
+ 'permission', // Invalidate permission metadata
+ 'layout', // Invalidate layout metadata
+ 'custom', // Custom invalidation pattern
+]);
+
+export type CacheInvalidationTarget = z.infer;
+
+/**
+ * Cache Invalidation Request Schema
+ * Request to invalidate cached metadata
+ *
+ * @example
+ * // POST /api/v1/metadata/cache/invalidate
+ * {
+ * "target": "object",
+ * "identifiers": ["account", "contact"],
+ * "cascade": true
+ * }
+ */
+export const CacheInvalidationRequestSchema = z.object({
+ target: CacheInvalidationTarget.describe('What to invalidate'),
+ identifiers: z.array(z.string()).optional().describe('Specific resources to invalidate (e.g., object names)'),
+ cascade: z.boolean().optional().default(false).describe('If true, invalidate dependent resources'),
+ pattern: z.string().optional().describe('Pattern for custom invalidation (supports wildcards)'),
+});
+
+export type CacheInvalidationRequest = z.infer;
+
+/**
+ * Cache Invalidation Response Schema
+ * Response for cache invalidation
+ *
+ * @example
+ * {
+ * "success": true,
+ * "invalidated": 5,
+ * "targets": ["account", "contact", "opportunity"]
+ * }
+ */
+export const CacheInvalidationResponseSchema = z.object({
+ success: z.boolean().describe('Whether invalidation succeeded'),
+ invalidated: z.number().describe('Number of cache entries invalidated'),
+ targets: z.array(z.string()).optional().describe('List of invalidated resources'),
+});
+
+export type CacheInvalidationResponse = z.infer;
+
+// ==========================================
+// Metadata Cache API Methods
+// ==========================================
+
+/**
+ * Metadata Cache API Client Interface
+ *
+ * @example Usage
+ * // Get metadata with cache support
+ * const response = await client.meta.getCached('account', {
+ * ifNoneMatch: '"686897696a7c876b7e"'
+ * });
+ *
+ * if (response.notModified) {
+ * // Use cached version
+ * } else {
+ * // Update cache with response.data
+ * cache.set('account', response.data, {
+ * etag: response.etag?.value,
+ * maxAge: response.cacheControl?.maxAge
+ * });
+ * }
+ */
+export const MetadataCacheApi = {
+ getCached: {
+ input: MetadataCacheRequestSchema,
+ output: MetadataCacheResponseSchema,
+ },
+ invalidate: {
+ input: CacheInvalidationRequestSchema,
+ output: CacheInvalidationResponseSchema,
+ },
+};
diff --git a/packages/spec/src/api/errors.test.ts b/packages/spec/src/api/errors.test.ts
new file mode 100644
index 000000000..c1237b3a2
--- /dev/null
+++ b/packages/spec/src/api/errors.test.ts
@@ -0,0 +1,278 @@
+import { describe, it, expect } from 'vitest';
+import {
+ ErrorCategory,
+ StandardErrorCode,
+ RetryStrategy,
+ FieldErrorSchema,
+ EnhancedApiErrorSchema,
+ ErrorResponseSchema,
+ getHttpStatusForCategory,
+ createErrorResponse,
+} from './errors.zod';
+
+describe('ErrorCategory', () => {
+ it('should accept valid error categories', () => {
+ expect(ErrorCategory.parse('validation')).toBe('validation');
+ expect(ErrorCategory.parse('authentication')).toBe('authentication');
+ expect(ErrorCategory.parse('authorization')).toBe('authorization');
+ expect(ErrorCategory.parse('not_found')).toBe('not_found');
+ expect(ErrorCategory.parse('conflict')).toBe('conflict');
+ expect(ErrorCategory.parse('rate_limit')).toBe('rate_limit');
+ expect(ErrorCategory.parse('server')).toBe('server');
+ });
+});
+
+describe('StandardErrorCode', () => {
+ it('should accept validation error codes', () => {
+ expect(StandardErrorCode.parse('validation_error')).toBe('validation_error');
+ expect(StandardErrorCode.parse('invalid_field')).toBe('invalid_field');
+ expect(StandardErrorCode.parse('missing_required_field')).toBe('missing_required_field');
+ });
+
+ it('should accept authentication error codes', () => {
+ expect(StandardErrorCode.parse('unauthenticated')).toBe('unauthenticated');
+ expect(StandardErrorCode.parse('invalid_credentials')).toBe('invalid_credentials');
+ expect(StandardErrorCode.parse('expired_token')).toBe('expired_token');
+ });
+
+ it('should accept authorization error codes', () => {
+ expect(StandardErrorCode.parse('permission_denied')).toBe('permission_denied');
+ expect(StandardErrorCode.parse('insufficient_privileges')).toBe('insufficient_privileges');
+ });
+
+ it('should accept batch operation error codes', () => {
+ expect(StandardErrorCode.parse('batch_partial_failure')).toBe('batch_partial_failure');
+ expect(StandardErrorCode.parse('transaction_failed')).toBe('transaction_failed');
+ });
+});
+
+describe('RetryStrategy', () => {
+ it('should accept valid retry strategies', () => {
+ expect(RetryStrategy.parse('no_retry')).toBe('no_retry');
+ expect(RetryStrategy.parse('retry_immediate')).toBe('retry_immediate');
+ expect(RetryStrategy.parse('retry_backoff')).toBe('retry_backoff');
+ expect(RetryStrategy.parse('retry_after')).toBe('retry_after');
+ });
+});
+
+describe('FieldErrorSchema', () => {
+ it('should accept basic field error', () => {
+ const error = FieldErrorSchema.parse({
+ field: 'email',
+ code: 'invalid_format',
+ message: 'Email format is invalid',
+ });
+
+ expect(error.field).toBe('email');
+ expect(error.code).toBe('invalid_format');
+ });
+
+ it('should accept field error with value and constraint', () => {
+ const error = FieldErrorSchema.parse({
+ field: 'age',
+ code: 'value_out_of_range',
+ message: 'Age must be between 0 and 120',
+ value: 150,
+ constraint: { min: 0, max: 120 },
+ });
+
+ expect(error.value).toBe(150);
+ expect(error.constraint).toEqual({ min: 0, max: 120 });
+ });
+
+ it('should support nested field paths', () => {
+ const error = FieldErrorSchema.parse({
+ field: 'user.profile.email',
+ code: 'invalid_format',
+ message: 'Invalid email',
+ });
+
+ expect(error.field).toBe('user.profile.email');
+ });
+});
+
+describe('EnhancedApiErrorSchema', () => {
+ it('should accept minimal error', () => {
+ const error = EnhancedApiErrorSchema.parse({
+ code: 'validation_error',
+ message: 'Validation failed',
+ });
+
+ expect(error.code).toBe('validation_error');
+ expect(error.message).toBe('Validation failed');
+ expect(error.retryable).toBe(false);
+ });
+
+ it('should accept complete error with all fields', () => {
+ const error = EnhancedApiErrorSchema.parse({
+ code: 'validation_error',
+ message: 'Validation failed for 2 fields',
+ category: 'validation',
+ httpStatus: 400,
+ retryable: false,
+ retryStrategy: 'no_retry',
+ details: { count: 2 },
+ fieldErrors: [
+ {
+ field: 'email',
+ code: 'invalid_format',
+ message: 'Invalid email format',
+ },
+ ],
+ timestamp: '2026-01-29T12:00:00Z',
+ requestId: 'req_123',
+ traceId: 'trace_456',
+ documentation: 'https://docs.objectstack.dev/errors/validation_error',
+ helpText: 'Please check the field values',
+ });
+
+ expect(error.category).toBe('validation');
+ expect(error.httpStatus).toBe(400);
+ expect(error.fieldErrors).toHaveLength(1);
+ expect(error.documentation).toContain('objectstack.dev');
+ });
+
+ it('should accept rate limit error with retry info', () => {
+ const error = EnhancedApiErrorSchema.parse({
+ code: 'rate_limit_exceeded',
+ message: 'Rate limit exceeded',
+ category: 'rate_limit',
+ httpStatus: 429,
+ retryable: true,
+ retryStrategy: 'retry_after',
+ retryAfter: 60,
+ details: {
+ limit: 1000,
+ remaining: 0,
+ resetAt: '2026-01-29T13:00:00Z',
+ },
+ });
+
+ expect(error.retryable).toBe(true);
+ expect(error.retryAfter).toBe(60);
+ expect(error.details.limit).toBe(1000);
+ });
+
+ it('should accept authorization error', () => {
+ const error = EnhancedApiErrorSchema.parse({
+ code: 'permission_denied',
+ message: 'You do not have permission to perform this action',
+ category: 'authorization',
+ httpStatus: 403,
+ retryable: false,
+ });
+
+ expect(error.category).toBe('authorization');
+ expect(error.httpStatus).toBe(403);
+ });
+});
+
+describe('ErrorResponseSchema', () => {
+ it('should accept error response', () => {
+ const response = ErrorResponseSchema.parse({
+ success: false,
+ error: {
+ code: 'resource_not_found',
+ message: 'Resource not found',
+ },
+ });
+
+ expect(response.success).toBe(false);
+ expect(response.error.code).toBe('resource_not_found');
+ });
+
+ it('should accept error response with metadata', () => {
+ const response = ErrorResponseSchema.parse({
+ success: false,
+ error: {
+ code: 'internal_error',
+ message: 'Internal server error',
+ },
+ meta: {
+ timestamp: '2026-01-29T12:00:00Z',
+ requestId: 'req_123',
+ },
+ });
+
+ expect(response.meta?.requestId).toBe('req_123');
+ });
+
+ it('should only accept success=false', () => {
+ expect(() =>
+ ErrorResponseSchema.parse({
+ success: true,
+ error: {
+ code: 'validation_error',
+ message: 'Error',
+ },
+ })
+ ).toThrow();
+ });
+});
+
+describe('getHttpStatusForCategory', () => {
+ it('should return correct HTTP status for each category', () => {
+ expect(getHttpStatusForCategory('validation')).toBe(400);
+ expect(getHttpStatusForCategory('authentication')).toBe(401);
+ expect(getHttpStatusForCategory('authorization')).toBe(403);
+ expect(getHttpStatusForCategory('not_found')).toBe(404);
+ expect(getHttpStatusForCategory('conflict')).toBe(409);
+ expect(getHttpStatusForCategory('rate_limit')).toBe(429);
+ expect(getHttpStatusForCategory('server')).toBe(500);
+ expect(getHttpStatusForCategory('external')).toBe(502);
+ expect(getHttpStatusForCategory('maintenance')).toBe(503);
+ });
+});
+
+describe('createErrorResponse', () => {
+ it('should create basic error response', () => {
+ const response = createErrorResponse(
+ 'validation_error',
+ 'Validation failed'
+ );
+
+ expect(response.success).toBe(false);
+ expect(response.error.code).toBe('validation_error');
+ expect(response.error.message).toBe('Validation failed');
+ expect(response.error.category).toBe('validation');
+ expect(response.error.httpStatus).toBe(400);
+ });
+
+ it('should create error response with options', () => {
+ const response = createErrorResponse(
+ 'permission_denied',
+ 'Access denied',
+ {
+ retryable: false,
+ documentation: 'https://docs.example.com',
+ details: { requiredPermission: 'admin' },
+ }
+ );
+
+ expect(response.error.category).toBe('authorization');
+ expect(response.error.httpStatus).toBe(403);
+ expect(response.error.retryable).toBe(false);
+ expect(response.error.documentation).toBe('https://docs.example.com');
+ });
+
+ it('should infer correct category from code', () => {
+ const validationError = createErrorResponse('invalid_field', 'Invalid field');
+ expect(validationError.error.category).toBe('validation');
+
+ const authError = createErrorResponse('expired_token', 'Token expired');
+ expect(authError.error.category).toBe('authentication');
+
+ const authzError = createErrorResponse('insufficient_privileges', 'Insufficient privileges');
+ expect(authzError.error.category).toBe('authorization');
+
+ const notFoundError = createErrorResponse('record_not_found', 'Record not found');
+ expect(notFoundError.error.category).toBe('not_found');
+ });
+
+ it('should include timestamp', () => {
+ const response = createErrorResponse('internal_error', 'Server error');
+
+ expect(response.error.timestamp).toBeDefined();
+ expect(new Date(response.error.timestamp!).getTime()).toBeGreaterThan(0);
+ });
+});
diff --git a/packages/spec/src/api/errors.zod.ts b/packages/spec/src/api/errors.zod.ts
new file mode 100644
index 000000000..b7c1bc72b
--- /dev/null
+++ b/packages/spec/src/api/errors.zod.ts
@@ -0,0 +1,332 @@
+import { z } from 'zod';
+
+/**
+ * Standardized Error Codes Protocol
+ *
+ * Implements P0 requirement for ObjectStack kernel.
+ * Provides consistent, machine-readable error codes across the platform.
+ *
+ * Features:
+ * - Categorized error codes (validation, authentication, authorization, etc.)
+ * - HTTP status code mapping
+ * - Localization support
+ * - Retry guidance
+ *
+ * Industry alignment: Google Cloud Errors, AWS Error Codes, Stripe API Errors
+ */
+
+// ==========================================
+// Error Code Categories
+// ==========================================
+
+/**
+ * Error Category Enum
+ * High-level categorization of errors
+ */
+export const ErrorCategory = z.enum([
+ 'validation', // Input validation errors (400)
+ 'authentication', // Authentication failures (401)
+ 'authorization', // Permission denied errors (403)
+ 'not_found', // Resource not found (404)
+ 'conflict', // Resource conflict (409)
+ 'rate_limit', // Rate limiting (429)
+ 'server', // Internal server errors (500)
+ 'external', // External service errors (502/503)
+ 'maintenance', // Planned maintenance (503)
+]);
+
+export type ErrorCategory = z.infer;
+
+// ==========================================
+// Standard Error Codes
+// ==========================================
+
+/**
+ * Standard Error Code Enum
+ * Machine-readable error codes for common error scenarios
+ */
+export const StandardErrorCode = z.enum([
+ // Validation Errors (400)
+ 'validation_error', // Generic validation failure
+ 'invalid_field', // Invalid field value
+ 'missing_required_field', // Required field missing
+ 'invalid_format', // Field format invalid (e.g., email, date)
+ 'value_too_long', // Field value exceeds max length
+ 'value_too_short', // Field value below min length
+ 'value_out_of_range', // Numeric value out of range
+ 'invalid_reference', // Invalid foreign key reference
+ 'duplicate_value', // Unique constraint violation
+ 'invalid_query', // Malformed query syntax
+ 'invalid_filter', // Invalid filter expression
+ 'invalid_sort', // Invalid sort specification
+ 'max_records_exceeded', // Query would return too many records
+
+ // Authentication Errors (401)
+ 'unauthenticated', // No valid authentication provided
+ 'invalid_credentials', // Wrong username/password
+ 'expired_token', // Authentication token expired
+ 'invalid_token', // Authentication token invalid
+ 'session_expired', // User session expired
+ 'mfa_required', // Multi-factor authentication required
+ 'email_not_verified', // Email verification required
+
+ // Authorization Errors (403)
+ 'permission_denied', // User lacks required permission
+ 'insufficient_privileges', // Operation requires higher privileges
+ 'field_not_accessible', // Field-level security restriction
+ 'record_not_accessible', // Sharing rule restriction
+ 'license_required', // Feature requires license
+ 'ip_restricted', // IP address not allowed
+ 'time_restricted', // Access outside allowed time window
+
+ // Not Found Errors (404)
+ 'resource_not_found', // Generic resource not found
+ 'object_not_found', // Object/table not found
+ 'record_not_found', // Record with given ID not found
+ 'field_not_found', // Field not found in object
+ 'endpoint_not_found', // API endpoint not found
+
+ // Conflict Errors (409)
+ 'resource_conflict', // Generic resource conflict
+ 'concurrent_modification', // Record modified by another user
+ 'delete_restricted', // Cannot delete due to dependencies
+ 'duplicate_record', // Record already exists
+ 'lock_conflict', // Record is locked by another process
+
+ // Rate Limiting (429)
+ 'rate_limit_exceeded', // Too many requests
+ 'quota_exceeded', // API quota exceeded
+ 'concurrent_limit_exceeded', // Too many concurrent requests
+
+ // Server Errors (500)
+ 'internal_error', // Generic internal server error
+ 'database_error', // Database operation failed
+ 'timeout', // Operation timed out
+ 'service_unavailable', // Service temporarily unavailable
+ 'not_implemented', // Feature not yet implemented
+
+ // External Service Errors (502/503)
+ 'external_service_error', // External API call failed
+ 'integration_error', // Integration service error
+ 'webhook_delivery_failed', // Webhook delivery failed
+
+ // Batch Operation Errors
+ 'batch_partial_failure', // Batch operation partially succeeded
+ 'batch_complete_failure', // Batch operation completely failed
+ 'transaction_failed', // Transaction rolled back
+]);
+
+export type StandardErrorCode = z.infer;
+
+// ==========================================
+// Enhanced Error Schema
+// ==========================================
+
+/**
+ * HTTP Status Code mapping for error categories
+ */
+export const ErrorHttpStatusMap: Record = {
+ validation: 400,
+ authentication: 401,
+ authorization: 403,
+ not_found: 404,
+ conflict: 409,
+ rate_limit: 429,
+ server: 500,
+ external: 502,
+ maintenance: 503,
+};
+
+/**
+ * Retry Strategy Enum
+ * Guidance on whether to retry failed requests
+ */
+export const RetryStrategy = z.enum([
+ 'no_retry', // Do not retry (permanent failure)
+ 'retry_immediate', // Retry immediately
+ 'retry_backoff', // Retry with exponential backoff
+ 'retry_after', // Retry after specified delay
+]);
+
+export type RetryStrategy = z.infer;
+
+/**
+ * Field Error Schema
+ * Detailed error for a specific field
+ */
+export const FieldErrorSchema = z.object({
+ field: z.string().describe('Field path (supports dot notation)'),
+ code: StandardErrorCode.describe('Error code for this field'),
+ message: z.string().describe('Human-readable error message'),
+ value: z.any().optional().describe('The invalid value that was provided'),
+ constraint: z.any().optional().describe('The constraint that was violated (e.g., max length)'),
+});
+
+export type FieldError = z.infer;
+
+/**
+ * Enhanced API Error Schema
+ * Standardized error response with detailed metadata
+ *
+ * @example Validation Error
+ * {
+ * "code": "validation_error",
+ * "message": "Validation failed for 2 fields",
+ * "category": "validation",
+ * "httpStatus": 400,
+ * "retryable": false,
+ * "retryStrategy": "no_retry",
+ * "details": {
+ * "fieldErrors": [
+ * {
+ * "field": "email",
+ * "code": "invalid_format",
+ * "message": "Email format is invalid",
+ * "value": "not-an-email"
+ * },
+ * {
+ * "field": "age",
+ * "code": "value_out_of_range",
+ * "message": "Age must be between 0 and 120",
+ * "value": 150,
+ * "constraint": { "min": 0, "max": 120 }
+ * }
+ * ]
+ * },
+ * "timestamp": "2026-01-29T12:00:00Z",
+ * "requestId": "req_123456",
+ * "documentation": "https://docs.objectstack.dev/errors/validation_error"
+ * }
+ *
+ * @example Rate Limit Error
+ * {
+ * "code": "rate_limit_exceeded",
+ * "message": "Rate limit exceeded. Try again in 60 seconds.",
+ * "category": "rate_limit",
+ * "httpStatus": 429,
+ * "retryable": true,
+ * "retryStrategy": "retry_after",
+ * "retryAfter": 60,
+ * "details": {
+ * "limit": 1000,
+ * "remaining": 0,
+ * "resetAt": "2026-01-29T13:00:00Z"
+ * }
+ * }
+ */
+export const EnhancedApiErrorSchema = z.object({
+ code: StandardErrorCode.describe('Machine-readable error code'),
+ message: z.string().describe('Human-readable error message'),
+ category: ErrorCategory.optional().describe('Error category'),
+ httpStatus: z.number().optional().describe('HTTP status code'),
+ retryable: z.boolean().default(false).describe('Whether the request can be retried'),
+ retryStrategy: RetryStrategy.optional().describe('Recommended retry strategy'),
+ retryAfter: z.number().optional().describe('Seconds to wait before retrying'),
+ details: z.any().optional().describe('Additional error context'),
+ fieldErrors: z.array(FieldErrorSchema).optional().describe('Field-specific validation errors'),
+ timestamp: z.string().datetime().optional().describe('When the error occurred'),
+ requestId: z.string().optional().describe('Request ID for tracking'),
+ traceId: z.string().optional().describe('Distributed trace ID'),
+ documentation: z.string().url().optional().describe('URL to error documentation'),
+ helpText: z.string().optional().describe('Suggested actions to resolve the error'),
+});
+
+export type EnhancedApiError = z.infer;
+
+// ==========================================
+// Error Response Schema
+// ==========================================
+
+/**
+ * Standardized Error Response Schema
+ * Complete error response envelope
+ *
+ * @example
+ * {
+ * "success": false,
+ * "error": {
+ * "code": "permission_denied",
+ * "message": "You do not have permission to update this record",
+ * "category": "authorization",
+ * "httpStatus": 403,
+ * "retryable": false
+ * }
+ * }
+ */
+export const ErrorResponseSchema = z.object({
+ success: z.literal(false).describe('Always false for error responses'),
+ error: EnhancedApiErrorSchema.describe('Error details'),
+ meta: z.object({
+ timestamp: z.string().datetime().optional(),
+ requestId: z.string().optional(),
+ traceId: z.string().optional(),
+ }).optional().describe('Response metadata'),
+});
+
+export type ErrorResponse = z.infer;
+
+// ==========================================
+// Helper Functions
+// ==========================================
+
+/**
+ * Get HTTP status code for an error category
+ */
+export function getHttpStatusForCategory(category: ErrorCategory): number {
+ return ErrorHttpStatusMap[category] || 500;
+}
+
+/**
+ * Create a standardized error response
+ */
+export function createErrorResponse(
+ code: StandardErrorCode,
+ message: string,
+ options?: Partial
+): ErrorResponse {
+ const category = getCategoryForCode(code);
+
+ return {
+ success: false,
+ error: {
+ code,
+ message,
+ category,
+ httpStatus: getHttpStatusForCategory(category),
+ timestamp: new Date().toISOString(),
+ retryable: false,
+ ...options,
+ },
+ };
+}
+
+/**
+ * Infer error category from error code
+ */
+function getCategoryForCode(code: StandardErrorCode): ErrorCategory {
+ if (code.includes('validation') || code.includes('invalid') || code.includes('missing') || code.includes('duplicate')) {
+ return 'validation';
+ }
+ if (code.includes('unauthenticated') || code.includes('token') || code.includes('credentials') || code.includes('session')) {
+ return 'authentication';
+ }
+ if (code.includes('permission') || code.includes('privileges') || code.includes('accessible') || code.includes('restricted')) {
+ return 'authorization';
+ }
+ if (code.includes('not_found')) {
+ return 'not_found';
+ }
+ if (code.includes('conflict') || code.includes('concurrent') || code.includes('lock')) {
+ return 'conflict';
+ }
+ if (code.includes('rate_limit') || code.includes('quota')) {
+ return 'rate_limit';
+ }
+ if (code.includes('external') || code.includes('integration') || code.includes('webhook')) {
+ return 'external';
+ }
+ if (code.includes('maintenance')) {
+ return 'maintenance';
+ }
+ return 'server';
+}
diff --git a/packages/spec/src/api/index.ts b/packages/spec/src/api/index.ts
index 7fda26d57..ef7efb07d 100644
--- a/packages/spec/src/api/index.ts
+++ b/packages/spec/src/api/index.ts
@@ -5,6 +5,9 @@
* - Request/Response schemas
* - Error handling
* - OData v4 compatibility
+ * - Batch operations
+ * - Metadata caching
+ * - View storage
*/
export * from './contract.zod';
@@ -15,6 +18,10 @@ export * from './websocket.zod';
export * from './router.zod';
export * from './odata.zod';
export * from './graphql.zod';
+export * from './batch.zod';
+export * from './cache.zod';
+export * from './errors.zod';
+export * from './view-storage.zod';
export * from './protocol';
diff --git a/packages/spec/src/api/view-storage.zod.ts b/packages/spec/src/api/view-storage.zod.ts
new file mode 100644
index 000000000..e1aa435bb
--- /dev/null
+++ b/packages/spec/src/api/view-storage.zod.ts
@@ -0,0 +1,286 @@
+import { z } from 'zod';
+import { QuerySchema } from '../data/query.zod';
+
+/**
+ * View Definition Storage Protocol
+ *
+ * Implements P2 requirement for ObjectStack kernel.
+ * Allows persisting UI view configurations (list views, filters, layouts).
+ *
+ * Features:
+ * - Save custom views with filters and columns
+ * - Share views with users/teams
+ * - Set default views per user/profile
+ * - View templates and presets
+ *
+ * Industry alignment: Salesforce List Views, Microsoft Dynamics Saved Views
+ */
+
+// ==========================================
+// View Configuration Types
+// ==========================================
+
+/**
+ * View Type Enum
+ * Types of views that can be stored
+ */
+export const ViewType = z.enum([
+ 'list', // List/table view
+ 'kanban', // Kanban board view
+ 'calendar', // Calendar view
+ 'gantt', // Gantt chart view
+ 'timeline', // Timeline view
+ 'chart', // Chart/graph view
+ 'pivot', // Pivot table view
+ 'custom', // Custom view type
+]);
+
+export type ViewType = z.infer;
+
+/**
+ * View Visibility Enum
+ * Who can see and use this view
+ */
+export const ViewVisibility = z.enum([
+ 'private', // Only the creator can see it
+ 'shared', // Shared with specific users/teams
+ 'public', // All users can see it
+ 'organization', // All org users can see it
+]);
+
+export type ViewVisibility = z.infer;
+
+/**
+ * Column Configuration Schema
+ * Defines which columns to display and how
+ */
+export const ViewColumnSchema = z.object({
+ field: z.string().describe('Field name'),
+ label: z.string().optional().describe('Custom column label'),
+ width: z.number().optional().describe('Column width in pixels'),
+ sortable: z.boolean().optional().default(true).describe('Whether column is sortable'),
+ filterable: z.boolean().optional().default(true).describe('Whether column is filterable'),
+ visible: z.boolean().optional().default(true).describe('Whether column is visible'),
+ pinned: z.enum(['left', 'right']).optional().describe('Pin column to left or right'),
+ formatter: z.string().optional().describe('Custom formatter name'),
+ aggregation: z.string().optional().describe('Aggregation function for column (sum, avg, etc.)'),
+});
+
+export type ViewColumn = z.infer;
+
+/**
+ * View Layout Configuration
+ * Layout-specific settings for different view types
+ */
+export const ViewLayoutSchema = z.object({
+ // List view settings
+ columns: z.array(ViewColumnSchema).optional().describe('Column configuration for list views'),
+ rowHeight: z.number().optional().describe('Row height in pixels'),
+
+ // Kanban view settings
+ groupByField: z.string().optional().describe('Field to group by (for kanban)'),
+ cardFields: z.array(z.string()).optional().describe('Fields to display on cards'),
+
+ // Calendar view settings
+ dateField: z.string().optional().describe('Date field for calendar view'),
+ startDateField: z.string().optional().describe('Start date field for event ranges'),
+ endDateField: z.string().optional().describe('End date field for event ranges'),
+ titleField: z.string().optional().describe('Field to use as event title'),
+
+ // Chart view settings
+ chartType: z.enum(['bar', 'line', 'pie', 'scatter', 'area']).optional().describe('Chart type'),
+ xAxis: z.string().optional().describe('X-axis field'),
+ yAxis: z.string().optional().describe('Y-axis field'),
+ series: z.array(z.string()).optional().describe('Series fields for multi-series charts'),
+});
+
+export type ViewLayout = z.infer;
+
+// ==========================================
+// View Definition Schema
+// ==========================================
+
+/**
+ * Saved View Schema
+ * Complete view configuration that can be persisted
+ *
+ * @example
+ * {
+ * "id": "view_123",
+ * "name": "active_contacts",
+ * "label": "Active Contacts",
+ * "object": "contact",
+ * "type": "list",
+ * "visibility": "public",
+ * "query": {
+ * "object": "contact",
+ * "where": { "status": "active" },
+ * "orderBy": [{ "field": "last_name", "order": "asc" }],
+ * "limit": 50
+ * },
+ * "layout": {
+ * "columns": [
+ * { "field": "first_name", "label": "First Name", "width": 150 },
+ * { "field": "last_name", "label": "Last Name", "width": 150 },
+ * { "field": "email", "label": "Email", "width": 200 }
+ * ]
+ * },
+ * "isDefault": false,
+ * "createdBy": "user_456",
+ * "createdAt": "2026-01-29T12:00:00Z"
+ * }
+ */
+export const SavedViewSchema = z.object({
+ id: z.string().describe('Unique view identifier'),
+ name: z.string().regex(/^[a-z_][a-z0-9_]*$/).describe('View machine name (snake_case)'),
+ label: z.string().describe('Display label'),
+ description: z.string().optional().describe('View description'),
+
+ // View configuration
+ object: z.string().describe('Object/table this view is for'),
+ type: ViewType.describe('View type'),
+ visibility: ViewVisibility.describe('Who can access this view'),
+
+ // Query configuration
+ query: QuerySchema.describe('Query configuration (filters, sorting, etc.)'),
+
+ // Layout configuration
+ layout: ViewLayoutSchema.optional().describe('Layout configuration'),
+
+ // Sharing
+ sharedWith: z.array(z.string()).optional().describe('User/team IDs this view is shared with'),
+
+ // Defaults
+ isDefault: z.boolean().optional().default(false).describe('Is this the default view for this object?'),
+ isSystem: z.boolean().optional().default(false).describe('Is this a system-defined view?'),
+
+ // Metadata
+ createdBy: z.string().describe('User ID who created this view'),
+ createdAt: z.string().datetime().describe('When the view was created'),
+ updatedBy: z.string().optional().describe('User ID who last updated this view'),
+ updatedAt: z.string().datetime().optional().describe('When the view was last updated'),
+
+ // Settings
+ settings: z.record(z.any()).optional().describe('Additional view-specific settings'),
+});
+
+export type SavedView = z.infer;
+
+// ==========================================
+// View CRUD Operations
+// ==========================================
+
+/**
+ * Create View Request Schema
+ */
+export const CreateViewRequestSchema = z.object({
+ name: z.string().regex(/^[a-z_][a-z0-9_]*$/).describe('View machine name'),
+ label: z.string().describe('Display label'),
+ description: z.string().optional(),
+ object: z.string().describe('Object name'),
+ type: ViewType.describe('View type'),
+ visibility: ViewVisibility.describe('View visibility'),
+ query: QuerySchema.describe('Query configuration'),
+ layout: ViewLayoutSchema.optional().describe('Layout configuration'),
+ sharedWith: z.array(z.string()).optional().describe('Users/teams to share with'),
+ isDefault: z.boolean().optional().default(false).describe('Set as default view'),
+ settings: z.record(z.any()).optional(),
+});
+
+export type CreateViewRequest = z.infer;
+
+/**
+ * Update View Request Schema
+ */
+export const UpdateViewRequestSchema = CreateViewRequestSchema.partial().extend({
+ id: z.string().describe('View ID to update'),
+});
+
+export type UpdateViewRequest = z.infer;
+
+/**
+ * List Views Request Schema
+ */
+export const ListViewsRequestSchema = z.object({
+ object: z.string().optional().describe('Filter by object name'),
+ type: ViewType.optional().describe('Filter by view type'),
+ visibility: ViewVisibility.optional().describe('Filter by visibility'),
+ createdBy: z.string().optional().describe('Filter by creator user ID'),
+ isDefault: z.boolean().optional().describe('Filter for default views'),
+ limit: z.number().optional().default(50).describe('Max results'),
+ offset: z.number().optional().default(0).describe('Offset for pagination'),
+});
+
+export type ListViewsRequest = z.infer;
+
+/**
+ * View Response Schema
+ */
+export const ViewResponseSchema = z.object({
+ success: z.boolean(),
+ data: SavedViewSchema.optional().describe('The saved view'),
+ error: z.object({
+ code: z.string(),
+ message: z.string(),
+ }).optional(),
+});
+
+export type ViewResponse = z.infer;
+
+/**
+ * List Views Response Schema
+ */
+export const ListViewsResponseSchema = z.object({
+ success: z.boolean(),
+ data: z.array(SavedViewSchema).describe('Array of saved views'),
+ pagination: z.object({
+ total: z.number(),
+ limit: z.number(),
+ offset: z.number(),
+ hasMore: z.boolean(),
+ }),
+ error: z.object({
+ code: z.string(),
+ message: z.string(),
+ }).optional(),
+});
+
+export type ListViewsResponse = z.infer;
+
+// ==========================================
+// View Storage API
+// ==========================================
+
+/**
+ * View Storage API Contracts
+ */
+export const ViewStorageApiContracts = {
+ createView: {
+ input: CreateViewRequestSchema,
+ output: ViewResponseSchema,
+ },
+ updateView: {
+ input: UpdateViewRequestSchema,
+ output: ViewResponseSchema,
+ },
+ getView: {
+ input: z.object({ id: z.string() }),
+ output: ViewResponseSchema,
+ },
+ listViews: {
+ input: ListViewsRequestSchema,
+ output: ListViewsResponseSchema,
+ },
+ deleteView: {
+ input: z.object({ id: z.string() }),
+ output: z.object({ success: z.boolean() }),
+ },
+ setDefaultView: {
+ input: z.object({
+ viewId: z.string(),
+ object: z.string(),
+ userId: z.string().optional().describe('User to set default for (defaults to current user)'),
+ }),
+ output: ViewResponseSchema,
+ },
+};
diff --git a/packages/spec/src/data/query.test.ts b/packages/spec/src/data/query.test.ts
index e67cc0bd3..7f8bb3997 100644
--- a/packages/spec/src/data/query.test.ts
+++ b/packages/spec/src/data/query.test.ts
@@ -1931,3 +1931,180 @@ describe('QuerySchema - Type Coercion Edge Cases', () => {
expect(() => QuerySchema.parse(query)).not.toThrow();
});
});
+
+describe('QuerySchema - Full-Text Search', () => {
+ it('should accept basic full-text search query', () => {
+ const query: QueryAST = {
+ object: 'article',
+ search: {
+ query: 'machine learning',
+ },
+ };
+
+ expect(() => QuerySchema.parse(query)).not.toThrow();
+ });
+
+ it('should accept full-text search with field specification', () => {
+ const query: QueryAST = {
+ object: 'article',
+ search: {
+ query: 'machine learning',
+ fields: ['title', 'content', 'abstract'],
+ },
+ };
+
+ expect(() => QuerySchema.parse(query)).not.toThrow();
+ });
+
+ it('should accept full-text search with fuzzy matching', () => {
+ const query: QueryAST = {
+ object: 'product',
+ search: {
+ query: 'loptop', // typo: laptop
+ fuzzy: true,
+ },
+ };
+
+ expect(() => QuerySchema.parse(query)).not.toThrow();
+ });
+
+ it('should accept full-text search with field boosting', () => {
+ const query: QueryAST = {
+ object: 'article',
+ search: {
+ query: 'artificial intelligence',
+ fields: ['title', 'content'],
+ boost: {
+ title: 2.0,
+ content: 1.0,
+ },
+ },
+ };
+
+ expect(() => QuerySchema.parse(query)).not.toThrow();
+ });
+
+ it('should accept full-text search with AND operator', () => {
+ const query: QueryAST = {
+ object: 'document',
+ search: {
+ query: 'contract agreement',
+ operator: 'and',
+ },
+ };
+
+ const result = QuerySchema.parse(query);
+ expect(result.search?.operator).toBe('and');
+ });
+
+ it('should default to OR operator', () => {
+ const query: QueryAST = {
+ object: 'document',
+ search: {
+ query: 'contract agreement',
+ },
+ };
+
+ const result = QuerySchema.parse(query);
+ expect(result.search?.operator).toBe('or');
+ });
+
+ it('should accept full-text search with minimum score', () => {
+ const query: QueryAST = {
+ object: 'article',
+ search: {
+ query: 'technology',
+ minScore: 0.5,
+ },
+ };
+
+ expect(() => QuerySchema.parse(query)).not.toThrow();
+ });
+
+ it('should accept full-text search with language', () => {
+ const query: QueryAST = {
+ object: 'article',
+ search: {
+ query: '机器学习',
+ language: 'zh',
+ },
+ };
+
+ expect(() => QuerySchema.parse(query)).not.toThrow();
+ });
+
+ it('should accept full-text search with highlighting', () => {
+ const query: QueryAST = {
+ object: 'article',
+ search: {
+ query: 'artificial intelligence',
+ highlight: true,
+ },
+ };
+
+ expect(() => QuerySchema.parse(query)).not.toThrow();
+ });
+
+ it('should accept full-text search combined with filters', () => {
+ const query: QueryAST = {
+ object: 'article',
+ search: {
+ query: 'machine learning',
+ fields: ['title', 'content'],
+ },
+ where: { published: true },
+ orderBy: [{ field: 'created_at', order: 'desc' }],
+ limit: 10,
+ };
+
+ expect(() => QuerySchema.parse(query)).not.toThrow();
+ });
+
+ it('should accept full-text search with all options', () => {
+ const query: QueryAST = {
+ object: 'research_paper',
+ search: {
+ query: 'neural networks deep learning',
+ fields: ['title', 'abstract', 'content'],
+ fuzzy: true,
+ operator: 'and',
+ boost: {
+ title: 3.0,
+ abstract: 2.0,
+ content: 1.0,
+ },
+ minScore: 0.7,
+ language: 'en',
+ highlight: true,
+ },
+ where: { status: 'published' },
+ limit: 20,
+ };
+
+ expect(() => QuerySchema.parse(query)).not.toThrow();
+ });
+
+ it('should default fuzzy to false', () => {
+ const query: QueryAST = {
+ object: 'article',
+ search: {
+ query: 'test',
+ },
+ };
+
+ const result = QuerySchema.parse(query);
+ expect(result.search?.fuzzy).toBe(false);
+ });
+
+ it('should default highlight to false', () => {
+ const query: QueryAST = {
+ object: 'article',
+ search: {
+ query: 'test',
+ },
+ };
+
+ const result = QuerySchema.parse(query);
+ expect(result.search?.highlight).toBe(false);
+ });
+});
diff --git a/packages/spec/src/data/query.zod.ts b/packages/spec/src/data/query.zod.ts
index 53dedf9fc..43217f62c 100644
--- a/packages/spec/src/data/query.zod.ts
+++ b/packages/spec/src/data/query.zod.ts
@@ -395,6 +395,37 @@ export const FieldNodeSchema: z.ZodType = z.lazy(() =>
])
);
+/**
+ * Full-Text Search Configuration
+ * Defines full-text search parameters for text queries.
+ *
+ * Supports:
+ * - Multi-field search
+ * - Relevance scoring
+ * - Fuzzy matching
+ * - Language-specific analyzers
+ *
+ * @example
+ * {
+ * query: "John Smith",
+ * fields: ["name", "email", "description"],
+ * fuzzy: true,
+ * boost: { "name": 2.0, "email": 1.5 }
+ * }
+ */
+export const FullTextSearchSchema = z.object({
+ query: z.string().describe('Search query text'),
+ fields: z.array(z.string()).optional().describe('Fields to search in (if not specified, searches all text fields)'),
+ fuzzy: z.boolean().optional().default(false).describe('Enable fuzzy matching (tolerates typos)'),
+ operator: z.enum(['and', 'or']).optional().default('or').describe('Logical operator between terms'),
+ boost: z.record(z.number()).optional().describe('Field-specific relevance boosting (field name -> boost factor)'),
+ minScore: z.number().optional().describe('Minimum relevance score threshold'),
+ language: z.string().optional().describe('Language for text analysis (e.g., "en", "zh", "es")'),
+ highlight: z.boolean().optional().default(false).describe('Enable search result highlighting'),
+});
+
+export type FullTextSearch = z.infer;
+
/**
* Query AST Schema
* The universal data retrieval contract defined in `ast-structure.mdx`.
@@ -408,6 +439,9 @@ export const FieldNodeSchema: z.ZodType = z.lazy(() =>
* - Renamed `top`/`skip` to `limit`/`offset`
* - Unified filtering syntax with `FilterConditionSchema`
*
+ * Updates (v3):
+ * - Added `search` parameter for full-text search (P2 requirement)
+ *
* @example
* // Simple query: SELECT name, email FROM account WHERE status = 'active'
* {
@@ -425,6 +459,19 @@ export const FieldNodeSchema: z.ZodType = z.lazy(() =>
* limit: 20,
* offset: 40
* }
+ *
+ * @example
+ * // Full-text search
+ * {
+ * object: 'article',
+ * search: {
+ * query: "machine learning",
+ * fields: ["title", "content"],
+ * fuzzy: true,
+ * boost: { "title": 2.0 }
+ * },
+ * limit: 10
+ * }
*/
export const QuerySchema = z.object({
/** Target Entity */
@@ -436,6 +483,9 @@ export const QuerySchema = z.object({
/** Where Clause (Filtering) */
where: FilterConditionSchema.optional().describe('Filtering criteria (WHERE)'),
+ /** Full-Text Search */
+ search: FullTextSearchSchema.optional().describe('Full-text search configuration ($search parameter)'),
+
/** Order By Clause (Sorting) */
orderBy: z.array(SortNodeSchema).optional().describe('Sorting instructions (ORDER BY)'),