@decocms/runtime 0.0.1-testing-beta.1
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/config-schema.json +553 -0
- package/dist/admin.d.ts +5 -0
- package/dist/admin.js +21 -0
- package/dist/admin.js.map +1 -0
- package/dist/bindings/deconfig/index.d.ts +9 -0
- package/dist/bindings/deconfig/index.js +9 -0
- package/dist/bindings/deconfig/index.js.map +1 -0
- package/dist/bindings/index.d.ts +1053 -0
- package/dist/bindings/index.js +132 -0
- package/dist/bindings/index.js.map +1 -0
- package/dist/chunk-4XSQKJLU.js +105 -0
- package/dist/chunk-4XSQKJLU.js.map +1 -0
- package/dist/chunk-AOFOWQXY.js +27 -0
- package/dist/chunk-AOFOWQXY.js.map +1 -0
- package/dist/chunk-F6XZPFWM.js +127 -0
- package/dist/chunk-F6XZPFWM.js.map +1 -0
- package/dist/chunk-IB3KGSMB.js +150 -0
- package/dist/chunk-IB3KGSMB.js.map +1 -0
- package/dist/chunk-NKUMVYKI.js +128 -0
- package/dist/chunk-NKUMVYKI.js.map +1 -0
- package/dist/chunk-NMXOC7PT.js +763 -0
- package/dist/chunk-NMXOC7PT.js.map +1 -0
- package/dist/chunk-OSSKGDAG.js +395 -0
- package/dist/chunk-OSSKGDAG.js.map +1 -0
- package/dist/chunk-UHR3BLMF.js +92 -0
- package/dist/chunk-UHR3BLMF.js.map +1 -0
- package/dist/client.d.ts +28 -0
- package/dist/client.js +4 -0
- package/dist/client.js.map +1 -0
- package/dist/connection-DDtQYrea.d.ts +30 -0
- package/dist/drizzle.d.ts +47 -0
- package/dist/drizzle.js +121 -0
- package/dist/drizzle.js.map +1 -0
- package/dist/index-AKVjfH4b.d.ts +336 -0
- package/dist/index-kMsI0ELb.d.ts +530 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +507 -0
- package/dist/index.js.map +1 -0
- package/dist/mastra.d.ts +8 -0
- package/dist/mastra.js +5 -0
- package/dist/mastra.js.map +1 -0
- package/dist/mcp-Bv7IAgWX.d.ts +109 -0
- package/dist/mcp-client.d.ts +236 -0
- package/dist/mcp-client.js +3 -0
- package/dist/mcp-client.js.map +1 -0
- package/dist/proxy.d.ts +10 -0
- package/dist/proxy.js +4 -0
- package/dist/proxy.js.map +1 -0
- package/dist/resources.d.ts +362 -0
- package/dist/resources.js +3 -0
- package/dist/resources.js.map +1 -0
- package/dist/views.d.ts +72 -0
- package/dist/views.js +3 -0
- package/dist/views.js.map +1 -0
- package/package.json +98 -0
- package/src/admin.ts +16 -0
- package/src/auth.ts +233 -0
- package/src/bindings/README.md +132 -0
- package/src/bindings/binder.ts +143 -0
- package/src/bindings/channels.ts +54 -0
- package/src/bindings/deconfig/helpers.ts +107 -0
- package/src/bindings/deconfig/index.ts +1 -0
- package/src/bindings/deconfig/resources.ts +659 -0
- package/src/bindings/deconfig/types.ts +106 -0
- package/src/bindings/index.ts +61 -0
- package/src/bindings/resources/bindings.ts +99 -0
- package/src/bindings/resources/helpers.ts +95 -0
- package/src/bindings/resources/schemas.ts +265 -0
- package/src/bindings/utils.ts +22 -0
- package/src/bindings/views.ts +14 -0
- package/src/bindings.ts +179 -0
- package/src/client.ts +201 -0
- package/src/connection.ts +53 -0
- package/src/drizzle.ts +201 -0
- package/src/http-client-transport.ts +66 -0
- package/src/index.ts +394 -0
- package/src/mastra.ts +666 -0
- package/src/mcp-client.ts +119 -0
- package/src/mcp.ts +171 -0
- package/src/proxy.ts +204 -0
- package/src/resources.ts +168 -0
- package/src/state.ts +44 -0
- package/src/views.ts +26 -0
- package/src/well-known.ts +20 -0
- package/src/wrangler.ts +146 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
/* oxlint-disable no-explicit-any */
|
|
2
|
+
import type { ExecutionContext } from "@cloudflare/workers-types";
|
|
3
|
+
import { decodeJwt } from "jose";
|
|
4
|
+
import type { z } from "zod";
|
|
5
|
+
import {
|
|
6
|
+
getReqToken,
|
|
7
|
+
handleAuthCallback,
|
|
8
|
+
handleLogout,
|
|
9
|
+
StateParser,
|
|
10
|
+
} from "./auth.ts";
|
|
11
|
+
import {
|
|
12
|
+
createContractBinding,
|
|
13
|
+
createIntegrationBinding,
|
|
14
|
+
workspaceClient,
|
|
15
|
+
} from "./bindings.ts";
|
|
16
|
+
import { DeconfigResource } from "./bindings/deconfig/index.ts";
|
|
17
|
+
import { DECO_MCP_CLIENT_HEADER } from "./client.ts";
|
|
18
|
+
import {
|
|
19
|
+
createMCPServer,
|
|
20
|
+
type CreateMCPServerOptions,
|
|
21
|
+
MCPServer,
|
|
22
|
+
} from "./mastra.ts";
|
|
23
|
+
import { MCPClient, type QueryResult } from "./mcp.ts";
|
|
24
|
+
import { State } from "./state.ts";
|
|
25
|
+
import type { Binding, ContractBinding, MCPBinding } from "./wrangler.ts";
|
|
26
|
+
export { proxyConnectionForId } from "./bindings.ts";
|
|
27
|
+
export {
|
|
28
|
+
createMCPFetchStub,
|
|
29
|
+
type CreateStubAPIOptions,
|
|
30
|
+
type ToolBinder,
|
|
31
|
+
} from "./mcp.ts";
|
|
32
|
+
export interface WorkspaceDB {
|
|
33
|
+
query: (params: {
|
|
34
|
+
sql: string;
|
|
35
|
+
params: string[];
|
|
36
|
+
}) => Promise<{ result: QueryResult[] }>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface DefaultEnv<TSchema extends z.ZodTypeAny = any> {
|
|
40
|
+
DECO_REQUEST_CONTEXT: RequestContext<TSchema>;
|
|
41
|
+
DECO_APP_NAME: string;
|
|
42
|
+
DECO_APP_SLUG: string;
|
|
43
|
+
DECO_APP_ENTRYPOINT: string;
|
|
44
|
+
DECO_API_URL?: string;
|
|
45
|
+
DECO_WORKSPACE: string;
|
|
46
|
+
DECO_API_JWT_PUBLIC_KEY: string;
|
|
47
|
+
DECO_APP_DEPLOYMENT_ID: string;
|
|
48
|
+
DECO_BINDINGS: string;
|
|
49
|
+
DECO_API_TOKEN: string;
|
|
50
|
+
DECO_WORKSPACE_DB: WorkspaceDB & {
|
|
51
|
+
forContext: (ctx: RequestContext) => WorkspaceDB;
|
|
52
|
+
};
|
|
53
|
+
IS_LOCAL: boolean;
|
|
54
|
+
[key: string]: unknown;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface BindingsObject {
|
|
58
|
+
bindings?: Binding[];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const WorkersMCPBindings = {
|
|
62
|
+
parse: (bindings?: string): Binding[] => {
|
|
63
|
+
if (!bindings) return [];
|
|
64
|
+
try {
|
|
65
|
+
return JSON.parse(atob(bindings)) as Binding[];
|
|
66
|
+
} catch {
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
stringify: (bindings: Binding[]): string => {
|
|
71
|
+
return btoa(JSON.stringify(bindings));
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export interface UserDefaultExport<
|
|
76
|
+
TUserEnv = Record<string, unknown>,
|
|
77
|
+
TSchema extends z.ZodTypeAny = never,
|
|
78
|
+
TEnv = TUserEnv & DefaultEnv<TSchema>,
|
|
79
|
+
> extends CreateMCPServerOptions<TEnv, TSchema> {
|
|
80
|
+
fetch?: (
|
|
81
|
+
req: Request,
|
|
82
|
+
env: TEnv,
|
|
83
|
+
ctx: ExecutionContext,
|
|
84
|
+
) => Promise<Response> | Response;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// 1. Map binding type to its interface
|
|
88
|
+
interface BindingTypeMap {
|
|
89
|
+
mcp: MCPBinding;
|
|
90
|
+
contract: ContractBinding;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface User {
|
|
94
|
+
id: string;
|
|
95
|
+
email: string;
|
|
96
|
+
workspace: string;
|
|
97
|
+
user_metadata: {
|
|
98
|
+
avatar_url: string;
|
|
99
|
+
full_name: string;
|
|
100
|
+
picture: string;
|
|
101
|
+
[key: string]: unknown;
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface RequestContext<TSchema extends z.ZodTypeAny = any> {
|
|
106
|
+
state: z.infer<TSchema>;
|
|
107
|
+
branch?: string;
|
|
108
|
+
token: string;
|
|
109
|
+
workspace: string;
|
|
110
|
+
ensureAuthenticated: (options?: {
|
|
111
|
+
workspaceHint?: string;
|
|
112
|
+
}) => User | undefined;
|
|
113
|
+
callerApp?: string;
|
|
114
|
+
integrationId?: string;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 2. Map binding type to its creator function
|
|
118
|
+
type CreatorByType = {
|
|
119
|
+
[K in keyof BindingTypeMap]: (
|
|
120
|
+
value: BindingTypeMap[K],
|
|
121
|
+
env: DefaultEnv,
|
|
122
|
+
) => unknown;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// 3. Strongly type creatorByType
|
|
126
|
+
const creatorByType: CreatorByType = {
|
|
127
|
+
mcp: createIntegrationBinding,
|
|
128
|
+
contract: createContractBinding,
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const withDefaultBindings = ({
|
|
132
|
+
env,
|
|
133
|
+
server,
|
|
134
|
+
ctx,
|
|
135
|
+
url,
|
|
136
|
+
}: {
|
|
137
|
+
env: DefaultEnv;
|
|
138
|
+
server: MCPServer<any, any>;
|
|
139
|
+
ctx: RequestContext;
|
|
140
|
+
url?: string;
|
|
141
|
+
}) => {
|
|
142
|
+
const client = workspaceClient(ctx, env.DECO_API_URL);
|
|
143
|
+
const createWorkspaceDB = (ctx: RequestContext): WorkspaceDB => {
|
|
144
|
+
const client = workspaceClient(ctx, env.DECO_API_URL);
|
|
145
|
+
return {
|
|
146
|
+
query: ({ sql, params }) => {
|
|
147
|
+
return client.DATABASES_RUN_SQL({
|
|
148
|
+
sql,
|
|
149
|
+
params,
|
|
150
|
+
});
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
};
|
|
154
|
+
env["SELF"] = new Proxy(
|
|
155
|
+
{},
|
|
156
|
+
{
|
|
157
|
+
get: (_, prop) => {
|
|
158
|
+
if (prop === "toJSON") {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return async (args: unknown) => {
|
|
163
|
+
return await server.callTool({
|
|
164
|
+
toolCallId: prop as string,
|
|
165
|
+
toolCallInput: args,
|
|
166
|
+
});
|
|
167
|
+
};
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
const workspaceDbBinding = {
|
|
173
|
+
...createWorkspaceDB(ctx),
|
|
174
|
+
forContext: createWorkspaceDB,
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
env["DECO_API"] = MCPClient;
|
|
178
|
+
env["DECO_WORKSPACE_API"] = client;
|
|
179
|
+
env["DECO_WORKSPACE_DB"] = workspaceDbBinding;
|
|
180
|
+
|
|
181
|
+
env["IS_LOCAL"] =
|
|
182
|
+
(url?.startsWith("http://localhost") ||
|
|
183
|
+
url?.startsWith("http://127.0.0.1")) ??
|
|
184
|
+
false;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
export class UnauthorizedError extends Error {
|
|
188
|
+
constructor(
|
|
189
|
+
message: string,
|
|
190
|
+
public redirectTo: URL,
|
|
191
|
+
) {
|
|
192
|
+
super(message);
|
|
193
|
+
this.name = "UnauthorizedError";
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const AUTH_CALLBACK_ENDPOINT = "/oauth/callback";
|
|
198
|
+
const AUTH_START_ENDPOINT = "/oauth/start";
|
|
199
|
+
const AUTH_LOGOUT_ENDPOINT = "/oauth/logout";
|
|
200
|
+
const AUTHENTICATED = (user?: unknown, workspace?: string) => () => {
|
|
201
|
+
return {
|
|
202
|
+
...((user as User) ?? {}),
|
|
203
|
+
workspace,
|
|
204
|
+
} as User;
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
export const withBindings = <TEnv>({
|
|
208
|
+
env: _env,
|
|
209
|
+
server,
|
|
210
|
+
tokenOrContext,
|
|
211
|
+
origin,
|
|
212
|
+
url,
|
|
213
|
+
branch,
|
|
214
|
+
}: {
|
|
215
|
+
env: TEnv;
|
|
216
|
+
server: MCPServer<TEnv, any>;
|
|
217
|
+
tokenOrContext?: string | RequestContext;
|
|
218
|
+
origin?: string | null;
|
|
219
|
+
url?: string;
|
|
220
|
+
branch?: string | null;
|
|
221
|
+
}): TEnv => {
|
|
222
|
+
branch ??= undefined;
|
|
223
|
+
const env = _env as DefaultEnv<any>;
|
|
224
|
+
|
|
225
|
+
const apiUrl = env.DECO_API_URL ?? "https://api.decocms.com";
|
|
226
|
+
let context;
|
|
227
|
+
if (typeof tokenOrContext === "string") {
|
|
228
|
+
const decoded = decodeJwt(tokenOrContext);
|
|
229
|
+
const workspace = decoded.aud as string;
|
|
230
|
+
|
|
231
|
+
context = {
|
|
232
|
+
state: decoded.state as Record<string, unknown>,
|
|
233
|
+
token: tokenOrContext,
|
|
234
|
+
integrationId: decoded.integrationId as string,
|
|
235
|
+
workspace,
|
|
236
|
+
ensureAuthenticated: AUTHENTICATED(decoded.user, workspace),
|
|
237
|
+
branch,
|
|
238
|
+
} as RequestContext<any>;
|
|
239
|
+
} else if (typeof tokenOrContext === "object") {
|
|
240
|
+
context = tokenOrContext;
|
|
241
|
+
const decoded = decodeJwt(tokenOrContext.token);
|
|
242
|
+
const workspace = decoded.aud as string;
|
|
243
|
+
const appName = decoded.appName as string | undefined;
|
|
244
|
+
context.callerApp = appName;
|
|
245
|
+
context.integrationId ??= decoded.integrationId as string;
|
|
246
|
+
context.ensureAuthenticated = AUTHENTICATED(decoded.user, workspace);
|
|
247
|
+
} else {
|
|
248
|
+
context = {
|
|
249
|
+
state: undefined,
|
|
250
|
+
token: env.DECO_API_TOKEN,
|
|
251
|
+
workspace: env.DECO_WORKSPACE,
|
|
252
|
+
branch,
|
|
253
|
+
ensureAuthenticated: (options?: { workspaceHint?: string }) => {
|
|
254
|
+
const workspaceHint = options?.workspaceHint ?? env.DECO_WORKSPACE;
|
|
255
|
+
const authUri = new URL("/apps/oauth", apiUrl);
|
|
256
|
+
authUri.searchParams.set("client_id", env.DECO_APP_NAME);
|
|
257
|
+
authUri.searchParams.set(
|
|
258
|
+
"redirect_uri",
|
|
259
|
+
new URL(AUTH_CALLBACK_ENDPOINT, origin ?? env.DECO_APP_ENTRYPOINT)
|
|
260
|
+
.href,
|
|
261
|
+
);
|
|
262
|
+
workspaceHint &&
|
|
263
|
+
authUri.searchParams.set("workspace_hint", workspaceHint);
|
|
264
|
+
throw new UnauthorizedError("Unauthorized", authUri);
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
env.DECO_REQUEST_CONTEXT = context;
|
|
270
|
+
const bindings = WorkersMCPBindings.parse(env.DECO_BINDINGS);
|
|
271
|
+
|
|
272
|
+
for (const binding of bindings) {
|
|
273
|
+
env[binding.name] = creatorByType[binding.type](binding as any, env);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
withDefaultBindings({
|
|
277
|
+
env,
|
|
278
|
+
server,
|
|
279
|
+
ctx: env.DECO_REQUEST_CONTEXT,
|
|
280
|
+
url,
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
return env as TEnv;
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
export const withRuntime = <TEnv, TSchema extends z.ZodTypeAny = never>(
|
|
287
|
+
userFns: UserDefaultExport<TEnv, TSchema>,
|
|
288
|
+
): ExportedHandler<TEnv & DefaultEnv<TSchema>> => {
|
|
289
|
+
const server = createMCPServer<TEnv, TSchema>(userFns);
|
|
290
|
+
const fetcher = async (
|
|
291
|
+
req: Request,
|
|
292
|
+
env: TEnv & DefaultEnv<TSchema>,
|
|
293
|
+
ctx: ExecutionContext,
|
|
294
|
+
) => {
|
|
295
|
+
const url = new URL(req.url);
|
|
296
|
+
if (url.pathname === AUTH_CALLBACK_ENDPOINT) {
|
|
297
|
+
return handleAuthCallback(req, {
|
|
298
|
+
apiUrl: env.DECO_API_URL,
|
|
299
|
+
appName: env.DECO_APP_NAME,
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
if (url.pathname === AUTH_START_ENDPOINT) {
|
|
303
|
+
env.DECO_REQUEST_CONTEXT.ensureAuthenticated();
|
|
304
|
+
const redirectTo = new URL("/", url);
|
|
305
|
+
const next = url.searchParams.get("next");
|
|
306
|
+
return Response.redirect(next ?? redirectTo, 302);
|
|
307
|
+
}
|
|
308
|
+
if (url.pathname === AUTH_LOGOUT_ENDPOINT) {
|
|
309
|
+
return handleLogout(req);
|
|
310
|
+
}
|
|
311
|
+
if (url.pathname === "/mcp") {
|
|
312
|
+
return server.fetch(req, env, ctx);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (url.pathname.startsWith("/mcp/call-tool")) {
|
|
316
|
+
const toolCallId = url.pathname.split("/").pop();
|
|
317
|
+
if (!toolCallId) {
|
|
318
|
+
return new Response("Not found", { status: 404 });
|
|
319
|
+
}
|
|
320
|
+
const toolCallInput = await req.json();
|
|
321
|
+
const result = await server.callTool({
|
|
322
|
+
toolCallId,
|
|
323
|
+
toolCallInput,
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
if (result instanceof Response) {
|
|
327
|
+
return result;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return new Response(JSON.stringify(result), {
|
|
331
|
+
headers: {
|
|
332
|
+
"Content-Type": "application/json",
|
|
333
|
+
},
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (url.pathname.startsWith(DeconfigResource.WatchPathNameBase)) {
|
|
338
|
+
return DeconfigResource.watchAPI(req, env);
|
|
339
|
+
}
|
|
340
|
+
return (
|
|
341
|
+
userFns.fetch?.(req, env, ctx) ||
|
|
342
|
+
new Response("Not found", { status: 404 })
|
|
343
|
+
);
|
|
344
|
+
};
|
|
345
|
+
return {
|
|
346
|
+
fetch: async (
|
|
347
|
+
req: Request,
|
|
348
|
+
env: TEnv & DefaultEnv<TSchema>,
|
|
349
|
+
ctx: ExecutionContext,
|
|
350
|
+
) => {
|
|
351
|
+
const referer = req.headers.get("referer");
|
|
352
|
+
const isFetchRequest = req.headers.has(DECO_MCP_CLIENT_HEADER);
|
|
353
|
+
|
|
354
|
+
try {
|
|
355
|
+
const bindings = withBindings({
|
|
356
|
+
env,
|
|
357
|
+
server,
|
|
358
|
+
branch:
|
|
359
|
+
req.headers.get("x-deco-branch") ??
|
|
360
|
+
new URL(req.url).searchParams.get("__b"),
|
|
361
|
+
tokenOrContext: await getReqToken(req, env),
|
|
362
|
+
origin:
|
|
363
|
+
referer ?? req.headers.get("origin") ?? new URL(req.url).origin,
|
|
364
|
+
url: req.url,
|
|
365
|
+
});
|
|
366
|
+
return await State.run(
|
|
367
|
+
{ req, env: bindings, ctx },
|
|
368
|
+
async () => await fetcher(req, bindings, ctx),
|
|
369
|
+
);
|
|
370
|
+
} catch (error) {
|
|
371
|
+
if (error instanceof UnauthorizedError) {
|
|
372
|
+
if (!isFetchRequest) {
|
|
373
|
+
const url = new URL(req.url);
|
|
374
|
+
error.redirectTo.searchParams.set(
|
|
375
|
+
"state",
|
|
376
|
+
StateParser.stringify({
|
|
377
|
+
next: url.searchParams.get("next") ?? referer ?? req.url,
|
|
378
|
+
}),
|
|
379
|
+
);
|
|
380
|
+
return Response.redirect(error.redirectTo, 302);
|
|
381
|
+
}
|
|
382
|
+
return new Response(null, { status: 401 });
|
|
383
|
+
}
|
|
384
|
+
throw error;
|
|
385
|
+
}
|
|
386
|
+
},
|
|
387
|
+
};
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
export {
|
|
391
|
+
type Contract,
|
|
392
|
+
type Migration,
|
|
393
|
+
type WranglerConfig,
|
|
394
|
+
} from "./wrangler.ts";
|