1
+ mod utils;
2
+
3
+ use crate :: utils:: { current_time, random} ;
4
+
5
+ use serde:: { Serialize , Deserialize } ;
6
+ use wasm_bindgen:: prelude:: * ;
7
+
8
+ //// GLOBAL CONSTANTS ///////////////////////////////////////////////////////////////////////
9
+
10
+ /// TOTAL_BITS is the total number of bits that
11
+ /// the ID can have
12
+ const TOTAL_BITS : u64 = 64 ;
13
+
14
+ /// EPOCH_BITS is the total number of bits that
15
+ /// are occupied by the UNIX timestamp
16
+ const EPOCH_BITS : u64 = 42 ;
17
+
18
+ /// INSTANCE_ID_BITS is the total number of bits that
19
+ /// are occupied by the node id
20
+ const INSTANCE_ID_BITS : u64 = 12 ;
21
+
22
+ /// SEQUENCE_BITS is the total number of bits that
23
+ /// are occupied by the sequence ids
24
+ const SEQUENCE_BITS : u64 = 10 ;
25
+
26
+ const MAX_INSTANCE_ID : u16 = ( 1 << INSTANCE_ID_BITS ) - 1 ;
27
+ const MAX_SEQUENCE : u16 = ( 1 << SEQUENCE_BITS ) - 1 ;
28
+
29
+ //// CUSTOM TYPESCRIPT EXPORTS //////////////////////////////////////////////////////////////
30
+ #[ wasm_bindgen( typescript_custom_section) ]
31
+ const CUSTOM_TS : & ' static str = r#"
32
+ export interface SnowflakeOpts {
33
+ custom_epoch?: number;
34
+ instance_id?: number;
35
+ };
36
+ "# ;
37
+
38
+ //// HELPER TYPES //////////////////////////////////////////////////////////////////////////
39
+ #[ wasm_bindgen]
40
+ extern "C" {
41
+ #[ wasm_bindgen( typescript_type = "SnowflakeOpts" ) ]
42
+ pub type SnowflakeOpts ;
43
+ }
44
+
45
+ impl From < SnowflakeConfig > for SnowflakeOpts {
46
+ fn from ( cfg : SnowflakeConfig ) -> Self {
47
+ Self :: from ( JsValue :: from_serde ( & cfg) . unwrap ( ) )
48
+ }
49
+ }
50
+
51
+ #[ derive( Serialize , Deserialize ) ]
52
+ pub struct SnowflakeConfig {
53
+ pub custom_epoch : Option < u64 > ,
54
+ pub instance_id : Option < u16 > ,
55
+ }
56
+
57
+ //// CORE IMPLEMENTATION ///////////////////////////////////////////////////////////////////
58
+ #[ wasm_bindgen]
59
+ #[ derive( Debug ) ]
60
+ pub struct Snowflake {
61
+ last_timestamp : u64 ,
62
+ custom_epoch : u64 ,
63
+ sequence : u16 ,
64
+ instance_id : u16 ,
65
+ }
66
+
67
+ #[ wasm_bindgen]
68
+ impl Snowflake {
69
+ #[ wasm_bindgen( constructor) ]
70
+ /// Constructs a Snowflake object which stores method for generation
71
+ /// of a unique 64 bit time sortable ID
72
+ pub fn new ( opts : Option < SnowflakeOpts > ) -> Result < Snowflake , JsValue > {
73
+ match opts {
74
+ Some ( opts) => {
75
+ match opts. into_serde :: < SnowflakeConfig > ( ) {
76
+ Ok ( opts) => {
77
+ let epoch = opts. custom_epoch . unwrap_or_else ( ||current_time ( 0 ) ) ;
78
+ let instance_id = opts. instance_id . unwrap_or_else ( ||random ( MAX_INSTANCE_ID as f64 ) as u16 ) ;
79
+
80
+ // If passed instance ID is greater than the max then return error
81
+ if instance_id > MAX_INSTANCE_ID {
82
+ return Err ( JsValue :: from_str ( & format ! ( "instance_id must be between 0 and {}" , MAX_INSTANCE_ID ) ) ) ;
83
+ }
84
+
85
+ Ok ( Self {
86
+ last_timestamp : 0 ,
87
+ custom_epoch : epoch,
88
+ sequence : 0 ,
89
+ instance_id : instance_id & MAX_INSTANCE_ID ,
90
+ } )
91
+ }
92
+ Err ( _) => {
93
+ Err ( JsValue :: from_str ( "[NATIVE]: failed to parse object into SnowflakeOpts" ) )
94
+ }
95
+ }
96
+ }
97
+ None => {
98
+ Ok ( Self {
99
+ last_timestamp : 0 ,
100
+ custom_epoch : current_time ( 0 ) ,
101
+ sequence : 0 ,
102
+ instance_id : random ( MAX_INSTANCE_ID as f64 ) as u16 ,
103
+ } )
104
+ }
105
+ }
106
+ }
107
+
108
+ #[ wasm_bindgen( js_name = getUniqueID) ]
109
+ /// getUniqueID generates a 64 bit unique ID
110
+ ///
111
+ /// NOTE: This method is blocking in nature, the function also
112
+ /// has theorotical limit of generating 1,024,000 IDs/sec
113
+ pub fn get_unique_id ( & mut self ) -> u64 {
114
+ let mut current_timestamp = current_time ( self . custom_epoch ) ;
115
+
116
+ if current_timestamp == self . last_timestamp {
117
+ self . sequence = ( self . sequence + 1 ) & MAX_SEQUENCE ;
118
+
119
+ // If we have exhausted all of the sequence number as well
120
+ if self . sequence == 0 {
121
+ // Wait for roughly a millisecond
122
+ while current_time ( self . custom_epoch ) - current_timestamp < 1 { }
123
+
124
+ // Update timestamp by one
125
+ current_timestamp += 1 ;
126
+ }
127
+ } else {
128
+ // Reset the sequence
129
+ self . sequence = 0 ;
130
+ }
131
+
132
+ self . last_timestamp = current_timestamp;
133
+
134
+ let mut id: u64 = current_timestamp << ( TOTAL_BITS - EPOCH_BITS ) ;
135
+ id |= ( self . instance_id as u64 ) << ( TOTAL_BITS - EPOCH_BITS - INSTANCE_ID_BITS ) ;
136
+ id |= self . sequence as u64 ;
137
+
138
+ id
139
+ }
140
+
141
+ #[ wasm_bindgen( js_name = idFromTimestamp) ]
142
+ /// idFromTimestamp takes a UNIX timestamp without any offset
143
+ /// and returns an ID that has timestamp set to the given timestamp
144
+ pub fn id_from_timestamp ( & self , timestamp : f64 ) -> u64 {
145
+ let timestamp = timestamp. round ( ) as u64 - self . custom_epoch ;
146
+
147
+ let mut id: u64 = timestamp << ( TOTAL_BITS - EPOCH_BITS ) ;
148
+ id |= u64:: from ( self . instance_id ) << ( TOTAL_BITS - EPOCH_BITS - INSTANCE_ID_BITS ) ;
149
+
150
+ id
151
+ }
152
+
153
+ #[ wasm_bindgen( js_name = instanceID) ]
154
+ /// instanceID returns the current node id
155
+ pub fn instance_id ( & self ) -> f64 {
156
+ self . instance_id as f64
157
+ }
158
+
159
+ #[ wasm_bindgen( js_name = customEpoch) ]
160
+ /// customEpoch returns the current custom epoch
161
+ pub fn custom_epoch ( & self ) -> f64 {
162
+ self . custom_epoch as f64
163
+ }
164
+ }
165
+
166
+ #[ wasm_bindgen]
167
+ impl Snowflake {
168
+ #[ wasm_bindgen( js_name = timestampFromID) ]
169
+ /// timestampFromID takes a unique ID and returns the timestamp
170
+ /// when the Unique ID was created
171
+ pub fn timestamp_from_id ( unique_id : u64 , epoch_offset : f64 ) -> f64 {
172
+ ( ( unique_id >> ( TOTAL_BITS - EPOCH_BITS ) ) as f64 ) + ( epoch_offset as f64 )
173
+ }
174
+
175
+ #[ wasm_bindgen( js_name = instanceIDFromID) ]
176
+ /// instanceIDFromID takes a unique ID and returns the instance
177
+ /// ID where the unique ID was created
178
+ ///
179
+ /// NOTE: The unique ID could be created on ANY instance
180
+ pub fn instance_id_from_id ( unique_id : u64 ) -> i32 {
181
+ let bits = TOTAL_BITS - INSTANCE_ID_BITS - SEQUENCE_BITS ;
182
+ ( ( unique_id << bits) >> ( bits + SEQUENCE_BITS ) ) as i32
183
+ }
184
+ }
0 commit comments