effect-start 0.17.2 → 0.19.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.
- package/dist/Development.d.ts +7 -2
- package/dist/Development.js +12 -6
- package/dist/PlatformRuntime.d.ts +4 -0
- package/dist/PlatformRuntime.js +9 -0
- package/dist/Route.d.ts +6 -2
- package/dist/Route.js +22 -0
- package/dist/RouteHttp.d.ts +1 -1
- package/dist/RouteHttp.js +12 -19
- package/dist/RouteMount.d.ts +2 -1
- package/dist/Start.d.ts +1 -5
- package/dist/Start.js +1 -8
- package/dist/Unique.d.ts +50 -0
- package/dist/Unique.js +187 -0
- package/dist/bun/BunHttpServer.js +5 -6
- package/dist/bun/BunRoute.d.ts +1 -1
- package/dist/bun/BunRoute.js +2 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/node/Effectify.d.ts +209 -0
- package/dist/node/Effectify.js +19 -0
- package/dist/node/FileSystem.d.ts +3 -5
- package/dist/node/FileSystem.js +42 -62
- package/dist/node/PlatformError.d.ts +46 -0
- package/dist/node/PlatformError.js +43 -0
- package/dist/testing/TestLogger.js +1 -1
- package/package.json +10 -5
- package/src/Development.ts +13 -18
- package/src/PlatformRuntime.ts +11 -0
- package/src/Route.ts +31 -2
- package/src/RouteHttp.ts +15 -31
- package/src/RouteMount.ts +1 -1
- package/src/Start.ts +1 -15
- package/src/Unique.ts +232 -0
- package/src/bun/BunHttpServer.ts +6 -9
- package/src/bun/BunRoute.ts +3 -3
- package/src/index.ts +1 -0
- package/src/node/Effectify.ts +262 -0
- package/src/node/FileSystem.ts +59 -97
- package/src/node/PlatformError.ts +102 -0
- package/src/testing/TestLogger.ts +1 -1
- package/dist/Random.d.ts +0 -5
- package/dist/Random.js +0 -49
- package/src/Commander.test.ts +0 -1639
- package/src/ContentNegotiation.test.ts +0 -603
- package/src/Development.test.ts +0 -119
- package/src/Entity.test.ts +0 -592
- package/src/FileRouterPattern.test.ts +0 -147
- package/src/FileRouter_files.test.ts +0 -64
- package/src/FileRouter_path.test.ts +0 -145
- package/src/FileRouter_tree.test.ts +0 -132
- package/src/Http.test.ts +0 -319
- package/src/HttpAppExtra.test.ts +0 -103
- package/src/HttpUtils.test.ts +0 -85
- package/src/PathPattern.test.ts +0 -648
- package/src/Random.ts +0 -59
- package/src/RouteBody.test.ts +0 -232
- package/src/RouteHook.test.ts +0 -40
- package/src/RouteHttp.test.ts +0 -2909
- package/src/RouteMount.test.ts +0 -481
- package/src/RouteSchema.test.ts +0 -427
- package/src/RouteSse.test.ts +0 -249
- package/src/RouteTree.test.ts +0 -494
- package/src/RouteTrie.test.ts +0 -322
- package/src/RouterPattern.test.ts +0 -676
- package/src/Values.test.ts +0 -263
- package/src/bun/BunBundle.test.ts +0 -268
- package/src/bun/BunBundle_imports.test.ts +0 -48
- package/src/bun/BunHttpServer.test.ts +0 -251
- package/src/bun/BunImportTrackerPlugin.test.ts +0 -77
- package/src/bun/BunRoute.test.ts +0 -162
- package/src/bundler/BundleHttp.test.ts +0 -132
- package/src/effect/HttpRouter.test.ts +0 -548
- package/src/experimental/EncryptedCookies.test.ts +0 -488
- package/src/hyper/HyperHtml.test.ts +0 -209
- package/src/hyper/HyperRoute.test.tsx +0 -197
- package/src/middlewares/BasicAuthMiddleware.test.ts +0 -84
- package/src/testing/TestHttpClient.test.ts +0 -83
- package/src/testing/TestLogger.test.ts +0 -51
- package/src/x/datastar/Datastar.test.ts +0 -266
- package/src/x/tailwind/TailwindPlugin.test.ts +0 -333
package/dist/Development.d.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import * as Context from "effect/Context";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as Layer from "effect/Layer";
|
|
4
|
+
import * as PubSub from "effect/PubSub";
|
|
5
|
+
import * as Stream from "effect/Stream";
|
|
6
|
+
import * as Error from "./node/PlatformError.ts";
|
|
7
|
+
import * as FileSystem from "@effect/platform/FileSystem";
|
|
3
8
|
export type DevelopmentEvent = FileSystem.WatchEvent | {
|
|
4
9
|
readonly _tag: "Reload";
|
|
5
10
|
};
|
package/dist/Development.js
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
|
|
1
|
+
import * as Context from "effect/Context";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as Function from "effect/Function";
|
|
4
|
+
import * as GlobalValue from "effect/GlobalValue";
|
|
5
|
+
import * as Layer from "effect/Layer";
|
|
6
|
+
import * as Option from "effect/Option";
|
|
7
|
+
import * as PubSub from "effect/PubSub";
|
|
8
|
+
import * as Stream from "effect/Stream";
|
|
9
|
+
import * as FileSystem from "@effect/platform/FileSystem";
|
|
10
|
+
const devState = GlobalValue.globalValue(Symbol.for("effect-start/Development"), () => ({
|
|
5
11
|
count: 0,
|
|
6
12
|
pubsub: null,
|
|
7
13
|
}));
|
|
@@ -34,7 +40,7 @@ export const watch = (opts) => Effect.gen(function* () {
|
|
|
34
40
|
if (devState.count === 1) {
|
|
35
41
|
const pubsub = yield* PubSub.unbounded();
|
|
36
42
|
devState.pubsub = pubsub;
|
|
37
|
-
yield* pipe(watchSource({
|
|
43
|
+
yield* Function.pipe(watchSource({
|
|
38
44
|
path: opts?.path,
|
|
39
45
|
recursive: opts?.recursive,
|
|
40
46
|
filter: opts?.filter ?? filterSourceFiles,
|
|
@@ -46,7 +52,7 @@ export const watch = (opts) => Effect.gen(function* () {
|
|
|
46
52
|
return { events: devState.pubsub };
|
|
47
53
|
});
|
|
48
54
|
export const layerWatch = (opts) => Layer.scoped(Development, watch(opts));
|
|
49
|
-
export const stream = () => Stream.unwrap(pipe(Effect.serviceOption(Development), Effect.map(Option.match({
|
|
55
|
+
export const stream = () => Stream.unwrap(Function.pipe(Effect.serviceOption(Development), Effect.map(Option.match({
|
|
50
56
|
onNone: () => Stream.empty,
|
|
51
57
|
onSome: (dev) => Stream.fromPubSub(dev.events),
|
|
52
58
|
}))));
|
package/dist/Route.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as Context from "effect/Context";
|
|
2
|
-
import
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
3
|
import * as Layer from "effect/Layer";
|
|
4
4
|
import * as Pipeable from "effect/Pipeable";
|
|
5
|
-
import
|
|
5
|
+
import * as Entity from "./Entity.ts";
|
|
6
6
|
import * as RouteBody from "./RouteBody.ts";
|
|
7
7
|
import * as RouteTree from "./RouteTree.ts";
|
|
8
8
|
import * as Values from "./Values.ts";
|
|
@@ -91,8 +91,12 @@ export declare const json: RouteBody.BuildReturn<Values.Json, "json">;
|
|
|
91
91
|
export declare const bytes: RouteBody.BuildReturn<Uint8Array<ArrayBufferLike>, "bytes">;
|
|
92
92
|
export { render, } from "./RouteBody.ts";
|
|
93
93
|
export { sse, } from "./RouteSse.ts";
|
|
94
|
+
export declare function redirect(url: string | URL, status?: 301 | 302 | 303 | 307 | 308): Entity.Entity<"">;
|
|
94
95
|
declare const Routes_base: Context.TagClass<Routes, "effect-start/Routes", RouteTree.RouteTree<RouteTree.RouteMap>>;
|
|
95
96
|
export declare class Routes extends Routes_base {
|
|
96
97
|
}
|
|
97
98
|
export declare function layer(routes: RouteTree.RouteMap | RouteTree.RouteTree): Layer.Layer<Routes, never, never>;
|
|
98
99
|
export { make as tree, } from "./RouteTree.ts";
|
|
100
|
+
export declare function lazy<T extends RouteSet.Any>(load: () => Promise<{
|
|
101
|
+
default: T;
|
|
102
|
+
}>): Effect.Effect<T, never, never>;
|
package/dist/Route.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import * as Context from "effect/Context";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
2
3
|
import * as Layer from "effect/Layer";
|
|
3
4
|
import * as Pipeable from "effect/Pipeable";
|
|
4
5
|
import * as Predicate from "effect/Predicate";
|
|
6
|
+
import * as Entity from "./Entity.js";
|
|
5
7
|
import * as RouteBody from "./RouteBody.js";
|
|
6
8
|
import * as RouteTree from "./RouteTree.js";
|
|
7
9
|
export const RouteItems = Symbol();
|
|
@@ -71,6 +73,14 @@ export const bytes = RouteBody.build({
|
|
|
71
73
|
});
|
|
72
74
|
export { render, } from "./RouteBody.js";
|
|
73
75
|
export { sse, } from "./RouteSse.js";
|
|
76
|
+
export function redirect(url, status = 302) {
|
|
77
|
+
return Entity.make("", {
|
|
78
|
+
status,
|
|
79
|
+
headers: {
|
|
80
|
+
location: url instanceof URL ? url.href : url,
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
}
|
|
74
84
|
export class Routes extends Context.Tag("effect-start/Routes")() {
|
|
75
85
|
}
|
|
76
86
|
export function layer(routes) {
|
|
@@ -79,3 +89,15 @@ export function layer(routes) {
|
|
|
79
89
|
: RouteTree.make(routes));
|
|
80
90
|
}
|
|
81
91
|
export { make as tree, } from "./RouteTree.js";
|
|
92
|
+
export function lazy(load) {
|
|
93
|
+
let cached;
|
|
94
|
+
return Effect.suspend(() => {
|
|
95
|
+
if (cached !== undefined) {
|
|
96
|
+
return Effect.succeed(cached);
|
|
97
|
+
}
|
|
98
|
+
return Effect.promise(load).pipe(Effect.map((mod) => {
|
|
99
|
+
cached = mod.default;
|
|
100
|
+
return cached;
|
|
101
|
+
}));
|
|
102
|
+
});
|
|
103
|
+
}
|
package/dist/RouteHttp.d.ts
CHANGED
|
@@ -5,7 +5,6 @@ import * as Route from "./Route.ts";
|
|
|
5
5
|
import * as RouteBody from "./RouteBody.ts";
|
|
6
6
|
import * as RouteMount from "./RouteMount.ts";
|
|
7
7
|
import * as RouteTree from "./RouteTree.ts";
|
|
8
|
-
export { currentSpanNameGenerator, currentTracerDisabledWhen, parentSpanFromHeaders, withSpanNameGenerator, withTracerDisabledWhen, } from "./RouteHttpTracer.ts";
|
|
9
8
|
type UnboundedRouteWithMethod = Route.Route.With<{
|
|
10
9
|
method: RouteMount.RouteMount.Method;
|
|
11
10
|
format?: RouteBody.Format;
|
|
@@ -19,3 +18,4 @@ export declare const clientAbortFiberId: FiberId.Runtime;
|
|
|
19
18
|
export declare const toWebHandlerRuntime: <R>(runtime: Runtime.Runtime<R>) => (routes: Iterable<UnboundedRouteWithMethod>) => Http.WebHandler;
|
|
20
19
|
export declare const toWebHandler: (routes: Iterable<UnboundedRouteWithMethod>) => Http.WebHandler;
|
|
21
20
|
export declare function walkHandles(tree: RouteTree.RouteTree, runtime?: Runtime.Runtime<never>): Generator<[path: string, handler: Http.WebHandler]>;
|
|
21
|
+
export {};
|
package/dist/RouteHttp.js
CHANGED
|
@@ -12,7 +12,6 @@ import * as Route from "./Route.js";
|
|
|
12
12
|
import * as RouteHttpTracer from "./RouteHttpTracer.js";
|
|
13
13
|
import * as RouteTree from "./RouteTree.js";
|
|
14
14
|
import * as StreamExtra from "./StreamExtra.js";
|
|
15
|
-
export { currentSpanNameGenerator, currentTracerDisabledWhen, parentSpanFromHeaders, withSpanNameGenerator, withTracerDisabledWhen, } from "./RouteHttpTracer.js";
|
|
16
15
|
// Used to match Accept headers against available route formats.
|
|
17
16
|
// text/* matches any text type (text/plain, text/event-stream, text/markdown, etc.)
|
|
18
17
|
const formatToMediaType = {
|
|
@@ -46,6 +45,10 @@ const getStatusFromCause = (cause) => {
|
|
|
46
45
|
}
|
|
47
46
|
return 500;
|
|
48
47
|
};
|
|
48
|
+
const respondError = (options) => new Response(JSON.stringify(options, null, 2), {
|
|
49
|
+
status: options.status,
|
|
50
|
+
headers: { "content-type": "application/json" },
|
|
51
|
+
});
|
|
49
52
|
function streamResponse(stream, headers, status, runtime) {
|
|
50
53
|
const encoder = new TextEncoder();
|
|
51
54
|
const byteStream = stream.pipe(Stream.map((chunk) => typeof chunk === "string"
|
|
@@ -116,9 +119,7 @@ export const toWebHandlerRuntime = (runtime) => {
|
|
|
116
119
|
const accept = request.headers.get("accept");
|
|
117
120
|
const methodRoutes = methodGroups[method] ?? [];
|
|
118
121
|
if (methodRoutes.length === 0 && wildcards.length === 0) {
|
|
119
|
-
return Promise.resolve(
|
|
120
|
-
status: 405,
|
|
121
|
-
}));
|
|
122
|
+
return Promise.resolve(respondError({ status: 405, message: "method not allowed" }));
|
|
122
123
|
}
|
|
123
124
|
const allRoutes = [...wildcards, ...methodRoutes];
|
|
124
125
|
const selectedFormat = determineSelectedFormat(accept, allRoutes);
|
|
@@ -130,9 +131,7 @@ export const toWebHandlerRuntime = (runtime) => {
|
|
|
130
131
|
if (selectedFormat === undefined
|
|
131
132
|
&& hasSpecificFormatRoutes
|
|
132
133
|
&& !hasWildcardFormatRoutes) {
|
|
133
|
-
return Promise.resolve(
|
|
134
|
-
status: 406,
|
|
135
|
-
}));
|
|
134
|
+
return Promise.resolve(respondError({ status: 406, message: "not acceptable" }));
|
|
136
135
|
}
|
|
137
136
|
const createChain = (initialContext) => {
|
|
138
137
|
let index = 0;
|
|
@@ -178,9 +177,7 @@ export const toWebHandlerRuntime = (runtime) => {
|
|
|
178
177
|
? result
|
|
179
178
|
: Entity.make(result, { status: 200 });
|
|
180
179
|
if (entity.status === 404 && entity.body === undefined) {
|
|
181
|
-
return
|
|
182
|
-
status: 406,
|
|
183
|
-
});
|
|
180
|
+
return respondError({ status: 406, message: "not acceptable" });
|
|
184
181
|
}
|
|
185
182
|
return yield* toResponse(entity, selectedFormat, runtime);
|
|
186
183
|
});
|
|
@@ -217,9 +214,8 @@ export const toWebHandlerRuntime = (runtime) => {
|
|
|
217
214
|
const fiber = runFork(effect.pipe(Effect.scoped, Effect.catchAllCause((cause) => Effect.gen(function* () {
|
|
218
215
|
yield* Effect.logError(cause);
|
|
219
216
|
const status = getStatusFromCause(cause);
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
});
|
|
217
|
+
const message = Cause.pretty(cause, { renderErrorCause: true });
|
|
218
|
+
return respondError({ status, message });
|
|
223
219
|
}))));
|
|
224
220
|
request.signal?.addEventListener("abort", () => {
|
|
225
221
|
fiber.unsafeInterruptAsFork(clientAbortFiberId);
|
|
@@ -229,15 +225,12 @@ export const toWebHandlerRuntime = (runtime) => {
|
|
|
229
225
|
resolve(exit.value);
|
|
230
226
|
}
|
|
231
227
|
else if (isClientAbort(exit.cause)) {
|
|
232
|
-
resolve(
|
|
233
|
-
status: 499,
|
|
234
|
-
}));
|
|
228
|
+
resolve(respondError({ status: 499, message: "client closed request" }));
|
|
235
229
|
}
|
|
236
230
|
else {
|
|
237
231
|
const status = getStatusFromCause(exit.cause);
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}));
|
|
232
|
+
const message = Cause.pretty(exit.cause, { renderErrorCause: true });
|
|
233
|
+
resolve(respondError({ status, message }));
|
|
241
234
|
}
|
|
242
235
|
});
|
|
243
236
|
});
|
package/dist/RouteMount.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import * as Types from "effect/Types";
|
|
|
2
2
|
import * as Http from "./Http.ts";
|
|
3
3
|
import * as PathPattern from "./PathPattern.ts";
|
|
4
4
|
import * as Route from "./Route.ts";
|
|
5
|
+
import * as RouteBody from "./RouteBody.ts";
|
|
5
6
|
type Module = typeof import("./RouteMount.ts");
|
|
6
7
|
export type Self = RouteMount.Builder | Module;
|
|
7
8
|
export declare const use: RouteMount.Describer<"*">;
|
|
@@ -16,7 +17,7 @@ export declare const add: RouteMount.Add;
|
|
|
16
17
|
export type MountedRoute = Route.Route.Route<{
|
|
17
18
|
method: RouteMount.Method;
|
|
18
19
|
path: PathPattern.PathPattern;
|
|
19
|
-
format?:
|
|
20
|
+
format?: RouteBody.Format;
|
|
20
21
|
}, {}, any, any, any>;
|
|
21
22
|
export declare namespace RouteMount {
|
|
22
23
|
export type Method = "*" | Http.Method;
|
package/dist/Start.d.ts
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
import * as FileSystem from "@effect/platform/FileSystem";
|
|
2
|
-
import * as HttpClient from "@effect/platform/HttpClient";
|
|
3
|
-
import * as HttpRouter from "@effect/platform/HttpRouter";
|
|
4
|
-
import * as HttpServer from "@effect/platform/HttpServer";
|
|
5
1
|
import * as Layer from "effect/Layer";
|
|
6
2
|
import * as BunHttpServer from "./bun/BunHttpServer.ts";
|
|
7
3
|
export declare function layer<Layers extends [
|
|
@@ -15,5 +11,5 @@ export declare function layer<Layers extends [
|
|
|
15
11
|
[k in keyof Layers]: Layer.Layer.Context<Layers[k]>;
|
|
16
12
|
}[number]>;
|
|
17
13
|
export declare function serve<ROut, E>(load: () => Promise<{
|
|
18
|
-
default: Layer.Layer<ROut, E,
|
|
14
|
+
default: Layer.Layer<ROut, E, BunHttpServer.BunHttpServer>;
|
|
19
15
|
}>): void;
|
package/dist/Start.js
CHANGED
|
@@ -1,23 +1,16 @@
|
|
|
1
|
-
import * as FetchHttpClient from "@effect/platform/FetchHttpClient";
|
|
2
|
-
import * as HttpRouter from "@effect/platform/HttpRouter";
|
|
3
|
-
import * as HttpServer from "@effect/platform/HttpServer";
|
|
4
1
|
import * as Effect from "effect/Effect";
|
|
5
2
|
import * as Function from "effect/Function";
|
|
6
3
|
import * as Layer from "effect/Layer";
|
|
7
4
|
import * as BunHttpServer from "./bun/BunHttpServer.js";
|
|
8
5
|
import * as BunRuntime from "./bun/BunRuntime.js";
|
|
9
|
-
import * as NodeFileSystem from "./node/FileSystem.js";
|
|
10
6
|
import * as StartApp from "./StartApp.js";
|
|
11
7
|
export function layer(...layers) {
|
|
12
8
|
return Layer.mergeAll(...layers);
|
|
13
9
|
}
|
|
14
10
|
export function serve(load) {
|
|
15
11
|
const appLayer = Function.pipe(Effect.tryPromise(load), Effect.map(v => v.default), Effect.orDie, Layer.unwrapEffect);
|
|
16
|
-
return Function.pipe(BunHttpServer.layerAuto(),
|
|
17
|
-
FetchHttpClient.layer,
|
|
18
|
-
HttpRouter.Default.Live,
|
|
12
|
+
return Function.pipe(BunHttpServer.layerAuto(), Layer.provide(appLayer), Layer.provide([
|
|
19
13
|
BunHttpServer.layer(),
|
|
20
|
-
NodeFileSystem.layer,
|
|
21
14
|
StartApp.layer(),
|
|
22
15
|
]), Layer.launch, BunRuntime.runMain);
|
|
23
16
|
}
|
package/dist/Unique.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export declare const ALPHABET_BASE32_CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
2
|
+
export declare const ALPHABET_BASE32_RFC4648 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
|
3
|
+
export declare const ALPHABET_BASE64_URL = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
4
|
+
export declare const ALPHABET_HEX = "0123456789abcdef";
|
|
5
|
+
/**
|
|
6
|
+
* Generate a random string for ids, session tokens, and API keys.
|
|
7
|
+
* It uses human-friendly crockford base32 encoding (5 bit of entropy per char)
|
|
8
|
+
*
|
|
9
|
+
* Minimal recommended length:
|
|
10
|
+
* - public ids: 16 chars (~80 bits)
|
|
11
|
+
* - API keys: 32 chars (~160 bits)
|
|
12
|
+
* - session tokens: 32-40 chars (~160-200 bits)
|
|
13
|
+
*/
|
|
14
|
+
export declare function token(length?: number): string;
|
|
15
|
+
export declare function bytes(length: number): Uint8Array;
|
|
16
|
+
export declare const UUID_NIL = "00000000-0000-0000-0000-000000000000";
|
|
17
|
+
export declare function uuid4(): string;
|
|
18
|
+
export declare function uuid7(time?: number): string;
|
|
19
|
+
/**
|
|
20
|
+
* Decode a 48-bit Unix timestamp (ms) from UUID7 or ULID.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* const bytes = Unique.uuid7Bytes()
|
|
24
|
+
* const timestamp = Unique.toTimestamp(bytes)
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* const bytes = Unique.ulidBytes()
|
|
28
|
+
* const timestamp = Unique.toTimestamp(bytes)
|
|
29
|
+
*/
|
|
30
|
+
export declare function toTimestamp(bytes: Uint8Array): number;
|
|
31
|
+
export declare function uuid7Bytes(time?: number): Uint8Array;
|
|
32
|
+
/**
|
|
33
|
+
* Convert UUID bytes to canonical (RFC9562) representation.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* Unique.formatUuid(new Uint8Array(16))
|
|
37
|
+
*/
|
|
38
|
+
export declare function formatUuid(bytes: Uint8Array): string;
|
|
39
|
+
export declare function ulid(time?: number): string;
|
|
40
|
+
export declare function ulidBytes(time?: number): Uint8Array;
|
|
41
|
+
/**
|
|
42
|
+
* Generate a nanoid-style random string.
|
|
43
|
+
*
|
|
44
|
+
* FUN_FACT: Original nanoid implementation uses base64url alphabet
|
|
45
|
+
* with non-standard custom order where charater form common words found
|
|
46
|
+
* in source code (like use, random, strict) to make gzip/brotli more efficient.
|
|
47
|
+
* It's qt lil opt from the times where web developers
|
|
48
|
+
* were competing to have the smallest possible bundle size.
|
|
49
|
+
*/
|
|
50
|
+
export declare function nanoid(size?: number, alphabet?: string): string;
|
package/dist/Unique.js
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
export const ALPHABET_BASE32_CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
2
|
+
export const ALPHABET_BASE32_RFC4648 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
|
3
|
+
export const ALPHABET_BASE64_URL = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
4
|
+
export const ALPHABET_HEX = "0123456789abcdef";
|
|
5
|
+
/**
|
|
6
|
+
* Generate a random string for ids, session tokens, and API keys.
|
|
7
|
+
* It uses human-friendly crockford base32 encoding (5 bit of entropy per char)
|
|
8
|
+
*
|
|
9
|
+
* Minimal recommended length:
|
|
10
|
+
* - public ids: 16 chars (~80 bits)
|
|
11
|
+
* - API keys: 32 chars (~160 bits)
|
|
12
|
+
* - session tokens: 32-40 chars (~160-200 bits)
|
|
13
|
+
*/
|
|
14
|
+
export function token(length = 32) {
|
|
15
|
+
if (length <= 0)
|
|
16
|
+
return "";
|
|
17
|
+
const buf = new Uint8Array(length);
|
|
18
|
+
crypto.getRandomValues(buf);
|
|
19
|
+
let result = "";
|
|
20
|
+
for (let i = 0; i < buf.length; i++) {
|
|
21
|
+
result += ALPHABET_BASE32_CROCKFORD[buf[i] & 31];
|
|
22
|
+
}
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
export function bytes(length) {
|
|
26
|
+
const buf = new Uint8Array(length);
|
|
27
|
+
crypto.getRandomValues(buf);
|
|
28
|
+
return buf;
|
|
29
|
+
}
|
|
30
|
+
export const UUID_NIL = "00000000-0000-0000-0000-000000000000";
|
|
31
|
+
export function uuid4() {
|
|
32
|
+
return formatUuid(uuid4bytes());
|
|
33
|
+
}
|
|
34
|
+
export function uuid7(time = Date.now()) {
|
|
35
|
+
return formatUuid(uuid7Bytes(time));
|
|
36
|
+
}
|
|
37
|
+
function uuid4bytes() {
|
|
38
|
+
const buf = bytes(16);
|
|
39
|
+
buf[6] = (buf[6] & 0x0f) | 0x40; // version 4
|
|
40
|
+
buf[8] = (buf[8] & 0x3f) | 0x80; // variant
|
|
41
|
+
return buf;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Decode a 48-bit Unix timestamp (ms) from UUID7 or ULID.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* const bytes = Unique.uuid7Bytes()
|
|
48
|
+
* const timestamp = Unique.toTimestamp(bytes)
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* const bytes = Unique.ulidBytes()
|
|
52
|
+
* const timestamp = Unique.toTimestamp(bytes)
|
|
53
|
+
*/
|
|
54
|
+
export function toTimestamp(bytes) {
|
|
55
|
+
if (bytes.length < 6)
|
|
56
|
+
return 0;
|
|
57
|
+
return (bytes[0] * 0x10000000000
|
|
58
|
+
+ bytes[1] * 0x100000000
|
|
59
|
+
+ bytes[2] * 0x1000000
|
|
60
|
+
+ bytes[3] * 0x10000
|
|
61
|
+
+ bytes[4] * 0x100
|
|
62
|
+
+ bytes[5]);
|
|
63
|
+
}
|
|
64
|
+
export function uuid7Bytes(time = Date.now()) {
|
|
65
|
+
const buf = new Uint8Array(16);
|
|
66
|
+
const timestamp = BigInt(toSafeTime(time));
|
|
67
|
+
// 48-bit timestamp (6 bytes)
|
|
68
|
+
buf[0] = Number((timestamp >> 40n) & 0xffn);
|
|
69
|
+
buf[1] = Number((timestamp >> 32n) & 0xffn);
|
|
70
|
+
buf[2] = Number((timestamp >> 24n) & 0xffn);
|
|
71
|
+
buf[3] = Number((timestamp >> 16n) & 0xffn);
|
|
72
|
+
buf[4] = Number((timestamp >> 8n) & 0xffn);
|
|
73
|
+
buf[5] = Number(timestamp & 0xffn);
|
|
74
|
+
// 12-bit random A (1.5 bytes)
|
|
75
|
+
crypto.getRandomValues(buf.subarray(6, 8));
|
|
76
|
+
buf[6] = (buf[6] & 0x0f) | 0x70; // version 7
|
|
77
|
+
// 2-bit variant + 62-bit random B (8 bytes)
|
|
78
|
+
crypto.getRandomValues(buf.subarray(8, 16));
|
|
79
|
+
buf[8] = (buf[8] & 0x3f) | 0x80; // variant
|
|
80
|
+
return buf;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Convert UUID bytes to canonical (RFC9562) representation.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* Unique.formatUuid(new Uint8Array(16))
|
|
87
|
+
*/
|
|
88
|
+
export function formatUuid(bytes) {
|
|
89
|
+
if (bytes.length === 0)
|
|
90
|
+
return "";
|
|
91
|
+
let result = "";
|
|
92
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
93
|
+
const byte = bytes[i];
|
|
94
|
+
result += ALPHABET_HEX[(byte >> 4) & 0x0f];
|
|
95
|
+
result += ALPHABET_HEX[byte & 0x0f];
|
|
96
|
+
if (i === 3 || i === 5 || i === 7 || i === 9)
|
|
97
|
+
result += "-";
|
|
98
|
+
}
|
|
99
|
+
return result;
|
|
100
|
+
}
|
|
101
|
+
export function ulid(time = Date.now()) {
|
|
102
|
+
const bytes = ulidBytes(time);
|
|
103
|
+
return formatUlid(bytes);
|
|
104
|
+
}
|
|
105
|
+
export function ulidBytes(time = Date.now()) {
|
|
106
|
+
const buf = new Uint8Array(16);
|
|
107
|
+
const timestamp = BigInt(toSafeTime(time));
|
|
108
|
+
buf[0] = Number((timestamp >> 40n) & 0xffn);
|
|
109
|
+
buf[1] = Number((timestamp >> 32n) & 0xffn);
|
|
110
|
+
buf[2] = Number((timestamp >> 24n) & 0xffn);
|
|
111
|
+
buf[3] = Number((timestamp >> 16n) & 0xffn);
|
|
112
|
+
buf[4] = Number((timestamp >> 8n) & 0xffn);
|
|
113
|
+
buf[5] = Number(timestamp & 0xffn);
|
|
114
|
+
crypto.getRandomValues(buf.subarray(6, 16));
|
|
115
|
+
return buf;
|
|
116
|
+
}
|
|
117
|
+
function formatUlid(bytes) {
|
|
118
|
+
if (bytes.length !== 16)
|
|
119
|
+
return "";
|
|
120
|
+
const timestamp = toTimestamp(bytes);
|
|
121
|
+
const timePart = encodeUlidTime(timestamp);
|
|
122
|
+
const randomPart = toBase32(bytes.subarray(6, 16));
|
|
123
|
+
return `${timePart}${randomPart}`;
|
|
124
|
+
}
|
|
125
|
+
function encodeUlidTime(time) {
|
|
126
|
+
let value = BigInt(time);
|
|
127
|
+
const result = new Array(10);
|
|
128
|
+
for (let i = 9; i >= 0; i--) {
|
|
129
|
+
result[i] = ALPHABET_BASE32_CROCKFORD[Number(value & 31n)];
|
|
130
|
+
value >>= 5n;
|
|
131
|
+
}
|
|
132
|
+
return result.join("");
|
|
133
|
+
}
|
|
134
|
+
function toSafeTime(time) {
|
|
135
|
+
if (!Number.isFinite(time))
|
|
136
|
+
return 0;
|
|
137
|
+
return Math.max(0, Math.trunc(time));
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Generate a nanoid-style random string.
|
|
141
|
+
*
|
|
142
|
+
* FUN_FACT: Original nanoid implementation uses base64url alphabet
|
|
143
|
+
* with non-standard custom order where charater form common words found
|
|
144
|
+
* in source code (like use, random, strict) to make gzip/brotli more efficient.
|
|
145
|
+
* It's qt lil opt from the times where web developers
|
|
146
|
+
* were competing to have the smallest possible bundle size.
|
|
147
|
+
*/
|
|
148
|
+
export function nanoid(size = 21, alphabet = ALPHABET_BASE64_URL) {
|
|
149
|
+
if (size <= 0 || alphabet.length === 0)
|
|
150
|
+
return "";
|
|
151
|
+
const length = alphabet.length;
|
|
152
|
+
const mask = (2 << Math.floor(Math.log2(length - 1))) - 1;
|
|
153
|
+
const step = Math.ceil((1.6 * mask * size) / length);
|
|
154
|
+
let id = "";
|
|
155
|
+
while (id.length < size) {
|
|
156
|
+
const bytes = new Uint8Array(step);
|
|
157
|
+
crypto.getRandomValues(bytes);
|
|
158
|
+
for (let i = 0; i < step && id.length < size; i++) {
|
|
159
|
+
const index = bytes[i] & mask;
|
|
160
|
+
if (index < length)
|
|
161
|
+
id += alphabet[index];
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return id;
|
|
165
|
+
}
|
|
166
|
+
function toBase32(bytes, alphabet = ALPHABET_BASE32_CROCKFORD) {
|
|
167
|
+
if (bytes.length === 0)
|
|
168
|
+
return "";
|
|
169
|
+
let result = "";
|
|
170
|
+
let buffer = 0;
|
|
171
|
+
let bits = 0;
|
|
172
|
+
for (const byte of bytes) {
|
|
173
|
+
buffer = (buffer << 8) | byte;
|
|
174
|
+
bits += 8;
|
|
175
|
+
while (bits >= 5) {
|
|
176
|
+
bits -= 5;
|
|
177
|
+
const index = (buffer >> bits) & 31;
|
|
178
|
+
result += alphabet[index];
|
|
179
|
+
buffer &= (1 << bits) - 1;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (bits > 0) {
|
|
183
|
+
const index = (buffer << (5 - bits)) & 31;
|
|
184
|
+
result += alphabet[index];
|
|
185
|
+
}
|
|
186
|
+
return result;
|
|
187
|
+
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
1
|
import * as HttpApp from "@effect/platform/HttpApp";
|
|
3
2
|
import * as HttpServer from "@effect/platform/HttpServer";
|
|
4
3
|
import * as HttpServerError from "@effect/platform/HttpServerError";
|
|
@@ -14,19 +13,19 @@ import * as FiberSet from "effect/FiberSet";
|
|
|
14
13
|
import * as Layer from "effect/Layer";
|
|
15
14
|
import * as Option from "effect/Option";
|
|
16
15
|
import * as PathPattern from "../PathPattern.js";
|
|
17
|
-
import * as
|
|
16
|
+
import * as PlataformRuntime from "../PlatformRuntime.js";
|
|
18
17
|
import * as Route from "../Route.js";
|
|
19
18
|
import * as RouteHttp from "../RouteHttp.js";
|
|
20
19
|
import * as RouteTree from "../RouteTree.js";
|
|
20
|
+
import * as Unique from "../Unique.js";
|
|
21
21
|
import EmptyHTML from "./_empty.html";
|
|
22
22
|
import { makeResponse, ServerRequestImpl, } from "./BunHttpServer_web.js";
|
|
23
23
|
import * as BunRoute from "./BunRoute.js";
|
|
24
24
|
export const BunHttpServer = Context.GenericTag("effect-start/BunServer");
|
|
25
25
|
export const make = (options) => Effect.gen(function* () {
|
|
26
26
|
const port = yield* Config.number("PORT").pipe(Effect.catchTag("ConfigError", () => {
|
|
27
|
-
if (
|
|
28
|
-
|
|
29
|
-
&& process.env.CLAUDECODE) {
|
|
27
|
+
if (PlataformRuntime.isAgentHarness()) {
|
|
28
|
+
// use random port
|
|
30
29
|
return Effect.succeed(0);
|
|
31
30
|
}
|
|
32
31
|
return Effect.succeed(3000);
|
|
@@ -41,7 +40,7 @@ export const make = (options) => Effect.gen(function* () {
|
|
|
41
40
|
// Bun HMR doesn't work on successive calls to `server.reload` if there are no routes
|
|
42
41
|
// on server start. We workaround that by passing a dummy HTMLBundle [2025-11-26]
|
|
43
42
|
// see: https://github.com/oven-sh/bun/issues/23564
|
|
44
|
-
currentRoutes[`/.BunEmptyHtml-${
|
|
43
|
+
currentRoutes[`/.BunEmptyHtml-${Unique.token(10)}`] = EmptyHTML;
|
|
45
44
|
const websocket = {
|
|
46
45
|
open(ws) {
|
|
47
46
|
Deferred.unsafeDone(ws.data.deferred, Exit.succeed(ws));
|
package/dist/bun/BunRoute.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export type BunDescriptors = {
|
|
|
15
15
|
bunPrefix: string;
|
|
16
16
|
bunLoad: () => Promise<Bun.HTMLBundle>;
|
|
17
17
|
};
|
|
18
|
-
export declare function descriptors(route: Route.Route.Route): BunDescriptors | undefined;
|
|
18
|
+
export declare function descriptors(route: Route.Route.Route<any, any, any, any, any>): BunDescriptors | undefined;
|
|
19
19
|
export declare function htmlBundle(load: () => Promise<Bun.HTMLBundle | {
|
|
20
20
|
default: Bun.HTMLBundle;
|
|
21
21
|
}>): <D extends Route.RouteDescriptor.Any, B extends {}, I extends Route.Route.Tuple>(self: Route.RouteSet.RouteSet<D, B, I>) => Route.RouteSet.RouteSet<D, B, [...I, Route.Route.Route<BunDescriptors & {
|
package/dist/bun/BunRoute.js
CHANGED
|
@@ -5,7 +5,7 @@ import * as Option from "effect/Option";
|
|
|
5
5
|
import * as Entity from "../Entity.js";
|
|
6
6
|
import * as Hyper from "../hyper/Hyper.js";
|
|
7
7
|
import * as HyperHtml from "../hyper/HyperHtml.js";
|
|
8
|
-
import * as
|
|
8
|
+
import * as Unique from "../Unique.js";
|
|
9
9
|
import * as Route from "../Route.js";
|
|
10
10
|
import * as RouterPattern from "../RouterPattern.js";
|
|
11
11
|
import * as BunHttpServer from "./BunHttpServer.js";
|
|
@@ -21,7 +21,7 @@ export function descriptors(route) {
|
|
|
21
21
|
return undefined;
|
|
22
22
|
}
|
|
23
23
|
export function htmlBundle(load) {
|
|
24
|
-
const bunPrefix = `/.BunRoute-${
|
|
24
|
+
const bunPrefix = `/.BunRoute-${Unique.token(10)}`;
|
|
25
25
|
const bunLoad = () => load().then(mod => "default" in mod ? mod.default : mod);
|
|
26
26
|
const descriptors = { bunPrefix, bunLoad, format: "html" };
|
|
27
27
|
return function (self) {
|
package/dist/index.d.ts
CHANGED
|
@@ -2,5 +2,6 @@ export * as Bundle from "./bundler/Bundle.ts";
|
|
|
2
2
|
export * as Development from "./Development.ts";
|
|
3
3
|
export * as Entity from "./Entity.ts";
|
|
4
4
|
export * as FileRouter from "./FileRouter.ts";
|
|
5
|
+
export * as Unique from "./Unique.ts";
|
|
5
6
|
export * as Route from "./Route.ts";
|
|
6
7
|
export * as Start from "./Start.ts";
|
package/dist/index.js
CHANGED
|
@@ -2,5 +2,6 @@ export * as Bundle from "./bundler/Bundle.js";
|
|
|
2
2
|
export * as Development from "./Development.js";
|
|
3
3
|
export * as Entity from "./Entity.js";
|
|
4
4
|
export * as FileRouter from "./FileRouter.js";
|
|
5
|
+
export * as Unique from "./Unique.js";
|
|
5
6
|
export * as Route from "./Route.js";
|
|
6
7
|
export * as Start from "./Start.js";
|