effect-start 0.18.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/Development.d.ts +8 -3
- package/dist/Development.js +14 -7
- package/dist/Effectify.d.ts +212 -0
- package/dist/Effectify.js +19 -0
- package/dist/FilePathPattern.d.ts +29 -0
- package/dist/FilePathPattern.js +86 -0
- package/dist/FileRouter.d.ts +39 -41
- package/dist/FileRouter.js +104 -158
- package/dist/FileRouterCodegen.d.ts +7 -8
- package/dist/FileRouterCodegen.js +97 -66
- package/dist/PlatformError.d.ts +46 -0
- package/dist/PlatformError.js +43 -0
- package/dist/PlatformRuntime.d.ts +27 -0
- package/dist/PlatformRuntime.js +51 -0
- package/dist/Route.d.ts +6 -2
- package/dist/Route.js +22 -0
- package/dist/RouteBody.d.ts +1 -1
- 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 +33 -6
- package/dist/Start.js +31 -13
- package/dist/Unique.d.ts +50 -0
- package/dist/Unique.js +187 -0
- package/dist/bun/BunHttpServer.js +5 -6
- package/dist/bun/BunPlatformHttpServer.d.ts +10 -0
- package/dist/bun/BunPlatformHttpServer.js +53 -0
- package/dist/bun/BunRoute.d.ts +4 -6
- package/dist/bun/BunRoute.js +10 -18
- package/dist/bun/BunRuntime.d.ts +2 -1
- package/dist/bun/BunRuntime.js +10 -5
- package/dist/bun/BunServer.d.ts +33 -0
- package/dist/bun/BunServer.js +133 -0
- package/dist/bun/BunServerRequest.d.ts +60 -0
- package/dist/bun/BunServerRequest.js +252 -0
- package/dist/bun/index.d.ts +1 -1
- package/dist/bun/index.js +1 -1
- package/dist/datastar/actions/fetch.d.ts +30 -0
- package/dist/datastar/actions/fetch.js +411 -0
- package/dist/datastar/actions/peek.d.ts +1 -0
- package/dist/datastar/actions/peek.js +14 -0
- package/dist/datastar/actions/setAll.d.ts +1 -0
- package/dist/datastar/actions/setAll.js +13 -0
- package/dist/datastar/actions/toggleAll.d.ts +1 -0
- package/dist/datastar/actions/toggleAll.js +13 -0
- package/dist/datastar/attributes/attr.d.ts +1 -0
- package/dist/datastar/attributes/attr.js +49 -0
- package/dist/datastar/attributes/bind.d.ts +1 -0
- package/dist/datastar/attributes/bind.js +183 -0
- package/dist/datastar/attributes/class.d.ts +1 -0
- package/dist/datastar/attributes/class.js +50 -0
- package/dist/datastar/attributes/computed.d.ts +1 -0
- package/dist/datastar/attributes/computed.js +27 -0
- package/dist/datastar/attributes/effect.d.ts +1 -0
- package/dist/datastar/attributes/effect.js +10 -0
- package/dist/datastar/attributes/indicator.d.ts +1 -0
- package/dist/datastar/attributes/indicator.js +32 -0
- package/dist/datastar/attributes/init.d.ts +1 -0
- package/dist/datastar/attributes/init.js +27 -0
- package/dist/datastar/attributes/jsonSignals.d.ts +1 -0
- package/dist/datastar/attributes/jsonSignals.js +31 -0
- package/dist/datastar/attributes/on.d.ts +1 -0
- package/dist/datastar/attributes/on.js +59 -0
- package/dist/datastar/attributes/onIntersect.d.ts +1 -0
- package/dist/datastar/attributes/onIntersect.js +54 -0
- package/dist/datastar/attributes/onInterval.d.ts +1 -0
- package/dist/datastar/attributes/onInterval.js +31 -0
- package/dist/datastar/attributes/onSignalPatch.d.ts +1 -0
- package/dist/datastar/attributes/onSignalPatch.js +44 -0
- package/dist/datastar/attributes/ref.d.ts +1 -0
- package/dist/datastar/attributes/ref.js +11 -0
- package/dist/datastar/attributes/show.d.ts +1 -0
- package/dist/datastar/attributes/show.js +32 -0
- package/dist/datastar/attributes/signals.d.ts +1 -0
- package/dist/datastar/attributes/signals.js +18 -0
- package/dist/datastar/attributes/style.d.ts +1 -0
- package/dist/datastar/attributes/style.js +56 -0
- package/dist/datastar/attributes/text.d.ts +1 -0
- package/dist/datastar/attributes/text.js +27 -0
- package/dist/datastar/engine.d.ts +156 -0
- package/dist/datastar/engine.js +971 -0
- package/dist/datastar/index.d.ts +24 -0
- package/dist/datastar/index.js +24 -0
- package/dist/datastar/load.d.ts +24 -0
- package/dist/datastar/load.js +24 -0
- package/dist/datastar/utils.d.ts +51 -0
- package/dist/datastar/utils.js +205 -0
- package/dist/datastar/watchers/patchElements.d.ts +1 -0
- package/dist/datastar/watchers/patchElements.js +420 -0
- package/dist/datastar/watchers/patchSignals.d.ts +1 -0
- package/dist/datastar/watchers/patchSignals.js +15 -0
- 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/NodeFileSystem.d.ts +7 -0
- package/dist/node/NodeFileSystem.js +420 -0
- package/dist/node/NodeUtils.d.ts +2 -0
- package/dist/node/NodeUtils.js +20 -0
- package/dist/node/PlatformError.d.ts +46 -0
- package/dist/node/PlatformError.js +43 -0
- package/dist/testing/TestLogger.js +1 -1
- package/dist/x/tailwind/plugin.js +1 -1
- package/package.json +18 -7
- package/src/Development.ts +36 -40
- package/src/Effectify.ts +269 -0
- package/src/FilePathPattern.ts +115 -0
- package/src/FileRouter.ts +178 -255
- package/src/FileRouterCodegen.ts +135 -92
- package/src/PlatformError.ts +117 -0
- package/src/PlatformRuntime.ts +108 -0
- package/src/Route.ts +31 -2
- package/src/RouteBody.ts +1 -1
- package/src/RouteHttp.ts +15 -29
- package/src/RouteMount.ts +1 -1
- package/src/Start.ts +61 -27
- package/src/Unique.ts +232 -0
- package/src/bun/BunPlatformHttpServer.ts +88 -0
- package/src/bun/BunRoute.ts +14 -24
- package/src/bun/BunRuntime.ts +21 -5
- package/src/bun/BunServer.ts +228 -0
- package/src/bun/index.ts +1 -1
- package/src/datastar/README.md +18 -0
- package/src/datastar/actions/fetch.ts +609 -0
- package/src/datastar/actions/peek.ts +17 -0
- package/src/datastar/actions/setAll.ts +20 -0
- package/src/datastar/actions/toggleAll.ts +20 -0
- package/src/datastar/attributes/attr.ts +50 -0
- package/src/datastar/attributes/bind.ts +220 -0
- package/src/datastar/attributes/class.ts +57 -0
- package/src/datastar/attributes/computed.ts +33 -0
- package/src/datastar/attributes/effect.ts +11 -0
- package/src/datastar/attributes/indicator.ts +39 -0
- package/src/datastar/attributes/init.ts +35 -0
- package/src/datastar/attributes/jsonSignals.ts +38 -0
- package/src/datastar/attributes/on.ts +71 -0
- package/src/datastar/attributes/onIntersect.ts +65 -0
- package/src/datastar/attributes/onInterval.ts +39 -0
- package/src/datastar/attributes/onSignalPatch.ts +63 -0
- package/src/datastar/attributes/ref.ts +12 -0
- package/src/datastar/attributes/show.ts +33 -0
- package/src/datastar/attributes/signals.ts +22 -0
- package/src/datastar/attributes/style.ts +63 -0
- package/src/datastar/attributes/text.ts +30 -0
- package/src/datastar/engine.ts +1341 -0
- package/src/datastar/index.ts +25 -0
- package/src/datastar/utils.ts +286 -0
- package/src/datastar/watchers/patchElements.ts +554 -0
- package/src/datastar/watchers/patchSignals.ts +15 -0
- package/src/index.ts +1 -0
- package/src/node/{FileSystem.ts → NodeFileSystem.ts} +59 -97
- package/src/node/{Utils.ts → NodeUtils.ts} +2 -0
- package/src/testing/TestLogger.ts +1 -1
- package/src/x/tailwind/plugin.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/FileRouterCodegen.todo.ts +0 -1133
- package/src/FileRouterPattern.test.ts +0 -147
- package/src/FileRouterPattern.ts +0 -59
- 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/RouterPattern.ts +0 -416
- package/src/StartApp.ts +0 -47
- 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/BunHttpServer.ts +0 -306
- 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/src/bun/{BunHttpServer_web.ts → BunServerRequest.ts} +0 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import * as Data from "effect/Data";
|
|
2
|
+
import * as Predicate from "effect/Predicate";
|
|
3
|
+
import * as Schema from "effect/Schema";
|
|
4
|
+
import { TypeId as TypeId_ } from "@effect/platform/Error";
|
|
5
|
+
export const TypeId = TypeId_;
|
|
6
|
+
export const isPlatformError = (u) => Predicate.hasProperty(u, TypeId);
|
|
7
|
+
export const TypeIdError = (typeId, tag) => {
|
|
8
|
+
class Base extends Data.Error {
|
|
9
|
+
_tag = tag;
|
|
10
|
+
}
|
|
11
|
+
;
|
|
12
|
+
Base.prototype[typeId] = typeId;
|
|
13
|
+
Base.prototype.name = tag;
|
|
14
|
+
return Base;
|
|
15
|
+
};
|
|
16
|
+
export const Module = Schema.Literal("Clipboard", "Command", "FileSystem", "KeyValueStore", "Path", "Stream", "Terminal");
|
|
17
|
+
export class BadArgument extends Schema.TaggedError("@effect/platform/Error/BadArgument")("BadArgument", {
|
|
18
|
+
module: Module,
|
|
19
|
+
method: Schema.String,
|
|
20
|
+
description: Schema.optional(Schema.String),
|
|
21
|
+
cause: Schema.optional(Schema.Defect),
|
|
22
|
+
}) {
|
|
23
|
+
[TypeId] = TypeId;
|
|
24
|
+
get message() {
|
|
25
|
+
return `${this.module}.${this.method}${this.description ? `: ${this.description}` : ""}`;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export const SystemErrorReason = Schema.Literal("AlreadyExists", "BadResource", "Busy", "InvalidData", "NotFound", "PermissionDenied", "TimedOut", "UnexpectedEof", "Unknown", "WouldBlock", "WriteZero");
|
|
29
|
+
export class SystemError extends Schema.TaggedError("@effect/platform/Error/SystemError")("SystemError", {
|
|
30
|
+
reason: SystemErrorReason,
|
|
31
|
+
module: Module,
|
|
32
|
+
method: Schema.String,
|
|
33
|
+
description: Schema.optional(Schema.String),
|
|
34
|
+
syscall: Schema.optional(Schema.String),
|
|
35
|
+
pathOrDescriptor: Schema.optional(Schema.Union(Schema.String, Schema.Number)),
|
|
36
|
+
cause: Schema.optional(Schema.Defect),
|
|
37
|
+
}) {
|
|
38
|
+
[TypeId] = TypeId;
|
|
39
|
+
get message() {
|
|
40
|
+
return `${this.reason}: ${this.module}.${this.method}${this.pathOrDescriptor !== undefined ? ` (${this.pathOrDescriptor})` : ""}${this.description ? `: ${this.description}` : ""}`;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export const PlatformError = Schema.Union(BadArgument, SystemError);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import * as Exit from "effect/Exit";
|
|
3
|
+
import type * as Fiber from "effect/Fiber";
|
|
4
|
+
export interface Teardown {
|
|
5
|
+
<E, A>(exit: Exit.Exit<E, A>, onExit: (code: number) => void): void;
|
|
6
|
+
}
|
|
7
|
+
export declare const defaultTeardown: Teardown;
|
|
8
|
+
export interface RunMain {
|
|
9
|
+
(options?: {
|
|
10
|
+
readonly disableErrorReporting?: boolean | undefined;
|
|
11
|
+
readonly disablePrettyLogger?: boolean | undefined;
|
|
12
|
+
readonly teardown?: Teardown | undefined;
|
|
13
|
+
}): <E, A>(effect: Effect.Effect<A, E>) => void;
|
|
14
|
+
<E, A>(effect: Effect.Effect<A, E>, options?: {
|
|
15
|
+
readonly disableErrorReporting?: boolean | undefined;
|
|
16
|
+
readonly disablePrettyLogger?: boolean | undefined;
|
|
17
|
+
readonly teardown?: Teardown | undefined;
|
|
18
|
+
}): void;
|
|
19
|
+
}
|
|
20
|
+
export declare const makeRunMain: (f: <E, A>(options: {
|
|
21
|
+
readonly fiber: Fiber.RuntimeFiber<A, E>;
|
|
22
|
+
readonly teardown: Teardown;
|
|
23
|
+
}) => void) => RunMain;
|
|
24
|
+
/**
|
|
25
|
+
* Are we running within an agent harness, like Claude Code?
|
|
26
|
+
*/
|
|
27
|
+
export declare function isAgentHarness(): string | false | undefined;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import * as Cause from "effect/Cause";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as Exit from "effect/Exit";
|
|
4
|
+
import * as FiberRef from "effect/FiberRef";
|
|
5
|
+
import * as FiberRefs from "effect/FiberRefs";
|
|
6
|
+
import * as Function from "effect/Function";
|
|
7
|
+
import * as HashSet from "effect/HashSet";
|
|
8
|
+
import * as Logger from "effect/Logger";
|
|
9
|
+
export const defaultTeardown = (exit, onExit) => {
|
|
10
|
+
onExit(Exit.isFailure(exit) && !Cause.isInterruptedOnly(exit.cause) ? 1 : 0);
|
|
11
|
+
};
|
|
12
|
+
const addPrettyLogger = (refs, fiberId) => {
|
|
13
|
+
const loggers = FiberRefs.getOrDefault(refs, FiberRef.currentLoggers);
|
|
14
|
+
if (!HashSet.has(loggers, Logger.defaultLogger)) {
|
|
15
|
+
return refs;
|
|
16
|
+
}
|
|
17
|
+
return FiberRefs.updateAs(refs, {
|
|
18
|
+
fiberId,
|
|
19
|
+
fiberRef: FiberRef.currentLoggers,
|
|
20
|
+
value: loggers.pipe(HashSet.remove(Logger.defaultLogger), HashSet.add(Logger.prettyLoggerDefault)),
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
export const makeRunMain = (f) => Function.dual((args) => Effect.isEffect(args[0]), (effect, options) => {
|
|
24
|
+
const fiber = options?.disableErrorReporting === true
|
|
25
|
+
? Effect.runFork(effect, {
|
|
26
|
+
updateRefs: options?.disablePrettyLogger === true
|
|
27
|
+
? undefined
|
|
28
|
+
: addPrettyLogger,
|
|
29
|
+
})
|
|
30
|
+
: Effect.runFork(Effect.tapErrorCause(effect, (cause) => {
|
|
31
|
+
if (Cause.isInterruptedOnly(cause)) {
|
|
32
|
+
return Effect.void;
|
|
33
|
+
}
|
|
34
|
+
return Effect.logError(cause);
|
|
35
|
+
}), {
|
|
36
|
+
updateRefs: options?.disablePrettyLogger === true
|
|
37
|
+
? undefined
|
|
38
|
+
: addPrettyLogger,
|
|
39
|
+
});
|
|
40
|
+
const teardown = options?.teardown ?? defaultTeardown;
|
|
41
|
+
return f({ fiber, teardown });
|
|
42
|
+
});
|
|
43
|
+
/**
|
|
44
|
+
* Are we running within an agent harness, like Claude Code?
|
|
45
|
+
*/
|
|
46
|
+
export function isAgentHarness() {
|
|
47
|
+
return typeof process !== "undefined"
|
|
48
|
+
&& !process.stdout.isTTY
|
|
49
|
+
&& (process.env.CLAUDECODE
|
|
50
|
+
|| process.env.CURSOR_AGENT);
|
|
51
|
+
}
|
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/RouteBody.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export type Format = "text" | "html" | "json" | "bytes" | "*";
|
|
|
8
8
|
type UnwrapStream<T> = T extends Stream.Stream<infer V, any, any> ? V : T;
|
|
9
9
|
type YieldError<T> = T extends Utils.YieldWrap<Effect.Effect<any, infer E, any>> ? E : never;
|
|
10
10
|
type YieldContext<T> = T extends Utils.YieldWrap<Effect.Effect<any, any, infer R>> ? R : never;
|
|
11
|
-
export type GeneratorHandler<B, A, Y> = (context: Values.Simplify<B>, next: (context?: Partial<B> & Record<string, unknown>) => Entity.Entity<UnwrapStream<A>>) => Generator<Y, A | Entity.Entity<A>,
|
|
11
|
+
export type GeneratorHandler<B, A, Y> = (context: Values.Simplify<B>, next: (context?: Partial<B> & Record<string, unknown>) => Entity.Entity<UnwrapStream<A>>) => Generator<Y, A | Entity.Entity<A>, never>;
|
|
12
12
|
export type HandlerInput<B, A, E, R> = A | Entity.Entity<A> | Effect.Effect<A | Entity.Entity<A>, E, R> | ((context: Values.Simplify<B>, next: (context?: Partial<B> & Record<string, unknown>) => Entity.Entity<UnwrapStream<A>>) => Effect.Effect<A | Entity.Entity<A>, E, R> | Generator<Utils.YieldWrap<Effect.Effect<unknown, E, R>>, A | Entity.Entity<A>, unknown>);
|
|
13
13
|
export declare function handle<B, A, Y extends Utils.YieldWrap<Effect.Effect<any, any, any>>>(handler: GeneratorHandler<B, A, Y>): Route.Route.Handler<B, A, YieldError<Y>, YieldContext<Y>>;
|
|
14
14
|
export declare function handle<B, A, E, R>(handler: HandlerInput<B, A, E, R>): Route.Route.Handler<B, A, E, R>;
|
package/dist/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,9 +1,6 @@
|
|
|
1
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
2
|
import * as Layer from "effect/Layer";
|
|
6
|
-
import * as
|
|
3
|
+
import * as BunServer from "./bun/BunServer.ts";
|
|
7
4
|
export declare function layer<Layers extends [
|
|
8
5
|
Layer.Layer<never, any, any>,
|
|
9
6
|
...Array<Layer.Layer<never, any, any>>
|
|
@@ -14,6 +11,36 @@ export declare function layer<Layers extends [
|
|
|
14
11
|
}[number], {
|
|
15
12
|
[k in keyof Layers]: Layer.Layer.Context<Layers[k]>;
|
|
16
13
|
}[number]>;
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Bundles layers together, wiring their dependencies automatically.
|
|
16
|
+
*
|
|
17
|
+
* Equivalent to chaining `Layer.provide` calls, but more concise.
|
|
18
|
+
*
|
|
19
|
+
* **Ordering: dependents first, dependencies last.**
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* // UserRepo needs Database, Database needs Logger
|
|
24
|
+
* const AppLayer = Start.pack(
|
|
25
|
+
* UserRepoLive, // needs Database, Logger
|
|
26
|
+
* DatabaseLive, // needs Logger
|
|
27
|
+
* LoggerLive, // no deps
|
|
28
|
+
* )
|
|
29
|
+
* // Result: Layer<UserRepo | Database | Logger, never, never>
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @since 1.0.0
|
|
33
|
+
* @category constructors
|
|
34
|
+
*/
|
|
35
|
+
export declare function pack<const Layers extends readonly [Layer.Layer.Any, ...Array<Layer.Layer.Any>]>(...layers: Layers): Layer.Layer<{
|
|
36
|
+
[K in keyof Layers]: Layer.Layer.Success<Layers[K]>;
|
|
37
|
+
}[number], {
|
|
38
|
+
[K in keyof Layers]: Layer.Layer.Error<Layers[K]>;
|
|
39
|
+
}[number], Exclude<{
|
|
40
|
+
[K in keyof Layers]: Layer.Layer.Context<Layers[K]>;
|
|
41
|
+
}[number], {
|
|
42
|
+
[K in keyof Layers]: Layer.Layer.Success<Layers[K]>;
|
|
43
|
+
}[number]>>;
|
|
44
|
+
export declare function serve<ROut, E, RIn extends BunServer.BunServer | FileSystem.FileSystem>(load: () => Promise<{
|
|
45
|
+
default: Layer.Layer<ROut, E, RIn>;
|
|
19
46
|
}>): void;
|
package/dist/Start.js
CHANGED
|
@@ -1,23 +1,41 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
import * as HttpRouter from "@effect/platform/HttpRouter";
|
|
3
|
-
import * as HttpServer from "@effect/platform/HttpServer";
|
|
1
|
+
import * as Context from "effect/Context";
|
|
4
2
|
import * as Effect from "effect/Effect";
|
|
5
3
|
import * as Function from "effect/Function";
|
|
6
4
|
import * as Layer from "effect/Layer";
|
|
7
|
-
import * as BunHttpServer from "./bun/BunHttpServer.js";
|
|
8
5
|
import * as BunRuntime from "./bun/BunRuntime.js";
|
|
9
|
-
import * as
|
|
10
|
-
import * as
|
|
6
|
+
import * as BunServer from "./bun/BunServer.js";
|
|
7
|
+
import * as NodeFileSystem from "./node/NodeFileSystem.js";
|
|
11
8
|
export function layer(...layers) {
|
|
12
9
|
return Layer.mergeAll(...layers);
|
|
13
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* Bundles layers together, wiring their dependencies automatically.
|
|
13
|
+
*
|
|
14
|
+
* Equivalent to chaining `Layer.provide` calls, but more concise.
|
|
15
|
+
*
|
|
16
|
+
* **Ordering: dependents first, dependencies last.**
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* // UserRepo needs Database, Database needs Logger
|
|
21
|
+
* const AppLayer = Start.pack(
|
|
22
|
+
* UserRepoLive, // needs Database, Logger
|
|
23
|
+
* DatabaseLive, // needs Logger
|
|
24
|
+
* LoggerLive, // no deps
|
|
25
|
+
* )
|
|
26
|
+
* // Result: Layer<UserRepo | Database | Logger, never, never>
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* @since 1.0.0
|
|
30
|
+
* @category constructors
|
|
31
|
+
*/
|
|
32
|
+
export function pack(...layers) {
|
|
33
|
+
const layerArray = layers;
|
|
34
|
+
const result = layerArray.reduce((acc, layer) => Layer.provideMerge(acc, layer), Layer.succeedContext(Context.empty()));
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
14
37
|
export function serve(load) {
|
|
15
38
|
const appLayer = Function.pipe(Effect.tryPromise(load), Effect.map(v => v.default), Effect.orDie, Layer.unwrapEffect);
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
HttpRouter.Default.Live,
|
|
19
|
-
BunHttpServer.layer(),
|
|
20
|
-
NodeFileSystem.layer,
|
|
21
|
-
StartApp.layer(),
|
|
22
|
-
]), Layer.launch, BunRuntime.runMain);
|
|
39
|
+
const composed = Function.pipe(BunServer.layer(), BunServer.withLogAddress, Layer.provide(appLayer), Layer.provide(NodeFileSystem.layer), Layer.provide(BunServer.layer()));
|
|
40
|
+
return Function.pipe(composed, Layer.launch, BunRuntime.runMain);
|
|
23
41
|
}
|
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
|
+
}
|