diff --git a/README.md b/README.md index 910f92831..8141dd690 100644 --- a/README.md +++ b/README.md @@ -196,6 +196,65 @@ succeeds without error before submitting a pull request. This will ensure that there are no broken links or invalid MDX syntax in the content you have authored. +## Examples + +(https://docs.deno.com/examples)[Deno by Example] is a collection of small snippets showcasing various functions of the APIs implemented in Deno. + + +- Examples are written in TypeScript +- Each example should be a single file, no more than 50 lines +- Each example should be a self-contained unit, and should depend on no + dependencies other than Deno builtins and the standard library, unless a + third-party library is strictly required. +- Each example should be runnable without additional dependencies on all systems + (exceptions can be made for platform specific functionality) +- Examples should be introduce at most one (or in exceptional cases two or + three) concepts in Deno / Web APIs. Existing concepts should be linked to. +- Code should be kept _really simple_, and should be easy to read and understand + by anyone. Do not use complicated code constructs, or hard to follow builtins + like `Array.reduce` +- Concepts introduced in an example should be explained + +### Adding an example + +To add an example, create a file in the `by-example` directory. The file name should +be a short description of the example (in kebab case) and the contents should be the code for the example. +The file should be in the `.ts` format. The file should start with a JSDoc style +multi line comment that describes the example: + +```ts +/** + * @title HTTP server: Hello World + * @difficulty intermediate + * @tags cli, deploy + * @run --allow-net + * @group Basics + * + * An example of a HTTP server that serves a "Hello World" message. + */ +``` + +You should add a title, a difficulty level (`beginner` or `intermediate`), and a +list of tags (`cli`, `deploy`, `web` depending on where an example is runnable). +The `@run` tag should be included if the example can be run locally by just +doing `deno run `. If running requires permissions, add these: + +```ts +/** + * ... + * @run --allow-net --allow-read + */ +``` + +After the pragmas, you can add a description of the example. This is optional, +but recommended for most examples. It should not be longer than one or two +lines. The description shows up at the top of the example in the example page, +and in search results. + +After the JS Doc comment, you can write the code. Code can be prefixed with a +comment that describes the code. The comment will be rendered next to the code +in the example page. + ## Special thanks for historical contributions This repository was created using content from the diff --git a/by-example/benchmarking.ts b/by-example/benchmarking.ts new file mode 100644 index 000000000..98edbadc6 --- /dev/null +++ b/by-example/benchmarking.ts @@ -0,0 +1,51 @@ +/** + * @title Benchmarking + * @difficulty beginner + * @tags cli + * @run deno bench + * @resource {https://docs.deno.com/runtime/manual/tools/benchmarker} Manual: Benchmarker tool + * @resource {/http-requests} Example: HTTP Requests + * @group System + * + * When writing libraries, a very common task that needs to be done is + * testing the speed of methods, usually against other libraries. Deno + * provides an easy-to-use subcommand for this purpose. + */ + +// The most basic form of deno benchmarking is providing a name and an +// anonymous function to run. +Deno.bench("URL parsing", () => { + new URL("https://deno.land"); +}); + +// We are also able to use an async function. +Deno.bench("Async method", async () => { + await crypto.subtle.digest("SHA-256", new Uint8Array([1, 2, 3])); +}); + +// We can optionally use long form bench definitions. +Deno.bench({ + name: "Long form", + fn: () => { + new URL("https://deno.land"); + }, +}); + +// We are also able to group certain benchmarks together +// using the optional group and baseline arguments. +Deno.bench({ + name: "Date.now()", + group: "timing", + baseline: true, + fn: () => { + Date.now(); + }, +}); + +Deno.bench({ + name: "performance.now()", + group: "timing", + fn: () => { + performance.now(); + }, +}); diff --git a/by-example/byte-manipulation.ts b/by-example/byte-manipulation.ts new file mode 100644 index 000000000..f19dbcfe6 --- /dev/null +++ b/by-example/byte-manipulation.ts @@ -0,0 +1,34 @@ +/** + * @title Manipulating byte arrays + * @difficulty beginner + * @tags cli + * @run + * @resource {$std/bytes} Doc: std/bytes + * @group Encoding + * + * When working with lower-level data we often deal + * with byte arrays in the form of Uint8Arrays. There + * are some common manipulations and queries that can + * be done and are included with the standard library. + */ + +// Let's initialize some byte arrays +const a = new Uint8Array([0, 1, 2, 3, 4]); +const b = new Uint8Array([5, 6, 7, 8, 9]); +const c = new Uint8Array([4, 5]); + +// We can concatenate two byte arrays using the +// concat method +import { concat } from "jsr:@std/bytes/concat"; +const d = concat([a, b]); +console.log(d); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + +// Sometimes we need to repeat certain bytes +import { repeat } from "jsr:@std/bytes/repeat"; +console.log(repeat(c, 4)); // [4, 5, 4, 5, 4, 5, 4, 5] + +// Sometimes we need to mutate a Uint8Array and need a copy +import { copy } from "jsr:@std/bytes/copy"; +const cpy = new Uint8Array(5); +console.log("Bytes copied:", copy(b, cpy)); // 5 +console.log("Bytes:", cpy); // [5, 6, 7, 8, 9] diff --git a/by-example/checking-file-existence.ts b/by-example/checking-file-existence.ts new file mode 100644 index 000000000..350f57bbe --- /dev/null +++ b/by-example/checking-file-existence.ts @@ -0,0 +1,42 @@ +/** + * @title Checking for file existence + * @difficulty beginner + * @tags cli, deploy + * @run --allow-read --allow-write + * @group File System + * + * Sometimes we as developers think that we need to check + * if a file exists or not. More often than not, we are + * entirely wrong. + */ + +// Let's say we wanted to create a folder if one doesn't +// already exist. Logically it makes sense to first verify +// that the folder exists, then try to create it right? +// Wrong. This will create a race condition where if a folder +// gets created in between when you check if the folder exists +// and when you create a folder, your program will crash. +// Instead, you should just create a folder and try to catch +// errors like so. +try { + await Deno.mkdir("new_dir"); +} catch (err) { + if (!(err instanceof Deno.errors.AlreadyExists)) { + throw err; + } +} + +// This applies to almost every usecase. If you have a niche +// usecase that requires you to check for existence of a file +// without doing an filesystem operations other than that +// (which is quite rare), then you can simply lstat the file +// and catch the error. +try { + await Deno.lstat("example.txt"); + console.log("exists!"); +} catch (err) { + if (!(err instanceof Deno.errors.NotFound)) { + throw err; + } + console.log("not exists!"); +} diff --git a/by-example/color-logging.ts b/by-example/color-logging.ts new file mode 100644 index 000000000..875c48f8d --- /dev/null +++ b/by-example/color-logging.ts @@ -0,0 +1,41 @@ +/** + * @title Logging with colors + * @difficulty beginner + * @tags cli, deploy, web + * @run + * @resource {https://developer.mozilla.org/en-US/docs/Web/API/console#styling_console_output} MDN: Styling console output + * @group Basics + * + * Most modern terminals can display program logs in colors and with text + * decorations. This example shows how to display colors when using + * `console.log`. + */ + +// Deno has support for CSS using the %c syntax in console.log. Here, the text +// "Hello World" is displayed in red. This also works in the browser. +console.log("%cHello World", "color: red"); + +// Not just foreground, but also background colors can be set. +console.log("%cHello World", "background-color: blue"); + +// Text decorations like underline and strikethrough can be set too. +console.log("%cHello World", "text-decoration: underline"); +console.log("%cHello World", "text-decoration: line-through"); + +// Font weight can also be set (either to normal, or to bold). +console.log("%cHello World", "font-weight: bold"); + +// Multiple CSS rules can also be applied at once. Here the text is red and bold. +console.log("%cHello World", "color: red; font-weight: bold"); + +// A single console.log can also contain multiple %c values. Styling is reset at +// every %c. +console.log("%cHello %cWorld", "color: red", "color: blue"); + +// Instead of predefined colors, hex literals and `rgb()` are also supported. +// Note that some terminals do not support displaying these colors. +console.log("%cHello World", "color: #FFC0CB"); +console.log("%cHello World", "color: rgb(255, 192, 203)"); + +// It is not possible to configure font size, font family, leading, or any other +// CSS attributes. diff --git a/by-example/command-line-arguments.ts b/by-example/command-line-arguments.ts new file mode 100644 index 000000000..a2ff0c360 --- /dev/null +++ b/by-example/command-line-arguments.ts @@ -0,0 +1,39 @@ +/** + * @title Command Line Arguments + * @difficulty beginner + * @tags cli + * @run Deno Sushi --help --version=1.0.0 --no-color + * @resource {https://deno.land/api?s=Deno.args} Doc: Deno.args + * @resource {$std/cli/parse_args.ts} Doc: std/cli + * @group CLI + * + * Command line arguments are often used to pass configuration options to a + * program. + */ + +// You can get the list of command line arguments from `Deno.args`. +const name = Deno.args[0]; +const food = Deno.args[1]; +console.log(`Hello ${name}, I like ${food}!`); + +// Often you want to parse command line arguments like `--foo=bar` into +// structured data. This can be done using `std/cli`. +import { parseArgs } from "jsr:@std/cli/parse-args"; + +// The `parseArgs` function takes the argument list, and a list of options. In these +// options you specify the types of the accepted arguments and possibly default +// values. An object is returned with the parsed arguments. +// NOTE: this function is based on [`minimist`](https://github.com/minimistjs/minimist), not compatible with the `parseArgs()` function in `node:util`. +const flags = parseArgs(Deno.args, { + boolean: ["help", "color"], + string: ["version"], + default: { color: true }, + negatable: ["color"], +}); +console.log("Wants help?", flags.help); +console.log("Version:", flags.version); +console.log("Wants color?:", flags.color); + +// The `_` field of the returned object contains all arguments that were not +// recognized as flags. +console.log("Other:", flags._); diff --git a/by-example/create-remove-directories.ts b/by-example/create-remove-directories.ts new file mode 100644 index 000000000..9b3118ef4 --- /dev/null +++ b/by-example/create-remove-directories.ts @@ -0,0 +1,36 @@ +/** + * @title Creating & Removing Directories + * @difficulty beginner + * @tags cli + * @run --allow-write + * @resource {https://deno.land/api?s=Deno.mkdir} Doc: Deno.mkdir + * @resource {https://deno.land/api?s=Deno.remove} Doc: Deno.remove + * @group File System + * + * Creating and removing directories is a common task. Deno has a number of + * functions for this task. + */ + +// The `Deno.mkdir()` function creates a directory at the specified path. +// If the directory already exists, it errors. +await Deno.mkdir("new_dir"); + +// A directory can also be created recursively. In the code below, three new +// directories are created: `./dir`, `./dir/dir2`, and `./dir/dir2/subdir`. If +// the recursive option is specified the function will not error if any of the +// directories already exist. +await Deno.mkdir("./dir/dir2/subdir", { recursive: true }); + +// Directories can also be removed. This function below removes the `./new_dir` +// directory. If the directory is not empty, the function will error. +await Deno.remove("./new_dir"); + +// To remove a directory recursively, use the `recursive` option. This will +// remove the `./dir` directory and all of its contents. +await Deno.remove("./dir", { recursive: true }); + +// Synchronous versions of the above functions are also available. +Deno.mkdirSync("new_dir"); +Deno.removeSync("new_dir"); + +// Creating and removing directories requires the `write` permission. diff --git a/by-example/cron.ts b/by-example/cron.ts new file mode 100644 index 000000000..fdf6bdb72 --- /dev/null +++ b/by-example/cron.ts @@ -0,0 +1,25 @@ +/** + * @title Deno Cron + * @difficulty intermediate + * @tags cli, deploy + * @run --unstable + * @resource {https://docs.deno.com/deploy/kv/manual/cron} Deno Cron user guide + * @resource {https://deno.land/api?s=Deno.cron&unstable=} Deno Cron Runtime API docs + * @group Scheduled Tasks + * + * Deno Cron is a cron task scheduler built into the Deno runtime and works with + * zero configuration on Deno Deploy. There's no overlapping cron executions and + * has automatic handler retries on exceptions. + */ + +// Create a cron job called "Log a message" that runs once a minute. +Deno.cron("Log a message", "* * * * *", () => { + console.log("This will print once a minute."); +}); + +// Create a cron job with a backoff schedule measured in milliseconds. +Deno.cron("Retry example", "* * * * *", { + backoffSchedule: [1000, 5000, 10000], +}, () => { + throw new Error("Deno.cron will retry this three times, to no avail!"); +}); diff --git a/by-example/deleting-files.ts b/by-example/deleting-files.ts new file mode 100644 index 000000000..3b8f19fcb --- /dev/null +++ b/by-example/deleting-files.ts @@ -0,0 +1,36 @@ +/** + * @title Deleting Files + * @difficulty beginner + * @tags cli + * @resource {https://deno.land/api?s=Deno.remove} Doc: Deno.remove + * @group File System + * + * Removing files and directories is a common task. Deno has a number of + * functions for this task. + */ + +// In the case that we want to remove a simple file, +// we can simply call Deno.remove with the filename as +// a parameter +await Deno.remove("example.txt"); + +// There is also a sync version of the api available +Deno.removeSync("example.txt"); + +// If we want to remove a directory, we could do exactly +// what we did above. If the directory has contents, the +// call would error out. If we want to recursively delete +// the contents of a directory, we should set recursive to +// true +await Deno.remove("./dir", { recursive: true }); + +// A common pattern is to remove a file or directory only +// if it already exists. The correct way of doing this is +// by just doing it and trying to catch any NotFound errors. +try { + await Deno.remove("example.txt"); +} catch (err) { + if (!(err instanceof Deno.errors.NotFound)) { + throw err; + } +} diff --git a/by-example/deno-version.ts b/by-example/deno-version.ts new file mode 100644 index 000000000..10f0ac1e4 --- /dev/null +++ b/by-example/deno-version.ts @@ -0,0 +1,20 @@ +/** + * @title Getting the Deno version + * @difficulty beginner + * @tags cli + * @run + * @resource {https://deno.land/api?s=Deno.version} Doc: Deno.version + * @group CLI + * + * How to examine the version of Deno being used. + */ + +// To print the current version of Deno, just reach into the Deno global object +// where all non-web-standard APIs reside. +console.log("Current Deno version", Deno.version.deno); + +// Deno has two main dependencies: the V8 JavaScript engine (from the Chrome web +// browser) and the TypeScript compiler. The versions of these are also +// accessible in the `Deno.version` object. +console.log("Current TypeScript version", Deno.version.typescript); +console.log("Current V8 version", Deno.version.v8); diff --git a/by-example/dependency-management.ts b/by-example/dependency-management.ts new file mode 100644 index 000000000..de99c9b27 --- /dev/null +++ b/by-example/dependency-management.ts @@ -0,0 +1,28 @@ +/** + * @title Dependency Management + * @difficulty beginner + * @tags cli, deploy + * @resource {/import-export} Example: Importing & Exporting + * @run + * @group Basics + * + * It is unwieldy to have to import the same remote module over and over again. + * Deno provides some conventions to make managing dependencies easier. + */ + +// File: ./deps.ts + +// The Deno ecosystem has a convention to re-export all remote dependencies from +// a deps.ts file at the root of the repo. This keeps remote dependencies +// organized, and in a single place. +export * as http from "jsr:@std/http"; +export * as path from "jsr:@std/path"; + +// File: ./main.ts + +// Other files can then import dependencies from the deps.ts file. +// deno-lint-ignore no-unused-vars +import { path } from "./deps.ts"; + +// Doing this makes package version upgrades really easy, as all external +// dependency specifiers live in the same file. diff --git a/by-example/dns-queries.ts b/by-example/dns-queries.ts new file mode 100644 index 000000000..57e80d50f --- /dev/null +++ b/by-example/dns-queries.ts @@ -0,0 +1,31 @@ +/** + * @title Running DNS Queries + * @difficulty beginner + * @tags cli, deploy + * @run --allow-net + * @resource {https://deno.land/api?s=Deno.resolveDns} Doc: Deno.resolveDns + * @resource {https://developer.mozilla.org/en-US/docs/Glossary/DNS} MDN: DNS + * @group Network + * + * There are some cases when running DNS queries is useful. This is + * usually the case when one would like to use a DNS server not configured + * on the machine. + */ + +// In the most basic basic case, we can query a domain's A record. +// This will give us a list of ipv4 addresses. +const a = await Deno.resolveDns("example.com", "A"); +console.log(a); + +// We can also query other record types. In this case we are querying +// an MX record which is related to email protocols. Deno supports querying +// A, AAAA, ANAME, CAA, CNAME, MX, NAPTR, NS, PTR, SOA, SRV, and TXT records. +const mx = await Deno.resolveDns("example.com", "MX"); +console.log(mx); + +// We are also optionally able to specify a nameserver via an ip address +// and (optionally) a port number. To override the system configuration. +const aaaa = await Deno.resolveDns("example.com", "AAAA", { + nameServer: { ipAddr: "8.8.8.8", port: 53 }, +}); +console.log(aaaa); diff --git a/by-example/environment-variables.ts b/by-example/environment-variables.ts new file mode 100644 index 000000000..7bdc4c161 --- /dev/null +++ b/by-example/environment-variables.ts @@ -0,0 +1,41 @@ +/** + * @title Environment Variables + * @difficulty beginner + * @tags cli, deploy + * @run --allow-env + * @resource {https://deno.land/api?s=Deno.env} Doc: Deno.env + * @resource {https://docs.deno.com/deploy/manual/environment-variables} Deploy Docs: Environment Variables + * @group System + * + * Environment variables can be used to configure the behavior of a program, + * or pass data from one program to another. + */ + +// Here an environment variable with the name "PORT" is read. If this variable +// is set the return value will be a string. If it is unset it will be `undefined`. +const PORT = Deno.env.get("PORT"); +console.log("PORT:", PORT); + +// You can also get an object containing all environment variables. +const env = Deno.env.toObject(); +console.log("env:", env); + +// Environment variables can also be set. The set environment variable only affects +// the current process, and any new processes that are spawned from it. It does +// not affect parent processes or the user shell. +Deno.env.set("MY_PASSWORD", "123456"); + +// You can also unset an environment variable. +Deno.env.delete("MY_PASSWORD"); + +// Note that environment variables are case-sensitive on unix, but not on +// Windows. This means that these two invocations will have different results +// between platforms. +Deno.env.set("MY_PASSWORD", "123"); +Deno.env.set("my_password", "456"); +console.log("UPPERCASE:", Deno.env.get("MY_PASSWORD")); +console.log("lowercase:", Deno.env.get("my_password")); + +// Access to environment variables is only possible if the Deno process is +// running with env var permissions (`--allow-env`). You can limit the permission +// to only a specific number of environment variables (`--allow-env=PORT,MY_PASSWORD`). diff --git a/by-example/hashing.ts b/by-example/hashing.ts new file mode 100644 index 000000000..1f557301a --- /dev/null +++ b/by-example/hashing.ts @@ -0,0 +1,58 @@ +/** + * @title Hashing + * @difficulty intermediate + * @tags cli, deploy + * @run --allow-read + * @resource {https://deno.land/api?s=SubtleCrypto} Doc: crypto.subtle + * @resource {https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest} MDN: Cryptographic Digests + * @group Cryptography + * + * Hashing data is a common operation that is facilitated + * through Deno's support for the Web Crypto API. In addition, + * the Deno standard library's implementation extends the standard API, allowing for + * more advanced uses. + */ + +// In our first example, we'll hash the contents of a string variable. +const message = "The easiest, most secure JavaScript runtime."; + +// Before we can pass our message to the hashing function, +// we first need to encode it into a uint8 array. +const messageBuffer = new TextEncoder().encode(message); + +// Here, we use the built-in crypto.subtle.digest method to hash our original message. +// The hash is returned as an ArrayBuffer. To obtain a string +// we'll need to do a little more work. +const hashBuffer = await crypto.subtle.digest("SHA-256", messageBuffer); + +// We can decode this into a string using the standard +// library's encodeHex method. +import { encodeHex } from "jsr:@std/encoding/hex"; +const hash = encodeHex(hashBuffer); +console.log(hash); + +// For our second example, we'll hash the contents of a file. +// Hashing a file is a common operation and doing this +// without loading the whole file into memory is a typical +// requirement. + +// The standard library has extensions to the Web +// Crypto API that are useful when doing things +// like hashing a file. These can be accessed through the +// "crypto" module, a drop-in replacement for the Web Crypto +// API that delegates to the native implementation when +// possible. +import { crypto } from "jsr:@std/crypto"; +const file = await Deno.open("example.txt", { read: true }); + +// We obtain an async iterable using the readable property. +const readableStream = file.readable; + +// This time, when we call crypto.subtle.digest, we're using the +// imported version that allows us to operate on the +// async iterable. +const fileHashBuffer = await crypto.subtle.digest("SHA-256", readableStream); + +// Finally, we obtain the hex result using encodeHex like earlier. +const fileHash = encodeHex(fileHashBuffer); +console.log(fileHash); diff --git a/by-example/hex-base64-encoding.ts b/by-example/hex-base64-encoding.ts new file mode 100644 index 000000000..d80021153 --- /dev/null +++ b/by-example/hex-base64-encoding.ts @@ -0,0 +1,41 @@ +/** + * @title Hex and Base64 Encoding + * @difficulty beginner + * @tags cli + * @run + * @group Encoding + * + * There are a few cases where it would be practical to encode + * and decode between different string and array buffer formats. + * The Deno standard library makes this easy. + */ + +// The standard library provides hex and base64 encoding and decoding utilities +import * as base64 from "jsr:@std/encoding/base64"; +import * as hex from "jsr:@std/encoding/hex"; + +// We can easily encode a string or an array buffer into base64 using the base64.encode method. +const base64Encoded = base64.encode("somestringtoencode"); +console.log(base64.encode(new Uint8Array([1, 32, 67, 120, 19]))); + +// We can then decode base64 into a byte array using the decode method. +const base64Decoded = base64.decode(base64Encoded); + +// If we want to get the value as a string we can use the built-in TextDecoder. +// We will use these a lot so we can store them in variables to reuse them. +const textEncoder = new TextEncoder(); +const textDecoder = new TextDecoder(); +console.log(textDecoder.decode(base64Decoded)); + +// To encode hex, we always use array buffers. +// To use a string as an input we can encode our text. +const arrayBuffer = textEncoder.encode("somestringtoencode"); +const hexEncoded = hex.encode(arrayBuffer); +console.log(hexEncoded); + +// To read our hex values as a string, we can decode the buffer. +console.log(textDecoder.decode(hexEncoded)); + +// We can convert back to a string by using the decode method. +const hexDecoded = hex.decode(hexEncoded); +console.log(textDecoder.decode(hexDecoded)); diff --git a/by-example/http-requests.ts b/by-example/http-requests.ts new file mode 100644 index 000000000..82c9a611e --- /dev/null +++ b/by-example/http-requests.ts @@ -0,0 +1,87 @@ +/** + * @title HTTP Requests + * @difficulty beginner + * @tags cli, deploy, web + * @run --allow-net + * @resource {https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API} MDN: Fetch API + * @resource {https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream} MDN: ReadableStream + * @group Network + * + * This example demonstrates how to make a HTTP request to a server. + */ + +// To make a request to a server, you use the `fetch` API. +let resp = await fetch("https://example.com"); + +// The response is a `Response` object. This contains the status code, headers, +// and the body. +console.log(resp.status); // 200 +console.log(resp.headers.get("Content-Type")); // "text/html" +console.log(await resp.text()); // "Hello, World!" + +// The response body can also be read as JSON, an ArrayBuffer, or a Blob. A body +// can be read only once. +resp = await fetch("https://example.com"); +await resp.arrayBuffer(); +/** or await resp.json(); */ +/** or await resp.blob(); */ + +// The response body can also be streamed in chunks. +resp = await fetch("https://example.com"); +for await (const chunk of resp.body!) { + console.log("chunk", chunk); +} + +// When making a request, you can also specify the method, headers, and a body. +const body = `{"name": "Deno"}`; +resp = await fetch("https://example.com", { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-API-Key": "foobar", + }, + body, +}); + +// `fetch` also accepts a `Request` object instead of URL + options. +const req = new Request("https://example.com", { + method: "DELETE", +}); +resp = await fetch(req); + +// Instead of a string, the body can also be any typed array, blob, or +// a URLSearchParams object. +const url = "https://example.com"; +new Request(url, { + method: "POST", + body: new Uint8Array([1, 2, 3]), +}); +new Request(url, { + method: "POST", + body: new Blob(["Hello, World!"]), +}); +new Request(url, { + method: "POST", + body: new URLSearchParams({ "foo": "bar" }), +}); + +// Forms can also be sent with `fetch` by using a `FormData` object as the body. +const formData = new FormData(); +formData.append("name", "Deno"); +formData.append("file", new Blob(["Hello, World!"]), "hello.txt"); +resp = await fetch("https://example.com", { + method: "POST", + body: formData, +}); + +// Fetch also supports streaming the request body. +const bodyStream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode("Hello, World!")); + controller.close(); + }, +}); +resp = await fetch("https://example.com", { + method: "POST", + body: bodyStream, +}); diff --git a/by-example/http-server-files.ts b/by-example/http-server-files.ts new file mode 100644 index 000000000..cc7e5e681 --- /dev/null +++ b/by-example/http-server-files.ts @@ -0,0 +1,43 @@ +/** + * @title HTTP Server: Serving Files + * @difficulty intermediate + * @tags cli, deploy + * @run --allow-net --allow-read + * @resource {https://deno.land/api?s=Deno.serve} Doc: Deno.serve + * @resource {$std/http/file_server.ts} Doc: std/http/file_server + * @resource {/http-server} Example: HTTP Server: Hello World + * @group Network + * + * An example of a HTTP server that serves files. + */ + +// Import utility methods for serving files with mime types. +import { serveDir, serveFile } from "jsr:@std/http/file-server"; + +// Here we start a simple server +Deno.serve((req: Request) => { + // Get the path from the url (ie. example.com/whatever -> /whatever) + const pathname = new URL(req.url).pathname; + + if (pathname === "/simple_file") { + // In the most basic case we can just call this function with the + // request object and path to the file + return serveFile(req, "./path/to/file.txt"); + } + + if (pathname.startsWith("/static")) { + // We can also serve a whole directory using the serveDir utility + // method. By default it serves the current directory but this + // can be changed using the "fsRoot" option. We can use the "urlRoot" + // option to strip off the start of the url in the case we don't + // serve the directory at the top level. + return serveDir(req, { + fsRoot: "public", + urlRoot: "static", + }); + } + + return new Response("404: Not Found", { + status: 404, + }); +}); diff --git a/by-example/http-server-routing.ts b/by-example/http-server-routing.ts new file mode 100644 index 000000000..6933f8b52 --- /dev/null +++ b/by-example/http-server-routing.ts @@ -0,0 +1,35 @@ +/** + * @title HTTP Server: Routing + * @difficulty intermediate + * @tags cli, deploy + * @run --allow-net + * @resource {/http-server} Example: HTTP Server: Hello World + * @resource {https://developer.mozilla.org/en-US/docs/Web/API/URL_Pattern_API} MDN: URL Pattern API + * @playground https://dash.deno.com/playground/example-routing + * @group Network + * + * An example of a HTTP server that handles requests with different responses + * based on the incoming URL. + */ + +// URL patterns can be used to match request URLs. They can contain named groups +// that can be used to extract parts of the URL, e.g. the book ID. +const BOOK_ROUTE = new URLPattern({ pathname: "/books/:id" }); + +function handler(req: Request): Response { + // Match the incoming request against the URL patterns. + const match = BOOK_ROUTE.exec(req.url); + // If there is a match, extract the book ID and return a response. + if (match) { + const id = match.pathname.groups.id; + return new Response(`Book ${id}`); + } + + // If there is no match, return a 404 response. + return new Response("Not found (try /books/1)", { + status: 404, + }); +} + +// To start the server on the default port, call `Deno.serve` with the handler. +Deno.serve(handler); diff --git a/by-example/http-server-streaming.ts b/by-example/http-server-streaming.ts new file mode 100644 index 000000000..b5e53ff9f --- /dev/null +++ b/by-example/http-server-streaming.ts @@ -0,0 +1,44 @@ +/** + * @title HTTP Server: Streaming + * @difficulty intermediate + * @tags cli, deploy + * @run --allow-net + * @resource {/http-server} Example: HTTP Server: Hello World + * @resource {https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream} MDN: ReadableStream + * @playground https://dash.deno.com/playground/example-streaming + * @group Network + * + * An example HTTP server that streams a response back to the client. + */ + +function handler(_req: Request): Response { + // Set up a variable to store a timer ID, and the ReadableStream. + let timer: number | undefined = undefined; + const body = new ReadableStream({ + // When the stream is first created, start an interval that will emit a + // chunk every second containing the current time. + start(controller) { + timer = setInterval(() => { + const message = `It is ${new Date().toISOString()}\n`; + controller.enqueue(new TextEncoder().encode(message)); + }, 1000); + }, + // If the stream is closed (the client disconnects), cancel the interval. + cancel() { + if (timer !== undefined) { + clearInterval(timer); + } + }, + }); + + // Return a response with the stream as the body. + return new Response(body, { + headers: { + "content-type": "text/plain", + "x-content-type-options": "nosniff", + }, + }); +} + +// To start the server on the default port, call `Deno.serve` with the handler. +Deno.serve(handler); diff --git a/by-example/http-server-websocket.ts b/by-example/http-server-websocket.ts new file mode 100644 index 000000000..52209b19f --- /dev/null +++ b/by-example/http-server-websocket.ts @@ -0,0 +1,41 @@ +/** + * @title HTTP Server: WebSockets + * @difficulty intermediate + * @tags cli, deploy + * @run --allow-net + * @resource {/http-server} Example: HTTP Server: Hello World + * @resource {https://developer.mozilla.org/en-US/docs/Web/API/WebSocket} MDN: WebSocket + * @group Network + * + * An example of a HTTP server that handles websocket requests. + */ + +// To start the server on the default port, call `Deno.serve` with the handler. +Deno.serve((req) => { + // First, we verify if the client is negotiating to upgrade to websockets. + // If not, we can give a status of 501 to specify we don't support plain + // http requests. + if (req.headers.get("upgrade") != "websocket") { + return new Response(null, { status: 501 }); + } + + // We can then upgrade the request to a websocket + const { socket, response } = Deno.upgradeWebSocket(req); + + // We now have access to a standard websocket object. + // Let's handle the "open" event + socket.addEventListener("open", () => { + console.log("a client connected!"); + }); + + // We can also handle messages in a similar way. Here we set up + // a simple ping / pong example. + socket.addEventListener("message", (event) => { + if (event.data === "ping") { + socket.send("pong"); + } + }); + + // Lastly we return the response created from upgradeWebSocket. + return response; +}); diff --git a/by-example/http-server.ts b/by-example/http-server.ts new file mode 100644 index 000000000..81df77759 --- /dev/null +++ b/by-example/http-server.ts @@ -0,0 +1,22 @@ +/** + * @title HTTP Server: Hello World + * @difficulty intermediate + * @tags cli, deploy + * @run --allow-net + * @resource {https://deno.land/api?s=Deno.serve} Doc: Deno.serve + * @resource {https://developer.mozilla.org/en-US/docs/Web/API/Response} MDN: Response + * @playground https://dash.deno.com/playground/example-helloworld + * @group Network + * + * An example of a HTTP server that serves a "Hello World" message. + */ + +// HTTP servers need a handler function. This function is called for every +// request that comes in. It must return a `Response`. The handler function can +// be asynchronous (it may return a `Promise`). +function handler(_req: Request): Response { + return new Response("Hello, World!"); +} + +// To start the server on the default port, call `Deno.serve` with the handler. +Deno.serve(handler); diff --git a/by-example/import-export.ts b/by-example/import-export.ts new file mode 100644 index 000000000..de7b862a3 --- /dev/null +++ b/by-example/import-export.ts @@ -0,0 +1,41 @@ +/** + * @title Importing & Exporting + * @difficulty beginner + * @tags cli, deploy + * @run + * @resource {/dependency-management} Example: Dependency Management + * @resource {https://docs.deno.com/runtime/manual/basics/modules} Manual: Modules + * @group Basics + * + * To build composable programs, it is necessary to be able to import and export + * functions from other modules. This is accomplished by using ECMA script + * modules in Deno. + */ + +// File: ./util.ts + +// To export a function, you use the export keyword. +export function sayHello(thing: string) { + console.log(`Hello, ${thing}!`); +} + +// You can also export types, variables, and classes. +// deno-lint-ignore no-empty-interface +export interface Foo {} +export class Bar {} +export const baz = "baz"; + +// File: ./main.ts + +// To import things from files other files can use the import keyword. +import { sayHello } from "./util.ts"; +sayHello("World"); + +// You can also import all exports from a file. +import * as util from "./util.ts"; +util.sayHello("World"); + +// Imports don't have to be relative, they can also reference absolute file or +// https URLs. +import { VERSION } from "https://deno.land/std/version.ts"; +console.log(VERSION); diff --git a/by-example/importing-json.ts b/by-example/importing-json.ts new file mode 100644 index 000000000..647484243 --- /dev/null +++ b/by-example/importing-json.ts @@ -0,0 +1,29 @@ +/** + * @title Importing JSON + * @difficulty beginner + * @tags cli, web + * @run + * @group Encoding + * + * JSON files can be imported in JS and TS files using the `import` keyword. + * This makes including static data in a library much easier. + */ + +// File: ./main.ts + +// JSON files can be imported in JS and TS modules. When doing so, you need to +// specify the "json" import assertion type. +import file from "./version.json" with { type: "json" }; +console.log(file.version); + +// Dynamic imports are also supported. +const module = await import("./version.json", { + with: { type: "json" }, +}); +console.log(module.default.version); + +/* File: ./version.json +{ + "version": "1.0.0" +} +*/ diff --git a/by-example/kv-watch.ts b/by-example/kv-watch.ts new file mode 100644 index 000000000..9b6f4ce8c --- /dev/null +++ b/by-example/kv-watch.ts @@ -0,0 +1,65 @@ +/** + * @title Deno KV Watch + * @difficulty intermediate + * @tags cli, deploy + * @run --unstable + * @resource {https://docs.deno.com/deploy/kv/manual} Deno KV user guide + * @resource {https://deno.land/api?unstable=&s=Deno.Kv} Deno KV Runtime API docs + * @group Databases + * + * Deno KV watch allows you to detect changes to your KV database, making + * it easier to build real-time applications, newsfeeds, chat, and more. + */ + +// Open the default database +const kv = await Deno.openKv(); + +// Set "counter" value to 0, then increment every second. +await kv.set(["counter"], new Deno.KvU64(0n)); +setInterval(() => { + kv.atomic().sum(["counter"], 1n).commit(); +}, 1000); + +// Listen for changes to "counter" and log value. +for await (const [entry] of kv.watch([["counter"]])) { + console.log(`Counter: ${entry.value}`); +} + +// You can also create a stream reader from kv.watch, which returns +// a ReadableStream. +const stream = kv.watch([["counter"]]).getReader(); +while (true) { + const counter = await stream.read(); + if (counter.done) { + break; + } + console.log(`Counter: ${counter.value[0].value}`); +} + +// To use server-sent events, let's create a server that +// responds with a stream. Each time a change to "counter" +// is detected, send the updated value to the client. +Deno.serve((_req) => { + const stream = kv.watch([["counter"]]).getReader(); + const body = new ReadableStream({ + async start(controller) { + while (true) { + if ((await stream.read()).done) { + return; + } + const data = await kv.get(["counter"]); + controller.enqueue( + new TextEncoder().encode(`Counter: ${data.value}\n`), + ); + } + }, + cancel() { + stream.cancel(); + }, + }); + return new Response(body, { + headers: { + "Content-Type": "text/event-stream", + }, + }); +}); diff --git a/by-example/kv.ts b/by-example/kv.ts new file mode 100644 index 000000000..29d150b16 --- /dev/null +++ b/by-example/kv.ts @@ -0,0 +1,84 @@ +/** + * @title Deno KV: Key/Value Database + * @difficulty intermediate + * @tags cli, deploy + * @run --unstable + * @resource {https://docs.deno.com/deploy/kv/manual} Deno KV user guide + * @resource {https://deno.land/api?unstable=&s=Deno.Kv} Deno KV Runtime API docs + * @group Databases + * + * Deno KV is a key/value database built in to the Deno runtime, and works with + * zero configuration on Deno Deploy. It's great for use cases that require fast + * reads and don't require the query flexibility of a SQL database. + */ + +// Open the default database +const kv = await Deno.openKv(); + +// Define an interface in TypeScript for our data +enum Rank { + Bronze, + Silver, + Gold, +} + +interface Player { + username: string; + rank: Rank; +} + +// Create a few instances for testing +const player1: Player = { username: "carlos", rank: Rank.Bronze }; +const player2: Player = { username: "briana", rank: Rank.Silver }; +const player3: Player = { username: "alice", rank: Rank.Bronze }; + +// Store object data in Deno KV using the "set" operation. Keys can be arranged +// hierarchically, not unlike resources in a REST API. +await kv.set(["players", player1.username], player1); +await kv.set(["players", player2.username], player2); +await kv.set(["players", player3.username], player3); + +// The "set" operation is used to both create and update data for a given key +player3.rank = Rank.Gold; +await kv.set(["players", player3.username], player3); + +// Fetch a single object by key with the "get" operation +const record = await kv.get(["players", "alice"]); +const alice: Player = record.value as Player; +console.log(record.key, record.versionstamp, alice); + +// Fetch several objects by key with "getMany" +const [record1, record2] = await kv.getMany([ + ["players", "carlos"], + ["players", "briana"], +]); +console.log(record1, record2); + +// List several records by key prefix - note that results are ordered +// lexicographically, so our players will be fetched in the order +// "alice", "briana", "carlos" +const records = kv.list({ prefix: ["players"] }); +const players = []; +for await (const res of records) { + players.push(res.value as Player); +} +console.log(players); + +// Delete a value for a given key +await kv.delete(["players", "carlos"]); + +// The Deno.KvU64 object is a wrapper for 64 bit integers (BigInt), so you can +// quickly update very large numbers. Let's add a "score" for alice. +const aliceScoreKey = ["scores", "alice"]; +await kv.set(aliceScoreKey, new Deno.KvU64(0n)); + +// Add 10 to the player's score in an atomic transaction +await kv.atomic() + .mutate({ + type: "sum", + key: aliceScoreKey, + value: new Deno.KvU64(10n), + }) + .commit(); +const newScore = (await kv.get(aliceScoreKey)).value; +console.log("Alice's new score is: ", newScore); diff --git a/by-example/mongo.ts b/by-example/mongo.ts new file mode 100644 index 000000000..69db6ca44 --- /dev/null +++ b/by-example/mongo.ts @@ -0,0 +1,45 @@ +/** + * @title Connect to MongoDB + * @difficulty intermediate + * @tags cli, deploy + * @run --allow-net --allow-sys --allow-read + * @resource {https://deno.land/x/mongo} Deno MongoDB on deno.land/x + * @group Databases + * + * Using the Deno MongoDB client, you can connect to a Mongo database + * running anywhere. + */ + +import { MongoClient } from "npm:mongodb@6.1.0"; + +// Create a new instance of the MongoDB client running locally on port 27017 +const client = new MongoClient("mongodb://127.0.0.1:27017"); + +// Connect to the MongoDB server +await client.connect(); + +// Define the schema for the collection +interface DinosaurSchema { + name: string; + skills: string[]; +} + +// Access the database +const db = client.db("animals"); + +// Access the collection within the database +const dinosaurs = db.collection("dinosaurs"); + +// Insert a new document into the collection +await dinosaurs.insertOne({ + name: "deno", + skills: ["dancing", "hiding"], +}); + +// Find all documents in the collection with the filter +const allDinosaurs = await dinosaurs.find({ name: "deno" }).toArray(); + +console.log(allDinosaurs); + +// Close the MongoDB client connection +client.close(); diff --git a/by-example/moving-renaming-files.ts b/by-example/moving-renaming-files.ts new file mode 100644 index 000000000..832fb230b --- /dev/null +++ b/by-example/moving-renaming-files.ts @@ -0,0 +1,38 @@ +/** + * @title Moving/Renaming Files + * @difficulty beginner + * @tags cli + * @run --allow-read=./ --allow-write=./ + * @resource {https://deno.land/api?s=Deno.rename} Doc: Deno.rename + * @group File System + * + * An example of how to move and rename files and directories in Deno. + */ + +// To rename or move a file, you can use the `Deno.rename` function. The first +// argument is the path to the file to rename. The second argument is the new +// path. +await Deno.writeTextFile("./hello.txt", "Hello World!"); +await Deno.rename("./hello.txt", "./hello-renamed.txt"); +console.log(await Deno.readTextFile("./hello-renamed.txt")); + +// If the source file or the destination directory does not exist, the function +// will reject the returned promise with a `Deno.errors.NotFound` error. You can +// catch this error with a `try/catch` block. +try { + await Deno.rename("./hello.txt", "./does/not/exist"); +} catch (err) { + console.error(err); +} + +// A synchronous version of this function is also available. +Deno.renameSync("./hello-renamed.txt", "./hello-again.txt"); + +// If the destination file already exists, it will be overwritten. +await Deno.writeTextFile("./hello.txt", "Invisible content."); +await Deno.rename("./hello-again.txt", "./hello.txt"); +console.log(await Deno.readTextFile("./hello.txt")); + +// Read and write permissions are necessary to perform this operation. The +// source file needs to be readable and the destination path needs to be +// writable. diff --git a/by-example/node.ts b/by-example/node.ts new file mode 100644 index 000000000..f83f25cf5 --- /dev/null +++ b/by-example/node.ts @@ -0,0 +1,19 @@ +/** + * @title Use Node.js built-in modules + * @difficulty beginner + * @tags cli, deploy + * @run --allow-env + * @resource {https://docs.deno.com/runtime/manual/node} Node.js / npm support in Deno + * @resource {https://docs.deno.com/runtime/manual/node/node_specifiers} node: specifiers + * @group Basics + * + * Deno supports most built-in Node.js modules natively - you can include them + * in your code using "node:" specifiers in your imports. + */ + +// Import the os module from core Node to get operating system info +import os from "node:os"; + +// Use the module as you would in Node.js +console.log("Current architecture is:", os.arch()); +console.log("Home directory is:", os.homedir()); diff --git a/by-example/npm.ts b/by-example/npm.ts new file mode 100644 index 000000000..4d7e95ace --- /dev/null +++ b/by-example/npm.ts @@ -0,0 +1,29 @@ +/** + * @title Import modules from npm + * @difficulty beginner + * @tags cli + * @run --allow-net --allow-read --allow-env + * @resource {https://docs.deno.com/runtime/manual/node} Node.js / npm support in Deno + * @resource {https://docs.deno.com/runtime/manual/node/npm_specifiers} npm: specifiers + * @resource {https://www.npmjs.com/package/express} express module on npm + * @group Basics + * + * Use JavaScript modules from npm in your Deno programs with the "npm:" + * specifier in your imports. + */ + +// Import the express module from npm using an npm: prefix, and appending a +// version number. Dependencies from npm can be configured in an import map +// also. +import express from "npm:express@4.18.2"; + +// Create an express server +const app = express(); + +// Configure a route that will process HTTP GET requests +app.get("/", (_req, res) => { + res.send("Welcome to the Dinosaur API!"); +}); + +// Start an HTTP server using the configured Express app +app.listen(3000); diff --git a/by-example/os-signals.ts b/by-example/os-signals.ts new file mode 100644 index 000000000..f2527bb20 --- /dev/null +++ b/by-example/os-signals.ts @@ -0,0 +1,39 @@ +/** + * @title Handling OS Signals + * @difficulty beginner + * @tags cli + * @run + * @resource {https://deno.land/api?s=Deno.addSignalListener} Doc: Deno.addSignalListener + * @group System + * + * You can listen for OS signals using the `Deno.addSignalListener` function. + * This allows you to do things like gracefully shutdown a server when a + * `SIGINT` signal is received. + */ + +console.log("Counting seconds..."); + +let i = 0; + +// We isolate the signal handler function so that we can remove it later. +function sigIntHandler() { + console.log("interrupted! your number was", i); + Deno.exit(); +} + +// Then, we can listen for the `SIGINT` signal, +// which is sent when the user presses Ctrl+C. +Deno.addSignalListener("SIGINT", sigIntHandler); + +// While we're waiting for the signal, we can do other things, +// like count seconds, or start a server. +const interval = setInterval(() => { + i++; +}, 1000); + +// And, after 10 seconds, we can exit and remove the signal listener. +setTimeout(() => { + clearInterval(interval); + Deno.removeSignalListener("SIGINT", sigIntHandler); + console.log("done! it has been 10 seconds"); +}, 10_000); diff --git a/by-example/parsing-serializing-csv.ts b/by-example/parsing-serializing-csv.ts new file mode 100644 index 000000000..ab39cd3b4 --- /dev/null +++ b/by-example/parsing-serializing-csv.ts @@ -0,0 +1,59 @@ +/** + * @title Parsing and serializing CSV + * @difficulty beginner + * @tags cli, deploy, web + * @run + * @resource {/import-export} Example: Importing & Exporting + * @resource {https://datatracker.ietf.org/doc/html/rfc4180} Spec: CSV + * @group Encoding + * + * CSV is a data serialization format that is designed to be portable for table-like applications. + */ +import { parse, stringify } from "jsr:@std/csv"; + +// To parse a CSV string, you can use the the standard library's CSV +// parse function. The value is returned as a JavaScript object. +let text = ` +url,views,likes +https://deno.land,10,7 +https://deno.land/x,20,15 +https://deno.dev,30,23 +`; +let data = parse(text, { + skipFirstRow: true, + strip: true, +}); +console.log(data[0].url); // https://deno.land +console.log(data[0].views); // 10 +console.log(data[0].likes); // 7 + +// In the case where our CSV is formatted differently, we are also able to +// provide the columns through code. +text = ` +https://deno.land,10,7 +https://deno.land/x,20,15 +https://deno.dev,30,23 +`; +data = parse(text, { + columns: ["url", "views", "likes"], +}); +console.log(data[0].url); // https://deno.land +console.log(data[0].views); // 10 +console.log(data[0].likes); // 7 + +// To turn a list of JavaScript object into a CSV string, you can use the +// standard library's CSV stringify function. +const obj = [ + { mascot: "dino", fans: { old: 100, new: 200 } }, + { mascot: "bread", fans: { old: 5, new: 2 } }, +]; +const csv = stringify(obj, { + columns: [ + "mascot", + ["fans", "new"], + ], +}); +console.log(csv); +//- mascot,new +//- dino,200 +//- bread,2 diff --git a/by-example/parsing-serializing-json.ts b/by-example/parsing-serializing-json.ts new file mode 100644 index 000000000..7dd9c75b0 --- /dev/null +++ b/by-example/parsing-serializing-json.ts @@ -0,0 +1,44 @@ +/** + * @title Parsing and serializing JSON + * @difficulty beginner + * @tags cli, deploy, web + * @run + * @resource {https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON} MDN: JSON + * @group Encoding + * + * JSON is a widely used data interchange format. It is a human-readable, but + * also easily machine-readable. + */ + +// To parse a JSON string, you can use the builtin JSON.parse function. The +// value is returned as a JavaScript object. +const text = `{ + "hello": "world", + "numbers": [1, 2, 3] +}`; +const data = JSON.parse(text); +console.log(data.hello); +console.log(data.numbers.length); + +// To turn a JavaScript object into a JSON string, you can use the builtin +// JSON.stringify function. +const obj = { + hello: "world", + numbers: [1, 2, 3], +}; +const json = JSON.stringify(obj); +console.log(json); +//- {"hello":"world","numbers":[1,2,3]} + +// By default JSON.stringify will output a minified JSON string. You can +// customize this by specifying an indentation number in the third argument. +const json2 = JSON.stringify(obj, null, 2); +console.log(json2); +//- { +//- "hello": "world", +//- "numbers": [ +//- 1, +//- 2, +//- 3 +//- ] +//- } diff --git a/by-example/parsing-serializing-toml.ts b/by-example/parsing-serializing-toml.ts new file mode 100644 index 000000000..ab7b55958 --- /dev/null +++ b/by-example/parsing-serializing-toml.ts @@ -0,0 +1,51 @@ +/** + * @title Parsing and serializing TOML + * @difficulty beginner + * @tags cli, deploy, web + * @run + * @resource {/import-export} Example: Importing & Exporting + * @resource {https://toml.io} Spec: TOML + * @group Encoding + * + * TOML is a widely used configuration language designed to be feature-rich and intuitive to write. + */ +import { parse, stringify } from "jsr:@std/toml"; + +// To parse a TOML string, you can use the the standard library's TOML +// parse function. The value is returned as a JavaScript object. +const text = ` +int = 1_000_000 +bool = true + +[[bin]] +name = "deno" +path = "cli/main.rs" + +[[bin]] +name = "deno_core" +path = "src/foo.rs" +`; +const data = parse(text); +console.log(data.int); +console.log(data.bin.length); + +// To turn a JavaScript object into a TOML string, you can use the standard +// library's TOML stringify function. +const obj = { + ping: "pong", + complex: [ + { name: "bob", age: 10 }, + { name: "alice", age: 12 }, + ], +}; +const toml = stringify(obj); +console.log(toml); +//- ping = "pong" +//- +//- [[complex]] +//- name = "bob" +//- age = 10 +//- +//- [[complex]] +//- name = "alice" +//- age = 12 diff --git a/by-example/parsing-serializing-yaml.ts b/by-example/parsing-serializing-yaml.ts new file mode 100644 index 000000000..a90bdb3cc --- /dev/null +++ b/by-example/parsing-serializing-yaml.ts @@ -0,0 +1,38 @@ +/** + * @title Parsing and serializing YAML + * @difficulty beginner + * @tags cli, deploy, web + * @run + * @resource {/import-export} Example: Importing & Exporting + * @resource {https://yaml.org} Spec: YAML + * @group Encoding + * + * YAML is a widely used data serialization language designed to be easily human readable and writeable. + */ +import { parse, stringify } from "jsr:@std/yaml"; + +// To parse a YAML string, you can use the the standard library's YAML +// parse function. The value is returned as a JavaScript object. +const text = ` +foo: bar +baz: + - qux + - quux +`; +const data = parse(text); +console.log(data.foo); +console.log(data.baz.length); + +// To turn a JavaScript object into a YAML string, you can use the standard +// library's YAML stringify function. +const obj = { + hello: "world", + numbers: [1, 2, 3], +}; +const yaml = stringify(obj); +console.log(yaml); +//- hello: word +//- numbers: +//- - 1 +//- - 2 +//- - 3 diff --git a/by-example/path-operations.ts b/by-example/path-operations.ts new file mode 100644 index 000000000..8b22e30f5 --- /dev/null +++ b/by-example/path-operations.ts @@ -0,0 +1,56 @@ +/** + * @title Path operations + * @difficulty beginner + * @tags cli + * @run --allow-read + * @resource {$std/path} Deno: std/path + * @resource {https://deno.land/api?s=Deno.cwd} Deno: Deno.cwd + * @group File System + * + * Many applications need to manipulate file paths in one way or another. + * The Deno standard library provides simple utilities for this. + */ + +// First we will import the module from the Deno standard library +import * as path from "jsr:@std/path"; + +// Converting from a file url to a directory can be done simply by the `fromFileUrl` +// method from the appropriate implementation. +const p1 = path.posix.fromFileUrl("file:///home/foo"); +const p2 = path.win32.fromFileUrl("file:///home/foo"); +console.log(`Path 1: ${p1} Path 2: ${p2}`); + +// We can also choose to not specify and automatically use whatever Deno is running on +const p3 = path.fromFileUrl("file:///home/foo"); +console.log(`Path on current OS: ${p3}`); + +// We can get the last part of a file path using the basename method +const p = path.basename("./deno/is/awesome/mod.ts"); +console.log(p); // mod.ts + +// We can get the directory of a file path using the dirname method +const base = path.dirname("./deno/is/awesome/mod.ts"); +console.log(base); // ./deno/is/awesome + +// We can get the extension of a file path using the extname method +const ext = path.extname("./deno/is/awesome/mod.ts"); +console.log(ext); // .ts + +// We can format a path using a FormatInputPathObject +const formatPath = path.format({ + root: "/", + dir: "/home/user/dir", + ext: ".html", + name: "index", +}); +console.log(formatPath); // "/home/user/dir/index.html" + +// When we want to make our code cross-platform, we can use the join method. +// This joins any number of string by the OS-specific file separator. On +// Mac OS this would be foo/bar. On windows, this would be foo\bar. +const joinPath = path.join("foo", "bar"); +console.log(joinPath); + +// We can get the current working directory using the built-in cwd method +const current = Deno.cwd(); +console.log(current); diff --git a/by-example/permissions.ts b/by-example/permissions.ts new file mode 100644 index 000000000..57bcbb0ad --- /dev/null +++ b/by-example/permissions.ts @@ -0,0 +1,48 @@ +/** + * @title Permission Management + * @difficulty beginner + * @tags cli + * @run + * @resource {https://deno.land/api?s=Deno.Permissions} Doc: Deno.Permissions + * @group CLI + * + * There are times where depending on the state of permissions + * granted to a process, we want to do different things. This is + * made very easy to do with the Deno permissions API. + */ + +// In the most simple case, we can just request a permission by it's name. +// In this case, we ask for --allow-env and prompt the user. The user will +// not be prompted if it was already allowed in the past and not revoked. +let status = await Deno.permissions.request({ name: "env" }); +if (status.state === "granted") { + console.log("'env' permission is granted."); +} else { + console.log("'env' permission is denied."); +} + +// There are also synchronous versions of all the permission APIs +status = Deno.permissions.requestSync({ name: "env" }); +if (status.state === "granted") { + console.log("'env' permission is granted."); +} else { + console.log("'env' permission is denied."); +} + +// We can also query permissions without asking for them. In this case, +// we are querying whether or not we have the read permission. Not only +// can we query whether we have a permission or not, we can even specify +// what directories we have permissions in using the path option. +const readStatus = await Deno.permissions.query({ + name: "read", + path: "/etc", +}); +console.log(readStatus.state); + +// In the case that we no longer need a permission, it is also possible +// to revoke a process's access to that permission. This is useful when +// a process starts running untrusted code. +import { assert } from "$std/assert/assert.ts"; + +const runStatus = await Deno.permissions.revoke({ name: "run" }); +assert(runStatus.state !== "granted"); diff --git a/by-example/pid.ts b/by-example/pid.ts new file mode 100644 index 000000000..d9870d3db --- /dev/null +++ b/by-example/pid.ts @@ -0,0 +1,13 @@ +/** + * @title Process Information + * @difficulty beginner + * @tags cli + * @run + * @group System + */ + +// The current process's process ID is available in the `Deno.pid` variable. +console.log(Deno.pid); + +// The parent process ID is available in the Deno namespace too. +console.log(Deno.ppid); diff --git a/by-example/piping-streams.ts b/by-example/piping-streams.ts new file mode 100644 index 000000000..7581b993a --- /dev/null +++ b/by-example/piping-streams.ts @@ -0,0 +1,53 @@ +/** + * @title Piping Streams + * @difficulty intermediate + * @tags cli + * @run --allow-net --allow-read --allow-write + * @resource {https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream} MDN: ReadableStream + * @resource {/tcp-listener.ts} Example: TCP Listener + * @group Network + * + * Deno implements web-standard streams which comes with many advantages. One of the most useful + * features of streams is how they can be piped to and from different sources. + */ + +// A common usecase for streams is downloading files without buffering the whole file in memory. +// First, we open a file we want to write to +const download = await Deno.open("example.html", { create: true, write: true }); + +// Now let's make a request to a web page +const req = await fetch("https://examples.deno.land"); + +// We can pipe this response straight into the file. In a more realistic example, we would +// handle a bad request, but here we'll just use the nullish coalescing operator. Instead of +// opening the file and manually piping the fetch body to it, we could also just use `Deno.writeFile`. +req.body?.pipeTo(download.writable); + +// Pipes can connect a lot of things. For example, we could pipe the file we just downloaded into +// stdout. We could also pipe our streams through transformers to get a more interesting result. In this +// case, we will pipe our file through a stream which will highlight all "<" characters in the terminal. + +// First we will import a utility from the standard library to help us with this. +import { bgBrightYellow } from "jsr:@std/fmt/colors"; + +// Then we will create a transform stream utility class +class HighlightTransformStream extends TransformStream { + constructor() { + super({ + transform: (chunk, controller) => { + controller.enqueue(chunk.replaceAll("<", bgBrightYellow("<"))); + }, + }); + } +} + +// Let's open our file for reading +const example = await Deno.open("example.html", { read: true }); + +// Now we can pipe the result from the file, into a TextDecoderStream, into our custom transform class, +// back through a TextEncoderStream, and finally into stdout +await example.readable + .pipeThrough(new TextDecoderStream()) + .pipeThrough(new HighlightTransformStream()) + .pipeThrough(new TextEncoderStream()) + .pipeTo(Deno.stdout.writable); diff --git a/by-example/postgres.ts b/by-example/postgres.ts new file mode 100644 index 000000000..2069ae68d --- /dev/null +++ b/by-example/postgres.ts @@ -0,0 +1,32 @@ +/** + * @title Connect to Postgres + * @difficulty intermediate + * @tags cli, deploy + * @run --allow-net --allow-env + * @resource {https://deno-postgres.com/} Deno Postgres docs + * @resource {https://deno.land/x/postgres} Deno Postgres on deno.land/x + * @group Databases + * + * Using the Deno Postgres client, you can connect to a Postgres database + * running anywhere. + */ + +// Import the Client constructor from deno.land/x +import { Client } from "https://deno.land/x/postgres@v0.17.0/mod.ts"; + +// Initialize the client with connection information for your database, and +// create a connection. +const client = new Client({ + user: "user", + database: "test", + hostname: "localhost", + port: 5432, +}); +await client.connect(); + +// Execute a SQL query +const result = await client.queryArray("SELECT ID, NAME FROM PEOPLE"); +console.log(result.rows); // [[1, 'Carlos'], [2, 'John'], ...] + +// Close the connection to the database +await client.end(); diff --git a/by-example/prompts.ts b/by-example/prompts.ts new file mode 100644 index 000000000..6e7946ed8 --- /dev/null +++ b/by-example/prompts.ts @@ -0,0 +1,32 @@ +/** + * @title Input Prompts + * @difficulty beginner + * @tags cli, web + * @run + * @resource {https://developer.mozilla.org/en-US/docs/Web/API/Window/prompt} MDN: prompt + * @resource {https://developer.mozilla.org/en-US/docs/Web/API/Window/alert} MDN: alert + * @resource {https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm} MDN: confirm + * @group CLI + * + * Prompts are used to ask the user for input or feedback on actions. + */ + +// The most basic way to interact with the user is by alerting them, and waiting +// for them to acknowledge by pressing [Enter]. +alert("Please acknowledge the message."); +console.log("The message has been acknowledged."); + +// Instead of just an acknowledgement, we can also ask the user for a yes/no +// response. +const shouldProceed = confirm("Do you want to proceed?"); +console.log("Should proceed?", shouldProceed); + +// We can also prompt the user for some text input. If the user cancels the +// prompt, the returned value will be `null`. +const name = prompt("Please enter your name:"); +console.log("Name:", name); + +// When prompting you can also specify a default value to use if the user +// cancels the prompt. +const age = prompt("Please enter your age:", "18"); +console.log("Age:", age); diff --git a/by-example/queues.ts b/by-example/queues.ts new file mode 100644 index 000000000..52be0cd3c --- /dev/null +++ b/by-example/queues.ts @@ -0,0 +1,48 @@ +/** + * @title Deno Queues + * @difficulty intermediate + * @tags cli, deploy + * @run --unstable + * @resource {https://docs.deno.com/deploy/kv/manual/queue_overview} Deno Queues user guide + * @resource {https://deno.land/api?s=Deno.Kv&unstable=} Deno Queues Runtime API docs + * @group Scheduled Tasks + * + * Deno Queues, built on Deno KV, allow you to offload parts of your application + * or schedule work for the future to run asynchronously. It's an easy way to add + * scalable background processing to your project. + */ + +// Describe the shape of your message object (optional) +interface Notification { + forUser: string; + body: string; +} + +// Get a reference to a KV instance +const kv = await Deno.openKv(); + +// Create a notification object +const message: Notification = { + forUser: "alovelace", + body: "You've got mail!", +}; + +// Enqueue the message for immediate delivery +await kv.enqueue(message); + +// Enqueue the message for delivery in 3 days +const delay = 1000 * 60 * 60 * 24 * 3; +await kv.enqueue(message, { delay }); + +// Retrieve an unsent message by configuring a key +const backupKey = ["failed_notifications", "alovelace", Date.now()]; +await kv.enqueue(message, { keysIfUndelivered: [backupKey] }); +// ... disaster strikes ... +// Get the unsent message +const r = await kv.get(backupKey); +console.log("Found failed notification for:", r.value?.forUser); + +// Listen for and handle messages. +kv.listenQueue((msg: Notification) => { + console.log(`Dear ${msg.forUser}: ${msg.body}`); +}); diff --git a/by-example/reading-files.ts b/by-example/reading-files.ts new file mode 100644 index 000000000..b83d425bc --- /dev/null +++ b/by-example/reading-files.ts @@ -0,0 +1,57 @@ +/** + * @title Reading Files + * @difficulty beginner + * @tags cli, deploy + * @run --allow-read + * @resource {https://deno.land/api?s=Deno.readFile} Doc: Deno.readFile + * @resource {https://deno.land/api?s=Deno.open} Doc: Deno.open + * @resource {https://deno.land/api?s=Deno.FsFile} Doc: Deno.FsFile + * @group File System + * + * Many applications need to read files from disk. Deno provides a simple + * interface for reading files. + */ + +// The easiest way to read a file is to just read the entire contents into +// memory as bytes. +// deno-lint-ignore no-unused-vars +const bytes = await Deno.readFile("hello.txt"); + +// Instead of reading the file as bytes, there is a convenience function to +// read the file as a string. +// deno-lint-ignore no-unused-vars +const text = await Deno.readTextFile("hello.txt"); + +// Often you need more control over when what parts of the file are read. +// For this you start by opening a file to get a `Deno.FsFile` object. +const file = await Deno.open("hello.txt"); + +// Read some bytes from the beginning of the file. Allow up to 5 to be read but +// also note how many actually were read. +const buffer = new Uint8Array(5); +const bytesRead = await file.read(buffer); +console.log(`Read ${bytesRead} bytes`); + +// You can also seek to a known location in the file and read from there. +const pos = await file.seek(6, Deno.SeekMode.Start); +console.log(`Seeked to position ${pos}`); +const buffer2 = new Uint8Array(2); +const bytesRead2 = await file.read(buffer2); +console.log(`Read ${bytesRead2} bytes`); + +// You can use rewind back to the start using seek as well. +await file.seek(0, Deno.SeekMode.Start); + +// Make sure to close the file when you are done. +file.close(); + +// Synchronous reading is also supported. +Deno.readFileSync("hello.txt"); +Deno.readTextFileSync("hello.txt"); +const f = Deno.openSync("hello.txt"); +f.seekSync(6, Deno.SeekMode.Start); +const buf = new Uint8Array(5); +f.readSync(buf); +f.close(); + +// The `--allow-read` permission is required to read files. diff --git a/by-example/redis.ts b/by-example/redis.ts new file mode 100644 index 000000000..2094c7921 --- /dev/null +++ b/by-example/redis.ts @@ -0,0 +1,33 @@ +/** + * @title Connect to Redis + * @difficulty intermediate + * @tags cli, deploy + * @run --allow-net --allow-env + * @resource {https://deno.land/x/r2d2} r2d2 on deno.land/x + * @resource {https://redis.io/docs/getting-started/} Getting started with Redis + * @group Databases + * + * Using the r2d2 module, you can connect to a Redis database running anywhere. + */ + +// Import the `sendCommand()` function from r2d2 +import { sendCommand } from "https://deno.land/x/r2d2/mod.ts"; + +// Create a TCP connection with the Redis server +const redisConn = await Deno.connect({ port: 6379 }); + +// Authenticate with the server by sending the command "AUTH " +await sendCommand(redisConn, [ + "AUTH", + Deno.env.get("REDIS_USERNAME")!, + Deno.env.get("REDIS_PASSWORD")!, +]); + +// Set the "hello" key to have value "world" using the command "SET hello world" +await sendCommand(redisConn, ["SET", "hello", "world"]); // "OK" + +// Get the "hello" key using the command "GET hello" +await sendCommand(redisConn, ["GET", "hello"]); // "world" + +// Close the connection to the database +redisConn.close(); diff --git a/by-example/streaming-files.ts b/by-example/streaming-files.ts new file mode 100644 index 000000000..97868c6d7 --- /dev/null +++ b/by-example/streaming-files.ts @@ -0,0 +1,54 @@ +/** + * @title Streaming File Operations + * @difficulty intermediate + * @tags cli + * @run --allow-read --allow-write + * @resource {https://deno.land/api?s=Deno.open} Doc: Deno.open + * @resource {https://deno.land/api?s=Deno.FsFile} Doc: Deno.FsFile + * @group File System + * + * Sometimes we need more granular control over file operations. + * Deno provides a low level interface to file operations that + * may be used for this purpose. + */ + +// For our first example we will demonstrate using a writeable stream. +// To handle any low level file operations we must first open the file. +const output = await Deno.open("example.txt", { + create: true, + append: true, +}); + +// We are now able to obtain a writer from the output. +// This automatically handles closing the file when done. +const outputWriter = output.writable.getWriter(); + +// Before we do anything with the writer, we should make sure it's ready. +await outputWriter.ready; + +// Let's write some text to the file +const outputText = "I love Deno!"; +const encoded = new TextEncoder().encode(outputText); +await outputWriter.write(encoded); + +// Now we close the write stream (and the file) +await outputWriter.close(); + +// For our next example, let's read the text from the file! +const input = await Deno.open("example.txt"); + +// Let's get a reader from the input +const inputReader = input.readable.getReader(); +const decoder = new TextDecoder(); + +// Let's read each chunk from the file +// and print it to the console. +let done = false; +do { + const result = await inputReader.read(); + done = result.done; + + if (result.value) { + console.log(`Read chunk: ${decoder.decode(result.value)}`); + } +} while (!done); diff --git a/by-example/subprocesses-output.ts b/by-example/subprocesses-output.ts new file mode 100644 index 000000000..095f35031 --- /dev/null +++ b/by-example/subprocesses-output.ts @@ -0,0 +1,37 @@ +/** + * @title Subprocesses: Collecting Output + * @difficulty intermediate + * @tags cli + * @run --allow-run + * @resource {https://deno.land/api?s=Deno.Command} Doc: Deno.Command + * @group System + * + * We don't often write programs in isolation. In a lot of cases we want + * to interact with the outside system and spawning a subprocess is a + * common way to do this. + */ + +// The Deno namespace has a unified api for interacting with the outside system +// called Deno.Command. With it, we can initialize some information about the +// command but it will not be executed immediately. +const command = new Deno.Command("deno", { + args: [ + "eval", + "\ + console.log('hello from deno'); \ + console.error('hello from stderr'); \ + ", + ], +}); + +// In the most simple case we just want to run the process to completion. This +// can be achieved using command.output() +let result = await command.output(); + +// It can also be achieved synchronously using command.outputSync() +result = command.outputSync(); + +// We can now interact with stdout and stderr +const textDecoder = new TextDecoder(); +console.log("stdout:", textDecoder.decode(result.stdout)); +console.log("stderr:", textDecoder.decode(result.stderr)); diff --git a/by-example/subprocesses-spawn.ts b/by-example/subprocesses-spawn.ts new file mode 100644 index 000000000..d59209cf5 --- /dev/null +++ b/by-example/subprocesses-spawn.ts @@ -0,0 +1,41 @@ +/** + * @title Subprocesses: Spawning + * @difficulty intermediate + * @tags cli + * @run --allow-run + * @resource {https://deno.land/api?s=Deno.Command} Doc: Deno.Command + * @group System + * + * For more complex usecases, we don't simply want the output of some + * command. In this case, we can spawn a subprocess and interact with + * it. + */ + +// The Deno namespace has a unified api for interacting with the outside system +// called Deno.Command. With it, we can initialize some information about the +// command but it will not be executed immediately. +const command = new Deno.Command("deno", { + args: [ + "fmt", + "-", + ], + stdin: "piped", + stdout: "piped", +}); + +// In a slightly more complex case, we want to interact with a spawned +// process. To do this, we first need to spawn it. +const process = command.spawn(); + +// We can now pipe the input into stdin. To do this we must first get +// a writer from the stream and write to it +const writer = process.stdin.getWriter(); +writer.write(new TextEncoder().encode("console.log('hello')")); +writer.releaseLock(); + +// We must then close stdin +await process.stdin.close(); + +// We can now wait for the process to output the results +const result = await process.output(); +console.log(new TextDecoder().decode(result.stdout)); diff --git a/by-example/symlinks.ts b/by-example/symlinks.ts new file mode 100644 index 000000000..24f2d2fc5 --- /dev/null +++ b/by-example/symlinks.ts @@ -0,0 +1,30 @@ +/** + * @title Creating & Resolving Symlinks + * @difficulty beginner + * @tags cli + * @run --allow-write --allow-read + * @resource {https://deno.land/api?s=Deno.writeTextFile} Doc: Deno.writeTextFile + * @resource {https://deno.land/api?s=Deno.symlink} Doc: Deno.symlink + * @group File System + * + * Creating and resolving symlink is a common task. Deno has a number of + * functions for this task. + */ + +// First we will create a text file to link to. +await Deno.writeTextFile("example.txt", "hello from symlink!"); + +// Now we can create a soft link to the file +await Deno.symlink("example.txt", "link"); + +// To resolve the path of a symlink, we can use Deno.realPath +console.log(await Deno.realPath("link")); + +// Symlinks are automatically resolved, so we can just read +// them like text files +console.log(await Deno.readTextFile("link")); + +// In certain cases, soft links don't work. In this case we +// can choose to make "hard links". +await Deno.link("example.txt", "hardlink"); +console.log(await Deno.readTextFile("hardlink")); diff --git a/by-example/tcp-connector.ts b/by-example/tcp-connector.ts new file mode 100644 index 000000000..5681c343f --- /dev/null +++ b/by-example/tcp-connector.ts @@ -0,0 +1,22 @@ +/** + * @title TCP Connector: Ping + * @difficulty intermediate + * @tags cli + * @run --allow-net + * @resource {https://deno.land/api?s=Deno.connect} Doc: Deno.connect + * @resource {/tcp-listener.ts} Example: TCP Listener + * @group Network + * + * An example of connecting to a TCP server on localhost and writing a 'ping' message to the server. + */ + +// Instantiate an instance of text encoder to write to the TCP stream. +const encoder = new TextEncoder(); +// Establish a connection to our TCP server that is currently being run on localhost port 8080. +const conn = await Deno.connect({ + hostname: "127.0.0.1", + port: 8080, + transport: "tcp", +}); +// Encode the 'ping' message and write to the TCP connection for the server to receive. +await conn.write(encoder.encode("ping")); diff --git a/by-example/tcp-listener.ts b/by-example/tcp-listener.ts new file mode 100644 index 000000000..75c854af9 --- /dev/null +++ b/by-example/tcp-listener.ts @@ -0,0 +1,31 @@ +/** + * @title TCP Listener: Ping + * @difficulty intermediate + * @tags cli + * @run --allow-net + * @resource {https://deno.land/api?s=Deno.listen} Doc: Deno.listen + * @group Network + * + * An example of a TCP listener on localhost that will log the message if written to and close the connection if connected to. + */ + +// Instantiate an instance of text decoder to read the TCP stream bytes back into plaintext. +const decoder = new TextDecoder(); + +// Instantiate an instance of a TCP listener on localhost port 8080. +const listener = Deno.listen({ + hostname: "127.0.0.1", + port: 8080, + transport: "tcp", +}); +// Await asynchronous connections that are established to our TCP listener. +for await (const conn of listener) { + // Instantiate an buffer array to store the contents of our read TCP stream. + const buf = new Uint8Array(1024); + // Read the contents of the TCP stream into our buffer array. + await conn.read(buf); + // Here we log the results of the bytes that were read into our buffer array. + console.log("Server - received: ", decoder.decode(buf)); + // We close the connection that was established. + conn.close(); +} diff --git a/by-example/temporary-files.ts b/by-example/temporary-files.ts new file mode 100644 index 000000000..230ba9d23 --- /dev/null +++ b/by-example/temporary-files.ts @@ -0,0 +1,53 @@ +/** + * @title Temporary Files & Directories + * @difficulty beginner + * @tags cli + * @run --allow-read --allow-write + * @resource {https://deno.land/api?s=Deno.makeTempFile} Doc: Deno.makeTempFile + * @resource {https://deno.land/api?s=Deno.makeTempDir} Doc: Deno.makeTempDir + * @group File System + * + * Temporary files and directories are used to store data that is not intended + * to be permanent. For example, as a local cache of downloaded data. + */ + +// The `Deno.makeTempFile()` function creates a temporary file in the default +// temporary directory and returns the path to the file. +const tempFilePath = await Deno.makeTempFile(); +console.log("Temp file path:", tempFilePath); +await Deno.writeTextFile(tempFilePath, "Hello world!"); +const data = await Deno.readTextFile(tempFilePath); +console.log("Temp file data:", data); + +// A custom prefix and suffix for the temporary file can be specified. +const tempFilePath2 = await Deno.makeTempFile({ + prefix: "logs_", + suffix: ".txt", +}); +console.log("Temp file path 2:", tempFilePath2); + +// The directory that temporary files are created in can be customized too. +// Here we use a relative ./tmp directory. +await Deno.mkdir("./tmp", { recursive: true }); +const tempFilePath3 = await Deno.makeTempFile({ + dir: "./tmp", +}); +console.log("Temp file path 3:", tempFilePath3); + +// A temporary directory can also be created. +const tempDirPath = await Deno.makeTempDir(); +console.log("Temp dir path:", tempDirPath); + +// It has the same prefix, suffix, and directory options as `makeTempFile()`. +const tempDirPath2 = await Deno.makeTempDir({ + prefix: "logs_", + suffix: "_folder", + dir: "./tmp", +}); +console.log("Temp dir path 2:", tempDirPath2); + +// Synchronous versions of the above functions are also available. +const tempFilePath4 = Deno.makeTempFileSync(); +const tempDirPath3 = Deno.makeTempDirSync(); +console.log("Temp file path 4:", tempFilePath4); +console.log("Temp dir path 3:", tempDirPath3); diff --git a/by-example/timers.ts b/by-example/timers.ts new file mode 100644 index 000000000..a3ac40f43 --- /dev/null +++ b/by-example/timers.ts @@ -0,0 +1,26 @@ +/** + * @title Timeouts & Intervals + * @difficulty beginner + * @tags cli, deploy, web + * @run + * @resource {https://developer.mozilla.org/en-US/docs/Web/API/Window/setTimeout} MDN: setTimeout + * @resource {https://developer.mozilla.org/en-US/docs/Web/API/Window/setInterval} MDN: setInterval + * @group Basics + * + * Timers are used to schedule functions to happen at a later time. + */ + +// Here we create a timer that will print "Hello, World!" to the console after +// 1 second (1000 milliseconds). +setTimeout(() => console.log("Hello, World!"), 1000); + +// You can also cancel a timer after it has been created. +const timerId = setTimeout(() => console.log("No!"), 1000); +clearTimeout(timerId); + +// Intervals can be created to repeat a function at a regular interval. +setInterval(() => console.log("Hey!"), 1000); + +// Intervals can also be cancelled. +const intervalId = setInterval(() => console.log("Nope"), 1000); +clearInterval(intervalId); diff --git a/by-example/tls-connector.ts b/by-example/tls-connector.ts new file mode 100644 index 000000000..f23d30cb3 --- /dev/null +++ b/by-example/tls-connector.ts @@ -0,0 +1,29 @@ +/** + * @title TCP/TLS Connector: Ping + * @difficulty intermediate + * @tags cli + * @run --allow-net --allow-read + * @resource {https://deno.land/api?s=Deno.connectTls} Doc: Deno.connectTls + * @resource {/tls-listener.ts} Example: TCP/TLS Listener + * @group Network + * + * An example of connecting to a TCP server using TLS on localhost and writing a 'ping' message to the server. + */ + +// Read a CA Certificate from the file system +const caCert = await Deno.readTextFile("./root.pem"); + +// Establish a connection to our TCP server using TLS that is currently being run on localhost port 443. +// We use a custom CA root certificate here. If we remove this option, Deno defaults to using +// Mozilla's root certificates. +const conn = await Deno.connectTls({ + hostname: "127.0.0.1", + port: 443, + caCerts: [caCert], +}); + +// Instantiate an instance of text encoder to write to the TCP stream. +const encoder = new TextEncoder(); + +// Encode the 'ping' message and write to the TCP connection for the server to receive. +await conn.write(encoder.encode("ping")); diff --git a/by-example/tls-listener.ts b/by-example/tls-listener.ts new file mode 100644 index 000000000..14d5bbe1e --- /dev/null +++ b/by-example/tls-listener.ts @@ -0,0 +1,28 @@ +/** + * @title TCP/TLS Listener: Ping + * @difficulty intermediate + * @tags cli + * @run --allow-net --allow-read + * @resource {https://deno.land/api?s=Deno.listenTls} Doc: Deno.listenTls + * @group Network + * + * 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. + */ + +// Instantiate an instance of a TCP listener on localhost port 443. +const listener = Deno.listenTls({ + hostname: "127.0.0.1", + port: 443, + transport: "tcp", + cert: Deno.readTextFileSync("./server.crt"), + key: Deno.readTextFileSync("./server.key"), +}); + +// Await asynchronous connections that are established to our TCP listener. +for await (const conn of listener) { + // Pipe the contents of the TCP stream into stdout + await conn.readable.pipeTo(Deno.stdout.writable); + + // We close the connection that was established. + conn.close(); +} diff --git a/by-example/typescript.ts b/by-example/typescript.ts new file mode 100644 index 000000000..90ca6b2e4 --- /dev/null +++ b/by-example/typescript.ts @@ -0,0 +1,26 @@ +/** + * @title Built-in TypeScript support + * @difficulty beginner + * @tags cli, deploy + * @run + * @resource {https://www.typescriptlang.org/docs/handbook/intro.html} TypeScript handbook + * @group Basics + * + * Deno natively understands TypeScript code with no compiler to configure. + * Start writing code in .ts files, and the runtime will work with them just + * fine. + */ + +// Define an interface in TypeScript +interface Person { + name: string; + age: number; +} + +// Provide a typed input to a function +function greet(person: Person) { + return "Hello, " + person.name + "!"; +} + +// Everything works with zero config! +console.log(greet({ name: "Alice", age: 36 })); diff --git a/by-example/udp-connector.ts b/by-example/udp-connector.ts new file mode 100644 index 000000000..83714fa63 --- /dev/null +++ b/by-example/udp-connector.ts @@ -0,0 +1,31 @@ +/** + * @title UDP Connector: Ping + * @difficulty intermediate + * @tags cli + * @run --allow-net --unstable + * @resource {https://deno.land/api?s=Deno.connect} Doc: Deno.connect + * @resource {/udp-listener.ts} Example: UDP Listener + * @group Network + * + * An example of writing a 'ping' message to a UDP server on localhost. + */ + +// Instantiate an instance of text encoder to write to the UDP stream. +const encoder = new TextEncoder(); + +// Create a UDP listener to allow us to send a ping to the other UDP server. +const listener = Deno.listenDatagram({ + port: 10001, + transport: "udp", +}); + +// Since UDP is a connectionless protocol, we need to define the address of the listener +const peerAddress: Deno.NetAddr = { + transport: "udp", + hostname: "127.0.0.1", + port: 10000, +}; + +// Encode the 'ping' message and write to the UDP connection for the server to receive. +await listener.send(encoder.encode("ping"), peerAddress); +listener.close(); diff --git a/by-example/udp-listener.ts b/by-example/udp-listener.ts new file mode 100644 index 000000000..c1e10f269 --- /dev/null +++ b/by-example/udp-listener.ts @@ -0,0 +1,33 @@ +/** + * @title UDP Listener: Ping + * @difficulty intermediate + * @tags cli + * @run --allow-net --unstable + * @resource {https://deno.land/api?s=Deno.listenDatagram} Doc: Deno.listenDatagram + * @resource {/udp-connector.ts} Example: UDP Connector + * @group Network + * + * An example of a UDP listener on localhost that will log the message + * if written to and close the connection if connected to. + */ + +// Instantiate an instance of text decoder to read the UDP stream bytes back into plaintext. +const decoder = new TextDecoder(); + +// Instantiate an instance of a UDP listener on localhost port 10000. +const listener = Deno.listenDatagram({ + port: 10000, + transport: "udp", +}); + +// Await asynchronous messages that are sent to our UDP listener. +for await (const [data, address] of listener) { + // Here we log the address of the sender of the data + console.log("Server - received information from", address); + + // Here we log the results of the bytes that were read into our buffer array. + console.log("Server - received:", decoder.decode(data)); + + // We close the connection that was established. + listener.close(); +} diff --git a/by-example/ulid.ts b/by-example/ulid.ts new file mode 100644 index 000000000..40d83dd0c --- /dev/null +++ b/by-example/ulid.ts @@ -0,0 +1,40 @@ +/** + * @title ULID + * @difficulty beginner + * @tags cli, deploy + * @run + * @resource {https://github.com/ulid/spec} ULID: Specification + * @group Cryptography + * + * One common need for distributed systems are identifiers. ULIDs are a universally + * unique lexicographically sortable identifier with some nice properties. They are + * 128-bit values, encoded as 26 character strings which also encode the timestamp. + * They play very nicely with Deno KV. + */ + +// The standard library contains a function for generating ULIDs. +import { ulid } from "jsr:@std/ulid"; + +// To generate a ULID, simply call the function. +console.log(ulid()); +console.log(ulid()); +console.log(ulid()); + +// ULIDs can also be generated from a timestamp. This is useful for migrating from +// another system. +const timestamp = Date.now(); +console.log(ulid(timestamp)); +console.log(ulid(timestamp)); +console.log(ulid(timestamp)); + +// Given a ULID, you can get the timestamp back out +import { decodeTime } from "$std/ulid/mod.ts"; +const myULID = ulid(); +console.log(decodeTime(myULID)); + +// Optionally, if you're not on a distributed system and want monotonic ULIDs, +// you can use the monotonic ULID generator instead. +import { monotonicUlid } from "$std/ulid/mod.ts"; +console.log(monotonicUlid(150000)); // 000XAL6S41ACTAV9WEVGEMMVR8 +console.log(monotonicUlid(150000)); // 000XAL6S41ACTAV9WEVGEMMVR9 +console.log(monotonicUlid(150000)); // 000XAL6S41ACTAV9WEVGEMMVRA diff --git a/by-example/url-parsing.ts b/by-example/url-parsing.ts new file mode 100644 index 000000000..4c9661c82 --- /dev/null +++ b/by-example/url-parsing.ts @@ -0,0 +1,39 @@ +/** + * @title Manipulating & Parsing URLs + * @difficulty beginner + * @tags cli, deploy, web + * @run + * @resource {https://developer.mozilla.org/en-US/docs/Web/API/URL} MDN: URL + * @group Basics + * + * URL is the web standard interface to parse and manipulate URLs. + */ + +// We can create a new object in a variety of ways +// In the most simple case we can simply just write the whole url +let url = new URL("https://deno.land/manual/introduction"); + +// Alternatively we are able to pass a (relative) url which will +// be automatically resolved to an absolute url +url = new URL("/manual/introduction", "https://deno.land"); + +// To get the full url out of an object, we can check the href property +console.log(url.href); // https://deno.land/manual/introduction + +// We are also able to get various other properties from the url. +// Here are a few examples of properties we have access to. +console.log(url.host); // deno.land +console.log(url.origin); // https://deno.land +console.log(url.pathname); // /manual/introduction +console.log(url.protocol); // https: + +// When parsing a url we often need to read the search parameters. +url = new URL("https://deno.land/api?s=Deno.readFile"); + +console.log(url.searchParams.get("s")); // Deno.readFile + +// We're able to manipulate any of these parameters on the fly +url.host = "deno.com"; +url.protocol = "http:"; + +console.log(url.href); // http://deno.com/api?s=Deno.readFile diff --git a/by-example/uuids.ts b/by-example/uuids.ts new file mode 100644 index 000000000..15dee2903 --- /dev/null +++ b/by-example/uuids.ts @@ -0,0 +1,34 @@ +/** + * @title Generating & Validating UUIDs + * @difficulty beginner + * @tags cli, deploy, web + * @run + * @resource {https://developer.mozilla.org/en-US/docs/Web/API/Crypto/randomUUID} MDN: crypto.randomUUID + * @resource {$std/uuid/mod.ts} Doc: std/uuid + * @group Cryptography + * + * UUIDs (universally unique identifier) can be used to uniquely identify some + * object or data. + */ + +// A random UUID can be generated using the builtin Web Cryptography API. This +// type of UUID is also known as UUID v4. +const myUUID = crypto.randomUUID(); +console.log("Random UUID:", myUUID); + +// The standard library contains some more functions for working with UUIDs. +import * as uuid from "jsr:@std/uuid"; + +// You can validate that a given string is a valid UUID. +console.log(uuid.validate("not a UUID")); // false +console.log(uuid.validate("6ec0bd7f-11c0-43da-975e-2a8ad9ebae0b")); // true + +// You can also generate a time-based (v1) UUID. By default this uses system +// time as the time source. +console.log(uuid.v1.generate()); + +// SHA-1 namespaced (v5) UUIDs can also be generated. For this you need +// to specify a namespace and data: +const NAMESPACE_URL = "6ba7b810-9dad-11d1-80b4-00c04fd430c8"; +const data = new TextEncoder().encode("deno.land"); +console.log(await uuid.v5.generate(NAMESPACE_URL, data)); diff --git a/by-example/walking-directories.ts b/by-example/walking-directories.ts new file mode 100644 index 000000000..5e1cc6229 --- /dev/null +++ b/by-example/walking-directories.ts @@ -0,0 +1,35 @@ +/** + * @title Walking directories + * @difficulty beginner + * @tags cli + * @run --allow-read + * @resource {https://deno.land/api?s=Deno.readDir} Doc: Deno.readDir + * @resource {$std/fs/walk.ts} Doc: std/walk + * @group File System + * + * When doing something like filesystem routing, it is + * useful to be able to walk down a directory to visit + * files. + */ + +// If the directory has no depth (no folders), we can use +// the built-in Deno.readDir +for await (const dirEntry of Deno.readDir(".")) { + console.log("Basic listing:", dirEntry.name); +} + +// If on the other hand you need to recursively walk +// a repository, the standard library has a method for this. +// In the most simple case it is a drop-in replacement +import { walk } from "jsr:@std/fs/walk"; + +for await (const dirEntry of walk(".")) { + console.log("Recursive walking:", dirEntry.name); +} + +// We are also able to specify some settings to customize +// our results. In the case of building filesystem routing +// limiting results to only certain extensions may be useful +for await (const dirEntry of walk(".", { exts: ["ts"] })) { + console.log("Recursive walking with extension:", dirEntry.name); +} diff --git a/by-example/watching-files.ts b/by-example/watching-files.ts new file mode 100644 index 000000000..090e44244 --- /dev/null +++ b/by-example/watching-files.ts @@ -0,0 +1,41 @@ +/** + * @title Watching the filesystem + * @difficulty beginner + * @tags cli + * @run --allow-read + * @resource {https://deno.land/api?s=Deno.watchFs} Doc: Deno.watchFs + * @resource {https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of} MDN: for await of + * @resource {$std/async/debounce.ts} Doc: std/debounce + * @group File System + * + * When creating frameworks or CLI tools, it is often necessary to watch the filesystem for changes. + */ +// The easiest way to watch a filesystem is to use the Deno builtin watchFs. +// Deno.watchFs returns an FsWatcher which is an async iterable. +let watcher = Deno.watchFs("./"); + +// The easiest way to interact with async iterables is the javascript for await of syntax. +for await (const event of watcher) { + console.log(">>>> event", event); + + // To stop the watcher we can simply call `watcher.close()` + watcher.close(); +} + +// In real applications, it is quite rare that an application needs to react +// to every change instantly. Events will be duplicated and multiple events will +// be dispatched for the same changes. To get around this, we can "debounce" our +// functions. +import { debounce } from "jsr:@std/async/debounce"; + +// In this specific case, we use the standard library to do the work for us. +// This function will run at most once every two hundred milliseconds +const log = debounce((event: Deno.FsEvent) => { + console.log("[%s] %s", event.kind, event.paths[0]); +}, 200); + +watcher = Deno.watchFs("./"); + +for await (const event of watcher) { + log(event); +} diff --git a/by-example/web-workers.ts b/by-example/web-workers.ts new file mode 100644 index 000000000..2b9b2134c --- /dev/null +++ b/by-example/web-workers.ts @@ -0,0 +1,61 @@ +/** + * @title Web Workers + * @difficulty intermediate + * @tags cli, web + * @resource {https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers} MDN: Web Workers + * @resource {https://deno.land/manual@v1.30.0/runtime/workers} Manual: Workers + * @group Advanced + * + * Workers are the only way of running javascript off of the main thread. + * This can be useful for a wide variety of programs, especially those where + * there is a lot of computation that needs to be done without blocking a + * thread. + */ + +// File: ./worker.ts + +// First we will define a web worker, we can receive messages from the main +// thread and do some processing as a result of them. +self.onmessage = async (e) => { + const { filename } = e.data; + const text = await Deno.readTextFile(filename); + console.log(text); + self.close(); +}; + +// File: ./main.ts + +// Currently, Deno only supports module-type workers. To instantiate one +// we can use similar syntax to what is found on the web. +const worker = new Worker( + new URL("./worker.ts", import.meta.url).href, + { + type: "module", + }, +); + +// We can send a message to the worker by using the `postMessage` method +worker.postMessage({ filename: "./log.txt" }); + +// By default the worker inherits the permissions of the thread it was +// instantiated from. We can give workers certain permissions by using +// the deno.permissions option. +const worker2 = new Worker( + new URL("./worker.ts", import.meta.url).href, + { + type: "module", + deno: { + permissions: { + read: [ + new URL("./file_1.txt", import.meta.url), + new URL("./file_2.txt", import.meta.url), + ], + }, + }, + }, +); + +// Because we instantiated this worker with specific permissions, this will +// cause the worker to try to read a file it doesn't have access to and thus +// will throw a permission error. +worker2.postMessage({ filename: "./log.txt" }); diff --git a/by-example/webassembly.ts b/by-example/webassembly.ts new file mode 100644 index 000000000..ab3196a4b --- /dev/null +++ b/by-example/webassembly.ts @@ -0,0 +1,35 @@ +/** + * @title Web Assembly + * @difficulty intermediate + * @tags cli, deploy, web + * @run + * @resource {https://webassembly.github.io/spec/core/syntax/modules.html} WebAssembly Spec: Modules + * @resource {https://webassembly.github.io/spec/core/syntax/instructions.html} WebAssembly Spec: Instructions + * @resource {https://webassembly.github.io/spec/core/syntax/values.html} WebAssembly Spec: Values + * @resource {https://webassembly.github.io/spec/core/syntax/types.html} WebAssembly Spec: Types + * @group Advanced + * + * WebAssembly is a binary format for describing a program's data and instructions. + * It is a new and more efficient binary format. + */ + +// We create a new Uint8Array with the bytes of the WebAssembly module. +// This is usually the output of some compiler and not written by hand. +// deno-fmt-ignore +const bytes = new Uint8Array([ + 0,97,115,109,1,0,0,0,1,7,1,96,2, + 127,127,1,127,2,1,0,3,2,1,0,4,1, + 0,5,1,0,6,1,0,7,7,1,3,97,100,100, + 0,0,9,1,0,10,10,1,8,0,32,0,32,1, + 106,15,11,11,1,0, +]); +// We create an interface for the WebAssembly module containing all exports. +interface WebAssemblyExports { + add(a: number, b: number): number; +} +// The WebAssembly module is a binary format for describing a program's data and instructions. +const exports = await WebAssembly.instantiate(bytes); +// We get the exports from the WebAssembly module and cast it to the interface. +const functions = exports.instance.exports as unknown as WebAssemblyExports; +// We call the exported function. +console.log(functions.add(1, 2)); // 3 diff --git a/by-example/websocket.ts b/by-example/websocket.ts new file mode 100644 index 000000000..1d1af507e --- /dev/null +++ b/by-example/websocket.ts @@ -0,0 +1,37 @@ +/** + * @title Outbound WebSockets + * @difficulty beginner + * @tags cli, deploy, web + * @run --allow-net + * @resource {/http-server-websocket} HTTP Server: WebSockets + * @resource {https://developer.mozilla.org/en-US/docs/Web/API/WebSocket} MDN: WebSocket + * @group Network + * + * Opening a WebSocket connection for real-time, bi-directional communication with Deno is very simple. + */ + +// First we need to use the WebSocket constructor to initiate +// our connection to an external server +const socket = new WebSocket("ws://localhost:8000"); + +// Before we do anything with the websocket, we should +// wait to make sure that we are connected. We can do +// so by listening to the "open" event. +socket.addEventListener("open", () => { + // We can read the "ready state" of our instance. + // This determines if we are able to send messages. + // The ready state for open should be 1. + console.log(socket.readyState); + + // We can now send messages to the server. The messages + // can be of the type string, ArrayBuffer, Blob, or + // TypedArray. In this case we will be sending a string + socket.send("ping"); +}); + +// We can handle messages back from the server by listening +// to the "message" event. We can read the data sent by +// the server using the data property of the event. +socket.addEventListener("message", (event) => { + console.log(event.data); +}); diff --git a/by-example/writing-files.ts b/by-example/writing-files.ts new file mode 100644 index 000000000..a48600d64 --- /dev/null +++ b/by-example/writing-files.ts @@ -0,0 +1,44 @@ +/** + * @title Writing Files + * @difficulty beginner + * @tags cli + * @run --allow-read --allow-write + * @resource {https://deno.land/api?s=Deno.writeFile} Doc: Deno.writeFile + * @resource {https://deno.land/api?s=Deno.create} Doc: Deno.create + * @resource {https://deno.land/api?s=Deno.FsFile} Doc: Deno.FsFile + * @resource {https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder} MDN: TextEncoder + * @group File System + * + * Many applications need to write files to disk. Deno provides a simple + * interface for writing files. + */ + +// The easiest way to write a file, is to dump an entire buffer into the file at +// once. +const bytes = new Uint8Array([72, 101, 108, 108, 111]); +await Deno.writeFile("hello.txt", bytes, { mode: 0o644 }); + +// You can also write a string instead of a byte array. +await Deno.writeTextFile("hello.txt", "Hello World"); + +// Synchronous writing is also supported. +Deno.writeFileSync("hello.txt", bytes); +Deno.writeTextFileSync("hello.txt", "Hello World"); + +// For more granular writes, open a new file for writing. +const file = await Deno.create("hello.txt"); + +// You can write chunks of data to the file. +const written = await file.write(bytes); +console.log(`${written} bytes written.`); + +// A `file.write` returns the number of bytes written, as it might not write all +// bytes passed. We can get a Writer instead to make sure the entire buffer is written. +const writer = file.writable.getWriter(); +await writer.write(new TextEncoder().encode("World!")); + +// Closing the writer automatically closes the file. +// If you don't use a writer, make sure to close the file after you are done with it. +await writer.close(); + +// The `--allow-write` permission is required to write files. diff --git a/by-example/writing-tests.ts b/by-example/writing-tests.ts new file mode 100644 index 000000000..2794402e7 --- /dev/null +++ b/by-example/writing-tests.ts @@ -0,0 +1,61 @@ +/** + * @title Writing Tests + * @difficulty beginner + * @tags cli + * @run deno test --allow-read --allow-write + * @resource {https://deno.land/api?s=Deno.test} Doc: Deno.test + * @resource {$std/testing/asserts.ts} Doc: std/testing/asserts + * @group CLI + * + * One of the most common tasks in developing software is writing tests for + * existing code. Deno has a built-in test runner which makes this very easy. + */ + +// First, we import assert statements from the standard library. There are +// quite a few options but we will just import the most common ones here. +import { assert, assertEquals } from "jsr:@std/assert"; + +// The most simple way to use the test runner is to just pass it a description +// and a callback function +Deno.test("assert works correctly", () => { + assert(true); + assertEquals(1, 1); +}); + +// In more complex scenarios, we often need to have some setup and teardown code +// with some steps in between. This is also made simple with the built-in test runner. +Deno.test("testing steps", async (t) => { + const file = await Deno.open("example.txt", { + read: true, + write: true, + create: true, + }); + const encoder = new TextEncoder(); + const data = encoder.encode("Hello world!"); + + await t.step("write some bytes", async () => { + const bytesWritten = await file.write(data); + assertEquals(bytesWritten, data.length); + await file.seek(0, Deno.SeekMode.Start); + }); + + await t.step("read some bytes", async () => { + const buffer = new Uint8Array(data.length); + await file.read(buffer); + assertEquals(buffer, data); + }); + + file.close(); +}); + +// The test runner by default makes it very hard to shoot yourself in the foot. For +// each test, the test runner checks to make sure all resources created during the +// test are freed. There are situations where this is not useful behavior. We can use +// the more complex test definition to disable this behavior +Deno.test({ + name: "leaky test", + async fn() { + await Deno.open("example.txt"); + }, + sanitizeResources: false, +}); diff --git a/deploy/api/runtime-fs.md b/deploy/api/runtime-fs.md index 6933d634b..577071ea5 100644 --- a/deploy/api/runtime-fs.md +++ b/deploy/api/runtime-fs.md @@ -21,14 +21,14 @@ The APIs that are available are: - [Deno.realPath](#denorealpath) - [Deno.readLink](#denoreadlink) -## `Deno.cwd` +## Deno.cwd `Deno.cwd()` returns the current working directory of your deployment. It is located at the root of your deployment's root directory. For example, if you deployed via the GitHub integration, the current working directory is the root of your GitHub repository. -## `Deno.readDir` +## Deno.readDir `Deno.readDir()` allows you to list the contents of a directory. @@ -66,7 +66,7 @@ async function handler(_req) { Deno.serve(handler); ``` -## `Deno.readFile` +## Deno.readFile `Deno.readFile()` allows you to read a file fully into memory. @@ -171,7 +171,7 @@ The path provided to the to the root of the repository. You can also specify absolute paths, if they are inside `Deno.cwd`. -## `Deno.readTextFile` +## Deno.readTextFile This function is similar to [Deno.readFile](#Deno.readFile) except it decodes the file contents as a UTF-8 string. @@ -194,7 +194,7 @@ async function handler(_req) { Deno.serve(handler); ``` -## `Deno.open` +## Deno.open `Deno.open()` allows you to open a file, returning a file handle. This file handle can then be used to read the contents of the file. See @@ -229,7 +229,7 @@ async function handler(_req) { Deno.serve(handler); ``` -## `Deno.File` +## Deno.File `Deno.File` is a file handle returned from [`Deno.open()`](#denoopen). It can be used to read chunks of the file using the `read()` method. The file handle can @@ -251,7 +251,7 @@ class File { The path can be a relative or absolute. It can also be a `file:` URL. -### `Deno.File#read()` +## Deno.File#read() The read method is used to read a chunk of the file. It should be passed a buffer to read the data into. It returns the number of bytes read or `null` if @@ -261,7 +261,7 @@ the end of the file has been reached. function read(p: Uint8Array): Promise; ``` -### `Deno.File#close()` +### Deno.File#close() The close method is used to close the file handle. Closing the handle will interrupt all ongoing reads. @@ -270,7 +270,7 @@ interrupt all ongoing reads. function close(): void; ``` -## `Deno.stat` +## Deno.stat `Deno.stat()` reads a file system entry's metadata. It returns a [`Deno.FileInfo`](#fileinfo) object. Symlinks are followed. @@ -304,7 +304,7 @@ async function handler(_req) { Deno.serve(handler); ``` -## `Deno.lstat` +## Deno.lstat `Deno.lstat()` is similar to `Deno.stat()`, but it does not follow symlinks. @@ -318,7 +318,7 @@ function Deno.lstat(path: string | URL): Promise The path can be a relative or absolute. It can also be a `file:` URL. -## `Deno.FileInfo` +## Deno.FileInfo The `Deno.FileInfo` interface is used to represent a file system entry's metadata. It is returned by the [`Deno.stat()`](#denostat) and @@ -337,7 +337,7 @@ interface FileInfo { } ``` -## `Deno.realPath` +## Deno.realPath `Deno.realPath()` returns the resolved absolute path to a file after following symlinks. @@ -366,7 +366,7 @@ async function handler(_req) { Deno.serve(handler); ``` -## `Deno.readLink` +## Deno.readLink `Deno.readLink()` returns the target path for a symlink. diff --git a/docusaurus.config.js b/docusaurus.config.js index 54857942c..ef04c0ec6 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -102,6 +102,7 @@ const config = { }, }; }, + "./src/plugins/deno-by-example/plugin.ts", ], themeConfig: ({ @@ -138,6 +139,12 @@ const config = { label: "Subhosting", activeBaseRegex: `^/subhosting`, }, + { + to: "/examples", + position: "left", + label: "Examples", + activeBaseRegex: `^/examples`, + }, { href: "https://deno.land/api?unstable=true", label: "API Reference", @@ -172,14 +179,18 @@ const config = { { label: "Deno Deploy", to: "/deploy/manual", + }, + { + label: "Deno Subhosting", + to: "/subhosting/manual", }, { - label: "Standard Library", - href: "https://deno.land/std", + label: "Examples", + href: "/examples", }, { - label: "Deno by Example", - href: "https://examples.deno.land", + label: "Standard Library", + href: "https://deno.land/std", }, ], }, diff --git a/package-lock.json b/package-lock.json index a147e15e6..7d89b78b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "markdown-it": "^13.0.2", "node-polyfill-webpack-plugin": "^2.0.1", "prism-react-renderer": "^1.3.5", + "prismjs": "^1.29.0", "react": "^18.0.0", "react-dom": "^18.0.0", "unist-util-visit": "^2.0.1", @@ -34,7 +35,8 @@ "postcss": "^8.4.28", "tailwindcss": "^3.3.3", "typescript": "^4.7.4", - "unist-util-visit-parents": "^3.1.1" + "unist-util-visit-parents": "^3.1.1", + "vitest": "^1.5.3" }, "engines": { "node": ">=16.14" @@ -2742,6 +2744,374 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@hapi/hoek": { "version": "9.3.0", "license": "BSD-3-Clause" @@ -3077,6 +3447,214 @@ "openapi-types": ">=7" } }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.17.2.tgz", + "integrity": "sha512-NM0jFxY8bB8QLkoKxIQeObCaDlJKewVlIEkuyYKm5An1tdVZ966w2+MPQ2l8LBZLjR+SgyV+nRkTIunzOYBMLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.17.2.tgz", + "integrity": "sha512-yeX/Usk7daNIVwkq2uGoq2BYJKZY1JfyLTaHO/jaiSwi/lsf8fTFoQW/n6IdAsx5tx+iotu2zCJwz8MxI6D/Bw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.17.2.tgz", + "integrity": "sha512-kcMLpE6uCwls023+kknm71ug7MZOrtXo+y5p/tsg6jltpDtgQY1Eq5sGfHcQfb+lfuKwhBmEURDga9N0ol4YPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.17.2.tgz", + "integrity": "sha512-AtKwD0VEx0zWkL0ZjixEkp5tbNLzX+FCqGG1SvOu993HnSz4qDI6S4kGzubrEJAljpVkhRSlg5bzpV//E6ysTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.17.2.tgz", + "integrity": "sha512-3reX2fUHqN7sffBNqmEyMQVj/CKhIHZd4y631duy0hZqI8Qoqf6lTtmAKvJFYa6bhU95B1D0WgzHkmTg33In0A==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.17.2.tgz", + "integrity": "sha512-uSqpsp91mheRgw96xtyAGP9FW5ChctTFEoXP0r5FAzj/3ZRv3Uxjtc7taRQSaQM/q85KEKjKsZuiZM3GyUivRg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.17.2.tgz", + "integrity": "sha512-EMMPHkiCRtE8Wdk3Qhtciq6BndLtstqZIroHiiGzB3C5LDJmIZcSzVtLRbwuXuUft1Cnv+9fxuDtDxz3k3EW2A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.17.2.tgz", + "integrity": "sha512-NMPylUUZ1i0z/xJUIx6VUhISZDRT+uTWpBcjdv0/zkp7b/bQDF+NfnfdzuTiB1G6HTodgoFa93hp0O1xl+/UbA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.17.2.tgz", + "integrity": "sha512-T19My13y8uYXPw/L/k0JYaX1fJKFT/PWdXiHr8mTbXWxjVF1t+8Xl31DgBBvEKclw+1b00Chg0hxE2O7bTG7GQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.17.2.tgz", + "integrity": "sha512-BOaNfthf3X3fOWAB+IJ9kxTgPmMqPPH5f5k2DcCsRrBIbWnaJCgX2ll77dV1TdSy9SaXTR5iDXRL8n7AnoP5cg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.17.2.tgz", + "integrity": "sha512-W0UP/x7bnn3xN2eYMql2T/+wpASLE5SjObXILTMPUBDB/Fg/FxC+gX4nvCfPBCbNhz51C+HcqQp2qQ4u25ok6g==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.17.2.tgz", + "integrity": "sha512-Hy7pLwByUOuyaFC6mAr7m+oMC+V7qyifzs/nW2OJfC8H4hbCzOX07Ov0VFk/zP3kBsELWNFi7rJtgbKYsav9QQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.17.2.tgz", + "integrity": "sha512-h1+yTWeYbRdAyJ/jMiVw0l6fOOm/0D1vNLui9iPuqgRGnXA0u21gAqOyB5iHjlM9MMfNOm9RHCQ7zLIzT0x11Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.17.2.tgz", + "integrity": "sha512-tmdtXMfKAjy5+IQsVtDiCfqbynAQE/TQRpWdVataHmhMb9DCoJxp9vLcCBjEQWMiUYxO1QprH/HbY9ragCEFLA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.17.2.tgz", + "integrity": "sha512-7II/QCSTAHuE5vdZaQEwJq2ZACkBpQDOmQsE6D6XUbnBHW8IAhm4eTufL6msLJorzrHDFv3CF8oCA/hSIRuZeQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.17.2.tgz", + "integrity": "sha512-TGGO7v7qOq4CYmSBVEYpI1Y5xDuCEnbVC5Vth8mOsW0gDSzxNrVERPc790IGHsrT2dQSimgMr9Ub3Y1Jci5/8w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@sideway/address": { "version": "4.1.4", "license": "BSD-3-Clause", @@ -3691,6 +4269,90 @@ "version": "1.2.0", "license": "ISC" }, + "node_modules/@vitest/expect": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.5.3.tgz", + "integrity": "sha512-y+waPz31pOFr3rD7vWTbwiLe5+MgsMm40jTZbQE8p8/qXyBX3CQsIXRx9XK12IbY7q/t5a5aM/ckt33b4PxK2g==", + "dev": true, + "dependencies": { + "@vitest/spy": "1.5.3", + "@vitest/utils": "1.5.3", + "chai": "^4.3.10" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.5.3.tgz", + "integrity": "sha512-7PlfuReN8692IKQIdCxwir1AOaP5THfNkp0Uc4BKr2na+9lALNit7ub9l3/R7MP8aV61+mHKRGiqEKRIwu6iiQ==", + "dev": true, + "dependencies": { + "@vitest/utils": "1.5.3", + "p-limit": "^5.0.0", + "pathe": "^1.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner/node_modules/p-limit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", + "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/snapshot": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.5.3.tgz", + "integrity": "sha512-K3mvIsjyKYBhNIDujMD2gfQEzddLe51nNOAf45yKRt/QFJcUIeTQd2trRvv6M6oCBHNVnZwFWbQ4yj96ibiDsA==", + "dev": true, + "dependencies": { + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.5.3.tgz", + "integrity": "sha512-Llj7Jgs6lbnL55WoshJUUacdJfjU2honvGcAJBxhra5TPEzTJH8ZuhI3p/JwqqfnTr4PmP7nDmOXP53MS7GJlg==", + "dev": true, + "dependencies": { + "tinyspy": "^2.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.5.3.tgz", + "integrity": "sha512-rE9DTN1BRhzkzqNQO+kw8ZgfeEBCLXiHJwetk668shmNBpSagQxneT5eSqEBLP+cqSiAeecvQmbpFfdMyLcIQA==", + "dev": true, + "dependencies": { + "diff-sequences": "^29.6.3", + "estree-walker": "^3.0.3", + "loupe": "^2.3.7", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/@webassemblyjs/ast": { "version": "1.11.6", "license": "MIT", @@ -3878,8 +4540,9 @@ } }, "node_modules/acorn-walk": { - "version": "8.3.1", - "license": "MIT", + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", "engines": { "node": ">=0.4.0" } @@ -4096,6 +4759,15 @@ "util": "^0.12.5" } }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/astring": { "version": "1.8.6", "license": "MIT", @@ -4523,6 +5195,15 @@ "node": ">= 0.8" } }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/cacheable-lookup": { "version": "7.0.0", "license": "MIT", @@ -4641,6 +5322,24 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/chai": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", + "integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.0.8" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/chalk": { "version": "4.1.2", "license": "MIT", @@ -4694,6 +5393,18 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, "node_modules/cheerio": { "version": "1.0.0-rc.12", "license": "MIT", @@ -4988,6 +5699,12 @@ "version": "0.0.1", "license": "MIT" }, + "node_modules/confbox": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.7.tgz", + "integrity": "sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==", + "dev": true + }, "node_modules/config-chain": { "version": "1.1.13", "license": "MIT", @@ -5558,6 +6275,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/deep-extend": { "version": "0.6.0", "license": "MIT", @@ -5731,6 +6460,15 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/diffie-hellman": { "version": "5.0.3", "license": "MIT", @@ -5956,6 +6694,44 @@ "version": "1.4.1", "license": "MIT" }, + "node_modules/esbuild": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" + } + }, "node_modules/escalade": { "version": "3.1.1", "license": "MIT", @@ -6752,6 +7528,15 @@ "node": ">=6.9.0" } }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/get-intrinsic": { "version": "1.2.2", "license": "MIT", @@ -8275,6 +9060,22 @@ "node": ">=8.9.0" } }, + "node_modules/local-pkg": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz", + "integrity": "sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==", + "dev": true, + "dependencies": { + "mlly": "^1.4.2", + "pkg-types": "^1.0.3" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/locate-path": { "version": "7.2.0", "license": "MIT", @@ -8344,6 +9145,15 @@ "loose-envify": "cli.js" } }, + "node_modules/loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.1" + } + }, "node_modules/lower-case": { "version": "2.0.2", "license": "MIT", @@ -8368,6 +9178,15 @@ "yallist": "^3.0.2" } }, + "node_modules/magic-string": { + "version": "0.30.10", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz", + "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + } + }, "node_modules/markdown-extensions": { "version": "2.0.0", "license": "MIT", @@ -10592,6 +11411,18 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/mlly": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.0.tgz", + "integrity": "sha512-U9SDaXGEREBYQgfejV97coK0UL1r+qnF2SyO9A3qcI8MzKnsIFKHNVEkrDyNncQTKQQumsasmeq84eNMdBfsNQ==", + "dev": true, + "dependencies": { + "acorn": "^8.11.3", + "pathe": "^1.1.2", + "pkg-types": "^1.1.0", + "ufo": "^1.5.3" + } + }, "node_modules/mrmime": { "version": "2.0.0", "license": "MIT", @@ -11154,11 +11985,26 @@ "isarray": "0.0.1" } }, - "node_modules/path-type": { - "version": "4.0.0", - "license": "MIT", + "node_modules/path-type": { + "version": "4.0.0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, "engines": { - "node": ">=8" + "node": "*" } }, "node_modules/pbkdf2": { @@ -11227,6 +12073,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/pkg-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.1.0.tgz", + "integrity": "sha512-/RpmvKdxKf8uILTtoOhAgf30wYbP2Qw+L9p3Rvshx1JZVX+XQNZQFjlbmGHEGIm4CkVPlSn+NXmIM8+9oWQaSA==", + "dev": true, + "dependencies": { + "confbox": "^0.1.7", + "mlly": "^1.6.1", + "pathe": "^1.1.2" + } + }, "node_modules/pkg-up": { "version": "3.1.0", "license": "MIT", @@ -11289,7 +12146,9 @@ } }, "node_modules/postcss": { - "version": "8.4.33", + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", "funding": [ { "type": "opencollective", @@ -11304,11 +12163,10 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" }, "engines": { "node": "^10 || ^12 || >=14" @@ -11953,6 +12811,38 @@ "renderkid": "^3.0.0" } }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/pretty-format/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, "node_modules/pretty-time": { "version": "1.1.0", "license": "MIT", @@ -12886,6 +13776,41 @@ "inherits": "^2.0.1" } }, + "node_modules/rollup": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.17.2.tgz", + "integrity": "sha512-/9ClTJPByC0U4zNLowV1tMBe8yMEAxewtR3cUNX5BoEpGH3dQEWpJLr6CLp0fPdYRF/fzVOgvDb1zXuakwF5kQ==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.17.2", + "@rollup/rollup-android-arm64": "4.17.2", + "@rollup/rollup-darwin-arm64": "4.17.2", + "@rollup/rollup-darwin-x64": "4.17.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.17.2", + "@rollup/rollup-linux-arm-musleabihf": "4.17.2", + "@rollup/rollup-linux-arm64-gnu": "4.17.2", + "@rollup/rollup-linux-arm64-musl": "4.17.2", + "@rollup/rollup-linux-powerpc64le-gnu": "4.17.2", + "@rollup/rollup-linux-riscv64-gnu": "4.17.2", + "@rollup/rollup-linux-s390x-gnu": "4.17.2", + "@rollup/rollup-linux-x64-gnu": "4.17.2", + "@rollup/rollup-linux-x64-musl": "4.17.2", + "@rollup/rollup-win32-arm64-msvc": "4.17.2", + "@rollup/rollup-win32-ia32-msvc": "4.17.2", + "@rollup/rollup-win32-x64-msvc": "4.17.2", + "fsevents": "~2.3.2" + } + }, "node_modules/rtl-detect": { "version": "1.1.2", "license": "BSD-3-Clause" @@ -13290,6 +14215,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true + }, "node_modules/signal-exit": { "version": "3.0.7", "license": "ISC" @@ -13375,8 +14306,9 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "license": "BSD-3-Clause", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "engines": { "node": ">=0.10.0" } @@ -13460,6 +14392,12 @@ "version": "0.1.8", "license": "MIT" }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true + }, "node_modules/statuses": { "version": "2.0.1", "license": "MIT", @@ -13648,6 +14586,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strip-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.0.tgz", + "integrity": "sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==", + "dev": true, + "dependencies": { + "js-tokens": "^9.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/strip-literal/node_modules/js-tokens": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.0.tgz", + "integrity": "sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==", + "dev": true + }, "node_modules/style-to-object": { "version": "0.4.4", "license": "MIT", @@ -14066,6 +15022,30 @@ "version": "1.0.3", "license": "MIT" }, + "node_modules/tinybench": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.8.0.tgz", + "integrity": "sha512-1/eK7zUnIklz4JUUlL+658n58XO2hHLQfSk1Zf2LKieUjxidN16eKFEoDEfjHc3ohofSSqK3X5yO6VGb6iW8Lw==", + "dev": true + }, + "node_modules/tinypool": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz", + "integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz", + "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/to-fast-properties": { "version": "2.0.0", "license": "MIT", @@ -14126,6 +15106,15 @@ "version": "0.0.1", "license": "MIT" }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/type-fest": { "version": "2.19.0", "license": "(MIT OR CC0-1.0)", @@ -14186,6 +15175,12 @@ "version": "1.0.6", "license": "MIT" }, + "node_modules/ufo": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.3.tgz", + "integrity": "sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==", + "dev": true + }, "node_modules/undici-types": { "version": "5.26.5", "license": "MIT" @@ -14687,6 +15682,282 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/vite": { + "version": "5.2.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.11.tgz", + "integrity": "sha512-HndV31LWW05i1BLPMUCE1B9E9GFbOu1MbenhS58FuK6owSO5qHm7GiCotrNY1YE5rMeQSFBGmT5ZaLEjFizgiQ==", + "dev": true, + "dependencies": { + "esbuild": "^0.20.1", + "postcss": "^8.4.38", + "rollup": "^4.13.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.5.3.tgz", + "integrity": "sha512-axFo00qiCpU/JLd8N1gu9iEYL3xTbMbMrbe5nDp9GL0nb6gurIdZLkkFogZXWnE8Oyy5kfSLwNVIcVsnhE7lgQ==", + "dev": true, + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.4", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.5.3.tgz", + "integrity": "sha512-2oM7nLXylw3mQlW6GXnRriw+7YvZFk/YNV8AxIC3Z3MfFbuziLGWP9GPxxu/7nRlXhqyxBikpamr+lEEj1sUEw==", + "dev": true, + "dependencies": { + "@vitest/expect": "1.5.3", + "@vitest/runner": "1.5.3", + "@vitest/snapshot": "1.5.3", + "@vitest/spy": "1.5.3", + "@vitest/utils": "1.5.3", + "acorn-walk": "^8.3.2", + "chai": "^4.3.10", + "debug": "^4.3.4", + "execa": "^8.0.1", + "local-pkg": "^0.5.0", + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.5.0", + "strip-literal": "^2.0.0", + "tinybench": "^2.5.1", + "tinypool": "^0.8.3", + "vite": "^5.0.0", + "vite-node": "1.5.3", + "why-is-node-running": "^2.2.2" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "1.5.3", + "@vitest/ui": "1.5.3", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/vitest/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vitest/node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/vitest/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vitest/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vitest/node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vitest/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vitest/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vitest/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/vitest/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/vm-browserify": { "version": "1.1.2", "license": "MIT" @@ -15068,6 +16339,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/why-is-node-running": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", + "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==", + "dev": true, + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/widest-line": { "version": "4.0.1", "license": "MIT", diff --git a/package.json b/package.json index 882693325..871d7374f 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "markdown-it": "^13.0.2", "node-polyfill-webpack-plugin": "^2.0.1", "prism-react-renderer": "^1.3.5", + "prismjs": "^1.29.0", "react": "^18.0.0", "react-dom": "^18.0.0", "unist-util-visit": "^2.0.1", @@ -42,7 +43,8 @@ "postcss": "^8.4.28", "tailwindcss": "^3.3.3", "typescript": "^4.7.4", - "unist-util-visit-parents": "^3.1.1" + "unist-util-visit-parents": "^3.1.1", + "vitest": "^1.5.3" }, "browserslist": { "production": [ diff --git a/src/components/DenoByExample/ByExample.tsx b/src/components/DenoByExample/ByExample.tsx new file mode 100644 index 000000000..5791437ea --- /dev/null +++ b/src/components/DenoByExample/ByExample.tsx @@ -0,0 +1,248 @@ +import React from "react"; +import Layout from "@theme/Layout"; +import Footer from "@theme/Footer"; +import { parseExample, DIFFICULTIES, TAGS, ExampleSnippet } from '../../plugins/deno-by-example/example'; +import CodeBlock from "@theme/CodeBlock"; + +export default function ByExample({ example, examplesList }) { + const { name, content } = example; + + const parsed = parseExample(name, content); + + const { + id, + title, + description, + difficulty, + tags, + additionalResources, + run, + playground, + files, + } = parsed; + + const freshProps = { + url: { + origin: "https://github.com/denoland/deno-docs/blob/main/by-example/", + pathname: id, + }, + data: [ + parsed, + content + ] + } + + return ( + +

By Example

+ + + + + +