effect-start 0.9.0 → 0.11.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 (54) hide show
  1. package/package.json +15 -14
  2. package/src/BundleHttp.test.ts +1 -1
  3. package/src/Commander.test.ts +15 -15
  4. package/src/Commander.ts +58 -88
  5. package/src/EncryptedCookies.test.ts +4 -4
  6. package/src/FileHttpRouter.test.ts +85 -16
  7. package/src/FileHttpRouter.ts +119 -32
  8. package/src/FileRouter.ts +62 -166
  9. package/src/FileRouterCodegen.test.ts +252 -66
  10. package/src/FileRouterCodegen.ts +13 -56
  11. package/src/FileRouterPattern.test.ts +116 -0
  12. package/src/FileRouterPattern.ts +59 -0
  13. package/src/FileRouter_path.test.ts +63 -102
  14. package/src/FileSystemExtra.test.ts +226 -0
  15. package/src/FileSystemExtra.ts +24 -60
  16. package/src/HttpAppExtra.test.ts +84 -0
  17. package/src/HttpAppExtra.ts +399 -47
  18. package/src/HttpUtils.test.ts +68 -0
  19. package/src/HttpUtils.ts +15 -0
  20. package/src/HyperHtml.ts +24 -5
  21. package/src/JsModule.test.ts +1 -1
  22. package/src/NodeFileSystem.ts +764 -0
  23. package/src/Random.ts +59 -0
  24. package/src/Route.test.ts +515 -18
  25. package/src/Route.ts +321 -166
  26. package/src/RouteRender.ts +40 -0
  27. package/src/Router.test.ts +416 -0
  28. package/src/Router.ts +288 -31
  29. package/src/RouterPattern.test.ts +655 -0
  30. package/src/RouterPattern.ts +416 -0
  31. package/src/Start.ts +14 -52
  32. package/src/TestHttpClient.test.ts +29 -0
  33. package/src/TestHttpClient.ts +122 -73
  34. package/src/assets.d.ts +39 -0
  35. package/src/bun/BunBundle.test.ts +0 -3
  36. package/src/bun/BunHttpServer.test.ts +74 -0
  37. package/src/bun/BunHttpServer.ts +259 -0
  38. package/src/bun/BunHttpServer_web.ts +384 -0
  39. package/src/bun/BunRoute.test.ts +514 -0
  40. package/src/bun/BunRoute.ts +427 -0
  41. package/src/bun/BunRoute_bundles.test.ts +218 -0
  42. package/src/bun/BunRuntime.ts +33 -0
  43. package/src/bun/BunTailwindPlugin.test.ts +1 -1
  44. package/src/bun/_empty.html +1 -0
  45. package/src/bun/index.ts +2 -1
  46. package/src/index.ts +14 -14
  47. package/src/middlewares/BasicAuthMiddleware.test.ts +74 -0
  48. package/src/middlewares/BasicAuthMiddleware.ts +36 -0
  49. package/src/testing.ts +12 -3
  50. package/src/Datastar.test.ts +0 -267
  51. package/src/Datastar.ts +0 -68
  52. package/src/bun/BunFullstackServer.ts +0 -45
  53. package/src/bun/BunFullstackServer_httpServer.ts +0 -541
  54. package/src/jsx-datastar.d.ts +0 -63
@@ -1,541 +0,0 @@
1
- /**
2
- * Taken from @effect/platform-bun@0.65.0
3
- */
4
- import {
5
- BunContext,
6
- BunHttpPlatform as Platform,
7
- BunMultipart as MultipartBun,
8
- } from "@effect/platform-bun"
9
- import * as Cookies from "@effect/platform/Cookies"
10
- import * as Etag from "@effect/platform/Etag"
11
- import * as FetchHttpClient from "@effect/platform/FetchHttpClient"
12
- import type * as FileSystem from "@effect/platform/FileSystem"
13
- import * as Headers from "@effect/platform/Headers"
14
- import * as App from "@effect/platform/HttpApp"
15
- import * as IncomingMessage from "@effect/platform/HttpIncomingMessage"
16
- import type { HttpMethod } from "@effect/platform/HttpMethod"
17
- import * as Server from "@effect/platform/HttpServer"
18
- import * as Error from "@effect/platform/HttpServerError"
19
- import * as ServerRequest from "@effect/platform/HttpServerRequest"
20
- import type * as ServerResponse from "@effect/platform/HttpServerResponse"
21
- import type * as Multipart from "@effect/platform/Multipart"
22
- import type * as Path from "@effect/platform/Path"
23
- import * as Socket from "@effect/platform/Socket"
24
- import * as UrlParams from "@effect/platform/UrlParams"
25
- import type {
26
- ServeOptions,
27
- Server as BunServer,
28
- ServerWebSocket,
29
- } from "bun"
30
- import * as Config from "effect/Config"
31
- import * as Deferred from "effect/Deferred"
32
- import * as Effect from "effect/Effect"
33
- import * as Exit from "effect/Exit"
34
- import * as FiberSet from "effect/FiberSet"
35
- import * as Inspectable from "effect/Inspectable"
36
- import * as Layer from "effect/Layer"
37
- import * as Option from "effect/Option"
38
- import type { ReadonlyRecord } from "effect/Record"
39
- import type * as Runtime from "effect/Runtime"
40
- import type * as Scope from "effect/Scope"
41
- import * as Stream from "effect/Stream"
42
-
43
- /** @internal */
44
- export const make = (
45
- options: Omit<ServeOptions, "fetch" | "error">,
46
- ): Effect.Effect<Server.HttpServer, never, Scope.Scope> =>
47
- Effect.gen(function*() {
48
- const handlerStack: Array<
49
- (request: Request, server: BunServer) => Response | Promise<Response>
50
- > = [
51
- function(_request, _server) {
52
- return new Response("not found", { status: 404 })
53
- },
54
- ]
55
- const server = Bun.serve({
56
- ...options,
57
- fetch: handlerStack[0],
58
- // @ts-ignore
59
- websocket: {
60
- open(ws) {
61
- Deferred.unsafeDone(ws.data.deferred, Exit.succeed(ws))
62
- },
63
- message(ws, message) {
64
- ws.data.run(message)
65
- },
66
- close(ws, code, closeReason) {
67
- Deferred.unsafeDone(
68
- ws.data.closeDeferred,
69
- Socket.defaultCloseCodeIsError(code)
70
- ? Exit.fail(
71
- new Socket.SocketCloseError({
72
- reason: "Close",
73
- code,
74
- closeReason,
75
- }),
76
- )
77
- : Exit.void,
78
- )
79
- },
80
- } as any,
81
- })
82
-
83
- yield* Effect.addFinalizer(() =>
84
- Effect.sync(() => {
85
- server.stop()
86
- })
87
- )
88
-
89
- return Server.make({
90
- address: {
91
- _tag: "TcpAddress",
92
- port: server.port,
93
- hostname: server.hostname,
94
- } as any,
95
- serve(httpApp, middleware) {
96
- return Effect.gen(function*() {
97
- const runFork = yield* FiberSet.makeRuntime<never>()
98
- const runtime = yield* Effect.runtime<never>()
99
- const app = App.toHandled(
100
- httpApp,
101
- (request, response) =>
102
- Effect.sync(() => {
103
- ;(request as ServerRequestImpl).resolve(
104
- makeResponse(request, response, runtime),
105
- )
106
- }),
107
- middleware,
108
- )
109
-
110
- function handler(request: Request, server: BunServer) {
111
- return new Promise<Response>((resolve, _reject) => {
112
- const fiber = runFork(Effect.provideService(
113
- app,
114
- ServerRequest.HttpServerRequest,
115
- new ServerRequestImpl(
116
- request,
117
- resolve,
118
- removeHost(request.url),
119
- server,
120
- ),
121
- ))
122
- request.signal.addEventListener("abort", () => {
123
- runFork(fiber.interruptAsFork(Error.clientAbortFiberId))
124
- }, { once: true })
125
- })
126
- }
127
-
128
- yield* Effect.acquireRelease(
129
- Effect.sync(() => {
130
- handlerStack.push(handler)
131
- server.reload({
132
- fetch: handler,
133
- // @ts-expect-error current effect veresion doesn't support routes
134
- routes: options.routes,
135
- } as ServeOptions)
136
- }),
137
- () =>
138
- Effect.sync(() => {
139
- handlerStack.pop()
140
- server.reload(
141
- {
142
- fetch: handlerStack[handlerStack.length - 1],
143
- // @ts-expect-error current effect veresion doesn't support routes
144
- routes: options.routes,
145
- } as ServeOptions,
146
- )
147
- }),
148
- )
149
- })
150
- },
151
- })
152
- })
153
-
154
- const makeResponse = (
155
- request: ServerRequest.HttpServerRequest,
156
- response: ServerResponse.HttpServerResponse,
157
- runtime: Runtime.Runtime<never>,
158
- ): Response => {
159
- const fields: {
160
- headers: globalThis.Headers
161
- status?: number
162
- statusText?: string
163
- } = {
164
- headers: new globalThis.Headers(response.headers),
165
- status: response.status,
166
- }
167
-
168
- if (!Cookies.isEmpty(response.cookies)) {
169
- for (const header of Cookies.toSetCookieHeaders(response.cookies)) {
170
- fields.headers.append("set-cookie", header)
171
- }
172
- }
173
-
174
- if (response.statusText !== undefined) {
175
- fields.statusText = response.statusText
176
- }
177
-
178
- if (request.method === "HEAD") {
179
- return new Response(undefined, fields)
180
- }
181
- const body = response.body
182
- switch (body._tag) {
183
- case "Empty": {
184
- return new Response(undefined, fields)
185
- }
186
- case "Uint8Array":
187
- case "Raw": {
188
- return new Response(body.body as any, fields)
189
- }
190
- case "FormData": {
191
- return new Response(body.formData as any, fields)
192
- }
193
- case "Stream": {
194
- return new Response(
195
- Stream.toReadableStreamRuntime(body.stream, runtime),
196
- fields,
197
- )
198
- }
199
- }
200
- }
201
-
202
- /** @internal */
203
- export const layerServer = (
204
- options: Omit<ServeOptions, "fetch" | "error">,
205
- ) => Layer.scoped(Server.HttpServer, make(options))
206
-
207
- /** @internal */
208
- export const layerContext = Layer.mergeAll(
209
- Platform.layer,
210
- Etag.layerWeak,
211
- BunContext.layer,
212
- )
213
-
214
- /** @internal */
215
- export const layer = (
216
- options: Omit<ServeOptions, "fetch" | "error">,
217
- ) =>
218
- Layer.mergeAll(
219
- Layer.scoped(Server.HttpServer, make(options)),
220
- layerContext,
221
- )
222
-
223
- /** @internal */
224
- export const layerTest = Server.layerTestClient.pipe(
225
- Layer.provide(FetchHttpClient.layer.pipe(
226
- Layer.provide(
227
- Layer.succeed(FetchHttpClient.RequestInit, { keepalive: false }),
228
- ),
229
- )),
230
- Layer.provideMerge(layer({ port: 0 })),
231
- )
232
-
233
- /** @internal */
234
- export const layerConfig = (
235
- options: Config.Config.Wrap<Omit<ServeOptions, "fetch" | "error">>,
236
- ) =>
237
- Layer.mergeAll(
238
- Layer.scoped(
239
- Server.HttpServer,
240
- Effect.flatMap(Config.unwrap(options), make),
241
- ),
242
- layerContext,
243
- )
244
-
245
- interface WebSocketContext {
246
- readonly deferred: Deferred.Deferred<ServerWebSocket<WebSocketContext>>
247
- readonly closeDeferred: Deferred.Deferred<void, Socket.SocketError>
248
- readonly buffer: Array<Uint8Array | string>
249
- run: (_: Uint8Array | string) => void
250
- }
251
-
252
- function wsDefaultRun(this: WebSocketContext, _: Uint8Array | string) {
253
- this.buffer.push(_)
254
- }
255
-
256
- class ServerRequestImpl extends Inspectable.Class
257
- implements ServerRequest.HttpServerRequest
258
- {
259
- readonly [ServerRequest.TypeId]: ServerRequest.TypeId
260
- readonly [IncomingMessage.TypeId]: IncomingMessage.TypeId
261
- constructor(
262
- readonly source: Request,
263
- public resolve: (response: Response) => void,
264
- readonly url: string,
265
- private bunServer: BunServer,
266
- public headersOverride?: Headers.Headers,
267
- private remoteAddressOverride?: string,
268
- ) {
269
- super()
270
- this[ServerRequest.TypeId] = ServerRequest.TypeId
271
- this[IncomingMessage.TypeId] = IncomingMessage.TypeId
272
- }
273
- toJSON(): unknown {
274
- return IncomingMessage.inspect(this, {
275
- _id: "@effect/platform/HttpServerRequest",
276
- method: this.method,
277
- url: this.originalUrl,
278
- })
279
- }
280
- modify(
281
- options: {
282
- readonly url?: string | undefined
283
- readonly headers?: Headers.Headers | undefined
284
- readonly remoteAddress?: string | undefined
285
- },
286
- ) {
287
- return new ServerRequestImpl(
288
- this.source,
289
- this.resolve,
290
- options.url ?? this.url,
291
- this.bunServer,
292
- options.headers ?? this.headersOverride,
293
- options.remoteAddress ?? this.remoteAddressOverride,
294
- )
295
- }
296
- get method(): HttpMethod {
297
- return this.source.method.toUpperCase() as HttpMethod
298
- }
299
- get originalUrl() {
300
- return this.source.url
301
- }
302
- get remoteAddress(): Option.Option<string> {
303
- return this.remoteAddressOverride
304
- ? Option.some(this.remoteAddressOverride)
305
- : Option.fromNullable(this.bunServer.requestIP(this.source)?.address)
306
- }
307
- get headers(): Headers.Headers {
308
- this.headersOverride ??= Headers.fromInput(this.source.headers)
309
- return this.headersOverride
310
- }
311
-
312
- private cachedCookies: ReadonlyRecord<string, string> | undefined
313
- get cookies() {
314
- if (this.cachedCookies) {
315
- return this.cachedCookies
316
- }
317
- return this.cachedCookies = Cookies.parseHeader(this.headers.cookie ?? "")
318
- }
319
-
320
- get stream(): Stream.Stream<Uint8Array, Error.RequestError> {
321
- return this.source.body
322
- ? Stream.fromReadableStream(
323
- () => this.source.body as any,
324
- (cause) =>
325
- new Error.RequestError({
326
- request: this,
327
- reason: "Decode",
328
- cause,
329
- }),
330
- )
331
- : Stream.fail(
332
- new Error.RequestError({
333
- request: this,
334
- reason: "Decode",
335
- description: "can not create stream from empty body",
336
- }),
337
- )
338
- }
339
-
340
- private textEffect: Effect.Effect<string, Error.RequestError> | undefined
341
- get text(): Effect.Effect<string, Error.RequestError> {
342
- if (this.textEffect) {
343
- return this.textEffect
344
- }
345
- this.textEffect = Effect.runSync(Effect.cached(
346
- Effect.tryPromise({
347
- try: () => this.source.text(),
348
- catch: (cause) =>
349
- new Error.RequestError({
350
- request: this,
351
- reason: "Decode",
352
- cause,
353
- }),
354
- }),
355
- ))
356
- return this.textEffect
357
- }
358
-
359
- get json(): Effect.Effect<unknown, Error.RequestError> {
360
- return Effect.tryMap(this.text, {
361
- try: (_) => JSON.parse(_) as unknown,
362
- catch: (cause) =>
363
- new Error.RequestError({
364
- request: this,
365
- reason: "Decode",
366
- cause,
367
- }),
368
- })
369
- }
370
-
371
- get urlParamsBody(): Effect.Effect<UrlParams.UrlParams, Error.RequestError> {
372
- return Effect.flatMap(this.text, (_) =>
373
- Effect.try({
374
- try: () => UrlParams.fromInput(new URLSearchParams(_)),
375
- catch: (cause) =>
376
- new Error.RequestError({
377
- request: this,
378
- reason: "Decode",
379
- cause,
380
- }),
381
- }))
382
- }
383
-
384
- private multipartEffect:
385
- | Effect.Effect<
386
- Multipart.Persisted,
387
- Multipart.MultipartError,
388
- Scope.Scope | FileSystem.FileSystem | Path.Path
389
- >
390
- | undefined
391
- get multipart(): Effect.Effect<
392
- Multipart.Persisted,
393
- Multipart.MultipartError,
394
- Scope.Scope | FileSystem.FileSystem | Path.Path
395
- > {
396
- if (this.multipartEffect) {
397
- return this.multipartEffect
398
- }
399
- this.multipartEffect = Effect.runSync(Effect.cached(
400
- MultipartBun.persisted(this.source),
401
- ))
402
- return this.multipartEffect
403
- }
404
-
405
- get multipartStream(): Stream.Stream<
406
- Multipart.Part,
407
- Multipart.MultipartError
408
- > {
409
- return MultipartBun.stream(this.source)
410
- }
411
-
412
- private arrayBufferEffect:
413
- | Effect.Effect<ArrayBuffer, Error.RequestError>
414
- | undefined
415
- get arrayBuffer(): Effect.Effect<ArrayBuffer, Error.RequestError> {
416
- if (this.arrayBufferEffect) {
417
- return this.arrayBufferEffect
418
- }
419
- this.arrayBufferEffect = Effect.runSync(Effect.cached(
420
- Effect.tryPromise({
421
- try: () => this.source.arrayBuffer(),
422
- catch: (cause) =>
423
- new Error.RequestError({
424
- request: this,
425
- reason: "Decode",
426
- cause,
427
- }),
428
- }),
429
- ))
430
- return this.arrayBufferEffect
431
- }
432
-
433
- get upgrade(): Effect.Effect<Socket.Socket, Error.RequestError> {
434
- return Effect.flatMap(
435
- Effect.all([
436
- Deferred.make<ServerWebSocket<WebSocketContext>>(),
437
- Deferred.make<void, Socket.SocketError>(),
438
- Effect.makeSemaphore(1),
439
- ]),
440
- ([deferred, closeDeferred, semaphore]) =>
441
- Effect.async<Socket.Socket, Error.RequestError>((resume) => {
442
- const success = this.bunServer.upgrade<WebSocketContext>(
443
- this.source,
444
- {
445
- data: {
446
- deferred,
447
- closeDeferred,
448
- buffer: [],
449
- run: wsDefaultRun,
450
- },
451
- },
452
- )
453
- if (!success) {
454
- resume(Effect.fail(
455
- new Error.RequestError({
456
- request: this,
457
- reason: "Decode",
458
- description: "Not an upgradeable ServerRequest",
459
- }),
460
- ))
461
- return
462
- }
463
- resume(Effect.map(Deferred.await(deferred), (ws) => {
464
- const write = (chunk: Uint8Array | string | Socket.CloseEvent) =>
465
- Effect.sync(() => {
466
- if (typeof chunk === "string") {
467
- ws.sendText(chunk)
468
- } else if (Socket.isCloseEvent(chunk)) {
469
- ws.close(chunk.code, chunk.reason)
470
- } else {
471
- ws.sendBinary(chunk)
472
- }
473
-
474
- return true
475
- })
476
- const writer = Effect.succeed(write)
477
- const runRaw = <R, E, _>(
478
- handler: (
479
- _: Uint8Array | string,
480
- ) => Effect.Effect<_, E, R> | void,
481
- ): Effect.Effect<void, Socket.SocketError | E, R> =>
482
- FiberSet.make<any, E>().pipe(
483
- Effect.flatMap((set) =>
484
- FiberSet.runtime(set)<R>().pipe(
485
- Effect.flatMap((run) => {
486
- function runRaw(data: Uint8Array | string) {
487
- const result = handler(data)
488
- if (Effect.isEffect(result)) {
489
- run(result)
490
- }
491
- }
492
- ws.data.run = runRaw
493
- ws.data.buffer.forEach(runRaw)
494
- ws.data.buffer.length = 0
495
- return FiberSet.join(set)
496
- }),
497
- )
498
- ),
499
- Effect.scoped,
500
- Effect.onExit((exit) =>
501
- Effect.sync(() =>
502
- ws.close(exit._tag === "Success" ? 1000 : 1011)
503
- )
504
- ),
505
- Effect.raceFirst(Deferred.await(closeDeferred)),
506
- semaphore.withPermits(1),
507
- )
508
-
509
- const encoder = new TextEncoder()
510
- const run = <R, E, _>(
511
- handler: (_: Uint8Array) => Effect.Effect<_, E, R> | void,
512
- ) =>
513
- runRaw((data) =>
514
- typeof data === "string"
515
- ? handler(encoder.encode(data))
516
- : handler(data)
517
- )
518
-
519
- return Socket.Socket.of({
520
- [Socket.TypeId]: Socket.TypeId,
521
- run,
522
- runRaw,
523
- writer,
524
- })
525
- }))
526
- }),
527
- )
528
- }
529
- }
530
-
531
- const removeHost = (url: string) => {
532
- if (url[0] === "/") {
533
- return url
534
- }
535
- const index = url.indexOf("/", url.indexOf("//") + 2)
536
- return index === -1 ? "/" : url.slice(index)
537
- }
538
-
539
- /** @internal */
540
- export const requestSource = (self: ServerRequest.HttpServerRequest) =>
541
- (self as ServerRequestImpl).source
@@ -1,63 +0,0 @@
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
- }