Documentation (fetch and more)

This commit is contained in:
Eric Allam
2023-01-23 14:11:18 +00:00
parent ac15256fe6
commit f316c6e3af
15 changed files with 686 additions and 46 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"@trigger.dev/sdk": patch
---
Add ability to use fetch without having to use context param
+206
View File
@@ -0,0 +1,206 @@
---
title: "Generic Fetch"
sidebarTitle: "Fetch"
description: "A generic fetch function that can be used to call any HTTP endpoint"
---
## Usage
A `fetch` function is available to use inside a `Trigger.run` function through the `context` argument, and should be familiar for anyone who has used the standard [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) API, with a few modifications.
```ts
import { Trigger } from "@trigger.dev/sdk";
new Trigger({
id: "fetch-example",
name: "Fetch Example",
on: customEvent({
name: "example.fetch",
}),
run: async (event, ctx) => {
await ctx.fetch("Example Key", "http://httpbin.org/get");
},
}).listen();
```
Notice the first parameter to `fetch` is a `key` that is used to identify this call to fetch to support resumability. Please see the [Resumability](/guides/resumability) guide for more information.
You can also import `fetch` and use it outside of a `Trigger.run` function:
```ts httpBin.ts
import { fetch } from "@trigger.dev/sdk";
export function httpBinGet() {
return fetch("Example Key", "http://httpbin.org/get");
}
```
Now you can use the exported `httpBinGet` function inside your `Trigger.run` function:
```ts
import { Trigger } from "@trigger.dev/sdk";
import { httpBinGet } from "./httpBin";
new Trigger({
id: "fetch-example",
name: "Fetch Example",
on: customEvent({
name: "example.fetch",
}),
run: async (event, ctx) => {
await httpBinGet();
},
}).listen();
```
This is useful if you want to wrap `fetch` to provide an SDK like experience inside your workflows.
<Warning>
Calling `fetch` when not inside of a workflow run will result in a thrown
Error.
</Warning>
## Response
The return value of `fetch` is a similar to a normal fetch response, but we will automatically parse the response body as JSON and provide it as `body`, like so:
```ts
const response = await ctx.fetch("Example Key", "http://httpbin.org/get");
console.log(response.body.url); // http://httpbin.org/get
```
By default, the `body` is typed as `any`, but you can provide a [`Zod`](/guides/zod) schema to validate the response body and get a more specific type:
```ts
import { z } from "zod";
const response = await ctx.fetch("Get HTTPBin", "http://httpbin.org/get", {
responseSchema: z.object({
url: z.string(),
origin: z.string(),
headers: z.record(z.string()),
}),
});
```
Now the `body` will be typed as:
```ts
{
url: string;
origin: string;
headers: Record<string, string>;
}
```
It's okay to not be comprehensive with the schema, as long as the response body matches the schema, it will be valid. Do note that any properties not included in the schema will be excluded, unless you use `.passthrough()`:
```ts
const response = await ctx.fetch("Get HTTPBin", "http://httpbin.org/get", {
responseSchema: z.object({
url: z.string(),
origin: z.string(),
headers: z.record(z.string()),
}),
});
console.log(response.body); // Only includes url, origin, and headers
// Using passthrough():
const response = await ctx.fetch("Get HTTPBin", "http://httpbin.org/get", {
responseSchema: z
.object({
url: z.string(),
origin: z.string(),
headers: z.record(z.string()),
})
.passthrough(),
});
console.log(response.body); // Includes url, origin, headers, and everything else
```
<Note>
The fetch function currently only supports JSON request and response bodies.
</Note>
## Secret values
If you are using a header with a secret value, you can use our `secureString` [tagged template](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates) to ensure that the value is not logged in the trigger.dev logs:
```ts
import { Trigger, secureString } from "@trigger.dev/sdk";
new Trigger({
id: "fetch-example",
name: "Fetch Example",
on: customEvent({
name: "example.fetch",
}),
run: async (event, ctx) => {
await ctx.fetch("🔑 Secret API", "http://httpbin.org/post", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: secureString`Bearer ${process.env.SECRET_API_KEY}`,
},
});
},
}).listen();
```
Which will show in the trigger.dev app as:
![title](/images/secure-string.png)
## Params
<ParamField path="key" type="string" required={true}>
A unique string. Please see the [Keys and Resumability](/guides/resumability)
doc for more info.
</ParamField>
<ParamField path="url" type="string | URL" required={true}>
The URL to fetch.
</ParamField>
<ParamField path="options" type="object" required={false}>
<Expandable title="properties">
<ParamField path="method" type="string" required={false}>
The HTTP method to use. Defaults to `GET`.
</ParamField>
<ParamField path="headers" type="record" required={false}>
An object of headers to send with the request.
</ParamField>
<ParamField path="responseSchema" type="zod Schema" required={false}>
A [Zod](/guides/zod) schema to validate the response body.
</ParamField>
<ParamField path="body" type="json" required={false}>
The body to send with the request. Only valid for `POST`, `PUT`, and `PATCH`. You don't need to `JSON.stringify` before passing it in.
</ParamField>
</Expandable>
</ParamField>
## Response
<ParamField path="ok" type="boolean">
Whether the response was successful. Will always be true because we throw an
error and stop the trigger if the response is not successful.
</ParamField>
<ParamField path="body" type="object" required={false}>
The response body, parsed with the `responseSchema` if provided.
</ParamField>
<ParamField path="status" type="number">
The HTTP status code.
</ParamField>
<ParamField path="headers" type="object">
The response headers.
</ParamField>
+7
View File
@@ -0,0 +1,7 @@
---
title: "Resumability"
sidebarTitle: "Keys and Resumability"
description: "Keys and Resumability"
---
## Coming soon
+7
View File
@@ -0,0 +1,7 @@
---
title: "Zod"
sidebarTitle: "Zod"
description: "TypeScript-first schema validation with static type inference"
---
## Coming soon
Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

@@ -0,0 +1,19 @@
---
title: "GitHub Events"
sidebarTitle: "Events"
description: "GitHub Triggers"
---
## GitHub API integration
Full reference of all functions is coming soon.
Check out some example workflows using the GitHub API [here](/examples/github).
### Events
These are the events you can use to trigger a workflow
### Requests
These are the requests you can make to the GitHub API.
@@ -0,0 +1,221 @@
---
title: "New Star Event"
sidebarTitle: "New Star"
description: "Trigger a workflow whenever a user star's the specified GitHub repository."
---
# New Star
Trigger a workflow whenever a user star's the specified GitHub repository.
## Params
<ParamField path="repo" type="string" required={true}>
The full path to the repository, including the organization
</ParamField>
## Event
<ResponseField name="action" type="string" default="created">
The star action, which in this case is always `"created"`.
</ResponseField>
<ResponseField name="starred_at" type="Date" required={true}>
The date and time the starring occurred.
</ResponseField>
<ResponseField name="repository" type="object" required={true}>
A repository on GitHub.
</ResponseField>
<ResponseField name="sender" type="object" required={true}>
The GitHub user object who starred the repository.
</ResponseField>
<ResponseField name="organization" type="object">
The organization object the repository belongs to.
</ResponseField>
## Example Workflows
<CodeGroup>
```typescript Basic
new Trigger({
id: "new-star",
name: "On New Star",
on: github.events.newStarEvent({
repo: "triggerdotdev/trigger.dev",
}),
run: async (event, ctx) => {},
}).listen();
```
```typescript Notify Slack on New Star
import { slack } from "@trigger.dev/integrations";
new Trigger({
id: "new-star",
name: "On New Star",
on: github.events.newStarEvent({
repo: "triggerdotdev/trigger.dev",
}),
run: async (event, ctx) => {
await slack.postMessage("⭐️ New Star", {
channelName: "github-stars",
text: `Repo ${event.repository.full_name} got a star from ${event.sender.login}, for a total of ${event.repository.stargazers_count} stars!`,
});
},
}).listen();
```
</CodeGroup>
## Example Event payload
```json Example
{
"action": "created",
"starred_at": "2023-01-21T23:47:38Z",
"repository": {
"id": 516315865,
"node_id": "R_kgDOHsZa2Q",
"name": "apihero-openapi-generator",
"full_name": "triggerdotdev/apihero-openapi-generator",
"private": false,
"owner": {
"login": "triggerdotdev",
"id": 95297378,
"node_id": "O_kgDOBa4fYg",
"avatar_url": "https://avatars.githubusercontent.com/u/95297378?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/triggerdotdev",
"html_url": "https://github.com/triggerdotdev",
"followers_url": "https://api.github.com/users/triggerdotdev/followers",
"following_url": "https://api.github.com/users/triggerdotdev/following{/other_user}",
"gists_url": "https://api.github.com/users/triggerdotdev/gists{/gist_id}",
"starred_url": "https://api.github.com/users/triggerdotdev/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/triggerdotdev/subscriptions",
"organizations_url": "https://api.github.com/users/triggerdotdev/orgs",
"repos_url": "https://api.github.com/users/triggerdotdev/repos",
"events_url": "https://api.github.com/users/triggerdotdev/events{/privacy}",
"received_events_url": "https://api.github.com/users/triggerdotdev/received_events",
"type": "Organization",
"site_admin": false
},
"html_url": "https://github.com/triggerdotdev/apihero-openapi-generator",
"description": null,
"fork": false,
"url": "https://api.github.com/repos/triggerdotdev/apihero-openapi-generator",
"forks_url": "https://api.github.com/repos/triggerdotdev/apihero-openapi-generator/forks",
"keys_url": "https://api.github.com/repos/triggerdotdev/apihero-openapi-generator/keys{/key_id}",
"collaborators_url": "https://api.github.com/repos/triggerdotdev/apihero-openapi-generator/collaborators{/collaborator}",
"teams_url": "https://api.github.com/repos/triggerdotdev/apihero-openapi-generator/teams",
"hooks_url": "https://api.github.com/repos/triggerdotdev/apihero-openapi-generator/hooks",
"issue_events_url": "https://api.github.com/repos/triggerdotdev/apihero-openapi-generator/issues/events{/number}",
"events_url": "https://api.github.com/repos/triggerdotdev/apihero-openapi-generator/events",
"assignees_url": "https://api.github.com/repos/triggerdotdev/apihero-openapi-generator/assignees{/user}",
"branches_url": "https://api.github.com/repos/triggerdotdev/apihero-openapi-generator/branches{/branch}",
"tags_url": "https://api.github.com/repos/triggerdotdev/apihero-openapi-generator/tags",
"blobs_url": "https://api.github.com/repos/triggerdotdev/apihero-openapi-generator/git/blobs{/sha}",
"git_tags_url": "https://api.github.com/repos/triggerdotdev/apihero-openapi-generator/git/tags{/sha}",
"git_refs_url": "https://api.github.com/repos/triggerdotdev/apihero-openapi-generator/git/refs{/sha}",
"trees_url": "https://api.github.com/repos/triggerdotdev/apihero-openapi-generator/git/trees{/sha}",
"statuses_url": "https://api.github.com/repos/triggerdotdev/apihero-openapi-generator/statuses/{sha}",
"languages_url": "https://api.github.com/repos/triggerdotdev/apihero-openapi-generator/languages",
"stargazers_url": "https://api.github.com/repos/triggerdotdev/apihero-openapi-generator/stargazers",
"contributors_url": "https://api.github.com/repos/triggerdotdev/apihero-openapi-generator/contributors",
"subscribers_url": "https://api.github.com/repos/triggerdotdev/apihero-openapi-generator/subscribers",
"subscription_url": "https://api.github.com/repos/triggerdotdev/apihero-openapi-generator/subscription",
"commits_url": "https://api.github.com/repos/triggerdotdev/apihero-openapi-generator/commits{/sha}",
"git_commits_url": "https://api.github.com/repos/triggerdotdev/apihero-openapi-generator/git/commits{/sha}",
"comments_url": "https://api.github.com/repos/triggerdotdev/apihero-openapi-generator/comments{/number}",
"issue_comment_url": "https://api.github.com/repos/triggerdotdev/apihero-openapi-generator/issues/comments{/number}",
"contents_url": "https://api.github.com/repos/triggerdotdev/apihero-openapi-generator/contents/{+path}",
"compare_url": "https://api.github.com/repos/triggerdotdev/apihero-openapi-generator/compare/{base}...{head}",
"merges_url": "https://api.github.com/repos/triggerdotdev/apihero-openapi-generator/merges",
"archive_url": "https://api.github.com/repos/triggerdotdev/apihero-openapi-generator/{archive_format}{/ref}",
"downloads_url": "https://api.github.com/repos/triggerdotdev/apihero-openapi-generator/downloads",
"issues_url": "https://api.github.com/repos/triggerdotdev/apihero-openapi-generator/issues{/number}",
"pulls_url": "https://api.github.com/repos/triggerdotdev/apihero-openapi-generator/pulls{/number}",
"milestones_url": "https://api.github.com/repos/triggerdotdev/apihero-openapi-generator/milestones{/number}",
"notifications_url": "https://api.github.com/repos/triggerdotdev/apihero-openapi-generator/notifications{?since,all,participating}",
"labels_url": "https://api.github.com/repos/triggerdotdev/apihero-openapi-generator/labels{/name}",
"releases_url": "https://api.github.com/repos/triggerdotdev/apihero-openapi-generator/releases{/id}",
"deployments_url": "https://api.github.com/repos/triggerdotdev/apihero-openapi-generator/deployments",
"created_at": "2022-07-21T09:57:53Z",
"updated_at": "2023-01-21T23:47:38Z",
"pushed_at": "2022-10-20T13:21:09Z",
"git_url": "git://github.com/triggerdotdev/apihero-openapi-generator.git",
"ssh_url": "git@github.com:triggerdotdev/apihero-openapi-generator.git",
"clone_url": "https://github.com/triggerdotdev/apihero-openapi-generator.git",
"svn_url": "https://github.com/triggerdotdev/apihero-openapi-generator",
"homepage": null,
"size": 4962,
"stargazers_count": 1,
"watchers_count": 1,
"language": "TypeScript",
"has_issues": true,
"has_projects": true,
"has_downloads": true,
"has_wiki": true,
"has_pages": false,
"has_discussions": false,
"forks_count": 0,
"mirror_url": null,
"archived": false,
"disabled": false,
"open_issues_count": 0,
"license": {
"key": "mit",
"name": "MIT License",
"spdx_id": "MIT",
"url": "https://api.github.com/licenses/mit",
"node_id": "MDc6TGljZW5zZTEz"
},
"allow_forking": true,
"is_template": false,
"web_commit_signoff_required": false,
"topics": [],
"visibility": "public",
"forks": 0,
"open_issues": 0,
"watchers": 1,
"default_branch": "main"
},
"organization": {
"login": "triggerdotdev",
"id": 95297378,
"node_id": "O_kgDOBa4fYg",
"url": "https://api.github.com/orgs/triggerdotdev",
"repos_url": "https://api.github.com/orgs/triggerdotdev/repos",
"events_url": "https://api.github.com/orgs/triggerdotdev/events",
"hooks_url": "https://api.github.com/orgs/triggerdotdev/hooks",
"issues_url": "https://api.github.com/orgs/triggerdotdev/issues",
"members_url": "https://api.github.com/orgs/triggerdotdev/members{/member}",
"public_members_url": "https://api.github.com/orgs/triggerdotdev/public_members{/member}",
"avatar_url": "https://avatars.githubusercontent.com/u/95297378?v=4",
"description": ""
},
"sender": {
"login": "ericallam",
"id": 534,
"node_id": "MDQ6VXNlcjUzNA==",
"avatar_url": "https://avatars.githubusercontent.com/u/534?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/ericallam",
"html_url": "https://github.com/ericallam",
"followers_url": "https://api.github.com/users/ericallam/followers",
"following_url": "https://api.github.com/users/ericallam/following{/other_user}",
"gists_url": "https://api.github.com/users/ericallam/gists{/gist_id}",
"starred_url": "https://api.github.com/users/ericallam/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/ericallam/subscriptions",
"organizations_url": "https://api.github.com/users/ericallam/orgs",
"repos_url": "https://api.github.com/users/ericallam/repos",
"events_url": "https://api.github.com/users/ericallam/events{/privacy}",
"received_events_url": "https://api.github.com/users/ericallam/received_events",
"type": "User",
"site_admin": false
}
}
```
@@ -0,0 +1,109 @@
---
title: "Post Message"
sidebarTitle: "Post Message"
description: "Post a message to a Slack channel"
---
Publish slack messages to a public or private channel in your Slack Workspace as the Trigger.dev Slack bot. If you need to publish messages to your customer's Slack channels, consider using [Incoming Webhooks](https://api.slack.com/messaging/webhooks) and our [fetch](/functions/fetch) function.
## Params
<ParamField path="key" type="string" required={true}>
A unique string. Please see the [Keys and Resumability](/guides/resumability)
doc for more info.
</ParamField>
<ParamField path="message" type="object" required={true}>
<Expandable title="properties" defaultOpen={true}>
<ParamField path="channelName" type="string" required={false}>
The name of the channel, can optionally include the `#`. E.g. `#team`.
Alternatively you can use the `channelId` param.
</ParamField>
<ParamField path="channelId" type="string" required={false}>
The slack ID of the channel, e.g. `C04GWUTDC4W`. Can be used instead of `channelName`. Use `channelId` if the slack channel name could change.
</ParamField>
<ParamField path="text" type="string" required={true}>
The formatted text of the message to be published, formatted as [mrkdwn](https://api.slack.com/reference/surfaces/formatting#basics).
</ParamField>
</Expandable>
</ParamField>
## Response
<ResponseField name="ok" type="boolean" default="true">
Always true; non-ok responses will halt the workflow run and throw an error.
</ResponseField>
<ResponseField name="channel" type="string">
The channel ID of the channel the message was published to.
</ResponseField>
<ResponseField name="ts" type="string">
The "timestamp ID" of the message, which can be used to update or delete the
message.
</ResponseField>
<ResponseField name="message" type="object">
<Expandable title="properties">
<ResponseField name="ts" type="string">
The "timestamp ID" of the message, which can be used to update or delete the message.
</ResponseField>
<ResponseField name="text" type="string">
The text published to the channel.
</ResponseField>
<ResponseField name="user" type="string">
The user ID of the user who published the message.
</ResponseField>
<ResponseField name="bot_id" type="string">
The bot ID of the bot that published the message.
</ResponseField>
</Expandable>
</ResponseField>
## Example Workflows
<CodeGroup>
```typescript Notify Slack on New Star
import { slack, github } from "@trigger.dev/integrations";
new Trigger({
id: "new-star",
name: "On New Star",
on: github.events.newStarEvent({
repo: "triggerdotdev/trigger.dev",
}),
run: async (event, ctx) => {
await slack.postMessage("⭐️ New Star", {
channelName: "github-stars",
text: `Repo ${event.repository.full_name} got a star from ${event.sender.login}, for a total of ${event.repository.stargazers_count} stars!`,
});
},
}).listen();
```
</CodeGroup>
## Example Response
```json
{
"ok": true,
"ts": "1673618429.084699",
"channel": "C04GWUTDC3W",
"message": {
"ts": "1673618429.084699",
"text": "New domain created: trigger.dev by customer 1st-ever-slack-workflow-yippie",
"type": "message",
"user": "U04JTTQ08SF",
"bot_id": "B04JR3DCB4M"
}
}
```
+42 -5
View File
@@ -29,11 +29,12 @@
"name": "Trigger.dev", "name": "Trigger.dev",
"url": "https://app.trigger.dev" "url": "https://app.trigger.dev"
}, },
"navigation": [ "navigation": [
{ {
"group": "Getting Started", "group": "Getting Started",
"pages": ["getting-started"] "pages": [
"getting-started"
]
}, },
{ {
"group": "Triggers", "group": "Triggers",
@@ -45,20 +46,56 @@
}, },
{ {
"group": "Integrations", "group": "Integrations",
"pages": ["integrations/apis", "integrations/authentication"] "pages": [
{
"group": "APIs",
"pages": [
{
"group": "Slack",
"pages": [
"integrations/apis/slack/actions/post-message"
]
}
]
},
"integrations/authentication"
]
}, },
{ {
"group": "Functions", "group": "Functions",
"pages": [ "pages": [
"functions/fetch",
"functions/logging", "functions/logging",
"functions/delays", "functions/delays",
"functions/send-event", "functions/send-event",
"functions/loops-conditionals-etc" "functions/loops-conditionals-etc"
] ]
}, },
{
"group": "Guides",
"pages": [
"guides/resumability",
"guides/zod"
]
},
{
"group": "Webhook Catalog",
"pages": [
{
"group": "GitHub",
"pages": [
"integrations/apis/github/events/new-star"
]
}
]
},
{ {
"group": "Example workflows", "group": "Example workflows",
"pages": ["examples/github", "examples/shopify", "examples/slack"] "pages": [
"examples/github",
"examples/shopify",
"examples/slack"
]
} }
], ],
"footerSocials": { "footerSocials": {
@@ -66,4 +103,4 @@
"twitter": "https://twitter.com/triggerdotdev", "twitter": "https://twitter.com/triggerdotdev",
"github": "https://github.com/triggerdotdev/trigger.dev" "github": "https://github.com/triggerdotdev/trigger.dev"
} }
} }
@@ -12,7 +12,7 @@ export type PostMessageResponse = z.infer<
export async function postMessage( export async function postMessage(
key: string, key: string,
options: PostMessageOptions message: PostMessageOptions
): Promise<PostMessageResponse> { ): Promise<PostMessageResponse> {
const run = getTriggerRun(); const run = getTriggerRun();
@@ -23,7 +23,7 @@ export async function postMessage(
const output = await run.performRequest(key, { const output = await run.performRequest(key, {
service: "slack", service: "slack",
endpoint: "chat.postMessage", endpoint: "chat.postMessage",
params: options, params: message,
response: { response: {
schema: slack.schemas.PostMessageSuccessResponseSchema, schema: slack.schemas.PostMessageSuccessResponseSchema,
}, },
+35 -32
View File
@@ -13,7 +13,7 @@ import { HostConnection, TimeoutError } from "./connection";
import { triggerRunLocalStorage } from "./localStorage"; import { triggerRunLocalStorage } from "./localStorage";
import { ContextLogger } from "./logger"; import { ContextLogger } from "./logger";
import { Trigger, TriggerOptions } from "./trigger"; import { Trigger, TriggerOptions } from "./trigger";
import { TriggerContext } from "./types"; import { TriggerContext, TriggerFetch } from "./types";
export class TriggerClient<TSchema extends z.ZodTypeAny> { export class TriggerClient<TSchema extends z.ZodTypeAny> {
#trigger: Trigger<TSchema>; #trigger: Trigger<TSchema>;
@@ -292,6 +292,38 @@ export class TriggerClient<TSchema extends z.ZodTypeAny> {
TRIGGER_WORKFLOW: async (data) => { TRIGGER_WORKFLOW: async (data) => {
this.#logger.debug("Handling TRIGGER_WORKFLOW", data); this.#logger.debug("Handling TRIGGER_WORKFLOW", data);
const fetchFunction: TriggerFetch = async (key, url, options) => {
const result = new Promise<FetchOutput>((resolve, reject) => {
this.#fetchCallbacks.set(messageKey(data.id, key), {
resolve,
reject,
});
});
await serverRPC.send("SEND_FETCH", {
runId: data.id,
key,
fetch: {
url: url.toString(),
method: options.method ?? "GET",
headers: options.headers,
body: options.body,
},
timestamp: String(highPrecisionTimestamp()),
});
const response = await result;
return {
status: response.status,
ok: response.ok,
headers: response.headers,
body: response.body
? (options.responseSchema ?? z.any()).parse(response.body)
: undefined,
};
};
const ctx: TriggerContext = { const ctx: TriggerContext = {
id: data.id, id: data.id,
environment: data.meta.environment, environment: data.meta.environment,
@@ -364,37 +396,7 @@ export class TriggerClient<TSchema extends z.ZodTypeAny> {
return; return;
}, },
fetch: async (key, url, options) => { fetch: fetchFunction,
const result = new Promise<FetchOutput>((resolve, reject) => {
this.#fetchCallbacks.set(messageKey(data.id, key), {
resolve,
reject,
});
});
await serverRPC.send("SEND_FETCH", {
runId: data.id,
key,
fetch: {
url: url.toString(),
method: options.method,
headers: options.headers,
body: options.body,
},
timestamp: String(highPrecisionTimestamp()),
});
const response = await result;
return {
status: response.status,
ok: response.ok,
headers: response.headers,
body: response.body
? (options.responseSchema ?? z.any()).parse(response.body)
: undefined,
};
},
}; };
const eventData = this.#options.on.schema.parse(data.trigger.input); const eventData = this.#options.on.schema.parse(data.trigger.input);
@@ -437,6 +439,7 @@ export class TriggerClient<TSchema extends z.ZodTypeAny> {
timestamp: String(highPrecisionTimestamp()), timestamp: String(highPrecisionTimestamp()),
}); });
}, },
fetch: fetchFunction,
}, },
() => { () => {
this.#logger.debug("Running trigger..."); this.#logger.debug("Running trigger...");
+17
View File
@@ -0,0 +1,17 @@
import { triggerRunLocalStorage } from "./localStorage";
import { z } from "zod";
import { FetchOptions, FetchResponse } from "./types";
export function fetch<TBodySchema extends z.ZodTypeAny = z.ZodTypeAny>(
key: string,
url: string | URL,
options: FetchOptions<TBodySchema>
): Promise<FetchResponse<TBodySchema>> {
const triggerRun = triggerRunLocalStorage.getStore();
if (!triggerRun) {
throw new Error("Cannot call fetch outside of a trigger run");
}
return triggerRun.fetch(key, url, options);
}
+1
View File
@@ -1,6 +1,7 @@
export * from "./events"; export * from "./events";
export * from "./trigger"; export * from "./trigger";
export * from "./customEvents"; export * from "./customEvents";
export * from "./fetch";
import { triggerRunLocalStorage } from "./localStorage"; import { triggerRunLocalStorage } from "./localStorage";
import { SecureString } from "./types"; import { SecureString } from "./types";
+7 -1
View File
@@ -1,6 +1,11 @@
import { AsyncLocalStorage } from "node:async_hooks"; import { AsyncLocalStorage } from "node:async_hooks";
import { z } from "zod"; import { z } from "zod";
import { TriggerCustomEvent } from "./types"; import {
FetchOptions,
FetchResponse,
TriggerCustomEvent,
TriggerFetch,
} from "./types";
type PerformRequestOptions<TSchema extends z.ZodTypeAny> = { type PerformRequestOptions<TSchema extends z.ZodTypeAny> = {
service: string; service: string;
@@ -17,6 +22,7 @@ type TriggerRunLocalStorage = {
options: PerformRequestOptions<TSchema> options: PerformRequestOptions<TSchema>
) => Promise<z.infer<TSchema>>; ) => Promise<z.infer<TSchema>>;
sendEvent: (key: string, event: TriggerCustomEvent) => Promise<void>; sendEvent: (key: string, event: TriggerCustomEvent) => Promise<void>;
fetch: TriggerFetch;
}; };
export const triggerRunLocalStorage = export const triggerRunLocalStorage =
+8 -6
View File
@@ -19,7 +19,7 @@ export type WaitForOptions = {
export type FetchOptions< export type FetchOptions<
TResponseBodySchema extends z.ZodTypeAny = z.ZodTypeAny TResponseBodySchema extends z.ZodTypeAny = z.ZodTypeAny
> = { > = {
method: method?:
| "GET" | "GET"
| "POST" | "POST"
| "PUT" | "PUT"
@@ -42,6 +42,12 @@ export type FetchResponse<
status: number; status: number;
}; };
export type TriggerFetch = <TBodySchema extends z.ZodTypeAny = z.ZodTypeAny>(
key: string,
url: string | URL,
options: FetchOptions<TBodySchema>
) => Promise<FetchResponse<TBodySchema>>;
export interface TriggerContext { export interface TriggerContext {
id: string; id: string;
environment: string; environment: string;
@@ -51,11 +57,7 @@ export interface TriggerContext {
sendEvent(key: string, event: TriggerCustomEvent): Promise<void>; sendEvent(key: string, event: TriggerCustomEvent): Promise<void>;
waitFor(key: string, options: WaitForOptions): Promise<void>; waitFor(key: string, options: WaitForOptions): Promise<void>;
waitUntil(key: string, date: Date): Promise<void>; waitUntil(key: string, date: Date): Promise<void>;
fetch<TBodySchema extends z.ZodTypeAny = z.ZodTypeAny>( fetch: TriggerFetch;
key: string,
url: string | URL,
options: FetchOptions<TBodySchema>
): Promise<FetchResponse<TBodySchema>>;
} }
export interface TriggerLogger { export interface TriggerLogger {