@vyckr/tachyon 1.1.11 → 1.2.0

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.
Files changed (49) hide show
  1. package/.env.example +7 -4
  2. package/LICENSE +21 -0
  3. package/README.md +210 -90
  4. package/package.json +50 -33
  5. package/src/cli/bundle.ts +37 -0
  6. package/src/cli/serve.ts +100 -0
  7. package/src/{client/template.js → compiler/render-template.js} +10 -17
  8. package/src/compiler/template-compiler.ts +419 -0
  9. package/src/runtime/hot-reload-client.ts +15 -0
  10. package/src/{client/dev.html → runtime/shells/development.html} +2 -2
  11. package/src/runtime/shells/not-found.html +73 -0
  12. package/src/{client/prod.html → runtime/shells/production.html} +1 -1
  13. package/src/runtime/spa-renderer.ts +439 -0
  14. package/src/server/console-logger.ts +39 -0
  15. package/src/server/process-executor.ts +287 -0
  16. package/src/server/process-pool.ts +80 -0
  17. package/src/server/route-handler.ts +229 -0
  18. package/src/server/schema-validator.ts +161 -0
  19. package/bun.lock +0 -127
  20. package/components/clicker.html +0 -30
  21. package/deno.lock +0 -19
  22. package/go.mod +0 -3
  23. package/lib/gson-2.3.jar +0 -0
  24. package/main.js +0 -13
  25. package/routes/DELETE +0 -18
  26. package/routes/GET +0 -17
  27. package/routes/HTML +0 -135
  28. package/routes/POST +0 -32
  29. package/routes/SOCKET +0 -26
  30. package/routes/api/:version/DELETE +0 -10
  31. package/routes/api/:version/GET +0 -29
  32. package/routes/api/:version/PATCH +0 -24
  33. package/routes/api/GET +0 -29
  34. package/routes/api/POST +0 -16
  35. package/routes/api/PUT +0 -21
  36. package/src/client/404.html +0 -7
  37. package/src/client/dist.ts +0 -20
  38. package/src/client/hmr.ts +0 -12
  39. package/src/client/render.ts +0 -417
  40. package/src/client/routes.json +0 -1
  41. package/src/client/yon.ts +0 -364
  42. package/src/router.ts +0 -186
  43. package/src/serve.ts +0 -147
  44. package/src/server/logger.ts +0 -31
  45. package/src/server/tach.ts +0 -238
  46. package/tests/index.test.ts +0 -110
  47. package/tests/stream.ts +0 -24
  48. package/tests/worker.ts +0 -7
  49. package/tsconfig.json +0 -17
package/routes/api/GET DELETED
@@ -1,29 +0,0 @@
1
- #!/usr/bin/env dotnet-script
2
- #r "nuget: System.Text.Json, 9.0.0"
3
- using System;
4
- using System.Text.Json;
5
- using System.Text.Json.Nodes;
6
- using System.IO;
7
-
8
- Console.WriteLine("Executing .NET Script....");
9
-
10
- // Parse the JSON context
11
- var ctx = JsonNode.Parse(Console.ReadLine())?.AsObject();
12
-
13
- // Create a new empty JsonObject
14
- var result = new JsonObject();
15
-
16
- // Copy each property from ctx to result
17
- foreach (var prop in ctx)
18
- {
19
- result[prop.Key] = JsonNode.Parse(prop.Value.ToJsonString());
20
- }
21
-
22
- // Add the message
23
- result["message"] = "Hello from .NET Script!";
24
-
25
- // Write to file
26
- File.WriteAllText("/tmp/" + Environment.ProcessId, result.ToJsonString(new JsonSerializerOptions
27
- {
28
- WriteIndented = false
29
- }));
package/routes/api/POST DELETED
@@ -1,16 +0,0 @@
1
- #!/usr/bin/env python3
2
- import json
3
- import sys
4
- import os
5
-
6
- print("Executing Python....")
7
-
8
- ctx = json.loads(sys.stdin.read())
9
-
10
- ctx["message"] = "Hello from Python!"
11
-
12
- file = open(f"/tmp/{os.getpid()}", "w")
13
-
14
- file.write(json.dumps(ctx))
15
-
16
- file.close()
package/routes/api/PUT DELETED
@@ -1,21 +0,0 @@
1
- #!/usr/bin/env php
2
- <?php
3
-
4
- while(FALSE !== ($line = fgets(STDIN))) {
5
-
6
- $ctx = json_decode($line, true);
7
-
8
- echo "Executing PHP...\n";
9
-
10
- $ctx["message"] = "Hello from PHP!";
11
-
12
- $pid = getmypid();
13
-
14
- $file = fopen("/tmp/$pid", "w");
15
-
16
- fwrite($file, json_encode($ctx));
17
-
18
- fclose($file);
19
- }
20
-
21
- ?>
@@ -1,7 +0,0 @@
1
- <script>
2
- document.title = 404
3
- </script>
4
-
5
- <h1>404</h1>
6
- <p>Page not found</p>
7
- <a href="/">Go back to home</a>
@@ -1,20 +0,0 @@
1
- #!/usr/bin/env bun
2
- import Router from "../router.js";
3
- import Yon from "./yon.js";
4
-
5
- const start = Date.now()
6
-
7
- await Yon.createStaticRoutes()
8
-
9
- for(const route in Router.reqRoutes) {
10
-
11
- if(route.includes('hmr')) continue
12
-
13
- const res = await Router.reqRoutes[route][`GET`]()
14
-
15
- await Bun.write(Bun.file(`${process.cwd()}/dist/${route}`), await res.blob())
16
- }
17
-
18
- await Bun.write(Bun.file(`${process.cwd()}/dist/index.html`), await Bun.file(`${import.meta.dir}/prod.html`).text())
19
-
20
- console.log(`Built in ${Date.now() - start}ms`)
package/src/client/hmr.ts DELETED
@@ -1,12 +0,0 @@
1
- const url = new URL(window.location.href);
2
-
3
- const ws = new WebSocket(`ws://localhost:9876${url.pathname}`);
4
-
5
- ws.onopen = () => {
6
- console.log('HMR Enabled');
7
- }
8
-
9
- ws.onmessage = (event) => {
10
- console.log('HMR Update');
11
- window.location.reload()
12
- }
@@ -1,417 +0,0 @@
1
- let render: Function
2
- const routes = new Map<string, Record<string, number>>()
3
- let params: any[] = []
4
- const slugs: Record<string, any> = {}
5
- let previousRender: string
6
- let madeRequest: boolean = false
7
- let elementId: string | null;
8
- let selectionStart: number | null;
9
- const parser = new DOMParser()
10
- let firstRender = routes.size === 0
11
- const elementEvents: Record<string, EventListener> = {}
12
-
13
- if(firstRender) {
14
-
15
- fetch('/routes.json')
16
- .then(res => res.json())
17
- .then(data => {
18
- for (const [path, slugs] of Object.entries(data)) {
19
- routes.set(path, slugs as Record<string, number>)
20
- }
21
- setPageTemplate(window.location.pathname)
22
- firstRender = false
23
- })
24
- }
25
-
26
- function mergeBodyHTML(html: string) {
27
-
28
- if(html === previousRender) return
29
-
30
- previousRender = html
31
-
32
- // document.body.innerHTML = html
33
-
34
- if(madeRequest) {
35
- document.body.innerHTML = html
36
- } else {
37
- const nextDom = parser.parseFromString(html, 'text/html')
38
- updateDOM(document.body, nextDom.body)
39
- deleteDOM(document.body, nextDom.body)
40
- insertDOM(document.body, nextDom.body)
41
- }
42
-
43
- addEvents()
44
-
45
- if(elementId) {
46
-
47
- const element = document.getElementById(elementId)
48
-
49
- if(element) {
50
-
51
- element.focus()
52
-
53
- if(selectionStart && 'setSelectionRange' in element) {
54
-
55
- (element as HTMLInputElement).setSelectionRange(selectionStart, selectionStart)
56
- }
57
- }
58
-
59
- elementId = null;
60
- selectionStart = null
61
- }
62
- }
63
-
64
- function addEvents(elements?: HTMLCollection) {
65
-
66
- if(!elements) elements = document.body.children
67
-
68
- for (const element of elements) {
69
-
70
- const allEvents: string[] = []
71
-
72
- for (const attribute of element.attributes) {
73
-
74
- if (attribute.name.startsWith('@')) {
75
-
76
- const event = attribute.name.substring(1)
77
-
78
- allEvents.push(event)
79
-
80
- if(elementEvents[`${element.id}_${event}`]) {
81
- element.removeEventListener(event, elementEvents[`${element.id}_${event}`])
82
- }
83
-
84
- elementEvents[`${element.id}_${event}`] = async function(e) {
85
-
86
- elementId = element.id
87
-
88
- if (e.target) {
89
- selectionStart = (e.target as HTMLInputElement).selectionStart;
90
- }
91
-
92
- mergeBodyHTML(await render(elementId))
93
- }
94
-
95
- element.addEventListener(event, elementEvents[`${element.id}_${event}`])
96
- }
97
-
98
- if(attribute.name.endsWith('ed') && attribute.value === "false") element.removeAttribute(attribute.name)
99
- }
100
-
101
- if(element instanceof HTMLSelectElement || element instanceof HTMLInputElement) {
102
-
103
- element.oninput = async (ev) => {
104
- if (ev.target) {
105
-
106
- elementId = element.id
107
-
108
- if (ev.target) {
109
- selectionStart = (ev.target as HTMLInputElement).selectionStart;
110
- }
111
-
112
- const evt = { value: (ev.target as any).value, defaultValue: (ev.target as any).defaultValue }
113
-
114
- mergeBodyHTML(await render(elementId, evt))
115
- }
116
- }
117
- }
118
-
119
- addEvents(element.children)
120
- }
121
- }
122
-
123
- async function onClickEvent(ev: MouseEvent) {
124
- const target = ev.target as HTMLAnchorElement;
125
- if(target?.href) {
126
- const url = new URL(target?.href)
127
- if(url.origin !== location.origin) return
128
- ev.preventDefault()
129
- setPageTemplate(url.pathname)
130
- } else mergeBodyHTML(await render())
131
- }
132
-
133
- function setPageTemplate(pathname: string) {
134
-
135
- let url;
136
-
137
- try {
138
-
139
- let handler = getHandler(pathname)
140
-
141
- if(handler === '/') handler = ''
142
-
143
- url = `${handler}/HTML.js`
144
-
145
- } catch(err) {
146
- url = `/404.js`
147
- }
148
-
149
- import(`/pages${url}`).then(async module => {
150
- window.history.replaceState({}, '', pathname)
151
- render = await module.default()
152
- madeRequest = true
153
- mergeBodyHTML(await render())
154
- madeRequest = false
155
- })
156
- }
157
-
158
-
159
- function getHandler(pathname: string) {
160
-
161
- let handler;
162
-
163
- if (pathname === '/') return pathname
164
-
165
- const paths = pathname.split('/').slice(1);
166
-
167
- let bestMatchKey = '';
168
- let bestMatchLength = -1;
169
-
170
- for (const [routeKey] of routes) {
171
-
172
- const routeSegs = routeKey.split('/')
173
-
174
- const isMatch = pathsMatch(routeSegs, paths.slice(0, routeSegs.length));
175
-
176
- if (isMatch && routeSegs.length > bestMatchLength) {
177
- bestMatchKey = routeKey;
178
- bestMatchLength = routeSegs.length;
179
- }
180
- }
181
-
182
- if (bestMatchKey) {
183
-
184
- handler = bestMatchKey
185
-
186
- params = parseParams(paths.slice(bestMatchLength))
187
-
188
- const slugMap = routes.get(bestMatchKey) ?? {}
189
-
190
- Object.entries(slugMap).forEach(([key, idx]) => {
191
- key = key.replace(':', '')
192
- slugs[key] = paths[idx]
193
- })
194
- }
195
-
196
- if (!handler) throw new Error(`Route ${pathname} not found`, { cause: 404 });
197
-
198
- return handler
199
- }
200
-
201
-
202
- function pathsMatch(routeSegs: string[], pathSegs: string[]) {
203
-
204
- if (routeSegs.length !== pathSegs.length) {
205
- return false;
206
- }
207
-
208
- const slugs = routes.get(routeSegs.join('/')) || {}
209
-
210
- for (let i = 0; i < routeSegs.length; i++) {
211
- if (!slugs[routeSegs[i]] && routeSegs[i] !== pathSegs[i]) {
212
- return false;
213
- }
214
- }
215
-
216
- return true;
217
- }
218
-
219
-
220
- function parseParams(input: string[]) {
221
-
222
- const params = []
223
-
224
- for(const param of input) {
225
-
226
- const num = Number(param)
227
-
228
- if(!Number.isNaN(num)) params.push(num)
229
-
230
- else if(param === 'true') params.push(true)
231
-
232
- else if(param === 'false') params.push(false)
233
-
234
- else if(param === 'null') params.push(null)
235
-
236
- else if(param === 'undefined') params.push(undefined)
237
-
238
- else params.push(param)
239
- }
240
-
241
- return params
242
- }
243
-
244
- Object.keys(window).forEach(key => {
245
- if(/^on/.test(key)) {
246
- document.addEventListener(key.slice(2), async ev => {
247
-
248
- switch(ev.type) {
249
- case 'click':
250
- await onClickEvent(ev as MouseEvent)
251
- break
252
- case 'popstate':
253
- setPageTemplate(window.location.pathname)
254
- break
255
- default:
256
- mergeBodyHTML(await render())
257
- break
258
- }
259
- })
260
- }
261
- })
262
-
263
-
264
-
265
- /**
266
- * Updates existing nodes in oldDOM with properties from newDOM
267
- * @param {Node} oldDOM - The current DOM
268
- * @param {Node} newDOM - The desired DOM state
269
- */
270
- function updateDOM(oldDOM: Node, newDOM: Node): void {
271
- // Skip if nodes are not of the same type
272
- if (!areNodesOfSameType(oldDOM, newDOM)) {
273
- return;
274
- }
275
-
276
- // If it's a text node, update the content
277
- if (oldDOM.nodeType === Node.TEXT_NODE && newDOM.nodeType === Node.TEXT_NODE) {
278
- if (oldDOM.textContent !== newDOM.textContent) {
279
- oldDOM.textContent = newDOM.textContent;
280
- }
281
- return;
282
- }
283
-
284
- // If it's an element node, update attributes
285
- if (oldDOM.nodeType === Node.ELEMENT_NODE && newDOM.nodeType === Node.ELEMENT_NODE) {
286
- // Type assertion since we've already checked nodeType
287
- const oldElement = oldDOM as Element;
288
- const newElement = newDOM as Element;
289
-
290
- // Update attributes
291
- updateAttributes(oldElement, newElement);
292
-
293
- // Recursively update children that exist in both DOMs
294
- const oldChildren = Array.from(oldDOM.childNodes);
295
- const newChildren = Array.from(newDOM.childNodes);
296
-
297
- const minLength = Math.min(oldChildren.length, newChildren.length);
298
-
299
- for (let i = 0; i < minLength; i++) {
300
- updateDOM(oldChildren[i], newChildren[i]);
301
- }
302
- }
303
- }
304
-
305
- /**
306
- * Deletes nodes from oldDOM that don't exist in newDOM
307
- * @param {Node} oldDOM - The current DOM
308
- * @param {Node} newDOM - The desired DOM state
309
- */
310
- function deleteDOM(oldDOM: Node, newDOM: Node): void {
311
- // If nodes are not of the same type, skip (will be handled by insertDOM)
312
- if (!areNodesOfSameType(oldDOM, newDOM)) {
313
- return;
314
- }
315
-
316
- // If it's an element node, check children
317
- if (oldDOM.nodeType === Node.ELEMENT_NODE && newDOM.nodeType === Node.ELEMENT_NODE) {
318
- const oldChildren = Array.from(oldDOM.childNodes);
319
- const newChildren = Array.from(newDOM.childNodes);
320
-
321
- // Remove children that exist in oldDOM but not in newDOM
322
- // Start from the end to avoid index shifting issues
323
- for (let i = oldChildren.length - 1; i >= newChildren.length; i--) {
324
- oldDOM.removeChild(oldChildren[i]);
325
- }
326
-
327
- // Recursively delete for remaining children
328
- const minLength = Math.min(oldChildren.length, newChildren.length);
329
- for (let i = 0; i < minLength; i++) {
330
- deleteDOM(oldChildren[i], newChildren[i]);
331
- }
332
- }
333
- }
334
-
335
- /**
336
- * Inserts nodes from newDOM that don't exist in oldDOM
337
- * @param {Node} oldDOM - The current DOM
338
- * @param {Node} newDOM - The desired DOM state
339
- */
340
- function insertDOM(oldDOM: Node, newDOM: Node): void {
341
- // If nodes are not of the same type, replace the entire node
342
- if (!areNodesOfSameType(oldDOM, newDOM)) {
343
- if (oldDOM.parentNode) {
344
- oldDOM.parentNode.replaceChild(newDOM.cloneNode(true), oldDOM);
345
- }
346
- return;
347
- }
348
-
349
- // If it's an element node, check children
350
- if (oldDOM.nodeType === Node.ELEMENT_NODE && newDOM.nodeType === Node.ELEMENT_NODE) {
351
- const oldChildren = Array.from(oldDOM.childNodes);
352
- const newChildren = Array.from(newDOM.childNodes);
353
-
354
- // Add children that exist in newDOM but not in oldDOM
355
- for (let i = oldChildren.length; i < newChildren.length; i++) {
356
- oldDOM.appendChild(newChildren[i].cloneNode(true));
357
- }
358
-
359
- // Recursively insert for existing children
360
- const minLength = Math.min(oldChildren.length, newChildren.length);
361
- for (let i = 0; i < minLength; i++) {
362
- insertDOM(oldChildren[i], newChildren[i]);
363
- }
364
- }
365
- }
366
-
367
- /**
368
- * Helper function to update attributes of an element
369
- * @param {Element} oldElement - The element to update
370
- * @param {Element} newElement - The element with the desired attributes
371
- */
372
- function updateAttributes(oldElement: Element, newElement: Element): void {
373
- // Remove attributes not in newElement
374
- for (const attr of Array.from(oldElement.attributes)) {
375
- if (!newElement.hasAttribute(attr.name)) {
376
- oldElement.removeAttribute(attr.name);
377
- }
378
- }
379
-
380
- // Add or update attributes from newElement
381
- for (const attr of Array.from(newElement.attributes)) {
382
- // Use strict equality to ensure empty strings are properly handled
383
- if (!attr.name.startsWith('@') &&
384
- (oldElement.getAttribute(attr.name) !== attr.value ||
385
- (!oldElement.hasAttribute(attr.name) && attr.value === ""))) {
386
- if(oldElement.children.length === 0) {
387
- oldElement.outerHTML = newElement.outerHTML
388
- } else oldElement.setAttribute(attr.name, attr.value)
389
- }
390
- }
391
- }
392
- /**
393
- * Helper function to check if two nodes are of the same type
394
- * @param {Node} oldNode - The old node
395
- * @param {Node} newNode - The new node
396
- * @returns {boolean} - Whether the nodes are of the same type
397
- */
398
- function areNodesOfSameType(oldNode: Node, newNode: Node): boolean {
399
- // Check if both nodes are defined
400
- if (!oldNode || !newNode) {
401
- return false;
402
- }
403
-
404
- // Check if node types match
405
- if (oldNode.nodeType !== newNode.nodeType) {
406
- return false;
407
- }
408
-
409
- // For element nodes, check if tag names match
410
- if (oldNode.nodeType === Node.ELEMENT_NODE && newNode.nodeType === Node.ELEMENT_NODE) {
411
- const oldElement = oldNode as Element;
412
- const newElement = newNode as Element;
413
- return oldElement.tagName === newElement.tagName;
414
- }
415
-
416
- return true;
417
- }
@@ -1 +0,0 @@
1
- {"/api/:version":{":version":1},"/":{}}