@usearete/sdk 0.25.0 → 0.25.1
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 +3 -0
- package/dist/index.cjs +124 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +76 -20
- package/dist/index.esm.js +123 -9
- package/dist/index.esm.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -382,8 +382,11 @@ for await (const update of session.stacks.myStack.views.settlementGame.list.watc
|
|
|
382
382
|
}
|
|
383
383
|
|
|
384
384
|
const game = await session.stacks.myStack.views.settlementGame.state.get('game-123');
|
|
385
|
+
const latest = await session.stacks.ore.views.OreRound.latest.getOne();
|
|
385
386
|
```
|
|
386
387
|
|
|
388
|
+
`get` and `getOne` open (or reuse) an equivalent subscription, wait for its initial snapshot, and release it. They reject with `InitialDataTimeoutError` after `timeoutMs` (5000 by default; `null` waits forever). `getSync` only reads a subscription that is already active and returns `undefined` when there is none.
|
|
389
|
+
|
|
387
390
|
Every options object is a protocol v2 query with independent ordered membership. Different windows and filters on the same view can run concurrently, while equivalent normalized queries share one reference-counted wire subscription:
|
|
388
391
|
|
|
389
392
|
```ts
|
package/dist/index.cjs
CHANGED
|
@@ -444,6 +444,7 @@ class SubscriptionRegistry {
|
|
|
444
444
|
this.queryStore = queryStore;
|
|
445
445
|
this.subscriptions = new Map();
|
|
446
446
|
this.subscriptionsById = new Map();
|
|
447
|
+
this.failureListeners = new Set();
|
|
447
448
|
}
|
|
448
449
|
subscribe(query, snapshotEnabled = true) {
|
|
449
450
|
const request = normalizeSubscriptionRequest({
|
|
@@ -506,12 +507,30 @@ class SubscriptionRegistry {
|
|
|
506
507
|
? this.queryStore.getSnapshot(tracker.subscription.subscriptionId)
|
|
507
508
|
: undefined;
|
|
508
509
|
}
|
|
509
|
-
handleConnectionState(state) {
|
|
510
|
+
handleConnectionState(state, message) {
|
|
510
511
|
if (state === 'reconnecting')
|
|
511
512
|
this.queryStore.beginReconnect();
|
|
512
|
-
if (state
|
|
513
|
-
this.
|
|
513
|
+
if (state !== 'error') {
|
|
514
|
+
this.connectionError = undefined;
|
|
515
|
+
return;
|
|
514
516
|
}
|
|
517
|
+
this.queryStore.failRefreshing(new AreteError('Connection failed while refreshing subscriptions', 'CONNECTION_ERROR'));
|
|
518
|
+
// Queries still waiting for their first snapshot are not failed above;
|
|
519
|
+
// one-shot reads learn about the failure here instead of timing out.
|
|
520
|
+
this.connectionError = new AreteError(message ?? 'Connection failed', 'CONNECTION_ERROR');
|
|
521
|
+
this.notifyFailure(this.connectionError);
|
|
522
|
+
}
|
|
523
|
+
/** The terminal connection failure while the connection is in the `error` state. */
|
|
524
|
+
getConnectionError() {
|
|
525
|
+
return this.connectionError;
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Called when pending queries can no longer resolve: the connection failed
|
|
529
|
+
* terminally, or a disconnect cleared every subscription.
|
|
530
|
+
*/
|
|
531
|
+
onFailure(callback) {
|
|
532
|
+
this.failureListeners.add(callback);
|
|
533
|
+
return () => this.failureListeners.delete(callback);
|
|
515
534
|
}
|
|
516
535
|
clear() {
|
|
517
536
|
for (const { subscription } of this.subscriptions.values()) {
|
|
@@ -527,6 +546,11 @@ class SubscriptionRegistry {
|
|
|
527
546
|
}
|
|
528
547
|
this.subscriptions.clear();
|
|
529
548
|
this.subscriptionsById.clear();
|
|
549
|
+
this.notifyFailure(new AreteError('Subscriptions were cleared by a disconnect', 'CONNECTION_CANCELLED'));
|
|
550
|
+
}
|
|
551
|
+
notifyFailure(error) {
|
|
552
|
+
for (const listener of [...this.failureListeners])
|
|
553
|
+
listener(error);
|
|
530
554
|
}
|
|
531
555
|
createLease(tracker) {
|
|
532
556
|
let released = false;
|
|
@@ -3565,10 +3589,90 @@ function createRichUpdateStream(_storage, subscriptionRegistry, query, keyFilter
|
|
|
3565
3589
|
};
|
|
3566
3590
|
}
|
|
3567
3591
|
|
|
3592
|
+
/** How long `get` and `getOne` wait for an initial snapshot, as in the Rust and Python SDKs. */
|
|
3593
|
+
const DEFAULT_INITIAL_DATA_TIMEOUT_MS = 5000;
|
|
3594
|
+
/**
|
|
3595
|
+
* `get` or `getOne` gave up waiting for the initial snapshot: the socket never
|
|
3596
|
+
* delivered it (no connection, a reconnect storm, a server that never
|
|
3597
|
+
* answers). The read's subscription is released before this is thrown.
|
|
3598
|
+
*/
|
|
3599
|
+
class InitialDataTimeoutError extends AreteError {
|
|
3600
|
+
constructor(view, timeoutMs) {
|
|
3601
|
+
super(`Timed out after ${timeoutMs}ms waiting for the initial snapshot of view '${view}'`, 'INITIAL_DATA_TIMEOUT', { view, timeoutMs });
|
|
3602
|
+
this.view = view;
|
|
3603
|
+
this.timeoutMs = timeoutMs;
|
|
3604
|
+
this.name = 'InitialDataTimeoutError';
|
|
3605
|
+
}
|
|
3606
|
+
}
|
|
3568
3607
|
function queryOptions(options) {
|
|
3569
|
-
const { schema: _schema, withSnapshot, ...query } = options ?? {};
|
|
3608
|
+
const { schema: _schema, withSnapshot, timeoutMs: _timeoutMs, ...query } = options ?? {};
|
|
3570
3609
|
return { query, snapshotEnabled: withSnapshot ?? true };
|
|
3571
3610
|
}
|
|
3611
|
+
function resolveTimeout(options) {
|
|
3612
|
+
const timeoutMs = options?.timeoutMs === undefined
|
|
3613
|
+
? DEFAULT_INITIAL_DATA_TIMEOUT_MS
|
|
3614
|
+
: options.timeoutMs;
|
|
3615
|
+
if (timeoutMs !== null && (!Number.isFinite(timeoutMs) || timeoutMs <= 0)) {
|
|
3616
|
+
throw new RangeError('timeoutMs must be a positive finite number or null');
|
|
3617
|
+
}
|
|
3618
|
+
return timeoutMs;
|
|
3619
|
+
}
|
|
3620
|
+
/**
|
|
3621
|
+
* Open (or reuse) the equivalent subscription, wait until its snapshot has
|
|
3622
|
+
* resolved or its query has failed, and release it. A terminal connection
|
|
3623
|
+
* failure or a disconnect rejects with that reason. Bounded by `timeoutMs`
|
|
3624
|
+
* (`null` waits forever) so a socket that never delivers cannot hang the caller.
|
|
3625
|
+
*/
|
|
3626
|
+
async function readResolved(registry, query, snapshotEnabled, timeoutMs) {
|
|
3627
|
+
const lease = registry.subscribe(query, snapshotEnabled);
|
|
3628
|
+
try {
|
|
3629
|
+
await new Promise((resolve, reject) => {
|
|
3630
|
+
let settled = false;
|
|
3631
|
+
let stop = () => { };
|
|
3632
|
+
let timer;
|
|
3633
|
+
const finish = (error) => {
|
|
3634
|
+
if (settled)
|
|
3635
|
+
return;
|
|
3636
|
+
settled = true;
|
|
3637
|
+
stop();
|
|
3638
|
+
if (timer !== undefined)
|
|
3639
|
+
clearTimeout(timer);
|
|
3640
|
+
if (error)
|
|
3641
|
+
reject(error);
|
|
3642
|
+
else
|
|
3643
|
+
resolve();
|
|
3644
|
+
};
|
|
3645
|
+
const check = () => {
|
|
3646
|
+
const error = lease.getError();
|
|
3647
|
+
if (error)
|
|
3648
|
+
finish(error);
|
|
3649
|
+
else if (!lease.getSnapshot().isLoading)
|
|
3650
|
+
finish();
|
|
3651
|
+
else {
|
|
3652
|
+
const connectionError = registry.getConnectionError();
|
|
3653
|
+
if (connectionError)
|
|
3654
|
+
finish(connectionError);
|
|
3655
|
+
}
|
|
3656
|
+
};
|
|
3657
|
+
check();
|
|
3658
|
+
if (settled)
|
|
3659
|
+
return;
|
|
3660
|
+
const stopChanges = lease.onChange(check);
|
|
3661
|
+
const stopFailures = registry.onFailure(finish);
|
|
3662
|
+
stop = () => {
|
|
3663
|
+
stopChanges();
|
|
3664
|
+
stopFailures();
|
|
3665
|
+
};
|
|
3666
|
+
if (timeoutMs !== null) {
|
|
3667
|
+
timer = setTimeout(() => finish(new InitialDataTimeoutError(query.view, timeoutMs)), timeoutMs);
|
|
3668
|
+
}
|
|
3669
|
+
});
|
|
3670
|
+
return lease.getSnapshot();
|
|
3671
|
+
}
|
|
3672
|
+
finally {
|
|
3673
|
+
lease.release();
|
|
3674
|
+
}
|
|
3675
|
+
}
|
|
3572
3676
|
function serializeViewKeyValue(value, view, field) {
|
|
3573
3677
|
const location = field === undefined ? `view '${view}'` : `key field '${field}' for view '${view}'`;
|
|
3574
3678
|
if (typeof value === 'string')
|
|
@@ -3619,7 +3723,10 @@ function createTypedStateView(viewDef, storage, subscriptionRegistry) {
|
|
|
3619
3723
|
return createRichUpdateStream(storage, subscriptionRegistry, { view: viewDef.view, key: serializedKey, ...query }, serializedKey, snapshotEnabled);
|
|
3620
3724
|
},
|
|
3621
3725
|
async get(key, options) {
|
|
3622
|
-
|
|
3726
|
+
const timeoutMs = resolveTimeout(options);
|
|
3727
|
+
const { query, snapshotEnabled } = queryOptions(options);
|
|
3728
|
+
const snapshot = await readResolved(subscriptionRegistry, { view: viewDef.view, key: wireKey(key), ...query }, snapshotEnabled, timeoutMs);
|
|
3729
|
+
return snapshot.data[0] ?? null;
|
|
3623
3730
|
},
|
|
3624
3731
|
getSync(key, options) {
|
|
3625
3732
|
const { query, snapshotEnabled } = queryOptions(options);
|
|
@@ -3647,7 +3754,14 @@ function createTypedListView(viewDef, storage, subscriptionRegistry) {
|
|
|
3647
3754
|
return createRichUpdateStream(storage, subscriptionRegistry, { view: viewDef.view, ...query }, undefined, snapshotEnabled);
|
|
3648
3755
|
},
|
|
3649
3756
|
async get(options) {
|
|
3650
|
-
|
|
3757
|
+
const timeoutMs = resolveTimeout(options);
|
|
3758
|
+
const { query, snapshotEnabled } = queryOptions(options);
|
|
3759
|
+
const snapshot = await readResolved(subscriptionRegistry, { view: viewDef.view, ...query }, snapshotEnabled, timeoutMs);
|
|
3760
|
+
return [...snapshot.data];
|
|
3761
|
+
},
|
|
3762
|
+
async getOne(options) {
|
|
3763
|
+
const items = await this.get({ ...options, take: 1 });
|
|
3764
|
+
return items[0] ?? null;
|
|
3651
3765
|
},
|
|
3652
3766
|
getSync(options) {
|
|
3653
3767
|
const { query, snapshotEnabled } = queryOptions(options);
|
|
@@ -7565,8 +7679,8 @@ let Arete$1 = class Arete {
|
|
|
7565
7679
|
this.connection.onFrame((frame) => {
|
|
7566
7680
|
this.processor.handleFrame(frame);
|
|
7567
7681
|
});
|
|
7568
|
-
this.connection.onStateChange((state) => {
|
|
7569
|
-
this.subscriptionRegistry.handleConnectionState(state);
|
|
7682
|
+
this.connection.onStateChange((state, error) => {
|
|
7683
|
+
this.subscriptionRegistry.handleConnectionState(state, error);
|
|
7570
7684
|
});
|
|
7571
7685
|
this._views = createTypedViews(this.stack, this.storage, this.subscriptionRegistry);
|
|
7572
7686
|
this._queries = this.buildQueries();
|
|
@@ -8554,9 +8668,11 @@ exports.Arete = Arete;
|
|
|
8554
8668
|
exports.AreteError = AreteError;
|
|
8555
8669
|
exports.ConnectionManager = ConnectionManager;
|
|
8556
8670
|
exports.DEFAULT_CONFIG = DEFAULT_CONFIG;
|
|
8671
|
+
exports.DEFAULT_INITIAL_DATA_TIMEOUT_MS = DEFAULT_INITIAL_DATA_TIMEOUT_MS;
|
|
8557
8672
|
exports.DEFAULT_MAX_ENTRIES_PER_VIEW = DEFAULT_MAX_ENTRIES_PER_VIEW;
|
|
8558
8673
|
exports.EXTENSION_API_VERSION = EXTENSION_API_VERSION;
|
|
8559
8674
|
exports.FrameProcessor = FrameProcessor;
|
|
8675
|
+
exports.InitialDataTimeoutError = InitialDataTimeoutError;
|
|
8560
8676
|
exports.InstructionError = InstructionError;
|
|
8561
8677
|
exports.MemoryAdapter = MemoryAdapter;
|
|
8562
8678
|
exports.OPERATION_KIND = OPERATION_KIND;
|