@epoch32/visage 0.0.0-stage → 0.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 (138) hide show
  1. package/LICENSE +373 -0
  2. package/README.md +110 -2
  3. package/dist/bundle.dev.js +4875 -0
  4. package/dist/index.js +9633 -0
  5. package/dist/src/api/index.js +4002 -0
  6. package/dist/src/charts/index.js +2717 -0
  7. package/dist/src/devtools/index.js +4677 -0
  8. package/dist/src/dom/index.js +1550 -0
  9. package/dist/src/form/index.js +1415 -0
  10. package/dist/src/index.js +11826 -0
  11. package/dist/src/jsx-runtime/index.js +1630 -0
  12. package/dist/src/query/index.js +2422 -0
  13. package/dist/src/router/index.js +1856 -0
  14. package/dist/src/rpc/index.js +3484 -0
  15. package/dist/src/server/index.js +1173 -0
  16. package/dist/src/signals/index.js +1077 -0
  17. package/dist/src/style/index.js +2340 -0
  18. package/dist/src/template/index.js +1149 -0
  19. package/dist/src/ui/index.js +4155 -0
  20. package/dist/src/virtual/index.js +1955 -0
  21. package/dist/src/vite/index.js +984 -0
  22. package/dist/vite/index.js +707 -0
  23. package/package.json +140 -4
  24. package/src/api/client.ts +371 -0
  25. package/src/api/file-router.ts +183 -0
  26. package/src/api/handler.ts +326 -0
  27. package/src/api/index.ts +71 -0
  28. package/src/api/middleware.ts +54 -0
  29. package/src/api/pragma.ts +155 -0
  30. package/src/api/procedure.ts +172 -0
  31. package/src/api/types.ts +249 -0
  32. package/src/charts/Area.tsx +111 -0
  33. package/src/charts/Axis.tsx +126 -0
  34. package/src/charts/Bar.tsx +86 -0
  35. package/src/charts/CartesianGrid.tsx +72 -0
  36. package/src/charts/Chart.tsx +269 -0
  37. package/src/charts/Cursor.tsx +42 -0
  38. package/src/charts/Legend.tsx +56 -0
  39. package/src/charts/Line.tsx +106 -0
  40. package/src/charts/Pie.tsx +88 -0
  41. package/src/charts/Tooltip.tsx +127 -0
  42. package/src/charts/context.ts +30 -0
  43. package/src/charts/index.ts +14 -0
  44. package/src/charts/path.ts +155 -0
  45. package/src/charts/scale.ts +133 -0
  46. package/src/charts/types.ts +50 -0
  47. package/src/devtools/DomTab.tsx +134 -0
  48. package/src/devtools/GraphInspector.tsx +196 -0
  49. package/src/devtools/GraphTab.tsx +315 -0
  50. package/src/devtools/QueryTab.tsx +200 -0
  51. package/src/devtools/RpcTab.tsx +190 -0
  52. package/src/devtools/SignalsTab.tsx +129 -0
  53. package/src/devtools/component.tsx +243 -0
  54. package/src/devtools/highlighter.ts +134 -0
  55. package/src/devtools/hub.ts +175 -0
  56. package/src/devtools/index.ts +20 -0
  57. package/src/devtools/styles.ts +313 -0
  58. package/src/devtools/types.ts +45 -0
  59. package/src/dom/async.ts +232 -0
  60. package/src/dom/control-flow.ts +353 -0
  61. package/src/dom/index.ts +32 -0
  62. package/src/dom/lazy.ts +82 -0
  63. package/src/dom/render.ts +286 -0
  64. package/src/dom/resource.ts +120 -0
  65. package/src/form/form.ts +218 -0
  66. package/src/form/index.ts +17 -0
  67. package/src/form/schema.ts +299 -0
  68. package/src/index.ts +413 -0
  69. package/src/jsx-dev-runtime/index.ts +1 -0
  70. package/src/jsx-runtime/index.ts +112 -0
  71. package/src/jsx-runtime/jsx.d.ts +278 -0
  72. package/src/query/create-mutation.ts +64 -0
  73. package/src/query/create-query.ts +426 -0
  74. package/src/query/focus-manager.ts +68 -0
  75. package/src/query/index.ts +63 -0
  76. package/src/query/mutation-cache.ts +138 -0
  77. package/src/query/online-manager.ts +67 -0
  78. package/src/query/query-cache.ts +369 -0
  79. package/src/query/query-client.ts +248 -0
  80. package/src/query/types.ts +221 -0
  81. package/src/query/utils.ts +132 -0
  82. package/src/router/index.ts +19 -0
  83. package/src/router/router.tsx +352 -0
  84. package/src/rpc/client.ts +317 -0
  85. package/src/rpc/error.ts +70 -0
  86. package/src/rpc/index.ts +52 -0
  87. package/src/rpc/procedure.ts +88 -0
  88. package/src/rpc/router.ts +105 -0
  89. package/src/rpc/server.ts +511 -0
  90. package/src/rpc/types.ts +328 -0
  91. package/src/server/hydrate.ts +21 -0
  92. package/src/server/index.ts +10 -0
  93. package/src/server/render-to-stream.ts +31 -0
  94. package/src/server/render-to-string.ts +125 -0
  95. package/src/signals/computed.ts +217 -0
  96. package/src/signals/context.ts +89 -0
  97. package/src/signals/effect.ts +161 -0
  98. package/src/signals/index.ts +57 -0
  99. package/src/signals/scope.ts +132 -0
  100. package/src/signals/signal.ts +203 -0
  101. package/src/signals/store.ts +163 -0
  102. package/src/signals/system.ts +124 -0
  103. package/src/signals/types.ts +64 -0
  104. package/src/style/component.tsx +122 -0
  105. package/src/style/create-style.ts +229 -0
  106. package/src/style/cx.ts +75 -0
  107. package/src/style/hash.ts +19 -0
  108. package/src/style/index.ts +8 -0
  109. package/src/style/layout.tsx +406 -0
  110. package/src/style/manager.ts +87 -0
  111. package/src/style/parser.ts +257 -0
  112. package/src/style/scale.ts +237 -0
  113. package/src/template/index.ts +4 -0
  114. package/src/template/template.ts +126 -0
  115. package/src/ui/accordion.tsx +286 -0
  116. package/src/ui/command-dialog.tsx +49 -0
  117. package/src/ui/command.tsx +400 -0
  118. package/src/ui/data-table.tsx +322 -0
  119. package/src/ui/dialog.tsx +362 -0
  120. package/src/ui/dropdown-menu.tsx +381 -0
  121. package/src/ui/icon.tsx +69 -0
  122. package/src/ui/index.ts +138 -0
  123. package/src/ui/layout.tsx +13 -0
  124. package/src/ui/popover.tsx +249 -0
  125. package/src/ui/switch.tsx +114 -0
  126. package/src/ui/tabs.tsx +297 -0
  127. package/src/ui/toast.tsx +280 -0
  128. package/src/ui/tooltip.tsx +262 -0
  129. package/src/ui/utils.ts +110 -0
  130. package/src/virtual/index.ts +12 -0
  131. package/src/virtual/virtual-list.tsx +185 -0
  132. package/src/virtual/virtualizer.ts +274 -0
  133. package/src/vite/hmr.ts +38 -0
  134. package/src/vite/index.ts +3 -0
  135. package/src/vite/lucide.ts +124 -0
  136. package/src/vite/plugin.ts +102 -0
  137. package/src/vite/rpc-middleware.ts +82 -0
  138. package/src/vite/types.ts +65 -0
@@ -0,0 +1,426 @@
1
+ import { SuspenseContext } from "../dom/async";
2
+ import {
3
+ COMPUTED_BRAND,
4
+ computed,
5
+ effect,
6
+ onCleanup,
7
+ type ReadonlySignal,
8
+ SIGNAL_BRAND,
9
+ signal,
10
+ untrack,
11
+ type WritableSignal
12
+ } from "../signals";
13
+ import { useContext } from "../signals/context";
14
+ import { focusManager } from "./focus-manager";
15
+ import type { Query, QueryObserver } from "./query-cache";
16
+ import { type QueryClient, useQueryClient } from "./query-client";
17
+ import type {
18
+ CreateQueryOptions,
19
+ FetchStatus,
20
+ InfiniteData,
21
+ InfiniteQueryOptions,
22
+ InfiniteQueryResult,
23
+ QueryKey,
24
+ QueryResult,
25
+ QueryStatus,
26
+ RefetchOptions
27
+ } from "./types";
28
+
29
+ export function createQuery<
30
+ TData = unknown,
31
+ TError = any,
32
+ TSelected = TData,
33
+ TQueryKey extends QueryKey = QueryKey
34
+ >(
35
+ options:
36
+ | CreateQueryOptions<TData, TError, TSelected, TQueryKey>
37
+ | (() => CreateQueryOptions<TData, TError, TSelected, TQueryKey>),
38
+ client?: QueryClient
39
+ ): QueryResult<TSelected, TError> {
40
+ const queryClient = useQueryClient(client);
41
+ const resolveOptions = () => (typeof options === "function" ? options() : options);
42
+
43
+ const currentQuery = signal<Query<TData, TError> | null>(null);
44
+ const version: WritableSignal<number> = signal(0);
45
+ let activeObserver: QueryObserver<TData, TError> | null = null;
46
+ let intervalTimer: any = null;
47
+
48
+ const clearIntervalTimer = () => {
49
+ if (intervalTimer !== null) {
50
+ clearInterval(intervalTimer);
51
+ intervalTimer = null;
52
+ }
53
+ };
54
+
55
+ effect(() => {
56
+ const rawOpts = resolveOptions();
57
+ const defaults = queryClient.getDefaultOptions().queries ?? {};
58
+ const mergedOpts: CreateQueryOptions<TData, TError, TSelected, TQueryKey> = {
59
+ ...defaults,
60
+ ...rawOpts
61
+ };
62
+
63
+ const isEnabled =
64
+ typeof mergedOpts.enabled === "function"
65
+ ? mergedOpts.enabled()
66
+ : mergedOpts.enabled !== false;
67
+
68
+ const targetQuery = queryClient.queryCache.build<TData, TError>(queryClient, mergedOpts);
69
+
70
+ if (untrack(currentQuery) !== targetQuery || !activeObserver) {
71
+ const prevQ = untrack(currentQuery);
72
+ if (prevQ && activeObserver) {
73
+ prevQ.removeObserver(activeObserver);
74
+ }
75
+
76
+ const observer: QueryObserver<TData, TError> = {
77
+ options: mergedOpts,
78
+ onQueryUpdate: () => {
79
+ version.update((v) => v + 1);
80
+ }
81
+ };
82
+
83
+ targetQuery.addObserver(observer);
84
+ activeObserver = observer;
85
+ currentQuery.set(targetQuery);
86
+ } else if (activeObserver) {
87
+ activeObserver.options = mergedOpts;
88
+ }
89
+
90
+ clearIntervalTimer();
91
+ const refetchInterval =
92
+ typeof mergedOpts.refetchInterval === "function"
93
+ ? mergedOpts.refetchInterval()
94
+ : mergedOpts.refetchInterval;
95
+
96
+ if (refetchInterval && refetchInterval > 0) {
97
+ intervalTimer = setInterval(() => {
98
+ if (focusManager.isFocused() || mergedOpts.refetchIntervalInBackground) {
99
+ targetQuery.fetch(mergedOpts).catch(() => {});
100
+ }
101
+ }, refetchInterval);
102
+ }
103
+
104
+ if (isEnabled) {
105
+ untrack(() => {
106
+ if (targetQuery.isStale(mergedOpts.staleTime) && targetQuery.fetchStatus() === "idle") {
107
+ const fetchPromise = targetQuery.fetch(mergedOpts);
108
+ fetchPromise.catch(() => {});
109
+ const suspense = useContext(SuspenseContext);
110
+ if (suspense) {
111
+ suspense.registerPromise(fetchPromise);
112
+ }
113
+ }
114
+ });
115
+ }
116
+ });
117
+
118
+ onCleanup(() => {
119
+ clearIntervalTimer();
120
+ if (currentQuery() && activeObserver) {
121
+ currentQuery()!.removeObserver(activeObserver);
122
+ }
123
+ });
124
+
125
+ const data: ReadonlySignal<TSelected | undefined> = computed(() => {
126
+ version(); // subscribe to reactive updates
127
+ const q = currentQuery();
128
+ const rawOpts = resolveOptions();
129
+ const rawData = q?.data();
130
+
131
+ if (rawData !== undefined) {
132
+ if (rawOpts.select) {
133
+ return rawOpts.select(rawData);
134
+ }
135
+ return rawData as unknown as TSelected;
136
+ }
137
+
138
+ if (rawOpts.placeholderData !== undefined) {
139
+ return (
140
+ typeof rawOpts.placeholderData === "function"
141
+ ? (rawOpts.placeholderData as (prev: any) => any)(undefined)
142
+ : rawOpts.placeholderData
143
+ ) as TSelected;
144
+ }
145
+
146
+ return undefined;
147
+ });
148
+
149
+ const error: ReadonlySignal<TError | null> = computed(() => {
150
+ version();
151
+ return currentQuery()?.error() ?? null;
152
+ });
153
+
154
+ const status: ReadonlySignal<QueryStatus> = computed(() => {
155
+ version();
156
+ return currentQuery()?.status() ?? "pending";
157
+ });
158
+
159
+ const fetchStatus: ReadonlySignal<FetchStatus> = computed(() => {
160
+ version();
161
+ return currentQuery()?.fetchStatus() ?? "idle";
162
+ });
163
+
164
+ const isLoading: ReadonlySignal<boolean> = computed(() => {
165
+ return status() === "pending" && fetchStatus() === "fetching";
166
+ });
167
+
168
+ const isFetching: ReadonlySignal<boolean> = computed(() => {
169
+ return fetchStatus() === "fetching";
170
+ });
171
+
172
+ const isSuccess: ReadonlySignal<boolean> = computed(() => {
173
+ return status() === "success";
174
+ });
175
+
176
+ const isError: ReadonlySignal<boolean> = computed(() => {
177
+ return status() === "error";
178
+ });
179
+
180
+ const isStale: ReadonlySignal<boolean> = computed(() => {
181
+ version();
182
+ const q = currentQuery();
183
+ const rawOpts = resolveOptions();
184
+ return q?.isStale(rawOpts.staleTime) ?? true;
185
+ });
186
+
187
+ const dataUpdatedAt: ReadonlySignal<number> = computed(() => {
188
+ version();
189
+ return currentQuery()?.dataUpdatedAt() ?? 0;
190
+ });
191
+
192
+ const errorUpdatedAt: ReadonlySignal<number> = computed(() => {
193
+ version();
194
+ return currentQuery()?.errorUpdatedAt() ?? 0;
195
+ });
196
+
197
+ const refetch = async (refetchOpts?: RefetchOptions): Promise<TSelected | undefined> => {
198
+ const q = untrack(() => currentQuery());
199
+ if (!q) return undefined;
200
+ const rawOpts = resolveOptions();
201
+ const defaults = queryClient.getDefaultOptions().queries ?? {};
202
+ const mergedOpts = { ...defaults, ...rawOpts };
203
+
204
+ const rawResult = await q.fetch(mergedOpts, { cancelRefetch: refetchOpts?.cancelRefetch });
205
+ if (rawOpts.select) {
206
+ return rawOpts.select(rawResult);
207
+ }
208
+ return rawResult as unknown as TSelected;
209
+ };
210
+
211
+ const accessor = ((): TSelected | undefined => {
212
+ const q = currentQuery();
213
+ const rawOpts = resolveOptions();
214
+
215
+ if ((rawOpts.suspense || status() === "pending") && q?.promise) {
216
+ const suspense = useContext(SuspenseContext);
217
+ if (suspense) {
218
+ suspense.registerPromise(q.promise);
219
+ }
220
+ }
221
+
222
+ return data();
223
+ }) as QueryResult<TSelected, TError>;
224
+
225
+ Object.defineProperties(accessor, {
226
+ [SIGNAL_BRAND]: { value: true },
227
+ [COMPUTED_BRAND]: { value: true },
228
+ value: { get: () => data() },
229
+ data: { get: () => data },
230
+ error: { get: () => error },
231
+ status: { get: () => status },
232
+ fetchStatus: { get: () => fetchStatus },
233
+ isLoading: { get: () => isLoading },
234
+ isFetching: { get: () => isFetching },
235
+ isSuccess: { get: () => isSuccess },
236
+ isError: { get: () => isError },
237
+ isStale: { get: () => isStale },
238
+ dataUpdatedAt: { get: () => dataUpdatedAt },
239
+ errorUpdatedAt: { get: () => errorUpdatedAt },
240
+ refetch: { value: refetch }
241
+ });
242
+
243
+ return accessor;
244
+ }
245
+
246
+ export function createInfiniteQuery<
247
+ TData = unknown,
248
+ TError = any,
249
+ TPageParam = unknown,
250
+ TSelected = InfiniteData<TData, TPageParam>,
251
+ TQueryKey extends QueryKey = QueryKey
252
+ >(
253
+ options:
254
+ | InfiniteQueryOptions<TData, TError, TPageParam, TSelected, TQueryKey>
255
+ | (() => InfiniteQueryOptions<TData, TError, TPageParam, TSelected, TQueryKey>),
256
+ client?: QueryClient
257
+ ): InfiniteQueryResult<TData, TError, TPageParam> {
258
+ const queryClient = useQueryClient(client);
259
+ const resolveOptions = () => (typeof options === "function" ? options() : options);
260
+
261
+ const isFetchingNextPage: WritableSignal<boolean> = signal(false);
262
+ const isFetchingPreviousPage: WritableSignal<boolean> = signal(false);
263
+
264
+ const baseQuery = createQuery<
265
+ InfiniteData<TData, TPageParam>,
266
+ TError,
267
+ InfiniteData<TData, TPageParam>,
268
+ TQueryKey
269
+ >(() => {
270
+ const rawOpts = resolveOptions();
271
+ return {
272
+ ...rawOpts,
273
+ queryFn: async (ctx) => {
274
+ const pageParam = ctx.pageParam ?? rawOpts.initialPageParam;
275
+ const page = await (rawOpts.queryFn as any)({
276
+ ...ctx,
277
+ pageParam
278
+ });
279
+ return {
280
+ pages: [page],
281
+ pageParams: [pageParam]
282
+ };
283
+ },
284
+ select: undefined
285
+ };
286
+ }, queryClient);
287
+
288
+ const hasNextPage = computed(() => {
289
+ const current = baseQuery.data();
290
+ if (!current || current.pages.length === 0) return false;
291
+ const rawOpts = resolveOptions();
292
+ const lastPage = current.pages[current.pages.length - 1];
293
+ const lastParam = current.pageParams[current.pageParams.length - 1];
294
+ const nextParam = rawOpts.getNextPageParam(
295
+ lastPage!,
296
+ current.pages,
297
+ lastParam!,
298
+ current.pageParams
299
+ );
300
+ return nextParam !== undefined && nextParam !== null;
301
+ });
302
+
303
+ const hasPreviousPage = computed(() => {
304
+ const current = baseQuery.data();
305
+ if (!current || current.pages.length === 0) return false;
306
+ const rawOpts = resolveOptions();
307
+ if (!rawOpts.getPreviousPageParam) return false;
308
+ const firstPage = current.pages[0];
309
+ const firstParam = current.pageParams[0];
310
+ const prevParam = rawOpts.getPreviousPageParam(
311
+ firstPage!,
312
+ current.pages,
313
+ firstParam!,
314
+ current.pageParams
315
+ );
316
+ return prevParam !== undefined && prevParam !== null;
317
+ });
318
+
319
+ const fetchNextPage = async (): Promise<InfiniteData<TData, TPageParam> | undefined> => {
320
+ const current = baseQuery.data();
321
+ if (!current || current.pages.length === 0) return undefined;
322
+ const rawOpts = resolveOptions();
323
+ const lastPage = current.pages[current.pages.length - 1];
324
+ const lastParam = current.pageParams[current.pageParams.length - 1];
325
+ const nextParam = rawOpts.getNextPageParam(
326
+ lastPage!,
327
+ current.pages,
328
+ lastParam!,
329
+ current.pageParams
330
+ );
331
+
332
+ if (nextParam === undefined || nextParam === null) {
333
+ return current;
334
+ }
335
+
336
+ isFetchingNextPage.set(true);
337
+ try {
338
+ const newPage = await (rawOpts.queryFn as any)({
339
+ queryKey: rawOpts.queryKey,
340
+ signal: new AbortController().signal,
341
+ pageParam: nextParam,
342
+ meta: rawOpts.meta
343
+ });
344
+
345
+ const nextData: InfiniteData<TData, TPageParam> = {
346
+ pages: [...current.pages, newPage],
347
+ pageParams: [...current.pageParams, nextParam]
348
+ };
349
+
350
+ queryClient.setQueryData(rawOpts.queryKey, nextData);
351
+ return nextData;
352
+ } finally {
353
+ isFetchingNextPage.set(false);
354
+ }
355
+ };
356
+
357
+ const fetchPreviousPage = async (): Promise<InfiniteData<TData, TPageParam> | undefined> => {
358
+ const current = baseQuery.data();
359
+ if (!current || current.pages.length === 0) return undefined;
360
+ const rawOpts = resolveOptions();
361
+ if (!rawOpts.getPreviousPageParam) return current;
362
+
363
+ const firstPage = current.pages[0];
364
+ const firstParam = current.pageParams[0];
365
+ const prevParam = rawOpts.getPreviousPageParam(
366
+ firstPage!,
367
+ current.pages,
368
+ firstParam!,
369
+ current.pageParams
370
+ );
371
+
372
+ if (prevParam === undefined || prevParam === null) {
373
+ return current;
374
+ }
375
+
376
+ isFetchingPreviousPage.set(true);
377
+ try {
378
+ const newPage = await (rawOpts.queryFn as any)({
379
+ queryKey: rawOpts.queryKey,
380
+ signal: new AbortController().signal,
381
+ pageParam: prevParam,
382
+ meta: rawOpts.meta
383
+ });
384
+
385
+ const nextData: InfiniteData<TData, TPageParam> = {
386
+ pages: [newPage, ...current.pages],
387
+ pageParams: [prevParam, ...current.pageParams]
388
+ };
389
+
390
+ queryClient.setQueryData(rawOpts.queryKey, nextData);
391
+ return nextData;
392
+ } finally {
393
+ isFetchingPreviousPage.set(false);
394
+ }
395
+ };
396
+
397
+ const accessor = ((): InfiniteData<TData, TPageParam> | undefined => {
398
+ return baseQuery();
399
+ }) as unknown as InfiniteQueryResult<TData, TError, TPageParam>;
400
+
401
+ Object.defineProperties(accessor, {
402
+ [SIGNAL_BRAND]: { value: true },
403
+ [COMPUTED_BRAND]: { value: true },
404
+ value: { get: () => baseQuery.data() },
405
+ data: { get: () => baseQuery.data },
406
+ error: { get: () => baseQuery.error },
407
+ status: { get: () => baseQuery.status },
408
+ fetchStatus: { get: () => baseQuery.fetchStatus },
409
+ isLoading: { get: () => baseQuery.isLoading },
410
+ isFetching: { get: () => baseQuery.isFetching },
411
+ isSuccess: { get: () => baseQuery.isSuccess },
412
+ isError: { get: () => baseQuery.isError },
413
+ isStale: { get: () => baseQuery.isStale },
414
+ dataUpdatedAt: { get: () => baseQuery.dataUpdatedAt },
415
+ errorUpdatedAt: { get: () => baseQuery.errorUpdatedAt },
416
+ refetch: { value: baseQuery.refetch },
417
+ fetchNextPage: { value: fetchNextPage },
418
+ fetchPreviousPage: { value: fetchPreviousPage },
419
+ hasNextPage: { get: () => hasNextPage },
420
+ hasPreviousPage: { get: () => hasPreviousPage },
421
+ isFetchingNextPage: { get: () => isFetchingNextPage },
422
+ isFetchingPreviousPage: { get: () => isFetchingPreviousPage }
423
+ });
424
+
425
+ return accessor;
426
+ }
@@ -0,0 +1,68 @@
1
+ export type FocusListener = (focused: boolean) => void;
2
+
3
+ export class FocusManager {
4
+ private _focused?: boolean;
5
+ private _listeners = new Set<FocusListener>();
6
+ private _cleanup?: () => void;
7
+
8
+ constructor() {
9
+ this.setupEventListener();
10
+ }
11
+
12
+ private setupEventListener() {
13
+ if (typeof window === "undefined" || typeof window.addEventListener === "undefined") {
14
+ return;
15
+ }
16
+
17
+ const listener = () => {
18
+ this.notify(this.isFocused());
19
+ };
20
+
21
+ window.addEventListener("visibilitychange", listener, false);
22
+ window.addEventListener("focus", listener, false);
23
+
24
+ this._cleanup = () => {
25
+ window.removeEventListener("visibilitychange", listener);
26
+ window.removeEventListener("focus", listener);
27
+ };
28
+ }
29
+
30
+ setFocused(focused?: boolean) {
31
+ this._focused = focused;
32
+ this.notify(this.isFocused());
33
+ }
34
+
35
+ isFocused(): boolean {
36
+ if (typeof this._focused === "boolean") {
37
+ return this._focused;
38
+ }
39
+ if (typeof document !== "undefined" && typeof document.visibilityState === "string") {
40
+ return document.visibilityState !== "hidden";
41
+ }
42
+ return true;
43
+ }
44
+
45
+ subscribe(listener: FocusListener): () => void {
46
+ this._listeners.add(listener);
47
+ return () => {
48
+ this._listeners.delete(listener);
49
+ };
50
+ }
51
+
52
+ private notify(focused: boolean) {
53
+ for (const listener of this._listeners) {
54
+ try {
55
+ listener(focused);
56
+ } catch (err) {
57
+ console.error("Error in focus listener:", err);
58
+ }
59
+ }
60
+ }
61
+
62
+ cleanup() {
63
+ this._cleanup?.();
64
+ this._listeners.clear();
65
+ }
66
+ }
67
+
68
+ export const focusManager = new FocusManager();
@@ -0,0 +1,63 @@
1
+ export { createMutation } from "./create-mutation";
2
+ export {
3
+ createInfiniteQuery,
4
+ createQuery
5
+ } from "./create-query";
6
+ export {
7
+ type FocusListener,
8
+ FocusManager,
9
+ focusManager
10
+ } from "./focus-manager";
11
+ export {
12
+ Mutation,
13
+ MutationCache
14
+ } from "./mutation-cache";
15
+ export {
16
+ type OnlineListener,
17
+ OnlineManager,
18
+ onlineManager
19
+ } from "./online-manager";
20
+ export {
21
+ Query,
22
+ QueryCache,
23
+ type QueryObserver
24
+ } from "./query-cache";
25
+ export {
26
+ QueryClient,
27
+ type QueryClientConfig,
28
+ QueryClientContext,
29
+ QueryClientProvider,
30
+ useQueryClient
31
+ } from "./query-client";
32
+ export type {
33
+ CreateQueryOptions,
34
+ DefaultOptions,
35
+ FetchStatus,
36
+ InfiniteData,
37
+ InfiniteQueryOptions,
38
+ InfiniteQueryResult,
39
+ MutateOptions,
40
+ MutationFunction,
41
+ MutationOptions,
42
+ MutationResult,
43
+ MutationStatus,
44
+ QueryFilters,
45
+ QueryFunction,
46
+ QueryFunctionContext,
47
+ QueryKey,
48
+ QueryOptions,
49
+ QueryResult,
50
+ QueryState,
51
+ QueryStatus,
52
+ RefetchOptions,
53
+ RetryDelayValue,
54
+ RetryValue
55
+ } from "./types";
56
+ export {
57
+ deepEqual,
58
+ hashKey,
59
+ isPlainObject,
60
+ matchQuery,
61
+ partialDeepEqual,
62
+ sleep
63
+ } from "./utils";
@@ -0,0 +1,138 @@
1
+ import { computed, type ReadonlySignal, signal, type WritableSignal } from "../signals";
2
+ import type { MutateOptions, MutationOptions, MutationStatus } from "./types";
3
+
4
+ export class Mutation<TData = unknown, TError = any, TVariables = void, TContext = unknown> {
5
+ readonly options: MutationOptions<TData, TError, TVariables, TContext>;
6
+
7
+ readonly data: WritableSignal<TData | undefined>;
8
+ readonly error: WritableSignal<TError | null>;
9
+ readonly status: WritableSignal<MutationStatus>;
10
+ readonly submittedAt: WritableSignal<number>;
11
+ readonly variables: WritableSignal<TVariables | undefined>;
12
+
13
+ readonly isIdle: ReadonlySignal<boolean>;
14
+ readonly isPending: ReadonlySignal<boolean>;
15
+ readonly isLoading: ReadonlySignal<boolean>;
16
+ readonly isSuccess: ReadonlySignal<boolean>;
17
+ readonly isError: ReadonlySignal<boolean>;
18
+
19
+ constructor(options: MutationOptions<TData, TError, TVariables, TContext>) {
20
+ this.options = options;
21
+ this.data = signal<TData | undefined>(undefined);
22
+ this.error = signal<TError | null>(null);
23
+ this.status = signal<MutationStatus>("idle");
24
+ this.submittedAt = signal<number>(0);
25
+ this.variables = signal<TVariables | undefined>(undefined);
26
+
27
+ this.isIdle = computed(() => this.status() === "idle");
28
+ this.isPending = computed(() => this.status() === "pending");
29
+ this.isLoading = this.isPending;
30
+ this.isSuccess = computed(() => this.status() === "success");
31
+ this.isError = computed(() => this.status() === "error");
32
+ }
33
+
34
+ async execute(
35
+ variables: TVariables,
36
+ mutateOptions?: MutateOptions<TData, TError, TVariables, TContext>
37
+ ): Promise<TData> {
38
+ this.status.set("pending");
39
+ this.error.set(null);
40
+ this.variables.set(variables);
41
+ this.submittedAt.set(Date.now());
42
+
43
+ let context: TContext | undefined;
44
+
45
+ try {
46
+ if (this.options.onMutate) {
47
+ const res = await this.options.onMutate(variables);
48
+ if (res !== undefined) {
49
+ context = res as TContext;
50
+ }
51
+ }
52
+
53
+ const result = await this.options.mutationFn(variables);
54
+
55
+ this.data.set(result);
56
+ this.status.set("success");
57
+
58
+ try {
59
+ await this.options.onSuccess?.(result, variables, context);
60
+ } catch (err) {
61
+ console.error("Error in mutation onSuccess:", err);
62
+ }
63
+ try {
64
+ mutateOptions?.onSuccess?.(result, variables, context);
65
+ } catch (err) {
66
+ console.error("Error in mutateOptions onSuccess:", err);
67
+ }
68
+
69
+ try {
70
+ await this.options.onSettled?.(result, null, variables, context);
71
+ } catch (err) {
72
+ console.error("Error in mutation onSettled:", err);
73
+ }
74
+ try {
75
+ mutateOptions?.onSettled?.(result, null, variables, context);
76
+ } catch (err) {
77
+ console.error("Error in mutateOptions onSettled:", err);
78
+ }
79
+
80
+ return result;
81
+ } catch (err: any) {
82
+ this.error.set(err);
83
+ this.status.set("error");
84
+
85
+ try {
86
+ await this.options.onError?.(err, variables, context);
87
+ } catch (e) {
88
+ console.error("Error in mutation onError:", e);
89
+ }
90
+ try {
91
+ mutateOptions?.onError?.(err, variables, context);
92
+ } catch (e) {
93
+ console.error("Error in mutateOptions onError:", e);
94
+ }
95
+
96
+ try {
97
+ await this.options.onSettled?.(undefined, err, variables, context);
98
+ } catch (e) {
99
+ console.error("Error in mutation onSettled:", e);
100
+ }
101
+ try {
102
+ mutateOptions?.onSettled?.(undefined, err, variables, context);
103
+ } catch (e) {
104
+ console.error("Error in mutateOptions onSettled:", e);
105
+ }
106
+
107
+ throw err;
108
+ }
109
+ }
110
+
111
+ reset() {
112
+ this.data.set(undefined);
113
+ this.error.set(null);
114
+ this.status.set("idle");
115
+ this.submittedAt.set(0);
116
+ this.variables.set(undefined);
117
+ }
118
+ }
119
+
120
+ export class MutationCache {
121
+ mutations = new Set<Mutation<any, any, any, any>>();
122
+
123
+ build<TData, TError, TVariables, TContext>(
124
+ options: MutationOptions<TData, TError, TVariables, TContext>
125
+ ): Mutation<TData, TError, TVariables, TContext> {
126
+ const mutation = new Mutation(options);
127
+ this.mutations.add(mutation);
128
+ return mutation;
129
+ }
130
+
131
+ remove(mutation: Mutation<any, any, any, any>) {
132
+ this.mutations.delete(mutation);
133
+ }
134
+
135
+ clear() {
136
+ this.mutations.clear();
137
+ }
138
+ }