@effect-uai/ui 0.19.0-dev-20261110065930 → 0.20.0-dev-20261110202152

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/Atoms.d.mts CHANGED
@@ -7,7 +7,7 @@ import { Effect, Schema } from "effect";
7
7
  import { Atom } from "effect/reactivity";
8
8
  import { SecureRandom } from "effect/crypto";
9
9
  declare namespace Atoms_d_exports {
10
- export { Busy, Decode, Options, Runtime, SessionOptions, make, session, sessionThread, thread };
10
+ export { Busy, Decode, Open, Options, Runtime, SessionOptions, make, session, sessionThread, thread };
11
11
  }
12
12
  /**
13
13
  * What `submit` does while a run is in flight. `"block"` ignores the new
@@ -103,19 +103,26 @@ type BaseSessionOptions<I, S, E, R, ER, D> = {
103
103
  readonly ended?: (state: S, reason: "stopped" | "disconnected") => S;
104
104
  };
105
105
  type SessionOptions<I, S, E, R, ER, D = Envelope> = BaseSessionOptions<I, S, E, R, ER, D> & WithDecode<D>;
106
+ /** What `open` follows: a session from its start, or from `after` (e.g. a hydrated thread's `lastSeq`). */
107
+ type Open = StreamId | {
108
+ readonly streamId: StreamId;
109
+ readonly after: number;
110
+ };
106
111
  declare const buildSession: <I, S, E, R, ER, D>(options: BaseSessionOptions<I, S, E, R, ER, D> & {
107
112
  readonly decode: Decode<D>;
108
113
  }) => {
109
114
  state: Atom.Writable<S, S>;
110
115
  streamId: Atom.Writable<(string & import("effect/Brand").Brand<"StreamId">) | null, (string & import("effect/Brand").Brand<"StreamId">) | null>;
111
- open: Atom.AtomResultFn<string & import("effect/Brand").Brand<"StreamId">, unknown, unknown>;
116
+ open: Atom.AtomResultFn<Open, unknown, unknown>;
112
117
  submit: Atom.AtomResultFn<Draft<I>, unknown, unknown>;
113
118
  stop: Atom.AtomResultFn<void, void, E | ER>;
114
119
  };
115
120
  type BuiltSession<I, S, E, R, ER> = ReturnType<typeof buildSession<I, S, E, R, ER, unknown>>;
116
121
  /**
117
122
  * State over a session transport. `open(streamId)` follows a session's
118
- * stream from its start (e.g. a chat id kept across reloads). `submit` sends
123
+ * stream from its start (e.g. a chat id kept across reloads);
124
+ * `open({ streamId, after })` from a position, for state hydrated elsewhere
125
+ * (pass it as `initial`; `initial` never opens anything). `submit` sends
119
126
  * an input; without an open session the server starts one, which is then
120
127
  * opened. Runs arrive on the followed stream, not as responses to `submit`.
121
128
  */
@@ -124,6 +131,8 @@ type SessionThreadOptions<I, E, R, ER, P extends Payloads> = {
124
131
  readonly runtime: Runtime<R, ER>;
125
132
  readonly transport: Session<Sent<I>, Thread<P>, E, R>;
126
133
  readonly isBusy?: (thread: Thread<P>) => boolean;
134
+ /** A thread hydrated elsewhere (e.g. from a database); follow it with `open({ streamId, after: lastSeq })`. */
135
+ readonly initial?: Thread<P>;
127
136
  };
128
137
  /**
129
138
  * `session` with the `Thread` preset. Input sent while a run is active, or
@@ -137,5 +146,5 @@ declare function sessionThread<I extends Input, E, R, ER, P extends Payloads>(op
137
146
  }): BuiltSession<I, Thread<P>, E, R, ER>;
138
147
  declare function sessionThread<I extends Input, E, R, ER>(options: SessionThreadOptions<I, E, R, ER, Untyped>): BuiltSession<I, Thread, E, R, ER>;
139
148
  //#endregion
140
- export { Busy, Decode, Options, Runtime, SessionOptions, make, session, sessionThread, Atoms_d_exports as t, thread };
149
+ export { Busy, Decode, Open, Options, Runtime, SessionOptions, make, session, sessionThread, Atoms_d_exports as t, thread };
141
150
  //# sourceMappingURL=Atoms.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Atoms.d.mts","names":[],"sources":["../src/Atoms.ts"],"mappings":"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"}
1
+ {"version":3,"file":"Atoms.d.mts","names":[],"sources":["../src/Atoms.ts"],"mappings":"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"}
package/dist/Atoms.mjs CHANGED
@@ -86,7 +86,13 @@ const buildSession = (options) => {
86
86
  const ended = options.ended;
87
87
  return ended === void 0 || Exit.isSuccess(exit) || Exit.hasInterrupts(exit) ? Effect.void : Atom.update(state, (s) => ended(s, "disconnected"));
88
88
  };
89
- const open = options.runtime.fn()((id) => Atom.set(streamId, id).pipe(Effect.andThen(options.transport.events(id, 0).pipe(Stream.runForEach((envelope) => options.decode(envelope).pipe(Effect.flatMap((decoded) => Atom.update(state, (s) => options.step(s, decoded))))))), Effect.onExit(settle))).pipe(Atom.keepAlive);
89
+ const open = options.runtime.fn()((target) => {
90
+ const { streamId: id, after } = typeof target === "string" ? {
91
+ streamId: target,
92
+ after: 0
93
+ } : target;
94
+ return Atom.set(streamId, id).pipe(Effect.andThen(options.transport.events(id, after).pipe(Stream.runForEach((envelope) => options.decode(envelope).pipe(Effect.flatMap((decoded) => Atom.update(state, (s) => options.step(s, decoded))))))), Effect.onExit(settle));
95
+ }).pipe(Atom.keepAlive);
90
96
  return {
91
97
  state,
92
98
  streamId,
@@ -118,7 +124,7 @@ function sessionThread(options) {
118
124
  return buildSession({
119
125
  runtime: options.runtime,
120
126
  transport: options.transport,
121
- initial: fold.initial,
127
+ initial: options.initial ?? fold.initial,
122
128
  step: fold.step,
123
129
  decode: options.contract?.decode ?? identity,
124
130
  ...options.isBusy !== void 0 ? { isBusy: options.isBusy } : {},
@@ -1 +1 @@
1
- {"version":3,"file":"Atoms.mjs","names":["Input.withId","Thread.presetFor"],"sources":["../src/Atoms.ts"],"sourcesContent":["/**\n * Framework-neutral state over a transport, built on `effect/reactivity`.\n * Per-run transports: `make` takes any fold (`initial` + `step`), `thread`\n * is the `Thread` preset. Session transports: `session` and\n * `sessionThread`, likewise. React reads them with `@effect/atom-react` (`useAtomValue`,\n * `useAtomSet`), Vue and Solid with their official bindings, plain JS with\n * `AtomRegistry.subscribe`.\n *\n * With a `UiContract`, every envelope is decoded once on arrival, so the\n * state holds decoded payloads and is typed by the contract.\n */\nimport { Effect, Exit, Option, type Schema, Stream } from \"effect\"\nimport type { SecureRandom } from \"effect/crypto\"\nimport { Atom } from \"effect/reactivity\"\nimport * as Input from \"./Input.js\"\nimport type { Envelope, Payloads, StreamId, Untyped } from \"./RunEvent.js\"\nimport * as Thread from \"./Thread.js\"\nimport type * as Transport from \"./Transport.js\"\nimport type { UiContract } from \"./UiContract.js\"\n\n/**\n * What `submit` does while a run is in flight. `\"block\"` ignores the new\n * input; `\"stop-then-submit\"` abandons the current run locally and starts\n * the new one (in durable mode the server-side run keeps going).\n */\nexport type Busy = \"block\" | \"stop-then-submit\"\n\n/**\n * The atoms' runtime: the transport's services plus `SecureRandom` for\n * input ids, e.g. `Atom.runtime(Layer.mergeAll(FetchHttpClient.layer,\n * SecureRandom.layerWebCrypto))`. A test provides a seeded one.\n */\nexport type Runtime<R, ER> = Atom.AtomRuntime<R | SecureRandom.SecureRandom, ER>\n\n/** Turns a wire envelope into what `step` folds (a contract's `decode`). */\nexport type Decode<D> = (envelope: Envelope) => Effect.Effect<D, Schema.SchemaError>\n\n// `decode` may be left out only when `step` folds wire envelopes.\ntype WithDecode<D> = [Envelope] extends [D]\n ? { readonly decode?: Decode<D> }\n : { readonly decode: Decode<D> }\n\n/**\n * `I` is the app's input type. `submit` takes a draft of it (`id` optional);\n * the transport receives it with the id assigned (`Input.Sent<I>`, a UUIDv7\n * from `SecureRandom`). `D` is the envelope `step` folds: the wire envelope,\n * or a contract's decoded one (then pass `decode`).\n */\ntype BaseOptions<I, S, E, R, ER, D> = {\n readonly runtime: Runtime<R, ER>\n readonly transport: Transport.Transport<Input.Sent<I>, S, E, R>\n readonly initial: S\n readonly step: (state: S, envelope: D) => S\n readonly busy: Busy\n readonly isBusy?: (state: S) => boolean\n /** Optimistic update applied before the request goes out. */\n readonly submitted?: (state: S, input: Input.Sent<I>) => S\n /** Applied when a run's stream ends without its terminal. */\n readonly ended?: (state: S, reason: \"stopped\" | \"disconnected\") => S\n}\n\nexport type Options<I, S, E, R, ER, D = Envelope> = BaseOptions<I, S, E, R, ER, D> & WithDecode<D>\n\nconst identity: Decode<Envelope> = Effect.succeed\n\nconst build = <I, S, E, R, ER, D>(\n options: BaseOptions<I, S, E, R, ER, D> & { readonly decode: Decode<D> },\n) => {\n const state = Atom.make(options.initial).pipe(Atom.keepAlive)\n const streamId = Atom.make<StreamId | null>(null).pipe(Atom.keepAlive)\n\n const follow = (envelopes: Stream.Stream<Envelope, E, R>) =>\n envelopes.pipe(\n Stream.runForEach((envelope) =>\n options\n .decode(envelope)\n .pipe(\n Effect.flatMap((decoded) =>\n Atom.set(streamId, envelope.streamId).pipe(\n Effect.andThen(Atom.update(state, (s) => options.step(s, decoded))),\n ),\n ),\n ),\n ),\n Effect.onExit((exit) => {\n const ended = options.ended\n if (Exit.isSuccess(exit) || ended === undefined) return Effect.void\n const reason = Exit.hasInterrupts(exit) ? \"stopped\" : \"disconnected\"\n return Atom.update(state, (s) => ended(s, reason))\n }),\n )\n\n // Writing to a non-concurrent `Atom.fn` interrupts its previous run, so the\n // streaming lives in `run` and `submit` is a concurrent gate that applies\n // the busy policy before anything is interrupted. Every fn atom is kept\n // alive: an unobserved one is disposed, which interrupts its fiber.\n const run = options.runtime\n .fn<Input.Sent<I>>()((input) =>\n Effect.gen(function* () {\n const submitted = options.submitted\n if (submitted !== undefined) yield* Atom.update(state, (s) => submitted(s, input))\n yield* follow(options.transport.start(input, yield* Atom.get(state)))\n }),\n )\n .pipe(Atom.keepAlive)\n\n const submit = options.runtime\n .fn<Input.Draft<I>>()(\n (draft) =>\n Effect.gen(function* () {\n const blocked =\n options.busy === \"block\" && options.isBusy?.(yield* Atom.get(state)) === true\n if (blocked) return\n const input = yield* Input.withId(draft)\n yield* Atom.set(run, input)\n }),\n { concurrent: true },\n )\n .pipe(Atom.keepAlive)\n\n // Durable: ask the server (its `run-stopped` arrives on the stream).\n // Direct: interrupt the request; `ended` settles the run locally.\n const stop = options.runtime\n .fn<void>()(() =>\n Effect.gen(function* () {\n const id = yield* Atom.get(streamId)\n const serverStop = options.transport.stop\n if (serverStop !== undefined && id !== null) return yield* serverStop(id)\n yield* Atom.set(run, Atom.Interrupt)\n }),\n )\n .pipe(Atom.keepAlive)\n\n const resume = options.transport.resume\n const attach =\n resume === undefined\n ? undefined\n : options.runtime\n .fn<{ readonly streamId: StreamId; readonly after: number }>()((arg) =>\n follow(resume(arg.streamId, arg.after)),\n )\n .pipe(Atom.keepAlive)\n\n return {\n state,\n streamId,\n submit,\n /** The current run; `AtomRegistry.getResult` on it waits for the run to finish. */\n run,\n stop,\n /** Re-attach to a run (`{ streamId, after }`); set for durable transports. */\n resume: attach,\n }\n}\n\ntype Built<I, S, E, R, ER> = ReturnType<typeof build<I, S, E, R, ER, unknown>>\n\n/**\n * State over a per-run transport: `state`, `submit`, `stop`, `run`, and\n * `resume` when the transport can re-attach (`Transport.durable`,\n * `durableHttp`), typed as present for those.\n */\nexport function make<I, S, E, R, ER, D = Envelope>(\n options: Options<I, S, E, R, ER, D> & {\n readonly transport: Transport.Durable<Input.Sent<I>, S, E, R>\n },\n): Omit<Built<I, S, E, R, ER>, \"resume\"> & {\n readonly resume: NonNullable<Built<I, S, E, R, ER>[\"resume\"]>\n}\nexport function make<I, S, E, R, ER, D = Envelope>(\n options: Options<I, S, E, R, ER, D>,\n): Built<I, S, E, R, ER>\nexport function make<I, S, E, R, ER>(\n options: Options<I, S, E, R, ER, Envelope>,\n): Built<I, S, E, R, ER> {\n return build({ ...options, decode: options.decode ?? identity })\n}\n\ntype ThreadOptions<I, E, R, ER, P extends Payloads> = {\n readonly runtime: Runtime<R, ER>\n readonly transport: Transport.Transport<Input.Sent<I>, Thread.Thread<P>, E, R>\n readonly busy: Busy\n readonly initial?: Thread.Thread<P>\n}\n\n/**\n * `make` with the `Thread` preset: a message list plus the active run. Takes\n * the `Input` preset, with or without typed `data`. With `contract`, the\n * state is typed by it. For other entry types, spread a `Thread.make(config)`\n * fold into `make`.\n */\nexport function thread<I extends Input.Input, E, R, ER, P extends Payloads>(\n options: ThreadOptions<I, E, R, ER, P> & { readonly contract: UiContract<P> },\n): Built<I, Thread.Thread<P>, E, R, ER>\nexport function thread<I extends Input.Input, E, R, ER>(\n options: ThreadOptions<I, E, R, ER, Untyped>,\n): Built<I, Thread.Thread, E, R, ER>\n// The implementation sees `P` as `any`: the options both read and receive\n// `Thread<P>`, so no single `P` fits both overloads. Callers get the overloads.\nexport function thread<I extends Input.Input, E, R, ER>(\n options: ThreadOptions<I, E, R, ER, any> & { readonly contract?: UiContract<any> },\n): Built<I, Thread.Thread<any>, E, R, ER> {\n const fold = Thread.presetFor<any>()\n return build({\n runtime: options.runtime,\n transport: options.transport,\n busy: options.busy,\n initial: options.initial ?? fold.initial,\n step: fold.step,\n decode: options.contract?.decode ?? identity,\n isBusy: (t) => t.active !== null,\n submitted: (t, input: Input.Sent<I>) => fold.submitted(t, input),\n ended: fold.ended,\n })\n}\n\n// ---------------------------------------------------------------------------\n// Sessions: one stream carries every run (a durable chat)\n// ---------------------------------------------------------------------------\n\ntype BaseSessionOptions<I, S, E, R, ER, D> = {\n readonly runtime: Runtime<R, ER>\n readonly transport: Transport.Session<Input.Sent<I>, S, E, R>\n readonly initial: S\n readonly step: (state: S, envelope: D) => S\n /** While true, `submit` ignores new input (the server may also refuse). */\n readonly isBusy?: (state: S) => boolean\n /** Optimistic update applied before the input is sent. */\n readonly submitted?: (state: S, input: Input.Sent<I>) => S\n /** Applied when sending fails or the session's stream gives up. */\n readonly ended?: (state: S, reason: \"stopped\" | \"disconnected\") => S\n}\n\nexport type SessionOptions<I, S, E, R, ER, D = Envelope> = BaseSessionOptions<I, S, E, R, ER, D> &\n WithDecode<D>\n\nconst buildSession = <I, S, E, R, ER, D>(\n options: BaseSessionOptions<I, S, E, R, ER, D> & { readonly decode: Decode<D> },\n) => {\n const state = Atom.make(options.initial).pipe(Atom.keepAlive)\n const streamId = Atom.make<StreamId | null>(null).pipe(Atom.keepAlive)\n\n const settle = (exit: Exit.Exit<unknown, unknown>) => {\n const ended = options.ended\n return ended === undefined || Exit.isSuccess(exit) || Exit.hasInterrupts(exit)\n ? Effect.void\n : Atom.update(state, (s) => ended(s, \"disconnected\"))\n }\n\n // Non-concurrent: opening another session stops following the previous one.\n const open = options.runtime\n .fn<StreamId>()((id) =>\n Atom.set(streamId, id).pipe(\n Effect.andThen(\n options.transport\n .events(id, 0)\n .pipe(\n Stream.runForEach((envelope) =>\n options\n .decode(envelope)\n .pipe(\n Effect.flatMap((decoded) =>\n Atom.update(state, (s) => options.step(s, decoded)),\n ),\n ),\n ),\n ),\n ),\n Effect.onExit(settle),\n ),\n )\n .pipe(Atom.keepAlive)\n\n const submit = options.runtime\n .fn<Input.Draft<I>>()(\n (draft) =>\n Effect.gen(function* () {\n if (options.isBusy?.(yield* Atom.get(state)) === true) return\n const input = yield* Input.withId(draft)\n const submitted = options.submitted\n if (submitted !== undefined) yield* Atom.update(state, (s) => submitted(s, input))\n const opened = Option.fromNullishOr(yield* Atom.get(streamId))\n const sent = yield* options.transport.send(opened, input, yield* Atom.get(state))\n if (Option.isNone(opened)) yield* Atom.set(open, sent)\n }).pipe(Effect.onExit(settle)),\n { concurrent: true },\n )\n .pipe(Atom.keepAlive)\n\n // The server stops the active run; its `run-stopped` arrives on the stream.\n const stop = options.runtime\n .fn<void>()(() =>\n Effect.gen(function* () {\n const id = yield* Atom.get(streamId)\n const serverStop = options.transport.stop\n if (serverStop !== undefined && id !== null) yield* serverStop(id)\n }),\n )\n .pipe(Atom.keepAlive)\n\n return { state, streamId, open, submit, stop }\n}\n\ntype BuiltSession<I, S, E, R, ER> = ReturnType<typeof buildSession<I, S, E, R, ER, unknown>>\n\n/**\n * State over a session transport. `open(streamId)` follows a session's\n * stream from its start (e.g. a chat id kept across reloads). `submit` sends\n * an input; without an open session the server starts one, which is then\n * opened. Runs arrive on the followed stream, not as responses to `submit`.\n */\nexport function session<I, S, E, R, ER, D = Envelope>(\n options: SessionOptions<I, S, E, R, ER, D>,\n): BuiltSession<I, S, E, R, ER>\nexport function session<I, S, E, R, ER>(\n options: SessionOptions<I, S, E, R, ER, Envelope>,\n): BuiltSession<I, S, E, R, ER> {\n return buildSession({ ...options, decode: options.decode ?? identity })\n}\n\ntype SessionThreadOptions<I, E, R, ER, P extends Payloads> = {\n readonly runtime: Runtime<R, ER>\n readonly transport: Transport.Session<Input.Sent<I>, Thread.Thread<P>, E, R>\n readonly isBusy?: (thread: Thread.Thread<P>) => boolean\n}\n\n/**\n * `session` with the `Thread` preset. Input sent while a run is active, or\n * echoed by the server (`input-accepted`), waits in `thread.pending`: the\n * server queues it. Pass `isBusy` for a server that refuses instead. With\n * `contract`, the state is typed by it. For other entry types, spread a\n * `Thread.make(config)` fold into `session`.\n */\nexport function sessionThread<I extends Input.Input, E, R, ER, P extends Payloads>(\n options: SessionThreadOptions<I, E, R, ER, P> & { readonly contract: UiContract<P> },\n): BuiltSession<I, Thread.Thread<P>, E, R, ER>\nexport function sessionThread<I extends Input.Input, E, R, ER>(\n options: SessionThreadOptions<I, E, R, ER, Untyped>,\n): BuiltSession<I, Thread.Thread, E, R, ER>\n// `any` for `P` in the implementation only, as in `thread`.\nexport function sessionThread<I extends Input.Input, E, R, ER>(\n options: SessionThreadOptions<I, E, R, ER, any> & { readonly contract?: UiContract<any> },\n): BuiltSession<I, Thread.Thread<any>, E, R, ER> {\n const fold = Thread.presetFor<any>()\n return buildSession({\n runtime: options.runtime,\n transport: options.transport,\n initial: fold.initial,\n step: fold.step,\n decode: options.contract?.decode ?? identity,\n ...(options.isBusy !== undefined ? { isBusy: options.isBusy } : {}),\n submitted: (t, input: Input.Sent<I>) => fold.submitted(t, input),\n ended: fold.ended,\n })\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"Atoms.mjs","names":["Input.withId","Thread.presetFor"],"sources":["../src/Atoms.ts"],"sourcesContent":["/**\n * Framework-neutral state over a transport, built on `effect/reactivity`.\n * Per-run transports: `make` takes any fold (`initial` + `step`), `thread`\n * is the `Thread` preset. Session transports: `session` and\n * `sessionThread`, likewise. React reads them with `@effect/atom-react` (`useAtomValue`,\n * `useAtomSet`), Vue and Solid with their official bindings, plain JS with\n * `AtomRegistry.subscribe`.\n *\n * With a `UiContract`, every envelope is decoded once on arrival, so the\n * state holds decoded payloads and is typed by the contract.\n */\nimport { Effect, Exit, Option, type Schema, Stream } from \"effect\"\nimport type { SecureRandom } from \"effect/crypto\"\nimport { Atom } from \"effect/reactivity\"\nimport * as Input from \"./Input.js\"\nimport type { Envelope, Payloads, StreamId, Untyped } from \"./RunEvent.js\"\nimport * as Thread from \"./Thread.js\"\nimport type * as Transport from \"./Transport.js\"\nimport type { UiContract } from \"./UiContract.js\"\n\n/**\n * What `submit` does while a run is in flight. `\"block\"` ignores the new\n * input; `\"stop-then-submit\"` abandons the current run locally and starts\n * the new one (in durable mode the server-side run keeps going).\n */\nexport type Busy = \"block\" | \"stop-then-submit\"\n\n/**\n * The atoms' runtime: the transport's services plus `SecureRandom` for\n * input ids, e.g. `Atom.runtime(Layer.mergeAll(FetchHttpClient.layer,\n * SecureRandom.layerWebCrypto))`. A test provides a seeded one.\n */\nexport type Runtime<R, ER> = Atom.AtomRuntime<R | SecureRandom.SecureRandom, ER>\n\n/** Turns a wire envelope into what `step` folds (a contract's `decode`). */\nexport type Decode<D> = (envelope: Envelope) => Effect.Effect<D, Schema.SchemaError>\n\n// `decode` may be left out only when `step` folds wire envelopes.\ntype WithDecode<D> = [Envelope] extends [D]\n ? { readonly decode?: Decode<D> }\n : { readonly decode: Decode<D> }\n\n/**\n * `I` is the app's input type. `submit` takes a draft of it (`id` optional);\n * the transport receives it with the id assigned (`Input.Sent<I>`, a UUIDv7\n * from `SecureRandom`). `D` is the envelope `step` folds: the wire envelope,\n * or a contract's decoded one (then pass `decode`).\n */\ntype BaseOptions<I, S, E, R, ER, D> = {\n readonly runtime: Runtime<R, ER>\n readonly transport: Transport.Transport<Input.Sent<I>, S, E, R>\n readonly initial: S\n readonly step: (state: S, envelope: D) => S\n readonly busy: Busy\n readonly isBusy?: (state: S) => boolean\n /** Optimistic update applied before the request goes out. */\n readonly submitted?: (state: S, input: Input.Sent<I>) => S\n /** Applied when a run's stream ends without its terminal. */\n readonly ended?: (state: S, reason: \"stopped\" | \"disconnected\") => S\n}\n\nexport type Options<I, S, E, R, ER, D = Envelope> = BaseOptions<I, S, E, R, ER, D> & WithDecode<D>\n\nconst identity: Decode<Envelope> = Effect.succeed\n\nconst build = <I, S, E, R, ER, D>(\n options: BaseOptions<I, S, E, R, ER, D> & { readonly decode: Decode<D> },\n) => {\n const state = Atom.make(options.initial).pipe(Atom.keepAlive)\n const streamId = Atom.make<StreamId | null>(null).pipe(Atom.keepAlive)\n\n const follow = (envelopes: Stream.Stream<Envelope, E, R>) =>\n envelopes.pipe(\n Stream.runForEach((envelope) =>\n options\n .decode(envelope)\n .pipe(\n Effect.flatMap((decoded) =>\n Atom.set(streamId, envelope.streamId).pipe(\n Effect.andThen(Atom.update(state, (s) => options.step(s, decoded))),\n ),\n ),\n ),\n ),\n Effect.onExit((exit) => {\n const ended = options.ended\n if (Exit.isSuccess(exit) || ended === undefined) return Effect.void\n const reason = Exit.hasInterrupts(exit) ? \"stopped\" : \"disconnected\"\n return Atom.update(state, (s) => ended(s, reason))\n }),\n )\n\n // Writing to a non-concurrent `Atom.fn` interrupts its previous run, so the\n // streaming lives in `run` and `submit` is a concurrent gate that applies\n // the busy policy before anything is interrupted. Every fn atom is kept\n // alive: an unobserved one is disposed, which interrupts its fiber.\n const run = options.runtime\n .fn<Input.Sent<I>>()((input) =>\n Effect.gen(function* () {\n const submitted = options.submitted\n if (submitted !== undefined) yield* Atom.update(state, (s) => submitted(s, input))\n yield* follow(options.transport.start(input, yield* Atom.get(state)))\n }),\n )\n .pipe(Atom.keepAlive)\n\n const submit = options.runtime\n .fn<Input.Draft<I>>()(\n (draft) =>\n Effect.gen(function* () {\n const blocked =\n options.busy === \"block\" && options.isBusy?.(yield* Atom.get(state)) === true\n if (blocked) return\n const input = yield* Input.withId(draft)\n yield* Atom.set(run, input)\n }),\n { concurrent: true },\n )\n .pipe(Atom.keepAlive)\n\n // Durable: ask the server (its `run-stopped` arrives on the stream).\n // Direct: interrupt the request; `ended` settles the run locally.\n const stop = options.runtime\n .fn<void>()(() =>\n Effect.gen(function* () {\n const id = yield* Atom.get(streamId)\n const serverStop = options.transport.stop\n if (serverStop !== undefined && id !== null) return yield* serverStop(id)\n yield* Atom.set(run, Atom.Interrupt)\n }),\n )\n .pipe(Atom.keepAlive)\n\n const resume = options.transport.resume\n const attach =\n resume === undefined\n ? undefined\n : options.runtime\n .fn<{ readonly streamId: StreamId; readonly after: number }>()((arg) =>\n follow(resume(arg.streamId, arg.after)),\n )\n .pipe(Atom.keepAlive)\n\n return {\n state,\n streamId,\n submit,\n /** The current run; `AtomRegistry.getResult` on it waits for the run to finish. */\n run,\n stop,\n /** Re-attach to a run (`{ streamId, after }`); set for durable transports. */\n resume: attach,\n }\n}\n\ntype Built<I, S, E, R, ER> = ReturnType<typeof build<I, S, E, R, ER, unknown>>\n\n/**\n * State over a per-run transport: `state`, `submit`, `stop`, `run`, and\n * `resume` when the transport can re-attach (`Transport.durable`,\n * `durableHttp`), typed as present for those.\n */\nexport function make<I, S, E, R, ER, D = Envelope>(\n options: Options<I, S, E, R, ER, D> & {\n readonly transport: Transport.Durable<Input.Sent<I>, S, E, R>\n },\n): Omit<Built<I, S, E, R, ER>, \"resume\"> & {\n readonly resume: NonNullable<Built<I, S, E, R, ER>[\"resume\"]>\n}\nexport function make<I, S, E, R, ER, D = Envelope>(\n options: Options<I, S, E, R, ER, D>,\n): Built<I, S, E, R, ER>\nexport function make<I, S, E, R, ER>(\n options: Options<I, S, E, R, ER, Envelope>,\n): Built<I, S, E, R, ER> {\n return build({ ...options, decode: options.decode ?? identity })\n}\n\ntype ThreadOptions<I, E, R, ER, P extends Payloads> = {\n readonly runtime: Runtime<R, ER>\n readonly transport: Transport.Transport<Input.Sent<I>, Thread.Thread<P>, E, R>\n readonly busy: Busy\n readonly initial?: Thread.Thread<P>\n}\n\n/**\n * `make` with the `Thread` preset: a message list plus the active run. Takes\n * the `Input` preset, with or without typed `data`. With `contract`, the\n * state is typed by it. For other entry types, spread a `Thread.make(config)`\n * fold into `make`.\n */\nexport function thread<I extends Input.Input, E, R, ER, P extends Payloads>(\n options: ThreadOptions<I, E, R, ER, P> & { readonly contract: UiContract<P> },\n): Built<I, Thread.Thread<P>, E, R, ER>\nexport function thread<I extends Input.Input, E, R, ER>(\n options: ThreadOptions<I, E, R, ER, Untyped>,\n): Built<I, Thread.Thread, E, R, ER>\n// The implementation sees `P` as `any`: the options both read and receive\n// `Thread<P>`, so no single `P` fits both overloads. Callers get the overloads.\nexport function thread<I extends Input.Input, E, R, ER>(\n options: ThreadOptions<I, E, R, ER, any> & { readonly contract?: UiContract<any> },\n): Built<I, Thread.Thread<any>, E, R, ER> {\n const fold = Thread.presetFor<any>()\n return build({\n runtime: options.runtime,\n transport: options.transport,\n busy: options.busy,\n initial: options.initial ?? fold.initial,\n step: fold.step,\n decode: options.contract?.decode ?? identity,\n isBusy: (t) => t.active !== null,\n submitted: (t, input: Input.Sent<I>) => fold.submitted(t, input),\n ended: fold.ended,\n })\n}\n\n// ---------------------------------------------------------------------------\n// Sessions: one stream carries every run (a durable chat)\n// ---------------------------------------------------------------------------\n\ntype BaseSessionOptions<I, S, E, R, ER, D> = {\n readonly runtime: Runtime<R, ER>\n readonly transport: Transport.Session<Input.Sent<I>, S, E, R>\n readonly initial: S\n readonly step: (state: S, envelope: D) => S\n /** While true, `submit` ignores new input (the server may also refuse). */\n readonly isBusy?: (state: S) => boolean\n /** Optimistic update applied before the input is sent. */\n readonly submitted?: (state: S, input: Input.Sent<I>) => S\n /** Applied when sending fails or the session's stream gives up. */\n readonly ended?: (state: S, reason: \"stopped\" | \"disconnected\") => S\n}\n\nexport type SessionOptions<I, S, E, R, ER, D = Envelope> = BaseSessionOptions<I, S, E, R, ER, D> &\n WithDecode<D>\n\n/** What `open` follows: a session from its start, or from `after` (e.g. a hydrated thread's `lastSeq`). */\nexport type Open = StreamId | { readonly streamId: StreamId; readonly after: number }\n\nconst buildSession = <I, S, E, R, ER, D>(\n options: BaseSessionOptions<I, S, E, R, ER, D> & { readonly decode: Decode<D> },\n) => {\n const state = Atom.make(options.initial).pipe(Atom.keepAlive)\n const streamId = Atom.make<StreamId | null>(null).pipe(Atom.keepAlive)\n\n const settle = (exit: Exit.Exit<unknown, unknown>) => {\n const ended = options.ended\n return ended === undefined || Exit.isSuccess(exit) || Exit.hasInterrupts(exit)\n ? Effect.void\n : Atom.update(state, (s) => ended(s, \"disconnected\"))\n }\n\n // Non-concurrent: opening another session stops following the previous one.\n const open = options.runtime\n .fn<Open>()((target) => {\n const { streamId: id, after } =\n typeof target === \"string\" ? { streamId: target, after: 0 } : target\n return Atom.set(streamId, id).pipe(\n Effect.andThen(\n options.transport\n .events(id, after)\n .pipe(\n Stream.runForEach((envelope) =>\n options\n .decode(envelope)\n .pipe(\n Effect.flatMap((decoded) =>\n Atom.update(state, (s) => options.step(s, decoded)),\n ),\n ),\n ),\n ),\n ),\n Effect.onExit(settle),\n )\n })\n .pipe(Atom.keepAlive)\n\n const submit = options.runtime\n .fn<Input.Draft<I>>()(\n (draft) =>\n Effect.gen(function* () {\n if (options.isBusy?.(yield* Atom.get(state)) === true) return\n const input = yield* Input.withId(draft)\n const submitted = options.submitted\n if (submitted !== undefined) yield* Atom.update(state, (s) => submitted(s, input))\n const opened = Option.fromNullishOr(yield* Atom.get(streamId))\n const sent = yield* options.transport.send(opened, input, yield* Atom.get(state))\n if (Option.isNone(opened)) yield* Atom.set(open, sent)\n }).pipe(Effect.onExit(settle)),\n { concurrent: true },\n )\n .pipe(Atom.keepAlive)\n\n // The server stops the active run; its `run-stopped` arrives on the stream.\n const stop = options.runtime\n .fn<void>()(() =>\n Effect.gen(function* () {\n const id = yield* Atom.get(streamId)\n const serverStop = options.transport.stop\n if (serverStop !== undefined && id !== null) yield* serverStop(id)\n }),\n )\n .pipe(Atom.keepAlive)\n\n return { state, streamId, open, submit, stop }\n}\n\ntype BuiltSession<I, S, E, R, ER> = ReturnType<typeof buildSession<I, S, E, R, ER, unknown>>\n\n/**\n * State over a session transport. `open(streamId)` follows a session's\n * stream from its start (e.g. a chat id kept across reloads);\n * `open({ streamId, after })` from a position, for state hydrated elsewhere\n * (pass it as `initial`; `initial` never opens anything). `submit` sends\n * an input; without an open session the server starts one, which is then\n * opened. Runs arrive on the followed stream, not as responses to `submit`.\n */\nexport function session<I, S, E, R, ER, D = Envelope>(\n options: SessionOptions<I, S, E, R, ER, D>,\n): BuiltSession<I, S, E, R, ER>\nexport function session<I, S, E, R, ER>(\n options: SessionOptions<I, S, E, R, ER, Envelope>,\n): BuiltSession<I, S, E, R, ER> {\n return buildSession({ ...options, decode: options.decode ?? identity })\n}\n\ntype SessionThreadOptions<I, E, R, ER, P extends Payloads> = {\n readonly runtime: Runtime<R, ER>\n readonly transport: Transport.Session<Input.Sent<I>, Thread.Thread<P>, E, R>\n readonly isBusy?: (thread: Thread.Thread<P>) => boolean\n /** A thread hydrated elsewhere (e.g. from a database); follow it with `open({ streamId, after: lastSeq })`. */\n readonly initial?: Thread.Thread<P>\n}\n\n/**\n * `session` with the `Thread` preset. Input sent while a run is active, or\n * echoed by the server (`input-accepted`), waits in `thread.pending`: the\n * server queues it. Pass `isBusy` for a server that refuses instead. With\n * `contract`, the state is typed by it. For other entry types, spread a\n * `Thread.make(config)` fold into `session`.\n */\nexport function sessionThread<I extends Input.Input, E, R, ER, P extends Payloads>(\n options: SessionThreadOptions<I, E, R, ER, P> & { readonly contract: UiContract<P> },\n): BuiltSession<I, Thread.Thread<P>, E, R, ER>\nexport function sessionThread<I extends Input.Input, E, R, ER>(\n options: SessionThreadOptions<I, E, R, ER, Untyped>,\n): BuiltSession<I, Thread.Thread, E, R, ER>\n// `any` for `P` in the implementation only, as in `thread`.\nexport function sessionThread<I extends Input.Input, E, R, ER>(\n options: SessionThreadOptions<I, E, R, ER, any> & { readonly contract?: UiContract<any> },\n): BuiltSession<I, Thread.Thread<any>, E, R, ER> {\n const fold = Thread.presetFor<any>()\n return buildSession({\n runtime: options.runtime,\n transport: options.transport,\n initial: options.initial ?? fold.initial,\n step: fold.step,\n decode: options.contract?.decode ?? identity,\n ...(options.isBusy !== undefined ? { isBusy: options.isBusy } : {}),\n submitted: (t, input: Input.Sent<I>) => fold.submitted(t, input),\n ended: fold.ended,\n })\n}\n"],"mappings":"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"}
@@ -203,17 +203,18 @@ declare const ndjson: (request: HttpClientRequest.HttpClientRequest) => Stream.S
203
203
  * Keep events flowing across dropped connections: `connect(after)` is
204
204
  * re-invoked with the last seen `seq` on failure, per `schedule`; duplicates
205
205
  * are dropped. A connection that ends cleanly before the end counts as a
206
- * failure (`Disconnected`), so it is retried too.
206
+ * failure (`Disconnected`), so it is retried too. Without `schedule` nothing
207
+ * is retried: an early end just fails with `Disconnected`.
207
208
  *
208
209
  * `until: "terminal"` (default) follows one run and completes after its
209
210
  * terminal. `until: "never"` follows a session stream that carries many runs
210
211
  * (a durable chat) and never completes on its own.
211
212
  */
212
- declare const resumable: <E, R, X>(connect: (after: number) => Stream.Stream<Envelope, E, R>, options: {
213
+ declare const resumable: <E, R, X = never>(connect: (after: number) => Stream.Stream<Envelope, E, R>, options?: {
213
214
  readonly from?: number;
214
215
  readonly until?: "terminal" | "never";
215
- readonly schedule: Schedule.Schedule<X, E | Disconnected>;
216
+ readonly schedule?: Schedule.Schedule<X, E | Disconnected>;
216
217
  }) => Stream.Stream<Envelope, E | Disconnected, R>;
217
218
  //#endregion
218
219
  export { ndjson as a, execute as i, Client_d_exports as n, resumable as o, Disconnected as r, sse as s, ClientError as t };
219
- //# sourceMappingURL=Client-C9UYN_VK.d.mts.map
220
+ //# sourceMappingURL=Client-B2Eqo39q.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Client-C9UYN_VK.d.mts","names":[],"sources":["../src/Client.ts"],"mappings":";;;;;;;;;;;cAaa,qBAAqB;WAA4C;;;KAGlE,cACR,gBAAgB,kBAChB,OAAO,cACP,IAAI,QACJ,IAAI,WACJ,OAAO;;cAGE,UAAO,SAAa,kBAAkB,sBAAiB,OAAA,gDAAA,oBAAA,gBAAA,iBAAA,WAAA;;cASvD,MAAG,SAAa,kBAAkB,sBAAiB,OAAA,OAAA,UAAA,gBAAA,kBAAA,IAAA,QAAA,OAAA,cAAA,IAAA,UAAA,WAAA;;cAInD,SAAM,SAAa,kBAAkB,sBAAiB,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GACuB,gBAAA,kBAAA,OAAA,cAAA,OAAA,aAAA,WAAA;;;;;;;;;;;cAY7E,YAAa,GAAG,GAAG,GAAC,UACrB,kBAAkB,OAAO,OAAO,UAAU,GAAG,IAAE;WAE9C;WACA;WACA,UAAU,SAAS,SAAS,GAAG,IAAI;MAE7C,OAAO,OAAO,UAAU,IAAI,cAAc"}
1
+ {"version":3,"file":"Client-B2Eqo39q.d.mts","names":[],"sources":["../src/Client.ts"],"mappings":";;;;;;;;;;;cAaa,qBAAqB;WAA4C;;;KAGlE,cACR,gBAAgB,kBAChB,OAAO,cACP,IAAI,QACJ,IAAI,WACJ,OAAO;;cAGE,UAAO,SAAa,kBAAkB,sBAAiB,OAAA,gDAAA,oBAAA,gBAAA,iBAAA,WAAA;;cASvD,MAAG,SAAa,kBAAkB,sBAAiB,OAAA,OAAA,UAAA,gBAAA,kBAAA,IAAA,QAAA,OAAA,cAAA,IAAA,UAAA,WAAA;;cAInD,SAAM,SAAa,kBAAkB,sBAAiB,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GACuB,gBAAA,kBAAA,OAAA,cAAA,OAAA,aAAA,WAAA;;;;;;;;;;;;cAa7E,YAAa,GAAG,GAAG,WAAS,UAC7B,kBAAkB,OAAO,OAAO,UAAU,GAAG,IAAE;WAE9C;WACA;WACA,WAAW,SAAS,SAAS,GAAG,IAAI;MAE9C,OAAO,OAAO,UAAU,IAAI,cAAc"}
package/dist/Client.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { a as ndjson, i as execute, o as resumable, r as Disconnected, s as sse, t as ClientError } from "./Client-C9UYN_VK.mjs";
1
+ import { a as ndjson, i as execute, o as resumable, r as Disconnected, s as sse, t as ClientError } from "./Client-B2Eqo39q.mjs";
2
2
  export { ClientError, Disconnected, execute, ndjson, resumable, sse };
package/dist/Client.mjs CHANGED
@@ -31,23 +31,26 @@ const ndjson = (request) => body(request).pipe(Stream.pipeThroughChannelOrFail(N
31
31
  * Keep events flowing across dropped connections: `connect(after)` is
32
32
  * re-invoked with the last seen `seq` on failure, per `schedule`; duplicates
33
33
  * are dropped. A connection that ends cleanly before the end counts as a
34
- * failure (`Disconnected`), so it is retried too.
34
+ * failure (`Disconnected`), so it is retried too. Without `schedule` nothing
35
+ * is retried: an early end just fails with `Disconnected`.
35
36
  *
36
37
  * `until: "terminal"` (default) follows one run and completes after its
37
38
  * terminal. `until: "never"` follows a session stream that carries many runs
38
39
  * (a durable chat) and never completes on its own.
39
40
  */
40
41
  const resumable = (connect, options) => Stream.unwrap(Effect.gen(function* () {
41
- const untilTerminal = options.until !== "never";
42
+ const untilTerminal = options?.until !== "never";
42
43
  const ends = (envelope) => untilTerminal && isTerminal(envelope.event);
43
44
  const position = yield* Ref.make({
44
- seq: options.from ?? 0,
45
+ seq: options?.from ?? 0,
45
46
  done: false
46
47
  });
47
- return Stream.unwrap(Ref.get(position).pipe(Effect.map(({ seq: after }) => connect(after).pipe(Stream.filterEffect((envelope) => Ref.modify(position, (p) => envelope.seq > p.seq ? [true, {
48
+ const attempt = Stream.unwrap(Ref.get(position).pipe(Effect.map(({ seq: after }) => connect(after).pipe(Stream.filterEffect((envelope) => Ref.modify(position, (p) => envelope.seq > p.seq ? [true, {
48
49
  seq: envelope.seq,
49
50
  done: ends(envelope)
50
- }] : [false, p])), Stream.takeUntil(ends), Stream.concat(Stream.unwrap(Ref.get(position).pipe(Effect.map((p) => p.done ? Stream.empty : Stream.fail(new Disconnected({ lastSeq: p.seq })))))))))).pipe(Stream.retry(options.schedule));
51
+ }] : [false, p])), Stream.takeUntil(ends), Stream.concat(Stream.unwrap(Ref.get(position).pipe(Effect.map((p) => p.done ? Stream.empty : Stream.fail(new Disconnected({ lastSeq: p.seq }))))))))));
52
+ const schedule = options?.schedule;
53
+ return schedule === void 0 ? attempt : attempt.pipe(Stream.retry(schedule));
51
54
  }));
52
55
  //#endregion
53
56
  export { Disconnected, execute, ndjson, resumable, sse, Client_exports as t };
@@ -1 +1 @@
1
- {"version":3,"file":"Client.mjs","names":["SseEncoding.decode"],"sources":["../src/Client.ts"],"sourcesContent":["/**\n * Browser-safe sources of envelopes. Every source yields\n * `Stream<Envelope>`; `resumable` adds reconnect-from-last-seq on top of\n * any of them. HTTP goes through `effect/http`'s `HttpClient`, so a browser\n * provides `FetchHttpClient.layer` and tests provide a fake client.\n */\nimport { Data, Effect, Ref, type Schedule, type Schema, Stream } from \"effect\"\nimport { Ndjson, type Sse } from \"effect/encoding\"\nimport { HttpClient, type HttpClientError, type HttpClientRequest } from \"effect/http\"\nimport { type Envelope, EnvelopeJson, isTerminal } from \"./RunEvent.js\"\nimport * as SseEncoding from \"./SseEncoding.js\"\n\n/** The connection ended before the run's terminal event arrived. */\nexport class Disconnected extends Data.TaggedError(\"Disconnected\")<{ readonly lastSeq: number }> {}\n\n/** What an HTTP source can fail with: transport, a malformed SSE frame, or a malformed NDJSON line. */\nexport type ClientError =\n | HttpClientError.HttpClientError\n | Schema.SchemaError\n | Sse.Retry\n | Sse.SseError\n | Ndjson.NdjsonError\n\n/** Run a request, failing on a non-2xx status. */\nexport const execute = (request: HttpClientRequest.HttpClientRequest) =>\n HttpClient.HttpClient.pipe(\n Effect.flatMap((client) => HttpClient.filterStatusOk(client).execute(request)),\n )\n\nconst body = (request: HttpClientRequest.HttpClientRequest) =>\n Stream.unwrap(Effect.map(execute(request), (response) => response.stream))\n\n/** An SSE response (`text/event-stream`) as envelopes. */\nexport const sse = (request: HttpClientRequest.HttpClientRequest) =>\n body(request).pipe(SseEncoding.decode)\n\n/** An NDJSON response (one envelope per line) as envelopes. */\nexport const ndjson = (request: HttpClientRequest.HttpClientRequest) =>\n body(request).pipe(Stream.pipeThroughChannelOrFail(Ndjson.decodeSchema(EnvelopeJson)()))\n\n/**\n * Keep events flowing across dropped connections: `connect(after)` is\n * re-invoked with the last seen `seq` on failure, per `schedule`; duplicates\n * are dropped. A connection that ends cleanly before the end counts as a\n * failure (`Disconnected`), so it is retried too.\n *\n * `until: \"terminal\"` (default) follows one run and completes after its\n * terminal. `until: \"never\"` follows a session stream that carries many runs\n * (a durable chat) and never completes on its own.\n */\nexport const resumable = <E, R, X>(\n connect: (after: number) => Stream.Stream<Envelope, E, R>,\n options: {\n readonly from?: number\n readonly until?: \"terminal\" | \"never\"\n readonly schedule: Schedule.Schedule<X, E | Disconnected>\n },\n): Stream.Stream<Envelope, E | Disconnected, R> =>\n Stream.unwrap(\n Effect.gen(function* () {\n const untilTerminal = options.until !== \"never\"\n const ends = (envelope: Envelope) => untilTerminal && isTerminal(envelope.event)\n // The last seen seq, and whether the run's terminal arrived.\n const position = yield* Ref.make({ seq: options.from ?? 0, done: false })\n const attempt = Stream.unwrap(\n Ref.get(position).pipe(\n Effect.map(({ seq: after }) =>\n connect(after).pipe(\n Stream.filterEffect((envelope) =>\n Ref.modify(position, (p) =>\n envelope.seq > p.seq\n ? [true, { seq: envelope.seq, done: ends(envelope) }]\n : [false, p],\n ),\n ),\n Stream.takeUntil(ends),\n Stream.concat(\n Stream.unwrap(\n Ref.get(position).pipe(\n Effect.map((p) =>\n p.done ? Stream.empty : Stream.fail(new Disconnected({ lastSeq: p.seq })),\n ),\n ),\n ),\n ),\n ),\n ),\n ),\n )\n return attempt.pipe(Stream.retry(options.schedule))\n }),\n )\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAaA,IAAa,eAAb,cAAkC,KAAK,YAAY,cAAc,CAAC,CAA+B,CAAC;;AAWlG,MAAa,WAAW,YACtB,WAAW,WAAW,KACpB,OAAO,SAAS,WAAW,WAAW,eAAe,MAAM,CAAC,CAAC,QAAQ,OAAO,CAAC,CAC/E;AAEF,MAAM,QAAQ,YACZ,OAAO,OAAO,OAAO,IAAI,QAAQ,OAAO,IAAI,aAAa,SAAS,MAAM,CAAC;;AAG3E,MAAa,OAAO,YAClB,KAAK,OAAO,CAAC,CAAC,KAAKA,MAAkB;;AAGvC,MAAa,UAAU,YACrB,KAAK,OAAO,CAAC,CAAC,KAAK,OAAO,yBAAyB,OAAO,aAAa,YAAY,CAAC,CAAC,CAAC,CAAC;;;;;;;;;;;AAYzF,MAAa,aACX,SACA,YAMA,OAAO,OACL,OAAO,IAAI,aAAa;CACtB,MAAM,gBAAgB,QAAQ,UAAU;CACxC,MAAM,QAAQ,aAAuB,iBAAiB,WAAW,SAAS,KAAK;CAE/E,MAAM,WAAW,OAAO,IAAI,KAAK;EAAE,KAAK,QAAQ,QAAQ;EAAG,MAAM;CAAM,CAAC;CA0BxE,OAzBgB,OAAO,OACrB,IAAI,IAAI,QAAQ,CAAC,CAAC,KAChB,OAAO,KAAK,EAAE,KAAK,YACjB,QAAQ,KAAK,CAAC,CAAC,KACb,OAAO,cAAc,aACnB,IAAI,OAAO,WAAW,MACpB,SAAS,MAAM,EAAE,MACb,CAAC,MAAM;EAAE,KAAK,SAAS;EAAK,MAAM,KAAK,QAAQ;CAAE,CAAC,IAClD,CAAC,OAAO,CAAC,CACf,CACF,GACA,OAAO,UAAU,IAAI,GACrB,OAAO,OACL,OAAO,OACL,IAAI,IAAI,QAAQ,CAAC,CAAC,KAChB,OAAO,KAAK,MACV,EAAE,OAAO,OAAO,QAAQ,OAAO,KAAK,IAAI,aAAa,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC,CAC1E,CACF,CACF,CACF,CACF,CACF,CACF,CAEW,CAAC,CAAC,KAAK,OAAO,MAAM,QAAQ,QAAQ,CAAC;AACpD,CAAC,CACH"}
1
+ {"version":3,"file":"Client.mjs","names":["SseEncoding.decode"],"sources":["../src/Client.ts"],"sourcesContent":["/**\n * Browser-safe sources of envelopes. Every source yields\n * `Stream<Envelope>`; `resumable` adds reconnect-from-last-seq on top of\n * any of them. HTTP goes through `effect/http`'s `HttpClient`, so a browser\n * provides `FetchHttpClient.layer` and tests provide a fake client.\n */\nimport { Data, Effect, Ref, type Schedule, type Schema, Stream } from \"effect\"\nimport { Ndjson, type Sse } from \"effect/encoding\"\nimport { HttpClient, type HttpClientError, type HttpClientRequest } from \"effect/http\"\nimport { type Envelope, EnvelopeJson, isTerminal } from \"./RunEvent.js\"\nimport * as SseEncoding from \"./SseEncoding.js\"\n\n/** The connection ended before the run's terminal event arrived. */\nexport class Disconnected extends Data.TaggedError(\"Disconnected\")<{ readonly lastSeq: number }> {}\n\n/** What an HTTP source can fail with: transport, a malformed SSE frame, or a malformed NDJSON line. */\nexport type ClientError =\n | HttpClientError.HttpClientError\n | Schema.SchemaError\n | Sse.Retry\n | Sse.SseError\n | Ndjson.NdjsonError\n\n/** Run a request, failing on a non-2xx status. */\nexport const execute = (request: HttpClientRequest.HttpClientRequest) =>\n HttpClient.HttpClient.pipe(\n Effect.flatMap((client) => HttpClient.filterStatusOk(client).execute(request)),\n )\n\nconst body = (request: HttpClientRequest.HttpClientRequest) =>\n Stream.unwrap(Effect.map(execute(request), (response) => response.stream))\n\n/** An SSE response (`text/event-stream`) as envelopes. */\nexport const sse = (request: HttpClientRequest.HttpClientRequest) =>\n body(request).pipe(SseEncoding.decode)\n\n/** An NDJSON response (one envelope per line) as envelopes. */\nexport const ndjson = (request: HttpClientRequest.HttpClientRequest) =>\n body(request).pipe(Stream.pipeThroughChannelOrFail(Ndjson.decodeSchema(EnvelopeJson)()))\n\n/**\n * Keep events flowing across dropped connections: `connect(after)` is\n * re-invoked with the last seen `seq` on failure, per `schedule`; duplicates\n * are dropped. A connection that ends cleanly before the end counts as a\n * failure (`Disconnected`), so it is retried too. Without `schedule` nothing\n * is retried: an early end just fails with `Disconnected`.\n *\n * `until: \"terminal\"` (default) follows one run and completes after its\n * terminal. `until: \"never\"` follows a session stream that carries many runs\n * (a durable chat) and never completes on its own.\n */\nexport const resumable = <E, R, X = never>(\n connect: (after: number) => Stream.Stream<Envelope, E, R>,\n options?: {\n readonly from?: number\n readonly until?: \"terminal\" | \"never\"\n readonly schedule?: Schedule.Schedule<X, E | Disconnected>\n },\n): Stream.Stream<Envelope, E | Disconnected, R> =>\n Stream.unwrap(\n Effect.gen(function* () {\n const untilTerminal = options?.until !== \"never\"\n const ends = (envelope: Envelope) => untilTerminal && isTerminal(envelope.event)\n // The last seen seq, and whether the run's terminal arrived.\n const position = yield* Ref.make({ seq: options?.from ?? 0, done: false })\n const attempt = Stream.unwrap(\n Ref.get(position).pipe(\n Effect.map(({ seq: after }) =>\n connect(after).pipe(\n Stream.filterEffect((envelope) =>\n Ref.modify(position, (p) =>\n envelope.seq > p.seq\n ? [true, { seq: envelope.seq, done: ends(envelope) }]\n : [false, p],\n ),\n ),\n Stream.takeUntil(ends),\n Stream.concat(\n Stream.unwrap(\n Ref.get(position).pipe(\n Effect.map((p) =>\n p.done ? Stream.empty : Stream.fail(new Disconnected({ lastSeq: p.seq })),\n ),\n ),\n ),\n ),\n ),\n ),\n ),\n )\n const schedule = options?.schedule\n return schedule === undefined ? attempt : attempt.pipe(Stream.retry(schedule))\n }),\n )\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAaA,IAAa,eAAb,cAAkC,KAAK,YAAY,cAAc,CAAC,CAA+B,CAAC;;AAWlG,MAAa,WAAW,YACtB,WAAW,WAAW,KACpB,OAAO,SAAS,WAAW,WAAW,eAAe,MAAM,CAAC,CAAC,QAAQ,OAAO,CAAC,CAC/E;AAEF,MAAM,QAAQ,YACZ,OAAO,OAAO,OAAO,IAAI,QAAQ,OAAO,IAAI,aAAa,SAAS,MAAM,CAAC;;AAG3E,MAAa,OAAO,YAClB,KAAK,OAAO,CAAC,CAAC,KAAKA,MAAkB;;AAGvC,MAAa,UAAU,YACrB,KAAK,OAAO,CAAC,CAAC,KAAK,OAAO,yBAAyB,OAAO,aAAa,YAAY,CAAC,CAAC,CAAC,CAAC;;;;;;;;;;;;AAazF,MAAa,aACX,SACA,YAMA,OAAO,OACL,OAAO,IAAI,aAAa;CACtB,MAAM,gBAAgB,SAAS,UAAU;CACzC,MAAM,QAAQ,aAAuB,iBAAiB,WAAW,SAAS,KAAK;CAE/E,MAAM,WAAW,OAAO,IAAI,KAAK;EAAE,KAAK,SAAS,QAAQ;EAAG,MAAM;CAAM,CAAC;CACzE,MAAM,UAAU,OAAO,OACrB,IAAI,IAAI,QAAQ,CAAC,CAAC,KAChB,OAAO,KAAK,EAAE,KAAK,YACjB,QAAQ,KAAK,CAAC,CAAC,KACb,OAAO,cAAc,aACnB,IAAI,OAAO,WAAW,MACpB,SAAS,MAAM,EAAE,MACb,CAAC,MAAM;EAAE,KAAK,SAAS;EAAK,MAAM,KAAK,QAAQ;CAAE,CAAC,IAClD,CAAC,OAAO,CAAC,CACf,CACF,GACA,OAAO,UAAU,IAAI,GACrB,OAAO,OACL,OAAO,OACL,IAAI,IAAI,QAAQ,CAAC,CAAC,KAChB,OAAO,KAAK,MACV,EAAE,OAAO,OAAO,QAAQ,OAAO,KAAK,IAAI,aAAa,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC,CAC1E,CACF,CACF,CACF,CACF,CACF,CACF,CACF;CACA,MAAM,WAAW,SAAS;CAC1B,OAAO,aAAa,KAAA,IAAY,UAAU,QAAQ,KAAK,OAAO,MAAM,QAAQ,CAAC;AAC/E,CAAC,CACH"}
package/dist/Run.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Envelope, InputId, RunEvent, RunId, StreamId } from "./RunEvent.mjs";
2
2
  import { Cause, Effect, Schema, Stream } from "effect";
3
- import * as Duration from "effect/Duration";
3
+ import * as Duration$1 from "effect/Duration";
4
4
  declare namespace Run_d_exports {
5
5
  export { SequenceState, StartOptions, coalesce, complete, failed, sequence, start, stopWhen };
6
6
  }
@@ -38,7 +38,7 @@ declare const sequence: (streamId: StreamId, options?: {
38
38
  * Bounds frames on the wire and rows in a durable log; adds up to `window`
39
39
  * latency.
40
40
  */
41
- declare const coalesce: (window: Duration.Input) => <E, R>(self: Stream.Stream<RunEvent, E, R>) => Stream.Stream<RunEvent, E, R>;
41
+ declare const coalesce: (window: Duration$1.Input) => <E, R>(self: Stream.Stream<RunEvent, E, R>) => Stream.Stream<RunEvent, E, R>;
42
42
  //#endregion
43
43
  export { SequenceState, StartOptions, coalesce, complete, failed, sequence, start, stopWhen, Run_d_exports as t };
44
44
  //# sourceMappingURL=Run.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Run.d.mts","names":[],"sources":["../src/Run.ts"],"mappings":";;;;;;KAmCY;WACD,OAAO;;WAEP,SAAS,cAAc;;WAEvB;;;cAIE,QAAK,SACN,kBACT,GAAG,GAAC,MAAQ,OAAO,OAAO,UAAU,GAAG,OAAK,OAAO,OAAO,UAAU,GAAG;;cAY7D,WAAY,GAAG,GAAC,MACrB,OAAO,OAAO,UAAU,GAAG,OAChC,OAAO,OAAO,UAAU,GAAG;;cAcjB,WAAQ,QACV,OAAO,kBACf,GAAG,GAAC,MAAQ,OAAO,OAAO,UAAU,GAAG,OAAK,OAAO,OAAO,UAAU,GAAG;;cAS7D,SAAM,OAAW,MAAM,mBAAiB;cAWxC,eAAa,OAAA;;;;;KAKd,uBAAuB,cAAc;;;;;cAiBpC,WAAQ,UACR,UAAQ;WAAuB;OACzC,GAAG,GAAC,MAAQ,OAAO,OAAO,UAAU,GAAG,OAAK,OAAO,OAAO,UAAU,GAAG;;;;;;;cAuC7D,WAAQ,QACV,SAAS,WACjB,GAAG,GAAC,MAAQ,OAAO,OAAO,UAAU,GAAG,OAAK,OAAO,OAAO,UAAU,GAAG"}
1
+ {"version":3,"file":"Run.d.mts","names":[],"sources":["../src/Run.ts"],"mappings":";;;;;;KAmCY;WACD,OAAO;;WAEP,SAAS,cAAc;;WAEvB;;;cAIE,QAAK,SACN,kBACT,GAAG,GAAC,MAAQ,OAAO,OAAO,UAAU,GAAG,OAAK,OAAO,OAAO,UAAU,GAAG;;cAY7D,WAAY,GAAG,GAAC,MACrB,OAAO,OAAO,UAAU,GAAG,OAChC,OAAO,OAAO,UAAU,GAAG;;cAcjB,WAAQ,QACV,OAAO,kBACf,GAAG,GAAC,MAAQ,OAAO,OAAO,UAAU,GAAG,OAAK,OAAO,OAAO,UAAU,GAAG;;cAS7D,SAAM,OAAW,MAAM,mBAAiB;cAWxC,eAAa,OAAA;;;;;KAKd,uBAAuB,cAAc;;;;;cAiBpC,WAAQ,UACR,UAAQ;WAAuB;OACzC,GAAG,GAAC,MAAQ,OAAO,OAAO,UAAU,GAAG,OAAK,OAAO,OAAO,UAAU,GAAG;;;;;;;cAuC7D,WAAQ,QACV,WAAS,WACjB,GAAG,GAAC,MAAQ,OAAO,OAAO,UAAU,GAAG,OAAK,OAAO,OAAO,UAAU,GAAG"}
@@ -1,8 +1,8 @@
1
1
  import { Envelope } from "./RunEvent.mjs";
2
- import { Option, Schema, Stream } from "effect";
2
+ import { Duration, Option, Schema, Stream } from "effect";
3
3
  import { Sse } from "effect/encoding";
4
4
  declare namespace SseEncoding_d_exports {
5
- export { EnvelopeEvent, decode, encode, lastEventId, responseHeaders };
5
+ export { EnvelopeEvent, decode, encode, heartbeat, lastEventId, responseHeaders };
6
6
  }
7
7
  /** An envelope as an SSE event: `id` = seq, `event` = type, `data` = the envelope as JSON. */
8
8
  declare const EnvelopeEvent: Schema.decodeTo<Schema.toType<Schema.Struct<{
@@ -416,6 +416,13 @@ declare const EnvelopeEvent: Schema.decodeTo<Schema.toType<Schema.Struct<{
416
416
  declare const encode: <E, R>(self: Stream.Stream<Envelope, E, R>) => Stream.Stream<Uint8Array, E | Schema.SchemaError, R>;
417
417
  /** An SSE response body → envelopes. A `retry:` hint from the server fails with `Sse.Retry`. */
418
418
  declare const decode: <E, R>(self: Stream.Stream<Uint8Array, E, R>) => Stream.Stream<Envelope, E | Sse.Retry | Sse.SseError | Schema.SchemaError, R>;
419
+ /**
420
+ * Interleave an SSE comment every `interval`, so proxies don't close a stream
421
+ * that sits idle (a session between answers). Pipe after `encode`: each frame
422
+ * is one chunk, so a comment always lands between whole frames. Parsers
423
+ * (`EventSource`, `decode`) ignore comments. Ends with the event stream.
424
+ */
425
+ declare const heartbeat: (interval: Duration.Input) => <E, R>(self: Stream.Stream<Uint8Array, E, R>) => Stream.Stream<Uint8Array, E, R>;
419
426
  /**
420
427
  * Headers an SSE response needs. Behind nginx-style proxies, also send
421
428
  * `x-accel-buffering: no` so frames are not buffered.
@@ -433,5 +440,5 @@ declare const lastEventId: (headers: Headers | {
433
440
  readonly [key: string]: string | undefined;
434
441
  }) => Option.Option<number>;
435
442
  //#endregion
436
- export { EnvelopeEvent, decode, encode, lastEventId, responseHeaders, SseEncoding_d_exports as t };
443
+ export { EnvelopeEvent, decode, encode, heartbeat, lastEventId, responseHeaders, SseEncoding_d_exports as t };
437
444
  //# sourceMappingURL=SseEncoding.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SseEncoding.d.mts","names":[],"sources":["../src/SseEncoding.ts"],"mappings":";;;;;;;cAaa,eAAa,OAAA,SAAA,OAAA,OAAA,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAqBb,SAAU,GAAG,GAAC,MACnB,OAAO,OAAO,UAAU,GAAG,OAChC,OAAO,OAAO,YAAY,IAAI,OAAO,aAAa;;cAWxC,SAAU,GAAG,GAAC,MACnB,OAAO,OAAO,YAAY,GAAG,OAClC,OAAO,OAAO,UAAU,IAAI,IAAI,QAAQ,IAAI,WAAW,OAAO,aAAa;;;;;cAOjE,iBAAiB,SAAS;;;;;;;;;cAe1B,cAAW,SACb;YAAsB;MAC9B,OAAO"}
1
+ {"version":3,"file":"SseEncoding.d.mts","names":[],"sources":["../src/SseEncoding.ts"],"mappings":";;;;;;;cAaa,eAAa,OAAA,SAAA,OAAA,OAAA,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAqBb,SAAU,GAAG,GAAC,MACnB,OAAO,OAAO,UAAU,GAAG,OAChC,OAAO,OAAO,YAAY,IAAI,OAAO,aAAa;;cAWxC,SAAU,GAAG,GAAC,MACnB,OAAO,OAAO,YAAY,GAAG,OAClC,OAAO,OAAO,UAAU,IAAI,IAAI,QAAQ,IAAI,WAAW,OAAO,aAAa;;;;;;;cAWjE,YAAS,UACT,SAAS,WACnB,GAAG,GAAC,MAAQ,OAAO,OAAO,YAAY,GAAG,OAAK,OAAO,OAAO,YAAY,GAAG;;;;;cAWjE,iBAAiB,SAAS;;;;;;;;;cAe1B,cAAW,SACb;YAAsB;MAC9B,OAAO"}
@@ -15,6 +15,7 @@ var SseEncoding_exports = /* @__PURE__ */ __exportAll({
15
15
  EnvelopeEvent: () => EnvelopeEvent,
16
16
  decode: () => decode,
17
17
  encode: () => encode,
18
+ heartbeat: () => heartbeat,
18
19
  lastEventId: () => lastEventId,
19
20
  responseHeaders: () => responseHeaders
20
21
  });
@@ -40,6 +41,14 @@ const encode = (self) => self.pipe(Stream.mapEffect((envelope) => encodeEvent(en
40
41
  })))), Stream.encodeText);
41
42
  /** An SSE response body → envelopes. A `retry:` hint from the server fails with `Sse.Retry`. */
42
43
  const decode = (self) => self.pipe(Stream.decodeText, Stream.pipeThroughChannelOrFail(Sse.decodeSchema(EnvelopeEvent)));
44
+ const comment = new TextEncoder().encode(": heartbeat\n\n");
45
+ /**
46
+ * Interleave an SSE comment every `interval`, so proxies don't close a stream
47
+ * that sits idle (a session between answers). Pipe after `encode`: each frame
48
+ * is one chunk, so a comment always lands between whole frames. Parsers
49
+ * (`EventSource`, `decode`) ignore comments. Ends with the event stream.
50
+ */
51
+ const heartbeat = (interval) => (self) => self.pipe(Stream.merge(Stream.tick(interval).pipe(Stream.map(() => comment)), { haltStrategy: "left" }));
43
52
  /**
44
53
  * Headers an SSE response needs. Behind nginx-style proxies, also send
45
54
  * `x-accel-buffering: no` so frames are not buffered.
@@ -59,6 +68,6 @@ const parseSeq = Schema.decodeUnknownOption(Schema.FiniteFromString.check(Schema
59
68
  */
60
69
  const lastEventId = (headers) => Option.fromNullishOr(headers instanceof Headers ? headers.get("last-event-id") : headers["last-event-id"]).pipe(Option.flatMap(parseSeq));
61
70
  //#endregion
62
- export { EnvelopeEvent, decode, encode, lastEventId, responseHeaders, SseEncoding_exports as t };
71
+ export { EnvelopeEvent, decode, encode, heartbeat, lastEventId, responseHeaders, SseEncoding_exports as t };
63
72
 
64
73
  //# sourceMappingURL=SseEncoding.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"SseEncoding.mjs","names":[],"sources":["../src/SseEncoding.ts"],"sourcesContent":["/**\n * SSE framing for envelopes, on Effect's `effect/encoding/Sse`. A frame\n * carries `id: <seq>` (so a browser's auto-reconnect sends `Last-Event-ID`),\n * `event: <type>` and the envelope's JSON in `data`. `EnvelopeEvent` is that\n * mapping as a Schema; `encode` / `decode` run it with Effect's SSE\n * encoder and parser.\n * For NDJSON use `effect/encoding/Ndjson` with `RunEvent.EnvelopeJson`.\n */\nimport { Effect, Option, Schema, SchemaTransformation, Stream } from \"effect\"\nimport { Sse } from \"effect/encoding\"\nimport { Envelope, EnvelopeJson } from \"./RunEvent.js\"\n\n/** An envelope as an SSE event: `id` = seq, `event` = type, `data` = the envelope as JSON. */\nexport const EnvelopeEvent = Schema.Struct({\n id: Schema.optional(Schema.String),\n event: Schema.String,\n data: Schema.fromJsonString(EnvelopeJson),\n}).pipe(\n Schema.decodeTo(\n Schema.toType(Envelope),\n SchemaTransformation.transform({\n decode: (event) => event.data,\n encode: (envelope) => ({\n id: String(envelope.seq),\n event: envelope.event.type,\n data: envelope,\n }),\n }),\n ),\n)\n\nconst encodeEvent = Schema.encodeEffect(EnvelopeEvent)\n\n/** Envelopes → an SSE response body. */\nexport const encode = <E, R>(\n self: Stream.Stream<Envelope, E, R>,\n): Stream.Stream<Uint8Array, E | Schema.SchemaError, R> =>\n self.pipe(\n Stream.mapEffect((envelope) =>\n encodeEvent(envelope).pipe(\n Effect.map((event) => Sse.encoder.write({ _tag: \"Event\", id: event.id, ...event })),\n ),\n ),\n Stream.encodeText,\n )\n\n/** An SSE response body → envelopes. A `retry:` hint from the server fails with `Sse.Retry`. */\nexport const decode = <E, R>(\n self: Stream.Stream<Uint8Array, E, R>,\n): Stream.Stream<Envelope, E | Sse.Retry | Sse.SseError | Schema.SchemaError, R> =>\n self.pipe(Stream.decodeText, Stream.pipeThroughChannelOrFail(Sse.decodeSchema(EnvelopeEvent)))\n\n/**\n * Headers an SSE response needs. Behind nginx-style proxies, also send\n * `x-accel-buffering: no` so frames are not buffered.\n */\nexport const responseHeaders: Readonly<Record<string, string>> = {\n \"content-type\": \"text/event-stream\",\n \"cache-control\": \"no-cache\",\n}\n\nconst parseSeq = Schema.decodeUnknownOption(Schema.FiniteFromString.check(Schema.isInt()))\n\n/**\n * The `seq` a reconnecting SSE client last received, from the standard\n * `Last-Event-ID` header (a browser's EventSource sends it on every\n * auto-reconnect). Accepts web `Headers` or Effect's record headers.\n *\n * Prefer it over any position the app carries in the URL: EventSource keeps\n * reconnecting to its original URL, so a URL position goes stale.\n */\nexport const lastEventId = (\n headers: Headers | { readonly [key: string]: string | undefined },\n): Option.Option<number> =>\n Option.fromNullishOr(\n headers instanceof Headers ? headers.get(\"last-event-id\") : headers[\"last-event-id\"],\n ).pipe(Option.flatMap(parseSeq))\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAaA,MAAa,gBAAgB,OAAO,OAAO;CACzC,IAAI,OAAO,SAAS,OAAO,MAAM;CACjC,OAAO,OAAO;CACd,MAAM,OAAO,eAAe,YAAY;AAC1C,CAAC,CAAC,CAAC,KACD,OAAO,SACL,OAAO,OAAO,QAAQ,GACtB,qBAAqB,UAAU;CAC7B,SAAS,UAAU,MAAM;CACzB,SAAS,cAAc;EACrB,IAAI,OAAO,SAAS,GAAG;EACvB,OAAO,SAAS,MAAM;EACtB,MAAM;CACR;AACF,CAAC,CACH,CACF;AAEA,MAAM,cAAc,OAAO,aAAa,aAAa;;AAGrD,MAAa,UACX,SAEA,KAAK,KACH,OAAO,WAAW,aAChB,YAAY,QAAQ,CAAC,CAAC,KACpB,OAAO,KAAK,UAAU,IAAI,QAAQ,MAAM;CAAE,MAAM;CAAS,IAAI,MAAM;CAAI,GAAG;AAAM,CAAC,CAAC,CACpF,CACF,GACA,OAAO,UACT;;AAGF,MAAa,UACX,SAEA,KAAK,KAAK,OAAO,YAAY,OAAO,yBAAyB,IAAI,aAAa,aAAa,CAAC,CAAC;;;;;AAM/F,MAAa,kBAAoD;CAC/D,gBAAgB;CAChB,iBAAiB;AACnB;AAEA,MAAM,WAAW,OAAO,oBAAoB,OAAO,iBAAiB,MAAM,OAAO,MAAM,CAAC,CAAC;;;;;;;;;AAUzF,MAAa,eACX,YAEA,OAAO,cACL,mBAAmB,UAAU,QAAQ,IAAI,eAAe,IAAI,QAAQ,gBACtE,CAAC,CAAC,KAAK,OAAO,QAAQ,QAAQ,CAAC"}
1
+ {"version":3,"file":"SseEncoding.mjs","names":[],"sources":["../src/SseEncoding.ts"],"sourcesContent":["/**\n * SSE framing for envelopes, on Effect's `effect/encoding/Sse`. A frame\n * carries `id: <seq>` (so a browser's auto-reconnect sends `Last-Event-ID`),\n * `event: <type>` and the envelope's JSON in `data`. `EnvelopeEvent` is that\n * mapping as a Schema; `encode` / `decode` run it with Effect's SSE\n * encoder and parser.\n * For NDJSON use `effect/encoding/Ndjson` with `RunEvent.EnvelopeJson`.\n */\nimport { type Duration, Effect, Option, Schema, SchemaTransformation, Stream } from \"effect\"\nimport { Sse } from \"effect/encoding\"\nimport { Envelope, EnvelopeJson } from \"./RunEvent.js\"\n\n/** An envelope as an SSE event: `id` = seq, `event` = type, `data` = the envelope as JSON. */\nexport const EnvelopeEvent = Schema.Struct({\n id: Schema.optional(Schema.String),\n event: Schema.String,\n data: Schema.fromJsonString(EnvelopeJson),\n}).pipe(\n Schema.decodeTo(\n Schema.toType(Envelope),\n SchemaTransformation.transform({\n decode: (event) => event.data,\n encode: (envelope) => ({\n id: String(envelope.seq),\n event: envelope.event.type,\n data: envelope,\n }),\n }),\n ),\n)\n\nconst encodeEvent = Schema.encodeEffect(EnvelopeEvent)\n\n/** Envelopes → an SSE response body. */\nexport const encode = <E, R>(\n self: Stream.Stream<Envelope, E, R>,\n): Stream.Stream<Uint8Array, E | Schema.SchemaError, R> =>\n self.pipe(\n Stream.mapEffect((envelope) =>\n encodeEvent(envelope).pipe(\n Effect.map((event) => Sse.encoder.write({ _tag: \"Event\", id: event.id, ...event })),\n ),\n ),\n Stream.encodeText,\n )\n\n/** An SSE response body → envelopes. A `retry:` hint from the server fails with `Sse.Retry`. */\nexport const decode = <E, R>(\n self: Stream.Stream<Uint8Array, E, R>,\n): Stream.Stream<Envelope, E | Sse.Retry | Sse.SseError | Schema.SchemaError, R> =>\n self.pipe(Stream.decodeText, Stream.pipeThroughChannelOrFail(Sse.decodeSchema(EnvelopeEvent)))\n\nconst comment = new TextEncoder().encode(\": heartbeat\\n\\n\")\n\n/**\n * Interleave an SSE comment every `interval`, so proxies don't close a stream\n * that sits idle (a session between answers). Pipe after `encode`: each frame\n * is one chunk, so a comment always lands between whole frames. Parsers\n * (`EventSource`, `decode`) ignore comments. Ends with the event stream.\n */\nexport const heartbeat =\n (interval: Duration.Input) =>\n <E, R>(self: Stream.Stream<Uint8Array, E, R>): Stream.Stream<Uint8Array, E, R> =>\n self.pipe(\n Stream.merge(Stream.tick(interval).pipe(Stream.map(() => comment)), {\n haltStrategy: \"left\",\n }),\n )\n\n/**\n * Headers an SSE response needs. Behind nginx-style proxies, also send\n * `x-accel-buffering: no` so frames are not buffered.\n */\nexport const responseHeaders: Readonly<Record<string, string>> = {\n \"content-type\": \"text/event-stream\",\n \"cache-control\": \"no-cache\",\n}\n\nconst parseSeq = Schema.decodeUnknownOption(Schema.FiniteFromString.check(Schema.isInt()))\n\n/**\n * The `seq` a reconnecting SSE client last received, from the standard\n * `Last-Event-ID` header (a browser's EventSource sends it on every\n * auto-reconnect). Accepts web `Headers` or Effect's record headers.\n *\n * Prefer it over any position the app carries in the URL: EventSource keeps\n * reconnecting to its original URL, so a URL position goes stale.\n */\nexport const lastEventId = (\n headers: Headers | { readonly [key: string]: string | undefined },\n): Option.Option<number> =>\n Option.fromNullishOr(\n headers instanceof Headers ? headers.get(\"last-event-id\") : headers[\"last-event-id\"],\n ).pipe(Option.flatMap(parseSeq))\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAaA,MAAa,gBAAgB,OAAO,OAAO;CACzC,IAAI,OAAO,SAAS,OAAO,MAAM;CACjC,OAAO,OAAO;CACd,MAAM,OAAO,eAAe,YAAY;AAC1C,CAAC,CAAC,CAAC,KACD,OAAO,SACL,OAAO,OAAO,QAAQ,GACtB,qBAAqB,UAAU;CAC7B,SAAS,UAAU,MAAM;CACzB,SAAS,cAAc;EACrB,IAAI,OAAO,SAAS,GAAG;EACvB,OAAO,SAAS,MAAM;EACtB,MAAM;CACR;AACF,CAAC,CACH,CACF;AAEA,MAAM,cAAc,OAAO,aAAa,aAAa;;AAGrD,MAAa,UACX,SAEA,KAAK,KACH,OAAO,WAAW,aAChB,YAAY,QAAQ,CAAC,CAAC,KACpB,OAAO,KAAK,UAAU,IAAI,QAAQ,MAAM;CAAE,MAAM;CAAS,IAAI,MAAM;CAAI,GAAG;AAAM,CAAC,CAAC,CACpF,CACF,GACA,OAAO,UACT;;AAGF,MAAa,UACX,SAEA,KAAK,KAAK,OAAO,YAAY,OAAO,yBAAyB,IAAI,aAAa,aAAa,CAAC,CAAC;AAE/F,MAAM,UAAU,IAAI,YAAY,CAAC,CAAC,OAAO,iBAAiB;;;;;;;AAQ1D,MAAa,aACV,cACM,SACL,KAAK,KACH,OAAO,MAAM,OAAO,KAAK,QAAQ,CAAC,CAAC,KAAK,OAAO,UAAU,OAAO,CAAC,GAAG,EAClE,cAAc,OAChB,CAAC,CACH;;;;;AAMJ,MAAa,kBAAoD;CAC/D,gBAAgB;CAChB,iBAAiB;AACnB;AAEA,MAAM,WAAW,OAAO,oBAAoB,OAAO,iBAAiB,MAAM,OAAO,MAAM,CAAC,CAAC;;;;;;;;;AAUzF,MAAa,eACX,YAEA,OAAO,cACL,mBAAmB,UAAU,QAAQ,IAAI,eAAe,IAAI,QAAQ,gBACtE,CAAC,CAAC,KAAK,OAAO,QAAQ,QAAQ,CAAC"}
@@ -1,9 +1,9 @@
1
1
  import { Envelope, RunEvent, StreamId } from "./RunEvent.mjs";
2
- import { r as Disconnected, t as ClientError } from "./Client-C9UYN_VK.mjs";
3
- import { Effect, Option, Schedule, Stream } from "effect";
2
+ import { r as Disconnected, t as ClientError } from "./Client-B2Eqo39q.mjs";
3
+ import { Effect, Option, Predicate, Schedule, Stream } from "effect";
4
4
  import { HttpClient, HttpClientRequest } from "effect/http";
5
5
  declare namespace Transport_d_exports {
6
- export { Durable, HttpError, Session, Transport, direct, durable, durableHttp, local, session, sessionHttp };
6
+ export { Durable, ErrorOf, HttpError, ReconnectOptions, Reconnected, Resumable, Session, Transport, direct, durable, durableHttp, local, reconnect, session, sessionHttp };
7
7
  }
8
8
  type Transport<I, S, E, R> = {
9
9
  /** Start a run for `input` and stream its envelopes. Interrupting the stream abandons it. */
@@ -32,38 +32,44 @@ declare const direct: <I, S, EB = never, RB = never>(options: {
32
32
  declare const local: <I, S, E, R>(run: (input: I, state: S) => Stream.Stream<RunEvent, E, R>, options?: {
33
33
  readonly streamId?: StreamId;
34
34
  }) => Transport<I, S, E, R>;
35
- /** A per-run transport that can re-attach to a run (durable). */
35
+ /** A per-run transport that can re-attach to a run (durable). `start` is `submit`, then `resume` from 0. */
36
36
  type Durable<I, S, E, R> = Transport<I, S, E, R> & {
37
+ /** Start a run on the server and return its stream id. */
38
+ readonly submit: (input: I, state: S) => Effect.Effect<StreamId, E, R>;
37
39
  readonly resume: (streamId: StreamId, after: number) => Stream.Stream<Envelope, E, R>;
38
40
  };
39
41
  /**
40
42
  * Durable runs: `submit` starts one on the server and returns its stream id;
41
- * `events` reads it from `after` (wrap it in `Client.resumable` to survive
42
- * drops). The run lives on the server, so a reload re-attaches with `resume`.
43
+ * `events` reads it from `after`. The run lives on the server, so a reload
44
+ * re-attaches with `resume`. A stream that ends before the run's terminal
45
+ * fails with `Client.Disconnected`; pipe through `reconnect` to resume it.
43
46
  */
44
47
  declare const durable: <I, S, ES, RS, EE, RE, EX = never, RX = never>(options: {
45
48
  readonly submit: (input: I, state: S) => Effect.Effect<StreamId, ES, RS>;
46
49
  readonly events: (streamId: StreamId, after: number) => Stream.Stream<Envelope, EE, RE>;
47
50
  readonly stop?: (streamId: StreamId) => Effect.Effect<void, EX, RX>;
48
- }) => Durable<I, S, ES | EE | EX, RS | RE | RX>;
51
+ }) => Durable<I, S, ES | EE | EX | Disconnected, RS | RE | RX>;
49
52
  /**
50
53
  * A durable session (a chat): one stream carries every run, and the server
51
54
  * owns its history. `send` without a stream id starts a new session and
52
55
  * returns its id (the app's own chat id, branded); with one, it adds a run to
53
- * that session. `events` follows the whole session from `after` (wrap it in
54
- * `Client.resumable` with `until: "never"`). The run arrives on that stream,
55
- * not as a response to `send`.
56
+ * that session. `events` follows the whole session from `after`. The run
57
+ * arrives on that stream, not as a response to `send`.
56
58
  */
57
59
  type Session<I, S, E, R> = {
58
60
  readonly send: (streamId: Option.Option<StreamId>, input: I, state: S) => Effect.Effect<StreamId, E, R>;
59
61
  readonly events: (streamId: StreamId, after: number) => Stream.Stream<Envelope, E, R>;
60
62
  readonly stop?: (streamId: StreamId) => Effect.Effect<void, E, R>;
61
63
  };
64
+ /**
65
+ * A session stream never ends on its own, so one that completes fails with
66
+ * `Client.Disconnected`; pipe through `reconnect` to resume it.
67
+ */
62
68
  declare const session: <I, S, ES, RS, EE, RE, EX = never, RX = never>(options: {
63
69
  readonly send: (streamId: Option.Option<StreamId>, input: I, state: S) => Effect.Effect<StreamId, ES, RS>;
64
70
  readonly events: (streamId: StreamId, after: number) => Stream.Stream<Envelope, EE, RE>;
65
71
  readonly stop?: (streamId: StreamId) => Effect.Effect<void, EX, RX>;
66
- }) => Session<I, S, ES | EE | EX, RS | RE | RX>;
72
+ }) => Session<I, S, ES | EE | EX | Disconnected, RS | RE | RX>;
67
73
  /** What the HTTP helpers can fail with. */
68
74
  type HttpError = ClientError | Disconnected;
69
75
  type HttpOptions = {
@@ -71,13 +77,11 @@ type HttpOptions = {
71
77
  readonly events: (streamId: StreamId, after: number) => HttpClientRequest.HttpClientRequest;
72
78
  /** The request that stops a stream's active run. */
73
79
  readonly stop?: (streamId: StreamId) => HttpClientRequest.HttpClientRequest;
74
- /** When to reconnect a dropped event stream (it resumes from the last `seq`). */
75
- readonly reconnect: Schedule.Schedule<unknown, HttpError>;
76
80
  };
77
81
  /**
78
82
  * `durable` over HTTP. `submit` starts a run and returns its stream id (the
79
83
  * app decodes its own response, e.g. with `HttpClientResponse.schemaJson`);
80
- * `events` is read as SSE and reconnects per `reconnect`.
84
+ * `events` is read as SSE. Pipe through `reconnect` to survive drops.
81
85
  */
82
86
  declare const durableHttp: <I, S, ES = never, RS = never>(options: HttpOptions & {
83
87
  readonly submit: (input: I, state: S) => Effect.Effect<StreamId, ES, RS>;
@@ -85,12 +89,49 @@ declare const durableHttp: <I, S, ES = never, RS = never>(options: HttpOptions &
85
89
  /**
86
90
  * `session` over HTTP. `send` adds an input and returns the session's stream
87
91
  * id (without one, the server starts a session); `events` follows the whole
88
- * session as SSE. It carries many runs and never ends on its own, so
89
- * `reconnect` usually retries forever (e.g. `Schedule.spaced("1 second")`).
92
+ * session as SSE. Pipe through `reconnect` to survive drops.
90
93
  */
91
94
  declare const sessionHttp: <I, S, ES = never, RS = never>(options: HttpOptions & {
92
95
  readonly send: (streamId: Option.Option<StreamId>, input: I, state: S) => Effect.Effect<StreamId, ES, RS>;
93
96
  }) => Session<I, S, HttpError | ES, HttpClient.HttpClient | RS>;
97
+ type ReconnectOptions<E> = {
98
+ /**
99
+ * When to retry. Default: exponential from 500 ms, capped at 10 s, jittered.
100
+ * Cap your own exponential: the schedule resets only when a frame arrives,
101
+ * so an idle session's backoff otherwise grows without bound.
102
+ */
103
+ readonly schedule?: Schedule.Schedule<unknown, E | Disconnected>;
104
+ /** More errors to treat as transient, on top of drops, timeouts, 408 / 429 / 5xx and SSE `retry:`. */
105
+ readonly while?: Predicate.Predicate<E>;
106
+ /** Max attempts in a row; the count resets once a frame arrives. Default: unlimited. */
107
+ readonly times?: number;
108
+ };
109
+ /** A transport `reconnect` applies to: one with a position to resume from. */
110
+ type Resumable = Session<any, any, any, any> | Durable<any, any, any, any>;
111
+ /** What a transport can fail with. */
112
+ type ErrorOf<T extends Resumable> = T extends Session<any, any, infer E, any> ? E : T extends Durable<any, any, infer E, any> ? E : never;
113
+ type WithDisconnected<F> = F extends ((...args: infer A) => Stream.Stream<infer X, infer E, infer R>) ? (...args: A) => Stream.Stream<X, E | Disconnected, R> : F;
114
+ /** `T` with `Client.Disconnected` added to its streams; `send` / `submit` keep their exact types. */
115
+ type Reconnected<T extends Resumable> = { readonly [K in keyof T]: WithDisconnected<T[K]>; };
116
+ /**
117
+ * Resume a dropped event stream from the last seq, as `HttpClient.retryTransient`
118
+ * does for requests. Retries drops (`Client.Disconnected`), timeouts, transport
119
+ * errors, 408 / 429 / 5xx and an SSE `retry:` line, waiting at least what the
120
+ * server asks for (`Retry-After`, `retry:`); anything else (404, 401, a
121
+ * malformed frame) ends the stream with that error. `while` adds the app's own
122
+ * transient errors (e.g. a dropped WebSocket).
123
+ *
124
+ * A session is followed forever; a durable run until its terminal. A durable
125
+ * `submit` is never retried, so an input is never sent twice. Not for `direct`
126
+ * / `local`: there the run is the request, with no position to resume from.
127
+ *
128
+ * const transport = Transport.reconnect(Transport.sessionHttp({ send, events, stop }))
129
+ * const ws = pipe(Transport.session({ send, events }), Transport.reconnect({ while: isDropped }))
130
+ */
131
+ declare const reconnect: {
132
+ <E>(options?: ReconnectOptions<E>): <T extends Resumable>(self: T) => Reconnected<T>;
133
+ <T extends Resumable>(self: T, options?: ReconnectOptions<ErrorOf<T>>): Reconnected<T>;
134
+ };
94
135
  //#endregion
95
- export { Durable, HttpError, Session, Transport, direct, durable, durableHttp, local, session, sessionHttp, Transport_d_exports as t };
136
+ export { Durable, ErrorOf, HttpError, ReconnectOptions, Reconnected, Resumable, Session, Transport, direct, durable, durableHttp, local, reconnect, session, sessionHttp, Transport_d_exports as t };
96
137
  //# sourceMappingURL=Transport.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Transport.d.mts","names":[],"sources":["../src/Transport.ts"],"mappings":";;;;;;;KAgBY,UAAU,GAAG,GAAG,GAAG;;WAEpB,QAAQ,OAAO,GAAG,OAAO,MAAM,OAAO,OAAO,UAAU,GAAG;;WAE1D,UAAU,UAAU,UAAU,kBAAkB,OAAO,OAAO,UAAU,GAAG;;WAE3E,QAAQ,UAAU,aAAa,OAAO,aAAa,GAAG;;;;;;;;;;cAWpD,SAAU,GAAG,GAAG,YAAY,YAAU;WACxC,UACP,OAAO,GACP,OAAO,MACJ,OAAO,OAAO,kBAAkB,mBAAmB,IAAI;WACnD;MACP,UAAU,GAAG,GAAG,cAAqB,IAAI,WAAW,aAAa;;;;;cAkBxD,QAAS,GAAG,GAAG,GAAG,GAAC,MACxB,OAAO,GAAG,OAAO,MAAM,OAAO,OAAO,UAAU,GAAG,IAAE;WACrC,WAAW;MAC/B,UAAU,GAAG,GAAG,GAAG;;KAMV,QAAQ,GAAG,GAAG,GAAG,KAAK,UAAU,GAAG,GAAG,GAAG;WAC1C,SAAS,UAAU,UAAU,kBAAkB,OAAO,OAAO,UAAU,GAAG;;;;;;;cAQxE,UAAW,GAAG,GAAG,IAAI,IAAI,IAAI,IAAI,YAAY,YAAU;WACzD,SAAS,OAAO,GAAG,OAAO,MAAM,OAAO,OAAO,UAAU,IAAI;WAC5D,SAAS,UAAU,UAAU,kBAAkB,OAAO,OAAO,UAAU,IAAI;WAC3E,QAAQ,UAAU,aAAa,OAAO,aAAa,IAAI;MAC9D,QAAQ,GAAG,GAAG,KAAK,KAAK,IAAI,KAAK,KAAK;;;;;;;;;KAiB9B,QAAQ,GAAG,GAAG,GAAG;WAClB,OACP,UAAU,OAAO,OAAO,WACxB,OAAO,GACP,OAAO,MACJ,OAAO,OAAO,UAAU,GAAG;WACvB,SAAS,UAAU,UAAU,kBAAkB,OAAO,OAAO,UAAU,GAAG;WAC1E,QAAQ,UAAU,aAAa,OAAO,aAAa,GAAG;;cAGpD,UAAW,GAAG,GAAG,IAAI,IAAI,IAAI,IAAI,YAAY,YAAU;WACzD,OACP,UAAU,OAAO,OAAO,WACxB,OAAO,GACP,OAAO,MACJ,OAAO,OAAO,UAAU,IAAI;WACxB,SAAS,UAAU,UAAU,kBAAkB,OAAO,OAAO,UAAU,IAAI;WAC3E,QAAQ,UAAU,aAAa,OAAO,aAAa,IAAI;MAC9D,QAAQ,GAAG,GAAG,KAAK,KAAK,IAAI,KAAK,KAAK;;KAO9B,YAAY,cAAqB;KAExC;;WAEM,SAAS,UAAU,UAAU,kBAAkB,kBAAkB;;WAEjE,QAAQ,UAAU,aAAa,kBAAkB;;WAEjD,WAAW,SAAS,kBAAkB;;;;;;;cAapC,cAAe,GAAG,GAAG,YAAY,YAAU,SAC7C;WACE,SAAS,OAAO,GAAG,OAAO,MAAM,OAAO,OAAO,UAAU,IAAI;MAEtE,QAAQ,GAAG,GAAG,YAAY,IAAI,WAAW,aAAa;;;;;;;cAiB5C,cAAe,GAAG,GAAG,YAAY,YAAU,SAC7C;WACE,OACP,UAAU,OAAO,OAAO,WACxB,OAAO,GACP,OAAO,MACJ,OAAO,OAAO,UAAU,IAAI;MAElC,QAAQ,GAAG,GAAG,YAAY,IAAI,WAAW,aAAa"}
1
+ {"version":3,"file":"Transport.d.mts","names":[],"sources":["../src/Transport.ts"],"mappings":"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"}