effect-start 0.17.0 → 0.17.2

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 (148) hide show
  1. package/dist/Commander.d.ts +103 -0
  2. package/dist/Commander.js +333 -0
  3. package/dist/ContentNegotiation.d.ts +13 -0
  4. package/dist/ContentNegotiation.js +364 -0
  5. package/dist/Development.d.ts +34 -0
  6. package/dist/Development.js +52 -0
  7. package/dist/Entity.d.ts +47 -0
  8. package/dist/Entity.js +224 -0
  9. package/dist/FileRouter.d.ts +61 -0
  10. package/dist/FileRouter.js +203 -0
  11. package/dist/FileRouterCodegen.d.ts +19 -0
  12. package/dist/FileRouterCodegen.js +176 -0
  13. package/dist/FileRouterPattern.d.ts +9 -0
  14. package/dist/FileRouterPattern.js +35 -0
  15. package/dist/Http.d.ts +37 -0
  16. package/dist/Http.js +92 -0
  17. package/dist/HttpAppExtra.d.ts +7 -0
  18. package/dist/HttpAppExtra.js +320 -0
  19. package/dist/HttpUtils.d.ts +3 -0
  20. package/dist/HttpUtils.js +11 -0
  21. package/dist/PathPattern.d.ts +134 -0
  22. package/dist/PathPattern.js +415 -0
  23. package/dist/Random.d.ts +5 -0
  24. package/dist/Random.js +49 -0
  25. package/dist/Route.d.ts +98 -0
  26. package/dist/Route.js +81 -0
  27. package/dist/RouteBody.d.ts +53 -0
  28. package/dist/RouteBody.js +67 -0
  29. package/dist/RouteHook.d.ts +12 -0
  30. package/dist/RouteHook.js +45 -0
  31. package/dist/RouteHttp.d.ts +21 -0
  32. package/dist/RouteHttp.js +260 -0
  33. package/dist/RouteHttpTracer.d.ts +10 -0
  34. package/dist/RouteHttpTracer.js +62 -0
  35. package/dist/RouteMount.d.ts +119 -0
  36. package/dist/RouteMount.js +77 -0
  37. package/dist/RouteSchema.d.ts +65 -0
  38. package/dist/RouteSchema.js +155 -0
  39. package/dist/RouteSse.d.ts +21 -0
  40. package/dist/RouteSse.js +85 -0
  41. package/dist/RouteTree.d.ts +56 -0
  42. package/dist/RouteTree.js +91 -0
  43. package/dist/RouteTrie.d.ts +20 -0
  44. package/dist/RouteTrie.js +157 -0
  45. package/dist/RouterPattern.d.ts +118 -0
  46. package/dist/RouterPattern.js +269 -0
  47. package/dist/SchemaExtra.d.ts +7 -0
  48. package/dist/SchemaExtra.js +74 -0
  49. package/dist/Start.d.ts +19 -0
  50. package/dist/Start.js +23 -0
  51. package/dist/StartApp.d.ts +19 -0
  52. package/dist/StartApp.js +21 -0
  53. package/dist/StreamExtra.d.ts +28 -0
  54. package/dist/StreamExtra.js +100 -0
  55. package/dist/TuplePathPattern.d.ts +9 -0
  56. package/dist/TuplePathPattern.js +63 -0
  57. package/dist/Values.d.ts +26 -0
  58. package/dist/Values.js +30 -0
  59. package/dist/bun/BunBundle.d.ts +12 -0
  60. package/dist/bun/BunBundle.js +145 -0
  61. package/dist/bun/BunHttpServer.d.ts +44 -0
  62. package/dist/bun/BunHttpServer.js +187 -0
  63. package/dist/bun/BunHttpServer_web.d.ts +60 -0
  64. package/dist/bun/BunHttpServer_web.js +252 -0
  65. package/dist/bun/BunImportTrackerPlugin.d.ts +13 -0
  66. package/dist/bun/BunImportTrackerPlugin.js +71 -0
  67. package/dist/bun/BunRoute.d.ts +49 -0
  68. package/dist/bun/BunRoute.js +131 -0
  69. package/dist/bun/BunRuntime.d.ts +1 -0
  70. package/dist/bun/BunRuntime.js +26 -0
  71. package/dist/bun/BunVirtualFilesPlugin.d.ts +4 -0
  72. package/dist/bun/BunVirtualFilesPlugin.js +40 -0
  73. package/dist/bun/_BunEnhancedResolve.d.ts +45 -0
  74. package/dist/bun/_BunEnhancedResolve.js +104 -0
  75. package/dist/bun/index.d.ts +4 -0
  76. package/dist/bun/index.js +4 -0
  77. package/dist/bundler/Bundle.d.ts +60 -0
  78. package/dist/bundler/Bundle.js +48 -0
  79. package/dist/bundler/BundleFiles.d.ts +13 -0
  80. package/dist/bundler/BundleFiles.js +94 -0
  81. package/dist/bundler/BundleHttp.d.ts +45 -0
  82. package/dist/bundler/BundleHttp.js +176 -0
  83. package/dist/client/Overlay.d.ts +2 -0
  84. package/dist/client/Overlay.js +32 -0
  85. package/dist/client/ScrollState.d.ts +6 -0
  86. package/dist/client/ScrollState.js +98 -0
  87. package/dist/client/index.d.ts +6 -0
  88. package/dist/client/index.js +81 -0
  89. package/dist/experimental/EncryptedCookies.d.ts +51 -0
  90. package/dist/experimental/EncryptedCookies.js +243 -0
  91. package/dist/experimental/SseHttpResponse.d.ts +7 -0
  92. package/dist/experimental/SseHttpResponse.js +28 -0
  93. package/dist/experimental/index.d.ts +2 -0
  94. package/dist/experimental/index.js +2 -0
  95. package/dist/hyper/Hyper.d.ts +32 -0
  96. package/dist/hyper/Hyper.js +34 -0
  97. package/dist/hyper/HyperHtml.d.ts +23 -0
  98. package/dist/hyper/HyperHtml.js +144 -0
  99. package/dist/hyper/HyperNode.d.ts +14 -0
  100. package/dist/hyper/HyperNode.js +11 -0
  101. package/dist/hyper/HyperRoute.d.ts +8 -0
  102. package/dist/hyper/HyperRoute.js +32 -0
  103. package/dist/hyper/HyperRoute.test.d.ts +1 -0
  104. package/dist/hyper/HyperRoute.test.js +72 -0
  105. package/dist/hyper/index.d.ts +4 -0
  106. package/dist/hyper/index.js +4 -0
  107. package/dist/hyper/jsx-runtime.d.ts +7 -0
  108. package/dist/hyper/jsx-runtime.js +8 -0
  109. package/dist/index.d.ts +6 -0
  110. package/dist/index.js +6 -0
  111. package/dist/inference_check.d.ts +1 -0
  112. package/dist/inference_check.js +15 -0
  113. package/dist/middlewares/BasicAuthMiddleware.d.ts +8 -0
  114. package/dist/middlewares/BasicAuthMiddleware.js +22 -0
  115. package/dist/middlewares/index.d.ts +1 -0
  116. package/dist/middlewares/index.js +1 -0
  117. package/dist/node/FileSystem.d.ts +9 -0
  118. package/dist/node/FileSystem.js +440 -0
  119. package/dist/node/Utils.d.ts +1 -0
  120. package/dist/node/Utils.js +19 -0
  121. package/dist/repro_fail.d.ts +1 -0
  122. package/dist/repro_fail.js +14 -0
  123. package/dist/testing/TestHttpClient.d.ts +13 -0
  124. package/dist/testing/TestHttpClient.js +68 -0
  125. package/dist/testing/TestLogger.d.ts +13 -0
  126. package/dist/testing/TestLogger.js +29 -0
  127. package/dist/testing/index.d.ts +3 -0
  128. package/dist/testing/index.js +3 -0
  129. package/dist/testing/utils.d.ts +9 -0
  130. package/dist/testing/utils.js +39 -0
  131. package/dist/x/cloudflare/CloudflareTunnel.d.ts +13 -0
  132. package/dist/x/cloudflare/CloudflareTunnel.js +43 -0
  133. package/dist/x/cloudflare/index.d.ts +1 -0
  134. package/dist/x/cloudflare/index.js +1 -0
  135. package/dist/x/datastar/Datastar.d.ts +6 -0
  136. package/dist/x/datastar/Datastar.js +46 -0
  137. package/dist/x/datastar/index.d.ts +2 -0
  138. package/dist/x/datastar/index.js +2 -0
  139. package/dist/x/tailwind/TailwindPlugin.d.ts +23 -0
  140. package/dist/x/tailwind/TailwindPlugin.js +219 -0
  141. package/dist/x/tailwind/compile.d.ts +19 -0
  142. package/dist/x/tailwind/compile.js +156 -0
  143. package/dist/x/tailwind/plugin.d.ts +2 -0
  144. package/dist/x/tailwind/plugin.js +15 -0
  145. package/package.json +68 -16
  146. package/src/RouteBody.test.ts +18 -0
  147. package/src/RouteBody.ts +126 -2
  148. package/src/x/tailwind/compile.ts +8 -2
@@ -0,0 +1,21 @@
1
+ import * as Context from "effect/Context";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Function from "effect/Function";
4
+ import * as Layer from "effect/Layer";
5
+ import * as PubSub from "effect/PubSub";
6
+ import * as Ref from "effect/Ref";
7
+ export class StartApp extends Context.Tag("effect-start/StartApp")() {
8
+ }
9
+ export function layer(options) {
10
+ return Layer.effect(StartApp, Effect.gen(function* () {
11
+ const env = options?.env ?? process.env.NODE_ENV ?? "development";
12
+ const middleware = yield* Ref.make(Function.identity);
13
+ const events = yield* PubSub.unbounded();
14
+ return StartApp.of({
15
+ env,
16
+ middleware,
17
+ addMiddleware: (f) => Ref.update(middleware, (prev) => (app) => f(prev(app))),
18
+ events,
19
+ });
20
+ }));
21
+ }
@@ -0,0 +1,28 @@
1
+ import * as Runtime from "effect/Runtime";
2
+ import type * as Stream from "effect/Stream";
3
+ export declare const isStream: (u: unknown) => u is Stream.Stream<unknown, unknown, unknown>;
4
+ export type IsStream<T> = T extends Stream.Stream<infer _A, infer _E, infer _R> ? true : false;
5
+ export type Chunk<T> = T extends Stream.Stream<infer A, infer _E, infer _R> ? A : never;
6
+ export type StreamError<T> = T extends Stream.Stream<infer _A, infer E, infer _R> ? E : never;
7
+ export type Context<T> = T extends Stream.Stream<infer _A, infer _E, infer R> ? R : never;
8
+ /**
9
+ * Patched version of original Stream.toReadableStreamRuntime (v3.14.4) to
10
+ * fix an issue in Bun when native stream controller stops working when request
11
+ * is terminated by the client:
12
+ *
13
+ * TypeError: Value of "this" must be of type ReadableStreamDefaultController
14
+ *
15
+ * See related issues:
16
+ * https://github.com/Effect-TS/effect/issues/4538
17
+ * https://github.com/oven-sh/bun/issues/17837
18
+ */
19
+ export declare const toReadableStreamRuntimePatched: (<A, XR>(runtime: Runtime.Runtime<XR>, options?: {
20
+ readonly strategy?: QueuingStrategy<A> | undefined;
21
+ }) => <E, R extends XR>(self: Stream.Stream<A, E, R>) => ReadableStream<A>) & (<A, E, XR, R extends XR>(self: Stream.Stream<A, E, R>, runtime: Runtime.Runtime<XR>, options?: {
22
+ readonly strategy?: QueuingStrategy<A> | undefined;
23
+ }) => ReadableStream<A>);
24
+ export declare const toReadableStreamRuntimePatched2: (<A, XR>(runtime: Runtime.Runtime<XR>, options?: {
25
+ readonly strategy?: QueuingStrategy<A> | undefined;
26
+ }) => <E, R extends XR>(self: Stream.Stream<A, E, R>) => ReadableStream<A>) & (<A, E, XR, R extends XR>(self: Stream.Stream<A, E, R>, runtime: Runtime.Runtime<XR>, options?: {
27
+ readonly strategy?: QueuingStrategy<A> | undefined;
28
+ }) => ReadableStream<A>);
@@ -0,0 +1,100 @@
1
+ import * as Cause from "effect/Cause";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Fiber from "effect/Fiber";
4
+ import { dual } from "effect/Function";
5
+ import * as Predicate from "effect/Predicate";
6
+ import * as Runtime from "effect/Runtime";
7
+ import { runForEachChunk, StreamTypeId, } from "effect/Stream";
8
+ export const isStream = (u) => Predicate.hasProperty(u, StreamTypeId);
9
+ /**
10
+ * Patched version of original Stream.toReadableStreamRuntime (v3.14.4) to
11
+ * fix an issue in Bun when native stream controller stops working when request
12
+ * is terminated by the client:
13
+ *
14
+ * TypeError: Value of "this" must be of type ReadableStreamDefaultController
15
+ *
16
+ * See related issues:
17
+ * https://github.com/Effect-TS/effect/issues/4538
18
+ * https://github.com/oven-sh/bun/issues/17837
19
+ */
20
+ export const toReadableStreamRuntimePatched = dual((args) => Predicate.hasProperty(args[0], StreamTypeId) || Effect.isEffect(args[0]), (self, runtime, options) => {
21
+ const runFork = Runtime.runFork(runtime);
22
+ let currentResolve = undefined;
23
+ let fiber = undefined;
24
+ const latch = Effect.unsafeMakeLatch(false);
25
+ return new ReadableStream({
26
+ start(controller) {
27
+ fiber = runFork(runForEachChunk(self, (chunk) => latch.whenOpen(Effect.sync(() => {
28
+ latch.unsafeClose();
29
+ try {
30
+ for (const item of chunk) {
31
+ controller.enqueue(item);
32
+ }
33
+ }
34
+ catch (e) {
35
+ if (e.message
36
+ === `Value of "this" must be of type ReadableStreamDefaultController`) {
37
+ // Do nothing when this happens in Bun.
38
+ }
39
+ else {
40
+ throw e;
41
+ }
42
+ }
43
+ currentResolve();
44
+ currentResolve = undefined;
45
+ }))));
46
+ // --- CHANGES HERE ---
47
+ // In original code, we had fiber.addObserver here that called
48
+ // error() or close() on controller. This patched version removes it.
49
+ },
50
+ pull() {
51
+ return new Promise((resolve) => {
52
+ currentResolve = resolve;
53
+ Effect.runSync(latch.open);
54
+ });
55
+ },
56
+ cancel() {
57
+ if (!fiber)
58
+ return;
59
+ return Effect.runPromise(Effect.asVoid(Fiber.interrupt(fiber)));
60
+ },
61
+ }, options?.strategy);
62
+ });
63
+ export const toReadableStreamRuntimePatched2 = dual((args) => Predicate.hasProperty(args[0], StreamTypeId) || Effect.isEffect(args[0]), (self, runtime, options) => {
64
+ const runSync = Runtime.runSync(runtime);
65
+ const runFork = Runtime.runFork(runtime);
66
+ let currentResolve = undefined;
67
+ let fiber = undefined;
68
+ const latch = Effect.unsafeMakeLatch(false);
69
+ return new ReadableStream({
70
+ start(controller) {
71
+ fiber = runFork(runForEachChunk(self, (chunk) => latch.whenOpen(Effect.sync(() => {
72
+ latch.unsafeClose();
73
+ for (const item of chunk) {
74
+ controller.enqueue(item);
75
+ }
76
+ currentResolve();
77
+ currentResolve = undefined;
78
+ }))));
79
+ fiber.addObserver((exit) => {
80
+ if (exit._tag === "Failure") {
81
+ controller.error(Cause.squash(exit.cause));
82
+ }
83
+ else {
84
+ controller.close();
85
+ }
86
+ });
87
+ },
88
+ pull() {
89
+ return new Promise((resolve) => {
90
+ currentResolve = resolve;
91
+ Effect.runSync(latch.open);
92
+ });
93
+ },
94
+ cancel() {
95
+ if (!fiber)
96
+ return;
97
+ return Effect.runPromise(Effect.asVoid(Fiber.interrupt(fiber)));
98
+ },
99
+ }, options?.strategy);
100
+ });
@@ -0,0 +1,9 @@
1
+ export type PathTuple = ReadonlyArray<string | [string, string?, string?] | [[string]]>;
2
+ export declare function format(tuple: PathTuple): `/${string}`;
3
+ export declare function toColon(tuple: PathTuple): string;
4
+ export declare const toHono: typeof toColon;
5
+ export declare function toExpress(tuple: PathTuple): string;
6
+ export declare const toEffect: typeof toColon;
7
+ export declare function toURLPattern(tuple: PathTuple): string;
8
+ export declare function toRemix(tuple: PathTuple): string;
9
+ export declare const toBun: typeof toColon;
@@ -0,0 +1,63 @@
1
+ export function format(tuple) {
2
+ return "/" + tuple
3
+ .map((el) => {
4
+ if (typeof el === "string")
5
+ return el;
6
+ if (Array.isArray(el[0]))
7
+ return "[[" + el[0][0] + "]]";
8
+ const [name, suffix, prefix] = el;
9
+ return (prefix ?? "") + "[" + name + "]" + (suffix ?? "");
10
+ })
11
+ .join("/");
12
+ }
13
+ export function toColon(tuple) {
14
+ return "/" + tuple
15
+ .map((el) => {
16
+ if (typeof el === "string")
17
+ return el;
18
+ if (Array.isArray(el[0]))
19
+ return "*";
20
+ const [name, suffix, prefix] = el;
21
+ return (prefix ?? "") + ":" + name + (suffix ?? "");
22
+ })
23
+ .join("/");
24
+ }
25
+ export const toHono = toColon;
26
+ export function toExpress(tuple) {
27
+ return "/" + tuple
28
+ .map((el) => {
29
+ if (typeof el === "string")
30
+ return el;
31
+ if (Array.isArray(el[0]))
32
+ return "*" + el[0][0];
33
+ const [name, suffix, prefix] = el;
34
+ return (prefix ?? "") + ":" + name + (suffix ?? "");
35
+ })
36
+ .join("/");
37
+ }
38
+ export const toEffect = toColon;
39
+ export function toURLPattern(tuple) {
40
+ return "/" + tuple
41
+ .map((el) => {
42
+ if (typeof el === "string")
43
+ return el;
44
+ if (Array.isArray(el[0]))
45
+ return ":" + el[0][0] + "+";
46
+ const [name, suffix, prefix] = el;
47
+ return (prefix ?? "") + ":" + name + (suffix ?? "");
48
+ })
49
+ .join("/");
50
+ }
51
+ export function toRemix(tuple) {
52
+ return "/" + tuple
53
+ .map((el) => {
54
+ if (typeof el === "string")
55
+ return el;
56
+ if (Array.isArray(el[0]))
57
+ return "$";
58
+ const [name, suffix, prefix] = el;
59
+ return (prefix ?? "") + "$" + name + (suffix ?? "");
60
+ })
61
+ .join("/");
62
+ }
63
+ export const toBun = toColon;
@@ -0,0 +1,26 @@
1
+ type JsonPrimitives = string | number | boolean | null;
2
+ export type JsonObject = {
3
+ [key: string]: Json | undefined;
4
+ };
5
+ export type Json = JsonPrimitives | Json[] | JsonObject;
6
+ export declare function isPlainObject(value: unknown): value is Record<string, unknown>;
7
+ /**
8
+ * Type helper that returns `true` if type T has any method properties,
9
+ * otherwise returns `never`.
10
+ *
11
+ * Used internally by IsPlainObject to distinguish plain objects from
12
+ * class instances or objects with methods.
13
+ */
14
+ type HasMethod<T> = {
15
+ [K in keyof T]: T[K] extends (...args: any[]) => any ? true : never;
16
+ }[keyof T];
17
+ export type IsPlainObject<T> = T extends object ? T extends Function ? false : HasMethod<T> extends never ? true : false : false;
18
+ export type Simplify<T> = {
19
+ -readonly [K in keyof T]: IsPlainObject<T[K]> extends true ? {
20
+ -readonly [P in keyof T[K]]: T[K][P];
21
+ } : T[K];
22
+ } extends infer U ? {
23
+ [K in keyof U]: U[K];
24
+ } : never;
25
+ export declare const concatBytes: (a: Uint8Array, b: Uint8Array) => Uint8Array;
26
+ export {};
package/dist/Values.js ADDED
@@ -0,0 +1,30 @@
1
+ export function isPlainObject(value) {
2
+ if (value === null || typeof value !== "object") {
3
+ return false;
4
+ }
5
+ // Check for built-in types and web APIs
6
+ if (ArrayBuffer.isView(value)
7
+ || value instanceof ArrayBuffer
8
+ || value instanceof Blob
9
+ || value instanceof FormData
10
+ || value instanceof URLSearchParams
11
+ || value instanceof ReadableStream
12
+ || value instanceof Date
13
+ || value instanceof Map
14
+ || value instanceof Set
15
+ || value instanceof RegExp
16
+ || value instanceof Error
17
+ || value instanceof Promise
18
+ || Array.isArray(value)) {
19
+ return false;
20
+ }
21
+ // Check if it's a plain object (Object.prototype or null prototype)
22
+ const proto = Object.getPrototypeOf(value);
23
+ return proto === null || proto === Object.prototype;
24
+ }
25
+ export const concatBytes = (a, b) => {
26
+ const result = new Uint8Array(a.byteLength + b.byteLength);
27
+ result.set(a);
28
+ result.set(b, a.byteLength);
29
+ return result;
30
+ };
@@ -0,0 +1,12 @@
1
+ import type { BuildConfig } from "bun";
2
+ import { Context, Effect, Layer } from "effect";
3
+ import type { BundleContext } from "../bundler/Bundle.ts";
4
+ import * as Bundle from "../bundler/Bundle.ts";
5
+ export type BuildOptions = Omit<BuildConfig, "outdir">;
6
+ export declare const buildClient: (config: BuildOptions | string) => Effect.Effect<BundleContext, Bundle.BundleError, never>;
7
+ export declare const buildServer: (config: BuildOptions | string) => Effect.Effect<BundleContext, Bundle.BundleError, never>;
8
+ /**
9
+ * Given a config, build a bundle and returns every time when effect is executed.
10
+ */
11
+ export declare function build(config: BuildOptions): Effect.Effect<BundleContext, Bundle.BundleError>;
12
+ export declare function layer<T>(tag: Context.Tag<T, BundleContext>, config: BuildOptions): Layer.Layer<T, Bundle.BundleError, never>;
@@ -0,0 +1,145 @@
1
+ import { Array, Effect, Iterable, Layer, pipe, Record, } from "effect";
2
+ import * as NPath from "node:path";
3
+ import * as Bundle from "../bundler/Bundle.js";
4
+ export const buildClient = (config) => {
5
+ if (typeof config === "string") {
6
+ config = {
7
+ entrypoints: [config],
8
+ };
9
+ }
10
+ const baseConfig = {
11
+ sourcemap: "linked",
12
+ naming: {
13
+ entry: "[name]-[hash].[ext]",
14
+ chunk: "[name]-[hash].[ext]",
15
+ asset: "[name]-[hash].[ext]",
16
+ },
17
+ packages: "bundle",
18
+ publicPath: "/_bundle/",
19
+ };
20
+ const resolvedConfig = {
21
+ ...baseConfig,
22
+ target: "browser",
23
+ ...config,
24
+ };
25
+ return build(resolvedConfig);
26
+ };
27
+ export const buildServer = (config) => {
28
+ if (typeof config === "string") {
29
+ config = {
30
+ entrypoints: [config],
31
+ };
32
+ }
33
+ const baseConfig = {
34
+ sourcemap: "linked",
35
+ naming: {
36
+ entry: "[dir]/[name]-[hash].[ext]",
37
+ chunk: "[name]-[hash].[ext]",
38
+ asset: "[name]-[hash].[ext]",
39
+ },
40
+ packages: "bundle",
41
+ };
42
+ const resolvedConfig = {
43
+ ...baseConfig,
44
+ target: "bun",
45
+ ...config,
46
+ };
47
+ return build(resolvedConfig);
48
+ };
49
+ /**
50
+ * Given a config, build a bundle and returns every time when effect is executed.
51
+ */
52
+ export function build(config) {
53
+ return Effect.gen(function* () {
54
+ const output = yield* buildBun(config);
55
+ const manifest = generateManifestfromBunBundle(config, output);
56
+ const artifactsMap = Record.fromIterableBy(output.outputs, (v) => v.path.replace(/^\.\//, ""));
57
+ const resolve = (path) => {
58
+ return manifest.entrypoints[path] ?? null;
59
+ };
60
+ const getArtifact = (path) => {
61
+ return artifactsMap[resolve(path)]
62
+ ?? artifactsMap[path]
63
+ ?? null;
64
+ };
65
+ return {
66
+ ...manifest,
67
+ resolve,
68
+ getArtifact,
69
+ };
70
+ });
71
+ }
72
+ export function layer(tag, config) {
73
+ return Layer.effect(tag, build(config));
74
+ }
75
+ /**
76
+ * Finds common path prefix across provided paths.
77
+ */
78
+ function getBaseDir(paths) {
79
+ if (paths.length === 0)
80
+ return "";
81
+ if (paths.length === 1)
82
+ return NPath.dirname(paths[0]);
83
+ const segmentsList = paths.map((path) => NPath.dirname(path).split("/").filter(Boolean));
84
+ return segmentsList[0]
85
+ .filter((segment, i) => segmentsList.every((segs) => segs[i] === segment))
86
+ .reduce((path, seg) => `${path}/${seg}`, "") ?? "";
87
+ }
88
+ /**
89
+ * Maps entrypoints to their respective build artifacts.
90
+ * Entrypoint key is trimmed to remove common path prefix.
91
+ */
92
+ function joinBuildEntrypoints(options, output) {
93
+ const commonPathPrefix = getBaseDir(options.entrypoints) + "/";
94
+ return pipe(Iterable.zip(options.entrypoints, pipe(output.outputs,
95
+ // Filter out source maps to properly map artifacts to entrypoints.
96
+ Iterable.filter((v) => v.kind !== "sourcemap"
97
+ && !(v.loader === "html" && v.path.endsWith(".js"))))), Iterable.map(([entrypoint, artifact]) => {
98
+ return {
99
+ shortPath: entrypoint.replace(commonPathPrefix, ""),
100
+ fullPath: entrypoint,
101
+ artifact,
102
+ };
103
+ }));
104
+ }
105
+ /**
106
+ * Generate manifest from a build.
107
+ * Useful for SSR and providing source->artifact path mapping.
108
+ */
109
+ function generateManifestfromBunBundle(options, output, imports) {
110
+ const entrypointArtifacts = joinBuildEntrypoints(options, output);
111
+ return {
112
+ entrypoints: pipe(entrypointArtifacts, Iterable.map((v) => [
113
+ v.shortPath,
114
+ v.artifact.path.replace(/^\.\//, ""),
115
+ ]), Record.fromEntries),
116
+ artifacts: pipe(output.outputs, Iterable.map((v) => {
117
+ // strip './' prefix
118
+ const shortPath = v.path.replace(/^\.\//, "");
119
+ return {
120
+ path: shortPath,
121
+ type: v.type,
122
+ size: v.size,
123
+ hash: v.hash ?? undefined,
124
+ imports: imports?.get(v.path),
125
+ };
126
+ }), Array.fromIterable),
127
+ };
128
+ }
129
+ function buildBun(config) {
130
+ return Object.assign(Effect.gen(function* () {
131
+ const buildOutput = yield* Effect.tryPromise({
132
+ try: () => Bun.build(config),
133
+ catch: (err) => {
134
+ const cause = err instanceof AggregateError
135
+ ? err.errors?.[0] ?? err
136
+ : err;
137
+ return new Bundle.BundleError({
138
+ message: "Failed to Bun.build: " + cause,
139
+ cause: cause,
140
+ });
141
+ },
142
+ });
143
+ return buildOutput;
144
+ }));
145
+ }
@@ -0,0 +1,44 @@
1
+ import * as HttpServer from "@effect/platform/HttpServer";
2
+ import * as Bun from "bun";
3
+ import * as Context from "effect/Context";
4
+ import * as Effect from "effect/Effect";
5
+ import * as Layer from "effect/Layer";
6
+ import type * as Scope from "effect/Scope";
7
+ import * as RouteTree from "../RouteTree.ts";
8
+ import { WebSocketContext } from "./BunHttpServer_web.ts";
9
+ import * as BunRoute from "./BunRoute.ts";
10
+ type FetchHandler = (request: Request, server: Bun.Server<WebSocketContext>) => Response | Promise<Response>;
11
+ /**
12
+ * Basically `Omit<Bun.Serve.Options, "fetch" | "error" | "websocket">`
13
+ * TypeScript 5.9 cannot verify discriminated union types used in
14
+ * {@link Bun.serve} so we need to define them explicitly.
15
+ */
16
+ interface ServeOptions {
17
+ readonly port?: number;
18
+ readonly hostname?: string;
19
+ readonly reusePort?: boolean;
20
+ readonly ipv6Only?: boolean;
21
+ readonly idleTimeout?: number;
22
+ readonly development?: boolean;
23
+ }
24
+ export type BunHttpServer = {
25
+ readonly server: Bun.Server<WebSocketContext>;
26
+ readonly addRoutes: (routes: BunRoute.BunRoutes) => void;
27
+ readonly pushHandler: (fetch: FetchHandler) => void;
28
+ readonly popHandler: () => void;
29
+ };
30
+ export declare const BunHttpServer: Context.Tag<BunHttpServer, BunHttpServer>;
31
+ export declare const make: (options: ServeOptions) => Effect.Effect<BunHttpServer, never, Scope.Scope>;
32
+ /**
33
+ * Provides HttpServer using BunHttpServer under the hood.
34
+ */
35
+ export declare const layer: (options?: ServeOptions) => Layer.Layer<HttpServer.HttpServer | BunHttpServer>;
36
+ /**
37
+ * Registers routes provided via {@link Route.layer}
38
+ */
39
+ export declare function layerAuto(): Layer.Layer<never, never, BunHttpServer>;
40
+ /**
41
+ * Register routes in Bun.serve.
42
+ */
43
+ export declare function layerRoutes(tree: RouteTree.RouteTree): Layer.Layer<never, never, BunHttpServer>;
44
+ export {};
@@ -0,0 +1,187 @@
1
+ // @ts-nocheck
2
+ import * as HttpApp from "@effect/platform/HttpApp";
3
+ import * as HttpServer from "@effect/platform/HttpServer";
4
+ import * as HttpServerError from "@effect/platform/HttpServerError";
5
+ import * as HttpServerRequest from "@effect/platform/HttpServerRequest";
6
+ import * as Socket from "@effect/platform/Socket";
7
+ import * as Bun from "bun";
8
+ import * as Config from "effect/Config";
9
+ import * as Context from "effect/Context";
10
+ import * as Deferred from "effect/Deferred";
11
+ import * as Effect from "effect/Effect";
12
+ import * as Exit from "effect/Exit";
13
+ import * as FiberSet from "effect/FiberSet";
14
+ import * as Layer from "effect/Layer";
15
+ import * as Option from "effect/Option";
16
+ import * as PathPattern from "../PathPattern.js";
17
+ import * as Random from "../Random.js";
18
+ import * as Route from "../Route.js";
19
+ import * as RouteHttp from "../RouteHttp.js";
20
+ import * as RouteTree from "../RouteTree.js";
21
+ import EmptyHTML from "./_empty.html";
22
+ import { makeResponse, ServerRequestImpl, } from "./BunHttpServer_web.js";
23
+ import * as BunRoute from "./BunRoute.js";
24
+ export const BunHttpServer = Context.GenericTag("effect-start/BunServer");
25
+ export const make = (options) => Effect.gen(function* () {
26
+ const port = yield* Config.number("PORT").pipe(Effect.catchTag("ConfigError", () => {
27
+ if (typeof process !== "undefined"
28
+ && !process.stdout.isTTY
29
+ && process.env.CLAUDECODE) {
30
+ return Effect.succeed(0);
31
+ }
32
+ return Effect.succeed(3000);
33
+ }));
34
+ const hostname = yield* Config.string("HOSTNAME").pipe(Effect.catchTag("ConfigError", () => Effect.succeed(undefined)));
35
+ const handlerStack = [
36
+ function (_request, _server) {
37
+ return new Response("not found", { status: 404 });
38
+ },
39
+ ];
40
+ let currentRoutes = {};
41
+ // Bun HMR doesn't work on successive calls to `server.reload` if there are no routes
42
+ // on server start. We workaround that by passing a dummy HTMLBundle [2025-11-26]
43
+ // see: https://github.com/oven-sh/bun/issues/23564
44
+ currentRoutes[`/.BunEmptyHtml-${Random.token(6)}`] = EmptyHTML;
45
+ const websocket = {
46
+ open(ws) {
47
+ Deferred.unsafeDone(ws.data.deferred, Exit.succeed(ws));
48
+ },
49
+ message(ws, message) {
50
+ ws.data.run(message);
51
+ },
52
+ close(ws, code, closeReason) {
53
+ Deferred.unsafeDone(ws.data.closeDeferred, Socket.defaultCloseCodeIsError(code)
54
+ ? Exit.fail(new Socket.SocketCloseError({
55
+ reason: "Close",
56
+ code,
57
+ closeReason,
58
+ }))
59
+ : Exit.void);
60
+ },
61
+ };
62
+ const server = Bun.serve({
63
+ port,
64
+ hostname,
65
+ ...options,
66
+ routes: currentRoutes,
67
+ fetch: handlerStack[0],
68
+ websocket,
69
+ });
70
+ yield* Effect.addFinalizer(() => Effect.sync(() => {
71
+ server.stop();
72
+ }));
73
+ const reload = () => {
74
+ server.reload({
75
+ fetch: handlerStack[handlerStack.length - 1],
76
+ routes: currentRoutes,
77
+ websocket,
78
+ });
79
+ };
80
+ return BunHttpServer.of({
81
+ server,
82
+ pushHandler(fetch) {
83
+ handlerStack.push(fetch);
84
+ reload();
85
+ },
86
+ popHandler() {
87
+ handlerStack.pop();
88
+ reload();
89
+ },
90
+ addRoutes(routes) {
91
+ currentRoutes = {
92
+ ...currentRoutes,
93
+ ...routes,
94
+ };
95
+ reload();
96
+ },
97
+ });
98
+ });
99
+ /**
100
+ * Provides HttpServer using BunHttpServer under the hood.
101
+ */
102
+ export const layer = (options) => Layer.provideMerge(Layer.scoped(HttpServer.HttpServer, makeBunServer), Layer.scoped(BunHttpServer, make(options ?? {})));
103
+ /**
104
+ * Registers routes provided via {@link Route.layer}
105
+ */
106
+ export function layerAuto() {
107
+ return Layer.unwrapEffect(Effect.gen(function* () {
108
+ const bunServer = yield* BunHttpServer;
109
+ const routes = yield* Effect.serviceOption(Route.Routes);
110
+ if (Option.isSome(routes)) {
111
+ return layerRoutes(routes.value);
112
+ }
113
+ else {
114
+ return Layer.empty;
115
+ }
116
+ }));
117
+ }
118
+ /**
119
+ * Register routes in Bun.serve.
120
+ */
121
+ export function layerRoutes(tree) {
122
+ return Layer.effectDiscard(Effect.gen(function* () {
123
+ const bunServer = yield* BunHttpServer;
124
+ const runtime = yield* Effect.runtime();
125
+ const toWebHandler = RouteHttp.toWebHandlerRuntime(runtime);
126
+ const bunRoutes = {};
127
+ const pathGroups = new Map();
128
+ for (const route of RouteTree.walk(tree)) {
129
+ const bunDescriptors = BunRoute.descriptors(route);
130
+ if (bunDescriptors) {
131
+ const htmlBundle = yield* Effect.promise(bunDescriptors.bunLoad);
132
+ bunRoutes[`${bunDescriptors.bunPrefix}/*`] = htmlBundle;
133
+ }
134
+ const path = Route.descriptor(route).path;
135
+ const group = pathGroups.get(path) ?? [];
136
+ group.push(route);
137
+ pathGroups.set(path, group);
138
+ }
139
+ for (const [path, routes] of pathGroups) {
140
+ const handler = toWebHandler(routes);
141
+ for (const bunPath of PathPattern.toBun(path)) {
142
+ bunRoutes[bunPath] = handler;
143
+ }
144
+ }
145
+ bunServer.addRoutes(bunRoutes);
146
+ }));
147
+ }
148
+ const makeBunServer = Effect.gen(function* () {
149
+ const bunServer = yield* BunHttpServer;
150
+ return HttpServer.make({
151
+ address: {
152
+ _tag: "TcpAddress",
153
+ port: bunServer.server.port,
154
+ hostname: bunServer.server.hostname,
155
+ },
156
+ serve(httpApp, middleware) {
157
+ return Effect.gen(function* () {
158
+ const runFork = yield* FiberSet.makeRuntime();
159
+ const runtime = yield* Effect.runtime();
160
+ const app = HttpApp.toHandled(httpApp, (request, response) => Effect.sync(() => {
161
+ ;
162
+ request.resolve(makeResponse(request, response, runtime));
163
+ }), middleware);
164
+ function handler(request, server) {
165
+ return new Promise((resolve, _reject) => {
166
+ const fiber = runFork(Effect.provideService(app, HttpServerRequest.HttpServerRequest, new ServerRequestImpl(request, resolve, removeHost(request.url), server)));
167
+ request.signal.addEventListener("abort", () => {
168
+ runFork(fiber.interruptAsFork(HttpServerError.clientAbortFiberId));
169
+ }, { once: true });
170
+ });
171
+ }
172
+ yield* Effect.acquireRelease(Effect.sync(() => {
173
+ bunServer.pushHandler(handler);
174
+ }), () => Effect.sync(() => {
175
+ bunServer.popHandler();
176
+ }));
177
+ });
178
+ },
179
+ });
180
+ });
181
+ const removeHost = (url) => {
182
+ if (url[0] === "/") {
183
+ return url;
184
+ }
185
+ const index = url.indexOf("/", url.indexOf("//") + 2);
186
+ return index === -1 ? "/" : url.slice(index);
187
+ };