@ai-matrx/records 0.80.7 → 0.81.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.
@@ -18,6 +18,10 @@ function registry() {
18
18
  return g[REGISTRY] ??= /* @__PURE__ */ new Map();
19
19
  }
20
20
  var primedKey = (organizationId, tableId, viewId) => `${organizationId}|${tableId}|${viewId ?? ""}`;
21
+ function primeTablePageBundle(input) {
22
+ const answer = Promise.resolve(input.answer).catch((thrown) => ({ data: null, error: thrown }));
23
+ registry().set(primedKey(input.organizationId, input.tableId, input.viewId), { answer, at: Date.now() });
24
+ }
21
25
  function takePrimedTablePageBundle(organizationId, tableId, viewId) {
22
26
  if (!organizationId) return null;
23
27
  const key = primedKey(organizationId, tableId, viewId);
@@ -27,6 +31,9 @@ function takePrimedTablePageBundle(organizationId, tableId, viewId) {
27
31
  primed.delete(key);
28
32
  return Date.now() - held.at < PRIMED_TTL_MS ? held.answer : null;
29
33
  }
34
+ function primeRecordPageBundle(input) {
35
+ primeTablePageBundle({ organizationId: input.organizationId, tableId: `record:${input.recordId}`, answer: input.answer });
36
+ }
30
37
 
31
38
  // src/core/recordChanges.ts
32
39
  var listeners = /* @__PURE__ */ new Set();
@@ -144,8 +151,8 @@ function liftWithheld(rows) {
144
151
  }
145
152
 
146
153
  // src/subscription.ts
147
- function hasQuietHours(quiet) {
148
- return Boolean(quiet?.start && quiet?.end);
154
+ function hasQuietHours(quiet2) {
155
+ return Boolean(quiet2?.start && quiet2?.end);
149
156
  }
150
157
  function callersTimeZone() {
151
158
  try {
@@ -7569,7 +7576,7 @@ function useRecordsActor() {
7569
7576
  }
7570
7577
 
7571
7578
  // src/react/hooks.ts
7572
- import { useCallback, useEffect as useEffect2, useId, useMemo as useMemo2, useRef as useRef2, useState, useSyncExternalStore } from "react";
7579
+ import { use, useCallback, useEffect as useEffect3, useId, useMemo as useMemo2, useRef as useRef2, useState, useSyncExternalStore } from "react";
7573
7580
 
7574
7581
  // src/react/dataCache.ts
7575
7582
  var RECORDS_CACHE_GRACE_MS = 3e4;
@@ -7722,6 +7729,12 @@ function releaseEntry(handle) {
7722
7729
  entry.refs = Math.max(0, entry.refs - 1);
7723
7730
  if (entry.refs === 0) scheduleClose(entry);
7724
7731
  }
7732
+ function seedEntry(handle, data, stale) {
7733
+ const entry = handle;
7734
+ if (entry.started || entry.snapshot.data !== null) return;
7735
+ entry.snapshot = { data, loading: false, error: null };
7736
+ entry.started = !stale;
7737
+ }
7725
7738
  function openHold(holdKey) {
7726
7739
  const holds = root().holds;
7727
7740
  let hold = holds.get(holdKey);
@@ -7752,17 +7765,129 @@ function recordsCacheCounters() {
7752
7765
  return { ...root().counters };
7753
7766
  }
7754
7767
 
7755
- // src/react/hold.tsx
7768
+ // src/react/seed.tsx
7756
7769
  import { createContext as createContext2, createElement as createElement2, useContext as useContext2, useEffect } from "react";
7757
- var HoldContext = createContext2(null);
7770
+
7771
+ // src/core/seed.ts
7772
+ var BUNDLE_DOORS = /* @__PURE__ */ new Set(["table_page_bundle", "record_page_bundle"]);
7773
+ function seedKey(door, args) {
7774
+ return `${door}|${canonicalArgs(args)}`;
7775
+ }
7776
+ function answersOf(door, args, data) {
7777
+ const out = [{ door, args, data }];
7778
+ if (!BUNDLE_DOORS.has(door) || !data || typeof data !== "object") return out;
7779
+ const parts = data.parts;
7780
+ if (!Array.isArray(parts)) return out;
7781
+ for (const part of parts) {
7782
+ if (part.error !== void 0 || part.data === void 0) continue;
7783
+ if (typeof part.door !== "string" || !part.args || typeof part.args !== "object") continue;
7784
+ out.push({ door: part.door, args: part.args, data: part.data });
7785
+ }
7786
+ return out;
7787
+ }
7788
+ function seedIndex(seed) {
7789
+ const index = /* @__PURE__ */ new Map();
7790
+ for (const a of seed.answers) {
7791
+ for (const spread of answersOf(a.door, a.args, a.data)) index.set(seedKey(spread.door, spread.args), spread.data);
7792
+ }
7793
+ return index;
7794
+ }
7795
+ var MISS = { code: "SEED_MISS", message: "Not in the page's seed.", details: null, hint: null };
7796
+ function missingQuery(onMiss) {
7797
+ const answer = () => {
7798
+ onMiss();
7799
+ return Promise.resolve({ data: null, error: MISS, count: null });
7800
+ };
7801
+ const chain = new Proxy(function() {
7802
+ }, {
7803
+ get(_target, prop) {
7804
+ if (prop === "then") return (ok2, bad) => answer().then(ok2, bad);
7805
+ return () => chain;
7806
+ },
7807
+ apply: () => chain
7808
+ });
7809
+ return { select: () => chain };
7810
+ }
7811
+ function seededDataSource(index) {
7812
+ let missed = false;
7813
+ const source = {
7814
+ rpc(fn, args) {
7815
+ const key = seedKey(fn, args);
7816
+ if (index.has(key)) return Promise.resolve({ data: index.get(key), error: null });
7817
+ missed = true;
7818
+ return Promise.resolve({ data: null, error: MISS });
7819
+ },
7820
+ schema: () => ({ from: () => missingQuery(() => missed = true) })
7821
+ };
7822
+ return { source, missed: () => missed };
7823
+ }
7824
+
7825
+ // src/react/seed.tsx
7826
+ var SEED_FRESH_MS = 3e4;
7827
+ var stores = /* @__PURE__ */ new WeakMap();
7828
+ var quiet = () => {
7829
+ };
7830
+ function storeFor(seed) {
7831
+ const held = stores.get(seed);
7832
+ if (held) return held;
7833
+ const index = seedIndex(seed);
7834
+ const runs = /* @__PURE__ */ new Map();
7835
+ const store = {
7836
+ live: true,
7837
+ run(slot, run, config) {
7838
+ const asked = runs.get(slot);
7839
+ if (asked) return asked;
7840
+ const { source, missed } = seededDataSource(index);
7841
+ const { realtime: _live, ...quietConfig } = config;
7842
+ const client = createRecordsClient({ ...quietConfig, dataSource: source, onError: quiet });
7843
+ const stale = typeof seed.at !== "number" || Date.now() - seed.at > SEED_FRESH_MS;
7844
+ const outcome = Promise.resolve().then(() => run(client)).then(
7845
+ (answer) => answer.ok && !missed() ? { seeded: true, data: answer.data, stale } : { seeded: false },
7846
+ () => ({ seeded: false })
7847
+ );
7848
+ runs.set(slot, outcome);
7849
+ return outcome;
7850
+ }
7851
+ };
7852
+ stores.set(seed, store);
7853
+ if (typeof window !== "undefined") {
7854
+ for (const a of seed.answers) {
7855
+ const org = a.args["p_organization_id"];
7856
+ if (typeof org !== "string") continue;
7857
+ if (a.door === "table_page_bundle" && typeof a.args["p_table_id"] === "string") {
7858
+ const view = a.args["p_view_id"];
7859
+ primeTablePageBundle({ organizationId: org, tableId: a.args["p_table_id"], viewId: typeof view === "string" ? view : null, answer: { data: a.data, error: null } });
7860
+ } else if (a.door === "record_page_bundle" && typeof a.args["p_record_id"] === "string") {
7861
+ primeRecordPageBundle({ organizationId: org, recordId: a.args["p_record_id"], answer: { data: a.data, error: null } });
7862
+ }
7863
+ }
7864
+ }
7865
+ return store;
7866
+ }
7867
+ var SeedContext = createContext2(null);
7868
+ function RecordsSeedProvider({ seed, children }) {
7869
+ const store = seed && Array.isArray(seed.answers) ? storeFor(seed) : null;
7870
+ useEffect(() => {
7871
+ if (store) store.live = false;
7872
+ }, [store]);
7873
+ return createElement2(SeedContext.Provider, { value: store }, children);
7874
+ }
7875
+ function useSeedStore() {
7876
+ const store = useContext2(SeedContext);
7877
+ return store && store.live ? store : null;
7878
+ }
7879
+
7880
+ // src/react/hold.tsx
7881
+ import { createContext as createContext3, createElement as createElement3, useContext as useContext3, useEffect as useEffect2 } from "react";
7882
+ var HoldContext = createContext3(null);
7758
7883
  function RecordsHoldScope({ holdKey, children }) {
7759
- return createElement2(HoldContext.Provider, { value: holdKey }, children);
7884
+ return createElement3(HoldContext.Provider, { value: holdKey }, children);
7760
7885
  }
7761
7886
  function useRecordsHold(holdKey) {
7762
- useEffect(() => holdKey ? openHold(holdKey) : void 0, [holdKey]);
7887
+ useEffect2(() => holdKey ? openHold(holdKey) : void 0, [holdKey]);
7763
7888
  }
7764
7889
  function useHoldKey() {
7765
- return useContext2(HoldContext);
7890
+ return useContext3(HoldContext);
7766
7891
  }
7767
7892
 
7768
7893
  // src/react/hooks.ts
@@ -7777,12 +7902,17 @@ function useAsync(run, key, live) {
7777
7902
  );
7778
7903
  entry.run = run;
7779
7904
  entry.client = client;
7905
+ const seed = useSeedStore();
7906
+ if (seed && entry.snapshot.data === null && !entry.snapshot.error && !entry.started) {
7907
+ const outcome = use(seed.run(`${identityOf(client)}|${key}`, run, client.config));
7908
+ if (outcome.seeded) seedEntry(entry, outcome.data, outcome.stale);
7909
+ }
7780
7910
  const snapshot = useSyncExternalStore(entry.subscribe, entry.getSnapshot, entry.getSnapshot);
7781
- useEffect2(() => {
7911
+ useEffect3(() => {
7782
7912
  acquireEntry(entry);
7783
7913
  return () => releaseEntry(entry);
7784
7914
  }, [entry]);
7785
- useEffect2(() => {
7915
+ useEffect3(() => {
7786
7916
  if (holdKey) pinToHold(holdKey, slot, entry);
7787
7917
  }, [holdKey, slot, entry]);
7788
7918
  const lastData = useRef2(null);
@@ -7792,25 +7922,33 @@ function useAsync(run, key, live) {
7792
7922
  const setData = useCallback((update) => entry.setData(update), [entry]);
7793
7923
  return { ...shown, reload, setData };
7794
7924
  }
7925
+ function useSeededAnswer(key, run) {
7926
+ const client = useRecordsClient();
7927
+ const seed = useSeedStore();
7928
+ if (!seed || key === null) return void 0;
7929
+ const outcome = use(seed.run(`${identityOf(client)}|own:${key}`, run, client.config));
7930
+ return outcome.seeded ? outcome.data : void 0;
7931
+ }
7795
7932
  function serverEntry(run, client) {
7796
- const snapshot = { data: null, loading: true, error: null };
7797
- return {
7933
+ const entry = {
7798
7934
  key: "server",
7799
- snapshot,
7935
+ snapshot: { data: null, loading: true, error: null },
7800
7936
  run,
7801
7937
  client,
7802
7938
  subscribe: () => () => {
7803
7939
  },
7804
- getSnapshot: () => snapshot,
7940
+ // Read through the entry: a page seed may answer it during this render (`seedEntry`).
7941
+ getSnapshot: () => entry.snapshot,
7805
7942
  refetch: () => {
7806
7943
  },
7807
7944
  setData: () => {
7808
7945
  }
7809
7946
  };
7947
+ return entry;
7810
7948
  }
7811
7949
  function useTable(tableId) {
7812
7950
  const revision = useShapeRevision(tableId);
7813
- useEffect2(() => {
7951
+ useEffect3(() => {
7814
7952
  if (tableId) registerShapeInterest(tableId);
7815
7953
  }, [tableId]);
7816
7954
  return useAsync(
@@ -7872,7 +8010,7 @@ function useShapeRevision(tableId) {
7872
8010
  }
7873
8011
  function useFields(tableId, recordType) {
7874
8012
  const revision = useShapeRevision(tableId);
7875
- useEffect2(() => {
8013
+ useEffect3(() => {
7876
8014
  if (tableId) registerShapeInterest(tableId);
7877
8015
  }, [tableId]);
7878
8016
  return useAsync(
@@ -7908,7 +8046,7 @@ function useReverseColumns(tableId) {
7908
8046
  }
7909
8047
  function useReverseLinks(tableId, columns, recordIds, limit = 3, offset = 0) {
7910
8048
  const [writes, setWrites] = useState(0);
7911
- useEffect2(() => onRecordChanged(() => setWrites((n) => n + 1)), []);
8049
+ useEffect3(() => onRecordChanged(() => setWrites((n) => n + 1)), []);
7912
8050
  const idsKey = recordIds.join(",");
7913
8051
  const columnsKey = columns.map((c) => c.source_field_id).join(",");
7914
8052
  return useAsync(
@@ -8096,7 +8234,7 @@ function useRecordPage(tableId, options = {}) {
8096
8234
  }
8097
8235
  function useRecordChangeRevision(recordId) {
8098
8236
  const [revision, setRevision] = useState(0);
8099
- useEffect2(() => {
8237
+ useEffect3(() => {
8100
8238
  if (!recordId) return;
8101
8239
  return onRecordChanged((changed) => {
8102
8240
  if (changed === recordId) setRevision((n) => n + 1);
@@ -8467,7 +8605,7 @@ function useMergeField(key, state) {
8467
8605
  const client = useRecordsClient();
8468
8606
  const [resolution, setResolution] = useState(null);
8469
8607
  const resolver = client.config.resolver;
8470
- useEffect2(() => {
8608
+ useEffect3(() => {
8471
8609
  if (!key) {
8472
8610
  setResolution(null);
8473
8611
  return;
@@ -8502,11 +8640,11 @@ function useMergeField(key, state) {
8502
8640
  }
8503
8641
 
8504
8642
  // src/react/entity-reference.ts
8505
- import { useEffect as useEffect3, useState as useState2 } from "react";
8643
+ import { useEffect as useEffect4, useState as useState2 } from "react";
8506
8644
  function useEntityReferenceKinds() {
8507
8645
  const client = useRecordsClient();
8508
8646
  const [state, setState] = useState2({ data: null, loading: true, error: null });
8509
- useEffect3(() => {
8647
+ useEffect4(() => {
8510
8648
  let live = true;
8511
8649
  void entityReferenceKinds(client.config.dataSource).then((res) => {
8512
8650
  if (!live) return;
@@ -8522,7 +8660,7 @@ function useEntityReferenceWords(refs) {
8522
8660
  const client = useRecordsClient();
8523
8661
  const key = refs.map((r) => `${r.token}:${r.id}`).join(",");
8524
8662
  const [state, setState] = useState2({ data: null, loading: refs.length > 0, error: null });
8525
- useEffect3(() => {
8663
+ useEffect4(() => {
8526
8664
  let live = true;
8527
8665
  void entityReferenceWords(client.config.dataSource, client.config.organizationId, refs).then((res) => {
8528
8666
  if (!live) return;
@@ -8538,7 +8676,7 @@ function useEntityReferenceSearch(tokens, search, enabled = true) {
8538
8676
  const client = useRecordsClient();
8539
8677
  const key = `${tokens.join(",")}|${search.trim()}|${enabled ? 1 : 0}`;
8540
8678
  const [state, setState] = useState2({ data: null, loading: enabled && tokens.length > 0, error: null });
8541
- useEffect3(() => {
8679
+ useEffect4(() => {
8542
8680
  if (!enabled || tokens.length === 0) {
8543
8681
  setState({ data: [], loading: false, error: null });
8544
8682
  return;
@@ -8560,7 +8698,7 @@ function useEntityReferenceSearch(tokens, search, enabled = true) {
8560
8698
  }
8561
8699
 
8562
8700
  // src/react/useAppTable.ts
8563
- import { useCallback as useCallback2, useEffect as useEffect4, useRef as useRef3, useState as useState3 } from "react";
8701
+ import { useCallback as useCallback2, useEffect as useEffect5, useRef as useRef3, useState as useState3 } from "react";
8564
8702
 
8565
8703
  // src/core/declareTable.ts
8566
8704
  function tokenFor(name) {
@@ -8864,7 +9002,7 @@ function useAppTable(def, options = {}) {
8864
9002
  const [revision, setRevision] = useState3(0);
8865
9003
  const rowsRef = useRef3([]);
8866
9004
  rowsRef.current = rows;
8867
- useEffect4(() => {
9005
+ useEffect5(() => {
8868
9006
  let live = true;
8869
9007
  setLoading(true);
8870
9008
  (async () => {
@@ -8925,7 +9063,7 @@ function archivedTableOf(error) {
8925
9063
  }
8926
9064
 
8927
9065
  // src/react/useStoreTable.ts
8928
- import { useEffect as useEffect6, useMemo as useMemo4 } from "react";
9066
+ import { useEffect as useEffect7, useMemo as useMemo4 } from "react";
8929
9067
 
8930
9068
  // src/app-table/store-table.ts
8931
9069
  function storeTable(def) {
@@ -10160,7 +10298,7 @@ function createTableDataPort({ client, aliases }) {
10160
10298
  }
10161
10299
 
10162
10300
  // src/data/react.tsx
10163
- import { createContext as createContext3, createElement as createElement3, useContext as useContext3, useEffect as useEffect5, useMemo as useMemo3, useRef as useRef4 } from "react";
10301
+ import { createContext as createContext4, createElement as createElement4, useContext as useContext4, useEffect as useEffect6, useMemo as useMemo3, useRef as useRef4 } from "react";
10164
10302
  import {
10165
10303
  QueryClient as QueryClientClass,
10166
10304
  QueryClientContext,
@@ -10214,13 +10352,13 @@ function retryable(failureCount, thrown) {
10214
10352
  const code = thrown instanceof DataRefusal ? thrown.error.code : "unreachable";
10215
10353
  return (code === "unreachable" || code === "timed_out") && failureCount < 2;
10216
10354
  }
10217
- var DataPortContext = createContext3(null);
10355
+ var DataPortContext = createContext4(null);
10218
10356
  function hostEvents(port) {
10219
10357
  return { on: (event, listener) => port.subscribe(event, listener) };
10220
10358
  }
10221
10359
  var warnedNotLive = false;
10222
10360
  function useDataPort(scope) {
10223
- const held = useContext3(DataPortContext);
10361
+ const held = useContext4(DataPortContext);
10224
10362
  if (scope) return scope;
10225
10363
  if (!held) {
10226
10364
  throw new Error(
@@ -10506,7 +10644,7 @@ function acquireLive(qc, port, events, portKey, alias) {
10506
10644
  }
10507
10645
  var FALLBACK_KEY = /* @__PURE__ */ Symbol.for("@ai-matrx/records/data-query-client");
10508
10646
  function useRecordsQueryClient() {
10509
- const provided = useContext3(QueryClientContext);
10647
+ const provided = useContext4(QueryClientContext);
10510
10648
  if (provided) return provided;
10511
10649
  const g = globalThis;
10512
10650
  let held = g[FALLBACK_KEY];
@@ -10519,7 +10657,7 @@ function useRecordsQueryClient() {
10519
10657
  function useLive(alias, scope) {
10520
10658
  const qc = useRecordsQueryClient();
10521
10659
  const { port, portKey, events } = useDataPort(scope);
10522
- useEffect5(() => {
10660
+ useEffect6(() => {
10523
10661
  if (!alias) return;
10524
10662
  return acquireLive(qc, port, events, portKey, alias);
10525
10663
  }, [qc, port, events, portKey, alias]);
@@ -10555,7 +10693,7 @@ function useDataRows(alias, query = {}, scope) {
10555
10693
  const keyHash = hashKey(rowsKey);
10556
10694
  const loaded = q.data?.pages.length ?? 0;
10557
10695
  const { isFetching, hasNextPage, isFetchNextPageError, fetchNextPage } = q;
10558
- useEffect5(() => {
10696
+ useEffect6(() => {
10559
10697
  const w = want.current;
10560
10698
  if (w.key !== keyHash || isFetching || loaded >= w.pages) return;
10561
10699
  if (!hasNextPage || isFetchNextPageError) {
@@ -10732,7 +10870,7 @@ function useStoreTable(table, options = {}) {
10732
10870
  const limit = Math.min(options.limit ?? STORE_ROWS_CEILING, STORE_ROWS_CEILING);
10733
10871
  const read = useDataRows(alias, built.ok ? built.data : { pageSize: 1 }, scope);
10734
10872
  const write = useDataMutations(alias, scope);
10735
- useEffect6(() => {
10873
+ useEffect7(() => {
10736
10874
  if (built.ok && read.hasMore && !read.loadingMore && !read.loading && !read.error && read.rows.length < limit) read.loadMore();
10737
10875
  }, [built.ok, read.hasMore, read.loadingMore, read.loading, read.error, read.rows.length, limit, read]);
10738
10876
  const rows = useMemo4(() => read.rows.slice(0, limit).filter((r) => r["_pending"] !== true).map((r) => toNamed(table, r)), [read.rows, limit, table]);
@@ -12203,6 +12341,8 @@ export {
12203
12341
  RULE_USES,
12204
12342
  RecordsHoldScope,
12205
12343
  RecordsProvider,
12344
+ RecordsSeedProvider,
12345
+ SEED_FRESH_MS,
12206
12346
  SHOWN_TO_LABEL,
12207
12347
  SHOWN_TO_VALUES,
12208
12348
  SHOWN_TO_WHO_MAY_CHANGE,
@@ -12394,6 +12534,7 @@ export {
12394
12534
  useReverseColumns,
12395
12535
  useReverseLinks,
12396
12536
  useRowActions,
12537
+ useSeededAnswer,
12397
12538
  useStoreTable,
12398
12539
  useTable,
12399
12540
  useTableArchive,