revojs 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,118 +1,226 @@
1
- import defu, { defu as defu$1 } from "defu";
1
+ import { defu } from "defu";
2
2
  import { h } from "revojs/jsx-runtime";
3
3
 
4
4
  //#region src/app/index.ts
5
- const getContent = async (key) => {
6
- return await import("#virtual/content").then((module) => module.index[key]);
5
+ const getRoutes = async () => {
6
+ return await import("#virtual/routes").then((module) => module.index);
7
+ };
8
+ const getAssets = async () => {
9
+ return await import("#virtual/assets").then((module) => module.index);
7
10
  };
8
11
  const createApp = (config) => {
9
12
  return {
10
- config: defu$1(config, {
13
+ config: defu(config, {
11
14
  client: { entry: "./index.html" },
12
15
  server: { entry: "revojs/presets/node" },
13
- content: {
14
- static: {
15
- type: "static",
16
- source: "./dist/client",
17
- include: ["**/*.*"]
18
- },
19
- routes: {
20
- type: "asset",
21
- source: "./routes",
22
- include: [
23
- "**/*.js",
24
- "**/*.ts",
25
- "**/*.jsx",
26
- "**/*.tsx"
27
- ]
28
- }
29
- },
30
- markdown: {}
16
+ markdown: {},
17
+ dev: { middleware: [] }
31
18
  }),
32
19
  virtuals: {}
33
20
  };
34
21
  };
35
22
 
36
23
  //#endregion
37
- //#region src/event/index.ts
38
- const createEvent = (request, context) => {
39
- return {
40
- request,
41
- response: { headers: new Headers() },
42
- context
43
- };
24
+ //#region src/types/index.ts
25
+ const descriptor = (descriptor$1) => {
26
+ return (typeof descriptor$1 === "object" ? descriptor$1.key : descriptor$1).toString();
44
27
  };
45
- const sendText = (event, text) => {
46
- return new Response(text, event.response);
47
- };
48
- const sendHtml = (event, text) => {
49
- setHeader(event, "Content-Type", "text/html");
50
- return new Response(text, event.response);
28
+
29
+ //#endregion
30
+ //#region src/hooks/index.ts
31
+ var Hooks = class {
32
+ hooks;
33
+ constructor() {
34
+ this.hooks = new Map();
35
+ }
36
+ on = (scope, input, invoke) => {
37
+ const key = descriptor(input);
38
+ const invokes = this.hooks.get(key) ?? new Set();
39
+ invokes.add(invoke);
40
+ this.hooks.set(key, invokes);
41
+ return scope.dispose.push(() => invokes.delete(invoke));
42
+ };
43
+ dispatch = (input, value) => {
44
+ const invokes = this.hooks.get(descriptor(input));
45
+ if (invokes) for (const invoke of invokes) invoke(value);
46
+ };
51
47
  };
52
- const sendJson = (event, value) => {
53
- return new Response(JSON.stringify(value), event.response);
48
+ const defineHook = (description) => {
49
+ return { key: Symbol(description) };
54
50
  };
55
- const setHeader = (event, name, value) => {
56
- event.response.headers.set(name, value);
51
+
52
+ //#endregion
53
+ //#region src/jsx/index.ts
54
+ const svgElements = new Set([
55
+ "altGlyph",
56
+ "altGlyphDef",
57
+ "altGlyphItem",
58
+ "animate",
59
+ "animateColor",
60
+ "animateMotion",
61
+ "animateTransform",
62
+ "circle",
63
+ "clipPath",
64
+ "color-profile",
65
+ "cursor",
66
+ "defs",
67
+ "desc",
68
+ "ellipse",
69
+ "feBlend",
70
+ "feColorMatrix",
71
+ "feComponentTransfer",
72
+ "feComposite",
73
+ "feConvolveMatrix",
74
+ "feDiffuseLighting",
75
+ "feDisplacementMap",
76
+ "feDistantLight",
77
+ "feDropShadow",
78
+ "feFlood",
79
+ "feFuncA",
80
+ "feFuncB",
81
+ "feFuncG",
82
+ "feFuncR",
83
+ "feGaussianBlur",
84
+ "feImage",
85
+ "feMerge",
86
+ "feMergeNode",
87
+ "feMorphology",
88
+ "feOffset",
89
+ "fePointLight",
90
+ "feSpecularLighting",
91
+ "feSpotLight",
92
+ "feTile",
93
+ "feTurbulence",
94
+ "filter",
95
+ "font",
96
+ "font-face",
97
+ "font-face-format",
98
+ "font-face-name",
99
+ "font-face-src",
100
+ "font-face-uri",
101
+ "foreignObject",
102
+ "g",
103
+ "glyph",
104
+ "glyphRef",
105
+ "hkern",
106
+ "image",
107
+ "line",
108
+ "linearGradient",
109
+ "marker",
110
+ "mask",
111
+ "metadata",
112
+ "missing-glyph",
113
+ "mpath",
114
+ "path",
115
+ "pattern",
116
+ "polygon",
117
+ "polyline",
118
+ "radialGradient",
119
+ "rect",
120
+ "set",
121
+ "stop",
122
+ "svg",
123
+ "switch",
124
+ "symbol",
125
+ "text",
126
+ "textPath",
127
+ "tref",
128
+ "tspan",
129
+ "use",
130
+ "view",
131
+ "vkern"
132
+ ]);
133
+ const namespace = (tag) => {
134
+ return svgElements.has(tag) ? "http://www.w3.org/2000/svg" : "http://www.w3.org/1999/xhtml";
57
135
  };
58
136
 
59
137
  //#endregion
60
138
  //#region src/signals/index.ts
139
+ var Scope = class {
140
+ dispose;
141
+ constructor() {
142
+ this.dispose = new Array();
143
+ }
144
+ stop() {
145
+ while (this.dispose.length) this.dispose.pop()?.(this);
146
+ }
147
+ };
148
+ var Compute = class extends Scope {
149
+ scope;
150
+ invoke;
151
+ constructor(scope, invoke) {
152
+ super();
153
+ this.scope = scope;
154
+ this.invoke = invoke;
155
+ }
156
+ run() {
157
+ this.stop();
158
+ return this.invoke(this);
159
+ }
160
+ };
61
161
  var Handler = class Handler {
62
- get = (target, path) => {
63
- if (runningCompute) {
64
- const effects = targets.get(target) ?? new Map();
65
- const set = effects.get(path) ?? new Set();
66
- effects.set(path, set.add(runningCompute));
67
- targets.set(target, effects);
68
- runningCompute.previous?.cleanUps.push((compute) => set.delete(compute));
162
+ get(target, key) {
163
+ const compute = activeCompute;
164
+ if (compute) {
165
+ const computes = targets.get(target) ?? new Map();
166
+ const set = computes.get(key) ?? new Set();
167
+ computes.set(key, set.add(compute));
168
+ targets.set(target, computes);
169
+ compute.scope.dispose.push(() => {
170
+ compute.stop();
171
+ set.delete(compute);
172
+ });
69
173
  }
70
- const value = Reflect.get(target, path);
71
- if (value instanceof Object) return new Proxy(value, new Handler());
72
- return value;
73
- };
74
- set = (target, path, value) => {
75
- const result = Reflect.set(target, path, value);
76
- for (const effect of targets.get(target)?.get(path) ?? []) {
77
- while (effect.cleanUps.length) effect.cleanUps.pop()?.(effect);
78
- effect.invoke();
174
+ const value = Reflect.get(target, key);
175
+ if (value) {
176
+ if (typeof value === "function") return value.bind(target);
177
+ if (typeof value === "object") return new Proxy(value, new Handler());
79
178
  }
179
+ return value;
180
+ }
181
+ set(target, key, value) {
182
+ const result = Reflect.set(target, key, value);
183
+ for (const compute of targets.get(target)?.get(key) ?? []) compute.run();
80
184
  return result;
81
- };
185
+ }
82
186
  };
83
- const createState = (value) => {
187
+ function createState(value) {
84
188
  return new Proxy({ value }, new Handler());
85
- };
86
- const createCompute = (invoke) => {
87
- return runCompute({
88
- invoke,
89
- cleanUps: []
90
- });
91
- };
92
- const runCompute = (compute) => {
93
- compute.previous = runningCompute;
94
- runningCompute = compute;
189
+ }
190
+ function createCompute(scope, invoke) {
191
+ let previous = activeCompute;
192
+ activeCompute = new Compute(scope, invoke);
95
193
  try {
96
- return compute.invoke();
194
+ return invoke(activeCompute);
97
195
  } finally {
98
- runningCompute = compute.previous;
99
- }
100
- };
101
- const fromValue = (value) => {
102
- if (value) {
103
- if (value instanceof Function) return fromValue(value());
104
- if (value instanceof Object) return value.value;
196
+ activeCompute = previous;
105
197
  }
198
+ }
199
+ function createMemo(scope, invoke) {
200
+ let state;
201
+ const compute = createCompute(scope, () => {
202
+ const value = invoke();
203
+ if (typeof state === "object") state.value = value;
204
+ return value;
205
+ });
206
+ state = createState(compute);
207
+ return state;
208
+ }
209
+ function fromValue(value) {
210
+ if (value instanceof Function) return fromValue(value());
106
211
  return value;
107
- };
108
- const onCleanUp = (cleanUp) => {
109
- runningCompute?.cleanUps.push(cleanUp);
110
- };
212
+ }
213
+ let activeCompute;
111
214
  const targets = new WeakMap();
112
- let runningCompute;
113
215
 
114
216
  //#endregion
115
217
  //#region src/html/index.ts
218
+ const isTemplate = (value) => {
219
+ return "tag" in value && "attributes" in value && "children" in value;
220
+ };
221
+ const defineContext = (description) => {
222
+ return { key: Symbol(description) };
223
+ };
116
224
  const createElement = (input, attributes, ...children) => {
117
225
  return {
118
226
  tag: typeof input === "function" ? input.$name : input,
@@ -132,58 +240,107 @@ const toString = (slot) => {
132
240
  default: return "";
133
241
  }
134
242
  };
135
- const slotToString = async (slot) => {
136
- if (Array.isArray(slot)) return await Promise.all(slot.map(slotToString)).then((chunks) => chunks.join(" "));
137
- if (typeof slot === "number" || typeof slot === "bigint" || typeof slot === "boolean" || typeof slot === "string" || typeof slot === "symbol") return toString(slot);
138
- if (typeof slot === "function") return await slotToString(await slot());
139
- if (typeof slot === "object") {
140
- if ("tag" in slot && "attributes" in slot && "children" in slot) {
141
- const Constructor = components.get(slot.tag);
142
- const prefix = [slot.tag, ...Object.entries(slot.attributes ?? {}).map(([name, value]) => {
143
- if (!name.startsWith("on")) return `${name}="${toString(value)}"`;
144
- })].filter(Boolean).join(" ");
145
- const children = await slotToString(slot.children);
146
- if (Constructor) {
147
- const component = new Constructor(slot.attributes);
148
- const template = await slotToString(await component.setup());
149
- if (component.shadowRoot) return `<${prefix}> <template shadowRootMode="${component.shadowRoot.mode}"> ${template} </template> ${children} </${slot.tag}>`;
150
- return `<${prefix}> ${template} ${children} </${slot.tag}>`;
243
+ const toFragment = (nodes) => {
244
+ const fragment = document.createDocumentFragment();
245
+ fragment.replaceChildren(...nodes);
246
+ return fragment;
247
+ };
248
+ const renderToString = async (slot, context) => {
249
+ if (slot) {
250
+ if (typeof slot === "number" || typeof slot === "bigint" || typeof slot === "boolean" || typeof slot === "string" || typeof slot === "symbol") return slot.toString();
251
+ if (typeof slot === "function") return await renderToString(await slot(), context);
252
+ if (typeof slot === "object") {
253
+ if (Array.isArray(slot)) return await Promise.all(slot.map((slot$1) => renderToString(slot$1, context))).then((chunks) => chunks.join(""));
254
+ if (isTemplate(slot)) {
255
+ const customElement = components.get(slot.tag);
256
+ const prefix = Object.entries(slot.attributes).reduce((chunks, [name, value]) => {
257
+ if (!name.startsWith("on")) chunks.push(`${name}='${toString(value)}'`);
258
+ return chunks;
259
+ }, [slot.tag]).join(" ");
260
+ const children = await renderToString(slot.children, context);
261
+ if (customElement) {
262
+ const element = new customElement(slot.attributes, context);
263
+ const template = await renderToString(await element.setup(), context);
264
+ if (element.shadowRoot) {
265
+ const shadow = await renderToString({
266
+ tag: "template",
267
+ attributes: { shadowRootMode: element.shadowRoot.mode },
268
+ children: [template]
269
+ }, context);
270
+ return `<${prefix}>` + shadow + children + `</${slot.tag}>`;
271
+ }
272
+ return `<${prefix}>` + template + children + `</${slot.tag}>`;
273
+ }
274
+ return `<${prefix}>` + children + `</${slot.tag}>`;
151
275
  }
152
- return `<${prefix}> ${children} </${slot.tag}>`;
276
+ return JSON.stringify(slot);
153
277
  }
154
- return toString(slot);
155
278
  }
156
- return "";
157
- };
158
- const slotToNode = async (slot) => {
159
- if (Array.isArray(slot)) {
160
- const fragment = document.createDocumentFragment();
161
- fragment.replaceChildren(...await Promise.all(slot.map(slotToNode)));
162
- return fragment;
163
- }
164
- if (typeof slot === "number" || typeof slot === "bigint" || typeof slot === "boolean" || typeof slot === "string" || typeof slot === "symbol") return document.createTextNode(slot.toString());
165
- if (typeof slot === "function") {
166
- let oldNode;
167
- return await createCompute(async () => {
168
- const node = await slotToNode(await slot());
169
- oldNode?.parentNode?.replaceChild(node, oldNode);
170
- oldNode = node;
171
- return node;
172
- });
173
- }
174
- if (typeof slot === "object") {
175
- if ("tag" in slot && "attributes" in slot && "children" in slot) {
176
- const node = document.createElement(slot.tag);
177
- for (const key in slot.attributes) {
178
- const value = slot.attributes[key];
179
- if (value) if (key.startsWith("on")) {
180
- const event = key.substring(2).toLowerCase();
181
- node.addEventListener(event, value);
182
- onCleanUp(() => node.removeEventListener(event, value));
183
- } else node.setAttribute(key, toString(value));
279
+ return "<!---->";
280
+ };
281
+ const renderToNode = async (scope, slot) => {
282
+ if (slot) {
283
+ if (typeof slot === "number" || typeof slot === "bigint" || typeof slot === "boolean" || typeof slot === "string" || typeof slot === "symbol") return document.createTextNode(slot.toString());
284
+ if (typeof slot === "function") {
285
+ let node;
286
+ await createCompute(scope, async (scope$1) => {
287
+ let next;
288
+ let input = slot;
289
+ let parentNode = Array.isArray(node) ? node.at(0)?.parentNode : node?.parentNode;
290
+ while (typeof input === "function") input = await input();
291
+ if (Array.isArray(input)) if (input.length) next = await Promise.all(input.map((child) => renderToNode(scope$1, child)));
292
+ else next = document.createComment("");
293
+ else next = await renderToNode(scope$1, input);
294
+ if (Array.isArray(next)) if (Array.isArray(node)) {
295
+ if (parentNode) {
296
+ const range = document.createRange();
297
+ const firstNode = node.at(0);
298
+ if (firstNode) range.setStartBefore(firstNode);
299
+ const lastNode = node.at(-1);
300
+ if (lastNode) range.setEndAfter(lastNode);
301
+ range.deleteContents();
302
+ for (const child of next) parentNode?.appendChild(child);
303
+ }
304
+ } else node?.parentNode?.replaceChild(toFragment(next), node);
305
+ else if (Array.isArray(node)) {
306
+ if (parentNode) {
307
+ const range = document.createRange();
308
+ const firstNode = node.at(0);
309
+ if (firstNode) range.setStartBefore(firstNode);
310
+ const lastNode = node.at(-1);
311
+ if (lastNode) range.setEndAfter(lastNode);
312
+ range.deleteContents();
313
+ parentNode?.appendChild(next);
314
+ }
315
+ } else node?.parentNode?.replaceChild(next, node);
316
+ node = next;
317
+ });
318
+ if (Array.isArray(node)) return toFragment(node);
319
+ return node ?? document.createComment("");
320
+ }
321
+ if (typeof slot === "object") {
322
+ if (Array.isArray(slot)) {
323
+ if (slot.length) {
324
+ const children = await Promise.all(slot.map((child) => renderToNode(scope, child)));
325
+ return toFragment(children);
326
+ }
327
+ return document.createComment("");
328
+ }
329
+ if (isTemplate(slot)) {
330
+ const element = document.createElementNS(namespace(slot.tag), slot.tag);
331
+ for (const name in slot.attributes) {
332
+ const value = slot.attributes[name];
333
+ if (value) if (name.startsWith("on") && typeof value === "function") {
334
+ const event = name.substring(2).toLowerCase();
335
+ const controller = new AbortController();
336
+ element.addEventListener(event, value, { signal: controller.signal });
337
+ scope.dispose.push(() => controller.abort());
338
+ } else createCompute(scope, () => element.setAttribute(name, toString(value)));
339
+ }
340
+ element.replaceChildren(await renderToNode(scope, slot.children));
341
+ return element;
184
342
  }
185
- node.replaceChildren(await slotToNode(slot.children));
186
- return node;
343
+ return document.createTextNode(JSON.stringify(slot));
187
344
  }
188
345
  }
189
346
  return document.createComment("");
@@ -193,47 +350,64 @@ const defineComponent = (options) => {
193
350
  static $name = options.name;
194
351
  static $events = options.events ?? {};
195
352
  static $attributes = options.attributes ?? {};
353
+ scope;
354
+ hooks;
196
355
  events;
197
356
  attributes;
198
357
  shadowRoot;
199
- constructor(input) {
358
+ context;
359
+ host;
360
+ constructor(input, context, host) {
361
+ this.scope = new Scope();
362
+ this.hooks = new Hooks();
200
363
  this.events = Object.keys(options.events ?? {}).reduce((output, name) => {
201
364
  Reflect.set(output, name, input?.[name], output);
202
365
  return output;
203
366
  }, {});
204
- this.attributes = Object.entries(options.attributes ?? {}).reduce((output, [name, attribute]) => {
205
- Reflect.set(output.value, name, input?.[name] ?? attribute.default, output.value);
206
- return output;
367
+ this.attributes = Object.entries(options.attributes ?? {}).reduce((attributes, [name, attribute]) => {
368
+ Reflect.set(attributes.value, name, fromValue(input?.[name]) ?? attribute.default, attributes.value);
369
+ return attributes;
207
370
  }, createState({}));
208
371
  this.shadowRoot = options.shadowRoot ?? { mode: "open" };
372
+ this.context = context ?? {};
373
+ this.host = host;
209
374
  }
375
+ getContext = (input) => {
376
+ return this.context[descriptor(input)] ?? {};
377
+ };
378
+ setContext = (input, value) => {
379
+ this.context[descriptor(input)] = value;
380
+ };
381
+ onMounted = (invoke) => {
382
+ return this.hooks.on(this.scope, MOUNTED_HOOK, invoke);
383
+ };
210
384
  setup = () => options.setup(this);
211
385
  });
212
386
  };
213
387
  const toCustomElement = (component) => {
214
388
  return class extends HTMLElement {
389
+ static formAssociated = true;
215
390
  component;
216
391
  constructor() {
217
392
  super();
218
- this.component = new component();
393
+ this.component = new component(undefined, undefined, this);
219
394
  }
220
395
  async connectedCallback() {
221
- let previous = activeElement;
222
- activeElement = this;
223
- try {
224
- const shadow = this.component.shadowRoot ? this.attachShadow(this.component.shadowRoot) : this;
225
- for (const [name, event] of Object.entries(component.$events)) Reflect.set(this.component.events, name, (value) => {
226
- this.dispatchEvent(new CustomEvent(name.substring(2).toLowerCase(), {
227
- ...event,
228
- detail: value
229
- }));
230
- }, this.component.events);
231
- shadow.replaceChildren(await slotToNode(await this.component.setup()));
232
- } finally {
233
- activeElement = previous;
234
- }
396
+ const shadow = this.component.shadowRoot ? this.attachShadow(this.component.shadowRoot) : this;
397
+ const parentNode = getCustomElement(this.parentNode);
398
+ for (const [name, event] of Object.entries(component.$events)) Reflect.set(this.component.events, name, (value) => {
399
+ if (value instanceof Event) return;
400
+ this.dispatchEvent(new CustomEvent(name.substring(2).toLowerCase(), {
401
+ ...event,
402
+ detail: value
403
+ }));
404
+ }, this.component.events);
405
+ if (parentNode) for (const key in parentNode.component.context) this.component.setContext(key, parentNode.component.context[key]);
406
+ shadow.replaceChildren(await renderToNode(this.component.scope, await this.component.setup()));
407
+ this.component.hooks.dispatch(MOUNTED_HOOK, this);
235
408
  }
236
- attributeChangedCallback(name, _, value) {
409
+ attributeChangedCallback(name, oldValue, value) {
410
+ if (value === oldValue) return;
237
411
  const attribute = component.$attributes?.[name];
238
412
  if (attribute) {
239
413
  let convertedValue;
@@ -245,7 +419,7 @@ const toCustomElement = (component) => {
245
419
  convertedValue = Number(value);
246
420
  break;
247
421
  case Boolean:
248
- convertedValue = Boolean(value);
422
+ convertedValue = value.toLowerCase() === "true";
249
423
  break;
250
424
  case Object:
251
425
  convertedValue = JSON.parse(value);
@@ -254,25 +428,19 @@ const toCustomElement = (component) => {
254
428
  Reflect.set(this.component.attributes.value, name, convertedValue ?? attribute.default, this.component.attributes.value);
255
429
  }
256
430
  }
431
+ disconnectedCallback() {
432
+ this.component.scope.stop();
433
+ }
257
434
  static get observedAttributes() {
258
435
  return Object.keys(component.$attributes ?? {});
259
436
  }
260
437
  };
261
438
  };
262
439
  const registerComponent = (component) => {
263
- components.set(component.$name, component);
440
+ if (isServer()) components.set(component.$name, component);
264
441
  if (isClient()) customElements.define(component.$name, toCustomElement(component));
265
442
  return component;
266
443
  };
267
- const isClient = () => {
268
- return typeof window !== "undefined";
269
- };
270
- const isServer = () => {
271
- return typeof window === "undefined";
272
- };
273
- const addStyles = (...styles) => {
274
- activeElement?.shadowRoot?.adoptedStyleSheets.push(...styles);
275
- };
276
444
  const getGlobalStyles = () => {
277
445
  return Array.from(isServer() ? [] : document.styleSheets).map((style) => {
278
446
  const sheet = new CSSStyleSheet();
@@ -281,21 +449,94 @@ const getGlobalStyles = () => {
281
449
  return sheet;
282
450
  });
283
451
  };
284
- let globalStyles;
285
- let activeElement;
452
+ const getCustomElement = (node) => {
453
+ if (node) {
454
+ if ("component" in node) return node;
455
+ return getCustomElement(node.parentNode);
456
+ }
457
+ };
458
+ const isClient = () => typeof window !== "undefined";
459
+ const isServer = () => typeof window === "undefined";
460
+ const preventDefault = (event) => event.preventDefault();
461
+ const stopPropagation = (event) => event.stopPropagation();
462
+ const stopImmediatePropagation = (event) => event.stopImmediatePropagation();
463
+ const MOUNTED_HOOK = defineHook("MOUNTED_HOOK");
286
464
  const components = new Map();
287
465
 
288
466
  //#endregion
289
467
  //#region src/http/index.ts
290
- const mimeTypes = {
291
- css: "text/css",
292
- js: "text/javascript",
293
- txt: "text/plain"
468
+ const createEvent = (request, context) => {
469
+ return {
470
+ request,
471
+ response: { headers: new Headers() },
472
+ context
473
+ };
474
+ };
475
+ const sendText = (event, text) => {
476
+ event.response.headers.set("Content-Type", "text/plain");
477
+ return new Response(text, event.response);
478
+ };
479
+ const sendHtml = (event, text) => {
480
+ event.response.headers.set("Content-Type", "text/html");
481
+ return new Response(text, event.response);
482
+ };
483
+ const sendJson = (event, value) => {
484
+ event.response.headers.set("Content-Type", "application/json");
485
+ return new Response(JSON.stringify(value), event.response);
486
+ };
487
+ const sendRedirect = (event, path) => {
488
+ event.response.status = 302;
489
+ event.response.headers.set("Location", path);
490
+ return new Response(null, event.response);
491
+ };
492
+ const sendBadRequest = (event, text) => {
493
+ event.response.status = 400;
494
+ return new Response(text, event.response);
495
+ };
496
+ const sendUnauthorized = (event) => {
497
+ event.response.status = 401;
498
+ return new Response(null, event.response);
499
+ };
500
+ const getRequestUrl = (event) => {
501
+ return new URL(event instanceof Request ? event.url : event.request.url);
502
+ };
503
+ const getCookies = (event) => {
504
+ const cookies = event.request.headers.get("Cookie")?.split("; ") ?? [];
505
+ return cookies.reduce((result, cookie) => {
506
+ const [name, value] = cookie.split("=");
507
+ if (name && value) result[name] = decodeURIComponent(value);
508
+ return result;
509
+ }, {});
510
+ };
511
+ const getSetCookies = (event) => {
512
+ const cookies = event.request.headers.getSetCookie();
513
+ return cookies.reduce((result, cookie) => {
514
+ const [name, value] = cookie.split("=");
515
+ if (name && value) result[name] = decodeURIComponent(value);
516
+ return result;
517
+ }, {});
518
+ };
519
+ const setCookie = (event, name, value, options) => {
520
+ let cookie = name + "=" + encodeURIComponent(value);
521
+ if (options?.domain) cookie += `; Domain=${options.domain}`;
522
+ if (options?.expires) cookie += `; Expires=${options.expires.toUTCString()}`;
523
+ if (options?.httpOnly) cookie += `; HttpOnly`;
524
+ if (options?.maxAge) cookie += `; Max-Age=${options.maxAge}`;
525
+ if (options?.path) cookie += `; Path=${options.path}`;
526
+ if (options?.priority) cookie += `; Priority=${options.priority}`;
527
+ if (options?.sameSite) cookie += `; SameSite=${options.sameSite}`;
528
+ if (options?.secure) cookie += `; Secure`;
529
+ event.response.headers.append("Set-Cookie", cookie);
294
530
  };
295
531
  const getMimeType = (file) => {
296
532
  const extension = /\.([a-zA-Z0-9]+?)$/.exec(file)?.at(1);
297
533
  return mimeTypes[extension ?? ""] ?? "text/plain";
298
534
  };
535
+ const mimeTypes = {
536
+ css: "text/css",
537
+ js: "text/javascript",
538
+ txt: "text/plain"
539
+ };
299
540
 
300
541
  //#endregion
301
542
  //#region src/markdown/index.ts
@@ -435,63 +676,96 @@ var Radix = class Radix {
435
676
  const defineRoute = (route) => {
436
677
  return route;
437
678
  };
438
- const defineSocket = (socket) => {
439
- return socket;
440
- };
441
679
  const fileName = (path) => {
442
680
  return path.split("/").pop()?.split(".").slice(0, -1).join(".");
443
681
  };
444
682
  const toPath = (value) => {
445
- return value.replace(/\/index/, "").replace(/index/, "").replace(/\.(js|ts|jsx|tsx)$/, "").replaceAll(/\[(.*)\]/g, (_, name) => ":" + name);
683
+ const path = (value.startsWith("/") ? value : "/" + value).replaceAll(/\/index/g, "").replaceAll(/\[(.*)\]/g, (_, name) => ":" + name);
684
+ const route = path.startsWith("/") ? path : "/" + path;
685
+ const split = route.split(".");
686
+ return split.length === 3 ? [split.at(0), split.at(1)] : [split.at(0)];
687
+ };
688
+ const $fetch = async (event, input, init) => {
689
+ let response;
690
+ if (event) {
691
+ const url = new URL(input.toString(), event.request.url);
692
+ response = await (await import("#virtual/runtime")).runtime.fetch(new Request(url, init), event.context);
693
+ } else response = await fetch(input, init);
694
+ if (response.ok === false) throw response;
695
+ switch (response.headers.get("Content-Type")) {
696
+ case "application/json": return response.json();
697
+ default: return response;
698
+ }
699
+ };
700
+ const getVariables = (event) => {
701
+ return event ? event.context.variables : import.meta.env;
446
702
  };
447
703
  const createRuntime = async () => {
448
704
  const radix = new Radix();
449
- const assets = await getContent("static");
450
- for (const path in assets) radix.insert("GET/" + path, defineRoute({ fetch: async (event) => {
451
- setHeader(event, "Content-Type", getMimeType(path));
452
- return sendText(event, await assets[path]());
453
- } }));
454
- const routes = await getContent("routes");
455
- for (const path in routes) radix.insert("GET/" + toPath(path), defineRoute({ fetch: async (event) => {
456
- const route = await routes[path]();
457
- if (route) {
458
- if ("message" in route) return sendText(event, "WebSocket");
459
- if ("$name" in route) {
705
+ const middlewares = new Array();
706
+ const routes = await getRoutes();
707
+ for (const path in routes) {
708
+ const [name, method] = toPath(path);
709
+ radix.insert((method ?? "GET").toUpperCase() + name, defineRoute({ fetch: async (event) => {
710
+ const route = await routes[path]?.();
711
+ if (typeof route === "object") return route.fetch(event);
712
+ if (route) {
460
713
  const slot = await import("#virtual/client").then((module) => module.index);
461
- return sendHtml(event, await slotToString(slot));
714
+ return sendHtml(event, await renderToString(slot, { [descriptor(RUNTIME_CONTEXT)]: { event } }));
462
715
  }
463
- return route.fetch(event);
464
- }
716
+ } }));
717
+ }
718
+ const assets = await getAssets();
719
+ for (const path in assets) radix.insert("GET/" + path, defineRoute({ fetch: async (event) => {
720
+ event.response.headers.set("Content-Type", getMimeType(path));
721
+ return new Response(await assets[path]?.(), event.response);
465
722
  } }));
466
- return { fetch: async (request, context) => {
467
- const url = new URL(request.url);
468
- const { value: route, inputs } = radix.match(request.method + url.pathname);
469
- activeEvent = createEvent(request, {
470
- ...context,
471
- inputs
472
- });
473
- try {
474
- return await route?.fetch(activeEvent) ?? sendText(activeEvent, "Not found");
475
- } finally {
476
- activeEvent = undefined;
723
+ const invoke = (event, next, index) => {
724
+ return middlewares.at(index)?.(event, () => invoke(event, next, index + 1)) ?? next(event);
725
+ };
726
+ return {
727
+ radix,
728
+ middlewares,
729
+ fetch: async (request, context) => {
730
+ const url = getRequestUrl(request);
731
+ const { value: route, inputs } = radix.match(request.method + url.pathname);
732
+ const event = createEvent(request, {
733
+ ...context,
734
+ inputs
735
+ });
736
+ try {
737
+ if (route) {
738
+ const response = await invoke(event, route.fetch, 0);
739
+ if (response) return response;
740
+ }
741
+ return sendText(event, "Not found");
742
+ } catch (response) {
743
+ if (response instanceof Response) return response;
744
+ throw response;
745
+ }
477
746
  }
478
- } };
747
+ };
479
748
  };
480
- let activeEvent;
749
+ const RUNTIME_CONTEXT = defineContext("RUNTIME_CONTEXT");
481
750
 
482
751
  //#endregion
483
752
  //#region src/router/index.tsx
484
753
  const Outlet = defineComponent({
485
754
  name: "x-outlet",
486
- setup: async () => {
755
+ shadowRoot: false,
756
+ setup: async ({ scope, getContext }) => {
757
+ const { event } = getContext(RUNTIME_CONTEXT);
487
758
  const radix = new Radix();
488
- const routes = await getContent("routes");
489
- for (const path in routes) radix.insert("/" + toPath(path), routes[path]);
490
- const url = createState(new URL(activeEvent ? activeEvent.request.url : window.location.href));
759
+ const routes = await getRoutes();
760
+ for (const path in routes) {
761
+ const [name] = toPath(path);
762
+ if (name) radix.insert(name, routes[path]);
763
+ }
764
+ const url = createState(new URL(event ? event.request.url : window.location.href));
491
765
  if (isClient()) {
492
766
  const controller = new AbortController();
493
767
  window.addEventListener("popstate", () => url.value = new URL(window.location.href), { signal: controller.signal });
494
- onCleanUp(() => controller.abort());
768
+ scope.dispose.push(() => controller.abort());
495
769
  }
496
770
  return async () => {
497
771
  const { value, inputs } = radix.match(url.value.pathname);
@@ -513,4 +787,4 @@ const anchorNavigate = (event) => {
513
787
  };
514
788
 
515
789
  //#endregion
516
- export { Handler, Outlet, Radix, activeElement, activeEvent, addStyles, anchorNavigate, components, createApp, createCompute, createElement, createEvent, createRuntime, createState, defineComponent, defineRoute, defineSocket, fileName, fromValue, getContent, getGlobalStyles, getMimeType, globalStyles, isClient, isServer, markdownToSlot, navigate, onCleanUp, registerComponent, runCompute, runningCompute, sendHtml, sendJson, sendText, setHeader, slotToNode, slotToString, targets, toCustomElement, toPath, toString };
790
+ export { $fetch, Compute, Handler, Hooks, MOUNTED_HOOK, Outlet, RUNTIME_CONTEXT, Radix, Scope, activeCompute, anchorNavigate, components, createApp, createCompute, createElement, createEvent, createMemo, createRuntime, createState, defineComponent, defineContext, defineHook, defineRoute, descriptor, fileName, fromValue, getAssets, getCookies, getCustomElement, getGlobalStyles, getMimeType, getRequestUrl, getRoutes, getSetCookies, getVariables, isClient, isServer, isTemplate, markdownToSlot, navigate, preventDefault, registerComponent, renderToNode, renderToString, sendBadRequest, sendHtml, sendJson, sendRedirect, sendText, sendUnauthorized, setCookie, stopImmediatePropagation, stopPropagation, targets, toCustomElement, toFragment, toPath, toString };