@nkzw/fate 0.0.6 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -526,6 +526,7 @@ Let's assume that our `Post` entity has a tRPC mutation for liking a post called
526
526
 
527
527
  ```tsx
528
528
  const LikeButton = ({ post }: { post: { id: string; likes: number } }) => {
529
+ const fate = useFateClient();
529
530
  const [result, like] = useActionState(fate.actions.post.like, null);
530
531
 
531
532
  return (
@@ -540,6 +541,7 @@ If you are not using an async component library, you can use React's `useTransit
540
541
 
541
542
  ```tsx
542
543
  const LikeButton = ({ post }: { post: { id: string; likes: number } }) => {
544
+ const fate = useFateClient();
543
545
  const [, startTransition] = useTransition();
544
546
  const [result, like, isPending] = useActionState(
545
547
  fate.actions.post.like,
@@ -959,10 +961,10 @@ export type AppRouter = typeof appRouter;
959
961
  export * from './views.ts';
960
962
  ```
961
963
 
962
- _Note: We try to keep magic to a minimum and you can handwrite the [generated client](https://github.com/nkzw-tech/fate/blob/main/example/client/src/lib/fate.generated.ts) if you prefer._
964
+ _Note: We try to keep magic to a minimum and you can handwrite the [generated client](https://github.com/nkzw-tech/fate/blob/main/example/client/src/fate.ts) if you prefer._
963
965
 
964
966
  ```bash
965
- pnpm fate generate @your-org/server/trpc/router.ts client/src/lib/fate.generated.ts
967
+ pnpm fate generate @your-org/server/trpc/router.ts client/src/fate.ts
966
968
  ```
967
969
 
968
970
  _Note: fate uses the specified server module name to extract the server types it needs and uses the same module name to import the views into the generated client. Make sure that the module is available both at the root where you are running the CLI and in the client package._
@@ -1041,5 +1043,6 @@ Probably. One day. _Maybe._
1041
1043
 
1042
1044
  - [Relay](https://relay.dev/), [Isograph](https://isograph.dev/) & [GraphQL](https://graphql.org/) for inspiration
1043
1045
  - [Ricky Hanlon](https://x.com/rickyfm) for guidance on Async React
1046
+ - [Anthony Powell](https://x.com/Cephalization) for testing fate and providing feedback
1044
1047
 
1045
1048
  **_fate_** was created by [@cnakazawa](https://x.com/cnakazawa) and is maintained by [Nakazawa Tech](https://nakazawa.tech/).
package/lib/cli.mjs CHANGED
@@ -66,7 +66,7 @@ Generates the fate client from the server's tRPC router.
66
66
  ${styleText("dim", "<moduleName>")} The module name to import the tRPC router from.
67
67
  ${styleText("dim", "<targetFile>")} The file path to write the generated client to.
68
68
 
69
- ${styleText("bold", "Example:")} ${styleText("blue", `pnpm fate generate @org/server/trpc/router.ts client/lib/fate.generated.ts`)}
69
+ ${styleText("bold", "Example:")} ${styleText("blue", `pnpm fate generate @org/server/trpc/router.ts client/lib/fate.ts`)}
70
70
  `);
71
71
  process.exit(1);
72
72
  }
@@ -149,7 +149,7 @@ const generate = async () => {
149
149
  const typeImports = `import type { ${viewTypes.join(", ")} } from '${moduleName}';`;
150
150
  const typesBlock = indentBlock(formatTypes(types), 6);
151
151
  const mutationResolverBlock = indentBlock(mutationResolverLines.join("\n"), 4);
152
- const mutationConfigBlock = indentBlock(mutationConfigLines.join("\n"), 6);
152
+ const mutationConfigBlock = indentBlock(mutationConfigLines.join("\n"), 2);
153
153
  const byIdBlock = indentBlock(byIdLines.join("\n"), 8);
154
154
  const listsBlockContent = listLines.join("\n");
155
155
  const source = `// @generated by \`pnpm fate generate\`
@@ -162,27 +162,35 @@ type TRPCClientType = ReturnType<typeof createTRPCProxyClient<AppRouter>>;
162
162
  type RouterInputs = inferRouterInputs<AppRouter>;
163
163
  type RouterOutputs = inferRouterOutputs<AppRouter>;
164
164
 
165
+ const mutations = {
166
+ ${mutationConfigBlock}
167
+ } as const;
168
+
169
+ type GeneratedClientMutations = typeof mutations;
170
+
171
+ declare module 'react-fate' {
172
+ interface ClientMutations extends GeneratedClientMutations {}
173
+ }
174
+
165
175
  export const createFateClient = (options: {
166
176
  links: Parameters<typeof createTRPCProxyClient>[0]['links'];
167
177
  }) => {
168
178
  const trpcClient = createTRPCProxyClient<AppRouter>(options);
169
179
 
170
- const mutations = {
180
+ const trpcMutations = {
171
181
  ${mutationResolverBlock}
172
182
  } as const;
173
183
 
174
184
  return createClient({
175
- mutations: {
176
- ${mutationConfigBlock}
177
- },
178
- transport: createTRPCTransport<AppRouter, typeof mutations>({
185
+ mutations,
186
+ transport: createTRPCTransport<AppRouter, typeof trpcMutations>({
179
187
  byId: {
180
188
  ${byIdBlock}
181
189
  },
182
190
  client: trpcClient,
183
- ${listLines.length ? ` lists: {\n${indentBlock(listsBlockContent, 8)}\n },\n` : ""} mutations,
191
+ ${listLines.length ? ` lists: {\n${indentBlock(listsBlockContent, 8)}\n },\n` : ""} mutations: trpcMutations,
184
192
  }),
185
- types: ${typesBlock},
193
+ types: ${typesBlock.trimStart()},
186
194
  });
187
195
  };
188
196
  `;
package/lib/index.d.mts CHANGED
@@ -7,277 +7,230 @@ type FieldMask = {
7
7
  children: Map<string, FieldMask>;
8
8
  };
9
9
  //#endregion
10
- //#region src/types.d.ts
11
- /** Canonical runtime name for an entity type as returned by the server. */
12
- type TypeName = string;
13
- /** Globally unique identifier for an entity in the normalized cache (`<TypeName>:<id>` format). */
14
- type EntityId = string;
15
- /** Internal marker added to objects that represent a view payload. */
16
- declare const ViewKind: unique symbol;
17
- /** Symbol used to attach the set of view tags that were spread into a ref or masked record. */
18
- declare const ViewsTag: unique symbol;
19
- /** Symbol attached to connection results so pagination helpers can find their metadata. */
20
- declare const ConnectionTag: unique symbol;
21
- declare const __FateEntityBrand: unique symbol;
22
- declare const __FateSelectionBrand: unique symbol;
23
- declare const __FateMutationEntityBrand: unique symbol;
24
- declare const __FateMutationInputBrand: unique symbol;
25
- declare const __FateMutationResultBrand: unique symbol;
26
- type __ViewEntityAnchor<T$1 extends Entity> = {
27
- readonly [__FateEntityBrand]?: T$1;
28
- };
29
- type __ViewSelectionAnchor<S> = {
30
- [__FateSelectionBrand]?: S;
31
- };
32
- type __MutationEntityAnchor<T$1 extends Entity> = {
33
- readonly [__FateMutationEntityBrand]?: T$1;
34
- };
35
- type __MutationInputAnchor<I$1> = {
36
- readonly [__FateMutationInputBrand]?: I$1;
37
- };
38
- type __MutationResultAnchor<R$1> = {
39
- readonly [__FateMutationResultBrand]?: R$1;
10
+ //#region src/selection.d.ts
11
+ /**
12
+ * Representation of a composed selection including hashed args and the
13
+ * flat set of field paths to read or fetch.
14
+ */
15
+ type SelectionPlan = {
16
+ readonly args: Map<string, Readonly<{
17
+ hash: string;
18
+ ignoreKeys?: ReadonlySet<string>;
19
+ value: AnyRecord;
20
+ }>>;
21
+ readonly paths: Set<string>;
40
22
  };
41
- /** Unique key that identifies a view composition entry inside a selection or reference. */
42
- type ViewTag = `__fate-view__${string}`;
43
- /** Determines whether a property key is a fate view tag. */
44
- declare function isViewTag(key: string): key is ViewTag;
45
- /** Alias for a loose record used throughout the fate's internals. */
46
- type AnyRecord = Record<string, unknown>;
47
- type SelectionArgs = Readonly<{
48
- args: AnyRecord;
49
- }>;
50
- /** Metadata stored alongside connection results to power pagination and cache updates. */
51
- type ConnectionMetadata = Readonly<{
52
- args?: AnyRecord;
53
- field: string;
54
- hash?: string;
55
- key: string;
56
- owner: EntityId;
57
- procedure: string;
58
- root?: boolean;
59
- type: string;
60
- }>;
61
- /** Reference to a normalized entity instance that can be resolved against one or more view tags. */
62
- type ViewRef<TName extends string> = Readonly<{
63
- __typename: TName;
64
- id: string | number;
65
- [ViewsTag]: Set<string>;
66
- }>;
67
- /** Describes how a field relates to another entity for normalization. */
68
- type RelationDescriptor = /** Field stores a scalar value that does not link to another type. */
69
- 'scalar'
70
- /** Field points to a single entity of the given type. */ | {
71
- type: string;
23
+ /**
24
+ * Flattens a view into a `SelectionPlan`, expanding composed views and
25
+ * partitioning nested args so the client can fetch exactly what is declared.
26
+ */
27
+ declare const getSelectionPlan: <T$1 extends Entity, S extends Selection<T$1>, V extends View<T$1, S>>(viewComposition: V, ref: ViewRef<T$1["__typename"]> | null) => SelectionPlan;
28
+ //#endregion
29
+ //#region src/cache.d.ts
30
+ declare class ViewDataCache {
31
+ private cache;
32
+ private rootDependencies;
33
+ private dependencyIndex;
34
+ get<T$1 extends Entity, S extends Selection<T$1>, V extends View<T$1, S>>(entityId: EntityId, view: V, ref: ViewRef<T$1['__typename']>): FateThenable<ViewSnapshot<T$1, S>> | null;
35
+ set<T$1 extends Entity, S extends Selection<T$1>, V extends View<T$1, S>>(entityId: EntityId, view: V, ref: ViewRef<T$1['__typename']>, thenable: FateThenable<ViewSnapshot<T$1, S>>, dependencies: ReadonlySet<EntityId>): void;
36
+ invalidate(entityId: EntityId): void;
37
+ private invalidateDependents;
38
+ private delete;
72
39
  }
73
- /** Field holds a list of entities of the given type. */ | {
74
- listOf: string;
75
- };
76
- /** Configuration for a server entity type used by the client cache. */
77
- type TypeConfig = {
78
- fields?: Record<string, RelationDescriptor>;
79
- getId: (record: unknown) => string | number;
80
- type: string;
81
- };
82
- /** Pagination state returned alongside connection lists. */
83
- type Pagination = {
84
- hasNext: boolean;
85
- hasPrevious: boolean;
86
- nextCursor?: string;
87
- previousCursor?: string;
88
- };
89
- /** Ref for a connection, including pagination metadata. */
90
- type ConnectionRef<TName extends string> = Readonly<{
91
- items: ReadonlyArray<{
92
- cursor?: string;
93
- node: ViewRef<TName>;
94
- }>;
40
+ //#endregion
41
+ //#region src/store.d.ts
42
+ type List = Readonly<{
43
+ cursors?: ReadonlyArray<string | undefined>;
44
+ ids: ReadonlyArray<EntityId>;
95
45
  pagination?: Pagination;
96
46
  }>;
97
- /** Base shape shared by all entities fetched by fate. */
98
- type Entity = {
99
- __typename: string;
100
- };
101
- type PlainObjectSelectionField<V> = V extends Array<infer U> ? PlainObjectSelectionField<U> | true : V extends AnyRecord ? PlainObjectSelection<V> | true : true;
102
- type PlainObjectSelection<T$1> = { [K in keyof T$1]?: PlainObjectSelectionField<T$1[K]> };
103
- type ConnectionSelectionBase<T$1 extends Entity> = Readonly<{
104
- items: Readonly<{
105
- cursor?: true;
106
- node: Selection<T$1> | View<T$1, Selection<T$1>>;
107
- }>;
108
- pagination?: Readonly<{
109
- hasNext?: true;
110
- hasPrevious?: true;
111
- nextCursor?: true;
112
- previousCursor?: true;
47
+ declare class Store {
48
+ private coverage;
49
+ private lists;
50
+ private records;
51
+ private subscriptions;
52
+ private listSubscriptions;
53
+ read(id: EntityId): AnyRecord | undefined;
54
+ merge(id: EntityId, partial: AnyRecord, paths: Iterable<string>): void;
55
+ private mergeInternal;
56
+ deleteRecord(id: EntityId): void;
57
+ missingForSelection(id: EntityId, paths: Iterable<string>): Set<string>;
58
+ subscribe(id: EntityId, selection: ReadonlySet<string> | null, fn: () => void): () => void;
59
+ subscribe(id: EntityId, fn: () => void): () => void;
60
+ private notify;
61
+ private notifyListSubscribers;
62
+ getList(key: string): ReadonlyArray<EntityId> | undefined;
63
+ getListState(key: string): List | undefined;
64
+ getListsForField(ownerId: EntityId, field: string): Array<readonly [string, List]>;
65
+ setList(key: string, state: List): void;
66
+ restoreList(key: string, list?: List): void;
67
+ subscribeList(key: string, fn: () => void): () => void;
68
+ removeReferencesTo(targetId: EntityId, viewDataCache: ViewDataCache, snapshots?: Map<EntityId, Snapshot>, listSnapshots?: Map<string, List>): void;
69
+ snapshot(id: EntityId): Snapshot;
70
+ restore(id: EntityId, snapshot: Snapshot): void;
71
+ }
72
+ //#endregion
73
+ //#region src/transport.d.ts
74
+ /**
75
+ * Normalized representation of args passed to a transport.
76
+ */
77
+ type ResolvedArgsPayload = AnyRecord;
78
+ type TransportMutations = Record<string, MutationShape>;
79
+ type EmptyTransportMutations = Record<never, MutationShape>;
80
+ /**
81
+ * Contract the fate client expects from a network transport. The transport is
82
+ * responsible for fetching records by ID, fetching lists, and executing
83
+ * mutations with the provided selections.
84
+ */
85
+ interface Transport<Mutations extends TransportMutations = EmptyTransportMutations> {
86
+ fetchById(type: string, ids: Array<string | number>, select: Iterable<string>, args?: ResolvedArgsPayload): Promise<Array<unknown>>;
87
+ fetchList?(proc: string, select: Iterable<string>, args?: ResolvedArgsPayload): Promise<{
88
+ items: Array<{
89
+ cursor: string | undefined;
90
+ node: unknown;
91
+ }>;
92
+ pagination: Pagination;
113
93
  }>;
114
- }>;
115
- /** Selection shape for a connection-style list, including pagination metadata and optional arguments. */
116
- type ConnectionSelection<T$1 extends Entity> = ConnectionSelectionBase<T$1> | (SelectionArgs & ConnectionSelectionBase<T$1>);
117
- type BaseSelectionFieldValue<T$1 extends Entity, K$1 extends keyof T$1> = NonNullable<T$1[K$1]> extends Array<infer U extends Entity> ? true | Selection<U> | ConnectionSelection<U> | View<U, Selection<U>> : NonNullable<T$1[K$1]> extends Entity ? true | Selection<NonNullable<T$1[K$1]>> | View<NonNullable<T$1[K$1]>, Selection<NonNullable<T$1[K$1]>>> : NonNullable<T$1[K$1]> extends AnyRecord ? PlainObjectSelection<NonNullable<T$1[K$1]>> : true;
118
- type SelectionFieldValue<T$1 extends Entity, K$1 extends keyof T$1> = BaseSelectionFieldValue<T$1, K$1> | SelectionArgs | (SelectionArgs & Extract<BaseSelectionFieldValue<T$1, K$1>, object>);
119
- type SelectionShape<T$1 extends Entity> = { [K in keyof T$1 as K extends '__typename' ? never : K]?: SelectionFieldValue<T$1, K> } & {
120
- __typename?: true;
121
- };
122
- type SelectionViewSpread<T$1 extends Entity> = { readonly [K in ViewTag]?: Readonly<{
123
- select: Selection<T$1>;
124
- [ViewKind]: true;
125
- }> };
126
- /** Declarative selection of the fields a view needs from an entity. */
127
- type Selection<T$1 extends Entity> = SelectionShape<T$1> & SelectionViewSpread<T$1>;
128
- /** View payload stored on a view tag containing the raw selection used to mask data. */
129
- type ViewPayload<T$1 extends Entity, S extends Selection<T$1> = Selection<T$1>> = Readonly<{
130
- select: S;
131
- [ViewKind]: true;
132
- }>;
133
- /** Definition of a view over an entity type, including the selection of fields. */
134
- type View<T$1 extends Entity, S extends Selection<T$1> = Selection<T$1>> = Readonly<{
135
- [viewTag: ViewTag]: ViewPayload<T$1, S>;
136
- }> & __ViewEntityAnchor<T$1> & __ViewSelectionAnchor<S>;
137
- type HasViewTag<S> = S extends { [K in ViewTag]?: infer P } ? P extends {
138
- [ViewKind]: true;
139
- } ? true : false : false;
94
+ mutate?<K$1 extends Extract<keyof Mutations, string>>(proc: K$1, input: Mutations[K$1]['input'], select: Set<string>): Promise<Mutations[K$1]['output']>;
95
+ }
140
96
  /**
141
- * Data returned from a resolved view with masking applied and view tags
142
- * attached for downstream composition.
97
+ * Mapping of entity type to tRPC procedures used for fetching entities by ID.
143
98
  */
144
- type ViewData<T$1 extends Entity, S extends Selection<T$1>> = Readonly<(S extends Selection<T$1> ? Mask<T$1, S> : T$1) & {
145
- [ViewsTag]: Set<string>;
146
- }>;
99
+ type TRPCByIdResolvers<AppRouter extends AnyRouter> = Record<string, (client: TRPCClient<AppRouter>) => (input: {
100
+ args?: ResolvedArgsPayload;
101
+ ids: Array<string | number>;
102
+ select: Array<string>;
103
+ }) => Promise<Array<unknown>>>;
147
104
  /**
148
- * Snapshot returned by the cache for a view, including the masked data and all
149
- * referenced entity IDs.
105
+ * Mapping of list procedure name to a tRPC resolver factory.
150
106
  */
151
- type ViewSnapshot<T$1 extends Entity, S extends Selection<T$1>> = Readonly<{
152
- coverage: ReadonlyArray<readonly [id: EntityId, paths: ReadonlySet<string>]>;
153
- data: ViewData<T$1, S>;
154
- }>;
155
- type ConnectionMask<T$1 extends Entity, S> = S extends {
156
- items: infer ItemSelection;
157
- pagination?: infer PaginationSelection;
158
- } ? {
159
- items: ItemSelection extends {
160
- cursor?: infer CursorSelection;
107
+ type TRPCListResolvers<AppRouter extends AnyRouter> = Record<string, (client: TRPCClient<AppRouter>) => (input: {
108
+ args?: ResolvedArgsPayload;
109
+ select: Array<string>;
110
+ }) => Promise<{
111
+ items: Array<{
112
+ cursor: string | undefined;
161
113
  node: unknown;
162
- } ? Array<(CursorSelection extends true ? {
163
- cursor: string;
164
- } : Record<string, never>) & {
165
- node: ViewRef<T$1['__typename']>;
166
- }> : Array<{
167
- node: ViewRef<T$1['__typename']>;
168
114
  }>;
169
- } & (PaginationSelection extends object ? {
170
- pagination: { [K in keyof PaginationSelection]: Pagination[K & keyof Pagination] };
171
- } : Record<string, never>) : Array<T$1>;
172
- type EntityName<T$1> = T$1 extends {
173
- __typename: infer N extends string;
174
- } ? N : never;
175
- /** Recursively applies a view selection to an entity to mask fields that aren't selected. */
176
- type Mask<T$1, S> = T$1 extends Array<infer U extends Entity> ? S extends true ? Array<U> : S extends ConnectionSelection<U> ? ConnectionMask<U, S> : HasViewTag<S> extends true ? Array<ViewRef<U['__typename']>> : Array<Mask<U, S>> : S extends true ? T$1 : S extends object ? HasViewTag<S> extends true ? ViewRef<EntityName<NonNullable<T$1>>> : { [K in keyof S as K extends 'args' ? never : K]: S[K] extends true ? NonNullable<T$1>[Extract<K, keyof T$1>] : Mask<NonNullable<T$1>[Extract<K, keyof T$1>], Extract<S[K], object>> } & (T$1 extends Entity ? Pick<NonNullable<T$1>, '__typename'> : Record<never, never>) : T$1;
177
- /** Entity type captured from a view definition. */
178
- type ViewEntity<V> = V extends View<infer T, any> ? T : never;
179
- /** Name of the entity type captured from a view definition. */
180
- type ViewEntityName<V> = ViewEntity<V>['__typename'] & string;
181
- /** Selection captured from a view definition. */
182
- type ViewSelection<V> = V extends {
183
- readonly [__FateSelectionBrand]?: infer S;
184
- } ? S : never;
185
- /** Definition of a list request for fetching data from the backend. */
186
- type ListItem<V extends View<any, any>> = Readonly<{
187
- args?: Record<string, unknown>;
188
- root: V;
189
- type: ViewEntityName<V>;
190
- }>;
191
- /** Definition of a node request with one explicit ID for fetching data from the backend. */
192
- type NodeItem<V extends View<any, any>> = Readonly<{
193
- id: string | number;
194
- root: V;
195
- type: ViewEntityName<V>;
196
- }>;
197
- /** Definition of a node request with explicit IDs for fetching data from the backend. */
198
- type NodesItem<V extends View<any, any>> = Readonly<{
199
- ids: ReadonlyArray<string | number>;
200
- root: V;
201
- type: ViewEntityName<V>;
202
- }>;
203
- type RequestItem = ListItem<View<any, any>> | NodeItem<View<any, any>> | NodesItem<View<any, any>>;
204
- /** Collection of node and list requests describing the data a screen needs. */
205
- type Request = Record<string, RequestItem>;
206
- type AnyView = View<any, any>;
207
- type AnyListItem = ListItem<AnyView>;
208
- type AnyNodeItem = NodeItem<AnyView>;
209
- type AnyNodesItem = NodesItem<AnyView>;
210
- type AnyRequestItem = AnyListItem | AnyNodeItem | AnyNodesItem;
211
- type AnyRequest = Record<string, AnyRequestItem>;
115
+ pagination: Pagination;
116
+ }>>;
212
117
  /**
213
- * Typed result returned by `useRequest` and `FateClient.request`, mapping each
214
- * request key to an array of view refs for the requested type.
118
+ * Mapping of a mutation procedure name to a tRPC resolver factory.
215
119
  */
216
- type ConnectionNodeType<Root> = Root extends {
217
- items?: {
218
- node?: infer Node;
219
- };
220
- } ? ViewEntityName<Node & View<any, any>> : never;
221
- type ListResult<Item extends AnyRequestItem> = Item extends AnyNodeItem ? ViewRef<Item['type']> : Item extends AnyNodesItem ? Array<ViewRef<Item['type']>> : Item extends AnyListItem ? Item['root'] extends {
222
- items?: {
223
- node?: View<any, any>;
224
- };
225
- } ? Readonly<{
226
- items: ReadonlyArray<{
227
- cursor?: string | undefined;
228
- node: ViewRef<ConnectionNodeType<Item['root']>>;
229
- }>;
230
- pagination?: Pagination;
231
- }> : Array<ViewRef<Item['type']>> : never;
120
+ type MutationResolver<AppRouter extends AnyRouter> = (client: TRPCClient<AppRouter>) => (input: any) => Promise<any>;
121
+ type TRPCMutationResolvers<AppRouter extends AnyRouter> = Record<string, MutationResolver<AppRouter>>;
122
+ type MutationMapFromResolvers<R$1 extends Record<string, MutationResolver<any>>> = { [K in keyof R$1]: R$1[K] extends ((client: any) => (input: infer Input) => Promise<infer Output>) ? {
123
+ input: Input;
124
+ output: Output;
125
+ } : never };
126
+ type EmptyMutationResolvers<AppRouter extends AnyRouter> = Record<never, MutationResolver<AppRouter>>;
232
127
  /**
233
- * The result of a `FateClient.request` and `useRequest` call, mapping each
234
- * request key to its corresponding result.
128
+ * Builds a `Transport` backed by a tRPC client using the configured resolvers
129
+ * for by-id queries, lists, and mutations.
235
130
  */
236
- type RequestResult<Q extends AnyRequest> = { [K in keyof Q]: ListResult<Q[K]> };
237
- /** Brand used on mutation definitions to mark their identity in the d.ts output. */
238
- declare const MutationKind = "__fate__mutation";
239
- /** Metadata describing a mutation for a particular entity, input, and output. */
240
- type MutationDefinition<T$1 extends Entity, I$1, R$1> = Readonly<{
241
- entity: T$1['__typename'];
242
- [MutationKind]: true;
243
- }> & __MutationEntityAnchor<T$1> & __MutationInputAnchor<I$1> & __MutationResultAnchor<R$1>;
244
- type MutationIdentifier<T$1 extends Entity, I$1, R$1> = MutationDefinition<T$1, I$1, R$1> & Readonly<{
245
- key: string;
131
+ declare function createTRPCTransport<AppRouter extends AnyRouter, Mutations extends TRPCMutationResolvers<AppRouter> = EmptyMutationResolvers<AppRouter>>({
132
+ byId,
133
+ client,
134
+ lists,
135
+ mutations
136
+ }: {
137
+ byId: TRPCByIdResolvers<AppRouter>;
138
+ client: TRPCClient<AppRouter>;
139
+ lists?: TRPCListResolvers<AppRouter>;
140
+ mutations?: Mutations;
141
+ }): Transport<MutationMapFromResolvers<Mutations>>;
142
+ //#endregion
143
+ //#region src/client.d.ts
144
+ /**
145
+ * Strategy used when resolving a request.
146
+ */
147
+ type RequestMode = /** (default) Use cached data if present, otherwise fetch. */
148
+ 'cache-first'
149
+ /** Show cached data immediately and refresh in the background. */ | 'stale-while-revalidate'
150
+ /** Always fetch from the network and ignore cached entries. */ | 'network-only';
151
+ /**
152
+ * Request options that affect how requests are fetched and retained.
153
+ */
154
+ type RequestOptions = Readonly<{
155
+ mode?: RequestMode;
246
156
  }>;
247
- /** Extracts the input type from a mutation definition or identifier. */
248
- type MutationInput<M> = M extends __MutationInputAnchor<infer I> ? I : never;
249
- /** Extracts the result type from a mutation definition or identifier. */
250
- type MutationResult<M> = M extends __MutationResultAnchor<infer R> ? R : never;
251
- /** Extracts the entity type from a mutation definition or identifier. */
252
- type MutationEntity<M> = M extends __MutationEntityAnchor<infer E> ? E : never;
253
- /** Minimal mutation description used for transport typing. */
254
- type MutationShape = {
255
- input: unknown;
256
- output: unknown;
157
+ type FateClientOptions<Mutations extends FateMutations = EmptyMutations> = {
158
+ mutations?: Mutations;
159
+ transport: Transport<MutationMapFromDefinitions<Mutations>>;
160
+ types: ReadonlyArray<Omit<TypeConfig, 'getId'> & Partial<{
161
+ getId: TypeConfig['getId'];
162
+ }>>;
257
163
  };
258
164
  /**
259
- * Convenience helper that maps mutation definitions to their input/output
260
- * shapes for use by transports.
165
+ * Core client that normalizes records, manages the view cache, and coordinates
166
+ * data fetching.
261
167
  */
262
- type MutationMapFromDefinitions<D extends Record<string, MutationDefinition<any, any, any>>> = { [K in keyof D]: {
263
- input: MutationInput<D[K]>;
264
- output: MutationResult<D[K]>;
265
- } };
266
- type Nullish<T$1> = Extract<T$1, null | undefined>;
267
- type NonNullish<T$1> = Exclude<T$1, null | undefined>;
268
- type OptimisticUpdateValue<T$1> = T$1 extends ReadonlyArray<infer U> ? Array<OptimisticUpdateValue<U>> : NonNullish<T$1> extends AnyRecord ? OptimisticUpdate<NonNullish<T$1>> | Nullish<T$1> : NonNullish<T$1> | Nullish<T$1>;
269
- /** Shape used to describe optimistic updates for mutations. */
270
- type OptimisticUpdate<T$1> = { [K in keyof T$1]?: OptimisticUpdateValue<T$1[K]> };
271
- /** Snapshot captured before mutating the cache, used to roll back on errors. */
272
- type Snapshot = Readonly<{
273
- mask?: FieldMask;
274
- record?: AnyRecord;
275
- }>;
276
- /** Promise-like value returned by cache reads that already have a resolved payload for React `use`. */
277
- interface FateThenable<T$1> extends PromiseLike<T$1> {
278
- status: 'fulfilled';
279
- value: T$1;
168
+ declare class FateClient<Mutations extends FateMutations> {
169
+ private readonly mutationMap;
170
+ private readonly parentLists;
171
+ private readonly pending;
172
+ private readonly optimisticMasks;
173
+ private readonly optimisticByEntity;
174
+ private optimisticTokenCounter;
175
+ private readonly requests;
176
+ private readonly stalledRequests;
177
+ readonly store: Store;
178
+ private readonly types;
179
+ private readonly transport;
180
+ private readonly viewDataCache;
181
+ readonly actions: MutationActionsFor<Mutations>;
182
+ readonly mutations: MutationFunctionsFor<Mutations>;
183
+ constructor(options: FateClientOptions<Mutations>);
184
+ private initializeParentLists;
185
+ getTypeConfig(type: string): TypeConfig;
186
+ executeMutation(key: string, input: unknown, select: Set<string>, options?: {
187
+ args?: AnyRecord;
188
+ plan?: SelectionPlan;
189
+ }): Promise<unknown>;
190
+ write(type: string, data: AnyRecord, select: ReadonlySet<string>, snapshots?: Map<EntityId, Snapshot>, plan?: SelectionPlan, pathPrefix?: string | null, blockedMask?: FieldMask | null): string;
191
+ deleteRecord(type: string, id: string | number, snapshots?: Map<EntityId, Snapshot>, listSnapshots?: Map<string, List>): void;
192
+ restore(id: EntityId, snapshot: Snapshot): void;
193
+ restoreList(name: string, list?: List): void;
194
+ ref<T$1 extends Entity>(type: T$1['__typename'], id: string | number, view: View<T$1, Selection<T$1>>): ViewRef<T$1['__typename']>;
195
+ rootListRef(entityId: EntityId, rootView: View<any, any>): Readonly<{
196
+ __typename: string;
197
+ id: string | number;
198
+ [ViewsTag]: Set<string>;
199
+ }>;
200
+ readView<T$1 extends Entity, S extends Selection<T$1>, V extends View<T$1, S>>(view: V, ref: ViewRef<T$1['__typename']>): FateThenable<ViewSnapshot<T$1, S>>;
201
+ private mergeListState;
202
+ registerOptimisticUpdate(entityId: EntityId | null, select: ReadonlySet<string>): number | null;
203
+ clearOptimisticUpdate(token: number | null): void;
204
+ getPendingOptimisticMask(entityId: EntityId | null, options?: {
205
+ excludeToken?: number | null;
206
+ }): FieldMask | null;
207
+ filterSelectionForPendingOptimistics(entityId: EntityId | null, select: Set<string>, options?: {
208
+ excludeToken?: number | null;
209
+ }): Set<string>;
210
+ loadConnection<V extends View<any, any>>(view: V, connection: ConnectionMetadata, args: Record<string, unknown>, options?: {
211
+ direction?: 'forward' | 'backward';
212
+ }): Promise<Readonly<{
213
+ cursors?: ReadonlyArray<string | undefined>;
214
+ ids: ReadonlyArray<EntityId>;
215
+ pagination?: Pagination;
216
+ }> | undefined>;
217
+ request<R$1 extends Request>(request: R$1, options?: RequestOptions): Promise<RequestResult<R$1>>;
218
+ releaseRequest(request: Request, mode: RequestMode): void;
219
+ private handleStoreAndNetworkRequest;
220
+ private executeRequest;
221
+ private hasRequestData;
222
+ getRequestResult<R$1 extends Request>(request: R$1): RequestResult<R$1>;
223
+ private fetchByIdAndNormalize;
224
+ private fetchListAndNormalize;
225
+ private resolveListSelection;
226
+ private writeEntity;
227
+ private linkParentLists;
228
+ private readViewSelection;
229
+ private clearStalledRequestsForEntity;
230
+ private pendingPrefix;
231
+ private pendingKey;
280
232
  }
233
+ declare function createClient<Mutations extends FateMutations = EmptyMutations>(options: FateClientOptions<Mutations>): FateClient<Mutations>;
281
234
  //#endregion
282
235
  //#region src/mutation.d.ts
283
236
  /**
@@ -327,241 +280,291 @@ options: MutationOptions<I$1> | 'reset') => Promise<{
327
280
  error: Error;
328
281
  result: undefined;
329
282
  }>;
330
- //#endregion
331
- //#region src/selection.d.ts
332
- /**
333
- * Representation of a composed selection including hashed args and the
334
- * flat set of field paths to read or fetch.
335
- */
336
- type SelectionPlan = {
337
- readonly args: Map<string, Readonly<{
338
- hash: string;
339
- ignoreKeys?: ReadonlySet<string>;
340
- value: AnyRecord;
341
- }>>;
342
- readonly paths: Set<string>;
343
- };
283
+ type MutationIdentifierFor<K$1 extends string, Def extends MutationDefinition<any, any, any>> = Def extends MutationDefinition<infer T, infer I, infer R> ? MutationIdentifier<T, I, R> & Readonly<{
284
+ key: K$1;
285
+ }> : never;
286
+ type UnionToIntersection<U$1> = (U$1 extends any ? (k: U$1) => void : never) extends ((k: infer I) => void) ? I : never;
287
+ type NestedValue<Path extends string, Value> = Path extends `${infer Head}.${infer Tail}` ? { [K in Head]: NestedValue<Tail, Value> } : { [K in Path]: Value };
288
+ type EmptyMutations = Record<never, MutationDefinition<any, any, any>>;
344
289
  /**
345
- * Flattens a view into a `SelectionPlan`, expanding composed views and
346
- * partitioning nested args so the client can fetch exactly what is declared.
290
+ * Base type for client mutations.
347
291
  */
348
- declare const getSelectionPlan: <T$1 extends Entity, S extends Selection<T$1>, V extends View<T$1, S>>(viewComposition: V, ref: ViewRef<T$1["__typename"]> | null) => SelectionPlan;
292
+ type FateMutations = Record<string, MutationDefinition<any, any, any>>;
293
+ type MutationTreeFromRecord<Mutations extends FateMutations, ValueMap extends Record<string, unknown>> = [keyof Mutations] extends [never] ? object : UnionToIntersection<{ [K in keyof Mutations & string]: NestedValue<K, ValueMap[K]> }[keyof Mutations & string]>;
294
+ type MutationFunctionsFor<Mutations extends FateMutations> = MutationTreeFromRecord<Mutations, { [K in keyof Mutations & string]: MutationFunction<MutationIdentifierFor<K, Mutations[K]>> }>;
295
+ type MutationActionsFor<Mutations extends FateMutations> = MutationTreeFromRecord<Mutations, { [K in keyof Mutations & string]: MutationAction<MutationIdentifierFor<K, Mutations[K]>> }>;
349
296
  //#endregion
350
- //#region src/cache.d.ts
351
- declare class ViewDataCache {
352
- private cache;
353
- private rootDependencies;
354
- private dependencyIndex;
355
- get<T$1 extends Entity, S extends Selection<T$1>, V extends View<T$1, S>>(entityId: EntityId, view: V, ref: ViewRef<T$1['__typename']>): FateThenable<ViewSnapshot<T$1, S>> | null;
356
- set<T$1 extends Entity, S extends Selection<T$1>, V extends View<T$1, S>>(entityId: EntityId, view: V, ref: ViewRef<T$1['__typename']>, thenable: FateThenable<ViewSnapshot<T$1, S>>, dependencies: ReadonlySet<EntityId>): void;
357
- invalidate(entityId: EntityId): void;
358
- private invalidateDependents;
359
- private delete;
297
+ //#region src/types.d.ts
298
+ /** Canonical runtime name for an entity type as returned by the server. */
299
+ type TypeName = string;
300
+ /** Globally unique identifier for an entity in the normalized cache (`<TypeName>:<id>` format). */
301
+ type EntityId = string;
302
+ /** Internal marker added to objects that represent a view payload. */
303
+ declare const ViewKind: unique symbol;
304
+ /** Symbol used to attach the set of view tags that were spread into a ref or masked record. */
305
+ declare const ViewsTag: unique symbol;
306
+ /** Symbol attached to connection results so pagination helpers can find their metadata. */
307
+ declare const ConnectionTag: unique symbol;
308
+ declare const __FateEntityBrand: unique symbol;
309
+ declare const __FateSelectionBrand: unique symbol;
310
+ declare const __FateMutationEntityBrand: unique symbol;
311
+ declare const __FateMutationInputBrand: unique symbol;
312
+ declare const __FateMutationResultBrand: unique symbol;
313
+ type __ViewEntityAnchor<T$1 extends Entity> = {
314
+ readonly [__FateEntityBrand]?: T$1;
315
+ };
316
+ type __ViewSelectionAnchor<S> = {
317
+ [__FateSelectionBrand]?: S;
318
+ };
319
+ type __MutationEntityAnchor<T$1 extends Entity> = {
320
+ readonly [__FateMutationEntityBrand]?: T$1;
321
+ };
322
+ type __MutationInputAnchor<I$1> = {
323
+ readonly [__FateMutationInputBrand]?: I$1;
324
+ };
325
+ type __MutationResultAnchor<R$1> = {
326
+ readonly [__FateMutationResultBrand]?: R$1;
327
+ };
328
+ /** Unique key that identifies a view composition entry inside a selection or reference. */
329
+ type ViewTag = `__fate-view__${string}`;
330
+ /** Determines whether a property key is a fate view tag. */
331
+ declare function isViewTag(key: string): key is ViewTag;
332
+ /** Alias for a loose record used throughout the fate's internals. */
333
+ type AnyRecord = Record<string, unknown>;
334
+ type SelectionArgs = Readonly<{
335
+ args: AnyRecord;
336
+ }>;
337
+ /** Metadata stored alongside connection results to power pagination and cache updates. */
338
+ type ConnectionMetadata = Readonly<{
339
+ args?: AnyRecord;
340
+ field: string;
341
+ hash?: string;
342
+ key: string;
343
+ owner: EntityId;
344
+ procedure: string;
345
+ root?: boolean;
346
+ type: string;
347
+ }>;
348
+ /** Reference to a normalized entity instance that can be resolved against one or more view tags. */
349
+ type ViewRef<TName extends string> = Readonly<{
350
+ __typename: TName;
351
+ id: string | number;
352
+ [ViewsTag]: Set<string>;
353
+ }>;
354
+ /** Describes how a field relates to another entity for normalization. */
355
+ type RelationDescriptor = /** Field stores a scalar value that does not link to another type. */
356
+ 'scalar'
357
+ /** Field points to a single entity of the given type. */ | {
358
+ type: string;
360
359
  }
361
- //#endregion
362
- //#region src/store.d.ts
363
- type List = Readonly<{
364
- cursors?: ReadonlyArray<string | undefined>;
365
- ids: ReadonlyArray<EntityId>;
360
+ /** Field holds a list of entities of the given type. */ | {
361
+ listOf: string;
362
+ };
363
+ /** Configuration for a server entity type used by the client cache. */
364
+ type TypeConfig = {
365
+ fields?: Record<string, RelationDescriptor>;
366
+ getId: (record: unknown) => string | number;
367
+ type: string;
368
+ };
369
+ /** Pagination state returned alongside connection lists. */
370
+ type Pagination = {
371
+ hasNext: boolean;
372
+ hasPrevious: boolean;
373
+ nextCursor?: string;
374
+ previousCursor?: string;
375
+ };
376
+ /** Ref for a connection, including pagination metadata. */
377
+ type ConnectionRef<TName extends string> = Readonly<{
378
+ items: ReadonlyArray<{
379
+ cursor?: string;
380
+ node: ViewRef<TName>;
381
+ }>;
366
382
  pagination?: Pagination;
367
383
  }>;
368
- declare class Store {
369
- private coverage;
370
- private lists;
371
- private records;
372
- private subscriptions;
373
- private listSubscriptions;
374
- read(id: EntityId): AnyRecord | undefined;
375
- merge(id: EntityId, partial: AnyRecord, paths: Iterable<string>): void;
376
- private mergeInternal;
377
- deleteRecord(id: EntityId): void;
378
- missingForSelection(id: EntityId, paths: Iterable<string>): Set<string>;
379
- subscribe(id: EntityId, selection: ReadonlySet<string> | null, fn: () => void): () => void;
380
- subscribe(id: EntityId, fn: () => void): () => void;
381
- private notify;
382
- private notifyListSubscribers;
383
- getList(key: string): ReadonlyArray<EntityId> | undefined;
384
- getListState(key: string): List | undefined;
385
- getListsForField(ownerId: EntityId, field: string): Array<readonly [string, List]>;
386
- setList(key: string, state: List): void;
387
- restoreList(key: string, list?: List): void;
388
- subscribeList(key: string, fn: () => void): () => void;
389
- removeReferencesTo(targetId: EntityId, viewDataCache: ViewDataCache, snapshots?: Map<EntityId, Snapshot>, listSnapshots?: Map<string, List>): void;
390
- snapshot(id: EntityId): Snapshot;
391
- restore(id: EntityId, snapshot: Snapshot): void;
392
- }
393
- //#endregion
394
- //#region src/transport.d.ts
395
- /**
396
- * Normalized representation of args passed to a transport.
397
- */
398
- type ResolvedArgsPayload = AnyRecord;
399
- type TransportMutations = Record<string, MutationShape>;
400
- type EmptyTransportMutations = Record<never, MutationShape>;
401
- /**
402
- * Contract the fate client expects from a network transport. The transport is
403
- * responsible for fetching records by ID, fetching lists, and executing
404
- * mutations with the provided selections.
405
- */
406
- interface Transport<Mutations extends TransportMutations = EmptyTransportMutations> {
407
- fetchById(type: string, ids: Array<string | number>, select: Iterable<string>, args?: ResolvedArgsPayload): Promise<Array<unknown>>;
408
- fetchList?(proc: string, select: Iterable<string>, args?: ResolvedArgsPayload): Promise<{
409
- items: Array<{
410
- cursor: string | undefined;
411
- node: unknown;
412
- }>;
413
- pagination: Pagination;
384
+ /** Base shape shared by all entities fetched by fate. */
385
+ type Entity = {
386
+ __typename: string;
387
+ };
388
+ type PlainObjectSelectionField<V> = V extends Array<infer U> ? PlainObjectSelectionField<U> | true : V extends AnyRecord ? PlainObjectSelection<V> | true : true;
389
+ type PlainObjectSelection<T$1> = { [K in keyof T$1]?: PlainObjectSelectionField<T$1[K]> };
390
+ type ConnectionSelectionBase<T$1 extends Entity> = Readonly<{
391
+ items: Readonly<{
392
+ cursor?: true;
393
+ node: Selection<T$1> | View<T$1, Selection<T$1>>;
414
394
  }>;
415
- mutate?<K$1 extends Extract<keyof Mutations, string>>(proc: K$1, input: Mutations[K$1]['input'], select: Set<string>): Promise<Mutations[K$1]['output']>;
416
- }
395
+ pagination?: Readonly<{
396
+ hasNext?: true;
397
+ hasPrevious?: true;
398
+ nextCursor?: true;
399
+ previousCursor?: true;
400
+ }>;
401
+ }>;
402
+ /** Selection shape for a connection-style list, including pagination metadata and optional arguments. */
403
+ type ConnectionSelection<T$1 extends Entity> = ConnectionSelectionBase<T$1> | (SelectionArgs & ConnectionSelectionBase<T$1>);
404
+ type BaseSelectionFieldValue<T$1 extends Entity, K$1 extends keyof T$1> = NonNullable<T$1[K$1]> extends Array<infer U extends Entity> ? true | Selection<U> | ConnectionSelection<U> | View<U, Selection<U>> : NonNullable<T$1[K$1]> extends Entity ? true | Selection<NonNullable<T$1[K$1]>> | View<NonNullable<T$1[K$1]>, Selection<NonNullable<T$1[K$1]>>> : NonNullable<T$1[K$1]> extends AnyRecord ? PlainObjectSelection<NonNullable<T$1[K$1]>> : true;
405
+ type SelectionFieldValue<T$1 extends Entity, K$1 extends keyof T$1> = BaseSelectionFieldValue<T$1, K$1> | SelectionArgs | (SelectionArgs & Extract<BaseSelectionFieldValue<T$1, K$1>, object>);
406
+ type SelectionShape<T$1 extends Entity> = { [K in keyof T$1 as K extends '__typename' ? never : K]?: SelectionFieldValue<T$1, K> } & {
407
+ __typename?: true;
408
+ };
409
+ type SelectionViewSpread<T$1 extends Entity> = { readonly [K in ViewTag]?: Readonly<{
410
+ select: Selection<T$1>;
411
+ [ViewKind]: true;
412
+ }> };
413
+ /** Declarative selection of the fields a view needs from an entity. */
414
+ type Selection<T$1 extends Entity> = SelectionShape<T$1> & SelectionViewSpread<T$1>;
415
+ /** View payload stored on a view tag containing the raw selection used to mask data. */
416
+ type ViewPayload<T$1 extends Entity, S extends Selection<T$1> = Selection<T$1>> = Readonly<{
417
+ select: S;
418
+ [ViewKind]: true;
419
+ }>;
420
+ /** Definition of a view over an entity type, including the selection of fields. */
421
+ type View<T$1 extends Entity, S extends Selection<T$1> = Selection<T$1>> = Readonly<{
422
+ [viewTag: ViewTag]: ViewPayload<T$1, S>;
423
+ }> & __ViewEntityAnchor<T$1> & __ViewSelectionAnchor<S>;
424
+ type HasViewTag<S> = S extends { [K in ViewTag]?: infer P } ? P extends {
425
+ [ViewKind]: true;
426
+ } ? true : false : false;
417
427
  /**
418
- * Mapping of entity type to tRPC procedures used for fetching entities by ID.
428
+ * Data returned from a resolved view with masking applied and view tags
429
+ * attached for downstream composition.
419
430
  */
420
- type TRPCByIdResolvers<AppRouter extends AnyRouter> = Record<string, (client: TRPCClient<AppRouter>) => (input: {
421
- args?: ResolvedArgsPayload;
422
- ids: Array<string | number>;
423
- select: Array<string>;
424
- }) => Promise<Array<unknown>>>;
431
+ type ViewData<T$1 extends Entity, S extends Selection<T$1>> = Readonly<(S extends Selection<T$1> ? Mask<T$1, S> : T$1) & {
432
+ [ViewsTag]: Set<string>;
433
+ }>;
425
434
  /**
426
- * Mapping of list procedure name to a tRPC resolver factory.
435
+ * Snapshot returned by the cache for a view, including the masked data and all
436
+ * referenced entity IDs.
427
437
  */
428
- type TRPCListResolvers<AppRouter extends AnyRouter> = Record<string, (client: TRPCClient<AppRouter>) => (input: {
429
- args?: ResolvedArgsPayload;
430
- select: Array<string>;
431
- }) => Promise<{
432
- items: Array<{
433
- cursor: string | undefined;
438
+ type ViewSnapshot<T$1 extends Entity, S extends Selection<T$1>> = Readonly<{
439
+ coverage: ReadonlyArray<readonly [id: EntityId, paths: ReadonlySet<string>]>;
440
+ data: ViewData<T$1, S>;
441
+ }>;
442
+ type ConnectionMask<T$1 extends Entity, S> = S extends {
443
+ items: infer ItemSelection;
444
+ pagination?: infer PaginationSelection;
445
+ } ? {
446
+ items: ItemSelection extends {
447
+ cursor?: infer CursorSelection;
434
448
  node: unknown;
449
+ } ? Array<(CursorSelection extends true ? {
450
+ cursor: string;
451
+ } : Record<string, never>) & {
452
+ node: ViewRef<T$1['__typename']>;
453
+ }> : Array<{
454
+ node: ViewRef<T$1['__typename']>;
435
455
  }>;
436
- pagination: Pagination;
437
- }>>;
438
- /**
439
- * Mapping of a mutation procedure name to a tRPC resolver factory.
440
- */
441
- type MutationResolver<AppRouter extends AnyRouter> = (client: TRPCClient<AppRouter>) => (input: any) => Promise<any>;
442
- type TRPCMutationResolvers<AppRouter extends AnyRouter> = Record<string, MutationResolver<AppRouter>>;
443
- type MutationMapFromResolvers<R$1 extends Record<string, MutationResolver<any>>> = { [K in keyof R$1]: R$1[K] extends ((client: any) => (input: infer Input) => Promise<infer Output>) ? {
444
- input: Input;
445
- output: Output;
446
- } : never };
447
- type EmptyMutationResolvers<AppRouter extends AnyRouter> = Record<never, MutationResolver<AppRouter>>;
448
- /**
449
- * Builds a `Transport` backed by a tRPC client using the configured resolvers
450
- * for by-id queries, lists, and mutations.
451
- */
452
- declare function createTRPCTransport<AppRouter extends AnyRouter, Mutations extends TRPCMutationResolvers<AppRouter> = EmptyMutationResolvers<AppRouter>>({
453
- byId,
454
- client,
455
- lists,
456
- mutations
457
- }: {
458
- byId: TRPCByIdResolvers<AppRouter>;
459
- client: TRPCClient<AppRouter>;
460
- lists?: TRPCListResolvers<AppRouter>;
461
- mutations?: Mutations;
462
- }): Transport<MutationMapFromResolvers<Mutations>>;
463
- //#endregion
464
- //#region src/client.d.ts
456
+ } & (PaginationSelection extends object ? {
457
+ pagination: { [K in keyof PaginationSelection]: Pagination[K & keyof Pagination] };
458
+ } : Record<string, never>) : Array<T$1>;
459
+ type EntityName<T$1> = T$1 extends {
460
+ __typename: infer N extends string;
461
+ } ? N : never;
462
+ /** Recursively applies a view selection to an entity to mask fields that aren't selected. */
463
+ type Mask<T$1, S> = T$1 extends Array<infer U extends Entity> ? S extends true ? Array<U> : S extends ConnectionSelection<U> ? ConnectionMask<U, S> : HasViewTag<S> extends true ? Array<ViewRef<U['__typename']>> : Array<Mask<U, S>> : S extends true ? T$1 : S extends object ? HasViewTag<S> extends true ? ViewRef<EntityName<NonNullable<T$1>>> : { [K in keyof S as K extends 'args' ? never : K]: S[K] extends true ? NonNullable<T$1>[Extract<K, keyof T$1>] : Mask<NonNullable<T$1>[Extract<K, keyof T$1>], Extract<S[K], object>> } & (T$1 extends Entity ? Pick<NonNullable<T$1>, '__typename'> : Record<never, never>) : T$1;
464
+ /** Entity type captured from a view definition. */
465
+ type ViewEntity<V> = V extends View<infer T, any> ? T : never;
466
+ /** Name of the entity type captured from a view definition. */
467
+ type ViewEntityName<V> = ViewEntity<V>['__typename'] & string;
468
+ /** Selection captured from a view definition. */
469
+ type ViewSelection<V> = V extends {
470
+ readonly [__FateSelectionBrand]?: infer S;
471
+ } ? S : never;
472
+ /** Definition of a list request for fetching data from the backend. */
473
+ type ListItem<V extends View<any, any>> = Readonly<{
474
+ args?: Record<string, unknown>;
475
+ root: V;
476
+ type: ViewEntityName<V>;
477
+ }>;
478
+ /** Definition of a node request with one explicit ID for fetching data from the backend. */
479
+ type NodeItem<V extends View<any, any>> = Readonly<{
480
+ id: string | number;
481
+ root: V;
482
+ type: ViewEntityName<V>;
483
+ }>;
484
+ /** Definition of a node request with explicit IDs for fetching data from the backend. */
485
+ type NodesItem<V extends View<any, any>> = Readonly<{
486
+ ids: ReadonlyArray<string | number>;
487
+ root: V;
488
+ type: ViewEntityName<V>;
489
+ }>;
490
+ type RequestItem = ListItem<View<any, any>> | NodeItem<View<any, any>> | NodesItem<View<any, any>>;
491
+ /** Collection of node and list requests describing the data a screen needs. */
492
+ type Request = Record<string, RequestItem>;
493
+ type AnyView = View<any, any>;
494
+ type AnyListItem = ListItem<AnyView>;
495
+ type AnyNodeItem = NodeItem<AnyView>;
496
+ type AnyNodesItem = NodesItem<AnyView>;
497
+ type AnyRequestItem = AnyListItem | AnyNodeItem | AnyNodesItem;
498
+ type AnyRequest = Record<string, AnyRequestItem>;
465
499
  /**
466
- * Strategy used when resolving a request.
500
+ * Typed result returned by `useRequest` and `FateClient.request`, mapping each
501
+ * request key to an array of view refs for the requested type.
467
502
  */
468
- type RequestMode = /** (default) Use cached data if present, otherwise fetch. */
469
- 'cache-first'
470
- /** Show cached data immediately and refresh in the background. */ | 'stale-while-revalidate'
471
- /** Always fetch from the network and ignore cached entries. */ | 'network-only';
503
+ type ConnectionNodeType<Root> = Root extends {
504
+ items?: {
505
+ node?: infer Node;
506
+ };
507
+ } ? ViewEntityName<Node & View<any, any>> : never;
508
+ type ListResult<Item extends AnyRequestItem> = Item extends AnyNodeItem ? ViewRef<Item['type']> : Item extends AnyNodesItem ? Array<ViewRef<Item['type']>> : Item extends AnyListItem ? Item['root'] extends {
509
+ items?: {
510
+ node?: View<any, any>;
511
+ };
512
+ } ? Readonly<{
513
+ items: ReadonlyArray<{
514
+ cursor?: string | undefined;
515
+ node: ViewRef<ConnectionNodeType<Item['root']>>;
516
+ }>;
517
+ pagination?: Pagination;
518
+ }> : Array<ViewRef<Item['type']>> : never;
472
519
  /**
473
- * Request options that affect how requests are fetched and retained.
520
+ * The result of a `FateClient.request` and `useRequest` call, mapping each
521
+ * request key to its corresponding result.
474
522
  */
475
- type RequestOptions = Readonly<{
476
- mode?: RequestMode;
523
+ type RequestResult<Q extends AnyRequest> = { [K in keyof Q]: ListResult<Q[K]> };
524
+ /** Brand used on mutation definitions to mark their identity in the d.ts output. */
525
+ declare const MutationKind = "__fate__mutation";
526
+ /** Metadata describing a mutation for a particular entity, input, and output. */
527
+ type MutationDefinition<T$1 extends Entity, I$1, R$1> = Readonly<{
528
+ entity: T$1['__typename'];
529
+ [MutationKind]: true;
530
+ }> & __MutationEntityAnchor<T$1> & __MutationInputAnchor<I$1> & __MutationResultAnchor<R$1>;
531
+ type MutationIdentifier<T$1 extends Entity, I$1, R$1> = MutationDefinition<T$1, I$1, R$1> & Readonly<{
532
+ key: string;
477
533
  }>;
478
- type MutationIdentifierFor<K$1 extends string, Def extends MutationDefinition<any, any, any>> = Def extends MutationDefinition<infer T, infer I, infer R> ? MutationIdentifier<T, I, R> & Readonly<{
479
- key: K$1;
480
- }> : never;
481
- type MutationTransport<Mutations extends Record<string, MutationDefinition<any, any, any>>> = MutationMapFromDefinitions<Mutations>;
482
- type UnionToIntersection<U$1> = (U$1 extends any ? (k: U$1) => void : never) extends ((k: infer I) => void) ? I : never;
483
- type NestedValue<Path extends string, Value> = Path extends `${infer Head}.${infer Tail}` ? { [K in Head]: NestedValue<Tail, Value> } : { [K in Path]: Value };
484
- type MutationTreeFromRecord<Mutations extends Record<string, MutationDefinition<any, any, any>>, ValueMap extends Record<string, unknown>> = [keyof Mutations] extends [never] ? object : UnionToIntersection<{ [K in keyof Mutations & string]: NestedValue<K, ValueMap[K]> }[keyof Mutations & string]>;
485
- type MutationFunctionsFor<Mutations extends Record<string, MutationDefinition<any, any, any>>> = MutationTreeFromRecord<Mutations, { [K in keyof Mutations & string]: MutationFunction<MutationIdentifierFor<K, Mutations[K]>> }>;
486
- type MutationActionsFor<Mutations extends Record<string, MutationDefinition<any, any, any>>> = MutationTreeFromRecord<Mutations, { [K in keyof Mutations & string]: MutationAction<MutationIdentifierFor<K, Mutations[K]>> }>;
487
- type EmptyMutations = Record<never, MutationDefinition<any, any, any>>;
488
- type FateClientOptions<Mutations extends Record<string, MutationDefinition<any, any, any>> = EmptyMutations> = {
489
- mutations?: Mutations;
490
- transport: Transport<MutationTransport<Mutations>>;
491
- types: ReadonlyArray<Omit<TypeConfig, 'getId'> & Partial<{
492
- getId: TypeConfig['getId'];
493
- }>>;
534
+ /** Extracts the input type from a mutation definition or identifier. */
535
+ type MutationInput<M> = M extends __MutationInputAnchor<infer I> ? I : never;
536
+ /** Extracts the result type from a mutation definition or identifier. */
537
+ type MutationResult<M> = M extends __MutationResultAnchor<infer R> ? R : never;
538
+ /** Extracts the entity type from a mutation definition or identifier. */
539
+ type MutationEntity<M> = M extends __MutationEntityAnchor<infer E> ? E : never;
540
+ /** Minimal mutation description used for transport typing. */
541
+ type MutationShape = {
542
+ input: unknown;
543
+ output: unknown;
494
544
  };
495
545
  /**
496
- * Core client that normalizes records, manages the view cache, and coordinates
497
- * data fetching.
546
+ * Convenience helper that maps mutation definitions to their input/output
547
+ * shapes for use by transports.
498
548
  */
499
- declare class FateClient<Mutations extends Record<string, MutationDefinition<any, any, any>> = EmptyMutations> {
500
- private readonly mutationMap;
501
- private readonly parentLists;
502
- private readonly pending;
503
- private readonly optimisticMasks;
504
- private readonly optimisticByEntity;
505
- private optimisticTokenCounter;
506
- private readonly requests;
507
- private readonly stalledRequests;
508
- readonly store: Store;
509
- private readonly types;
510
- private readonly transport;
511
- private readonly viewDataCache;
512
- readonly mutations: MutationFunctionsFor<Mutations>;
513
- readonly actions: MutationActionsFor<Mutations>;
514
- constructor(options: FateClientOptions<Mutations>);
515
- private initializeParentLists;
516
- getTypeConfig(type: string): TypeConfig;
517
- executeMutation(key: string, input: unknown, select: Set<string>, options?: {
518
- args?: AnyRecord;
519
- plan?: SelectionPlan;
520
- }): Promise<unknown>;
521
- write(type: string, data: AnyRecord, select: ReadonlySet<string>, snapshots?: Map<EntityId, Snapshot>, plan?: SelectionPlan, pathPrefix?: string | null, blockedMask?: FieldMask | null): string;
522
- deleteRecord(type: string, id: string | number, snapshots?: Map<EntityId, Snapshot>, listSnapshots?: Map<string, List>): void;
523
- restore(id: EntityId, snapshot: Snapshot): void;
524
- restoreList(name: string, list?: List): void;
525
- ref<T$1 extends Entity>(type: T$1['__typename'], id: string | number, view: View<T$1, Selection<T$1>>): ViewRef<T$1['__typename']>;
526
- rootListRef(entityId: EntityId, rootView: View<any, any>): Readonly<{
527
- __typename: string;
528
- id: string | number;
529
- [ViewsTag]: Set<string>;
530
- }>;
531
- readView<T$1 extends Entity, S extends Selection<T$1>, V extends View<T$1, S>>(view: V, ref: ViewRef<T$1['__typename']>): FateThenable<ViewSnapshot<T$1, S>>;
532
- private mergeListState;
533
- registerOptimisticUpdate(entityId: EntityId | null, select: ReadonlySet<string>): number | null;
534
- clearOptimisticUpdate(token: number | null): void;
535
- getPendingOptimisticMask(entityId: EntityId | null, options?: {
536
- excludeToken?: number | null;
537
- }): FieldMask | null;
538
- filterSelectionForPendingOptimistics(entityId: EntityId | null, select: Set<string>, options?: {
539
- excludeToken?: number | null;
540
- }): Set<string>;
541
- loadConnection<V extends View<any, any>>(view: V, connection: ConnectionMetadata, args: Record<string, unknown>, options?: {
542
- direction?: 'forward' | 'backward';
543
- }): Promise<Readonly<{
544
- cursors?: ReadonlyArray<string | undefined>;
545
- ids: ReadonlyArray<EntityId>;
546
- pagination?: Pagination;
547
- }> | undefined>;
548
- request<R$1 extends Request>(request: R$1, options?: RequestOptions): Promise<RequestResult<R$1>>;
549
- releaseRequest(request: Request, mode: RequestMode): void;
550
- private handleStoreAndNetworkRequest;
551
- private executeRequest;
552
- private hasRequestData;
553
- getRequestResult<R$1 extends Request>(request: R$1): RequestResult<R$1>;
554
- private fetchByIdAndNormalize;
555
- private fetchListAndNormalize;
556
- private resolveListSelection;
557
- private writeEntity;
558
- private linkParentLists;
559
- private readViewSelection;
560
- private clearStalledRequestsForEntity;
561
- private pendingPrefix;
562
- private pendingKey;
549
+ type MutationMapFromDefinitions<D extends FateMutations> = { [K in keyof D]: {
550
+ input: MutationInput<D[K]>;
551
+ output: MutationResult<D[K]>;
552
+ } };
553
+ type Nullish<T$1> = Extract<T$1, null | undefined>;
554
+ type NonNullish<T$1> = Exclude<T$1, null | undefined>;
555
+ type OptimisticUpdateValue<T$1> = T$1 extends ReadonlyArray<infer U> ? Array<OptimisticUpdateValue<U>> : NonNullish<T$1> extends AnyRecord ? OptimisticUpdate<NonNullish<T$1>> | Nullish<T$1> : NonNullish<T$1> | Nullish<T$1>;
556
+ /** Shape used to describe optimistic updates for mutations. */
557
+ type OptimisticUpdate<T$1> = { [K in keyof T$1]?: OptimisticUpdateValue<T$1[K]> };
558
+ /** Snapshot captured before mutating the cache, used to roll back on errors. */
559
+ type Snapshot = Readonly<{
560
+ mask?: FieldMask;
561
+ record?: AnyRecord;
562
+ }>;
563
+ /** Promise-like value returned by cache reads that already have a resolved payload for React `use`. */
564
+ interface FateThenable<T$1> extends PromiseLike<T$1> {
565
+ status: 'fulfilled';
566
+ value: T$1;
563
567
  }
564
- declare function createClient<Mutations extends Record<string, MutationDefinition<any, any, any>> = EmptyMutations>(options: FateClientOptions<Mutations>): FateClient<Mutations>;
565
568
  //#endregion
566
569
  //#region src/ref.d.ts
567
570
  /**
@@ -582,4 +585,4 @@ type SelectionValidation<T$1 extends Entity, S extends Selection<T$1>> = Exclude
582
585
  */
583
586
  declare function view<T$1 extends Entity>(): <S extends Selection<T$1>>(select: S & SelectionValidation<T$1, S>) => View<T$1, S>;
584
587
  //#endregion
585
- export { type ConnectionMetadata, type ConnectionRef, ConnectionTag, type Entity, type EntityId, FateClient, type AnyRecord as FateRecord, type ListItem, type Mask, type MutationDefinition, type MutationEntity, type MutationIdentifier, type MutationInput, type MutationResult, type NodesItem, type Pagination, type Request, type RequestMode, type RequestOptions, type RequestResult, type Selection, type Snapshot, type Transport, type TypeConfig, type View, type ViewData, type ViewEntity, type ViewEntityName, type ViewRef, type ViewSelection, type ViewSnapshot, type ViewTag, createClient, createTRPCTransport, getSelectionPlan, isViewTag, mutation, toEntityId, view };
588
+ export { type ConnectionMetadata, type ConnectionRef, ConnectionTag, type Entity, type EntityId, FateClient, type FateMutations, type AnyRecord as FateRecord, type ListItem, type Mask, type MutationDefinition, type MutationEntity, type MutationIdentifier, type MutationInput, type MutationResult, type NodesItem, type Pagination, type Request, type RequestMode, type RequestOptions, type RequestResult, type Selection, type Snapshot, type Transport, type TypeConfig, type View, type ViewData, type ViewEntity, type ViewEntityName, type ViewRef, type ViewSelection, type ViewSnapshot, type ViewTag, createClient, createTRPCTransport, getSelectionPlan, isViewTag, mutation, toEntityId, view };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nkzw/fate",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "description": "fate is a modern data client for React.",
5
5
  "homepage": "https://github.com/nkzw-tech/fate",
6
6
  "repository": {