@elaraai/e3-ui-components 1.0.79 → 1.0.80
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/README.md +1 -1
- package/dist/components/DatasetPreview.d.ts.map +1 -1
- package/dist/components/PagedDatasetPreview.d.ts.map +1 -1
- package/dist/components/TaskPreview.d.ts.map +1 -1
- package/dist/components/UITaskPreview.d.ts +7 -3
- package/dist/components/UITaskPreview.d.ts.map +1 -1
- package/dist/hooks/index.d.ts +1 -1
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/useDatasetValue.d.ts +13 -2
- package/dist/hooks/useDatasetValue.d.ts.map +1 -1
- package/dist/hooks/useTaskDetails.d.ts +1 -5
- package/dist/hooks/useTaskDetails.d.ts.map +1 -1
- package/dist/hooks/workspaces.d.ts +16 -3
- package/dist/hooks/workspaces.d.ts.map +1 -1
- package/dist/index.cjs +740 -319
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +745 -324
- package/dist/index.js.map +1 -1
- package/dist/platform/bind-runtime.d.ts +14 -5
- package/dist/platform/bind-runtime.d.ts.map +1 -1
- package/dist/platform/dataset-hooks.d.ts +12 -4
- package/dist/platform/dataset-hooks.d.ts.map +1 -1
- package/dist/platform/dataset-store.d.ts +47 -11
- package/dist/platform/dataset-store.d.ts.map +1 -1
- package/dist/platform/func-runtime.d.ts +14 -4
- package/dist/platform/func-runtime.d.ts.map +1 -1
- package/dist/platform/index.d.ts +1 -1
- package/dist/platform/index.d.ts.map +1 -1
- package/dist/platform/paged-runtime.d.ts +183 -49
- package/dist/platform/paged-runtime.d.ts.map +1 -1
- package/dist/platform/record-runtime.d.ts +14 -4
- package/dist/platform/record-runtime.d.ts.map +1 -1
- package/dist/platform/staged-store.d.ts +19 -2
- package/dist/platform/staged-store.d.ts.map +1 -1
- package/package.json +13 -13
package/dist/index.cjs
CHANGED
|
@@ -2077,6 +2077,7 @@ const realClock = {
|
|
|
2077
2077
|
return { clear: () => clearInterval(id3) };
|
|
2078
2078
|
}
|
|
2079
2079
|
};
|
|
2080
|
+
const HASH_POLL_MS = 1e3;
|
|
2080
2081
|
function createDefaultDatasetApi(apiUrl, repo, getToken) {
|
|
2081
2082
|
const opts = () => ({ token: getToken() });
|
|
2082
2083
|
return {
|
|
@@ -2142,12 +2143,17 @@ class ReactiveDatasetCache {
|
|
|
2142
2143
|
__publicField(this, "keyVersions", /* @__PURE__ */ new Map());
|
|
2143
2144
|
// Workspace status polling — groups subscriptions by workspace for
|
|
2144
2145
|
// efficiency. Each entry holds the active interval handle from the
|
|
2145
|
-
// injected {@link Clock} so tests can drive ticking deterministically
|
|
2146
|
+
// injected {@link Clock} so tests can drive ticking deterministically,
|
|
2147
|
+
// and each polled path with how many watchers poll it.
|
|
2146
2148
|
__publicField(this, "workspacePollers", /* @__PURE__ */ new Map());
|
|
2147
2149
|
__publicField(this, "clock");
|
|
2148
2150
|
// In-flight poll dedup — concurrent callers (interval tick + a
|
|
2149
2151
|
// post-write trigger) share a single round-trip rather than racing.
|
|
2150
2152
|
__publicField(this, "inFlightPolls", /* @__PURE__ */ new Map());
|
|
2153
|
+
// Hash-only watches, by workspace and path: each callback with the hash it
|
|
2154
|
+
// was last told. A watched path rides its workspace's poll, but its
|
|
2155
|
+
// content is never fetched — a paged source reads windows, never the value.
|
|
2156
|
+
__publicField(this, "hashWatches", /* @__PURE__ */ new Map());
|
|
2151
2157
|
// Batching
|
|
2152
2158
|
__publicField(this, "batchDepth", 0);
|
|
2153
2159
|
__publicField(this, "changedKeys", /* @__PURE__ */ new Set());
|
|
@@ -2236,9 +2242,7 @@ class ReactiveDatasetCache {
|
|
|
2236
2242
|
hash: void 0,
|
|
2237
2243
|
status: east.variant("stale", null)
|
|
2238
2244
|
});
|
|
2239
|
-
|
|
2240
|
-
void this.pollWorkspaceStatus(workspace);
|
|
2241
|
-
}
|
|
2245
|
+
void this.refresh(workspace);
|
|
2242
2246
|
} catch (error3) {
|
|
2243
2247
|
if (!this.destroyed && this.writeEpochs.get(key) === myEpoch) {
|
|
2244
2248
|
const baseline = this.writeBaselines.get(key);
|
|
@@ -2344,15 +2348,15 @@ class ReactiveDatasetCache {
|
|
|
2344
2348
|
* 3. Only fetches full content when hash changes
|
|
2345
2349
|
*
|
|
2346
2350
|
* Multiple subscriptions to the same workspace share a single poller.
|
|
2347
|
-
* Pair with {@link clearRefetchInterval} on unmount so the
|
|
2348
|
-
* stop when nothing is watching.
|
|
2351
|
+
* Pair each call with one {@link clearRefetchInterval} on unmount so the
|
|
2352
|
+
* poller can stop when nothing is watching.
|
|
2349
2353
|
*/
|
|
2350
2354
|
setRefetchInterval(workspace, path2, intervalMs) {
|
|
2351
2355
|
var _a2;
|
|
2352
2356
|
const pathStr = datasetPathToString(path2);
|
|
2353
2357
|
let poller = this.workspacePollers.get(workspace);
|
|
2354
2358
|
if (poller) {
|
|
2355
|
-
poller.paths.
|
|
2359
|
+
poller.paths.set(pathStr, (poller.paths.get(pathStr) ?? 0) + 1);
|
|
2356
2360
|
if (intervalMs < poller.intervalMs) {
|
|
2357
2361
|
(_a2 = poller.handle) == null ? void 0 : _a2.clear();
|
|
2358
2362
|
poller.intervalMs = intervalMs;
|
|
@@ -2364,7 +2368,7 @@ class ReactiveDatasetCache {
|
|
|
2364
2368
|
} else {
|
|
2365
2369
|
poller = {
|
|
2366
2370
|
intervalMs,
|
|
2367
|
-
paths: /* @__PURE__ */ new
|
|
2371
|
+
paths: /* @__PURE__ */ new Map([[pathStr, 1]]),
|
|
2368
2372
|
handle: this.clock.setInterval(
|
|
2369
2373
|
() => this.pollWorkspaceStatus(workspace),
|
|
2370
2374
|
intervalMs
|
|
@@ -2375,23 +2379,79 @@ class ReactiveDatasetCache {
|
|
|
2375
2379
|
}
|
|
2376
2380
|
}
|
|
2377
2381
|
/**
|
|
2378
|
-
* Stop polling a dataset
|
|
2379
|
-
*
|
|
2382
|
+
* Stop polling a dataset for one watcher; the path goes when its last
|
|
2383
|
+
* watcher clears it, and the workspace poller is torn down when its last
|
|
2384
|
+
* path goes.
|
|
2380
2385
|
*/
|
|
2381
2386
|
clearRefetchInterval(workspace, path2) {
|
|
2382
|
-
var _a2;
|
|
2383
2387
|
const poller = this.workspacePollers.get(workspace);
|
|
2384
2388
|
if (!poller) return;
|
|
2385
|
-
|
|
2386
|
-
|
|
2389
|
+
const pathStr = datasetPathToString(path2);
|
|
2390
|
+
const watchers = (poller.paths.get(pathStr) ?? 0) - 1;
|
|
2391
|
+
if (watchers > 0) {
|
|
2392
|
+
poller.paths.set(pathStr, watchers);
|
|
2393
|
+
return;
|
|
2394
|
+
}
|
|
2395
|
+
poller.paths.delete(pathStr);
|
|
2396
|
+
this.stopIdlePoller(workspace);
|
|
2397
|
+
}
|
|
2398
|
+
/**
|
|
2399
|
+
* Follow a dataset's content hash without fetching its content; see the
|
|
2400
|
+
* interface doc.
|
|
2401
|
+
*/
|
|
2402
|
+
watchHash(workspace, path2, onHash, intervalMs = HASH_POLL_MS) {
|
|
2403
|
+
var _a2;
|
|
2404
|
+
const pathStr = datasetPathToString(path2);
|
|
2405
|
+
let byPath = this.hashWatches.get(workspace);
|
|
2406
|
+
if (!byPath) {
|
|
2407
|
+
byPath = /* @__PURE__ */ new Map();
|
|
2408
|
+
this.hashWatches.set(workspace, byPath);
|
|
2409
|
+
}
|
|
2410
|
+
let callbacks = byPath.get(pathStr);
|
|
2411
|
+
if (!callbacks) {
|
|
2412
|
+
callbacks = /* @__PURE__ */ new Map();
|
|
2413
|
+
byPath.set(pathStr, callbacks);
|
|
2414
|
+
}
|
|
2415
|
+
callbacks.set(onHash, void 0);
|
|
2416
|
+
const poller = this.workspacePollers.get(workspace);
|
|
2417
|
+
if (!poller) {
|
|
2418
|
+
this.workspacePollers.set(workspace, {
|
|
2419
|
+
intervalMs,
|
|
2420
|
+
paths: /* @__PURE__ */ new Map(),
|
|
2421
|
+
handle: this.clock.setInterval(() => this.pollWorkspaceStatus(workspace), intervalMs)
|
|
2422
|
+
});
|
|
2423
|
+
void this.pollWorkspaceStatus(workspace);
|
|
2424
|
+
} else if (intervalMs < poller.intervalMs) {
|
|
2387
2425
|
(_a2 = poller.handle) == null ? void 0 : _a2.clear();
|
|
2388
|
-
|
|
2426
|
+
poller.intervalMs = intervalMs;
|
|
2427
|
+
poller.handle = this.clock.setInterval(() => this.pollWorkspaceStatus(workspace), intervalMs);
|
|
2389
2428
|
}
|
|
2429
|
+
return () => {
|
|
2430
|
+
const watched = this.hashWatches.get(workspace);
|
|
2431
|
+
const set3 = watched == null ? void 0 : watched.get(pathStr);
|
|
2432
|
+
if (!watched || !set3) return;
|
|
2433
|
+
set3.delete(onHash);
|
|
2434
|
+
if (set3.size === 0) watched.delete(pathStr);
|
|
2435
|
+
if (watched.size === 0) this.hashWatches.delete(workspace);
|
|
2436
|
+
this.stopIdlePoller(workspace);
|
|
2437
|
+
};
|
|
2438
|
+
}
|
|
2439
|
+
/** Stops a workspace's poller once it has no path to reconcile and no hash
|
|
2440
|
+
* to follow — without this, a long-lived session polls forever. */
|
|
2441
|
+
stopIdlePoller(workspace) {
|
|
2442
|
+
var _a2;
|
|
2443
|
+
const poller = this.workspacePollers.get(workspace);
|
|
2444
|
+
if (!poller || poller.paths.size > 0 || this.hashWatches.has(workspace)) return;
|
|
2445
|
+
(_a2 = poller.handle) == null ? void 0 : _a2.clear();
|
|
2446
|
+
this.workspacePollers.delete(workspace);
|
|
2390
2447
|
}
|
|
2391
2448
|
refresh(workspace) {
|
|
2392
2449
|
if (this.destroyed) return Promise.resolve();
|
|
2393
2450
|
if (!this.workspacePollers.has(workspace)) return Promise.resolve();
|
|
2394
|
-
|
|
2451
|
+
const inFlight = this.inFlightPolls.get(workspace);
|
|
2452
|
+
if (inFlight === void 0) return this.pollWorkspaceStatus(workspace);
|
|
2453
|
+
const again = () => this.destroyed || !this.workspacePollers.has(workspace) ? void 0 : this.pollWorkspaceStatus(workspace);
|
|
2454
|
+
return inFlight.then(again, again);
|
|
2395
2455
|
}
|
|
2396
2456
|
/**
|
|
2397
2457
|
* Poll workspace status and reconcile the cache with the server.
|
|
@@ -2407,11 +2467,12 @@ class ReactiveDatasetCache {
|
|
|
2407
2467
|
return promise4;
|
|
2408
2468
|
}
|
|
2409
2469
|
async doPollWorkspaceStatus(workspace) {
|
|
2470
|
+
var _a2;
|
|
2410
2471
|
if (this.destroyed) return;
|
|
2411
2472
|
const poller = this.workspacePollers.get(workspace);
|
|
2412
|
-
if (!poller || poller.paths.size === 0) return;
|
|
2473
|
+
if (!poller || poller.paths.size === 0 && !this.hashWatches.has(workspace)) return;
|
|
2413
2474
|
const epochsAtStart = /* @__PURE__ */ new Map();
|
|
2414
|
-
for (const pathStr of poller.paths) {
|
|
2475
|
+
for (const pathStr of poller.paths.keys()) {
|
|
2415
2476
|
const key = pathStr ? `${workspace}.${pathStr}` : workspace;
|
|
2416
2477
|
epochsAtStart.set(key, this.writeEpochs.get(key) ?? 0);
|
|
2417
2478
|
}
|
|
@@ -2425,15 +2486,28 @@ class ReactiveDatasetCache {
|
|
|
2425
2486
|
if (this.destroyed) return;
|
|
2426
2487
|
const infoByPath = /* @__PURE__ */ new Map();
|
|
2427
2488
|
for (const info of status.datasets) infoByPath.set(info.path, info);
|
|
2489
|
+
for (const [pathStr, callbacks] of this.hashWatches.get(workspace) ?? []) {
|
|
2490
|
+
const info = infoByPath.get(pathStr ? `.${pathStr}` : "");
|
|
2491
|
+
const hash2 = ((_a2 = info == null ? void 0 : info.hash) == null ? void 0 : _a2.type) === "some" ? info.hash.value : null;
|
|
2492
|
+
for (const [onHash, told] of [...callbacks]) {
|
|
2493
|
+
if (told === hash2 || !callbacks.has(onHash)) continue;
|
|
2494
|
+
callbacks.set(onHash, hash2);
|
|
2495
|
+
try {
|
|
2496
|
+
onHash(hash2);
|
|
2497
|
+
} catch (error3) {
|
|
2498
|
+
console.error(`A hash watch on ${workspace}.${pathStr} failed:`, error3);
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2428
2502
|
const pending = [];
|
|
2429
2503
|
this.batch(() => {
|
|
2430
|
-
var
|
|
2431
|
-
for (const pathStr of poller.paths) {
|
|
2504
|
+
var _a3;
|
|
2505
|
+
for (const pathStr of poller.paths.keys()) {
|
|
2432
2506
|
const e3Path = pathStr ? `.${pathStr}` : "";
|
|
2433
2507
|
const info = infoByPath.get(e3Path);
|
|
2434
2508
|
const key = pathStr ? `${workspace}.${pathStr}` : workspace;
|
|
2435
2509
|
const path2 = this.stringToPath(pathStr);
|
|
2436
|
-
const currentHash = ((
|
|
2510
|
+
const currentHash = ((_a3 = info == null ? void 0 : info.hash) == null ? void 0 : _a3.type) === "some" ? info.hash.value : null;
|
|
2437
2511
|
const knownHash = this.knownHashes.get(key);
|
|
2438
2512
|
if (info == null ? void 0 : info.status) {
|
|
2439
2513
|
const previous = this.statuses.get(key);
|
|
@@ -2599,6 +2673,7 @@ class ReactiveDatasetCache {
|
|
|
2599
2673
|
}
|
|
2600
2674
|
this.workspacePollers.clear();
|
|
2601
2675
|
this.inFlightPolls.clear();
|
|
2676
|
+
this.hashWatches.clear();
|
|
2602
2677
|
this.keySubscribers.clear();
|
|
2603
2678
|
this.globalSubscribers.clear();
|
|
2604
2679
|
this.knownHashes.clear();
|
|
@@ -2713,9 +2788,16 @@ class MemoryStagedAdapter {
|
|
|
2713
2788
|
this.map.clear();
|
|
2714
2789
|
}
|
|
2715
2790
|
}
|
|
2791
|
+
const SCOPE_SEPARATOR = "\0";
|
|
2792
|
+
function cacheKeyOf(key) {
|
|
2793
|
+
return key.slice(key.indexOf(SCOPE_SEPARATOR) + 1);
|
|
2794
|
+
}
|
|
2716
2795
|
class StagedStore {
|
|
2717
2796
|
constructor(adapter) {
|
|
2797
|
+
// By scope and `datasetCacheKey`; subscribers and versions go by the
|
|
2798
|
+
// `datasetCacheKey` alone, which is what a reactive view tracks.
|
|
2718
2799
|
__publicField(this, "entries", /* @__PURE__ */ new Map());
|
|
2800
|
+
__publicField(this, "scope", null);
|
|
2719
2801
|
__publicField(this, "subscribers", /* @__PURE__ */ new Map());
|
|
2720
2802
|
__publicField(this, "versions", /* @__PURE__ */ new Map());
|
|
2721
2803
|
__publicField(this, "adapter");
|
|
@@ -2739,25 +2821,25 @@ class StagedStore {
|
|
|
2739
2821
|
}
|
|
2740
2822
|
}
|
|
2741
2823
|
hasPending(workspace, path2) {
|
|
2742
|
-
return this.entries.has(
|
|
2824
|
+
return this.entries.has(this.keyOf(workspace, path2));
|
|
2743
2825
|
}
|
|
2744
2826
|
getBuffered(workspace, path2) {
|
|
2745
2827
|
var _a2;
|
|
2746
|
-
return (_a2 = this.entries.get(
|
|
2828
|
+
return (_a2 = this.entries.get(this.keyOf(workspace, path2))) == null ? void 0 : _a2.buffered;
|
|
2747
2829
|
}
|
|
2748
2830
|
getSnapshot(workspace, path2) {
|
|
2749
2831
|
var _a2;
|
|
2750
|
-
return (_a2 = this.entries.get(
|
|
2832
|
+
return (_a2 = this.entries.get(this.keyOf(workspace, path2))) == null ? void 0 : _a2.snapshot;
|
|
2751
2833
|
}
|
|
2752
2834
|
getEntry(workspace, path2) {
|
|
2753
|
-
return this.entries.get(
|
|
2835
|
+
return this.entries.get(this.keyOf(workspace, path2));
|
|
2754
2836
|
}
|
|
2755
2837
|
write(workspace, path2, snapshotIfNew, buffered) {
|
|
2756
|
-
const key =
|
|
2838
|
+
const key = this.keyOf(workspace, path2);
|
|
2757
2839
|
const existing = this.entries.get(key);
|
|
2758
2840
|
const next2 = existing ? { snapshot: existing.snapshot, buffered } : { snapshot: snapshotIfNew, buffered };
|
|
2759
2841
|
this.entries.set(key, next2);
|
|
2760
|
-
this.notify(
|
|
2842
|
+
this.notify(datasetCacheKey(workspace, path2));
|
|
2761
2843
|
const persisted = {
|
|
2762
2844
|
snapshot: next2.snapshot,
|
|
2763
2845
|
buffered: next2.buffered
|
|
@@ -2765,15 +2847,26 @@ class StagedStore {
|
|
|
2765
2847
|
this.persist(key, () => this.adapter.save(key, persisted));
|
|
2766
2848
|
}
|
|
2767
2849
|
discard(workspace, path2) {
|
|
2768
|
-
const key =
|
|
2850
|
+
const key = this.keyOf(workspace, path2);
|
|
2769
2851
|
if (!this.entries.has(key)) return false;
|
|
2770
2852
|
this.entries.delete(key);
|
|
2771
|
-
this.notify(
|
|
2853
|
+
this.notify(datasetCacheKey(workspace, path2));
|
|
2772
2854
|
this.persist(key, () => this.adapter.remove(key));
|
|
2773
2855
|
return true;
|
|
2774
2856
|
}
|
|
2775
2857
|
listKeys() {
|
|
2776
|
-
|
|
2858
|
+
const prefix2 = `${this.scope ?? ""}${SCOPE_SEPARATOR}`;
|
|
2859
|
+
return [...this.entries.keys()].filter((key) => key.startsWith(prefix2)).map(cacheKeyOf);
|
|
2860
|
+
}
|
|
2861
|
+
setScope(scope) {
|
|
2862
|
+
const before = this.scope;
|
|
2863
|
+
this.scope = scope;
|
|
2864
|
+
this.notifyScopes(before, scope);
|
|
2865
|
+
return () => {
|
|
2866
|
+
if (this.scope !== scope) return;
|
|
2867
|
+
this.scope = null;
|
|
2868
|
+
this.notifyScopes(scope, null);
|
|
2869
|
+
};
|
|
2777
2870
|
}
|
|
2778
2871
|
subscribe(key, callback) {
|
|
2779
2872
|
let set3 = this.subscribers.get(key);
|
|
@@ -2796,15 +2889,29 @@ class StagedStore {
|
|
|
2796
2889
|
this.errorListeners.delete(cb);
|
|
2797
2890
|
};
|
|
2798
2891
|
}
|
|
2799
|
-
/** Test-only: clear in-memory + persisted state. */
|
|
2892
|
+
/** Test-only: clear in-memory + persisted state, in every scope. */
|
|
2800
2893
|
async clear() {
|
|
2801
2894
|
const keys = [...this.entries.keys()];
|
|
2802
2895
|
this.entries.clear();
|
|
2803
|
-
for (const key of keys) this.notify(key);
|
|
2896
|
+
for (const key of new Set(keys.map(cacheKeyOf))) this.notify(key);
|
|
2804
2897
|
await this.flushPending();
|
|
2805
2898
|
await this.adapter.clear();
|
|
2806
2899
|
}
|
|
2807
2900
|
// ----- internals -------------------------------------------------------
|
|
2901
|
+
/** The key a path's entry is held under in the current scope. */
|
|
2902
|
+
keyOf(workspace, path2) {
|
|
2903
|
+
return `${this.scope ?? ""}${SCOPE_SEPARATOR}${datasetCacheKey(workspace, path2)}`;
|
|
2904
|
+
}
|
|
2905
|
+
/** Tells every path with an entry in either scope that what it reads has
|
|
2906
|
+
* changed: the scope's move shows one's entries and hides the other's. */
|
|
2907
|
+
notifyScopes(from2, to) {
|
|
2908
|
+
const keys = /* @__PURE__ */ new Set();
|
|
2909
|
+
for (const key of this.entries.keys()) {
|
|
2910
|
+
const scope = key.slice(0, key.indexOf(SCOPE_SEPARATOR));
|
|
2911
|
+
if (scope === (from2 ?? "") || scope === (to ?? "")) keys.add(cacheKeyOf(key));
|
|
2912
|
+
}
|
|
2913
|
+
for (const key of keys) this.notify(key);
|
|
2914
|
+
}
|
|
2808
2915
|
notify(key) {
|
|
2809
2916
|
this.versions.set(key, (this.versions.get(key) ?? 0) + 1);
|
|
2810
2917
|
const subs = this.subscribers.get(key);
|
|
@@ -2821,7 +2928,7 @@ class StagedStore {
|
|
|
2821
2928
|
const settled = prev2.then(op, op).then(
|
|
2822
2929
|
() => void 0,
|
|
2823
2930
|
(err) => {
|
|
2824
|
-
this.emitPersistError(key, err);
|
|
2931
|
+
this.emitPersistError(cacheKeyOf(key), err);
|
|
2825
2932
|
}
|
|
2826
2933
|
).then(() => {
|
|
2827
2934
|
if (this.saveChains.get(key) === settled) this.saveChains.delete(key);
|
|
@@ -2852,11 +2959,18 @@ class StagedStore {
|
|
|
2852
2959
|
return;
|
|
2853
2960
|
}
|
|
2854
2961
|
for (const [key, p2] of persisted) {
|
|
2962
|
+
if (!key.includes(SCOPE_SEPARATOR)) {
|
|
2963
|
+
this.persist(key, () => this.adapter.remove(key));
|
|
2964
|
+
this.emitPersistError(key, new Error(
|
|
2965
|
+
`a staged edit of ${key} that an older build left names no server or repository, and was dropped`
|
|
2966
|
+
));
|
|
2967
|
+
continue;
|
|
2968
|
+
}
|
|
2855
2969
|
this.entries.set(key, {
|
|
2856
2970
|
snapshot: p2.snapshot,
|
|
2857
2971
|
buffered: p2.buffered
|
|
2858
2972
|
});
|
|
2859
|
-
this.notify(key);
|
|
2973
|
+
this.notify(cacheKeyOf(key));
|
|
2860
2974
|
}
|
|
2861
2975
|
}
|
|
2862
2976
|
}
|
|
@@ -2973,8 +3087,14 @@ class BindRuntime {
|
|
|
2973
3087
|
}
|
|
2974
3088
|
this.cache = cache3;
|
|
2975
3089
|
}
|
|
2976
|
-
/**
|
|
2977
|
-
|
|
3090
|
+
/**
|
|
3091
|
+
* Clear the active cache and drop any queued writes.
|
|
3092
|
+
*
|
|
3093
|
+
* @param cache - Clear only while this is the active cache, so a provider
|
|
3094
|
+
* tearing down after another installed its own leaves that one in place
|
|
3095
|
+
*/
|
|
3096
|
+
clearCache(cache3) {
|
|
3097
|
+
if (cache3 !== void 0 && this.cache !== cache3) return;
|
|
2978
3098
|
this.cache = null;
|
|
2979
3099
|
this.clearPendingWrites();
|
|
2980
3100
|
this.handleCache.clear();
|
|
@@ -3299,6 +3419,10 @@ class BindRuntime {
|
|
|
3299
3419
|
this.queueWrite(() => cache3.launchDataflow(ws));
|
|
3300
3420
|
return null;
|
|
3301
3421
|
};
|
|
3422
|
+
const hasSource = () => {
|
|
3423
|
+
this.trackPath(ws, sourcePath);
|
|
3424
|
+
return cache3.has(ws, sourcePath);
|
|
3425
|
+
};
|
|
3302
3426
|
if (mode === "direct" && !patchPath) {
|
|
3303
3427
|
return {
|
|
3304
3428
|
read: () => {
|
|
@@ -3321,7 +3445,7 @@ class BindRuntime {
|
|
|
3321
3445
|
pending: () => false,
|
|
3322
3446
|
commit: () => null,
|
|
3323
3447
|
discard: () => null,
|
|
3324
|
-
has:
|
|
3448
|
+
has: hasSource,
|
|
3325
3449
|
status: () => {
|
|
3326
3450
|
this.trackPath(ws, sourcePath);
|
|
3327
3451
|
return cache3.getStatus(ws, sourcePath);
|
|
@@ -3370,7 +3494,7 @@ class BindRuntime {
|
|
|
3370
3494
|
this.queueWrite(() => cache3.write(ws, pPath2, encodePatch(UNCHANGED_VARIANT)));
|
|
3371
3495
|
return null;
|
|
3372
3496
|
},
|
|
3373
|
-
has:
|
|
3497
|
+
has: hasSource,
|
|
3374
3498
|
status: () => {
|
|
3375
3499
|
this.trackPath(ws, sourcePath);
|
|
3376
3500
|
return cache3.getStatus(ws, sourcePath);
|
|
@@ -3414,7 +3538,7 @@ class BindRuntime {
|
|
|
3414
3538
|
staged.discard(ws, sourcePath);
|
|
3415
3539
|
return null;
|
|
3416
3540
|
},
|
|
3417
|
-
has:
|
|
3541
|
+
has: hasSource,
|
|
3418
3542
|
status: () => {
|
|
3419
3543
|
this.trackPath(ws, sourcePath);
|
|
3420
3544
|
return cache3.getStatus(ws, sourcePath);
|
|
@@ -3461,7 +3585,7 @@ class BindRuntime {
|
|
|
3461
3585
|
staged.discard(ws, sourcePath);
|
|
3462
3586
|
return null;
|
|
3463
3587
|
},
|
|
3464
|
-
has:
|
|
3588
|
+
has: hasSource,
|
|
3465
3589
|
status: () => {
|
|
3466
3590
|
this.trackPath(ws, sourcePath);
|
|
3467
3591
|
return cache3.getStatus(ws, sourcePath);
|
|
@@ -3519,8 +3643,8 @@ function getReactiveDatasetCache() {
|
|
|
3519
3643
|
function initializeReactiveDatasetCache(cache3) {
|
|
3520
3644
|
defaultBindRuntime.initializeCache(cache3);
|
|
3521
3645
|
}
|
|
3522
|
-
function clearReactiveDatasetCache() {
|
|
3523
|
-
defaultBindRuntime.clearCache();
|
|
3646
|
+
function clearReactiveDatasetCache(cache3) {
|
|
3647
|
+
defaultBindRuntime.clearCache(cache3);
|
|
3524
3648
|
}
|
|
3525
3649
|
function enableBindingTracking() {
|
|
3526
3650
|
return defaultBindRuntime.enableTracking();
|
|
@@ -3700,7 +3824,11 @@ class TrackedChannelStore {
|
|
|
3700
3824
|
this.keyVersions.clear();
|
|
3701
3825
|
}
|
|
3702
3826
|
}
|
|
3703
|
-
|
|
3827
|
+
const NO_INDEX = { index: null, join: false };
|
|
3828
|
+
function selectorKey(selector2) {
|
|
3829
|
+
return selector2.index === null ? "" : `@${selector2.index}${selector2.join ? "+join" : ""}`;
|
|
3830
|
+
}
|
|
3831
|
+
function createDefaultPagedApi(apiUrl, repo, getToken, datasets) {
|
|
3704
3832
|
const opts = () => ({ token: getToken() });
|
|
3705
3833
|
return {
|
|
3706
3834
|
async getPage(workspace, path2, window3) {
|
|
@@ -3708,58 +3836,115 @@ function createDefaultPagedApi(apiUrl, repo, getToken) {
|
|
|
3708
3836
|
},
|
|
3709
3837
|
async findKey(workspace, path2, query) {
|
|
3710
3838
|
return e3ApiClient.datasetFindKey(apiUrl, repo, workspace, path2, query, opts());
|
|
3839
|
+
},
|
|
3840
|
+
async getRevision(workspace, path2) {
|
|
3841
|
+
const status = await e3ApiClient.datasetGetStatus(apiUrl, repo, workspace, path2, opts());
|
|
3842
|
+
return status.hash.type === "some" ? status.hash.value : null;
|
|
3843
|
+
},
|
|
3844
|
+
watchRevision(workspace, path2, onChange2) {
|
|
3845
|
+
return datasets.watchHash(workspace, path2, onChange2);
|
|
3711
3846
|
}
|
|
3712
3847
|
};
|
|
3713
3848
|
}
|
|
3714
3849
|
const RETRY_AFTER_MS = 2e3;
|
|
3715
3850
|
const MAX_RETAINED_WINDOWS = 24;
|
|
3716
|
-
const PERMANENT_PAGE_ERRORS = /* @__PURE__ */ new Set([
|
|
3717
|
-
|
|
3851
|
+
const PERMANENT_PAGE_ERRORS = /* @__PURE__ */ new Set([
|
|
3852
|
+
"bad_request",
|
|
3853
|
+
"dataset_not_canonical",
|
|
3854
|
+
"dataset_not_indexed",
|
|
3855
|
+
"dataset_not_pageable",
|
|
3856
|
+
"dataset_not_searchable",
|
|
3857
|
+
"dataset_not_segmented",
|
|
3858
|
+
"index_not_found",
|
|
3859
|
+
"key_parse_error"
|
|
3860
|
+
]);
|
|
3861
|
+
const NO_VALUE_ERRORS = /* @__PURE__ */ new Set(["dataset_unassigned", "dataset_null"]);
|
|
3862
|
+
function toSelector(indexArg, joinArg) {
|
|
3863
|
+
const index2 = indexArg;
|
|
3864
|
+
return {
|
|
3865
|
+
index: index2 !== void 0 && index2.type === "some" ? index2.value : null,
|
|
3866
|
+
join: joinArg === true
|
|
3867
|
+
};
|
|
3868
|
+
}
|
|
3869
|
+
function checkDeclared(allowed, path2) {
|
|
3870
|
+
if (allowed === null) return;
|
|
3871
|
+
const pathStr = datasetPathToString(path2);
|
|
3872
|
+
if (!allowed.has(pathStr)) {
|
|
3873
|
+
throw new Error(
|
|
3874
|
+
`Data.bindPaged: source path "${pathStr}" not declared in manifest — bind it in the task body so derivation records it`
|
|
3875
|
+
);
|
|
3876
|
+
}
|
|
3877
|
+
}
|
|
3878
|
+
function errorCode(err) {
|
|
3718
3879
|
const code = err == null ? void 0 : err.code;
|
|
3719
|
-
return typeof code === "string"
|
|
3880
|
+
return typeof code === "string" ? code : void 0;
|
|
3881
|
+
}
|
|
3882
|
+
function isPermanentPageError(err) {
|
|
3883
|
+
const code = errorCode(err);
|
|
3884
|
+
return code !== void 0 && PERMANENT_PAGE_ERRORS.has(code);
|
|
3885
|
+
}
|
|
3886
|
+
function failureOf(path2, err) {
|
|
3887
|
+
const code = errorCode(err);
|
|
3888
|
+
const details = err == null ? void 0 : err.details;
|
|
3889
|
+
const reason = code === void 0 ? err instanceof Error ? err.message : String(err) : typeof details === "string" && details !== "" ? `${code}: ${details}` : code;
|
|
3890
|
+
return `Data.bindPaged: ${datasetPathToString(path2)}: ${reason}`;
|
|
3720
3891
|
}
|
|
3721
3892
|
function keyTypeOf(sourceType) {
|
|
3722
3893
|
if (sourceType.type === "Dict") return sourceType.value.key;
|
|
3723
3894
|
if (sourceType.type === "Set") return sourceType.value;
|
|
3724
3895
|
return null;
|
|
3725
3896
|
}
|
|
3726
|
-
function buildSeek(sourceType, T2, pathExpr, platform2) {
|
|
3727
|
-
if (keyTypeOf(sourceType) === null) return east.none;
|
|
3897
|
+
function buildSeek(sourceType, T2, pathExpr, indexExpr, joinExpr, selector2, platform2) {
|
|
3898
|
+
if (selector2.index === null && keyTypeOf(sourceType) === null) return east.none;
|
|
3728
3899
|
const { seek } = internal$1.DataPagedPrimitives;
|
|
3729
3900
|
return east.some(east.East.compile(
|
|
3730
3901
|
east.East.function([eastUi.SeekQueryType], east.OptionType(eastUi.SeekRangeType), ($3, query) => {
|
|
3731
|
-
$3.return(seek([T2], pathExpr, query));
|
|
3902
|
+
$3.return(seek([T2], pathExpr, indexExpr, joinExpr, query));
|
|
3732
3903
|
}),
|
|
3733
3904
|
platform2
|
|
3734
3905
|
));
|
|
3735
3906
|
}
|
|
3736
|
-
function
|
|
3737
|
-
return `paged:${workspace}:${datasetPathToString(path2)}
|
|
3907
|
+
function pagedRevisionKey(workspace, path2) {
|
|
3908
|
+
return `paged:${workspace}:${datasetPathToString(path2)}#revision`;
|
|
3738
3909
|
}
|
|
3739
|
-
function
|
|
3740
|
-
return `paged:${workspace}:${datasetPathToString(path2)}
|
|
3910
|
+
function pagedWindowKey(workspace, path2, revision, offset, limit2, selector2 = NO_INDEX) {
|
|
3911
|
+
return `paged:${workspace}:${datasetPathToString(path2)}${selectorKey(selector2)}@${revision}#${offset}+${limit2}`;
|
|
3741
3912
|
}
|
|
3742
|
-
function
|
|
3743
|
-
|
|
3744
|
-
return `paged:${workspace}:${datasetPathToString(path2)}#seek:${q}`;
|
|
3913
|
+
function pagedTotalKey(workspace, path2, revision, selector2 = NO_INDEX) {
|
|
3914
|
+
return `paged:${workspace}:${datasetPathToString(path2)}${selectorKey(selector2)}@${revision}#total`;
|
|
3745
3915
|
}
|
|
3746
|
-
function
|
|
3916
|
+
function pagedSeekKey(workspace, path2, revision, query, selector2 = NO_INDEX) {
|
|
3917
|
+
const q = "key" in query ? `k=${query.key}` : "fields" in query ? `f=${query.fields.join("\0")}|p=${query.prefix ?? ""}` : "prefix" in query ? `p=${query.prefix}` : `r=${(query.from ?? []).join("\0")}|${(query.to ?? []).join("\0")}`;
|
|
3918
|
+
return `paged:${workspace}:${datasetPathToString(path2)}${selectorKey(selector2)}@${revision}#seek:${q}`;
|
|
3919
|
+
}
|
|
3920
|
+
function toFindQuery(query, selector2 = NO_INDEX) {
|
|
3747
3921
|
const q = query;
|
|
3748
|
-
|
|
3749
|
-
if (q.type === "
|
|
3922
|
+
const scope = selector2.index === null ? {} : { index: selector2.index };
|
|
3923
|
+
if (q.type === "key") return { key: q.value, ...scope };
|
|
3924
|
+
if (q.type === "prefix") return { prefix: q.value, ...scope };
|
|
3925
|
+
if (q.type === "range") {
|
|
3926
|
+
const r2 = q.value;
|
|
3927
|
+
const from2 = [...r2.from];
|
|
3928
|
+
const to = [...r2.to];
|
|
3929
|
+
if (from2.length === 0 && to.length === 0) {
|
|
3930
|
+
throw new Error("Data.bindPaged: a range seek names no end — give it a `from`, a `to`, or both");
|
|
3931
|
+
}
|
|
3932
|
+
return from2.length === 0 ? { to, ...scope } : to.length === 0 ? { from: from2, ...scope } : { from: from2, to, ...scope };
|
|
3933
|
+
}
|
|
3750
3934
|
const f2 = q.value;
|
|
3751
3935
|
const fields = [...f2.values];
|
|
3752
|
-
return f2.prefix.type === "some" ? { fields, prefix: f2.prefix.value } : { fields };
|
|
3936
|
+
return f2.prefix.type === "some" ? { fields, prefix: f2.prefix.value, ...scope } : { fields, ...scope };
|
|
3753
3937
|
}
|
|
3754
3938
|
class PagedRuntime extends TrackedChannelStore {
|
|
3755
3939
|
constructor() {
|
|
3756
3940
|
super(...arguments);
|
|
3757
3941
|
__publicField(this, "api", null);
|
|
3758
3942
|
__publicField(this, "workspace", null);
|
|
3759
|
-
// Compiled-handle cache (issue #106 perf): buildHandle compiles
|
|
3943
|
+
// Compiled-handle cache (issue #106 perf): buildHandle compiles several
|
|
3760
3944
|
// East.functions per bind, and binds re-run every reactive frame. The
|
|
3761
|
-
// method IR is a pure function of (sourceType,
|
|
3762
|
-
// resolve api/workspace LIVE, so a cached handle still
|
|
3945
|
+
// method IR is a pure function of (sourceType, path, selector, shape), and
|
|
3946
|
+
// the methods resolve api/workspace LIVE, so a cached handle still
|
|
3947
|
+
// re-binds.
|
|
3763
3948
|
//
|
|
3764
3949
|
// Keyed by TYPE first, then path — never by path alone. The window decoder
|
|
3765
3950
|
// is baked from `sourceType`, so a path re-bound at a different type (a
|
|
@@ -3774,6 +3959,10 @@ class PagedRuntime extends TrackedChannelStore {
|
|
|
3774
3959
|
/** Loaded window keys in least-recently-READ order (a Map preserves
|
|
3775
3960
|
* insertion order; a read re-inserts). Bounds the decoded-window cache. */
|
|
3776
3961
|
__publicField(this, "loadedWindows", /* @__PURE__ */ new Map());
|
|
3962
|
+
/** Each dataset's snapshot, by workspace and path. */
|
|
3963
|
+
__publicField(this, "snapshots", /* @__PURE__ */ new Map());
|
|
3964
|
+
/** Fetches launched so far — the source of every channel's `launchSeq`. */
|
|
3965
|
+
__publicField(this, "launches", 0);
|
|
3777
3966
|
}
|
|
3778
3967
|
/** Monotonic clock seam so tests can drive the retry gate. */
|
|
3779
3968
|
now() {
|
|
@@ -3784,17 +3973,33 @@ class PagedRuntime extends TrackedChannelStore {
|
|
|
3784
3973
|
}
|
|
3785
3974
|
// ----- wiring ----------------------------------------------------------
|
|
3786
3975
|
/** Install the API adapter + workspace — called by the React provider
|
|
3787
|
-
* (or a test/showcase harness) before any handle is used.
|
|
3976
|
+
* (or a test/showcase harness) before any handle is used. Another adapter
|
|
3977
|
+
* or workspace starts from nothing: no snapshot of the old one's carries
|
|
3978
|
+
* over. */
|
|
3788
3979
|
initialize(api3, workspace) {
|
|
3980
|
+
if (this.api !== api3 || this.workspace !== workspace) this.reset();
|
|
3789
3981
|
this.api = api3;
|
|
3790
3982
|
this.workspace = workspace;
|
|
3791
3983
|
}
|
|
3792
|
-
/**
|
|
3793
|
-
|
|
3984
|
+
/**
|
|
3985
|
+
* Tear down the adapter and all window state.
|
|
3986
|
+
*
|
|
3987
|
+
* @param api - Clear only while this is the installed adapter, so a
|
|
3988
|
+
* provider tearing down after another installed its own leaves that one
|
|
3989
|
+
* in place
|
|
3990
|
+
*/
|
|
3991
|
+
clear(api3) {
|
|
3992
|
+
if (api3 !== void 0 && this.api !== api3) return;
|
|
3794
3993
|
this.api = null;
|
|
3795
3994
|
this.workspace = null;
|
|
3796
|
-
this.
|
|
3995
|
+
this.reset();
|
|
3797
3996
|
this.handleCache.clear();
|
|
3997
|
+
}
|
|
3998
|
+
/** Stop following every dataset, and drop what every snapshot delivered. */
|
|
3999
|
+
reset() {
|
|
4000
|
+
for (const snapshot of this.snapshots.values()) snapshot.unwatch();
|
|
4001
|
+
this.snapshots.clear();
|
|
4002
|
+
this.clearChannels();
|
|
3798
4003
|
this.loadedWindows.clear();
|
|
3799
4004
|
}
|
|
3800
4005
|
resolveWorkspace() {
|
|
@@ -3805,7 +4010,178 @@ class PagedRuntime extends TrackedChannelStore {
|
|
|
3805
4010
|
}
|
|
3806
4011
|
return this.workspace;
|
|
3807
4012
|
}
|
|
3808
|
-
// -----
|
|
4013
|
+
// ----- snapshots -------------------------------------------------------
|
|
4014
|
+
/** A dataset's snapshot, followed from its first read until the runtime
|
|
4015
|
+
* is cleared. */
|
|
4016
|
+
snapshotOf(workspace, path2) {
|
|
4017
|
+
const key = `${workspace}:${datasetPathToString(path2)}`;
|
|
4018
|
+
const existing = this.snapshots.get(key);
|
|
4019
|
+
if (existing !== void 0) return existing;
|
|
4020
|
+
const snapshot = {
|
|
4021
|
+
key,
|
|
4022
|
+
revision: void 0,
|
|
4023
|
+
moves: 0,
|
|
4024
|
+
findSeq: 0,
|
|
4025
|
+
finding: false,
|
|
4026
|
+
channels: /* @__PURE__ */ new Set(),
|
|
4027
|
+
unwatch: () => {
|
|
4028
|
+
}
|
|
4029
|
+
};
|
|
4030
|
+
this.snapshots.set(key, snapshot);
|
|
4031
|
+
if (this.api !== null) {
|
|
4032
|
+
snapshot.unwatch = this.api.watchRevision(workspace, path2, (hash2) => this.moveTo(workspace, path2, snapshot, hash2));
|
|
4033
|
+
}
|
|
4034
|
+
return snapshot;
|
|
4035
|
+
}
|
|
4036
|
+
/** Whether a snapshot is still the runtime's, rather than one a clear or a
|
|
4037
|
+
* re-initialization dropped. */
|
|
4038
|
+
isLive(snapshot) {
|
|
4039
|
+
return this.snapshots.get(snapshot.key) === snapshot;
|
|
4040
|
+
}
|
|
4041
|
+
/** Whether a live snapshot is still pinned to `revision`. */
|
|
4042
|
+
pinnedTo(snapshot, revision) {
|
|
4043
|
+
return this.isLive(snapshot) && snapshot.revision === revision;
|
|
4044
|
+
}
|
|
4045
|
+
/**
|
|
4046
|
+
* The revision a read of `path` is pinned to — a hash, `null` while the
|
|
4047
|
+
* dataset has no value, or `undefined` while it is being found — tracked so
|
|
4048
|
+
* the read re-fires when the source moves.
|
|
4049
|
+
*
|
|
4050
|
+
* @throws The failed lookup's reason, until a read after the retry gap
|
|
4051
|
+
* looks again
|
|
4052
|
+
*/
|
|
4053
|
+
revisionFor(workspace, path2) {
|
|
4054
|
+
this.track(pagedRevisionKey(workspace, path2));
|
|
4055
|
+
const snapshot = this.snapshotOf(workspace, path2);
|
|
4056
|
+
if (snapshot.revision !== void 0 || snapshot.finding) return snapshot.revision;
|
|
4057
|
+
if (snapshot.findError !== void 0 && this.now() - (snapshot.findFailedAtMs ?? 0) < RETRY_AFTER_MS) {
|
|
4058
|
+
throw new Error(snapshot.findError);
|
|
4059
|
+
}
|
|
4060
|
+
this.find(workspace, path2, snapshot);
|
|
4061
|
+
return void 0;
|
|
4062
|
+
}
|
|
4063
|
+
/**
|
|
4064
|
+
* Ask the server which content the dataset holds, and move there — unless
|
|
4065
|
+
* a move lands first, which came from something no older than the answer.
|
|
4066
|
+
*/
|
|
4067
|
+
find(workspace, path2, snapshot) {
|
|
4068
|
+
const seq = ++snapshot.findSeq;
|
|
4069
|
+
const moves = snapshot.moves;
|
|
4070
|
+
snapshot.finding = true;
|
|
4071
|
+
const api3 = this.api;
|
|
4072
|
+
void (async () => {
|
|
4073
|
+
let revision;
|
|
4074
|
+
try {
|
|
4075
|
+
if (api3 === null) throw new Error("no PagedApi installed");
|
|
4076
|
+
revision = await api3.getRevision(workspace, path2);
|
|
4077
|
+
} catch (err) {
|
|
4078
|
+
if (!this.isLive(snapshot) || snapshot.findSeq !== seq) return;
|
|
4079
|
+
snapshot.finding = false;
|
|
4080
|
+
if (snapshot.revision !== void 0) return;
|
|
4081
|
+
snapshot.findError = failureOf(path2, err);
|
|
4082
|
+
snapshot.findFailedAtMs = this.now();
|
|
4083
|
+
console.error(`Data.bindPaged: could not find which content ${datasetPathToString(path2)} holds:`, err);
|
|
4084
|
+
this.notify(pagedRevisionKey(workspace, path2));
|
|
4085
|
+
return;
|
|
4086
|
+
}
|
|
4087
|
+
if (!this.isLive(snapshot) || snapshot.findSeq !== seq) return;
|
|
4088
|
+
snapshot.finding = false;
|
|
4089
|
+
if (snapshot.moves !== moves) return;
|
|
4090
|
+
this.moveTo(workspace, path2, snapshot, revision);
|
|
4091
|
+
})();
|
|
4092
|
+
}
|
|
4093
|
+
/**
|
|
4094
|
+
* Move a dataset's source to `revision`: drop what the old snapshot
|
|
4095
|
+
* delivered and re-fire every read of the dataset, so the windows and
|
|
4096
|
+
* searches still in use are fetched again, pinned to the new one. A move to
|
|
4097
|
+
* the revision the source already has changes nothing.
|
|
4098
|
+
*/
|
|
4099
|
+
moveTo(workspace, path2, snapshot, revision) {
|
|
4100
|
+
if (!this.isLive(snapshot) || snapshot.revision === revision) return;
|
|
4101
|
+
snapshot.revision = revision;
|
|
4102
|
+
snapshot.moves += 1;
|
|
4103
|
+
delete snapshot.findError;
|
|
4104
|
+
delete snapshot.findFailedAtMs;
|
|
4105
|
+
for (const key of snapshot.channels) {
|
|
4106
|
+
this.entries.delete(key);
|
|
4107
|
+
this.loadedWindows.delete(key);
|
|
4108
|
+
}
|
|
4109
|
+
snapshot.channels.clear();
|
|
4110
|
+
this.notify(pagedRevisionKey(workspace, path2));
|
|
4111
|
+
}
|
|
4112
|
+
/**
|
|
4113
|
+
* Handle what a pinned fetch threw when it is not the read's failure: a
|
|
4114
|
+
* refusal naming other content moves the source there, and "no value"
|
|
4115
|
+
* moves it to no snapshot. Returns whether the error was one of those, or
|
|
4116
|
+
* arrived after the source had already moved on.
|
|
4117
|
+
*/
|
|
4118
|
+
followed(workspace, path2, snapshot, revision, err) {
|
|
4119
|
+
if (!this.pinnedTo(snapshot, revision)) return true;
|
|
4120
|
+
if (err instanceof e3ApiClient.DatasetHashMismatchError) {
|
|
4121
|
+
if (err.currentHash !== null && err.currentHash !== revision) {
|
|
4122
|
+
this.moveTo(workspace, path2, snapshot, err.currentHash);
|
|
4123
|
+
return true;
|
|
4124
|
+
}
|
|
4125
|
+
this.find(workspace, path2, snapshot);
|
|
4126
|
+
return false;
|
|
4127
|
+
}
|
|
4128
|
+
const code = errorCode(err);
|
|
4129
|
+
if (code !== void 0 && NO_VALUE_ERRORS.has(code)) {
|
|
4130
|
+
this.moveTo(workspace, path2, snapshot, null);
|
|
4131
|
+
return true;
|
|
4132
|
+
}
|
|
4133
|
+
return false;
|
|
4134
|
+
}
|
|
4135
|
+
// ----- channels ----------------------------------------------------------
|
|
4136
|
+
/** A channel of the snapshot's current revision, dropped when it moves. */
|
|
4137
|
+
channel(snapshot, key) {
|
|
4138
|
+
snapshot.channels.add(key);
|
|
4139
|
+
return this.entry(key);
|
|
4140
|
+
}
|
|
4141
|
+
/** Whether a channel is due a fetch: never fetched, evicted, or failed
|
|
4142
|
+
* for a reason a retry can fix, longer ago than the retry gap. */
|
|
4143
|
+
due(entry) {
|
|
4144
|
+
if (entry.status === "idle") return true;
|
|
4145
|
+
if (entry.status !== "failed" || entry.permanent === true) return false;
|
|
4146
|
+
return this.now() - (entry.failedAtMs ?? 0) >= RETRY_AFTER_MS;
|
|
4147
|
+
}
|
|
4148
|
+
/**
|
|
4149
|
+
* Mark a channel's fetch as started, returning the fetch's sequence number.
|
|
4150
|
+
*
|
|
4151
|
+
* Deliberately does NOT notify — the read that triggers it runs inside a
|
|
4152
|
+
* render pass, and notifying there would re-enter the renderer. Only the
|
|
4153
|
+
* settle notifies.
|
|
4154
|
+
*/
|
|
4155
|
+
launch(entry) {
|
|
4156
|
+
entry.status = "running";
|
|
4157
|
+
entry.launchSeq = ++this.launches;
|
|
4158
|
+
return entry.launchSeq;
|
|
4159
|
+
}
|
|
4160
|
+
/** Apply a fetch's outcome if the channel still waits on it, re-firing the
|
|
4161
|
+
* channel's reads. Returns whether it applied. */
|
|
4162
|
+
settle(key, seq, mutate) {
|
|
4163
|
+
const entry = this.entries.get(key);
|
|
4164
|
+
if (entry === void 0 || entry.launchSeq !== seq) return false;
|
|
4165
|
+
mutate(entry);
|
|
4166
|
+
this.notify(key);
|
|
4167
|
+
return true;
|
|
4168
|
+
}
|
|
4169
|
+
/** Settle a fetch as failed, with the reason a read will throw. */
|
|
4170
|
+
fail(key, seq, reason, permanent) {
|
|
4171
|
+
this.settle(key, seq, (e3) => {
|
|
4172
|
+
e3.status = "failed";
|
|
4173
|
+
e3.error = reason;
|
|
4174
|
+
e3.failedAtMs = this.now();
|
|
4175
|
+
e3.permanent = permanent;
|
|
4176
|
+
});
|
|
4177
|
+
}
|
|
4178
|
+
/** What a read of a channel answers: its value once loaded, `none` while
|
|
4179
|
+
* in flight, or its failure, thrown. */
|
|
4180
|
+
answer(entry, value) {
|
|
4181
|
+
if (entry.status === "loaded" && value !== void 0) return east.some(value);
|
|
4182
|
+
if (entry.status === "failed") throw new Error(entry.error ?? "Data.bindPaged: the read failed");
|
|
4183
|
+
return east.none;
|
|
4184
|
+
}
|
|
3809
4185
|
/** Note a window as most-recently-read, and drop the coldest decoded
|
|
3810
4186
|
* windows once the cache exceeds its cap. An evicted window returns to
|
|
3811
4187
|
* `idle`, so the next read refetches it rather than reading a hole. */
|
|
@@ -3823,131 +4199,83 @@ class PagedRuntime extends TrackedChannelStore {
|
|
|
3823
4199
|
delete entry.window;
|
|
3824
4200
|
}
|
|
3825
4201
|
}
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
*
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
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;
|
|
4202
|
+
// ----- window loading --------------------------------------------------
|
|
4203
|
+
/** Start the pinned fetch for a window if it is due one, returning its
|
|
4204
|
+
* channel. */
|
|
4205
|
+
ensureWindow(sourceType, workspace, path2, snapshot, revision, offset, limit2, selector2, key) {
|
|
4206
|
+
const entry = this.channel(snapshot, key);
|
|
4207
|
+
if (!this.due(entry)) return entry;
|
|
4208
|
+
const seq = this.launch(entry);
|
|
3845
4209
|
const api3 = this.api;
|
|
3846
4210
|
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
4211
|
let page;
|
|
3862
4212
|
try {
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
e3.permanent = permanent;
|
|
4213
|
+
if (api3 === null) throw new Error("no PagedApi installed");
|
|
4214
|
+
page = await api3.getPage(workspace, path2, {
|
|
4215
|
+
offset,
|
|
4216
|
+
limit: limit2,
|
|
4217
|
+
hash: revision,
|
|
4218
|
+
...selector2.index !== null && { index: selector2.index, join: selector2.join }
|
|
3870
4219
|
});
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
);
|
|
4220
|
+
} catch (err) {
|
|
4221
|
+
if (this.followed(workspace, path2, snapshot, revision, err)) return;
|
|
4222
|
+
this.fail(key, seq, failureOf(path2, err), isPermanentPageError(err));
|
|
4223
|
+
console.error(`Data.bindPaged: fetch failed for ${key}:`, err);
|
|
3875
4224
|
return;
|
|
3876
4225
|
}
|
|
4226
|
+
if (!this.pinnedTo(snapshot, revision)) return;
|
|
3877
4227
|
let decoded;
|
|
3878
4228
|
try {
|
|
3879
4229
|
decoded = east.decodeBeast2For(sourceType)(page.data);
|
|
3880
4230
|
} catch (err) {
|
|
3881
|
-
|
|
3882
|
-
e3.status = "failed";
|
|
3883
|
-
e3.failedAtMs = this.now();
|
|
3884
|
-
});
|
|
4231
|
+
this.fail(key, seq, `${failureOf(path2, err)} (the window does not decode as the source's type)`, true);
|
|
3885
4232
|
console.error(`Data.bindPaged: decode failed for ${key}:`, err);
|
|
3886
4233
|
return;
|
|
3887
4234
|
}
|
|
3888
|
-
settle((e3) => {
|
|
4235
|
+
const landed = this.settle(key, seq, (e3) => {
|
|
3889
4236
|
e3.status = "loaded";
|
|
3890
4237
|
e3.window = decoded;
|
|
3891
4238
|
e3.total = page.totalElements;
|
|
4239
|
+
delete e3.error;
|
|
3892
4240
|
});
|
|
4241
|
+
if (!landed) return;
|
|
3893
4242
|
this.touchWindow(key);
|
|
3894
|
-
const totalKey = pagedTotalKey(workspace, path2);
|
|
3895
|
-
const totalEntry = this.
|
|
4243
|
+
const totalKey = pagedTotalKey(workspace, path2, revision, selector2);
|
|
4244
|
+
const totalEntry = this.channel(snapshot, totalKey);
|
|
3896
4245
|
if (totalEntry.total !== page.totalElements) {
|
|
3897
4246
|
totalEntry.total = page.totalElements;
|
|
3898
4247
|
totalEntry.status = "loaded";
|
|
3899
4248
|
this.notify(totalKey);
|
|
3900
4249
|
}
|
|
3901
4250
|
})();
|
|
4251
|
+
return entry;
|
|
3902
4252
|
}
|
|
3903
|
-
/**
|
|
3904
|
-
*
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
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;
|
|
4253
|
+
/** Start the pinned fence search for one key query if it is due one,
|
|
4254
|
+
* returning its channel — the seek sibling of {@link ensureWindow}. */
|
|
4255
|
+
ensureSeek(workspace, path2, snapshot, revision, query, key) {
|
|
4256
|
+
const entry = this.channel(snapshot, key);
|
|
4257
|
+
if (!this.due(entry)) return entry;
|
|
4258
|
+
const seq = this.launch(entry);
|
|
3919
4259
|
const api3 = this.api;
|
|
3920
4260
|
void (async () => {
|
|
3921
|
-
|
|
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
|
-
}
|
|
4261
|
+
let range;
|
|
3935
4262
|
try {
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
e3.status = "loaded";
|
|
3939
|
-
e3.range = range;
|
|
3940
|
-
});
|
|
4263
|
+
if (api3 === null) throw new Error("no PagedApi installed");
|
|
4264
|
+
range = await api3.findKey(workspace, path2, { ...query, hash: revision });
|
|
3941
4265
|
} catch (err) {
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
e3.status = "failed";
|
|
3945
|
-
e3.failedAtMs = this.now();
|
|
3946
|
-
e3.permanent = permanent;
|
|
3947
|
-
});
|
|
4266
|
+
if (this.followed(workspace, path2, snapshot, revision, err)) return;
|
|
4267
|
+
this.fail(key, seq, failureOf(path2, err), isPermanentPageError(err));
|
|
3948
4268
|
console.error(`Data.bindPaged: key search failed for ${key}:`, err);
|
|
4269
|
+
return;
|
|
3949
4270
|
}
|
|
4271
|
+
if (!this.pinnedTo(snapshot, revision)) return;
|
|
4272
|
+
this.settle(key, seq, (e3) => {
|
|
4273
|
+
e3.status = "loaded";
|
|
4274
|
+
e3.range = range;
|
|
4275
|
+
delete e3.error;
|
|
4276
|
+
});
|
|
3950
4277
|
})();
|
|
4278
|
+
return entry;
|
|
3951
4279
|
}
|
|
3952
4280
|
/**
|
|
3953
4281
|
* The low-level primitives backing handle methods, bound to THIS runtime.
|
|
@@ -3957,53 +4285,92 @@ class PagedRuntime extends TrackedChannelStore {
|
|
|
3957
4285
|
*/
|
|
3958
4286
|
buildPrimitives() {
|
|
3959
4287
|
return [
|
|
3960
|
-
internal$1.DataPagedPrimitives.page.implement((sourceType) => (pathArg, offsetArg, limitArg) => {
|
|
4288
|
+
internal$1.DataPagedPrimitives.page.implement((sourceType) => (pathArg, indexArg, joinArg, offsetArg, limitArg) => {
|
|
3961
4289
|
const workspace = this.resolveWorkspace();
|
|
3962
4290
|
const path2 = pathArg;
|
|
4291
|
+
const revision = this.revisionFor(workspace, path2);
|
|
4292
|
+
if (typeof revision !== "string") return east.none;
|
|
4293
|
+
const selector2 = toSelector(indexArg, joinArg);
|
|
3963
4294
|
const offset = Number(offsetArg);
|
|
3964
4295
|
const limit2 = Number(limitArg);
|
|
3965
|
-
const key = pagedWindowKey(workspace, path2, offset, limit2);
|
|
4296
|
+
const key = pagedWindowKey(workspace, path2, revision, offset, limit2, selector2);
|
|
3966
4297
|
this.track(key);
|
|
3967
|
-
this.ensureWindow(
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
4298
|
+
const entry = this.ensureWindow(
|
|
4299
|
+
sourceType,
|
|
4300
|
+
workspace,
|
|
4301
|
+
path2,
|
|
4302
|
+
this.snapshotOf(workspace, path2),
|
|
4303
|
+
revision,
|
|
4304
|
+
offset,
|
|
4305
|
+
limit2,
|
|
4306
|
+
selector2,
|
|
4307
|
+
key
|
|
4308
|
+
);
|
|
4309
|
+
if (entry.status === "loaded" && entry.window !== void 0) this.touchWindow(key);
|
|
4310
|
+
return this.answer(entry, entry.window);
|
|
3974
4311
|
}),
|
|
3975
|
-
internal$1.DataPagedPrimitives.total.implement((_sourceType) => (pathArg) => {
|
|
4312
|
+
internal$1.DataPagedPrimitives.total.implement((_sourceType) => (pathArg, indexArg, joinArg) => {
|
|
3976
4313
|
const workspace = this.resolveWorkspace();
|
|
3977
|
-
const
|
|
4314
|
+
const path2 = pathArg;
|
|
4315
|
+
const revision = this.revisionFor(workspace, path2);
|
|
4316
|
+
if (typeof revision !== "string") return east.none;
|
|
4317
|
+
const key = pagedTotalKey(workspace, path2, revision, toSelector(indexArg, joinArg));
|
|
3978
4318
|
this.track(key);
|
|
3979
|
-
const entry = this.
|
|
3980
|
-
return entry.total !== void 0 ? east.
|
|
4319
|
+
const entry = this.channel(this.snapshotOf(workspace, path2), key);
|
|
4320
|
+
return entry.total !== void 0 ? east.some(BigInt(entry.total)) : east.none;
|
|
3981
4321
|
}),
|
|
3982
|
-
internal$1.DataPagedPrimitives.seek.implement((_sourceType) => (pathArg, queryArg) => {
|
|
4322
|
+
internal$1.DataPagedPrimitives.seek.implement((_sourceType) => (pathArg, indexArg, joinArg, queryArg) => {
|
|
3983
4323
|
const workspace = this.resolveWorkspace();
|
|
3984
4324
|
const path2 = pathArg;
|
|
3985
|
-
const
|
|
3986
|
-
|
|
4325
|
+
const revision = this.revisionFor(workspace, path2);
|
|
4326
|
+
if (typeof revision !== "string") return east.none;
|
|
4327
|
+
const selector2 = toSelector(indexArg, joinArg);
|
|
4328
|
+
const query = toFindQuery(queryArg, selector2);
|
|
4329
|
+
const key = pagedSeekKey(workspace, path2, revision, query, selector2);
|
|
3987
4330
|
this.track(key);
|
|
3988
|
-
this.ensureSeek(workspace, path2, query, key);
|
|
3989
|
-
const
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
4331
|
+
const entry = this.ensureSeek(workspace, path2, this.snapshotOf(workspace, path2), revision, query, key);
|
|
4332
|
+
const range = entry.range;
|
|
4333
|
+
return this.answer(entry, range === void 0 ? void 0 : { found: range.found, row: BigInt(range.row), count: BigInt(range.count) });
|
|
4334
|
+
}),
|
|
4335
|
+
internal$1.DataPagedPrimitives.revision.implement((_sourceType) => (pathArg, _indexArg, _joinArg) => {
|
|
4336
|
+
const revision = this.revisionFor(this.resolveWorkspace(), pathArg);
|
|
4337
|
+
return typeof revision === "string" ? east.some(revision) : east.none;
|
|
4338
|
+
}),
|
|
4339
|
+
internal$1.DataPagedPrimitives.refresh.implement((_sourceType) => (pathArg, _indexArg, _joinArg, targetArg) => {
|
|
4340
|
+
const workspace = this.resolveWorkspace();
|
|
4341
|
+
const path2 = pathArg;
|
|
4342
|
+
const target = targetArg;
|
|
4343
|
+
queueMicrotask(() => {
|
|
4344
|
+
if (this.workspace !== workspace) return;
|
|
4345
|
+
const snapshot = this.snapshotOf(workspace, path2);
|
|
4346
|
+
if (target.type === "some") this.moveTo(workspace, path2, snapshot, target.value);
|
|
4347
|
+
else this.find(workspace, path2, snapshot);
|
|
4348
|
+
});
|
|
4349
|
+
return null;
|
|
3993
4350
|
})
|
|
3994
4351
|
];
|
|
3995
4352
|
}
|
|
3996
4353
|
/**
|
|
3997
|
-
* Build the handle value for one
|
|
3998
|
-
*
|
|
3999
|
-
*
|
|
4000
|
-
* type-arg) — so the handle is ordinary serializable East data (issue
|
|
4354
|
+
* Build the handle value for one paged bind. Every method is a thin
|
|
4355
|
+
* IR-bearing `East.function` over {@link buildPrimitives}, capturing only
|
|
4356
|
+
* the plain-data source path and selector (the value type rides as a
|
|
4357
|
+
* type-arg) — so the handle is ordinary serializable East data (issue
|
|
4358
|
+
* #106).
|
|
4359
|
+
*
|
|
4360
|
+
* @param sourceType - The dataset's type, or an index's window type
|
|
4361
|
+
* @param path - The dataset's path
|
|
4362
|
+
* @param selector - The rows the bind serves
|
|
4363
|
+
* @param shape - `released`: the handle `data_bind_paged` returns, without
|
|
4364
|
+
* `revision` or `refresh`; `pinned`: the handle `data_bind_paged_pinned`
|
|
4365
|
+
* returns. Both read through the same snapshot.
|
|
4366
|
+
* @returns The handle
|
|
4001
4367
|
*/
|
|
4002
|
-
buildHandle(sourceType, path2) {
|
|
4003
|
-
const
|
|
4368
|
+
buildHandle(sourceType, path2, selector2, shape) {
|
|
4369
|
+
const id3 = `${datasetPathToString(path2)}${selectorKey(selector2)}`;
|
|
4370
|
+
const cacheKey = `${id3}#${shape}`;
|
|
4004
4371
|
let byPath = this.handleCache.get(sourceType);
|
|
4005
4372
|
if (byPath) {
|
|
4006
|
-
const hit = byPath.get(
|
|
4373
|
+
const hit = byPath.get(cacheKey);
|
|
4007
4374
|
if (hit) return hit;
|
|
4008
4375
|
} else {
|
|
4009
4376
|
byPath = /* @__PURE__ */ new Map();
|
|
@@ -4011,24 +4378,27 @@ class PagedRuntime extends TrackedChannelStore {
|
|
|
4011
4378
|
}
|
|
4012
4379
|
const T2 = east.fromEastTypeValue(sourceType);
|
|
4013
4380
|
const pathExpr = east.East.value(path2, e3Types.TreePathType);
|
|
4381
|
+
const indexExpr = east.East.value(selector2.index === null ? east.none : east.some(selector2.index), east.OptionType(east.StringType));
|
|
4382
|
+
const joinExpr = east.East.value(selector2.join, east.BooleanType);
|
|
4014
4383
|
const platform2 = this.buildPrimitives();
|
|
4015
|
-
const { page, total } = internal$1.DataPagedPrimitives;
|
|
4384
|
+
const { page, total, revision, refresh } = internal$1.DataPagedPrimitives;
|
|
4016
4385
|
const handle = {
|
|
4017
4386
|
// The comparable identity east-ui's `PagedSourceType` requires:
|
|
4018
4387
|
// East compares every function as EQUAL, so a struct of nothing but
|
|
4019
4388
|
// closures is indistinguishable from any other and a memoized
|
|
4020
4389
|
// component would never re-render on a source swap (#567 D4). The
|
|
4021
|
-
// dataset path
|
|
4022
|
-
|
|
4390
|
+
// dataset path and selector are the natural identity — same path,
|
|
4391
|
+
// same rows. The snapshot they serve is `revision`'s, not the id's.
|
|
4392
|
+
id: id3,
|
|
4023
4393
|
page: east.East.compile(
|
|
4024
4394
|
east.East.function([east.IntegerType, east.IntegerType], east.OptionType(T2), ($3, offset, limit2) => {
|
|
4025
|
-
$3.return(page([T2], pathExpr, offset, limit2));
|
|
4395
|
+
$3.return(page([T2], pathExpr, indexExpr, joinExpr, offset, limit2));
|
|
4026
4396
|
}),
|
|
4027
4397
|
platform2
|
|
4028
4398
|
),
|
|
4029
4399
|
total: east.East.compile(
|
|
4030
4400
|
east.East.function([], east.OptionType(east.IntegerType), ($3) => {
|
|
4031
|
-
$3.return(total([T2], pathExpr));
|
|
4401
|
+
$3.return(total([T2], pathExpr, indexExpr, joinExpr));
|
|
4032
4402
|
}),
|
|
4033
4403
|
platform2
|
|
4034
4404
|
),
|
|
@@ -4037,28 +4407,50 @@ class PagedRuntime extends TrackedChannelStore {
|
|
|
4037
4407
|
// segments) against the stored fences; an Array's stream order has
|
|
4038
4408
|
// nothing to search. Resolved at bind time from the dataset's own
|
|
4039
4409
|
// type, so a component renders the affordance only when it works.
|
|
4040
|
-
seek: buildSeek(sourceType, T2, pathExpr, platform2)
|
|
4410
|
+
seek: buildSeek(sourceType, T2, pathExpr, indexExpr, joinExpr, selector2, platform2)
|
|
4041
4411
|
};
|
|
4042
|
-
|
|
4412
|
+
if (shape === "pinned") {
|
|
4413
|
+
handle.revision = east.East.compile(
|
|
4414
|
+
east.East.function([], east.OptionType(east.StringType), ($3) => {
|
|
4415
|
+
$3.return(revision([T2], pathExpr, indexExpr, joinExpr));
|
|
4416
|
+
}),
|
|
4417
|
+
platform2
|
|
4418
|
+
);
|
|
4419
|
+
handle.refresh = east.East.compile(
|
|
4420
|
+
east.East.function([east.OptionType(east.StringType)], east.NullType, ($3, target) => {
|
|
4421
|
+
$3.return(refresh([T2], pathExpr, indexExpr, joinExpr, target));
|
|
4422
|
+
}),
|
|
4423
|
+
platform2
|
|
4424
|
+
);
|
|
4425
|
+
}
|
|
4426
|
+
byPath.set(cacheKey, handle);
|
|
4043
4427
|
return handle;
|
|
4044
4428
|
}
|
|
4045
4429
|
// ----- platform building -------------------------------------------------
|
|
4046
|
-
/** Build
|
|
4047
|
-
*
|
|
4048
|
-
*
|
|
4430
|
+
/** Build the PlatformFunction behind `data_bind_paged` — the bind a UI
|
|
4431
|
+
* exported before pinned reads calls — bound to this runtime. Its handle
|
|
4432
|
+
* has no `revision` or `refresh`, but its reads are pinned, follow the
|
|
4433
|
+
* dataset and fail visibly all the same. Pass `allowed=null` for an
|
|
4434
|
+
* unscoped impl, or a Set of path strings for manifest scoping. */
|
|
4049
4435
|
buildPlatform(allowed) {
|
|
4050
4436
|
return internal$1.bindPagedPlatformFn.implement(
|
|
4051
4437
|
(sourceType) => (pathArg) => {
|
|
4052
4438
|
const path2 = pathArg;
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4439
|
+
checkDeclared(allowed, path2);
|
|
4440
|
+
return this.buildHandle(sourceType, path2, NO_INDEX, "released");
|
|
4441
|
+
}
|
|
4442
|
+
);
|
|
4443
|
+
}
|
|
4444
|
+
/** Build the PlatformFunction behind `data_bind_paged_pinned` — what
|
|
4445
|
+
* `Data.bindPaged` emits, for a dataset's own rows or a record's index —
|
|
4446
|
+
* bound to this runtime and scoped by `allowed` exactly as
|
|
4447
|
+
* {@link buildPlatform} is: an index read is a read of the record's path. */
|
|
4448
|
+
buildPinnedPlatform(allowed) {
|
|
4449
|
+
return internal$1.bindPagedPinnedPlatformFn.implement(
|
|
4450
|
+
(sourceType) => (pathArg, indexArg, joinArg) => {
|
|
4451
|
+
const path2 = pathArg;
|
|
4452
|
+
checkDeclared(allowed, path2);
|
|
4453
|
+
return this.buildHandle(sourceType, path2, toSelector(indexArg, joinArg), "pinned");
|
|
4062
4454
|
}
|
|
4063
4455
|
);
|
|
4064
4456
|
}
|
|
@@ -4067,17 +4459,19 @@ const defaultPagedRuntime = new PagedRuntime();
|
|
|
4067
4459
|
function initializePagedApi(api3, workspace) {
|
|
4068
4460
|
defaultPagedRuntime.initialize(api3, workspace);
|
|
4069
4461
|
}
|
|
4070
|
-
function clearPagedApi() {
|
|
4071
|
-
defaultPagedRuntime.clear();
|
|
4462
|
+
function clearPagedApi(api3) {
|
|
4463
|
+
defaultPagedRuntime.clear(api3);
|
|
4072
4464
|
}
|
|
4073
4465
|
const PagedPlatform = [
|
|
4074
4466
|
defaultPagedRuntime.buildPlatform(null),
|
|
4467
|
+
defaultPagedRuntime.buildPinnedPlatform(null),
|
|
4075
4468
|
...defaultPagedRuntime.buildPrimitives()
|
|
4076
4469
|
];
|
|
4077
4470
|
function createScopedPagedPlatform(pages) {
|
|
4078
4471
|
const allowed = new Set(pages.map((p2) => datasetPathToString(p2)));
|
|
4079
4472
|
return [
|
|
4080
4473
|
defaultPagedRuntime.buildPlatform(allowed),
|
|
4474
|
+
defaultPagedRuntime.buildPinnedPlatform(allowed),
|
|
4081
4475
|
...defaultPagedRuntime.buildPrimitives()
|
|
4082
4476
|
];
|
|
4083
4477
|
}
|
|
@@ -4203,8 +4597,15 @@ class FuncRuntime extends TrackedChannelStore {
|
|
|
4203
4597
|
this.api = api3;
|
|
4204
4598
|
this.workspace = workspace;
|
|
4205
4599
|
}
|
|
4206
|
-
/**
|
|
4207
|
-
|
|
4600
|
+
/**
|
|
4601
|
+
* Tear down the adapter and all call state.
|
|
4602
|
+
*
|
|
4603
|
+
* @param api - Clear only while this is the installed adapter, so a
|
|
4604
|
+
* provider tearing down after another installed its own leaves that one
|
|
4605
|
+
* in place
|
|
4606
|
+
*/
|
|
4607
|
+
clear(api3) {
|
|
4608
|
+
if (api3 !== void 0 && this.api !== api3) return;
|
|
4208
4609
|
this.api = null;
|
|
4209
4610
|
this.workspace = null;
|
|
4210
4611
|
this.clearChannels();
|
|
@@ -4437,8 +4838,8 @@ const defaultFuncRuntime = new FuncRuntime();
|
|
|
4437
4838
|
function initializeFunctionApi(api3, workspace) {
|
|
4438
4839
|
defaultFuncRuntime.initialize(api3, workspace);
|
|
4439
4840
|
}
|
|
4440
|
-
function clearFunctionApi() {
|
|
4441
|
-
defaultFuncRuntime.clear();
|
|
4841
|
+
function clearFunctionApi(api3) {
|
|
4842
|
+
defaultFuncRuntime.clear(api3);
|
|
4442
4843
|
}
|
|
4443
4844
|
const FuncPlatform = [
|
|
4444
4845
|
defaultFuncRuntime.buildPlatform(null),
|
|
@@ -8211,12 +8612,6 @@ function errorOfMutationResult(result) {
|
|
|
8211
8612
|
message: `reducer exited with code ${outcome.value.exitCode}`,
|
|
8212
8613
|
stderr: outcome.value.stderr
|
|
8213
8614
|
};
|
|
8214
|
-
case "too_large":
|
|
8215
|
-
return {
|
|
8216
|
-
kind: east.variant("too_large", { bytes: outcome.value.bytes, limit: outcome.value.limit }),
|
|
8217
|
-
message: `new state too large (${outcome.value.bytes} bytes, limit ${outcome.value.limit})`,
|
|
8218
|
-
stderr: outcome.value.stderr
|
|
8219
|
-
};
|
|
8220
8615
|
case "timed_out":
|
|
8221
8616
|
return {
|
|
8222
8617
|
kind: east.variant("timed_out", { ms: outcome.value.ms }),
|
|
@@ -8276,8 +8671,15 @@ class RecordRuntime extends TrackedChannelStore {
|
|
|
8276
8671
|
this.cache = cache3;
|
|
8277
8672
|
this.workspace = workspace;
|
|
8278
8673
|
}
|
|
8279
|
-
/**
|
|
8280
|
-
|
|
8674
|
+
/**
|
|
8675
|
+
* Tear down the adapter and all record state.
|
|
8676
|
+
*
|
|
8677
|
+
* @param api - Clear only while this is the installed adapter, so a
|
|
8678
|
+
* provider tearing down after another installed its own leaves that one
|
|
8679
|
+
* in place
|
|
8680
|
+
*/
|
|
8681
|
+
clear(api3) {
|
|
8682
|
+
if (api3 !== void 0 && this.api !== api3) return;
|
|
8281
8683
|
this.api = null;
|
|
8282
8684
|
this.cache = null;
|
|
8283
8685
|
this.workspace = null;
|
|
@@ -8585,8 +8987,8 @@ const defaultRecordRuntime = new RecordRuntime();
|
|
|
8585
8987
|
function initializeRecordApi(api3, cache3, workspace) {
|
|
8586
8988
|
defaultRecordRuntime.initialize(api3, cache3, workspace);
|
|
8587
8989
|
}
|
|
8588
|
-
function clearRecordApi() {
|
|
8589
|
-
defaultRecordRuntime.clear();
|
|
8990
|
+
function clearRecordApi(api3) {
|
|
8991
|
+
defaultRecordRuntime.clear(api3);
|
|
8590
8992
|
}
|
|
8591
8993
|
const RecordPlatform = [
|
|
8592
8994
|
defaultRecordRuntime.buildPlatform(null),
|
|
@@ -8612,7 +9014,8 @@ function createInMemoryRecordApi(cache3, workspace, defs2) {
|
|
|
8612
9014
|
state: `${def.name}-state-0`.padEnd(64, "0"),
|
|
8613
9015
|
mutation: "$init",
|
|
8614
9016
|
actor: "memory",
|
|
8615
|
-
at: /* @__PURE__ */ new Date(0)
|
|
9017
|
+
at: /* @__PURE__ */ new Date(0),
|
|
9018
|
+
delta: east.none
|
|
8616
9019
|
};
|
|
8617
9020
|
compiled.set(def.name, { stateType, mutations, commits: [genesis], seq: 0 });
|
|
8618
9021
|
void cache3.write(workspace, recordPath(def.name), east.encodeBeast2For(stateType)(def.initial));
|
|
@@ -8644,7 +9047,8 @@ function createInMemoryRecordApi(cache3, workspace, defs2) {
|
|
|
8644
9047
|
state: `${record}-state-${c2.seq}`.padEnd(64, "0"),
|
|
8645
9048
|
mutation,
|
|
8646
9049
|
actor: "memory",
|
|
8647
|
-
at: /* @__PURE__ */ new Date(0)
|
|
9050
|
+
at: /* @__PURE__ */ new Date(0),
|
|
9051
|
+
delta: east.none
|
|
8648
9052
|
});
|
|
8649
9053
|
return { outcome: east.variant("committed", { commitHash: hash2, stateHash: `${record}-state-${c2.seq}`.padEnd(64, "0") }) };
|
|
8650
9054
|
},
|
|
@@ -8693,68 +9097,49 @@ function ReactiveDatasetProvider({
|
|
|
8693
9097
|
children: children2
|
|
8694
9098
|
}) {
|
|
8695
9099
|
const e3 = useE3Config();
|
|
9100
|
+
const { apiUrl, workspace } = e3;
|
|
9101
|
+
const repo = e3.repo ?? "default";
|
|
8696
9102
|
const tokenRef = React.useRef(e3.token ?? null);
|
|
8697
9103
|
tokenRef.current = e3.token ?? null;
|
|
8698
|
-
const
|
|
8699
|
-
() => createDefaultDatasetApi(e3.apiUrl, e3.repo ?? "default", () => tokenRef.current),
|
|
8700
|
-
[e3.apiUrl, e3.repo]
|
|
8701
|
-
);
|
|
8702
|
-
const cache3 = React.useMemo(() => {
|
|
8703
|
-
const cfg = {};
|
|
8704
|
-
if (e3.workspace !== void 0) cfg.workspace = e3.workspace;
|
|
8705
|
-
return new ReactiveDatasetCache(cfg, api3);
|
|
8706
|
-
}, [api3, e3.workspace]);
|
|
8707
|
-
React.useMemo(() => {
|
|
8708
|
-
initializeReactiveDatasetCache(cache3);
|
|
8709
|
-
cache3.setScheduler((notify3) => queueMicrotask(notify3));
|
|
8710
|
-
}, [cache3]);
|
|
8711
|
-
React.useMemo(() => {
|
|
8712
|
-
if (e3.workspace !== void 0) {
|
|
8713
|
-
initializeFunctionApi(
|
|
8714
|
-
createDefaultFunctionApi(e3.apiUrl, e3.repo ?? "default", () => tokenRef.current),
|
|
8715
|
-
e3.workspace
|
|
8716
|
-
);
|
|
8717
|
-
}
|
|
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]);
|
|
8727
|
-
React.useMemo(() => {
|
|
8728
|
-
if (e3.workspace !== void 0) {
|
|
8729
|
-
initializeRecordApi(
|
|
8730
|
-
createDefaultRecordApi(e3.apiUrl, e3.repo ?? "default", () => tokenRef.current),
|
|
8731
|
-
cache3,
|
|
8732
|
-
e3.workspace
|
|
8733
|
-
);
|
|
8734
|
-
}
|
|
8735
|
-
}, [e3.apiUrl, e3.repo, e3.workspace, cache3]);
|
|
9104
|
+
const [installed, setInstalled] = React.useState(null);
|
|
8736
9105
|
React.useEffect(() => {
|
|
9106
|
+
const getToken = () => tokenRef.current;
|
|
9107
|
+
const cache22 = new ReactiveDatasetCache(
|
|
9108
|
+
workspace !== void 0 ? { workspace } : {},
|
|
9109
|
+
createDefaultDatasetApi(apiUrl, repo, getToken)
|
|
9110
|
+
);
|
|
9111
|
+
cache22.setScheduler((notify3) => queueMicrotask(notify3));
|
|
9112
|
+
initializeReactiveDatasetCache(cache22);
|
|
9113
|
+
const functionApi = createDefaultFunctionApi(apiUrl, repo, getToken);
|
|
9114
|
+
const pagedApi = createDefaultPagedApi(apiUrl, repo, getToken, cache22);
|
|
9115
|
+
const recordApi = createDefaultRecordApi(apiUrl, repo, getToken);
|
|
9116
|
+
if (workspace !== void 0) {
|
|
9117
|
+
initializeFunctionApi(functionApi, workspace);
|
|
9118
|
+
initializePagedApi(pagedApi, workspace);
|
|
9119
|
+
initializeRecordApi(recordApi, cache22, workspace);
|
|
9120
|
+
}
|
|
9121
|
+
const unscopeStaged = getStagedStore().setScope(JSON.stringify([apiUrl, repo]));
|
|
9122
|
+
setInstalled({ apiUrl, repo, workspace, cache: cache22 });
|
|
8737
9123
|
return () => {
|
|
8738
|
-
|
|
8739
|
-
|
|
8740
|
-
|
|
8741
|
-
|
|
8742
|
-
|
|
8743
|
-
|
|
8744
|
-
if (
|
|
9124
|
+
clearReactiveDatasetCache(cache22);
|
|
9125
|
+
clearFunctionApi(functionApi);
|
|
9126
|
+
clearPagedApi(pagedApi);
|
|
9127
|
+
clearRecordApi(recordApi);
|
|
9128
|
+
unscopeStaged();
|
|
9129
|
+
cache22.destroy();
|
|
9130
|
+
if (workspace) clearBindingRegistry(workspace);
|
|
8745
9131
|
else clearBindingRegistry();
|
|
8746
9132
|
};
|
|
8747
|
-
}, [
|
|
9133
|
+
}, [apiUrl, repo, workspace]);
|
|
9134
|
+
const cache3 = installed !== null && installed.apiUrl === apiUrl && installed.repo === repo && installed.workspace === workspace ? installed.cache : null;
|
|
8748
9135
|
React.useEffect(() => {
|
|
8749
|
-
if (typeof window
|
|
8750
|
-
|
|
8751
|
-
}
|
|
9136
|
+
if (cache3 === null || typeof window === "undefined") return;
|
|
9137
|
+
window.__EAST_REACTIVE_DATASET_CACHE__ = cache3;
|
|
8752
9138
|
return () => {
|
|
8753
|
-
|
|
8754
|
-
delete window.__EAST_REACTIVE_DATASET_CACHE__;
|
|
8755
|
-
}
|
|
9139
|
+
delete window.__EAST_REACTIVE_DATASET_CACHE__;
|
|
8756
9140
|
};
|
|
8757
9141
|
}, [cache3]);
|
|
9142
|
+
if (cache3 === null) return null;
|
|
8758
9143
|
return /* @__PURE__ */ jsxRuntime.jsx(ReactiveDatasetCacheContext.Provider, { value: cache3, children: children2 });
|
|
8759
9144
|
}
|
|
8760
9145
|
function useReactiveDatasetCache() {
|
|
@@ -64206,6 +64591,7 @@ function useDatasetValue(apiUrl, repo, workspace, datasetPath, options2) {
|
|
|
64206
64591
|
const { requestOptions, queryOverrides, enabled = true, hash: hash2, platforms, type } = options2;
|
|
64207
64592
|
const platformImpls = platforms ?? defaultPlatformImplementations;
|
|
64208
64593
|
const reqOpts = requestOptions ?? { token: null };
|
|
64594
|
+
const queryClient = reactQuery.useQueryClient();
|
|
64209
64595
|
const pathParts = React.useMemo(
|
|
64210
64596
|
() => (datasetPath == null ? void 0 : datasetPath.split(".").filter(Boolean).map((v3) => east.variant("field", v3))) ?? [],
|
|
64211
64597
|
[datasetPath]
|
|
@@ -64215,8 +64601,14 @@ function useDatasetValue(apiUrl, repo, workspace, datasetPath, options2) {
|
|
|
64215
64601
|
queryFn: async () => {
|
|
64216
64602
|
const result = await e3ApiClient.datasetGet(apiUrl, repo, workspace, pathParts, reqOpts);
|
|
64217
64603
|
const decoded = east.decodeBeast2For(type, { platform: platformImpls })(result.data);
|
|
64218
|
-
|
|
64604
|
+
const value = { decoded, sizeBytes: result.data.length, hash: result.hash ?? hash2 ?? null };
|
|
64605
|
+
if (value.hash !== hash2) {
|
|
64606
|
+
queryClient.setQueryData(["datasetValue", apiUrl, repo, workspace, datasetPath, value.hash], value);
|
|
64607
|
+
void queryClient.invalidateQueries({ queryKey: ["datasetStatus", apiUrl, repo, workspace, datasetPath] });
|
|
64608
|
+
}
|
|
64609
|
+
return value;
|
|
64219
64610
|
},
|
|
64611
|
+
select: (value) => value.hash === (hash2 ?? null) ? value : void 0,
|
|
64220
64612
|
enabled: enabled && !!workspace && !!datasetPath && hash2 != null,
|
|
64221
64613
|
...queryOverrides
|
|
64222
64614
|
});
|
|
@@ -64282,14 +64674,6 @@ function useTaskDetails(apiUrl, repo, workspace, taskName, options2) {
|
|
|
64282
64674
|
...options2 == null ? void 0 : options2.queryOverrides
|
|
64283
64675
|
});
|
|
64284
64676
|
}
|
|
64285
|
-
function getTaskKind(details) {
|
|
64286
|
-
var _a2;
|
|
64287
|
-
return ((_a2 = details.kind) == null ? void 0 : _a2.type) === "some" ? details.kind.value : null;
|
|
64288
|
-
}
|
|
64289
|
-
function getTaskMetadata(details) {
|
|
64290
|
-
var _a2;
|
|
64291
|
-
return ((_a2 = details.metadata) == null ? void 0 : _a2.type) === "some" ? details.metadata.value : null;
|
|
64292
|
-
}
|
|
64293
64677
|
const variantStyles = {
|
|
64294
64678
|
// Mode-aware semantic tokens (the raw `red.50` / `gray.50` washes rendered
|
|
64295
64679
|
// as bright light boxes in dark mode — #362).
|
|
@@ -64473,17 +64857,21 @@ const PagedDatasetPreview = React.memo(function PagedDatasetPreview2({
|
|
|
64473
64857
|
const [error3, setError] = React.useState(null);
|
|
64474
64858
|
const [jumpRow, setJumpRow] = React.useState(void 0);
|
|
64475
64859
|
const inflightRef = React.useRef(/* @__PURE__ */ new Set());
|
|
64860
|
+
const shownRef = React.useRef({ path: path2, hash: hash2 });
|
|
64476
64861
|
React.useEffect(() => {
|
|
64477
64862
|
pagingDebug(`reset (path=${path2}, hash=${hash2.slice(0, 8)})`);
|
|
64863
|
+
shownRef.current = { path: path2, hash: hash2 };
|
|
64478
64864
|
setPages(/* @__PURE__ */ new Map());
|
|
64479
64865
|
setTotals(null);
|
|
64480
64866
|
setError(null);
|
|
64481
64867
|
setJumpRow(void 0);
|
|
64482
|
-
inflightRef.current
|
|
64868
|
+
inflightRef.current = /* @__PURE__ */ new Set();
|
|
64483
64869
|
}, [path2, hash2]);
|
|
64484
64870
|
const loadPage = React.useCallback((pageIdx) => {
|
|
64485
|
-
|
|
64486
|
-
|
|
64871
|
+
const inflight = inflightRef.current;
|
|
64872
|
+
if (inflight.has(pageIdx)) return;
|
|
64873
|
+
inflight.add(pageIdx);
|
|
64874
|
+
const current = () => shownRef.current.path === path2 && shownRef.current.hash === hash2;
|
|
64487
64875
|
setLoadingCount((n2) => n2 + 1);
|
|
64488
64876
|
pagingDebug(`fetch p${pageIdx} (offset=${pageIdx * PAGE_SIZE}, limit=${PAGE_SIZE}, hash=${hash2.slice(0, 8)})`);
|
|
64489
64877
|
const pathParts2 = path2.split(".").filter(Boolean).map((v3) => east.variant("field", v3));
|
|
@@ -64496,6 +64884,7 @@ const PagedDatasetPreview = React.memo(function PagedDatasetPreview2({
|
|
|
64496
64884
|
staleTime: Infinity
|
|
64497
64885
|
// pages of one content hash are immutable
|
|
64498
64886
|
}).then((page) => {
|
|
64887
|
+
if (!current()) return;
|
|
64499
64888
|
const decoded = east.decodeBeast2For(type)(page.data);
|
|
64500
64889
|
const rows = pageRows(type, decoded, page.offset);
|
|
64501
64890
|
pagingDebug(
|
|
@@ -64509,12 +64898,17 @@ const PagedDatasetPreview = React.memo(function PagedDatasetPreview2({
|
|
|
64509
64898
|
setPages((prev2) => new Map(prev2).set(pageIdx, rows));
|
|
64510
64899
|
}).catch((err) => {
|
|
64511
64900
|
pagingDebug(`p${pageIdx} FAILED:`, err);
|
|
64901
|
+
if (!current()) return;
|
|
64902
|
+
if (err instanceof e3ApiClient.DatasetHashMismatchError) {
|
|
64903
|
+
void queryClient.invalidateQueries({ queryKey: ["datasetStatus", apiUrl, repo, workspace, path2] });
|
|
64904
|
+
return;
|
|
64905
|
+
}
|
|
64512
64906
|
if (err instanceof e3ApiClient.ApiError && err.code === "dataset_not_indexed") {
|
|
64513
64907
|
onNotIndexed == null ? void 0 : onNotIndexed();
|
|
64514
64908
|
}
|
|
64515
64909
|
setError(err instanceof Error ? err : new Error(String(err)));
|
|
64516
64910
|
}).finally(() => {
|
|
64517
|
-
|
|
64911
|
+
inflight.delete(pageIdx);
|
|
64518
64912
|
setLoadingCount((n2) => n2 - 1);
|
|
64519
64913
|
});
|
|
64520
64914
|
}, [queryClient, apiUrl, repo, workspace, path2, hash2, requestOptions, type, onNotIndexed]);
|
|
@@ -64611,7 +65005,10 @@ const PagedDatasetPreview = React.memo(function PagedDatasetPreview2({
|
|
|
64611
65005
|
] }),
|
|
64612
65006
|
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { flex: 1, minHeight: 0, overflow: "hidden", children: /* @__PURE__ */ jsxRuntime.jsx(eastUiComponents.EastChakraValueTree, { value: treeValue, storageKey: `${path2}:page`, paging }) })
|
|
64613
65007
|
] });
|
|
64614
|
-
}, (prev2, next2) =>
|
|
65008
|
+
}, (prev2, next2) => {
|
|
65009
|
+
var _a2, _b2;
|
|
65010
|
+
return prev2.apiUrl === next2.apiUrl && prev2.repo === next2.repo && prev2.workspace === next2.workspace && prev2.path === next2.path && prev2.hash === next2.hash && prev2.sizeBytes === next2.sizeBytes && ((_a2 = prev2.requestOptions) == null ? void 0 : _a2.token) === ((_b2 = next2.requestOptions) == null ? void 0 : _b2.token);
|
|
65011
|
+
});
|
|
64615
65012
|
const DEFAULT_SIZE_LIMIT = 200 * 1024;
|
|
64616
65013
|
function formatSize(bytes) {
|
|
64617
65014
|
if (bytes < 1024) return `${bytes} B`;
|
|
@@ -64671,12 +65068,29 @@ const DatasetPreview = React.memo(function DatasetPreview2({
|
|
|
64671
65068
|
const setMutation = useDatasetSet(apiUrl, repo, workspace, requestOptions);
|
|
64672
65069
|
const queryClient = reactQuery.useQueryClient();
|
|
64673
65070
|
const decoded = (_a2 = valueQuery.data) == null ? void 0 : _a2.decoded;
|
|
64674
|
-
const
|
|
64675
|
-
|
|
65071
|
+
const edited = React.useRef(null);
|
|
65072
|
+
const writing = React.useRef(0);
|
|
65073
|
+
const writes = React.useRef(Promise.resolve());
|
|
65074
|
+
React.useEffect(() => {
|
|
65075
|
+
if (writing.current === 0) edited.current = null;
|
|
65076
|
+
}, [decoded]);
|
|
65077
|
+
const write = React.useCallback((next2) => {
|
|
65078
|
+
if (workspace == null || path2 == null || type === void 0) return Promise.resolve();
|
|
64676
65079
|
const treePath = path2.split(".").filter(Boolean).map((p2) => east.variant("field", p2));
|
|
64677
65080
|
const data4 = east.encodeBeast2For(type)(next2);
|
|
64678
|
-
|
|
64679
|
-
|
|
65081
|
+
edited.current = { value: next2 };
|
|
65082
|
+
writing.current++;
|
|
65083
|
+
const run = writes.current.then(async () => {
|
|
65084
|
+
await setMutation.mutateAsync({ path: treePath, data: data4 });
|
|
65085
|
+
await queryClient.invalidateQueries({ queryKey: ["datasetStatus", apiUrl, repo, workspace, path2] });
|
|
65086
|
+
});
|
|
65087
|
+
writes.current = run.catch(() => void 0);
|
|
65088
|
+
return run.catch((err) => {
|
|
65089
|
+
edited.current = null;
|
|
65090
|
+
throw err;
|
|
65091
|
+
}).finally(() => {
|
|
65092
|
+
writing.current--;
|
|
65093
|
+
});
|
|
64680
65094
|
}, [apiUrl, repo, workspace, path2, type, setMutation, queryClient]);
|
|
64681
65095
|
const keyType = React.useMemo(() => type === void 0 ? null : type.type === "Dict" ? type.value.key : type.type === "Set" ? type.value : null, [type]);
|
|
64682
65096
|
const inlineKeys = React.useMemo(() => decoded === void 0 || type === void 0 ? null : type.type === "Dict" ? [...decoded.keys()] : type.type === "Set" ? [...decoded.values()] : null, [type, decoded]);
|
|
@@ -64712,11 +65126,12 @@ const DatasetPreview = React.memo(function DatasetPreview2({
|
|
|
64712
65126
|
const treeValue = React.useMemo(() => {
|
|
64713
65127
|
if (type === void 0 || decoded === void 0) return null;
|
|
64714
65128
|
const root2 = eastUi.ValueTree.materialize(type, decoded);
|
|
65129
|
+
const base = () => edited.current !== null ? edited.current.value : decoded;
|
|
64715
65130
|
const wire = editable ? {
|
|
64716
|
-
onEdit: east.some((p2, leaf) => write(eastUi.ValueTree.applyEdit(type,
|
|
64717
|
-
onInsert: east.some((p2) => write(eastUi.ValueTree.applyEdit(type,
|
|
64718
|
-
onRemove: east.some((p2) => write(eastUi.ValueTree.applyEdit(type,
|
|
64719
|
-
onTag: east.some((p2, tag) => write(eastUi.ValueTree.applyEdit(type,
|
|
65131
|
+
onEdit: east.some((p2, leaf) => write(eastUi.ValueTree.applyEdit(type, base(), p2, { kind: "edit", leaf }))),
|
|
65132
|
+
onInsert: east.some((p2) => write(eastUi.ValueTree.applyEdit(type, base(), p2, { kind: "insert" }))),
|
|
65133
|
+
onRemove: east.some((p2) => write(eastUi.ValueTree.applyEdit(type, base(), p2, { kind: "remove" }))),
|
|
65134
|
+
onTag: east.some((p2, tag) => write(eastUi.ValueTree.applyEdit(type, base(), p2, { kind: "tag", tag })))
|
|
64720
65135
|
} : { onEdit: east.none, onInsert: east.none, onRemove: east.none, onTag: east.none };
|
|
64721
65136
|
return { root: root2, ...wire, style: east.some({ height: east.some("100%"), maxHeight: east.none, openDepth: east.none, toolbar: east.some(true) }) };
|
|
64722
65137
|
}, [type, decoded, editable, write]);
|
|
@@ -64758,8 +65173,11 @@ const DatasetPreview = React.memo(function DatasetPreview2({
|
|
|
64758
65173
|
/* @__PURE__ */ jsxRuntime.jsx(DownloadButton, { onClick: download, label: "Download value" })
|
|
64759
65174
|
] });
|
|
64760
65175
|
}
|
|
65176
|
+
if (valueQuery.error) {
|
|
65177
|
+
const { message, details } = formatApiError(valueQuery.error);
|
|
65178
|
+
return /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "error", title: "Load failed", message, details: details ?? formatError(valueQuery.error) });
|
|
65179
|
+
}
|
|
64761
65180
|
if (valueQuery.isLoading || !valueQuery.data) return /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "loading", title: "Loading..." });
|
|
64762
|
-
if (valueQuery.error) return /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "error", title: "Decode failed", message: valueQuery.error.message });
|
|
64763
65181
|
const count = type === void 0 || decoded === void 0 ? null : type.type === "Array" ? decoded.length : type.type === "Set" || type.type === "Dict" ? decoded.size : null;
|
|
64764
65182
|
const countText = count === null ? "" : `${count.toLocaleString()} ${(type == null ? void 0 : type.type) === "Dict" ? "entries" : "items"} · `;
|
|
64765
65183
|
return /* @__PURE__ */ jsxRuntime.jsxs(react.Flex, { direction: "column", height: "100%", overflow: "hidden", children: [
|
|
@@ -64830,19 +65248,15 @@ const UITaskPreview = React.memo(function UITaskPreview2({
|
|
|
64830
65248
|
const workspace = (config2 == null ? void 0 : config2.workspace) ?? (e3 == null ? void 0 : e3.workspace) ?? null;
|
|
64831
65249
|
const token2 = (config2 == null ? void 0 : config2.token) ?? (e3 == null ? void 0 : e3.token) ?? null;
|
|
64832
65250
|
const requestOptions = { token: token2 };
|
|
65251
|
+
const bound3 = cache3 == null ? void 0 : cache3.getConfig().workspace;
|
|
65252
|
+
const foreign = (config2 == null ? void 0 : config2.workspace) !== void 0 && bound3 !== void 0 && config2.workspace !== bound3;
|
|
64833
65253
|
const detailsQuery = useTaskDetails(apiUrl ?? "", repo, workspace, task, { requestOptions });
|
|
64834
65254
|
const details = detailsQuery.data;
|
|
64835
|
-
const
|
|
64836
|
-
const
|
|
64837
|
-
const manifest = React.useMemo(() => {
|
|
64838
|
-
if (!details || !isUI) return null;
|
|
64839
|
-
const meta3 = getTaskMetadata(details);
|
|
64840
|
-
return meta3 ? internal$1.decodeManifest(meta3) : { paths: [], functions: [], records: [], pages: [] };
|
|
64841
|
-
}, [details, isUI]);
|
|
64842
|
-
const outputPath = details ? treePathToString$1(details.output) : null;
|
|
65255
|
+
const manifest = React.useMemo(() => (details == null ? void 0 : details.role.type) === "ui" ? details.role.value : null, [details]);
|
|
65256
|
+
const outputPath = details ? treePathToString$1(details.output.path) : null;
|
|
64843
65257
|
const preloads = React.useMemo(
|
|
64844
|
-
() => manifest && workspace ? manifest.paths.map((path2) => ({ workspace, path: path2 })) : [],
|
|
64845
|
-
[manifest, workspace]
|
|
65258
|
+
() => manifest && workspace && !foreign ? manifest.paths.map((path2) => ({ workspace, path: path2 })) : [],
|
|
65259
|
+
[manifest, workspace, foreign]
|
|
64846
65260
|
);
|
|
64847
65261
|
const { loading: preloading, error: preloadError } = usePreloadReactiveDatasets(preloads);
|
|
64848
65262
|
const scopedPlatforms = React.useMemo(
|
|
@@ -64868,7 +65282,7 @@ const UITaskPreview = React.memo(function UITaskPreview2({
|
|
|
64868
65282
|
[manifest]
|
|
64869
65283
|
);
|
|
64870
65284
|
React.useEffect(() => {
|
|
64871
|
-
if (!cache3 || !manifest || !workspace) return;
|
|
65285
|
+
if (!cache3 || !manifest || !workspace || foreign) return;
|
|
64872
65286
|
for (const path2 of manifest.paths) {
|
|
64873
65287
|
cache3.setRefetchInterval(workspace, path2, pollInterval);
|
|
64874
65288
|
}
|
|
@@ -64877,7 +65291,7 @@ const UITaskPreview = React.memo(function UITaskPreview2({
|
|
|
64877
65291
|
cache3.clearRefetchInterval(workspace, path2);
|
|
64878
65292
|
}
|
|
64879
65293
|
};
|
|
64880
|
-
}, [cache3, manifest, workspace, pollInterval]);
|
|
65294
|
+
}, [cache3, manifest, workspace, pollInterval, foreign]);
|
|
64881
65295
|
const statusQuery = useDatasetStatus(apiUrl ?? "", repo, workspace, outputPath, { requestOptions });
|
|
64882
65296
|
const valueQuery = useDatasetValue(apiUrl ?? "", repo, workspace, outputPath, {
|
|
64883
65297
|
requestOptions,
|
|
@@ -64887,16 +65301,20 @@ const UITaskPreview = React.memo(function UITaskPreview2({
|
|
|
64887
65301
|
});
|
|
64888
65302
|
const store = React.useMemo(() => eastUiComponents.createUIStore(), [task]);
|
|
64889
65303
|
if (!apiUrl) return /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "error", title: "Configuration error", message: "No apiUrl available — render UITaskPreview inside an <E3Provider> or pass a config prop." });
|
|
65304
|
+
if (foreign) {
|
|
65305
|
+
return /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "error", title: "Workspace mismatch", message: `config names workspace "${config2 == null ? void 0 : config2.workspace}", and the data bindings read workspace "${bound3}", the one the surrounding <ReactiveDatasetProvider> serves: a page previews one workspace.` });
|
|
65306
|
+
}
|
|
64890
65307
|
if (detailsQuery.isLoading) return /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "loading", title: "Loading task..." });
|
|
64891
65308
|
if (detailsQuery.error) return /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "error", title: "Error", message: detailsQuery.error.message });
|
|
64892
65309
|
if (!details) return /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "info", title: "No task", message: `Task "${task}" not found` });
|
|
64893
|
-
if (!
|
|
65310
|
+
if (!manifest) return /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "error", title: "Not a UI task", message: `Task "${task}" is a ${details.role.type} task` });
|
|
64894
65311
|
if (preloadError) return /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "error", title: "Preload failed", message: preloadError.message });
|
|
64895
65312
|
if (preloading) return /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "loading", title: "Loading datasets..." });
|
|
65313
|
+
if (statusQuery.error) return /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "error", title: "Error", message: statusQuery.error.message });
|
|
64896
65314
|
if (statusQuery.isLoading || !statusQuery.data) return /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "loading", title: "Loading..." });
|
|
64897
65315
|
if (statusQuery.data.refType !== "value") return /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "info", title: "No output yet", message: "Task has not produced a value" });
|
|
65316
|
+
if (valueQuery.error) return /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "error", title: "Load failed", message: valueQuery.error.message });
|
|
64898
65317
|
if (valueQuery.isLoading || !valueQuery.data) return /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "loading", title: "Loading..." });
|
|
64899
|
-
if (valueQuery.error) return /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "error", title: "Decode failed", message: valueQuery.error.message });
|
|
64900
65318
|
return /* @__PURE__ */ jsxRuntime.jsx(eastUiComponents.UIStoreProvider, { store, children: /* @__PURE__ */ jsxRuntime.jsx(ErrorBoundary, { children: /* @__PURE__ */ jsxRuntime.jsx(react.Box, { height: "100%", overflow: bare ? void 0 : "auto", p: bare ? void 0 : "4", minH: bare ? 0 : void 0, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
64901
65319
|
eastUiComponents.EastChakraComponent,
|
|
64902
65320
|
{
|
|
@@ -65353,7 +65771,7 @@ const DataTaskPreview = React.memo(function DataTaskPreview2({
|
|
|
65353
65771
|
const detailsQuery = useTaskDetails(apiUrl, repo, workspace, task, {
|
|
65354
65772
|
...requestOptions != null && { requestOptions }
|
|
65355
65773
|
});
|
|
65356
|
-
const outputPath = detailsQuery.data ? treePathToString(detailsQuery.data.output) : null;
|
|
65774
|
+
const outputPath = detailsQuery.data ? treePathToString(detailsQuery.data.output.path) : null;
|
|
65357
65775
|
return /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { height: "100%", display: "flex", flexDirection: "column", overflow: "hidden", children: [
|
|
65358
65776
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
65359
65777
|
react.Flex,
|
|
@@ -65417,13 +65835,14 @@ const TaskPreview = React.memo(function TaskPreview2({
|
|
|
65417
65835
|
requestOptions,
|
|
65418
65836
|
bare = false
|
|
65419
65837
|
}) {
|
|
65838
|
+
var _a2;
|
|
65420
65839
|
const detailsQuery = useTaskDetails(apiUrl, repo, workspace, task, {
|
|
65421
65840
|
...requestOptions != null && { requestOptions }
|
|
65422
65841
|
});
|
|
65423
|
-
const
|
|
65842
|
+
const isUI = ((_a2 = detailsQuery.data) == null ? void 0 : _a2.role.type) === "ui";
|
|
65424
65843
|
return /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { height: "100%", display: "flex", flexDirection: "column", overflow: "hidden", children: [
|
|
65425
65844
|
!bare && /* @__PURE__ */ jsxRuntime.jsx(react.Flex, { px: 4, py: 2, borderBottom: "1px solid", borderColor: "border.subtle", bg: "bg.surface", align: "center", flexShrink: 0, children: /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "sm", fontWeight: "medium", color: "fg", children: task }) }),
|
|
65426
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { flex: 1, overflow: "hidden", minHeight: 0, children: detailsQuery.isLoading ? /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "loading", title: "Loading task..." }) : detailsQuery.error ? /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "error", title: "Error", message: detailsQuery.error.message }) :
|
|
65845
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { flex: 1, overflow: "hidden", minHeight: 0, children: detailsQuery.isLoading ? /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "loading", title: "Loading task..." }) : detailsQuery.error ? /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "error", title: "Error", message: detailsQuery.error.message }) : isUI ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
65427
65846
|
UITaskPreview,
|
|
65428
65847
|
{
|
|
65429
65848
|
task,
|
|
@@ -65441,7 +65860,10 @@ const TaskPreview = React.memo(function TaskPreview2({
|
|
|
65441
65860
|
}
|
|
65442
65861
|
) })
|
|
65443
65862
|
] });
|
|
65444
|
-
}, (prev2, next2) =>
|
|
65863
|
+
}, (prev2, next2) => {
|
|
65864
|
+
var _a2, _b2;
|
|
65865
|
+
return prev2.task === next2.task && prev2.apiUrl === next2.apiUrl && prev2.repo === next2.repo && prev2.workspace === next2.workspace && prev2.bare === next2.bare && ((_a2 = prev2.requestOptions) == null ? void 0 : _a2.token) === ((_b2 = next2.requestOptions) == null ? void 0 : _b2.token);
|
|
65866
|
+
});
|
|
65445
65867
|
function formatPrimitive(type, value) {
|
|
65446
65868
|
switch (type.type) {
|
|
65447
65869
|
case "Null":
|
|
@@ -65838,8 +66260,6 @@ exports.funcChannelKey = funcChannelKey;
|
|
|
65838
66260
|
exports.getBindingTypes = getBindingTypes;
|
|
65839
66261
|
exports.getReactiveDatasetCache = getReactiveDatasetCache;
|
|
65840
66262
|
exports.getStagedStore = getStagedStore;
|
|
65841
|
-
exports.getTaskKind = getTaskKind;
|
|
65842
|
-
exports.getTaskMetadata = getTaskMetadata;
|
|
65843
66263
|
exports.initializeFunctionApi = initializeFunctionApi;
|
|
65844
66264
|
exports.initializePagedApi = initializePagedApi;
|
|
65845
66265
|
exports.initializeReactiveDatasetCache = initializeReactiveDatasetCache;
|
|
@@ -65847,6 +66267,7 @@ exports.initializeRecordApi = initializeRecordApi;
|
|
|
65847
66267
|
exports.initializeStagedStore = initializeStagedStore;
|
|
65848
66268
|
exports.isBindingTracking = isBindingTracking;
|
|
65849
66269
|
exports.onWriteError = onWriteError;
|
|
66270
|
+
exports.pagedRevisionKey = pagedRevisionKey;
|
|
65850
66271
|
exports.pagedTotalKey = pagedTotalKey;
|
|
65851
66272
|
exports.pagedWindowKey = pagedWindowKey;
|
|
65852
66273
|
exports.preloadReactiveDatasetList = preloadReactiveDatasetList;
|