Class: DiscordStore::Log::Record

Inherits:
Object
  • Object
show all
Defined in:
lib/discord_store/log/record.rb

Overview

One entry in the log.

lsn is the Discord message ID, which is a snowflake, which means the log sequence number is free, globally unique, and carries its own timestamp. position disambiguates records packed into the same message.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(stream:, data:, nonce: nil, lsn: nil, position: 0, channel_id: nil) ⇒ Record

Returns a new instance of Record.

Parameters:

  • stream (String) —

    logical topic; also the partition key

  • data (Hash) —

    anything JSON can represent

  • nonce (String) (defaults to: nil) —

    idempotency key; Discord deduplicates on it



18
19
20
21
22
23
24
25
# File 'lib/discord_store/log/record.rb', line 18

def initialize(stream:, data:, nonce: nil, lsn: nil, position: 0, channel_id: nil)
  @stream = stream.to_s
  @data = data
  @nonce = nonce || SecureRandom.uuid
  @lsn = lsn&.to_s
  @position = position
  @channel_id = channel_id&.to_s
end

Instance Attribute Details

#channel_id ⇒ Object (readonly)

Returns the value of attribute channel_id.



13
14
15
# File 'lib/discord_store/log/record.rb', line 13

def channel_id
  @channel_id
end

#data ⇒ Object (readonly)

Returns the value of attribute data.



13
14
15
# File 'lib/discord_store/log/record.rb', line 13

def data
  @data
end

#lsn ⇒ Object (readonly)

Returns the value of attribute lsn.



13
14
15
# File 'lib/discord_store/log/record.rb', line 13

def lsn
  @lsn
end

#nonce ⇒ Object (readonly)

Returns the value of attribute nonce.



13
14
15
# File 'lib/discord_store/log/record.rb', line 13

def nonce
  @nonce
end

#position ⇒ Object (readonly)

Returns the value of attribute position.



13
14
15
# File 'lib/discord_store/log/record.rb', line 13

def position
  @position
end

#stream ⇒ Object (readonly)

Returns the value of attribute stream.



13
14
15
# File 'lib/discord_store/log/record.rb', line 13

def stream
  @stream
end

Class Method Details

.from_wire(hash, lsn:, position:, channel_id: nil) ⇒ Record

Parameters:

Returns:



56
57
58
59
60
61
62
63
# File 'lib/discord_store/log/record.rb', line 56

def self.from_wire(hash, lsn:, position:, channel_id: nil)
  unless hash.is_a?(Hash) && hash.key?("s")
    raise CorruptRecordError, "record at #{lsn}:#{position} is not a discord_store record"
  end

  new(stream: hash["s"], data: hash["d"], nonce: hash["n"],
      lsn: lsn, position: position, channel_id: channel_id)
end

.tombstone(stream:, target:) ⇒ Record

A record that marks target deleted. The log never rewrites history, so a delete is an append like everything else.

Parameters:

  • stream (String)
  • target (String) —

    the cursor or key being retired

Returns:



71
72
73
# File 'lib/discord_store/log/record.rb', line 71

def self.tombstone(stream:, target:)
  new(stream: stream, data: { "__tombstone" => true, "target" => target.to_s })
end

Instance Method Details

#==(other) ⇒ Object Also known as: eql?



75
76
77
# File 'lib/discord_store/log/record.rb', line 75

def ==(other)
  other.is_a?(Record) && other.stream == stream && other.data == data && other.nonce == nonce
end

#created_at ⇒ Time?

When Discord accepted this record. Read straight out of the snowflake; no created_at column required.

Returns:

  • (Time, nil)


31
32
33
# File 'lib/discord_store/log/record.rb', line 31

def created_at
  @lsn && Snowflake.at(@lsn)
end

#cursor ⇒ String?

A cursor that orders correctly within a partition.

Returns:

  • (String, nil)


38
39
40
# File 'lib/discord_store/log/record.rb', line 38

def cursor
  @lsn && format("%<lsn>s:%<position>04d", lsn: @lsn, position: @position)
end

#hash ⇒ Object



80
# File 'lib/discord_store/log/record.rb', line 80

def hash = [stream, data, nonce].hash

#inspect ⇒ Object



82
83
84
85
# File 'lib/discord_store/log/record.rb', line 82

def inspect
  "#<DiscordStore::Log::Record stream=#{stream.inspect} lsn=#{lsn.inspect} " \
    "position=#{position} data=#{data.inspect}>"
end

#to_wire ⇒ Hash

Wire form. Keys are short because every byte competes for the 2000 characters a message can hold, and a shorter key is one more record per request.

Returns:

  • (Hash)


50
51
52
# File 'lib/discord_store/log/record.rb', line 50

def to_wire
  { "s" => stream, "n" => nonce, "d" => data }
end

#tombstone? ⇒ Boolean

Returns whether this record marks a deleted predecessor.

Returns:

  • (Boolean) —

    whether this record marks a deleted predecessor



43
# File 'lib/discord_store/log/record.rb', line 43

def tombstone? = data.is_a?(Hash) && data["__tombstone"] == true