@nkzw/fate 0.1.3 → 1.0.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.
Files changed (170) hide show
  1. package/README.md +810 -129
  2. package/docs/api/classes/FateClient.md +832 -0
  3. package/docs/api/functions/clientRoot.md +27 -0
  4. package/docs/api/functions/createClient.md +21 -0
  5. package/docs/api/functions/createHTTPTransport.md +51 -0
  6. package/docs/api/functions/createTRPCTransport.md +46 -0
  7. package/docs/api/functions/getListEntries.md +15 -0
  8. package/docs/api/functions/getSelectionPlan.md +36 -0
  9. package/docs/api/functions/isRecord.md +15 -0
  10. package/docs/api/functions/isViewTag.md +17 -0
  11. package/docs/api/functions/liveConnectionTopic.md +19 -0
  12. package/docs/api/functions/liveEntityTopic.md +19 -0
  13. package/docs/api/functions/liveGlobalConnectionTopic.md +15 -0
  14. package/docs/api/functions/mutation.md +32 -0
  15. package/docs/api/functions/toEntityId.md +21 -0
  16. package/docs/api/functions/view.md +26 -0
  17. package/docs/api/index.md +81 -0
  18. package/docs/api/interfaces/FateThenable.md +31 -0
  19. package/docs/api/interfaces/Transport.md +199 -0
  20. package/docs/api/server/classes/FateRequestError.md +67 -0
  21. package/docs/api/server/drizzle/functions/createDrizzleFate.md +431 -0
  22. package/docs/api/server/drizzle/functions/createDrizzleSourceAdapter.md +21 -0
  23. package/docs/api/server/drizzle/functions/createDrizzleSourceRegistry.md +21 -0
  24. package/docs/api/server/drizzle/index.md +23 -0
  25. package/docs/api/server/drizzle/type-aliases/DrizzleManyToManyConfig.md +15 -0
  26. package/docs/api/server/drizzle/type-aliases/DrizzleManyToManyInput.md +5 -0
  27. package/docs/api/server/drizzle/type-aliases/DrizzleQueryExtra.md +21 -0
  28. package/docs/api/server/drizzle/type-aliases/DrizzleSourceAdapter.md +337 -0
  29. package/docs/api/server/drizzle/type-aliases/DrizzleViewConfig.md +29 -0
  30. package/docs/api/server/functions/bindSourceProcedures.md +32 -0
  31. package/docs/api/server/functions/computed.md +43 -0
  32. package/docs/api/server/functions/count.md +21 -0
  33. package/docs/api/server/functions/createFateFetchHandler.md +21 -0
  34. package/docs/api/server/functions/createFateServer.md +41 -0
  35. package/docs/api/server/functions/createHonoFateHandler.md +21 -0
  36. package/docs/api/server/functions/createLiveEventBus.md +17 -0
  37. package/docs/api/server/functions/createNestedSourcePlan.md +103 -0
  38. package/docs/api/server/functions/createResolver.md +74 -0
  39. package/docs/api/server/functions/createSourcePlan.md +95 -0
  40. package/docs/api/server/functions/createSourceProcedures.md +46 -0
  41. package/docs/api/server/functions/dataView.md +32 -0
  42. package/docs/api/server/functions/field.md +15 -0
  43. package/docs/api/server/functions/getNestedSelection.md +19 -0
  44. package/docs/api/server/functions/getScopedArgs.md +21 -0
  45. package/docs/api/server/functions/hasNestedSelection.md +19 -0
  46. package/docs/api/server/functions/list.md +28 -0
  47. package/docs/api/server/functions/refetchSourceById.md +55 -0
  48. package/docs/api/server/functions/resolveSourceById.md +55 -0
  49. package/docs/api/server/functions/resolveSourceByIds.md +55 -0
  50. package/docs/api/server/functions/resolveSourceConnection.md +67 -0
  51. package/docs/api/server/functions/resolver.md +42 -0
  52. package/docs/api/server/functions/toPrismaSelect.md +25 -0
  53. package/docs/api/server/functions/withConnection.md +24 -0
  54. package/docs/api/server/index.md +109 -0
  55. package/docs/api/server/prisma/functions/createPrismaFate.md +435 -0
  56. package/docs/api/server/prisma/functions/createPrismaSourceAdapter.md +21 -0
  57. package/docs/api/server/prisma/functions/createPrismaSourceRegistry.md +21 -0
  58. package/docs/api/server/prisma/functions/prismaConnectionArgs.md +57 -0
  59. package/docs/api/server/prisma/index.md +23 -0
  60. package/docs/api/server/prisma/type-aliases/PrismaDelegate.md +59 -0
  61. package/docs/api/server/prisma/type-aliases/PrismaQueryExtra.md +5 -0
  62. package/docs/api/server/prisma/type-aliases/PrismaSourceAdapter.md +343 -0
  63. package/docs/api/server/prisma/type-aliases/PrismaViewConfig.md +31 -0
  64. package/docs/api/server/type-aliases/ComputedField.md +55 -0
  65. package/docs/api/server/type-aliases/ComputedSelection.md +5 -0
  66. package/docs/api/server/type-aliases/ConnectionItem.md +29 -0
  67. package/docs/api/server/type-aliases/ConnectionPagination.md +39 -0
  68. package/docs/api/server/type-aliases/ConnectionResult.md +29 -0
  69. package/docs/api/server/type-aliases/CountSelection.md +29 -0
  70. package/docs/api/server/type-aliases/CountWhere.md +5 -0
  71. package/docs/api/server/type-aliases/DataViewListOptions.md +13 -0
  72. package/docs/api/server/type-aliases/DataViewOrderBy.md +5 -0
  73. package/docs/api/server/type-aliases/DataViewOrderDirection.md +5 -0
  74. package/docs/api/server/type-aliases/DataViewResult.md +11 -0
  75. package/docs/api/server/type-aliases/Entity.md +21 -0
  76. package/docs/api/server/type-aliases/FateServer.md +75 -0
  77. package/docs/api/server/type-aliases/FateServerManifest.md +5 -0
  78. package/docs/api/server/type-aliases/FieldSelection.md +21 -0
  79. package/docs/api/server/type-aliases/LiveConnectionEventType.md +5 -0
  80. package/docs/api/server/type-aliases/LiveConnectionSourceEvent.md +5 -0
  81. package/docs/api/server/type-aliases/LiveEventBus.md +5 -0
  82. package/docs/api/server/type-aliases/LiveEventType.md +5 -0
  83. package/docs/api/server/type-aliases/LiveSourceEvent.md +5 -0
  84. package/docs/api/server/type-aliases/NativeFateAPI.md +47 -0
  85. package/docs/api/server/type-aliases/OrderDirection.md +5 -0
  86. package/docs/api/server/type-aliases/SourceConfig.md +55 -0
  87. package/docs/api/server/type-aliases/SourceDefinition.md +55 -0
  88. package/docs/api/server/type-aliases/SourceOrder.md +5 -0
  89. package/docs/api/server/type-aliases/SourceOrderField.md +21 -0
  90. package/docs/api/server/type-aliases/SourcePlan.md +29 -0
  91. package/docs/api/server/type-aliases/SourcePlanNode.md +29 -0
  92. package/docs/api/server/type-aliases/SourceRegistry.md +11 -0
  93. package/docs/api/server/type-aliases/SourceRelation.md +15 -0
  94. package/docs/api/server/type-aliases/SourceRelationConfig.md +23 -0
  95. package/docs/api/server/variables/byIdInput.md +5 -0
  96. package/docs/api/server/variables/connectionArgs.md +7 -0
  97. package/docs/api/type-aliases/ConnectionMetadata.md +7 -0
  98. package/docs/api/type-aliases/ConnectionRef.md +13 -0
  99. package/docs/api/type-aliases/Entity.md +15 -0
  100. package/docs/api/type-aliases/EntityId.md +7 -0
  101. package/docs/api/type-aliases/FateLiveConnectionEvent.md +5 -0
  102. package/docs/api/type-aliases/FateLiveEvent.md +5 -0
  103. package/docs/api/type-aliases/FateMutations.md +7 -0
  104. package/docs/api/type-aliases/FateOperation.md +5 -0
  105. package/docs/api/type-aliases/FateProtocolRequest.md +5 -0
  106. package/docs/api/type-aliases/FateProtocolResponse.md +5 -0
  107. package/docs/api/type-aliases/FateRecord.md +7 -0
  108. package/docs/api/type-aliases/FateRoots.md +7 -0
  109. package/docs/api/type-aliases/InferFateAPI.md +11 -0
  110. package/docs/api/type-aliases/List.md +5 -0
  111. package/docs/api/type-aliases/ListEntry.md +5 -0
  112. package/docs/api/type-aliases/ListItem.md +13 -0
  113. package/docs/api/type-aliases/Mask.md +15 -0
  114. package/docs/api/type-aliases/MutationDefinition.md +21 -0
  115. package/docs/api/type-aliases/MutationEntity.md +13 -0
  116. package/docs/api/type-aliases/MutationIdentifier.md +19 -0
  117. package/docs/api/type-aliases/MutationInput.md +13 -0
  118. package/docs/api/type-aliases/MutationResult.md +13 -0
  119. package/docs/api/type-aliases/NodesItem.md +13 -0
  120. package/docs/api/type-aliases/Pagination.md +39 -0
  121. package/docs/api/type-aliases/Request.md +7 -0
  122. package/docs/api/type-aliases/RequestMode.md +7 -0
  123. package/docs/api/type-aliases/RequestOptions.md +7 -0
  124. package/docs/api/type-aliases/RequestResult.md +18 -0
  125. package/docs/api/type-aliases/Selection.md +13 -0
  126. package/docs/api/type-aliases/Snapshot.md +7 -0
  127. package/docs/api/type-aliases/TypeConfig.md +41 -0
  128. package/docs/api/type-aliases/View.md +17 -0
  129. package/docs/api/type-aliases/ViewData.md +18 -0
  130. package/docs/api/type-aliases/ViewEntity.md +13 -0
  131. package/docs/api/type-aliases/ViewEntityName.md +13 -0
  132. package/docs/api/type-aliases/ViewRef.md +13 -0
  133. package/docs/api/type-aliases/ViewSelection.md +13 -0
  134. package/docs/api/type-aliases/ViewSnapshot.md +18 -0
  135. package/docs/api/type-aliases/ViewTag.md +7 -0
  136. package/docs/api/variables/ConnectionTag.md +7 -0
  137. package/docs/guide/actions.md +263 -0
  138. package/docs/guide/core-concepts.md +22 -0
  139. package/docs/guide/getting-started.md +57 -0
  140. package/docs/guide/list-views.md +86 -0
  141. package/docs/guide/live-views.md +245 -0
  142. package/docs/guide/requests.md +130 -0
  143. package/docs/guide/server-integration.md +630 -0
  144. package/docs/guide/views.md +278 -0
  145. package/docs/guide/void-integration.md +167 -0
  146. package/docs/index.md +4 -0
  147. package/lib/cli.mjs +17 -250
  148. package/lib/clientStub.d.mts +6 -0
  149. package/lib/clientStub.mjs +6 -0
  150. package/lib/executor-BqwHdN3n.d.mts +468 -0
  151. package/lib/index.d.mts +64 -2
  152. package/lib/index.mjs +1961 -555
  153. package/lib/list-4wRNSVgI.d.mts +10 -0
  154. package/lib/list-C_mi6GnE.mjs +16 -0
  155. package/lib/list.d.mts +2 -0
  156. package/lib/list.mjs +2 -0
  157. package/lib/liveTopics-DzNtJaBD.mjs +197 -0
  158. package/lib/{record-DnhZuvUe.mjs → record-AeZJC9fd.mjs} +1 -2
  159. package/lib/record-ENh92gyb.d.mts +315 -0
  160. package/lib/server/drizzle.d.mts +304 -0
  161. package/lib/server/drizzle.mjs +688 -0
  162. package/lib/server/prisma.d.mts +309 -0
  163. package/lib/server/prisma.mjs +345 -0
  164. package/lib/server.d.mts +82 -183
  165. package/lib/server.mjs +870 -416
  166. package/lib/sourceRouter-Bwpkr062.mjs +972 -0
  167. package/lib/{types-HECWvGpd.d.mts → types-Dz46PXr3.d.mts} +215 -63
  168. package/lib/vite.d.mts +17 -0
  169. package/lib/vite.mjs +593 -0
  170. package/package.json +44 -6
package/lib/vite.mjs ADDED
@@ -0,0 +1,593 @@
1
+ import path from "node:path";
2
+ import { runnerImport, transformWithOxc } from "vite";
3
+ import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
4
+ //#region src/codegen/schema.ts
5
+ const isDataView = (value) => Boolean(value) && typeof value === "object" && typeof value.typeName === "string" && Boolean(value.fields) && typeof value.fields === "object";
6
+ /**
7
+ * Builds the schema object used by the Vite plugin from your data views and
8
+ * root resolver configs.
9
+ */
10
+ const createSchema = (dataViews, roots) => {
11
+ const rootSchema = {};
12
+ const fateTypes = /* @__PURE__ */ new Map();
13
+ const processing = /* @__PURE__ */ new Set();
14
+ const ensureType = (view) => {
15
+ const typeName = view.typeName;
16
+ const existing = fateTypes.get(typeName);
17
+ if (processing.has(typeName)) return typeName;
18
+ processing.add(typeName);
19
+ const fields = existing?.fields ?? {};
20
+ for (const [field, child] of Object.entries(view.fields)) if (isDataView(child)) {
21
+ const relationType = ensureType(child);
22
+ fields[field] = child.kind === "list" ? { listOf: relationType } : { type: relationType };
23
+ }
24
+ const descriptor = { type: typeName };
25
+ if (Object.keys(fields).length) descriptor.fields = fields;
26
+ fateTypes.set(typeName, descriptor);
27
+ processing.delete(typeName);
28
+ return typeName;
29
+ };
30
+ for (const view of dataViews) if (!view.typeName) throw new Error("Data view is missing a type name.");
31
+ for (const view of dataViews) ensureType(view);
32
+ for (const [name, root] of Object.entries(roots)) {
33
+ const config = "fields" in root ? { view: root } : root;
34
+ const view = config.view;
35
+ if (!view.typeName) throw new Error(`Root "${name}" is missing a data view.`);
36
+ const type = ensureType(view);
37
+ const router = config.router ?? view.typeName[0]?.toLowerCase() + view.typeName.slice(1);
38
+ if (!router) throw new Error(`Root "${name}" is missing a router name.`);
39
+ rootSchema[name] = {
40
+ kind: view.kind === "list" ? "list" : "query",
41
+ procedure: config.procedure ?? (view.kind === "list" ? "list" : name),
42
+ router,
43
+ type
44
+ };
45
+ }
46
+ for (const view of dataViews) ensureType(view);
47
+ return {
48
+ roots: rootSchema,
49
+ types: Array.from(fateTypes.values())
50
+ };
51
+ };
52
+ //#endregion
53
+ //#region src/codegen/client.ts
54
+ const formatRelation = (value) => "listOf" in value ? `{ listOf: '${value.listOf}' }` : `{ type: '${value.type}' }`;
55
+ const formatTypes = (types) => {
56
+ if (!types.length) return "[]";
57
+ const lines = ["["];
58
+ for (const typeConfig of types) {
59
+ lines.push(" {");
60
+ if (typeConfig.fields) {
61
+ lines.push(" fields: {");
62
+ for (const [field, relation] of Object.entries(typeConfig.fields)) lines.push(` ${field}: ${formatRelation(relation)},`);
63
+ lines.push(" },");
64
+ }
65
+ lines.push(` type: '${typeConfig.type}',`, " },");
66
+ }
67
+ lines.push("]");
68
+ return lines.join("\n");
69
+ };
70
+ const indentBlock = (value, spaces) => value.split("\n").map((line) => line.length ? `${" ".repeat(spaces)}${line}` : line).join("\n");
71
+ const createClientSource = ({ clientModule = "react-fate", moduleExports, moduleName, runtimeModuleName = moduleName, transport = "trpc" }) => {
72
+ if (transport === "native" || transport === "void") return createNativeClientSource({
73
+ clientModule,
74
+ moduleExports,
75
+ moduleName,
76
+ runtimeModuleName,
77
+ voidTransport: transport === "void"
78
+ });
79
+ const { appRouter, Root } = moduleExports;
80
+ const { roots, types } = createSchema(Object.values(moduleExports).filter(isDataView), Root ?? {});
81
+ const routerRecord = appRouter._def?.record ?? {};
82
+ const mutationEntries = [];
83
+ const byIdEntries = [];
84
+ const listEntries = [];
85
+ const queryEntries = [];
86
+ const rootsByRouter = /* @__PURE__ */ new Map();
87
+ for (const entry of Object.entries(roots)) {
88
+ const list = rootsByRouter.get(entry[1].router) ?? [];
89
+ list.push(entry);
90
+ rootsByRouter.set(entry[1].router, list);
91
+ }
92
+ for (const [router, procedures] of Object.entries(routerRecord)) {
93
+ const routerRoots = rootsByRouter.get(router);
94
+ if (!routerRoots?.length) continue;
95
+ const entityType = routerRoots[0][1].type;
96
+ for (const [procedureName, procedure] of Object.entries(procedures)) {
97
+ const type = procedure?._def?.type;
98
+ if (!type) continue;
99
+ if (type === "mutation") {
100
+ mutationEntries.push({
101
+ entityType,
102
+ name: `${router}.${procedureName}`,
103
+ procedure: procedureName,
104
+ router
105
+ });
106
+ continue;
107
+ }
108
+ if (procedureName === "byId" && type === "query") {
109
+ byIdEntries.push({
110
+ entityType,
111
+ router
112
+ });
113
+ continue;
114
+ }
115
+ for (const [queryName, root] of routerRoots) {
116
+ if (root.kind !== "query") continue;
117
+ if (procedureName === root.procedure && type === "query") queryEntries.push({
118
+ name: queryName,
119
+ procedure: root.procedure,
120
+ router,
121
+ type: root.type
122
+ });
123
+ }
124
+ for (const [listName, root] of routerRoots) {
125
+ if (root.kind !== "list") continue;
126
+ if (procedureName === root.procedure && type === "query") listEntries.push({
127
+ list: listName,
128
+ procedure: root.procedure,
129
+ router,
130
+ type: root.type
131
+ });
132
+ }
133
+ }
134
+ }
135
+ mutationEntries.sort((a, b) => a.name.localeCompare(b.name));
136
+ byIdEntries.sort((a, b) => a.entityType.localeCompare(b.entityType));
137
+ queryEntries.sort((a, b) => a.name.localeCompare(b.name));
138
+ listEntries.sort((a, b) => a.list.localeCompare(b.list));
139
+ const rootEntries = [
140
+ ...byIdEntries.map(({ entityType, router }) => ({
141
+ name: router,
142
+ type: entityType,
143
+ value: `'${router}': clientRoot<RouterOutputs['${router}']['byId'], '${entityType}'>('${entityType}'),`
144
+ })),
145
+ ...queryEntries.map(({ name, procedure, router, type }) => ({
146
+ name,
147
+ type,
148
+ value: `'${name}': clientRoot<RouterOutputs['${router}']['${procedure}'], '${type}'>('${type}'),`
149
+ })),
150
+ ...listEntries.map(({ list, procedure, router, type }) => ({
151
+ name: list,
152
+ type,
153
+ value: `'${list}': clientRoot<RouterOutputs['${router}']['${procedure}'], '${type}'>('${type}'),`
154
+ }))
155
+ ];
156
+ rootEntries.sort((a, b) => a.name.localeCompare(b.name));
157
+ const viewTypes = Array.from(["AppRouter", ...new Set(mutationEntries.map((entry) => entry.entityType))]).sort();
158
+ const mutationResolverLines = mutationEntries.map(({ name, procedure, router }) => `'${name}': (client: TRPCClientType) => client.${router}.${procedure}.mutate,`);
159
+ const mutationConfigLines = mutationEntries.map(({ entityType, name, procedure, router }) => `'${name}': mutation<
160
+ ${entityType},
161
+ RouterInputs['${router}']['${procedure}'],
162
+ RouterOutputs['${router}']['${procedure}']
163
+ >('${entityType}'),`);
164
+ const byIdLines = byIdEntries.map(({ entityType, router }) => `${entityType}: (client: TRPCClientType) => ({
165
+ args,
166
+ ids,
167
+ select,
168
+ }: { args?: Record<string, unknown>; ids: Array<string | number>; select: Array<string> }) =>
169
+ client.${router}.byId.query({
170
+ args,
171
+ ids: ids.map(String),
172
+ select,
173
+ }),`);
174
+ const listLines = listEntries.map(({ list, procedure, router }) => `${list}: (client: TRPCClientType) => client.${router}.${procedure}.query,`);
175
+ const queryLines = queryEntries.map(({ name, procedure, router }) => `${name}: (client: TRPCClientType) => client.${router}.${procedure}.query,`);
176
+ const typeImports = `import type { ${viewTypes.join(", ")} } from '${moduleName}';`;
177
+ const typesBlock = indentBlock(formatTypes(types), 6);
178
+ const mutationResolverBlock = indentBlock(mutationResolverLines.join("\n"), 4);
179
+ const mutationConfigBlock = indentBlock(mutationConfigLines.join("\n"), 2);
180
+ const byIdBlock = indentBlock(byIdLines.join("\n"), 8);
181
+ const rootsBlock = indentBlock(rootEntries.map((entry) => entry.value).join("\n"), 2);
182
+ const listsBlockContent = listLines.join("\n");
183
+ const listsBlock = listLines.length ? ` lists: {\n${indentBlock(listsBlockContent, 8)}\n },\n` : "";
184
+ const queriesBlockContent = queryLines.join("\n");
185
+ const queriesBlock = queryLines.length ? ` queries: {\n${indentBlock(queriesBlockContent, 8)}\n },\n` : "";
186
+ const trpcFateServer = moduleExports.fateServer?.manifest ? moduleExports.fateServer : null;
187
+ const hasLive = Object.keys(trpcFateServer?.manifest.live ?? {}).length > 0;
188
+ const liveOptions = hasLive ? `fetch?: typeof fetch;
189
+ headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
190
+ liveRetryMs?: number;
191
+ liveUrl: string | URL;` : "";
192
+ const liveImport = hasLive ? ", createHTTPTransport" : "";
193
+ const liveTransportSetup = hasLive ? `
194
+ const liveTransport = createHTTPTransport({
195
+ fetch: options.fetch,
196
+ headers: options.headers,
197
+ liveRetryMs: options.liveRetryMs,
198
+ url: options.liveUrl,
199
+ });
200
+ ` : "";
201
+ const liveTransportAssignment = hasLive ? `
202
+ transport.subscribeById = liveTransport.subscribeById;
203
+ transport.subscribeConnection = liveTransport.subscribeConnection;
204
+ ` : "";
205
+ const generatedClientTypes = `
206
+ declare module '${`${clientModule}/client`}' {
207
+ interface ClientMutations extends GeneratedClientMutations {}
208
+ interface ClientRoots extends GeneratedClientRoots {}
209
+
210
+ export function createFateClient(
211
+ ...args: Parameters<GeneratedCreateFateClient>
212
+ ): ReturnType<GeneratedCreateFateClient>;
213
+ }
214
+ `;
215
+ return `// @generated by @nkzw/fate/vite
216
+ ${typeImports}
217
+ import { createTRPCProxyClient } from '@trpc/client';
218
+ import type { inferRouterInputs, inferRouterOutputs } from '@trpc/server';
219
+ import { clientRoot, createClient${liveImport}, createTRPCTransport, mutation } from '${clientModule}';
220
+
221
+ type TRPCClientType = ReturnType<typeof createTRPCProxyClient<AppRouter>>;
222
+ type RouterInputs = inferRouterInputs<AppRouter>;
223
+ type RouterOutputs = inferRouterOutputs<AppRouter>;
224
+
225
+ const mutations = {
226
+ ${mutationConfigBlock}
227
+ } as const;
228
+
229
+ const roots = {
230
+ ${rootsBlock}
231
+ } as const;
232
+
233
+ export type GeneratedClientMutations = typeof mutations;
234
+ export type GeneratedClientRoots = typeof roots;
235
+
236
+ export const createFateClient = (options: {
237
+ links: Parameters<typeof createTRPCProxyClient>[0]['links'];
238
+ ${indentBlock(liveOptions.trim(), 2)}
239
+ onLiveError?: (error: unknown) => void;
240
+ }) => {
241
+ const trpcClient = createTRPCProxyClient<AppRouter>(options);
242
+ ${liveTransportSetup}
243
+
244
+ const trpcMutations = {
245
+ ${mutationResolverBlock}
246
+ } as const;
247
+
248
+ const transport = createTRPCTransport<AppRouter, typeof trpcMutations>({
249
+ byId: {
250
+ ${byIdBlock}
251
+ },
252
+ client: trpcClient,
253
+ ${queriesBlock}${listsBlock} mutations: trpcMutations,
254
+ });
255
+ ${liveTransportAssignment}
256
+
257
+ return createClient<[GeneratedClientRoots, GeneratedClientMutations]>({
258
+ mutations,
259
+ onLiveError: options.onLiveError,
260
+ roots,
261
+ transport,
262
+ types: ${typesBlock.trimStart()},
263
+ });
264
+ };
265
+
266
+ type GeneratedCreateFateClient = typeof createFateClient;
267
+ ${generatedClientTypes}
268
+ `;
269
+ };
270
+ const lowerTypeName = (type) => type[0]?.toLowerCase() + type.slice(1);
271
+ const createNativeClientSource = ({ clientModule, moduleExports, moduleName, runtimeModuleName, voidTransport }) => {
272
+ const { Root } = moduleExports;
273
+ const fateExportName = moduleExports.fate?.manifest ? "fate" : moduleExports.fateServer?.manifest ? "fateServer" : null;
274
+ const fateServer = fateExportName ? moduleExports[fateExportName] : null;
275
+ if (!fateServer?.manifest) throw new Error(`Native fate client generation requires the server module to export a value named "fate" or "fateServer" created by createFateServer().`);
276
+ const { roots, types } = createSchema(Object.values(moduleExports).filter(isDataView), Root ?? {});
277
+ const manifest = fateServer.manifest;
278
+ const hasLive = Object.keys(manifest.live ?? {}).length > 0;
279
+ const byIdEntries = Object.keys(manifest.types ?? {}).map((type) => ({
280
+ name: lowerTypeName(type),
281
+ type
282
+ }));
283
+ const mutationEntries = Object.entries(manifest.mutations ?? {}).map(([name, config]) => ({
284
+ name,
285
+ type: config.type
286
+ }));
287
+ const rootEntries = [...byIdEntries.map(({ name, type }) => ({
288
+ name,
289
+ value: `'${name}': clientRoot<Array<${type}>, '${type}'>('${type}'),`
290
+ })), ...Object.entries(roots).map(([name, root]) => {
291
+ const apiKey = name;
292
+ return {
293
+ name,
294
+ value: `'${name}': clientRoot<FateAPI['${root.kind === "list" ? "lists" : "queries"}']['${apiKey}']['output'], '${root.type}'>('${root.type}'),`
295
+ };
296
+ })].sort((a, b) => a.name.localeCompare(b.name));
297
+ const importedTypes = Array.from(new Set([
298
+ fateExportName === "fate" ? "fate as fateServer" : "fateServer",
299
+ ...types.map((type) => type.type),
300
+ ...mutationEntries.map((entry) => entry.type)
301
+ ])).sort((a, b) => a.localeCompare(b));
302
+ const mutationConfigLines = mutationEntries.map(({ name, type }) => `'${name}': mutation<
303
+ ${type},
304
+ FateAPI['mutations']['${name}']['input'],
305
+ FateAPI['mutations']['${name}']['output']
306
+ >('${type}'),`);
307
+ const typesBlock = indentBlock(formatTypes(types), 6);
308
+ const rootBlock = indentBlock(rootEntries.map((entry) => entry.value).join("\n"), 2);
309
+ const mutationConfigBlock = indentBlock(mutationConfigLines.join("\n"), 2);
310
+ const clientDeclarationModule = `${clientModule}/client`;
311
+ const voidServerFetch = voidTransport ? `
312
+ import type { FateServer } from '@nkzw/fate/server';
313
+
314
+ const defaultVoidFateRpcPath = '/fate';
315
+ const defaultVoidFateLivePath = '/fate-live';
316
+
317
+ let serverFateFetchPromise: Promise<(request: Request) => Promise<Response>> | null = null;
318
+
319
+ const isFateServer = (value: unknown): value is FateServer<unknown> =>
320
+ Boolean(value && typeof value === 'object' && 'manifest' in value);
321
+
322
+ const getServerFateFetch = async () => {
323
+ serverFateFetchPromise ??= Promise.all([
324
+ import('@nkzw/fate/server'),
325
+ import('${runtimeModuleName}'),
326
+ ]).then(([{ createFateFetchHandler }, serverModule]) => {
327
+ const moduleRecord = serverModule as Record<string, unknown>;
328
+ const server = isFateServer(moduleRecord.fate)
329
+ ? moduleRecord.fate
330
+ : isFateServer(moduleRecord.fateServer)
331
+ ? moduleRecord.fateServer
332
+ : null;
333
+
334
+ if (!server) {
335
+ throw new Error('void-fate: Expected the server module to export a Fate server named "fate" or "fateServer".');
336
+ }
337
+
338
+ return createFateFetchHandler(server);
339
+ });
340
+
341
+ return serverFateFetchPromise;
342
+ };
343
+
344
+ const getDefaultOrigin = () =>
345
+ typeof window === 'undefined' ? 'http://localhost' : window.location.origin;
346
+
347
+ const toEndpointUrl = (url: string | URL | undefined, path: string, origin: string | URL) =>
348
+ url ?? new URL(path, origin);
349
+
350
+ const createVoidFetch = (options: {
351
+ fetch?: typeof fetch;
352
+ userId?: null | string;
353
+ }): typeof fetch => {
354
+ if (options.fetch) {
355
+ return options.fetch;
356
+ }
357
+
358
+ if (import.meta.env.SSR) {
359
+ return async (input, init) => (await getServerFateFetch())(new Request(input, init));
360
+ }
361
+
362
+ return (input, init) =>
363
+ fetch(input, {
364
+ ...init,
365
+ credentials: options.userId ? 'include' : init?.credentials,
366
+ });
367
+ };
368
+ ` : "";
369
+ const createClientOptions = voidTransport ? `options: {
370
+ fetch?: typeof fetch;
371
+ headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
372
+ livePath?: string;
373
+ liveRetryMs?: number;
374
+ liveUrl?: string | URL;
375
+ onLiveError?: (error: unknown) => void;
376
+ origin?: string | URL;
377
+ rpcPath?: string;
378
+ url?: string | URL;
379
+ userId?: null | string;
380
+ } = {}` : `options: {
381
+ fetch?: typeof fetch;
382
+ headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
383
+ liveRetryMs?: number;
384
+ liveUrl?: string | URL;
385
+ onLiveError?: (error: unknown) => void;
386
+ url: string | URL;
387
+ }`;
388
+ const clientSetup = voidTransport ? ` => {
389
+ const origin = options.origin ?? getDefaultOrigin();
390
+
391
+ return createClient<[GeneratedClientRoots, GeneratedClientMutations]>({
392
+ mutations,
393
+ onLiveError: options.onLiveError,
394
+ roots,
395
+ transport: createHTTPTransport<FateAPI>({
396
+ fetch: createVoidFetch(options),
397
+ headers: options.headers,
398
+ live: ${hasLive ? "connectLiveStream" : "false"},
399
+ liveRetryMs: options.liveRetryMs,
400
+ liveUrl: toEndpointUrl(
401
+ options.liveUrl,
402
+ options.livePath ?? defaultVoidFateLivePath,
403
+ origin,
404
+ ),
405
+ url: toEndpointUrl(options.url, options.rpcPath ?? defaultVoidFateRpcPath, origin),
406
+ }),
407
+ types: ${typesBlock.trimStart()},
408
+ });
409
+ }` : ` =>
410
+ createClient<[GeneratedClientRoots, GeneratedClientMutations]>({
411
+ mutations,
412
+ onLiveError: options.onLiveError,
413
+ roots,
414
+ transport: createHTTPTransport<FateAPI>({
415
+ fetch: options.fetch,
416
+ headers: options.headers,
417
+ live: ${String(hasLive)},
418
+ liveRetryMs: options.liveRetryMs,
419
+ liveUrl: options.liveUrl,
420
+ url: options.url,
421
+ }),
422
+ types: ${typesBlock.trimStart()},
423
+ })`;
424
+ const generatedClientTypes = `
425
+ declare module '${clientDeclarationModule}' {
426
+ interface ClientMutations extends GeneratedClientMutations {}
427
+ interface ClientRoots extends GeneratedClientRoots {}
428
+
429
+ export function createFateClient(
430
+ ...args: Parameters<GeneratedCreateFateClient>
431
+ ): ReturnType<GeneratedCreateFateClient>;
432
+ }
433
+ `;
434
+ return `// @generated by @nkzw/fate/vite
435
+ import type { ${importedTypes.join(", ")} } from '${moduleName}';
436
+ import { clientRoot, createClient, createHTTPTransport, mutation, type InferFateAPI } from '${clientModule}';
437
+ ${voidTransport && hasLive ? "import { connectLiveStream } from 'void/live/client';\n" : ""}${voidServerFetch}
438
+
439
+ type FateAPI = InferFateAPI<typeof fateServer>;
440
+
441
+ const mutations = {
442
+ ${mutationConfigBlock}
443
+ } as const;
444
+
445
+ const roots = {
446
+ ${rootBlock}
447
+ } as const;
448
+
449
+ export type GeneratedClientMutations = typeof mutations;
450
+ export type GeneratedClientRoots = typeof roots;
451
+
452
+ export const createFateClient = (${createClientOptions})${clientSetup};
453
+
454
+ type GeneratedCreateFateClient = typeof createFateClient;
455
+ ${generatedClientTypes}
456
+ `;
457
+ };
458
+ //#endregion
459
+ //#region src/vite.ts
460
+ const defaultClientRuntime = "@nkzw/fate";
461
+ const defaultClientModule = `${defaultClientRuntime}/client`;
462
+ const defaultGeneratedFile = ".fate/client.generated.ts";
463
+ const legacyDeclarationFile = ".fate/client.d.ts";
464
+ const defaultTsconfigFile = ".fate/tsconfig.json";
465
+ const resolvedClientModule = `\0${defaultClientModule}.ts`;
466
+ const toTsconfigPath = (fromFile, toFile) => {
467
+ const relativePath = path.relative(path.dirname(fromFile), toFile).replaceAll(path.sep, "/");
468
+ return relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
469
+ };
470
+ const toGeneratedModuleName = ({ fromFile, moduleName, root }) => {
471
+ if (!moduleName.startsWith(".") && !path.isAbsolute(moduleName)) return moduleName;
472
+ return toTsconfigPath(fromFile, path.isAbsolute(moduleName) ? moduleName : path.resolve(root, moduleName));
473
+ };
474
+ const toVirtualRuntimeModuleName = ({ moduleName, root }) => {
475
+ if (!moduleName.startsWith(".") && !path.isAbsolute(moduleName)) return moduleName;
476
+ const resolvedModule = path.isAbsolute(moduleName) ? moduleName : path.resolve(root, moduleName);
477
+ const rootRelativeModule = path.relative(root, resolvedModule).replaceAll(path.sep, "/");
478
+ return rootRelativeModule.startsWith("..") || path.isAbsolute(rootRelativeModule) ? `/@fs/${resolvedModule.replaceAll(path.sep, "/")}` : `/${rootRelativeModule}`;
479
+ };
480
+ const writeIfChanged = async (file, contents) => {
481
+ try {
482
+ if (await readFile(file, "utf8") === contents) return;
483
+ } catch {}
484
+ await mkdir(path.dirname(file), { recursive: true });
485
+ await writeFile(file, contents);
486
+ };
487
+ const removeIfExists = async (file) => {
488
+ try {
489
+ await rm(file);
490
+ } catch (error) {
491
+ if (error.code !== "ENOENT") throw error;
492
+ }
493
+ };
494
+ const fate = (options) => {
495
+ const clientModule = `${options.clientModule ?? defaultClientRuntime}/client`;
496
+ const clientModules = new Set([defaultClientModule, clientModule]);
497
+ let config;
498
+ let dependencies = /* @__PURE__ */ new Set();
499
+ let generatedSource = null;
500
+ let generationPromise = null;
501
+ let server = null;
502
+ const getGeneratedFile = () => options.generatedFile === false ? null : path.resolve(config.root, options.generatedFile ?? defaultGeneratedFile);
503
+ const getTsconfigFile = () => options.tsconfigFile === false ? null : path.resolve(config.root, options.tsconfigFile ?? defaultTsconfigFile);
504
+ const writeGeneratedFiles = async (moduleExports) => {
505
+ const generatedFile = getGeneratedFile();
506
+ if (!generatedFile) return;
507
+ await writeIfChanged(generatedFile, createClientSource({
508
+ clientModule: options.clientModule ?? defaultClientRuntime,
509
+ moduleExports,
510
+ moduleName: toGeneratedModuleName({
511
+ fromFile: generatedFile,
512
+ moduleName: options.module,
513
+ root: config.root
514
+ }),
515
+ transport: options.transport ?? "trpc"
516
+ }));
517
+ await removeIfExists(path.resolve(config.root, legacyDeclarationFile));
518
+ const tsconfigFile = getTsconfigFile();
519
+ if (!tsconfigFile) return;
520
+ await writeIfChanged(tsconfigFile, `${JSON.stringify({ files: [toTsconfigPath(tsconfigFile, generatedFile)] }, null, 2)}\n`);
521
+ };
522
+ const importServerModule = async () => {
523
+ const inlineConfig = {
524
+ configFile: false,
525
+ mode: config.mode,
526
+ resolve: {
527
+ alias: config.resolve.alias,
528
+ conditions: config.resolve.conditions
529
+ },
530
+ root: config.root
531
+ };
532
+ const result = await runnerImport(options.module, inlineConfig);
533
+ dependencies = new Set(result.dependencies.map((dependency) => path.normalize(dependency)));
534
+ return result.module;
535
+ };
536
+ const generate = async () => {
537
+ const moduleExports = await importServerModule();
538
+ const source = createClientSource({
539
+ clientModule: options.clientModule ?? defaultClientRuntime,
540
+ moduleExports,
541
+ moduleName: options.module,
542
+ runtimeModuleName: toVirtualRuntimeModuleName({
543
+ moduleName: options.module,
544
+ root: config.root
545
+ }),
546
+ transport: options.transport ?? "trpc"
547
+ });
548
+ generatedSource = source;
549
+ await writeGeneratedFiles(moduleExports);
550
+ server?.watcher.add([...dependencies]);
551
+ return source;
552
+ };
553
+ const ensureGenerated = () => {
554
+ generationPromise ??= generate().finally(() => {
555
+ generationPromise = null;
556
+ });
557
+ return generationPromise;
558
+ };
559
+ return {
560
+ buildStart: async function() {
561
+ await ensureGenerated();
562
+ const resolvedServerModule = await this.resolve(options.module);
563
+ if (resolvedServerModule) dependencies.add(path.normalize(resolvedServerModule.id));
564
+ for (const dependency of dependencies) this.addWatchFile(dependency);
565
+ },
566
+ config: () => ({ ssr: { noExternal: [...new Set([defaultClientRuntime, options.clientModule ?? defaultClientRuntime])] } }),
567
+ configResolved: (resolvedConfig) => {
568
+ config = resolvedConfig;
569
+ },
570
+ configureServer: (viteServer) => {
571
+ server = viteServer;
572
+ },
573
+ enforce: "pre",
574
+ handleHotUpdate: async ({ file, server: viteServer }) => {
575
+ if (!dependencies.has(path.normalize(file))) return;
576
+ generatedSource = null;
577
+ await ensureGenerated();
578
+ const clientModuleNode = viteServer.moduleGraph.getModuleById(resolvedClientModule);
579
+ if (clientModuleNode) {
580
+ viteServer.moduleGraph.invalidateModule(clientModuleNode);
581
+ return [clientModuleNode];
582
+ }
583
+ },
584
+ load: async (id) => {
585
+ if (id !== resolvedClientModule) return;
586
+ return transformWithOxc(generatedSource ?? await ensureGenerated(), id, { lang: "ts" });
587
+ },
588
+ name: "@nkzw/fate",
589
+ resolveId: (id) => clientModules.has(id) ? resolvedClientModule : void 0
590
+ };
591
+ };
592
+ //#endregion
593
+ export { fate };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nkzw/fate",
3
- "version": "0.1.3",
3
+ "version": "1.0.0",
4
4
  "description": "fate is a modern data client for React.",
5
5
  "homepage": "https://github.com/nkzw-tech/fate",
6
6
  "license": "MIT",
@@ -16,6 +16,7 @@
16
16
  "fate": "./lib/cli.mjs"
17
17
  },
18
18
  "files": [
19
+ "docs",
19
20
  "lib"
20
21
  ],
21
22
  "type": "module",
@@ -27,6 +28,16 @@
27
28
  "@nkzw/source": "./src/index.ts",
28
29
  "default": "./lib/index.mjs"
29
30
  },
31
+ "./client": {
32
+ "types": "./lib/clientStub.d.mts",
33
+ "@nkzw/source": "./src/clientStub.ts",
34
+ "default": "./lib/clientStub.mjs"
35
+ },
36
+ "./list": {
37
+ "types": "./src/list.ts",
38
+ "@nkzw/source": "./src/list.ts",
39
+ "default": "./lib/list.mjs"
40
+ },
30
41
  "./cli": {
31
42
  "types": "./lib/cli.d.mts",
32
43
  "@nkzw/source": "./src/cli.ts",
@@ -36,20 +47,47 @@
36
47
  "types": "./lib/server.d.mts",
37
48
  "@nkzw/source": "./src/server.ts",
38
49
  "default": "./lib/server.mjs"
50
+ },
51
+ "./server/drizzle": {
52
+ "types": "./lib/server/drizzle.d.mts",
53
+ "@nkzw/source": "./src/server/drizzle.ts",
54
+ "default": "./lib/server/drizzle.mjs"
55
+ },
56
+ "./server/prisma": {
57
+ "types": "./lib/server/prisma.d.mts",
58
+ "@nkzw/source": "./src/server/prisma.ts",
59
+ "default": "./lib/server/prisma.mjs"
60
+ },
61
+ "./vite": {
62
+ "types": "./lib/vite.d.mts",
63
+ "@nkzw/source": "./src/vite.ts",
64
+ "default": "./lib/vite.mjs"
39
65
  }
40
66
  },
41
67
  "dependencies": {
42
68
  "superjson": "^2.2.6",
43
- "zod": "^4.3.6"
69
+ "zod": "^4.4.3"
44
70
  },
45
71
  "devDependencies": {
46
- "@trpc/client": "^11.8.1",
47
- "@trpc/server": "^11.8.1"
72
+ "@trpc/client": "^11.17.0",
73
+ "@trpc/server": "^11.17.0",
74
+ "drizzle-orm": "^0.45.2",
75
+ "vite": "^8.0.12"
48
76
  },
49
77
  "peerDependencies": {
50
- "@trpc/client": "^11.6.0"
78
+ "@trpc/client": "^11.6.0",
79
+ "drizzle-orm": "^0.45.0",
80
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
81
+ },
82
+ "peerDependenciesMeta": {
83
+ "drizzle-orm": {
84
+ "optional": true
85
+ },
86
+ "vite": {
87
+ "optional": true
88
+ }
51
89
  },
52
90
  "scripts": {
53
- "build": "vite lib -d lib --target=node24 src/index.ts src/server.ts src/cli.ts"
91
+ "build": "vp pack -d lib --target=node24 src/index.ts src/clientStub.ts src/list.ts src/cli.ts src/server.ts src/server/drizzle.ts src/server/prisma.ts src/vite.ts"
54
92
  }
55
93
  }