@cargo-ai/worker-sdk 1.0.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/README.md +90 -0
- package/build/src/createWorker.d.ts +51 -0
- package/build/src/createWorker.d.ts.map +1 -0
- package/build/src/createWorker.js +44 -0
- package/build/src/customIntegration.d.ts +97 -0
- package/build/src/customIntegration.d.ts.map +1 -0
- package/build/src/customIntegration.js +212 -0
- package/build/src/index.d.ts +9 -0
- package/build/src/index.d.ts.map +1 -0
- package/build/src/index.js +4 -0
- package/build/src/types.d.ts +15 -0
- package/build/src/types.d.ts.map +1 -0
- package/build/src/types.js +1 -0
- package/build/tsconfig.tsbuildinfo +1 -0
- package/package.json +47 -0
- package/templates/blank/README.md +112 -0
- package/templates/blank/manifest.json +3 -0
- package/templates/blank/package.json +20 -0
- package/templates/blank/scripts/copy-runtime-files.mjs +25 -0
- package/templates/blank/src/index.ts +46 -0
- package/templates/blank/tsconfig.json +24 -0
- package/templates/custom-integration/.env.example +5 -0
- package/templates/custom-integration/README.md +136 -0
- package/templates/custom-integration/manifest.json +3 -0
- package/templates/custom-integration/package.json +21 -0
- package/templates/custom-integration/scripts/copy-runtime-files.mjs +25 -0
- package/templates/custom-integration/src/actions/createRecord.ts +34 -0
- package/templates/custom-integration/src/actions/index.ts +20 -0
- package/templates/custom-integration/src/authenticate.ts +27 -0
- package/templates/custom-integration/src/autocompletes/index.ts +13 -0
- package/templates/custom-integration/src/completeOauth.ts +10 -0
- package/templates/custom-integration/src/connectorConfig.ts +16 -0
- package/templates/custom-integration/src/dynamicSchemas/index.ts +13 -0
- package/templates/custom-integration/src/extractors/index.ts +18 -0
- package/templates/custom-integration/src/extractors/listRecords.ts +51 -0
- package/templates/custom-integration/src/index.ts +44 -0
- package/templates/custom-integration/src/listUsers.ts +11 -0
- package/templates/custom-integration/tsconfig.json +24 -0
package/README.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# @cargo-ai/worker-sdk
|
|
2
|
+
|
|
3
|
+
Build Cargo Hosting **workers** — serverless edge HTTP handlers running on `*.worker.getcargo.io` — with automatic OpenAPI 3.1 generation. Hono + Chanfana + Zod are wrapped in two opinionated factories so you define routes once and get runtime validation, a Cargo-native manifest, and live OpenAPI docs for free.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install @cargo-ai/worker-sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Write a worker
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { type Context, createWorker, OpenAPIRoute } from "@cargo-ai/worker-sdk";
|
|
15
|
+
import { z } from "zod";
|
|
16
|
+
|
|
17
|
+
const { app, openapi } = createWorker({ title: "my-worker" });
|
|
18
|
+
|
|
19
|
+
class GetHello extends OpenAPIRoute {
|
|
20
|
+
override schema = {
|
|
21
|
+
request: {
|
|
22
|
+
query: z.object({ name: z.string().default("world") }),
|
|
23
|
+
},
|
|
24
|
+
responses: {
|
|
25
|
+
"200": {
|
|
26
|
+
description: "Greeting.",
|
|
27
|
+
content: {
|
|
28
|
+
"application/json": {
|
|
29
|
+
schema: z.object({ message: z.string() }),
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
override async handle(c: Context): Promise<Response> {
|
|
37
|
+
const { query } = await this.getValidatedData<typeof this.schema>();
|
|
38
|
+
return c.json({ message: `Hello, ${query.name}!` });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
openapi.get("/hello", GetHello);
|
|
43
|
+
|
|
44
|
+
export default app;
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`createWorker` gives you:
|
|
48
|
+
|
|
49
|
+
- `GET /openapi.json` — OpenAPI 3.1 spec derived from every route's Zod schema.
|
|
50
|
+
- `GET /docs` — Swagger UI.
|
|
51
|
+
|
|
52
|
+
## Write a custom integration
|
|
53
|
+
|
|
54
|
+
`createCustomIntegration({ info, connector, authenticate, actions, extractors, ... })` builds a worker that implements the [Cargo Custom Integration HTTP contract](https://docs.getcargo.ai/integration/custom-integration) from a single declarative spec. The same Zod schemas feed `GET /manifest` (Cargo-native wire format) and `GET /openapi.json`. See `templates/custom-integration/`.
|
|
55
|
+
|
|
56
|
+
Integration-domain types (`IntegrationActionExecutePayload`, `IntegrationExtractorFetchResult`, …) live in [`@cargo-ai/types`](https://www.npmjs.com/package/@cargo-ai/types) under the `ConnectionTypes` namespace — you rarely need to import them directly because the SDK's `CustomIntegrationAction` / `CustomIntegrationExtractor` types wire them in for you.
|
|
57
|
+
|
|
58
|
+
## Scaffold a project
|
|
59
|
+
|
|
60
|
+
You almost never want to wire this from scratch. Use the CLI:
|
|
61
|
+
|
|
62
|
+
```sh
|
|
63
|
+
# Blank edge handler
|
|
64
|
+
npx @cargo-ai/cli hosting worker init my-worker --template blank
|
|
65
|
+
|
|
66
|
+
# Worker that implements the Cargo Custom Integration HTTP contract
|
|
67
|
+
npx @cargo-ai/cli hosting worker init my-integration --template custom-integration
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The CLI copies the matching template from `@cargo-ai/worker-sdk/templates/<slug>/` into your target directory.
|
|
71
|
+
|
|
72
|
+
## Build & deploy
|
|
73
|
+
|
|
74
|
+
Templates ship with a `build` script that runs `tsc` and copies `manifest.json`, `package.json`, and `package-lock.json` into `dist/`. The bundle uploaded to Cargo Hosting is the contents of `dist/`:
|
|
75
|
+
|
|
76
|
+
```sh
|
|
77
|
+
npm install
|
|
78
|
+
npm run build
|
|
79
|
+
cargo-ai hosting deployment create --worker-uuid <workerUuid> --source ./dist
|
|
80
|
+
cargo-ai hosting deployment promote --uuid <deploymentUuid>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The Cargo Hosting build pipeline runs `npm ci` against `dist/package.json` and `esbuild dist/index.js --bundle --format=esm --platform=neutral --target=es2022` to produce the final edge bundle. So you can ship multi-file TypeScript projects — esbuild will tree-shake everything into a single ESM file at deploy time.
|
|
84
|
+
|
|
85
|
+
## Available templates
|
|
86
|
+
|
|
87
|
+
- `blank` — a ready-to-extend Hono app built by `createWorker()`, with one sample route and `/openapi.json` + `/docs` out of the box. Use this when the worker just responds to inbound HTTP and doesn't need to integrate into the Cargo connector catalog.
|
|
88
|
+
- `custom-integration` — a ready-to-extend spec passed to `createCustomIntegration()`. Handles `/manifest`, `/openapi.json`, `/docs`, `/authenticate`, `/actions/<slug>/execute`, `/extractors/<slug>/{fetch,count}`, `/autocompletes/<slug>`, `/dynamicSchemas/<slug>`, `/completeOauth`, `/listUsers` from a single declaration. Edge-native equivalent of [`getcargohq/dummy-integration`](https://github.com/getcargohq/dummy-integration). After deploying, register it as a connector with `cargo-ai connection custom-integration create --kind worker --worker-uuid <workerUuid>`.
|
|
89
|
+
|
|
90
|
+
See each template's own `README.md` for the file layout and how to extend it.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type HonoOpenAPIRouterType } from "chanfana";
|
|
2
|
+
import { Hono } from "hono";
|
|
3
|
+
import type { WorkerEnv } from "./types.js";
|
|
4
|
+
export interface CreateWorkerOptions {
|
|
5
|
+
/** Title shown in the generated OpenAPI spec. */
|
|
6
|
+
title: string;
|
|
7
|
+
/** Defaults to `"0.0.1"`. */
|
|
8
|
+
version?: string;
|
|
9
|
+
/** Optional long description shown in the generated OpenAPI spec. */
|
|
10
|
+
description?: string;
|
|
11
|
+
/** Path where Swagger UI is served. Defaults to `/docs`. */
|
|
12
|
+
docsUrl?: string;
|
|
13
|
+
/** Path where the OpenAPI 3.1 document is served. Defaults to `/openapi.json`. */
|
|
14
|
+
openapiUrl?: string;
|
|
15
|
+
}
|
|
16
|
+
export type WorkerApp<Env extends WorkerEnv = WorkerEnv> = Hono<{
|
|
17
|
+
Bindings: Env;
|
|
18
|
+
}>;
|
|
19
|
+
export type WorkerOpenAPI<Env extends WorkerEnv = WorkerEnv> = HonoOpenAPIRouterType<{
|
|
20
|
+
Bindings: Env;
|
|
21
|
+
}>;
|
|
22
|
+
/**
|
|
23
|
+
* Creates a Hono app wrapped with Chanfana so every route you register on the
|
|
24
|
+
* returned `openapi` handle is automatically described in the generated
|
|
25
|
+
* OpenAPI 3.1 spec served at `openapiUrl` (and browsable at `docsUrl`).
|
|
26
|
+
*
|
|
27
|
+
* `app` and `openapi` are two views of the same router: use `app` to add
|
|
28
|
+
* un-described Hono middleware / routes, and use `openapi` to register typed
|
|
29
|
+
* endpoints that extend `OpenAPIRoute`.
|
|
30
|
+
*
|
|
31
|
+
* ```ts
|
|
32
|
+
* import { createWorker, OpenAPIRoute } from "@cargo-ai/worker-sdk";
|
|
33
|
+
* import { z } from "zod";
|
|
34
|
+
*
|
|
35
|
+
* const { app, openapi } = createWorker({ title: "my-worker" });
|
|
36
|
+
*
|
|
37
|
+
* class GetHello extends OpenAPIRoute {
|
|
38
|
+
* schema = { ... };
|
|
39
|
+
* async handle(c) { ... }
|
|
40
|
+
* }
|
|
41
|
+
*
|
|
42
|
+
* openapi.get("/hello", GetHello);
|
|
43
|
+
*
|
|
44
|
+
* export default app;
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export declare const createWorker: <Env extends WorkerEnv = WorkerEnv>(options: CreateWorkerOptions) => {
|
|
48
|
+
app: WorkerApp<Env>;
|
|
49
|
+
openapi: WorkerOpenAPI<Env>;
|
|
50
|
+
};
|
|
51
|
+
//# sourceMappingURL=createWorker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createWorker.d.ts","sourceRoot":"","sources":["../../src/createWorker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAChE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,MAAM,WAAW,mBAAmB;IAClC,iDAAiD;IACjD,KAAK,EAAE,MAAM,CAAC;IACd,6BAA6B;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qEAAqE;IACrE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4DAA4D;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kFAAkF;IAClF,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,SAAS,CAAC,GAAG,SAAS,SAAS,GAAG,SAAS,IAAI,IAAI,CAAC;IAC9D,QAAQ,EAAE,GAAG,CAAC;CACf,CAAC,CAAC;AAEH,MAAM,MAAM,aAAa,CAAC,GAAG,SAAS,SAAS,GAAG,SAAS,IACzD,qBAAqB,CAAC;IAAE,QAAQ,EAAE,GAAG,CAAA;CAAE,CAAC,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,YAAY,+CACd,mBAAmB;;;CAoB7B,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { fromHono } from "chanfana";
|
|
2
|
+
import { Hono } from "hono";
|
|
3
|
+
/**
|
|
4
|
+
* Creates a Hono app wrapped with Chanfana so every route you register on the
|
|
5
|
+
* returned `openapi` handle is automatically described in the generated
|
|
6
|
+
* OpenAPI 3.1 spec served at `openapiUrl` (and browsable at `docsUrl`).
|
|
7
|
+
*
|
|
8
|
+
* `app` and `openapi` are two views of the same router: use `app` to add
|
|
9
|
+
* un-described Hono middleware / routes, and use `openapi` to register typed
|
|
10
|
+
* endpoints that extend `OpenAPIRoute`.
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { createWorker, OpenAPIRoute } from "@cargo-ai/worker-sdk";
|
|
14
|
+
* import { z } from "zod";
|
|
15
|
+
*
|
|
16
|
+
* const { app, openapi } = createWorker({ title: "my-worker" });
|
|
17
|
+
*
|
|
18
|
+
* class GetHello extends OpenAPIRoute {
|
|
19
|
+
* schema = { ... };
|
|
20
|
+
* async handle(c) { ... }
|
|
21
|
+
* }
|
|
22
|
+
*
|
|
23
|
+
* openapi.get("/hello", GetHello);
|
|
24
|
+
*
|
|
25
|
+
* export default app;
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export const createWorker = (options) => {
|
|
29
|
+
const app = new Hono();
|
|
30
|
+
const openapi = fromHono(app, {
|
|
31
|
+
docs_url: options.docsUrl !== undefined ? options.docsUrl : "/docs",
|
|
32
|
+
openapi_url: options.openapiUrl !== undefined ? options.openapiUrl : "/openapi.json",
|
|
33
|
+
schema: {
|
|
34
|
+
info: {
|
|
35
|
+
title: options.title,
|
|
36
|
+
version: options.version !== undefined ? options.version : "0.0.1",
|
|
37
|
+
...(options.description !== undefined
|
|
38
|
+
? { description: options.description }
|
|
39
|
+
: {}),
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
return { app, openapi };
|
|
44
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { ConnectionTypes } from "@cargo-ai/types";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { type WorkerApp, type WorkerOpenAPI } from "./createWorker.js";
|
|
4
|
+
import type { WorkerEnv } from "./types.js";
|
|
5
|
+
/**
|
|
6
|
+
* A single Custom Integration action — e.g. "Create Record". The zod
|
|
7
|
+
* `ConfigSchema` is the source of truth for the action's per-invocation config
|
|
8
|
+
* and drives:
|
|
9
|
+
* - runtime validation of `payload.config`
|
|
10
|
+
* - the JSON Schema exposed at `GET /manifest#actions[slug].config.jsonSchema`
|
|
11
|
+
* - the OpenAPI schema for `POST /actions/<slug>/execute`
|
|
12
|
+
*/
|
|
13
|
+
export interface CustomIntegrationAction<ConnectorConfig, ConfigSchema extends z.ZodType = z.ZodType> {
|
|
14
|
+
name: string;
|
|
15
|
+
description: string;
|
|
16
|
+
config: {
|
|
17
|
+
schema: ConfigSchema;
|
|
18
|
+
uiSchema?: Record<string, unknown>;
|
|
19
|
+
};
|
|
20
|
+
execute: (payload: ConnectionTypes.IntegrationActionExecutePayload<ConnectorConfig, z.infer<ConfigSchema>>) => Promise<ConnectionTypes.IntegrationActionExecuteResult>;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* A single Custom Integration extractor — e.g. "List Records". Both
|
|
24
|
+
* `ConfigSchema` and `MetaSchema` are source-of-truth for the manifest +
|
|
25
|
+
* OpenAPI + runtime validation.
|
|
26
|
+
*/
|
|
27
|
+
export interface CustomIntegrationExtractor<ConnectorConfig, ConfigSchema extends z.ZodType = z.ZodType, MetaSchema extends z.ZodType = z.ZodType> {
|
|
28
|
+
name: string;
|
|
29
|
+
description: string;
|
|
30
|
+
config: {
|
|
31
|
+
schema: ConfigSchema;
|
|
32
|
+
uiSchema?: Record<string, unknown>;
|
|
33
|
+
};
|
|
34
|
+
meta: {
|
|
35
|
+
schema: MetaSchema;
|
|
36
|
+
};
|
|
37
|
+
mode: ConnectionTypes.IntegrationExtractorMode;
|
|
38
|
+
preview: "none" | "records" | "count";
|
|
39
|
+
fetch: (payload: ConnectionTypes.IntegrationExtractorFetchPayload<ConnectorConfig, z.infer<ConfigSchema>, z.infer<MetaSchema>>) => Promise<ConnectionTypes.IntegrationExtractorFetchResult<z.infer<MetaSchema>>>;
|
|
40
|
+
count: (payload: ConnectionTypes.IntegrationExtractorCountPayload<ConnectorConfig, z.infer<ConfigSchema>>) => Promise<ConnectionTypes.IntegrationExtractorCountResult>;
|
|
41
|
+
}
|
|
42
|
+
export type CustomIntegrationAutocomplete<ConnectorConfig> = (payload: ConnectionTypes.IntegrationAutocompletePayload<ConnectorConfig>) => Promise<ConnectionTypes.IntegrationAutocompleteResult>;
|
|
43
|
+
export type CustomIntegrationDynamicSchema<ConnectorConfig> = (payload: ConnectionTypes.IntegrationDynamicSchemaGetPayload<ConnectorConfig>) => Promise<{
|
|
44
|
+
jsonSchema: Record<string, unknown>;
|
|
45
|
+
uiSchema?: Record<string, unknown>;
|
|
46
|
+
}>;
|
|
47
|
+
export interface CustomIntegration<ConnectorConfigSchema extends z.ZodType> {
|
|
48
|
+
info: {
|
|
49
|
+
name: string;
|
|
50
|
+
description: string;
|
|
51
|
+
icon?: string;
|
|
52
|
+
color?: string;
|
|
53
|
+
url?: string;
|
|
54
|
+
version?: string;
|
|
55
|
+
};
|
|
56
|
+
connector: {
|
|
57
|
+
config: {
|
|
58
|
+
schema: ConnectorConfigSchema;
|
|
59
|
+
uiSchema?: Record<string, unknown>;
|
|
60
|
+
};
|
|
61
|
+
rateLimit?: {
|
|
62
|
+
unit: "second" | "minute" | "hour";
|
|
63
|
+
max: number;
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
authenticate: (payload: ConnectionTypes.IntegrationAuthenticatePayload<z.infer<ConnectorConfigSchema>>) => Promise<ConnectionTypes.IntegrationAuthenticateResult>;
|
|
67
|
+
listUsers?: (payload: ConnectionTypes.IntegrationListUsersPayload<z.infer<ConnectorConfigSchema>>) => Promise<ConnectionTypes.IntegrationUserData[]>;
|
|
68
|
+
completeOauth?: (payload: ConnectionTypes.IntegrationCompleteOauthPayload) => Promise<ConnectionTypes.IntegrationCompleteOauthResult>;
|
|
69
|
+
actions?: Record<string, CustomIntegrationAction<z.infer<ConnectorConfigSchema>, any>>;
|
|
70
|
+
extractors?: Record<string, CustomIntegrationExtractor<z.infer<ConnectorConfigSchema>, any, any>>;
|
|
71
|
+
autocompletes?: Record<string, CustomIntegrationAutocomplete<z.infer<ConnectorConfigSchema>>>;
|
|
72
|
+
dynamicSchemas?: Record<string, CustomIntegrationDynamicSchema<z.infer<ConnectorConfigSchema>>>;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Build a Cargo Custom Integration worker from a declarative spec. Produces a
|
|
76
|
+
* Hono app that serves:
|
|
77
|
+
*
|
|
78
|
+
* - `GET /manifest` — Cargo-native connector manifest
|
|
79
|
+
* (actions/extractors/autocompletes/dynamicSchemas with JSON Schema configs).
|
|
80
|
+
* - `GET /openapi.json` + `GET /docs` — OpenAPI 3.1 spec + Swagger UI
|
|
81
|
+
* derived from the same Zod schemas.
|
|
82
|
+
* - `POST /authenticate` — runs `spec.authenticate`.
|
|
83
|
+
* - `POST /listUsers` — runs `spec.listUsers` (if set).
|
|
84
|
+
* - `POST /completeOauth` — runs `spec.completeOauth` (if set).
|
|
85
|
+
* - `POST /actions/<slug>/execute` — one route per action.
|
|
86
|
+
* - `POST /extractors/<slug>/{fetch,count}` — two routes per extractor.
|
|
87
|
+
* - `POST /autocompletes/<slug>` — one route per autocomplete.
|
|
88
|
+
* - `POST /dynamicSchemas/<slug>` — one route per dynamic schema.
|
|
89
|
+
*
|
|
90
|
+
* Per-handler request `config` payloads are validated against their Zod
|
|
91
|
+
* schemas; validation errors respond 400 with Chanfana's standard format.
|
|
92
|
+
*/
|
|
93
|
+
export declare const createCustomIntegration: <ConnectorConfigSchema extends z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, Env extends WorkerEnv = WorkerEnv>(spec: CustomIntegration<ConnectorConfigSchema>) => {
|
|
94
|
+
app: WorkerApp<Env>;
|
|
95
|
+
openapi: WorkerOpenAPI<Env>;
|
|
96
|
+
};
|
|
97
|
+
//# sourceMappingURL=customIntegration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"customIntegration.d.ts","sourceRoot":"","sources":["../../src/customIntegration.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { OpenAPIRoute } from "chanfana";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { createWorker, } from "./createWorker.js";
|
|
4
|
+
// --- Route helpers -----------------------------------------------------------
|
|
5
|
+
// The Cargo backend controls the envelope shape, so we don't re-declare every
|
|
6
|
+
// field here — we just accept the envelope permissively and let the action /
|
|
7
|
+
// extractor Zod schemas validate the one field the worker author owns.
|
|
8
|
+
const looseEnvelope = z.object({}).passthrough();
|
|
9
|
+
const jsonResponse = (schema) => ({
|
|
10
|
+
"200": {
|
|
11
|
+
description: "OK",
|
|
12
|
+
content: { "application/json": { schema } },
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
const jsonBody = (schema) => ({
|
|
16
|
+
content: { "application/json": { schema } },
|
|
17
|
+
});
|
|
18
|
+
const makeRoute = (schema, handle) => {
|
|
19
|
+
return class extends OpenAPIRoute {
|
|
20
|
+
schema = schema;
|
|
21
|
+
async handle(c) {
|
|
22
|
+
return handle(c);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
// --- Manifest builder --------------------------------------------------------
|
|
27
|
+
const buildManifest = (spec) => {
|
|
28
|
+
return {
|
|
29
|
+
name: spec.info.name,
|
|
30
|
+
description: spec.info.description,
|
|
31
|
+
...(spec.info.icon !== undefined ? { icon: spec.info.icon } : {}),
|
|
32
|
+
...(spec.info.color !== undefined ? { color: spec.info.color } : {}),
|
|
33
|
+
...(spec.info.url !== undefined ? { url: spec.info.url } : {}),
|
|
34
|
+
connector: {
|
|
35
|
+
config: {
|
|
36
|
+
jsonSchema: z.toJSONSchema(spec.connector.config.schema),
|
|
37
|
+
...(spec.connector.config.uiSchema !== undefined
|
|
38
|
+
? { uiSchema: spec.connector.config.uiSchema }
|
|
39
|
+
: {}),
|
|
40
|
+
},
|
|
41
|
+
...(spec.connector.rateLimit !== undefined
|
|
42
|
+
? { rateLimit: spec.connector.rateLimit }
|
|
43
|
+
: {}),
|
|
44
|
+
},
|
|
45
|
+
actions: Object.fromEntries(Object.entries(spec.actions !== undefined ? spec.actions : {}).map(([slug, action]) => [
|
|
46
|
+
slug,
|
|
47
|
+
{
|
|
48
|
+
name: action.name,
|
|
49
|
+
description: action.description,
|
|
50
|
+
config: {
|
|
51
|
+
jsonSchema: z.toJSONSchema(action.config.schema),
|
|
52
|
+
...(action.config.uiSchema !== undefined
|
|
53
|
+
? { uiSchema: action.config.uiSchema }
|
|
54
|
+
: {}),
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
])),
|
|
58
|
+
extractors: Object.fromEntries(Object.entries(spec.extractors !== undefined ? spec.extractors : {}).map(([slug, extractor]) => [
|
|
59
|
+
slug,
|
|
60
|
+
{
|
|
61
|
+
name: extractor.name,
|
|
62
|
+
description: extractor.description,
|
|
63
|
+
config: {
|
|
64
|
+
jsonSchema: z.toJSONSchema(extractor.config.schema),
|
|
65
|
+
...(extractor.config.uiSchema !== undefined
|
|
66
|
+
? { uiSchema: extractor.config.uiSchema }
|
|
67
|
+
: {}),
|
|
68
|
+
},
|
|
69
|
+
mode: extractor.mode,
|
|
70
|
+
preview: extractor.preview,
|
|
71
|
+
},
|
|
72
|
+
])),
|
|
73
|
+
autocompletes: Object.fromEntries(Object.keys(spec.autocompletes !== undefined ? spec.autocompletes : {}).map((slug) => [
|
|
74
|
+
slug,
|
|
75
|
+
{ params: { jsonSchema: {} }, cacheExpirationInSeconds: 300 },
|
|
76
|
+
])),
|
|
77
|
+
dynamicSchemas: Object.fromEntries(Object.keys(spec.dynamicSchemas !== undefined ? spec.dynamicSchemas : {}).map((slug) => [slug, { params: { jsonSchema: {} } }])),
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
// --- createCustomIntegration -------------------------------------------------
|
|
81
|
+
/**
|
|
82
|
+
* Build a Cargo Custom Integration worker from a declarative spec. Produces a
|
|
83
|
+
* Hono app that serves:
|
|
84
|
+
*
|
|
85
|
+
* - `GET /manifest` — Cargo-native connector manifest
|
|
86
|
+
* (actions/extractors/autocompletes/dynamicSchemas with JSON Schema configs).
|
|
87
|
+
* - `GET /openapi.json` + `GET /docs` — OpenAPI 3.1 spec + Swagger UI
|
|
88
|
+
* derived from the same Zod schemas.
|
|
89
|
+
* - `POST /authenticate` — runs `spec.authenticate`.
|
|
90
|
+
* - `POST /listUsers` — runs `spec.listUsers` (if set).
|
|
91
|
+
* - `POST /completeOauth` — runs `spec.completeOauth` (if set).
|
|
92
|
+
* - `POST /actions/<slug>/execute` — one route per action.
|
|
93
|
+
* - `POST /extractors/<slug>/{fetch,count}` — two routes per extractor.
|
|
94
|
+
* - `POST /autocompletes/<slug>` — one route per autocomplete.
|
|
95
|
+
* - `POST /dynamicSchemas/<slug>` — one route per dynamic schema.
|
|
96
|
+
*
|
|
97
|
+
* Per-handler request `config` payloads are validated against their Zod
|
|
98
|
+
* schemas; validation errors respond 400 with Chanfana's standard format.
|
|
99
|
+
*/
|
|
100
|
+
export const createCustomIntegration = (spec) => {
|
|
101
|
+
const { app, openapi } = createWorker({
|
|
102
|
+
title: spec.info.name,
|
|
103
|
+
version: spec.info.version !== undefined ? spec.info.version : "0.0.1",
|
|
104
|
+
description: spec.info.description,
|
|
105
|
+
});
|
|
106
|
+
const manifest = buildManifest(spec);
|
|
107
|
+
openapi.get("/manifest", makeRoute({
|
|
108
|
+
tags: ["manifest"],
|
|
109
|
+
summary: "Cargo connector manifest.",
|
|
110
|
+
responses: jsonResponse(z.object({}).passthrough()),
|
|
111
|
+
}, async (c) => c.json(manifest)));
|
|
112
|
+
openapi.post("/authenticate", makeRoute({
|
|
113
|
+
tags: ["connector"],
|
|
114
|
+
summary: "Validate the connector config against the upstream API.",
|
|
115
|
+
request: { body: jsonBody(looseEnvelope) },
|
|
116
|
+
responses: jsonResponse(z.object({}).passthrough()),
|
|
117
|
+
}, async (c) => {
|
|
118
|
+
const payload = (await c.req.json());
|
|
119
|
+
return c.json(await spec.authenticate(payload));
|
|
120
|
+
}));
|
|
121
|
+
if (spec.listUsers !== undefined) {
|
|
122
|
+
const { listUsers } = spec;
|
|
123
|
+
openapi.post("/listUsers", makeRoute({
|
|
124
|
+
tags: ["connector"],
|
|
125
|
+
summary: "List users visible to the connected account.",
|
|
126
|
+
request: { body: jsonBody(looseEnvelope) },
|
|
127
|
+
responses: jsonResponse(z.array(z.object({}).passthrough())),
|
|
128
|
+
}, async (c) => {
|
|
129
|
+
const payload = (await c.req.json());
|
|
130
|
+
return c.json(await listUsers(payload));
|
|
131
|
+
}));
|
|
132
|
+
}
|
|
133
|
+
if (spec.completeOauth !== undefined) {
|
|
134
|
+
const { completeOauth } = spec;
|
|
135
|
+
openapi.post("/completeOauth", makeRoute({
|
|
136
|
+
tags: ["connector"],
|
|
137
|
+
summary: "Exchange OAuth params for a credential.",
|
|
138
|
+
request: { body: jsonBody(looseEnvelope) },
|
|
139
|
+
responses: jsonResponse(z.object({}).passthrough()),
|
|
140
|
+
}, async (c) => {
|
|
141
|
+
const payload = (await c.req.json());
|
|
142
|
+
return c.json(await completeOauth(payload));
|
|
143
|
+
}));
|
|
144
|
+
}
|
|
145
|
+
for (const [slug, action] of Object.entries(spec.actions !== undefined ? spec.actions : {})) {
|
|
146
|
+
const capturedAction = action;
|
|
147
|
+
openapi.post(`/actions/${slug}/execute`, makeRoute({
|
|
148
|
+
tags: ["actions"],
|
|
149
|
+
summary: capturedAction.name,
|
|
150
|
+
description: capturedAction.description,
|
|
151
|
+
request: {
|
|
152
|
+
body: jsonBody(z.object({ config: capturedAction.config.schema }).passthrough()),
|
|
153
|
+
},
|
|
154
|
+
responses: jsonResponse(z.object({}).passthrough()),
|
|
155
|
+
}, async (c) => {
|
|
156
|
+
const payload = (await c.req.json());
|
|
157
|
+
return c.json(await capturedAction.execute(payload));
|
|
158
|
+
}));
|
|
159
|
+
}
|
|
160
|
+
for (const [slug, extractor] of Object.entries(spec.extractors !== undefined ? spec.extractors : {})) {
|
|
161
|
+
const captured = extractor;
|
|
162
|
+
openapi.post(`/extractors/${slug}/fetch`, makeRoute({
|
|
163
|
+
tags: ["extractors"],
|
|
164
|
+
summary: `${captured.name} — fetch`,
|
|
165
|
+
description: captured.description,
|
|
166
|
+
request: {
|
|
167
|
+
body: jsonBody(z.object({ config: captured.config.schema }).passthrough()),
|
|
168
|
+
},
|
|
169
|
+
responses: jsonResponse(z.object({}).passthrough()),
|
|
170
|
+
}, async (c) => {
|
|
171
|
+
const payload = (await c.req.json());
|
|
172
|
+
return c.json(await captured.fetch(payload));
|
|
173
|
+
}));
|
|
174
|
+
openapi.post(`/extractors/${slug}/count`, makeRoute({
|
|
175
|
+
tags: ["extractors"],
|
|
176
|
+
summary: `${captured.name} — count`,
|
|
177
|
+
description: captured.description,
|
|
178
|
+
request: {
|
|
179
|
+
body: jsonBody(z.object({ config: captured.config.schema }).passthrough()),
|
|
180
|
+
},
|
|
181
|
+
responses: jsonResponse(z.object({}).passthrough()),
|
|
182
|
+
}, async (c) => {
|
|
183
|
+
const payload = (await c.req.json());
|
|
184
|
+
return c.json(await captured.count(payload));
|
|
185
|
+
}));
|
|
186
|
+
}
|
|
187
|
+
for (const [slug, handler] of Object.entries(spec.autocompletes !== undefined ? spec.autocompletes : {})) {
|
|
188
|
+
const captured = handler;
|
|
189
|
+
openapi.post(`/autocompletes/${slug}`, makeRoute({
|
|
190
|
+
tags: ["autocompletes"],
|
|
191
|
+
summary: `Autocomplete: ${slug}`,
|
|
192
|
+
request: { body: jsonBody(looseEnvelope) },
|
|
193
|
+
responses: jsonResponse(z.object({}).passthrough()),
|
|
194
|
+
}, async (c) => {
|
|
195
|
+
const payload = (await c.req.json());
|
|
196
|
+
return c.json(await captured(payload));
|
|
197
|
+
}));
|
|
198
|
+
}
|
|
199
|
+
for (const [slug, handler] of Object.entries(spec.dynamicSchemas !== undefined ? spec.dynamicSchemas : {})) {
|
|
200
|
+
const captured = handler;
|
|
201
|
+
openapi.post(`/dynamicSchemas/${slug}`, makeRoute({
|
|
202
|
+
tags: ["dynamicSchemas"],
|
|
203
|
+
summary: `Dynamic schema: ${slug}`,
|
|
204
|
+
request: { body: jsonBody(looseEnvelope) },
|
|
205
|
+
responses: jsonResponse(z.object({}).passthrough()),
|
|
206
|
+
}, async (c) => {
|
|
207
|
+
const payload = (await c.req.json());
|
|
208
|
+
return c.json(await captured(payload));
|
|
209
|
+
}));
|
|
210
|
+
}
|
|
211
|
+
return { app, openapi };
|
|
212
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type { CreateWorkerOptions, WorkerApp, WorkerOpenAPI, } from "./createWorker.js";
|
|
2
|
+
export { createWorker } from "./createWorker.js";
|
|
3
|
+
export type { CustomIntegration, CustomIntegrationAction, CustomIntegrationAutocomplete, CustomIntegrationDynamicSchema, CustomIntegrationExtractor, } from "./customIntegration.js";
|
|
4
|
+
export { createCustomIntegration } from "./customIntegration.js";
|
|
5
|
+
export type { WorkerEnv, WorkerManifest } from "./types.js";
|
|
6
|
+
export { fromHono, OpenAPIRoute } from "chanfana";
|
|
7
|
+
export type { Context } from "hono";
|
|
8
|
+
export { Hono } from "hono";
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,mBAAmB,EACnB,SAAS,EACT,aAAa,GACd,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,YAAY,EACV,iBAAiB,EACjB,uBAAuB,EACvB,6BAA6B,EAC7B,8BAA8B,EAC9B,0BAA0B,GAC3B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACjE,YAAY,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5D,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAClD,YAAY,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACpC,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `env` shape passed to every handler by the Cargo Hosting runtime. Keys come
|
|
3
|
+
* from the worker's configured secrets / bindings. Override with a stricter
|
|
4
|
+
* interface in your worker if you want type-safety on individual env values.
|
|
5
|
+
*/
|
|
6
|
+
export type WorkerEnv = Record<string, string | undefined>;
|
|
7
|
+
/**
|
|
8
|
+
* `manifest.json` at the root of the deployed bundle. Tells the Cargo Hosting
|
|
9
|
+
* sandbox which outbound hosts the worker is allowed to call (defence in depth
|
|
10
|
+
* — the runtime blocks `fetch(...)` calls to anything not on the allowlist).
|
|
11
|
+
*/
|
|
12
|
+
export interface WorkerManifest {
|
|
13
|
+
outboundAllowlist: string[];
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAE3D;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,iBAAiB,EAAE,MAAM,EAAE,CAAC;CAC7B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|