@farmslot/protocol 0.74.9 → 0.75.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -0
- package/dist/agents/roles.d.ts +2 -0
- package/dist/agents/roles.d.ts.map +1 -1
- package/dist/agents/roles.js +8 -1
- package/dist/agents/roles.js.map +1 -1
- package/dist/client-cache/artifact-response.d.ts +15 -0
- package/dist/client-cache/artifact-response.d.ts.map +1 -0
- package/dist/client-cache/artifact-response.js +103 -0
- package/dist/client-cache/artifact-response.js.map +1 -0
- package/dist/client-cache/cache.d.ts +6 -1
- package/dist/client-cache/cache.d.ts.map +1 -1
- package/dist/client-cache/cache.js +207 -33
- package/dist/client-cache/cache.js.map +1 -1
- package/dist/client-cache/command-method.d.ts +17 -4
- package/dist/client-cache/command-method.d.ts.map +1 -1
- package/dist/client-cache/command-method.js +221 -1
- package/dist/client-cache/command-method.js.map +1 -1
- package/dist/client-cache/conformance.d.ts +2 -5
- package/dist/client-cache/conformance.d.ts.map +1 -1
- package/dist/client-cache/conformance.js +4 -0
- package/dist/client-cache/conformance.js.map +1 -1
- package/dist/client-cache/content.d.ts +11 -0
- package/dist/client-cache/content.d.ts.map +1 -1
- package/dist/client-cache/content.js +43 -0
- package/dist/client-cache/content.js.map +1 -1
- package/dist/client-cache/index.d.ts +5 -2
- package/dist/client-cache/index.d.ts.map +1 -1
- package/dist/client-cache/index.js +5 -2
- package/dist/client-cache/index.js.map +1 -1
- package/dist/client-cache/native-history.d.ts +22 -1
- package/dist/client-cache/native-history.d.ts.map +1 -1
- package/dist/client-cache/native-history.js +59 -4
- package/dist/client-cache/native-history.js.map +1 -1
- package/dist/client-cache/network-cost.d.ts +4 -0
- package/dist/client-cache/network-cost.d.ts.map +1 -0
- package/dist/client-cache/network-cost.js +24 -0
- package/dist/client-cache/network-cost.js.map +1 -0
- package/dist/client-cache/prefetch.d.ts +17 -0
- package/dist/client-cache/prefetch.d.ts.map +1 -1
- package/dist/client-cache/prefetch.js +110 -47
- package/dist/client-cache/prefetch.js.map +1 -1
- package/dist/client-cache/read-stored.d.ts +7 -0
- package/dist/client-cache/read-stored.d.ts.map +1 -0
- package/dist/client-cache/read-stored.js +18 -0
- package/dist/client-cache/read-stored.js.map +1 -0
- package/dist/client-cache/registry.d.ts +309 -16
- package/dist/client-cache/registry.d.ts.map +1 -1
- package/dist/client-cache/registry.js +422 -27
- package/dist/client-cache/registry.js.map +1 -1
- package/dist/client-cache/run-details.d.ts +29 -0
- package/dist/client-cache/run-details.d.ts.map +1 -0
- package/dist/client-cache/run-details.js +145 -0
- package/dist/client-cache/run-details.js.map +1 -0
- package/dist/client-cache/run-list-versions.d.ts +2 -0
- package/dist/client-cache/run-list-versions.d.ts.map +1 -1
- package/dist/client-cache/run-list-versions.js +9 -0
- package/dist/client-cache/run-list-versions.js.map +1 -1
- package/dist/client-cache/store.d.ts +6 -0
- package/dist/client-cache/store.d.ts.map +1 -1
- package/dist/client-cache/store.js +25 -0
- package/dist/client-cache/store.js.map +1 -1
- package/dist/client-cache/transport.d.ts +8 -1
- package/dist/client-cache/transport.d.ts.map +1 -1
- package/dist/client-cache/transport.js +8 -0
- package/dist/client-cache/transport.js.map +1 -1
- package/dist/client-cache/types.d.ts +11 -1
- package/dist/client-cache/types.d.ts.map +1 -1
- package/dist/contracts/agents.d.ts +2 -0
- package/dist/contracts/agents.d.ts.map +1 -1
- package/dist/contracts/agents.js.map +1 -1
- package/dist/contracts/config.d.ts +10 -0
- package/dist/contracts/config.d.ts.map +1 -1
- package/dist/contracts/config.js +7 -0
- package/dist/contracts/config.js.map +1 -1
- package/dist/contracts/dispatch.d.ts +18 -0
- package/dist/contracts/dispatch.d.ts.map +1 -1
- package/dist/contracts/run-lifecycle-view.d.ts +9 -0
- package/dist/contracts/run-lifecycle-view.d.ts.map +1 -1
- package/dist/contracts/run-lifecycle-view.js +8 -0
- package/dist/contracts/run-lifecycle-view.js.map +1 -1
- package/dist/contracts/runs.d.ts +76 -7
- package/dist/contracts/runs.d.ts.map +1 -1
- package/dist/contracts/runs.js +33 -2
- package/dist/contracts/runs.js.map +1 -1
- package/dist/contracts/slot-recovery.d.ts +11 -0
- package/dist/contracts/slot-recovery.d.ts.map +1 -1
- package/dist/contracts/slot-recovery.js +11 -1
- package/dist/contracts/slot-recovery.js.map +1 -1
- package/dist/contracts/slots.d.ts +17 -0
- package/dist/contracts/slots.d.ts.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/markdown-fence.d.ts +6 -0
- package/dist/markdown-fence.d.ts.map +1 -0
- package/dist/markdown-fence.js +12 -0
- package/dist/markdown-fence.js.map +1 -0
- package/dist/recipe/recording.d.ts +1 -1
- package/dist/recipe/recording.d.ts.map +1 -1
- package/dist/recipe/recording.js +2 -2
- package/dist/recipe/recording.js.map +1 -1
- package/dist/rpc/auth.d.ts +5 -2
- package/dist/rpc/auth.d.ts.map +1 -1
- package/dist/rpc/auth.js.map +1 -1
- package/dist/rpc/dispatch.d.ts +8 -0
- package/dist/rpc/dispatch.d.ts.map +1 -1
- package/dist/rpc/dispatch.js +8 -0
- package/dist/rpc/dispatch.js.map +1 -1
- package/dist/rpc/fleet.d.ts +3 -1
- package/dist/rpc/fleet.d.ts.map +1 -1
- package/dist/rpc/fleet.js.map +1 -1
- package/dist/rpc/index.d.ts +1 -0
- package/dist/rpc/index.d.ts.map +1 -1
- package/dist/rpc/index.js +1 -0
- package/dist/rpc/index.js.map +1 -1
- package/dist/rpc/native-session.d.ts +11 -0
- package/dist/rpc/native-session.d.ts.map +1 -1
- package/dist/rpc/native-session.js +9 -0
- package/dist/rpc/native-session.js.map +1 -1
- package/dist/rpc/nodes.d.ts +2 -0
- package/dist/rpc/nodes.d.ts.map +1 -1
- package/dist/rpc/nodes.js.map +1 -1
- package/dist/rpc/run.d.ts +4 -0
- package/dist/rpc/run.d.ts.map +1 -1
- package/dist/rpc/run.js.map +1 -1
- package/dist/rpc/runner-models.d.ts +14 -3
- package/dist/rpc/runner-models.d.ts.map +1 -1
- package/dist/rpc/runner-models.js +37 -13
- package/dist/rpc/runner-models.js.map +1 -1
- package/dist/rpc/slot.d.ts +3 -2
- package/dist/rpc/slot.d.ts.map +1 -1
- package/dist/rpc/slot.js.map +1 -1
- package/dist/rpc/terminal-input-record.d.ts +20 -0
- package/dist/rpc/terminal-input-record.d.ts.map +1 -0
- package/dist/rpc/terminal-input-record.js +42 -0
- package/dist/rpc/terminal-input-record.js.map +1 -0
- package/dist/rpc/terminal.d.ts +1 -1
- package/dist/rpc/terminal.d.ts.map +1 -1
- package/dist/runs/list-trim.d.ts +13 -0
- package/dist/runs/list-trim.d.ts.map +1 -0
- package/dist/runs/list-trim.js +71 -0
- package/dist/runs/list-trim.js.map +1 -0
- package/dist/runs/list-version.d.ts +4 -0
- package/dist/runs/list-version.d.ts.map +1 -0
- package/dist/runs/list-version.js +9 -0
- package/dist/runs/list-version.js.map +1 -0
- package/dist/runs/review-chain.d.ts.map +1 -1
- package/dist/runs/review-chain.js +2 -1
- package/dist/runs/review-chain.js.map +1 -1
- package/dist/runs/run-engine-label.d.ts +40 -0
- package/dist/runs/run-engine-label.d.ts.map +1 -0
- package/dist/runs/run-engine-label.js +27 -0
- package/dist/runs/run-engine-label.js.map +1 -0
- package/dist/runs/run-family-groups.d.ts.map +1 -1
- package/dist/runs/run-family-groups.js +5 -4
- package/dist/runs/run-family-groups.js.map +1 -1
- package/dist/runs/run-list-view.d.ts.map +1 -1
- package/dist/runs/run-list-view.js +2 -1
- package/dist/runs/run-list-view.js.map +1 -1
- package/dist/slots/tracking-branch.d.ts +7 -0
- package/dist/slots/tracking-branch.d.ts.map +1 -1
- package/dist/slots/tracking-branch.js.map +1 -1
- package/dist/text-order.d.ts +7 -0
- package/dist/text-order.d.ts.map +1 -0
- package/dist/text-order.js +7 -0
- package/dist/text-order.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { Methods } from '../rpc/registry.js';
|
|
2
|
+
import { runListVersion } from '../runs/list-version.js';
|
|
2
3
|
import { ResourceCollectorAdmission } from './collector-admission.js';
|
|
3
|
-
import { resourceBudgetEvictions, resourceContentWithinLimits, verifyResourceContent, } from './content.js';
|
|
4
|
+
import { resourceBudgetEvictions, resourceContentWithinLimits, verifyResourceContent, verifyResourceContentInSlices, } from './content.js';
|
|
4
5
|
import { isKnownResourceFamily, RESOURCE_PUBLISH_INTERVAL_MS, RESOURCE_REREAD_INTERVAL_MS, RESOURCE_RETRY_MAX_WAIT_MS, resourceKeyId, } from './keys.js';
|
|
5
6
|
import { nativeHistoryStorageId, readNativeHistory, storedNativeHistoryMatches, } from './native-history.js';
|
|
6
7
|
import { resourceQuery } from './registry.js';
|
|
8
|
+
import { createRunDetailSync, runDetailTarget } from './run-details.js';
|
|
7
9
|
import { isRunListCollectionRead, RUN_LIST_COLLECTION_ID, RunListCollection, runListSummaryFlags, } from './run-list-versions.js';
|
|
8
|
-
import { createMemoryResourceStore, normalizeResourceStoreScope, resourceStoreScopeId, sameResourceAuthority, } from './store.js';
|
|
10
|
+
import { createMemoryResourceStore, normalizeResourceStoreScope, readStoredQueryBatch, resourceStoreScopeId, sameResourceAuthority, } from './store.js';
|
|
9
11
|
import { isUpstreamStoredQuery, upstreamRetentionEvictions } from './upstream-retention.js';
|
|
10
12
|
import { workspaceRetentionEvictions, workspaceRetentionMembership, } from './workspace-retention.js';
|
|
11
13
|
const deletionReasons = new Set([
|
|
@@ -21,6 +23,13 @@ const evictionCodes = new Set([
|
|
|
21
23
|
'RUN_NOT_FOUND',
|
|
22
24
|
'RESOURCE_NOT_FOUND',
|
|
23
25
|
]);
|
|
26
|
+
/**
|
|
27
|
+
* The gateway refused the read: the resource is gone or not this principal's. The
|
|
28
|
+
* cache evicts saved data on these; a network failure or timeout is not one.
|
|
29
|
+
*/
|
|
30
|
+
export function readRefused(error) {
|
|
31
|
+
return Boolean(error && evictionCodes.has(error.code));
|
|
32
|
+
}
|
|
24
33
|
function queryError(error) {
|
|
25
34
|
const code = error && typeof error === 'object' ? Reflect.get(error, 'code') : undefined;
|
|
26
35
|
return {
|
|
@@ -44,6 +53,7 @@ function normalizeConnection(connection) {
|
|
|
44
53
|
};
|
|
45
54
|
}
|
|
46
55
|
const defaultContentBudgetBytes = 500 * 1024 * 1024;
|
|
56
|
+
const startupQueryBytes = 8 * 1024 * 1024;
|
|
47
57
|
// Only the identity crosses the store seam, never a caller's other fields.
|
|
48
58
|
const contentKey = ({ kind, id }) => ({ kind, id });
|
|
49
59
|
function profileId(connection) {
|
|
@@ -56,6 +66,9 @@ export function createGatewayResourceCache(options) {
|
|
|
56
66
|
const transport = options.transport;
|
|
57
67
|
let store = options.store ?? createMemoryResourceStore();
|
|
58
68
|
let metadata;
|
|
69
|
+
let startupStore;
|
|
70
|
+
let startupOwnerVersion = -1;
|
|
71
|
+
const startupQueries = new Map();
|
|
59
72
|
let connection = normalizeConnection(transport.getConnection());
|
|
60
73
|
let usable = false;
|
|
61
74
|
let disposed = false;
|
|
@@ -91,9 +104,33 @@ export function createGatewayResourceCache(options) {
|
|
|
91
104
|
maxBodyBytes: resourceQuery(Methods.RUN_LIST).maxBytes,
|
|
92
105
|
});
|
|
93
106
|
let runListLoad;
|
|
107
|
+
const fallbackDetailTargets = new Map();
|
|
108
|
+
const detailSync = createRunDetailSync({
|
|
109
|
+
connection: () => connection,
|
|
110
|
+
list: async () => (await readStoredContent((target) => target.listQueries())) ?? [],
|
|
111
|
+
stored: (runId) => readStoredContent((target) => target.getQuery(resourceQuery(Methods.RUN_GET, { runId }).id)),
|
|
112
|
+
touch: async (runId) => {
|
|
113
|
+
const target = store;
|
|
114
|
+
const ownerVersion = hydrationOwnerVersion;
|
|
115
|
+
await updateStore(target, () => target === store && !target.deleted && ownerVersion === hydrationOwnerVersion, () => target.touchQuery?.(resourceQuery(Methods.RUN_GET, { runId }).id, Date.now()) ??
|
|
116
|
+
Promise.resolve());
|
|
117
|
+
},
|
|
118
|
+
fetch: (runId, maxStoredBytes, signal) => retryQuery(resourceQuery(Methods.RUN_GET, { runId }), { origin: 'prefetch', cohort: 'run', maxStoredBytes }, signal),
|
|
119
|
+
onError: (error) => options.onStorageNotice?.(`Background run details could not sync: ${queryError(error).message}`),
|
|
120
|
+
});
|
|
121
|
+
function refreshDetailTargets() {
|
|
122
|
+
const versioned = runList.detailTargets();
|
|
123
|
+
detailSync.setTargets(store.persistent && usable
|
|
124
|
+
? runList.held
|
|
125
|
+
? versioned
|
|
126
|
+
: [...fallbackDetailTargets.values()]
|
|
127
|
+
: []);
|
|
128
|
+
}
|
|
94
129
|
function forgetRunList() {
|
|
95
130
|
runList.clear();
|
|
96
131
|
runListLoad = undefined;
|
|
132
|
+
fallbackDetailTargets.clear();
|
|
133
|
+
detailSync.reset();
|
|
97
134
|
}
|
|
98
135
|
function updateCollectors() {
|
|
99
136
|
collectors.update({ ...connection, foreground: usable && connection.foreground !== false }, usable
|
|
@@ -116,12 +153,29 @@ export function createGatewayResourceCache(options) {
|
|
|
116
153
|
unsubscribeStore();
|
|
117
154
|
store = next;
|
|
118
155
|
storeVersion++;
|
|
156
|
+
startupStore = undefined;
|
|
157
|
+
startupQueries.clear();
|
|
158
|
+
detailSync.reset();
|
|
119
159
|
unsubscribeStore = store.onDeleted(storeDeleted);
|
|
120
160
|
}
|
|
161
|
+
/** Observers whose failure was already logged, so a repeating one is reported once. */
|
|
162
|
+
const failedObservers = new WeakSet();
|
|
163
|
+
/** One observer's failure is its own: the other observers and the cache's reads go on. */
|
|
164
|
+
function deliver(entry, listener) {
|
|
165
|
+
try {
|
|
166
|
+
listener(entry.state);
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
if (failedObservers.has(listener))
|
|
170
|
+
return;
|
|
171
|
+
failedObservers.add(listener);
|
|
172
|
+
console.error(`Resource cache observer of ${entry.query.id} failed:`, error);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
121
175
|
function notify(entry) {
|
|
122
176
|
entry.state = { ...entry.state, storageNotice };
|
|
123
177
|
for (const listener of entry.listeners)
|
|
124
|
-
|
|
178
|
+
deliver(entry, listener);
|
|
125
179
|
}
|
|
126
180
|
function cancelScheduledRead(entry) {
|
|
127
181
|
if (entry.readTimer !== undefined)
|
|
@@ -232,6 +286,46 @@ export function createGatewayResourceCache(options) {
|
|
|
232
286
|
if (entry.state.status !== 'ready')
|
|
233
287
|
hydrateEntry(entry);
|
|
234
288
|
}
|
|
289
|
+
async function warmStoredQueries() {
|
|
290
|
+
if (!store.persistent ||
|
|
291
|
+
(startupStore === store && startupOwnerVersion === hydrationOwnerVersion))
|
|
292
|
+
return;
|
|
293
|
+
const readingStore = store;
|
|
294
|
+
const incarnation = storeVersion;
|
|
295
|
+
const ownerVersion = hydrationOwnerVersion;
|
|
296
|
+
const current = () => !disposed &&
|
|
297
|
+
store === readingStore &&
|
|
298
|
+
!readingStore.deleted &&
|
|
299
|
+
incarnation === storeVersion &&
|
|
300
|
+
ownerVersion === hydrationOwnerVersion;
|
|
301
|
+
try {
|
|
302
|
+
const batch = await readStoredQueryBatch(readingStore, startupQueryBytes);
|
|
303
|
+
if (!current())
|
|
304
|
+
return;
|
|
305
|
+
const collection = batch.find((saved) => saved.id === RUN_LIST_COLLECTION_ID);
|
|
306
|
+
const rows = collection && readingStore.getRows
|
|
307
|
+
? await readingStore.getRows(RUN_LIST_COLLECTION_ID)
|
|
308
|
+
: undefined;
|
|
309
|
+
if (!current())
|
|
310
|
+
return;
|
|
311
|
+
startupQueries.clear();
|
|
312
|
+
let remaining = startupQueryBytes;
|
|
313
|
+
for (const saved of batch) {
|
|
314
|
+
if (saved.bytes > remaining)
|
|
315
|
+
continue;
|
|
316
|
+
remaining -= saved.bytes;
|
|
317
|
+
if (saved.id !== RUN_LIST_COLLECTION_ID)
|
|
318
|
+
startupQueries.set(saved.id, saved);
|
|
319
|
+
}
|
|
320
|
+
if (rows)
|
|
321
|
+
runList.hydrate(rows);
|
|
322
|
+
startupStore = readingStore;
|
|
323
|
+
startupOwnerVersion = ownerVersion;
|
|
324
|
+
}
|
|
325
|
+
catch (error) {
|
|
326
|
+
await recoverStore(error, readingStore);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
235
329
|
function schedule(entry) {
|
|
236
330
|
if (entry.queued || disposed)
|
|
237
331
|
return;
|
|
@@ -261,8 +355,13 @@ export function createGatewayResourceCache(options) {
|
|
|
261
355
|
connection.id === next.id &&
|
|
262
356
|
!scopeChanged &&
|
|
263
357
|
!authorityChanged &&
|
|
264
|
-
!revoke)
|
|
358
|
+
!revoke) {
|
|
359
|
+
if (connection.metered !== next.metered) {
|
|
360
|
+
connection = { ...connection, metered: next.metered };
|
|
361
|
+
refreshDetailTargets();
|
|
362
|
+
}
|
|
265
363
|
return;
|
|
364
|
+
}
|
|
266
365
|
const ticket = ++version;
|
|
267
366
|
runList.abort();
|
|
268
367
|
// Stored facts belong to an owner, not a socket attempt. Fence a revoked
|
|
@@ -273,6 +372,7 @@ export function createGatewayResourceCache(options) {
|
|
|
273
372
|
}
|
|
274
373
|
connection = next;
|
|
275
374
|
usable = false;
|
|
375
|
+
refreshDetailTargets();
|
|
276
376
|
updateCollectors();
|
|
277
377
|
revisions.clear();
|
|
278
378
|
for (const entry of entries.values()) {
|
|
@@ -361,9 +461,14 @@ export function createGatewayResourceCache(options) {
|
|
|
361
461
|
await recoverStore(error, target);
|
|
362
462
|
}
|
|
363
463
|
}
|
|
464
|
+
if (disposed || ticket !== version)
|
|
465
|
+
return;
|
|
466
|
+
if (!revoke && canPersist(next))
|
|
467
|
+
await warmStoredQueries();
|
|
364
468
|
if (disposed || ticket !== version)
|
|
365
469
|
return;
|
|
366
470
|
usable = next.connected && next.foreground !== false && !revoke;
|
|
471
|
+
refreshDetailTargets();
|
|
367
472
|
updateCollectors();
|
|
368
473
|
if (usable)
|
|
369
474
|
for (const entry of entries.values())
|
|
@@ -409,7 +514,7 @@ export function createGatewayResourceCache(options) {
|
|
|
409
514
|
if (change.revision <= (revisions.get(id) ?? -1))
|
|
410
515
|
continue;
|
|
411
516
|
revisions.set(id, change.revision);
|
|
412
|
-
const affected = [...entries.values()].filter((entry) => entry.
|
|
517
|
+
const affected = [...entries.values()].filter((entry) => entry.dependencyIds.has(id));
|
|
413
518
|
// Coverage counts declared dependencies only, so a known key that no entry declares (another
|
|
414
519
|
// slot, an unopened workspace) changes nothing held. Only a family this client cannot name might.
|
|
415
520
|
if (!affected.length && isKnownResourceFamily(change.key?.family))
|
|
@@ -441,6 +546,7 @@ export function createGatewayResourceCache(options) {
|
|
|
441
546
|
}
|
|
442
547
|
const entry = {
|
|
443
548
|
query,
|
|
549
|
+
dependencyIds: new Set(query.dependencies.map(resourceKeyId)),
|
|
444
550
|
state: { status: 'loading' },
|
|
445
551
|
listeners: new Set(),
|
|
446
552
|
hydration: Promise.resolve(),
|
|
@@ -454,9 +560,40 @@ export function createGatewayResourceCache(options) {
|
|
|
454
560
|
failures: 0,
|
|
455
561
|
};
|
|
456
562
|
entries.set(query.id, entry);
|
|
457
|
-
|
|
563
|
+
const saved = isRunListView(query) ? heldRunListView(query) : takeStartupQuery(query);
|
|
564
|
+
if (applyStoredQuery(entry, saved)) {
|
|
565
|
+
entry.hydrated = true;
|
|
566
|
+
queueMicrotask(prune);
|
|
567
|
+
}
|
|
568
|
+
else
|
|
569
|
+
hydrateEntry(entry);
|
|
458
570
|
return entry;
|
|
459
571
|
}
|
|
572
|
+
function takeStartupQuery(query) {
|
|
573
|
+
if (startupStore !== store || startupOwnerVersion !== hydrationOwnerVersion)
|
|
574
|
+
return undefined;
|
|
575
|
+
const id = nativeHistoryStorageId(query);
|
|
576
|
+
const saved = startupQueries.get(id);
|
|
577
|
+
startupQueries.delete(id);
|
|
578
|
+
return saved;
|
|
579
|
+
}
|
|
580
|
+
function applyStoredQuery(entry, saved) {
|
|
581
|
+
const query = entry.query;
|
|
582
|
+
if (!saved ||
|
|
583
|
+
!query.persisted ||
|
|
584
|
+
saved.bytes > query.maxBytes ||
|
|
585
|
+
(query.method === Methods.NATIVE_SESSION_READ &&
|
|
586
|
+
!storedNativeHistoryMatches(query.params, saved.data)))
|
|
587
|
+
return false;
|
|
588
|
+
const sourceObservation = query.sourceObservation?.(saved.data);
|
|
589
|
+
entry.state = {
|
|
590
|
+
status: 'stale',
|
|
591
|
+
data: saved.data,
|
|
592
|
+
observedAt: saved.observedAt,
|
|
593
|
+
...(sourceObservation ? { sourceObservation, error: sourceObservation.error } : {}),
|
|
594
|
+
};
|
|
595
|
+
return true;
|
|
596
|
+
}
|
|
460
597
|
function hydrateEntry(entry) {
|
|
461
598
|
const query = entry.query;
|
|
462
599
|
const hydrationSerial = ++entry.hydrationSerial;
|
|
@@ -470,7 +607,8 @@ export function createGatewayResourceCache(options) {
|
|
|
470
607
|
const readingStore = store;
|
|
471
608
|
try {
|
|
472
609
|
const saved = query.persisted
|
|
473
|
-
? ((
|
|
610
|
+
? (takeStartupQuery(query) ??
|
|
611
|
+
(isRunListView(query) ? await storedRunListView(query, readingStore) : undefined) ??
|
|
474
612
|
(await readingStore.getQuery(nativeHistoryStorageId(query))))
|
|
475
613
|
: undefined;
|
|
476
614
|
if (!disposed &&
|
|
@@ -480,17 +618,7 @@ export function createGatewayResourceCache(options) {
|
|
|
480
618
|
ownerVersion === hydrationOwnerVersion &&
|
|
481
619
|
entry.hydrationSerial === hydrationSerial &&
|
|
482
620
|
entry.state.status !== 'ready' &&
|
|
483
|
-
saved
|
|
484
|
-
saved.bytes <= query.maxBytes &&
|
|
485
|
-
(query.method !== Methods.NATIVE_SESSION_READ ||
|
|
486
|
-
storedNativeHistoryMatches(query.params, saved.data))) {
|
|
487
|
-
const sourceObservation = query.sourceObservation?.(saved.data);
|
|
488
|
-
entry.state = {
|
|
489
|
-
status: 'stale',
|
|
490
|
-
data: saved.data,
|
|
491
|
-
observedAt: saved.observedAt,
|
|
492
|
-
...(sourceObservation ? { sourceObservation, error: sourceObservation.error } : {}),
|
|
493
|
-
};
|
|
621
|
+
applyStoredQuery(entry, saved)) {
|
|
494
622
|
notify(entry);
|
|
495
623
|
}
|
|
496
624
|
}
|
|
@@ -521,6 +649,9 @@ export function createGatewayResourceCache(options) {
|
|
|
521
649
|
};
|
|
522
650
|
await runListLoad.promise;
|
|
523
651
|
}
|
|
652
|
+
return heldRunListView(query);
|
|
653
|
+
}
|
|
654
|
+
function heldRunListView(query) {
|
|
524
655
|
const data = runList.view(query.params);
|
|
525
656
|
return data && runList.observedAt !== undefined
|
|
526
657
|
? { id: query.id, data, observedAt: runList.observedAt, bytes: 0 }
|
|
@@ -623,7 +754,11 @@ export function createGatewayResourceCache(options) {
|
|
|
623
754
|
const shared = entry.pending.prefetch;
|
|
624
755
|
entry.pending.prefetch =
|
|
625
756
|
shared && prefetch
|
|
626
|
-
? {
|
|
757
|
+
? {
|
|
758
|
+
origin: 'prefetch',
|
|
759
|
+
cohort: shared.cohort === 'gate' ? 'gate' : prefetch.cohort,
|
|
760
|
+
maxStoredBytes: Math.min(shared.maxStoredBytes ?? Infinity, prefetch.maxStoredBytes ?? Infinity),
|
|
761
|
+
}
|
|
627
762
|
: undefined;
|
|
628
763
|
return entry.pending.promise;
|
|
629
764
|
}
|
|
@@ -744,6 +879,7 @@ export function createGatewayResourceCache(options) {
|
|
|
744
879
|
const retention = entry.query.retention;
|
|
745
880
|
const saved = serialized !== undefined &&
|
|
746
881
|
bytes <= entry.query.maxBytes &&
|
|
882
|
+
(entry.listeners.size > 0 || bytes <= (pending.prefetch?.maxStoredBytes ?? Infinity)) &&
|
|
747
883
|
(!retention || bytes <= retention.maxBytes)
|
|
748
884
|
? {
|
|
749
885
|
id: nativeHistoryStorageId(entry.query),
|
|
@@ -816,7 +952,9 @@ export function createGatewayResourceCache(options) {
|
|
|
816
952
|
(retention.cohort === 'upstream'
|
|
817
953
|
? isUpstreamStoredQuery(value.id)
|
|
818
954
|
: value.id.startsWith(`${entry.query.method}:`)))
|
|
819
|
-
.sort((a, b) =>
|
|
955
|
+
.sort((a, b) => (entry.query.method === Methods.RUN_GET
|
|
956
|
+
? (a.accessedAt ?? a.observedAt) - (b.accessedAt ?? b.observedAt)
|
|
957
|
+
: a.observedAt - b.observedAt) ||
|
|
820
958
|
(entries.get(a.id)?.lastUsed ?? 0) - (entries.get(b.id)?.lastUsed ?? 0));
|
|
821
959
|
if (!current())
|
|
822
960
|
return;
|
|
@@ -836,6 +974,14 @@ export function createGatewayResourceCache(options) {
|
|
|
836
974
|
await writingStore.putQuery(row);
|
|
837
975
|
});
|
|
838
976
|
}
|
|
977
|
+
if (store.persistent && entry.query.method === Methods.RUN_LIST) {
|
|
978
|
+
const listed = reply.data;
|
|
979
|
+
if (Array.isArray(listed.runs)) {
|
|
980
|
+
for (const run of listed.runs)
|
|
981
|
+
fallbackDetailTargets.set(run.id, runDetailTarget(run, runListVersion(run)));
|
|
982
|
+
}
|
|
983
|
+
refreshDetailTargets();
|
|
984
|
+
}
|
|
839
985
|
}
|
|
840
986
|
catch (error) {
|
|
841
987
|
if (disposed ||
|
|
@@ -962,14 +1108,25 @@ export function createGatewayResourceCache(options) {
|
|
|
962
1108
|
});
|
|
963
1109
|
return written;
|
|
964
1110
|
}
|
|
1111
|
+
function contentWriteToken() {
|
|
1112
|
+
return { connectionId: connection.id, ownerVersion: hydrationOwnerVersion, storeVersion };
|
|
1113
|
+
}
|
|
965
1114
|
/** Stores verified bytes within the caps and the shared budget. */
|
|
966
|
-
function storeContent(input) {
|
|
1115
|
+
async function storeContent(input) {
|
|
967
1116
|
const key = contentKey(input.key);
|
|
968
1117
|
const { data, origin, cohort } = input;
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
1118
|
+
// Prefetch verifies between frames, so the write still answers to the owner it was asked under.
|
|
1119
|
+
const token = input.token ?? contentWriteToken();
|
|
1120
|
+
if (!resourceContentWithinLimits(key, data))
|
|
1121
|
+
return false;
|
|
1122
|
+
// Prefetch is background work; content a person opened is on their critical path and
|
|
1123
|
+
// verifies at once, as before.
|
|
1124
|
+
const verified = origin === 'prefetch'
|
|
1125
|
+
? await verifyResourceContentInSlices(key, data, input.untrackedFiles)
|
|
1126
|
+
: verifyResourceContent(key, data, input.untrackedFiles);
|
|
1127
|
+
if (!verified)
|
|
1128
|
+
return false;
|
|
1129
|
+
return writeContent(token, async (target, current) => {
|
|
973
1130
|
const accessedAt = Date.now();
|
|
974
1131
|
const content = await target.listContent();
|
|
975
1132
|
if (content.some((row) => row.kind === key.kind && row.id === key.id)) {
|
|
@@ -1019,6 +1176,13 @@ export function createGatewayResourceCache(options) {
|
|
|
1019
1176
|
return true;
|
|
1020
1177
|
});
|
|
1021
1178
|
}
|
|
1179
|
+
async function retryQuery(query, placement, signal) {
|
|
1180
|
+
if (disposed)
|
|
1181
|
+
throw new Error('Resource cache is disposed');
|
|
1182
|
+
const entry = entryFor(query);
|
|
1183
|
+
await fetchEntry(entry, false, placement, signal);
|
|
1184
|
+
return entry.state;
|
|
1185
|
+
}
|
|
1022
1186
|
return {
|
|
1023
1187
|
observedKey(match) {
|
|
1024
1188
|
for (const entry of entries.values())
|
|
@@ -1071,7 +1235,7 @@ export function createGatewayResourceCache(options) {
|
|
|
1071
1235
|
notify(entry);
|
|
1072
1236
|
});
|
|
1073
1237
|
}
|
|
1074
|
-
|
|
1238
|
+
deliver(entry, receive);
|
|
1075
1239
|
if (entry.state.status !== 'ready' || entry.dirty)
|
|
1076
1240
|
schedule(entry);
|
|
1077
1241
|
return () => {
|
|
@@ -1091,13 +1255,21 @@ export function createGatewayResourceCache(options) {
|
|
|
1091
1255
|
throw new Error('Resource cache is disposed');
|
|
1092
1256
|
return entryFor(query).state;
|
|
1093
1257
|
},
|
|
1094
|
-
async
|
|
1258
|
+
async readStored(query, refresh = true) {
|
|
1095
1259
|
if (disposed)
|
|
1096
1260
|
throw new Error('Resource cache is disposed');
|
|
1261
|
+
const ownerVersion = hydrationOwnerVersion;
|
|
1262
|
+
await barrier;
|
|
1097
1263
|
const entry = entryFor(query);
|
|
1098
|
-
await
|
|
1264
|
+
await entry.hydration;
|
|
1265
|
+
if (disposed || ownerVersion !== hydrationOwnerVersion)
|
|
1266
|
+
throw new Error('The offline store owner changed');
|
|
1267
|
+
// fetchEntry records network refusals in the query state and notifies its readers.
|
|
1268
|
+
if (refresh && entry.state.data !== undefined)
|
|
1269
|
+
void fetchEntry(entry);
|
|
1099
1270
|
return entry.state;
|
|
1100
1271
|
},
|
|
1272
|
+
retry: retryQuery,
|
|
1101
1273
|
async retryAfterPending(query) {
|
|
1102
1274
|
if (disposed)
|
|
1103
1275
|
throw new Error('Resource cache is disposed');
|
|
@@ -1112,20 +1284,22 @@ export function createGatewayResourceCache(options) {
|
|
|
1112
1284
|
readContent: (key) => readContentBytes((target) => target.getContent(contentKey(key)), false),
|
|
1113
1285
|
runListProjections: () => runList.projections(),
|
|
1114
1286
|
openContent: (key) => readContentBytes((target) => target.getContent(contentKey(key)), true),
|
|
1287
|
+
async forgetContent(key) {
|
|
1288
|
+
const target = store;
|
|
1289
|
+
const ownerVersion = hydrationOwnerVersion;
|
|
1290
|
+
await updateStore(target, () => target === store && !target.deleted && ownerVersion === hydrationOwnerVersion, () => target.deleteContent(contentKey(key)));
|
|
1291
|
+
},
|
|
1115
1292
|
readContentByAlias: (alias) => readContentBytes((target) => target.getContentByAlias(alias), false),
|
|
1116
1293
|
openContentByAlias: (alias) => readContentBytes((target) => target.getContentByAlias(alias), true),
|
|
1117
1294
|
storeContent,
|
|
1118
1295
|
retainContent,
|
|
1119
|
-
contentWriteToken
|
|
1120
|
-
connectionId: connection.id,
|
|
1121
|
-
ownerVersion: hydrationOwnerVersion,
|
|
1122
|
-
storeVersion,
|
|
1123
|
-
}),
|
|
1296
|
+
contentWriteToken,
|
|
1124
1297
|
async dispose(reason) {
|
|
1125
1298
|
if (disposed)
|
|
1126
1299
|
return;
|
|
1127
1300
|
const withdrawing = collectors.close();
|
|
1128
1301
|
disposed = true;
|
|
1302
|
+
detailSync.reset();
|
|
1129
1303
|
version++;
|
|
1130
1304
|
unsubscribeChanges();
|
|
1131
1305
|
unsubscribeConnection();
|