skedyul 1.7.6 → 1.7.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.build-stamp +1 -1
- package/dist/cli/index.js +129 -0
- package/dist/config/app-config.d.ts +5 -1
- package/dist/config/define.d.ts +36 -0
- package/dist/config/index.d.ts +1 -1
- package/dist/config/types/batch-operation.d.ts +152 -0
- package/dist/config/types/index.d.ts +1 -0
- package/dist/dedicated/server.js +129 -0
- package/dist/esm/index.mjs +165 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +170 -1
- package/dist/schemas.d.ts +53 -0
- package/dist/server/index.d.ts +3 -2
- package/dist/server/route-handlers/handlers.d.ts +7 -0
- package/dist/server/route-handlers/index.d.ts +1 -1
- package/dist/server/route-handlers/types.d.ts +2 -1
- package/dist/server.js +129 -0
- package/dist/serverless/server.mjs +129 -0
- package/dist/types/batch-operation.d.ts +47 -0
- package/dist/types/index.d.ts +1 -0
- package/package.json +1 -1
package/dist/.build-stamp
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
1785985393398
|
package/dist/cli/index.js
CHANGED
|
@@ -4658,6 +4658,128 @@ async function handleMcpBatchRoute(req, ctx) {
|
|
|
4658
4658
|
}
|
|
4659
4659
|
};
|
|
4660
4660
|
}
|
|
4661
|
+
async function handleBatchOperationRoute(req, ctx) {
|
|
4662
|
+
const bodyResult = parseJsonBody(req);
|
|
4663
|
+
if (!bodyResult.success) {
|
|
4664
|
+
return bodyResult.error;
|
|
4665
|
+
}
|
|
4666
|
+
const body = bodyResult.data;
|
|
4667
|
+
const registry = ctx.batchOperationRegistry;
|
|
4668
|
+
if (!registry) {
|
|
4669
|
+
return {
|
|
4670
|
+
status: 404,
|
|
4671
|
+
body: {
|
|
4672
|
+
success: false,
|
|
4673
|
+
error: "No batch operations registered"
|
|
4674
|
+
}
|
|
4675
|
+
};
|
|
4676
|
+
}
|
|
4677
|
+
const operation = registry[body.handle];
|
|
4678
|
+
if (!operation) {
|
|
4679
|
+
return {
|
|
4680
|
+
status: 404,
|
|
4681
|
+
body: {
|
|
4682
|
+
success: false,
|
|
4683
|
+
error: `Batch operation not found: ${body.handle}`
|
|
4684
|
+
}
|
|
4685
|
+
};
|
|
4686
|
+
}
|
|
4687
|
+
const context = body.context;
|
|
4688
|
+
if (!context?.workplaceId || !context?.appInstallationId || !context?.appId) {
|
|
4689
|
+
return {
|
|
4690
|
+
status: 400,
|
|
4691
|
+
body: {
|
|
4692
|
+
success: false,
|
|
4693
|
+
error: "Missing context (workplaceId, appInstallationId, and appId required)"
|
|
4694
|
+
}
|
|
4695
|
+
};
|
|
4696
|
+
}
|
|
4697
|
+
const env = body.env ?? {};
|
|
4698
|
+
const log = {
|
|
4699
|
+
info: (message, meta) => {
|
|
4700
|
+
console.log(`[batch:${body.handle}] ${message}`, meta ?? "");
|
|
4701
|
+
},
|
|
4702
|
+
warn: (message, meta) => {
|
|
4703
|
+
console.warn(`[batch:${body.handle}] ${message}`, meta ?? "");
|
|
4704
|
+
},
|
|
4705
|
+
error: (message, meta) => {
|
|
4706
|
+
console.error(`[batch:${body.handle}] ${message}`, meta ?? "");
|
|
4707
|
+
}
|
|
4708
|
+
};
|
|
4709
|
+
const requestConfig = {
|
|
4710
|
+
baseUrl: env.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
4711
|
+
apiToken: env.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
4712
|
+
};
|
|
4713
|
+
try {
|
|
4714
|
+
if (body.method === "setup") {
|
|
4715
|
+
if (!operation.setup) {
|
|
4716
|
+
return {
|
|
4717
|
+
status: 200,
|
|
4718
|
+
body: {
|
|
4719
|
+
success: true,
|
|
4720
|
+
result: { state: {}, total: void 0 }
|
|
4721
|
+
}
|
|
4722
|
+
};
|
|
4723
|
+
}
|
|
4724
|
+
const result = await runWithConfig(requestConfig, async () => {
|
|
4725
|
+
return operation.setup({
|
|
4726
|
+
workplaceId: context.workplaceId,
|
|
4727
|
+
appInstallationId: context.appInstallationId,
|
|
4728
|
+
appId: context.appId,
|
|
4729
|
+
input: body.input,
|
|
4730
|
+
env,
|
|
4731
|
+
log
|
|
4732
|
+
});
|
|
4733
|
+
});
|
|
4734
|
+
return {
|
|
4735
|
+
status: 200,
|
|
4736
|
+
body: {
|
|
4737
|
+
success: true,
|
|
4738
|
+
result
|
|
4739
|
+
}
|
|
4740
|
+
};
|
|
4741
|
+
}
|
|
4742
|
+
if (body.method === "iterate") {
|
|
4743
|
+
const result = await runWithConfig(requestConfig, async () => {
|
|
4744
|
+
return operation.iterate({
|
|
4745
|
+
workplaceId: context.workplaceId,
|
|
4746
|
+
appInstallationId: context.appInstallationId,
|
|
4747
|
+
appId: context.appId,
|
|
4748
|
+
input: body.input,
|
|
4749
|
+
state: body.state,
|
|
4750
|
+
page: body.page,
|
|
4751
|
+
cursor: body.cursor,
|
|
4752
|
+
limit: body.limit,
|
|
4753
|
+
env,
|
|
4754
|
+
log
|
|
4755
|
+
});
|
|
4756
|
+
});
|
|
4757
|
+
return {
|
|
4758
|
+
status: 200,
|
|
4759
|
+
body: {
|
|
4760
|
+
success: true,
|
|
4761
|
+
result
|
|
4762
|
+
}
|
|
4763
|
+
};
|
|
4764
|
+
}
|
|
4765
|
+
return {
|
|
4766
|
+
status: 400,
|
|
4767
|
+
body: {
|
|
4768
|
+
success: false,
|
|
4769
|
+
error: `Unknown method: ${body.method}`
|
|
4770
|
+
}
|
|
4771
|
+
};
|
|
4772
|
+
} catch (err) {
|
|
4773
|
+
log.error("Batch operation failed", { error: err });
|
|
4774
|
+
return {
|
|
4775
|
+
status: 500,
|
|
4776
|
+
body: {
|
|
4777
|
+
success: false,
|
|
4778
|
+
error: err instanceof Error ? err.message : String(err)
|
|
4779
|
+
}
|
|
4780
|
+
};
|
|
4781
|
+
}
|
|
4782
|
+
}
|
|
4661
4783
|
function createNotFoundResponse() {
|
|
4662
4784
|
return {
|
|
4663
4785
|
status: 404,
|
|
@@ -4737,6 +4859,9 @@ async function routeRequest(req, ctx) {
|
|
|
4737
4859
|
if (req.path === "/mcp/batch" && req.method === "POST") {
|
|
4738
4860
|
return handleMcpBatchRoute(req, ctx);
|
|
4739
4861
|
}
|
|
4862
|
+
if (req.path === "/batch-operation" && req.method === "POST") {
|
|
4863
|
+
return handleBatchOperationRoute(req, ctx);
|
|
4864
|
+
}
|
|
4740
4865
|
return createNotFoundResponse();
|
|
4741
4866
|
} catch (err) {
|
|
4742
4867
|
return createErrorResponse(err);
|
|
@@ -4778,11 +4903,13 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
4778
4903
|
const defaultHeaders = getDefaultHeaders(config.cors);
|
|
4779
4904
|
const registry = config.tools;
|
|
4780
4905
|
const webhookRegistry = config.webhooks;
|
|
4906
|
+
const batchOperationRegistry = config.batchOperations;
|
|
4781
4907
|
const ctx = {
|
|
4782
4908
|
config,
|
|
4783
4909
|
tools,
|
|
4784
4910
|
registry,
|
|
4785
4911
|
webhookRegistry,
|
|
4912
|
+
batchOperationRegistry,
|
|
4786
4913
|
callTool,
|
|
4787
4914
|
state,
|
|
4788
4915
|
mcpServer,
|
|
@@ -4897,12 +5024,14 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer) {
|
|
|
4897
5024
|
const defaultHeaders = getDefaultHeaders(config.cors);
|
|
4898
5025
|
const registry = config.tools;
|
|
4899
5026
|
const webhookRegistry = config.webhooks;
|
|
5027
|
+
const batchOperationRegistry = config.batchOperations;
|
|
4900
5028
|
let hasLoggedStartup = false;
|
|
4901
5029
|
const ctx = {
|
|
4902
5030
|
config,
|
|
4903
5031
|
tools,
|
|
4904
5032
|
registry,
|
|
4905
5033
|
webhookRegistry,
|
|
5034
|
+
batchOperationRegistry,
|
|
4906
5035
|
callTool,
|
|
4907
5036
|
state,
|
|
4908
5037
|
mcpServer,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* This module defines the main configuration interfaces for Skedyul apps.
|
|
5
5
|
*/
|
|
6
|
-
import type { ToolRegistry, WebhookRegistry, ToolMetadata, WebhookMetadata } from '../types';
|
|
6
|
+
import type { ToolRegistry, WebhookRegistry, ToolMetadata, WebhookMetadata, BatchOperationRegistry } from '../types';
|
|
7
7
|
import type { ServerHooks } from '../types/handlers';
|
|
8
8
|
import type { CoreApiConfig } from '../core/types';
|
|
9
9
|
import type { EnvSchema, ComputeLayer, ModelDefinition, EntityDefinition, RelationshipDefinition, ChannelDefinition, WorkflowDefinition, PageDefinition, NavigationConfig, AgentDefinition, SignalDefinition, SetupStepDefinition } from './types';
|
|
@@ -103,6 +103,10 @@ export interface SkedyulConfig {
|
|
|
103
103
|
webhooks?: WebhookRegistry | Promise<{
|
|
104
104
|
webhookRegistry: WebhookRegistry;
|
|
105
105
|
}>;
|
|
106
|
+
/** Batch operation registry - direct object or dynamic import */
|
|
107
|
+
batchOperations?: BatchOperationRegistry | Promise<{
|
|
108
|
+
batchOperationRegistry: BatchOperationRegistry;
|
|
109
|
+
}>;
|
|
106
110
|
/** Lifecycle hooks for install, provision, uninstall, and oauth_callback */
|
|
107
111
|
hooks?: ServerHooks;
|
|
108
112
|
/** Provision configuration - direct object or dynamic import */
|
package/dist/config/define.d.ts
CHANGED
|
@@ -24,6 +24,7 @@ import type { AgentDefinition } from './types/agent';
|
|
|
24
24
|
import type { EnvSchema } from './types/env';
|
|
25
25
|
import type { NavigationConfig } from './types/navigation';
|
|
26
26
|
import type { SetupStepDefinition } from './types/setup';
|
|
27
|
+
import type { BatchOperationDefinition } from './types/batch-operation';
|
|
27
28
|
/**
|
|
28
29
|
* Define a model with full type safety.
|
|
29
30
|
*
|
|
@@ -166,3 +167,38 @@ export declare function defineNavigation(navigation: NavigationConfig): Navigati
|
|
|
166
167
|
* })
|
|
167
168
|
*/
|
|
168
169
|
export declare function defineSetupStep(step: SetupStepDefinition): SetupStepDefinition;
|
|
170
|
+
/**
|
|
171
|
+
* Define a batch operation with full type safety.
|
|
172
|
+
*
|
|
173
|
+
* Batch operations are long-running, paginated tasks like imports and syncs.
|
|
174
|
+
* The platform handles orchestration, progress tracking, CRM mapping, and upsert.
|
|
175
|
+
*
|
|
176
|
+
* @example
|
|
177
|
+
* export default defineBatchOperation({
|
|
178
|
+
* handle: 'import_members',
|
|
179
|
+
* label: 'Import Members',
|
|
180
|
+
* entity: 'member',
|
|
181
|
+
* setup: async (ctx) => {
|
|
182
|
+
* const count = await glofox.getMemberCount()
|
|
183
|
+
* return { total: count }
|
|
184
|
+
* },
|
|
185
|
+
* iterate: async (ctx) => {
|
|
186
|
+
* const apiKey = ctx.env.GLOFOX_API_KEY
|
|
187
|
+
* const { data, pagination } = await glofox.getMembers({
|
|
188
|
+
* apiKey,
|
|
189
|
+
* limit: ctx.limit,
|
|
190
|
+
* offset: ctx.cursor ?? 0,
|
|
191
|
+
* })
|
|
192
|
+
* return {
|
|
193
|
+
* items: data,
|
|
194
|
+
* pagination: {
|
|
195
|
+
* hasMore: pagination.hasMore,
|
|
196
|
+
* nextCursor: pagination.offset + pagination.limit,
|
|
197
|
+
* }
|
|
198
|
+
* }
|
|
199
|
+
* },
|
|
200
|
+
* maxConcurrent: 1,
|
|
201
|
+
* pageSize: 50,
|
|
202
|
+
* })
|
|
203
|
+
*/
|
|
204
|
+
export declare function defineBatchOperation(operation: BatchOperationDefinition): BatchOperationDefinition;
|
package/dist/config/index.d.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
export * from './types';
|
|
12
12
|
export type { InstallConfig, ProvisionConfig, BuildConfig, CorsOptions, SkedyulConfig, SerializableSkedyulConfig, QueueScope, QueueConfig, SerializableQueueConfig, QueueRegistry, SequencerScope, SerializableSequencerConfig, SequencerRegistry, } from './app-config';
|
|
13
13
|
export { defineConfig } from './app-config';
|
|
14
|
-
export { defineModel, defineEntity, defineChannel, definePage, defineWorkflow, defineAgent, defineEnv, defineNavigation, defineSetupStep, } from './define';
|
|
14
|
+
export { defineModel, defineEntity, defineChannel, definePage, defineWorkflow, defineAgent, defineEnv, defineNavigation, defineSetupStep, defineBatchOperation, } from './define';
|
|
15
15
|
export { SCHEMA_FILE_EXTENSIONS, loadSchema, saveSchema, serializeSchemaToJson, serializeSchemaToTypeScript, transformToBackendSchema, transformFromBackendSchema, type LoadSchemaOptions, type LoadSchemaResult, type SerializeSchemaOptions, type BackendModelDefinition, type BackendFieldDefinition, type BackendRelationshipDefinition, type BackendDesiredSchema, } from './schema-loader';
|
|
16
16
|
export { loadAndResolveConfig, serializeResolvedConfig, type ResolvedConfig } from './resolver';
|
|
17
17
|
export { getAllEnvKeys, getRequiredInstallEnvKeys } from './utils';
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Batch operation definition types.
|
|
3
|
+
*
|
|
4
|
+
* Batch operations allow apps to define long-running, paginated operations
|
|
5
|
+
* like member imports, data syncs, and batch API calls. The platform handles
|
|
6
|
+
* orchestration, progress tracking, CRM mapping, and upsert.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Pagination response from an iterate function.
|
|
10
|
+
* Supports both cursor-based and page-based pagination.
|
|
11
|
+
*/
|
|
12
|
+
export interface BatchPagination {
|
|
13
|
+
/** Whether there are more items to fetch */
|
|
14
|
+
hasMore: boolean;
|
|
15
|
+
/** Current page number (1-indexed, for page-based pagination) */
|
|
16
|
+
page?: number;
|
|
17
|
+
/** Total number of pages (for page-based pagination) */
|
|
18
|
+
total?: number;
|
|
19
|
+
/** Cursor for next page (for cursor-based pagination) */
|
|
20
|
+
nextCursor?: string | number;
|
|
21
|
+
/** Items per page */
|
|
22
|
+
limit?: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Result from a batch operation setup function.
|
|
26
|
+
*/
|
|
27
|
+
export interface BatchOperationSetupResult {
|
|
28
|
+
/** Optional state to persist across iterate calls */
|
|
29
|
+
state?: Record<string, unknown>;
|
|
30
|
+
/** Optional total count for progress display */
|
|
31
|
+
total?: number;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Result from a batch operation iterate function.
|
|
35
|
+
*/
|
|
36
|
+
export interface BatchOperationIterateResult {
|
|
37
|
+
/** Array of raw items from the external source */
|
|
38
|
+
items: Record<string, unknown>[];
|
|
39
|
+
/** Pagination info for fetching more pages */
|
|
40
|
+
pagination: BatchPagination;
|
|
41
|
+
/** Optional state to persist for the next iterate call */
|
|
42
|
+
state?: Record<string, unknown>;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Context passed to batch operation setup and iterate functions.
|
|
46
|
+
*/
|
|
47
|
+
export interface BatchOperationContext {
|
|
48
|
+
/** Workplace ID */
|
|
49
|
+
workplaceId: string;
|
|
50
|
+
/** App installation ID */
|
|
51
|
+
appInstallationId: string;
|
|
52
|
+
/** App ID */
|
|
53
|
+
appId: string;
|
|
54
|
+
/** User-provided input (if any) */
|
|
55
|
+
input?: Record<string, unknown>;
|
|
56
|
+
/** State from previous setup or iterate call */
|
|
57
|
+
state?: Record<string, unknown>;
|
|
58
|
+
/**
|
|
59
|
+
* Runtime environment variables from the platform invoke
|
|
60
|
+
* (APP_INSTALL overrides + SKEDYUL_API_TOKEN / SKEDYUL_API_URL).
|
|
61
|
+
*/
|
|
62
|
+
env: Record<string, string>;
|
|
63
|
+
/** Logger instance */
|
|
64
|
+
log: {
|
|
65
|
+
info: (message: string, meta?: Record<string, unknown>) => void;
|
|
66
|
+
warn: (message: string, meta?: Record<string, unknown>) => void;
|
|
67
|
+
error: (message: string, meta?: Record<string, unknown>) => void;
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Type for batch operation setup function.
|
|
72
|
+
*/
|
|
73
|
+
export type BatchOperationSetupFn = (ctx: BatchOperationContext) => Promise<BatchOperationSetupResult>;
|
|
74
|
+
/**
|
|
75
|
+
* Type for batch operation iterate function.
|
|
76
|
+
*/
|
|
77
|
+
export type BatchOperationIterateFn = (ctx: BatchOperationContext & {
|
|
78
|
+
/** Page to fetch (1-indexed, for page-based pagination) */
|
|
79
|
+
page?: number;
|
|
80
|
+
/** Cursor from previous iterate (for cursor-based pagination) */
|
|
81
|
+
cursor?: string | number;
|
|
82
|
+
/** Items per page */
|
|
83
|
+
limit: number;
|
|
84
|
+
}) => Promise<BatchOperationIterateResult>;
|
|
85
|
+
/**
|
|
86
|
+
* Batch operation definition.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* export default defineBatchOperation({
|
|
90
|
+
* handle: 'import_members',
|
|
91
|
+
* label: 'Import Members',
|
|
92
|
+
* entity: 'member',
|
|
93
|
+
* setup: async (ctx) => {
|
|
94
|
+
* const count = await glofox.getMemberCount()
|
|
95
|
+
* return { total: count }
|
|
96
|
+
* },
|
|
97
|
+
* iterate: async (ctx) => {
|
|
98
|
+
* const apiKey = ctx.env.GLOFOX_API_KEY
|
|
99
|
+
* const { data, pagination } = await glofox.getMembers({
|
|
100
|
+
* apiKey,
|
|
101
|
+
* limit: ctx.limit,
|
|
102
|
+
* offset: ctx.cursor ?? 0,
|
|
103
|
+
* })
|
|
104
|
+
* return {
|
|
105
|
+
* items: data,
|
|
106
|
+
* pagination: {
|
|
107
|
+
* hasMore: pagination.hasMore,
|
|
108
|
+
* nextCursor: pagination.offset + pagination.limit,
|
|
109
|
+
* }
|
|
110
|
+
* }
|
|
111
|
+
* },
|
|
112
|
+
* maxConcurrent: 1,
|
|
113
|
+
* pageSize: 50,
|
|
114
|
+
* })
|
|
115
|
+
*/
|
|
116
|
+
export interface BatchOperationDefinition {
|
|
117
|
+
/** Unique handle for this operation */
|
|
118
|
+
handle: string;
|
|
119
|
+
/** Human-readable label */
|
|
120
|
+
label: string;
|
|
121
|
+
/** Description of what this operation does */
|
|
122
|
+
description?: string;
|
|
123
|
+
/**
|
|
124
|
+
* Entity handle that items will be upserted to.
|
|
125
|
+
* The platform applies CRM mappings and calls upsertMany.
|
|
126
|
+
*/
|
|
127
|
+
entity: string;
|
|
128
|
+
/**
|
|
129
|
+
* Setup function called once at the start of the operation.
|
|
130
|
+
* Use to initialize state, fetch total count, etc.
|
|
131
|
+
*/
|
|
132
|
+
setup?: BatchOperationSetupFn;
|
|
133
|
+
/**
|
|
134
|
+
* Iterate function called for each page of data.
|
|
135
|
+
* Must return items and pagination info.
|
|
136
|
+
*/
|
|
137
|
+
iterate: BatchOperationIterateFn;
|
|
138
|
+
/**
|
|
139
|
+
* Maximum concurrent operations per app installation.
|
|
140
|
+
* @default 1
|
|
141
|
+
*/
|
|
142
|
+
maxConcurrent?: number;
|
|
143
|
+
/**
|
|
144
|
+
* Default page size (items per iterate call).
|
|
145
|
+
* @default 50
|
|
146
|
+
*/
|
|
147
|
+
pageSize?: number;
|
|
148
|
+
/**
|
|
149
|
+
* Optional icon for the operation (Lucide icon name)
|
|
150
|
+
*/
|
|
151
|
+
icon?: string;
|
|
152
|
+
}
|
package/dist/dedicated/server.js
CHANGED
|
@@ -1988,6 +1988,128 @@ async function handleMcpBatchRoute(req, ctx) {
|
|
|
1988
1988
|
}
|
|
1989
1989
|
};
|
|
1990
1990
|
}
|
|
1991
|
+
async function handleBatchOperationRoute(req, ctx) {
|
|
1992
|
+
const bodyResult = parseJsonBody(req);
|
|
1993
|
+
if (!bodyResult.success) {
|
|
1994
|
+
return bodyResult.error;
|
|
1995
|
+
}
|
|
1996
|
+
const body = bodyResult.data;
|
|
1997
|
+
const registry = ctx.batchOperationRegistry;
|
|
1998
|
+
if (!registry) {
|
|
1999
|
+
return {
|
|
2000
|
+
status: 404,
|
|
2001
|
+
body: {
|
|
2002
|
+
success: false,
|
|
2003
|
+
error: "No batch operations registered"
|
|
2004
|
+
}
|
|
2005
|
+
};
|
|
2006
|
+
}
|
|
2007
|
+
const operation = registry[body.handle];
|
|
2008
|
+
if (!operation) {
|
|
2009
|
+
return {
|
|
2010
|
+
status: 404,
|
|
2011
|
+
body: {
|
|
2012
|
+
success: false,
|
|
2013
|
+
error: `Batch operation not found: ${body.handle}`
|
|
2014
|
+
}
|
|
2015
|
+
};
|
|
2016
|
+
}
|
|
2017
|
+
const context = body.context;
|
|
2018
|
+
if (!context?.workplaceId || !context?.appInstallationId || !context?.appId) {
|
|
2019
|
+
return {
|
|
2020
|
+
status: 400,
|
|
2021
|
+
body: {
|
|
2022
|
+
success: false,
|
|
2023
|
+
error: "Missing context (workplaceId, appInstallationId, and appId required)"
|
|
2024
|
+
}
|
|
2025
|
+
};
|
|
2026
|
+
}
|
|
2027
|
+
const env = body.env ?? {};
|
|
2028
|
+
const log = {
|
|
2029
|
+
info: (message, meta) => {
|
|
2030
|
+
console.log(`[batch:${body.handle}] ${message}`, meta ?? "");
|
|
2031
|
+
},
|
|
2032
|
+
warn: (message, meta) => {
|
|
2033
|
+
console.warn(`[batch:${body.handle}] ${message}`, meta ?? "");
|
|
2034
|
+
},
|
|
2035
|
+
error: (message, meta) => {
|
|
2036
|
+
console.error(`[batch:${body.handle}] ${message}`, meta ?? "");
|
|
2037
|
+
}
|
|
2038
|
+
};
|
|
2039
|
+
const requestConfig = {
|
|
2040
|
+
baseUrl: env.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
2041
|
+
apiToken: env.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
2042
|
+
};
|
|
2043
|
+
try {
|
|
2044
|
+
if (body.method === "setup") {
|
|
2045
|
+
if (!operation.setup) {
|
|
2046
|
+
return {
|
|
2047
|
+
status: 200,
|
|
2048
|
+
body: {
|
|
2049
|
+
success: true,
|
|
2050
|
+
result: { state: {}, total: void 0 }
|
|
2051
|
+
}
|
|
2052
|
+
};
|
|
2053
|
+
}
|
|
2054
|
+
const result = await runWithConfig(requestConfig, async () => {
|
|
2055
|
+
return operation.setup({
|
|
2056
|
+
workplaceId: context.workplaceId,
|
|
2057
|
+
appInstallationId: context.appInstallationId,
|
|
2058
|
+
appId: context.appId,
|
|
2059
|
+
input: body.input,
|
|
2060
|
+
env,
|
|
2061
|
+
log
|
|
2062
|
+
});
|
|
2063
|
+
});
|
|
2064
|
+
return {
|
|
2065
|
+
status: 200,
|
|
2066
|
+
body: {
|
|
2067
|
+
success: true,
|
|
2068
|
+
result
|
|
2069
|
+
}
|
|
2070
|
+
};
|
|
2071
|
+
}
|
|
2072
|
+
if (body.method === "iterate") {
|
|
2073
|
+
const result = await runWithConfig(requestConfig, async () => {
|
|
2074
|
+
return operation.iterate({
|
|
2075
|
+
workplaceId: context.workplaceId,
|
|
2076
|
+
appInstallationId: context.appInstallationId,
|
|
2077
|
+
appId: context.appId,
|
|
2078
|
+
input: body.input,
|
|
2079
|
+
state: body.state,
|
|
2080
|
+
page: body.page,
|
|
2081
|
+
cursor: body.cursor,
|
|
2082
|
+
limit: body.limit,
|
|
2083
|
+
env,
|
|
2084
|
+
log
|
|
2085
|
+
});
|
|
2086
|
+
});
|
|
2087
|
+
return {
|
|
2088
|
+
status: 200,
|
|
2089
|
+
body: {
|
|
2090
|
+
success: true,
|
|
2091
|
+
result
|
|
2092
|
+
}
|
|
2093
|
+
};
|
|
2094
|
+
}
|
|
2095
|
+
return {
|
|
2096
|
+
status: 400,
|
|
2097
|
+
body: {
|
|
2098
|
+
success: false,
|
|
2099
|
+
error: `Unknown method: ${body.method}`
|
|
2100
|
+
}
|
|
2101
|
+
};
|
|
2102
|
+
} catch (err) {
|
|
2103
|
+
log.error("Batch operation failed", { error: err });
|
|
2104
|
+
return {
|
|
2105
|
+
status: 500,
|
|
2106
|
+
body: {
|
|
2107
|
+
success: false,
|
|
2108
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2109
|
+
}
|
|
2110
|
+
};
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
1991
2113
|
function createNotFoundResponse() {
|
|
1992
2114
|
return {
|
|
1993
2115
|
status: 404,
|
|
@@ -2067,6 +2189,9 @@ async function routeRequest(req, ctx) {
|
|
|
2067
2189
|
if (req.path === "/mcp/batch" && req.method === "POST") {
|
|
2068
2190
|
return handleMcpBatchRoute(req, ctx);
|
|
2069
2191
|
}
|
|
2192
|
+
if (req.path === "/batch-operation" && req.method === "POST") {
|
|
2193
|
+
return handleBatchOperationRoute(req, ctx);
|
|
2194
|
+
}
|
|
2070
2195
|
return createNotFoundResponse();
|
|
2071
2196
|
} catch (err) {
|
|
2072
2197
|
return createErrorResponse(err);
|
|
@@ -2108,11 +2233,13 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
2108
2233
|
const defaultHeaders = getDefaultHeaders(config.cors);
|
|
2109
2234
|
const registry = config.tools;
|
|
2110
2235
|
const webhookRegistry = config.webhooks;
|
|
2236
|
+
const batchOperationRegistry = config.batchOperations;
|
|
2111
2237
|
const ctx = {
|
|
2112
2238
|
config,
|
|
2113
2239
|
tools,
|
|
2114
2240
|
registry,
|
|
2115
2241
|
webhookRegistry,
|
|
2242
|
+
batchOperationRegistry,
|
|
2116
2243
|
callTool,
|
|
2117
2244
|
state,
|
|
2118
2245
|
mcpServer,
|
|
@@ -2227,12 +2354,14 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer) {
|
|
|
2227
2354
|
const defaultHeaders = getDefaultHeaders(config.cors);
|
|
2228
2355
|
const registry = config.tools;
|
|
2229
2356
|
const webhookRegistry = config.webhooks;
|
|
2357
|
+
const batchOperationRegistry = config.batchOperations;
|
|
2230
2358
|
let hasLoggedStartup = false;
|
|
2231
2359
|
const ctx = {
|
|
2232
2360
|
config,
|
|
2233
2361
|
tools,
|
|
2234
2362
|
registry,
|
|
2235
2363
|
webhookRegistry,
|
|
2364
|
+
batchOperationRegistry,
|
|
2236
2365
|
callTool,
|
|
2237
2366
|
state,
|
|
2238
2367
|
mcpServer,
|