@xnetjs/core 0.11.1 → 0.12.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/dist/index.d.ts +91 -1
- package/dist/index.js +69 -0
- package/package.json +2 -1
package/dist/index.d.ts
CHANGED
|
@@ -864,6 +864,96 @@ declare function validateExternalUrl(rawUrl: string): {
|
|
|
864
864
|
error?: string;
|
|
865
865
|
};
|
|
866
866
|
|
|
867
|
+
/**
|
|
868
|
+
* Retry/backoff policies (exploration 0300).
|
|
869
|
+
*
|
|
870
|
+
* One dependency-free vocabulary for every reconnect/retry loop in the repo,
|
|
871
|
+
* replacing the hand-rolled backoff math that had drifted into three parallel
|
|
872
|
+
* implementations (sync connection-manager, WebSocketSyncProvider, webhook
|
|
873
|
+
* emitter). Deliberately shaped like Effect's `Schedule` combinators so a
|
|
874
|
+
* later migration would be mechanical — but scope-guarded: if this module
|
|
875
|
+
* needs union/intersect/cron/hedging, stop and re-read exploration 0300.
|
|
876
|
+
*/
|
|
877
|
+
/**
|
|
878
|
+
* A retry policy maps a 1-based attempt number to the delay (in ms) to wait
|
|
879
|
+
* before that attempt, or `null` to give up retrying.
|
|
880
|
+
*/
|
|
881
|
+
interface RetryPolicy {
|
|
882
|
+
/** Delay in ms before the given 1-based attempt, or `null` to give up. */
|
|
883
|
+
delayFor(attempt: number): number | null;
|
|
884
|
+
}
|
|
885
|
+
/** The same delay before every attempt. */
|
|
886
|
+
declare function fixed(delayMs: number): RetryPolicy;
|
|
887
|
+
/**
|
|
888
|
+
* Exponential backoff: `baseMs * factor^(attempt - 1)`.
|
|
889
|
+
* Attempt 1 waits `baseMs`, attempt 2 waits `baseMs * factor`, and so on.
|
|
890
|
+
*/
|
|
891
|
+
declare function exponential(baseMs: number, factor?: number): RetryPolicy;
|
|
892
|
+
/** Cap another policy's delay at `maxDelayMs`. Passes `null` (give up) through. */
|
|
893
|
+
declare function capped(policy: RetryPolicy, maxDelayMs: number): RetryPolicy;
|
|
894
|
+
/**
|
|
895
|
+
* Add random jitter on top of another policy's delay: `delay + floor(random()
|
|
896
|
+
* * delay * ratio)`, i.e. up to `ratio` extra. Matches the hub rate-limit
|
|
897
|
+
* backoff behavior (exploration 0206) at the default `ratio` of 0.5. Passes
|
|
898
|
+
* `null` (give up) through. `random` is injectable for deterministic tests.
|
|
899
|
+
*/
|
|
900
|
+
declare function jittered(policy: RetryPolicy, ratio?: number, random?: () => number): RetryPolicy;
|
|
901
|
+
/** Give up (return `null`) once `attempt` exceeds `maxAttempts`. */
|
|
902
|
+
declare function limitAttempts(policy: RetryPolicy, maxAttempts: number): RetryPolicy;
|
|
903
|
+
|
|
904
|
+
/**
|
|
905
|
+
* Tagged errors (exploration 0300).
|
|
906
|
+
*
|
|
907
|
+
* The repo-wide convention for structured errors, formalizing what
|
|
908
|
+
* `NodeRelayError.code` and friends already did by hand: every structured
|
|
909
|
+
* error carries a string-literal `_tag` discriminant so catch sites can
|
|
910
|
+
* narrow exhaustively instead of sniffing `instanceof` chains or message
|
|
911
|
+
* strings. Deliberately shaped like Effect's `Data.TaggedError` so a later
|
|
912
|
+
* migration would be mechanical.
|
|
913
|
+
*
|
|
914
|
+
* Convention (see CLAUDE.md): new structured errors extend `TaggedError`,
|
|
915
|
+
* set `_tag` to the class name, and put machine-readable context in readonly
|
|
916
|
+
* fields (a `code` union where one error class spans several failure kinds).
|
|
917
|
+
* Existing error classes migrate on touch, not as a campaign.
|
|
918
|
+
*/
|
|
919
|
+
declare abstract class TaggedError<Tag extends string = string> extends Error {
|
|
920
|
+
/** String-literal discriminant — by convention the class name. */
|
|
921
|
+
abstract readonly _tag: Tag;
|
|
922
|
+
constructor(message: string, options?: ErrorOptions);
|
|
923
|
+
}
|
|
924
|
+
/** Narrow an unknown catch value to a specific tagged error. */
|
|
925
|
+
declare function isTagged<Tag extends string>(error: unknown, tag: Tag): error is TaggedError<Tag>;
|
|
926
|
+
|
|
927
|
+
/**
|
|
928
|
+
* Single-flight promise memoization (exploration 0300).
|
|
929
|
+
*
|
|
930
|
+
* The one implementation of the "share the in-flight promise so concurrent
|
|
931
|
+
* callers don't convoy the backend" pattern that had been independently
|
|
932
|
+
* hand-rolled at several call sites (schema registry lazy loads, sqlite
|
|
933
|
+
* adapter EXPLAIN diagnostics — explorations 0271/0276).
|
|
934
|
+
*
|
|
935
|
+
* The caller owns the map, so lifetime policy (instance field vs module
|
|
936
|
+
* scope), size caps, and explicit invalidation stay at the call site.
|
|
937
|
+
*/
|
|
938
|
+
interface SingleFlightOptions {
|
|
939
|
+
/**
|
|
940
|
+
* What happens to the map entry after the promise settles:
|
|
941
|
+
*
|
|
942
|
+
* - `'settled'` (default): the entry is removed once the promise settles —
|
|
943
|
+
* the map only ever holds in-flight work (dedupe, not a cache).
|
|
944
|
+
* - `'keep'`: the entry stays after success, memoizing the result until the
|
|
945
|
+
* caller evicts it. Rejections are always removed, so a failure never
|
|
946
|
+
* poisons the key and the next caller retries.
|
|
947
|
+
*/
|
|
948
|
+
retain?: 'settled' | 'keep';
|
|
949
|
+
}
|
|
950
|
+
/**
|
|
951
|
+
* Return the in-flight promise for `key` if one exists; otherwise start
|
|
952
|
+
* `fn()`, store its promise in `map`, and return it. Entry lifetime after
|
|
953
|
+
* settling is controlled by {@link SingleFlightOptions.retain}.
|
|
954
|
+
*/
|
|
955
|
+
declare function singleFlight<K, V>(map: Map<K, Promise<V>>, key: K, fn: () => Promise<V>, options?: SingleFlightOptions): Promise<V>;
|
|
956
|
+
|
|
867
957
|
/**
|
|
868
958
|
* The ONE Last-Write-Wins ordering for xNet (docs/specs/protocol §L1.7,
|
|
869
959
|
* exploration 0276).
|
|
@@ -926,4 +1016,4 @@ declare function lwwUpdateGuardSql(input: {
|
|
|
926
1016
|
type DID = `did:key:${string}`;
|
|
927
1017
|
type DocumentPath = `xnet://${DID}/workspace/${string}/doc/${string}`;
|
|
928
1018
|
|
|
929
|
-
export { ALL_CAPABILITIES, AUTH_ACTIONS, type ActionKey, type AllowExpr, type AndExpr, type AuthAction, type AuthCheckInput, type AuthDecision, type AuthDenyReason, type AuthExpression, type AuthTrace, type AuthTraceStep, type AuthenticatedExpr, type AuthorizationDefinition, BOOTSTRAP_PEERS, type Capability, type ChainStatus, type Condition, type ContentChunk, type ContentId, type ContentResolver, type ContentTree, type CreatorRoleResolver, DEFAULT_SNAPSHOT_TRIGGERS, DEFAULT_STREAMING_OPTIONS, DHT_CONFIG, type DID, type DIDResolution, type DIDResolver, type DataSource, type DenyExpr, type DocumentLoad, type DocumentPath, type Fork, type Group, type IPCondition, type LwwStamp, type MembershipRoleResolver, type MerkleNode, type NotExpr, type OrExpr, type PeerLocation, type PermissionEvaluator, type PermissionGrant, type PolicyEvaluator, type PropertyRoleResolver, type PublicExpr, type Query, type QueryPlan, type QueryRequest, type QueryResponse, type QueryRouter, RESOLUTION_CACHE_CONFIG, type RelationRoleResolver, type ResolutionStrategy, type ResourceScope, type Role, type RoleKey, type RoleRefExpr, type RoleResolver, STANDARD_ROLES, type SchemaAction, type SerializedAuthExpression, type SerializedAuthorization, type SerializedRoleResolver, type SignedUpdate, type Snapshot, type SnapshotTriggers, SsrfError, type StreamingQueryOptions, type SubQuery, type TimeCondition, type UpdateVerifier, type VectorClock, assertPublicUrl, buildMerkleTree, clamp, clamp01, compareChangeApplicationOrder, compareLwwStamps, compareVectorClocks, createChunk, createContentId, deduplicatedUnion, detectFork, estimateQueryCost, evaluateCondition, formatBytes, getMostPermissiveCapability, hashContent, incrementVectorClock, isLocationFresh, isValidDID, isValidProgression, lwwUpdateGuardSql, lwwWins, mergeStateVectors, mergeVectorClocks, parseContentId, parseDID, roleHasCapability, shouldCreateSnapshot, unionAggregate, validateExternalUrl, verifyContent, verifyUpdateChain };
|
|
1019
|
+
export { ALL_CAPABILITIES, AUTH_ACTIONS, type ActionKey, type AllowExpr, type AndExpr, type AuthAction, type AuthCheckInput, type AuthDecision, type AuthDenyReason, type AuthExpression, type AuthTrace, type AuthTraceStep, type AuthenticatedExpr, type AuthorizationDefinition, BOOTSTRAP_PEERS, type Capability, type ChainStatus, type Condition, type ContentChunk, type ContentId, type ContentResolver, type ContentTree, type CreatorRoleResolver, DEFAULT_SNAPSHOT_TRIGGERS, DEFAULT_STREAMING_OPTIONS, DHT_CONFIG, type DID, type DIDResolution, type DIDResolver, type DataSource, type DenyExpr, type DocumentLoad, type DocumentPath, type Fork, type Group, type IPCondition, type LwwStamp, type MembershipRoleResolver, type MerkleNode, type NotExpr, type OrExpr, type PeerLocation, type PermissionEvaluator, type PermissionGrant, type PolicyEvaluator, type PropertyRoleResolver, type PublicExpr, type Query, type QueryPlan, type QueryRequest, type QueryResponse, type QueryRouter, RESOLUTION_CACHE_CONFIG, type RelationRoleResolver, type ResolutionStrategy, type ResourceScope, type RetryPolicy, type Role, type RoleKey, type RoleRefExpr, type RoleResolver, STANDARD_ROLES, type SchemaAction, type SerializedAuthExpression, type SerializedAuthorization, type SerializedRoleResolver, type SignedUpdate, type SingleFlightOptions, type Snapshot, type SnapshotTriggers, SsrfError, type StreamingQueryOptions, type SubQuery, TaggedError, type TimeCondition, type UpdateVerifier, type VectorClock, assertPublicUrl, buildMerkleTree, capped, clamp, clamp01, compareChangeApplicationOrder, compareLwwStamps, compareVectorClocks, createChunk, createContentId, deduplicatedUnion, detectFork, estimateQueryCost, evaluateCondition, exponential, fixed, formatBytes, getMostPermissiveCapability, hashContent, incrementVectorClock, isLocationFresh, isTagged, isValidDID, isValidProgression, jittered, limitAttempts, lwwUpdateGuardSql, lwwWins, mergeStateVectors, mergeVectorClocks, parseContentId, parseDID, roleHasCapability, shouldCreateSnapshot, singleFlight, unionAggregate, validateExternalUrl, verifyContent, verifyUpdateChain };
|
package/dist/index.js
CHANGED
|
@@ -408,6 +408,67 @@ function validateExternalUrl(rawUrl) {
|
|
|
408
408
|
}
|
|
409
409
|
}
|
|
410
410
|
|
|
411
|
+
// src/retry/policy.ts
|
|
412
|
+
function fixed(delayMs) {
|
|
413
|
+
return {
|
|
414
|
+
delayFor: () => delayMs
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
function exponential(baseMs, factor = 2) {
|
|
418
|
+
return {
|
|
419
|
+
delayFor: (attempt) => baseMs * factor ** (attempt - 1)
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
function capped(policy, maxDelayMs) {
|
|
423
|
+
return {
|
|
424
|
+
delayFor: (attempt) => {
|
|
425
|
+
const delay = policy.delayFor(attempt);
|
|
426
|
+
return delay === null ? null : Math.min(delay, maxDelayMs);
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
function jittered(policy, ratio = 0.5, random = Math.random) {
|
|
431
|
+
return {
|
|
432
|
+
delayFor: (attempt) => {
|
|
433
|
+
const delay = policy.delayFor(attempt);
|
|
434
|
+
return delay === null ? null : delay + Math.floor(random() * delay * ratio);
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
function limitAttempts(policy, maxAttempts) {
|
|
439
|
+
return {
|
|
440
|
+
delayFor: (attempt) => attempt > maxAttempts ? null : policy.delayFor(attempt)
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// src/errors/tagged.ts
|
|
445
|
+
var TaggedError = class extends Error {
|
|
446
|
+
constructor(message, options) {
|
|
447
|
+
super(message, options);
|
|
448
|
+
this.name = new.target.name;
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
function isTagged(error, tag) {
|
|
452
|
+
return error instanceof TaggedError && error._tag === tag;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// src/async/single-flight.ts
|
|
456
|
+
function singleFlight(map, key, fn, options = {}) {
|
|
457
|
+
const existing = map.get(key);
|
|
458
|
+
if (existing) return existing;
|
|
459
|
+
const promise = fn();
|
|
460
|
+
map.set(key, promise);
|
|
461
|
+
const evict = () => {
|
|
462
|
+
if (map.get(key) === promise) map.delete(key);
|
|
463
|
+
};
|
|
464
|
+
if ((options.retain ?? "settled") === "settled") {
|
|
465
|
+
promise.then(evict, evict);
|
|
466
|
+
} else {
|
|
467
|
+
promise.then(void 0, evict);
|
|
468
|
+
}
|
|
469
|
+
return promise;
|
|
470
|
+
}
|
|
471
|
+
|
|
411
472
|
// src/lww.ts
|
|
412
473
|
function compareLwwStamps(a, b) {
|
|
413
474
|
if (a.lamport !== b.lamport) return a.lamport - b.lamport;
|
|
@@ -439,8 +500,10 @@ export {
|
|
|
439
500
|
RESOLUTION_CACHE_CONFIG,
|
|
440
501
|
STANDARD_ROLES,
|
|
441
502
|
SsrfError,
|
|
503
|
+
TaggedError,
|
|
442
504
|
assertPublicUrl,
|
|
443
505
|
buildMerkleTree,
|
|
506
|
+
capped,
|
|
444
507
|
clamp,
|
|
445
508
|
clamp01,
|
|
446
509
|
compareChangeApplicationOrder,
|
|
@@ -452,13 +515,18 @@ export {
|
|
|
452
515
|
detectFork,
|
|
453
516
|
estimateQueryCost,
|
|
454
517
|
evaluateCondition,
|
|
518
|
+
exponential,
|
|
519
|
+
fixed,
|
|
455
520
|
formatBytes,
|
|
456
521
|
getMostPermissiveCapability,
|
|
457
522
|
hashContent,
|
|
458
523
|
incrementVectorClock,
|
|
459
524
|
isLocationFresh,
|
|
525
|
+
isTagged,
|
|
460
526
|
isValidDID,
|
|
461
527
|
isValidProgression,
|
|
528
|
+
jittered,
|
|
529
|
+
limitAttempts,
|
|
462
530
|
lwwUpdateGuardSql,
|
|
463
531
|
lwwWins,
|
|
464
532
|
mergeStateVectors,
|
|
@@ -467,6 +535,7 @@ export {
|
|
|
467
535
|
parseDID,
|
|
468
536
|
roleHasCapability,
|
|
469
537
|
shouldCreateSnapshot,
|
|
538
|
+
singleFlight,
|
|
470
539
|
unionAggregate,
|
|
471
540
|
validateExternalUrl,
|
|
472
541
|
verifyContent,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xnetjs/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"provenance": true
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
+
"fast-check": "^4.8.0",
|
|
28
29
|
"tsup": "^8.0.0",
|
|
29
30
|
"typescript": "^5.4.0"
|
|
30
31
|
},
|