@effect-uai/core 0.5.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{Items-BH8xUkoR.d.mts → Items-pXR5BAgW.d.mts} +12 -12
- package/dist/Items-pXR5BAgW.d.mts.map +1 -0
- package/dist/SandboxError-BvKypkJm.d.mts +158 -0
- package/dist/SandboxError-BvKypkJm.d.mts.map +1 -0
- package/dist/{Tool-87ViKCCO.d.mts → Tool-DP-AwKOf.d.mts} +11 -11
- package/dist/{Tool-87ViKCCO.d.mts.map → Tool-DP-AwKOf.d.mts.map} +1 -1
- package/dist/domain/Items.d.mts +2 -2
- package/dist/domain/Items.mjs +15 -15
- package/dist/domain/Items.mjs.map +1 -1
- package/dist/domain/Turn.d.mts +388 -2
- package/dist/domain/Turn.d.mts.map +1 -0
- package/dist/domain/Turn.mjs +13 -13
- package/dist/domain/Turn.mjs.map +1 -1
- package/dist/index.d.mts +12 -6
- package/dist/index.mjs +9 -3
- package/dist/language-model/LanguageModel.d.mts +29 -27
- package/dist/language-model/LanguageModel.d.mts.map +1 -1
- package/dist/language-model/LanguageModel.mjs +16 -25
- package/dist/language-model/LanguageModel.mjs.map +1 -1
- package/dist/language-model/LanguageModel.test.mjs +10 -81
- package/dist/language-model/LanguageModel.test.mjs.map +1 -1
- package/dist/loop/Loop.d.mts +37 -90
- package/dist/loop/Loop.d.mts.map +1 -1
- package/dist/loop/Loop.mjs +39 -93
- package/dist/loop/Loop.mjs.map +1 -1
- package/dist/loop/Loop.test.mjs +46 -54
- package/dist/loop/Loop.test.mjs.map +1 -1
- package/dist/retry/Retry.d.mts +64 -0
- package/dist/retry/Retry.d.mts.map +1 -0
- package/dist/retry/Retry.mjs +69 -0
- package/dist/retry/Retry.mjs.map +1 -0
- package/dist/retry/Retry.test.mjs +142 -0
- package/dist/retry/Retry.test.mjs.map +1 -0
- package/dist/sandbox/Memory.d.mts +53 -0
- package/dist/sandbox/Memory.d.mts.map +1 -0
- package/dist/sandbox/Memory.mjs +68 -0
- package/dist/sandbox/Memory.mjs.map +1 -0
- package/dist/sandbox/Sandbox.d.mts +717 -0
- package/dist/sandbox/Sandbox.d.mts.map +1 -0
- package/dist/sandbox/Sandbox.mjs +192 -0
- package/dist/sandbox/Sandbox.mjs.map +1 -0
- package/dist/sandbox/SandboxError.d.mts +2 -0
- package/dist/sandbox/SandboxError.mjs +89 -0
- package/dist/sandbox/SandboxError.mjs.map +1 -0
- package/dist/sandbox/SandboxImage.d.mts +28 -0
- package/dist/sandbox/SandboxImage.d.mts.map +1 -0
- package/dist/sandbox/SandboxImage.mjs +32 -0
- package/dist/sandbox/SandboxImage.mjs.map +1 -0
- package/dist/sandbox/SandboxNetwork.d.mts +34 -0
- package/dist/sandbox/SandboxNetwork.d.mts.map +1 -0
- package/dist/sandbox/SandboxNetwork.mjs +36 -0
- package/dist/sandbox/SandboxNetwork.mjs.map +1 -0
- package/dist/speech-synthesizer/SpeechSynthesizer.d.mts +98 -2
- package/dist/speech-synthesizer/SpeechSynthesizer.d.mts.map +1 -1
- package/dist/speech-synthesizer/SpeechSynthesizer.mjs +32 -2
- package/dist/speech-synthesizer/SpeechSynthesizer.mjs.map +1 -1
- package/dist/speech-synthesizer/SpeechSynthesizer.test.mjs +56 -2
- package/dist/speech-synthesizer/SpeechSynthesizer.test.mjs.map +1 -1
- package/dist/testing/MockProvider.d.mts +3 -3
- package/dist/testing/MockProvider.d.mts.map +1 -1
- package/dist/testing/MockProvider.mjs +14 -8
- package/dist/testing/MockProvider.mjs.map +1 -1
- package/dist/testing/MockSandbox.d.mts +604 -0
- package/dist/testing/MockSandbox.d.mts.map +1 -0
- package/dist/testing/MockSandbox.mjs +160 -0
- package/dist/testing/MockSandbox.mjs.map +1 -0
- package/dist/testing/MockSpeechSynthesizer.d.mts +26 -11
- package/dist/testing/MockSpeechSynthesizer.d.mts.map +1 -1
- package/dist/testing/MockSpeechSynthesizer.mjs +75 -51
- package/dist/testing/MockSpeechSynthesizer.mjs.map +1 -1
- package/dist/tool/{Resolvers.d.mts → Approval.d.mts} +27 -27
- package/dist/tool/Approval.d.mts.map +1 -0
- package/dist/tool/{Resolvers.mjs → Approval.mjs} +18 -18
- package/dist/tool/Approval.mjs.map +1 -0
- package/dist/tool/Approval.test.d.mts +1 -0
- package/dist/tool/{Resolvers.test.mjs → Approval.test.mjs} +40 -40
- package/dist/tool/Approval.test.mjs.map +1 -0
- package/dist/tool/HistoryCheck.d.mts +6 -6
- package/dist/tool/HistoryCheck.d.mts.map +1 -1
- package/dist/tool/HistoryCheck.mjs +5 -5
- package/dist/tool/HistoryCheck.mjs.map +1 -1
- package/dist/tool/Tool.d.mts +2 -2
- package/dist/tool/Tool.mjs +4 -4
- package/dist/tool/Tool.mjs.map +1 -1
- package/dist/tool/ToolEvent.d.mts +19 -19
- package/dist/tool/ToolEvent.d.mts.map +1 -1
- package/dist/tool/ToolEvent.mjs +8 -8
- package/dist/tool/ToolEvent.mjs.map +1 -1
- package/dist/tool/{Outcome.d.mts → ToolResult.d.mts} +29 -29
- package/dist/tool/ToolResult.d.mts.map +1 -0
- package/dist/tool/ToolResult.mjs +61 -0
- package/dist/tool/ToolResult.mjs.map +1 -0
- package/dist/tool/Toolkit.d.mts +58 -26
- package/dist/tool/Toolkit.d.mts.map +1 -1
- package/dist/tool/Toolkit.mjs +62 -34
- package/dist/tool/Toolkit.mjs.map +1 -1
- package/dist/tool/Toolkit.test.mjs +13 -13
- package/dist/tool/Toolkit.test.mjs.map +1 -1
- package/package.json +35 -7
- package/src/domain/Items.ts +9 -9
- package/src/domain/Turn.ts +16 -16
- package/src/index.ts +8 -2
- package/src/language-model/LanguageModel.test.ts +21 -102
- package/src/language-model/LanguageModel.ts +36 -59
- package/src/loop/Loop.test.ts +93 -91
- package/src/loop/Loop.ts +72 -170
- package/src/retry/Retry.test.ts +163 -0
- package/src/retry/Retry.ts +84 -0
- package/src/sandbox/Memory.ts +128 -0
- package/src/sandbox/Sandbox.ts +639 -0
- package/src/sandbox/SandboxError.ts +140 -0
- package/src/sandbox/SandboxImage.ts +26 -0
- package/src/sandbox/SandboxNetwork.ts +34 -0
- package/src/speech-synthesizer/SpeechSynthesizer.test.ts +89 -0
- package/src/speech-synthesizer/SpeechSynthesizer.ts +124 -0
- package/src/testing/MockProvider.ts +31 -25
- package/src/testing/MockSandbox.ts +342 -0
- package/src/testing/MockSpeechSynthesizer.ts +137 -77
- package/src/tool/{Resolvers.test.ts → Approval.test.ts} +45 -54
- package/src/tool/{Resolvers.ts → Approval.ts} +25 -25
- package/src/tool/HistoryCheck.ts +10 -13
- package/src/tool/Tool.ts +13 -15
- package/src/tool/ToolEvent.ts +7 -7
- package/src/tool/{Outcome.ts → ToolResult.ts} +20 -20
- package/src/tool/Toolkit.test.ts +13 -13
- package/src/tool/Toolkit.ts +97 -73
- package/dist/Items-BH8xUkoR.d.mts.map +0 -1
- package/dist/Turn-0CwCAyVe.d.mts +0 -388
- package/dist/Turn-0CwCAyVe.d.mts.map +0 -1
- package/dist/tool/Outcome.d.mts.map +0 -1
- package/dist/tool/Outcome.mjs +0 -61
- package/dist/tool/Outcome.mjs.map +0 -1
- package/dist/tool/Resolvers.d.mts.map +0 -1
- package/dist/tool/Resolvers.mjs.map +0 -1
- package/dist/tool/Resolvers.test.mjs.map +0 -1
- /package/dist/{tool/Resolvers.test.d.mts → retry/Retry.test.d.mts} +0 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { n as __exportAll } from "../chunk-uyGKjUfl.mjs";
|
|
2
|
+
import { Data, Effect, Stream } from "effect";
|
|
3
|
+
//#region src/retry/Retry.ts
|
|
4
|
+
var Retry_exports = /* @__PURE__ */ __exportAll({
|
|
5
|
+
Retryable: () => Retryable,
|
|
6
|
+
effect: () => effect,
|
|
7
|
+
isRetryable: () => isRetryable,
|
|
8
|
+
stream: () => stream
|
|
9
|
+
});
|
|
10
|
+
/**
|
|
11
|
+
* Subset-aware retry helpers for `AiError`.
|
|
12
|
+
*
|
|
13
|
+
* Effect's own `Effect.retry` / `Stream.retry` apply the schedule to
|
|
14
|
+
* every failure. For AI calls that's the wrong policy — replaying a
|
|
15
|
+
* `ContentFiltered` or `InvalidRequest` won't change the outcome and
|
|
16
|
+
* just wastes quota. These helpers gate the schedule on the
|
|
17
|
+
* {@link Retryable} subset (rate limits, transport hiccups, timeouts);
|
|
18
|
+
* everything else propagates unchanged.
|
|
19
|
+
*
|
|
20
|
+
* Two carriers:
|
|
21
|
+
* - {@link stream} — for `Stream<A, AiError, R>` (e.g. `streamTurn`,
|
|
22
|
+
* `streamSynthesis`, `streamTranscriptionFrom`). Caveat: the entire
|
|
23
|
+
* stream re-runs on retry, so deltas before the failure replay on
|
|
24
|
+
* the next attempt.
|
|
25
|
+
* - {@link effect} — for `Effect<A, AiError, R>` (e.g. `turn`, `embed`,
|
|
26
|
+
* `embedMany`, `synthesize`, `transcribe`). At-most-once.
|
|
27
|
+
*
|
|
28
|
+
* Composes with `Stream.retry` / `Effect.retry` directly (no naming
|
|
29
|
+
* collision since these live under the `Retry` namespace).
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* Wrapper around the retryable subset of `AiError`. Internal to the
|
|
33
|
+
* lift/retry/unlift dance — exported so callers can build their own
|
|
34
|
+
* variants if needed (e.g. a custom schedule that observes `cause`).
|
|
35
|
+
*/
|
|
36
|
+
var Retryable = class extends Data.TaggedError("RetryableAi") {};
|
|
37
|
+
/** Type-narrowing predicate for the retryable subset. */
|
|
38
|
+
const isRetryable = (e) => e._tag === "RateLimited" || e._tag === "Unavailable" || e._tag === "Timeout";
|
|
39
|
+
/**
|
|
40
|
+
* Retry a `Stream<A, AiError, R>` on the retryable subset. Other
|
|
41
|
+
* failures bypass the schedule and propagate unchanged. The whole
|
|
42
|
+
* stream replays on each attempt — deltas emitted before the failure
|
|
43
|
+
* are re-emitted on the next try.
|
|
44
|
+
*/
|
|
45
|
+
const stream = (schedule) => (s) => s.pipe(Stream.map((value) => ({
|
|
46
|
+
_tag: "Item",
|
|
47
|
+
value
|
|
48
|
+
})), Stream.catchIf(isRetryable, (cause) => Stream.fail(new Retryable({ cause })), (cause) => Stream.succeed({
|
|
49
|
+
_tag: "Terminal",
|
|
50
|
+
cause
|
|
51
|
+
})), Stream.retry(schedule), Stream.catchTag("RetryableAi", (e) => Stream.fail(e.cause)), Stream.flatMap((item) => item._tag === "Item" ? Stream.succeed(item.value) : Stream.fail(item.cause)));
|
|
52
|
+
/**
|
|
53
|
+
* Retry an `Effect<A, AiError, R>` on the retryable subset. Other
|
|
54
|
+
* failures bypass the schedule and propagate unchanged. At-most-once
|
|
55
|
+
* — the underlying call only re-runs if the previous attempt failed
|
|
56
|
+
* with a retryable tag.
|
|
57
|
+
*
|
|
58
|
+
* Skips the lift/unlift dance the Stream variant needs: `Effect.retry`
|
|
59
|
+
* exposes a `while:` predicate so the error channel stays a plain
|
|
60
|
+
* `AiError` throughout.
|
|
61
|
+
*/
|
|
62
|
+
const effect = (schedule) => (eff) => Effect.retry(eff, {
|
|
63
|
+
schedule,
|
|
64
|
+
while: isRetryable
|
|
65
|
+
});
|
|
66
|
+
//#endregion
|
|
67
|
+
export { Retryable, effect, isRetryable, stream, Retry_exports as t };
|
|
68
|
+
|
|
69
|
+
//# sourceMappingURL=Retry.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Retry.mjs","names":[],"sources":["../../src/retry/Retry.ts"],"sourcesContent":["import { Data, Effect, type Schedule, Stream } from \"effect\"\nimport type * as AiError from \"../domain/AiError.js\"\n\n/**\n * Subset-aware retry helpers for `AiError`.\n *\n * Effect's own `Effect.retry` / `Stream.retry` apply the schedule to\n * every failure. For AI calls that's the wrong policy — replaying a\n * `ContentFiltered` or `InvalidRequest` won't change the outcome and\n * just wastes quota. These helpers gate the schedule on the\n * {@link Retryable} subset (rate limits, transport hiccups, timeouts);\n * everything else propagates unchanged.\n *\n * Two carriers:\n * - {@link stream} — for `Stream<A, AiError, R>` (e.g. `streamTurn`,\n * `streamSynthesis`, `streamTranscriptionFrom`). Caveat: the entire\n * stream re-runs on retry, so deltas before the failure replay on\n * the next attempt.\n * - {@link effect} — for `Effect<A, AiError, R>` (e.g. `turn`, `embed`,\n * `embedMany`, `synthesize`, `transcribe`). At-most-once.\n *\n * Composes with `Stream.retry` / `Effect.retry` directly (no naming\n * collision since these live under the `Retry` namespace).\n */\n\n/**\n * Wrapper around the retryable subset of `AiError`. Internal to the\n * lift/retry/unlift dance — exported so callers can build their own\n * variants if needed (e.g. a custom schedule that observes `cause`).\n */\nexport class Retryable extends Data.TaggedError(\"RetryableAi\")<{\n readonly cause: AiError.RateLimited | AiError.Unavailable | AiError.Timeout\n}> {}\n\n/** Type-narrowing predicate for the retryable subset. */\nexport const isRetryable = (\n e: AiError.AiError,\n): e is AiError.RateLimited | AiError.Unavailable | AiError.Timeout =>\n e._tag === \"RateLimited\" || e._tag === \"Unavailable\" || e._tag === \"Timeout\"\n\n// Lift events to Items, non-retryable failures to Terminal values\n// (escape retry), retryable failures to wrapped errors (only thing\n// retry sees).\ntype Lifted<A> =\n | { readonly _tag: \"Item\"; readonly value: A }\n | { readonly _tag: \"Terminal\"; readonly cause: AiError.AiError }\n\n/**\n * Retry a `Stream<A, AiError, R>` on the retryable subset. Other\n * failures bypass the schedule and propagate unchanged. The whole\n * stream replays on each attempt — deltas emitted before the failure\n * are re-emitted on the next try.\n */\nexport const stream =\n <Out>(schedule: Schedule.Schedule<Out, Retryable>) =>\n <A, R>(s: Stream.Stream<A, AiError.AiError, R>): Stream.Stream<A, AiError.AiError, R> =>\n s.pipe(\n Stream.map((value): Lifted<A> => ({ _tag: \"Item\", value })),\n Stream.catchIf(\n isRetryable,\n (cause) => Stream.fail(new Retryable({ cause })),\n (cause) => Stream.succeed<Lifted<A>>({ _tag: \"Terminal\", cause }),\n ),\n Stream.retry(schedule),\n Stream.catchTag(\"RetryableAi\", (e) => Stream.fail<AiError.AiError>(e.cause)),\n Stream.flatMap((item) =>\n item._tag === \"Item\" ? Stream.succeed(item.value) : Stream.fail(item.cause),\n ),\n )\n\n/**\n * Retry an `Effect<A, AiError, R>` on the retryable subset. Other\n * failures bypass the schedule and propagate unchanged. At-most-once\n * — the underlying call only re-runs if the previous attempt failed\n * with a retryable tag.\n *\n * Skips the lift/unlift dance the Stream variant needs: `Effect.retry`\n * exposes a `while:` predicate so the error channel stays a plain\n * `AiError` throughout.\n */\nexport const effect =\n <Out>(schedule: Schedule.Schedule<Out, AiError.AiError>) =>\n <A, R>(eff: Effect.Effect<A, AiError.AiError, R>): Effect.Effect<A, AiError.AiError, R> =>\n Effect.retry(eff, { schedule, while: isRetryable })\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,IAAa,YAAb,cAA+B,KAAK,YAAY,cAAc,CAE3D;;AAGH,MAAa,eACX,MAEA,EAAE,SAAS,iBAAiB,EAAE,SAAS,iBAAiB,EAAE,SAAS;;;;;;;AAerE,MAAa,UACL,cACC,MACL,EAAE,KACA,OAAO,KAAK,WAAsB;CAAE,MAAM;CAAQ;CAAO,EAAE,EAC3D,OAAO,QACL,cACC,UAAU,OAAO,KAAK,IAAI,UAAU,EAAE,OAAO,CAAC,CAAC,GAC/C,UAAU,OAAO,QAAmB;CAAE,MAAM;CAAY;CAAO,CAAC,CAClE,EACD,OAAO,MAAM,SAAS,EACtB,OAAO,SAAS,gBAAgB,MAAM,OAAO,KAAsB,EAAE,MAAM,CAAC,EAC5E,OAAO,SAAS,SACd,KAAK,SAAS,SAAS,OAAO,QAAQ,KAAK,MAAM,GAAG,OAAO,KAAK,KAAK,MAAM,CAC5E,CACF;;;;;;;;;;;AAYL,MAAa,UACL,cACC,QACL,OAAO,MAAM,KAAK;CAAE;CAAU,OAAO;CAAa,CAAC"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { ContentFiltered, RateLimited, Timeout, Unavailable } from "../domain/AiError.mjs";
|
|
2
|
+
import { effect, stream } from "./Retry.mjs";
|
|
3
|
+
import { i as it, n as globalExpect, r as describe } from "../dist-DV5ISja1.mjs";
|
|
4
|
+
import { Cause, Effect, Exit, Option, Ref, Schedule, Stream } from "effect";
|
|
5
|
+
//#region src/retry/Retry.test.ts
|
|
6
|
+
const planned = (attempts, plan) => Stream.unwrap(Ref.getAndUpdate(attempts, (n) => n + 1).pipe(Effect.map((n) => plan[Math.min(n, plan.length - 1)])));
|
|
7
|
+
const plannedEffect = (attempts, plan) => Ref.getAndUpdate(attempts, (n) => n + 1).pipe(Effect.flatMap((n) => plan[Math.min(n, plan.length - 1)]));
|
|
8
|
+
describe("Retry.stream", () => {
|
|
9
|
+
it("retries on RateLimited and yields success on the next attempt", async () => {
|
|
10
|
+
const program = Effect.gen(function* () {
|
|
11
|
+
const attempts = yield* Ref.make(0);
|
|
12
|
+
const s = planned(attempts, [Stream.fail(new RateLimited({
|
|
13
|
+
provider: "mock",
|
|
14
|
+
raw: null
|
|
15
|
+
})), Stream.fromIterable(["a", "b"])]).pipe(stream(Schedule.recurs(3)));
|
|
16
|
+
const out = yield* Stream.runCollect(s);
|
|
17
|
+
const count = yield* Ref.get(attempts);
|
|
18
|
+
return {
|
|
19
|
+
out: Array.from(out),
|
|
20
|
+
count
|
|
21
|
+
};
|
|
22
|
+
});
|
|
23
|
+
const { out, count } = await Effect.runPromise(program);
|
|
24
|
+
globalExpect(count).toBe(2);
|
|
25
|
+
globalExpect(out).toEqual(["a", "b"]);
|
|
26
|
+
});
|
|
27
|
+
it("surfaces the underlying retryable failure when retries are exhausted", async () => {
|
|
28
|
+
const cause = new Unavailable({
|
|
29
|
+
provider: "mock",
|
|
30
|
+
raw: null
|
|
31
|
+
});
|
|
32
|
+
const s = Stream.fail(cause).pipe(stream(Schedule.recurs(2)));
|
|
33
|
+
const exit = await Effect.runPromise(Effect.exit(Stream.runCollect(s)));
|
|
34
|
+
globalExpect(Exit.isFailure(exit)).toBe(true);
|
|
35
|
+
if (Exit.isFailure(exit)) globalExpect(Cause.findErrorOption(exit.cause)).toEqual(Option.some(cause));
|
|
36
|
+
});
|
|
37
|
+
it("bypasses retry for non-retryable AiError (ContentFiltered)", async () => {
|
|
38
|
+
const program = Effect.gen(function* () {
|
|
39
|
+
const attempts = yield* Ref.make(0);
|
|
40
|
+
const cause = new ContentFiltered({
|
|
41
|
+
provider: "mock",
|
|
42
|
+
raw: null
|
|
43
|
+
});
|
|
44
|
+
const s = planned(attempts, [Stream.fail(cause)]).pipe(stream(Schedule.recurs(5)));
|
|
45
|
+
return {
|
|
46
|
+
exit: yield* Effect.exit(Stream.runCollect(s)),
|
|
47
|
+
count: yield* Ref.get(attempts),
|
|
48
|
+
cause
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
const { exit, count, cause } = await Effect.runPromise(program);
|
|
52
|
+
globalExpect(count).toBe(1);
|
|
53
|
+
globalExpect(Exit.isFailure(exit)).toBe(true);
|
|
54
|
+
if (Exit.isFailure(exit)) globalExpect(Cause.findErrorOption(exit.cause)).toEqual(Option.some(cause));
|
|
55
|
+
});
|
|
56
|
+
it("preserves items emitted before a retryable failure (and replays on retry)", async () => {
|
|
57
|
+
const program = Effect.gen(function* () {
|
|
58
|
+
const s = planned(yield* Ref.make(0), [Stream.concat(Stream.succeed("partial"), Stream.fail(new Timeout({
|
|
59
|
+
provider: "mock",
|
|
60
|
+
raw: null
|
|
61
|
+
}))), Stream.fromIterable(["partial", "done"])]).pipe(stream(Schedule.recurs(1)));
|
|
62
|
+
const out = yield* Stream.runCollect(s);
|
|
63
|
+
return Array.from(out);
|
|
64
|
+
});
|
|
65
|
+
globalExpect(await Effect.runPromise(program)).toEqual([
|
|
66
|
+
"partial",
|
|
67
|
+
"partial",
|
|
68
|
+
"done"
|
|
69
|
+
]);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
describe("Retry.effect", () => {
|
|
73
|
+
it("retries on RateLimited and yields success on the next attempt", async () => {
|
|
74
|
+
const program = Effect.gen(function* () {
|
|
75
|
+
const attempts = yield* Ref.make(0);
|
|
76
|
+
return {
|
|
77
|
+
value: yield* plannedEffect(attempts, [Effect.fail(new RateLimited({
|
|
78
|
+
provider: "mock",
|
|
79
|
+
raw: null
|
|
80
|
+
})), Effect.succeed("ok")]).pipe(effect(Schedule.recurs(3))),
|
|
81
|
+
count: yield* Ref.get(attempts)
|
|
82
|
+
};
|
|
83
|
+
});
|
|
84
|
+
const { value, count } = await Effect.runPromise(program);
|
|
85
|
+
globalExpect(count).toBe(2);
|
|
86
|
+
globalExpect(value).toBe("ok");
|
|
87
|
+
});
|
|
88
|
+
it("surfaces the underlying retryable failure when retries are exhausted", async () => {
|
|
89
|
+
const cause = new Unavailable({
|
|
90
|
+
provider: "mock",
|
|
91
|
+
raw: null
|
|
92
|
+
});
|
|
93
|
+
const eff = Effect.fail(cause).pipe(effect(Schedule.recurs(2)));
|
|
94
|
+
const exit = await Effect.runPromise(Effect.exit(eff));
|
|
95
|
+
globalExpect(Exit.isFailure(exit)).toBe(true);
|
|
96
|
+
if (Exit.isFailure(exit)) globalExpect(Cause.findErrorOption(exit.cause)).toEqual(Option.some(cause));
|
|
97
|
+
});
|
|
98
|
+
it("bypasses retry for non-retryable AiError (ContentFiltered)", async () => {
|
|
99
|
+
const program = Effect.gen(function* () {
|
|
100
|
+
const attempts = yield* Ref.make(0);
|
|
101
|
+
const cause = new ContentFiltered({
|
|
102
|
+
provider: "mock",
|
|
103
|
+
raw: null
|
|
104
|
+
});
|
|
105
|
+
return {
|
|
106
|
+
exit: yield* Effect.exit(plannedEffect(attempts, [Effect.fail(cause)]).pipe(effect(Schedule.recurs(5)))),
|
|
107
|
+
count: yield* Ref.get(attempts),
|
|
108
|
+
cause
|
|
109
|
+
};
|
|
110
|
+
});
|
|
111
|
+
const { exit, count, cause } = await Effect.runPromise(program);
|
|
112
|
+
globalExpect(count).toBe(1);
|
|
113
|
+
globalExpect(Exit.isFailure(exit)).toBe(true);
|
|
114
|
+
if (Exit.isFailure(exit)) globalExpect(Cause.findErrorOption(exit.cause)).toEqual(Option.some(cause));
|
|
115
|
+
});
|
|
116
|
+
it("does NOT replay successful prior work — at-most-once semantics", async () => {
|
|
117
|
+
const program = Effect.gen(function* () {
|
|
118
|
+
const attempts = yield* Ref.make(0);
|
|
119
|
+
return {
|
|
120
|
+
value: yield* plannedEffect(attempts, [
|
|
121
|
+
Effect.fail(new Timeout({
|
|
122
|
+
provider: "mock",
|
|
123
|
+
raw: null
|
|
124
|
+
})),
|
|
125
|
+
Effect.fail(new Timeout({
|
|
126
|
+
provider: "mock",
|
|
127
|
+
raw: null
|
|
128
|
+
})),
|
|
129
|
+
Effect.succeed("done")
|
|
130
|
+
]).pipe(effect(Schedule.recurs(5))),
|
|
131
|
+
count: yield* Ref.get(attempts)
|
|
132
|
+
};
|
|
133
|
+
});
|
|
134
|
+
const { value, count } = await Effect.runPromise(program);
|
|
135
|
+
globalExpect(count).toBe(3);
|
|
136
|
+
globalExpect(value).toBe("done");
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
//#endregion
|
|
140
|
+
export {};
|
|
141
|
+
|
|
142
|
+
//# sourceMappingURL=Retry.test.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Retry.test.mjs","names":["AiError.RateLimited","Retry.stream","AiError.Unavailable","AiError.ContentFiltered","AiError.Timeout","Retry.effect"],"sources":["../../src/retry/Retry.test.ts"],"sourcesContent":["import { Cause, Effect, Exit, Option, Ref, Schedule, Stream } from \"effect\"\nimport { describe, expect, it } from \"vitest\"\nimport * as AiError from \"../domain/AiError.js\"\nimport * as Retry from \"./Retry.js\"\n\n// Each call pulls the next plan entry; the last entry repeats forever\n// (so \"succeed on attempt N\" plans naturally re-yield success after).\nconst planned = <A, E>(\n attempts: Ref.Ref<number>,\n plan: ReadonlyArray<Stream.Stream<A, E>>,\n): Stream.Stream<A, E> =>\n Stream.unwrap(\n Ref.getAndUpdate(attempts, (n) => n + 1).pipe(\n Effect.map((n) => plan[Math.min(n, plan.length - 1)]!),\n ),\n )\n\nconst plannedEffect = <A, E>(\n attempts: Ref.Ref<number>,\n plan: ReadonlyArray<Effect.Effect<A, E>>,\n): Effect.Effect<A, E> =>\n Ref.getAndUpdate(attempts, (n) => n + 1).pipe(\n Effect.flatMap((n) => plan[Math.min(n, plan.length - 1)]!),\n )\n\ndescribe(\"Retry.stream\", () => {\n it(\"retries on RateLimited and yields success on the next attempt\", async () => {\n const program = Effect.gen(function* () {\n const attempts = yield* Ref.make(0)\n const s = planned(attempts, [\n Stream.fail<AiError.AiError>(new AiError.RateLimited({ provider: \"mock\", raw: null })),\n Stream.fromIterable([\"a\", \"b\"]),\n ]).pipe(Retry.stream(Schedule.recurs(3)))\n const out = yield* Stream.runCollect(s)\n const count = yield* Ref.get(attempts)\n return { out: Array.from(out), count }\n })\n\n const { out, count } = await Effect.runPromise(program)\n expect(count).toBe(2)\n expect(out).toEqual([\"a\", \"b\"])\n })\n\n it(\"surfaces the underlying retryable failure when retries are exhausted\", async () => {\n const cause = new AiError.Unavailable({ provider: \"mock\", raw: null })\n const s = Stream.fail<AiError.AiError>(cause).pipe(Retry.stream(Schedule.recurs(2)))\n\n const exit = await Effect.runPromise(Effect.exit(Stream.runCollect(s)))\n expect(Exit.isFailure(exit)).toBe(true)\n if (Exit.isFailure(exit)) {\n expect(Cause.findErrorOption(exit.cause)).toEqual(Option.some(cause))\n }\n })\n\n it(\"bypasses retry for non-retryable AiError (ContentFiltered)\", async () => {\n const program = Effect.gen(function* () {\n const attempts = yield* Ref.make(0)\n const cause = new AiError.ContentFiltered({ provider: \"mock\", raw: null })\n const s = planned(attempts, [Stream.fail<AiError.AiError>(cause)]).pipe(\n Retry.stream(Schedule.recurs(5)),\n )\n const exit = yield* Effect.exit(Stream.runCollect(s))\n const count = yield* Ref.get(attempts)\n return { exit, count, cause }\n })\n\n const { exit, count, cause } = await Effect.runPromise(program)\n expect(count).toBe(1)\n expect(Exit.isFailure(exit)).toBe(true)\n if (Exit.isFailure(exit)) {\n expect(Cause.findErrorOption(exit.cause)).toEqual(Option.some(cause))\n }\n })\n\n it(\"preserves items emitted before a retryable failure (and replays on retry)\", async () => {\n // Documents the 'replays on retry' caveat: the entire stream re-runs.\n const program = Effect.gen(function* () {\n const attempts = yield* Ref.make(0)\n const s = planned(attempts, [\n Stream.concat(\n Stream.succeed(\"partial\"),\n Stream.fail<AiError.AiError>(new AiError.Timeout({ provider: \"mock\", raw: null })),\n ),\n Stream.fromIterable([\"partial\", \"done\"]),\n ]).pipe(Retry.stream(Schedule.recurs(1)))\n const out = yield* Stream.runCollect(s)\n return Array.from(out)\n })\n\n const out = await Effect.runPromise(program)\n expect(out).toEqual([\"partial\", \"partial\", \"done\"])\n })\n})\n\ndescribe(\"Retry.effect\", () => {\n it(\"retries on RateLimited and yields success on the next attempt\", async () => {\n const program = Effect.gen(function* () {\n const attempts = yield* Ref.make(0)\n const value = yield* plannedEffect(attempts, [\n Effect.fail<AiError.AiError>(new AiError.RateLimited({ provider: \"mock\", raw: null })),\n Effect.succeed(\"ok\"),\n ]).pipe(Retry.effect(Schedule.recurs(3)))\n const count = yield* Ref.get(attempts)\n return { value, count }\n })\n\n const { value, count } = await Effect.runPromise(program)\n expect(count).toBe(2)\n expect(value).toBe(\"ok\")\n })\n\n it(\"surfaces the underlying retryable failure when retries are exhausted\", async () => {\n const cause = new AiError.Unavailable({ provider: \"mock\", raw: null })\n const eff = Effect.fail<AiError.AiError>(cause).pipe(Retry.effect(Schedule.recurs(2)))\n\n const exit = await Effect.runPromise(Effect.exit(eff))\n expect(Exit.isFailure(exit)).toBe(true)\n if (Exit.isFailure(exit)) {\n expect(Cause.findErrorOption(exit.cause)).toEqual(Option.some(cause))\n }\n })\n\n it(\"bypasses retry for non-retryable AiError (ContentFiltered)\", async () => {\n const program = Effect.gen(function* () {\n const attempts = yield* Ref.make(0)\n const cause = new AiError.ContentFiltered({ provider: \"mock\", raw: null })\n const exit = yield* Effect.exit(\n plannedEffect(attempts, [Effect.fail<AiError.AiError>(cause)]).pipe(\n Retry.effect(Schedule.recurs(5)),\n ),\n )\n const count = yield* Ref.get(attempts)\n return { exit, count, cause }\n })\n\n const { exit, count, cause } = await Effect.runPromise(program)\n expect(count).toBe(1)\n expect(Exit.isFailure(exit)).toBe(true)\n if (Exit.isFailure(exit)) {\n expect(Cause.findErrorOption(exit.cause)).toEqual(Option.some(cause))\n }\n })\n\n it(\"does NOT replay successful prior work — at-most-once semantics\", async () => {\n // Contrast with Retry.stream: an Effect.retry only re-invokes the\n // failing computation. No replay of work done before the failure\n // (there's nothing to replay — Effects are single-shot).\n const program = Effect.gen(function* () {\n const attempts = yield* Ref.make(0)\n const value = yield* plannedEffect(attempts, [\n Effect.fail<AiError.AiError>(new AiError.Timeout({ provider: \"mock\", raw: null })),\n Effect.fail<AiError.AiError>(new AiError.Timeout({ provider: \"mock\", raw: null })),\n Effect.succeed(\"done\"),\n ]).pipe(Retry.effect(Schedule.recurs(5)))\n const count = yield* Ref.get(attempts)\n return { value, count }\n })\n\n const { value, count } = await Effect.runPromise(program)\n expect(count).toBe(3)\n expect(value).toBe(\"done\")\n })\n})\n"],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Brand } from "effect";
|
|
2
|
+
|
|
3
|
+
//#region src/sandbox/Memory.d.ts
|
|
4
|
+
declare namespace Memory_d_exports {
|
|
5
|
+
export { Input, Memory, bytes, fromInputUnsafe, gb, gib, kb, kib, mb, mib, toBytes };
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* A memory / data size, always represented internally as a number of
|
|
9
|
+
* bytes. Mirrors the `Duration` shape from `effect` — a branded value
|
|
10
|
+
* type with a wider `Input` union for ergonomic construction at call
|
|
11
|
+
* sites.
|
|
12
|
+
*
|
|
13
|
+
* `effect` itself has no byte-count primitive (only `Duration`), so we
|
|
14
|
+
* own this here rather than leak provider-specific shapes (Deno's
|
|
15
|
+
* template-literal `Memory`, Vercel's `mb`, Modal's `gpuMemory`, …)
|
|
16
|
+
* through the cross-provider `Sandbox` request types.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* import * as Memory from "@effect-uai/core/Memory"
|
|
21
|
+
*
|
|
22
|
+
* Memory.gib(2) // 2 GiB → 2_147_483_648 bytes
|
|
23
|
+
* Memory.fromInputUnsafe("1.5 GiB") // human-string parsing
|
|
24
|
+
* Memory.fromInputUnsafe(1_280_000_000) // raw bytes
|
|
25
|
+
* Memory.toBytes(m) // unwrap
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
type Memory = Brand.Branded<number, "Memory">;
|
|
29
|
+
type Input = number | Memory | string;
|
|
30
|
+
/**
|
|
31
|
+
* Coerce a {@link Input} to a {@link Memory}. Throws on malformed
|
|
32
|
+
* strings or non-finite numbers. Mirrors `Duration.fromInputUnsafe`.
|
|
33
|
+
*/
|
|
34
|
+
declare const fromInputUnsafe: (input: Input) => Memory;
|
|
35
|
+
/** Return the underlying byte count. */
|
|
36
|
+
declare const toBytes: (m: Memory | Input) => number;
|
|
37
|
+
/** N bytes. */
|
|
38
|
+
declare const bytes: (n: number) => Memory;
|
|
39
|
+
/** N KiB (binary kibibytes, `n * 1024`). */
|
|
40
|
+
declare const kib: (n: number) => Memory;
|
|
41
|
+
/** N MiB (binary mebibytes, `n * 1024^2`). */
|
|
42
|
+
declare const mib: (n: number) => Memory;
|
|
43
|
+
/** N GiB (binary gibibytes, `n * 1024^3`). */
|
|
44
|
+
declare const gib: (n: number) => Memory;
|
|
45
|
+
/** N kB (decimal kilobytes, `n * 1000`). */
|
|
46
|
+
declare const kb: (n: number) => Memory;
|
|
47
|
+
/** N MB (decimal megabytes, `n * 1_000_000`). */
|
|
48
|
+
declare const mb: (n: number) => Memory;
|
|
49
|
+
/** N GB (decimal gigabytes, `n * 1_000_000_000`). */
|
|
50
|
+
declare const gb: (n: number) => Memory;
|
|
51
|
+
//#endregion
|
|
52
|
+
export { Input, Memory, bytes, fromInputUnsafe, gb, gib, kb, kib, mb, mib, Memory_d_exports as t, toBytes };
|
|
53
|
+
//# sourceMappingURL=Memory.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Memory.d.mts","names":[],"sources":["../../src/sandbox/Memory.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAwBA;;;;;KAAY,MAAA,GAAS,KAAA,CAAM,OAAA;AAAA,KAuBf,KAAA,YAAiB,MAAA;;;;AAqC7B;cAAa,eAAA,GAAmB,KAAA,EAAO,KAAA,KAAQ,MAAA;;cAelC,OAAA,GAAW,CAAA,EAAG,MAAA,GAAS,KAAA;;cAUvB,KAAA,GAAS,CAAA,aAAY,MAAA;;cAGrB,GAAA,GAAO,CAAA,aAAY,MAAA;;cAGnB,GAAA,GAAO,CAAA,aAAY,MAAA;;cAGnB,GAAA,GAAO,CAAA,aAAY,MAAA;;cAGnB,EAAA,GAAM,CAAA,aAAY,MAAA;;cAGlB,EAAA,GAAM,CAAA,aAAY,MAAA;;cAGlB,EAAA,GAAM,CAAA,aAAY,MAAA"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { n as __exportAll } from "../chunk-uyGKjUfl.mjs";
|
|
2
|
+
import { Brand } from "effect";
|
|
3
|
+
//#region src/sandbox/Memory.ts
|
|
4
|
+
var Memory_exports = /* @__PURE__ */ __exportAll({
|
|
5
|
+
bytes: () => bytes,
|
|
6
|
+
fromInputUnsafe: () => fromInputUnsafe,
|
|
7
|
+
gb: () => gb,
|
|
8
|
+
gib: () => gib,
|
|
9
|
+
kb: () => kb,
|
|
10
|
+
kib: () => kib,
|
|
11
|
+
mb: () => mb,
|
|
12
|
+
mib: () => mib,
|
|
13
|
+
toBytes: () => toBytes
|
|
14
|
+
});
|
|
15
|
+
const make = Brand.nominal();
|
|
16
|
+
const UNIT_BYTES = {
|
|
17
|
+
b: 1,
|
|
18
|
+
kb: 1e3,
|
|
19
|
+
mb: 1e6,
|
|
20
|
+
gb: 1e9,
|
|
21
|
+
tb: 0xe8d4a51000,
|
|
22
|
+
kib: 1024,
|
|
23
|
+
mib: 1024 * 1024,
|
|
24
|
+
gib: 1024 * 1024 * 1024,
|
|
25
|
+
tib: 1024 * 1024 * 1024 * 1024
|
|
26
|
+
};
|
|
27
|
+
const PARSE_RE = /^\s*(\d+(?:\.\d+)?)\s*([a-zA-Z]*)\s*$/;
|
|
28
|
+
const parseString = (s) => {
|
|
29
|
+
const m = PARSE_RE.exec(s);
|
|
30
|
+
if (m === null) throw new Error(`Memory: cannot parse "${s}" — expected forms like "1024", "10 MB", "1GiB"`);
|
|
31
|
+
const value = Number(m[1]);
|
|
32
|
+
const unit = (m[2] ?? "").toLowerCase();
|
|
33
|
+
if (unit === "") return value;
|
|
34
|
+
const factor = UNIT_BYTES[unit];
|
|
35
|
+
if (factor === void 0) throw new Error(`Memory: unknown unit "${m[2]}" in "${s}" — supported: ${Object.keys(UNIT_BYTES).join(", ")}`);
|
|
36
|
+
return value * factor;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Coerce a {@link Input} to a {@link Memory}. Throws on malformed
|
|
40
|
+
* strings or non-finite numbers. Mirrors `Duration.fromInputUnsafe`.
|
|
41
|
+
*/
|
|
42
|
+
const fromInputUnsafe = (input) => {
|
|
43
|
+
if (typeof input === "number") {
|
|
44
|
+
if (!Number.isFinite(input) || input < 0) throw new Error(`Memory: bytes must be a non-negative finite number, got ${input}`);
|
|
45
|
+
return make(Math.floor(input));
|
|
46
|
+
}
|
|
47
|
+
return make(Math.floor(parseString(input)));
|
|
48
|
+
};
|
|
49
|
+
/** Return the underlying byte count. */
|
|
50
|
+
const toBytes = (m) => typeof m === "number" && m === Math.floor(m) && m >= 0 ? m : fromInputUnsafe(m);
|
|
51
|
+
/** N bytes. */
|
|
52
|
+
const bytes = (n) => make(Math.floor(n));
|
|
53
|
+
/** N KiB (binary kibibytes, `n * 1024`). */
|
|
54
|
+
const kib = (n) => make(Math.floor(n * 1024));
|
|
55
|
+
/** N MiB (binary mebibytes, `n * 1024^2`). */
|
|
56
|
+
const mib = (n) => make(Math.floor(n * 1024 * 1024));
|
|
57
|
+
/** N GiB (binary gibibytes, `n * 1024^3`). */
|
|
58
|
+
const gib = (n) => make(Math.floor(n * 1024 * 1024 * 1024));
|
|
59
|
+
/** N kB (decimal kilobytes, `n * 1000`). */
|
|
60
|
+
const kb = (n) => make(Math.floor(n * 1e3));
|
|
61
|
+
/** N MB (decimal megabytes, `n * 1_000_000`). */
|
|
62
|
+
const mb = (n) => make(Math.floor(n * 1e6));
|
|
63
|
+
/** N GB (decimal gigabytes, `n * 1_000_000_000`). */
|
|
64
|
+
const gb = (n) => make(Math.floor(n * 1e9));
|
|
65
|
+
//#endregion
|
|
66
|
+
export { bytes, fromInputUnsafe, gb, gib, kb, kib, mb, mib, Memory_exports as t, toBytes };
|
|
67
|
+
|
|
68
|
+
//# sourceMappingURL=Memory.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Memory.mjs","names":[],"sources":["../../src/sandbox/Memory.ts"],"sourcesContent":["import { Brand } from \"effect\"\n\n/**\n * A memory / data size, always represented internally as a number of\n * bytes. Mirrors the `Duration` shape from `effect` — a branded value\n * type with a wider `Input` union for ergonomic construction at call\n * sites.\n *\n * `effect` itself has no byte-count primitive (only `Duration`), so we\n * own this here rather than leak provider-specific shapes (Deno's\n * template-literal `Memory`, Vercel's `mb`, Modal's `gpuMemory`, …)\n * through the cross-provider `Sandbox` request types.\n *\n * @example\n * ```ts\n * import * as Memory from \"@effect-uai/core/Memory\"\n *\n * Memory.gib(2) // 2 GiB → 2_147_483_648 bytes\n * Memory.fromInputUnsafe(\"1.5 GiB\") // human-string parsing\n * Memory.fromInputUnsafe(1_280_000_000) // raw bytes\n * Memory.toBytes(m) // unwrap\n * ```\n */\n\nexport type Memory = Brand.Branded<number, \"Memory\">\n\nconst make = Brand.nominal<Memory>()\n\n// ---------------------------------------------------------------------------\n// Input — what the public APIs accept.\n//\n// Supported string forms (case-insensitive on the unit, whitespace\n// optional between number and unit):\n//\n// \"<n>\" bytes (no unit)\n// \"<n> B\" bytes\n// \"<n> kB\" 1_000 bytes (decimal)\n// \"<n> MB\" 1_000_000 (decimal)\n// \"<n> GB\" 1_000_000_000 (decimal)\n// \"<n> KiB\" 1_024 (binary)\n// \"<n> MiB\" 1_048_576 (binary)\n// \"<n> GiB\" 1_073_741_824 (binary)\n//\n// Decimal vs binary follows the SI / IEC convention (`MB` = 10^6,\n// `MiB` = 2^20). When in doubt, prefer the binary `*iB` units.\n// ---------------------------------------------------------------------------\n\nexport type Input = number | Memory | string\n\nconst UNIT_BYTES: Record<string, number> = {\n b: 1,\n kb: 1_000,\n mb: 1_000_000,\n gb: 1_000_000_000,\n tb: 1_000_000_000_000,\n kib: 1_024,\n mib: 1_024 * 1_024,\n gib: 1_024 * 1_024 * 1_024,\n tib: 1_024 * 1_024 * 1_024 * 1_024,\n}\n\nconst PARSE_RE = /^\\s*(\\d+(?:\\.\\d+)?)\\s*([a-zA-Z]*)\\s*$/\n\nconst parseString = (s: string): number => {\n const m = PARSE_RE.exec(s)\n if (m === null) {\n throw new Error(`Memory: cannot parse \"${s}\" — expected forms like \"1024\", \"10 MB\", \"1GiB\"`)\n }\n const value = Number(m[1])\n const unit = (m[2] ?? \"\").toLowerCase()\n if (unit === \"\") return value\n const factor = UNIT_BYTES[unit]\n if (factor === undefined) {\n throw new Error(\n `Memory: unknown unit \"${m[2]}\" in \"${s}\" — supported: ${Object.keys(UNIT_BYTES).join(\", \")}`,\n )\n }\n return value * factor\n}\n\n/**\n * Coerce a {@link Input} to a {@link Memory}. Throws on malformed\n * strings or non-finite numbers. Mirrors `Duration.fromInputUnsafe`.\n */\nexport const fromInputUnsafe = (input: Input): Memory => {\n if (typeof input === \"number\") {\n if (!Number.isFinite(input) || input < 0) {\n throw new Error(`Memory: bytes must be a non-negative finite number, got ${input}`)\n }\n return make(Math.floor(input))\n }\n return make(Math.floor(parseString(input)))\n}\n\n// ---------------------------------------------------------------------------\n// Unwrap\n// ---------------------------------------------------------------------------\n\n/** Return the underlying byte count. */\nexport const toBytes = (m: Memory | Input): number =>\n typeof m === \"number\" && (m as number) === Math.floor(m as number) && (m as number) >= 0\n ? (m as number)\n : (fromInputUnsafe(m) as unknown as number)\n\n// ---------------------------------------------------------------------------\n// Unit constructors — terse for use at call sites.\n// ---------------------------------------------------------------------------\n\n/** N bytes. */\nexport const bytes = (n: number): Memory => make(Math.floor(n))\n\n/** N KiB (binary kibibytes, `n * 1024`). */\nexport const kib = (n: number): Memory => make(Math.floor(n * 1_024))\n\n/** N MiB (binary mebibytes, `n * 1024^2`). */\nexport const mib = (n: number): Memory => make(Math.floor(n * 1_024 * 1_024))\n\n/** N GiB (binary gibibytes, `n * 1024^3`). */\nexport const gib = (n: number): Memory => make(Math.floor(n * 1_024 * 1_024 * 1_024))\n\n/** N kB (decimal kilobytes, `n * 1000`). */\nexport const kb = (n: number): Memory => make(Math.floor(n * 1_000))\n\n/** N MB (decimal megabytes, `n * 1_000_000`). */\nexport const mb = (n: number): Memory => make(Math.floor(n * 1_000_000))\n\n/** N GB (decimal gigabytes, `n * 1_000_000_000`). */\nexport const gb = (n: number): Memory => make(Math.floor(n * 1_000_000_000))\n"],"mappings":";;;;;;;;;;;;;;AA0BA,MAAM,OAAO,MAAM,SAAiB;AAuBpC,MAAM,aAAqC;CACzC,GAAG;CACH,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,KAAK;CACL,KAAK,OAAQ;CACb,KAAK,OAAQ,OAAQ;CACrB,KAAK,OAAQ,OAAQ,OAAQ;CAC9B;AAED,MAAM,WAAW;AAEjB,MAAM,eAAe,MAAsB;CACzC,MAAM,IAAI,SAAS,KAAK,EAAE;AAC1B,KAAI,MAAM,KACR,OAAM,IAAI,MAAM,yBAAyB,EAAE,iDAAiD;CAE9F,MAAM,QAAQ,OAAO,EAAE,GAAG;CAC1B,MAAM,QAAQ,EAAE,MAAM,IAAI,aAAa;AACvC,KAAI,SAAS,GAAI,QAAO;CACxB,MAAM,SAAS,WAAW;AAC1B,KAAI,WAAW,KAAA,EACb,OAAM,IAAI,MACR,yBAAyB,EAAE,GAAG,QAAQ,EAAE,iBAAiB,OAAO,KAAK,WAAW,CAAC,KAAK,KAAK,GAC5F;AAEH,QAAO,QAAQ;;;;;;AAOjB,MAAa,mBAAmB,UAAyB;AACvD,KAAI,OAAO,UAAU,UAAU;AAC7B,MAAI,CAAC,OAAO,SAAS,MAAM,IAAI,QAAQ,EACrC,OAAM,IAAI,MAAM,2DAA2D,QAAQ;AAErF,SAAO,KAAK,KAAK,MAAM,MAAM,CAAC;;AAEhC,QAAO,KAAK,KAAK,MAAM,YAAY,MAAM,CAAC,CAAC;;;AAQ7C,MAAa,WAAW,MACtB,OAAO,MAAM,YAAa,MAAiB,KAAK,MAAM,EAAY,IAAK,KAAgB,IAClF,IACA,gBAAgB,EAAE;;AAOzB,MAAa,SAAS,MAAsB,KAAK,KAAK,MAAM,EAAE,CAAC;;AAG/D,MAAa,OAAO,MAAsB,KAAK,KAAK,MAAM,IAAI,KAAM,CAAC;;AAGrE,MAAa,OAAO,MAAsB,KAAK,KAAK,MAAM,IAAI,OAAQ,KAAM,CAAC;;AAG7E,MAAa,OAAO,MAAsB,KAAK,KAAK,MAAM,IAAI,OAAQ,OAAQ,KAAM,CAAC;;AAGrF,MAAa,MAAM,MAAsB,KAAK,KAAK,MAAM,IAAI,IAAM,CAAC;;AAGpE,MAAa,MAAM,MAAsB,KAAK,KAAK,MAAM,IAAI,IAAU,CAAC;;AAGxE,MAAa,MAAM,MAAsB,KAAK,KAAK,MAAM,IAAI,IAAc,CAAC"}
|