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,45 @@
1
+ type ErrorWithDetail = Error & {
2
+ details?: string;
3
+ };
4
+ interface ResolveRequest {
5
+ path: string | false;
6
+ context?: object;
7
+ descriptionFilePath?: string;
8
+ descriptionFileRoot?: string;
9
+ descriptionFileData?: Record<string, unknown>;
10
+ relativePath?: string;
11
+ ignoreSymlinks?: boolean;
12
+ fullySpecified?: boolean;
13
+ }
14
+ interface ResolveContext {
15
+ contextDependencies?: {
16
+ add: (item: string) => void;
17
+ };
18
+ fileDependencies?: {
19
+ add: (item: string) => void;
20
+ };
21
+ missingDependencies?: {
22
+ add: (item: string) => void;
23
+ };
24
+ stack?: Set<string>;
25
+ log?: (str: string) => void;
26
+ yield?: (request: ResolveRequest) => void;
27
+ }
28
+ interface ResolveOptions {
29
+ extensions?: string[];
30
+ mainFields?: (string | string[])[];
31
+ conditionNames?: string[];
32
+ fileSystem?: unknown;
33
+ useSyncFileSystemCalls?: boolean;
34
+ modules?: string | string[];
35
+ }
36
+ export interface Resolver {
37
+ resolve(context: object, path: string, request: string, resolveContext: ResolveContext, callback: (err: null | ErrorWithDetail, res?: string | false, req?: ResolveRequest) => void): void;
38
+ }
39
+ export declare class CachedInputFileSystem {
40
+ constructor(_fileSystem: unknown, _duration: number);
41
+ }
42
+ export declare const ResolverFactory: {
43
+ createResolver(options: ResolveOptions): Resolver;
44
+ };
45
+ export {};
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Bun adapter for enhanced-resolve
3
+ *
4
+ * This module provides a drop-in replacement for `enhanced-resolve` that uses
5
+ * Bun's built-in resolver. It implements the subset of the enhanced-resolve API
6
+ * used for Tailwind CSS.
7
+ */
8
+ import fs from "node:fs";
9
+ import path from "node:path";
10
+ export class CachedInputFileSystem {
11
+ constructor(_fileSystem, _duration) { }
12
+ }
13
+ export const ResolverFactory = {
14
+ createResolver(options) {
15
+ const extensions = options.extensions ?? [];
16
+ const mainFields = (options.mainFields ?? []).flatMap((f) => Array.isArray(f) ? f : [f]);
17
+ const conditionNames = options.conditionNames ?? [];
18
+ return {
19
+ resolve(_context, basePath, id, _resolveContext, callback) {
20
+ try {
21
+ const result = resolveSync(id, basePath, {
22
+ extensions,
23
+ mainFields,
24
+ conditionNames,
25
+ });
26
+ callback(null, result);
27
+ }
28
+ catch (err) {
29
+ callback(err instanceof Error ? err : new Error(String(err)));
30
+ }
31
+ },
32
+ };
33
+ },
34
+ };
35
+ function resolveSync(id, base, options) {
36
+ if (id.startsWith(".") || id.startsWith("/")) {
37
+ for (const ext of ["", ...options.extensions]) {
38
+ const fullPath = path.resolve(base, id + ext);
39
+ if (fs.existsSync(fullPath) && fs.statSync(fullPath).isFile()) {
40
+ return fullPath;
41
+ }
42
+ }
43
+ return undefined;
44
+ }
45
+ const packagePath = resolvePackagePath(id, base);
46
+ if (!packagePath)
47
+ return undefined;
48
+ const packageJsonPath = path.join(packagePath, "package.json");
49
+ if (!fs.existsSync(packageJsonPath))
50
+ return undefined;
51
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
52
+ for (const field of options.mainFields) {
53
+ if (typeof packageJson[field] === "string") {
54
+ const resolved = path.resolve(packagePath, packageJson[field]);
55
+ if (fs.existsSync(resolved))
56
+ return resolved;
57
+ }
58
+ }
59
+ if (packageJson.exports) {
60
+ const resolved = resolveExports(packageJson.exports, packagePath, options.conditionNames);
61
+ if (resolved && fs.existsSync(resolved))
62
+ return resolved;
63
+ }
64
+ try {
65
+ return Bun.resolveSync(id, base);
66
+ }
67
+ catch {
68
+ return undefined;
69
+ }
70
+ }
71
+ function resolvePackagePath(id, base) {
72
+ const parts = id.split("/");
73
+ const packageName = id.startsWith("@")
74
+ ? parts.slice(0, 2).join("/")
75
+ : parts[0];
76
+ let dir = base;
77
+ while (dir !== path.dirname(dir)) {
78
+ const candidate = path.join(dir, "node_modules", packageName);
79
+ if (fs.existsSync(candidate))
80
+ return candidate;
81
+ dir = path.dirname(dir);
82
+ }
83
+ return undefined;
84
+ }
85
+ function resolveExports(exports, packagePath, conditionNames) {
86
+ if (typeof exports === "string") {
87
+ return path.resolve(packagePath, exports);
88
+ }
89
+ if (exports && typeof exports === "object" && !Array.isArray(exports)) {
90
+ const exportsObj = exports;
91
+ if ("." in exportsObj) {
92
+ return resolveExports(exportsObj["."], packagePath, conditionNames);
93
+ }
94
+ for (const condition of conditionNames) {
95
+ if (condition in exportsObj) {
96
+ return resolveExports(exportsObj[condition], packagePath, conditionNames);
97
+ }
98
+ }
99
+ if ("default" in exportsObj) {
100
+ return resolveExports(exportsObj["default"], packagePath, conditionNames);
101
+ }
102
+ }
103
+ return undefined;
104
+ }
@@ -0,0 +1,4 @@
1
+ export * as BunBundle from "./BunBundle.ts";
2
+ export * as BunHttpServer from "./BunHttpServer.ts";
3
+ export * as BunImportTrackerPlugin from "./BunImportTrackerPlugin.ts";
4
+ export * as BunRoute from "./BunRoute.ts";
@@ -0,0 +1,4 @@
1
+ export * as BunBundle from "./BunBundle.js";
2
+ export * as BunHttpServer from "./BunHttpServer.js";
3
+ export * as BunImportTrackerPlugin from "./BunImportTrackerPlugin.js";
4
+ export * as BunRoute from "./BunRoute.js";
@@ -0,0 +1,60 @@
1
+ import { Context, Effect, PubSub } from "effect";
2
+ import * as Schema from "effect/Schema";
3
+ export declare const BundleEntrypointMetaKey: unique symbol;
4
+ export type BundleOutputMetaValue = {};
5
+ /**
6
+ * Generic shape describing a bundle across multiple bundlers
7
+ * (like bun, esbuild & vite)
8
+ */
9
+ export declare const BundleManifestSchema: Schema.Struct<{
10
+ entrypoints: Schema.Record$<typeof Schema.String, typeof Schema.String>;
11
+ artifacts: Schema.Array$<Schema.Struct<{
12
+ path: typeof Schema.String;
13
+ type: typeof Schema.String;
14
+ size: typeof Schema.Number;
15
+ hash: Schema.optional<typeof Schema.String>;
16
+ imports: Schema.optional<Schema.Array$<Schema.Struct<{
17
+ path: typeof Schema.String;
18
+ kind: Schema.Literal<["import-statement", "require-call", "require-resolve", "dynamic-import", "import-rule", "url-token", "internal", "entry-point-run", "entry-point-build"]>;
19
+ }>>>;
20
+ }>>;
21
+ }>;
22
+ export type BundleManifest = typeof BundleManifestSchema.Type;
23
+ export declare const BundleEvent: Schema.Union<[Schema.TaggedStruct<"Change", {
24
+ path: typeof Schema.String;
25
+ }>, Schema.TaggedStruct<"BuildError", {
26
+ error: typeof Schema.String;
27
+ }>]>;
28
+ export type BundleEvent = typeof BundleEvent.Type;
29
+ declare const IdPrefix = "effect-start/tags/";
30
+ export type BundleKey = `${string}Bundle`;
31
+ export type BundleId = `${typeof IdPrefix}${BundleKey}`;
32
+ /**
33
+ * Passed to bundle effects and within bundle runtime.
34
+ * Used to expose artifacts via HTTP server and properly resolve
35
+ * imports within the bundle.
36
+ */
37
+ export type BundleContext = BundleManifest & {
38
+ resolve: (url: string) => string | null;
39
+ getArtifact: (path: string) => Blob | null;
40
+ events?: PubSub.PubSub<BundleEvent>;
41
+ };
42
+ declare const BundleError_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 & {
43
+ readonly _tag: "BundleError";
44
+ } & Readonly<A>;
45
+ export declare class BundleError extends BundleError_base<{
46
+ message: string;
47
+ cause?: unknown;
48
+ }> {
49
+ }
50
+ export declare const emptyBundleContext: BundleContext;
51
+ export declare const handleBundleErrorSilently: (effect: Effect.Effect<BundleContext, BundleError>) => Effect.Effect<BundleContext, never>;
52
+ export declare const Tag: <const T extends BundleKey>(name: T) => <Identifier>() => Context.TagClass<Identifier, `effect-start/tags/${string}Bundle`, BundleContext>;
53
+ export type Tag = Context.Tag<BundleId, BundleContext>;
54
+ declare const ClientBundle_base: Context.TagClass<ClientBundle, `effect-start/tags/${string}Bundle`, BundleContext>;
55
+ export declare class ClientBundle extends ClientBundle_base {
56
+ }
57
+ declare const ServerBundle_base: Context.TagClass<ServerBundle, `effect-start/tags/${string}Bundle`, BundleContext>;
58
+ export declare class ServerBundle extends ServerBundle_base {
59
+ }
60
+ export {};
@@ -0,0 +1,48 @@
1
+ import { Context, Data, Effect, pipe, } from "effect";
2
+ import * as Schema from "effect/Schema";
3
+ export const BundleEntrypointMetaKey = Symbol.for("effect-start/BundleEntrypointMetaKey");
4
+ /**
5
+ * Generic shape describing a bundle across multiple bundlers
6
+ * (like bun, esbuild & vite)
7
+ */
8
+ export const BundleManifestSchema = Schema.Struct({
9
+ entrypoints: Schema.Record({
10
+ key: Schema.String,
11
+ value: Schema.String,
12
+ }),
13
+ artifacts: Schema.Array(Schema.Struct({
14
+ path: Schema.String,
15
+ type: Schema.String,
16
+ size: Schema.Number,
17
+ hash: pipe(Schema.String, Schema.optional),
18
+ imports: pipe(Schema.Array(Schema.Struct({
19
+ path: Schema.String,
20
+ kind: Schema.Literal("import-statement", "require-call", "require-resolve", "dynamic-import", "import-rule", "url-token", "internal", "entry-point-run", "entry-point-build"),
21
+ })), Schema.optional),
22
+ })),
23
+ });
24
+ const BundleEventChange = Schema.TaggedStruct("Change", {
25
+ path: Schema.String,
26
+ });
27
+ const BundleEventBuildError = Schema.TaggedStruct("BuildError", {
28
+ error: Schema.String,
29
+ });
30
+ export const BundleEvent = Schema.Union(BundleEventChange, BundleEventBuildError);
31
+ const IdPrefix = "effect-start/tags/";
32
+ export class BundleError extends Data.TaggedError("BundleError") {
33
+ }
34
+ export const emptyBundleContext = {
35
+ entrypoints: {},
36
+ artifacts: [],
37
+ resolve: () => null,
38
+ getArtifact: () => null,
39
+ };
40
+ export const handleBundleErrorSilently = (effect) => pipe(effect, Effect.catchTag("BundleError", (error) => Effect.gen(function* () {
41
+ yield* Effect.logError("Bundle build failed", error);
42
+ return emptyBundleContext;
43
+ })));
44
+ export const Tag = (name) => () => Context.Tag(`${IdPrefix}${name}`)();
45
+ export class ClientBundle extends Tag("ClientBundle")() {
46
+ }
47
+ export class ServerBundle extends Tag("ServerBundle")() {
48
+ }
@@ -0,0 +1,13 @@
1
+ import * as FileSystem from "@effect/platform/FileSystem";
2
+ import * as Effect from "effect/Effect";
3
+ import { type BundleContext, BundleError } from "./Bundle.ts";
4
+ /**
5
+ * Exports a bundle to a file system under specified directory.
6
+ */
7
+ export declare const toFiles: (context: BundleContext, outDir: string) => Effect.Effect<undefined, import("@effect/platform/Error").PlatformError | BundleError, FileSystem.FileSystem>;
8
+ /**
9
+ * Loads a bundle from a directory and returns a BundleContext.
10
+ * Expects the directory to contain a manifest.json file and all the artifacts
11
+ * referenced in the manifest.
12
+ */
13
+ export declare const fromFiles: (directory: string) => Effect.Effect<BundleContext, BundleError, FileSystem.FileSystem>;
@@ -0,0 +1,94 @@
1
+ import * as FileSystem from "@effect/platform/FileSystem";
2
+ import * as Array from "effect/Array";
3
+ import * as Effect from "effect/Effect";
4
+ import * as Function from "effect/Function";
5
+ import * as Iterable from "effect/Iterable";
6
+ import * as Record from "effect/Record";
7
+ import * as S from "effect/Schema";
8
+ import { BundleError, BundleManifestSchema, } from "./Bundle.js";
9
+ /**
10
+ * Exports a bundle to a file system under specified directory.
11
+ */
12
+ export const toFiles = (context, outDir) => {
13
+ return Effect.gen(function* () {
14
+ const fs = yield* FileSystem.FileSystem;
15
+ const manifest = {
16
+ entrypoints: context.entrypoints,
17
+ artifacts: context.artifacts,
18
+ };
19
+ const normalizedOutDir = outDir.replace(/\/$/, "");
20
+ const bundleArtifacts = Function.pipe(manifest.artifacts, Array.map((artifact) => [artifact.path, context.getArtifact(artifact.path)]), Record.fromEntries);
21
+ const extraArtifacts = {
22
+ "manifest.json": new Blob([JSON.stringify(manifest, undefined, 2)], {
23
+ type: "application/json",
24
+ }),
25
+ };
26
+ const allArtifacts = {
27
+ ...bundleArtifacts,
28
+ ...extraArtifacts,
29
+ };
30
+ const existingOutDirFiles = yield* fs.readDirectory(normalizedOutDir).pipe(Effect.catchAll(() => Effect.succeed(null)));
31
+ // check if the output directory is empty. if it contains previous build,
32
+ // remove it. Otherwise fail.
33
+ if (existingOutDirFiles && existingOutDirFiles.length > 0) {
34
+ if (existingOutDirFiles.includes("manifest.json")) {
35
+ yield* Effect.logWarning("Output directory seems to contain previous build. Overwriting...");
36
+ yield* fs.remove(normalizedOutDir, {
37
+ recursive: true,
38
+ });
39
+ }
40
+ else {
41
+ return yield* Effect.fail(new BundleError({
42
+ message: "Output directory is not empty",
43
+ }));
44
+ }
45
+ }
46
+ yield* fs.makeDirectory(normalizedOutDir, {
47
+ recursive: true,
48
+ });
49
+ // write all artifacts to files
50
+ yield* Effect.all(Function.pipe(allArtifacts, Record.toEntries, Array.map(([p, b]) => Function.pipe(Effect.tryPromise({
51
+ try: () => b.arrayBuffer(),
52
+ catch: (e) => new BundleError({
53
+ message: "Failed to read an artifact as a buffer",
54
+ cause: e,
55
+ }),
56
+ }), Effect.andThen((b) => fs.writeFile(`${normalizedOutDir}/${p}`, new Uint8Array(b)))))), { concurrency: 16 });
57
+ });
58
+ };
59
+ /**
60
+ * Loads a bundle from a directory and returns a BundleContext.
61
+ * Expects the directory to contain a manifest.json file and all the artifacts
62
+ * referenced in the manifest.
63
+ */
64
+ export const fromFiles = (directory) => {
65
+ return Effect.gen(function* () {
66
+ const fs = yield* FileSystem.FileSystem;
67
+ const normalizedDir = directory.replace(/\/$/, "");
68
+ const manifest = yield* Function.pipe(fs.readFileString(`${normalizedDir}/manifest.json`), Effect.andThen((v) => JSON.parse(v)), Effect.andThen(S.decodeUnknownSync(BundleManifestSchema)), Effect.catchAll((e) => Effect.fail(new BundleError({
69
+ message: `Failed to read manifest.json from ${normalizedDir}`,
70
+ cause: e,
71
+ }))));
72
+ const artifactPaths = Array.map(manifest.artifacts, (a) => a.path);
73
+ const artifactBlobs = yield* Function.pipe(artifactPaths, Iterable.map((path) => fs.readFile(`${normalizedDir}/${path}`)), Effect.all, Effect.catchAll((e) => new BundleError({
74
+ message: `Failed to read an artifact from ${normalizedDir}`,
75
+ cause: e,
76
+ })), Effect.andThen(Iterable.map((v, i) => new Blob([v.slice(0)], {
77
+ type: manifest.artifacts[i].type,
78
+ }))));
79
+ const artifactsRecord = Function.pipe(Iterable.zip(artifactPaths, artifactBlobs), Record.fromEntries);
80
+ const bundleContext = {
81
+ ...manifest,
82
+ // TODO: support fullpath file:// urls
83
+ // this will require having an access to base path of a build
84
+ // and maybe problematic because bundlers transform urls on build
85
+ resolve: (url) => {
86
+ return manifest.entrypoints[url] ?? null;
87
+ },
88
+ getArtifact: (path) => {
89
+ return artifactsRecord[path] ?? null;
90
+ },
91
+ };
92
+ return bundleContext;
93
+ });
94
+ };
@@ -0,0 +1,45 @@
1
+ import type * as HttpApp from "@effect/platform/HttpApp";
2
+ import { RouteNotFound } from "@effect/platform/HttpServerError";
3
+ import * as HttpServerRequest from "@effect/platform/HttpServerRequest";
4
+ import * as HttpServerResponse from "@effect/platform/HttpServerResponse";
5
+ import * as Effect from "effect/Effect";
6
+ import * as Scope from "effect/Scope";
7
+ import * as Bundle from "./Bundle.ts";
8
+ /**
9
+ * Handles all entrypoints automatically.
10
+ * Serves HTML entrypoints without requiring explicit route definitions for each one.
11
+ * Examples:
12
+ * index.html -> /
13
+ * contact.html -> /contact
14
+ * about/index.html -> /about
15
+ */
16
+ export declare function entrypoint(uri?: string): HttpApp.Default<RouteNotFound, Bundle.ClientBundle>;
17
+ export declare function httpApp(opts?: {
18
+ urlPrefix?: string;
19
+ }): HttpApp.Default<RouteNotFound, Scope.Scope | Bundle.ClientBundle>;
20
+ export declare const toHttpApp: <E, R>(bundleTag: Effect.Effect<Bundle.BundleContext, E, R>, opts?: {
21
+ urlPrefix?: string;
22
+ }) => Effect.Effect<HttpServerResponse.HttpServerResponse, RouteNotFound | E, HttpServerRequest.HttpServerRequest | R>;
23
+ /**
24
+ * Render HTML to a string.
25
+ * Useful for SSR.
26
+ */
27
+ export declare function renderPromise(clientBundle: Bundle.Tag, render: (request: Request, resolve: (url: string) => string) => Promise<Response>): Effect.Effect<HttpServerResponse.HttpServerResponse, Bundle.BundleError, HttpServerRequest.HttpServerRequest | `effect-start/tags/${string}Bundle`>;
28
+ /**
29
+ * Exposes bundle assets via HTTP routes.
30
+ * Serves bundle artifacts, manifest.json, and events endpoint at the specified path.
31
+ */
32
+ export declare function withAssets(opts?: {
33
+ path?: string;
34
+ }): <E, R>(app: HttpApp.Default<E, R>) => Effect.Effect<HttpServerResponse.HttpServerResponse, RouteNotFound | E, HttpServerRequest.HttpServerRequest | Bundle.ClientBundle | R>;
35
+ /**
36
+ * @see {entrypoint}
37
+ */
38
+ export declare function withEntrypoints(): <E, R>(app: HttpApp.Default<E, R>) => Effect.Effect<HttpServerResponse.HttpServerResponse, E, HttpServerRequest.HttpServerRequest | Bundle.ClientBundle | R>;
39
+ /**
40
+ * Combines both withAssets and withEntrypoints.
41
+ * Provides complete bundle HTTP functionality in a single function call.
42
+ */
43
+ export declare function withBundle(opts?: {
44
+ path?: string;
45
+ }): <E, R>(app: HttpApp.Default<E, R>) => Effect.Effect<HttpServerResponse.HttpServerResponse, RouteNotFound | E, HttpServerRequest.HttpServerRequest | Bundle.ClientBundle | R>;
@@ -0,0 +1,176 @@
1
+ import * as Headers from "@effect/platform/Headers";
2
+ import * as HttpMiddleware from "@effect/platform/HttpMiddleware";
3
+ import { RouteNotFound } from "@effect/platform/HttpServerError";
4
+ import * as HttpServerRequest from "@effect/platform/HttpServerRequest";
5
+ import * as HttpServerResponse from "@effect/platform/HttpServerResponse";
6
+ import * as Effect from "effect/Effect";
7
+ import * as Function from "effect/Function";
8
+ import * as Option from "effect/Option";
9
+ import * as Stream from "effect/Stream";
10
+ import * as NPath from "node:path";
11
+ import * as NUrl from "node:url";
12
+ import * as SseHttpResponse from "../experimental/SseHttpResponse.js";
13
+ import * as Bundle from "./Bundle.js";
14
+ const DefaultBundleEndpoint = "/_bundle";
15
+ /**
16
+ * Handles all entrypoints automatically.
17
+ * Serves HTML entrypoints without requiring explicit route definitions for each one.
18
+ * Examples:
19
+ * index.html -> /
20
+ * contact.html -> /contact
21
+ * about/index.html -> /about
22
+ */
23
+ export function entrypoint(uri) {
24
+ return Effect.gen(function* () {
25
+ uri = uri?.startsWith("file://") ? NUrl.fileURLToPath(uri) : uri;
26
+ const request = yield* HttpServerRequest.HttpServerRequest;
27
+ const bundle = yield* Bundle.ClientBundle;
28
+ const requestPath = request.url.substring(1);
29
+ const pathAttempts = uri
30
+ ? [
31
+ // try paths from all parent directories in case absolute path is passed,
32
+ // like it is the case for `import(f, { type: "file" })`
33
+ ...uri
34
+ .split(NPath.sep)
35
+ .map((_, i, a) => NPath.join(...a.slice(i))),
36
+ ]
37
+ : [
38
+ requestPath ? `${requestPath}.html` : null,
39
+ requestPath ? `${requestPath}/index.html` : null,
40
+ requestPath === "" ? "index.html" : "",
41
+ ];
42
+ const artifact = pathAttempts
43
+ .filter(Boolean)
44
+ .map(path => bundle.getArtifact(path))
45
+ .find(Boolean);
46
+ if (artifact) {
47
+ return yield* renderBlob(artifact);
48
+ }
49
+ return yield* Effect.fail(new RouteNotFound({
50
+ request,
51
+ }));
52
+ });
53
+ }
54
+ export function httpApp(opts) {
55
+ return toHttpApp(Bundle.ClientBundle, opts);
56
+ }
57
+ export const toHttpApp = (bundleTag, opts) => {
58
+ return Effect.gen(function* () {
59
+ const request = yield* HttpServerRequest.HttpServerRequest;
60
+ const bundle = yield* bundleTag;
61
+ const path = opts?.urlPrefix && request.url.startsWith(opts.urlPrefix + "/")
62
+ ? request.url.substring(opts.urlPrefix.length + 1)
63
+ : request.url.substring(1);
64
+ /**
65
+ * Expose manifest that contains information about the bundle.
66
+ */
67
+ if (path === "manifest.json") {
68
+ return HttpServerResponse.text(JSON.stringify({
69
+ entrypoints: bundle.entrypoints,
70
+ artifacts: bundle.artifacts,
71
+ }, undefined, 2), {
72
+ headers: {
73
+ "Content-Type": "application/json",
74
+ },
75
+ });
76
+ }
77
+ /**
78
+ * Expose events endpoint if available.
79
+ * Useful for development to implement live reload.
80
+ */
81
+ if (bundle.events && path === "events") {
82
+ return yield* SseHttpResponse.make(Stream.fromPubSub(bundle.events));
83
+ }
84
+ const artifact = bundle.artifacts.find((a) => a.path === path);
85
+ /**
86
+ * Expose artifacts.
87
+ */
88
+ if (artifact) {
89
+ const artifactBlob = bundle.getArtifact(path);
90
+ return yield* renderBlob(artifactBlob);
91
+ }
92
+ return yield* Effect.fail(new RouteNotFound({
93
+ request,
94
+ }));
95
+ });
96
+ };
97
+ /**
98
+ * Render HTML to a string.
99
+ * Useful for SSR.
100
+ */
101
+ export function renderPromise(clientBundle, render) {
102
+ return Effect.gen(function* () {
103
+ const bundle = yield* clientBundle;
104
+ const req = yield* HttpServerRequest.HttpServerRequest;
105
+ const fetchReq = req.source;
106
+ // TODO: add support for file:// urls
107
+ // this will require handling source base path
108
+ const resolve = (url) => {
109
+ const path = url.startsWith("file://")
110
+ ? NUrl.fileURLToPath(url)
111
+ : url;
112
+ const publicBase = "/.bundle";
113
+ const publicPath = bundle.resolve(path);
114
+ return NPath.join(publicBase, publicPath ?? path);
115
+ };
116
+ const output = yield* Effect.tryPromise({
117
+ try: () => render(fetchReq, resolve),
118
+ catch: (e) => new Bundle.BundleError({
119
+ message: "Failed to render",
120
+ cause: e,
121
+ }),
122
+ });
123
+ return yield* HttpServerResponse.raw(output.body, {
124
+ status: output.status,
125
+ statusText: output.statusText,
126
+ headers: Headers.fromInput(output.headers),
127
+ });
128
+ });
129
+ }
130
+ const renderBlob = (blob) => {
131
+ return Effect.gen(function* () {
132
+ const bytes = yield* Effect
133
+ .promise(() => blob.arrayBuffer())
134
+ .pipe(Effect.andThen(v => new Uint8Array(v)));
135
+ return HttpServerResponse.uint8Array(bytes, {
136
+ headers: {
137
+ "content-type": blob.type,
138
+ "content-length": String(blob.size),
139
+ "cache-control": "public, max-age=3600",
140
+ },
141
+ });
142
+ });
143
+ };
144
+ /**
145
+ * Exposes bundle assets via HTTP routes.
146
+ * Serves bundle artifacts, manifest.json, and events endpoint at the specified path.
147
+ */
148
+ export function withAssets(opts) {
149
+ const path = opts?.path ?? DefaultBundleEndpoint;
150
+ return HttpMiddleware.make((app) => Effect.gen(function* () {
151
+ const request = yield* HttpServerRequest.HttpServerRequest;
152
+ if (request.url.startsWith(path + "/")) {
153
+ return yield* toHttpApp(Bundle.ClientBundle, { urlPrefix: path });
154
+ }
155
+ return yield* app;
156
+ }));
157
+ }
158
+ /**
159
+ * @see {entrypoint}
160
+ */
161
+ export function withEntrypoints() {
162
+ return HttpMiddleware.make((app) => Effect.gen(function* () {
163
+ const entrypointResponse = yield* entrypoint().pipe(Effect.option);
164
+ if (Option.isSome(entrypointResponse)) {
165
+ return entrypointResponse.value;
166
+ }
167
+ return yield* app;
168
+ }));
169
+ }
170
+ /**
171
+ * Combines both withAssets and withEntrypoints.
172
+ * Provides complete bundle HTTP functionality in a single function call.
173
+ */
174
+ export function withBundle(opts) {
175
+ return Function.flow(withAssets(opts), withEntrypoints());
176
+ }
@@ -0,0 +1,2 @@
1
+ export declare function getOverlay(): HTMLPreElement;
2
+ export declare function showBuildError(message: string): void;
@@ -0,0 +1,32 @@
1
+ const OVERLAY_ID = "_bundler_error_overlay";
2
+ export function getOverlay() {
3
+ let overlay = document.getElementById(OVERLAY_ID);
4
+ if (!overlay) {
5
+ overlay = document.createElement("pre");
6
+ overlay.id = OVERLAY_ID;
7
+ Object.assign(overlay.style, {
8
+ position: "fixed",
9
+ top: "0",
10
+ left: "0",
11
+ right: "0",
12
+ maxHeight: "40%",
13
+ overflowY: "auto",
14
+ margin: "0",
15
+ padding: "4px",
16
+ background: "black",
17
+ color: "red",
18
+ fontFamily: "monospace",
19
+ zIndex: "2147483647",
20
+ whiteSpace: "pre-wrap",
21
+ });
22
+ document.body.appendChild(overlay);
23
+ }
24
+ return overlay;
25
+ }
26
+ export function showBuildError(message) {
27
+ const overlay = getOverlay();
28
+ const atBottom = overlay.scrollTop + overlay.clientHeight >= overlay.scrollHeight - 1;
29
+ overlay.textContent += message + "\n";
30
+ if (atBottom)
31
+ overlay.scrollTop = overlay.scrollHeight;
32
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Persist current scroll state to session storage.
3
+ * Scroll state is saved relatively to visible elements.
4
+ */
5
+ export declare function persist(): void;
6
+ export declare function restore(): void;