effect-start 0.9.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 (67) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +109 -0
  3. package/package.json +57 -0
  4. package/src/Bundle.ts +167 -0
  5. package/src/BundleFiles.ts +174 -0
  6. package/src/BundleHttp.test.ts +160 -0
  7. package/src/BundleHttp.ts +259 -0
  8. package/src/Commander.test.ts +1378 -0
  9. package/src/Commander.ts +672 -0
  10. package/src/Datastar.test.ts +267 -0
  11. package/src/Datastar.ts +68 -0
  12. package/src/Effect_HttpRouter.test.ts +570 -0
  13. package/src/EncryptedCookies.test.ts +427 -0
  14. package/src/EncryptedCookies.ts +451 -0
  15. package/src/FileHttpRouter.test.ts +207 -0
  16. package/src/FileHttpRouter.ts +122 -0
  17. package/src/FileRouter.ts +405 -0
  18. package/src/FileRouterCodegen.test.ts +598 -0
  19. package/src/FileRouterCodegen.ts +251 -0
  20. package/src/FileRouter_files.test.ts +64 -0
  21. package/src/FileRouter_path.test.ts +132 -0
  22. package/src/FileRouter_tree.test.ts +126 -0
  23. package/src/FileSystemExtra.ts +102 -0
  24. package/src/HttpAppExtra.ts +127 -0
  25. package/src/Hyper.ts +194 -0
  26. package/src/HyperHtml.test.ts +90 -0
  27. package/src/HyperHtml.ts +139 -0
  28. package/src/HyperNode.ts +37 -0
  29. package/src/JsModule.test.ts +14 -0
  30. package/src/JsModule.ts +116 -0
  31. package/src/PublicDirectory.test.ts +280 -0
  32. package/src/PublicDirectory.ts +108 -0
  33. package/src/Route.test.ts +873 -0
  34. package/src/Route.ts +992 -0
  35. package/src/Router.ts +80 -0
  36. package/src/SseHttpResponse.ts +55 -0
  37. package/src/Start.ts +133 -0
  38. package/src/StartApp.ts +43 -0
  39. package/src/StartHttp.ts +42 -0
  40. package/src/StreamExtra.ts +146 -0
  41. package/src/TestHttpClient.test.ts +54 -0
  42. package/src/TestHttpClient.ts +100 -0
  43. package/src/bun/BunBundle.test.ts +277 -0
  44. package/src/bun/BunBundle.ts +309 -0
  45. package/src/bun/BunBundle_imports.test.ts +50 -0
  46. package/src/bun/BunFullstackServer.ts +45 -0
  47. package/src/bun/BunFullstackServer_httpServer.ts +541 -0
  48. package/src/bun/BunImportTrackerPlugin.test.ts +77 -0
  49. package/src/bun/BunImportTrackerPlugin.ts +97 -0
  50. package/src/bun/BunTailwindPlugin.test.ts +335 -0
  51. package/src/bun/BunTailwindPlugin.ts +322 -0
  52. package/src/bun/BunVirtualFilesPlugin.ts +59 -0
  53. package/src/bun/index.ts +4 -0
  54. package/src/client/Overlay.ts +34 -0
  55. package/src/client/ScrollState.ts +120 -0
  56. package/src/client/index.ts +101 -0
  57. package/src/index.ts +24 -0
  58. package/src/jsx-datastar.d.ts +63 -0
  59. package/src/jsx-runtime.ts +23 -0
  60. package/src/jsx.d.ts +4402 -0
  61. package/src/testing.ts +55 -0
  62. package/src/x/cloudflare/CloudflareTunnel.ts +110 -0
  63. package/src/x/cloudflare/index.ts +1 -0
  64. package/src/x/datastar/Datastar.test.ts +267 -0
  65. package/src/x/datastar/Datastar.ts +68 -0
  66. package/src/x/datastar/index.ts +4 -0
  67. package/src/x/datastar/jsx-datastar.d.ts +63 -0
@@ -0,0 +1,59 @@
1
+ import type {
2
+ BunPlugin,
3
+ Loader,
4
+ } from "bun"
5
+
6
+ type VirtualFiles = Record<string, string>
7
+
8
+ function loaderFromPath(path: string): Loader {
9
+ return path.slice(path.lastIndexOf(".") + 1) as Loader
10
+ }
11
+
12
+ export function make(files: VirtualFiles): BunPlugin {
13
+ return {
14
+ name: "virtual-fs",
15
+ setup(build) {
16
+ build.onResolve(
17
+ {
18
+ // change the filter so it only works for file namespace
19
+ filter: /.*/,
20
+ },
21
+ (args) => {
22
+ const resolved = resolvePath(args.path, args.resolveDir)
23
+ const resolvedFile = files[resolved]
24
+
25
+ if (resolvedFile) {
26
+ return {
27
+ path: resolved,
28
+ namespace: "virtual-fs",
29
+ }
30
+ }
31
+ return
32
+ },
33
+ )
34
+
35
+ build.onLoad(
36
+ {
37
+ filter: /.*/,
38
+ namespace: "virtual-fs",
39
+ },
40
+ (args) => {
41
+ const contents = files[args.path]
42
+
43
+ if (!contents) {
44
+ return undefined
45
+ }
46
+
47
+ return {
48
+ contents,
49
+ loader: loaderFromPath(args.path),
50
+ }
51
+ },
52
+ )
53
+ },
54
+ }
55
+ }
56
+
57
+ function resolvePath(path: string, base = process.cwd()) {
58
+ return Bun.resolveSync(path, base)
59
+ }
@@ -0,0 +1,4 @@
1
+ export * as BunBundle from "./BunBundle.ts"
2
+ export * as BunFullstackServer from "./BunFullstackServer.ts"
3
+ export * as BunImportTrackerPlugin from "./BunImportTrackerPlugin.ts"
4
+ export * as BunTailwindPlugin from "./BunTailwindPlugin.ts"
@@ -0,0 +1,34 @@
1
+ const OVERLAY_ID = "_bundler_error_overlay"
2
+
3
+ export function getOverlay() {
4
+ let overlay = document.getElementById(OVERLAY_ID) as HTMLPreElement | null
5
+ if (!overlay) {
6
+ overlay = document.createElement("pre")
7
+ overlay.id = OVERLAY_ID
8
+ Object.assign(overlay.style, {
9
+ position: "fixed",
10
+ top: "0",
11
+ left: "0",
12
+ right: "0",
13
+ maxHeight: "40%",
14
+ overflowY: "auto",
15
+ margin: "0",
16
+ padding: "4px",
17
+ background: "black",
18
+ color: "red",
19
+ fontFamily: "monospace",
20
+ zIndex: "2147483647",
21
+ whiteSpace: "pre-wrap",
22
+ })
23
+ document.body.appendChild(overlay)
24
+ }
25
+ return overlay
26
+ }
27
+
28
+ export function showBuildError(message: string) {
29
+ const overlay = getOverlay()
30
+ const atBottom =
31
+ overlay.scrollTop + overlay.clientHeight >= overlay.scrollHeight - 1
32
+ overlay.textContent += message + "\n"
33
+ if (atBottom) overlay.scrollTop = overlay.scrollHeight
34
+ }
@@ -0,0 +1,120 @@
1
+ const ScrollKey = "_BUNDLER_SCROLL"
2
+
3
+ type Anchor = {
4
+ selector: string
5
+ offset: number
6
+ }
7
+
8
+ type ScrollState = {
9
+ scrollY: number
10
+ anchors: Anchor[]
11
+ }
12
+
13
+ /**
14
+ * Persist current scroll state to session storage.
15
+ * Scroll state is saved relatively to visible elements.
16
+ */
17
+ export function persist() {
18
+ const anchors: Anchor[] = []
19
+ const step = window.innerHeight / 4
20
+
21
+ for (let i = 1; i <= 3; i++) {
22
+ const y = step * i
23
+ const element = document.elementFromPoint(0, y)
24
+ if (!element) continue
25
+ const target = element.id
26
+ ? element
27
+ : element.closest("[id]") ?? element
28
+
29
+ anchors.push({
30
+ selector: selectorFromElement(target),
31
+ offset: target.getBoundingClientRect().top,
32
+ })
33
+ }
34
+
35
+ const state: ScrollState = {
36
+ anchors,
37
+ scrollY: window.scrollY,
38
+ }
39
+
40
+ sessionStorage.setItem(ScrollKey, JSON.stringify(state))
41
+ }
42
+
43
+ export function restore() {
44
+ const timeout = 3000
45
+ const tick = 50
46
+ const raw = sessionStorage.getItem(ScrollKey)
47
+ if (!raw) return
48
+
49
+ sessionStorage.removeItem(ScrollKey)
50
+
51
+ const state: ScrollState = JSON.parse(raw)
52
+
53
+ const apply = () => {
54
+ for (const anchor of state.anchors) {
55
+ const element = document.querySelector(anchor.selector)
56
+ if (element) {
57
+ const rect = element.getBoundingClientRect()
58
+ const top = window.scrollY + rect.top - anchor.offset
59
+ window.scrollTo({
60
+ top,
61
+ })
62
+ return
63
+ }
64
+ }
65
+
66
+ window.scrollTo({
67
+ top: state.scrollY,
68
+ })
69
+ }
70
+
71
+ let observer: MutationObserver
72
+ let stableTimer: ReturnType<typeof setTimeout> | undefined
73
+ const deadline = setTimeout(() => {
74
+ observer.disconnect()
75
+ if (stableTimer) clearTimeout(stableTimer)
76
+ apply()
77
+ }, timeout)
78
+
79
+ observer = new MutationObserver(() => {
80
+ if (stableTimer) clearTimeout(stableTimer)
81
+ stableTimer = setTimeout(() => {
82
+ observer.disconnect()
83
+ clearTimeout(deadline)
84
+ apply()
85
+ }, tick)
86
+ })
87
+
88
+ observer.observe(document.body, {
89
+ subtree: true,
90
+ childList: true,
91
+ attributes: true,
92
+ characterData: true,
93
+ })
94
+
95
+ stableTimer = setTimeout(() => {
96
+ observer.disconnect()
97
+ clearTimeout(deadline)
98
+ apply()
99
+ }, tick)
100
+ }
101
+
102
+ function selectorFromElement(element: Element): string {
103
+ if (element.id) {
104
+ return `#${CSS.escape(element.id)}`
105
+ }
106
+ const parts: string[] = []
107
+ let current: Element | null = element
108
+
109
+ while (current && current !== document.body) {
110
+ const parent = current.parentElement
111
+ if (!parent) break
112
+ const index = Array
113
+ .from(parent.children)
114
+ .indexOf(current) + 1
115
+ parts.unshift(`${current.tagName.toLowerCase()}:nth-child(${index})`)
116
+ current = parent
117
+ }
118
+
119
+ return parts.join(" > ")
120
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * This module is intended to be imported in a browser bundle in a development.
3
+ * It is responsible for live reloading the page when bundle changes.
4
+ * When NODE_ENV=production, it does nothing.
5
+ */
6
+
7
+ /// <reference lib="dom" />
8
+ /// <reference lib="dom.iterable" />
9
+
10
+ import type {
11
+ BundleEvent,
12
+ BundleManifest,
13
+ } from "../Bundle.ts"
14
+ import { showBuildError } from "./Overlay.ts"
15
+ import * as ScrollState from "./ScrollState.ts"
16
+
17
+ const BUNDLE_URL = globalThis._BUNDLE_URL ?? "/_bundle"
18
+
19
+ function reload() {
20
+ ScrollState.persist()
21
+ window.location.reload()
22
+ }
23
+
24
+ async function loadAllEntrypoints() {
25
+ const manifest: BundleManifest = await fetch(`/${BUNDLE_URL}/manifest.json`)
26
+ .then(v => v.json())
27
+
28
+ manifest
29
+ .artifacts
30
+ .filter(v => v.path.endsWith(".js"))
31
+ .forEach((artifact) => {
32
+ console.log(artifact.path)
33
+ const script = document.createElement("script")
34
+ script.src = `${BUNDLE_URL}/${artifact.path}`
35
+ script.type = "module"
36
+ script.onload = () => {
37
+ console.debug("Bundle reloaded")
38
+ }
39
+ document.body.appendChild(script)
40
+ })
41
+ }
42
+
43
+ function handleBundleEvent(event: BundleEvent) {
44
+ switch (event._tag) {
45
+ case "Change":
46
+ console.debug("Bundle change detected...")
47
+ reload()
48
+ break
49
+ case "BuildError":
50
+ showBuildError(event.error)
51
+ break
52
+ }
53
+ }
54
+
55
+ function listen() {
56
+ const eventSource = new EventSource(`${BUNDLE_URL}/events`)
57
+
58
+ eventSource.addEventListener("message", (event) => {
59
+ try {
60
+ reloadAllMetaLinks()
61
+ const data = JSON.parse(event.data)
62
+
63
+ handleBundleEvent(data)
64
+ } catch (error) {
65
+ console.error("Error parsing SSE event", {
66
+ error,
67
+ event,
68
+ })
69
+ }
70
+ })
71
+
72
+ eventSource.addEventListener("error", error => {
73
+ console.error("SSE connection error:", error)
74
+ })
75
+
76
+ return () => {
77
+ eventSource.close()
78
+ }
79
+ }
80
+
81
+ function reloadAllMetaLinks() {
82
+ for (const link of document.getElementsByTagName("link")) {
83
+ const url = new URL(link.href)
84
+
85
+ if (url.host === window.location.host) {
86
+ const next = link.cloneNode() as HTMLLinkElement
87
+ // TODO: this won't work when link already has query params
88
+ next.href = next.href + "?" + Math.random().toString(36).slice(2)
89
+ next.onload = () => link.remove()
90
+ link.parentNode!.insertBefore(next, link.nextSibling)
91
+ return
92
+ }
93
+ }
94
+ }
95
+
96
+ if (process.env.NODE_ENV !== "production") {
97
+ window.addEventListener("load", () => {
98
+ ScrollState.restore()
99
+ listen()
100
+ })
101
+ }
package/src/index.ts ADDED
@@ -0,0 +1,24 @@
1
+ export * as Bundle from "./Bundle"
2
+ export * as BundleHttp from "./BundleHttp"
3
+
4
+ export * as StartHttp from "./StartHttp"
5
+
6
+ export * as HttpAppExtra from "./HttpAppExtra"
7
+
8
+ export * as PublicDirectory from "./PublicDirectory"
9
+
10
+ export * as TestHttpClient from "./TestHttpClient"
11
+ export {
12
+ effectFn,
13
+ } from "./testing"
14
+
15
+ export * as FileHttpRouter from "./FileHttpRouter"
16
+ export * as FileRouter from "./FileRouter"
17
+
18
+ export * as Route from "./Route"
19
+ export * as Router from "./Router"
20
+
21
+ export * as Start from "./Start"
22
+
23
+ export * as Hyper from "./Hyper"
24
+ export * as HyperHtml from "./HyperHtml"
@@ -0,0 +1,63 @@
1
+ // Datastar object types for specific attributes
2
+ type DatastarSignalsObject = Record<string, any>
3
+ type DatastarClassObject = Record<string, boolean | string>
4
+ type DatastarAttrObject = Record<string, string | boolean | number>
5
+ type DatastarStyleObject = Record<
6
+ string,
7
+ string | number | boolean | null | undefined
8
+ >
9
+
10
+ /**
11
+ * Datastar attributes for reactive web applications
12
+ * @see https://data-star.dev/reference/attributes
13
+ */
14
+ export interface DatastarAttributes {
15
+ // Core attributes that can accept objects (but also strings)
16
+ "data-signals"?: string | DatastarSignalsObject | undefined
17
+ "data-class"?: string | DatastarClassObject | undefined
18
+ "data-attr"?: string | DatastarAttrObject | undefined
19
+ "data-style"?: Function | string | DatastarStyleObject | undefined
20
+
21
+ // Boolean/presence attributes (but also strings)
22
+ "data-show"?: string | boolean | undefined
23
+ "data-ignore"?: string | boolean | undefined
24
+ "data-ignore-morph"?: string | boolean | undefined
25
+
26
+ // All other Datastar attributes as strings only
27
+ "data-bind"?: string | undefined
28
+ "data-computed"?: string | undefined
29
+ "data-effect"?: string | undefined
30
+ "data-indicator"?: string | undefined
31
+ "data-json-signals"?: string | undefined
32
+ "data-on"?: string | undefined
33
+ "data-on-intersect"?: string | undefined
34
+ "data-on-interval"?: string | undefined
35
+ "data-on-load"?: string | undefined
36
+ "data-on-signal-patch"?: string | undefined
37
+ "data-on-signal-patch-filter"?: string | undefined
38
+ "data-preserve-attr"?: string | undefined
39
+ "data-ref"?: string | undefined
40
+ "data-text"?: string | undefined
41
+
42
+ // Pro attributes (strings only)
43
+ "data-animate"?: string | undefined
44
+ "data-custom-validity"?: string | undefined
45
+ "data-on-raf"?: string | undefined
46
+ "data-on-resize"?: string | undefined
47
+ "data-persist"?: string | undefined
48
+ "data-query-string"?: string | undefined
49
+ "data-replace-url"?: string | undefined
50
+ "data-scroll-into-view"?: string | undefined
51
+ "data-view-transition"?: string | undefined
52
+
53
+ // Dynamic attributes with suffixes
54
+ [key: `data-signals-${string}`]: string | undefined
55
+ [key: `data-class-${string}`]: string | undefined
56
+ [key: `data-attr-${string}`]: string | undefined
57
+ [key: `data-style-${string}`]: string | undefined
58
+ [key: `data-bind-${string}`]: string | undefined
59
+ [key: `data-computed-${string}`]: string | undefined
60
+ [key: `data-indicator-${string}`]: string | undefined
61
+ [key: `data-ref-${string}`]: string | undefined
62
+ [key: `data-on-${string}`]: Function | string | undefined
63
+ }
@@ -0,0 +1,23 @@
1
+ import * as HyperNode from "./HyperNode.ts"
2
+ import type { JSX } from "./jsx.d.ts"
3
+
4
+ function Fragment(props: { children: JSX.Element }) {
5
+ return props.children
6
+ }
7
+
8
+ function jsx<T extends HyperNode.Type>(
9
+ type: T,
10
+ props: T extends string ? HyperNode.Props
11
+ : T extends (props: infer P) => any ? P
12
+ : never,
13
+ ): HyperNode.HyperNode {
14
+ return HyperNode.make(type, props)
15
+ }
16
+
17
+ export {
18
+ Fragment,
19
+ type JSX,
20
+ jsx,
21
+ jsx as jsxDEV,
22
+ jsx as jsxs,
23
+ }