@xnetjs/core 0.2.0 → 0.3.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
@@ -864,6 +864,61 @@ declare function validateExternalUrl(rawUrl: string): {
864
864
  error?: string;
865
865
  };
866
866
 
867
+ /**
868
+ * The ONE Last-Write-Wins ordering for xNet (docs/specs/protocol §L1.7,
869
+ * exploration 0276).
870
+ *
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.
877
+ *
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).
882
+ */
883
+ /** The timestamp triple every LWW comparison runs on. */
884
+ interface LwwStamp {
885
+ lamport: number;
886
+ wallTime: number;
887
+ author: string;
888
+ }
889
+ /**
890
+ * Spec comparator (§L1.7): negative when `a` loses to `b`, positive when `a`
891
+ * beats `b`, zero only for identical stamps.
892
+ */
893
+ declare function compareLwwStamps(a: LwwStamp, b: LwwStamp): number;
894
+ /** Whether an incoming write replaces the existing one under LWW. */
895
+ declare function lwwWins(incoming: LwwStamp, existing: LwwStamp): boolean;
896
+ /**
897
+ * Deterministic application order for change logs: lamport time, then author
898
+ * by code units. Used when replaying/relaying batches so every peer folds
899
+ * changes in the same sequence (matches the hub's
900
+ * `ORDER BY lamport_time ASC, lamport_author ASC`).
901
+ */
902
+ declare function compareChangeApplicationOrder(a: {
903
+ lamport: number;
904
+ author: string;
905
+ }, b: {
906
+ lamport: number;
907
+ author: string;
908
+ }): number;
909
+ /**
910
+ * SQL `ON CONFLICT … DO UPDATE … WHERE` guard implementing {@link lwwWins}
911
+ * inside SQLite (the `excluded.` pseudo-table is the incoming row). Column
912
+ * text comparison (`>`) is byte order under SQLite's default BINARY
913
+ * collation, which matches the code-unit rule for our ASCII DID strings.
914
+ */
915
+ declare function lwwUpdateGuardSql(input: {
916
+ table: string;
917
+ lamportColumn: string;
918
+ wallTimeColumn: string;
919
+ authorColumn: string;
920
+ }): string;
921
+
867
922
  /**
868
923
  * @xnetjs/core - Core types, schemas, and content addressing
869
924
  */
@@ -871,4 +926,4 @@ declare function validateExternalUrl(rawUrl: string): {
871
926
  type DID = `did:key:${string}`;
872
927
  type DocumentPath = `xnet://${DID}/workspace/${string}/doc/${string}`;
873
928
 
874
- 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 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, compareVectorClocks, createChunk, createContentId, deduplicatedUnion, detectFork, estimateQueryCost, evaluateCondition, formatBytes, getMostPermissiveCapability, hashContent, incrementVectorClock, isLocationFresh, isValidDID, isValidProgression, mergeStateVectors, mergeVectorClocks, parseContentId, parseDID, roleHasCapability, shouldCreateSnapshot, unionAggregate, validateExternalUrl, verifyContent, verifyUpdateChain };
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 };
package/dist/index.js CHANGED
@@ -407,6 +407,28 @@ function validateExternalUrl(rawUrl) {
407
407
  return { valid: false, error: error instanceof Error ? error.message : "Invalid URL" };
408
408
  }
409
409
  }
410
+
411
+ // src/lww.ts
412
+ function compareLwwStamps(a, b) {
413
+ if (a.lamport !== b.lamport) return a.lamport - b.lamport;
414
+ if (a.wallTime !== b.wallTime) return a.wallTime - b.wallTime;
415
+ return a.author < b.author ? -1 : a.author > b.author ? 1 : 0;
416
+ }
417
+ function lwwWins(incoming, existing) {
418
+ return compareLwwStamps(incoming, existing) > 0;
419
+ }
420
+ function compareChangeApplicationOrder(a, b) {
421
+ if (a.lamport !== b.lamport) return a.lamport - b.lamport;
422
+ return a.author < b.author ? -1 : a.author > b.author ? 1 : 0;
423
+ }
424
+ function lwwUpdateGuardSql(input) {
425
+ const { table, lamportColumn, wallTimeColumn, authorColumn } = input;
426
+ return `excluded.${lamportColumn} > ${table}.${lamportColumn}
427
+ OR (excluded.${lamportColumn} = ${table}.${lamportColumn}
428
+ AND (excluded.${wallTimeColumn} > ${table}.${wallTimeColumn}
429
+ OR (excluded.${wallTimeColumn} = ${table}.${wallTimeColumn}
430
+ AND excluded.${authorColumn} > ${table}.${authorColumn})))`;
431
+ }
410
432
  export {
411
433
  ALL_CAPABILITIES,
412
434
  AUTH_ACTIONS,
@@ -421,6 +443,8 @@ export {
421
443
  buildMerkleTree,
422
444
  clamp,
423
445
  clamp01,
446
+ compareChangeApplicationOrder,
447
+ compareLwwStamps,
424
448
  compareVectorClocks,
425
449
  createChunk,
426
450
  createContentId,
@@ -435,6 +459,8 @@ export {
435
459
  isLocationFresh,
436
460
  isValidDID,
437
461
  isValidProgression,
462
+ lwwUpdateGuardSql,
463
+ lwwWins,
438
464
  mergeStateVectors,
439
465
  mergeVectorClocks,
440
466
  parseContentId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xnetjs/core",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",