@elaraai/e3-ui-components 1.0.68 → 1.0.70

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.cjs CHANGED
@@ -26,6 +26,7 @@ const internal$1 = require("@elaraai/e3-ui/internal");
26
26
  const platform = require("@elaraai/east-ui-components/platform");
27
27
  const e3Types = require("@elaraai/e3-types");
28
28
  const e3ApiClient = require("@elaraai/e3-api-client");
29
+ const eastUi = require("@elaraai/east-ui");
29
30
  const jsxRuntime = require("react/jsx-runtime");
30
31
  const React = require("react");
31
32
  const react = require("@chakra-ui/react");
@@ -33,7 +34,6 @@ const eastUiComponents = require("@elaraai/east-ui-components");
33
34
  const reactDom = require("react-dom");
34
35
  const internal$2 = require("@elaraai/east-ui/internal");
35
36
  const reactQuery = require("@tanstack/react-query");
36
- const eastUi = require("@elaraai/east-ui");
37
37
  const reactVirtual = require("@tanstack/react-virtual");
38
38
  function _interopNamespaceDefault(e3) {
39
39
  const n2 = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
@@ -3611,6 +3611,14 @@ class TrackedChannelStore {
3611
3611
  isTracking() {
3612
3612
  return this.trackingContext !== null;
3613
3613
  }
3614
+ /** Whether the CURRENT evaluation has already read this channel. A cache
3615
+ * that evicts by least-recently-read must never drop one of these: a read
3616
+ * pass longer than the cache would otherwise evict its own head, and the
3617
+ * next pass would find it gone (#581). */
3618
+ isTracked(key) {
3619
+ var _a2;
3620
+ return ((_a2 = this.trackingContext) == null ? void 0 : _a2.has(key)) ?? false;
3621
+ }
3614
3622
  /** Record a channel-key dependency for the current render (no-op when not
3615
3623
  * tracking). */
3616
3624
  track(key) {
@@ -3692,6 +3700,397 @@ class TrackedChannelStore {
3692
3700
  this.keyVersions.clear();
3693
3701
  }
3694
3702
  }
3703
+ function createDefaultPagedApi(apiUrl, repo, getToken) {
3704
+ const opts = () => ({ token: getToken() });
3705
+ return {
3706
+ async getPage(workspace, path2, window3) {
3707
+ return e3ApiClient.datasetGetPage(apiUrl, repo, workspace, path2, window3, opts());
3708
+ },
3709
+ async findKey(workspace, path2, query) {
3710
+ return e3ApiClient.datasetFindKey(apiUrl, repo, workspace, path2, query, opts());
3711
+ }
3712
+ };
3713
+ }
3714
+ const RETRY_AFTER_MS = 2e3;
3715
+ const MAX_RETAINED_WINDOWS = 24;
3716
+ const PERMANENT_PAGE_ERRORS = /* @__PURE__ */ new Set(["dataset_not_pageable"]);
3717
+ function isPermanentPageError(err) {
3718
+ const code = err == null ? void 0 : err.code;
3719
+ return typeof code === "string" && PERMANENT_PAGE_ERRORS.has(code);
3720
+ }
3721
+ function keyTypeOf(sourceType) {
3722
+ if (sourceType.type === "Dict") return sourceType.value.key;
3723
+ if (sourceType.type === "Set") return sourceType.value;
3724
+ return null;
3725
+ }
3726
+ function buildSeek(sourceType, T2, pathExpr, platform2) {
3727
+ if (keyTypeOf(sourceType) === null) return east.none;
3728
+ const { seek } = internal$1.DataPagedPrimitives;
3729
+ return east.some(east.East.compile(
3730
+ east.East.function([eastUi.SeekQueryType], east.OptionType(eastUi.SeekRangeType), ($3, query) => {
3731
+ $3.return(seek([T2], pathExpr, query));
3732
+ }),
3733
+ platform2
3734
+ ));
3735
+ }
3736
+ function pagedWindowKey(workspace, path2, offset, limit2) {
3737
+ return `paged:${workspace}:${datasetPathToString(path2)}#${offset}+${limit2}`;
3738
+ }
3739
+ function pagedTotalKey(workspace, path2) {
3740
+ return `paged:${workspace}:${datasetPathToString(path2)}#total`;
3741
+ }
3742
+ function pagedSeekKey(workspace, path2, query) {
3743
+ const q = "key" in query ? `k=${query.key}` : "fields" in query ? `f=${query.fields.join("\0")}|p=${query.prefix ?? ""}` : `p=${query.prefix}`;
3744
+ return `paged:${workspace}:${datasetPathToString(path2)}#seek:${q}`;
3745
+ }
3746
+ function toFindQuery(query) {
3747
+ const q = query;
3748
+ if (q.type === "key") return { key: q.value };
3749
+ if (q.type === "prefix") return { prefix: q.value };
3750
+ const f2 = q.value;
3751
+ const fields = [...f2.values];
3752
+ return f2.prefix.type === "some" ? { fields, prefix: f2.prefix.value } : { fields };
3753
+ }
3754
+ class PagedRuntime extends TrackedChannelStore {
3755
+ constructor() {
3756
+ super(...arguments);
3757
+ __publicField(this, "api", null);
3758
+ __publicField(this, "workspace", null);
3759
+ // Compiled-handle cache (issue #106 perf): buildHandle compiles 2
3760
+ // East.functions per bind, and binds re-run every reactive frame. The
3761
+ // method IR is a pure function of (sourceType, sourcePath), and the methods
3762
+ // resolve api/workspace LIVE, so a cached handle still re-binds.
3763
+ //
3764
+ // Keyed by TYPE first, then path — never by path alone. The window decoder
3765
+ // is baked from `sourceType`, so a path re-bound at a different type (a
3766
+ // redeployed dataset whose schema changed, inside a live session) must not
3767
+ // hand back the handle compiled against the old type. Structural key for
3768
+ // the same reason `bind-runtime` uses one: every bind builds a fresh
3769
+ // `EastTypeValue` from IR, so a by-identity cache would miss every render.
3770
+ __publicField(this, "handleCache", new east.SortedMap(
3771
+ void 0,
3772
+ east.compareFor(internal.EastTypeType)
3773
+ ));
3774
+ /** Loaded window keys in least-recently-READ order (a Map preserves
3775
+ * insertion order; a read re-inserts). Bounds the decoded-window cache. */
3776
+ __publicField(this, "loadedWindows", /* @__PURE__ */ new Map());
3777
+ }
3778
+ /** Monotonic clock seam so tests can drive the retry gate. */
3779
+ now() {
3780
+ return Date.now();
3781
+ }
3782
+ createEntry() {
3783
+ return { status: "idle", launchSeq: 0 };
3784
+ }
3785
+ // ----- wiring ----------------------------------------------------------
3786
+ /** Install the API adapter + workspace — called by the React provider
3787
+ * (or a test/showcase harness) before any handle is used. */
3788
+ initialize(api3, workspace) {
3789
+ this.api = api3;
3790
+ this.workspace = workspace;
3791
+ }
3792
+ /** Tear down the adapter and all window state. */
3793
+ clear() {
3794
+ this.api = null;
3795
+ this.workspace = null;
3796
+ this.clearChannels();
3797
+ this.handleCache.clear();
3798
+ this.loadedWindows.clear();
3799
+ }
3800
+ resolveWorkspace() {
3801
+ if (!this.workspace) {
3802
+ throw new Error(
3803
+ "Data.bindPaged: no paging service — a paged source is served BY THE SERVER (datasetGetPage), so it resolves only inside a live workspace. Render this component against a deployed workspace, or bind the whole value with Data.bind."
3804
+ );
3805
+ }
3806
+ return this.workspace;
3807
+ }
3808
+ // ----- window loading --------------------------------------------------
3809
+ /** Note a window as most-recently-read, and drop the coldest decoded
3810
+ * windows once the cache exceeds its cap. An evicted window returns to
3811
+ * `idle`, so the next read refetches it rather than reading a hole. */
3812
+ touchWindow(key) {
3813
+ this.loadedWindows.delete(key);
3814
+ this.loadedWindows.set(key, true);
3815
+ if (this.loadedWindows.size <= MAX_RETAINED_WINDOWS) return;
3816
+ for (const candidate of [...this.loadedWindows.keys()]) {
3817
+ if (this.loadedWindows.size <= MAX_RETAINED_WINDOWS) break;
3818
+ if (this.isTracked(candidate)) continue;
3819
+ const entry = this.entries.get(candidate);
3820
+ if (entry === void 0 || entry.status !== "loaded") continue;
3821
+ this.loadedWindows.delete(candidate);
3822
+ entry.status = "idle";
3823
+ delete entry.window;
3824
+ }
3825
+ }
3826
+ /**
3827
+ * Start the fetch for a window if it isn't loaded or already in flight.
3828
+ *
3829
+ * Deliberately does NOT notify on launch — the read that triggers it runs
3830
+ * inside a render pass, and notifying there would re-enter the renderer.
3831
+ * Only the settle notifies.
3832
+ */
3833
+ ensureWindow(sourceType, workspace, path2, offset, limit2) {
3834
+ const key = pagedWindowKey(workspace, path2, offset, limit2);
3835
+ const entry = this.entry(key);
3836
+ if (entry.status === "running" || entry.status === "loaded") return;
3837
+ if (entry.status === "failed") {
3838
+ if (entry.permanent) return;
3839
+ const since = this.now() - (entry.failedAtMs ?? 0);
3840
+ if (since < RETRY_AFTER_MS) return;
3841
+ }
3842
+ entry.status = "running";
3843
+ entry.launchSeq += 1;
3844
+ const mySeq = entry.launchSeq;
3845
+ const api3 = this.api;
3846
+ void (async () => {
3847
+ const settle = (mutate) => {
3848
+ const current = this.entries.get(key);
3849
+ if (!current || current.launchSeq !== mySeq) return;
3850
+ mutate(current);
3851
+ this.notify(key);
3852
+ };
3853
+ if (!api3) {
3854
+ settle((e3) => {
3855
+ e3.status = "failed";
3856
+ e3.failedAtMs = this.now();
3857
+ });
3858
+ console.error("Data.bindPaged: no PagedApi installed");
3859
+ return;
3860
+ }
3861
+ let page;
3862
+ try {
3863
+ page = await api3.getPage(workspace, path2, { offset, limit: limit2 });
3864
+ } catch (err) {
3865
+ const permanent = isPermanentPageError(err);
3866
+ settle((e3) => {
3867
+ e3.status = "failed";
3868
+ e3.failedAtMs = this.now();
3869
+ e3.permanent = permanent;
3870
+ });
3871
+ console.error(
3872
+ permanent ? `Data.bindPaged: ${datasetPathToString(path2)} is not a pageable dataset — bind a collection (Array / Set / Dict), or use Data.bind for a whole value:` : `Data.bindPaged: fetch failed for ${key}:`,
3873
+ err
3874
+ );
3875
+ return;
3876
+ }
3877
+ let decoded;
3878
+ try {
3879
+ decoded = east.decodeBeast2For(sourceType)(page.data);
3880
+ } catch (err) {
3881
+ settle((e3) => {
3882
+ e3.status = "failed";
3883
+ e3.failedAtMs = this.now();
3884
+ });
3885
+ console.error(`Data.bindPaged: decode failed for ${key}:`, err);
3886
+ return;
3887
+ }
3888
+ settle((e3) => {
3889
+ e3.status = "loaded";
3890
+ e3.window = decoded;
3891
+ e3.total = page.totalElements;
3892
+ });
3893
+ this.touchWindow(key);
3894
+ const totalKey = pagedTotalKey(workspace, path2);
3895
+ const totalEntry = this.entry(totalKey);
3896
+ if (totalEntry.total !== page.totalElements) {
3897
+ totalEntry.total = page.totalElements;
3898
+ totalEntry.status = "loaded";
3899
+ this.notify(totalKey);
3900
+ }
3901
+ })();
3902
+ }
3903
+ /**
3904
+ * Start the fence search for one key query if it isn't answered or already
3905
+ * in flight — the seek sibling of {@link ensureWindow}, with the same
3906
+ * launch-does-not-notify rule (the read runs inside a render pass).
3907
+ */
3908
+ ensureSeek(workspace, path2, query, key) {
3909
+ const entry = this.entry(key);
3910
+ if (entry.status === "running" || entry.status === "loaded") return;
3911
+ if (entry.status === "failed") {
3912
+ if (entry.permanent) return;
3913
+ const since = this.now() - (entry.failedAtMs ?? 0);
3914
+ if (since < RETRY_AFTER_MS) return;
3915
+ }
3916
+ entry.status = "running";
3917
+ entry.launchSeq += 1;
3918
+ const mySeq = entry.launchSeq;
3919
+ const api3 = this.api;
3920
+ void (async () => {
3921
+ const settle = (mutate) => {
3922
+ const current = this.entries.get(key);
3923
+ if (!current || current.launchSeq !== mySeq) return;
3924
+ mutate(current);
3925
+ this.notify(key);
3926
+ };
3927
+ if (!api3) {
3928
+ settle((e3) => {
3929
+ e3.status = "failed";
3930
+ e3.failedAtMs = this.now();
3931
+ });
3932
+ console.error("Data.bindPaged: no PagedApi installed");
3933
+ return;
3934
+ }
3935
+ try {
3936
+ const range = await api3.findKey(workspace, path2, query);
3937
+ settle((e3) => {
3938
+ e3.status = "loaded";
3939
+ e3.range = range;
3940
+ });
3941
+ } catch (err) {
3942
+ const permanent = isPermanentPageError(err);
3943
+ settle((e3) => {
3944
+ e3.status = "failed";
3945
+ e3.failedAtMs = this.now();
3946
+ e3.permanent = permanent;
3947
+ });
3948
+ console.error(`Data.bindPaged: key search failed for ${key}:`, err);
3949
+ }
3950
+ })();
3951
+ }
3952
+ /**
3953
+ * The low-level primitives backing handle methods, bound to THIS runtime.
3954
+ * Registered globally (extension registry) and included by the scoped
3955
+ * platform (e3 `ui()` tasks) so a decoded handle re-binds to whatever
3956
+ * runtime resolves the primitives on the decode side.
3957
+ */
3958
+ buildPrimitives() {
3959
+ return [
3960
+ internal$1.DataPagedPrimitives.page.implement((sourceType) => (pathArg, offsetArg, limitArg) => {
3961
+ const workspace = this.resolveWorkspace();
3962
+ const path2 = pathArg;
3963
+ const offset = Number(offsetArg);
3964
+ const limit2 = Number(limitArg);
3965
+ const key = pagedWindowKey(workspace, path2, offset, limit2);
3966
+ this.track(key);
3967
+ this.ensureWindow(sourceType, workspace, path2, offset, limit2);
3968
+ const entry = this.entry(key);
3969
+ if (entry.status === "loaded" && entry.window !== void 0) {
3970
+ this.touchWindow(key);
3971
+ return east.variant("some", entry.window);
3972
+ }
3973
+ return east.variant("none", null);
3974
+ }),
3975
+ internal$1.DataPagedPrimitives.total.implement((_sourceType) => (pathArg) => {
3976
+ const workspace = this.resolveWorkspace();
3977
+ const key = pagedTotalKey(workspace, pathArg);
3978
+ this.track(key);
3979
+ const entry = this.entry(key);
3980
+ return entry.total !== void 0 ? east.variant("some", BigInt(entry.total)) : east.variant("none", null);
3981
+ }),
3982
+ internal$1.DataPagedPrimitives.seek.implement((_sourceType) => (pathArg, queryArg) => {
3983
+ const workspace = this.resolveWorkspace();
3984
+ const path2 = pathArg;
3985
+ const query = toFindQuery(queryArg);
3986
+ const key = pagedSeekKey(workspace, path2, query);
3987
+ this.track(key);
3988
+ this.ensureSeek(workspace, path2, query, key);
3989
+ const entry = this.entry(key);
3990
+ if (entry.status !== "loaded" || entry.range === void 0) return east.none;
3991
+ const r2 = entry.range;
3992
+ return east.some({ found: r2.found, row: BigInt(r2.row), count: BigInt(r2.count) });
3993
+ })
3994
+ ];
3995
+ }
3996
+ /**
3997
+ * Build the handle value for one `Data.bindPaged` evaluation. Both methods
3998
+ * are thin IR-bearing `East.function`s over {@link buildPrimitives},
3999
+ * capturing only the plain-data source path (the value type rides as a
4000
+ * type-arg) — so the handle is ordinary serializable East data (issue #106).
4001
+ */
4002
+ buildHandle(sourceType, path2) {
4003
+ const pathKey = datasetPathToString(path2);
4004
+ let byPath = this.handleCache.get(sourceType);
4005
+ if (byPath) {
4006
+ const hit = byPath.get(pathKey);
4007
+ if (hit) return hit;
4008
+ } else {
4009
+ byPath = /* @__PURE__ */ new Map();
4010
+ this.handleCache.set(sourceType, byPath);
4011
+ }
4012
+ const T2 = east.fromEastTypeValue(sourceType);
4013
+ const pathExpr = east.East.value(path2, e3Types.TreePathType);
4014
+ const platform2 = this.buildPrimitives();
4015
+ const { page, total } = internal$1.DataPagedPrimitives;
4016
+ const handle = {
4017
+ // The comparable identity east-ui's `PagedSourceType` requires:
4018
+ // East compares every function as EQUAL, so a struct of nothing but
4019
+ // closures is indistinguishable from any other and a memoized
4020
+ // component would never re-render on a source swap (#567 D4). The
4021
+ // dataset path is the natural identity — same path, same rows.
4022
+ id: pathKey,
4023
+ page: east.East.compile(
4024
+ east.East.function([east.IntegerType, east.IntegerType], east.OptionType(T2), ($3, offset, limit2) => {
4025
+ $3.return(page([T2], pathExpr, offset, limit2));
4026
+ }),
4027
+ platform2
4028
+ ),
4029
+ total: east.East.compile(
4030
+ east.East.function([], east.OptionType(east.IntegerType), ($3) => {
4031
+ $3.return(total([T2], pathExpr));
4032
+ }),
4033
+ platform2
4034
+ ),
4035
+ // Key search is a KEY-ORDER capability: Set/Dict element windows
4036
+ // ride the canonical East key order, so a key locates in O(log
4037
+ // segments) against the stored fences; an Array's stream order has
4038
+ // nothing to search. Resolved at bind time from the dataset's own
4039
+ // type, so a component renders the affordance only when it works.
4040
+ seek: buildSeek(sourceType, T2, pathExpr, platform2)
4041
+ };
4042
+ byPath.set(pathKey, handle);
4043
+ return handle;
4044
+ }
4045
+ // ----- platform building -------------------------------------------------
4046
+ /** Build a `Data.bindPaged` PlatformFunction bound to this runtime. Pass
4047
+ * `allowed=null` for an unscoped impl; pass a Set of path strings for
4048
+ * manifest scoping. */
4049
+ buildPlatform(allowed) {
4050
+ return internal$1.bindPagedPlatformFn.implement(
4051
+ (sourceType) => (pathArg) => {
4052
+ const path2 = pathArg;
4053
+ if (allowed) {
4054
+ const pathStr = datasetPathToString(path2);
4055
+ if (!allowed.has(pathStr)) {
4056
+ throw new Error(
4057
+ `Data.bindPaged: source path "${pathStr}" not declared in manifest — bind it in the task body so derivation records it`
4058
+ );
4059
+ }
4060
+ }
4061
+ return this.buildHandle(sourceType, path2);
4062
+ }
4063
+ );
4064
+ }
4065
+ }
4066
+ const defaultPagedRuntime = new PagedRuntime();
4067
+ function initializePagedApi(api3, workspace) {
4068
+ defaultPagedRuntime.initialize(api3, workspace);
4069
+ }
4070
+ function clearPagedApi() {
4071
+ defaultPagedRuntime.clear();
4072
+ }
4073
+ const PagedPlatform = [
4074
+ defaultPagedRuntime.buildPlatform(null),
4075
+ ...defaultPagedRuntime.buildPrimitives()
4076
+ ];
4077
+ function createScopedPagedPlatform(pages) {
4078
+ const allowed = new Set(pages.map((p2) => datasetPathToString(p2)));
4079
+ return [
4080
+ defaultPagedRuntime.buildPlatform(allowed),
4081
+ ...defaultPagedRuntime.buildPrimitives()
4082
+ ];
4083
+ }
4084
+ platform.registerReactiveTracker({
4085
+ id: "data-bind-paged",
4086
+ enableTracking: () => defaultPagedRuntime.enableTracking(),
4087
+ disableTracking: () => defaultPagedRuntime.disableTracking(),
4088
+ getStore: () => ({
4089
+ subscribe: (key, cb) => defaultPagedRuntime.subscribe(key, cb),
4090
+ getKeyVersion: (key) => defaultPagedRuntime.getKeyVersion(key)
4091
+ })
4092
+ });
4093
+ platform.registerPlatformImplementation(PagedPlatform);
3695
4094
  function createDefaultFunctionApi(apiUrl, repo, getToken) {
3696
4095
  const opts = () => ({ token: getToken() });
3697
4096
  return {
@@ -8317,6 +8716,14 @@ function ReactiveDatasetProvider({
8317
8716
  );
8318
8717
  }
8319
8718
  }, [e3.apiUrl, e3.repo, e3.workspace]);
8719
+ React.useMemo(() => {
8720
+ if (e3.workspace !== void 0) {
8721
+ initializePagedApi(
8722
+ createDefaultPagedApi(e3.apiUrl, e3.repo ?? "default", () => tokenRef.current),
8723
+ e3.workspace
8724
+ );
8725
+ }
8726
+ }, [e3.apiUrl, e3.repo, e3.workspace]);
8320
8727
  React.useMemo(() => {
8321
8728
  if (e3.workspace !== void 0) {
8322
8729
  initializeRecordApi(
@@ -8331,6 +8738,7 @@ function ReactiveDatasetProvider({
8331
8738
  const ws = cache3.getConfig().workspace;
8332
8739
  clearReactiveDatasetCache();
8333
8740
  clearFunctionApi();
8741
+ clearPagedApi();
8334
8742
  clearRecordApi();
8335
8743
  cache3.destroy();
8336
8744
  if (ws) clearBindingRegistry(ws);
@@ -9290,6 +9698,7 @@ const EastChakraDiff = React.memo(function EastChakraDiff2({ value }) {
9290
9698
  const es = eyebrow({});
9291
9699
  const commit = react.useSlotRecipe({ key: "commitBar" });
9292
9700
  const cs = commit({});
9701
+ const btn = react.useRecipe({ key: "button" });
9293
9702
  const allDatasetPaths = React.useMemo(() => {
9294
9703
  const paths = [];
9295
9704
  for (const b2 of irBindings) {
@@ -9591,12 +10000,12 @@ const EastChakraDiff = React.memo(function EastChakraDiff2({ value }) {
9591
10000
  " · clean merge"
9592
10001
  ] }) }),
9593
10002
  /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { css: cs.btnRow, children: [
9594
- /* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "button", css: cs.btnDanger, onClick: onDiscardAll, children: "Discard all" }),
10003
+ /* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "button", css: btn({ variant: "danger", size: "md" }), onClick: onDiscardAll, children: "Discard all" }),
9595
10004
  /* @__PURE__ */ jsxRuntime.jsx(
9596
10005
  react.Box,
9597
10006
  {
9598
10007
  as: "button",
9599
- css: cs.btnPrimary,
10008
+ css: btn({ variant: "solid", size: "md" }),
9600
10009
  "aria-disabled": applyDisabled,
9601
10010
  onClick: applyDisabled ? void 0 : onApply,
9602
10011
  title: hasStaleLeaves ? "Discard or re-edit the stale changes before applying." : void 0,
@@ -63789,6 +64198,7 @@ const defaultPlatformImplementations = [
63789
64198
  ...eastUiComponents.ShareImpl,
63790
64199
  ...DecisionBindPlatform,
63791
64200
  ...BindPlatform,
64201
+ ...PagedPlatform,
63792
64202
  ...FuncPlatform,
63793
64203
  ...RecordPlatform
63794
64204
  ];
@@ -64006,271 +64416,6 @@ class ErrorBoundary extends React.Component {
64006
64416
  return this.props.children;
64007
64417
  }
64008
64418
  }
64009
- const FIND_DEBOUNCE_MS = 250;
64010
- const POPUP_LIMIT = 20;
64011
- function structFields(keyType) {
64012
- if (keyType.type !== "Struct") return null;
64013
- const fields = keyType.value;
64014
- return fields.length > 0 ? fields : null;
64015
- }
64016
- function splitTopLevel(text) {
64017
- const parts = [];
64018
- let current = "";
64019
- let inString = false;
64020
- let depth = 0;
64021
- for (let i = 0; i < text.length; i++) {
64022
- const ch = text[i];
64023
- if (inString) {
64024
- current += ch;
64025
- if (ch === "\\" && i + 1 < text.length) {
64026
- current += text[i + 1];
64027
- i++;
64028
- } else if (ch === '"') {
64029
- inString = false;
64030
- }
64031
- continue;
64032
- }
64033
- if (ch === '"') {
64034
- inString = true;
64035
- current += ch;
64036
- } else if (ch === "(" || ch === "[" || ch === "{") {
64037
- depth++;
64038
- current += ch;
64039
- } else if (ch === ")" || ch === "]" || ch === "}") {
64040
- depth--;
64041
- current += ch;
64042
- } else if (ch === "," && depth === 0) {
64043
- parts.push(current);
64044
- current = "";
64045
- } else {
64046
- current += ch;
64047
- }
64048
- }
64049
- parts.push(current);
64050
- return parts;
64051
- }
64052
- function keySignature(keyType) {
64053
- const fields = structFields(keyType);
64054
- if (fields === null) return keyType.type;
64055
- return `(${fields.map((f2) => `${f2.name}: ${f2.type.type}`).join(", ")})`;
64056
- }
64057
- function parseKeyInput(keyType, text) {
64058
- const fields = structFields(keyType);
64059
- if (fields === null) {
64060
- if (keyType.type === "String") return { kind: "query", query: { prefix: text } };
64061
- const parsed = east.parseFor(keyType)(text);
64062
- if (!parsed.success) return { kind: "hint", hint: `Key is ${keyType.type}` };
64063
- return { kind: "query", query: { key: east.printFor(keyType)(parsed.value) } };
64064
- }
64065
- if (text.trimStart().startsWith("(")) {
64066
- const parsed = east.parseFor(keyType)(text);
64067
- if (!parsed.success) return { kind: "hint", hint: `Key is ${keySignature(keyType)}` };
64068
- return { kind: "query", query: { key: east.printFor(keyType)(parsed.value) } };
64069
- }
64070
- const segments = splitTopLevel(text);
64071
- if (segments.length > fields.length) {
64072
- return { kind: "hint", hint: `Key is ${keySignature(keyType)}` };
64073
- }
64074
- const exact = [];
64075
- for (let i = 0; i < segments.length; i++) {
64076
- const field = fields[i];
64077
- const raw = segments[i].trim();
64078
- const isLast = i === segments.length - 1;
64079
- if (isLast && raw === "" && i > 0) {
64080
- return { kind: "query", query: { fields: exact } };
64081
- }
64082
- if (field.type.type === "String" && !raw.startsWith('"')) {
64083
- if (isLast) {
64084
- return exact.length === 0 ? { kind: "query", query: { prefix: raw } } : { kind: "query", query: { fields: exact, prefix: raw } };
64085
- }
64086
- exact.push(east.printFor(east.StringType)(raw));
64087
- continue;
64088
- }
64089
- const parsed = east.parseFor(field.type)(raw);
64090
- if (!parsed.success) {
64091
- return { kind: "hint", hint: `${field.name} is ${field.type.type} — key is ${keySignature(keyType)}` };
64092
- }
64093
- exact.push(east.printFor(field.type)(parsed.value));
64094
- }
64095
- return { kind: "query", query: { fields: exact } };
64096
- }
64097
- const MATCH_NOTHING = { lower: () => false, upper: () => false };
64098
- function keyRangePredicates(keyType, query) {
64099
- if ("key" in query) return null;
64100
- if (keyType.type === "Struct") {
64101
- const meta3 = keyType.value;
64102
- const literals = "fields" in query ? query.fields : [];
64103
- if (literals.length > meta3.length) return MATCH_NOTHING;
64104
- const values = [];
64105
- for (let j2 = 0; j2 < literals.length; j2++) {
64106
- const parsed = east.parseFor(meta3[j2].type)(literals[j2]);
64107
- if (!parsed.success) return MATCH_NOTHING;
64108
- values.push(parsed.value);
64109
- }
64110
- const cmps = meta3.map((f2) => east.compareFor(f2.type));
64111
- const lead = (k2) => {
64112
- for (let j2 = 0; j2 < values.length; j2++) {
64113
- const c2 = cmps[j2](k2[meta3[j2].name], values[j2]);
64114
- if (c2 !== 0) return c2;
64115
- }
64116
- return 0;
64117
- };
64118
- const prefix22 = query.prefix;
64119
- if (prefix22 === void 0) {
64120
- return { lower: (k2) => lead(k2) >= 0, upper: (k2) => lead(k2) > 0 };
64121
- }
64122
- const prefixIdx = values.length;
64123
- if (prefixIdx >= meta3.length || meta3[prefixIdx].type.type !== "String") return MATCH_NOTHING;
64124
- const prefixName = meta3[prefixIdx].name;
64125
- const prefixCmp = cmps[prefixIdx];
64126
- return {
64127
- lower: (k2) => {
64128
- const c2 = lead(k2);
64129
- return c2 !== 0 ? c2 > 0 : prefixCmp(k2[prefixName], prefix22) >= 0;
64130
- },
64131
- upper: (k2) => {
64132
- const c2 = lead(k2);
64133
- if (c2 !== 0) return c2 > 0;
64134
- const field = k2[prefixName];
64135
- return prefixCmp(field, prefix22) > 0 && !field.startsWith(prefix22);
64136
- }
64137
- };
64138
- }
64139
- if (!("prefix" in query) || query.prefix === void 0 || keyType.type !== "String") return MATCH_NOTHING;
64140
- const prefix2 = query.prefix;
64141
- const cmp = east.compareFor(keyType);
64142
- return {
64143
- lower: (k2) => cmp(k2, prefix2) >= 0,
64144
- upper: (k2) => cmp(k2, prefix2) > 0 && !k2.startsWith(prefix2)
64145
- };
64146
- }
64147
- const DatasetKeySearch = React.memo(function DatasetKeySearch2({ keyType, onFind, onListRange, onJump, onClear }) {
64148
- const [range, setRange] = React.useState(null);
64149
- const [items, setItems] = React.useState([]);
64150
- const [activeIdx, setActiveIdx] = React.useState(-1);
64151
- const [hint, setHint] = React.useState(null);
64152
- const [resetSeq, setResetSeq] = React.useState(0);
64153
- const debounceRef = React.useRef(void 0);
64154
- const findSeqRef = React.useRef(0);
64155
- React.useEffect(() => () => clearTimeout(debounceRef.current), []);
64156
- const runFind = React.useCallback((query) => {
64157
- const seq = ++findSeqRef.current;
64158
- void (async () => {
64159
- try {
64160
- const result = await onFind(query);
64161
- if (seq !== findSeqRef.current) return;
64162
- setRange(result);
64163
- setActiveIdx(-1);
64164
- if (result.found) {
64165
- const labels = await onListRange(result.row, Math.min(POPUP_LIMIT, result.count));
64166
- if (seq !== findSeqRef.current) return;
64167
- setItems(labels.map((label, i) => ({ row: result.row + i, label })));
64168
- } else {
64169
- setItems([]);
64170
- }
64171
- } catch {
64172
- if (seq !== findSeqRef.current) return;
64173
- setRange(null);
64174
- setItems([]);
64175
- }
64176
- })();
64177
- }, [onFind, onListRange]);
64178
- const resetQueryState = React.useCallback(() => {
64179
- clearTimeout(debounceRef.current);
64180
- findSeqRef.current++;
64181
- setRange(null);
64182
- setItems([]);
64183
- setActiveIdx(-1);
64184
- setHint(null);
64185
- }, []);
64186
- const handleInput = React.useCallback((text) => {
64187
- clearTimeout(debounceRef.current);
64188
- if (text === "") {
64189
- resetQueryState();
64190
- onClear == null ? void 0 : onClear();
64191
- return;
64192
- }
64193
- const parsed = parseKeyInput(keyType, text);
64194
- if (parsed.kind === "hint") {
64195
- findSeqRef.current++;
64196
- setRange(null);
64197
- setItems([]);
64198
- setActiveIdx(-1);
64199
- setHint(parsed.hint);
64200
- return;
64201
- }
64202
- setHint(null);
64203
- debounceRef.current = setTimeout(() => runFind(parsed.query), FIND_DEBOUNCE_MS);
64204
- }, [keyType, runFind, resetQueryState, onClear]);
64205
- const clearSearch = React.useCallback(() => {
64206
- resetQueryState();
64207
- setResetSeq((s2) => s2 + 1);
64208
- onClear == null ? void 0 : onClear();
64209
- }, [resetQueryState, onClear]);
64210
- const commit = React.useCallback((row) => {
64211
- onJump(row);
64212
- if (range !== null) setActiveIdx(row - range.row);
64213
- }, [onJump, range]);
64214
- const step3 = React.useCallback((delta) => {
64215
- if (range === null || !range.found) return;
64216
- const next2 = Math.max(0, Math.min(range.count - 1, (activeIdx === -1 ? 0 : activeIdx) + delta));
64217
- commit(range.row + next2);
64218
- }, [range, activeIdx, commit]);
64219
- const onKeyDownCapture = React.useCallback((e3) => {
64220
- if (e3.key !== "Enter" || range === null || !range.found) return;
64221
- if (document.querySelector('[data-scope="combobox"][data-part="content"] [data-highlighted]') !== null) return;
64222
- commit(range.row + Math.max(0, activeIdx));
64223
- }, [range, activeIdx, commit]);
64224
- const placeholder = React.useMemo(() => {
64225
- const fields = structFields(keyType);
64226
- if (fields !== null) return `Search keys (${fields.map((f2) => f2.name).join(", ")})`;
64227
- return keyType.type === "String" ? "Search keys" : `Find key (${keyType.type})`;
64228
- }, [keyType]);
64229
- const payload = React.useMemo(() => ({
64230
- value: east.none,
64231
- items: items.map((it) => ({ value: String(it.row), label: it.label, disabled: east.none })),
64232
- placeholder: east.some(placeholder),
64233
- multiple: east.none,
64234
- disabled: east.none,
64235
- allowCustomValue: east.some(true),
64236
- onChange: east.some((v3) => commit(Number(v3))),
64237
- onChangeMultiple: east.none,
64238
- onInputValueChange: east.some(handleInput),
64239
- onOpenChange: east.none,
64240
- style: east.some({ size: east.some(east.variant("xs", null)), color: east.none, background: east.none, borderColor: east.none })
64241
- }), [items, placeholder, commit, handleInput]);
64242
- const status = hint !== null ? hint : range === null ? null : !range.found ? "No matches" : activeIdx === -1 ? `${range.count.toLocaleString()} ${range.count === 1 ? "match" : "matches"}` : `${(activeIdx + 1).toLocaleString()} of ${range.count.toLocaleString()}`;
64243
- const canStep = range !== null && range.found && range.count > 1;
64244
- return /* @__PURE__ */ jsxRuntime.jsxs(react.Flex, { gap: 1, align: "center", minW: "0", onKeyDownCapture, "data-part": "dataset-key-search", children: [
64245
- /* @__PURE__ */ jsxRuntime.jsx(react.Flex, { width: `min(${placeholder.length + 10}ch, 40rem)`, minW: "12rem", flexShrink: 1, children: /* @__PURE__ */ jsxRuntime.jsx(eastUiComponents.EastChakraCombobox, { value: payload, selectionBehavior: "preserve" }, resetSeq) }),
64246
- status !== null && /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "xs", color: "fg.muted", whiteSpace: "nowrap", children: status }),
64247
- canStep && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
64248
- /* @__PURE__ */ jsxRuntime.jsx(
64249
- react.IconButton,
64250
- {
64251
- "aria-label": "Previous match",
64252
- size: "2xs",
64253
- variant: "ghost",
64254
- disabled: activeIdx <= 0,
64255
- onClick: () => step3(-1),
64256
- children: /* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: faChevronUp })
64257
- }
64258
- ),
64259
- /* @__PURE__ */ jsxRuntime.jsx(
64260
- react.IconButton,
64261
- {
64262
- "aria-label": "Next match",
64263
- size: "2xs",
64264
- variant: "ghost",
64265
- disabled: activeIdx >= range.count - 1,
64266
- onClick: () => step3(1),
64267
- children: /* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: faChevronDown })
64268
- }
64269
- )
64270
- ] }),
64271
- (range !== null || hint !== null) && /* @__PURE__ */ jsxRuntime.jsx(react.IconButton, { "aria-label": "Clear search", size: "2xs", variant: "ghost", onClick: clearSearch, children: /* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: faXmark }) })
64272
- ] });
64273
- });
64274
64419
  function pagingDebug(...args) {
64275
64420
  try {
64276
64421
  if (typeof localStorage === "undefined") return;
@@ -64463,7 +64608,7 @@ const PagedDatasetPreview = React.memo(function PagedDatasetPreview2({
64463
64608
  ] }),
64464
64609
  loadingCount > 0 && /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "xs", color: "fg.muted", children: "Loading…" }),
64465
64610
  keyType !== null && /* @__PURE__ */ jsxRuntime.jsx(
64466
- DatasetKeySearch,
64611
+ eastUiComponents.DatasetKeySearch,
64467
64612
  {
64468
64613
  keyType,
64469
64614
  onFind: onFindKey,
@@ -64551,7 +64696,7 @@ const DatasetPreview = React.memo(function DatasetPreview2({
64551
64696
  }, [decoded]);
64552
64697
  const onFindInline = React.useCallback(async (query) => {
64553
64698
  if (inlineKeys === null || keyType === null) return { found: false, row: 0, count: 0 };
64554
- const range = keyRangePredicates(keyType, query);
64699
+ const range = eastUiComponents.keyRangePredicates(keyType, query);
64555
64700
  if (range === null) {
64556
64701
  const parsed = east.parseFor(keyType)(query.key);
64557
64702
  if (!parsed.success) return { found: false, row: 0, count: 0 };
@@ -64630,7 +64775,7 @@ const DatasetPreview = React.memo(function DatasetPreview2({
64630
64775
  return /* @__PURE__ */ jsxRuntime.jsxs(react.Flex, { direction: "column", height: "100%", overflow: "hidden", children: [
64631
64776
  /* @__PURE__ */ jsxRuntime.jsxs(react.Flex, { px: 4, py: 2, gap: 2, align: "center", flexShrink: 0, borderBottom: "1px solid", borderColor: "border.subtle", children: [
64632
64777
  keyType !== null && inlineKeys !== null && /* @__PURE__ */ jsxRuntime.jsx(
64633
- DatasetKeySearch,
64778
+ eastUiComponents.DatasetKeySearch,
64634
64779
  {
64635
64780
  keyType,
64636
64781
  onFind: onFindInline,
@@ -64702,7 +64847,7 @@ const UITaskPreview = React.memo(function UITaskPreview2({
64702
64847
  const manifest = React.useMemo(() => {
64703
64848
  if (!details || !isUI) return null;
64704
64849
  const meta3 = getTaskMetadata(details);
64705
- return meta3 ? internal$1.decodeManifest(meta3) : { paths: [], functions: [], records: [] };
64850
+ return meta3 ? internal$1.decodeManifest(meta3) : { paths: [], functions: [], records: [], pages: [] };
64706
64851
  }, [details, isUI]);
64707
64852
  const outputPath = details ? treePathToString$1(details.output) : null;
64708
64853
  const preloads = React.useMemo(
@@ -64726,6 +64871,7 @@ const UITaskPreview = React.memo(function UITaskPreview2({
64726
64871
  ...eastUiComponents.ShareImpl,
64727
64872
  ...DecisionBindPlatform,
64728
64873
  ...createScopedBindPlatform(manifest),
64874
+ ...createScopedPagedPlatform(manifest.pages),
64729
64875
  ...createScopedFuncPlatform(manifest.functions),
64730
64876
  ...createScopedRecordPlatform(manifest.records)
64731
64877
  ] : void 0,
@@ -65635,10 +65781,13 @@ function EastValueViewer({ type, value }) {
65635
65781
  }
65636
65782
  );
65637
65783
  }
65784
+ Object.defineProperty(exports, "DatasetKeySearch", {
65785
+ enumerable: true,
65786
+ get: () => eastUiComponents.DatasetKeySearch
65787
+ });
65638
65788
  exports.BindPlatform = BindPlatform;
65639
65789
  exports.BindRuntime = BindRuntime;
65640
65790
  exports.DataTaskPreview = DataTaskPreview;
65641
- exports.DatasetKeySearch = DatasetKeySearch;
65642
65791
  exports.DatasetPreview = DatasetPreview;
65643
65792
  exports.E3Provider = E3Provider;
65644
65793
  exports.EastChakraDecisionJournal = EastChakraDecisionJournal;
@@ -65653,6 +65802,8 @@ exports.FuncRuntime = FuncRuntime;
65653
65802
  exports.IndexedDBStagedAdapter = IndexedDBStagedAdapter;
65654
65803
  exports.InputPreview = InputPreview;
65655
65804
  exports.MemoryStagedAdapter = MemoryStagedAdapter;
65805
+ exports.PagedPlatform = PagedPlatform;
65806
+ exports.PagedRuntime = PagedRuntime;
65656
65807
  exports.ReactiveDatasetCache = ReactiveDatasetCache;
65657
65808
  exports.ReactiveDatasetLoader = ReactiveDatasetLoader;
65658
65809
  exports.ReactiveDatasetProvider = ReactiveDatasetProvider;
@@ -65666,6 +65817,7 @@ exports.VirtualizedLogViewer = VirtualizedLogViewer;
65666
65817
  exports.awaitPendingWrites = awaitPendingWrites;
65667
65818
  exports.clearBindingRegistry = clearBindingRegistry;
65668
65819
  exports.clearFunctionApi = clearFunctionApi;
65820
+ exports.clearPagedApi = clearPagedApi;
65669
65821
  exports.clearPendingWrites = clearPendingWrites;
65670
65822
  exports.clearReactiveDatasetCache = clearReactiveDatasetCache;
65671
65823
  exports.clearReactiveDatasetListCache = clearReactiveDatasetListCache;
@@ -65673,17 +65825,20 @@ exports.clearRecordApi = clearRecordApi;
65673
65825
  exports.clearStagedStoreSingleton = clearStagedStoreSingleton;
65674
65826
  exports.createDefaultDatasetApi = createDefaultDatasetApi;
65675
65827
  exports.createDefaultFunctionApi = createDefaultFunctionApi;
65828
+ exports.createDefaultPagedApi = createDefaultPagedApi;
65676
65829
  exports.createDefaultRecordApi = createDefaultRecordApi;
65677
65830
  exports.createInMemoryFunctionApi = createInMemoryFunctionApi;
65678
65831
  exports.createInMemoryRecordApi = createInMemoryRecordApi;
65679
65832
  exports.createReactiveDatasetCache = createReactiveDatasetCache;
65680
65833
  exports.createScopedBindPlatform = createScopedBindPlatform;
65681
65834
  exports.createScopedFuncPlatform = createScopedFuncPlatform;
65835
+ exports.createScopedPagedPlatform = createScopedPagedPlatform;
65682
65836
  exports.createScopedRecordPlatform = createScopedRecordPlatform;
65683
65837
  exports.datasetCacheKey = datasetCacheKey;
65684
65838
  exports.datasetPathToString = datasetPathToString;
65685
65839
  exports.defaultBindRuntime = defaultBindRuntime;
65686
65840
  exports.defaultFuncRuntime = defaultFuncRuntime;
65841
+ exports.defaultPagedRuntime = defaultPagedRuntime;
65687
65842
  exports.defaultRecordRuntime = defaultRecordRuntime;
65688
65843
  exports.disableBindingTracking = disableBindingTracking;
65689
65844
  exports.enableBindingTracking = enableBindingTracking;
@@ -65696,11 +65851,14 @@ exports.getStagedStore = getStagedStore;
65696
65851
  exports.getTaskKind = getTaskKind;
65697
65852
  exports.getTaskMetadata = getTaskMetadata;
65698
65853
  exports.initializeFunctionApi = initializeFunctionApi;
65854
+ exports.initializePagedApi = initializePagedApi;
65699
65855
  exports.initializeReactiveDatasetCache = initializeReactiveDatasetCache;
65700
65856
  exports.initializeRecordApi = initializeRecordApi;
65701
65857
  exports.initializeStagedStore = initializeStagedStore;
65702
65858
  exports.isBindingTracking = isBindingTracking;
65703
65859
  exports.onWriteError = onWriteError;
65860
+ exports.pagedTotalKey = pagedTotalKey;
65861
+ exports.pagedWindowKey = pagedWindowKey;
65704
65862
  exports.preloadReactiveDatasetList = preloadReactiveDatasetList;
65705
65863
  exports.realClock = realClock;
65706
65864
  exports.recordChannelKey = recordChannelKey;