Skip to content

Commit 123acc2

Browse files
Merge branch 'main' into docs/description-of-commit-message
2 parents 7d4496f + d0b80bc commit 123acc2

File tree

29 files changed

+833
-143
lines changed

29 files changed

+833
-143
lines changed

_components/CopyPage.tsx

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
export default function CopyPage({ file }: { file: string | undefined }) {
2+
if (!file || file.includes("[")) return null;
3+
4+
const markdownUrl = `https://docs.deno.com${file}`;
5+
const claudeUrl = `https://claude.ai/new?q=${
6+
encodeURIComponent(
7+
`Read this page from the Deno docs: ${markdownUrl} and answer questions about the content.`,
8+
)
9+
}`;
10+
11+
return (
12+
<div class="copy-page-split inline-flex shrink-0 rounded-full border-2 border-foreground-primary dark:border-background-tertiary">
13+
{/* Primary: directly copies the URL */}
14+
<button
15+
type="button"
16+
class="copy-page-main-btn flex items-center gap-2 px-4 py-2 text-sm font-semibold text-foreground-primary bg-transparent hover:bg-header-highlight dark:hover:text-background-primary rounded-l-full transition-colors cursor-pointer select-none"
17+
>
18+
<svg
19+
xmlns="http://www.w3.org/2000/svg"
20+
aria-hidden="true"
21+
fill="currentColor"
22+
width="12"
23+
height="12"
24+
viewBox="0 0 16 16"
25+
>
26+
<path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z" />
27+
<path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z" />
28+
</svg>
29+
<span class="copy-page-main-label">Copy page</span>
30+
</button>
31+
32+
{/* Visual divider between the two halves */}
33+
<span
34+
class="self-stretch w-0.5 bg-foreground-primary dark:bg-background-tertiary shrink-0"
35+
aria-hidden="true"
36+
>
37+
</span>
38+
39+
{/* Chevron: opens the popover panel */}
40+
<button
41+
type="button"
42+
class="copy-page-toggle-btn flex items-center px-3 py-2 text-foreground-primary bg-transparent hover:bg-header-highlight dark:hover:text-background-primary rounded-r-full transition-colors cursor-pointer select-none"
43+
popovertarget="copy-page-menu"
44+
aria-label="More copy options"
45+
aria-haspopup="menu"
46+
>
47+
<svg
48+
class="copy-page-chevron transition-transform duration-200"
49+
xmlns="http://www.w3.org/2000/svg"
50+
aria-hidden="true"
51+
fill="currentColor"
52+
width="12"
53+
height="12"
54+
viewBox="0 0 16 16"
55+
>
56+
<path d="M12.78 5.22a.749.749 0 0 1 0 1.06l-4.25 4.25a.749.749 0 0 1-1.06 0L3.22 6.28a.749.749 0 1 1 1.06-1.06L8 8.939l3.72-3.719a.749.749 0 0 1 1.06 0Z" />
57+
</svg>
58+
</button>
59+
60+
{/* Popover panel — lives in top layer, positioned via JS */}
61+
<div id="copy-page-menu" popover="auto" class="copy-page-panel">
62+
<a
63+
href={file}
64+
target="_blank"
65+
class="no-underline flex items-start gap-3 px-4 py-3 hover:bg-background-secondary dark:hover:bg-gray-800 transition-colors"
66+
>
67+
<svg
68+
xmlns="http://www.w3.org/2000/svg"
69+
aria-hidden="true"
70+
fill="currentColor"
71+
width="16"
72+
height="16"
73+
viewBox="0 0 16 16"
74+
class="mt-0.5 shrink-0"
75+
>
76+
<path d="M3.75 2h3.5a.75.75 0 0 1 0 1.5h-3.5a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-3.5a.75.75 0 0 1 1.5 0v3.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-8.5C2 2.784 2.784 2 3.75 2Zm6.854-1h4.146a.25.25 0 0 1 .25.25v4.146a.25.25 0 0 1-.427.177L13.03 4.03 9.28 7.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.75-3.75-1.543-1.543A.25.25 0 0 1 10.604 1Z" />
77+
</svg>
78+
<span>
79+
<span class="block text-sm font-medium">View Page as Markdown</span>
80+
<span class="block text-xs text-foreground-secondary mt-0.5">
81+
Open the Markdown file in a new tab
82+
</span>
83+
</span>
84+
</a>
85+
<a
86+
href={claudeUrl}
87+
target="_blank"
88+
class="no-underline flex items-start gap-3 px-4 py-3 border-t border-foreground-tertiary hover:bg-background-secondary dark:hover:bg-gray-800 transition-colors"
89+
>
90+
<svg
91+
xmlns="http://www.w3.org/2000/svg"
92+
aria-hidden="true"
93+
fill="currentColor"
94+
width="16"
95+
height="16"
96+
viewBox="0 0 24 24"
97+
class="mt-0.5 shrink-0"
98+
>
99+
<path d="M12 0C12 0 10.5 10.5 0 12C10.5 12 12 24 12 24C12 24 13.5 13.5 24 12C13.5 12 12 0 12 0Z" />
100+
</svg>
101+
<span>
102+
<span class="block text-sm font-medium">Open in Claude</span>
103+
<span class="block text-xs text-foreground-secondary mt-0.5">
104+
Ask Claude about this page
105+
</span>
106+
</span>
107+
</a>
108+
</div>
109+
</div>
110+
);
111+
}

_components/TableOfContents.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export default function TableOfContents({ data, toc, hasSubNav }: {
1212

1313
return (
1414
<ul
15-
className={`toc-list hidden sticky p-4 pr-0 h-screen-minus-header overflow-y-auto border-l border-l-foreground-tertiary lg:block lg:w-full ${topClasses}`}
15+
className={`toc-list hidden sticky ${topClasses} h-screen-minus-header overflow-y-auto border-l border-l-foreground-tertiary p-4 pr-0 lg:flex lg:flex-col lg:w-full`}
1616
id="toc"
1717
>
1818
{toc.map((item: TableOfContentsItem_) => (

_config.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,24 @@ site.addEventListener("afterBuild", async () => {
190190
/* NOTE: we used to get gfm.css from the jsr.io CDN, but now we simply have a local copy. This is because it needs to be placed on a CSS layer, which isn't possible with an imported file. */
191191
// Deno.writeTextFileSync(site.dest("gfm.css"), GFM_CSS);
192192

193+
// Copy lint rule markdown source files to _site so they're served at /lint/rules/*.md.
194+
// These files are excluded from Lume's processing pipeline (site.ignore) because
195+
// lint_rule.page.tsx handles page generation, but we still want the raw .md accessible.
196+
try {
197+
await Deno.mkdir(site.dest("lint/rules"), { recursive: true });
198+
for await (const entry of Deno.readDir("lint/rules")) {
199+
if (entry.isFile && entry.name.endsWith(".md")) {
200+
await Deno.copyFile(
201+
`lint/rules/${entry.name}`,
202+
site.dest(`lint/rules/${entry.name}`),
203+
);
204+
}
205+
}
206+
log.info("Copied lint rule markdown files to _site/lint/rules/");
207+
} catch (error) {
208+
log.error("Error copying lint rule markdown files: " + error);
209+
}
210+
193211
// Generate LLMs documentation files directly to _site directory
194212
if (Deno.env.get("BUILD_TYPE") == "FULL") {
195213
try {

_includes/doc.tsx

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,17 @@ export default function Doc(data: Lume.Data, helpers: Lume.Helpers) {
6969
class="markdown-body mt-6 sm:mt-6"
7070
>
7171
{!(isReference && !isApiLandingPage) && (
72-
<h1
73-
dangerouslySetInnerHTML={{
74-
__html: helpers.md(data.title!, true),
75-
}}
76-
>
77-
</h1>
72+
<header class="flex items-start justify-between gap-4">
73+
<h1
74+
dangerouslySetInnerHTML={{
75+
__html: helpers.md(data.title!, true),
76+
}}
77+
>
78+
</h1>
79+
{file && !file.includes("[") && file.endsWith(".md") && (
80+
<data.comp.CopyPage file={file} />
81+
)}
82+
</header>
7883
)}
7984
{data.available_since && (
8085
<div class="bg-gray-200 rounded-md text-sm py-3 px-4 mb-4 font-semibold">

_includes/layout.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ export default function Layout(data: Lume.Data) {
7777
<script type="module" defer src="/js/copy.js"></script>
7878
<script type="module" defer src="/js/tabs.js"></script>
7979
<script type="module" defer src="/js/feedback.js"></script>
80+
<script type="module" defer src="/js/copy-page.js"></script>
8081
<script type="module" defer src="/js/search.js"></script>
8182
<script
8283
async

deploy/_data.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ export const sidebar = [
5454
title: "Domains",
5555
href: "/deploy/reference/domains/",
5656
},
57+
{
58+
title: "Cron",
59+
href: "/deploy/reference/cron/",
60+
},
5761
{
5862
title: "Deno KV",
5963
href: "/deploy/reference/deno_kv/",

deploy/reference/cron.md

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
---
2+
title: Cron
3+
description: "Scheduling and managing cron jobs in Deno Deploy, including defining cron jobs in code, execution lifecycle, retries, and observability."
4+
---
5+
6+
Cron jobs are scheduled tasks that run automatically on a defined schedule. You
7+
define cron jobs in your code using the `Deno.cron()` API, deploy your
8+
application, and the platform discovers and runs them on schedule.
9+
10+
## Defining cron jobs in code
11+
12+
`Deno.cron()` takes a human-readable name, a schedule, and a handler function.
13+
The name identifies the cron job in the dashboard and logs, the schedule
14+
determines when it fires, and the handler contains the code to run on each
15+
invocation. The schedule can be either a standard
16+
[5-field cron expression](https://en.wikipedia.org/wiki/Cron#UNIX-like) or a
17+
structured object. All times are UTC — this avoids ambiguity around daylight
18+
saving transitions. See the
19+
[full API reference](https://docs.deno.com/api/deno/~/Deno.cron) for details.
20+
21+
```typescript
22+
Deno.cron("cleanup-old-data", "0 * * * *", () => {
23+
console.log("Cleaning up old data...");
24+
});
25+
26+
Deno.cron(
27+
"sync-data",
28+
"*/15 * * * *",
29+
{
30+
backoffSchedule: [1000, 5000, 10000],
31+
},
32+
async () => {
33+
await syncExternalData();
34+
},
35+
);
36+
```
37+
38+
### Common schedule expressions
39+
40+
| Schedule | Expression |
41+
| ------------------------------ | -------------- |
42+
| Every minute | `* * * * *` |
43+
| Every 15 minutes | `*/15 * * * *` |
44+
| Every hour | `0 * * * *` |
45+
| Every 3 hours | `0 */3 * * *` |
46+
| Daily at 1 AM | `0 1 * * *` |
47+
| Every Wednesday at midnight | `0 0 * * WED` |
48+
| First of the month at midnight | `0 0 1 * *` |
49+
50+
### Organizing cron declarations
51+
52+
You must register cron jobs at the module top level, before `Deno.serve()`
53+
starts. The platform extracts `Deno.cron()` definitions at deployment time by
54+
evaluating your module's top-level code — this is how it discovers which cron
55+
jobs exist and what their schedules are. Cron jobs registered inside request
56+
handlers, conditionals, or after the server starts will not be picked up.
57+
58+
As the number of cron jobs grows, keeping them all in your main entrypoint can
59+
get noisy. A common convention is to define cron handlers in a dedicated file
60+
and import it at the top of your entrypoint.
61+
62+
```typescript title="crons.ts"
63+
Deno.cron("cleanup-old-data", "0 * * * *", async () => {
64+
await deleteExpiredRecords();
65+
});
66+
67+
Deno.cron("sync-data", "*/15 * * * *", async () => {
68+
await syncExternalData();
69+
});
70+
```
71+
72+
```typescript title="main.ts"
73+
import "./crons.ts";
74+
75+
Deno.serve(handler);
76+
```
77+
78+
Because `Deno.cron()` calls execute at the module top level, the import alone is
79+
enough to register them — no need to call or re-export anything.
80+
81+
For projects with many cron jobs, you can use a `crons/` directory with one file
82+
per cron job or group of related cron jobs, and a barrel file that re-exports
83+
them:
84+
85+
```typescript title="crons/mod.ts"
86+
import "./cleanup.ts";
87+
import "./sync.ts";
88+
```
89+
90+
```typescript title="main.ts"
91+
import "./crons/mod.ts";
92+
93+
Deno.serve(handler);
94+
```
95+
96+
### Restrictions
97+
98+
There are a couple of restrictions to keep in mind when defining cron jobs. The
99+
name passed to `Deno.cron()` can be at most **256 characters** long — this is
100+
the identifier that appears in the dashboard and logs, so keeping it concise is
101+
good practice regardless. On free organizations, each revision can register at
102+
most **10 cron jobs**. If your project needs more, upgrading to a paid plan
103+
removes this limit.
104+
105+
## Execution lifecycle & status
106+
107+
When a cron job is due, it fires independently on each timeline where it's
108+
registered. Each execution runs the handler from that timeline's active
109+
revision. For example, if a `cleanup-old-data` cron job is registered in both
110+
the production timeline and a `staging` branch timeline, the production
111+
execution runs the handler from the production revision, and the staging
112+
execution runs the handler from the staging revision. Each execution will be
113+
billed as one inbound HTTP request.
114+
115+
Cron job executions progress through these statuses:
116+
117+
| Status | Color | Description |
118+
| ----------- | ------ | --------------------------------------- |
119+
| **Running** | Yellow | Cron job handler is currently executing |
120+
| **OK** | Green | Completed successfully |
121+
| **Error** | Red | Failed — hover to see the error message |
122+
123+
The platform prevents overlapping executions: the same cron job cannot run
124+
concurrently. If a cron job is still running when the next scheduled invocation
125+
is due, that invocation is skipped. This avoids resource contention and ensures
126+
each execution can complete without interference.
127+
128+
## Retries & backoff
129+
130+
By default, failed cron job executions are not retried. You can optionally
131+
provide a `backoffSchedule` array to enable retries and control exactly when
132+
each one happens.
133+
134+
The `backoffSchedule` is an array of delays in milliseconds, where each element
135+
specifies how long to wait before the next retry attempt:
136+
137+
- Maximum **5 retries** per execution
138+
- Maximum delay per retry: **1 hour** (3,600,000 ms)
139+
- Retries do not affect the cron job schedule — the next scheduled run will be
140+
executed on time, even if the previous run has retries pending. If a retry and
141+
the next scheduled run coincide, the later one will be skipped as per the
142+
overlapping policy described above.
143+
144+
Example: `backoffSchedule: [1000, 5000, 10000]` retries up to 3 times with
145+
delays of 1s, 5s, and 10s.
146+
147+
## Dashboard
148+
149+
The **Cron** tab in the app sidebar gives you an overview of all registered cron
150+
jobs across your project. Each entry shows the cron job's schedule, its most
151+
recent executions, and the active [timelines](/deploy/reference/timelines/) it
152+
belongs to. When a cron job with the same name is registered with different
153+
schedules on different timelines, each distinct schedule appears as its own
154+
entry so you can track them independently.
155+
156+
Click "View Details" on any cron job to open its detail page, which shows the
157+
full execution history. You can filter executions using the search bar:
158+
159+
- **`status:<value>`** — filter by status (`ok`, `error`, `running`)
160+
- **`timeline:<name>`** — filter by timeline name (substring match,
161+
case-insensitive)
162+
163+
## Observability integration
164+
165+
Cron job executions produce OpenTelemetry traces. Click "View Trace" in the
166+
execution history to navigate to the
167+
[Observability](/deploy/reference/observability/) traces page for that specific
168+
trace.
169+
170+
On the traces page, you can use these cron-related filters:
171+
172+
- **`kind:cron`** — show only cron spans
173+
- **`cron.name:<name>`** — filter by cron name
174+
- **`cron.schedule:<schedule>`** — filter by schedule expression
175+
176+
## Timelines
177+
178+
Cron jobs run on production and git branch
179+
[timelines](/deploy/reference/timelines/). The platform extracts `Deno.cron()`
180+
definitions at deployment time and schedules them for execution, so each
181+
timeline runs the set of cron jobs defined in its active revision's code. To
182+
add, remove, or modify cron jobs, update your code and deploy a new revision.
183+
Rolling back to a previous deployment re-registers the cron jobs from that
184+
deployment. You can see which cron jobs are currently registered in a given
185+
timeline from its page in the dashboard.

examples/_data.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,11 @@ export const sidebar = [
166166
"https://www.youtube.com/watch?v=oxVwTT-rZRo&list=PLvvLnBDNuTEov9EBIp3MMfHlBxaKGRWTe&index=6",
167167
type: "video",
168168
},
169+
{
170+
title: "Temporal API",
171+
href: "/examples/temporal/",
172+
type: "example",
173+
},
169174
{
170175
title: "Better debugging with the console API",
171176
href: "/examples/debugging_with_console_tutorial/",
@@ -1077,11 +1082,6 @@ export const sidebar = [
10771082
href: "/examples/udp_connector/",
10781083
type: "example",
10791084
},
1080-
{
1081-
title: "Temporal API",
1082-
href: "/examples/temporal/",
1083-
type: "example",
1084-
},
10851085
{
10861086
title: "Importing text",
10871087
href: "/examples/importing_text/",

0 commit comments

Comments
 (0)