├── static ├── robots.txt ├── logo.png └── favicon.ico ├── CODEOWNERS ├── .gitignore ├── data ├── version.json ├── pid.ts ├── hello-world.ts ├── node.ts ├── typescript.ts ├── deno-version.ts ├── importing-json.ts ├── tcp-connector.ts ├── http-server.ts ├── cron.ts ├── tls-listener.ts ├── dependency-management.ts ├── timers.ts ├── postgres.ts ├── npm.ts ├── udp-connector.ts ├── parsing-serializing-yaml.ts ├── symlinks.ts ├── tls-connector.ts ├── redis.ts ├── byte-manipulation.ts ├── deleting-files.ts ├── tcp-listener.ts ├── udp-listener.ts ├── os-signals.ts ├── walking-directories.ts ├── dns-queries.ts ├── mongo.ts ├── prompts.ts ├── import-export.ts ├── parsing-serializing-toml.ts ├── http-server-routing.ts ├── subprocesses-output.ts ├── parsing-serializing-json.ts ├── uuids.ts ├── subprocesses-spawn.ts ├── checking-file-existence.ts ├── benchmarking.ts ├── websocket.ts ├── http-server-websocket.ts ├── create-remove-directories.ts ├── url-parsing.ts ├── ulid.ts ├── http-server-files.ts ├── moving-renaming-files.ts ├── http-server-streaming.ts ├── command-line-arguments.ts ├── webassembly.ts ├── queues.ts ├── watching-files.ts ├── hex-base64-encoding.ts ├── color-logging.ts ├── parsing-serializing-csv.ts ├── environment-variables.ts ├── writing-files.ts ├── streaming-files.ts ├── permissions.ts ├── kv-watch.ts ├── temporary-files.ts ├── web-workers.ts ├── reading-files.ts ├── path-operations.ts ├── writing-tests.ts ├── piping-streams.ts ├── hashing.ts ├── http-requests.ts └── kv.ts ├── twind.config.ts ├── utils ├── prism.ts ├── constants.ts ├── example.ts └── example_test.ts ├── dev.ts ├── fresh.config.ts ├── routes ├── gfm.css.ts ├── _app.tsx ├── [id] │ ├── [file].ts │ └── index.tsx └── index.tsx ├── main.ts ├── .vscode └── settings.json ├── .github └── workflows │ └── ci.yml ├── components ├── Page.tsx ├── List.tsx ├── Footer.tsx ├── Header.tsx └── Logo.tsx ├── _tools └── check_valid_data.ts ├── fresh.gen.ts ├── LICENSE ├── islands └── CopyButton.tsx ├── deno.json ├── README.md └── toc.ts /static/robots.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @lucacasonato -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | deno.lock 3 | # Fresh build directory 4 | _fresh/ 5 | -------------------------------------------------------------------------------- /data/version.json: -------------------------------------------------------------------------------- 1 | { 2 | "file": { 3 | "version": "1" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/denoland/denobyexample/main/static/logo.png -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/denoland/denobyexample/main/static/favicon.ico -------------------------------------------------------------------------------- /twind.config.ts: -------------------------------------------------------------------------------- 1 | import { Options } from "$fresh/plugins/twind.ts"; 2 | 3 | export default { 4 | selfURL: import.meta.url, 5 | } as Options; 6 | -------------------------------------------------------------------------------- /utils/prism.ts: -------------------------------------------------------------------------------- 1 | export { default as Prism } from "$prism"; 2 | import "$prism/components/prism-jsx?no-check"; 3 | import "$prism/components/prism-typescript?no-check"; 4 | import "$prism/components/prism-tsx?no-check"; 5 | -------------------------------------------------------------------------------- /dev.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S deno run -A --watch=static/,routes/ 2 | 3 | import dev from "$fresh/dev.ts"; 4 | import config from "./fresh.config.ts"; 5 | 6 | import "$std/dotenv/load.ts"; 7 | 8 | await dev(import.meta.url, "./main.ts", config); 9 | -------------------------------------------------------------------------------- /fresh.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "$fresh/server.ts"; 2 | import twindPlugin from "$fresh/plugins/twind.ts"; 3 | import twindConfig from "./twind.config.ts"; 4 | export default defineConfig({ 5 | plugins: [twindPlugin(twindConfig)], 6 | }); 7 | -------------------------------------------------------------------------------- /routes/gfm.css.ts: -------------------------------------------------------------------------------- 1 | import { Handlers } from "$fresh/server.ts"; 2 | import { CSS } from "$gfm"; 3 | 4 | export const handler: Handlers = { 5 | GET() { 6 | return new Response(CSS, { 7 | status: 200, 8 | headers: { 9 | "content-type": "text/css; charset: utf-8", 10 | }, 11 | }); 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /data/pid.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Process Information 3 | * @difficulty beginner 4 | * @tags cli 5 | * @run 6 | */ 7 | 8 | // The current process's process ID is available in the `Deno.pid` variable. 9 | console.log(Deno.pid); 10 | 11 | // The parent process ID is available in the Deno namespace too. 12 | console.log(Deno.ppid); 13 | -------------------------------------------------------------------------------- /routes/_app.tsx: -------------------------------------------------------------------------------- 1 | import { PageProps } from "$fresh/server.ts"; 2 | 3 | export default function App({ Component }: PageProps) { 4 | return ( 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /main.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | /// 5 | /// 6 | 7 | import "$std/dotenv/load.ts"; 8 | 9 | import { start } from "$fresh/server.ts"; 10 | import manifest from "./fresh.gen.ts"; 11 | import config from "./fresh.config.ts"; 12 | 13 | await start(manifest, config); 14 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "deno.enable": true, 3 | "deno.lint": true, 4 | "deno.unstable": true, 5 | "editor.formatOnSave": true, 6 | "editor.defaultFormatter": "denoland.vscode-deno", 7 | "[typescriptreact]": { 8 | "editor.defaultFormatter": "denoland.vscode-deno" 9 | }, 10 | "[typescript]": { 11 | "editor.defaultFormatter": "denoland.vscode-deno" 12 | }, 13 | "[javascriptreact]": { 14 | "editor.defaultFormatter": "denoland.vscode-deno" 15 | }, 16 | "[javascript]": { 17 | "editor.defaultFormatter": "denoland.vscode-deno" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /utils/constants.ts: -------------------------------------------------------------------------------- 1 | export const TAGS = { 2 | cli: { 3 | title: "cli", 4 | description: "Works in Deno CLI", 5 | }, 6 | deploy: { 7 | title: "deploy", 8 | description: "Works on Deno Deploy", 9 | }, 10 | web: { 11 | title: "web", 12 | description: "Works in on the Web", 13 | }, 14 | }; 15 | 16 | export const DIFFICULTIES = { 17 | "beginner": { 18 | title: "Beginner", 19 | description: "No significant prior knowledge is required for this example.", 20 | }, 21 | "intermediate": { 22 | title: "Intermediate", 23 | description: "Some prior knowledge is needed for this example.", 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | format_lint: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout repository 14 | uses: actions/checkout@v3 15 | 16 | - name: Set up Deno 17 | uses: denoland/setup-deno@v1 18 | 19 | - name: Format 20 | run: deno task fmt_check 21 | 22 | - name: Format 23 | run: deno task lint 24 | 25 | - name: Check Valid Data 26 | run: deno run --allow-read ./_tools/check_valid_data.ts 27 | 28 | - name: Test 29 | run: deno task test 30 | -------------------------------------------------------------------------------- /data/hello-world.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Hello World 3 | * @difficulty beginner 4 | * @tags cli, deploy, web 5 | * @run 6 | * @resource {https://docs.deno.com/runtime/manual/getting_started/installation} Deno: Installation 7 | * @resource {https://docs.deno.com/runtime/manual/getting_started/setup_your_environment} Manual: Set up your environment 8 | */ 9 | 10 | // The one and only line in this program will print "Hello, World!" to the 11 | // console. 12 | console.log("Hello, World!"); 13 | 14 | // Deno programs can either be written in JavaScript or TypeScript, or a mixture 15 | // of both. All code in these examples is written in TypeScript, but all the 16 | // examples also work in JavaScript. 17 | -------------------------------------------------------------------------------- /data/node.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Use Node.js built-in modules 3 | * @difficulty beginner 4 | * @tags cli, deploy 5 | * @run --allow-env 6 | * @resource {https://docs.deno.com/runtime/manual/node} Node.js / npm support in Deno 7 | * @resource {https://docs.deno.com/runtime/manual/node/node_specifiers} node: specifiers 8 | * 9 | * Deno supports most built-in Node.js modules natively - you can include them 10 | * in your code using "node:" specifiers in your imports. 11 | */ 12 | 13 | // Import the os module from core Node to get operating system info 14 | import os from "node:os"; 15 | 16 | // Use the module as you would in Node.js 17 | console.log("Current architecture is:", os.arch()); 18 | console.log("Home directory is:", os.homedir()); 19 | -------------------------------------------------------------------------------- /data/typescript.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Built-in TypeScript support 3 | * @difficulty beginner 4 | * @tags cli, deploy 5 | * @run 6 | * @resource {https://www.typescriptlang.org/docs/handbook/intro.html} TypeScript handbook 7 | * 8 | * Deno natively understands TypeScript code with no compiler to configure. 9 | * Start writing code in .ts files, and the runtime will work with them just 10 | * fine. 11 | */ 12 | 13 | // Define an interface in TypeScript 14 | interface Person { 15 | name: string; 16 | age: number; 17 | } 18 | 19 | // Provide a typed input to a function 20 | function greet(person: Person) { 21 | return "Hello, " + person.name + "!"; 22 | } 23 | 24 | // Everything works with zero config! 25 | console.log(greet({ name: "Alice", age: 36 })); 26 | -------------------------------------------------------------------------------- /data/deno-version.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Getting the Deno version 3 | * @difficulty beginner 4 | * @tags cli 5 | * @run 6 | * @resource {https://deno.land/api?s=Deno.version} Doc: Deno.version 7 | * 8 | * How to examine the version of Deno being used. 9 | */ 10 | 11 | // To print the current version of Deno, just reach into the Deno global object 12 | // where all non-web-standard APIs reside. 13 | console.log("Current Deno version", Deno.version.deno); 14 | 15 | // Deno has two main dependencies: the V8 JavaScript engine (from the Chrome web 16 | // browser) and the TypeScript compiler. The versions of these are also 17 | // accessible in the `Deno.version` object. 18 | console.log("Current TypeScript version", Deno.version.typescript); 19 | console.log("Current V8 version", Deno.version.v8); 20 | -------------------------------------------------------------------------------- /data/importing-json.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Importing JSON 3 | * @difficulty beginner 4 | * @tags cli, web 5 | * @run 6 | * 7 | * JSON files can be imported in JS and TS files using the `import` keyword. 8 | * This makes including static data in a library much easier. 9 | */ 10 | 11 | // File: ./main.ts 12 | 13 | // JSON files can be imported in JS and TS modules. When doing so, you need to 14 | // specify the "json" import assertion type. 15 | import file from "./version.json" with { type: "json" }; 16 | console.log(file.version); 17 | 18 | // Dynamic imports are also supported. 19 | const module = await import("./version.json", { 20 | with: { type: "json" }, 21 | }); 22 | console.log(module.default.version); 23 | 24 | /* File: ./version.json 25 | { 26 | "version": "1.0.0" 27 | } 28 | */ 29 | -------------------------------------------------------------------------------- /components/Page.tsx: -------------------------------------------------------------------------------- 1 | import type { ComponentChildren } from "preact"; 2 | import { Head } from "$fresh/runtime.ts"; 3 | import { Footer } from "./Footer.tsx"; 4 | import { Header } from "./Header.tsx"; 5 | 6 | export function Page(props: { 7 | title: string; 8 | noSubtitle?: boolean; 9 | children: ComponentChildren; 10 | }) { 11 | return ( 12 |
16 | 17 | 22 | {props.title} 23 | 24 |
25 |
26 | {props.children} 27 |
28 |
29 |
30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /data/tcp-connector.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title TCP Connector: Ping 3 | * @difficulty intermediate 4 | * @tags cli 5 | * @run --allow-net 6 | * @resource {https://deno.land/api?s=Deno.connect} Doc: Deno.connect 7 | * @resource {/tcp-listener.ts} Example: TCP Listener 8 | * 9 | * An example of connecting to a TCP server on localhost and writing a 'ping' message to the server. 10 | */ 11 | 12 | // Instantiate an instance of text encoder to write to the TCP stream. 13 | const encoder = new TextEncoder(); 14 | // Establish a connection to our TCP server that is currently being run on localhost port 8080. 15 | const conn = await Deno.connect({ 16 | hostname: "127.0.0.1", 17 | port: 8080, 18 | transport: "tcp", 19 | }); 20 | // Encode the 'ping' message and write to the TCP connection for the server to receive. 21 | await conn.write(encoder.encode("ping")); 22 | -------------------------------------------------------------------------------- /data/http-server.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title HTTP Server: Hello World 3 | * @difficulty intermediate 4 | * @tags cli, deploy 5 | * @run --allow-net 6 | * @resource {https://deno.land/api?s=Deno.serve} Doc: Deno.serve 7 | * @resource {https://developer.mozilla.org/en-US/docs/Web/API/Response} MDN: Response 8 | * @playground https://dash.deno.com/playground/example-helloworld 9 | * 10 | * An example of a HTTP server that serves a "Hello World" message. 11 | */ 12 | 13 | // HTTP servers need a handler function. This function is called for every 14 | // request that comes in. It must return a `Response`. The handler function can 15 | // be asynchronous (it may return a `Promise`). 16 | function handler(_req: Request): Response { 17 | return new Response("Hello, World!"); 18 | } 19 | 20 | // To start the server on the default port, call `Deno.serve` with the handler. 21 | Deno.serve(handler); 22 | -------------------------------------------------------------------------------- /components/List.tsx: -------------------------------------------------------------------------------- 1 | import { Example, ExampleGroup } from "../utils/example.ts"; 2 | 3 | export function IndexItem(props: { example: Example }) { 4 | return ( 5 |
  • 6 | 11 | {props.example.title} 12 | 13 |
  • 14 | ); 15 | } 16 | 17 | export function IndexGroup(props: { group: ExampleGroup }) { 18 | return ( 19 |
  • 20 |

    21 | {props.group.icon && ( 22 |
    23 | 24 |
    25 | )} 26 |
    27 | {props.group.title} 28 |
    29 |

    30 |
      31 | {props.group.items.map((example) => )} 32 |
    33 |
  • 34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /data/cron.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Deno Cron 3 | * @difficulty intermediate 4 | * @tags cli, deploy 5 | * @run --unstable 6 | * @resource {https://docs.deno.com/deploy/kv/manual/cron} Deno Cron user guide 7 | * @resource {https://deno.land/api?s=Deno.cron&unstable=} Deno Cron Runtime API docs 8 | * 9 | * Deno Cron is a cron task scheduler built into the Deno runtime and works with 10 | * zero configuration on Deno Deploy. There's no overlapping cron executions and 11 | * has automatic handler retries on exceptions. 12 | */ 13 | 14 | // Create a cron job called "Log a message" that runs once a minute. 15 | Deno.cron("Log a message", "* * * * *", () => { 16 | console.log("This will print once a minute."); 17 | }); 18 | 19 | // Create a cron job with a backoff schedule measured in milliseconds. 20 | Deno.cron("Retry example", "* * * * *", { 21 | backoffSchedule: [1000, 5000, 10000], 22 | }, () => { 23 | throw new Error("Deno.cron will retry this three times, to no avail!"); 24 | }); 25 | -------------------------------------------------------------------------------- /_tools/check_valid_data.ts: -------------------------------------------------------------------------------- 1 | import { TOC } from "../toc.ts"; 2 | 3 | const files: string[] = []; 4 | const tocFlatten = TOC.flatMap((group) => group.items); 5 | 6 | // Check if every file in data is listed in the TOC 7 | for await (const dirEntry of Deno.readDir("data")) { 8 | if (dirEntry.isDirectory) { 9 | throw `Unexpected directory ${dirEntry.name} in data`; 10 | } 11 | if (dirEntry.isSymlink) { 12 | throw `Unexpected symlink ${dirEntry.name} in data`; 13 | } 14 | 15 | const slug = dirEntry.name.slice(0, -3); 16 | 17 | // ignore .json 18 | if (!dirEntry.name.endsWith(".ts")) { 19 | continue; 20 | } 21 | 22 | files.push(slug); 23 | if (!tocFlatten.includes(slug)) { 24 | throw `${dirEntry.name} is not listed in toc.js`; 25 | } 26 | } 27 | 28 | // Check if everything in TOC is a file in data 29 | for (const filename of tocFlatten) { 30 | if (!files.includes(filename)) { 31 | throw `Found "${filename}" in TOC but no file found in directory`; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /data/tls-listener.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title TCP/TLS Listener: Ping 3 | * @difficulty intermediate 4 | * @tags cli 5 | * @run --allow-net --allow-read 6 | * @resource {https://deno.land/api?s=Deno.listenTls} Doc: Deno.listenTls 7 | * 8 | * An example of a TCP listener using TLS on localhost that will log the message if written to and close the connection if connected to. 9 | */ 10 | 11 | // Instantiate an instance of a TCP listener on localhost port 443. 12 | const listener = Deno.listenTls({ 13 | hostname: "127.0.0.1", 14 | port: 443, 15 | transport: "tcp", 16 | cert: Deno.readTextFileSync("./server.crt"), 17 | key: Deno.readTextFileSync("./server.key"), 18 | }); 19 | 20 | // Await asynchronous connections that are established to our TCP listener. 21 | for await (const conn of listener) { 22 | // Pipe the contents of the TCP stream into stdout 23 | await conn.readable.pipeTo(Deno.stdout.writable); 24 | 25 | // We close the connection that was established. 26 | conn.close(); 27 | } 28 | -------------------------------------------------------------------------------- /components/Footer.tsx: -------------------------------------------------------------------------------- 1 | import { DenoLogo } from "./Logo.tsx"; 2 | 3 | const FOOTER_LINKS = [ 4 | ["https://deno.land/manual", "Manual"], 5 | ["https://doc.deno.land/builtin/stable", "Runtime API"], 6 | ["https://deno.land/std", "Standard Library"], 7 | ["https://deno.land/x", "Third Party Modules"], 8 | ["https://deno.com/blog", "Blog"], 9 | ["https://deno.com/company", "Company"], 10 | ]; 11 | 12 | export function Footer() { 13 | return ( 14 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /data/dependency-management.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Dependency Management 3 | * @difficulty beginner 4 | * @tags cli, deploy 5 | * @resource {/import-export} Example: Importing & Exporting 6 | * @run 7 | * 8 | * It is unwieldy to have to import the same remote module over and over again. 9 | * Deno provides some conventions to make managing dependencies easier. 10 | */ 11 | 12 | // File: ./deps.ts 13 | 14 | // The Deno ecosystem has a convention to re-export all remote dependencies from 15 | // a deps.ts file at the root of the repo. This keeps remote dependencies 16 | // organized, and in a single place. 17 | export * as http from "jsr:@std/http"; 18 | export * as path from "jsr:@std/path"; 19 | 20 | // File: ./main.ts 21 | 22 | // Other files can then import dependencies from the deps.ts file. 23 | // deno-lint-ignore no-unused-vars 24 | import { path } from "./deps.ts"; 25 | 26 | // Doing this makes package version upgrades really easy, as all external 27 | // dependency specifiers live in the same file. 28 | -------------------------------------------------------------------------------- /fresh.gen.ts: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. This file is generated by Fresh. 2 | // This file SHOULD be checked into source version control. 3 | // This file is automatically updated during development when running `dev.ts`. 4 | 5 | import * as $_id_file_ from "./routes/[id]/[file].ts"; 6 | import * as $_id_index from "./routes/[id]/index.tsx"; 7 | import * as $_app from "./routes/_app.tsx"; 8 | import * as $gfm_css from "./routes/gfm.css.ts"; 9 | import * as $index from "./routes/index.tsx"; 10 | import * as $CopyButton from "./islands/CopyButton.tsx"; 11 | import { type Manifest } from "$fresh/server.ts"; 12 | 13 | const manifest = { 14 | routes: { 15 | "./routes/[id]/[file].ts": $_id_file_, 16 | "./routes/[id]/index.tsx": $_id_index, 17 | "./routes/_app.tsx": $_app, 18 | "./routes/gfm.css.ts": $gfm_css, 19 | "./routes/index.tsx": $index, 20 | }, 21 | islands: { 22 | "./islands/CopyButton.tsx": $CopyButton, 23 | }, 24 | baseUrl: import.meta.url, 25 | } satisfies Manifest; 26 | 27 | export default manifest; 28 | -------------------------------------------------------------------------------- /data/timers.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Timeouts & Intervals 3 | * @difficulty beginner 4 | * @tags cli, deploy, web 5 | * @run 6 | * @resource {https://developer.mozilla.org/en-US/docs/Web/API/Window/setTimeout} MDN: setTimeout 7 | * @resource {https://developer.mozilla.org/en-US/docs/Web/API/Window/setInterval} MDN: setInterval 8 | * 9 | * Timers are used to schedule functions to happen at a later time. 10 | */ 11 | 12 | // Here we create a timer that will print "Hello, World!" to the console after 13 | // 1 second (1000 milliseconds). 14 | setTimeout(() => console.log("Hello, World!"), 1000); 15 | 16 | // You can also cancel a timer after it has been created. 17 | const timerId = setTimeout(() => console.log("No!"), 1000); 18 | clearTimeout(timerId); 19 | 20 | // Intervals can be created to repeat a function at a regular interval. 21 | setInterval(() => console.log("Hey!"), 1000); 22 | 23 | // Intervals can also be cancelled. 24 | const intervalId = setInterval(() => console.log("Nope"), 1000); 25 | clearInterval(intervalId); 26 | -------------------------------------------------------------------------------- /data/postgres.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Connect to Postgres 3 | * @difficulty intermediate 4 | * @tags cli, deploy 5 | * @run --allow-net --allow-env 6 | * @resource {https://deno-postgres.com/} Deno Postgres docs 7 | * @resource {https://deno.land/x/postgres} Deno Postgres on deno.land/x 8 | * 9 | * Using the Deno Postgres client, you can connect to a Postgres database 10 | * running anywhere. 11 | */ 12 | 13 | // Import the Client constructor from deno.land/x 14 | import { Client } from "https://deno.land/x/postgres@v0.17.0/mod.ts"; 15 | 16 | // Initialize the client with connection information for your database, and 17 | // create a connection. 18 | const client = new Client({ 19 | user: "user", 20 | database: "test", 21 | hostname: "localhost", 22 | port: 5432, 23 | }); 24 | await client.connect(); 25 | 26 | // Execute a SQL query 27 | const result = await client.queryArray("SELECT ID, NAME FROM PEOPLE"); 28 | console.log(result.rows); // [[1, 'Carlos'], [2, 'John'], ...] 29 | 30 | // Close the connection to the database 31 | await client.end(); 32 | -------------------------------------------------------------------------------- /data/npm.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Import modules from npm 3 | * @difficulty beginner 4 | * @tags cli 5 | * @run --allow-net --allow-read --allow-env 6 | * @resource {https://docs.deno.com/runtime/manual/node} Node.js / npm support in Deno 7 | * @resource {https://docs.deno.com/runtime/manual/node/npm_specifiers} npm: specifiers 8 | * @resource {https://www.npmjs.com/package/express} express module on npm 9 | * 10 | * Use JavaScript modules from npm in your Deno programs with the "npm:" 11 | * specifier in your imports. 12 | */ 13 | 14 | // Import the express module from npm using an npm: prefix, and appending a 15 | // version number. Dependencies from npm can be configured in an import map 16 | // also. 17 | import express from "npm:express@4.18.2"; 18 | 19 | // Create an express server 20 | const app = express(); 21 | 22 | // Configure a route that will process HTTP GET requests 23 | app.get("/", (_req, res) => { 24 | res.send("Welcome to the Dinosaur API!"); 25 | }); 26 | 27 | // Start an HTTP server using the configured Express app 28 | app.listen(3000); 29 | -------------------------------------------------------------------------------- /data/udp-connector.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title UDP Connector: Ping 3 | * @difficulty intermediate 4 | * @tags cli 5 | * @run --allow-net --unstable 6 | * @resource {https://deno.land/api?s=Deno.connect} Doc: Deno.connect 7 | * @resource {/udp-listener.ts} Example: UDP Listener 8 | * 9 | * An example of writing a 'ping' message to a UDP server on localhost. 10 | */ 11 | 12 | // Instantiate an instance of text encoder to write to the UDP stream. 13 | const encoder = new TextEncoder(); 14 | 15 | // Create a UDP listener to allow us to send a ping to the other UDP server. 16 | const listener = Deno.listenDatagram({ 17 | port: 10001, 18 | transport: "udp", 19 | }); 20 | 21 | // Since UDP is a connectionless protocol, we need to define the address of the listener 22 | const peerAddress: Deno.NetAddr = { 23 | transport: "udp", 24 | hostname: "127.0.0.1", 25 | port: 10000, 26 | }; 27 | 28 | // Encode the 'ping' message and write to the UDP connection for the server to receive. 29 | await listener.send(encoder.encode("ping"), peerAddress); 30 | listener.close(); 31 | -------------------------------------------------------------------------------- /data/parsing-serializing-yaml.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Parsing and serializing YAML 3 | * @difficulty beginner 4 | * @tags cli, deploy, web 5 | * @run 6 | * @resource {/import-export} Example: Importing & Exporting 7 | * @resource {https://yaml.org} Spec: YAML 8 | * 9 | * YAML is a widely used data serialization language designed to be easily human readable and writeable. 10 | */ 11 | import { parse, stringify } from "jsr:@std/yaml"; 12 | 13 | // To parse a YAML string, you can use the the standard library's YAML 14 | // parse function. The value is returned as a JavaScript object. 15 | const text = ` 16 | foo: bar 17 | baz: 18 | - qux 19 | - quux 20 | `; 21 | const data = parse(text); 22 | console.log(data.foo); 23 | console.log(data.baz.length); 24 | 25 | // To turn a JavaScript object into a YAML string, you can use the standard 26 | // library's YAML stringify function. 27 | const obj = { 28 | hello: "world", 29 | numbers: [1, 2, 3], 30 | }; 31 | const yaml = stringify(obj); 32 | console.log(yaml); 33 | //- hello: word 34 | //- numbers: 35 | //- - 1 36 | //- - 2 37 | //- - 3 38 | -------------------------------------------------------------------------------- /data/symlinks.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Creating & Resolving Symlinks 3 | * @difficulty beginner 4 | * @tags cli 5 | * @run --allow-write --allow-read 6 | * @resource {https://deno.land/api?s=Deno.writeTextFile} Doc: Deno.writeTextFile 7 | * @resource {https://deno.land/api?s=Deno.symlink} Doc: Deno.symlink 8 | * 9 | * Creating and resolving symlink is a common task. Deno has a number of 10 | * functions for this task. 11 | */ 12 | 13 | // First we will create a text file to link to. 14 | await Deno.writeTextFile("example.txt", "hello from symlink!"); 15 | 16 | // Now we can create a soft link to the file 17 | await Deno.symlink("example.txt", "link"); 18 | 19 | // To resolve the path of a symlink, we can use Deno.realPath 20 | console.log(await Deno.realPath("link")); 21 | 22 | // Symlinks are automatically resolved, so we can just read 23 | // them like text files 24 | console.log(await Deno.readTextFile("link")); 25 | 26 | // In certain cases, soft links don't work. In this case we 27 | // can choose to make "hard links". 28 | await Deno.link("example.txt", "hardlink"); 29 | console.log(await Deno.readTextFile("hardlink")); 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Deno Land Inc 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /data/tls-connector.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title TCP/TLS Connector: Ping 3 | * @difficulty intermediate 4 | * @tags cli 5 | * @run --allow-net --allow-read 6 | * @resource {https://deno.land/api?s=Deno.connectTls} Doc: Deno.connectTls 7 | * @resource {/tls-listener.ts} Example: TCP/TLS Listener 8 | * 9 | * An example of connecting to a TCP server using TLS on localhost and writing a 'ping' message to the server. 10 | */ 11 | 12 | // Read a CA Certificate from the file system 13 | const caCert = await Deno.readTextFile("./root.pem"); 14 | 15 | // Establish a connection to our TCP server using TLS that is currently being run on localhost port 443. 16 | // We use a custom CA root certificate here. If we remove this option, Deno defaults to using 17 | // Mozilla's root certificates. 18 | const conn = await Deno.connectTls({ 19 | hostname: "127.0.0.1", 20 | port: 443, 21 | caCerts: [caCert], 22 | }); 23 | 24 | // Instantiate an instance of text encoder to write to the TCP stream. 25 | const encoder = new TextEncoder(); 26 | 27 | // Encode the 'ping' message and write to the TCP connection for the server to receive. 28 | await conn.write(encoder.encode("ping")); 29 | -------------------------------------------------------------------------------- /data/redis.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Connect to Redis 3 | * @difficulty intermediate 4 | * @tags cli, deploy 5 | * @run --allow-net --allow-env 6 | * @resource {https://deno.land/x/r2d2} r2d2 on deno.land/x 7 | * @resource {https://redis.io/docs/getting-started/} Getting started with Redis 8 | * 9 | * Using the r2d2 module, you can connect to a Redis database running anywhere. 10 | */ 11 | 12 | // Import the `sendCommand()` function from r2d2 13 | import { sendCommand } from "https://deno.land/x/r2d2/mod.ts"; 14 | 15 | // Create a TCP connection with the Redis server 16 | const redisConn = await Deno.connect({ port: 6379 }); 17 | 18 | // Authenticate with the server by sending the command "AUTH " 19 | await sendCommand(redisConn, [ 20 | "AUTH", 21 | Deno.env.get("REDIS_USERNAME")!, 22 | Deno.env.get("REDIS_PASSWORD")!, 23 | ]); 24 | 25 | // Set the "hello" key to have value "world" using the command "SET hello world" 26 | await sendCommand(redisConn, ["SET", "hello", "world"]); // "OK" 27 | 28 | // Get the "hello" key using the command "GET hello" 29 | await sendCommand(redisConn, ["GET", "hello"]); // "world" 30 | 31 | // Close the connection to the database 32 | redisConn.close(); 33 | -------------------------------------------------------------------------------- /data/byte-manipulation.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Manipulating byte arrays 3 | * @difficulty beginner 4 | * @tags cli 5 | * @run 6 | * @resource {$std/bytes} Doc: std/bytes 7 | * 8 | * When working with lower-level data we often deal 9 | * with byte arrays in the form of Uint8Arrays. There 10 | * are some common manipulations and queries that can 11 | * be done and are included with the standard library. 12 | */ 13 | 14 | // Let's initialize some byte arrays 15 | const a = new Uint8Array([0, 1, 2, 3, 4]); 16 | const b = new Uint8Array([5, 6, 7, 8, 9]); 17 | const c = new Uint8Array([4, 5]); 18 | 19 | // We can concatenate two byte arrays using the 20 | // concat method 21 | import { concat } from "jsr:@std/bytes/concat"; 22 | const d = concat([a, b]); 23 | console.log(d); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 24 | 25 | // Sometimes we need to repeat certain bytes 26 | import { repeat } from "jsr:@std/bytes/repeat"; 27 | console.log(repeat(c, 4)); // [4, 5, 4, 5, 4, 5, 4, 5] 28 | 29 | // Sometimes we need to mutate a Uint8Array and need a copy 30 | import { copy } from "jsr:@std/bytes/copy"; 31 | const cpy = new Uint8Array(5); 32 | console.log("Bytes copied:", copy(b, cpy)); // 5 33 | console.log("Bytes:", cpy); // [5, 6, 7, 8, 9] 34 | -------------------------------------------------------------------------------- /data/deleting-files.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Deleting Files 3 | * @difficulty beginner 4 | * @tags cli 5 | * @resource {https://deno.land/api?s=Deno.remove} Doc: Deno.remove 6 | * 7 | * Removing files and directories is a common task. Deno has a number of 8 | * functions for this task. 9 | */ 10 | 11 | // In the case that we want to remove a simple file, 12 | // we can simply call Deno.remove with the filename as 13 | // a parameter 14 | await Deno.remove("example.txt"); 15 | 16 | // There is also a sync version of the api available 17 | Deno.removeSync("example.txt"); 18 | 19 | // If we want to remove a directory, we could do exactly 20 | // what we did above. If the directory has contents, the 21 | // call would error out. If we want to recursively delete 22 | // the contents of a directory, we should set recursive to 23 | // true 24 | await Deno.remove("./dir", { recursive: true }); 25 | 26 | // A common pattern is to remove a file or directory only 27 | // if it already exists. The correct way of doing this is 28 | // by just doing it and trying to catch any NotFound errors. 29 | try { 30 | await Deno.remove("example.txt"); 31 | } catch (err) { 32 | if (!(err instanceof Deno.errors.NotFound)) { 33 | throw err; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /islands/CopyButton.tsx: -------------------------------------------------------------------------------- 1 | // https://github.com/denoland/doc_components/blob/main/icons.tsx 2 | function Copy() { 3 | return ( 4 | 11 | 15 | 16 | ); 17 | } 18 | 19 | export default function CopyButton(props: { text: string }) { 20 | return ( 21 |
    22 | 28 |
    29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /data/tcp-listener.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title TCP Listener: Ping 3 | * @difficulty intermediate 4 | * @tags cli 5 | * @run --allow-net 6 | * @resource {https://deno.land/api?s=Deno.listen} Doc: Deno.listen 7 | * 8 | * An example of a TCP listener on localhost that will log the message if written to and close the connection if connected to. 9 | */ 10 | 11 | // Instantiate an instance of text decoder to read the TCP stream bytes back into plaintext. 12 | const decoder = new TextDecoder(); 13 | 14 | // Instantiate an instance of a TCP listener on localhost port 8080. 15 | const listener = Deno.listen({ 16 | hostname: "127.0.0.1", 17 | port: 8080, 18 | transport: "tcp", 19 | }); 20 | // Await asynchronous connections that are established to our TCP listener. 21 | for await (const conn of listener) { 22 | // Instantiate an buffer array to store the contents of our read TCP stream. 23 | const buf = new Uint8Array(1024); 24 | // Read the contents of the TCP stream into our buffer array. 25 | await conn.read(buf); 26 | // Here we log the results of the bytes that were read into our buffer array. 27 | console.log("Server - received: ", decoder.decode(buf)); 28 | // We close the connection that was established. 29 | conn.close(); 30 | } 31 | -------------------------------------------------------------------------------- /data/udp-listener.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title UDP Listener: Ping 3 | * @difficulty intermediate 4 | * @tags cli 5 | * @run --allow-net --unstable 6 | * @resource {https://deno.land/api?s=Deno.listenDatagram} Doc: Deno.listenDatagram 7 | * @resource {/udp-connector.ts} Example: UDP Connector 8 | * 9 | * An example of a UDP listener on localhost that will log the message 10 | * if written to and close the connection if connected to. 11 | */ 12 | 13 | // Instantiate an instance of text decoder to read the UDP stream bytes back into plaintext. 14 | const decoder = new TextDecoder(); 15 | 16 | // Instantiate an instance of a UDP listener on localhost port 10000. 17 | const listener = Deno.listenDatagram({ 18 | port: 10000, 19 | transport: "udp", 20 | }); 21 | 22 | // Await asynchronous messages that are sent to our UDP listener. 23 | for await (const [data, address] of listener) { 24 | // Here we log the address of the sender of the data 25 | console.log("Server - received information from", address); 26 | 27 | // Here we log the results of the bytes that were read into our buffer array. 28 | console.log("Server - received:", decoder.decode(data)); 29 | 30 | // We close the connection that was established. 31 | listener.close(); 32 | } 33 | -------------------------------------------------------------------------------- /components/Header.tsx: -------------------------------------------------------------------------------- 1 | export function Header(props: { noSubtitle?: boolean }) { 2 | return ( 3 |
    4 | {!props.noSubtitle 5 | ? ( 6 | 7 | logo 8 | 9 | 10 | Deno 11 | 12 | 13 | by example 14 | 15 | 16 | 17 | ) 18 | :
    } 19 | 33 |
    34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /data/os-signals.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Handling OS Signals 3 | * @difficulty beginner 4 | * @tags cli 5 | * @run 6 | * @resource {https://deno.land/api?s=Deno.addSignalListener} Doc: Deno.addSignalListener 7 | * 8 | * You can listen for OS signals using the `Deno.addSignalListener` function. 9 | * This allows you to do things like gracefully shutdown a server when a 10 | * `SIGINT` signal is received. 11 | */ 12 | 13 | console.log("Counting seconds..."); 14 | 15 | let i = 0; 16 | 17 | // We isolate the signal handler function so that we can remove it later. 18 | function sigIntHandler() { 19 | console.log("interrupted! your number was", i); 20 | Deno.exit(); 21 | } 22 | 23 | // Then, we can listen for the `SIGINT` signal, 24 | // which is sent when the user presses Ctrl+C. 25 | Deno.addSignalListener("SIGINT", sigIntHandler); 26 | 27 | // While we're waiting for the signal, we can do other things, 28 | // like count seconds, or start a server. 29 | const interval = setInterval(() => { 30 | i++; 31 | }, 1000); 32 | 33 | // And, after 10 seconds, we can exit and remove the signal listener. 34 | setTimeout(() => { 35 | clearInterval(interval); 36 | Deno.removeSignalListener("SIGINT", sigIntHandler); 37 | console.log("done! it has been 10 seconds"); 38 | }, 10_000); 39 | -------------------------------------------------------------------------------- /data/walking-directories.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Walking directories 3 | * @difficulty beginner 4 | * @tags cli 5 | * @run --allow-read 6 | * @resource {https://deno.land/api?s=Deno.readDir} Doc: Deno.readDir 7 | * @resource {$std/fs/walk.ts} Doc: std/walk 8 | * 9 | * When doing something like filesystem routing, it is 10 | * useful to be able to walk down a directory to visit 11 | * files. 12 | */ 13 | 14 | // If the directory has no depth (no folders), we can use 15 | // the built-in Deno.readDir 16 | for await (const dirEntry of Deno.readDir(".")) { 17 | console.log("Basic listing:", dirEntry.name); 18 | } 19 | 20 | // If on the other hand you need to recursively walk 21 | // a repository, the standard library has a method for this. 22 | // In the most simple case it is a drop-in replacement 23 | import { walk } from "jsr:@std/fs/walk"; 24 | 25 | for await (const dirEntry of walk(".")) { 26 | console.log("Recursive walking:", dirEntry.name); 27 | } 28 | 29 | // We are also able to specify some settings to customize 30 | // our results. In the case of building filesystem routing 31 | // limiting results to only certain extensions may be useful 32 | for await (const dirEntry of walk(".", { exts: ["ts"] })) { 33 | console.log("Recursive walking with extension:", dirEntry.name); 34 | } 35 | -------------------------------------------------------------------------------- /data/dns-queries.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Running DNS Queries 3 | * @difficulty beginner 4 | * @tags cli, deploy 5 | * @run --allow-net 6 | * @resource {https://deno.land/api?s=Deno.resolveDns} Doc: Deno.resolveDns 7 | * @resource {https://developer.mozilla.org/en-US/docs/Glossary/DNS} MDN: DNS 8 | * 9 | * There are some cases when running DNS queries is useful. This is 10 | * usually the case when one would like to use a DNS server not configured 11 | * on the machine. 12 | */ 13 | 14 | // In the most basic basic case, we can query a domain's A record. 15 | // This will give us a list of ipv4 addresses. 16 | const a = await Deno.resolveDns("example.com", "A"); 17 | console.log(a); 18 | 19 | // We can also query other record types. In this case we are querying 20 | // an MX record which is related to email protocols. Deno supports querying 21 | // A, AAAA, ANAME, CAA, CNAME, MX, NAPTR, NS, PTR, SOA, SRV, and TXT records. 22 | const mx = await Deno.resolveDns("example.com", "MX"); 23 | console.log(mx); 24 | 25 | // We are also optionally able to specify a nameserver via an ip address 26 | // and (optionally) a port number. To override the system configuration. 27 | const aaaa = await Deno.resolveDns("example.com", "AAAA", { 28 | nameServer: { ipAddr: "8.8.8.8", port: 53 }, 29 | }); 30 | console.log(aaaa); 31 | -------------------------------------------------------------------------------- /data/mongo.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Connect to MongoDB 3 | * @difficulty intermediate 4 | * @tags cli, deploy 5 | * @run --allow-net --allow-sys --allow-read 6 | * @resource {https://deno.land/x/mongo} Deno MongoDB on deno.land/x 7 | * 8 | * Using the Deno MongoDB client, you can connect to a Mongo database 9 | * running anywhere. 10 | */ 11 | 12 | import { MongoClient } from "npm:mongodb@6.1.0"; 13 | 14 | // Create a new instance of the MongoDB client running locally on port 27017 15 | const client = new MongoClient("mongodb://127.0.0.1:27017"); 16 | 17 | // Connect to the MongoDB server 18 | await client.connect(); 19 | 20 | // Define the schema for the collection 21 | interface DinosaurSchema { 22 | name: string; 23 | skills: string[]; 24 | } 25 | 26 | // Access the database 27 | const db = client.db("animals"); 28 | 29 | // Access the collection within the database 30 | const dinosaurs = db.collection("dinosaurs"); 31 | 32 | // Insert a new document into the collection 33 | await dinosaurs.insertOne({ 34 | name: "deno", 35 | skills: ["dancing", "hiding"], 36 | }); 37 | 38 | // Find all documents in the collection with the filter 39 | const allDinosaurs = await dinosaurs.find({ name: "deno" }).toArray(); 40 | 41 | console.log(allDinosaurs); 42 | 43 | // Close the MongoDB client connection 44 | client.close(); 45 | -------------------------------------------------------------------------------- /data/prompts.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Input Prompts 3 | * @difficulty beginner 4 | * @tags cli, web 5 | * @run 6 | * @resource {https://developer.mozilla.org/en-US/docs/Web/API/Window/prompt} MDN: prompt 7 | * @resource {https://developer.mozilla.org/en-US/docs/Web/API/Window/alert} MDN: alert 8 | * @resource {https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm} MDN: confirm 9 | * 10 | * Prompts are used to ask the user for input or feedback on actions. 11 | */ 12 | 13 | // The most basic way to interact with the user is by alerting them, and waiting 14 | // for them to acknowledge by pressing [Enter]. 15 | alert("Please acknowledge the message."); 16 | console.log("The message has been acknowledged."); 17 | 18 | // Instead of just an acknowledgement, we can also ask the user for a yes/no 19 | // response. 20 | const shouldProceed = confirm("Do you want to proceed?"); 21 | console.log("Should proceed?", shouldProceed); 22 | 23 | // We can also prompt the user for some text input. If the user cancels the 24 | // prompt, the returned value will be `null`. 25 | const name = prompt("Please enter your name:"); 26 | console.log("Name:", name); 27 | 28 | // When prompting you can also specify a default value to use if the user 29 | // cancels the prompt. 30 | const age = prompt("Please enter your age:", "18"); 31 | console.log("Age:", age); 32 | -------------------------------------------------------------------------------- /data/import-export.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Importing & Exporting 3 | * @difficulty beginner 4 | * @tags cli, deploy 5 | * @run 6 | * @resource {/dependency-management} Example: Dependency Management 7 | * @resource {https://docs.deno.com/runtime/manual/basics/modules} Manual: Modules 8 | * 9 | * To build composable programs, it is necessary to be able to import and export 10 | * functions from other modules. This is accomplished by using ECMA script 11 | * modules in Deno. 12 | */ 13 | 14 | // File: ./util.ts 15 | 16 | // To export a function, you use the export keyword. 17 | export function sayHello(thing: string) { 18 | console.log(`Hello, ${thing}!`); 19 | } 20 | 21 | // You can also export types, variables, and classes. 22 | // deno-lint-ignore no-empty-interface 23 | export interface Foo {} 24 | export class Bar {} 25 | export const baz = "baz"; 26 | 27 | // File: ./main.ts 28 | 29 | // To import things from files other files can use the import keyword. 30 | import { sayHello } from "./util.ts"; 31 | sayHello("World"); 32 | 33 | // You can also import all exports from a file. 34 | import * as util from "./util.ts"; 35 | util.sayHello("World"); 36 | 37 | // Imports don't have to be relative, they can also reference absolute file or 38 | // https URLs. 39 | import { VERSION } from "https://deno.land/std/version.ts"; 40 | console.log(VERSION); 41 | -------------------------------------------------------------------------------- /data/parsing-serializing-toml.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Parsing and serializing TOML 3 | * @difficulty beginner 4 | * @tags cli, deploy, web 5 | * @run 6 | * @resource {/import-export} Example: Importing & Exporting 7 | * @resource {https://toml.io} Spec: TOML 8 | * 9 | * TOML is a widely used configuration language designed to be feature-rich and intuitive to write. 10 | */ 11 | import { parse, stringify } from "jsr:@std/toml"; 12 | 13 | // To parse a TOML string, you can use the the standard library's TOML 14 | // parse function. The value is returned as a JavaScript object. 15 | const text = ` 16 | int = 1_000_000 17 | bool = true 18 | 19 | [[bin]] 20 | name = "deno" 21 | path = "cli/main.rs" 22 | 23 | [[bin]] 24 | name = "deno_core" 25 | path = "src/foo.rs" 26 | `; 27 | const data = parse(text); 28 | console.log(data.int); 29 | console.log(data.bin.length); 30 | 31 | // To turn a JavaScript object into a TOML string, you can use the standard 32 | // library's TOML stringify function. 33 | const obj = { 34 | ping: "pong", 35 | complex: [ 36 | { name: "bob", age: 10 }, 37 | { name: "alice", age: 12 }, 38 | ], 39 | }; 40 | const toml = stringify(obj); 41 | console.log(toml); 42 | //- ping = "pong" 43 | //- 44 | //- [[complex]] 45 | //- name = "bob" 46 | //- age = 10 47 | //- 48 | //- [[complex]] 49 | //- name = "alice" 50 | //- age = 12 51 | -------------------------------------------------------------------------------- /data/http-server-routing.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title HTTP Server: Routing 3 | * @difficulty intermediate 4 | * @tags cli, deploy 5 | * @run --allow-net 6 | * @resource {/http-server} Example: HTTP Server: Hello World 7 | * @resource {https://developer.mozilla.org/en-US/docs/Web/API/URL_Pattern_API} MDN: URL Pattern API 8 | * @playground https://dash.deno.com/playground/example-routing 9 | * 10 | * An example of a HTTP server that handles requests with different responses 11 | * based on the incoming URL. 12 | */ 13 | 14 | // URL patterns can be used to match request URLs. They can contain named groups 15 | // that can be used to extract parts of the URL, e.g. the book ID. 16 | const BOOK_ROUTE = new URLPattern({ pathname: "/books/:id" }); 17 | 18 | function handler(req: Request): Response { 19 | // Match the incoming request against the URL patterns. 20 | const match = BOOK_ROUTE.exec(req.url); 21 | // If there is a match, extract the book ID and return a response. 22 | if (match) { 23 | const id = match.pathname.groups.id; 24 | return new Response(`Book ${id}`); 25 | } 26 | 27 | // If there is no match, return a 404 response. 28 | return new Response("Not found (try /books/1)", { 29 | status: 404, 30 | }); 31 | } 32 | 33 | // To start the server on the default port, call `Deno.serve` with the handler. 34 | Deno.serve(handler); 35 | -------------------------------------------------------------------------------- /data/subprocesses-output.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Subprocesses: Collecting Output 3 | * @difficulty intermediate 4 | * @tags cli 5 | * @run --allow-run 6 | * @resource {https://deno.land/api?s=Deno.Command} Doc: Deno.Command 7 | * 8 | * We don't often write programs in isolation. In a lot of cases we want 9 | * to interact with the outside system and spawning a subprocess is a 10 | * common way to do this. 11 | */ 12 | 13 | // The Deno namespace has a unified api for interacting with the outside system 14 | // called Deno.Command. With it, we can initialize some information about the 15 | // command but it will not be executed immediately. 16 | const command = new Deno.Command("deno", { 17 | args: [ 18 | "eval", 19 | "\ 20 | console.log('hello from deno'); \ 21 | console.error('hello from stderr'); \ 22 | ", 23 | ], 24 | }); 25 | 26 | // In the most simple case we just want to run the process to completion. This 27 | // can be achieved using command.output() 28 | let result = await command.output(); 29 | 30 | // It can also be achieved synchronously using command.outputSync() 31 | result = command.outputSync(); 32 | 33 | // We can now interact with stdout and stderr 34 | const textDecoder = new TextDecoder(); 35 | console.log("stdout:", textDecoder.decode(result.stdout)); 36 | console.log("stderr:", textDecoder.decode(result.stderr)); 37 | -------------------------------------------------------------------------------- /data/parsing-serializing-json.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Parsing and serializing JSON 3 | * @difficulty beginner 4 | * @tags cli, deploy, web 5 | * @run 6 | * @resource {https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON} MDN: JSON 7 | * 8 | * JSON is a widely used data interchange format. It is a human-readable, but 9 | * also easily machine-readable. 10 | */ 11 | 12 | // To parse a JSON string, you can use the builtin JSON.parse function. The 13 | // value is returned as a JavaScript object. 14 | const text = `{ 15 | "hello": "world", 16 | "numbers": [1, 2, 3] 17 | }`; 18 | const data = JSON.parse(text); 19 | console.log(data.hello); 20 | console.log(data.numbers.length); 21 | 22 | // To turn a JavaScript object into a JSON string, you can use the builtin 23 | // JSON.stringify function. 24 | const obj = { 25 | hello: "world", 26 | numbers: [1, 2, 3], 27 | }; 28 | const json = JSON.stringify(obj); 29 | console.log(json); 30 | //- {"hello":"world","numbers":[1,2,3]} 31 | 32 | // By default JSON.stringify will output a minified JSON string. You can 33 | // customize this by specifying an indentation number in the third argument. 34 | const json2 = JSON.stringify(obj, null, 2); 35 | console.log(json2); 36 | //- { 37 | //- "hello": "world", 38 | //- "numbers": [ 39 | //- 1, 40 | //- 2, 41 | //- 3 42 | //- ] 43 | //- } 44 | -------------------------------------------------------------------------------- /data/uuids.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Generating & Validating UUIDs 3 | * @difficulty beginner 4 | * @tags cli, deploy, web 5 | * @run 6 | * @resource {https://developer.mozilla.org/en-US/docs/Web/API/Crypto/randomUUID} MDN: crypto.randomUUID 7 | * @resource {$std/uuid/mod.ts} Doc: std/uuid 8 | * 9 | * UUIDs (universally unique identifier) can be used to uniquely identify some 10 | * object or data. 11 | */ 12 | 13 | // A random UUID can be generated using the builtin Web Cryptography API. This 14 | // type of UUID is also known as UUID v4. 15 | const myUUID = crypto.randomUUID(); 16 | console.log("Random UUID:", myUUID); 17 | 18 | // The standard library contains some more functions for working with UUIDs. 19 | import * as uuid from "jsr:@std/uuid"; 20 | 21 | // You can validate that a given string is a valid UUID. 22 | console.log(uuid.validate("not a UUID")); // false 23 | console.log(uuid.validate("6ec0bd7f-11c0-43da-975e-2a8ad9ebae0b")); // true 24 | 25 | // You can also generate a time-based (v1) UUID. By default this uses system 26 | // time as the time source. 27 | console.log(uuid.v1.generate()); 28 | 29 | // SHA-1 namespaced (v5) UUIDs can also be generated. For this you need 30 | // to specify a namespace and data: 31 | const NAMESPACE_URL = "6ba7b810-9dad-11d1-80b4-00c04fd430c8"; 32 | const data = new TextEncoder().encode("deno.land"); 33 | console.log(await uuid.v5.generate(NAMESPACE_URL, data)); 34 | -------------------------------------------------------------------------------- /data/subprocesses-spawn.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Subprocesses: Spawning 3 | * @difficulty intermediate 4 | * @tags cli 5 | * @run --allow-run 6 | * @resource {https://deno.land/api?s=Deno.Command} Doc: Deno.Command 7 | * 8 | * For more complex usecases, we don't simply want the output of some 9 | * command. In this case, we can spawn a subprocess and interact with 10 | * it. 11 | */ 12 | 13 | // The Deno namespace has a unified api for interacting with the outside system 14 | // called Deno.Command. With it, we can initialize some information about the 15 | // command but it will not be executed immediately. 16 | const command = new Deno.Command("deno", { 17 | args: [ 18 | "fmt", 19 | "-", 20 | ], 21 | stdin: "piped", 22 | stdout: "piped", 23 | }); 24 | 25 | // In a slightly more complex case, we want to interact with a spawned 26 | // process. To do this, we first need to spawn it. 27 | const process = command.spawn(); 28 | 29 | // We can now pipe the input into stdin. To do this we must first get 30 | // a writer from the stream and write to it 31 | const writer = process.stdin.getWriter(); 32 | writer.write(new TextEncoder().encode("console.log('hello')")); 33 | writer.releaseLock(); 34 | 35 | // We must then close stdin 36 | await process.stdin.close(); 37 | 38 | // We can now wait for the process to output the results 39 | const result = await process.output(); 40 | console.log(new TextDecoder().decode(result.stdout)); 41 | -------------------------------------------------------------------------------- /deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": { 3 | "fmt": "deno fmt", 4 | "fmt_check": "deno fmt --check", 5 | "lint": "deno lint", 6 | "test": "deno test -A", 7 | "start": "deno run -A --watch=static/,routes/ dev.ts", 8 | "build": "deno run -A dev.ts build", 9 | "preview": "deno run -A main.ts" 10 | }, 11 | "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "preact" }, 12 | "imports": { 13 | "$fresh/": "https://deno.land/x/fresh@1.6.8/", 14 | "preact": "https://esm.sh/preact@10.19.6", 15 | "preact/": "https://esm.sh/preact@10.19.6/", 16 | "preact-render-to-string": "https://esm.sh/*preact-render-to-string@6.2.1", 17 | "@preact/signals": "https://esm.sh/*@preact/signals@1.2.2", 18 | "@preact/signals-core": "https://esm.sh/*@preact/signals-core@1.5.1", 19 | "twind": "https://esm.sh/twind@0.16.19", 20 | "twind/": "https://esm.sh/twind@0.16.19/", 21 | "$gfm": "https://deno.land/x/gfm@0.1.28/mod.ts", 22 | "$prism": "https://esm.sh/prismjs@1.29.0", 23 | "$prism/": "https://esm.sh/prismjs@1.29.0/", 24 | "$std/": "https://deno.land/std@0.207.0/", 25 | "@twind/core": "https://esm.sh/@twind/core@1.1.3", 26 | "@twind/preset-tailwind": "https://esm.sh/@twind/preset-tailwind@1.1.4/", 27 | "@twind/preset-autoprefix": "https://esm.sh/@twind/preset-autoprefix@1.0.7/" 28 | }, 29 | "lock": false, 30 | "lint": { "rules": { "tags": ["fresh", "recommended"] } }, 31 | "exclude": ["**/_fresh/*"] 32 | } 33 | -------------------------------------------------------------------------------- /data/checking-file-existence.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Checking for file existence 3 | * @difficulty beginner 4 | * @tags cli, deploy 5 | * @run --allow-read --allow-write 6 | * 7 | * Sometimes we as developers think that we need to check 8 | * if a file exists or not. More often than not, we are 9 | * entirely wrong. 10 | */ 11 | 12 | // Let's say we wanted to create a folder if one doesn't 13 | // already exist. Logically it makes sense to first verify 14 | // that the folder exists, then try to create it right? 15 | // Wrong. This will create a race condition where if a folder 16 | // gets created in between when you check if the folder exists 17 | // and when you create a folder, your program will crash. 18 | // Instead, you should just create a folder and try to catch 19 | // errors like so. 20 | try { 21 | await Deno.mkdir("new_dir"); 22 | } catch (err) { 23 | if (!(err instanceof Deno.errors.AlreadyExists)) { 24 | throw err; 25 | } 26 | } 27 | 28 | // This applies to almost every usecase. If you have a niche 29 | // usecase that requires you to check for existence of a file 30 | // without doing an filesystem operations other than that 31 | // (which is quite rare), then you can simply lstat the file 32 | // and catch the error. 33 | try { 34 | await Deno.lstat("example.txt"); 35 | console.log("exists!"); 36 | } catch (err) { 37 | if (!(err instanceof Deno.errors.NotFound)) { 38 | throw err; 39 | } 40 | console.log("not exists!"); 41 | } 42 | -------------------------------------------------------------------------------- /data/benchmarking.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Benchmarking 3 | * @difficulty beginner 4 | * @tags cli 5 | * @run deno bench 6 | * @resource {https://docs.deno.com/runtime/manual/tools/benchmarker} Manual: Benchmarker tool 7 | * @resource {/http-requests} Example: HTTP Requests 8 | * 9 | * When writing libraries, a very common task that needs to be done is 10 | * testing the speed of methods, usually against other libraries. Deno 11 | * provides an easy-to-use subcommand for this purpose. 12 | */ 13 | 14 | // The most basic form of deno benchmarking is providing a name and an 15 | // anonymous function to run. 16 | Deno.bench("URL parsing", () => { 17 | new URL("https://deno.land"); 18 | }); 19 | 20 | // We are also able to use an async function. 21 | Deno.bench("Async method", async () => { 22 | await crypto.subtle.digest("SHA-256", new Uint8Array([1, 2, 3])); 23 | }); 24 | 25 | // We can optionally use long form bench definitions. 26 | Deno.bench({ 27 | name: "Long form", 28 | fn: () => { 29 | new URL("https://deno.land"); 30 | }, 31 | }); 32 | 33 | // We are also able to group certain benchmarks together 34 | // using the optional group and baseline arguments. 35 | Deno.bench({ 36 | name: "Date.now()", 37 | group: "timing", 38 | baseline: true, 39 | fn: () => { 40 | Date.now(); 41 | }, 42 | }); 43 | 44 | Deno.bench({ 45 | name: "performance.now()", 46 | group: "timing", 47 | fn: () => { 48 | performance.now(); 49 | }, 50 | }); 51 | -------------------------------------------------------------------------------- /data/websocket.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Outbound WebSockets 3 | * @difficulty beginner 4 | * @tags cli, deploy, web 5 | * @run --allow-net 6 | * @resource {/http-server-websocket} HTTP Server: WebSockets 7 | * @resource {https://developer.mozilla.org/en-US/docs/Web/API/WebSocket} MDN: WebSocket 8 | * 9 | * Opening a WebSocket connection for real-time, bi-directional communication with Deno is very simple. 10 | */ 11 | 12 | // First we need to use the WebSocket constructor to initiate 13 | // our connection to an external server 14 | const socket = new WebSocket("ws://localhost:8000"); 15 | 16 | // Before we do anything with the websocket, we should 17 | // wait to make sure that we are connected. We can do 18 | // so by listening to the "open" event. 19 | socket.addEventListener("open", () => { 20 | // We can read the "ready state" of our instance. 21 | // This determines if we are able to send messages. 22 | // The ready state for open should be 1. 23 | console.log(socket.readyState); 24 | 25 | // We can now send messages to the server. The messages 26 | // can be of the type string, ArrayBuffer, Blob, or 27 | // TypedArray. In this case we will be sending a string 28 | socket.send("ping"); 29 | }); 30 | 31 | // We can handle messages back from the server by listening 32 | // to the "message" event. We can read the data sent by 33 | // the server using the data property of the event. 34 | socket.addEventListener("message", (event) => { 35 | console.log(event.data); 36 | }); 37 | -------------------------------------------------------------------------------- /data/http-server-websocket.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title HTTP Server: WebSockets 3 | * @difficulty intermediate 4 | * @tags cli, deploy 5 | * @run --allow-net 6 | * @resource {/http-server} Example: HTTP Server: Hello World 7 | * @resource {https://developer.mozilla.org/en-US/docs/Web/API/WebSocket} MDN: WebSocket 8 | * 9 | * An example of a HTTP server that handles websocket requests. 10 | */ 11 | 12 | // To start the server on the default port, call `Deno.serve` with the handler. 13 | Deno.serve((req) => { 14 | // First, we verify if the client is negotiating to upgrade to websockets. 15 | // If not, we can give a status of 501 to specify we don't support plain 16 | // http requests. 17 | if (req.headers.get("upgrade") != "websocket") { 18 | return new Response(null, { status: 501 }); 19 | } 20 | 21 | // We can then upgrade the request to a websocket 22 | const { socket, response } = Deno.upgradeWebSocket(req); 23 | 24 | // We now have access to a standard websocket object. 25 | // Let's handle the "open" event 26 | socket.addEventListener("open", () => { 27 | console.log("a client connected!"); 28 | }); 29 | 30 | // We can also handle messages in a similar way. Here we set up 31 | // a simple ping / pong example. 32 | socket.addEventListener("message", (event) => { 33 | if (event.data === "ping") { 34 | socket.send("pong"); 35 | } 36 | }); 37 | 38 | // Lastly we return the response created from upgradeWebSocket. 39 | return response; 40 | }); 41 | -------------------------------------------------------------------------------- /data/create-remove-directories.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Creating & Removing Directories 3 | * @difficulty beginner 4 | * @tags cli 5 | * @run --allow-write 6 | * @resource {https://deno.land/api?s=Deno.mkdir} Doc: Deno.mkdir 7 | * @resource {https://deno.land/api?s=Deno.remove} Doc: Deno.remove 8 | * 9 | * Creating and removing directories is a common task. Deno has a number of 10 | * functions for this task. 11 | */ 12 | 13 | // The `Deno.mkdir()` function creates a directory at the specified path. 14 | // If the directory already exists, it errors. 15 | await Deno.mkdir("new_dir"); 16 | 17 | // A directory can also be created recursively. In the code below, three new 18 | // directories are created: `./dir`, `./dir/dir2`, and `./dir/dir2/subdir`. If 19 | // the recursive option is specified the function will not error if any of the 20 | // directories already exist. 21 | await Deno.mkdir("./dir/dir2/subdir", { recursive: true }); 22 | 23 | // Directories can also be removed. This function below removes the `./new_dir` 24 | // directory. If the directory is not empty, the function will error. 25 | await Deno.remove("./new_dir"); 26 | 27 | // To remove a directory recursively, use the `recursive` option. This will 28 | // remove the `./dir` directory and all of its contents. 29 | await Deno.remove("./dir", { recursive: true }); 30 | 31 | // Synchronous versions of the above functions are also available. 32 | Deno.mkdirSync("new_dir"); 33 | Deno.removeSync("new_dir"); 34 | 35 | // Creating and removing directories requires the `write` permission. 36 | -------------------------------------------------------------------------------- /data/url-parsing.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Manipulating & Parsing URLs 3 | * @difficulty beginner 4 | * @tags cli, deploy, web 5 | * @run 6 | * @resource {https://developer.mozilla.org/en-US/docs/Web/API/URL} MDN: URL 7 | * 8 | * URL is the web standard interface to parse and manipulate URLs. 9 | */ 10 | 11 | // We can create a new object in a variety of ways 12 | // In the most simple case we can simply just write the whole url 13 | let url = new URL("https://deno.land/manual/introduction"); 14 | 15 | // Alternatively we are able to pass a (relative) url which will 16 | // be automatically resolved to an absolute url 17 | url = new URL("/manual/introduction", "https://deno.land"); 18 | 19 | // To get the full url out of an object, we can check the href property 20 | console.log(url.href); // https://deno.land/manual/introduction 21 | 22 | // We are also able to get various other properties from the url. 23 | // Here are a few examples of properties we have access to. 24 | console.log(url.host); // deno.land 25 | console.log(url.origin); // https://deno.land 26 | console.log(url.pathname); // /manual/introduction 27 | console.log(url.protocol); // https: 28 | 29 | // When parsing a url we often need to read the search parameters. 30 | url = new URL("https://deno.land/api?s=Deno.readFile"); 31 | 32 | console.log(url.searchParams.get("s")); // Deno.readFile 33 | 34 | // We're able to manipulate any of these parameters on the fly 35 | url.host = "deno.com"; 36 | url.protocol = "http:"; 37 | 38 | console.log(url.href); // http://deno.com/api?s=Deno.readFile 39 | -------------------------------------------------------------------------------- /data/ulid.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title ULID 3 | * @difficulty beginner 4 | * @tags cli, deploy 5 | * @run 6 | * @resource {https://github.com/ulid/spec} ULID: Specification 7 | * 8 | * One common need for distributed systems are identifiers. ULIDs are a universally 9 | * unique lexicographically sortable identifier with some nice properties. They are 10 | * 128-bit values, encoded as 26 character strings which also encode the timestamp. 11 | * They play very nicely with Deno KV. 12 | */ 13 | 14 | // The standard library contains a function for generating ULIDs. 15 | import { ulid } from "jsr:@std/ulid"; 16 | 17 | // To generate a ULID, simply call the function. 18 | console.log(ulid()); 19 | console.log(ulid()); 20 | console.log(ulid()); 21 | 22 | // ULIDs can also be generated from a timestamp. This is useful for migrating from 23 | // another system. 24 | const timestamp = Date.now(); 25 | console.log(ulid(timestamp)); 26 | console.log(ulid(timestamp)); 27 | console.log(ulid(timestamp)); 28 | 29 | // Given a ULID, you can get the timestamp back out 30 | import { decodeTime } from "$std/ulid/mod.ts"; 31 | const myULID = ulid(); 32 | console.log(decodeTime(myULID)); 33 | 34 | // Optionally, if you're not on a distributed system and want monotonic ULIDs, 35 | // you can use the monotonic ULID generator instead. 36 | import { monotonicUlid } from "$std/ulid/mod.ts"; 37 | console.log(monotonicUlid(150000)); // 000XAL6S41ACTAV9WEVGEMMVR8 38 | console.log(monotonicUlid(150000)); // 000XAL6S41ACTAV9WEVGEMMVR9 39 | console.log(monotonicUlid(150000)); // 000XAL6S41ACTAV9WEVGEMMVRA 40 | -------------------------------------------------------------------------------- /data/http-server-files.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title HTTP Server: Serving Files 3 | * @difficulty intermediate 4 | * @tags cli, deploy 5 | * @run --allow-net --allow-read 6 | * @resource {https://deno.land/api?s=Deno.serve} Doc: Deno.serve 7 | * @resource {$std/http/file_server.ts} Doc: std/http/file_server 8 | * @resource {/http-server} Example: HTTP Server: Hello World 9 | * 10 | * An example of a HTTP server that serves files. 11 | */ 12 | 13 | // Import utility methods for serving files with mime types. 14 | import { serveDir, serveFile } from "jsr:@std/http/file-server"; 15 | 16 | // Here we start a simple server 17 | Deno.serve((req: Request) => { 18 | // Get the path from the url (ie. example.com/whatever -> /whatever) 19 | const pathname = new URL(req.url).pathname; 20 | 21 | if (pathname === "/simple_file") { 22 | // In the most basic case we can just call this function with the 23 | // request object and path to the file 24 | return serveFile(req, "./path/to/file.txt"); 25 | } 26 | 27 | if (pathname.startsWith("/static")) { 28 | // We can also serve a whole directory using the serveDir utility 29 | // method. By default it serves the current directory but this 30 | // can be changed using the "fsRoot" option. We can use the "urlRoot" 31 | // option to strip off the start of the url in the case we don't 32 | // serve the directory at the top level. 33 | return serveDir(req, { 34 | fsRoot: "public", 35 | urlRoot: "static", 36 | }); 37 | } 38 | 39 | return new Response("404: Not Found", { 40 | status: 404, 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /data/moving-renaming-files.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Moving/Renaming Files 3 | * @difficulty beginner 4 | * @tags cli 5 | * @run --allow-read=./ --allow-write=./ 6 | * @resource {https://deno.land/api?s=Deno.rename} Doc: Deno.rename 7 | * 8 | * An example of how to move and rename files and directories in Deno. 9 | */ 10 | 11 | // To rename or move a file, you can use the `Deno.rename` function. The first 12 | // argument is the path to the file to rename. The second argument is the new 13 | // path. 14 | await Deno.writeTextFile("./hello.txt", "Hello World!"); 15 | await Deno.rename("./hello.txt", "./hello-renamed.txt"); 16 | console.log(await Deno.readTextFile("./hello-renamed.txt")); 17 | 18 | // If the source file or the destination directory does not exist, the function 19 | // will reject the returned promise with a `Deno.errors.NotFound` error. You can 20 | // catch this error with a `try/catch` block. 21 | try { 22 | await Deno.rename("./hello.txt", "./does/not/exist"); 23 | } catch (err) { 24 | console.error(err); 25 | } 26 | 27 | // A synchronous version of this function is also available. 28 | Deno.renameSync("./hello-renamed.txt", "./hello-again.txt"); 29 | 30 | // If the destination file already exists, it will be overwritten. 31 | await Deno.writeTextFile("./hello.txt", "Invisible content."); 32 | await Deno.rename("./hello-again.txt", "./hello.txt"); 33 | console.log(await Deno.readTextFile("./hello.txt")); 34 | 35 | // Read and write permissions are necessary to perform this operation. The 36 | // source file needs to be readable and the destination path needs to be 37 | // writable. 38 | -------------------------------------------------------------------------------- /data/http-server-streaming.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title HTTP Server: Streaming 3 | * @difficulty intermediate 4 | * @tags cli, deploy 5 | * @run --allow-net 6 | * @resource {/http-server} Example: HTTP Server: Hello World 7 | * @resource {https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream} MDN: ReadableStream 8 | * @playground https://dash.deno.com/playground/example-streaming 9 | * 10 | * An example HTTP server that streams a response back to the client. 11 | */ 12 | 13 | function handler(_req: Request): Response { 14 | // Set up a variable to store a timer ID, and the ReadableStream. 15 | let timer: number | undefined = undefined; 16 | const body = new ReadableStream({ 17 | // When the stream is first created, start an interval that will emit a 18 | // chunk every second containing the current time. 19 | start(controller) { 20 | timer = setInterval(() => { 21 | const message = `It is ${new Date().toISOString()}\n`; 22 | controller.enqueue(new TextEncoder().encode(message)); 23 | }, 1000); 24 | }, 25 | // If the stream is closed (the client disconnects), cancel the interval. 26 | cancel() { 27 | if (timer !== undefined) { 28 | clearInterval(timer); 29 | } 30 | }, 31 | }); 32 | 33 | // Return a response with the stream as the body. 34 | return new Response(body, { 35 | headers: { 36 | "content-type": "text/plain", 37 | "x-content-type-options": "nosniff", 38 | }, 39 | }); 40 | } 41 | 42 | // To start the server on the default port, call `Deno.serve` with the handler. 43 | Deno.serve(handler); 44 | -------------------------------------------------------------------------------- /data/command-line-arguments.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Command Line Arguments 3 | * @difficulty beginner 4 | * @tags cli 5 | * @run Deno Sushi --help --version=1.0.0 --no-color 6 | * @resource {https://deno.land/api?s=Deno.args} Doc: Deno.args 7 | * @resource {$std/cli/parse_args.ts} Doc: std/cli 8 | * 9 | * Command line arguments are often used to pass configuration options to a 10 | * program. 11 | */ 12 | 13 | // You can get the list of command line arguments from `Deno.args`. 14 | const name = Deno.args[0]; 15 | const food = Deno.args[1]; 16 | console.log(`Hello ${name}, I like ${food}!`); 17 | 18 | // Often you want to parse command line arguments like `--foo=bar` into 19 | // structured data. This can be done using `std/cli`. 20 | import { parseArgs } from "jsr:@std/cli/parse-args"; 21 | 22 | // The `parseArgs` function takes the argument list, and a list of options. In these 23 | // options you specify the types of the accepted arguments and possibly default 24 | // values. An object is returned with the parsed arguments. 25 | // NOTE: this function is based on [`minimist`](https://github.com/minimistjs/minimist), not compatible with the `parseArgs()` function in `node:util`. 26 | const flags = parseArgs(Deno.args, { 27 | boolean: ["help", "color"], 28 | string: ["version"], 29 | default: { color: true }, 30 | negatable: ["color"], 31 | }); 32 | console.log("Wants help?", flags.help); 33 | console.log("Version:", flags.version); 34 | console.log("Wants color?:", flags.color); 35 | 36 | // The `_` field of the returned object contains all arguments that were not 37 | // recognized as flags. 38 | console.log("Other:", flags._); 39 | -------------------------------------------------------------------------------- /data/webassembly.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Web Assembly 3 | * @difficulty intermediate 4 | * @tags cli, deploy, web 5 | * @run 6 | * @resource {https://webassembly.github.io/spec/core/syntax/modules.html} WebAssembly Spec: Modules 7 | * @resource {https://webassembly.github.io/spec/core/syntax/instructions.html} WebAssembly Spec: Instructions 8 | * @resource {https://webassembly.github.io/spec/core/syntax/values.html} WebAssembly Spec: Values 9 | * @resource {https://webassembly.github.io/spec/core/syntax/types.html} WebAssembly Spec: Types 10 | * 11 | * WebAssembly is a binary format for describing a program's data and instructions. 12 | * It is a new and more efficient binary format. 13 | */ 14 | 15 | // We create a new Uint8Array with the bytes of the WebAssembly module. 16 | // This is usually the output of some compiler and not written by hand. 17 | // deno-fmt-ignore 18 | const bytes = new Uint8Array([ 19 | 0,97,115,109,1,0,0,0,1,7,1,96,2, 20 | 127,127,1,127,2,1,0,3,2,1,0,4,1, 21 | 0,5,1,0,6,1,0,7,7,1,3,97,100,100, 22 | 0,0,9,1,0,10,10,1,8,0,32,0,32,1, 23 | 106,15,11,11,1,0, 24 | ]); 25 | // We create an interface for the WebAssembly module containing all exports. 26 | interface WebAssemblyExports { 27 | add(a: number, b: number): number; 28 | } 29 | // The WebAssembly module is a binary format for describing a program's data and instructions. 30 | const exports = await WebAssembly.instantiate(bytes); 31 | // We get the exports from the WebAssembly module and cast it to the interface. 32 | const functions = exports.instance.exports as unknown as WebAssemblyExports; 33 | // We call the exported function. 34 | console.log(functions.add(1, 2)); // 3 35 | -------------------------------------------------------------------------------- /data/queues.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Deno Queues 3 | * @difficulty intermediate 4 | * @tags cli, deploy 5 | * @run --unstable 6 | * @resource {https://docs.deno.com/deploy/kv/manual/queue_overview} Deno Queues user guide 7 | * @resource {https://deno.land/api?s=Deno.Kv&unstable=} Deno Queues Runtime API docs 8 | * 9 | * Deno Queues, built on Deno KV, allow you to offload parts of your application 10 | * or schedule work for the future to run asynchronously. It's an easy way to add 11 | * scalable background processing to your project. 12 | */ 13 | 14 | // Describe the shape of your message object (optional) 15 | interface Notification { 16 | forUser: string; 17 | body: string; 18 | } 19 | 20 | // Get a reference to a KV instance 21 | const kv = await Deno.openKv(); 22 | 23 | // Create a notification object 24 | const message: Notification = { 25 | forUser: "alovelace", 26 | body: "You've got mail!", 27 | }; 28 | 29 | // Enqueue the message for immediate delivery 30 | await kv.enqueue(message); 31 | 32 | // Enqueue the message for delivery in 3 days 33 | const delay = 1000 * 60 * 60 * 24 * 3; 34 | await kv.enqueue(message, { delay }); 35 | 36 | // Retrieve an unsent message by configuring a key 37 | const backupKey = ["failed_notifications", "alovelace", Date.now()]; 38 | await kv.enqueue(message, { keysIfUndelivered: [backupKey] }); 39 | // ... disaster strikes ... 40 | // Get the unsent message 41 | const r = await kv.get(backupKey); 42 | console.log("Found failed notification for:", r.value?.forUser); 43 | 44 | // Listen for and handle messages. 45 | kv.listenQueue((msg: Notification) => { 46 | console.log(`Dear ${msg.forUser}: ${msg.body}`); 47 | }); 48 | -------------------------------------------------------------------------------- /data/watching-files.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Watching the filesystem 3 | * @difficulty beginner 4 | * @tags cli 5 | * @run --allow-read 6 | * @resource {https://deno.land/api?s=Deno.watchFs} Doc: Deno.watchFs 7 | * @resource {https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of} MDN: for await of 8 | * @resource {$std/async/debounce.ts} Doc: std/debounce 9 | * 10 | * When creating frameworks or CLI tools, it is often necessary to watch the filesystem for changes. 11 | */ 12 | // The easiest way to watch a filesystem is to use the Deno builtin watchFs. 13 | // Deno.watchFs returns an FsWatcher which is an async iterable. 14 | let watcher = Deno.watchFs("./"); 15 | 16 | // The easiest way to interact with async iterables is the javascript for await of syntax. 17 | for await (const event of watcher) { 18 | console.log(">>>> event", event); 19 | 20 | // To stop the watcher we can simply call `watcher.close()` 21 | watcher.close(); 22 | } 23 | 24 | // In real applications, it is quite rare that an application needs to react 25 | // to every change instantly. Events will be duplicated and multiple events will 26 | // be dispatched for the same changes. To get around this, we can "debounce" our 27 | // functions. 28 | import { debounce } from "jsr:@std/async/debounce"; 29 | 30 | // In this specific case, we use the standard library to do the work for us. 31 | // This function will run at most once every two hundred milliseconds 32 | const log = debounce((event: Deno.FsEvent) => { 33 | console.log("[%s] %s", event.kind, event.paths[0]); 34 | }, 200); 35 | 36 | watcher = Deno.watchFs("./"); 37 | 38 | for await (const event of watcher) { 39 | log(event); 40 | } 41 | -------------------------------------------------------------------------------- /data/hex-base64-encoding.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Hex and Base64 Encoding 3 | * @difficulty beginner 4 | * @tags cli 5 | * @run 6 | * 7 | * There are a few cases where it would be practical to encode 8 | * and decode between different string and array buffer formats. 9 | * The Deno standard library makes this easy. 10 | */ 11 | 12 | // The standard library provides hex and base64 encoding and decoding utilities 13 | import * as base64 from "jsr:@std/encoding/base64"; 14 | import * as hex from "jsr:@std/encoding/hex"; 15 | 16 | // We can easily encode a string or an array buffer into base64 using the base64.encode method. 17 | const base64Encoded = base64.encode("somestringtoencode"); 18 | console.log(base64.encode(new Uint8Array([1, 32, 67, 120, 19]))); 19 | 20 | // We can then decode base64 into a byte array using the decode method. 21 | const base64Decoded = base64.decode(base64Encoded); 22 | 23 | // If we want to get the value as a string we can use the built-in TextDecoder. 24 | // We will use these a lot so we can store them in variables to reuse them. 25 | const textEncoder = new TextEncoder(); 26 | const textDecoder = new TextDecoder(); 27 | console.log(textDecoder.decode(base64Decoded)); 28 | 29 | // To encode hex, we always use array buffers. 30 | // To use a string as an input we can encode our text. 31 | const arrayBuffer = textEncoder.encode("somestringtoencode"); 32 | const hexEncoded = hex.encode(arrayBuffer); 33 | console.log(hexEncoded); 34 | 35 | // To read our hex values as a string, we can decode the buffer. 36 | console.log(textDecoder.decode(hexEncoded)); 37 | 38 | // We can convert back to a string by using the decode method. 39 | const hexDecoded = hex.decode(hexEncoded); 40 | console.log(textDecoder.decode(hexDecoded)); 41 | -------------------------------------------------------------------------------- /data/color-logging.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Logging with colors 3 | * @difficulty beginner 4 | * @tags cli, deploy, web 5 | * @run 6 | * @resource {https://developer.mozilla.org/en-US/docs/Web/API/console#styling_console_output} MDN: Styling console output 7 | * 8 | * Most modern terminals can display program logs in colors and with text 9 | * decorations. This example shows how to display colors when using 10 | * `console.log`. 11 | */ 12 | 13 | // Deno has support for CSS using the %c syntax in console.log. Here, the text 14 | // "Hello World" is displayed in red. This also works in the browser. 15 | console.log("%cHello World", "color: red"); 16 | 17 | // Not just foreground, but also background colors can be set. 18 | console.log("%cHello World", "background-color: blue"); 19 | 20 | // Text decorations like underline and strikethrough can be set too. 21 | console.log("%cHello World", "text-decoration: underline"); 22 | console.log("%cHello World", "text-decoration: line-through"); 23 | 24 | // Font weight can also be set (either to normal, or to bold). 25 | console.log("%cHello World", "font-weight: bold"); 26 | 27 | // Multiple CSS rules can also be applied at once. Here the text is red and bold. 28 | console.log("%cHello World", "color: red; font-weight: bold"); 29 | 30 | // A single console.log can also contain multiple %c values. Styling is reset at 31 | // every %c. 32 | console.log("%cHello %cWorld", "color: red", "color: blue"); 33 | 34 | // Instead of predefined colors, hex literals and `rgb()` are also supported. 35 | // Note that some terminals do not support displaying these colors. 36 | console.log("%cHello World", "color: #FFC0CB"); 37 | console.log("%cHello World", "color: rgb(255, 192, 203)"); 38 | 39 | // It is not possible to configure font size, font family, leading, or any other 40 | // CSS attributes. 41 | -------------------------------------------------------------------------------- /data/parsing-serializing-csv.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Parsing and serializing CSV 3 | * @difficulty beginner 4 | * @tags cli, deploy, web 5 | * @run 6 | * @resource {/import-export} Example: Importing & Exporting 7 | * @resource {https://datatracker.ietf.org/doc/html/rfc4180} Spec: CSV 8 | * 9 | * CSV is a data serialization format that is designed to be portable for table-like applications. 10 | */ 11 | import { parse, stringify } from "jsr:@std/csv"; 12 | 13 | // To parse a CSV string, you can use the the standard library's CSV 14 | // parse function. The value is returned as a JavaScript object. 15 | let text = ` 16 | url,views,likes 17 | https://deno.land,10,7 18 | https://deno.land/x,20,15 19 | https://deno.dev,30,23 20 | `; 21 | let data = parse(text, { 22 | skipFirstRow: true, 23 | strip: true, 24 | }); 25 | console.log(data[0].url); // https://deno.land 26 | console.log(data[0].views); // 10 27 | console.log(data[0].likes); // 7 28 | 29 | // In the case where our CSV is formatted differently, we are also able to 30 | // provide the columns through code. 31 | text = ` 32 | https://deno.land,10,7 33 | https://deno.land/x,20,15 34 | https://deno.dev,30,23 35 | `; 36 | data = parse(text, { 37 | columns: ["url", "views", "likes"], 38 | }); 39 | console.log(data[0].url); // https://deno.land 40 | console.log(data[0].views); // 10 41 | console.log(data[0].likes); // 7 42 | 43 | // To turn a list of JavaScript object into a CSV string, you can use the 44 | // standard library's CSV stringify function. 45 | const obj = [ 46 | { mascot: "dino", fans: { old: 100, new: 200 } }, 47 | { mascot: "bread", fans: { old: 5, new: 2 } }, 48 | ]; 49 | const csv = stringify(obj, { 50 | columns: [ 51 | "mascot", 52 | ["fans", "new"], 53 | ], 54 | }); 55 | console.log(csv); 56 | //- mascot,new 57 | //- dino,200 58 | //- bread,2 59 | -------------------------------------------------------------------------------- /data/environment-variables.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Environment Variables 3 | * @difficulty beginner 4 | * @tags cli, deploy 5 | * @run --allow-env 6 | * @resource {https://deno.land/api?s=Deno.env} Doc: Deno.env 7 | * @resource {https://docs.deno.com/deploy/manual/environment-variables} Deploy Docs: Environment Variables 8 | * 9 | * Environment variables can be used to configure the behavior of a program, 10 | * or pass data from one program to another. 11 | */ 12 | 13 | // Here an environment variable with the name "PORT" is read. If this variable 14 | // is set the return value will be a string. If it is unset it will be `undefined`. 15 | const PORT = Deno.env.get("PORT"); 16 | console.log("PORT:", PORT); 17 | 18 | // You can also get an object containing all environment variables. 19 | const env = Deno.env.toObject(); 20 | console.log("env:", env); 21 | 22 | // Environment variables can also be set. The set environment variable only affects 23 | // the current process, and any new processes that are spawned from it. It does 24 | // not affect parent processes or the user shell. 25 | Deno.env.set("MY_PASSWORD", "123456"); 26 | 27 | // You can also unset an environment variable. 28 | Deno.env.delete("MY_PASSWORD"); 29 | 30 | // Note that environment variables are case-sensitive on unix, but not on 31 | // Windows. This means that these two invocations will have different results 32 | // between platforms. 33 | Deno.env.set("MY_PASSWORD", "123"); 34 | Deno.env.set("my_password", "456"); 35 | console.log("UPPERCASE:", Deno.env.get("MY_PASSWORD")); 36 | console.log("lowercase:", Deno.env.get("my_password")); 37 | 38 | // Access to environment variables is only possible if the Deno process is 39 | // running with env var permissions (`--allow-env`). You can limit the permission 40 | // to only a specific number of environment variables (`--allow-env=PORT,MY_PASSWORD`). 41 | -------------------------------------------------------------------------------- /data/writing-files.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Writing Files 3 | * @difficulty beginner 4 | * @tags cli 5 | * @run --allow-read --allow-write 6 | * @resource {https://deno.land/api?s=Deno.writeFile} Doc: Deno.writeFile 7 | * @resource {https://deno.land/api?s=Deno.create} Doc: Deno.create 8 | * @resource {https://deno.land/api?s=Deno.FsFile} Doc: Deno.FsFile 9 | * @resource {https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder} MDN: TextEncoder 10 | * 11 | * Many applications need to write files to disk. Deno provides a simple 12 | * interface for writing files. 13 | */ 14 | 15 | // The easiest way to write a file, is to dump an entire buffer into the file at 16 | // once. 17 | const bytes = new Uint8Array([72, 101, 108, 108, 111]); 18 | await Deno.writeFile("hello.txt", bytes, { mode: 0o644 }); 19 | 20 | // You can also write a string instead of a byte array. 21 | await Deno.writeTextFile("hello.txt", "Hello World"); 22 | 23 | // Synchronous writing is also supported. 24 | Deno.writeFileSync("hello.txt", bytes); 25 | Deno.writeTextFileSync("hello.txt", "Hello World"); 26 | 27 | // For more granular writes, open a new file for writing. 28 | const file = await Deno.create("hello.txt"); 29 | 30 | // You can write chunks of data to the file. 31 | const written = await file.write(bytes); 32 | console.log(`${written} bytes written.`); 33 | 34 | // A `file.write` returns the number of bytes written, as it might not write all 35 | // bytes passed. We can get a Writer instead to make sure the entire buffer is written. 36 | const writer = file.writable.getWriter(); 37 | await writer.write(new TextEncoder().encode("World!")); 38 | 39 | // Closing the writer automatically closes the file. 40 | // If you don't use a writer, make sure to close the file after you are done with it. 41 | await writer.close(); 42 | 43 | // The `--allow-write` permission is required to write files. 44 | -------------------------------------------------------------------------------- /data/streaming-files.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Streaming File Operations 3 | * @difficulty intermediate 4 | * @tags cli 5 | * @run --allow-read --allow-write 6 | * @resource {https://deno.land/api?s=Deno.open} Doc: Deno.open 7 | * @resource {https://deno.land/api?s=Deno.FsFile} Doc: Deno.FsFile 8 | * 9 | * Sometimes we need more granular control over file operations. 10 | * Deno provides a low level interface to file operations that 11 | * may be used for this purpose. 12 | */ 13 | 14 | // For our first example we will demonstrate using a writeable stream. 15 | // To handle any low level file operations we must first open the file. 16 | const output = await Deno.open("example.txt", { 17 | create: true, 18 | append: true, 19 | }); 20 | 21 | // We are now able to obtain a writer from the output. 22 | // This automatically handles closing the file when done. 23 | const outputWriter = output.writable.getWriter(); 24 | 25 | // Before we do anything with the writer, we should make sure it's ready. 26 | await outputWriter.ready; 27 | 28 | // Let's write some text to the file 29 | const outputText = "I love Deno!"; 30 | const encoded = new TextEncoder().encode(outputText); 31 | await outputWriter.write(encoded); 32 | 33 | // Now we close the write stream (and the file) 34 | await outputWriter.close(); 35 | 36 | // For our next example, let's read the text from the file! 37 | const input = await Deno.open("example.txt"); 38 | 39 | // Let's get a reader from the input 40 | const inputReader = input.readable.getReader(); 41 | const decoder = new TextDecoder(); 42 | 43 | // Let's read each chunk from the file 44 | // and print it to the console. 45 | let done = false; 46 | do { 47 | const result = await inputReader.read(); 48 | done = result.done; 49 | 50 | if (result.value) { 51 | console.log(`Read chunk: ${decoder.decode(result.value)}`); 52 | } 53 | } while (!done); 54 | -------------------------------------------------------------------------------- /data/permissions.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Permission Management 3 | * @difficulty beginner 4 | * @tags cli 5 | * @run 6 | * @resource {https://deno.land/api?s=Deno.Permissions} Doc: Deno.Permissions 7 | * 8 | * There are times where depending on the state of permissions 9 | * granted to a process, we want to do different things. This is 10 | * made very easy to do with the Deno permissions API. 11 | */ 12 | 13 | // In the most simple case, we can just request a permission by it's name. 14 | // In this case, we ask for --allow-env and prompt the user. The user will 15 | // not be prompted if it was already allowed in the past and not revoked. 16 | let status = await Deno.permissions.request({ name: "env" }); 17 | if (status.state === "granted") { 18 | console.log("'env' permission is granted."); 19 | } else { 20 | console.log("'env' permission is denied."); 21 | } 22 | 23 | // There are also synchronous versions of all the permission APIs 24 | status = Deno.permissions.requestSync({ name: "env" }); 25 | if (status.state === "granted") { 26 | console.log("'env' permission is granted."); 27 | } else { 28 | console.log("'env' permission is denied."); 29 | } 30 | 31 | // We can also query permissions without asking for them. In this case, 32 | // we are querying whether or not we have the read permission. Not only 33 | // can we query whether we have a permission or not, we can even specify 34 | // what directories we have permissions in using the path option. 35 | const readStatus = await Deno.permissions.query({ 36 | name: "read", 37 | path: "/etc", 38 | }); 39 | console.log(readStatus.state); 40 | 41 | // In the case that we no longer need a permission, it is also possible 42 | // to revoke a process's access to that permission. This is useful when 43 | // a process starts running untrusted code. 44 | import { assert } from "$std/assert/assert.ts"; 45 | 46 | const runStatus = await Deno.permissions.revoke({ name: "run" }); 47 | assert(runStatus.state !== "granted"); 48 | -------------------------------------------------------------------------------- /routes/[id]/[file].ts: -------------------------------------------------------------------------------- 1 | import { Handlers } from "$fresh/server.ts"; 2 | import { typeByExtension } from "$std/media_types/type_by_extension.ts"; 3 | import { parseExample } from "../../utils/example.ts"; 4 | 5 | export const handler: Handlers = { 6 | async GET(req, ctx) { 7 | const id = ctx.params.id; 8 | const filename = ctx.params.file; 9 | const extension = filename.split(".").pop(); 10 | 11 | if (extension && ["ts", "json"].includes(extension)) { 12 | const accept = req.headers.get("accept") || ""; 13 | const acceptsHTML = accept.includes("text/html"); 14 | try { 15 | const data = await Deno.readTextFile(`./data/${id}.ts`); 16 | const example = parseExample(id, data); 17 | if (example.files.length === 1) { 18 | return new Response( 19 | "Source for single file examples can not be split by files", 20 | { 21 | status: 400, 22 | }, 23 | ); 24 | } 25 | const file = example.files.find((file) => 26 | file.name === "./" + filename 27 | ); 28 | 29 | if (file) { 30 | let code = ""; 31 | for (const snippet of file.snippets) { 32 | code += snippet.code + "\n"; 33 | } 34 | return new Response(code, { 35 | headers: { 36 | "content-type": acceptsHTML 37 | ? "text/plain; charset=utf-8" 38 | : `${typeByExtension(extension)}; charset=utf-8`, 39 | }, 40 | }); 41 | } else { 42 | return new Response("404 File Not Found", { status: 404 }); 43 | } 44 | } catch (err) { 45 | if (err instanceof Deno.errors.NotFound) { 46 | return new Response("404 Example Not Found", { status: 404 }); 47 | } 48 | console.error(err); 49 | return new Response("500 Internal Server Error", { status: 500 }); 50 | } 51 | } 52 | return new Response("400 Must view source", { status: 400 }); 53 | }, 54 | }; 55 | -------------------------------------------------------------------------------- /data/kv-watch.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Deno KV Watch 3 | * @difficulty intermediate 4 | * @tags cli, deploy 5 | * @run --unstable 6 | * @resource {https://docs.deno.com/deploy/kv/manual} Deno KV user guide 7 | * @resource {https://deno.land/api?unstable=&s=Deno.Kv} Deno KV Runtime API docs 8 | * 9 | * Deno KV watch allows you to detect changes to your KV database, making 10 | * it easier to build real-time applications, newsfeeds, chat, and more. 11 | */ 12 | 13 | // Open the default database 14 | const kv = await Deno.openKv(); 15 | 16 | // Set "counter" value to 0, then increment every second. 17 | await kv.set(["counter"], new Deno.KvU64(0n)); 18 | setInterval(() => { 19 | kv.atomic().sum(["counter"], 1n).commit(); 20 | }, 1000); 21 | 22 | // Listen for changes to "counter" and log value. 23 | for await (const [entry] of kv.watch([["counter"]])) { 24 | console.log(`Counter: ${entry.value}`); 25 | } 26 | 27 | // You can also create a stream reader from kv.watch, which returns 28 | // a ReadableStream. 29 | const stream = kv.watch([["counter"]]).getReader(); 30 | while (true) { 31 | const counter = await stream.read(); 32 | if (counter.done) { 33 | break; 34 | } 35 | console.log(`Counter: ${counter.value[0].value}`); 36 | } 37 | 38 | // To use server-sent events, let's create a server that 39 | // responds with a stream. Each time a change to "counter" 40 | // is detected, send the updated value to the client. 41 | Deno.serve((_req) => { 42 | const stream = kv.watch([["counter"]]).getReader(); 43 | const body = new ReadableStream({ 44 | async start(controller) { 45 | while (true) { 46 | if ((await stream.read()).done) { 47 | return; 48 | } 49 | const data = await kv.get(["counter"]); 50 | controller.enqueue( 51 | new TextEncoder().encode(`Counter: ${data.value}\n`), 52 | ); 53 | } 54 | }, 55 | cancel() { 56 | stream.cancel(); 57 | }, 58 | }); 59 | return new Response(body, { 60 | headers: { 61 | "Content-Type": "text/event-stream", 62 | }, 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /data/temporary-files.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Temporary Files & Directories 3 | * @difficulty beginner 4 | * @tags cli 5 | * @run --allow-read --allow-write 6 | * @resource {https://deno.land/api?s=Deno.makeTempFile} Doc: Deno.makeTempFile 7 | * @resource {https://deno.land/api?s=Deno.makeTempDir} Doc: Deno.makeTempDir 8 | * 9 | * Temporary files and directories are used to store data that is not intended 10 | * to be permanent. For example, as a local cache of downloaded data. 11 | */ 12 | 13 | // The `Deno.makeTempFile()` function creates a temporary file in the default 14 | // temporary directory and returns the path to the file. 15 | const tempFilePath = await Deno.makeTempFile(); 16 | console.log("Temp file path:", tempFilePath); 17 | await Deno.writeTextFile(tempFilePath, "Hello world!"); 18 | const data = await Deno.readTextFile(tempFilePath); 19 | console.log("Temp file data:", data); 20 | 21 | // A custom prefix and suffix for the temporary file can be specified. 22 | const tempFilePath2 = await Deno.makeTempFile({ 23 | prefix: "logs_", 24 | suffix: ".txt", 25 | }); 26 | console.log("Temp file path 2:", tempFilePath2); 27 | 28 | // The directory that temporary files are created in can be customized too. 29 | // Here we use a relative ./tmp directory. 30 | await Deno.mkdir("./tmp", { recursive: true }); 31 | const tempFilePath3 = await Deno.makeTempFile({ 32 | dir: "./tmp", 33 | }); 34 | console.log("Temp file path 3:", tempFilePath3); 35 | 36 | // A temporary directory can also be created. 37 | const tempDirPath = await Deno.makeTempDir(); 38 | console.log("Temp dir path:", tempDirPath); 39 | 40 | // It has the same prefix, suffix, and directory options as `makeTempFile()`. 41 | const tempDirPath2 = await Deno.makeTempDir({ 42 | prefix: "logs_", 43 | suffix: "_folder", 44 | dir: "./tmp", 45 | }); 46 | console.log("Temp dir path 2:", tempDirPath2); 47 | 48 | // Synchronous versions of the above functions are also available. 49 | const tempFilePath4 = Deno.makeTempFileSync(); 50 | const tempDirPath3 = Deno.makeTempDirSync(); 51 | console.log("Temp file path 4:", tempFilePath4); 52 | console.log("Temp dir path 3:", tempDirPath3); 53 | -------------------------------------------------------------------------------- /data/web-workers.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Web Workers 3 | * @difficulty intermediate 4 | * @tags cli, web 5 | * @resource {https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers} MDN: Web Workers 6 | * @resource {https://deno.land/manual@v1.30.0/runtime/workers} Manual: Workers 7 | * 8 | * Workers are the only way of running javascript off of the main thread. 9 | * This can be useful for a wide variety of programs, especially those where 10 | * there is a lot of computation that needs to be done without blocking a 11 | * thread. 12 | */ 13 | 14 | // File: ./worker.ts 15 | 16 | // First we will define a web worker, we can receive messages from the main 17 | // thread and do some processing as a result of them. 18 | self.onmessage = async (e) => { 19 | const { filename } = e.data; 20 | const text = await Deno.readTextFile(filename); 21 | console.log(text); 22 | self.close(); 23 | }; 24 | 25 | // File: ./main.ts 26 | 27 | // Currently, Deno only supports module-type workers. To instantiate one 28 | // we can use similar syntax to what is found on the web. 29 | const worker = new Worker( 30 | new URL("./worker.ts", import.meta.url).href, 31 | { 32 | type: "module", 33 | }, 34 | ); 35 | 36 | // We can send a message to the worker by using the `postMessage` method 37 | worker.postMessage({ filename: "./log.txt" }); 38 | 39 | // By default the worker inherits the permissions of the thread it was 40 | // instantiated from. We can give workers certain permissions by using 41 | // the deno.permissions option. 42 | const worker2 = new Worker( 43 | new URL("./worker.ts", import.meta.url).href, 44 | { 45 | type: "module", 46 | deno: { 47 | permissions: { 48 | read: [ 49 | new URL("./file_1.txt", import.meta.url), 50 | new URL("./file_2.txt", import.meta.url), 51 | ], 52 | }, 53 | }, 54 | }, 55 | ); 56 | 57 | // Because we instantiated this worker with specific permissions, this will 58 | // cause the worker to try to read a file it doesn't have access to and thus 59 | // will throw a permission error. 60 | worker2.postMessage({ filename: "./log.txt" }); 61 | -------------------------------------------------------------------------------- /data/reading-files.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Reading Files 3 | * @difficulty beginner 4 | * @tags cli, deploy 5 | * @run --allow-read 6 | * @resource {https://deno.land/api?s=Deno.readFile} Doc: Deno.readFile 7 | * @resource {https://deno.land/api?s=Deno.open} Doc: Deno.open 8 | * @resource {https://deno.land/api?s=Deno.FsFile} Doc: Deno.FsFile 9 | * 10 | * Many applications need to read files from disk. Deno provides a simple 11 | * interface for reading files. 12 | */ 13 | 14 | // The easiest way to read a file is to just read the entire contents into 15 | // memory as bytes. 16 | // deno-lint-ignore no-unused-vars 17 | const bytes = await Deno.readFile("hello.txt"); 18 | 19 | // Instead of reading the file as bytes, there is a convenience function to 20 | // read the file as a string. 21 | // deno-lint-ignore no-unused-vars 22 | const text = await Deno.readTextFile("hello.txt"); 23 | 24 | // Often you need more control over when what parts of the file are read. 25 | // For this you start by opening a file to get a `Deno.FsFile` object. 26 | const file = await Deno.open("hello.txt"); 27 | 28 | // Read some bytes from the beginning of the file. Allow up to 5 to be read but 29 | // also note how many actually were read. 30 | const buffer = new Uint8Array(5); 31 | const bytesRead = await file.read(buffer); 32 | console.log(`Read ${bytesRead} bytes`); 33 | 34 | // You can also seek to a known location in the file and read from there. 35 | const pos = await file.seek(6, Deno.SeekMode.Start); 36 | console.log(`Seeked to position ${pos}`); 37 | const buffer2 = new Uint8Array(2); 38 | const bytesRead2 = await file.read(buffer2); 39 | console.log(`Read ${bytesRead2} bytes`); 40 | 41 | // You can use rewind back to the start using seek as well. 42 | await file.seek(0, Deno.SeekMode.Start); 43 | 44 | // Make sure to close the file when you are done. 45 | file.close(); 46 | 47 | // Synchronous reading is also supported. 48 | Deno.readFileSync("hello.txt"); 49 | Deno.readTextFileSync("hello.txt"); 50 | const f = Deno.openSync("hello.txt"); 51 | f.seekSync(6, Deno.SeekMode.Start); 52 | const buf = new Uint8Array(5); 53 | f.readSync(buf); 54 | f.close(); 55 | 56 | // The `--allow-read` permission is required to read files. 57 | -------------------------------------------------------------------------------- /data/path-operations.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Path operations 3 | * @difficulty beginner 4 | * @tags cli 5 | * @run --allow-read 6 | * @resource {$std/path} Deno: std/path 7 | * @resource {https://deno.land/api?s=Deno.cwd} Deno: Deno.cwd 8 | * 9 | * Many applications need to manipulate file paths in one way or another. 10 | * The Deno standard library provides simple utilities for this. 11 | */ 12 | 13 | // First we will import the module from the Deno standard library 14 | import * as path from "jsr:@std/path"; 15 | 16 | // Converting from a file url to a directory can be done simply by the `fromFileUrl` 17 | // method from the appropriate implementation. 18 | const p1 = path.posix.fromFileUrl("file:///home/foo"); 19 | const p2 = path.win32.fromFileUrl("file:///home/foo"); 20 | console.log(`Path 1: ${p1} Path 2: ${p2}`); 21 | 22 | // We can also choose to not specify and automatically use whatever Deno is running on 23 | const p3 = path.fromFileUrl("file:///home/foo"); 24 | console.log(`Path on current OS: ${p3}`); 25 | 26 | // We can get the last part of a file path using the basename method 27 | const p = path.basename("./deno/is/awesome/mod.ts"); 28 | console.log(p); // mod.ts 29 | 30 | // We can get the directory of a file path using the dirname method 31 | const base = path.dirname("./deno/is/awesome/mod.ts"); 32 | console.log(base); // ./deno/is/awesome 33 | 34 | // We can get the extension of a file path using the extname method 35 | const ext = path.extname("./deno/is/awesome/mod.ts"); 36 | console.log(ext); // .ts 37 | 38 | // We can format a path using a FormatInputPathObject 39 | const formatPath = path.format({ 40 | root: "/", 41 | dir: "/home/user/dir", 42 | ext: ".html", 43 | name: "index", 44 | }); 45 | console.log(formatPath); // "/home/user/dir/index.html" 46 | 47 | // When we want to make our code cross-platform, we can use the join method. 48 | // This joins any number of string by the OS-specific file separator. On 49 | // Mac OS this would be foo/bar. On windows, this would be foo\bar. 50 | const joinPath = path.join("foo", "bar"); 51 | console.log(joinPath); 52 | 53 | // We can get the current working directory using the built-in cwd method 54 | const current = Deno.cwd(); 55 | console.log(current); 56 | -------------------------------------------------------------------------------- /data/writing-tests.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Writing Tests 3 | * @difficulty beginner 4 | * @tags cli 5 | * @run deno test --allow-read --allow-write 6 | * @resource {https://deno.land/api?s=Deno.test} Doc: Deno.test 7 | * @resource {$std/testing/asserts.ts} Doc: std/testing/asserts 8 | * 9 | * One of the most common tasks in developing software is writing tests for 10 | * existing code. Deno has a built-in test runner which makes this very easy. 11 | */ 12 | 13 | // First, we import assert statements from the standard library. There are 14 | // quite a few options but we will just import the most common ones here. 15 | import { assert, assertEquals } from "jsr:@std/assert"; 16 | 17 | // The most simple way to use the test runner is to just pass it a description 18 | // and a callback function 19 | Deno.test("assert works correctly", () => { 20 | assert(true); 21 | assertEquals(1, 1); 22 | }); 23 | 24 | // In more complex scenarios, we often need to have some setup and teardown code 25 | // with some steps in between. This is also made simple with the built-in test runner. 26 | Deno.test("testing steps", async (t) => { 27 | const file = await Deno.open("example.txt", { 28 | read: true, 29 | write: true, 30 | create: true, 31 | }); 32 | const encoder = new TextEncoder(); 33 | const data = encoder.encode("Hello world!"); 34 | 35 | await t.step("write some bytes", async () => { 36 | const bytesWritten = await file.write(data); 37 | assertEquals(bytesWritten, data.length); 38 | await file.seek(0, Deno.SeekMode.Start); 39 | }); 40 | 41 | await t.step("read some bytes", async () => { 42 | const buffer = new Uint8Array(data.length); 43 | await file.read(buffer); 44 | assertEquals(buffer, data); 45 | }); 46 | 47 | file.close(); 48 | }); 49 | 50 | // The test runner by default makes it very hard to shoot yourself in the foot. For 51 | // each test, the test runner checks to make sure all resources created during the 52 | // test are freed. There are situations where this is not useful behavior. We can use 53 | // the more complex test definition to disable this behavior 54 | Deno.test({ 55 | name: "leaky test", 56 | async fn() { 57 | await Deno.open("example.txt"); 58 | }, 59 | sanitizeResources: false, 60 | }); 61 | -------------------------------------------------------------------------------- /data/piping-streams.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Piping Streams 3 | * @difficulty intermediate 4 | * @tags cli 5 | * @run --allow-net --allow-read --allow-write 6 | * @resource {https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream} MDN: ReadableStream 7 | * @resource {/tcp-listener.ts} Example: TCP Listener 8 | * 9 | * Deno implements web-standard streams which comes with many advantages. One of the most useful 10 | * features of streams is how they can be piped to and from different sources. 11 | */ 12 | 13 | // A common usecase for streams is downloading files without buffering the whole file in memory. 14 | // First, we open a file we want to write to 15 | const download = await Deno.open("example.html", { create: true, write: true }); 16 | 17 | // Now let's make a request to a web page 18 | const req = await fetch("https://examples.deno.land"); 19 | 20 | // We can pipe this response straight into the file. In a more realistic example, we would 21 | // handle a bad request, but here we'll just use the nullish coalescing operator. Instead of 22 | // opening the file and manually piping the fetch body to it, we could also just use `Deno.writeFile`. 23 | req.body?.pipeTo(download.writable); 24 | 25 | // Pipes can connect a lot of things. For example, we could pipe the file we just downloaded into 26 | // stdout. We could also pipe our streams through transformers to get a more interesting result. In this 27 | // case, we will pipe our file through a stream which will highlight all "<" characters in the terminal. 28 | 29 | // First we will import a utility from the standard library to help us with this. 30 | import { bgBrightYellow } from "jsr:@std/fmt/colors"; 31 | 32 | // Then we will create a transform stream utility class 33 | class HighlightTransformStream extends TransformStream { 34 | constructor() { 35 | super({ 36 | transform: (chunk, controller) => { 37 | controller.enqueue(chunk.replaceAll("<", bgBrightYellow("<"))); 38 | }, 39 | }); 40 | } 41 | } 42 | 43 | // Let's open our file for reading 44 | const example = await Deno.open("example.html", { read: true }); 45 | 46 | // Now we can pipe the result from the file, into a TextDecoderStream, into our custom transform class, 47 | // back through a TextEncoderStream, and finally into stdout 48 | await example.readable 49 | .pipeThrough(new TextDecoderStream()) 50 | .pipeThrough(new HighlightTransformStream()) 51 | .pipeThrough(new TextEncoderStream()) 52 | .pipeTo(Deno.stdout.writable); 53 | -------------------------------------------------------------------------------- /data/hashing.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Hashing 3 | * @difficulty intermediate 4 | * @tags cli, deploy 5 | * @run --allow-read 6 | * @resource {https://deno.land/api?s=SubtleCrypto} Doc: crypto.subtle 7 | * @resource {https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest} MDN: Cryptographic Digests 8 | * 9 | * Hashing data is a common operation that is facilitated 10 | * through Deno's support for the Web Crypto API. In addition, 11 | * the Deno standard library's implementation extends the standard API, allowing for 12 | * more advanced uses. 13 | */ 14 | 15 | // In our first example, we'll hash the contents of a string variable. 16 | const message = "The easiest, most secure JavaScript runtime."; 17 | 18 | // Before we can pass our message to the hashing function, 19 | // we first need to encode it into a uint8 array. 20 | const messageBuffer = new TextEncoder().encode(message); 21 | 22 | // Here, we use the built-in crypto.subtle.digest method to hash our original message. 23 | // The hash is returned as an ArrayBuffer. To obtain a string 24 | // we'll need to do a little more work. 25 | const hashBuffer = await crypto.subtle.digest("SHA-256", messageBuffer); 26 | 27 | // We can decode this into a string using the standard 28 | // library's encodeHex method. 29 | import { encodeHex } from "jsr:@std/encoding/hex"; 30 | const hash = encodeHex(hashBuffer); 31 | console.log(hash); 32 | 33 | // For our second example, we'll hash the contents of a file. 34 | // Hashing a file is a common operation and doing this 35 | // without loading the whole file into memory is a typical 36 | // requirement. 37 | 38 | // The standard library has extensions to the Web 39 | // Crypto API that are useful when doing things 40 | // like hashing a file. These can be accessed through the 41 | // "crypto" module, a drop-in replacement for the Web Crypto 42 | // API that delegates to the native implementation when 43 | // possible. 44 | import { crypto } from "jsr:@std/crypto"; 45 | const file = await Deno.open("example.txt", { read: true }); 46 | 47 | // We obtain an async iterable using the readable property. 48 | const readableStream = file.readable; 49 | 50 | // This time, when we call crypto.subtle.digest, we're using the 51 | // imported version that allows us to operate on the 52 | // async iterable. 53 | const fileHashBuffer = await crypto.subtle.digest("SHA-256", readableStream); 54 | 55 | // Finally, we obtain the hex result using encodeHex like earlier. 56 | const fileHash = encodeHex(fileHashBuffer); 57 | console.log(fileHash); 58 | -------------------------------------------------------------------------------- /data/http-requests.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title HTTP Requests 3 | * @difficulty beginner 4 | * @tags cli, deploy, web 5 | * @run --allow-net 6 | * @resource {https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API} MDN: Fetch API 7 | * @resource {https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream} MDN: ReadableStream 8 | * 9 | * This example demonstrates how to make a HTTP request to a server. 10 | */ 11 | 12 | // To make a request to a server, you use the `fetch` API. 13 | let resp = await fetch("https://example.com"); 14 | 15 | // The response is a `Response` object. This contains the status code, headers, 16 | // and the body. 17 | console.log(resp.status); // 200 18 | console.log(resp.headers.get("Content-Type")); // "text/html" 19 | console.log(await resp.text()); // "Hello, World!" 20 | 21 | // The response body can also be read as JSON, an ArrayBuffer, or a Blob. A body 22 | // can be read only once. 23 | resp = await fetch("https://example.com"); 24 | await resp.arrayBuffer(); 25 | /** or await resp.json(); */ 26 | /** or await resp.blob(); */ 27 | 28 | // The response body can also be streamed in chunks. 29 | resp = await fetch("https://example.com"); 30 | for await (const chunk of resp.body!) { 31 | console.log("chunk", chunk); 32 | } 33 | 34 | // When making a request, you can also specify the method, headers, and a body. 35 | const body = `{"name": "Deno"}`; 36 | resp = await fetch("https://example.com", { 37 | method: "POST", 38 | headers: { 39 | "Content-Type": "application/json", 40 | "X-API-Key": "foobar", 41 | }, 42 | body, 43 | }); 44 | 45 | // `fetch` also accepts a `Request` object instead of URL + options. 46 | const req = new Request("https://example.com", { 47 | method: "DELETE", 48 | }); 49 | resp = await fetch(req); 50 | 51 | // Instead of a string, the body can also be any typed array, blob, or 52 | // a URLSearchParams object. 53 | const url = "https://example.com"; 54 | new Request(url, { 55 | method: "POST", 56 | body: new Uint8Array([1, 2, 3]), 57 | }); 58 | new Request(url, { 59 | method: "POST", 60 | body: new Blob(["Hello, World!"]), 61 | }); 62 | new Request(url, { 63 | method: "POST", 64 | body: new URLSearchParams({ "foo": "bar" }), 65 | }); 66 | 67 | // Forms can also be sent with `fetch` by using a `FormData` object as the body. 68 | const formData = new FormData(); 69 | formData.append("name", "Deno"); 70 | formData.append("file", new Blob(["Hello, World!"]), "hello.txt"); 71 | resp = await fetch("https://example.com", { 72 | method: "POST", 73 | body: formData, 74 | }); 75 | 76 | // Fetch also supports streaming the request body. 77 | const bodyStream = new ReadableStream({ 78 | start(controller) { 79 | controller.enqueue(new TextEncoder().encode("Hello, World!")); 80 | controller.close(); 81 | }, 82 | }); 83 | resp = await fetch("https://example.com", { 84 | method: "POST", 85 | body: bodyStream, 86 | }); 87 | -------------------------------------------------------------------------------- /routes/index.tsx: -------------------------------------------------------------------------------- 1 | import { TOC } from "../toc.ts"; 2 | import { Head } from "$fresh/runtime.ts"; 3 | import { Handlers, PageProps } from "$fresh/server.ts"; 4 | import { Page } from "../components/Page.tsx"; 5 | import { ExampleGroup, parseExample } from "../utils/example.ts"; 6 | import { IndexGroup } from "../components/List.tsx"; 7 | 8 | async function loadExample(id: string) { 9 | const data = await Deno.readTextFile(`./data/${id}.ts`); 10 | return parseExample(id, data); 11 | } 12 | 13 | export const handler: Handlers = { 14 | GET(_req) { 15 | return Response.redirect("https://docs.deno.com/examples"); 16 | }, 17 | }; 18 | 19 | export default function Home(props: PageProps) { 20 | return ( 21 | 22 | 23 | 27 | 28 |
    29 |

    30 | logo 31 |
    32 | 33 | Deno 34 | 35 | 36 | by example 37 | 38 |
    39 |

    40 |

    41 | Deno is a simple, modern and secure runtime for JavaScript and 42 | TypeScript that uses V8 and is built in Rust. 43 |

    44 |

    45 | Deno by example{" "} 46 | is a collection of annotated examples for how to use Deno, and the 47 | various features it provides. It acts as a reference for how to do 48 | various things in Deno, but can also be used as a guide to learn about 49 | many of the features Deno provides. 50 |

    51 |
      52 | {props.data.map( 53 | (group) => , 54 | )} 55 |
    56 |

    57 | 61 | Source 62 | {" "} 63 | |{" "} 64 | 68 | License 69 | {" "} 70 | | Inspired by{" "} 71 | 72 | Go by Example 73 | 74 |

    75 |
    76 |
    77 | ); 78 | } 79 | -------------------------------------------------------------------------------- /data/kv.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Deno KV: Key/Value Database 3 | * @difficulty intermediate 4 | * @tags cli, deploy 5 | * @run --unstable 6 | * @resource {https://docs.deno.com/deploy/kv/manual} Deno KV user guide 7 | * @resource {https://deno.land/api?unstable=&s=Deno.Kv} Deno KV Runtime API docs 8 | * 9 | * Deno KV is a key/value database built in to the Deno runtime, and works with 10 | * zero configuration on Deno Deploy. It's great for use cases that require fast 11 | * reads and don't require the query flexibility of a SQL database. 12 | */ 13 | 14 | // Open the default database 15 | const kv = await Deno.openKv(); 16 | 17 | // Define an interface in TypeScript for our data 18 | enum Rank { 19 | Bronze, 20 | Silver, 21 | Gold, 22 | } 23 | 24 | interface Player { 25 | username: string; 26 | rank: Rank; 27 | } 28 | 29 | // Create a few instances for testing 30 | const player1: Player = { username: "carlos", rank: Rank.Bronze }; 31 | const player2: Player = { username: "briana", rank: Rank.Silver }; 32 | const player3: Player = { username: "alice", rank: Rank.Bronze }; 33 | 34 | // Store object data in Deno KV using the "set" operation. Keys can be arranged 35 | // hierarchically, not unlike resources in a REST API. 36 | await kv.set(["players", player1.username], player1); 37 | await kv.set(["players", player2.username], player2); 38 | await kv.set(["players", player3.username], player3); 39 | 40 | // The "set" operation is used to both create and update data for a given key 41 | player3.rank = Rank.Gold; 42 | await kv.set(["players", player3.username], player3); 43 | 44 | // Fetch a single object by key with the "get" operation 45 | const record = await kv.get(["players", "alice"]); 46 | const alice: Player = record.value as Player; 47 | console.log(record.key, record.versionstamp, alice); 48 | 49 | // Fetch several objects by key with "getMany" 50 | const [record1, record2] = await kv.getMany([ 51 | ["players", "carlos"], 52 | ["players", "briana"], 53 | ]); 54 | console.log(record1, record2); 55 | 56 | // List several records by key prefix - note that results are ordered 57 | // lexicographically, so our players will be fetched in the order 58 | // "alice", "briana", "carlos" 59 | const records = kv.list({ prefix: ["players"] }); 60 | const players = []; 61 | for await (const res of records) { 62 | players.push(res.value as Player); 63 | } 64 | console.log(players); 65 | 66 | // Delete a value for a given key 67 | await kv.delete(["players", "carlos"]); 68 | 69 | // The Deno.KvU64 object is a wrapper for 64 bit integers (BigInt), so you can 70 | // quickly update very large numbers. Let's add a "score" for alice. 71 | const aliceScoreKey = ["scores", "alice"]; 72 | await kv.set(aliceScoreKey, new Deno.KvU64(0n)); 73 | 74 | // Add 10 to the player's score in an atomic transaction 75 | await kv.atomic() 76 | .mutate({ 77 | type: "sum", 78 | key: aliceScoreKey, 79 | value: new Deno.KvU64(10n), 80 | }) 81 | .commit(); 82 | const newScore = (await kv.get(aliceScoreKey)).value; 83 | console.log("Alice's new score is: ", newScore); 84 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Archived 2 | 3 | The contents of this repository have been moved to 4 | https://docs.deno.com/examples (website) / https://github.com/denoland/deno-docs 5 | (source). 6 | 7 | # Deno by Example 8 | 9 | This repository contains the source code for https://examples.deno.land/. 10 | 11 | Deno by Example is a collection of small snippets showcasing various functions 12 | of the APIs implemented in Deno. 13 | 14 | - Examples are written in TypeScript 15 | - Each example should be a single file, no more than 50 lines 16 | - Each example should be a self-contained unit, and should depend on no 17 | dependencies other than Deno builtins and the standard library, unless a 18 | third-party library is strictly required. 19 | - Each example should be runnable without additional dependencies on all systems 20 | (exceptions can be made for platform specific functionality) 21 | - Examples should be introduce at most one (or in exceptional cases two or 22 | three) concepts in Deno / Web APIs. Existing concepts should be linked to. 23 | - Code should be kept _really simple_, and should be easy to read and understand 24 | by anyone. Do not use complicated code constructs, or hard to follow builtins 25 | like `Array.reduce` 26 | - Concepts introduced in an example should be explained 27 | 28 | ## Contributing 29 | 30 | ### Adding an example 31 | 32 | To add an example, create a file in the `data` directory. The file name should 33 | be the id of the example, and the contents should be the code for the example. 34 | The file should be in the `.ts` format. The file should start with a JSDoc style 35 | multi line comment that describes the example: 36 | 37 | ```ts 38 | /** 39 | * @title HTTP server: Hello World 40 | * @difficulty intermediate 41 | * @tags cli, deploy 42 | * @run --allow-net 43 | * 44 | * An example of a HTTP server that serves a "Hello World" message. 45 | */ 46 | ``` 47 | 48 | You should add a title, a difficulty level (`beginner` or `intermediate`), and a 49 | list of tags (`cli`, `deploy`, `web` depending on where an example is runnable). 50 | The `@run` tag should be included if the example can be run locally by just 51 | doing `deno run `. If running requires permissions, add these: 52 | 53 | ```ts 54 | /** 55 | * ... 56 | * @run --allow-net --allow-read 57 | */ 58 | ``` 59 | 60 | After the pragmas, you can add a description of the example. This is optional, 61 | but recommended for most examples. It should not be longer than one or two 62 | lines. The description shows up at the top of the example in the example page, 63 | and in search results. 64 | 65 | After the JS Doc comment, you can write the code. Code can be prefixed with a 66 | comment that describes the code. The comment will be rendered next to the code 67 | in the example page. 68 | 69 | Now add your example to the `toc.js` file. This will cause it to show up on the 70 | index page. 71 | 72 | After you have added the example, run `deno task fmt` and `deno task lint` to 73 | format and lint the example. 74 | 75 | ### Running the webserver locally 76 | 77 | To run the webserver locally, open a terminal and run: 78 | 79 | ```sh 80 | deno task start 81 | ``` 82 | 83 | You can then view the page at http://localhost:8000/ 84 | 85 | Before opening a PR with a change, make sure `deno task fmt` and 86 | `deno task lint` pass. 87 | -------------------------------------------------------------------------------- /toc.ts: -------------------------------------------------------------------------------- 1 | import IconDatabase from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/database.tsx"; 2 | import IconFlag3 from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/flag-3.tsx"; 3 | import IconTransform from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/transform.tsx"; 4 | import IconFileShredder from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/file-shredder.tsx"; 5 | import IconTerminal2 from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/terminal-2.tsx"; 6 | import IconDeviceDesktop from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/device-desktop.tsx"; 7 | import IconFiles from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/files.tsx"; 8 | import IconNetwork from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/network.tsx"; 9 | import IconStars from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/stars.tsx"; 10 | import IconClock from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/clock.tsx"; 11 | import { FunctionComponent } from "preact"; 12 | 13 | interface TocGroup { 14 | title: string; 15 | icon?: FunctionComponent; 16 | items: string[]; 17 | } 18 | 19 | export const TOC: TocGroup[] = [ 20 | { 21 | title: "Basics", 22 | icon: IconFlag3, 23 | items: [ 24 | "hello-world", 25 | "color-logging", 26 | "import-export", 27 | "dependency-management", 28 | "node", 29 | "npm", 30 | "typescript", 31 | "timers", 32 | "url-parsing", 33 | ], 34 | }, 35 | { 36 | title: "Encoding", 37 | icon: IconTransform, 38 | items: [ 39 | "importing-json", 40 | "parsing-serializing-json", 41 | "parsing-serializing-toml", 42 | "parsing-serializing-yaml", 43 | "parsing-serializing-csv", 44 | "hex-base64-encoding", 45 | "byte-manipulation", 46 | ], 47 | }, 48 | 49 | { 50 | title: "CLI", 51 | icon: IconTerminal2, 52 | items: [ 53 | "command-line-arguments", 54 | "prompts", 55 | "deno-version", 56 | "permissions", 57 | "writing-tests", 58 | ], 59 | }, 60 | { 61 | title: "Network", 62 | icon: IconNetwork, 63 | items: [ 64 | "http-requests", 65 | "websocket", 66 | "dns-queries", 67 | "http-server", 68 | "http-server-routing", 69 | "http-server-streaming", 70 | "http-server-files", 71 | "http-server-websocket", 72 | "tcp-listener", 73 | "tcp-connector", 74 | "udp-listener", 75 | "udp-connector", 76 | "tls-listener", 77 | "tls-connector", 78 | "piping-streams", 79 | ], 80 | }, 81 | { 82 | title: "System", 83 | icon: IconDeviceDesktop, 84 | items: [ 85 | "benchmarking", 86 | "pid", 87 | "os-signals", 88 | "environment-variables", 89 | "subprocesses-output", 90 | "subprocesses-spawn", 91 | ], 92 | }, 93 | { 94 | title: "File System", 95 | icon: IconFiles, 96 | items: [ 97 | "reading-files", 98 | "writing-files", 99 | "deleting-files", 100 | "moving-renaming-files", 101 | "temporary-files", 102 | "create-remove-directories", 103 | "symlinks", 104 | "watching-files", 105 | "walking-directories", 106 | "checking-file-existence", 107 | "path-operations", 108 | "streaming-files", 109 | ], 110 | }, 111 | { 112 | title: "Databases", 113 | icon: IconDatabase, 114 | items: [ 115 | "postgres", 116 | "kv", 117 | "kv-watch", 118 | "redis", 119 | "mongo", 120 | ], 121 | }, 122 | { 123 | title: "Scheduled tasks", 124 | icon: IconClock, 125 | items: [ 126 | "cron", 127 | "queues", 128 | ], 129 | }, 130 | { 131 | title: "Cryptography", 132 | icon: IconFileShredder, 133 | items: [ 134 | "hashing", 135 | "uuids", 136 | "ulid", 137 | ], 138 | }, 139 | { 140 | title: "Advanced", 141 | icon: IconStars, 142 | items: [ 143 | "web-workers", 144 | "webassembly", 145 | ], 146 | }, 147 | ]; 148 | -------------------------------------------------------------------------------- /utils/example.ts: -------------------------------------------------------------------------------- 1 | import { DIFFICULTIES, TAGS } from "./constants.ts"; 2 | import { FunctionComponent } from "preact"; 3 | 4 | export interface ExampleGroup { 5 | title: string; 6 | icon?: FunctionComponent; 7 | items: Example[]; 8 | } 9 | 10 | export interface Example { 11 | id: string; 12 | title: string; 13 | description: string; 14 | difficulty: keyof typeof DIFFICULTIES; 15 | tags: (keyof typeof TAGS)[]; 16 | additionalResources: [string, string][]; 17 | run?: string; 18 | playground?: string; 19 | files: ExampleFile[]; 20 | } 21 | 22 | export interface ExampleFile { 23 | name: string; 24 | snippets: ExampleSnippet[]; 25 | } 26 | 27 | export interface ExampleSnippet { 28 | text: string; 29 | code: string; 30 | } 31 | 32 | export function parseExample(id: string, file: string): Example { 33 | // Substitute $std/ with the full import url 34 | file = file.replaceAll("$std/", "https://deno.land/std@0.207.0/"); 35 | 36 | // Extract the multi line JS doc comment at the top of the file 37 | const [, jsdoc, rest] = file.match(/^\s*\/\*\*(.*?)\*\/\s*(.*)/s) || []; 38 | 39 | // Extract the @key value pairs from the JS doc comment 40 | let description = ""; 41 | const kvs: Record = {}; 42 | const resources = []; 43 | for (let line of jsdoc.split("\n")) { 44 | line = line.trim().replace(/^\*/, "").trim(); 45 | const [, key, value] = line.match(/^\s*@(\w+)\s+(.*)/) || []; 46 | if (key) { 47 | if (key === "resource") { 48 | resources.push(value); 49 | } else { 50 | kvs[key] = value.trim(); 51 | } 52 | } else { 53 | description += " " + line; 54 | } 55 | } 56 | description = description.trim(); 57 | 58 | // Separate the code into snippets. 59 | const files: ExampleFile[] = [{ 60 | name: "", 61 | snippets: [], 62 | }]; 63 | let parseMode = "code"; 64 | let currentFile = files[0]; 65 | let text = ""; 66 | let code = ""; 67 | 68 | for (const line of rest.split("\n")) { 69 | const trimmedLine = line.trim(); 70 | if (parseMode == "code") { 71 | if (line.startsWith("// File:")) { 72 | if (text || code.trimEnd()) { 73 | code = code.trimEnd(); 74 | currentFile.snippets.push({ text, code }); 75 | text = ""; 76 | code = ""; 77 | } 78 | const name = line.slice(8).trim(); 79 | if (currentFile.snippets.length == 0) { 80 | currentFile.name = name; 81 | } else { 82 | currentFile = { 83 | name, 84 | snippets: [], 85 | }; 86 | files.push(currentFile); 87 | } 88 | } else if (line.startsWith("/* File:")) { 89 | if (text || code.trimEnd()) { 90 | code = code.trimEnd(); 91 | currentFile.snippets.push({ text, code }); 92 | text = ""; 93 | code = ""; 94 | } 95 | const name = line.slice(8).trim(); 96 | if (currentFile.snippets.length == 0) { 97 | currentFile.name = name; 98 | } else { 99 | currentFile = { 100 | name, 101 | snippets: [], 102 | }; 103 | files.push(currentFile); 104 | } 105 | parseMode = "file"; 106 | } else if ( 107 | trimmedLine.startsWith("// deno-lint-ignore") || 108 | trimmedLine.startsWith("//deno-lint-ignore") || 109 | trimmedLine.startsWith("// deno-fmt-ignore") || 110 | trimmedLine.startsWith("//deno-fmt-ignore") 111 | ) { 112 | // skip deno directives 113 | } else if (trimmedLine.startsWith("//-")) { 114 | code += line.replace("//-", "//") + "\n"; 115 | } else if (trimmedLine.startsWith("//")) { 116 | if (text || code.trimEnd()) { 117 | code = code.trimEnd(); 118 | currentFile.snippets.push({ text, code }); 119 | } 120 | text = trimmedLine.slice(2).trim(); 121 | code = ""; 122 | parseMode = "comment"; 123 | } else { 124 | code += line + "\n"; 125 | } 126 | } else if (parseMode == "comment") { 127 | if ( 128 | trimmedLine.startsWith("// deno-lint-ignore") || 129 | trimmedLine.startsWith("//deno-lint-ignore") || 130 | trimmedLine.startsWith("// deno-fmt-ignore") || 131 | trimmedLine.startsWith("//deno-fmt-ignore") 132 | ) { 133 | // skip deno directives 134 | } else if (trimmedLine.startsWith("//")) { 135 | text += " " + trimmedLine.slice(2).trim(); 136 | } else { 137 | code += line + "\n"; 138 | parseMode = "code"; 139 | } 140 | } else if (parseMode == "file") { 141 | if (line == "*/") { 142 | parseMode = "code"; 143 | } else { 144 | code += line + "\n"; 145 | } 146 | } 147 | } 148 | if (text || code.trimEnd()) { 149 | code = code.trimEnd(); 150 | currentFile.snippets.push({ text, code }); 151 | } 152 | 153 | if (!kvs.title) { 154 | throw new Error("Missing title in JS doc comment."); 155 | } 156 | 157 | const tags = kvs.tags.split(",").map((s) => s.trim() as keyof typeof TAGS); 158 | for (const tag of tags) { 159 | if (!TAGS[tag]) { 160 | throw new Error(`Unknown tag '${tag}'`); 161 | } 162 | } 163 | 164 | const difficulty = kvs.difficulty as keyof typeof DIFFICULTIES; 165 | if (!DIFFICULTIES[difficulty]) { 166 | throw new Error(`Unknown difficulty '${difficulty}'`); 167 | } 168 | 169 | const additionalResources: [string, string][] = []; 170 | for (const resource of resources) { 171 | // @resource {https://deno.land/std/http/server.ts} std/http/server.ts 172 | const [_, url, title] = resource.match(/^\{(.*?)\}\s(.*)/) || []; 173 | if (!url || !title) { 174 | throw new Error(`Invalid resource: ${resource}`); 175 | } 176 | additionalResources.push([url, title]); 177 | } 178 | 179 | return { 180 | id, 181 | title: kvs.title, 182 | description, 183 | difficulty, 184 | tags, 185 | additionalResources, 186 | run: kvs.run, 187 | playground: kvs.playground, 188 | files, 189 | }; 190 | } 191 | -------------------------------------------------------------------------------- /components/Logo.tsx: -------------------------------------------------------------------------------- 1 | export function DenoLogo() { 2 | return ( 3 | 9 | Deno logo 10 | 11 | 12 | 13 | 14 | 21 | 22 | 29 | 30 | 31 | 32 | ); 33 | } 34 | 35 | export function DeployLogo() { 36 | return ( 37 | 43 | 47 | 48 | 49 | ); 50 | } 51 | 52 | export function CircleArrow(props: { right?: boolean }) { 53 | return ( 54 | 64 | 68 | 69 | 70 | ); 71 | } 72 | -------------------------------------------------------------------------------- /routes/[id]/index.tsx: -------------------------------------------------------------------------------- 1 | import { TOC } from "../../toc.ts"; 2 | import { Head } from "$fresh/runtime.ts"; 3 | import { Handlers, PageProps } from "$fresh/server.ts"; 4 | import { Page } from "../../components/Page.tsx"; 5 | import { CircleArrow, DeployLogo } from "../../components/Logo.tsx"; 6 | import { Prism } from "../../utils/prism.ts"; 7 | import { DIFFICULTIES, TAGS } from "../../utils/constants.ts"; 8 | import { Example, ExampleSnippet, parseExample } from "../../utils/example.ts"; 9 | import CopyButton from "../../islands/CopyButton.tsx"; 10 | 11 | type Data = [Example, Example | null, Example | null] | null; 12 | 13 | export const handler: Handlers = { 14 | GET(_req, ctx) { 15 | const id = ctx.params.id; 16 | const newUrl = "https://docs.deno.com/examples/"; 17 | if (id === "hello-world") { 18 | // hello-world doesn't exist in the new site 19 | return Response.redirect(newUrl); 20 | } 21 | return Response.redirect(newUrl + id); 22 | }, 23 | }; 24 | 25 | export default function ExamplePage(props: PageProps) { 26 | if (props.data === null) { 27 | return
    404 Example Not Found
    ; 28 | } 29 | 30 | const [example, prev, next] = props.data; 31 | const url = `${props.url.origin}${props.url.pathname}${ 32 | example.files.length > 1 ? "/main" : "" 33 | }.ts`; 34 | 35 | const description = (example.description || example.title) + 36 | " -- Deno by example is a collection of annotated examples for how to use Deno, and the various features it provides."; 37 | 38 | return ( 39 | 40 | 41 | 42 | 43 | 44 |
    45 |
    46 |

    50 | {DIFFICULTIES[example.difficulty].title} 51 |

    52 |
    53 | {example.tags.map((tag) => ( 54 | 58 | {TAGS[tag].title} 59 | 60 | ))} 61 |
    62 |
    63 |
    64 |

    {example.title}

    65 | 69 | Edit 70 | 71 |
    72 | {example.description && ( 73 |
    74 |

    {example.description}

    75 |
    76 | )} 77 | {example.files.map((file) => ( 78 |
    79 | 84 | {file.snippets.map((snippet, i) => ( 85 | 92 | ))} 93 |
    94 | ))} 95 |
    96 |
    97 |
    98 | {example.run && ( 99 | <> 100 |

    101 | Run{" "} 102 | 103 | this example 104 | {" "} 105 | locally using the Deno CLI: 106 |

    107 |
    108 |                   {example.run.startsWith("deno")
    109 |                     ? example.run.replace("", url)
    110 |                     : "deno run " + example.run.replace("", url)}
    111 |                 
    112 | 113 | )} 114 | {example.playground && ( 115 |
    116 |

    117 | Try this example in a Deno Deploy playground: 118 |

    119 |

    120 | 127 | 128 | 129 |

    130 |
    131 | )} 132 | {example.additionalResources.length > 0 && ( 133 |
    134 |

    Additional resources:

    135 |
      136 | {example.additionalResources.map(([link, title]) => ( 137 |
    • 141 | 142 | {title} 143 | 144 |
    • 145 | ))} 146 |
    147 |
    148 | )} 149 |
    150 |
    151 | 152 |
    153 | {prev 154 | ? ( 155 | 159 | 160 | {prev.title} 161 | 162 | ) 163 | :
    } 164 | {next && ( 165 | 169 | {next.title} 170 | 171 | 172 | )} 173 |
    174 |
    175 |
    176 | ); 177 | } 178 | 179 | function SnippetComponent(props: { 180 | filename: string; 181 | firstOfFile: boolean; 182 | lastOfFile: boolean; 183 | snippet: ExampleSnippet; 184 | }) { 185 | const renderedSnippet = Prism.highlight( 186 | props.snippet.code, 187 | Prism.languages.ts, 188 | "ts", 189 | ); 190 | 191 | return ( 192 |
    193 |
    194 | {props.snippet.text} 195 |
    196 |
    203 | {props.filename && ( 204 | 209 | {props.filename} 210 | 211 | )} 212 |
    213 | 214 |
    215 |
    216 |
    217 |         
    218 |
    219 |
    220 | ); 221 | } 222 | -------------------------------------------------------------------------------- /utils/example_test.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | /// 5 | /// 6 | 7 | import { assertEquals, assertThrows } from "$std/testing/asserts.ts"; 8 | import { Example, parseExample } from "./example.ts"; 9 | import importMap from "../deno.json" with { type: "json" }; 10 | 11 | Deno.test("parse jsdoc", () => { 12 | const example = ` 13 | /** 14 | * @title Input Prompts 15 | * @difficulty beginner 16 | * @tags cli, web , deploy 17 | * @run 18 | * @playground 19 | * 20 | * Prompts are used to ask the user for input or feedback on actions. 21 | */ 22 | `; 23 | const expected: Example = { 24 | id: "input-prompts", 25 | title: "Input Prompts", 26 | description: 27 | "Prompts are used to ask the user for input or feedback on actions.", 28 | difficulty: "beginner", 29 | tags: ["cli", "web", "deploy"], 30 | additionalResources: [], 31 | run: "", 32 | playground: "", 33 | files: [{ 34 | name: "", 35 | snippets: [], 36 | }], 37 | }; 38 | const actual = parseExample("input-prompts", example); 39 | assertEquals(actual, expected); 40 | }); 41 | 42 | Deno.test("parse jsdoc unknown tag", () => { 43 | const example = ` 44 | /** 45 | * @title abc 46 | * @difficulty beginner 47 | * @tags foo, cli, deploy 48 | * @run 49 | * 50 | * xyz 51 | */ 52 | `; 53 | assertThrows( 54 | () => { 55 | parseExample("abc", example); 56 | }, 57 | Error, 58 | "Unknown tag 'foo'", 59 | ); 60 | }); 61 | 62 | Deno.test("parse jsdoc unknown difficulty", () => { 63 | const example = ` 64 | /** 65 | * @title abc 66 | * @difficulty garbage 67 | * @tags cli, deploy 68 | * @run 69 | * 70 | * xyz 71 | */ 72 | `; 73 | assertThrows( 74 | () => { 75 | parseExample("abc", example); 76 | }, 77 | Error, 78 | "Unknown difficulty 'garbage'", 79 | ); 80 | }); 81 | 82 | Deno.test("parse jsdoc missing title", () => { 83 | const example = ` 84 | /** 85 | * @difficulty garbage 86 | * @tags cli, deploy 87 | * @run 88 | * 89 | * xyz 90 | */ 91 | `; 92 | assertThrows( 93 | () => { 94 | parseExample("abc", example); 95 | }, 96 | Error, 97 | "Missing title", 98 | ); 99 | }); 100 | 101 | Deno.test("parse jsdoc no run", () => { 102 | const example = ` 103 | /** 104 | * @title abc 105 | * @difficulty beginner 106 | * @tags cli, deploy 107 | * 108 | * xyz 109 | */ 110 | `; 111 | const expected: Example = { 112 | id: "abc", 113 | title: "abc", 114 | description: "xyz", 115 | difficulty: "beginner", 116 | tags: ["cli", "deploy"], 117 | additionalResources: [], 118 | run: undefined, 119 | playground: undefined, 120 | files: [{ 121 | name: "", 122 | snippets: [], 123 | }], 124 | }; 125 | const actual = parseExample("abc", example); 126 | assertEquals(actual, expected); 127 | }); 128 | 129 | Deno.test("parse jsdoc no description", () => { 130 | const example = ` 131 | /** 132 | * @title abc 133 | * @difficulty beginner 134 | * @tags cli, deploy 135 | */ 136 | `; 137 | const expected: Example = { 138 | id: "abc", 139 | title: "abc", 140 | description: "", 141 | difficulty: "beginner", 142 | tags: ["cli", "deploy"], 143 | additionalResources: [], 144 | run: undefined, 145 | playground: undefined, 146 | files: [{ 147 | name: "", 148 | snippets: [], 149 | }], 150 | }; 151 | const actual = parseExample("abc", example); 152 | assertEquals(actual, expected); 153 | }); 154 | 155 | Deno.test("parse jsdoc resources", () => { 156 | const example = ` 157 | /** 158 | * @title abc 159 | * @difficulty beginner 160 | * @tags cli, deploy 161 | * @resource {https://deno.land#install} Deno: Installation 162 | * @resource {https://deno.land/manual/getting_started/setup_your_environment} Deno Manual: Setup your environment 163 | */ 164 | `; 165 | const expected: Example = { 166 | id: "abc", 167 | title: "abc", 168 | description: "", 169 | difficulty: "beginner", 170 | tags: ["cli", "deploy"], 171 | additionalResources: [ 172 | ["https://deno.land#install", "Deno: Installation"], 173 | [ 174 | "https://deno.land/manual/getting_started/setup_your_environment", 175 | "Deno Manual: Setup your environment", 176 | ], 177 | ], 178 | run: undefined, 179 | playground: undefined, 180 | files: [{ 181 | name: "", 182 | snippets: [], 183 | }], 184 | }; 185 | const actual = parseExample("abc", example); 186 | assertEquals(actual, expected); 187 | }); 188 | 189 | Deno.test("parse jsdoc resources broken", () => { 190 | const example = ` 191 | /** 192 | * @title abc 193 | * @difficulty beginner 194 | * @tags cli, deploy 195 | * @resource {} 196 | */ 197 | `; 198 | assertThrows( 199 | () => { 200 | parseExample("abc", example); 201 | }, 202 | Error, 203 | "Invalid resource", 204 | ); 205 | }); 206 | 207 | const BASIC_JSDOC = ` 208 | /** 209 | * @title abc 210 | * @difficulty beginner 211 | * @tags cli, deploy 212 | */ 213 | `; 214 | const EXPECTED_BASIC: Example = { 215 | id: "abc", 216 | title: "abc", 217 | description: "", 218 | difficulty: "beginner", 219 | tags: ["cli", "deploy"], 220 | additionalResources: [], 221 | run: undefined, 222 | playground: undefined, 223 | files: [{ 224 | name: "", 225 | snippets: [], 226 | }], 227 | }; 228 | 229 | Deno.test("parse snippets", () => { 230 | const example = `${BASIC_JSDOC} 231 | 232 | // snippet 1 233 | code 1; 234 | 235 | // snippet 2 236 | 237 | // snippet 3 238 | foo; 239 | 240 | /** still snippet 3 */ 241 | 242 | // ending snippet 243 | `; 244 | const expected: Example = { 245 | ...EXPECTED_BASIC, 246 | files: [ 247 | { 248 | name: "", 249 | snippets: [ 250 | { 251 | text: "snippet 1", 252 | code: "code 1;", 253 | }, 254 | { 255 | text: "snippet 2", 256 | code: "", 257 | }, 258 | { 259 | text: "snippet 3", 260 | code: " foo;\n\n/** still snippet 3 */", 261 | }, 262 | { 263 | text: "ending snippet", 264 | code: "", 265 | }, 266 | ], 267 | }, 268 | ], 269 | }; 270 | const actual = parseExample("abc", example); 271 | assertEquals(actual, expected); 272 | }); 273 | 274 | Deno.test("parser ignores deno-lint-ignore", () => { 275 | const example = `${BASIC_JSDOC} 276 | // deno-lint-ignore no-unused-vars 277 | const foo = "bar"; 278 | 279 | // comment 280 | // deno-lint-ignore no-unused-vars 281 | const bar = "baz"; 282 | 283 | // comment 2 284 | foo; 285 | // deno-lint-ignore no-unused-vars 286 | bar; 287 | // deno-lint-ignore no-unused-vars 288 | `; 289 | 290 | const expected: Example = { 291 | ...EXPECTED_BASIC, 292 | files: [ 293 | { 294 | name: "", 295 | snippets: [ 296 | { 297 | text: "", 298 | code: `const foo = "bar";`, 299 | }, 300 | { 301 | text: "comment", 302 | code: `const bar = "baz";`, 303 | }, 304 | { 305 | text: "comment 2", 306 | code: `foo;\nbar;`, 307 | }, 308 | ], 309 | }, 310 | ], 311 | }; 312 | const actual = parseExample("abc", example); 313 | assertEquals(actual, expected); 314 | }); 315 | 316 | Deno.test("parse multiple files", () => { 317 | const example = `${BASIC_JSDOC} 318 | 319 | // File: main.ts 320 | // Main file 321 | import "./foo.ts"; 322 | 323 | // File: foo.ts 324 | // Hello 325 | globalThis.Hello = "World"; 326 | 327 | // Back to main 328 | foo; 329 | // File: main.ts 330 | 331 | // foo 332 | // File: bar.ts 333 | 334 | /* File: hey.json 335 | { 336 | "hello": "world" 337 | } 338 | */ 339 | `; 340 | const expected: Example = { 341 | ...EXPECTED_BASIC, 342 | files: [ 343 | { 344 | name: "main.ts", 345 | snippets: [ 346 | { 347 | text: "Main file", 348 | code: `import "./foo.ts";`, 349 | }, 350 | ], 351 | }, 352 | { 353 | name: "foo.ts", 354 | snippets: [ 355 | { 356 | text: "Hello", 357 | code: `globalThis.Hello = "World";`, 358 | }, 359 | { 360 | code: "foo;", 361 | text: "Back to main", 362 | }, 363 | { 364 | code: "", 365 | text: "File: main.ts", 366 | }, 367 | { 368 | text: "foo File: bar.ts", 369 | code: "", 370 | }, 371 | ], 372 | }, 373 | { 374 | name: "hey.json", 375 | snippets: [ 376 | { 377 | text: "", 378 | code: `{ 379 | "hello": "world" 380 | }`, 381 | }, 382 | ], 383 | }, 384 | ], 385 | }; 386 | const actual = parseExample("abc", example); 387 | assertEquals(actual, expected); 388 | }); 389 | 390 | Deno.test("replace $std/", () => { 391 | const example = ` 392 | /** 393 | * @title Replacement test 394 | * @difficulty beginner 395 | * @tags cli 396 | * @run 397 | * @playground 398 | * @resource {$std/flags/mod.ts} Doc: std/flags 399 | * 400 | * Simple check for replacing std version 401 | */ 402 | `; 403 | const expected: Example = { 404 | id: "replacement-test", 405 | title: "Replacement test", 406 | description: "Simple check for replacing std version", 407 | difficulty: "beginner", 408 | tags: ["cli"], 409 | additionalResources: [ 410 | [importMap.imports["$std/"] + "flags/mod.ts", "Doc: std/flags"], 411 | ], 412 | run: "", 413 | playground: "", 414 | files: [{ 415 | name: "", 416 | snippets: [], 417 | }], 418 | }; 419 | const actual = parseExample("replacement-test", example); 420 | assertEquals(actual, expected); 421 | }); 422 | --------------------------------------------------------------------------------