effect-start 0.18.0 → 0.20.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 (203) hide show
  1. package/README.md +3 -3
  2. package/dist/Development.d.ts +8 -3
  3. package/dist/Development.js +14 -7
  4. package/dist/Effectify.d.ts +212 -0
  5. package/dist/Effectify.js +19 -0
  6. package/dist/FilePathPattern.d.ts +29 -0
  7. package/dist/FilePathPattern.js +86 -0
  8. package/dist/FileRouter.d.ts +39 -41
  9. package/dist/FileRouter.js +104 -158
  10. package/dist/FileRouterCodegen.d.ts +7 -8
  11. package/dist/FileRouterCodegen.js +97 -66
  12. package/dist/PlatformError.d.ts +46 -0
  13. package/dist/PlatformError.js +43 -0
  14. package/dist/PlatformRuntime.d.ts +27 -0
  15. package/dist/PlatformRuntime.js +51 -0
  16. package/dist/Route.d.ts +6 -2
  17. package/dist/Route.js +22 -0
  18. package/dist/RouteBody.d.ts +1 -1
  19. package/dist/RouteHttp.d.ts +1 -1
  20. package/dist/RouteHttp.js +12 -19
  21. package/dist/RouteMount.d.ts +2 -1
  22. package/dist/Start.d.ts +33 -6
  23. package/dist/Start.js +31 -13
  24. package/dist/Unique.d.ts +50 -0
  25. package/dist/Unique.js +187 -0
  26. package/dist/bun/BunHttpServer.js +5 -6
  27. package/dist/bun/BunPlatformHttpServer.d.ts +10 -0
  28. package/dist/bun/BunPlatformHttpServer.js +53 -0
  29. package/dist/bun/BunRoute.d.ts +4 -6
  30. package/dist/bun/BunRoute.js +10 -18
  31. package/dist/bun/BunRuntime.d.ts +2 -1
  32. package/dist/bun/BunRuntime.js +10 -5
  33. package/dist/bun/BunServer.d.ts +33 -0
  34. package/dist/bun/BunServer.js +133 -0
  35. package/dist/bun/BunServerRequest.d.ts +60 -0
  36. package/dist/bun/BunServerRequest.js +252 -0
  37. package/dist/bun/index.d.ts +1 -1
  38. package/dist/bun/index.js +1 -1
  39. package/dist/datastar/actions/fetch.d.ts +30 -0
  40. package/dist/datastar/actions/fetch.js +411 -0
  41. package/dist/datastar/actions/peek.d.ts +1 -0
  42. package/dist/datastar/actions/peek.js +14 -0
  43. package/dist/datastar/actions/setAll.d.ts +1 -0
  44. package/dist/datastar/actions/setAll.js +13 -0
  45. package/dist/datastar/actions/toggleAll.d.ts +1 -0
  46. package/dist/datastar/actions/toggleAll.js +13 -0
  47. package/dist/datastar/attributes/attr.d.ts +1 -0
  48. package/dist/datastar/attributes/attr.js +49 -0
  49. package/dist/datastar/attributes/bind.d.ts +1 -0
  50. package/dist/datastar/attributes/bind.js +183 -0
  51. package/dist/datastar/attributes/class.d.ts +1 -0
  52. package/dist/datastar/attributes/class.js +50 -0
  53. package/dist/datastar/attributes/computed.d.ts +1 -0
  54. package/dist/datastar/attributes/computed.js +27 -0
  55. package/dist/datastar/attributes/effect.d.ts +1 -0
  56. package/dist/datastar/attributes/effect.js +10 -0
  57. package/dist/datastar/attributes/indicator.d.ts +1 -0
  58. package/dist/datastar/attributes/indicator.js +32 -0
  59. package/dist/datastar/attributes/init.d.ts +1 -0
  60. package/dist/datastar/attributes/init.js +27 -0
  61. package/dist/datastar/attributes/jsonSignals.d.ts +1 -0
  62. package/dist/datastar/attributes/jsonSignals.js +31 -0
  63. package/dist/datastar/attributes/on.d.ts +1 -0
  64. package/dist/datastar/attributes/on.js +59 -0
  65. package/dist/datastar/attributes/onIntersect.d.ts +1 -0
  66. package/dist/datastar/attributes/onIntersect.js +54 -0
  67. package/dist/datastar/attributes/onInterval.d.ts +1 -0
  68. package/dist/datastar/attributes/onInterval.js +31 -0
  69. package/dist/datastar/attributes/onSignalPatch.d.ts +1 -0
  70. package/dist/datastar/attributes/onSignalPatch.js +44 -0
  71. package/dist/datastar/attributes/ref.d.ts +1 -0
  72. package/dist/datastar/attributes/ref.js +11 -0
  73. package/dist/datastar/attributes/show.d.ts +1 -0
  74. package/dist/datastar/attributes/show.js +32 -0
  75. package/dist/datastar/attributes/signals.d.ts +1 -0
  76. package/dist/datastar/attributes/signals.js +18 -0
  77. package/dist/datastar/attributes/style.d.ts +1 -0
  78. package/dist/datastar/attributes/style.js +56 -0
  79. package/dist/datastar/attributes/text.d.ts +1 -0
  80. package/dist/datastar/attributes/text.js +27 -0
  81. package/dist/datastar/engine.d.ts +156 -0
  82. package/dist/datastar/engine.js +971 -0
  83. package/dist/datastar/index.d.ts +24 -0
  84. package/dist/datastar/index.js +24 -0
  85. package/dist/datastar/load.d.ts +24 -0
  86. package/dist/datastar/load.js +24 -0
  87. package/dist/datastar/utils.d.ts +51 -0
  88. package/dist/datastar/utils.js +205 -0
  89. package/dist/datastar/watchers/patchElements.d.ts +1 -0
  90. package/dist/datastar/watchers/patchElements.js +420 -0
  91. package/dist/datastar/watchers/patchSignals.d.ts +1 -0
  92. package/dist/datastar/watchers/patchSignals.js +15 -0
  93. package/dist/index.d.ts +1 -0
  94. package/dist/index.js +1 -0
  95. package/dist/node/Effectify.d.ts +209 -0
  96. package/dist/node/Effectify.js +19 -0
  97. package/dist/node/FileSystem.d.ts +3 -5
  98. package/dist/node/FileSystem.js +42 -62
  99. package/dist/node/NodeFileSystem.d.ts +7 -0
  100. package/dist/node/NodeFileSystem.js +420 -0
  101. package/dist/node/NodeUtils.d.ts +2 -0
  102. package/dist/node/NodeUtils.js +20 -0
  103. package/dist/node/PlatformError.d.ts +46 -0
  104. package/dist/node/PlatformError.js +43 -0
  105. package/dist/testing/TestLogger.js +1 -1
  106. package/dist/x/tailwind/plugin.js +1 -1
  107. package/package.json +18 -7
  108. package/src/Development.ts +36 -40
  109. package/src/Effectify.ts +269 -0
  110. package/src/FilePathPattern.ts +115 -0
  111. package/src/FileRouter.ts +178 -255
  112. package/src/FileRouterCodegen.ts +135 -92
  113. package/src/PlatformError.ts +117 -0
  114. package/src/PlatformRuntime.ts +108 -0
  115. package/src/Route.ts +31 -2
  116. package/src/RouteBody.ts +1 -1
  117. package/src/RouteHttp.ts +15 -29
  118. package/src/RouteMount.ts +1 -1
  119. package/src/Start.ts +61 -27
  120. package/src/Unique.ts +232 -0
  121. package/src/bun/BunPlatformHttpServer.ts +88 -0
  122. package/src/bun/BunRoute.ts +14 -24
  123. package/src/bun/BunRuntime.ts +21 -5
  124. package/src/bun/BunServer.ts +228 -0
  125. package/src/bun/index.ts +1 -1
  126. package/src/datastar/README.md +18 -0
  127. package/src/datastar/actions/fetch.ts +609 -0
  128. package/src/datastar/actions/peek.ts +17 -0
  129. package/src/datastar/actions/setAll.ts +20 -0
  130. package/src/datastar/actions/toggleAll.ts +20 -0
  131. package/src/datastar/attributes/attr.ts +50 -0
  132. package/src/datastar/attributes/bind.ts +220 -0
  133. package/src/datastar/attributes/class.ts +57 -0
  134. package/src/datastar/attributes/computed.ts +33 -0
  135. package/src/datastar/attributes/effect.ts +11 -0
  136. package/src/datastar/attributes/indicator.ts +39 -0
  137. package/src/datastar/attributes/init.ts +35 -0
  138. package/src/datastar/attributes/jsonSignals.ts +38 -0
  139. package/src/datastar/attributes/on.ts +71 -0
  140. package/src/datastar/attributes/onIntersect.ts +65 -0
  141. package/src/datastar/attributes/onInterval.ts +39 -0
  142. package/src/datastar/attributes/onSignalPatch.ts +63 -0
  143. package/src/datastar/attributes/ref.ts +12 -0
  144. package/src/datastar/attributes/show.ts +33 -0
  145. package/src/datastar/attributes/signals.ts +22 -0
  146. package/src/datastar/attributes/style.ts +63 -0
  147. package/src/datastar/attributes/text.ts +30 -0
  148. package/src/datastar/engine.ts +1341 -0
  149. package/src/datastar/index.ts +25 -0
  150. package/src/datastar/utils.ts +286 -0
  151. package/src/datastar/watchers/patchElements.ts +554 -0
  152. package/src/datastar/watchers/patchSignals.ts +15 -0
  153. package/src/index.ts +1 -0
  154. package/src/node/{FileSystem.ts → NodeFileSystem.ts} +59 -97
  155. package/src/node/{Utils.ts → NodeUtils.ts} +2 -0
  156. package/src/testing/TestLogger.ts +1 -1
  157. package/src/x/tailwind/plugin.ts +1 -1
  158. package/dist/Random.d.ts +0 -5
  159. package/dist/Random.js +0 -49
  160. package/src/Commander.test.ts +0 -1639
  161. package/src/ContentNegotiation.test.ts +0 -603
  162. package/src/Development.test.ts +0 -119
  163. package/src/Entity.test.ts +0 -592
  164. package/src/FileRouterCodegen.todo.ts +0 -1133
  165. package/src/FileRouterPattern.test.ts +0 -147
  166. package/src/FileRouterPattern.ts +0 -59
  167. package/src/FileRouter_files.test.ts +0 -64
  168. package/src/FileRouter_path.test.ts +0 -145
  169. package/src/FileRouter_tree.test.ts +0 -132
  170. package/src/Http.test.ts +0 -319
  171. package/src/HttpAppExtra.test.ts +0 -103
  172. package/src/HttpUtils.test.ts +0 -85
  173. package/src/PathPattern.test.ts +0 -648
  174. package/src/Random.ts +0 -59
  175. package/src/RouteBody.test.ts +0 -232
  176. package/src/RouteHook.test.ts +0 -40
  177. package/src/RouteHttp.test.ts +0 -2909
  178. package/src/RouteMount.test.ts +0 -481
  179. package/src/RouteSchema.test.ts +0 -427
  180. package/src/RouteSse.test.ts +0 -249
  181. package/src/RouteTree.test.ts +0 -494
  182. package/src/RouteTrie.test.ts +0 -322
  183. package/src/RouterPattern.test.ts +0 -676
  184. package/src/RouterPattern.ts +0 -416
  185. package/src/StartApp.ts +0 -47
  186. package/src/Values.test.ts +0 -263
  187. package/src/bun/BunBundle.test.ts +0 -268
  188. package/src/bun/BunBundle_imports.test.ts +0 -48
  189. package/src/bun/BunHttpServer.test.ts +0 -251
  190. package/src/bun/BunHttpServer.ts +0 -306
  191. package/src/bun/BunImportTrackerPlugin.test.ts +0 -77
  192. package/src/bun/BunRoute.test.ts +0 -162
  193. package/src/bundler/BundleHttp.test.ts +0 -132
  194. package/src/effect/HttpRouter.test.ts +0 -548
  195. package/src/experimental/EncryptedCookies.test.ts +0 -488
  196. package/src/hyper/HyperHtml.test.ts +0 -209
  197. package/src/hyper/HyperRoute.test.tsx +0 -197
  198. package/src/middlewares/BasicAuthMiddleware.test.ts +0 -84
  199. package/src/testing/TestHttpClient.test.ts +0 -83
  200. package/src/testing/TestLogger.test.ts +0 -51
  201. package/src/x/datastar/Datastar.test.ts +0 -266
  202. package/src/x/tailwind/TailwindPlugin.test.ts +0 -333
  203. /package/src/bun/{BunHttpServer_web.ts → BunServerRequest.ts} +0 -0
@@ -1,4 +1,3 @@
1
- // @ts-nocheck
2
1
  import * as HttpApp from "@effect/platform/HttpApp";
3
2
  import * as HttpServer from "@effect/platform/HttpServer";
4
3
  import * as HttpServerError from "@effect/platform/HttpServerError";
@@ -14,19 +13,19 @@ import * as FiberSet from "effect/FiberSet";
14
13
  import * as Layer from "effect/Layer";
15
14
  import * as Option from "effect/Option";
16
15
  import * as PathPattern from "../PathPattern.js";
17
- import * as Random from "../Random.js";
16
+ import * as PlataformRuntime from "../PlatformRuntime.js";
18
17
  import * as Route from "../Route.js";
19
18
  import * as RouteHttp from "../RouteHttp.js";
20
19
  import * as RouteTree from "../RouteTree.js";
20
+ import * as Unique from "../Unique.js";
21
21
  import EmptyHTML from "./_empty.html";
22
22
  import { makeResponse, ServerRequestImpl, } from "./BunHttpServer_web.js";
23
23
  import * as BunRoute from "./BunRoute.js";
24
24
  export const BunHttpServer = Context.GenericTag("effect-start/BunServer");
25
25
  export const make = (options) => Effect.gen(function* () {
26
26
  const port = yield* Config.number("PORT").pipe(Effect.catchTag("ConfigError", () => {
27
- if (typeof process !== "undefined"
28
- && !process.stdout.isTTY
29
- && process.env.CLAUDECODE) {
27
+ if (PlataformRuntime.isAgentHarness()) {
28
+ // use random port
30
29
  return Effect.succeed(0);
31
30
  }
32
31
  return Effect.succeed(3000);
@@ -41,7 +40,7 @@ export const make = (options) => Effect.gen(function* () {
41
40
  // Bun HMR doesn't work on successive calls to `server.reload` if there are no routes
42
41
  // on server start. We workaround that by passing a dummy HTMLBundle [2025-11-26]
43
42
  // see: https://github.com/oven-sh/bun/issues/23564
44
- currentRoutes[`/.BunEmptyHtml-${Random.token(6)}`] = EmptyHTML;
43
+ currentRoutes[`/.BunEmptyHtml-${Unique.token(10)}`] = EmptyHTML;
45
44
  const websocket = {
46
45
  open(ws) {
47
46
  Deferred.unsafeDone(ws.data.deferred, Exit.succeed(ws));
@@ -0,0 +1,10 @@
1
+ import * as HttpServer from "@effect/platform/HttpServer";
2
+ import * as Effect from "effect/Effect";
3
+ import type * as Scope from "effect/Scope";
4
+ import * as BunServer from "./BunServer.ts";
5
+ /**
6
+ * From times when we used @effect/platform
7
+ * Not used any more internally. Kept for the future,
8
+ * in case someone will need it for whatever reason. [2026]
9
+ */
10
+ export declare const make: Effect.Effect<HttpServer.HttpServer, never, Scope.Scope | BunServer.BunServer>;
@@ -0,0 +1,53 @@
1
+ import * as HttpApp from "@effect/platform/HttpApp";
2
+ import * as HttpServer from "@effect/platform/HttpServer";
3
+ import * as HttpServerError from "@effect/platform/HttpServerError";
4
+ import * as HttpServerRequest from "@effect/platform/HttpServerRequest";
5
+ import * as Effect from "effect/Effect";
6
+ import * as FiberSet from "effect/FiberSet";
7
+ import * as BunServer from "./BunServer.js";
8
+ import * as BunServerRequest from "./BunServerRequest.js";
9
+ /**
10
+ * From times when we used @effect/platform
11
+ * Not used any more internally. Kept for the future,
12
+ * in case someone will need it for whatever reason. [2026]
13
+ */
14
+ export const make = Effect.gen(function* () {
15
+ const bunServer = yield* BunServer.BunServer;
16
+ return HttpServer.make({
17
+ address: {
18
+ _tag: "TcpAddress",
19
+ port: bunServer.server.port,
20
+ hostname: bunServer.server.hostname,
21
+ },
22
+ serve(httpApp, middleware) {
23
+ return Effect.gen(function* () {
24
+ const runFork = yield* FiberSet.makeRuntime();
25
+ const runtime = yield* Effect.runtime();
26
+ const app = HttpApp.toHandled(httpApp, (request, response) => Effect.sync(() => {
27
+ ;
28
+ request.resolve(BunServerRequest.makeResponse(request, response, runtime));
29
+ }), middleware);
30
+ function handler(request, server) {
31
+ return new Promise((resolve, _reject) => {
32
+ const fiber = runFork(Effect.provideService(app, HttpServerRequest.HttpServerRequest, new BunServerRequest.ServerRequestImpl(request, resolve, removeHost(request.url), server)));
33
+ request.signal.addEventListener("abort", () => {
34
+ runFork(fiber.interruptAsFork(HttpServerError.clientAbortFiberId));
35
+ }, { once: true });
36
+ });
37
+ }
38
+ yield* Effect.acquireRelease(Effect.sync(() => {
39
+ bunServer.pushHandler(handler);
40
+ }), () => Effect.sync(() => {
41
+ bunServer.popHandler();
42
+ }));
43
+ });
44
+ },
45
+ });
46
+ });
47
+ const removeHost = (url) => {
48
+ if (url[0] === "/") {
49
+ return url;
50
+ }
51
+ const index = url.indexOf("/", url.indexOf("//") + 2);
52
+ return index === -1 ? "/" : url.slice(index);
53
+ };
@@ -1,7 +1,7 @@
1
1
  import type * as Bun from "bun";
2
2
  import * as Option from "effect/Option";
3
3
  import * as Route from "../Route.ts";
4
- import * as BunHttpServer from "./BunHttpServer.ts";
4
+ import * as BunServer from "./BunServer.ts";
5
5
  declare const BunRouteError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => import("effect/Cause").YieldableError & {
6
6
  readonly _tag: "BunRouteError";
7
7
  } & Readonly<A>;
@@ -15,14 +15,14 @@ export type BunDescriptors = {
15
15
  bunPrefix: string;
16
16
  bunLoad: () => Promise<Bun.HTMLBundle>;
17
17
  };
18
- export declare function descriptors(route: Route.Route.Route): BunDescriptors | undefined;
18
+ export declare function descriptors(route: Route.Route.Route<any, any, any, any, any>): BunDescriptors | undefined;
19
19
  export declare function htmlBundle(load: () => Promise<Bun.HTMLBundle | {
20
20
  default: Bun.HTMLBundle;
21
21
  }>): <D extends Route.RouteDescriptor.Any, B extends {}, I extends Route.Route.Tuple>(self: Route.RouteSet.RouteSet<D, B, I>) => Route.RouteSet.RouteSet<D, B, [...I, Route.Route.Route<BunDescriptors & {
22
22
  format: "html";
23
23
  }, {
24
24
  request: Request;
25
- }, string, BunRouteError, BunHttpServer.BunHttpServer>]>;
25
+ }, string, BunRouteError, BunServer.BunServer>]>;
26
26
  type BunServerFetchHandler = (request: Request, server: Bun.Server<unknown>) => Response | Promise<Response>;
27
27
  type BunServerRouteHandler = Bun.HTMLBundle | BunServerFetchHandler | Partial<Record<Bun.Serve.HTTPMethod, BunServerFetchHandler>>;
28
28
  export type BunRoutes = Record<string, BunServerRouteHandler>;
@@ -33,9 +33,7 @@ export type BunRoutes = Record<string, BunServerRouteHandler>;
33
33
  * - /exact - Exact match
34
34
  * - /users/:id - Full-segment named param
35
35
  * - /path/* - Directory wildcard
36
- * - /* - Catch-all
37
- * - /[[id]] - Optional param (implemented via `/` and `/:id`)
38
- * - /[[...rest]] - Optional rest param (implemented via `/` and `/*`)
36
+ * - /[[404]] - Catch-all / Rest
39
37
  *
40
38
  * Unsupported patterns (cannot be implemented in Bun):
41
39
  * - /pk_[id] - Prefix before param
@@ -3,12 +3,12 @@ import * as Data from "effect/Data";
3
3
  import * as Effect from "effect/Effect";
4
4
  import * as Option from "effect/Option";
5
5
  import * as Entity from "../Entity.js";
6
+ import * as FilePathPattern from "../FilePathPattern.js";
6
7
  import * as Hyper from "../hyper/Hyper.js";
7
8
  import * as HyperHtml from "../hyper/HyperHtml.js";
8
- import * as Random from "../Random.js";
9
9
  import * as Route from "../Route.js";
10
- import * as RouterPattern from "../RouterPattern.js";
11
- import * as BunHttpServer from "./BunHttpServer.js";
10
+ import * as Unique from "../Unique.js";
11
+ import * as BunServer from "./BunServer.js";
12
12
  const INTERNAL_FETCH_HEADER = "x-effect-start-internal-fetch";
13
13
  export class BunRouteError extends Data.TaggedError("BunRouteError") {
14
14
  }
@@ -21,7 +21,7 @@ export function descriptors(route) {
21
21
  return undefined;
22
22
  }
23
23
  export function htmlBundle(load) {
24
- const bunPrefix = `/.BunRoute-${Random.token(6)}`;
24
+ const bunPrefix = `/.BunRoute-${Unique.token(10)}`;
25
25
  const bunLoad = () => load().then(mod => "default" in mod ? mod.default : mod);
26
26
  const descriptors = { bunPrefix, bunLoad, format: "html" };
27
27
  return function (self) {
@@ -35,7 +35,7 @@ export function htmlBundle(load) {
35
35
  message: "Request to internal Bun server was caught by BunRoute handler. This should not happen. Please report a bug.",
36
36
  }));
37
37
  }
38
- const bunServer = yield* BunHttpServer.BunHttpServer;
38
+ const bunServer = yield* BunServer.BunServer;
39
39
  const url = new URL(originalRequest.url);
40
40
  const internalPath = `${bunPrefix}${url.pathname}`;
41
41
  const internalUrl = new URL(internalPath, bunServer.server.url);
@@ -94,9 +94,7 @@ export function htmlBundle(load) {
94
94
  * - /exact - Exact match
95
95
  * - /users/:id - Full-segment named param
96
96
  * - /path/* - Directory wildcard
97
- * - /* - Catch-all
98
- * - /[[id]] - Optional param (implemented via `/` and `/:id`)
99
- * - /[[...rest]] - Optional rest param (implemented via `/` and `/*`)
97
+ * - /[[404]] - Catch-all / Rest
100
98
  *
101
99
  * Unsupported patterns (cannot be implemented in Bun):
102
100
  * - /pk_[id] - Prefix before param
@@ -106,19 +104,13 @@ export function htmlBundle(load) {
106
104
  * - /hello-* - Inline prefix wildcard
107
105
  */
108
106
  export function validateBunPattern(pattern) {
109
- const segments = RouterPattern.parse(pattern);
110
- const unsupported = Array.findFirst(segments, (seg) => {
111
- if (seg._tag === "ParamSegment") {
112
- return seg.prefix !== undefined || seg.suffix !== undefined;
113
- }
114
- return false;
115
- });
116
- if (Option.isSome(unsupported)) {
107
+ const segs = FilePathPattern.segments(pattern);
108
+ const invalid = Array.findFirst(segs, (seg) => seg._tag === "InvalidSegment");
109
+ if (Option.isSome(invalid)) {
117
110
  return Option.some(new BunRouteError({
118
111
  reason: "UnsupportedPattern",
119
112
  pattern,
120
- message: `Pattern "${pattern}" uses prefixed/suffixed params (prefix_[param] or [param]_suffix) `
121
- + `which cannot be implemented in Bun.serve.`,
113
+ message: `Pattern "${pattern}" contains invalid segment.`,
122
114
  }));
123
115
  }
124
116
  return Option.none();
@@ -1 +1,2 @@
1
- export declare const runMain: import("@effect/platform/Runtime").RunMain;
1
+ import * as PlatformRuntime from "../PlatformRuntime.ts";
2
+ export declare const runMain: PlatformRuntime.RunMain;
@@ -1,14 +1,16 @@
1
- import { makeRunMain } from "@effect/platform/Runtime";
2
- import { constVoid } from "effect/Function";
3
- export const runMain = makeRunMain(({ fiber, teardown, }) => {
4
- const keepAlive = setInterval(constVoid, 2 ** 31 - 1);
1
+ import * as GlobalValue from "effect/GlobalValue";
2
+ import * as MutableRef from "effect/MutableRef";
3
+ import * as PlatformRuntime from "../PlatformRuntime.js";
4
+ const mainFiber = GlobalValue.globalValue(Symbol.for("effect-start/BunRuntime/existingFiber"), () => MutableRef.make(undefined));
5
+ export const runMain = PlatformRuntime.makeRunMain(({ fiber, teardown, }) => {
6
+ const prevFiber = MutableRef.get(mainFiber);
7
+ MutableRef.set(mainFiber, fiber);
5
8
  let receivedSignal = false;
6
9
  fiber.addObserver((exit) => {
7
10
  if (!receivedSignal) {
8
11
  process.removeListener("SIGINT", onSigint);
9
12
  process.removeListener("SIGTERM", onSigint);
10
13
  }
11
- clearInterval(keepAlive);
12
14
  teardown(exit, (code) => {
13
15
  if (receivedSignal || code !== 0) {
14
16
  process.exit(code);
@@ -23,4 +25,7 @@ export const runMain = makeRunMain(({ fiber, teardown, }) => {
23
25
  }
24
26
  process.on("SIGINT", onSigint);
25
27
  process.on("SIGTERM", onSigint);
28
+ if (prevFiber) {
29
+ prevFiber.unsafeInterruptAsFork(prevFiber.id());
30
+ }
26
31
  });
@@ -0,0 +1,33 @@
1
+ import * as Bun from "bun";
2
+ import * as Context from "effect/Context";
3
+ import * as Effect from "effect/Effect";
4
+ import * as Layer from "effect/Layer";
5
+ import type * as Scope from "effect/Scope";
6
+ import * as BunServerRequest from "./BunServerRequest.ts";
7
+ type FetchHandler = (request: Request, server: Bun.Server<BunServerRequest.WebSocketContext>) => Response | Promise<Response>;
8
+ /**
9
+ * Basically `Omit<Bun.Serve.Options, "fetch" | "error" | "websocket">`
10
+ * TypeScript 5.9 cannot verify discriminated union types used in
11
+ * {@link Bun.serve} so we need to define them explicitly.
12
+ */
13
+ interface BunServeOptions {
14
+ readonly port?: number;
15
+ readonly hostname?: string;
16
+ readonly reusePort?: boolean;
17
+ readonly ipv6Only?: boolean;
18
+ readonly idleTimeout?: number;
19
+ readonly development?: boolean;
20
+ }
21
+ export type BunServer = {
22
+ readonly server: Bun.Server<BunServerRequest.WebSocketContext>;
23
+ readonly pushHandler: (fetch: FetchHandler) => void;
24
+ readonly popHandler: () => void;
25
+ };
26
+ export declare const BunServer: Context.Tag<BunServer, BunServer>;
27
+ export declare const make: (options: BunServeOptions) => Effect.Effect<BunServer, never, Scope.Scope>;
28
+ /**
29
+ * Provides HttpServer using BunServer under the hood.
30
+ */
31
+ export declare const layer: (options?: BunServeOptions) => Layer.Layer<BunServer>;
32
+ export declare const withLogAddress: <A, E, R>(layer: Layer.Layer<A, E, R>) => Layer.Layer<A, E, R | Exclude<BunServer, A>>;
33
+ export {};
@@ -0,0 +1,133 @@
1
+ import * as Socket from "@effect/platform/Socket";
2
+ import * as Bun from "bun";
3
+ import * as Config from "effect/Config";
4
+ import * as Context from "effect/Context";
5
+ import * as Deferred from "effect/Deferred";
6
+ import * as Effect from "effect/Effect";
7
+ import * as Exit from "effect/Exit";
8
+ import * as Layer from "effect/Layer";
9
+ import * as Option from "effect/Option";
10
+ import * as Runtime from "effect/Runtime";
11
+ import * as PathPattern from "../PathPattern.js";
12
+ import * as PlataformRuntime from "../PlatformRuntime.js";
13
+ import * as Route from "../Route.js";
14
+ import * as RouteHttp from "../RouteHttp.js";
15
+ import * as RouteTree from "../RouteTree.js";
16
+ import * as BunRoute from "./BunRoute.js";
17
+ export const BunServer = Context.GenericTag("effect-start/BunServer");
18
+ export const make = (options) => Effect.gen(function* () {
19
+ const routes = yield* Effect.serviceOption(Route.Routes).pipe(Effect.andThen(Option.getOrUndefined));
20
+ const port = yield* Config.number("PORT").pipe(Effect.catchTag("ConfigError", () => {
21
+ return PlataformRuntime.isAgentHarness()
22
+ ? Effect.succeed(0) // random port
23
+ : Effect.succeed(3000);
24
+ }));
25
+ const hostname = yield* Config.string("HOSTNAME").pipe(Effect.catchTag("ConfigError", () => Effect.succeed(undefined)));
26
+ const handlerStack = [
27
+ function (_request, _server) {
28
+ return new Response("not found", { status: 404 });
29
+ },
30
+ ];
31
+ const service = BunServer.of({
32
+ // During the construction we need to create a service imlpementation
33
+ // first so we can provide it in the runtime that will be used in web
34
+ // handlers. After we create the runtime, we set it below so it's always
35
+ // available at runtime.
36
+ // An alternative approach would be to use Bun.Server.reload but I prefer
37
+ // to avoid it since it's badly documented and has bunch of bugs.
38
+ server: undefined,
39
+ pushHandler(fetch) {
40
+ handlerStack.push(fetch);
41
+ reload();
42
+ },
43
+ popHandler() {
44
+ handlerStack.pop();
45
+ reload();
46
+ },
47
+ });
48
+ const runtime = yield* Effect.runtime().pipe(Effect.andThen(Runtime.provideService(BunServer, service)));
49
+ let currentRoutes = routes
50
+ ? yield* walkBunRoutes(runtime, routes)
51
+ : {};
52
+ const websocket = {
53
+ open(ws) {
54
+ Deferred.unsafeDone(ws.data.deferred, Exit.succeed(ws));
55
+ },
56
+ message(ws, message) {
57
+ ws.data.run(message);
58
+ },
59
+ close(ws, code, closeReason) {
60
+ Deferred.unsafeDone(ws.data.closeDeferred, Socket.defaultCloseCodeIsError(code)
61
+ ? Exit.fail(new Socket.SocketCloseError({
62
+ reason: "Close",
63
+ code,
64
+ closeReason,
65
+ }))
66
+ : Exit.void);
67
+ },
68
+ };
69
+ const server = Bun.serve({
70
+ port,
71
+ hostname,
72
+ ...options,
73
+ routes: currentRoutes,
74
+ fetch: handlerStack[0],
75
+ websocket,
76
+ });
77
+ // @ts-expect-error
78
+ service.server = server;
79
+ yield* Effect.addFinalizer(() => Effect.sync(() => {
80
+ server.stop();
81
+ }));
82
+ const reload = () => {
83
+ server.reload({
84
+ fetch: handlerStack[handlerStack.length - 1],
85
+ routes: currentRoutes,
86
+ websocket,
87
+ });
88
+ };
89
+ const bunServer = BunServer.of({
90
+ server,
91
+ pushHandler(fetch) {
92
+ handlerStack.push(fetch);
93
+ reload();
94
+ },
95
+ popHandler() {
96
+ handlerStack.pop();
97
+ reload();
98
+ },
99
+ });
100
+ return bunServer;
101
+ });
102
+ /**
103
+ * Provides HttpServer using BunServer under the hood.
104
+ */
105
+ export const layer = (options) => Layer.scoped(BunServer, make(options ?? {}));
106
+ export const withLogAddress = (layer) => Layer
107
+ .effectDiscard(BunServer.pipe(Effect.andThen(server => Effect.log(`Listening on ${server.server.hostname}:${server.server.port}`))))
108
+ .pipe(Layer.provideMerge(layer));
109
+ function walkBunRoutes(runtime, tree) {
110
+ return Effect.gen(function* () {
111
+ const bunRoutes = {};
112
+ const pathGroups = new Map();
113
+ const toWebHandler = RouteHttp.toWebHandlerRuntime(runtime);
114
+ for (const route of RouteTree.walk(tree)) {
115
+ const bunDescriptors = BunRoute.descriptors(route);
116
+ if (bunDescriptors) {
117
+ const htmlBundle = yield* Effect.promise(bunDescriptors.bunLoad);
118
+ bunRoutes[`${bunDescriptors.bunPrefix}/*`] = htmlBundle;
119
+ }
120
+ const path = Route.descriptor(route).path;
121
+ const group = pathGroups.get(path) ?? [];
122
+ group.push(route);
123
+ pathGroups.set(path, group);
124
+ }
125
+ for (const [path, routes] of pathGroups) {
126
+ const handler = toWebHandler(routes);
127
+ for (const bunPath of PathPattern.toBun(path)) {
128
+ bunRoutes[bunPath] = handler;
129
+ }
130
+ }
131
+ return bunRoutes;
132
+ });
133
+ }
@@ -0,0 +1,60 @@
1
+ import type * as FileSystem from "@effect/platform/FileSystem";
2
+ import * as Headers from "@effect/platform/Headers";
3
+ import * as HttpIncomingMessage from "@effect/platform/HttpIncomingMessage";
4
+ import type { HttpMethod } from "@effect/platform/HttpMethod";
5
+ import * as HttpServerError from "@effect/platform/HttpServerError";
6
+ import * as HttpServerRequest from "@effect/platform/HttpServerRequest";
7
+ import * as HttpServerResponse from "@effect/platform/HttpServerResponse";
8
+ import type * as Multipart from "@effect/platform/Multipart";
9
+ import type * as Path from "@effect/platform/Path";
10
+ import * as Socket from "@effect/platform/Socket";
11
+ import * as UrlParams from "@effect/platform/UrlParams";
12
+ import type { Server as BunServerInstance, ServerWebSocket } from "bun";
13
+ import * as Deferred from "effect/Deferred";
14
+ import * as Effect from "effect/Effect";
15
+ import * as Inspectable from "effect/Inspectable";
16
+ import * as Option from "effect/Option";
17
+ import * as Runtime from "effect/Runtime";
18
+ import type * as Scope from "effect/Scope";
19
+ import * as Stream from "effect/Stream";
20
+ export interface WebSocketContext {
21
+ readonly deferred: Deferred.Deferred<ServerWebSocket<WebSocketContext>>;
22
+ readonly closeDeferred: Deferred.Deferred<void, Socket.SocketError>;
23
+ readonly buffer: Array<Uint8Array | string>;
24
+ run: (_: Uint8Array | string) => void;
25
+ }
26
+ export declare class ServerRequestImpl extends Inspectable.Class implements HttpServerRequest.HttpServerRequest {
27
+ readonly [HttpServerRequest.TypeId]: HttpServerRequest.TypeId;
28
+ readonly [HttpIncomingMessage.TypeId]: HttpIncomingMessage.TypeId;
29
+ readonly source: Request;
30
+ resolve: (response: Response) => void;
31
+ readonly url: string;
32
+ private bunServer;
33
+ headersOverride?: Headers.Headers;
34
+ private remoteAddressOverride?;
35
+ constructor(source: Request, resolve: (response: Response) => void, url: string, bunServer: BunServerInstance<WebSocketContext>, headersOverride?: Headers.Headers, remoteAddressOverride?: string);
36
+ toJSON(): unknown;
37
+ modify(options: {
38
+ readonly url?: string | undefined;
39
+ readonly headers?: Headers.Headers | undefined;
40
+ readonly remoteAddress?: string | undefined;
41
+ }): ServerRequestImpl;
42
+ get method(): HttpMethod;
43
+ get originalUrl(): string;
44
+ get remoteAddress(): Option.Option<string>;
45
+ get headers(): Headers.Headers;
46
+ private cachedCookies;
47
+ get cookies(): Record<string, string>;
48
+ get stream(): Stream.Stream<Uint8Array, HttpServerError.RequestError>;
49
+ private textEffect;
50
+ get text(): Effect.Effect<string, HttpServerError.RequestError>;
51
+ get json(): Effect.Effect<unknown, HttpServerError.RequestError>;
52
+ get urlParamsBody(): Effect.Effect<UrlParams.UrlParams, HttpServerError.RequestError>;
53
+ private multipartEffect;
54
+ get multipart(): Effect.Effect<Multipart.Persisted, Multipart.MultipartError, Scope.Scope | FileSystem.FileSystem | Path.Path>;
55
+ get multipartStream(): Stream.Stream<Multipart.Part, Multipart.MultipartError>;
56
+ private arrayBufferEffect;
57
+ get arrayBuffer(): Effect.Effect<ArrayBuffer, HttpServerError.RequestError>;
58
+ get upgrade(): Effect.Effect<Socket.Socket, HttpServerError.RequestError>;
59
+ }
60
+ export declare function makeResponse(request: HttpServerRequest.HttpServerRequest, response: HttpServerResponse.HttpServerResponse, runtime: Runtime.Runtime<never>): Response;