From 055ac4209ce5f0e9c5251f883a8836814518b0c6 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 30 Jan 2026 01:09:54 +0000
Subject: [PATCH 1/4] Initial plan
From f837385191aa87c1dd6c3a28cb7472c9d01c082a Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 30 Jan 2026 01:15:57 +0000
Subject: [PATCH 2/4] Add comprehensive GraphQL protocol schemas and security
features
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
---
content/docs/references/api/graphql.mdx | 275 ++++
content/docs/references/api/index.mdx | 1 +
content/docs/references/api/meta.json | 1 +
.../api/FieldTypeToGraphQLMapping.json | 260 +++
.../spec/json-schema/api/GraphQLConfig.json | 1389 +++++++++++++++++
.../api/GraphQLDataLoaderConfig.json | 101 ++
.../api/GraphQLDirectiveConfig.json | 108 ++
.../api/GraphQLDirectiveLocation.json | 30 +
.../api/GraphQLMutationConfig.json | 180 +++
.../api/GraphQLPersistedQuery.json | 151 ++
.../api/GraphQLQueryComplexity.json | 83 +
.../json-schema/api/GraphQLQueryConfig.json | 231 +++
.../api/GraphQLQueryDepthLimit.json | 44 +
.../json-schema/api/GraphQLRateLimit.json | 138 ++
.../api/GraphQLResolverConfig.json | 98 ++
.../json-schema/api/GraphQLScalarType.json | 32 +
.../api/GraphQLSubscriptionConfig.json | 118 ++
.../json-schema/api/GraphQLTypeConfig.json | 113 ++
packages/spec/src/api/graphql.test.ts | 962 ++++++++++++
packages/spec/src/api/graphql.zod.ts | 983 ++++++++++++
packages/spec/src/api/index.ts | 1 +
21 files changed, 5299 insertions(+)
create mode 100644 content/docs/references/api/graphql.mdx
create mode 100644 packages/spec/json-schema/api/FieldTypeToGraphQLMapping.json
create mode 100644 packages/spec/json-schema/api/GraphQLConfig.json
create mode 100644 packages/spec/json-schema/api/GraphQLDataLoaderConfig.json
create mode 100644 packages/spec/json-schema/api/GraphQLDirectiveConfig.json
create mode 100644 packages/spec/json-schema/api/GraphQLDirectiveLocation.json
create mode 100644 packages/spec/json-schema/api/GraphQLMutationConfig.json
create mode 100644 packages/spec/json-schema/api/GraphQLPersistedQuery.json
create mode 100644 packages/spec/json-schema/api/GraphQLQueryComplexity.json
create mode 100644 packages/spec/json-schema/api/GraphQLQueryConfig.json
create mode 100644 packages/spec/json-schema/api/GraphQLQueryDepthLimit.json
create mode 100644 packages/spec/json-schema/api/GraphQLRateLimit.json
create mode 100644 packages/spec/json-schema/api/GraphQLResolverConfig.json
create mode 100644 packages/spec/json-schema/api/GraphQLScalarType.json
create mode 100644 packages/spec/json-schema/api/GraphQLSubscriptionConfig.json
create mode 100644 packages/spec/json-schema/api/GraphQLTypeConfig.json
create mode 100644 packages/spec/src/api/graphql.test.ts
create mode 100644 packages/spec/src/api/graphql.zod.ts
diff --git a/content/docs/references/api/graphql.mdx b/content/docs/references/api/graphql.mdx
new file mode 100644
index 00000000000..3ccf4394cc0
--- /dev/null
+++ b/content/docs/references/api/graphql.mdx
@@ -0,0 +1,275 @@
+---
+title: Graphql
+description: Graphql protocol schemas
+---
+
+# Graphql
+
+
+**Source:** `packages/spec/src/api/graphql.zod.ts`
+
+
+## TypeScript Usage
+
+```typescript
+import { FieldTypeToGraphQLMappingSchema, GraphQLConfigSchema, GraphQLDataLoaderConfigSchema, GraphQLDirectiveConfigSchema, GraphQLDirectiveLocationSchema, GraphQLMutationConfigSchema, GraphQLPersistedQuerySchema, GraphQLQueryComplexitySchema, GraphQLQueryConfigSchema, GraphQLQueryDepthLimitSchema, GraphQLRateLimitSchema, GraphQLResolverConfigSchema, GraphQLScalarTypeSchema, GraphQLSubscriptionConfigSchema, GraphQLTypeConfigSchema } from '@objectstack/spec/api';
+import type { FieldTypeToGraphQLMapping, GraphQLConfig, GraphQLDataLoaderConfig, GraphQLDirectiveConfig, GraphQLDirectiveLocation, GraphQLMutationConfig, GraphQLPersistedQuery, GraphQLQueryComplexity, GraphQLQueryConfig, GraphQLQueryDepthLimit, GraphQLRateLimit, GraphQLResolverConfig, GraphQLScalarType, GraphQLSubscriptionConfig, GraphQLTypeConfig } from '@objectstack/spec/api';
+
+// Validate data
+const result = FieldTypeToGraphQLMappingSchema.parse(data);
+```
+
+---
+
+## FieldTypeToGraphQLMapping
+
+Field type to GraphQL type mapping configuration
+
+---
+
+## GraphQLConfig
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **enabled** | `boolean` | optional | Enable GraphQL API |
+| **path** | `string` | optional | GraphQL endpoint path |
+| **playground** | `object` | optional | GraphQL Playground configuration |
+| **schema** | `object` | optional | Schema generation configuration |
+| **dataLoaders** | `object[]` | optional | DataLoader configurations |
+| **security** | `object` | optional | Security configuration |
+
+---
+
+## GraphQLDataLoaderConfig
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **name** | `string` | ✅ | DataLoader name |
+| **source** | `string` | ✅ | Source object or datasource |
+| **batchFunction** | `object` | ✅ | Batch function configuration |
+| **cache** | `object` | optional | DataLoader caching |
+| **options** | `object` | optional | DataLoader options |
+
+---
+
+## GraphQLDirectiveConfig
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **name** | `string` | ✅ | Directive name (camelCase) |
+| **description** | `string` | optional | Directive description |
+| **locations** | `Enum<'QUERY' \| 'MUTATION' \| 'SUBSCRIPTION' \| 'FIELD' \| 'FRAGMENT_DEFINITION' \| 'FRAGMENT_SPREAD' \| 'INLINE_FRAGMENT' \| 'VARIABLE_DEFINITION' \| 'SCHEMA' \| 'SCALAR' \| 'OBJECT' \| 'FIELD_DEFINITION' \| 'ARGUMENT_DEFINITION' \| 'INTERFACE' \| 'UNION' \| 'ENUM' \| 'ENUM_VALUE' \| 'INPUT_OBJECT' \| 'INPUT_FIELD_DEFINITION'>[]` | ✅ | Directive locations |
+| **args** | `Record` | optional | Directive arguments |
+| **repeatable** | `boolean` | optional | Can be applied multiple times |
+| **implementation** | `object` | optional | Directive implementation |
+
+---
+
+## GraphQLDirectiveLocation
+
+### Allowed Values
+
+* `QUERY`
+* `MUTATION`
+* `SUBSCRIPTION`
+* `FIELD`
+* `FRAGMENT_DEFINITION`
+* `FRAGMENT_SPREAD`
+* `INLINE_FRAGMENT`
+* `VARIABLE_DEFINITION`
+* `SCHEMA`
+* `SCALAR`
+* `OBJECT`
+* `FIELD_DEFINITION`
+* `ARGUMENT_DEFINITION`
+* `INTERFACE`
+* `UNION`
+* `ENUM`
+* `ENUM_VALUE`
+* `INPUT_OBJECT`
+* `INPUT_FIELD_DEFINITION`
+
+---
+
+## GraphQLMutationConfig
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **name** | `string` | ✅ | Mutation field name (camelCase recommended) |
+| **object** | `string` | ✅ | Source ObjectQL object name |
+| **type** | `Enum<'create' \| 'update' \| 'delete' \| 'upsert' \| 'custom'>` | ✅ | Mutation type |
+| **description** | `string` | optional | Mutation description |
+| **input** | `object` | optional | Input configuration |
+| **output** | `object` | optional | Output configuration |
+| **transaction** | `object` | optional | Transaction configuration |
+| **authRequired** | `boolean` | optional | Require authentication |
+| **permissions** | `string[]` | optional | Required permissions |
+| **hooks** | `object` | optional | Lifecycle hooks |
+
+---
+
+## GraphQLPersistedQuery
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **enabled** | `boolean` | optional | Enable persisted queries |
+| **mode** | `Enum<'optional' \| 'required'>` | optional | Persisted query mode (optional: allow both, required: only persisted) |
+| **store** | `object` | optional | Query store configuration |
+| **apq** | `object` | optional | Automatic Persisted Queries configuration |
+| **allowlist** | `object` | optional | Query allowlist configuration |
+| **security** | `object` | optional | Security configuration |
+
+---
+
+## GraphQLQueryComplexity
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **enabled** | `boolean` | optional | Enable query complexity limiting |
+| **maxComplexity** | `integer` | optional | Maximum query complexity |
+| **defaultFieldComplexity** | `integer` | optional | Default complexity per field |
+| **fieldComplexity** | `Record` | optional | Per-field complexity configuration |
+| **listMultiplier** | `number` | optional | Multiplier for list fields |
+| **onComplexityExceeded** | `Enum<'reject' \| 'log' \| 'warn'>` | optional | Action when complexity exceeded |
+| **errorMessage** | `string` | optional | Custom error message for complexity violations |
+
+---
+
+## GraphQLQueryConfig
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **name** | `string` | ✅ | Query field name (camelCase recommended) |
+| **object** | `string` | ✅ | Source ObjectQL object name |
+| **type** | `Enum<'get' \| 'list' \| 'search'>` | ✅ | Query type |
+| **description** | `string` | optional | Query description |
+| **args** | `Record` | optional | Query arguments |
+| **filtering** | `object` | optional | Filtering capabilities |
+| **sorting** | `object` | optional | Sorting capabilities |
+| **pagination** | `object` | optional | Pagination configuration |
+| **fields** | `object` | optional | Field selection configuration |
+| **authRequired** | `boolean` | optional | Require authentication |
+| **permissions** | `string[]` | optional | Required permissions |
+| **cache** | `object` | optional | Query caching |
+
+---
+
+## GraphQLQueryDepthLimit
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **enabled** | `boolean` | optional | Enable query depth limiting |
+| **maxDepth** | `integer` | optional | Maximum query depth |
+| **ignoreFields** | `string[]` | optional | Fields excluded from depth calculation |
+| **onDepthExceeded** | `Enum<'reject' \| 'log' \| 'warn'>` | optional | Action when depth exceeded |
+| **errorMessage** | `string` | optional | Custom error message for depth violations |
+
+---
+
+## GraphQLRateLimit
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **enabled** | `boolean` | optional | Enable rate limiting |
+| **strategy** | `Enum<'token_bucket' \| 'fixed_window' \| 'sliding_window' \| 'cost_based'>` | optional | Rate limiting strategy |
+| **global** | `object` | optional | Global rate limits |
+| **perUser** | `object` | optional | Per-user rate limits |
+| **costBased** | `object` | optional | Cost-based rate limiting |
+| **operations** | `Record` | optional | Per-operation rate limits |
+| **onLimitExceeded** | `Enum<'reject' \| 'queue' \| 'log'>` | optional | Action when rate limit exceeded |
+| **errorMessage** | `string` | optional | Custom error message for rate limit violations |
+| **includeHeaders** | `boolean` | optional | Include rate limit headers in response |
+
+---
+
+## GraphQLResolverConfig
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **path** | `string` | ✅ | Resolver path (Type.field) |
+| **type** | `Enum<'datasource' \| 'computed' \| 'script' \| 'proxy'>` | ✅ | Resolver implementation type |
+| **implementation** | `object` | optional | Implementation configuration |
+| **cache** | `object` | optional | Resolver caching |
+
+---
+
+## GraphQLScalarType
+
+### Allowed Values
+
+* `ID`
+* `String`
+* `Int`
+* `Float`
+* `Boolean`
+* `DateTime`
+* `Date`
+* `Time`
+* `JSON`
+* `JSONObject`
+* `Upload`
+* `URL`
+* `Email`
+* `PhoneNumber`
+* `Currency`
+* `Decimal`
+* `BigInt`
+* `Long`
+* `UUID`
+* `Base64`
+* `Void`
+
+---
+
+## GraphQLSubscriptionConfig
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **name** | `string` | ✅ | Subscription field name (camelCase recommended) |
+| **object** | `string` | ✅ | Source ObjectQL object name |
+| **events** | `Enum<'created' \| 'updated' \| 'deleted' \| 'custom'>[]` | ✅ | Events to subscribe to |
+| **description** | `string` | optional | Subscription description |
+| **filter** | `object` | optional | Subscription filtering |
+| **payload** | `object` | optional | Payload configuration |
+| **authRequired** | `boolean` | optional | Require authentication |
+| **permissions** | `string[]` | optional | Required permissions |
+| **rateLimit** | `object` | optional | Subscription rate limiting |
+
+---
+
+## GraphQLTypeConfig
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **name** | `string` | ✅ | GraphQL type name (PascalCase recommended) |
+| **object** | `string` | ✅ | Source ObjectQL object name |
+| **description** | `string` | optional | Type description |
+| **fields** | `object` | optional | Field configuration |
+| **interfaces** | `string[]` | optional | GraphQL interface names |
+| **isInterface** | `boolean` | optional | Define as GraphQL interface |
+| **directives** | `object[]` | optional | GraphQL directives |
+
diff --git a/content/docs/references/api/index.mdx b/content/docs/references/api/index.mdx
index 764e3f46cc2..980afbd4b36 100644
--- a/content/docs/references/api/index.mdx
+++ b/content/docs/references/api/index.mdx
@@ -11,6 +11,7 @@ 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 969dca1e903..7b29372f8d8 100644
--- a/content/docs/references/api/meta.json
+++ b/content/docs/references/api/meta.json
@@ -4,6 +4,7 @@
"contract",
"discovery",
"endpoint",
+ "graphql",
"odata",
"realtime",
"router"
diff --git a/packages/spec/json-schema/api/FieldTypeToGraphQLMapping.json b/packages/spec/json-schema/api/FieldTypeToGraphQLMapping.json
new file mode 100644
index 00000000000..5d689285a08
--- /dev/null
+++ b/packages/spec/json-schema/api/FieldTypeToGraphQLMapping.json
@@ -0,0 +1,260 @@
+{
+ "$ref": "#/definitions/FieldTypeToGraphQLMapping",
+ "definitions": {
+ "FieldTypeToGraphQLMapping": {
+ "anyOf": [
+ {
+ "not": {}
+ },
+ {
+ "type": "object",
+ "properties": {
+ "text": {
+ "type": "string",
+ "const": "String",
+ "default": "String"
+ },
+ "textarea": {
+ "type": "string",
+ "const": "String",
+ "default": "String"
+ },
+ "email": {
+ "type": "string",
+ "const": "Email",
+ "default": "Email"
+ },
+ "url": {
+ "type": "string",
+ "const": "URL",
+ "default": "URL"
+ },
+ "phone": {
+ "type": "string",
+ "const": "PhoneNumber",
+ "default": "PhoneNumber"
+ },
+ "password": {
+ "type": "string",
+ "const": "String",
+ "default": "String"
+ },
+ "markdown": {
+ "type": "string",
+ "const": "String",
+ "default": "String"
+ },
+ "html": {
+ "type": "string",
+ "const": "String",
+ "default": "String"
+ },
+ "richtext": {
+ "type": "string",
+ "const": "String",
+ "default": "String"
+ },
+ "number": {
+ "type": "string",
+ "const": "Float",
+ "default": "Float"
+ },
+ "currency": {
+ "type": "string",
+ "const": "Currency",
+ "default": "Currency"
+ },
+ "percent": {
+ "type": "string",
+ "const": "Float",
+ "default": "Float"
+ },
+ "date": {
+ "type": "string",
+ "const": "Date",
+ "default": "Date"
+ },
+ "datetime": {
+ "type": "string",
+ "const": "DateTime",
+ "default": "DateTime"
+ },
+ "time": {
+ "type": "string",
+ "const": "Time",
+ "default": "Time"
+ },
+ "boolean": {
+ "type": "string",
+ "const": "Boolean",
+ "default": "Boolean"
+ },
+ "toggle": {
+ "type": "string",
+ "const": "Boolean",
+ "default": "Boolean"
+ },
+ "select": {
+ "type": "string",
+ "const": "String",
+ "default": "String"
+ },
+ "multiselect": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "const": "String"
+ },
+ "default": [
+ "String"
+ ]
+ },
+ "radio": {
+ "type": "string",
+ "const": "String",
+ "default": "String"
+ },
+ "checkboxes": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "const": "String"
+ },
+ "default": [
+ "String"
+ ]
+ },
+ "lookup": {
+ "type": "string",
+ "const": "ID",
+ "default": "ID"
+ },
+ "master_detail": {
+ "type": "string",
+ "const": "ID",
+ "default": "ID"
+ },
+ "tree": {
+ "type": "string",
+ "const": "ID",
+ "default": "ID"
+ },
+ "image": {
+ "type": "string",
+ "const": "URL",
+ "default": "URL"
+ },
+ "file": {
+ "type": "string",
+ "const": "URL",
+ "default": "URL"
+ },
+ "avatar": {
+ "type": "string",
+ "const": "URL",
+ "default": "URL"
+ },
+ "video": {
+ "type": "string",
+ "const": "URL",
+ "default": "URL"
+ },
+ "audio": {
+ "type": "string",
+ "const": "URL",
+ "default": "URL"
+ },
+ "formula": {
+ "type": "string",
+ "const": "String",
+ "default": "String"
+ },
+ "summary": {
+ "type": "string",
+ "const": "Float",
+ "default": "Float"
+ },
+ "autonumber": {
+ "type": "string",
+ "const": "String",
+ "default": "String"
+ },
+ "location": {
+ "type": "string",
+ "const": "JSONObject",
+ "default": "JSONObject"
+ },
+ "address": {
+ "type": "string",
+ "const": "JSONObject",
+ "default": "JSONObject"
+ },
+ "code": {
+ "type": "string",
+ "const": "String",
+ "default": "String"
+ },
+ "json": {
+ "type": "string",
+ "const": "JSON",
+ "default": "JSON"
+ },
+ "color": {
+ "type": "string",
+ "const": "String",
+ "default": "String"
+ },
+ "rating": {
+ "type": "string",
+ "const": "Float",
+ "default": "Float"
+ },
+ "slider": {
+ "type": "string",
+ "const": "Float",
+ "default": "Float"
+ },
+ "signature": {
+ "type": "string",
+ "const": "String",
+ "default": "String"
+ },
+ "qrcode": {
+ "type": "string",
+ "const": "String",
+ "default": "String"
+ },
+ "progress": {
+ "type": "string",
+ "const": "Float",
+ "default": "Float"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "const": "String"
+ },
+ "default": [
+ "String"
+ ]
+ },
+ "vector": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "const": "Float"
+ },
+ "default": [
+ "Float"
+ ]
+ }
+ },
+ "additionalProperties": false
+ }
+ ],
+ "description": "Field type to GraphQL type mapping configuration"
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/GraphQLConfig.json b/packages/spec/json-schema/api/GraphQLConfig.json
new file mode 100644
index 00000000000..c8b6a08fc8b
--- /dev/null
+++ b/packages/spec/json-schema/api/GraphQLConfig.json
@@ -0,0 +1,1389 @@
+{
+ "$ref": "#/definitions/GraphQLConfig",
+ "definitions": {
+ "GraphQLConfig": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "default": true,
+ "description": "Enable GraphQL API"
+ },
+ "path": {
+ "type": "string",
+ "default": "/graphql",
+ "description": "GraphQL endpoint path"
+ },
+ "playground": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "default": true,
+ "description": "Enable GraphQL Playground"
+ },
+ "path": {
+ "type": "string",
+ "default": "/playground",
+ "description": "Playground path"
+ }
+ },
+ "additionalProperties": false,
+ "description": "GraphQL Playground configuration"
+ },
+ "schema": {
+ "type": "object",
+ "properties": {
+ "autoGenerateTypes": {
+ "type": "boolean",
+ "default": true,
+ "description": "Auto-generate types from Objects"
+ },
+ "types": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "GraphQL type name (PascalCase recommended)"
+ },
+ "object": {
+ "type": "string",
+ "description": "Source ObjectQL object name"
+ },
+ "description": {
+ "type": "string",
+ "description": "Type description"
+ },
+ "fields": {
+ "type": "object",
+ "properties": {
+ "include": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Fields to include"
+ },
+ "exclude": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Fields to exclude (e.g., sensitive fields)"
+ },
+ "mappings": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "object",
+ "properties": {
+ "graphqlName": {
+ "type": "string",
+ "description": "Custom GraphQL field name"
+ },
+ "graphqlType": {
+ "type": "string",
+ "description": "Override GraphQL type"
+ },
+ "description": {
+ "type": "string",
+ "description": "Field description"
+ },
+ "deprecationReason": {
+ "type": "string",
+ "description": "Why field is deprecated"
+ },
+ "nullable": {
+ "type": "boolean",
+ "description": "Override nullable"
+ }
+ },
+ "additionalProperties": false
+ },
+ "description": "Field-level customizations"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Field configuration"
+ },
+ "interfaces": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "GraphQL interface names"
+ },
+ "isInterface": {
+ "type": "boolean",
+ "default": false,
+ "description": "Define as GraphQL interface"
+ },
+ "directives": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "Directive name"
+ },
+ "args": {
+ "type": "object",
+ "additionalProperties": {},
+ "description": "Directive arguments"
+ }
+ },
+ "required": [
+ "name"
+ ],
+ "additionalProperties": false
+ },
+ "description": "GraphQL directives"
+ }
+ },
+ "required": [
+ "name",
+ "object"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Type configurations"
+ },
+ "queries": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "Query field name (camelCase recommended)"
+ },
+ "object": {
+ "type": "string",
+ "description": "Source ObjectQL object name"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "get",
+ "list",
+ "search"
+ ],
+ "description": "Query type"
+ },
+ "description": {
+ "type": "string",
+ "description": "Query description"
+ },
+ "args": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "description": "GraphQL type (e.g., \"ID!\", \"String\", \"Int\")"
+ },
+ "description": {
+ "type": "string",
+ "description": "Argument description"
+ },
+ "defaultValue": {
+ "description": "Default value"
+ }
+ },
+ "required": [
+ "type"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Query arguments"
+ },
+ "filtering": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "default": true,
+ "description": "Allow filtering"
+ },
+ "fields": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Filterable fields"
+ },
+ "operators": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "enum": [
+ "eq",
+ "ne",
+ "gt",
+ "gte",
+ "lt",
+ "lte",
+ "in",
+ "notIn",
+ "contains",
+ "startsWith",
+ "endsWith",
+ "isNull",
+ "isNotNull"
+ ]
+ },
+ "description": "Allowed filter operators"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Filtering capabilities"
+ },
+ "sorting": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "default": true,
+ "description": "Allow sorting"
+ },
+ "fields": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Sortable fields"
+ },
+ "defaultSort": {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string"
+ },
+ "direction": {
+ "type": "string",
+ "enum": [
+ "ASC",
+ "DESC"
+ ]
+ }
+ },
+ "required": [
+ "field",
+ "direction"
+ ],
+ "additionalProperties": false,
+ "description": "Default sort order"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Sorting capabilities"
+ },
+ "pagination": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "default": true,
+ "description": "Enable pagination"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "offset",
+ "cursor",
+ "relay"
+ ],
+ "default": "offset",
+ "description": "Pagination style"
+ },
+ "defaultLimit": {
+ "type": "integer",
+ "minimum": 1,
+ "default": 20,
+ "description": "Default page size"
+ },
+ "maxLimit": {
+ "type": "integer",
+ "minimum": 1,
+ "default": 100,
+ "description": "Maximum page size"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Pagination configuration"
+ },
+ "fields": {
+ "type": "object",
+ "properties": {
+ "required": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Required fields (always returned)"
+ },
+ "selectable": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Selectable fields"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Field selection configuration"
+ },
+ "authRequired": {
+ "type": "boolean",
+ "default": true,
+ "description": "Require authentication"
+ },
+ "permissions": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Required permissions"
+ },
+ "cache": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "default": false,
+ "description": "Enable caching"
+ },
+ "ttl": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Cache TTL in seconds"
+ },
+ "key": {
+ "type": "string",
+ "description": "Cache key template"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Query caching"
+ }
+ },
+ "required": [
+ "name",
+ "object",
+ "type"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Query configurations"
+ },
+ "mutations": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "Mutation field name (camelCase recommended)"
+ },
+ "object": {
+ "type": "string",
+ "description": "Source ObjectQL object name"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "create",
+ "update",
+ "delete",
+ "upsert",
+ "custom"
+ ],
+ "description": "Mutation type"
+ },
+ "description": {
+ "type": "string",
+ "description": "Mutation description"
+ },
+ "input": {
+ "type": "object",
+ "properties": {
+ "typeName": {
+ "type": "string",
+ "description": "Custom input type name"
+ },
+ "fields": {
+ "type": "object",
+ "properties": {
+ "include": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Fields to include"
+ },
+ "exclude": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Fields to exclude"
+ },
+ "required": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Required input fields"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Input field configuration"
+ },
+ "validation": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "default": true,
+ "description": "Enable input validation"
+ },
+ "rules": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Custom validation rules"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Input validation"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Input configuration"
+ },
+ "output": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "object",
+ "payload",
+ "boolean",
+ "custom"
+ ],
+ "default": "object",
+ "description": "Output type"
+ },
+ "includeEnvelope": {
+ "type": "boolean",
+ "default": false,
+ "description": "Wrap in success/error payload"
+ },
+ "customType": {
+ "type": "string",
+ "description": "Custom output type name"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Output configuration"
+ },
+ "transaction": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "default": true,
+ "description": "Use database transaction"
+ },
+ "isolationLevel": {
+ "type": "string",
+ "enum": [
+ "read_uncommitted",
+ "read_committed",
+ "repeatable_read",
+ "serializable"
+ ],
+ "description": "Transaction isolation level"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Transaction configuration"
+ },
+ "authRequired": {
+ "type": "boolean",
+ "default": true,
+ "description": "Require authentication"
+ },
+ "permissions": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Required permissions"
+ },
+ "hooks": {
+ "type": "object",
+ "properties": {
+ "before": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Pre-mutation hooks"
+ },
+ "after": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Post-mutation hooks"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Lifecycle hooks"
+ }
+ },
+ "required": [
+ "name",
+ "object",
+ "type"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Mutation configurations"
+ },
+ "subscriptions": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "Subscription field name (camelCase recommended)"
+ },
+ "object": {
+ "type": "string",
+ "description": "Source ObjectQL object name"
+ },
+ "events": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "enum": [
+ "created",
+ "updated",
+ "deleted",
+ "custom"
+ ]
+ },
+ "description": "Events to subscribe to"
+ },
+ "description": {
+ "type": "string",
+ "description": "Subscription description"
+ },
+ "filter": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "default": true,
+ "description": "Allow filtering subscriptions"
+ },
+ "fields": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Filterable fields"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Subscription filtering"
+ },
+ "payload": {
+ "type": "object",
+ "properties": {
+ "includeEntity": {
+ "type": "boolean",
+ "default": true,
+ "description": "Include entity in payload"
+ },
+ "includePreviousValues": {
+ "type": "boolean",
+ "default": false,
+ "description": "Include previous field values"
+ },
+ "includeMeta": {
+ "type": "boolean",
+ "default": true,
+ "description": "Include metadata (timestamp, user, etc.)"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Payload configuration"
+ },
+ "authRequired": {
+ "type": "boolean",
+ "default": true,
+ "description": "Require authentication"
+ },
+ "permissions": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Required permissions"
+ },
+ "rateLimit": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "default": true,
+ "description": "Enable rate limiting"
+ },
+ "maxSubscriptionsPerUser": {
+ "type": "integer",
+ "minimum": 1,
+ "default": 10,
+ "description": "Max concurrent subscriptions per user"
+ },
+ "throttleMs": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Throttle interval in milliseconds"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Subscription rate limiting"
+ }
+ },
+ "required": [
+ "name",
+ "object",
+ "events"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Subscription configurations"
+ },
+ "resolvers": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "path": {
+ "type": "string",
+ "description": "Resolver path (Type.field)"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "datasource",
+ "computed",
+ "script",
+ "proxy"
+ ],
+ "description": "Resolver implementation type"
+ },
+ "implementation": {
+ "type": "object",
+ "properties": {
+ "datasource": {
+ "type": "string",
+ "description": "Datasource ID"
+ },
+ "query": {
+ "type": "string",
+ "description": "Query/SQL to execute"
+ },
+ "expression": {
+ "type": "string",
+ "description": "Computation expression"
+ },
+ "dependencies": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Dependent fields"
+ },
+ "script": {
+ "type": "string",
+ "description": "Script ID or inline code"
+ },
+ "url": {
+ "type": "string",
+ "description": "Proxy URL"
+ },
+ "method": {
+ "type": "string",
+ "enum": [
+ "GET",
+ "POST",
+ "PUT",
+ "DELETE"
+ ],
+ "description": "HTTP method"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Implementation configuration"
+ },
+ "cache": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "default": false,
+ "description": "Enable resolver caching"
+ },
+ "ttl": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Cache TTL in seconds"
+ },
+ "keyArgs": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Arguments to include in cache key"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Resolver caching"
+ }
+ },
+ "required": [
+ "path",
+ "type"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Custom resolver configurations"
+ },
+ "directives": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "pattern": "^[a-z][a-zA-Z0-9]*$",
+ "description": "Directive name (camelCase)"
+ },
+ "description": {
+ "type": "string",
+ "description": "Directive description"
+ },
+ "locations": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "enum": [
+ "QUERY",
+ "MUTATION",
+ "SUBSCRIPTION",
+ "FIELD",
+ "FRAGMENT_DEFINITION",
+ "FRAGMENT_SPREAD",
+ "INLINE_FRAGMENT",
+ "VARIABLE_DEFINITION",
+ "SCHEMA",
+ "SCALAR",
+ "OBJECT",
+ "FIELD_DEFINITION",
+ "ARGUMENT_DEFINITION",
+ "INTERFACE",
+ "UNION",
+ "ENUM",
+ "ENUM_VALUE",
+ "INPUT_OBJECT",
+ "INPUT_FIELD_DEFINITION"
+ ]
+ },
+ "description": "Directive locations"
+ },
+ "args": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "description": "Argument type"
+ },
+ "description": {
+ "type": "string",
+ "description": "Argument description"
+ },
+ "defaultValue": {
+ "description": "Default value"
+ }
+ },
+ "required": [
+ "type"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Directive arguments"
+ },
+ "repeatable": {
+ "type": "boolean",
+ "default": false,
+ "description": "Can be applied multiple times"
+ },
+ "implementation": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "auth",
+ "validation",
+ "transform",
+ "cache",
+ "deprecation",
+ "custom"
+ ],
+ "description": "Directive type"
+ },
+ "handler": {
+ "type": "string",
+ "description": "Handler function name or script"
+ }
+ },
+ "required": [
+ "type"
+ ],
+ "additionalProperties": false,
+ "description": "Directive implementation"
+ }
+ },
+ "required": [
+ "name",
+ "locations"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Custom directive configurations"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Schema generation configuration"
+ },
+ "dataLoaders": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "DataLoader name"
+ },
+ "source": {
+ "type": "string",
+ "description": "Source object or datasource"
+ },
+ "batchFunction": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "findByIds",
+ "query",
+ "script",
+ "custom"
+ ],
+ "description": "Batch function type"
+ },
+ "keyField": {
+ "type": "string",
+ "description": "Field to batch on (e.g., \"id\")"
+ },
+ "query": {
+ "type": "string",
+ "description": "Query template"
+ },
+ "script": {
+ "type": "string",
+ "description": "Script ID"
+ },
+ "maxBatchSize": {
+ "type": "integer",
+ "minimum": 1,
+ "default": 100,
+ "description": "Maximum batch size"
+ }
+ },
+ "required": [
+ "type"
+ ],
+ "additionalProperties": false,
+ "description": "Batch function configuration"
+ },
+ "cache": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "default": true,
+ "description": "Enable per-request caching"
+ },
+ "keyFn": {
+ "type": "string",
+ "description": "Custom cache key function"
+ }
+ },
+ "additionalProperties": false,
+ "description": "DataLoader caching"
+ },
+ "options": {
+ "type": "object",
+ "properties": {
+ "batch": {
+ "type": "boolean",
+ "default": true,
+ "description": "Enable batching"
+ },
+ "cache": {
+ "type": "boolean",
+ "default": true,
+ "description": "Enable caching"
+ },
+ "maxCacheSize": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Max cache entries"
+ }
+ },
+ "additionalProperties": false,
+ "description": "DataLoader options"
+ }
+ },
+ "required": [
+ "name",
+ "source",
+ "batchFunction"
+ ],
+ "additionalProperties": false
+ },
+ "description": "DataLoader configurations"
+ },
+ "security": {
+ "type": "object",
+ "properties": {
+ "depthLimit": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "default": true,
+ "description": "Enable query depth limiting"
+ },
+ "maxDepth": {
+ "type": "integer",
+ "minimum": 1,
+ "default": 10,
+ "description": "Maximum query depth"
+ },
+ "ignoreFields": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Fields excluded from depth calculation"
+ },
+ "onDepthExceeded": {
+ "type": "string",
+ "enum": [
+ "reject",
+ "log",
+ "warn"
+ ],
+ "default": "reject",
+ "description": "Action when depth exceeded"
+ },
+ "errorMessage": {
+ "type": "string",
+ "description": "Custom error message for depth violations"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Query depth limiting"
+ },
+ "complexity": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "default": true,
+ "description": "Enable query complexity limiting"
+ },
+ "maxComplexity": {
+ "type": "integer",
+ "minimum": 1,
+ "default": 1000,
+ "description": "Maximum query complexity"
+ },
+ "defaultFieldComplexity": {
+ "type": "integer",
+ "minimum": 0,
+ "default": 1,
+ "description": "Default complexity per field"
+ },
+ "fieldComplexity": {
+ "type": "object",
+ "additionalProperties": {
+ "anyOf": [
+ {
+ "type": "integer",
+ "minimum": 0
+ },
+ {
+ "type": "object",
+ "properties": {
+ "base": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Base complexity"
+ },
+ "multiplier": {
+ "type": "string",
+ "description": "Argument multiplier (e.g., \"limit\")"
+ },
+ "calculator": {
+ "type": "string",
+ "description": "Custom calculator function"
+ }
+ },
+ "required": [
+ "base"
+ ],
+ "additionalProperties": false
+ }
+ ]
+ },
+ "description": "Per-field complexity configuration"
+ },
+ "listMultiplier": {
+ "type": "number",
+ "minimum": 0,
+ "default": 10,
+ "description": "Multiplier for list fields"
+ },
+ "onComplexityExceeded": {
+ "type": "string",
+ "enum": [
+ "reject",
+ "log",
+ "warn"
+ ],
+ "default": "reject",
+ "description": "Action when complexity exceeded"
+ },
+ "errorMessage": {
+ "type": "string",
+ "description": "Custom error message for complexity violations"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Query complexity calculation"
+ },
+ "rateLimit": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "default": true,
+ "description": "Enable rate limiting"
+ },
+ "strategy": {
+ "type": "string",
+ "enum": [
+ "token_bucket",
+ "fixed_window",
+ "sliding_window",
+ "cost_based"
+ ],
+ "default": "token_bucket",
+ "description": "Rate limiting strategy"
+ },
+ "global": {
+ "type": "object",
+ "properties": {
+ "maxRequests": {
+ "type": "integer",
+ "minimum": 1,
+ "default": 1000,
+ "description": "Maximum requests per window"
+ },
+ "windowMs": {
+ "type": "integer",
+ "minimum": 1000,
+ "default": 60000,
+ "description": "Time window in milliseconds"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Global rate limits"
+ },
+ "perUser": {
+ "type": "object",
+ "properties": {
+ "maxRequests": {
+ "type": "integer",
+ "minimum": 1,
+ "default": 100,
+ "description": "Maximum requests per user per window"
+ },
+ "windowMs": {
+ "type": "integer",
+ "minimum": 1000,
+ "default": 60000,
+ "description": "Time window in milliseconds"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Per-user rate limits"
+ },
+ "costBased": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "default": false,
+ "description": "Enable cost-based rate limiting"
+ },
+ "maxCost": {
+ "type": "integer",
+ "minimum": 1,
+ "default": 10000,
+ "description": "Maximum cost per window"
+ },
+ "windowMs": {
+ "type": "integer",
+ "minimum": 1000,
+ "default": 60000,
+ "description": "Time window in milliseconds"
+ },
+ "useComplexityAsCost": {
+ "type": "boolean",
+ "default": true,
+ "description": "Use query complexity as cost"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Cost-based rate limiting"
+ },
+ "operations": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "object",
+ "properties": {
+ "maxRequests": {
+ "type": "integer",
+ "minimum": 1,
+ "description": "Max requests for this operation"
+ },
+ "windowMs": {
+ "type": "integer",
+ "minimum": 1000,
+ "description": "Time window"
+ }
+ },
+ "required": [
+ "maxRequests",
+ "windowMs"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Per-operation rate limits"
+ },
+ "onLimitExceeded": {
+ "type": "string",
+ "enum": [
+ "reject",
+ "queue",
+ "log"
+ ],
+ "default": "reject",
+ "description": "Action when rate limit exceeded"
+ },
+ "errorMessage": {
+ "type": "string",
+ "description": "Custom error message for rate limit violations"
+ },
+ "includeHeaders": {
+ "type": "boolean",
+ "default": true,
+ "description": "Include rate limit headers in response"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Rate limiting"
+ },
+ "persistedQueries": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "default": false,
+ "description": "Enable persisted queries"
+ },
+ "mode": {
+ "type": "string",
+ "enum": [
+ "optional",
+ "required"
+ ],
+ "default": "optional",
+ "description": "Persisted query mode (optional: allow both, required: only persisted)"
+ },
+ "store": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "memory",
+ "redis",
+ "database",
+ "file"
+ ],
+ "default": "memory",
+ "description": "Query store type"
+ },
+ "connection": {
+ "type": "string",
+ "description": "Store connection string or path"
+ },
+ "ttl": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "TTL in seconds for stored queries"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Query store configuration"
+ },
+ "apq": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "default": true,
+ "description": "Enable Automatic Persisted Queries"
+ },
+ "hashAlgorithm": {
+ "type": "string",
+ "enum": [
+ "sha256",
+ "sha1",
+ "md5"
+ ],
+ "default": "sha256",
+ "description": "Hash algorithm for query IDs"
+ },
+ "cache": {
+ "type": "object",
+ "properties": {
+ "ttl": {
+ "type": "integer",
+ "minimum": 0,
+ "default": 3600,
+ "description": "Cache TTL in seconds"
+ },
+ "maxSize": {
+ "type": "integer",
+ "minimum": 1,
+ "description": "Maximum number of cached queries"
+ }
+ },
+ "additionalProperties": false,
+ "description": "APQ cache configuration"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Automatic Persisted Queries configuration"
+ },
+ "allowlist": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "default": false,
+ "description": "Enable query allowlist (reject queries not in list)"
+ },
+ "queries": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "description": "Query ID or hash"
+ },
+ "operation": {
+ "type": "string",
+ "description": "Operation name"
+ },
+ "query": {
+ "type": "string",
+ "description": "Query string"
+ }
+ },
+ "required": [
+ "id"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Allowed queries"
+ },
+ "source": {
+ "type": "string",
+ "description": "External allowlist source (file path or URL)"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Query allowlist configuration"
+ },
+ "security": {
+ "type": "object",
+ "properties": {
+ "maxQuerySize": {
+ "type": "integer",
+ "minimum": 1,
+ "description": "Maximum query string size in bytes"
+ },
+ "rejectIntrospection": {
+ "type": "boolean",
+ "default": false,
+ "description": "Reject introspection queries"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Security configuration"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Persisted queries"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Security configuration"
+ }
+ },
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/GraphQLDataLoaderConfig.json b/packages/spec/json-schema/api/GraphQLDataLoaderConfig.json
new file mode 100644
index 00000000000..8a5f640c65a
--- /dev/null
+++ b/packages/spec/json-schema/api/GraphQLDataLoaderConfig.json
@@ -0,0 +1,101 @@
+{
+ "$ref": "#/definitions/GraphQLDataLoaderConfig",
+ "definitions": {
+ "GraphQLDataLoaderConfig": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "DataLoader name"
+ },
+ "source": {
+ "type": "string",
+ "description": "Source object or datasource"
+ },
+ "batchFunction": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "findByIds",
+ "query",
+ "script",
+ "custom"
+ ],
+ "description": "Batch function type"
+ },
+ "keyField": {
+ "type": "string",
+ "description": "Field to batch on (e.g., \"id\")"
+ },
+ "query": {
+ "type": "string",
+ "description": "Query template"
+ },
+ "script": {
+ "type": "string",
+ "description": "Script ID"
+ },
+ "maxBatchSize": {
+ "type": "integer",
+ "minimum": 1,
+ "default": 100,
+ "description": "Maximum batch size"
+ }
+ },
+ "required": [
+ "type"
+ ],
+ "additionalProperties": false,
+ "description": "Batch function configuration"
+ },
+ "cache": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "default": true,
+ "description": "Enable per-request caching"
+ },
+ "keyFn": {
+ "type": "string",
+ "description": "Custom cache key function"
+ }
+ },
+ "additionalProperties": false,
+ "description": "DataLoader caching"
+ },
+ "options": {
+ "type": "object",
+ "properties": {
+ "batch": {
+ "type": "boolean",
+ "default": true,
+ "description": "Enable batching"
+ },
+ "cache": {
+ "type": "boolean",
+ "default": true,
+ "description": "Enable caching"
+ },
+ "maxCacheSize": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Max cache entries"
+ }
+ },
+ "additionalProperties": false,
+ "description": "DataLoader options"
+ }
+ },
+ "required": [
+ "name",
+ "source",
+ "batchFunction"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/GraphQLDirectiveConfig.json b/packages/spec/json-schema/api/GraphQLDirectiveConfig.json
new file mode 100644
index 00000000000..9ac1bdb2d19
--- /dev/null
+++ b/packages/spec/json-schema/api/GraphQLDirectiveConfig.json
@@ -0,0 +1,108 @@
+{
+ "$ref": "#/definitions/GraphQLDirectiveConfig",
+ "definitions": {
+ "GraphQLDirectiveConfig": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "pattern": "^[a-z][a-zA-Z0-9]*$",
+ "description": "Directive name (camelCase)"
+ },
+ "description": {
+ "type": "string",
+ "description": "Directive description"
+ },
+ "locations": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "enum": [
+ "QUERY",
+ "MUTATION",
+ "SUBSCRIPTION",
+ "FIELD",
+ "FRAGMENT_DEFINITION",
+ "FRAGMENT_SPREAD",
+ "INLINE_FRAGMENT",
+ "VARIABLE_DEFINITION",
+ "SCHEMA",
+ "SCALAR",
+ "OBJECT",
+ "FIELD_DEFINITION",
+ "ARGUMENT_DEFINITION",
+ "INTERFACE",
+ "UNION",
+ "ENUM",
+ "ENUM_VALUE",
+ "INPUT_OBJECT",
+ "INPUT_FIELD_DEFINITION"
+ ]
+ },
+ "description": "Directive locations"
+ },
+ "args": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "description": "Argument type"
+ },
+ "description": {
+ "type": "string",
+ "description": "Argument description"
+ },
+ "defaultValue": {
+ "description": "Default value"
+ }
+ },
+ "required": [
+ "type"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Directive arguments"
+ },
+ "repeatable": {
+ "type": "boolean",
+ "default": false,
+ "description": "Can be applied multiple times"
+ },
+ "implementation": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "auth",
+ "validation",
+ "transform",
+ "cache",
+ "deprecation",
+ "custom"
+ ],
+ "description": "Directive type"
+ },
+ "handler": {
+ "type": "string",
+ "description": "Handler function name or script"
+ }
+ },
+ "required": [
+ "type"
+ ],
+ "additionalProperties": false,
+ "description": "Directive implementation"
+ }
+ },
+ "required": [
+ "name",
+ "locations"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/GraphQLDirectiveLocation.json b/packages/spec/json-schema/api/GraphQLDirectiveLocation.json
new file mode 100644
index 00000000000..0cf630e2726
--- /dev/null
+++ b/packages/spec/json-schema/api/GraphQLDirectiveLocation.json
@@ -0,0 +1,30 @@
+{
+ "$ref": "#/definitions/GraphQLDirectiveLocation",
+ "definitions": {
+ "GraphQLDirectiveLocation": {
+ "type": "string",
+ "enum": [
+ "QUERY",
+ "MUTATION",
+ "SUBSCRIPTION",
+ "FIELD",
+ "FRAGMENT_DEFINITION",
+ "FRAGMENT_SPREAD",
+ "INLINE_FRAGMENT",
+ "VARIABLE_DEFINITION",
+ "SCHEMA",
+ "SCALAR",
+ "OBJECT",
+ "FIELD_DEFINITION",
+ "ARGUMENT_DEFINITION",
+ "INTERFACE",
+ "UNION",
+ "ENUM",
+ "ENUM_VALUE",
+ "INPUT_OBJECT",
+ "INPUT_FIELD_DEFINITION"
+ ]
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/GraphQLMutationConfig.json b/packages/spec/json-schema/api/GraphQLMutationConfig.json
new file mode 100644
index 00000000000..5777879b800
--- /dev/null
+++ b/packages/spec/json-schema/api/GraphQLMutationConfig.json
@@ -0,0 +1,180 @@
+{
+ "$ref": "#/definitions/GraphQLMutationConfig",
+ "definitions": {
+ "GraphQLMutationConfig": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "Mutation field name (camelCase recommended)"
+ },
+ "object": {
+ "type": "string",
+ "description": "Source ObjectQL object name"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "create",
+ "update",
+ "delete",
+ "upsert",
+ "custom"
+ ],
+ "description": "Mutation type"
+ },
+ "description": {
+ "type": "string",
+ "description": "Mutation description"
+ },
+ "input": {
+ "type": "object",
+ "properties": {
+ "typeName": {
+ "type": "string",
+ "description": "Custom input type name"
+ },
+ "fields": {
+ "type": "object",
+ "properties": {
+ "include": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Fields to include"
+ },
+ "exclude": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Fields to exclude"
+ },
+ "required": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Required input fields"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Input field configuration"
+ },
+ "validation": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "default": true,
+ "description": "Enable input validation"
+ },
+ "rules": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Custom validation rules"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Input validation"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Input configuration"
+ },
+ "output": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "object",
+ "payload",
+ "boolean",
+ "custom"
+ ],
+ "default": "object",
+ "description": "Output type"
+ },
+ "includeEnvelope": {
+ "type": "boolean",
+ "default": false,
+ "description": "Wrap in success/error payload"
+ },
+ "customType": {
+ "type": "string",
+ "description": "Custom output type name"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Output configuration"
+ },
+ "transaction": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "default": true,
+ "description": "Use database transaction"
+ },
+ "isolationLevel": {
+ "type": "string",
+ "enum": [
+ "read_uncommitted",
+ "read_committed",
+ "repeatable_read",
+ "serializable"
+ ],
+ "description": "Transaction isolation level"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Transaction configuration"
+ },
+ "authRequired": {
+ "type": "boolean",
+ "default": true,
+ "description": "Require authentication"
+ },
+ "permissions": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Required permissions"
+ },
+ "hooks": {
+ "type": "object",
+ "properties": {
+ "before": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Pre-mutation hooks"
+ },
+ "after": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Post-mutation hooks"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Lifecycle hooks"
+ }
+ },
+ "required": [
+ "name",
+ "object",
+ "type"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/GraphQLPersistedQuery.json b/packages/spec/json-schema/api/GraphQLPersistedQuery.json
new file mode 100644
index 00000000000..df100c33537
--- /dev/null
+++ b/packages/spec/json-schema/api/GraphQLPersistedQuery.json
@@ -0,0 +1,151 @@
+{
+ "$ref": "#/definitions/GraphQLPersistedQuery",
+ "definitions": {
+ "GraphQLPersistedQuery": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "default": false,
+ "description": "Enable persisted queries"
+ },
+ "mode": {
+ "type": "string",
+ "enum": [
+ "optional",
+ "required"
+ ],
+ "default": "optional",
+ "description": "Persisted query mode (optional: allow both, required: only persisted)"
+ },
+ "store": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "memory",
+ "redis",
+ "database",
+ "file"
+ ],
+ "default": "memory",
+ "description": "Query store type"
+ },
+ "connection": {
+ "type": "string",
+ "description": "Store connection string or path"
+ },
+ "ttl": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "TTL in seconds for stored queries"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Query store configuration"
+ },
+ "apq": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "default": true,
+ "description": "Enable Automatic Persisted Queries"
+ },
+ "hashAlgorithm": {
+ "type": "string",
+ "enum": [
+ "sha256",
+ "sha1",
+ "md5"
+ ],
+ "default": "sha256",
+ "description": "Hash algorithm for query IDs"
+ },
+ "cache": {
+ "type": "object",
+ "properties": {
+ "ttl": {
+ "type": "integer",
+ "minimum": 0,
+ "default": 3600,
+ "description": "Cache TTL in seconds"
+ },
+ "maxSize": {
+ "type": "integer",
+ "minimum": 1,
+ "description": "Maximum number of cached queries"
+ }
+ },
+ "additionalProperties": false,
+ "description": "APQ cache configuration"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Automatic Persisted Queries configuration"
+ },
+ "allowlist": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "default": false,
+ "description": "Enable query allowlist (reject queries not in list)"
+ },
+ "queries": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "description": "Query ID or hash"
+ },
+ "operation": {
+ "type": "string",
+ "description": "Operation name"
+ },
+ "query": {
+ "type": "string",
+ "description": "Query string"
+ }
+ },
+ "required": [
+ "id"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Allowed queries"
+ },
+ "source": {
+ "type": "string",
+ "description": "External allowlist source (file path or URL)"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Query allowlist configuration"
+ },
+ "security": {
+ "type": "object",
+ "properties": {
+ "maxQuerySize": {
+ "type": "integer",
+ "minimum": 1,
+ "description": "Maximum query string size in bytes"
+ },
+ "rejectIntrospection": {
+ "type": "boolean",
+ "default": false,
+ "description": "Reject introspection queries"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Security configuration"
+ }
+ },
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/GraphQLQueryComplexity.json b/packages/spec/json-schema/api/GraphQLQueryComplexity.json
new file mode 100644
index 00000000000..34ba299a5b8
--- /dev/null
+++ b/packages/spec/json-schema/api/GraphQLQueryComplexity.json
@@ -0,0 +1,83 @@
+{
+ "$ref": "#/definitions/GraphQLQueryComplexity",
+ "definitions": {
+ "GraphQLQueryComplexity": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "default": true,
+ "description": "Enable query complexity limiting"
+ },
+ "maxComplexity": {
+ "type": "integer",
+ "minimum": 1,
+ "default": 1000,
+ "description": "Maximum query complexity"
+ },
+ "defaultFieldComplexity": {
+ "type": "integer",
+ "minimum": 0,
+ "default": 1,
+ "description": "Default complexity per field"
+ },
+ "fieldComplexity": {
+ "type": "object",
+ "additionalProperties": {
+ "anyOf": [
+ {
+ "type": "integer",
+ "minimum": 0
+ },
+ {
+ "type": "object",
+ "properties": {
+ "base": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Base complexity"
+ },
+ "multiplier": {
+ "type": "string",
+ "description": "Argument multiplier (e.g., \"limit\")"
+ },
+ "calculator": {
+ "type": "string",
+ "description": "Custom calculator function"
+ }
+ },
+ "required": [
+ "base"
+ ],
+ "additionalProperties": false
+ }
+ ]
+ },
+ "description": "Per-field complexity configuration"
+ },
+ "listMultiplier": {
+ "type": "number",
+ "minimum": 0,
+ "default": 10,
+ "description": "Multiplier for list fields"
+ },
+ "onComplexityExceeded": {
+ "type": "string",
+ "enum": [
+ "reject",
+ "log",
+ "warn"
+ ],
+ "default": "reject",
+ "description": "Action when complexity exceeded"
+ },
+ "errorMessage": {
+ "type": "string",
+ "description": "Custom error message for complexity violations"
+ }
+ },
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/GraphQLQueryConfig.json b/packages/spec/json-schema/api/GraphQLQueryConfig.json
new file mode 100644
index 00000000000..e9cad9ea40d
--- /dev/null
+++ b/packages/spec/json-schema/api/GraphQLQueryConfig.json
@@ -0,0 +1,231 @@
+{
+ "$ref": "#/definitions/GraphQLQueryConfig",
+ "definitions": {
+ "GraphQLQueryConfig": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "Query field name (camelCase recommended)"
+ },
+ "object": {
+ "type": "string",
+ "description": "Source ObjectQL object name"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "get",
+ "list",
+ "search"
+ ],
+ "description": "Query type"
+ },
+ "description": {
+ "type": "string",
+ "description": "Query description"
+ },
+ "args": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "description": "GraphQL type (e.g., \"ID!\", \"String\", \"Int\")"
+ },
+ "description": {
+ "type": "string",
+ "description": "Argument description"
+ },
+ "defaultValue": {
+ "description": "Default value"
+ }
+ },
+ "required": [
+ "type"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Query arguments"
+ },
+ "filtering": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "default": true,
+ "description": "Allow filtering"
+ },
+ "fields": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Filterable fields"
+ },
+ "operators": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "enum": [
+ "eq",
+ "ne",
+ "gt",
+ "gte",
+ "lt",
+ "lte",
+ "in",
+ "notIn",
+ "contains",
+ "startsWith",
+ "endsWith",
+ "isNull",
+ "isNotNull"
+ ]
+ },
+ "description": "Allowed filter operators"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Filtering capabilities"
+ },
+ "sorting": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "default": true,
+ "description": "Allow sorting"
+ },
+ "fields": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Sortable fields"
+ },
+ "defaultSort": {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string"
+ },
+ "direction": {
+ "type": "string",
+ "enum": [
+ "ASC",
+ "DESC"
+ ]
+ }
+ },
+ "required": [
+ "field",
+ "direction"
+ ],
+ "additionalProperties": false,
+ "description": "Default sort order"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Sorting capabilities"
+ },
+ "pagination": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "default": true,
+ "description": "Enable pagination"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "offset",
+ "cursor",
+ "relay"
+ ],
+ "default": "offset",
+ "description": "Pagination style"
+ },
+ "defaultLimit": {
+ "type": "integer",
+ "minimum": 1,
+ "default": 20,
+ "description": "Default page size"
+ },
+ "maxLimit": {
+ "type": "integer",
+ "minimum": 1,
+ "default": 100,
+ "description": "Maximum page size"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Pagination configuration"
+ },
+ "fields": {
+ "type": "object",
+ "properties": {
+ "required": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Required fields (always returned)"
+ },
+ "selectable": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Selectable fields"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Field selection configuration"
+ },
+ "authRequired": {
+ "type": "boolean",
+ "default": true,
+ "description": "Require authentication"
+ },
+ "permissions": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Required permissions"
+ },
+ "cache": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "default": false,
+ "description": "Enable caching"
+ },
+ "ttl": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Cache TTL in seconds"
+ },
+ "key": {
+ "type": "string",
+ "description": "Cache key template"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Query caching"
+ }
+ },
+ "required": [
+ "name",
+ "object",
+ "type"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/GraphQLQueryDepthLimit.json b/packages/spec/json-schema/api/GraphQLQueryDepthLimit.json
new file mode 100644
index 00000000000..f15b683e2ae
--- /dev/null
+++ b/packages/spec/json-schema/api/GraphQLQueryDepthLimit.json
@@ -0,0 +1,44 @@
+{
+ "$ref": "#/definitions/GraphQLQueryDepthLimit",
+ "definitions": {
+ "GraphQLQueryDepthLimit": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "default": true,
+ "description": "Enable query depth limiting"
+ },
+ "maxDepth": {
+ "type": "integer",
+ "minimum": 1,
+ "default": 10,
+ "description": "Maximum query depth"
+ },
+ "ignoreFields": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Fields excluded from depth calculation"
+ },
+ "onDepthExceeded": {
+ "type": "string",
+ "enum": [
+ "reject",
+ "log",
+ "warn"
+ ],
+ "default": "reject",
+ "description": "Action when depth exceeded"
+ },
+ "errorMessage": {
+ "type": "string",
+ "description": "Custom error message for depth violations"
+ }
+ },
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/GraphQLRateLimit.json b/packages/spec/json-schema/api/GraphQLRateLimit.json
new file mode 100644
index 00000000000..2c053e6227b
--- /dev/null
+++ b/packages/spec/json-schema/api/GraphQLRateLimit.json
@@ -0,0 +1,138 @@
+{
+ "$ref": "#/definitions/GraphQLRateLimit",
+ "definitions": {
+ "GraphQLRateLimit": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "default": true,
+ "description": "Enable rate limiting"
+ },
+ "strategy": {
+ "type": "string",
+ "enum": [
+ "token_bucket",
+ "fixed_window",
+ "sliding_window",
+ "cost_based"
+ ],
+ "default": "token_bucket",
+ "description": "Rate limiting strategy"
+ },
+ "global": {
+ "type": "object",
+ "properties": {
+ "maxRequests": {
+ "type": "integer",
+ "minimum": 1,
+ "default": 1000,
+ "description": "Maximum requests per window"
+ },
+ "windowMs": {
+ "type": "integer",
+ "minimum": 1000,
+ "default": 60000,
+ "description": "Time window in milliseconds"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Global rate limits"
+ },
+ "perUser": {
+ "type": "object",
+ "properties": {
+ "maxRequests": {
+ "type": "integer",
+ "minimum": 1,
+ "default": 100,
+ "description": "Maximum requests per user per window"
+ },
+ "windowMs": {
+ "type": "integer",
+ "minimum": 1000,
+ "default": 60000,
+ "description": "Time window in milliseconds"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Per-user rate limits"
+ },
+ "costBased": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "default": false,
+ "description": "Enable cost-based rate limiting"
+ },
+ "maxCost": {
+ "type": "integer",
+ "minimum": 1,
+ "default": 10000,
+ "description": "Maximum cost per window"
+ },
+ "windowMs": {
+ "type": "integer",
+ "minimum": 1000,
+ "default": 60000,
+ "description": "Time window in milliseconds"
+ },
+ "useComplexityAsCost": {
+ "type": "boolean",
+ "default": true,
+ "description": "Use query complexity as cost"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Cost-based rate limiting"
+ },
+ "operations": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "object",
+ "properties": {
+ "maxRequests": {
+ "type": "integer",
+ "minimum": 1,
+ "description": "Max requests for this operation"
+ },
+ "windowMs": {
+ "type": "integer",
+ "minimum": 1000,
+ "description": "Time window"
+ }
+ },
+ "required": [
+ "maxRequests",
+ "windowMs"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Per-operation rate limits"
+ },
+ "onLimitExceeded": {
+ "type": "string",
+ "enum": [
+ "reject",
+ "queue",
+ "log"
+ ],
+ "default": "reject",
+ "description": "Action when rate limit exceeded"
+ },
+ "errorMessage": {
+ "type": "string",
+ "description": "Custom error message for rate limit violations"
+ },
+ "includeHeaders": {
+ "type": "boolean",
+ "default": true,
+ "description": "Include rate limit headers in response"
+ }
+ },
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/GraphQLResolverConfig.json b/packages/spec/json-schema/api/GraphQLResolverConfig.json
new file mode 100644
index 00000000000..d0a411372ca
--- /dev/null
+++ b/packages/spec/json-schema/api/GraphQLResolverConfig.json
@@ -0,0 +1,98 @@
+{
+ "$ref": "#/definitions/GraphQLResolverConfig",
+ "definitions": {
+ "GraphQLResolverConfig": {
+ "type": "object",
+ "properties": {
+ "path": {
+ "type": "string",
+ "description": "Resolver path (Type.field)"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "datasource",
+ "computed",
+ "script",
+ "proxy"
+ ],
+ "description": "Resolver implementation type"
+ },
+ "implementation": {
+ "type": "object",
+ "properties": {
+ "datasource": {
+ "type": "string",
+ "description": "Datasource ID"
+ },
+ "query": {
+ "type": "string",
+ "description": "Query/SQL to execute"
+ },
+ "expression": {
+ "type": "string",
+ "description": "Computation expression"
+ },
+ "dependencies": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Dependent fields"
+ },
+ "script": {
+ "type": "string",
+ "description": "Script ID or inline code"
+ },
+ "url": {
+ "type": "string",
+ "description": "Proxy URL"
+ },
+ "method": {
+ "type": "string",
+ "enum": [
+ "GET",
+ "POST",
+ "PUT",
+ "DELETE"
+ ],
+ "description": "HTTP method"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Implementation configuration"
+ },
+ "cache": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "default": false,
+ "description": "Enable resolver caching"
+ },
+ "ttl": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Cache TTL in seconds"
+ },
+ "keyArgs": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Arguments to include in cache key"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Resolver caching"
+ }
+ },
+ "required": [
+ "path",
+ "type"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/GraphQLScalarType.json b/packages/spec/json-schema/api/GraphQLScalarType.json
new file mode 100644
index 00000000000..836231b7a42
--- /dev/null
+++ b/packages/spec/json-schema/api/GraphQLScalarType.json
@@ -0,0 +1,32 @@
+{
+ "$ref": "#/definitions/GraphQLScalarType",
+ "definitions": {
+ "GraphQLScalarType": {
+ "type": "string",
+ "enum": [
+ "ID",
+ "String",
+ "Int",
+ "Float",
+ "Boolean",
+ "DateTime",
+ "Date",
+ "Time",
+ "JSON",
+ "JSONObject",
+ "Upload",
+ "URL",
+ "Email",
+ "PhoneNumber",
+ "Currency",
+ "Decimal",
+ "BigInt",
+ "Long",
+ "UUID",
+ "Base64",
+ "Void"
+ ]
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/GraphQLSubscriptionConfig.json b/packages/spec/json-schema/api/GraphQLSubscriptionConfig.json
new file mode 100644
index 00000000000..4ded12bbfe7
--- /dev/null
+++ b/packages/spec/json-schema/api/GraphQLSubscriptionConfig.json
@@ -0,0 +1,118 @@
+{
+ "$ref": "#/definitions/GraphQLSubscriptionConfig",
+ "definitions": {
+ "GraphQLSubscriptionConfig": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "Subscription field name (camelCase recommended)"
+ },
+ "object": {
+ "type": "string",
+ "description": "Source ObjectQL object name"
+ },
+ "events": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "enum": [
+ "created",
+ "updated",
+ "deleted",
+ "custom"
+ ]
+ },
+ "description": "Events to subscribe to"
+ },
+ "description": {
+ "type": "string",
+ "description": "Subscription description"
+ },
+ "filter": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "default": true,
+ "description": "Allow filtering subscriptions"
+ },
+ "fields": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Filterable fields"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Subscription filtering"
+ },
+ "payload": {
+ "type": "object",
+ "properties": {
+ "includeEntity": {
+ "type": "boolean",
+ "default": true,
+ "description": "Include entity in payload"
+ },
+ "includePreviousValues": {
+ "type": "boolean",
+ "default": false,
+ "description": "Include previous field values"
+ },
+ "includeMeta": {
+ "type": "boolean",
+ "default": true,
+ "description": "Include metadata (timestamp, user, etc.)"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Payload configuration"
+ },
+ "authRequired": {
+ "type": "boolean",
+ "default": true,
+ "description": "Require authentication"
+ },
+ "permissions": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Required permissions"
+ },
+ "rateLimit": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "default": true,
+ "description": "Enable rate limiting"
+ },
+ "maxSubscriptionsPerUser": {
+ "type": "integer",
+ "minimum": 1,
+ "default": 10,
+ "description": "Max concurrent subscriptions per user"
+ },
+ "throttleMs": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Throttle interval in milliseconds"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Subscription rate limiting"
+ }
+ },
+ "required": [
+ "name",
+ "object",
+ "events"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/GraphQLTypeConfig.json b/packages/spec/json-schema/api/GraphQLTypeConfig.json
new file mode 100644
index 00000000000..f79f34e6611
--- /dev/null
+++ b/packages/spec/json-schema/api/GraphQLTypeConfig.json
@@ -0,0 +1,113 @@
+{
+ "$ref": "#/definitions/GraphQLTypeConfig",
+ "definitions": {
+ "GraphQLTypeConfig": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "GraphQL type name (PascalCase recommended)"
+ },
+ "object": {
+ "type": "string",
+ "description": "Source ObjectQL object name"
+ },
+ "description": {
+ "type": "string",
+ "description": "Type description"
+ },
+ "fields": {
+ "type": "object",
+ "properties": {
+ "include": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Fields to include"
+ },
+ "exclude": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Fields to exclude (e.g., sensitive fields)"
+ },
+ "mappings": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "object",
+ "properties": {
+ "graphqlName": {
+ "type": "string",
+ "description": "Custom GraphQL field name"
+ },
+ "graphqlType": {
+ "type": "string",
+ "description": "Override GraphQL type"
+ },
+ "description": {
+ "type": "string",
+ "description": "Field description"
+ },
+ "deprecationReason": {
+ "type": "string",
+ "description": "Why field is deprecated"
+ },
+ "nullable": {
+ "type": "boolean",
+ "description": "Override nullable"
+ }
+ },
+ "additionalProperties": false
+ },
+ "description": "Field-level customizations"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Field configuration"
+ },
+ "interfaces": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "GraphQL interface names"
+ },
+ "isInterface": {
+ "type": "boolean",
+ "default": false,
+ "description": "Define as GraphQL interface"
+ },
+ "directives": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "Directive name"
+ },
+ "args": {
+ "type": "object",
+ "additionalProperties": {},
+ "description": "Directive arguments"
+ }
+ },
+ "required": [
+ "name"
+ ],
+ "additionalProperties": false
+ },
+ "description": "GraphQL directives"
+ }
+ },
+ "required": [
+ "name",
+ "object"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/src/api/graphql.test.ts b/packages/spec/src/api/graphql.test.ts
new file mode 100644
index 00000000000..6566f2db29c
--- /dev/null
+++ b/packages/spec/src/api/graphql.test.ts
@@ -0,0 +1,962 @@
+import { describe, it, expect } from 'vitest';
+import {
+ GraphQLScalarType,
+ GraphQLTypeConfigSchema,
+ GraphQLQueryConfigSchema,
+ GraphQLMutationConfigSchema,
+ GraphQLSubscriptionConfigSchema,
+ GraphQLResolverConfigSchema,
+ GraphQLDataLoaderConfigSchema,
+ GraphQLDirectiveConfigSchema,
+ GraphQLDirectiveLocation,
+ GraphQLQueryDepthLimitSchema,
+ GraphQLQueryComplexitySchema,
+ GraphQLRateLimitSchema,
+ GraphQLPersistedQuerySchema,
+ GraphQLConfigSchema,
+ GraphQLConfig,
+ mapFieldTypeToGraphQL,
+} from './graphql.zod';
+
+describe('GraphQLScalarType', () => {
+ it('should accept built-in GraphQL scalar types', () => {
+ const builtInTypes = ['ID', 'String', 'Int', 'Float', 'Boolean'];
+
+ builtInTypes.forEach(type => {
+ expect(() => GraphQLScalarType.parse(type)).not.toThrow();
+ });
+ });
+
+ it('should accept extended scalar types', () => {
+ const extendedTypes = ['DateTime', 'Date', 'JSON', 'URL', 'Email', 'UUID'];
+
+ extendedTypes.forEach(type => {
+ expect(() => GraphQLScalarType.parse(type)).not.toThrow();
+ });
+ });
+
+ it('should reject invalid scalar types', () => {
+ expect(() => GraphQLScalarType.parse('CustomType')).toThrow();
+ expect(() => GraphQLScalarType.parse('NotAScalar')).toThrow();
+ });
+});
+
+describe('GraphQLTypeConfigSchema', () => {
+ it('should accept minimal type configuration', () => {
+ const config = GraphQLTypeConfigSchema.parse({
+ name: 'Customer',
+ object: 'customer',
+ });
+
+ expect(config.name).toBe('Customer');
+ expect(config.object).toBe('customer');
+ });
+
+ it('should accept type configuration with field mappings', () => {
+ const config = GraphQLTypeConfigSchema.parse({
+ name: 'Customer',
+ object: 'customer',
+ description: 'Customer type',
+ fields: {
+ include: ['id', 'name', 'email'],
+ exclude: ['password', 'ssn'],
+ mappings: {
+ email: {
+ graphqlName: 'emailAddress',
+ description: 'Customer email',
+ },
+ },
+ },
+ });
+
+ expect(config.fields?.include).toContain('email');
+ expect(config.fields?.exclude).toContain('password');
+ expect(config.fields?.mappings?.email?.graphqlName).toBe('emailAddress');
+ });
+
+ it('should accept type with interfaces', () => {
+ const config = GraphQLTypeConfigSchema.parse({
+ name: 'Customer',
+ object: 'customer',
+ interfaces: ['Node', 'Timestamped'],
+ });
+
+ expect(config.interfaces).toHaveLength(2);
+ });
+
+ it('should accept interface definition', () => {
+ const config = GraphQLTypeConfigSchema.parse({
+ name: 'Node',
+ object: 'base_entity',
+ isInterface: true,
+ });
+
+ expect(config.isInterface).toBe(true);
+ });
+
+ it('should accept type with directives', () => {
+ const config = GraphQLTypeConfigSchema.parse({
+ name: 'Customer',
+ object: 'customer',
+ directives: [
+ { name: 'auth', args: { requires: 'ADMIN' } },
+ { name: 'cache', args: { maxAge: 60 } },
+ ],
+ });
+
+ expect(config.directives).toHaveLength(2);
+ });
+});
+
+describe('GraphQLQueryConfigSchema', () => {
+ it('should accept minimal query configuration', () => {
+ const config = GraphQLQueryConfigSchema.parse({
+ name: 'customer',
+ object: 'customer',
+ type: 'get',
+ });
+
+ expect(config.name).toBe('customer');
+ expect(config.type).toBe('get');
+ });
+
+ it('should accept list query with filtering', () => {
+ const config = GraphQLQueryConfigSchema.parse({
+ name: 'customers',
+ object: 'customer',
+ type: 'list',
+ filtering: {
+ enabled: true,
+ fields: ['name', 'email', 'status'],
+ operators: ['eq', 'ne', 'contains', 'in'],
+ },
+ });
+
+ expect(config.filtering?.enabled).toBe(true);
+ expect(config.filtering?.fields).toContain('name');
+ });
+
+ it('should accept query with sorting', () => {
+ const config = GraphQLQueryConfigSchema.parse({
+ name: 'customers',
+ object: 'customer',
+ type: 'list',
+ sorting: {
+ enabled: true,
+ fields: ['name', 'createdAt'],
+ defaultSort: {
+ field: 'createdAt',
+ direction: 'DESC',
+ },
+ },
+ });
+
+ expect(config.sorting?.defaultSort?.direction).toBe('DESC');
+ });
+
+ it('should accept query with pagination', () => {
+ const config = GraphQLQueryConfigSchema.parse({
+ name: 'customers',
+ object: 'customer',
+ type: 'list',
+ pagination: {
+ enabled: true,
+ type: 'cursor',
+ defaultLimit: 50,
+ maxLimit: 200,
+ },
+ });
+
+ expect(config.pagination?.type).toBe('cursor');
+ expect(config.pagination?.maxLimit).toBe(200);
+ });
+
+ it('should accept different pagination types', () => {
+ const types: Array<'offset' | 'cursor' | 'relay'> = ['offset', 'cursor', 'relay'];
+
+ types.forEach(type => {
+ const config = GraphQLQueryConfigSchema.parse({
+ name: 'test',
+ object: 'test',
+ type: 'list',
+ pagination: { enabled: true, type },
+ });
+ expect(config.pagination?.type).toBe(type);
+ });
+ });
+
+ it('should accept query with arguments', () => {
+ const config = GraphQLQueryConfigSchema.parse({
+ name: 'customer',
+ object: 'customer',
+ type: 'get',
+ args: {
+ id: {
+ type: 'ID!',
+ description: 'Customer ID',
+ },
+ },
+ });
+
+ expect(config.args?.id?.type).toBe('ID!');
+ });
+
+ it('should accept query with caching', () => {
+ const config = GraphQLQueryConfigSchema.parse({
+ name: 'customer',
+ object: 'customer',
+ type: 'get',
+ cache: {
+ enabled: true,
+ ttl: 300,
+ key: 'customer:{id}',
+ },
+ });
+
+ expect(config.cache?.enabled).toBe(true);
+ expect(config.cache?.ttl).toBe(300);
+ });
+
+ it('should apply default authRequired', () => {
+ const config = GraphQLQueryConfigSchema.parse({
+ name: 'test',
+ object: 'test',
+ type: 'get',
+ });
+
+ expect(config.authRequired).toBe(true);
+ });
+});
+
+describe('GraphQLMutationConfigSchema', () => {
+ it('should accept minimal mutation configuration', () => {
+ const config = GraphQLMutationConfigSchema.parse({
+ name: 'createCustomer',
+ object: 'customer',
+ type: 'create',
+ });
+
+ expect(config.name).toBe('createCustomer');
+ expect(config.type).toBe('create');
+ });
+
+ it('should accept different mutation types', () => {
+ const types: Array<'create' | 'update' | 'delete' | 'upsert' | 'custom'> =
+ ['create', 'update', 'delete', 'upsert', 'custom'];
+
+ types.forEach(type => {
+ const config = GraphQLMutationConfigSchema.parse({
+ name: 'testMutation',
+ object: 'test',
+ type,
+ });
+ expect(config.type).toBe(type);
+ });
+ });
+
+ it('should accept mutation with input configuration', () => {
+ const config = GraphQLMutationConfigSchema.parse({
+ name: 'createCustomer',
+ object: 'customer',
+ type: 'create',
+ input: {
+ typeName: 'CreateCustomerInput',
+ fields: {
+ include: ['name', 'email', 'phone'],
+ exclude: ['id', 'createdAt'],
+ required: ['name', 'email'],
+ },
+ validation: {
+ enabled: true,
+ rules: ['email_format', 'phone_format'],
+ },
+ },
+ });
+
+ expect(config.input?.typeName).toBe('CreateCustomerInput');
+ expect(config.input?.fields?.required).toContain('email');
+ });
+
+ it('should accept mutation with output configuration', () => {
+ const config = GraphQLMutationConfigSchema.parse({
+ name: 'createCustomer',
+ object: 'customer',
+ type: 'create',
+ output: {
+ type: 'payload',
+ includeEnvelope: true,
+ },
+ });
+
+ expect(config.output?.type).toBe('payload');
+ expect(config.output?.includeEnvelope).toBe(true);
+ });
+
+ it('should accept mutation with transaction configuration', () => {
+ const config = GraphQLMutationConfigSchema.parse({
+ name: 'createOrder',
+ object: 'order',
+ type: 'create',
+ transaction: {
+ enabled: true,
+ isolationLevel: 'serializable',
+ },
+ });
+
+ expect(config.transaction?.enabled).toBe(true);
+ expect(config.transaction?.isolationLevel).toBe('serializable');
+ });
+
+ it('should accept mutation with hooks', () => {
+ const config = GraphQLMutationConfigSchema.parse({
+ name: 'createCustomer',
+ object: 'customer',
+ type: 'create',
+ hooks: {
+ before: ['validate_email', 'check_duplicates'],
+ after: ['send_welcome_email', 'create_audit_log'],
+ },
+ });
+
+ expect(config.hooks?.before).toHaveLength(2);
+ expect(config.hooks?.after).toContain('send_welcome_email');
+ });
+});
+
+describe('GraphQLSubscriptionConfigSchema', () => {
+ it('should accept minimal subscription configuration', () => {
+ const config = GraphQLSubscriptionConfigSchema.parse({
+ name: 'customerCreated',
+ object: 'customer',
+ events: ['created'],
+ });
+
+ expect(config.name).toBe('customerCreated');
+ expect(config.events).toContain('created');
+ });
+
+ it('should accept subscription with multiple events', () => {
+ const config = GraphQLSubscriptionConfigSchema.parse({
+ name: 'customerChanged',
+ object: 'customer',
+ events: ['created', 'updated', 'deleted'],
+ });
+
+ expect(config.events).toHaveLength(3);
+ });
+
+ it('should accept subscription with filtering', () => {
+ const config = GraphQLSubscriptionConfigSchema.parse({
+ name: 'customerUpdated',
+ object: 'customer',
+ events: ['updated'],
+ filter: {
+ enabled: true,
+ fields: ['status', 'priority'],
+ },
+ });
+
+ expect(config.filter?.enabled).toBe(true);
+ });
+
+ it('should accept subscription with payload configuration', () => {
+ const config = GraphQLSubscriptionConfigSchema.parse({
+ name: 'customerUpdated',
+ object: 'customer',
+ events: ['updated'],
+ payload: {
+ includeEntity: true,
+ includePreviousValues: true,
+ includeMeta: true,
+ },
+ });
+
+ expect(config.payload?.includePreviousValues).toBe(true);
+ });
+
+ it('should accept subscription with rate limiting', () => {
+ const config = GraphQLSubscriptionConfigSchema.parse({
+ name: 'customerUpdated',
+ object: 'customer',
+ events: ['updated'],
+ rateLimit: {
+ enabled: true,
+ maxSubscriptionsPerUser: 5,
+ throttleMs: 1000,
+ },
+ });
+
+ expect(config.rateLimit?.maxSubscriptionsPerUser).toBe(5);
+ });
+});
+
+describe('GraphQLResolverConfigSchema', () => {
+ it('should accept minimal resolver configuration', () => {
+ const config = GraphQLResolverConfigSchema.parse({
+ path: 'Query.customers',
+ type: 'datasource',
+ });
+
+ expect(config.path).toBe('Query.customers');
+ expect(config.type).toBe('datasource');
+ });
+
+ it('should accept different resolver types', () => {
+ const types: Array<'datasource' | 'computed' | 'script' | 'proxy'> =
+ ['datasource', 'computed', 'script', 'proxy'];
+
+ types.forEach(type => {
+ const config = GraphQLResolverConfigSchema.parse({
+ path: 'Query.test',
+ type,
+ });
+ expect(config.type).toBe(type);
+ });
+ });
+
+ it('should accept datasource resolver', () => {
+ const config = GraphQLResolverConfigSchema.parse({
+ path: 'Query.customers',
+ type: 'datasource',
+ implementation: {
+ datasource: 'external_db',
+ query: 'SELECT * FROM customers WHERE id = $1',
+ },
+ });
+
+ expect(config.implementation?.datasource).toBe('external_db');
+ });
+
+ it('should accept computed resolver', () => {
+ const config = GraphQLResolverConfigSchema.parse({
+ path: 'Customer.fullName',
+ type: 'computed',
+ implementation: {
+ expression: 'concat(firstName, " ", lastName)',
+ dependencies: ['firstName', 'lastName'],
+ },
+ });
+
+ expect(config.implementation?.dependencies).toContain('firstName');
+ });
+
+ it('should accept resolver with caching', () => {
+ const config = GraphQLResolverConfigSchema.parse({
+ path: 'Query.customer',
+ type: 'datasource',
+ cache: {
+ enabled: true,
+ ttl: 600,
+ keyArgs: ['id'],
+ },
+ });
+
+ expect(config.cache?.enabled).toBe(true);
+ expect(config.cache?.keyArgs).toContain('id');
+ });
+});
+
+describe('GraphQLDataLoaderConfigSchema', () => {
+ it('should accept minimal DataLoader configuration', () => {
+ const config = GraphQLDataLoaderConfigSchema.parse({
+ name: 'customerLoader',
+ source: 'customer',
+ batchFunction: {
+ type: 'findByIds',
+ keyField: 'id',
+ },
+ });
+
+ expect(config.name).toBe('customerLoader');
+ expect(config.batchFunction.type).toBe('findByIds');
+ });
+
+ it('should accept different batch function types', () => {
+ const types: Array<'findByIds' | 'query' | 'script' | 'custom'> =
+ ['findByIds', 'query', 'script', 'custom'];
+
+ types.forEach(type => {
+ const config = GraphQLDataLoaderConfigSchema.parse({
+ name: 'testLoader',
+ source: 'test',
+ batchFunction: { type },
+ });
+ expect(config.batchFunction.type).toBe(type);
+ });
+ });
+
+ it('should apply default maxBatchSize', () => {
+ const config = GraphQLDataLoaderConfigSchema.parse({
+ name: 'testLoader',
+ source: 'test',
+ batchFunction: {
+ type: 'findByIds',
+ },
+ });
+
+ expect(config.batchFunction.maxBatchSize).toBe(100);
+ });
+
+ it('should accept DataLoader with caching options', () => {
+ const config = GraphQLDataLoaderConfigSchema.parse({
+ name: 'customerLoader',
+ source: 'customer',
+ batchFunction: {
+ type: 'findByIds',
+ },
+ cache: {
+ enabled: true,
+ keyFn: 'customKeyFunction',
+ },
+ options: {
+ batch: true,
+ cache: true,
+ maxCacheSize: 1000,
+ },
+ });
+
+ expect(config.cache?.enabled).toBe(true);
+ expect(config.options?.maxCacheSize).toBe(1000);
+ });
+});
+
+describe('GraphQLDirectiveConfigSchema', () => {
+ it('should accept minimal directive configuration', () => {
+ const config = GraphQLDirectiveConfigSchema.parse({
+ name: 'auth',
+ locations: ['FIELD_DEFINITION'],
+ });
+
+ expect(config.name).toBe('auth');
+ expect(config.locations).toContain('FIELD_DEFINITION');
+ });
+
+ it('should accept directive with arguments', () => {
+ const config = GraphQLDirectiveConfigSchema.parse({
+ name: 'auth',
+ locations: ['FIELD_DEFINITION', 'OBJECT'],
+ args: {
+ requires: {
+ type: 'String!',
+ description: 'Required permission',
+ },
+ },
+ });
+
+ expect(config.args?.requires?.type).toBe('String!');
+ });
+
+ it('should accept repeatable directive', () => {
+ const config = GraphQLDirectiveConfigSchema.parse({
+ name: 'validate',
+ locations: ['FIELD_DEFINITION'],
+ repeatable: true,
+ });
+
+ expect(config.repeatable).toBe(true);
+ });
+
+ it('should validate directive name format', () => {
+ expect(() => GraphQLDirectiveConfigSchema.parse({
+ name: 'myDirective',
+ locations: ['FIELD'],
+ })).not.toThrow();
+
+ expect(() => GraphQLDirectiveConfigSchema.parse({
+ name: 'MyDirective',
+ locations: ['FIELD'],
+ })).toThrow();
+
+ expect(() => GraphQLDirectiveConfigSchema.parse({
+ name: 'my-directive',
+ locations: ['FIELD'],
+ })).toThrow();
+ });
+
+ it('should accept directive with implementation', () => {
+ const config = GraphQLDirectiveConfigSchema.parse({
+ name: 'cache',
+ locations: ['FIELD_DEFINITION'],
+ implementation: {
+ type: 'cache',
+ handler: 'cacheHandler',
+ },
+ });
+
+ expect(config.implementation?.type).toBe('cache');
+ });
+});
+
+describe('GraphQLQueryDepthLimitSchema', () => {
+ it('should apply default values', () => {
+ const config = GraphQLQueryDepthLimitSchema.parse({});
+
+ expect(config.enabled).toBe(true);
+ expect(config.maxDepth).toBe(10);
+ expect(config.onDepthExceeded).toBe('reject');
+ });
+
+ it('should accept custom depth limit', () => {
+ const config = GraphQLQueryDepthLimitSchema.parse({
+ enabled: true,
+ maxDepth: 5,
+ ignoreFields: ['__typename', 'id'],
+ onDepthExceeded: 'log',
+ errorMessage: 'Query too deep',
+ });
+
+ expect(config.maxDepth).toBe(5);
+ expect(config.ignoreFields).toContain('__typename');
+ expect(config.onDepthExceeded).toBe('log');
+ });
+});
+
+describe('GraphQLQueryComplexitySchema', () => {
+ it('should apply default values', () => {
+ const config = GraphQLQueryComplexitySchema.parse({});
+
+ expect(config.enabled).toBe(true);
+ expect(config.maxComplexity).toBe(1000);
+ expect(config.defaultFieldComplexity).toBe(1);
+ expect(config.listMultiplier).toBe(10);
+ });
+
+ it('should accept custom complexity configuration', () => {
+ const config = GraphQLQueryComplexitySchema.parse({
+ enabled: true,
+ maxComplexity: 5000,
+ defaultFieldComplexity: 2,
+ listMultiplier: 20,
+ });
+
+ expect(config.maxComplexity).toBe(5000);
+ expect(config.listMultiplier).toBe(20);
+ });
+
+ it('should accept field-specific complexity as number', () => {
+ const config = GraphQLQueryComplexitySchema.parse({
+ fieldComplexity: {
+ 'Query.customers': 10,
+ 'Customer.orders': 5,
+ },
+ });
+
+ expect(config.fieldComplexity?.['Query.customers']).toBe(10);
+ });
+
+ it('should accept field-specific complexity as object', () => {
+ const config = GraphQLQueryComplexitySchema.parse({
+ fieldComplexity: {
+ 'Query.customers': {
+ base: 10,
+ multiplier: 'limit',
+ calculator: 'customCalculator',
+ },
+ },
+ });
+
+ const complexityConfig = config.fieldComplexity?.['Query.customers'];
+ if (typeof complexityConfig === 'object' && 'base' in complexityConfig) {
+ expect(complexityConfig.base).toBe(10);
+ expect(complexityConfig.multiplier).toBe('limit');
+ }
+ });
+});
+
+describe('GraphQLRateLimitSchema', () => {
+ it('should apply default values', () => {
+ const config = GraphQLRateLimitSchema.parse({});
+
+ expect(config.enabled).toBe(true);
+ expect(config.strategy).toBe('token_bucket');
+ expect(config.onLimitExceeded).toBe('reject');
+ expect(config.includeHeaders).toBe(true);
+ });
+
+ it('should accept different strategies', () => {
+ const strategies: Array<'token_bucket' | 'fixed_window' | 'sliding_window' | 'cost_based'> =
+ ['token_bucket', 'fixed_window', 'sliding_window', 'cost_based'];
+
+ strategies.forEach(strategy => {
+ const config = GraphQLRateLimitSchema.parse({ strategy });
+ expect(config.strategy).toBe(strategy);
+ });
+ });
+
+ it('should accept global rate limits', () => {
+ const config = GraphQLRateLimitSchema.parse({
+ global: {
+ maxRequests: 5000,
+ windowMs: 3600000,
+ },
+ });
+
+ expect(config.global?.maxRequests).toBe(5000);
+ });
+
+ it('should accept per-user rate limits', () => {
+ const config = GraphQLRateLimitSchema.parse({
+ perUser: {
+ maxRequests: 100,
+ windowMs: 60000,
+ },
+ });
+
+ expect(config.perUser?.maxRequests).toBe(100);
+ });
+
+ it('should accept cost-based rate limiting', () => {
+ const config = GraphQLRateLimitSchema.parse({
+ costBased: {
+ enabled: true,
+ maxCost: 10000,
+ windowMs: 60000,
+ useComplexityAsCost: true,
+ },
+ });
+
+ expect(config.costBased?.enabled).toBe(true);
+ expect(config.costBased?.useComplexityAsCost).toBe(true);
+ });
+
+ it('should accept operation-specific limits', () => {
+ const config = GraphQLRateLimitSchema.parse({
+ operations: {
+ 'createOrder': {
+ maxRequests: 10,
+ windowMs: 60000,
+ },
+ 'deleteCustomer': {
+ maxRequests: 5,
+ windowMs: 60000,
+ },
+ },
+ });
+
+ expect(config.operations?.createOrder?.maxRequests).toBe(10);
+ });
+});
+
+describe('GraphQLPersistedQuerySchema', () => {
+ it('should apply default values', () => {
+ const config = GraphQLPersistedQuerySchema.parse({});
+
+ expect(config.enabled).toBe(false);
+ expect(config.mode).toBe('optional');
+ });
+
+ it('should accept different modes', () => {
+ const modes: Array<'optional' | 'required'> = ['optional', 'required'];
+
+ modes.forEach(mode => {
+ const config = GraphQLPersistedQuerySchema.parse({ mode });
+ expect(config.mode).toBe(mode);
+ });
+ });
+
+ it('should accept store configuration', () => {
+ const config = GraphQLPersistedQuerySchema.parse({
+ store: {
+ type: 'redis',
+ connection: 'redis://localhost:6379',
+ ttl: 86400,
+ },
+ });
+
+ expect(config.store.type).toBe('redis');
+ expect(config.store.ttl).toBe(86400);
+ });
+
+ it('should accept APQ configuration', () => {
+ const config = GraphQLPersistedQuerySchema.parse({
+ apq: {
+ enabled: true,
+ hashAlgorithm: 'sha256',
+ cache: {
+ ttl: 3600,
+ maxSize: 1000,
+ },
+ },
+ });
+
+ expect(config.apq?.enabled).toBe(true);
+ expect(config.apq?.hashAlgorithm).toBe('sha256');
+ });
+
+ it('should accept allowlist configuration', () => {
+ const config = GraphQLPersistedQuerySchema.parse({
+ allowlist: {
+ enabled: true,
+ queries: [
+ { id: 'query1', operation: 'GetCustomer' },
+ { id: 'query2', operation: 'ListOrders' },
+ ],
+ source: '/path/to/allowlist.json',
+ },
+ });
+
+ expect(config.allowlist?.enabled).toBe(true);
+ expect(config.allowlist?.queries).toHaveLength(2);
+ });
+
+ it('should accept security configuration', () => {
+ const config = GraphQLPersistedQuerySchema.parse({
+ security: {
+ maxQuerySize: 10000,
+ rejectIntrospection: true,
+ },
+ });
+
+ expect(config.security?.rejectIntrospection).toBe(true);
+ });
+});
+
+describe('GraphQLConfigSchema', () => {
+ it('should apply default values', () => {
+ const config = GraphQLConfigSchema.parse({});
+
+ expect(config.enabled).toBe(true);
+ expect(config.path).toBe('/graphql');
+ });
+
+ it('should accept complete GraphQL configuration', () => {
+ const config = GraphQLConfigSchema.parse({
+ enabled: true,
+ path: '/api/graphql',
+ playground: {
+ enabled: true,
+ path: '/graphql-playground',
+ },
+ schema: {
+ autoGenerateTypes: true,
+ types: [
+ {
+ name: 'Customer',
+ object: 'customer',
+ },
+ ],
+ queries: [
+ {
+ name: 'customers',
+ object: 'customer',
+ type: 'list',
+ },
+ ],
+ mutations: [
+ {
+ name: 'createCustomer',
+ object: 'customer',
+ type: 'create',
+ },
+ ],
+ },
+ security: {
+ depthLimit: {
+ enabled: true,
+ maxDepth: 10,
+ },
+ complexity: {
+ enabled: true,
+ maxComplexity: 1000,
+ },
+ rateLimit: {
+ enabled: true,
+ strategy: 'token_bucket',
+ },
+ persistedQueries: {
+ enabled: true,
+ mode: 'optional',
+ store: {
+ type: 'memory',
+ },
+ },
+ },
+ });
+
+ expect(config.path).toBe('/api/graphql');
+ expect(config.schema?.types).toHaveLength(1);
+ expect(config.security?.depthLimit?.enabled).toBe(true);
+ });
+
+ it('should use GraphQLConfig factory', () => {
+ const config = GraphQLConfig.create({
+ enabled: true,
+ path: '/graphql',
+ });
+
+ expect(config.enabled).toBe(true);
+ });
+});
+
+describe('mapFieldTypeToGraphQL', () => {
+ it('should map text types to String', () => {
+ expect(mapFieldTypeToGraphQL('text')).toBe('String');
+ expect(mapFieldTypeToGraphQL('textarea')).toBe('String');
+ expect(mapFieldTypeToGraphQL('markdown')).toBe('String');
+ });
+
+ it('should map email to Email scalar', () => {
+ expect(mapFieldTypeToGraphQL('email')).toBe('Email');
+ });
+
+ it('should map url to URL scalar', () => {
+ expect(mapFieldTypeToGraphQL('url')).toBe('URL');
+ });
+
+ it('should map number types to Float', () => {
+ expect(mapFieldTypeToGraphQL('number')).toBe('Float');
+ expect(mapFieldTypeToGraphQL('percent')).toBe('Float');
+ });
+
+ it('should map currency to Currency scalar', () => {
+ expect(mapFieldTypeToGraphQL('currency')).toBe('Currency');
+ });
+
+ it('should map date types correctly', () => {
+ expect(mapFieldTypeToGraphQL('date')).toBe('Date');
+ expect(mapFieldTypeToGraphQL('datetime')).toBe('DateTime');
+ expect(mapFieldTypeToGraphQL('time')).toBe('Time');
+ });
+
+ it('should map boolean types to Boolean', () => {
+ expect(mapFieldTypeToGraphQL('boolean')).toBe('Boolean');
+ expect(mapFieldTypeToGraphQL('toggle')).toBe('Boolean');
+ });
+
+ it('should map select to String', () => {
+ expect(mapFieldTypeToGraphQL('select')).toBe('String');
+ });
+
+ it('should map multiselect to [String]', () => {
+ expect(mapFieldTypeToGraphQL('multiselect')).toBe('[String]');
+ });
+
+ it('should map lookup types to ID', () => {
+ expect(mapFieldTypeToGraphQL('lookup')).toBe('ID');
+ expect(mapFieldTypeToGraphQL('master_detail')).toBe('ID');
+ });
+
+ it('should map media types to URL', () => {
+ expect(mapFieldTypeToGraphQL('image')).toBe('URL');
+ expect(mapFieldTypeToGraphQL('file')).toBe('URL');
+ expect(mapFieldTypeToGraphQL('avatar')).toBe('URL');
+ });
+
+ it('should map location and address to JSONObject', () => {
+ expect(mapFieldTypeToGraphQL('location')).toBe('JSONObject');
+ expect(mapFieldTypeToGraphQL('address')).toBe('JSONObject');
+ });
+
+ it('should map json to JSON scalar', () => {
+ expect(mapFieldTypeToGraphQL('json')).toBe('JSON');
+ });
+
+ it('should map vector to [Float]', () => {
+ expect(mapFieldTypeToGraphQL('vector')).toBe('[Float]');
+ });
+
+ it('should default to String for unknown types', () => {
+ // @ts-expect-error Testing unknown type
+ expect(mapFieldTypeToGraphQL('unknown_type')).toBe('String');
+ });
+});
diff --git a/packages/spec/src/api/graphql.zod.ts b/packages/spec/src/api/graphql.zod.ts
new file mode 100644
index 00000000000..757948373af
--- /dev/null
+++ b/packages/spec/src/api/graphql.zod.ts
@@ -0,0 +1,983 @@
+import { z } from 'zod';
+import { FieldType } from '../data/field.zod';
+
+/**
+ * GraphQL Protocol Support
+ *
+ * GraphQL is a query language for APIs and a runtime for executing those queries.
+ * It provides a complete and understandable description of the data in your API,
+ * gives clients the power to ask for exactly what they need, and enables powerful
+ * developer tools.
+ *
+ * ## Overview
+ *
+ * GraphQL provides:
+ * - Type-safe schema definition
+ * - Precise data fetching (no over/under-fetching)
+ * - Introspection and documentation
+ * - Real-time subscriptions
+ * - Batched queries with DataLoader
+ *
+ * ## Use Cases
+ *
+ * 1. **Modern API Development**
+ * - Mobile and web applications
+ * - Microservices federation
+ * - Real-time dashboards
+ *
+ * 2. **Data Aggregation**
+ * - Multi-source data integration
+ * - Complex nested queries
+ * - Efficient data loading
+ *
+ * 3. **Developer Experience**
+ * - Self-documenting API
+ * - Type safety and validation
+ * - GraphQL playground
+ *
+ * @see https://graphql.org/
+ * @see https://spec.graphql.org/
+ *
+ * @example GraphQL Query
+ * ```graphql
+ * query GetCustomer($id: ID!) {
+ * customer(id: $id) {
+ * id
+ * name
+ * email
+ * orders(limit: 10, status: "active") {
+ * id
+ * total
+ * items {
+ * product {
+ * name
+ * price
+ * }
+ * }
+ * }
+ * }
+ * }
+ * ```
+ *
+ * @example GraphQL Mutation
+ * ```graphql
+ * mutation CreateOrder($input: CreateOrderInput!) {
+ * createOrder(input: $input) {
+ * id
+ * orderNumber
+ * status
+ * }
+ * }
+ * ```
+ */
+
+// ==========================================
+// 1. GraphQL Type System
+// ==========================================
+
+/**
+ * GraphQL Scalar Types
+ *
+ * Built-in scalar types in GraphQL plus custom scalars.
+ */
+export const GraphQLScalarType = z.enum([
+ // Built-in GraphQL Scalars
+ 'ID',
+ 'String',
+ 'Int',
+ 'Float',
+ 'Boolean',
+
+ // Extended Scalars (common custom types)
+ 'DateTime',
+ 'Date',
+ 'Time',
+ 'JSON',
+ 'JSONObject',
+ 'Upload',
+ 'URL',
+ 'Email',
+ 'PhoneNumber',
+ 'Currency',
+ 'Decimal',
+ 'BigInt',
+ 'Long',
+ 'UUID',
+ 'Base64',
+ 'Void',
+]);
+
+export type GraphQLScalarType = z.infer;
+
+/**
+ * ObjectQL to GraphQL Type Mapping
+ *
+ * Maps ObjectQL field types to GraphQL scalar types.
+ * This enables automatic schema generation from Object definitions.
+ */
+export const FieldTypeToGraphQLMapping = z.object({
+ // Core Text -> String
+ text: z.literal('String').default('String'),
+ textarea: z.literal('String').default('String'),
+ email: z.literal('Email').default('Email'),
+ url: z.literal('URL').default('URL'),
+ phone: z.literal('PhoneNumber').default('PhoneNumber'),
+ password: z.literal('String').default('String'),
+
+ // Rich Content -> String
+ markdown: z.literal('String').default('String'),
+ html: z.literal('String').default('String'),
+ richtext: z.literal('String').default('String'),
+
+ // Numbers
+ number: z.literal('Float').default('Float'),
+ currency: z.literal('Currency').default('Currency'),
+ percent: z.literal('Float').default('Float'),
+
+ // Date & Time
+ date: z.literal('Date').default('Date'),
+ datetime: z.literal('DateTime').default('DateTime'),
+ time: z.literal('Time').default('Time'),
+
+ // Logic
+ boolean: z.literal('Boolean').default('Boolean'),
+ toggle: z.literal('Boolean').default('Boolean'),
+
+ // Selection -> String (enum values)
+ select: z.literal('String').default('String'),
+ multiselect: z.array(z.literal('String')).default(['String']),
+ radio: z.literal('String').default('String'),
+ checkboxes: z.array(z.literal('String')).default(['String']),
+
+ // Relational -> ID
+ lookup: z.literal('ID').default('ID'),
+ master_detail: z.literal('ID').default('ID'),
+ tree: z.literal('ID').default('ID'),
+
+ // Media -> String (URLs) or Upload
+ image: z.literal('URL').default('URL'),
+ file: z.literal('URL').default('URL'),
+ avatar: z.literal('URL').default('URL'),
+ video: z.literal('URL').default('URL'),
+ audio: z.literal('URL').default('URL'),
+
+ // Calculated
+ formula: z.literal('String').default('String'),
+ summary: z.literal('Float').default('Float'),
+ autonumber: z.literal('String').default('String'),
+
+ // Enhanced Types
+ location: z.literal('JSONObject').default('JSONObject'),
+ address: z.literal('JSONObject').default('JSONObject'),
+ code: z.literal('String').default('String'),
+ json: z.literal('JSON').default('JSON'),
+ color: z.literal('String').default('String'),
+ rating: z.literal('Float').default('Float'),
+ slider: z.literal('Float').default('Float'),
+ signature: z.literal('String').default('String'),
+ qrcode: z.literal('String').default('String'),
+ progress: z.literal('Float').default('Float'),
+ tags: z.array(z.literal('String')).default(['String']),
+
+ // AI/ML Types
+ vector: z.array(z.literal('Float')).default(['Float']),
+}).optional().describe('Field type to GraphQL type mapping configuration');
+
+export type FieldTypeToGraphQLMapping = z.infer;
+
+/**
+ * GraphQL Type Configuration
+ *
+ * Configuration for generating GraphQL types from Object definitions.
+ */
+export const GraphQLTypeConfigSchema = z.object({
+ /** Type name in GraphQL schema */
+ name: z.string().describe('GraphQL type name (PascalCase recommended)'),
+
+ /** Source Object name */
+ object: z.string().describe('Source ObjectQL object name'),
+
+ /** Description for GraphQL schema documentation */
+ description: z.string().optional().describe('Type description'),
+
+ /** Fields to include/exclude */
+ fields: z.object({
+ /** Include only these fields (whitelist) */
+ include: z.array(z.string()).optional().describe('Fields to include'),
+
+ /** Exclude these fields (blacklist) */
+ exclude: z.array(z.string()).optional().describe('Fields to exclude (e.g., sensitive fields)'),
+
+ /** Custom field mappings */
+ mappings: z.record(z.object({
+ graphqlName: z.string().optional().describe('Custom GraphQL field name'),
+ graphqlType: z.string().optional().describe('Override GraphQL type'),
+ description: z.string().optional().describe('Field description'),
+ deprecationReason: z.string().optional().describe('Why field is deprecated'),
+ nullable: z.boolean().optional().describe('Override nullable'),
+ })).optional().describe('Field-level customizations'),
+ }).optional().describe('Field configuration'),
+
+ /** Interfaces this type implements */
+ interfaces: z.array(z.string()).optional().describe('GraphQL interface names'),
+
+ /** Whether this is an interface definition */
+ isInterface: z.boolean().optional().default(false).describe('Define as GraphQL interface'),
+
+ /** Custom directives */
+ directives: z.array(z.object({
+ name: z.string().describe('Directive name'),
+ args: z.record(z.any()).optional().describe('Directive arguments'),
+ })).optional().describe('GraphQL directives'),
+});
+
+export type GraphQLTypeConfig = z.infer;
+
+// ==========================================
+// 2. Query Generation Configuration
+// ==========================================
+
+/**
+ * GraphQL Query Configuration
+ *
+ * Configuration for auto-generating query fields from Objects.
+ */
+export const GraphQLQueryConfigSchema = z.object({
+ /** Query name */
+ name: z.string().describe('Query field name (camelCase recommended)'),
+
+ /** Source Object */
+ object: z.string().describe('Source ObjectQL object name'),
+
+ /** Query type: single record or list */
+ type: z.enum(['get', 'list', 'search']).describe('Query type'),
+
+ /** Description */
+ description: z.string().optional().describe('Query description'),
+
+ /** Input arguments */
+ args: z.record(z.object({
+ type: z.string().describe('GraphQL type (e.g., "ID!", "String", "Int")'),
+ description: z.string().optional().describe('Argument description'),
+ defaultValue: z.any().optional().describe('Default value'),
+ })).optional().describe('Query arguments'),
+
+ /** Filtering configuration */
+ filtering: z.object({
+ enabled: z.boolean().default(true).describe('Allow filtering'),
+ fields: z.array(z.string()).optional().describe('Filterable fields'),
+ operators: z.array(z.enum([
+ 'eq', 'ne', 'gt', 'gte', 'lt', 'lte',
+ 'in', 'notIn', 'contains', 'startsWith', 'endsWith',
+ 'isNull', 'isNotNull',
+ ])).optional().describe('Allowed filter operators'),
+ }).optional().describe('Filtering capabilities'),
+
+ /** Sorting configuration */
+ sorting: z.object({
+ enabled: z.boolean().default(true).describe('Allow sorting'),
+ fields: z.array(z.string()).optional().describe('Sortable fields'),
+ defaultSort: z.object({
+ field: z.string(),
+ direction: z.enum(['ASC', 'DESC']),
+ }).optional().describe('Default sort order'),
+ }).optional().describe('Sorting capabilities'),
+
+ /** Pagination configuration */
+ pagination: z.object({
+ enabled: z.boolean().default(true).describe('Enable pagination'),
+ type: z.enum(['offset', 'cursor', 'relay']).default('offset').describe('Pagination style'),
+ defaultLimit: z.number().int().min(1).default(20).describe('Default page size'),
+ maxLimit: z.number().int().min(1).default(100).describe('Maximum page size'),
+ }).optional().describe('Pagination configuration'),
+
+ /** Field selection */
+ fields: z.object({
+ /** Always include these fields */
+ required: z.array(z.string()).optional().describe('Required fields (always returned)'),
+
+ /** Allow selecting these fields */
+ selectable: z.array(z.string()).optional().describe('Selectable fields'),
+ }).optional().describe('Field selection configuration'),
+
+ /** Authorization */
+ authRequired: z.boolean().default(true).describe('Require authentication'),
+ permissions: z.array(z.string()).optional().describe('Required permissions'),
+
+ /** Caching */
+ cache: z.object({
+ enabled: z.boolean().default(false).describe('Enable caching'),
+ ttl: z.number().int().min(0).optional().describe('Cache TTL in seconds'),
+ key: z.string().optional().describe('Cache key template'),
+ }).optional().describe('Query caching'),
+});
+
+export type GraphQLQueryConfig = z.infer;
+
+// ==========================================
+// 3. Mutation Generation Configuration
+// ==========================================
+
+/**
+ * GraphQL Mutation Configuration
+ *
+ * Configuration for auto-generating mutation fields from Objects.
+ */
+export const GraphQLMutationConfigSchema = z.object({
+ /** Mutation name */
+ name: z.string().describe('Mutation field name (camelCase recommended)'),
+
+ /** Source Object */
+ object: z.string().describe('Source ObjectQL object name'),
+
+ /** Mutation type */
+ type: z.enum(['create', 'update', 'delete', 'upsert', 'custom']).describe('Mutation type'),
+
+ /** Description */
+ description: z.string().optional().describe('Mutation description'),
+
+ /** Input type configuration */
+ input: z.object({
+ /** Input type name */
+ typeName: z.string().optional().describe('Custom input type name'),
+
+ /** Fields to include in input */
+ fields: z.object({
+ include: z.array(z.string()).optional().describe('Fields to include'),
+ exclude: z.array(z.string()).optional().describe('Fields to exclude'),
+ required: z.array(z.string()).optional().describe('Required input fields'),
+ }).optional().describe('Input field configuration'),
+
+ /** Validation */
+ validation: z.object({
+ enabled: z.boolean().default(true).describe('Enable input validation'),
+ rules: z.array(z.string()).optional().describe('Custom validation rules'),
+ }).optional().describe('Input validation'),
+ }).optional().describe('Input configuration'),
+
+ /** Return type configuration */
+ output: z.object({
+ /** Type of output */
+ type: z.enum(['object', 'payload', 'boolean', 'custom']).default('object').describe('Output type'),
+
+ /** Include success/error envelope */
+ includeEnvelope: z.boolean().optional().default(false).describe('Wrap in success/error payload'),
+
+ /** Custom output type */
+ customType: z.string().optional().describe('Custom output type name'),
+ }).optional().describe('Output configuration'),
+
+ /** Transaction handling */
+ transaction: z.object({
+ enabled: z.boolean().default(true).describe('Use database transaction'),
+ isolationLevel: z.enum(['read_uncommitted', 'read_committed', 'repeatable_read', 'serializable']).optional().describe('Transaction isolation level'),
+ }).optional().describe('Transaction configuration'),
+
+ /** Authorization */
+ authRequired: z.boolean().default(true).describe('Require authentication'),
+ permissions: z.array(z.string()).optional().describe('Required permissions'),
+
+ /** Hooks */
+ hooks: z.object({
+ before: z.array(z.string()).optional().describe('Pre-mutation hooks'),
+ after: z.array(z.string()).optional().describe('Post-mutation hooks'),
+ }).optional().describe('Lifecycle hooks'),
+});
+
+export type GraphQLMutationConfig = z.infer;
+
+// ==========================================
+// 4. Subscription Configuration
+// ==========================================
+
+/**
+ * GraphQL Subscription Configuration
+ *
+ * Configuration for real-time GraphQL subscriptions.
+ */
+export const GraphQLSubscriptionConfigSchema = z.object({
+ /** Subscription name */
+ name: z.string().describe('Subscription field name (camelCase recommended)'),
+
+ /** Source Object */
+ object: z.string().describe('Source ObjectQL object name'),
+
+ /** Subscription trigger events */
+ events: z.array(z.enum(['created', 'updated', 'deleted', 'custom'])).describe('Events to subscribe to'),
+
+ /** Description */
+ description: z.string().optional().describe('Subscription description'),
+
+ /** Filtering */
+ filter: z.object({
+ enabled: z.boolean().default(true).describe('Allow filtering subscriptions'),
+ fields: z.array(z.string()).optional().describe('Filterable fields'),
+ }).optional().describe('Subscription filtering'),
+
+ /** Payload configuration */
+ payload: z.object({
+ /** Include the modified entity */
+ includeEntity: z.boolean().default(true).describe('Include entity in payload'),
+
+ /** Include previous values (for updates) */
+ includePreviousValues: z.boolean().optional().default(false).describe('Include previous field values'),
+
+ /** Include mutation metadata */
+ includeMeta: z.boolean().optional().default(true).describe('Include metadata (timestamp, user, etc.)'),
+ }).optional().describe('Payload configuration'),
+
+ /** Authorization */
+ authRequired: z.boolean().default(true).describe('Require authentication'),
+ permissions: z.array(z.string()).optional().describe('Required permissions'),
+
+ /** Rate limiting for subscriptions */
+ rateLimit: z.object({
+ enabled: z.boolean().default(true).describe('Enable rate limiting'),
+ maxSubscriptionsPerUser: z.number().int().min(1).default(10).describe('Max concurrent subscriptions per user'),
+ throttleMs: z.number().int().min(0).optional().describe('Throttle interval in milliseconds'),
+ }).optional().describe('Subscription rate limiting'),
+});
+
+export type GraphQLSubscriptionConfig = z.infer;
+
+// ==========================================
+// 5. Resolver Configuration
+// ==========================================
+
+/**
+ * GraphQL Resolver Configuration
+ *
+ * Configuration for custom resolver logic.
+ */
+export const GraphQLResolverConfigSchema = z.object({
+ /** Field path (e.g., "Query.users", "Mutation.createUser") */
+ path: z.string().describe('Resolver path (Type.field)'),
+
+ /** Resolver implementation type */
+ type: z.enum(['datasource', 'computed', 'script', 'proxy']).describe('Resolver implementation type'),
+
+ /** Implementation details */
+ implementation: z.object({
+ /** For datasource type */
+ datasource: z.string().optional().describe('Datasource ID'),
+ query: z.string().optional().describe('Query/SQL to execute'),
+
+ /** For computed type */
+ expression: z.string().optional().describe('Computation expression'),
+ dependencies: z.array(z.string()).optional().describe('Dependent fields'),
+
+ /** For script type */
+ script: z.string().optional().describe('Script ID or inline code'),
+
+ /** For proxy type */
+ url: z.string().optional().describe('Proxy URL'),
+ method: z.enum(['GET', 'POST', 'PUT', 'DELETE']).optional().describe('HTTP method'),
+ }).optional().describe('Implementation configuration'),
+
+ /** Caching */
+ cache: z.object({
+ enabled: z.boolean().default(false).describe('Enable resolver caching'),
+ ttl: z.number().int().min(0).optional().describe('Cache TTL in seconds'),
+ keyArgs: z.array(z.string()).optional().describe('Arguments to include in cache key'),
+ }).optional().describe('Resolver caching'),
+});
+
+export type GraphQLResolverConfig = z.infer;
+
+// ==========================================
+// 6. DataLoader Configuration
+// ==========================================
+
+/**
+ * GraphQL DataLoader Configuration
+ *
+ * Configuration for batching and caching with DataLoader pattern.
+ * Prevents N+1 query problems in GraphQL.
+ */
+export const GraphQLDataLoaderConfigSchema = z.object({
+ /** Loader name */
+ name: z.string().describe('DataLoader name'),
+
+ /** Source Object or datasource */
+ source: z.string().describe('Source object or datasource'),
+
+ /** Batch function configuration */
+ batchFunction: z.object({
+ /** Type of batch operation */
+ type: z.enum(['findByIds', 'query', 'script', 'custom']).describe('Batch function type'),
+
+ /** For findByIds */
+ keyField: z.string().optional().describe('Field to batch on (e.g., "id")'),
+
+ /** For query */
+ query: z.string().optional().describe('Query template'),
+
+ /** For script */
+ script: z.string().optional().describe('Script ID'),
+
+ /** Maximum batch size */
+ maxBatchSize: z.number().int().min(1).optional().default(100).describe('Maximum batch size'),
+ }).describe('Batch function configuration'),
+
+ /** Caching */
+ cache: z.object({
+ enabled: z.boolean().default(true).describe('Enable per-request caching'),
+
+ /** Cache key function */
+ keyFn: z.string().optional().describe('Custom cache key function'),
+ }).optional().describe('DataLoader caching'),
+
+ /** Options */
+ options: z.object({
+ /** Batch multiple requests in single tick */
+ batch: z.boolean().default(true).describe('Enable batching'),
+
+ /** Cache loaded values */
+ cache: z.boolean().default(true).describe('Enable caching'),
+
+ /** Maximum cache size */
+ maxCacheSize: z.number().int().min(0).optional().describe('Max cache entries'),
+ }).optional().describe('DataLoader options'),
+});
+
+export type GraphQLDataLoaderConfig = z.infer;
+
+// ==========================================
+// 7. GraphQL Directive Schema
+// ==========================================
+
+/**
+ * GraphQL Directive Location
+ *
+ * Where a directive can be used in the schema.
+ */
+export const GraphQLDirectiveLocation = z.enum([
+ // Executable Directive Locations
+ 'QUERY',
+ 'MUTATION',
+ 'SUBSCRIPTION',
+ 'FIELD',
+ 'FRAGMENT_DEFINITION',
+ 'FRAGMENT_SPREAD',
+ 'INLINE_FRAGMENT',
+ 'VARIABLE_DEFINITION',
+
+ // Type System Directive Locations
+ 'SCHEMA',
+ 'SCALAR',
+ 'OBJECT',
+ 'FIELD_DEFINITION',
+ 'ARGUMENT_DEFINITION',
+ 'INTERFACE',
+ 'UNION',
+ 'ENUM',
+ 'ENUM_VALUE',
+ 'INPUT_OBJECT',
+ 'INPUT_FIELD_DEFINITION',
+]);
+
+export type GraphQLDirectiveLocation = z.infer;
+
+/**
+ * GraphQL Directive Configuration
+ *
+ * Custom directives for schema metadata and behavior.
+ */
+export const GraphQLDirectiveConfigSchema = z.object({
+ /** Directive name */
+ name: z.string().regex(/^[a-z][a-zA-Z0-9]*$/).describe('Directive name (camelCase)'),
+
+ /** Description */
+ description: z.string().optional().describe('Directive description'),
+
+ /** Where directive can be used */
+ locations: z.array(GraphQLDirectiveLocation).describe('Directive locations'),
+
+ /** Arguments */
+ args: z.record(z.object({
+ type: z.string().describe('Argument type'),
+ description: z.string().optional().describe('Argument description'),
+ defaultValue: z.any().optional().describe('Default value'),
+ })).optional().describe('Directive arguments'),
+
+ /** Is repeatable */
+ repeatable: z.boolean().optional().default(false).describe('Can be applied multiple times'),
+
+ /** Implementation */
+ implementation: z.object({
+ /** Directive behavior type */
+ type: z.enum(['auth', 'validation', 'transform', 'cache', 'deprecation', 'custom']).describe('Directive type'),
+
+ /** Handler function */
+ handler: z.string().optional().describe('Handler function name or script'),
+ }).optional().describe('Directive implementation'),
+});
+
+export type GraphQLDirectiveConfig = z.infer;
+
+// ==========================================
+// 8. GraphQL Security - Query Depth Limiting
+// ==========================================
+
+/**
+ * Query Depth Limiting Configuration
+ *
+ * Prevents deeply nested queries that could cause performance issues.
+ */
+export const GraphQLQueryDepthLimitSchema = z.object({
+ /** Enable depth limiting */
+ enabled: z.boolean().default(true).describe('Enable query depth limiting'),
+
+ /** Maximum allowed depth */
+ maxDepth: z.number().int().min(1).default(10).describe('Maximum query depth'),
+
+ /** Fields to ignore in depth calculation */
+ ignoreFields: z.array(z.string()).optional().describe('Fields excluded from depth calculation'),
+
+ /** Callback on depth exceeded */
+ onDepthExceeded: z.enum(['reject', 'log', 'warn']).default('reject').describe('Action when depth exceeded'),
+
+ /** Custom error message */
+ errorMessage: z.string().optional().describe('Custom error message for depth violations'),
+});
+
+export type GraphQLQueryDepthLimit = z.infer;
+
+// ==========================================
+// 9. GraphQL Security - Query Complexity
+// ==========================================
+
+/**
+ * Query Complexity Calculation Configuration
+ *
+ * Assigns complexity scores to fields and limits total query complexity.
+ * Prevents expensive queries from overloading the server.
+ */
+export const GraphQLQueryComplexitySchema = z.object({
+ /** Enable complexity limiting */
+ enabled: z.boolean().default(true).describe('Enable query complexity limiting'),
+
+ /** Maximum allowed complexity score */
+ maxComplexity: z.number().int().min(1).default(1000).describe('Maximum query complexity'),
+
+ /** Default field complexity */
+ defaultFieldComplexity: z.number().int().min(0).default(1).describe('Default complexity per field'),
+
+ /** Field-specific complexity scores */
+ fieldComplexity: z.record(z.union([
+ z.number().int().min(0),
+ z.object({
+ /** Base complexity */
+ base: z.number().int().min(0).describe('Base complexity'),
+
+ /** Multiplier based on arguments */
+ multiplier: z.string().optional().describe('Argument multiplier (e.g., "limit")'),
+
+ /** Custom complexity calculation */
+ calculator: z.string().optional().describe('Custom calculator function'),
+ }),
+ ])).optional().describe('Per-field complexity configuration'),
+
+ /** List multiplier */
+ listMultiplier: z.number().min(0).default(10).describe('Multiplier for list fields'),
+
+ /** Callback on complexity exceeded */
+ onComplexityExceeded: z.enum(['reject', 'log', 'warn']).default('reject').describe('Action when complexity exceeded'),
+
+ /** Custom error message */
+ errorMessage: z.string().optional().describe('Custom error message for complexity violations'),
+});
+
+export type GraphQLQueryComplexity = z.infer;
+
+// ==========================================
+// 10. GraphQL Security - Rate Limiting
+// ==========================================
+
+/**
+ * GraphQL Rate Limiting Configuration
+ *
+ * Rate limiting for GraphQL operations.
+ */
+export const GraphQLRateLimitSchema = z.object({
+ /** Enable rate limiting */
+ enabled: z.boolean().default(true).describe('Enable rate limiting'),
+
+ /** Rate limit strategy */
+ strategy: z.enum(['token_bucket', 'fixed_window', 'sliding_window', 'cost_based']).default('token_bucket').describe('Rate limiting strategy'),
+
+ /** Global rate limits */
+ global: z.object({
+ /** Requests per time window */
+ maxRequests: z.number().int().min(1).default(1000).describe('Maximum requests per window'),
+
+ /** Time window in milliseconds */
+ windowMs: z.number().int().min(1000).default(60000).describe('Time window in milliseconds'),
+ }).optional().describe('Global rate limits'),
+
+ /** Per-user rate limits */
+ perUser: z.object({
+ /** Requests per time window */
+ maxRequests: z.number().int().min(1).default(100).describe('Maximum requests per user per window'),
+
+ /** Time window in milliseconds */
+ windowMs: z.number().int().min(1000).default(60000).describe('Time window in milliseconds'),
+ }).optional().describe('Per-user rate limits'),
+
+ /** Cost-based rate limiting */
+ costBased: z.object({
+ /** Enable cost-based limiting */
+ enabled: z.boolean().default(false).describe('Enable cost-based rate limiting'),
+
+ /** Maximum cost per time window */
+ maxCost: z.number().int().min(1).default(10000).describe('Maximum cost per window'),
+
+ /** Time window in milliseconds */
+ windowMs: z.number().int().min(1000).default(60000).describe('Time window in milliseconds'),
+
+ /** Use complexity as cost */
+ useComplexityAsCost: z.boolean().default(true).describe('Use query complexity as cost'),
+ }).optional().describe('Cost-based rate limiting'),
+
+ /** Operation-specific limits */
+ operations: z.record(z.object({
+ maxRequests: z.number().int().min(1).describe('Max requests for this operation'),
+ windowMs: z.number().int().min(1000).describe('Time window'),
+ })).optional().describe('Per-operation rate limits'),
+
+ /** Callback on limit exceeded */
+ onLimitExceeded: z.enum(['reject', 'queue', 'log']).default('reject').describe('Action when rate limit exceeded'),
+
+ /** Custom error message */
+ errorMessage: z.string().optional().describe('Custom error message for rate limit violations'),
+
+ /** Headers to include in response */
+ includeHeaders: z.boolean().default(true).describe('Include rate limit headers in response'),
+});
+
+export type GraphQLRateLimit = z.infer;
+
+// ==========================================
+// 11. GraphQL Security - Persisted Queries
+// ==========================================
+
+/**
+ * Persisted Queries Configuration
+ *
+ * Only allow pre-registered queries to execute (whitelist approach).
+ * Improves security and performance.
+ */
+export const GraphQLPersistedQuerySchema = z.object({
+ /** Enable persisted queries */
+ enabled: z.boolean().default(false).describe('Enable persisted queries'),
+
+ /** Enforcement mode */
+ mode: z.enum(['optional', 'required']).default('optional').describe('Persisted query mode (optional: allow both, required: only persisted)'),
+
+ /** Query store configuration */
+ store: z.object({
+ /** Store type */
+ type: z.enum(['memory', 'redis', 'database', 'file']).default('memory').describe('Query store type'),
+
+ /** Store connection string */
+ connection: z.string().optional().describe('Store connection string or path'),
+
+ /** TTL for cached queries */
+ ttl: z.number().int().min(0).optional().describe('TTL in seconds for stored queries'),
+ }).optional().describe('Query store configuration'),
+
+ /** Automatic Persisted Queries (APQ) */
+ apq: z.object({
+ /** Enable APQ */
+ enabled: z.boolean().default(true).describe('Enable Automatic Persisted Queries'),
+
+ /** Hash algorithm */
+ hashAlgorithm: z.enum(['sha256', 'sha1', 'md5']).default('sha256').describe('Hash algorithm for query IDs'),
+
+ /** Cache control */
+ cache: z.object({
+ /** Cache TTL */
+ ttl: z.number().int().min(0).default(3600).describe('Cache TTL in seconds'),
+
+ /** Max cache size */
+ maxSize: z.number().int().min(1).optional().describe('Maximum number of cached queries'),
+ }).optional().describe('APQ cache configuration'),
+ }).optional().describe('Automatic Persisted Queries configuration'),
+
+ /** Query allowlist */
+ allowlist: z.object({
+ /** Enable allowlist mode */
+ enabled: z.boolean().default(false).describe('Enable query allowlist (reject queries not in list)'),
+
+ /** Allowed query IDs */
+ queries: z.array(z.object({
+ id: z.string().describe('Query ID or hash'),
+ operation: z.string().optional().describe('Operation name'),
+ query: z.string().optional().describe('Query string'),
+ })).optional().describe('Allowed queries'),
+
+ /** External allowlist source */
+ source: z.string().optional().describe('External allowlist source (file path or URL)'),
+ }).optional().describe('Query allowlist configuration'),
+
+ /** Security */
+ security: z.object({
+ /** Maximum query size */
+ maxQuerySize: z.number().int().min(1).optional().describe('Maximum query string size in bytes'),
+
+ /** Reject introspection in production */
+ rejectIntrospection: z.boolean().default(false).describe('Reject introspection queries'),
+ }).optional().describe('Security configuration'),
+});
+
+export type GraphQLPersistedQuery = z.infer;
+
+// ==========================================
+// 12. Complete GraphQL Configuration
+// ==========================================
+
+/**
+ * Complete GraphQL Configuration
+ *
+ * Root configuration for GraphQL API generation and security.
+ */
+export const GraphQLConfigSchema = z.object({
+ /** Enable GraphQL API */
+ enabled: z.boolean().default(true).describe('Enable GraphQL API'),
+
+ /** GraphQL endpoint path */
+ path: z.string().default('/graphql').describe('GraphQL endpoint path'),
+
+ /** GraphQL Playground */
+ playground: z.object({
+ enabled: z.boolean().default(true).describe('Enable GraphQL Playground'),
+ path: z.string().default('/playground').describe('Playground path'),
+ }).optional().describe('GraphQL Playground configuration'),
+
+ /** Schema generation */
+ schema: z.object({
+ /** Auto-generate types from Objects */
+ autoGenerateTypes: z.boolean().default(true).describe('Auto-generate types from Objects'),
+
+ /** Type configurations */
+ types: z.array(GraphQLTypeConfigSchema).optional().describe('Type configurations'),
+
+ /** Query configurations */
+ queries: z.array(GraphQLQueryConfigSchema).optional().describe('Query configurations'),
+
+ /** Mutation configurations */
+ mutations: z.array(GraphQLMutationConfigSchema).optional().describe('Mutation configurations'),
+
+ /** Subscription configurations */
+ subscriptions: z.array(GraphQLSubscriptionConfigSchema).optional().describe('Subscription configurations'),
+
+ /** Custom resolvers */
+ resolvers: z.array(GraphQLResolverConfigSchema).optional().describe('Custom resolver configurations'),
+
+ /** Custom directives */
+ directives: z.array(GraphQLDirectiveConfigSchema).optional().describe('Custom directive configurations'),
+ }).optional().describe('Schema generation configuration'),
+
+ /** DataLoader configurations */
+ dataLoaders: z.array(GraphQLDataLoaderConfigSchema).optional().describe('DataLoader configurations'),
+
+ /** Security configuration */
+ security: z.object({
+ /** Query depth limiting */
+ depthLimit: GraphQLQueryDepthLimitSchema.optional().describe('Query depth limiting'),
+
+ /** Query complexity */
+ complexity: GraphQLQueryComplexitySchema.optional().describe('Query complexity calculation'),
+
+ /** Rate limiting */
+ rateLimit: GraphQLRateLimitSchema.optional().describe('Rate limiting'),
+
+ /** Persisted queries */
+ persistedQueries: GraphQLPersistedQuerySchema.optional().describe('Persisted queries'),
+ }).optional().describe('Security configuration'),
+});
+
+export const GraphQLConfig = Object.assign(GraphQLConfigSchema, {
+ create: >(config: T) => config,
+});
+
+export type GraphQLConfig = z.infer;
+
+// ==========================================
+// Helper Functions
+// ==========================================
+
+/**
+ * Helper to map ObjectQL field type to GraphQL scalar type
+ */
+export const mapFieldTypeToGraphQL = (fieldType: z.infer): string => {
+ const mapping: Record = {
+ // Core Text
+ 'text': 'String',
+ 'textarea': 'String',
+ 'email': 'Email',
+ 'url': 'URL',
+ 'phone': 'PhoneNumber',
+ 'password': 'String',
+
+ // Rich Content
+ 'markdown': 'String',
+ 'html': 'String',
+ 'richtext': 'String',
+
+ // Numbers
+ 'number': 'Float',
+ 'currency': 'Currency',
+ 'percent': 'Float',
+
+ // Date & Time
+ 'date': 'Date',
+ 'datetime': 'DateTime',
+ 'time': 'Time',
+
+ // Logic
+ 'boolean': 'Boolean',
+ 'toggle': 'Boolean',
+
+ // Selection
+ 'select': 'String',
+ 'multiselect': '[String]',
+ 'radio': 'String',
+ 'checkboxes': '[String]',
+
+ // Relational
+ 'lookup': 'ID',
+ 'master_detail': 'ID',
+ 'tree': 'ID',
+
+ // Media
+ 'image': 'URL',
+ 'file': 'URL',
+ 'avatar': 'URL',
+ 'video': 'URL',
+ 'audio': 'URL',
+
+ // Calculated
+ 'formula': 'String',
+ 'summary': 'Float',
+ 'autonumber': 'String',
+
+ // Enhanced Types
+ 'location': 'JSONObject',
+ 'address': 'JSONObject',
+ 'code': 'String',
+ 'json': 'JSON',
+ 'color': 'String',
+ 'rating': 'Float',
+ 'slider': 'Float',
+ 'signature': 'String',
+ 'qrcode': 'String',
+ 'progress': 'Float',
+ 'tags': '[String]',
+
+ // AI/ML
+ 'vector': '[Float]',
+ };
+
+ return mapping[fieldType] || 'String';
+};
diff --git a/packages/spec/src/api/index.ts b/packages/spec/src/api/index.ts
index 5ddaad289e8..3b754fe9bd3 100644
--- a/packages/spec/src/api/index.ts
+++ b/packages/spec/src/api/index.ts
@@ -13,6 +13,7 @@ export * from './discovery.zod';
export * from './realtime.zod';
export * from './router.zod';
export * from './odata.zod';
+export * from './graphql.zod';
export * from './protocol';
From 5530d3b29bddfc176b2762370a9f29b285640644 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 30 Jan 2026 01:19:36 +0000
Subject: [PATCH 3/4] Remove redundant FieldTypeToGraphQLMapping schema, keep
helper function
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
---
content/docs/references/api/graphql.mdx | 12 +-
.../api/FieldTypeToGraphQLMapping.json | 260 ------------------
packages/spec/src/api/graphql.zod.ts | 76 -----
3 files changed, 3 insertions(+), 345 deletions(-)
delete mode 100644 packages/spec/json-schema/api/FieldTypeToGraphQLMapping.json
diff --git a/content/docs/references/api/graphql.mdx b/content/docs/references/api/graphql.mdx
index 3ccf4394cc0..19158ed1919 100644
--- a/content/docs/references/api/graphql.mdx
+++ b/content/docs/references/api/graphql.mdx
@@ -12,21 +12,15 @@ description: Graphql protocol schemas
## TypeScript Usage
```typescript
-import { FieldTypeToGraphQLMappingSchema, GraphQLConfigSchema, GraphQLDataLoaderConfigSchema, GraphQLDirectiveConfigSchema, GraphQLDirectiveLocationSchema, GraphQLMutationConfigSchema, GraphQLPersistedQuerySchema, GraphQLQueryComplexitySchema, GraphQLQueryConfigSchema, GraphQLQueryDepthLimitSchema, GraphQLRateLimitSchema, GraphQLResolverConfigSchema, GraphQLScalarTypeSchema, GraphQLSubscriptionConfigSchema, GraphQLTypeConfigSchema } from '@objectstack/spec/api';
-import type { FieldTypeToGraphQLMapping, GraphQLConfig, GraphQLDataLoaderConfig, GraphQLDirectiveConfig, GraphQLDirectiveLocation, GraphQLMutationConfig, GraphQLPersistedQuery, GraphQLQueryComplexity, GraphQLQueryConfig, GraphQLQueryDepthLimit, GraphQLRateLimit, GraphQLResolverConfig, GraphQLScalarType, GraphQLSubscriptionConfig, GraphQLTypeConfig } from '@objectstack/spec/api';
+import { GraphQLConfigSchema, GraphQLDataLoaderConfigSchema, GraphQLDirectiveConfigSchema, GraphQLDirectiveLocationSchema, GraphQLMutationConfigSchema, GraphQLPersistedQuerySchema, GraphQLQueryComplexitySchema, GraphQLQueryConfigSchema, GraphQLQueryDepthLimitSchema, GraphQLRateLimitSchema, GraphQLResolverConfigSchema, GraphQLScalarTypeSchema, GraphQLSubscriptionConfigSchema, GraphQLTypeConfigSchema } from '@objectstack/spec/api';
+import type { GraphQLConfig, GraphQLDataLoaderConfig, GraphQLDirectiveConfig, GraphQLDirectiveLocation, GraphQLMutationConfig, GraphQLPersistedQuery, GraphQLQueryComplexity, GraphQLQueryConfig, GraphQLQueryDepthLimit, GraphQLRateLimit, GraphQLResolverConfig, GraphQLScalarType, GraphQLSubscriptionConfig, GraphQLTypeConfig } from '@objectstack/spec/api';
// Validate data
-const result = FieldTypeToGraphQLMappingSchema.parse(data);
+const result = GraphQLConfigSchema.parse(data);
```
---
-## FieldTypeToGraphQLMapping
-
-Field type to GraphQL type mapping configuration
-
----
-
## GraphQLConfig
### Properties
diff --git a/packages/spec/json-schema/api/FieldTypeToGraphQLMapping.json b/packages/spec/json-schema/api/FieldTypeToGraphQLMapping.json
deleted file mode 100644
index 5d689285a08..00000000000
--- a/packages/spec/json-schema/api/FieldTypeToGraphQLMapping.json
+++ /dev/null
@@ -1,260 +0,0 @@
-{
- "$ref": "#/definitions/FieldTypeToGraphQLMapping",
- "definitions": {
- "FieldTypeToGraphQLMapping": {
- "anyOf": [
- {
- "not": {}
- },
- {
- "type": "object",
- "properties": {
- "text": {
- "type": "string",
- "const": "String",
- "default": "String"
- },
- "textarea": {
- "type": "string",
- "const": "String",
- "default": "String"
- },
- "email": {
- "type": "string",
- "const": "Email",
- "default": "Email"
- },
- "url": {
- "type": "string",
- "const": "URL",
- "default": "URL"
- },
- "phone": {
- "type": "string",
- "const": "PhoneNumber",
- "default": "PhoneNumber"
- },
- "password": {
- "type": "string",
- "const": "String",
- "default": "String"
- },
- "markdown": {
- "type": "string",
- "const": "String",
- "default": "String"
- },
- "html": {
- "type": "string",
- "const": "String",
- "default": "String"
- },
- "richtext": {
- "type": "string",
- "const": "String",
- "default": "String"
- },
- "number": {
- "type": "string",
- "const": "Float",
- "default": "Float"
- },
- "currency": {
- "type": "string",
- "const": "Currency",
- "default": "Currency"
- },
- "percent": {
- "type": "string",
- "const": "Float",
- "default": "Float"
- },
- "date": {
- "type": "string",
- "const": "Date",
- "default": "Date"
- },
- "datetime": {
- "type": "string",
- "const": "DateTime",
- "default": "DateTime"
- },
- "time": {
- "type": "string",
- "const": "Time",
- "default": "Time"
- },
- "boolean": {
- "type": "string",
- "const": "Boolean",
- "default": "Boolean"
- },
- "toggle": {
- "type": "string",
- "const": "Boolean",
- "default": "Boolean"
- },
- "select": {
- "type": "string",
- "const": "String",
- "default": "String"
- },
- "multiselect": {
- "type": "array",
- "items": {
- "type": "string",
- "const": "String"
- },
- "default": [
- "String"
- ]
- },
- "radio": {
- "type": "string",
- "const": "String",
- "default": "String"
- },
- "checkboxes": {
- "type": "array",
- "items": {
- "type": "string",
- "const": "String"
- },
- "default": [
- "String"
- ]
- },
- "lookup": {
- "type": "string",
- "const": "ID",
- "default": "ID"
- },
- "master_detail": {
- "type": "string",
- "const": "ID",
- "default": "ID"
- },
- "tree": {
- "type": "string",
- "const": "ID",
- "default": "ID"
- },
- "image": {
- "type": "string",
- "const": "URL",
- "default": "URL"
- },
- "file": {
- "type": "string",
- "const": "URL",
- "default": "URL"
- },
- "avatar": {
- "type": "string",
- "const": "URL",
- "default": "URL"
- },
- "video": {
- "type": "string",
- "const": "URL",
- "default": "URL"
- },
- "audio": {
- "type": "string",
- "const": "URL",
- "default": "URL"
- },
- "formula": {
- "type": "string",
- "const": "String",
- "default": "String"
- },
- "summary": {
- "type": "string",
- "const": "Float",
- "default": "Float"
- },
- "autonumber": {
- "type": "string",
- "const": "String",
- "default": "String"
- },
- "location": {
- "type": "string",
- "const": "JSONObject",
- "default": "JSONObject"
- },
- "address": {
- "type": "string",
- "const": "JSONObject",
- "default": "JSONObject"
- },
- "code": {
- "type": "string",
- "const": "String",
- "default": "String"
- },
- "json": {
- "type": "string",
- "const": "JSON",
- "default": "JSON"
- },
- "color": {
- "type": "string",
- "const": "String",
- "default": "String"
- },
- "rating": {
- "type": "string",
- "const": "Float",
- "default": "Float"
- },
- "slider": {
- "type": "string",
- "const": "Float",
- "default": "Float"
- },
- "signature": {
- "type": "string",
- "const": "String",
- "default": "String"
- },
- "qrcode": {
- "type": "string",
- "const": "String",
- "default": "String"
- },
- "progress": {
- "type": "string",
- "const": "Float",
- "default": "Float"
- },
- "tags": {
- "type": "array",
- "items": {
- "type": "string",
- "const": "String"
- },
- "default": [
- "String"
- ]
- },
- "vector": {
- "type": "array",
- "items": {
- "type": "string",
- "const": "Float"
- },
- "default": [
- "Float"
- ]
- }
- },
- "additionalProperties": false
- }
- ],
- "description": "Field type to GraphQL type mapping configuration"
- }
- },
- "$schema": "http://json-schema.org/draft-07/schema#"
-}
\ No newline at end of file
diff --git a/packages/spec/src/api/graphql.zod.ts b/packages/spec/src/api/graphql.zod.ts
index 757948373af..edcb374d929 100644
--- a/packages/spec/src/api/graphql.zod.ts
+++ b/packages/spec/src/api/graphql.zod.ts
@@ -109,82 +109,6 @@ export const GraphQLScalarType = z.enum([
export type GraphQLScalarType = z.infer;
-/**
- * ObjectQL to GraphQL Type Mapping
- *
- * Maps ObjectQL field types to GraphQL scalar types.
- * This enables automatic schema generation from Object definitions.
- */
-export const FieldTypeToGraphQLMapping = z.object({
- // Core Text -> String
- text: z.literal('String').default('String'),
- textarea: z.literal('String').default('String'),
- email: z.literal('Email').default('Email'),
- url: z.literal('URL').default('URL'),
- phone: z.literal('PhoneNumber').default('PhoneNumber'),
- password: z.literal('String').default('String'),
-
- // Rich Content -> String
- markdown: z.literal('String').default('String'),
- html: z.literal('String').default('String'),
- richtext: z.literal('String').default('String'),
-
- // Numbers
- number: z.literal('Float').default('Float'),
- currency: z.literal('Currency').default('Currency'),
- percent: z.literal('Float').default('Float'),
-
- // Date & Time
- date: z.literal('Date').default('Date'),
- datetime: z.literal('DateTime').default('DateTime'),
- time: z.literal('Time').default('Time'),
-
- // Logic
- boolean: z.literal('Boolean').default('Boolean'),
- toggle: z.literal('Boolean').default('Boolean'),
-
- // Selection -> String (enum values)
- select: z.literal('String').default('String'),
- multiselect: z.array(z.literal('String')).default(['String']),
- radio: z.literal('String').default('String'),
- checkboxes: z.array(z.literal('String')).default(['String']),
-
- // Relational -> ID
- lookup: z.literal('ID').default('ID'),
- master_detail: z.literal('ID').default('ID'),
- tree: z.literal('ID').default('ID'),
-
- // Media -> String (URLs) or Upload
- image: z.literal('URL').default('URL'),
- file: z.literal('URL').default('URL'),
- avatar: z.literal('URL').default('URL'),
- video: z.literal('URL').default('URL'),
- audio: z.literal('URL').default('URL'),
-
- // Calculated
- formula: z.literal('String').default('String'),
- summary: z.literal('Float').default('Float'),
- autonumber: z.literal('String').default('String'),
-
- // Enhanced Types
- location: z.literal('JSONObject').default('JSONObject'),
- address: z.literal('JSONObject').default('JSONObject'),
- code: z.literal('String').default('String'),
- json: z.literal('JSON').default('JSON'),
- color: z.literal('String').default('String'),
- rating: z.literal('Float').default('Float'),
- slider: z.literal('Float').default('Float'),
- signature: z.literal('String').default('String'),
- qrcode: z.literal('String').default('String'),
- progress: z.literal('Float').default('Float'),
- tags: z.array(z.literal('String')).default(['String']),
-
- // AI/ML Types
- vector: z.array(z.literal('Float')).default(['Float']),
-}).optional().describe('Field type to GraphQL type mapping configuration');
-
-export type FieldTypeToGraphQLMapping = z.infer;
-
/**
* GraphQL Type Configuration
*
From e3594081c0f3cc7a9beade00fbe4b478984a3846 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 30 Jan 2026 01:22:23 +0000
Subject: [PATCH 4/4] Use inclusive terminology: replace whitelist/blacklist
with allow list/deny list
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
---
content/docs/references/api/graphql.mdx | 2 +-
.../spec/json-schema/api/GraphQLConfig.json | 6 +++---
.../json-schema/api/GraphQLPersistedQuery.json | 6 +++---
packages/spec/src/api/graphql.zod.ts | 18 +++++++++---------
4 files changed, 16 insertions(+), 16 deletions(-)
diff --git a/content/docs/references/api/graphql.mdx b/content/docs/references/api/graphql.mdx
index 19158ed1919..9b0bd77a5e4 100644
--- a/content/docs/references/api/graphql.mdx
+++ b/content/docs/references/api/graphql.mdx
@@ -120,7 +120,7 @@ const result = GraphQLConfigSchema.parse(data);
| **mode** | `Enum<'optional' \| 'required'>` | optional | Persisted query mode (optional: allow both, required: only persisted) |
| **store** | `object` | optional | Query store configuration |
| **apq** | `object` | optional | Automatic Persisted Queries configuration |
-| **allowlist** | `object` | optional | Query allowlist configuration |
+| **allowlist** | `object` | optional | Query allow list configuration |
| **security** | `object` | optional | Security configuration |
---
diff --git a/packages/spec/json-schema/api/GraphQLConfig.json b/packages/spec/json-schema/api/GraphQLConfig.json
index c8b6a08fc8b..979bb487473 100644
--- a/packages/spec/json-schema/api/GraphQLConfig.json
+++ b/packages/spec/json-schema/api/GraphQLConfig.json
@@ -1321,7 +1321,7 @@
"enabled": {
"type": "boolean",
"default": false,
- "description": "Enable query allowlist (reject queries not in list)"
+ "description": "Enable query allow list (reject queries not in list)"
},
"queries": {
"type": "array",
@@ -1350,11 +1350,11 @@
},
"source": {
"type": "string",
- "description": "External allowlist source (file path or URL)"
+ "description": "External allow list source (file path or URL)"
}
},
"additionalProperties": false,
- "description": "Query allowlist configuration"
+ "description": "Query allow list configuration"
},
"security": {
"type": "object",
diff --git a/packages/spec/json-schema/api/GraphQLPersistedQuery.json b/packages/spec/json-schema/api/GraphQLPersistedQuery.json
index df100c33537..cbfe6062c7d 100644
--- a/packages/spec/json-schema/api/GraphQLPersistedQuery.json
+++ b/packages/spec/json-schema/api/GraphQLPersistedQuery.json
@@ -91,7 +91,7 @@
"enabled": {
"type": "boolean",
"default": false,
- "description": "Enable query allowlist (reject queries not in list)"
+ "description": "Enable query allow list (reject queries not in list)"
},
"queries": {
"type": "array",
@@ -120,11 +120,11 @@
},
"source": {
"type": "string",
- "description": "External allowlist source (file path or URL)"
+ "description": "External allow list source (file path or URL)"
}
},
"additionalProperties": false,
- "description": "Query allowlist configuration"
+ "description": "Query allow list configuration"
},
"security": {
"type": "object",
diff --git a/packages/spec/src/api/graphql.zod.ts b/packages/spec/src/api/graphql.zod.ts
index edcb374d929..158c8ea65c9 100644
--- a/packages/spec/src/api/graphql.zod.ts
+++ b/packages/spec/src/api/graphql.zod.ts
@@ -126,10 +126,10 @@ export const GraphQLTypeConfigSchema = z.object({
/** Fields to include/exclude */
fields: z.object({
- /** Include only these fields (whitelist) */
+ /** Include only these fields (allow list) */
include: z.array(z.string()).optional().describe('Fields to include'),
- /** Exclude these fields (blacklist) */
+ /** Exclude these fields (deny list) */
exclude: z.array(z.string()).optional().describe('Fields to exclude (e.g., sensitive fields)'),
/** Custom field mappings */
@@ -688,7 +688,7 @@ export type GraphQLRateLimit = z.infer;
/**
* Persisted Queries Configuration
*
- * Only allow pre-registered queries to execute (whitelist approach).
+ * Only allow pre-registered queries to execute (allow list approach).
* Improves security and performance.
*/
export const GraphQLPersistedQuerySchema = z.object({
@@ -728,10 +728,10 @@ export const GraphQLPersistedQuerySchema = z.object({
}).optional().describe('APQ cache configuration'),
}).optional().describe('Automatic Persisted Queries configuration'),
- /** Query allowlist */
+ /** Query allow list */
allowlist: z.object({
- /** Enable allowlist mode */
- enabled: z.boolean().default(false).describe('Enable query allowlist (reject queries not in list)'),
+ /** Enable allow list mode */
+ enabled: z.boolean().default(false).describe('Enable query allow list (reject queries not in list)'),
/** Allowed query IDs */
queries: z.array(z.object({
@@ -740,9 +740,9 @@ export const GraphQLPersistedQuerySchema = z.object({
query: z.string().optional().describe('Query string'),
})).optional().describe('Allowed queries'),
- /** External allowlist source */
- source: z.string().optional().describe('External allowlist source (file path or URL)'),
- }).optional().describe('Query allowlist configuration'),
+ /** External allow list source */
+ source: z.string().optional().describe('External allow list source (file path or URL)'),
+ }).optional().describe('Query allow list configuration'),
/** Security */
security: z.object({