janus-ledger 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 142a0c0c0e6fc368f3ddda5cce6581842a7bb32c401d189319c8699089f0622a
4
+ data.tar.gz: 32a84c4fed548d0b65f7d402bcd92fda15ec2ba2d895b28e2630b699bed37bba
5
+ SHA512:
6
+ metadata.gz: 0c8b2b32be9600cc9816019c3d2a5dd0e94f0b084e4255045ac2d895d17718d5ae9e901cb050cdb90ac44a78dbc41ca119f7fad4633ca6773a0a21519f71b3d8
7
+ data.tar.gz: 478722f651cb772c110cb64a41818d06f2299ccb4ea35e4c2dc2cfc5fc019ef8925e95e6d309b13c058d4fe8384b2224ae4138e9ed4c5cae416b084c5372f329
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
3
+ # source: janus.proto
4
+
5
+ require 'google/protobuf'
6
+
7
+
8
+ descriptor_data = "\n\x0bjanus.proto\x12\x08janus.v1\"+\n\tTimestamp\x12\x0f\n\x07seconds\x18\x01 \x01(\x03\x12\r\n\x05nanos\x18\x02 \x01(\x05\"\x96\x02\n\x06\x45ntity\x12\n\n\x02id\x18\x01 \x01(\t\x12\x13\n\x0b\x65xternal_id\x18\x02 \x01(\t\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x04 \x01(\tH\x00\x88\x01\x01\x12\x30\n\x08metadata\x18\x05 \x03(\x0b\x32\x1e.janus.v1.Entity.MetadataEntry\x12\'\n\ncreated_at\x18\x06 \x01(\x0b\x32\x13.janus.v1.Timestamp\x12\'\n\nupdated_at\x18\x07 \x01(\x0b\x32\x13.janus.v1.Timestamp\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x42\x0e\n\x0c_description\"\xd2\x01\n\x13\x43reateEntityRequest\x12\x13\n\x0b\x65xternal_id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x12=\n\x08metadata\x18\x04 \x03(\x0b\x32+.janus.v1.CreateEntityRequest.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x42\x0e\n\x0c_description\"8\n\x14\x43reateEntityResponse\x12 \n\x06\x65ntity\x18\x01 \x01(\x0b\x32\x10.janus.v1.Entity\"-\n\x10GetEntityRequest\x12\x19\n\x11\x65xternal_id_or_id\x18\x01 \x01(\t\"E\n\x11GetEntityResponse\x12%\n\x06\x65ntity\x18\x01 \x01(\x0b\x32\x10.janus.v1.EntityH\x00\x88\x01\x01\x42\t\n\x07_entity\"\x15\n\x13ListEntitiesRequest\":\n\x14ListEntitiesResponse\x12\"\n\x08\x65ntities\x18\x01 \x03(\x0b\x32\x10.janus.v1.Entity\"\xbf\x02\n\x04\x42ook\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x0c\n\x04type\x18\x04 \x01(\t\x12\x10\n\x08\x63urrency\x18\x05 \x01(\t\x12\x0f\n\x07\x62\x61lance\x18\x06 \x01(\x03\x12.\n\x08metadata\x18\x08 \x03(\x0b\x32\x1c.janus.v1.Book.MetadataEntry\x12\'\n\ncreated_at\x18\t \x01(\x0b\x32\x13.janus.v1.Timestamp\x12\'\n\nupdated_at\x18\n \x01(\x0b\x32\x13.janus.v1.Timestamp\x12\x11\n\tentity_id\x18\x0b \x01(\t\x12\x1a\n\x12\x65ntity_external_id\x18\x0c \x01(\t\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01J\x04\x08\x02\x10\x03J\x04\x08\x07\x10\x08\"\xd7\x01\n\x11\x43reateBookRequest\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0c\n\x04type\x18\x03 \x01(\t\x12\x10\n\x08\x63urrency\x18\x04 \x01(\t\x12;\n\x08metadata\x18\x06 \x03(\x0b\x32).janus.v1.CreateBookRequest.MetadataEntry\x12\x1a\n\x12\x65ntity_external_id\x18\x07 \x01(\t\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01J\x04\x08\x01\x10\x02J\x04\x08\x05\x10\x06\"2\n\x12\x43reateBookResponse\x12\x1c\n\x04\x62ook\x18\x01 \x01(\x0b\x32\x0e.janus.v1.Book\"$\n\x0eGetBookRequest\x12\x12\n\ntype_or_id\x18\x01 \x01(\t\"=\n\x0fGetBookResponse\x12!\n\x04\x62ook\x18\x01 \x01(\x0b\x32\x0e.janus.v1.BookH\x00\x88\x01\x01\x42\x07\n\x05_book\"\x8b\x01\n\x10ListBooksRequest\x12\x11\n\x04type\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\r\n\x05limit\x18\x03 \x01(\x05\x12\x0e\n\x06offset\x18\x04 \x01(\x05\x12\x1f\n\x12\x65ntity_external_id\x18\x05 \x01(\tH\x01\x88\x01\x01\x42\x07\n\x05_typeB\x15\n\x13_entity_external_idJ\x04\x08\x02\x10\x03\"2\n\x11ListBooksResponse\x12\x1d\n\x05\x62ooks\x18\x01 \x03(\x0b\x32\x0e.janus.v1.Book\"\xc7\x01\n\x0e\x42ookEntryInput\x12\x0c\n\x04\x62ook\x18\x01 \x01(\t\x12\x12\n\x05\x64\x65\x62it\x18\x02 \x01(\x03H\x00\x88\x01\x01\x12\x13\n\x06\x63redit\x18\x03 \x01(\x03H\x01\x88\x01\x01\x12\x38\n\x08metadata\x18\x04 \x03(\x0b\x32&.janus.v1.BookEntryInput.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x42\x08\n\x06_debitB\t\n\x07_credit\"\x9c\x02\n\x19\x43reateJournalEntryRequest\x12\x17\n\x0fidempotency_key\x18\x01 \x01(\t\x12\x1b\n\x0e\x65\x66\x66\x65\x63tive_date\x18\x04 \x01(\tH\x00\x88\x01\x01\x12.\n\x0c\x62ook_entries\x18\x05 \x03(\x0b\x32\x18.janus.v1.BookEntryInput\x12\x43\n\x08metadata\x18\x06 \x03(\x0b\x32\x31.janus.v1.CreateJournalEntryRequest.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x42\x11\n\x0f_effective_dateJ\x04\x08\x02\x10\x03J\x04\x08\x03\x10\x04J\x04\x08\x07\x10\x08\"\x89\x03\n\x0cJournalEntry\x12\n\n\x02id\x18\x01 \x01(\t\x12\x17\n\x0fidempotency_key\x18\x02 \x01(\t\x12\x16\n\x0e\x65\x66\x66\x65\x63tive_date\x18\x05 \x01(\t\x12\x36\n\x08metadata\x18\x07 \x03(\x0b\x32$.janus.v1.JournalEntry.MetadataEntry\x12\'\n\ncreated_at\x18\x08 \x01(\x0b\x32\x13.janus.v1.Timestamp\x12-\n\x0breversed_at\x18\n \x01(\x0b\x32\x13.janus.v1.TimestampH\x00\x88\x01\x01\x12\x18\n\x0breverses_id\x18\x0b \x01(\tH\x01\x88\x01\x01\x12)\n\x0c\x62ook_entries\x18\x0c \x03(\x0b\x32\x13.janus.v1.BookEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x42\x0e\n\x0c_reversed_atB\x0e\n\x0c_reverses_idJ\x04\x08\x03\x10\x04J\x04\x08\x04\x10\x05J\x04\x08\x06\x10\x07J\x04\x08\t\x10\n\"\xa9\x01\n\tBookEntry\x12\n\n\x02id\x18\x01 \x01(\t\x12\x11\n\tbook_name\x18\x03 \x01(\t\x12\x12\n\x05\x64\x65\x62it\x18\x04 \x01(\x03H\x00\x88\x01\x01\x12\x13\n\x06\x63redit\x18\x05 \x01(\x03H\x01\x88\x01\x01\x12\x10\n\x08\x63urrency\x18\x06 \x01(\t\x12\x14\n\x0c\x62ook_balance\x18\x07 \x01(\x03\x12\x11\n\tentity_id\x18\x08 \x01(\tB\x08\n\x06_debitB\t\n\x07_creditJ\x04\x08\x02\x10\x03\"K\n\x1a\x43reateJournalEntryResponse\x12-\n\rjournal_entry\x18\x01 \x01(\x0b\x32\x16.janus.v1.JournalEntry\"g\n\x1aReverseJournalEntryRequest\x12\x10\n\x08\x65ntry_id\x18\x01 \x01(\t\x12\x17\n\x0fidempotency_key\x18\x02 \x01(\t\x12\x13\n\x06reason\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\t\n\x07_reason\"L\n\x1bReverseJournalEntryResponse\x12-\n\rjournal_entry\x18\x01 \x01(\x0b\x32\x16.janus.v1.JournalEntry\"$\n\x16GetJournalEntryRequest\x12\n\n\x02id\x18\x01 \x01(\t\"6\n\x1bGetJournalEntryByKeyRequest\x12\x17\n\x0fidempotency_key\x18\x01 \x01(\t\"_\n\x17GetJournalEntryResponse\x12\x32\n\rjournal_entry\x18\x01 \x01(\x0b\x32\x16.janus.v1.JournalEntryH\x00\x88\x01\x01\x42\x10\n\x0e_journal_entry\"T\n\x11GetBalanceRequest\x12\x0c\n\x04\x62ook\x18\x01 \x01(\t\x12\'\n\x05\x61s_of\x18\x02 \x01(\x0b\x32\x13.janus.v1.TimestampH\x00\x88\x01\x01\x42\x08\n\x06_as_of\"\x7f\n\x12GetBalanceResponse\x12\x0f\n\x07\x62ook_id\x18\x01 \x01(\t\x12\x11\n\tbook_name\x18\x02 \x01(\t\x12\x0f\n\x07\x62\x61lance\x18\x03 \x01(\x03\x12\x10\n\x08\x63urrency\x18\x04 \x01(\t\x12\"\n\x05\x61s_of\x18\x05 \x01(\x0b\x32\x13.janus.v1.Timestamp\"V\n\x12GetBalancesRequest\x12\r\n\x05\x62ooks\x18\x01 \x03(\t\x12\'\n\x05\x61s_of\x18\x02 \x01(\x0b\x32\x13.janus.v1.TimestampH\x00\x88\x01\x01\x42\x08\n\x06_as_of\"E\n\x13GetBalancesResponse\x12.\n\x08\x62\x61lances\x18\x01 \x03(\x0b\x32\x1c.janus.v1.GetBalanceResponse2\xd5\x07\n\x0cJanusService\x12M\n\x0c\x43reateEntity\x12\x1d.janus.v1.CreateEntityRequest\x1a\x1e.janus.v1.CreateEntityResponse\x12\x44\n\tGetEntity\x12\x1a.janus.v1.GetEntityRequest\x1a\x1b.janus.v1.GetEntityResponse\x12M\n\x0cListEntities\x12\x1d.janus.v1.ListEntitiesRequest\x1a\x1e.janus.v1.ListEntitiesResponse\x12G\n\nCreateBook\x12\x1b.janus.v1.CreateBookRequest\x1a\x1c.janus.v1.CreateBookResponse\x12>\n\x07GetBook\x12\x18.janus.v1.GetBookRequest\x1a\x19.janus.v1.GetBookResponse\x12\x44\n\tListBooks\x12\x1a.janus.v1.ListBooksRequest\x1a\x1b.janus.v1.ListBooksResponse\x12_\n\x12\x43reateJournalEntry\x12#.janus.v1.CreateJournalEntryRequest\x1a$.janus.v1.CreateJournalEntryResponse\x12\x62\n\x13ReverseJournalEntry\x12$.janus.v1.ReverseJournalEntryRequest\x1a%.janus.v1.ReverseJournalEntryResponse\x12V\n\x0fGetJournalEntry\x12 .janus.v1.GetJournalEntryRequest\x1a!.janus.v1.GetJournalEntryResponse\x12`\n\x14GetJournalEntryByKey\x12%.janus.v1.GetJournalEntryByKeyRequest\x1a!.janus.v1.GetJournalEntryResponse\x12G\n\nGetBalance\x12\x1b.janus.v1.GetBalanceRequest\x1a\x1c.janus.v1.GetBalanceResponse\x12J\n\x0bGetBalances\x12\x1c.janus.v1.GetBalancesRequest\x1a\x1d.janus.v1.GetBalancesResponseb\x06proto3"
9
+
10
+ pool = ::Google::Protobuf::DescriptorPool.generated_pool
11
+ pool.add_serialized_file(descriptor_data)
12
+
13
+ module Janus
14
+ module V1
15
+ Timestamp = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("janus.v1.Timestamp").msgclass
16
+ Entity = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("janus.v1.Entity").msgclass
17
+ CreateEntityRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("janus.v1.CreateEntityRequest").msgclass
18
+ CreateEntityResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("janus.v1.CreateEntityResponse").msgclass
19
+ GetEntityRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("janus.v1.GetEntityRequest").msgclass
20
+ GetEntityResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("janus.v1.GetEntityResponse").msgclass
21
+ ListEntitiesRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("janus.v1.ListEntitiesRequest").msgclass
22
+ ListEntitiesResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("janus.v1.ListEntitiesResponse").msgclass
23
+ Book = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("janus.v1.Book").msgclass
24
+ CreateBookRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("janus.v1.CreateBookRequest").msgclass
25
+ CreateBookResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("janus.v1.CreateBookResponse").msgclass
26
+ GetBookRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("janus.v1.GetBookRequest").msgclass
27
+ GetBookResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("janus.v1.GetBookResponse").msgclass
28
+ ListBooksRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("janus.v1.ListBooksRequest").msgclass
29
+ ListBooksResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("janus.v1.ListBooksResponse").msgclass
30
+ BookEntryInput = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("janus.v1.BookEntryInput").msgclass
31
+ CreateJournalEntryRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("janus.v1.CreateJournalEntryRequest").msgclass
32
+ JournalEntry = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("janus.v1.JournalEntry").msgclass
33
+ BookEntry = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("janus.v1.BookEntry").msgclass
34
+ CreateJournalEntryResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("janus.v1.CreateJournalEntryResponse").msgclass
35
+ ReverseJournalEntryRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("janus.v1.ReverseJournalEntryRequest").msgclass
36
+ ReverseJournalEntryResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("janus.v1.ReverseJournalEntryResponse").msgclass
37
+ GetJournalEntryRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("janus.v1.GetJournalEntryRequest").msgclass
38
+ GetJournalEntryByKeyRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("janus.v1.GetJournalEntryByKeyRequest").msgclass
39
+ GetJournalEntryResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("janus.v1.GetJournalEntryResponse").msgclass
40
+ GetBalanceRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("janus.v1.GetBalanceRequest").msgclass
41
+ GetBalanceResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("janus.v1.GetBalanceResponse").msgclass
42
+ GetBalancesRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("janus.v1.GetBalancesRequest").msgclass
43
+ GetBalancesResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("janus.v1.GetBalancesResponse").msgclass
44
+ end
45
+ end
@@ -0,0 +1,42 @@
1
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
2
+ # Source: janus.proto for package 'janus.v1'
3
+ # Original file comments:
4
+ # JanusService provides double-entry accounting operations
5
+
6
+ require 'grpc'
7
+ require_relative 'janus_pb'
8
+
9
+ module Janus
10
+ module V1
11
+ module JanusService
12
+ # JanusService provides double-entry accounting operations
13
+ class Service
14
+
15
+ include ::GRPC::GenericService
16
+
17
+ self.marshal_class_method = :encode
18
+ self.unmarshal_class_method = :decode
19
+ self.service_name = 'janus.v1.JanusService'
20
+
21
+ # Entities
22
+ rpc :CreateEntity, ::Janus::V1::CreateEntityRequest, ::Janus::V1::CreateEntityResponse
23
+ rpc :GetEntity, ::Janus::V1::GetEntityRequest, ::Janus::V1::GetEntityResponse
24
+ rpc :ListEntities, ::Janus::V1::ListEntitiesRequest, ::Janus::V1::ListEntitiesResponse
25
+ # Books
26
+ rpc :CreateBook, ::Janus::V1::CreateBookRequest, ::Janus::V1::CreateBookResponse
27
+ rpc :GetBook, ::Janus::V1::GetBookRequest, ::Janus::V1::GetBookResponse
28
+ rpc :ListBooks, ::Janus::V1::ListBooksRequest, ::Janus::V1::ListBooksResponse
29
+ # Journal Entries
30
+ rpc :CreateJournalEntry, ::Janus::V1::CreateJournalEntryRequest, ::Janus::V1::CreateJournalEntryResponse
31
+ rpc :ReverseJournalEntry, ::Janus::V1::ReverseJournalEntryRequest, ::Janus::V1::ReverseJournalEntryResponse
32
+ rpc :GetJournalEntry, ::Janus::V1::GetJournalEntryRequest, ::Janus::V1::GetJournalEntryResponse
33
+ rpc :GetJournalEntryByKey, ::Janus::V1::GetJournalEntryByKeyRequest, ::Janus::V1::GetJournalEntryResponse
34
+ # Balances
35
+ rpc :GetBalance, ::Janus::V1::GetBalanceRequest, ::Janus::V1::GetBalanceResponse
36
+ rpc :GetBalances, ::Janus::V1::GetBalancesRequest, ::Janus::V1::GetBalancesResponse
37
+ end
38
+
39
+ Stub = Service.rpc_stub_class
40
+ end
41
+ end
42
+ end
data/lib/janus.rb ADDED
@@ -0,0 +1,270 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Janus -- Ruby gRPC Client
4
+ #
5
+ # Connects to a remote janus-server via gRPC.
6
+ #
7
+ # @example Connect to a gRPC server
8
+ # client = Janus.new(server_url: 'localhost:50051')
9
+ #
10
+ # @example With TLS
11
+ # client = Janus.new(server_url: 'janus.example.com:443', secure: true)
12
+ #
13
+ module Janus
14
+ VERSION = '0.8.0'
15
+
16
+ Entity = Struct.new(
17
+ :id, :external_id, :name, :description, :metadata, :created_at, :updated_at,
18
+ keyword_init: true
19
+ )
20
+
21
+ Book = Struct.new(
22
+ :id, :entity_id, :name, :book_type, :currency,
23
+ :metadata, :created_at, :updated_at,
24
+ keyword_init: true
25
+ )
26
+
27
+ BookEntry = Struct.new(
28
+ :id, :book_name, :debit, :credit, :currency,
29
+ :book_balance, :entity_id,
30
+ keyword_init: true
31
+ )
32
+
33
+ JournalEntry = Struct.new(
34
+ :id, :idempotency_key, :effective_date, :metadata,
35
+ :created_at, :reversed_at, :reversed_by_id, :reverses_id,
36
+ :book_entries,
37
+ keyword_init: true
38
+ )
39
+
40
+ BalanceResult = Struct.new(
41
+ :book_id, :book_name, :balance, :currency, :as_of,
42
+ keyword_init: true
43
+ )
44
+
45
+ # Money helpers
46
+ module Money
47
+ def self.cents(amount, currency = 'USD')
48
+ { amount: amount, currency: currency }
49
+ end
50
+
51
+ def self.from_decimal(amount, currency = 'USD', decimals = 2)
52
+ multiplier = 10**decimals
53
+ { amount: (amount * multiplier).round, currency: currency }
54
+ end
55
+
56
+ def self.to_decimal(amount, decimals = 2)
57
+ divisor = 10**decimals
58
+ amount.to_f / divisor
59
+ end
60
+ end
61
+
62
+ # gRPC-only Janus client
63
+ class Client
64
+ def initialize(server_url:, secure: false)
65
+ require 'grpc'
66
+ require 'json'
67
+ require_relative 'janus/_generated/janus_pb'
68
+ require_relative 'janus/_generated/janus_services_pb'
69
+
70
+ credentials = secure ? :this_channel_is_secure : :this_channel_is_insecure
71
+ @stub = ::Janus::V1::JanusService::Stub.new(server_url, credentials)
72
+ end
73
+
74
+ # --- Entity methods ---
75
+
76
+ def create_entity(external_id:, name:, description: nil, metadata: nil)
77
+ meta = metadata ? JSON.parse(metadata) : {}
78
+ req = ::Janus::V1::CreateEntityRequest.new(
79
+ external_id: external_id,
80
+ name: name,
81
+ metadata: meta
82
+ )
83
+ req.description = description if description
84
+ resp = @stub.create_entity(req)
85
+ map_entity(resp.entity)
86
+ end
87
+
88
+ def get_entity(external_id_or_id:)
89
+ resp = @stub.get_entity(
90
+ ::Janus::V1::GetEntityRequest.new(external_id_or_id: external_id_or_id)
91
+ )
92
+ return nil unless resp.entity
93
+ map_entity(resp.entity)
94
+ end
95
+
96
+ def list_entities
97
+ resp = @stub.list_entities(::Janus::V1::ListEntitiesRequest.new)
98
+ resp.entities.map { |e| map_entity(e) }
99
+ end
100
+
101
+ # --- Book methods ---
102
+
103
+ def create_book(name:, book_type:, entity_id:, currency: nil, metadata: nil)
104
+ meta = metadata ? JSON.parse(metadata) : {}
105
+ req = ::Janus::V1::CreateBookRequest.new(
106
+ entity_external_id: entity_id,
107
+ name: name,
108
+ type: book_type,
109
+ currency: currency || 'USD',
110
+ metadata: meta
111
+ )
112
+ resp = @stub.create_book(req)
113
+ map_book(resp.book)
114
+ end
115
+
116
+ def get_book(name_or_id:)
117
+ resp = @stub.get_book(
118
+ ::Janus::V1::GetBookRequest.new(type_or_id: name_or_id)
119
+ )
120
+ return nil unless resp.book
121
+ map_book(resp.book)
122
+ end
123
+
124
+ def list_books(book_type: nil, entity_id: nil)
125
+ req = ::Janus::V1::ListBooksRequest.new
126
+ req.type = book_type if book_type
127
+ req.entity_external_id = entity_id if entity_id
128
+ resp = @stub.list_books(req)
129
+ resp.books.map { |b| map_book(b) }
130
+ end
131
+
132
+ # --- Journal Entry methods ---
133
+
134
+ def create_journal_entry(idempotency_key:, book_entries_json:, effective_date: nil, metadata: nil)
135
+ entries = JSON.parse(book_entries_json)
136
+ pb_entries = entries.map do |e|
137
+ entry = ::Janus::V1::BookEntryInput.new(book: e['book'], metadata: e.fetch('metadata', {}))
138
+ entry.debit = e['debit'] if e.key?('debit') && !e['debit'].nil?
139
+ entry.credit = e['credit'] if e.key?('credit') && !e['credit'].nil?
140
+ entry
141
+ end
142
+
143
+ meta = metadata ? JSON.parse(metadata) : {}
144
+ req = ::Janus::V1::CreateJournalEntryRequest.new(
145
+ idempotency_key: idempotency_key,
146
+ book_entries: pb_entries,
147
+ metadata: meta
148
+ )
149
+ req.effective_date = effective_date if effective_date
150
+ resp = @stub.create_journal_entry(req)
151
+ map_journal_entry(resp.journal_entry)
152
+ end
153
+
154
+ def get_journal_entry(id:)
155
+ resp = @stub.get_journal_entry(
156
+ ::Janus::V1::GetJournalEntryRequest.new(id: id)
157
+ )
158
+ return nil unless resp.journal_entry
159
+ map_journal_entry(resp.journal_entry)
160
+ end
161
+
162
+ def get_journal_entry_by_key(key:)
163
+ resp = @stub.get_journal_entry_by_key(
164
+ ::Janus::V1::GetJournalEntryByKeyRequest.new(idempotency_key: key)
165
+ )
166
+ return nil unless resp.journal_entry
167
+ map_journal_entry(resp.journal_entry)
168
+ end
169
+
170
+ def reverse_journal_entry(entry_id:, idempotency_key:, reason: nil)
171
+ req = ::Janus::V1::ReverseJournalEntryRequest.new(
172
+ entry_id: entry_id,
173
+ idempotency_key: idempotency_key
174
+ )
175
+ req.reason = reason if reason
176
+ resp = @stub.reverse_journal_entry(req)
177
+ map_journal_entry(resp.journal_entry)
178
+ end
179
+
180
+ # --- Balance methods ---
181
+
182
+ def get_balance(book:)
183
+ resp = @stub.get_balance(
184
+ ::Janus::V1::GetBalanceRequest.new(book: book)
185
+ )
186
+ map_balance(resp)
187
+ end
188
+
189
+ def get_balances(books:)
190
+ resp = @stub.get_balances(
191
+ ::Janus::V1::GetBalancesRequest.new(books: books)
192
+ )
193
+ resp.balances.map { |b| map_balance(b) }
194
+ end
195
+
196
+ # --- Lifecycle ---
197
+
198
+ def close
199
+ # no-op for gRPC (channel managed by grpc gem)
200
+ end
201
+
202
+ private
203
+
204
+ def map_entity(e)
205
+ Entity.new(
206
+ id: e.id,
207
+ external_id: e.external_id,
208
+ name: e.name,
209
+ description: e.description.empty? ? nil : e.description,
210
+ metadata: e.metadata.to_h.to_s,
211
+ created_at: e.created_at ? e.created_at.seconds.to_s : '',
212
+ updated_at: e.updated_at ? e.updated_at.seconds.to_s : ''
213
+ )
214
+ end
215
+
216
+ def map_book(b)
217
+ Book.new(
218
+ id: b.id,
219
+ entity_id: b.entity_id,
220
+ name: b.name,
221
+ book_type: b.type.empty? ? 'asset' : b.type,
222
+ currency: b.currency,
223
+ metadata: b.metadata.to_h.to_s,
224
+ created_at: b.created_at ? b.created_at.seconds.to_s : '',
225
+ updated_at: b.updated_at ? b.updated_at.seconds.to_s : ''
226
+ )
227
+ end
228
+
229
+ def map_book_entry(be)
230
+ BookEntry.new(
231
+ id: be.id,
232
+ book_name: be.book_name,
233
+ debit: be.respond_to?(:has_debit?) && be.has_debit? ? be.debit : nil,
234
+ credit: be.respond_to?(:has_credit?) && be.has_credit? ? be.credit : nil,
235
+ currency: be.currency,
236
+ book_balance: be.book_balance,
237
+ entity_id: be.entity_id.empty? ? nil : be.entity_id
238
+ )
239
+ end
240
+
241
+ def map_journal_entry(je)
242
+ JournalEntry.new(
243
+ id: je.id,
244
+ idempotency_key: je.idempotency_key,
245
+ effective_date: je.effective_date,
246
+ metadata: je.metadata.to_h.to_s,
247
+ created_at: je.created_at ? je.created_at.seconds.to_s : '',
248
+ reversed_at: je.reversed_at ? je.reversed_at.seconds.to_s : nil,
249
+ reversed_by_id: nil,
250
+ reverses_id: je.respond_to?(:has_reverses_id?) && je.has_reverses_id? ? je.reverses_id : nil,
251
+ book_entries: je.book_entries.map { |be| map_book_entry(be) }
252
+ )
253
+ end
254
+
255
+ def map_balance(b)
256
+ BalanceResult.new(
257
+ book_id: b.book_id,
258
+ book_name: b.book_name,
259
+ balance: b.balance,
260
+ currency: b.currency,
261
+ as_of: b.as_of ? b.as_of.seconds.to_s : ''
262
+ )
263
+ end
264
+ end
265
+
266
+ # Module-level constructor for convenience
267
+ def self.new(server_url:, secure: false)
268
+ Client.new(server_url: server_url, secure: secure)
269
+ end
270
+ end
data/proto/janus.proto ADDED
@@ -0,0 +1,207 @@
1
+ syntax = "proto3";
2
+
3
+ package janus.v1;
4
+
5
+ // JanusService provides double-entry accounting operations
6
+ service JanusService {
7
+ // Entities
8
+ rpc CreateEntity(CreateEntityRequest) returns (CreateEntityResponse);
9
+ rpc GetEntity(GetEntityRequest) returns (GetEntityResponse);
10
+ rpc ListEntities(ListEntitiesRequest) returns (ListEntitiesResponse);
11
+
12
+ // Books
13
+ rpc CreateBook(CreateBookRequest) returns (CreateBookResponse);
14
+ rpc GetBook(GetBookRequest) returns (GetBookResponse);
15
+ rpc ListBooks(ListBooksRequest) returns (ListBooksResponse);
16
+
17
+ // Journal Entries
18
+ rpc CreateJournalEntry(CreateJournalEntryRequest) returns (CreateJournalEntryResponse);
19
+ rpc ReverseJournalEntry(ReverseJournalEntryRequest) returns (ReverseJournalEntryResponse);
20
+ rpc GetJournalEntry(GetJournalEntryRequest) returns (GetJournalEntryResponse);
21
+ rpc GetJournalEntryByKey(GetJournalEntryByKeyRequest) returns (GetJournalEntryResponse);
22
+
23
+ // Balances
24
+ rpc GetBalance(GetBalanceRequest) returns (GetBalanceResponse);
25
+ rpc GetBalances(GetBalancesRequest) returns (GetBalancesResponse);
26
+ }
27
+
28
+ // Common types
29
+ message Timestamp {
30
+ int64 seconds = 1;
31
+ int32 nanos = 2;
32
+ }
33
+
34
+ // Entity messages
35
+ message Entity {
36
+ string id = 1;
37
+ string external_id = 2;
38
+ string name = 3;
39
+ optional string description = 4;
40
+ map<string, string> metadata = 5;
41
+ Timestamp created_at = 6;
42
+ Timestamp updated_at = 7;
43
+ }
44
+
45
+ message CreateEntityRequest {
46
+ string external_id = 1;
47
+ string name = 2;
48
+ optional string description = 3;
49
+ map<string, string> metadata = 4;
50
+ }
51
+
52
+ message CreateEntityResponse {
53
+ Entity entity = 1;
54
+ }
55
+
56
+ message GetEntityRequest {
57
+ string external_id_or_id = 1;
58
+ }
59
+
60
+ message GetEntityResponse {
61
+ optional Entity entity = 1;
62
+ }
63
+
64
+ message ListEntitiesRequest {
65
+ }
66
+
67
+ message ListEntitiesResponse {
68
+ repeated Entity entities = 1;
69
+ }
70
+
71
+ // Book messages
72
+ message Book {
73
+ string id = 1;
74
+ reserved 2; // removed: code
75
+ string name = 3;
76
+ string type = 4;
77
+ string currency = 5;
78
+ int64 balance = 6;
79
+ reserved 7; // removed: parent_id
80
+ map<string, string> metadata = 8;
81
+ Timestamp created_at = 9;
82
+ Timestamp updated_at = 10;
83
+ string entity_id = 11;
84
+ string entity_external_id = 12;
85
+ }
86
+
87
+ message CreateBookRequest {
88
+ reserved 1; // removed: code
89
+ string name = 2;
90
+ string type = 3;
91
+ string currency = 4;
92
+ reserved 5; // removed: parent_code
93
+ map<string, string> metadata = 6;
94
+ string entity_external_id = 7;
95
+ }
96
+
97
+ message CreateBookResponse {
98
+ Book book = 1;
99
+ }
100
+
101
+ message GetBookRequest {
102
+ string type_or_id = 1;
103
+ }
104
+
105
+ message GetBookResponse {
106
+ optional Book book = 1;
107
+ }
108
+
109
+ message ListBooksRequest {
110
+ optional string type = 1;
111
+ reserved 2; // removed: parent_code
112
+ int32 limit = 3;
113
+ int32 offset = 4;
114
+ optional string entity_external_id = 5;
115
+ }
116
+
117
+ message ListBooksResponse {
118
+ repeated Book books = 1;
119
+ }
120
+
121
+ // Journal Entry messages
122
+ message BookEntryInput {
123
+ string book = 1;
124
+ optional int64 debit = 2;
125
+ optional int64 credit = 3;
126
+ map<string, string> metadata = 4;
127
+ }
128
+
129
+ message CreateJournalEntryRequest {
130
+ string idempotency_key = 1;
131
+ reserved 2, 3, 7; // removed: entry_type, description, auto_post
132
+ optional string effective_date = 4; // YYYY-MM-DD
133
+ repeated BookEntryInput book_entries = 5;
134
+ map<string, string> metadata = 6;
135
+ }
136
+
137
+ message JournalEntry {
138
+ string id = 1;
139
+ string idempotency_key = 2;
140
+ reserved 3, 4, 6, 9; // removed: entry_type, description, status, posted_at
141
+ string effective_date = 5;
142
+ map<string, string> metadata = 7;
143
+ Timestamp created_at = 8;
144
+ optional Timestamp reversed_at = 10;
145
+ optional string reverses_id = 11;
146
+ repeated BookEntry book_entries = 12;
147
+ }
148
+
149
+ message BookEntry {
150
+ string id = 1;
151
+ reserved 2; // removed: book_code
152
+ string book_name = 3;
153
+ optional int64 debit = 4;
154
+ optional int64 credit = 5;
155
+ string currency = 6;
156
+ int64 book_balance = 7;
157
+ string entity_id = 8;
158
+ }
159
+
160
+ message CreateJournalEntryResponse {
161
+ JournalEntry journal_entry = 1;
162
+ }
163
+
164
+ message ReverseJournalEntryRequest {
165
+ string entry_id = 1;
166
+ string idempotency_key = 2;
167
+ optional string reason = 3;
168
+ }
169
+
170
+ message ReverseJournalEntryResponse {
171
+ JournalEntry journal_entry = 1;
172
+ }
173
+
174
+ message GetJournalEntryRequest {
175
+ string id = 1;
176
+ }
177
+
178
+ message GetJournalEntryByKeyRequest {
179
+ string idempotency_key = 1;
180
+ }
181
+
182
+ message GetJournalEntryResponse {
183
+ optional JournalEntry journal_entry = 1;
184
+ }
185
+
186
+ // Balance messages
187
+ message GetBalanceRequest {
188
+ string book = 1;
189
+ optional Timestamp as_of = 2;
190
+ }
191
+
192
+ message GetBalanceResponse {
193
+ string book_id = 1;
194
+ string book_name = 2;
195
+ int64 balance = 3;
196
+ string currency = 4;
197
+ Timestamp as_of = 5;
198
+ }
199
+
200
+ message GetBalancesRequest {
201
+ repeated string books = 1;
202
+ optional Timestamp as_of = 2;
203
+ }
204
+
205
+ message GetBalancesResponse {
206
+ repeated GetBalanceResponse balances = 1;
207
+ }
metadata ADDED
@@ -0,0 +1,103 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: janus-ledger
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Solumos
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-03-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: grpc
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.60'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.60'
27
+ - !ruby/object:Gem::Dependency
28
+ name: google-protobuf
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '4.25'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '4.25'
41
+ - !ruby/object:Gem::Dependency
42
+ name: grpc-tools
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.60'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.60'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.12'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.12'
69
+ description: A Ruby gRPC client library for the Janus double-entry accounting engine.
70
+ email:
71
+ - dev@solumos.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - lib/janus.rb
77
+ - lib/janus/_generated/janus_pb.rb
78
+ - lib/janus/_generated/janus_services_pb.rb
79
+ - proto/janus.proto
80
+ homepage: https://github.com/solumos/janus
81
+ licenses:
82
+ - MIT
83
+ metadata: {}
84
+ post_install_message:
85
+ rdoc_options: []
86
+ require_paths:
87
+ - lib
88
+ required_ruby_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: 3.2.0
93
+ required_rubygems_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ requirements: []
99
+ rubygems_version: 3.5.22
100
+ signing_key:
101
+ specification_version: 4
102
+ summary: Ruby gRPC client for Janus
103
+ test_files: []