From 6f172a2835b6be798c701431e48bb7c86be1c4bf Mon Sep 17 00:00:00 2001 From: Giulio Canti Date: Mon, 18 May 2026 17:53:59 +0200 Subject: [PATCH 01/10] Move previous API names to migration maps (#2219) --- MIGRATION.md | 4 +- .../{v3-to-v4-import-map.md => v3-to-v4.md} | 72 +++++++++- packages/effect/src/Effect.ts | 73 ---------- packages/effect/src/Latch.ts | 12 -- packages/effect/src/Layer.ts | 18 --- packages/effect/src/Queue.ts | 12 -- packages/effect/src/Result.ts | 18 --- packages/effect/src/Scope.ts | 6 - packages/effect/src/Semaphore.ts | 12 -- packages/effect/src/Stream.ts | 135 ------------------ 10 files changed, 69 insertions(+), 293 deletions(-) rename migration/{v3-to-v4-import-map.md => v3-to-v4.md} (91%) diff --git a/MIGRATION.md b/MIGRATION.md index ff59832f4a..902888a685 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -60,9 +60,9 @@ minimal Effect program bundles to ~6.3 KB (minified + gzipped). With Schema, ## Migration Guides -### Imports +### Import and API Rename Maps -- [v3 to v4 Import Map](./migration/v3-to-v4-import-map.md) +- [v3 to v4 Import and API Rename Maps](./migration/v3-to-v4.md) ### Core diff --git a/migration/v3-to-v4-import-map.md b/migration/v3-to-v4.md similarity index 91% rename from migration/v3-to-v4-import-map.md rename to migration/v3-to-v4.md index d5db37d334..5e3f3c7718 100644 --- a/migration/v3-to-v4-import-map.md +++ b/migration/v3-to-v4.md @@ -1,13 +1,14 @@ -# v3 to v4 Import Map +# v3 to v4 Import and API Rename Maps Mapped modules: 290 No counterpart: 43 +API renames: 53 -This file is intended for migration agents. The primary map is expressed in -terms of user-facing import specifiers, not repository file paths. +This file is intended for migration agents. It contains user-facing import +specifier mappings and API rename mappings. -Use the import map when rewriting user code. The map intentionally avoids -repository file paths so it can be used directly against import declarations. +Use the import map when rewriting import declarations. Use the API renames when +rewriting renamed symbols. ## Import Map @@ -357,3 +358,64 @@ effect/unstable/reactivity/AtomRpc (barrel: effect/unstable/reactivity) effect/unstable/reactivity/Hydration (barrel: effect/unstable/reactivity) effect/unstable/rpc/Utils (barrel: effect/unstable/rpc) ``` + +## API Renames + +Each line is `v3 API -> v4 API`. Use these mappings when rewriting renamed +symbols from v3 source code to v4. + +```text +Effect.async -> Effect.callback +Effect.zipRight -> Effect.andThen +Effect.zipLeft -> Effect.tap +Effect.either -> Effect.result +Effect.catchAll -> Effect.catch +Effect.catchAllCause -> Effect.catchCause +Effect.catchAllDefect -> Effect.catchDefect +Effect.catchSome -> Effect.catchIf +Effect.catchIf -> Effect.catchIf +Effect.optionFromOptional -> Effect.catchNoSuchElement +Effect.catchSomeCause -> Effect.catchCauseIf +Effect.tapErrorCause -> Effect.tapCause +Effect.ignoreLogged -> Effect.ignore +Effect.makeLatchUnsafe -> Latch.makeUnsafe +Effect.makeLatch -> Latch.make +Layer.scoped -> Layer.effect +Layer.scopedDiscard -> Layer.effectDiscard +Layer.tapErrorCause -> Layer.tapCause +Mailbox -> Queue.Queue +Mailbox.make -> Queue.make +Either -> Result.Result +Either.right -> Result.succeed +Either.left -> Result.fail +Scope.extend -> Scope.provide +Effect.makeSemaphoreUnsafe -> Semaphore.makeUnsafe +Effect.makeSemaphore -> Semaphore.make +Stream.Context -> Stream.Services +StreamHaltStrategy.HaltStrategy -> Stream.HaltStrategy +Stream.repeatEffect -> Stream.fromEffectRepeat +Stream.repeatEffectWithSchedule -> Stream.fromEffectSchedule +Stream.async -> Stream.callback +Stream.asyncEffect -> Stream.callback +Stream.asyncPush -> Stream.callback +Stream.asyncScoped -> Stream.callback +Stream.repeatEffectChunk -> Stream.fromIterableEffectRepeat +Stream.fromChunk -> Stream.fromArray +Stream.fromChunks -> Stream.fromArrays +Stream.mapChunks -> Stream.mapArray +Stream.mapChunksEffect -> Stream.mapArrayEffect +Stream.either -> Stream.result +Stream.flattenChunks -> Stream.flattenArray +Stream.flattenIterables -> Stream.flattenIterable +Stream.mergeEither -> Stream.mergeResult +Stream.zipWithChunks -> Stream.zipWithArray +Stream.bufferChunks -> Stream.bufferArray +Stream.catchAllCause -> Stream.catchCause +Stream.tapErrorCause -> Stream.tapCause +Stream.catchAll -> Stream.catch +Stream.catchSome -> Stream.catchIf +Stream.catchSomeCause -> Stream.catchCauseIf +Stream.combineChunks -> Stream.combineArray +provideSomeLayer -> Stream.provide +provideSomeContext -> Stream.provide +``` diff --git a/packages/effect/src/Effect.ts b/packages/effect/src/Effect.ts index 4351d869bd..402a90ae0e 100644 --- a/packages/effect/src/Effect.ts +++ b/packages/effect/src/Effect.ts @@ -1168,12 +1168,6 @@ export { * Use `Effect.callback` when integrating APIs that complete through callbacks * instead of returning a `Promise`. * - * **Previously Known As** - * - * This API replaces the following from Effect 3.x: - * - * - `Effect.async` - * * **Example** (Usage) * * ```ts @@ -1899,12 +1893,6 @@ export const flatten: (self: Effect, E2, R2>) = * Failures or requirements from either effect are preserved in the returned * effect. * - * **Previously Known As** - * - * This API replaces the following from Effect 3.x: - * - * - `Effect.zipRight` - * * **Example** (Applying a Discount Based on Fetched Amount) * * ```ts @@ -1982,12 +1970,6 @@ export const andThen: { * next part of the chain. Note that if the side effect fails, the entire chain * will fail too. * - * **Previously Known As** - * - * This API replaces the following from Effect 3.x: - * - * - `Effect.zipLeft` - * * **Example** (Logging a step in a pipeline) * * ```ts @@ -2064,12 +2046,6 @@ export const tap: { * The resulting effect cannot fail directly because all recoverable failures * are represented inside the `Result` type. * - * **Previously Known As** - * - * This API replaces the following from Effect 3.x: - * - * - `Effect.either` - * * **Example** (Usage) * * ```ts @@ -2494,12 +2470,6 @@ export { * * @see {@link catchCause} for a version that can recover from both recoverable and unrecoverable errors. * - * **Previously Known As** - * - * This API replaces the following from Effect 3.x: - * - * - `Effect.catchAll` - * * @category error handling * @since 4.0.0 */ @@ -2973,12 +2943,6 @@ export const unwrapReason: { * they often indicate serious issues. However, in some cases, such as * dynamically loaded plugins, controlled recovery might be needed. * - * **Previously Known As** - * - * This API replaces the following from Effect 3.x: - * - * - `Effect.catchAllCause` - * * **Example** (Usage) * * ```ts @@ -3030,12 +2994,6 @@ export const catchCause: { * they often indicate serious issues. In some cases, such as dynamically loaded * plugins, controlled recovery may be needed. * - * **Previously Known As** - * - * This API replaces the following from Effect 3.x: - * - * - `Effect.catchAllDefect` - * * **Example** (Usage) * * ```ts @@ -3077,13 +3035,6 @@ export const catchDefect: { * matching. Non-matching errors re-fail with the original cause. Defects and * interrupts are not caught. * - * **Previously Known As** - * - * This API replaces the following: - * - * - `Effect.catchSome` (Effect 3.x) - * - `Effect.catchIf` - * * **Example** (Usage) * * ```ts @@ -3176,12 +3127,6 @@ export const catchFilter: { * Effect.runPromise(none).then(console.log) // { _id: 'Option', _tag: 'None' } * ``` * - * **Previously Known As** - * - * This API replaces the following from Effect 3.x: - * - * - `Effect.optionFromOptional` - * * @category error handling * @since 4.0.0 */ @@ -3196,12 +3141,6 @@ export const catchNoSuchElement: ( * that match a specific predicate. This is useful when you want to handle * only certain types of errors while letting others propagate. * - * **Previously Known As** - * - * This API replaces the following from Effect 3.x: - * - * - `Effect.catchSomeCause` - * * **Example** (Usage) * * ```ts @@ -3488,12 +3427,6 @@ export const tapErrorTag: { * the operation succeeds, the original cause is preserved. If the operation * fails, its error is also represented in the returned effect. * - * **Previously Known As** - * - * This API replaces the following from Effect 3.x: - * - * - `Effect.tapErrorCause` - * * **Example** (Usage) * * ```ts @@ -3935,12 +3868,6 @@ export const sandbox: ( * const programWarn = task.pipe(Effect.ignore({ log: "Warn", message: "Ignoring task failure" })) * ``` * - * **Previously Known As** - * - * This API replaces the following from Effect 3.x: - * - * - `Effect.ignoreLogged` - * * @category error handling * @since 2.0.0 */ diff --git a/packages/effect/src/Latch.ts b/packages/effect/src/Latch.ts index f6147642ac..6ec09602b1 100644 --- a/packages/effect/src/Latch.ts +++ b/packages/effect/src/Latch.ts @@ -87,12 +87,6 @@ export interface Latch { * The latch starts closed by default; pass `true` to create it open. Use this * only when synchronous allocation is required, otherwise prefer `make`. * - * **Previously Known As** - * - * This API replaces the following from Effect 3.x: - * - * - `Effect.makeLatchUnsafe` - * * **Example** (Creating a latch unsafely) * * ```ts @@ -125,12 +119,6 @@ export const makeUnsafe: (open?: boolean | undefined) => Latch = internal.makeLa * * The latch starts closed by default; pass `true` to create it open. * - * **Previously Known As** - * - * This API replaces the following from Effect 3.x: - * - * - `Effect.makeLatch` - * * **Example** (Creating a latch) * * ```ts diff --git a/packages/effect/src/Layer.ts b/packages/effect/src/Layer.ts index e12e15b9ac..76b2ca6965 100644 --- a/packages/effect/src/Layer.ts +++ b/packages/effect/src/Layer.ts @@ -841,12 +841,6 @@ export const syncContext = (evaluate: LazyArg>): Layer * The Effect is executed in the scope of the layer, allowing for proper * resource management. * - * **Previously Known As** - * - * This API replaces the following from Effect 3.x: - * - * - `Layer.scoped` - * * **Example** (Creating a layer from an effect) * * ```ts @@ -924,12 +918,6 @@ export const effectContext = ( * This is useful when you want to run an Effect for its side effects during * layer construction, but don't need to provide any services. * - * **Previously Known As** - * - * This API replaces the following from Effect 3.x: - * - * - `Layer.scopedDiscard` - * * **Example** (Running an effect during layer construction) * * ```ts @@ -1509,12 +1497,6 @@ export const tapError: { * fails again with the original cause; if the callback fails, that failure is * added to the layer's error type. * - * **Previously Known As** - * - * This API replaces the following from Effect 3.x: - * - * - `Layer.tapErrorCause` - * * @category sequencing * @since 4.0.0 */ diff --git a/packages/effect/src/Queue.ts b/packages/effect/src/Queue.ts index 46cad2ec52..54c00f7c7d 100644 --- a/packages/effect/src/Queue.ts +++ b/packages/effect/src/Queue.ts @@ -245,12 +245,6 @@ export declare namespace Dequeue { * * It also supports signaling that it is done or failed. * - * **Previously Known As** - * - * This API replaces the following from Effect 3.x: - * - * - `Mailbox` - * * **Example** (Offering and taking queue values) * * ```ts @@ -381,12 +375,6 @@ const QueueProto = { * `"sliding"` to control what happens when the queue is full. The returned * queue can be offered to, taken from, failed, ended, interrupted, or shut down. * - * **Previously Known As** - * - * This API replaces the following from Effect 3.x: - * - * - `Mailbox.make` - * * **Example** (Creating queues) * * ```ts diff --git a/packages/effect/src/Result.ts b/packages/effect/src/Result.ts index 910074079a..636e5e3edd 100644 --- a/packages/effect/src/Result.ts +++ b/packages/effect/src/Result.ts @@ -95,12 +95,6 @@ const TypeId = "~effect/data/Result" * * `E` defaults to `never`, so `Result` means a result that cannot fail. * - * **Previously Known As** - * - * This API replaces the following from Effect 3.x: - * - * - `Either` - * * **Example** (Creating and matching a Result) * * ```ts @@ -303,12 +297,6 @@ export declare namespace Result { * - The error type `E` defaults to `never` * - Does not mutate input; allocates a new `Success` wrapper * - * **Previously Known As** - * - * This API replaces the following from Effect 3.x: - * - * - `Either.right` - * * **Example** (Wrapping a value) * * ```ts @@ -335,12 +323,6 @@ export const succeed: (right: A) => Result = result.succeed * - The success type `A` defaults to `never` * - Does not mutate input; allocates a new `Failure` wrapper * - * **Previously Known As** - * - * This API replaces the following from Effect 3.x: - * - * - `Either.left` - * * **Example** (Creating a failure) * * ```ts diff --git a/packages/effect/src/Scope.ts b/packages/effect/src/Scope.ts index 1dbe5fc029..6cf32bf940 100644 --- a/packages/effect/src/Scope.ts +++ b/packages/effect/src/Scope.ts @@ -276,12 +276,6 @@ export const makeUnsafe: (finalizerStrategy?: "sequential" | "parallel") => Clos * Provides a `Scope` to an `Effect`, removing the `Scope` requirement from its context. * This allows you to run effects that require a scope by explicitly providing one. * - * **Previously Known As** - * - * This API replaces the following from Effect 3.x: - * - * - `Scope.extend` - * * **Example** (Providing a scope) * * ```ts diff --git a/packages/effect/src/Semaphore.ts b/packages/effect/src/Semaphore.ts index fb4ba5b915..f02926421c 100644 --- a/packages/effect/src/Semaphore.ts +++ b/packages/effect/src/Semaphore.ts @@ -126,12 +126,6 @@ export interface Semaphore { * Use this low-level constructor when an immediate semaphore value is required; * otherwise prefer the effectful `make` constructor. * - * **Previously Known As** - * - * This API replaces the following from Effect 3.x: - * - * - `Effect.makeSemaphoreUnsafe` - * * **Example** (Creating an unsafe semaphore) * * ```ts @@ -273,12 +267,6 @@ class SemaphoreImpl implements Semaphore { * Use the returned semaphore to limit concurrency with `withPermit` or * `withPermits`, or to manually `take` and `release` permits. * - * **Previously Known As** - * - * This API replaces the following from Effect 3.x: - * - * - `Effect.makeSemaphore` - * * **Example** (Creating a semaphore) * * ```ts diff --git a/packages/effect/src/Stream.ts b/packages/effect/src/Stream.ts index b9efa32a43..026ca514cb 100644 --- a/packages/effect/src/Stream.ts +++ b/packages/effect/src/Stream.ts @@ -256,10 +256,6 @@ export type Error> = [T] extends [Stream(effect: Effect.Effect): Stream /** * Creates a stream from an effect producing a value of type `A` which repeats forever. * - * **Previously Known As** - * - * This API replaces the following from Effect 3.x: - * - * - `Stream.repeatEffect` - * * **Example** (Repeating an effect forever) * * ```ts @@ -525,12 +509,6 @@ export const fromEffectRepeat = (effect: Effect.Effect): Strea * Creates a stream from an effect producing a value of type `A`, which is * repeated using the specified schedule. * - * **Previously Known As** - * - * This API replaces the following from Effect 3.x: - * - * - `Stream.repeatEffectWithSchedule` - * * **Example** (Repeating an effect with a schedule) * * ```ts @@ -773,15 +751,6 @@ export const toChannel = ( * You can customize the buffer size and strategy by passing an object as the * second argument with the `bufferSize` and `strategy` fields. * - * **Previously Known As** - * - * This API replaces the following from Effect 3.x: - * - * - `Stream.async` - * - `Stream.asyncEffect` - * - `Stream.asyncPush` - * - `Stream.asyncScoped` - * * **Example** (Creating a stream from a callback that can emit values into a queue) * * ```ts @@ -1180,12 +1149,6 @@ export const fromIterableEffect = (iterable: Effect.Effect, /** * Creates a stream by repeatedly running an effect that yields an iterable of values. * - * **Previously Known As** - * - * This API replaces the following from Effect 3.x: - * - * - `Stream.repeatEffectChunk` - * * **Example** (Repeating an iterable effect) * * ```ts @@ -1213,12 +1176,6 @@ export const fromIterableEffectRepeat = ( /** * Creates a stream from an array of values. * - * **Previously Known As** - * - * This API replaces the following from Effect 3.x: - * - * - `Stream.fromChunk` - * * **Example** (Creating a stream from an array of values) * * ```ts @@ -1268,12 +1225,6 @@ export const fromArrayEffect = ( /** * Creates a stream from an arbitrary number of arrays. * - * **Previously Known As** - * - * This API replaces the following from Effect 3.x: - * - * - `Stream.fromChunks` - * * **Example** (Creating a stream from an arbitrary number of arrays) * * ```ts @@ -1959,12 +1910,6 @@ export const mapBoth: { /** * Transforms each emitted chunk using the provided function, which receives the chunk and its index. * - * **Previously Known As** - * - * This API replaces the following from Effect 3.x: - * - * - `Stream.mapChunks` - * * **Example** (Mapping stream chunks) * * ```ts @@ -2117,12 +2062,6 @@ export const flattenEffect: < /** * Effectfully maps over non-empty array chunks emitted by the stream. * - * **Previously Known As** - * - * This API replaces the following from Effect 3.x: - * - * - `Stream.mapChunksEffect` - * * **Example** (Effectfully mapping stream chunks) * * ```ts @@ -2164,12 +2103,6 @@ export const mapArrayEffect: { * * The stream ends after the first failure, emitting a `Result.fail` value. * - * **Previously Known As:** - * - * This API replaces the following from Effect 3.x: - * - * - `Stream.either` - * * **Example** (Converting failures to results) * * ```ts @@ -2593,12 +2526,6 @@ export const flatten: < /** * Flattens a stream of non-empty arrays into a stream of elements. * - * **Previously Known As** - * - * This API replaces the following from Effect 3.x: - * - * - `Stream.flattenChunks` - * * **Example** (Flattening a stream of non-empty arrays into a stream of elements) * * ```ts @@ -2987,12 +2914,6 @@ export const forever = (self: Stream): Stream => from /** * Submerges the iterables emitted by this stream into the stream's structure. * - * **Previously Known As** - * - * This API replaces the following from Effect 3.x: - * - * - `Stream.flattenIterables` - * * **Example** (Flattening iterable values) * * ```ts @@ -3205,12 +3126,6 @@ export const mergeEffect: { * Merges this stream and the specified stream together, tagging values from the * left stream as `Result.succeed` and values from the right stream as `Result.fail`. * - * **Previously Known As** - * - * This API replaces the following from Effect 3.x: - * - * - `Stream.mergeEither` - * * **Example** (Merging streams into results) * * ```ts @@ -3521,12 +3436,6 @@ const zipArrays = ( * * The function returns output plus leftover arrays that carry into the next pull. * - * **Previously Known As** - * - * This API replaces the following from Effect 3.x: - * - * - `Stream.zipWithChunks` - * * **Example** (Zipping stream chunks) * * ```ts @@ -4765,12 +4674,6 @@ export const buffer: { * buffer is full. This combinator preserves chunking and is best with * power-of-2 capacities. * - * **Previously Known As** - * - * This API replaces the following from Effect 3.x: - * - * - `Stream.bufferChunks` - * * **Example** (Buffering stream chunks) * * ```ts @@ -4817,12 +4720,6 @@ export const bufferArray: { * one fails. Allows recovery from all causes of failure, including * interruption if the stream is uninterruptible. * - * **Previously Known As** - * - * This API replaces the following from Effect 3.x: - * - * - `Stream.catchAllCause` - * * **Example** (Catching stream causes) * * ```ts @@ -4870,12 +4767,6 @@ export const catchCause: { * Runs an effect when the stream fails without changing its values or error, * unless the tap effect itself fails. * - * **Previously Known As** - * - * This API replaces the following from Effect 3.x: - * - * - `Stream.tapErrorCause` - * * **Example** (Tapping stream causes) * * ```ts @@ -4934,12 +4825,6 @@ export { /** * Switches over to the stream produced by the provided function if this one fails. * - * **Previously Known As** - * - * This API replaces the following from Effect 3.x: - * - * - `Stream.catchAll` - * * **Example** (Catching stream failures) * * ```ts @@ -5017,12 +4902,6 @@ export const tapError: { * stream. Non-matching failures propagate downstream, so the error type is * preserved unless the filter narrows it. * - * **Previously Known As** - * - * This API replaces the following from Effect 3.x: - * - * - `Stream.catchSome` - * * **Example** (Catching matching failures) * * ```ts @@ -5660,12 +5539,6 @@ export const mapError: { * Recovers from stream failures by filtering the `Cause` and switching to a recovery stream. * Non-matching causes are re-emitted as failures. * - * **Previously Known As** - * - * This API replaces the following from Effect 3.x: - * - * - `Stream.catchSomeCause` - * * **Example** (Catching matching causes) * * ```ts @@ -7115,12 +6988,6 @@ export const combine: { * internal state to control the combining process, with the initial state * being specified by `s`. * - * **Previously Known As** - * - * This API replaces the following from Effect 3.x: - * - * - `Stream.combineChunks` - * * **Example** (Combining stream chunks with state) * * ```ts @@ -9708,8 +9575,6 @@ export const ensuring: { * service requirements. Use `options.local` to build the layer every time; by * default, layers are shared between provide calls. * - * **Previously Known As:** `provideSomeLayer`, `provideSomeContext`. - * * **Example** (Providing stream requirements) * * ```ts From f136bb763048cbc6b17edd26496dba3e2415b9fa Mon Sep 17 00:00:00 2001 From: Giulio Canti Date: Mon, 18 May 2026 18:29:04 +0200 Subject: [PATCH 02/10] Change `Schema.asserts` and `SchemaParser.asserts` to assert a value directly with `asserts(schema, input)` and remove `Schema.Codec.ToAsserts` (#2221) --- .changeset/schema-asserts-signature.md | 5 ++++ migration/schema.md | 24 +++++++++++++++++ packages/effect/src/Schema.ts | 30 +++++----------------- packages/effect/src/SchemaParser.ts | 23 ++++++++--------- packages/effect/test/schema/Schema.test.ts | 5 ++-- packages/effect/typetest/schema/api.tst.ts | 8 +++--- 6 files changed, 51 insertions(+), 44 deletions(-) create mode 100644 .changeset/schema-asserts-signature.md diff --git a/.changeset/schema-asserts-signature.md b/.changeset/schema-asserts-signature.md new file mode 100644 index 0000000000..173f65875f --- /dev/null +++ b/.changeset/schema-asserts-signature.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Change `Schema.asserts` and `SchemaParser.asserts` to assert a value directly with `asserts(schema, input)` and remove `Schema.Codec.ToAsserts`. diff --git a/migration/schema.md b/migration/schema.md index fc20fddd53..da26e692be 100644 --- a/migration/schema.md +++ b/migration/schema.md @@ -38,6 +38,7 @@ This document maps v3 Schema APIs to their v4 equivalents. Simple renames and ar | `encode` | `encodeEffect` | rename | | `encodeUnknownEither` | `encodeUnknownExit` | rename | | `encodeEither` | `encodeExit` | rename | +| `asserts(schema)(input)` | `asserts(schema, input)` | semi-auto | | `Literal(null)` | `Null` | restructure | | `Literal("a", "b")` | `Literals(["a", "b"])` | variadic-to-array | | `pickLiteral("a", "b")` | `Literals(...).pick(["a", "b"])` | restructure | @@ -99,6 +100,29 @@ Note: `positive`, `negative`, `nonNegative`, `nonPositive` have been removed in ## Detailed migrations +### asserts signature + +**Migration: semi-auto** + +`Schema.asserts` now asserts an input directly instead of returning an assertion function. + +v3 + +```ts +import { Schema } from "effect" + +const assertString = Schema.asserts(Schema.String) +assertString(input) +``` + +v4 + +```ts +import { Schema } from "effect" + +Schema.asserts(Schema.String, input) +``` + ### validate* removal **Migration: removed** diff --git a/packages/effect/src/Schema.ts b/packages/effect/src/Schema.ts index fa43d4e4c0..281858f8b6 100644 --- a/packages/effect/src/Schema.ts +++ b/packages/effect/src/Schema.ts @@ -702,17 +702,6 @@ export declare namespace Codec { * @since 4.0.0 */ export type EncodingServices = S extends Top ? S["EncodingServices"] : never - /** - * Converts a schema type into an assertion function signature. The resulting - * function narrows its argument to `I & S["Type"]`. Only schemas with - * `DecodingServices: never` (i.e. no required services) can be used here. - * - * Produced by {@link asserts}. - * - * @category utility types - * @since 3.10.0 - */ - export type ToAsserts = (input: I) => asserts input is I & S["Type"] } /** @@ -1122,21 +1111,16 @@ export const is = Parser.is * ```ts * import { Schema } from "effect" * - * const assertString: (u: unknown) => asserts u is string = Schema.asserts( - * Schema.String - * ) + * const input: unknown = "hello" * - * // This will pass silently (no return value) - * try { - * assertString("hello") - * console.log("String assertion passed") - * } catch (error) { - * console.log("String assertion failed") - * } + * // This will pass silently (no return value) and narrow input to string + * Schema.asserts(Schema.String, input) + * console.log(input.toUpperCase()) * * // This will throw an error * try { - * assertString(123) + * const invalid: unknown = 123 + * Schema.asserts(Schema.String, invalid) * } catch (error) { * console.log("Non-string assertion failed as expected") * } @@ -1145,7 +1129,7 @@ export const is = Parser.is * @category guards * @since 4.0.0 */ -export const asserts = Parser.asserts +export const asserts: (schema: S, input: I) => asserts input is I & S["Type"] = Parser.asserts /** * Decodes an `unknown` input against a schema, returning an `Effect` that diff --git a/packages/effect/src/SchemaParser.ts b/packages/effect/src/SchemaParser.ts index a1f67dcf82..6d53303385 100644 --- a/packages/effect/src/SchemaParser.ts +++ b/packages/effect/src/SchemaParser.ts @@ -155,26 +155,23 @@ export function _issue(ast: AST.AST) { } /** - * Creates an assertion function that narrows an input to the schema's decoded type - * side. + * Asserts that an input satisfies the schema's decoded type side. * * The assertion returns normally when validation succeeds and throws when the * input does not satisfy the schema. * * @category Asserting - * @since 3.10.0 + * @since 4.0.0 */ -export function asserts(schema: Schema.Schema) { - const parser = asExit(run(AST.toType(schema.ast))) - return (input: I): asserts input is I & T => { - const exit = parser(input, AST.defaultParseOptions) - if (Exit.isFailure(exit)) { - const issue = Cause.findError(exit.cause) - if (Result.isFailure(issue)) { - throw Cause.squash(issue.failure) - } - throw new Error(issue.success.toString(), { cause: issue.success }) +export function asserts(schema: S, input: I): asserts input is I & S["Type"] { + const parser = asExit(run(AST.toType(schema.ast))) + const exit = parser(input, AST.defaultParseOptions) + if (Exit.isFailure(exit)) { + const issue = Cause.findError(exit.cause) + if (Result.isFailure(issue)) { + throw Cause.squash(issue.failure) } + throw new Error(issue.success.toString(), { cause: issue.success }) } } diff --git a/packages/effect/test/schema/Schema.test.ts b/packages/effect/test/schema/Schema.test.ts index a7212032fe..b8f000ca21 100644 --- a/packages/effect/test/schema/Schema.test.ts +++ b/packages/effect/test/schema/Schema.test.ts @@ -6736,14 +6736,13 @@ Expected a value with a size of at most 2, got Map([["a",1],["b",NaN],["c",3]])` describe("asserts", () => { it("FiniteFromString", () => { const schema = Schema.FiniteFromString - const asserts: Schema.Codec.ToAsserts = Schema.asserts(schema) try { - asserts(1) + Schema.asserts(schema, 1) } catch { fail("Expected asserts to not throw an error") } try { - asserts("a") + Schema.asserts(schema, "a") fail("Expected asserts to throw an error") } catch (e) { ok(e instanceof Error) diff --git a/packages/effect/typetest/schema/api.tst.ts b/packages/effect/typetest/schema/api.tst.ts index 4abef89993..680150c5e2 100644 --- a/packages/effect/typetest/schema/api.tst.ts +++ b/packages/effect/typetest/schema/api.tst.ts @@ -29,22 +29,20 @@ describe("decoding / encoding API", () => { it("asserts", () => { const schema = Schema.String - const asserts: Schema.Codec.ToAsserts = Schema.asserts(schema) const u = hole() { - asserts(u) + Schema.asserts(schema, u) expect(u).type.toBe() } const sn = hole() { - asserts(sn) + Schema.asserts(schema, sn) expect(sn).type.toBe() } const struct = Schema.Struct({ a: Schema.String }) - const assertsStruct: Schema.Codec.ToAsserts = Schema.asserts(struct) const s = hole<{ b: string }>() { - assertsStruct(s) + Schema.asserts(struct, s) expect(s).type.toBe<{ readonly a: string; b: string }>() } }) From 4d56068bd3aef7eea8779167754855a91514d134 Mon Sep 17 00:00:00 2001 From: Giulio Canti Date: Mon, 18 May 2026 19:25:34 +0200 Subject: [PATCH 03/10] Removed stale JSDoc analysis plumbing (#2222) --- .github/workflows/bundle-comment.yml | 4 +- .github/workflows/check.yml | 29 +- .github/workflows/jsdoc-analysis-comment.yml | 79 --- .gitignore | 3 - package.json | 1 - scripts/analyze-jsdoc.mjs | 539 ------------------- scripts/audit-jsdoc-categories.mjs | 479 ---------------- scripts/audit-jsdoc-descriptions.mjs | 412 -------------- scripts/codemod-ts-fence.mjs | 19 - scripts/codemods/ts-fence.test.ts | 206 ------- scripts/codemods/ts-fence.ts | 140 ----- scripts/codemods/vitest.config.ts | 5 - scripts/jsdocs/code2jsdoc-example.html | 69 --- 13 files changed, 5 insertions(+), 1980 deletions(-) delete mode 100644 .github/workflows/jsdoc-analysis-comment.yml delete mode 100644 scripts/analyze-jsdoc.mjs delete mode 100644 scripts/audit-jsdoc-categories.mjs delete mode 100644 scripts/audit-jsdoc-descriptions.mjs delete mode 100644 scripts/codemod-ts-fence.mjs delete mode 100644 scripts/codemods/ts-fence.test.ts delete mode 100644 scripts/codemods/ts-fence.ts delete mode 100644 scripts/codemods/vitest.config.ts delete mode 100644 scripts/jsdocs/code2jsdoc-example.html diff --git a/.github/workflows/bundle-comment.yml b/.github/workflows/bundle-comment.yml index 15a8973ad0..3ce7040e53 100644 --- a/.github/workflows/bundle-comment.yml +++ b/.github/workflows/bundle-comment.yml @@ -24,6 +24,8 @@ jobs: - name: Download Artifact uses: actions/download-artifact@v8 with: + name: bundle-stats + path: bundle-stats run-id: ${{ github.event.workflow_run.id }} github-token: ${{ secrets.GITHUB_TOKEN }} - name: Get stats @@ -31,7 +33,7 @@ jobs: run: | { echo 'stats<> $GITHUB_OUTPUT # https://github.com/orgs/community/discussions/25220#discussioncomment-11300118 diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 3c778fcb66..a16b645ad5 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -104,7 +104,9 @@ jobs: if: github.event_name == 'pull_request' uses: actions/upload-artifact@v4 with: + name: bundle-stats path: stats.txt + if-no-files-found: error test: name: Test @@ -146,33 +148,6 @@ jobs: if: matrix.runtime == 'Deno' run: deno task test --shard ${{ matrix.shard }} - jsdoc-analysis: - name: JSDoc Analysis - if: github.event_name == 'pull_request' - runs-on: ubuntu-latest - permissions: - contents: read - timeout-minutes: 5 - steps: - - uses: actions/checkout@v6 - - name: Install node - uses: actions/setup-node@v6 - - name: Generate JSDoc Analysis - shell: bash - run: | - # Run JSDoc analysis and format output for GitHub - node scripts/analyze-jsdoc.mjs > jsdoc-analysis.txt 2>&1 || true - echo "" > jsdoc-stats.md - echo "" >> jsdoc-stats.md - echo "\`\`\`" >> jsdoc-stats.md - cat jsdoc-analysis.txt >> jsdoc-stats.md - echo "\`\`\`" >> jsdoc-stats.md - - name: Upload JSDoc stats artifact - uses: actions/upload-artifact@v4 - with: - name: jsdoc-stats - path: jsdoc-stats.md - docgen: name: Documentation Generation runs-on: ubuntu-latest diff --git a/.github/workflows/jsdoc-analysis-comment.yml b/.github/workflows/jsdoc-analysis-comment.yml deleted file mode 100644 index 12fc3a9115..0000000000 --- a/.github/workflows/jsdoc-analysis-comment.yml +++ /dev/null @@ -1,79 +0,0 @@ -name: JSDoc Analysis Comment -on: - workflow_run: - workflows: ["Check"] - types: - - completed - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -permissions: {} - -jobs: - comment: - name: JSDoc Analysis - if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' - runs-on: ubuntu-latest - permissions: - actions: read - pull-requests: write - timeout-minutes: 1 - steps: - - name: Download Artifact - uses: actions/download-artifact@v8 - with: - run-id: ${{ github.event.workflow_run.id }} - github-token: ${{ secrets.GITHUB_TOKEN }} - - name: Get JSDoc stats - id: stats - run: | - { - echo 'stats<> $GITHUB_OUTPUT - # https://github.com/orgs/community/discussions/25220#discussioncomment-11300118 - - name: Get PR number - id: pr-context - env: - GH_TOKEN: ${{ github.token }} - PR_TARGET_REPO: ${{ github.repository }} - PR_BRANCH: |- - ${{ - (github.event.workflow_run.head_repository.owner.login != github.event.workflow_run.repository.owner.login) - && format('{0}:{1}', github.event.workflow_run.head_repository.owner.login, github.event.workflow_run.head_branch) - || github.event.workflow_run.head_branch - }} - run: gh pr view --repo "${PR_TARGET_REPO}" "${PR_BRANCH}" --json 'number' --jq '"number=\(.number)"' >> "${GITHUB_OUTPUT}" - - name: Find Comment - id: find-comment - uses: peter-evans/find-comment@v4 - with: - issue-number: ${{ steps.pr-context.outputs.number }} - comment-author: "github-actions[bot]" - body-includes: - - name: Create Comment - id: comment - uses: peter-evans/create-or-update-comment@v5 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - JSDOC_STATS: "${{ steps.stats.outputs.stats }}" - with: - comment-id: ${{ steps.find-comment.outputs.comment-id }} - issue-number: ${{ steps.pr-context.outputs.number }} - edit-mode: replace - body: | - - ## šŸ“Š JSDoc Documentation Analysis - -
- šŸ“ˆ Current Analysis Results - - ${{ env.JSDOC_STATS }} - -
- - --- - *This comment is automatically updated on each push. View the [analysis script](https://github.com/Effect-TS/effect-smol/blob/main/scripts/analyze-jsdoc.mjs) for details.* diff --git a/.gitignore b/.gitignore index 59fa7214c4..a1742005e5 100644 --- a/.gitignore +++ b/.gitignore @@ -15,9 +15,6 @@ node_modules/ coverage/ docs/ tmp/ -stats.html -jsdoc-analysis-results.json -jsdoc-stats.md # Generated by MacOS .DS_Store diff --git a/package.json b/package.json index 11b0275ff0..9edfe6f0e8 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,6 @@ "docgen": "pnpm --recursive --filter \"./packages/**/*\" exec docgen && node scripts/docs.mjs", "ai-docgen": "effect-ai-docgen ai-docs/src -o LLMS.md", "ai-docgen:watch": "pnpm ai-docgen --watch", - "analyze-jsdoc": "node scripts/analyze-jsdoc.mjs", "test-types": "tstyche", "changeset-version": "changeset version", "changeset-publish": "pnpm codemod && pnpm build && changeset publish", diff --git a/scripts/analyze-jsdoc.mjs b/scripts/analyze-jsdoc.mjs deleted file mode 100644 index fafbfc02bf..0000000000 --- a/scripts/analyze-jsdoc.mjs +++ /dev/null @@ -1,539 +0,0 @@ -#!/usr/bin/env node - -import * as Fs from "node:fs" -import * as Path from "node:path" -import * as Process from "node:process" - -// Parse command line arguments -const args = Process.argv.slice(2) -const fileFilter = args.find((arg) => arg.startsWith("--file="))?.replace("--file=", "") - -/** - * Analyzes TypeScript files for missing JSDoc examples and category tags - */ -class JSDocAnalyzer { - constructor() { - this.results = { - totalFiles: 0, - totalExports: 0, - missingExamples: 0, - missingCategories: 0, - fileDetails: [], - missingItems: [] - } - } - - /** - * Get all TypeScript files in the effect/src directory (including schema subdirectory) - */ - getEffectFiles() { - const effectSrcDir = Path.join(Process.cwd(), "packages/effect/src") - const files = Fs.readdirSync(effectSrcDir) - const allFiles = [] - - // Add root level files - files - .filter((file) => file.endsWith(".ts")) - .filter((file) => !file.endsWith(".test.ts")) - .filter((file) => { - // Only include files, not directories - const fullPath = Path.join(effectSrcDir, file) - return Fs.statSync(fullPath).isFile() - }) - .forEach((file) => allFiles.push(Path.join(effectSrcDir, file))) - - // Add schema subdirectory files - const schemaDir = Path.join(effectSrcDir, "schema") - if (Fs.existsSync(schemaDir)) { - const schemaFiles = Fs.readdirSync(schemaDir) - schemaFiles - .filter((file) => file.endsWith(".ts")) - .filter((file) => !file.endsWith(".test.ts")) - .filter((file) => { - const fullPath = Path.join(schemaDir, file) - return Fs.statSync(fullPath).isFile() - }) - .forEach((file) => allFiles.push(Path.join(schemaDir, file))) - } - - // Add config subdirectory files - const configDir = Path.join(effectSrcDir, "config") - if (Fs.existsSync(configDir)) { - const configFiles = Fs.readdirSync(configDir) - configFiles - .filter((file) => file.endsWith(".ts")) - .filter((file) => !file.endsWith(".test.ts")) - .filter((file) => { - const fullPath = Path.join(configDir, file) - return Fs.statSync(fullPath).isFile() - }) - .forEach((file) => allFiles.push(Path.join(configDir, file))) - } - - return allFiles - } - - /** - * Extract exported members from a TypeScript file using more comprehensive parsing - */ - extractExports(content, filename) { - const exports = [] - const lines = content.split("\n") - const processedFunctions = new Set() // Track functions to avoid counting overloads - - for (let i = 0; i < lines.length; i++) { - const line = lines[i].trim() - - // Skip comments and empty lines - if (line.startsWith("//") || line.startsWith("*") || !line) continue - - // More comprehensive export patterns including multi-line declarations - // Note: Using [\w$]+ to include $ character in export names (e.g., Array$, Object$) - const exportPatterns = [ - /^export\s+const\s+([\w$]+)[\s:=]/, - /^export\s+function\s+([\w$]+)\s*[(<]/, - /^export\s+type\s+([\w$]+)[\s=<]/, - /^export\s+interface\s+([\w$]+)[\s<{]/, - /^export\s+class\s+([\w$]+)[\s<{]/, - /^export\s+enum\s+([\w$]+)[\s{]/, - /^export\s+namespace\s+([\w$]+)[\s{]/, - /^export\s+declare\s+const\s+([\w$]+)[\s:]/, - /^export\s+declare\s+function\s+([\w$]+)\s*[(<]/, - /^export\s+declare\s+type\s+([\w$]+)[\s=<]/, - /^export\s+declare\s+interface\s+([\w$]+)[\s<{]/, - /^export\s+declare\s+class\s+([\w$]+)[\s<{]/, - /^export\s+declare\s+enum\s+([\w$]+)[\s{]/, - /^export\s+declare\s+namespace\s+([\w$]+)[\s{]/, - // Handle object destructuring exports - /^export\s+\{\s*([\w$]+)/ - ] - - for (const pattern of exportPatterns) { - const match = line.match(pattern) - if (match) { - const exportName = match[1] - - // Skip re-exports and internal exports - if (line.includes("from ") || exportName.startsWith("_")) { - continue - } - - // Skip certain common re-export patterns - if (line.includes("export {") && line.includes("}")) { - continue - } - - // For function overloads, only count the first declaration (with JSDoc) - if (line.includes("function ")) { - if (processedFunctions.has(exportName)) { - continue // Skip this overload - } - processedFunctions.add(exportName) - } - - // Find associated JSDoc block - const jsdoc = this.findJSDocBlock(lines, i) - - // Skip internal exports - they don't need categories or examples - if (jsdoc.isInternal) { - break - } - - const exportType = this.getExportType(line) - - const effectSrcDir = Path.join(Process.cwd(), "packages/effect/src") - exports.push({ - name: exportName, - line: i + 1, - type: exportType, - hasExample: jsdoc.hasExample, - hasCategory: jsdoc.hasCategory, - jsdocStart: jsdoc.start, - filename: Path.relative(effectSrcDir, filename), - exportLine: line - }) - break - } - } - } - - return exports - } - - /** - * Find JSDoc block preceding an export - improved to handle gaps and better detection - */ - findJSDocBlock(lines, exportLineIndex) { - let hasExample = false - let hasCategory = false - let isInternal = false - let jsdocStartLine = -1 - let jsdocEndLine = -1 - let emptyLinesCount = 0 - - // Look backwards for JSDoc block, allowing for empty lines - for (let i = exportLineIndex - 1; i >= 0; i--) { - const line = lines[i].trim() - - // Empty line - count them but continue searching - if (!line) { - emptyLinesCount++ - // Allow up to 3 empty lines between JSDoc and export - if (emptyLinesCount > 3 && jsdocEndLine === -1) { - break - } - continue - } - - // Reset empty line count when we find content - if (line) { - emptyLinesCount = 0 - } - - // End of JSDoc block - if (line === "*/") { - jsdocEndLine = i - continue - } - - // Start of JSDoc block - if (line.startsWith("/**")) { - jsdocStartLine = i - - // Single line JSDoc /** comment */ - if (line.endsWith("*/")) { - if (line.includes("@example")) hasExample = true - if (line.includes("@category")) hasCategory = true - if (line.includes("@internal")) isInternal = true - break - } - - // Multi-line JSDoc block - scan the entire block - // Note: jsdocEndLine is found first (going backwards), then jsdocStartLine - if (jsdocEndLine !== -1) { - for (let j = jsdocStartLine; j <= jsdocEndLine; j++) { - const blockLine = lines[j].trim() - if (blockLine.includes("@example")) { - hasExample = true - } - if (blockLine.includes("@category")) { - hasCategory = true - } - if (blockLine.includes("@internal")) { - isInternal = true - } - } - } - break - } - - // Hit another export/declaration - stop searching if we haven't found JSDoc yet - if ( - line && (line.startsWith("export ") || - line.startsWith("import ") || - line.startsWith("const ") || - line.startsWith("function ") || - line.startsWith("class ") || - line.startsWith("interface ") || - line.startsWith("type ") || - line.startsWith("enum ")) - ) { - break - } - } - - return { hasExample, hasCategory, isInternal, start: jsdocStartLine } - } - - /** - * Determine the type of export with better detection - */ - getExportType(line) { - if (line.includes("const ")) return "const" - if (line.includes("function ")) return "function" - if (line.includes("type ")) return "type" - if (line.includes("interface ")) return "interface" - if (line.includes("class ")) return "class" - if (line.includes("enum ")) return "enum" - if (line.includes("namespace ")) return "namespace" - if (line.includes("declare ")) return "declare" - return "unknown" - } - - /** - * Analyze a single file - */ - analyzeFile(filepath) { - const content = Fs.readFileSync(filepath, "utf8") - const effectSrcDir = Path.join(Process.cwd(), "packages/effect/src") - const filename = Path.relative(effectSrcDir, filepath) - const exports = this.extractExports(content, filepath) - - const fileStats = { - filename, - totalExports: exports.length, - missingExamples: exports.filter((e) => !e.hasExample).length, - missingCategories: exports.filter((e) => !e.hasCategory).length, - exports: exports.map((e) => ({ - name: e.name, - type: e.type, - line: e.line, - hasExample: e.hasExample, - hasCategory: e.hasCategory - })) - } - - // Track missing items for detailed reporting - exports.forEach((exp) => { - if (!exp.hasExample || !exp.hasCategory) { - this.results.missingItems.push({ - file: filename, - name: exp.name, - type: exp.type, - line: exp.line, - missingExample: !exp.hasExample, - missingCategory: !exp.hasCategory - }) - } - }) - - return fileStats - } - - /** - * Run analysis on all Effect source files or a specific file - */ - analyze(targetFile = null) { - const files = this.getEffectFiles() - - if (targetFile) { - const targetPath = Path.join(Process.cwd(), "packages/effect/src", targetFile) - - if (!files.includes(targetPath)) { - Process.stdout.write(`Error: File '${targetFile}' not found.\n`) - Process.stdout.write(`Use relative paths from packages/effect/src/:\n`) - Process.stdout.write(` - For root files: Effect.ts, Array.ts, etc.\n`) - Process.stdout.write(` - For schema files: schema/Schema.ts, schema/AST.ts, etc.\n`) - Process.stdout.write(` - For config files: config/Config.ts, config/ConfigError.ts, etc.\n\n`) - Process.stdout.write(`Available files:\n`) - const effectSrcDir = Path.join(Process.cwd(), "packages/effect/src") - files.forEach((f) => { - const relativePath = Path.relative(effectSrcDir, f) - Process.stdout.write(` ${relativePath}\n`) - }) - return - } - - // Analyze only the target file - const fileStats = this.analyzeFile(targetPath) - this.generateFileReport(fileStats) - return - } - - Process.stdout.write( - `Analyzing ${files.length} TypeScript files in packages/effect/src/ (including schema and config subdirectories)...\n\n` - ) - - this.results.totalFiles = files.length - - for (const filepath of files) { - const fileStats = this.analyzeFile(filepath) - this.results.fileDetails.push(fileStats) - - this.results.totalExports += fileStats.totalExports - this.results.missingExamples += fileStats.missingExamples - this.results.missingCategories += fileStats.missingCategories - } - - this.generateReport() - } - - /** - * Generate report for a single file - */ - generateFileReport(fileStats) { - const { exports, filename, missingCategories, missingExamples, totalExports } = fileStats - - Process.stdout.write("=".repeat(60) + "\n") - Process.stdout.write(` ${filename.toUpperCase()} DOCUMENTATION REPORT\n`) - Process.stdout.write("=".repeat(60) + "\n\n") - - // Summary - Process.stdout.write("šŸ“Š SUMMARY\n") - Process.stdout.write("-".repeat(20) + "\n") - Process.stdout.write(`Total exports: ${totalExports}\n`) - Process.stdout.write( - `Missing examples: ${missingExamples} (${((missingExamples / totalExports) * 100).toFixed(1)}%)\n` - ) - Process.stdout.write( - `Missing categories: ${missingCategories} (${((missingCategories / totalExports) * 100).toFixed(1)}%)\n\n` - ) - - // Missing examples - if (missingExamples > 0) { - Process.stdout.write("šŸ“ MISSING EXAMPLES\n") - Process.stdout.write("-".repeat(30) + "\n") - const missingExampleItems = exports.filter((e) => !e.hasExample) - missingExampleItems.forEach((item, index) => { - Process.stdout.write(`${index + 1}. ${item.name} (${item.type}) - Line ${item.line}\n`) - }) - Process.stdout.write("\n") - } - - // Missing categories - if (missingCategories > 0) { - Process.stdout.write("šŸ·ļø MISSING CATEGORIES\n") - Process.stdout.write("-".repeat(30) + "\n") - const missingCategoryItems = exports.filter((e) => !e.hasCategory) - missingCategoryItems.forEach((item, index) => { - Process.stdout.write(`${index + 1}. ${item.name} (${item.type}) - Line ${item.line}\n`) - }) - Process.stdout.write("\n") - } - - // Breakdown by type - Process.stdout.write("šŸ“‹ BREAKDOWN BY TYPE\n") - Process.stdout.write("-".repeat(25) + "\n") - const typeStats = {} - exports.forEach((exp) => { - if (!typeStats[exp.type]) { - typeStats[exp.type] = { total: 0, missingExample: 0, missingCategory: 0 } - } - typeStats[exp.type].total++ - if (!exp.hasExample) typeStats[exp.type].missingExample++ - if (!exp.hasCategory) typeStats[exp.type].missingCategory++ - }) - - Object.entries(typeStats).forEach(([type, stats]) => { - Process.stdout.write( - `${type}: ${stats.total} total, ${stats.missingExample} missing examples, ${stats.missingCategory} missing categories\n` - ) - }) - - Process.stdout.write("\n" + "=".repeat(60) + "\n") - Process.stdout.write(`Analysis complete for ${filename}!\n`) - Process.stdout.write("=".repeat(60) + "\n") - } - - /** - * Generate comprehensive analysis report - */ - generateReport() { - const { fileDetails, missingCategories, missingExamples, missingItems, totalExports, totalFiles } = this.results - - Process.stdout.write("=".repeat(60) + "\n") - Process.stdout.write(" EFFECT JSDOC ANALYSIS REPORT\n") - Process.stdout.write("=".repeat(60) + "\n") - Process.stdout.write("\n") - - // Summary Statistics - Process.stdout.write("šŸ“Š SUMMARY STATISTICS\n") - Process.stdout.write("-".repeat(30) + "\n") - Process.stdout.write(`Total files analyzed: ${totalFiles}\n`) - Process.stdout.write(`Total exported members: ${totalExports}\n`) - Process.stdout.write( - `Missing @example: ${missingExamples} (${((missingExamples / totalExports) * 100).toFixed(1)}%)\n` - ) - Process.stdout.write( - `Missing @category: ${missingCategories} (${((missingCategories / totalExports) * 100).toFixed(1)}%)\n` - ) - Process.stdout.write("\n") - - // Top files needing attention (sorted by total missing items) - Process.stdout.write("šŸŽÆ TOP FILES NEEDING ATTENTION\n") - Process.stdout.write("-".repeat(40) + "\n") - const sortedFiles = fileDetails - .filter((f) => f.missingExamples > 0 || f.missingCategories > 0) - .sort((a, b) => (b.missingExamples + b.missingCategories) - (a.missingExamples + a.missingCategories)) - .slice(0, 15) - - sortedFiles.forEach((file, index) => { - Process.stdout.write(`${index + 1}. ${file.filename}\n`) - Process.stdout.write( - ` šŸ“ ${file.missingExamples} missing examples, šŸ·ļø ${file.missingCategories} missing categories\n` - ) - Process.stdout.write(` šŸ“¦ ${file.totalExports} total exports\n`) - }) - - Process.stdout.write("\n") - - // Files with perfect documentation - const perfectFiles = fileDetails.filter((f) => f.missingExamples === 0 && f.missingCategories === 0) - if (perfectFiles.length > 0) { - Process.stdout.write("āœ… PERFECTLY DOCUMENTED FILES\n") - Process.stdout.write("-".repeat(35) + "\n") - perfectFiles.forEach((file) => { - Process.stdout.write(` ${file.filename} (${file.totalExports} exports)\n`) - }) - Process.stdout.write("\n") - } - - // Show a sample of missing items for the top file - if (sortedFiles.length > 0) { - const topFile = sortedFiles[0] - const topFileMissingItems = missingItems.filter((item) => item.file === topFile.filename).slice(0, 10) - if (topFileMissingItems.length > 0) { - Process.stdout.write(`šŸ” SAMPLE MISSING ITEMS FROM ${topFile.filename}\n`) - Process.stdout.write("-".repeat(35) + "\n") - topFileMissingItems.forEach((item) => { - const missing = [] - if (item.missingExample) missing.push("example") - if (item.missingCategory) missing.push("category") - Process.stdout.write(` ${item.name} (${item.type}, line ${item.line}): missing ${missing.join(", ")}\n`) - }) - Process.stdout.write("\n") - } - } - - // Detailed breakdown by type - Process.stdout.write("šŸ“‹ BREAKDOWN BY EXPORT TYPE\n") - Process.stdout.write("-".repeat(35) + "\n") - const typeStats = {} - missingItems.forEach((item) => { - if (!typeStats[item.type]) { - typeStats[item.type] = { total: 0, missingExample: 0, missingCategory: 0 } - } - typeStats[item.type].total++ - if (item.missingExample) typeStats[item.type].missingExample++ - if (item.missingCategory) typeStats[item.type].missingCategory++ - }) - - Object.entries(typeStats).forEach(([type, stats]) => { - Process.stdout.write( - `${type}: ${stats.missingExample} missing examples, ${stats.missingCategory} missing categories\n` - ) - }) - - Process.stdout.write("\n") - - // Progress tracking - const documentedExamples = totalExports - missingExamples - const documentedCategories = totalExports - missingCategories - Process.stdout.write("šŸ“ˆ DOCUMENTATION PROGRESS\n") - Process.stdout.write("-".repeat(30) + "\n") - Process.stdout.write( - `Examples: ${documentedExamples}/${totalExports} (${ - ((documentedExamples / totalExports) * 100).toFixed(1) - }% complete)\n` - ) - Process.stdout.write( - `Categories: ${documentedCategories}/${totalExports} (${ - ((documentedCategories / totalExports) * 100).toFixed(1) - }% complete)\n` - ) - Process.stdout.write("\n") - - Process.stdout.write("=".repeat(60) + "\n") - Process.stdout.write(`Analysis complete! ${missingExamples + missingCategories} items need attention.\n`) - Process.stdout.write("=".repeat(60) + "\n") - - // Save detailed results to JSON for further analysis - const outputFile = "jsdoc-analysis-results.json" - Fs.writeFileSync(outputFile, JSON.stringify(this.results, null, 2)) - Process.stdout.write(`\nšŸ“„ Detailed results saved to: ${outputFile}\n`) - } -} - -// Run the analysis -const analyzer = new JSDocAnalyzer() -analyzer.analyze(fileFilter) diff --git a/scripts/audit-jsdoc-categories.mjs b/scripts/audit-jsdoc-categories.mjs deleted file mode 100644 index 6f66eaf35f..0000000000 --- a/scripts/audit-jsdoc-categories.mjs +++ /dev/null @@ -1,479 +0,0 @@ -#!/usr/bin/env node - -// Collects files that need JSDoc category cleanup into a per-file queue. -// This script deliberately does not judge category quality; category taxonomy -// redesign is handled by reviewers/agents working directly in each source file. - -import * as Fs from "node:fs" -import * as Path from "node:path" -import * as Process from "node:process" -import ts from "typescript" - -const cwd = Process.cwd() -const args = Process.argv.slice(2) -const outputDirectory = getArg("--output-dir") ?? "reports/jsdoc-categories" - -const files = listSourceFiles(Path.join(cwd, "packages")) -const entries = [] -let topLevelRelevantTotal = 0 -let topLevelMissingCategoryTotal = 0 -let topLevelCategoryWithoutSinceTotal = 0 -let topLevelNamespaceCategoryTotal = 0 -let nestedJSDocTotal = 0 -let nestedCategoryTotal = 0 - -for (const file of files) { - const source = Fs.readFileSync(file, "utf8") - const sourceFile = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true) - const summary = summarizeFile(sourceFile) - - topLevelRelevantTotal += summary.topLevelRelevant - topLevelMissingCategoryTotal += summary.topLevelMissingCategory - topLevelCategoryWithoutSinceTotal += summary.topLevelCategoryWithoutSince - topLevelNamespaceCategoryTotal += summary.topLevelNamespaceCategory - nestedJSDocTotal += summary.nestedJSDoc - nestedCategoryTotal += summary.nestedCategory - - if ( - summary.topLevelRelevant === 0 && - summary.topLevelNamespaceCategory === 0 && - summary.nestedCategory === 0 - ) { - continue - } - - entries.push({ - file: normalizePath(Path.relative(cwd, file)), - packageName: getPackageName(file), - moduleName: getModuleName(file), - ...summary - }) -} - -entries.sort((a, b) => - a.packageName.localeCompare(b.packageName) || - a.moduleName.localeCompare(b.moduleName) || - a.file.localeCompare(b.file) -) - -writeQueue(entries, { - files: files.length, - topLevelRelevant: topLevelRelevantTotal, - topLevelMissingCategory: topLevelMissingCategoryTotal, - topLevelCategoryWithoutSince: topLevelCategoryWithoutSinceTotal, - topLevelNamespaceCategory: topLevelNamespaceCategoryTotal, - nestedJSDoc: nestedJSDocTotal, - nestedCategory: nestedCategoryTotal -}) - -console.log(`Scanned ${files.length} source file(s).`) -console.log(`Queued ${entries.length} file(s) for JSDoc category cleanup.`) -console.log(`Found ${topLevelRelevantTotal} public non-namespace exported JSDoc block(s) with @since or @category.`) -console.log(`Found ${topLevelMissingCategoryTotal} public non-namespace exported @since block(s) missing @category.`) -console.log(`Found ${topLevelNamespaceCategoryTotal} namespace @category tag(s) to remove.`) -console.log(`Found ${nestedCategoryTotal} nested @category tag(s) to remove.`) -console.log(`Wrote ${Path.join(outputDirectory, "queue.md")}.`) - -function getArg(name) { - const direct = args.find((arg) => arg.startsWith(`${name}=`)) - if (direct !== undefined) { - return direct.slice(name.length + 1) - } - const index = args.indexOf(name) - return index === -1 ? undefined : args[index + 1] -} - -function listSourceFiles(root) { - const out = [] - - function visit(directory) { - for (const entry of Fs.readdirSync(directory, { withFileTypes: true })) { - const fullPath = Path.join(directory, entry.name) - if (entry.isDirectory()) { - if ( - entry.name === "node_modules" || - entry.name === "dist" || - entry.name === "docs" || - entry.name === "test" || - entry.name === "typetest" - ) { - continue - } - visit(fullPath) - continue - } - - if (!entry.isFile() || !entry.name.endsWith(".ts")) { - continue - } - if ( - entry.name === "index.ts" || - entry.name.endsWith(".d.ts") || - entry.name.endsWith(".test.ts") || - entry.name.endsWith(".tst.ts") || - entry.name.endsWith("Generated.ts") - ) { - continue - } - if (!normalizePath(fullPath).includes("/src/")) { - continue - } - out.push(fullPath) - } - } - - visit(root) - return out.sort((a, b) => normalizePath(a).localeCompare(normalizePath(b))) -} - -function summarizeFile(sourceFile) { - const categories = new Map() - const nestedCategories = new Map() - let topLevelRelevant = 0 - let topLevelMissingCategory = 0 - let topLevelCategoryWithoutSince = 0 - let topLevelNamespaceCategory = 0 - let nestedJSDoc = 0 - let nestedCategory = 0 - - for (const statement of sourceFile.statements) { - if (!isExportedTopLevelDeclaration(statement)) { - continue - } - - let topLevelInternal = false - const jsdoc = getLeadingJSDoc(statement) - if (jsdoc !== undefined) { - const parsed = parseJSDoc(sourceFile, jsdoc) - topLevelInternal = hasTag(parsed, "internal") - if (!topLevelInternal && ts.isModuleDeclaration(statement) && hasTag(parsed, "category")) { - topLevelNamespaceCategory += tagCount(parsed, "category") - } - if (!topLevelInternal && !ts.isModuleDeclaration(statement)) { - const summary = summarizePublicJSDoc(parsed) - topLevelRelevant += summary.relevant - topLevelMissingCategory += summary.missingCategory - topLevelCategoryWithoutSince += summary.categoryWithoutSince - addCategories(categories, summary.categories) - } - } - - if (topLevelInternal) { - continue - } - - if (ts.isModuleDeclaration(statement)) { - const namespace = summarizeNamespaceMembers(sourceFile, statement) - topLevelRelevant += namespace.relevant - topLevelMissingCategory += namespace.missingCategory - topLevelCategoryWithoutSince += namespace.categoryWithoutSince - topLevelNamespaceCategory += namespace.namespaceCategory - nestedJSDoc += namespace.nestedJSDoc - nestedCategory += namespace.nestedCategory - addCategories(categories, namespace.categories) - addCategories(nestedCategories, namespace.nestedCategories) - } else { - const nested = summarizeNestedJSDocs(sourceFile, statement) - nestedJSDoc += nested.count - nestedCategory += nested.category - addCategories(nestedCategories, nested.categories) - } - } - - return { - topLevelRelevant, - topLevelMissingCategory, - topLevelCategoryWithoutSince, - topLevelNamespaceCategory, - nestedJSDoc, - nestedCategory, - categories: Array.from(categories, ([name, count]) => ({ name, count })) - .sort((a, b) => b.count - a.count || a.name.localeCompare(b.name)), - nestedCategories: Array.from(nestedCategories, ([name, count]) => ({ name, count })) - .sort((a, b) => b.count - a.count || a.name.localeCompare(b.name)) - } -} - -function isExportedTopLevelDeclaration(node) { - return ( - ts.isClassDeclaration(node) || - ts.isEnumDeclaration(node) || - ts.isFunctionDeclaration(node) || - ts.isInterfaceDeclaration(node) || - ts.isModuleDeclaration(node) || - ts.isTypeAliasDeclaration(node) || - ts.isVariableStatement(node) - ) && hasModifier(node, ts.SyntaxKind.ExportKeyword) -} - -function summarizeNamespaceMembers(sourceFile, namespaceNode) { - const categories = new Map() - const nestedCategories = new Map() - let relevant = 0 - let missingCategory = 0 - let categoryWithoutSince = 0 - let namespaceCategory = 0 - let nestedJSDoc = 0 - let nestedCategory = 0 - - for (const statement of getNamespaceStatements(namespaceNode)) { - if (!isExportedTopLevelDeclaration(statement)) { - continue - } - - let internal = false - const jsdoc = getLeadingJSDoc(statement) - if (jsdoc !== undefined) { - const parsed = parseJSDoc(sourceFile, jsdoc) - internal = hasTag(parsed, "internal") - if (!internal && ts.isModuleDeclaration(statement) && hasTag(parsed, "category")) { - namespaceCategory += tagCount(parsed, "category") - } - if (!internal && !ts.isModuleDeclaration(statement)) { - const summary = summarizePublicJSDoc(parsed) - relevant += summary.relevant - missingCategory += summary.missingCategory - categoryWithoutSince += summary.categoryWithoutSince - addCategories(categories, summary.categories) - } - } - - if (internal) { - continue - } - - const nested = ts.isModuleDeclaration(statement) - ? summarizeNamespaceMembers(sourceFile, statement) - : summarizeNestedJSDocs(sourceFile, statement) - - if (ts.isModuleDeclaration(statement)) { - relevant += nested.relevant - missingCategory += nested.missingCategory - categoryWithoutSince += nested.categoryWithoutSince - namespaceCategory += nested.namespaceCategory - nestedJSDoc += nested.nestedJSDoc - nestedCategory += nested.nestedCategory - addCategories(categories, nested.categories) - addCategories(nestedCategories, nested.nestedCategories) - } else { - nestedJSDoc += nested.count - nestedCategory += nested.category - addCategories(nestedCategories, nested.categories) - } - } - - return { - relevant, - missingCategory, - categoryWithoutSince, - namespaceCategory, - nestedJSDoc, - nestedCategory, - categories: Array.from(categories, ([name, count]) => ({ name, count })), - nestedCategories: Array.from(nestedCategories, ([name, count]) => ({ name, count })) - } -} - -function summarizePublicJSDoc(parsed) { - const categoryValues = tagValues(parsed, "category") - const relevant = hasTag(parsed, "since") || categoryValues.length > 0 ? 1 : 0 - return { - relevant, - missingCategory: relevant === 1 && categoryValues.length === 0 ? 1 : 0, - categoryWithoutSince: categoryValues.length > 0 && !hasTag(parsed, "since") ? 1 : 0, - categories: categoryValues.map((name) => ({ name, count: 1 })) - } -} - -function getNamespaceStatements(namespaceNode) { - return namespaceNode.body?.statements ?? [] -} - -function hasModifier(node, kind) { - return node.modifiers?.some((modifier) => modifier.kind === kind) === true -} - -function addCategories(target, categories) { - for (const category of categories) { - target.set(category.name, (target.get(category.name) ?? 0) + category.count) - } -} - -function summarizeNestedJSDocs(sourceFile, root) { - let count = 0 - let category = 0 - const categories = new Map() - - function visit(node) { - if (node !== root) { - const jsdoc = getLeadingJSDoc(node) - if (jsdoc !== undefined) { - const parsed = parseJSDoc(sourceFile, jsdoc) - if (!hasTag(parsed, "internal")) { - count++ - const categoryValues = tagValues(parsed, "category") - category += categoryValues.length - for (const value of categoryValues) { - categories.set(value, (categories.get(value) ?? 0) + 1) - } - } - } - } - - if (ts.isBlock(node)) { - return - } - - ts.forEachChild(node, visit) - } - - ts.forEachChild(root, visit) - return { - count, - category, - categories: Array.from(categories, ([name, count]) => ({ name, count })) - } -} - -function getLeadingJSDoc(node) { - const jsdocs = node.jsDoc - if (jsdocs === undefined || jsdocs.length === 0) { - return undefined - } - return jsdocs[jsdocs.length - 1] -} - -function parseJSDoc(sourceFile, jsdoc) { - const raw = sourceFile.text.slice(jsdoc.pos, jsdoc.end) - const lines = raw - .replace(/^\/\*\*/, "") - .replace(/\*\/$/, "") - .split(/\r\n|\r|\n/) - .map((line) => line.replace(/^\s*\* ?/, "").trimEnd()) - - const tags = [] - let inFence = false - - for (const line of lines) { - const trimmed = line.trim() - if (trimmed.startsWith("```")) { - inFence = !inFence - continue - } - if (inFence) { - continue - } - - const tag = trimmed.match(/^@([A-Za-z][\w-]*)(?:\s+(.*))?$/) - if (tag !== null) { - tags.push({ - name: tag[1], - value: tag[2]?.trim() ?? "" - }) - } - } - - return { tags } -} - -function hasTag(jsdoc, name) { - return jsdoc.tags.some((tag) => tag.name === name) -} - -function tagCount(jsdoc, name) { - return jsdoc.tags.filter((tag) => tag.name === name).length -} - -function tagValues(jsdoc, name) { - return jsdoc.tags - .filter((tag) => tag.name === name) - .map((tag) => tag.value.trim()) - .filter((value) => value !== "") -} - -function writeQueue(entries, summary) { - const output = Path.join(cwd, outputDirectory) - Fs.mkdirSync(output, { recursive: true }) - Fs.writeFileSync(Path.join(output, "queue.md"), renderQueue(entries, summary)) -} - -function renderQueue(entries, summary) { - const lines = [ - "# JSDoc Category Cleanup Queue", - "", - "Generated by `node scripts/audit-jsdoc-categories.mjs`.", - "", - "This file is a work queue, not a semantic audit result. The script only partitions files that contain public non-namespace exported JSDoc blocks with `@since` or `@category`, namespace JSDoc blocks with `@category`, or ordinary nested JSDoc blocks that already contain `@category`.", - "", - "## Swarm Instructions", - "", - "- Use one worker per file.", - "- Keep at most 6 workers active at a time.", - "- Workers may edit only `@category` lines in their assigned source file.", - "- For public non-namespace exported JSDocs, including direct namespace exports, redesign the file-local category taxonomy for generated docs navigation.", - "- For public non-namespace exported JSDocs with `@since` but no `@category`, add a category from the file-local taxonomy.", - "- For namespace JSDocs, remove `@category`.", - "- For nested JSDocs inside top-level exported declarations, remove `@category`.", - "- Do not edit descriptions, examples, `@since`, runtime code, types, imports, exports, or generated `index.ts` files.", - "- Only the coordinator should update this queue.", - "- Mark a file as done by changing `[ ]` to `[x]`; leave a short note when a category decision was ambiguous.", - "", - "## Summary", - "", - `- Source files scanned: ${summary.files}`, - `- Files queued: ${entries.length}`, - `- Public non-namespace exported JSDoc blocks with @since or @category: ${summary.topLevelRelevant}`, - `- Public non-namespace exported @since blocks missing @category: ${summary.topLevelMissingCategory}`, - `- Public non-namespace exported @category blocks missing @since: ${summary.topLevelCategoryWithoutSince}`, - `- Namespace @category tags to remove: ${summary.topLevelNamespaceCategory}`, - `- Nested JSDoc blocks inside top-level exports: ${summary.nestedJSDoc}`, - `- Nested @category tags to remove: ${summary.nestedCategory}`, - "", - "## Files", - "" - ] - - for (const entry of entries) { - lines.push( - `- [ ] \`${entry.file}\``, - ` - Package: \`${entry.packageName}\``, - ` - Module: \`${entry.moduleName}\``, - ` - Public non-namespace relevant JSDocs: ${entry.topLevelRelevant}`, - ` - Missing public non-namespace @category: ${entry.topLevelMissingCategory}`, - ` - Public non-namespace @category without @since: ${entry.topLevelCategoryWithoutSince}`, - ` - Namespace @category tags to remove: ${entry.topLevelNamespaceCategory}`, - ` - Nested JSDoc blocks: ${entry.nestedJSDoc}`, - ` - Nested @category tags to remove: ${entry.nestedCategory}`, - ` - Current top-level categories: ${renderCategories(entry.categories)}`, - ` - Current nested categories: ${renderCategories(entry.nestedCategories)}` - ) - } - - lines.push("") - return lines.join("\n") -} - -function renderCategories(categories) { - if (categories.length === 0) { - return "(none)" - } - return categories.map((category) => `\`${category.name}\` (${category.count})`).join(", ") -} - -function getPackageName(file) { - const relative = normalizePath(Path.relative(cwd, file)) - const parts = relative.split("/") - const srcIndex = parts.indexOf("src") - return parts.slice(0, srcIndex).join("/") -} - -function getModuleName(file) { - const relative = normalizePath(Path.relative(cwd, file)) - const parts = relative.split("/") - const srcIndex = parts.indexOf("src") - return parts.slice(srcIndex + 1).join("/").replace(/\.ts$/, "") -} - -function normalizePath(file) { - return file.split(Path.sep).join("/") -} diff --git a/scripts/audit-jsdoc-descriptions.mjs b/scripts/audit-jsdoc-descriptions.mjs deleted file mode 100644 index 03a4ca6d37..0000000000 --- a/scripts/audit-jsdoc-descriptions.mjs +++ /dev/null @@ -1,412 +0,0 @@ -#!/usr/bin/env node - -// Collects exported, non-internal JSDoc descriptions into review chunks. -// This script deliberately does not judge quality; human/agent reviewers read -// the chunks and write only the descriptions they flag into the backlog. - -import * as Fs from "node:fs" -import * as Path from "node:path" -import * as Process from "node:process" -import ts from "typescript" - -const cwd = Process.cwd() -const args = Process.argv.slice(2) -const outputDirectory = getArg("--output-dir") ?? "reports/jsdoc-descriptions" -const chunkSize = Number(getArg("--chunk-size") ?? 120) - -const ignoredBasenames = new Set(["Generated.ts", "index.ts"]) -const declarationKinds = new Set([ - ts.SyntaxKind.ClassDeclaration, - ts.SyntaxKind.EnumDeclaration, - ts.SyntaxKind.FunctionDeclaration, - ts.SyntaxKind.InterfaceDeclaration, - ts.SyntaxKind.ModuleDeclaration, - ts.SyntaxKind.TypeAliasDeclaration, - ts.SyntaxKind.VariableStatement -]) - -const files = listSourceFiles(Path.join(cwd, "packages")) -const entries = [] -let skippedInternal = 0 - -for (const file of files) { - const source = Fs.readFileSync(file, "utf8") - const sourceFile = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true) - - visitSourceFile(sourceFile, (node, exportPath) => { - const jsdoc = getLeadingJSDoc(node) - if (jsdoc === undefined) { - return - } - - const parsed = parseJSDoc(sourceFile, jsdoc) - if (parsed.tags.includes("internal")) { - skippedInternal++ - return - } - - const declarations = getExportedDeclarations(sourceFile, node) - for (const declaration of declarations) { - const position = sourceFile.getLineAndCharacterOfPosition(declaration.position) - entries.push({ - packageName: getPackageName(file), - moduleName: getModuleName(file), - file: normalizePath(Path.relative(cwd, file)), - line: position.line + 1, - apiName: [...exportPath, declaration.name].join("."), - apiKind: declaration.kind, - description: parsed.description, - tags: parsed.tags - }) - } - }) -} - -entries.sort((a, b) => - a.packageName.localeCompare(b.packageName) || - a.moduleName.localeCompare(b.moduleName) || - a.line - b.line || - a.apiName.localeCompare(b.apiName) -) - -writeInventory(entries, { - files: files.length, - skippedInternal -}) - -console.log(`Collected ${entries.length} exported non-internal JSDoc declaration(s) from ${files.length} file(s).`) -console.log(`Skipped ${skippedInternal} @internal JSDoc declaration(s).`) -console.log(`Wrote inventory and review chunks to ${outputDirectory}.`) - -function getArg(name) { - const direct = args.find((arg) => arg.startsWith(`${name}=`)) - if (direct !== undefined) { - return direct.slice(name.length + 1) - } - const index = args.indexOf(name) - return index === -1 ? undefined : args[index + 1] -} - -function listSourceFiles(root) { - const out = [] - - function visit(directory) { - for (const entry of Fs.readdirSync(directory, { withFileTypes: true })) { - const fullPath = Path.join(directory, entry.name) - if (entry.isDirectory()) { - if ( - entry.name === "node_modules" || - entry.name === "dist" || - entry.name === "docs" || - entry.name === "test" || - entry.name === "typetest" - ) { - continue - } - visit(fullPath) - continue - } - - if (!entry.isFile() || !entry.name.endsWith(".ts")) { - continue - } - if ( - entry.name.endsWith(".d.ts") || - entry.name.endsWith(".test.ts") || - entry.name.endsWith(".tst.ts") || - ignoredBasenames.has(entry.name) - ) { - continue - } - if (!normalizePath(fullPath).includes("/src/")) { - continue - } - out.push(fullPath) - } - } - - visit(root) - return out.sort((a, b) => normalizePath(a).localeCompare(normalizePath(b))) -} - -function visitSourceFile(sourceFile, onExport) { - function visit(node, exportPath, exportedNamespaceDepth) { - if (!declarationKinds.has(node.kind)) { - ts.forEachChild(node, (child) => visit(child, exportPath, exportedNamespaceDepth)) - return - } - - const isExported = hasModifier(node, ts.SyntaxKind.ExportKeyword) || exportedNamespaceDepth > 0 - - if (isExported) { - onExport(node, exportPath) - } - - if (ts.isModuleDeclaration(node) && isExported && node.name !== undefined) { - const namespaceName = node.name.getText(sourceFile).replaceAll("\"", "") - if (node.body !== undefined) { - ts.forEachChild(node.body, (child) => visit(child, [...exportPath, namespaceName], exportedNamespaceDepth + 1)) - } - return - } - - ts.forEachChild(node, (child) => visit(child, exportPath, exportedNamespaceDepth)) - } - - ts.forEachChild(sourceFile, (node) => visit(node, [], 0)) -} - -function hasModifier(node, kind) { - return node.modifiers?.some((modifier) => modifier.kind === kind) === true -} - -function getLeadingJSDoc(node) { - const jsdocs = node.jsDoc - if (jsdocs === undefined || jsdocs.length === 0) { - return undefined - } - return jsdocs[jsdocs.length - 1] -} - -function getExportedDeclarations(sourceFile, node) { - if (ts.isVariableStatement(node)) { - return node.declarationList.declarations - .filter((declaration) => ts.isIdentifier(declaration.name)) - .map((declaration) => ({ - name: declaration.name.text, - kind: "value", - position: declaration.name.getStart(sourceFile) - })) - } - - if ( - (ts.isFunctionDeclaration(node) || - ts.isClassDeclaration(node) || - ts.isInterfaceDeclaration(node) || - ts.isTypeAliasDeclaration(node) || - ts.isEnumDeclaration(node) || - ts.isModuleDeclaration(node)) && - node.name !== undefined - ) { - return [{ - name: node.name.getText(sourceFile), - kind: getApiKind(node), - position: node.name.getStart(sourceFile) - }] - } - - return [] -} - -function getApiKind(node) { - if (ts.isFunctionDeclaration(node)) { - return "value" - } - if (ts.isClassDeclaration(node)) { - return "class" - } - if (ts.isInterfaceDeclaration(node)) { - return "interface" - } - if (ts.isTypeAliasDeclaration(node)) { - return "type" - } - if (ts.isEnumDeclaration(node)) { - return "enum" - } - if (ts.isModuleDeclaration(node)) { - return "namespace" - } - return "value" -} - -function parseJSDoc(sourceFile, jsdoc) { - const raw = sourceFile.text.slice(jsdoc.pos, jsdoc.end) - const lines = raw - .replace(/^\/\*\*/, "") - .replace(/\*\/$/, "") - .split(/\r\n|\r|\n/) - .map((line) => line.replace(/^\s*\* ?/, "").trimEnd()) - - const tags = [] - let inFence = false - let inExample = false - const descriptionLines = [] - - for (const line of lines) { - const trimmed = line.trim() - if (trimmed.startsWith("```")) { - inFence = !inFence - continue - } - if (inFence) { - continue - } - - const tag = trimmed.match(/^@([A-Za-z][\w-]*)/) - if (tag !== null) { - tags.push(tag[1]) - if (tag[1] === "example") { - inExample = true - } - continue - } - - if (/^\*\*Examples?\*\*/i.test(trimmed) || /^#{2,}\s+Examples?/i.test(trimmed)) { - inExample = true - continue - } - - if (inExample) { - continue - } - - if (trimmed !== "") { - descriptionLines.push(trimmed) - } - } - - return { - tags: Array.from(new Set(tags)), - description: normalizeDescription(descriptionLines.join("\n")) - } -} - -function normalizeDescription(description) { - return description - .replaceAll(/\{@link\s+([^}\s]+)(?:\s+[^}]*)?\}/g, "$1") - .replaceAll(/[ \t]+\n/g, "\n") - .replaceAll(/\n{3,}/g, "\n\n") - .trim() -} - -function writeInventory(entries, summary) { - const output = Path.join(cwd, outputDirectory) - const chunksDirectory = Path.join(output, "chunks") - const findingsDirectory = Path.join(output, "findings") - - Fs.mkdirSync(output, { recursive: true }) - Fs.rmSync(chunksDirectory, { recursive: true, force: true }) - Fs.mkdirSync(chunksDirectory, { recursive: true }) - Fs.mkdirSync(findingsDirectory, { recursive: true }) - - Fs.writeFileSync(Path.join(output, "inventory.json"), `${JSON.stringify({ - generatedBy: "node scripts/audit-jsdoc-descriptions.mjs", - scannedFiles: summary.files, - skippedInternal: summary.skippedInternal, - entries - }, null, 2)}\n`) - - const chunks = [] - for (let start = 0; start < entries.length; start += chunkSize) { - const chunkEntries = entries.slice(start, start + chunkSize) - const chunkNumber = String(chunks.length + 1).padStart(3, "0") - const chunkPath = Path.join(chunksDirectory, `chunk-${chunkNumber}.md`) - const chunk = { - number: chunks.length + 1, - path: normalizePath(Path.relative(cwd, chunkPath)), - start: start + 1, - end: start + chunkEntries.length, - count: chunkEntries.length - } - chunks.push(chunk) - Fs.writeFileSync(chunkPath, renderChunk(chunk, chunkEntries)) - } - - const backlogPath = Path.join(output, "review-needed.md") - if (!Fs.existsSync(backlogPath)) { - Fs.writeFileSync(backlogPath, renderBacklogSkeleton(entries, chunks, summary)) - } -} - -function renderBacklogSkeleton(entries, chunks, summary) { - return [ - "# JSDoc Description Review Needed", - "", - "Generated by `node scripts/audit-jsdoc-descriptions.mjs`.", - "", - "This backlog is intentionally empty until reviewers judge the inventory chunks.", - "The script only detects exported declarations with leading JSDoc blocks that are not tagged `@internal`; it does not decide whether a description is good.", - "", - "## Summary", - "", - `- Source files scanned: ${summary.files}`, - `- Exported non-internal JSDoc declarations collected: ${entries.length}`, - `- @internal JSDoc declarations skipped: ${summary.skippedInternal}`, - `- Review chunks: ${chunks.length}`, - "- Review-needed entries remaining: 0", - "", - "## Review Chunks", - "", - ...chunks.map((chunk) => `- [${Path.basename(chunk.path, ".md")}](${chunk.path}) (${chunk.count} entries, ${chunk.start}-${chunk.end})`), - "", - "## Entries", - "", - "No reviewed findings have been merged yet.", - "" - ].join("\n") -} - -function renderChunk(chunk, entries) { - const lines = [ - `# JSDoc Description Inventory ${String(chunk.number).padStart(3, "0")}`, - "", - "This file is reviewer input, not a backlog. Judge each description manually.", - "Only copy flagged descriptions into `reports/jsdoc-descriptions/review-needed.md` or a worker findings file.", - "", - "For each flagged entry, record: severity, issue labels, reason, current description, and suggested replacement.", - "", - `Entries: ${chunk.start}-${chunk.end}`, - "" - ] - - let currentPackage = "" - let currentModule = "" - - for (const entry of entries) { - if (entry.packageName !== currentPackage) { - currentPackage = entry.packageName - currentModule = "" - lines.push("", `## ${entry.packageName}`) - } - if (entry.moduleName !== currentModule) { - currentModule = entry.moduleName - lines.push("", `### ${entry.moduleName}`) - } - - lines.push( - "", - `#### ${entry.apiName}`, - "", - `- File: \`${entry.file}:${entry.line}\``, - `- API kind: \`${entry.apiKind}\``, - `- Tags: ${entry.tags.length === 0 ? "(none)" : entry.tags.map((tag) => `\`${tag}\``).join(", ")}`, - "- Current description:", - "", - "```md", - entry.description === "" ? "(missing)" : entry.description, - "```" - ) - } - - lines.push("") - return lines.join("\n") -} - -function getPackageName(file) { - const relative = normalizePath(Path.relative(cwd, file)) - const parts = relative.split("/") - const srcIndex = parts.indexOf("src") - return parts.slice(0, srcIndex).join("/") -} - -function getModuleName(file) { - const relative = normalizePath(Path.relative(cwd, file)) - const parts = relative.split("/") - const srcIndex = parts.indexOf("src") - return parts.slice(srcIndex + 1).join("/").replace(/\.ts$/, "") -} - -function normalizePath(file) { - return file.split(Path.sep).join("/") -} diff --git a/scripts/codemod-ts-fence.mjs b/scripts/codemod-ts-fence.mjs deleted file mode 100644 index f802b3581b..0000000000 --- a/scripts/codemod-ts-fence.mjs +++ /dev/null @@ -1,19 +0,0 @@ -// @ts-check -import * as Glob from "glob" -import * as Jscodeshift from "jscodeshift/src/Runner.js" -import * as Path from "node:path" - -// Look up files in all workspace packages including those nested in -// sub-packages (e.g. `packages/ai/openapi`). -const pattern = "packages/{*,*/*}/src/**/*.ts" - -const paths = Glob.globSync(pattern, { - ignore: ["**/internal/**"] -}).map((path) => Path.resolve(path)) - -const transformer = Path.resolve("scripts/codemods/ts-fence.ts") - -Jscodeshift.run(transformer, paths, { - babel: true, - parser: "ts" -}) diff --git a/scripts/codemods/ts-fence.test.ts b/scripts/codemods/ts-fence.test.ts deleted file mode 100644 index 595bd62c2b..0000000000 --- a/scripts/codemods/ts-fence.test.ts +++ /dev/null @@ -1,206 +0,0 @@ -// You can run this test suite with the following command: -// npx vitest scripts/codemods/ts-fence.test.ts --config scripts/codemods/vitest.config.ts -import type * as cs from "jscodeshift" -import * as TestUtils from "jscodeshift/src/testUtils" -import transformer from "./ts-fence.ts" - -const expectTransformation_ = (transformer: cs.Transform) => -( - description: string, - input: string, - output: string -) => { - TestUtils.defineInlineTest( - { default: transformer, parser: "ts" }, - {}, - input, - output, - description - ) -} - -const expectTransformation = expectTransformation_(transformer) - -expectTransformation( - "should ignore line comments", - `// description -const v = 1`, - `// description -const v = 1` -) - -expectTransformation( - "should ignore block comments that don't contain an @example tag", - ` -/** - * description - */ -const v = 1`, - ` -/** - * description - */ -const v = 1` -) - -expectTransformation( - "should wrap the given code in a ts fence (without following tags)", - ` -/** - * a - * - * b - * - * @example - * const x = 1 - * - */ -const v = 1`, - ` -/** - * a - * - * b - * - * @example - * \`\`\`ts - * const x = 1 - * - * \`\`\` - */ -const v = 1` -) - -expectTransformation( - "should wrap the given code in a ts fence (with following tags)", - ` -/** - * a - * - * b - * - * @example - * const x = 1 - * - * @since 1.0.0 - * @category collecting & elements - */ -const v = 1`, - ` -/** - * a - * - * b - * - * @example - * \`\`\`ts - * const x = 1 - * \`\`\` - * - * @since 1.0.0 - * @category collecting & elements - */ -const v = 1` -) - -expectTransformation( - "should skip wrapping if the code is already in a ts fence", - ` -/** - * a - * - * b - * - * @example - * \`\`\`ts - * const x = 1 - * \`\`\` - */ -const v = 1`, - ` -/** - * a - * - * b - * - * @example - * \`\`\`ts - * const x = 1 - * \`\`\` - */ -const v = 1` -) - -expectTransformation( - "should skip wrapping indented examples that are already in a ts fence", - ` -export declare namespace ReadonlyArray { - /** - * Flattens a nested array type. - * - * @example - * \`\`\`ts - * import type { Array } from "effect" - * - * type Nested = ReadonlyArray> - * type Flattened = Array.ReadonlyArray.Flatten - * // Flattened is Array - * \`\`\` - * - * @category types - * @since 2.0.0 - */ - export type Flatten>> = Array -}`, - ` -export declare namespace ReadonlyArray { - /** - * Flattens a nested array type. - * - * @example - * \`\`\`ts - * import type { Array } from "effect" - * - * type Nested = ReadonlyArray> - * type Flattened = Array.ReadonlyArray.Flatten - * // Flattened is Array - * \`\`\` - * - * @category types - * @since 2.0.0 - */ - export type Flatten>> = Array -}` -) - -expectTransformation( - "should wrap indented examples using the current doc prefix", - ` -export declare namespace ReadonlyArray { - /** - * Infers the element type of an iterable. - * - * @example - * type A = string - * - * @category types - * @since 2.0.0 - */ - export type Infer> = S extends Iterable ? A : never -}`, - ` -export declare namespace ReadonlyArray { - /** - * Infers the element type of an iterable. - * - * @example - * \`\`\`ts - * type A = string - * \`\`\` - * - * @category types - * @since 2.0.0 - */ - export type Infer> = S extends Iterable ? A : never -}` -) diff --git a/scripts/codemods/ts-fence.ts b/scripts/codemods/ts-fence.ts deleted file mode 100644 index 1739c023ab..0000000000 --- a/scripts/codemods/ts-fence.ts +++ /dev/null @@ -1,140 +0,0 @@ -import type * as cs from "jscodeshift" - -export default function transformer(file: cs.FileInfo, api: cs.API) { - const j = api.jscodeshift - const root = j(file.source) - - root.find(j.Comment as any).forEach((path) => { - if (path.value.type === "CommentBlock") { - const value = (path.value) as any - const comment = value.value - const wrapped = wrapExamplesWithFence(comment) - if (wrapped !== comment) { - value.value = wrapped - } - } - }) - - return root.toSource() -} - -function wrapExamplesWithFence(jsdocComment: string) { - const lines = normalizeJSDocLines(jsdocComment.split("\n")) - const output: Array = [] - let changed = false - - for (let i = 0; i < lines.length; i++) { - const line = lines[i] - output.push(line) - - if (!isExampleLine(line)) { - continue - } - - const nextContentLine = findNextContentLine(lines, i + 1) - if (nextContentLine !== -1 && isTsFenceLine(lines[nextContentLine])) { - continue - } - - changed = true - - const prefix = getJSDocLine(line)?.prefix ?? " * " - const nextTagLine = findNextTagLine(lines, i + 1) - const endLine = nextTagLine === -1 ? findClosingPaddingLine(lines) : nextTagLine - let exampleEnd = endLine === -1 ? lines.length : endLine - - if (nextTagLine !== -1) { - while (exampleEnd > i + 1 && isBlankJSDocLine(lines[exampleEnd - 1])) { - exampleEnd-- - } - } - - output.push(`${prefix}\`\`\`ts`) - for (let j = i + 1; j < exampleEnd; j++) { - output.push(lines[j]) - } - output.push(`${prefix}\`\`\``) - - if (endLine !== -1) { - for (let j = exampleEnd; j < endLine; j++) { - output.push(lines[j]) - } - i = endLine - 1 - } else { - i = lines.length - 1 - } - } - - return changed ? output.join("\n") : jsdocComment -} - -function normalizeJSDocLines(lines: Array) { - return lines.map((line, index) => { - const jsdocLine = getJSDocLine(line) - if (jsdocLine === null) { - return isClosingPaddingLine(line) ? " " : line - } - if (index === 0 && jsdocLine.text === "") { - return "*" - } - return jsdocLine.text === "" ? " *" : ` * ${jsdocLine.text}` - }) -} - -function getJSDocLine(line: string) { - const match = line.match(/^(\s*\*\s?)(.*)$/) - if (!match) { - return null - } - return { prefix: match[1], text: match[2] } -} - -function isExampleLine(line: string) { - const jsdocLine = getJSDocLine(line) - return jsdocLine !== null && /^@example\b/.test(jsdocLine.text.trim()) -} - -function isTsFenceLine(line: string) { - const jsdocLine = getJSDocLine(line) - return jsdocLine !== null && /^```(?:ts|typescript)?\s*$/.test(jsdocLine.text.trim()) -} - -function isTagLine(line: string) { - const jsdocLine = getJSDocLine(line) - return jsdocLine !== null && /^@\w/.test(jsdocLine.text.trim()) -} - -function isBlankJSDocLine(line: string) { - const jsdocLine = getJSDocLine(line) - return jsdocLine !== null && jsdocLine.text.trim() === "" -} - -function isClosingPaddingLine(line: string) { - return getJSDocLine(line) === null && line.trim() === "" -} - -function findNextContentLine(lines: Array, start: number) { - for (let i = start; i < lines.length; i++) { - if (!isBlankJSDocLine(lines[i])) { - return i - } - } - return -1 -} - -function findNextTagLine(lines: Array, start: number) { - for (let i = start; i < lines.length; i++) { - if (isTagLine(lines[i])) { - return i - } - } - return -1 -} - -function findClosingPaddingLine(lines: Array) { - const lastLineIndex = lines.length - 1 - if (lastLineIndex >= 0 && isClosingPaddingLine(lines[lastLineIndex])) { - return lastLineIndex - } - return -1 -} diff --git a/scripts/codemods/vitest.config.ts b/scripts/codemods/vitest.config.ts deleted file mode 100644 index 0a565d350a..0000000000 --- a/scripts/codemods/vitest.config.ts +++ /dev/null @@ -1,5 +0,0 @@ -export default { - test: { - globals: true - } -} diff --git a/scripts/jsdocs/code2jsdoc-example.html b/scripts/jsdocs/code2jsdoc-example.html deleted file mode 100644 index b592aef991..0000000000 --- a/scripts/jsdocs/code2jsdoc-example.html +++ /dev/null @@ -1,69 +0,0 @@ - - - - - code 2 jsdoc @example - - - -
-

code 2 jsdoc @example

-

- Given an example pasted in the left textarea, will generate a result - ready to be pasted into a JSDoc comment. -

-
- - -
-
- - - From 0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5 Mon Sep 17 00:00:00 2001 From: Maxwell Brown Date: Mon, 18 May 2026 18:46:16 -0400 Subject: [PATCH 04/10] Add platform Crypto service (#2180) Co-authored-by: Tim Smart --- .changeset/green-rings-prove.md | 5 + .changeset/platform-crypto-service.md | 9 + .specs/README.md | 1 + .specs/effect-platform-crypto.md | 338 +++ packages/ai/anthropic/src/index.ts | 44 + packages/ai/openai-compat/src/index.ts | 56 + packages/ai/openai/src/index.ts | 16 + packages/ai/openrouter/src/index.ts | 60 + packages/effect/src/Crypto.ts | 327 +++ packages/effect/src/Random.ts | 87 +- packages/effect/src/index.ts | 2323 ++++++++++++++--- packages/effect/src/testing/index.ts | 34 + packages/effect/src/unstable/ai/index.ts | 84 +- packages/effect/src/unstable/cli/index.ts | 211 +- packages/effect/src/unstable/cluster/index.ts | 790 ++++++ .../effect/src/unstable/devtools/index.ts | 58 + .../effect/src/unstable/encoding/index.ts | 49 + .../effect/src/unstable/eventlog/index.ts | 240 ++ packages/effect/src/unstable/http/index.ts | 452 ++++ packages/effect/src/unstable/httpapi/index.ts | 335 ++- .../src/unstable/observability/index.ts | 160 +- .../effect/src/unstable/persistence/index.ts | 147 ++ .../effect/src/unstable/reactivity/index.ts | 167 ++ packages/effect/src/unstable/rpc/index.ts | 272 ++ packages/effect/src/unstable/schema/Model.ts | 96 +- packages/effect/src/unstable/schema/index.ts | 37 + packages/effect/src/unstable/socket/index.ts | 44 + packages/effect/src/unstable/sql/index.ts | 178 ++ packages/effect/src/unstable/workers/index.ts | 80 + .../effect/src/unstable/workflow/index.ts | 160 ++ packages/effect/test/Crypto.test.ts | 86 + packages/effect/test/Random.test.ts | 72 +- packages/opentelemetry/src/index.ts | 119 + .../platform-browser/src/BrowserCrypto.ts | 71 + packages/platform-browser/src/index.ts | 324 +++ .../test/BrowserCrypto.test.ts | 74 + packages/platform-bun/src/BunCrypto.ts | 16 + packages/platform-bun/src/BunServices.ts | 5 +- packages/platform-bun/src/index.ts | 376 +++ .../platform-node-shared/src/NodeCrypto.ts | 55 + packages/platform-node/src/NodeCrypto.ts | 16 + packages/platform-node/src/NodeServices.ts | 5 +- packages/platform-node/src/index.ts | 353 +++ .../platform-node/test/NodeCrypto.test.ts | 59 + packages/sql/clickhouse/src/index.ts | 37 + packages/sql/d1/src/index.ts | 21 + packages/sql/libsql/src/index.ts | 37 + packages/sql/mssql/src/index.ts | 84 + packages/sql/mysql2/src/index.ts | 35 + packages/sql/pg/src/index.ts | 37 + packages/sql/pglite/src/index.ts | 39 + packages/sql/sqlite-bun/src/index.ts | 37 + packages/sql/sqlite-do/src/index.ts | 39 + packages/sql/sqlite-node/src/index.ts | 29 + packages/sql/sqlite-react-native/src/index.ts | 33 + 55 files changed, 8362 insertions(+), 557 deletions(-) create mode 100644 .changeset/green-rings-prove.md create mode 100644 .changeset/platform-crypto-service.md create mode 100644 .specs/effect-platform-crypto.md create mode 100644 packages/effect/src/Crypto.ts create mode 100644 packages/effect/test/Crypto.test.ts create mode 100644 packages/platform-browser/src/BrowserCrypto.ts create mode 100644 packages/platform-browser/test/BrowserCrypto.test.ts create mode 100644 packages/platform-bun/src/BunCrypto.ts create mode 100644 packages/platform-node-shared/src/NodeCrypto.ts create mode 100644 packages/platform-node/src/NodeCrypto.ts create mode 100644 packages/platform-node/test/NodeCrypto.test.ts diff --git a/.changeset/green-rings-prove.md b/.changeset/green-rings-prove.md new file mode 100644 index 0000000000..c7a2ea6063 --- /dev/null +++ b/.changeset/green-rings-prove.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +update Model uuid helpers diff --git a/.changeset/platform-crypto-service.md b/.changeset/platform-crypto-service.md new file mode 100644 index 0000000000..afbf216e6c --- /dev/null +++ b/.changeset/platform-crypto-service.md @@ -0,0 +1,9 @@ +--- +"effect": patch +"@effect/platform-node": patch +"@effect/platform-node-shared": patch +"@effect/platform-bun": patch +"@effect/platform-browser": patch +--- + +Add a platform-agnostic `Crypto` service for cryptographic random bytes, secure random generators, UUIDv4 / UUIDv7 generation, and digest operations. UUID generation should now use the `Crypto` service's `randomUUIDv4` or `randomUUIDv7`, which format bytes from the platform `Crypto` service; UUIDv7 also uses the `Clock` service timestamp. `Random.nextUUIDv4` has been removed because the base `Random` service is not cryptographically secure. diff --git a/.specs/README.md b/.specs/README.md index 0bf40b7564..573160801e 100644 --- a/.specs/README.md +++ b/.specs/README.md @@ -1,3 +1,4 @@ # Specifications - [Effect SQL UniqueViolation SqlError Reason](./effect-sql-unique-violation.md) — Adds a `UniqueViolation` SQL error reason with a `constraint` property and updates driver classification for UNIQUE constraint violations. +- [Effect Platform Crypto Service](./effect-platform-crypto.md) — Adds a platform-agnostic `Crypto` service for cryptographic random bytes, native UUIDv4 generation, and digest operations. diff --git a/.specs/effect-platform-crypto.md b/.specs/effect-platform-crypto.md new file mode 100644 index 0000000000..04a7ba2183 --- /dev/null +++ b/.specs/effect-platform-crypto.md @@ -0,0 +1,338 @@ +# Effect Platform Crypto Service Specification + +## Summary + +Add a platform-agnostic `Crypto` service to `effect`. The service provides cryptographic random bytes, cryptographically secure random generators, UUIDv4 / UUIDv7 generation, and digest operations through an Effect service interface, with platform-specific implementations supplied by runtime packages such as `@effect/platform-node`, `@effect/platform-bun`, and `@effect/platform-browser`. + +The service replaces `Random.nextUUIDv4`. UUIDv4 and UUIDv7 generation are exposed on the `Crypto` service and derived by the `make` constructor from the primitive `randomBytes` implementation. UUIDv7 also uses the `Clock` service for the Unix millisecond timestamp. + +## Background and Research + +Platform services in this repository follow the pattern used by `FileSystem`: + +- Core service abstraction: `packages/effect/src/FileSystem.ts`. +- Node wrapper: `packages/platform-node/src/NodeFileSystem.ts`. +- Bun wrapper: `packages/platform-bun/src/BunFileSystem.ts`. +- Shared Node/Bun implementation: `packages/platform-node-shared/src/NodeFileSystem.ts`. + +Important conventions from that implementation: + +- The service interface lives in `effect` and carries a unique `TypeId` property. +- The service tag is created with `Context.Service`. +- Platform-specific packages expose `layer` values that provide the service. +- Node and Bun can share implementation code when Bun supports the relevant Node API. +- Generated `index.ts` barrel files must not be edited manually; run `pnpm codegen` after adding modules. +- Platform errors should use `PlatformError` where possible. +- Library implementation code should avoid `async` / `await` and `try` / `catch`, using Effect APIs instead. + +The current base `Random` service is not cryptographically secure because its default implementation is based on `Math.random`. `Crypto` extends the `Random` service shape so platform crypto implementations can expose cryptographically secure random number generation through `Crypto` service methods. + +## User Requirements and Clarifications + +- Add a platform-agnostic `Crypto` Effect service. +- `Crypto` must extend the `Random` service. +- Include UUIDv4 and UUIDv7 generators so users can stop using `Random.nextUUIDv4`. +- `randomUUIDv4` must be a service method derived by `make`, not a primitive required from platform implementations. +- `randomUUIDv4` must format bytes from the service's `randomBytes(16)` and follow UUIDv4 version/variant requirements. +- `randomUUIDv7` must be a service method derived by `make`, format bytes from `randomBytes(16)`, use `Clock.currentTimeMillis` for the 48-bit Unix millisecond timestamp, and follow UUIDv7 version/variant requirements. +- Define `DigestAlgorithm` as a string literal union. +- Add cryptographically secure counterparts to the `Random` module generators as service methods with clearer names: `random`, `randomBoolean`, `randomInt`, `randomBetween`, `randomIntBetween`, `randomShuffle`, `randomUUIDv4`, and `randomUUIDv7`. +- Keep the initial `Crypto` surface limited to random bytes, random generators, UUID generation, and digests. + +## Proposed Public API + +Add `packages/effect/src/Crypto.ts`. + +```ts +import * as Context from "./Context.ts" +import * as Clock from "./Clock.ts" +import type * as Effect from "./Effect.ts" +import type { PlatformError } from "./PlatformError.ts" +import type * as Random from "./Random.ts" + +const TypeId = "~effect/platform/Crypto" + +export type DigestAlgorithm = "SHA-1" | "SHA-256" | "SHA-384" | "SHA-512" + +export interface Crypto extends Random.Random { + readonly [TypeId]: typeof TypeId + + readonly randomBytes: ( + size: number + ) => Effect.Effect + + readonly digest: ( + algorithm: DigestAlgorithm, + data: Uint8Array + ) => Effect.Effect + + readonly random: Effect.Effect + readonly randomBoolean: Effect.Effect + readonly randomInt: Effect.Effect + readonly randomBetween: (min: number, max: number) => Effect.Effect + readonly randomIntBetween: ( + min: number, + max: number, + options?: { readonly halfOpen?: boolean | undefined } + ) => Effect.Effect + readonly randomShuffle:
(elements: Iterable) => Effect.Effect> + readonly randomUUIDv4: Effect.Effect + readonly randomUUIDv7: Effect.Effect +} + +export const Crypto: Context.Service = Context.Service("effect/platform/Crypto") + +export const make: (impl: Omit) => Crypto +``` + +## Functional Requirements + +### Core `Crypto` Module + +1. Add `packages/effect/src/Crypto.ts`. +2. Define `TypeId` as `"~effect/platform/Crypto"`. +3. Define `DigestAlgorithm` as a top-level string literal union. +4. Use Web Crypto algorithm names `"SHA-1"`, `"SHA-256"`, `"SHA-384"`, and `"SHA-512"`. +5. Do not require platform implementations to provide derived random generator helpers. +6. Define `Crypto` as an extension of `Random.Random` with `randomBytes` and `digest`. +7. Include `randomUUIDv4`, `randomUUIDv7`, and the random generator helpers on the `Crypto` service interface. +8. Define the service tag with `Context.Service("effect/platform/Crypto")`. +9. Do not add top-level accessors; users should retrieve the service and call its methods. +10. Add cryptographically secure random generator helpers matching the `Random` module capabilities with clearer names to the service interface. +11. Derive service `randomUUIDv4` from `randomBytes(16)`. +12. Derive service `randomUUIDv7` from `Clock.currentTimeMillis` and `randomBytes(16)`. +13. Keep the core module platform-agnostic; it must not import `node:crypto` or rely directly on `globalThis.crypto`. +14. The `make` helper should accept the primitive implementation and derive random helper methods, similar to `ChildProcessSpawner.make`. + +### Random Generator Requirements + +1. `random` must use `Crypto.nextDoubleUnsafe`. +2. `randomBoolean` must use `Crypto.nextDoubleUnsafe() > 0.5`. +3. `randomInt` must use `Crypto.nextIntUnsafe`. +4. `randomBetween` must follow `Random.nextBetween` semantics. +5. `randomIntBetween` must follow `Random.nextIntBetween` semantics, including `halfOpen`. +6. `randomShuffle` must follow `Random.shuffle` semantics. +7. The random generator helper names must be more descriptive than the base `Random` names. + +### UUIDv4 Requirements + +1. `randomUUIDv4` must return a lowercase UUIDv4 string. +2. It must satisfy the standard UUIDv4 shape: `xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`, where `y` is one of `8`, `9`, `a`, or `b`. +3. It must call `randomBytes(16)` and format those bytes according to UUIDv4 rules. +4. It must set the version bits to `0100` in byte 6. +5. It must set the variant bits to `10` in byte 8. +6. It must not use `Random`, `Math.random`, `Date.now`, `new Date`, or platform `crypto.randomUUID`. + +### UUIDv7 Requirements + +1. `randomUUIDv7` must return a lowercase UUIDv7 string. +2. It must satisfy the standard UUIDv7 shape: `xxxxxxxx-xxxx-7xxx-yxxx-xxxxxxxxxxxx`, where `y` is one of `8`, `9`, `a`, or `b`. +3. Bytes 0 through 5 must encode the current Unix timestamp in milliseconds from `Clock.currentTimeMillis` as a big-endian 48-bit integer. +4. Remaining UUID bits must come from `randomBytes(16)` except for overwritten timestamp, version, and variant bits. +5. It must set the version bits to `0111` in byte 6. +6. It must set the variant bits to `10` in byte 8. +7. It must not use `Random`, `Math.random`, `Date.now`, `new Date`, or platform `crypto.randomUUID`. + +### Random Bytes Requirements + +1. `randomBytes(size)` must generate cryptographically secure random bytes. +2. `size` must be a safe non-negative integer. +3. Invalid sizes must fail with `PlatformError.badArgument`. +4. `randomBytes(0)` should succeed with an empty `Uint8Array`. +5. Browser implementations must respect the `crypto.getRandomValues` per-call limit by chunking large requests, typically at `65_536` bytes. +6. Returned byte arrays should be fresh arrays owned by the caller. + +### Digest Requirements + +1. `digest(algorithm, data)` must compute a cryptographic digest of `data`. +2. The returned value must be a `Uint8Array`. +3. The input `data` must not be mutated. +4. Platform failures must be represented as `PlatformError` values. +5. SHA-1 is included for compatibility with Web Crypto and existing protocols, but documentation must state that SHA-1 should not be used for new security-sensitive designs. + +## Platform Implementation Plan + +### Node and Bun Shared Implementation + +Add `packages/platform-node-shared/src/NodeCrypto.ts`. + +Requirements: + +1. Import `node:crypto`. +2. Implement `randomBytes` with `crypto.randomBytes`. +3. Implement `nextIntUnsafe` and `nextDoubleUnsafe` with synchronous cryptographically secure random bytes. +4. Implement `digest` with `crypto.webcrypto.subtle.digest` or equivalent Node crypto APIs. +5. Convert backend exceptions and promise rejections to `PlatformError`. +6. Export `layer: Layer.Layer`. + +Add thin runtime wrappers: + +- `packages/platform-node/src/NodeCrypto.ts` re-exports `NodeCrypto.layer`. +- `packages/platform-bun/src/BunCrypto.ts` re-exports the shared layer if Bun compatibility is sufficient. + +### Browser Implementation + +Add `packages/platform-browser/src/BrowserCrypto.ts`. + +Requirements: + +1. Use `globalThis.crypto` as the backend. +2. Implement `randomBytes` with `crypto.getRandomValues`, chunking large requests. +3. Implement `nextIntUnsafe` and `nextDoubleUnsafe` with synchronous `crypto.getRandomValues`. +4. Implement `digest` with `crypto.subtle.digest`. +5. Fail with a clear `PlatformError.systemError({ _tag: "Unknown" })` when required crypto capabilities are unavailable. +6. Do not require Node types or Node imports. + +## Service Aggregation + +Update Node service aggregation: + +- Add `Crypto` to `packages/platform-node/src/NodeServices.ts` imports and `NodeServices` union. +- Merge `NodeCrypto.layer` into `NodeServices.layer`. + +Update Bun service aggregation: + +- Add `Crypto` to `packages/platform-bun/src/BunServices.ts` imports and `BunServices` union. +- Merge `BunCrypto.layer` into `BunServices.layer`. + +No browser aggregate service module currently exists. Do not introduce one solely for this task unless maintainers request it. + +## Random Migration Plan + +1. Remove `Random.nextUUIDv4`. +2. Update `Random` module documentation to remove UUID examples. +3. Update `packages/effect/test/Random.test.ts` to remove UUID-specific tests. +4. Add tests and documentation showing `Crypto.Crypto` service `randomUUIDv4` as the replacement. +5. Document that the base `Random` service is not cryptographically secure and should not be used for security-sensitive values. + +## Documentation Requirements + +Add detailed JSDoc to `packages/effect/src/Crypto.ts` and update `packages/effect/src/Random.ts`. + +The documentation must explain: + +1. `Crypto` is for cryptographic randomness and cryptographic operations. +2. The base `Random` service is not cryptographically secure. +3. `Random.withSeed` provides deterministic replacement for repeatability; predictable seeds are not cryptographically secure. +4. UUID generation should use the `Crypto` service's `randomUUIDv4` or `randomUUIDv7`, not `Random.nextUUIDv4`. +5. Platform implementations must be provided through layers. +6. SHA-1 is available only for compatibility and should be avoided for new security-sensitive designs. + +## Testing Requirements + +### Core Tests + +Add `packages/effect/test/Crypto.test.ts`. + +Test cases: + +1. `DigestAlgorithm` supports the expected string literal values. +2. `randomBytes` delegates to the provided service. +3. Random generator methods derive from the `Random` methods on the provided `Crypto` service. +4. `randomUUIDv4` formats bytes from the provided service's `randomBytes` method. +5. `randomUUIDv7` formats bytes from the provided service's `randomBytes` method and the `Clock` timestamp. +6. `digest` delegates to the service. +7. A custom `Crypto` service can be provided via `Effect.provideService`. + +### Node Tests + +Add `packages/platform-node/test/NodeCrypto.test.ts`. + +Test cases: + +1. `randomBytes(0)` returns an empty `Uint8Array`. +2. `randomBytes(32)` returns 32 bytes. +3. Invalid sizes fail with `PlatformError`. +4. Service `randomUUIDv4` returns a valid UUIDv4 string when provided with the Node layer. +5. Service `randomUUIDv7` returns a valid UUIDv7 string with the current `Clock` timestamp when provided with the Node layer. +6. Two UUIDs generated from Node cryptographic random bytes are not equal in a basic smoke test. +7. SHA-256 digest of a known input matches the known vector. + +### Browser Tests + +Add `packages/platform-browser/test/BrowserCrypto.test.ts`. + +Test cases: + +1. `randomBytes` delegates to `getRandomValues` and handles chunking. +2. Service `randomUUIDv4` formats bytes from browser `getRandomValues`. +3. Service `randomUUIDv7` formats bytes from browser `getRandomValues` and the `Clock` timestamp. +4. Missing crypto capabilities fail with `PlatformError`. +5. SHA-256 digest matches a known vector if `crypto.subtle` is available. + +## Generated Files + +After adding modules, run `pnpm codegen`. + +Expected generated barrel updates: + +- `packages/effect/src/index.ts`. +- `packages/platform-node/src/index.ts`. +- `packages/platform-bun/src/index.ts`. +- `packages/platform-browser/src/index.ts`. + +Do not manually edit generated barrel files. + +## Changeset Requirements + +Add a changeset covering at least: + +- `effect`. +- `@effect/platform-node`. +- `@effect/platform-node-shared`. +- `@effect/platform-bun`. +- `@effect/platform-browser`. + +The changeset must state: + +- A new platform-agnostic `Crypto` service was added. +- `Crypto` extends `Random` and exposes cryptographically secure random generator helpers. +- `Crypto` service `randomUUIDv4` formats bytes from the platform `Crypto` service. +- `Crypto` service `randomUUIDv7` formats bytes from the platform `Crypto` service and uses the `Clock` service timestamp. +- `DigestAlgorithm` is represented as a string literal union. +- Users should migrate away from `Random.nextUUIDv4` for UUID generation. + +## Validation Plan + +Run validation in this order: + +1. `pnpm lint-fix`. +2. `pnpm codegen`. +3. `pnpm test packages/effect/test/Crypto.test.ts`. +4. `pnpm test packages/platform-node/test/NodeCrypto.test.ts`. +5. `pnpm test packages/platform-browser/test/BrowserCrypto.test.ts`. +6. `pnpm test packages/effect/test/Random.test.ts`. +7. `pnpm check:tsgo`. +8. If `pnpm check:tsgo` repeatedly fails due to stale caches, run `pnpm clean` and rerun `pnpm check:tsgo`. +9. For localized `effect` package documentation changes, run `cd packages/effect && pnpm docgen`. + +## Acceptance Criteria + +1. `Crypto` is available from `effect/Crypto`. +2. `DigestAlgorithm` is a top-level string literal union. +3. `Crypto` extends the base `Random` service type. +4. Service `randomUUIDv4` derives UUIDs from service `randomBytes(16)` and formats UUIDv4 version/variant bits correctly. +5. The service interface contains `randomUUIDv4` and `randomUUIDv7` as derived methods. +6. `randomBytes` validates sizes and returns cryptographically secure random bytes. +7. `digest` supports SHA-1, SHA-256, SHA-384, and SHA-512 through `DigestAlgorithm` values. +8. The `Crypto` service exposes `random`, `randomBoolean`, `randomInt`, `randomBetween`, `randomIntBetween`, `randomShuffle`, `randomUUIDv4`, and `randomUUIDv7`. +9. Platform failures are represented as `PlatformError` values. +10. Core and platform tests pass. +11. JSDoc examples compile with docgen. +12. Generated barrel files are regenerated with `pnpm codegen`. +13. A changeset documents the new service and UUID migration guidance. + +## Risks and Mitigations + +1. Risk: Formatting UUIDv4 in the core module could set version or variant bits incorrectly. + - Mitigation: Add tests with deterministic bytes and validate the UUIDv4 shape. +2. Risk: Browser `getRandomValues` is unavailable in some environments. + - Mitigation: Fail with a structured `PlatformError` for effectful operations. +3. Risk: `DigestAlgorithm` string inputs invite typos or inconsistent algorithm spelling. + - Mitigation: Use a top-level string literal union with Web Crypto algorithm names and centralize platform mapping. +4. Risk: Existing users rely on deterministic UUIDs from `Random.nextUUIDv4` in tests. + - Mitigation: Document that deterministic IDs should be provided through a fake `Crypto` service or an explicit test service. + +## Open Questions + +None. The user clarified that `Random.nextUUIDv4` should be removed and the initial crypto surface should remain limited. PR review clarified that `DigestAlgorithm` should be a string literal union and that random helpers should live on the service interface. diff --git a/packages/ai/anthropic/src/index.ts b/packages/ai/anthropic/src/index.ts index c4bd0be5b2..23e2a21150 100644 --- a/packages/ai/anthropic/src/index.ts +++ b/packages/ai/anthropic/src/index.ts @@ -15,6 +15,24 @@ export * as AnthropicClient from "./AnthropicClient.ts" /** + * The `AnthropicConfig` module provides contextual configuration for the + * Anthropic AI provider integration. It is used to customize the generated + * Anthropic HTTP client without changing individual request code. + * + * **Common tasks** + * + * - Provide a shared `HttpClient` transformation for Anthropic requests + * - Add provider-specific concerns such as request instrumentation, proxying, + * retries, or header manipulation + * - Scope a client transformation to a single effect with {@link withClientTransform} + * + * **Gotchas** + * + * - Configuration is read from the Effect context, so overrides only apply to + * effects run inside the configured scope + * - `withClientTransform` replaces the current `transformClient` value while + * preserving any other Anthropic configuration fields + * * @since 4.0.0 */ export * as AnthropicConfig from "./AnthropicConfig.ts" @@ -30,6 +48,32 @@ export * as AnthropicConfig from "./AnthropicConfig.ts" export * as AnthropicError from "./AnthropicError.ts" /** + * The `AnthropicLanguageModel` module provides the Anthropic implementation of + * Effect AI's `LanguageModel` service. It turns Effect AI prompts, tools, files, + * reasoning parts, and provider options into Anthropic Messages API requests, + * and converts Anthropic responses and streams back into Effect AI response + * parts with Anthropic-specific metadata. + * + * **Common tasks** + * + * - Create an Anthropic-backed model with {@link model} + * - Build or provide a `LanguageModel.LanguageModel` layer with {@link layer} + * or {@link make} + * - Supply default request options through {@link Config} + * - Override configuration for a scoped operation with {@link withConfigOverride} + * - Attach Anthropic provider options for prompt caching, document citations, + * reasoning signatures, MCP metadata, and server-side tools + * + * **Gotchas** + * + * - Prompt files are translated to Anthropic image or document blocks; only the + * supported media types can be sent to the provider. + * - Structured output support depends on the selected Claude model, so this + * module may use Anthropic's native structured output or fall back to a JSON + * response tool. + * - Some features require Anthropic beta headers, which are added + * automatically from the selected tools, files, and model capabilities. + * * @since 4.0.0 */ export * as AnthropicLanguageModel from "./AnthropicLanguageModel.ts" diff --git a/packages/ai/openai-compat/src/index.ts b/packages/ai/openai-compat/src/index.ts index 794e9b66bc..33f715b744 100644 --- a/packages/ai/openai-compat/src/index.ts +++ b/packages/ai/openai-compat/src/index.ts @@ -5,11 +5,54 @@ // @barrel: Auto-generated exports. Do not edit manually. /** + * The `OpenAiClient` module provides an Effect service for calling + * OpenAI-compatible chat completions and embeddings APIs. It builds on the + * Effect HTTP client, adds authentication and OpenAI header handling, and + * exposes typed helpers for regular responses, server-sent event streaming, and + * embedding requests. + * + * **Common tasks** + * + * - Create a client service directly with {@link make} + * - Provide the service as a layer with {@link layer} or {@link layerConfig} + * - Send non-streaming chat completion requests with `createResponse` + * - Send streaming chat completion requests with `createResponseStream` + * - Generate embeddings with `createEmbedding` + * - Reuse the exported request and response types when integrating compatible providers + * + * **Gotchas** + * + * - The default base URL is `https://api.openai.com/v1`; set `apiUrl` for other + * OpenAI-compatible providers. + * - `createResponseStream` forces `stream: true` and requests usage events with + * `stream_options.include_usage`. + * - HTTP and schema decoding failures are mapped into `AiError`. + * * @since 4.0.0 */ export * as OpenAiClient from "./OpenAiClient.ts" /** + * The `OpenAiConfig` module provides shared configuration for clients that + * talk to OpenAI-compatible APIs. It is used to customize the HTTP client + * wiring around a provider without changing the higher-level model, + * embeddings, or tool-calling APIs that consume the client. + * + * **Common tasks** + * + * - Install a client transform with {@link withClientTransform} + * - Add provider-specific HTTP behavior, such as headers, retries, proxies, or + * instrumentation + * - Read the active configuration from the Effect context when implementing + * OpenAI-compatible integrations + * + * **Gotchas** + * + * - The transform receives and returns an `HttpClient`, so it should preserve + * the existing client behavior unless it intentionally replaces it + * - Configuration is provided through Effect context and is scoped to the + * effect that receives the service + * * @since 4.0.0 */ export * as OpenAiConfig from "./OpenAiConfig.ts" @@ -24,6 +67,19 @@ export * as OpenAiConfig from "./OpenAiConfig.ts" export * as OpenAiEmbeddingModel from "./OpenAiEmbeddingModel.ts" /** + * The `OpenAiError` module defines OpenAI-specific metadata that can be + * attached to the shared `AiError` error types used by the AI packages. It is + * primarily used by OpenAI-compatible clients to preserve provider details + * such as error codes, error types, request IDs, and rate limit headers while + * still exposing errors through the provider-neutral Effect AI error model. + * + * Use this module when mapping OpenAI API failures into `AiError` values and + * when consumers need enough structured metadata to debug failed requests, + * inspect quota or rate limit responses, or correlate an error with OpenAI + * support. The exported types are metadata shapes only; the module augmentation + * makes those shapes available on the corresponding shared AI error metadata + * interfaces without defining new runtime error classes. + * * @since 4.0.0 */ export * as OpenAiError from "./OpenAiError.ts" diff --git a/packages/ai/openai/src/index.ts b/packages/ai/openai/src/index.ts index be39a81121..0b0b758cff 100644 --- a/packages/ai/openai/src/index.ts +++ b/packages/ai/openai/src/index.ts @@ -25,6 +25,22 @@ export * as OpenAiClient from "./OpenAiClient.ts" export * as OpenAiClientGenerated from "./OpenAiClientGenerated.ts" /** + * The `OpenAiConfig` module provides contextual configuration for the + * `@effect/ai-openai` integration. It is used to customize how OpenAI clients + * are built and interpreted without threading configuration through every API + * call manually. + * + * The primary use case is installing an HTTP client transform with + * {@link withClientTransform}. This lets applications adapt the underlying + * OpenAI HTTP client for cross-cutting concerns such as custom middleware, + * instrumentation, proxying, or request policy changes while keeping the + * OpenAI service APIs unchanged. + * + * Configuration is scoped through Effect's context, so transforms only apply to + * the effect they are provided to and anything evaluated inside that scope. + * When multiple transforms are needed, compose them into a single + * `HttpClient => HttpClient` function before providing the configuration. + * * @since 4.0.0 */ export * as OpenAiConfig from "./OpenAiConfig.ts" diff --git a/packages/ai/openrouter/src/index.ts b/packages/ai/openrouter/src/index.ts index cbac6769f5..6b06b47d93 100644 --- a/packages/ai/openrouter/src/index.ts +++ b/packages/ai/openrouter/src/index.ts @@ -10,11 +10,51 @@ export * as Generated from "./Generated.ts" /** + * The `OpenRouterClient` module provides an Effect service for calling + * OpenRouter's chat completions API. It wraps the generated OpenRouter HTTP + * client with Effect-native constructors, layers, typed errors, and streaming + * support. + * + * **Common tasks** + * + * - Build a client from explicit options with {@link make} + * - Provide the client to an application with {@link layer} or {@link layerConfig} + * - Create non-streaming chat completions with {@link Service.createChatCompletion} + * - Create server-sent event chat completion streams with + * {@link Service.createChatCompletionStream} + * - Customize authentication, base URL, OpenRouter ranking headers, or the + * underlying HTTP client through {@link Options} + * + * **Gotchas** + * + * - Streaming requests are sent directly to `/chat/completions` with `stream` + * and `stream_options.include_usage` enabled by this module. + * - OpenRouter API failures, HTTP client failures, and schema decoding failures + * are mapped into `AiError` values for the exported service methods. + * * @since 4.0.0 */ export * as OpenRouterClient from "./OpenRouterClient.ts" /** + * The `OpenRouterConfig` module provides contextual configuration for the + * OpenRouter provider integration. It is used to customize the HTTP client that + * backs OpenRouter requests without rebuilding the provider layer itself. + * + * Use {@link withClientTransform} when a single effect, workflow, or scoped + * portion of an application needs to add cross-cutting HTTP client behavior + * such as request logging, retries, proxy routing, additional headers, or test + * doubles. The configuration is read from the current Effect context, so the + * transform only applies where the returned effect is run with that context. + * + * **Gotchas** + * + * - Each call to {@link withClientTransform} replaces the current client + * transform for the provided effect; compose transforms manually when both + * behaviors should apply. + * - The transform receives and returns an `HttpClient`, so it should preserve + * the OpenRouter client contract while adding behavior around it. + * * @since 4.0.0 */ export * as OpenRouterConfig from "./OpenRouterConfig.ts" @@ -30,6 +70,26 @@ export * as OpenRouterConfig from "./OpenRouterConfig.ts" export * as OpenRouterError from "./OpenRouterError.ts" /** + * The `OpenRouterLanguageModel` module provides constructors for using + * OpenRouter chat completion models through the Effect AI `LanguageModel` + * interface. It adapts Effect prompts, tools, structured output schemas, file + * parts, reasoning details, cache-control hints, and telemetry annotations into + * the OpenRouter request and response formats. + * + * Use this module when an application wants to select an OpenRouter model by + * name while keeping the rest of its AI workflow provider-agnostic. The + * exported layer and model constructors install a `LanguageModel` service backed + * by `OpenRouterClient`, and `withConfigOverride` can scope per-request + * OpenRouter options such as sampling, routing, tool use, or JSON schema + * behavior. + * + * OpenRouter routes requests to many underlying providers, so model support for + * images, files, tools, structured outputs, caching, and reasoning metadata can + * vary. Provider-specific prompt and response metadata is preserved under the + * `openrouter` option namespace so multi-turn conversations can round-trip + * details such as reasoning blocks and file annotations when the selected model + * supports them. + * * @since 4.0.0 */ export * as OpenRouterLanguageModel from "./OpenRouterLanguageModel.ts" diff --git a/packages/effect/src/Crypto.ts b/packages/effect/src/Crypto.ts new file mode 100644 index 0000000000..18bb477e75 --- /dev/null +++ b/packages/effect/src/Crypto.ts @@ -0,0 +1,327 @@ +/** + * The `Crypto` module provides a platform-agnostic service for cryptographic + * operations. Runtime packages such as `@effect/platform-node`, + * `@effect/platform-bun`, and `@effect/platform-browser` provide concrete + * implementations backed by the host platform's cryptography APIs. + * + * Use `Crypto` for cryptographic randomness, UUID generation, random values, + * and message digests. The base `Random` service is not cryptographically + * secure unless you replace it with a cryptographically secure implementation. + * + * **Example** (Providing a test Crypto service) + * + * ```ts + * import { Console, Crypto, Effect, Layer } from "effect" + * + * const TestCrypto = Layer.succeed( + * Crypto.Crypto, + * Crypto.make({ + * randomBytes: (size) => new Uint8Array(size), + * digest: (_algorithm, data) => Effect.succeed(data) + * }) + * ) + * + * const program = Effect.gen(function*() { + * const crypto = yield* Crypto.Crypto + * const id = yield* crypto.randomUUIDv4 + * yield* Console.log(`Created id: ${id}`) + * }) + * + * Effect.runPromise(Effect.provide(program, TestCrypto)) + * ``` + * + * **Example** (Generating random bytes) + * + * ```ts + * import { Crypto, Effect, Layer } from "effect" + * + * const TestCrypto = Layer.succeed( + * Crypto.Crypto, + * Crypto.make({ + * randomBytes: (size) => new Uint8Array(size), + * digest: (_algorithm, data) => Effect.succeed(data) + * }) + * ) + * + * const program = Effect.gen(function*() { + * const crypto = yield* Crypto.Crypto + * return yield* crypto.randomBytes(32) + * }) + * + * Effect.runPromise(Effect.provide(program, TestCrypto)) + * ``` + * + * @since 4.0.0 + */ +import * as Context from "./Context.ts" +import * as Effect from "./Effect.ts" +import * as PlatformError from "./PlatformError.ts" + +const TypeId = "~effect/platform/Crypto" + +/** + * Digest algorithms supported by the platform `Crypto` service. + * + * SHA-1 is included for interoperability with existing protocols. Do not use + * SHA-1 for new security-sensitive designs. + * + * **Example** (Using a digest algorithm) + * + * ```ts + * import { Crypto } from "effect" + * + * const algorithm: Crypto.DigestAlgorithm = "SHA-256" + * ``` + * + * @category models + * @since 4.0.0 + */ +export type DigestAlgorithm = "SHA-1" | "SHA-256" | "SHA-384" | "SHA-512" + +/** + * Platform-agnostic cryptographic operations. + * + * `Crypto` implementations must use cryptographically secure platform APIs. + * The random generator helpers are derived by the `make` constructor from + * the random methods on this service. + * + * **Example** (Using cryptographic operations) + * + * ```ts + * import { Crypto, Effect, Layer } from "effect" + * + * const TestCrypto = Layer.succeed( + * Crypto.Crypto, + * Crypto.make({ + * randomBytes: (size) => new Uint8Array(size), + * digest: (_algorithm, data) => Effect.succeed(data) + * }) + * ) + * + * const program = Effect.gen(function*() { + * const crypto = yield* Crypto.Crypto + * const bytes = yield* crypto.randomBytes(16) + * const uuidv4 = yield* crypto.randomUUIDv4 + * const uuidv7 = yield* crypto.randomUUIDv7 + * const hash = yield* crypto.digest("SHA-256", bytes) + * return { uuidv4, uuidv7, hash } + * }) + * + * Effect.runPromise(Effect.provide(program, TestCrypto)) + * ``` + * + * @category models + * @since 4.0.0 + */ +export interface Crypto { + readonly [TypeId]: typeof TypeId + + /** + * Generates a random integer in the range Number.MIN_SAFE_INTEGER to + * Number.MAX_SAFE_INTEGER (both inclusive). + */ + nextIntUnsafe(): number + + /** + * Generates a random number in the range 0 (inclusive) to 1 (exclusive). + */ + nextDoubleUnsafe(): number + + /** + * Generates cryptographically secure random bytes. + */ + randomBytes(size: number): Effect.Effect + + /** + * Computes a cryptographic digest for the supplied data. + */ + digest( + algorithm: DigestAlgorithm, + data: Uint8Array + ): Effect.Effect + + /** + * Generates a cryptographically secure random number between 0 (inclusive) + * and 1 (exclusive). + */ + readonly random: Effect.Effect + + /** + * Generates a cryptographically secure random boolean. + */ + readonly randomBoolean: Effect.Effect + + /** + * Generates a cryptographically secure random integer between + * `Number.MIN_SAFE_INTEGER` and `Number.MAX_SAFE_INTEGER` (both inclusive). + */ + readonly randomInt: Effect.Effect + + /** + * Generates a cryptographically secure random number between `min` + * (inclusive) and `max` (exclusive). + */ + randomBetween(min: number, max: number): Effect.Effect + + /** + * Generates a cryptographically secure random integer between `min` and `max`. + * + * The lower bound is rounded up with `Math.ceil` and the upper bound is + * rounded down with `Math.floor`. By default the range is inclusive; set + * `options.halfOpen: true` to exclude the upper bound. + */ + randomIntBetween(min: number, max: number, options?: { + readonly halfOpen?: boolean | undefined + }): Effect.Effect + + /** + * Uses the cryptographically secure random generator to shuffle the supplied + * iterable. + */ + randomShuffle(elements: Iterable): Effect.Effect> + + /** + * Generates a cryptographically secure UUIDv4 string. + */ + readonly randomUUIDv4: Effect.Effect + + /** + * Generates a cryptographically secure UUIDv7 string. + */ + readonly randomUUIDv7: Effect.Effect +} + +/** + * The service identifier for the platform `Crypto` service. + * + * @category services + * @since 4.0.0 + */ +export const Crypto: Context.Service = Context.Service("effect/Crypto") + +/** + * Creates a `Crypto` service from the primitive implementation, deriving the + * random generator helpers and UUID generation from those primitives. + * + * **Example** (Creating a Crypto service) + * + * ```ts + * import { Crypto, Effect, Layer } from "effect" + * + * const TestCrypto = Layer.succeed( + * Crypto.Crypto, + * Crypto.make({ + * randomBytes: (size) => new Uint8Array(size), + * digest: (_algorithm, data) => Effect.succeed(data) + * }) + * ) + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const make = ( + impl: { + readonly randomBytes: (size: number) => Uint8Array + readonly digest: ( + algorithm: DigestAlgorithm, + data: Uint8Array + ) => Effect.Effect + } +): Crypto => { + const randomBytesUnsafe = impl.randomBytes + + const randomBytes: Crypto["randomBytes"] = (size) => Effect.map(validateSize("randomBytes", size), randomBytesUnsafe) + + const nextDoubleUnsafe = (): number => { + const bytes = randomBytesUnsafe(7) + const value = ((bytes[0] & 0x1f) * 2 ** 48) + (bytes[1] * 2 ** 40) + (bytes[2] * 2 ** 32) + + (bytes[3] * 2 ** 24) + (bytes[4] * 2 ** 16) + (bytes[5] * 2 ** 8) + bytes[6] + return value / 2 ** 53 + } + + const nextIntUnsafe = (): number => + Math.floor(nextDoubleUnsafe() * (Number.MAX_SAFE_INTEGER - Number.MIN_SAFE_INTEGER + 1)) + Number.MIN_SAFE_INTEGER + + return Crypto.of({ + [TypeId]: TypeId, + randomBytes, + nextDoubleUnsafe, + nextIntUnsafe, + digest: impl.digest, + random: Effect.sync(() => nextDoubleUnsafe()), + randomBoolean: Effect.sync(() => nextDoubleUnsafe() > 0.5), + randomInt: Effect.sync(() => nextIntUnsafe()), + randomBetween: (min, max) => Effect.sync(() => nextDoubleUnsafe() * (max - min) + min), + randomIntBetween(min, max, options) { + const extra = options?.halfOpen === true ? 0 : 1 + return Effect.sync(() => { + const minInt = Math.ceil(min) + const maxInt = Math.floor(max) + return Math.floor(nextDoubleUnsafe() * (maxInt - minInt + extra)) + minInt + }) + }, + randomShuffle: (elements) => + Effect.sync(() => { + const buffer = Array.from(elements) + for (let i = buffer.length - 1; i >= 1; i = i - 1) { + const index = Math.min(i, Math.floor(nextDoubleUnsafe() * (i + 1))) + const value = buffer[i]! + buffer[i] = buffer[index]! + buffer[index] = value + } + return buffer + }), + randomUUIDv4: Effect.sync(() => formatUUIDv4(randomBytesUnsafe(16))), + randomUUIDv7: Effect.clockWith((clock) => + Effect.succeed(formatUUIDv7(clock.currentTimeMillisUnsafe(), randomBytesUnsafe(16))) + ) + }) +} + +const validateSize = (method: string, size: number): Effect.Effect => + Number.isSafeInteger(size) && size >= 0 + ? Effect.succeed(size) + : Effect.fail(PlatformError.badArgument({ + module: "Crypto", + method, + description: "size must be a non-negative safe integer" + })) + +const hex = (byte: number): string => byte.toString(16).padStart(2, "0") + +const formatUUID = (bytes: Uint8Array): string => { + const segments = [ + bytes.subarray(0, 4), + bytes.subarray(4, 6), + bytes.subarray(6, 8), + bytes.subarray(8, 10), + bytes.subarray(10, 16) + ] + + return segments.map((segment) => Array.from(segment, hex).join("")).join("-") +} + +const formatUUIDv4 = (bytes: Uint8Array): string => { + bytes[6] = (bytes[6] & 0x0f) | 0x40 + bytes[8] = (bytes[8] & 0x3f) | 0x80 + + return formatUUID(bytes) +} + +const maxUUIDv7Timestamp = 2 ** 48 - 1 + +const formatUUIDv7 = (timestampMillis: number, bytes: Uint8Array): string => { + const timestamp = Math.min(Math.max(0, Math.trunc(timestampMillis)), maxUUIDv7Timestamp) + + bytes[0] = Math.floor(timestamp / 2 ** 40) + bytes[1] = Math.floor(timestamp / 2 ** 32) & 0xff + bytes[2] = Math.floor(timestamp / 2 ** 24) & 0xff + bytes[3] = Math.floor(timestamp / 2 ** 16) & 0xff + bytes[4] = Math.floor(timestamp / 2 ** 8) & 0xff + bytes[5] = timestamp & 0xff + bytes[6] = (bytes[6] & 0x0f) | 0x70 + bytes[8] = (bytes[8] & 0x3f) | 0x80 + + return formatUUID(bytes) +} diff --git a/packages/effect/src/Random.ts b/packages/effect/src/Random.ts index cd9ad7c626..c16637fb4c 100644 --- a/packages/effect/src/Random.ts +++ b/packages/effect/src/Random.ts @@ -1,7 +1,15 @@ /** - * The Random module provides a service for generating random numbers in Effect - * programs. It offers a testable and composable way to work with randomness, - * supporting integers, floating-point numbers, and range-based generation. + * The `Random` module provides a service for generating pseudo-random numbers + * in Effect programs. It offers a testable and composable way to work with + * randomness, supporting integers, floating-point numbers, and range-based + * generation. + * + * The default `Random` service is not cryptographically secure. Do not use it + * for secrets, tokens, UUIDs, session identifiers, or other security-sensitive + * values. For cryptographically secure random generation, replace the service + * with a cryptographically secure implementation such as the platform `Crypto` + * service. `Random.withSeed` also replaces the service, but predictable seeds + * remain deterministic and must not be treated as cryptographically secure. * * **Example** (Generating random values) * @@ -29,7 +37,11 @@ import * as random from "./internal/random.ts" import * as Predicate from "./Predicate.ts" /** - * Represents a service for generating random numbers. + * Represents a service for generating pseudo-random numbers. + * + * The default implementation is based on `Math.random` and is not + * cryptographically secure. Replace the service with a cryptographically secure + * implementation before using these generators for security-sensitive values. * * **Example** (Accessing the random service) * @@ -40,12 +52,10 @@ import * as Predicate from "./Predicate.ts" * const float = yield* Random.next * const integer = yield* Random.nextInt * const inRange = yield* Random.nextIntBetween(1, 100) - * const uuid = yield* Random.nextUUIDv4 * * console.log("Float:", float) * console.log("Integer:", integer) * console.log("In range:", inRange) - * console.log("UUID:", uuid) * }) * ``` * @@ -61,7 +71,7 @@ const randomWith = (f: (random: typeof Random["Service"]) => A): Effect.Effec Effect.withFiber((fiber) => Effect.succeed(f(fiber.getRef(Random)))) /** - * Generates a random number between 0 (inclusive) and 1 (inclusive). + * Generates a random number between 0 (inclusive) and 1 (exclusive). * * **Example** (Generating a random number) * @@ -119,7 +129,7 @@ export const nextBoolean: Effect.Effect = randomWith((r) => r.nextDoubl export const nextInt: Effect.Effect = randomWith((r) => r.nextIntUnsafe()) /** - * Generates a random number between `min` (inclusive) and `max` (inclusive). + * Generates a random number between `min` (inclusive) and `max` (exclusive). * * **Example** (Generating a bounded random number) * @@ -203,51 +213,7 @@ export const shuffle = (elements: Iterable): Effect.Effect> => }) /** - * Generates a random UUID (v4) string. - * - * **Example** (Generating a UUID) - * - * ```ts - * import { Effect, Random } from "effect" - * - * const program = Effect.gen(function*() { - * const uuid = yield* Random.nextUUIDv4 - * console.log("UUID:", uuid) - * }) - * ``` - * - * @category Random Number Generators - * @since 4.0.0 - */ -export const nextUUIDv4: Effect.Effect = randomWith((r) => { - // Generate 16 random bytes (128 bits) for UUID - const bytes: Array = [] - for (let i = 0; i < 16; i++) { - // Get unsigned byte [0, 255] from nextInt (signed 32-bit) - bytes.push((r.nextIntUnsafe() >>> 0) & 0xFF) - } - - // Set version to 4 (bits 12-15 of time_hi_and_version) - bytes[6] = (bytes[6] & 0x0F) | 0x40 - - // Set variant to RFC 4122 (bits 6-7 of clock_seq_hi_and_reserved) - bytes[8] = (bytes[8] & 0x3F) | 0x80 - - // Format as UUID string: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx - const hex = (n: number) => n.toString(16).padStart(2, "0") - - return [ - bytes.slice(0, 4).map(hex).join(""), - bytes.slice(4, 6).map(hex).join(""), - bytes.slice(6, 8).map(hex).join(""), - bytes.slice(8, 10).map(hex).join(""), - bytes.slice(10, 16).map(hex).join("") - ].join("-") -}) - -/** - * Runs an effect with a pseudorandom number generator initialized from the - * specified seed. + * Seeds the pseudo-random number generator with the specified value. * * Using the same seed produces the same random sequence, which is useful for * tests and reproducible simulations. Use an unpredictable seed when uniqueness @@ -488,7 +454,7 @@ function ISAAC_CSPRNG(userSeed?: string | number) { } /** - * Returns a signed, random integer in the range [-2^31, 2^31]. + * Returns a signed, random integer in the range [-2^31, 2^31). */ function nextInt32(): number { if (!generation--) { @@ -499,15 +465,8 @@ function ISAAC_CSPRNG(userSeed?: string | number) { } function nextIntUnsafe(): number { - // Get 32 bits (unsigned) - const low = nextInt32() >>> 0 // [0, 2^32-1] - - // Get 21 more bits for a total of 53 - const high = nextInt32() & 0x1FFFFF // [0, 2^21-1] - - // Combine: high bits * 2^32 + low bits, then shift to signed range - // This gives [0, 2^53-1], subtract 2^52 to center around 0 - return (high * 0x100000000) + low - 0x10000000000000 + return Math.floor(nextDoubleUnsafe() * (Number.MAX_SAFE_INTEGER - Number.MIN_SAFE_INTEGER + 1)) + + Number.MIN_SAFE_INTEGER } /** @@ -520,7 +479,7 @@ function ISAAC_CSPRNG(userSeed?: string | number) { // 53-bit integer const combined = hi * 4294967296 + lo - return combined / 9007199254740991 // [0, 1) + return combined / 0x20000000000000 // [0, 1) } return { nextIntUnsafe, nextDoubleUnsafe } diff --git a/packages/effect/src/index.ts b/packages/effect/src/index.ts index 5337cda7cd..cb7cf191e8 100644 --- a/packages/effect/src/index.ts +++ b/packages/effect/src/index.ts @@ -161,6 +161,41 @@ export * as Boolean from "./Boolean.ts" export * as Brand from "./Brand.ts" /** + * The `Cache` module provides an effectful, mutable key-value cache for values + * that are computed by a lookup function. A `Cache` stores lookup + * results for keys, shares concurrent lookups for the same key, and manages + * entry lifetime with capacity limits and optional time-to-live policies. + * + * **Mental model** + * + * - A cache is created from a lookup function and a maximum capacity + * - {@link get} returns a cached value when present, or runs the lookup on a miss + * - Concurrent misses for the same key share one pending lookup + * - Lookup failures are cached as failures until the entry expires, is invalidated, or is refreshed + * - Entries can live forever, expire after a fixed duration, or use a dynamic TTL based on the lookup `Exit` + * - Capacity is enforced by removing the oldest stored entries when new entries are added + * + * **Common tasks** + * + * - Create a cache: {@link make}, {@link makeWith} + * - Read values: {@link get}, {@link getOption}, {@link getSuccess} + * - Seed or overwrite values: {@link set} + * - Refresh values: {@link refresh} + * - Remove entries: {@link invalidate}, {@link invalidateWhen}, {@link invalidateAll} + * - Inspect contents: {@link has}, {@link size}, {@link keys}, {@link values}, {@link entries} + * + * **Gotchas** + * + * - {@link getOption} does not run the lookup; it only reads an existing non-expired entry + * - {@link size} may include expired entries until they are observed and removed + * - {@link values} and {@link entries} include only successfully resolved entries + * - Use `Data` or another `Equal`-compatible key type when keys need structural equality + * + * **See also** + * + * - {@link Duration} for configuring fixed or dynamic time-to-live values + * - {@link Effect} for the lookup effects used to compute cached values + * * @since 4.0.0 */ export * as Cache from "./Cache.ts" @@ -309,6 +344,48 @@ export * as Cause from "./Cause.ts" export * as Channel from "./Channel.ts" /** + * The `ChannelSchema` module provides helpers for applying `Schema` encoding + * and decoding at `Channel` boundaries. It is useful when a channel should + * expose typed values to application code while communicating with an upstream + * or downstream component through an encoded representation such as JSON-ready + * data, wire protocol values, or any other schema-defined format. + * + * **Mental model** + * + * - A channel schema adapter is a streaming boundary: chunks flow through a + * `Channel`, and each non-empty chunk is validated and transformed with a + * `Schema` + * - `encode` turns typed schema values into their encoded representation before + * they leave a typed part of a pipeline + * - `decode` turns encoded input into typed schema values before application + * code consumes them + * - `duplex` wraps a bidirectional channel so callers work with typed input and + * output while the wrapped channel continues to operate on encoded chunks + * - Schema failures are surfaced through the channel error type as + * `SchemaError`, and schema services are reflected in the channel + * requirements + * + * **Common tasks** + * + * - Encode typed channel input before sending it to an encoded transport: + * {@link encode} + * - Decode encoded channel output before handling it as domain data: + * {@link decode} + * - Use unknown encoded boundaries when static encoded types are intentionally + * erased: {@link encodeUnknown} and {@link decodeUnknown} + * - Wrap a bidirectional encoded channel with typed input and output schemas: + * {@link duplex} or {@link duplexUnknown} + * + * **Gotchas** + * + * - These helpers operate on `NonEmptyReadonlyArray` chunks, so schemas are + * applied to non-empty batches rather than individual scalar values + * - Encoding and decoding can require services from the schema; those + * requirements become part of the resulting channel type + * - `duplex` encodes values flowing into the wrapped channel and decodes values + * emitted by it, so choose `inputSchema` and `outputSchema` from the + * perspective of the typed caller + * * @since 4.0.0 */ export * as ChannelSchema from "./ChannelSchema.ts" @@ -376,12 +453,11 @@ export * as ChannelSchema from "./ChannelSchema.ts" * import { Chunk, Effect } from "effect" * * // Working with Effects - * const processChunk = (chunk: Chunk.Chunk) => - * Effect.gen(function*() { - * const mapped = Chunk.map(chunk, (n) => n * 2) - * const filtered = Chunk.filter(mapped, (n) => n > 5) - * return Chunk.toReadonlyArray(filtered) - * }) + * const processChunk = Effect.fnUntraced(function*(chunk: Chunk.Chunk) { + * const mapped = Chunk.map(chunk, (n) => n * 2) + * const filtered = Chunk.filter(mapped, (n) => n > 5) + * return Chunk.toReadonlyArray(filtered) + * }) * ``` * * @since 2.0.0 @@ -762,10 +838,107 @@ export * as Console from "./Console.ts" export * as Context from "./Context.ts" /** + * The `Cron` module provides utilities for representing recurring calendar + * schedules with cron expressions. A `Cron` value stores allowed seconds, + * minutes, hours, days of month, months, weekdays, and an optional time zone, + * then uses those constraints to test dates and find scheduled occurrences. + * + * **Mental model** + * + * - A cron schedule is a set of allowed values for each time field + * - Expressions may use five fields (`minute hour day month weekday`) or six + * fields (`second minute hour day month weekday`); five-field expressions + * default seconds to `0` + * - Each field supports `*`, comma-separated values, ranges, and step syntax + * - Month and weekday fields support aliases such as `JAN`, `DEC`, `SUN`, and + * `MON` + * - Empty internal field sets represent an unconstrained field, the same idea + * as `*` + * - When both day-of-month and weekday are constrained, matching uses cron's + * inclusive behavior: either field may match + * + * **Common tasks** + * + * - Build directly from field constraints: {@link make} + * - Parse expressions safely: {@link parse} + * - Parse expressions and throw on invalid input: {@link parseUnsafe} + * - Check whether a date satisfies a schedule: {@link match} + * - Find adjacent scheduled dates: {@link next}, {@link prev} + * - Iterate future scheduled dates: {@link sequence} + * - Compare schedule constraints: {@link equals}, {@link Equivalence} + * - Detect parse failures: {@link CronParseError}, {@link isCronParseError} + * + * **Gotchas** + * + * - Weekdays are numbered `0` through `6`, with `0` representing Sunday + * - Months are numbered `1` through `12`, while JavaScript `Date` months are + * zero-based + * - `*` normalizes to an empty set internally, so inspect schedules with the + * public helpers instead of assuming every allowed value is stored + * - `next` and `prev` search strictly after or before the provided instant + * - Time-zone-aware schedules account for daylight saving transitions; during + * a fall-back transition, repeated local times are emitted once when moving + * forward + * * @since 2.0.0 */ export * as Cron from "./Cron.ts" +/** + * The `Crypto` module provides a platform-agnostic service for cryptographic + * operations. Runtime packages such as `@effect/platform-node`, + * `@effect/platform-bun`, and `@effect/platform-browser` provide concrete + * implementations backed by the host platform's cryptography APIs. + * + * Use `Crypto` for cryptographic randomness, UUID generation, random values, + * and message digests. The base `Random` service is not cryptographically + * secure unless you replace it with a cryptographically secure implementation. + * + * @example + * ```ts + * import { Console, Crypto, Effect, Layer } from "effect" + * + * const TestCrypto = Layer.succeed( + * Crypto.Crypto, + * Crypto.make({ + * randomBytes: (size) => new Uint8Array(size), + * digest: (_algorithm, data) => Effect.succeed(data) + * }) + * ) + * + * const program = Effect.gen(function*() { + * const crypto = yield* Crypto.Crypto + * const id = yield* crypto.randomUUIDv4 + * yield* Console.log(`Created id: ${id}`) + * }) + * + * Effect.runPromise(Effect.provide(program, TestCrypto)) + * ``` + * + * @example + * ```ts + * import { Crypto, Effect, Layer } from "effect" + * + * const TestCrypto = Layer.succeed( + * Crypto.Crypto, + * Crypto.make({ + * randomBytes: (size) => new Uint8Array(size), + * digest: (_algorithm, data) => Effect.succeed(data) + * }) + * ) + * + * const program = Effect.gen(function*() { + * const crypto = yield* Crypto.Crypto + * return yield* crypto.randomBytes(32) + * }) + * + * Effect.runPromise(Effect.provide(program, TestCrypto)) + * ``` + * + * @since 4.0.0 + */ +export * as Crypto from "./Crypto.ts" + /** * Immutable data constructors with discriminated-union support. * @@ -842,6 +1015,55 @@ export * as Cron from "./Cron.ts" export * as Data from "./Data.ts" /** + * The `DateTime` module provides immutable data types and utilities for working + * with instants, UTC date-times, zoned date-times, and time zones. A + * `DateTime` is always an absolute point in time, represented internally by + * epoch milliseconds, and may also carry a `TimeZone` for zone-aware calendar + * parts and formatting. + * + * **Mental model** + * + * - `DateTime` is a discriminated union: `Utc | Zoned` + * - `Utc` stores an absolute instant without an associated time zone + * - `Zoned` stores the same kind of absolute instant plus a `TimeZone` + * - Time zones can be fixed offsets or named IANA zones such as `"Europe/Rome"` + * - Comparison and ordering use the instant, so two values in different zones + * can still be equivalent + * - Calendar parts and formatted output depend on whether you ask for UTC parts + * or zone-adjusted parts + * + * **Common tasks** + * + * - Construct values: {@link make}, {@link makeUnsafe}, {@link makeZoned}, {@link makeZonedUnsafe} + * - Get the current instant: {@link now}, {@link nowInCurrentZone} + * - Create time zones: {@link zoneMakeOffset}, {@link zoneMakeNamed}, {@link zoneFromString} + * - Attach or change zones: {@link setZone}, {@link setZoneNamed}, {@link setZoneCurrent}, {@link toUtc} + * - Convert to platform values or parts: {@link toDate}, {@link toDateUtc}, {@link toEpochMillis}, {@link toParts}, {@link toPartsUtc} + * - Compare and bound values: {@link Equivalence}, {@link Order}, {@link distance}, {@link min}, {@link max}, {@link clamp}, {@link between} + * - Transform values: {@link add}, {@link subtract}, {@link startOf}, {@link endOf}, {@link nearest}, {@link setParts}, {@link mutate} + * - Format values: {@link format}, {@link formatUtc}, {@link formatLocal}, {@link formatIntl}, {@link formatIso}, {@link formatIsoZoned} + * - Provide an application time zone: {@link CurrentTimeZone}, {@link withCurrentZone}, {@link layerCurrentZone} + * + * **Gotchas** + * + * - `make` and `makeZoned` return `Option`; unsafe constructors throw on invalid + * input + * - `DateTime` equality is instant-based, not display-time-based + * - `setZone` changes the zone used for local parts and formatting without + * changing the represented instant + * - Use `adjustForTimeZone` with {@link makeZoned} when input parts should be + * interpreted as wall-clock time in the target zone + * - Daylight-saving gaps and repeated local times are resolved with + * `Disambiguation` + * - Prefer the Clock-backed {@link now} and `CurrentTimeZone` services in + * Effect workflows; unsafe helpers read from the host environment directly + * + * **See also** + * + * - {@link DateTime} for the UTC/zoned data model + * - {@link TimeZone} for offset and named time-zone values + * - {@link Disambiguation} for daylight-saving ambiguity handling + * * @since 3.6.0 */ export * as DateTime from "./DateTime.ts" @@ -918,6 +1140,42 @@ export * as DateTime from "./DateTime.ts" export * as Deferred from "./Deferred.ts" /** + * The `Differ` module defines the core abstraction for describing changes to a + * value. A `Differ` knows how to compare two `T` values, produce a + * patch that represents the difference, combine multiple patches, and apply a + * patch to an old value to obtain the updated value. + * + * **Mental model** + * + * - A differ separates "what changed" from "the value after the change" + * - `diff(oldValue, newValue)` produces a `Patch` that can later be applied + * - `patch(oldValue, patch)` replays a patch against a value of the same domain + * - `empty` is the identity patch: applying it should leave the value unchanged + * - `combine(first, second)` composes patches in sequence, where `second` + * represents changes that happen after `first` + * - Patch types are chosen by the differ implementation and may be compact, + * domain-specific, or compatible with a serialization format such as JSON + * Patch + * + * **Common tasks** + * + * - Construct a differ by providing the four operations of the {@link Differ} + * interface + * - Compute a patch with `diff` when you have an old value and a new value + * - Store, transmit, or aggregate patches instead of storing full replacement + * values + * - Combine incremental updates with `combine` before applying them + * - Apply updates with `patch` to reconstruct the next value from a previous + * value and a patch + * + * **Gotchas** + * + * - `combine` is order-sensitive for most patch formats + * - A patch is generally meaningful only for values that belong to the same + * domain and assumptions used by the differ that created it + * - Differs should make `empty` a true identity and should make combined + * patches behave the same as applying the original patches in order + * * @since 4.0.0 */ export * as Differ from "./Differ.ts" @@ -1014,6 +1272,41 @@ export * as Duration from "./Duration.ts" export * as Effect from "./Effect.ts" /** + * The `Effectable` module provides low-level building blocks for defining + * custom values that behave like `Effect`s. It is primarily used by library + * authors who need domain-specific effect-like data types, such as service + * keys, configuration descriptions, prompts, or other declarative programs + * that can be yielded inside `Effect.gen`. + * + * **Mental model** + * + * - `Effectable` does not run effects by itself; it provides prototypes that + * implement the internal Effect protocol. + * - {@link Prototype} creates a primitive Effect prototype with a custom + * evaluation function that receives the current `Fiber`. + * - {@link Class} is an abstract base class for defining custom classes whose + * instances are also `Effect` values. + * - The success, error, and service requirements of the custom type are + * preserved through the `Effect.Effect` type parameters. + * + * **Common tasks** + * + * - Build an effect-like interface around a declarative data structure. + * - Implement a custom `evaluate` hook that interprets the value in terms of + * the current fiber and returns the underlying `Effect`. + * - Extend {@link Class} when a nominal class-based API is more convenient + * than manually wiring a prototype. + * + * **Gotchas** + * + * - This module is intentionally low-level; most application code should use + * `Effect` constructors and combinators instead. + * - `evaluate` must return an `Effect` with the same success, error, and + * service types as the custom value. + * - Because these APIs participate in the internal Effect protocol, keep + * implementations small and follow existing modules such as `Config` and + * `Context` when adding new effect-like types. + * * @since 4.0.0 */ export * as Effectable from "./Effectable.ts" @@ -1207,6 +1500,43 @@ export * as Equivalence from "./Equivalence.ts" export * as ErrorReporter from "./ErrorReporter.ts" /** + * The `ExecutionPlan` module provides a way to describe ordered fallback + * strategies for effects and streams that need different resources across + * repeated attempts. An `ExecutionPlan` is a non-empty list of steps, where + * each step supplies a `Context` or `Layer` and may control retries with an + * attempt limit, a `Schedule`, or a `while` predicate. + * + * **Mental model** + * + * - A plan is evaluated step by step until the wrapped effect or stream + * succeeds, or until every step has been exhausted + * - Each step provides the services used while that step is active + * - `attempts` limits how many times a step may be tried + * - `schedule` controls retry timing and receives the failure input + * - `while` can stop retrying a step based on the failure input + * - `CurrentMetadata` exposes the current 1-based attempt and 0-based step + * index to code running under a plan + * + * **Common tasks** + * + * - Build a plan with {@link make} + * - Run an effect with a plan using `Effect.withExecutionPlan` + * - Run a stream with a plan using `Stream.withExecutionPlan` + * - Combine plans in order with {@link merge} + * - Capture required services up front with `captureRequirements` + * - Inspect the current attempt and step with {@link CurrentMetadata} + * + * **Gotchas** + * + * - Plans must contain at least one step + * - `attempts` must be greater than zero when provided + * - If `attempts` is omitted, a step is attempted once unless a `schedule` is + * provided + * - A `while` predicate returning `false` skips the remaining retries for that + * step and moves the plan forward + * - Layer, schedule, and predicate requirements are tracked in the plan type + * until they are provided or captured + * * @since 3.16.0 */ export * as ExecutionPlan from "./ExecutionPlan.ts" @@ -1349,16 +1679,127 @@ export * as Exit from "./Exit.ts" export * as Fiber from "./Fiber.ts" /** + * The `FiberHandle` module provides a scoped handle for managing the lifecycle + * of at most one fiber at a time. A `FiberHandle` can hold one + * `Fiber`; when a new fiber is installed, the previous fiber is + * interrupted unless the operation is configured with `onlyIfMissing`. + * + * **Mental model** + * + * - A handle is either open with zero or one current fiber, or closed by its + * surrounding `Scope` + * - Closing the scope interrupts the current fiber and prevents new work from + * being accepted + * - Completed fibers remove themselves from the handle, so the handle can be + * reused for later work + * - Replacing a fiber uses the handle's internal interruption id, allowing + * expected replacement interruptions to be distinguished from real failures + * + * **Common tasks** + * + * - Create a scoped handle: {@link make} + * - Fork an effect into the handle: {@link run} + * - Store an existing fiber: {@link set} + * - Read or clear the current fiber: {@link get}, {@link clear} + * - Capture runtime-specific runners: {@link makeRuntime}, {@link runtime} + * - Run handled effects as Promises: {@link makeRuntimePromise}, + * {@link runtimePromise} + * - Wait for failure or closure: {@link join} + * - Wait until the current fiber is gone: {@link awaitEmpty} + * + * **Gotchas** + * + * - The handle never contains more than one live fiber; starting or setting + * another fiber interrupts the previous one by default + * - Use `onlyIfMissing` when a call should leave an already running fiber in + * place instead of replacing it + * - `join` observes the handle's failure/close signal; successful fiber + * completion only empties the handle + * - `awaitEmpty` waits for the fiber that is current when it starts; later + * calls to {@link run} or {@link set} can install new work + * * @since 2.0.0 */ export * as FiberHandle from "./FiberHandle.ts" /** + * The `FiberMap` module provides a scoped, mutable collection for managing + * fibers by key. A `FiberMap` owns a set of running fibers, interrupts + * them when its scope closes, and automatically removes each entry when the + * corresponding fiber completes. + * + * **Mental model** + * + * - A `FiberMap` is a keyed registry of fibers with lifecycle management + * - Keys identify the currently active fiber for a logical task or resource + * - Adding a fiber under an existing key interrupts the previous fiber by default + * - Completed fibers remove themselves from the map if they are still current + * - Closing the map's scope interrupts every fiber that remains in the map + * - The map can surface the first non-ignored managed fiber failure via {@link join} + * + * **Common tasks** + * + * - Create a scoped map: {@link make} + * - Fork effects into the map: {@link run} + * - Add existing fibers: {@link set} + * - Create captured runners: {@link makeRuntime}, {@link runtime} + * - Bridge to Promise-based callers: {@link makeRuntimePromise}, {@link runtimePromise} + * - Inspect entries: {@link get}, {@link has}, {@link size} + * - Stop work: {@link remove}, {@link clear} + * - Coordinate completion or failure: {@link awaitEmpty}, {@link join} + * + * **Gotchas** + * + * - `FiberMap` is scoped; use it with `Effect.scoped` or another scope owner so + * managed fibers are interrupted when the scope closes + * - Reusing a key is a replacement operation unless `onlyIfMissing` is enabled + * - `join` waits for the map to fail or close; use {@link awaitEmpty} to wait + * until all currently managed fibers have completed + * - The `Unsafe` variants mutate synchronously and should only be used when the + * caller already controls the surrounding execution context + * * @since 2.0.0 */ export * as FiberMap from "./FiberMap.ts" /** + * The `FiberSet` module provides a scoped container for managing many fibers as + * one lifecycle. A `FiberSet` tracks fibers whose successful values are + * compatible with `A` and whose failures are compatible with `E`, removes each + * fiber when it completes, and interrupts all still-running fibers when the + * owning `Scope` closes. + * + * **Mental model** + * + * - A `FiberSet` is an owned, scoped collection of fibers + * - Fibers can be added directly with {@link add} / {@link addUnsafe} + * - Effects can be forked into the set with {@link run}, {@link runtime}, or + * {@link runtimePromise} + * - Completed fibers are automatically removed from the set + * - Closing the scope or calling {@link clear} interrupts the currently tracked + * fibers + * - {@link join} waits for the set's first non-ignored failure, while + * {@link awaitEmpty} waits until all tracked fibers have completed + * + * **Common tasks** + * + * - Create a scoped set: {@link make} + * - Create scoped runners: {@link makeRuntime}, {@link makeRuntimePromise} + * - Add an existing fiber: {@link add} + * - Fork an effect into the set: {@link run} + * - Interrupt tracked fibers: {@link clear} + * - Observe the set: {@link size}, {@link awaitEmpty}, {@link join} + * - Check a value: {@link isFiberSet} + * + * **Gotchas** + * + * - `FiberSet` values are scoped; use them inside `Effect.scoped` or another + * scope owner so their fibers are interrupted reliably + * - Adding or running into a closed set interrupts the fiber immediately + * - By default, interruptions are not treated as failures for {@link join}; + * use the `propagateInterruption` option when interruption should be + * propagated + * * @since 2.0.0 */ export * as FiberSet from "./FiberSet.ts" @@ -1404,6 +1845,40 @@ export * as FiberSet from "./FiberSet.ts" export * as FileSystem from "./FileSystem.ts" /** + * The `Filter` module provides composable functions for accepting, rejecting, + * narrowing, and transforming values. A `Filter` receives an + * input and returns a `Result`: success means the value passed the filter, while + * failure means the value was filtered out. + * + * **Mental model** + * + * - A filter is a typed predicate that can also transform the successful value + * - Predicate-based filters pass the original input when the predicate returns `true` + * - Refinement-based filters narrow the successful type, for example from `unknown` to `string` + * - Custom filters return `Result.succeed(pass)` or `Result.fail(fail)` directly + * - Filters compose with logical and sequential combinators instead of throwing exceptions + * - `FilterEffect` is the effectful form for filters that need asynchronous work, errors, or services + * + * **Common tasks** + * + * - Build filters: {@link make}, {@link makeEffect}, {@link fromPredicate}, {@link fromPredicateOption} + * - Narrow unknown values: {@link string}, {@link number}, {@link boolean}, {@link bigint}, {@link symbol}, {@link date} + * - Match shapes and variants: {@link instanceOf}, {@link tagged}, {@link reason}, {@link has} + * - Match exact values: {@link equals}, {@link equalsStrict} + * - Combine alternatives: {@link or} + * - Require multiple filters: {@link zip}, {@link zipWith}, {@link andLeft}, {@link andRight} + * - Run filters in sequence: {@link compose}, {@link composePassthrough} + * - Convert results: {@link toPredicate}, {@link toOption}, {@link toResult} + * - Adjust failure values: {@link mapFail} + * + * **Gotchas** + * + * - A failed filter is data in the `Result` failure channel; it is not an exception + * - `compose` preserves intermediate failure values, while {@link composePassthrough} fails with the original input + * - `equalsStrict` uses JavaScript `===`; use {@link equals} for structural equality + * - `fromPredicateOption` fails with the original input when the returned `Option` is `None` + * - Prefer refinement predicates when you want TypeScript to narrow the successful value type + * * @since 4.0.0 */ export * as Filter from "./Filter.ts" @@ -1465,11 +1940,106 @@ export * as Filter from "./Filter.ts" export * as Formatter from "./Formatter.ts" /** + * The `Function` module provides small, pure helpers for defining, composing, + * adapting, and reusing TypeScript functions. It is the foundation for the + * data-first and data-last APIs used throughout Effect, and it includes the + * core pipeline utilities that make those APIs ergonomic. + * + * **Mental model** + * + * - {@link pipe} starts with a value and passes it through one unary function at + * a time + * - {@link flow} composes unary functions into a reusable function + * - {@link dual} builds APIs that support both direct calls and `pipe`-friendly + * data-last calls + * - {@link identity}, {@link constant}, and the `const*` helpers model common + * identity and thunk patterns without allocating ad hoc callbacks + * - {@link tupled}, {@link untupled}, {@link flip}, and {@link apply} adapt + * call shapes without changing the underlying behavior + * - Type helpers such as {@link LazyArg}, {@link FunctionN}, {@link satisfies}, + * and {@link cast} describe or constrain functions at the type level + * + * **Common tasks** + * + * - Build readable transformation pipelines: {@link pipe} + * - Create reusable composed functions: {@link flow}, {@link compose} + * - Define functions callable in both data-first and data-last style: {@link dual} + * - Return a value unchanged: {@link identity} + * - Create thunks and common constant functions: {@link constant}, + * {@link constTrue}, {@link constFalse}, {@link constNull}, + * {@link constUndefined}, {@link constVoid} + * - Convert between rest-argument and tuple-argument functions: {@link tupled}, + * {@link untupled} + * - Express impossible branches: {@link absurd} + * - Cache results for object keys: {@link memoize} + * + * **Gotchas** + * + * - Functions passed to {@link pipe} and {@link flow} are applied left-to-right + * and should be unary at each step + * - {@link dual} uses either an arity or a predicate to decide whether a call is + * data-first or data-last; use a predicate when optional arguments make arity + * ambiguous + * - {@link cast} changes only the static TypeScript type and performs no runtime + * validation + * - {@link memoize} is intended for object keys and stores cached values in a + * `WeakMap` + * * @since 2.0.0 */ export * as Function from "./Function.ts" /** + * The `Graph` module provides immutable and scoped-mutable graph data + * structures for modeling relationships between indexed nodes and edges. A + * graph can be directed or undirected, stores user-defined data on both nodes + * and edges, and exposes traversal, analysis, path finding, transformation, and + * diagram export utilities. + * + * **Mental model** + * + * - Nodes and edges are addressed by stable numeric indices: {@link NodeIndex} + * and {@link EdgeIndex} + * - Node data has type `N`; edge data has type `E` + * - {@link Graph} values are immutable snapshots; use {@link MutableGraph} + * through {@link mutate}, {@link beginMutation}, or constructor callbacks to + * add, remove, or update nodes and edges + * - Directed graphs follow edge direction for neighbors and traversals, while + * undirected graphs treat each edge as connecting both endpoints + * - Missing lookups return `Option`, while structurally invalid operations such + * as adding an edge to a missing node throw {@link GraphError} + * + * **Common tasks** + * + * - Create graphs: {@link directed}, {@link undirected} + * - Mutate safely: {@link mutate}, {@link addNode}, {@link addEdge}, + * {@link removeNode}, {@link removeEdge} + * - Query contents: {@link getNode}, {@link getEdge}, {@link hasNode}, + * {@link hasEdge}, {@link nodeCount}, {@link edgeCount}, {@link neighbors} + * - Transform data: {@link updateNode}, {@link updateEdge}, {@link mapNodes}, + * {@link mapEdges}, {@link filterNodes}, {@link filterEdges}, + * {@link filterMapNodes}, {@link filterMapEdges} + * - Traverse lazily: {@link dfs}, {@link bfs}, {@link topo}, + * {@link dfsPostOrder}, {@link nodes}, {@link edges}, {@link Walker} + * - Analyze structure: {@link isAcyclic}, {@link isBipartite}, + * {@link connectedComponents}, {@link stronglyConnectedComponents}, + * {@link externals} + * - Find paths: {@link dijkstra}, {@link astar}, {@link bellmanFord}, + * {@link floydWarshall} + * - Export diagrams: {@link toGraphViz}, {@link toMermaid} + * + * **Gotchas** + * + * - Only mutable graphs can be changed. Create one with {@link mutate} or by + * passing a callback to {@link directed} / {@link undirected}. + * - Traversal APIs return lazy {@link Walker} values. Use {@link indices}, + * {@link values}, or {@link entries} to choose what each iteration yields. + * - `NodeIndex` and `EdgeIndex` values are identifiers, not array offsets. They + * are not reused after removals. + * - Shortest-path algorithms require a cost function. {@link dijkstra} and + * {@link astar} reject negative weights; use {@link bellmanFord} or + * {@link floydWarshall} when negative weights are part of the model. + * * @since 4.0.0 */ export * as Graph from "./Graph.ts" @@ -1486,16 +2056,184 @@ export * as Graph from "./Graph.ts" export * as Hash from "./Hash.ts" /** + * The `HashMap` module provides an immutable key-value data structure with + * efficient lookup, insertion, removal, and transformation operations. A + * `HashMap` stores entries by hashing keys and resolving matches + * with Effect's structural equality semantics. + * + * **Mental model** + * + * - A `HashMap` is an immutable collection of key-value pairs + * - Operations such as {@link set}, {@link remove}, and {@link modifyAt} return + * new maps; existing maps are not mutated + * - Keys are compared using the `Equal` protocol and are grouped by hashes from + * the `Hash` protocol + * - Plain JavaScript primitives work as keys, and custom objects can define + * `Equal` / `Hash` behavior for structural lookup + * - Lookups with {@link get} return an `Option`, making missing keys explicit + * - Iteration order is based on the map's internal hash structure and should + * not be treated as insertion order + * + * **Common tasks** + * + * - Create maps: {@link empty}, {@link make}, {@link fromIterable} + * - Read values: {@link get}, {@link getUnsafe}, {@link has}, {@link hasBy} + * - Add or update entries: {@link set}, {@link modify}, {@link modifyAt}, {@link setMany} + * - Remove entries: {@link remove}, {@link removeMany} + * - Combine maps: {@link union} + * - Iterate or convert: {@link keys}, {@link values}, {@link entries}, {@link toValues}, {@link toEntries} + * - Transform values: {@link map}, {@link flatMap}, {@link filter}, {@link filterMap}, {@link compact} + * - Fold and search: {@link reduce}, {@link findFirst}, {@link some}, {@link every} + * - Batch updates efficiently: {@link mutate}, {@link beginMutation}, {@link endMutation} + * + * **Gotchas** + * + * - {@link getUnsafe} throws when the key is absent; prefer {@link get} unless + * absence is impossible by construction + * - Mutating a key object after insertion can make future lookups fail if its + * equality or hash changes + * - Hash collisions are handled by equality checks, so matching hashes alone do + * not make two keys equal + * - Use {@link getHash} and {@link hasHash} only when you already have the + * correct hash for the same key + * - Convert entries to an array and sort them when deterministic presentation is + * required + * + * **Quickstart** + * + * **Example** (Working with immutable maps) + * + * ```ts + * import { HashMap, Option } from "effect" + * + * const scores = HashMap.make(["alice", 10], ["bob", 15]) + * + * const updated = scores.pipe( + * HashMap.set("carol", 20), + * HashMap.modify("alice", (score) => score + 1) + * ) + * + * console.log(HashMap.get(updated, "alice")) + * // Output: Option.some(11) + * + * console.log(HashMap.get(scores, "carol")) + * // Output: Option.none() + * + * console.log(Option.getOrElse(HashMap.get(updated, "dave"), () => 0)) + * // Output: 0 + * ``` + * + * **See also** + * + * - {@link HashSet} for immutable sets backed by hash semantics + * - {@link Equal} for structural equality + * - {@link Hash} for hash implementations used by hashed collections + * * @since 2.0.0 */ export * as HashMap from "./HashMap.ts" /** + * The `HashRing` module provides a weighted consistent-hashing data structure + * for assigning arbitrary string inputs to a changing set of nodes. A hash ring + * minimizes remapping when nodes are added, removed, or reweighted, which makes + * it useful for routing requests, partitioning keys, and distributing shards + * across service instances or storage backends. + * + * **Mental model** + * + * - Each node is identified by its {@link PrimaryKey.PrimaryKey} value + * - {@link add} and {@link addMany} place weighted virtual points on the ring + * - {@link get} hashes an input string and returns the nearest node on the ring + * - {@link getShards} assigns a fixed number of shard indexes across the nodes + * - Higher weights receive proportionally more virtual points and shard + * allocations + * - Operations mutate and return the same ring instance + * + * **Common tasks** + * + * - Create an empty ring: {@link make} + * - Add or update nodes: {@link add}, {@link addMany} + * - Remove nodes: {@link remove} + * - Check membership by primary key: {@link has} + * - Route an input key to a node: {@link get} + * - Precompute shard ownership: {@link getShards} + * - Guard unknown values: {@link isHashRing} + * + * **Gotchas** + * + * - Empty rings return `undefined` from {@link get} and {@link getShards} + * - Nodes with the same primary key represent the same ring member + * - Weights are clamped to a positive minimum so a node remains represented + * - Mutating a ring in place is intentional; create a new ring when independent + * snapshots are required + * + * **Quickstart** + * + * **Example** (Routing keys across nodes) + * + * ```ts + * import { HashRing, PrimaryKey } from "effect" + * + * class Node implements PrimaryKey.PrimaryKey { + * constructor(readonly id: string) {} + * + * [PrimaryKey.symbol](): string { + * return this.id + * } + * } + * + * const ring = HashRing.make().pipe( + * HashRing.add(new Node("node-a")), + * HashRing.add(new Node("node-b"), { weight: 2 }) + * ) + * + * const owner = HashRing.get(ring, "user:123") + * console.log(owner ? PrimaryKey.value(owner) : undefined) + * ``` + * * @since 4.0.0 */ export * as HashRing from "./HashRing.ts" /** + * The `HashSet` module provides an immutable set data structure for storing + * unique values with efficient membership checks, additions, removals, and set + * operations. A `HashSet` contains at most one value for each equality class + * as determined by Effect's `Equal` / `Hash` semantics. + * + * **Mental model** + * + * - `HashSet` is an immutable collection of unique values of type `A` + * - Operations such as {@link add}, {@link remove}, {@link union}, and + * {@link difference} return new sets; the input set is never mutated + * - Membership is checked with {@link has}, using Effect equality and hashing + * rather than array-style linear scanning + * - Duplicate values are collapsed when using {@link make}, {@link fromIterable}, + * {@link add}, or {@link map} + * - `HashSet` is iterable, but iteration order is not a sorting guarantee + * + * **Common tasks** + * + * - Create sets: {@link empty}, {@link make}, {@link fromIterable} + * - Check membership and size: {@link has}, {@link size}, {@link isEmpty} + * - Add or remove values: {@link add}, {@link remove} + * - Combine sets: {@link union}, {@link intersection}, {@link difference} + * - Compare sets: {@link isSubset} + * - Transform or select values: {@link map}, {@link filter} + * - Test values: {@link some}, {@link every} + * - Fold values: {@link reduce} + * + * **Gotchas** + * + * - Values that should compare structurally should implement compatible + * `Equal` and `Hash` behavior; otherwise object identity may affect whether + * values are considered distinct + * - {@link map} may reduce the set size when multiple input values map to the + * same output value + * - Do not rely on iteration order for deterministic presentation; sort the + * values after converting to an array when order matters + * * @since 2.0.0 */ export * as HashSet from "./HashSet.ts" @@ -1815,6 +2553,38 @@ export * as JsonPointer from "./JsonPointer.ts" export * as JsonSchema from "./JsonSchema.ts" /** + * The `Latch` module provides a reusable synchronization primitive for + * coordinating fibers. A `Latch` is either open or closed: when it is closed, + * fibers that use {@link await} or {@link whenOpen} suspend until the latch is + * opened or the current waiters are released. + * + * **Mental model** + * + * - An open latch lets current and future waiters continue immediately + * - A closed latch causes `await` and `whenOpen` to suspend + * - {@link open} permanently opens the latch until it is closed again + * - {@link release} wakes only the fibers currently waiting and leaves the + * latch closed for future waiters + * - {@link close} resets the latch so later waiters suspend again + * + * **Common tasks** + * + * - Create a latch inside `Effect`: {@link make} + * - Create a latch synchronously: {@link makeUnsafe} + * - Wait for a signal before continuing: {@link await} + * - Guard an effect so it runs only after the latch is open: {@link whenOpen} + * - Let all current and future waiters proceed: {@link open} + * - Let only the current waiters proceed: {@link release} + * - Re-enable waiting after opening: {@link close} + * + * **Gotchas** + * + * - `release` is not the same as `open`; new waiters still suspend after the + * current waiters are released + * - `open` and `close` report whether they changed the latch state + * - Prefer the effectful APIs unless synchronous allocation or mutation is + * required + * * @since 3.8.0 */ export * as Latch from "./Latch.ts" @@ -1841,231 +2611,210 @@ export * as Latch from "./Latch.ts" export * as Layer from "./Layer.ts" /** - * @since 3.14.0 - */ -export * as LayerMap from "./LayerMap.ts" - -/** - * @since 2.0.0 - * - * The `Logger` module provides a robust and flexible logging system for Effect applications. - * It offers structured logging, multiple output formats, and seamless integration with the - * Effect runtime's tracing and context management. + * The `LayerMap` module provides utilities for managing scoped resources that + * are selected by key and built from `Layer` values. A `LayerMap` turns + * a key into a cached service `Context`, so applications can lazily acquire + * and reuse different resource instances such as tenant clients, regional + * connections, environment-specific services, or other keyed infrastructure. * - * ## Key Features - * - * - **Structured Logging**: Built-in support for structured log messages with metadata - * - **Multiple Formats**: JSON, LogFmt, Pretty, and custom formatting options - * - **Context Integration**: Automatic capture of fiber context, spans, and annotations - * - **Batching**: Efficient log aggregation and batch processing - * - **File Output**: Direct file writing with configurable batch windows - * - **Composable**: Transform and compose loggers using functional patterns + * **Mental model** * - * ## Basic Usage + * - A `LayerMap` is a scoped, reference-counted cache of contexts produced by layers + * - Keys identify which layer-backed resource set should be acquired + * - Resources are acquired on demand when a key is requested + * - The same key reuses the cached context while it remains live + * - Cached resources are finalized when invalidated, when their scope closes, or after idle expiration + * - The layers built by a `LayerMap` share the current layer memoization map * - * **Example** (Logging messages with structured data) + * **Common tasks** * - * ```ts - * import { Effect } from "effect" + * - Create from a lookup function: {@link make} + * - Create from a fixed record of layers: {@link fromRecord} + * - Define a service wrapper with accessor helpers: {@link Service} + * - Retrieve a layer for a key: {@link LayerMap.get} + * - Retrieve a scoped context directly: {@link LayerMap.contextEffect} + * - Force a cached entry to be rebuilt later: {@link LayerMap.invalidate} + * - Remove idle entries automatically with the `idleTimeToLive` option + * - Eagerly build known entries with `preloadKeys` or `preload` * - * // Basic logging - * const program = Effect.gen(function*() { - * yield* Effect.log("Application started") - * yield* Effect.logInfo("Processing user request") - * yield* Effect.logWarning("Resource limit approaching") - * yield* Effect.logError("Database connection failed") - * }) + * **Gotchas** * - * // With structured data - * const structuredLog = Effect.gen(function*() { - * yield* Effect.log("User action", { userId: 123, action: "login" }) - * yield* Effect.logInfo("Request processed", { duration: 150, statusCode: 200 }) - * }) - * ``` + * - `contextEffect` requires a `Scope.Scope` because it exposes the acquired context directly + * - `get` returns a `Layer` that can be provided to programs expecting the keyed services + * - Invalidating a key finalizes the current cached resources for that key; the next access rebuilds them + * - Preloading moves layer construction errors to `LayerMap` creation instead of first use * - * ## Custom Loggers + * @since 3.14.0 + */ +export * as LayerMap from "./LayerMap.ts" + +/** + * The `Logger` module defines the logging model used by the Effect runtime and + * provides constructors for formatting, routing, batching, and installing + * loggers. A `Logger` receives each runtime log event as an + * {@link Options} value and transforms it into an output such as a string, + * structured object, JSON line, console write, file write, or trace span event. * - * **Example** (Creating and providing custom loggers) + * **Mental model** * - * ```ts - * import { Effect, Logger } from "effect" + * - Effect programs emit log events with APIs such as `Effect.log`, + * `Effect.logInfo`, `Effect.logWarning`, and `Effect.logError` + * - Each event contains a message, log level, cause, fiber, and timestamp + * - Loggers are ordinary values created with {@link make} and installed with + * {@link layer} + * - Multiple loggers can be active at once by providing a layer with several + * logger values + * - Formatter loggers such as {@link formatLogFmt}, {@link formatStructured}, + * and {@link formatJson} return formatted data without writing it anywhere + * - Console loggers such as {@link consolePretty}, {@link consoleLogFmt}, + * {@link consoleStructured}, and {@link consoleJson} write formatted output + * to the active Effect console + * + * **Log output structure** + * + * Built-in formatters include the log level, timestamp, fiber identifier, and + * logged message. When present, they also include the pretty-printed cause, + * active log annotations, and active log spans. Structured and JSON loggers keep + * these fields as machine-readable data, while logfmt and pretty loggers render + * them as human-readable text. * - * // Create a custom logger - * const customLogger = Logger.make((options) => { - * console.log(`[${options.logLevel}] ${options.message}`) - * }) + * **Common tasks** * - * // Use JSON format for production - * const jsonLogger = Logger.consoleJson + * - Create a custom logger: {@link make} + * - Transform logger output: {@link map} + * - Write formatter output to the console: {@link withConsoleLog}, + * {@link withConsoleError}, {@link withLeveledConsole} + * - Use built-in console loggers: {@link consolePretty}, {@link consoleLogFmt}, + * {@link consoleStructured}, {@link consoleJson} + * - Use built-in formatter loggers: {@link formatSimple}, {@link formatLogFmt}, + * {@link formatStructured}, {@link formatJson} + * - Batch logger output before flushing to a sink: {@link batched} + * - Write string logger output to a file: {@link toFile} + * - Preserve trace correlation by including {@link tracerLogger} + * - Install or replace loggers for an effect: {@link layer} * - * // Pretty format for development - * const prettyLogger = Logger.consolePretty() + * **Gotchas** * - * const program = Effect.log("Hello World").pipe( - * Effect.provide(Logger.layer([jsonLogger])) - * ) - * ``` + * - {@link layer} replaces the current logger set by default; pass + * `mergeWithExisting: true` when adding loggers to the existing runtime + * loggers + * - Formatter loggers only produce values; wrap them with console, file, batch, + * or custom sink loggers when output should be written somewhere + * - {@link batched} and {@link toFile} are scoped; keep their scope open while + * logs are being emitted so buffered entries can flush reliably + * - {@link toFile} accepts only loggers that output strings, so pair it with + * string formatters such as {@link formatJson} or {@link formatLogFmt} + * - The default runtime logger set includes {@link tracerLogger}; replacing + * loggers without merging may remove automatic log-to-trace-span recording * - * ## Multiple Loggers + * **Quickstart** * - * **Example** (Combining multiple loggers) + * **Example** (Installing a JSON console logger) * * ```ts * import { Effect, Logger } from "effect" * - * // Combine multiple loggers - * const CombinedLoggerLive = Logger.layer([ - * Logger.consoleJson, - * Logger.consolePretty() - * ]) - * - * const program = Effect.log("Application event").pipe( - * Effect.provide(CombinedLoggerLive) + * const program = Effect.gen(function*() { + * yield* Effect.logInfo("request started", { method: "GET", path: "/users" }) + * yield* Effect.logError("request failed", { status: 500 }) + * }).pipe( + * Effect.annotateLogs("service", "users-api"), + * Effect.withLogSpan("http.request"), + * Effect.provide(Logger.layer([Logger.consoleJson])) * ) * ``` * - * ## Batched Logging - * - * **Example** (Batching log messages) + * **See also** * - * ```ts - * import { Duration, Effect, Logger } from "effect" - * - * const batchedLogger = Logger.batched(Logger.formatJson, { - * window: Duration.seconds(5), - * flush: (messages) => - * Effect.sync(() => { - * // Process batch of log messages - * console.log("Flushing", messages.length, "log entries") - * }) - * }) + * - {@link make} for defining custom loggers + * - {@link layer} for installing loggers + * - {@link formatJson} and {@link consoleJson} for structured production logs + * - {@link consolePretty} for readable local logs * - * const program = Effect.gen(function*() { - * const logger = yield* batchedLogger - * yield* Effect.provide( - * Effect.all([ - * Effect.log("Event 1"), - * Effect.log("Event 2"), - * Effect.log("Event 3") - * ]), - * Logger.layer([logger]) - * ) - * }) - * ``` + * @since 2.0.0 */ export * as Logger from "./Logger.ts" /** - * @since 2.0.0 - * - * The `LogLevel` module provides utilities for managing log levels in Effect applications. - * It defines a hierarchy of log levels and provides functions for comparing and filtering logs - * based on their severity. - * - * ## Log Level Hierarchy - * - * The log levels are ordered from most severe to least severe: - * - * 1. **All** - Special level that allows all messages - * 2. **Fatal** - System is unusable, immediate attention required - * 3. **Error** - Error conditions that should be investigated - * 4. **Warn** - Warning conditions that may indicate problems - * 5. **Info** - Informational messages about normal operation - * 6. **Debug** - Debug information useful during development - * 7. **Trace** - Very detailed trace information - * 8. **None** - Special level that suppresses all messages - * - * ## Basic Usage - * - * **Example** (Logging at different levels) - * - * ```ts - * import { Effect } from "effect" - * - * // Basic log level usage - * const program = Effect.gen(function*() { - * yield* Effect.logFatal("System is shutting down") - * yield* Effect.logError("Database connection failed") - * yield* Effect.logWarning("Memory usage is high") - * yield* Effect.logInfo("User logged in") - * yield* Effect.logDebug("Processing request") - * yield* Effect.logTrace("Variable value: xyz") - * }) - * ``` + * The `LogLevel` module defines the levels used by Effect logging and the + * ordering operations used to compare, filter, and enable log output. * - * ## Level Comparison - * - * **Example** (Comparing log levels) - * - * ```ts - * import { LogLevel } from "effect" - * - * // Check if one level is more severe than another - * console.log(LogLevel.isGreaterThan("Error", "Info")) // true - * console.log(LogLevel.isGreaterThan("Debug", "Error")) // false + * **Mental model** * - * // Check if level meets minimum threshold - * console.log(LogLevel.isGreaterThanOrEqualTo("Info", "Debug")) // true - * console.log(LogLevel.isLessThan("Trace", "Info")) // true - * ``` + * - A `LogLevel` is one of `All`, `Fatal`, `Error`, `Warn`, `Info`, `Debug`, + * `Trace`, or `None` + * - `Fatal` is the most severe concrete level and `Trace` is the least severe + * - `All` and `None` are sentinel levels: `All` enables every message and + * `None` disables every message + * - Ordering follows logging severity, so higher levels are more important and + * lower levels are more verbose + * - Filtering is usually expressed as "log this message when its level is + * greater than or equal to the configured minimum" * - * ## Filtering by Level + * **Common tasks** * - * **Example** (Filtering logger output) + * - Enumerate levels with {@link values} + * - Compare exact levels with {@link Equivalence} + * - Sort or compare by severity with {@link Order} and {@link getOrdinal} + * - Check thresholds with {@link isGreaterThanOrEqualTo} and + * {@link isLessThanOrEqualTo} + * - Test whether a level is enabled for the current fiber with + * {@link isEnabled} * - * ```ts - * import { Logger, LogLevel } from "effect" + * **Gotchas** * - * // Create a logger that only logs Error and above - * const errorLogger = Logger.make((options) => { - * if (LogLevel.isGreaterThanOrEqualTo(options.logLevel, "Error")) { - * console.log(`[${options.logLevel}] ${options.message}`) - * } - * }) + * - `All` and `None` are useful for configuration boundaries, but they are not + * concrete message severities; use {@link Severity} when only emitted message + * levels are valid + * - The comparison helpers compare severity, not declaration position in source + * code or alphabetical order + * - `isEnabled` reads the current fiber's `MinimumLogLevel` reference, so it is + * context-sensitive; use the pure comparison helpers when checking an + * explicit threshold * - * // Production logger - Info and above - * const productionLogger = Logger.make((options) => { - * if (LogLevel.isGreaterThanOrEqualTo(options.logLevel, "Info")) { - * console.log( - * `${options.date.toISOString()} [${options.logLevel}] ${options.message}` - * ) - * } - * }) + * @since 2.0.0 + */ +export * as LogLevel from "./LogLevel.ts" + +/** + * The `ManagedRuntime` module provides a way to build a reusable runtime from + * a `Layer` and use it to run effects that require the services produced by + * that layer. A `ManagedRuntime` owns the lifecycle of the layer-built + * resources, caches the resulting `Context`, and exposes runners for + * integrating Effect programs with JavaScript entry points. * - * // Development logger - Debug and above - * const devLogger = Logger.make((options) => { - * if (LogLevel.isGreaterThanOrEqualTo(options.logLevel, "Debug")) { - * console.log(`[${options.logLevel}] ${options.message}`) - * } - * }) - * ``` + * **Mental model** * - * ## Runtime Configuration + * - A managed runtime is created from a `Layer` with {@link make} + * - The layer is built lazily the first time the runtime is used + * - The built context is cached and reused for subsequent effect executions + * - Resources acquired by the layer are owned by the runtime's internal scope + * - Disposing the runtime closes that scope and releases all managed resources + * - Effects run through the runtime receive the layer's services automatically * - * **Example** (Configuring log level from the environment) + * **Common tasks** * - * ```ts - * import { Config, Effect, Logger, LogLevel } from "effect" + * - Create a runtime from application services: {@link make} + * - Run an effect as a `Promise`: {@link ManagedRuntime.runPromise} + * - Run an effect and keep its `Exit`: {@link ManagedRuntime.runPromiseExit} + * - Fork an effect into a `Fiber`: {@link ManagedRuntime.runFork} + * - Bridge callback-style APIs: {@link ManagedRuntime.runCallback} + * - Run synchronous effects at program boundaries: {@link ManagedRuntime.runSync}, + * {@link ManagedRuntime.runSyncExit} + * - Access the cached service context: {@link ManagedRuntime.context} + * - Release layer resources: {@link ManagedRuntime.dispose}, + * {@link ManagedRuntime.disposeEffect} * - * // Configure log level from environment - * const logLevelConfig = Config.string("LOG_LEVEL").pipe( - * Config.withDefault("Info") - * ) + * **Gotchas** * - * const configurableLogger = Effect.gen(function*() { - * const minLevel = yield* logLevelConfig + * - Always dispose a managed runtime when it is no longer needed, especially + * when the layer acquires resources such as connections, servers, or files + * - Layer construction errors are included in the error channel of runtime + * runners, so `ER` is combined with the effect's own error type + * - `runSync` can only execute effects without asynchronous boundaries; use + * `runPromise` for asynchronous programs + * - After disposal, the runtime cannot be reused * - * return Logger.make((options) => { - * if (LogLevel.isGreaterThanOrEqualTo(options.logLevel, minLevel)) { - * console.log(`[${options.logLevel}] ${options.message}`) - * } - * }) - * }) - * ``` - */ -export * as LogLevel from "./LogLevel.ts" - -/** * @since 2.0.0 */ export * as ManagedRuntime from "./ManagedRuntime.ts" @@ -2101,51 +2850,56 @@ export * as ManagedRuntime from "./ManagedRuntime.ts" export * as Match from "./Match.ts" /** - * @since 2.0.0 - * - * The `Metric` module provides a comprehensive system for collecting, aggregating, and observing - * application metrics in Effect applications. It offers type-safe, concurrent metrics that can - * be used to monitor performance, track business metrics, and gain insights into application behavior. - * - * ## Key Features - * - * - **Five Metric Types**: Counters, Gauges, Frequencies, Histograms, and Summaries - * - **Type Safety**: Fully typed metrics with compile-time guarantees - * - **Concurrency Safe**: Thread-safe metrics that work with Effect's concurrency model - * - **Attributes**: Tag metrics with key-value attributes for filtering and grouping - * - **Snapshots**: Take point-in-time snapshots of all metrics for reporting - * - **Runtime Integration**: Automatic fiber runtime metrics collection + * The `Metric` module provides tools for defining, updating, tagging, and + * reading application metrics from Effect programs. A `Metric` + * accepts typed input values and aggregates them into a typed state that can be + * read directly or exported from a snapshot. * - * ## Metric Types + * **Mental model** * - * ### Counter - * Tracks cumulative values that only increase or can be reset to zero. - * Perfect for counting events, requests, errors, etc. + * - A metric has an identifier, a type, an optional description, optional attributes, and mutable aggregate state + * - Use counters for cumulative values such as requests, errors, retries, or bytes processed + * - Use gauges for point-in-time values that can rise or fall, such as active connections or queue size + * - Use frequencies to count occurrences of discrete string values, such as status codes or action names + * - Use histograms to bucket numeric observations and inspect count, min, max, and sum + * - Use summaries to calculate quantiles over a bounded, time-based observation window + * - Metrics are updated from effects with {@link update} and {@link modify}, and read with {@link value} + * - Attributes tag metrics with key-value dimensions so the same logical metric can be grouped by service, endpoint, method, or other labels + * - Snapshots capture the currently registered metrics and their aggregate states for reporting or export * - * ### Gauge - * Represents a single numerical value that can go up or down. - * Ideal for current resource usage, temperature, queue sizes, etc. + * **Common tasks** * - * ### Frequency - * Counts occurrences of discrete string values. - * Useful for tracking categorical data like HTTP status codes, user actions, etc. + * - Create counters: {@link counter} + * - Create gauges: {@link gauge} + * - Create frequencies: {@link frequency} + * - Create histograms: {@link histogram}, {@link linearBoundaries}, {@link exponentialBoundaries} + * - Create summaries: {@link summary}, {@link summaryWithTimestamp} + * - Measure effect duration: {@link timer} + * - Update a metric: {@link update} + * - Apply relative updates where supported: {@link modify} + * - Read one metric: {@link value} + * - Tag a metric: {@link withAttributes} + * - Transform accepted input values: {@link mapInput} + * - Record a constant input for repeated events: {@link withConstantInput} + * - Inspect all registered metrics: {@link snapshot}, {@link dump} + * - Enable fiber runtime metrics: {@link enableRuntimeMetrics} * - * ### Histogram - * Records observations in configurable buckets to analyze distribution. - * Great for response times, request sizes, and other measured values. + * **Gotchas** * - * ### Summary - * Calculates quantiles over a sliding time window. - * Provides statistical insights into value distributions over time. + * - Counter and gauge metrics can use `number` inputs by default or `bigint` inputs with the `bigint` option + * - Incremental counters ignore negative updates; use non-incremental counters only when decreases are meaningful + * - {@link update} sets a gauge to an absolute value, while {@link modify} changes it relative to its current value + * - Histogram buckets are cumulative and depend on the boundaries supplied when the metric is created + * - Summary quantiles are calculated from the configured sliding window, so old observations expire + * - Prefer low-cardinality attributes; using unbounded values such as request IDs can create too many metric series * - * ## Basic Usage + * **Quickstart** * * **Example** (Creating and updating metrics) * * ```ts * import { Effect, Metric } from "effect" * - * // Create metrics * const requestCount = Metric.counter("http_requests_total", { * description: "Total number of HTTP requests" * }) @@ -2155,33 +2909,7 @@ export * as Match from "./Match.ts" * boundaries: Metric.linearBoundaries({ start: 0, width: 50, count: 20 }) * }) * - * // Use metrics in your application * const handleRequest = Effect.gen(function*() { - * yield* Metric.update(requestCount, 1) - * - * const startTime = yield* Effect.clockWith((clock) => clock.currentTimeMillis) - * - * // Process request... - * yield* Effect.sleep("100 millis") - * - * const endTime = yield* Effect.clockWith((clock) => clock.currentTimeMillis) - * yield* Metric.update(responseTime, endTime - startTime) - * }) - * ``` - * - * ## Attributes and Tagging - * - * **Example** (Tagging metrics with attributes) - * - * ```ts - * import { Effect, Metric } from "effect" - * - * const requestCount = Metric.counter("requests", { - * description: "Number of requests by endpoint and method" - * }) - * - * const program = Effect.gen(function*() { - * // Add attributes to metrics * yield* Metric.update( * Metric.withAttributes(requestCount, { * endpoint: "/api/users", @@ -2190,59 +2918,21 @@ export * as Match from "./Match.ts" * 1 * ) * - * // Or use withAttributes for compile-time attributes - * const taggedCounter = Metric.withAttributes(requestCount, { - * endpoint: "/api/posts", - * method: "POST" - * }) - * yield* Metric.update(taggedCounter, 1) - * }) - * ``` - * - * ## Advanced Examples - * - * **Example** (Recording business and performance metrics) - * - * ```ts - * import { Effect, Metric } from "effect" - * - * // Business metrics - * const userSignups = Metric.counter("user_signups_total") - * const activeUsers = Metric.gauge("active_users_current") - * const featureUsage = Metric.frequency("feature_usage") + * yield* Metric.update(responseTime, 125) * - * // Performance metrics - * const dbQueryTime = Metric.summary("db_query_duration", { - * maxAge: "5 minutes", - * maxSize: 1000, - * quantiles: [0.5, 0.9, 0.95, 0.99] + * return yield* Metric.value(requestCount) * }) + * ``` * - * const program = Effect.gen(function*() { - * // Track user signup - * yield* Metric.update(userSignups, 1) - * - * // Update active user count - * yield* Metric.update(activeUsers, 1250) - * - * // Record feature usage - * yield* Metric.update(featureUsage, "dashboard_view") - * - * // Measure database query time - * yield* Effect.timed(performDatabaseQuery).pipe( - * Effect.tap(([duration]) => Metric.update(dbQueryTime, duration)) - * ) - * }) + * **See also** * - * // Get metric snapshots - * const getMetrics = Effect.gen(function*() { - * const snapshots = yield* Metric.snapshot + * - {@link counter} / {@link gauge} / {@link frequency} for common metric types + * - {@link histogram} / {@link summary} for distribution metrics + * - {@link update} / {@link modify} / {@link value} for working with metric state + * - {@link withAttributes} for adding dimensions + * - {@link snapshot} for exporting all registered metric values * - * for (const metric of snapshots) { - * console.log(`${metric.id}: ${JSON.stringify(metric.state)}`) - * } - * }) - * ``` + * @since 2.0.0 */ export * as Metric from "./Metric.ts" @@ -2275,99 +2965,166 @@ export * as Metric from "./Metric.ts" export * as MutableHashMap from "./MutableHashMap.ts" /** - * @fileoverview - * MutableHashSet is a high-performance, mutable set implementation that provides efficient storage - * and retrieval of unique values. Built on top of MutableHashMap, it inherits the same performance - * characteristics and support for both structural and referential equality. + * The `MutableHashSet` module provides a mutable hash set for storing unique + * values with efficient membership checks, insertion, removal, and iteration. + * It is built on {@link MutableHashMap}: each set value is stored as a map key, + * so uniqueness follows the same hashing and equality rules as the underlying + * mutable hash map. + * + * **Mental model** + * + * - `MutableHashSet` is a mutable collection of unique values of type `V` + * - Operations such as {@link add}, {@link remove}, and {@link clear} mutate + * the set in place + * - Duplicate values are ignored according to Effect equality and hashing semantics + * - Values that implement `Equal` / `Hash` are compared structurally + * - Primitive values and references that do not implement Effect equality use + * the normal hash map behavior + * - The set is iterable, so `Array.from(set)` or `for...of` can be used to + * inspect its values + * + * **Common tasks** + * + * - Create an empty set: {@link empty} + * - Create from values: {@link make} + * - Create from any iterable: {@link fromIterable} + * - Add a value: {@link add} + * - Check membership: {@link has} + * - Remove a value: {@link remove} + * - Remove all values: {@link clear} + * - Count unique values: {@link size} + * - Narrow unknown values: {@link isMutableHashSet} + * + * **Gotchas** + * + * - This data structure is intentionally mutable; keep ownership clear when + * sharing it between callers + * - Mutating operations return the same set instance for convenient piping, not + * a copy + * - Iteration order should not be used as a stable sorting mechanism + * - For immutable set operations, use Effect's immutable collection modules + * instead + * + * **Performance** + * + * - Add, membership checks, and removal are O(1) on average and O(n) in the + * presence of hash collisions + * - Clearing and reading the size are O(1) + * - Iteration is O(n) + * + * **Quickstart** + * + * **Example** (Tracking unique values) + * + * ```ts + * import { MutableHashSet } from "effect" * - * The implementation uses a MutableHashMap internally where each value is stored as a key with a - * boolean flag, providing O(1) average-case performance for all operations. + * const set = MutableHashSet.make("apple", "banana", "apple") * - * Key Features: - * - Mutable operations for performance-critical scenarios - * - Supports both structural and referential equality - * - Efficient duplicate detection and removal - * - Iterable interface for easy traversal - * - Memory-efficient storage with automatic deduplication - * - Seamless integration with Effect's Equal and Hash interfaces + * MutableHashSet.add(set, "cherry") + * MutableHashSet.remove(set, "banana") * - * Performance Characteristics: - * - Add/Has/Remove: O(1) average, O(n) worst case (hash collisions) - * - Clear: O(1) - * - Size: O(1) - * - Iteration: O(n) + * console.log(MutableHashSet.has(set, "apple")) + * // Output: true + * + * console.log(MutableHashSet.size(set)) + * // Output: 2 + * + * console.log(Array.from(set)) + * // Output: ["apple", "cherry"] + * ``` * + * @fileoverview * @category data-structures * @since 2.0.0 */ export * as MutableHashSet from "./MutableHashSet.ts" /** - * @fileoverview - * MutableList is an efficient, mutable linked list implementation optimized for high-throughput - * scenarios like logging, queuing, and streaming. It uses a bucket-based architecture where - * elements are stored in arrays (buckets) linked together, providing optimal performance for - * both append and prepend operations. + * The `MutableList` module provides a mutable linked list for accumulating, + * ordering, inspecting, and draining values with efficient operations at both + * ends of the list. * - * The implementation uses a sophisticated bucket system: - * - Each bucket contains an array of elements with an offset pointer - * - Buckets can be marked as mutable or immutable for optimization - * - Elements are taken from the head and added to the tail - * - Memory is efficiently managed through bucket reuse and cleanup + * A `MutableList` stores values in linked buckets of arrays. Appending adds + * values to the tail, prepending adds values to the head, and taking removes + * values from the head. Unlike persistent collections, every mutation updates + * the list object in place: operations such as {@link append}, {@link prepend}, + * {@link take}, {@link takeN}, {@link clear}, {@link filter}, and {@link remove} + * change the same `MutableList` instance and update its `length`. * - * Key Features: - * - Highly optimized for high-frequency append/prepend operations - * - Memory efficient with automatic cleanup of consumed elements - * - Support for bulk operations (appendAll, prependAll, takeN) - * - Filtering and removal operations - * - Zero-copy optimizations for certain scenarios + * **Mental model** * - * Performance Characteristics: - * - Append/Prepend: O(1) amortized - * - Take/TakeN: O(1) per element taken - * - Length: O(1) - * - Clear: O(1) - * - Filter: O(n) + * - `MutableList` is a stateful container with `head`, `tail`, and `length` + * - Values are consumed from the head with {@link take}, {@link takeN}, or + * {@link takeAll} + * - {@link append} and {@link appendAll} preserve FIFO queue order for normal + * producer-consumer use cases + * - {@link prepend} and {@link prependAll} place values before the current + * contents, which is useful for priority work or restoring items to the front + * - {@link toArray} and {@link toArrayN} copy values without modifying the list + * - The `head` and `tail` bucket fields are exposed for advanced use, but most + * code should treat them as implementation details + * + * **Common tasks** * - * Ideal Use Cases: - * - High-throughput logging systems - * - Producer-consumer queues - * - Streaming data buffers - * - Real-time data processing pipelines + * - Create an empty list: {@link make} + * - Add one value: {@link append}, {@link prepend} + * - Add many values: {@link appendAll}, {@link prependAll} + * - Drain one value: {@link take} + * - Drain many values: {@link takeN}, {@link takeAll} + * - Inspect without draining: {@link toArrayN}, {@link toArray} + * - Reset the list: {@link clear} + * - Mutate contents in place: {@link filter}, {@link remove} * + * **Gotchas** + * + * - `MutableList` is intentionally mutable; sharing a list means sharing its + * changing state + * - {@link take} returns the {@link Empty} symbol when the list has no value, so + * compare with `MutableList.Empty` instead of relying on falsy checks + * - {@link appendAllUnsafe} and {@link prependAllUnsafe} may reuse the provided + * array when `mutable` is `true`; only enable that optimization when callers + * will not keep using the array independently + * - {@link remove} uses JavaScript strict equality semantics, not structural + * equality + * + * @fileoverview * @category data-structures * @since 4.0.0 */ export * as MutableList from "./MutableList.ts" /** - * @fileoverview - * MutableRef provides a mutable reference container that allows safe mutation of values - * in functional programming contexts. It serves as a bridge between functional and imperative - * programming paradigms, offering atomic operations for state management. + * The `MutableRef` module provides a small synchronous container for mutable + * state. A `MutableRef` stores one current value of type `A`, exposes that + * value through `.current`, and offers pipeable helpers for reading, replacing, + * and transforming the value in place. * - * Unlike regular variables, MutableRef encapsulates mutable state and provides controlled - * access through a standardized API. It supports atomic compare-and-set operations for - * thread-safe updates and integrates seamlessly with Effect's ecosystem. + * **Mental model** * - * Key Features: - * - Mutable reference semantics with functional API - * - Atomic compare-and-set operations for safe concurrent updates - * - Specialized operations for numeric and boolean values - * - Chainable operations that return the reference or the value - * - Integration with Effect's Equal interface for value comparison - * - * Common Use Cases: - * - State containers in functional applications - * - Counters and accumulators - * - Configuration that needs to be updated at runtime - * - Caching and memoization scenarios - * - Inter-module communication via shared references + * - `MutableRef` is a stable reference whose `.current` field may change over time + * - Reads and writes are synchronous and return immediately + * - `set`, `update`, `increment`, `decrement`, and `toggle` mutate the same reference in place + * - `getAnd*` helpers return the previous value, while `*AndGet` helpers return the new value + * - `compareAndSet` updates only when the current value is equal to the expected value using `Equal.equals` + * - A `MutableRef` is useful for local mutable state, but it does not make updates transactional or effectful * - * Performance Characteristics: - * - Get/Set: O(1) - * - Compare-and-set: O(1) - * - All operations: O(1) + * **Common tasks** + * + * - Create a reference: {@link make} + * - Read the current value: {@link get} or `.current` + * - Replace the current value: {@link set}, {@link setAndGet}, {@link getAndSet} + * - Transform the current value: {@link update}, {@link updateAndGet}, {@link getAndUpdate} + * - Coordinate conditional replacement: {@link compareAndSet} + * - Work with counters: {@link increment}, {@link decrement}, {@link incrementAndGet}, {@link decrementAndGet} + * - Work with boolean flags: {@link toggle} + * + * **Gotchas** + * + * - All updates are imperative mutations; aliases to the same `MutableRef` observe the same changing value + * - Updating object or array values does not clone them unless the update function creates a new value + * - `compareAndSet` compares with Effect equality semantics, not only JavaScript reference equality + * - For state that must participate in `Effect` workflows, interruption, or fiber coordination, prefer higher-level Effect data types * * @category data-structures * @since 2.0.0 @@ -2442,28 +3199,34 @@ export * as MutableRef from "./MutableRef.ts" export * as Newtype from "./Newtype.ts" /** - * @since 2.0.0 + * The `NonEmptyIterable` module provides a type-level representation of any + * JavaScript `Iterable` that is known to contain at least one element. A + * `NonEmptyIterable` can be consumed anywhere an `Iterable` is expected, + * while also carrying the guarantee that reading the first element is safe. * - * The `NonEmptyIterable` module provides types and utilities for working with iterables - * that are guaranteed to contain at least one element. This provides compile-time - * safety when working with collections that must not be empty. + * **Mental model** * - * ## Key Features + * - `NonEmptyIterable` is an `Iterable` branded with a non-empty guarantee + * - The guarantee is static: values should only be typed this way when construction or validation proves at least one element exists + * - The iterable can be an array, string, set, map, generator, or any custom iterable + * - `unprepend` safely separates the first element from an iterator for the remaining elements + * - Operations that may remove elements, such as filtering, usually return ordinary collections because they can become empty * - * - **Type Safety**: Compile-time guarantee that the iterable contains at least one element - * - **Iterator Protocol**: Fully compatible with JavaScript's built-in iteration protocol - * - **Functional Operations**: Safe operations that preserve the non-empty property - * - **Lightweight**: Minimal overhead with maximum type safety + * **Common tasks** + * + * - Accept inputs that must contain at least one value + * - Extract a head element and process the remaining iterator with {@link unprepend} + * - Model APIs such as reductions, comparisons, or aggregation that are undefined for empty inputs + * - Preserve compatibility with the JavaScript iteration protocol while documenting the stronger invariant * - * ## Why NonEmptyIterable? + * **Gotchas** * - * Many operations require non-empty collections to be meaningful: - * - Finding the maximum or minimum value - * - Getting the first or last element - * - Reducing without an initial value - * - Operations that would otherwise need runtime checks + * - A type assertion does not make an empty iterable non-empty; only assert after a trusted check or constructor + * - Iterators are stateful, so calling {@link unprepend} consumes the first yielded value from that iterator + * - The order of the first element follows the source iterable's iteration order, for example insertion order for `Map` and `Set` + * - Some transformations preserve non-emptiness, but transformations that can discard elements must account for the empty case * - * ## Basic Usage + * **Quickstart** * * **Example** (Requiring a non-empty iterable) * @@ -2575,6 +3338,8 @@ export * as Newtype from "./Newtype.ts" * // This would still be non-empty if the source was non-empty * ) * ``` + * + * @since 2.0.0 */ export * as NonEmptyIterable from "./NonEmptyIterable.ts" @@ -2812,55 +3577,207 @@ export * as Option from "./Option.ts" export * as Order from "./Order.ts" /** - * @fileoverview - * The Ordering module provides utilities for working with comparison results and ordering operations. - * An Ordering represents the result of comparing two values, expressing whether the first value is - * less than (-1), equal to (0), or greater than (1) the second value. + * The `Ordering` module provides the standard representation for the result of + * comparing two values. An `Ordering` is one of three numeric literals: `-1` + * when the first value is less than the second, `0` when both values compare as + * equal, and `1` when the first value is greater than the second. + * + * **Mental model** * - * This module is fundamental for building comparison functions, sorting algorithms, and implementing - * ordered data structures. It provides composable operations for combining multiple comparison results - * and pattern matching on ordering outcomes. + * - `Ordering` describes the relationship between two compared values, not the + * values themselves + * - Negative means "less than", zero means "equal", and positive means "greater + * than" + * - Unlike JavaScript comparators, this type is normalized to exactly `-1`, `0`, + * or `1` + * - `0` is neutral when combining comparisons; the first non-zero ordering + * determines the result * - * Key Features: - * - Type-safe representation of comparison results (-1, 0, 1) - * - Composable operations for combining multiple orderings - * - Pattern matching utilities for handling different ordering cases - * - Ordering reversal and combination functions - * - Integration with Effect's functional programming patterns - * - * Common Use Cases: - * - Implementing custom comparison functions - * - Building complex sorting criteria - * - Combining multiple comparison results - * - Creating ordered data structures - * - Pattern matching on comparison outcomes + * **Common tasks** + * + * - Interpret a comparison result with {@link match} + * - Reverse ascending and descending order with {@link reverse} + * - Combine multiple comparison criteria with {@link Reducer} + * - Build custom comparison functions for sorting, ordered collections, and + * domain-specific ordering rules * + * **Gotchas** + * + * - Do not cast arbitrary comparator results such as `a.localeCompare(b)` + * directly unless they have been normalized to `-1`, `0`, or `1` + * - In comparator-style APIs, `-1` means the left value should come before the + * right value, while `1` means it should come after + * - Reversing an `Ordering` swaps `-1` and `1`, but leaves `0` unchanged + * + * @fileoverview * @category utilities * @since 2.0.0 */ export * as Ordering from "./Ordering.ts" /** + * The `PartitionedSemaphore` module provides a semaphore for limiting + * concurrency across a shared permit pool while keeping waiters grouped by + * partition key. A `PartitionedSemaphore` is useful when many independent + * groups of work compete for the same bounded resource and each group should + * make progress without one busy group monopolizing released permits. + * + * **Mental model** + * + * - The semaphore has a fixed shared capacity measured in permits + * - Work acquires permits with a partition key of type `K` + * - Waiting acquisitions are tracked per partition + * - Released permits are assigned to waiting partitions in round-robin order + * - `withPermit` and `withPermits` acquire permits around an effect and + * release them when the effect exits, fails, or is interrupted + * + * **Common tasks** + * + * - Create a semaphore: {@link make}, {@link makeUnsafe} + * - Inspect capacity and availability: {@link capacity}, {@link available} + * - Acquire and release manually: {@link take}, {@link release} + * - Limit a single operation per partition: {@link withPermit} + * - Limit weighted work per partition: {@link withPermits} + * - Run only when permits are immediately available: + * {@link withPermitsIfAvailable} + * + * **Gotchas** + * + * - `withPermitsIfAvailable` does not use a partition key; it only succeeds + * when the shared pool has enough permits immediately + * - Acquiring more permits than the semaphore capacity never completes + * - Requests for zero or negative permits complete without acquiring anything + * - Non-finite capacities create an unbounded semaphore whose acquire and + * release operations complete immediately + * * @since 4.0.0 */ export * as PartitionedSemaphore from "./PartitionedSemaphore.ts" /** + * The `Path` module provides a platform path service for manipulating file + * system paths through Effect's environment. It models path operations as a + * replaceable service so programs can depend on path behavior without directly + * coupling to a particular runtime implementation. + * + * **Mental model** + * + * - `Path.Path` is a `Context.Service` tag used to access the current path implementation + * - The service offers familiar path operations such as joining, resolving, parsing, and formatting + * - Most operations are pure string transformations and follow POSIX-style path semantics + * - File URL conversions return `Effect`s because invalid paths or URLs can fail with `BadArgument` + * - Custom implementations can be provided with `Layer.succeed` for alternate platforms or tests + * + * **Common tasks** + * + * - Combine path segments with `join` or turn segments into an absolute path with `resolve` + * - Normalize `.` and `..` segments with `normalize` + * - Inspect paths with `basename`, `dirname`, `extname`, and `isAbsolute` + * - Convert between structured path parts and strings with `parse` and `format` + * - Compute relative paths with `relative` + * - Convert between file paths and `file:` URLs with `toFileUrl` and `fromFileUrl` + * + * **Gotchas** + * + * - Path strings are not checked against the file system; these operations only manipulate syntax + * - `resolve` may consult the host current working directory when no absolute segment is supplied + * - `fromFileUrl` only accepts valid `file:` URLs and rejects encoded path separators + * - Use the service from the environment when writing portable Effect code instead of importing + * host-specific path APIs directly + * * @since 4.0.0 */ export * as Path from "./Path.ts" /** + * The `Pipeable` module defines the shared interface and implementation helpers + * for values that support Effect-style method chaining with `.pipe(...)`. + * + * A `Pipeable` value can pass itself through a sequence of unary functions from + * left to right, so code can be written as `value.pipe(f, g, h)` instead of + * deeply nesting calls. This is the method form used by many Effect data types + * to compose transformations, validations, and effectful operations while + * keeping the original value as the starting point of the pipeline. + * + * **Common tasks** + * + * - Type values that expose a `.pipe(...)` method with the {@link Pipeable} interface + * - Implement a custom `.pipe(...)` method with {@link pipeArguments} + * - Reuse the standard implementation through {@link Prototype}, {@link Class}, or {@link Mixin} + * + * **Gotchas** + * + * - Each function receives the result of the previous function, not the original value + * - The overloads preserve precise types for long pipelines, but very long chains may be easier to read when split + * * @since 2.0.0 */ export * as Pipeable from "./Pipeable.ts" /** + * The `PlatformError` module defines the normalized error model used by + * platform APIs when adapting host operations into Effect programs. It gives + * callers a stable `PlatformError` wrapper whose `reason` is either a + * `BadArgument`, for invalid inputs rejected before an operation runs, or a + * `SystemError`, for failures reported by the host platform or operating + * system. + * + * Use this module when implementing or consuming platform services such as + * file systems, terminal access, sockets, or other environment-specific APIs. + * `SystemError` intentionally groups many low-level failures into a small set + * of portable tags like `NotFound`, `PermissionDenied`, and `TimedOut`, while + * still preserving operation details such as the module, method, syscall, path + * or descriptor, description, and original cause when available. + * + * **Common tasks** + * + * - Create platform failures from system operations with {@link systemError} + * - Report rejected caller input with {@link badArgument} + * - Inspect the underlying reason via {@link PlatformError.reason} + * - Match normalized system failures with {@link SystemErrorTag} + * + * **Gotchas** + * + * - `PlatformError` is a wrapper; inspect `reason` to distinguish + * `BadArgument` from `SystemError` + * - `SystemErrorTag` values are normalized categories, not necessarily raw + * platform error codes + * - The original cause is preserved when provided, but portable handling + * should rely on the normalized fields + * * @since 4.0.0 */ export * as PlatformError from "./PlatformError.ts" /** + * The `Pool` module provides scoped resource pools for sharing expensive or + * limited resources across fibers. A `Pool` manages values of type `A` + * acquired by an effect that may fail with `E`, automatically releasing all + * allocated resources when the surrounding `Scope` closes. + * + * **Mental model** + * + * - A pool owns a bounded set of acquired items and hands them out with {@link get} + * - Each checkout is scoped; leaving the scope returns the item to the pool + * - `concurrency` controls how many fibers may use the same item at once + * - `targetUtilization` controls when the pool grows between its minimum and maximum sizes + * - {@link invalidate} removes a specific item so it can be replaced lazily + * + * **Common tasks** + * + * - Create a fixed-size pool with {@link make} + * - Create an elastic pool with time-to-live reclamation using {@link makeWithTTL} + * - Implement custom resizing and reclamation behavior with {@link makeWithStrategy} + * - Borrow resources safely in scoped effects with {@link get} + * + * **Gotchas** + * + * - Pool construction and item checkout require `Scope`; closing the scope shuts + * down the pool or returns the borrowed item + * - Failed acquisitions are represented by the `get` effect failing with the + * acquisition error, and retrying `get` can retry acquisition + * - Resource finalization order during shutdown is unspecified + * * @since 2.0.0 */ export * as Pool from "./Pool.ts" @@ -2936,13 +3853,14 @@ export * as PrimaryKey from "./PrimaryKey.ts" * const program = Effect.gen(function*() { * const pubsub = yield* PubSub.bounded(10) * - * // Publisher - * yield* PubSub.publish(pubsub, "Hello") - * yield* PubSub.publish(pubsub, "World") - * - * // Subscriber * yield* Effect.scoped(Effect.gen(function*() { * const subscription = yield* PubSub.subscribe(pubsub) + * + * // Publisher + * yield* PubSub.publish(pubsub, "Hello") + * yield* PubSub.publish(pubsub, "World") + * + * // Subscriber * const message1 = yield* PubSub.take(subscription) * const message2 = yield* PubSub.take(subscription) * console.log(message1, message2) // "Hello", "World" @@ -2955,19 +3873,105 @@ export * as PrimaryKey from "./PrimaryKey.ts" export * as PubSub from "./PubSub.ts" /** + * The `Pull` module provides the low-level pull-step abstraction used by + * stream-like consumers. A `Pull` is an `Effect` that can + * produce one value of type `A`, fail with an ordinary error `E`, or signal + * end-of-input with a `Cause.Done` value. + * + * **Mental model** + * + * - `Pull` is an `Effect` with a distinguished completion signal in the error channel + * - ordinary failures and completion are both represented by `Cause`, but can be separated with the helpers in this module + * - the `Done` value can carry leftover state or a final value needed by a downstream consumer + * - `Pull` is useful when repeatedly evaluating an effect until it either produces values, fails, or reports that no more input is available + * + * **Common tasks** + * + * - Extract type parameters from a pull: {@link Success}, {@link Error}, {@link Leftover}, {@link Services} + * - Detect and filter completion: {@link isDoneCause}, {@link filterDone}, {@link filterNoDone} + * - Recover from completion while preserving ordinary failures: {@link catchDone} + * - Convert done causes to successful exits: {@link doneExitFromCause} + * - Handle all outcomes explicitly: {@link matchEffect} + * + * **Gotchas** + * + * - `Cause.Done` is not an ordinary failure; use this module's helpers before treating a pull failure as an error + * - `Done` lives in the error channel, so generic `Effect` error handling can catch it unless you filter it deliberately + * - `Pull` is a low-level primitive; most user-facing stream workflows should prefer higher-level stream APIs when available + * * @since 4.0.0 */ export * as Pull from "./Pull.ts" /** + * The `Queue` module provides asynchronous queues for communicating between + * fibers. A `Queue` can receive values of type `A`, deliver them to + * consumers in order, and eventually complete or fail with an error of type + * `E`. + * + * **Mental model** + * + * - A queue is a fiber-aware channel with one write side ({@link Enqueue}) and + * one read side ({@link Dequeue}) + * - Producers add values with {@link offer} or {@link offerAll}; consumers + * remove values with {@link take}, {@link takeN}, {@link takeBetween}, or + * {@link takeAll} + * - Bounded queues use an overflow strategy: {@link bounded} suspends + * producers, {@link dropping} rejects new values, and {@link sliding} drops + * old values + * - Queues can be completed with {@link end}, failed with {@link fail} or + * {@link failCause}, interrupted with {@link interrupt}, and shut down with + * {@link shutdown} + * - Operations are expressed as `Effect` values so waiting producers and + * consumers compose with interruption, scheduling, and structured + * concurrency + * + * **Common tasks** + * + * - Create queues: {@link make}, {@link bounded}, {@link dropping}, + * {@link sliding}, {@link unbounded} + * - Restrict capabilities: {@link asEnqueue}, {@link asDequeue} + * - Produce values: {@link offer}, {@link offerAll} + * - Consume values: {@link take}, {@link takeN}, {@link takeBetween}, + * {@link takeAll}, {@link poll}, {@link peek} + * - Drain or reset buffered values: {@link collect}, {@link clear} + * - Signal lifecycle: {@link end}, {@link fail}, {@link failCause}, + * {@link interrupt}, {@link shutdown} + * - Inspect state: {@link size}, {@link isFull} + * + * **Gotchas** + * + * - `take` waits when the queue is empty; use {@link poll} when absence should + * be represented as `Option.None` + * - `dropping` and `sliding` queues can lose values by design; use + * {@link bounded} when every offered value must be preserved + * - Completion and failure are observed by consumers through the queue's error + * channel, so include `Cause.Done` in the error type when using {@link end} + * - The `Unsafe` variants are synchronous, low-level operations; prefer the + * effectful APIs in application code + * + * **See also** + * + * - {@link Enqueue} for write-only queue handles + * - {@link Dequeue} for read-only queue handles + * - {@link Pull} for stream-style completion errors + * * @since 3.8.0 */ export * as Queue from "./Queue.ts" /** - * The Random module provides a service for generating random numbers in Effect - * programs. It offers a testable and composable way to work with randomness, - * supporting integers, floating-point numbers, and range-based generation. + * The `Random` module provides a service for generating pseudo-random numbers + * in Effect programs. It offers a testable and composable way to work with + * randomness, supporting integers, floating-point numbers, and range-based + * generation. + * + * The default `Random` service is not cryptographically secure. Do not use it + * for secrets, tokens, UUIDs, session identifiers, or other security-sensitive + * values. For cryptographically secure random generation, replace the service + * with a cryptographically secure implementation such as the platform `Crypto` + * service. `Random.withSeed` also replaces the service, but predictable seeds + * remain deterministic and must not be treated as cryptographically secure. * * **Example** (Generating random values) * @@ -2991,11 +3995,45 @@ export * as Queue from "./Queue.ts" export * as Random from "./Random.ts" /** + * The `RcMap` module provides a scoped, reference-counted map for sharing + * resources by key. It is useful when many fibers may request the same + * resource, such as a connection, client, session, or cached handle, and the + * resource should be acquired once, reused while it has active references, and + * released automatically when it is no longer needed. + * + * Each key is resolved with a user-provided lookup effect on first access via + * {@link get}. Further accesses to the same key share the in-flight or acquired + * resource and increment its reference count for the caller's current + * `Scope`. When those scopes close, references are released; resources can be + * closed immediately, kept alive for an idle time-to-live, invalidated + * explicitly, or bounded by a maximum capacity. + * + * `RcMap` is designed for Effect resource lifecycles rather than general + * mutable caching. The map itself is scoped, lookups require a `Scope`, and + * complex keys should provide `Equal` / `Hash` behavior when they need + * value-based lookup semantics. + * * @since 3.5.0 */ export * as RcMap from "./RcMap.ts" /** + * The `RcRef` module provides reference-counted access to a shared resource + * whose lifecycle is managed by `Scope`. An `RcRef` lazily acquires its + * resource the first time it is requested, shares that resource across active + * users, and releases it when the final scope holding a reference closes. + * + * Use `RcRef` when several scoped operations should reuse the same expensive + * or stateful resource, such as a connection, client, cache, or worker, without + * making each operation acquire and release its own copy. `make` defines how + * the resource is acquired, `get` borrows the current resource for the active + * scope, and `invalidate` forces a future `get` to acquire a fresh resource. + * + * The resource is tied to scopes rather than ordinary object reachability: + * every `get` must run with a `Scope`, and the reference count is decremented + * when that scope closes. If `idleTimeToLive` is configured, a resource whose + * reference count reaches zero can remain cached briefly before release. + * * @since 3.5.0 */ export * as RcRef from "./RcRef.ts" @@ -3229,11 +4267,86 @@ export * as RegExp from "./RegExp.ts" export * as Request from "./Request.ts" /** + * The `RequestResolver` module provides the data-loading side of + * `Effect.request`. A `Request` describes what a fiber needs, while a + * `RequestResolver` describes how to collect, batch, execute, cache, trace, + * and complete those requests. + * + * **Mental model** + * + * - A resolver receives one or more `Request.Entry` values and must complete + * each entry with either a success or failure + * - Concurrent requests made with the same resolver can be gathered into a + * batch before the resolver is run + * - Batch keys split pending requests into independent groups, which is useful + * when different backends, tenants, or query shapes must be resolved + * separately + * - Delays and `batchN` tune how long requests are collected and how large + * each batch may become + * - Resolvers can be wrapped with tracing, in-memory caching, cache services, + * and persistence without changing the request type + * + * **Common tasks** + * + * - Create a resolver from batch logic: {@link make} + * - Create grouped batch logic: {@link makeGrouped} or {@link grouped} + * - Create a resolver from pure logic: {@link fromFunction} or + * {@link fromFunctionBatched} + * - Create a resolver from effectful logic: {@link fromEffect} or + * {@link fromEffectTagged} + * - Control batching: {@link setDelay}, {@link setDelayEffect}, + * {@link batchN} + * - Add operational behavior: {@link around}, {@link race}, {@link withSpan} + * - Reuse results: {@link withCache}, {@link asCache}, {@link persisted} + * + * **Gotchas** + * + * - Every entry passed to a resolver must be completed; leaving an entry + * incomplete causes the waiting request to fail + * - Batched result collections must line up with the input entries in order + * and length when using the batched helper constructors + * - Grouping controls which requests share a resolver run; choose stable keys + * for requests that can safely be handled together + * - Caching and persistence depend on request identity and the request's + * equality semantics, so model request values deliberately when cached + * * @since 2.0.0 */ export * as RequestResolver from "./RequestResolver.ts" /** + * The `Resource` module provides refreshable, scoped values. A + * `Resource` stores the latest successful or failed acquisition result and + * can be read with {@link get}, refreshed manually with {@link refresh}, or + * refreshed automatically with {@link auto}. + * + * **Mental model** + * + * - A `Resource` wraps an acquisition `Effect` whose result is kept in a + * `ScopedRef` + * - Each refresh re-runs acquisition and replaces the stored `Exit` + * - Replacing the stored value releases resources associated with the previous + * scoped value + * - Reading a resource returns the current acquired value or fails with the + * current acquisition error + * + * **Common tasks** + * + * - Create a manually refreshed resource with {@link manual} + * - Create a schedule-driven resource with {@link auto} + * - Read the current value with {@link get} + * - Force a reload with {@link refresh} + * - Check whether an unknown value is a resource with {@link isResource} + * + * **Gotchas** + * + * - Creating a resource requires a `Scope`; when the scope closes, scoped + * values held by the resource are released + * - Failed acquisitions are stored too, so subsequent {@link get} calls fail + * until a refresh succeeds + * - Automatic refreshes run in the resource scope and stop when that scope is + * closed + * * @since 2.0.0 */ export * as Resource from "./Resource.ts" @@ -3375,6 +4488,25 @@ export * as Runtime from "./Runtime.ts" export * as Schedule from "./Schedule.ts" /** + * The `Scheduler` module defines the runtime scheduling services used by + * Effect fibers. A scheduler decides how runnable tasks are enqueued, when they + * are dispatched, and whether a fiber should yield after consuming its + * operation budget. + * + * **Common tasks** + * + * - Use {@link Scheduler} to provide a custom runtime scheduler + * - Use {@link MixedScheduler} for the default priority-aware scheduler + * - Use {@link MaxOpsBeforeYield} to tune fairness for CPU-bound fibers + * - Use {@link PreventSchedulerYield} only when a runtime should bypass yield checks + * + * **Gotchas** + * + * - Scheduler priorities affect the order of queued runtime tasks, not the + * semantic result of an `Effect` + * - Disabling scheduler yields can improve throughput for controlled workloads, + * but it can also let long-running fibers monopolize the JavaScript thread + * * @since 2.0.0 */ export * as Scheduler from "./Scheduler.ts" @@ -3714,6 +4846,25 @@ export * as SchemaGetter from "./SchemaGetter.ts" export * as SchemaIssue from "./SchemaIssue.ts" /** + * The `SchemaParser` module turns schemas into reusable runtime operations for + * constructing, validating, decoding, and encoding values. It is the execution + * layer behind a schema's AST: parsers walk the schema structure, apply + * transformations, honor parse options, run checks, and report failures as + * `SchemaIssue.Issue` values. + * + * Use this module when you need a parser with a specific result shape: + * `Effect` for effectful parsing and service requirements, `Promise` for + * JavaScript interop, `Exit` or `Result` when failures should stay in data, + * `Option` for yes/no validation, and synchronous helpers when throwing is the + * desired boundary. + * + * Decoding reads from the encoded/input side of a schema into its decoded + * `Type`, while encoding runs the schema in the opposite direction. The + * `make*` helpers construct decoded values and apply constructor defaults before + * validation. Parse options supplied when a parser is created are merged with + * options supplied at call time, and schema-level parse annotations can further + * refine behavior. + * * @since 4.0.0 */ export * as SchemaParser from "./SchemaParser.ts" @@ -3897,6 +5048,22 @@ export * as SchemaRepresentation from "./SchemaRepresentation.ts" export * as SchemaTransformation from "./SchemaTransformation.ts" /** + * The `SchemaUtils` module contains focused helpers for schema patterns that + * are useful but too specialized for the core `Schema` API surface. + * + * Use this module when you need to describe a native class with a schema while + * keeping a plain struct as its encoded representation. This is especially + * useful for classes such as `Data.Error` subclasses that should decode from + * structured data, encode back to that data, and still preserve class identity + * for instance checks and schema optics. + * + * **Gotchas** + * + * - The constructor is called with the decoded struct fields as a single + * argument, so the class constructor must accept that shape. + * - Encoding uses the instance itself as the encoded shape, so the instance + * should expose properties compatible with the provided struct schema. + * * @since 4.0.0 */ export * as SchemaUtils from "./SchemaUtils.ts" @@ -3919,31 +5086,179 @@ export * as SchemaUtils from "./SchemaUtils.ts" export * as Scope from "./Scope.ts" /** + * The `ScopedCache` module provides a cache for values that acquire scoped + * resources during lookup. Each cached entry owns a `Scope`, so resources + * created while computing a value stay alive for as long as that entry remains + * cached and are released when the entry is removed. + * + * A `ScopedCache` is itself created inside a scope. Calls to {@link get} run the + * lookup effect on cache misses, share the same in-flight lookup among + * concurrent callers for the same key, and store the resulting exit according + * to a time-to-live policy. Entries can be inserted manually with {@link set}, + * refreshed with {@link refresh}, inspected without triggering lookup with + * {@link getOption}, and removed with {@link invalidate} or + * {@link invalidateAll}. Capacity limits evict the oldest entries. + * + * **Lifecycle notes** + * + * - Entry scopes are closed when entries expire, are invalidated, are evicted, + * are replaced, or when the cache's owning scope closes + * - Successful and failed lookup exits are both cached according to the + * configured TTL + * - Expired entries may remain counted by {@link size} until a cache operation + * observes and removes them + * - Once the owning scope closes, the cache is closed and lookup-style + * operations interrupt instead of acquiring new values + * * @since 4.0.0 */ export * as ScopedCache from "./ScopedCache.ts" /** + * The `ScopedRef` module provides a mutable reference for values that are tied + * to scoped resources. Each value stored in a `ScopedRef` is acquired within its + * own `Scope`, and replacing the value safely releases the resources associated + * with the previous value. + * + * Use `ScopedRef` when an application needs to keep a current resource-backed + * value, such as a live client, connection, subscription, or cached handle, and + * later swap it for a newly acquired value without leaking the old resources. + * Reads are simple, while updates are synchronized and resource-safe. + * + * **Gotchas** + * + * - A `ScopedRef` must itself be created and used within a `Scope`; when that + * scope closes, the currently stored value is finalized. + * - Use {@link fromAcquire} or {@link set} for resourceful values so acquisition + * and finalization are tracked correctly. + * - Use {@link make} only for values that do not acquire resources. + * - Updating a `ScopedRef` waits for the replacement acquisition and old + * finalization to complete before returning. + * * @since 2.0.0 */ export * as ScopedRef from "./ScopedRef.ts" /** + * The `Semaphore` module provides a counting semaphore for coordinating + * concurrent access to shared or limited resources. A semaphore tracks a fixed + * number of permits: effects acquire permits before entering a critical section + * and release them when they leave. + * + * Use semaphores to bound parallel work, protect rate-limited services, or + * serialize access to resources that cannot safely handle unlimited + * concurrency. Prefer {@link withPermit} and {@link withPermits} when possible, + * because they release permits automatically when the protected effect exits. + * Use {@link take} and {@link release} for lower-level protocols that need + * manual control. + * + * **Gotchas** + * + * - Pending acquisitions wait until enough permits are available. + * - {@link withPermitsIfAvailable} does not wait; it returns `Option.none` when + * the requested permits cannot be acquired immediately. + * - Manual `take` / `release` usage must keep permit counts balanced. + * * @since 2.0.0 */ export * as Semaphore from "./Semaphore.ts" /** + * The `Sink` module provides composable consumers for `Stream` values. A + * `Sink` pulls input elements of type `In`, may require + * services `R`, may fail with `E`, and eventually produces a result `A` plus + * any leftover input `L` that was read but not consumed. + * + * **Mental model** + * + * - A sink is the terminal consumer used by `Stream.run` + * - Sinks can consume zero, one, many, or all input elements before finishing + * - Leftovers allow one sink to stop early without losing already-pulled input + * - Sink composition preserves typed errors and service requirements + * - Most sinks are built from `Channel` internally, but users compose them with + * the higher-level APIs in this module + * + * **Common tasks** + * + * - Create simple sinks: {@link succeed}, {@link fail}, {@link fromEffect} + * - Fold input: {@link fold}, {@link foldEffect}, {@link foldLeft} + * - Collect values: {@link collectAll}, {@link collectAllN}, {@link collectAllWhile} + * - Count or drain input: {@link count}, {@link drain} + * - Transform results: {@link map}, {@link mapEffect}, {@link as} + * - Combine sinks: {@link zip}, {@link zipWith}, {@link race} + * - Filter and refine input: {@link filterInput}, {@link filterInputEffect} + * + * **Gotchas** + * + * - A sink can finish before the stream is exhausted; check leftover-aware + * combinators when composing parsers or protocol decoders + * - `In` is contravariant, so a sink that accepts broader input can be used + * where narrower input is expected + * - Resource and service requirements are tracked in the `R` type parameter + * * @since 2.0.0 */ export * as Sink from "./Sink.ts" /** + * The `Stdio` module defines the service interface used by Effect programs to + * interact with process standard I/O. It models command-line arguments, + * standard output, standard error, and standard input as Effects, Sinks, and + * Streams so programs can depend on console I/O through `Context` instead of + * directly coupling to a specific runtime. + * + * Use this module when building command-line programs, tests, or platform + * integrations that need to read bytes from stdin, write text or bytes to + * stdout/stderr, or provide deterministic replacements for those capabilities. + * The `layerTest` helper is useful for tests because it supplies inert defaults + * and lets individual fields be overridden. + * + * Standard I/O operations are platform capabilities and may fail with + * `PlatformError`; handle those failures in the Effect error channel rather than + * assuming writes or reads are infallible. + * * @since 4.0.0 */ export * as Stdio from "./Stdio.ts" /** + * The `Stream` module provides a typed, composable way to describe effectful + * sequences of values. A `Stream` can emit zero or more `A` values, + * fail with an `E`, and require services from `R` while preserving + * backpressure and resource safety. + * + * **Mental model** + * + * - A stream is a lazy description; it runs only when consumed with a `run*` function + * - Streams are pull-based and emit chunks internally for efficient throughput + * - `A` is the element type, `E` is the failure type, and `R` is the required context + * - Stream composition mirrors `Effect`: use `map`, `flatMap`, error handling, and `pipe` + * - Resource scopes, interruption, and finalizers are tracked by the Effect runtime + * - Interop functions connect streams to queues, pub/subs, web streams, async iterables, and channels + * + * **Common tasks** + * + * - Create streams: {@link make}, {@link fromIterable}, {@link fromEffect}, {@link fromQueue} + * - Transform values: {@link map}, {@link mapEffect}, {@link flatMap}, {@link filter} + * - Combine streams: {@link concat}, {@link merge}, {@link zip}, {@link race} + * - Control demand and timing: {@link take}, {@link drop}, {@link debounce}, {@link throttle} + * - Manage errors: {@link catchCause}, {@link catchIf}, {@link mapError}, {@link retry} + * - Manage resources and services: {@link scoped}, {@link ensuring}, {@link provide} + * - Consume streams: {@link runCollect}, {@link runForEach}, {@link runFold}, {@link runDrain} + * + * **Gotchas** + * + * - A stream is not a collection; constructors and operators build a description until it is run + * - Re-running a stream re-executes its effects unless it is explicitly shared or backed by external state + * - Operators such as {@link merge}, {@link race}, and {@link broadcast} introduce concurrency and interruption semantics + * - Prefer bounded constructors and sinks for large or infinite streams instead of collecting everything into memory + * + * **See also** + * + * - {@link Effect.Effect} for single-result effectful programs + * - {@link Sink.Sink} for consuming and folding streams + * - {@link Channel.Channel} for the lower-level primitive underlying streams + * * @since 2.0.0 */ export * as Stream from "./Stream.ts" @@ -4034,31 +5349,147 @@ export * as String from "./String.ts" export * as Struct from "./Struct.ts" /** + * The `SubscriptionRef` module provides a mutable reference that can be read + * and updated like a `Ref`, while also exposing a stream of its current value + * and every subsequent change. It is useful when one part of an application + * owns evolving state and many fibers need to subscribe to consistent updates, + * such as configuration, coordination state, cached snapshots, or UI models. + * + * Updates are serialized with an internal semaphore and each update is + * published to subscribers. The {@link changes} stream replays the latest value + * first, then emits future updates, so new subscribers can start from the + * current state without performing a separate read. Prefer the effectful + * getters and update operations for concurrent code; the unsafe helpers bypass + * synchronization and should only be used when the caller already controls + * access. + * * @since 2.0.0 */ export * as SubscriptionRef from "./SubscriptionRef.ts" /** + * The `Symbol` module provides a small runtime guard for working with + * JavaScript `symbol` values. Use {@link isSymbol} when validating unknown + * input, narrowing union types, or building predicates that need to recognize + * primitive symbols such as those created by `Symbol()` or `Symbol.for`. + * + * The guard checks for the primitive `symbol` type; boxed objects created with + * `Object(Symbol())` are objects and do not satisfy this predicate. + * * @since 2.0.0 */ export * as Symbol from "./Symbol.ts" /** + * The `SynchronizedRef` module provides mutable references whose updates are + * serialized, including updates that run effects before deciding the next + * value. A `SynchronizedRef` behaves like a `Ref` for reading and basic + * updates, but uses an internal semaphore so concurrent modifications observe a + * consistent current value and apply one at a time. + * + * **When to use** + * + * - Coordinating shared state that may be updated by many fibers + * - Running effectful state transitions that must not overlap + * - Computing both a return value and a new stored value atomically + * - Applying partial updates with `Option`, where `None` leaves the value + * unchanged + * + * **Gotchas** + * + * - Effectful update functions run while the semaphore is held, so long-running + * effects delay other updates to the same ref + * - Failed effectful updates do not replace the stored value + * - `getUnsafe` and `makeUnsafe` bypass the `Effect` API and should be reserved + * for low-level or carefully controlled code + * * @since 2.0.0 */ export * as SynchronizedRef from "./SynchronizedRef.ts" /** + * The `Take` module provides the representation used by stream-like producers + * to describe a single pull result. A `Take` is either a + * non-empty batch of emitted values, a failed `Exit`, or a successful `Exit` + * carrying the stream's completion value. + * + * `Take` is useful at boundaries where pull results need to be stored, + * transferred, or interpreted later while preserving the distinction between + * emitted elements, failures, and normal completion. Use {@link toPull} to turn + * a `Take` back into a `Pull`: value batches become successful pulls, failure + * exits are propagated, and successful exits signal completion with `Done`. + * + * **Gotchas** + * + * - A value batch is always represented by a `NonEmptyReadonlyArray`; empty + * batches are not valid `Take` values. + * - Successful `Exit` values do not emit elements. They represent pull + * completion and carry the `Done` value. + * * @since 2.0.0 */ export * as Take from "./Take.ts" /** + * The `Terminal` module defines the service interface used by platform + * integrations to model command-line input and output. It gives programs a + * uniform way to query terminal dimensions, read lines, stream low-level key + * events, and write text without depending directly on Node, the browser, or a + * test-specific console implementation. + * + * Use this module when building interactive command-line tools, prompts, or + * platform abstractions that need terminal capabilities as an Effect service. + * Implementations are supplied through context, so application code can depend + * on `Terminal` while tests and runtimes provide the concrete behavior. + * + * `readLine` can fail with {@link QuitError} when the user requests to quit, + * commonly via `Ctrl+C`. For lower-level interaction, `readInput` returns a + * scoped stream of {@link UserInput} values containing parsed key metadata and + * any raw character input. + * * @since 4.0.0 */ export * as Terminal from "./Terminal.ts" /** + * The `Tracer` module defines the low-level tracing model used by Effect to + * describe and propagate spans. A span records the lifetime of an operation, + * including its name, parent, attributes, links, annotations, sampling decision, + * kind, and completion status. + * + * **Mental model** + * + * - `Tracer` is the backend interface responsible for creating spans + * - `Span` values represent Effect-managed operations with mutable lifecycle + * hooks for ending spans and adding attributes, events, or links + * - `ExternalSpan` represents trace context imported from another tracing + * system so Effect spans can be parented by or linked to external work + * - `ParentSpan`, `Tracer`, and related context references control propagation, + * sampling, and trace-level filtering through the Effect context + * + * **Common tasks** + * + * - Implement a custom tracing backend with {@link make} + * - Provide or inspect parent span context with {@link ParentSpan} + * - Convert external trace identifiers into Effect span values with + * {@link externalSpan} + * - Configure span metadata with {@link SpanOptions}, {@link SpanKind}, and + * {@link SpanLink} + * - Disable propagation or adjust trace filtering with + * {@link DisablePropagation}, {@link CurrentTraceLevel}, and + * {@link MinimumTraceLevel} + * + * **Gotchas** + * + * - This module exposes the tracing data model and backend hooks; most + * application code should create spans through higher-level Effect APIs such + * as `Effect.withSpan` + * - `ExternalSpan` only carries identity and metadata from another system; it + * does not have lifecycle methods like `Span` + * - Propagation and sampling are context-dependent, so parent selection can be + * affected by disabled propagation, root span options, and trace-level + * thresholds + * * @since 2.0.0 */ export * as Tracer from "./Tracer.ts" @@ -4177,11 +5608,56 @@ export * as TxChunk from "./TxChunk.ts" export * as TxDeferred from "./TxDeferred.ts" /** + * The `TxHashMap` module provides a transactional hash map for storing and + * updating key-value pairs inside Effect transactions. It is useful when + * multiple fibers need to coordinate shared map state and each read-modify-write + * sequence must be committed atomically. + * + * A `TxHashMap` has the familiar shape of a `HashMap`, but every + * operation returns an `Effect` and participates in transaction semantics + * through `TxRef`. Use it for concurrent registries, caches, counters, indexes, + * and other mutable maps whose updates should compose safely with other + * transactional references. + * + * **Common tasks** + * + * - Create maps with {@link empty}, {@link fromIterable}, or {@link make} + * - Read entries with {@link get}, {@link has}, {@link keys}, {@link values}, and {@link entries} + * - Update entries with {@link set}, {@link modify}, {@link modifyAt}, and {@link remove} + * - Inspect aggregate state with {@link size}, {@link isEmpty}, and {@link reduce} + * + * **Gotchas** + * + * - Operations are effectful; run them in `Effect.gen` and wrap multi-step + * transactions with `Effect.tx` when the whole sequence must commit together. + * - Reads that may be absent return `Option`, so handle both `Some` and `None` + * instead of assuming a key exists. + * * @since 2.0.0 */ export * as TxHashMap from "./TxHashMap.ts" /** + * The `TxHashSet` module provides a transactional hash set for storing unique + * values inside Effect transactions. A `TxHashSet` wraps a `HashSet` in a + * transactional reference, so reads and writes can be composed with other + * transactional operations and committed atomically. + * + * **Common tasks** + * + * - Create transactional sets with {@link empty}, {@link make}, or {@link fromIterable} + * - Mutate an existing set with {@link add}, {@link remove}, and {@link clear} + * - Query membership and size with {@link has}, {@link size}, and {@link isEmpty} + * - Derive new sets with {@link map}, {@link filter}, {@link union}, {@link intersection}, and {@link difference} + * - Fold or collect values with {@link reduce}, {@link toArray}, and {@link toHashSet} + * + * **Gotchas** + * + * - Mutation operations update the same transactional set; transform operations + * return a new `TxHashSet` + * - Operations are `Effect` values and must be yielded, piped, or run to take effect + * - Use `Effect.tx` when several operations must observe and commit one atomic transaction + * * @since 2.0.0 */ export * as TxHashSet from "./TxHashSet.ts" @@ -4241,6 +5717,24 @@ export * as TxReentrantLock from "./TxReentrantLock.ts" export * as TxRef from "./TxRef.ts" /** + * The `TxSemaphore` module provides a transactional semaphore for coordinating + * access to limited resources from within Effect transactions. A semaphore + * tracks a fixed number of permits, and transactional operations can acquire, + * release, or inspect those permits atomically with other transactional state. + * + * Use `TxSemaphore` when permit accounting needs to compose with `TxRef` and + * other transactional updates, such as guarding resource pools, rate-limited + * sections, or workflows that must reserve capacity consistently before + * committing related state changes. + * + * **Gotchas** + * + * - Permit operations are intended for transactional workflows and are wrapped + * with `Effect.tx`. + * - The semaphore capacity is fixed at construction time; releasing more + * permits than the original capacity fails. + * - Creating a semaphore with a negative number of permits defects. + * * @since 4.0.0 */ export * as TxSemaphore from "./TxSemaphore.ts" @@ -4341,6 +5835,19 @@ export * as Types from "./Types.ts" export * as UndefinedOr from "./UndefinedOr.ts" /** + * The `Unify` module contains the type-level protocol Effect uses to normalize + * unions of data types that opt in to unification. It is primarily a library + * authoring tool: data types expose hidden symbol properties describing how + * their variants should be widened, and {@link Unify} turns those protocol + * entries into the user-facing union type that TypeScript should infer. + * + * Most application code does not need to interact with these symbols directly. + * The main runtime helper, {@link unify}, is an identity function that preserves + * values and functions at runtime while applying {@link Unify} to the relevant + * static type. This is useful when authoring APIs that return branded or + * protocol-enabled values and need inference to collapse to the public Effect + * data type rather than exposing implementation details. + * * @since 2.0.0 */ export * as Unify from "./Unify.ts" diff --git a/packages/effect/src/testing/index.ts b/packages/effect/src/testing/index.ts index 459557cbab..4d0c2f6e61 100644 --- a/packages/effect/src/testing/index.ts +++ b/packages/effect/src/testing/index.ts @@ -18,11 +18,45 @@ export * as FastCheck from "./FastCheck.ts" /** + * The `TestClock` module provides a controllable implementation of the Effect + * `Clock` service for tests. Instead of waiting for real time to pass, effects + * that use `Effect.sleep`, timeouts, schedules, retries, and other time-based + * operators can be driven deterministically by advancing the test clock. + * + * **Common use cases** + * + * - Testing sleeps, delays, timeouts, debouncing, retries, and schedules without + * slowing the test suite down + * - Advancing time with {@link adjust} or jumping to an exact timestamp with + * {@link setTime} + * - Running a specific effect against the live clock with {@link withLive} + * while the rest of the test remains under test-clock control + * + * **Testing gotchas** + * + * - Effects that sleep semantically block until the clock is advanced far + * enough, so tests usually fork the time-dependent effect before calling + * {@link adjust} or {@link setTime} + * - Scheduled sleeps are resumed in clock-time order as the test clock moves + * forward + * - If a test uses time but never advances the `TestClock`, the module starts a + * delayed warning to help identify a hanging test + * * @since 2.0.0 */ export * as TestClock from "./TestClock.ts" /** + * The `TestConsole` module provides a test implementation of the `Console` + * service that records console calls instead of writing them to the host + * environment. It is useful when testing workflows that use `Console.log` or + * `Console.error` and need to assert on the produced output. + * + * Use {@link layer} to provide the test console to an effect, then inspect + * captured output with {@link logLines} and {@link errorLines}. Because console + * operations are service-based effects, programs under test must be run with + * this layer for their output to be captured. + * * @since 4.0.0 */ export * as TestConsole from "./TestConsole.ts" diff --git a/packages/effect/src/unstable/ai/index.ts b/packages/effect/src/unstable/ai/index.ts index 6b6ef5a2f3..2c3aa91513 100644 --- a/packages/effect/src/unstable/ai/index.ts +++ b/packages/effect/src/unstable/ai/index.ts @@ -282,11 +282,62 @@ export * as IdGenerator from "./IdGenerator.ts" export * as LanguageModel from "./LanguageModel.ts" /** + * The `McpSchema` module defines Effect Schema and RPC models for the Model + * Context Protocol (MCP). It provides the shared protocol vocabulary used by + * MCP clients and servers, including JSON-RPC request identifiers, metadata, + * capabilities, errors, resources, prompts, tools, logging, sampling, + * completions, roots, and elicitation. + * + * **Common tasks** + * + * - Describe MCP payloads with schemas such as {@link Resource}, {@link Tool}, + * {@link Prompt}, and {@link ContentBlock} + * - Build typed protocol handlers with request RPCs such as {@link Initialize}, + * {@link ListResources}, {@link ReadResource}, {@link ListTools}, and + * {@link CallTool} + * - Work with protocol notifications such as + * {@link ProgressNotification}, {@link ResourceUpdatedNotification}, and + * {@link LoggingMessageNotification} + * - Use {@link ClientRpcs} and server RPC groups to derive encoded request, + * notification, success, and failure message types + * + * **Gotchas** + * + * - MCP distinguishes absent fields from present fields whose value is + * `undefined`; use {@link optional} and {@link optionalWithDefault} for + * protocol fields so encoding omits undefined values consistently. + * - Capability objects are extensible. Known fields are modeled here, but + * `experimental` and `extensions` allow implementations to advertise + * additional protocol features. + * * @since 4.0.0 */ export * as McpSchema from "./McpSchema.ts" /** + * The `McpServer` module provides Effect services and layers for building + * Model Context Protocol servers. It keeps track of registered tools, + * resources, resource templates, prompts, completions, and server + * notifications, then exposes them through the MCP request handlers. + * + * **Common tasks** + * + * - Start a server over stdio with {@link layerStdio} + * - Register HTTP routes for an existing `HttpRouter` with {@link layerHttp} + * - Expose Effect AI toolkits as MCP tools with {@link registerToolkit} + * - Register resources, resource templates, and prompts with {@link resource} + * and {@link prompt} + * - Ask the connected MCP client for structured input with {@link elicit} + * + * **Gotchas** + * + * - Registration helpers require an `McpServer` service, usually provided by + * one of this module's layers. + * - HTTP clients must complete MCP initialization before other requests; the + * server tracks initialized sessions with the `Mcp-Session-Id` header. + * - Resource template parameters are decoded with the schemas embedded in the + * template literal. + * * @since 4.0.0 */ export * as McpServer from "./McpServer.ts" @@ -420,6 +471,18 @@ export * as Prompt from "./Prompt.ts" export * as Response from "./Response.ts" /** + * The `ResponseIdTracker` module provides a small service for reusing provider + * response IDs across incremental language model calls. It records which prompt + * message objects were sent for a provider response, then prepares a later + * prompt by returning the recognized `previousResponseId` together with only + * the new messages that should be sent. + * + * Use this when integrating providers that support continuing a conversation + * from a prior response ID instead of resending the entire prompt. The tracker + * is intentionally identity-based and mutable: it only recognizes the same + * message objects that were previously marked, and it clears its state when a + * prompt can no longer be matched safely. + * * @since 4.0.0 */ export * as ResponseIdTracker from "./ResponseIdTracker.ts" @@ -540,10 +603,9 @@ export * as Tool from "./Tool.ts" * **Example** (Creating and implementing toolkits) * * ```ts - * import { Effect, Schema } from "effect" + * import { Effect, Schema, Stream } from "effect" * import { Tool, Toolkit } from "effect/unstable/ai" * - * // Create individual tools * const GetCurrentTime = Tool.make("GetCurrentTime", { * description: "Get the current timestamp", * success: Schema.Number @@ -558,17 +620,29 @@ export * as Tool from "./Tool.ts" * }) * }) * - * // Create a toolkit with multiple tools * const MyToolkit = Toolkit.make(GetCurrentTime, GetWeather) * * const MyToolkitLayer = MyToolkit.toLayer({ - * GetCurrentTime: () => Effect.succeed(Date.now()), + * GetCurrentTime: () => Effect.succeed(1_704_067_200_000), * GetWeather: ({ location }) => * Effect.succeed({ * temperature: 72, - * condition: "sunny" + * condition: `sunny in ${location}` * }) * }) + * + * const program = Effect.gen(function*() { + * const toolkit = yield* MyToolkit + * const stream = yield* toolkit.handle("GetWeather", { + * location: "San Francisco" + * }) + * const results = yield* Stream.runCollect(stream) + * + * return Array.from(results, ({ result }) => result) + * }).pipe(Effect.provide(MyToolkitLayer)) + * + * console.log(Effect.runSync(program)) + * // [{ temperature: 72, condition: "sunny in San Francisco" }] * ``` * * @since 4.0.0 diff --git a/packages/effect/src/unstable/cli/index.ts b/packages/effect/src/unstable/cli/index.ts index d81b5187f0..a8b4c0a59b 100644 --- a/packages/effect/src/unstable/cli/index.ts +++ b/packages/effect/src/unstable/cli/index.ts @@ -5,21 +5,116 @@ // @barrel: Auto-generated exports. Do not edit manually. /** + * The `Argument` module defines typed positional command-line arguments for + * Effect CLI applications. Arguments consume ordered values after a command name + * and its flags, then parse them into the types your command handler expects. + * + * Use this module for required inputs such as file paths, directories, numbers, + * dates, choices, secrets, and structured configuration files. Arguments can be + * made optional, variadic, validated with Schema, transformed with pure or + * effectful functions, and supplied from defaults, config, or prompts when + * missing. + * + * **Gotchas** + * + * - Positional arguments are order-sensitive; compose them in the same order the + * user should type them. + * - `variadic` arguments can consume more than one value, so place them where + * the remaining positional input belongs. + * - Use {@link withDescription} and {@link withMetavar} to keep generated help + * text clear, especially when the parser name differs from the value users + * should provide. + * - Boolean positional arguments are intentionally not provided. Prefer boolean + * flags, or use {@link choice} for explicit `"true"` / `"false"` values. + * * @since 4.0.0 */ export * as Argument from "./Argument.ts" /** + * The `CliError` module defines the structured error model used by the + * unstable CLI parser and runner. It distinguishes command-line parse failures, + * CLI definition problems, explicit help requests, and user handler failures so + * applications can report errors consistently while still pattern matching on + * the exact cause. + * + * **Common tasks** + * + * - Detect CLI errors at runtime with {@link isCliError} + * - Represent parse failures such as unknown flags, missing required inputs, or + * invalid argument values + * - Attach parse or validation errors to {@link ShowHelp} when the runner should + * render help text together with the failure + * - Preserve command handler failures with {@link UserError} + * + * **Gotchas** + * + * - {@link ShowHelp} is a control-flow error, not a parse failure; it exits with + * code `0` for explicit help and `1` when it carries errors + * - Duplicate option names between parent and child commands are rejected + * because the parent command claims the flag before the child can see it + * - Suggestion-bearing errors keep suggestions separate from the primary cause + * so help renderers can decide how much guidance to display + * * @since 4.0.0 */ export * as CliError from "./CliError.ts" /** + * The `CliOutput` module provides the formatting service used by Effect CLI + * applications to turn parsed CLI metadata and failures into terminal text. + * It renders help documents, parser errors, grouped errors, and version output, + * while allowing applications and tests to replace the formatter through a + * `Context` service or `Layer`. + * + * **Common tasks** + * + * - Render generated {@link HelpDoc} values for `--help` output + * - Display parser failures and validation errors from CLI programs + * - Customize output for plain text, colored terminals, tests, or alternate + * formats + * - Format version strings consistently with the rest of CLI output + * + * **Gotchas** + * + * - Color output is auto-detected from `process.stdout.isTTY` and `NO_COLOR`, + * but can be forced with {@link defaultFormatter} + * - Help tables measure visible width after stripping ANSI escape codes, so + * colored output stays aligned with plain output + * * @since 4.0.0 */ export * as CliOutput from "./CliOutput.ts" /** + * The `Command` module provides the core building block for defining and + * running Effect-based command-line applications. A `Command` combines a name, + * typed flags and positional arguments, optional subcommands, metadata for help + * output, and an effectful handler. + * + * **Common tasks** + * + * - Create commands with {@link make} + * - Add handlers with {@link withHandler} + * - Build nested command trees with {@link withSubcommands} + * - Share parent flags with subcommands using {@link withSharedFlags} + * - Add command-scoped global flags with {@link withGlobalFlags} + * - Attach help metadata with {@link withDescription}, {@link withShortDescription}, + * {@link withAlias}, and {@link withExamples} + * - Provide handler dependencies with {@link provide}, {@link provideSync}, + * {@link provideEffect}, and {@link provideEffectDiscard} + * - Execute commands with {@link run} or test them with {@link runWith} + * + * **Gotchas** + * + * - `withSharedFlags` accepts only flags, not positional arguments, and the + * parsed values are available to descendants by yielding the parent command. + * - Shared flags may be written before or after the selected subcommand name. + * - Duplicate flags across command scopes are rejected so parsing and help + * output remain unambiguous. + * - `runWith` is the preferred entry point for tests because it accepts an + * explicit argument array instead of reading from the `Stdio` service. + * * @since 4.0.0 */ export * as Command from "./Command.ts" @@ -32,30 +127,110 @@ export * as Command from "./Command.ts" export * as Completions from "./Completions.ts" /** + * The `Flag` module provides typed command-line options for Effect CLI + * applications. A `Flag` describes how to read one named option from the + * parsed command line, validate it, and produce a value of type `A`. + * + * Use flags for inputs that are naturally named options, such as ports, + * verbosity switches, configuration files, output directories, enum-like + * choices, secrets, and repeated values. Constructors such as {@link string}, + * {@link boolean}, {@link integer}, {@link file}, and {@link fileSchema} + * define the accepted input shape, while combinators add aliases, defaults, + * optionality, fallback config or prompts, validation, and typed mapping. + * + * Flag names are rendered as long options, for example `Flag.integer("port")` + * parses `--port 8080`. Boolean flags also support the disabled form shown by + * this module's boolean documentation, and repeated flags are modeled with the + * repetition combinators instead of by manually inspecting raw arguments. Help + * text is generated from flag metadata, so prefer {@link withDescription} and + * {@link withMetavar} when a flag's value, format, or file-system expectation + * would otherwise be ambiguous. + * * @since 4.0.0 */ export * as Flag from "./Flag.ts" /** + * The `GlobalFlag` module defines flags that are available to every command in + * an Effect CLI application. Global flags are useful for cross-cutting command + * line behavior such as printing help, showing the application version, + * generating shell completions, or configuring shared handler settings like the + * minimum log level. + * + * **Common tasks** + * + * - Create an action flag with {@link action} for side effects that should run + * before the selected command, such as `--help` or `--version` + * - Create a setting flag with {@link setting} for values that should be made + * available to command handlers through the Effect context + * - Reuse the built-in {@link Help}, {@link Version}, {@link Completions}, and + * {@link LogLevel} flags when constructing command runners + * + * **Gotchas** + * + * - Action flags are intended to perform their effect and exit instead of + * continuing into the command handler + * - Setting flags allocate a distinct context service for each call to + * {@link setting}, so reuse exported settings when handlers need to read the + * same parsed global value + * * @since 4.0.0 */ export * as GlobalFlag from "./GlobalFlag.ts" /** + * The `HelpDoc` module defines the structured documentation model used by the + * unstable CLI package to describe command help. A `HelpDoc` value captures the + * user-facing parts of a command, including its description, usage string, + * positional arguments, flags, global flags, subcommands, and examples. + * + * **Common tasks** + * + * - Build help data from command definitions before rendering it + * - Pass command documentation to `CliOutput.Formatter` implementations + * - Represent custom help output formats without changing command parsing + * - Group subcommands and distinguish local flags from global flags + * + * **Gotchas** + * + * - `HelpDoc` is format-agnostic; layout, ANSI styling, and table alignment are + * handled by the output formatter + * - Optional argument and flag descriptions use `Option.Option`, while + * optional sections are omitted when they have no entries + * - Long names, aliases, and descriptions may require formatter-specific width + * handling when rendering terminal help + * * @since 4.0.0 */ export * as HelpDoc from "./HelpDoc.ts" /** - * @internal + * The `Param` module defines the shared parser tree used by the unstable CLI + * `Argument` and `Flag` modules. A `Param` describes how to consume + * either positional arguments or named flags from parsed command-line input and + * return a typed value. * - * Param is the polymorphic implementation shared by Argument.ts and Flag.ts. - * The `Kind` type parameter ("argument" | "flag") enables type-safe separation - * while sharing parsing logic and combinators. + * **Common tasks** * - * Users should import from `Argument` and `Flag` modules, not this module directly. - * This module is not exported from the public API. + * - Build primitive CLI inputs such as strings, booleans, numbers, choices, + * paths, files, and redacted values + * - Attach help metadata with aliases and descriptions + * - Transform parsed values with pure or effectful validation + * - Model missing inputs with `Option`, defaults, config fallbacks, or prompts + * - Accept repeated inputs with variadic, bounded, and non-empty parameters * + * **Gotchas** + * + * - The `Kind` type parameter (`"argument"` or `"flag"`) keeps positional + * arguments and flags separate while allowing the implementation and + * combinators to be shared. + * - Combinators preserve the parameter kind, so an argument parameter cannot be + * accidentally composed into a flag parameter or the reverse. + * - Parsers return both the remaining positional arguments and the parsed + * value; this is important for argument ordering and variadic parameters. + * - Some parsers require CLI services such as filesystem, path, terminal, or + * child-process support through the parsing environment. + * @internal * @since 4.0.0 */ export * as Param from "./Param.ts" @@ -76,6 +251,30 @@ export * as Param from "./Param.ts" export * as Primitive from "./Primitive.ts" /** + * The `Prompt` module provides composable, effectful building blocks for + * interactive command-line questions. A `Prompt` describes terminal UI that + * renders frames, reads keyboard input, validates responses, and eventually + * produces a value of type `A`. + * + * **Common tasks** + * + * - Ask for text, password, hidden, list, confirm, toggle, number, or date input + * - Let users choose from select, autocomplete, multi-select, and file prompts + * - Combine prompts with {@link all}, {@link map}, and {@link flatMap} + * - Build specialized prompts with {@link custom} + * - Run a prompt against the current terminal with {@link run} + * + * **Gotchas** + * + * - Prompts require terminal services and may fail with `Terminal.QuitError` + * when input ends or the prompt is quit + * - Rendering is frame-based: custom prompts must return ANSI output from + * `render` and matching ANSI clearing output from `clear` + * - Choices and file lists are paged by `maxPerPage`, so keyboard navigation + * and filtering should account for hidden off-page entries + * - `password` and `hidden` return `Redacted` values; unwrap them only at the + * boundary where the secret is needed + * * @since 4.0.0 */ export * as Prompt from "./Prompt.ts" diff --git a/packages/effect/src/unstable/cluster/index.ts b/packages/effect/src/unstable/cluster/index.ts index 3ffc13b9bc..11eb8936a0 100644 --- a/packages/effect/src/unstable/cluster/index.ts +++ b/packages/effect/src/unstable/cluster/index.ts @@ -5,191 +5,981 @@ // @barrel: Auto-generated exports. Do not edit manually. /** + * The `ClusterCron` module provides a small integration between cron schedules + * and cluster sharding. It turns a `Cron.Cron` schedule into a `Layer` that + * coordinates one recurring job across a cluster by registering a singleton for + * the initial scheduling step and a persisted entity message for each run. + * + * This is useful for distributed maintenance work such as periodic cleanup, + * reconciliation, report generation, cache refreshes, or polling external + * systems where the job should be owned by the cluster rather than by every + * runner independently. + * + * **Mental model** + * + * - {@link make} registers a named cluster cron job as a layer + * - a singleton schedules the first run for the selected shard group + * - each run is delivered as a persisted entity message at its scheduled time + * - after a run exits, the handler schedules the next occurrence + * - stale runs can be skipped with `skipIfOlderThan` + * + * **Gotchas** + * + * - Job effects should be idempotent because persisted messages, retries, and + * runner failover are part of normal distributed execution. + * - By default, the next run is calculated from the current time after the + * handler exits; use `calculateNextRunFromPrevious` when preserving the + * schedule cadence is more important than catching up from delays. + * - Long outages can produce old scheduled messages; keep `skipIfOlderThan` + * aligned with the job's business semantics. + * * @since 4.0.0 */ export * as ClusterCron from "./ClusterCron.ts" /** + * The `ClusterError` module defines the typed error values used by the + * unstable cluster runtime when routing messages to entities, coordinating + * runners, and persisting mailbox work. + * + * These errors are useful when implementing cluster transports, runner + * supervision, mailbox storage, and entity request handling. They make common + * distributed-system failures explicit: a message may reach a runner that no + * longer owns the entity, a runner may be unavailable or unregistered, a + * payload may fail to decode, persistence may fail, a mailbox may be at + * capacity, or an envelope may already be in progress. + * + * **Gotchas** + * + * - Entity ownership and runner availability can change while messages are in + * flight, so routing errors should generally be treated as retryable or + * recoverable by higher-level cluster logic. + * - `MalformedMessage` points to a schema/serialization boundary failure, + * while `PersistenceError` preserves failures from durable mailbox storage. + * - `AlreadyProcessingMessage` protects an entity mailbox from processing the + * same envelope concurrently. + * * @since 4.0.0 */ export * as ClusterError from "./ClusterError.ts" /** + * The `ClusterMetrics` module defines the standard metrics emitted by the + * unstable cluster runtime. These gauges track the shape and health of a + * running cluster from the perspective of runners, entities, singletons, and + * shard ownership. + * + * **Common tasks** + * + * - Monitor how many entity instances and singleton processes are active on a + * runner + * - Track registered runners and the subset currently considered healthy + * - Observe shard distribution across runners during startup, rebalancing, and + * failover + * + * **Gotchas** + * + * - Runner-local gauges such as {@link entities}, {@link singletons}, and + * {@link shards} describe the current runner, so aggregate them carefully in + * dashboards + * - Cluster-wide gauges such as {@link runners} and {@link runnersHealthy} + * reflect the runtime's current view, which may lag briefly during membership + * changes or failure detection + * * @since 4.0.0 */ export * as ClusterMetrics from "./ClusterMetrics.ts" /** + * The `ClusterSchema` module defines the schema annotations used by Effect + * Cluster protocols. These annotations attach cluster-specific behavior to + * RPCs and entities without changing the request or response schemas + * themselves. + * + * **Common tasks** + * + * - Mark requests as persisted so mailbox storage can replay them after + * interruption or restart + * - Run server-side handling inside a storage transaction when durable state + * and SQL updates must commit together + * - Control whether client sending, server handling, or both are treated as + * uninterruptible + * - Route entity ids into shard groups + * - Disable client tracing for internal protocols such as cron dispatch + * - Derive per-request annotations from the encoded request with {@link Dynamic} + * + * **Protocol notes** + * + * Cluster transports serialize the RPC payloads, not arbitrary runtime + * annotation values. Prefer static, deterministic annotations, and use + * {@link Dynamic} when a persisted or transactional decision depends on the + * request value that is already part of the protocol. Persisted requests require + * message storage support, and shard group selection must remain stable for a + * given entity id so routing is consistent across cluster members. + * * @since 4.0.0 */ export * as ClusterSchema from "./ClusterSchema.ts" /** + * The cluster workflow engine runs durable workflows on top of cluster sharding + * and message storage. It adapts `WorkflowEngine.WorkflowEngine` so workflow + * executions, activities, deferred completions, resumes, interrupts, and durable + * clock wakeups are represented as persisted cluster entity messages. + * + * **Common tasks** + * + * - Provide a workflow engine for services that already use cluster sharding + * - Execute workflows by stable execution id and poll their persisted result + * - Resume suspended workflows after activities, deferreds, or durable clock wakeups + * - Interrupt workflow executions and propagate resume signals to parent workflows + * + * **Gotchas** + * + * - Workflow names and execution ids determine the cluster entity address used + * for persistence, so they must remain stable across deploys + * - Activities are persisted by activity name and attempt; retries and suspended + * activity resumes depend on those primary keys + * - Durable clock wakeups are scheduled through a separate clock entity and are + * cleared when an interrupted workflow stops waiting + * * @since 4.0.0 */ export * as ClusterWorkflowEngine from "./ClusterWorkflowEngine.ts" /** + * The `DeliverAt` module defines the protocol used by cluster message payloads + * that carry their own scheduled delivery time. A value implements the protocol + * by exposing a method at the `DeliverAt` symbol that returns the target + * `DateTime`. + * + * **Common tasks** + * + * - Mark a message payload as deliverable at a specific time by implementing + * {@link DeliverAt} + * - Check whether an arbitrary value carries a scheduled delivery time with + * {@link isDeliverAt} + * - Convert a scheduled delivery time to epoch milliseconds with + * {@link toMillis} + * + * **Gotchas** + * + * - The protocol records the requested delivery instant; cluster infrastructure + * may still deliver later because of clock skew, queue latency, or worker + * availability + * - Values that do not implement the symbol method are treated as unscheduled; + * {@link toMillis} returns `null` for those values + * * @since 4.0.0 */ export * as DeliverAt from "./DeliverAt.ts" /** + * The `Entity` module defines sharded, addressable actors for Effect Cluster. + * An entity type pairs a stable entity name with an RPC protocol and describes + * how requests for individual entity ids are routed to shard groups and + * runners. + * + * **Mental model** + * + * - An `Entity` is the cluster-facing definition for one logical actor type + * - Each entity id maps deterministically to a shard group and shard id + * - Clients are created per entity id and send typed RPC messages through the + * cluster sharding layer + * - Server layers register handlers or mailbox processors for the entity type + * + * **Common tasks** + * + * - Define an entity protocol with RPCs and create an entity with {@link make} + * - Send messages to a specific entity id with {@link Entity.client} + * - Register typed RPC handlers with {@link Entity.toLayer} + * - Process envelopes directly with {@link Entity.toLayerQueue} + * - Access the current entity or runner address with {@link CurrentAddress} and + * {@link CurrentRunnerAddress} + * + * **Gotchas** + * + * - Entity ids are part of routing: changing id formats can move work to + * different shards + * - Entity type names should be stable and unique within a cluster deployment + * - Mailbox capacity and concurrency determine back pressure and duplicate + * processing behavior + * - Persistence, mailbox, and already-processing failures are surfaced through + * the generated clients + * * @since 4.0.0 */ export * as Entity from "./Entity.ts" /** + * The `EntityAddress` module defines the value used to locate an entity within + * a cluster. An address combines the entity type, entity id, and shard id so + * messages, persisted envelopes, workflow executions, and entity managers can + * agree on the same routing target. + * + * **Common tasks** + * + * - Build an address after resolving an entity id to a shard with `Sharding` + * - Attach an address to cluster envelopes and persisted messages + * - Compare or hash addresses when tracking active or resuming entities + * + * **Gotchas** + * + * - The shard id is part of the address identity; the same entity type and id + * on a different shard is a different address. + * - Entity ids should be routed through the same shard group logic used by the + * entity definition so messages are sent to the runner that owns the shard. + * * @since 4.0.0 */ export * as EntityAddress from "./EntityAddress.ts" /** + * The `EntityId` module provides a branded string identifier for addressing a + * specific entity instance inside the cluster. Entity ids are commonly used as + * stable routing keys when sending messages to an entity, looking up its state, + * or deriving the shard responsible for that entity. + * + * Because routing is based on the exact string value, choose ids that are + * deterministic, normalized, and unique within the entity type you are + * addressing. Avoid display names or other values that may change over time. + * * @since 4.0.0 */ export * as EntityId from "./EntityId.ts" /** + * The `EntityProxy` module derives external RPC and HTTP API surfaces from a + * clustered {@link Entity.Entity}. It is used when callers should communicate + * with entities through ordinary RPC clients or HTTP routes while the cluster + * runtime keeps responsibility for locating, routing, and delivering messages + * to the entity instance identified by `entityId`. + * + * **Common tasks** + * + * - Derive an `RpcGroup` from an entity with {@link toRpcGroup} + * - Derive an `HttpApiGroup` from an entity with {@link toHttpApiGroup} + * - Expose both request/response calls and discard variants for fire-and-forget + * delivery + * + * **Gotchas** + * + * - Proxy RPC payloads wrap the original RPC payload with an `entityId`; HTTP + * endpoints place the same identifier in the route path. + * - Generated RPC names are prefixed with the entity type, while HTTP endpoint + * paths are based on lower-cased RPC tags. + * - Proxy errors include cluster delivery errors such as mailbox saturation, + * duplicate in-flight messages, and persistence failures. + * * @since 4.0.0 */ export * as EntityProxy from "./EntityProxy.ts" /** + * The `EntityProxyServer` module provides server-side layers for exposing + * clustered entities through proxy APIs. It connects proxy requests to an + * entity client, extracts the target `entityId`, and forwards the payload to + * the matching entity RPC method. + * + * **Common tasks** + * + * - Serve an entity proxy over an HTTP API group with {@link layerHttpApi} + * - Serve the RPC group produced by an entity proxy with {@link layerRpcHandlers} + * - Route both normal calls and discard calls to the same underlying entity + * method + * + * **Gotchas** + * + * - HTTP proxy endpoints expect the `entityId` path parameter to identify the + * target entity instance + * - RPC proxy handlers use tags prefixed with the entity type, matching the + * group generated by `EntityProxy.toRpcGroup` + * - Both layers require `Sharding` and the entity RPC server services in the + * environment so requests can be routed to the owning shard + * * @since 4.0.0 */ export * as EntityProxyServer from "./EntityProxyServer.ts" /** + * The `EntityResource` module provides helpers for acquiring resources inside a + * cluster entity and keeping them available across entity restarts. It is useful + * for long-lived resources tied to an entity address, such as external + * processes, network clients, Kubernetes Pods, or other handles that should not + * be torn down during routine shard movement. + * + * **Common tasks** + * + * - Create a reusable entity-scoped resource with {@link make} + * - Keep an entity alive while the resource is acquired + * - Explicitly release the resource with `EntityResource.close` + * - Attach cleanup work to the resource close scope with {@link CloseScope} + * - Create and manage a Kubernetes Pod resource with {@link makeK8sPod} + * + * **Lifecycle gotchas** + * + * - Resources are retained by an `RcRef` and are only fully released after + * `idleTimeToLive` expires or `close` is called + * - The default idle time to live is infinite, so resources remain alive until + * explicitly closed + * - `CloseScope` is separate from the caller scope and is not closed by entity + * restarts, shard movement, or node shutdown finalization + * * @since 4.0.0 */ export * as EntityResource from "./EntityResource.ts" /** + * The `EntityType` module defines the branded string used to identify a kind of + * entity in an Effect cluster. Entity type names are part of the cluster routing + * identity: they distinguish one family of entities from another before an + * individual entity id is considered. + * + * **Common tasks** + * + * - Declare the stable name for an entity family handled by a cluster service + * - Brand a string literal as an {@link EntityType} with {@link make} + * - Validate or encode entity type names with the {@link EntityType} schema + * + * **Gotchas** + * + * - Entity type names should be stable and unique within the cluster because + * changing them changes where entity messages are routed + * - The entity type name identifies the entity family, not a specific entity + * instance; combine it with the entity id at the call site that routes work + * * @since 4.0.0 */ export * as EntityType from "./EntityType.ts" /** + * The `Envelope` module defines the transport messages exchanged by Effect + * cluster entities while processing RPC requests. Envelopes wrap decoded + * request payloads with routing metadata, trace context, and request ids, and + * also model delivery-control messages such as streamed-reply acknowledgements + * and request interrupts. + * + * **Common use cases** + * + * - Construct a runtime request envelope with {@link makeRequest} + * - Decode or encode envelopes crossing a network or durable queue with {@link PartialJson} + * - Batch encoded envelopes with {@link PartialArray} + * - Detect envelope values at runtime with {@link isEnvelope} + * - Build storage keys for keyed request payloads with {@link primaryKey} + * + * **Serialization and delivery notes** + * + * Request envelopes are decoded in two phases: the envelope metadata is parsed + * first, while the RPC payload remains `unknown` until the receiving side knows + * the target RPC schema. Snowflake identifiers are encoded as strings for JSON + * transport, and acknowledgement / interrupt envelopes carry the original + * request id so delivery protocols can correlate control messages with the + * in-flight request. + * * @since 4.0.0 */ export * as Envelope from "./Envelope.ts" /** + * The `HttpRunner` module wires cluster runner RPCs to HTTP transports. It + * provides client protocol layers for contacting runners over HTTP or + * WebSocket, server-side HTTP effects for exposing runner RPC handlers, and + * complete layers that install those routes into an `HttpRouter`. + * + * **Common tasks** + * + * - Serve runner RPC routes with {@link layerHttp} or {@link layerWebsocket} + * - Configure client-only runner communication with {@link layerHttpClientOnly} + * or {@link layerWebsocketClientOnly} + * - Use custom route paths with {@link layerHttpOptions}, + * {@link layerWebsocketOptions}, {@link layerClientProtocolHttp}, or + * {@link layerClientProtocolWebsocket} + * + * **Transport gotchas** + * + * - Client protocol paths are appended to each runner address when building the + * target URL + * - `https: true` switches HTTP clients from `http` to `https`, and WebSocket + * clients from `ws` to `wss` + * - The default complete layers serve and connect at `/`; use the `Options` + * variants when your runner routes live under a different path + * * @since 4.0.0 */ export * as HttpRunner from "./HttpRunner.ts" /** + * The `K8sHttpClient` module provides an HTTP client service for talking to the + * Kubernetes API from code running inside a cluster. + * + * It configures requests for the in-cluster service endpoint, attaches the + * mounted service-account token when present, and exposes helpers for common + * cluster tasks such as discovering running pods by namespace or label selector + * and creating scoped pods that are cleaned up automatically. + * + * **Gotchas** + * + * - The default layer targets `https://kubernetes.default.svc/api`, so it is + * intended for workloads with Kubernetes DNS and service-account mounts. + * - Pod discovery is keyed by pod IP address and only includes pods whose phase + * is `Running`; callers should choose selectors that match the intended + * service topology. + * - Network policies, RBAC, and service-account token availability can all + * prevent the client from reaching or authorizing with the Kubernetes API. + * * @since 4.0.0 */ export * as K8sHttpClient from "./K8sHttpClient.ts" /** + * The `MachineId` module provides the branded integer identifier used to + * distinguish cluster runners when generating distributed ids and coordinating + * runner state. + * + * **When to use** + * + * - Persisting or exchanging the machine id assigned to a cluster runner + * - Passing a runner-specific identity to the cluster snowflake generator + * - Decoding machine ids from storage while keeping them distinct from plain numbers + * + * **Gotchas** + * + * - Machine ids must be unique for concurrently active runners that generate snowflakes + * - Snowflake ids store the machine component in 10 bits, so only the value modulo 1024 is encoded + * * @since 4.0.0 */ export * as MachineId from "./MachineId.ts" /** + * The cluster `Message` module defines the in-memory shapes used while moving + * requests and control envelopes between callers, durable storage, transports, + * and entity runners. + * + * **Common use cases** + * + * - Representing outgoing entity requests before they are stored or sent + * - Reconstructing incoming requests that runners read from storage or transport + * - Converting outgoing messages into local, in-process deliveries + * - Serializing request payloads with the associated RPC schema and context + * - Passing control envelopes such as acknowledgements and interrupts through + * without payload decoding + * + * **Gotchas** + * + * - Requests can exist in decoded local form or encoded persisted form; choose + * `IncomingLocal` / `OutgoingRequest` for local delivery and `IncomingRequest` + * / `Envelope.PartialRequest` for storage or transport boundaries. + * - Request payloads must be encoded and decoded with the matching RPC payload + * schema and service context, otherwise failures are surfaced as + * `MalformedMessage`. + * - Delivery state such as the last sent or received reply is carried alongside + * messages so retries and persisted replies can preserve cluster semantics. + * * @since 4.0.0 */ export * as Message from "./Message.ts" /** + * The `MessageStorage` module defines the persistence boundary used by Effect + * Cluster to store mailbox messages and replies. Storage implementations keep + * requests, envelopes, and reply chunks durable enough for runners to recover + * work after restarts, replay unprocessed messages for assigned shards, and + * deliver replies back to locally registered handlers. + * + * **Common use cases** + * + * - Persist outgoing requests and control envelopes before delivery + * - Detect duplicate requests by primary key and resume from an existing reply + * - Query unprocessed messages when shards are assigned to a runner + * - Store, load, and clear replies for request streams and completions + * - Reset or clear mailbox state during shard or address lifecycle changes + * + * **Gotchas** + * + * - Implementations should make save and reply operations transactional when + * possible so recovery does not observe partial mailbox state + * - Duplicate detection depends on stable request primary keys and persisted + * request ids + * - Reply handlers are local process state; persisted replies may need to be + * loaded again after restarts or reassignment + * - Concurrent runners must only process messages for shards they currently own + * * @since 4.0.0 */ export * as MessageStorage from "./MessageStorage.ts" /** + * The `Reply` module models responses produced by clustered RPC execution. A + * reply belongs to a request and is either a terminal {@link WithExit}, which + * carries the final RPC `Exit`, or a streaming {@link Chunk}, which carries a + * non-empty batch of success values for RPCs that stream results. + * + * **Common tasks** + * + * - Represent runtime replies with {@link Reply}, {@link WithExit}, and {@link Chunk} + * - Encode and decode transport payloads with {@link Encoded} and {@link Reply} + * - Persist replies together with schema context via {@link ReplyWithContext} + * - Serialize the latest received reply when resuming or de-duplicating requests with {@link serializeLastReceived} + * + * **Streaming and acknowledgement notes** + * + * - Chunk replies are sequenced and can be replayed until acknowledged by the + * receiver. + * - A `WithExit` reply is terminal and completes the request, while chunks only + * represent intermediate streamed success values. + * - `Chunk.emptyFrom` is used as an acknowledgement marker for an empty streamed + * reply; it is not a general-purpose success payload. + * * @since 4.0.0 */ export * as Reply from "./Reply.ts" /** + * The `Runner` module defines the membership record used by the unstable + * cluster runtime to describe a process that can host entity shards. + * + * A runner combines the network address used by other runners to reach it, the + * shard groups it participates in, and a relative weight used when the sharding + * service assigns shards across the healthy runners in each group. + * + * **Common tasks** + * + * - Construct the runner registered by the local `Sharding` layer + * - Persist or exchange runner metadata through `RunnerStorage` + * - Encode and decode runner values at cluster transport or storage boundaries + * - Tune shard distribution by adjusting the runner's group membership and + * relative weight + * + * **Gotchas** + * + * - Runner addresses must be stable and unique while a runner is registered, + * because they identify the owner used for routing and shard locks. + * - Weights are relative within each shard group; changing weights or groups can + * rebalance shard ownership as the cluster refreshes its runner view. + * - Runner equality and hashing are based on address and weight, so compare + * `groups` explicitly when group membership is the important distinction. + * * @since 4.0.0 */ export * as Runner from "./Runner.ts" /** + * The `RunnerAddress` module defines the network identity used to locate a + * cluster runner. A runner address is a host and port pair that can be encoded, + * compared, hashed, inspected, and used as a stable primary key. + * + * **Common use cases** + * + * - Representing the target runner for cluster routing and placement decisions + * - Persisting or exchanging runner endpoints through schemas + * - Using runner endpoints as keys in maps, registries, or shard ownership data + * + * **Gotchas** + * + * - Identity is structural: two addresses are equal when both host and port match + * - The primary key is formatted as `host:port`, so host strings should already + * be normalized for the routing layer using them + * * @since 4.0.0 */ export * as RunnerAddress from "./RunnerAddress.ts" /** + * The `RunnerHealth` module defines the health-check service used by cluster + * sharding to decide whether a runner may still own its assigned shards. A + * runner that is reported as alive is allowed to keep processing messages, + * while a runner that is reported as unavailable can have its shards moved to + * another runner. + * + * **Common tasks** + * + * - Provide a custom {@link RunnerHealth} service for a cluster deployment + * - Use {@link layerPing} to check runners through the cluster runner protocol + * - Use {@link layerK8s} when Kubernetes pod readiness should drive health + * - Use {@link layerNoop} in tests or environments where runners are always considered healthy + * + * **Gotchas** + * + * - Health checks affect shard reassignment, so false negatives can move shards + * away from runners that may still be processing messages + * - The Kubernetes implementation treats API failures as healthy to avoid + * reassignment caused by a temporary control-plane outage + * * @since 4.0.0 */ export * as RunnerHealth from "./RunnerHealth.ts" /** + * The `Runners` module defines the service used by the unstable cluster runtime + * to communicate with processes that host entity shards. It is the transport + * boundary between sharding decisions and runner execution: callers can ping a + * runner, send requests or envelopes, notify a runner that persisted work is + * available, and report an address as unavailable. + * + * The default implementation wraps lower-level runner callbacks with cluster + * message semantics. Persisted messages are written to `MessageStorage` before + * delivery, duplicate requests can resume from stored replies, and local sends + * can optionally serialize and deserialize messages to exercise the same path as + * remote delivery. + * + * **Common tasks** + * + * - Provide runner communication with {@link layerRpc} + * - Build a custom implementation with {@link make} + * - Use {@link makeNoop} or {@link layerNoop} when no remote runners are + * available + * - Define runner-to-runner protocol support with {@link Rpcs} and + * {@link RpcClientProtocol} + * + * **Gotchas** + * + * - `notify` is only for RPCs annotated as persisted; non-persisted messages + * should be sent directly. + * - Failed remote sends can fall back to reading replies from storage, so reply + * polling and `entityReplyPollInterval` affect recovery latency. + * - Unavailable runners invalidate cached RPC clients, but shard ownership and + * rebalancing are coordinated by the sharding layer rather than this module. + * * @since 4.0.0 */ export * as Runners from "./Runners.ts" /** + * The `RunnerServer` module provides the transport-agnostic server side of the + * cluster runner protocol. It turns the runner RPC group into handlers that + * receive ping, notification, request, stream, and envelope messages from other + * runners, then forwards them into `Sharding` and coordinates persisted replies + * through `MessageStorage`. + * + * **Common tasks** + * + * - Build a runner server once an `RpcServer.Protocol` has been supplied by a + * transport such as HTTP, WebSocket, or sockets + * - Provide the complete runner runtime with `Sharding` and `Runners` clients + * using {@link layerWithClients} + * - Embed a cluster client without serving runner RPCs or accepting shard + * assignments using {@link layerClientOnly} + * + * **Gotchas** + * + * - This module does not choose a wire transport; transport-specific modules + * provide the `RpcServer.Protocol` + * - Persisted requests register reply handlers in `MessageStorage` before the + * message is delivered to `Sharding` + * - Client-only layers clear the configured runner address, so they can send + * cluster messages but do not register as shard-owning runners + * * @since 4.0.0 */ export * as RunnerServer from "./RunnerServer.ts" /** + * The `RunnerStorage` module defines the persistence boundary used by clustered + * runners to register themselves and coordinate shard ownership. + * + * Implementations keep track of runner metadata, health, machine ids, and shard + * locks so a cluster can rebalance work as runners join, leave, or lose their + * leases. Production adapters usually implement the string-encoded interface and + * adapt it with {@link makeEncoded}; tests and local setups can use + * {@link makeMemory}. + * + * **Common tasks** + * + * - Register and unregister runners in a shared store + * - Read runner health for scheduling and rebalancing decisions + * - Acquire, refresh, and release shard locks for distributed processing + * - Bridge typed cluster values to string or numeric database representations + * + * **Gotchas** + * + * - Shard acquisition may be partial; callers must use the returned shard list + * - Refreshing leases is part of keeping shard ownership during rebalancing + * - The in-memory implementation is process-local and does not persist runner + * registrations or locks across restarts + * * @since 4.0.0 */ export * as RunnerStorage from "./RunnerStorage.ts" /** + * The `ShardId` module models the address of a shard inside an Effect Cluster + * shard group. A shard id is made from a string `group` and numeric `id`, and + * the module gives that pair stable equality, hashing, primary-key behavior, + * schema support, and conversion to and from the `group:id` string form used by + * routing and storage boundaries. + * + * **Common tasks** + * + * - Create or reuse a cached shard identifier with {@link make} + * - Check runtime values with {@link isShardId} + * - Encode or decode shard identifiers with {@link ShardId} + * - Format for logs, persistence, or transport with {@link toString} + * - Parse encoded shard keys with {@link fromString} or {@link fromStringEncoded} + * + * **Gotchas** + * + * - Equality and hashing are based on the `group:id` representation, so both + * fields must match for two shard ids to be equal + * - Encoded strings are split at the last `:`; groups may contain colons, but + * ids must parse as numbers + * - This module identifies shards after a routing or hashing decision; it does + * not choose a shard for an arbitrary entity key + * * @since 4.0.0 */ export * as ShardId from "./ShardId.ts" /** + * The `Sharding` module coordinates cluster-wide placement and delivery for + * entities and singletons. It hashes entity ids into shard ids, tracks which + * runner owns each shard, acquires local shard locks, and routes RPC messages + * to the runner that is responsible for the addressed entity. + * + * Use this module when building clustered services that need location + * transparency for stateful entities, singleton workloads that should run once + * per shard group, or durable message processing backed by cluster storage. + * Registered entity handlers are started on demand for shards owned by the + * current runner, while clients produced by {@link Sharding.makeClient} route + * requests through the sharding service instead of calling handlers directly. + * + * **Gotchas** + * + * - Shard assignment and shard acquisition are distinct: a runner may be + * assigned a shard before it has acquired the storage lock for that shard. + * - Routing depends on the entity shard group and the configured shard count, + * so changing either value affects where entity ids are placed. + * - Persisted messages are only read and dispatched for shards currently owned + * by the local runner; shutdown and runner health changes can temporarily + * move work between runners. + * * @since 4.0.0 */ export * as Sharding from "./Sharding.ts" /** + * The `ShardingConfig` module defines the configuration used by a cluster + * runner to participate in Effect Cluster sharding. It describes how a runner is + * addressed by other runners, which shard groups it can host, how many shards + * are assigned per group, and the timing settings used for locks, assignment + * refreshes, health checks, entity lifecycle, and message polling. + * + * Use this module when wiring a sharded application locally with + * {@link layer}, loading deployment settings from environment variables with + * {@link layerFromEnv}, or overriding selected defaults for tests and + * single-node development. In production, keep cluster-wide values such as + * `shardsPerGroup` and shard groups consistent across runners, choose stable + * externally reachable runner addresses, and tune lock expiration and refresh + * intervals to match the storage backend and shutdown behavior of the + * deployment platform. + * * @since 4.0.0 */ export * as ShardingConfig from "./ShardingConfig.ts" /** + * The `ShardingRegistrationEvent` module defines the events emitted by + * `Sharding` when the local runner registers entity handlers or singleton + * workloads. These events are useful for observing the set of capabilities a + * runner has made available, coordinating startup hooks, and writing tests or + * integrations that need to react when registrations are complete. + * + * Registration events describe local registration, not shard ownership or + * execution. A runner may register an entity or singleton before it owns the + * shard that will run it, and the events are in-memory notifications from the + * `Sharding` service rather than persisted cluster state. For persisted + * messages, treat registration as the point where the handler is available to + * the runner; it does not imply that existing storage work has already been + * read or processed. + * * @since 4.0.0 */ export * as ShardingRegistrationEvent from "./ShardingRegistrationEvent.ts" /** + * The `SingleRunner` module provides a ready-to-use layer for running the + * cluster sharding services in a single process. It wires together sharding, + * message storage, runner registration, runner health, and sharding + * configuration so durable entities and workflows can run without a fleet of + * external runners. + * + * **Common tasks** + * + * - Start a local or embedded cluster runner backed by SQL message storage + * - Run durable entities and workflows in development, tests, or small + * single-node deployments + * - Choose SQL runner storage for persistence or in-memory runner storage for + * short-lived scenarios + * - Override sharding configuration while still using the standard + * environment-based defaults + * + * **Gotchas** + * + * - The layer still requires a `SqlClient` because message storage is SQL-backed + * - Runner health and runner coordination are no-op implementations, so this is + * for single-node use rather than multi-runner cluster coordination + * * @since 4.0.0 */ export * as SingleRunner from "./SingleRunner.ts" /** + * The `Singleton` module provides a small helper for registering effects that + * should run once across an Effect cluster. A singleton is coordinated through + * `Sharding`, which assigns ownership to one node at a time and can move that + * ownership when nodes leave or fail. + * + * Use singletons for cluster-wide background work such as schedulers, polling + * loops, maintenance jobs, or consumers that must not have one instance per + * process. Because ownership can change during failover, the registered effect + * should be interruptible, scoped, and able to resume work without assuming that + * the previous owner completed every in-flight action exactly once. + * * @since 4.0.0 */ export * as Singleton from "./Singleton.ts" /** + * The `SingletonAddress` module defines the address used by cluster sharding to + * identify a registered singleton. A singleton address combines the singleton + * name with the `ShardId` that owns it, giving the runtime a stable key for + * registration events, equality checks, hashing, and runner-local fiber + * tracking. + * + * Use this module when observing singleton registrations or working with + * sharding internals that need to tell which shard currently owns a singleton. + * The shard id is derived from the singleton name and shard group at + * registration time, so changing either value changes ownership and routing. + * Ownership can also move as shard locks are acquired or released, so an address + * identifies the target shard rather than guaranteeing that a particular runner + * is currently executing the singleton. + * * @since 4.0.0 */ export * as SingletonAddress from "./SingletonAddress.ts" /** + * The `Snowflake` module provides compact, sortable identifiers for cluster + * resources and events. A snowflake id is a branded `bigint` made from a + * millisecond timestamp, a machine id, and a per-machine sequence number. + * + * **Common use cases** + * + * - Creating ids without coordinating through a central database + * - Ordering cluster events, entity ids, or log records by generation time + * - Encoding ids as strings at service boundaries with {@link SnowflakeFromString} + * - Decoding a generated id into timestamp, machine id, and sequence parts with {@link toParts} + * + * **Gotchas** + * + * - Uniqueness depends on each concurrent generator using a distinct machine id + * - Generated ids are time-sortable, but they are not random or secret values + * - The default generator prevents local clock drift from moving ids backward + * - More than 4096 ids in the same millisecond advance the logical timestamp + * * @since 4.0.0 */ export * as Snowflake from "./Snowflake.ts" /** + * The `SocketRunner` module wires cluster runner RPCs to socket transports. It + * provides a complete runner layer that serves RPC handlers on a `SocketServer` + * and installs `Sharding` and `Runners` clients for talking to other runners + * through the socket RPC protocol. + * + * **Common tasks** + * + * - Run a cluster worker over TCP or Unix sockets with {@link layer} + * - Connect to other runners while exposing `Sharding` and `Runners` clients + * - Embed a client-only cluster participant with {@link layerClientOnly} when + * the process should send messages but not receive shard assignments + * + * **Transport gotchas** + * + * - The server listen address comes from the provided `SocketServer` and is + * logged when {@link layer} starts + * - TCP addresses are logged as `hostname:port`, while Unix socket addresses + * are logged as their filesystem path + * - The client-only layer does not start a socket server; provide the full + * layer when the process must accept runner RPCs + * * @since 4.0.0 */ export * as SocketRunner from "./SocketRunner.ts" /** + * SQL-backed message storage for the unstable cluster runtime. + * + * This module persists encoded cluster envelopes and replies in SQL tables so + * shards can resume work after process restarts, redeliver unprocessed messages, + * deduplicate requests by primary key, and replay outstanding reply chunks until + * they are acknowledged. It is the storage implementation to use when a cluster + * needs durable request / reply state backed by `SqlClient` rather than an + * in-memory store. + * + * The storage layer runs its own migrations and creates messages, replies, and + * migration tables using the configured prefix (`cluster` by default). Choose a + * stable prefix before deploying, because changing it points the runtime at a + * different set of tables. Existing deployments should also keep the generated + * migration history table with the message tables so future schema changes can + * be applied consistently across supported SQL dialects. + * * @since 4.0.0 */ export * as SqlMessageStorage from "./SqlMessageStorage.ts" /** + * SQL-backed storage for Effect Cluster runner metadata and shard ownership. + * + * The `SqlRunnerStorage` module builds a `RunnerStorage` implementation from a + * `SqlClient`, creating the runner and lock tables it needs using the configured + * table prefix. It is used by clustered applications that need runner + * registration, health tracking, shard acquisition, refresh, and release to be + * coordinated through an external database instead of in-memory state. + * + * **Common tasks** + * + * - Provide the default storage layer with {@link layer} + * - Use {@link layerWith} when multiple clusters share the same database and + * need distinct table prefixes + * - Create a storage implementation directly with {@link make} for custom layer + * composition + * + * **Gotchas** + * + * - Runner heartbeats and persisted shard locks expire according to + * `ShardingConfig.shardLockExpiration`; stale rows may be reused or cleaned up + * by later storage operations. + * - PostgreSQL and MySQL use advisory locks by default, keeping shard ownership + * tied to a reserved database connection. Set + * `ShardingConfig.shardLockDisableAdvisory` when persisted lock rows should be + * used instead. + * - The selected table prefix controls the generated `runners` and `locks` + * table names, so changing it points the cluster at a different storage + * namespace. + * * @since 4.0.0 */ export * as SqlRunnerStorage from "./SqlRunnerStorage.ts" /** + * The `TestRunner` module provides a lightweight in-memory cluster layer for + * tests that need the cluster sharding services without starting real runners + * or relying on external storage. + * + * Use it when exercising sharding behavior, message storage, or code that + * depends on the cluster runner services in unit and integration tests. The + * layer wires the normal sharding service to in-memory message and runner + * storage, along with no-op runner and health implementations. + * + * **Testing gotchas** + * + * - State is held in memory and scoped to the layer lifetime; it is not shared + * across independently constructed layers or persisted between test runs + * - Runner execution and health checks are no-ops, so this layer is best suited + * for testing coordination and storage behavior rather than real distributed + * runner processes + * * @since 4.0.0 */ export * as TestRunner from "./TestRunner.ts" diff --git a/packages/effect/src/unstable/devtools/index.ts b/packages/effect/src/unstable/devtools/index.ts index f185c5dfeb..4880a790f9 100644 --- a/packages/effect/src/unstable/devtools/index.ts +++ b/packages/effect/src/unstable/devtools/index.ts @@ -5,21 +5,79 @@ // @barrel: Auto-generated exports. Do not edit manually. /** + * Utilities for wiring an Effect application to the Effect devtools runtime + * tracer. + * + * This module is the high-level entry point for installing the devtools tracer + * as a `Layer`. Use it when an application should stream spans, span events, + * span completions, and metrics snapshots to a devtools process for local + * inspection. The default `layer` and `layerWebSocket` helpers connect over a + * WebSocket to `ws://localhost:34437`, while `layerSocket` lets integrations + * provide their own `Socket` transport. + * + * These layers install tracing for the scoped runtime they are provided to; + * they do not start a devtools server, and a compatible devtools endpoint must + * be reachable separately. Because this API lives under `unstable`, its + * protocol and surface may change between releases. + * * @since 4.0.0 */ export * as DevTools from "./DevTools.ts" /** + * Provides the low-level client used by the unstable devtools integration to + * exchange telemetry with an Effect devtools server over the current `Socket`. + * + * The client speaks the devtools NDJSON protocol, publishes span starts, span + * events, span completions, and metric snapshots, and exposes layers for + * installing a tracer that mirrors the current tracer while forwarding data to + * devtools. Most applications should use the higher-level devtools layers + * instead of constructing this service directly. When using this module + * directly, provide a live `Socket`, keep the layer scoped so the background + * ping and stream fibers are finalized, and prefer `layerTracer` when the goal + * is to observe an application's Effect traces. + * * @since 4.0.0 */ export * as DevToolsClient from "./DevToolsClient.ts" /** + * Schemas and TypeScript types for the Effect devtools protocol. + * + * This module defines the wire format used by devtools clients and servers to + * exchange telemetry, including spans, span events, metric snapshots, fiber + * dumps, heartbeat messages, and request/response payloads. Use these schemas + * when encoding or decoding messages at the devtools boundary, validating + * custom transports, or building integrations that need to inspect the same + * protocol data as the built-in devtools implementation. + * + * The exported values describe serialized protocol payloads rather than the + * full in-memory runtime data structures. Some fields intentionally normalize + * runtime values for transport, for example ended span exits are encoded with + * successful values erased via `Exit.asVoid`, timestamps are represented as + * `bigint`s, and arbitrary attributes are accepted as unknown schema values. + * The module lives under `unstable`, so consumers should treat the protocol + * shape as experimental. + * * @since 4.0.0 */ export * as DevToolsSchema from "./DevToolsSchema.ts" /** + * Server-side helpers for exposing the Effect devtools protocol over a socket. + * + * This module is used by runtime integrations that want to accept devtools + * clients, decode newline-delimited JSON protocol messages, and hand each + * connected client to application-specific handling logic. It is most useful + * for building a devtools endpoint that can inspect running fibers, spans, and + * other telemetry described by `DevToolsSchema`. + * + * The server automatically responds to protocol `Ping` requests with `Pong` + * responses. All other requests are delivered through the connected `Client` + * queue, while responses should be written with `Client.send`. The queue is + * shut down when the socket processing fiber terminates, so handlers should + * treat it as connection-scoped state rather than a long-lived global channel. + * * @since 4.0.0 */ export * as DevToolsServer from "./DevToolsServer.ts" diff --git a/packages/effect/src/unstable/encoding/index.ts b/packages/effect/src/unstable/encoding/index.ts index d6c7fe41b9..f36e827b40 100644 --- a/packages/effect/src/unstable/encoding/index.ts +++ b/packages/effect/src/unstable/encoding/index.ts @@ -5,16 +5,65 @@ // @barrel: Auto-generated exports. Do not edit manually. /** + * Utilities for encoding Effect channel payloads and schema values as + * MessagePack bytes. + * + * This module is useful when a protocol or storage layer expects compact binary + * frames instead of JSON text, such as RPC transports, socket streams, caches, + * or database columns that carry typed Effect data. Use the raw channel helpers + * when both sides already agree on the MessagePack value shape, and use the + * schema-aware helpers when values should be validated, transformed, or decoded + * into domain types at the boundary. + * + * MessagePack preserves binary data and common JavaScript collection shapes, but + * it is still a data format rather than an Effect schema. Schema encoders run + * before packing and schema decoders run after unpacking, so unsupported runtime + * values, lossy schema encodings, or mismatched schemas surface as either + * `MsgPackError` or `SchemaError` depending on where the failure occurs. + * * @since 4.0.0 */ export * as Msgpack from "./Msgpack.ts" /** + * Utilities for encoding Effect channel payloads and schema values as + * newline-delimited JSON. + * + * NDJSON represents a stream as one complete JSON value per line, making this + * module useful for log pipelines, long-lived HTTP responses, socket protocols, + * and file formats where records should be processed incrementally instead of + * buffering a whole JSON array. Use the byte helpers at transport boundaries + * that speak UTF-8, the string helpers when text framing is already handled, + * and the schema-aware helpers when each record should be validated or + * transformed at the boundary. + * + * Encoders append a trailing newline after each emitted chunk, and decoders + * tolerate records split across input chunks. Empty lines are only skipped when + * `ignoreEmptyLines` is enabled; otherwise they are passed to `JSON.parse` and + * fail like any other invalid JSON record. + * * @since 4.0.0 */ export * as Ndjson from "./Ndjson.ts" /** + * Utilities for parsing and rendering Server-Sent Events text streams. + * + * This module is useful at HTTP streaming boundaries that speak the EventSource + * wire format, such as live updates, notifications, progress feeds, and other + * unidirectional server-to-client event streams. It provides low-level parser + * and encoder primitives, channel combinators for streaming chunks through + * Effect pipelines, and schema-aware helpers for validating or transforming the + * `id`, `event`, and string `data` fields at the edge of an application. + * + * SSE is line-oriented text rather than a framed binary protocol. Incoming + * chunks may split fields across arbitrary boundaries, events are dispatched by + * a blank line, repeated `data:` lines are joined with newlines, and `retry:` + * directives are control messages rather than regular events. The decoder + * handles UTF-8 byte order marks, CRLF and LF line endings, default `message` + * events, and retry directives so callers can reconnect with the requested + * delay while preserving the last event ID when available. + * * @since 4.0.0 */ export * as Sse from "./Sse.ts" diff --git a/packages/effect/src/unstable/eventlog/index.ts b/packages/effect/src/unstable/eventlog/index.ts index 72e77d4692..84a08f7f87 100644 --- a/packages/effect/src/unstable/eventlog/index.ts +++ b/packages/effect/src/unstable/eventlog/index.ts @@ -5,71 +5,311 @@ // @barrel: Auto-generated exports. Do not edit manually. /** + * Defines typed event-log events for use with `EventLog` and event groups. + * + * An event definition names a durable domain event with a tag, derives the + * aggregate or entity primary key from the payload, and records the schemas used + * to encode the payload and decode handler success or failure values. These + * definitions are the shared contract between clients that write events and + * servers that register handlers, so they are useful for command-style writes, + * replicated logs, audit trails, and workflows that need replayable domain + * facts. + * + * Payloads are serialized with MessagePack, while success and error values are + * described separately for the handler result. Keep payload schemas stable once + * events have been persisted or replicated, prefer explicit versioned event tags + * or backward-compatible schemas for changes, and make primary keys deterministic + * so related entries are grouped consistently across stores and remotes. + * * @since 4.0.0 */ export * as Event from "./Event.ts" /** + * Defines typed groups of event-log event definitions. + * + * Event groups describe the events that belong to one event-log domain, such as + * commands for an aggregate, application workflow, or synced local store. Start + * from `empty`, add event tags with their payload, success, and error schemas, + * then use the group with `EventLog.group` to provide the handlers that execute + * and commit those events. + * + * Each event tag becomes the key in the group's events record, so tags should be + * unique within a group. The `primaryKey` function is part of the event + * definition and should derive the stable partition key from the decoded + * payload. Omitted schemas default to `Schema.Void` for payload and success, and + * `Schema.Never` for errors; use `addError` when every event in the group shares + * an additional error schema. + * * @since 4.0.0 */ export * as EventGroup from "./EventGroup.ts" /** + * Low-level storage and replay primitives for the unstable event-log system. + * + * An `EventJournal` records committed event entries, exposes them for replay, + * and tracks the replication state needed to exchange entries with remote + * journals. It is the persistence boundary used by higher-level event-log + * schemas and handlers for workflows such as rebuilding projections, syncing + * offline clients, importing remote changes, and coordinating per-store writes. + * + * Journal entries are ordered by their UUID v7 entry ids, so persistence and + * replay code should account for clock-derived ordering when detecting + * conflicts. Payloads are stored as encoded bytes and must remain compatible + * with the event schemas that will decode them later. Remote writes may include + * duplicate entries, compaction can rewrite the set of imported entries before + * effects run, and replay handlers should be prepared for entries that arrive + * after local changes for the same event and primary key. + * * @since 4.0.0 */ export * as EventJournal from "./EventJournal.ts" /** + * Typed event-log runtime for appending domain events to an `EventJournal` and + * replaying entries from remote replicas. + * + * This module is used to define event-log schemas, register handlers for event + * groups, build clients that write typed payloads, and connect local journals to + * authenticated remote sessions. It is useful for event-sourced state, + * offline-first synchronization, audit trails, and replicated stores where each + * event must run its handler before the entry is committed. + * + * Local appends encode payloads with the event schema and commit only after the + * registered handler succeeds. Remote replay decodes entries with the same + * schema, passes duplicate or conflicting entries to handlers, may run + * compaction before committing, and invalidates registered reactivity keys. + * Remote sessions depend on the current `Identity` and `CurrentStoreId`, so use + * stable values when multiple replicas or stores must share the same log. + * * @since 4.0.0 */ export * as EventLog from "./EventLog.ts" /** + * Event-log encryption primitives for encrypted remote replication. + * + * This module defines the `EventLogEncryption` service used by encrypted + * `EventLogRemote` clients to turn local journal entries into encrypted remote + * payloads, decrypt encrypted changes received from a server, hash byte data, + * and create event-log identities. It is useful when events need to be + * replicated through infrastructure that stores or transports only ciphertext, + * such as an encrypted event-log server, offline-first synchronization backend, + * or multi-device replicated store. + * + * Encryption keys are deterministically derived from the identity private key + * material, so the same stable identity is required to decrypt entries across + * sessions and devices. The public key identifies the replicated log, while the + * private key material must remain secret and must not be rotated without a + * migration plan for existing encrypted entries. The default implementation + * uses Web Crypto AES-GCM with generated initialization vectors that are stored + * alongside encrypted entries; persisted ciphertext, IVs, entry schemas, and + * identity key derivation labels are part of the compatibility surface for + * future event-log encryption versions. + * * @since 4.0.0 */ export * as EventLogEncryption from "./EventLogEncryption.ts" /** + * Defines the wire messages used by event-log remotes to authenticate clients, + * write event batches, and stream changes back to replicas. + * + * This module is the protocol boundary between `EventLogRemote` clients and + * event-log servers: it provides schemas for store identifiers, protocol + * errors, session handshake payloads, authenticated RPCs, and the msgpack + * payloads used to carry encrypted or plaintext journal entries. + * + * Event batches are serialized as binary payloads before transport. Small + * payloads can be sent as a single frame, while larger payloads are split into + * `ChunkedMessage` parts and must be reassembled by message id after every part + * has arrived. Transports should preserve `Uint8Array` bytes exactly and avoid + * treating msgpack data as text. + * * @since 4.0.0 */ export * as EventLogMessage from "./EventLogMessage.ts" /** + * Client-side remote replica support for writing event-log entries and + * receiving change streams over the event-log RPC protocol. + * + * This module builds `EventLogRemote` services backed by `EventLogRemoteRpcs`. + * It is used by local event logs that need to replicate entries to another + * journal, subscribe to remote changes from a sequence number, or run effects + * only after the current event-log identity has completed the remote + * authentication handshake. The encrypted constructor is the default choice for + * synchronizing browser, edge, or service replicas across an untrusted network, + * while the unencrypted constructor is intended for trusted transports or tests. + * + * Remote sessions begin with `Hello` and `Authenticate`, cache authentication by + * identity public key, and retry forbidden responses by refreshing the handshake. + * The RPC transport must preserve a stable client session across hello, + * authentication, writes, and change streams. Entries and change batches may be + * split into protocol chunks, so callers should treat `changes` as a scoped + * streaming queue and rely on the remote `Registry` registration instead of + * manually sharing partially assembled payloads between sessions. + * * @since 4.0.0 */ export * as EventLogRemote from "./EventLogRemote.ts" /** + * Server-side RPC handlers for accepting remote event-log writes and streaming + * changes back to authenticated clients. + * + * This module is the protocol glue used by concrete event-log servers: it + * performs the hello / authenticate challenge flow, attaches the authenticated + * `EventLog.Identity` to subsequent RPC requests, reassembles chunked writes, + * and chunks large change payloads before they are sent to clients. It is useful + * when exposing an event-log replica over HTTP-backed RPC, for example to sync + * browser, edge, or service replicas with a central journal. + * + * The authentication state is tied to the RPC client session annotations, so the + * transport must preserve a stable client session between `Hello`, + * `Authenticate`, writes, and change streams. Deployments should run the endpoint + * over TLS, avoid exposing unauthenticated write or changes routes, and persist + * session-auth key bindings with the same trust boundary as the event-log data. + * * @since 4.0.0 */ export * as EventLogServer from "./EventLogServer.ts" /** + * Server-side RPC layers and storage contracts for encrypted event-log + * replication. + * + * This module is used by encrypted `EventLogRemote` clients that need a remote + * synchronization endpoint without exposing plaintext events to the server. The + * server stores ciphertext, initialization vectors, entry ids, and remote + * sequence numbers keyed by the client's public key and store id, then streams + * encrypted changes back to clients so they can decrypt locally with their + * identity private key material. This makes it suitable for offline-first + * synchronization, multi-device replication, and hosted backends where the + * transport or storage layer should not inspect event payloads. + * + * The server does not derive or hold encryption keys. It treats public keys as + * log identities, persists one session authentication binding per public key, + * and reuses the initialization vector supplied with each encrypted write + * request for the entries in that batch. Persisted remote ids, session signing + * key bindings, ciphertext, IVs, and sequence numbers are therefore part of the + * encrypted replication protocol and should be kept stable by durable storage + * implementations. + * * @since 4.0.0 */ export * as EventLogServerEncrypted from "./EventLogServerEncrypted.ts" /** + * Server implementation for event logs whose entries are persisted and streamed + * in plaintext. + * + * This module is useful for trusted deployments, local development, tests, and + * server-side event sources that need typed event handlers, conflict detection, + * compaction, and RPC change streams without an encryption layer. It includes + * services for mapping client store ids to server stores, authorizing reads and + * writes, storing remote entries, and binding session authentication keys. + * + * Because payloads and journals are unencrypted, storage must be protected by + * the surrounding infrastructure. Session authentication bindings are part of + * the storage contract and must be persisted by durable implementations so a + * public key cannot silently bind to a different signing key after a restart. + * The provided memory storage is process-local and intended for ephemeral + * servers, tests, or examples rather than durable multi-process use. + * * @since 4.0.0 */ export * as EventLogServerUnencrypted from "./EventLogServerUnencrypted.ts" /** + * Utilities for authenticating event log sessions with short-lived challenges + * and Ed25519 signatures. + * + * This module builds and verifies the canonical payload that a remote peer signs + * when proving control of a session signing key. It is used by event log + * transports that need to bind a connection attempt to a remote identifier, + * session challenge, advertised event log public key, and signing public key + * before accepting session traffic. + * + * Callers are responsible for issuing fresh challenges, enforcing the challenge + * time-to-live, and tracking whether a challenge has already been consumed. The + * helpers here provide deterministic payload encoding, algorithm checks, + * signature validation, and Web Crypto integration; they do not establish peer + * trust by themselves. Trust decisions still need to compare the supplied keys + * and remote identity against the application's authorization policy, and + * signed payloads should be treated as bearer authentication material until the + * challenge expires. + * * @since 4.0.0 */ export * as EventLogSessionAuth from "./EventLogSessionAuth.ts" /** + * SQL-backed persistence for the unstable event-log journal. + * + * This module provides an `EventJournal` implementation that stores local + * entries and remote replication metadata in a `SqlClient` database. It is + * useful when event-log data needs to survive process restarts, be replayed to + * rebuild projections, or be synchronized with other journals such as remote + * servers, peer replicas, or offline clients that later reconnect. + * + * The adapter creates the entry and remotes tables with dialect-specific UUID, + * binary payload, and timestamp column types, but it only performs the minimal + * `CREATE TABLE IF NOT EXISTS` setup needed by the journal. Applications that + * customize table names, add indexes, or evolve storage should manage those + * migrations explicitly and keep encoded payloads compatible with the schemas + * that will decode historical entries. Remote sequence rows are persisted + * separately from entries, duplicate imports are ignored by primary key, and + * conflict checks rely on event name, primary key, and the timestamp derived + * from the entry id. + * * @since 4.0.0 */ export * as SqlEventJournal from "./SqlEventJournal.ts" /** + * SQL-backed storage for encrypted event-log servers. + * + * This module provides the encrypted server-side storage implementation used + * when event-log entries should be durable in a SQL database without exposing + * plaintext event data to that database. It is intended for remote event-log + * servers, sync services, and multi-client deployments where the server assigns + * stable sequence numbers and broadcasts changes while clients retain control of + * event encryption and decryption. + * + * The storage creates dialect-specific tables for the server remote id and + * session authentication bindings, then creates per-identity/store entry tables + * using a SHA-256-derived table suffix. Those entry tables store IVs, entry ids, + * encrypted entry bytes, and the SQL sequence used for ordering. Operators + * should account for these dynamically created tables in migrations, backups, + * retention policies, and table-prefix changes. Encryption key material is not + * stored here, so rotating encryption schemes or moving data between databases + * requires compatibility with the clients that produced the encrypted entries. + * * @since 4.0.0 */ export * as SqlEventLogServerEncrypted from "./SqlEventLogServerEncrypted.ts" /** + * SQL-backed storage for an unencrypted event-log server. + * + * This module provides the persistence layer used by + * `EventLogServerUnencrypted` when remote entries should be stored in a SQL + * database and streamed back to clients by store sequence. It creates and uses + * dialect-specific tables for the server remote id, per-store sequence state, + * plaintext entries, and session authentication bindings, which makes it useful + * for durable local or service-side event-log deployments where database + * backup, replication, and transactional ordering are desired. + * + * Entry payloads are intentionally written as plaintext bytes. Use this storage + * only when the database, transport, backups, logs, and operators are trusted, + * or when encryption is handled outside this module. Table names are derived + * from the provided prefixes, and writes rely on SQL transactions plus + * store-level sequence rows, so deployments should provision compatible + * isolation/locking behavior and account for dialect-specific binary and text + * column limits. + * * @since 4.0.0 */ export * as SqlEventLogServerUnencrypted from "./SqlEventLogServerUnencrypted.ts" diff --git a/packages/effect/src/unstable/http/index.ts b/packages/effect/src/unstable/http/index.ts index 462637685f..8744a66ae7 100644 --- a/packages/effect/src/unstable/http/index.ts +++ b/packages/effect/src/unstable/http/index.ts @@ -5,16 +5,61 @@ // @barrel: Auto-generated exports. Do not edit manually. /** + * Utilities for representing, validating, parsing, and serializing HTTP cookies. + * + * This module provides an immutable `Cookies` collection keyed by cookie name, + * constructors for validated `Cookie` values, and helpers for common server and + * client flows such as reading `Cookie` request headers, emitting `Set-Cookie` + * response headers, merging cookie sets, and expiring cookies. + * + * Cookie parsing is intentionally tolerant of malformed input: unsupported or + * invalid `Set-Cookie` attributes are ignored, values are percent-decoded on a + * best-effort basis, and collections keep one cookie per name. Security + * attributes such as `HttpOnly`, `Secure`, `SameSite`, and `Partitioned` are + * serialized when present, but browsers enforce their final behavior, so set + * them explicitly for session, cross-site, and HTTPS-sensitive cookies. + * * @since 4.0.0 */ export * as Cookies from "./Cookies.ts" /** + * Utilities for representing and generating HTTP entity tags. + * + * ETags are validators that identify a particular representation of a + * resource. Servers commonly attach them to responses so clients and + * intermediaries can revalidate cached content with conditional requests such + * as `If-None-Match`, or protect updates with preconditions such as `If-Match`. + * + * This module models weak and strong ETags, formats them for the `ETag` header, + * and provides generator layers that derive tags from file size and + * modification-time metadata. Metadata-derived tags are convenient for static + * files, but they are only as precise as the underlying metadata: choose strong + * tags only when that metadata reliably changes for every byte-level change, + * and use weak tags when the validator is suitable for cache revalidation but + * not for operations that require byte-for-byte identity. + * * @since 4.0.0 */ export * as Etag from "./Etag.ts" /** + * Provides an `HttpClient` implementation backed by the Web Fetch API. + * + * Use this module when an application should run HTTP requests through the + * platform's `fetch` implementation, such as browser code, edge runtimes, or + * Node.js environments that provide `globalThis.fetch`. The `Fetch` reference + * allows tests and custom runtimes to supply a different fetch function, while + * `RequestInit` can provide defaults such as credentials, redirect behavior, + * cache mode, or other platform-specific fetch options. + * + * The client translates Effect HTTP requests into fetch calls and wraps Web + * `Response` values as `HttpClientResponse`s. Fetch implementations control + * details such as CORS, cookies, redirect handling, and abort semantics, so + * behavior can vary by platform. Stream request bodies are sent as Web streams + * with `duplex: "half"` for runtimes that require it, and `content-length` is + * omitted so fetch can manage body framing itself. + * * @since 4.0.0 */ export * as FetchHttpClient from "./FetchHttpClient.ts" @@ -25,101 +70,459 @@ export * as FetchHttpClient from "./FetchHttpClient.ts" export * as FindMyWay from "./FindMyWay.ts" /** + * Utilities for representing and transforming HTTP headers. + * + * This module provides an immutable `Headers` collection for request and + * response metadata, along with constructors and combinators for common header + * workflows such as reading values, checking for presence, setting or merging + * header sets, removing names, and redacting sensitive headers before + * inspection. + * + * Header names are normalized to lowercase by the safe constructors and + * lookups, matching HTTP's case-insensitive header-name semantics. Each stored + * header name maps to a single string value: array values in record input are + * joined with `", "`, iterable input keeps the last value for duplicate names, + * and later values override earlier ones when setting or merging. Be careful + * with headers that require distinct field lines, such as `set-cookie`, because + * this representation does not preserve multiple values separately. + * * @since 4.0.0 */ export * as Headers from "./Headers.ts" /** + * Utilities and data types for describing outgoing HTTP body content. + * + * This module provides a small set of `HttpBody` variants used by HTTP client + * requests and server responses: empty bodies, raw runtime values, in-memory + * bytes, `FormData`, and byte streams. Constructors cover the common cases of + * text, JSON, URL-encoded forms, multipart forms, and files while carrying the + * content type and, when known, the content length used by platform adapters. + * + * Streaming bodies are represented as streams of `Uint8Array` chunks and may + * omit `contentLength` when the size is not known ahead of time. Multipart + * `FormData` intentionally leaves `contentType` unset so the runtime can add + * the required boundary; setting that header manually can produce invalid + * requests. + * * @since 4.0.0 */ export * as HttpBody from "./HttpBody.ts" /** + * Composable HTTP client service for executing `HttpClientRequest` values and + * receiving `HttpClientResponse` values inside Effect programs. + * + * This module provides the `HttpClient` service tag, method-specific accessors, + * constructors for low-level runtimes, and middleware-style combinators for + * common client concerns such as request rewriting, response filtering, retries, + * redirects, cookies, rate limiting, and tracing. It is intended for code that + * needs dependency-injected outbound HTTP calls, reusable clients customized for + * an API, or cross-cutting behavior layered around a concrete platform client. + * + * Responses are successful Effects even for non-2xx status codes unless a + * filter such as `filterStatus` or `filterStatusOk` is applied. Request + * middleware is ordered by whether it prepends to or appends after the existing + * preprocessing pipeline, so use `mapRequestInput` for transformations that + * should run before previously installed request middleware and `mapRequest` + * for transformations that should run after it. Non-scoped responses are tied to + * an abort controller for interruption cleanup; use `withScope` when the request + * lifetime should instead be controlled by a surrounding `Scope`. + * * @since 4.0.0 */ export * as HttpClient from "./HttpClient.ts" /** + * Error types used by the HTTP client to describe failures that occur while + * preparing requests, sending them, validating response status codes, and + * decoding response bodies. + * + * The module exposes the `HttpClientError` wrapper together with the specific + * reason classes it can carry, so applications can either handle all HTTP + * client failures uniformly or branch on the exact `_tag` for retries, logging, + * metrics, and user-facing messages. A common gotcha is that only response + * errors carry an `HttpClientResponse`: transport, encoding, and invalid URL + * failures happen before a response is available, while status-code, decode, and + * empty-body failures preserve the response that triggered them. + * * @since 4.0.0 */ export * as HttpClientError from "./HttpClientError.ts" /** + * Utilities for constructing immutable outgoing HTTP client requests. + * + * This module models the request data passed to HTTP clients and adapters: + * method, URL, query parameters, hash, headers, and body. It provides + * method-specific constructors, pipeable combinators for adding authentication + * headers and accepted media types, helpers for JSON, form, stream, and file + * bodies, and conversions to and from the Web `Request` type. + * + * Request construction keeps the base URL, query parameters, and hash as + * separate fields until conversion. Passing a `URL` extracts its search + * parameters and fragment into those structured fields, while string URLs are + * kept as provided. Use the `setUrlParam` helpers when replacing query values + * and the `appendUrlParam` helpers when multiple values for the same key should + * be preserved. Setting a body also updates `Content-Type` and + * `Content-Length` from the body metadata when available; `FormData` leaves + * those headers to the runtime so multipart boundaries can be generated + * correctly. + * * @since 4.0.0 */ export * as HttpClientRequest from "./HttpClientRequest.ts" /** + * Utilities for inspecting, decoding, and filtering HTTP client responses. + * + * An `HttpClientResponse` pairs the platform `Response` with the request that + * produced it, exposing status, headers, cookies, and effectful views of the + * response body. Use this module after an `HttpClient` call to branch on status + * with `matchStatus` or `filterStatus`, decode JSON or URL-encoded bodies with + * schemas, stream bytes, or adapt a Web `Response` with `fromWeb`. + * + * Response bodies come from the underlying Web response and should be decoded + * deliberately: `json` parses an empty text body as `null`, body readers fail + * with `HttpClientError` when decoding fails, and the raw stream fails when no + * body is present. Headers are represented by the HTTP `Headers` module's + * single-value, lowercase map, while response cookies are parsed separately from + * `Set-Cookie` headers. Status values are not considered errors by themselves; + * use the provided filters or matchers when only specific status codes are + * acceptable. + * * @since 4.0.0 */ export * as HttpClientResponse from "./HttpClientResponse.ts" /** + * Utilities for running HTTP server effects at the boundary between Effect and + * platform request handlers. + * + * This module is used to turn an effect that produces an `HttpServerResponse` + * into a concrete handler, such as a Web `Request` handler, while applying + * middleware, converting failures into HTTP responses, and preserving the + * current `HttpServerRequest` in the Effect context. It also provides hooks for + * adjusting a response immediately before it is sent and helpers for managing + * the request `Scope`, especially when a streaming response must own that scope + * until the stream completes. + * + * Handlers built here expect the per-request context to contain + * `HttpServerRequest` and, for scoped resources, `Scope.Scope`. Failures are + * reported and translated through `HttpServerError` / respondable conversions, + * so unhandled defects generally become server error responses while request + * aborts and already-sent responses need to be handled with the provided + * middleware and scope utilities. + * * @since 4.0.0 */ export * as HttpEffect from "./HttpEffect.ts" /** + * Shared utilities for reading and decoding incoming HTTP messages. + * + * `HttpIncomingMessage` is the common body-and-header surface used by HTTP + * server requests and client responses. It keeps transport-specific metadata in + * the surrounding request and response modules while this module focuses on + * headers, optional remote address information, byte streams, buffered body + * views, and schema decoders for JSON bodies, URL-encoded bodies, and headers. + * + * Use these helpers in middleware, route handlers, client response processing, + * and adapters when code should work with any incoming message instead of a + * concrete request or response type. Body access is effectful because reading, + * parsing, and decoding can fail; use `stream` when bytes should stay + * streaming, and use `text`, `json`, `urlParamsBody`, or `arrayBuffer` when a + * buffered view is appropriate. Some runtimes expose bodies as one-shot Web + * streams, so prefer one body representation per message and let each + * implementation's cached accessors handle repeated reads where available. + * + * Headers use the HTTP `Headers` module's lowercase, single-value map, so + * repeated values may already have been combined or normalized by the adapter. + * Decode headers with `schemaHeaders` when their shape matters. For form + * bodies, `urlParamsBody` handles URL-encoded payloads; multipart support lives + * on `HttpServerRequest`, with `MaxBodySize` providing the shared limit + * reference used by multipart parsing. + * * @since 4.0.0 */ export * as HttpIncomingMessage from "./HttpIncomingMessage.ts" /** + * Defines the supported HTTP method literals shared by the unstable HTTP client, + * server, and routing APIs. + * + * Use this module when constructing method-specific requests, matching incoming + * requests, validating unknown method values, or deriving method helper names. + * Methods are represented as uppercase string literals, so values such as `"get"` + * are not accepted as `HttpMethod` values. + * + * The body classification is intentionally conservative and file-specific: + * `GET`, `HEAD`, `OPTIONS`, and `TRACE` are modeled as bodyless methods, while + * `POST`, `PUT`, `DELETE`, and `PATCH` are modeled as methods that can carry a + * request body. This means `DELETE` is allowed to carry a body even though some + * servers and intermediaries may ignore it, and `GET` request bodies are not + * represented by these helpers even though the wire protocol does not strictly + * forbid them. + * * @since 4.0.0 */ export * as HttpMethod from "./HttpMethod.ts" /** + * Server-side HTTP middleware for wrapping `HttpServerResponse` effects with + * cross-cutting request and response behavior. + * + * A middleware is a function from one HTTP server app effect to another. The app + * is evaluated with the current `HttpServerRequest` service in its context, so + * middleware in this module can inspect or rewrite the request, provide + * request-scoped services, attach pre-response hooks, or observe the app exit + * while preserving normal Effect error and interruption semantics. + * + * Use this module for common server concerns such as access logging, trace span + * creation, trusting forwarded proxy headers, parsing search parameters, and + * adding CORS handling. Middleware can be applied directly when serving an + * `HttpServer` / `HttpEffect` app or registered through `HttpRouter.middleware` + * for route-scoped or global behavior. + * + * Middleware composition is order-sensitive, and each middleware may change the + * wrapped effect's requirements or error channel. These functions expect a + * per-request `HttpServerRequest` to be present; context-providing middleware + * should wrap handlers before they access the provided service, and + * error-handling middleware should be installed where its transformed error type + * matches the surrounding app or router registration. + * * @since 4.0.0 */ export * as HttpMiddleware from "./HttpMiddleware.ts" /** + * Platform-specific support for serving files as HTTP server responses. + * + * `HttpPlatform` is the boundary between the portable HTTP response model and + * the runtime that knows how to stream bytes from the host platform. Server + * code uses this service when it needs to return local files, static assets, + * downloads, byte ranges, or Web `File`-like values without constructing the + * response body by hand. + * + * The helpers in this module enrich those responses with file metadata such as + * `etag`, `last-modified`, and content length where available. Path-based + * responses require `FileSystem` and can fail with `PlatformError` while + * inspecting or streaming the file; `File`-like responses use the Web + * `ReadableStream` and `lastModified` metadata exposed by the value. + * + * Provide `layer` when the default streaming implementation is suitable, or + * use `make` to plug in a runtime-specific response constructor. The default + * layer supplies weak ETag generation itself, but the surrounding runtime still + * needs to provide the `FileSystem` service and run the resulting + * `HttpServerResponse` on an HTTP server adapter that understands Effect + * streams. + * * @since 4.0.0 */ export * as HttpPlatform from "./HttpPlatform.ts" /** + * Layer-based server-side HTTP routing for Effect applications. + * + * This module provides the `HttpRouter` service and helpers for registering + * method/path handlers, grouping routes under prefixes, decoding request + * schemas from route and search parameters, and turning an application layer + * into an `HttpServer` or Fetch-compatible handler. It is intended for HTTP + * APIs, webhooks, and other server endpoints that want request-scoped services + * and typed middleware to be composed through `Layer`. + * + * Route paths must be absolute paths beginning with `/`, or the wildcard `*`. + * Prefixed routes remove the matched prefix from the request URL seen by the + * handler, `HEAD` requests fall back to matching `GET` routes, and wildcard + * paths ending in `/*` also match the prefix path itself. Use router middleware + * when you need to provide request dependencies, handle configured route errors, + * or modify route responses; server middleware wraps the wider server chain and + * is not the right hook for changing the final response body or headers. + * * @since 4.0.0 */ export * as HttpRouter from "./HttpRouter.ts" /** + * Service and helpers for running Effect HTTP applications on a concrete server + * runtime. + * + * This module defines the `HttpServer` service tag used by platform integrations + * to expose a listening server, plus accessors for serving an + * `HttpServerResponse` effect, formatting and logging server addresses, and + * building test clients against a running server. It is intended for low-level + * server runtimes, router integrations, HTTP API tests, and applications that + * need to start serving from a provided `Layer`. + * + * The server supplies `HttpServerRequest` for each request, so application + * effects should rely on the server for request-scoped data while still + * providing their other services through the surrounding environment. `serve` + * returns a `Layer` whose listener lifetime is managed by the layer scope; use + * `serveEffect` when composing directly in an effect with an explicit `Scope`. + * Test clients only support TCP addresses, and rewrite `0.0.0.0` to + * `127.0.0.1` for local requests. + * * @since 4.0.0 */ export * as HttpServer from "./HttpServer.ts" /** + * Error types and response conversion helpers used by the HTTP server runtime. + * + * This module models the failure cases that can happen around a server request: + * malformed or unreadable requests, unmatched routes, unexpected handler + * failures, response construction or delivery failures, and lower-level server + * implementation failures. These errors keep the relevant request, and for + * response failures the response that was being produced, so applications can + * report, inspect, or translate failures without losing HTTP context. + * + * Most users encounter these errors when decoding request bodies, implementing + * fallback routes, adding error reporting, or customizing how handler failures + * become responses. Request parse errors become `400` responses, missing routes + * become `404` responses and are ignored by the error reporter, and internal or + * response errors become `500` responses. A `ResponseError` records the response + * involved in the failure, but its default conversion intentionally sends an + * empty `500` instead of reusing a response that may already be invalid or + * partially failed. Handler causes can also contain respondable failures, + * response defects, or interrupts; the conversion helpers preserve those + * distinctions, including `499` for client aborts and `503` for server aborts. + * * @since 4.0.0 */ export * as HttpServerError from "./HttpServerError.ts" /** + * Utilities for working with the request visible to HTTP server handlers. + * + * This module defines `HttpServerRequest`, the request-scoped context service + * used by server effects, middleware, schema decoders, multipart parsers, + * WebSocket upgrades, and conversions between Effect HTTP requests, client + * requests, and Web `Request` values. Handlers commonly use it to inspect the + * method, URL, headers, cookies, remote address, and body, or to decode those + * parts with schemas instead of parsing raw values by hand. + * + * Body access is effectful because reading, parsing, schema decoding, or + * multipart persistence can fail. Streaming request bodies may be single-use + * depending on the underlying platform, while cached accessors such as text, + * JSON, URL parameters, array buffers, and persisted multipart data reuse the + * first read. Multipart persistence also requires `Scope`, `FileSystem`, and + * `Path` services, and search parameter decoding depends on the + * `ParsedSearchParams` service being provided by the router or adapter. + * * @since 4.0.0 */ export * as HttpServerRequest from "./HttpServerRequest.ts" /** + * Protocol and conversion helpers for values that can become HTTP server + * responses. + * + * This module lets server-side domain errors, HTTP API errors, and helper + * modules describe how they should be sent to a client without constructing an + * `HttpServerResponse` at every call site. Implement `Respondable` on values + * that should choose their own status, headers, cookies, or body when a route + * fails or a server helper recovers by sending a response. + * + * Conversion is intentionally conservative. Existing `HttpServerResponse` + * values are returned directly, fallback conversion maps schema errors to `400` + * and no-such-element errors to `404`, and otherwise uses the caller-provided + * fallback. Errors raised while running a respondable conversion become defects + * with `toResponse`, while the fallback helpers catch conversion failures and + * use the fallback response. Defect conversion only gives special handling to + * `HttpServerResponse` and `Respondable` values. + * * @since 4.0.0 */ export * as HttpServerRespondable from "./HttpServerRespondable.ts" /** + * Server-side HTTP response values and constructors for Effect HTTP handlers. + * + * This module defines `HttpServerResponse`, the immutable response model returned + * from routers and server effects, together with constructors for empty, + * redirect, text, HTML, JSON, URL-encoded, raw, form-data, stream, and file + * bodies. It also includes combinators for adjusting status, headers, cookies, + * and bodies, plus conversions to and from Web `Response` and client responses. + * + * Response constructors choose status defaults for common cases: `204` for + * `empty`, `302` for `redirect`, and `200` for responses with bodies. Body + * metadata is mirrored into headers, so content type and content length carried + * by the body are written to `content-type` and `content-length`, overriding + * existing values when present. JSON helpers can fail while encoding or + * serializing unless the unsafe constructor is used. + * + * Cookies are tracked separately from the normal header map so they can be + * encoded as `Set-Cookie` headers when converting to platform responses. Use the + * effectful cookie helpers when invalid cookie names, values, or options should + * stay in the Effect error channel. + * * @since 4.0.0 */ export * as HttpServerResponse from "./HttpServerResponse.ts" /** + * Static file serving for Effect HTTP applications. + * + * This module builds request handlers and router layers that serve files from a + * configured root directory. It is intended for public assets such as compiled + * front-end bundles, images, fonts, downloads, documentation sites, and single + * page applications that need an `index.html` fallback. + * + * Requests are resolved relative to the configured root after decoding and + * normalizing the URL path. Malformed paths, null bytes, and `..` traversal + * outside the root are rejected, but the module still serves anything the + * configured `FileSystem` can reach below that root. Keep secrets out of the + * served tree, be careful with symlinks or generated files, and remember that + * dotfiles are not hidden automatically. File responses include content type, + * optional cache control, byte-range support, and conditional request handling + * based on the metadata supplied by `HttpPlatform`. + * * @since 4.0.0 */ export * as HttpStaticServer from "./HttpStaticServer.ts" /** + * Utilities for HTTP trace-context propagation. + * + * This module converts Effect `Tracer.Span` values to outbound tracing headers + * and decodes inbound propagation headers into `Tracer.ExternalSpan` parents. + * It is used by traced HTTP clients to continue the current span across an + * outbound request, and by server middleware to parent request spans from + * upstream services. The helpers are also useful for adapters or middleware + * that need to bridge Effect tracing with W3C Trace Context or B3-compatible + * systems. + * + * Outbound propagation writes both W3C `traceparent` and compact B3 `b3` + * headers. Inbound decoding prefers W3C `traceparent`, then compact B3, then + * multi-header B3 (`x-b3-*`). Header names in `Headers.Headers` are expected to + * be lowercase; use the safe header constructors when accepting raw platform + * headers. Invalid or unsupported header shapes simply decode to `Option.none`, + * so callers should treat missing trace context as "start a new trace" rather + * than as an error. + * * @since 4.0.0 */ export * as HttpTraceContext from "./HttpTraceContext.ts" /** + * Utilities for parsing and working with HTTP `multipart/form-data` request + * bodies. + * + * This module converts multipart byte streams into typed `Part` values, either + * as decoded text `Field` values or streamed `File` values. It is used by HTTP + * server request handling for browser form submissions, API endpoints that + * accept file uploads, and mixed payloads where structured fields accompany one + * or more uploaded files. Persisted helpers collect fields into records and + * write files into scoped temporary paths that can be decoded with schemas. + * + * Multipart bodies can be large and are often backed by one-shot request + * streams, so prefer streaming file content unless the file is small enough to + * collect with `contentEffect`. Persisted file paths are valid only while their + * scope is open, and client-provided filenames should be treated as metadata + * rather than trusted filesystem paths. Parser limits for part count, field + * size, file size, total body size, and field MIME type handling are provided + * through the module's context references. + * * @since 4.0.0 */ export * as Multipart from "./Multipart.ts" @@ -130,16 +533,65 @@ export * as Multipart from "./Multipart.ts" export * as Multipasta from "./Multipasta.ts" /** + * Template literal helpers for rendering HTTP-oriented text with Effect values. + * + * This module powers response helpers that accept template tags, such as HTML + * responses with dynamic fragments, deferred service lookups, or streaming + * sections. Use `make` when the whole rendered value should be assembled before + * building the response, and `stream` when parts of the template can be emitted + * incrementally from effects or streams. + * + * Interpolation is intentionally simple: primitive values are converted to + * strings, arrays are concatenated without separators, and `Option.none`, + * `null`, and `undefined` render as empty text. The module does not escape HTML, + * encode bytes, set content types, or compute content lengths, so callers should + * escape or encode untrusted values and choose the appropriate response + * constructor for the rendered output. + * * @since 4.0.0 */ export * as Template from "./Template.ts" /** + * Utilities for parsing and immutably updating HTTP URLs. + * + * This module works with the platform `URL` type used by HTTP clients and + * servers, adding safe constructors and pipeable setters for common workflows + * such as resolving request targets against a base URL, changing credentials, + * host, path, protocol, query, and hash components, and reading or rewriting + * query parameters through `UrlParams`. + * + * Parsing and serialization follow the platform WHATWG `URL` behavior. Relative + * inputs need an explicit base, assigned components may be normalized or + * percent-encoded by `URL`, and query strings should usually be handled through + * `UrlParams` when preserving repeated keys or applying key/value encoding is + * important. + * * @since 4.0.0 */ export * as Url from "./Url.ts" /** + * Utilities for representing, transforming, and serializing URL query + * parameters. + * + * This module provides an immutable `UrlParams` collection backed by ordered + * string key-value pairs. It is used for HTTP client request queries, + * URL-encoded form bodies, and server-side decoding workflows where query + * parameters need to be built from records, iterables, or native + * `URLSearchParams`, then inspected, appended, replaced, removed, converted to a + * URL, or decoded with schemas. + * + * Duplicate keys are preserved by the core representation and by append-style + * operations; use `getAll` when all values matter, and note that `set` and + * `setAll` replace existing values for matching keys. Serialization through + * `toString` and `makeUrl` delegates to the platform `URLSearchParams` / `URL` + * implementations, so provide decoded strings rather than pre-encoded query + * fragments. Record-based and schema-based conversions intentionally collapse + * repeated keys into string arrays and do not preserve the full global pair + * ordering; `schemaJsonField` reads the first matching value for the selected + * field. + * * @since 4.0.0 */ export * as UrlParams from "./UrlParams.ts" diff --git a/packages/effect/src/unstable/httpapi/index.ts b/packages/effect/src/unstable/httpapi/index.ts index 8a92a73af1..299f93691f 100644 --- a/packages/effect/src/unstable/httpapi/index.ts +++ b/packages/effect/src/unstable/httpapi/index.ts @@ -5,92 +5,377 @@ // @barrel: Auto-generated exports. Do not edit manually. /** + * The `HttpApi` module defines the top-level contract for an Effect HTTP API. + * An `HttpApi` names an API and collects one or more `HttpApiGroup`s, whose + * endpoints describe the request inputs, response schemas, middleware, and + * annotations that later drive server builders, clients, and OpenAPI + * generation. + * + * Use this module when you want to compose a domain API from endpoint groups, + * combine APIs from multiple modules, apply a shared path prefix or middleware, + * attach annotations, or reflect over the final route shape. Implementations are + * provided separately with `HttpApiBuilder.group`, and the completed API is + * registered with `HttpApiBuilder.layer`. + * + * A few composition details are worth keeping in mind: group identifiers are + * used as keys, so adding another group with the same identifier replaces the + * previous one; `prefix` and `middleware` are applied to the groups and + * endpoints already present when they are called; and `addHttpApi` merges the + * added API's annotations into its groups. During reflection, success and error + * schemas are grouped by the HTTP status recorded on their `HttpApiSchema` + * annotations, endpoints without an explicit success schema default to + * `NoContent`, and middleware error schemas are included with endpoint errors. + * Extra schemas supplied through `AdditionalSchemas` must have an `identifier` + * annotation so they can be emitted as OpenAPI components. + * * @since 4.0.0 */ export * as HttpApi from "./HttpApi.ts" /** + * The `HttpApiBuilder` module connects declarative `HttpApi` definitions to + * runnable HTTP server routes. + * + * Use this module when you have described an API with `HttpApi`, + * `HttpApiGroup`, and `HttpApiEndpoint` values and need to provide the + * server-side implementation. `group` creates a layer for implementing every + * endpoint in one API group, `layer` registers the implemented groups with an + * `HttpRouter` and can expose the generated OpenAPI specification, and + * `endpoint` builds the effect for a single endpoint when custom composition is + * needed. + * + * The builder performs the runtime work implied by endpoint metadata: it decodes + * path parameters, headers, query parameters, and request payloads with + * `Schema`, applies endpoint middleware and security middleware, invokes the + * registered handler, and encodes successful or declared error results into + * `HttpServerResponse` values. Handlers can return an `HttpServerResponse` + * directly to bypass success encoding, and `handleRaw` can be used when payload + * decoding should be handled manually. + * + * A few implementation details are worth keeping in mind. Every group in the + * API must be provided with `HttpApiBuilder.group` before `layer` is evaluated, + * otherwise registration fails with a defect that names the missing group and + * the available group services. Payload decoding is selected by request media type; + * unsupported content types produce a `415` response before the handler runs. + * Schema failures are wrapped as `HttpApiSchemaError`, while ordinary handler + * failures are encoded with the endpoint's declared error schemas. + * * @since 4.0.0 */ export * as HttpApiBuilder from "./HttpApiBuilder.ts" /** + * Builds type-safe clients and URL builders from `HttpApi` declarations. + * + * This module turns the groups and endpoints described by an `HttpApi` into + * callable client methods backed by an `HttpClient`. Applications commonly use + * `make` or `makeWith` to call a remote API with the same schema-driven contract + * as the server, while `group`, `endpoint`, and `urlBuilder` are useful when only + * part of an API or only the encoded URL is needed. + * + * Client calls encode path parameters, query values, headers, and payloads from + * endpoint schemas before executing the request, then decode successful responses + * according to the endpoint success schemas. The selected `responseMode` can + * return the decoded value, the raw `HttpClientResponse`, or both; the raw + * response mode skips success and error decoding for custom response handling. + * + * Pay attention to the endpoint schemas when shaping requests: payloads for HTTP + * methods without request bodies are encoded into URL parameters, multipart + * payloads must be supplied as `FormData`, and response decoding can fail with + * `SchemaError`. Declared error responses are decoded into the endpoint error + * type, unknown statuses fail as `HttpClientError.DecodeError`, and failures while + * decoding a declared error response include the original status-code failure. + * * @since 4.0.0 */ export * as HttpApiClient from "./HttpApiClient.ts" /** + * The `HttpApiEndpoint` module defines the per-route contracts used inside an + * `HttpApiGroup`. + * + * An endpoint couples a stable name with an HTTP method and `HttpRouter` path, + * plus schemas for path parameters, query parameters, headers, request payloads, + * success responses, and declared errors. Server builders, generated clients, + * and OpenAPI generation all read this metadata to decode requests, encode + * responses, type handler inputs, and derive client call signatures. + * + * Use this module to declare individual operations such as `get`, `post`, `put`, + * `patch`, `delete`, `head`, and `options`; attach endpoint-specific middleware + * or annotations; and model alternatives for payloads, successes, and errors + * with arrays of schemas. + * + * A few declaration details are worth keeping in mind. Paths use + * `HttpRouter.PathInput`, so route parameters come from the router and are + * decoded with the optional `params` schema. When codecs are enabled, params, + * query, and headers are transformed through string-tree codecs; body methods + * use JSON payload codecs by default, while no-body methods encode payloads as + * query-style values. `HttpApiSchema` annotations can change payload or response + * encodings and status codes, multipart payloads cannot be combined under the + * same content type, and endpoint errors are merged with middleware errors for + * server encoding and client decoding. + * * @since 4.0.0 */ export * as HttpApiEndpoint from "./HttpApiEndpoint.ts" /** + * The `HttpApiError` module defines the built-in error types used by Effect's + * unstable HTTP API layer for common failure responses. + * + * Each exported status error is a `Schema.ErrorClass` with an `httpApiStatus` + * annotation, so it can be declared as an endpoint or middleware error schema + * and then used by reflection, OpenAPI generation, clients, and server response + * encoding. The classes also implement `HttpServerRespondable`, which means + * using one directly as a server response produces an empty response with the + * matching HTTP status. + * + * Use the `*NoContent` variants when the wire response intentionally has no + * body but clients should still decode that status into a typed error value. + * For custom error schemas, make sure to add the intended + * `HttpApiSchema.status` annotation; error schemas without one are treated as + * `500 Internal Server Error` by the HTTP API machinery. Schema failures raised + * while decoding params, headers, query, body, or payload values are represented + * separately by `HttpApiSchemaError`, which responds as `400 Bad Request` unless + * transformed by middleware. + * * @since 4.0.0 */ export * as HttpApiError from "./HttpApiError.ts" /** + * The `HttpApiGroup` module defines named collections of `HttpApiEndpoint`s + * within an `HttpApi`. + * + * Groups are the main way to organize endpoints by a domain boundary, resource, + * or feature area before those endpoints are added to an API and implemented + * with `HttpApiBuilder.group`. A group carries its identifier, endpoint set, + * annotations, and `topLevel` flag, which are later used by builders, clients, + * URL builders, and OpenAPI generation. Non-top-level groups expose nested + * client methods under the group name, while top-level groups expose their + * endpoint methods directly. + * + * Composition is order-sensitive. Adding an endpoint with the same name as an + * existing endpoint replaces it, and `prefix`, `middleware`, + * `annotateEndpoints`, and `annotateEndpointsMerge` only affect endpoints that + * are already present when those APIs are called. Group annotations apply to the + * group itself; use the endpoint annotation helpers when metadata should be + * attached to each endpoint. + * + * The type helpers in this module reflect the endpoint union for a group and + * aggregate the services required by endpoint schemas, middleware, and declared + * errors. Error schemas are still declared on endpoints, while middleware can + * contribute additional error schemas and client/server service requirements + * through the endpoint middleware set. + * * @since 4.0.0 */ export * as HttpApiGroup from "./HttpApiGroup.ts" /** + * The `HttpApiMiddleware` module defines middleware services that can wrap + * `HttpApi` endpoint execution on the server and request execution in generated + * clients. + * + * Use this module for cross-cutting HTTP API behavior such as authentication and + * authorization, request logging or tracing, rate limiting, adding request-scoped + * services to the endpoint context, normalizing schema errors, or installing + * client-side request middleware for APIs that require the same concern on both + * sides. Middleware services carry type-level metadata describing the services + * they require and provide, the error schemas they may fail with, whether they + * implement security schemes, and whether generated clients must provide a + * matching client middleware. + * + * Security middleware is declared with non-empty `security` schemes and receives + * decoded credentials from `HttpApiSecurity`; ordinary middleware receives only + * endpoint and group metadata. Error declarations must be `Schema` values (or an + * array of them) because middleware failures are added to the endpoint error + * surface and must be encodable by the HTTP API builder. If a middleware turns + * `HttpApiSchemaError` failures into API errors, use + * `layerSchemaErrorTransform` and make sure the transformed error is covered by + * the middleware's declared schema. Client middleware installed with + * `layerClient` is made available through the `ForClient` marker and captures + * its surrounding context, so client requirements should be declared explicitly + * when `requiredForClient` is enabled. + * * @since 4.0.0 */ export * as HttpApiMiddleware from "./HttpApiMiddleware.ts" /** + * The `HttpApiScalar` module mounts an interactive Scalar API reference for a + * declarative `HttpApi`. + * + * Use this module when you want a browser-friendly documentation page for an + * `HttpApi` without maintaining a separate OpenAPI document. The `layer` + * helper registers a `GET` route on an `HttpRouter`, generates the OpenAPI + * specification with `OpenApi.fromApi`, embeds it into the HTML page, and loads + * the bundled Scalar browser script. `layerCdn` provides the same UI while + * loading Scalar from jsDelivr, optionally pinned with `version`. + * + * The mounted path is a documentation UI route, defaulting to `/docs`, rather + * than a raw JSON specification endpoint. If clients, gateways, or external + * documentation pipelines need the OpenAPI document directly, expose it + * separately with `HttpApiBuilder.layer`'s `openapiPath` option. Scalar + * configuration is forwarded to the page through `ScalarConfig`; values such as + * `proxyUrl`, theme and layout settings, and `baseServerURL` matter when + * enabling "Test Request", styling the docs, or rendering relative server URLs + * outside the browser origin. + * * @since 4.0.0 */ export * as HttpApiScalar from "./HttpApiScalar.ts" /** - * HttpApiSchema provides helpers to annotate Effect Schema values with HTTP API metadata - * (status codes and payload/response encodings) used by the HttpApi builder, client, - * and OpenAPI generation. - * - * Mental model: - * - A "Schema" is the base validation/encoding description from `Schema`. - * - An "Encoding" tells HttpApi how to serialize/parse a payload or response body. - * - A "Status" is metadata that chooses the HTTP response status code. - * - "Empty" schemas represent responses with no body (204/201/202 or custom). - * - "NoContent" schemas can still decode into a value via {@link asNoContent}. - * - Multipart is a payload-only encoding for file-like form data. - * - * Common tasks: - * - Set a response status on a schema -> {@link status} - * - Declare an empty response -> {@link Empty}, {@link NoContent}, {@link Created}, {@link Accepted} - * - Decode an empty response into a value -> {@link asNoContent} - * - Force a specific encoding -> {@link asJson}, {@link asFormUrlEncoded}, {@link asText}, {@link asUint8Array} - * - Mark multipart payloads -> {@link asMultipart}, {@link asMultipartStream} - * - * Gotchas: - * - If you don't set an encoding, HttpApi assumes JSON by default. - * - {@link asFormUrlEncoded} expects the schema's encoded type to be a record of strings. - * - {@link asText} expects the encoded type to be `string`, and {@link asUint8Array} expects `Uint8Array`. - * - Multipart encodings are intended for request payloads; response multipart is not supported. - * - These helpers annotate schemas; they don't perform validation or IO by themselves. + * Helpers for attaching HTTP API metadata to Effect Schema values. + * + * This module is the schema-side bridge used by the unstable HttpApi endpoint + * builder, generated clients, and OpenAPI support. It does not define routes or + * perform IO. Instead, helpers such as {@link status}, {@link asJson}, + * {@link asMultipart}, and {@link asNoContent} annotate schemas so downstream + * HTTP tooling can choose response status codes, content types, body codecs, and + * no-body handling while the original schema remains usable for validation and + * transformation. + * + * Common use cases: + * - Mark success or error schemas with explicit HTTP statuses using + * {@link status}, {@link NoContent}, {@link Created}, {@link Accepted}, or + * {@link Empty}. + * - Override the default JSON encoding for request payloads or responses with + * {@link asFormUrlEncoded}, {@link asText}, {@link asUint8Array}, or + * {@link asJson}. + * - Describe buffered or streaming multipart request payloads with + * {@link asMultipart} and {@link asMultipartStream}. + * - Represent an HTTP response with no body while still decoding a client-side + * value through {@link asNoContent}. + * + * Status and encoding details: + * - {@link status} only stores an annotation. The same annotation is interpreted + * by the surrounding HttpApi context; unannotated success responses default to + * `200`, and unannotated error responses default to `500`. + * - Missing encodings default to JSON for bodies and responses. Payload schemas + * used with methods that have no request body fall back to form-url-encoded + * metadata for parameter encoding. + * - {@link asFormUrlEncoded} expects the schema's encoded side to be a record + * of strings. {@link asText} expects `string`, and {@link asUint8Array} + * expects `Uint8Array`. + * - Multipart encodings are payload-only; response multipart is rejected when + * response encoding is resolved. + * - These helpers attach annotations consumed by HttpApi internals. They do not + * validate, encode, decode, or send data by themselves. * * @since 4.0.0 */ export * as HttpApiSchema from "./HttpApiSchema.ts" /** + * The `HttpApiSecurity` module defines the security scheme values used by + * declarative HTTP APIs. + * + * Use these constructors when an API group or endpoint needs authentication + * middleware for bearer tokens, API keys, or HTTP Basic credentials. The values + * are intentionally small declarations: `HttpApiMiddleware.Service` attaches + * them to middleware, `HttpApiBuilder` decodes the matching credential shape from + * each request, and OpenAPI generation emits the corresponding + * `components.securitySchemes` and operation security requirements. + * + * Common uses include modeling `Authorization: Bearer ...` tokens, Basic + * username/password credentials, and API keys passed through headers, query + * parameters, or cookies. Bearer tokens and API-key values are exposed to + * middleware as `Redacted` values; Basic credentials expose the username with a + * redacted password. Cookie API keys can also be written to responses with + * `HttpApiBuilder.securitySetCookie`. + * + * A security scheme does not authenticate by itself: middleware must reject empty + * or invalid credentials. Bearer and Basic schemes read the `Authorization` + * header, while API-key headers use HTTP header name normalization and API-key + * query or cookie names are matched exactly. OpenAPI annotations such as + * descriptions and bearer formats affect generated documentation only; they do + * not change runtime decoding. + * * @since 4.0.0 */ export * as HttpApiSecurity from "./HttpApiSecurity.ts" /** + * Serves Swagger UI for an `HttpApi` by rendering the OpenAPI document generated + * from the API directly into an HTML page. + * + * Use this module when you want a lightweight documentation route for a running + * `HttpApi`, typically in development, staging, internal consoles, or public API + * reference pages where Swagger UI's exploration and request-building tools are + * preferred. The exported `layer` adds a `GET` route to an `HttpRouter`, + * defaults the mount path to `/docs`, and leaves API implementation and server + * wiring to `HttpApiBuilder` and the surrounding router layers. + * + * The page is self-contained: `OpenApi.fromApi` derives the specification from + * the API's groups, endpoints, schemas, and OpenAPI annotations, then the JSON + * is embedded into the HTML served to the browser. No separate `/openapi.json` + * endpoint is installed by this module, so clients or documentation pipelines + * that need the raw spec should use `OpenApi.fromApi` directly or expose a JSON + * route elsewhere. If the docs are public, mount the layer behind the same + * routing, security, or environment controls you want for the UI; generated + * server URLs and operation metadata come from the API's OpenAPI annotations. + * * @since 4.0.0 */ export * as HttpApiSwagger from "./HttpApiSwagger.ts" /** + * The `HttpApiTest` module provides helpers for testing `HttpApi` + * implementations through the generated client interface without starting an + * HTTP server. + * + * Use this module when a test should exercise one or more implemented API + * groups with the same request encoding, routing, response encoding, and client + * decoding used by the production `HttpApiBuilder` and `HttpApiClient` + * pipeline. This is useful for focused handler tests, schema round-trip checks, + * middleware behavior, and tests that want to call an API through its typed + * client while keeping all traffic in memory. + * + * The selected groups must be provided in the test environment, usually by + * supplying the corresponding `HttpApiBuilder.group` layers. Groups that are not + * selected are still present on the returned client shape, but their handlers + * are placeholders that die if called, which helps catch accidental calls outside + * the test scope. The generated client is still a real `HttpApiClient`, so + * endpoint middleware, client middleware, platform services, and the optional + * `baseUrl` used for URL construction follow the same rules as normal clients; + * only the HTTP transport is replaced with an in-memory router dispatch. + * * @since 4.0.0 */ export * as HttpApiTest from "./HttpApiTest.ts" /** + * The `OpenApi` module converts declarative `HttpApi` definitions into + * OpenAPI 3.1 specifications and provides annotations for shaping the + * generated document. + * + * Use this module when you need to publish an `HttpApi` contract to tooling + * such as Swagger UI, Scalar, client generators, API gateways, or documentation + * pipelines. `fromApi` reflects the API's groups and endpoints into tags, + * paths, operations, parameters, request bodies, responses, security schemes, + * and component schemas while preserving Effect Schema metadata where OpenAPI + * can represent it. + * + * The generated specification is driven by annotations on APIs, groups, + * endpoints, security definitions, and schemas. `Title`, `Description`, + * `Summary`, `Version`, `Servers`, `License`, `ExternalDocs`, `Identifier`, + * `Deprecated`, and `Format` feed the corresponding OpenAPI fields; `Exclude` + * omits a group or endpoint; `Override` shallowly merges custom fields; and + * `Transform` can rewrite the generated API, tag, or operation object. Schema + * identifiers are important for stable component names, additional schemas must + * have identifiers, and invalid OpenAPI component keys are rejected during + * generation. + * + * A few generation details are worth keeping in mind: `HttpApiSchema` + * encodings choose media types and special representations for JSON, + * form-url-encoded, text, binary, and multipart payloads; no-content schemas + * emit responses without bodies; request and response unions are grouped by + * status code and content type; path parameters are rendered from `:id` route + * segments as `{id}`; and schemas are converted through the OpenAPI 3.1 JSON + * Schema representation before being patched into the final document. + * * @since 4.0.0 */ export * as OpenApi from "./OpenApi.ts" diff --git a/packages/effect/src/unstable/observability/index.ts b/packages/effect/src/unstable/observability/index.ts index ec0c0b31aa..f354349268 100644 --- a/packages/effect/src/unstable/observability/index.ts +++ b/packages/effect/src/unstable/observability/index.ts @@ -5,36 +5,176 @@ // @barrel: Auto-generated exports. Do not edit manually. /** + * Convenience layers for exporting Effect logs, metrics, and traces over + * OTLP/HTTP. + * + * This module wires the signal-specific OTLP logger, metrics, and tracer + * exporters together so an application can install full observability with a + * single layer. It is useful for services that report to an OpenTelemetry + * Collector, vendor OTLP endpoint, or local collector during development + * without configuring each signal independently. + * + * Pass `baseUrl` as the OTLP/HTTP root URL, such as `http://localhost:4318`; + * this module appends `/v1/logs`, `/v1/metrics`, and `/v1/traces` itself. + * Use `layerJson` or `layerProtobuf` when you want the serialization layer + * provided for you, or use `layer` with a custom `OtlpSerialization` + * implementation. Configure authentication with `headers`, provide resource + * metadata explicitly or through the standard OTEL resource environment + * variables, and tune batch size, export intervals, metric temporality, and + * shutdown timeout for the target backend so buffered telemetry is accepted + * and flushed before shutdown. + * * @since 4.0.0 */ export * as Otlp from "./Otlp.ts" /** + * Low-level OTLP/HTTP batch exporter used by the observability modules for + * logs, metrics, and traces. + * + * This module owns the scoped transport loop for already-encoded telemetry + * payloads: callers provide the OTLP endpoint, request headers, a body encoder, + * and batching settings, then push records that should be delivered to a + * collector. It is useful when implementing a concrete signal exporter, such + * as the OTLP logger or tracer, or when wiring a snapshot-based exporter like + * metrics into the same lifecycle and retry behavior. + * + * The exporter sends HTTP POST requests with the provided `HttpClient`, disables + * tracer propagation for its own traffic, retries transient failures, and + * honors numeric `retry-after` values on 429 responses. Exports run on + * `exportInterval`, flush during scope finalization up to `shutdownTimeout`, + * and can also be triggered early when the buffered item count reaches + * `maxBatchSize`. + * + * Use `maxBatchSize: "disabled"` only for pull-style exporters whose `body` + * callback builds a fresh payload without relying on drained buffered items, + * because pushed data is not cleared in that mode. After an unrecovered export + * failure the exporter drops the buffered batch and disables exporting for 60 + * seconds, so choose intervals, batch sizes, headers, and shutdown timeouts with + * collector limits and process shutdown behavior in mind. + * * @since 4.0.0 */ export * as OtlpExporter from "./OtlpExporter.ts" /** + * Exports Effect log records to an OpenTelemetry Protocol (OTLP) logs endpoint. + * + * Use this module to send Effect log messages, annotations, fiber identifiers, + * causes, and active span identifiers to an OpenTelemetry Collector or OTLP + * compatible observability backend. `make` is useful for custom logger wiring, + * while `layer` installs the logger for an application and can merge it with + * any existing loggers. + * + * Records are buffered by the shared OTLP exporter. `exportInterval`, + * `maxBatchSize`, and `shutdownTimeout` control periodic exports, early batch + * flushes, and how long scope finalization waits for the final flush. Resource + * options are attached to every export and override OpenTelemetry resource + * environment variables, so ensure a `service.name` is available through the + * options, `OTEL_RESOURCE_ATTRIBUTES`, or `OTEL_SERVICE_NAME`; use `headers` + * for collector authentication and `excludeLogSpans` when log span duration + * attributes would be too noisy. + * * @since 4.0.0 */ export * as OtlpLogger from "./OtlpLogger.ts" /** + * OTLP/HTTP metrics exporter for Effect's Metric system. + * + * This module periodically snapshots the metrics registered in the current + * Effect context, serializes them as OTLP resource metrics, and posts them to a + * metrics endpoint such as an OpenTelemetry Collector or vendor OTLP intake. + * It is typically installed with `layer` in long-running services that already + * update `Metric` counters, gauges, histograms, frequencies, or summaries and + * need those values exported without adding instrumentation-specific plumbing + * to the application code. + * + * Pass the concrete `/v1/metrics` endpoint to `make` or `layer`, or use the + * higher-level `Otlp` module when you want `baseUrl` path construction for all + * signals. The exporter requires an `HttpClient` and an `OtlpSerialization` + * implementation, takes resource metadata from explicit options or standard + * OTEL resource environment variables, and uses the resource `service.name` as + * the instrumentation scope name. Choose `temporality` for the target backend: + * cumulative is the default, while delta derives per-export changes from the + * previous snapshot. Gauges always report their current value, and delta + * histograms and summaries keep interval counts and sums from previous + * snapshots, so tune export intervals and shutdown timeouts with backend + * expectations and process shutdown behavior in mind. + * * @since 4.0.0 */ export * as OtlpMetrics from "./OtlpMetrics.ts" /** + * Helpers and data types for describing the OTLP resource attached to exported + * logs, metrics, and traces. + * + * A resource identifies the service that produced telemetry and carries + * process- or deployment-level attributes that should be shared across every + * signal sent by the Effect OTLP logger, metrics exporter, and tracer. Use this + * module when building explicit resource metadata, reading the standard OTEL + * resource environment variables, or converting application metadata into OTLP + * `KeyValue` / `AnyValue` shapes before serialization. + * + * `service.name` is required because the signal exporters also use it as the + * instrumentation scope name. Explicit resource options take precedence over + * `OTEL_RESOURCE_ATTRIBUTES`, `OTEL_SERVICE_NAME`, and + * `OTEL_SERVICE_VERSION`; `service.name` and `service.version` are normalized + * through the service metadata inputs and re-added as canonical OTLP + * attributes rather than left in the custom attribute map. Attribute values are + * converted to OTLP scalar or array values where possible, with unsupported + * runtime values formatted as strings. + * * @since 4.0.0 */ export * as OtlpResource from "./OtlpResource.ts" /** + * Defines the serialization boundary used by the OTLP observability layers. + * + * `OtlpSerialization` converts Effect's in-memory OTLP trace, metric, and log + * data into `HttpBody` values so exporters can send them to collectors over + * OTLP/HTTP. Use this module to choose between the JSON encoding that is useful + * for debugging and collector endpoints that explicitly accept OTLP/HTTP JSON, + * and the protobuf encoding commonly expected by production OpenTelemetry + * collectors. + * + * The JSON layer writes the telemetry structures directly with + * `HttpBody.jsonUnsafe`; the protobuf layer encodes the same structures with + * the internal OTLP protobuf encoder and sets the `application/x-protobuf` + * content type. Endpoint paths, authentication headers, batching, retries, and + * shutdown flushing are handled by the OTLP exporter layers that consume this + * service, while this module focuses only on preserving the wire format chosen + * for traces, metrics, and logs. + * * @since 4.0.0 */ export * as OtlpSerialization from "./OtlpSerialization.ts" /** + * Exports Effect spans to an OpenTelemetry Protocol (OTLP) traces endpoint. + * + * This module creates a `Tracer.Tracer` backed by the shared OTLP/HTTP batch + * exporter, so Effect spans created with tracing APIs can be delivered to an + * OpenTelemetry Collector, vendor OTLP intake, or local development collector. + * Use `make` when you need to install the tracer manually, or `layer` when an + * application should provide it through the Effect environment. + * + * Pass a concrete traces endpoint, typically `/v1/traces`, or use the + * higher-level `Otlp` module when you want `baseUrl` path construction for all + * observability signals. The tracer exports only ended sampled spans, converts + * span attributes, events, links, status, parent identifiers, and failure + * causes into OTLP trace data, and groups every batch under the configured + * resource. Resource options are resolved through `OtlpResource`, so ensure a + * stable `service.name` is available through options or standard OTEL resource + * environment variables because it is also used as the instrumentation scope + * name. Tune `exportInterval`, `maxBatchSize`, and `shutdownTimeout` for the + * target backend and process shutdown behavior, provide `headers` for + * authentication or routing, choose an `OtlpSerialization` layer accepted by + * the endpoint, and use `context` only when a backend needs custom evaluation + * around the active span. + * * @since 4.0.0 */ export * as OtlpTracer from "./OtlpTracer.ts" @@ -42,8 +182,24 @@ export * as OtlpTracer from "./OtlpTracer.ts" /** * Prometheus metrics exporter for Effect's Metric system. * - * This module provides functionality to export Effect metrics in the Prometheus - * exposition format, making them scrapeable by Prometheus servers. + * This module snapshots the metrics registered in the current Effect context + * and renders them in the Prometheus text exposition format. It is intended for + * services that already record `Metric` counters, gauges, histograms, + * frequencies, or summaries and need a pull-based `/metrics` endpoint, or for + * integrations that want the formatted scrape body for a custom HTTP server. + * + * Use `format` when you need the current runtime's registry rendered as a + * string, `formatUnsafe` when you already have the `Context`, and `layerHttp` + * when an `HttpRouter` should serve `GET /metrics` directly. Formatting happens + * at scrape time; the module does not push metrics, schedule exports, or start + * an HTTP server on its own. Make sure the route is installed in the same + * application context that records the metrics you want to expose. + * + * Metric and label names are sanitized for Prometheus, optional prefixes and + * name mappers are applied before output, and metric attributes become labels. + * Keep attributes low-cardinality, avoid relying on invalid characters being + * preserved exactly, and configure Prometheus to scrape the route served by + * `layerHttp` with the expected `text/plain; version=0.0.4` response. * * **Example** (Exporting Prometheus metrics) * diff --git a/packages/effect/src/unstable/persistence/index.ts b/packages/effect/src/unstable/persistence/index.ts index ee2f706141..be450fac59 100644 --- a/packages/effect/src/unstable/persistence/index.ts +++ b/packages/effect/src/unstable/persistence/index.ts @@ -5,36 +5,183 @@ // @barrel: Auto-generated exports. Do not edit manually. /** + * Effectful key/value storage for persistence backends. + * + * This module defines the `KeyValueStore` service used by the persistence + * package when a simple string or binary store is enough. It is useful for + * lightweight durable state, browser storage, local file-backed data, SQL + * tables, test stores, and as the storage primitive underneath higher-level + * persistence APIs. + * + * Values are stored as strings or `Uint8Array`s, and `toSchemaStore` adds a + * schema-aware JSON layer for typed values. Schema changes can make existing + * JSON fail to decode, and `prefix` should be used to separate namespaces when + * several logical stores share the same backend. This service does not provide + * native TTL support; higher-level persistence layers encode expiration + * metadata in stored values when they need TTLs. + * + * Backend behavior is intentionally small but not identical: Web Storage is + * string-only, `makeStringOnly` stores binary values as base64, filesystem + * keys become encoded file names, SQL stores value type metadata in a table, + * and the memory layer is process-local. Choose keys, prefixes, table names, + * and value formats with those backend constraints in mind. + * * @since 4.0.0 */ export * as KeyValueStore from "./KeyValueStore.ts" /** + * Defines the request-side contract used by the persistence layer. + * + * A `Persistable` request is a `PrimaryKey` value that carries the success and + * error schemas needed to encode and decode the stored `Exit` for that request. + * Persisted request resolvers and `PersistedCache` use this metadata to restore + * previous lookup results from a backing store before running the lookup again. + * + * Use `Class` for cacheable or durable requests whose results can safely be + * reused across fibers, processes, or restarts. The request primary key is the + * entry id inside a persistence store, so it should be stable, collision-free, + * and usually include a request-specific prefix. The `storeId` is configured on + * `Persistence` or `PersistedCache`; it selects the backing store namespace and + * is separate from the request primary key. + * + * Success and error schemas are encoded with the JSON codec, so persisted + * values must be representable by those schemas and any required schema services + * must be available where the store reads or writes entries. Changing a schema, + * primary-key format, or store id can make existing persisted values fail to + * decode or stop being found, so treat those changes as persistence migrations. + * * @since 4.0.0 */ export * as Persistable from "./Persistable.ts" /** + * Persistent caching for `Persistable` request keys. + * + * A `PersistedCache` combines a scoped in-memory `Cache` with a named + * `Persistence` store. It is useful for expensive or idempotent lookups such as + * remote API calls, database reads, and request results that should be reused + * across fibers, process restarts, or multiple workers sharing the same backing + * store. + * + * The persistent `timeToLive` is evaluated for the stored `Exit`, so successes + * and failures can be cached with different lifetimes. The in-memory cache has + * its own `inMemoryTTL` and capacity, and `invalidate` removes both the + * persisted value and the in-memory entry. Persisted values are encoded with + * the key's success and error schemas and stored under the key's primary key, so + * schema changes, primary-key changes, or store-id collisions can make old + * entries fail to decode until they are invalidated or written under a new + * `storeId`. + * * @since 4.0.0 */ export * as PersistedCache from "./PersistedCache.ts" /** + * Schema-aware persisted queues for background work. + * + * A `PersistedQueue` stores JSON-encoded values in a named queue and lets + * workers `take` one value at a time inside a scoped processing window. It is + * useful for durable handoffs, background jobs, outbox-style integrations, and + * workloads where failed work should be retried across fibers, process + * restarts, or multiple workers sharing Redis or SQL. + * + * Delivery is at-least-once: a handler that fails, is interrupted, or loses its + * backing-store lock may see the same element again until `maxAttempts` is + * reached. Use stable custom ids when offering idempotent work, and choose ids + * that are collision-free for the backing store because stores can enforce + * uniqueness at the queue, prefix, or table level. Ordering is intentionally a + * store-level concern; retries, lock expiration, polling, and multiple workers + * can move entries behind newer work, so handlers should not rely on strict + * FIFO processing. + * + * Values are encoded and decoded with the supplied schema using the JSON codec, + * so schema services must be available when offering and taking values. Changing + * a queue name, schema, Redis prefix, SQL table, or id format is a persistence + * migration: old entries may decode differently, stop being visible, or collide + * with new entries. The memory store is process-local and volatile, while Redis + * and SQL stores use leases that should be tuned for the expected processing + * time. + * * @since 4.0.0 */ export * as PersistedQueue from "./PersistedQueue.ts" /** + * Durable storage for encoded `Persistable` request results. + * + * The `Persistence` service creates scoped stores that read and write + * schema-encoded `Exit` values keyed by each request's `PrimaryKey`. It is the + * lower-level persistence layer used by `PersistedCache` and similar request + * workflows to reuse expensive or idempotent lookup results across fibers, + * process restarts, and workers that share a backing store. + * + * Each store is selected by a `storeId`, while each entry id comes from the + * request's primary key. Keep both stable and collision-free: changing the + * `storeId`, the primary-key format, or the success/error schemas is a + * persistence migration, because old entries may stop being found or fail to + * decode. Values are encoded with the request's success and error schemas using + * the JSON codec, so any required schema services must be available at store + * read and write time, and the backing value must stay JSON-compatible. + * + * TTLs are computed from the stored `Exit` and request key. Infinite TTLs are + * stored without an expiration, finite TTLs become backing-store expirations, + * and zero or negative TTLs skip the write entirely. Backing layers provide + * process-local memory, `KeyValueStore`, Redis, and SQL implementations; store + * ids are used as prefixes, table names, or SQL partitions and should be + * chosen with the target backing store in mind. + * * @since 4.0.0 */ export * as Persistence from "./Persistence.ts" /** + * Persistent rate limiting for effects that need to coordinate token + * consumption through a shared `RateLimiterStore`. + * + * The module exposes a `RateLimiter` service that can consume tokens for + * string keys using either fixed-window counters or token-bucket state. It is + * useful for protecting external APIs, enforcing per-user or per-tenant quotas, + * throttling job workers, and coordinating limits across multiple fibers or + * processes when they share the Redis-backed store. The helpers can fail fast + * with `RateLimiterError`, return a delay to apply yourself, or wrap an effect + * so it waits before continuing. + * + * Rate-limit keys and Redis prefixes are part of the persistence namespace, so + * choose stable, collision-free values. The in-memory store is process-local + * and is only coordinated inside one runtime, while the Redis store uses Lua + * scripts for atomic updates under concurrent consumers. Time is measured with + * the Effect `Clock`, windows are clamped to at least one millisecond, and + * refill calculations use millisecond granularity. + * + * Fixed-window state is TTL-driven: rejected `fail` attempts do not extend the + * current TTL, and Redis fixed-window keys expire automatically. Token-bucket + * state keeps the remaining token count and last-refill time instead of using a + * TTL, so high-cardinality dynamic keys may need an external cleanup or bounded + * key strategy. With `onExceeded: "delay"`, overflow can be recorded so callers + * should actually sleep for the returned delay, or use the provided accessors. + * * @since 4.0.0 */ export * as RateLimiter from "./RateLimiter.ts" /** + * Low-level Redis integration for the persistence modules. + * + * This module defines the `Redis` service used by Redis-backed persistence, + * persisted queues, and rate limiter stores. It adapts an external Redis + * connection to Effect through `send` for raw commands and `eval` for typed + * Lua scripts that are loaded with `SCRIPT LOAD` and executed with `EVALSHA`. + * + * The service does not create or manage Redis connections; callers provide a + * command sender from their Redis client or pool. Higher-level stores layer on + * key prefixes and store ids, so choose stable prefixes to avoid collisions + * and remember that schema or primary-key changes can make previously persisted + * JSON values fail to decode. Finite TTLs in the persistence stores are applied + * with millisecond Redis expirations, while non-finite TTLs are stored without + * expiration. Script parameters are stringified before execution, and the + * script descriptor's key count controls how Redis splits `KEYS` from `ARGV`. + * * @since 4.0.0 */ export * as Redis from "./Redis.ts" diff --git a/packages/effect/src/unstable/reactivity/index.ts b/packages/effect/src/unstable/reactivity/index.ts index e713ece56e..151298e7a6 100644 --- a/packages/effect/src/unstable/reactivity/index.ts +++ b/packages/effect/src/unstable/reactivity/index.ts @@ -5,41 +5,208 @@ // @barrel: Auto-generated exports. Do not edit manually. /** + * The `AsyncResult` module models the state of an asynchronous value inside the + * reactivity APIs. It represents whether a computation has not produced a + * result yet, has succeeded, or has failed, while keeping `waiting` as a + * separate flag for first loads, refreshes, retries, and other in-flight work. + * + * This is useful for atoms and UI integrations that need to render async + * queries, background refreshes, optimistic transitions, stream pulls, or RPC + * and HTTP calls without losing track of the current value. `Success` contains + * the latest value and timestamp, and `Failure` contains a `Cause` plus an + * optional `previousSuccess` so callers can keep showing stale data after a + * refresh fails. + * + * Treat `waiting` as an overlay rather than a fourth state: an `Initial` result + * can be waiting with no value, and a `Success` or `Failure` can also be waiting + * while a newer computation is running. Accessors such as `value` and + * `getOrElse` may return the previous success stored in a failure, so inspect + * `cause` or `error` when the difference between a current success and stale + * data matters. Matchers such as `matchWithWaiting` prioritize the waiting flag, + * while `match` and `matchWithError` expose the underlying state. + * * @since 4.0.0 */ export * as AsyncResult from "./AsyncResult.ts" /** + * The `Atom` module defines reactive values and the helpers for constructing, + * composing, running, and persisting them with an `AtomRegistry`. Atoms are + * small read functions whose regular `get` reads form a dependency graph, so + * derived values are cached by the registry and invalidated when their + * dependencies, writable state, refresh hooks, or subscriptions change. + * + * Use atoms for application and UI state, derived data, `Effect` or `Stream` + * queries exposed as `AsyncResult`, writable function atoms for commands, + * subscription refs, pull-based streams, optimistic updates, URL search + * parameters, `KeyValueStore` entries, and serializable or server-specific + * hydration. + * + * The cache belongs to the registry, not the atom object: the same atom can have + * different values in different registries, and serializable atoms are keyed by + * their serialization key. Stable atom identity matters for dependency tracking + * and cache reuse, so use `family` for parameterized atoms. Unobserved atoms are + * disposed unless kept alive or retained by an idle TTL, which can cause derived + * state, effects, streams, and finalizers to be rebuilt later. Runtime-backed + * atoms run effects and streams with the registry scheduler, scope, and + * `AtomRuntime` layer context; `runtime.withReactivity` only refreshes after + * explicit `Reactivity` invalidations, while one-shot reads such as `once` do + * not create dependency edges. + * * @since 4.0.0 */ export * as Atom from "./Atom.ts" /** + * The `AtomHttpApi` module adapts typed `HttpApi` clients to the unstable atom + * reactivity runtime. Use it to define a `Context.Service` whose generated HTTP + * API client is available directly and whose endpoints can also be invoked as + * atoms: `query` creates an atom of `AsyncResult` for reads, while `mutation` + * creates an `AtomResultFn` for writes. + * + * It is intended for applications that want server state to participate in atom + * caching, invalidation, and hydration. Queries can be associated with + * `reactivityKeys` so they refresh when those keys are invalidated, mutations can + * invalidate the same keys after the request succeeds, and `timeToLive` controls + * whether idle query atoms expire, stay alive for a duration, or are kept alive. + * + * Serialization is schema-based and intentionally limited to decoded values. + * Mutation atoms are serializable only in `"decoded-only"` mode, while query + * atoms are serializable only in `"decoded-only"` mode when a stable + * `serializationKey` is supplied. Choose serialization keys that uniquely + * identify the endpoint request, keep reactivity keys stable across client and + * server registries during hydration, and avoid serializing response modes that + * expose raw `HttpClientResponse` values. + * + * The service wraps `HttpApiClient.make`, so the same `HttpApi` definition, + * schemas, base URL, middleware services, and HTTP client layer must be available + * wherever the atom runtime is constructed. Use `transformClient` and + * `transformResponse` for cross-cutting client behavior, and remember that + * schema or low-level HTTP client failures are raised as defects while endpoint + * and middleware failures remain typed errors. + * * @since 4.0.0 */ export * as AtomHttpApi from "./AtomHttpApi.ts" /** + * Mutable reactive references for local, in-memory state that should be read, + * updated, and observed without going through an `AtomRegistry`. + * + * `AtomRef` is useful for small state models, form-like state, and collections + * of item references where callers need direct mutation methods together with + * subscriptions. A ref exposes its current `value`, notifies subscribers after + * `set` or `update`, can derive read-only views with `map`, and can focus on + * nested object or array properties with `prop`. + * + * Notifications are equality-aware: setting a value that is `Equal.equals` to + * the current value is ignored, and mapped or property subscriptions only emit + * when their derived value changes. Mutate state through `set`, `update`, or a + * property ref so subscribers are notified; direct mutation of the stored value + * does not notify listeners. Collection subscribers are notified when items are + * inserted, removed, or when an item ref changes, while `toArray` returns the + * current raw item values. + * * @since 4.0.0 */ export * as AtomRef from "./AtomRef.ts" /** + * The `AtomRegistry` module provides the runtime cache used by reactivity + * atoms. A registry owns the node graph for a group of atoms, stores their + * current values, records parent/child dependencies while atoms are read, and + * coordinates writes, refreshes, stream conversions, and node disposal. + * + * Create a registry directly with {@link make} or provide it with {@link layer} + * or {@link layerOptions} when a UI root, request, test, or other Effect scope + * needs its own atom state. The same atom can have different cached values in + * different registries, while serializable atoms are keyed by their + * serialization key so preloaded values can hydrate a node before its first + * read. + * + * Subscriptions and {@link mount} keep nodes alive and must be released when + * the consumer is done; scoped helpers install finalizers for this. Unobserved + * non-`keepAlive` atoms may be removed immediately or after their `idleTTL` (or + * the registry `defaultIdleTTL`), which means later reads can rebuild derived + * state. Disposing a registry clears its cache and makes future atom access an + * error. + * * @since 4.0.0 */ export * as AtomRegistry from "./AtomRegistry.ts" /** + * The `AtomRpc` module connects typed RPC clients to the atom reactivity + * runtime. It builds a `Context.Service` that exposes the flattened + * `RpcClient`, an `AtomRuntime`, mutation helpers, and query helpers for every + * RPC in an `RpcGroup`. + * + * Use it when remote read models should be represented as atoms, mutations + * should refresh affected reads through `Reactivity` keys, or non-streaming + * query results need serialization metadata for hydration. The RPC `protocol` + * layer supplies the transport, and may be static or derived from the current + * atom context, so request headers, transport dependencies, and client + * middleware remain part of the normal Effect environment. + * + * Non-streaming queries produce atoms of `AsyncResult` values. Supplying a + * `serializationKey` marks those query atoms as serializable using codecs + * derived from the RPC success schema and the combined RPC, middleware, and + * client error schemas; choose stable, unique keys when dehydrating. Streaming + * RPCs produce writable pull atoms instead, so callers advance the stream by + * writing to the atom and should not expect serialization metadata. Query family + * caching includes the payload, normalized headers, reactivity keys, TTL, and + * serialization key, so use stable values for those inputs when atom identity + * matters. + * * @since 4.0.0 */ export * as AtomRpc from "./AtomRpc.ts" /** + * Utilities for moving serializable reactivity state between atom registries. + * + * `dehydrate` snapshots atoms marked with `Atom.serializable` from an + * `AtomRegistry`, preserving their serialization keys, encoded values, and + * dehydration time so another registry can preload the same state with `hydrate`. + * This is useful for server rendering, browser bootstrapping, route transitions, + * and other handoffs where a registry should start from values that were already + * computed elsewhere. + * + * Only serializable atoms are included, and the receiving registry needs atoms + * with matching stable keys and compatible schemas. Values crossing a + * client/server boundary should be the encoded JSON-safe values produced by the + * atom codecs. The optional `resultPromise` used for `AsyncResult.Initial` + * handoffs is a live JavaScript promise, so it cannot be sent through JSON and + * should be omitted or replaced by an application-level streaming protocol when + * dehydrated state leaves the current runtime. + * * @since 4.0.0 */ export * as Hydration from "./Hydration.ts" /** + * The `Reactivity` module provides an in-memory service for connecting writes to + * dependent reads through explicit invalidation keys. It is useful for keeping + * query results, UI subscriptions, read models, or other derived views fresh + * after mutations without coupling the writer to every consumer that should + * rerun. + * + * Reads are modeled with {@link query} and {@link stream}: the effect runs once + * immediately and then runs again whenever one of its keys is invalidated. + * Writes can use {@link mutation} to invalidate keys only after the wrapped + * effect succeeds, or call {@link invalidate} directly. Keys may be supplied as + * a flat collection or as a record of namespaces with ids, which lets callers + * invalidate both broad groups and individual records. + * + * The service tracks handlers by hashed keys and does not cache values by + * itself; consumers receive fresh queue or stream emissions and decide how to + * store them. Registrations are tied to the surrounding scope, failures from a + * query fail the queue or stream, and invalidations that arrive while a query is + * already running schedule a single follow-up run. Use stable key values, be + * aware that the default layer is process-local, and wrap related work in + * {@link Reactivity.withBatch} when many invalidations should be coalesced until + * the batch exits. + * * @since 4.0.0 */ export * as Reactivity from "./Reactivity.ts" diff --git a/packages/effect/src/unstable/rpc/index.ts b/packages/effect/src/unstable/rpc/index.ts index 35a9427e62..0b7ebe3c29 100644 --- a/packages/effect/src/unstable/rpc/index.ts +++ b/packages/effect/src/unstable/rpc/index.ts @@ -5,61 +5,333 @@ // @barrel: Auto-generated exports. Do not edit manually. /** + * The `Rpc` module defines the typed declaration for a single remote + * procedure. An RPC definition is the shared contract used by `RpcGroup`, + * clients, and servers: it stores the procedure tag, payload schema, success + * schema, error schema, defect schema, middleware, annotations, and the type + * information needed to derive client calls and server handler signatures. + * + * Use this module to declare request/response procedures with {@link make}, + * build custom constructors that transform success and error schemas with + * {@link custom}, add middleware or annotations to individual procedures, and + * derive helper types such as {@link Payload}, {@link Success}, + * {@link Error}, and {@link ToHandlerFn}. Server implementations can also use + * {@link fork} and {@link uninterruptible} wrappers to control how handler + * results are executed. + * + * **Schema gotchas** + * + * - Payloads default to `Schema.Void`; passing struct fields creates a + * `Schema.Struct`, while `primaryKey` creates a payload class with a derived + * `PrimaryKey` + * - Success values default to `Schema.Void`, ordinary errors default to + * `Schema.Never`, and middleware errors are included in the effective RPC + * error channel + * - Streaming RPCs store the element and stream error schemas in + * `RpcSchema.Stream`; the immediate exit success is `void` and the normal + * RPC error schema is set to `Schema.Never` + * - Defects use a separate defect schema, defaulting to `Schema.Defect`; custom + * defect schemas must not require decoding or encoding services + * - Schema services are directional: clients encode payloads and decode + * responses, while servers decode payloads and encode responses + * * @since 4.0.0 */ export * as Rpc from "./Rpc.ts" /** + * Client-side support for calling RPCs defined in an `RpcGroup`. + * + * This module derives typed client APIs from RPC definitions, turns method + * calls into request messages, and routes server responses back to the waiting + * `Effect` or `Stream`. Use it to construct schema-aware clients over the + * provided HTTP, socket, and worker transports, or use `makeNoSerialization` + * when an already-decoded message channel should participate in the same + * request, interruption, acknowledgement, and streaming lifecycle. + * + * The `make` constructor requires a `Protocol`, which owns the encoded + * transport. HTTP sends one request per call and does not support client + * acknowledgements, while socket and worker protocols keep receive loops alive, + * support streaming acknowledgements, and can fail in-flight requests with + * protocol errors. Streaming RPCs return `Stream`s by default, or scoped + * queues when `asQueue` is enabled, so `streamBufferSize` controls the client + * side of streaming back pressure. + * + * Payloads, exits, and stream chunks are encoded and decoded through the RPC + * schemas with the active `RpcSerialization`; any schema services required by + * those codecs remain part of the generated client method environments. + * Client middleware declared on an RPC is looked up from + * `Rpc.MiddlewareClient`, can rewrite or short-circuit outgoing requests, and + * contributes its client error type to the call signature. Outgoing request + * headers combine `CurrentHeaders` with per-call headers before the request is + * passed through middleware and then to the transport. + * * @since 4.0.0 */ export * as RpcClient from "./RpcClient.ts" /** + * Shared error types for the RPC client protocol layer. + * + * This module defines the client-side failures added to schema-aware RPC + * clients. `RpcClientError` wraps transport failures from the built-in HTTP, + * socket, and worker protocols, while `RpcClientDefect` records protocol + * problems such as empty HTTP responses, malformed response batches, failed + * transport decoding, or unexpected connection failures. + * + * These errors are separate from a remote handler's typed error. Remote + * failures that match an RPC's error schema are decoded from the RPC exit and + * remain part of the procedure's domain error channel. Server defects and + * schema mismatches are not normal remote errors: they surface as defects or + * protocol failures, so handlers commonly inspect `RpcClientError.reason` to + * decide whether a failure is retryable transport trouble or an incompatible + * client/server schema or serialization boundary. + * * @since 4.0.0 */ export * as RpcClientError from "./RpcClientError.ts" /** + * Groups typed `Rpc` definitions into a protocol that can be shared by + * clients, servers, tests, cluster entities, workflows, and other RPC + * integrations. + * + * An `RpcGroup` keeps RPC definitions keyed by their tags while preserving the + * payload, success, error, defect, middleware, and annotation metadata carried + * by each `Rpc`. Build groups with `make`, extend them with `add`, combine them + * with `merge`, remove calls with `omit`, and turn the final protocol into + * handler contexts or layers with `toHandlers`, `toLayer`, or `toLayerHandler`. + * + * Common uses include defining a service surface once and deriving both client + * and server implementations from it, splitting a large protocol into feature + * groups that are merged later, prefixing generated or proxied RPC names, and + * attaching metadata for higher-level runtimes. Composition order matters: + * `middleware` and `annotateRpcs` update only the RPCs currently in the group, + * duplicate tags from `add` or `merge` replace the existing definition, and + * handlers are keyed by the tags after any prefixing. Schema requirements still + * come from each RPC's payload, success, error, defect, and middleware schemas; + * grouping preserves those requirements but does not provide the services + * needed to encode, decode, or handle them. + * * @since 4.0.0 */ export * as RpcGroup from "./RpcGroup.ts" /** + * Defines the protocol message envelopes shared by unstable RPC clients, + * servers, and transports. + * + * This module is used when implementing or testing RPC transports, codecs, and + * protocol handlers. It separates decoded messages, which carry typed RPC tags, + * payloads, headers, exits, and branded request identifiers, from encoded + * messages, which are suitable for transport boundaries where request ids and + * payloads have already been serialized. + * + * Request identifiers are the correlation point for requests, response chunks, + * terminal exits, acknowledgements, and interrupts, so transports must preserve + * them across the encoded string form and the decoded branded form. Streaming + * responses can send one or more `Chunk` batches before a terminal `Exit`; use + * `Ack` messages only for transports that require backpressure, treat `Eof` as + * the end of client input, and reserve `Ping`/`Pong` for connection liveness + * rather than RPC completion. + * * @since 4.0.0 */ export * as RpcMessage from "./RpcMessage.ts" /** + * The `RpcMiddleware` module defines middleware services that can wrap RPC + * handler execution on the server and request execution in generated clients. + * + * Use middleware to attach cross-cutting behavior to individual RPCs or whole + * `RpcGroup`s, such as authentication, authorization, request logging, tracing, + * metrics, rate limiting, header propagation, or adding request-scoped services + * to the handler context. A middleware service records the services it requires + * and provides, the schema for errors it can fail with, and whether clients must + * install a matching middleware via `layerClient`. + * + * Server middleware receives the target `rpc`, decoded `payload`, request + * `headers`, `requestId`, and `Rpc.ServerClient`, then wraps the handler effect. + * Its `provides` type removes services from the downstream handler requirement, + * while `requires` adds the services needed by the middleware implementation. + * Middleware errors must be declared with a `Schema` so they can be encoded as + * RPC failures, and any schema encoding or decoding services remain part of the + * generated RPC environments. + * + * Client middleware is installed with `layerClient`, captures the surrounding + * layer context, and can inspect, rewrite, retry, or short-circuit outgoing + * requests before calling `next`. Set `requiredForClient` when an RPC's typed + * client must require that `ForClient` layer; otherwise a client implementation + * is used only when one is present. `clientError` contributes only to the + * client-side call error channel, while the middleware `error` schema is shared + * with server failures. + * * @since 4.0.0 */ export * as RpcMiddleware from "./RpcMiddleware.ts" /** + * The `RpcSchema` module contains the RPC-specific schema markers and cause + * annotations shared by the RPC declaration, client, and server layers. It is + * used when an RPC response is a `Stream`, and when server-side interruption + * logic needs to identify a client-initiated abort. + * + * Use {@link Stream} to mark an RPC success schema as a streamed response, + * {@link isStreamSchema} to detect that marker, and the stored success and + * error schemas to encode or decode stream chunks. Request payload schemas live + * on the `Rpc` definition itself; this module only describes the streamed + * response shape. For streaming RPCs, the success schema passed to + * `RpcSchema.Stream` is the stream element schema, while the error schema is + * the stream error schema. When the marker is installed by the `Rpc` + * constructor's `stream` option, the immediate RPC exit succeeds with `void`, + * the ordinary RPC error schema is set to `Schema.Never`, and the stream error + * schema is used for stream failures. + * + * Streaming schemas are not general-purpose codecs for arbitrary stream values: + * they are RPC metadata that lets the protocol distinguish one-shot successes + * from streamed elements and keep stream errors on the chunk stream. Use + * {@link ClientAbort} when annotating interruptions caused by a remote client + * closing or cancelling a request. + * * @since 4.0.0 */ export * as RpcSchema from "./RpcSchema.ts" /** + * Serialization support for the unstable RPC protocol. + * + * This module provides the `RpcSerialization` service used by RPC clients and + * servers to encode and decode transport-level `RpcMessage` envelopes. Use the + * built-in JSON, newline-delimited JSON, JSON-RPC 2.0, and MessagePack + * implementations when wiring HTTP, sockets, workers, or custom transports, or + * provide a custom service when a transport needs a different content type, + * frame format, or binary codec. + * + * Serialization runs after RPC schemas have encoded payloads, successes, + * failures, and stream chunks into transport-safe values, and before schemas + * decode those values on the other side. Choose a format that can represent the + * schema-encoded data: JSON is easy to inspect but needs schema encodings for + * arbitrary binary values, while MessagePack is more compact and carries binary + * data more naturally. + * + * Transport framing is significant. `json` and `jsonRpc` expect a complete + * payload for each decode call and are intended for transports such as HTTP + * that already delimit message bodies. `ndjson`, `ndJsonRpc`, and `msgPack` + * maintain parser state for chunked streams, so they can decode multiple + * messages or incomplete fragments from sockets and other streaming transports. + * Match the serialization layer to the transport boundary, otherwise messages + * may be buffered, split, or parsed at the wrong frame. + * * @since 4.0.0 */ export * as RpcSerialization from "./RpcSerialization.ts" /** + * Server-side support for running RPCs defined in an `RpcGroup`. + * + * This module connects typed RPC handlers to an encoded or already-decoded + * transport, decodes client requests, invokes the matching handler, and sends + * exits, stream chunks, defects, interrupts, and client-end notifications back + * through the active server protocol. Use it to expose an RPC group through + * `layerHttp`, standalone HTTP effects, websocket or socket servers, stdio, + * worker runners, or a custom `Protocol`; use `makeNoSerialization` when the + * surrounding system already owns message serialization. + * + * The `Protocol` service is the transport boundary. It declares how encoded + * client messages are received and how encoded responses are written, plus + * whether the transport supports stream acknowledgements, transferable + * objects, and span propagation. HTTP request/response serving is useful for + * simple calls and response streaming, but it does not provide client + * acknowledgements or span propagation; websocket, socket, stdio, and worker + * protocols keep a live channel and can participate in the streaming + * acknowledgement lifecycle. + * + * **Handler gotchas** + * + * - Server handlers are looked up from `Rpc.ToHandler`, while RPC middleware + * is looked up from `Rpc.Middleware` and wraps the handler with metadata + * containing the `Rpc.ServerClient`, request id, headers, and decoded payload + * - Payloads are decoded on the server and exits, stream chunks, and request + * defects are encoded on the server using the RPC schemas and the handler's + * schema services; encode failures are reported as request defects and the + * in-flight request is interrupted + * - Streaming RPCs send chunks before the final exit, and transports with + * acknowledgement support wait for client acknowledgements between chunks to + * provide back pressure + * - Fatal handler defects are sent as protocol defects by default; set + * `disableFatalDefects` when defects should remain ordinary request exits + * * @since 4.0.0 */ export * as RpcServer from "./RpcServer.ts" /** + * Utilities for testing RPC groups without opening a network transport. + * + * This module connects a generated RPC client directly to an in-memory + * `RpcServer` for the same group, using the group's handlers from the Effect + * environment and the no-serialization message path. It is intended for tests + * that need to exercise client calls, server handlers, middleware, request + * routing, and streaming behavior without standing up HTTP, sockets, workers, + * or a serializer. + * + * Because messages stay decoded in memory, this module is not a substitute for + * transport or schema-encoding tests. Callers still need to provide the handler + * layer, any client/server middleware services, and a `Scope`; the returned + * client is scoped to that in-memory connection. The `flatten` option follows + * `RpcClient.makeNoSerialization`, and acknowledgements are enabled to match + * the normal bidirectional client/server protocol used by the test harness. + * * @since 4.0.0 */ export * as RpcTest from "./RpcTest.ts" /** + * Helpers for passing a schema-encoded bootstrap message to worker-backed RPC + * protocols. + * + * Worker RPC protocols can send one initial message when each worker starts, + * before ordinary RPC requests begin flowing. Use this module to build and + * provide that message from the client side, and to decode it inside the + * worker-side server. Common payloads include per-worker configuration, + * credentials or session metadata, feature flags, preloaded data, or + * transferable resources such as `ArrayBuffer` and `MessagePort` values. + * + * The initial message uses the supplied schema's JSON codec and is posted as a + * worker message, so it is separate from the normal `RpcSerialization` used for + * RPC request and response traffic. Values still need to be valid for the + * worker transport's structured clone boundary. Transferable annotations can + * collect objects for the `postMessage` transfer list, but transferring moves + * ownership to the worker and may detach buffers from the sender. + * * @since 4.0.0 */ export * as RpcWorker from "./RpcWorker.ts" /** + * Internal helpers for constructing RPC protocol services whose receive loop is + * installed separately from the operations that write to it. + * + * This module is used by the client and server `Protocol.make` constructors to + * let transports expose a stable service immediately while buffering messages + * until the protocol's `run` method has installed the active receiver. Buffered + * writes keep the `Context` that was current at the time of the write, so + * replaying early messages preserves fiber-local services such as tracing or + * request metadata. + * + * The general `withRun` helper is for single receive-loop services, such as + * server transports, while `withRunClient` specializes the same pattern for + * client transports by tracking active client ids and keeping a separate buffer + * per client. They are most useful when implementing custom RPC transports or + * test protocols that need to send before the consumer fiber has started. + * + * These helpers intentionally work on `Omit` and re-add `run` + * so generated `Context.Service` static `make` members can preserve their + * exact service shape. When using them from generated or type-helper-heavy + * protocol code, keep the `run` signature aligned with the target service: + * the server helper has one shared writer, but the client helper requires a + * `clientId` because responses and buffered messages are routed per client. + * * @since 4.0.0 */ export * as Utils from "./Utils.ts" diff --git a/packages/effect/src/unstable/schema/Model.ts b/packages/effect/src/unstable/schema/Model.ts index 1dc0706f46..9a6382a738 100644 --- a/packages/effect/src/unstable/schema/Model.ts +++ b/packages/effect/src/unstable/schema/Model.ts @@ -685,7 +685,7 @@ export const JsonFromString = ( * @category uuid * @since 4.0.0 */ -export interface UuidV4Insert extends +export interface UuidV4BytesInsert extends VariantSchema.Field<{ readonly select: Schema.brand>, B> readonly insert: Schema.withConstructorDefault>, B>> @@ -711,7 +711,7 @@ export const Uint8Array: Schema.instanceOf> = Schema.Uin * @category uuid * @since 4.0.0 */ -export const UuidV4WithGenerate = ( +export const UuidV4BytesWithGenerate = ( schema: Schema.brand>, B> ): Schema.withConstructorDefault>, B>> => schema.pipe(Schema.withConstructorDefault(Effect.sync(() => Uuid.v4({}, new globalThis.Uint8Array(16))))) @@ -722,8 +722,51 @@ export const UuidV4WithGenerate = ( * @category uuid * @since 4.0.0 */ -export const UuidV4Insert = ( +export const UuidV4BytesInsert = ( schema: Schema.brand>, B> +): UuidV4BytesInsert => + Field({ + select: schema, + insert: UuidV4BytesWithGenerate(schema), + update: schema, + json: schema + }) + +/** + * Variant field type for a branded string UUID v4 value whose insert variant + * generates a UUID by default. + * + * @category uuid + * @since 4.0.0 + */ +export interface UuidV4Insert extends + VariantSchema.Field<{ + readonly select: Schema.brand + readonly insert: Schema.withConstructorDefault> + readonly update: Schema.brand + readonly json: Schema.brand + }> +{} + +/** + * Adds a constructor default that generates a string UUID v4. + * + * @category uuid + * @since 4.0.0 + */ +export const UuidV4WithGenerate = ( + schema: Schema.brand +): Schema.withConstructorDefault> => + schema.pipe(Schema.withConstructorDefault(Effect.sync(() => Uuid.v4()))) + +/** + * A field that represents a string UUID v4 that is generated on inserts. + * + * @category uuid + * @since 4.0.0 + */ +export const UuidV4Insert = ( + schema: Schema.brand ): UuidV4Insert => Field({ select: schema, @@ -731,3 +774,50 @@ export const UuidV4Insert = ( update: schema, json: schema }) + +/** + * Variant field type for a branded string UUID v7 value whose insert variant + * generates a UUID by default. + * + * @category uuid + * @since 4.0.0 + */ +export interface UuidV7Insert extends + VariantSchema.Field<{ + readonly select: Schema.brand + readonly insert: Schema.withConstructorDefault> + readonly update: Schema.brand + readonly json: Schema.brand + }> +{} + +/** + * Adds a constructor default that generates a string UUID v7. + * + * @category uuid + * @since 4.0.0 + */ +export const UuidV7WithGenerate = ( + schema: Schema.brand +): Schema.withConstructorDefault> => + schema.pipe(Schema.withConstructorDefault(Effect.clockWith((clock) => + Effect.succeed(Uuid.v7({ + msecs: clock.currentTimeMillisUnsafe() + })) + ))) + +/** + * A field that represents a string UUID v7 that is generated on inserts. + * + * @category uuid + * @since 4.0.0 + */ +export const UuidV7Insert = ( + schema: Schema.brand +): UuidV7Insert => + Field({ + select: schema, + insert: UuidV7WithGenerate(schema), + update: schema, + json: schema + }) diff --git a/packages/effect/src/unstable/schema/index.ts b/packages/effect/src/unstable/schema/index.ts index af8486da16..e750f8196f 100644 --- a/packages/effect/src/unstable/schema/index.ts +++ b/packages/effect/src/unstable/schema/index.ts @@ -5,11 +5,48 @@ // @barrel: Auto-generated exports. Do not edit manually. /** + * Utilities for defining schema-backed domain models that need different shapes + * for database access and JSON APIs. + * + * A model defined with this module keeps one field declaration as the source of + * truth and derives the `select`, `insert`, `update`, `json`, `jsonCreate`, and + * `jsonUpdate` variants from it. This is useful for persistence models whose + * database representation differs from the public API, for example generated + * columns, application-generated identifiers, sensitive fields that must not be + * serialized to JSON, nullable database columns exposed as `Option`, SQLite + * booleans, JSON stored as text, date-time audit columns, and generated UUIDs. + * + * Each variant is a schema in its own right, so choose the variant that matches + * the boundary you are validating or encoding. Plain schemas are included in all + * variants, while `Field` helpers opt a property into only the variants they + * declare. Overrideable defaults such as timestamp helpers can still be provided + * explicitly with `Override`, and JSON variants may differ from database variants + * in both optionality and encoded representation. + * * @since 4.0.0 */ export * as Model from "./Model.ts" /** + * Build families of related struct schemas from one field definition. + * + * `VariantSchema` is useful when the same domain object needs several schema + * views, such as database select / insert / update shapes, JSON read / write + * shapes, public versus private API views, or constructor schemas with + * generated defaults. {@link make} fixes a closed set of variant names and a + * default variant, then returns helpers for defining shared `Struct` values, + * per-variant `Field` values, schema classes, unions, and extracted + * `Schema.Struct` projections. + * + * A plain schema in a variant struct is present in every variant, a `Field` + * contributes a property only to the variants named in its config, and nested + * variant structs are extracted recursively. Variants are projections, not + * discriminated alternatives: this module does not add a tag field, so include + * an explicit literal tag when a decoded union needs runtime discrimination. + * Also remember that the default variant is the schema used by generated + * classes and ordinary variant unions; per-variant schemas are exposed + * separately on those generated values. + * * @since 4.0.0 */ export * as VariantSchema from "./VariantSchema.ts" diff --git a/packages/effect/src/unstable/socket/index.ts b/packages/effect/src/unstable/socket/index.ts index 3e71f59e28..05ec228196 100644 --- a/packages/effect/src/unstable/socket/index.ts +++ b/packages/effect/src/unstable/socket/index.ts @@ -5,11 +5,55 @@ // @barrel: Auto-generated exports. Do not edit manually. /** + * Effect-based socket abstractions for bidirectional connections that exchange + * text frames, binary frames, and close events. + * + * This module defines the `Socket` service, constructors for WebSocket-backed + * and transform-stream-backed sockets, typed socket errors, and adapters that + * expose a socket as a bidirectional `Channel`. It is intended for WebSocket + * clients, HTTP server upgrades, protocol clients and servers, and tests or + * adapters that need a scoped duplex transport inside Effect programs. + * + * Incoming data can be consumed as raw frames, binary bytes, or strings. + * `runRaw` preserves whether the transport delivered a string or `Uint8Array`, + * while `run` encodes string frames as UTF-8 bytes and `runString` decodes + * binary frames with `TextDecoder`. Use the raw or mapping APIs when preserving + * frame boundaries, binary payloads, or text encodings matters. + * + * Writers are scoped to an active run and are gated until the underlying + * connection is open; use `onOpen` when startup writes must wait for that + * point. Outgoing strings and bytes are sent as data frames, while `CloseEvent` + * values request a close. Close events are modeled as `SocketCloseError` by + * default, and `closeCodeIsError` controls which close codes should fail a run + * versus complete cleanly. + * * @since 4.0.0 */ export * as Socket from "./Socket.ts" /** + * Effect service model for servers that accept socket connections and hand each + * accepted connection to an Effect handler as a `Socket.Socket`. + * + * This module contains the shared, platform-independent contract for socket + * servers: a bound `address`, a long-running `run` accept loop, the TCP and + * Unix socket address models, and the server-level errors reported while + * opening or running a server. Concrete transports, such as Node TCP servers or + * WebSocket servers, provide this service through platform-specific layers. + * + * `SocketServer` is commonly used as the server transport for RPC protocols, + * cluster runners, developer tools, and tests that need an ephemeral TCP port or + * Unix-domain socket. A server address may differ from the requested listen + * options after binding, for example when listening on port `0`, so consumers + * should read the provided `address` from the service. + * + * The `run` effect represents the server accept loop and is expected to remain + * alive until interrupted or until the providing scope is closed. Protocol + * framing is intentionally outside this module: handlers receive a generic + * `Socket.Socket`, so callers are responsible for choosing byte, string, raw + * frame, or higher-level protocol adapters and for treating connection-level + * failures separately from `SocketServerError` values. + * * @since 4.0.0 */ export * as SocketServer from "./SocketServer.ts" diff --git a/packages/effect/src/unstable/sql/index.ts b/packages/effect/src/unstable/sql/index.ts index 9198f650f0..4f05b04ec7 100644 --- a/packages/effect/src/unstable/sql/index.ts +++ b/packages/effect/src/unstable/sql/index.ts @@ -5,47 +5,225 @@ // @barrel: Auto-generated exports. Do not edit manually. /** + * Effect SQL migration helpers for loading, ordering, and running schema + * changes against a `SqlClient`. + * + * This module provides a migrator constructor plus loaders for common migration + * layouts, including dynamic glob imports, Babel-style glob records, in-memory + * records, and filesystem directories. It is intended for applications and + * libraries that need to apply numbered SQL migrations on startup, in tests, or + * as part of deployment tooling while keeping migration effects inside the + * Effect environment. + * + * The migrator tracks applied migrations in a configurable table, defaults that + * table to `effect_sql_migrations`, rejects duplicate migration ids, and only + * runs migrations with an id greater than the latest recorded id. Pending + * migrations are recorded and executed inside a `SqlClient` transaction; on + * PostgreSQL the migrations table is explicitly locked, while other dialects + * rely on the table's primary key or unique constraint to detect concurrent + * runners. Migration effects should therefore be written to be transaction-aware, + * and callers should account for dialect-specific DDL transaction behavior and + * custom table names when coordinating schema dumps or external migration + * tooling. + * * @since 4.0.0 */ export * as Migrator from "./Migrator.ts" /** + * Defines the `SqlClient` service, the central runtime entry point for Effect's + * unstable SQL support. + * + * A `SqlClient` combines the tagged-template statement constructor with a + * scoped connection acquirer, dialect compiler, tracing attributes, optional row + * transforms, reactive query helpers, and transaction management. Applications + * typically consume it from `Context` to build parameterized queries, stream + * rows, run raw driver operations, reserve a connection for lower-level work, or + * wrap several query effects in `withTransaction`. + * + * Transactions are tracked through a per-client context service. Top-level + * transactions acquire the configured transaction connection and issue the + * dialect's begin/commit/rollback SQL; nested transactions reuse that + * connection and rely on dialect-provided savepoint SQL. A query only joins a + * transaction when it is run with the same client service, so avoid mixing + * clients or manually reserved connections when atomicity matters. Dialect + * integrations are also responsible for the compiler and transaction + * statements, which means placeholder syntax, identifier escaping, row + * transforms, savepoint support, and unprepared/raw statement behavior can + * differ by database. + * * @since 4.0.0 */ export * as SqlClient from "./SqlClient.ts" /** + * Defines the low-level SQL connection service and shared row/acquirer types + * used by Effect's unstable SQL driver integrations. + * + * A `Connection` is the driver-facing layer underneath `SqlClient`: it executes + * already-compiled SQL with positional parameters and exposes transformed row + * results, raw driver results, streams, value arrays, and unprepared statement + * execution. Most applications should work through `SqlClient`, while driver + * integrations and advanced code use this module to provide scoped connection + * acquisition, implement pooling, reserve a connection for a workflow, or adapt + * a dialect-specific client into Effect. + * + * Connections are resources and should be acquired through an `Acquirer` in a + * `Scope` so pool checkout, transaction pinning, and release semantics are + * preserved. Transaction coordination lives at the `SqlClient` layer, so mixing + * manually reserved connections with transactional client queries can bypass the + * expected atomic boundary. Raw, unprepared, streaming, parameter, and row + * transformation behavior ultimately comes from the driver and dialect; check + * each integration for differences in placeholders, prepared statement support, + * cursor lifetime, and result shapes. + * * @since 4.0.0 */ export * as SqlConnection from "./SqlConnection.ts" /** + * Structured SQL errors used by the unstable SQL APIs. + * + * This module defines the top-level `SqlError` wrapper, the concrete + * `SqlErrorReason` variants used by drivers and adapters, and helpers for + * recognizing and classifying database failures. It is useful when turning + * native driver errors into typed Effect failures, choosing retry policies from + * `isRetryable`, or distinguishing user-facing query problems such as syntax + * and constraint failures from infrastructure problems such as connection, + * lock, statement timeout, deadlock, and serialization failures. + * + * Query, connection, and migration code should preserve the original cause and + * operation metadata when constructing these errors. Retrying can be appropriate + * for transient connection and concurrency failures, but syntax, authorization, + * authentication, and constraint failures generally require changing the query, + * credentials, permissions, or migration data. When classifying SQLite errors, + * the helpers inspect `code` and `errno` values and extract unique constraint + * names when available. + * * @since 4.0.0 */ export * as SqlError from "./SqlError.ts" /** + * Builds SQL repositories and request resolvers from Effect schema models. + * + * Use this module when a `Model` describes rows in a concrete SQL table and + * you want the common insert, update, find-by-id, and delete operations without + * hand-writing the schema encoding, row decoding, and resolver batching each + * time. The helpers are intended for model-backed tables where the model field + * names line up with the encoded table columns and the chosen `idColumn` is + * present in both the model fields and update schema. + * + * Returned rows are decoded with the full model schema, while insert and update + * requests are encoded with the model's dedicated input schemas. Soft deletes + * are opt-in via `softDeleteColumn`: reads and updates only see rows where that + * column is `null`, and deletes set it to `CURRENT_TIMESTAMP` instead of + * removing the row. Dialects with `returning` support return changed rows + * directly; MySQL performs a follow-up `select`, so generated ids, defaults, + * and trigger-updated values must be observable from that query. + * * @category models * @since 4.0.0 */ export * as SqlModel from "./SqlModel.ts" /** + * Schema-aware `RequestResolver` helpers for SQL-backed data loading. + * + * This module bridges `Effect.request` with `SqlClient` by representing each + * lookup or mutation as a `SqlRequest` and batching concurrent requests into a + * single SQL operation. Request payloads are encoded with the request schema + * before `execute` is called, and rows returned by the query are decoded with + * the result schema before entries are completed. + * + * Use `ordered` when a query returns exactly one row per request in the same + * order, `findById` for `where id in (...)` lookups, `grouped` for one-to-many + * relationships, and `void` for inserts, updates, deletes, or other + * side-effecting statements where no row is needed. + * + * **Gotchas** + * + * - `ordered` requires the result count and order to match the request batch. + * - `grouped` and `findById` rely on stable request/result keys and fail + * missing requests with `NoSuchElementError`. + * - Equal payloads are equal `SqlRequest`s, which enables request batching, + * deduplication, and cache reuse; model payload identity deliberately. + * - Batches are split by the active SQL transaction connection, so requests + * made in different transactions are not resolved together. + * - Queries like `where id in (...)` often return rows in database order; use + * `findById` or `grouped`, or preserve input order explicitly before choosing + * `ordered`. + * * @since 4.0.0 */ export * as SqlResolver from "./SqlResolver.ts" /** + * Schema-driven helpers for wrapping SQL executions in typed query functions. + * + * This module connects `Schema` request and result definitions to an `execute` + * callback that runs the actual SQL statement. The returned functions accept + * the request schema's decoded `Type`, encode it to the SQL-facing `Encoded` + * shape, run the callback, and then decode unknown driver rows through the + * result schema. This is useful for repository methods, CRUD helpers, request + * resolvers, and write operations where callers should work with domain values + * instead of raw SQL parameters or rows. + * + * The `execute` callback always receives `Req["Encoded"]`, so schema + * transformations, required encoding services, and database representations + * such as nullable columns, JSON values, dates, and bigints must line up with + * the statement builder and dialect in use. Result schemas decode the rows + * returned by the driver after any SQL client row transforms; `findOne` and + * `findOneOption` only inspect the first row, `findNonEmpty` requires at least + * one row, and `void` discards any driver result after request encoding. + * * @since 4.0.0 */ export * as SqlSchema from "./SqlSchema.ts" /** + * Low-level helpers for adapting push-based SQL row sources into Effect + * streams. + * + * SQL drivers often expose large query results through cursors, event emitters, + * or driver-specific streams that push rows as they arrive. This module + * provides the small interop layer used by SQL integrations to turn those + * producers into `Stream` values for `Statement.stream` and + * `Connection.executeStream`, so callers can process large result sets + * incrementally instead of materializing every row in memory. + * + * The adapter is scoped: driver cursors, query streams, or reserved + * connections should be acquired in the registration effect and released with + * finalizers. The internal queue is bounded and calls the producer's + * `onPause`/`onResume` hooks when downstream consumption falls behind, but the + * underlying driver still has to honor those hooks for backpressure to be + * effective. Slow consumers may keep a database cursor and connection open for + * the lifetime of the stream, so integrations should close or destroy driver + * resources on interruption, failure, or normal completion and should signal + * terminal events with `fail` or `end` exactly once. + * * @since 4.0.0 */ export * as SqlStream from "./SqlStream.ts" /** + * Building blocks for Effect's unstable SQL statement API. + * + * This module defines the low-level `Statement` and `Fragment` model used by + * SQL clients, the tagged-template `Constructor` for creating executable + * parameterized statements, and dialect compilers that turn statement segments + * into SQL text plus bind parameters. It also provides helpers for escaped + * identifiers, `IN` lists, comma-separated clause fragments, record inserts and + * updates, custom segments, and row or identifier transforms. + * + * In tagged templates, interpolated `Fragment`s and known `Segment`s are + * spliced into the statement, while ordinary values become bound parameters. Use + * identifiers for table and column names and the record helpers for generated + * column lists; `literal` and `unsafe` insert SQL text directly and should only + * be used with trusted SQL. Compilation is dialect-specific, caches rendered SQL + * on the statement, and has a `withoutTransform` path for bypassing identifier + * transforms, so compiled output can differ from normal transformed execution. + * * @since 4.0.0 */ export * as Statement from "./Statement.ts" diff --git a/packages/effect/src/unstable/workers/index.ts b/packages/effect/src/unstable/workers/index.ts index 2cc31d36ab..93f2321cfb 100644 --- a/packages/effect/src/unstable/workers/index.ts +++ b/packages/effect/src/unstable/workers/index.ts @@ -5,21 +5,101 @@ // @barrel: Auto-generated exports. Do not edit manually. /** + * Utilities for marking the parts of worker messages that should be transferred + * through `postMessage` instead of copied by the structured clone algorithm. + * + * This module is used with worker message schemas to collect + * `globalThis.Transferable` values while encoding a message, so the worker + * platform can pass the collected list as the `postMessage` transfer list. + * Common cases include sending large `Uint8Array` payloads, `ImageData` pixel + * buffers, or `MessagePort` channels without paying for an extra copy. + * + * Transferable annotations do not make an otherwise unsupported value + * structured-cloneable; the encoded message still has to be valid for + * `postMessage`. Transferring also moves ownership to the receiver, so buffers + * are detached from the sender after the send completes. Be careful when a + * typed array view shares a backing buffer with other data, since collecting + * that buffer transfers ownership of the whole buffer. + * * @since 4.0.0 */ export * as Transferable from "./Transferable.ts" /** + * Client-side worker primitives shared by browser, Node, and Bun platform + * packages. + * + * A `WorkerPlatform` turns a numeric worker id into a long-lived `Worker` + * client using a runtime-specific `Spawner`. This module is the low-level + * building block used by worker-backed RPC clients and by platform adapters + * that need to communicate with dedicated workers, shared workers, + * `MessagePort`s, worker threads, or child-process transports while keeping + * setup, message handling, and cleanup inside `Effect` scopes. + * + * The worker protocol separates spawning from message delivery. Calls to + * `send` made before the platform reports readiness are buffered and flushed + * after `run` receives the ready signal, so a spawned worker must eventually be + * run or buffered messages will never leave the client. Message values are + * passed through `postMessage`, which means callers are responsible for + * encoding payloads into values supported by the selected runtime's structured + * clone implementation. Transfer lists can avoid copies for buffers, ports, or + * other transferable values, but ownership moves to the worker and invalid + * transfer lists surface as `WorkerSendError`s. Incoming messages are handled + * by forking each handler invocation into the worker run's `FiberSet`, so + * processing is concurrent rather than serialized; use an explicit queue, + * semaphore, or protocol-level acknowledgement when ordering or back pressure + * matters. + * * @since 4.0.0 */ export * as Worker from "./Worker.ts" /** + * Typed error definitions for the unstable worker APIs. + * + * `WorkerError` is the shared error channel for `WorkerPlatform` and + * `WorkerRunnerPlatform` implementations. The nested reason identifies where a + * platform failure happened: spawning or setting up a worker, sending through + * `postMessage`, receiving worker events, or handling a runtime-specific + * failure that does not fit the other categories. This is useful when building + * worker-backed RPC clients and servers, implementing a platform adapter, or + * recovering differently from startup, transport, and worker-exit failures. + * + * Worker transports cross browser, Node, Bun, and child-process runtimes, so the + * original cause is best treated as diagnostic data. Spawn failures can mean the + * runner is not actually executing inside a worker context, send failures often + * come from structured-clone or transfer-list problems, and receive failures + * may be reported as `messageerror`, `error`, or exit events depending on the + * runtime. The `WorkerErrorReason` schema supports encoding and decoding the + * tagged reasons, but message payloads still need to be valid for the selected + * worker protocol and runtime. + * * @since 4.0.0 */ export * as WorkerError from "./WorkerError.ts" /** + * Server-side worker runner primitives shared by the browser, Node, and Bun + * platform packages. + * + * A `WorkerRunnerPlatform` is installed in code that is already running inside + * a worker-like runtime. Starting it yields a `WorkerRunner`, which listens for + * parent or client requests, identifies each connection with a numeric port id, + * and sends responses back through the same transport. The main Effect use case + * is `RpcServer.layerProtocolWorkerRunner`, but platform adapters can also use + * these types to expose lower-level request handlers for dedicated workers, + * shared workers, worker threads, or child-process channels. + * + * The wire protocol is intentionally small: inbound messages are + * `PlatformMessage` values where `[0, payload]` is a request and `[1]` closes a + * port. Higher-level protocols are responsible for encoding request and + * response payloads before they cross the worker boundary. Values must still be + * accepted by the selected runtime's message mechanism, so structured-clone + * support, transfer lists, `messageerror` events, and single-port runtimes such + * as Node or Bun should be considered when choosing payload schemas and + * resource lifetimes. Handler effects run on the runtime captured by `run`, so + * services required by the handler must be provided to the running effect. + * * @since 4.0.0 */ export * as WorkerRunner from "./WorkerRunner.ts" diff --git a/packages/effect/src/unstable/workflow/index.ts b/packages/effect/src/unstable/workflow/index.ts index 1e234ffd5a..00b8153b1d 100644 --- a/packages/effect/src/unstable/workflow/index.ts +++ b/packages/effect/src/unstable/workflow/index.ts @@ -5,41 +5,201 @@ // @barrel: Auto-generated exports. Do not edit manually. /** + * The `Activity` module defines named, schema-backed effects that run at the + * side-effect boundary of a durable workflow. Activities are executed through a + * `WorkflowEngine`, encode their success and failure values with the provided + * schemas, and can be replayed from persisted results instead of rerunning the + * underlying effect. + * + * Use activities for work that should not be embedded directly in workflow + * control flow, such as calling external services, writing to databases, + * enqueueing durable jobs, short sleeps delegated by `DurableClock`, or racing + * multiple external operations with `raceAll`. Keep activity names and schemas + * stable because engines use them, together with the workflow execution and + * retry attempt, to identify stored results. + * + * Activities can be interrupted and retried, and workflow resumes may observe a + * completed encoded result or run the activity again depending on what the + * engine has persisted. Make external side effects idempotent, use + * `idempotencyKey` for stable request keys derived from the workflow execution, + * and include the current attempt only when each retry must address a distinct + * external operation. + * * @since 4.0.0 */ export * as Activity from "./Activity.ts" /** + * Durable workflow clocks provide workflow-safe timers and sleep operations. + * + * Use this module when a workflow needs to pause until a timeout, reminder, + * deadline, retry delay, or other scheduled wake-up. Short sleeps can run as + * in-memory activities, while longer sleeps are scheduled with the workflow + * engine and resumed through a durable deferred signal when the timer fires. + * + * Because workflows may be replayed, timer names and durations should be + * deterministic and stable for a given workflow path. Avoid deriving them from + * ambient wall-clock state, and give distinct sleeps distinct names so replayed + * executions can be matched with the correct scheduled wake-up. Lower the + * in-memory threshold when a delay must be handled by the workflow engine + * rather than the current process. + * * @since 4.0.0 */ export * as DurableClock from "./DurableClock.ts" /** + * Durable deferreds are named workflow wait points whose result is stored by + * the workflow engine as an encoded `Exit`. A workflow can `await` one and + * suspend until an activity, worker, timer, or external callback completes it + * with `done`, `succeed`, `fail`, or `failCause`. + * + * Use this module to coordinate work that finishes outside the current + * workflow turn: durable races, queues that report worker results, timers, + * human approvals, webhooks, and other callback-style integrations. Tokens + * encode the workflow name, execution ID, and deferred name so completion can + * be routed back to the correct workflow execution without keeping an + * in-memory handle. + * + * Deferred names are part of persisted workflow state, so keep them stable + * across replays and unique for each logical wait. Completion is persisted as + * an `Exit` and decoded through the success and error schemas when awaited + * again; changing schemas or reusing a name for a different result type can + * make old completions fail to decode or resume the wrong wait. Complete a + * deferred once, and use `withActivityAttempt` when an activity retry needs an + * attempt-scoped completion name. + * * @since 4.0.0 */ export * as DurableDeferred from "./DurableDeferred.ts" /** + * Durable queues bridge workflow executions with persisted background workers. + * A workflow calls `process` to enqueue a schema-encoded payload in a named + * `PersistedQueue`, attach a durable deferred token, and suspend until a worker + * records the handler's `Exit` back through that deferred. + * + * Use this module for workflow steps that should be delegated to independent + * workers: long-running side effects, rate-limited or concurrency-limited + * integrations, fan-out jobs, API calls, and other work that must survive + * workflow suspension, process restarts, or handoff to another service. + * + * Queue names, payload schemas, result schemas, and idempotency keys become + * persisted coordination state. Keep them deterministic and stable across + * deployments; changing them is a persistence migration. Delivery follows the + * underlying `PersistedQueue` semantics, so handlers should be idempotent and + * prepared for retries, duplicate observations, and worker restarts. + * * @since 4.0.0 */ export * as DurableQueue from "./DurableQueue.ts" /** + * The `Workflow` module defines typed durable workflow descriptions and the + * helpers used to execute them through a `WorkflowEngine`. A workflow combines + * a stable name, a struct payload schema, success and error schemas, and an + * idempotency key so callers can derive deterministic execution IDs, execute or + * discard runs, poll results, interrupt or resume suspended executions, and + * register handlers with `toLayer`. + * + * Workflows are intended for long-running business processes that coordinate + * activities, durable deferreds, durable clocks, retries, and compensation. + * Keep external side effects at activity boundaries so engine implementations + * can safely persist, suspend, and resume execution state. Running activities + * can delay workflow suspension until they finish or suspend, and compensation + * registered with `withCompensation` only applies to top-level workflow + * effects, not nested activities. + * + * When exposing workflows through `WorkflowProxy`, remember that proxy APIs are + * derived from the workflow name and schemas. Discard execution returns the + * `executionId` instead of the workflow result, resume requires the persisted + * `executionId`, and idempotency keys must remain stable for the same logical + * request. + * * @since 4.0.0 */ export * as Workflow from "./Workflow.ts" /** + * Workflow engine service definitions and the default in-memory engine used to + * run durable workflows. + * + * This module is the runtime boundary for `Workflow` values. It registers + * workflow handlers, starts or polls executions by stable execution ID, links + * child workflow interruption to parents, and coordinates activities, durable + * deferred values, and durable clocks. Library users usually depend on the + * typed `WorkflowEngine` service, while persistence backends implement the + * lower-level `Encoded` contract and pass it to `makeUnsafe`. + * + * Durable execution requires engine implementations to make retries and resumes + * idempotent. Reusing an execution ID should observe the existing execution + * instead of starting duplicate work, suspended executions are retried according + * to `suspendedRetrySchedule`, and concurrent deferred completions or clock + * wake-ups must be serialized by the backend. Use `interrupt` when + * compensation and child workflow cleanup matter; `interruptUnsafe` can stop + * work more directly but may bypass those guarantees. The provided + * `layerMemory` is useful for tests and local development, but it keeps state + * in process memory and does not provide production durability. + * * @since 4.0.0 */ export * as WorkflowEngine from "./WorkflowEngine.ts" /** + * The `WorkflowProxy` module derives transport contracts from durable + * `Workflow` definitions. + * + * Use it when workflows should be invoked through RPC or HTTP instead of by + * importing the workflow implementation directly. `toRpcGroup` creates the + * `RpcGroup` that RPC clients and servers share, while `toHttpApiGroup` creates + * the `HttpApiGroup` that can be mounted in an HTTP API. Each workflow expands + * into execute, discard, and resume operations so external callers can start a + * workflow, start it through the discard path, or resume a suspended execution + * by `executionId`. + * + * The generated names and schemas come from the workflow definitions, so keep + * workflow names stable and pass the same workflow list to the matching + * `WorkflowProxyServer` layer. RPC proxies may be prefixed, but the same prefix + * must be used by the server handlers. HTTP endpoint paths are derived from the + * lower-cased workflow name. Preserve workflow arrays as const tuples when you + * want the generated RPC and HTTP API types to retain each workflow's literal + * name, payload, success, and error types. + * + * Discard and resume are control operations rather than ordinary workflow + * result reads. The discard proxy does not expose the normal success or error + * schemas, and resume expects the persisted `executionId`; it cannot recreate + * that boundary value from the original payload. + * * @since 4.0.0 */ export * as WorkflowProxy from "./WorkflowProxy.ts" /** + * The `WorkflowProxyServer` module provides server-side layers for exposing + * workflows through the proxy APIs generated by `WorkflowProxy`. It connects + * HTTP API endpoints or RPC handlers to the supplied workflow definitions, + * routing execute, discard, and resume requests to the corresponding workflow + * operation while keeping the `WorkflowEngine` and workflow implementation + * services on the server side. + * + * **Common tasks** + * + * - Serve a workflow proxy over an HTTP API group with {@link layerHttpApi} + * - Serve the RPC group produced by `WorkflowProxy.toRpcGroup` with + * {@link layerRpcHandlers} + * - Expose durable workflow starts and explicit resume operations to clients + * without exposing the workflow implementation itself + * + * **Gotchas** + * + * - The workflows passed to these layers must match the group produced by + * `WorkflowProxy`; RPC prefixes must be the same on both sides + * - Discard handlers use the workflow discard execution path, so callers should + * not rely on receiving the normal workflow success or error value + * - Resume handlers expect a payload with the persisted `executionId`; clients + * must preserve that boundary value because it is not recomputed from the + * original workflow payload + * * @since 4.0.0 */ export * as WorkflowProxyServer from "./WorkflowProxyServer.ts" diff --git a/packages/effect/test/Crypto.test.ts b/packages/effect/test/Crypto.test.ts new file mode 100644 index 0000000000..b63c66a063 --- /dev/null +++ b/packages/effect/test/Crypto.test.ts @@ -0,0 +1,86 @@ +import { assert, describe, it } from "@effect/vitest" +import * as Crypto from "effect/Crypto" +import * as Effect from "effect/Effect" +import * as TestClock from "effect/testing/TestClock" + +const testCrypto = Crypto.make({ + randomBytes: (size) => + size === 7 ? Uint8Array.of(0x18, 0, 0, 0, 0, 0, 0) : Uint8Array.from({ length: size }, (_, i) => i), + digest: (algorithm, data) => Effect.succeed(Uint8Array.of(data.length, algorithm.length)) +}) + +describe("Crypto", () => { + it("supports string literal digest algorithms", () => { + const algorithm: Crypto.DigestAlgorithm = "SHA-256" + assert.strictEqual(algorithm, "SHA-256") + }) + + it.effect("randomBytes delegates to the service", () => + Effect.gen(function*() { + const crypto = yield* Crypto.Crypto + const bytes = yield* crypto.randomBytes(4) + assert.deepStrictEqual(bytes, Uint8Array.of(0, 1, 2, 3)) + }).pipe(Effect.provideService(Crypto.Crypto, testCrypto))) + + it.effect("random generators delegate to the service", () => + Effect.gen(function*() { + const crypto = yield* Crypto.Crypto + const random = yield* crypto.random + const randomInt = yield* crypto.randomInt + const randomBoolean = yield* crypto.randomBoolean + const randomBetween = yield* crypto.randomBetween(10, 20) + const randomBetweenDecimalBounds = yield* crypto.randomBetween(10.5, 20.5) + const randomIntBetween = yield* crypto.randomIntBetween(1, 6) + const randomShuffle = yield* crypto.randomShuffle([1, 2, 3]) + + assert.strictEqual(random, 0.75) + assert.strictEqual(randomInt, 4503599627370497) + assert.strictEqual(randomBoolean, true) + assert.strictEqual(randomBetween, 17.5) + assert.strictEqual(randomBetweenDecimalBounds, 18) + assert.strictEqual(randomIntBetween, 5) + assert.deepStrictEqual(randomShuffle, [1, 2, 3]) + }).pipe(Effect.provideService(Crypto.Crypto, testCrypto))) + + it.effect("randomIntBetween excludes the upper bound in half-open ranges", () => + Effect.gen(function*() { + const crypto = yield* Crypto.Crypto + const value = yield* crypto.randomIntBetween(1, 6, { halfOpen: true }) + assert.strictEqual(value, 5) + }).pipe(Effect.provideService( + Crypto.Crypto, + Crypto.make({ + randomBytes: (size) => new Uint8Array(size).fill(0xff), + digest: (_algorithm, data) => Effect.succeed(data) + }) + ))) + + it.effect("randomUUIDv4 formats UUID bytes from randomBytes", () => + Effect.gen(function*() { + const crypto = yield* Crypto.Crypto + const uuid = yield* crypto.randomUUIDv4 + assert.strictEqual(uuid, "00010203-0405-4607-8809-0a0b0c0d0e0f") + }).pipe(Effect.provideService(Crypto.Crypto, testCrypto))) + + it.effect("randomUUIDv7 formats UUID bytes with the Clock timestamp", () => + Effect.gen(function*() { + yield* TestClock.setTime(0x0123456789ab) + const crypto = yield* Crypto.Crypto + const uuid = yield* crypto.randomUUIDv7 + assert.strictEqual(uuid, "01234567-89ab-7607-8809-0a0b0c0d0e0f") + }).pipe(Effect.provideService(Crypto.Crypto, testCrypto))) + + it.effect("digest delegates to the service", () => + Effect.gen(function*() { + const crypto = yield* Crypto.Crypto + const digest = yield* crypto.digest("SHA-256", Uint8Array.of(1, 2, 3)) + assert.deepStrictEqual(digest, Uint8Array.of(3, "SHA-256".length)) + }).pipe(Effect.provideService(Crypto.Crypto, testCrypto))) + + it.effect("can access a provided custom Crypto service", () => + Effect.gen(function*() { + const crypto = yield* Crypto.Crypto + const bytes = yield* crypto.randomBytes(1) + assert.deepStrictEqual(bytes, Uint8Array.of(0)) + }).pipe(Effect.provideService(Crypto.Crypto, testCrypto))) +}) diff --git a/packages/effect/test/Random.test.ts b/packages/effect/test/Random.test.ts index 350fe254a7..7acf411f3b 100644 --- a/packages/effect/test/Random.test.ts +++ b/packages/effect/test/Random.test.ts @@ -4,12 +4,12 @@ import * as Random from "effect/Random" describe("Random", () => { describe("next", () => { - it.effect("generates a number between 0 and 1", () => + it.effect("generates a number between 0 inclusive and 1 exclusive", () => Effect.gen(function*() { const value = yield* Random.next assert.isAtLeast(value, 0) - assert.isAtMost(value, 1) + assert.isBelow(value, 1) })) }) @@ -59,21 +59,33 @@ describe("Random", () => { }) describe("nextBetween", () => { - it.effect("generates number in closed range", () => + it.effect("generates number in half-open range", () => Effect.gen(function*() { for (let i = 0; i < 100; i++) { const value = yield* Random.nextBetween(10, 20) assert.isAtLeast(value, 10) - assert.isAtMost(value, 20) + assert.isBelow(value, 20) } })) + it.effect("does not round the bounds", () => + Effect.gen(function*() { + const value = yield* Random.nextBetween(10.5, 20.5).pipe( + Effect.provideService(Random.Random, { + nextIntUnsafe: () => 0, + nextDoubleUnsafe: () => 0.75 + }) + ) + + assert.strictEqual(value, 18) + })) + it.effect("handles negative ranges", () => Effect.gen(function*() { const value = yield* Random.nextBetween(-10, 10) assert.isAtLeast(value, -10) - assert.isAtMost(value, 10) + assert.isBelow(value, 10) })) }) @@ -99,6 +111,18 @@ describe("Random", () => { assert.isBelow(value, 6) } })) + + it.effect("excludes the upper bound in half-open ranges", () => + Effect.gen(function*() { + const value = yield* Random.nextIntBetween(1, 6, { halfOpen: true }).pipe( + Effect.provideService(Random.Random, { + nextIntUnsafe: () => 0, + nextDoubleUnsafe: () => 1 - Number.EPSILON + }) + ) + + assert.strictEqual(value, 5) + })) }) describe("shuffle", () => { @@ -123,49 +147,13 @@ describe("Random", () => { })) }) - describe("nextUUIDv4", () => { - it.effect("generates valid UUID v4 format", () => - Effect.gen(function*() { - const uuid = yield* Random.nextUUIDv4 - - assert.isString(uuid) - assert.match(uuid, /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i) - })) - - it.effect("generates unique UUIDs", () => - Effect.gen(function*() { - const uuid1 = yield* Random.nextUUIDv4 - const uuid2 = yield* Random.nextUUIDv4 - - assert.notStrictEqual(uuid1, uuid2) - })) - - it.effect("generates deterministic UUIDs with same seed", () => - Effect.gen(function*() { - const program = Effect.gen(function*() { - const uuid1 = yield* Random.nextUUIDv4 - const uuid2 = yield* Random.nextUUIDv4 - return [uuid1, uuid2] - }) - - const result1 = yield* program.pipe(Random.withSeed("uuid-seed")) - const result2 = yield* program.pipe(Random.withSeed("uuid-seed")) - - assert.deepStrictEqual(result1, result2) - - assert.match(result1[0], /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i) - assert.match(result1[1], /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i) - })) - }) - describe("withSeed", () => { it.effect("produces deterministic sequence with same seed", () => Effect.gen(function*() { const program = Effect.gen(function*() { const v1 = yield* Random.next const v2 = yield* Random.nextInt - const v3 = yield* Random.nextUUIDv4 - return [v1, v2, v3] + return [v1, v2] }) const result1 = yield* program.pipe(Random.withSeed("test-seed")) diff --git a/packages/opentelemetry/src/index.ts b/packages/opentelemetry/src/index.ts index f181a380a8..2d118e7df7 100644 --- a/packages/opentelemetry/src/index.ts +++ b/packages/opentelemetry/src/index.ts @@ -5,31 +5,150 @@ // @barrel: Auto-generated exports. Do not edit manually. /** + * Connects Effect's logging system to the OpenTelemetry Logs SDK. + * + * This module provides a logger provider service, an Effect `Logger` that + * emits OpenTelemetry log records, and layers for installing that logger in an + * application. It is commonly used to send Effect logs to OTLP, console, or + * vendor-specific exporters through OpenTelemetry `LogRecordProcessor`s while + * keeping logs correlated with Effect fibers and spans. Emitted records include + * the current fiber id, span identifiers when a parent span is present, log + * annotations, log spans, severity text, and the matching OpenTelemetry + * severity number. + * + * Log export depends on the configured OpenTelemetry processors and exporters; + * this module creates the provider and logger, but does not choose an exporter. + * Use the `Resource` layer to attach service and deployment metadata to the + * provider rather than repeating that data on every log record. When using + * `layerLoggerProvider`, the provider is scoped and is force-flushed and shut + * down when the layer is released, with a configurable shutdown timeout. If you + * supply or manage an OpenTelemetry provider yourself, make sure it is flushed + * and shut down during application shutdown, especially when using batching + * processors that may otherwise drop buffered logs. + * * @since 4.0.0 */ export * as Logger from "./Logger.ts" /** + * Bridges Effect metrics into OpenTelemetry by exposing the current Effect + * metric snapshot as an OpenTelemetry `MetricProducer` and registering it with + * one or more SDK `MetricReader`s. Use this module when an application already + * records metrics with Effect and needs those counters, gauges, histograms, + * frequencies, or summaries exported through OTLP, Prometheus, or another + * OpenTelemetry-compatible reader/exporter. + * + * The `layer` constructor is the usual entry point, and is also used by the + * Node and Web SDK layers when `metricReader` configuration is supplied. Metric + * readers are acquired inside the layer scope and shut down when the scope is + * released, so periodic exporters need the runtime to stay alive long enough to + * collect and export data. The exporter or backend determines whether + * cumulative or delta aggregation is expected; this module defaults to + * cumulative temporality and can be configured with `temporality: "delta"` for + * backends that require interval-based values. + * * @since 4.0.0 */ export * as Metrics from "./Metrics.ts" /** + * Provides an Effect layer for configuring OpenTelemetry in Node.js + * processes. The module wires the Effect tracer, metrics producer, and logger + * into OpenTelemetry SDK providers when span processors, metric readers, or log + * record processors are supplied, and it builds the shared resource from + * `OTEL_SERVICE_NAME`, `OTEL_RESOURCE_ATTRIBUTES`, and optional explicit + * service metadata. + * + * Use this module in Node services, workers, CLIs, or server runtimes that need + * Effect spans, metrics, and logs exported through OpenTelemetry processors and + * exporters. Telemetry is enabled only for the configured signal types, so an + * application can install tracing alone, metrics alone, logging alone, or any + * combination of them from the same layer. + * + * The layer is scoped. Tracer and logger providers are force-flushed and shut + * down when the scope is released, metric readers are shut down with the same + * lifecycle, and all shutdown waits are bounded by `shutdownTimeout` with a + * default of three seconds. Keep the layer scope alive for the lifetime of the + * process and release it during graceful shutdown so batched exporters have a + * chance to export final telemetry. When combining this layer with Node + * auto-instrumentations, register instrumentation before importing modules that + * should be patched, because many Node instrumentations hook module loading. + * * @since 4.0.0 */ export * as NodeSdk from "./NodeSdk.ts" /** + * Provides the OpenTelemetry resource used by the Effect OpenTelemetry layers. + * + * A resource describes the entity that produces telemetry, such as a service, + * process, deployment, or browser application. The tracing, metrics, logging, + * and SDK layers use this module's `Resource` service to configure providers + * and identify emitted telemetry with service-level metadata. + * + * Use `layer` when service metadata is known in code, `layerFromEnv` when + * deploying with `OTEL_SERVICE_NAME` and `OTEL_RESOURCE_ATTRIBUTES`, and + * `layerEmpty` when no resource attributes should be provided. Resource + * attributes are for stable process or service metadata, not per-span or + * per-log data. The explicit `layer` helper sets `service.name` and the + * `telemetry.sdk.*` attributes after merging custom attributes, so those keys + * are controlled by this integration. With `layerFromEnv`, `OTEL_SERVICE_NAME` + * overrides `service.name` from `OTEL_RESOURCE_ATTRIBUTES`, and additional + * attributes passed to the layer are merged last. + * * @since 4.0.0 */ export * as Resource from "./Resource.ts" /** + * Bridges Effect tracing into OpenTelemetry by installing an Effect `Tracer` + * that creates OpenTelemetry spans, records attributes, events, links, errors, + * and status, and keeps OpenTelemetry context active while traced effects run. + * Use this module when an application already has an OpenTelemetry + * `TracerProvider`, or when the Node and Web SDK layers should expose Effect + * spans to OTLP, console, or other OpenTelemetry-compatible exporters. + * + * The layer constructors wire Effect's tracer service to either the global + * OpenTelemetry tracer provider or an explicitly provided `OtelTracer`. This + * module does not create exporters or span processors by itself, so spans are + * exported only when the provider has been configured by the application or by + * the Node/Web SDK layers. Parentage is taken from Effect spans first and can + * also attach to the active OpenTelemetry context, while `makeExternalSpan` and + * `withSpanContext` are the entry points for continuing an incoming remote + * trace. Preserve `traceFlags` and `traceState` when building external spans; + * otherwise sampling defaults to sampled and trace state cannot be propagated. + * * @since 4.0.0 */ export * as Tracer from "./Tracer.ts" /** + * Provides an Effect layer for configuring OpenTelemetry in browser + * applications. The module builds a shared resource from explicit service + * metadata and wires Effect tracing, metrics, and logging into OpenTelemetry + * SDK providers when span processors, metric readers, or log record processors + * are supplied. + * + * Use this module in client-side applications that need Effect spans, metrics, + * and logs exported from browser runtimes, such as single-page apps, + * multi-page apps with hydrated Effect code, frontend workers, or UI flows + * that should be correlated with backend traces. Telemetry is enabled only for + * the configured signal types, so tracing, metrics, and logging can be + * installed independently from the same layer. + * + * Browser SDKs cannot rely on process environment resource configuration, so + * provide stable service metadata explicitly and use resource attributes for + * application, release, deployment, or page-shell identity rather than + * per-event data. This module does not create exporters; supply + * browser-compatible processors, readers, and exporters yourself, and make sure + * their endpoints are reachable from the browser with the required CORS and + * authentication behavior. The layer is scoped: tracer providers are + * force-flushed and shut down when the scope is released, while metric readers + * and logger providers follow their respective layer lifecycles. Keep the + * scope alive for the lifetime of the browser application and release it during + * application teardown when possible so batched exporters and periodic metric + * readers can deliver buffered telemetry before the page is unloaded. + * * @since 4.0.0 */ export * as WebSdk from "./WebSdk.ts" diff --git a/packages/platform-browser/src/BrowserCrypto.ts b/packages/platform-browser/src/BrowserCrypto.ts new file mode 100644 index 0000000000..7a6b60fa8f --- /dev/null +++ b/packages/platform-browser/src/BrowserCrypto.ts @@ -0,0 +1,71 @@ +/** + * Browser platform implementation of the Crypto service. + * + * @since 1.0.0 + */ +import * as Context from "effect/Context" +import * as EffectCrypto from "effect/Crypto" +import * as Effect from "effect/Effect" +import * as Layer from "effect/Layer" +import * as PlatformError from "effect/PlatformError" + +/** + * Browser Web Crypto APIs used by the Crypto service implementation. + * + * @category models + * @since 1.0.0 + */ +export const WebCrypto = Context.Reference("@effect/platform-browser/Crypto/WebCrypto", { + defaultValue: () => globalThis.crypto +}) + +/** + * A layer that directly interfaces with the Web Crypto API. + * + * @category layers + * @since 1.0.0 + */ +export const layer: Layer.Layer = Layer.effect( + EffectCrypto.Crypto, + Effect.gen(function*() { + const crypto = yield* WebCrypto + if (!crypto) { + return yield* Effect.die(new Error("Web Crypto API is not available")) + } + const randomBytes = (size: number): Uint8Array => { + const bytes = new Uint8Array(size) + crypto.getRandomValues(bytes) + return bytes + } + + const digest: EffectCrypto.Crypto["digest"] = (algorithm, data) => { + if (typeof crypto.subtle.digest !== "function") { + return Effect.fail(PlatformError.systemError({ + module: "Crypto", + method: "digest", + _tag: "Unknown", + description: "crypto.subtle.digest is not available" + })) + } + return Effect.map( + Effect.tryPromise({ + try: () => crypto.subtle.digest(algorithm, new Uint8Array(data)), + catch: (cause) => + PlatformError.systemError({ + module: "Crypto", + method: "digest", + _tag: "Unknown", + description: "Could not compute digest", + cause + }) + }), + (buffer) => new Uint8Array(buffer) + ) + } + + return EffectCrypto.make({ + randomBytes, + digest + }) + }) +) diff --git a/packages/platform-browser/src/index.ts b/packages/platform-browser/src/index.ts index d938ee9394..3033a3dda7 100644 --- a/packages/platform-browser/src/index.ts +++ b/packages/platform-browser/src/index.ts @@ -5,81 +5,405 @@ // @barrel: Auto-generated exports. Do not edit manually. /** + * Browser platform implementation of the Crypto service. + * + * @since 1.0.0 + */ +export * as BrowserCrypto from "./BrowserCrypto.ts" + +/** + * Browser implementations of the Effect `HttpClient`. + * + * This module exposes HTTP client layers for code that runs in a browser. It + * re-exports the fetch-based client for the common case, where requests should + * use the platform `fetch` implementation and optional `RequestInit` defaults, + * and it provides an `XMLHttpRequest`-backed layer for integrations that need + * XHR semantics such as response type control or environments where XHR is the + * required transport. + * + * Use these layers for single-page applications, browser tests, generated + * `HttpApiClient`s, and other client-side Effect programs that need HTTP + * requests to participate in interruption, typed transport / decode errors, and + * Effect's response body readers. + * + * Browser networking rules still apply. Cross-origin requests are subject to + * CORS preflights and server allowlists, especially when using custom headers, + * non-simple methods, or non-simple content types. Only CORS-exposed response + * headers are readable, and cookies / authentication are controlled by the + * browser and the configured fetch `RequestInit.credentials` policy. The XHR + * layer uses the browser's `XMLHttpRequest` defaults for credentials. + * + * Body handling differs between the transports. Fetch delegates body framing to + * the Web Fetch implementation. The XHR client sends empty, raw, `Uint8Array`, + * and `FormData` bodies directly, buffers `Stream` request bodies before + * sending, defaults responses to text, and can be switched to `ArrayBuffer` + * responses with `withXHRArrayBuffer`. When sending `FormData`, avoid setting + * an incompatible `Content-Type` header so the browser can generate the + * multipart boundary. + * * @since 4.0.0 */ export * as BrowserHttpClient from "./BrowserHttpClient.ts" /** + * Browser-backed `KeyValueStore` layers for Effect programs. + * + * This module provides `KeyValueStore` implementations backed by the browser's + * synchronous Web Storage APIs: `localStorage` for origin-scoped data that + * persists across page reloads and browser sessions, and `sessionStorage` for + * page-session data that is cleared when that tab or window's page session + * ends. They are useful for small client-side values such as user preferences, + * feature flags, lightweight caches, persisted drafts, or session-only workflow + * state. + * + * Web Storage is only available in browser environments and is scoped by origin. + * Browsers may deny access in private modes or restricted contexts, and writes + * can fail when storage quotas are exceeded. The API stores strings and runs + * synchronously on the main thread, so prefer it for small payloads and avoid + * treating it as a database or a secure place for sensitive data. + * * @since 4.0.0 */ export * as BrowserKeyValueStore from "./BrowserKeyValueStore.ts" /** + * Browser-backed persistence layers for Effect's persistence service. + * + * This module provides IndexedDB implementations of the Effect persistence services for applications that need a + * durable client-side cache, such as remembered query results, offline-capable workflows, or values that should survive + * page reloads. Entries are stored by persistence store id and key in a shared IndexedDB object store, with optional + * expiration timestamps for TTL-based invalidation. + * + * Because this storage depends on browser IndexedDB, operations can fail when storage is unavailable, quota is exceeded, + * data is cleared by the user or browser, or the payload cannot be structured-cloned by IndexedDB. Expired entries are + * removed lazily when they are read, so this module is best suited for application-managed cached objects rather than + * security-sensitive or authoritative data. + * * @since 4.0.0 */ export * as BrowserPersistence from "./BrowserPersistence.ts" /** + * Browser entry-point helpers for running Effect programs. + * + * This module exposes `runMain`, a browser-oriented main runner for launching + * an Effect as the root program of a page, single-page application, demo, or + * browser test harness. It delegates execution to the core Effect runtime while + * adding the browser lifecycle hook needed to interrupt the main fiber when the + * page receives `beforeunload`. + * + * `BrowserRuntime` does not provide application services by itself. Provide + * any required layers, such as browser HTTP, storage, worker, geolocation, or + * permission services, before passing the effect to `runMain`. Keep long-lived + * browser resources scoped so interruption can run their finalizers while the + * page is still active. + * + * Browser unload is more constrained than a process signal. Finalizers that + * need the network, timers, prompts, or long asynchronous work may not complete + * once navigation or tab close has started, and browsers do not expose a + * process exit status. Use `runMain` to connect the page lifecycle to Effect + * interruption, and use browser-specific persistence or delivery APIs for work + * that must survive page teardown. + * * @since 4.0.0 */ export * as BrowserRuntime from "./BrowserRuntime.ts" /** + * Browser WebSocket layers for Effect sockets. + * + * This module provides the browser entry point for `Socket.Socket` values + * backed by the platform `WebSocket` implementation. Use `layerWebSocket` when + * client-side Effect programs, browser tests, RPC transports, or realtime UI + * features need a bidirectional socket connected to a WebSocket URL, and use + * `layerWebSocketConstructor` when lower-level socket APIs need access to the + * browser constructor service. + * + * Browser WebSocket rules still apply. Connections are created through + * `globalThis.WebSocket`, so URL schemes, subprotocol negotiation, mixed-content + * blocking, cookies, authentication, CORS-like origin checks, and extension + * negotiation are controlled by the browser and server rather than by Effect. + * Close events are translated into socket errors unless the provided + * `closeCodeIsError` predicate classifies the close code as clean, which is + * useful for protocols that use application-specific close codes. + * + * Messages are delivered as strings or binary `Uint8Array` values; browser + * `Blob` messages are read into bytes before they reach the socket handler. + * Outgoing data should already be serialized to a string or bytes, and protocol + * frames that represent an intentional close should be sent as `CloseEvent` + * values so the underlying `WebSocket.close` code and reason are preserved. + * * @since 4.0.0 */ export * as BrowserSocket from "./BrowserSocket.ts" /** + * Browser `Stream` constructors for DOM event targets. + * + * This module provides typed helpers for turning `window.addEventListener` and + * `document.addEventListener` callbacks into Effect `Stream`s. They are useful + * for UI and runtime signals such as resize, visibility, keyboard, pointer, + * focus, online / offline, and other browser events that should be composed + * with Effect stream operators and finalized with the consuming fiber. + * + * Browser events are push-based `EventTarget` notifications, so they do not + * apply Web Streams backpressure to the browser event source. Events are + * buffered until downstream pulls them; the default buffer is unbounded, so + * high-frequency sources like scroll, pointermove, or mousemove should usually + * set `bufferSize` and use stream operators that sample, debounce, throttle, or + * drop work as appropriate. + * + * These helpers are for DOM events, not for adapting `ReadableStream` request + * or response bodies. Fetch bodies follow the Web Streams body rules, including + * single-consumer locking and disturbed bodies after reads, and should be + * handled with body-specific HTTP or Web Streams APIs instead. When using the + * browser `once` option, pair the stream with `Stream.take(1)` if a finite + * stream is required. + * * @since 4.0.0 */ export * as BrowserStream from "./BrowserStream.ts" /** + * Parent-side browser support for Effect workers. + * + * This module provides the `WorkerPlatform` used by browser applications that + * spawn or connect to `Worker`, `SharedWorker`, and `MessagePort` endpoints + * through Effect's worker protocol. Pair it with `BrowserWorkerRunner` in the + * worker entrypoint when building worker-backed RPC clients, moving CPU-bound + * work off the main thread, isolating browser-only services, or adapting an + * existing `MessageChannel` in tests and custom transports. + * + * Dedicated workers communicate through the worker object itself, while shared + * workers communicate through `worker.port`; raw `MessagePort` values are also + * accepted and are started when supported. Messages are posted with the browser + * structured-clone algorithm, so payloads must be cloneable by the target + * runtime. Transfer lists can avoid copying values such as `ArrayBuffer` or + * `MessagePort`, but transferring moves ownership away from the sender and + * invalid or mismatched transferables can fail the send. Scope finalization + * sends the worker close signal over the port; the application that created a + * dedicated `Worker` remains responsible for any broader lifecycle such as + * terminating it. + * * @since 4.0.0 */ export * as BrowserWorker from "./BrowserWorker.ts" /** + * Browser runtime support for Effect worker runners. + * + * This module is intended for code that is already executing in a browser + * worker context, or for tests and adapters that supply a `MessagePort` or + * `Window` endpoint directly. It provides the `WorkerRunnerPlatform` used by + * `WorkerRunner` and `RpcServer.layerProtocolWorkerRunner` to receive parent + * or client requests, run Effect handlers, and send responses through the + * browser `postMessage` channel. + * + * Use it with `BrowserWorker` when a browser application needs to move RPC + * handlers, CPU-bound computations, or browser-only services into a dedicated + * worker or shared worker. Dedicated workers communicate through the current + * `self` endpoint; shared workers accept multiple `onconnect` ports and cache + * ports that connect before the runner layer starts. Messages still use the + * browser structured-clone algorithm, so payload schemas, transfer lists, + * `messageerror` events, and the lifetime of each `MessagePort` must be + * considered when crossing worker boundaries. + * * @since 4.0.0 */ export * as BrowserWorkerRunner from "./BrowserWorkerRunner.ts" /** + * Browser clipboard service for Effect programs. + * + * This module wraps the browser `navigator.clipboard` API in a `Clipboard` + * service so client-side applications can read, write, and clear clipboard + * contents as typed Effects. It is useful for common UI workflows such as copy + * buttons, paste/import actions, sharing generated text, and moving rich + * clipboard payloads like `Blob`-backed `ClipboardItem`s through an Effect + * environment. + * + * Browser clipboard rules still apply. Clipboard access generally requires a + * secure context, and browsers may require a user gesture, permission prompt, or + * active focused document before reads or writes are allowed. Support also + * varies by operation and payload type: text helpers are the most portable, + * while `ClipboardItem` and non-text MIME types may be unavailable or restricted + * in some browsers. Failed browser operations are surfaced as `ClipboardError`. + * * @since 4.0.0 */ export * as Clipboard from "./Clipboard.ts" /** + * Browser geolocation support for Effect programs. + * + * This module provides a `Geolocation` service and browser-backed layer for + * reading device location through `navigator.geolocation`. Use + * `getCurrentPosition` when an application needs one location fix, such as a + * nearby-search, check-in, or delivery estimate, and `watchPosition` when it + * needs a stream of updates for navigation, tracking, or location-aware UI. + * + * The implementation is browser-only and relies on the browser permission and + * policy model for geolocation. Calls may prompt the user, fail when permission + * is denied, time out, or report that position data is unavailable because of + * device, browser, privacy, origin, or secure-context restrictions. Watched + * positions are scoped so the underlying browser watch is cleared when the + * stream is finalized, and slow consumers should account for the sliding + * buffer used by `watchPosition`. + * * @since 4.0.0 */ export * as Geolocation from "./Geolocation.ts" /** + * Browser IndexedDB primitives and key schemas for Effect applications. + * + * This module is the low-level bridge used by the platform-browser IndexedDB + * integration. It provides an `IndexedDb` service around the browser + * `indexedDB` factory and `IDBKeyRange` constructor, a `layerWindow` layer for + * wiring those primitives from `window`, and schemas for the key shapes accepted + * by IndexedDB object stores and indexes. + * + * Use it when building typed local persistence for browser caches, + * offline-first state, background queues, drafts, or other client-side data + * that should be validated before it reaches IndexedDB. Higher-level database, + * version, table, and query modules build on these primitives for migrations + * and typed transactions. + * + * IndexedDB still follows the browser rules: schema changes happen only during + * version upgrades, upgrades may be blocked by other open tabs or connections, + * and reads or writes must run in transactions scoped to the object stores they + * touch. The `layerWindow` constructor should be used only where browser + * globals are available, and code that also runs during SSR or in restricted + * browser contexts should account for `indexedDB` or `IDBKeyRange` being + * missing. + * * @since 4.0.0 */ export * as IndexedDb from "./IndexedDb.ts" /** + * Builds and opens typed IndexedDB databases from versioned schema migrations. + * + * This module turns an `IndexedDbVersion` migration chain into an + * `IndexedDbDatabase` layer. The layer opens the browser database, runs any + * pending upgrade migrations, provides a query builder for the current schema, + * and exposes a `rebuild` effect that deletes and reopens the database. It is + * the database-level companion to the table, version, and query builder + * modules. + * + * Use it for browser-local persistence such as offline-first application + * state, cached server data, background queues, drafts, and other client-side + * stores that need typed reads and writes backed by IndexedDB transactions. + * + * IndexedDB schema changes can only happen inside upgrade transactions, so + * every call to `make` or `.add` represents the next browser database version + * and only migrations after the existing browser version are run. Table and + * index definitions type the migration and query APIs, but object stores and + * indexes still need to be created or removed explicitly with the migration + * transaction helpers. Include the complete target table set in each version, + * create indexes before querying them, and treat key path or auto-increment + * changes as store migrations that copy data into a replacement object store. + * Upgrades can be blocked by other open connections, and all migration reads, + * writes, store changes, and index changes share the single upgrade + * transaction supplied by the browser. + * * @since 4.0.0 */ export * as IndexedDbDatabase from "./IndexedDbDatabase.ts" /** + * Builds effectful, schema-aware queries for typed browser IndexedDB versions. + * + * An `IndexedDbQueryBuilder` is created from an open database and a version's + * table descriptors, then exposes `from(tableName)` as the entry point for + * table operations. The resulting query objects can select, count, delete, + * insert, upsert, clear tables, stream paged reads, react to invalidations, and + * run multiple effects in a shared `IDBTransaction` with `withTransaction`. + * + * Use this module for local browser persistence such as caches, offline-first + * state, background queues, drafts, and other client-side data where writes + * should be encoded through `Schema` and reads should be decoded before they + * reach application code. + * + * Index and range helpers are thinly typed wrappers around IndexedDB object + * stores, indexes, `IDBKeyRange`, and cursors. Index names must be declared on + * the table and created during migrations; without an index, queries use the + * object store key path. Range values are encoded IndexedDB key values, and + * compound key paths must follow the declared key order. Filters, offsets, + * reverse reads, out-of-line keys, and limited deletes require cursor-based + * scans, while simpler selects can use `getAll`. + * + * Table schema details affect runtime behavior: auto-increment writes may omit + * the generated numeric key, stores without a key path require an out-of-line + * `key` for writes and add that `key` back to selected rows, and schema + * mismatches surface as `EncodeError` or `DecodeError` query failures. + * * @since 4.0.0 */ export * as IndexedDbQueryBuilder from "./IndexedDbQueryBuilder.ts" /** + * Defines typed table descriptors for the browser IndexedDB integration. + * + * An `IndexedDbTable` records the object store name, row schema, primary key + * path, indexes, auto-increment behavior, and transaction durability used by + * database versions, migrations, and typed queries. These descriptors are + * useful for local caches, offline-first application state, background queues, + * drafts, and other browser-persisted data that should be validated through + * `Schema`. + * + * Key paths and index paths must reference encoded schema fields whose values + * are valid IndexedDB keys, and compound paths are represented as readonly + * arrays. Tables without a key path use an out-of-line `key` that is added to + * reads and required for writes, so the row schema itself cannot define a + * `key` field. Auto-increment tables require a numeric key path; when that key + * is omitted on write, the module uses a derived schema without the generated + * key. Declaring indexes here types query builder index selection, but the + * indexes still need to be created during database migrations. + * * @since 4.0.0 */ export * as IndexedDbTable from "./IndexedDbTable.ts" /** + * Typed IndexedDB schema version definitions. + * + * This module represents one logical IndexedDB database version as a non-empty set of `IndexedDbTable` definitions. + * Versions are consumed by `IndexedDbDatabase.make` and `.add` to type query builders and migration transactions, so + * applications can describe the tables available after initialization or after each schema upgrade. + * + * Use an `IndexedDbVersion` when defining the initial stores for a browser database, adding or removing object stores, + * changing indexes, or moving data between differently shaped table schemas. The version value is a typed description of + * the target schema; creating and deleting object stores or indexes still happens explicitly inside the corresponding + * `IndexedDbDatabase` migration callback. + * + * IndexedDB versioning is ordered by the migration chain rather than by a number stored here. Each `.add` step becomes + * the next browser database version, and only migrations after the browser's current version are run. Include every table + * that should be queryable in each target version, avoid duplicate table names, and remember that key-path or + * auto-increment changes usually require creating a new object store and copying data during the upgrade transaction. + * * @since 4.0.0 */ export * as IndexedDbVersion from "./IndexedDbVersion.ts" /** + * Browser Permissions API support for Effect programs. + * + * This module provides a `Permissions` service and browser-backed layer for + * querying `navigator.permissions` from Effect code. Use it to check whether a + * browser capability is currently `granted`, `prompt`, or `denied` before + * showing UI for flows such as geolocation, notifications, clipboard access, + * camera, microphone, or persistent storage. + * + * Permission queries do not request access by themselves and should not replace + * the feature API that actually performs the operation. Browser support for + * permission names and states is uneven, queries may reject for unsupported or + * invalid descriptors, and some permissions are only meaningful in secure + * contexts or after user activation. Returned `PermissionStatus` objects can + * change when the user updates browser settings or responds to prompts; when + * watching `change` or `onchange`, account for browser differences and clean up + * listeners when the surrounding Effect scope ends. + * * @since 4.0.0 */ export * as Permissions from "./Permissions.ts" diff --git a/packages/platform-browser/test/BrowserCrypto.test.ts b/packages/platform-browser/test/BrowserCrypto.test.ts new file mode 100644 index 0000000000..45650777cc --- /dev/null +++ b/packages/platform-browser/test/BrowserCrypto.test.ts @@ -0,0 +1,74 @@ +import * as BrowserCrypto from "@effect/platform-browser/BrowserCrypto" +import { assert, describe, it } from "@effect/vitest" +import { Layer } from "effect" +import * as Crypto from "effect/Crypto" +import * as Effect from "effect/Effect" +import * as TestClock from "effect/testing/TestClock" + +const uuidV4Regex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/ +const uuidV7Regex = /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/ + +const getRandomValues = (array: T): T => { + if (array instanceof Uint8Array) { + for (let i = 0; i < array.length; i++) { + array[i] = i & 0xff + } + } + return array +} + +describe("BrowserCrypto", () => { + it.effect("generates UUIDv4 values from getRandomValues", () => + Effect.gen(function*() { + const crypto = yield* Crypto.Crypto + const uuid = yield* crypto.randomUUIDv4 + assert.strictEqual(uuid, "00010203-0405-4607-8809-0a0b0c0d0e0f") + assert.match(uuid, uuidV4Regex) + }).pipe(Effect.provide(BrowserCrypto.layer.pipe( + Layer.provide(Layer.succeed(BrowserCrypto.WebCrypto, { + ...crypto, + getRandomValues(array) { + return getRandomValues(array) + } + })) + )))) + + it.effect("generates UUIDv7 values from getRandomValues and the Clock", () => + Effect.gen(function*() { + yield* TestClock.setTime(0x0123456789ab) + const crypto = yield* Crypto.Crypto + const uuid = yield* crypto.randomUUIDv7 + assert.strictEqual(uuid, "01234567-89ab-7607-8809-0a0b0c0d0e0f") + assert.match(uuid, uuidV7Regex) + }).pipe(Effect.provide(BrowserCrypto.layer.pipe( + Layer.provide(Layer.succeed(BrowserCrypto.WebCrypto, { + ...crypto, + getRandomValues(array) { + return getRandomValues(array) + } + })) + )))) + + it.effect("computes digests with subtle crypto", () => { + const buffer = new ArrayBuffer(3) + new Uint8Array(buffer).set([1, 2, 3]) + + return Effect.gen(function*() { + const crypto = yield* Crypto.Crypto + const digest = yield* crypto.digest("SHA-256", new Uint8Array(buffer)) + assert.deepStrictEqual(digest, new Uint8Array([1, 2, 3])) + }).pipe( + Effect.provide(BrowserCrypto.layer.pipe( + Layer.provide(Layer.succeed(BrowserCrypto.WebCrypto, { + ...crypto, + subtle: { + ...crypto.subtle, + digest() { + return Promise.resolve(buffer) + } + } + })) + )) + ) + }) +}) diff --git a/packages/platform-bun/src/BunCrypto.ts b/packages/platform-bun/src/BunCrypto.ts new file mode 100644 index 0000000000..f71fba29c7 --- /dev/null +++ b/packages/platform-bun/src/BunCrypto.ts @@ -0,0 +1,16 @@ +/** + * Bun platform Crypto service layer. + * + * @since 1.0.0 + */ +import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto" +import type * as Crypto from "effect/Crypto" +import type * as Layer from "effect/Layer" + +/** + * A layer that provides the Bun Crypto service implementation. + * + * @category layers + * @since 1.0.0 + */ +export const layer: Layer.Layer = NodeCrypto.layer diff --git a/packages/platform-bun/src/BunServices.ts b/packages/platform-bun/src/BunServices.ts index a3b8b4652e..cf968b1c9c 100644 --- a/packages/platform-bun/src/BunServices.ts +++ b/packages/platform-bun/src/BunServices.ts @@ -21,6 +21,7 @@ * * @since 4.0.0 */ +import type { Crypto } from "effect/Crypto" import type { FileSystem } from "effect/FileSystem" import * as Layer from "effect/Layer" import type { Path } from "effect/Path" @@ -28,6 +29,7 @@ import type { Stdio } from "effect/Stdio" import type { Terminal } from "effect/Terminal" import type { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner" import * as BunChildProcessSpawner from "./BunChildProcessSpawner.ts" +import * as BunCrypto from "./BunCrypto.ts" import * as BunFileSystem from "./BunFileSystem.ts" import * as BunPath from "./BunPath.ts" import * as BunStdio from "./BunStdio.ts" @@ -40,7 +42,7 @@ import * as BunTerminal from "./BunTerminal.ts" * @category models * @since 4.0.0 */ -export type BunServices = ChildProcessSpawner | FileSystem | Path | Terminal | Stdio +export type BunServices = ChildProcessSpawner | Crypto | FileSystem | Path | Terminal | Stdio /** * Provides the default Bun implementations for child process spawning, @@ -52,6 +54,7 @@ export type BunServices = ChildProcessSpawner | FileSystem | Path | Terminal | S export const layer: Layer.Layer = BunChildProcessSpawner.layer.pipe( Layer.provideMerge(Layer.mergeAll( BunFileSystem.layer, + BunCrypto.layer, BunPath.layer, BunStdio.layer, BunTerminal.layer diff --git a/packages/platform-bun/src/index.ts b/packages/platform-bun/src/index.ts index 5c6d06c59f..ebacf9f740 100644 --- a/packages/platform-bun/src/index.ts +++ b/packages/platform-bun/src/index.ts @@ -12,16 +12,113 @@ export * as BunChildProcessSpawner from "./BunChildProcessSpawner.ts" /** + * The `BunClusterHttp` module provides the Bun HTTP and WebSocket transports + * for Effect Cluster runners. It wires `HttpRunner` to the Bun HTTP server, + * supplies Fetch and Bun WebSocket client protocols, and builds a complete + * sharding layer with serialization, runner health, runner storage, and message + * storage. + * + * **Common tasks** + * + * - Run a Bun process as a cluster runner over HTTP or WebSocket with + * {@link layer} + * - Connect a client-only process to an existing HTTP cluster without starting + * a runner server + * - Use SQL-backed storage for durable multi-process clusters, `local` storage + * for short-lived development, or `byo` storage when the deployment owns the + * persistence boundary + * - Check runner health with protocol pings or Kubernetes pod readiness through + * {@link layerK8sHttpClient} + * + * **Gotchas** + * + * - `runnerAddress` is the host and port advertised to other runners; set + * `runnerListenAddress` when the local bind address differs from the + * externally reachable address + * - The HTTP and WebSocket transports serve runner RPCs at the default + * `HttpRunner` route, so proxies and load balancers must preserve the path + * and allow WebSocket upgrades when `transport` is `"websocket"` + * - `clientOnly` does not start an HTTP server or receive shard assignments + * - SQL storage is the default; `local` storage is in-memory/noop and `byo` + * requires the surrounding application to provide both runner and message + * storage services + * - Ping health checks use the selected transport and serialization, so route, + * port, proxy, or codec mismatches can make a runner appear unhealthy + * * @since 4.0.0 */ export * as BunClusterHttp from "./BunClusterHttp.ts" /** + * The `BunClusterSocket` module provides the Bun socket transport for Effect + * Cluster runners. It wires `SocketRunner` to Bun-compatible TCP sockets, + * supplies RPC client and server protocol layers, and builds a complete + * sharding layer with serialization, runner health, runner storage, and message + * storage. + * + * **Common tasks** + * + * - Run a Bun process as a cluster runner over raw TCP sockets with + * {@link layer} + * - Connect a client-only process to an existing socket cluster without + * starting a runner server + * - Use SQL-backed storage for durable multi-process clusters, `local` storage + * for short-lived development, or `byo` storage when the deployment owns the + * persistence boundary + * - Check runner health with socket pings or Kubernetes pod readiness through + * {@link layerK8sHttpClient} + * + * **Gotchas** + * + * - `runnerAddress` is the host and port advertised to other runners; set + * `runnerListenAddress` when the local bind address differs from the + * externally reachable address + * - The socket transport is point-to-point RPC, not cluster gossip: runner + * membership, shard ownership, and persisted delivery are coordinated through + * `RunnerStorage`, `MessageStorage`, and `RunnerHealth` + * - `clientOnly` does not start a socket server or receive shard assignments + * - SQL storage is the default; `local` storage is in-memory/noop and `byo` + * requires the surrounding application to provide both runner and message + * storage services + * - Ping health checks use the same socket protocol, so unreachable ports, + * firewalls, or serialization mismatches can make a runner appear unhealthy + * - Kubernetes health checks use Bun's Fetch-backed HTTP client and the service + * account CA certificate when it is available + * * @since 4.0.0 */ export * as BunClusterSocket from "./BunClusterSocket.ts" /** + * Bun platform Crypto service layer. + * + * @since 1.0.0 + */ +export * as BunCrypto from "./BunCrypto.ts" + +/** + * Bun layer for Effect's `FileSystem` service. + * + * Use this module at the edge of Bun applications, CLIs, scripts, and tests + * that need real local filesystem access through `effect/FileSystem`: reading + * and writing files, creating directories and temporary files, inspecting + * metadata, managing links, or watching paths for changes. It exposes only the + * Bun `FileSystem` layer; the operations themselves are accessed from the + * `FileSystem` service once the layer is provided, or from `BunServices.layer` + * when the program also needs the standard Bun path, stdio, terminal, and child + * process services. + * + * Bun supports Node-compatible filesystem APIs, so this layer reuses the shared + * Node filesystem implementation. Paths therefore follow the current process and + * host platform rules: relative paths are resolved from the current working + * directory, separators and drive/UNC behavior are platform-dependent, and + * request URLs should be decoded and validated before being mapped to local + * paths. The service works with bytes, scoped file handles, and Effect + * streams/sinks; use `FileSystem.stream` for large files instead of + * `readFile`, and remember that stream offsets and lengths are byte positions. + * Bun `File` and `Blob` values are not filesystem handles here; path-based HTTP + * file responses are handled by the Bun HTTP platform adapter with `Bun.file`. + * * @since 4.0.0 */ export * as BunFileSystem from "./BunFileSystem.ts" @@ -32,41 +129,212 @@ export * as BunFileSystem from "./BunFileSystem.ts" export * as BunHttpClient from "./BunHttpClient.ts" /** + * Bun implementation of the Effect HTTP platform service. + * + * This module connects the portable `HttpPlatform` file response helpers to + * Bun's Web-compatible runtime. `BunHttpServer` provides this layer when + * applications serve local files, public assets, downloads, byte ranges, or + * Web `File` values from Effect `HttpServerResponse` constructors. + * + * Path-based responses are backed by `Bun.file`, and Web `File` responses are + * returned directly as raw response bodies. The shared `HttpPlatform` service + * still computes file metadata such as ETags and last-modified headers, while + * this adapter lets Bun's `Response` implementation handle the platform body. + * + * Because the Bun server adapter sits on top of Web `Request` and `Response`, + * request bodies follow the usual single-consumption rules: choose the + * streamed, text, URL-encoded, or multipart view that matches the route. For + * `FormData` responses, let the `Response` constructor create the multipart + * content type and boundary unless you intentionally override it. File + * responses take filesystem paths, not request URLs; Bun request URLs are + * absolute at the runtime edge, and route paths are normalized by + * `BunHttpServer`, so decode and validate URL pathnames before mapping them to + * files. + * * @since 4.0.0 */ export * as BunHttpPlatform from "./BunHttpPlatform.ts" /** + * Bun implementation of the Effect `HttpServer`. + * + * This module builds an Effect HTTP server from `Bun.serve`, translating Bun's + * Web `Request` objects into `HttpServerRequest` values and Effect + * `HttpServerResponse` values back into Web `Response` objects. It is the Bun + * runtime entry point for serving `HttpApp`s, streaming responses, file + * responses through `BunHttpPlatform`, multipart requests, and websocket + * endpoints through `HttpServerRequest.upgrade`. + * + * Common use cases include using {@link layer} or {@link layerConfig} to serve + * an application from Bun configuration, {@link layerServer} when only the + * `HttpServer` service is needed, and {@link layerTest} for tests that need an + * ephemeral Bun listener and fetch-compatible client. + * + * Bun supplies absolute request URLs and Web-standard request bodies. This + * adapter stores the normalized path-and-query URL on `HttpServerRequest.url`, + * while the underlying `Request` still follows Web body rules: pick the + * streamed, text, JSON, URL-encoded, or multipart view that matches the route + * instead of consuming the same body in incompatible ways. Because `Bun.serve` + * has a single active `fetch` handler, each `serve` call reloads that handler + * and restores the previous one when the serve scope finalizes. + * + * WebSocket upgrades must happen from the Bun request handler. The + * `HttpServerRequest.upgrade` effect calls `server.upgrade`, fails when Bun says + * the request is not upgradeable, buffers messages that arrive before the + * Effect socket handler is installed, and maps non-normal close codes into + * `Socket` errors. The server is stopped with `server.stop()` when its + * acquisition scope closes; unless preemptive shutdown is disabled, finalizing + * a serve scope also starts that stop with the configured graceful shutdown + * timeout or the default timeout. + * * @since 4.0.0 */ export * as BunHttpServer from "./BunHttpServer.ts" /** + * Accessors for the Bun `Request` object backing a platform Bun + * `HttpServerRequest`. + * + * Use this module at interop boundaries when an Effect HTTP handler needs the + * original `Bun.BunRequest`, for example to read Bun route parameters, pass the + * request to Bun-specific APIs, inspect Web `Request` fields that are not + * exposed by the portable `HttpServerRequest` interface, or coordinate with code + * that already works directly with Bun's server request type. + * + * The returned request is the original Web request supplied by `Bun.serve`. It + * does not reflect Effect request overrides made by middleware, such as a + * rewritten URL, adjusted headers, or a substituted remote address. Its body is + * the same one-shot Web `ReadableStream` used by the Effect body helpers, so + * calling `text`, `json`, `formData`, `arrayBuffer`, or reading `body` directly + * can disturb the request and conflict with Effect body, multipart, or stream + * helpers unless ownership of the body is clear. + * + * Bun stores client IP information on the server rather than on the request + * object. Prefer `HttpServerRequest.remoteAddress` when you need the address + * seen by Effect or middleware; the raw request returned here will not expose + * middleware-provided remote address overrides. + * * @since 4.0.0 */ export * as BunHttpServerRequest from "./BunHttpServerRequest.ts" /** + * Bun-specific helpers for parsing HTTP `multipart/form-data` request bodies. + * + * This module adapts a Bun `Request` body and headers into the shared + * `Multipart` model. Use `stream` from Bun HTTP handlers when form fields and + * uploaded files should be consumed incrementally, for example validating text + * fields while piping large file parts to storage. Use `persisted` when the + * whole form should be collected into a record and file parts should be written + * to scoped temporary files through the current `FileSystem`, `Path`, and + * `Scope` services. + * + * Bun requests expose one-shot web streams, so choose one body reader and do + * not call `formData`, `text`, `json`, or `arrayBuffer` before using this + * module. Incoming `FormData` uploads must include a `multipart/form-data` + * content type with the boundary generated by the client; when constructing + * `FormData` requests, let the runtime set that header. Stream file content for + * large uploads, reserve `contentEffect` for small files, and treat client + * filenames as metadata rather than trusted filesystem paths. Persisted file + * paths remain valid only for the surrounding scope. + * * @since 4.0.0 */ export * as BunMultipart from "./BunMultipart.ts" /** + * Bun layers for Effect's `Path` service. + * + * Use this module when an Effect program running on Bun needs path + * manipulation from the `Path` service, such as joining and normalizing local + * filesystem locations, resolving configuration or static asset paths, handling + * CLI path arguments, or converting between filesystem paths and `file:` URLs. + * + * Bun exposes Node-compatible path behavior, so these layers reuse the shared + * Node path implementation. The default `layer` follows the host operating + * system's path rules, including separators, absolute paths, drive letters, and + * UNC paths where applicable. Use `layerPosix` or `layerWin32` when code needs + * stable POSIX or Windows semantics regardless of where Bun is running. These + * layers only manipulate path strings; they do not read the filesystem, validate + * that paths exist, or turn request URLs into safe local paths. `BunServices` + * already includes the default Bun path layer, so provide this module directly + * when you need only `Path` or one of the platform-specific variants. + * * @since 4.0.0 */ export * as BunPath from "./BunPath.ts" /** + * Bun Redis integration backed by Bun's built-in `RedisClient`. + * + * This module provides scoped layers that create a Bun `RedisClient` and expose + * both the low-level `Redis` service used by Effect persistence modules and the + * `BunRedis` service for direct access to the underlying client. Use it in Bun + * applications that need Redis-backed persistence, persisted queues, + * distributed rate limiting, custom Redis commands, or Bun Redis features such + * as pub/sub through the raw client. + * + * The client is acquired when the layer is built and closed with `close` when + * the layer scope ends, so install the layer at the lifetime you want for the + * connection and pass a Redis URL, Bun `RedisOptions`, or `layerConfig` for + * connection settings. The portable `Redis` service sends ordinary commands + * through `RedisClient.send`; pub/sub is available through `BunRedis.client` + * or `BunRedis.use` and should normally use a separately scoped client so a + * subscription does not interfere with command traffic used by persistence or + * rate limiter stores. + * + * Persistence and rate limiter stores build keys and Lua scripts on top of this + * service. Choose stable prefixes and store ids to avoid collisions, account + * for persisted values that may fail to decode after schema changes, and avoid + * unbounded high-cardinality rate-limit keys unless you have a cleanup or + * bounding strategy. + * * @since 4.0.0 */ export * as BunRedis from "./BunRedis.ts" /** + * Bun entry-point helpers for running Effect programs. + * + * This module exposes `runMain`, the Bun runtime launcher used at the edge of + * CLIs, scripts, servers, and worker processes. It runs an already + * self-contained Effect as the process main program, using the shared + * Node-compatible runtime implementation for error reporting, teardown, and + * `process` signal handling available in Bun. + * + * `BunRuntime` does not provide application services by itself. Provide any + * required layers, such as `BunServices.layer` or narrower service-specific + * layers, before passing the effect to `runMain`. On `SIGINT` or `SIGTERM`, + * the main fiber is interrupted so scoped resources and finalizers can shut + * down; keep long-running servers, workers, and subscriptions attached to that + * scope and avoid finalizers that never complete, otherwise process shutdown + * can be delayed. + * * @since 4.0.0 */ export * as BunRuntime from "./BunRuntime.ts" /** + * Provides the aggregate Bun platform services layer for applications that run + * on the Bun runtime. + * + * This module is useful when an application needs the standard Bun-backed + * implementations of filesystem access, path operations, stdio, terminal + * interaction, and child process spawning from a single layer. Provide + * `BunServices.layer` near the edge of a program to satisfy effects that read + * or write files, resolve paths, interact with stdin/stdout/stderr or a + * terminal, or launch subprocesses. + * + * The layer only supplies the runtime services listed by `BunServices`; it does + * not provide unrelated platform services such as HTTP clients, HTTP servers, + * sockets, workers, or Redis. Several of these core Bun services are backed by + * the shared Node-compatible implementations used by the Bun adapters, so the + * default path, stdio, terminal, and subprocess behavior follows the current + * process and host platform. Libraries should continue to depend on the + * individual service tags they use, while Bun applications, CLIs, and tests can + * choose this layer or narrower service-specific layers depending on how much + * of the Bun runtime they want to expose. + * * @since 4.0.0 */ export * as BunServices from "./BunServices.ts" @@ -77,6 +345,25 @@ export * as BunServices from "./BunServices.ts" export * as BunSink from "./BunSink.ts" /** + * Bun platform socket entry point for Effect sockets backed by Bun-compatible + * Node streams and Bun's native WebSocket implementation. + * + * This module re-exports the shared Node socket constructors for TCP clients, + * Unix domain socket clients, and adapters from existing Node `Duplex` streams, + * then adds Bun-specific WebSocket layers using `globalThis.WebSocket`. Use it + * in Bun applications that connect to raw socket protocols, Unix sockets, + * realtime WebSocket services, or Effect RPC transports that need a + * `Socket.Socket` layer. + * + * TCP lifecycle behavior comes from the shared Node layer: sockets are scoped, + * finalizers close or destroy the underlying stream, open timeouts become + * socket open errors, and read, write, and close events are mapped to + * `SocketError` values. TLS concerns depend on the transport being used: `wss:` + * URLs are handled by Bun's WebSocket implementation, while TLS-wrapped + * `Duplex` streams can be adapted after they have been created elsewhere. + * When closing intentionally, send `Socket.CloseEvent` values so the close code + * and reason are preserved through the socket lifecycle. + * * @since 4.0.0 */ export * as BunSocket from "./BunSocket.ts" @@ -87,26 +374,115 @@ export * as BunSocket from "./BunSocket.ts" export * as BunSocketServer from "./BunSocketServer.ts" /** + * Bun-backed implementation of Effect's `Stdio` service. + * + * This module provides the process stdio layer for Bun applications by reusing + * the shared Node-compatible implementation. The layer connects `Stdio` to the + * current Bun process: arguments come from `process.argv`, input is read from + * `process.stdin`, and output and error output write to `process.stdout` and + * `process.stderr`. It is intended for CLIs, scripts, command runners, test + * harnesses, and other process-oriented programs that need standard input and + * output through Effect services. + * + * The underlying stdio streams are global resources owned by the Bun process. + * The layer keeps stdin open and does not end stdout or stderr by default, + * which avoids closing handles that prompts, loggers, or other code may still + * use. Stdio may be attached to a TTY, pipe, or redirected file, so + * terminal-specific behavior such as raw mode, echo, colors, cursor control, + * and terminal dimensions should be coordinated with terminal APIs rather than + * assumed from this layer. + * * @since 4.0.0 */ export * as BunStdio from "./BunStdio.ts" /** + * Bun stream interoperability for Effect streams. + * + * This module provides Bun-specific adapters for working with streaming data at + * the boundary between Bun APIs and Effect. It re-exports the shared Node stream + * adapters for Bun's Node-compatible stream APIs, and adds an optimized + * `ReadableStream` constructor that uses Bun's `readMany` support to pull + * batches of Web Stream values into an Effect `Stream`. + * + * Common uses include adapting Bun `Request` and `Response` bodies, multipart + * uploads, and other Web `ReadableStream` sources so they can be transformed, + * decoded, or piped with Effect stream operators. Pulling from the Effect stream + * drives reads from the underlying reader, while Bun and the Web Streams runtime + * still control their own internal buffering and source backpressure. + * + * Web `ReadableStream` readers take an exclusive lock on the source. Request and + * response bodies are also one-shot: once consumed they become disturbed and + * should not be read through another API. The adapter cancels the reader when + * the consuming scope is finalized by default; set `releaseLockOnEnd` when the + * stream is externally owned and should only have its lock released. Read errors + * are mapped through the provided `onError` function. + * * @since 4.0.0 */ export * as BunStream from "./BunStream.ts" /** + * Bun-backed implementation of Effect's `Terminal` service. + * + * This module provides a scoped, process-backed terminal for Bun programs by + * adapting the runtime's Node-compatible stdin, stdout, and `readline` support. + * It is useful for CLIs, prompts, REPLs, and terminal interfaces that need + * prompt output, line input, keypress input, or terminal dimensions. + * + * The service uses the current process streams, so acquire it with a scope or + * provide `layer` to ensure cleanup. When stdin is attached to a TTY, raw mode + * is enabled while the terminal is active and restored when the scope closes; + * this changes how keys are delivered and can affect other consumers of stdin. + * In pipes, redirected input, or CI, raw mode may be unavailable, keypress input + * is limited, and stdout dimensions may be reported as zero. + * * @since 4.0.0 */ export * as BunTerminal from "./BunTerminal.ts" /** + * Parent-side Bun support for Effect workers. + * + * This module provides the `WorkerPlatform` used by Bun programs that spawn + * and communicate with `globalThis.Worker` instances through Effect's worker + * protocol. Pair it with `BunWorkerRunner` in the worker entrypoint when + * building worker-backed RPC clients, moving CPU-bound work off the main + * thread, isolating Bun-only services, or hosting long-lived handlers behind a + * typed message boundary. + * + * The supplied spawner is responsible for creating the Bun worker for each + * numeric worker id. Messages follow Bun's worker cloning and transfer + * semantics, so payloads and transfer lists must be accepted by the Bun worker + * runtime. Calls to `send` are buffered until the worker runner posts its ready + * signal; if the worker entrypoint never starts `BunWorkerRunner`, those + * buffered messages will not be delivered. Scope finalization sends the Effect + * worker close signal, waits for Bun's `close` event for a short grace period, + * and then terminates the worker if graceful shutdown does not complete. + * * @since 4.0.0 */ export * as BunWorker from "./BunWorker.ts" /** + * Bun runtime support for Effect worker runners. + * + * This module is intended for code that is already executing inside a Bun + * `Worker`. It provides the `WorkerRunnerPlatform` used by `WorkerRunner` to + * receive request messages from the parent, run the registered Effect handler, + * and send responses back over Bun's worker `postMessage` channel. + * + * Use it with `BunWorker` when a Bun program needs to move RPC handlers, + * CPU-bound computations, or Bun-only services into an isolated worker while + * communicating through the Effect worker protocol. The runner must be started + * from the worker entrypoint, not the parent process; startup fails when the + * current global worker scope does not expose `postMessage`. Shutdown is driven + * by the parent protocol message, which closes the worker port, so long-running + * handlers should remain interruptible and keep resource cleanup in scopes. + * Messages follow Bun's worker cloning and transfer semantics, so payload + * schemas, transfer lists, `messageerror` events, and worker `error` events + * should be considered at the boundary. + * * @since 4.0.0 */ export * as BunWorkerRunner from "./BunWorkerRunner.ts" diff --git a/packages/platform-node-shared/src/NodeCrypto.ts b/packages/platform-node-shared/src/NodeCrypto.ts new file mode 100644 index 0000000000..211d29c259 --- /dev/null +++ b/packages/platform-node-shared/src/NodeCrypto.ts @@ -0,0 +1,55 @@ +/** + * Node.js implementation of the Crypto service. + * + * @since 1.0.0 + */ +import * as EffectCrypto from "effect/Crypto" +import * as Effect from "effect/Effect" +import * as Layer from "effect/Layer" +import * as PlatformError from "effect/PlatformError" +import * as NodeCrypto from "node:crypto" + +const toHashAlgorithm = (algorithm: EffectCrypto.DigestAlgorithm): string => { + switch (algorithm) { + case "SHA-1": + return "sha1" + case "SHA-256": + return "sha256" + case "SHA-384": + return "sha384" + case "SHA-512": + return "sha512" + } +} + +const digest: EffectCrypto.Crypto["digest"] = (algorithm, data) => + Effect.try({ + try: () => Uint8Array.from(NodeCrypto.createHash(toHashAlgorithm(algorithm)).update(data).digest()), + catch: (cause) => + PlatformError.systemError({ + module: "Crypto", + method: "digest", + _tag: "Unknown", + description: "Could not compute digest", + cause + }) + }) + +/** + * The default Node.js Crypto service implementation. + * + * @category constructors + * @since 1.0.0 + */ +export const make: EffectCrypto.Crypto = EffectCrypto.make({ + randomBytes: NodeCrypto.randomBytes, + digest +}) + +/** + * A layer that provides the Node.js Crypto service implementation. + * + * @category layers + * @since 1.0.0 + */ +export const layer: Layer.Layer = Layer.succeed(EffectCrypto.Crypto, make) diff --git a/packages/platform-node/src/NodeCrypto.ts b/packages/platform-node/src/NodeCrypto.ts new file mode 100644 index 0000000000..8bc23fc780 --- /dev/null +++ b/packages/platform-node/src/NodeCrypto.ts @@ -0,0 +1,16 @@ +/** + * Node.js platform Crypto service layer. + * + * @since 1.0.0 + */ +import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto" +import type * as Crypto from "effect/Crypto" +import type * as Layer from "effect/Layer" + +/** + * A layer that provides the Node.js Crypto service implementation. + * + * @category layers + * @since 1.0.0 + */ +export const layer: Layer.Layer = NodeCrypto.layer diff --git a/packages/platform-node/src/NodeServices.ts b/packages/platform-node/src/NodeServices.ts index 3986fa44a3..38ae971e6c 100644 --- a/packages/platform-node/src/NodeServices.ts +++ b/packages/platform-node/src/NodeServices.ts @@ -18,6 +18,7 @@ * * @since 4.0.0 */ +import type { Crypto } from "effect/Crypto" import type { FileSystem } from "effect/FileSystem" import * as Layer from "effect/Layer" import type { Path } from "effect/Path" @@ -25,6 +26,7 @@ import type { Stdio } from "effect/Stdio" import type { Terminal } from "effect/Terminal" import type { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner" import * as NodeChildProcessSpawner from "./NodeChildProcessSpawner.ts" +import * as NodeCrypto from "./NodeCrypto.ts" import * as NodeFileSystem from "./NodeFileSystem.ts" import * as NodePath from "./NodePath.ts" import * as NodeStdio from "./NodeStdio.ts" @@ -37,7 +39,7 @@ import * as NodeTerminal from "./NodeTerminal.ts" * @category models * @since 4.0.0 */ -export type NodeServices = ChildProcessSpawner | FileSystem | Path | Stdio | Terminal +export type NodeServices = ChildProcessSpawner | Crypto | FileSystem | Path | Stdio | Terminal /** * Provides the default Node implementations for child process spawning, @@ -50,6 +52,7 @@ export const layer: Layer.Layer = Layer.provideMerge( NodeChildProcessSpawner.layer, Layer.mergeAll( NodeFileSystem.layer, + NodeCrypto.layer, NodePath.layer, NodeStdio.layer, NodeTerminal.layer diff --git a/packages/platform-node/src/index.ts b/packages/platform-node/src/index.ts index 7762edf0d0..be0158dbfd 100644 --- a/packages/platform-node/src/index.ts +++ b/packages/platform-node/src/index.ts @@ -17,66 +17,340 @@ export * as Mime from "./Mime.ts" export * as NodeChildProcessSpawner from "./NodeChildProcessSpawner.ts" /** + * The `NodeClusterHttp` module provides the Node.js HTTP and WebSocket + * transports for Effect Cluster runners. It wires `HttpRunner` to the Node HTTP + * server, supplies Undici and WebSocket client protocols, and builds a complete + * sharding layer with serialization, runner health, runner storage, and message + * storage. + * + * **Common tasks** + * + * - Run a Node process as a cluster runner over HTTP or WebSocket with + * {@link layer} + * - Connect a client-only process to an existing HTTP cluster without starting + * a runner server + * - Use SQL-backed storage for durable multi-process clusters, `local` storage + * for short-lived development, or `byo` storage when the deployment owns the + * persistence boundary + * - Check runner health with protocol pings or Kubernetes pod readiness through + * {@link layerK8sHttpClient} + * + * **Gotchas** + * + * - `runnerAddress` is the host and port advertised to other runners; set + * `runnerListenAddress` when the local bind address differs from the + * externally reachable address + * - The HTTP and WebSocket transports serve runner RPCs at the default + * `HttpRunner` route, so proxies and load balancers must preserve the path + * and allow WebSocket upgrades when `transport` is `"websocket"` + * - `clientOnly` does not start an HTTP server or receive shard assignments + * - SQL storage is the default; `local` storage is in-memory/noop and `byo` + * requires the surrounding application to provide both runner and message + * storage services + * - Ping health checks use the selected transport and serialization, so route, + * port, proxy, or codec mismatches can make a runner appear unhealthy + * * @since 4.0.0 */ export * as NodeClusterHttp from "./NodeClusterHttp.ts" /** + * The `NodeClusterSocket` module provides the Node.js socket transport for + * Effect Cluster runners. It wires `SocketRunner` to Node TCP sockets, supplies + * RPC client and server protocol layers, and builds a complete sharding layer + * with serialization, runner health, runner storage, and message storage. + * + * **Common tasks** + * + * - Run a Node process as a cluster runner over raw TCP sockets with + * {@link layer} + * - Connect a client-only process to an existing socket cluster without + * starting a runner server + * - Use SQL-backed storage for durable multi-process clusters, `local` storage + * for short-lived development, or `byo` storage when the deployment owns the + * persistence boundary + * - Check runner health with socket pings or Kubernetes pod readiness through + * {@link layerK8sHttpClient} + * + * **Gotchas** + * + * - `runnerAddress` is the host and port advertised to other runners; set + * `runnerListenAddress` when the local bind address differs from the + * externally reachable address + * - The socket transport is point-to-point RPC, not cluster gossip: runner + * membership, shard ownership, and persisted delivery are coordinated through + * `RunnerStorage`, `MessageStorage`, and `RunnerHealth` + * - `clientOnly` does not start a socket server or receive shard assignments + * - Ping health checks use the same socket protocol, so unreachable ports, + * firewalls, or serialization mismatches can make a runner appear unhealthy + * * @since 4.0.0 */ export * as NodeClusterSocket from "./NodeClusterSocket.ts" /** + * Node.js platform Crypto service layer. + * + * @since 1.0.0 + */ +export * as NodeCrypto from "./NodeCrypto.ts" + +/** + * Provides the Node.js `FileSystem` layer for Effect programs. + * + * Use this module when a Node application, CLI, script, or test needs to + * satisfy the `FileSystem` service with real filesystem access for reading and + * writing files, creating directories and temporary files, inspecting metadata, + * managing links, or watching paths for changes. + * + * This module only exposes the Node-backed layer; filesystem operations are + * accessed through the `FileSystem` service from `effect/FileSystem`. Provide + * `NodeFileSystem.layer` at the edge of the program, or use + * `NodeServices.layer` when you also want the standard Node path, stdio, + * terminal, and child process services. The implementation is shared with + * other Node-compatible platform packages, so optional services such as + * `FileSystem.WatchBackend` are honored when present; otherwise file watching + * follows Node's `node:fs.watch` behavior. Paths are interpreted by Node, so + * relative paths use the current working directory and platform path rules. + * * @since 4.0.0 */ export * as NodeFileSystem from "./NodeFileSystem.ts" /** + * Node.js implementations of the Effect `HttpClient`. + * + * This module provides the Node-specific layers and constructors for sending + * Effect HTTP client requests. It re-exports the fetch-based client for + * programs that want to use `globalThis.fetch`, provides an Undici-backed + * client for applications that need Undici dispatcher control, and provides a + * lower-level `node:http` / `node:https` client for integrations that need + * native Node agent configuration. + * + * Use these clients in server-side applications, CLIs, tests, and integrations + * where requests should participate in Effect resource management, interruption, + * streaming, and typed transport / decode errors. The Undici path sends each + * request through the current `Dispatcher`; `layerUndici` owns a scoped + * `Agent`, while `dispatcherLayerGlobal` uses Undici's process-global dispatcher + * without destroying it. The `node:http` path uses separate scoped HTTP and + * HTTPS agents, making it the right choice when native agent options such as + * TLS, proxy, keep-alive, or socket behavior need to be configured directly. + * + * The backends are not completely interchangeable. Fetch, Undici, and + * `node:http` expose different agent and dispatcher hooks, body implementations, + * abort behavior, upgrade support, and response body readers. This module + * converts Effect request bodies to the selected runtime representation: + * streams remain streaming, `FormData` may contribute generated content headers, + * and body read failures are reported as `HttpClientError` decode or transport + * errors. + * * @since 4.0.0 */ export * as NodeHttpClient from "./NodeHttpClient.ts" /** + * Utilities for adapting Node `http.IncomingMessage` values to the Effect HTTP + * incoming message interface used by the platform Node server and client + * implementations. + * + * This module is useful when code needs to keep access to Node's request or + * response object while also exposing Effect's typed headers, remote address, + * body decoders, and stream interface. The body helpers consume Node's readable + * stream, cache decoded text and array-buffer results, and honor the + * `HttpIncomingMessage.MaxBodySize` fiber ref. Prefer a single body access + * strategy per message: raw `stream` access is not cached, and Node request + * bodies cannot be replayed once the underlying stream has been consumed. + * * @since 4.0.0 */ export * as NodeHttpIncomingMessage from "./NodeHttpIncomingMessage.ts" /** + * Node.js implementation of the Effect HTTP platform service. + * + * This module connects the portable `HttpPlatform` file response helpers to + * Node runtime primitives. It is used by Node HTTP servers and static file + * handlers when returning local files, public assets, downloads, byte ranges, + * or Web `File` values as `HttpServerResponse` bodies. + * + * Path-based responses are served with `node:fs.createReadStream`; Web `File` + * responses are bridged with `Readable.fromWeb`. The implementation fills in + * `content-type` from `Mime`, falls back to `application/octet-stream`, and + * writes the `content-length` for the selected range or whole file. Node's + * stream `end` option is inclusive, so the platform converts Effect's half-open + * range before reading. Empty bodies use an empty readable stream. + * + * Provide `layer` at the Node runtime edge when file responses, static serving, + * or response bodies created from files need real filesystem and ETag support. + * These responses are raw Node streams, so they are intended for the Node HTTP + * server adapter; keep files available until the response body has been + * consumed and prefer the portable `HttpServerResponse` constructors when a + * response does not depend on Node file or stream behavior. + * * @since 4.0.0 */ export * as NodeHttpPlatform from "./NodeHttpPlatform.ts" /** + * Node.js implementation of the Effect `HttpServer`. + * + * This module adapts a supplied Node `http.Server` into Effect's + * platform-independent HTTP server service. It starts the server with Node + * `listen` options, converts `request` events into `HttpServerRequest` values, + * writes `HttpServerResponse` bodies through Node's `ServerResponse`, and + * handles `upgrade` events by exposing the upgraded socket through + * `HttpServerRequest.upgrade`. + * + * Common use cases include serving an Effect HTTP application with {@link layer} + * or {@link layerConfig}, embedding request or upgrade handlers into an + * existing Node server with {@link makeHandler} and {@link makeUpgradeHandler}, + * and using {@link layerTest} for integration tests that need an ephemeral + * listening port and a client pointed at it. + * + * Listen options are passed directly to Node, so host, port, backlog, and Unix + * socket path behavior follow `node:http`. The server begins listening when the + * `HttpServer` is acquired, and handlers are installed when `serve` is run. + * Request fibers are interrupted with `ClientAbort` when the client disconnects + * before a response finishes. WebSocket support only applies to Node `upgrade` + * requests, and ordinary HTTP requests fail if their application attempts to use + * `HttpServerRequest.upgrade`. + * + * Scope ownership is important: the server is closed when the acquiring scope + * finalizes, while each `serve` call installs its own request and upgrade + * listeners and removes them on finalization. Unless preemptive shutdown is + * disabled, finalizing a serve scope also starts a graceful server close, using + * the configured timeout or the default timeout. + * * @since 4.0.0 */ export * as NodeHttpServer from "./NodeHttpServer.ts" /** + * Accessors for the Node.js objects backing a platform Node + * `HttpServerRequest`. + * + * Use this module at interop boundaries when an Effect HTTP handler needs the + * original `http.IncomingMessage` or `http.ServerResponse` for APIs that are + * specific to Node, such as existing middleware, socket inspection, raw stream + * piping, or response customization that cannot be expressed with the portable + * `HttpServerRequest` and `HttpServerResponse` interfaces. + * + * The returned request is the original Node request supplied to the server. It + * does not reflect Effect request overrides made by middleware, such as a + * rewritten URL, adjusted headers, or a substituted remote address. Its body is + * also Node's one-shot readable stream, so avoid mixing raw stream consumption + * with Effect body, multipart, or stream helpers unless ownership of the body + * is clear. The returned response is the Node response owned by the platform + * server; writing to it directly bypasses the usual Effect response writer and + * must be coordinated carefully to avoid duplicate writes. Upgrade requests may + * create that response lazily when it is first requested. + * * @since 4.0.0 */ export * as NodeHttpServerRequest from "./NodeHttpServerRequest.ts" /** + * Node-specific helpers for parsing HTTP `multipart/form-data` request bodies. + * + * This module adapts a Node `Readable` request body plus its incoming headers + * into the shared `Multipart` model. Use `stream` when an HTTP server route + * wants to handle form fields and uploaded files incrementally, for example API + * endpoints that validate text fields while piping file parts to storage. Use + * `persisted` when the whole form should be collected into a record and uploaded + * files should be written into scoped temporary files through the current + * `FileSystem` and `Path` services. + * + * Node request bodies are one-shot streams, so consume either `stream` or + * `persisted`, and make sure file parts are drained, piped, or otherwise + * deliberately handled. `contentEffect` loads a file into memory and should be + * reserved for small uploads. Persisted paths live only for the surrounding + * `Scope`, and filenames supplied by clients should be treated as metadata, not + * trusted filesystem paths. + * * @since 4.0.0 */ export * as NodeMultipart from "./NodeMultipart.ts" /** + * Node.js layers for Effect's `Path` service. + * + * Use this module when an Effect program running on Node needs path operations + * from the `Path` service, such as joining and normalizing filesystem + * locations, resolving configuration or static asset paths, working with CLI + * path arguments, or converting between file paths and `file:` URLs. + * + * `layer` follows the host platform's `node:path` semantics. Use `layerPosix` + * or `layerWin32` when code needs stable POSIX or Windows behavior regardless + * of the operating system. These layers provide only path manipulation; they do + * not read the filesystem or validate that paths exist. `NodeServices.layer` + * already includes the default Node path layer, so provide this module directly + * when you want the narrower service or one of the platform-specific variants. + * * @since 4.0.0 */ export * as NodePath from "./NodePath.ts" /** + * Node.js Redis integration backed by `ioredis`. + * + * This module provides scoped layers that create an `ioredis` client and expose + * both the low-level `Redis` service used by Effect persistence modules and the + * `NodeRedis` service for direct access to the underlying client. It is useful + * for Node applications that want Redis-backed persistence, persisted queues, + * distributed rate limiting, or custom Redis commands alongside the Effect + * services that build on Redis. + * + * The client is acquired when the layer is built and closed with `quit` when + * the layer scope ends, so install the layer at the lifetime you want for the + * connection and pass `ioredis` options, or `layerConfig`, for connection, + * TLS, database, retry, and reconnect settings. Persistence and rate limiter + * stores build their own keys and Lua scripts on top of this service; choose + * stable prefixes and store ids to avoid collisions, account for persisted + * values that may fail to decode after schema changes, and avoid unbounded + * high-cardinality rate-limit keys unless you have a cleanup or bounding + * strategy. + * * @since 4.0.0 */ export * as NodeRedis from "./NodeRedis.ts" /** + * Node.js entry-point helpers for running Effect programs. + * + * This module exposes `runMain`, the Node runtime launcher used at the edge of + * CLI tools, scripts, servers, and worker processes. It runs an already + * self-contained Effect as the process main program, with built-in error + * reporting and Node signal handling. + * + * `NodeRuntime` does not provide application services by itself. Provide any + * required layers, such as `NodeServices.layer` or narrower service-specific + * layers, before passing the effect to `runMain`. On `SIGINT` or `SIGTERM`, + * the main fiber is interrupted so scoped resources and finalizers can shut + * down; keep long-running work attached to that scope and avoid finalizers that + * never complete, otherwise process shutdown can be delayed. + * * @since 4.0.0 */ export * as NodeRuntime from "./NodeRuntime.ts" /** + * Provides the aggregate Node platform services layer for applications that run + * on the Node.js runtime. + * + * This module is useful when an application needs the standard Node-backed + * implementations of filesystem access, path operations, stdio, terminal + * interaction, and child process spawning from a single layer. Provide + * `NodeServices.layer` near the edge of a program to satisfy effects that read + * or write files, resolve paths, interact with stdin/stdout/stderr or a + * terminal, or launch subprocesses. + * + * The layer only supplies the runtime services listed by `NodeServices`; it does + * not provide unrelated platform services such as HTTP clients or servers. + * Libraries should continue to depend on the individual service tags they use, + * while applications, CLIs, and tests can choose this layer or narrower + * service-specific layers depending on how much of the Node runtime they want to + * expose. + * * @since 4.0.0 */ export * as NodeServices from "./NodeServices.ts" @@ -87,6 +361,23 @@ export * as NodeServices from "./NodeServices.ts" export * as NodeSink from "./NodeSink.ts" /** + * Node platform socket entry point for Effect sockets backed by Node streams + * and WebSocket implementations. + * + * This module re-exports the shared Node socket constructors for TCP clients, + * Unix domain socket clients, and adapters from existing Node `Duplex` streams, + * then adds Node-specific WebSocket constructor layers. Use it when connecting + * to raw socket protocols, wiring RPC transports over TCP or Unix sockets, or + * opening WebSocket clients in Node. + * + * TCP and Unix socket behavior comes from the shared Node layer: Unix sockets + * are selected with `NetConnectOpts.path`, scoped sockets close or destroy the + * underlying stream on finalization, and Node open, read, write, and close + * events are translated into `SocketError` values. For WebSockets, + * `layerWebSocketConstructor` prefers `globalThis.WebSocket` when available + * and falls back to `ws`; use `layerWebSocketConstructorWS` when you need the + * `ws` implementation consistently across Node versions. + * * @since 4.0.0 */ export * as NodeSocket from "./NodeSocket.ts" @@ -97,6 +388,22 @@ export * as NodeSocket from "./NodeSocket.ts" export * as NodeSocketServer from "./NodeSocketServer.ts" /** + * Node.js implementation of the Effect `Stdio` service. + * + * This module exposes a layer that connects `Stdio` to the current process: + * command-line arguments come from `process.argv`, input is read from + * `process.stdin`, and output and error output write to `process.stdout` and + * `process.stderr`. It is intended for CLIs, scripts, command runners, and + * other process-oriented programs that need standard input and output through + * Effect services. + * + * The underlying streams are owned by the Node process. The layer keeps stdin + * open and does not end stdout or stderr when a stream finishes, which avoids + * closing global process handles that other code may still use. Be mindful that + * stdio may be a pipe, file, or TTY, so terminal-specific behavior such as raw + * mode, echo, colors, and cursor control should be handled with the terminal + * APIs instead of assuming an interactive console. + * * @since 4.0.0 */ export * as NodeStdio from "./NodeStdio.ts" @@ -107,16 +414,62 @@ export * as NodeStdio from "./NodeStdio.ts" export * as NodeStream from "./NodeStream.ts" /** + * Provides the Node.js `Terminal` service for interactive command-line + * programs, prompts, and tools that need to read lines, react to key presses, + * write to stdout, or inspect terminal dimensions. + * + * The implementation is backed by the current process' stdin and stdout. When + * stdin is a TTY, key input temporarily enables raw mode for the scope of the + * service, so callers should acquire it with a scope or use the provided layer + * to ensure terminal state is restored. In non-TTY environments, terminal + * dimensions may be reported as zero and raw-mode key handling is unavailable. + * * @since 4.0.0 */ export * as NodeTerminal from "./NodeTerminal.ts" /** + * Parent-side Node.js support for Effect workers. + * + * This module provides the `WorkerPlatform` used by Node programs that spawn + * and communicate with `node:worker_threads` workers or IPC-enabled child + * processes through Effect's worker protocol. Pair it with `NodeWorkerRunner` + * in the worker entrypoint when building worker-backed RPC clients, offloading + * CPU-bound work, isolating Node resources, or hosting services that should + * exchange typed messages with the parent process. + * + * Worker-thread spawners can use `postMessage` transfer lists for values such + * as `ArrayBuffer` and `MessagePort`, but transferring moves ownership and + * invalid transfer lists surface as worker send or receive failures. + * Child-process spawners must provide an IPC channel, for example via + * `child_process.fork` or `stdio: "ipc"`; their messages use Node IPC + * serialization and this module does not forward transfer lists to + * `ChildProcess.send`. Scope finalization sends the worker close signal and + * waits for exit before falling back to `terminate()` or `SIGKILL`. + * * @since 4.0.0 */ export * as NodeWorker from "./NodeWorker.ts" /** + * Runtime support for Effect workers that are executed by Node.js. + * + * This module is intended to be installed in the program running inside a + * `node:worker_threads` worker or an IPC-enabled child process. It provides the + * `WorkerRunnerPlatform` used by `WorkerRunner` to receive request messages + * from the parent, run the registered Effect handler, and send responses back + * over the parent channel. + * + * Use it when the parent side is created with `NodeWorker` and the worker code + * needs to perform CPU-bound work, isolate Node resources, or host services that + * should communicate through the Effect worker protocol. The runner must be + * started from an actual worker context: `parentPort` is required for worker + * threads, while child processes must be spawned with an IPC channel so + * `process.send` is available. Transfer lists only apply to worker-thread + * `postMessage`; child-process messages go through Node IPC serialization. + * Shutdown is coordinated by the parent message protocol, so long-running + * handlers should remain interruptible and keep resource cleanup in scopes. + * * @since 4.0.0 */ export * as NodeWorkerRunner from "./NodeWorkerRunner.ts" diff --git a/packages/platform-node/test/NodeCrypto.test.ts b/packages/platform-node/test/NodeCrypto.test.ts new file mode 100644 index 0000000000..b83ba7606f --- /dev/null +++ b/packages/platform-node/test/NodeCrypto.test.ts @@ -0,0 +1,59 @@ +import * as NodeCrypto from "@effect/platform-node/NodeCrypto" +import { assert, describe, it } from "@effect/vitest" +import * as Crypto from "effect/Crypto" +import * as Effect from "effect/Effect" +import * as TestClock from "effect/testing/TestClock" + +const uuidV4Regex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/ +const uuidV7Regex = /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/ + +const hex = (bytes: Uint8Array): string => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("") + +describe("NodeCrypto", () => { + it.effect("generates empty random bytes", () => + Effect.gen(function*() { + const crypto = yield* Crypto.Crypto + const bytes = yield* crypto.randomBytes(0) + assert.deepStrictEqual(bytes, new Uint8Array(0)) + }).pipe(Effect.provide(NodeCrypto.layer))) + + it.effect("generates random bytes with the requested size", () => + Effect.gen(function*() { + const crypto = yield* Crypto.Crypto + const bytes = yield* crypto.randomBytes(32) + assert.strictEqual(bytes.length, 32) + }).pipe(Effect.provide(NodeCrypto.layer))) + + it.effect("fails invalid random byte sizes", () => + Effect.gen(function*() { + const crypto = yield* Crypto.Crypto + const error = yield* Effect.flip(crypto.randomBytes(-1)) + assert.strictEqual(error._tag, "PlatformError") + }).pipe(Effect.provide(NodeCrypto.layer))) + + it.effect("generates UUIDv4 values", () => + Effect.gen(function*() { + const crypto = yield* Crypto.Crypto + const uuid1 = yield* crypto.randomUUIDv4 + const uuid2 = yield* crypto.randomUUIDv4 + assert.match(uuid1, uuidV4Regex) + assert.match(uuid2, uuidV4Regex) + assert.notStrictEqual(uuid1, uuid2) + }).pipe(Effect.provide(NodeCrypto.layer))) + + it.effect("generates UUIDv7 values", () => + Effect.gen(function*() { + yield* TestClock.setTime(0x0123456789ab) + const crypto = yield* Crypto.Crypto + const uuid = yield* crypto.randomUUIDv7 + assert.match(uuid, uuidV7Regex) + assert.strictEqual(uuid.slice(0, 13), "01234567-89ab") + }).pipe(Effect.provide(NodeCrypto.layer))) + + it.effect("computes SHA-256 digests", () => + Effect.gen(function*() { + const crypto = yield* Crypto.Crypto + const digest = yield* crypto.digest("SHA-256", new TextEncoder().encode("hello")) + assert.strictEqual(hex(digest), "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824") + }).pipe(Effect.provide(NodeCrypto.layer))) +}) diff --git a/packages/sql/clickhouse/src/index.ts b/packages/sql/clickhouse/src/index.ts index 63331e891a..08c22d53a8 100644 --- a/packages/sql/clickhouse/src/index.ts +++ b/packages/sql/clickhouse/src/index.ts @@ -5,11 +5,48 @@ // @barrel: Auto-generated exports. Do not edit manually. /** + * ClickHouse client implementation for Effect SQL, backed by + * `@clickhouse/client`. + * + * This module exposes constructors and layers for providing both the + * ClickHouse-specific `ClickhouseClient` service and the generic `SqlClient` + * service. It is intended for analytical application queries, migrations, + * background jobs, bulk inserts, and streaming reads that need Effect SQL query + * compilation, scoped lifecycle management, interruption, and consistent + * `SqlError` classification for ClickHouse failures. + * + * The client uses the ClickHouse HTTP client APIs for `query`, `command`, and + * `insert` operations. Regular queries read JSON result sets, `executeValues` + * requests `JSONCompact`, streams request `JSONEachRow`, and `insertQuery` + * defaults inserts to `JSONEachRow`. Interrupting an operation aborts the + * underlying HTTP request and attempts to kill the generated or supplied + * `query_id`. The statement compiler emits ClickHouse typed placeholders such + * as `{p1: Type}`; use `param` when the inferred type is too broad, and write + * ClickHouse-specific clauses such as engines, `SETTINGS`, `FORMAT`, or + * cluster directives explicitly. + * * @since 4.0.0 */ export * as ClickhouseClient from "./ClickhouseClient.ts" /** + * Utilities for applying Effect SQL migrations to ClickHouse databases. + * + * This module re-exports the shared `Migrator` loaders and error types, then + * provides `run` and `layer` helpers for applying ordered migrations through + * the current ClickHouse `SqlClient`. It is typically used during application + * startup, deployment, or integration tests that need to prepare analytical + * tables before dependent services begin reading or writing data. + * + * Applied migrations are stored in `effect_sql_migrations` by default and use + * the shared `_` loader convention. Only migrations with ids greater + * than the latest recorded id are run. ClickHouse schema changes often depend + * on engine, `ORDER BY`, database, and cluster settings, and many deployments + * rely on explicit `ON CLUSTER` clauses or coordinated rollout tooling. This + * adapter does not add a ClickHouse-specific table lock or schema dumper, so + * coordinate concurrent migrators and do not expect `schemaDirectory` to emit a + * ClickHouse schema snapshot. + * * @since 4.0.0 */ export * as ClickhouseMigrator from "./ClickhouseMigrator.ts" diff --git a/packages/sql/d1/src/index.ts b/packages/sql/d1/src/index.ts index 44a87caffa..1319dccd7f 100644 --- a/packages/sql/d1/src/index.ts +++ b/packages/sql/d1/src/index.ts @@ -5,6 +5,27 @@ // @barrel: Auto-generated exports. Do not edit manually. /** + * Cloudflare D1 client implementation for Effect SQL, backed by a Workers `D1Database` binding. + * + * This module adapts a Cloudflare D1 database binding into both the + * D1-specific `D1Client` service and the generic Effect `SqlClient` service. + * Use it in Workers, Pages Functions, and tests that provide a D1 binding to + * run SQLite-compatible queries through Effect services and layers, including + * repositories, migrations, request handlers, and local development against + * Wrangler or Miniflare-backed D1 databases. + * + * The client prepares statements with D1, caches them by SQL string, and uses + * the SQLite statement compiler for query and result name transforms. D1 + * commits individual statements automatically, and native `D1Database.batch` + * is the D1 API for sequential, transactional multi-statement work; this + * adapter does not expose Effect SQL transactions because it cannot map + * `withTransaction` onto a connection-scoped D1 transaction. D1 databases are + * serverless SQLite storage with platform limits and single-database serialized + * execution, so keep queries small and indexed, batch large maintenance work + * at the D1 API level, and use D1 sessions outside this client when an + * application needs bookmark-based sequential consistency or read replicas. + * Streaming queries and `updateValues` are not supported. + * * @since 4.0.0 */ export * as D1Client from "./D1Client.ts" diff --git a/packages/sql/libsql/src/index.ts b/packages/sql/libsql/src/index.ts index f61e5c3146..4245a260bf 100644 --- a/packages/sql/libsql/src/index.ts +++ b/packages/sql/libsql/src/index.ts @@ -5,11 +5,48 @@ // @barrel: Auto-generated exports. Do not edit manually. /** + * libSQL client implementation for Effect SQL, backed by `@libsql/client`. + * + * This module creates or wraps a libSQL SDK client and exposes it as both the + * libSQL-specific `LibsqlClient` service and the generic Effect `SqlClient`. + * Use it for Turso-hosted libSQL databases, local `file:` databases, embedded + * replicas configured with `syncUrl`, migrations, tests, and application code + * that wants SQLite-compatible SQL through Effect services and layers. + * + * When connection options are supplied the SDK client is scoped and closed by + * the layer; when `liveClient` is supplied ownership stays with the caller. + * Top-level `withTransaction` blocks open a libSQL write transaction, nested + * transactions use SQLite savepoints, and only statements run through the same + * Effect client participate in that transaction. Keep Turso or remote libSQL + * transactions short, because the transaction holds the client reservation + * until commit or rollback; direct SDK calls made outside this service are not + * coordinated with Effect SQL transactions. Row streaming is not implemented. + * * @since 4.0.0 */ export * as LibsqlClient from "./LibsqlClient.ts" /** + * Utilities for applying Effect SQL migrations to libSQL and Turso databases. + * + * This module re-exports the shared `Migrator` loaders and error types, then + * provides `run` and `layer` helpers for applying ordered migrations through the + * current libSQL-backed `SqlClient`. It is typically used at application + * startup, in deployment or setup scripts for Turso databases, in tests that + * create temporary `file:` databases, or in layer graphs that must ensure the + * schema exists before dependent services are acquired. + * + * Migrations are recorded in `effect_sql_migrations` by default and are loaded + * using the shared `_` file or record-key convention. Because libSQL + * uses SQLite-compatible SQL, migrations should avoid dialect features that are + * not supported by libSQL or the configured Turso deployment. Remote Turso + * databases, local `file:` databases, and embedded replicas can each observe + * different state until replication has caught up, so run schema-changing + * migrations against the intended writer and wait for replicas to sync before + * serving code that depends on the new schema. Concurrent migrators rely on the + * migrations table primary key to detect races, and this adapter does not + * currently write schema dumps for `schemaDirectory`. + * * @since 4.0.0 */ export * as LibsqlMigrator from "./LibsqlMigrator.ts" diff --git a/packages/sql/mssql/src/index.ts b/packages/sql/mssql/src/index.ts index fdec623a7a..8fe885cb1f 100644 --- a/packages/sql/mssql/src/index.ts +++ b/packages/sql/mssql/src/index.ts @@ -12,21 +12,105 @@ export { // @barrel: Auto-generated exports. Do not edit manually. /** + * Microsoft SQL Server client implementation for Effect SQL, backed by the + * `tedious` driver. + * + * This module provides the `MssqlClient` service and layers that also satisfy + * the generic `SqlClient` service. It is intended for server applications, + * background workers, migrations, and tests that need SQL Server query + * compilation, Tedious parameter typing, scoped connection management, + * transactions, and typed stored procedure calls. + * + * Clients own a scoped pool of Tedious connections and validate startup with + * `SELECT 1`. Regular queries borrow a pooled connection per operation, while + * transactions keep one pooled connection for their lifetime and use SQL Server + * savepoints for nested transactions. Long-running transactions therefore + * reduce available pool capacity; size `maxConnections`, `connectionTTL`, and + * `connectTimeout` accordingly. + * + * Tedious permits one active request per connection. This client compiles + * statements with named `@1`-style parameters, maps Effect SQL primitive values + * to Tedious `DataType`s unless `param` is used, and does not implement + * streaming queries. Be deliberate about TLS options: `encrypt` defaults to + * `false` and `trustServerCertificate` defaults to `true` unless overridden. + * Stored procedure calls go through `callProcedure`; define input and output + * parameters with the `Procedure` and `Parameter` helpers so Tedious receives + * the correct data types and output values can be collected from `returnValue` + * events. + * * @since 4.0.0 */ export * as MssqlClient from "./MssqlClient.ts" /** + * Utilities for applying Effect SQL migrations to Microsoft SQL Server. + * + * This module re-exports the shared `Migrator` loaders and error types, then + * provides `run` and `layer` helpers for applying ordered migrations through + * the current SQL Server `SqlClient`. It is typically used at application + * startup, during deployment, in integration tests that provision temporary SQL + * Server databases, or in layer graphs that need the database schema to be + * current before dependent services are acquired. + * + * Applied migrations are stored in `effect_sql_migrations` by default and use + * the shared `_` loader convention. Only migrations with ids greater + * than the latest recorded id are run, so avoid inserting older migration ids + * after a later migration has reached production. SQL Server migrations run + * inside the shared migrator transaction and this adapter does not add a + * dialect-specific table lock, so coordinate concurrent startup runners and + * write T-SQL that is valid inside an explicit transaction. Remember that `GO` + * is a client-side batch separator rather than a T-SQL statement, and split + * migrations when SQL Server requires an object definition such as `CREATE + * VIEW`, `CREATE PROCEDURE`, or `CREATE TRIGGER` to start its own batch. This + * adapter also does not emit SQL Server schema dumps for `schemaDirectory`. + * * @since 4.0.0 */ export * as MssqlMigrator from "./MssqlMigrator.ts" /** + * Typed metadata for SQL Server stored procedure parameters. + * + * This module records the bare parameter name, Tedious `DataType`, Tedious + * `ParameterOptions`, and phantom TypeScript value type used by + * `Procedure.param` and `Procedure.outputParam`. `MssqlClient.call` later + * forwards input parameters to Tedious with `Request.addParameter` and output + * parameters with `Request.addOutputParameter`, so names should match the + * stored procedure parameter name without a leading `@`. + * + * Use these values when defining stored procedures that need explicit SQL + * Server parameter metadata, such as sized strings or binary values, decimal + * precision/scale, table-valued parameters, and output parameters. The generic + * type parameter is only a compile-time guide for the value record accepted by + * `Procedure.compile`; Tedious still validates and encodes the runtime value. + * In particular, TVP values must use Tedious' table shape with `name`, + * optional `schema`, `columns`, and `rows`, and output parameters are registered + * with no initial value, so SQL Server input-output parameters need separate + * care rather than assuming an output parameter is populated from compiled + * input values. + * * @since 4.0.0 */ export * as Parameter from "./Parameter.ts" /** + * Typed builders for Microsoft SQL Server stored procedure definitions. + * + * This module describes the metadata consumed by `MssqlClient.call`: create a + * definition with `make`, add input and output parameters with their Tedious + * data types and `ParameterOptions`, optionally describe the returned row shape + * with `withRows`, and use `compile` to bind the input values before execution. + * It is useful when application code calls stored procedures for commands, + * reports, migrations, or workflows that return both result sets and output + * parameters. + * + * Parameter value types are supplied explicitly through `param()` and + * `outputParam()`; they are not inferred from the Tedious data type. Input + * values must be keyed by the parameter names in the definition, output + * parameters are collected separately from returned rows, and `withRows` only + * records the expected TypeScript row type, so row names and transforms still + * follow the configured `MssqlClient` result handling. + * * @since 4.0.0 */ export * as Procedure from "./Procedure.ts" diff --git a/packages/sql/mysql2/src/index.ts b/packages/sql/mysql2/src/index.ts index a49cb4eca3..4a5155d8f2 100644 --- a/packages/sql/mysql2/src/index.ts +++ b/packages/sql/mysql2/src/index.ts @@ -5,11 +5,46 @@ // @barrel: Auto-generated exports. Do not edit manually. /** + * MySQL client implementation for Effect SQL, backed by the `mysql2` driver. + * + * This module exposes constructors and layers for providing both the MySQL-specific + * `MysqlClient` service and the generic `SqlClient` service. It is intended for server + * applications, background workers, migrations, and tests that need Effect SQL query + * compilation, scoped resource management, streaming queries, and consistent `SqlError` + * classification for MySQL driver failures. + * + * Each client owns a scoped mysql2 pool, validates connectivity with `SELECT 1` during + * acquisition, and closes the pool when the surrounding scope is released. You can configure + * the pool from a connection URI or discrete connection fields; when `url` is supplied it + * takes precedence over the host, port, database, username, and password fields. Regular + * queries run through the shared pool, while transactions acquire a dedicated pooled + * connection for their lifetime, so long-running transactions and streams can occupy pool + * capacity. Size `maxConnections`, `connectionTTL`, and any mysql2 `poolConfig` with that in + * mind. + * * @since 4.0.0 */ export * as MysqlClient from "./MysqlClient.ts" /** + * Utilities for applying Effect SQL migrations to MySQL databases through the + * mysql2-backed `SqlClient`. + * + * This module re-exports the shared `Migrator` loaders and error types, then + * provides `run` and `layer` helpers for applying ordered migrations using the + * currently configured MySQL `SqlClient`. It is commonly used during application + * startup, in integration tests that provision a temporary schema, or in layer + * graphs where dependent services should not start until the database schema is + * current. + * + * Applied migrations are stored in `effect_sql_migrations` by default and use + * the shared `_` loader convention. Only migrations with ids greater + * than the latest recorded id are run. MySQL DDL can cause implicit commits, and + * this adapter relies on migration table constraints to detect concurrent + * runners, so coordinate startup runners and write migrations to tolerate + * MySQL's transactional semantics. Schema dump support is not enabled in this + * adapter, so `schemaDirectory` does not emit a MySQL dump. + * * @since 4.0.0 */ export * as MysqlMigrator from "./MysqlMigrator.ts" diff --git a/packages/sql/pg/src/index.ts b/packages/sql/pg/src/index.ts index 5a556221d3..f46dda4ba8 100644 --- a/packages/sql/pg/src/index.ts +++ b/packages/sql/pg/src/index.ts @@ -5,11 +5,48 @@ // @barrel: Auto-generated exports. Do not edit manually. /** + * PostgreSQL client implementation for Effect SQL, backed by `pg`. + * + * This module exposes constructors for creating a scoped `PgClient` from a + * managed `pg` pool, a single managed `pg` client, or lower-level connection + * acquirers. The resulting service can be provided as both `PgClient` and the + * generic `SqlClient`, and is intended for application database access, + * migrations, transactional workflows, row streaming, JSON parameters, and + * PostgreSQL LISTEN/NOTIFY integration. + * + * Pool-backed clients acquire connections per operation and reserve dedicated + * connections for transactions and cursor streams. Clients built from one + * `pg.Client` serialize shared access; enable `acquireForStream` when streams + * or listeners need their own client instead of sharing the query connection. + * LISTEN uses a scoped long-lived client and automatically issues `UNLISTEN` + * when the stream scope closes, so listeners should be scoped for as long as + * notifications are needed. + * * @since 4.0.0 */ export * as PgClient from "./PgClient.ts" /** + * Utilities for applying Effect SQL migrations to PostgreSQL databases. + * + * This module re-exports the shared `Migrator` loaders and error types, then + * provides `run` and `layer` helpers for applying ordered migrations through + * the current PostgreSQL `SqlClient` and `PgClient`. It is typically used at + * application startup, during deployment, in integration tests that provision a + * temporary PostgreSQL database, or in layer graphs that must prepare the + * schema before dependent services are acquired. + * + * Migrations are recorded in `effect_sql_migrations` by default and are loaded + * using the shared `_` file or record-key convention. Only migrations + * with an id greater than the latest recorded id are applied, so concurrent + * application instances should coordinate startup against the same database and + * avoid racing to install the same changes. When `schemaDirectory` is enabled, + * this adapter shells out to `pg_dump` using the active `PgClient` + * configuration, so `pg_dump` must be available on `PATH` and the layer must + * provide child process, filesystem, and path services. The generated dumps + * intentionally strip comments, session settings, ownership, and privilege + * statements to keep schema snapshots portable across PostgreSQL environments. + * * @since 4.0.0 */ export * as PgMigrator from "./PgMigrator.ts" diff --git a/packages/sql/pglite/src/index.ts b/packages/sql/pglite/src/index.ts index aae0bf02cc..0bb08f27ee 100644 --- a/packages/sql/pglite/src/index.ts +++ b/packages/sql/pglite/src/index.ts @@ -5,11 +5,50 @@ // @barrel: Auto-generated exports. Do not edit manually. /** + * Embedded PostgreSQL client implementation for Effect SQL, backed by + * `@electric-sql/pglite`. + * + * This module exposes constructors and layers for providing a `PgliteClient` + * as both the PGlite-specific service and the generic `SqlClient`. It can + * create a scoped `PGlite` instance from constructor options or wrap a + * caller-owned `liveClient`, making it useful for local-first browser storage, + * web worker databases, tests, demos, migrations, and development tools that + * want PostgreSQL syntax without connecting to a separate PostgreSQL server. + * + * The client uses the PostgreSQL statement compiler and adds PGlite-specific + * access to the underlying instance, JSON fragments, LISTEN/NOTIFY streams, + * data directory dumps, and array type refresh. Because PGlite is embedded in + * the current JavaScript runtime, operations share the supplied instance and + * are serialized by this client; a `liveClient` remains caller-owned and is not + * closed by the layer. In browsers or workers, persistence, durability, + * extension availability, and lifecycle all follow the selected PGlite + * `dataDir`/runtime rather than a hosted PostgreSQL process. + * * @since 4.0.0 */ export * as PgliteClient from "./PgliteClient.ts" /** + * Utilities for applying Effect SQL migrations to embedded PGlite databases. + * + * This module re-exports the shared `Migrator` loaders and error types, then + * provides `run` and `layer` helpers for applying ordered migrations through the + * current PGlite-backed `SqlClient`. It is typically used to bootstrap schemas + * for local-first applications, browser or Node.js integration tests, examples, + * and layer graphs that need an embedded PostgreSQL-compatible database to be + * ready before dependent services start. + * + * Migrations are recorded in `effect_sql_migrations` by default and are loaded + * using the shared `_` file or record-key convention. PGlite uses + * PostgreSQL semantics inside the embedded database, so migrations run in a + * transaction and the shared migrator uses PostgreSQL table locking to avoid + * concurrent runners. Coordinate every process or layer using the same + * `liveClient` or `dataDir`, and remember that in-memory PGlite clients start + * with no recorded migrations. This adapter does not currently write schema + * dumps for `schemaDirectory`; use PGlite data-directory persistence or + * `PgliteClient.dumpDataDir` when a portable embedded database snapshot is + * needed. + * * @since 4.0.0 */ export * as PgliteMigrator from "./PgliteMigrator.ts" diff --git a/packages/sql/sqlite-bun/src/index.ts b/packages/sql/sqlite-bun/src/index.ts index 986ce8399a..bfc6dd4e65 100644 --- a/packages/sql/sqlite-bun/src/index.ts +++ b/packages/sql/sqlite-bun/src/index.ts @@ -5,11 +5,48 @@ // @barrel: Auto-generated exports. Do not edit manually. /** + * Bun SQLite client implementation for Effect SQL, backed by `bun:sqlite`. + * + * This module provides constructors and layers for using a Bun-managed SQLite database as both the + * SQLite-specific `SqliteClient` service and the generic `SqlClient` service. It is intended for + * file-backed or in-memory databases in Bun applications, local development tools, migrations, + * integration tests, and embedded persistence use cases that need Effect SQL query compilation plus + * SQLite-specific helpers such as database export and native extension loading. + * + * Each client owns one scoped `bun:sqlite` `Database` handle and serializes access through it, which + * is important because Bun executes SQLite statements synchronously. WAL mode is enabled by default, + * so set `disableWAL` when opening read-only databases or when the database file or directory cannot + * be updated with SQLite's WAL side files. A transaction holds the serialized connection permit for + * the transaction scope, so concurrent fibers using the same client wait until it completes, while + * separate database handles or processes can still contend for SQLite write locks. Safe integer + * handling follows the `SqlClient` fiber-local setting, `executeStream` is not implemented, and + * SQLite does not support `updateValues`. + * * @since 4.0.0 */ export * as SqliteClient from "./SqliteClient.ts" /** + * Utilities for applying Effect SQL migrations to Bun SQLite databases. + * + * This module re-exports the shared `Migrator` loaders and error types, then + * provides `run` and `layer` helpers for applying ordered migrations through + * the current Bun-backed SQLite `SqlClient`. It is typically used at + * application startup, in deployment or setup scripts that prepare a local + * SQLite file, in integration tests with temporary database files, or in layer + * graphs that must install the schema before dependent services are acquired. + * + * Migrations are recorded in `effect_sql_migrations` by default and are loaded + * using the shared `_` file or record-key convention. Only migrations + * with an id greater than the latest recorded id are applied, so every client + * involved in startup should point at the same SQLite filename and use a + * writable Bun SQLite configuration. The Bun client enables WAL by default and + * serializes access through a single `bun:sqlite` database handle, but separate + * handles or processes can still contend for SQLite write locks. Bun's SQLite + * driver runs statements synchronously, so large migration sets can block the + * invoking runtime thread, and this adapter does not currently write SQLite + * schema dumps for `schemaDirectory`. + * * @since 4.0.0 */ export * as SqliteMigrator from "./SqliteMigrator.ts" diff --git a/packages/sql/sqlite-do/src/index.ts b/packages/sql/sqlite-do/src/index.ts index 986ce8399a..7af58bf180 100644 --- a/packages/sql/sqlite-do/src/index.ts +++ b/packages/sql/sqlite-do/src/index.ts @@ -5,11 +5,50 @@ // @barrel: Auto-generated exports. Do not edit manually. /** + * Provides an Effect SQL client for Cloudflare Durable Object SQLite storage. + * + * This module adapts a Durable Object `SqlStorage` handle into both the + * Durable Object-specific `SqliteClient` service and the generic Effect + * `SqlClient` service. Use it from inside a Durable Object to run local + * per-object queries, repositories, migrations, transactional read/write + * workflows, and tests that exercise Cloudflare's SQLite-backed storage API. + * + * Durable Object SQLite storage is scoped to one object id, so each object + * instance has its own database and callers should pass the same `SqlStorage` + * handle that the object uses for normal reads and writes. This adapter + * serializes Effect SQL access through one connection; a transaction holds that + * permit for the lifetime of its scope, so keep transactions short, avoid + * suspending them across unrelated work, and use them when multi-statement + * writes must commit atomically. `SqlStorage.exec` returns `ArrayBuffer` values + * for SQLite blobs, which this client normalizes to `Uint8Array`, and SQLite + * does not support `updateValues`. + * * @since 4.0.0 */ export * as SqliteClient from "./SqliteClient.ts" /** + * Utilities for applying Effect SQL migrations to Cloudflare Durable Object SQLite storage. + * + * This module re-exports the shared `Migrator` loaders and error types, then + * provides `run` and `layer` helpers that execute ordered migrations through the + * current Durable Object `SqlStorage`-backed `SqlClient`. Use it when a Durable + * Object needs to create or upgrade its local schema during construction, before + * repositories or request handlers use the object storage, or in tests that + * exercise Durable Object persistence. + * + * Migrations are recorded in `effect_sql_migrations` by default and are loaded + * using the shared `_` file or record-key convention. The underlying + * storage is scoped to a Durable Object id, so running migrations for one object + * does not update any other object instance; run the migrator against the same + * `SqlStorage` handle that the object uses for normal queries. These SQL + * migrations are separate from Cloudflare's Durable Object class migrations, and + * the Durable Object must already be configured with SQLite storage before this + * module can apply schema changes. Repeated startup runs are expected and are + * guarded by the migrations table, but request handling should wait until the + * migration layer has finished. This adapter does not currently write SQLite + * schema dumps for `schemaDirectory`. + * * @since 4.0.0 */ export * as SqliteMigrator from "./SqliteMigrator.ts" diff --git a/packages/sql/sqlite-node/src/index.ts b/packages/sql/sqlite-node/src/index.ts index 986ce8399a..7823968b61 100644 --- a/packages/sql/sqlite-node/src/index.ts +++ b/packages/sql/sqlite-node/src/index.ts @@ -5,11 +5,40 @@ // @barrel: Auto-generated exports. Do not edit manually. /** + * Node.js SQLite client implementation for Effect SQL, backed by `better-sqlite3`. + * + * This module exposes constructors and layers for providing both the SQLite-specific `SqliteClient` + * service and the generic `SqlClient` service. It is intended for file-backed or in-memory SQLite + * databases in Node applications, local development tools, tests, migrations, and embedded + * persistence use cases that need Effect SQL query compilation plus SQLite-specific operations such + * as exporting a database, creating backups, or loading native SQLite extensions. + * + * Each client owns one scoped `better-sqlite3` connection and serializes access through it. WAL mode + * is enabled by default, so set `disableWAL` when opening read-only databases or when the database + * location cannot change journal mode. Prepared statements are cached by SQL text, safe integer + * handling follows the `SqlClient` fiber-local setting, `executeStream` is not implemented, and + * SQLite does not support `updateValues`. + * * @since 4.0.0 */ export * as SqliteClient from "./SqliteClient.ts" /** + * Utilities for applying Effect SQL migrations to Node.js SQLite databases. + * + * This module re-exports the shared `Migrator` loaders and error types, then + * provides `run` and `layer` helpers for applying ordered migrations through the + * current SQLite `SqlClient`. It is typically used at application startup, in + * tests that create temporary database files, or in layer graphs that must + * ensure a file-backed SQLite schema exists before dependent services start. + * + * Migrations are recorded in `effect_sql_migrations` by default and are loaded + * using the shared `_` file or record-key convention. Only migrations + * with an id greater than the latest recorded id are applied, so every client + * involved in startup should point at the same SQLite filename. Concurrent + * writers can surface SQLite lock timeout errors, and this adapter does not + * currently write SQLite schema dumps for `schemaDirectory`. + * * @since 4.0.0 */ export * as SqliteMigrator from "./SqliteMigrator.ts" diff --git a/packages/sql/sqlite-react-native/src/index.ts b/packages/sql/sqlite-react-native/src/index.ts index 986ce8399a..c53001a394 100644 --- a/packages/sql/sqlite-react-native/src/index.ts +++ b/packages/sql/sqlite-react-native/src/index.ts @@ -5,11 +5,44 @@ // @barrel: Auto-generated exports. Do not edit manually. /** + * Provides a React Native SQLite `SqlClient` backed by `@op-engineering/op-sqlite`. + * + * Use this module to open an on-device SQLite database, expose it as both the + * React Native-specific `SqliteClient` and the generic Effect `SqlClient`, and + * run application queries, migrations, and transactional reads or writes from + * Effect services and layers. + * + * The client uses one serialized connection. Regular queries and transactions + * share that handle, and a transaction holds it for the lifetime of its scope, + * so keep mobile transactions short and wrap multi-statement writes in a + * transaction to avoid partial updates. By default statements use the driver's + * synchronous API, which can block the JavaScript thread; `withAsyncQuery` + * switches a fiber to the asynchronous driver API when UI responsiveness is more + * important than sync execution. + * * @since 4.0.0 */ export * as SqliteClient from "./SqliteClient.ts" /** + * Utilities for applying Effect SQL migrations to React Native SQLite databases. + * + * This module re-exports the shared `Migrator` loaders and error types, then + * provides `run` and `layer` helpers that execute ordered migrations through the + * current React Native SQLite `SqlClient`. Use it when a mobile app needs to + * bring its on-device database schema up to date during startup, before opening + * repositories or sync services, or in integration tests that create app-local + * database files. + * + * React Native SQLite databases are scoped by the client configuration, so the + * migrator should be run with the same `filename`, `location`, and encryption + * key as the rest of the application. Migrations run through the package's + * single serialized connection; by default statements use the synchronous + * driver API and can block the JS thread, so long migration sets may want to run + * under `SqliteClient.withAsyncQuery`. Mobile upgrades can be interrupted by app + * suspension or process death, so keep migrations transaction-aware and avoid + * assuming a fresh database on every launch. + * * @since 4.0.0 */ export * as SqliteMigrator from "./SqliteMigrator.ts" From f1efe8bae73b81e3440a2c655db2bde3a7010c7e Mon Sep 17 00:00:00 2001 From: Tim Smart Date: Tue, 19 May 2026 10:50:15 +1200 Subject: [PATCH 05/10] fix changeset level --- .changeset/add-stream-broadcastn.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/add-stream-broadcastn.md b/.changeset/add-stream-broadcastn.md index d8b41a34cf..ca1fa612f2 100644 --- a/.changeset/add-stream-broadcastn.md +++ b/.changeset/add-stream-broadcastn.md @@ -1,5 +1,5 @@ --- -"effect": minor +"effect": patch --- Add Stream.broadcastN for fixed-size stream broadcasts. From 0f8d9b94585894757f1e301cbcb5816a8747ed88 Mon Sep 17 00:00:00 2001 From: effect-bot <129879685+effect-bot@users.noreply.github.com> Date: Mon, 18 May 2026 21:19:43 -0400 Subject: [PATCH 06/10] Version Packages (beta) (#2208) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/pre.json | 6 ++++++ packages/ai/anthropic/CHANGELOG.md | 7 +++++++ packages/ai/anthropic/package.json | 2 +- packages/ai/openai-compat/CHANGELOG.md | 7 +++++++ packages/ai/openai-compat/package.json | 2 +- packages/ai/openai/CHANGELOG.md | 7 +++++++ packages/ai/openai/package.json | 2 +- packages/ai/openrouter/CHANGELOG.md | 7 +++++++ packages/ai/openrouter/package.json | 2 +- packages/atom/react/CHANGELOG.md | 7 +++++++ packages/atom/react/package.json | 2 +- packages/atom/solid/CHANGELOG.md | 7 +++++++ packages/atom/solid/package.json | 2 +- packages/atom/vue/CHANGELOG.md | 7 +++++++ packages/atom/vue/package.json | 2 +- packages/effect/CHANGELOG.md | 16 ++++++++++++++++ packages/effect/package.json | 2 +- packages/opentelemetry/CHANGELOG.md | 7 +++++++ packages/opentelemetry/package.json | 2 +- packages/platform-browser/CHANGELOG.md | 9 +++++++++ packages/platform-browser/package.json | 2 +- packages/platform-bun/CHANGELOG.md | 10 ++++++++++ packages/platform-bun/package.json | 2 +- packages/platform-node-shared/CHANGELOG.md | 9 +++++++++ packages/platform-node-shared/package.json | 2 +- packages/platform-node/CHANGELOG.md | 10 ++++++++++ packages/platform-node/package.json | 2 +- packages/sql/clickhouse/CHANGELOG.md | 8 ++++++++ packages/sql/clickhouse/package.json | 2 +- packages/sql/d1/CHANGELOG.md | 7 +++++++ packages/sql/d1/package.json | 2 +- packages/sql/libsql/CHANGELOG.md | 7 +++++++ packages/sql/libsql/package.json | 2 +- packages/sql/mssql/CHANGELOG.md | 7 +++++++ packages/sql/mssql/package.json | 2 +- packages/sql/mysql2/CHANGELOG.md | 7 +++++++ packages/sql/mysql2/package.json | 2 +- packages/sql/pg/CHANGELOG.md | 7 +++++++ packages/sql/pg/package.json | 2 +- packages/sql/pglite/CHANGELOG.md | 7 +++++++ packages/sql/pglite/package.json | 2 +- packages/sql/sqlite-bun/CHANGELOG.md | 7 +++++++ packages/sql/sqlite-bun/package.json | 2 +- packages/sql/sqlite-do/CHANGELOG.md | 7 +++++++ packages/sql/sqlite-do/package.json | 2 +- packages/sql/sqlite-node/CHANGELOG.md | 7 +++++++ packages/sql/sqlite-node/package.json | 2 +- packages/sql/sqlite-react-native/CHANGELOG.md | 7 +++++++ packages/sql/sqlite-react-native/package.json | 2 +- packages/sql/sqlite-wasm/CHANGELOG.md | 7 +++++++ packages/sql/sqlite-wasm/package.json | 2 +- packages/tools/openapi-generator/CHANGELOG.md | 8 ++++++++ packages/tools/openapi-generator/package.json | 2 +- packages/vitest/CHANGELOG.md | 7 +++++++ packages/vitest/package.json | 2 +- 55 files changed, 243 insertions(+), 27 deletions(-) diff --git a/.changeset/pre.json b/.changeset/pre.json index ad382c8918..d59f3aa6ac 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -59,6 +59,7 @@ "add-schema-string-encoding", "add-sql-pglite", "add-standard-jsdoc-rule", + "add-stream-broadcastn", "add-unstable-encoding-export", "afraid-cobras-like", "ai-openai-config-field-leak", @@ -292,6 +293,7 @@ "green-chips-wash", "green-moons-smile", "green-pugs-play", + "green-rings-prove", "happy-mirrors-dream", "heavy-loops-cut", "heavy-trams-fix", @@ -354,6 +356,7 @@ "olive-poems-visit", "open-hotels-remain", "petite-months-allow", + "platform-crypto-service", "plenty-moons-pull", "polite-brooms-tickle", "polite-pigs-speak", @@ -392,6 +395,7 @@ "ripe-lies-battle", "rpc-middleware-provides-fix", "schema-as-class", + "schema-asserts-signature", "schema-clean-up-additionalProperties", "schema-datetime-utc-from-string", "schema-decoding-defaults-services", @@ -415,6 +419,7 @@ "shiny-trains-hug", "short-cows-relate", "short-foxes-admire", + "shy-cycles-flow", "shy-geckos-sniff", "silent-geckos-matter", "silent-needles-design", @@ -445,6 +450,7 @@ "soft-seals-allow", "solid-doors-ring", "solid-items-tease", + "solid-towns-smoke", "sour-canyons-rescue", "sparkly-bears-act", "sparkly-coins-sit", diff --git a/packages/ai/anthropic/CHANGELOG.md b/packages/ai/anthropic/CHANGELOG.md index 920608ddf4..33d81cf939 100644 --- a/packages/ai/anthropic/CHANGELOG.md +++ b/packages/ai/anthropic/CHANGELOG.md @@ -1,5 +1,12 @@ # @effect/ai-anthropic +## 4.0.0-beta.68 + +### Patch Changes + +- Updated dependencies [[`af8267f`](https://github.com/Effect-TS/effect-smol/commit/af8267f2f3588c3fb611e9286f6f933f29ce1217), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`f136bb7`](https://github.com/Effect-TS/effect-smol/commit/f136bb763048cbc6b17edd26496dba3e2415b9fa), [`6f38f07`](https://github.com/Effect-TS/effect-smol/commit/6f38f07d5941a211b251383aaab0f4f55e8a6557), [`aec9c40`](https://github.com/Effect-TS/effect-smol/commit/aec9c401a53db227f18bf5e0c84db7130ad862d6)]: + - effect@4.0.0-beta.68 + ## 4.0.0-beta.67 ### Patch Changes diff --git a/packages/ai/anthropic/package.json b/packages/ai/anthropic/package.json index 884ce537e2..02abc27130 100644 --- a/packages/ai/anthropic/package.json +++ b/packages/ai/anthropic/package.json @@ -1,6 +1,6 @@ { "name": "@effect/ai-anthropic", - "version": "4.0.0-beta.67", + "version": "4.0.0-beta.68", "type": "module", "license": "MIT", "description": "An Anthropic provider integration for Effect AI SDK", diff --git a/packages/ai/openai-compat/CHANGELOG.md b/packages/ai/openai-compat/CHANGELOG.md index a33110b1e7..172170141e 100644 --- a/packages/ai/openai-compat/CHANGELOG.md +++ b/packages/ai/openai-compat/CHANGELOG.md @@ -1,5 +1,12 @@ # @effect/ai-openai-compat +## 4.0.0-beta.68 + +### Patch Changes + +- Updated dependencies [[`af8267f`](https://github.com/Effect-TS/effect-smol/commit/af8267f2f3588c3fb611e9286f6f933f29ce1217), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`f136bb7`](https://github.com/Effect-TS/effect-smol/commit/f136bb763048cbc6b17edd26496dba3e2415b9fa), [`6f38f07`](https://github.com/Effect-TS/effect-smol/commit/6f38f07d5941a211b251383aaab0f4f55e8a6557), [`aec9c40`](https://github.com/Effect-TS/effect-smol/commit/aec9c401a53db227f18bf5e0c84db7130ad862d6)]: + - effect@4.0.0-beta.68 + ## 4.0.0-beta.67 ### Patch Changes diff --git a/packages/ai/openai-compat/package.json b/packages/ai/openai-compat/package.json index b922c2476c..6199cb3add 100644 --- a/packages/ai/openai-compat/package.json +++ b/packages/ai/openai-compat/package.json @@ -1,6 +1,6 @@ { "name": "@effect/ai-openai-compat", - "version": "4.0.0-beta.67", + "version": "4.0.0-beta.68", "type": "module", "license": "MIT", "description": "An OpenAI compat integration for Effect", diff --git a/packages/ai/openai/CHANGELOG.md b/packages/ai/openai/CHANGELOG.md index 741251b12d..09c8eb02e6 100644 --- a/packages/ai/openai/CHANGELOG.md +++ b/packages/ai/openai/CHANGELOG.md @@ -1,5 +1,12 @@ # @effect/ai-openai +## 4.0.0-beta.68 + +### Patch Changes + +- Updated dependencies [[`af8267f`](https://github.com/Effect-TS/effect-smol/commit/af8267f2f3588c3fb611e9286f6f933f29ce1217), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`f136bb7`](https://github.com/Effect-TS/effect-smol/commit/f136bb763048cbc6b17edd26496dba3e2415b9fa), [`6f38f07`](https://github.com/Effect-TS/effect-smol/commit/6f38f07d5941a211b251383aaab0f4f55e8a6557), [`aec9c40`](https://github.com/Effect-TS/effect-smol/commit/aec9c401a53db227f18bf5e0c84db7130ad862d6)]: + - effect@4.0.0-beta.68 + ## 4.0.0-beta.67 ### Patch Changes diff --git a/packages/ai/openai/package.json b/packages/ai/openai/package.json index a30448c8f4..8afe8d5f6a 100644 --- a/packages/ai/openai/package.json +++ b/packages/ai/openai/package.json @@ -1,6 +1,6 @@ { "name": "@effect/ai-openai", - "version": "4.0.0-beta.67", + "version": "4.0.0-beta.68", "type": "module", "license": "MIT", "description": "An OpenAI provider integration for Effect AI SDK", diff --git a/packages/ai/openrouter/CHANGELOG.md b/packages/ai/openrouter/CHANGELOG.md index d89acda3dc..c5b91d594c 100644 --- a/packages/ai/openrouter/CHANGELOG.md +++ b/packages/ai/openrouter/CHANGELOG.md @@ -1,5 +1,12 @@ # @effect/ai-openrouter +## 4.0.0-beta.68 + +### Patch Changes + +- Updated dependencies [[`af8267f`](https://github.com/Effect-TS/effect-smol/commit/af8267f2f3588c3fb611e9286f6f933f29ce1217), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`f136bb7`](https://github.com/Effect-TS/effect-smol/commit/f136bb763048cbc6b17edd26496dba3e2415b9fa), [`6f38f07`](https://github.com/Effect-TS/effect-smol/commit/6f38f07d5941a211b251383aaab0f4f55e8a6557), [`aec9c40`](https://github.com/Effect-TS/effect-smol/commit/aec9c401a53db227f18bf5e0c84db7130ad862d6)]: + - effect@4.0.0-beta.68 + ## 4.0.0-beta.67 ### Patch Changes diff --git a/packages/ai/openrouter/package.json b/packages/ai/openrouter/package.json index 276bb972c1..b5dc220906 100644 --- a/packages/ai/openrouter/package.json +++ b/packages/ai/openrouter/package.json @@ -1,6 +1,6 @@ { "name": "@effect/ai-openrouter", - "version": "4.0.0-beta.67", + "version": "4.0.0-beta.68", "type": "module", "license": "MIT", "description": "An OpenRouter provider integration for Effect AI SDK", diff --git a/packages/atom/react/CHANGELOG.md b/packages/atom/react/CHANGELOG.md index 828b3b83dc..d0019a54d2 100644 --- a/packages/atom/react/CHANGELOG.md +++ b/packages/atom/react/CHANGELOG.md @@ -1,5 +1,12 @@ # @effect/atom-react +## 4.0.0-beta.68 + +### Patch Changes + +- Updated dependencies [[`af8267f`](https://github.com/Effect-TS/effect-smol/commit/af8267f2f3588c3fb611e9286f6f933f29ce1217), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`f136bb7`](https://github.com/Effect-TS/effect-smol/commit/f136bb763048cbc6b17edd26496dba3e2415b9fa), [`6f38f07`](https://github.com/Effect-TS/effect-smol/commit/6f38f07d5941a211b251383aaab0f4f55e8a6557), [`aec9c40`](https://github.com/Effect-TS/effect-smol/commit/aec9c401a53db227f18bf5e0c84db7130ad862d6)]: + - effect@4.0.0-beta.68 + ## 4.0.0-beta.67 ### Patch Changes diff --git a/packages/atom/react/package.json b/packages/atom/react/package.json index e613bfcc7d..d239670a3b 100644 --- a/packages/atom/react/package.json +++ b/packages/atom/react/package.json @@ -1,6 +1,6 @@ { "name": "@effect/atom-react", - "version": "4.0.0-beta.67", + "version": "4.0.0-beta.68", "type": "module", "license": "MIT", "description": "React bindings for the Effect Atom modules", diff --git a/packages/atom/solid/CHANGELOG.md b/packages/atom/solid/CHANGELOG.md index f7395ca03a..3ce20a8bdf 100644 --- a/packages/atom/solid/CHANGELOG.md +++ b/packages/atom/solid/CHANGELOG.md @@ -1,5 +1,12 @@ # @effect/atom-solid +## 4.0.0-beta.68 + +### Patch Changes + +- Updated dependencies [[`af8267f`](https://github.com/Effect-TS/effect-smol/commit/af8267f2f3588c3fb611e9286f6f933f29ce1217), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`f136bb7`](https://github.com/Effect-TS/effect-smol/commit/f136bb763048cbc6b17edd26496dba3e2415b9fa), [`6f38f07`](https://github.com/Effect-TS/effect-smol/commit/6f38f07d5941a211b251383aaab0f4f55e8a6557), [`aec9c40`](https://github.com/Effect-TS/effect-smol/commit/aec9c401a53db227f18bf5e0c84db7130ad862d6)]: + - effect@4.0.0-beta.68 + ## 4.0.0-beta.67 ### Patch Changes diff --git a/packages/atom/solid/package.json b/packages/atom/solid/package.json index 508041ecff..95cab8ec56 100644 --- a/packages/atom/solid/package.json +++ b/packages/atom/solid/package.json @@ -1,6 +1,6 @@ { "name": "@effect/atom-solid", - "version": "4.0.0-beta.67", + "version": "4.0.0-beta.68", "type": "module", "license": "MIT", "description": "SolidJS bindings for the Effect Atom modules", diff --git a/packages/atom/vue/CHANGELOG.md b/packages/atom/vue/CHANGELOG.md index c594d05de7..69113d7983 100644 --- a/packages/atom/vue/CHANGELOG.md +++ b/packages/atom/vue/CHANGELOG.md @@ -1,5 +1,12 @@ # @effect/atom-vue +## 4.0.0-beta.68 + +### Patch Changes + +- Updated dependencies [[`af8267f`](https://github.com/Effect-TS/effect-smol/commit/af8267f2f3588c3fb611e9286f6f933f29ce1217), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`f136bb7`](https://github.com/Effect-TS/effect-smol/commit/f136bb763048cbc6b17edd26496dba3e2415b9fa), [`6f38f07`](https://github.com/Effect-TS/effect-smol/commit/6f38f07d5941a211b251383aaab0f4f55e8a6557), [`aec9c40`](https://github.com/Effect-TS/effect-smol/commit/aec9c401a53db227f18bf5e0c84db7130ad862d6)]: + - effect@4.0.0-beta.68 + ## 4.0.0-beta.67 ### Patch Changes diff --git a/packages/atom/vue/package.json b/packages/atom/vue/package.json index 3b45386cc8..83f694b2e1 100644 --- a/packages/atom/vue/package.json +++ b/packages/atom/vue/package.json @@ -1,6 +1,6 @@ { "name": "@effect/atom-vue", - "version": "4.0.0-beta.67", + "version": "4.0.0-beta.68", "type": "module", "license": "MIT", "description": "Vue bindings for the Effect Atom modules", diff --git a/packages/effect/CHANGELOG.md b/packages/effect/CHANGELOG.md index 6ad0651f2f..2e2a3e435c 100644 --- a/packages/effect/CHANGELOG.md +++ b/packages/effect/CHANGELOG.md @@ -1,5 +1,21 @@ # effect +## 4.0.0-beta.68 + +### Patch Changes + +- [#2210](https://github.com/Effect-TS/effect-smol/pull/2210) [`af8267f`](https://github.com/Effect-TS/effect-smol/commit/af8267f2f3588c3fb611e9286f6f933f29ce1217) Thanks @tim-smart! - Add Stream.broadcastN for fixed-size stream broadcasts. + +- [#2180](https://github.com/Effect-TS/effect-smol/pull/2180) [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5) Thanks @IMax153! - update Model uuid helpers + +- [#2180](https://github.com/Effect-TS/effect-smol/pull/2180) [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5) Thanks @IMax153! - Add a platform-agnostic `Crypto` service for cryptographic random bytes, secure random generators, UUIDv4 / UUIDv7 generation, and digest operations. UUID generation should now use the `Crypto` service's `randomUUIDv4` or `randomUUIDv7`, which format bytes from the platform `Crypto` service; UUIDv7 also uses the `Clock` service timestamp. `Random.nextUUIDv4` has been removed because the base `Random` service is not cryptographically secure. + +- [#2221](https://github.com/Effect-TS/effect-smol/pull/2221) [`f136bb7`](https://github.com/Effect-TS/effect-smol/commit/f136bb763048cbc6b17edd26496dba3e2415b9fa) Thanks @gcanti! - Change `Schema.asserts` and `SchemaParser.asserts` to assert a value directly with `asserts(schema, input)` and remove `Schema.Codec.ToAsserts`. + +- [#2209](https://github.com/Effect-TS/effect-smol/pull/2209) [`6f38f07`](https://github.com/Effect-TS/effect-smol/commit/6f38f07d5941a211b251383aaab0f4f55e8a6557) Thanks @tim-smart! - Fix Channel.decodeText corrupting UTF-8 characters split across chunk boundaries. + +- [#2207](https://github.com/Effect-TS/effect-smol/pull/2207) [`aec9c40`](https://github.com/Effect-TS/effect-smol/commit/aec9c401a53db227f18bf5e0c84db7130ad862d6) Thanks @tim-smart! - rename Model.Generated to Model.GeneratedByDb + ## 4.0.0-beta.67 ### Patch Changes diff --git a/packages/effect/package.json b/packages/effect/package.json index 520942f674..8141639c42 100644 --- a/packages/effect/package.json +++ b/packages/effect/package.json @@ -1,7 +1,7 @@ { "name": "effect", "type": "module", - "version": "4.0.0-beta.67", + "version": "4.0.0-beta.68", "license": "MIT", "description": "The missing standard library for TypeScript, for writing production-grade software.", "homepage": "https://effect.website", diff --git a/packages/opentelemetry/CHANGELOG.md b/packages/opentelemetry/CHANGELOG.md index fbd3c9b630..44aff1faf6 100644 --- a/packages/opentelemetry/CHANGELOG.md +++ b/packages/opentelemetry/CHANGELOG.md @@ -1,5 +1,12 @@ # @effect/opentelemetry +## 4.0.0-beta.68 + +### Patch Changes + +- Updated dependencies [[`af8267f`](https://github.com/Effect-TS/effect-smol/commit/af8267f2f3588c3fb611e9286f6f933f29ce1217), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`f136bb7`](https://github.com/Effect-TS/effect-smol/commit/f136bb763048cbc6b17edd26496dba3e2415b9fa), [`6f38f07`](https://github.com/Effect-TS/effect-smol/commit/6f38f07d5941a211b251383aaab0f4f55e8a6557), [`aec9c40`](https://github.com/Effect-TS/effect-smol/commit/aec9c401a53db227f18bf5e0c84db7130ad862d6)]: + - effect@4.0.0-beta.68 + ## 4.0.0-beta.67 ### Patch Changes diff --git a/packages/opentelemetry/package.json b/packages/opentelemetry/package.json index b9321f8593..20255c6be0 100644 --- a/packages/opentelemetry/package.json +++ b/packages/opentelemetry/package.json @@ -1,7 +1,7 @@ { "name": "@effect/opentelemetry", "type": "module", - "version": "4.0.0-beta.67", + "version": "4.0.0-beta.68", "license": "MIT", "description": "OpenTelemetry integration for Effect", "homepage": "https://effect.website", diff --git a/packages/platform-browser/CHANGELOG.md b/packages/platform-browser/CHANGELOG.md index ffc748603a..0278a918ff 100644 --- a/packages/platform-browser/CHANGELOG.md +++ b/packages/platform-browser/CHANGELOG.md @@ -1,5 +1,14 @@ # @effect/platform-browser +## 4.0.0-beta.68 + +### Patch Changes + +- [#2180](https://github.com/Effect-TS/effect-smol/pull/2180) [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5) Thanks @IMax153! - Add a platform-agnostic `Crypto` service for cryptographic random bytes, secure random generators, UUIDv4 / UUIDv7 generation, and digest operations. UUID generation should now use the `Crypto` service's `randomUUIDv4` or `randomUUIDv7`, which format bytes from the platform `Crypto` service; UUIDv7 also uses the `Clock` service timestamp. `Random.nextUUIDv4` has been removed because the base `Random` service is not cryptographically secure. + +- Updated dependencies [[`af8267f`](https://github.com/Effect-TS/effect-smol/commit/af8267f2f3588c3fb611e9286f6f933f29ce1217), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`f136bb7`](https://github.com/Effect-TS/effect-smol/commit/f136bb763048cbc6b17edd26496dba3e2415b9fa), [`6f38f07`](https://github.com/Effect-TS/effect-smol/commit/6f38f07d5941a211b251383aaab0f4f55e8a6557), [`aec9c40`](https://github.com/Effect-TS/effect-smol/commit/aec9c401a53db227f18bf5e0c84db7130ad862d6)]: + - effect@4.0.0-beta.68 + ## 4.0.0-beta.67 ### Patch Changes diff --git a/packages/platform-browser/package.json b/packages/platform-browser/package.json index fe9f4938b6..f5dfb1e39e 100644 --- a/packages/platform-browser/package.json +++ b/packages/platform-browser/package.json @@ -1,7 +1,7 @@ { "name": "@effect/platform-browser", "type": "module", - "version": "4.0.0-beta.67", + "version": "4.0.0-beta.68", "license": "MIT", "description": "Platform specific implementations for the browser", "homepage": "https://effect.website", diff --git a/packages/platform-bun/CHANGELOG.md b/packages/platform-bun/CHANGELOG.md index 762aeb6f64..075bde2cdb 100644 --- a/packages/platform-bun/CHANGELOG.md +++ b/packages/platform-bun/CHANGELOG.md @@ -1,5 +1,15 @@ # @effect/platform-bun +## 4.0.0-beta.68 + +### Patch Changes + +- [#2180](https://github.com/Effect-TS/effect-smol/pull/2180) [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5) Thanks @IMax153! - Add a platform-agnostic `Crypto` service for cryptographic random bytes, secure random generators, UUIDv4 / UUIDv7 generation, and digest operations. UUID generation should now use the `Crypto` service's `randomUUIDv4` or `randomUUIDv7`, which format bytes from the platform `Crypto` service; UUIDv7 also uses the `Clock` service timestamp. `Random.nextUUIDv4` has been removed because the base `Random` service is not cryptographically secure. + +- Updated dependencies [[`af8267f`](https://github.com/Effect-TS/effect-smol/commit/af8267f2f3588c3fb611e9286f6f933f29ce1217), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`f136bb7`](https://github.com/Effect-TS/effect-smol/commit/f136bb763048cbc6b17edd26496dba3e2415b9fa), [`6f38f07`](https://github.com/Effect-TS/effect-smol/commit/6f38f07d5941a211b251383aaab0f4f55e8a6557), [`aec9c40`](https://github.com/Effect-TS/effect-smol/commit/aec9c401a53db227f18bf5e0c84db7130ad862d6)]: + - effect@4.0.0-beta.68 + - @effect/platform-node-shared@4.0.0-beta.68 + ## 4.0.0-beta.67 ### Patch Changes diff --git a/packages/platform-bun/package.json b/packages/platform-bun/package.json index bf3f7f9a15..8a41e2710d 100644 --- a/packages/platform-bun/package.json +++ b/packages/platform-bun/package.json @@ -1,7 +1,7 @@ { "name": "@effect/platform-bun", "type": "module", - "version": "4.0.0-beta.67", + "version": "4.0.0-beta.68", "license": "MIT", "description": "Platform specific implementations for the Bun runtime", "homepage": "https://effect.website", diff --git a/packages/platform-node-shared/CHANGELOG.md b/packages/platform-node-shared/CHANGELOG.md index 18912155ae..79be2a7cdf 100644 --- a/packages/platform-node-shared/CHANGELOG.md +++ b/packages/platform-node-shared/CHANGELOG.md @@ -1,5 +1,14 @@ # @effect/platform-node-shared +## 4.0.0-beta.68 + +### Patch Changes + +- [#2180](https://github.com/Effect-TS/effect-smol/pull/2180) [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5) Thanks @IMax153! - Add a platform-agnostic `Crypto` service for cryptographic random bytes, secure random generators, UUIDv4 / UUIDv7 generation, and digest operations. UUID generation should now use the `Crypto` service's `randomUUIDv4` or `randomUUIDv7`, which format bytes from the platform `Crypto` service; UUIDv7 also uses the `Clock` service timestamp. `Random.nextUUIDv4` has been removed because the base `Random` service is not cryptographically secure. + +- Updated dependencies [[`af8267f`](https://github.com/Effect-TS/effect-smol/commit/af8267f2f3588c3fb611e9286f6f933f29ce1217), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`f136bb7`](https://github.com/Effect-TS/effect-smol/commit/f136bb763048cbc6b17edd26496dba3e2415b9fa), [`6f38f07`](https://github.com/Effect-TS/effect-smol/commit/6f38f07d5941a211b251383aaab0f4f55e8a6557), [`aec9c40`](https://github.com/Effect-TS/effect-smol/commit/aec9c401a53db227f18bf5e0c84db7130ad862d6)]: + - effect@4.0.0-beta.68 + ## 4.0.0-beta.67 ### Patch Changes diff --git a/packages/platform-node-shared/package.json b/packages/platform-node-shared/package.json index ca09795ce4..3c80573434 100644 --- a/packages/platform-node-shared/package.json +++ b/packages/platform-node-shared/package.json @@ -1,7 +1,7 @@ { "name": "@effect/platform-node-shared", "type": "module", - "version": "4.0.0-beta.67", + "version": "4.0.0-beta.68", "license": "MIT", "description": "Unified interfaces for common platform-specific services", "homepage": "https://effect.website", diff --git a/packages/platform-node/CHANGELOG.md b/packages/platform-node/CHANGELOG.md index 994d2d54c4..1455eef5ca 100644 --- a/packages/platform-node/CHANGELOG.md +++ b/packages/platform-node/CHANGELOG.md @@ -1,5 +1,15 @@ # @effect/platform-node +## 4.0.0-beta.68 + +### Patch Changes + +- [#2180](https://github.com/Effect-TS/effect-smol/pull/2180) [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5) Thanks @IMax153! - Add a platform-agnostic `Crypto` service for cryptographic random bytes, secure random generators, UUIDv4 / UUIDv7 generation, and digest operations. UUID generation should now use the `Crypto` service's `randomUUIDv4` or `randomUUIDv7`, which format bytes from the platform `Crypto` service; UUIDv7 also uses the `Clock` service timestamp. `Random.nextUUIDv4` has been removed because the base `Random` service is not cryptographically secure. + +- Updated dependencies [[`af8267f`](https://github.com/Effect-TS/effect-smol/commit/af8267f2f3588c3fb611e9286f6f933f29ce1217), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`f136bb7`](https://github.com/Effect-TS/effect-smol/commit/f136bb763048cbc6b17edd26496dba3e2415b9fa), [`6f38f07`](https://github.com/Effect-TS/effect-smol/commit/6f38f07d5941a211b251383aaab0f4f55e8a6557), [`aec9c40`](https://github.com/Effect-TS/effect-smol/commit/aec9c401a53db227f18bf5e0c84db7130ad862d6)]: + - effect@4.0.0-beta.68 + - @effect/platform-node-shared@4.0.0-beta.68 + ## 4.0.0-beta.67 ### Patch Changes diff --git a/packages/platform-node/package.json b/packages/platform-node/package.json index adf4288be3..f2909caa99 100644 --- a/packages/platform-node/package.json +++ b/packages/platform-node/package.json @@ -1,7 +1,7 @@ { "name": "@effect/platform-node", "type": "module", - "version": "4.0.0-beta.67", + "version": "4.0.0-beta.68", "license": "MIT", "description": "Platform specific implementations for the Node.js runtime", "homepage": "https://effect.website", diff --git a/packages/sql/clickhouse/CHANGELOG.md b/packages/sql/clickhouse/CHANGELOG.md index 518da8f75b..1958db441a 100644 --- a/packages/sql/clickhouse/CHANGELOG.md +++ b/packages/sql/clickhouse/CHANGELOG.md @@ -1,5 +1,13 @@ # @effect/sql-clickhouse +## 4.0.0-beta.68 + +### Patch Changes + +- Updated dependencies [[`af8267f`](https://github.com/Effect-TS/effect-smol/commit/af8267f2f3588c3fb611e9286f6f933f29ce1217), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`f136bb7`](https://github.com/Effect-TS/effect-smol/commit/f136bb763048cbc6b17edd26496dba3e2415b9fa), [`6f38f07`](https://github.com/Effect-TS/effect-smol/commit/6f38f07d5941a211b251383aaab0f4f55e8a6557), [`aec9c40`](https://github.com/Effect-TS/effect-smol/commit/aec9c401a53db227f18bf5e0c84db7130ad862d6)]: + - effect@4.0.0-beta.68 + - @effect/platform-node@4.0.0-beta.68 + ## 4.0.0-beta.67 ### Patch Changes diff --git a/packages/sql/clickhouse/package.json b/packages/sql/clickhouse/package.json index 429c574e20..2b8c8f5556 100644 --- a/packages/sql/clickhouse/package.json +++ b/packages/sql/clickhouse/package.json @@ -1,6 +1,6 @@ { "name": "@effect/sql-clickhouse", - "version": "4.0.0-beta.67", + "version": "4.0.0-beta.68", "type": "module", "license": "MIT", "description": "A Clickhouse toolkit for Effect", diff --git a/packages/sql/d1/CHANGELOG.md b/packages/sql/d1/CHANGELOG.md index bad0615ed8..5a0c739962 100644 --- a/packages/sql/d1/CHANGELOG.md +++ b/packages/sql/d1/CHANGELOG.md @@ -1,5 +1,12 @@ # @effect/sql-d1 +## 4.0.0-beta.68 + +### Patch Changes + +- Updated dependencies [[`af8267f`](https://github.com/Effect-TS/effect-smol/commit/af8267f2f3588c3fb611e9286f6f933f29ce1217), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`f136bb7`](https://github.com/Effect-TS/effect-smol/commit/f136bb763048cbc6b17edd26496dba3e2415b9fa), [`6f38f07`](https://github.com/Effect-TS/effect-smol/commit/6f38f07d5941a211b251383aaab0f4f55e8a6557), [`aec9c40`](https://github.com/Effect-TS/effect-smol/commit/aec9c401a53db227f18bf5e0c84db7130ad862d6)]: + - effect@4.0.0-beta.68 + ## 4.0.0-beta.67 ### Patch Changes diff --git a/packages/sql/d1/package.json b/packages/sql/d1/package.json index c5896ae73c..fdb5a9bb92 100644 --- a/packages/sql/d1/package.json +++ b/packages/sql/d1/package.json @@ -1,6 +1,6 @@ { "name": "@effect/sql-d1", - "version": "4.0.0-beta.67", + "version": "4.0.0-beta.68", "type": "module", "license": "MIT", "description": "A Cloudflare D1 integration for Effect", diff --git a/packages/sql/libsql/CHANGELOG.md b/packages/sql/libsql/CHANGELOG.md index 60c03c4e51..c99c847af5 100644 --- a/packages/sql/libsql/CHANGELOG.md +++ b/packages/sql/libsql/CHANGELOG.md @@ -1,5 +1,12 @@ # @effect/sql-libsql +## 4.0.0-beta.68 + +### Patch Changes + +- Updated dependencies [[`af8267f`](https://github.com/Effect-TS/effect-smol/commit/af8267f2f3588c3fb611e9286f6f933f29ce1217), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`f136bb7`](https://github.com/Effect-TS/effect-smol/commit/f136bb763048cbc6b17edd26496dba3e2415b9fa), [`6f38f07`](https://github.com/Effect-TS/effect-smol/commit/6f38f07d5941a211b251383aaab0f4f55e8a6557), [`aec9c40`](https://github.com/Effect-TS/effect-smol/commit/aec9c401a53db227f18bf5e0c84db7130ad862d6)]: + - effect@4.0.0-beta.68 + ## 4.0.0-beta.67 ### Patch Changes diff --git a/packages/sql/libsql/package.json b/packages/sql/libsql/package.json index 579a0b8397..bdf5298e75 100644 --- a/packages/sql/libsql/package.json +++ b/packages/sql/libsql/package.json @@ -1,6 +1,6 @@ { "name": "@effect/sql-libsql", - "version": "4.0.0-beta.67", + "version": "4.0.0-beta.68", "type": "module", "license": "MIT", "description": "A libSQL toolkit for Effect", diff --git a/packages/sql/mssql/CHANGELOG.md b/packages/sql/mssql/CHANGELOG.md index 73450098ee..6767375a74 100644 --- a/packages/sql/mssql/CHANGELOG.md +++ b/packages/sql/mssql/CHANGELOG.md @@ -1,5 +1,12 @@ # @effect/sql-mssql +## 4.0.0-beta.68 + +### Patch Changes + +- Updated dependencies [[`af8267f`](https://github.com/Effect-TS/effect-smol/commit/af8267f2f3588c3fb611e9286f6f933f29ce1217), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`f136bb7`](https://github.com/Effect-TS/effect-smol/commit/f136bb763048cbc6b17edd26496dba3e2415b9fa), [`6f38f07`](https://github.com/Effect-TS/effect-smol/commit/6f38f07d5941a211b251383aaab0f4f55e8a6557), [`aec9c40`](https://github.com/Effect-TS/effect-smol/commit/aec9c401a53db227f18bf5e0c84db7130ad862d6)]: + - effect@4.0.0-beta.68 + ## 4.0.0-beta.67 ### Patch Changes diff --git a/packages/sql/mssql/package.json b/packages/sql/mssql/package.json index 3eb7d509e6..00c1b28184 100644 --- a/packages/sql/mssql/package.json +++ b/packages/sql/mssql/package.json @@ -1,6 +1,6 @@ { "name": "@effect/sql-mssql", - "version": "4.0.0-beta.67", + "version": "4.0.0-beta.68", "type": "module", "license": "MIT", "description": "A Microsoft SQL Server toolkit for Effect", diff --git a/packages/sql/mysql2/CHANGELOG.md b/packages/sql/mysql2/CHANGELOG.md index 4771fbad6d..cd88615093 100644 --- a/packages/sql/mysql2/CHANGELOG.md +++ b/packages/sql/mysql2/CHANGELOG.md @@ -1,5 +1,12 @@ # @effect/sql-mysql2 +## 4.0.0-beta.68 + +### Patch Changes + +- Updated dependencies [[`af8267f`](https://github.com/Effect-TS/effect-smol/commit/af8267f2f3588c3fb611e9286f6f933f29ce1217), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`f136bb7`](https://github.com/Effect-TS/effect-smol/commit/f136bb763048cbc6b17edd26496dba3e2415b9fa), [`6f38f07`](https://github.com/Effect-TS/effect-smol/commit/6f38f07d5941a211b251383aaab0f4f55e8a6557), [`aec9c40`](https://github.com/Effect-TS/effect-smol/commit/aec9c401a53db227f18bf5e0c84db7130ad862d6)]: + - effect@4.0.0-beta.68 + ## 4.0.0-beta.67 ### Patch Changes diff --git a/packages/sql/mysql2/package.json b/packages/sql/mysql2/package.json index 3f17eca6e1..c3b442ff75 100644 --- a/packages/sql/mysql2/package.json +++ b/packages/sql/mysql2/package.json @@ -1,6 +1,6 @@ { "name": "@effect/sql-mysql2", - "version": "4.0.0-beta.67", + "version": "4.0.0-beta.68", "type": "module", "license": "MIT", "description": "A MySQL toolkit for Effect", diff --git a/packages/sql/pg/CHANGELOG.md b/packages/sql/pg/CHANGELOG.md index 0a2bda5cfc..85c503585c 100644 --- a/packages/sql/pg/CHANGELOG.md +++ b/packages/sql/pg/CHANGELOG.md @@ -1,5 +1,12 @@ # @effect/sql-pg +## 4.0.0-beta.68 + +### Patch Changes + +- Updated dependencies [[`af8267f`](https://github.com/Effect-TS/effect-smol/commit/af8267f2f3588c3fb611e9286f6f933f29ce1217), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`f136bb7`](https://github.com/Effect-TS/effect-smol/commit/f136bb763048cbc6b17edd26496dba3e2415b9fa), [`6f38f07`](https://github.com/Effect-TS/effect-smol/commit/6f38f07d5941a211b251383aaab0f4f55e8a6557), [`aec9c40`](https://github.com/Effect-TS/effect-smol/commit/aec9c401a53db227f18bf5e0c84db7130ad862d6)]: + - effect@4.0.0-beta.68 + ## 4.0.0-beta.67 ### Patch Changes diff --git a/packages/sql/pg/package.json b/packages/sql/pg/package.json index 222fc59dc5..9ddf2983fd 100644 --- a/packages/sql/pg/package.json +++ b/packages/sql/pg/package.json @@ -1,6 +1,6 @@ { "name": "@effect/sql-pg", - "version": "4.0.0-beta.67", + "version": "4.0.0-beta.68", "type": "module", "license": "MIT", "description": "A PostgreSQL toolkit for Effect", diff --git a/packages/sql/pglite/CHANGELOG.md b/packages/sql/pglite/CHANGELOG.md index 62d82e2d37..9fd2d1a224 100644 --- a/packages/sql/pglite/CHANGELOG.md +++ b/packages/sql/pglite/CHANGELOG.md @@ -1,5 +1,12 @@ # @effect/sql-pglite +## 4.0.0-beta.68 + +### Patch Changes + +- Updated dependencies [[`af8267f`](https://github.com/Effect-TS/effect-smol/commit/af8267f2f3588c3fb611e9286f6f933f29ce1217), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`f136bb7`](https://github.com/Effect-TS/effect-smol/commit/f136bb763048cbc6b17edd26496dba3e2415b9fa), [`6f38f07`](https://github.com/Effect-TS/effect-smol/commit/6f38f07d5941a211b251383aaab0f4f55e8a6557), [`aec9c40`](https://github.com/Effect-TS/effect-smol/commit/aec9c401a53db227f18bf5e0c84db7130ad862d6)]: + - effect@4.0.0-beta.68 + ## 4.0.0-beta.67 ### Patch Changes diff --git a/packages/sql/pglite/package.json b/packages/sql/pglite/package.json index fae9b16f66..53f2a7e697 100644 --- a/packages/sql/pglite/package.json +++ b/packages/sql/pglite/package.json @@ -1,6 +1,6 @@ { "name": "@effect/sql-pglite", - "version": "4.0.0-beta.67", + "version": "4.0.0-beta.68", "type": "module", "license": "MIT", "description": "A PGlite toolkit for Effect", diff --git a/packages/sql/sqlite-bun/CHANGELOG.md b/packages/sql/sqlite-bun/CHANGELOG.md index 43359255ff..fcc3470028 100644 --- a/packages/sql/sqlite-bun/CHANGELOG.md +++ b/packages/sql/sqlite-bun/CHANGELOG.md @@ -1,5 +1,12 @@ # @effect/sql-sqlite-bun +## 4.0.0-beta.68 + +### Patch Changes + +- Updated dependencies [[`af8267f`](https://github.com/Effect-TS/effect-smol/commit/af8267f2f3588c3fb611e9286f6f933f29ce1217), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`f136bb7`](https://github.com/Effect-TS/effect-smol/commit/f136bb763048cbc6b17edd26496dba3e2415b9fa), [`6f38f07`](https://github.com/Effect-TS/effect-smol/commit/6f38f07d5941a211b251383aaab0f4f55e8a6557), [`aec9c40`](https://github.com/Effect-TS/effect-smol/commit/aec9c401a53db227f18bf5e0c84db7130ad862d6)]: + - effect@4.0.0-beta.68 + ## 4.0.0-beta.67 ### Patch Changes diff --git a/packages/sql/sqlite-bun/package.json b/packages/sql/sqlite-bun/package.json index 910bce932a..0d7b183afc 100644 --- a/packages/sql/sqlite-bun/package.json +++ b/packages/sql/sqlite-bun/package.json @@ -1,6 +1,6 @@ { "name": "@effect/sql-sqlite-bun", - "version": "4.0.0-beta.67", + "version": "4.0.0-beta.68", "type": "module", "license": "MIT", "description": "A SQLite toolkit for Effect", diff --git a/packages/sql/sqlite-do/CHANGELOG.md b/packages/sql/sqlite-do/CHANGELOG.md index 5de4a891ca..3a399320c6 100644 --- a/packages/sql/sqlite-do/CHANGELOG.md +++ b/packages/sql/sqlite-do/CHANGELOG.md @@ -1,5 +1,12 @@ # @effect/sql-sqlite-do +## 4.0.0-beta.68 + +### Patch Changes + +- Updated dependencies [[`af8267f`](https://github.com/Effect-TS/effect-smol/commit/af8267f2f3588c3fb611e9286f6f933f29ce1217), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`f136bb7`](https://github.com/Effect-TS/effect-smol/commit/f136bb763048cbc6b17edd26496dba3e2415b9fa), [`6f38f07`](https://github.com/Effect-TS/effect-smol/commit/6f38f07d5941a211b251383aaab0f4f55e8a6557), [`aec9c40`](https://github.com/Effect-TS/effect-smol/commit/aec9c401a53db227f18bf5e0c84db7130ad862d6)]: + - effect@4.0.0-beta.68 + ## 4.0.0-beta.67 ### Patch Changes diff --git a/packages/sql/sqlite-do/package.json b/packages/sql/sqlite-do/package.json index 1bde11d60e..df55906d4b 100644 --- a/packages/sql/sqlite-do/package.json +++ b/packages/sql/sqlite-do/package.json @@ -1,6 +1,6 @@ { "name": "@effect/sql-sqlite-do", - "version": "4.0.0-beta.67", + "version": "4.0.0-beta.68", "type": "module", "license": "MIT", "description": "A SQLite toolkit for Effect", diff --git a/packages/sql/sqlite-node/CHANGELOG.md b/packages/sql/sqlite-node/CHANGELOG.md index 6b6ce6611d..4f3a996a2a 100644 --- a/packages/sql/sqlite-node/CHANGELOG.md +++ b/packages/sql/sqlite-node/CHANGELOG.md @@ -1,5 +1,12 @@ # @effect/sql-sqlite-node +## 4.0.0-beta.68 + +### Patch Changes + +- Updated dependencies [[`af8267f`](https://github.com/Effect-TS/effect-smol/commit/af8267f2f3588c3fb611e9286f6f933f29ce1217), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`f136bb7`](https://github.com/Effect-TS/effect-smol/commit/f136bb763048cbc6b17edd26496dba3e2415b9fa), [`6f38f07`](https://github.com/Effect-TS/effect-smol/commit/6f38f07d5941a211b251383aaab0f4f55e8a6557), [`aec9c40`](https://github.com/Effect-TS/effect-smol/commit/aec9c401a53db227f18bf5e0c84db7130ad862d6)]: + - effect@4.0.0-beta.68 + ## 4.0.0-beta.67 ### Patch Changes diff --git a/packages/sql/sqlite-node/package.json b/packages/sql/sqlite-node/package.json index 2fd70644b9..ce5bd4c92b 100644 --- a/packages/sql/sqlite-node/package.json +++ b/packages/sql/sqlite-node/package.json @@ -1,6 +1,6 @@ { "name": "@effect/sql-sqlite-node", - "version": "4.0.0-beta.67", + "version": "4.0.0-beta.68", "type": "module", "license": "MIT", "description": "A SQLite toolkit for Effect", diff --git a/packages/sql/sqlite-react-native/CHANGELOG.md b/packages/sql/sqlite-react-native/CHANGELOG.md index 533e9a6414..55320c6e79 100644 --- a/packages/sql/sqlite-react-native/CHANGELOG.md +++ b/packages/sql/sqlite-react-native/CHANGELOG.md @@ -1,5 +1,12 @@ # @effect/sql-sqlite-react-native +## 4.0.0-beta.68 + +### Patch Changes + +- Updated dependencies [[`af8267f`](https://github.com/Effect-TS/effect-smol/commit/af8267f2f3588c3fb611e9286f6f933f29ce1217), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`f136bb7`](https://github.com/Effect-TS/effect-smol/commit/f136bb763048cbc6b17edd26496dba3e2415b9fa), [`6f38f07`](https://github.com/Effect-TS/effect-smol/commit/6f38f07d5941a211b251383aaab0f4f55e8a6557), [`aec9c40`](https://github.com/Effect-TS/effect-smol/commit/aec9c401a53db227f18bf5e0c84db7130ad862d6)]: + - effect@4.0.0-beta.68 + ## 4.0.0-beta.67 ### Patch Changes diff --git a/packages/sql/sqlite-react-native/package.json b/packages/sql/sqlite-react-native/package.json index ae35a254b8..0227087241 100644 --- a/packages/sql/sqlite-react-native/package.json +++ b/packages/sql/sqlite-react-native/package.json @@ -1,6 +1,6 @@ { "name": "@effect/sql-sqlite-react-native", - "version": "4.0.0-beta.67", + "version": "4.0.0-beta.68", "type": "module", "license": "MIT", "description": "A SQLite toolkit for Effect", diff --git a/packages/sql/sqlite-wasm/CHANGELOG.md b/packages/sql/sqlite-wasm/CHANGELOG.md index 6e7d01d86f..7f95e54099 100644 --- a/packages/sql/sqlite-wasm/CHANGELOG.md +++ b/packages/sql/sqlite-wasm/CHANGELOG.md @@ -1,5 +1,12 @@ # @effect/sql-sqlite-wasm +## 4.0.0-beta.68 + +### Patch Changes + +- Updated dependencies [[`af8267f`](https://github.com/Effect-TS/effect-smol/commit/af8267f2f3588c3fb611e9286f6f933f29ce1217), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`f136bb7`](https://github.com/Effect-TS/effect-smol/commit/f136bb763048cbc6b17edd26496dba3e2415b9fa), [`6f38f07`](https://github.com/Effect-TS/effect-smol/commit/6f38f07d5941a211b251383aaab0f4f55e8a6557), [`aec9c40`](https://github.com/Effect-TS/effect-smol/commit/aec9c401a53db227f18bf5e0c84db7130ad862d6)]: + - effect@4.0.0-beta.68 + ## 4.0.0-beta.67 ### Patch Changes diff --git a/packages/sql/sqlite-wasm/package.json b/packages/sql/sqlite-wasm/package.json index 441a4baa70..6ae9986356 100644 --- a/packages/sql/sqlite-wasm/package.json +++ b/packages/sql/sqlite-wasm/package.json @@ -1,6 +1,6 @@ { "name": "@effect/sql-sqlite-wasm", - "version": "4.0.0-beta.67", + "version": "4.0.0-beta.68", "type": "module", "license": "MIT", "description": "A SQLite toolkit for Effect", diff --git a/packages/tools/openapi-generator/CHANGELOG.md b/packages/tools/openapi-generator/CHANGELOG.md index 658a92bc23..1b8a7e4a66 100644 --- a/packages/tools/openapi-generator/CHANGELOG.md +++ b/packages/tools/openapi-generator/CHANGELOG.md @@ -1,5 +1,13 @@ # @effect/openapi-generator +## 4.0.0-beta.68 + +### Patch Changes + +- Updated dependencies [[`af8267f`](https://github.com/Effect-TS/effect-smol/commit/af8267f2f3588c3fb611e9286f6f933f29ce1217), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`f136bb7`](https://github.com/Effect-TS/effect-smol/commit/f136bb763048cbc6b17edd26496dba3e2415b9fa), [`6f38f07`](https://github.com/Effect-TS/effect-smol/commit/6f38f07d5941a211b251383aaab0f4f55e8a6557), [`aec9c40`](https://github.com/Effect-TS/effect-smol/commit/aec9c401a53db227f18bf5e0c84db7130ad862d6)]: + - effect@4.0.0-beta.68 + - @effect/platform-node@4.0.0-beta.68 + ## 4.0.0-beta.67 ### Patch Changes diff --git a/packages/tools/openapi-generator/package.json b/packages/tools/openapi-generator/package.json index 3aab83926a..ea0d8b0c98 100644 --- a/packages/tools/openapi-generator/package.json +++ b/packages/tools/openapi-generator/package.json @@ -1,7 +1,7 @@ { "name": "@effect/openapi-generator", "type": "module", - "version": "4.0.0-beta.67", + "version": "4.0.0-beta.68", "license": "MIT", "description": "Generate Effect Schema types, HTTP clients, and HttpApi modules from OpenAPI specifications", "homepage": "https://effect.website", diff --git a/packages/vitest/CHANGELOG.md b/packages/vitest/CHANGELOG.md index 8f058e7fd0..cbe9b552a4 100644 --- a/packages/vitest/CHANGELOG.md +++ b/packages/vitest/CHANGELOG.md @@ -1,5 +1,12 @@ # @effect/vitest +## 4.0.0-beta.68 + +### Patch Changes + +- Updated dependencies [[`af8267f`](https://github.com/Effect-TS/effect-smol/commit/af8267f2f3588c3fb611e9286f6f933f29ce1217), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`f136bb7`](https://github.com/Effect-TS/effect-smol/commit/f136bb763048cbc6b17edd26496dba3e2415b9fa), [`6f38f07`](https://github.com/Effect-TS/effect-smol/commit/6f38f07d5941a211b251383aaab0f4f55e8a6557), [`aec9c40`](https://github.com/Effect-TS/effect-smol/commit/aec9c401a53db227f18bf5e0c84db7130ad862d6)]: + - effect@4.0.0-beta.68 + ## 4.0.0-beta.67 ### Patch Changes diff --git a/packages/vitest/package.json b/packages/vitest/package.json index 2601d58635..2d191fd725 100644 --- a/packages/vitest/package.json +++ b/packages/vitest/package.json @@ -1,6 +1,6 @@ { "name": "@effect/vitest", - "version": "4.0.0-beta.67", + "version": "4.0.0-beta.68", "type": "module", "license": "MIT", "description": "A set of helpers for testing Effects with vitest", From 4c0bb78ff420a37ba637f077f849ea178c896d7f Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 18 May 2026 15:28:42 +0000 Subject: [PATCH 07/10] feat(cli): add Flag.withHidden to hide flags from help and completions Adds `Flag.withHidden` (and underlying `Param.withHidden`) so flags can parse normally on the command line while being omitted from `--help` output, shared/global flag listings, and shell completion descriptors. This is useful for experimental, internal, or deprecated flags such as `--experimental-foo`, debug toggles, or escape hatches that should be accepted but not yet advertised on the public CLI surface. --- .changeset/add-flag-hidden.md | 15 ++++++++ packages/effect/src/unstable/cli/Flag.ts | 25 +++++++++++++ packages/effect/src/unstable/cli/HelpDoc.ts | 8 +++++ packages/effect/src/unstable/cli/Param.ts | 32 +++++++++++++++++ .../src/unstable/cli/internal/command.ts | 4 ++- .../cli/internal/completions/descriptor.ts | 1 + .../effect/src/unstable/cli/internal/help.ts | 4 +++ .../effect/test/unstable/cli/Help.test.ts | 35 +++++++++++++++++++ 8 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 .changeset/add-flag-hidden.md diff --git a/.changeset/add-flag-hidden.md b/.changeset/add-flag-hidden.md new file mode 100644 index 0000000000..1c31e6cd8b --- /dev/null +++ b/.changeset/add-flag-hidden.md @@ -0,0 +1,15 @@ +--- +"effect": patch +--- + +Add `Flag.withHidden` (and `Param.withHidden`) to hide flags from `--help` output and shell completions while keeping them fully parseable on the command line. + +Useful for experimental, internal, or deprecated flags that should be accepted but not advertised, e.g. `--experimental-foo`, debug toggles, or escape hatches that are not yet committed to the public CLI surface. + +```ts +import { Flag } from "effect/unstable/cli" + +const experimental = Flag.boolean("experimental-foo").pipe( + Flag.withHidden +) +``` diff --git a/packages/effect/src/unstable/cli/Flag.ts b/packages/effect/src/unstable/cli/Flag.ts index 73e0df970b..c35db96f40 100644 --- a/packages/effect/src/unstable/cli/Flag.ts +++ b/packages/effect/src/unstable/cli/Flag.ts @@ -487,6 +487,31 @@ export const withMetavar: { (self: Flag, metavar: string): Flag } = dual(2, (self: Flag, metavar: string) => Param.withMetavar(self, metavar)) +/** + * Hides a flag from generated help output and shell completions while keeping + * it fully parseable on the command line. + * + * Useful for experimental, internal, or deprecated flags that should be + * accepted but not advertised — for example, `--experimental-foo`, debug + * toggles, or escape hatches you are not ready to commit to the public CLI + * surface. + * + * **Example** (Hiding a flag from help) + * + * ```ts + * import { Flag } from "effect/unstable/cli" + * + * // Flag still parses --experimental-foo, but it does not appear in --help. + * const experimental = Flag.boolean("experimental-foo").pipe( + * Flag.withHidden + * ) + * ``` + * + * @category metadata + * @since 4.0.0 + */ +export const withHidden = (self: Flag): Flag => Param.withHidden(self) + /** * Makes a flag optional, returning an Option type that can be None if not provided. * diff --git a/packages/effect/src/unstable/cli/HelpDoc.ts b/packages/effect/src/unstable/cli/HelpDoc.ts index 2293c571ad..bd7ffda7c7 100644 --- a/packages/effect/src/unstable/cli/HelpDoc.ts +++ b/packages/effect/src/unstable/cli/HelpDoc.ts @@ -188,6 +188,14 @@ export interface FlagDoc { * Whether this flag is required */ readonly required: boolean + + /** + * Whether this flag is hidden from help output. + * + * Hidden flags are still fully parseable on the command line but are omitted + * from rendered help text and shell completions. + */ + readonly hidden?: boolean | undefined } /** diff --git a/packages/effect/src/unstable/cli/Param.ts b/packages/effect/src/unstable/cli/Param.ts index b9833e51a2..eb92f81e84 100644 --- a/packages/effect/src/unstable/cli/Param.ts +++ b/packages/effect/src/unstable/cli/Param.ts @@ -205,6 +205,7 @@ export interface Single extends Param { readonly aliases: ReadonlyArray readonly primitiveType: Primitive.Primitive readonly typeName?: string | undefined + readonly hidden: boolean } /** @@ -346,6 +347,7 @@ export const makeSingle = (params: { readonly typeName?: string | undefined readonly description?: Option.Option | undefined readonly aliases?: ReadonlyArray | undefined + readonly hidden?: boolean | undefined }): Single => { const parse: Parse = (args) => params.kind === argumentKind @@ -356,6 +358,7 @@ export const makeSingle = (params: { ...params, description: params.description ?? Option.none(), aliases: params.aliases ?? [], + hidden: params.hidden ?? false, parse }) } @@ -1001,6 +1004,35 @@ export const withDescription: { })) }) +/** + * Hides a parameter from generated help output and completions while keeping + * it parseable on the command line. + * + * Useful for experimental, internal, or deprecated flags that should be + * accepted but not advertised. + * + * **Example** (Hiding a flag from help) + * + * ```ts + * import { Param } from "effect/unstable/cli" + * + * // @internal - this module is not exported publicly + * + * const experimental = Param.boolean(Param.flagKind, "experimental-foo").pipe( + * Param.withHidden + * ) + * ``` + * + * @category metadata + * @since 4.0.0 + */ +export const withHidden = (self: Param): Param => + transformSingle(self, (single: Single) => + makeSingle({ + ...single, + hidden: true + })) + /** * Transforms the parsed value of an option using a mapping function. * diff --git a/packages/effect/src/unstable/cli/internal/command.ts b/packages/effect/src/unstable/cli/internal/command.ts index 2b6c6f7fcb..a0dc8557de 100644 --- a/packages/effect/src/unstable/cli/internal/command.ts +++ b/packages/effect/src/unstable/cli/internal/command.ts @@ -157,6 +157,7 @@ export const makeCommand = ): aliases: formattedAliases, type: single.typeName ?? Primitive.getTypeName(single.primitiveType), description: appendChoiceKeys(single.description, Primitive.getChoiceKeys(single.primitiveType)), - required: single.primitiveType._tag !== "Boolean" + required: single.primitiveType._tag !== "Boolean", + ...(single.hidden ? { hidden: true } : {}) } } diff --git a/packages/effect/src/unstable/cli/internal/completions/descriptor.ts b/packages/effect/src/unstable/cli/internal/completions/descriptor.ts index 97da645c35..c8ae208549 100644 --- a/packages/effect/src/unstable/cli/internal/completions/descriptor.ts +++ b/packages/effect/src/unstable/cli/internal/completions/descriptor.ts @@ -85,6 +85,7 @@ export const fromCommand = (cmd: Command.Any): Completions.CommandDescriptor => const singles = Param.extractSingleParams(flag) for (const single of singles) { if (single.kind !== "flag") continue + if (single.hidden) continue flags.push({ name: single.name, aliases: single.aliases, diff --git a/packages/effect/src/unstable/cli/internal/help.ts b/packages/effect/src/unstable/cli/internal/help.ts index 7acc760c90..ed918f08ad 100644 --- a/packages/effect/src/unstable/cli/internal/help.ts +++ b/packages/effect/src/unstable/cli/internal/help.ts @@ -116,6 +116,9 @@ const getSharedFlagsForCommandPath = ( continue } seen.add(single.name) + if (single.hidden) { + continue + } sharedFlags.push(toFlagDoc(single)) } } @@ -160,6 +163,7 @@ export const getHelpForCommandPath = { expect(shortLine!.indexOf("Short flag description")).toBe(longLine!.indexOf("Long flag description")) }).pipe(Effect.provide(TestLayer))) + it.effect("hides flags marked with withHidden from help output", () => + Effect.gen(function*() { + const command = Command.make("tool", { + visible: Flag.string("visible").pipe(Flag.withDescription("Visible flag")), + secret: Flag.string("experimental-foo").pipe( + Flag.withDescription("Should not appear"), + Flag.withHidden + ) + }) + const run = Command.runWith(command, { version: "1.0.0" }) + + yield* run(["--help"]) + + const helpText = (yield* TestConsole.logLines).join("\n") + expect(helpText).toContain("--visible") + expect(helpText).not.toContain("--experimental-foo") + expect(helpText).not.toContain("Should not appear") + }).pipe(Effect.provide(TestLayer))) + + it.effect("hidden flag still parses on the command line", () => + Effect.gen(function*() { + let captured: string | undefined + const command = Command.make("tool", { + secret: Flag.string("experimental-foo").pipe(Flag.withHidden) + }, (config) => + Effect.sync(() => { + captured = config.secret + })) + const run = Command.runWith(command, { version: "1.0.0" }) + + yield* run(["--experimental-foo", "value"]) + + expect(captured).toBe("value") + }).pipe(Effect.provide(TestLayer))) + it.effect("command help renders examples", () => Effect.gen(function*() { const command = Command.make("login").pipe( From c21635bff5c55b81e6994e007388f01a848de414 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 18 May 2026 15:33:10 +0000 Subject: [PATCH 08/10] fix(cli): suppress hidden flag names in unrecognized-flag suggestions, drop unused FlagDoc.hidden MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Filter hidden flags from suggestion candidates in createUnrecognizedFlagError so a typo cannot leak a hidden flag name. - Remove the unused `hidden` field from `HelpDoc.FlagDoc` — buildHelpDoc filters hidden flags out before constructing FlagDocs, so the field was dead weight on the public API surface. - Add a regression test for the suggestion filter. --- packages/effect/src/unstable/cli/HelpDoc.ts | 8 -------- .../effect/src/unstable/cli/internal/command.ts | 3 +-- .../effect/src/unstable/cli/internal/parser.ts | 1 + packages/effect/test/unstable/cli/Help.test.ts | 16 ++++++++++++++++ 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/packages/effect/src/unstable/cli/HelpDoc.ts b/packages/effect/src/unstable/cli/HelpDoc.ts index bd7ffda7c7..2293c571ad 100644 --- a/packages/effect/src/unstable/cli/HelpDoc.ts +++ b/packages/effect/src/unstable/cli/HelpDoc.ts @@ -188,14 +188,6 @@ export interface FlagDoc { * Whether this flag is required */ readonly required: boolean - - /** - * Whether this flag is hidden from help output. - * - * Hidden flags are still fully parseable on the command line but are omitted - * from rendered help text and shell completions. - */ - readonly hidden?: boolean | undefined } /** diff --git a/packages/effect/src/unstable/cli/internal/command.ts b/packages/effect/src/unstable/cli/internal/command.ts index a0dc8557de..87897fea9e 100644 --- a/packages/effect/src/unstable/cli/internal/command.ts +++ b/packages/effect/src/unstable/cli/internal/command.ts @@ -229,8 +229,7 @@ export const toFlagDoc = (single: Param.Single): aliases: formattedAliases, type: single.typeName ?? Primitive.getTypeName(single.primitiveType), description: appendChoiceKeys(single.description, Primitive.getChoiceKeys(single.primitiveType)), - required: single.primitiveType._tag !== "Boolean", - ...(single.hidden ? { hidden: true } : {}) + required: single.primitiveType._tag !== "Boolean" } } diff --git a/packages/effect/src/unstable/cli/internal/parser.ts b/packages/effect/src/unstable/cli/internal/parser.ts index 6c72c9dcb7..60e738f113 100644 --- a/packages/effect/src/unstable/cli/internal/parser.ts +++ b/packages/effect/src/unstable/cli/internal/parser.ts @@ -517,6 +517,7 @@ const createUnrecognizedFlagError = ( const validNames: Array = [] for (const p of params) { + if (p.hidden) continue validNames.push(p.name) if (Primitive.isBoolean(p.primitiveType)) { validNames.push(`no-${p.name}`) diff --git a/packages/effect/test/unstable/cli/Help.test.ts b/packages/effect/test/unstable/cli/Help.test.ts index 23241bbf2d..9eb64e153d 100644 --- a/packages/effect/test/unstable/cli/Help.test.ts +++ b/packages/effect/test/unstable/cli/Help.test.ts @@ -131,6 +131,22 @@ describe("Command help output", () => { expect(captured).toBe("value") }).pipe(Effect.provide(TestLayer))) + it.effect("hidden flag name does not leak through unrecognized-flag suggestions", () => + Effect.gen(function*() { + const command = Command.make("tool", { + secret: Flag.string("experimental-foo").pipe(Flag.withHidden) + }, () => Effect.void) + const run = Command.runWith(command, { version: "1.0.0" }) + + yield* run(["--experimental-fo", "value"]).pipe( + Effect.catchTag("ShowHelp", () => Effect.void) + ) + + const errorText = (yield* TestConsole.errorLines).join("\n") + const helpText = (yield* TestConsole.logLines).join("\n") + expect(errorText + helpText).not.toContain("experimental-foo") + }).pipe(Effect.provide(TestLayer))) + it.effect("command help renders examples", () => Effect.gen(function*() { const command = Command.make("login").pipe( From 5929e86dcba506d843faab6061c4680d4a486771 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 18 May 2026 17:36:42 +0000 Subject: [PATCH 09/10] docs(cli): drop 'deprecated' from withHidden docstring --- packages/effect/src/unstable/cli/Flag.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/effect/src/unstable/cli/Flag.ts b/packages/effect/src/unstable/cli/Flag.ts index c35db96f40..586be076c2 100644 --- a/packages/effect/src/unstable/cli/Flag.ts +++ b/packages/effect/src/unstable/cli/Flag.ts @@ -491,10 +491,9 @@ export const withMetavar: { * Hides a flag from generated help output and shell completions while keeping * it fully parseable on the command line. * - * Useful for experimental, internal, or deprecated flags that should be - * accepted but not advertised — for example, `--experimental-foo`, debug - * toggles, or escape hatches you are not ready to commit to the public CLI - * surface. + * Useful for experimental or internal flags that should be accepted but not + * advertised — for example, `--experimental-foo`, debug toggles, or escape + * hatches that are not yet committed to the public CLI surface. * * **Example** (Hiding a flag from help) * From 06a4fd98133c0a608e4300501fafcfa3ed3f6a54 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 18 May 2026 17:45:12 +0000 Subject: [PATCH 10/10] docs(cli): explain each hidden-flag filter site --- packages/effect/src/unstable/cli/internal/command.ts | 2 ++ .../src/unstable/cli/internal/completions/descriptor.ts | 2 ++ packages/effect/src/unstable/cli/internal/help.ts | 8 +++++++- packages/effect/src/unstable/cli/internal/parser.ts | 2 ++ 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/effect/src/unstable/cli/internal/command.ts b/packages/effect/src/unstable/cli/internal/command.ts index 87897fea9e..a7de705684 100644 --- a/packages/effect/src/unstable/cli/internal/command.ts +++ b/packages/effect/src/unstable/cli/internal/command.ts @@ -157,6 +157,8 @@ export const makeCommand =