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,259 @@
1
+ import * as Headers from "@effect/platform/Headers"
2
+ import type * as HttpApp from "@effect/platform/HttpApp"
3
+ import * as HttpMiddleware from "@effect/platform/HttpMiddleware"
4
+ import { RouteNotFound } from "@effect/platform/HttpServerError"
5
+ import * as HttpServerRequest from "@effect/platform/HttpServerRequest"
6
+ import * as HttpServerResponse from "@effect/platform/HttpServerResponse"
7
+ import * as Context from "effect/Context"
8
+ import * as Effect from "effect/Effect"
9
+ import * as Function from "effect/Function"
10
+ import * as Option from "effect/Option"
11
+ import * as Scope from "effect/Scope"
12
+ import * as Stream from "effect/Stream"
13
+ import * as NPath from "node:path"
14
+ import * as NUrl from "node:url"
15
+ import * as Bundle from "./Bundle.ts"
16
+ import * as SseHttpResponse from "./SseHttpResponse.ts"
17
+
18
+ const DefaultBundleEndpoint = "/_bundle"
19
+
20
+ /**
21
+ * Handles all entrypoints automatically.
22
+ * Serves HTML entrypoints without requiring explicit route definitions for each one.
23
+ * Examples:
24
+ * index.html -> /
25
+ * contact.html -> /contact
26
+ * about/index.html -> /about
27
+ */
28
+ export function entrypoint(
29
+ uri?: string,
30
+ ): HttpApp.Default<RouteNotFound, Bundle.ClientBundle> {
31
+ return Effect.gen(function*() {
32
+ uri = uri?.startsWith("file://") ? NUrl.fileURLToPath(uri) : uri
33
+ const request = yield* HttpServerRequest.HttpServerRequest
34
+ const bundle = yield* Bundle.ClientBundle
35
+ const requestPath = request.url.substring(1)
36
+ const pathAttempts = uri
37
+ ? [
38
+ // try paths from all parent directories in case absolute path is passed,
39
+ // like it is the case for `import(f, { type: "file" })`
40
+ ...uri
41
+ .split(NPath.sep)
42
+ .map((_, i, a) => NPath.join(...a.slice(i))),
43
+ ]
44
+ : [
45
+ requestPath ? `${requestPath}.html` : null,
46
+ requestPath ? `${requestPath}/index.html` : null,
47
+ requestPath === "" ? "index.html" : "",
48
+ ]
49
+ const artifact = pathAttempts
50
+ .filter(Boolean)
51
+ .map(path => bundle.getArtifact(path as string))
52
+ .find(Boolean)
53
+
54
+ if (artifact) {
55
+ return yield* renderBlob(artifact)
56
+ }
57
+
58
+ return yield* Effect.fail(
59
+ new RouteNotFound({
60
+ request,
61
+ }),
62
+ )
63
+ })
64
+ }
65
+
66
+ export function httpApp(
67
+ opts?: { urlPrefix?: string },
68
+ ): HttpApp.Default<
69
+ RouteNotFound,
70
+ Scope.Scope | Bundle.ClientBundle
71
+ > {
72
+ return toHttpApp(Bundle.ClientBundle, opts)
73
+ }
74
+
75
+ export const toHttpApp = <E, R>(
76
+ bundleTag: Effect.Effect<Bundle.BundleContext, E, R>,
77
+ opts?: { urlPrefix?: string },
78
+ ): Effect.Effect<
79
+ HttpServerResponse.HttpServerResponse,
80
+ RouteNotFound | E,
81
+ HttpServerRequest.HttpServerRequest | R
82
+ > => {
83
+ return Effect.gen(function*() {
84
+ const request = yield* HttpServerRequest.HttpServerRequest
85
+ const bundle = yield* bundleTag
86
+ const path = opts?.urlPrefix && request.url.startsWith(opts.urlPrefix + "/")
87
+ ? request.url.substring(opts.urlPrefix.length + 1)
88
+ : request.url.substring(1)
89
+
90
+ /**
91
+ * Expose manifest that contains information about the bundle.
92
+ */
93
+ if (path === "manifest.json") {
94
+ return HttpServerResponse.text(
95
+ JSON.stringify(
96
+ {
97
+ entrypoints: bundle.entrypoints,
98
+ artifacts: bundle.artifacts,
99
+ },
100
+ undefined,
101
+ 2,
102
+ ),
103
+ {
104
+ headers: {
105
+ "Content-Type": "application/json",
106
+ },
107
+ },
108
+ )
109
+ }
110
+
111
+ /**
112
+ * Expose events endpoint if available.
113
+ * Useful for development to implement live reload.
114
+ */
115
+ if (bundle.events && path === "events") {
116
+ return yield* SseHttpResponse.make<Bundle.BundleEvent>(
117
+ Stream.fromPubSub(bundle.events),
118
+ )
119
+ }
120
+
121
+ const artifact = bundle.artifacts.find((a) => a.path === path)
122
+
123
+ /**
124
+ * Expose artifacts.
125
+ */
126
+ if (artifact) {
127
+ const artifactBlob = bundle.getArtifact(path)!
128
+
129
+ return yield* renderBlob(artifactBlob)
130
+ }
131
+
132
+ return yield* Effect.fail(
133
+ new RouteNotFound({
134
+ request,
135
+ }),
136
+ )
137
+ })
138
+ }
139
+
140
+ /**
141
+ * Render HTML to a string.
142
+ * Useful for SSR.
143
+ */
144
+ export function renderPromise(
145
+ clientBundle: Bundle.Tag,
146
+ render: (
147
+ request: Request,
148
+ resolve: (url: string) => string,
149
+ ) => Promise<Response>,
150
+ ) {
151
+ return Effect.gen(function*() {
152
+ const bundle = yield* clientBundle
153
+ const req = yield* HttpServerRequest.HttpServerRequest
154
+ const fetchReq = req.source as Request
155
+
156
+ // TODO: add support for file:// urls
157
+ // this will require handling source base path
158
+ const resolve = (url: string): string => {
159
+ const path = url.startsWith("file://")
160
+ ? NUrl.fileURLToPath(url)
161
+ : url
162
+ const publicBase = "/.bundle"
163
+ const publicPath = bundle.resolve(path)
164
+
165
+ return NPath.join(publicBase, publicPath ?? path)
166
+ }
167
+
168
+ const output = yield* Effect.tryPromise({
169
+ try: () =>
170
+ render(
171
+ fetchReq,
172
+ resolve,
173
+ ),
174
+ catch: (e) =>
175
+ new Bundle.BundleError({
176
+ message: "Failed to render",
177
+ cause: e,
178
+ }),
179
+ })
180
+
181
+ return yield* HttpServerResponse.raw(output.body, {
182
+ status: output.status,
183
+ statusText: output.statusText,
184
+ headers: Headers.fromInput(output.headers as any),
185
+ })
186
+ })
187
+ }
188
+
189
+ const renderBlob = (blob: Blob) => {
190
+ return Effect.gen(function*() {
191
+ const bytes = yield* Effect
192
+ .promise(() => blob.arrayBuffer())
193
+ .pipe(
194
+ Effect.andThen(v => new Uint8Array(v)),
195
+ )
196
+
197
+ return HttpServerResponse.uint8Array(bytes, {
198
+ headers: {
199
+ "content-type": blob.type,
200
+ "content-length": String(blob.size),
201
+ "cache-control": "public, max-age=3600",
202
+ },
203
+ })
204
+ })
205
+ }
206
+
207
+ /**
208
+ * Exposes bundle assets via HTTP routes.
209
+ * Serves bundle artifacts, manifest.json, and events endpoint at the specified path.
210
+ */
211
+ export function withAssets(
212
+ opts?: { path?: string },
213
+ ) {
214
+ const path = opts?.path ?? DefaultBundleEndpoint
215
+
216
+ return HttpMiddleware.make((app) =>
217
+ Effect.gen(function*() {
218
+ const request = yield* HttpServerRequest.HttpServerRequest
219
+
220
+ if (request.url.startsWith(path + "/")) {
221
+ return yield* toHttpApp(Bundle.ClientBundle, { urlPrefix: path })
222
+ }
223
+
224
+ return yield* app
225
+ })
226
+ )
227
+ }
228
+
229
+ /**
230
+ * @see {entrypoint}
231
+ */
232
+ export function withEntrypoints() {
233
+ return HttpMiddleware.make((app) =>
234
+ Effect.gen(function*() {
235
+ const entrypointResponse = yield* entrypoint().pipe(
236
+ Effect.option,
237
+ )
238
+
239
+ if (Option.isSome(entrypointResponse)) {
240
+ return entrypointResponse.value
241
+ }
242
+
243
+ return yield* app
244
+ })
245
+ )
246
+ }
247
+
248
+ /**
249
+ * Combines both withAssets and withEntrypoints.
250
+ * Provides complete bundle HTTP functionality in a single function call.
251
+ */
252
+ export function withBundle(
253
+ opts?: { path?: string },
254
+ ) {
255
+ return Function.flow(
256
+ withAssets(opts),
257
+ withEntrypoints(),
258
+ )
259
+ }