@xnetjs/react 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -581,8 +581,15 @@ interface UseGridDatabaseOptions {
581
581
  viewId?: string;
582
582
  /** Quick-find text (full-text search via SQLite FTS) */
583
583
  search?: string;
584
- /** Row window size (default 500) */
584
+ /** Rows fetched per window grow (default 500) */
585
585
  pageSize?: number;
586
+ /**
587
+ * Ceiling on the live row window (default 2000). The window grows by
588
+ * `pageSize` via `fetchMoreRows` until this bound; beyond it further
589
+ * fetches are no-ops so the overfetch buffer and live snapshot stay
590
+ * bounded (exploration 0340).
591
+ */
592
+ maxLoaded?: number;
586
593
  }
587
594
  interface UseGridDatabaseResult {
588
595
  /** Database node (flattened) */
@@ -601,6 +608,14 @@ interface UseGridDatabaseResult {
601
608
  /** Rows: view filters + sorts applied to the fetched window */
602
609
  rows: GridRowModel[];
603
610
  loading: boolean;
611
+ /** Exact matching row count for the whole database (null while unknown). */
612
+ totalRowCount: number | null;
613
+ /** Whether the row window can grow further (more rows exist past it). */
614
+ hasMoreRows: boolean;
615
+ /** Whether a window grow is awaiting the larger read. */
616
+ isFetchingMoreRows: boolean;
617
+ /** Grow the row window by `pageSize` (bounded by `maxLoaded`). */
618
+ fetchMoreRows: () => Promise<void>;
604
619
  updateCell: (rowId: string, fieldId: string, value: CellValue) => Promise<void>;
605
620
  clearCells: (cells: Array<{
606
621
  rowId: string;
package/dist/index.js CHANGED
@@ -4377,7 +4377,7 @@ function dropSortKey(ordered, movedId, targetIndex) {
4377
4377
  }
4378
4378
  var formulaService = new FormulaService();
4379
4379
  function useGridDatabase(databaseId, options = {}) {
4380
- const { viewId, search, pageSize = 500 } = options;
4380
+ const { viewId, search, pageSize = 500, maxLoaded = 2e3 } = options;
4381
4381
  const mutate = useMutate();
4382
4382
  const { did } = useIdentity();
4383
4383
  const { data: database, status: dbStatus } = useQuery(DatabaseSchema, databaseId);
@@ -4393,10 +4393,22 @@ function useGridDatabase(databaseId, options = {}) {
4393
4393
  where: { database: databaseId },
4394
4394
  orderBy: { sortKey: "asc" }
4395
4395
  });
4396
- const { data: rowNodes, status: rowStatus } = useQuery(DatabaseRowSchema, {
4396
+ const {
4397
+ data: rowNodes,
4398
+ // Masked while a window grow is in flight (previous rows stay visible):
4399
+ // consumers must not fall back to a skeleton mid-grow or scroll position
4400
+ // is lost — `isFetchingMoreRows` reports the in-flight grow instead.
4401
+ loading: rowsLoading,
4402
+ totalCount: totalRowCount,
4403
+ hasMore: hasMoreRows,
4404
+ isFetchingNextPage: isFetchingMoreRows,
4405
+ fetchNextPage: fetchMoreRows
4406
+ } = useInfiniteQuery(DatabaseRowSchema, {
4397
4407
  where: { database: databaseId },
4398
4408
  orderBy: { sortKey: "asc" },
4399
- limit: pageSize,
4409
+ pageSize,
4410
+ maxLoaded,
4411
+ page: { count: "exact" },
4400
4412
  materializedView: `db:${databaseId}${viewId ? `:view:${viewId}` : ""}`,
4401
4413
  ...search ? { search } : {}
4402
4414
  });
@@ -4484,11 +4496,22 @@ function useGridDatabase(databaseId, options = {}) {
4484
4496
  cancelled = true;
4485
4497
  };
4486
4498
  }, [rowNodes, fields, store]);
4499
+ const rowModelCacheRef = useRef4(
4500
+ { deps: [], map: /* @__PURE__ */ new WeakMap() }
4501
+ );
4487
4502
  const rows = useMemo5(() => {
4503
+ const cacheDeps = [fields, rollupValues, databaseId];
4504
+ if (cacheDeps.length !== rowModelCacheRef.current.deps.length || cacheDeps.some((d, i) => d !== rowModelCacheRef.current.deps[i])) {
4505
+ rowModelCacheRef.current = { deps: cacheDeps, map: /* @__PURE__ */ new WeakMap() };
4506
+ }
4507
+ const cache = rowModelCacheRef.current.map;
4488
4508
  const autoFields = fields.filter(
4489
4509
  (f) => ["created", "createdBy", "updated", "updatedBy"].includes(f.type)
4490
4510
  );
4491
- const base = (rowNodes ?? []).map((node) => {
4511
+ const formulaFields = fields.filter((f) => f.type === "formula");
4512
+ const rollupFields = fields.filter((f) => f.type === "rollup");
4513
+ const columnDefs = formulaFields.length > 0 || activeView ? fieldsToColumnDefinitions(fields) : [];
4514
+ const buildRow = (node) => {
4492
4515
  const cells = fromCellProperties(node);
4493
4516
  for (const field of autoFields) {
4494
4517
  switch (field.type) {
@@ -4506,47 +4529,43 @@ function useGridDatabase(databaseId, options = {}) {
4506
4529
  break;
4507
4530
  }
4508
4531
  }
4509
- return {
4532
+ const row = {
4510
4533
  id: node.id,
4511
4534
  sortKey: node.sortKey ?? "",
4512
4535
  cells
4513
4536
  };
4514
- });
4515
- const formulaFields = fields.filter((f) => f.type === "formula");
4516
- if (formulaFields.length > 0) {
4517
- const columnDefs = fieldsToColumnDefinitions(fields);
4518
- for (const row of base) {
4519
- for (const field of formulaFields) {
4520
- const columnDef = columnDefs.find((c) => c.id === field.id);
4521
- if (!columnDef) continue;
4522
- try {
4523
- row.cells[field.id] = formulaService.compute(
4524
- { id: row.id, databaseId, cells: row.cells },
4525
- columnDef,
4526
- columnDefs
4527
- );
4528
- } catch {
4529
- row.cells[field.id] = null;
4530
- }
4537
+ for (const field of formulaFields) {
4538
+ const columnDef = columnDefs.find((c) => c.id === field.id);
4539
+ if (!columnDef) continue;
4540
+ try {
4541
+ row.cells[field.id] = formulaService.compute(
4542
+ { id: row.id, databaseId, cells: row.cells },
4543
+ columnDef,
4544
+ columnDefs
4545
+ );
4546
+ } catch {
4547
+ row.cells[field.id] = null;
4531
4548
  }
4532
4549
  }
4533
- }
4534
- if (rollupValues.size > 0) {
4535
- for (const row of base) {
4536
- for (const field of fields) {
4537
- if (field.type !== "rollup") continue;
4538
- const computed = rollupValues.get(`${row.id}:${field.id}`);
4539
- if (computed !== void 0) row.cells[field.id] = computed;
4540
- }
4550
+ for (const field of rollupFields) {
4551
+ const computed = rollupValues.get(`${row.id}:${field.id}`);
4552
+ if (computed !== void 0) row.cells[field.id] = computed;
4541
4553
  }
4542
- }
4554
+ return row;
4555
+ };
4556
+ const base = (rowNodes ?? []).map((node) => {
4557
+ const hit = cache.get(node);
4558
+ if (hit) return hit;
4559
+ const row = buildRow(node);
4560
+ cache.set(node, row);
4561
+ return row;
4562
+ });
4543
4563
  base.sort((a, b) => compareSortKeys(a.sortKey, b.sortKey));
4544
4564
  if (!activeView) return base;
4545
- const columns = fieldsToColumnDefinitions(fields);
4546
- const filtered = filterRows(base, columns, activeView.filters);
4547
- return sortRows(filtered, columns, activeView.sorts);
4565
+ const filtered = filterRows(base, columnDefs, activeView.filters);
4566
+ return sortRows(filtered, columnDefs, activeView.sorts);
4548
4567
  }, [rowNodes, activeView, fields, databaseId, rollupValues]);
4549
- const loading = dbStatus === "loading" || fieldStatus === "loading" || viewStatus === "loading" || rowStatus === "loading";
4568
+ const loading = dbStatus === "loading" || fieldStatus === "loading" || viewStatus === "loading" || rowsLoading;
4550
4569
  const rowTailRef = useRef4(void 0);
4551
4570
  const fieldTailRef = useRef4(void 0);
4552
4571
  const viewTailRef = useRef4(void 0);
@@ -4899,6 +4918,10 @@ function useGridDatabase(databaseId, options = {}) {
4899
4918
  activeView,
4900
4919
  rows,
4901
4920
  loading,
4921
+ totalRowCount,
4922
+ hasMoreRows,
4923
+ isFetchingMoreRows,
4924
+ fetchMoreRows,
4902
4925
  updateCell,
4903
4926
  clearCells,
4904
4927
  addRow,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xnetjs/react",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -46,15 +46,15 @@
46
46
  "y-protocols": "^1.0.6",
47
47
  "yjs": "^13.6.24",
48
48
  "@xnetjs/billing": "0.0.2",
49
- "@xnetjs/core": "2.0.0",
50
- "@xnetjs/crypto": "2.0.0",
51
- "@xnetjs/data": "2.0.0",
52
- "@xnetjs/data-bridge": "2.0.0",
53
- "@xnetjs/history": "2.0.0",
54
- "@xnetjs/identity": "2.0.0",
55
- "@xnetjs/plugins": "2.0.0",
56
- "@xnetjs/runtime": "0.5.0",
57
- "@xnetjs/sync": "2.0.0"
49
+ "@xnetjs/core": "2.1.0",
50
+ "@xnetjs/crypto": "2.1.0",
51
+ "@xnetjs/data": "2.1.0",
52
+ "@xnetjs/data-bridge": "2.1.0",
53
+ "@xnetjs/history": "2.1.0",
54
+ "@xnetjs/identity": "2.1.0",
55
+ "@xnetjs/plugins": "2.1.0",
56
+ "@xnetjs/runtime": "0.5.1",
57
+ "@xnetjs/sync": "2.1.0"
58
58
  },
59
59
  "peerDependencies": {
60
60
  "react": "^18.0.0 || ^19.0.0"