@systemfsoftware/effect-atom 0.5.3

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.
@@ -0,0 +1,111 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
+ import { O as runtime, g as keepAlive, k as serializable, l as family, tt as setIdleTTL } from "./Atom-DVWTGeoV.mjs";
3
+ import { j as schemaCodec } from "./Result-rlUvoHzK.mjs";
4
+ import * as Context from "effect/Context";
5
+ import * as Duration from "effect/Duration";
6
+ import * as Effect from "effect/Effect";
7
+ import * as Layer from "effect/Layer";
8
+ import * as Schema from "effect/Schema";
9
+ import "effect/Scope";
10
+ import * as Stream from "effect/Stream";
11
+ import * as Reactivity from "effect/unstable/reactivity/Reactivity";
12
+ import * as Headers from "effect/unstable/http/Headers";
13
+ import * as RpcClient from "effect/unstable/rpc/RpcClient";
14
+ import { RpcClientError } from "effect/unstable/rpc/RpcClientError";
15
+ import * as RpcSchema from "effect/unstable/rpc/RpcSchema";
16
+ //#region src/AtomRpc.ts
17
+ /**
18
+ * Connects typed RPC clients to atoms.
19
+ *
20
+ * The service created here exposes a flattened RPC client plus atom-based query
21
+ * and mutation helpers. Query atoms call RPCs and track asynchronous or
22
+ * streaming results, while mutations run RPC calls that can invalidate
23
+ * reactivity keys after success. Query atoms can also use request headers,
24
+ * time-to-live settings, and serialization keys for hydration.
25
+ *
26
+ * @since 4.0.0
27
+ */
28
+ var AtomRpc_exports = /* @__PURE__ */ __exportAll({ Service: () => Service });
29
+ /**
30
+ * Creates a `Context.Service` class for an RPC client backed by an atom runtime.
31
+ *
32
+ * **Details**
33
+ *
34
+ * The options provide the RPC group, protocol layer, tracing options, request id generation, optional custom client
35
+ * effect, and runtime factory used by the query and mutation helpers.
36
+ *
37
+ * @category constructors
38
+ * @since 4.0.0
39
+ */
40
+ const Service = () => (id, options) => {
41
+ const service = Context.Service()(id);
42
+ const layer = Layer.effect(service, options.makeEffect ?? RpcClient.make(options.group, {
43
+ ...options,
44
+ flatten: true
45
+ }));
46
+ const protocol = options.protocol;
47
+ const runtime$1 = (options.runtime ?? runtime)(typeof protocol === "function" ? (get) => Layer.provide(layer, Layer.orDie(protocol(get))) : Layer.provide(layer, Layer.orDie(protocol)));
48
+ const isAnyWithProps = (u) => (typeof u === "object" && u !== null || typeof u === "function") && "payloadSchema" in u && "successSchema" in u && "errorSchema" in u;
49
+ const getRpc = (tag) => {
50
+ const rpc = options.group.requests.get(tag);
51
+ if (rpc === void 0 || !isAnyWithProps(rpc)) throw new Error(`Unknown RPC tag: ${tag}`);
52
+ return rpc;
53
+ };
54
+ /** `Flat` is a callable, so this narrowing is a check rather than a claim. */
55
+ const isErasedFlatCall = (client) => typeof client === "function";
56
+ function callFlat(client, tag, payload, headers, _shape) {
57
+ if (!isErasedFlatCall(client)) throw new Error(`RpcClient.Flat is not callable for tag: ${tag}`);
58
+ return client(tag, payload, { headers });
59
+ }
60
+ const resultSchema = schemaCodec;
61
+ const mutationFamily = family((tag) => {
62
+ const rpc = getRpc(tag);
63
+ const fnAtom = runtime$1.fn()(Effect.fnUntraced(function* ({ headers, payload, reactivityKeys }) {
64
+ const effect = callFlat(yield* service, tag, payload, headers, "effect");
65
+ return yield* reactivityKeys ? Reactivity.mutation(effect, reactivityKeys) : effect;
66
+ }));
67
+ return serializable(fnAtom, {
68
+ key: `AtomRpc:mutation:${tag}`,
69
+ schema: resultSchema(rpc.successSchema, makeErrorSchema(rpc))
70
+ });
71
+ });
72
+ function mutation(arg) {
73
+ return mutationFamily(arg);
74
+ }
75
+ const queryFamily = family((key) => {
76
+ const { headers, payload, reactivityKeys, tag, timeToLive } = key;
77
+ const rpc = getRpc(tag);
78
+ const isStream = RpcSchema.isStreamSchema(rpc.successSchema);
79
+ let atom = isStream ? runtime$1.pull(Stream.unwrap(service.use((client) => Effect.succeed(callFlat(client, tag, payload, headers, "stream"))))) : runtime$1.atom(service.use((client) => callFlat(client, tag, payload, headers, "effect")));
80
+ if (reactivityKeys) atom = runtime$1.factory.withReactivity(reactivityKeys)(atom);
81
+ if (!isStream && key.serializationKey) atom = serializable(atom, {
82
+ key: `AtomRpc:${key.tag}:${key.serializationKey}`,
83
+ schema: resultSchema(rpc.successSchema, makeErrorSchema(rpc))
84
+ });
85
+ if (timeToLive) atom = Duration.isFinite(timeToLive) ? setIdleTTL(atom, timeToLive) : keepAlive(atom);
86
+ return atom;
87
+ });
88
+ function query(tag, payload, options) {
89
+ const key = {
90
+ tag,
91
+ payload,
92
+ headers: options?.headers ? Headers.fromInput(options.headers) : void 0,
93
+ reactivityKeys: options?.reactivityKeys,
94
+ timeToLive: options?.timeToLive ? Duration.fromInputUnsafe(options.timeToLive) : void 0,
95
+ serializationKey: options?.serializationKey
96
+ };
97
+ return queryFamily(key);
98
+ }
99
+ return Object.assign(service, {
100
+ runtime: runtime$1,
101
+ mutation,
102
+ query
103
+ });
104
+ };
105
+ const makeErrorSchema = (rpc) => Schema.Union([
106
+ rpc.errorSchema,
107
+ ...Array.from(rpc.middlewares, (middleware) => middleware.error),
108
+ RpcClientError
109
+ ]);
110
+ //#endregion
111
+ export { Service, AtomRpc_exports as t };
@@ -0,0 +1,75 @@
1
+ import { _t as Registry } from "./Atom-BgqgP-Qc.js";
2
+ import * as Fiber from "effect/Fiber";
3
+ declare namespace Hydration_d_exports {
4
+ export { DehydratedAtom, DehydratedAtomValue, dehydrate, hydrate };
5
+ }
6
+ /**
7
+ * Marker interface for entries in a dehydrated atom registry state.
8
+ *
9
+ * @category models
10
+ * @since 4.0.0
11
+ */
12
+ interface DehydratedAtom {
13
+ readonly '~effect/reactivity/DehydratedAtom': true;
14
+ }
15
+ /**
16
+ * A dehydrated serializable atom value.
17
+ *
18
+ * **Details**
19
+ *
20
+ * It stores the atom serialization key, encoded value, and dehydration
21
+ * timestamp.
22
+ *
23
+ * @category models
24
+ * @since 4.0.0
25
+ */
26
+ interface DehydratedAtomValue extends DehydratedAtom {
27
+ readonly key: string;
28
+ readonly value: unknown;
29
+ readonly dehydratedAt: number;
30
+ }
31
+ /**
32
+ * Encodes the serializable atoms currently stored in a registry into dehydrated
33
+ * state.
34
+ *
35
+ * **Details**
36
+ *
37
+ * Only atoms marked with `Atom.serializable` are included. `encodeInitialAs`
38
+ * controls whether `AsyncResult.Initial` values are ignored, encoded as values,
39
+ * or carried as a pending update that completes when the atom leaves the
40
+ * initial state.
41
+ *
42
+ * @category dehydration
43
+ * @since 4.0.0
44
+ */
45
+ declare const dehydrate: (registry: Registry, options?: {
46
+ /**
47
+ * How to encode `AsyncResult.Initial` values. Default is "ignore".
48
+ */
49
+ readonly encodeInitialAs?: 'ignore' | 'deferred' | 'value-only' | undefined;
50
+ }) => DehydratedAtomValue[];
51
+ /**
52
+ * Applies dehydrated atom state to a registry.
53
+ *
54
+ * **When to use**
55
+ *
56
+ * Use to preload serialized atom values into a target registry before those
57
+ * atoms are read.
58
+ *
59
+ * **Details**
60
+ *
61
+ * Encoded values are preloaded by serialization key. Entries whose initial
62
+ * state was carried as pending (see `dehydrate`'s `encodeInitialAs`) update the
63
+ * matching registry node, or preload the resolved value, when that pending
64
+ * value completes.
65
+ *
66
+ * Returns a fiber that completes once every pending update has been applied to
67
+ * the registry. Callers that need the state fully settled — tests, SSR flushes
68
+ * — can join it; fire-and-forget callers can ignore it.
69
+ *
70
+ * @category hydration
71
+ * @since 4.0.0
72
+ */
73
+ declare const hydrate: (registry: Registry, dehydratedState: Iterable<DehydratedAtomValue>) => Fiber.Fiber<void, never>;
74
+ //#endregion
75
+ export { DehydratedAtom, DehydratedAtomValue, dehydrate, hydrate, Hydration_d_exports as t };
@@ -0,0 +1,113 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
+ import { h as isSerializable, i as SerializableTypeId } from "./Atom-DVWTGeoV.mjs";
3
+ import { F as isResult, h as isInitial } from "./Result-rlUvoHzK.mjs";
4
+ import * as Effect from "effect/Effect";
5
+ import * as Clock from "effect/Clock";
6
+ import * as Deferred from "effect/Deferred";
7
+ //#region src/Hydration.ts
8
+ /**
9
+ * Saves and restores serializable atom state.
10
+ *
11
+ * `dehydrate` reads atoms marked with `Atom.serializable` from an
12
+ * `AtomRegistry` and returns encoded entries keyed by their serialization keys.
13
+ * `hydrate` preloads those entries into another registry before the atoms are
14
+ * read. Initial `AsyncResult` values can be ignored, encoded as values, or
15
+ * carried as a pending update that settles the target registry once the source
16
+ * atom leaves the initial state.
17
+ *
18
+ * @since 4.0.0
19
+ */
20
+ var Hydration_exports = /* @__PURE__ */ __exportAll({
21
+ dehydrate: () => dehydrate,
22
+ hydrate: () => hydrate
23
+ });
24
+ /**
25
+ * Non-serializable completion channel for entries dehydrated in `'deferred'`
26
+ * mode. Keyed by entry identity so nothing appears on the public surface: the
27
+ * same objects `dehydrate` returns must be handed to `hydrate`. A `Deferred`
28
+ * cannot cross a serialization boundary anyway, so entries that do cross one
29
+ * are simply applied as plain preloads.
30
+ */
31
+ const pendingResults = /* @__PURE__ */ new WeakMap();
32
+ /**
33
+ * Encodes the serializable atoms currently stored in a registry into dehydrated
34
+ * state.
35
+ *
36
+ * **Details**
37
+ *
38
+ * Only atoms marked with `Atom.serializable` are included. `encodeInitialAs`
39
+ * controls whether `AsyncResult.Initial` values are ignored, encoded as values,
40
+ * or carried as a pending update that completes when the atom leaves the
41
+ * initial state.
42
+ *
43
+ * @category dehydration
44
+ * @since 4.0.0
45
+ */
46
+ const dehydrate = (registry, options) => {
47
+ const encodeInitialResultMode = options?.encodeInitialAs ?? "ignore";
48
+ const arr = [];
49
+ const now = Effect.runSync(Clock.currentTimeMillis);
50
+ registry.getNodes().forEach((node, key) => {
51
+ if (!isSerializable(node.atom)) return;
52
+ const atom = node.atom;
53
+ const value = node.value();
54
+ const isInitial$1 = isResult(value) && isInitial(value);
55
+ if (encodeInitialResultMode === "ignore" && isInitial$1) return;
56
+ if (typeof key !== "string") return;
57
+ const serializer = atom[SerializableTypeId];
58
+ const entry = {
59
+ "~effect/reactivity/DehydratedAtom": true,
60
+ key,
61
+ value: serializer.encode(value),
62
+ dehydratedAt: now
63
+ };
64
+ if (encodeInitialResultMode === "deferred" && isInitial$1) {
65
+ const deferred = Deferred.makeUnsafe();
66
+ const unsubscribe = registry.subscribe(atom, (newValue) => {
67
+ if (isResult(newValue) && !isInitial(newValue)) {
68
+ Deferred.doneUnsafe(deferred, Effect.succeed(serializer.encode(newValue)));
69
+ unsubscribe();
70
+ }
71
+ });
72
+ pendingResults.set(entry, deferred);
73
+ }
74
+ arr.push(entry);
75
+ });
76
+ return arr;
77
+ };
78
+ /**
79
+ * Applies dehydrated atom state to a registry.
80
+ *
81
+ * **When to use**
82
+ *
83
+ * Use to preload serialized atom values into a target registry before those
84
+ * atoms are read.
85
+ *
86
+ * **Details**
87
+ *
88
+ * Encoded values are preloaded by serialization key. Entries whose initial
89
+ * state was carried as pending (see `dehydrate`'s `encodeInitialAs`) update the
90
+ * matching registry node, or preload the resolved value, when that pending
91
+ * value completes.
92
+ *
93
+ * Returns a fiber that completes once every pending update has been applied to
94
+ * the registry. Callers that need the state fully settled — tests, SSR flushes
95
+ * — can join it; fire-and-forget callers can ignore it.
96
+ *
97
+ * @category hydration
98
+ * @since 4.0.0
99
+ */
100
+ const hydrate = (registry, dehydratedState) => {
101
+ const pending = [];
102
+ for (const datom of dehydratedState) {
103
+ registry.setSerializable(datom.key, datom.value);
104
+ const result = pendingResults.get(datom);
105
+ if (result === void 0) continue;
106
+ pending.push(Effect.flatMap(Deferred.await(result), (resolvedValue) => Effect.sync(() => {
107
+ registry.setSerializable(datom.key, resolvedValue);
108
+ })));
109
+ }
110
+ return Effect.runFork(Effect.forEach(pending, (effect) => effect, { discard: true }));
111
+ };
112
+ //#endregion
113
+ export { dehydrate, hydrate, Hydration_exports as t };