Skip to content

Commit 40865e5

Browse files
committed
Add zod schemas for mapping, query, webhook, and page
Introduced new Zod schemas for ETL mapping, query AST, webhooks, and UI pages in the spec package. Updated the index to export these new schemas. Also updated dependencies in pnpm-lock.yaml to include @objectdocs/site and related packages.
1 parent abd5cbe commit 40865e5

File tree

9 files changed

+5125
-475
lines changed

9 files changed

+5125
-475
lines changed

content/docs.site.json

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
{
2+
"meta": {
3+
"title": "Object Protocol",
4+
"description": "The Metadata-Driven Documentation Engine for the Low-Code Era.",
5+
"url": "https://docs.objectstack.ai",
6+
"favicon": "/favicon.ico"
7+
},
8+
"i18n": {
9+
"enabled": true,
10+
"defaultLanguage": "en",
11+
"languages": ["en", "cn"
12+
]
13+
},
14+
"branding": {
15+
"logo": {
16+
"text": "ObjectDocs",
17+
"light": "/logo.svg",
18+
"dark": "/logo.svg"
19+
},
20+
"theme": {
21+
"accentColor": "blue",
22+
"radius": "0.5rem"
23+
}
24+
},
25+
"layout": {
26+
"navbar": {
27+
"enabled": true,
28+
"transparentMode": "top",
29+
"links": [
30+
{
31+
"text": "Home",
32+
"url": "https://www.objectstack.ai",
33+
"external": true
34+
}
35+
],
36+
"socials": [
37+
{ "platform": "github", "url": "https://github.com/objectstack-ai" }
38+
]
39+
},
40+
"sidebar": {
41+
"enabled": true,
42+
"prefetch": true,
43+
"defaultOpenLevel": 1,
44+
"collapsible": true,
45+
"tabs": []
46+
},
47+
"toc": {
48+
"enabled": true,
49+
"depth": 3
50+
},
51+
"footer": {
52+
"enabled": false,
53+
"copyright": "© 2026 ObjectStack Inc."
54+
}
55+
},
56+
"page": {
57+
"showLastUpdate": true,
58+
"showEditLink": true,
59+
"repoBaseUrl": "https://github.com/objectstack-ai/objectdocs"
60+
},
61+
"content": {
62+
"math": false,
63+
"imageZoom": true,
64+
"codeBlock": {
65+
"theme": "vesper",
66+
"showLineNumbers": true
67+
}
68+
}
69+
}

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,8 @@
2727
"engines": {
2828
"node": ">=18.0.0"
2929
},
30-
"packageManager": "pnpm@10.28.0"
30+
"packageManager": "pnpm@10.28.0",
31+
"dependencies": {
32+
"@objectdocs/site": "^0.2.1"
33+
}
3134
}

packages/spec/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@
2424
"license": "MIT",
2525
"devDependencies": {
2626
"@types/node": "^20.10.0",
27-
"typescript": "^5.3.0"
27+
"tsx": "^4.21.0",
28+
"typescript": "^5.3.0",
29+
"zod-to-json-schema": "^3.25.1"
2830
},
2931
"dependencies": {
3032
"zod": "^3.22.4"
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { z } from 'zod';
2+
import { QuerySchema } from './query.zod';
3+
4+
/**
5+
* Transformation Logic
6+
* Built-in helpers for converting data during import.
7+
*/
8+
export const TransformType = z.enum([
9+
'none', // Direct copy
10+
'constant', // Use a hardcoded value
11+
'lookup', // Resolve FK (Name -> ID)
12+
'split', // "John Doe" -> ["John", "Doe"]
13+
'join', // ["John", "Doe"] -> "John Doe"
14+
'javascript', // Custom script (Review security!)
15+
'map' // Value mapping (e.g. "Active" -> "active")
16+
]);
17+
18+
/**
19+
* Field Mapping Item
20+
*/
21+
export const FieldMappingSchema = z.object({
22+
/** Source Column */
23+
source: z.union([z.string(), z.array(z.string())]).describe('Source column header(s)'),
24+
25+
/** Target Field */
26+
target: z.union([z.string(), z.array(z.string())]).describe('Target object field(s)'),
27+
28+
/** Transformation */
29+
transform: TransformType.default('none'),
30+
31+
/** Configuration for transform */
32+
params: z.object({
33+
// Constant
34+
value: z.any().optional(),
35+
36+
// Lookup
37+
object: z.string().optional(), // Lookup Object
38+
fromField: z.string().optional(), // Match on (e.g. "name")
39+
toField: z.string().optional(), // Value to take (e.g. "_id")
40+
autoCreate: z.boolean().optional(), // Create if missing
41+
42+
// Map
43+
valueMap: z.record(z.any()).optional(), // { "Open": "draft" }
44+
45+
// Split/Join
46+
separator: z.string().optional()
47+
}).optional()
48+
});
49+
50+
/**
51+
* ETL Mapping Schema
52+
* Definition for importing/exporting data defined in `integration-etl.mdx`.
53+
*/
54+
export const MappingSchema = z.object({
55+
/** Identity */
56+
name: z.string().regex(/^[a-z_][a-z0-9_]*$/),
57+
label: z.string().optional(),
58+
59+
/** Scope */
60+
sourceFormat: z.enum(['csv', 'json', 'xml', 'sql']).default('csv'),
61+
targetObject: z.string().describe('Target Object Name'),
62+
63+
/** Column Mappings */
64+
fieldMapping: z.array(FieldMappingSchema),
65+
66+
/** Upsert Logic */
67+
mode: z.enum(['insert', 'update', 'upsert']).default('insert'),
68+
upsertKey: z.array(z.string()).optional().describe('Fields to match for upsert (e.g. email)'),
69+
70+
/** Extract Logic (For Export) */
71+
extractQuery: QuerySchema.optional().describe('Query to run for export only'),
72+
73+
/** Error Handling */
74+
errorPolicy: z.enum(['skip', 'abort', 'retry']).default('skip'),
75+
batchSize: z.number().default(1000)
76+
});
77+
78+
export type Mapping = z.infer<typeof MappingSchema>;
79+
export type FieldMapping = z.infer<typeof FieldMappingSchema>;
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { z } from 'zod';
2+
3+
/**
4+
* Filter Operator Enum
5+
* Standard SQL/NoSQL operators supported by the engine.
6+
*/
7+
export const FilterOperator = z.enum([
8+
'=', '!=', '<>',
9+
'>', '>=', '<', '<=',
10+
'startswith', 'contains', 'notcontains',
11+
'between', 'in', 'notin',
12+
'is_null', 'is_not_null'
13+
]);
14+
15+
/**
16+
* Filter Logic Operator
17+
*/
18+
export const LogicOperator = z.enum(['and', 'or', 'not']);
19+
20+
/**
21+
* Recursive Filter Node
22+
* Represents the "Where" clause.
23+
*
24+
* Structure: [Field, Operator, Value] OR [Logic, Filter, Filter...]
25+
* Examples:
26+
* - Simple: ["amount", ">", 1000]
27+
* - Logic: [["status", "=", "closed"], "or", ["amount", ">", 1000]]
28+
*/
29+
export const FilterNodeSchema: z.ZodType<any> = z.lazy(() =>
30+
z.union([
31+
// Leaf Node: [Field, Operator, Value]
32+
z.tuple([z.string(), FilterOperator, z.any()]),
33+
34+
// Logic Node: [Expression, "or", Expression]
35+
z.array(z.union([z.string(), FilterNodeSchema]))
36+
])
37+
);
38+
39+
/**
40+
* Sort Node
41+
* Represents "Order By".
42+
*/
43+
export const SortNodeSchema = z.object({
44+
field: z.string(),
45+
order: z.enum(['asc', 'desc']).default('asc')
46+
});
47+
48+
/**
49+
* Field Selection Node
50+
* Represents "Select" attributes, including joins.
51+
*/
52+
export const FieldNodeSchema: z.ZodType<any> = z.lazy(() =>
53+
z.union([
54+
z.string(), // Primitive field: "name"
55+
z.object({
56+
field: z.string(), // Relationship field: "owner"
57+
fields: z.array(FieldNodeSchema).optional(), // Nested select: ["name", "email"]
58+
alias: z.string().optional()
59+
})
60+
])
61+
);
62+
63+
/**
64+
* Query AST Schema
65+
* The universal data retrieval contract defined in `ast-structure.mdx`.
66+
*/
67+
export const QuerySchema = z.object({
68+
/** Target Entity */
69+
object: z.string().describe('Object name (e.g. account)'),
70+
71+
/** Select Clause */
72+
fields: z.array(FieldNodeSchema).optional().describe('Fields to retrieve'),
73+
74+
/** Where Clause */
75+
filters: FilterNodeSchema.optional().describe('Filtering criteria'),
76+
77+
/** Order By Clause */
78+
sort: z.array(SortNodeSchema).optional().describe('Sorting instructions'),
79+
80+
/** Pagination */
81+
top: z.number().optional().describe('Limit results'),
82+
skip: z.number().optional().describe('Offset results'),
83+
});
84+
85+
export type QueryAST = z.infer<typeof QuerySchema>;
86+
export type FilterNode = z.infer<typeof FilterNodeSchema>;
87+
export type SortNode = z.infer<typeof SortNodeSchema>;

packages/spec/src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ export * from './data/sharing.zod';
1616
export * from './data/workflow.zod';
1717
export * from './data/flow.zod';
1818
export * from './data/dataset.zod';
19+
export * from './data/query.zod';
20+
export * from './data/mapping.zod';
1921

2022
// AI Protocol (Agent, RAG)
2123
export * from './ai/agent.zod';
@@ -26,6 +28,7 @@ export * from './ui/view.zod';
2628
export * from './ui/dashboard.zod';
2729
export * from './ui/report.zod';
2830
export * from './ui/action.zod';
31+
export * from './ui/page.zod';
2932

3033
// System Protocol (Manifest, Runtime, Constants)
3134
export * from './system/manifest.zod';
@@ -36,6 +39,7 @@ export * from './system/policy.zod';
3639
export * from './system/role.zod';
3740
export * from './system/territory.zod';
3841
export * from './system/license.zod';
42+
export * from './system/webhook.zod';
3943
export * from './system/translation.zod';
4044
export * from './system/constants';
4145
export * from './system/types';
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { z } from 'zod';
2+
3+
/**
4+
* Webhook Trigger Event
5+
* When should this webhook fire?
6+
*/
7+
export const WebhookTriggerType = z.enum([
8+
'create',
9+
'update',
10+
'delete',
11+
'undelete',
12+
'api' // Manually triggered
13+
]);
14+
15+
/**
16+
* Webhook Schema
17+
* outbound Integration: Push data to external URL when events happen.
18+
*/
19+
export const WebhookSchema = z.object({
20+
name: z.string().regex(/^[a-z_][a-z0-9_]*$/),
21+
label: z.string().optional(),
22+
23+
/** Scope */
24+
object: z.string().describe('Object to listen to'),
25+
triggers: z.array(WebhookTriggerType).describe('Events that trigger execution'),
26+
27+
/** Target */
28+
url: z.string().url().describe('External URL payload'),
29+
method: z.enum(['POST', 'PUT', 'GET']).default('POST'),
30+
31+
/** Security */
32+
secret: z.string().optional().describe('Signing secret (HMAC)'),
33+
headers: z.record(z.string()).optional().describe('Custom headers (Auth)'),
34+
35+
/** Payload Configuration */
36+
payloadFields: z.array(z.string()).optional().describe('Fields to include. Empty = All'),
37+
includeSession: z.boolean().default(false).describe('Include user session info'),
38+
39+
/** Reliability */
40+
retryCount: z.number().default(3),
41+
isActive: z.boolean().default(true)
42+
});
43+
44+
/**
45+
* Webhook Receiver Schema (Inbound)
46+
* Handling incoming HTTP hooks from Stripe, Slack, etc.
47+
*/
48+
export const WebhookReceiverSchema = z.object({
49+
name: z.string().regex(/^[a-z_][a-z0-9_]*$/),
50+
path: z.string().describe('URL Path (e.g. /webhooks/stripe)'),
51+
52+
/** Verification */
53+
verificationType: z.enum(['none', 'header_token', 'hmac', 'ip_whitelist']).default('none'),
54+
verificationParams: z.object({
55+
header: z.string().optional(),
56+
secret: z.string().optional(),
57+
ips: z.array(z.string()).optional()
58+
}).optional(),
59+
60+
/** Action */
61+
action: z.enum(['trigger_flow', 'script', 'upsert_record']).default('trigger_flow'),
62+
target: z.string().describe('Flow ID or Script name'),
63+
});
64+
65+
export type Webhook = z.infer<typeof WebhookSchema>;
66+
export type WebhookReceiver = z.infer<typeof WebhookReceiverSchema>;

0 commit comments

Comments
 (0)