├── example
├── .gitignore
├── css
│ ├── main.css
│ └── another-css-file.css
├── README.md
├── web
│ └── index.html
├── deno.jsonc
└── src
│ └── main.tsx
├── jsx-codegen
├── deno.json
└── main.ts
├── src
├── mod.ts
├── safely.ts
├── router.ts
├── esbuild
│ ├── plugins.ts
│ └── mod.ts
├── signal.ts
├── jsx.ts
└── elem.ts
├── .editorconfig
├── .prettierrc
├── deno.json
├── README.md
└── deno.lock
/example/.gitignore:
--------------------------------------------------------------------------------
1 | /web/dist
2 |
--------------------------------------------------------------------------------
/example/css/main.css:
--------------------------------------------------------------------------------
1 | @import url(./another-css-file.css);
2 |
--------------------------------------------------------------------------------
/example/css/another-css-file.css:
--------------------------------------------------------------------------------
1 | :root {
2 | font-family: sans-serif;
3 | }
4 |
--------------------------------------------------------------------------------
/jsx-codegen/deno.json:
--------------------------------------------------------------------------------
1 | {
2 | "lock": false,
3 | "imports": {
4 | "typescript": "npm:typescript@^5.8.2"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/mod.ts:
--------------------------------------------------------------------------------
1 | export * from "./elem.ts";
2 | export * from "./router.ts";
3 | export * from "./safely.ts";
4 | export * from "./signal.ts";
5 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # aftercare example
2 |
3 | ```shell
4 | $ deno task dev
5 | Serving on http://127.0.0.1:3000/ ...
6 | $ deno task build
7 | $ # [check ./web/dist]
8 | ```
9 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = false
9 | insert_final_newline = true
10 |
--------------------------------------------------------------------------------
/example/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 2,
3 | "useTabs": false,
4 | "semi": true,
5 | "arrowParens": "avoid",
6 | "printWidth": 96,
7 | "trailingComma": "all",
8 | "overrides": [
9 | {
10 | "files": "*.jsonc",
11 | "options": {
12 | "trailingComma": "none"
13 | }
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/src/safely.ts:
--------------------------------------------------------------------------------
1 | // deno-lint-ignore-file no-explicit-any
2 | export function safely(
3 | f: (...args: Args) => R,
4 | ): (...args: Args) => [ok: R, err: undefined] | [ok: undefined, err: Error] {
5 | return (...args) => {
6 | try {
7 | return [f(...args), undefined] as const;
8 | } catch (err) {
9 | return [undefined, err as Error] as const;
10 | }
11 | };
12 | }
13 |
--------------------------------------------------------------------------------
/deno.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@char/aftercare",
3 | "version": "0.4.2",
4 | "license": "WTFPL",
5 | "exports": {
6 | ".": "./src/mod.ts",
7 | "./esbuild": "./src/esbuild/mod.ts",
8 | "./jsx-runtime": "./src/jsx.ts"
9 | },
10 | "compilerOptions": {
11 | "lib": ["deno.window", "dom"],
12 | "jsx": "react-jsx",
13 | "jsxImportSource": "@char/aftercare"
14 | },
15 | "publish": {
16 | "exclude": ["example/", ".editorconfig", ".prettierrc"]
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/example/deno.jsonc:
--------------------------------------------------------------------------------
1 | {
2 | "tasks": {
3 | "dev": "deno run -A ../src/esbuild/mod.ts --watch --serve --in src/main.tsx",
4 | "build": "deno run -A ../src/esbuild/mod.ts --in src/main.tsx"
5 | },
6 | "imports": {
7 | // usually you should be able to just have one import entry for @char/aftercare from jsr
8 | "@char/aftercare": "../src/mod.ts",
9 | "@char/aftercare/elem": "../src/elem.ts",
10 | "@char/aftercare/jsx-runtime": "../src/jsx.ts"
11 | },
12 | "compilerOptions": {
13 | "lib": ["deno.window", "dom"],
14 | "jsx": "react-jsx",
15 | "jsxImportSource": "@char/aftercare"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/example/src/main.tsx:
--------------------------------------------------------------------------------
1 | import { assertEquals } from "jsr:@std/assert";
2 | assertEquals(2, 1 + 1);
3 |
4 | import { Signal } from "@char/aftercare";
5 |
6 | const counter = new Signal(0);
7 | const increment = () => counter.value++;
8 | const decrement = () => counter.value--;
9 | const showCounter = (span: HTMLElement) =>
10 | counter.subscribeImmediate(v => (span.textContent = v + ""));
11 |
12 | document.body.append(
13 |
14 |
17 |
18 |
21 |
,
22 | );
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # aftercare
2 |
3 | makes the dom nicer
4 |
5 | a microframework for client-side frontend development
6 |
7 | ## features
8 |
9 | - deno/esbuild build system
10 | - reactive signals
11 | - strongly-typed element creation helpers
12 | - jsx runtime
13 | - monadic result wrapper for functions that throw
14 |
15 | ## example
16 |
17 | ```tsx
18 | // src/main.tsx
19 |
20 | // esbuild resolves + bundles jsr/npm/https imports:
21 | import { assertEquals } from "jsr:@std/assert";
22 | assertEquals(2, 1 + 1);
23 |
24 | // Signals have reactive behavior (set(..) calls all subscribe(..) listeners)
25 | import { Signal } from "jsr:@char/aftercare@0.3.0";
26 |
27 | const counter = new Signal(0);
28 | const increment = () => counter.value++;
29 | const decrement = () => counter.value--;
30 | const showCounter = (span: HTMLElement) =>
31 | counter.subscribeImmediate(value => (span.innerText = value + ""));
32 |
33 | document.body.append(
34 |
35 |
36 |
37 |
38 |
,
39 | );
40 | ```
41 |
42 | ```html
43 |
44 |
45 |
46 |
47 |
48 |
49 | ```
50 |
51 | build like so:
52 |
53 | ```shell
54 | $ deno run -A jsr:@char/aftercare/esbuild --in src/main.tsx
55 | ```
56 |
--------------------------------------------------------------------------------
/src/router.ts:
--------------------------------------------------------------------------------
1 | import { LazySignal } from "./signal.ts";
2 |
3 | export abstract class Router {
4 | readonly route: LazySignal = new LazySignal();
5 |
6 | abstract parseRoute(path: string): Route;
7 | abstract getPathForRoute(route: Route): string;
8 |
9 | navigateTo(pathOrRoute: string | Route) {
10 | const isPath = typeof pathOrRoute === "string";
11 | const path = isPath ? pathOrRoute : this.getPathForRoute(pathOrRoute);
12 | const route = isPath ? this.parseRoute(pathOrRoute) : pathOrRoute;
13 |
14 | history.pushState(null, "", path);
15 | globalThis.scrollTo(0, 0);
16 |
17 | this.route.set(route);
18 | }
19 |
20 | install() {
21 | document.addEventListener(
22 | "click",
23 | e => {
24 | if (!(e.target instanceof Element)) return;
25 | const anchor = e.target.closest("a");
26 | if (anchor === null) return;
27 | if (e.ctrlKey || e.button !== 0) return;
28 |
29 | const url = new URL(anchor.href);
30 | if (location.origin !== url.origin) return; // open external links normally
31 |
32 | e.preventDefault();
33 | this.navigateTo(url.pathname);
34 | },
35 | { signal: this.#dispose.signal },
36 | );
37 |
38 | globalThis.addEventListener(
39 | "popstate",
40 | () => {
41 | const parsedRoute = this.parseRoute(location.pathname);
42 | this.route.set(parsedRoute);
43 | },
44 | { signal: this.#dispose.signal },
45 | );
46 | }
47 |
48 | #dispose = new AbortController();
49 | dispose() {
50 | this.#dispose.abort();
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/esbuild/plugins.ts:
--------------------------------------------------------------------------------
1 | import * as dotenv from "jsr:@std/dotenv@0.225";
2 | import * as path from "jsr:@std/path@1";
3 | import type * as esbuild from "npm:esbuild@0.24";
4 |
5 | export const envPlugin = (files: string[]): esbuild.Plugin => ({
6 | name: "env",
7 | setup: build => {
8 | build.onResolve({ filter: /^build-system-env$/ }, args => {
9 | return {
10 | path: args.path,
11 | namespace: "env-ns",
12 | watchFiles: files,
13 | };
14 | });
15 |
16 | build.onLoad({ filter: /.*/, namespace: "env-ns" }, async () => {
17 | const env = {};
18 | for (const file of files) {
19 | try {
20 | const loaded = await dotenv.load({ envPath: file, export: false });
21 | Object.assign(env, loaded);
22 | } catch {
23 | // ignore
24 | }
25 | }
26 |
27 | return {
28 | contents: JSON.stringify(env),
29 | loader: "json",
30 | };
31 | });
32 | },
33 | });
34 |
35 | export const cssPlugin = (): esbuild.Plugin => ({
36 | name: "css",
37 | setup: build => {
38 | const options = build.initialOptions;
39 | options.loader = { ...options.loader, ".woff": "file", ".woff2": "file" };
40 |
41 | build.onResolve({ filter: /\.css$/ }, args => {
42 | if (args.path.startsWith("https://")) {
43 | return { path: args.path, external: true };
44 | }
45 |
46 | return { path: path.join(args.resolveDir, args.path) };
47 | });
48 |
49 | build.onLoad({ filter: /\.css$/ }, args => {
50 | const loader = args.path.endsWith(".css") ? "css" : "file";
51 | return { loader };
52 | });
53 | },
54 | });
55 |
--------------------------------------------------------------------------------
/src/signal.ts:
--------------------------------------------------------------------------------
1 | export type Subscription = (value: T) => unknown | Promise;
2 |
3 | class SignalBase {
4 | #listeners: Set> = new Set();
5 |
6 | #value: A;
7 |
8 | constructor(value: A) {
9 | this.#value = value;
10 | }
11 |
12 | get(): A {
13 | return this.#value;
14 | }
15 |
16 | set(newValue: B) {
17 | this.#value = newValue;
18 | for (const listener of this.#listeners) {
19 | try {
20 | void listener(newValue);
21 | } catch {
22 | // ignore
23 | }
24 | }
25 | }
26 |
27 | subscribe(
28 | listener: Subscription,
29 | opts: { signal?: AbortSignal } = {},
30 | ): { unsubscribe: () => void } {
31 | const listeners = this.#listeners;
32 | listeners.add(listener);
33 |
34 | const subscription = {
35 | unsubscribe() {
36 | listeners.delete(listener);
37 | },
38 | };
39 |
40 | if (opts.signal) {
41 | opts.signal.addEventListener("abort", () => subscription.unsubscribe());
42 | }
43 |
44 | return subscription;
45 | }
46 |
47 | get value(): A {
48 | return this.get();
49 | }
50 |
51 | set value(newValue: B) {
52 | this.set(newValue);
53 | }
54 | }
55 |
56 | export class Signal extends SignalBase {
57 | subscribeImmediate(listener: Subscription): ReturnType {
58 | listener(this.get());
59 | return this.subscribe(listener);
60 | }
61 | }
62 | export class LazySignal extends SignalBase {
63 | constructor() {
64 | super(undefined);
65 | }
66 |
67 | subscribeImmediate(listener: Subscription): ReturnType {
68 | const value = this.get();
69 | if (value) listener(value);
70 | return this.subscribe(listener);
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/jsx-codegen/main.ts:
--------------------------------------------------------------------------------
1 | import ts from "npm:typescript";
2 |
3 | const program = ts.createProgram(["./main.ts"], {
4 | target: ts.ScriptTarget.Latest,
5 | lib: ["lib.dom.d.ts", "lib.esnext.d.ts"],
6 | });
7 | const checker = program.getTypeChecker();
8 |
9 | const sourceFile = program.getSourceFiles().find(sf => !sf.isDeclarationFile);
10 | if (!sourceFile) throw new Error("bwah");
11 |
12 | const htmlMap = checker.resolveName(
13 | "HTMLElementTagNameMap",
14 | sourceFile,
15 | ts.SymbolFlags.Interface,
16 | false,
17 | )!;
18 |
19 | const getTypeName = (ty: ts.Type) => {
20 | if (ty.getCallSignatures().length > 0) return checker.typeToString(ty);
21 | if (ty.aliasSymbol) return ty.aliasSymbol.getName();
22 | if (ty.symbol) return ty.symbol.getName();
23 | return checker.typeToString(ty);
24 | };
25 |
26 | const isReadonly = (symbol: ts.Symbol) => {
27 | for (const decl of symbol.declarations ?? []) {
28 | if (ts.isPropertyDeclaration(decl) || ts.isPropertySignature(decl)) {
29 | const flags = ts.getCombinedModifierFlags(decl);
30 | if ((flags & ts.ModifierFlags.Readonly) !== 0) return true;
31 | }
32 | }
33 |
34 | return false;
35 | };
36 |
37 | console.log("export interface ElementPropertyMap {");
38 |
39 | for (const [key, htmlElem] of htmlMap.members!.entries()) {
40 | const htmlElemType = checker.getTypeOfSymbol(htmlElem);
41 |
42 | console.log(" ", `[${JSON.stringify(key)}]:`, "{");
43 |
44 | for (const propSymbol of htmlElemType.getProperties()) {
45 | if (isReadonly(propSymbol)) continue;
46 |
47 | const propType = checker.getTypeOfSymbol(propSymbol);
48 | if (propType.getCallSignatures().length > 0) continue;
49 |
50 | const propName = propSymbol.name;
51 | console.log(" ", `[${JSON.stringify(propName)}]:`, getTypeName(propType) + ";");
52 | }
53 |
54 | console.log(" };");
55 | }
56 |
57 | console.log("}");
58 |
--------------------------------------------------------------------------------
/src/jsx.ts:
--------------------------------------------------------------------------------
1 | import {
2 | elem,
3 | type ElementEventListeners,
4 | type ElementExtras,
5 | type ElementProps,
6 | type ElementType,
7 | type TagName,
8 | } from "./elem.ts";
9 |
10 | type ElementAttributes = Omit<
11 | ElementProps>,
12 | keyof ElementExtras>
13 | > &
14 | ElementExtras> &
15 | ElementEventListeners>;
16 |
17 | type DOMElement = Element; // Element gets shadowed in the JSX namespace later:
18 |
19 | // deno-lint-ignore no-namespace
20 | namespace JSX {
21 | export type Element = HTMLElement | SVGElement;
22 | export type Child = JSX.Element | DOMElement | DOMElement[] | string | Text;
23 | export type IntrinsicElements = {
24 | [K in TagName]: Omit, "children"> & {
25 | children?: JSX.Child | JSX.Child[] | undefined;
26 | };
27 | };
28 | }
29 |
30 | function Fragment(_props: Record, _key?: string): never {
31 | throw new Error("fragments are not supported!");
32 | }
33 |
34 | function jsx(
35 | tag: T | ((props: Record) => JSX.Element),
36 | props: Record,
37 | _key?: string,
38 | ): ElementType {
39 | // function components
40 | if (typeof tag === "function") return tag(props) as ElementType;
41 |
42 | const { children = [], classList, dataset, style, _tap, ...attrs } = props;
43 | const childrenArray = Array.isArray(children) ? children : [children];
44 | const extras = { classList, dataset, style, _tap } as ElementExtras> &
45 | ElementEventListeners>;
46 | for (const key of Object.keys(attrs)) {
47 | if (!key.startsWith("_on")) continue;
48 | // @ts-expect-error blind assignment
49 | extras[key] = attrs[key];
50 | delete attrs[key];
51 | }
52 | return elem(tag, attrs as ElementProps>, childrenArray, extras);
53 | }
54 |
55 | export { Fragment, jsx, jsx as jsxDEV, jsx as jsxs };
56 | export type { JSX };
57 |
--------------------------------------------------------------------------------
/src/esbuild/mod.ts:
--------------------------------------------------------------------------------
1 | import { denoPlugins } from "jsr:@luca/esbuild-deno-loader@0.11";
2 | import * as cli from "jsr:@std/cli@1";
3 | import * as esbuild from "npm:esbuild@0.24";
4 | import { cssPlugin, envPlugin } from "./plugins.ts";
5 |
6 | export * from "./plugins.ts";
7 |
8 | export interface BuildOptions {
9 | watch?: boolean;
10 | serve?: esbuild.ServeOptions;
11 | plugins?: esbuild.Plugin[];
12 | in: esbuild.BuildOptions["entryPoints"];
13 | outDir: string; // directory
14 | extraOptions?: Partial;
15 | }
16 |
17 | export async function build(opts: BuildOptions) {
18 | const buildOpts = {
19 | bundle: true,
20 | minify: true,
21 | splitting: true,
22 | target: "esnext",
23 | platform: "browser",
24 | format: "esm",
25 | keepNames: true,
26 | sourcemap: "linked",
27 | plugins: [...(opts.plugins ?? []), ...denoPlugins()],
28 | entryPoints: opts.in,
29 | outdir: opts.outDir,
30 | legalComments: "none",
31 | jsx: "automatic",
32 | jsxImportSource: "@char/aftercare",
33 | ...(opts.extraOptions ?? {}),
34 | } satisfies esbuild.BuildOptions;
35 |
36 | if (opts.watch) {
37 | const ctx = await esbuild.context(buildOpts);
38 | await ctx.watch();
39 | if (opts.serve) {
40 | console.log(`Serving on http://${opts.serve.host}:${opts.serve.port}/ ...`);
41 | await ctx.serve(opts.serve);
42 | }
43 | } else {
44 | await esbuild.build(buildOpts);
45 | }
46 | }
47 |
48 | if (import.meta.main) {
49 | const args = cli.parseArgs(Deno.args, {
50 | boolean: ["watch", "serve"],
51 | string: ["bind"],
52 | collect: ["in"],
53 | default: { bind: "127.0.0.1:3000" },
54 | });
55 |
56 | const createServeInfo = (bind: string) => {
57 | let hostAndPort: { host: string; port: number };
58 | if (bind.indexOf(":") === -1) {
59 | hostAndPort = { host: "127.0.0.1", port: parseInt(bind) };
60 | } else {
61 | const [host, port] = bind.split(":");
62 | hostAndPort = { host, port: parseInt(port) };
63 | }
64 | return { ...hostAndPort, servedir: "./web", fallback: "./web/index.html" };
65 | };
66 | const serve = args.serve ? createServeInfo(args.bind) : undefined;
67 |
68 | await build({
69 | watch: args.watch,
70 | serve,
71 | in: args.in ? (args.in as string[]) : ["./src/main.ts"],
72 | outDir: "./web/dist",
73 | plugins: [envPlugin([".env", ".env.local"]), cssPlugin()],
74 | });
75 | }
76 |
--------------------------------------------------------------------------------
/src/elem.ts:
--------------------------------------------------------------------------------
1 | type GenericElement = HTMLElement | SVGElement;
2 |
3 | // prettier-ignore
4 | type IsEqual =
5 | (() => G extends A ? 1 : 2) extends
6 | (() => G extends B ? 1 : 2) ?
7 | true : false;
8 |
9 | // prettier-ignore
10 | type WritableKeysOf = {
11 | [P in keyof T]: IsEqual<
12 | { [Q in P]: T[P] },
13 | { readonly [Q in P]: T[P] }
14 | > extends false ? P : never;
15 | }[keyof T];
16 |
17 | export type ElementProps = {
18 | // deno-lint-ignore ban-types
19 | [K in WritableKeysOf as NonNullable extends Function ? never : K]?: E[K];
20 | };
21 |
22 | export interface ElementExtras {
23 | classList?: (string | undefined)[];
24 | style?: {
25 | // prettier-ignore
26 | [K in WritableKeysOf
27 | as K extends string
28 | ? CSSStyleDeclaration[K] extends string ? K : never
29 | : never
30 | ]?: CSSStyleDeclaration[K];
31 | } & {
32 | [prop: `--${string}`]: string | undefined;
33 | };
34 | dataset?: Partial>;
35 | /** extra function to run on the element */
36 | _tap?: (elem: E) => void;
37 | }
38 |
39 | export type ElementEventListeners = {
40 | [K in keyof HTMLElementEventMap as K extends string ? `_on${K}` : never]?: (
41 | this: E,
42 | ev: HTMLElementEventMap[K],
43 | ) => void;
44 | };
45 |
46 | export type TagName = keyof HTMLElementTagNameMap;
47 | export type CustomTagType = new () => T;
48 | export type ElementType = T extends TagName
49 | ? HTMLElementTagNameMap[T]
50 | : T extends CustomTagType
51 | ? E
52 | : never;
53 |
54 | export function elem(
55 | tag: T,
56 | attrs: ElementProps> = {},
57 | children: (Element | string | Text)[] = [],
58 | extras: ElementExtras> & ElementEventListeners> = {},
59 | ): ElementType {
60 | const element = typeof tag === "string" ? document.createElement(tag) : new tag();
61 |
62 | Object.assign(
63 | element,
64 | Object.fromEntries(Object.entries(attrs).filter(([_k, v]) => v !== undefined)),
65 | );
66 |
67 | if (extras.classList) extras.classList.forEach(c => c && element.classList.add(c));
68 | if (extras.dataset && (element instanceof HTMLElement || element instanceof SVGElement))
69 | Object.entries(extras.dataset)
70 | .filter(([_k, v]) => v !== undefined)
71 | .forEach(([k, v]) => (element.dataset[k] = v));
72 |
73 | const childNodes = children.map(e =>
74 | typeof e === "string" ? document.createTextNode(e) : e,
75 | );
76 | element.append(...childNodes);
77 |
78 | if (extras._tap) extras._tap(element as ElementType);
79 |
80 | if (extras.style)
81 | Object.entries(extras.style).forEach(([k, v]) =>
82 | k.startsWith("--")
83 | ? v
84 | ? element.style.setProperty(k, v)
85 | : element.style.removeProperty(k)
86 | : // @ts-expect-error blind assignment
87 | (element.style[k] = v),
88 | );
89 |
90 | for (const [key, value] of Object.entries(extras)) {
91 | if (!key.startsWith("_on")) continue;
92 | element.addEventListener(key.substring(3), value.bind(element));
93 | }
94 |
95 | return element as ElementType;
96 | }
97 |
98 | export function rewrite(element: Element, children: (Element | string | Text)[] = []) {
99 | element.innerHTML = "";
100 | const nodes = children.map(e => (typeof e === "string" ? document.createTextNode(e) : e));
101 | element.append(...nodes);
102 | }
103 |
--------------------------------------------------------------------------------
/deno.lock:
--------------------------------------------------------------------------------
1 | {
2 | "version": "4",
3 | "specifiers": {
4 | "jsr:@luca/esbuild-deno-loader@0.11": "0.11.0",
5 | "jsr:@std/bytes@^1.0.2": "1.0.3",
6 | "jsr:@std/cli@1": "1.0.6",
7 | "jsr:@std/dotenv@0.225": "0.225.2",
8 | "jsr:@std/encoding@^1.0.5": "1.0.5",
9 | "jsr:@std/path@1": "1.0.8",
10 | "jsr:@std/path@^1.0.6": "1.0.8",
11 | "npm:esbuild@0.24": "0.24.0"
12 | },
13 | "jsr": {
14 | "@luca/esbuild-deno-loader@0.11.0": {
15 | "integrity": "c05a989aa7c4ee6992a27be5f15cfc5be12834cab7ff84cabb47313737c51a2c",
16 | "dependencies": [
17 | "jsr:@std/bytes",
18 | "jsr:@std/encoding",
19 | "jsr:@std/path@^1.0.6"
20 | ]
21 | },
22 | "@std/bytes@1.0.3": {
23 | "integrity": "e5d5b9e685966314e4edb4be60dfc4bd7624a075bfd4ec8109252b4320f76452"
24 | },
25 | "@std/cli@1.0.6": {
26 | "integrity": "d22d8b38c66c666d7ad1f2a66c5b122da1704f985d3c47f01129f05abb6c5d3d"
27 | },
28 | "@std/dotenv@0.225.2": {
29 | "integrity": "e2025dce4de6c7bca21dece8baddd4262b09d5187217e231b033e088e0c4dd23"
30 | },
31 | "@std/encoding@1.0.5": {
32 | "integrity": "ecf363d4fc25bd85bd915ff6733a7e79b67e0e7806334af15f4645c569fefc04"
33 | },
34 | "@std/path@1.0.8": {
35 | "integrity": "548fa456bb6a04d3c1a1e7477986b6cffbce95102d0bb447c67c4ee70e0364be"
36 | }
37 | },
38 | "npm": {
39 | "@esbuild/aix-ppc64@0.24.0": {
40 | "integrity": "sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw=="
41 | },
42 | "@esbuild/android-arm64@0.24.0": {
43 | "integrity": "sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w=="
44 | },
45 | "@esbuild/android-arm@0.24.0": {
46 | "integrity": "sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew=="
47 | },
48 | "@esbuild/android-x64@0.24.0": {
49 | "integrity": "sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ=="
50 | },
51 | "@esbuild/darwin-arm64@0.24.0": {
52 | "integrity": "sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw=="
53 | },
54 | "@esbuild/darwin-x64@0.24.0": {
55 | "integrity": "sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA=="
56 | },
57 | "@esbuild/freebsd-arm64@0.24.0": {
58 | "integrity": "sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA=="
59 | },
60 | "@esbuild/freebsd-x64@0.24.0": {
61 | "integrity": "sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ=="
62 | },
63 | "@esbuild/linux-arm64@0.24.0": {
64 | "integrity": "sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g=="
65 | },
66 | "@esbuild/linux-arm@0.24.0": {
67 | "integrity": "sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw=="
68 | },
69 | "@esbuild/linux-ia32@0.24.0": {
70 | "integrity": "sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA=="
71 | },
72 | "@esbuild/linux-loong64@0.24.0": {
73 | "integrity": "sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g=="
74 | },
75 | "@esbuild/linux-mips64el@0.24.0": {
76 | "integrity": "sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA=="
77 | },
78 | "@esbuild/linux-ppc64@0.24.0": {
79 | "integrity": "sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ=="
80 | },
81 | "@esbuild/linux-riscv64@0.24.0": {
82 | "integrity": "sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw=="
83 | },
84 | "@esbuild/linux-s390x@0.24.0": {
85 | "integrity": "sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g=="
86 | },
87 | "@esbuild/linux-x64@0.24.0": {
88 | "integrity": "sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA=="
89 | },
90 | "@esbuild/netbsd-x64@0.24.0": {
91 | "integrity": "sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg=="
92 | },
93 | "@esbuild/openbsd-arm64@0.24.0": {
94 | "integrity": "sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg=="
95 | },
96 | "@esbuild/openbsd-x64@0.24.0": {
97 | "integrity": "sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q=="
98 | },
99 | "@esbuild/sunos-x64@0.24.0": {
100 | "integrity": "sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA=="
101 | },
102 | "@esbuild/win32-arm64@0.24.0": {
103 | "integrity": "sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA=="
104 | },
105 | "@esbuild/win32-ia32@0.24.0": {
106 | "integrity": "sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw=="
107 | },
108 | "@esbuild/win32-x64@0.24.0": {
109 | "integrity": "sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA=="
110 | },
111 | "esbuild@0.24.0": {
112 | "integrity": "sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==",
113 | "dependencies": [
114 | "@esbuild/aix-ppc64",
115 | "@esbuild/android-arm",
116 | "@esbuild/android-arm64",
117 | "@esbuild/android-x64",
118 | "@esbuild/darwin-arm64",
119 | "@esbuild/darwin-x64",
120 | "@esbuild/freebsd-arm64",
121 | "@esbuild/freebsd-x64",
122 | "@esbuild/linux-arm",
123 | "@esbuild/linux-arm64",
124 | "@esbuild/linux-ia32",
125 | "@esbuild/linux-loong64",
126 | "@esbuild/linux-mips64el",
127 | "@esbuild/linux-ppc64",
128 | "@esbuild/linux-riscv64",
129 | "@esbuild/linux-s390x",
130 | "@esbuild/linux-x64",
131 | "@esbuild/netbsd-x64",
132 | "@esbuild/openbsd-arm64",
133 | "@esbuild/openbsd-x64",
134 | "@esbuild/sunos-x64",
135 | "@esbuild/win32-arm64",
136 | "@esbuild/win32-ia32",
137 | "@esbuild/win32-x64"
138 | ]
139 | }
140 | }
141 | }
142 |
--------------------------------------------------------------------------------