rxfy 0.2.1 → 0.3.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/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Observable, BehaviorSubject } from 'rxjs';
2
- import PQueue from 'p-queue';
2
+ import { z } from 'zod';
3
3
 
4
4
  type IAtom<T> = Observable<T> & {
5
5
  get: () => T;
@@ -15,6 +15,47 @@ declare class Atom<T> extends Observable<T> implements IAtom<T> {
15
15
  }
16
16
  declare function createAtom<T>(value: T): Atom<T>;
17
17
 
18
+ type ILens<TSource, TTarget> = {
19
+ get: (source: TSource) => TTarget;
20
+ set: (current: TTarget, source: TSource) => TSource;
21
+ };
22
+ declare class Lens<TSource, TTarget> extends Observable<TTarget> implements IAtom<TTarget> {
23
+ private subject$;
24
+ private sub;
25
+ private subCount;
26
+ private source$;
27
+ private lens;
28
+ constructor(source$: IAtom<TSource>, lens: ILens<TSource, TTarget>);
29
+ set: (value: TTarget) => void;
30
+ get: () => TTarget;
31
+ modify: (fn: (val: TTarget) => TTarget) => void;
32
+ }
33
+ declare function createLens<TSource, TTarget>(source$: IAtom<TSource>, lens: ILens<TSource, TTarget>): Lens<TSource, TTarget>;
34
+ declare function keyLens<TSource, TTarget extends keyof TSource>(key: TTarget): ILens<TSource, TSource[TTarget]>;
35
+
36
+ /** Extracts the key type from an entity's `id` field; falls back to `string` for plain string IDs. */
37
+ type EntityKey<T> = T extends {
38
+ id: infer TKey extends string;
39
+ } ? TKey : string;
40
+ type ModelDescriptor<T, TKey extends string = string> = {
41
+ readonly _key: symbol;
42
+ /** Stable string identity for SSR dehydration — symbols cannot cross the server/client boundary. */
43
+ readonly name?: string;
44
+ readonly schema: z.ZodType<T, z.ZodTypeDef, any>;
45
+ readonly getKey: (item: T) => TKey;
46
+ };
47
+ type FieldDescriptor<TShape> = {
48
+ readonly _shape?: TShape;
49
+ readonly kind: "single" | "array";
50
+ readonly model: ModelDescriptor<any, any>;
51
+ };
52
+ declare function createModel<TOutput, TKey extends string, TDef extends z.ZodTypeDef = z.ZodTypeDef, TInput = TOutput>(schema: z.ZodType<TOutput, TDef, TInput>, opts: {
53
+ getKey: (item: TOutput) => TKey;
54
+ name?: string;
55
+ }): ModelDescriptor<TOutput, TKey>;
56
+ declare function array<T, TKey extends string>(model: ModelDescriptor<T, TKey>): FieldDescriptor<T[]>;
57
+ declare function single<T, TKey extends string>(model: ModelDescriptor<T, TKey>): FieldDescriptor<T>;
58
+
18
59
  declare enum StatusEnum {
19
60
  IDLE = "IDLE",
20
61
  PENDING = "PENDING",
@@ -44,55 +85,154 @@ declare function createPending<Result>(): IWrapped<Result, StatusEnum.PENDING>;
44
85
  declare function createFulfilled<Result>(value: Result): IWrapped<Result, StatusEnum.FULFILLED>;
45
86
  declare function createRejected<Result>(error: unknown): IWrapped<Result, StatusEnum.REJECTED>;
46
87
 
47
- type IBranded<TBrand, TData> = {
48
- brand: TBrand;
49
- value: TData;
88
+ type QueryCache = {
89
+ /** Get-or-create the query's status Atom, seeded IDLE. Shared per key → dedup. Note: TValue is an unchecked assertion — the cache stores Atom<IWrapped<unknown>> internally, matching how peek works. */
90
+ getQuery: <TValue = unknown>(key: string) => Atom<IWrapped<TValue>>;
91
+ /** Current value without creating a cell — used by serialization and sync reads. */
92
+ peek: <TValue = unknown>(key: string) => IWrapped<TValue> | undefined;
93
+ delete: (key: string) => void;
94
+ /** Terminal-state entries (FULFILLED/REJECTED) for serialization. */
95
+ entries: () => [string, IWrapped<unknown>][];
96
+ /** In-flight promise slot — SSR Suspense throws and server-side request dedup. Never serialized. */
97
+ getPromise: (key: string) => Promise<unknown> | undefined;
98
+ setPromise: (key: string, promise: Promise<unknown>) => void;
99
+ inflight: () => Promise<unknown>[];
50
100
  };
51
- declare function toBranded<TBrand extends string, TData>(brand: TBrand, value: TData): IBranded<TBrand, TData>;
52
- declare function isBranded<TBrand extends string, TData>(brand: TBrand, val: any): val is IBranded<TBrand, TData>;
53
- type IEdgeState<TData> = IWrapped<TData>;
54
- type IEdgeJS<TData> = IBranded<"edge", IEdgeState<TData>>;
55
- type IEdge<TData> = {
56
- subject$: IAtom<IEdgeState<TData>>;
57
- toObservable: () => Observable<TData>;
58
- next: () => Promise<IWrapped<TData, StatusEnum.FULFILLED | StatusEnum.REJECTED>>;
101
+ declare function createQueryCache(): QueryCache;
102
+
103
+ type ModelStore<T> = {
104
+ get: (key: EntityKey<T>) => Observable<T>;
105
+ set: (key: string, val: T) => void;
106
+ setMany: (items: T[]) => void;
107
+ /** Synchronous read of the latest value — used by denormalization and dehydration. */
108
+ getValue: (key: string) => T | undefined;
109
+ valueEntries: () => [string, T][];
110
+ /**
111
+ * Writable handle over a single entity's cell — for field Lenses and form binding.
112
+ * Assumes the entity is already loaded; returns `undefined` typed as `T` if the key has not been set.
113
+ */
114
+ entity: (key: EntityKey<T>) => IAtom<T>;
115
+ /**
116
+ * Emits a key the first time its entity becomes present (the first `set`); updates to an existing
117
+ * entity do not re-emit. New subscribers replay the keys already present, so a late subscriber
118
+ * still learns about everything in the store. Lets a live-update layer track exactly what the
119
+ * store holds without each query wiring its ids in by hand.
120
+ */
121
+ added$: Observable<string>;
59
122
  };
60
- declare function createEdge<TData>(state$: IAtom<IEdgeState<TData>>, queue: PQueue, loader: () => Observable<TData>): IEdge<TData>;
123
+ type IModelRegistry = {
124
+ model: <T>(descriptor: ModelDescriptor<T>) => ModelStore<T>;
125
+ /** SSR query cache — fulfilled/rejected entries keyed by state key + params. */
126
+ queries: QueryCache;
127
+ namedStores: () => ReadonlyMap<string, ModelStore<any>>;
128
+ stores: () => {
129
+ descriptor: ModelDescriptor<any>;
130
+ store: ModelStore<any>;
131
+ }[];
132
+ /** Queue entities for a named model; seeds the store now if it exists, or on first creation otherwise. */
133
+ stashHydration: (name: string, entities: Record<string, unknown>) => void;
134
+ /**
135
+ * Every entity added to any *named* store, tagged with that store's `name` (the half of a
136
+ * `name:key` topic). Unnamed stores are skipped — there's no name to address them by. Replays
137
+ * what's already in the registry to new subscribers, and follows stores created after subscribe.
138
+ * A live-update client can drive its subscriptions straight off this instead of per-query wiring.
139
+ */
140
+ added$: Observable<{
141
+ name: string;
142
+ key: string;
143
+ }>;
144
+ };
145
+ declare function createModelStore<T>(descriptor: ModelDescriptor<T>): ModelStore<T>;
146
+ declare function createModelRegistry(): IModelRegistry;
147
+
148
+ /** Deterministic JSON.stringify — object keys sorted recursively so server and client produce identical cache keys. */
149
+ declare function stableStringify(value: unknown): string;
61
150
 
62
- type IMapJS = IBranded<"map", {
63
- [K in string]: IMapJS | IEdgeJS<unknown>;
64
- }>;
65
- type IStoreState = {
66
- [K in string]: IStoreStateJS | IMapJS | IEdgeJS<unknown>;
151
+ type SerializedError = {
152
+ name: string;
153
+ message: string;
67
154
  };
68
- type IStoreStateJS = IBranded<"store", IStoreState>;
69
- type IFactory<TData> = {
70
- get: (key: string) => IEdge<TData>;
155
+ declare function serializeError(error: unknown): SerializedError;
156
+ declare function rehydrateError(serialized: SerializedError): Error;
157
+ /** JSON for inline <script> embedding — escapes "<" so payloads cannot break out of the script tag. */
158
+ declare function serializeForHtml(value: unknown): string;
159
+ /** Wire form of a settled query — only terminal states cross the server/client boundary. */
160
+ type SerializedWrapped<TValue = unknown> = {
161
+ type: StatusEnum.FULFILLED;
162
+ value: TValue;
163
+ } | {
164
+ type: StatusEnum.REJECTED;
165
+ error: SerializedError;
71
166
  };
72
- type IStore<TState extends IStoreStateJS> = {
73
- state$: IAtom<TState>;
74
- collect: () => Promise<void>;
75
- node: (name: string) => IStore<IStoreStateJS>;
76
- factory: <TData>(name: string, loader: (key: string) => Observable<TData>) => IFactory<TData>;
77
- factoryBatch: <TData>(name: string, batch: (key: string[]) => Observable<Record<string, TData>>) => IFactory<TData>;
167
+ /** In-memory IWrapped wire form. Returns undefined for IDLE/PENDING (never serialized). */
168
+ declare function serializeWrapped<TValue>(wrapped: IWrapped<TValue>): SerializedWrapped<TValue> | undefined;
169
+ /** Wire form → in-memory IWrapped carrying a live Error. */
170
+ declare function deserializeWrapped<TValue>(entry: SerializedWrapped<TValue>): IWrapped<TValue>;
171
+
172
+ type DehydratedState = {
173
+ queries: Record<string, SerializedWrapped>;
174
+ models: Record<string, Record<string, unknown>>;
78
175
  };
79
- declare function createState(val: IStoreState): IStoreStateJS;
80
- declare function createStore<TState extends IStoreStateJS>(queue: PQueue, state$: IAtom<TState>): IStore<TState>;
176
+ /** Serializes the registry's query cache (ids) and named model stores (entities) to a JSON-safe snapshot. */
177
+ declare function dehydrate(registry: IModelRegistry): DehydratedState;
178
+ /** Ingests a dehydrated snapshot: model entities → stores (via stash), query entries → cache. */
179
+ declare function hydrate(registry: IModelRegistry, state: DehydratedState): void;
180
+ /**
181
+ * Complete inline <script> tag pushing a snapshot onto window.__RXFY_SSR__ — the queue
182
+ * StoreProvider drains automatically on the client, so no client-side wiring is needed.
183
+ * Inject it into the served HTML before the client entry script.
184
+ */
185
+ declare function hydrationScript(state: DehydratedState): string;
81
186
 
82
- type ILens<TSource, TTarget> = {
83
- get: (source: TSource) => TTarget;
84
- set: (current: TTarget, source: TSource) => TSource;
187
+ /**
188
+ * Marks an observable as emitting synchronously on subscribe — safe for usePending's render-time probe.
189
+ * Mutates the target via a symbol property; the target must be an extensible object (rxfy-created
190
+ * observables always are). Intended for rxfy-internal call sites, not arbitrary user observables.
191
+ */
192
+ declare function markSync<T extends object>(target: T): T;
193
+ /** True if the observable was flagged by markSync as emitting synchronously on subscribe. */
194
+ declare function isSyncMarked(target: object): boolean;
195
+ /** Attaches the owning handle's reload() so consumers can invalidate the query cache via getAttachedReload. */
196
+ declare function attachReload<T extends object>(target: T, reload: () => void): T;
197
+ /** Returns the reload callback set by attachReload, if any. */
198
+ declare function getAttachedReload(target: object): (() => void) | undefined;
199
+
200
+ type FieldsMap = Record<string, FieldDescriptor<any>>;
201
+ type ShapeFromFields<T extends FieldsMap> = {
202
+ [K in keyof T]: T[K] extends FieldDescriptor<infer S> ? S : never;
85
203
  };
86
- declare class Lens<TSource, TTarget> extends Observable<TTarget> implements IAtom<TTarget> {
87
- private subject$;
88
- private sub;
89
- private subCount;
90
- constructor(source$: IAtom<TSource>, lens: ILens<TSource, TTarget>);
91
- set: (value: TTarget) => void;
92
- get: () => TTarget;
93
- modify: (fn: (val: TTarget) => TTarget) => void;
94
- }
95
- declare function createLens<TSource, TTarget>(source$: IAtom<TSource>, lens: ILens<TSource, TTarget>): Lens<TSource, TTarget>;
96
- declare function keyLens<TSource, TTarget extends keyof TSource>(key: TTarget): ILens<TSource, TSource[TTarget]>;
204
+ /** The normalized shape data$ emits: array fields become entity key arrays, single fields become entity keys. */
205
+ type QueryShapeOf<TShape> = {
206
+ [K in keyof TShape]: TShape[K] extends readonly (infer Item)[] ? EntityKey<Item>[] : EntityKey<TShape[K]>;
207
+ };
208
+ type MutationDefs<TShape> = {
209
+ [key: string]: (prev: TShape, ...args: any[]) => TShape;
210
+ };
211
+ type StateDescriptor<TParams, TShape, TMutations extends MutationDefs<TShape> = Record<never, never>> = {
212
+ /** Stable string identity for the SSR query cache. States without a key opt out of SSR caching. */
213
+ readonly key?: string;
214
+ readonly paramsSchema: z.ZodType<TParams, z.ZodTypeDef, any>;
215
+ readonly fields: {
216
+ [K in keyof TShape]: FieldDescriptor<TShape[K]>;
217
+ };
218
+ readonly mutations: TMutations;
219
+ };
220
+ declare function defineState<TParams, TFields extends FieldsMap>(def: {
221
+ key?: string;
222
+ params: z.ZodType<TParams, z.ZodTypeDef, any>;
223
+ model: TFields;
224
+ mutations?: undefined;
225
+ }): StateDescriptor<TParams, ShapeFromFields<TFields>, Record<never, never>>;
226
+ declare function defineState<TParams, TFields extends FieldsMap, TMutations extends MutationDefs<ShapeFromFields<TFields>>>(def: {
227
+ key?: string;
228
+ params: z.ZodType<TParams, z.ZodTypeDef, any>;
229
+ model: TFields;
230
+ mutations: TMutations;
231
+ }): StateDescriptor<TParams, ShapeFromFields<TFields>, TMutations>;
232
+
233
+ /** Splits a denormalized fetch result: entities → model stores, ids → returned query shape. */
234
+ declare function normalizeResult<TShape>(registry: IModelRegistry, fields: FieldsMap, value: TShape): QueryShapeOf<TShape>;
235
+ /** Rebuilds the fetch shape from ids by reading store value maps — reducers always see the freshest entities. */
236
+ declare function denormalizeValue<TShape>(registry: IModelRegistry, fields: FieldsMap, ids: QueryShapeOf<TShape>): TShape;
97
237
 
98
- export { Atom, type IAtom, type IBranded, type IEdge, type IEdgeJS, type IEdgeState, type ILens, type IWrapped, Lens, StatusEnum, createAtom, createEdge, createFulfilled, createIdle, createLens, createPending, createRejected, createState, createStore, isBranded, keyLens, toBranded };
238
+ export { Atom, type DehydratedState, type EntityKey, type FieldDescriptor, type FieldsMap, type IAtom, type ILens, type IModelRegistry, type IWrapped, Lens, type ModelDescriptor, type ModelStore, type MutationDefs, type QueryCache, type QueryShapeOf, type SerializedError, type SerializedWrapped, type ShapeFromFields, type StateDescriptor, StatusEnum, array, attachReload, createAtom, createFulfilled, createIdle, createLens, createModel, createModelRegistry, createModelStore, createPending, createQueryCache, createRejected, defineState, dehydrate, denormalizeValue, deserializeWrapped, getAttachedReload, hydrate, hydrationScript, isSyncMarked, keyLens, markSync, normalizeResult, rehydrateError, serializeError, serializeForHtml, serializeWrapped, single, stableStringify };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Observable, BehaviorSubject } from 'rxjs';
2
- import PQueue from 'p-queue';
2
+ import { z } from 'zod';
3
3
 
4
4
  type IAtom<T> = Observable<T> & {
5
5
  get: () => T;
@@ -15,6 +15,47 @@ declare class Atom<T> extends Observable<T> implements IAtom<T> {
15
15
  }
16
16
  declare function createAtom<T>(value: T): Atom<T>;
17
17
 
18
+ type ILens<TSource, TTarget> = {
19
+ get: (source: TSource) => TTarget;
20
+ set: (current: TTarget, source: TSource) => TSource;
21
+ };
22
+ declare class Lens<TSource, TTarget> extends Observable<TTarget> implements IAtom<TTarget> {
23
+ private subject$;
24
+ private sub;
25
+ private subCount;
26
+ private source$;
27
+ private lens;
28
+ constructor(source$: IAtom<TSource>, lens: ILens<TSource, TTarget>);
29
+ set: (value: TTarget) => void;
30
+ get: () => TTarget;
31
+ modify: (fn: (val: TTarget) => TTarget) => void;
32
+ }
33
+ declare function createLens<TSource, TTarget>(source$: IAtom<TSource>, lens: ILens<TSource, TTarget>): Lens<TSource, TTarget>;
34
+ declare function keyLens<TSource, TTarget extends keyof TSource>(key: TTarget): ILens<TSource, TSource[TTarget]>;
35
+
36
+ /** Extracts the key type from an entity's `id` field; falls back to `string` for plain string IDs. */
37
+ type EntityKey<T> = T extends {
38
+ id: infer TKey extends string;
39
+ } ? TKey : string;
40
+ type ModelDescriptor<T, TKey extends string = string> = {
41
+ readonly _key: symbol;
42
+ /** Stable string identity for SSR dehydration — symbols cannot cross the server/client boundary. */
43
+ readonly name?: string;
44
+ readonly schema: z.ZodType<T, z.ZodTypeDef, any>;
45
+ readonly getKey: (item: T) => TKey;
46
+ };
47
+ type FieldDescriptor<TShape> = {
48
+ readonly _shape?: TShape;
49
+ readonly kind: "single" | "array";
50
+ readonly model: ModelDescriptor<any, any>;
51
+ };
52
+ declare function createModel<TOutput, TKey extends string, TDef extends z.ZodTypeDef = z.ZodTypeDef, TInput = TOutput>(schema: z.ZodType<TOutput, TDef, TInput>, opts: {
53
+ getKey: (item: TOutput) => TKey;
54
+ name?: string;
55
+ }): ModelDescriptor<TOutput, TKey>;
56
+ declare function array<T, TKey extends string>(model: ModelDescriptor<T, TKey>): FieldDescriptor<T[]>;
57
+ declare function single<T, TKey extends string>(model: ModelDescriptor<T, TKey>): FieldDescriptor<T>;
58
+
18
59
  declare enum StatusEnum {
19
60
  IDLE = "IDLE",
20
61
  PENDING = "PENDING",
@@ -44,55 +85,154 @@ declare function createPending<Result>(): IWrapped<Result, StatusEnum.PENDING>;
44
85
  declare function createFulfilled<Result>(value: Result): IWrapped<Result, StatusEnum.FULFILLED>;
45
86
  declare function createRejected<Result>(error: unknown): IWrapped<Result, StatusEnum.REJECTED>;
46
87
 
47
- type IBranded<TBrand, TData> = {
48
- brand: TBrand;
49
- value: TData;
88
+ type QueryCache = {
89
+ /** Get-or-create the query's status Atom, seeded IDLE. Shared per key → dedup. Note: TValue is an unchecked assertion — the cache stores Atom<IWrapped<unknown>> internally, matching how peek works. */
90
+ getQuery: <TValue = unknown>(key: string) => Atom<IWrapped<TValue>>;
91
+ /** Current value without creating a cell — used by serialization and sync reads. */
92
+ peek: <TValue = unknown>(key: string) => IWrapped<TValue> | undefined;
93
+ delete: (key: string) => void;
94
+ /** Terminal-state entries (FULFILLED/REJECTED) for serialization. */
95
+ entries: () => [string, IWrapped<unknown>][];
96
+ /** In-flight promise slot — SSR Suspense throws and server-side request dedup. Never serialized. */
97
+ getPromise: (key: string) => Promise<unknown> | undefined;
98
+ setPromise: (key: string, promise: Promise<unknown>) => void;
99
+ inflight: () => Promise<unknown>[];
50
100
  };
51
- declare function toBranded<TBrand extends string, TData>(brand: TBrand, value: TData): IBranded<TBrand, TData>;
52
- declare function isBranded<TBrand extends string, TData>(brand: TBrand, val: any): val is IBranded<TBrand, TData>;
53
- type IEdgeState<TData> = IWrapped<TData>;
54
- type IEdgeJS<TData> = IBranded<"edge", IEdgeState<TData>>;
55
- type IEdge<TData> = {
56
- subject$: IAtom<IEdgeState<TData>>;
57
- toObservable: () => Observable<TData>;
58
- next: () => Promise<IWrapped<TData, StatusEnum.FULFILLED | StatusEnum.REJECTED>>;
101
+ declare function createQueryCache(): QueryCache;
102
+
103
+ type ModelStore<T> = {
104
+ get: (key: EntityKey<T>) => Observable<T>;
105
+ set: (key: string, val: T) => void;
106
+ setMany: (items: T[]) => void;
107
+ /** Synchronous read of the latest value — used by denormalization and dehydration. */
108
+ getValue: (key: string) => T | undefined;
109
+ valueEntries: () => [string, T][];
110
+ /**
111
+ * Writable handle over a single entity's cell — for field Lenses and form binding.
112
+ * Assumes the entity is already loaded; returns `undefined` typed as `T` if the key has not been set.
113
+ */
114
+ entity: (key: EntityKey<T>) => IAtom<T>;
115
+ /**
116
+ * Emits a key the first time its entity becomes present (the first `set`); updates to an existing
117
+ * entity do not re-emit. New subscribers replay the keys already present, so a late subscriber
118
+ * still learns about everything in the store. Lets a live-update layer track exactly what the
119
+ * store holds without each query wiring its ids in by hand.
120
+ */
121
+ added$: Observable<string>;
59
122
  };
60
- declare function createEdge<TData>(state$: IAtom<IEdgeState<TData>>, queue: PQueue, loader: () => Observable<TData>): IEdge<TData>;
123
+ type IModelRegistry = {
124
+ model: <T>(descriptor: ModelDescriptor<T>) => ModelStore<T>;
125
+ /** SSR query cache — fulfilled/rejected entries keyed by state key + params. */
126
+ queries: QueryCache;
127
+ namedStores: () => ReadonlyMap<string, ModelStore<any>>;
128
+ stores: () => {
129
+ descriptor: ModelDescriptor<any>;
130
+ store: ModelStore<any>;
131
+ }[];
132
+ /** Queue entities for a named model; seeds the store now if it exists, or on first creation otherwise. */
133
+ stashHydration: (name: string, entities: Record<string, unknown>) => void;
134
+ /**
135
+ * Every entity added to any *named* store, tagged with that store's `name` (the half of a
136
+ * `name:key` topic). Unnamed stores are skipped — there's no name to address them by. Replays
137
+ * what's already in the registry to new subscribers, and follows stores created after subscribe.
138
+ * A live-update client can drive its subscriptions straight off this instead of per-query wiring.
139
+ */
140
+ added$: Observable<{
141
+ name: string;
142
+ key: string;
143
+ }>;
144
+ };
145
+ declare function createModelStore<T>(descriptor: ModelDescriptor<T>): ModelStore<T>;
146
+ declare function createModelRegistry(): IModelRegistry;
147
+
148
+ /** Deterministic JSON.stringify — object keys sorted recursively so server and client produce identical cache keys. */
149
+ declare function stableStringify(value: unknown): string;
61
150
 
62
- type IMapJS = IBranded<"map", {
63
- [K in string]: IMapJS | IEdgeJS<unknown>;
64
- }>;
65
- type IStoreState = {
66
- [K in string]: IStoreStateJS | IMapJS | IEdgeJS<unknown>;
151
+ type SerializedError = {
152
+ name: string;
153
+ message: string;
67
154
  };
68
- type IStoreStateJS = IBranded<"store", IStoreState>;
69
- type IFactory<TData> = {
70
- get: (key: string) => IEdge<TData>;
155
+ declare function serializeError(error: unknown): SerializedError;
156
+ declare function rehydrateError(serialized: SerializedError): Error;
157
+ /** JSON for inline <script> embedding — escapes "<" so payloads cannot break out of the script tag. */
158
+ declare function serializeForHtml(value: unknown): string;
159
+ /** Wire form of a settled query — only terminal states cross the server/client boundary. */
160
+ type SerializedWrapped<TValue = unknown> = {
161
+ type: StatusEnum.FULFILLED;
162
+ value: TValue;
163
+ } | {
164
+ type: StatusEnum.REJECTED;
165
+ error: SerializedError;
71
166
  };
72
- type IStore<TState extends IStoreStateJS> = {
73
- state$: IAtom<TState>;
74
- collect: () => Promise<void>;
75
- node: (name: string) => IStore<IStoreStateJS>;
76
- factory: <TData>(name: string, loader: (key: string) => Observable<TData>) => IFactory<TData>;
77
- factoryBatch: <TData>(name: string, batch: (key: string[]) => Observable<Record<string, TData>>) => IFactory<TData>;
167
+ /** In-memory IWrapped wire form. Returns undefined for IDLE/PENDING (never serialized). */
168
+ declare function serializeWrapped<TValue>(wrapped: IWrapped<TValue>): SerializedWrapped<TValue> | undefined;
169
+ /** Wire form → in-memory IWrapped carrying a live Error. */
170
+ declare function deserializeWrapped<TValue>(entry: SerializedWrapped<TValue>): IWrapped<TValue>;
171
+
172
+ type DehydratedState = {
173
+ queries: Record<string, SerializedWrapped>;
174
+ models: Record<string, Record<string, unknown>>;
78
175
  };
79
- declare function createState(val: IStoreState): IStoreStateJS;
80
- declare function createStore<TState extends IStoreStateJS>(queue: PQueue, state$: IAtom<TState>): IStore<TState>;
176
+ /** Serializes the registry's query cache (ids) and named model stores (entities) to a JSON-safe snapshot. */
177
+ declare function dehydrate(registry: IModelRegistry): DehydratedState;
178
+ /** Ingests a dehydrated snapshot: model entities → stores (via stash), query entries → cache. */
179
+ declare function hydrate(registry: IModelRegistry, state: DehydratedState): void;
180
+ /**
181
+ * Complete inline <script> tag pushing a snapshot onto window.__RXFY_SSR__ — the queue
182
+ * StoreProvider drains automatically on the client, so no client-side wiring is needed.
183
+ * Inject it into the served HTML before the client entry script.
184
+ */
185
+ declare function hydrationScript(state: DehydratedState): string;
81
186
 
82
- type ILens<TSource, TTarget> = {
83
- get: (source: TSource) => TTarget;
84
- set: (current: TTarget, source: TSource) => TSource;
187
+ /**
188
+ * Marks an observable as emitting synchronously on subscribe — safe for usePending's render-time probe.
189
+ * Mutates the target via a symbol property; the target must be an extensible object (rxfy-created
190
+ * observables always are). Intended for rxfy-internal call sites, not arbitrary user observables.
191
+ */
192
+ declare function markSync<T extends object>(target: T): T;
193
+ /** True if the observable was flagged by markSync as emitting synchronously on subscribe. */
194
+ declare function isSyncMarked(target: object): boolean;
195
+ /** Attaches the owning handle's reload() so consumers can invalidate the query cache via getAttachedReload. */
196
+ declare function attachReload<T extends object>(target: T, reload: () => void): T;
197
+ /** Returns the reload callback set by attachReload, if any. */
198
+ declare function getAttachedReload(target: object): (() => void) | undefined;
199
+
200
+ type FieldsMap = Record<string, FieldDescriptor<any>>;
201
+ type ShapeFromFields<T extends FieldsMap> = {
202
+ [K in keyof T]: T[K] extends FieldDescriptor<infer S> ? S : never;
85
203
  };
86
- declare class Lens<TSource, TTarget> extends Observable<TTarget> implements IAtom<TTarget> {
87
- private subject$;
88
- private sub;
89
- private subCount;
90
- constructor(source$: IAtom<TSource>, lens: ILens<TSource, TTarget>);
91
- set: (value: TTarget) => void;
92
- get: () => TTarget;
93
- modify: (fn: (val: TTarget) => TTarget) => void;
94
- }
95
- declare function createLens<TSource, TTarget>(source$: IAtom<TSource>, lens: ILens<TSource, TTarget>): Lens<TSource, TTarget>;
96
- declare function keyLens<TSource, TTarget extends keyof TSource>(key: TTarget): ILens<TSource, TSource[TTarget]>;
204
+ /** The normalized shape data$ emits: array fields become entity key arrays, single fields become entity keys. */
205
+ type QueryShapeOf<TShape> = {
206
+ [K in keyof TShape]: TShape[K] extends readonly (infer Item)[] ? EntityKey<Item>[] : EntityKey<TShape[K]>;
207
+ };
208
+ type MutationDefs<TShape> = {
209
+ [key: string]: (prev: TShape, ...args: any[]) => TShape;
210
+ };
211
+ type StateDescriptor<TParams, TShape, TMutations extends MutationDefs<TShape> = Record<never, never>> = {
212
+ /** Stable string identity for the SSR query cache. States without a key opt out of SSR caching. */
213
+ readonly key?: string;
214
+ readonly paramsSchema: z.ZodType<TParams, z.ZodTypeDef, any>;
215
+ readonly fields: {
216
+ [K in keyof TShape]: FieldDescriptor<TShape[K]>;
217
+ };
218
+ readonly mutations: TMutations;
219
+ };
220
+ declare function defineState<TParams, TFields extends FieldsMap>(def: {
221
+ key?: string;
222
+ params: z.ZodType<TParams, z.ZodTypeDef, any>;
223
+ model: TFields;
224
+ mutations?: undefined;
225
+ }): StateDescriptor<TParams, ShapeFromFields<TFields>, Record<never, never>>;
226
+ declare function defineState<TParams, TFields extends FieldsMap, TMutations extends MutationDefs<ShapeFromFields<TFields>>>(def: {
227
+ key?: string;
228
+ params: z.ZodType<TParams, z.ZodTypeDef, any>;
229
+ model: TFields;
230
+ mutations: TMutations;
231
+ }): StateDescriptor<TParams, ShapeFromFields<TFields>, TMutations>;
232
+
233
+ /** Splits a denormalized fetch result: entities → model stores, ids → returned query shape. */
234
+ declare function normalizeResult<TShape>(registry: IModelRegistry, fields: FieldsMap, value: TShape): QueryShapeOf<TShape>;
235
+ /** Rebuilds the fetch shape from ids by reading store value maps — reducers always see the freshest entities. */
236
+ declare function denormalizeValue<TShape>(registry: IModelRegistry, fields: FieldsMap, ids: QueryShapeOf<TShape>): TShape;
97
237
 
98
- export { Atom, type IAtom, type IBranded, type IEdge, type IEdgeJS, type IEdgeState, type ILens, type IWrapped, Lens, StatusEnum, createAtom, createEdge, createFulfilled, createIdle, createLens, createPending, createRejected, createState, createStore, isBranded, keyLens, toBranded };
238
+ export { Atom, type DehydratedState, type EntityKey, type FieldDescriptor, type FieldsMap, type IAtom, type ILens, type IModelRegistry, type IWrapped, Lens, type ModelDescriptor, type ModelStore, type MutationDefs, type QueryCache, type QueryShapeOf, type SerializedError, type SerializedWrapped, type ShapeFromFields, type StateDescriptor, StatusEnum, array, attachReload, createAtom, createFulfilled, createIdle, createLens, createModel, createModelRegistry, createModelStore, createPending, createQueryCache, createRejected, defineState, dehydrate, denormalizeValue, deserializeWrapped, getAttachedReload, hydrate, hydrationScript, isSyncMarked, keyLens, markSync, normalizeResult, rehydrateError, serializeError, serializeForHtml, serializeWrapped, single, stableStringify };