effect-start 0.19.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 (144) hide show
  1. package/README.md +3 -3
  2. package/dist/Development.d.ts +3 -3
  3. package/dist/Development.js +3 -2
  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 +23 -0
  15. package/dist/PlatformRuntime.js +42 -0
  16. package/dist/RouteBody.d.ts +1 -1
  17. package/dist/Start.d.ts +34 -3
  18. package/dist/Start.js +31 -6
  19. package/dist/bun/BunPlatformHttpServer.d.ts +10 -0
  20. package/dist/bun/BunPlatformHttpServer.js +53 -0
  21. package/dist/bun/BunRoute.d.ts +3 -5
  22. package/dist/bun/BunRoute.js +9 -17
  23. package/dist/bun/BunRuntime.d.ts +2 -1
  24. package/dist/bun/BunRuntime.js +10 -5
  25. package/dist/bun/BunServer.d.ts +33 -0
  26. package/dist/bun/BunServer.js +133 -0
  27. package/dist/bun/BunServerRequest.d.ts +60 -0
  28. package/dist/bun/BunServerRequest.js +252 -0
  29. package/dist/bun/index.d.ts +1 -1
  30. package/dist/bun/index.js +1 -1
  31. package/dist/datastar/actions/fetch.d.ts +30 -0
  32. package/dist/datastar/actions/fetch.js +411 -0
  33. package/dist/datastar/actions/peek.d.ts +1 -0
  34. package/dist/datastar/actions/peek.js +14 -0
  35. package/dist/datastar/actions/setAll.d.ts +1 -0
  36. package/dist/datastar/actions/setAll.js +13 -0
  37. package/dist/datastar/actions/toggleAll.d.ts +1 -0
  38. package/dist/datastar/actions/toggleAll.js +13 -0
  39. package/dist/datastar/attributes/attr.d.ts +1 -0
  40. package/dist/datastar/attributes/attr.js +49 -0
  41. package/dist/datastar/attributes/bind.d.ts +1 -0
  42. package/dist/datastar/attributes/bind.js +183 -0
  43. package/dist/datastar/attributes/class.d.ts +1 -0
  44. package/dist/datastar/attributes/class.js +50 -0
  45. package/dist/datastar/attributes/computed.d.ts +1 -0
  46. package/dist/datastar/attributes/computed.js +27 -0
  47. package/dist/datastar/attributes/effect.d.ts +1 -0
  48. package/dist/datastar/attributes/effect.js +10 -0
  49. package/dist/datastar/attributes/indicator.d.ts +1 -0
  50. package/dist/datastar/attributes/indicator.js +32 -0
  51. package/dist/datastar/attributes/init.d.ts +1 -0
  52. package/dist/datastar/attributes/init.js +27 -0
  53. package/dist/datastar/attributes/jsonSignals.d.ts +1 -0
  54. package/dist/datastar/attributes/jsonSignals.js +31 -0
  55. package/dist/datastar/attributes/on.d.ts +1 -0
  56. package/dist/datastar/attributes/on.js +59 -0
  57. package/dist/datastar/attributes/onIntersect.d.ts +1 -0
  58. package/dist/datastar/attributes/onIntersect.js +54 -0
  59. package/dist/datastar/attributes/onInterval.d.ts +1 -0
  60. package/dist/datastar/attributes/onInterval.js +31 -0
  61. package/dist/datastar/attributes/onSignalPatch.d.ts +1 -0
  62. package/dist/datastar/attributes/onSignalPatch.js +44 -0
  63. package/dist/datastar/attributes/ref.d.ts +1 -0
  64. package/dist/datastar/attributes/ref.js +11 -0
  65. package/dist/datastar/attributes/show.d.ts +1 -0
  66. package/dist/datastar/attributes/show.js +32 -0
  67. package/dist/datastar/attributes/signals.d.ts +1 -0
  68. package/dist/datastar/attributes/signals.js +18 -0
  69. package/dist/datastar/attributes/style.d.ts +1 -0
  70. package/dist/datastar/attributes/style.js +56 -0
  71. package/dist/datastar/attributes/text.d.ts +1 -0
  72. package/dist/datastar/attributes/text.js +27 -0
  73. package/dist/datastar/engine.d.ts +156 -0
  74. package/dist/datastar/engine.js +971 -0
  75. package/dist/datastar/index.d.ts +24 -0
  76. package/dist/datastar/index.js +24 -0
  77. package/dist/datastar/load.d.ts +24 -0
  78. package/dist/datastar/load.js +24 -0
  79. package/dist/datastar/utils.d.ts +51 -0
  80. package/dist/datastar/utils.js +205 -0
  81. package/dist/datastar/watchers/patchElements.d.ts +1 -0
  82. package/dist/datastar/watchers/patchElements.js +420 -0
  83. package/dist/datastar/watchers/patchSignals.d.ts +1 -0
  84. package/dist/datastar/watchers/patchSignals.js +15 -0
  85. package/dist/index.d.ts +1 -1
  86. package/dist/index.js +1 -1
  87. package/dist/node/NodeFileSystem.d.ts +7 -0
  88. package/dist/node/NodeFileSystem.js +420 -0
  89. package/dist/node/NodeUtils.d.ts +2 -0
  90. package/dist/node/NodeUtils.js +20 -0
  91. package/dist/x/tailwind/plugin.js +1 -1
  92. package/package.json +11 -7
  93. package/src/Development.ts +26 -25
  94. package/src/{node/Effectify.ts → Effectify.ts} +10 -3
  95. package/src/FilePathPattern.ts +115 -0
  96. package/src/FileRouter.ts +178 -255
  97. package/src/FileRouterCodegen.ts +135 -92
  98. package/src/{node/PlatformError.ts → PlatformError.ts} +34 -19
  99. package/src/PlatformRuntime.ts +97 -0
  100. package/src/RouteBody.ts +1 -1
  101. package/src/RouteHttp.ts +3 -1
  102. package/src/Start.ts +62 -14
  103. package/src/bun/BunPlatformHttpServer.ts +88 -0
  104. package/src/bun/BunRoute.ts +12 -22
  105. package/src/bun/BunRuntime.ts +21 -5
  106. package/src/bun/BunServer.ts +228 -0
  107. package/src/bun/index.ts +1 -1
  108. package/src/datastar/README.md +18 -0
  109. package/src/datastar/actions/fetch.ts +609 -0
  110. package/src/datastar/actions/peek.ts +17 -0
  111. package/src/datastar/actions/setAll.ts +20 -0
  112. package/src/datastar/actions/toggleAll.ts +20 -0
  113. package/src/datastar/attributes/attr.ts +50 -0
  114. package/src/datastar/attributes/bind.ts +220 -0
  115. package/src/datastar/attributes/class.ts +57 -0
  116. package/src/datastar/attributes/computed.ts +33 -0
  117. package/src/datastar/attributes/effect.ts +11 -0
  118. package/src/datastar/attributes/indicator.ts +39 -0
  119. package/src/datastar/attributes/init.ts +35 -0
  120. package/src/datastar/attributes/jsonSignals.ts +38 -0
  121. package/src/datastar/attributes/on.ts +71 -0
  122. package/src/datastar/attributes/onIntersect.ts +65 -0
  123. package/src/datastar/attributes/onInterval.ts +39 -0
  124. package/src/datastar/attributes/onSignalPatch.ts +63 -0
  125. package/src/datastar/attributes/ref.ts +12 -0
  126. package/src/datastar/attributes/show.ts +33 -0
  127. package/src/datastar/attributes/signals.ts +22 -0
  128. package/src/datastar/attributes/style.ts +63 -0
  129. package/src/datastar/attributes/text.ts +30 -0
  130. package/src/datastar/engine.ts +1341 -0
  131. package/src/datastar/index.ts +25 -0
  132. package/src/datastar/utils.ts +286 -0
  133. package/src/datastar/watchers/patchElements.ts +554 -0
  134. package/src/datastar/watchers/patchSignals.ts +15 -0
  135. package/src/index.ts +1 -1
  136. package/src/node/{FileSystem.ts → NodeFileSystem.ts} +2 -2
  137. package/src/node/{Utils.ts → NodeUtils.ts} +2 -0
  138. package/src/x/tailwind/plugin.ts +1 -1
  139. package/src/FileRouterCodegen.todo.ts +0 -1133
  140. package/src/FileRouterPattern.ts +0 -59
  141. package/src/RouterPattern.ts +0 -416
  142. package/src/StartApp.ts +0 -47
  143. package/src/bun/BunHttpServer.ts +0 -303
  144. /package/src/bun/{BunHttpServer_web.ts → BunServerRequest.ts} +0 -0
@@ -0,0 +1,43 @@
1
+ import * as Data from "effect/Data";
2
+ import * as Predicate from "effect/Predicate";
3
+ import * as Schema from "effect/Schema";
4
+ import { TypeId as TypeId_ } from "@effect/platform/Error";
5
+ export const TypeId = TypeId_;
6
+ export const isPlatformError = (u) => Predicate.hasProperty(u, TypeId);
7
+ export const TypeIdError = (typeId, tag) => {
8
+ class Base extends Data.Error {
9
+ _tag = tag;
10
+ }
11
+ ;
12
+ Base.prototype[typeId] = typeId;
13
+ Base.prototype.name = tag;
14
+ return Base;
15
+ };
16
+ export const Module = Schema.Literal("Clipboard", "Command", "FileSystem", "KeyValueStore", "Path", "Stream", "Terminal");
17
+ export class BadArgument extends Schema.TaggedError("@effect/platform/Error/BadArgument")("BadArgument", {
18
+ module: Module,
19
+ method: Schema.String,
20
+ description: Schema.optional(Schema.String),
21
+ cause: Schema.optional(Schema.Defect),
22
+ }) {
23
+ [TypeId] = TypeId;
24
+ get message() {
25
+ return `${this.module}.${this.method}${this.description ? `: ${this.description}` : ""}`;
26
+ }
27
+ }
28
+ export const SystemErrorReason = Schema.Literal("AlreadyExists", "BadResource", "Busy", "InvalidData", "NotFound", "PermissionDenied", "TimedOut", "UnexpectedEof", "Unknown", "WouldBlock", "WriteZero");
29
+ export class SystemError extends Schema.TaggedError("@effect/platform/Error/SystemError")("SystemError", {
30
+ reason: SystemErrorReason,
31
+ module: Module,
32
+ method: Schema.String,
33
+ description: Schema.optional(Schema.String),
34
+ syscall: Schema.optional(Schema.String),
35
+ pathOrDescriptor: Schema.optional(Schema.Union(Schema.String, Schema.Number)),
36
+ cause: Schema.optional(Schema.Defect),
37
+ }) {
38
+ [TypeId] = TypeId;
39
+ get message() {
40
+ return `${this.reason}: ${this.module}.${this.method}${this.pathOrDescriptor !== undefined ? ` (${this.pathOrDescriptor})` : ""}${this.description ? `: ${this.description}` : ""}`;
41
+ }
42
+ }
43
+ export const PlatformError = Schema.Union(BadArgument, SystemError);
@@ -1,3 +1,26 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Exit from "effect/Exit";
3
+ import type * as Fiber from "effect/Fiber";
4
+ export interface Teardown {
5
+ <E, A>(exit: Exit.Exit<E, A>, onExit: (code: number) => void): void;
6
+ }
7
+ export declare const defaultTeardown: Teardown;
8
+ export interface RunMain {
9
+ (options?: {
10
+ readonly disableErrorReporting?: boolean | undefined;
11
+ readonly disablePrettyLogger?: boolean | undefined;
12
+ readonly teardown?: Teardown | undefined;
13
+ }): <E, A>(effect: Effect.Effect<A, E>) => void;
14
+ <E, A>(effect: Effect.Effect<A, E>, options?: {
15
+ readonly disableErrorReporting?: boolean | undefined;
16
+ readonly disablePrettyLogger?: boolean | undefined;
17
+ readonly teardown?: Teardown | undefined;
18
+ }): void;
19
+ }
20
+ export declare const makeRunMain: (f: <E, A>(options: {
21
+ readonly fiber: Fiber.RuntimeFiber<A, E>;
22
+ readonly teardown: Teardown;
23
+ }) => void) => RunMain;
1
24
  /**
2
25
  * Are we running within an agent harness, like Claude Code?
3
26
  */
@@ -1,3 +1,45 @@
1
+ import * as Cause from "effect/Cause";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Exit from "effect/Exit";
4
+ import * as FiberRef from "effect/FiberRef";
5
+ import * as FiberRefs from "effect/FiberRefs";
6
+ import * as Function from "effect/Function";
7
+ import * as HashSet from "effect/HashSet";
8
+ import * as Logger from "effect/Logger";
9
+ export const defaultTeardown = (exit, onExit) => {
10
+ onExit(Exit.isFailure(exit) && !Cause.isInterruptedOnly(exit.cause) ? 1 : 0);
11
+ };
12
+ const addPrettyLogger = (refs, fiberId) => {
13
+ const loggers = FiberRefs.getOrDefault(refs, FiberRef.currentLoggers);
14
+ if (!HashSet.has(loggers, Logger.defaultLogger)) {
15
+ return refs;
16
+ }
17
+ return FiberRefs.updateAs(refs, {
18
+ fiberId,
19
+ fiberRef: FiberRef.currentLoggers,
20
+ value: loggers.pipe(HashSet.remove(Logger.defaultLogger), HashSet.add(Logger.prettyLoggerDefault)),
21
+ });
22
+ };
23
+ export const makeRunMain = (f) => Function.dual((args) => Effect.isEffect(args[0]), (effect, options) => {
24
+ const fiber = options?.disableErrorReporting === true
25
+ ? Effect.runFork(effect, {
26
+ updateRefs: options?.disablePrettyLogger === true
27
+ ? undefined
28
+ : addPrettyLogger,
29
+ })
30
+ : Effect.runFork(Effect.tapErrorCause(effect, (cause) => {
31
+ if (Cause.isInterruptedOnly(cause)) {
32
+ return Effect.void;
33
+ }
34
+ return Effect.logError(cause);
35
+ }), {
36
+ updateRefs: options?.disablePrettyLogger === true
37
+ ? undefined
38
+ : addPrettyLogger,
39
+ });
40
+ const teardown = options?.teardown ?? defaultTeardown;
41
+ return f({ fiber, teardown });
42
+ });
1
43
  /**
2
44
  * Are we running within an agent harness, like Claude Code?
3
45
  */
@@ -8,7 +8,7 @@ export type Format = "text" | "html" | "json" | "bytes" | "*";
8
8
  type UnwrapStream<T> = T extends Stream.Stream<infer V, any, any> ? V : T;
9
9
  type YieldError<T> = T extends Utils.YieldWrap<Effect.Effect<any, infer E, any>> ? E : never;
10
10
  type YieldContext<T> = T extends Utils.YieldWrap<Effect.Effect<any, any, infer R>> ? R : never;
11
- export type GeneratorHandler<B, A, Y> = (context: Values.Simplify<B>, next: (context?: Partial<B> & Record<string, unknown>) => Entity.Entity<UnwrapStream<A>>) => Generator<Y, A | Entity.Entity<A>, unknown>;
11
+ export type GeneratorHandler<B, A, Y> = (context: Values.Simplify<B>, next: (context?: Partial<B> & Record<string, unknown>) => Entity.Entity<UnwrapStream<A>>) => Generator<Y, A | Entity.Entity<A>, never>;
12
12
  export type HandlerInput<B, A, E, R> = A | Entity.Entity<A> | Effect.Effect<A | Entity.Entity<A>, E, R> | ((context: Values.Simplify<B>, next: (context?: Partial<B> & Record<string, unknown>) => Entity.Entity<UnwrapStream<A>>) => Effect.Effect<A | Entity.Entity<A>, E, R> | Generator<Utils.YieldWrap<Effect.Effect<unknown, E, R>>, A | Entity.Entity<A>, unknown>);
13
13
  export declare function handle<B, A, Y extends Utils.YieldWrap<Effect.Effect<any, any, any>>>(handler: GeneratorHandler<B, A, Y>): Route.Route.Handler<B, A, YieldError<Y>, YieldContext<Y>>;
14
14
  export declare function handle<B, A, E, R>(handler: HandlerInput<B, A, E, R>): Route.Route.Handler<B, A, E, R>;
package/dist/Start.d.ts CHANGED
@@ -1,5 +1,6 @@
1
+ import * as FileSystem from "@effect/platform/FileSystem";
1
2
  import * as Layer from "effect/Layer";
2
- import * as BunHttpServer from "./bun/BunHttpServer.ts";
3
+ import * as BunServer from "./bun/BunServer.ts";
3
4
  export declare function layer<Layers extends [
4
5
  Layer.Layer<never, any, any>,
5
6
  ...Array<Layer.Layer<never, any, any>>
@@ -10,6 +11,36 @@ export declare function layer<Layers extends [
10
11
  }[number], {
11
12
  [k in keyof Layers]: Layer.Layer.Context<Layers[k]>;
12
13
  }[number]>;
13
- export declare function serve<ROut, E>(load: () => Promise<{
14
- default: Layer.Layer<ROut, E, BunHttpServer.BunHttpServer>;
14
+ /**
15
+ * Bundles layers together, wiring their dependencies automatically.
16
+ *
17
+ * Equivalent to chaining `Layer.provide` calls, but more concise.
18
+ *
19
+ * **Ordering: dependents first, dependencies last.**
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * // UserRepo needs Database, Database needs Logger
24
+ * const AppLayer = Start.pack(
25
+ * UserRepoLive, // needs Database, Logger
26
+ * DatabaseLive, // needs Logger
27
+ * LoggerLive, // no deps
28
+ * )
29
+ * // Result: Layer<UserRepo | Database | Logger, never, never>
30
+ * ```
31
+ *
32
+ * @since 1.0.0
33
+ * @category constructors
34
+ */
35
+ export declare function pack<const Layers extends readonly [Layer.Layer.Any, ...Array<Layer.Layer.Any>]>(...layers: Layers): Layer.Layer<{
36
+ [K in keyof Layers]: Layer.Layer.Success<Layers[K]>;
37
+ }[number], {
38
+ [K in keyof Layers]: Layer.Layer.Error<Layers[K]>;
39
+ }[number], Exclude<{
40
+ [K in keyof Layers]: Layer.Layer.Context<Layers[K]>;
41
+ }[number], {
42
+ [K in keyof Layers]: Layer.Layer.Success<Layers[K]>;
43
+ }[number]>>;
44
+ export declare function serve<ROut, E, RIn extends BunServer.BunServer | FileSystem.FileSystem>(load: () => Promise<{
45
+ default: Layer.Layer<ROut, E, RIn>;
15
46
  }>): void;
package/dist/Start.js CHANGED
@@ -1,16 +1,41 @@
1
+ import * as Context from "effect/Context";
1
2
  import * as Effect from "effect/Effect";
2
3
  import * as Function from "effect/Function";
3
4
  import * as Layer from "effect/Layer";
4
- import * as BunHttpServer from "./bun/BunHttpServer.js";
5
5
  import * as BunRuntime from "./bun/BunRuntime.js";
6
- import * as StartApp from "./StartApp.js";
6
+ import * as BunServer from "./bun/BunServer.js";
7
+ import * as NodeFileSystem from "./node/NodeFileSystem.js";
7
8
  export function layer(...layers) {
8
9
  return Layer.mergeAll(...layers);
9
10
  }
11
+ /**
12
+ * Bundles layers together, wiring their dependencies automatically.
13
+ *
14
+ * Equivalent to chaining `Layer.provide` calls, but more concise.
15
+ *
16
+ * **Ordering: dependents first, dependencies last.**
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * // UserRepo needs Database, Database needs Logger
21
+ * const AppLayer = Start.pack(
22
+ * UserRepoLive, // needs Database, Logger
23
+ * DatabaseLive, // needs Logger
24
+ * LoggerLive, // no deps
25
+ * )
26
+ * // Result: Layer<UserRepo | Database | Logger, never, never>
27
+ * ```
28
+ *
29
+ * @since 1.0.0
30
+ * @category constructors
31
+ */
32
+ export function pack(...layers) {
33
+ const layerArray = layers;
34
+ const result = layerArray.reduce((acc, layer) => Layer.provideMerge(acc, layer), Layer.succeedContext(Context.empty()));
35
+ return result;
36
+ }
10
37
  export function serve(load) {
11
38
  const appLayer = Function.pipe(Effect.tryPromise(load), Effect.map(v => v.default), Effect.orDie, Layer.unwrapEffect);
12
- return Function.pipe(BunHttpServer.layerAuto(), Layer.provide(appLayer), Layer.provide([
13
- BunHttpServer.layer(),
14
- StartApp.layer(),
15
- ]), Layer.launch, BunRuntime.runMain);
39
+ const composed = Function.pipe(BunServer.layer(), BunServer.withLogAddress, Layer.provide(appLayer), Layer.provide(NodeFileSystem.layer), Layer.provide(BunServer.layer()));
40
+ return Function.pipe(composed, Layer.launch, BunRuntime.runMain);
16
41
  }
@@ -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>;
@@ -22,7 +22,7 @@ export declare function htmlBundle(load: () => Promise<Bun.HTMLBundle | {
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 Unique from "../Unique.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
  }
@@ -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;