@usearete/sdk 0.24.0 → 0.25.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/README.md +33 -1
- package/dist/index.cjs +577 -107
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +196 -8
- package/dist/index.esm.js +573 -108
- package/dist/index.esm.js.map +1 -1
- package/dist/testing.cjs +619 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.ts +482 -0
- package/dist/testing.esm.js +603 -0
- package/dist/testing.esm.js.map +1 -0
- package/package.json +18 -2
package/dist/index.esm.js
CHANGED
|
@@ -1872,6 +1872,7 @@ class ConnectionManager {
|
|
|
1872
1872
|
}
|
|
1873
1873
|
|
|
1874
1874
|
const INTERNAL_SEQ_FIELD = '__seq';
|
|
1875
|
+
const INTERNAL_VERSION_FIELD = '__version';
|
|
1875
1876
|
class ProcessedSlotTimeoutError extends Error {
|
|
1876
1877
|
constructor(targetSlot, processedSlot) {
|
|
1877
1878
|
super(`Timed out waiting for Arete to process slot ${targetSlot}`);
|
|
@@ -1932,6 +1933,9 @@ function toCamelCaseSegment(value) {
|
|
|
1932
1933
|
if (value === '_seq') {
|
|
1933
1934
|
return INTERNAL_SEQ_FIELD;
|
|
1934
1935
|
}
|
|
1936
|
+
if (value === '_version') {
|
|
1937
|
+
return INTERNAL_VERSION_FIELD;
|
|
1938
|
+
}
|
|
1935
1939
|
const pascal = value
|
|
1936
1940
|
.split(/[_.-]/)
|
|
1937
1941
|
.filter(segment => segment.length > 0)
|
|
@@ -1942,6 +1946,16 @@ function toCamelCaseSegment(value) {
|
|
|
1942
1946
|
}
|
|
1943
1947
|
return pascal[0].toLowerCase() + pascal.slice(1);
|
|
1944
1948
|
}
|
|
1949
|
+
/** `{epoch}:{counter}` from a frame's `_version`, or null if it does not parse. */
|
|
1950
|
+
function parseVersion(version) {
|
|
1951
|
+
const separator = version.lastIndexOf(':');
|
|
1952
|
+
if (separator <= 0)
|
|
1953
|
+
return null;
|
|
1954
|
+
const counter = version.slice(separator + 1);
|
|
1955
|
+
if (!/^\d+$/.test(counter))
|
|
1956
|
+
return null;
|
|
1957
|
+
return { epoch: version.slice(0, separator), counter: BigInt(counter) };
|
|
1958
|
+
}
|
|
1945
1959
|
class FrameProcessor {
|
|
1946
1960
|
/**
|
|
1947
1961
|
* Observe the connection's cursor tracker instead of a private one.
|
|
@@ -1960,6 +1974,15 @@ class FrameProcessor {
|
|
|
1960
1974
|
this.latestProcessedSlot = null;
|
|
1961
1975
|
this.processedSlotWaiters = new Set();
|
|
1962
1976
|
this.cursors = new CursorTracker();
|
|
1977
|
+
/**
|
|
1978
|
+
* Whether the server's latest acknowledgement guaranteed whole entities.
|
|
1979
|
+
* One connection talks to one server, so its latest ack speaks for every
|
|
1980
|
+
* subscription on it; a reconnect re-acknowledges each one.
|
|
1981
|
+
*/
|
|
1982
|
+
this.serverSendsWholeEntities = false;
|
|
1983
|
+
/** Keys dropped by `maxEntriesPerView`, per view, oldest first; bounded. */
|
|
1984
|
+
this.evictedKeys = new Map();
|
|
1985
|
+
this.warnedEvictedViews = new Set();
|
|
1963
1986
|
this.storage = storage;
|
|
1964
1987
|
this.maxEntriesPerView = config.maxEntriesPerView === undefined
|
|
1965
1988
|
? DEFAULT_MAX_ENTRIES_PER_VIEW
|
|
@@ -2007,18 +2030,22 @@ class FrameProcessor {
|
|
|
2007
2030
|
const diagnostic = {
|
|
2008
2031
|
view: viewPath,
|
|
2009
2032
|
...context,
|
|
2033
|
+
reason: 'schema',
|
|
2010
2034
|
error: validationError,
|
|
2011
2035
|
};
|
|
2036
|
+
this.reportDiagnostic(diagnostic);
|
|
2037
|
+
if (this.warnOnValidationError) {
|
|
2038
|
+
console.warn('[Arete] Frame validation failed:', diagnostic);
|
|
2039
|
+
}
|
|
2040
|
+
return null;
|
|
2041
|
+
}
|
|
2042
|
+
reportDiagnostic(diagnostic) {
|
|
2012
2043
|
try {
|
|
2013
2044
|
this.onValidationError?.(diagnostic);
|
|
2014
2045
|
}
|
|
2015
2046
|
catch (callbackError) {
|
|
2016
2047
|
console.error('[Arete] Frame validation callback failed:', callbackError);
|
|
2017
2048
|
}
|
|
2018
|
-
if (this.warnOnValidationError) {
|
|
2019
|
-
console.warn('[Arete] Frame validation failed:', diagnostic);
|
|
2020
|
-
}
|
|
2021
|
-
return null;
|
|
2022
2049
|
}
|
|
2023
2050
|
hasSchema(viewPath) {
|
|
2024
2051
|
return this.getSchema(viewPath) !== null;
|
|
@@ -2085,6 +2112,50 @@ class FrameProcessor {
|
|
|
2085
2112
|
}
|
|
2086
2113
|
return leftIndex.localeCompare(rightIndex);
|
|
2087
2114
|
}
|
|
2115
|
+
extractVersion(data) {
|
|
2116
|
+
if (!isObject(data)) {
|
|
2117
|
+
return undefined;
|
|
2118
|
+
}
|
|
2119
|
+
const version = data._version;
|
|
2120
|
+
return typeof version === 'string' ? version : undefined;
|
|
2121
|
+
}
|
|
2122
|
+
getInternalVersion(data) {
|
|
2123
|
+
if (!isObject(data)) {
|
|
2124
|
+
return undefined;
|
|
2125
|
+
}
|
|
2126
|
+
const version = data[INTERNAL_VERSION_FIELD];
|
|
2127
|
+
return typeof version === 'string' ? version : undefined;
|
|
2128
|
+
}
|
|
2129
|
+
/**
|
|
2130
|
+
* Whether a frame at `incoming` is one this client already applied, or older.
|
|
2131
|
+
* A version is `{epoch}:{counter}`, and counters compare only within an
|
|
2132
|
+
* epoch: a server restart or a reloaded stack starts a new one, whose first
|
|
2133
|
+
* frame is newer than anything from the old one. A version that does not
|
|
2134
|
+
* parse cannot be ordered and is never stale.
|
|
2135
|
+
*/
|
|
2136
|
+
isStaleVersion(incoming, held) {
|
|
2137
|
+
if (held === undefined) {
|
|
2138
|
+
return false;
|
|
2139
|
+
}
|
|
2140
|
+
const next = parseVersion(incoming);
|
|
2141
|
+
const current = parseVersion(held);
|
|
2142
|
+
if (next === null || current === null || next.epoch !== current.epoch) {
|
|
2143
|
+
return false;
|
|
2144
|
+
}
|
|
2145
|
+
return next.counter <= current.counter;
|
|
2146
|
+
}
|
|
2147
|
+
attachInternalVersion(data, version) {
|
|
2148
|
+
if (!version || !isObject(data)) {
|
|
2149
|
+
return data;
|
|
2150
|
+
}
|
|
2151
|
+
Object.defineProperty(data, INTERNAL_VERSION_FIELD, {
|
|
2152
|
+
value: version,
|
|
2153
|
+
enumerable: false,
|
|
2154
|
+
configurable: true,
|
|
2155
|
+
writable: true,
|
|
2156
|
+
});
|
|
2157
|
+
return data;
|
|
2158
|
+
}
|
|
2088
2159
|
attachInternalSeq(_viewPath, data, seq) {
|
|
2089
2160
|
if (!seq || !isObject(data)) {
|
|
2090
2161
|
return data;
|
|
@@ -2305,6 +2376,7 @@ class FrameProcessor {
|
|
|
2305
2376
|
}
|
|
2306
2377
|
handleSubscribedFrame(frame) {
|
|
2307
2378
|
this.cursors.observe(frame);
|
|
2379
|
+
this.serverSendsWholeEntities = frame.wholeEntities === true;
|
|
2308
2380
|
const viewPath = frame.query.view;
|
|
2309
2381
|
if (this.storage.setViewConfig && frame.sort) {
|
|
2310
2382
|
this.storage.setViewConfig(viewPath, {
|
|
@@ -2329,10 +2401,11 @@ class FrameProcessor {
|
|
|
2329
2401
|
if (normalized === null) {
|
|
2330
2402
|
continue;
|
|
2331
2403
|
}
|
|
2332
|
-
const nextValue = this.attachInternalSeq(viewPath, normalized, this.extractSeq(entity.data));
|
|
2404
|
+
const nextValue = this.attachInternalVersion(this.attachInternalSeq(viewPath, normalized, this.extractSeq(entity.data)), this.extractVersion(entity.data));
|
|
2333
2405
|
acceptedKeys.push(entity.key);
|
|
2334
2406
|
const previousValue = this.storage.get(viewPath, entity.key);
|
|
2335
2407
|
this.storage.set(viewPath, entity.key, nextValue);
|
|
2408
|
+
this.forgetEvicted(viewPath, entity.key);
|
|
2336
2409
|
this.storage.notifyUpdate(viewPath, entity.key, {
|
|
2337
2410
|
type: 'upsert',
|
|
2338
2411
|
key: entity.key,
|
|
@@ -2351,18 +2424,24 @@ class FrameProcessor {
|
|
|
2351
2424
|
const previousValue = this.storage.get(viewPath, frame.key);
|
|
2352
2425
|
const previousSequence = this.getInternalSeq(previousValue);
|
|
2353
2426
|
const cursor = this.cursors.observe(frame);
|
|
2354
|
-
//
|
|
2355
|
-
//
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2427
|
+
// A server that stamps `_version` orders one key's frames by it. `seq`
|
|
2428
|
+
// cannot: every update decoded from one transaction shares one, and within
|
|
2429
|
+
// a slot account updates and instructions number themselves differently,
|
|
2430
|
+
// so a later frame can carry a lower `seq`. Without a version (an older
|
|
2431
|
+
// server) the `seq` rule applies. On a tape the offset is the identity, so
|
|
2432
|
+
// neither does.
|
|
2433
|
+
const frameVersion = this.extractVersion(frame.data);
|
|
2434
|
+
const duplicateOrStale = frame.offset === undefined && (frameVersion !== undefined
|
|
2435
|
+
? this.isStaleVersion(frameVersion, this.getInternalVersion(previousValue))
|
|
2436
|
+
: frame.seq !== undefined
|
|
2437
|
+
&& previousSequence !== undefined
|
|
2438
|
+
&& this.compareSeq(frame.seq, previousSequence) <= 0);
|
|
2360
2439
|
switch (frame.op) {
|
|
2361
2440
|
case 'upsert':
|
|
2362
2441
|
{
|
|
2363
2442
|
if (frame.data === null)
|
|
2364
2443
|
break;
|
|
2365
|
-
if (
|
|
2444
|
+
if (duplicateOrStale && previousValue !== null) {
|
|
2366
2445
|
const update = {
|
|
2367
2446
|
type: 'upsert',
|
|
2368
2447
|
key: frame.key,
|
|
@@ -2379,14 +2458,17 @@ class FrameProcessor {
|
|
|
2379
2458
|
if (normalized === null) {
|
|
2380
2459
|
break;
|
|
2381
2460
|
}
|
|
2382
|
-
const nextValue = this.attachInternalSeq(viewPath, normalized,
|
|
2461
|
+
const nextValue = this.attachInternalVersion(this.attachInternalSeq(viewPath, normalized,
|
|
2383
2462
|
// Fall back to the previous entity's sequence, as the patch branch
|
|
2384
2463
|
// below does. Without this an unsequenced upsert drops the tracked
|
|
2385
2464
|
// sequence (it rides on the object being replaced), disarming the
|
|
2386
2465
|
// staleness guard until the next sequenced frame and letting a
|
|
2387
2466
|
// later older frame overwrite newer data.
|
|
2388
|
-
frame.seq ?? this.extractSeq(frame.data) ?? this.getInternalSeq(previousValue))
|
|
2467
|
+
frame.seq ?? this.extractSeq(frame.data) ?? this.getInternalSeq(previousValue)),
|
|
2468
|
+
// An unversioned write keeps the version it replaces, likewise.
|
|
2469
|
+
frameVersion ?? this.getInternalVersion(previousValue));
|
|
2389
2470
|
this.storage.set(viewPath, frame.key, nextValue);
|
|
2471
|
+
this.forgetEvicted(viewPath, frame.key);
|
|
2390
2472
|
const update = {
|
|
2391
2473
|
type: 'upsert',
|
|
2392
2474
|
key: frame.key,
|
|
@@ -2403,6 +2485,19 @@ class FrameProcessor {
|
|
|
2403
2485
|
if (frame.data === null)
|
|
2404
2486
|
break;
|
|
2405
2487
|
const existing = this.storage.get(viewPath, frame.key);
|
|
2488
|
+
if (existing === null && frame.offset === undefined && this.serverSendsWholeEntities) {
|
|
2489
|
+
// A patch carries only changed fields. With no entity to merge into
|
|
2490
|
+
// (never received, or evicted) it would be stored as a partial
|
|
2491
|
+
// entity typed as complete, so it is discarded: no storage write, no
|
|
2492
|
+
// sequence, no query membership, no update. The key appears when the
|
|
2493
|
+
// server sends a full upsert, which a server acknowledging
|
|
2494
|
+
// `wholeEntities` guarantees for every key it has not sent. Tape
|
|
2495
|
+
// records (frames with an `offset`) are events rather than entity
|
|
2496
|
+
// state and are always applied: a consumer resuming from a cursor
|
|
2497
|
+
// holds what came before it.
|
|
2498
|
+
this.reportDiscardedPatch(viewPath, frame);
|
|
2499
|
+
break;
|
|
2500
|
+
}
|
|
2406
2501
|
const normalizedPatch = this.normalizeEntity(viewPath, frame.data, {
|
|
2407
2502
|
key: frame.key,
|
|
2408
2503
|
operation: 'patch',
|
|
@@ -2411,7 +2506,7 @@ class FrameProcessor {
|
|
|
2411
2506
|
if (normalizedPatch === null) {
|
|
2412
2507
|
break;
|
|
2413
2508
|
}
|
|
2414
|
-
if (
|
|
2509
|
+
if (duplicateOrStale && existing !== null) {
|
|
2415
2510
|
const update = {
|
|
2416
2511
|
type: 'patch',
|
|
2417
2512
|
key: frame.key,
|
|
@@ -2424,7 +2519,7 @@ class FrameProcessor {
|
|
|
2424
2519
|
const merged = existing
|
|
2425
2520
|
? deepMergeWithAppend(existing, normalizedPatch, appendPaths)
|
|
2426
2521
|
: normalizedPatch;
|
|
2427
|
-
const nextValue = this.attachInternalSeq(viewPath, merged, frame.seq ?? this.extractSeq(frame.data) ?? this.getInternalSeq(existing));
|
|
2522
|
+
const nextValue = this.attachInternalVersion(this.attachInternalSeq(viewPath, merged, frame.seq ?? this.extractSeq(frame.data) ?? this.getInternalSeq(existing)), frameVersion ?? this.getInternalVersion(existing));
|
|
2428
2523
|
this.storage.set(viewPath, frame.key, nextValue);
|
|
2429
2524
|
const update = {
|
|
2430
2525
|
type: 'patch',
|
|
@@ -2443,6 +2538,7 @@ class FrameProcessor {
|
|
|
2443
2538
|
break;
|
|
2444
2539
|
case 'delete':
|
|
2445
2540
|
this.storage.delete(viewPath, frame.key);
|
|
2541
|
+
this.forgetEvicted(viewPath, frame.key);
|
|
2446
2542
|
this.storage.notifyUpdate(viewPath, frame.key, {
|
|
2447
2543
|
type: 'delete',
|
|
2448
2544
|
key: frame.key,
|
|
@@ -2479,8 +2575,56 @@ class FrameProcessor {
|
|
|
2479
2575
|
return;
|
|
2480
2576
|
while (this.storage.size(viewPath) > this.maxEntriesPerView) {
|
|
2481
2577
|
const evicted = this.storage.evictOldest(viewPath);
|
|
2482
|
-
if (evicted
|
|
2483
|
-
|
|
2578
|
+
if (evicted === undefined)
|
|
2579
|
+
continue;
|
|
2580
|
+
this.queryStore?.evict(viewPath, evicted);
|
|
2581
|
+
this.rememberEvicted(viewPath, evicted, this.maxEntriesPerView);
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
/**
|
|
2585
|
+
* Remember a key `maxEntriesPerView` dropped, so a later patch for it can be
|
|
2586
|
+
* reported as such. At most `limit` keys per view, most recent kept.
|
|
2587
|
+
*/
|
|
2588
|
+
rememberEvicted(viewPath, key, limit) {
|
|
2589
|
+
let keys = this.evictedKeys.get(viewPath);
|
|
2590
|
+
if (!keys) {
|
|
2591
|
+
keys = new Set();
|
|
2592
|
+
this.evictedKeys.set(viewPath, keys);
|
|
2593
|
+
}
|
|
2594
|
+
keys.delete(key);
|
|
2595
|
+
keys.add(key);
|
|
2596
|
+
while (keys.size > Math.max(limit, 1)) {
|
|
2597
|
+
const oldest = keys.values().next().value;
|
|
2598
|
+
if (oldest === undefined)
|
|
2599
|
+
break;
|
|
2600
|
+
keys.delete(oldest);
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2603
|
+
forgetEvicted(viewPath, key) {
|
|
2604
|
+
this.evictedKeys.get(viewPath)?.delete(key);
|
|
2605
|
+
}
|
|
2606
|
+
reportDiscardedPatch(viewPath, frame) {
|
|
2607
|
+
const seq = frame.seq ?? this.extractSeq(frame.data);
|
|
2608
|
+
const evicted = this.evictedKeys.get(viewPath)?.has(frame.key) === true;
|
|
2609
|
+
const error = evicted
|
|
2610
|
+
? new AreteError(`Discarded patch for '${frame.key}' in ${viewPath}: this client dropped the entity to `
|
|
2611
|
+
+ `stay within maxEntriesPerView (${String(this.maxEntriesPerView)}), and the server `
|
|
2612
|
+
+ 'sends no upsert for it until it re-enters the query or the subscription is '
|
|
2613
|
+
+ 're-established; raise maxEntriesPerView above the subscription size', 'PATCH_FOR_EVICTED_KEY')
|
|
2614
|
+
: new AreteError(`Discarded patch for '${frame.key}' in ${viewPath}: the client holds no entity for `
|
|
2615
|
+
+ 'this key; it appears when the server sends a full upsert', 'PATCH_FOR_UNKNOWN_KEY');
|
|
2616
|
+
const diagnostic = {
|
|
2617
|
+
view: viewPath,
|
|
2618
|
+
key: frame.key,
|
|
2619
|
+
...(seq !== undefined ? { seq } : {}),
|
|
2620
|
+
operation: 'patch',
|
|
2621
|
+
reason: evicted ? 'evicted-key' : 'unknown-key',
|
|
2622
|
+
error,
|
|
2623
|
+
};
|
|
2624
|
+
this.reportDiagnostic(diagnostic);
|
|
2625
|
+
if (evicted && this.warnOnValidationError && !this.warnedEvictedViews.has(viewPath)) {
|
|
2626
|
+
this.warnedEvictedViews.add(viewPath);
|
|
2627
|
+
console.warn('[Arete] Patch dropped for a locally evicted entity:', diagnostic);
|
|
2484
2628
|
}
|
|
2485
2629
|
}
|
|
2486
2630
|
}
|
|
@@ -2627,6 +2771,66 @@ class MemoryAdapter {
|
|
|
2627
2771
|
}
|
|
2628
2772
|
}
|
|
2629
2773
|
|
|
2774
|
+
/** Whether a sort value cannot be ranked: it is missing or `null`. */
|
|
2775
|
+
function isUnrankedSortValue(value) {
|
|
2776
|
+
return value === undefined || value === null;
|
|
2777
|
+
}
|
|
2778
|
+
/**
|
|
2779
|
+
* Compares two ranked sort values in ascending order. Numbers and bigints
|
|
2780
|
+
* compare numerically (also against each other), booleans as `false < true`,
|
|
2781
|
+
* and everything else with `localeCompare` on its string form (the SDK
|
|
2782
|
+
* ordering rule, shared with the Rust and Python SDKs).
|
|
2783
|
+
*
|
|
2784
|
+
* This is not the server's ranking. The server compares strings by byte
|
|
2785
|
+
* order, except that two decimal-integer strings compare numerically, and it
|
|
2786
|
+
* ranks numbers below strings and booleans below both. The server's order
|
|
2787
|
+
* decides which entities fill a `take` window; this one only orders the
|
|
2788
|
+
* members the client already holds, so the two can list a window's members
|
|
2789
|
+
* differently, never choose different members.
|
|
2790
|
+
*/
|
|
2791
|
+
function compareSortValues(left, right) {
|
|
2792
|
+
if (left === right)
|
|
2793
|
+
return 0;
|
|
2794
|
+
if (isUnrankedSortValue(left))
|
|
2795
|
+
return isUnrankedSortValue(right) ? 0 : 1;
|
|
2796
|
+
if (isUnrankedSortValue(right))
|
|
2797
|
+
return -1;
|
|
2798
|
+
if (typeof left === 'number' && typeof right === 'number')
|
|
2799
|
+
return left - right;
|
|
2800
|
+
if (typeof left === 'bigint' && typeof right === 'bigint')
|
|
2801
|
+
return left < right ? -1 : 1;
|
|
2802
|
+
if (typeof left === 'bigint' && typeof right === 'number' && Number.isInteger(right)) {
|
|
2803
|
+
const other = BigInt(right);
|
|
2804
|
+
return left === other ? 0 : left < other ? -1 : 1;
|
|
2805
|
+
}
|
|
2806
|
+
if (typeof left === 'number' && typeof right === 'bigint' && Number.isInteger(left)) {
|
|
2807
|
+
const other = BigInt(left);
|
|
2808
|
+
return other === right ? 0 : other < right ? -1 : 1;
|
|
2809
|
+
}
|
|
2810
|
+
if (typeof left === 'boolean' && typeof right === 'boolean')
|
|
2811
|
+
return Number(left) - Number(right);
|
|
2812
|
+
return String(left).localeCompare(String(right));
|
|
2813
|
+
}
|
|
2814
|
+
/**
|
|
2815
|
+
* Orders two entities' sort values for a view sorted in `order`.
|
|
2816
|
+
*
|
|
2817
|
+
* A missing or `null` sort value cannot be ranked, so it sorts after every
|
|
2818
|
+
* ranked value in both directions: `desc` reverses only the comparison between
|
|
2819
|
+
* two ranked values. Two unranked values tie. Callers break ties on the entity
|
|
2820
|
+
* key, ascending, whatever the order. The unranked-last rule is the server's
|
|
2821
|
+
* and the Rust and Python SDKs'; how two ranked values compare is
|
|
2822
|
+
* `compareRanked` (by default {@link compareSortValues}, which differs from
|
|
2823
|
+
* the server as described there).
|
|
2824
|
+
*/
|
|
2825
|
+
function compareSortOrder(left, right, order, compareRanked = compareSortValues) {
|
|
2826
|
+
const leftUnranked = isUnrankedSortValue(left);
|
|
2827
|
+
const rightUnranked = isUnrankedSortValue(right);
|
|
2828
|
+
if (leftUnranked || rightUnranked)
|
|
2829
|
+
return Number(leftUnranked) - Number(rightUnranked);
|
|
2830
|
+
const compared = compareRanked(left, right);
|
|
2831
|
+
return order === 'desc' ? -compared : compared;
|
|
2832
|
+
}
|
|
2833
|
+
|
|
2630
2834
|
function getNestedValue$1(obj, path) {
|
|
2631
2835
|
let current = obj;
|
|
2632
2836
|
for (const segment of path) {
|
|
@@ -2638,33 +2842,6 @@ function getNestedValue$1(obj, path) {
|
|
|
2638
2842
|
}
|
|
2639
2843
|
return current;
|
|
2640
2844
|
}
|
|
2641
|
-
function compareSortValues(a, b) {
|
|
2642
|
-
if (a === b)
|
|
2643
|
-
return 0;
|
|
2644
|
-
if (a === undefined || a === null)
|
|
2645
|
-
return -1;
|
|
2646
|
-
if (b === undefined || b === null)
|
|
2647
|
-
return 1;
|
|
2648
|
-
if (typeof a === 'bigint' && typeof b === 'bigint') {
|
|
2649
|
-
return a < b ? -1 : 1;
|
|
2650
|
-
}
|
|
2651
|
-
if (typeof a === 'number' && typeof b === 'number') {
|
|
2652
|
-
return a - b;
|
|
2653
|
-
}
|
|
2654
|
-
if (typeof a === 'bigint' && typeof b === 'number' && Number.isInteger(b)) {
|
|
2655
|
-
return a < BigInt(b) ? -1 : 1;
|
|
2656
|
-
}
|
|
2657
|
-
if (typeof a === 'number' && typeof b === 'bigint' && Number.isInteger(a)) {
|
|
2658
|
-
return BigInt(a) < b ? -1 : 1;
|
|
2659
|
-
}
|
|
2660
|
-
if (typeof a === 'string' && typeof b === 'string') {
|
|
2661
|
-
return a.localeCompare(b);
|
|
2662
|
-
}
|
|
2663
|
-
if (typeof a === 'boolean' && typeof b === 'boolean') {
|
|
2664
|
-
return (a ? 1 : 0) - (b ? 1 : 0);
|
|
2665
|
-
}
|
|
2666
|
-
return String(a).localeCompare(String(b));
|
|
2667
|
-
}
|
|
2668
2845
|
class SortedStorageDecorator {
|
|
2669
2846
|
constructor(inner) {
|
|
2670
2847
|
this.sortConfigs = new Map();
|
|
@@ -2784,7 +2961,6 @@ class SortedStorageDecorator {
|
|
|
2784
2961
|
}
|
|
2785
2962
|
binarySearchInsertPosition(viewPath, sortedKeys, sortConfig, newKey, newValue) {
|
|
2786
2963
|
const newSortValue = getNestedValue$1(newValue, sortConfig.field);
|
|
2787
|
-
const isDesc = sortConfig.order === 'desc';
|
|
2788
2964
|
let low = 0;
|
|
2789
2965
|
let high = sortedKeys.length;
|
|
2790
2966
|
while (low < high) {
|
|
@@ -2792,9 +2968,8 @@ class SortedStorageDecorator {
|
|
|
2792
2968
|
const midKey = sortedKeys[mid];
|
|
2793
2969
|
const midEntity = this.inner.get(viewPath, midKey);
|
|
2794
2970
|
const midValue = getNestedValue$1(midEntity, sortConfig.field);
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
cmp = -cmp;
|
|
2971
|
+
// Unranked (missing or null) values sort last in both directions.
|
|
2972
|
+
let cmp = compareSortOrder(newSortValue, midValue, sortConfig.order);
|
|
2798
2973
|
if (cmp === 0) {
|
|
2799
2974
|
cmp = newKey.localeCompare(midKey);
|
|
2800
2975
|
}
|
|
@@ -2811,14 +2986,11 @@ class SortedStorageDecorator {
|
|
|
2811
2986
|
const allKeys = this.inner.keys(viewPath);
|
|
2812
2987
|
if (allKeys.length === 0)
|
|
2813
2988
|
return;
|
|
2814
|
-
const isDesc = sortConfig.order === 'desc';
|
|
2815
2989
|
const entries = allKeys.map(k => [k, this.inner.get(viewPath, k)]);
|
|
2816
2990
|
entries.sort((a, b) => {
|
|
2817
2991
|
const aValue = getNestedValue$1(a[1], sortConfig.field);
|
|
2818
2992
|
const bValue = getNestedValue$1(b[1], sortConfig.field);
|
|
2819
|
-
let cmp =
|
|
2820
|
-
if (isDesc)
|
|
2821
|
-
cmp = -cmp;
|
|
2993
|
+
let cmp = compareSortOrder(aValue, bValue, sortConfig.order);
|
|
2822
2994
|
if (cmp === 0) {
|
|
2823
2995
|
cmp = a[0].localeCompare(b[0]);
|
|
2824
2996
|
}
|
|
@@ -2837,25 +3009,9 @@ function getNestedValue(value, path) {
|
|
|
2837
3009
|
}
|
|
2838
3010
|
return current;
|
|
2839
3011
|
}
|
|
2840
|
-
function compareValues(left, right) {
|
|
2841
|
-
if (left === right)
|
|
2842
|
-
return 0;
|
|
2843
|
-
if (left === undefined || left === null)
|
|
2844
|
-
return -1;
|
|
2845
|
-
if (right === undefined || right === null)
|
|
2846
|
-
return 1;
|
|
2847
|
-
if (typeof left === 'number' && typeof right === 'number')
|
|
2848
|
-
return left - right;
|
|
2849
|
-
if (typeof left === 'bigint' && typeof right === 'bigint')
|
|
2850
|
-
return left < right ? -1 : 1;
|
|
2851
|
-
if (typeof left === 'boolean' && typeof right === 'boolean') {
|
|
2852
|
-
return Number(left) - Number(right);
|
|
2853
|
-
}
|
|
2854
|
-
return String(left).localeCompare(String(right));
|
|
2855
|
-
}
|
|
2856
3012
|
function compareSequences(left, right) {
|
|
2857
3013
|
if (typeof left !== 'string' || typeof right !== 'string')
|
|
2858
|
-
return
|
|
3014
|
+
return compareSortValues(left, right);
|
|
2859
3015
|
const [leftSlot, leftIndex = ''] = left.split(':', 2);
|
|
2860
3016
|
const [rightSlot, rightIndex = ''] = right.split(':', 2);
|
|
2861
3017
|
if (/^\d+$/.test(leftSlot ?? '') && /^\d+$/.test(rightSlot ?? '')) {
|
|
@@ -3147,9 +3303,9 @@ class QueryStore {
|
|
|
3147
3303
|
const right = field
|
|
3148
3304
|
? getNestedValue(this.storage.get(view, rightKey), field)
|
|
3149
3305
|
: record.sequences.get(rightKey);
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3306
|
+
// A missing or null sort value (or sequence) sorts after every ranked
|
|
3307
|
+
// one in both directions; ties break on the key, always ascending.
|
|
3308
|
+
const compared = compareSortOrder(left, right, order, field ? compareSortValues : compareSequences);
|
|
3153
3309
|
return compared === 0 ? leftKey.localeCompare(rightKey) : compared;
|
|
3154
3310
|
});
|
|
3155
3311
|
}
|
|
@@ -6225,16 +6381,22 @@ const PROGRAM_READ_DESCRIPTOR = Symbol.for('@usearete/sdk/program-read-descripto
|
|
|
6225
6381
|
/**
|
|
6226
6382
|
* Bundle a generated program definition with its default read descriptor.
|
|
6227
6383
|
*
|
|
6228
|
-
* Generated SDK entries call this automatically. The descriptor
|
|
6229
|
-
*
|
|
6384
|
+
* Generated SDK entries call this automatically. The descriptor lives under a
|
|
6385
|
+
* registry symbol, so `Object.keys` and `JSON.stringify` never see it, while
|
|
6386
|
+
* `{ ...program }` keeps it: a spread program still reads through its release.
|
|
6387
|
+
* Every other own property (including the runtime extension carrier) is copied
|
|
6388
|
+
* unchanged, except `packageReleaseHash`: the result reads differently from
|
|
6389
|
+
* the generated program SDK, so it is no longer provably that SDK. Generated
|
|
6390
|
+
* entries apply `withProgramIdentity` last.
|
|
6230
6391
|
*/
|
|
6231
6392
|
function withProgramRead(program, descriptor) {
|
|
6232
6393
|
const properties = Object.getOwnPropertyDescriptors(program);
|
|
6233
6394
|
Reflect.deleteProperty(properties, PROGRAM_READ_DESCRIPTOR);
|
|
6395
|
+
Reflect.deleteProperty(properties, 'packageReleaseHash');
|
|
6234
6396
|
const bundled = Object.create(Object.getPrototypeOf(program), properties);
|
|
6235
6397
|
Object.defineProperty(bundled, PROGRAM_READ_DESCRIPTOR, {
|
|
6236
6398
|
value: descriptor,
|
|
6237
|
-
enumerable:
|
|
6399
|
+
enumerable: true,
|
|
6238
6400
|
configurable: false,
|
|
6239
6401
|
writable: false,
|
|
6240
6402
|
});
|
|
@@ -6244,9 +6406,62 @@ function withProgramRead(program, descriptor) {
|
|
|
6244
6406
|
function getProgramReadDescriptor(program) {
|
|
6245
6407
|
return program?.[PROGRAM_READ_DESCRIPTOR];
|
|
6246
6408
|
}
|
|
6409
|
+
/**
|
|
6410
|
+
* Stamp a program definition with the identity of the program SDK it is.
|
|
6411
|
+
*
|
|
6412
|
+
* Generated program SDK entries call this last, after the package's own
|
|
6413
|
+
* extension and read descriptor are applied, so the identity describes
|
|
6414
|
+
* exactly the generated SDK. `extendProgram`, `extendPrograms` and
|
|
6415
|
+
* `withProgramRead` drop the identity again: a program changed outside its
|
|
6416
|
+
* generated SDK is no longer provably that SDK. Every other own property,
|
|
6417
|
+
* including symbol-keyed runtime extensions and the read descriptor, is copied
|
|
6418
|
+
* unchanged. An absent or empty `packageReleaseHash` removes the identity.
|
|
6419
|
+
*/
|
|
6420
|
+
function withProgramIdentity(program, identity) {
|
|
6421
|
+
const properties = Object.getOwnPropertyDescriptors(program);
|
|
6422
|
+
Reflect.deleteProperty(properties, 'packageReleaseHash');
|
|
6423
|
+
const stamped = Object.create(Object.getPrototypeOf(program), properties);
|
|
6424
|
+
if (typeof identity.packageReleaseHash === 'string' && identity.packageReleaseHash.length > 0) {
|
|
6425
|
+
Object.defineProperty(stamped, 'packageReleaseHash', {
|
|
6426
|
+
value: identity.packageReleaseHash,
|
|
6427
|
+
enumerable: true,
|
|
6428
|
+
configurable: true,
|
|
6429
|
+
writable: true,
|
|
6430
|
+
});
|
|
6431
|
+
}
|
|
6432
|
+
return stamped;
|
|
6433
|
+
}
|
|
6247
6434
|
|
|
6248
|
-
|
|
6249
|
-
|
|
6435
|
+
/**
|
|
6436
|
+
* Version of the extension contract: `defineStackExtensions`,
|
|
6437
|
+
* `defineProgramExtensions`, `extendProgram`, `extendPrograms`, `extendStack`,
|
|
6438
|
+
* `withProgramRead`, and the instruction helpers generated code imports
|
|
6439
|
+
* (`createInstructionHandler`, `buildInstruction`, `instructionOperation`,
|
|
6440
|
+
* `createPreparedInstruction`, the PDA and read helpers, …).
|
|
6441
|
+
*
|
|
6442
|
+
* It only changes on a breaking change to that contract, so generated SDKs and
|
|
6443
|
+
* extensions built for one value keep working with every SDK release that
|
|
6444
|
+
* exports the same value. The same number is published in this package's
|
|
6445
|
+
* `package.json` as `arete.extensionApi` for tools that read `node_modules`.
|
|
6446
|
+
*/
|
|
6447
|
+
const EXTENSION_API_VERSION = 1;
|
|
6448
|
+
/**
|
|
6449
|
+
* Runtime extension carriers live under registry symbols (`Symbol.for`), so
|
|
6450
|
+
* two copies of this module still read each other's definitions. The
|
|
6451
|
+
* properties are enumerable: `{ ...stack }` and `{ ...program }` keep them,
|
|
6452
|
+
* while `Object.keys`, `for…in` and `JSON.stringify` never see a symbol key.
|
|
6453
|
+
*
|
|
6454
|
+
* The constants are typed as their historical property names rather than as
|
|
6455
|
+
* `unique symbol`. Generated program SDKs use them as computed keys inside
|
|
6456
|
+
* object literals, and TypeScript can only name a `unique symbol` key in
|
|
6457
|
+
* emitted declarations from a file that imports it; a string-literal key type
|
|
6458
|
+
* keeps every generated `.d.ts` nameable and identical across module copies.
|
|
6459
|
+
* Always read and write these properties through the constants (or
|
|
6460
|
+
* `getStackRuntimeExtensions` / `getProgramRuntimeExtensions`), never by the
|
|
6461
|
+
* literal name.
|
|
6462
|
+
*/
|
|
6463
|
+
const STACK_RUNTIME_EXTENSIONS = Symbol.for('@usearete/sdk/stack-runtime-extensions');
|
|
6464
|
+
const PROGRAM_OPERATION_EXTENSIONS = Symbol.for('@usearete/sdk/program-runtime-extensions');
|
|
6250
6465
|
function defineProgramExtensions() {
|
|
6251
6466
|
return (extensions) => extensions;
|
|
6252
6467
|
}
|
|
@@ -6275,7 +6490,12 @@ function extendProgram(program, extensions) {
|
|
|
6275
6490
|
if (readDescriptor) {
|
|
6276
6491
|
Object.defineProperty(extended, PROGRAM_READ_DESCRIPTOR, readDescriptor);
|
|
6277
6492
|
}
|
|
6493
|
+
// The extended program is no longer byte-for-byte the generated one, nor
|
|
6494
|
+
// provably the program SDK its package release describes: extensions
|
|
6495
|
+
// applied outside generated code must not borrow that identity. Generated
|
|
6496
|
+
// entries stamp it back with `withProgramIdentity` after their own.
|
|
6278
6497
|
delete extended.sdkDefinitionHash;
|
|
6498
|
+
delete extended.packageReleaseHash;
|
|
6279
6499
|
for (const key of ['pdas', 'accounts', 'queries', 'addresses', 'constants', 'defaults', 'math']) {
|
|
6280
6500
|
const extensionValue = extensions[key];
|
|
6281
6501
|
if (extensionValue !== undefined) {
|
|
@@ -6311,7 +6531,8 @@ function extendProgram(program, extensions) {
|
|
|
6311
6531
|
return mergeNamespace(baseRead, extensionReadFactory?.(context));
|
|
6312
6532
|
},
|
|
6313
6533
|
},
|
|
6314
|
-
|
|
6534
|
+
// Enumerable so spreading the extended program keeps its operations.
|
|
6535
|
+
enumerable: true,
|
|
6315
6536
|
configurable: false,
|
|
6316
6537
|
writable: false,
|
|
6317
6538
|
});
|
|
@@ -6356,7 +6577,8 @@ function extendStack(stack, extensions) {
|
|
|
6356
6577
|
? (client) => mergeNamespace(baseRuntime.createFlows(client), extensions.createFlows(client))
|
|
6357
6578
|
: extensions.createFlows ?? baseRuntime?.createFlows,
|
|
6358
6579
|
},
|
|
6359
|
-
|
|
6580
|
+
// Enumerable so `{ ...stack }` keeps `read`, `flows` and `readArgCounts`.
|
|
6581
|
+
enumerable: true,
|
|
6360
6582
|
configurable: false,
|
|
6361
6583
|
writable: false,
|
|
6362
6584
|
});
|
|
@@ -6394,7 +6616,7 @@ function isPreparedInstruction(instruction) {
|
|
|
6394
6616
|
&& instruction.kind === 'instruction'
|
|
6395
6617
|
&& 'instruction' in instruction;
|
|
6396
6618
|
}
|
|
6397
|
-
function nonEmpty(values, label) {
|
|
6619
|
+
function nonEmpty$1(values, label) {
|
|
6398
6620
|
if (values.length === 0) {
|
|
6399
6621
|
throw new Error(`${label} must contain at least one item`);
|
|
6400
6622
|
}
|
|
@@ -6407,7 +6629,7 @@ function inferSignerAddresses(instructions) {
|
|
|
6407
6629
|
return dedupe(instructions.flatMap((instruction) => instruction.keys.filter((key) => key.isSigner).map((key) => key.pubkey)));
|
|
6408
6630
|
}
|
|
6409
6631
|
function createPreparedTransactionBody(input) {
|
|
6410
|
-
const instructions = nonEmpty(input.instructions, `Transaction '${input.name}'`);
|
|
6632
|
+
const instructions = nonEmpty$1(input.instructions, `Transaction '${input.name}'`);
|
|
6411
6633
|
return {
|
|
6412
6634
|
name: input.name,
|
|
6413
6635
|
instructions,
|
|
@@ -6420,7 +6642,7 @@ function createPlan(name, artifacts, transactions) {
|
|
|
6420
6642
|
return {
|
|
6421
6643
|
name,
|
|
6422
6644
|
artifacts,
|
|
6423
|
-
transactions: nonEmpty(transactions, `Flow '${name}'`),
|
|
6645
|
+
transactions: nonEmpty$1(transactions, `Flow '${name}'`),
|
|
6424
6646
|
};
|
|
6425
6647
|
}
|
|
6426
6648
|
function createPreparedInstruction(input) {
|
|
@@ -6706,8 +6928,8 @@ async function executePreparedOperation(host, operation, options = {}) {
|
|
|
6706
6928
|
kind: 'flow',
|
|
6707
6929
|
operationName: operation.name,
|
|
6708
6930
|
artifacts: operation.artifacts,
|
|
6709
|
-
signatures: nonEmpty(receipts.map((receipt) => receipt.signature), `Operation '${operation.name}' signatures`),
|
|
6710
|
-
transactions: nonEmpty(receipts, `Operation '${operation.name}' receipts`),
|
|
6931
|
+
signatures: nonEmpty$1(receipts.map((receipt) => receipt.signature), `Operation '${operation.name}' signatures`),
|
|
6932
|
+
transactions: nonEmpty$1(receipts, `Operation '${operation.name}' receipts`),
|
|
6711
6933
|
...callbackErrorResult,
|
|
6712
6934
|
};
|
|
6713
6935
|
}
|
|
@@ -6983,6 +7205,127 @@ function createProgramReadTransport(config) {
|
|
|
6983
7205
|
};
|
|
6984
7206
|
}
|
|
6985
7207
|
|
|
7208
|
+
/** Error code for two different program SDKs claiming one program key. */
|
|
7209
|
+
const PROGRAM_KEY_CONFLICT = 'PROGRAM_KEY_CONFLICT';
|
|
7210
|
+
function nonEmpty(value) {
|
|
7211
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
7212
|
+
}
|
|
7213
|
+
/** Compare two program definitions by identity, never by name. */
|
|
7214
|
+
function compareProgramIdentity(left, right) {
|
|
7215
|
+
if (left === right)
|
|
7216
|
+
return 'same';
|
|
7217
|
+
const leftRelease = nonEmpty(left.packageReleaseHash);
|
|
7218
|
+
const rightRelease = nonEmpty(right.packageReleaseHash);
|
|
7219
|
+
if (leftRelease !== undefined && rightRelease !== undefined) {
|
|
7220
|
+
return leftRelease === rightRelease ? 'same' : 'different';
|
|
7221
|
+
}
|
|
7222
|
+
const leftSpec = nonEmpty(left.programSpecHash);
|
|
7223
|
+
return leftSpec !== undefined && leftSpec === nonEmpty(right.programSpecHash)
|
|
7224
|
+
? 'unproven'
|
|
7225
|
+
: 'different';
|
|
7226
|
+
}
|
|
7227
|
+
/**
|
|
7228
|
+
* Whether two program definitions are provably the same program SDK: the same
|
|
7229
|
+
* object, or both carry the same `packageReleaseHash` (the program package
|
|
7230
|
+
* release they were generated from). Anything else is not provably the same,
|
|
7231
|
+
* even under the same name.
|
|
7232
|
+
*/
|
|
7233
|
+
function isSameProgramSdk(left, right) {
|
|
7234
|
+
return compareProgramIdentity(left, right) === 'same';
|
|
7235
|
+
}
|
|
7236
|
+
function identityOf(program) {
|
|
7237
|
+
const release = nonEmpty(program.packageReleaseHash);
|
|
7238
|
+
if (release !== undefined)
|
|
7239
|
+
return `package release ${release}`;
|
|
7240
|
+
const spec = nonEmpty(program.programSpecHash);
|
|
7241
|
+
return spec !== undefined
|
|
7242
|
+
? `program spec ${spec}, no package release`
|
|
7243
|
+
: 'no program identity';
|
|
7244
|
+
}
|
|
7245
|
+
const warned = new Set();
|
|
7246
|
+
/** Emit a program identity warning once per distinct message. */
|
|
7247
|
+
function warnOnce(message) {
|
|
7248
|
+
if (warned.has(message))
|
|
7249
|
+
return;
|
|
7250
|
+
warned.add(message);
|
|
7251
|
+
console.warn(`[Arete] ${message}`);
|
|
7252
|
+
}
|
|
7253
|
+
/**
|
|
7254
|
+
* A program attached next to a stack (`withPrograms`, `ConnectOptions.programs`,
|
|
7255
|
+
* `useArete(stack, { programs })`, a session member's `programs`) under a key
|
|
7256
|
+
* the stack already provides with a different program SDK.
|
|
7257
|
+
*/
|
|
7258
|
+
function attachedProgramKeyConflict(stackName, key, stackProgram, attached) {
|
|
7259
|
+
return new AreteError(`Program key '${key}' conflicts with stack '${stackName}': the stack already provides `
|
|
7260
|
+
+ `a different '${key}' program SDK (stack: ${identityOf(stackProgram)}; attached: `
|
|
7261
|
+
+ `${identityOf(attached)}). Use the stack's program at programs.${key}, or attach the `
|
|
7262
|
+
+ `other program under a different key, one the stack does not use.`, PROGRAM_KEY_CONFLICT, { key, stack: stackName });
|
|
7263
|
+
}
|
|
7264
|
+
/**
|
|
7265
|
+
* An attached program replaces the stack's program under one key without a
|
|
7266
|
+
* provable identity match (same program spec, no shared package release).
|
|
7267
|
+
*/
|
|
7268
|
+
function warnUnprovenAttachedProgram(stackName, key, stackProgram, attached) {
|
|
7269
|
+
warnOnce(`programs.${key} uses the program attached to stack '${stackName}': it has the same `
|
|
7270
|
+
+ `program spec as the stack's '${key}' program but could not be proven identical `
|
|
7271
|
+
+ `(stack: ${identityOf(stackProgram)}; attached: ${identityOf(attached)}).`);
|
|
7272
|
+
}
|
|
7273
|
+
/** A standalone session program under a key a session stack already provides. */
|
|
7274
|
+
function sessionProgramKeyConflict(stackKey, key, stackProgram, standalone) {
|
|
7275
|
+
return new AreteError(`Program key '${key}' conflicts with stack '${stackKey}' in this session: the stack `
|
|
7276
|
+
+ `already provides a different '${key}' program SDK (stack: ${identityOf(stackProgram)}; `
|
|
7277
|
+
+ `standalone: ${identityOf(standalone)}). Use session.stacks.${stackKey}.programs.${key}, `
|
|
7278
|
+
+ `or attach the standalone program under a different key in createSession({ programs }).`, PROGRAM_KEY_CONFLICT, { key, stack: stackKey });
|
|
7279
|
+
}
|
|
7280
|
+
/**
|
|
7281
|
+
* A standalone session program takes the top-level key from stacks whose
|
|
7282
|
+
* program it could not be proven identical to: a different identity
|
|
7283
|
+
* (`'identity'`), or the same program SDK read through a different program
|
|
7284
|
+
* read configuration than the stack's (`'program-read'`).
|
|
7285
|
+
*/
|
|
7286
|
+
function warnUnprovenSessionProgram(key, standalone, providers, cause = 'identity') {
|
|
7287
|
+
const stacks = providers.map(({ stackKey }) => `'${stackKey}'`).join(' and ');
|
|
7288
|
+
const paths = providers
|
|
7289
|
+
.map(({ stackKey }) => `session.stacks.${stackKey}.programs.${key}`)
|
|
7290
|
+
.join(' and ');
|
|
7291
|
+
const identities = `standalone: ${identityOf(standalone)}; ${providers
|
|
7292
|
+
.map(({ stackKey, program }) => `${stackKey}: ${identityOf(program)}`)
|
|
7293
|
+
.join('; ')}`;
|
|
7294
|
+
const reason = cause === 'program-read'
|
|
7295
|
+
? `it is the same program SDK as the '${key}' program of stack ${stacks} (${identities}) `
|
|
7296
|
+
+ `but its programReads.${key} descriptor differs from the one the stack reads through, `
|
|
7297
|
+
+ `so it keeps its own program read configuration`
|
|
7298
|
+
: `it has the same program spec as the '${key}' program of stack ${stacks} but could not `
|
|
7299
|
+
+ `be proven identical (${identities})`;
|
|
7300
|
+
warnOnce(`session.programs.${key} uses the standalone program: ${reason}. `
|
|
7301
|
+
+ `${paths} keep${providers.length === 1 ? 's' : ''} the stack's program.`);
|
|
7302
|
+
}
|
|
7303
|
+
/**
|
|
7304
|
+
* Several session stacks bundle one key with the same program spec, not all
|
|
7305
|
+
* provably the same SDK; the first stack's program takes the top-level key.
|
|
7306
|
+
*/
|
|
7307
|
+
function warnUnprovenStackPrograms(key, providers) {
|
|
7308
|
+
const first = providers[0];
|
|
7309
|
+
warnOnce(`session.programs.${key} uses stack '${first.stackKey}''s program: stacks `
|
|
7310
|
+
+ `${providers.map(({ stackKey }) => `'${stackKey}'`).join(' and ')} bundle '${key}' `
|
|
7311
|
+
+ `programs with the same program spec that could not be proven identical (${providers
|
|
7312
|
+
.map(({ stackKey, program }) => `${stackKey}: ${identityOf(program)}`)
|
|
7313
|
+
.join('; ')}). Each stays at session.stacks.<stack>.programs.${key}.`);
|
|
7314
|
+
}
|
|
7315
|
+
/** Two session stacks provide different program SDKs under one key. */
|
|
7316
|
+
function ambiguousSessionProgram(key, providers) {
|
|
7317
|
+
const stacks = providers.map(({ stackKey }) => `'${stackKey}'`).join(' and ');
|
|
7318
|
+
const paths = providers
|
|
7319
|
+
.map(({ stackKey }) => `session.stacks.${stackKey}.programs.${key}`)
|
|
7320
|
+
.join(' or ');
|
|
7321
|
+
const identities = providers
|
|
7322
|
+
.map(({ stackKey, program }) => `${stackKey}: ${identityOf(program)}`)
|
|
7323
|
+
.join('; ');
|
|
7324
|
+
return new AreteError(`session.programs.${key} is ambiguous: stacks ${stacks} provide different '${key}' `
|
|
7325
|
+
+ `program SDKs (${identities}). Use ${paths}, or attach the one you want under a `
|
|
7326
|
+
+ `different key in createSession({ programs }).`, PROGRAM_KEY_CONFLICT, { key, stacks: providers.map(({ stackKey }) => stackKey) });
|
|
7327
|
+
}
|
|
7328
|
+
|
|
6986
7329
|
function effectiveProgramRead(stack, name, override) {
|
|
6987
7330
|
return override
|
|
6988
7331
|
?? stack.programReads?.[name]
|
|
@@ -7090,13 +7433,38 @@ function validateLocalProgramReadEndpoints(stack, overrides, connectHttpUrl) {
|
|
|
7090
7433
|
function hasProgramAccountReads(definition) {
|
|
7091
7434
|
return Object.keys(definition.accounts ?? {}).length > 0;
|
|
7092
7435
|
}
|
|
7436
|
+
/**
|
|
7437
|
+
* Attach programs next to a stack's own, matching a key the stack already
|
|
7438
|
+
* provides by program identity (see `compareProgramIdentity`):
|
|
7439
|
+
*
|
|
7440
|
+
* - the same program SDK (same object, or equal `packageReleaseHash`) resolves
|
|
7441
|
+
* to the stack's definition;
|
|
7442
|
+
* - the same `programSpecHash` without a provable identity match (at least one
|
|
7443
|
+
* side has no `packageReleaseHash`) uses the attached program, with one
|
|
7444
|
+
* warning;
|
|
7445
|
+
* - anything else throws `PROGRAM_KEY_CONFLICT`.
|
|
7446
|
+
*/
|
|
7093
7447
|
function mergeAttachedPrograms(stack, attachedPrograms) {
|
|
7094
7448
|
const merged = { ...(attachedPrograms ?? {}) };
|
|
7095
7449
|
for (const [name, definition] of Object.entries(stack.programs ?? {})) {
|
|
7096
|
-
|
|
7097
|
-
|
|
7450
|
+
const attached = attachedPrograms && Object.prototype.hasOwnProperty.call(attachedPrograms, name)
|
|
7451
|
+
? attachedPrograms[name]
|
|
7452
|
+
: undefined;
|
|
7453
|
+
if (attached === undefined) {
|
|
7454
|
+
merged[name] = definition;
|
|
7455
|
+
continue;
|
|
7456
|
+
}
|
|
7457
|
+
switch (compareProgramIdentity(definition, attached)) {
|
|
7458
|
+
case 'same':
|
|
7459
|
+
merged[name] = definition;
|
|
7460
|
+
break;
|
|
7461
|
+
case 'unproven':
|
|
7462
|
+
warnUnprovenAttachedProgram(stack.name, name, definition, attached);
|
|
7463
|
+
merged[name] = attached;
|
|
7464
|
+
break;
|
|
7465
|
+
case 'different':
|
|
7466
|
+
throw attachedProgramKeyConflict(stack.name, name, definition, attached);
|
|
7098
7467
|
}
|
|
7099
|
-
merged[name] = definition;
|
|
7100
7468
|
}
|
|
7101
7469
|
return merged;
|
|
7102
7470
|
}
|
|
@@ -7696,6 +8064,34 @@ function resolveSessionProgramReads(stack, member, options) {
|
|
|
7696
8064
|
}));
|
|
7697
8065
|
return Object.keys(reads).length > 0 ? reads : undefined;
|
|
7698
8066
|
}
|
|
8067
|
+
/**
|
|
8068
|
+
* The program read descriptor a session member connected for `stack` reads
|
|
8069
|
+
* `programKey` through, resolved the way `Arete.connect` resolves it: session
|
|
8070
|
+
* and member overrides, then the stack's `programReads`, then the descriptor
|
|
8071
|
+
* the program carries.
|
|
8072
|
+
*/
|
|
8073
|
+
function memberProgramRead(stack, programKey, member, options) {
|
|
8074
|
+
return resolveSessionProgramReads(stack, member, options)?.[programKey]
|
|
8075
|
+
?? stack.programReads?.[programKey]
|
|
8076
|
+
?? getProgramReadDescriptor(stack.programs?.[programKey]);
|
|
8077
|
+
}
|
|
8078
|
+
/** Structural equality of two program read descriptors (plain JSON data). */
|
|
8079
|
+
function sameProgramRead(left, right) {
|
|
8080
|
+
if (left === right)
|
|
8081
|
+
return true;
|
|
8082
|
+
if (left === null || right === null
|
|
8083
|
+
|| typeof left !== 'object' || typeof right !== 'object'
|
|
8084
|
+
|| Array.isArray(left) !== Array.isArray(right)) {
|
|
8085
|
+
return false;
|
|
8086
|
+
}
|
|
8087
|
+
const leftRecord = left;
|
|
8088
|
+
const rightRecord = right;
|
|
8089
|
+
const leftKeys = Object.keys(leftRecord).filter((key) => leftRecord[key] !== undefined);
|
|
8090
|
+
const rightKeys = Object.keys(rightRecord).filter((key) => rightRecord[key] !== undefined);
|
|
8091
|
+
return leftKeys.length === rightKeys.length
|
|
8092
|
+
&& leftKeys.every((key) => Object.prototype.hasOwnProperty.call(rightRecord, key)
|
|
8093
|
+
&& sameProgramRead(leftRecord[key], rightRecord[key]));
|
|
8094
|
+
}
|
|
7699
8095
|
function resolveMemberConnectOptions(stack, member, options, forceHttpOnly) {
|
|
7700
8096
|
const transport = member?.transport ?? options?.transport ?? (forceHttpOnly ? 'http' : 'ws');
|
|
7701
8097
|
// Stack endpoints remain on the stack definition. Only explicit session/member
|
|
@@ -7789,11 +8185,72 @@ async function createSession(definition, ...args) {
|
|
|
7789
8185
|
validateCompositionProgramReads(programAsStack(key, program, definition.programReads?.[key]), options?.programs?.[key], options);
|
|
7790
8186
|
}
|
|
7791
8187
|
}
|
|
8188
|
+
// Program identity, resolved before anything connects. Every stack's
|
|
8189
|
+
// effective programs (its own plus member-attached ones, matched by
|
|
8190
|
+
// `withPrograms`) are the providers for each key.
|
|
8191
|
+
const effectiveStacks = new Map(stackEntries.map(([key, stack]) => {
|
|
8192
|
+
const memberOptions = options?.stacks?.[key];
|
|
8193
|
+
return [key, withPrograms(stack, memberOptions?.programs)];
|
|
8194
|
+
}));
|
|
8195
|
+
const stackProviders = new Map();
|
|
8196
|
+
for (const [stackKey, stack] of effectiveStacks) {
|
|
8197
|
+
for (const [programKey, program] of Object.entries(stack.programs ?? {})) {
|
|
8198
|
+
const providers = stackProviders.get(programKey) ?? [];
|
|
8199
|
+
providers.push({ stackKey, program });
|
|
8200
|
+
stackProviders.set(programKey, providers);
|
|
8201
|
+
}
|
|
8202
|
+
}
|
|
8203
|
+
// Standalone programs a stack already provides as the same program SDK, read
|
|
8204
|
+
// through the same program read configuration, are served by that stack's
|
|
8205
|
+
// connected instance instead of a second member. One with the same program
|
|
8206
|
+
// spec but no provable identity match, or the same program SDK with its own
|
|
8207
|
+
// `programReads` descriptor that differs from the stack's, gets its own
|
|
8208
|
+
// member and takes session.programs.<key>; the stacks keep theirs.
|
|
8209
|
+
const sharedStandalonePrograms = new Set();
|
|
8210
|
+
if (definition.mode !== 'composition') {
|
|
8211
|
+
for (const [programKey, program] of programEntries) {
|
|
8212
|
+
const providers = stackProviders.get(programKey) ?? [];
|
|
8213
|
+
if (providers.length === 0)
|
|
8214
|
+
continue;
|
|
8215
|
+
const matches = providers.map((provider) => ({
|
|
8216
|
+
...provider,
|
|
8217
|
+
match: compareProgramIdentity(provider.program, program),
|
|
8218
|
+
}));
|
|
8219
|
+
const conflicting = matches.find(({ match }) => match === 'different');
|
|
8220
|
+
if (conflicting) {
|
|
8221
|
+
throw sessionProgramKeyConflict(conflicting.stackKey, programKey, conflicting.program, program);
|
|
8222
|
+
}
|
|
8223
|
+
if (matches.some(({ match }) => match === 'unproven')) {
|
|
8224
|
+
warnUnprovenSessionProgram(programKey, program, providers);
|
|
8225
|
+
continue;
|
|
8226
|
+
}
|
|
8227
|
+
const explicitRead = definition.programReads?.[programKey];
|
|
8228
|
+
if (explicitRead !== undefined) {
|
|
8229
|
+
// Sharing would read through the serving stack's configuration, so an
|
|
8230
|
+
// explicit descriptor must resolve to the same one.
|
|
8231
|
+
const servingStack = providers[0].stackKey;
|
|
8232
|
+
const standaloneRead = memberProgramRead(programAsStack(programKey, program, explicitRead), programKey, options?.programs?.[programKey], options);
|
|
8233
|
+
const stackRead = memberProgramRead(effectiveStacks.get(servingStack), programKey, options?.stacks?.[servingStack], options);
|
|
8234
|
+
if (!sameProgramRead(standaloneRead, stackRead)) {
|
|
8235
|
+
warnUnprovenSessionProgram(programKey, program, providers, 'program-read');
|
|
8236
|
+
continue;
|
|
8237
|
+
}
|
|
8238
|
+
}
|
|
8239
|
+
if (options?.programs?.[programKey] !== undefined) {
|
|
8240
|
+
throw new AreteError(`Standalone program '${programKey}' is the same program SDK that stack `
|
|
8241
|
+
+ `'${providers[0].stackKey}' provides, so the session serves it from the stack's `
|
|
8242
|
+
+ `connected instance; configure it through options.stacks.${providers[0].stackKey} `
|
|
8243
|
+
+ `instead of options.programs.${programKey}`, 'INVALID_CONFIG');
|
|
8244
|
+
}
|
|
8245
|
+
sharedStandalonePrograms.add(programKey);
|
|
8246
|
+
}
|
|
8247
|
+
}
|
|
8248
|
+
const memberProgramEntries = programEntries.filter(([key]) => !sharedStandalonePrograms.has(key));
|
|
7792
8249
|
let wallet = options?.wallet;
|
|
7793
8250
|
const signerRegistry = options?.signerRegistry ?? createSignerRegistry();
|
|
7794
8251
|
const connectedStacks = await Promise.all(stackEntries.map(async ([key, stack]) => {
|
|
7795
8252
|
const memberOptions = options?.stacks?.[key];
|
|
7796
|
-
const effectiveStack =
|
|
8253
|
+
const effectiveStack = effectiveStacks.get(key);
|
|
7797
8254
|
const connectOptions = resolveMemberConnectOptions(effectiveStack, memberOptions, {
|
|
7798
8255
|
...options,
|
|
7799
8256
|
chain: sharedChain,
|
|
@@ -7806,7 +8263,7 @@ async function createSession(definition, ...args) {
|
|
|
7806
8263
|
});
|
|
7807
8264
|
return [key, client];
|
|
7808
8265
|
}));
|
|
7809
|
-
const connectedPrograms = await Promise.all(
|
|
8266
|
+
const connectedPrograms = await Promise.all(memberProgramEntries.map(async ([key, program]) => {
|
|
7810
8267
|
const syntheticStack = programAsStack(key, program, definition.programReads?.[key]);
|
|
7811
8268
|
const connectOptions = resolveMemberConnectOptions(syntheticStack, options?.programs?.[key], {
|
|
7812
8269
|
...options,
|
|
@@ -7823,30 +8280,38 @@ async function createSession(definition, ...args) {
|
|
|
7823
8280
|
const executionHost = memberClients[0];
|
|
7824
8281
|
const transactions = sharedTransactions ?? executionHost.transactions;
|
|
7825
8282
|
const stacks = Object.fromEntries(connectedStacks);
|
|
7826
|
-
const explicitProgramKeys = new Set(connectedPrograms.map(([key]) => key));
|
|
7827
|
-
const programOwners = new Map();
|
|
7828
8283
|
const connectedProgramEntries = connectedPrograms.map(([key, client]) => [
|
|
7829
8284
|
key,
|
|
7830
8285
|
client.programs[key],
|
|
7831
8286
|
]);
|
|
7832
8287
|
const promotedPrograms = Object.fromEntries(connectedProgramEntries);
|
|
7833
8288
|
if (definition.mode !== 'composition') {
|
|
7834
|
-
|
|
7835
|
-
|
|
7836
|
-
|
|
7837
|
-
|
|
7838
|
-
|
|
7839
|
-
|
|
7840
|
-
|
|
7841
|
-
|
|
7842
|
-
|
|
7843
|
-
|
|
7844
|
-
|
|
7845
|
-
|
|
7846
|
-
|
|
7847
|
-
|
|
7848
|
-
programOwners.set(programKey, { stack: stackKey, programId: program.programId });
|
|
8289
|
+
const connectedStackClients = new Map(connectedStacks);
|
|
8290
|
+
for (const [programKey, providers] of stackProviders) {
|
|
8291
|
+
if (Object.prototype.hasOwnProperty.call(promotedPrograms, programKey))
|
|
8292
|
+
continue;
|
|
8293
|
+
const first = providers[0];
|
|
8294
|
+
const matches = providers.flatMap((left, index) => providers.slice(index + 1).map((right) => compareProgramIdentity(left.program, right.program)));
|
|
8295
|
+
if (!matches.includes('different')) {
|
|
8296
|
+
// One program, however many stacks bundle it: the first stack's
|
|
8297
|
+
// connected instance serves the top-level key. Copies with the same
|
|
8298
|
+
// program spec that cannot be proven identical say so once.
|
|
8299
|
+
if (matches.includes('unproven'))
|
|
8300
|
+
warnUnprovenStackPrograms(programKey, providers);
|
|
8301
|
+
promotedPrograms[programKey] = connectedStackClients.get(first.stackKey).programs[programKey];
|
|
8302
|
+
continue;
|
|
7849
8303
|
}
|
|
8304
|
+
// Different program SDKs under one key: every stack keeps its own at
|
|
8305
|
+
// session.stacks.<stack>.programs.<key>, and the ambiguous top-level key
|
|
8306
|
+
// explains how to choose instead of silently picking one.
|
|
8307
|
+
const error = ambiguousSessionProgram(programKey, providers);
|
|
8308
|
+
Object.defineProperty(promotedPrograms, programKey, {
|
|
8309
|
+
get() {
|
|
8310
|
+
throw error;
|
|
8311
|
+
},
|
|
8312
|
+
enumerable: false,
|
|
8313
|
+
configurable: true,
|
|
8314
|
+
});
|
|
7850
8315
|
}
|
|
7851
8316
|
}
|
|
7852
8317
|
const programs = promotedPrograms;
|
|
@@ -8082,5 +8547,5 @@ function stringifyBigints(value) {
|
|
|
8082
8547
|
|
|
8083
8548
|
const Arete = Object.assign(Arete$1, { session: createSession });
|
|
8084
8549
|
|
|
8085
|
-
export { ASSOCIATED_TOKEN_PROGRAM_ADDRESS, Arete, AreteError, ConnectionManager, DEFAULT_CONFIG, DEFAULT_MAX_ENTRIES_PER_VIEW, FrameProcessor, InstructionError, MemoryAdapter, OPERATION_KIND, OperationCallbackError, OperationExecutionError, PROGRAM_OPERATION_EXTENSIONS, ProcessedSlotTimeoutError, QueryStore, ReadRequestError, SPL_TOKEN_PROGRAM_ADDRESS, STACK_RUNTIME_EXTENSIONS, SYSTEM_PROGRAM_ADDRESS, StreamGapError, SubscriptionRegistry, TOKEN_2022_PROGRAM_ADDRESS, TRANSACTION_RESOURCE_OPTION_KEYS, TransactionExecutionError, TransactionOptionsError, TransactionTransportError, account, appendFlowTransactions, appendTransactionInstructions, applyConnectedStackExtensions, arg, buildInstruction, bytes, canonicalQueryKey, chainAccountLoader, createChainClient, createEntityStream, createHostedSolanaGatewayTransports, createInstructionExecutor, createInstructionHandler, createPreparedFlow, createPreparedInstruction, createPreparedTransaction, createPreparedTransactionBody, createProgramPdas, createPublicKeySeed, createRichUpdateStream, createSeed, createSession, createSignerRegistry, createSubscriptionId, createTransactionTransport, createTypedListView, createTypedStateView, createTypedViews, createUpdateStream, decodeBase58, defineProgramExtensions, defineStackExtensions, deriveAssociatedTokenAccount, deriveHttpEndpoint, findProgramAddress as derivePda, describePreparedOperation, encodeBase58, executeInstruction, executePreparedOperation, extendProgram, extendPrograms, extendStack, findProgramAddress, findProgramAddressSync, flowOperation, formatPreparedOperation, formatProgramError, formatRawToUi, getMintDecimals, getOperationKind, getProgramRuntimeExtensions, getStackRuntimeExtensions, getTransactionFailureOutcome, hostedWebsocketSuffixes, inspectPreparedOperation, instructionOperation, isEntityFrame, isErrorFrame, isHostedAreteEndpoint, isReplayErrorCode, isSnapshotFrame, isStackVersionRefusalCode, isSubscribedFrame, isUnsubscribedFrame, isValidFrame, literal, normalizeSubscription, normalizeSubscriptionQuery, normalizeSubscriptionRequest, normalizeTransactionError, parseFrame, parseFrameFromBlob, parseInstructionError, parseUiAmountToRaw, pda, prependFlowTransactionInstructions, prependTransactionInstructions, programAccountRead, programQuery, resolveAccounts, resolveAmount, resolveAmountToRaw, resolveAmountsToRaw, resolveTokenProgramAddress, resolveTransactionBuildOptions, safeToRawAmount, serializeInstructionData, serializeViewKey, setHostedWebsocketSuffixes, stackQuery, stringifyBigints, toJsonValue, toRawAmount, toWireResourceOptions, transactionOperation, unwrapOperationExecutionError, validateAccountResolution, validateProgramReadDescriptor, validateSubscriptionId, withProgramRead, withPrograms };
|
|
8550
|
+
export { ASSOCIATED_TOKEN_PROGRAM_ADDRESS, Arete, AreteError, ConnectionManager, DEFAULT_CONFIG, DEFAULT_MAX_ENTRIES_PER_VIEW, EXTENSION_API_VERSION, FrameProcessor, InstructionError, MemoryAdapter, OPERATION_KIND, OperationCallbackError, OperationExecutionError, PROGRAM_KEY_CONFLICT, PROGRAM_OPERATION_EXTENSIONS, ProcessedSlotTimeoutError, QueryStore, ReadRequestError, SPL_TOKEN_PROGRAM_ADDRESS, STACK_RUNTIME_EXTENSIONS, SYSTEM_PROGRAM_ADDRESS, StreamGapError, SubscriptionRegistry, TOKEN_2022_PROGRAM_ADDRESS, TRANSACTION_RESOURCE_OPTION_KEYS, TransactionExecutionError, TransactionOptionsError, TransactionTransportError, account, appendFlowTransactions, appendTransactionInstructions, applyConnectedStackExtensions, arg, buildInstruction, bytes, canonicalQueryKey, chainAccountLoader, compareProgramIdentity, createChainClient, createEntityStream, createHostedSolanaGatewayTransports, createInstructionExecutor, createInstructionHandler, createPreparedFlow, createPreparedInstruction, createPreparedTransaction, createPreparedTransactionBody, createProgramPdas, createPublicKeySeed, createRichUpdateStream, createSeed, createSession, createSignerRegistry, createSubscriptionId, createTransactionTransport, createTypedListView, createTypedStateView, createTypedViews, createUpdateStream, decodeBase58, defineProgramExtensions, defineStackExtensions, deriveAssociatedTokenAccount, deriveHttpEndpoint, findProgramAddress as derivePda, describePreparedOperation, encodeBase58, executeInstruction, executePreparedOperation, extendProgram, extendPrograms, extendStack, findProgramAddress, findProgramAddressSync, flowOperation, formatPreparedOperation, formatProgramError, formatRawToUi, getMintDecimals, getOperationKind, getProgramRuntimeExtensions, getStackRuntimeExtensions, getTransactionFailureOutcome, hostedWebsocketSuffixes, inspectPreparedOperation, instructionOperation, isEntityFrame, isErrorFrame, isHostedAreteEndpoint, isReplayErrorCode, isSameProgramSdk, isSnapshotFrame, isStackVersionRefusalCode, isSubscribedFrame, isUnsubscribedFrame, isValidFrame, literal, normalizeSubscription, normalizeSubscriptionQuery, normalizeSubscriptionRequest, normalizeTransactionError, parseFrame, parseFrameFromBlob, parseInstructionError, parseUiAmountToRaw, pda, prependFlowTransactionInstructions, prependTransactionInstructions, programAccountRead, programQuery, resolveAccounts, resolveAmount, resolveAmountToRaw, resolveAmountsToRaw, resolveTokenProgramAddress, resolveTransactionBuildOptions, safeToRawAmount, serializeInstructionData, serializeViewKey, setHostedWebsocketSuffixes, stackQuery, stringifyBigints, toJsonValue, toRawAmount, toWireResourceOptions, transactionOperation, unwrapOperationExecutionError, validateAccountResolution, validateProgramReadDescriptor, validateSubscriptionId, withProgramIdentity, withProgramRead, withPrograms };
|
|
8086
8551
|
//# sourceMappingURL=index.esm.js.map
|