├── Makefile
├── static
└── index.js
├── package.json
├── README.md
├── src
├── VariableExtractor.ts
├── ReachElement.ts
├── DataElement.ts
└── HTMLParser.ts
├── tsconfig.json
├── index.ts
├── client
└── index.ts
└── bun.lock
/Makefile:
--------------------------------------------------------------------------------
1 | bundle:
2 | bun build ./client/index.js --outdir ./static --watch
3 |
4 | run:
5 | bun run --hot index.ts
6 |
7 |
--------------------------------------------------------------------------------
/static/index.js:
--------------------------------------------------------------------------------
1 | // client/index.ts
2 | class Reach {
3 | signals;
4 | constructor() {
5 | this.signals = {};
6 | }
7 | }
8 | var $ = new Reach;
9 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "reach",
3 | "module": "index.ts",
4 | "type": "module",
5 | "private": true,
6 | "devDependencies": {
7 | "@types/bun": "latest"
8 | },
9 | "peerDependencies": {
10 | "typescript": "^5"
11 | },
12 | "dependencies": {
13 | "@types/jsdom": "^21.1.7",
14 | "jsdom": "^26.0.0",
15 | "xerus": "github:phillip-england/xerus"
16 | }
17 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Reach
2 | The ultimate html preprocessor. Provide Reach with your HTML templates, and let it compile them into reactive web components.
3 |
4 | ## Moto
5 | 1. Put that 💩 in you're markup.
6 | 2. Stay language agnostic.
7 | 3. Keep the DX dead-simple.
8 |
9 | Reach is all about banking on *the future*. With the rising adoption of view transitions, developers will be able to lean more on the server without sacraficing UX.
10 |
11 | Write the HTML templates and let reach generate your web components.
12 |
13 | Dead simple.
--------------------------------------------------------------------------------
/src/VariableExtractor.ts:
--------------------------------------------------------------------------------
1 | export class TemplateVariable {
2 | raw: string;
3 | inner: string;
4 |
5 | constructor(raw: string) {
6 | this.raw = raw;
7 | this.inner = raw.slice(2, -2).trim();
8 | }
9 | }
10 |
11 | export class VariableExtractor {
12 | variables: TemplateVariable[];
13 | private constructor(variables: TemplateVariable[]) {
14 | this.variables = variables;
15 | }
16 | static fromStr(str: string): VariableExtractor {
17 | const matches = str.match(/{{\s*[^}]+\s*}}/g) || [];
18 | const variables = matches.map((m) => new TemplateVariable(m));
19 | return new VariableExtractor(variables);
20 | }
21 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | // Environment setup & latest features
4 | "lib": ["esnext", "DOM"],
5 | "target": "ESNext",
6 | "module": "ESNext",
7 | "moduleDetection": "force",
8 | "jsx": "react-jsx",
9 | "allowJs": true,
10 |
11 | // Bundler mode
12 | "moduleResolution": "bundler",
13 | "allowImportingTsExtensions": true,
14 | "verbatimModuleSyntax": true,
15 | "noEmit": true,
16 |
17 | // Best practices
18 | "strict": true,
19 | "skipLibCheck": true,
20 | "noFallthroughCasesInSwitch": true,
21 | "noUncheckedIndexedAccess": true,
22 |
23 | // Some stricter flags (disabled by default)
24 | "noUnusedLocals": false,
25 | "noUnusedParameters": false,
26 | "noPropertyAccessFromIndexSignature": false
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/index.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | import { HTTPContext, logger, Xerus } from "xerus/xerus";
4 | import { tmpl } from "./src/HTMLParser";
5 |
6 | let app = new Xerus()
7 |
8 | app.use(logger)
9 |
10 | app.get("/static/*", async (c: HTTPContext) => {
11 | return await c.file("." + c.path);
12 | });
13 |
14 | app.get('/', async (c: HTTPContext) => {
15 |
16 | let colors: string[] = ['blue', 'red', 'orange']
17 |
18 | return c.html(tmpl(`
19 |
20 |
21 |
{{ count }}
22 |
23 |
24 |
25 |
26 | ${colors.map((color) => {
27 | return `
28 |
${color}
29 | `
30 | })}
31 |
32 |
33 |
34 |
35 |
36 |
37 | `))
38 | })
39 |
40 | await app.listen()
--------------------------------------------------------------------------------
/src/ReachElement.ts:
--------------------------------------------------------------------------------
1 | export enum ReachElementType {
2 | DATA = '__data'
3 | }
4 |
5 | export class ReachElement {
6 | elm: HTMLElement
7 | reachAttrs: string[]
8 | constructor(elm: HTMLElement, reachAttrs: string[]) {
9 | this.elm = elm
10 | this.reachAttrs = reachAttrs
11 | }
12 | isTypeOf(str: ReachElementType): boolean {
13 | if (this.reachAttrs.includes(str)) {
14 | return true
15 | }
16 | return false
17 | }
18 | static isReachElement(elm: HTMLElement): [string[], boolean] {
19 | let validAttrs: ReachElementType[] = [ReachElementType.DATA]
20 | let foundAttrs: string[] = []
21 | for (let i = 0; i < validAttrs.length; i++) {
22 | let attr = validAttrs[i] as string
23 | if (elm.hasAttribute(attr)) {
24 | foundAttrs.push(attr)
25 | }
26 | }
27 | if (foundAttrs.length == 0) {
28 | return [foundAttrs, false]
29 | }
30 | return [foundAttrs, true]
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/DataElement.ts:
--------------------------------------------------------------------------------
1 | import type { ReachElement } from "./ReachElement"
2 | import { TemplateVariable, VariableExtractor } from "./VariableExtractor"
3 |
4 | export class DataElement {
5 | reachElm: ReachElement
6 | varExtractor: VariableExtractor
7 | dataAttr: string
8 | data: Record = {}
9 | dataJSON: any
10 | html: string = ''
11 | constructor(reachElm: ReachElement) {
12 | this.reachElm = reachElm
13 | this.html = this.reachElm.elm.outerHTML
14 | this.varExtractor = VariableExtractor.fromStr(this.reachElm.elm.outerHTML)
15 | this.dataAttr = this.reachElm.elm.getAttribute('__data') as string
16 | let json = JSON.parse(this.dataAttr)
17 | this.data = json
18 | for (let i = 0; i < this.varExtractor.variables.length; i++) {
19 | let variable = this.varExtractor.variables[i] as TemplateVariable
20 | let stateValue = this.data[variable.inner]
21 | this.html = this.html.replace(variable.raw, stateValue)
22 | }
23 | console.log(this.html)
24 | }
25 |
26 | }
--------------------------------------------------------------------------------
/src/HTMLParser.ts:
--------------------------------------------------------------------------------
1 | import { JSDOM } from 'jsdom';
2 |
3 | import { ReachElement, ReachElementType } from './ReachElement';
4 | import { DataElement } from './DataElement';
5 |
6 | export class HTMLParser {
7 | dom: JSDOM
8 | doc: Document
9 | elms: NodeListOf
10 | reachElms: ReachElement[] = []
11 | dataElms: DataElement[] = []
12 | constructor(str: string) {
13 | this.dom = new JSDOM(str)
14 | this.doc = this.dom.window.document
15 | this.elms = this.doc.querySelectorAll('*')
16 | for (let i = 0; i < this.elms.length; i++) {
17 | let elm = this.elms[i] as HTMLElement
18 | let [attrs, isReachElement] = ReachElement.isReachElement(elm)
19 | if (!isReachElement) {
20 | continue
21 | }
22 | let reachElm = new ReachElement(elm, attrs)
23 | this.reachElms.push(reachElm)
24 | for (const [k, v] of Object.entries(ReachElementType)) {
25 | switch (v) {
26 | case ReachElementType.DATA: {
27 | let dataElm = new DataElement(reachElm)
28 | this.dataElms.push(dataElm)
29 | }
30 | }
31 | }
32 | }
33 | }
34 | }
35 |
36 |
37 | export function tmpl(str: string): string {
38 | let parser = new HTMLParser(str)
39 | return str
40 | }
--------------------------------------------------------------------------------
/client/index.ts:
--------------------------------------------------------------------------------
1 |
2 | //==================================
3 | // util
4 | //==================================
5 |
6 | // generate a random string contains only letters and numbers at a given length
7 | function randomStr(length: number): string {
8 | const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
9 | let result = '';
10 | for (let i = 0; i < length; i++) {
11 | result += chars.charAt(Math.floor(Math.random() * chars.length));
12 | }
13 | return result;
14 | }
15 |
16 |
17 | //==================================
18 | // signal
19 | //==================================
20 |
21 | // instantiate a reactive value which can subscribe to changes
22 | class Signal {
23 | private value: any;
24 | private listeners: Set<(value: any) => void>;
25 | dataType: string;
26 |
27 | constructor(value: any, dataType: string) {
28 | this.value = value;
29 | this.listeners = new Set();
30 | this.dataType = dataType;
31 | }
32 |
33 | get() {
34 | switch (this.dataType) {
35 | case 'bool':
36 | return Boolean(this.value);
37 | case 'int':
38 | return Number(this.value);
39 | case 'str':
40 | return String(this.value);
41 | case 'obj':
42 | return Object(this.value);
43 | default:
44 | return String(this.value);
45 | }
46 | }
47 |
48 | set(newValue: any): void {
49 | if (!this.checkType(newValue)) {
50 | console.warn(
51 | `Signal.set() received value of type '${typeof newValue}' but expected '${this.dataType}'. Value:`,
52 | newValue
53 | );
54 | }
55 | this.value = newValue;
56 | this.listeners.forEach((listener) => listener(newValue));
57 | }
58 |
59 | subscribe(listener: (value: any) => void): () => void {
60 | if (typeof listener !== 'function') {
61 | console.warn(
62 | `Signal.subscribe() expected a function but received:`,
63 | listener
64 | );
65 | return () => {};
66 | }
67 |
68 | const wrappedListener = (value: any) => {
69 | if (!this.checkType(value)) {
70 | console.warn(
71 | `Signal listener received value of type '${typeof value}' but expected '${this.dataType}'. Value:`,
72 | value
73 | );
74 | }
75 | listener(value);
76 | };
77 |
78 | this.listeners.add(wrappedListener);
79 | return () => this.listeners.delete(wrappedListener);
80 | }
81 |
82 | private checkType(value: any): boolean {
83 | switch (this.dataType) {
84 | case 'bool':
85 | return typeof value === 'boolean';
86 | case 'int':
87 | return typeof value === 'number' && Number.isInteger(value);
88 | case 'str':
89 | return typeof value === 'string';
90 | case 'obj':
91 | return typeof value === 'object' && value !== null;
92 | default:
93 | return true;
94 | }
95 | }
96 | }
97 |
98 |
99 | //==================================
100 | // reach
101 | //==================================
102 |
103 | class Reach {
104 | signals: Record
105 | constructor() {
106 | this.signals = {}
107 |
108 | }
109 | }
110 |
111 | //==================================
112 | // reach component
113 | //==================================
114 |
115 |
116 |
117 | //==================================
118 | // running
119 | //==================================
120 |
121 |
122 | const $ = new Reach()
--------------------------------------------------------------------------------
/bun.lock:
--------------------------------------------------------------------------------
1 | {
2 | "lockfileVersion": 1,
3 | "workspaces": {
4 | "": {
5 | "name": "reach",
6 | "dependencies": {
7 | "@types/jsdom": "^21.1.7",
8 | "jsdom": "^26.0.0",
9 | "xerus": "github:phillip-england/xerus",
10 | },
11 | "devDependencies": {
12 | "@types/bun": "latest",
13 | },
14 | "peerDependencies": {
15 | "typescript": "^5",
16 | },
17 | },
18 | },
19 | "packages": {
20 | "@asamuzakjp/css-color": ["@asamuzakjp/css-color@3.1.1", "", { "dependencies": { "@csstools/css-calc": "^2.1.2", "@csstools/css-color-parser": "^3.0.8", "@csstools/css-parser-algorithms": "^3.0.4", "@csstools/css-tokenizer": "^3.0.3", "lru-cache": "^10.4.3" } }, "sha512-hpRD68SV2OMcZCsrbdkccTw5FXjNDLo5OuqSHyHZfwweGsDWZwDJ2+gONyNAbazZclobMirACLw0lk8WVxIqxA=="],
21 |
22 | "@csstools/color-helpers": ["@csstools/color-helpers@5.0.2", "", {}, "sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA=="],
23 |
24 | "@csstools/css-calc": ["@csstools/css-calc@2.1.2", "", { "peerDependencies": { "@csstools/css-parser-algorithms": "^3.0.4", "@csstools/css-tokenizer": "^3.0.3" } }, "sha512-TklMyb3uBB28b5uQdxjReG4L80NxAqgrECqLZFQbyLekwwlcDDS8r3f07DKqeo8C4926Br0gf/ZDe17Zv4wIuw=="],
25 |
26 | "@csstools/css-color-parser": ["@csstools/css-color-parser@3.0.8", "", { "dependencies": { "@csstools/color-helpers": "^5.0.2", "@csstools/css-calc": "^2.1.2" }, "peerDependencies": { "@csstools/css-parser-algorithms": "^3.0.4", "@csstools/css-tokenizer": "^3.0.3" } }, "sha512-pdwotQjCCnRPuNi06jFuP68cykU1f3ZWExLe/8MQ1LOs8Xq+fTkYgd+2V8mWUWMrOn9iS2HftPVaMZDaXzGbhQ=="],
27 |
28 | "@csstools/css-parser-algorithms": ["@csstools/css-parser-algorithms@3.0.4", "", { "peerDependencies": { "@csstools/css-tokenizer": "^3.0.3" } }, "sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A=="],
29 |
30 | "@csstools/css-tokenizer": ["@csstools/css-tokenizer@3.0.3", "", {}, "sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw=="],
31 |
32 | "@types/bun": ["@types/bun@1.2.5", "", { "dependencies": { "bun-types": "1.2.5" } }, "sha512-w2OZTzrZTVtbnJew1pdFmgV99H0/L+Pvw+z1P67HaR18MHOzYnTYOi6qzErhK8HyT+DB782ADVPPE92Xu2/Opg=="],
33 |
34 | "@types/jsdom": ["@types/jsdom@21.1.7", "", { "dependencies": { "@types/node": "*", "@types/tough-cookie": "*", "parse5": "^7.0.0" } }, "sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA=="],
35 |
36 | "@types/node": ["@types/node@22.13.11", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-iEUCUJoU0i3VnrCmgoWCXttklWcvoCIx4jzcP22fioIVSdTmjgoEvmAO/QPw6TcS9k5FrNgn4w7q5lGOd1CT5g=="],
37 |
38 | "@types/tough-cookie": ["@types/tough-cookie@4.0.5", "", {}, "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA=="],
39 |
40 | "@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="],
41 |
42 | "agent-base": ["agent-base@7.1.3", "", {}, "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw=="],
43 |
44 | "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
45 |
46 | "bun-types": ["bun-types@1.2.5", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-3oO6LVGGRRKI4kHINx5PIdIgnLRb7l/SprhzqXapmoYkFl5m4j6EvALvbDVuuBFaamB46Ap6HCUxIXNLCGy+tg=="],
47 |
48 | "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
49 |
50 | "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
51 |
52 | "cssstyle": ["cssstyle@4.3.0", "", { "dependencies": { "@asamuzakjp/css-color": "^3.1.1", "rrweb-cssom": "^0.8.0" } }, "sha512-6r0NiY0xizYqfBvWp1G7WXJ06/bZyrk7Dc6PHql82C/pKGUTKu4yAX4Y8JPamb1ob9nBKuxWzCGTRuGwU3yxJQ=="],
53 |
54 | "data-urls": ["data-urls@5.0.0", "", { "dependencies": { "whatwg-mimetype": "^4.0.0", "whatwg-url": "^14.0.0" } }, "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg=="],
55 |
56 | "debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="],
57 |
58 | "decimal.js": ["decimal.js@10.5.0", "", {}, "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw=="],
59 |
60 | "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
61 |
62 | "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
63 |
64 | "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
65 |
66 | "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
67 |
68 | "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
69 |
70 | "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
71 |
72 | "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="],
73 |
74 | "form-data": ["form-data@4.0.2", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "mime-types": "^2.1.12" } }, "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w=="],
75 |
76 | "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
77 |
78 | "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
79 |
80 | "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
81 |
82 | "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
83 |
84 | "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
85 |
86 | "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="],
87 |
88 | "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
89 |
90 | "html-encoding-sniffer": ["html-encoding-sniffer@4.0.0", "", { "dependencies": { "whatwg-encoding": "^3.1.1" } }, "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ=="],
91 |
92 | "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="],
93 |
94 | "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
95 |
96 | "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
97 |
98 | "is-potential-custom-element-name": ["is-potential-custom-element-name@1.0.1", "", {}, "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ=="],
99 |
100 | "jsdom": ["jsdom@26.0.0", "", { "dependencies": { "cssstyle": "^4.2.1", "data-urls": "^5.0.0", "decimal.js": "^10.4.3", "form-data": "^4.0.1", "html-encoding-sniffer": "^4.0.0", "http-proxy-agent": "^7.0.2", "https-proxy-agent": "^7.0.6", "is-potential-custom-element-name": "^1.0.1", "nwsapi": "^2.2.16", "parse5": "^7.2.1", "rrweb-cssom": "^0.8.0", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", "tough-cookie": "^5.0.0", "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^7.0.0", "whatwg-encoding": "^3.1.1", "whatwg-mimetype": "^4.0.0", "whatwg-url": "^14.1.0", "ws": "^8.18.0", "xml-name-validator": "^5.0.0" }, "peerDependencies": { "canvas": "^3.0.0" }, "optionalPeers": ["canvas"] }, "sha512-BZYDGVAIriBWTpIxYzrXjv3E/4u8+/pSG5bQdIYCbNCGOvsPkDQfTVLAIXAf9ETdCpduCVTkDe2NNZ8NIwUVzw=="],
101 |
102 | "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
103 |
104 | "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
105 |
106 | "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
107 |
108 | "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
109 |
110 | "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
111 |
112 | "nwsapi": ["nwsapi@2.2.19", "", {}, "sha512-94bcyI3RsqiZufXjkr3ltkI86iEl+I7uiHVDtcq9wJUTwYQJ5odHDeSzkkrRzi80jJ8MaeZgqKjH1bAWAFw9bA=="],
113 |
114 | "parse5": ["parse5@7.2.1", "", { "dependencies": { "entities": "^4.5.0" } }, "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ=="],
115 |
116 | "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
117 |
118 | "rrweb-cssom": ["rrweb-cssom@0.8.0", "", {}, "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw=="],
119 |
120 | "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
121 |
122 | "saxes": ["saxes@6.0.0", "", { "dependencies": { "xmlchars": "^2.2.0" } }, "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA=="],
123 |
124 | "symbol-tree": ["symbol-tree@3.2.4", "", {}, "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="],
125 |
126 | "tldts": ["tldts@6.1.85", "", { "dependencies": { "tldts-core": "^6.1.85" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-gBdZ1RjCSevRPFix/hpaUWeak2/RNUZB4/8frF1r5uYMHjFptkiT0JXIebWvgI/0ZHXvxaUDDJshiA0j6GdL3w=="],
127 |
128 | "tldts-core": ["tldts-core@6.1.85", "", {}, "sha512-DTjUVvxckL1fIoPSb3KE7ISNtkWSawZdpfxGxwiIrZoO6EbHVDXXUIlIuWympPaeS+BLGyggozX/HTMsRAdsoA=="],
129 |
130 | "tough-cookie": ["tough-cookie@5.1.2", "", { "dependencies": { "tldts": "^6.1.32" } }, "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A=="],
131 |
132 | "tr46": ["tr46@5.1.0", "", { "dependencies": { "punycode": "^2.3.1" } }, "sha512-IUWnUK7ADYR5Sl1fZlO1INDUhVhatWl7BtJWsIhwJ0UAK7ilzzIa8uIqOO/aYVWHZPJkKbEL+362wrzoeRF7bw=="],
133 |
134 | "typescript": ["typescript@5.8.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="],
135 |
136 | "undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
137 |
138 | "w3c-xmlserializer": ["w3c-xmlserializer@5.0.0", "", { "dependencies": { "xml-name-validator": "^5.0.0" } }, "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA=="],
139 |
140 | "webidl-conversions": ["webidl-conversions@7.0.0", "", {}, "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="],
141 |
142 | "whatwg-encoding": ["whatwg-encoding@3.1.1", "", { "dependencies": { "iconv-lite": "0.6.3" } }, "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ=="],
143 |
144 | "whatwg-mimetype": ["whatwg-mimetype@4.0.0", "", {}, "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg=="],
145 |
146 | "whatwg-url": ["whatwg-url@14.2.0", "", { "dependencies": { "tr46": "^5.1.0", "webidl-conversions": "^7.0.0" } }, "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw=="],
147 |
148 | "ws": ["ws@8.18.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w=="],
149 |
150 | "xerus": ["xerus@github:phillip-england/xerus#071edd1", { "peerDependencies": { "typescript": "^5.0.0" } }, "Phillip-England-xerus-071edd1"],
151 |
152 | "xml-name-validator": ["xml-name-validator@5.0.0", "", {}, "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg=="],
153 |
154 | "xmlchars": ["xmlchars@2.2.0", "", {}, "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="],
155 | }
156 | }
157 |
--------------------------------------------------------------------------------