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)'),