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.
- package/LICENSE +21 -0
- package/README.md +109 -0
- package/package.json +57 -0
- package/src/Bundle.ts +167 -0
- package/src/BundleFiles.ts +174 -0
- package/src/BundleHttp.test.ts +160 -0
- package/src/BundleHttp.ts +259 -0
- package/src/Commander.test.ts +1378 -0
- package/src/Commander.ts +672 -0
- package/src/Datastar.test.ts +267 -0
- package/src/Datastar.ts +68 -0
- package/src/Effect_HttpRouter.test.ts +570 -0
- package/src/EncryptedCookies.test.ts +427 -0
- package/src/EncryptedCookies.ts +451 -0
- package/src/FileHttpRouter.test.ts +207 -0
- package/src/FileHttpRouter.ts +122 -0
- package/src/FileRouter.ts +405 -0
- package/src/FileRouterCodegen.test.ts +598 -0
- package/src/FileRouterCodegen.ts +251 -0
- package/src/FileRouter_files.test.ts +64 -0
- package/src/FileRouter_path.test.ts +132 -0
- package/src/FileRouter_tree.test.ts +126 -0
- package/src/FileSystemExtra.ts +102 -0
- package/src/HttpAppExtra.ts +127 -0
- package/src/Hyper.ts +194 -0
- package/src/HyperHtml.test.ts +90 -0
- package/src/HyperHtml.ts +139 -0
- package/src/HyperNode.ts +37 -0
- package/src/JsModule.test.ts +14 -0
- package/src/JsModule.ts +116 -0
- package/src/PublicDirectory.test.ts +280 -0
- package/src/PublicDirectory.ts +108 -0
- package/src/Route.test.ts +873 -0
- package/src/Route.ts +992 -0
- package/src/Router.ts +80 -0
- package/src/SseHttpResponse.ts +55 -0
- package/src/Start.ts +133 -0
- package/src/StartApp.ts +43 -0
- package/src/StartHttp.ts +42 -0
- package/src/StreamExtra.ts +146 -0
- package/src/TestHttpClient.test.ts +54 -0
- package/src/TestHttpClient.ts +100 -0
- package/src/bun/BunBundle.test.ts +277 -0
- package/src/bun/BunBundle.ts +309 -0
- package/src/bun/BunBundle_imports.test.ts +50 -0
- package/src/bun/BunFullstackServer.ts +45 -0
- package/src/bun/BunFullstackServer_httpServer.ts +541 -0
- package/src/bun/BunImportTrackerPlugin.test.ts +77 -0
- package/src/bun/BunImportTrackerPlugin.ts +97 -0
- package/src/bun/BunTailwindPlugin.test.ts +335 -0
- package/src/bun/BunTailwindPlugin.ts +322 -0
- package/src/bun/BunVirtualFilesPlugin.ts +59 -0
- package/src/bun/index.ts +4 -0
- package/src/client/Overlay.ts +34 -0
- package/src/client/ScrollState.ts +120 -0
- package/src/client/index.ts +101 -0
- package/src/index.ts +24 -0
- package/src/jsx-datastar.d.ts +63 -0
- package/src/jsx-runtime.ts +23 -0
- package/src/jsx.d.ts +4402 -0
- package/src/testing.ts +55 -0
- package/src/x/cloudflare/CloudflareTunnel.ts +110 -0
- package/src/x/cloudflare/index.ts +1 -0
- package/src/x/datastar/Datastar.test.ts +267 -0
- package/src/x/datastar/Datastar.ts +68 -0
- package/src/x/datastar/index.ts +4 -0
- 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
|
+
}
|
package/src/bun/index.ts
ADDED
|
@@ -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
|
+
}
|