kitcn 0.17.1 → 0.17.2

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 (2) hide show
  1. package/dist/solid/index.js +107 -56
  2. package/package.json +1 -1
@@ -1,10 +1,10 @@
1
1
  import { getFunctionName } from "convex/server";
2
- import { Show, createContext, createEffect, createMemo, createSignal, on, onCleanup, onMount, useContext } from "solid-js";
2
+ import { Show, createComputed, createContext, createEffect, createMemo, createRenderEffect, createSignal, on, onCleanup, onMount, useContext } from "solid-js";
3
3
  import { createComponent, memo } from "solid-js/web";
4
4
  import { createStore } from "solid-js/store";
5
- import { notifyManager, skipToken, useQueries, useQueryClient } from "@tanstack/solid-query";
5
+ import { notifyManager, skipToken, useQueryClient } from "@tanstack/solid-query";
6
6
  import { ConvexClient, ConvexHttpClient } from "convex/browser";
7
- import { hashKey } from "@tanstack/query-core";
7
+ import { QueriesObserver, hashKey } from "@tanstack/query-core";
8
8
  import { convexToJson } from "convex/values";
9
9
 
10
10
  //#region src/crpc/error.ts
@@ -2504,6 +2504,42 @@ const getConvexQueryClientSingleton = ({ authStore, convex, queryClient, symbolK
2504
2504
  //#region src/internal/pagination.ts
2505
2505
  const shouldSplitPaginationPage = (page, initialNumItems) => Boolean(page.splitCursor) && (page.pageStatus === "SplitRecommended" || page.pageStatus === "SplitRequired" || initialNumItems !== void 0 && page.page.length > initialNumItems * 2);
2506
2506
 
2507
+ //#endregion
2508
+ //#region src/solid/create-queries-results.ts
2509
+ /**
2510
+ * Subscribe a reactive list of queries and expose the raw observer results.
2511
+ *
2512
+ * Solid Query's `useQueries` cannot back an aggregate result: it feeds the
2513
+ * `combine` output straight into `createStore` and then calls `.map()` on it,
2514
+ * so any non-array aggregate throws `state.map is not a function` while the
2515
+ * component is still setting up. It also routes `.data` through
2516
+ * `createResource`, which suspends the nearest boundary for as long as a query
2517
+ * is in flight.
2518
+ *
2519
+ * Driving `QueriesObserver` directly keeps every entry a plain
2520
+ * `QueryObserverResult` - the same value the React port aggregates - and leaves
2521
+ * aggregation to the caller.
2522
+ *
2523
+ * @param queries Reactive list of query options.
2524
+ * @returns Accessor for the raw observer results, in query order.
2525
+ */
2526
+ function createQueriesResults(queries) {
2527
+ const queryClient = useQueryClient();
2528
+ const defaulted = () => queries().map((options) => ({
2529
+ ...queryClient.defaultQueryOptions(options),
2530
+ _optimisticResults: "optimistic"
2531
+ }));
2532
+ const observer = new QueriesObserver(queryClient, defaulted());
2533
+ const [results, setResults] = createSignal(observer.getCurrentResult(), { equals: false });
2534
+ createComputed(() => {
2535
+ const next = defaulted();
2536
+ observer.setQueries(next);
2537
+ setResults(observer.getOptimisticResult(next, void 0)[0]);
2538
+ });
2539
+ onCleanup(observer.subscribe((next) => setResults(next)));
2540
+ return results;
2541
+ }
2542
+
2507
2543
  //#endregion
2508
2544
  //#region src/solid/use-infinite-query.ts
2509
2545
  const PAGINATION_KEY_PREFIX = "__pagination__";
@@ -2516,6 +2552,59 @@ const getOrCreatePaginationId = (storeKey) => {
2516
2552
  paginationIdStore.set(storeKey, newId);
2517
2553
  return newId;
2518
2554
  };
2555
+ /** Read the identity of a Convex document, tolerating `id` and `_id` shapes */
2556
+ const getItemId = (item) => {
2557
+ const doc = item;
2558
+ return doc?._id || doc?.id;
2559
+ };
2560
+ /**
2561
+ * Fold the per-page observer results into one pagination-shaped aggregate.
2562
+ * Pure: same inputs always produce the same output, so it is safe to re-run
2563
+ * inside a reactive derivation.
2564
+ */
2565
+ const aggregatePages = (results, hasPlaceholderData) => {
2566
+ const allItems = [];
2567
+ const pages = [];
2568
+ const seenIds = /* @__PURE__ */ new Set();
2569
+ let lastPage;
2570
+ let status = "LoadingFirstPage";
2571
+ for (let i = 0; i < results.length; i++) {
2572
+ const pageQuery = results[i];
2573
+ if (pageQuery.isLoading || pageQuery.data === void 0) {
2574
+ status = i === 0 ? "LoadingFirstPage" : "LoadingMore";
2575
+ break;
2576
+ }
2577
+ const page = pageQuery.data;
2578
+ lastPage = page;
2579
+ pages.push(page.page);
2580
+ for (const item of page.page) {
2581
+ const id = getItemId(item);
2582
+ if (id && seenIds.has(id)) continue;
2583
+ if (id) seenIds.add(id);
2584
+ allItems.push(item);
2585
+ }
2586
+ status = page.isDone ? "Exhausted" : "CanLoadMore";
2587
+ }
2588
+ const firstPage = results.length > 0 ? results[0] : void 0;
2589
+ const isPlaceholderData = firstPage ? firstPage.isPlaceholderData : hasPlaceholderData;
2590
+ const isFetching = results.some((r) => r.isFetching);
2591
+ const error = results.find((r) => r.isError)?.error ?? null;
2592
+ return {
2593
+ data: allItems,
2594
+ dataUpdatedAt: Math.max(...results.map((r) => r.dataUpdatedAt)),
2595
+ error,
2596
+ failureReason: error,
2597
+ isError: results.some((r) => r.isError),
2598
+ isFetchNextPageError: results.length > 1 && (results.at(-1)?.isError ?? false),
2599
+ isFetching,
2600
+ isLoading: status === "LoadingFirstPage",
2601
+ isPlaceholderData,
2602
+ isRefetching: isFetching && allItems.length > 0 && !isPlaceholderData,
2603
+ lastPage,
2604
+ pages,
2605
+ status
2606
+ };
2607
+ };
2519
2608
  /** Build a unique key for recovery attempt detection */
2520
2609
  const buildRecoveryKey = (pageKeys, page0Cursor, page0UpdatedAt) => JSON.stringify({
2521
2610
  pageKeys,
@@ -2531,26 +2620,27 @@ const buildRecoveryKey = (pageKeys, page0Cursor, page0UpdatedAt) => JSON.stringi
2531
2620
  * This hook detects this pattern and creates a recovery page that fetches
2532
2621
  * enough items to cover the lost pages, preserving the user's scroll position.
2533
2622
  */
2534
- const useStaleCursorRecovery = ({ argsObject, combined, limit, setState, state }) => {
2623
+ const useStaleCursorRecovery = ({ argsObject, combined, limit, pageResults, setState, state }) => {
2535
2624
  createEffect(on([
2536
2625
  () => combined.isFetchNextPageError,
2537
- () => combined._rawResults,
2626
+ pageResults,
2538
2627
  () => state().pageKeys,
2539
2628
  () => state().queries,
2540
2629
  () => state().autoRecoveryAttempted,
2541
2630
  argsObject
2542
2631
  ], () => {
2543
2632
  if (!combined.isFetchNextPageError) return;
2544
- const page0Result = combined._rawResults[0];
2633
+ const results = pageResults();
2634
+ const page0Result = results[0];
2545
2635
  const page0Data = page0Result?.data;
2546
2636
  const page0UpdatedAt = page0Result?.dataUpdatedAt ?? 0;
2547
2637
  const hasPage0Data = page0Data !== void 0 && !page0Result?.isError;
2548
- const hasSubsequentErrors = combined._rawResults.slice(1).some((q) => q?.isError && !q?.isFetching);
2638
+ const hasSubsequentErrors = results.slice(1).some((q) => q?.isError && !q?.isFetching);
2549
2639
  if (!hasPage0Data || !hasSubsequentErrors || !page0Data?.continueCursor) return;
2550
2640
  const currentState = state();
2551
2641
  const recoveryKey = buildRecoveryKey(currentState.pageKeys, page0Data.continueCursor, page0UpdatedAt);
2552
2642
  if (currentState.autoRecoveryAttempted === recoveryKey) return;
2553
- const erroredPageKeys = currentState.pageKeys.filter((_, i) => i > 0 && combined._rawResults[i]?.isError);
2643
+ const erroredPageKeys = currentState.pageKeys.filter((_, i) => i > 0 && results[i]?.isError);
2554
2644
  const itemsToRecover = erroredPageKeys.reduce((sum, key) => {
2555
2645
  return sum + (currentState.queries[key]?.args?.limit ?? limit ?? 20);
2556
2646
  }, 0);
@@ -2711,66 +2801,27 @@ const useInfiniteQueryInternal = (query, args, options) => {
2711
2801
  } } : {}
2712
2802
  };
2713
2803
  }));
2714
- const combined = useQueries(() => ({
2715
- queries: tanstackQueries(),
2716
- combine: (results) => {
2717
- const allItems = [];
2718
- const pages = [];
2719
- const seenIds = /* @__PURE__ */ new Set();
2720
- let lastPage;
2721
- let paginationStatus = "LoadingFirstPage";
2722
- for (let i = 0; i < results.length; i++) {
2723
- const pageQuery = results[i];
2724
- if (pageQuery.isLoading || pageQuery.data === void 0) {
2725
- paginationStatus = i === 0 ? "LoadingFirstPage" : "LoadingMore";
2726
- break;
2727
- }
2728
- const page = pageQuery.data;
2729
- lastPage = page;
2730
- pages.push(page.page);
2731
- for (const item of page.page) {
2732
- const id = item._id || item.id;
2733
- if (id && seenIds.has(id)) continue;
2734
- if (id) seenIds.add(id);
2735
- allItems.push(item);
2736
- }
2737
- paginationStatus = page.isDone ? "Exhausted" : "CanLoadMore";
2738
- }
2739
- const isPlaceholderData = results[0]?.isPlaceholderData ?? !!placeholderData;
2740
- const isFetching = results.some((r) => r.isFetching);
2741
- return {
2742
- data: allItems,
2743
- dataUpdatedAt: Math.max(...results.map((r) => r.dataUpdatedAt ?? 0)),
2744
- lastPage,
2745
- pages,
2746
- status: paginationStatus,
2747
- error: results.find((r) => r.isError)?.error ?? null,
2748
- isError: results.some((r) => r.isError),
2749
- isFetching,
2750
- isFetchNextPageError: results.length > 1 && (results.at(-1)?.isError ?? false),
2751
- isPlaceholderData,
2752
- isRefetching: isFetching && allItems.length > 0 && !isPlaceholderData,
2753
- isLoading: paginationStatus === "LoadingFirstPage",
2754
- failureReason: results.find((r) => r.isError)?.error ?? null,
2755
- _rawResults: results
2756
- };
2757
- }
2758
- }));
2804
+ const pageResults = createQueriesResults(() => tanstackQueries());
2805
+ const derive = () => aggregatePages(pageResults(), !!placeholderData);
2806
+ const [combined, setCombined] = createStore(derive());
2807
+ createRenderEffect(() => setCombined(derive()));
2759
2808
  useStaleCursorRecovery({
2760
2809
  argsObject,
2761
2810
  combined,
2762
2811
  limit,
2812
+ pageResults,
2763
2813
  setState,
2764
2814
  state
2765
2815
  });
2766
2816
  createEffect(on([
2767
- () => combined._rawResults,
2817
+ pageResults,
2768
2818
  () => state().pageKeys,
2769
2819
  () => state().queries,
2770
2820
  argsObject
2771
2821
  ], () => {
2772
- for (let i = 0; i < combined._rawResults.length; i++) {
2773
- const pageQuery = combined._rawResults[i];
2822
+ const results = pageResults();
2823
+ for (let i = 0; i < results.length; i++) {
2824
+ const pageQuery = results[i];
2774
2825
  if (pageQuery.data) {
2775
2826
  const page = pageQuery.data;
2776
2827
  const pageKey = state().pageKeys[i];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kitcn",
3
- "version": "0.17.1",
3
+ "version": "0.17.2",
4
4
  "description": "kitcn - React Query integration and CLI tools for Convex",
5
5
  "keywords": [
6
6
  "convex",