@xnetjs/core 0.11.1 → 1.0.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 CHANGED
@@ -385,9 +385,13 @@ interface Role {
385
385
  capabilities: Capability[];
386
386
  }
387
387
  /**
388
- * Available capabilities
388
+ * Available capabilities.
389
+ *
390
+ * `create`/`update` are refinements of `write` (exploration 0304): a role
391
+ * holding `write` implicitly covers both; the granular capabilities cover
392
+ * only themselves.
389
393
  */
390
- type Capability = 'read' | 'write' | 'delete' | 'share' | 'admin';
394
+ type Capability = 'read' | 'create' | 'update' | 'write' | 'delete' | 'share' | 'admin';
391
395
  /**
392
396
  * All capabilities in order of privilege
393
397
  */
@@ -451,7 +455,8 @@ interface PermissionEvaluator {
451
455
  */
452
456
  declare const STANDARD_ROLES: Record<string, Role>;
453
457
  /**
454
- * Check if a capability is included in a role
458
+ * Check if a capability is included in a role.
459
+ * A role holding `write` covers the `create`/`update` refinements.
455
460
  */
456
461
  declare function roleHasCapability(role: Role, capability: Capability): boolean;
457
462
  /**
@@ -483,12 +488,46 @@ type SchemaIRI = `xnet://${string}/${string}`;
483
488
  /**
484
489
  * Canonical authorization actions.
485
490
  * All action checks map to one of these values.
491
+ *
492
+ * `create` and `update` are refinements of the coarse `write` action
493
+ * (exploration 0304): `create` governs bringing a node into existence,
494
+ * `update` governs mutating a node that already exists. A schema MAY declare
495
+ * either refinement; when one is absent its expression falls back to the
496
+ * schema's `write` expression (see {@link actionExpressionOrder}), so schemas
497
+ * that only declare `write` behave exactly as before. Checking `write` on an
498
+ * existing node is equivalent to checking `update`.
499
+ *
500
+ * `create` checks evaluate roles against the *draft* node built from the
501
+ * create payload (`createdBy` = the caller). Because `role.creator()` then
502
+ * always matches, a schema that wants real admission control must exclude the
503
+ * creator role from `create` and require a container role instead — the
504
+ * draft's relation properties (e.g. `space`, `channel`) resolve normally.
486
505
  */
487
- declare const AUTH_ACTIONS: readonly ["read", "write", "delete", "share", "admin"];
506
+ declare const AUTH_ACTIONS: readonly ["read", "create", "update", "write", "delete", "share", "admin"];
488
507
  /**
489
508
  * An authorization action that can be checked or granted.
490
509
  */
491
510
  type AuthAction = (typeof AUTH_ACTIONS)[number];
511
+ /**
512
+ * Expression lookup order for a checked action — the first action name whose
513
+ * expression a schema defines wins (exploration 0304 fallback table):
514
+ *
515
+ * | checked | expression used |
516
+ * |--------------------|-------------------------------------|
517
+ * | `create` | `actions.create` ?? `actions.write` |
518
+ * | `update` / `write` | `actions.update` ?? `actions.write` |
519
+ * | anything else | `actions[action]` |
520
+ */
521
+ declare function actionExpressionOrder(action: AuthAction): readonly AuthAction[];
522
+ /**
523
+ * Whether a granted action satisfies a checked action.
524
+ *
525
+ * A `write` grant covers its refinements (`create`, `update`); a granular
526
+ * grant covers only itself. A granular grant does NOT satisfy a legacy
527
+ * `write` check (fail closed — old callers checking `write` on an existing
528
+ * node mean update, which `update` covers).
529
+ */
530
+ declare function grantActionSatisfies(granted: AuthAction, checked: AuthAction): boolean;
492
531
  /**
493
532
  * The result of an authorization check.
494
533
  * Contains whether access is allowed plus diagnostic info for debugging.
@@ -554,7 +593,11 @@ interface AuthTraceStep {
554
593
  * },
555
594
  * actions: {
556
595
  * read: allow('editor', 'admin', 'owner'),
596
+ * // coarse mutation policy; also the fallback for create/update
557
597
  * write: allow('editor', 'admin', 'owner'),
598
+ * // optional refinements (0304): who may add vs. who may modify
599
+ * create: allow('editor', 'admin'),
600
+ * update: allow('owner', 'admin'),
558
601
  * delete: allow('admin', 'owner'),
559
602
  * share: allow('admin', 'owner')
560
603
  * },
@@ -865,26 +908,117 @@ declare function validateExternalUrl(rawUrl: string): {
865
908
  };
866
909
 
867
910
  /**
868
- * The ONE Last-Write-Wins ordering for xNet (docs/specs/protocol §L1.7,
869
- * exploration 0276).
911
+ * Retry/backoff policies (exploration 0303).
912
+ *
913
+ * One dependency-free vocabulary for every reconnect/retry loop in the repo,
914
+ * replacing the hand-rolled backoff math that had drifted into three parallel
915
+ * implementations (sync connection-manager, WebSocketSyncProvider, webhook
916
+ * emitter). Deliberately shaped like Effect's `Schedule` combinators so a
917
+ * later migration would be mechanical — but scope-guarded: if this module
918
+ * needs union/intersect/cron/hedging, stop and re-read exploration 0303.
919
+ */
920
+ /**
921
+ * A retry policy maps a 1-based attempt number to the delay (in ms) to wait
922
+ * before that attempt, or `null` to give up retrying.
923
+ */
924
+ interface RetryPolicy {
925
+ /** Delay in ms before the given 1-based attempt, or `null` to give up. */
926
+ delayFor(attempt: number): number | null;
927
+ }
928
+ /** The same delay before every attempt. */
929
+ declare function fixed(delayMs: number): RetryPolicy;
930
+ /**
931
+ * Exponential backoff: `baseMs * factor^(attempt - 1)`.
932
+ * Attempt 1 waits `baseMs`, attempt 2 waits `baseMs * factor`, and so on.
933
+ */
934
+ declare function exponential(baseMs: number, factor?: number): RetryPolicy;
935
+ /** Cap another policy's delay at `maxDelayMs`. Passes `null` (give up) through. */
936
+ declare function capped(policy: RetryPolicy, maxDelayMs: number): RetryPolicy;
937
+ /**
938
+ * Add random jitter on top of another policy's delay: `delay + floor(random()
939
+ * * delay * ratio)`, i.e. up to `ratio` extra. Matches the hub rate-limit
940
+ * backoff behavior (exploration 0206) at the default `ratio` of 0.5. Passes
941
+ * `null` (give up) through. `random` is injectable for deterministic tests.
942
+ */
943
+ declare function jittered(policy: RetryPolicy, ratio?: number, random?: () => number): RetryPolicy;
944
+ /** Give up (return `null`) once `attempt` exceeds `maxAttempts`. */
945
+ declare function limitAttempts(policy: RetryPolicy, maxAttempts: number): RetryPolicy;
946
+
947
+ /**
948
+ * Tagged errors (exploration 0303).
949
+ *
950
+ * The repo-wide convention for structured errors, formalizing what
951
+ * `NodeRelayError.code` and friends already did by hand: every structured
952
+ * error carries a string-literal `_tag` discriminant so catch sites can
953
+ * narrow exhaustively instead of sniffing `instanceof` chains or message
954
+ * strings. Deliberately shaped like Effect's `Data.TaggedError` so a later
955
+ * migration would be mechanical.
956
+ *
957
+ * Convention (see CLAUDE.md): new structured errors extend `TaggedError`,
958
+ * set `_tag` to the class name, and put machine-readable context in readonly
959
+ * fields (a `code` union where one error class spans several failure kinds).
960
+ * Existing error classes migrate on touch, not as a campaign.
961
+ */
962
+ declare abstract class TaggedError<Tag extends string = string> extends Error {
963
+ /** String-literal discriminant — by convention the class name. */
964
+ abstract readonly _tag: Tag;
965
+ constructor(message: string, options?: ErrorOptions);
966
+ }
967
+ /** Narrow an unknown catch value to a specific tagged error. */
968
+ declare function isTagged<Tag extends string>(error: unknown, tag: Tag): error is TaggedError<Tag>;
969
+
970
+ /**
971
+ * Single-flight promise memoization (exploration 0303).
972
+ *
973
+ * The one implementation of the "share the in-flight promise so concurrent
974
+ * callers don't convoy the backend" pattern that had been independently
975
+ * hand-rolled at several call sites (schema registry lazy loads, sqlite
976
+ * adapter EXPLAIN diagnostics — explorations 0271/0276).
870
977
  *
871
- * Per-property conflict resolution and change-log application ordering were
872
- * previously re-implemented in three places (`NodeStore.applyChange`, the
873
- * SQLite adapter's ON CONFLICT guards, and the hub storages' change
874
- * ordering) — a drift class on the protocol's core convergence invariant.
875
- * Every implementation now derives from this module, and the golden-vector
876
- * conformance suite (exploration 0200) pins them equal.
978
+ * The caller owns the map, so lifetime policy (instance field vs module
979
+ * scope), size caps, and explicit invalidation stay at the call site.
980
+ */
981
+ interface SingleFlightOptions {
982
+ /**
983
+ * What happens to the map entry after the promise settles:
984
+ *
985
+ * - `'settled'` (default): the entry is removed once the promise settles —
986
+ * the map only ever holds in-flight work (dedupe, not a cache).
987
+ * - `'keep'`: the entry stays after success, memoizing the result until the
988
+ * caller evicts it. Rejections are always removed, so a failure never
989
+ * poisons the key and the next caller retries.
990
+ */
991
+ retain?: 'settled' | 'keep';
992
+ }
993
+ /**
994
+ * Return the in-flight promise for `key` if one exists; otherwise start
995
+ * `fn()`, store its promise in `map`, and return it. Entry lifetime after
996
+ * settling is controlled by {@link SingleFlightOptions.retain}.
997
+ */
998
+ declare function singleFlight<K, V>(map: Map<K, Promise<V>>, key: K, fn: () => Promise<V>, options?: SingleFlightOptions): Promise<V>;
999
+
1000
+ /** Protocol version at which the grinding-resistant tiebreak key activates. */
1001
+ declare const LWW_TIEBREAK_KEY_VERSION = 4;
1002
+ /**
1003
+ * The grinding-resistant LWW final tiebreak key (exploration 0305):
1004
+ * `blake3_hex( author ‖ US ‖ propertyKey ‖ US ‖ canonicalJSON(value) )`.
877
1005
  *
878
- * Ordering: lamport time, then wall time, then author DID compared by UTF-16
879
- * code units. NEVER `localeCompare` locale collation is non-deterministic
880
- * across ICU versions and would break CRDT convergence (see the
881
- * `0004-tie-author-case-codeunit` golden vector).
1006
+ * Portable by construction a kernel in any language reproduces it from
1007
+ * canonical JSON of the value (which every kernel already computes for the
1008
+ * change hash). A deletion (`value === undefined`) canonicalises as `null`.
882
1009
  */
883
- /** The timestamp triple every LWW comparison runs on. */
1010
+ declare function computeLwwTiebreakKey(author: string, propertyKey: string, value: unknown): string;
1011
+ /** The timestamp every LWW comparison runs on. */
884
1012
  interface LwwStamp {
885
1013
  lamport: number;
886
1014
  wallTime: number;
887
1015
  author: string;
1016
+ /**
1017
+ * Grinding-resistant tiebreak key ({@link computeLwwTiebreakKey}), present
1018
+ * only for changes at protocol version ≥ {@link LWW_TIEBREAK_KEY_VERSION}.
1019
+ * Absent (legacy) stamps fall back to the author-DID tiebreak.
1020
+ */
1021
+ tiebreakKey?: string;
888
1022
  }
889
1023
  /**
890
1024
  * Spec comparator (§L1.7): negative when `a` loses to `b`, positive when `a`
@@ -910,13 +1044,22 @@ declare function compareChangeApplicationOrder(a: {
910
1044
  * SQL `ON CONFLICT … DO UPDATE … WHERE` guard implementing {@link lwwWins}
911
1045
  * inside SQLite (the `excluded.` pseudo-table is the incoming row). Column
912
1046
  * text comparison (`>`) is byte order under SQLite's default BINARY
913
- * collation, which matches the code-unit rule for our ASCII DID strings.
1047
+ * collation, which matches the code-unit rule for our ASCII DID/hex strings.
1048
+ *
1049
+ * When `tiebreakKeyColumn` is supplied, the final rung mirrors
1050
+ * {@link compareLwwStamps}: on a `lamport`+`wallTime` tie, if BOTH rows carry a
1051
+ * non-null tiebreak key the larger key wins outright (author irrelevant);
1052
+ * otherwise (either key null, or keys equal) the author DID decides. The stored
1053
+ * key is precomputed in application code ({@link computeLwwTiebreakKey}), so SQL
1054
+ * only ever compares the opaque hex — never recomputes it — keeping the JS and
1055
+ * SQL paths byte-identical without a user-defined function.
914
1056
  */
915
1057
  declare function lwwUpdateGuardSql(input: {
916
1058
  table: string;
917
1059
  lamportColumn: string;
918
1060
  wallTimeColumn: string;
919
1061
  authorColumn: string;
1062
+ tiebreakKeyColumn?: string;
920
1063
  }): string;
921
1064
 
922
1065
  /**
@@ -926,4 +1069,4 @@ declare function lwwUpdateGuardSql(input: {
926
1069
  type DID = `did:key:${string}`;
927
1070
  type DocumentPath = `xnet://${DID}/workspace/${string}/doc/${string}`;
928
1071
 
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 };
1072
+ 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, LWW_TIEBREAK_KEY_VERSION, 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, actionExpressionOrder, assertPublicUrl, buildMerkleTree, capped, clamp, clamp01, compareChangeApplicationOrder, compareLwwStamps, compareVectorClocks, computeLwwTiebreakKey, createChunk, createContentId, deduplicatedUnion, detectFork, estimateQueryCost, evaluateCondition, exponential, fixed, formatBytes, getMostPermissiveCapability, grantActionSatisfies, 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
@@ -267,8 +267,38 @@ function deduplicatedUnion(results) {
267
267
  return output;
268
268
  }
269
269
 
270
+ // src/auth-types.ts
271
+ var AUTH_ACTIONS = [
272
+ "read",
273
+ "create",
274
+ "update",
275
+ "write",
276
+ "delete",
277
+ "share",
278
+ "admin"
279
+ ];
280
+ function actionExpressionOrder(action) {
281
+ if (action === "create") return ["create", "write"];
282
+ if (action === "update" || action === "write") return ["update", "write"];
283
+ return [action];
284
+ }
285
+ function grantActionSatisfies(granted, checked) {
286
+ if (granted === checked) return true;
287
+ if (granted === "write") return checked === "create" || checked === "update";
288
+ if (granted === "update") return checked === "write";
289
+ return false;
290
+ }
291
+
270
292
  // src/permissions.ts
271
- var ALL_CAPABILITIES = ["read", "write", "delete", "share", "admin"];
293
+ var ALL_CAPABILITIES = [
294
+ "read",
295
+ "create",
296
+ "update",
297
+ "write",
298
+ "delete",
299
+ "share",
300
+ "admin"
301
+ ];
272
302
  var STANDARD_ROLES = {
273
303
  viewer: {
274
304
  id: "viewer",
@@ -284,7 +314,7 @@ var STANDARD_ROLES = {
284
314
  }
285
315
  };
286
316
  function roleHasCapability(role, capability) {
287
- return role.capabilities.includes(capability);
317
+ return role.capabilities.some((held) => grantActionSatisfies(held, capability));
288
318
  }
289
319
  function evaluateCondition(condition, context) {
290
320
  switch (condition.type) {
@@ -306,9 +336,6 @@ function getMostPermissiveCapability(capabilities) {
306
336
  return null;
307
337
  }
308
338
 
309
- // src/auth-types.ts
310
- var AUTH_ACTIONS = ["read", "write", "delete", "share", "admin"];
311
-
312
339
  // src/utils/math.ts
313
340
  function clamp(value, min, max) {
314
341
  if (value < min) return min;
@@ -408,10 +435,98 @@ function validateExternalUrl(rawUrl) {
408
435
  }
409
436
  }
410
437
 
438
+ // src/retry/policy.ts
439
+ function fixed(delayMs) {
440
+ return {
441
+ delayFor: () => delayMs
442
+ };
443
+ }
444
+ function exponential(baseMs, factor = 2) {
445
+ return {
446
+ delayFor: (attempt) => baseMs * factor ** (attempt - 1)
447
+ };
448
+ }
449
+ function capped(policy, maxDelayMs) {
450
+ return {
451
+ delayFor: (attempt) => {
452
+ const delay = policy.delayFor(attempt);
453
+ return delay === null ? null : Math.min(delay, maxDelayMs);
454
+ }
455
+ };
456
+ }
457
+ function jittered(policy, ratio = 0.5, random = Math.random) {
458
+ return {
459
+ delayFor: (attempt) => {
460
+ const delay = policy.delayFor(attempt);
461
+ return delay === null ? null : delay + Math.floor(random() * delay * ratio);
462
+ }
463
+ };
464
+ }
465
+ function limitAttempts(policy, maxAttempts) {
466
+ return {
467
+ delayFor: (attempt) => attempt > maxAttempts ? null : policy.delayFor(attempt)
468
+ };
469
+ }
470
+
471
+ // src/errors/tagged.ts
472
+ var TaggedError = class extends Error {
473
+ constructor(message, options) {
474
+ super(message, options);
475
+ this.name = new.target.name;
476
+ }
477
+ };
478
+ function isTagged(error, tag) {
479
+ return error instanceof TaggedError && error._tag === tag;
480
+ }
481
+
482
+ // src/async/single-flight.ts
483
+ function singleFlight(map, key, fn, options = {}) {
484
+ const existing = map.get(key);
485
+ if (existing) return existing;
486
+ const promise = fn();
487
+ map.set(key, promise);
488
+ const evict = () => {
489
+ if (map.get(key) === promise) map.delete(key);
490
+ };
491
+ if ((options.retain ?? "settled") === "settled") {
492
+ promise.then(evict, evict);
493
+ } else {
494
+ promise.then(void 0, evict);
495
+ }
496
+ return promise;
497
+ }
498
+
411
499
  // src/lww.ts
500
+ import { blake3 as blake32 } from "@noble/hashes/blake3.js";
501
+ var LWW_TIEBREAK_KEY_VERSION = 4;
502
+ var US = "";
503
+ function canonicalize(value) {
504
+ if (value === null || typeof value !== "object") return value;
505
+ if (Array.isArray(value)) return value.map(canonicalize);
506
+ const out = {};
507
+ for (const key of Object.keys(value).sort()) {
508
+ out[key] = canonicalize(value[key]);
509
+ }
510
+ return out;
511
+ }
512
+ function toHex(bytes) {
513
+ let s = "";
514
+ for (const b of bytes) s += b.toString(16).padStart(2, "0");
515
+ return s;
516
+ }
517
+ function computeLwwTiebreakKey(author, propertyKey, value) {
518
+ const canonical = JSON.stringify(canonicalize(value === void 0 ? null : value));
519
+ const bytes = new TextEncoder().encode(`${author}${US}${propertyKey}${US}${canonical}`);
520
+ return toHex(blake32(bytes));
521
+ }
412
522
  function compareLwwStamps(a, b) {
413
523
  if (a.lamport !== b.lamport) return a.lamport - b.lamport;
414
524
  if (a.wallTime !== b.wallTime) return a.wallTime - b.wallTime;
525
+ const aK = a.tiebreakKey;
526
+ const bK = b.tiebreakKey;
527
+ if (aK !== void 0 && bK !== void 0 && aK !== bK) {
528
+ return aK < bK ? -1 : 1;
529
+ }
415
530
  return a.author < b.author ? -1 : a.author > b.author ? 1 : 0;
416
531
  }
417
532
  function lwwWins(incoming, existing) {
@@ -422,12 +537,19 @@ function compareChangeApplicationOrder(a, b) {
422
537
  return a.author < b.author ? -1 : a.author > b.author ? 1 : 0;
423
538
  }
424
539
  function lwwUpdateGuardSql(input) {
425
- const { table, lamportColumn, wallTimeColumn, authorColumn } = input;
540
+ const { table, lamportColumn, wallTimeColumn, authorColumn, tiebreakKeyColumn } = input;
541
+ const finalRung = tiebreakKeyColumn ? `(excluded.${tiebreakKeyColumn} IS NOT NULL
542
+ AND ${table}.${tiebreakKeyColumn} IS NOT NULL
543
+ AND excluded.${tiebreakKeyColumn} > ${table}.${tiebreakKeyColumn})
544
+ OR (NOT (excluded.${tiebreakKeyColumn} IS NOT NULL
545
+ AND ${table}.${tiebreakKeyColumn} IS NOT NULL
546
+ AND excluded.${tiebreakKeyColumn} <> ${table}.${tiebreakKeyColumn})
547
+ AND excluded.${authorColumn} > ${table}.${authorColumn})` : `excluded.${authorColumn} > ${table}.${authorColumn}`;
426
548
  return `excluded.${lamportColumn} > ${table}.${lamportColumn}
427
549
  OR (excluded.${lamportColumn} = ${table}.${lamportColumn}
428
550
  AND (excluded.${wallTimeColumn} > ${table}.${wallTimeColumn}
429
551
  OR (excluded.${wallTimeColumn} = ${table}.${wallTimeColumn}
430
- AND excluded.${authorColumn} > ${table}.${authorColumn})))`;
552
+ AND (${finalRung}))))`;
431
553
  }
432
554
  export {
433
555
  ALL_CAPABILITIES,
@@ -436,29 +558,40 @@ export {
436
558
  DEFAULT_SNAPSHOT_TRIGGERS,
437
559
  DEFAULT_STREAMING_OPTIONS,
438
560
  DHT_CONFIG,
561
+ LWW_TIEBREAK_KEY_VERSION,
439
562
  RESOLUTION_CACHE_CONFIG,
440
563
  STANDARD_ROLES,
441
564
  SsrfError,
565
+ TaggedError,
566
+ actionExpressionOrder,
442
567
  assertPublicUrl,
443
568
  buildMerkleTree,
569
+ capped,
444
570
  clamp,
445
571
  clamp01,
446
572
  compareChangeApplicationOrder,
447
573
  compareLwwStamps,
448
574
  compareVectorClocks,
575
+ computeLwwTiebreakKey,
449
576
  createChunk,
450
577
  createContentId,
451
578
  deduplicatedUnion,
452
579
  detectFork,
453
580
  estimateQueryCost,
454
581
  evaluateCondition,
582
+ exponential,
583
+ fixed,
455
584
  formatBytes,
456
585
  getMostPermissiveCapability,
586
+ grantActionSatisfies,
457
587
  hashContent,
458
588
  incrementVectorClock,
459
589
  isLocationFresh,
590
+ isTagged,
460
591
  isValidDID,
461
592
  isValidProgression,
593
+ jittered,
594
+ limitAttempts,
462
595
  lwwUpdateGuardSql,
463
596
  lwwWins,
464
597
  mergeStateVectors,
@@ -467,6 +600,7 @@ export {
467
600
  parseDID,
468
601
  roleHasCapability,
469
602
  shouldCreateSnapshot,
603
+ singleFlight,
470
604
  unionAggregate,
471
605
  validateExternalUrl,
472
606
  verifyContent,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xnetjs/core",
3
- "version": "0.11.1",
3
+ "version": "1.0.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
  },