From f8d317237b060e047c2a196343aec968f570f025 Mon Sep 17 00:00:00 2001 From: Jo Franchetti Date: Tue, 7 Jan 2025 22:34:22 +0000 Subject: [PATCH 1/7] Parallelise the generation of assets and merge the distinct jobs into one. This can generate all the ref material in about ~30s, and does a hash check to prevent wasted time. --- reference_gen/common.ts | 23 ++++++--- reference_gen/deno-doc.ts | 33 ------------ reference_gen/deno.jsonc | 8 ++- reference_gen/doc.ts | 90 +++++++++++++++++++++++++++++++++ reference_gen/node-doc.ts | 57 --------------------- reference_gen/registrations.ts | 91 ++++++++++++++++++++++++++++++++++ reference_gen/web-doc.ts | 33 ------------ 7 files changed, 200 insertions(+), 135 deletions(-) delete mode 100644 reference_gen/deno-doc.ts create mode 100644 reference_gen/doc.ts delete mode 100644 reference_gen/node-doc.ts create mode 100644 reference_gen/registrations.ts delete mode 100644 reference_gen/web-doc.ts diff --git a/reference_gen/common.ts b/reference_gen/common.ts index 32f8a0729..628ac2126 100644 --- a/reference_gen/common.ts +++ b/reference_gen/common.ts @@ -130,13 +130,11 @@ export function renderMarkdown( return undefined; } - return `
${ - titleOnlyRenderer.renderer.render(parsed, titleOnlyRenderer.options, {}) - }
`; + return `
${titleOnlyRenderer.renderer.render(parsed, titleOnlyRenderer.options, {}) + }
`; } else { - return `
${ - renderer.render(md) - }
`; + return `
${renderer.render(md) + }
`; } } @@ -183,7 +181,9 @@ export const hrefResolver: HrefResolver = { }; export async function writeFiles(root: string, files: Record) { - await Deno.remove(root, { recursive: true }); + if (await fileExists(root)) { + await Deno.remove(root, { recursive: true }); + } await Promise.all( Object.entries(files).map(async ([path, content]) => { @@ -196,3 +196,12 @@ export async function writeFiles(root: string, files: Record) { console.log(`Written ${Object.keys(files).length} files`); } + +export async function fileExists(path: string) { + try { + await Deno.lstat(path); + return true; + } catch { + return false; + } +} diff --git a/reference_gen/deno-doc.ts b/reference_gen/deno-doc.ts deleted file mode 100644 index 4b6dd54a8..000000000 --- a/reference_gen/deno-doc.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { doc, generateHtml } from "@deno/doc"; -import { - hrefResolver, - renderMarkdown, - stripMarkdown, - writeFiles, -} from "./common.ts"; -import categoryDocs from "./deno-categories.json" with { type: "json" }; - -const url = import.meta.resolve("./types/deno.d.ts"); - -console.log("Generating doc nodes..."); - -const nodes = await doc(url, { includeAll: true }); - -console.log("Generating html files..."); - -const files = await generateHtml({ [url]: nodes }, { - packageName: "Deno", - categoryDocs, - disableSearch: true, - hrefResolver, - usageComposer: { - singleMode: true, - compose(_currentResolve, _usageToMd) { - return new Map(); - }, - }, - markdownRenderer: renderMarkdown, - markdownStripper: stripMarkdown, -}); - -await writeFiles("./gen/deno", files); diff --git a/reference_gen/deno.jsonc b/reference_gen/deno.jsonc index 0f71a53cc..774b5032b 100644 --- a/reference_gen/deno.jsonc +++ b/reference_gen/deno.jsonc @@ -3,11 +3,12 @@ "imports": { "@b-fuze/deno-dom": "jsr:@b-fuze/deno-dom@^0.1.47", "@std/async": "jsr:@std/async@^0.224.2", + "@std/encoding": "jsr:@std/encoding@^1.0.6", "@std/fs": "jsr:@std/fs@^1.0.1", "@std/media-types": "jsr:@std/media-types@^0.224.1", "@std/path": "jsr:@std/path@^0.225.1", "@std/yaml": "jsr:@std/yaml@^1.0.3", - "@deno/doc": "jsr:@deno/doc@0.159.0", + "@deno/doc": "jsr:@deno/doc@0.163.0", "@types/node": "npm:@types/node@22.7.2", "browserslist": "npm:browserslist@4.23.0", "dax": "jsr:@david/dax@^0.40.1", @@ -19,10 +20,7 @@ "types:deno": "deno run --allow-read --allow-write --allow-run --allow-env --allow-sys deno-types.ts", "types:node": "deno run --allow-read --allow-write=. --allow-env --allow-sys node-types.ts", "types": "deno task types:deno && deno task types:node", - "doc:deno": "mkdir -p gen/deno && deno run --allow-read --allow-write --allow-env --allow-net deno-doc.ts", - "doc:web": "mkdir -p gen/web && deno run --allow-read --allow-write --allow-env --allow-net web-doc.ts", - "doc:node": "mkdir -p gen/node && deno run --allow-read --allow-write --allow-env --allow-net node-doc.ts", - "doc": "deno task doc:deno && deno task doc:web && deno task doc:node" + "doc": "deno run -A doc.ts" }, "exclude": [ "types", diff --git a/reference_gen/doc.ts b/reference_gen/doc.ts new file mode 100644 index 000000000..13018919f --- /dev/null +++ b/reference_gen/doc.ts @@ -0,0 +1,90 @@ +import { doc, DocNode, generateHtml, GenerateOptions } from "@deno/doc"; +import { encodeHex } from "jsr:@std/encoding/hex"; +import registrations from "./registrations.ts"; +import { fileExists, writeFiles } from "./common.ts"; + +type FileMetadata = { fileIdentifier: string; filePath: string; hashPath: string; objectHash: string }; + +console.time("doc"); +console.timeLog("doc", "Generating doc nodes..."); + +for (const { sources, generateOptions } of registrations) { + console.timeLog("doc", `Processing ${generateOptions.packageName}...`); + + const paths = resolveFilePaths(sources); + const docs = await loadDocumentation(paths); + const metadata = await generateFileMetadata(docs, generateOptions); + + if (await fileExists(metadata.hashPath)) { + console.timeLog("doc", `Checking ${metadata.fileIdentifier} for changes...`); + const existingHash = await Deno.readTextFile(metadata.hashPath); + + if (existingHash === metadata.objectHash) { + console.timeLog("doc", `Skipping ${metadata.fileIdentifier} because source hash and previously generated file hash match exactly. Delete the hash file ${metadata.hashPath} to force regeneration.`); + continue; + } + } + + const files = await generateFiles(docs, generateOptions); + + await writeFiles("gen/" + metadata.fileIdentifier, files); + await Deno.writeTextFile(metadata.hashPath, metadata.objectHash); + //await saveResults(files, metadata); +} + +console.timeEnd("doc"); + +function resolveFilePaths(sources: string[]) { + return sources.map((file) => + file.startsWith("./") ? import.meta.resolve(file) : `file://${file}` + ); +} + +async function loadDocumentation(paths: string[]) { + const docGenerationPromises = paths.map(async (path) => { + return await doc([path]); + }); + + const nodes = await Promise.all(docGenerationPromises); + return nodes.reduce((acc, val) => ({ ...acc, ...val }), {}); +} + +async function generateFileMetadata(nodes: Record, generateOptions: GenerateOptions) { + const fileIdentifier = generateOptions.packageName?.toLowerCase()!; + const filePath = `./gen/${fileIdentifier}.json`; + const hashPath = `./gen/${fileIdentifier}.hash`; + const objectHash = await hashObject(nodes); + return { fileIdentifier, filePath, hashPath, objectHash }; +} + +async function generateFiles(nodes: Record, generateOptions: GenerateOptions) { + console.timeLog("doc", "Generating files..."); + + const fileGenerationPromises = Object.keys(nodes).map(async (key) => { + const data = Object.fromEntries([[key, nodes[key]]]); + const html = await generateHtml(data, generateOptions); + + console.timeLog("doc", `Generated ${key}.`); + return html; + }); + + const allGeneratedFiles = await Promise.all(fileGenerationPromises); + return allGeneratedFiles.reduce((acc, val) => ({ ...acc, ...val }), {}); +} + +async function saveResults(files: Record, metadata: FileMetadata) { + console.timeLog("doc", "Writing files..."); + const asString = JSON.stringify(files, null, 2); + + await Deno.writeTextFile(metadata.filePath, asString); + await Deno.writeTextFile(metadata.hashPath, metadata.objectHash); + + console.timeLog("doc", `Wrote ${metadata.fileIdentifier}.`); +} + +// deno-lint-ignore no-explicit-any +async function hashObject(obj: any) { + const messageBuffer = new TextEncoder().encode(JSON.stringify(obj)); + const hashBuffer = await crypto.subtle.digest("SHA-256", messageBuffer); + return encodeHex(hashBuffer); +} diff --git a/reference_gen/node-doc.ts b/reference_gen/node-doc.ts deleted file mode 100644 index e175d2964..000000000 --- a/reference_gen/node-doc.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { doc, DocNode, generateHtml } from "@deno/doc"; -import { expandGlob } from "@std/fs"; -import { - hrefResolver, - renderMarkdown, - stripMarkdown, - writeFiles, -} from "./common.ts"; -import symbolRedirectMap from "./node-symbol-map.json" with { type: "json" }; -import defaultSymbolMap from "./node-default-map.json" with { type: "json" }; -import rewriteMap from "./node-rewrite-map.json" with { type: "json" }; - -const newRewriteMap = Object.fromEntries( - Object.entries(rewriteMap).map(( - [key, val], - ) => [import.meta.resolve(val), key]), -); - -const nodesByUrl: Record = {}; - -console.log("Generating doc nodes..."); - -for await (const file of expandGlob("./types/node/[!_]*")) { - const path = `file://${file.path}`; - - nodesByUrl[path] = await doc(path); -} - -console.log("Generating html files..."); - -const files = await generateHtml(nodesByUrl, { - packageName: "Node", - disableSearch: true, - symbolRedirectMap, - defaultSymbolMap, - rewriteMap: newRewriteMap, - hrefResolver, - usageComposer: { - singleMode: true, - compose(currentResolve, usageToMd) { - if ("file" in currentResolve) { - return new Map([[ - { - name: "", - }, - usageToMd(`node:${currentResolve.file.path}`, undefined), - ]]); - } else { - return new Map(); - } - }, - }, - markdownRenderer: renderMarkdown, - markdownStripper: stripMarkdown, -}); - -await writeFiles("./gen/node", files); diff --git a/reference_gen/registrations.ts b/reference_gen/registrations.ts new file mode 100644 index 000000000..d5f130efd --- /dev/null +++ b/reference_gen/registrations.ts @@ -0,0 +1,91 @@ +import { GenerateOptions } from "@deno/doc"; +import { + hrefResolver, + renderMarkdown, + stripMarkdown, +} from "./common.ts"; +import webCategoryDocs from "./web-categories.json" with { type: "json" }; +import denoCategoryDocs from "./deno-categories.json" with { type: "json" }; +import symbolRedirectMap from "./node-symbol-map.json" with { type: "json" }; +import defaultSymbolMap from "./node-default-map.json" with { type: "json" }; +import rewriteMap from "./node-rewrite-map.json" with { type: "json" }; +import { expandGlob } from "@std/fs"; + +export type SourceRegistration = { + sources: string[]; + generateOptions: GenerateOptions; +} + +async function getNodeTypeFiles() { + const urls: string[] = []; + for await (const file of expandGlob("./types/node/[!_]*")) { + urls.push(file.path); + } + return urls; +} + +function buildNewRewriteMap() { + return Object.fromEntries( + Object.entries(rewriteMap).map(( + [key, val], + ) => [import.meta.resolve(val), key]), + ); +} + +const defaultGenerateOptions: Partial = { + disableSearch: true, + hrefResolver, + usageComposer: { + singleMode: true, + compose(_currentResolve, _usageToMd) { + return new Map(); + }, + }, + markdownRenderer: renderMarkdown, + markdownStripper: stripMarkdown, +} + +export default [ + { + sources: ["./types/web.d.ts"], + generateOptions: { + ...defaultGenerateOptions, + packageName: "Web", + categoryDocs: webCategoryDocs, + } as GenerateOptions + }, + { + sources: ["./types/deno.d.ts"], + generateOptions: { + ...defaultGenerateOptions, + packageName: "Deno", + categoryDocs: denoCategoryDocs, + } as GenerateOptions + }, + { + sources: await getNodeTypeFiles(), + generateOptions: { + ...defaultGenerateOptions, + packageName: "Node", + categoryDocs: undefined, + usageComposer: { + singleMode: true, + compose(currentResolve, usageToMd) { + if ("file" in currentResolve) { + return new Map([[ + { + name: "", + }, + usageToMd(`node:${currentResolve.file.path}`, undefined), + ]]); + } else { + return new Map(); + } + }, + }, + symbolRedirectMap, + defaultSymbolMap, + rewriteMap: buildNewRewriteMap(), + } as GenerateOptions + } +] as SourceRegistration[]; diff --git a/reference_gen/web-doc.ts b/reference_gen/web-doc.ts deleted file mode 100644 index 8b0c32b32..000000000 --- a/reference_gen/web-doc.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { doc, generateHtml } from "@deno/doc"; -import { - hrefResolver, - renderMarkdown, - stripMarkdown, - writeFiles, -} from "./common.ts"; -import categoryDocs from "./web-categories.json" with { type: "json" }; - -const url = import.meta.resolve("./types/web.d.ts"); - -console.log("Generating doc nodes..."); - -const nodes = await doc(url, { includeAll: true }); - -console.log("Generating html files..."); - -const files = await generateHtml({ [url]: nodes }, { - packageName: "Web", - categoryDocs, - disableSearch: true, - hrefResolver, - usageComposer: { - singleMode: true, - compose(_currentResolve, _usageToMd) { - return new Map(); - }, - }, - markdownRenderer: renderMarkdown, - markdownStripper: stripMarkdown, -}); - -await writeFiles("./gen/web", files); From efd4c546f70bd12447d337e27d35791c81df968f Mon Sep 17 00:00:00 2001 From: Jo Franchetti Date: Wed, 8 Jan 2025 10:51:49 +0000 Subject: [PATCH 2/7] WIP runtime generation of reference docs --- deno.json | 6 +- deno.lock | 42 ++++++++++- reference.page.jsx | 9 ++- reference/_pages/Category.tsx | 69 ++++++++++++++++++ reference/_pages/Class.tsx | 73 +++++++++++++++++++ reference/_pages/Function.tsx | 32 +++++++++ reference/_pages/Import.tsx | 28 ++++++++ reference/_pages/Interface.tsx | 51 +++++++++++++ reference/_pages/Module.tsx | 30 ++++++++ reference/_pages/Namespace.tsx | 69 ++++++++++++++++++ reference/_pages/NotImplemented.tsx | 33 +++++++++ reference/_pages/TypeAlias.tsx | 30 ++++++++ reference/_pages/Variable.tsx | 31 ++++++++ reference/pageFactory.ts | 26 +++++++ reference/reference.page.raw.tsx | 106 ++++++++++++++++++++++++++++ reference/types.ts | 18 +++++ reference_gen/deno.lock | 91 +++++++++++++----------- reference_gen/doc.ts | 93 +++++++++++------------- reference_gen/registrations.ts | 2 +- 19 files changed, 740 insertions(+), 99 deletions(-) create mode 100644 reference/_pages/Category.tsx create mode 100644 reference/_pages/Class.tsx create mode 100644 reference/_pages/Function.tsx create mode 100644 reference/_pages/Import.tsx create mode 100644 reference/_pages/Interface.tsx create mode 100644 reference/_pages/Module.tsx create mode 100644 reference/_pages/Namespace.tsx create mode 100644 reference/_pages/NotImplemented.tsx create mode 100644 reference/_pages/TypeAlias.tsx create mode 100644 reference/_pages/Variable.tsx create mode 100644 reference/pageFactory.ts create mode 100644 reference/reference.page.raw.tsx create mode 100644 reference/types.ts diff --git a/deno.json b/deno.json index 99d6eeaf7..60200a5bf 100644 --- a/deno.json +++ b/deno.json @@ -7,11 +7,13 @@ "@std/html": "jsr:@std/html@^1.0.3", "@std/media-types": "jsr:@std/media-types@^1.0.3", "@std/path": "jsr:@std/path@^1.0.8", + "@deno/doc": "jsr:@deno/doc@0.163.0", "ga4": "https://raw.githubusercontent.com/denoland/ga4/04a1ce209116f158b5ef1658b957bdb109db68ed/mod.ts", "lume/": "https://deno.land/x/lume@v2.4.1/", "googleapis": "npm:googleapis@^144.0.0", "prismjs": "npm:prismjs@1.29.0", - "tailwindcss": "npm:tailwindcss@^3.4.9" + "tailwindcss": "npm:tailwindcss@^3.4.9", + "markdown-it": "npm:markdown-it@14.1.0" }, "tasks": { "serve": "deno run -A lume.ts -s", @@ -42,4 +44,4 @@ "_site", "reference_gen" ] -} +} \ No newline at end of file diff --git a/deno.lock b/deno.lock index ae4059855..2f9e187da 100644 --- a/deno.lock +++ b/deno.lock @@ -2,10 +2,15 @@ "version": "4", "specifiers": { "jsr:@davidbonnet/astring@1.8.6": "1.8.6", + "jsr:@deno/cache-dir@0.14": "0.14.0", + "jsr:@deno/doc@0.163.0": "0.163.0", + "jsr:@deno/graph@0.86": "0.86.7", + "jsr:@deno/graph@~0.82.3": "0.82.3", "jsr:@std/assert@*": "1.0.8", "jsr:@std/assert@^1.0.6": "1.0.8", "jsr:@std/assert@^1.0.8": "1.0.8", "jsr:@std/bytes@*": "1.0.4", + "jsr:@std/bytes@^1.0.2": "1.0.4", "jsr:@std/cli@*": "1.0.6", "jsr:@std/cli@1.0.6": "1.0.6", "jsr:@std/cli@^1.0.6": "1.0.6", @@ -21,6 +26,7 @@ "jsr:@std/fs@*": "1.0.5", "jsr:@std/fs@1.0.5": "1.0.5", "jsr:@std/fs@^1.0.4": "1.0.5", + "jsr:@std/fs@^1.0.6": "1.0.8", "jsr:@std/fs@~0.229.3": "0.229.3", "jsr:@std/html@1.0.3": "1.0.3", "jsr:@std/html@^1.0.3": "1.0.3", @@ -84,9 +90,32 @@ "@davidbonnet/astring@1.8.6": { "integrity": "98b4914c8863cdf8c0ff83bb5c528caa67a8dca6020ad6234113499f00583e3a" }, + "@deno/cache-dir@0.14.0": { + "integrity": "729f0b68e7fc96443c09c2c544b830ca70897bdd5168598446d752f7a4c731ad", + "dependencies": [ + "jsr:@deno/graph@0.86", + "jsr:@std/fmt@^1.0.3", + "jsr:@std/fs@^1.0.6", + "jsr:@std/io", + "jsr:@std/path@^1.0.8" + ] + }, + "@deno/doc@0.163.0": { + "integrity": "c9593b9ff7a750a95659b55292e12daf9c2f7c689a83bbc72e2e4eee103a5247", + "dependencies": [ + "jsr:@deno/cache-dir", + "jsr:@deno/graph@~0.82.3" + ] + }, "@deno/gfm@0.8.2": { "integrity": "a7528367cbd954a2d0de316bd0097ee92f06d2cb66502c8574de133ed223424f" }, + "@deno/graph@0.82.3": { + "integrity": "5c1fe944368172a9c87588ac81b82eb027ca78002a57521567e6264be322637e" + }, + "@deno/graph@0.86.7": { + "integrity": "fb4e5ab648d1b9d464ea55e0f70cd489251b1926e622dd83697b4e28e59c7291" + }, "@std/assert@1.0.8": { "integrity": "ebe0bd7eb488ee39686f77003992f389a06c3da1bbd8022184804852b2fa641b", "dependencies": [ @@ -133,6 +162,12 @@ "jsr:@std/path@^1.0.7" ] }, + "@std/fs@1.0.8": { + "integrity": "161c721b6f9400b8100a851b6f4061431c538b204bb76c501d02c508995cffe0", + "dependencies": [ + "jsr:@std/path@^1.0.8" + ] + }, "@std/html@1.0.3": { "integrity": "7a0ac35e050431fb49d44e61c8b8aac1ebd55937e0dc9ec6409aa4bab39a7988" }, @@ -152,7 +187,10 @@ "integrity": "54a546004f769c1ac9e025abd15a76b6671ddc9687e2313b67376125650dc7ba" }, "@std/io@0.225.0": { - "integrity": "c1db7c5e5a231629b32d64b9a53139445b2ca640d828c26bf23e1c55f8c079b3" + "integrity": "c1db7c5e5a231629b32d64b9a53139445b2ca640d828c26bf23e1c55f8c079b3", + "dependencies": [ + "jsr:@std/bytes@^1.0.2" + ] }, "@std/json@1.0.1": { "integrity": "1f0f70737e8827f9acca086282e903677bc1bb0c8ffcd1f21bca60039563049f" @@ -1928,6 +1966,7 @@ }, "workspace": { "dependencies": [ + "jsr:@deno/doc@0.163.0", "jsr:@std/assert@^1.0.6", "jsr:@std/dotenv@~0.225.2", "jsr:@std/front-matter@1.0.5", @@ -1936,6 +1975,7 @@ "jsr:@std/media-types@^1.0.3", "jsr:@std/path@^1.0.8", "npm:googleapis@144", + "npm:markdown-it@14.1.0", "npm:preact@*", "npm:prismjs@1.29.0", "npm:tailwindcss@^3.4.9" diff --git a/reference.page.jsx b/reference.page.jsx index f707ecf62..8f4ac9039 100644 --- a/reference.page.jsx +++ b/reference.page.jsx @@ -45,9 +45,14 @@ export default function* () { let title = ""; try { const match = titleRegexp.exec(content); - const titleFirst = match[1].slice(0, -"documentation".length); - title = unescape(titleFirst, { entityList }) + "- Deno Docs"; + if (!match) { + //console.log("No title found in", file.path); + title = file.path + " - Deno Docs"; + } else { + const titleFirst = match[1].slice(0, -"documentation".length); + title = unescape(titleFirst, { entityList }) + "- Deno Docs"; + } } catch (e) { if (!file.path.endsWith("prototype.html")) { console.error(file.path); diff --git a/reference/_pages/Category.tsx b/reference/_pages/Category.tsx new file mode 100644 index 000000000..7b32e24eb --- /dev/null +++ b/reference/_pages/Category.tsx @@ -0,0 +1,69 @@ +import { LumeDocument, ReferenceContext } from "../types.ts"; + +type Props = { + data: Record; + context: ReferenceContext; +}; + +export default function* getPages( + item: Record, + context: ReferenceContext, +): IterableIterator { + yield { + title: context.section, + url: `${context.root}/${context.section.toLocaleLowerCase()}/`, + content: , + }; + + for (const [key, value] of Object.entries(item)) { + if (value) { + yield { + title: key, + url: + `${context.root}/${context.section.toLocaleLowerCase()}/${key.toLocaleLowerCase()}`, + content: , + }; + } + } +} + +export function Category({ data, context }: Props) { + return ( +
+

I am a category: {data.name}

+
    + {Object.entries(data).map(([key, value]) => ( +
  • + {value + ? ( + <> + + {key} + +

    + {value} +

    + + ) + : key} +
  • + ))} +
+
+ ); +} + +export function CategoryListing({ data, context }: Props) { + // const itemsInThisCategory = context.dataCollection.filter((item) => item.jsDoc?.tag === data.name); + + return ( +
+

I am a category listing page {data.name}

+
    +
  • This is a list of all the APIs in this category
  • +
+
+ ); +} diff --git a/reference/_pages/Class.tsx b/reference/_pages/Class.tsx new file mode 100644 index 000000000..2bc026c06 --- /dev/null +++ b/reference/_pages/Class.tsx @@ -0,0 +1,73 @@ +import { DocNodeClass } from "@deno/doc/types"; +import { LumeDocument, ReferenceContext } from "../types.ts"; +import factoryFor from "../pageFactory.ts"; + +type Props = { data: DocNodeClass; context: ReferenceContext }; + +export default function* getPages( + item: DocNodeClass, + context: ReferenceContext, +): IterableIterator { + const prefix = context.parentName ? `${context.parentName}.` : ""; + + yield { + title: item.name, + url: `${context.root}/${context.section.toLocaleLowerCase()}/~/${prefix}${item.name}`, + content: , + }; +} + +export function Class({ data, context }: Props) { + const fullName = context.parentName + ? `${context.parentName}.${data.name}` + : data.name; + + const isUnstable = data.jsDoc?.tags?.some((tag) => + tag.kind === "experimental" as string + ); + + const jsDocParagraphs = data.jsDoc?.doc?.split("\n\n").map((paragraph) => ( +

{paragraph}

+ )); + + const constructors = data.classDef.constructors.map((constructor) => { + return ( +
+

Constructor

+
+          {JSON.stringify(constructor, null, 2)}
+        
+
+ ); + }); + + const properties = data.classDef.properties.map((property) => { + return ( +
+

{property.name}

+
+ ); + }); + + return ( +
+

Class: {fullName}

+ {isUnstable &&

UNSTABLE

} + {jsDocParagraphs && jsDocParagraphs} + +

Constructors

+ {constructors && constructors} + +

Properties

+ {properties && properties} + +

Methods

+ +

Static Methods

+ +
+        {JSON.stringify(data, null, 2)}
+      
+
+ ); +} diff --git a/reference/_pages/Function.tsx b/reference/_pages/Function.tsx new file mode 100644 index 000000000..15449af4d --- /dev/null +++ b/reference/_pages/Function.tsx @@ -0,0 +1,32 @@ +import { DocNodeFunction, DocNodeImport } from "@deno/doc/types"; +import { LumeDocument, ReferenceContext } from "../types.ts"; + +type Props = { data: DocNodeFunction }; + +export default function* getPages( + item: DocNodeFunction, + context: ReferenceContext, +): IterableIterator { + const prefix = context.parentName ? `${context.parentName}.` : ""; + + yield { + title: item.name, + url: + `${context.root}/${context.section.toLocaleLowerCase()}/~/${prefix}${item.name}`, + content: , + }; +} + +export function Function({ data }: Props) { + return ( +
+ I am a function, my name is {data.name} + + {data.jsDoc?.doc &&

{data.jsDoc?.doc}

} + +
+        {JSON.stringify(data, null, 2)}
+      
+
+ ); +} diff --git a/reference/_pages/Import.tsx b/reference/_pages/Import.tsx new file mode 100644 index 000000000..ff96978a4 --- /dev/null +++ b/reference/_pages/Import.tsx @@ -0,0 +1,28 @@ +import { DocNode, DocNodeImport } from "@deno/doc/types"; +import { LumeDocument, ReferenceContext } from "../types.ts"; + +type Props = { data: DocNode }; + +export default function* getPages( + item: DocNodeImport, + context: ReferenceContext, +): IterableIterator { + yield { + title: item.name, + url: + `${context.root}/${context.section.toLocaleLowerCase()}/${item.name.toLocaleLowerCase()}.import`, + content: , + }; +} + +export function Import({ data }: Props) { + return ( +
+ I am a Import, my name is {data.name} + +
+        {JSON.stringify(data, null, 2)}
+      
+
+ ); +} diff --git a/reference/_pages/Interface.tsx b/reference/_pages/Interface.tsx new file mode 100644 index 000000000..93ad50ec5 --- /dev/null +++ b/reference/_pages/Interface.tsx @@ -0,0 +1,51 @@ +import { DocNodeInterface } from "@deno/doc/types"; +import { LumeDocument, ReferenceContext } from "../types.ts"; + +type Props = { data: DocNodeInterface; context: ReferenceContext }; + +export default function* getPages( + item: DocNodeInterface, + context: ReferenceContext, +): IterableIterator { + const prefix = context.parentName ? `${context.parentName}.` : ""; + + yield { + title: item.name, + url: `${context.root}/${context.section.toLocaleLowerCase()}/~/${prefix}${item.name}`, + content: , + }; +} + +export function Interface({ data, context }: Props) { + const fullName = context.parentName + ? `${context.parentName}.${data.name}` + : data.name; + + const isUnstable = data.jsDoc?.tags?.some((tag) => + tag.kind === "experimental" as string + ); + + const jsDocParagraphs = data.jsDoc?.doc?.split("\n\n").map((paragraph) => ( +

{paragraph}

+ )); + + return ( +
+

Interface: {fullName}

+ {isUnstable &&

UNSTABLE

} + {jsDocParagraphs && jsDocParagraphs} + +

Constructors

+ +

Properties

+ +

Methods

+ +

Static Methods

+ +
+        {JSON.stringify(data, null, 2)}
+      
+
+ ); +} diff --git a/reference/_pages/Module.tsx b/reference/_pages/Module.tsx new file mode 100644 index 000000000..1da29e3df --- /dev/null +++ b/reference/_pages/Module.tsx @@ -0,0 +1,30 @@ +import { DocNode, DocNodeModuleDoc } from "@deno/doc/types"; +import { LumeDocument, ReferenceContext } from "../types.ts"; + +type Props = { data: DocNode }; + +export default function* getPages( + item: DocNodeModuleDoc, + context: ReferenceContext, +): IterableIterator { + yield { + title: item.name, + url: + `${context.root}/${context.section.toLocaleLowerCase()}/${item.name.toLocaleLowerCase()}`, + content: , + }; + + console.log("Module found", item); +} + +export function Module({ data }: Props) { + return ( +
+ I am a module, my name is {data.name} + +
+        {JSON.stringify(data, null, 2)}
+      
+
+ ); +} diff --git a/reference/_pages/Namespace.tsx b/reference/_pages/Namespace.tsx new file mode 100644 index 000000000..546084bf2 --- /dev/null +++ b/reference/_pages/Namespace.tsx @@ -0,0 +1,69 @@ +import { DocNodeNamespace } from "@deno/doc/types"; +import { LumeDocument, ReferenceContext } from "../types.ts"; +import factoryFor from "../pageFactory.ts"; + +type Props = { data: DocNodeNamespace; context: ReferenceContext }; + +export default function* getPages( + item: DocNodeNamespace, + context: ReferenceContext, +): IterableIterator { + yield { + title: item.name, + url: + `${context.root}/${context.section.toLocaleLowerCase()}/${item.name.toLocaleLowerCase()}`, + content: , + }; + + for (const element of item.namespaceDef.elements) { + const factory = factoryFor(element); + + yield* factory(element, { + ...context, + dataCollection: item.namespaceDef.elements, + parentName: item.name, + }); + } +} + +export function Namespace({ data, context }: Props) { + const interfaces = data.namespaceDef.elements.filter((elements) => + elements.kind === "interface" + ); + + const classes = data.namespaceDef.elements.filter((elements) => + elements.kind === "class" + ); + + return ( +
+

Namespace: {context.section} - {data.name}

+ +

Classes

+ + +

Interfaces

+ + +
+        {JSON.stringify(data, null, 2)}
+      
+
+ ); +} diff --git a/reference/_pages/NotImplemented.tsx b/reference/_pages/NotImplemented.tsx new file mode 100644 index 000000000..1a380aa9a --- /dev/null +++ b/reference/_pages/NotImplemented.tsx @@ -0,0 +1,33 @@ +import { DocNode, DocNodeBase } from "@deno/doc/types"; +import { LumeDocument, ReferenceContext } from "../types.ts"; + +type Props = { data: DocNode }; + +export default function* getPages( + item: DocNodeBase, + context: ReferenceContext, +): IterableIterator { + // console.log( + // "Not implemented factory encountered", + // item.name, + // item.kind, + // item.location.filename, + // ); + + // console.log("Context", context); + + // yield { + // title: item.name, + // url: `/${context.root}/${context.section}/~/${item.name}.${item.kind}`, + // content: , + // }; +} + +export function NotImplemented({ data }: Props) { + return ( +
+ I am not yet implemented, but I am supposed to represent:{" "} + {JSON.stringify(data)} +
+ ); +} diff --git a/reference/_pages/TypeAlias.tsx b/reference/_pages/TypeAlias.tsx new file mode 100644 index 000000000..db9fe3c78 --- /dev/null +++ b/reference/_pages/TypeAlias.tsx @@ -0,0 +1,30 @@ +import { DocNodeTypeAlias } from "@deno/doc/types"; +import { LumeDocument, ReferenceContext } from "../types.ts"; + +type Props = { data: DocNodeTypeAlias }; + +export default function* getPages( + item: DocNodeTypeAlias, + context: ReferenceContext, +): IterableIterator { + const prefix = context.parentName ? `${context.parentName}.` : ""; + yield { + title: item.name, + url: `${context.root}/${context.section.toLocaleLowerCase()}/~/${prefix}${item.name}`, + content: , + }; +} + +export function TypeAlias({ data }: Props) { + return ( +
+ I am a type alias, my name is {data.name} + + {data.jsDoc?.doc &&

{data.jsDoc?.doc}

} + +
+        {JSON.stringify(data, null, 2)}
+      
+
+ ); +} diff --git a/reference/_pages/Variable.tsx b/reference/_pages/Variable.tsx new file mode 100644 index 000000000..040a819da --- /dev/null +++ b/reference/_pages/Variable.tsx @@ -0,0 +1,31 @@ +import { DocNodeVariable } from "@deno/doc/types"; +import { LumeDocument, ReferenceContext } from "../types.ts"; + +type Props = { data: DocNodeVariable }; + +export default function* getPages( + item: DocNodeVariable, + context: ReferenceContext, +): IterableIterator { + const prefix = context.parentName ? `${context.parentName}.prototype.` : ""; + yield { + title: item.name, + url: + `${context.root}/${context.section.toLocaleLowerCase()}/~/${prefix}${item.name}`, + content: , + }; +} + +export function Variable({ data }: Props) { + return ( +
+ I am a variable, my name is {data.name} + + {data.jsDoc?.doc &&

{data.jsDoc?.doc}

} + +
+        {JSON.stringify(data, null, 2)}
+      
+
+ ); +} diff --git a/reference/pageFactory.ts b/reference/pageFactory.ts new file mode 100644 index 000000000..b0f729d3b --- /dev/null +++ b/reference/pageFactory.ts @@ -0,0 +1,26 @@ +import { ReferenceDocumentFactoryFunction } from "./types.ts"; +import getPagesForNamespace from "./_pages/Namespace.tsx"; +import getPagesForNotImplemented from "./_pages/NotImplemented.tsx"; +import getPagesForModule from "./_pages/Module.tsx"; +import getPagesForClass from "./_pages/Class.tsx"; +import getPagesForInterface from "./_pages/Interface.tsx"; +import getPagesForImport from "./_pages/Import.tsx"; +import getPagesForFunction from "./_pages/Function.tsx"; +import getPagesForTypeAlias from "./_pages/TypeAlias.tsx"; +import getPagesForVariable from "./_pages/Variable.tsx"; +import { DocNodeBase } from "@deno/doc/types"; + +const factories = new Map>(); +factories.set("moduleDoc", getPagesForModule as ReferenceDocumentFactoryFunction); +factories.set("namespace", getPagesForNamespace as ReferenceDocumentFactoryFunction); +factories.set("function", getPagesForFunction as ReferenceDocumentFactoryFunction); +factories.set("variable", getPagesForVariable as ReferenceDocumentFactoryFunction); +factories.set("enum", getPagesForNotImplemented as ReferenceDocumentFactoryFunction); +factories.set("class", getPagesForClass as ReferenceDocumentFactoryFunction); +factories.set("typeAlias", getPagesForTypeAlias as ReferenceDocumentFactoryFunction); +factories.set("interface", getPagesForInterface as ReferenceDocumentFactoryFunction); +factories.set("import", getPagesForImport as ReferenceDocumentFactoryFunction); + +export default function factoryFor(item: T): ReferenceDocumentFactoryFunction { + return factories.get(item.kind) || getPagesForNotImplemented; +} \ No newline at end of file diff --git a/reference/reference.page.raw.tsx b/reference/reference.page.raw.tsx new file mode 100644 index 000000000..f318cd138 --- /dev/null +++ b/reference/reference.page.raw.tsx @@ -0,0 +1,106 @@ +import { doc } from "@deno/doc"; +import registrations from "../reference_gen/registrations.ts"; +import factoryFor from "./pageFactory.ts"; +import getCategoryPages from "./_pages/Category.tsx"; + +export const layout = "raw.tsx"; +const root = "/new_api"; + +export const sidebar = [ + { + items: [ + { label: "Deno APIs", id: `${root}/deno/` }, + { label: "Web APIs", id: `${root}/web/` }, + { label: "Node APIs", id: `${root}/node/` }, + ], + }, +]; + +const generated: string[] = []; + +export default async function* () { + try { + if (Deno.env.has("SKIP_REFERENCE")) { + throw new Error(); + } + + for await ( + const { key, dataCollection, generateOptions } of referenceItems() + ) { + const section = generateOptions.packageName || "unknown"; + const definitionFile = key; + + const context = { + root, + section, + dataCollection, + definitionFile, + parentName: "", + }; + + for ( + const catPage of getCategoryPages( + generateOptions.categoryDocs!, + context, + ) + ) { + yield catPage; + generated.push(catPage.url); + } + + for (const item of dataCollection) { + const factory = factoryFor(item); + const pages = factory(item, context); + + if (!pages) { + continue; + } + + for await (const page of pages) { + if (generated.includes(page.url)) { + console.warn( + `⚠️ Skipping duplicate page: ${page.url} - NEED TO MERGE THESE DOCS`, + ); + continue; + } + + yield page; + generated.push(page.url); + } + } + } + } catch (ex) { + console.warn("⚠️ Reference docs were not generated." + ex); + } + + console.log("Generated", generated.length, "reference pages"); +} + +async function* referenceItems() { + for (const { sources, generateOptions } of registrations) { + const paths = sources.map((file) => { + if (!file.startsWith("./")) { + return `file://${file}`; + } else { + const newPath = file.replace("./", "../reference_gen/"); + return import.meta.resolve(newPath); + } + }); + + const docs = await loadDocumentation(paths); + + for (const key of Object.keys(docs)) { + const dataCollection = docs[key]; + yield { key, dataCollection, generateOptions }; + } + } +} + +async function loadDocumentation(paths: string[]) { + const docGenerationPromises = paths.map(async (path) => { + return await doc([path]); + }); + + const nodes = await Promise.all(docGenerationPromises); + return nodes.reduce((acc, val) => ({ ...acc, ...val }), {}); +} diff --git a/reference/types.ts b/reference/types.ts new file mode 100644 index 000000000..9fd8af9ba --- /dev/null +++ b/reference/types.ts @@ -0,0 +1,18 @@ +import { DocNode, DocNodeBase } from "@deno/doc/types"; +import { JSX } from "npm:preact/jsx-runtime"; + +export type LumeDocument = { + title: string; + url: string; + content: JSX.Element; +} + +export type ReferenceContext = { + root: string; + section: string; + dataCollection: DocNode[]; + definitionFile: string; + parentName: string; +} + +export type ReferenceDocumentFactoryFunction = (item: T, context: ReferenceContext) => IterableIterator; diff --git a/reference_gen/deno.lock b/reference_gen/deno.lock index 8afab21cb..8d009e5bc 100644 --- a/reference_gen/deno.lock +++ b/reference_gen/deno.lock @@ -5,37 +5,40 @@ "jsr:@david/code-block-writer@13": "13.0.3", "jsr:@david/dax@~0.40.1": "0.40.1", "jsr:@david/which@0.3": "0.3.0", - "jsr:@deno/cache-dir@0.11": "0.11.1", - "jsr:@deno/doc@0.159.0": "0.159.0", + "jsr:@deno/cache-dir@0.14": "0.14.0", + "jsr:@deno/doc@0.163.0": "0.163.0", + "jsr:@deno/graph@0.86": "0.86.7", + "jsr:@deno/graph@~0.82.3": "0.82.3", "jsr:@denosaurs/plug@1.0.3": "1.0.3", "jsr:@std/assert@0.221": "0.221.0", - "jsr:@std/assert@0.223": "0.223.0", "jsr:@std/assert@0.226": "0.226.0", "jsr:@std/assert@~0.213.1": "0.213.1", "jsr:@std/async@~0.224.2": "0.224.2", "jsr:@std/bytes@0.221": "0.221.0", - "jsr:@std/bytes@0.223": "0.223.0", + "jsr:@std/bytes@^1.0.2": "1.0.4", + "jsr:@std/encoding@*": "1.0.6", "jsr:@std/encoding@0.213.1": "0.213.1", + "jsr:@std/encoding@^1.0.6": "1.0.6", "jsr:@std/fmt@0.213.1": "0.213.1", "jsr:@std/fmt@0.221": "0.221.0", - "jsr:@std/fmt@0.223": "0.223.0", + "jsr:@std/fmt@^1.0.3": "1.0.3", "jsr:@std/fs@*": "1.0.4", "jsr:@std/fs@0.213.1": "0.213.1", "jsr:@std/fs@0.221.0": "0.221.0", - "jsr:@std/fs@0.223": "0.223.0", - "jsr:@std/fs@^1.0.1": "1.0.4", + "jsr:@std/fs@^1.0.1": "1.0.8", + "jsr:@std/fs@^1.0.6": "1.0.8", "jsr:@std/fs@~0.229.3": "0.229.3", "jsr:@std/io@0.221": "0.221.0", "jsr:@std/io@0.221.0": "0.221.0", - "jsr:@std/io@0.223": "0.223.0", + "jsr:@std/io@0.225": "0.225.0", "jsr:@std/media-types@~0.224.1": "0.224.1", "jsr:@std/path@*": "1.0.7", "jsr:@std/path@0.213.1": "0.213.1", "jsr:@std/path@0.221": "0.221.0", "jsr:@std/path@0.221.0": "0.221.0", - "jsr:@std/path@0.223": "0.223.0", "jsr:@std/path@1.0.0-rc.1": "1.0.0-rc.1", "jsr:@std/path@^1.0.6": "1.0.7", + "jsr:@std/path@^1.0.8": "1.0.8", "jsr:@std/path@~0.213.1": "0.213.1", "jsr:@std/path@~0.225.1": "0.225.2", "jsr:@std/path@~0.225.2": "0.225.2", @@ -74,25 +77,33 @@ "@david/which@0.3.0": { "integrity": "6bdb62c40ac90edcf328e854fa8103a8db21e7c326089cbe3c3a1cf7887d3204" }, - "@deno/cache-dir@0.11.1": { - "integrity": "eaedd37ff99928cfbdee0174448322228bdc9e5a4f0fbd722fe925f28756f900", + "@deno/cache-dir@0.14.0": { + "integrity": "729f0b68e7fc96443c09c2c544b830ca70897bdd5168598446d752f7a4c731ad", "dependencies": [ - "jsr:@std/fmt@0.223", - "jsr:@std/fs@0.223", - "jsr:@std/io@0.223", - "jsr:@std/path@0.223" + "jsr:@deno/graph@0.86", + "jsr:@std/fmt@^1.0.3", + "jsr:@std/fs@^1.0.6", + "jsr:@std/io@0.225", + "jsr:@std/path@^1.0.8" ] }, - "@deno/doc@0.159.0": { - "integrity": "da55389502342281133c8894c862746d092d390f493d579ca47bdfa15b433a56", + "@deno/doc@0.163.0": { + "integrity": "c9593b9ff7a750a95659b55292e12daf9c2f7c689a83bbc72e2e4eee103a5247", "dependencies": [ - "jsr:@deno/cache-dir" + "jsr:@deno/cache-dir", + "jsr:@deno/graph@~0.82.3" ] }, + "@deno/graph@0.82.3": { + "integrity": "5c1fe944368172a9c87588ac81b82eb027ca78002a57521567e6264be322637e" + }, + "@deno/graph@0.86.7": { + "integrity": "fb4e5ab648d1b9d464ea55e0f70cd489251b1926e622dd83697b4e28e59c7291" + }, "@denosaurs/plug@1.0.3": { "integrity": "b010544e386bea0ff3a1d05e0c88f704ea28cbd4d753439c2f1ee021a85d4640", "dependencies": [ - "jsr:@std/encoding", + "jsr:@std/encoding@0.213.1", "jsr:@std/fmt@0.213.1", "jsr:@std/fs@0.213.1", "jsr:@std/path@0.213.1" @@ -104,9 +115,6 @@ "@std/assert@0.221.0": { "integrity": "a5f1aa6e7909dbea271754fd4ab3f4e687aeff4873b4cef9a320af813adb489a" }, - "@std/assert@0.223.0": { - "integrity": "eb8d6d879d76e1cc431205bd346ed4d88dc051c6366365b1af47034b0670be24" - }, "@std/assert@0.226.0": { "integrity": "0dfb5f7c7723c18cec118e080fec76ce15b4c31154b15ad2bd74822603ef75b3" }, @@ -116,20 +124,23 @@ "@std/bytes@0.221.0": { "integrity": "64a047011cf833890a4a2ab7293ac55a1b4f5a050624ebc6a0159c357de91966" }, - "@std/bytes@0.223.0": { - "integrity": "84b75052cd8680942c397c2631318772b295019098f40aac5c36cead4cba51a8" + "@std/bytes@1.0.4": { + "integrity": "11a0debe522707c95c7b7ef89b478c13fb1583a7cfb9a85674cd2cc2e3a28abc" }, "@std/encoding@0.213.1": { "integrity": "fcbb6928713dde941a18ca5db88ca1544d0755ec8fb20fe61e2dc8144b390c62" }, + "@std/encoding@1.0.6": { + "integrity": "ca87122c196e8831737d9547acf001766618e78cd8c33920776c7f5885546069" + }, "@std/fmt@0.213.1": { "integrity": "a06d31777566d874b9c856c10244ac3e6b660bdec4c82506cd46be052a1082c3" }, "@std/fmt@0.221.0": { "integrity": "379fed69bdd9731110f26b9085aeb740606b20428ce6af31ef6bd45ef8efa62a" }, - "@std/fmt@0.223.0": { - "integrity": "6deb37794127dfc7d7bded2586b9fc6f5d50e62a8134846608baf71ffc1a5208" + "@std/fmt@1.0.3": { + "integrity": "97765c16aa32245ff4e2204ecf7d8562496a3cb8592340a80e7e554e0bb9149f" }, "@std/fs@0.213.1": { "integrity": "fbcaf099f8a85c27ab0712b666262cda8fe6d02e9937bf9313ecaea39a22c501", @@ -145,9 +156,6 @@ "jsr:@std/path@0.221" ] }, - "@std/fs@0.223.0": { - "integrity": "3b4b0550b2c524cbaaa5a9170c90e96cbb7354e837ad1bdaf15fc9df1ae9c31c" - }, "@std/fs@0.229.3": { "integrity": "783bca21f24da92e04c3893c9e79653227ab016c48e96b3078377ebd5222e6eb", "dependencies": [ @@ -160,6 +168,12 @@ "jsr:@std/path@^1.0.6" ] }, + "@std/fs@1.0.8": { + "integrity": "161c721b6f9400b8100a851b6f4061431c538b204bb76c501d02c508995cffe0", + "dependencies": [ + "jsr:@std/path@^1.0.8" + ] + }, "@std/io@0.221.0": { "integrity": "faf7f8700d46ab527fa05cc6167f4b97701a06c413024431c6b4d207caa010da", "dependencies": [ @@ -167,11 +181,10 @@ "jsr:@std/bytes@0.221" ] }, - "@std/io@0.223.0": { - "integrity": "2d8c3c2ab3a515619b90da2c6ff5ea7b75a94383259ef4d02116b228393f84f1", + "@std/io@0.225.0": { + "integrity": "c1db7c5e5a231629b32d64b9a53139445b2ca640d828c26bf23e1c55f8c079b3", "dependencies": [ - "jsr:@std/assert@0.223", - "jsr:@std/bytes@0.223" + "jsr:@std/bytes@^1.0.2" ] }, "@std/media-types@0.224.1": { @@ -189,12 +202,6 @@ "jsr:@std/assert@0.221" ] }, - "@std/path@0.223.0": { - "integrity": "593963402d7e6597f5a6e620931661053572c982fc014000459edc1f93cc3989", - "dependencies": [ - "jsr:@std/assert@0.223" - ] - }, "@std/path@0.225.2": { "integrity": "0f2db41d36b50ef048dcb0399aac720a5348638dd3cb5bf80685bf2a745aa506", "dependencies": [ @@ -207,6 +214,9 @@ "@std/path@1.0.7": { "integrity": "76a689e07f0e15dcc6002ec39d0866797e7156629212b28f27179b8a5c3b33a1" }, + "@std/path@1.0.8": { + "integrity": "548fa456bb6a04d3c1a1e7477986b6cffbce95102d0bb447c67c4ee70e0364be" + }, "@std/streams@0.221.0": { "integrity": "47f2f74634b47449277c0ee79fe878da4424b66bd8975c032e3afdca88986e61", "dependencies": [ @@ -354,8 +364,9 @@ "dependencies": [ "jsr:@b-fuze/deno-dom@~0.1.47", "jsr:@david/dax@~0.40.1", - "jsr:@deno/doc@0.159.0", + "jsr:@deno/doc@0.163.0", "jsr:@std/async@~0.224.2", + "jsr:@std/encoding@^1.0.6", "jsr:@std/fs@^1.0.1", "jsr:@std/media-types@~0.224.1", "jsr:@std/path@~0.225.1", diff --git a/reference_gen/doc.ts b/reference_gen/doc.ts index 13018919f..709fec026 100644 --- a/reference_gen/doc.ts +++ b/reference_gen/doc.ts @@ -3,88 +3,75 @@ import { encodeHex } from "jsr:@std/encoding/hex"; import registrations from "./registrations.ts"; import { fileExists, writeFiles } from "./common.ts"; -type FileMetadata = { fileIdentifier: string; filePath: string; hashPath: string; objectHash: string }; - console.time("doc"); console.timeLog("doc", "Generating doc nodes..."); for (const { sources, generateOptions } of registrations) { - console.timeLog("doc", `Processing ${generateOptions.packageName}...`); + console.timeLog("doc", `Processing ${generateOptions.packageName}...`); - const paths = resolveFilePaths(sources); - const docs = await loadDocumentation(paths); - const metadata = await generateFileMetadata(docs, generateOptions); + const paths = resolveFilePaths(sources); + const docs = await loadDocumentation(paths); + const metadata = await generateFileMetadata(docs, generateOptions); - if (await fileExists(metadata.hashPath)) { - console.timeLog("doc", `Checking ${metadata.fileIdentifier} for changes...`); - const existingHash = await Deno.readTextFile(metadata.hashPath); + if (await fileExists(metadata.hashPath)) { + console.timeLog("doc", `Checking ${metadata.fileIdentifier} for changes...`); + const existingHash = await Deno.readTextFile(metadata.hashPath); - if (existingHash === metadata.objectHash) { - console.timeLog("doc", `Skipping ${metadata.fileIdentifier} because source hash and previously generated file hash match exactly. Delete the hash file ${metadata.hashPath} to force regeneration.`); - continue; - } + if (existingHash === metadata.objectHash) { + console.timeLog("doc", `Skipping ${metadata.fileIdentifier} because source hash and previously generated file hash match exactly. Delete the hash file ${metadata.hashPath} to force regeneration.`); + continue; } + } - const files = await generateFiles(docs, generateOptions); - - await writeFiles("gen/" + metadata.fileIdentifier, files); - await Deno.writeTextFile(metadata.hashPath, metadata.objectHash); - //await saveResults(files, metadata); + const files = await generateFiles(docs, generateOptions); + await writeFiles("gen/" + metadata.fileIdentifier, files); + await Deno.writeTextFile(metadata.hashPath, metadata.objectHash); } console.timeEnd("doc"); function resolveFilePaths(sources: string[]) { - return sources.map((file) => - file.startsWith("./") ? import.meta.resolve(file) : `file://${file}` - ); + return sources.map((file) => + file.startsWith("./") ? import.meta.resolve(file) : `file://${file}` + ); } async function loadDocumentation(paths: string[]) { - const docGenerationPromises = paths.map(async (path) => { - return await doc([path]); - }); + const docGenerationPromises = paths.map(async (path) => { + return await doc([path]); + }); - const nodes = await Promise.all(docGenerationPromises); - return nodes.reduce((acc, val) => ({ ...acc, ...val }), {}); + const nodes = await Promise.all(docGenerationPromises); + return nodes.reduce((acc, val) => ({ ...acc, ...val }), {}); } async function generateFileMetadata(nodes: Record, generateOptions: GenerateOptions) { - const fileIdentifier = generateOptions.packageName?.toLowerCase()!; - const filePath = `./gen/${fileIdentifier}.json`; - const hashPath = `./gen/${fileIdentifier}.hash`; - const objectHash = await hashObject(nodes); - return { fileIdentifier, filePath, hashPath, objectHash }; + const fileIdentifier = generateOptions.packageName?.toLowerCase()!; + const filePath = `./gen/${fileIdentifier}.json`; + const hashPath = `./gen/${fileIdentifier}.hash`; + const objectHash = await hashObject(nodes); + return { fileIdentifier, filePath, hashPath, objectHash }; } async function generateFiles(nodes: Record, generateOptions: GenerateOptions) { - console.timeLog("doc", "Generating files..."); + console.timeLog("doc", "Generating files..."); - const fileGenerationPromises = Object.keys(nodes).map(async (key) => { - const data = Object.fromEntries([[key, nodes[key]]]); - const html = await generateHtml(data, generateOptions); + const fileGenerationPromises = Object.keys(nodes).map(async (key) => { + const data = Object.fromEntries([[key, nodes[key]]]); + const html = await generateHtml(data, generateOptions); - console.timeLog("doc", `Generated ${key}.`); - return html; - }); + console.timeLog("doc", `Generated ${key}.`); + return html; + }); - const allGeneratedFiles = await Promise.all(fileGenerationPromises); - return allGeneratedFiles.reduce((acc, val) => ({ ...acc, ...val }), {}); -} - -async function saveResults(files: Record, metadata: FileMetadata) { - console.timeLog("doc", "Writing files..."); - const asString = JSON.stringify(files, null, 2); - - await Deno.writeTextFile(metadata.filePath, asString); - await Deno.writeTextFile(metadata.hashPath, metadata.objectHash); - - console.timeLog("doc", `Wrote ${metadata.fileIdentifier}.`); + const allGeneratedFiles = await Promise.all(fileGenerationPromises); + return allGeneratedFiles.reduce((acc, val) => ({ ...acc, ...val }), {}); } // deno-lint-ignore no-explicit-any async function hashObject(obj: any) { - const messageBuffer = new TextEncoder().encode(JSON.stringify(obj)); - const hashBuffer = await crypto.subtle.digest("SHA-256", messageBuffer); - return encodeHex(hashBuffer); + const messageBuffer = new TextEncoder().encode(JSON.stringify(obj)); + const hashBuffer = await crypto.subtle.digest("SHA-256", messageBuffer); + return encodeHex(hashBuffer); } + diff --git a/reference_gen/registrations.ts b/reference_gen/registrations.ts index d5f130efd..445657f1a 100644 --- a/reference_gen/registrations.ts +++ b/reference_gen/registrations.ts @@ -88,4 +88,4 @@ export default [ rewriteMap: buildNewRewriteMap(), } as GenerateOptions } -] as SourceRegistration[]; +] as SourceRegistration[]; \ No newline at end of file From 43904093c5d7d82878a4934459b7862519e60d36 Mon Sep 17 00:00:00 2001 From: Jo Franchetti Date: Wed, 8 Jan 2025 13:30:48 +0000 Subject: [PATCH 3/7] namespacing for page sections --- deno.lock | 7 ++ reference/_layouts/ReferencePage.tsx | 15 +++ reference/_pages/Category.tsx | 160 ++++++++++++++++++++------- reference/_pages/Class.tsx | 7 +- reference/_pages/Function.tsx | 5 +- reference/_pages/Import.tsx | 5 +- reference/_pages/Interface.tsx | 5 +- reference/_pages/Module.tsx | 5 +- reference/_pages/Namespace.tsx | 5 +- reference/_pages/NotImplemented.tsx | 5 +- reference/_pages/TypeAlias.tsx | 5 +- reference/_pages/Variable.tsx | 5 +- reference/_util/common.ts | 29 +++++ reference/reference.page.raw.tsx | 13 ++- reference/types.ts | 20 ++++ 15 files changed, 226 insertions(+), 65 deletions(-) create mode 100644 reference/_layouts/ReferencePage.tsx create mode 100644 reference/_util/common.ts diff --git a/deno.lock b/deno.lock index 2f9e187da..c85ea4d23 100644 --- a/deno.lock +++ b/deno.lock @@ -53,6 +53,7 @@ "jsr:@std/yaml@1.0.5": "1.0.5", "jsr:@std/yaml@^1.0.5": "1.0.5", "npm:@js-temporal/polyfill@0.4.4": "0.4.4", + "npm:@preact/compat@*": "18.3.1_preact@10.24.3", "npm:@supabase/auth-js@2.65.1": "2.65.1", "npm:@supabase/functions-js@2.4.3": "2.4.3", "npm:@supabase/node-fetch@2.6.15": "2.6.15", @@ -315,6 +316,12 @@ "@pkgjs/parseargs@0.11.0": { "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==" }, + "@preact/compat@18.3.1_preact@10.24.3": { + "integrity": "sha512-Kog4PSRxtT4COtOXjsuQPV1vMXpUzREQfv+6Dmcy9/rMk0HOPK0HTE9fspFjAmY8R80T/T8gtgmZ68u5bOSngw==", + "dependencies": [ + "preact" + ] + }, "@supabase/auth-js@2.65.1": { "integrity": "sha512-IA7i2Xq2SWNCNMKxwmPlHafBQda0qtnFr8QnyyBr+KaSxoXXqEzFCnQ1dGTy6bsZjVBgXu++o3qrDypTspaAPw==", "dependencies": [ diff --git a/reference/_layouts/ReferencePage.tsx b/reference/_layouts/ReferencePage.tsx new file mode 100644 index 000000000..a814fedb7 --- /dev/null +++ b/reference/_layouts/ReferencePage.tsx @@ -0,0 +1,15 @@ +import React from "npm:@preact/compat"; + +export default function Layout({ children }: { children: React.ReactNode }) { + return ( +
+
+

Reference Docs

+
+

+
+ {children} +
+
+ ); +} diff --git a/reference/_pages/Category.tsx b/reference/_pages/Category.tsx index 7b32e24eb..480eba408 100644 --- a/reference/_pages/Category.tsx +++ b/reference/_pages/Category.tsx @@ -1,4 +1,12 @@ -import { LumeDocument, ReferenceContext } from "../types.ts"; +import { DocNodeBase } from "@deno/doc/types"; +import ReferencePage from "../_layouts/ReferencePage.tsx"; +import { flattenItems } from "../_util/common.ts"; +import { + HasNamespace, + LumeDocument, + MightHaveNamespace, + ReferenceContext, +} from "../types.ts"; type Props = { data: Record; @@ -12,58 +20,124 @@ export default function* getPages( yield { title: context.section, url: `${context.root}/${context.section.toLocaleLowerCase()}/`, - content: , + content: , }; - for (const [key, value] of Object.entries(item)) { - if (value) { - yield { - title: key, - url: - `${context.root}/${context.section.toLocaleLowerCase()}/${key.toLocaleLowerCase()}`, - content: , - }; - } + for (const [key] of Object.entries(item)) { + yield { + title: key, + url: + `${context.root}/${context.section.toLocaleLowerCase()}/${key.toLocaleLowerCase()}`, + content: , + }; } } -export function Category({ data, context }: Props) { +export function CategoryHomePage({ data, context }: Props) { + const categoryListItems = Object.entries(data).map(([key, value]) => { + const categoryLinkUrl = + `${context.root}/${context.section.toLocaleLowerCase()}/${key.toLocaleLowerCase()}`; + + return ( +
  • + {key} +

    {value}

    +
  • + ); + }); + + return ( + +
    +

    I am a category: {data.name}

    +
      + {categoryListItems} +
    +
    +
    + ); +} + +type ListingProps = { + categoryName: string; + context: ReferenceContext; +}; + +export function SingleCategoryView({ categoryName, context }: ListingProps) { + const allItems = flattenItems(context.dataCollection); + + const itemsInThisCategory = allItems.filter((item) => + item.jsDoc?.tags?.some((tag) => + tag.kind === "category" && + tag.doc.toLocaleLowerCase() === categoryName?.toLocaleLowerCase() + ) + ); + + const classes = itemsInThisCategory.filter((item) => item.kind === "class") + .sort((a, b) => a.name.localeCompare(b.name)); + + const functions = itemsInThisCategory.filter((item) => + item.kind === "function" + ).sort((a, b) => a.name.localeCompare(b.name)); + + const interfaces = itemsInThisCategory.filter((item) => + item.kind === "interface" + ).sort((a, b) => a.name.localeCompare(b.name)); + + const typeAliases = itemsInThisCategory.filter((item) => + item.kind === "typeAlias" + ).sort((a, b) => a.name.localeCompare(b.name)); + return ( -
    -

    I am a category: {data.name}

    -
      - {Object.entries(data).map(([key, value]) => ( -
    • - {value - ? ( - <> - - {key} - -

      - {value} -

      - - ) - : key} -
    • - ))} -
    -
    + +

    I am a category listing page {categoryName}

    + +

    Classes

    +
    + +
    + + + +
    ); } -export function CategoryListing({ data, context }: Props) { - // const itemsInThisCategory = context.dataCollection.filter((item) => item.jsDoc?.tag === data.name); +function CategoryPageSection( + { title, items }: { title: string; items: DocNodeBase[] }, +) { + return ( +
    +

    {title}

    + +
    + ); +} +function CategoryPageList({ items }: { items: DocNodeBase[] }) { return ( -
    -

    I am a category listing page {data.name}

    -
      -
    • This is a list of all the APIs in this category
    • -
    -
    +
      + {items.map((item) => ( +
    • + +
    • + ))} +
    ); } + +function CategoryPageListItem( + { item }: { item: DocNodeBase & MightHaveNamespace }, +) { + return ( +
  • + + + +
  • + ); +} + +function ItemName({ item }: { item: DocNodeBase & MightHaveNamespace }) { + return <>{item.fullName || item.name}; +} diff --git a/reference/_pages/Class.tsx b/reference/_pages/Class.tsx index 2bc026c06..a2868a0fd 100644 --- a/reference/_pages/Class.tsx +++ b/reference/_pages/Class.tsx @@ -1,6 +1,7 @@ import { DocNodeClass } from "@deno/doc/types"; import { LumeDocument, ReferenceContext } from "../types.ts"; import factoryFor from "../pageFactory.ts"; +import ReferencePage from "../_layouts/ReferencePage.tsx"; type Props = { data: DocNodeClass; context: ReferenceContext }; @@ -49,8 +50,8 @@ export function Class({ data, context }: Props) { ); }); - return ( -
    + return ( +

    Class: {fullName}

    {isUnstable &&

    UNSTABLE

    } {jsDocParagraphs && jsDocParagraphs} @@ -68,6 +69,6 @@ export function Class({ data, context }: Props) {
             {JSON.stringify(data, null, 2)}
           
    -
    + ); } diff --git a/reference/_pages/Function.tsx b/reference/_pages/Function.tsx index 15449af4d..2cf22d7c8 100644 --- a/reference/_pages/Function.tsx +++ b/reference/_pages/Function.tsx @@ -1,5 +1,6 @@ import { DocNodeFunction, DocNodeImport } from "@deno/doc/types"; import { LumeDocument, ReferenceContext } from "../types.ts"; +import ReferencePage from "../_layouts/ReferencePage.tsx"; type Props = { data: DocNodeFunction }; @@ -19,7 +20,7 @@ export default function* getPages( export function Function({ data }: Props) { return ( -
    + I am a function, my name is {data.name} {data.jsDoc?.doc &&

    {data.jsDoc?.doc}

    } @@ -27,6 +28,6 @@ export function Function({ data }: Props) {
             {JSON.stringify(data, null, 2)}
           
    -
    + ); } diff --git a/reference/_pages/Import.tsx b/reference/_pages/Import.tsx index ff96978a4..63e34aba2 100644 --- a/reference/_pages/Import.tsx +++ b/reference/_pages/Import.tsx @@ -1,5 +1,6 @@ import { DocNode, DocNodeImport } from "@deno/doc/types"; import { LumeDocument, ReferenceContext } from "../types.ts"; +import ReferencePage from "../_layouts/ReferencePage.tsx"; type Props = { data: DocNode }; @@ -17,12 +18,12 @@ export default function* getPages( export function Import({ data }: Props) { return ( -
    + I am a Import, my name is {data.name}
             {JSON.stringify(data, null, 2)}
           
    -
    + ); } diff --git a/reference/_pages/Interface.tsx b/reference/_pages/Interface.tsx index 93ad50ec5..ba5f9fcff 100644 --- a/reference/_pages/Interface.tsx +++ b/reference/_pages/Interface.tsx @@ -1,5 +1,6 @@ import { DocNodeInterface } from "@deno/doc/types"; import { LumeDocument, ReferenceContext } from "../types.ts"; +import ReferencePage from "../_layouts/ReferencePage.tsx"; type Props = { data: DocNodeInterface; context: ReferenceContext }; @@ -30,7 +31,7 @@ export function Interface({ data, context }: Props) { )); return ( -
    +

    Interface: {fullName}

    {isUnstable &&

    UNSTABLE

    } {jsDocParagraphs && jsDocParagraphs} @@ -46,6 +47,6 @@ export function Interface({ data, context }: Props) {
             {JSON.stringify(data, null, 2)}
           
    -
    + ); } diff --git a/reference/_pages/Module.tsx b/reference/_pages/Module.tsx index 1da29e3df..835e5445d 100644 --- a/reference/_pages/Module.tsx +++ b/reference/_pages/Module.tsx @@ -1,5 +1,6 @@ import { DocNode, DocNodeModuleDoc } from "@deno/doc/types"; import { LumeDocument, ReferenceContext } from "../types.ts"; +import ReferencePage from "../_layouts/ReferencePage.tsx"; type Props = { data: DocNode }; @@ -19,12 +20,12 @@ export default function* getPages( export function Module({ data }: Props) { return ( -
    + I am a module, my name is {data.name}
             {JSON.stringify(data, null, 2)}
           
    -
    + ); } diff --git a/reference/_pages/Namespace.tsx b/reference/_pages/Namespace.tsx index 546084bf2..1364f1d4c 100644 --- a/reference/_pages/Namespace.tsx +++ b/reference/_pages/Namespace.tsx @@ -1,6 +1,7 @@ import { DocNodeNamespace } from "@deno/doc/types"; import { LumeDocument, ReferenceContext } from "../types.ts"; import factoryFor from "../pageFactory.ts"; +import ReferencePage from "../_layouts/ReferencePage.tsx"; type Props = { data: DocNodeNamespace; context: ReferenceContext }; @@ -36,7 +37,7 @@ export function Namespace({ data, context }: Props) { ); return ( -
    +

    Namespace: {context.section} - {data.name}

    Classes

    @@ -64,6 +65,6 @@ export function Namespace({ data, context }: Props) {
             {JSON.stringify(data, null, 2)}
           
    -
    + ); } diff --git a/reference/_pages/NotImplemented.tsx b/reference/_pages/NotImplemented.tsx index 1a380aa9a..6961cbe91 100644 --- a/reference/_pages/NotImplemented.tsx +++ b/reference/_pages/NotImplemented.tsx @@ -1,5 +1,6 @@ import { DocNode, DocNodeBase } from "@deno/doc/types"; import { LumeDocument, ReferenceContext } from "../types.ts"; +import ReferencePage from "../_layouts/ReferencePage.tsx"; type Props = { data: DocNode }; @@ -25,9 +26,9 @@ export default function* getPages( export function NotImplemented({ data }: Props) { return ( -
    + I am not yet implemented, but I am supposed to represent:{" "} {JSON.stringify(data)} -
    + ); } diff --git a/reference/_pages/TypeAlias.tsx b/reference/_pages/TypeAlias.tsx index db9fe3c78..14cd93265 100644 --- a/reference/_pages/TypeAlias.tsx +++ b/reference/_pages/TypeAlias.tsx @@ -1,5 +1,6 @@ import { DocNodeTypeAlias } from "@deno/doc/types"; import { LumeDocument, ReferenceContext } from "../types.ts"; +import ReferencePage from "../_layouts/ReferencePage.tsx"; type Props = { data: DocNodeTypeAlias }; @@ -17,7 +18,7 @@ export default function* getPages( export function TypeAlias({ data }: Props) { return ( -
    + I am a type alias, my name is {data.name} {data.jsDoc?.doc &&

    {data.jsDoc?.doc}

    } @@ -25,6 +26,6 @@ export function TypeAlias({ data }: Props) {
             {JSON.stringify(data, null, 2)}
           
    -
    + ); } diff --git a/reference/_pages/Variable.tsx b/reference/_pages/Variable.tsx index 040a819da..05615e2d0 100644 --- a/reference/_pages/Variable.tsx +++ b/reference/_pages/Variable.tsx @@ -1,5 +1,6 @@ import { DocNodeVariable } from "@deno/doc/types"; import { LumeDocument, ReferenceContext } from "../types.ts"; +import ReferencePage from "../_layouts/ReferencePage.tsx"; type Props = { data: DocNodeVariable }; @@ -18,7 +19,7 @@ export default function* getPages( export function Variable({ data }: Props) { return ( -
    + I am a variable, my name is {data.name} {data.jsDoc?.doc &&

    {data.jsDoc?.doc}

    } @@ -26,6 +27,6 @@ export function Variable({ data }: Props) {
             {JSON.stringify(data, null, 2)}
           
    -
    + ); } diff --git a/reference/_util/common.ts b/reference/_util/common.ts new file mode 100644 index 000000000..4b8f46658 --- /dev/null +++ b/reference/_util/common.ts @@ -0,0 +1,29 @@ +import { DocNode, DocNodeBase } from "@deno/doc/types"; +import { HasNamespace, MightHaveNamespace } from "../types.ts"; + +export function flattenItems(items: (DocNode & MightHaveNamespace)[]): (DocNodeBase & MightHaveNamespace)[] { + return populateItemNamespaces(items, true); +} + +export function populateItemNamespaces(items: DocNode[], populateNamespace = true, ns = ""): (DocNodeBase & HasNamespace)[] { + const flattened: (DocNodeBase & HasNamespace)[] = []; + + for (const item of items) { + + if (item.kind === "namespace") { + const namespace = ns + (ns ? "." : "") + item.name; + flattened.push(...populateItemNamespaces(item.namespaceDef.elements, populateNamespace, namespace)); + } + else { + const withNamespace = { ...item, namespace: "" } as DocNodeBase & HasNamespace; + withNamespace.namespace = ns; + withNamespace.fullName = ns + ? `${ns}.${item.name}` + : item.name; + + flattened.push(withNamespace); + } + } + + return flattened; +} diff --git a/reference/reference.page.raw.tsx b/reference/reference.page.raw.tsx index f318cd138..88ca4b79c 100644 --- a/reference/reference.page.raw.tsx +++ b/reference/reference.page.raw.tsx @@ -2,6 +2,7 @@ import { doc } from "@deno/doc"; import registrations from "../reference_gen/registrations.ts"; import factoryFor from "./pageFactory.ts"; import getCategoryPages from "./_pages/Category.tsx"; +import { flattenItems, populateItemNamespaces } from "./_util/common.ts"; export const layout = "raw.tsx"; const root = "/new_api"; @@ -24,9 +25,14 @@ export default async function* () { throw new Error(); } - for await ( - const { key, dataCollection, generateOptions } of referenceItems() - ) { + const sources = referenceItems(); + const referenceDocContext = { + categories: sidebar[0].items, + }; + + for await (const { key, dataCollection, generateOptions } of sources) { + populateItemNamespaces(dataCollection); + const section = generateOptions.packageName || "unknown"; const definitionFile = key; @@ -36,6 +42,7 @@ export default async function* () { dataCollection, definitionFile, parentName: "", + referenceDocContext, }; for ( diff --git a/reference/types.ts b/reference/types.ts index 9fd8af9ba..e55c286f2 100644 --- a/reference/types.ts +++ b/reference/types.ts @@ -7,12 +7,32 @@ export type LumeDocument = { content: JSX.Element; } +export type HasNamespace = { + namespace: string; + fullName: string; +} + +export type MightHaveNamespace = { + namespace?: string; + fullName?: string; +} + +export type ReferenceDocCategory = { + id: string; + label: string; +} + +export type ReferenceDocContext = { + categories: ReferenceDocCategory[]; +} + export type ReferenceContext = { root: string; section: string; dataCollection: DocNode[]; definitionFile: string; parentName: string; + referenceDocContext: ReferenceDocContext; } export type ReferenceDocumentFactoryFunction = (item: T, context: ReferenceContext) => IterableIterator; From 07bae1f7819fde25724e31b66328370edb06d2e5 Mon Sep 17 00:00:00 2001 From: Jo Franchetti Date: Thu, 9 Jan 2025 12:02:47 +0000 Subject: [PATCH 4/7] disable old content generation, start adding styles to newly generated pages --- deno.json | 2 +- reference.page._jsx | 81 ++ reference/_categories/deno-categories.json | 17 + reference/_categories/web-categories.json | 22 + reference/_dataSources/dtsSymbolSource.ts | 50 + reference/_layouts/ReferencePage.tsx | 88 +- reference/_pages/Category.tsx | 159 ++- reference/_pages/Class.tsx | 242 +++- reference/_pages/Function.tsx | 21 +- reference/_pages/Import.tsx | 13 +- reference/_pages/Interface.tsx | 28 +- reference/_pages/Module.tsx | 13 +- reference/_pages/Namespace.tsx | 18 +- reference/_pages/NotImplemented.tsx | 32 +- reference/_pages/Package.tsx | 77 + reference/_pages/TypeAlias.tsx | 22 +- reference/_pages/Variable.tsx | 18 +- .../_pages/primatives/AnchorableHeading.tsx | 41 + reference/_pages/primatives/CodeIcon.tsx | 45 + reference/_pages/primatives/LinkCode.tsx | 59 + reference/_util/common.ts | 57 +- reference/_util/methodSignatureRendering.ts | 95 ++ reference/pageFactory.ts | 68 +- reference/reference.page.raw.tsx | 107 +- reference/types.ts | 53 +- reference_gen/deno.jsonc | 2 +- reference_gen/doc.ts | 77 - static/reference-styles/page.css | 234 +++ static/reference-styles/styles.css | 1252 +++++++++++++++++ 29 files changed, 2585 insertions(+), 408 deletions(-) create mode 100644 reference.page._jsx create mode 100644 reference/_categories/deno-categories.json create mode 100644 reference/_categories/web-categories.json create mode 100644 reference/_dataSources/dtsSymbolSource.ts create mode 100644 reference/_pages/Package.tsx create mode 100644 reference/_pages/primatives/AnchorableHeading.tsx create mode 100644 reference/_pages/primatives/CodeIcon.tsx create mode 100644 reference/_pages/primatives/LinkCode.tsx create mode 100644 reference/_util/methodSignatureRendering.ts delete mode 100644 reference_gen/doc.ts create mode 100644 static/reference-styles/page.css create mode 100644 static/reference-styles/styles.css diff --git a/deno.json b/deno.json index 60200a5bf..4485dfc6d 100644 --- a/deno.json +++ b/deno.json @@ -44,4 +44,4 @@ "_site", "reference_gen" ] -} \ No newline at end of file +} diff --git a/reference.page._jsx b/reference.page._jsx new file mode 100644 index 000000000..8f4ac9039 --- /dev/null +++ b/reference.page._jsx @@ -0,0 +1,81 @@ +import { walkSync } from "@std/fs/walk"; +import { unescape } from "@std/html/entities"; +import entityList from "@std/html/named-entity-list.json" with { type: "json" }; + +export const layout = "raw.tsx"; + +export const sidebar = [ + { + items: [ + { + label: "Deno APIs", + id: "/api/deno/", + }, + { + label: "Web APIs", + id: "/api/web/", + }, + { + label: "Node APIs", + id: "/api/node/", + }, + ], + }, +]; + +const resetRegexp = + /\s*/; +const titleRegexp = /(.+?)<\/title>\s*/s; + +export default function* () { + try { + if (Deno.env.has("SKIP_REFERENCE")) { + throw new Error(); + } + const files = walkSync("reference_gen/gen", { + exts: [".html"], + }); + + for (const file of files) { + const content = Deno.readTextFileSync(file.path).replace( + resetRegexp, + "", + ); + + let title = ""; + try { + const match = titleRegexp.exec(content); + + if (!match) { + //console.log("No title found in", file.path); + title = file.path + " - Deno Docs"; + } else { + const titleFirst = match[1].slice(0, -"documentation".length); + title = unescape(titleFirst, { entityList }) + "- Deno Docs"; + } + } catch (e) { + if (!file.path.endsWith("prototype.html")) { + console.error(file.path); + throw e; + } + } + + const trailingLength = file.path.endsWith("index.html") + ? -"index.html".length + : -".html".length; + + let path = file.path.slice("reference_gen/gen".length, trailingLength); + + // replace slashes for windows + path = path.replace(/\\/g, "/"); + + yield { + url: "/api" + path, + title, + content, + }; + } + } catch (ex) { + console.warn("⚠️ Reference docs were not generated." + ex); + } +} diff --git a/reference/_categories/deno-categories.json b/reference/_categories/deno-categories.json new file mode 100644 index 000000000..04ffdc279 --- /dev/null +++ b/reference/_categories/deno-categories.json @@ -0,0 +1,17 @@ +{ + "Cloud": "Tools for managing state, scheduling tasks, and interacting with key-value stores. \n\nEg {@linkcode Deno.openKv }, {@linkcode Deno.cron}", + "Errors": "Error types and utilities for handling exceptions and custom error scenarios. Helps improve error handling and debugging. \n\nEg {@linkcode Deno.errors.NotFound}", + "FFI": "Foreign Function Interface. Call functions from shared libraries (e.g., C/C++) directly from Deno.\n\nUseful for integrating with existing native code or accessing low-level system functionality. \n\nEg {@linkcode Deno.dlopen}", + "Fetch": "HTTP client for fetching data across a network. Retrieve resources from servers, handle responses, and manage network requests. \n\nEg {@linkcode fetch}, {@linkcode Response}, {@linkcode Request}, {@linkcode Headers}", + "File System": "File System APIs for working with files, directories, and file metadata. Includes functions for reading, writing, and manipulating file paths. \n\nEg {@linkcode Deno.readDir}, {@linkcode Deno.readTextFile}", + "GPU": "GPU programming and rendering. Efficiently use a device’s graphics processing unit (GPU) for high-performance computations and complex image rendering. \n\nEg {@linkcode GPUDevice}", + "HTTP Server": "Handling HTTP requests, serving responses, and managing server behavior. \n\nEg {@linkcode Deno.serveHttp}, {@linkcode Deno.serve}", + "I/O": "Interfaces for reading, writing, seeking, and managing resources. For handling of data streams, file I/O, and console interactions. \n\nEg {@linkcode Deno.stdin}, {@linkcode Deno.inspect}", + "Jupyter": "Create interactive notebooks and execute code cells. Useful for data analysis, visualization, and educational purposes. \n\nEg {@linkcode Deno.jupyter}", + "Network": "A wide range of networking tasks, from low-level connections to high-level server creation. Handle HTTP requests, WebSocket communication, and DNS resolution. Useful for building web servers, clients, and networking tools. \n\nEg {@linkcode Deno.connect}, {@linkcode Deno.listen}, {@linkcode Deno.resolveDns}", + "Permissions": "Permission system controls access to resources (e.g., file system, network, environment variables).\n\nRequest permissions explicitly, enhancing security and user trust. \n\nEg {@linkcode Deno.permissions}", + "Runtime": "System-related functionality, process management, and observability. \n\nEg {@linkcode Deno.mainModule}, {@linkcode Deno.exit}, {@linkcode Deno.cwd}", + "Subprocess": "Spawn and manage child processes, execute commands, and collect output. Useful for executing external programs from Deno.\n\nEg {@linkcode Deno.Command}", + "Testing": "Robust testing and benchmarking capabilities to ensure code quality and performance.\n\nEg {@linkcode Deno.test}, {@linkcode Deno.bench}", + "WebSockets": "Enable real-time communication between clients and servers using WebSockets. Tools to create interactive and dynamic applications.\n\nEg {@linkcode WebSocket}" +} diff --git a/reference/_categories/web-categories.json b/reference/_categories/web-categories.json new file mode 100644 index 000000000..45a673c4f --- /dev/null +++ b/reference/_categories/web-categories.json @@ -0,0 +1,22 @@ +{ + "Cache": "Tools for caching and managing data. Store and retrieve data from the cache, manage cache storage limits, and handle cache-related events.\n\nEg {@linkcode caches}", + "Canvas": "Create, transform, and display images and graphics within the HTML element.\n\nEg {@linkcode createImageBitmap}", + "Crypto": "Cryptographic functionality for secure communication, data protection, and key management. Generate secure random numbers, encrypt and decrypt data, and manage cryptographic keys.\n\nEg {@linkcode crypto}", + "Encoding": "Handle character encoding, decoding, and binary data conversion.\n\nEg {@linkcode TextDecoder}, {@linkcode TextEncoder}", + "Events": "Handle events and interactions. Listen for and respond to various events, including custom events, errors, progress, and promise rejections.\n\nEg {@linkcode addEventListener}", + "Fetch": "HTTP client for fetching data across a network. Retrieve resources from servers, handle responses, and manage network requests.\n\nEg {@linkcode fetch}, {@linkcode Response}, {@linkcode Request}, {@linkcode Headers}", + "File": "File and data manipulation tools. Handle files, read data and work with binary content.\n\nEg {@linkcode File}, {@linkcode Blob}", + "GPU": "GPU programming and rendering. Efficiently use a device’s graphics processing unit (GPU) for high-performance computations and complex image rendering.\n\nEg {@linkcode GPUDevice}", + "I/O": "Interfaces for reading, writing, seeking, and managing resources. For handling of data streams, file I/O, and console interactions. \n\nEg {@linkcode stdin}, {@linkcode inspect}", + "Intl": "Tools for internationalization and localization. Create language-aware applications, handle date and time formatting, and adapt content for different locales. \n\nEg {@linkcode Intl}", + "Messaging": "Facilitate communication between different parts of an application, allowing data exchange and coordination.\n\nEg {@linkcode BroadcastChannel}, {@linkcode MessageChannel}", + "Performance": "Measure, analyze, and optimize application performance. \n\nEg {@linkcode performance}", + "Platform": "A set of essential interfaces and functions to interact with the browser environment. Eg handling user interactions, getting browser and device information, scheduling and canceling periodic or one-time tasks.\n\n{@linkcode prompt}, {@linkcode console}", + "Storage": "Store data locally within the browser. Manage session storage and local storage.\n\n{@linkcode sessionStorage}, {@linkcode localStorage}", + "Streams": "Manage data streams, queuing strategies, and transformations. Handle data in chunks, process large datasets, and optimize memory usage.\n\nEg {@linkcode ReadableStream}, {@linkcode WritableStream}, {@linkcode TransformStream}", + "Temporal": "Date and time handling. Includes long-lived Workflows, calendar systems, time zones, and precise duration calculations.\n\nEg {@linkcode Temporal.Now}, {@linkcode Temporal.PlainDate}", + "URL": "Manipulate URLs, extract data from URLs and manage query parameters.\n\nEg {@linkcode URL}", + "Wasm": "Efficiently execute computationally intensive tasks. Wasm module compilation, instantiation, memory management, and interaction with imports and exports.\n\nEg {@linkcode WebAssembly.instantiate}, {@linkcode WebAssembly.Module}, {@linkcode WebAssembly.Instance}", + "WebSockets": "Enable real-time communication between clients and servers using WebSockets. Tools to create interactive and dynamic applications.\n\nEg {@linkcode WebSocket}", + "Workers": "Run script operations in background threads. Manage worker threads, communicate with workers, and handle data transfer between workers and the main thread.\n\nEg {@linkcode Worker}" +} diff --git a/reference/_dataSources/dtsSymbolSource.ts b/reference/_dataSources/dtsSymbolSource.ts new file mode 100644 index 000000000..1a0897705 --- /dev/null +++ b/reference/_dataSources/dtsSymbolSource.ts @@ -0,0 +1,50 @@ +import { expandGlob } from "@std/fs"; +import { doc, DocNode } from "@deno/doc"; + +export type Package = { + name: string; + symbols: DocNode[]; +}; + +const packages = [ + { packageName: "Web", files: ["./types/web.d.ts"] }, + { packageName: "Deno", files: ["./types/deno.d.ts"] }, + { packageName: "Node", files: await getNodeTypeFiles() }, +]; + +export async function* getSymbols() { + for (const { packageName, files } of packages) { + const paths = files.map((file) => { + if (!file.startsWith("./")) { + return `file://${file}`; + } else { + const newPath = file.replace("./", "../../reference_gen/"); + return import.meta.resolve(newPath); + } + }); + + const docs = await loadDocumentation(paths); + + for (const sourceFile of Object.keys(docs)) { + const symbols = docs[sourceFile]; + yield { packageName, symbols }; + } + } +} + +async function loadDocumentation(paths: string[]) { + const docGenerationPromises = paths.map(async (path) => { + return await doc([path]); + }); + + const nodes = await Promise.all(docGenerationPromises); + return nodes.reduce((acc, val) => ({ ...acc, ...val }), {}); +} + +async function getNodeTypeFiles() { + const urls: string[] = []; + for await (const file of expandGlob("./types/node/[!_]*")) { + urls.push(file.path); + } + return urls; +} diff --git a/reference/_layouts/ReferencePage.tsx b/reference/_layouts/ReferencePage.tsx index a814fedb7..5ce3aa91b 100644 --- a/reference/_layouts/ReferencePage.tsx +++ b/reference/_layouts/ReferencePage.tsx @@ -1,15 +1,87 @@ import React from "npm:@preact/compat"; +import { Navigation, ReferenceContext } from "../types.ts"; -export default function Layout({ children }: { children: React.ReactNode }) { +export default function Layout( + { context, navigation, children }: { + context: ReferenceContext; + navigation?: Navigation; + children: React.ReactNode; + }, +) { return ( - <div> - <div> - <h1>Reference Docs</h1> + <> + {/* sorry mum, put these somewhere good */} + <link rel="stylesheet" href="/reference-styles/styles.css" /> + <link rel="stylesheet" href="/reference-styles/page.css" /> + + <div className={"ddoc"}> + <CategoryPanel context={context} /> + <div> + {navigation && <TopNav {...navigation} />} + <div id={"content"}> + {children} + </div> + </div> </div> - <h1></h1> - <main> - {children} - </main> + </> + ); +} + +function CategoryPanel({ context }: { context: ReferenceContext }) { + const categories = context.currentCategoryList; + + const categoryListItems = Object.entries(categories).map(([key, value]) => { + const categoryLinkUrl = + `${context.root}/${context.packageName.toLocaleLowerCase()}/${key.toLocaleLowerCase()}`; + + return ( + <li> + <a href={categoryLinkUrl}>{key}</a> + </li> + ); + }); + + return ( + <div id={"categoryPanel"}> + <ul> + {categoryListItems} + </ul> </div> ); } + +function TopNav( + { category, currentItemName }: Navigation, +) { + return ( + <nav id={"topnav"} className={"top-0 sticky bg-white z-50 py-3 h-14"}> + <div className={"h-full"}> + <div> + <ul className={"breadcrumbs"}> + <li> + <a href="./" className={"contextLink"}>{category}</a> + </li> + <span class="text-[#0F172A]"> + <svg + width="16" + height="16" + viewBox="0 0 16 16" + fill="none" + xmlns="http://www.w3.org/2000/svg" + > + <path + fill-rule="evenodd" + clip-rule="evenodd" + d="M5.76748 11.8159C5.5378 11.577 5.54525 11.1972 5.78411 10.9675L8.93431 8L5.78411 5.0325C5.54525 4.80282 5.5378 4.423 5.76748 4.18413C5.99715 3.94527 6.37698 3.93782 6.61584 4.1675L10.2158 7.5675C10.3335 7.68062 10.4 7.83679 10.4 8C10.4 8.16321 10.3335 8.31938 10.2158 8.4325L6.61584 11.8325C6.37698 12.0622 5.99715 12.0547 5.76748 11.8159Z" + fill="currentColor" + > + </path> + </svg> + </span> + <li>{currentItemName}</li> + </ul> + </div> + </div> + </nav> + ); +} diff --git a/reference/_pages/Category.tsx b/reference/_pages/Category.tsx index 480eba408..9ac1ebfec 100644 --- a/reference/_pages/Category.tsx +++ b/reference/_pages/Category.tsx @@ -1,4 +1,4 @@ -import { DocNodeBase } from "@deno/doc/types"; +import { DocNodeBase, DocNodeClass } from "@deno/doc/types"; import ReferencePage from "../_layouts/ReferencePage.tsx"; import { flattenItems } from "../_util/common.ts"; import { @@ -7,64 +7,37 @@ import { MightHaveNamespace, ReferenceContext, } from "../types.ts"; - -type Props = { - data: Record<string, string | undefined>; - context: ReferenceContext; -}; +import { insertLinkCodes } from "./primatives/LinkCode.tsx"; +import { AnchorableHeading } from "./primatives/AnchorableHeading.tsx"; +import { CodeIcon } from "./primatives/CodeIcon.tsx"; +import { Package } from "./Package.tsx"; export default function* getPages( - item: Record<string, string | undefined>, context: ReferenceContext, ): IterableIterator<LumeDocument> { yield { - title: context.section, - url: `${context.root}/${context.section.toLocaleLowerCase()}/`, - content: <CategoryHomePage data={item} context={context} />, + title: context.packageName, + url: `${context.root}/${context.packageName.toLocaleLowerCase()}/`, + content: <Package data={context.currentCategoryList} context={context} />, }; - for (const [key] of Object.entries(item)) { + for (const [key] of Object.entries(context.currentCategoryList)) { yield { title: key, url: - `${context.root}/${context.section.toLocaleLowerCase()}/${key.toLocaleLowerCase()}`, - content: <SingleCategoryView categoryName={key} context={context} />, + `${context.root}/${context.packageName.toLocaleLowerCase()}/${key.toLocaleLowerCase()}`, + content: <CategoryBrowse categoryName={key} context={context} />, }; } } -export function CategoryHomePage({ data, context }: Props) { - const categoryListItems = Object.entries(data).map(([key, value]) => { - const categoryLinkUrl = - `${context.root}/${context.section.toLocaleLowerCase()}/${key.toLocaleLowerCase()}`; - - return ( - <li> - <a href={categoryLinkUrl}>{key}</a> - <p>{value}</p> - </li> - ); - }); - - return ( - <ReferencePage> - <div> - <h1>I am a category: {data.name}</h1> - <ul> - {categoryListItems} - </ul> - </div> - </ReferencePage> - ); -} - type ListingProps = { categoryName: string; context: ReferenceContext; }; -export function SingleCategoryView({ categoryName, context }: ListingProps) { - const allItems = flattenItems(context.dataCollection); +export function CategoryBrowse({ categoryName, context }: ListingProps) { + const allItems = flattenItems(context.symbols); const itemsInThisCategory = allItems.filter((item) => item.jsDoc?.tags?.some((tag) => @@ -89,16 +62,21 @@ export function SingleCategoryView({ categoryName, context }: ListingProps) { ).sort((a, b) => a.name.localeCompare(b.name)); return ( - <ReferencePage> - <h1>I am a category listing page {categoryName}</h1> - - <h2 className={"anchorable mb-1"}>Classes</h2> - <div className={"namespaceSection"}> - <CategoryPageList items={classes} /> - </div> - <CategoryPageSection title={"Functions"} items={functions} /> - <CategoryPageSection title={"Interfaces"} items={interfaces} /> - <CategoryPageSection title={"Type Aliases"} items={typeAliases} /> + <ReferencePage + context={context} + navigation={{ + category: context.packageName, + currentItemName: categoryName, + }} + > + <main> + <div className={"space-y-7"}> + <CategoryPageSection title={"Classes"} items={classes} /> + <CategoryPageSection title={"Functions"} items={functions} /> + <CategoryPageSection title={"Interfaces"} items={interfaces} /> + <CategoryPageSection title={"Type Aliases"} items={typeAliases} /> + </div> + </main> </ReferencePage> ); } @@ -106,38 +84,73 @@ export function SingleCategoryView({ categoryName, context }: ListingProps) { function CategoryPageSection( { title, items }: { title: string; items: DocNodeBase[] }, ) { + const anchorId = title.replace(" ", "-").toLocaleLowerCase(); return ( - <section> - <h2>{title}</h2> - <CategoryPageList items={items} /> + <section id={anchorId} className={"section"}> + <AnchorableHeading anchor={anchorId}>{title}</AnchorableHeading> + <div className={"namespaceSection"}> + {items.map((item) => <CategoryPageItem item={item} />)} + </div> </section> ); } -function CategoryPageList({ items }: { items: DocNodeBase[] }) { - return ( - <ul className={"anchorable mb-1"}> - {items.map((item) => ( - <li> - <CategoryPageListItem item={item} /> - </li> - ))} - </ul> - ); -} - -function CategoryPageListItem( +function CategoryPageItem( { item }: { item: DocNodeBase & MightHaveNamespace }, ) { + const displayName = item.fullName || item.name; + const firstLineOfJsDoc = item.jsDoc?.doc?.split("\n")[0] || ""; + const descriptionWithLinkCode = insertLinkCodes(firstLineOfJsDoc); + return ( - <li> - <a href={`~/${item.fullName || item.name}`}> - <ItemName item={item} /> - </a> - </li> + <div className={"namespaceItem"}> + <CodeIcon glyph={item.kind} /> + <div className={"namespaceItemContent"}> + <a href={`~/${displayName}`}> + {displayName} + </a> + <p> + {descriptionWithLinkCode} + </p> + <MethodLinks item={item} /> + </div> + </div> ); } -function ItemName({ item }: { item: DocNodeBase & MightHaveNamespace }) { - return <>{item.fullName || item.name}</>; +function MethodLinks({ item }: { item: DocNodeBase }) { + if (item.kind !== "class") { + return <></>; + } + + const asClass = item as DocNodeClass & HasNamespace; + const methods = asClass.classDef.methods.sort((a, b) => + a.name.localeCompare(b.name) + ); + + const filteredMethodsList = [ + "[Symbol.dispose]", + "[Symbol.asyncDispose]", + "[Symbol.asyncIterator]", + ]; + + const filteredMethods = methods.filter((method) => + !filteredMethodsList.includes(method.name) + ); + + const methodLinks = filteredMethods.map((method) => { + return ( + <li> + <a href={`~/${asClass.fullName}#${method.name}`}> + {method.name} + </a> + </li> + ); + }); + + return ( + <ul className={"namespaceItemContentSubItems"}> + {methodLinks} + </ul> + ); } diff --git a/reference/_pages/Class.tsx b/reference/_pages/Class.tsx index a2868a0fd..f2f36a9fa 100644 --- a/reference/_pages/Class.tsx +++ b/reference/_pages/Class.tsx @@ -1,74 +1,244 @@ -import { DocNodeClass } from "@deno/doc/types"; -import { LumeDocument, ReferenceContext } from "../types.ts"; -import factoryFor from "../pageFactory.ts"; +import React from "npm:@preact/compat"; +import { ClassMethodDef, DocNodeClass, TsTypeDef } from "@deno/doc/types"; +import { HasFullName, LumeDocument, ReferenceContext } from "../types.ts"; import ReferencePage from "../_layouts/ReferencePage.tsx"; +import { AnchorableHeading } from "./primatives/AnchorableHeading.tsx"; +import { linkCodeAndParagraph } from "./primatives/LinkCode.tsx"; +import { + methodParameter, + methodSignature, +} from "../_util/methodSignatureRendering.ts"; -type Props = { data: DocNodeClass; context: ReferenceContext }; +type Props = { data: DocNodeClass & HasFullName; context: ReferenceContext }; export default function* getPages( - item: DocNodeClass, + item: DocNodeClass & HasFullName, context: ReferenceContext, ): IterableIterator<LumeDocument> { - const prefix = context.parentName ? `${context.parentName}.` : ""; - yield { title: item.name, - url: `${context.root}/${context.section.toLocaleLowerCase()}/~/${prefix}${item.name}`, + url: + `${context.root}/${context.packageName.toLocaleLowerCase()}/~/${item.fullName}`, content: <Class data={item} context={context} />, }; } export function Class({ data, context }: Props) { - const fullName = context.parentName - ? `${context.parentName}.${data.name}` - : data.name; + const properties = data.classDef.properties.map((property) => { + return ( + <div> + <h3>{property.name}</h3> + </div> + ); + }); + + return ( + <ReferencePage + context={context} + navigation={{ + category: context.packageName, + currentItemName: data.fullName, + }} + > + <div id={"content"}> + <main class={"symbolGroup"}> + <article> + <div> + <div> + <ClassNameHeading data={data} /> + <ImplementsSummary _implements={data.classDef.implements} /> + <StabilitySummary data={data} /> + </div> + </div> + <div> + <Description data={data} /> + <Constructors data={data} /> + <Methods data={data} /> + <Properties data={data} /> + + <h2>Static Methods</h2> + </div> + </article> + </main> + </div> + </ReferencePage> + ); +} + +function ClassNameHeading({ data }: { data: DocNodeClass & HasFullName }) { + return ( + <div className={"text-2xl leading-none break-all"}> + <span class="text-Class">class</span> + <span class="font-bold"> +   + {data.fullName} + </span> + </div> + ); +} +function StabilitySummary({ data }: { data: DocNodeClass }) { const isUnstable = data.jsDoc?.tags?.some((tag) => tag.kind === "experimental" as string ); - const jsDocParagraphs = data.jsDoc?.doc?.split("\n\n").map((paragraph) => ( - <p>{paragraph}</p> - )); + if (!isUnstable) { + return null; + } + + return ( + <div class="space-x-2 !mt-2"> + <div class="text-unstable border border-unstable/50 bg-unstable/5 inline-flex items-center gap-0.5 *:flex-none rounded-md leading-none font-bold py-2 px-3"> + Unstable + </div> + </div> + ); +} + +function ImplementsSummary({ _implements }: { _implements: TsTypeDef[] }) { + if (_implements.length === 0) { + return null; + } + + const spans = _implements.map((iface) => { + return <span>{iface.repr}</span>; + }); + + return ( + <div class="symbolSubtitle"> + <div> + <span class="type">implements</span> + {spans} + </div> + </div> + ); +} + +function Description({ data }: { data: DocNodeClass }) { + const description = linkCodeAndParagraph(data.jsDoc?.doc || "") || []; + if (description.length === 0) { + return null; + } - const constructors = data.classDef.constructors.map((constructor) => { + return ( + <div className={"space-y-7"}> + <div + data-color-mode="auto" + data-light-theme="light" + data-dark-theme="dark" + class="markdown-body" + > + {description && description} + </div> + </div> + ); +} + +function Methods({ data }: { data: DocNodeClass }) { + if (data.classDef.methods.length === 0) { + return <></>; + } + + const items = data.classDef.methods.map((method) => { return ( <div> - <h3>Constructor</h3> - <pre> - {JSON.stringify(constructor, null, 2)} - </pre> + <div> + <MethodSignature method={method} /> + </div> + <MarkdownBody> + {linkCodeAndParagraph(method.jsDoc?.doc || "")} + </MarkdownBody> </div> ); }); - const properties = data.classDef.properties.map((property) => { + return ( + <MemberSection title="Methods"> + {items} + </MemberSection> + ); +} + +function MethodSignature({ method }: { method: ClassMethodDef }) { + const asString = methodSignature(method); + + return ( + <> + {asString} + </> + ); +} + +function Properties({ data }: { data: DocNodeClass }) { + if (data.classDef.properties.length === 0) { + return <></>; + } + + const items = data.classDef.properties.map((prop) => { return ( <div> - <h3>{property.name}</h3> + <h3>{prop.name}</h3> + <MarkdownBody> + {linkCodeAndParagraph(prop.jsDoc?.doc || "")} + </MarkdownBody> </div> ); }); - return ( - <ReferencePage> - <h1>Class: {fullName}</h1> - {isUnstable && <p>UNSTABLE</p>} - {jsDocParagraphs && jsDocParagraphs} + return ( + <MemberSection title="Properties"> + {items} + </MemberSection> + ); +} - <h2>Constructors</h2> - {constructors && constructors} +function Constructors({ data }: { data: DocNodeClass }) { + if (data.classDef.constructors.length === 0) { + return <></>; + } - <h2>Properties</h2> - {properties && properties} + return ( + <MemberSection title="Constructors"> + <pre> + {JSON.stringify(data.classDef.constructors, null, 2)} + </pre> + </MemberSection> + ); +} - <h2>Methods</h2> +function MarkdownBody( + { children }: { children: React.ReactNode }, +) { + return ( + <div class="max-w-[75ch]"> + <div + data-color-mode="auto" + data-light-theme="light" + data-dark-theme="dark" + class="markdown-body" + > + {children} + </div> + </div> + ); +} - <h2>Static Methods</h2> +function MemberSection( + { title, children }: { title: string; children: React.ReactNode }, +) { + return ( + <div> + <div className={"space-y-7"}> + <section className={"section"}> + <AnchorableHeading anchor={title}> + {title} + </AnchorableHeading> + </section> + </div> - <pre> - {JSON.stringify(data, null, 2)} - </pre> - </ReferencePage> + <div className={"space-y-7"}> + {children} + </div> + </div> ); } diff --git a/reference/_pages/Function.tsx b/reference/_pages/Function.tsx index 2cf22d7c8..1ccd77524 100644 --- a/reference/_pages/Function.tsx +++ b/reference/_pages/Function.tsx @@ -1,26 +1,27 @@ -import { DocNodeFunction, DocNodeImport } from "@deno/doc/types"; -import { LumeDocument, ReferenceContext } from "../types.ts"; +import { DocNodeFunction } from "@deno/doc/types"; +import { HasFullName, LumeDocument, ReferenceContext } from "../types.ts"; import ReferencePage from "../_layouts/ReferencePage.tsx"; -type Props = { data: DocNodeFunction }; +type Props = { data: DocNodeFunction & HasFullName; context: ReferenceContext }; export default function* getPages( - item: DocNodeFunction, + item: DocNodeFunction & HasFullName, context: ReferenceContext, ): IterableIterator<LumeDocument> { - const prefix = context.parentName ? `${context.parentName}.` : ""; - yield { title: item.name, url: - `${context.root}/${context.section.toLocaleLowerCase()}/~/${prefix}${item.name}`, - content: <Function data={item} />, + `${context.root}/${context.packageName.toLocaleLowerCase()}/~/${item.fullName}`, + content: <Function data={item} context={context} />, }; } -export function Function({ data }: Props) { +export function Function({ data, context }: Props) { return ( - <ReferencePage> + <ReferencePage + context={context} + navigation={{ category: context.packageName, currentItemName: data.name }} + > I am a function, my name is {data.name} {data.jsDoc?.doc && <p>{data.jsDoc?.doc}</p>} diff --git a/reference/_pages/Import.tsx b/reference/_pages/Import.tsx index 63e34aba2..f6fce00b8 100644 --- a/reference/_pages/Import.tsx +++ b/reference/_pages/Import.tsx @@ -2,7 +2,7 @@ import { DocNode, DocNodeImport } from "@deno/doc/types"; import { LumeDocument, ReferenceContext } from "../types.ts"; import ReferencePage from "../_layouts/ReferencePage.tsx"; -type Props = { data: DocNode }; +type Props = { data: DocNode; context: ReferenceContext }; export default function* getPages( item: DocNodeImport, @@ -11,14 +11,17 @@ export default function* getPages( yield { title: item.name, url: - `${context.root}/${context.section.toLocaleLowerCase()}/${item.name.toLocaleLowerCase()}.import`, - content: <Import data={item} />, + `${context.root}/${context.packageName.toLocaleLowerCase()}/${item.name.toLocaleLowerCase()}.import`, + content: <Import data={item} context={context} />, }; } -export function Import({ data }: Props) { +export function Import({ data, context }: Props) { return ( - <ReferencePage> + <ReferencePage + context={context} + navigation={{ category: context.packageName, currentItemName: data.name }} + > I am a Import, my name is {data.name} <pre> diff --git a/reference/_pages/Interface.tsx b/reference/_pages/Interface.tsx index ba5f9fcff..281a26173 100644 --- a/reference/_pages/Interface.tsx +++ b/reference/_pages/Interface.tsx @@ -1,27 +1,25 @@ import { DocNodeInterface } from "@deno/doc/types"; -import { LumeDocument, ReferenceContext } from "../types.ts"; +import { HasFullName, LumeDocument, ReferenceContext } from "../types.ts"; import ReferencePage from "../_layouts/ReferencePage.tsx"; -type Props = { data: DocNodeInterface; context: ReferenceContext }; +type Props = { + data: DocNodeInterface & HasFullName; + context: ReferenceContext; +}; export default function* getPages( - item: DocNodeInterface, + item: DocNodeInterface & HasFullName, context: ReferenceContext, ): IterableIterator<LumeDocument> { - const prefix = context.parentName ? `${context.parentName}.` : ""; - yield { title: item.name, - url: `${context.root}/${context.section.toLocaleLowerCase()}/~/${prefix}${item.name}`, + url: + `${context.root}/${context.packageName.toLocaleLowerCase()}/~/${item.fullName}`, content: <Interface data={item} context={context} />, }; } export function Interface({ data, context }: Props) { - const fullName = context.parentName - ? `${context.parentName}.${data.name}` - : data.name; - const isUnstable = data.jsDoc?.tags?.some((tag) => tag.kind === "experimental" as string ); @@ -31,8 +29,14 @@ export function Interface({ data, context }: Props) { )); return ( - <ReferencePage> - <h1>Interface: {fullName}</h1> + <ReferencePage + context={context} + navigation={{ + category: context.packageName, + currentItemName: data.fullName, + }} + > + <h1>Interface: {data.fullName}</h1> {isUnstable && <p>UNSTABLE</p>} {jsDocParagraphs && jsDocParagraphs} diff --git a/reference/_pages/Module.tsx b/reference/_pages/Module.tsx index 835e5445d..8fdb270c0 100644 --- a/reference/_pages/Module.tsx +++ b/reference/_pages/Module.tsx @@ -2,7 +2,7 @@ import { DocNode, DocNodeModuleDoc } from "@deno/doc/types"; import { LumeDocument, ReferenceContext } from "../types.ts"; import ReferencePage from "../_layouts/ReferencePage.tsx"; -type Props = { data: DocNode }; +type Props = { data: DocNode; context: ReferenceContext }; export default function* getPages( item: DocNodeModuleDoc, @@ -11,16 +11,19 @@ export default function* getPages( yield { title: item.name, url: - `${context.root}/${context.section.toLocaleLowerCase()}/${item.name.toLocaleLowerCase()}`, - content: <Module data={item} />, + `${context.root}/${context.packageName.toLocaleLowerCase()}/${item.name.toLocaleLowerCase()}`, + content: <Module data={item} context={context} />, }; console.log("Module found", item); } -export function Module({ data }: Props) { +export function Module({ data, context }: Props) { return ( - <ReferencePage> + <ReferencePage + context={context} + navigation={{ category: context.packageName, currentItemName: data.name }} + > I am a module, my name is {data.name} <pre> diff --git a/reference/_pages/Namespace.tsx b/reference/_pages/Namespace.tsx index 1364f1d4c..bcbba0db1 100644 --- a/reference/_pages/Namespace.tsx +++ b/reference/_pages/Namespace.tsx @@ -1,6 +1,6 @@ import { DocNodeNamespace } from "@deno/doc/types"; import { LumeDocument, ReferenceContext } from "../types.ts"; -import factoryFor from "../pageFactory.ts"; +import generatePageFor from "../pageFactory.ts"; import ReferencePage from "../_layouts/ReferencePage.tsx"; type Props = { data: DocNodeNamespace; context: ReferenceContext }; @@ -12,17 +12,14 @@ export default function* getPages( yield { title: item.name, url: - `${context.root}/${context.section.toLocaleLowerCase()}/${item.name.toLocaleLowerCase()}`, + `${context.root}/${context.packageName.toLocaleLowerCase()}/${item.name.toLocaleLowerCase()}`, content: <Namespace data={item} context={context} />, }; for (const element of item.namespaceDef.elements) { - const factory = factoryFor(element); - - yield* factory(element, { + yield* generatePageFor(element, { ...context, - dataCollection: item.namespaceDef.elements, - parentName: item.name, + symbols: item.namespaceDef.elements, }); } } @@ -37,8 +34,11 @@ export function Namespace({ data, context }: Props) { ); return ( - <ReferencePage> - <h1>Namespace: {context.section} - {data.name}</h1> + <ReferencePage + context={context} + navigation={{ category: context.packageName, currentItemName: data.name }} + > + <h1>Namespace: {context.packageName} - {data.name}</h1> <h2>Classes</h2> <ul> diff --git a/reference/_pages/NotImplemented.tsx b/reference/_pages/NotImplemented.tsx index 6961cbe91..c9c6f2e54 100644 --- a/reference/_pages/NotImplemented.tsx +++ b/reference/_pages/NotImplemented.tsx @@ -1,34 +1,8 @@ -import { DocNode, DocNodeBase } from "@deno/doc/types"; +import { DocNodeBase } from "@deno/doc/types"; import { LumeDocument, ReferenceContext } from "../types.ts"; -import ReferencePage from "../_layouts/ReferencePage.tsx"; - -type Props = { data: DocNode }; export default function* getPages( - item: DocNodeBase, - context: ReferenceContext, + _: DocNodeBase, + __: ReferenceContext, ): IterableIterator<LumeDocument> { - // console.log( - // "Not implemented factory encountered", - // item.name, - // item.kind, - // item.location.filename, - // ); - - // console.log("Context", context); - - // yield { - // title: item.name, - // url: `/${context.root}/${context.section}/~/${item.name}.${item.kind}`, - // content: <NotImplemented data={item} />, - // }; -} - -export function NotImplemented({ data }: Props) { - return ( - <ReferencePage> - I am not yet implemented, but I am supposed to represent:{" "} - {JSON.stringify(data)} - </ReferencePage> - ); } diff --git a/reference/_pages/Package.tsx b/reference/_pages/Package.tsx new file mode 100644 index 000000000..b492bc1e6 --- /dev/null +++ b/reference/_pages/Package.tsx @@ -0,0 +1,77 @@ +import ReferencePage from "../_layouts/ReferencePage.tsx"; +import { ReferenceContext } from "../types.ts"; +import { AnchorableHeading } from "./primatives/AnchorableHeading.tsx"; +import { insertLinkCodes } from "./primatives/LinkCode.tsx"; + +type Props = { + data: Record<string, string | undefined>; + context: ReferenceContext; +}; + +export function Package({ data, context }: Props) { + const categoryListItems = Object.entries(data).map(([key, value]) => { + return ( + <CategoryListSection + title={key} + href={`${context.root}/${context.packageName.toLocaleLowerCase()}/${key.toLocaleLowerCase()}`} + summary={value || ""} + /> + ); + }); + + return ( + <ReferencePage + context={context} + navigation={{ + category: context.packageName, + currentItemName: "", + }} + > + <main> + <section> + <div class="space-y-2 flex-1 "> + <div class="space-y-7" id="module_doc"></div> + </div> + </section> + <div className={"space-y-7"}> + {categoryListItems} + </div> + </main> + </ReferencePage> + ); +} + +function CategoryListSection( + { title, href, summary }: { title: string; href: string; summary: string }, +) { + const anchorId = title.replace(" ", "-").toLocaleLowerCase(); + + const parts = summary.split("\n\n"); + const examplePart = parts[parts.length - 1]; + const partsExceptLast = parts.slice(0, parts.length - 1); + const summaryBody = partsExceptLast.join("\n\n"); + + const exampleBody = insertLinkCodes(examplePart); + const summaryBodyParas = summaryBody.split("\n\n").map((paragraph) => ( + <p>{paragraph}</p> + )); + + return ( + <section id={anchorId} className={"section"}> + <AnchorableHeading anchor={anchorId}> + <a href={href}>{title}</a> + </AnchorableHeading> + <div + data-color-mode="auto" + data-light-theme="light" + data-dark-theme="dark" + class="markdown-body" + > + {summaryBodyParas} + <p> + {exampleBody} + </p> + </div> + </section> + ); +} diff --git a/reference/_pages/TypeAlias.tsx b/reference/_pages/TypeAlias.tsx index 14cd93265..3094f6a44 100644 --- a/reference/_pages/TypeAlias.tsx +++ b/reference/_pages/TypeAlias.tsx @@ -1,24 +1,30 @@ import { DocNodeTypeAlias } from "@deno/doc/types"; -import { LumeDocument, ReferenceContext } from "../types.ts"; +import { HasFullName, LumeDocument, ReferenceContext } from "../types.ts"; import ReferencePage from "../_layouts/ReferencePage.tsx"; -type Props = { data: DocNodeTypeAlias }; +type Props = { + data: DocNodeTypeAlias & HasFullName; + context: ReferenceContext; +}; export default function* getPages( - item: DocNodeTypeAlias, + item: DocNodeTypeAlias & HasFullName, context: ReferenceContext, ): IterableIterator<LumeDocument> { - const prefix = context.parentName ? `${context.parentName}.` : ""; yield { title: item.name, - url: `${context.root}/${context.section.toLocaleLowerCase()}/~/${prefix}${item.name}`, - content: <TypeAlias data={item} />, + url: + `${context.root}/${context.packageName.toLocaleLowerCase()}/~/${item.fullName}`, + content: <TypeAlias data={item} context={context} />, }; } -export function TypeAlias({ data }: Props) { +export function TypeAlias({ data, context }: Props) { return ( - <ReferencePage> + <ReferencePage + context={context} + navigation={{ category: context.packageName, currentItemName: data.name }} + > I am a type alias, my name is {data.name} {data.jsDoc?.doc && <p>{data.jsDoc?.doc}</p>} diff --git a/reference/_pages/Variable.tsx b/reference/_pages/Variable.tsx index 05615e2d0..ec50e25ea 100644 --- a/reference/_pages/Variable.tsx +++ b/reference/_pages/Variable.tsx @@ -1,25 +1,27 @@ import { DocNodeVariable } from "@deno/doc/types"; -import { LumeDocument, ReferenceContext } from "../types.ts"; +import { HasFullName, LumeDocument, ReferenceContext } from "../types.ts"; import ReferencePage from "../_layouts/ReferencePage.tsx"; -type Props = { data: DocNodeVariable }; +type Props = { data: DocNodeVariable & HasFullName; context: ReferenceContext }; export default function* getPages( - item: DocNodeVariable, + item: DocNodeVariable & HasFullName, context: ReferenceContext, ): IterableIterator<LumeDocument> { - const prefix = context.parentName ? `${context.parentName}.prototype.` : ""; yield { title: item.name, url: - `${context.root}/${context.section.toLocaleLowerCase()}/~/${prefix}${item.name}`, - content: <Variable data={item} />, + `${context.root}/${context.packageName.toLocaleLowerCase()}/~/${item.fullName}`, + content: <Variable data={item} context={context} />, }; } -export function Variable({ data }: Props) { +export function Variable({ data, context }: Props) { return ( - <ReferencePage> + <ReferencePage + context={context} + navigation={{ category: context.packageName, currentItemName: data.name }} + > I am a variable, my name is {data.name} {data.jsDoc?.doc && <p>{data.jsDoc?.doc}</p>} diff --git a/reference/_pages/primatives/AnchorableHeading.tsx b/reference/_pages/primatives/AnchorableHeading.tsx new file mode 100644 index 000000000..5a569911a --- /dev/null +++ b/reference/_pages/primatives/AnchorableHeading.tsx @@ -0,0 +1,41 @@ +import React from "npm:@preact/compat"; + +export function AnchorableHeading( + { children, anchor }: { children: React.ReactNode; anchor: string }, +) { + const anchorValue = `#${anchor}`; + const clipPath = `url(${anchorValue})`; + const headerId = anchorValue; + + return ( + <div> + <h2 id={headerId} class="anchorable mb-1"> + <a href={anchorValue} class="anchor" aria-label="Anchor"> + <svg + width="16" + height="16" + viewBox="0 0 14 14" + fill="none" + xmlns="http://www.w3.org/2000/svg" + > + <g clip-path={clipPath}> + <path + fill-rule="evenodd" + clip-rule="evenodd" + d="M6.80328 2.8656C6.68736 2.99 6.62425 3.15454 6.62725 3.32456C6.63025 3.49457 6.69913 3.65678 6.81936 3.77702C6.9396 3.89725 7.10181 3.96613 7.27182 3.96913C7.44184 3.97213 7.60638 3.90902 7.73078 3.7931L8.82453 2.69935C8.98712 2.53676 9.18015 2.40778 9.39259 2.31978C9.60503 2.23179 9.83272 2.1865 10.0627 2.1865C10.2926 2.1865 10.5203 2.23179 10.7327 2.31978C10.9452 2.40778 11.1382 2.53676 11.3008 2.69935C11.4634 2.86194 11.5923 3.05497 11.6803 3.2674C11.7683 3.47984 11.8136 3.70753 11.8136 3.93747C11.8136 4.16741 11.7683 4.3951 11.6803 4.60754C11.5923 4.81998 11.4634 5.013 11.3008 5.1756L9.11328 7.3631C8.95075 7.52581 8.75775 7.65488 8.5453 7.74295C8.33285 7.83102 8.10513 7.87635 7.87516 7.87635C7.64518 7.87635 7.41746 7.83102 7.20501 7.74295C6.99256 7.65488 6.79956 7.52581 6.63703 7.3631C6.51263 7.24718 6.34809 7.18407 6.17807 7.18707C6.00806 7.19007 5.84585 7.25894 5.72561 7.37918C5.60538 7.49942 5.5365 7.66163 5.5335 7.83164C5.5305 8.00165 5.59361 8.1662 5.70953 8.2906C5.99391 8.57501 6.33154 8.80062 6.70312 8.95455C7.0747 9.10847 7.47296 9.18769 7.87516 9.18769C8.27736 9.18769 8.67562 9.10847 9.0472 8.95455C9.41878 8.80062 9.7564 8.57501 10.0408 8.2906L12.2283 6.1031C12.8026 5.52874 13.1253 4.74974 13.1253 3.93747C13.1253 3.12521 12.8026 2.34621 12.2283 1.77185C11.6539 1.19749 10.8749 0.874817 10.0627 0.874817C9.25039 0.874817 8.47139 1.19749 7.89703 1.77185L6.80328 2.8656ZM2.69953 11.3006C2.53682 11.1381 2.40774 10.9451 2.31968 10.7326C2.23161 10.5202 2.18628 10.2924 2.18628 10.0625C2.18628 9.8325 2.23161 9.60477 2.31968 9.39233C2.40774 9.17988 2.53682 8.98688 2.69953 8.82435L4.88703 6.63685C5.04956 6.47414 5.24256 6.34506 5.45501 6.25699C5.66746 6.16893 5.89518 6.1236 6.12516 6.1236C6.35513 6.1236 6.58285 6.16893 6.7953 6.25699C7.00775 6.34506 7.20075 6.47414 7.36328 6.63685C7.48768 6.75277 7.65223 6.81588 7.82224 6.81288C7.99225 6.80988 8.15446 6.741 8.2747 6.62077C8.39494 6.50053 8.46381 6.33832 8.46681 6.1683C8.46981 5.99829 8.4067 5.83375 8.29078 5.70935C8.0064 5.42494 7.66878 5.19933 7.2972 5.0454C6.92562 4.89148 6.52736 4.81225 6.12516 4.81225C5.72296 4.81225 5.3247 4.89148 4.95312 5.0454C4.58154 5.19933 4.24391 5.42494 3.95953 5.70935L1.77203 7.89685C1.19767 8.47121 0.875 9.25021 0.875 10.0625C0.875 10.8747 1.19767 11.6537 1.77203 12.2281C2.34639 12.8025 3.12539 13.1251 3.93766 13.1251C4.74992 13.1251 5.52892 12.8025 6.10328 12.2281L7.19703 11.1343C7.31295 11.0099 7.37606 10.8454 7.37306 10.6754C7.37006 10.5054 7.30119 10.3432 7.18095 10.2229C7.06071 10.1027 6.8985 10.0338 6.72849 10.0308C6.55848 10.0278 6.39393 10.0909 6.26953 10.2068L5.17578 11.3006C5.01325 11.4633 4.82025 11.5924 4.6078 11.6805C4.39535 11.7685 4.16763 11.8138 3.93766 11.8138C3.70768 11.8138 3.47996 11.7685 3.26751 11.6805C3.05506 11.5924 2.86206 11.4633 2.69953 11.3006Z" + fill="currentColor" + > + </path> + </g> + <defs> + <clipPath id={anchorValue + "-clip"}> + <rect width="14" height="14" fill="white"></rect> + </clipPath> + </defs> + </svg> + </a> + {children} + </h2> + </div> + ); +} diff --git a/reference/_pages/primatives/CodeIcon.tsx b/reference/_pages/primatives/CodeIcon.tsx new file mode 100644 index 000000000..5f581738b --- /dev/null +++ b/reference/_pages/primatives/CodeIcon.tsx @@ -0,0 +1,45 @@ +import { DocNodeKind } from "@deno/doc/types"; + +export function CodeIcon({ glyph }: { glyph: DocNodeKind }) { + let classStyle = ""; + switch (glyph) { + case "class": + classStyle = "text-Class bg-Class/15"; + break; + case "function": + classStyle = "text-Function bg-Function/15"; + break; + case "interface": + classStyle = "text-Interface bg-Interface/15"; + break; + case "typeAlias": + classStyle = "text-TypeAlias bg-TypeAlias/15"; + break; + } + + let title = ""; + switch (glyph) { + case "class": + title = "c"; + break; + case "function": + title = "f"; + break; + case "interface": + title = "I"; + break; + case "typeAlias": + title = "T"; + break; + } + + if (classStyle === "") { + return <></>; + } + + return ( + <div class="docNodeKindIcon"> + <div class={classStyle} title={glyph}>{title}</div> + </div> + ); +} diff --git a/reference/_pages/primatives/LinkCode.tsx b/reference/_pages/primatives/LinkCode.tsx new file mode 100644 index 000000000..91377e91e --- /dev/null +++ b/reference/_pages/primatives/LinkCode.tsx @@ -0,0 +1,59 @@ +export function LinkCode({ symbol }: { symbol: string }) { + const target = "~/" + symbol; + + return ( + <a href={target}> + <code>{symbol}</code> + </a> + ); +} + +export function insertLinkCodes(text: string) { + // replace each text occurance of {@linkcode foo} with <LinkCode symbol="foo" /> + const tokens = text.split(/(\{@linkcode [^\}]+\})/); + const firstLineOfJsDocWithLinks = tokens.map((token, index) => { + if (token.startsWith("{@linkcode ")) { + const symbol = token.slice(11, -1); + return <LinkCode symbol={symbol} key={index} />; + } + return token; + }); + + const merge = firstLineOfJsDocWithLinks.reduce( + // deno-lint-ignore no-explicit-any + (acc: any, curr: any) => { + return acc === null ? [curr] : [...acc, " ", curr]; + }, + null, + ); + + return merge; +} + +export function linkCodeAndParagraph(text: string) { + // deno-lint-ignore no-explicit-any + // const withLinks: any = insertLinkCodes(text); + + // for (const index in withLinks) { + // const current = withLinks[index]; + + // if ( + // typeof current === "string" && current.includes("\n\n") + // ) { + // const withBreaks = current.split("\n\n").map((line: string) => { + // return ( + // <> + // {line} + // <br /> + // <br /> + // </> + // ); + // }); + + // withLinks.splice(index, 1, withBreaks); + // } + // } + + // TODO: fixme + return insertLinkCodes(text); +} diff --git a/reference/_util/common.ts b/reference/_util/common.ts index 4b8f46658..9d600777c 100644 --- a/reference/_util/common.ts +++ b/reference/_util/common.ts @@ -1,29 +1,48 @@ import { DocNode, DocNodeBase } from "@deno/doc/types"; -import { HasNamespace, MightHaveNamespace } from "../types.ts"; +import { HasFullName, HasNamespace, MightHaveNamespace } from "../types.ts"; -export function flattenItems(items: (DocNode & MightHaveNamespace)[]): (DocNodeBase & MightHaveNamespace)[] { - return populateItemNamespaces(items, true); +export function flattenItems( + items: DocNode[], +): (DocNodeBase & MightHaveNamespace)[] { + const flattened: (DocNodeBase)[] = []; + for (const item of items) { + if (item.kind === "namespace") { + flattened.push(...flattenItems(item.namespaceDef.elements)); + } else { + flattened.push(item); + } + } + return flattened; } -export function populateItemNamespaces(items: DocNode[], populateNamespace = true, ns = ""): (DocNodeBase & HasNamespace)[] { - const flattened: (DocNodeBase & HasNamespace)[] = []; +export function populateItemNamespaces( + items: DocNode[], + populateNamespace = true, + ns = "", +): (DocNodeBase & HasNamespace)[] { + const flattened: (DocNodeBase & HasNamespace)[] = []; - for (const item of items) { + for (const item of items) { + const withFullName = item as DocNodeBase & HasFullName; + withFullName.fullName = item.name; - if (item.kind === "namespace") { - const namespace = ns + (ns ? "." : "") + item.name; - flattened.push(...populateItemNamespaces(item.namespaceDef.elements, populateNamespace, namespace)); - } - else { - const withNamespace = { ...item, namespace: "" } as DocNodeBase & HasNamespace; - withNamespace.namespace = ns; - withNamespace.fullName = ns - ? `${ns}.${item.name}` - : item.name; + if (item.kind === "namespace") { + const namespace = ns + (ns ? "." : "") + item.name; + flattened.push( + ...populateItemNamespaces( + item.namespaceDef.elements, + populateNamespace, + namespace, + ), + ); + } else { + const withNamespace = item as DocNodeBase & HasNamespace; + withNamespace.namespace = ns; + withNamespace.fullName = ns ? `${ns}.${item.name}` : item.name; - flattened.push(withNamespace); - } + flattened.push(withNamespace); } + } - return flattened; + return flattened; } diff --git a/reference/_util/methodSignatureRendering.ts b/reference/_util/methodSignatureRendering.ts new file mode 100644 index 000000000..53e14c973 --- /dev/null +++ b/reference/_util/methodSignatureRendering.ts @@ -0,0 +1,95 @@ +import { + ClassMethodDef, + ParamDef, + ParamIdentifierDef, + ParamRestDef, + TsTypeDef, +} from "@deno/doc/types"; + +export function methodSignature(method: ClassMethodDef) { + const parts = []; + if (method.accessibility) { + parts.push(method.accessibility); + } + + if (method.isStatic) { + parts.push("static "); + } + + if (method.isAbstract) { + parts.push("abstract "); + } + + if (method.isOverride) { + parts.push("override "); + } + + parts.push(method.functionDef.defName || method.name); + + if (method.functionDef.params.length > 0) { + console.log(method.functionDef); + const params = method.functionDef.params.map((param) => ( + methodParameter(param) + )); + + parts.push(`(${params.join(", ")})`); + } else { + parts.push("()"); + } + + return parts.join(""); +} + +export function methodParameter(param: ParamDef): string { + console.log(param); + const parts = []; + + if (param.kind === "rest") { + parts.push(restParameter(param)); + } + + if (param.kind === "identifier") { + parts.push(identifier(param)); + } + + if (param.kind === "array") { + parts.push("[]"); + } + + return parts.join(""); +} + +export function restParameter(param: ParamRestDef) { + const parts = []; + parts.push("..."); + parts.push(methodParameter(param.arg)); + return parts.join(""); +} + +export function identifier(param: ParamIdentifierDef) { + const parts = []; + parts.push(param.name); + if (param.optional) { + parts.push("?"); + } + + if (param.tsType) { + parts.push(typeInformation(param.tsType)); + } + return parts.join(""); +} + +export function typeInformation(type: TsTypeDef | undefined) { + if (!type) { + return ""; + } + + const parts = []; + parts.push(": "); + parts.push(type.repr); + if (type.kind === "array") { + parts.push("[]"); + } + + return parts.join(""); +} diff --git a/reference/pageFactory.ts b/reference/pageFactory.ts index b0f729d3b..9a4797116 100644 --- a/reference/pageFactory.ts +++ b/reference/pageFactory.ts @@ -1,4 +1,4 @@ -import { ReferenceDocumentFactoryFunction } from "./types.ts"; +import { ReferenceContext, ReferenceDocumentFactoryFunction } from "./types.ts"; import getPagesForNamespace from "./_pages/Namespace.tsx"; import getPagesForNotImplemented from "./_pages/NotImplemented.tsx"; import getPagesForModule from "./_pages/Module.tsx"; @@ -10,17 +10,57 @@ import getPagesForTypeAlias from "./_pages/TypeAlias.tsx"; import getPagesForVariable from "./_pages/Variable.tsx"; import { DocNodeBase } from "@deno/doc/types"; -const factories = new Map<string, ReferenceDocumentFactoryFunction<DocNodeBase>>(); -factories.set("moduleDoc", getPagesForModule as ReferenceDocumentFactoryFunction<DocNodeBase>); -factories.set("namespace", getPagesForNamespace as ReferenceDocumentFactoryFunction<DocNodeBase>); -factories.set("function", getPagesForFunction as ReferenceDocumentFactoryFunction<DocNodeBase>); -factories.set("variable", getPagesForVariable as ReferenceDocumentFactoryFunction<DocNodeBase>); -factories.set("enum", getPagesForNotImplemented as ReferenceDocumentFactoryFunction<DocNodeBase>); -factories.set("class", getPagesForClass as ReferenceDocumentFactoryFunction<DocNodeBase>); -factories.set("typeAlias", getPagesForTypeAlias as ReferenceDocumentFactoryFunction<DocNodeBase>); -factories.set("interface", getPagesForInterface as ReferenceDocumentFactoryFunction<DocNodeBase>); -factories.set("import", getPagesForImport as ReferenceDocumentFactoryFunction<DocNodeBase>); +const factories = new Map< + string, + ReferenceDocumentFactoryFunction<DocNodeBase> +>(); +factories.set( + "moduleDoc", + getPagesForModule as ReferenceDocumentFactoryFunction<DocNodeBase>, +); +factories.set( + "namespace", + getPagesForNamespace as ReferenceDocumentFactoryFunction<DocNodeBase>, +); +factories.set( + "function", + getPagesForFunction as ReferenceDocumentFactoryFunction<DocNodeBase>, +); +factories.set( + "variable", + getPagesForVariable as ReferenceDocumentFactoryFunction<DocNodeBase>, +); +factories.set( + "enum", + getPagesForNotImplemented as ReferenceDocumentFactoryFunction<DocNodeBase>, +); +factories.set( + "class", + getPagesForClass as ReferenceDocumentFactoryFunction<DocNodeBase>, +); +factories.set( + "typeAlias", + getPagesForTypeAlias as ReferenceDocumentFactoryFunction<DocNodeBase>, +); +factories.set( + "interface", + getPagesForInterface as ReferenceDocumentFactoryFunction<DocNodeBase>, +); +factories.set( + "import", + getPagesForImport as ReferenceDocumentFactoryFunction<DocNodeBase>, +); -export default function factoryFor<T extends DocNodeBase>(item: T): ReferenceDocumentFactoryFunction<T> { - return factories.get(item.kind) || getPagesForNotImplemented; -} \ No newline at end of file +function factoryFor<T extends DocNodeBase>( + item: T, +): ReferenceDocumentFactoryFunction<T> { + return factories.get(item.kind) || getPagesForNotImplemented; +} + +export default function generatePageFor<T extends DocNodeBase>( + item: T, + context: ReferenceContext, +) { + const factory = factoryFor(item); + return factory(item, context) || []; +} diff --git a/reference/reference.page.raw.tsx b/reference/reference.page.raw.tsx index 88ca4b79c..9793bfd77 100644 --- a/reference/reference.page.raw.tsx +++ b/reference/reference.page.raw.tsx @@ -1,19 +1,30 @@ -import { doc } from "@deno/doc"; -import registrations from "../reference_gen/registrations.ts"; -import factoryFor from "./pageFactory.ts"; +import generatePageFor from "./pageFactory.ts"; import getCategoryPages from "./_pages/Category.tsx"; -import { flattenItems, populateItemNamespaces } from "./_util/common.ts"; +import { populateItemNamespaces } from "./_util/common.ts"; +import { getSymbols } from "./_dataSources/dtsSymbolSource.ts"; +import webCategoryDocs from "./_categories/web-categories.json" with { + type: "json", +}; +import denoCategoryDocs from "./_categories/deno-categories.json" with { + type: "json", +}; +import { DocNode } from "@deno/doc/types"; export const layout = "raw.tsx"; -const root = "/new_api"; + +const root = "/api"; +const sections = [ + { name: "Deno APIs", path: "deno", categoryDocs: denoCategoryDocs }, + { name: "Web APIs", path: "web", categoryDocs: webCategoryDocs }, + { name: "Node APIs", path: "node", categoryDocs: undefined }, +]; export const sidebar = [ { - items: [ - { label: "Deno APIs", id: `${root}/deno/` }, - { label: "Web APIs", id: `${root}/web/` }, - { label: "Node APIs", id: `${root}/node/` }, - ], + items: sections.map((section) => ({ + label: section.name, + id: `${root}/${section.path}/`, + })), }, ]; @@ -25,54 +36,37 @@ export default async function* () { throw new Error(); } - const sources = referenceItems(); - const referenceDocContext = { - categories: sidebar[0].items, - }; + for await (const { packageName, symbols } of getSymbols()) { + const cleanedSymbols = populateItemNamespaces(symbols) as DocNode[]; - for await (const { key, dataCollection, generateOptions } of sources) { - populateItemNamespaces(dataCollection); - - const section = generateOptions.packageName || "unknown"; - const definitionFile = key; + const currentCategoryList = sections.filter((x) => + x.path === packageName.toLocaleLowerCase() + )[0]!.categoryDocs as Record<string, string | undefined>; const context = { root, - section, - dataCollection, - definitionFile, - parentName: "", - referenceDocContext, + packageName, + symbols: cleanedSymbols, + currentCategoryList: currentCategoryList, }; - for ( - const catPage of getCategoryPages( - generateOptions.categoryDocs!, - context, - ) - ) { - yield catPage; - generated.push(catPage.url); + for (const p of getCategoryPages(context)) { + yield p; + generated.push(p.url); } - for (const item of dataCollection) { - const factory = factoryFor(item); - const pages = factory(item, context); - - if (!pages) { - continue; - } + for (const item of cleanedSymbols) { + const pages = generatePageFor(item, context); for await (const page of pages) { if (generated.includes(page.url)) { - console.warn( - `⚠️ Skipping duplicate page: ${page.url} - NEED TO MERGE THESE DOCS`, - ); + console.warn(`⚠️ Skipping duplicate page: ${page.url}!`); continue; } yield page; generated.push(page.url); + console.log("Generated", page.url); } } } @@ -82,32 +76,3 @@ export default async function* () { console.log("Generated", generated.length, "reference pages"); } - -async function* referenceItems() { - for (const { sources, generateOptions } of registrations) { - const paths = sources.map((file) => { - if (!file.startsWith("./")) { - return `file://${file}`; - } else { - const newPath = file.replace("./", "../reference_gen/"); - return import.meta.resolve(newPath); - } - }); - - const docs = await loadDocumentation(paths); - - for (const key of Object.keys(docs)) { - const dataCollection = docs[key]; - yield { key, dataCollection, generateOptions }; - } - } -} - -async function loadDocumentation(paths: string[]) { - const docGenerationPromises = paths.map(async (path) => { - return await doc([path]); - }); - - const nodes = await Promise.all(docGenerationPromises); - return nodes.reduce((acc, val) => ({ ...acc, ...val }), {}); -} diff --git a/reference/types.ts b/reference/types.ts index e55c286f2..f2b2008ad 100644 --- a/reference/types.ts +++ b/reference/types.ts @@ -2,37 +2,46 @@ import { DocNode, DocNodeBase } from "@deno/doc/types"; import { JSX } from "npm:preact/jsx-runtime"; export type LumeDocument = { - title: string; - url: string; - content: JSX.Element; + title: string; + url: string; + content: JSX.Element; +}; + +export interface HasFullName { + fullName: string; } -export type HasNamespace = { - namespace: string; - fullName: string; +export interface HasNamespace extends HasFullName { + namespace: string; + fullName: string; } -export type MightHaveNamespace = { - namespace?: string; - fullName?: string; +export interface MightHaveNamespace { + namespace?: string; + fullName?: string; } export type ReferenceDocCategory = { - id: string; - label: string; -} + id: string; + label: string; +}; export type ReferenceDocContext = { - categories: ReferenceDocCategory[]; -} + categories: ReferenceDocCategory[]; +}; + +export type Navigation = { + category: string; + currentItemName: string; +}; export type ReferenceContext = { - root: string; - section: string; - dataCollection: DocNode[]; - definitionFile: string; - parentName: string; - referenceDocContext: ReferenceDocContext; -} + root: string; + packageName: string; + symbols: DocNode[]; + currentCategoryList: Record<string, string | undefined>; +}; -export type ReferenceDocumentFactoryFunction<T extends DocNodeBase = DocNodeBase> = (item: T, context: ReferenceContext) => IterableIterator<LumeDocument>; +export type ReferenceDocumentFactoryFunction< + T extends DocNodeBase = DocNodeBase, +> = (item: T, context: ReferenceContext) => IterableIterator<LumeDocument>; diff --git a/reference_gen/deno.jsonc b/reference_gen/deno.jsonc index 774b5032b..379a207b8 100644 --- a/reference_gen/deno.jsonc +++ b/reference_gen/deno.jsonc @@ -20,7 +20,7 @@ "types:deno": "deno run --allow-read --allow-write --allow-run --allow-env --allow-sys deno-types.ts", "types:node": "deno run --allow-read --allow-write=. --allow-env --allow-sys node-types.ts", "types": "deno task types:deno && deno task types:node", - "doc": "deno run -A doc.ts" + "doc": "" }, "exclude": [ "types", diff --git a/reference_gen/doc.ts b/reference_gen/doc.ts deleted file mode 100644 index 709fec026..000000000 --- a/reference_gen/doc.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { doc, DocNode, generateHtml, GenerateOptions } from "@deno/doc"; -import { encodeHex } from "jsr:@std/encoding/hex"; -import registrations from "./registrations.ts"; -import { fileExists, writeFiles } from "./common.ts"; - -console.time("doc"); -console.timeLog("doc", "Generating doc nodes..."); - -for (const { sources, generateOptions } of registrations) { - console.timeLog("doc", `Processing ${generateOptions.packageName}...`); - - const paths = resolveFilePaths(sources); - const docs = await loadDocumentation(paths); - const metadata = await generateFileMetadata(docs, generateOptions); - - if (await fileExists(metadata.hashPath)) { - console.timeLog("doc", `Checking ${metadata.fileIdentifier} for changes...`); - const existingHash = await Deno.readTextFile(metadata.hashPath); - - if (existingHash === metadata.objectHash) { - console.timeLog("doc", `Skipping ${metadata.fileIdentifier} because source hash and previously generated file hash match exactly. Delete the hash file ${metadata.hashPath} to force regeneration.`); - continue; - } - } - - const files = await generateFiles(docs, generateOptions); - await writeFiles("gen/" + metadata.fileIdentifier, files); - await Deno.writeTextFile(metadata.hashPath, metadata.objectHash); -} - -console.timeEnd("doc"); - -function resolveFilePaths(sources: string[]) { - return sources.map((file) => - file.startsWith("./") ? import.meta.resolve(file) : `file://${file}` - ); -} - -async function loadDocumentation(paths: string[]) { - const docGenerationPromises = paths.map(async (path) => { - return await doc([path]); - }); - - const nodes = await Promise.all(docGenerationPromises); - return nodes.reduce((acc, val) => ({ ...acc, ...val }), {}); -} - -async function generateFileMetadata(nodes: Record<string, DocNode[]>, generateOptions: GenerateOptions) { - const fileIdentifier = generateOptions.packageName?.toLowerCase()!; - const filePath = `./gen/${fileIdentifier}.json`; - const hashPath = `./gen/${fileIdentifier}.hash`; - const objectHash = await hashObject(nodes); - return { fileIdentifier, filePath, hashPath, objectHash }; -} - -async function generateFiles(nodes: Record<string, DocNode[]>, generateOptions: GenerateOptions) { - console.timeLog("doc", "Generating files..."); - - const fileGenerationPromises = Object.keys(nodes).map(async (key) => { - const data = Object.fromEntries([[key, nodes[key]]]); - const html = await generateHtml(data, generateOptions); - - console.timeLog("doc", `Generated ${key}.`); - return html; - }); - - const allGeneratedFiles = await Promise.all(fileGenerationPromises); - return allGeneratedFiles.reduce((acc, val) => ({ ...acc, ...val }), {}); -} - -// deno-lint-ignore no-explicit-any -async function hashObject(obj: any) { - const messageBuffer = new TextEncoder().encode(JSON.stringify(obj)); - const hashBuffer = await crypto.subtle.digest("SHA-256", messageBuffer); - return encodeHex(hashBuffer); -} - diff --git a/static/reference-styles/page.css b/static/reference-styles/page.css new file mode 100644 index 000000000..ea2bcb610 --- /dev/null +++ b/static/reference-styles/page.css @@ -0,0 +1,234 @@ +.sticky { + position: sticky; +} +.top-0 { + top: 0; +} +.z-50 { + z-index: 50; +} +.block { + display: block; +} +.flex { + display: flex; +} +.h-14 { + height: 3.5rem; +} +.h-full { + height: 100%; +} +.items-center { + align-items: center; +} +.justify-between { + justify-content: space-between; +} +.gap-2 { + gap: 0.5rem; +} +.gap-2\.5 { + gap: 0.625rem; +} +.gap-4 { + gap: 1rem; +} +.rounded { + border-radius: 0.25rem; +} +.rounded-lg { + border-radius: 0.5rem; +} +.border { + border-width: 1px; +} +.border-gray-300 { + --tw-border-opacity: 1; + border-color: rgb(209 213 219/var(--tw-border-opacity)); +} +.bg-transparent { + background-color: #0000; +} +.bg-white { + --tw-bg-opacity: 1; + background-color: rgb(255 255 255/var(--tw-bg-opacity)); +} +.px-2 { + padding-left: 0.5rem; + padding-right: 0.5rem; +} +.px-2\.5 { + padding-left: 0.625rem; + padding-right: 0.625rem; +} +.px-3 { + padding-left: 0.75rem; + padding-right: 0.75rem; +} +.py-2 { + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} +.py-3 { + padding-top: 0.75rem; + padding-bottom: 0.75rem; +} +.text-sm { + font-size: 0.875rem; + line-height: 1.25rem; +} +.leading-none { + line-height: 1; +} +.blur { + --tw-blur: blur(8px); + filter: var(--tw-blur)var(--tw-brightness)var(--tw-contrast)var( + --tw-grayscale + )var(--tw-hue-rotate)var(--tw-invert)var(--tw-saturate)var(--tw-sepia)var( + --tw-drop-shadow + ); +} +.ddoc { + align-items: flex-start; + gap: 1.5rem; + min-height: fit-content; + padding: 1rem; + display: flex; +} +@media (min-width: 1024px) { + .ddoc:has(#categoryPanel) { + padding: 0.5rem; + } +} +@media (min-width: 1024px) { + .ddoc:has(#categoryPanel) > div:not(#categoryPanel) { + padding-top: 1rem; + } + .ddoc:has(#categoryPanel) #topnav { + margin-left: -1.5rem !important; + padding-left: 1.5rem !important; + } +} +.ddoc:not(:has(#categoryPanel)), +.ddoc:has(#categoryPanel) > div:not(#categoryPanel) { + padding-top: 0.25rem; + padding-left: 1.5rem; + padding-right: 1.5rem; +} +.ddoc > div:not(#categoryPanel) { + flex-direction: column; + flex-grow: 1; + display: flex; +} +#categoryPanel { + box-sizing: border-box; + flex-shrink: 0; + width: 250px; + height: 100vh; + margin-top: 0; + padding-top: 0; + position: sticky; + top: 0; +} +@media not all and (min-width: 1024px) { + #categoryPanel { + display: none; + } +} +#categoryPanel > ul { + max-height: 100%; + overflow-y: auto; +} +#content { + margin-top: 1rem; +} +#content > main { + flex-direction: column; + flex-grow: 1; + grid-column: 1/-1; + gap: 0.75rem; + min-width: 0; + padding-bottom: 0; + display: flex; +} +@media (min-width: 768px) { + #content > main { + padding-bottom: 2rem; + } +} +@media (min-width: 1024px) { + #content > main { + padding-bottom: 3rem; + } + #content:has(.toc) > main { + grid-column: span 3/span 3; + grid-row-start: 1; + } +} +#topnav { + margin-left: -1rem; + padding-left: 1rem; +} +#content, #topnav > div { + flex-direction: row; + justify-content: space-between; + gap: 2rem; + display: flex; +} +@media (min-width: 1024px) { + #content, #topnav > div { + gap: 3rem; + } +} +.toc, #searchbar { + flex-shrink: 0; + min-width: 250px; + max-width: 300px; +} +.toc { + box-sizing: border-box; + row-gap: 1rem; + height: fit-content; + max-height: 100vh; + margin-top: -3.5rem; + padding-top: 3.5rem; + position: sticky; + top: 0; +} +@media not all and (min-width: 1024px) { + .toc { + grid-row-start: 1; + } +} +@media not all and (min-width: 640px) { + .toc { + display: none; + } +} +@media (min-width: 640px) { + .toc { + flex-direction: column; + display: flex; + } +} +@media (min-width: 1024px) { + .toc { + grid-column: span 1/-1; + } +} +.toc > div { + max-height: 100%; +} +@media (min-width: 1024px) { + .toc > div { + overflow-y: auto; + } +} +.toc > div > :last-child { + padding-bottom: 1rem; +} +.hover\:bg-stone-100:hover { + --tw-bg-opacity: 1; + background-color: rgb(245 245 244/var(--tw-bg-opacity)); +} diff --git a/static/reference-styles/styles.css b/static/reference-styles/styles.css new file mode 100644 index 000000000..7551552d8 --- /dev/null +++ b/static/reference-styles/styles.css @@ -0,0 +1,1252 @@ +.ddoc .container { + width: 100%; +} +@media (min-width: 640px) { + .ddoc .container { + max-width: 640px; + } +} +@media (min-width: 768px) { + .ddoc .container { + max-width: 768px; + } +} +@media (min-width: 1024px) { + .ddoc .container { + max-width: 1024px; + } +} +@media (min-width: 1280px) { + .ddoc .container { + max-width: 1280px; + } +} +@media (min-width: 1536px) { + .ddoc .container { + max-width: 1536px; + } +} +.ddoc .static { + position: static; +} +.ddoc .relative { + position: relative; +} +.ddoc .\!mb-0 { + margin-bottom: 0 !important; +} +.ddoc .\!mt-2 { + margin-top: 0.5rem !important; +} +.ddoc .mb-1 { + margin-bottom: 0.25rem; +} +.ddoc .ml-4 { + margin-left: 1rem; +} +.ddoc .ml-indent { + margin-left: 2ch; +} +.ddoc .mr-2 { + margin-right: 0.5rem; +} +.ddoc .mt-3 { + margin-top: 0.75rem; +} +.ddoc .inline { + display: inline; +} +.ddoc .\!flex { + display: flex !important; +} +.ddoc .flex { + display: flex; +} +.ddoc .inline-flex { + display: inline-flex; +} +.ddoc .table { + display: table; +} +.ddoc .contents { + display: contents; +} +.ddoc .hidden { + display: none; +} +.ddoc .h-4 { + height: 1rem; +} +.ddoc .h-5 { + height: 1.25rem; +} +.ddoc .min-w-0 { + min-width: 0; +} +.ddoc .max-w-\[75ch\] { + max-width: 75ch; +} +.ddoc .flex-1 { + flex: 1; +} +.ddoc .flex-none { + flex: none; +} +.ddoc .rotate-90 { + --tw-rotate: 90deg; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) + rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) + scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} +.ddoc .scroll-mt-16 { + scroll-margin-top: 4rem; +} +.ddoc .items-center { + align-items: center; +} +.ddoc .gap-0 { + gap: 0; +} +.ddoc .gap-0\.5 { + gap: 0.125rem; +} +.ddoc .gap-1 { + gap: 0.25rem; +} +.ddoc .space-x-1 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(0.25rem * var(--tw-space-x-reverse)); + margin-left: calc(0.25rem * calc(1 - var(--tw-space-x-reverse))); +} +.ddoc .space-x-2 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(0.5rem * var(--tw-space-x-reverse)); + margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse))); +} +.ddoc .space-y-2 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.5rem * var(--tw-space-y-reverse)); +} +.ddoc .space-y-7 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(1.75rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(1.75rem * var(--tw-space-y-reverse)); +} +.ddoc .space-y-8 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(2rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(2rem * var(--tw-space-y-reverse)); +} +.ddoc .break-words { + overflow-wrap: break-word; +} +.ddoc .break-all { + word-break: break-all; +} +.ddoc .rounded { + border-radius: 0.25rem; +} +.ddoc .rounded-md { + border-radius: 0.375rem; +} +.ddoc .border { + border-width: 1px; +} +.ddoc .border-b { + border-bottom-width: 1px; +} +.ddoc .border-l-2 { + border-left-width: 2px; +} +.ddoc .border-Class\/50 { + border-color: #20b44b80; +} +.ddoc .border-Enum\/50 { + border-color: #22abb080; +} +.ddoc .border-Function\/50 { + border-color: #056cf080; +} +.ddoc .border-Interface\/50 { + border-color: #d2a06480; +} +.ddoc .border-Method\/50 { + border-color: #056cf080; +} +.ddoc .border-Namespace\/50 { + border-color: #d2564680; +} +.ddoc .border-Property\/50 { + border-color: #7e57c080; +} +.ddoc .border-TypeAlias\/50 { + border-color: #a4478c80; +} +.ddoc .border-Variable\/50 { + border-color: #7e57c080; +} +.ddoc .border-abstract\/50 { + border-color: #0cafc680; +} +.ddoc .border-deprecated\/50 { + border-color: #dc262680; +} +.ddoc .border-gray-300 { + --tw-border-opacity: 1; + border-color: rgb(209 213 219/var(--tw-border-opacity)); +} +.ddoc .border-new\/50 { + border-color: #7b61ff80; +} +.ddoc .border-optional\/50 { + border-color: #0cafc680; +} +.ddoc .border-other\/50 { + border-color: #57534e80; +} +.ddoc .border-permissions\/50, .ddoc .border-private\/50 { + border-color: #0cafc680; +} +.ddoc .border-protected\/50, .ddoc .border-readonly\/50 { + border-color: #7b61ff80; +} +.ddoc .border-stone-300 { + --tw-border-opacity: 1; + border-color: rgb(214 211 209/var(--tw-border-opacity)); +} +.ddoc .border-unstable\/50, .ddoc .border-writeonly\/50 { + border-color: #7b61ff80; +} +.ddoc .bg-Class\/15 { + background-color: #20b44b26; +} +.ddoc .bg-Class\/5 { + background-color: #20b44b0d; +} +.ddoc .bg-Enum\/15 { + background-color: #22abb026; +} +.ddoc .bg-Enum\/5 { + background-color: #22abb00d; +} +.ddoc .bg-Function\/15 { + background-color: #056cf026; +} +.ddoc .bg-Function\/5 { + background-color: #056cf00d; +} +.ddoc .bg-Interface\/15 { + background-color: #d2a06426; +} +.ddoc .bg-Interface\/5 { + background-color: #d2a0640d; +} +.ddoc .bg-Method\/15 { + background-color: #056cf026; +} +.ddoc .bg-Method\/5 { + background-color: #056cf00d; +} +.ddoc .bg-Namespace\/15 { + background-color: #d2564626; +} +.ddoc .bg-Namespace\/5 { + background-color: #d256460d; +} +.ddoc .bg-Property\/15 { + background-color: #7e57c026; +} +.ddoc .bg-Property\/5 { + background-color: #7e57c00d; +} +.ddoc .bg-TypeAlias\/15 { + background-color: #a4478c26; +} +.ddoc .bg-TypeAlias\/5 { + background-color: #a4478c0d; +} +.ddoc .bg-Variable\/15 { + background-color: #7e57c026; +} +.ddoc .bg-Variable\/5 { + background-color: #7e57c00d; +} +.ddoc .bg-abstract\/15 { + background-color: #0cafc626; +} +.ddoc .bg-abstract\/5 { + background-color: #0cafc60d; +} +.ddoc .bg-deprecated\/15 { + background-color: #dc262626; +} +.ddoc .bg-deprecated\/5 { + background-color: #dc26260d; +} +.ddoc .bg-new\/15 { + background-color: #7b61ff26; +} +.ddoc .bg-new\/5 { + background-color: #7b61ff0d; +} +.ddoc .bg-optional\/15 { + background-color: #0cafc626; +} +.ddoc .bg-optional\/5 { + background-color: #0cafc60d; +} +.ddoc .bg-other\/15 { + background-color: #57534e26; +} +.ddoc .bg-other\/5 { + background-color: #57534e0d; +} +.ddoc .bg-permissions\/15 { + background-color: #0cafc626; +} +.ddoc .bg-permissions\/5 { + background-color: #0cafc60d; +} +.ddoc .bg-private\/15 { + background-color: #0cafc626; +} +.ddoc .bg-private\/5 { + background-color: #0cafc60d; +} +.ddoc .bg-protected\/15 { + background-color: #7b61ff26; +} +.ddoc .bg-protected\/5 { + background-color: #7b61ff0d; +} +.ddoc .bg-readonly\/15 { + background-color: #7b61ff26; +} +.ddoc .bg-readonly\/5 { + background-color: #7b61ff0d; +} +.ddoc .bg-stone-100 { + --tw-bg-opacity: 1; + background-color: rgb(245 245 244/var(--tw-bg-opacity)); +} +.ddoc .bg-unstable\/15 { + background-color: #7b61ff26; +} +.ddoc .bg-unstable\/5 { + background-color: #7b61ff0d; +} +.ddoc .bg-writeonly\/15 { + background-color: #7b61ff26; +} +.ddoc .bg-writeonly\/5 { + background-color: #7b61ff0d; +} +.ddoc .px-2 { + padding-left: 0.5rem; + padding-right: 0.5rem; +} +.ddoc .px-3 { + padding-left: 0.75rem; + padding-right: 0.75rem; +} +.ddoc .px-4 { + padding-left: 1rem; + padding-right: 1rem; +} +.ddoc .py-1 { + padding-top: 0.25rem; + padding-bottom: 0.25rem; +} +.ddoc .py-2 { + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} +.ddoc .pb-5 { + padding-bottom: 1.25rem; +} +.ddoc .pt-4 { + padding-top: 1rem; +} +.ddoc .text-2xl { + font-size: 1.5rem; + line-height: 2rem; +} +.ddoc .text-base { + font-size: 1rem; + line-height: 1.5rem; +} +.ddoc .text-sm { + font-size: 0.875rem; + line-height: 1.25rem; +} +.ddoc .font-bold { + font-weight: 700; +} +.ddoc .font-medium { + font-weight: 500; +} +.ddoc .font-normal { + font-weight: 400; +} +.ddoc .italic { + font-style: italic; +} +.ddoc .leading-none { + line-height: 1; +} +.ddoc .text-Class { + --tw-text-opacity: 1; + color: rgb(32 180 75/var(--tw-text-opacity)); +} +.ddoc .text-Enum { + --tw-text-opacity: 1; + color: rgb(34 171 176/var(--tw-text-opacity)); +} +.ddoc .text-Function { + --tw-text-opacity: 1; + color: rgb(5 108 240/var(--tw-text-opacity)); +} +.ddoc .text-Interface { + --tw-text-opacity: 1; + color: rgb(210 160 100/var(--tw-text-opacity)); +} +.ddoc .text-Method { + --tw-text-opacity: 1; + color: rgb(5 108 240/var(--tw-text-opacity)); +} +.ddoc .text-Namespace { + --tw-text-opacity: 1; + color: rgb(210 86 70/var(--tw-text-opacity)); +} +.ddoc .text-Property { + --tw-text-opacity: 1; + color: rgb(126 87 192/var(--tw-text-opacity)); +} +.ddoc .text-TypeAlias { + --tw-text-opacity: 1; + color: rgb(164 71 140/var(--tw-text-opacity)); +} +.ddoc .text-Variable { + --tw-text-opacity: 1; + color: rgb(126 87 192/var(--tw-text-opacity)); +} +.ddoc .text-\[\#0F172A\] { + --tw-text-opacity: 1; + color: rgb(15 23 42/var(--tw-text-opacity)); +} +.ddoc .text-abstract { + --tw-text-opacity: 1; + color: rgb(12 175 198/var(--tw-text-opacity)); +} +.ddoc .text-deprecated { + --tw-text-opacity: 1; + color: rgb(220 38 38/var(--tw-text-opacity)); +} +.ddoc .text-new { + --tw-text-opacity: 1; + color: rgb(123 97 255/var(--tw-text-opacity)); +} +.ddoc .text-optional { + --tw-text-opacity: 1; + color: rgb(12 175 198/var(--tw-text-opacity)); +} +.ddoc .text-other { + --tw-text-opacity: 1; + color: rgb(87 83 78/var(--tw-text-opacity)); +} +.ddoc .text-permissions, .ddoc .text-private { + --tw-text-opacity: 1; + color: rgb(12 175 198/var(--tw-text-opacity)); +} +.ddoc .text-protected, .ddoc .text-readonly { + --tw-text-opacity: 1; + color: rgb(123 97 255/var(--tw-text-opacity)); +} +.ddoc .text-stone-500 { + --tw-text-opacity: 1; + color: rgb(120 113 108/var(--tw-text-opacity)); +} +.ddoc .text-unstable, .ddoc .text-writeonly { + --tw-text-opacity: 1; + color: rgb(123 97 255/var(--tw-text-opacity)); +} +.ddoc .filter { + filter: var(--tw-blur)var(--tw-brightness)var(--tw-contrast)var( + --tw-grayscale + )var(--tw-hue-rotate)var(--tw-invert)var(--tw-saturate)var(--tw-sepia)var( + --tw-drop-shadow + ); +} +.ddoc summary::-webkit-details-marker { + display: none; +} +.ddoc a { + word-wrap: break-word; +} +.ddoc { + --ddoc-selection-border-width: 2px; + --ddoc-selection-border-color-default: #d6d3d1; + --ddoc-selection-selected-border-color: #2564eb; + --ddoc-selection-selected-bg: #056cf00c; + --ddoc-selection-padding: 9px 15px; +} +.ddoc .link { + --tw-text-opacity: 1; + color: rgb(37 99 235/var(--tw-text-opacity)); + transition-property: + color, + background-color, + border-color, + text-decoration-color, + fill, + stroke, + opacity, + box-shadow, + transform, + filter, + -webkit-backdrop-filter, + backdrop-filter, + -webkit-backdrop-filter; + transition-duration: 75ms; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); +} +.ddoc .link:hover { + --tw-text-opacity: 1; + color: rgb(96 165 250/var(--tw-text-opacity)); +} +.ddoc .anchor { + float: left; + --tw-text-opacity: 1; + color: rgb(87 83 78/var(--tw-text-opacity)); + margin-left: -24px; + padding: 0.25rem; + line-height: 1; + display: none; + top: 0; + bottom: 0; +} +.ddoc .anchorable { + scroll-margin-top: 4rem; + position: relative; +} +.ddoc .anchorable:hover .anchor { + display: block; +} +.ddoc .deprecated > div:first-child { + --tw-text-opacity: 1; + color: rgb(239 68 68/var(--tw-text-opacity)); + align-items: center; + gap: 0.25rem; + padding-top: 0.25rem; + padding-bottom: 0.25rem; + display: flex; +} +.ddoc .deprecated > div:first-child > span { + font-weight: 600; + line-height: 1.5rem; +} +.ddoc .deprecated > div:nth-child(2) { + --tw-border-opacity: 1; + border-left-width: 4px; + border-color: rgb(252 165 165/var(--tw-border-opacity)); + margin-left: 0.25rem; + padding-left: 0.5rem; +} +.ddoc .symbolSubtitle > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.125rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.125rem * var(--tw-space-y-reverse)); +} +.ddoc .symbolSubtitle { + font-size: 0.875rem; + line-height: 1rem; +} +.ddoc .symbolSubtitle .type { + --tw-text-opacity: 1; + color: rgb(168 162 158/var(--tw-text-opacity)); + font-style: italic; +} +.ddoc .docEntry { + margin-bottom: 1rem; +} +.ddoc .docEntry > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.5rem * var(--tw-space-y-reverse)); +} +.ddoc .docEntry .docEntryHeader { + justify-content: space-between; + align-items: flex-start; + display: flex; +} +@media (min-width: 768px) { + .ddoc .docEntry .docEntryHeader { + font-size: 1rem; + line-height: 1.5rem; + } +} +.ddoc .docEntry .docEntryHeader > div { + overflow-wrap: break-word; +} +.ddoc .section { + max-width: 75ch; + margin-bottom: 0.5rem; + scroll-margin-top: 4rem; +} +.ddoc .section > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.5rem * var(--tw-space-y-reverse)); +} +.ddoc .section > div:first-child > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.5rem * var(--tw-space-y-reverse)); +} +.ddoc .section > div:first-child > h2 { + margin-bottom: 0; + padding-top: 0.25rem; + padding-bottom: 0.25rem; + font-size: 1.25rem; + font-weight: 600; + line-height: 1.5rem; +} +.ddoc .section > div:first-child > div { + max-width: 75ch; + font-size: 1rem; + line-height: 1.5rem; +} +.ddoc .namespaceSection { + max-width: 75ch; + margin-top: 1rem; +} +.ddoc .namespaceSection > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(1rem * var(--tw-space-y-reverse)); +} +.ddoc .namespaceSection .namespaceItem { + -moz-column-gap: 0.625rem; + column-gap: 0.625rem; + min-height: 0; + display: flex; +} +@media (min-width: 768px) { + .ddoc .namespaceSection .namespaceItem { + min-height: 4rem; + } +} +@media (min-width: 1024px) { + .ddoc .namespaceSection .namespaceItem { + padding-right: 1rem; + } +} +.ddoc .namespaceSection .namespaceItem .docNodeKindIcon { + flex-direction: column; + justify-content: flex-start; + width: auto; +} +.ddoc .namespaceSection .namespaceItem .docNodeKindIcon > * + * { + margin-top: -0.125rem; + margin-left: 0; +} +.ddoc .namespaceSection .namespaceItem[aria-label="deprecated"] { + opacity: 0.6; +} +.ddoc + .namespaceSection + .namespaceItem[aria-label="deprecated"] + .namespaceItemContent + > a { + --tw-text-opacity: 1; + color: rgb(120 113 108/var(--tw-text-opacity)); + text-decoration-line: line-through; + text-decoration-color: #78716cb3; + text-decoration-thickness: 2px; +} +.ddoc .namespaceSection .namespaceItem .namespaceItemContent > a, +.ddoc + .namespaceSection + .namespaceItem + .namespaceItemContent + .namespaceItemContentSubItems + a { + text-decoration-line: underline; + text-decoration-color: #d6d3d1; +} +:is( + .ddoc .namespaceSection .namespaceItem .namespaceItemContent > a, + .ddoc + .namespaceSection + .namespaceItem + .namespaceItemContent + .namespaceItemContentSubItems + a +):hover { + text-decoration-line: none; +} +.ddoc .namespaceSection .namespaceItem .namespaceItemContent > a { + word-break: break-all; + font-weight: 500; + line-height: 1.25; + display: block; +} +.ddoc + .namespaceSection + .namespaceItem + .namespaceItemContent + .namespaceItemContentDoc { + --tw-text-opacity: 1; + color: rgb(87 83 78/var(--tw-text-opacity)); + margin-top: 0.5rem; + font-size: 0.875rem; + line-height: 1.25rem; +} +.ddoc + .namespaceSection + .namespaceItem + .namespaceItemContent + .namespaceItemContentSubItems { + flex-wrap: wrap; + row-gap: 0.25rem; + margin-top: 0.375rem; + font-size: 0.875rem; + line-height: 1.25rem; + display: flex; +} +.ddoc + .namespaceSection + .namespaceItem + .namespaceItemContent + .namespaceItemContentSubItems + > li:not(:last-child):after { + content: "|"; + -webkit-user-select: none; + user-select: none; + --tw-text-opacity: 1; + color: rgb(209 213 219/var(--tw-text-opacity)); + margin-left: 0.5rem; + margin-right: 0.5rem; +} +.ddoc .symbolGroup > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(3rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(3rem * var(--tw-space-y-reverse)); +} +.ddoc .symbolGroup article > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(1.25rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(1.25rem * var(--tw-space-y-reverse)); +} +.ddoc .symbolGroup article > div:first-child { + justify-content: space-between; + align-items: flex-start; + display: flex; +} +.ddoc + .symbolGroup + article + > div:first-child + > div:first-child + > :not([hidden]) + ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.25rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.25rem * var(--tw-space-y-reverse)); +} +.ddoc .symbolGroup article > div:first-child > div:first-child { + font-weight: 500; +} +.ddoc .docNodeKindIcon { + flex-shrink: 0; + justify-content: flex-end; + display: inline-flex; +} +.ddoc .docNodeKindIcon div { + -webkit-user-select: none; + user-select: none; + text-align: center; + vertical-align: middle; + border-radius: 9999px; + flex-shrink: 0; + width: 1.25rem; + height: 1.25rem; + font-family: + ui-monospace, + SFMono-Regular, + Menlo, + Monaco, + Consolas, + Liberation Mono, + Courier New, + monospace; + font-size: 0.75rem; + font-weight: 500; + line-height: 1.25rem; +} +.ddoc .docNodeKindIcon > * + * { + margin-left: -0.375rem; +} +.ddoc .example-header { + margin-bottom: 0.75rem; + font-size: 1.125rem; + font-weight: 700; + line-height: 1.75rem; +} +.ddoc .toc h3 { + margin-bottom: 0.75rem; + font-size: 1.125rem; + font-weight: 700; + line-height: 1.75rem; +} +.ddoc .toc > div > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(1.25rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(1.25rem * var(--tw-space-y-reverse)); +} +.ddoc .toc .topSymbols > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.75rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.75rem * var(--tw-space-y-reverse)); +} +.ddoc .toc .topSymbols { + font-size: 0.875rem; + line-height: 1.25rem; +} +.ddoc .toc .topSymbols ul { + list-style-type: none; +} +.ddoc .toc .topSymbols ul > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.625rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.625rem * var(--tw-space-y-reverse)); +} +.ddoc .toc .topSymbols ul li { + display: block; +} +.ddoc .toc .topSymbols ul li a { + align-items: center; + gap: 0.5rem; + display: flex; +} +.ddoc .toc .topSymbols ul li a > span { + text-overflow: ellipsis; + white-space: nowrap; + border-radius: 0.25rem; + width: 100%; + margin-top: -0.125rem; + margin-bottom: -0.125rem; + margin-left: -0.25rem; + padding-top: 0.125rem; + padding-bottom: 0.125rem; + padding-left: 0.25rem; + display: block; + overflow: hidden; +} +.ddoc .toc .topSymbols > a:hover { + text-decoration-line: underline; +} +.ddoc .toc .documentNavigation > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.75rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.75rem * var(--tw-space-y-reverse)); +} +.ddoc .toc .documentNavigation { + font-size: 0.875rem; + line-height: 1.25rem; +} +@media not all and (min-width: 640px) { + .ddoc .toc .documentNavigation { + display: none; + } +} +.ddoc .toc .documentNavigation > ul { + flex-grow: 1; + display: block; +} +.ddoc .toc .documentNavigation > ul > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.5rem * var(--tw-space-y-reverse)); +} +.ddoc .toc .documentNavigation > ul { + overflow-y: auto; +} +.ddoc .toc .documentNavigation > ul > li { + margin-top: 0.125rem; + margin-left: 0.75rem; + margin-right: 0.75rem; +} +.ddoc .toc .documentNavigation > ul li:has(> ul) { + margin-top: 0 !important; +} +.ddoc .toc .documentNavigation > ul li:has(> a) { + padding-bottom: 0 !important; +} +.ddoc .toc .documentNavigation > ul ul { + margin-left: 0.875rem; +} +.ddoc .toc .documentNavigation > ul ul > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.5rem * var(--tw-space-y-reverse)); +} +.ddoc .toc .documentNavigation > ul ul { + --tw-text-opacity: 1; + color: rgb(134 135 137/var(--tw-text-opacity)); + font-size: 0.8rem; + line-height: 1; +} +.ddoc .toc .documentNavigation > ul ul li { + margin-top: 0.25rem !important; +} +.ddoc .toc .documentNavigation > ul ul li a { + padding: 0.25rem; +} +.ddoc .toc .documentNavigation > ul ul li a:hover { + --tw-text-opacity: 1; + color: rgb(0 0 0/var(--tw-text-opacity)); +} +.ddoc .toc .documentNavigation a { + text-overflow: ellipsis; + white-space: nowrap; + display: block; + overflow: hidden; +} +.ddoc .toc .documentNavigation a:hover { + text-decoration-line: underline; +} +.ddoc .usages nav { + flex-direction: row; + align-items: center; + gap: 0.5rem; + margin-bottom: 0.75rem; + font-weight: 600; + display: flex; +} +.ddoc .usages nav details > summary { + cursor: pointer; + -webkit-user-select: none; + user-select: none; + --tw-border-opacity: 1; + border-width: 1px; + border-color: rgb(209 213 219/var(--tw-border-opacity)); + border-radius: 0.25rem; + gap: 0.25rem; + padding: 0.5rem 0.75rem; + display: flex; +} +@media (min-width: 768px) { + .ddoc .usages nav details > div { + position: relative; + } +} +.ddoc .usages nav details > div > div { + z-index: 30; + --tw-border-opacity: 1; + border-width: 1px; + border-color: rgb(209 213 219/var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: rgb(255 255 255/var(--tw-bg-opacity)); + margin-top: 0.375rem; + padding: 0.5rem; + display: block; + position: absolute; +} +@media not all and (min-width: 768px) { + .ddoc .usages nav details > div > div { + border-left-width: 0; + border-right-width: 0; + left: 0; + right: 0; + } +} +@media (min-width: 768px) { + .ddoc .usages nav details > div > div { + border-radius: 0.25rem; + width: 12rem; + } +} +.ddoc .usages nav details > div > div label { + cursor: pointer; + -webkit-user-select: none; + user-select: none; + border-radius: 0.125rem; + align-items: center; + gap: 0.5rem; + padding: 0.25rem 0.5rem; + line-height: 1.5; + display: flex; +} +.ddoc .usages nav details > div > div label:hover { + --tw-bg-opacity: 1; + background-color: rgb(249 250 251/var(--tw-bg-opacity)); +} +.ddoc .usageContent > h3 { + margin-bottom: 0.75rem; + font-size: 1.125rem; + font-weight: 700; + line-height: 1.75rem; +} +.ddoc .usageContent > div { + --tw-text-opacity: 1; + color: rgb(104 104 104/var(--tw-text-opacity)); + font-size: 0.75rem; + line-height: 1rem; +} +.ddoc .usageContent > div p { + margin: 0; +} +.ddoc .usageContent pre.highlight { + --tw-border-opacity: 1; + border-width: 1px; + border-color: rgb(209 213 219/var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: rgb(255 255 255/var(--tw-bg-opacity)); +} +@media not all and (min-width: 768px) { + .ddoc .usageContent pre.highlight { + border-left-width: 0; + border-right-width: 0; + } +} +.ddoc .usageContent pre.highlight { + margin-top: 0.25rem !important; +} +.ddoc .usageContent pre.highlight > code:first-child { + scrollbar-width: thin; + padding: 0.5rem 0.75rem; +} +.ddoc .usageContent pre.highlight .context_button { + border-width: 0; + display: none; + top: 0.25rem; + right: 0.5rem; +} +.ddoc .usageContent pre.highlight .context_button svg rect { + fill: #fff; +} +.ddoc .usageContent pre.highlight:hover .context_button { + opacity: 1; + display: block; +} +.ddoc #categoryPanel { + padding-top: 0.75rem; + font-size: 0.875rem; + line-height: 1.25rem; +} +.ddoc #categoryPanel ul > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.5rem * var(--tw-space-y-reverse)); +} +.ddoc #categoryPanel ul { + overflow-y: auto; +} +.ddoc #categoryPanel ul li { + margin-left: 0.25rem; + margin-right: 0.75rem; +} +.ddoc #categoryPanel ul li a { + text-overflow: ellipsis; + white-space: nowrap; + padding: 0.375rem 0.875rem; + display: block; + overflow: hidden; +} +.ddoc #categoryPanel ul li a:hover { + text-decoration-line: underline; +} +.ddoc .contextLink { + color: #0e6590cc; + text-underline-offset: 0.15em; + text-decoration-line: underline; + text-decoration-color: #0e659080; + text-decoration-thickness: 1.5px; +} +.ddoc .contextLink:hover { + --tw-text-opacity: 1; + color: rgb(14 101 144/var(--tw-text-opacity)); + text-decoration-color: #0e6590; +} +.ddoc .contextLink { + -webkit-text-decoration-skip: ink; + -webkit-text-decoration-skip-ink: auto; + text-decoration-skip-ink: auto; +} +.ddoc .breadcrumbs { + word-break: break-all; + flex-wrap: wrap; + align-items: center; + gap: 0.25rem; + display: inline-flex; +} +.ddoc .breadcrumbs > li:first-child { + font-size: 1.5rem; + font-weight: 700; + line-height: 1; +} +.ddoc .breadcrumbs li { + font-size: 1.125rem; + line-height: 0.9em; + display: inline; +} +@media (min-width: 1024px) { + .ddoc .breadcrumbs li { + font-size: 1.25rem; + line-height: 1.75rem; + } +} +.ddoc .context_button { + z-index: 10; + cursor: pointer; + background-color: inherit; + border-width: 1px; + border-radius: 0.25rem; + padding: 0.375rem; + line-height: 0; +} +.ddoc .context_button:hover { + --tw-bg-opacity: 1; + background-color: rgb(231 229 228/var(--tw-bg-opacity)); +} +.ddoc .see { + list-style-type: disc; + list-style-position: inside; +} +.ddoc .see > li * { + display: inline-block; +} +.ddoc .\*\:h-4 > * { + height: 1rem; +} +.ddoc .\*\:h-5 > * { + height: 1.25rem; +} +.ddoc .\*\:w-auto > * { + width: auto; +} +.ddoc .\*\:flex-none > * { + flex: none; +} +.ddoc .hover\:bg-Class\/15:hover { + background-color: #20b44b26; +} +.ddoc .hover\:bg-Class\/5:hover { + background-color: #20b44b0d; +} +.ddoc .hover\:bg-Enum\/15:hover { + background-color: #22abb026; +} +.ddoc .hover\:bg-Enum\/5:hover { + background-color: #22abb00d; +} +.ddoc .hover\:bg-Function\/15:hover { + background-color: #056cf026; +} +.ddoc .hover\:bg-Function\/5:hover { + background-color: #056cf00d; +} +.ddoc .hover\:bg-Interface\/15:hover { + background-color: #d2a06426; +} +.ddoc .hover\:bg-Interface\/5:hover { + background-color: #d2a0640d; +} +.ddoc .hover\:bg-Method\/15:hover { + background-color: #056cf026; +} +.ddoc .hover\:bg-Method\/5:hover { + background-color: #056cf00d; +} +.ddoc .hover\:bg-Namespace\/15:hover { + background-color: #d2564626; +} +.ddoc .hover\:bg-Namespace\/5:hover { + background-color: #d256460d; +} +.ddoc .hover\:bg-Property\/15:hover { + background-color: #7e57c026; +} +.ddoc .hover\:bg-Property\/5:hover { + background-color: #7e57c00d; +} +.ddoc .hover\:bg-TypeAlias\/15:hover { + background-color: #a4478c26; +} +.ddoc .hover\:bg-TypeAlias\/5:hover { + background-color: #a4478c0d; +} +.ddoc .hover\:bg-Variable\/15:hover { + background-color: #7e57c026; +} +.ddoc .hover\:bg-Variable\/5:hover { + background-color: #7e57c00d; +} +.ddoc .hover\:bg-abstract\/15:hover { + background-color: #0cafc626; +} +.ddoc .hover\:bg-abstract\/5:hover { + background-color: #0cafc60d; +} +.ddoc .hover\:bg-deprecated\/15:hover { + background-color: #dc262626; +} +.ddoc .hover\:bg-deprecated\/5:hover { + background-color: #dc26260d; +} +.ddoc .hover\:bg-new\/15:hover { + background-color: #7b61ff26; +} +.ddoc .hover\:bg-new\/5:hover { + background-color: #7b61ff0d; +} +.ddoc .hover\:bg-optional\/15:hover { + background-color: #0cafc626; +} +.ddoc .hover\:bg-optional\/5:hover { + background-color: #0cafc60d; +} +.ddoc .hover\:bg-other\/15:hover { + background-color: #57534e26; +} +.ddoc .hover\:bg-other\/5:hover { + background-color: #57534e0d; +} +.ddoc .hover\:bg-permissions\/15:hover { + background-color: #0cafc626; +} +.ddoc .hover\:bg-permissions\/5:hover { + background-color: #0cafc60d; +} +.ddoc .hover\:bg-private\/15:hover { + background-color: #0cafc626; +} +.ddoc .hover\:bg-private\/5:hover { + background-color: #0cafc60d; +} +.ddoc .hover\:bg-protected\/15:hover { + background-color: #7b61ff26; +} +.ddoc .hover\:bg-protected\/5:hover { + background-color: #7b61ff0d; +} +.ddoc .hover\:bg-readonly\/15:hover { + background-color: #7b61ff26; +} +.ddoc .hover\:bg-readonly\/5:hover { + background-color: #7b61ff0d; +} +.ddoc .hover\:bg-unstable\/15:hover { + background-color: #7b61ff26; +} +.ddoc .hover\:bg-unstable\/5:hover { + background-color: #7b61ff0d; +} +.ddoc .hover\:bg-writeonly\/15:hover { + background-color: #7b61ff26; +} +.ddoc .hover\:bg-writeonly\/5:hover { + background-color: #7b61ff0d; +} From 35f255f6bd77a4d8845c11adf0f335e0d991fbce Mon Sep 17 00:00:00 2001 From: Jo Franchetti <jofranchetti@gmail.com> Date: Thu, 9 Jan 2025 13:44:48 +0000 Subject: [PATCH 5/7] config files --- deploy/api/runtime-request.md | 2 +- reference.page.jsx | 81 --- runtime/_data.ts | 4 - runtime/contributing/building_from_source.md | 3 +- runtime/fundamentals/open_telemetry.md | 605 ------------------- runtime/fundamentals/security.md | 8 +- runtime/reference/cli/task.md | 6 +- runtime/reference/cli/unstable_flags.md | 5 - runtime/reference/continuous_integration.md | 2 +- 9 files changed, 10 insertions(+), 706 deletions(-) delete mode 100644 reference.page.jsx delete mode 100644 runtime/fundamentals/open_telemetry.md diff --git a/deploy/api/runtime-request.md b/deploy/api/runtime-request.md index 841e7fefa..d27111344 100644 --- a/deploy/api/runtime-request.md +++ b/deploy/api/runtime-request.md @@ -18,7 +18,7 @@ interface is part of the Fetch API and represents the request of fetch(). The Request() constructor creates a new Request instance. ```ts -let request = new Request(resource, init); +let request = new Request(input, init); ``` #### Parameters diff --git a/reference.page.jsx b/reference.page.jsx deleted file mode 100644 index 8f4ac9039..000000000 --- a/reference.page.jsx +++ /dev/null @@ -1,81 +0,0 @@ -import { walkSync } from "@std/fs/walk"; -import { unescape } from "@std/html/entities"; -import entityList from "@std/html/named-entity-list.json" with { type: "json" }; - -export const layout = "raw.tsx"; - -export const sidebar = [ - { - items: [ - { - label: "Deno APIs", - id: "/api/deno/", - }, - { - label: "Web APIs", - id: "/api/web/", - }, - { - label: "Node APIs", - id: "/api/node/", - }, - ], - }, -]; - -const resetRegexp = - /<link id="ddocResetStylesheet" rel="stylesheet" href=".*?reset\.css">\s*/; -const titleRegexp = /<title>(.+?)<\/title>\s*/s; - -export default function* () { - try { - if (Deno.env.has("SKIP_REFERENCE")) { - throw new Error(); - } - const files = walkSync("reference_gen/gen", { - exts: [".html"], - }); - - for (const file of files) { - const content = Deno.readTextFileSync(file.path).replace( - resetRegexp, - "", - ); - - let title = ""; - try { - const match = titleRegexp.exec(content); - - if (!match) { - //console.log("No title found in", file.path); - title = file.path + " - Deno Docs"; - } else { - const titleFirst = match[1].slice(0, -"documentation".length); - title = unescape(titleFirst, { entityList }) + "- Deno Docs"; - } - } catch (e) { - if (!file.path.endsWith("prototype.html")) { - console.error(file.path); - throw e; - } - } - - const trailingLength = file.path.endsWith("index.html") - ? -"index.html".length - : -".html".length; - - let path = file.path.slice("reference_gen/gen".length, trailingLength); - - // replace slashes for windows - path = path.replace(/\\/g, "/"); - - yield { - url: "/api" + path, - title, - content, - }; - } - } catch (ex) { - console.warn("⚠️ Reference docs were not generated." + ex); - } -} diff --git a/runtime/_data.ts b/runtime/_data.ts index 5c8fe983e..41ad2a756 100644 --- a/runtime/_data.ts +++ b/runtime/_data.ts @@ -36,10 +36,6 @@ export const sidebar = [ label: "HTTP Server", id: "/runtime/fundamentals/http_server/", }, - { - label: "Open Telemetry", - id: "/runtime/fundamentals/open_telemetry/", - }, "/runtime/fundamentals/stability_and_releases/", ], }, diff --git a/runtime/contributing/building_from_source.md b/runtime/contributing/building_from_source.md index ed240d6e4..7b17e3236 100644 --- a/runtime/contributing/building_from_source.md +++ b/runtime/contributing/building_from_source.md @@ -154,8 +154,7 @@ it refers to Python 3. The easiest way to build Deno is by using a precompiled version of V8. -_For WSL make sure you have sufficient memory allocated in `.wslconfig`. It is -recommended that you allocate at least 16GB._ +_For WSL make sure you have sufficient memory allocated in `.wslconfig`_ ```console cargo build -vv diff --git a/runtime/fundamentals/open_telemetry.md b/runtime/fundamentals/open_telemetry.md deleted file mode 100644 index 64bfcd951..000000000 --- a/runtime/fundamentals/open_telemetry.md +++ /dev/null @@ -1,605 +0,0 @@ ---- -title: OpenTelemetry ---- - -:::caution - -The OpenTelemetry integration for Deno is still in development and may change. -To use it, you must pass the `--unstable-otel` flag to Deno. - -::: - -Deno has built in support for [OpenTelemetry](https://opentelemetry.io/). - -> OpenTelemetry is a collection of APIs, SDKs, and tools. Use it to instrument, -> generate, collect, and export telemetry data (metrics, logs, and traces) to -> help you analyze your software’s performance and behavior. -> -> <i>- https://opentelemetry.io/</i> - -This integration enables you to monitor your Deno applications using -OpenTelemetry observability tooling with instruments like logs, metrics, and -traces. - -Deno provides the following features: - -- Exporting of collected metrics, traces, and logs to a server using the - OpenTelemetry protocol. -- [Automatic instrumentation](#auto-instrumentation) of the Deno runtime with - OpenTelemetry metrics, traces, and logs. -- [Collection of user defined metrics, traces, and logs](#user-metrics) created - with the `npm:@opentelemetry/api` package. - -## Quick start - -To enable the OpenTelemetry integration, run your Deno script with the -`--unstable-otel` flag and set the environment variable `OTEL_DENO=1`: - -```sh -OTEL_DENO=1 deno run --unstable-otel my_script.ts -``` - -This will automatically collect and export runtime observability data to an -OpenTelemetry endpoint at `localhost:4318` using Protobuf over HTTP -(`http/protobuf`). - -:::tip - -If you do not have an OpenTelemetry collector set up yet, you can get started -with a -[local LGTM stack in Docker](https://github.com/grafana/docker-otel-lgtm/tree/main?tab=readme-ov-file) -(Loki (logs), Grafana (dashboard), Tempo (traces), and Mimir (metrics)) by -running the following command: - -```sh -docker run --name lgtm -p 3000:3000 -p 4317:4317 -p 4318:4318 --rm -ti \ - -v "$PWD"/lgtm/grafana:/data/grafana \ - -v "$PWD"/lgtm/prometheus:/data/prometheus \ - -v "$PWD"/lgtm/loki:/data/loki \ - -e GF_PATHS_DATA=/data/grafana \ - docker.io/grafana/otel-lgtm:0.8.1 -``` - -You can then access the Grafana dashboard at `http://localhost:3000` with the -username `admin` and password `admin`. - -::: - -This will automatically collect and export runtime observability data like -`console.log`, traces for HTTP requests, and metrics for the Deno runtime. -[Learn more about auto instrumentation](#auto-instrumentation). - -You can also create your own metrics, traces, and logs using the -`npm:@opentelemetry/api` package. -[Learn more about user defined metrics](#user-metrics). - -## Auto instrumentation - -Deno automatically collects and exports some observability data to the OTLP -endpoint. - -This data is exported in the built-in instrumentation scope of the Deno runtime. -This scope has the name `deno`. The version of the Deno runtime is the version -of the `deno` instrumentation scope. (e.g. `deno:2.1.4`). - -### Traces - -Deno automatically creates spans for various operations, such as: - -- Incoming HTTP requests served with `Deno.serve`. -- Outgoing HTTP requests made with `fetch`. - -#### `Deno.serve` - -When you use `Deno.serve` to create an HTTP server, a span is created for each -incoming request. The span automatically ends when response headers are sent -(not when the response body is done sending). - -The name of the created span is `${method}`. The span kind is `server`. - -The following attributes are automatically added to the span on creation: - -- `http.request.method`: The HTTP method of the request. -- `url.full`: The full URL of the request (as would be reported by `req.url`). -- `url.scheme`: The scheme of the request URL (e.g. `http` or `https`). -- `url.path`: The path of the request URL. -- `url.query`: The query string of the request URL. - -After the request is handled, the following attributes are added: - -- `http.status_code`: The status code of the response. - -Deno does not automatically add a `http.route` attribute to the span as the -route is not known by the runtime, and instead is determined by the routing -logic in a user's handler function. If you want to add a `http.route` attribute -to the span, you can do so in your handler function using -`npm:@opentelemetry/api`. In this case you should also update the span name to -include the route. - -```ts -import { trace } from "npm:@opentelemetry/api@1"; - -const INDEX_ROUTE = new URLPattern({ pathname: "/" }); -const BOOK_ROUTE = new URLPattern({ pathname: "/book/:id" }); - -Deno.serve(async (req) => { - const span = trace.getActiveSpan(); - if (INDEX_ROUTE.test(req.url)) { - span.setAttribute("http.route", "/"); - span.updateName(`${req.method} /`); - - // handle index route - } else if (BOOK_ROUTE.test(req.url)) { - span.setAttribute("http.route", "/book/:id"); - span.updateName(`${req.method} /book/:id`); - - // handle book route - } else { - return new Response("Not found", { status: 404 }); - } -}); -``` - -#### `fetch` - -When you use `fetch` to make an HTTP request, a span is created for the request. -The span automatically ends when the response headers are received. - -The name of the created span is `${method}`. The span kind is `client`. - -The following attributes are automatically added to the span on creation: - -- `http.request.method`: The HTTP method of the request. -- `url.full`: The full URL of the request. -- `url.scheme`: The scheme of the request URL. -- `url.path`: The path of the request URL. -- `url.query`: The query string of the request URL. - -After the response is received, the following attributes are added: - -- `http.status_code`: The status code of the response. - -### Metrics - -The following metrics are automatically collected and exported: - -_None yet_ - -### Logs - -The following logs are automatically collected and exported: - -- Any logs created with `console.*` methods such as `console.log` and - `console.error`. -- Any logs created by the Deno runtime, such as debug logs, `Downloading` logs, - and similar. -- Any errors that cause the Deno runtime to exit (both from user code, and from - the runtime itself). - -Logs raised from JavaScript code will be exported with the relevant span -context, if the log occurred inside of an active span. - -`console` auto instrumentation can be configured using the `OTEL_DENO_CONSOLE` -environment variable: - -- `capture`: Logs are emitted to stdout/stderr and are also exported with - OpenTelemetry. (default) -- `replace`: Logs are only exported with OpenTelemetry, and not emitted to - stdout/stderr. -- `ignore`: Logs are emitted only to stdout/stderr, and will not be exported - with OpenTelemetry. - -## User metrics - -In addition to the automatically collected telemetry data, you can also create -your own metrics and traces using the `npm:@opentelemetry/api` package. - -You do not need to configure the `npm:@opentelemetry/api` package to use it with -Deno. Deno sets up the `npm:@opentelemetry/api` package automatically when the -`--unstable-otel` flag is passed. There is no need to call -`metrics.setGlobalMeterProvider()`, `trace.setGlobalTracerProvider()`, or -`context.setGlobalContextManager()`. All configuration of resources, exporter -settings, etc. is done via environment variables. - -Deno works with version `1.x` of the `npm:@opentelemetry/api` package. You can -either import directly from `npm:@opentelemetry/api@1`, or you can install the -package locally with `deno add` and import from `@opentelemetry/api`. - -```sh -deno add npm:@opentelemetry/api@1 -``` - -For both traces and metrics, you need to define names for the tracer and meter -respectively. If you are instrumenting a library, you should name the tracer or -meter after the library (such as `my-awesome-lib`). If you are instrumenting an -application, you should name the tracer or meter after the application (such as -`my-app`). The version of the tracer or meter should be set to the version of -the library or application. - -### Traces - -To create a new span, first import the `trace` object from -`npm:@opentelemetry/api` and create a new tracer: - -```ts -import { trace } from "npm:@opentelemetry/api@1"; - -const tracer = trace.getTracer("my-app", "1.0.0"); -``` - -Then, create a new span using the `tracer.startActiveSpan` method and pass a -callback function to it. You have to manually end the span by calling the `end` -method on the span object returned by `startActiveSpan`. - -```ts -function myFunction() { - return tracer.startActiveSpan("myFunction", (span) => { - try { - // do myFunction's work - } catch (error) { - span.recordException(error); - span.setStatus({ - code: trace.SpanStatusCode.ERROR, - message: (error as Error).message, - }); - throw error; - } finally { - span.end(); - } - }); -} -``` - -`span.end()` should be called in a `finally` block to ensure that the span is -ended even if an error occurs. `span.recordException` and `span.setStatus` -should also be called in a `catch` block, to record any errors that occur. - -Inside of the callback function, the created span is the "active span". You can -get the active span using `trace.getActiveSpan()`. The "active span" will be -used as the parent span for any spans created (manually, or automatically by the -runtime) inside of the callback function (or any functions that are called from -the callback function). -[Learn more about context propagation](#context-propagation). - -The `startActiveSpan` method returns the return value of the callback function. - -Spans can have attributes added to them during their lifetime. Attributes are -key value pairs that represent structured metadata about the span. Attributes -can be added using the `setAttribute` and `setAttributes` methods on the span -object. - -```ts -span.setAttribute("key", "value"); -span.setAttributes({ success: true, "bar.count": 42n, "foo.duration": 123.45 }); -``` - -Values for attributes can be strings, numbers (floats), bigints (clamped to -u64), booleans, or arrays of any of these types. If an attribute value is not -one of these types, it will be ignored. - -The name of a span can be updated using the `updateName` method on the span -object. - -```ts -span.updateName("new name"); -``` - -The status of a span can be set using the `setStatus` method on the span object. -The `recordException` method can be used to record an exception that occurred -during the span's lifetime. `recordException` creates an event with the -exception stack trace and name and attaches it to the span. **`recordException` -does not set the span status to `ERROR`, you must do that manually.** - -```ts -import { SpanStatusCode } from "npm:@opentelemetry/api@1"; - -span.setStatus({ - code: SpanStatusCode.ERROR, - message: "An error occurred", -}); -span.recordException(new Error("An error occurred")); - -// or - -span.setStatus({ - code: SpanStatusCode.OK, -}); -``` - -Spans can also have -[events](https://open-telemetry.github.io/opentelemetry-js/interfaces/_opentelemetry_api.Span.html#addEvent) -and -[links](https://open-telemetry.github.io/opentelemetry-js/interfaces/_opentelemetry_api.Span.html#addLink) -added to them. Events are points in time that are associated with the span. -Links are references to other spans. - -Spans can also be created manually with `tracer.startSpan` which returns a span -object. This method does not set the created span as the active span, so it will -not automatically be used as the parent span for any spans created later, or any -`console.log` calls. A span can manually be set as the active span for a -callback, by using the [context propagation API](#context-propagation). - -Both `tracer.startActiveSpan` and `tracer.startSpan` can take an optional -options bag containing any of the following properties: - -- `kind`: The kind of the span. Can be `SpanKind.CLIENT`, `SpanKind.SERVER`, - `SpanKind.PRODUCER`, `SpanKind.CONSUMER`, or `SpanKind.INTERNAL`. Defaults to - `SpanKind.INTERNAL`. -- `startTime` A `Date` object representing the start time of the span, or a - number representing the start time in milliseconds since the Unix epoch. If - not provided, the current time will be used. -- `attributes`: An object containing attributes to add to the span. -- `links`: An array of links to add to the span. -- `root`: A boolean indicating whether the span should be a root span. If - `true`, the span will not have a parent span (even if there is an active - span). - -After the options bag, both `tracer.startActiveSpan` and `tracer.startSpan` can -also take a `context` object from the -[context propagation API](#context-propagation). - -Learn more about the full tracing API in the -[OpenTelemetry JS API docs](https://open-telemetry.github.io/opentelemetry-js/classes/_opentelemetry_api.TraceAPI.html). - -### Metrics - -To create a metric, first import the `metrics` object from -`npm:@opentelemetry/api` and create a new meter: - -```ts -import { metrics } from "npm:@opentelemetry/api@1"; - -const meter = metrics.getMeter("my-app", "1.0.0"); -``` - -Then, an instrument can be created from the meter, and used to record values: - -```ts -const counter = meter.createCounter("my_counter", { - description: "A simple counter", - unit: "1", -}); - -counter.add(1); -counter.add(2); -``` - -Each recording can also have associated attributes: - -```ts -counter.add(1, { color: "red" }); -counter.add(2, { color: "blue" }); -``` - -:::tip - -In OpenTelemetry, metric attributes should generally have low cardinality. This -means that there should not be too many unique combinations of attribute values. -For example, it is probably fine to have an attribute for which continent a user -is on, but it would be too high cardinality to have an attribute for the exact -latitude and longitude of the user. High cardinality attributes can cause -problems with metric storage and exporting, and should be avoided. Use spans and -logs for high cardinality data. - -::: - -There are several types of instruments that can be created with a meter: - -- **Counter**: A counter is a monotonically increasing value. Counters can only - be positive. They can be used for values that are always increasing, such as - the number of requests handled. - -- **UpDownCounter**: An up-down counter is a value that can both increase and - decrease. Up-down counters can be used for values that can increase and - decrease, such as the number of active connections or requests in progress. - -- **Gauge**: A gauge is a value that can be set to any value. They are used for - values that do not "accumulate" over time, but rather have a specific value at - any given time, such as the current temperature. - -- **Histogram**: A histogram is a value that is recorded as a distribution of - values. Histograms can be used for values that are not just a single number, - but a distribution of numbers, such as the response time of a request in - milliseconds. Histograms can be used to calculate percentiles, averages, and - other statistics. They have a predefined set of boundaries that define the - buckets that the values are placed into. By default, the boundaries are - `[0.0, 5.0, 10.0, 25.0, 50.0, 75.0, 100.0, 250.0, 500.0, 750.0, 1000.0, 2500.0, 5000.0, 7500.0, 10000.0]`. - -There are also several types of observable instruments. These instruments do not -have a synchronous recording method, but instead return a callback that can be -called to record a value. The callback will be called when the OpenTelemetry SDK -is ready to record a value, for example just before exporting. - -```ts -const counter = meter.createObservableCounter("my_counter", { - description: "A simple counter", - unit: "1", -}); -counter.addCallback((res) => { - res.observe(1); - // or - res.observe(1, { color: "red" }); -}); -``` - -There are three types of observable instruments: - -- **ObservableCounter**: An observable counter is a counter that can be observed - asynchronously. It can be used for values that are always increasing, such as - the number of requests handled. -- **ObservableUpDownCounter**: An observable up-down counter is a value that can - both increase and decrease, and can be observed asynchronously. Up-down - counters can be used for values that can increase and decrease, such as the - number of active connections or requests in progress. -- **ObservableGauge**: An observable gauge is a value that can be set to any - value, and can be observed asynchronously. They are used for values that do - not "accumulate" over time, but rather have a specific value at any given - time, such as the current temperature. - -Learn more about the full metrics API in the -[OpenTelemetry JS API docs](https://open-telemetry.github.io/opentelemetry-js/classes/_opentelemetry_api.MetricsAPI.html). - -## Context propagation - -In OpenTelemetry, context propagation is the process of passing some context -information (such as the current span) from one part of an application to -another, without having to pass it explicitly as an argument to every function. - -In Deno, context propagation is done using the rules of `AsyncContext`, the TC39 -proposal for async context propagation. The `AsyncContext` API is not yet -exposed to users in Deno, but it is used internally to propagate the active span -and other context information across asynchronous boundaries. - -A quick overview how AsyncContext propagation works: - -- When a new asynchronous task is started (such as a promise, or a timer), the - current context is saved. -- Then some other code can execute concurrently with the asynchronous task, in a - different context. -- When the asynchronous task completes, the saved context is restored. - -This means that async context propagation essentially behaves like a global -variable that is scoped to the current asynchronous task, and is automatically -copied to any new asynchronous tasks that are started from this current task. - -The `context` API from `npm:@opentelemetry/api@1` exposes this functionality to -users. It works as follows: - -```ts -import { context } from "npm:@opentelemetry/api@1"; - -// Get the currently active context -const currentContext = context.active(); - -// You can add create a new context with a value added to it -const newContext = currentContext.setValue("id", 1); - -// The current context is not changed by calling setValue -console.log(currentContext.getValue("id")); // undefined - -// You can run a function inside a new context -context.with(newContext, () => { - // Any code in this block will run with the new context - console.log(context.active().getValue("id")); // 1 - - // The context is also available in any functions called from this block - function myFunction() { - return context.active().getValue("id"); - } - console.log(myFunction()); // 1 - - // And it is also available in any asynchronous callbacks scheduled from here - setTimeout(() => { - console.log(context.active().getValue("id")); // 1 - }, 10); -}); - -// Outside, the context is still the same -console.log(context.active().getValue("id")); // undefined -``` - -The context API integrates with spans too. For example, to run a function in the -context of a specific span, the span can be added to a context, and then the -function can be run in that context: - -```ts -import { context, trace } from "npm:@opentelemetry/api@1"; - -const tracer = trace.getTracer("my-app", "1.0.0"); - -const span = tracer.startSpan("myFunction"); -const contextWithSpan = trace.setSpan(context.active(), span); - -context.with(contextWithSpan, () => { - const activeSpan = trace.getActiveSpan(); - console.log(activeSpan === span); // true -}); - -// Don't forget to end the span! -span.end(); -``` - -Learn more about the full context API in the -[OpenTelemetry JS API docs](https://open-telemetry.github.io/opentelemetry-js/classes/_opentelemetry_api.ContextAPI.html). - -## Configuration - -The OpenTelemetry integration can be enabled by setting the `OTEL_DENO=1` -environment variable. - -The endpoint and protocol for the OTLP exporter can be configured using the -`OTEL_EXPORTER_OTLP_ENDPOINT` and `OTEL_EXPORTER_OTLP_PROTOCOL` environment -variables. - -If the endpoint requires authentication, headers can be configured using the -`OTEL_EXPORTER_OTLP_HEADERS` environment variable. - -Endpoint can all be overridden individually for metrics, traces, and logs by -using specific environment variables, such as: - -- `OTEL_EXPORTER_OTLP_METRICS_ENDPOINT` -- `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` -- `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT` - -For more information on headers that can be used to configure the OTLP exporter, -[see the OpenTelemetry website](https://opentelemetry.io/docs/specs/otel/protocol/exporter/#configuration-options). - -The resource that is associated with the telemetry data can be configured using -the `OTEL_SERVICE_NAME` and `OTEL_RESOURCE_ATTRIBUTES` environment variables. In -addition to attributes set via the `OTEL_RESOURCE_ATTRIBUTES` environment -variable, the following attributes are automatically set: - -- `service.name`: If `OTEL_SERVICE_NAME` is not set, the value is set to - `<unknown_service>`. -- `process.runtime.name`: `deno` -- `process.runtime.version`: The version of the Deno runtime. -- `telemetry.sdk.name`: `deno-opentelemetry` -- `telemetry.sdk.language`: `deno-rust` -- `telemetry.sdk.version`: The version of the Deno runtime, plus the version of - the `opentelemetry` Rust crate being used by Deno, separated by a `-`. - -Metric collection frequency can be configured using the -`OTEL_METRIC_EXPORT_INTERVAL` environment variable. The default value is `60000` -milliseconds (60 seconds). - -Span exporter batching can be configured using the batch span processor -environment variables described in the -[OpenTelemetry specification](https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#batch-span-processor). - -Log exporter batching can be configured using the batch log record processor -environment variables described in the -[OpenTelemetry specification](https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#batch-log-record-processor). - -## Limitations - -While the OpenTelemetry integration for Deno is in development, there are some -limitations to be aware of: - -- Traces are always sampled (i.e. `OTEL_TRACE_SAMPLER=parentbased_always_on`). -- Traces do not support events and links. -- Automatic propagation of the trace context in `Deno.serve` and `fetch` is not - supported. -- Metric exemplars are not supported. -- Custom log streams (e.g. logs other than `console.log` and `console.error`) - are not supported. -- The only supported exporter is OTLP - other exporters are not supported. -- Only `http/protobuf` and `http/json` protocols are supported for OTLP. Other - protocols such as `grpc` are not supported. -- Metrics from observable (asynchronous) meters are not collected on process - exit/crash, so the last value of metrics may not be exported. Synchronous - metrics are exported on process exit/crash. -- The limits specified in the `OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT`, - `OTEL_ATTRIBUTE_COUNT_LIMIT`, `OTEL_SPAN_EVENT_COUNT_LIMIT`, - `OTEL_SPAN_LINK_COUNT_LIMIT`, `OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT`, and - `OTEL_LINK_ATTRIBUTE_COUNT_LIMIT` environment variable are not respected for - trace spans. -- The `OTEL_METRIC_EXPORT_TIMEOUT` environment variable is not respected. -- HTTP methods are that are not known are not normalized to `_OTHER` in the - `http.request.method` span attribute as per the OpenTelemetry semantic - conventions. -- The HTTP server span for `Deno.serve` does not have an OpenTelemtry status - set, and if the handler throws (ie `onError` is invoked), the span will not - have an error status set and the error will not be attached to the span via - event. -- There is no mechanism to add a `http.route` attribute to the HTTP client span - for `fetch`, or to update the span name to include the route. diff --git a/runtime/fundamentals/security.md b/runtime/fundamentals/security.md index ef43e53e3..6123205de 100644 --- a/runtime/fundamentals/security.md +++ b/runtime/fundamentals/security.md @@ -333,10 +333,10 @@ its privileges without user consent. Deno provides a mechanism for executing subprocesses, but this requires explicit permission from the user. This is done using the `--allow-run` flag. -Any subprocesses you spawn from your program run independently from the -permissions granted to the parent process. This means the child processes can -access system resources regardless of the permissions you granted to the Deno -process that spawned it. This is often referred to as privilege escalation. +Any subprocesses you spawn in you program runs independently of the permission +you granted to the parent process. This means the child processes can access +system resources regardless of the permissions you granted to the Deno process +that spawned it. This is often referred to as privilege escalation. Because of this, make sure you carefully consider if you want to grant a program `--allow-run` access: it essentially invalidates the Deno security sandbox. If diff --git a/runtime/reference/cli/task.md b/runtime/reference/cli/task.md index 5a13ed8f9..42e70dcee 100644 --- a/runtime/reference/cli/task.md +++ b/runtime/reference/cli/task.md @@ -109,9 +109,9 @@ Task serve deno run -RN server.ts Listening on http://localhost:8000/ ``` -Dependency tasks are executed in parallel, with the default parallel limit being -equal to number of cores on your machine. To change this limit, use the -`DENO_JOBS` environmental variable. +Dependency tasks are in parallel, with the default parallel limit being equal to +number of cores on your machine. To change this limit use `DENO_JOBS` +environmental variable. Dependencies are tracked and if multiple tasks depend on the same task, that task will only be run once: diff --git a/runtime/reference/cli/unstable_flags.md b/runtime/reference/cli/unstable_flags.md index c33177fd8..f7ee8e5f8 100644 --- a/runtime/reference/cli/unstable_flags.md +++ b/runtime/reference/cli/unstable_flags.md @@ -236,11 +236,6 @@ Enable unstable net APIs in the `Deno` namespace. These APIs include: - [`Deno.DatagramConn`](https://docs.deno.com/api/deno/~/Deno.DatagramConn) -## `--unstable-otel` - -Enable the -[OpenTelemetry integration for Deno](/runtime/fundamentals/open_telemetry). - ## `--unstable` :::caution --unstable is deprecated - use granular flags instead diff --git a/runtime/reference/continuous_integration.md b/runtime/reference/continuous_integration.md index 84e9983c0..c26a2a8ae 100644 --- a/runtime/reference/continuous_integration.md +++ b/runtime/reference/continuous_integration.md @@ -151,7 +151,7 @@ env: steps: - name: Cache Deno dependencies - uses: actions/cache@v4 + uses: actions/cache@v2 with: path: ${{ env.DENO_DIR }} key: my_cache_key From 87652a0df7beb63d2e587c0495e33726fdfa88e1 Mon Sep 17 00:00:00 2001 From: Jo Franchetti <jofranchetti@gmail.com> Date: Thu, 9 Jan 2025 13:46:29 +0000 Subject: [PATCH 6/7] add back deleted file --- runtime/fundamentals/open_telemetry.md | 605 +++++++++++++++++++++++++ 1 file changed, 605 insertions(+) create mode 100644 runtime/fundamentals/open_telemetry.md diff --git a/runtime/fundamentals/open_telemetry.md b/runtime/fundamentals/open_telemetry.md new file mode 100644 index 000000000..64bfcd951 --- /dev/null +++ b/runtime/fundamentals/open_telemetry.md @@ -0,0 +1,605 @@ +--- +title: OpenTelemetry +--- + +:::caution + +The OpenTelemetry integration for Deno is still in development and may change. +To use it, you must pass the `--unstable-otel` flag to Deno. + +::: + +Deno has built in support for [OpenTelemetry](https://opentelemetry.io/). + +> OpenTelemetry is a collection of APIs, SDKs, and tools. Use it to instrument, +> generate, collect, and export telemetry data (metrics, logs, and traces) to +> help you analyze your software’s performance and behavior. +> +> <i>- https://opentelemetry.io/</i> + +This integration enables you to monitor your Deno applications using +OpenTelemetry observability tooling with instruments like logs, metrics, and +traces. + +Deno provides the following features: + +- Exporting of collected metrics, traces, and logs to a server using the + OpenTelemetry protocol. +- [Automatic instrumentation](#auto-instrumentation) of the Deno runtime with + OpenTelemetry metrics, traces, and logs. +- [Collection of user defined metrics, traces, and logs](#user-metrics) created + with the `npm:@opentelemetry/api` package. + +## Quick start + +To enable the OpenTelemetry integration, run your Deno script with the +`--unstable-otel` flag and set the environment variable `OTEL_DENO=1`: + +```sh +OTEL_DENO=1 deno run --unstable-otel my_script.ts +``` + +This will automatically collect and export runtime observability data to an +OpenTelemetry endpoint at `localhost:4318` using Protobuf over HTTP +(`http/protobuf`). + +:::tip + +If you do not have an OpenTelemetry collector set up yet, you can get started +with a +[local LGTM stack in Docker](https://github.com/grafana/docker-otel-lgtm/tree/main?tab=readme-ov-file) +(Loki (logs), Grafana (dashboard), Tempo (traces), and Mimir (metrics)) by +running the following command: + +```sh +docker run --name lgtm -p 3000:3000 -p 4317:4317 -p 4318:4318 --rm -ti \ + -v "$PWD"/lgtm/grafana:/data/grafana \ + -v "$PWD"/lgtm/prometheus:/data/prometheus \ + -v "$PWD"/lgtm/loki:/data/loki \ + -e GF_PATHS_DATA=/data/grafana \ + docker.io/grafana/otel-lgtm:0.8.1 +``` + +You can then access the Grafana dashboard at `http://localhost:3000` with the +username `admin` and password `admin`. + +::: + +This will automatically collect and export runtime observability data like +`console.log`, traces for HTTP requests, and metrics for the Deno runtime. +[Learn more about auto instrumentation](#auto-instrumentation). + +You can also create your own metrics, traces, and logs using the +`npm:@opentelemetry/api` package. +[Learn more about user defined metrics](#user-metrics). + +## Auto instrumentation + +Deno automatically collects and exports some observability data to the OTLP +endpoint. + +This data is exported in the built-in instrumentation scope of the Deno runtime. +This scope has the name `deno`. The version of the Deno runtime is the version +of the `deno` instrumentation scope. (e.g. `deno:2.1.4`). + +### Traces + +Deno automatically creates spans for various operations, such as: + +- Incoming HTTP requests served with `Deno.serve`. +- Outgoing HTTP requests made with `fetch`. + +#### `Deno.serve` + +When you use `Deno.serve` to create an HTTP server, a span is created for each +incoming request. The span automatically ends when response headers are sent +(not when the response body is done sending). + +The name of the created span is `${method}`. The span kind is `server`. + +The following attributes are automatically added to the span on creation: + +- `http.request.method`: The HTTP method of the request. +- `url.full`: The full URL of the request (as would be reported by `req.url`). +- `url.scheme`: The scheme of the request URL (e.g. `http` or `https`). +- `url.path`: The path of the request URL. +- `url.query`: The query string of the request URL. + +After the request is handled, the following attributes are added: + +- `http.status_code`: The status code of the response. + +Deno does not automatically add a `http.route` attribute to the span as the +route is not known by the runtime, and instead is determined by the routing +logic in a user's handler function. If you want to add a `http.route` attribute +to the span, you can do so in your handler function using +`npm:@opentelemetry/api`. In this case you should also update the span name to +include the route. + +```ts +import { trace } from "npm:@opentelemetry/api@1"; + +const INDEX_ROUTE = new URLPattern({ pathname: "/" }); +const BOOK_ROUTE = new URLPattern({ pathname: "/book/:id" }); + +Deno.serve(async (req) => { + const span = trace.getActiveSpan(); + if (INDEX_ROUTE.test(req.url)) { + span.setAttribute("http.route", "/"); + span.updateName(`${req.method} /`); + + // handle index route + } else if (BOOK_ROUTE.test(req.url)) { + span.setAttribute("http.route", "/book/:id"); + span.updateName(`${req.method} /book/:id`); + + // handle book route + } else { + return new Response("Not found", { status: 404 }); + } +}); +``` + +#### `fetch` + +When you use `fetch` to make an HTTP request, a span is created for the request. +The span automatically ends when the response headers are received. + +The name of the created span is `${method}`. The span kind is `client`. + +The following attributes are automatically added to the span on creation: + +- `http.request.method`: The HTTP method of the request. +- `url.full`: The full URL of the request. +- `url.scheme`: The scheme of the request URL. +- `url.path`: The path of the request URL. +- `url.query`: The query string of the request URL. + +After the response is received, the following attributes are added: + +- `http.status_code`: The status code of the response. + +### Metrics + +The following metrics are automatically collected and exported: + +_None yet_ + +### Logs + +The following logs are automatically collected and exported: + +- Any logs created with `console.*` methods such as `console.log` and + `console.error`. +- Any logs created by the Deno runtime, such as debug logs, `Downloading` logs, + and similar. +- Any errors that cause the Deno runtime to exit (both from user code, and from + the runtime itself). + +Logs raised from JavaScript code will be exported with the relevant span +context, if the log occurred inside of an active span. + +`console` auto instrumentation can be configured using the `OTEL_DENO_CONSOLE` +environment variable: + +- `capture`: Logs are emitted to stdout/stderr and are also exported with + OpenTelemetry. (default) +- `replace`: Logs are only exported with OpenTelemetry, and not emitted to + stdout/stderr. +- `ignore`: Logs are emitted only to stdout/stderr, and will not be exported + with OpenTelemetry. + +## User metrics + +In addition to the automatically collected telemetry data, you can also create +your own metrics and traces using the `npm:@opentelemetry/api` package. + +You do not need to configure the `npm:@opentelemetry/api` package to use it with +Deno. Deno sets up the `npm:@opentelemetry/api` package automatically when the +`--unstable-otel` flag is passed. There is no need to call +`metrics.setGlobalMeterProvider()`, `trace.setGlobalTracerProvider()`, or +`context.setGlobalContextManager()`. All configuration of resources, exporter +settings, etc. is done via environment variables. + +Deno works with version `1.x` of the `npm:@opentelemetry/api` package. You can +either import directly from `npm:@opentelemetry/api@1`, or you can install the +package locally with `deno add` and import from `@opentelemetry/api`. + +```sh +deno add npm:@opentelemetry/api@1 +``` + +For both traces and metrics, you need to define names for the tracer and meter +respectively. If you are instrumenting a library, you should name the tracer or +meter after the library (such as `my-awesome-lib`). If you are instrumenting an +application, you should name the tracer or meter after the application (such as +`my-app`). The version of the tracer or meter should be set to the version of +the library or application. + +### Traces + +To create a new span, first import the `trace` object from +`npm:@opentelemetry/api` and create a new tracer: + +```ts +import { trace } from "npm:@opentelemetry/api@1"; + +const tracer = trace.getTracer("my-app", "1.0.0"); +``` + +Then, create a new span using the `tracer.startActiveSpan` method and pass a +callback function to it. You have to manually end the span by calling the `end` +method on the span object returned by `startActiveSpan`. + +```ts +function myFunction() { + return tracer.startActiveSpan("myFunction", (span) => { + try { + // do myFunction's work + } catch (error) { + span.recordException(error); + span.setStatus({ + code: trace.SpanStatusCode.ERROR, + message: (error as Error).message, + }); + throw error; + } finally { + span.end(); + } + }); +} +``` + +`span.end()` should be called in a `finally` block to ensure that the span is +ended even if an error occurs. `span.recordException` and `span.setStatus` +should also be called in a `catch` block, to record any errors that occur. + +Inside of the callback function, the created span is the "active span". You can +get the active span using `trace.getActiveSpan()`. The "active span" will be +used as the parent span for any spans created (manually, or automatically by the +runtime) inside of the callback function (or any functions that are called from +the callback function). +[Learn more about context propagation](#context-propagation). + +The `startActiveSpan` method returns the return value of the callback function. + +Spans can have attributes added to them during their lifetime. Attributes are +key value pairs that represent structured metadata about the span. Attributes +can be added using the `setAttribute` and `setAttributes` methods on the span +object. + +```ts +span.setAttribute("key", "value"); +span.setAttributes({ success: true, "bar.count": 42n, "foo.duration": 123.45 }); +``` + +Values for attributes can be strings, numbers (floats), bigints (clamped to +u64), booleans, or arrays of any of these types. If an attribute value is not +one of these types, it will be ignored. + +The name of a span can be updated using the `updateName` method on the span +object. + +```ts +span.updateName("new name"); +``` + +The status of a span can be set using the `setStatus` method on the span object. +The `recordException` method can be used to record an exception that occurred +during the span's lifetime. `recordException` creates an event with the +exception stack trace and name and attaches it to the span. **`recordException` +does not set the span status to `ERROR`, you must do that manually.** + +```ts +import { SpanStatusCode } from "npm:@opentelemetry/api@1"; + +span.setStatus({ + code: SpanStatusCode.ERROR, + message: "An error occurred", +}); +span.recordException(new Error("An error occurred")); + +// or + +span.setStatus({ + code: SpanStatusCode.OK, +}); +``` + +Spans can also have +[events](https://open-telemetry.github.io/opentelemetry-js/interfaces/_opentelemetry_api.Span.html#addEvent) +and +[links](https://open-telemetry.github.io/opentelemetry-js/interfaces/_opentelemetry_api.Span.html#addLink) +added to them. Events are points in time that are associated with the span. +Links are references to other spans. + +Spans can also be created manually with `tracer.startSpan` which returns a span +object. This method does not set the created span as the active span, so it will +not automatically be used as the parent span for any spans created later, or any +`console.log` calls. A span can manually be set as the active span for a +callback, by using the [context propagation API](#context-propagation). + +Both `tracer.startActiveSpan` and `tracer.startSpan` can take an optional +options bag containing any of the following properties: + +- `kind`: The kind of the span. Can be `SpanKind.CLIENT`, `SpanKind.SERVER`, + `SpanKind.PRODUCER`, `SpanKind.CONSUMER`, or `SpanKind.INTERNAL`. Defaults to + `SpanKind.INTERNAL`. +- `startTime` A `Date` object representing the start time of the span, or a + number representing the start time in milliseconds since the Unix epoch. If + not provided, the current time will be used. +- `attributes`: An object containing attributes to add to the span. +- `links`: An array of links to add to the span. +- `root`: A boolean indicating whether the span should be a root span. If + `true`, the span will not have a parent span (even if there is an active + span). + +After the options bag, both `tracer.startActiveSpan` and `tracer.startSpan` can +also take a `context` object from the +[context propagation API](#context-propagation). + +Learn more about the full tracing API in the +[OpenTelemetry JS API docs](https://open-telemetry.github.io/opentelemetry-js/classes/_opentelemetry_api.TraceAPI.html). + +### Metrics + +To create a metric, first import the `metrics` object from +`npm:@opentelemetry/api` and create a new meter: + +```ts +import { metrics } from "npm:@opentelemetry/api@1"; + +const meter = metrics.getMeter("my-app", "1.0.0"); +``` + +Then, an instrument can be created from the meter, and used to record values: + +```ts +const counter = meter.createCounter("my_counter", { + description: "A simple counter", + unit: "1", +}); + +counter.add(1); +counter.add(2); +``` + +Each recording can also have associated attributes: + +```ts +counter.add(1, { color: "red" }); +counter.add(2, { color: "blue" }); +``` + +:::tip + +In OpenTelemetry, metric attributes should generally have low cardinality. This +means that there should not be too many unique combinations of attribute values. +For example, it is probably fine to have an attribute for which continent a user +is on, but it would be too high cardinality to have an attribute for the exact +latitude and longitude of the user. High cardinality attributes can cause +problems with metric storage and exporting, and should be avoided. Use spans and +logs for high cardinality data. + +::: + +There are several types of instruments that can be created with a meter: + +- **Counter**: A counter is a monotonically increasing value. Counters can only + be positive. They can be used for values that are always increasing, such as + the number of requests handled. + +- **UpDownCounter**: An up-down counter is a value that can both increase and + decrease. Up-down counters can be used for values that can increase and + decrease, such as the number of active connections or requests in progress. + +- **Gauge**: A gauge is a value that can be set to any value. They are used for + values that do not "accumulate" over time, but rather have a specific value at + any given time, such as the current temperature. + +- **Histogram**: A histogram is a value that is recorded as a distribution of + values. Histograms can be used for values that are not just a single number, + but a distribution of numbers, such as the response time of a request in + milliseconds. Histograms can be used to calculate percentiles, averages, and + other statistics. They have a predefined set of boundaries that define the + buckets that the values are placed into. By default, the boundaries are + `[0.0, 5.0, 10.0, 25.0, 50.0, 75.0, 100.0, 250.0, 500.0, 750.0, 1000.0, 2500.0, 5000.0, 7500.0, 10000.0]`. + +There are also several types of observable instruments. These instruments do not +have a synchronous recording method, but instead return a callback that can be +called to record a value. The callback will be called when the OpenTelemetry SDK +is ready to record a value, for example just before exporting. + +```ts +const counter = meter.createObservableCounter("my_counter", { + description: "A simple counter", + unit: "1", +}); +counter.addCallback((res) => { + res.observe(1); + // or + res.observe(1, { color: "red" }); +}); +``` + +There are three types of observable instruments: + +- **ObservableCounter**: An observable counter is a counter that can be observed + asynchronously. It can be used for values that are always increasing, such as + the number of requests handled. +- **ObservableUpDownCounter**: An observable up-down counter is a value that can + both increase and decrease, and can be observed asynchronously. Up-down + counters can be used for values that can increase and decrease, such as the + number of active connections or requests in progress. +- **ObservableGauge**: An observable gauge is a value that can be set to any + value, and can be observed asynchronously. They are used for values that do + not "accumulate" over time, but rather have a specific value at any given + time, such as the current temperature. + +Learn more about the full metrics API in the +[OpenTelemetry JS API docs](https://open-telemetry.github.io/opentelemetry-js/classes/_opentelemetry_api.MetricsAPI.html). + +## Context propagation + +In OpenTelemetry, context propagation is the process of passing some context +information (such as the current span) from one part of an application to +another, without having to pass it explicitly as an argument to every function. + +In Deno, context propagation is done using the rules of `AsyncContext`, the TC39 +proposal for async context propagation. The `AsyncContext` API is not yet +exposed to users in Deno, but it is used internally to propagate the active span +and other context information across asynchronous boundaries. + +A quick overview how AsyncContext propagation works: + +- When a new asynchronous task is started (such as a promise, or a timer), the + current context is saved. +- Then some other code can execute concurrently with the asynchronous task, in a + different context. +- When the asynchronous task completes, the saved context is restored. + +This means that async context propagation essentially behaves like a global +variable that is scoped to the current asynchronous task, and is automatically +copied to any new asynchronous tasks that are started from this current task. + +The `context` API from `npm:@opentelemetry/api@1` exposes this functionality to +users. It works as follows: + +```ts +import { context } from "npm:@opentelemetry/api@1"; + +// Get the currently active context +const currentContext = context.active(); + +// You can add create a new context with a value added to it +const newContext = currentContext.setValue("id", 1); + +// The current context is not changed by calling setValue +console.log(currentContext.getValue("id")); // undefined + +// You can run a function inside a new context +context.with(newContext, () => { + // Any code in this block will run with the new context + console.log(context.active().getValue("id")); // 1 + + // The context is also available in any functions called from this block + function myFunction() { + return context.active().getValue("id"); + } + console.log(myFunction()); // 1 + + // And it is also available in any asynchronous callbacks scheduled from here + setTimeout(() => { + console.log(context.active().getValue("id")); // 1 + }, 10); +}); + +// Outside, the context is still the same +console.log(context.active().getValue("id")); // undefined +``` + +The context API integrates with spans too. For example, to run a function in the +context of a specific span, the span can be added to a context, and then the +function can be run in that context: + +```ts +import { context, trace } from "npm:@opentelemetry/api@1"; + +const tracer = trace.getTracer("my-app", "1.0.0"); + +const span = tracer.startSpan("myFunction"); +const contextWithSpan = trace.setSpan(context.active(), span); + +context.with(contextWithSpan, () => { + const activeSpan = trace.getActiveSpan(); + console.log(activeSpan === span); // true +}); + +// Don't forget to end the span! +span.end(); +``` + +Learn more about the full context API in the +[OpenTelemetry JS API docs](https://open-telemetry.github.io/opentelemetry-js/classes/_opentelemetry_api.ContextAPI.html). + +## Configuration + +The OpenTelemetry integration can be enabled by setting the `OTEL_DENO=1` +environment variable. + +The endpoint and protocol for the OTLP exporter can be configured using the +`OTEL_EXPORTER_OTLP_ENDPOINT` and `OTEL_EXPORTER_OTLP_PROTOCOL` environment +variables. + +If the endpoint requires authentication, headers can be configured using the +`OTEL_EXPORTER_OTLP_HEADERS` environment variable. + +Endpoint can all be overridden individually for metrics, traces, and logs by +using specific environment variables, such as: + +- `OTEL_EXPORTER_OTLP_METRICS_ENDPOINT` +- `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` +- `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT` + +For more information on headers that can be used to configure the OTLP exporter, +[see the OpenTelemetry website](https://opentelemetry.io/docs/specs/otel/protocol/exporter/#configuration-options). + +The resource that is associated with the telemetry data can be configured using +the `OTEL_SERVICE_NAME` and `OTEL_RESOURCE_ATTRIBUTES` environment variables. In +addition to attributes set via the `OTEL_RESOURCE_ATTRIBUTES` environment +variable, the following attributes are automatically set: + +- `service.name`: If `OTEL_SERVICE_NAME` is not set, the value is set to + `<unknown_service>`. +- `process.runtime.name`: `deno` +- `process.runtime.version`: The version of the Deno runtime. +- `telemetry.sdk.name`: `deno-opentelemetry` +- `telemetry.sdk.language`: `deno-rust` +- `telemetry.sdk.version`: The version of the Deno runtime, plus the version of + the `opentelemetry` Rust crate being used by Deno, separated by a `-`. + +Metric collection frequency can be configured using the +`OTEL_METRIC_EXPORT_INTERVAL` environment variable. The default value is `60000` +milliseconds (60 seconds). + +Span exporter batching can be configured using the batch span processor +environment variables described in the +[OpenTelemetry specification](https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#batch-span-processor). + +Log exporter batching can be configured using the batch log record processor +environment variables described in the +[OpenTelemetry specification](https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#batch-log-record-processor). + +## Limitations + +While the OpenTelemetry integration for Deno is in development, there are some +limitations to be aware of: + +- Traces are always sampled (i.e. `OTEL_TRACE_SAMPLER=parentbased_always_on`). +- Traces do not support events and links. +- Automatic propagation of the trace context in `Deno.serve` and `fetch` is not + supported. +- Metric exemplars are not supported. +- Custom log streams (e.g. logs other than `console.log` and `console.error`) + are not supported. +- The only supported exporter is OTLP - other exporters are not supported. +- Only `http/protobuf` and `http/json` protocols are supported for OTLP. Other + protocols such as `grpc` are not supported. +- Metrics from observable (asynchronous) meters are not collected on process + exit/crash, so the last value of metrics may not be exported. Synchronous + metrics are exported on process exit/crash. +- The limits specified in the `OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT`, + `OTEL_ATTRIBUTE_COUNT_LIMIT`, `OTEL_SPAN_EVENT_COUNT_LIMIT`, + `OTEL_SPAN_LINK_COUNT_LIMIT`, `OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT`, and + `OTEL_LINK_ATTRIBUTE_COUNT_LIMIT` environment variable are not respected for + trace spans. +- The `OTEL_METRIC_EXPORT_TIMEOUT` environment variable is not respected. +- HTTP methods are that are not known are not normalized to `_OTHER` in the + `http.request.method` span attribute as per the OpenTelemetry semantic + conventions. +- The HTTP server span for `Deno.serve` does not have an OpenTelemtry status + set, and if the handler throws (ie `onError` is invoked), the span will not + have an error status set and the error will not be attached to the span via + event. +- There is no mechanism to add a `http.route` attribute to the HTTP client span + for `fetch`, or to update the span name to include the route. From 02b1d789b03fb090c935f9c50c72d23fc83f86ce Mon Sep 17 00:00:00 2001 From: Jo Franchetti <jofranchetti@gmail.com> Date: Tue, 14 Jan 2025 11:28:05 +0000 Subject: [PATCH 7/7] Re-org of components that have layout to "partials" and ones that are "raw data elements" as primitives (as originally intended, with corrected spelling) --- _config.markdown.ts | 52 ++ _config.ts | 49 +- deno.lock | 250 +++++++- reference/_layouts/ReferencePage.tsx | 5 +- reference/_pages/Category.tsx | 136 ++-- reference/_pages/Class.tsx | 231 +------ reference/_pages/Function.tsx | 37 +- reference/_pages/Interface.tsx | 62 +- reference/_pages/Module.tsx | 2 - reference/_pages/Namespace.tsx | 87 ++- reference/_pages/Package.tsx | 58 +- reference/_pages/Variable.tsx | 51 +- .../AnchorableHeading.tsx | 0 reference/_pages/partials/Badges.tsx | 69 ++ .../{primatives => partials}/CodeIcon.tsx | 0 reference/_pages/partials/DetailedSection.tsx | 16 + .../_pages/partials/ImplementsSummary.tsx | 26 + .../_pages/partials/JsDocDescription.tsx | 15 + reference/_pages/partials/MemberSection.tsx | 22 + reference/_pages/partials/NameHeading.tsx | 29 + reference/_pages/partials/PropertyItem.tsx | 29 + reference/_pages/partials/SymbolDetails.tsx | 180 ++++++ .../_pages/partials/SymbolSummaryItem.tsx | 61 ++ reference/_pages/partials/TableOfContents.tsx | 51 ++ reference/_pages/primatives/LinkCode.tsx | 59 -- .../primitives/FunctionDefinitionParts.tsx | 37 ++ .../_pages/primitives/FunctionSignature.tsx | 22 + .../primitives/InterfaceMethodSignature.tsx | 19 + reference/_pages/primitives/LinkCode.tsx | 62 ++ reference/_pages/primitives/LinkCode_test.ts | 35 + .../_pages/primitives/MarkdownContent.tsx | 25 + .../_pages/primitives/MethodSignature.tsx | 35 + reference/_pages/primitives/PropertyName.tsx | 35 + reference/_pages/primitives/RawHtml.tsx | 3 + reference/_pages/primitives/TypeSummary.tsx | 24 + reference/_util/categoryBuilding.ts | 32 + reference/_util/categoryBuilding_test.ts | 34 + reference/_util/common.ts | 30 +- reference/_util/methodSignatureRendering.ts | 95 --- reference/_util/symbolMerging.ts | 95 +++ reference/_util/symbolMerging_test.ts | 62 ++ reference/_util/symbolStringBuilding.ts | 229 +++++++ reference/_util/testData.ts | 34 + reference/reference.page.raw.tsx | 57 +- reference/types.ts | 70 +- runtime/fundamentals/open_telemetry.md | 605 ------------------ static/reference-styles/extra-styles.css | 1 + 47 files changed, 1985 insertions(+), 1233 deletions(-) create mode 100644 _config.markdown.ts rename reference/_pages/{primatives => partials}/AnchorableHeading.tsx (100%) create mode 100644 reference/_pages/partials/Badges.tsx rename reference/_pages/{primatives => partials}/CodeIcon.tsx (100%) create mode 100644 reference/_pages/partials/DetailedSection.tsx create mode 100644 reference/_pages/partials/ImplementsSummary.tsx create mode 100644 reference/_pages/partials/JsDocDescription.tsx create mode 100644 reference/_pages/partials/MemberSection.tsx create mode 100644 reference/_pages/partials/NameHeading.tsx create mode 100644 reference/_pages/partials/PropertyItem.tsx create mode 100644 reference/_pages/partials/SymbolDetails.tsx create mode 100644 reference/_pages/partials/SymbolSummaryItem.tsx create mode 100644 reference/_pages/partials/TableOfContents.tsx delete mode 100644 reference/_pages/primatives/LinkCode.tsx create mode 100644 reference/_pages/primitives/FunctionDefinitionParts.tsx create mode 100644 reference/_pages/primitives/FunctionSignature.tsx create mode 100644 reference/_pages/primitives/InterfaceMethodSignature.tsx create mode 100644 reference/_pages/primitives/LinkCode.tsx create mode 100644 reference/_pages/primitives/LinkCode_test.ts create mode 100644 reference/_pages/primitives/MarkdownContent.tsx create mode 100644 reference/_pages/primitives/MethodSignature.tsx create mode 100644 reference/_pages/primitives/PropertyName.tsx create mode 100644 reference/_pages/primitives/RawHtml.tsx create mode 100644 reference/_pages/primitives/TypeSummary.tsx create mode 100644 reference/_util/categoryBuilding.ts create mode 100644 reference/_util/categoryBuilding_test.ts delete mode 100644 reference/_util/methodSignatureRendering.ts create mode 100644 reference/_util/symbolMerging.ts create mode 100644 reference/_util/symbolMerging_test.ts create mode 100644 reference/_util/symbolStringBuilding.ts create mode 100644 reference/_util/testData.ts delete mode 100644 runtime/fundamentals/open_telemetry.md create mode 100644 static/reference-styles/extra-styles.css diff --git a/_config.markdown.ts b/_config.markdown.ts new file mode 100644 index 000000000..78809a4cd --- /dev/null +++ b/_config.markdown.ts @@ -0,0 +1,52 @@ +import anchor from "npm:markdown-it-anchor@9"; +import { full as emoji } from "npm:markdown-it-emoji@3"; +import admonitionPlugin from "./markdown-it/admonition.ts"; +import codeblockCopyPlugin from "./markdown-it/codeblock-copy.ts"; +import codeblockTitlePlugin from "./markdown-it/codeblock-title.ts"; +import relativeLinksPlugin from "./markdown-it/relative-path.ts"; +import replacerPlugin from "./markdown-it/replacer.ts"; +import { MarkdownItOptions } from "lume/deps/markdown_it.ts"; +import markdownit from "markdown-it"; +import Prism from "./prism.ts"; + +export const plugins = [ + replacerPlugin, + emoji, + admonitionPlugin, + codeblockCopyPlugin, + codeblockTitlePlugin, + [ + anchor, + { + permalink: anchor.permalink.linkInsideHeader({ + symbol: + `<span class="sr-only">Jump to heading</span><span aria-hidden="true" class="anchor-end">#</span>`, + placement: "after", + }), + getTokensText(tokens: { type: string; content: string }[]) { + return tokens + .filter((t) => ["text", "code_inline"].includes(t.type)) + .map((t) => t.content.replaceAll(/ \([0-9/]+?\)/g, "")) + .join("") + .trim(); + }, + }, + ], + relativeLinksPlugin, +]; + +export const options: MarkdownItOptions = { + linkify: true, + langPrefix: "highlight notranslate language-", + highlight: (code, lang) => { + if (!lang || !Prism.languages[lang]) { + return code; + } + const result = Prism.highlight(code, Prism.languages[lang], lang); + return result || code; + }, +}; + +export const markdownRenderer = markdownit(options).use(...plugins); + +export default { plugins, options }; diff --git a/_config.ts b/_config.ts index 23ef3a419..c4f388dff 100644 --- a/_config.ts +++ b/_config.ts @@ -12,18 +12,9 @@ import sitemap from "lume/plugins/sitemap.ts"; import tw from "tailwindcss"; import tailwindConfig from "./tailwind.config.js"; -import Prism from "./prism.ts"; - import title from "https://deno.land/x/lume_markdown_plugins@v0.7.0/title.ts"; import toc from "https://deno.land/x/lume_markdown_plugins@v0.7.0/toc.ts"; import { CSS as GFM_CSS } from "https://jsr.io/@deno/gfm/0.8.2/style.ts"; -import anchor from "npm:markdown-it-anchor@9"; -import { full as emoji } from "npm:markdown-it-emoji@3"; -import admonitionPlugin from "./markdown-it/admonition.ts"; -import codeblockCopyPlugin from "./markdown-it/codeblock-copy.ts"; -import codeblockTitlePlugin from "./markdown-it/codeblock-title.ts"; -import relativeLinksPlugin from "./markdown-it/relative-path.ts"; -import replacerPlugin from "./markdown-it/replacer.ts"; import apiDocumentContentTypeMiddleware from "./middleware/apiDocContentType.ts"; import createRoutingMiddleware from "./middleware/functionRoutes.ts"; import createGAMiddleware from "./middleware/googleAnalytics.ts"; @@ -32,6 +23,7 @@ import redirectsMiddleware, { } from "./middleware/redirects.ts"; import { cliNow } from "./timeUtils.ts"; import { log } from "lume/core/utils/log.ts"; +import markdownConfig from "./_config.markdown.ts"; const site = lume( { @@ -59,44 +51,7 @@ const site = lume( }, }, { - markdown: { - plugins: [ - replacerPlugin, - emoji, - admonitionPlugin, - codeblockCopyPlugin, - codeblockTitlePlugin, - [ - anchor, - { - permalink: anchor.permalink.linkInsideHeader({ - symbol: - `<span class="sr-only">Jump to heading</span><span aria-hidden="true" class="anchor-end">#</span>`, - placement: "after", - }), - getTokensText(tokens: { type: string; content: string }[]) { - return tokens - .filter((t) => ["text", "code_inline"].includes(t.type)) - .map((t) => t.content.replaceAll(/ \([0-9/]+?\)/g, "")) - .join("") - .trim(); - }, - }, - ], - relativeLinksPlugin, - ], - options: { - linkify: true, - langPrefix: "highlight notranslate language-", - highlight: (code, lang) => { - if (!lang || !Prism.languages[lang]) { - return code; - } - const result = Prism.highlight(code, Prism.languages[lang], lang); - return result || code; - }, - }, - }, + markdown: markdownConfig, }, ); diff --git a/deno.lock b/deno.lock index c85ea4d23..7d7121f6f 100644 --- a/deno.lock +++ b/deno.lock @@ -6,26 +6,34 @@ "jsr:@deno/doc@0.163.0": "0.163.0", "jsr:@deno/graph@0.86": "0.86.7", "jsr:@deno/graph@~0.82.3": "0.82.3", + "jsr:@hono/hono@*": "4.6.16", "jsr:@std/assert@*": "1.0.8", "jsr:@std/assert@^1.0.6": "1.0.8", "jsr:@std/assert@^1.0.8": "1.0.8", + "jsr:@std/async@*": "1.0.9", "jsr:@std/bytes@*": "1.0.4", "jsr:@std/bytes@^1.0.2": "1.0.4", + "jsr:@std/bytes@^1.0.3": "1.0.4", "jsr:@std/cli@*": "1.0.6", "jsr:@std/cli@1.0.6": "1.0.6", "jsr:@std/cli@^1.0.6": "1.0.6", + "jsr:@std/collections@*": "1.0.9", "jsr:@std/collections@^1.0.5": "1.0.9", + "jsr:@std/crypto@*": "1.0.3", "jsr:@std/crypto@1.0.3": "1.0.3", + "jsr:@std/csv@*": "1.0.4", "jsr:@std/dotenv@~0.225.2": "0.225.2", + "jsr:@std/encoding@*": "1.0.5", "jsr:@std/encoding@1.0.5": "1.0.5", "jsr:@std/encoding@^1.0.5": "1.0.5", + "jsr:@std/fmt@*": "1.0.3", "jsr:@std/fmt@1.0.3": "1.0.3", "jsr:@std/fmt@^1.0.2": "1.0.3", "jsr:@std/fmt@^1.0.3": "1.0.3", "jsr:@std/front-matter@1.0.5": "1.0.5", "jsr:@std/fs@*": "1.0.5", "jsr:@std/fs@1.0.5": "1.0.5", - "jsr:@std/fs@^1.0.4": "1.0.5", + "jsr:@std/fs@^1.0.4": "1.0.8", "jsr:@std/fs@^1.0.6": "1.0.8", "jsr:@std/fs@~0.229.3": "0.229.3", "jsr:@std/html@1.0.3": "1.0.3", @@ -45,7 +53,9 @@ "jsr:@std/path@^1.0.7": "1.0.8", "jsr:@std/path@^1.0.8": "1.0.8", "jsr:@std/streams@^1.0.7": "1.0.8", + "jsr:@std/streams@^1.0.8": "1.0.8", "jsr:@std/testing@*": "1.0.5", + "jsr:@std/toml@*": "1.0.1", "jsr:@std/toml@1.0.1": "1.0.1", "jsr:@std/toml@^1.0.1": "1.0.1", "jsr:@std/ulid@1": "1.0.0", @@ -76,6 +86,7 @@ "npm:meriyah@6.0.3": "6.0.3", "npm:mongodb@6.1.0": "6.1.0", "npm:path-to-regexp@6.2.1": "6.2.1", + "npm:path-to-regexp@^6.3.0": "6.3.0", "npm:postcss-import@16.1.0": "16.1.0_postcss@8.4.47", "npm:postcss@8.4.47": "8.4.47", "npm:preact-render-to-string@6.5.11": "6.5.11_preact@10.24.3", @@ -117,12 +128,18 @@ "@deno/graph@0.86.7": { "integrity": "fb4e5ab648d1b9d464ea55e0f70cd489251b1926e622dd83697b4e28e59c7291" }, + "@hono/hono@4.6.16": { + "integrity": "b540c6b1352d73142895f7bb6bfd0b6cc514ede61c12940338c4ae5dd06cb326" + }, "@std/assert@1.0.8": { "integrity": "ebe0bd7eb488ee39686f77003992f389a06c3da1bbd8022184804852b2fa641b", "dependencies": [ "jsr:@std/internal" ] }, + "@std/async@1.0.9": { + "integrity": "c6472fd0623b3f3daae023cdf7ca5535e1b721dfbf376562c0c12b3fb4867f91" + }, "@std/bytes@1.0.4": { "integrity": "11a0debe522707c95c7b7ef89b478c13fb1583a7cfb9a85674cd2cc2e3a28abc" }, @@ -135,6 +152,12 @@ "@std/crypto@1.0.3": { "integrity": "a2a32f51ddef632d299e3879cd027c630dcd4d1d9a5285d6e6788072f4e51e7f" }, + "@std/csv@1.0.4": { + "integrity": "aa351de654b10b8a407275b61f588863b9538027fd47aed90dd9e7f54ab5bdc0", + "dependencies": [ + "jsr:@std/streams@^1.0.8" + ] + }, "@std/dotenv@0.225.2": { "integrity": "e2025dce4de6c7bca21dece8baddd4262b09d5187217e231b033e088e0c4dd23" }, @@ -181,7 +204,7 @@ "jsr:@std/media-types", "jsr:@std/net", "jsr:@std/path@^1.0.7", - "jsr:@std/streams" + "jsr:@std/streams@^1.0.7" ] }, "@std/internal@1.0.5": { @@ -223,7 +246,10 @@ "integrity": "548fa456bb6a04d3c1a1e7477986b6cffbce95102d0bb447c67c4ee70e0364be" }, "@std/streams@1.0.8": { - "integrity": "b41332d93d2cf6a82fe4ac2153b930adf1a859392931e2a19d9fabfb6f154fb3" + "integrity": "b41332d93d2cf6a82fe4ac2153b930adf1a859392931e2a19d9fabfb6f154fb3", + "dependencies": [ + "jsr:@std/bytes@^1.0.3" + ] }, "@std/testing@1.0.5": { "integrity": "6e693cbec94c81a1ad3df668685c7ba8e20742bb10305bc7137faa5cf16d2ec4", @@ -234,7 +260,7 @@ "@std/toml@1.0.1": { "integrity": "b55b407159930f338d384b1f8fd317c8e8a35e27ebb8946155f49e3a158d16c4", "dependencies": [ - "jsr:@std/collections" + "jsr:@std/collections@^1.0.5" ] }, "@std/ulid@1.0.0": { @@ -1170,6 +1196,9 @@ "path-to-regexp@6.2.1": { "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==" }, + "path-to-regexp@6.3.0": { + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==" + }, "picocolors@1.1.1": { "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, @@ -1612,6 +1641,11 @@ "integrity": "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==" } }, + "redirects": { + "https://deno.land/std/testing/asserts.ts": "https://deno.land/std@0.224.0/testing/asserts.ts", + "https://deno.land/x/duckdb/mod.ts": "https://deno.land/x/duckdb@0.1.1/mod.ts", + "https://deno.land/x/r2d2/mod.ts": "https://deno.land/x/r2d2@v2.0.0/mod.ts" + }, "remote": { "https://deno.land/std@0.120.0/async/deadline.ts": "1d6ac7aeaee22f75eb86e4e105d6161118aad7b41ae2dd14f4cfd3bf97472b93", "https://deno.land/std@0.120.0/async/debounce.ts": "b2f693e4baa16b62793fd618de6c003b63228db50ecfe3bd51fc5f6dc0bc264b", @@ -1623,11 +1657,66 @@ "https://deno.land/std@0.120.0/async/tee.ts": "3e9f2ef6b36e55188de16a667c702ace4ad0cf84e3720379160e062bf27348ad", "https://deno.land/std@0.120.0/http/http_status.ts": "2ff185827bff21c7be2807fcb09a6a2166464ba57fcd94afe805abab8e09070a", "https://deno.land/std@0.120.0/http/server.ts": "d0be8a9da160255623e645f5b515fa1c6b65eecfbb9cad87ef8002d4f8d56616", + "https://deno.land/std@0.122.0/_util/assert.ts": "2f868145a042a11d5ad0a3c748dcf580add8a0dbc0e876eaa0026303a5488f58", + "https://deno.land/std@0.122.0/_util/os.ts": "dfb186cc4e968c770ab6cc3288bd65f4871be03b93beecae57d657232ecffcac", + "https://deno.land/std@0.122.0/fmt/colors.ts": "8368ddf2d48dfe413ffd04cdbb7ae6a1009cf0dccc9c7ff1d76259d9c61a0621", + "https://deno.land/std@0.122.0/path/_constants.ts": "1247fee4a79b70c89f23499691ef169b41b6ccf01887a0abd131009c5581b853", + "https://deno.land/std@0.122.0/path/_interface.ts": "1fa73b02aaa24867e481a48492b44f2598cd9dfa513c7b34001437007d3642e4", + "https://deno.land/std@0.122.0/path/_util.ts": "2e06a3b9e79beaf62687196bd4b60a4c391d862cfa007a20fc3a39f778ba073b", + "https://deno.land/std@0.122.0/path/common.ts": "f41a38a0719a1e85aa11c6ba3bea5e37c15dd009d705bd8873f94c833568cbc4", + "https://deno.land/std@0.122.0/path/glob.ts": "7bf2349e818e332a830f3d8874c3f45dd7580b6c742ed50dbf6282d84ab18405", + "https://deno.land/std@0.122.0/path/mod.ts": "4465dc494f271b02569edbb4a18d727063b5dbd6ed84283ff906260970a15d12", + "https://deno.land/std@0.122.0/path/posix.ts": "34349174b9cd121625a2810837a82dd8b986bbaaad5ade690d1de75bbb4555b2", + "https://deno.land/std@0.122.0/path/separator.ts": "8fdcf289b1b76fd726a508f57d3370ca029ae6976fcde5044007f062e643ff1c", + "https://deno.land/std@0.122.0/path/win32.ts": "11549e8c6df8307a8efcfa47ad7b2a75da743eac7d4c89c9723a944661c8bd2e", "https://deno.land/std@0.143.0/_util/assert.ts": "e94f2eb37cebd7f199952e242c77654e43333c1ac4c5c700e929ea3aa5489f74", "https://deno.land/std@0.143.0/datetime/formatter.ts": "7c8e6d16a0950f400aef41b9f1eb9168249869776ec520265dfda785d746589e", "https://deno.land/std@0.143.0/datetime/mod.ts": "dcab9ae7be83cbf74b7863e83bd16e7c646a8dea2f019092905630eb7a545739", "https://deno.land/std@0.143.0/datetime/tokenizer.ts": "7381e28f6ab51cb504c7e132be31773d73ef2f3e1e50a812736962b9df1e8c47", "https://deno.land/std@0.143.0/http/cookie.ts": "526f27762fad7bf84fbe491de7eba7c406057501eec6edcad7884a16b242fddf", + "https://deno.land/std@0.160.0/_util/assert.ts": "e94f2eb37cebd7f199952e242c77654e43333c1ac4c5c700e929ea3aa5489f74", + "https://deno.land/std@0.160.0/_util/os.ts": "8a33345f74990e627b9dfe2de9b040004b08ea5146c7c9e8fe9a29070d193934", + "https://deno.land/std@0.160.0/async/abortable.ts": "87aa7230be8360c24ad437212311c9e8d4328854baec27b4c7abb26e85515c06", + "https://deno.land/std@0.160.0/async/deadline.ts": "48ac998d7564969f3e6ec6b6f9bf0217ebd00239b1b2292feba61272d5dd58d0", + "https://deno.land/std@0.160.0/async/debounce.ts": "dc8b92d4a4fe7eac32c924f2b8d3e62112530db70cadce27042689d82970b350", + "https://deno.land/std@0.160.0/async/deferred.ts": "d8fb253ffde2a056e4889ef7e90f3928f28be9f9294b6505773d33f136aab4e6", + "https://deno.land/std@0.160.0/async/delay.ts": "0419dfc993752849692d1f9647edf13407c7facc3509b099381be99ffbc9d699", + "https://deno.land/std@0.160.0/async/mod.ts": "dd0a8ed4f3984ffabe2fcca7c9f466b7932d57b1864ffee148a5d5388316db6b", + "https://deno.land/std@0.160.0/async/mux_async_iterator.ts": "3447b28a2a582224a3d4d3596bccbba6e85040da3b97ed64012f7decce98d093", + "https://deno.land/std@0.160.0/async/pool.ts": "ef9eb97b388543acbf0ac32647121e4dbe629236899586c4d4311a8770fbb239", + "https://deno.land/std@0.160.0/async/tee.ts": "9af3a3e7612af75861308b52249e167f5ebc3dcfc8a1a4d45462d96606ee2b70", + "https://deno.land/std@0.160.0/bytes/bytes_list.ts": "aba5e2369e77d426b10af1de0dcc4531acecec27f9b9056f4f7bfbf8ac147ab4", + "https://deno.land/std@0.160.0/bytes/equals.ts": "3c3558c3ae85526f84510aa2b48ab2ad7bdd899e2e0f5b7a8ffc85acb3a6043a", + "https://deno.land/std@0.160.0/bytes/mod.ts": "b2e342fd3669176a27a4e15061e9d588b89c1aaf5008ab71766e23669565d179", + "https://deno.land/std@0.160.0/crypto/_fnv/fnv32.ts": "aa9bddead8c6345087d3abd4ef35fb9655622afc333fc41fff382b36e64280b5", + "https://deno.land/std@0.160.0/crypto/_fnv/fnv64.ts": "625d7e7505b6cb2e9801b5fd6ed0a89256bac12b2bbb3e4664b85a88b0ec5bef", + "https://deno.land/std@0.160.0/crypto/_fnv/index.ts": "a8f6a361b4c6d54e5e89c16098f99b6962a1dd6ad1307dbc97fa1ecac5d7060a", + "https://deno.land/std@0.160.0/crypto/_fnv/util.ts": "4848313bed7f00f55be3cb080aa0583fc007812ba965b03e4009665bde614ce3", + "https://deno.land/std@0.160.0/crypto/_wasm_crypto/lib/deno_std_wasm_crypto.generated.mjs": "258b484c2da27578bec61c01d4b62c21f72268d928d03c968c4eb590cb3bd830", + "https://deno.land/std@0.160.0/crypto/_wasm_crypto/mod.ts": "6c60d332716147ded0eece0861780678d51b560f533b27db2e15c64a4ef83665", + "https://deno.land/std@0.160.0/crypto/keystack.ts": "e481eed28007395e554a435e880fee83a5c73b9259ed8a135a75e4b1e4f381f7", + "https://deno.land/std@0.160.0/crypto/mod.ts": "fadedc013b4a86fda6305f1adc6d1c02225834d53cff5d95cc05f62b25127517", + "https://deno.land/std@0.160.0/crypto/timing_safe_equal.ts": "82a29b737bc8932d75d7a20c404136089d5d23629e94ba14efa98a8cc066c73e", + "https://deno.land/std@0.160.0/datetime/formatter.ts": "7c8e6d16a0950f400aef41b9f1eb9168249869776ec520265dfda785d746589e", + "https://deno.land/std@0.160.0/datetime/mod.ts": "ea927ca96dfb28c7b9a5eed5bdc7ac46bb9db38038c4922631895cea342fea87", + "https://deno.land/std@0.160.0/datetime/tokenizer.ts": "7381e28f6ab51cb504c7e132be31773d73ef2f3e1e50a812736962b9df1e8c47", + "https://deno.land/std@0.160.0/encoding/base64.ts": "c57868ca7fa2fbe919f57f88a623ad34e3d970d675bdc1ff3a9d02bba7409db2", + "https://deno.land/std@0.160.0/encoding/base64url.ts": "a5f82a9fa703bd85a5eb8e7c1296bc6529e601ebd9642cc2b5eaa6b38fa9e05a", + "https://deno.land/std@0.160.0/encoding/hex.ts": "4cc5324417cbb4ac9b828453d35aed45b9cc29506fad658f1f138d981ae33795", + "https://deno.land/std@0.160.0/fmt/colors.ts": "9e36a716611dcd2e4865adea9c4bec916b5c60caad4cdcdc630d4974e6bb8bd4", + "https://deno.land/std@0.160.0/io/buffer.ts": "fae02290f52301c4e0188670e730cd902f9307fb732d79c4aa14ebdc82497289", + "https://deno.land/std@0.160.0/path/_constants.ts": "df1db3ffa6dd6d1252cc9617e5d72165cd2483df90e93833e13580687b6083c3", + "https://deno.land/std@0.160.0/path/_interface.ts": "ee3b431a336b80cf445441109d089b70d87d5e248f4f90ff906820889ecf8d09", + "https://deno.land/std@0.160.0/path/_util.ts": "d16be2a16e1204b65f9d0dfc54a9bc472cafe5f4a190b3c8471ec2016ccd1677", + "https://deno.land/std@0.160.0/path/common.ts": "bee563630abd2d97f99d83c96c2fa0cca7cee103e8cb4e7699ec4d5db7bd2633", + "https://deno.land/std@0.160.0/path/glob.ts": "cb5255638de1048973c3e69e420c77dc04f75755524cb3b2e160fe9277d939ee", + "https://deno.land/std@0.160.0/path/mod.ts": "56fec03ad0ebd61b6ab39ddb9b0ddb4c4a5c9f2f4f632e09dd37ec9ebfd722ac", + "https://deno.land/std@0.160.0/path/posix.ts": "6b63de7097e68c8663c84ccedc0fd977656eb134432d818ecd3a4e122638ac24", + "https://deno.land/std@0.160.0/path/separator.ts": "fe1816cb765a8068afb3e8f13ad272351c85cbc739af56dacfc7d93d710fe0f9", + "https://deno.land/std@0.160.0/path/win32.ts": "ee8826dce087d31c5c81cd414714e677eb68febc40308de87a2ce4b40e10fb8d", + "https://deno.land/std@0.160.0/testing/_diff.ts": "a23e7fc2b4d8daa3e158fa06856bedf5334ce2a2831e8bf9e509717f455adb2c", + "https://deno.land/std@0.160.0/testing/_format.ts": "cd11136e1797791045e639e9f0f4640d5b4166148796cad37e6ef75f7d7f3832", + "https://deno.land/std@0.160.0/testing/asserts.ts": "1e340c589853e82e0807629ba31a43c84ebdcdeca910c4a9705715dfdb0f5ce8", "https://deno.land/std@0.170.0/_util/asserts.ts": "d0844e9b62510f89ce1f9878b046f6a57bf88f208a10304aab50efcb48365272", "https://deno.land/std@0.170.0/_util/os.ts": "8a33345f74990e627b9dfe2de9b040004b08ea5146c7c9e8fe9a29070d193934", "https://deno.land/std@0.170.0/encoding/base64.ts": "8605e018e49211efc767686f6f687827d7f5fd5217163e981d8d693105640d7a", @@ -1641,6 +1730,72 @@ "https://deno.land/std@0.170.0/path/posix.ts": "b859684bc4d80edfd4cad0a82371b50c716330bed51143d6dcdbe59e6278b30c", "https://deno.land/std@0.170.0/path/separator.ts": "fe1816cb765a8068afb3e8f13ad272351c85cbc739af56dacfc7d93d710fe0f9", "https://deno.land/std@0.170.0/path/win32.ts": "7cebd2bda6657371adc00061a1d23fdd87bcdf64b4843bb148b0b24c11b40f69", + "https://deno.land/std@0.203.0/bytes/bytes_list.ts": "ecf5098c230b793970f43c06e8f30d70b937c031658365aeb3de9a8ae4d406a3", + "https://deno.land/std@0.203.0/bytes/concat.ts": "d26d6f3d7922e6d663dacfcd357563b7bf4a380ce5b9c2bbe0c8586662f25ce2", + "https://deno.land/std@0.203.0/collections/chunk.ts": "f82c52a82ad9338018570c42f6de0fb132fcb14914c31a444e360ac104d7b55b", + "https://deno.land/std@0.203.0/io/read_delim.ts": "8ea988eac1503c7118bfcf00b4e4a422450548af8e18328f6f599553cfe78012", + "https://deno.land/std@0.203.0/streams/write_all.ts": "4cdd36256f892fe7aead46338054f6ea813a63765e87bda4c60e8c5a57d1c5c1", + "https://deno.land/std@0.224.0/assert/_constants.ts": "a271e8ef5a573f1df8e822a6eb9d09df064ad66a4390f21b3e31f820a38e0975", + "https://deno.land/std@0.224.0/assert/assert.ts": "09d30564c09de846855b7b071e62b5974b001bb72a4b797958fe0660e7849834", + "https://deno.land/std@0.224.0/assert/assert_almost_equals.ts": "9e416114322012c9a21fa68e187637ce2d7df25bcbdbfd957cd639e65d3cf293", + "https://deno.land/std@0.224.0/assert/assert_array_includes.ts": "14c5094471bc8e4a7895fc6aa5a184300d8a1879606574cb1cd715ef36a4a3c7", + "https://deno.land/std@0.224.0/assert/assert_equals.ts": "3bbca947d85b9d374a108687b1a8ba3785a7850436b5a8930d81f34a32cb8c74", + "https://deno.land/std@0.224.0/assert/assert_exists.ts": "43420cf7f956748ae6ed1230646567b3593cb7a36c5a5327269279c870c5ddfd", + "https://deno.land/std@0.224.0/assert/assert_false.ts": "3e9be8e33275db00d952e9acb0cd29481a44fa0a4af6d37239ff58d79e8edeff", + "https://deno.land/std@0.224.0/assert/assert_greater.ts": "5e57b201fd51b64ced36c828e3dfd773412c1a6120c1a5a99066c9b261974e46", + "https://deno.land/std@0.224.0/assert/assert_greater_or_equal.ts": "9870030f997a08361b6f63400273c2fb1856f5db86c0c3852aab2a002e425c5b", + "https://deno.land/std@0.224.0/assert/assert_instance_of.ts": "e22343c1fdcacfaea8f37784ad782683ec1cf599ae9b1b618954e9c22f376f2c", + "https://deno.land/std@0.224.0/assert/assert_is_error.ts": "f856b3bc978a7aa6a601f3fec6603491ab6255118afa6baa84b04426dd3cc491", + "https://deno.land/std@0.224.0/assert/assert_less.ts": "60b61e13a1982865a72726a5fa86c24fad7eb27c3c08b13883fb68882b307f68", + "https://deno.land/std@0.224.0/assert/assert_less_or_equal.ts": "d2c84e17faba4afe085e6c9123a63395accf4f9e00150db899c46e67420e0ec3", + "https://deno.land/std@0.224.0/assert/assert_match.ts": "ace1710dd3b2811c391946954234b5da910c5665aed817943d086d4d4871a8b7", + "https://deno.land/std@0.224.0/assert/assert_not_equals.ts": "78d45dd46133d76ce624b2c6c09392f6110f0df9b73f911d20208a68dee2ef29", + "https://deno.land/std@0.224.0/assert/assert_not_instance_of.ts": "3434a669b4d20cdcc5359779301a0588f941ffdc2ad68803c31eabdb4890cf7a", + "https://deno.land/std@0.224.0/assert/assert_not_match.ts": "df30417240aa2d35b1ea44df7e541991348a063d9ee823430e0b58079a72242a", + "https://deno.land/std@0.224.0/assert/assert_not_strict_equals.ts": "37f73880bd672709373d6dc2c5f148691119bed161f3020fff3548a0496f71b8", + "https://deno.land/std@0.224.0/assert/assert_object_match.ts": "411450fd194fdaabc0089ae68f916b545a49d7b7e6d0026e84a54c9e7eed2693", + "https://deno.land/std@0.224.0/assert/assert_rejects.ts": "4bee1d6d565a5b623146a14668da8f9eb1f026a4f338bbf92b37e43e0aa53c31", + "https://deno.land/std@0.224.0/assert/assert_strict_equals.ts": "b4f45f0fd2e54d9029171876bd0b42dd9ed0efd8f853ab92a3f50127acfa54f5", + "https://deno.land/std@0.224.0/assert/assert_string_includes.ts": "496b9ecad84deab72c8718735373feb6cdaa071eb91a98206f6f3cb4285e71b8", + "https://deno.land/std@0.224.0/assert/assert_throws.ts": "c6508b2879d465898dab2798009299867e67c570d7d34c90a2d235e4553906eb", + "https://deno.land/std@0.224.0/assert/assertion_error.ts": "ba8752bd27ebc51f723702fac2f54d3e94447598f54264a6653d6413738a8917", + "https://deno.land/std@0.224.0/assert/equal.ts": "bddf07bb5fc718e10bb72d5dc2c36c1ce5a8bdd3b647069b6319e07af181ac47", + "https://deno.land/std@0.224.0/assert/fail.ts": "0eba674ffb47dff083f02ced76d5130460bff1a9a68c6514ebe0cdea4abadb68", + "https://deno.land/std@0.224.0/assert/mod.ts": "48b8cb8a619ea0b7958ad7ee9376500fe902284bb36f0e32c598c3dc34cbd6f3", + "https://deno.land/std@0.224.0/assert/unimplemented.ts": "8c55a5793e9147b4f1ef68cd66496b7d5ba7a9e7ca30c6da070c1a58da723d73", + "https://deno.land/std@0.224.0/assert/unreachable.ts": "5ae3dbf63ef988615b93eb08d395dda771c96546565f9e521ed86f6510c29e19", + "https://deno.land/std@0.224.0/fmt/colors.ts": "508563c0659dd7198ba4bbf87e97f654af3c34eb56ba790260f252ad8012e1c5", + "https://deno.land/std@0.224.0/internal/diff.ts": "6234a4b493ebe65dc67a18a0eb97ef683626a1166a1906232ce186ae9f65f4e6", + "https://deno.land/std@0.224.0/internal/format.ts": "0a98ee226fd3d43450245b1844b47003419d34d210fa989900861c79820d21c2", + "https://deno.land/std@0.224.0/internal/mod.ts": "534125398c8e7426183e12dc255bb635d94e06d0f93c60a297723abe69d3b22e", + "https://deno.land/std@0.224.0/testing/asserts.ts": "d0cdbabadc49cc4247a50732ee0df1403fdcd0f95360294ad448ae8c240f3f5c", + "https://deno.land/std@0.97.0/_util/assert.ts": "2f868145a042a11d5ad0a3c748dcf580add8a0dbc0e876eaa0026303a5488f58", + "https://deno.land/std@0.97.0/_util/os.ts": "e282950a0eaa96760c0cf11e7463e66babd15ec9157d4c9ed49cc0925686f6a7", + "https://deno.land/std@0.97.0/encoding/base64.ts": "eecae390f1f1d1cae6f6c6d732ede5276bf4b9cd29b1d281678c054dc5cc009e", + "https://deno.land/std@0.97.0/encoding/hex.ts": "f952e0727bddb3b2fd2e6889d104eacbd62e92091f540ebd6459317a61932d9b", + "https://deno.land/std@0.97.0/fs/_util.ts": "f2ce811350236ea8c28450ed822a5f42a0892316515b1cd61321dec13569c56b", + "https://deno.land/std@0.97.0/fs/ensure_dir.ts": "b7c103dc41a3d1dbbb522bf183c519c37065fdc234831a4a0f7d671b1ed5fea7", + "https://deno.land/std@0.97.0/fs/exists.ts": "b0d2e31654819cc2a8d37df45d6b14686c0cc1d802e9ff09e902a63e98b85a00", + "https://deno.land/std@0.97.0/hash/_wasm/hash.ts": "cb6ad1ab429f8ac9d6eae48f3286e08236d662e1a2e5cfd681ba1c0f17375895", + "https://deno.land/std@0.97.0/hash/_wasm/wasm.js": "94b1b997ae6fb4e6d2156bcea8f79cfcd1e512a91252b08800a92071e5e84e1a", + "https://deno.land/std@0.97.0/hash/hasher.ts": "57a9ec05dd48a9eceed319ac53463d9873490feea3832d58679df6eec51c176b", + "https://deno.land/std@0.97.0/hash/mod.ts": "5d032bd34186cda2f8d17fc122d621430953a6030d4b3f11172004715e3e2441", + "https://deno.land/std@0.97.0/path/_constants.ts": "1247fee4a79b70c89f23499691ef169b41b6ccf01887a0abd131009c5581b853", + "https://deno.land/std@0.97.0/path/_interface.ts": "1fa73b02aaa24867e481a48492b44f2598cd9dfa513c7b34001437007d3642e4", + "https://deno.land/std@0.97.0/path/_util.ts": "2e06a3b9e79beaf62687196bd4b60a4c391d862cfa007a20fc3a39f778ba073b", + "https://deno.land/std@0.97.0/path/common.ts": "eaf03d08b569e8a87e674e4e265e099f237472b6fd135b3cbeae5827035ea14a", + "https://deno.land/std@0.97.0/path/glob.ts": "314ad9ff263b895795208cdd4d5e35a44618ca3c6dd155e226fb15d065008652", + "https://deno.land/std@0.97.0/path/mod.ts": "4465dc494f271b02569edbb4a18d727063b5dbd6ed84283ff906260970a15d12", + "https://deno.land/std@0.97.0/path/posix.ts": "f56c3c99feb47f30a40ce9d252ef6f00296fa7c0fcb6dd81211bdb3b8b99ca3b", + "https://deno.land/std@0.97.0/path/separator.ts": "8fdcf289b1b76fd726a508f57d3370ca029ae6976fcde5044007f062e643ff1c", + "https://deno.land/std@0.97.0/path/win32.ts": "77f7b3604e0de40f3a7c698e8a79e7f601dc187035a1c21cb1e596666ce112f8", + "https://deno.land/x/cache@0.2.13/cache.ts": "4005aad54fb9aac9ff02526ffa798032e57f2d7966905fdeb7949263b1c95f2f", + "https://deno.land/x/cache@0.2.13/deps.ts": "6f14e76a1a09f329e3f3830c6e72bd10b53a89a75769d5ea886e5d8603e503e6", + "https://deno.land/x/cache@0.2.13/directories.ts": "ef48531cab3f827252e248596d15cede0de179a2fb15392ae24cf8034519994f", + "https://deno.land/x/cache@0.2.13/file.ts": "5abe7d80c6ac594c98e66eb4262962139f48cd9c49dbe2a77e9608760508a09a", + "https://deno.land/x/cache@0.2.13/file_fetcher.ts": "5c793cc83a5b9377679ec313b2a2321e51bf7ed15380fa82d387f1cdef3b924f", + "https://deno.land/x/cache@0.2.13/helpers.ts": "d1545d6432277b7a0b5ea254d1c51d572b6452a8eadd9faa7ad9c5586a1725c4", + "https://deno.land/x/cache@0.2.13/mod.ts": "3188250d3a013ef6c9eb060e5284cf729083af7944a29e60bb3d8597dd20ebcd", "https://deno.land/x/case@2.1.1/lowerCase.ts": "86d5533f9587ed60003181591e40e648838c23f371edfa79d00288153d113b16", "https://deno.land/x/case@2.1.1/normalCase.ts": "6a8b924da9ab0790d99233ae54bfcfc996d229cb91b2533639fe20972cc33dac", "https://deno.land/x/case@2.1.1/snakeCase.ts": "ee2ab4e2c931d30bb79190d090c21eb5c00d1de1b7a9a3e7f33e035ae431333b", @@ -1755,6 +1910,8 @@ "https://deno.land/x/denoflate@1.2.1/mod.ts": "f5628e44b80b3d80ed525afa2ba0f12408e3849db817d47a883b801f9ce69dd6", "https://deno.land/x/denoflate@1.2.1/pkg/denoflate.js": "b9f9ad9457d3f12f28b1fb35c555f57443427f74decb403113d67364e4f2caf4", "https://deno.land/x/denoflate@1.2.1/pkg/denoflate_bg.wasm.js": "d581956245407a2115a3d7e8d85a9641c032940a8e810acbd59ca86afd34d44d", + "https://deno.land/x/duckdb@0.1.1/lib.js": "2eddbf57ac1212c94d60b659f74553b033b4d6fc6249330e07fa72131898d729", + "https://deno.land/x/duckdb@0.1.1/mod.ts": "278cd7eff130287b7edbadd3d6970fb64861b930aa38836a12d20eff53ec0c43", "https://deno.land/x/esbuild@v0.24.0/mod.js": "15b51f08198c373555700a695b6c6630a86f2c254938e81be7711eb6d4edc74e", "https://deno.land/x/jose@v5.9.4/index.ts": "e6e894e8c2b3a11c4071ee1008eb7d85a373ff68b5fbe5d7791d7384cfdee523", "https://deno.land/x/jose@v5.9.4/jwe/compact/decrypt.ts": "43c80cb2dd0545df1b0aed37b710e0c02cd58781236eb0426fa2dd65bf250d8f", @@ -1945,6 +2102,91 @@ "https://deno.land/x/lume_markdown_plugins@v0.7.0/toc/anchors.ts": "8a4a1c6b2c63156622695ceba57fa7100a6e5f109c9a383a1dcaf755233c8184", "https://deno.land/x/lume_markdown_plugins@v0.7.0/toc/mod.ts": "8c7aa6e1dcfabda4264503495a3875388108cd9a5a94b54853b45a8e8cba9f78", "https://deno.land/x/lume_markdown_plugins@v0.7.0/utils.ts": "6e6c3c394709eff39080562732c2dafe404f225253aaded937133ea694c4b735", + "https://deno.land/x/openai@v4.68.1/_shims/MultipartBody.ts": "fe06a56b54ae358c9cf99823096e34b36795fdba2e17513f0e9539210edd797d", + "https://deno.land/x/openai@v4.68.1/_shims/mod.ts": "2e253cb26bfa7060bd37e45626e60e4bf3e7b3fb3e0f559fdfdeef94c73f9372", + "https://deno.land/x/openai@v4.68.1/_vendor/partial-json-parser/parser.ts": "5ee7caf85edb570fb4b95a81e8fb48fc3c9450b70a46efe7f9c1a2010738d060", + "https://deno.land/x/openai@v4.68.1/core.ts": "1a22d633616438e05192c7220c2e6206ce16fb1c46316a50ac4b0e786b1617cb", + "https://deno.land/x/openai@v4.68.1/error.ts": "ffe2c1e6a373a88593c70289b94a44a508a925fde16807e489fa146b538f37de", + "https://deno.land/x/openai@v4.68.1/internal/decoders/line.ts": "6ca47bdd694f2b96506012aa3f4d23a82d71d44dbe66f6f07d62e4733b161bfb", + "https://deno.land/x/openai@v4.68.1/internal/qs/formats.ts": "7bbe68548238a984644a723bd045614c5b0e81c1a0ae5c3acc050931442d67d7", + "https://deno.land/x/openai@v4.68.1/internal/qs/mod.ts": "eab02831aa5e2896d12b824cebf9b2c3a0fc718e8488a05b6d7c2d35895c102a", + "https://deno.land/x/openai@v4.68.1/internal/qs/stringify.ts": "777514fc7107d6878a8ac80897bd4ca6f9be7ecf67ecf51f57359ec5e7186763", + "https://deno.land/x/openai@v4.68.1/internal/qs/types.ts": "9c319089f022ad9d1fd95d87e2232de8c352bb980ee51de757b4bc23181cfbc2", + "https://deno.land/x/openai@v4.68.1/internal/qs/utils.ts": "9a2ecb6321ed49aa4e6f7cd7a47778365c7c3778283264c7c5e1756bfd7b97d1", + "https://deno.land/x/openai@v4.68.1/lib/AbstractChatCompletionRunner.ts": "be24ad750e83495a1ec0ea2a6695798f6210e5f939a5d358597f3e46836c7d7c", + "https://deno.land/x/openai@v4.68.1/lib/AssistantStream.ts": "4d7ab356cc77299e98a5926908ed080de2af0083bba6a1ffeb5509bc8f8d0a9d", + "https://deno.land/x/openai@v4.68.1/lib/ChatCompletionRunner.ts": "f2038e4b0000d59bdfe601703ca9326085e493ba4b90ed8186a61d75d2377248", + "https://deno.land/x/openai@v4.68.1/lib/ChatCompletionStream.ts": "4cb95561b01aa471895d398f1461a96c7272b0efd2c010ad1d4b9620a7ae1f47", + "https://deno.land/x/openai@v4.68.1/lib/ChatCompletionStreamingRunner.ts": "35f8658c5fb354a724f59eca13731523bf565520b5923c00d995b09e2ea9a56f", + "https://deno.land/x/openai@v4.68.1/lib/EventStream.ts": "14836091490ea45c9d1168dd91c179dbad7757ca7c7f953049f2bce10b0cbf6e", + "https://deno.land/x/openai@v4.68.1/lib/RunnableFunction.ts": "640a9d35f27b470a95711d841295ccc530b851dec274f9a70bcd12cb04041545", + "https://deno.land/x/openai@v4.68.1/lib/Util.ts": "aa555aecdbba8b0ae2e8a982a6797cabc0942fa0d2e99ce629d243d9032941b8", + "https://deno.land/x/openai@v4.68.1/lib/chatCompletionUtils.ts": "4991d3e698a54f8e4273eb0f1d73851f7ba549a0a32fabbcd739627677003cb9", + "https://deno.land/x/openai@v4.68.1/lib/jsonschema.ts": "ed31a7861e19e505c43aa7612579b2739243ee8e8dd47ccb5f8908fa5ff3b5f0", + "https://deno.land/x/openai@v4.68.1/lib/parser.ts": "1461923499c521d97bdb7223adc228b3e1073f52d7d7c48822423081b8cc5c0c", + "https://deno.land/x/openai@v4.68.1/mod.ts": "c6a4cb63aa253223a0de019872b6ec6fdbeb5dcec321e35a520484b3fea91904", + "https://deno.land/x/openai@v4.68.1/pagination.ts": "02a6737ca6c987cab6effa2788327d022b9e0edc95dd98db0be986383f484e22", + "https://deno.land/x/openai@v4.68.1/resource.ts": "c496dbda8dc6324d25fcef3d7233310935259d6aa0baf4b0d95039e4913f6380", + "https://deno.land/x/openai@v4.68.1/resources/audio/audio.ts": "45b72cb5faa51c5823d7cc6b7629887373a0f3ccff6509c2d591ef08f1429a56", + "https://deno.land/x/openai@v4.68.1/resources/audio/speech.ts": "406fd5cb6d9799dfbad09f4c9156797114550552f63d9a1822846b4e79f1950e", + "https://deno.land/x/openai@v4.68.1/resources/audio/transcriptions.ts": "c0c9bdfb7a8c118947c70e8bb0d6e3e448c2a7dbf79ebfaa8beca5398dad87cb", + "https://deno.land/x/openai@v4.68.1/resources/audio/translations.ts": "b09cafa74f0f4e9a6d78a687d898d1d9057556142c7df6d5b469f2693a8911d9", + "https://deno.land/x/openai@v4.68.1/resources/batches.ts": "c74d0c40fd748fae60107297a10a62a1a98a646ac2a1b20e9be8499bdceeda57", + "https://deno.land/x/openai@v4.68.1/resources/beta/assistants.ts": "dcf463ceb2424d6cbf8a3785c7d4074226b4a45388567773e5b88d62b81d871b", + "https://deno.land/x/openai@v4.68.1/resources/beta/beta.ts": "18d141633c9ca356feed84f71789d37a0076f711012de4f0120a50226f9eaf88", + "https://deno.land/x/openai@v4.68.1/resources/beta/chat/chat.ts": "80dc8786a4b895e9fb152495496f44ac669e95fbb9c2f5a3c0bd379f408b1e1c", + "https://deno.land/x/openai@v4.68.1/resources/beta/chat/completions.ts": "4599a35da9351cd58ab46e536c3f62e207762f64655786de1f931438c125bf57", + "https://deno.land/x/openai@v4.68.1/resources/beta/threads/messages.ts": "51acea64463644078110c67e280a49f630a8cefa62d7cff20c8e2e1fa10ebf0d", + "https://deno.land/x/openai@v4.68.1/resources/beta/threads/runs/runs.ts": "a26a407ef55673e38e6c51abc8623a6469aed59d8a8fdde8706993e54694cb27", + "https://deno.land/x/openai@v4.68.1/resources/beta/threads/runs/steps.ts": "6c4860df114632ff38fafb2e1dc7485b77dcf88570bc07544016c847db4ed21b", + "https://deno.land/x/openai@v4.68.1/resources/beta/threads/threads.ts": "e410ba22441ce0d8dcfc155a5b57823189ff77ece733f6e8b932db4f3706f22f", + "https://deno.land/x/openai@v4.68.1/resources/beta/vector-stores/file-batches.ts": "9b727c0aa5306580bfc0c474ce1871968707dfa3906fba05d737f95cc05af406", + "https://deno.land/x/openai@v4.68.1/resources/beta/vector-stores/files.ts": "fff8fa00e3df2608741e7b83a95264b3bf9400bf9fe820e2a5ae72d7ea3f0198", + "https://deno.land/x/openai@v4.68.1/resources/beta/vector-stores/vector-stores.ts": "544bdb83534d746d5577801c092a92e1d515571d68fa6509954089b11e813c91", + "https://deno.land/x/openai@v4.68.1/resources/chat/chat.ts": "8209074deaa9a91a7c0fb660eba0bf09358c450cb0e9a2c0ed3df902e05658f6", + "https://deno.land/x/openai@v4.68.1/resources/chat/completions.ts": "3695d31c7256a0042be45746b65148eedc94ba89460be4e8a82a179981766126", + "https://deno.land/x/openai@v4.68.1/resources/chat/mod.ts": "5bf995771df8aa7e86c1ede244f3d710841fb60276456e016a2f5fca595f3388", + "https://deno.land/x/openai@v4.68.1/resources/completions.ts": "eed691559845988ce6ae9149fd4529977addb38174cb94165ebb7fc101fe052d", + "https://deno.land/x/openai@v4.68.1/resources/embeddings.ts": "f85f38aeb2963535a11065b4a1852e07ab5428ec1b0b7997d3d1027f0d4adc4d", + "https://deno.land/x/openai@v4.68.1/resources/files.ts": "b3edff249bbd1bd29c4bac1d2d32dfb44e9afbea0cbf31f37b240748059cff55", + "https://deno.land/x/openai@v4.68.1/resources/fine-tuning/fine-tuning.ts": "5cb89d96fc6d914ae2d68257a4d08a1f12c140c12b7892a14393177ca9c4ca2b", + "https://deno.land/x/openai@v4.68.1/resources/fine-tuning/jobs/checkpoints.ts": "d5a62d828186fb54b50ac2aa0f82a8f9b0c66a7ece0728e79e30cf6e19251bdf", + "https://deno.land/x/openai@v4.68.1/resources/fine-tuning/jobs/jobs.ts": "cce555739996ad8611963b2d68d328c757fa1535fa8fc347982ca8fae2d3d7ac", + "https://deno.land/x/openai@v4.68.1/resources/images.ts": "5774e74b1e88b7ba696272fac83a41c1558737d2640da64bcdfb11ba253b0943", + "https://deno.land/x/openai@v4.68.1/resources/mod.ts": "feac7be25f772e37663e9764a5c6ba6d95ee8aa44a664e119a858bd8c005f52f", + "https://deno.land/x/openai@v4.68.1/resources/models.ts": "fc8620e0d93158560be0e941bbb298870c7f85515ec2a411602fb46a1269ea93", + "https://deno.land/x/openai@v4.68.1/resources/moderations.ts": "b9fb1e5f9597b46628f0181decbbcef7abf54bfbe0f57953669e6c26b45ed6ee", + "https://deno.land/x/openai@v4.68.1/resources/shared.ts": "79abd8e478551d04209384197e86a7bc390a5a3de5d10b0f60aa67c9af4ad3b4", + "https://deno.land/x/openai@v4.68.1/resources/uploads/parts.ts": "261610445bbbacdf913aec27d9084dbdd95c524b5eb74c0a3b9cb650d6135584", + "https://deno.land/x/openai@v4.68.1/resources/uploads/uploads.ts": "6d42e5238aecba708eed1b48ad5eab37d7a9950d06e5f74aa059c8b94cfeff36", + "https://deno.land/x/openai@v4.68.1/streaming.ts": "cde0b254d2a56e2e737b93a80109bead39ebfe9a44595f4d6625a3e1b56311d0", + "https://deno.land/x/openai@v4.68.1/uploads.ts": "8dd7822de2fdb6a46be3941af43788000d4a2b56104180403f68999370d04542", + "https://deno.land/x/openai@v4.68.1/version.ts": "0aac2b4f2e0b91678d8e325ff480fdc863631c896e0e0e2354b3acc988b95d6e", + "https://deno.land/x/plug@0.5.2/deps.ts": "0f53866f60dc4f89bbc3be9d096f7501f2068a51923db220f43f0ad284b6b892", + "https://deno.land/x/plug@0.5.2/plug.ts": "e495c772bc3b19eb30d820bb83f1b75f6047e3009e19d9a5d81dbe68ca642fd9", + "https://deno.land/x/postgres@v0.17.0/client.ts": "348779c9f6a1c75ef1336db662faf08dce7d2101ff72f0d1e341ba1505c8431d", + "https://deno.land/x/postgres@v0.17.0/client/error.ts": "0817583b666fd546664ed52c1d37beccc5a9eebcc6e3c2ead20ada99b681e5f7", + "https://deno.land/x/postgres@v0.17.0/connection/auth.ts": "1070125e2ac4ca4ade36d69a4222d37001903092826d313217987583edd61ce9", + "https://deno.land/x/postgres@v0.17.0/connection/connection.ts": "428ed3efa055870db505092b5d3545ef743497b7b4b72cf8f0593e7dd4788acd", + "https://deno.land/x/postgres@v0.17.0/connection/connection_params.ts": "52bfe90e8860f584b95b1b08c254dde97c3aa763c4b6bee0c80c5930e35459e0", + "https://deno.land/x/postgres@v0.17.0/connection/message.ts": "f9257948b7f87d58bfbfe3fc6e2e08f0de3ef885655904d56a5f73655cc22c5a", + "https://deno.land/x/postgres@v0.17.0/connection/message_code.ts": "466719008b298770c366c5c63f6cf8285b7f76514dadb4b11e7d9756a8a1ddbf", + "https://deno.land/x/postgres@v0.17.0/connection/packet.ts": "050aeff1fc13c9349e89451a155ffcd0b1343dc313a51f84439e3e45f64b56c8", + "https://deno.land/x/postgres@v0.17.0/connection/scram.ts": "0c7a2551fe7b1a1c62dd856b7714731a7e7534ccca10093336782d1bfc5b2bd2", + "https://deno.land/x/postgres@v0.17.0/deps.ts": "f47ccb41f7f97eaad455d94f407ef97146ae99443dbe782894422c869fbba69e", + "https://deno.land/x/postgres@v0.17.0/mod.ts": "a1e18fd9e6fedc8bc24e5aeec3ae6de45e2274be1411fb66e9081420c5e81d7d", + "https://deno.land/x/postgres@v0.17.0/pool.ts": "892db7b5e1787988babecc994a151ebbd7d017f080905cbe9c3d7b44a73032a9", + "https://deno.land/x/postgres@v0.17.0/query/array_parser.ts": "f8a229d82c3801de8266fa2cc4afe12e94fef8d0c479e73655c86ed3667ef33f", + "https://deno.land/x/postgres@v0.17.0/query/decode.ts": "44a4a6cbcf494ed91a4fecae38a57dce63a7b519166f02c702791d9717371419", + "https://deno.land/x/postgres@v0.17.0/query/decoders.ts": "16cb0e60227d86692931e315421b15768c78526e3aeb84e25fcc4111096de9fd", + "https://deno.land/x/postgres@v0.17.0/query/encode.ts": "5f1418a2932b7c2231556e4a5f5f56efef48728014070cfafe7656963f342933", + "https://deno.land/x/postgres@v0.17.0/query/oid.ts": "8c33e1325f34e4ca9f11a48b8066c8cfcace5f64bc1eb17ad7247af4936999e1", + "https://deno.land/x/postgres@v0.17.0/query/query.ts": "edb473cbcfeff2ee1c631272afb25d079d06b66b5853f42492725b03ffa742b6", + "https://deno.land/x/postgres@v0.17.0/query/transaction.ts": "8e75c3ce0aca97da7fe126e68f8e6c08d640e5c8d2016e62cee5c254bebe7fe8", + "https://deno.land/x/postgres@v0.17.0/query/types.ts": "a6dc8024867fe7ccb0ba4b4fa403ee5d474c7742174128c8e689c3b5e5eaa933", + "https://deno.land/x/postgres@v0.17.0/utils/deferred.ts": "dd94f2a57355355c47812b061a51b55263f72d24e9cb3fdb474c7519f4d61083", + "https://deno.land/x/postgres@v0.17.0/utils/utils.ts": "19c3527ddd5c6c4c49ae36397120274c7f41f9d3cbf479cb36065d23329e9f90", + "https://deno.land/x/r2d2@v2.0.0/mod.ts": "9dd57845ed23db1a80e8df3ebaec4bd1ff1a39bbf533b4ee2a40f9c895cd662b", "https://deno.land/x/vento@v1.12.11/deps.ts": "47ad104c87a32292e978f0fba4f69954f7feff3b403833858e1cc51c5313e9db", "https://deno.land/x/vento@v1.12.11/mod.ts": "296c9cc4253c1b88a94fc630a05d9a12947a908966f2db43968141f1c282a7d6", "https://deno.land/x/vento@v1.12.11/plugins/echo.ts": "59adb9137e736029cf18e3d95e010803c8728046d0d040702c16c4930c7b6160", diff --git a/reference/_layouts/ReferencePage.tsx b/reference/_layouts/ReferencePage.tsx index 5ce3aa91b..f5e7261c2 100644 --- a/reference/_layouts/ReferencePage.tsx +++ b/reference/_layouts/ReferencePage.tsx @@ -13,6 +13,7 @@ export default function Layout( {/* sorry mum, put these somewhere good */} <link rel="stylesheet" href="/reference-styles/styles.css" /> <link rel="stylesheet" href="/reference-styles/page.css" /> + <link rel="stylesheet" href="/reference-styles/extra-styles.css" /> <div className={"ddoc"}> <CategoryPanel context={context} /> @@ -30,7 +31,7 @@ export default function Layout( function CategoryPanel({ context }: { context: ReferenceContext }) { const categories = context.currentCategoryList; - const categoryListItems = Object.entries(categories).map(([key, value]) => { + const categoryListItems = categories.entries().map(([key]) => { const categoryLinkUrl = `${context.root}/${context.packageName.toLocaleLowerCase()}/${key.toLocaleLowerCase()}`; @@ -39,7 +40,7 @@ function CategoryPanel({ context }: { context: ReferenceContext }) { <a href={categoryLinkUrl}>{key}</a> </li> ); - }); + }).toArray(); return ( <div id={"categoryPanel"}> diff --git a/reference/_pages/Category.tsx b/reference/_pages/Category.tsx index 9ac1ebfec..e616bacbd 100644 --- a/reference/_pages/Category.tsx +++ b/reference/_pages/Category.tsx @@ -1,16 +1,19 @@ -import { DocNodeBase, DocNodeClass } from "@deno/doc/types"; +import { DocNodeBase } from "@deno/doc/types"; import ReferencePage from "../_layouts/ReferencePage.tsx"; -import { flattenItems } from "../_util/common.ts"; +import { flattenItems, sections } from "../_util/common.ts"; import { - HasNamespace, LumeDocument, MightHaveNamespace, ReferenceContext, } from "../types.ts"; -import { insertLinkCodes } from "./primatives/LinkCode.tsx"; -import { AnchorableHeading } from "./primatives/AnchorableHeading.tsx"; -import { CodeIcon } from "./primatives/CodeIcon.tsx"; +import { AnchorableHeading } from "./partials/AnchorableHeading.tsx"; import { Package } from "./Package.tsx"; +import { SymbolSummaryItem } from "./partials/SymbolSummaryItem.tsx"; +import { + TableOfContents, + TocListItem, + TocSection, +} from "./partials/TableOfContents.tsx"; export default function* getPages( context: ReferenceContext, @@ -21,7 +24,7 @@ export default function* getPages( content: <Package data={context.currentCategoryList} context={context} />, }; - for (const [key] of Object.entries(context.currentCategoryList)) { + for (const [key] of context.currentCategoryList) { yield { title: key, url: @@ -39,27 +42,23 @@ type ListingProps = { export function CategoryBrowse({ categoryName, context }: ListingProps) { const allItems = flattenItems(context.symbols); - const itemsInThisCategory = allItems.filter((item) => + const validItems = allItems.filter((item) => item.jsDoc?.tags?.some((tag) => tag.kind === "category" && tag.doc.toLocaleLowerCase() === categoryName?.toLocaleLowerCase() ) - ); - - const classes = itemsInThisCategory.filter((item) => item.kind === "class") - .sort((a, b) => a.name.localeCompare(b.name)); - - const functions = itemsInThisCategory.filter((item) => - item.kind === "function" ).sort((a, b) => a.name.localeCompare(b.name)); - const interfaces = itemsInThisCategory.filter((item) => - item.kind === "interface" - ).sort((a, b) => a.name.localeCompare(b.name)); - - const typeAliases = itemsInThisCategory.filter((item) => - item.kind === "typeAlias" - ).sort((a, b) => a.name.localeCompare(b.name)); + const itemsOfType = new Map<string, (DocNodeBase & MightHaveNamespace)[]>(); + for (const item of validItems) { + if (!itemsOfType.has(item.kind)) { + itemsOfType.set(item.kind, []); + } + const collection = itemsOfType.get(item.kind); + if (!collection?.includes(item)) { + collection?.push(item); + } + } return ( <ReferencePage @@ -71,12 +70,31 @@ export function CategoryBrowse({ categoryName, context }: ListingProps) { > <main> <div className={"space-y-7"}> - <CategoryPageSection title={"Classes"} items={classes} /> - <CategoryPageSection title={"Functions"} items={functions} /> - <CategoryPageSection title={"Interfaces"} items={interfaces} /> - <CategoryPageSection title={"Type Aliases"} items={typeAliases} /> + {sections.map(([title, kind]) => { + const matching = itemsOfType.get(kind) || []; + return <CategoryPageSection title={title} items={matching} />; + })} </div> </main> + <TableOfContents> + <ul> + {sections.map(([title, kind]) => { + const matching = itemsOfType.get(kind) || []; + return ( + <TocSection title={title}> + {matching.map((x) => { + return ( + <TocListItem + item={{ name: x.fullName || x.name }} + type={kind} + /> + ); + })} + </TocSection> + ); + })} + </ul> + </TableOfContents> </ReferencePage> ); } @@ -84,73 +102,17 @@ export function CategoryBrowse({ categoryName, context }: ListingProps) { function CategoryPageSection( { title, items }: { title: string; items: DocNodeBase[] }, ) { + if (items.length === 0) { + return null; + } + const anchorId = title.replace(" ", "-").toLocaleLowerCase(); return ( <section id={anchorId} className={"section"}> <AnchorableHeading anchor={anchorId}>{title}</AnchorableHeading> <div className={"namespaceSection"}> - {items.map((item) => <CategoryPageItem item={item} />)} + {items.map((item) => <SymbolSummaryItem item={item} />)} </div> </section> ); } - -function CategoryPageItem( - { item }: { item: DocNodeBase & MightHaveNamespace }, -) { - const displayName = item.fullName || item.name; - const firstLineOfJsDoc = item.jsDoc?.doc?.split("\n")[0] || ""; - const descriptionWithLinkCode = insertLinkCodes(firstLineOfJsDoc); - - return ( - <div className={"namespaceItem"}> - <CodeIcon glyph={item.kind} /> - <div className={"namespaceItemContent"}> - <a href={`~/${displayName}`}> - {displayName} - </a> - <p> - {descriptionWithLinkCode} - </p> - <MethodLinks item={item} /> - </div> - </div> - ); -} - -function MethodLinks({ item }: { item: DocNodeBase }) { - if (item.kind !== "class") { - return <></>; - } - - const asClass = item as DocNodeClass & HasNamespace; - const methods = asClass.classDef.methods.sort((a, b) => - a.name.localeCompare(b.name) - ); - - const filteredMethodsList = [ - "[Symbol.dispose]", - "[Symbol.asyncDispose]", - "[Symbol.asyncIterator]", - ]; - - const filteredMethods = methods.filter((method) => - !filteredMethodsList.includes(method.name) - ); - - const methodLinks = filteredMethods.map((method) => { - return ( - <li> - <a href={`~/${asClass.fullName}#${method.name}`}> - {method.name} - </a> - </li> - ); - }); - - return ( - <ul className={"namespaceItemContentSubItems"}> - {methodLinks} - </ul> - ); -} diff --git a/reference/_pages/Class.tsx b/reference/_pages/Class.tsx index f2f36a9fa..eaffa8c4f 100644 --- a/reference/_pages/Class.tsx +++ b/reference/_pages/Class.tsx @@ -1,13 +1,10 @@ -import React from "npm:@preact/compat"; -import { ClassMethodDef, DocNodeClass, TsTypeDef } from "@deno/doc/types"; +import { DocNodeClass } from "@deno/doc/types"; import { HasFullName, LumeDocument, ReferenceContext } from "../types.ts"; import ReferencePage from "../_layouts/ReferencePage.tsx"; -import { AnchorableHeading } from "./primatives/AnchorableHeading.tsx"; -import { linkCodeAndParagraph } from "./primatives/LinkCode.tsx"; -import { - methodParameter, - methodSignature, -} from "../_util/methodSignatureRendering.ts"; +import { NameHeading } from "./partials/NameHeading.tsx"; +import { StabilitySummary } from "./partials/Badges.tsx"; +import { ImplementsSummary } from "./partials/ImplementsSummary.tsx"; +import { getSymbolDetails } from "./partials/SymbolDetails.tsx"; type Props = { data: DocNodeClass & HasFullName; context: ReferenceContext }; @@ -24,13 +21,7 @@ export default function* getPages( } export function Class({ data, context }: Props) { - const properties = data.classDef.properties.map((property) => { - return ( - <div> - <h3>{property.name}</h3> - </div> - ); - }); + const { details, contents } = getSymbolDetails(data); return ( <ReferencePage @@ -40,205 +31,21 @@ export function Class({ data, context }: Props) { currentItemName: data.fullName, }} > - <div id={"content"}> - <main class={"symbolGroup"}> - <article> + <main class={"symbolGroup"}> + <article> + <div> <div> - <div> - <ClassNameHeading data={data} /> - <ImplementsSummary _implements={data.classDef.implements} /> - <StabilitySummary data={data} /> - </div> + <NameHeading fullName={data.fullName} headingType="Class" /> + <ImplementsSummary typeDef={data.classDef.implements} /> + <StabilitySummary jsDoc={data.jsDoc} /> </div> - <div> - <Description data={data} /> - <Constructors data={data} /> - <Methods data={data} /> - <Properties data={data} /> - - <h2>Static Methods</h2> - </div> - </article> - </main> - </div> + </div> + <div> + {details} + </div> + </article> + </main> + {contents} </ReferencePage> ); } - -function ClassNameHeading({ data }: { data: DocNodeClass & HasFullName }) { - return ( - <div className={"text-2xl leading-none break-all"}> - <span class="text-Class">class</span> - <span class="font-bold"> -   - {data.fullName} - </span> - </div> - ); -} - -function StabilitySummary({ data }: { data: DocNodeClass }) { - const isUnstable = data.jsDoc?.tags?.some((tag) => - tag.kind === "experimental" as string - ); - - if (!isUnstable) { - return null; - } - - return ( - <div class="space-x-2 !mt-2"> - <div class="text-unstable border border-unstable/50 bg-unstable/5 inline-flex items-center gap-0.5 *:flex-none rounded-md leading-none font-bold py-2 px-3"> - Unstable - </div> - </div> - ); -} - -function ImplementsSummary({ _implements }: { _implements: TsTypeDef[] }) { - if (_implements.length === 0) { - return null; - } - - const spans = _implements.map((iface) => { - return <span>{iface.repr}</span>; - }); - - return ( - <div class="symbolSubtitle"> - <div> - <span class="type">implements</span> - {spans} - </div> - </div> - ); -} - -function Description({ data }: { data: DocNodeClass }) { - const description = linkCodeAndParagraph(data.jsDoc?.doc || "") || []; - if (description.length === 0) { - return null; - } - - return ( - <div className={"space-y-7"}> - <div - data-color-mode="auto" - data-light-theme="light" - data-dark-theme="dark" - class="markdown-body" - > - {description && description} - </div> - </div> - ); -} - -function Methods({ data }: { data: DocNodeClass }) { - if (data.classDef.methods.length === 0) { - return <></>; - } - - const items = data.classDef.methods.map((method) => { - return ( - <div> - <div> - <MethodSignature method={method} /> - </div> - <MarkdownBody> - {linkCodeAndParagraph(method.jsDoc?.doc || "")} - </MarkdownBody> - </div> - ); - }); - - return ( - <MemberSection title="Methods"> - {items} - </MemberSection> - ); -} - -function MethodSignature({ method }: { method: ClassMethodDef }) { - const asString = methodSignature(method); - - return ( - <> - {asString} - </> - ); -} - -function Properties({ data }: { data: DocNodeClass }) { - if (data.classDef.properties.length === 0) { - return <></>; - } - - const items = data.classDef.properties.map((prop) => { - return ( - <div> - <h3>{prop.name}</h3> - <MarkdownBody> - {linkCodeAndParagraph(prop.jsDoc?.doc || "")} - </MarkdownBody> - </div> - ); - }); - - return ( - <MemberSection title="Properties"> - {items} - </MemberSection> - ); -} - -function Constructors({ data }: { data: DocNodeClass }) { - if (data.classDef.constructors.length === 0) { - return <></>; - } - - return ( - <MemberSection title="Constructors"> - <pre> - {JSON.stringify(data.classDef.constructors, null, 2)} - </pre> - </MemberSection> - ); -} - -function MarkdownBody( - { children }: { children: React.ReactNode }, -) { - return ( - <div class="max-w-[75ch]"> - <div - data-color-mode="auto" - data-light-theme="light" - data-dark-theme="dark" - class="markdown-body" - > - {children} - </div> - </div> - ); -} - -function MemberSection( - { title, children }: { title: string; children: React.ReactNode }, -) { - return ( - <div> - <div className={"space-y-7"}> - <section className={"section"}> - <AnchorableHeading anchor={title}> - {title} - </AnchorableHeading> - </section> - </div> - - <div className={"space-y-7"}> - {children} - </div> - </div> - ); -} diff --git a/reference/_pages/Function.tsx b/reference/_pages/Function.tsx index 1ccd77524..3eca7fe02 100644 --- a/reference/_pages/Function.tsx +++ b/reference/_pages/Function.tsx @@ -1,6 +1,10 @@ import { DocNodeFunction } from "@deno/doc/types"; import { HasFullName, LumeDocument, ReferenceContext } from "../types.ts"; import ReferencePage from "../_layouts/ReferencePage.tsx"; +import { NameHeading } from "./partials/NameHeading.tsx"; +import { StabilitySummary } from "./partials/Badges.tsx"; +import { JsDocDescription } from "./partials/JsDocDescription.tsx"; +import { FunctionSignature } from "./primitives/FunctionSignature.tsx"; type Props = { data: DocNodeFunction & HasFullName; context: ReferenceContext }; @@ -17,18 +21,35 @@ export default function* getPages( } export function Function({ data, context }: Props) { + const nameOnly = data.fullName.split(".").pop(); + return ( <ReferencePage context={context} - navigation={{ category: context.packageName, currentItemName: data.name }} + navigation={{ + category: context.packageName, + currentItemName: data.fullName, + }} > - I am a function, my name is {data.name} - - {data.jsDoc?.doc && <p>{data.jsDoc?.doc}</p>} - - <pre> - {JSON.stringify(data, null, 2)} - </pre> + <main class={"symbolGroup"}> + <article> + <div> + <div> + <NameHeading fullName={data.fullName} headingType="Function" /> + <StabilitySummary jsDoc={data.jsDoc} /> + </div> + </div> + <div> + <FunctionSignature + functionDef={data.functionDef} + nameOverride={nameOnly} + /> + </div> + <div> + <JsDocDescription jsDoc={data.jsDoc} /> + </div> + </article> + </main> </ReferencePage> ); } diff --git a/reference/_pages/Interface.tsx b/reference/_pages/Interface.tsx index 281a26173..f51cc947a 100644 --- a/reference/_pages/Interface.tsx +++ b/reference/_pages/Interface.tsx @@ -1,6 +1,11 @@ -import { DocNodeInterface } from "@deno/doc/types"; +import { DocNodeInterface, TsTypeDef } from "@deno/doc/types"; import { HasFullName, LumeDocument, ReferenceContext } from "../types.ts"; import ReferencePage from "../_layouts/ReferencePage.tsx"; +import { NameHeading } from "./partials/NameHeading.tsx"; +import { StabilitySummary } from "./partials/Badges.tsx"; +import { TypeSummary } from "./primitives/TypeSummary.tsx"; +import { nbsp } from "../_util/common.ts"; +import { getSymbolDetails } from "./partials/SymbolDetails.tsx"; type Props = { data: DocNodeInterface & HasFullName; @@ -20,13 +25,7 @@ export default function* getPages( } export function Interface({ data, context }: Props) { - const isUnstable = data.jsDoc?.tags?.some((tag) => - tag.kind === "experimental" as string - ); - - const jsDocParagraphs = data.jsDoc?.doc?.split("\n\n").map((paragraph) => ( - <p>{paragraph}</p> - )); + const { details, contents } = getSymbolDetails(data); return ( <ReferencePage @@ -36,21 +35,44 @@ export function Interface({ data, context }: Props) { currentItemName: data.fullName, }} > - <h1>Interface: {data.fullName}</h1> - {isUnstable && <p>UNSTABLE</p>} - {jsDocParagraphs && jsDocParagraphs} - - <h2>Constructors</h2> + <main class={"symbolGroup"}> + <article> + <div> + <div> + <NameHeading fullName={data.fullName} headingType="Interface" /> + <ExtendsSummary typeDef={data.interfaceDef.extends} /> + <StabilitySummary jsDoc={data.jsDoc} /> + </div> + </div> + <div> + {details} + </div> + </article> + </main> + {contents} + </ReferencePage> + ); +} - <h2>Properties</h2> +function ExtendsSummary({ typeDef }: { typeDef: TsTypeDef[] }) { + if (typeDef.length === 0) { + return null; + } - <h2>Methods</h2> + const spans = typeDef.map((iface) => { + return <TypeSummary typeDef={iface} />; + }); - <h2>Static Methods</h2> + if (spans.length === 0) { + return null; + } - <pre> - {JSON.stringify(data, null, 2)} - </pre> - </ReferencePage> + return ( + <div class="symbolSubtitle"> + <div> + <span class="type">extends{nbsp}</span> + {spans} + </div> + </div> ); } diff --git a/reference/_pages/Module.tsx b/reference/_pages/Module.tsx index 8fdb270c0..771aae37c 100644 --- a/reference/_pages/Module.tsx +++ b/reference/_pages/Module.tsx @@ -14,8 +14,6 @@ export default function* getPages( `${context.root}/${context.packageName.toLocaleLowerCase()}/${item.name.toLocaleLowerCase()}`, content: <Module data={item} context={context} />, }; - - console.log("Module found", item); } export function Module({ data, context }: Props) { diff --git a/reference/_pages/Namespace.tsx b/reference/_pages/Namespace.tsx index bcbba0db1..d505a81e3 100644 --- a/reference/_pages/Namespace.tsx +++ b/reference/_pages/Namespace.tsx @@ -2,6 +2,16 @@ import { DocNodeNamespace } from "@deno/doc/types"; import { LumeDocument, ReferenceContext } from "../types.ts"; import generatePageFor from "../pageFactory.ts"; import ReferencePage from "../_layouts/ReferencePage.tsx"; +import { NameHeading } from "./partials/NameHeading.tsx"; +import { JsDocDescription } from "./partials/JsDocDescription.tsx"; +import { + TableOfContents, + TocListItem, + TocSection, +} from "./partials/TableOfContents.tsx"; +import { SymbolSummaryItem } from "./partials/SymbolSummaryItem.tsx"; +import { sections } from "../_util/common.ts"; +import { MemberSection } from "./partials/MemberSection.tsx"; type Props = { data: DocNodeNamespace; context: ReferenceContext }; @@ -12,7 +22,7 @@ export default function* getPages( yield { title: item.name, url: - `${context.root}/${context.packageName.toLocaleLowerCase()}/${item.name.toLocaleLowerCase()}`, + `${context.root}/${context.packageName.toLocaleLowerCase()}/~/${item.name}`, content: <Namespace data={item} context={context} />, }; @@ -25,46 +35,53 @@ export default function* getPages( } export function Namespace({ data, context }: Props) { - const interfaces = data.namespaceDef.elements.filter((elements) => - elements.kind === "interface" - ); - - const classes = data.namespaceDef.elements.filter((elements) => - elements.kind === "class" + const children = data.namespaceDef.elements.sort((a, b) => + a.name.localeCompare(b.name) ); return ( <ReferencePage context={context} - navigation={{ category: context.packageName, currentItemName: data.name }} + navigation={{ + category: context.packageName, + currentItemName: data.name, + }} > - <h1>Namespace: {context.packageName} - {data.name}</h1> - - <h2>Classes</h2> - <ul> - {classes.map((classDef) => ( - <li> - <a href={`~/${data.name}.${classDef.name}`}> - {data.name}.{classDef.name} - </a> - </li> - ))} - </ul> - - <h2>Interfaces</h2> - <ul> - {interfaces.map((interfaceDef) => ( - <li> - <a href={`~/${data.name}.${interfaceDef.name}`}> - {data.name}.{interfaceDef.name} - </a> - </li> - ))} - </ul> - - <pre> - {JSON.stringify(data, null, 2)} - </pre> + <main class={"symbolGroup"}> + <article> + <div> + <div> + <NameHeading fullName={data.name} headingType="Namespace" /> + </div> + </div> + <div> + <JsDocDescription jsDoc={data.jsDoc} /> + </div> + </article> + {sections.map(([title, kind]) => { + return ( + <MemberSection + title={title} + children={children.filter((x) => x.kind === kind).map((x) => { + return <SymbolSummaryItem item={x} />; + })} + /> + ); + })} + </main> + <TableOfContents> + <ul> + {sections.map(([title, kind]) => { + return ( + <TocSection title={title}> + {children.filter((x) => x.kind === kind).map((x) => { + return <TocListItem item={x} type={kind} />; + })} + </TocSection> + ); + })} + </ul> + </TableOfContents> </ReferencePage> ); } diff --git a/reference/_pages/Package.tsx b/reference/_pages/Package.tsx index b492bc1e6..1737151c9 100644 --- a/reference/_pages/Package.tsx +++ b/reference/_pages/Package.tsx @@ -1,23 +1,25 @@ import ReferencePage from "../_layouts/ReferencePage.tsx"; import { ReferenceContext } from "../types.ts"; -import { AnchorableHeading } from "./primatives/AnchorableHeading.tsx"; -import { insertLinkCodes } from "./primatives/LinkCode.tsx"; +import { AnchorableHeading } from "./partials/AnchorableHeading.tsx"; +import { linkCodeAndParagraph } from "./primitives/LinkCode.tsx"; type Props = { - data: Record<string, string | undefined>; + data: Map<string, string>; context: ReferenceContext; }; export function Package({ data, context }: Props) { - const categoryListItems = Object.entries(data).map(([key, value]) => { - return ( - <CategoryListSection - title={key} - href={`${context.root}/${context.packageName.toLocaleLowerCase()}/${key.toLocaleLowerCase()}`} - summary={value || ""} - /> - ); - }); + const categoryElements = data.entries().map( + ([key, value]) => { + return ( + <CategorySummary + identifier={key} + context={context} + summary={value || ""} + /> + ); + }, + ).toArray(); return ( <ReferencePage @@ -34,32 +36,29 @@ export function Package({ data, context }: Props) { </div> </section> <div className={"space-y-7"}> - {categoryListItems} + {categoryElements} </div> </main> </ReferencePage> ); } -function CategoryListSection( - { title, href, summary }: { title: string; href: string; summary: string }, +export function CategorySummary( + { identifier, context, summary }: { + identifier: string; + context: ReferenceContext; + summary: string; + }, ) { - const anchorId = title.replace(" ", "-").toLocaleLowerCase(); - - const parts = summary.split("\n\n"); - const examplePart = parts[parts.length - 1]; - const partsExceptLast = parts.slice(0, parts.length - 1); - const summaryBody = partsExceptLast.join("\n\n"); - - const exampleBody = insertLinkCodes(examplePart); - const summaryBodyParas = summaryBody.split("\n\n").map((paragraph) => ( - <p>{paragraph}</p> - )); + const href = + `${context.root}/${context.packageName.toLocaleLowerCase()}/${identifier.toLocaleLowerCase()}`; + const anchorId = identifier.replace(" ", "-").toLocaleLowerCase(); + const processedDescription = linkCodeAndParagraph(summary); return ( <section id={anchorId} className={"section"}> <AnchorableHeading anchor={anchorId}> - <a href={href}>{title}</a> + <a href={href}>{identifier}</a> </AnchorableHeading> <div data-color-mode="auto" @@ -67,10 +66,7 @@ function CategoryListSection( data-dark-theme="dark" class="markdown-body" > - {summaryBodyParas} - <p> - {exampleBody} - </p> + {processedDescription} </div> </section> ); diff --git a/reference/_pages/Variable.tsx b/reference/_pages/Variable.tsx index ec50e25ea..fd3b6f620 100644 --- a/reference/_pages/Variable.tsx +++ b/reference/_pages/Variable.tsx @@ -1,6 +1,13 @@ -import { DocNodeVariable } from "@deno/doc/types"; +import { DocNodeVariable, VariableDef } from "@deno/doc/types"; import { HasFullName, LumeDocument, ReferenceContext } from "../types.ts"; import ReferencePage from "../_layouts/ReferencePage.tsx"; +import { NameHeading } from "./partials/NameHeading.tsx"; +import { StabilitySummary } from "./partials/Badges.tsx"; +import { JsDocDescription } from "./partials/JsDocDescription.tsx"; +import { DetailedSection } from "./partials/DetailedSection.tsx"; +import { MemberSection } from "./partials/MemberSection.tsx"; +import { TypeSummary } from "./primitives/TypeSummary.tsx"; +import { getSymbolDetails } from "./partials/SymbolDetails.tsx"; type Props = { data: DocNodeVariable & HasFullName; context: ReferenceContext }; @@ -17,18 +24,44 @@ export default function* getPages( } export function Variable({ data, context }: Props) { + const { details, contents } = getSymbolDetails(data); + return ( <ReferencePage context={context} - navigation={{ category: context.packageName, currentItemName: data.name }} + navigation={{ + category: context.packageName, + currentItemName: data.fullName, + }} > - I am a variable, my name is {data.name} - - {data.jsDoc?.doc && <p>{data.jsDoc?.doc}</p>} - - <pre> - {JSON.stringify(data, null, 2)} - </pre> + <main class={"symbolGroup"}> + <article> + <div> + <div> + <NameHeading fullName={data.fullName} headingType="Variable" /> + <StabilitySummary jsDoc={data.jsDoc} /> + </div> + </div> + <div> + <JsDocDescription jsDoc={data.jsDoc} /> + <VariableType type={data.variableDef} /> + </div> + <div> + {details} + </div> + </article> + </main> + {contents} </ReferencePage> ); } + +function VariableType({ type }: { type: VariableDef }) { + return ( + <MemberSection title={"Type"}> + <DetailedSection> + <TypeSummary typeDef={type.tsType!} /> + </DetailedSection> + </MemberSection> + ); +} diff --git a/reference/_pages/primatives/AnchorableHeading.tsx b/reference/_pages/partials/AnchorableHeading.tsx similarity index 100% rename from reference/_pages/primatives/AnchorableHeading.tsx rename to reference/_pages/partials/AnchorableHeading.tsx diff --git a/reference/_pages/partials/Badges.tsx b/reference/_pages/partials/Badges.tsx new file mode 100644 index 000000000..99e4fa632 --- /dev/null +++ b/reference/_pages/partials/Badges.tsx @@ -0,0 +1,69 @@ +import { JsDoc } from "@deno/doc/types"; +import { ValidPropertyType } from "../../types.ts"; + +export function StabilitySummary({ jsDoc }: { jsDoc: JsDoc | undefined }) { + const isUnstable = jsDoc?.tags?.some((tag) => + tag.kind === "experimental" as string + ); + + if (!isUnstable) { + return null; + } + + return ( + <div class="space-x-2 !mt-2"> + <div class="text-unstable border border-unstable/50 bg-unstable/5 inline-flex items-center gap-0.5 *:flex-none rounded-md leading-none font-bold py-2 px-3"> + Unstable + </div> + </div> + ); +} + +export function PropertyBadges( + { property }: { + property: ValidPropertyType; + }, +) { + return ( + <> + <OptionalSummary property={property} /> + <ReadOnlySummary property={property} /> + </> + ); +} + +export function OptionalSummary( + { property }: { + property: ValidPropertyType; + }, +) { + if (!property.optional) { + return null; + } + + return ( + <div class="space-x-1 mb-1"> + <div class="text-optional border border-optional/50 bg-optional/5 inline-flex items-center gap-0.5 *:flex-none rounded-md leading-none text-sm py-1 px-2"> + optional + </div> + </div> + ); +} + +export function ReadOnlySummary( + { property }: { + property: ValidPropertyType; + }, +) { + if (!property.readonly) { + return null; + } + + return ( + <div class="space-x-1 mb-1"> + <div class="text-optional border border-optional/50 bg-optional/5 inline-flex items-center gap-0.5 *:flex-none rounded-md leading-none text-sm py-1 px-2text-readonly border border-readonly/50 bg-readonly/5 inline-flex items-center gap-0.5 *:flex-none rounded-md leading-none text-sm py-1 px-2"> + readonly + </div> + </div> + ); +} diff --git a/reference/_pages/primatives/CodeIcon.tsx b/reference/_pages/partials/CodeIcon.tsx similarity index 100% rename from reference/_pages/primatives/CodeIcon.tsx rename to reference/_pages/partials/CodeIcon.tsx diff --git a/reference/_pages/partials/DetailedSection.tsx b/reference/_pages/partials/DetailedSection.tsx new file mode 100644 index 000000000..bc4a4773c --- /dev/null +++ b/reference/_pages/partials/DetailedSection.tsx @@ -0,0 +1,16 @@ +import React from "npm:@preact/compat"; + +export function DetailedSection({ children }: { children: React.ReactNode }) { + return ( + <div class="max-w-[75ch]"> + <div + data-color-mode="auto" + data-light-theme="light" + data-dark-theme="dark" + class="markdown-body" + > + {children} + </div> + </div> + ); +} diff --git a/reference/_pages/partials/ImplementsSummary.tsx b/reference/_pages/partials/ImplementsSummary.tsx new file mode 100644 index 000000000..f75c18be5 --- /dev/null +++ b/reference/_pages/partials/ImplementsSummary.tsx @@ -0,0 +1,26 @@ +import { TsTypeDef } from "@deno/doc/types"; +import { TypeSummary } from "../primitives/TypeSummary.tsx"; +import { nbsp } from "../../_util/common.ts"; + +export function ImplementsSummary({ typeDef }: { typeDef: TsTypeDef[] }) { + if (typeDef.length === 0) { + return null; + } + + const spans = typeDef.map((iface) => { + return <TypeSummary typeDef={iface} />; + }); + + if (spans.length === 0) { + return null; + } + + return ( + <div class="symbolSubtitle"> + <div> + <span class="type">implements{nbsp}</span> + {spans} + </div> + </div> + ); +} diff --git a/reference/_pages/partials/JsDocDescription.tsx b/reference/_pages/partials/JsDocDescription.tsx new file mode 100644 index 000000000..0ff42af16 --- /dev/null +++ b/reference/_pages/partials/JsDocDescription.tsx @@ -0,0 +1,15 @@ +import { JsDoc } from "@deno/doc/types"; +import { DetailedSection } from "./DetailedSection.tsx"; +import { MarkdownContent } from "../primitives/MarkdownContent.tsx"; + +export function JsDocDescription({ jsDoc }: { jsDoc: JsDoc | undefined }) { + if (!jsDoc?.doc) { + return null; + } + + return ( + <DetailedSection> + <MarkdownContent text={jsDoc?.doc || ""} /> + </DetailedSection> + ); +} diff --git a/reference/_pages/partials/MemberSection.tsx b/reference/_pages/partials/MemberSection.tsx new file mode 100644 index 000000000..4322e3390 --- /dev/null +++ b/reference/_pages/partials/MemberSection.tsx @@ -0,0 +1,22 @@ +import React from "npm:@preact/compat"; +import { AnchorableHeading } from "./AnchorableHeading.tsx"; + +export function MemberSection( + { title, children }: { title: string; children: React.ReactNode }, +) { + return ( + <div> + <div className={"space-y-7"}> + <section className={"section"}> + <AnchorableHeading anchor={title}> + {title} + </AnchorableHeading> + </section> + </div> + + <div className={"space-y-7"}> + {children} + </div> + </div> + ); +} diff --git a/reference/_pages/partials/NameHeading.tsx b/reference/_pages/partials/NameHeading.tsx new file mode 100644 index 000000000..a9de3ea3d --- /dev/null +++ b/reference/_pages/partials/NameHeading.tsx @@ -0,0 +1,29 @@ +import { nbsp } from "../../_util/common.ts"; + +export function NameHeading( + { fullName, headingType }: { + fullName: string; + headingType: + | "Class" + | "Function" + | "Interface" + | "TypeAlias" + | "Variable" + | "Module" + | "Namespace" + | "Import"; + }, +) { + const className = "text-" + headingType; + const displayName = headingType.toLowerCase(); + + return ( + <div className={"text-2xl leading-none break-all"}> + <span class={className}>{displayName}</span> + <span class="font-bold"> + {nbsp} + {fullName} + </span> + </div> + ); +} diff --git a/reference/_pages/partials/PropertyItem.tsx b/reference/_pages/partials/PropertyItem.tsx new file mode 100644 index 000000000..62df84f87 --- /dev/null +++ b/reference/_pages/partials/PropertyItem.tsx @@ -0,0 +1,29 @@ +import { PropertyName } from "../primitives/PropertyName.tsx"; +import { DetailedSection } from "./DetailedSection.tsx"; +import { MarkdownContent } from "../primitives/MarkdownContent.tsx"; +import { PropertyBadges } from "./Badges.tsx"; +import { ValidPropertyWithOptionalJsDoc } from "../../types.ts"; + +export function PropertyItem( + { property }: { + property: ValidPropertyWithOptionalJsDoc; + }, +) { + const jsDocSection = property.jsDoc?.doc + ? ( + <DetailedSection> + <MarkdownContent text={property.jsDoc?.doc} /> + </DetailedSection> + ) + : null; + + return ( + <div> + <h3> + <PropertyBadges property={property} /> + <PropertyName property={property} /> + </h3> + {jsDocSection} + </div> + ); +} diff --git a/reference/_pages/partials/SymbolDetails.tsx b/reference/_pages/partials/SymbolDetails.tsx new file mode 100644 index 000000000..31474d0de --- /dev/null +++ b/reference/_pages/partials/SymbolDetails.tsx @@ -0,0 +1,180 @@ +import { ClassConstructorDef, DocNode } from "@deno/doc/types"; +import { + ClosureContent, + hasJsDoc, + ValidMethodType, + ValidPropertyWithOptionalJsDoc, +} from "../../types.ts"; +import { MethodSignature } from "../primitives/MethodSignature.tsx"; +import { MemberSection } from "./MemberSection.tsx"; +import { PropertyItem } from "./PropertyItem.tsx"; +import { DetailedSection } from "./DetailedSection.tsx"; +import { MarkdownContent } from "../primitives/MarkdownContent.tsx"; +import { JsDocDescription } from "./JsDocDescription.tsx"; +import { + TableOfContents, + TocListItem, + TocSection, +} from "./TableOfContents.tsx"; + +export function getSymbolDetails(data: DocNode) { + const members = getMembers(data); + + const details = [ + <JsDocDescription jsDoc={data.jsDoc} />, + <Constructors data={members.constructors} />, + <Properties properties={members.properties} />, + <Methods methods={members.methods} />, + ]; + + if (members.kind === "class") { + details.pop(); + details.push( + <Methods methods={members.instanceMethods || []} />, + <Methods + methods={members.staticMethods || []} + label={"Static Methods"} + />, + ); + } + + const contents = [ + <ul> + <TocSection title="Constructors"> + {members.constructors.map((prop) => { + return <TocListItem item={prop} type="constructor" />; + })} + </TocSection> + </ul>, + <ul> + <TocSection title="Properties"> + {members.properties.map((prop) => { + return <TocListItem item={prop} type="property" />; + })} + </TocSection> + </ul>, + <ul> + <TocSection title="Method"> + {members.methods.map((prop) => { + return <TocListItem item={prop} type="method" />; + })} + </TocSection> + </ul>, + ]; + + return { details, contents: <TableOfContents>{contents}</TableOfContents> }; +} + +function getMembers(x: DocNode): ClosureContent { + if (x.kind === "class" && x.classDef) { + return { + kind: "class", + constructors: x.classDef.constructors, + methods: x.classDef.methods, + properties: x.classDef.properties as ValidPropertyWithOptionalJsDoc[], + callSignatures: [], + indexSignatures: x.classDef.indexSignatures, + instanceMethods: x.classDef.methods.filter((method) => !method.isStatic), + staticMethods: x.classDef.methods.filter((method) => method.isStatic), + }; + } + + if (x.kind === "interface" && x.interfaceDef) { + return { + kind: "interface", + constructors: [], + methods: x.interfaceDef.methods, + properties: x.interfaceDef.properties as ValidPropertyWithOptionalJsDoc[], + callSignatures: x.interfaceDef.callSignatures, + indexSignatures: x.interfaceDef.indexSignatures, + }; + } + + if (x.kind === "variable") { + if (x.variableDef?.tsType?.kind === "typeLiteral") { + return { + kind: "typeLiteral", + constructors: [], + methods: x.variableDef.tsType.typeLiteral.methods, + properties: x.variableDef.tsType.typeLiteral.properties, + callSignatures: x.variableDef.tsType.typeLiteral.callSignatures, + indexSignatures: x.variableDef.tsType.typeLiteral.indexSignatures, + }; + } else { + return { + kind: "variable", + constructors: [], + methods: [], + properties: [], + callSignatures: [], + indexSignatures: [], + }; + } + } + + throw new Error(`Unexpected kind: ${x.kind}`); +} + +function Methods( + { methods, label = "Methods" }: { + methods: ValidMethodType[]; + label?: string; + }, +) { + if (methods.length === 0) { + return <></>; + } + + return ( + <MemberSection title={label}> + {methods.map((method) => { + return <MethodSummary method={method} />; + })} + </MemberSection> + ); +} + +function MethodSummary({ method }: { method: ValidMethodType }) { + const detailedSection = hasJsDoc(method) + ? ( + <DetailedSection> + <MarkdownContent text={method.jsDoc.doc} /> + </DetailedSection> + ) + : null; + + return ( + <div> + <div> + <MethodSignature method={method} /> + </div> + {detailedSection} + </div> + ); +} + +function Properties( + { properties }: { properties: ValidPropertyWithOptionalJsDoc[] }, +) { + if (properties.length === 0) { + return <></>; + } + + return ( + <MemberSection title="Properties"> + {properties.map((prop) => <PropertyItem property={prop} />)} + </MemberSection> + ); +} + +function Constructors({ data }: { data: ClassConstructorDef[] }) { + if (data.length === 0) { + return <></>; + } + + return ( + <MemberSection title="Constructors"> + <div>The details of the constrcutors should be populated here.</div> + </MemberSection> + ); +} diff --git a/reference/_pages/partials/SymbolSummaryItem.tsx b/reference/_pages/partials/SymbolSummaryItem.tsx new file mode 100644 index 000000000..b1a82c0e3 --- /dev/null +++ b/reference/_pages/partials/SymbolSummaryItem.tsx @@ -0,0 +1,61 @@ +import { DocNodeBase, DocNodeClass } from "@deno/doc/types"; +import { CodeIcon } from "./CodeIcon.tsx"; +import { MarkdownContent } from "../primitives/MarkdownContent.tsx"; +import { HasNamespace, MightHaveNamespace } from "../../types.ts"; + +export function SymbolSummaryItem( + { item }: { item: DocNodeBase & MightHaveNamespace }, +) { + const displayName = item.fullName || item.name; + const firstLine = item.jsDoc?.doc?.split("\n\n")[0]; + + return ( + <div className={"namespaceItem"}> + <CodeIcon glyph={item.kind} /> + <div className={"namespaceItemContent"}> + <a href={`~/${displayName}`}> + {displayName} + </a> + <MarkdownContent text={firstLine} /> + <MethodLinks item={item} /> + </div> + </div> + ); +} + +function MethodLinks({ item }: { item: DocNodeBase }) { + if (item.kind !== "class") { + return <></>; + } + + const asClass = item as DocNodeClass & HasNamespace; + const methods = asClass.classDef.methods.sort((a, b) => + a.name.localeCompare(b.name) + ); + + const filteredMethodsList = [ + "[Symbol.dispose]", + "[Symbol.asyncDispose]", + "[Symbol.asyncIterator]", + ]; + + const filteredMethods = methods.filter((method) => + !filteredMethodsList.includes(method.name) + ); + + const methodLinks = filteredMethods.map((method) => { + return ( + <li> + <a href={`~/${asClass.fullName}#${method.name}`}> + {method.name} + </a> + </li> + ); + }); + + return ( + <ul className={"namespaceItemContentSubItems"}> + {methodLinks} + </ul> + ); +} diff --git a/reference/_pages/partials/TableOfContents.tsx b/reference/_pages/partials/TableOfContents.tsx new file mode 100644 index 000000000..d0334f106 --- /dev/null +++ b/reference/_pages/partials/TableOfContents.tsx @@ -0,0 +1,51 @@ +import React from "npm:@preact/compat"; + +export function TableOfContents({ children }: { children: React.ReactNode }) { + return ( + <div class="toc"> + <div> + <nav class="documentNavigation"> + <h3>Document Navigation</h3> + {children} + </nav> + </div> + </div> + ); +} + +export function TocListItem( + { item, type }: { item: { name: string }; type: string }, +) { + return ( + <li> + <a href={`#${type}_${item.name}`} title={item.name}> + {item.name} + </a> + </li> + ); +} + +export function TocSection( + { title, children }: { title: string; children: React.ReactNode }, +) { + if (children === undefined) { + return null; + } + + if (Array.isArray(children) && children.length === 0) { + return null; + } + + return ( + <ul> + <li> + <a href={"#" + title.toLocaleLowerCase()} title={title}>{title}</a> + </li> + <li> + <ul> + {children} + </ul> + </li> + </ul> + ); +} diff --git a/reference/_pages/primatives/LinkCode.tsx b/reference/_pages/primatives/LinkCode.tsx deleted file mode 100644 index 91377e91e..000000000 --- a/reference/_pages/primatives/LinkCode.tsx +++ /dev/null @@ -1,59 +0,0 @@ -export function LinkCode({ symbol }: { symbol: string }) { - const target = "~/" + symbol; - - return ( - <a href={target}> - <code>{symbol}</code> - </a> - ); -} - -export function insertLinkCodes(text: string) { - // replace each text occurance of {@linkcode foo} with <LinkCode symbol="foo" /> - const tokens = text.split(/(\{@linkcode [^\}]+\})/); - const firstLineOfJsDocWithLinks = tokens.map((token, index) => { - if (token.startsWith("{@linkcode ")) { - const symbol = token.slice(11, -1); - return <LinkCode symbol={symbol} key={index} />; - } - return token; - }); - - const merge = firstLineOfJsDocWithLinks.reduce( - // deno-lint-ignore no-explicit-any - (acc: any, curr: any) => { - return acc === null ? [curr] : [...acc, " ", curr]; - }, - null, - ); - - return merge; -} - -export function linkCodeAndParagraph(text: string) { - // deno-lint-ignore no-explicit-any - // const withLinks: any = insertLinkCodes(text); - - // for (const index in withLinks) { - // const current = withLinks[index]; - - // if ( - // typeof current === "string" && current.includes("\n\n") - // ) { - // const withBreaks = current.split("\n\n").map((line: string) => { - // return ( - // <> - // {line} - // <br /> - // <br /> - // </> - // ); - // }); - - // withLinks.splice(index, 1, withBreaks); - // } - // } - - // TODO: fixme - return insertLinkCodes(text); -} diff --git a/reference/_pages/primitives/FunctionDefinitionParts.tsx b/reference/_pages/primitives/FunctionDefinitionParts.tsx new file mode 100644 index 000000000..2c0272f74 --- /dev/null +++ b/reference/_pages/primitives/FunctionDefinitionParts.tsx @@ -0,0 +1,37 @@ +import { CodePart } from "../../_util/symbolStringBuilding.ts"; + +export function FunctionDefinitionParts( + { asParts, forceLineBreaks = false }: { + asParts: CodePart[]; + forceLineBreaks?: boolean; + }, +) { + const partElements = []; + for (const part of asParts) { + if ( + part.kind === "method-brace" && part.value.trim() === ")" && + forceLineBreaks + ) { + partElements.push(<br />); + } + + partElements.push(<span className={part.kind}>{part.value}</span>); + + if ( + part.kind === "method-brace" && part.value.trim() === "(" && + forceLineBreaks + ) { + partElements.push(<br />); + } + + if (part.kind === "param-separator") { + partElements.push(<br />); + } + } + + return ( + <> + {partElements} + </> + ); +} diff --git a/reference/_pages/primitives/FunctionSignature.tsx b/reference/_pages/primitives/FunctionSignature.tsx new file mode 100644 index 000000000..c4be4f7c6 --- /dev/null +++ b/reference/_pages/primitives/FunctionSignature.tsx @@ -0,0 +1,22 @@ +import { FunctionDef } from "@deno/doc/types"; +import { functionSignature } from "../../_util/symbolStringBuilding.ts"; +import { FunctionDefinitionParts } from "./FunctionDefinitionParts.tsx"; + +export function FunctionSignature( + { functionDef, nameOverride = undefined }: { + functionDef: FunctionDef; + nameOverride?: string | undefined; + }, +) { + const asParts = functionSignature(functionDef, nameOverride); + const partElements = FunctionDefinitionParts({ + asParts, + forceLineBreaks: functionDef.params.length > 1, + }); + + return ( + <> + {partElements} + </> + ); +} diff --git a/reference/_pages/primitives/InterfaceMethodSignature.tsx b/reference/_pages/primitives/InterfaceMethodSignature.tsx new file mode 100644 index 000000000..37d6e7d7f --- /dev/null +++ b/reference/_pages/primitives/InterfaceMethodSignature.tsx @@ -0,0 +1,19 @@ +import { InterfaceMethodDef } from "@deno/doc/types"; +import { interfaceSignature } from "../../_util/symbolStringBuilding.ts"; +import { FunctionDefinitionParts } from "./FunctionDefinitionParts.tsx"; + +export function InterfaceMethodSignature( + { method }: { method: InterfaceMethodDef }, +) { + const asParts = interfaceSignature(method); + const partElements = FunctionDefinitionParts({ + asParts, + forceLineBreaks: method.params.length > 1, + }); + + return ( + <> + {partElements} + </> + ); +} diff --git a/reference/_pages/primitives/LinkCode.tsx b/reference/_pages/primitives/LinkCode.tsx new file mode 100644 index 000000000..589b3c170 --- /dev/null +++ b/reference/_pages/primitives/LinkCode.tsx @@ -0,0 +1,62 @@ +export function LinkCode({ symbol }: { symbol: string }) { + const target = "~/" + symbol; + + return ( + <a href={target}> + <code>{symbol}</code> + </a> + ); +} + +export function insertLinkCodes(text: string) { + // replace each text occurance of {@linkcode foo} with <LinkCode symbol="foo" /> + if (!text) { + return ""; + } + + const parts = text.split(/(\{@linkcode\s+[^}]+\})/g); + + const partsAfterSub = parts.map((part) => { + const match = part.match(/\{@linkcode\s+([^}]+)\}/); + if (!match) { + return part; + } + + const symbolParts = match[1].trim(); + const [url, title] = symbolParts.includes("|") + ? symbolParts.split("|").map((s) => s.trim()) + : [symbolParts, symbolParts]; + + // Edge case where markdown inserts full URL + if (url.startsWith("<a href")) { + return url.replace(/<a href="([^"]+)">([^<]+)<\/a>/, (_, url, __) => { + return `<a href="${url}"><code>${title}</code></a>`; + }); + } + + const href = url.startsWith("http") ? url : "~/" + url; + return `<a href="${href}"><code>${title}</code></a>`; + }); + + return partsAfterSub.join("").trim(); +} + +export function linkCodeAndParagraph(text: string) { + if (!text?.trim()) { + return null; + } + + const withLinkCode = insertLinkCodes(text); + + const paragraphs = withLinkCode + .split(/\n\n+/) + .map((p) => p.trim()) + .filter((p) => p.length > 0); + + const elements = paragraphs.map((paragraph, index) => ( + <p key={index} dangerouslySetInnerHTML={{ __html: paragraph }}> + </p> + )); + + return elements; +} diff --git a/reference/_pages/primitives/LinkCode_test.ts b/reference/_pages/primitives/LinkCode_test.ts new file mode 100644 index 000000000..ea861ccf6 --- /dev/null +++ b/reference/_pages/primitives/LinkCode_test.ts @@ -0,0 +1,35 @@ +import { assertEquals } from "@std/assert/equals"; +import { insertLinkCodes } from "./LinkCode.tsx"; +import { markdownRenderer } from "../../../_config.markdown.ts"; + +Deno.test("insertLinkcode, given LinkCode with URL and title, renders", () => { + const input = + "{@linkcode https://jsr.io/@std/io/doc/types/~/Writer | Writer}"; + const expected = + '<a href="https://jsr.io/@std/io/doc/types/~/Writer"><code>Writer</code></a>'; + + const result = insertLinkCodes(input); + + assertEquals(result, expected); +}); + +Deno.test("insertLinkcode, given LinkCode with URL only, renders", () => { + const input = "{@linkcode MyFoo}"; + const expected = '<a href="~/MyFoo"><code>MyFoo</code></a>'; + + const result = insertLinkCodes(input); + + assertEquals(result, expected); +}); + +Deno.test("insertLinkcode, given linkcode post markdown processing, renders without breaking links", () => { + const input = + "{@linkcode https://jsr.io/@std/io/doc/types/~/Writer | Writer}"; + const expected = + '<p><a href="https://jsr.io/@std/io/doc/types/~/Writer"><code>Writer</code></a></p>'; + + const asHtml = markdownRenderer.render(input); + const result = insertLinkCodes(asHtml); + + assertEquals(result, expected); +}); diff --git a/reference/_pages/primitives/MarkdownContent.tsx b/reference/_pages/primitives/MarkdownContent.tsx new file mode 100644 index 000000000..5a1cc50d6 --- /dev/null +++ b/reference/_pages/primitives/MarkdownContent.tsx @@ -0,0 +1,25 @@ +import { markdownRenderer } from "../../../_config.markdown.ts"; +import { insertLinkCodes } from "./LinkCode.tsx"; + +export function MarkdownContent( + { text }: { text: string | undefined | null }, +) { + if (!text) { + return null; + } + + const renderedDescription = markdownRenderer.render(text); + const withLinkCode = insertLinkCodes(renderedDescription); + + const paragraphs = withLinkCode + .split(/\n\n+/) + .map((p) => p.trim()) + .filter((p) => p.length > 0); + + const elements = paragraphs.map((paragraph, index) => ( + <p key={index} dangerouslySetInnerHTML={{ __html: paragraph }}> + </p> + )); + + return <>{elements}</>; +} diff --git a/reference/_pages/primitives/MethodSignature.tsx b/reference/_pages/primitives/MethodSignature.tsx new file mode 100644 index 000000000..89dd6c604 --- /dev/null +++ b/reference/_pages/primitives/MethodSignature.tsx @@ -0,0 +1,35 @@ +import { + CodePart, + interfaceSignature, + methodSignature, +} from "../../_util/symbolStringBuilding.ts"; +import { FunctionDefinitionParts } from "./FunctionDefinitionParts.tsx"; +import { + isClassMethodDef, + isInterfaceMethodDef, + ValidMethodType, +} from "../../types.ts"; + +export function MethodSignature({ method }: { method: ValidMethodType }) { + let asParts: CodePart[] = []; + let forceBreaks = false; + + if (isClassMethodDef(method)) { + asParts = methodSignature(method); + forceBreaks = method.functionDef.params.length > 1; + } else if (isInterfaceMethodDef(method)) { + asParts = interfaceSignature(method); + forceBreaks = method.params.length > 1; + } + + const partElements = FunctionDefinitionParts({ + asParts, + forceLineBreaks: forceBreaks, + }); + + return ( + <> + {partElements} + </> + ); +} diff --git a/reference/_pages/primitives/PropertyName.tsx b/reference/_pages/primitives/PropertyName.tsx new file mode 100644 index 000000000..3ea19b11a --- /dev/null +++ b/reference/_pages/primitives/PropertyName.tsx @@ -0,0 +1,35 @@ +import { + ClassPropertyDef, + InterfacePropertyDef, + LiteralPropertyDef, +} from "@deno/doc/types"; +import { nbsp } from "../../_util/common.ts"; +import { TypeSummary } from "./TypeSummary.tsx"; + +export function PropertyName( + { property }: { + property: ClassPropertyDef | InterfacePropertyDef | LiteralPropertyDef; + }, +) { + const propertyNameClass = "identifier font-bold font-lg link"; + const propertyTypeClass = "type font-medium text-stone-500"; + + const typeInfoElements = property.tsType + ? ( + <> + <span className={propertyTypeClass}>:{nbsp}</span> + <TypeSummary + typeDef={property.tsType} + extraClasses={["font-medium", "text-stone-500"]} + /> + </> + ) + : <></>; + + return ( + <> + <span className={propertyNameClass}>{property.name}</span> + {typeInfoElements} + </> + ); +} diff --git a/reference/_pages/primitives/RawHtml.tsx b/reference/_pages/primitives/RawHtml.tsx new file mode 100644 index 000000000..1958c0f8f --- /dev/null +++ b/reference/_pages/primitives/RawHtml.tsx @@ -0,0 +1,3 @@ +export function RawHtml({ rendered }: { rendered: string }) { + return <div dangerouslySetInnerHTML={{ __html: rendered }}></div>; +} diff --git a/reference/_pages/primitives/TypeSummary.tsx b/reference/_pages/primitives/TypeSummary.tsx new file mode 100644 index 000000000..97b7aff6b --- /dev/null +++ b/reference/_pages/primitives/TypeSummary.tsx @@ -0,0 +1,24 @@ +import { TsTypeDef } from "@deno/doc/types"; +import { typeInformation } from "../../_util/symbolStringBuilding.ts"; + +type Props = { + typeDef: TsTypeDef | undefined; + extraClasses?: string[]; +}; + +export function TypeSummary( + { typeDef, extraClasses = [] }: Props, +) { + if (!typeDef) { + return null; + } + + const parts = typeInformation(typeDef); + + const elements = parts.map((part) => { + const classes = [part.kind, ...extraClasses].join(" "); + return <span className={classes}>{part.value}</span>; + }); + + return <>{elements}</>; +} diff --git a/reference/_util/categoryBuilding.ts b/reference/_util/categoryBuilding.ts new file mode 100644 index 000000000..b3df0b8ea --- /dev/null +++ b/reference/_util/categoryBuilding.ts @@ -0,0 +1,32 @@ +import { DocNode, JsDocTagDoc } from "@deno/doc/types"; + +export function parseCategories( + symbols: DocNode[], + categoryDescriptionMap: Record<string, string | undefined>, +): Map<string, string> { + const allCategoriesFromJsDocTags = symbols.map((item) => + item.jsDoc?.tags?.filter((tag) => tag.kind === "category") + ).flat() as JsDocTagDoc[]; + + const distinctCategories = [ + ...new Set(allCategoriesFromJsDocTags.map((tag) => tag?.doc?.trim())), + ]; + + return distinctCategories + .filter((x) => x !== undefined) + .sort() + .reduce((acc, category) => { + const description = categoryDescriptionMap[category] || ""; + acc.set(category, description); + return acc; + }, new Map<string, string>()); +} + +// deno-lint-ignore no-explicit-any +export function categoryDataFrom( + sections: { path: string; categoryDocs: any }[], + packageName: string, +): Record<string, string | undefined> { + return sections.filter((x) => x.path === packageName.toLocaleLowerCase())[0]! + .categoryDocs as Record<string, string | undefined>; +} diff --git a/reference/_util/categoryBuilding_test.ts b/reference/_util/categoryBuilding_test.ts new file mode 100644 index 000000000..76ae8737a --- /dev/null +++ b/reference/_util/categoryBuilding_test.ts @@ -0,0 +1,34 @@ +import { parseCategories } from "./categoryBuilding.ts"; +import { assertEquals } from "@std/assert/equals"; +import { classFor } from "./testData.ts"; + +Deno.test("parseCategories, no category description metadata available, still surfaces categories from tags", () => { + const categoryMap = parseCategories([ + classFor("cat-1"), + classFor("cat-2"), + ], {}); + + assertEquals(categoryMap.size, 2); + assertEquals(categoryMap.get("cat-1"), ""); + assertEquals(categoryMap.get("cat-2"), ""); +}); + +Deno.test("parseCategories, category description metadata available, doesn't return items with no symbols", () => { + const categoryMap = parseCategories([], { + "cat-1": "Category 1", + "cat-2": "Category 2", + }); + + assertEquals(categoryMap.size, 0); +}); + +Deno.test("parseCategories, tagged symbols and matching metadata available, returns both", () => { + const categoryMap = parseCategories([ + classFor("cat-1"), + ], { + "cat-1": "Category 1", + }); + + assertEquals(categoryMap.size, 1); + assertEquals(categoryMap.get("cat-1"), "Category 1"); +}); diff --git a/reference/_util/common.ts b/reference/_util/common.ts index 9d600777c..51915a9e5 100644 --- a/reference/_util/common.ts +++ b/reference/_util/common.ts @@ -1,6 +1,16 @@ -import { DocNode, DocNodeBase } from "@deno/doc/types"; +import { DocNode, DocNodeBase, DocNodeNamespace } from "@deno/doc/types"; import { HasFullName, HasNamespace, MightHaveNamespace } from "../types.ts"; +export const sections = [ + ["Classes", "class"], + ["Enums", "enum"], + ["Functions", "function"], + ["Interfaces", "interface"], + ["Namespaces", "namespace"], + ["Type Aliases", "typeAlias"], + ["Variables", "variable"], +]; + export function flattenItems( items: DocNode[], ): (DocNodeBase & MightHaveNamespace)[] { @@ -27,6 +37,11 @@ export function populateItemNamespaces( withFullName.fullName = item.name; if (item.kind === "namespace") { + const withNamespace = item as DocNodeNamespace & HasNamespace; + withNamespace.namespace = ""; + withNamespace.fullName = item.name; + flattened.push(withNamespace); + const namespace = ns + (ns ? "." : "") + item.name; flattened.push( ...populateItemNamespaces( @@ -46,3 +61,16 @@ export function populateItemNamespaces( return flattened; } + +export const nbsp = "\u00A0"; + +export function countSymbols(symbols: DocNode[]): number { + let count = 0; + for (const symbol of symbols) { + count++; + if (symbol.kind === "namespace" && symbol.namespaceDef) { + count += countSymbols(symbol.namespaceDef.elements); + } + } + return count; +} diff --git a/reference/_util/methodSignatureRendering.ts b/reference/_util/methodSignatureRendering.ts deleted file mode 100644 index 53e14c973..000000000 --- a/reference/_util/methodSignatureRendering.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { - ClassMethodDef, - ParamDef, - ParamIdentifierDef, - ParamRestDef, - TsTypeDef, -} from "@deno/doc/types"; - -export function methodSignature(method: ClassMethodDef) { - const parts = []; - if (method.accessibility) { - parts.push(method.accessibility); - } - - if (method.isStatic) { - parts.push("static "); - } - - if (method.isAbstract) { - parts.push("abstract "); - } - - if (method.isOverride) { - parts.push("override "); - } - - parts.push(method.functionDef.defName || method.name); - - if (method.functionDef.params.length > 0) { - console.log(method.functionDef); - const params = method.functionDef.params.map((param) => ( - methodParameter(param) - )); - - parts.push(`(${params.join(", ")})`); - } else { - parts.push("()"); - } - - return parts.join(""); -} - -export function methodParameter(param: ParamDef): string { - console.log(param); - const parts = []; - - if (param.kind === "rest") { - parts.push(restParameter(param)); - } - - if (param.kind === "identifier") { - parts.push(identifier(param)); - } - - if (param.kind === "array") { - parts.push("[]"); - } - - return parts.join(""); -} - -export function restParameter(param: ParamRestDef) { - const parts = []; - parts.push("..."); - parts.push(methodParameter(param.arg)); - return parts.join(""); -} - -export function identifier(param: ParamIdentifierDef) { - const parts = []; - parts.push(param.name); - if (param.optional) { - parts.push("?"); - } - - if (param.tsType) { - parts.push(typeInformation(param.tsType)); - } - return parts.join(""); -} - -export function typeInformation(type: TsTypeDef | undefined) { - if (!type) { - return ""; - } - - const parts = []; - parts.push(": "); - parts.push(type.repr); - if (type.kind === "array") { - parts.push("[]"); - } - - return parts.join(""); -} diff --git a/reference/_util/symbolMerging.ts b/reference/_util/symbolMerging.ts new file mode 100644 index 000000000..cdb2026be --- /dev/null +++ b/reference/_util/symbolMerging.ts @@ -0,0 +1,95 @@ +import { + DocNode, + DocNodeClass, + DocNodeInterface, + DocNodeNamespace, +} from "@deno/doc/types"; +import { HasNamespace } from "../types.ts"; + +export function mergeSymbolsWithCollidingNames( + symbolsByName: Map<string, (DocNode & HasNamespace)[]>, +) { + const mergedSymbols = Array.from(symbolsByName.values()).map((items) => { + if (items.length === 1) { + return items[0]; + } + + // Sort by priority (class > interface > other) + const sorted = items.sort((a, b) => { + if (a.kind === "class") return -1; + if (b.kind === "class") return 1; + if (a.kind === "interface") return -1; + if (b.kind === "interface") return 1; + return 0; + }); + + // Merge docs if available + const primary = sorted[0]; + const jsDoc = sorted + .map((s) => s.jsDoc?.doc) + .filter(Boolean) + .join("\n\n"); + + if (jsDoc) { + primary.jsDoc = { ...primary.jsDoc, doc: jsDoc }; + } + + // merge members + if (primary.kind === "namespace") { + const asType = sorted as (DocNodeNamespace & HasNamespace)[]; + const namespaceDefs = asType.map((s) => s.namespaceDef); + mergeSymbolCollections(namespaceDefs); + } + + if (primary.kind === "class") { + const asType = sorted as (DocNodeClass & HasNamespace)[]; + const classDefs = asType.map((s) => s.classDef); + mergeSymbolCollections(classDefs); + } + + if (primary.kind === "interface") { + const asType = sorted as (DocNodeInterface & HasNamespace)[]; + const interfaceDefs = asType.map((s) => s.interfaceDef); + mergeSymbolCollections(interfaceDefs); + } + + return primary; + }); + + return mergedSymbols; +} + +// deno-lint-ignore no-explicit-any +function mergeSymbolCollections(matchingSymbols: any[]) { + if (matchingSymbols.length < 2) { + return; + } + + const primary = matchingSymbols[0]; + if (!primary) { + return; + } + + const props = [ + "constructors", + "methods", + "properties", + "indexSignatures", + "decorators", + "typeParams", + "implements", + ]; + + for (const prop of props) { + if (primary[prop]) { + const others = matchingSymbols.slice(1); + for (const source of others) { + if (!source || !source[prop]) { + continue; + } + + primary[prop].push(...source[prop]); + } + } + } +} diff --git a/reference/_util/symbolMerging_test.ts b/reference/_util/symbolMerging_test.ts new file mode 100644 index 000000000..2e85d4d1d --- /dev/null +++ b/reference/_util/symbolMerging_test.ts @@ -0,0 +1,62 @@ +import { assertEquals } from "@std/assert/equals"; +import { mergeSymbolsWithCollidingNames } from "./symbolMerging.ts"; +import { DocNodeClass } from "@deno/doc/types"; +import { assert } from "@std/assert/assert"; + +Deno.test("mergeSymbolsWithCollidingNames, names match, returns one item", () => { + const symbol1 = { + name: "Test", + kind: "class", + namespace: "", + fullName: "Test", + }; + const symbol2 = { + name: "Test", + kind: "class", + namespace: "", + fullName: "Test", + }; + + // deno-lint-ignore no-explicit-any + const items = new Map<string, any[]>(); + items.set("Test", [symbol1, symbol2]); + + const merged = mergeSymbolsWithCollidingNames(items); + + assertEquals(merged.length, 1); +}); + +Deno.test("mergeSymbolsWithCollidingNames, merges elements in classDef", () => { + const symbol1 = { + name: "Test", + kind: "class", + namespace: "", + fullName: "Test", + classDef: { + constructors: [ + { name: "bar" }, + ], + }, + }; + const symbol2 = { + name: "Test", + kind: "class", + namespace: "", + fullName: "Test", + classDef: { + constructors: [ + { name: "baz" }, + ], + }, + }; + + // deno-lint-ignore no-explicit-any + const items = new Map<string, any[]>(); + items.set("Test", [symbol1, symbol2]); + + const merged = mergeSymbolsWithCollidingNames(items)[0] as DocNodeClass; + + assertEquals(merged.classDef.constructors.length, 2); + assert(merged.classDef.constructors.some((x) => x.name === "bar")); + assert(merged.classDef.constructors.some((x) => x.name === "baz")); +}); diff --git a/reference/_util/symbolStringBuilding.ts b/reference/_util/symbolStringBuilding.ts new file mode 100644 index 000000000..149027ea3 --- /dev/null +++ b/reference/_util/symbolStringBuilding.ts @@ -0,0 +1,229 @@ +import { + ClassMethodDef, + FunctionDef, + InterfaceMethodDef, + LiteralPropertyDef, + ParamDef, + ParamIdentifierDef, + ParamRestDef, + TsTypeDef, +} from "@deno/doc/types"; + +export type CodePart = { + value: string; + kind: string; +}; + +function isFunctionDefTypeGuard( + method: FunctionDef | InterfaceMethodDef, +): method is FunctionDef { + return (method as FunctionDef).params !== undefined; +} + +export function methodSignature(method: ClassMethodDef) { + const parts: CodePart[] = []; + + if (method.accessibility) { + parts.push({ value: method.accessibility, kind: "accessibility" }); + } + + if (method.isStatic) { + parts.push({ value: "static ", kind: "modifier" }); + } + + if (method.isAbstract) { + parts.push({ value: "abstract ", kind: "modifier" }); + } + + if (method.isOverride) { + parts.push({ value: "override ", kind: "modifier" }); + } + + parts.push(...functionSignature(method.functionDef, method.name)); + + return parts; +} + +export function interfaceSignature(iface: InterfaceMethodDef) { + const parts: CodePart[] = []; + parts.push(...functionSignature(iface, iface.name)); + return parts; +} + +export function functionSignature( + functionDef: FunctionDef | InterfaceMethodDef, + nameOverride: string | undefined = undefined, +) { + const parts: CodePart[] = []; + + const nameValue = isFunctionDefTypeGuard(functionDef) + ? functionDef.defName || nameOverride || "" + : functionDef.name || nameOverride || ""; + + parts.push({ value: nameValue, kind: "name" }); + + if (functionDef.params.length > 0) { + parts.push({ value: "(", kind: "method-brace" }); + + const params = functionDef.params.map((param) => ( + parameter(param) + )); + + for (const param of params) { + parts.push(...param); + parts.push({ value: ", ", kind: "param-separator" }); + } + + parts.pop(); // remove last separator + parts.push({ value: ")", kind: "method-brace" }); + } else { + parts.push({ value: "()", kind: "brackets" }); + } + + if (functionDef.returnType) { + parts.push({ value: ": ", kind: "type" }); + parts.push(...typeInformation(functionDef.returnType)); + } + + return parts; +} + +export function parameter(param: ParamDef): CodePart[] { + const parts: CodePart[] = []; + + if (param.kind === "rest") { + parts.push(...restParameter(param)); + } + + if (param.kind === "identifier") { + parts.push(...identifier(param)); + } + + if (param.kind === "array") { + parts.push({ value: "[]", kind: "identifier" }); + } + + return parts; +} + +export function restParameter(param: ParamRestDef): CodePart[] { + const parts: CodePart[] = []; + param.arg.tsType = param.tsType; // Fix bug where TSType is not populated. + + parts.push({ value: "...", kind: "identifier" }); + parts.push(...parameter(param.arg)); + return parts; +} + +export function identifier(param: ParamIdentifierDef | LiteralPropertyDef) { + const parts: CodePart[] = []; + + parts.push({ value: "", kind: "identifier-start" }); + parts.push({ value: param.name, kind: "identifier" }); + + if (param.optional) { + parts.push({ value: "?", kind: "identifier" }); + } + + if (param.tsType) { + parts.push({ value: ": ", kind: "type" }); + parts.push(...typeInformation(param.tsType)); + } + + return parts; +} + +export function typeInformation(type: TsTypeDef | undefined): CodePart[] { + if (!type) { + return []; + } + + const parts: CodePart[] = []; + + parts.push({ value: type?.repr || "", kind: "type" }); + + if (type.kind === "typeRef") { + if (type.typeRef && (type.typeRef.typeParams || []).length > 0) { + const typeParams = type.typeRef.typeParams || []; + + parts.push({ value: "<", kind: "brackets" }); + + for (const param of typeParams) { + parts.push(...typeInformation(param)); + parts.push({ value: ", ", kind: "separator" }); + } + + parts.pop(); // remove last separator + parts.push({ value: ">", kind: "brackets" }); + } + } + + if (type.kind === "array") { + parts.push({ value: type?.array?.repr || "" + "[]", kind: "type" }); + parts.push({ value: "[]", kind: "type" }); + } + + if (type.kind === "literal") { + parts.push({ value: type.literal.kind, kind: "type" }); + } + + if (type.kind == "typeLiteral") { + parts.push({ value: "{ ", kind: "brackets" }); + + for (const prop of type.typeLiteral.properties) { + parts.push(...identifier(prop)); + parts.push({ value: "; ", kind: "separator" }); + } + + parts.push({ value: " }", kind: "brackets" }); + } + + if (type.kind === "union") { + for (const part of type.union) { + parts.push(...typeInformation(part)); + parts.push({ value: " | ", kind: "separator" }); + } + + parts.pop(); // remove last separator + } + + if (type.kind === "intersection") { + for (const part of type.intersection) { + parts.push(...typeInformation(part)); + parts.push({ value: " & ", kind: "separator" }); + } + + parts.pop(); // remove last separator + } + + if (type.kind === "parenthesized") { + parts.push({ value: "(", kind: "brackets" }); + parts.push(...typeInformation(type.parenthesized)); + parts.push({ value: ")", kind: "brackets" }); + } + + if (type.kind === "fnOrConstructor") { + parts.push({ value: "(", kind: "brackets" }); + + if (type.fnOrConstructor.params.length > 0) { + for (const param of type.fnOrConstructor.params) { + parts.push(...parameter(param)); + parts.push({ value: ", ", kind: "separator" }); + } + + parts.pop(); // remove last separator + } + + parts.push({ value: ")", kind: "brackets" }); + parts.push({ value: " => ", kind: "separator" }); + parts.push(...typeInformation(type.fnOrConstructor.tsType)); + } + + if (type.kind === "typeOperator") { + parts.push({ value: type.typeOperator.operator, kind: "type" }); + parts.push({ value: " ", kind: "type" }); + parts.push(...typeInformation(type.typeOperator.tsType)); + } + + return parts; +} diff --git a/reference/_util/testData.ts b/reference/_util/testData.ts new file mode 100644 index 000000000..c9096832e --- /dev/null +++ b/reference/_util/testData.ts @@ -0,0 +1,34 @@ +import { DocNode } from "@deno/doc/types"; + +export function classFor(name: string): DocNode { + return { + kind: "class", + name: "A", + classDef: { + isAbstract: false, + extends: "", + constructors: [], + implements: [], + typeParams: [], + indexSignatures: [], + methods: [], + superTypeParams: [], + properties: [], + decorators: [], + }, + declarationKind: "private", + location: { + filename: "a.ts", + line: 1, + col: 1, + }, + jsDoc: { + tags: [ + { + kind: "category", + doc: name, + }, + ], + }, + }; +} diff --git a/reference/reference.page.raw.tsx b/reference/reference.page.raw.tsx index 9793bfd77..73ab483a0 100644 --- a/reference/reference.page.raw.tsx +++ b/reference/reference.page.raw.tsx @@ -1,6 +1,6 @@ import generatePageFor from "./pageFactory.ts"; import getCategoryPages from "./_pages/Category.tsx"; -import { populateItemNamespaces } from "./_util/common.ts"; +import { countSymbols, populateItemNamespaces } from "./_util/common.ts"; import { getSymbols } from "./_dataSources/dtsSymbolSource.ts"; import webCategoryDocs from "./_categories/web-categories.json" with { type: "json", @@ -9,6 +9,9 @@ import denoCategoryDocs from "./_categories/deno-categories.json" with { type: "json", }; import { DocNode } from "@deno/doc/types"; +import { HasNamespace } from "./types.ts"; +import { mergeSymbolsWithCollidingNames } from "./_util/symbolMerging.ts"; +import { categoryDataFrom, parseCategories } from "./_util/categoryBuilding.ts"; export const layout = "raw.tsx"; @@ -28,26 +31,26 @@ export const sidebar = [ }, ]; -const generated: string[] = []; - export default async function* () { + let skipped = 0; + const generated: string[] = []; + try { if (Deno.env.has("SKIP_REFERENCE")) { throw new Error(); } - for await (const { packageName, symbols } of getSymbols()) { - const cleanedSymbols = populateItemNamespaces(symbols) as DocNode[]; + const allSymbols = await getAllSymbols(); - const currentCategoryList = sections.filter((x) => - x.path === packageName.toLocaleLowerCase() - )[0]!.categoryDocs as Record<string, string | undefined>; + for (const [packageName, symbols] of allSymbols.entries()) { + const descriptions = categoryDataFrom(sections, packageName); + const categories = parseCategories(symbols, descriptions); const context = { root, packageName, - symbols: cleanedSymbols, - currentCategoryList: currentCategoryList, + symbols, + currentCategoryList: categories, }; for (const p of getCategoryPages(context)) { @@ -55,18 +58,18 @@ export default async function* () { generated.push(p.url); } - for (const item of cleanedSymbols) { + for (const item of symbols) { const pages = generatePageFor(item, context); for await (const page of pages) { if (generated.includes(page.url)) { - console.warn(`⚠️ Skipping duplicate page: ${page.url}!`); + //console.warn(`⚠️ Skipping duplicate page: ${page.url}!`); + skipped++; continue; } yield page; generated.push(page.url); - console.log("Generated", page.url); } } } @@ -74,5 +77,31 @@ export default async function* () { console.warn("⚠️ Reference docs were not generated." + ex); } - console.log("Generated", generated.length, "reference pages"); + console.log("Generated", generated.length, "pages", skipped, "skipped"); +} + +async function getAllSymbols() { + const allSymbols = new Map<string, DocNode[]>(); + for await (const { packageName, symbols } of getSymbols()) { + console.log(`📚 ${packageName} has ${countSymbols(symbols)} symbols`); + + const cleaned = populateItemNamespaces( + symbols, + ) as (DocNode & HasNamespace)[]; + + console.log(`📚 ${packageName} has ${countSymbols(cleaned)} cleaned`); + + const symbolsByName = new Map<string, (DocNode & HasNamespace)[]>(); + + for (const symbol of cleaned) { + const existing = symbolsByName.get(symbol.fullName) || []; + symbolsByName.set(symbol.name, [...existing, symbol]); + } + + const mergedSymbols = mergeSymbolsWithCollidingNames(symbolsByName); + + allSymbols.set(packageName, mergedSymbols); + } + + return allSymbols; } diff --git a/reference/types.ts b/reference/types.ts index f2b2008ad..7ac67c2c3 100644 --- a/reference/types.ts +++ b/reference/types.ts @@ -1,4 +1,17 @@ -import { DocNode, DocNodeBase } from "@deno/doc/types"; +import { + ClassConstructorDef, + ClassMethodDef, + ClassPropertyDef, + DocNode, + DocNodeBase, + InterfaceCallSignatureDef, + InterfaceIndexSignatureDef, + InterfaceMethodDef, + InterfacePropertyDef, + LiteralCallSignatureDef, + LiteralMethodDef, + LiteralPropertyDef, +} from "@deno/doc/types"; import { JSX } from "npm:preact/jsx-runtime"; export type LumeDocument = { @@ -39,9 +52,62 @@ export type ReferenceContext = { root: string; packageName: string; symbols: DocNode[]; - currentCategoryList: Record<string, string | undefined>; + currentCategoryList: Map<string, string>; }; export type ReferenceDocumentFactoryFunction< T extends DocNodeBase = DocNodeBase, > = (item: T, context: ReferenceContext) => IterableIterator<LumeDocument>; + +export type ValidPropertyType = + | ClassPropertyDef + | InterfacePropertyDef + | LiteralPropertyDef; +export type ValidMethodType = + | ClassMethodDef + | InterfaceMethodDef + | LiteralMethodDef; +export type ValidCallSignaturesType = + | InterfaceCallSignatureDef + | LiteralCallSignatureDef; +export type ValidIndexSignaturesType = + | InterfaceIndexSignatureDef + | InterfaceIndexSignatureDef; + +export type HasJsDoc = { + jsDoc: { doc: string }; +}; + +export type ValidPropertyWithOptionalJsDoc = ValidPropertyType & { + jsDoc?: { doc: string }; +}; + +export function isClassMethodDef( + method: ValidMethodType, +): method is ClassMethodDef { + return (method as ClassMethodDef).functionDef !== undefined; +} + +export function isInterfaceMethodDef( + method: ValidMethodType, +): method is InterfaceMethodDef { + return (method as InterfaceMethodDef).location !== undefined && + (method as ClassMethodDef).functionDef === undefined; +} + +// deno-lint-ignore no-explicit-any +export function hasJsDoc(obj: any): obj is HasJsDoc { + return obj.jsDoc !== undefined; +} + +export interface ClosureContent { + kind: string; + constructors: ClassConstructorDef[]; + methods: ValidMethodType[]; + properties: ValidPropertyWithOptionalJsDoc[]; + callSignatures: ValidCallSignaturesType[]; + indexSignatures: ValidIndexSignaturesType[]; + + instanceMethods?: ClassMethodDef[]; + staticMethods?: ClassMethodDef[]; +} diff --git a/runtime/fundamentals/open_telemetry.md b/runtime/fundamentals/open_telemetry.md deleted file mode 100644 index 64bfcd951..000000000 --- a/runtime/fundamentals/open_telemetry.md +++ /dev/null @@ -1,605 +0,0 @@ ---- -title: OpenTelemetry ---- - -:::caution - -The OpenTelemetry integration for Deno is still in development and may change. -To use it, you must pass the `--unstable-otel` flag to Deno. - -::: - -Deno has built in support for [OpenTelemetry](https://opentelemetry.io/). - -> OpenTelemetry is a collection of APIs, SDKs, and tools. Use it to instrument, -> generate, collect, and export telemetry data (metrics, logs, and traces) to -> help you analyze your software’s performance and behavior. -> -> <i>- https://opentelemetry.io/</i> - -This integration enables you to monitor your Deno applications using -OpenTelemetry observability tooling with instruments like logs, metrics, and -traces. - -Deno provides the following features: - -- Exporting of collected metrics, traces, and logs to a server using the - OpenTelemetry protocol. -- [Automatic instrumentation](#auto-instrumentation) of the Deno runtime with - OpenTelemetry metrics, traces, and logs. -- [Collection of user defined metrics, traces, and logs](#user-metrics) created - with the `npm:@opentelemetry/api` package. - -## Quick start - -To enable the OpenTelemetry integration, run your Deno script with the -`--unstable-otel` flag and set the environment variable `OTEL_DENO=1`: - -```sh -OTEL_DENO=1 deno run --unstable-otel my_script.ts -``` - -This will automatically collect and export runtime observability data to an -OpenTelemetry endpoint at `localhost:4318` using Protobuf over HTTP -(`http/protobuf`). - -:::tip - -If you do not have an OpenTelemetry collector set up yet, you can get started -with a -[local LGTM stack in Docker](https://github.com/grafana/docker-otel-lgtm/tree/main?tab=readme-ov-file) -(Loki (logs), Grafana (dashboard), Tempo (traces), and Mimir (metrics)) by -running the following command: - -```sh -docker run --name lgtm -p 3000:3000 -p 4317:4317 -p 4318:4318 --rm -ti \ - -v "$PWD"/lgtm/grafana:/data/grafana \ - -v "$PWD"/lgtm/prometheus:/data/prometheus \ - -v "$PWD"/lgtm/loki:/data/loki \ - -e GF_PATHS_DATA=/data/grafana \ - docker.io/grafana/otel-lgtm:0.8.1 -``` - -You can then access the Grafana dashboard at `http://localhost:3000` with the -username `admin` and password `admin`. - -::: - -This will automatically collect and export runtime observability data like -`console.log`, traces for HTTP requests, and metrics for the Deno runtime. -[Learn more about auto instrumentation](#auto-instrumentation). - -You can also create your own metrics, traces, and logs using the -`npm:@opentelemetry/api` package. -[Learn more about user defined metrics](#user-metrics). - -## Auto instrumentation - -Deno automatically collects and exports some observability data to the OTLP -endpoint. - -This data is exported in the built-in instrumentation scope of the Deno runtime. -This scope has the name `deno`. The version of the Deno runtime is the version -of the `deno` instrumentation scope. (e.g. `deno:2.1.4`). - -### Traces - -Deno automatically creates spans for various operations, such as: - -- Incoming HTTP requests served with `Deno.serve`. -- Outgoing HTTP requests made with `fetch`. - -#### `Deno.serve` - -When you use `Deno.serve` to create an HTTP server, a span is created for each -incoming request. The span automatically ends when response headers are sent -(not when the response body is done sending). - -The name of the created span is `${method}`. The span kind is `server`. - -The following attributes are automatically added to the span on creation: - -- `http.request.method`: The HTTP method of the request. -- `url.full`: The full URL of the request (as would be reported by `req.url`). -- `url.scheme`: The scheme of the request URL (e.g. `http` or `https`). -- `url.path`: The path of the request URL. -- `url.query`: The query string of the request URL. - -After the request is handled, the following attributes are added: - -- `http.status_code`: The status code of the response. - -Deno does not automatically add a `http.route` attribute to the span as the -route is not known by the runtime, and instead is determined by the routing -logic in a user's handler function. If you want to add a `http.route` attribute -to the span, you can do so in your handler function using -`npm:@opentelemetry/api`. In this case you should also update the span name to -include the route. - -```ts -import { trace } from "npm:@opentelemetry/api@1"; - -const INDEX_ROUTE = new URLPattern({ pathname: "/" }); -const BOOK_ROUTE = new URLPattern({ pathname: "/book/:id" }); - -Deno.serve(async (req) => { - const span = trace.getActiveSpan(); - if (INDEX_ROUTE.test(req.url)) { - span.setAttribute("http.route", "/"); - span.updateName(`${req.method} /`); - - // handle index route - } else if (BOOK_ROUTE.test(req.url)) { - span.setAttribute("http.route", "/book/:id"); - span.updateName(`${req.method} /book/:id`); - - // handle book route - } else { - return new Response("Not found", { status: 404 }); - } -}); -``` - -#### `fetch` - -When you use `fetch` to make an HTTP request, a span is created for the request. -The span automatically ends when the response headers are received. - -The name of the created span is `${method}`. The span kind is `client`. - -The following attributes are automatically added to the span on creation: - -- `http.request.method`: The HTTP method of the request. -- `url.full`: The full URL of the request. -- `url.scheme`: The scheme of the request URL. -- `url.path`: The path of the request URL. -- `url.query`: The query string of the request URL. - -After the response is received, the following attributes are added: - -- `http.status_code`: The status code of the response. - -### Metrics - -The following metrics are automatically collected and exported: - -_None yet_ - -### Logs - -The following logs are automatically collected and exported: - -- Any logs created with `console.*` methods such as `console.log` and - `console.error`. -- Any logs created by the Deno runtime, such as debug logs, `Downloading` logs, - and similar. -- Any errors that cause the Deno runtime to exit (both from user code, and from - the runtime itself). - -Logs raised from JavaScript code will be exported with the relevant span -context, if the log occurred inside of an active span. - -`console` auto instrumentation can be configured using the `OTEL_DENO_CONSOLE` -environment variable: - -- `capture`: Logs are emitted to stdout/stderr and are also exported with - OpenTelemetry. (default) -- `replace`: Logs are only exported with OpenTelemetry, and not emitted to - stdout/stderr. -- `ignore`: Logs are emitted only to stdout/stderr, and will not be exported - with OpenTelemetry. - -## User metrics - -In addition to the automatically collected telemetry data, you can also create -your own metrics and traces using the `npm:@opentelemetry/api` package. - -You do not need to configure the `npm:@opentelemetry/api` package to use it with -Deno. Deno sets up the `npm:@opentelemetry/api` package automatically when the -`--unstable-otel` flag is passed. There is no need to call -`metrics.setGlobalMeterProvider()`, `trace.setGlobalTracerProvider()`, or -`context.setGlobalContextManager()`. All configuration of resources, exporter -settings, etc. is done via environment variables. - -Deno works with version `1.x` of the `npm:@opentelemetry/api` package. You can -either import directly from `npm:@opentelemetry/api@1`, or you can install the -package locally with `deno add` and import from `@opentelemetry/api`. - -```sh -deno add npm:@opentelemetry/api@1 -``` - -For both traces and metrics, you need to define names for the tracer and meter -respectively. If you are instrumenting a library, you should name the tracer or -meter after the library (such as `my-awesome-lib`). If you are instrumenting an -application, you should name the tracer or meter after the application (such as -`my-app`). The version of the tracer or meter should be set to the version of -the library or application. - -### Traces - -To create a new span, first import the `trace` object from -`npm:@opentelemetry/api` and create a new tracer: - -```ts -import { trace } from "npm:@opentelemetry/api@1"; - -const tracer = trace.getTracer("my-app", "1.0.0"); -``` - -Then, create a new span using the `tracer.startActiveSpan` method and pass a -callback function to it. You have to manually end the span by calling the `end` -method on the span object returned by `startActiveSpan`. - -```ts -function myFunction() { - return tracer.startActiveSpan("myFunction", (span) => { - try { - // do myFunction's work - } catch (error) { - span.recordException(error); - span.setStatus({ - code: trace.SpanStatusCode.ERROR, - message: (error as Error).message, - }); - throw error; - } finally { - span.end(); - } - }); -} -``` - -`span.end()` should be called in a `finally` block to ensure that the span is -ended even if an error occurs. `span.recordException` and `span.setStatus` -should also be called in a `catch` block, to record any errors that occur. - -Inside of the callback function, the created span is the "active span". You can -get the active span using `trace.getActiveSpan()`. The "active span" will be -used as the parent span for any spans created (manually, or automatically by the -runtime) inside of the callback function (or any functions that are called from -the callback function). -[Learn more about context propagation](#context-propagation). - -The `startActiveSpan` method returns the return value of the callback function. - -Spans can have attributes added to them during their lifetime. Attributes are -key value pairs that represent structured metadata about the span. Attributes -can be added using the `setAttribute` and `setAttributes` methods on the span -object. - -```ts -span.setAttribute("key", "value"); -span.setAttributes({ success: true, "bar.count": 42n, "foo.duration": 123.45 }); -``` - -Values for attributes can be strings, numbers (floats), bigints (clamped to -u64), booleans, or arrays of any of these types. If an attribute value is not -one of these types, it will be ignored. - -The name of a span can be updated using the `updateName` method on the span -object. - -```ts -span.updateName("new name"); -``` - -The status of a span can be set using the `setStatus` method on the span object. -The `recordException` method can be used to record an exception that occurred -during the span's lifetime. `recordException` creates an event with the -exception stack trace and name and attaches it to the span. **`recordException` -does not set the span status to `ERROR`, you must do that manually.** - -```ts -import { SpanStatusCode } from "npm:@opentelemetry/api@1"; - -span.setStatus({ - code: SpanStatusCode.ERROR, - message: "An error occurred", -}); -span.recordException(new Error("An error occurred")); - -// or - -span.setStatus({ - code: SpanStatusCode.OK, -}); -``` - -Spans can also have -[events](https://open-telemetry.github.io/opentelemetry-js/interfaces/_opentelemetry_api.Span.html#addEvent) -and -[links](https://open-telemetry.github.io/opentelemetry-js/interfaces/_opentelemetry_api.Span.html#addLink) -added to them. Events are points in time that are associated with the span. -Links are references to other spans. - -Spans can also be created manually with `tracer.startSpan` which returns a span -object. This method does not set the created span as the active span, so it will -not automatically be used as the parent span for any spans created later, or any -`console.log` calls. A span can manually be set as the active span for a -callback, by using the [context propagation API](#context-propagation). - -Both `tracer.startActiveSpan` and `tracer.startSpan` can take an optional -options bag containing any of the following properties: - -- `kind`: The kind of the span. Can be `SpanKind.CLIENT`, `SpanKind.SERVER`, - `SpanKind.PRODUCER`, `SpanKind.CONSUMER`, or `SpanKind.INTERNAL`. Defaults to - `SpanKind.INTERNAL`. -- `startTime` A `Date` object representing the start time of the span, or a - number representing the start time in milliseconds since the Unix epoch. If - not provided, the current time will be used. -- `attributes`: An object containing attributes to add to the span. -- `links`: An array of links to add to the span. -- `root`: A boolean indicating whether the span should be a root span. If - `true`, the span will not have a parent span (even if there is an active - span). - -After the options bag, both `tracer.startActiveSpan` and `tracer.startSpan` can -also take a `context` object from the -[context propagation API](#context-propagation). - -Learn more about the full tracing API in the -[OpenTelemetry JS API docs](https://open-telemetry.github.io/opentelemetry-js/classes/_opentelemetry_api.TraceAPI.html). - -### Metrics - -To create a metric, first import the `metrics` object from -`npm:@opentelemetry/api` and create a new meter: - -```ts -import { metrics } from "npm:@opentelemetry/api@1"; - -const meter = metrics.getMeter("my-app", "1.0.0"); -``` - -Then, an instrument can be created from the meter, and used to record values: - -```ts -const counter = meter.createCounter("my_counter", { - description: "A simple counter", - unit: "1", -}); - -counter.add(1); -counter.add(2); -``` - -Each recording can also have associated attributes: - -```ts -counter.add(1, { color: "red" }); -counter.add(2, { color: "blue" }); -``` - -:::tip - -In OpenTelemetry, metric attributes should generally have low cardinality. This -means that there should not be too many unique combinations of attribute values. -For example, it is probably fine to have an attribute for which continent a user -is on, but it would be too high cardinality to have an attribute for the exact -latitude and longitude of the user. High cardinality attributes can cause -problems with metric storage and exporting, and should be avoided. Use spans and -logs for high cardinality data. - -::: - -There are several types of instruments that can be created with a meter: - -- **Counter**: A counter is a monotonically increasing value. Counters can only - be positive. They can be used for values that are always increasing, such as - the number of requests handled. - -- **UpDownCounter**: An up-down counter is a value that can both increase and - decrease. Up-down counters can be used for values that can increase and - decrease, such as the number of active connections or requests in progress. - -- **Gauge**: A gauge is a value that can be set to any value. They are used for - values that do not "accumulate" over time, but rather have a specific value at - any given time, such as the current temperature. - -- **Histogram**: A histogram is a value that is recorded as a distribution of - values. Histograms can be used for values that are not just a single number, - but a distribution of numbers, such as the response time of a request in - milliseconds. Histograms can be used to calculate percentiles, averages, and - other statistics. They have a predefined set of boundaries that define the - buckets that the values are placed into. By default, the boundaries are - `[0.0, 5.0, 10.0, 25.0, 50.0, 75.0, 100.0, 250.0, 500.0, 750.0, 1000.0, 2500.0, 5000.0, 7500.0, 10000.0]`. - -There are also several types of observable instruments. These instruments do not -have a synchronous recording method, but instead return a callback that can be -called to record a value. The callback will be called when the OpenTelemetry SDK -is ready to record a value, for example just before exporting. - -```ts -const counter = meter.createObservableCounter("my_counter", { - description: "A simple counter", - unit: "1", -}); -counter.addCallback((res) => { - res.observe(1); - // or - res.observe(1, { color: "red" }); -}); -``` - -There are three types of observable instruments: - -- **ObservableCounter**: An observable counter is a counter that can be observed - asynchronously. It can be used for values that are always increasing, such as - the number of requests handled. -- **ObservableUpDownCounter**: An observable up-down counter is a value that can - both increase and decrease, and can be observed asynchronously. Up-down - counters can be used for values that can increase and decrease, such as the - number of active connections or requests in progress. -- **ObservableGauge**: An observable gauge is a value that can be set to any - value, and can be observed asynchronously. They are used for values that do - not "accumulate" over time, but rather have a specific value at any given - time, such as the current temperature. - -Learn more about the full metrics API in the -[OpenTelemetry JS API docs](https://open-telemetry.github.io/opentelemetry-js/classes/_opentelemetry_api.MetricsAPI.html). - -## Context propagation - -In OpenTelemetry, context propagation is the process of passing some context -information (such as the current span) from one part of an application to -another, without having to pass it explicitly as an argument to every function. - -In Deno, context propagation is done using the rules of `AsyncContext`, the TC39 -proposal for async context propagation. The `AsyncContext` API is not yet -exposed to users in Deno, but it is used internally to propagate the active span -and other context information across asynchronous boundaries. - -A quick overview how AsyncContext propagation works: - -- When a new asynchronous task is started (such as a promise, or a timer), the - current context is saved. -- Then some other code can execute concurrently with the asynchronous task, in a - different context. -- When the asynchronous task completes, the saved context is restored. - -This means that async context propagation essentially behaves like a global -variable that is scoped to the current asynchronous task, and is automatically -copied to any new asynchronous tasks that are started from this current task. - -The `context` API from `npm:@opentelemetry/api@1` exposes this functionality to -users. It works as follows: - -```ts -import { context } from "npm:@opentelemetry/api@1"; - -// Get the currently active context -const currentContext = context.active(); - -// You can add create a new context with a value added to it -const newContext = currentContext.setValue("id", 1); - -// The current context is not changed by calling setValue -console.log(currentContext.getValue("id")); // undefined - -// You can run a function inside a new context -context.with(newContext, () => { - // Any code in this block will run with the new context - console.log(context.active().getValue("id")); // 1 - - // The context is also available in any functions called from this block - function myFunction() { - return context.active().getValue("id"); - } - console.log(myFunction()); // 1 - - // And it is also available in any asynchronous callbacks scheduled from here - setTimeout(() => { - console.log(context.active().getValue("id")); // 1 - }, 10); -}); - -// Outside, the context is still the same -console.log(context.active().getValue("id")); // undefined -``` - -The context API integrates with spans too. For example, to run a function in the -context of a specific span, the span can be added to a context, and then the -function can be run in that context: - -```ts -import { context, trace } from "npm:@opentelemetry/api@1"; - -const tracer = trace.getTracer("my-app", "1.0.0"); - -const span = tracer.startSpan("myFunction"); -const contextWithSpan = trace.setSpan(context.active(), span); - -context.with(contextWithSpan, () => { - const activeSpan = trace.getActiveSpan(); - console.log(activeSpan === span); // true -}); - -// Don't forget to end the span! -span.end(); -``` - -Learn more about the full context API in the -[OpenTelemetry JS API docs](https://open-telemetry.github.io/opentelemetry-js/classes/_opentelemetry_api.ContextAPI.html). - -## Configuration - -The OpenTelemetry integration can be enabled by setting the `OTEL_DENO=1` -environment variable. - -The endpoint and protocol for the OTLP exporter can be configured using the -`OTEL_EXPORTER_OTLP_ENDPOINT` and `OTEL_EXPORTER_OTLP_PROTOCOL` environment -variables. - -If the endpoint requires authentication, headers can be configured using the -`OTEL_EXPORTER_OTLP_HEADERS` environment variable. - -Endpoint can all be overridden individually for metrics, traces, and logs by -using specific environment variables, such as: - -- `OTEL_EXPORTER_OTLP_METRICS_ENDPOINT` -- `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` -- `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT` - -For more information on headers that can be used to configure the OTLP exporter, -[see the OpenTelemetry website](https://opentelemetry.io/docs/specs/otel/protocol/exporter/#configuration-options). - -The resource that is associated with the telemetry data can be configured using -the `OTEL_SERVICE_NAME` and `OTEL_RESOURCE_ATTRIBUTES` environment variables. In -addition to attributes set via the `OTEL_RESOURCE_ATTRIBUTES` environment -variable, the following attributes are automatically set: - -- `service.name`: If `OTEL_SERVICE_NAME` is not set, the value is set to - `<unknown_service>`. -- `process.runtime.name`: `deno` -- `process.runtime.version`: The version of the Deno runtime. -- `telemetry.sdk.name`: `deno-opentelemetry` -- `telemetry.sdk.language`: `deno-rust` -- `telemetry.sdk.version`: The version of the Deno runtime, plus the version of - the `opentelemetry` Rust crate being used by Deno, separated by a `-`. - -Metric collection frequency can be configured using the -`OTEL_METRIC_EXPORT_INTERVAL` environment variable. The default value is `60000` -milliseconds (60 seconds). - -Span exporter batching can be configured using the batch span processor -environment variables described in the -[OpenTelemetry specification](https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#batch-span-processor). - -Log exporter batching can be configured using the batch log record processor -environment variables described in the -[OpenTelemetry specification](https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#batch-log-record-processor). - -## Limitations - -While the OpenTelemetry integration for Deno is in development, there are some -limitations to be aware of: - -- Traces are always sampled (i.e. `OTEL_TRACE_SAMPLER=parentbased_always_on`). -- Traces do not support events and links. -- Automatic propagation of the trace context in `Deno.serve` and `fetch` is not - supported. -- Metric exemplars are not supported. -- Custom log streams (e.g. logs other than `console.log` and `console.error`) - are not supported. -- The only supported exporter is OTLP - other exporters are not supported. -- Only `http/protobuf` and `http/json` protocols are supported for OTLP. Other - protocols such as `grpc` are not supported. -- Metrics from observable (asynchronous) meters are not collected on process - exit/crash, so the last value of metrics may not be exported. Synchronous - metrics are exported on process exit/crash. -- The limits specified in the `OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT`, - `OTEL_ATTRIBUTE_COUNT_LIMIT`, `OTEL_SPAN_EVENT_COUNT_LIMIT`, - `OTEL_SPAN_LINK_COUNT_LIMIT`, `OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT`, and - `OTEL_LINK_ATTRIBUTE_COUNT_LIMIT` environment variable are not respected for - trace spans. -- The `OTEL_METRIC_EXPORT_TIMEOUT` environment variable is not respected. -- HTTP methods are that are not known are not normalized to `_OTHER` in the - `http.request.method` span attribute as per the OpenTelemetry semantic - conventions. -- The HTTP server span for `Deno.serve` does not have an OpenTelemtry status - set, and if the handler throws (ie `onError` is invoked), the span will not - have an error status set and the error will not be attached to the span via - event. -- There is no mechanism to add a `http.route` attribute to the HTTP client span - for `fetch`, or to update the span name to include the route. diff --git a/static/reference-styles/extra-styles.css b/static/reference-styles/extra-styles.css new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/static/reference-styles/extra-styles.css @@ -0,0 +1 @@ +