@evolu/common 6.0.1-preview.2 → 6.0.1-preview.20

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.
Files changed (154) hide show
  1. package/dist/src/Assert.d.ts +6 -3
  2. package/dist/src/Assert.d.ts.map +1 -1
  3. package/dist/src/Assert.js +7 -4
  4. package/dist/src/Brand.d.ts +75 -0
  5. package/dist/src/Brand.d.ts.map +1 -0
  6. package/dist/src/Brand.js +1 -0
  7. package/dist/src/Buffer.d.ts +1 -1
  8. package/dist/src/Buffer.d.ts.map +1 -1
  9. package/dist/src/Buffer.js +1 -1
  10. package/dist/src/CallbackRegistry.d.ts +53 -0
  11. package/dist/src/CallbackRegistry.d.ts.map +1 -0
  12. package/dist/src/CallbackRegistry.js +25 -0
  13. package/dist/src/Console.d.ts +31 -6
  14. package/dist/src/Console.d.ts.map +1 -1
  15. package/dist/src/Console.js +72 -9
  16. package/dist/src/Crypto.d.ts +61 -34
  17. package/dist/src/Crypto.d.ts.map +1 -1
  18. package/dist/src/Crypto.js +29 -42
  19. package/dist/src/Evolu/Db.d.ts +137 -66
  20. package/dist/src/Evolu/Db.d.ts.map +1 -1
  21. package/dist/src/Evolu/Db.js +300 -705
  22. package/dist/src/Evolu/Diff.d.ts +3 -3
  23. package/dist/src/Evolu/Diff.d.ts.map +1 -1
  24. package/dist/src/Evolu/Diff.js +7 -5
  25. package/dist/src/Evolu/Evolu.d.ts +200 -115
  26. package/dist/src/Evolu/Evolu.d.ts.map +1 -1
  27. package/dist/src/Evolu/Evolu.js +287 -157
  28. package/dist/src/Evolu/Internal.d.ts +0 -2
  29. package/dist/src/Evolu/Internal.d.ts.map +1 -1
  30. package/dist/src/Evolu/Internal.js +0 -2
  31. package/dist/src/Evolu/LocalAuth.d.ts +144 -0
  32. package/dist/src/Evolu/LocalAuth.d.ts.map +1 -0
  33. package/dist/src/Evolu/LocalAuth.js +171 -0
  34. package/dist/src/Evolu/Owner.d.ts +152 -127
  35. package/dist/src/Evolu/Owner.d.ts.map +1 -1
  36. package/dist/src/Evolu/Owner.js +88 -107
  37. package/dist/src/Evolu/Platform.d.ts +9 -7
  38. package/dist/src/Evolu/Platform.d.ts.map +1 -1
  39. package/dist/src/Evolu/Protocol.d.ts +170 -186
  40. package/dist/src/Evolu/Protocol.d.ts.map +1 -1
  41. package/dist/src/Evolu/Protocol.js +487 -343
  42. package/dist/src/Evolu/Public.d.ts +6 -8
  43. package/dist/src/Evolu/Public.d.ts.map +1 -1
  44. package/dist/src/Evolu/Public.js +2 -3
  45. package/dist/src/Evolu/PublicKysely.js +3 -3
  46. package/dist/src/Evolu/Query.d.ts +2 -1
  47. package/dist/src/Evolu/Query.d.ts.map +1 -1
  48. package/dist/src/Evolu/Relay.d.ts +4 -3
  49. package/dist/src/Evolu/Relay.d.ts.map +1 -1
  50. package/dist/src/Evolu/Relay.js +50 -12
  51. package/dist/src/Evolu/Schema.d.ts +129 -73
  52. package/dist/src/Evolu/Schema.d.ts.map +1 -1
  53. package/dist/src/Evolu/Schema.js +169 -89
  54. package/dist/src/Evolu/Storage.d.ts +159 -14
  55. package/dist/src/Evolu/Storage.d.ts.map +1 -1
  56. package/dist/src/Evolu/Storage.js +42 -32
  57. package/dist/src/Evolu/Sync.d.ts +77 -13
  58. package/dist/src/Evolu/Sync.d.ts.map +1 -1
  59. package/dist/src/Evolu/Sync.js +453 -20
  60. package/dist/src/Evolu/Timestamp.d.ts +35 -27
  61. package/dist/src/Evolu/Timestamp.d.ts.map +1 -1
  62. package/dist/src/Evolu/Timestamp.js +27 -18
  63. package/dist/src/ManyToManyMap.d.ts +74 -10
  64. package/dist/src/ManyToManyMap.d.ts.map +1 -1
  65. package/dist/src/ManyToManyMap.js +41 -6
  66. package/dist/src/Number.d.ts +2 -1
  67. package/dist/src/Number.d.ts.map +1 -1
  68. package/dist/src/Random.d.ts +3 -2
  69. package/dist/src/Random.d.ts.map +1 -1
  70. package/dist/src/RefCountedResourceManager.d.ts +119 -0
  71. package/dist/src/RefCountedResourceManager.d.ts.map +1 -0
  72. package/dist/src/RefCountedResourceManager.js +197 -0
  73. package/dist/src/Result.d.ts +147 -51
  74. package/dist/src/Result.d.ts.map +1 -1
  75. package/dist/src/Result.js +7 -241
  76. package/dist/src/Sqlite.d.ts +21 -4
  77. package/dist/src/Sqlite.d.ts.map +1 -1
  78. package/dist/src/Sqlite.js +50 -8
  79. package/dist/src/Task.d.ts +511 -0
  80. package/dist/src/Task.d.ts.map +1 -0
  81. package/dist/src/Task.js +410 -0
  82. package/dist/src/Time.d.ts +59 -0
  83. package/dist/src/Time.d.ts.map +1 -1
  84. package/dist/src/Time.js +87 -4
  85. package/dist/src/Type.d.ts +462 -327
  86. package/dist/src/Type.d.ts.map +1 -1
  87. package/dist/src/Type.js +490 -436
  88. package/dist/src/Types.d.ts +0 -74
  89. package/dist/src/Types.d.ts.map +1 -1
  90. package/dist/src/WebSocket.d.ts +5 -2
  91. package/dist/src/WebSocket.d.ts.map +1 -1
  92. package/dist/src/WebSocket.js +12 -13
  93. package/dist/src/Worker.d.ts +39 -11
  94. package/dist/src/Worker.d.ts.map +1 -1
  95. package/dist/src/Worker.js +22 -4
  96. package/dist/src/index.d.ts +3 -3
  97. package/dist/src/index.d.ts.map +1 -1
  98. package/dist/src/index.js +3 -3
  99. package/package.json +14 -13
  100. package/src/Assert.ts +8 -7
  101. package/src/Brand.ts +75 -0
  102. package/src/Buffer.ts +1 -1
  103. package/src/CallbackRegistry.ts +84 -0
  104. package/src/Console.ts +91 -11
  105. package/src/Crypto.ts +94 -79
  106. package/src/Evolu/Db.ts +505 -1027
  107. package/src/Evolu/Diff.ts +7 -5
  108. package/src/Evolu/Evolu.ts +682 -329
  109. package/src/Evolu/Internal.ts +0 -2
  110. package/src/Evolu/LocalAuth.ts +422 -0
  111. package/src/Evolu/Owner.ts +221 -235
  112. package/src/Evolu/Platform.ts +9 -9
  113. package/src/Evolu/Protocol.ts +681 -614
  114. package/src/Evolu/Public.ts +7 -14
  115. package/src/Evolu/PublicKysely.ts +3 -3
  116. package/src/Evolu/Query.ts +2 -1
  117. package/src/Evolu/Relay.ts +62 -16
  118. package/src/Evolu/Schema.ts +391 -191
  119. package/src/Evolu/Storage.ts +275 -55
  120. package/src/Evolu/Sync.ts +758 -37
  121. package/src/Evolu/Timestamp.ts +38 -35
  122. package/src/ManyToManyMap.ts +127 -24
  123. package/src/Number.ts +2 -6
  124. package/src/Random.ts +3 -2
  125. package/src/RefCountedResourceManager.ts +368 -0
  126. package/src/Result.ts +152 -53
  127. package/src/Sqlite.ts +59 -23
  128. package/src/Task.ts +779 -0
  129. package/src/Time.ts +168 -4
  130. package/src/Type.ts +696 -660
  131. package/src/Types.ts +0 -76
  132. package/src/WebSocket.ts +23 -17
  133. package/src/Worker.ts +72 -23
  134. package/src/index.ts +3 -3
  135. package/dist/src/Callbacks.d.ts +0 -20
  136. package/dist/src/Callbacks.d.ts.map +0 -1
  137. package/dist/src/Callbacks.js +0 -18
  138. package/dist/src/Evolu/Config.d.ts +0 -69
  139. package/dist/src/Evolu/Config.d.ts.map +0 -1
  140. package/dist/src/Evolu/Config.js +0 -9
  141. package/dist/src/Evolu/Kysely.d.ts +0 -6
  142. package/dist/src/Evolu/Kysely.d.ts.map +0 -1
  143. package/dist/src/Evolu/Kysely.js +0 -21
  144. package/dist/src/NanoId.d.ts +0 -27
  145. package/dist/src/NanoId.d.ts.map +0 -1
  146. package/dist/src/NanoId.js +0 -6
  147. package/dist/src/Promise.d.ts +0 -180
  148. package/dist/src/Promise.d.ts.map +0 -1
  149. package/dist/src/Promise.js +0 -176
  150. package/src/Callbacks.ts +0 -43
  151. package/src/Evolu/Config.ts +0 -83
  152. package/src/Evolu/Kysely.ts +0 -38
  153. package/src/NanoId.ts +0 -39
  154. package/src/Promise.ts +0 -295
@@ -1,8 +1,9 @@
1
- import { NanoIdLibDep } from "../NanoId.js";
1
+ import { Brand } from "../Brand.js";
2
+ import { RandomBytesDep } from "../Crypto.js";
2
3
  import { Order } from "../Order.js";
3
4
  import { Result } from "../Result.js";
4
5
  import { TimeDep } from "../Time.js";
5
- import { Brand } from "../Types.js";
6
+ import { InferType } from "../Type.js";
6
7
  export interface TimestampConfig {
7
8
  /**
8
9
  * Maximum physical clock drift allowed in ms.
@@ -14,7 +15,7 @@ export interface TimestampConfig {
14
15
  export interface TimestampConfigDep {
15
16
  readonly timestampConfig: TimestampConfig;
16
17
  }
17
- export type TimestampError = TimestampDriftError | TimestampCounterOverflowError | TimestampDuplicateNodeError | TimestampTimeOutOfRangeError;
18
+ export type TimestampError = TimestampDriftError | TimestampCounterOverflowError | TimestampTimeOutOfRangeError;
18
19
  export interface TimestampDriftError {
19
20
  readonly type: "TimestampDriftError";
20
21
  readonly next: Millis;
@@ -23,10 +24,6 @@ export interface TimestampDriftError {
23
24
  export interface TimestampCounterOverflowError {
24
25
  readonly type: "TimestampCounterOverflowError";
25
26
  }
26
- export interface TimestampDuplicateNodeError {
27
- readonly type: "TimestampDuplicateNodeError";
28
- readonly nodeId: NodeId;
29
- }
30
27
  export interface TimestampTimeOutOfRangeError {
31
28
  readonly type: "TimestampTimeOutOfRangeError";
32
29
  }
@@ -42,11 +39,11 @@ export interface TimestampTimeOutOfRangeError {
42
39
  *
43
40
  * `new Date(281474976710654).toString()` = Tue Aug 02 10889 07:31:49
44
41
  */
45
- export declare const Millis: import("../Type.js").BrandType<import("../Type.js").BrandType<import("../Type.js").Type<"Brand", number & Brand<"Int"> & Brand<"NonNegative">, number, import("../Type.js").NonNegativeError, number & Brand<"Int">, import("../Type.js").NumberError | import("../Type.js").IntError>, `LessThanOrEqualTo${number}`, import("../Type.js").LessThanOrEqualToError<number>, import("../Type.js").NumberError | import("../Type.js").NonNegativeError | import("../Type.js").IntError>, "Millis", import("../Type.js").BrandWithoutRefineError<"Millis", import("../Type.js").NumberError | import("../Type.js").NonNegativeError | import("../Type.js").IntError | import("../Type.js").LessThanOrEqualToError<number>>, never>;
42
+ export declare const Millis: import("../Type.js").BrandType<import("../Type.js").BrandType<import("../Type.js").Type<"Brand", number & Brand<"Int"> & Brand<"NonNegative">, number, import("../Type.js").NonNegativeError, number & Brand<"Int">, import("../Type.js").IntError | import("../Type.js").NumberError>, `LessThanOrEqualTo${number}`, import("../Type.js").LessThanOrEqualToError<number>, import("../Type.js").NonNegativeError | import("../Type.js").IntError | import("../Type.js").NumberError>, "Millis", import("../Type.js").BrandWithoutRefineError<"Millis", import("../Type.js").NonNegativeError | import("../Type.js").IntError | import("../Type.js").NumberError | import("../Type.js").LessThanOrEqualToError<number>>, never>;
46
43
  export type Millis = typeof Millis.Type;
47
44
  export declare const minMillis: Millis;
48
45
  export declare const maxMillis: Millis;
49
- export declare const Counter: import("../Type.js").BrandType<import("../Type.js").BrandType<import("../Type.js").Type<"Brand", number & Brand<"Int"> & Brand<"NonNegative">, number, import("../Type.js").NonNegativeError, number & Brand<"Int">, import("../Type.js").NumberError | import("../Type.js").IntError>, "LessThanOrEqualTo65535", import("../Type.js").LessThanOrEqualToError<65535>, import("../Type.js").NumberError | import("../Type.js").NonNegativeError | import("../Type.js").IntError>, "Counter", import("../Type.js").BrandWithoutRefineError<"Counter", import("../Type.js").NumberError | import("../Type.js").NonNegativeError | import("../Type.js").IntError | import("../Type.js").LessThanOrEqualToError<65535>>, never>;
46
+ export declare const Counter: import("../Type.js").BrandType<import("../Type.js").BrandType<import("../Type.js").Type<"Brand", number & Brand<"Int"> & Brand<"NonNegative">, number, import("../Type.js").NonNegativeError, number & Brand<"Int">, import("../Type.js").IntError | import("../Type.js").NumberError>, "LessThanOrEqualTo65535", import("../Type.js").LessThanOrEqualToError<65535>, import("../Type.js").NonNegativeError | import("../Type.js").IntError | import("../Type.js").NumberError>, "Counter", import("../Type.js").BrandWithoutRefineError<"Counter", import("../Type.js").NonNegativeError | import("../Type.js").IntError | import("../Type.js").NumberError | import("../Type.js").LessThanOrEqualToError<65535>>, never>;
50
47
  export type Counter = typeof Counter.Type;
51
48
  export declare const minCounter: Counter;
52
49
  export declare const maxCounter: Counter;
@@ -63,14 +60,17 @@ export declare const maxCounter: Counter;
63
60
  *
64
61
  * https://lemire.me/blog/2019/12/12/are-64-bit-random-identifiers-free-from-collision
65
62
  *
66
- * What will happen if a different device generates the same NodeId?
63
+ * What happens if different devices generate the same NodeId?
67
64
  *
68
- * If the device belongs to a different owner, nothing will happen because
69
- * different owner have different owner IDs. Timestamps are partitioned by
70
- * OwnerId.
65
+ * If devices with the same NodeId use different owners, no issues occur.
71
66
  *
72
- * If the device belongs to the same owner, the other device will return
73
- * {@link TimestampDuplicateNodeError}.
67
+ * If devices with the same NodeId use the same owner, problems only arise when
68
+ * they generate CRDT messages with identical timestamps (same millis, counter,
69
+ * and NodeId). In this case, the protocol sync algorithm treats them as the
70
+ * same message: the first will be synced with the relay, while the affected
71
+ * message will not be delivered. The affected devices will see different data
72
+ * yet they will think they are synced. This is extremely rare and can be
73
+ * resolved by resetting one device to generate a new NodeId.
74
74
  */
75
75
  export declare const NodeId: import("../Type.js").BrandType<import("../Type.js").Type<"String", string, string, import("../Type.js").StringError, string, import("../Type.js").StringError>, "NodeId", import("../Type.js").RegexError<"NodeId">, import("../Type.js").StringError>;
76
76
  export type NodeId = typeof NodeId.Type;
@@ -84,23 +84,31 @@ export declare const maxNodeId: NodeId;
84
84
  * - https://jaredforsyth.com/posts/hybrid-logical-clocks/
85
85
  */
86
86
  export declare const Timestamp: import("../Type.js").ObjectType<{
87
- millis: import("../Type.js").BrandType<import("../Type.js").BrandType<import("../Type.js").Type<"Brand", number & Brand<"Int"> & Brand<"NonNegative">, number, import("../Type.js").NonNegativeError, number & Brand<"Int">, import("../Type.js").NumberError | import("../Type.js").IntError>, `LessThanOrEqualTo${number}`, import("../Type.js").LessThanOrEqualToError<number>, import("../Type.js").NumberError | import("../Type.js").NonNegativeError | import("../Type.js").IntError>, "Millis", import("../Type.js").BrandWithoutRefineError<"Millis", import("../Type.js").NumberError | import("../Type.js").NonNegativeError | import("../Type.js").IntError | import("../Type.js").LessThanOrEqualToError<number>>, never>;
88
- counter: import("../Type.js").BrandType<import("../Type.js").BrandType<import("../Type.js").Type<"Brand", number & Brand<"Int"> & Brand<"NonNegative">, number, import("../Type.js").NonNegativeError, number & Brand<"Int">, import("../Type.js").NumberError | import("../Type.js").IntError>, "LessThanOrEqualTo65535", import("../Type.js").LessThanOrEqualToError<65535>, import("../Type.js").NumberError | import("../Type.js").NonNegativeError | import("../Type.js").IntError>, "Counter", import("../Type.js").BrandWithoutRefineError<"Counter", import("../Type.js").NumberError | import("../Type.js").NonNegativeError | import("../Type.js").IntError | import("../Type.js").LessThanOrEqualToError<65535>>, never>;
87
+ millis: import("../Type.js").BrandType<import("../Type.js").BrandType<import("../Type.js").Type<"Brand", number & Brand<"Int"> & Brand<"NonNegative">, number, import("../Type.js").NonNegativeError, number & Brand<"Int">, import("../Type.js").IntError | import("../Type.js").NumberError>, `LessThanOrEqualTo${number}`, import("../Type.js").LessThanOrEqualToError<number>, import("../Type.js").NonNegativeError | import("../Type.js").IntError | import("../Type.js").NumberError>, "Millis", import("../Type.js").BrandWithoutRefineError<"Millis", import("../Type.js").NonNegativeError | import("../Type.js").IntError | import("../Type.js").NumberError | import("../Type.js").LessThanOrEqualToError<number>>, never>;
88
+ counter: import("../Type.js").BrandType<import("../Type.js").BrandType<import("../Type.js").Type<"Brand", number & Brand<"Int"> & Brand<"NonNegative">, number, import("../Type.js").NonNegativeError, number & Brand<"Int">, import("../Type.js").IntError | import("../Type.js").NumberError>, "LessThanOrEqualTo65535", import("../Type.js").LessThanOrEqualToError<65535>, import("../Type.js").NonNegativeError | import("../Type.js").IntError | import("../Type.js").NumberError>, "Counter", import("../Type.js").BrandWithoutRefineError<"Counter", import("../Type.js").NonNegativeError | import("../Type.js").IntError | import("../Type.js").NumberError | import("../Type.js").LessThanOrEqualToError<65535>>, never>;
89
89
  nodeId: import("../Type.js").BrandType<import("../Type.js").Type<"String", string, string, import("../Type.js").StringError, string, import("../Type.js").StringError>, "NodeId", import("../Type.js").RegexError<"NodeId">, import("../Type.js").StringError>;
90
90
  }>;
91
- export type Timestamp = typeof Timestamp.Type;
91
+ export interface Timestamp extends InferType<typeof Timestamp> {
92
+ }
93
+ /** Equality function for comparing {@link Timestamp}. */
94
+ export declare const eqTimestamp: import("../Eq.js").Eq<{
95
+ readonly millis: number & Brand<"Int"> & Brand<"NonNegative"> & Brand<`LessThanOrEqualTo${number}`> & Brand<"Millis">;
96
+ readonly counter: number & Brand<"Int"> & Brand<"NonNegative"> & Brand<"LessThanOrEqualTo65535"> & Brand<"Counter">;
97
+ readonly nodeId: string & Brand<"NodeId">;
98
+ }>;
92
99
  export declare const createTimestamp: ({ millis, counter, nodeId, }?: Partial<Timestamp>) => Timestamp;
93
- export declare const createInitialTimestamp: (deps: NanoIdLibDep) => Timestamp;
94
- /** TimestampString is a sortable string version of {@link Timestamp}. */
100
+ export declare const createInitialTimestamp: (deps: RandomBytesDep) => Timestamp;
101
+ /** Sortable string representation of {@link Timestamp}. */
95
102
  export type TimestampString = string & Brand<"TimestampString">;
96
103
  export declare const timestampToTimestampString: (t: Timestamp) => TimestampString;
97
104
  export declare const timestampStringToTimestamp: (timestampString: TimestampString) => Timestamp;
98
105
  export declare const sendTimestamp: (deps: TimeDep & TimestampConfigDep) => (timestamp: Timestamp) => Result<Timestamp, TimestampDriftError | TimestampCounterOverflowError | TimestampTimeOutOfRangeError>;
99
- export declare const receiveTimestamp: (deps: TimeDep & TimestampConfigDep) => (local: Timestamp, remote: Timestamp) => Result<Timestamp, TimestampDriftError | TimestampCounterOverflowError | TimestampDuplicateNodeError | TimestampTimeOutOfRangeError>;
100
- /** BinaryTimestamp is a binary and sortable version of {@link Timestamp} for DB. */
101
- export type BinaryTimestamp = Uint8Array & Brand<"BinaryTimestamp">;
102
- export declare const binaryTimestampLength: number & Brand<"Int"> & Brand<"NonNegative">;
103
- export declare const timestampToBinaryTimestamp: (timestamp: Timestamp) => BinaryTimestamp;
104
- export declare const binaryTimestampToTimestamp: (timestamp: BinaryTimestamp) => Timestamp;
105
- export declare const orderBinaryTimestamp: Order<BinaryTimestamp>;
106
+ export declare const receiveTimestamp: (deps: TimeDep & TimestampConfigDep) => (local: Timestamp, remote: Timestamp) => Result<Timestamp, TimestampDriftError | TimestampCounterOverflowError | TimestampTimeOutOfRangeError>;
107
+ /** Sortable bytes representation of {@link Timestamp}. */
108
+ export declare const TimestampBytes: import("../Type.js").BrandType<import("../Type.js").Type<"Uint8Array", Uint8Array<ArrayBufferLike>, Uint8Array<ArrayBufferLike>, import("../Type.js").Uint8ArrayError, Uint8Array<ArrayBufferLike>, import("../Type.js").Uint8ArrayError>, "TimestampBytes", import("../Type.js").BrandWithoutRefineError<"TimestampBytes", import("../Type.js").Uint8ArrayError>, never>;
109
+ export type TimestampBytes = typeof TimestampBytes.Type;
110
+ export declare const timestampBytesLength: number & Brand<"Int"> & Brand<"NonNegative">;
111
+ export declare const timestampToTimestampBytes: (timestamp: Timestamp) => TimestampBytes;
112
+ export declare const timestampBytesToTimestamp: (timestamp: TimestampBytes) => Timestamp;
113
+ export declare const orderTimestampBytes: Order<TimestampBytes>;
106
114
  //# sourceMappingURL=Timestamp.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Timestamp.d.ts","sourceRoot":"","sources":["../../../src/Evolu/Timestamp.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Timestamp.d.ts","sourceRoot":"","sources":["../../../src/Evolu/Timestamp.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,10 @@
1
1
  import { assert } from "../Assert.js";
2
+ import { bytesToHex } from "../Buffer.js";
3
+ import { createEqObject, eqNumber, eqString } from "../Eq.js";
2
4
  import { increment } from "../Number.js";
3
5
  import { orderUint8Array } from "../Order.js";
4
- import { err, getOrThrow, ok } from "../Result.js";
5
- import { brand, lessThanOrEqualTo, NonNegativeInt, object, regex, String, } from "../Type.js";
6
+ import { err, ok } from "../Result.js";
7
+ import { brand, lessThanOrEqualTo, NonNegativeInt, object, regex, String, Uint8Array, } from "../Type.js";
6
8
  /**
7
9
  * Millis is a timestamp in milliseconds, like `Date.now()`, but limited to the
8
10
  * maximum value representable in 6 bytes (281474976710655) minus 1 (reserved
@@ -34,14 +36,17 @@ export const maxCounter = 65535;
34
36
  *
35
37
  * https://lemire.me/blog/2019/12/12/are-64-bit-random-identifiers-free-from-collision
36
38
  *
37
- * What will happen if a different device generates the same NodeId?
39
+ * What happens if different devices generate the same NodeId?
38
40
  *
39
- * If the device belongs to a different owner, nothing will happen because
40
- * different owner have different owner IDs. Timestamps are partitioned by
41
- * OwnerId.
41
+ * If devices with the same NodeId use different owners, no issues occur.
42
42
  *
43
- * If the device belongs to the same owner, the other device will return
44
- * {@link TimestampDuplicateNodeError}.
43
+ * If devices with the same NodeId use the same owner, problems only arise when
44
+ * they generate CRDT messages with identical timestamps (same millis, counter,
45
+ * and NodeId). In this case, the protocol sync algorithm treats them as the
46
+ * same message: the first will be synced with the relay, while the affected
47
+ * message will not be delivered. The affected devices will see different data
48
+ * yet they will think they are synced. This is extremely rare and can be
49
+ * resolved by resetting one device to generate a new NodeId.
45
50
  */
46
51
  export const NodeId = regex("NodeId", /^[a-f0-9]{16}$/)(String);
47
52
  export const minNodeId = "0000000000000000";
@@ -58,10 +63,15 @@ export const Timestamp = object({
58
63
  counter: Counter,
59
64
  nodeId: NodeId,
60
65
  });
66
+ /** Equality function for comparing {@link Timestamp}. */
67
+ export const eqTimestamp = createEqObject({
68
+ millis: eqNumber,
69
+ counter: eqNumber,
70
+ nodeId: eqString,
71
+ });
61
72
  export const createTimestamp = ({ millis = minMillis, counter = minCounter, nodeId = minNodeId, } = {}) => ({ millis, counter, nodeId });
62
- const hexAlphabet = "0123456789abcdef";
63
73
  export const createInitialTimestamp = (deps) => {
64
- const nodeId = deps.nanoIdLib.customAlphabet(hexAlphabet, 16)();
74
+ const nodeId = bytesToHex(deps.randomBytes.create(8));
65
75
  return createTimestamp({ nodeId });
66
76
  };
67
77
  export const timestampToTimestampString = (t) => [
@@ -115,9 +125,6 @@ export const sendTimestamp = (deps) => (timestamp) => {
115
125
  });
116
126
  };
117
127
  export const receiveTimestamp = (deps) => (local, remote) => {
118
- if (local.nodeId === remote.nodeId) {
119
- return err({ type: "TimestampDuplicateNodeError", nodeId: local.nodeId });
120
- }
121
128
  const millis = getNextMillis(deps)([local.millis, remote.millis]);
122
129
  if (!millis.ok)
123
130
  return millis;
@@ -136,11 +143,13 @@ export const receiveTimestamp = (deps) => (local, remote) => {
136
143
  nodeId: local.nodeId,
137
144
  });
138
145
  };
139
- export const binaryTimestampLength = getOrThrow(NonNegativeInt.from(16));
140
- export const timestampToBinaryTimestamp = (timestamp) => {
146
+ /** Sortable bytes representation of {@link Timestamp}. */
147
+ export const TimestampBytes = brand("TimestampBytes", Uint8Array);
148
+ export const timestampBytesLength = NonNegativeInt.orThrow(16);
149
+ export const timestampToTimestampBytes = (timestamp) => {
141
150
  const { millis, counter, nodeId } = timestamp;
142
151
  // 6 bytes for millis, 2 bytes for counter, 8 bytes for nodeId.
143
- const value = new Uint8Array(16);
152
+ const value = new globalThis.Uint8Array(16);
144
153
  // Encode `millis` into the first 6 bytes.
145
154
  const millisBigInt = BigInt(millis);
146
155
  value[0] = Number((millisBigInt >> 40n) & 0xffn);
@@ -159,7 +168,7 @@ export const timestampToBinaryTimestamp = (timestamp) => {
159
168
  }
160
169
  return value;
161
170
  };
162
- export const binaryTimestampToTimestamp = (timestamp) => {
171
+ export const timestampBytesToTimestamp = (timestamp) => {
163
172
  // Decode `millis` from the first 6 bytes.
164
173
  const millis = (BigInt(timestamp[0]) << 40n) |
165
174
  (BigInt(timestamp[1]) << 32n) |
@@ -176,4 +185,4 @@ export const binaryTimestampToTimestamp = (timestamp) => {
176
185
  }
177
186
  return { millis: Number(millis), counter, nodeId };
178
187
  };
179
- export const orderBinaryTimestamp = orderUint8Array;
188
+ export const orderTimestampBytes = orderUint8Array;
@@ -1,17 +1,75 @@
1
- /** Represents a many-to-many mapping between keys and values. */
1
+ /**
2
+ * Bidirectional many-to-many map between keys and values.
3
+ *
4
+ * Why useful:
5
+ *
6
+ * - Provides O(1) (amortized) forward lookup (key -> values) and reverse lookup
7
+ * (value -> keys) without maintaining two maps manually and risking them
8
+ * diverging.
9
+ * - Natural fit for pub/sub style relations such as ownerId ↔ WebSocket, tag ↔
10
+ * item, user ↔ role, entity ↔ subscription where both directions are
11
+ * frequently queried.
12
+ * - Supports fast membership tests via `hasPair`, `hasKey`, and `hasValue`.
13
+ * - Iteration helpers (`forEach`, iterator) allow treating the structure as an
14
+ * edge list when needed.
15
+ *
16
+ * Complexity:
17
+ *
18
+ * - `add` / `remove` / `has*` / `get*` each perform a constant number of Map/Set
19
+ * operations (O(1) expected).
20
+ * - `deleteKey` and `deleteValue` are O(d) where d is the number of associated
21
+ * values / keys (the degree). This is optimal because every associated pair
22
+ * must be touched once.
23
+ * - In the Relay use case a socket (value) typically has only dozens of owners
24
+ * (degree small), and connection closes (triggering deleteValue) are
25
+ * relatively infrequent, so O(d) is acceptable.
26
+ *
27
+ * Object identity:
28
+ *
29
+ * - Keys and values are compared by reference (standard Map / Set semantics).
30
+ * Structural hashing of objects in JavaScript is non-trivial, can be
31
+ * expensive, and collision-prone if done naively. Prefer using stable
32
+ * primitive identifiers (ids, strings) instead of attempting to hash full
33
+ * object structures.
34
+ * - If structural equivalence is truly required, wrap objects in an adapter that
35
+ * supplies a canonical hash/id and stores/retrieves the original objects
36
+ * separately. This is a rare need; avoid unless you have clear requirements.
37
+ */
2
38
  export interface ManyToManyMap<K, V> {
3
- /** Adds a key-value association to the map. */
4
- add: (key: K, value: V) => ManyToManyMap<K, V>;
5
- /** Removes a specific key-value association. */
39
+ /**
40
+ * Adds a key-value association to the map. Returns true if the pair was newly
41
+ * added, false if it already existed.
42
+ */
43
+ add: (key: K, value: V) => boolean;
44
+ /**
45
+ * Removes a specific key-value association. Returns true if the pair existed
46
+ * and was removed, false if it was not present.
47
+ */
6
48
  remove: (key: K, value: V) => boolean;
7
- /** Gets all values associated with a key. */
8
- getValues: (key: K) => Set<V> | undefined;
9
- /** Gets all keys associated with a value. */
10
- getKeys: (value: V) => Set<K> | undefined;
49
+ /**
50
+ * Gets all values associated with a key. Returned set is the internal Set
51
+ * instance typed as ReadonlySet. Do not mutate.
52
+ */
53
+ getValues: (key: K) => ReadonlySet<V> | undefined;
54
+ /**
55
+ * Gets all keys associated with a value. Returned set is the internal Set
56
+ * instance typed as ReadonlySet. Do not mutate.
57
+ */
58
+ getKeys: (value: V) => ReadonlySet<K> | undefined;
59
+ /**
60
+ * Iterates over each key-value pair (in insertion order of keys, then values
61
+ * per key).
62
+ */
63
+ forEach: (callback: (key: K, value: V) => void) => void;
64
+ /**
65
+ * Iterator over all key-value pairs enabling for..of and spread. Yields
66
+ * readonly [key, value] tuples.
67
+ */
68
+ readonly [Symbol.iterator]: () => IterableIterator<readonly [K, V]>;
11
69
  /** Checks if a specific key-value pair exists. */
12
70
  hasPair: (key: K, value: V) => boolean;
13
71
  /** Checks if a key exists in the map. */
14
- hasKey(key: K): boolean;
72
+ hasKey: (key: K) => boolean;
15
73
  /** Checks if a value exists in the map. */
16
74
  hasValue: (value: V) => boolean;
17
75
  /** Deletes all associations for a key. */
@@ -19,7 +77,13 @@ export interface ManyToManyMap<K, V> {
19
77
  /** Deletes all associations for a value. */
20
78
  deleteValue: (value: V) => boolean;
21
79
  /** Clears all associations in the map. */
22
- clear(): void;
80
+ clear: () => void;
81
+ /** Number of distinct keys currently present. */
82
+ keyCount: () => number;
83
+ /** Number of distinct values currently present. */
84
+ valueCount: () => number;
85
+ /** Number of key-value pairs (associations) currently stored. */
86
+ pairCount: () => number;
23
87
  }
24
88
  /** Creates a {@link ManyToManyMap}. */
25
89
  export declare const createManyToManyMap: <K, V>() => ManyToManyMap<K, V>;
@@ -1 +1 @@
1
- {"version":3,"file":"ManyToManyMap.d.ts","sourceRoot":"","sources":["../../src/ManyToManyMap.ts"],"names":[],"mappings":"AAAA,iEAAiE;AACjE,MAAM,WAAW,aAAa,CAAC,CAAC,EAAE,CAAC;IACjC,+CAA+C;IAC/C,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAE/C,gDAAgD;IAChD,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,OAAO,CAAC;IAEtC,6CAA6C;IAC7C,SAAS,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;IAE1C,6CAA6C;IAC7C,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;IAE1C,kDAAkD;IAClD,OAAO,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,OAAO,CAAC;IAEvC,yCAAyC;IACzC,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC;IAExB,2CAA2C;IAC3C,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,OAAO,CAAC;IAEhC,0CAA0C;IAC1C,SAAS,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,OAAO,CAAC;IAE/B,4CAA4C;IAC5C,WAAW,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,OAAO,CAAC;IAEnC,0CAA0C;IAC1C,KAAK,IAAI,IAAI,CAAC;CACf;AAED,uCAAuC;AACvC,eAAO,MAAM,mBAAmB,GAAI,CAAC,EAAE,CAAC,OAAK,aAAa,CAAC,CAAC,EAAE,CAAC,CAyG9D,CAAC"}
1
+ {"version":3,"file":"ManyToManyMap.d.ts","sourceRoot":"","sources":["../../src/ManyToManyMap.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,MAAM,WAAW,aAAa,CAAC,CAAC,EAAE,CAAC;IACjC;;;OAGG;IACH,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,OAAO,CAAC;IAEnC;;;OAGG;IACH,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,OAAO,CAAC;IAEtC;;;OAGG;IACH,SAAS,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,WAAW,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;IAElD;;;OAGG;IACH,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,WAAW,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;IAElD;;;OAGG;IACH,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,IAAI,KAAK,IAAI,CAAC;IAExD;;;OAGG;IACH,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,MAAM,gBAAgB,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAEpE,kDAAkD;IAClD,OAAO,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,OAAO,CAAC;IAEvC,yCAAyC;IACzC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,OAAO,CAAC;IAE5B,2CAA2C;IAC3C,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,OAAO,CAAC;IAEhC,0CAA0C;IAC1C,SAAS,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,OAAO,CAAC;IAE/B,4CAA4C;IAC5C,WAAW,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,OAAO,CAAC;IAEnC,0CAA0C;IAC1C,KAAK,EAAE,MAAM,IAAI,CAAC;IAElB,iDAAiD;IACjD,QAAQ,EAAE,MAAM,MAAM,CAAC;IACvB,mDAAmD;IACnD,UAAU,EAAE,MAAM,MAAM,CAAC;IACzB,iEAAiE;IACjE,SAAS,EAAE,MAAM,MAAM,CAAC;CACzB;AAED,uCAAuC;AACvC,eAAO,MAAM,mBAAmB,GAAI,CAAC,EAAE,CAAC,OAAK,aAAa,CAAC,CAAC,EAAE,CAAC,CA2I9D,CAAC"}
@@ -1,10 +1,14 @@
1
+ import { assert } from "./Assert.js";
1
2
  /** Creates a {@link ManyToManyMap}. */
2
3
  export const createManyToManyMap = () => {
3
4
  const forwardMap = new Map();
4
5
  const reverseMap = new Map();
6
+ let pairCountInternal = 0;
5
7
  const map = {
6
8
  add(key, value) {
7
9
  let values = forwardMap.get(key);
10
+ if (values?.has(value))
11
+ return false;
8
12
  if (!values) {
9
13
  values = new Set();
10
14
  forwardMap.set(key, values);
@@ -16,7 +20,8 @@ export const createManyToManyMap = () => {
16
20
  reverseMap.set(value, keys);
17
21
  }
18
22
  keys.add(key);
19
- return map;
23
+ pairCountInternal++;
24
+ return true;
20
25
  },
21
26
  remove(key, value) {
22
27
  const values = forwardMap.get(key);
@@ -27,12 +32,12 @@ export const createManyToManyMap = () => {
27
32
  forwardMap.delete(key);
28
33
  }
29
34
  const keys = reverseMap.get(value);
30
- if (keys?.size) {
31
- keys.delete(key);
32
- if (keys.size === 0) {
33
- reverseMap.delete(value);
34
- }
35
+ assert(keys, "Key-value mapping inconsistency");
36
+ keys.delete(key);
37
+ if (keys.size === 0) {
38
+ reverseMap.delete(value);
35
39
  }
40
+ pairCountInternal--;
36
41
  return true;
37
42
  },
38
43
  getValues(key) {
@@ -41,6 +46,22 @@ export const createManyToManyMap = () => {
41
46
  getKeys(value) {
42
47
  return reverseMap.get(value);
43
48
  },
49
+ forEach(callback) {
50
+ for (const [key, values] of forwardMap) {
51
+ for (const value of values)
52
+ callback(key, value);
53
+ }
54
+ },
55
+ [Symbol.iterator]() {
56
+ const iterator = function* () {
57
+ for (const [key, values] of forwardMap) {
58
+ for (const value of values) {
59
+ yield [key, value];
60
+ }
61
+ }
62
+ };
63
+ return iterator();
64
+ },
44
65
  hasPair(key, value) {
45
66
  const values = forwardMap.get(key);
46
67
  return values?.has(value) ?? false;
@@ -55,6 +76,7 @@ export const createManyToManyMap = () => {
55
76
  const values = forwardMap.get(key);
56
77
  if (!values)
57
78
  return false;
79
+ const removed = values.size;
58
80
  for (const value of values) {
59
81
  const keys = reverseMap.get(value);
60
82
  if (keys) {
@@ -65,12 +87,14 @@ export const createManyToManyMap = () => {
65
87
  }
66
88
  }
67
89
  forwardMap.delete(key);
90
+ pairCountInternal -= removed;
68
91
  return true;
69
92
  },
70
93
  deleteValue(value) {
71
94
  const keys = reverseMap.get(value);
72
95
  if (!keys)
73
96
  return false;
97
+ const removed = keys.size;
74
98
  for (const key of keys) {
75
99
  const values = forwardMap.get(key);
76
100
  if (values) {
@@ -81,11 +105,22 @@ export const createManyToManyMap = () => {
81
105
  }
82
106
  }
83
107
  reverseMap.delete(value);
108
+ pairCountInternal -= removed;
84
109
  return true;
85
110
  },
86
111
  clear() {
87
112
  forwardMap.clear();
88
113
  reverseMap.clear();
114
+ pairCountInternal = 0;
115
+ },
116
+ keyCount() {
117
+ return forwardMap.size;
118
+ },
119
+ valueCount() {
120
+ return reverseMap.size;
121
+ },
122
+ pairCount() {
123
+ return pairCountInternal;
89
124
  },
90
125
  };
91
126
  return map;
@@ -1,7 +1,8 @@
1
1
  import { NonEmptyReadonlyArray } from "./Array.js";
2
2
  import { Result } from "./Result.js";
3
3
  import { NonNegativeInt, PositiveInt } from "./Type.js";
4
- import { IsBranded, Predicate, WidenLiteral } from "./Types.js";
4
+ import { Predicate, WidenLiteral } from "./Types.js";
5
+ import { IsBranded } from "./Brand.js";
5
6
  export declare const increment: (n: number) => number;
6
7
  export declare const decrement: (n: number) => number;
7
8
  /** Clamps a number within a given range. */
@@ -1 +1 @@
1
- {"version":3,"file":"Number.d.ts","sourceRoot":"","sources":["../../src/Number.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAEnD,OAAO,EAAW,MAAM,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxD,OAAO,EAEL,SAAS,EACT,SAAS,EACT,YAAY,EACb,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,SAAS,GAAI,GAAG,MAAM,KAAG,MAAe,CAAC;AAEtD,eAAO,MAAM,SAAS,GAAI,GAAG,MAAM,KAAG,MAAe,CAAC;AAEtD,4CAA4C;AAC5C,eAAO,MAAM,KAAK,GACf,KAAK,MAAM,EAAE,KAAK,MAAM,MACxB,GAAG,MAAM,KAAG,MACoB,CAAC;AAEpC;;;;;;;;;;GAUG;AACH,eAAO,MAAM,SAAS,GACnB,KAAK,MAAM,EAAE,KAAK,MAAM,KAAG,SAAS,CAAC,MAAM,CAEd,CAAC;AAEjC,wEAAwE;AACxE,eAAO,MAAM,GAAG,GAAI,CAAC,SAAS,MAAM,EAClC,GAAG,QAAQ,CAAC,CAAC,EAAE,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC,KAClC,SAAS,CAAC,CAAC,CAAC,SAAS,IAAI,GAAG,CAAC,GAAG,YAAY,CAAC,CAAC,CACa,CAAC;AAE/D,wEAAwE;AACxE,eAAO,MAAM,GAAG,GAAI,CAAC,SAAS,MAAM,EAClC,GAAG,QAAQ,CAAC,CAAC,EAAE,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC,KAClC,SAAS,CAAC,CAAC,CAAC,SAAS,IAAI,GAAG,CAAC,GAAG,YAAY,CAAC,CAAC,CACa,CAAC;AAE/D;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,sBAAsB,GACjC,eAAe,cAAc;AAE7B,kBAAkB;AAClB,kBAAwB,WAAW;AAEnC,iBAAiB;AACjB,4BAAiC,WAAW,KAC3C,MAAM,CAAC,qBAAqB,CAAC,WAAW,CAAC,EAAE,WAAW,CAoBxD,CAAC"}
1
+ {"version":3,"file":"Number.d.ts","sourceRoot":"","sources":["../../src/Number.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAEnD,OAAO,EAAW,MAAM,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxD,OAAO,EAAoB,SAAS,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACvE,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,eAAO,MAAM,SAAS,GAAI,GAAG,MAAM,KAAG,MAAe,CAAC;AAEtD,eAAO,MAAM,SAAS,GAAI,GAAG,MAAM,KAAG,MAAe,CAAC;AAEtD,4CAA4C;AAC5C,eAAO,MAAM,KAAK,GACf,KAAK,MAAM,EAAE,KAAK,MAAM,MACxB,GAAG,MAAM,KAAG,MACoB,CAAC;AAEpC;;;;;;;;;;GAUG;AACH,eAAO,MAAM,SAAS,GACnB,KAAK,MAAM,EAAE,KAAK,MAAM,KAAG,SAAS,CAAC,MAAM,CAEd,CAAC;AAEjC,wEAAwE;AACxE,eAAO,MAAM,GAAG,GAAI,CAAC,SAAS,MAAM,EAClC,GAAG,QAAQ,CAAC,CAAC,EAAE,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC,KAClC,SAAS,CAAC,CAAC,CAAC,SAAS,IAAI,GAAG,CAAC,GAAG,YAAY,CAAC,CAAC,CACa,CAAC;AAE/D,wEAAwE;AACxE,eAAO,MAAM,GAAG,GAAI,CAAC,SAAS,MAAM,EAClC,GAAG,QAAQ,CAAC,CAAC,EAAE,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC,KAClC,SAAS,CAAC,CAAC,CAAC,SAAS,IAAI,GAAG,CAAC,GAAG,YAAY,CAAC,CAAC,CACa,CAAC;AAE/D;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,sBAAsB,GACjC,eAAe,cAAc;AAE7B,kBAAkB;AAClB,kBAAwB,WAAW;AAEnC,iBAAiB;AACjB,4BAAiC,WAAW,KAC3C,MAAM,CAAC,qBAAqB,CAAC,WAAW,CAAC,EAAE,WAAW,CAoBxD,CAAC"}
@@ -5,8 +5,9 @@
5
5
  */
6
6
  import { Random as RandomLib } from "random";
7
7
  /**
8
- * A simple wrapper around Math.random(). Most apps need only this. For more
9
- * complex needs check {@link RandomLibDep}.
8
+ * A simple wrapper around Math.random().
9
+ *
10
+ * For more complex needs check {@link RandomLibDep}.
10
11
  *
11
12
  * ### Example
12
13
  *
@@ -1 +1 @@
1
- {"version":3,"file":"Random.d.ts","sourceRoot":"","sources":["../../src/Random.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,MAAM,IAAI,SAAS,EAAE,MAAM,QAAQ,CAAC;AAE7C;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,MAAM;IACrB,0EAA0E;IAC1E,IAAI,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,oDAAoD;AACpD,eAAO,MAAM,YAAY,QAAO,MAE9B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,oBAAoB,GAAI,MAAM,MAAM,KAAG,MAKnD,CAAC;AAEF;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,SAAS,CAAC;CACnB;AAED,4DAA4D;AAC5D,eAAO,MAAM,eAAe,QAAO,SAA4B,CAAC;AAEhE;;;GAGG;AACH,eAAO,MAAM,uBAAuB,GAAI,MAAM,MAAM,KAAG,YAErD,CAAC"}
1
+ {"version":3,"file":"Random.d.ts","sourceRoot":"","sources":["../../src/Random.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,MAAM,IAAI,SAAS,EAAE,MAAM,QAAQ,CAAC;AAE7C;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,MAAM;IACrB,0EAA0E;IAC1E,IAAI,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,oDAAoD;AACpD,eAAO,MAAM,YAAY,QAAO,MAE9B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,oBAAoB,GAAI,MAAM,MAAM,KAAG,MAKnD,CAAC;AAEF;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,SAAS,CAAC;CACnB;AAED,4DAA4D;AAC5D,eAAO,MAAM,eAAe,QAAO,SAA4B,CAAC;AAEhE;;;GAGG;AACH,eAAO,MAAM,uBAAuB,GAAI,MAAM,MAAM,KAAG,YAErD,CAAC"}
@@ -0,0 +1,119 @@
1
+ import { Result } from "./Result.js";
2
+ /**
3
+ * A generic resource manager that handles reference counting and delayed
4
+ * disposal of shared resources. Useful for managing expensive resources like
5
+ * WebSocket connections, database connections, or file handles that need to be
6
+ * shared among multiple consumers.
7
+ */
8
+ export interface RefCountedResourceManager<TResource extends Disposable, TResourceKey extends string, TResourceConfig, TConsumer, TConsumerId extends string> extends Disposable {
9
+ /**
10
+ * Adds a consumer to resources, creating them if necessary. Increments
11
+ * reference counts for existing consumer-resource pairs.
12
+ */
13
+ readonly addConsumer: (consumer: TConsumer, resourceConfigs: ReadonlyArray<TResourceConfig>) => void;
14
+ /**
15
+ * Removes a consumer from resources. Decrements reference counts and
16
+ * schedules disposal when no consumers remain.
17
+ *
18
+ * Returns an error if the resource doesn't exist or if the consumer wasn't
19
+ * added to the resource.
20
+ */
21
+ readonly removeConsumer: (consumer: TConsumer, resourceConfigs: ReadonlyArray<TResourceConfig>) => Result<void, ResourceNotFoundError<TResourceKey> | ConsumerNotFoundError<TConsumerId, TResourceKey>>;
22
+ /** Gets the resource for the specified key, or null if it doesn't exist. */
23
+ readonly getResource: (key: TResourceKey) => TResource | null;
24
+ /** Gets all consumer IDs currently using the specified resource key. */
25
+ readonly getConsumersForResource: (key: TResourceKey) => ReadonlyArray<TConsumerId>;
26
+ /** Checks if a consumer is currently using any resources. */
27
+ readonly hasConsumerAnyResource: (consumer: TConsumer) => boolean;
28
+ /**
29
+ * Gets the consumer for the specified consumer ID, or null if not found or
30
+ * not using any resources.
31
+ */
32
+ readonly getConsumer: (consumerId: TConsumerId) => TConsumer | null;
33
+ }
34
+ /** Error when trying to remove a consumer from a resource that doesn't exist. */
35
+ export interface ResourceNotFoundError<TResourceKey extends string = string> {
36
+ readonly type: "ResourceNotFoundError";
37
+ readonly resourceKey: TResourceKey;
38
+ }
39
+ /** Error when trying to remove a consumer that wasn't added to a resource. */
40
+ export interface ConsumerNotFoundError<TConsumerId extends string = string, TResourceKey extends string = string> {
41
+ readonly type: "ConsumerNotFoundError";
42
+ readonly consumerId: TConsumerId;
43
+ readonly resourceKey: TResourceKey;
44
+ }
45
+ export interface ResourceManagerConfig<TResource extends Disposable, TResourceKey extends string, TResourceConfig, TConsumer, TConsumerId extends string> {
46
+ /** Creates a new resource for the given config. */
47
+ readonly createResource: (config: TResourceConfig) => TResource;
48
+ /** Extracts a unique key from a resource config for deduplication. */
49
+ readonly getResourceKey: (config: TResourceConfig) => TResourceKey;
50
+ /** Extracts a unique identifier from a consumer for reference counting. */
51
+ readonly getConsumerId: (consumer: TConsumer) => TConsumerId;
52
+ /**
53
+ * Delay in milliseconds before disposing unused resources. Helps avoid
54
+ * resource churn during rapid add/remove cycles. Defaults to 100ms.
55
+ */
56
+ readonly disposalDelay?: number;
57
+ /**
58
+ * Called when a consumer is added to a resource for the first time. This
59
+ * happens when the consumer's reference count goes from 0 to 1 for this
60
+ * resource.
61
+ */
62
+ readonly onConsumerAdded?: (consumer: TConsumer, resource: TResource, resourceKey: TResourceKey) => void;
63
+ /**
64
+ * Called when a consumer is completely removed from a resource. This happens
65
+ * when the consumer's reference count goes from 1 to 0 for this resource.
66
+ */
67
+ readonly onConsumerRemoved?: (consumer: TConsumer, resource: TResource, resourceKey: TResourceKey) => void;
68
+ }
69
+ /**
70
+ * Creates a reference-counted resource manager.
71
+ *
72
+ * This manager tracks which consumers are using which resources and maintains
73
+ * reference counts to know when it's safe to dispose resources. Resources are
74
+ * created on-demand and disposed with a configurable delay to avoid churn.
75
+ *
76
+ * ### Example Usage
77
+ *
78
+ * ```ts
79
+ * // WebSocket connections manager
80
+ * interface WebSocketConfig {
81
+ * readonly url: WebSocketUrl;
82
+ * }
83
+ *
84
+ * type WebSocketUrl = string & Brand<"WebSocketUrl">;
85
+ * type UserId = string & Brand<"UserId">;
86
+ *
87
+ * const wsManager = createRefCountedResourceManager<
88
+ * WebSocket,
89
+ * WebSocketUrl,
90
+ * WebSocketConfig,
91
+ * User,
92
+ * UserId
93
+ * >({
94
+ * createResource: (config) => new WebSocket(config.url),
95
+ * getResourceKey: (config) => config.url,
96
+ * getConsumerId: (user) => user.id,
97
+ * disposalDelay: 1000,
98
+ * });
99
+ *
100
+ * // Add users to WebSocket connections
101
+ * wsManager.addConsumer(user1, [
102
+ * { url: "ws://server1.com" as WebSocketUrl },
103
+ * { url: "ws://server2.com" as WebSocketUrl },
104
+ * ]);
105
+ * wsManager.addConsumer(user2, [
106
+ * { url: "ws://server1.com" as WebSocketUrl },
107
+ * ]);
108
+ *
109
+ * // Remove users - server1 stays alive (user2 still using it)
110
+ * wsManager.removeConsumer(user1, [
111
+ * { url: "ws://server1.com" as WebSocketUrl },
112
+ * { url: "ws://server2.com" as WebSocketUrl },
113
+ * ]);
114
+ *
115
+ * // server2 gets disposed after delay, server1 stays alive
116
+ * ```
117
+ */
118
+ export declare const createRefCountedResourceManager: <TResource extends Disposable, TResourceKey extends string, TResourceConfig, TConsumer, TConsumerId extends string>(config: ResourceManagerConfig<TResource, TResourceKey, TResourceConfig, TConsumer, TConsumerId>) => RefCountedResourceManager<TResource, TResourceKey, TResourceConfig, TConsumer, TConsumerId>;
119
+ //# sourceMappingURL=RefCountedResourceManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RefCountedResourceManager.d.ts","sourceRoot":"","sources":["../../src/RefCountedResourceManager.ts"],"names":[],"mappings":"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"}