diff --git a/404.html b/404.html index 012cc14..b607707 100644 --- a/404.html +++ b/404.html @@ -6,13 +6,13 @@ Page Not Found | TigerBeetle Docs - +
Skip to main content

Page Not Found

We could not find what you were looking for.

Please contact the owner of the site that linked you to the original URL and let them know their link is broken.

- + \ No newline at end of file diff --git a/about/architecture/index.html b/about/architecture/index.html index 0d8ab4e..60a288c 100644 --- a/about/architecture/index.html +++ b/about/architecture/index.html @@ -6,7 +6,7 @@ Architecture | TigerBeetle Docs - + @@ -48,7 +48,7 @@ impact through false sharing.

We order the header struct as we do to keep any C protocol implementations padding-free.

We use AEGIS-128L as our checksum, designed to fully exploit the parallelism and built-in AES support of recent Intel and ARM CPUs.

The reason we use two checksums instead of only a single checksum across header and data is that we need a reliable way to know the size of the data to expect before we start receiving the data.

Here is an example showing the risk of a single checksum for the recipient:

  1. We receive a header with a single checksum protecting both header and data.
  2. We extract the SIZE of the data from the header (4 GiB in this case).
  3. We cannot tell if this SIZE value is corrupt until we receive the data.
  4. We wait for 4 GiB of data to arrive before calculating/comparing checksums.
  5. Except the SIZE was corrupted in transit from 16 MiB to 4 GiB (2-bit flips).
  6. We never detect the corruption, the connection times out, and we miss our SLA.
- + \ No newline at end of file diff --git a/about/index.html b/about/index.html index 11477ff..846d34d 100644 --- a/about/index.html +++ b/about/index.html @@ -6,7 +6,7 @@ About TigerBeetle | TigerBeetle Docs - + @@ -82,7 +82,7 @@ Changfoot and Joran Dirk Greef, a performance analysis of Mojaloop's central ledger that sparked the idea for "an accounting database" as Adrian Hope-Bailie put it. And the rest, as they say, is history!

- + \ No newline at end of file diff --git a/about/internals/cloud/index.html b/about/internals/cloud/index.html index 94518d2..7fb1b9a 100644 --- a/about/internals/cloud/index.html +++ b/about/internals/cloud/index.html @@ -6,7 +6,7 @@ Cloud | TigerBeetle Docs - + @@ -36,7 +36,7 @@ providers) and share these as we go.

Credit to @tdaly61 from the Mojaloop community for prompting us with some great questions about Tigerbeetle in the cloud.

You can read more about how we use io_uring in A Programmer-Friendly I/O Abstraction Over io_uring and kqueue.

- + \ No newline at end of file diff --git a/about/internals/data_file/index.html b/about/internals/data_file/index.html index 6ea088e..705a957 100644 --- a/about/internals/data_file/index.html +++ b/about/internals/data_file/index.html @@ -6,7 +6,7 @@ Data File | TigerBeetle Docs - + @@ -71,7 +71,7 @@ reconstruct the manifest in memory. Manifest describes levels and tables of a single LSM tree. A table is a pointer to its index block. The index block is a sorted array of pointers to data blocks. Data blocks are sorted arrays of values.

- + \ No newline at end of file diff --git a/about/internals/index.html b/about/internals/index.html index 940ce03..2b2b37f 100644 --- a/about/internals/index.html +++ b/about/internals/index.html @@ -6,7 +6,7 @@ Internals | TigerBeetle Docs - + @@ -15,7 +15,7 @@ intended audience is folks who are contributing to TigerBeetle source.

It will not be particularly useful on its own for understanding TigerBeetle at a high level.

Furthermore, it isn't particularly organized for reading. It is primarily a reference.

Contents

- + \ No newline at end of file diff --git a/about/internals/lsm/index.html b/about/internals/lsm/index.html index 0a870f0..d20d367 100644 --- a/about/internals/lsm/index.html +++ b/about/internals/lsm/index.html @@ -6,7 +6,7 @@ LSM | TigerBeetle Docs - + @@ -49,7 +49,7 @@ The superblock stores the head and tail address/checksum of this linked list. The reference on the header of the head manifest block "dangles" – the block it references has already been compacted.

Manifest Level

A ManifestLevel is an in-memory collection of the table metadata for a single level of a tree.

For a given level and snapshot, there may be gaps in the key ranges of the visible tables, but the key ranges are disjoint.

Manifest levels are queried for tables at a target snapshot and within a key range.

Example

Given the ManifestLevel tables (with values chosen for visualization, not realism):

       label   A   B   C   D   E   F   G   H   I   J   K   L   M
key_min 0 4 12 16 4 8 12 26 4 25 4 16 24
key_max 3 11 15 19 7 11 15 27 7 27 11 19 27
snapshot_min 1 1 1 1 3 3 3 3 5 5 7 7 7
snapshot_max 9 3 3 7 5 7 9 5 7 7 9 9 9

A level's tables can be visualized in 2D as a partitioned rectangle:

  0         1         2
0 4 8 2 6 0 4 8
9┌───┬───────┬───┬───┬───┬───┐
│ │ K │ │ L │###│ M │
7│ ├───┬───┤ ├───┤###└┬──┤
│ │ I │ │ G │ │####│ J│
5│ A ├───┤ F │ │ │####└┬─┤
│ │ E │ │ │ D │#####│H│
3│ ├───┴───┼───┤ │#####└─┤
│ │ B │ C │ │#######│
1└───┴───────┴───┴───┴───────┘

Example iterations:

visibility  snapshots   direction  key_min  key_max  tables
visible 2 ascending 0 28 A, B, C, D
visible 4 ascending 0 28 A, E, F, G, D, H
visible 6 descending 12 28 J, D, G
visible 8 ascending 0 28 A, K, G, L, M
invisible 2, 4, 6 ascending 0 28 K, L, M

Legend:

- + \ No newline at end of file diff --git a/about/internals/releases/index.html b/about/internals/releases/index.html index 97a0301..475172d 100644 --- a/about/internals/releases/index.html +++ b/about/internals/releases/index.html @@ -6,7 +6,7 @@ Releases | TigerBeetle Docs - + @@ -39,7 +39,7 @@ commits)
  • $ ./zig/zig build scripts -- changelog

    This will update local repository to match remote, create a branch for changelog PR, and add a scaffold of the new changelog to CHANGELOG.md. Importantly, the scaffold will contain a new version number with patch version incremented:

    ## TigerBeetle 0.16.3   <- Double check this version.

    Released 2024-08-29

    - [#2256](https://github.com/tigerbeetle/tigerbeetle/pull/2256)
    Build: Check zig version
    - [#2248](https://github.com/tigerbeetle/tigerbeetle/pull/2248)
    vopr: heal *both* wal header sectors before replica startup

    ### Safety And Performance

    -

    ### Features

    -

    ### Internals

    -

    ### TigerTracks 🎧

    - []()

    If the current release is being skipped, replace the header with ## TigerBeetle (unreleased).

  • Fill in the changelog:

  • Commit the changelog and submit a pull request for review

  • After the PR is merged, push to the release branch:

    $ git fetch origin && git push origin origin/main:release
  • Ask someone else to approve the GitHub workflow.

  • Ping release manager for the next week in Slack.

  • - + \ No newline at end of file diff --git a/about/internals/sync/index.html b/about/internals/sync/index.html index 86e3886..bd34c2f 100644 --- a/about/internals/sync/index.html +++ b/about/internals/sync/index.html @@ -6,7 +6,7 @@ State Sync | TigerBeetle Docs - + @@ -49,7 +49,7 @@ corresponding prepare was originally prepared.

    Storage Determinism

    When everything works, storage is deterministic. If non-determinism is detected (via checkpoint id mismatches) the replica which detects the mismatch will panic. This scenario should prompt operator investigation and manual intervention.

    - + \ No newline at end of file diff --git a/about/internals/testing/index.html b/about/internals/testing/index.html index 93a504f..45f0761 100644 --- a/about/internals/testing/index.html +++ b/about/internals/testing/index.html @@ -6,13 +6,13 @@ Testing | TigerBeetle Docs - +
    Skip to main content

    Testing

    Documentation for (roughly) code in the src/testing directory.

    VOPR Output

    Columns

    1. Replica index.
    2. Event:
      • $: crash
      • ^: recover
      • : commit
      • [: checkpoint start
      • ]: checkpoint done
      • >: sync done
    3. Role (according to the replica itself):
      • /: primary
      • \: backup
      • |: standby
      • ~: syncing
      • #: (crashed)
    4. Status:
      • The column (e.g. . vs .) corresponds to the replica index. (This can help identify events' replicas at a quick glance.)
      • The symbol indicates the replica.status.
      • .: normal
      • v: view_change
      • r: recovering
      • h: recovering_head
      • s: sync
    5. View: e.g. 74V indicates replica.view=74.
    6. Checkpoint and Commit: e.g. 83/_90/_98C indicates that:
      • the highest checkpointed op at the replica is 83 (replica.op_checkpoint()=83),
      • on top of that checkpoint, the replica applied ops up to and including 90 (replica.commit_min=90),
      • replica knows that ops at least up to 98 are committed in the cluster (replica.commit_max=98).
    7. Journal op: e.g. 87:150Jo indicates that the minimum op in the journal is 87 and the maximum is 150.
    8. Journal faulty/dirty: 0/1Jd indicates that the journal has 0 faulty headers and 1 dirty headers.
    9. WAL prepare ops: e.g. 85:149Wo indicates that the op of the oldest prepare in the WAL is 85 and the op of the newest prepare in the WAL is 149.
    10. Syncing ops: e.g. <0:123> indicates that vsr_state.sync_op_min=0 and vsr_state.sync_op_max=123.
    11. Release version: e.g. v1:2 indicates that the replica is running release version 1, and that its maximum available release is 2.
    12. Grid blocks acquired: e.g. 167Ga indicates that the grid has 167 blocks currently in use.
    13. Grid blocks queued grid.read_remote_queue: e.g. 0G! indicates that there are 0 reads awaiting remote fulfillment.
    14. Grid blocks queued grid_blocks_missing: e.g. 0G? indicates that there are 0 blocks awaiting remote repair.
    15. Pipeline prepares (primary-only): e.g. 1/4Pp indicates that the primary's pipeline has 2 prepares queued, out of a capacity of 4.
    16. Pipeline requests (primary-only): e.g. 0/3Pq indicates that the primary's pipeline has 0 requests queued, out of a capacity of 3.

    Example

    (The first line labels the columns, but is not part of the actual VOPR output).

     1 2 3 4-------- 5---  6----------  7-------  8-----  9------- 10-----   11-- 12-----  13-   14-   15---  16---

    3 [ / . 3V 71/_99/_99C 68:_99Jo 0/_0J! 68:_99Wo <__0:__0> v1:2 183Ga 0G! 0G? 0/4Pp 0/3Rq
    4 ^ \ . 2V 23/_23/_46C 19:_50Jo 0/_0J! 19:_50Wo <__0:__0> v1:2 nullGa 0G! 0G?
    2 \ . 3V 71/_99/_99C 68:_99Jo 0/_0J! 68:_99Wo <__0:__0> v1:2 183Ga 0G! 0G?
    2 [ \ . 3V 71/_99/_99C 68:_99Jo 0/_0J! 68:_99Wo <__0:__0> v1:2 183Ga 0G! 0G?
    6 | . 3V 71/_99/_99C 68:_99Jo 0/_0J! 68:_99Wo <__0:__0> v1:2 183Ga 0G! 0G?
    6 [ | . 3V 71/_99/_99C 68:_99Jo 0/_0J! 68:_99Wo <__0:__0> v1:2 183Ga 0G! 0G?
    3 ] / . 3V 95/_99/_99C 68:_99Jo 0/_0J! 68:_99Wo <__0:__0> v1:2 167Ga 0G! 0G? 0/4Pp 0/3Rq
    2 ] \ . 3V 95/_99/_99C 68:_99Jo 0/_0J! 68:_99Wo <__0:__0> v1:2 167Ga 0G! 0G?
    1 \ . 3V 71/_99/_99C 68:_99Jo 0/_1J! 67:_98Wo <__0:__0> v1:2 183Ga 0G! 0G?
    1 [ \ . 3V 71/_99/_99C 68:_99Jo 0/_1J! 67:_98Wo <__0:__0> v1:2 183Ga 0G! 0G?
    5 | . 3V 71/_99/_99C 68:_99Jo 0/_0J! 68:_99Wo <__0:__0> v1:2 183Ga 0G! 0G?
    5 [ | . 3V 71/_99/_99C 68:_99Jo 0/_0J! 68:_99Wo <__0:__0> v1:2 183Ga 0G! 0G?
    - + \ No newline at end of file diff --git a/about/internals/upgrades/index.html b/about/internals/upgrades/index.html index 12637cd..f0853db 100644 --- a/about/internals/upgrades/index.html +++ b/about/internals/upgrades/index.html @@ -6,7 +6,7 @@ Upgrades | TigerBeetle Docs - + @@ -40,7 +40,7 @@ run.


    1. Currently the total number of replicas, less one.
    2. MachO binaries are constructed as fat binaries, using unused, esoteric CPU identifiers to signal the header and body, for both x86_64 and arm64.
    3. The short names are for compatibility with Windows: PE supports up to 8 characters for section names without getting more complicated.
    - + \ No newline at end of file diff --git a/about/internals/vsr/index.html b/about/internals/vsr/index.html index 0c56bb3..adcfd7c 100644 --- a/about/internals/vsr/index.html +++ b/about/internals/vsr/index.html @@ -6,7 +6,7 @@ VSR | TigerBeetle Docs - + @@ -22,7 +22,7 @@ Repaired headers are a prerequisite for repairing prepares.

    Because the headers are repaired backwards (from the head) by hash-chaining, it is safe for both backups and transitioning primaries.

    Gaps/breaks in a replica's journal headers may occur:

    Protocol: Repair WAL

    The replica's journal tracks which prepares the WAL requires — i.e. headers for which either:

    During repair, missing/damaged prepares are requested & repaired chronologically, which:

    In response to a request_prepare:

    Per PAR's CTRL Protocol, we do not nack corrupt entries, since they might be the prepare being requested.

    See also State Sync protocol — the extent of WAL that the replica can/should repair depends on the checkpoint.

    Protocol: Repair Client Replies

    The replica stores the latest reply to each active client.

    During repair, corrupt client replies are requested & repaired.

    In response to a request_reply:

    Protocol: Client

    1. Client sends command=request operation=register to registers with the cluster by starting a new request-reply hashchain. (See also: Protocol: Normal).
    2. Client receives command=reply operation=register from the cluster. (If the cluster is at the maximum number of clients, it evicts the oldest).
    3. Repeat:
      1. Send command=request to cluster.
      2. If the client has been evicted, receive command=eviction from the cluster. (The client must re-register before sending more requests.)
      3. If the client has not been evicted, receive command=reply from cluster.

    See also:

    Protocol: Repair Grid

    Grid repair is triggered when a replica discovers a corrupt (or missing) grid block.

    1. The repairing replica sends a command=request_blocks to any other replica. The message body contains a list of block address/checksums.
    2. Upon receiving a command=request_blocks, a replica reads its own grid to check for the requested blocks. For each matching block found, reply with the command=block message (the block itself).
    3. Upon receiving a command=block, a replica writes the block to its grid, and resolves the reads that were blocked on it.

    Note that both sides of grid repair can run while the grid is being opened during replica startup. That is, a replica can help other replicas repair and repair itself simultaneously.

    TODO Describe state sync fallback.

    Protocol: Sync Client Replies

    Sync missed client replies using Protocol: Repair Grid.

    See State Sync for details.

    Protocol: Sync Forest

    Sync missed LSM manifest and table blocks using Protocol: Repair Grid.

    See State Sync for details.

    Protocol: Reconfiguration

    TODO (Unimplemented)

    Quorums

    With the default configuration:

    Replica Count123456
    Replication Quorum122233
    View-Change Quorum122334
    Nack Quorum112334

    See also:

    Further reading

    - + \ No newline at end of file diff --git a/about/oltp/index.html b/about/oltp/index.html index e848550..7a33e31 100644 --- a/about/oltp/index.html +++ b/about/oltp/index.html @@ -6,7 +6,7 @@ Built for OLTP | TigerBeetle Docs - + @@ -85,7 +85,7 @@ how much of business transactions. And it is built to handle write-heavy and high-contention workloads at high performance and with strong safety guarantees. TigerBeetle can help you build your application correctly today, and it can handle the scale as your business grows.

    - + \ No newline at end of file diff --git a/about/performance/index.html b/about/performance/index.html index d64dbb9..f2082f0 100644 --- a/about/performance/index.html +++ b/about/performance/index.html @@ -6,7 +6,7 @@ Performance | TigerBeetle Docs - + @@ -53,7 +53,7 @@ the transactions so the shards responsible for those accounts become bottlenecks.

    For more details on when single-threaded implementations of algorithms outperform multi-threaded implementations, see "Scalability! But at what COST?.

    - + \ No newline at end of file diff --git a/about/production-ready/index.html b/about/production-ready/index.html index adcb0f8..93a644e 100644 --- a/about/production-ready/index.html +++ b/about/production-ready/index.html @@ -6,7 +6,7 @@ Production Ready | TigerBeetle Docs - + @@ -21,7 +21,7 @@ transfers, may be replaced by TigerBeetle's new Query engine when it ships. These features will be simple to update in code, and will first be deprecated, with time to update between versions.

    We are happy to provide assistance with operating TigerBeetle in production. Please contact our CEO, Joran Dirk Greef, at joran@tigerbeetle.com if your company would like professional support.

    - + \ No newline at end of file diff --git a/about/safety/index.html b/about/safety/index.html index 7c68cce..1b9a22f 100644 --- a/about/safety/index.html +++ b/about/safety/index.html @@ -6,7 +6,7 @@ Safety | TigerBeetle Docs - + @@ -92,7 +92,7 @@ attack surface.

    We are confident that io_uring is the safest (and most performant) way for TigerBeetle to handle async I/O. It is significantly easier for the kernel to implement this correctly than for us to include a userspace multithreaded thread pool (for example, as libuv does).

    - + \ No newline at end of file diff --git a/about/vopr/index.html b/about/vopr/index.html index b1df5e5..d63a6ff 100644 --- a/about/vopr/index.html +++ b/about/vopr/index.html @@ -6,7 +6,7 @@ Deterministic Simulation Testing | TigerBeetle Docs - + @@ -39,7 +39,7 @@ storage checkers verify that this is the case across simulations.

    Inspiration

    TigerBeetle's approach to DST was heavily inspired by the work of FoundationDB and Antithesis.

    Learn More

    - + \ No newline at end of file diff --git a/about/zig/index.html b/about/zig/index.html index dec2508..bd61215 100644 --- a/about/zig/index.html +++ b/about/zig/index.html @@ -6,7 +6,7 @@ Zig | TigerBeetle Docs - + @@ -26,7 +26,7 @@ TigerBeetle to adopt Zig since our stable roadmaps will probably coincide. We wanted to invest for the next 20 years and didn't want to be stuck with C/C++ or compiler/language complexity and pay a tax for the lifetime of the project.

    - + \ No newline at end of file diff --git a/assets/js/13a99faa.46b4d6cd.js b/assets/js/13a99faa.7a5718e2.js similarity index 69% rename from assets/js/13a99faa.46b4d6cd.js rename to assets/js/13a99faa.7a5718e2.js index 4387740..c68449d 100644 --- a/assets/js/13a99faa.46b4d6cd.js +++ b/assets/js/13a99faa.7a5718e2.js @@ -1 +1 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[1372],{3905:(e,n,t)=>{t.d(n,{Zo:()=>u,kt:()=>f});var r=t(7294);function a(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function i(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);n&&(r=r.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,r)}return t}function s(e){for(var n=1;n=0||(a[t]=e[t]);return a}(e,n);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(a[t]=e[t])}return a}var l=r.createContext({}),c=function(e){var n=r.useContext(l),t=n;return e&&(t="function"==typeof e?e(n):s(s({},n),e)),t},u=function(e){var n=c(e.components);return r.createElement(l.Provider,{value:n},e.children)},p="mdxType",d={inlineCode:"code",wrapper:function(e){var n=e.children;return r.createElement(r.Fragment,{},n)}},h=r.forwardRef((function(e,n){var t=e.components,a=e.mdxType,i=e.originalType,l=e.parentName,u=o(e,["components","mdxType","originalType","parentName"]),p=c(t),h=a,f=p["".concat(l,".").concat(h)]||p[h]||d[h]||i;return t?r.createElement(f,s(s({ref:n},u),{},{components:t})):r.createElement(f,s({ref:n},u))}));function f(e,n){var t=arguments,a=n&&n.mdxType;if("string"==typeof e||a){var i=t.length,s=new Array(i);s[0]=h;var o={};for(var l in n)hasOwnProperty.call(n,l)&&(o[l]=n[l]);o.originalType=e,o[p]="string"==typeof e?e:a,s[1]=o;for(var c=2;c{t.r(n),t.d(n,{assets:()=>l,contentTitle:()=>s,default:()=>d,frontMatter:()=>i,metadata:()=>o,toc:()=>c});var r=t(7462),a=(t(7294),t(3905));const i={title:"Go"},s=void 0,o={unversionedId:"clients/go",id:"clients/go",title:"Go",description:"The TigerBeetle client for Go.",source:"@site/pages/clients/go.md",sourceDirName:"clients",slug:"/clients/go",permalink:"/clients/go",draft:!1,editUrl:"https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/clients/go.md",tags:[],version:"current",frontMatter:{title:"Go"},sidebar:"tutorialSidebar",previous:{title:".NET",permalink:"/clients/dotnet"},next:{title:"Java",permalink:"/clients/java"}},l={},c=[{value:"Prerequisites",id:"prerequisites",level:2},{value:"Setup",id:"setup",level:2},{value:"Sample projects",id:"sample-projects",level:2},{value:"Creating a Client",id:"creating-a-client",level:2},{value:"Creating Accounts",id:"creating-accounts",level:2},{value:"Account Flags",id:"account-flags",level:3},{value:"Response and Errors",id:"response-and-errors",level:3},{value:"Account Lookup",id:"account-lookup",level:2},{value:"Create Transfers",id:"create-transfers",level:2},{value:"Response and Errors",id:"response-and-errors-1",level:3},{value:"Batching",id:"batching",level:2},{value:"Queues and Workers",id:"queues-and-workers",level:3},{value:"Transfer Flags",id:"transfer-flags",level:2},{value:"Two-Phase Transfers",id:"two-phase-transfers",level:3},{value:"Post a Pending Transfer",id:"post-a-pending-transfer",level:4},{value:"Void a Pending Transfer",id:"void-a-pending-transfer",level:4},{value:"Transfer Lookup",id:"transfer-lookup",level:2},{value:"Get Account Transfers",id:"get-account-transfers",level:2},{value:"Get Account Balances",id:"get-account-balances",level:2},{value:"Query Accounts",id:"query-accounts",level:2},{value:"Query Transfers",id:"query-transfers",level:2},{value:"Linked Events",id:"linked-events",level:2},{value:"Imported Events",id:"imported-events",level:2}],u={toc:c},p="wrapper";function d(e){let{components:n,...t}=e;return(0,a.kt)(p,(0,r.Z)({},u,t,{components:n,mdxType:"MDXLayout"}),(0,a.kt)("h1",{id:"tigerbeetle-go"},"tigerbeetle-go"),(0,a.kt)("p",null,"The TigerBeetle client for Go."),(0,a.kt)("p",null,(0,a.kt)("a",{parentName:"p",href:"https://pkg.go.dev/github.com/tigerbeetle/tigerbeetle-go"},(0,a.kt)("img",{parentName:"a",src:"https://pkg.go.dev/badge/github.com/tigerbeetle/tigerbeetle-go.svg",alt:"Go Reference"}))),(0,a.kt)("p",null,"Make sure to import ",(0,a.kt)("inlineCode",{parentName:"p"},"github.com/tigerbeetle/tigerbeetle-go"),", not\nthis repo and subdirectory."),(0,a.kt)("h2",{id:"prerequisites"},"Prerequisites"),(0,a.kt)("p",null,"Linux >= 5.6 is the only production environment we\nsupport. But for ease of development we also support macOS and Windows."),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Go >= 1.21")),(0,a.kt)("p",null,(0,a.kt)("strong",{parentName:"p"},"Additionally on Windows"),": you must install ",(0,a.kt)("a",{parentName:"p",href:"https://ziglang.org/download/#release-0.13.0"},"Zig\n0.13.0")," and set the\n",(0,a.kt)("inlineCode",{parentName:"p"},"CC")," environment variable to ",(0,a.kt)("inlineCode",{parentName:"p"},"zig.exe cc"),". Use the full path for\n",(0,a.kt)("inlineCode",{parentName:"p"},"zig.exe"),"."),(0,a.kt)("h2",{id:"setup"},"Setup"),(0,a.kt)("p",null,"First, create a directory for your project and ",(0,a.kt)("inlineCode",{parentName:"p"},"cd")," into the directory."),(0,a.kt)("p",null,"Then, install the TigerBeetle client:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-console"},"go mod init tbtest\ngo get github.com/tigerbeetle/tigerbeetle-go\n")),(0,a.kt)("p",null,"Now, create ",(0,a.kt)("inlineCode",{parentName:"p"},"main.go")," and copy this into it:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'package main\n\nimport (\n "fmt"\n "log"\n "os"\n\n . "github.com/tigerbeetle/tigerbeetle-go"\n . "github.com/tigerbeetle/tigerbeetle-go/pkg/types"\n)\n\nfunc main() {\n fmt.Println("Import ok!")\n}\n\n')),(0,a.kt)("p",null,"Finally, build and run:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-console"},"go run main.go\n")),(0,a.kt)("p",null,"Now that all prerequisites and dependencies are correctly set\nup, let's dig into using TigerBeetle."),(0,a.kt)("h2",{id:"sample-projects"},"Sample projects"),(0,a.kt)("p",null,"This document is primarily a reference guide to\nthe client. Below are various sample projects demonstrating\nfeatures of TigerBeetle."),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"https://github.com/tigerbeetle/tigerbeetle/blob/main/src/clients/go/samples/basic/"},"Basic"),": Create two accounts and transfer an amount between them."),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"https://github.com/tigerbeetle/tigerbeetle/blob/main/src/clients/go/samples/two-phase/"},"Two-Phase Transfer"),": Create two accounts and start a pending transfer between\nthem, then post the transfer."),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"https://github.com/tigerbeetle/tigerbeetle/blob/main/src/clients/go/samples/two-phase-many/"},"Many Two-Phase Transfers"),": Create two accounts and start a number of pending transfer\nbetween them, posting and voiding alternating transfers.")),(0,a.kt)("h2",{id:"creating-a-client"},"Creating a Client"),(0,a.kt)("p",null,"A client is created with a cluster ID and replica\naddresses for all replicas in the cluster. The cluster\nID and replica addresses are both chosen by the system that\nstarts the TigerBeetle cluster."),(0,a.kt)("p",null,"Clients are thread-safe and a single instance should be shared\nbetween multiple concurrent tasks."),(0,a.kt)("p",null,"Multiple clients are useful when connecting to more than\none TigerBeetle cluster."),(0,a.kt)("p",null,"In this example the cluster ID is ",(0,a.kt)("inlineCode",{parentName:"p"},"0")," and there is one\nreplica. The address is read from the ",(0,a.kt)("inlineCode",{parentName:"p"},"TB_ADDRESS"),"\nenvironment variable and defaults to port ",(0,a.kt)("inlineCode",{parentName:"p"},"3000"),"."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'tbAddress := os.Getenv("TB_ADDRESS")\nif len(tbAddress) == 0 {\n tbAddress = "3000"\n}\nclient, err := NewClient(ToUint128(0), []string{tbAddress})\nif err != nil {\n log.Printf("Error creating client: %s", err)\n return\n}\ndefer client.Close()\n')),(0,a.kt)("p",null,"The following are valid addresses:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"3000")," (interpreted as ",(0,a.kt)("inlineCode",{parentName:"li"},"127.0.0.1:3000"),")"),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"127.0.0.1:3000")," (interpreted as ",(0,a.kt)("inlineCode",{parentName:"li"},"127.0.0.1:3000"),")"),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"127.0.0.1")," (interpreted as ",(0,a.kt)("inlineCode",{parentName:"li"},"127.0.0.1:3001"),", ",(0,a.kt)("inlineCode",{parentName:"li"},"3001")," is the default port)")),(0,a.kt)("h2",{id:"creating-accounts"},"Creating Accounts"),(0,a.kt)("p",null,"See details for account fields in the ",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/account"},"Accounts\nreference"),"."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'accountsRes, err := client.CreateAccounts([]Account{\n {\n ID: ToUint128(137),\n DebitsPending: ToUint128(0),\n DebitsPosted: ToUint128(0),\n CreditsPending: ToUint128(0),\n CreditsPosted: ToUint128(0),\n UserData128: ToUint128(0),\n UserData64: 0,\n UserData32: 0,\n Reserved: 0,\n Ledger: 1,\n Code: 718,\n Flags: 0,\n Timestamp: 0,\n },\n})\nif err != nil {\n log.Printf("Error creating accounts: %s", err)\n return\n}\n\nfor _, err := range accountsRes {\n log.Printf("Error creating account %d: %s", err.Index, err.Result)\n return\n}\n')),(0,a.kt)("p",null,"The ",(0,a.kt)("inlineCode",{parentName:"p"},"Uint128")," fields like ",(0,a.kt)("inlineCode",{parentName:"p"},"ID"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"UserData128"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"Amount")," and\naccount balances have a few helper functions to make it easier\nto convert 128-bit little-endian unsigned integers between\n",(0,a.kt)("inlineCode",{parentName:"p"},"string"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"math/big.Int"),", and ",(0,a.kt)("inlineCode",{parentName:"p"},"[]byte"),"."),(0,a.kt)("p",null,"See the type ",(0,a.kt)("a",{parentName:"p",href:"https://pkg.go.dev/github.com/tigerbeetle/tigerbeetle-go/pkg/types#Uint128"},"Uint128")," for more details."),(0,a.kt)("h3",{id:"account-flags"},"Account Flags"),(0,a.kt)("p",null,"The account flags value is a bitfield. See details for\nthese flags in the ",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/account#flags"},"Accounts\nreference"),"."),(0,a.kt)("p",null,"To toggle behavior for an account, use the ",(0,a.kt)("inlineCode",{parentName:"p"},"types.AccountFlags")," struct\nto combine enum values and generate a ",(0,a.kt)("inlineCode",{parentName:"p"},"uint16"),". Here are a\nfew examples:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"AccountFlags{Linked: true}.ToUint16()")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"AccountFlags{DebitsMustNotExceedCredits: true}.ToUint16()")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"AccountFlags{CreditsMustNotExceedDebits: true}.ToUint16()")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"AccountFlags{History: true}.ToUint16()"))),(0,a.kt)("p",null,"For example, to link two accounts where the first account\nadditionally has the ",(0,a.kt)("inlineCode",{parentName:"p"},"debits_must_not_exceed_credits")," constraint:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'account0 := Account{ /* ... account values ... */ }\naccount1 := Account{ /* ... account values ... */ }\naccount0.Flags = AccountFlags{Linked: true}.ToUint16()\n\naccountErrors, err := client.CreateAccounts([]Account{account0, account1})\nif err != nil {\n log.Printf("Error creating accounts: %s", err)\n return\n}\n')),(0,a.kt)("h3",{id:"response-and-errors"},"Response and Errors"),(0,a.kt)("p",null,"The response is an empty array if all accounts were\ncreated successfully. If the response is non-empty, each\nobject in the response array contains error information\nfor an account that failed. The error object contains an\nerror code and the index of the account in the request\nbatch."),(0,a.kt)("p",null,"See all error conditions in the ",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/requests/create_accounts"},"create_accounts\nreference"),"."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'account2 := Account{ /* ... account values ... */ }\naccount3 := Account{ /* ... account values ... */ }\naccount4 := Account{ /* ... account values ... */ }\n\naccountErrors, err = client.CreateAccounts([]Account{account2, account3, account4})\nif err != nil {\n log.Printf("Error creating accounts: %s", err)\n return\n}\nfor _, err := range accountErrors {\n log.Printf("Error creating account %d: %s", err.Index, err.Result)\n return\n}\n')),(0,a.kt)("p",null,"To handle errors you can either 1) exactly match error codes returned\nfrom ",(0,a.kt)("inlineCode",{parentName:"p"},"client.createAccounts")," with enum values in the\n",(0,a.kt)("inlineCode",{parentName:"p"},"CreateAccountError")," object, or you can 2) look up the error code in\nthe ",(0,a.kt)("inlineCode",{parentName:"p"},"CreateAccountError")," object for a human-readable string."),(0,a.kt)("h2",{id:"account-lookup"},"Account Lookup"),(0,a.kt)("p",null,"Account lookup is batched, like account creation. Pass\nin all IDs to fetch. The account for each matched ID is returned."),(0,a.kt)("p",null,"If no account matches an ID, no object is returned for\nthat account. So the order of accounts in the response is\nnot necessarily the same as the order of IDs in the\nrequest. You can refer to the ID field in the response to\ndistinguish accounts."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'accounts, err := client.LookupAccounts([]Uint128{ToUint128(137), ToUint128(138)})\nif err != nil {\n log.Printf("Could not fetch accounts: %s", err)\n return\n}\nlog.Println(accounts)\n')),(0,a.kt)("h2",{id:"create-transfers"},"Create Transfers"),(0,a.kt)("p",null,"This creates a journal entry between two accounts."),(0,a.kt)("p",null,"See details for transfer fields in the ",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/transfer"},"Transfers\nreference"),"."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'transfers := []Transfer{{\n ID: ToUint128(1),\n DebitAccountID: ToUint128(1),\n CreditAccountID: ToUint128(2),\n Amount: ToUint128(10),\n PendingID: ToUint128(0),\n UserData128: ToUint128(2),\n UserData64: 0,\n UserData32: 0,\n Timeout: 0,\n Ledger: 1,\n Code: 1,\n Flags: 0,\n Timestamp: 0,\n}}\n\ntransfersRes, err := client.CreateTransfers(transfers)\nif err != nil {\n log.Printf("Error creating transfer batch: %s", err)\n return\n}\n')),(0,a.kt)("h3",{id:"response-and-errors-1"},"Response and Errors"),(0,a.kt)("p",null,"The response is an empty array if all transfers were created\nsuccessfully. If the response is non-empty, each object in the\nresponse array contains error information for a transfer that\nfailed. The error object contains an error code and the index of the\ntransfer in the request batch."),(0,a.kt)("p",null,"See all error conditions in the ",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/requests/create_transfers"},"create_transfers\nreference"),"."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'for _, err := range transfersRes {\n log.Printf("Batch transfer at %d failed to create: %s", err.Index, err.Result)\n return\n}\n')),(0,a.kt)("h2",{id:"batching"},"Batching"),(0,a.kt)("p",null,"TigerBeetle performance is maximized when you batch\nAPI requests. The client does not do this automatically for\nyou. So, for example, you ",(0,a.kt)("em",{parentName:"p"},"can")," insert 1 million transfers\none at a time like so:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},"for i := 0; i < len(transfers); i++ {\n transfersRes, err = client.CreateTransfers([]Transfer{transfers[i]})\n // Error handling omitted.\n}\n")),(0,a.kt)("p",null,"But the insert rate will be a ",(0,a.kt)("em",{parentName:"p"},"fraction")," of\npotential. Instead, ",(0,a.kt)("strong",{parentName:"p"},"always batch what you can"),"."),(0,a.kt)("p",null,"The maximum batch size is set in the TigerBeetle server. The default\nis 8190."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},"BATCH_SIZE := 8190\nfor i := 0; i < len(transfers); i += BATCH_SIZE {\n batch := BATCH_SIZE\n if i+BATCH_SIZE > len(transfers) {\n batch = len(transfers) - i\n }\n transfersRes, err = client.CreateTransfers(transfers[i : i+batch])\n // Error handling omitted.\n}\n")),(0,a.kt)("h3",{id:"queues-and-workers"},"Queues and Workers"),(0,a.kt)("p",null,"If you are making requests to TigerBeetle from workers\npulling jobs from a queue, you can batch requests to\nTigerBeetle by having the worker act on multiple jobs from\nthe queue at once rather than one at a time. i.e. pulling\nmultiple jobs from the queue rather than just one."),(0,a.kt)("h2",{id:"transfer-flags"},"Transfer Flags"),(0,a.kt)("p",null,"The transfer ",(0,a.kt)("inlineCode",{parentName:"p"},"flags")," value is a bitfield. See details for these flags in\nthe ",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/transfer#flags"},"Transfers\nreference"),"."),(0,a.kt)("p",null,"To toggle behavior for an account, use the ",(0,a.kt)("inlineCode",{parentName:"p"},"types.TransferFlags")," struct\nto combine enum values and generate a ",(0,a.kt)("inlineCode",{parentName:"p"},"uint16"),". Here are a\nfew examples:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"TransferFlags{Linked: true}.ToUint16()")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"TransferFlags{Pending: true}.ToUint16()")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"TransferFlags{PostPendingTransfer: true}.ToUint16()")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"TransferFlags{VoidPendingTransfer: true}.ToUint16()"))),(0,a.kt)("p",null,"For example, to link ",(0,a.kt)("inlineCode",{parentName:"p"},"transfer0")," and ",(0,a.kt)("inlineCode",{parentName:"p"},"transfer1"),":"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},"transfer0 := Transfer{ /* ... account values ... */ }\ntransfer1 := Transfer{ /* ... account values ... */ }\ntransfer0.Flags = TransferFlags{Linked: true}.ToUint16()\ntransfersRes, err = client.CreateTransfers([]Transfer{transfer0, transfer1})\n// Error handling omitted.\n")),(0,a.kt)("h3",{id:"two-phase-transfers"},"Two-Phase Transfers"),(0,a.kt)("p",null,"Two-phase transfers are supported natively by toggling the appropriate\nflag. TigerBeetle will then adjust the ",(0,a.kt)("inlineCode",{parentName:"p"},"credits_pending")," and\n",(0,a.kt)("inlineCode",{parentName:"p"},"debits_pending")," fields of the appropriate accounts. A corresponding\npost pending transfer then needs to be sent to post or void the\ntransfer."),(0,a.kt)("h4",{id:"post-a-pending-transfer"},"Post a Pending Transfer"),(0,a.kt)("p",null,"With ",(0,a.kt)("inlineCode",{parentName:"p"},"flags")," set to ",(0,a.kt)("inlineCode",{parentName:"p"},"post_pending_transfer"),",\nTigerBeetle will post the transfer. TigerBeetle will atomically roll\nback the changes to ",(0,a.kt)("inlineCode",{parentName:"p"},"debits_pending")," and ",(0,a.kt)("inlineCode",{parentName:"p"},"credits_pending")," of the\nappropriate accounts and apply them to the ",(0,a.kt)("inlineCode",{parentName:"p"},"debits_posted")," and\n",(0,a.kt)("inlineCode",{parentName:"p"},"credits_posted")," balances."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},"transfer := Transfer{\n ID: ToUint128(2),\n // Post the entire pending amount.\n Amount: AmountMax,\n PendingID: ToUint128(1),\n Flags: TransferFlags{PostPendingTransfer: true}.ToUint16(),\n Timestamp: 0,\n}\ntransfersRes, err = client.CreateTransfers([]Transfer{transfer})\n// Error handling omitted.\n")),(0,a.kt)("h4",{id:"void-a-pending-transfer"},"Void a Pending Transfer"),(0,a.kt)("p",null,"In contrast, with ",(0,a.kt)("inlineCode",{parentName:"p"},"flags")," set to ",(0,a.kt)("inlineCode",{parentName:"p"},"void_pending_transfer"),",\nTigerBeetle will void the transfer. TigerBeetle will roll\nback the changes to ",(0,a.kt)("inlineCode",{parentName:"p"},"debits_pending")," and ",(0,a.kt)("inlineCode",{parentName:"p"},"credits_pending")," of the\nappropriate accounts and ",(0,a.kt)("strong",{parentName:"p"},"not")," apply them to the ",(0,a.kt)("inlineCode",{parentName:"p"},"debits_posted")," and\n",(0,a.kt)("inlineCode",{parentName:"p"},"credits_posted")," balances."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},"transfer = Transfer{\n ID: ToUint128(2),\n PendingID: ToUint128(1),\n Flags: TransferFlags{VoidPendingTransfer: true}.ToUint16(),\n Timestamp: 0,\n}\ntransfersRes, err = client.CreateTransfers([]Transfer{transfer})\n// Error handling omitted.\n")),(0,a.kt)("h2",{id:"transfer-lookup"},"Transfer Lookup"),(0,a.kt)("p",null,"NOTE: While transfer lookup exists, it is not a flexible query API. We\nare developing query APIs and there will be new methods for querying\ntransfers in the future."),(0,a.kt)("p",null,"Transfer lookup is batched, like transfer creation. Pass in all ",(0,a.kt)("inlineCode",{parentName:"p"},"id"),"s to\nfetch, and matched transfers are returned."),(0,a.kt)("p",null,"If no transfer matches an ",(0,a.kt)("inlineCode",{parentName:"p"},"id"),", no object is returned for that\ntransfer. So the order of transfers in the response is not necessarily\nthe same as the order of ",(0,a.kt)("inlineCode",{parentName:"p"},"id"),"s in the request. You can refer to the\n",(0,a.kt)("inlineCode",{parentName:"p"},"id")," field in the response to distinguish transfers."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'transfers, err = client.LookupTransfers([]Uint128{ToUint128(1), ToUint128(2)})\nif err != nil {\n log.Printf("Could not fetch transfers: %s", err)\n return\n}\nlog.Println(transfers)\n')),(0,a.kt)("h2",{id:"get-account-transfers"},"Get Account Transfers"),(0,a.kt)("p",null,"NOTE: This is a preview API that is subject to breaking changes once we have\na stable querying API."),(0,a.kt)("p",null,"Fetches the transfers involving a given account, allowing basic filter and pagination\ncapabilities."),(0,a.kt)("p",null,"The transfers in the response are sorted by ",(0,a.kt)("inlineCode",{parentName:"p"},"timestamp")," in chronological or\nreverse-chronological order."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'filter := AccountFilter{\n AccountID: ToUint128(2),\n TimestampMin: 0, // No filter by Timestamp.\n TimestampMax: 0, // No filter by Timestamp.\n Limit: 10, // Limit to ten transfers at most.\n Flags: AccountFilterFlags{\n Debits: true, // Include transfer from the debit side.\n Credits: true, // Include transfer from the credit side.\n Reversed: true, // Sort by timestamp in reverse-chronological order.\n }.ToUint32(),\n}\n\ntransfers, err = client.GetAccountTransfers(filter)\nif err != nil {\n log.Printf("Could not fetch transfers: %s", err)\n return\n}\nlog.Println(transfers)\n')),(0,a.kt)("h2",{id:"get-account-balances"},"Get Account Balances"),(0,a.kt)("p",null,"NOTE: This is a preview API that is subject to breaking changes once we have\na stable querying API."),(0,a.kt)("p",null,"Fetches the point-in-time balances of a given account, allowing basic filter and\npagination capabilities."),(0,a.kt)("p",null,"Only accounts created with the flag\n",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/account#flagshistory"},(0,a.kt)("inlineCode",{parentName:"a"},"history"))," set retain\n",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/requests/get_account_balances"},"historical balances"),"."),(0,a.kt)("p",null,"The balances in the response are sorted by ",(0,a.kt)("inlineCode",{parentName:"p"},"timestamp")," in chronological or\nreverse-chronological order."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'filter = AccountFilter{\n AccountID: ToUint128(2),\n TimestampMin: 0, // No filter by Timestamp.\n TimestampMax: 0, // No filter by Timestamp.\n Limit: 10, // Limit to ten balances at most.\n Flags: AccountFilterFlags{\n Debits: true, // Include transfer from the debit side.\n Credits: true, // Include transfer from the credit side.\n Reversed: true, // Sort by timestamp in reverse-chronological order.\n }.ToUint32(),\n}\n\naccount_balances, err := client.GetAccountBalances(filter)\nif err != nil {\n log.Printf("Could not fetch the history: %s", err)\n return\n}\nlog.Println(account_balances)\n')),(0,a.kt)("h2",{id:"query-accounts"},"Query Accounts"),(0,a.kt)("p",null,"NOTE: This is a preview API that is subject to breaking changes once we have\na stable querying API."),(0,a.kt)("p",null,"Query accounts by the intersection of some fields and by timestamp range."),(0,a.kt)("p",null,"The accounts in the response are sorted by ",(0,a.kt)("inlineCode",{parentName:"p"},"timestamp")," in chronological or\nreverse-chronological order."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'query_filter := QueryFilter{\n UserData128: ToUint128(1000), // Filter by UserData\n UserData64: 100,\n UserData32: 10,\n Code: 1, // Filter by Code\n Ledger: 0, // No filter by Ledger\n TimestampMin: 0, // No filter by Timestamp.\n TimestampMax: 0, // No filter by Timestamp.\n Limit: 10, // Limit to ten balances at most.\n Flags: QueryFilterFlags{\n Reversed: true, // Sort by timestamp in reverse-chronological order.\n }.ToUint32(),\n}\n\nquery_accounts, err := client.QueryAccounts(query_filter)\nif err != nil {\n log.Printf("Could not query accounts: %s", err)\n return\n}\nlog.Println(query_accounts)\n')),(0,a.kt)("h2",{id:"query-transfers"},"Query Transfers"),(0,a.kt)("p",null,"NOTE: This is a preview API that is subject to breaking changes once we have\na stable querying API."),(0,a.kt)("p",null,"Query transfers by the intersection of some fields and by timestamp range."),(0,a.kt)("p",null,"The transfers in the response are sorted by ",(0,a.kt)("inlineCode",{parentName:"p"},"timestamp")," in chronological or\nreverse-chronological order."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'query_filter = QueryFilter{\n UserData128: ToUint128(1000), // Filter by UserData.\n UserData64: 100,\n UserData32: 10,\n Code: 1, // Filter by Code.\n Ledger: 0, // No filter by Ledger.\n TimestampMin: 0, // No filter by Timestamp.\n TimestampMax: 0, // No filter by Timestamp.\n Limit: 10, // Limit to ten balances at most.\n Flags: QueryFilterFlags{\n Reversed: true, // Sort by timestamp in reverse-chronological order.\n }.ToUint32(),\n}\n\nquery_transfers, err := client.QueryTransfers(query_filter)\nif err != nil {\n log.Printf("Could not query transfers: %s", err)\n return\n}\nlog.Println(query_transfers)\n')),(0,a.kt)("h2",{id:"linked-events"},"Linked Events"),(0,a.kt)("p",null,"When the ",(0,a.kt)("inlineCode",{parentName:"p"},"linked")," flag is specified for an account when creating accounts or\na transfer when creating transfers, it links that event with the next event in the\nbatch, to create a chain of events, of arbitrary length, which all\nsucceed or fail together. The tail of a chain is denoted by the first\nevent without this flag. The last event in a batch may therefore never\nhave the ",(0,a.kt)("inlineCode",{parentName:"p"},"linked")," flag set as this would leave a chain\nopen-ended. Multiple chains or individual events may coexist within a\nbatch to succeed or fail independently."),(0,a.kt)("p",null,"Events within a chain are executed within order, or are rolled back on\nerror, so that the effect of each event in the chain is visible to the\nnext, and so that the chain is either visible or invisible as a unit\nto subsequent events after the chain. The event that was the first to\nbreak the chain will have a unique error result. Other events in the\nchain will have their error result set to ",(0,a.kt)("inlineCode",{parentName:"p"},"linked_event_failed"),"."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},"batch := []Transfer{}\nlinkedFlag := TransferFlags{Linked: true}.ToUint16()\n\n// An individual transfer (successful):\nbatch = append(batch, Transfer{ID: ToUint128(1) /* ... rest of transfer ... */})\n\n// A chain of 4 transfers (the last transfer in the chain closes the chain with linked=false):\nbatch = append(batch, Transfer{ID: ToUint128(2) /* ... , */, Flags: linkedFlag}) // Commit/rollback.\nbatch = append(batch, Transfer{ID: ToUint128(3) /* ... , */, Flags: linkedFlag}) // Commit/rollback.\nbatch = append(batch, Transfer{ID: ToUint128(2) /* ... , */, Flags: linkedFlag}) // Fail with exists\nbatch = append(batch, Transfer{ID: ToUint128(4) /* ... , */}) // Fail without committing\n\n// An individual transfer (successful):\n// This should not see any effect from the failed chain above.\nbatch = append(batch, Transfer{ID: ToUint128(2) /* ... rest of transfer ... */})\n\n// A chain of 2 transfers (the first transfer fails the chain):\nbatch = append(batch, Transfer{ID: ToUint128(2) /* ... rest of transfer ... */, Flags: linkedFlag})\nbatch = append(batch, Transfer{ID: ToUint128(3) /* ... rest of transfer ... */})\n\n// A chain of 2 transfers (successful):\nbatch = append(batch, Transfer{ID: ToUint128(3) /* ... rest of transfer ... */, Flags: linkedFlag})\nbatch = append(batch, Transfer{ID: ToUint128(4) /* ... rest of transfer ... */})\n\ntransfersRes, err = client.CreateTransfers(batch)\n")),(0,a.kt)("h2",{id:"imported-events"},"Imported Events"),(0,a.kt)("p",null,"When the ",(0,a.kt)("inlineCode",{parentName:"p"},"imported")," flag is specified for an account when creating accounts or\na transfer when creating transfers, it allows importing historical events with\na user-defined timestamp."),(0,a.kt)("p",null,"The entire batch of events must be set with the flag ",(0,a.kt)("inlineCode",{parentName:"p"},"imported"),"."),(0,a.kt)("p",null,"It's recommended to submit the whole batch as a ",(0,a.kt)("inlineCode",{parentName:"p"},"linked")," chain of events, ensuring that\nif any event fails, none of them are committed, preserving the last timestamp unchanged.\nThis approach gives the application a chance to correct failed imported events, re-submitting\nthe batch again with the same user-defined timestamps."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},"// First, load and import all accounts with their timestamps from the historical source.\naccountsBatch := []Account{}\nfor index, account := range historicalAccounts {\n // Set a unique and strictly increasing timestamp.\n historicalTimestamp += 1\n account.Timestamp = historicalTimestamp\n\n account.Flags = AccountFlags{\n // Set the account as `imported`.\n Imported: true,\n // To ensure atomicity, the entire batch (except the last event in the chain)\n // must be `linked`.\n Linked: index < len(historicalAccounts)-1,\n }.ToUint16()\n\n accountsBatch = append(accountsBatch, account)\n}\naccountsRes, err = client.CreateAccounts(accountsBatch)\n\n// Then, load and import all transfers with their timestamps from the historical source.\ntransfersBatch := []Transfer{}\nfor index, transfer := range historicalTransfers {\n // Set a unique and strictly increasing timestamp.\n historicalTimestamp += 1\n transfer.Timestamp = historicalTimestamp\n\n transfer.Flags = TransferFlags{\n // Set the transfer as `imported`.\n Imported: true,\n // To ensure atomicity, the entire batch (except the last event in the chain)\n // must be `linked`.\n Linked: index < len(historicalAccounts)-1,\n }.ToUint16()\n\n transfersBatch = append(transfersBatch, transfer)\n}\ntransfersRes, err = client.CreateTransfers(transfersBatch)\n// Error handling omitted..\n// Since it is a linked chain, in case of any error the entire batch is rolled back and can be retried\n// with the same historical timestamps without regressing the cluster timestamp.\n")))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[1372],{3905:(e,n,t)=>{t.d(n,{Zo:()=>u,kt:()=>h});var r=t(7294);function a(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function i(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);n&&(r=r.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,r)}return t}function s(e){for(var n=1;n=0||(a[t]=e[t]);return a}(e,n);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(a[t]=e[t])}return a}var l=r.createContext({}),c=function(e){var n=r.useContext(l),t=n;return e&&(t="function"==typeof e?e(n):s(s({},n),e)),t},u=function(e){var n=c(e.components);return r.createElement(l.Provider,{value:n},e.children)},p="mdxType",d={inlineCode:"code",wrapper:function(e){var n=e.children;return r.createElement(r.Fragment,{},n)}},f=r.forwardRef((function(e,n){var t=e.components,a=e.mdxType,i=e.originalType,l=e.parentName,u=o(e,["components","mdxType","originalType","parentName"]),p=c(t),f=a,h=p["".concat(l,".").concat(f)]||p[f]||d[f]||i;return t?r.createElement(h,s(s({ref:n},u),{},{components:t})):r.createElement(h,s({ref:n},u))}));function h(e,n){var t=arguments,a=n&&n.mdxType;if("string"==typeof e||a){var i=t.length,s=new Array(i);s[0]=f;var o={};for(var l in n)hasOwnProperty.call(n,l)&&(o[l]=n[l]);o.originalType=e,o[p]="string"==typeof e?e:a,s[1]=o;for(var c=2;c{t.r(n),t.d(n,{assets:()=>l,contentTitle:()=>s,default:()=>d,frontMatter:()=>i,metadata:()=>o,toc:()=>c});var r=t(7462),a=(t(7294),t(3905));const i={title:"Go"},s=void 0,o={unversionedId:"clients/go",id:"clients/go",title:"Go",description:"The TigerBeetle client for Go.",source:"@site/pages/clients/go.md",sourceDirName:"clients",slug:"/clients/go",permalink:"/clients/go",draft:!1,editUrl:"https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/clients/go.md",tags:[],version:"current",frontMatter:{title:"Go"},sidebar:"tutorialSidebar",previous:{title:".NET",permalink:"/clients/dotnet"},next:{title:"Java",permalink:"/clients/java"}},l={},c=[{value:"Prerequisites",id:"prerequisites",level:2},{value:"Setup",id:"setup",level:2},{value:"Sample projects",id:"sample-projects",level:2},{value:"Creating a Client",id:"creating-a-client",level:2},{value:"Creating Accounts",id:"creating-accounts",level:2},{value:"Account Flags",id:"account-flags",level:3},{value:"Response and Errors",id:"response-and-errors",level:3},{value:"Account Lookup",id:"account-lookup",level:2},{value:"Create Transfers",id:"create-transfers",level:2},{value:"Response and Errors",id:"response-and-errors-1",level:3},{value:"Batching",id:"batching",level:2},{value:"Queues and Workers",id:"queues-and-workers",level:3},{value:"Transfer Flags",id:"transfer-flags",level:2},{value:"Two-Phase Transfers",id:"two-phase-transfers",level:3},{value:"Post a Pending Transfer",id:"post-a-pending-transfer",level:4},{value:"Void a Pending Transfer",id:"void-a-pending-transfer",level:4},{value:"Transfer Lookup",id:"transfer-lookup",level:2},{value:"Get Account Transfers",id:"get-account-transfers",level:2},{value:"Get Account Balances",id:"get-account-balances",level:2},{value:"Query Accounts",id:"query-accounts",level:2},{value:"Query Transfers",id:"query-transfers",level:2},{value:"Linked Events",id:"linked-events",level:2},{value:"Imported Events",id:"imported-events",level:2}],u={toc:c},p="wrapper";function d(e){let{components:n,...t}=e;return(0,a.kt)(p,(0,r.Z)({},u,t,{components:n,mdxType:"MDXLayout"}),(0,a.kt)("h1",{id:"tigerbeetle-go"},"tigerbeetle-go"),(0,a.kt)("p",null,"The TigerBeetle client for Go."),(0,a.kt)("p",null,(0,a.kt)("a",{parentName:"p",href:"https://pkg.go.dev/github.com/tigerbeetle/tigerbeetle-go"},(0,a.kt)("img",{parentName:"a",src:"https://pkg.go.dev/badge/github.com/tigerbeetle/tigerbeetle-go.svg",alt:"Go Reference"}))),(0,a.kt)("p",null,"Make sure to import ",(0,a.kt)("inlineCode",{parentName:"p"},"github.com/tigerbeetle/tigerbeetle-go"),", not\nthis repo and subdirectory."),(0,a.kt)("h2",{id:"prerequisites"},"Prerequisites"),(0,a.kt)("p",null,"Linux >= 5.6 is the only production environment we\nsupport. But for ease of development we also support macOS and Windows."),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Go >= 1.21")),(0,a.kt)("p",null,(0,a.kt)("strong",{parentName:"p"},"Additionally on Windows"),": you must install ",(0,a.kt)("a",{parentName:"p",href:"https://ziglang.org/download/#release-0.13.0"},"Zig\n0.13.0")," and set the\n",(0,a.kt)("inlineCode",{parentName:"p"},"CC")," environment variable to ",(0,a.kt)("inlineCode",{parentName:"p"},"zig.exe cc"),". Use the full path for\n",(0,a.kt)("inlineCode",{parentName:"p"},"zig.exe"),"."),(0,a.kt)("h2",{id:"setup"},"Setup"),(0,a.kt)("p",null,"First, create a directory for your project and ",(0,a.kt)("inlineCode",{parentName:"p"},"cd")," into the directory."),(0,a.kt)("p",null,"Then, install the TigerBeetle client:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-console"},"go mod init tbtest\ngo get github.com/tigerbeetle/tigerbeetle-go\n")),(0,a.kt)("p",null,"Now, create ",(0,a.kt)("inlineCode",{parentName:"p"},"main.go")," and copy this into it:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'package main\n\nimport (\n "fmt"\n "log"\n "os"\n\n . "github.com/tigerbeetle/tigerbeetle-go"\n . "github.com/tigerbeetle/tigerbeetle-go/pkg/types"\n)\n\nfunc main() {\n fmt.Println("Import ok!")\n}\n\n')),(0,a.kt)("p",null,"Finally, build and run:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-console"},"go run main.go\n")),(0,a.kt)("p",null,"Now that all prerequisites and dependencies are correctly set\nup, let's dig into using TigerBeetle."),(0,a.kt)("h2",{id:"sample-projects"},"Sample projects"),(0,a.kt)("p",null,"This document is primarily a reference guide to\nthe client. Below are various sample projects demonstrating\nfeatures of TigerBeetle."),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"https://github.com/tigerbeetle/tigerbeetle/blob/main/src/clients/go/samples/basic/"},"Basic"),": Create two accounts and transfer an amount between them."),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"https://github.com/tigerbeetle/tigerbeetle/blob/main/src/clients/go/samples/two-phase/"},"Two-Phase Transfer"),": Create two accounts and start a pending transfer between\nthem, then post the transfer."),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"https://github.com/tigerbeetle/tigerbeetle/blob/main/src/clients/go/samples/two-phase-many/"},"Many Two-Phase Transfers"),": Create two accounts and start a number of pending transfer\nbetween them, posting and voiding alternating transfers.")),(0,a.kt)("h2",{id:"creating-a-client"},"Creating a Client"),(0,a.kt)("p",null,"A client is created with a cluster ID and replica\naddresses for all replicas in the cluster. The cluster\nID and replica addresses are both chosen by the system that\nstarts the TigerBeetle cluster."),(0,a.kt)("p",null,"Clients are thread-safe and a single instance should be shared\nbetween multiple concurrent tasks."),(0,a.kt)("p",null,"Multiple clients are useful when connecting to more than\none TigerBeetle cluster."),(0,a.kt)("p",null,"In this example the cluster ID is ",(0,a.kt)("inlineCode",{parentName:"p"},"0")," and there is one\nreplica. The address is read from the ",(0,a.kt)("inlineCode",{parentName:"p"},"TB_ADDRESS"),"\nenvironment variable and defaults to port ",(0,a.kt)("inlineCode",{parentName:"p"},"3000"),"."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'tbAddress := os.Getenv("TB_ADDRESS")\nif len(tbAddress) == 0 {\n tbAddress = "3000"\n}\nclient, err := NewClient(ToUint128(0), []string{tbAddress})\nif err != nil {\n log.Printf("Error creating client: %s", err)\n return\n}\ndefer client.Close()\n')),(0,a.kt)("p",null,"The following are valid addresses:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"3000")," (interpreted as ",(0,a.kt)("inlineCode",{parentName:"li"},"127.0.0.1:3000"),")"),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"127.0.0.1:3000")," (interpreted as ",(0,a.kt)("inlineCode",{parentName:"li"},"127.0.0.1:3000"),")"),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"127.0.0.1")," (interpreted as ",(0,a.kt)("inlineCode",{parentName:"li"},"127.0.0.1:3001"),", ",(0,a.kt)("inlineCode",{parentName:"li"},"3001")," is the default port)")),(0,a.kt)("h2",{id:"creating-accounts"},"Creating Accounts"),(0,a.kt)("p",null,"See details for account fields in the ",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/account"},"Accounts\nreference"),"."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'accountsRes, err := client.CreateAccounts([]Account{\n {\n ID: ToUint128(137),\n DebitsPending: ToUint128(0),\n DebitsPosted: ToUint128(0),\n CreditsPending: ToUint128(0),\n CreditsPosted: ToUint128(0),\n UserData128: ToUint128(0),\n UserData64: 0,\n UserData32: 0,\n Reserved: 0,\n Ledger: 1,\n Code: 718,\n Flags: 0,\n Timestamp: 0,\n },\n})\nif err != nil {\n log.Printf("Error creating accounts: %s", err)\n return\n}\n\nfor _, err := range accountsRes {\n log.Printf("Error creating account %d: %s", err.Index, err.Result)\n return\n}\n')),(0,a.kt)("p",null,"The ",(0,a.kt)("inlineCode",{parentName:"p"},"Uint128")," fields like ",(0,a.kt)("inlineCode",{parentName:"p"},"ID"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"UserData128"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"Amount")," and\naccount balances have a few helper functions to make it easier\nto convert 128-bit little-endian unsigned integers between\n",(0,a.kt)("inlineCode",{parentName:"p"},"string"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"math/big.Int"),", and ",(0,a.kt)("inlineCode",{parentName:"p"},"[]byte"),"."),(0,a.kt)("p",null,"See the type ",(0,a.kt)("a",{parentName:"p",href:"https://pkg.go.dev/github.com/tigerbeetle/tigerbeetle-go/pkg/types#Uint128"},"Uint128")," for more details."),(0,a.kt)("h3",{id:"account-flags"},"Account Flags"),(0,a.kt)("p",null,"The account flags value is a bitfield. See details for\nthese flags in the ",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/account#flags"},"Accounts\nreference"),"."),(0,a.kt)("p",null,"To toggle behavior for an account, use the ",(0,a.kt)("inlineCode",{parentName:"p"},"types.AccountFlags")," struct\nto combine enum values and generate a ",(0,a.kt)("inlineCode",{parentName:"p"},"uint16"),". Here are a\nfew examples:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"AccountFlags{Linked: true}.ToUint16()")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"AccountFlags{DebitsMustNotExceedCredits: true}.ToUint16()")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"AccountFlags{CreditsMustNotExceedDebits: true}.ToUint16()")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"AccountFlags{History: true}.ToUint16()"))),(0,a.kt)("p",null,"For example, to link two accounts where the first account\nadditionally has the ",(0,a.kt)("inlineCode",{parentName:"p"},"debits_must_not_exceed_credits")," constraint:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'account0 := Account{ /* ... account values ... */ }\naccount1 := Account{ /* ... account values ... */ }\naccount0.Flags = AccountFlags{Linked: true}.ToUint16()\n\naccountErrors, err := client.CreateAccounts([]Account{account0, account1})\nif err != nil {\n log.Printf("Error creating accounts: %s", err)\n return\n}\n')),(0,a.kt)("h3",{id:"response-and-errors"},"Response and Errors"),(0,a.kt)("p",null,"The response is an empty array if all accounts were\ncreated successfully. If the response is non-empty, each\nobject in the response array contains error information\nfor an account that failed. The error object contains an\nerror code and the index of the account in the request\nbatch."),(0,a.kt)("p",null,"See all error conditions in the ",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/requests/create_accounts"},"create_accounts\nreference"),"."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'account2 := Account{ /* ... account values ... */ }\naccount3 := Account{ /* ... account values ... */ }\naccount4 := Account{ /* ... account values ... */ }\n\naccountErrors, err = client.CreateAccounts([]Account{account2, account3, account4})\nif err != nil {\n log.Printf("Error creating accounts: %s", err)\n return\n}\nfor _, err := range accountErrors {\n log.Printf("Error creating account %d: %s", err.Index, err.Result)\n return\n}\n')),(0,a.kt)("p",null,"To handle errors you can either 1) exactly match error codes returned\nfrom ",(0,a.kt)("inlineCode",{parentName:"p"},"client.createAccounts")," with enum values in the\n",(0,a.kt)("inlineCode",{parentName:"p"},"CreateAccountError")," object, or you can 2) look up the error code in\nthe ",(0,a.kt)("inlineCode",{parentName:"p"},"CreateAccountError")," object for a human-readable string."),(0,a.kt)("h2",{id:"account-lookup"},"Account Lookup"),(0,a.kt)("p",null,"Account lookup is batched, like account creation. Pass\nin all IDs to fetch. The account for each matched ID is returned."),(0,a.kt)("p",null,"If no account matches an ID, no object is returned for\nthat account. So the order of accounts in the response is\nnot necessarily the same as the order of IDs in the\nrequest. You can refer to the ID field in the response to\ndistinguish accounts."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'accounts, err := client.LookupAccounts([]Uint128{ToUint128(137), ToUint128(138)})\nif err != nil {\n log.Printf("Could not fetch accounts: %s", err)\n return\n}\nlog.Println(accounts)\n')),(0,a.kt)("h2",{id:"create-transfers"},"Create Transfers"),(0,a.kt)("p",null,"This creates a journal entry between two accounts."),(0,a.kt)("p",null,"See details for transfer fields in the ",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/transfer"},"Transfers\nreference"),"."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'transfers := []Transfer{{\n ID: ToUint128(1),\n DebitAccountID: ToUint128(1),\n CreditAccountID: ToUint128(2),\n Amount: ToUint128(10),\n PendingID: ToUint128(0),\n UserData128: ToUint128(2),\n UserData64: 0,\n UserData32: 0,\n Timeout: 0,\n Ledger: 1,\n Code: 1,\n Flags: 0,\n Timestamp: 0,\n}}\n\ntransfersRes, err := client.CreateTransfers(transfers)\nif err != nil {\n log.Printf("Error creating transfer batch: %s", err)\n return\n}\n')),(0,a.kt)("h3",{id:"response-and-errors-1"},"Response and Errors"),(0,a.kt)("p",null,"The response is an empty array if all transfers were created\nsuccessfully. If the response is non-empty, each object in the\nresponse array contains error information for a transfer that\nfailed. The error object contains an error code and the index of the\ntransfer in the request batch."),(0,a.kt)("p",null,"See all error conditions in the ",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/requests/create_transfers"},"create_transfers\nreference"),"."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'for _, err := range transfersRes {\n log.Printf("Batch transfer at %d failed to create: %s", err.Index, err.Result)\n return\n}\n')),(0,a.kt)("h2",{id:"batching"},"Batching"),(0,a.kt)("p",null,"TigerBeetle performance is maximized when you batch\nAPI requests. The client does not do this automatically for\nyou. So, for example, you ",(0,a.kt)("em",{parentName:"p"},"can")," insert 1 million transfers\none at a time like so:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},"for i := 0; i < len(transfers); i++ {\n transfersRes, err = client.CreateTransfers([]Transfer{transfers[i]})\n // Error handling omitted.\n}\n")),(0,a.kt)("p",null,"But the insert rate will be a ",(0,a.kt)("em",{parentName:"p"},"fraction")," of\npotential. Instead, ",(0,a.kt)("strong",{parentName:"p"},"always batch what you can"),"."),(0,a.kt)("p",null,"The maximum batch size is set in the TigerBeetle server. The default\nis 8190."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},"BATCH_SIZE := 8190\nfor i := 0; i < len(transfers); i += BATCH_SIZE {\n batch := BATCH_SIZE\n if i+BATCH_SIZE > len(transfers) {\n batch = len(transfers) - i\n }\n transfersRes, err = client.CreateTransfers(transfers[i : i+batch])\n // Error handling omitted.\n}\n")),(0,a.kt)("h3",{id:"queues-and-workers"},"Queues and Workers"),(0,a.kt)("p",null,"If you are making requests to TigerBeetle from workers\npulling jobs from a queue, you can batch requests to\nTigerBeetle by having the worker act on multiple jobs from\nthe queue at once rather than one at a time. i.e. pulling\nmultiple jobs from the queue rather than just one."),(0,a.kt)("h2",{id:"transfer-flags"},"Transfer Flags"),(0,a.kt)("p",null,"The transfer ",(0,a.kt)("inlineCode",{parentName:"p"},"flags")," value is a bitfield. See details for these flags in\nthe ",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/transfer#flags"},"Transfers\nreference"),"."),(0,a.kt)("p",null,"To toggle behavior for an account, use the ",(0,a.kt)("inlineCode",{parentName:"p"},"types.TransferFlags")," struct\nto combine enum values and generate a ",(0,a.kt)("inlineCode",{parentName:"p"},"uint16"),". Here are a\nfew examples:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"TransferFlags{Linked: true}.ToUint16()")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"TransferFlags{Pending: true}.ToUint16()")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"TransferFlags{PostPendingTransfer: true}.ToUint16()")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"TransferFlags{VoidPendingTransfer: true}.ToUint16()"))),(0,a.kt)("p",null,"For example, to link ",(0,a.kt)("inlineCode",{parentName:"p"},"transfer0")," and ",(0,a.kt)("inlineCode",{parentName:"p"},"transfer1"),":"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},"transfer0 := Transfer{ /* ... account values ... */ }\ntransfer1 := Transfer{ /* ... account values ... */ }\ntransfer0.Flags = TransferFlags{Linked: true}.ToUint16()\ntransfersRes, err = client.CreateTransfers([]Transfer{transfer0, transfer1})\n// Error handling omitted.\n")),(0,a.kt)("h3",{id:"two-phase-transfers"},"Two-Phase Transfers"),(0,a.kt)("p",null,"Two-phase transfers are supported natively by toggling the appropriate\nflag. TigerBeetle will then adjust the ",(0,a.kt)("inlineCode",{parentName:"p"},"credits_pending")," and\n",(0,a.kt)("inlineCode",{parentName:"p"},"debits_pending")," fields of the appropriate accounts. A corresponding\npost pending transfer then needs to be sent to post or void the\ntransfer."),(0,a.kt)("h4",{id:"post-a-pending-transfer"},"Post a Pending Transfer"),(0,a.kt)("p",null,"With ",(0,a.kt)("inlineCode",{parentName:"p"},"flags")," set to ",(0,a.kt)("inlineCode",{parentName:"p"},"post_pending_transfer"),",\nTigerBeetle will post the transfer. TigerBeetle will atomically roll\nback the changes to ",(0,a.kt)("inlineCode",{parentName:"p"},"debits_pending")," and ",(0,a.kt)("inlineCode",{parentName:"p"},"credits_pending")," of the\nappropriate accounts and apply them to the ",(0,a.kt)("inlineCode",{parentName:"p"},"debits_posted")," and\n",(0,a.kt)("inlineCode",{parentName:"p"},"credits_posted")," balances."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},"transfer := Transfer{\n ID: ToUint128(2),\n // Post the entire pending amount.\n Amount: AmountMax,\n PendingID: ToUint128(1),\n Flags: TransferFlags{PostPendingTransfer: true}.ToUint16(),\n Timestamp: 0,\n}\ntransfersRes, err = client.CreateTransfers([]Transfer{transfer})\n// Error handling omitted.\n")),(0,a.kt)("h4",{id:"void-a-pending-transfer"},"Void a Pending Transfer"),(0,a.kt)("p",null,"In contrast, with ",(0,a.kt)("inlineCode",{parentName:"p"},"flags")," set to ",(0,a.kt)("inlineCode",{parentName:"p"},"void_pending_transfer"),",\nTigerBeetle will void the transfer. TigerBeetle will roll\nback the changes to ",(0,a.kt)("inlineCode",{parentName:"p"},"debits_pending")," and ",(0,a.kt)("inlineCode",{parentName:"p"},"credits_pending")," of the\nappropriate accounts and ",(0,a.kt)("strong",{parentName:"p"},"not")," apply them to the ",(0,a.kt)("inlineCode",{parentName:"p"},"debits_posted")," and\n",(0,a.kt)("inlineCode",{parentName:"p"},"credits_posted")," balances."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},"transfer = Transfer{\n ID: ToUint128(2),\n PendingID: ToUint128(1),\n Flags: TransferFlags{VoidPendingTransfer: true}.ToUint16(),\n Timestamp: 0,\n}\ntransfersRes, err = client.CreateTransfers([]Transfer{transfer})\n// Error handling omitted.\n")),(0,a.kt)("h2",{id:"transfer-lookup"},"Transfer Lookup"),(0,a.kt)("p",null,"NOTE: While transfer lookup exists, it is not a flexible query API. We\nare developing query APIs and there will be new methods for querying\ntransfers in the future."),(0,a.kt)("p",null,"Transfer lookup is batched, like transfer creation. Pass in all ",(0,a.kt)("inlineCode",{parentName:"p"},"id"),"s to\nfetch, and matched transfers are returned."),(0,a.kt)("p",null,"If no transfer matches an ",(0,a.kt)("inlineCode",{parentName:"p"},"id"),", no object is returned for that\ntransfer. So the order of transfers in the response is not necessarily\nthe same as the order of ",(0,a.kt)("inlineCode",{parentName:"p"},"id"),"s in the request. You can refer to the\n",(0,a.kt)("inlineCode",{parentName:"p"},"id")," field in the response to distinguish transfers."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'transfers, err = client.LookupTransfers([]Uint128{ToUint128(1), ToUint128(2)})\nif err != nil {\n log.Printf("Could not fetch transfers: %s", err)\n return\n}\nlog.Println(transfers)\n')),(0,a.kt)("h2",{id:"get-account-transfers"},"Get Account Transfers"),(0,a.kt)("p",null,"NOTE: This is a preview API that is subject to breaking changes once we have\na stable querying API."),(0,a.kt)("p",null,"Fetches the transfers involving a given account, allowing basic filter and pagination\ncapabilities."),(0,a.kt)("p",null,"The transfers in the response are sorted by ",(0,a.kt)("inlineCode",{parentName:"p"},"timestamp")," in chronological or\nreverse-chronological order."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'filter := AccountFilter{\n AccountID: ToUint128(2),\n UserData128: ToUint128(0), // No filter by UserData.\n UserData64: 0,\n UserData32: 0,\n Code: 0, // No filter by Code.\n TimestampMin: 0, // No filter by Timestamp.\n TimestampMax: 0, // No filter by Timestamp.\n Limit: 10, // Limit to ten transfers at most.\n Flags: AccountFilterFlags{\n Debits: true, // Include transfer from the debit side.\n Credits: true, // Include transfer from the credit side.\n Reversed: true, // Sort by timestamp in reverse-chronological order.\n }.ToUint32(),\n}\n\ntransfers, err = client.GetAccountTransfers(filter)\nif err != nil {\n log.Printf("Could not fetch transfers: %s", err)\n return\n}\nlog.Println(transfers)\n')),(0,a.kt)("h2",{id:"get-account-balances"},"Get Account Balances"),(0,a.kt)("p",null,"NOTE: This is a preview API that is subject to breaking changes once we have\na stable querying API."),(0,a.kt)("p",null,"Fetches the point-in-time balances of a given account, allowing basic filter and\npagination capabilities."),(0,a.kt)("p",null,"Only accounts created with the flag\n",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/account#flagshistory"},(0,a.kt)("inlineCode",{parentName:"a"},"history"))," set retain\n",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/requests/get_account_balances"},"historical balances"),"."),(0,a.kt)("p",null,"The balances in the response are sorted by ",(0,a.kt)("inlineCode",{parentName:"p"},"timestamp")," in chronological or\nreverse-chronological order."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'filter = AccountFilter{\n AccountID: ToUint128(2),\n UserData128: ToUint128(0), // No filter by UserData.\n UserData64: 0,\n UserData32: 0,\n Code: 0, // No filter by Code.\n TimestampMin: 0, // No filter by Timestamp.\n TimestampMax: 0, // No filter by Timestamp.\n Limit: 10, // Limit to ten balances at most.\n Flags: AccountFilterFlags{\n Debits: true, // Include transfer from the debit side.\n Credits: true, // Include transfer from the credit side.\n Reversed: true, // Sort by timestamp in reverse-chronological order.\n }.ToUint32(),\n}\n\naccount_balances, err := client.GetAccountBalances(filter)\nif err != nil {\n log.Printf("Could not fetch the history: %s", err)\n return\n}\nlog.Println(account_balances)\n')),(0,a.kt)("h2",{id:"query-accounts"},"Query Accounts"),(0,a.kt)("p",null,"NOTE: This is a preview API that is subject to breaking changes once we have\na stable querying API."),(0,a.kt)("p",null,"Query accounts by the intersection of some fields and by timestamp range."),(0,a.kt)("p",null,"The accounts in the response are sorted by ",(0,a.kt)("inlineCode",{parentName:"p"},"timestamp")," in chronological or\nreverse-chronological order."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'query_filter := QueryFilter{\n UserData128: ToUint128(1000), // Filter by UserData\n UserData64: 100,\n UserData32: 10,\n Code: 1, // Filter by Code\n Ledger: 0, // No filter by Ledger\n TimestampMin: 0, // No filter by Timestamp.\n TimestampMax: 0, // No filter by Timestamp.\n Limit: 10, // Limit to ten balances at most.\n Flags: QueryFilterFlags{\n Reversed: true, // Sort by timestamp in reverse-chronological order.\n }.ToUint32(),\n}\n\nquery_accounts, err := client.QueryAccounts(query_filter)\nif err != nil {\n log.Printf("Could not query accounts: %s", err)\n return\n}\nlog.Println(query_accounts)\n')),(0,a.kt)("h2",{id:"query-transfers"},"Query Transfers"),(0,a.kt)("p",null,"NOTE: This is a preview API that is subject to breaking changes once we have\na stable querying API."),(0,a.kt)("p",null,"Query transfers by the intersection of some fields and by timestamp range."),(0,a.kt)("p",null,"The transfers in the response are sorted by ",(0,a.kt)("inlineCode",{parentName:"p"},"timestamp")," in chronological or\nreverse-chronological order."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},'query_filter = QueryFilter{\n UserData128: ToUint128(1000), // Filter by UserData.\n UserData64: 100,\n UserData32: 10,\n Code: 1, // Filter by Code.\n Ledger: 0, // No filter by Ledger.\n TimestampMin: 0, // No filter by Timestamp.\n TimestampMax: 0, // No filter by Timestamp.\n Limit: 10, // Limit to ten balances at most.\n Flags: QueryFilterFlags{\n Reversed: true, // Sort by timestamp in reverse-chronological order.\n }.ToUint32(),\n}\n\nquery_transfers, err := client.QueryTransfers(query_filter)\nif err != nil {\n log.Printf("Could not query transfers: %s", err)\n return\n}\nlog.Println(query_transfers)\n')),(0,a.kt)("h2",{id:"linked-events"},"Linked Events"),(0,a.kt)("p",null,"When the ",(0,a.kt)("inlineCode",{parentName:"p"},"linked")," flag is specified for an account when creating accounts or\na transfer when creating transfers, it links that event with the next event in the\nbatch, to create a chain of events, of arbitrary length, which all\nsucceed or fail together. The tail of a chain is denoted by the first\nevent without this flag. The last event in a batch may therefore never\nhave the ",(0,a.kt)("inlineCode",{parentName:"p"},"linked")," flag set as this would leave a chain\nopen-ended. Multiple chains or individual events may coexist within a\nbatch to succeed or fail independently."),(0,a.kt)("p",null,"Events within a chain are executed within order, or are rolled back on\nerror, so that the effect of each event in the chain is visible to the\nnext, and so that the chain is either visible or invisible as a unit\nto subsequent events after the chain. The event that was the first to\nbreak the chain will have a unique error result. Other events in the\nchain will have their error result set to ",(0,a.kt)("inlineCode",{parentName:"p"},"linked_event_failed"),"."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},"batch := []Transfer{}\nlinkedFlag := TransferFlags{Linked: true}.ToUint16()\n\n// An individual transfer (successful):\nbatch = append(batch, Transfer{ID: ToUint128(1) /* ... rest of transfer ... */})\n\n// A chain of 4 transfers (the last transfer in the chain closes the chain with linked=false):\nbatch = append(batch, Transfer{ID: ToUint128(2) /* ... , */, Flags: linkedFlag}) // Commit/rollback.\nbatch = append(batch, Transfer{ID: ToUint128(3) /* ... , */, Flags: linkedFlag}) // Commit/rollback.\nbatch = append(batch, Transfer{ID: ToUint128(2) /* ... , */, Flags: linkedFlag}) // Fail with exists\nbatch = append(batch, Transfer{ID: ToUint128(4) /* ... , */}) // Fail without committing\n\n// An individual transfer (successful):\n// This should not see any effect from the failed chain above.\nbatch = append(batch, Transfer{ID: ToUint128(2) /* ... rest of transfer ... */})\n\n// A chain of 2 transfers (the first transfer fails the chain):\nbatch = append(batch, Transfer{ID: ToUint128(2) /* ... rest of transfer ... */, Flags: linkedFlag})\nbatch = append(batch, Transfer{ID: ToUint128(3) /* ... rest of transfer ... */})\n\n// A chain of 2 transfers (successful):\nbatch = append(batch, Transfer{ID: ToUint128(3) /* ... rest of transfer ... */, Flags: linkedFlag})\nbatch = append(batch, Transfer{ID: ToUint128(4) /* ... rest of transfer ... */})\n\ntransfersRes, err = client.CreateTransfers(batch)\n")),(0,a.kt)("h2",{id:"imported-events"},"Imported Events"),(0,a.kt)("p",null,"When the ",(0,a.kt)("inlineCode",{parentName:"p"},"imported")," flag is specified for an account when creating accounts or\na transfer when creating transfers, it allows importing historical events with\na user-defined timestamp."),(0,a.kt)("p",null,"The entire batch of events must be set with the flag ",(0,a.kt)("inlineCode",{parentName:"p"},"imported"),"."),(0,a.kt)("p",null,"It's recommended to submit the whole batch as a ",(0,a.kt)("inlineCode",{parentName:"p"},"linked")," chain of events, ensuring that\nif any event fails, none of them are committed, preserving the last timestamp unchanged.\nThis approach gives the application a chance to correct failed imported events, re-submitting\nthe batch again with the same user-defined timestamps."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-go"},"// First, load and import all accounts with their timestamps from the historical source.\naccountsBatch := []Account{}\nfor index, account := range historicalAccounts {\n // Set a unique and strictly increasing timestamp.\n historicalTimestamp += 1\n account.Timestamp = historicalTimestamp\n\n account.Flags = AccountFlags{\n // Set the account as `imported`.\n Imported: true,\n // To ensure atomicity, the entire batch (except the last event in the chain)\n // must be `linked`.\n Linked: index < len(historicalAccounts)-1,\n }.ToUint16()\n\n accountsBatch = append(accountsBatch, account)\n}\naccountsRes, err = client.CreateAccounts(accountsBatch)\n\n// Then, load and import all transfers with their timestamps from the historical source.\ntransfersBatch := []Transfer{}\nfor index, transfer := range historicalTransfers {\n // Set a unique and strictly increasing timestamp.\n historicalTimestamp += 1\n transfer.Timestamp = historicalTimestamp\n\n transfer.Flags = TransferFlags{\n // Set the transfer as `imported`.\n Imported: true,\n // To ensure atomicity, the entire batch (except the last event in the chain)\n // must be `linked`.\n Linked: index < len(historicalAccounts)-1,\n }.ToUint16()\n\n transfersBatch = append(transfersBatch, transfer)\n}\ntransfersRes, err = client.CreateTransfers(transfersBatch)\n// Error handling omitted..\n// Since it is a linked chain, in case of any error the entire batch is rolled back and can be retried\n// with the same historical timestamps without regressing the cluster timestamp.\n")))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/4fb5df53.33c30321.js b/assets/js/4fb5df53.a9e8a810.js similarity index 69% rename from assets/js/4fb5df53.33c30321.js rename to assets/js/4fb5df53.a9e8a810.js index 42a79dd..1d2be2d 100644 --- a/assets/js/4fb5df53.33c30321.js +++ b/assets/js/4fb5df53.a9e8a810.js @@ -1 +1 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[6994],{3905:(e,n,t)=>{t.d(n,{Zo:()=>d,kt:()=>f});var r=t(7294);function a(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function s(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);n&&(r=r.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,r)}return t}function i(e){for(var n=1;n=0||(a[t]=e[t]);return a}(e,n);if(Object.getOwnPropertySymbols){var s=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(a[t]=e[t])}return a}var o=r.createContext({}),c=function(e){var n=r.useContext(o),t=n;return e&&(t="function"==typeof e?e(n):i(i({},n),e)),t},d=function(e){var n=c(e.components);return r.createElement(o.Provider,{value:n},e.children)},u="mdxType",p={inlineCode:"code",wrapper:function(e){var n=e.children;return r.createElement(r.Fragment,{},n)}},h=r.forwardRef((function(e,n){var t=e.components,a=e.mdxType,s=e.originalType,o=e.parentName,d=l(e,["components","mdxType","originalType","parentName"]),u=c(t),h=a,f=u["".concat(o,".").concat(h)]||u[h]||p[h]||s;return t?r.createElement(f,i(i({ref:n},d),{},{components:t})):r.createElement(f,i({ref:n},d))}));function f(e,n){var t=arguments,a=n&&n.mdxType;if("string"==typeof e||a){var s=t.length,i=new Array(s);i[0]=h;var l={};for(var o in n)hasOwnProperty.call(n,o)&&(l[o]=n[o]);l.originalType=e,l[u]="string"==typeof e?e:a,i[1]=l;for(var c=2;c{t.r(n),t.d(n,{assets:()=>o,contentTitle:()=>i,default:()=>p,frontMatter:()=>s,metadata:()=>l,toc:()=>c});var r=t(7462),a=(t(7294),t(3905));const s={title:".NET"},i=void 0,l={unversionedId:"clients/dotnet",id:"clients/dotnet",title:".NET",description:"The TigerBeetle client for .NET.",source:"@site/pages/clients/dotnet.md",sourceDirName:"clients",slug:"/clients/dotnet",permalink:"/clients/dotnet",draft:!1,editUrl:"https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/clients/dotnet.md",tags:[],version:"current",frontMatter:{title:".NET"},sidebar:"tutorialSidebar",previous:{title:"Upgrading",permalink:"/operating/upgrading"},next:{title:"Go",permalink:"/clients/go"}},o={},c=[{value:"Prerequisites",id:"prerequisites",level:2},{value:"Setup",id:"setup",level:2},{value:"Sample projects",id:"sample-projects",level:2},{value:"Creating a Client",id:"creating-a-client",level:2},{value:"Creating Accounts",id:"creating-accounts",level:2},{value:"Account Flags",id:"account-flags",level:3},{value:"Response and Errors",id:"response-and-errors",level:3},{value:"Account Lookup",id:"account-lookup",level:2},{value:"Create Transfers",id:"create-transfers",level:2},{value:"Response and Errors",id:"response-and-errors-1",level:3},{value:"Batching",id:"batching",level:2},{value:"Queues and Workers",id:"queues-and-workers",level:3},{value:"Transfer Flags",id:"transfer-flags",level:2},{value:"Two-Phase Transfers",id:"two-phase-transfers",level:3},{value:"Post a Pending Transfer",id:"post-a-pending-transfer",level:4},{value:"Void a Pending Transfer",id:"void-a-pending-transfer",level:4},{value:"Transfer Lookup",id:"transfer-lookup",level:2},{value:"Get Account Transfers",id:"get-account-transfers",level:2},{value:"Get Account Balances",id:"get-account-balances",level:2},{value:"Query Accounts",id:"query-accounts",level:2},{value:"Query Transfers",id:"query-transfers",level:2},{value:"Linked Events",id:"linked-events",level:2},{value:"Imported Events",id:"imported-events",level:2}],d={toc:c},u="wrapper";function p(e){let{components:n,...t}=e;return(0,a.kt)(u,(0,r.Z)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,a.kt)("h1",{id:"tigerbeetle-dotnet"},"tigerbeetle-dotnet"),(0,a.kt)("p",null,"The TigerBeetle client for .NET."),(0,a.kt)("h2",{id:"prerequisites"},"Prerequisites"),(0,a.kt)("p",null,"Linux >= 5.6 is the only production environment we\nsupport. But for ease of development we also support macOS and Windows."),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},".NET >= 8.0.")),(0,a.kt)("p",null,"And if you do not already have NuGet.org as a package\nsource, make sure to add it:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-console"},"dotnet nuget add source https://api.nuget.org/v3/index.json -n nuget.org\n")),(0,a.kt)("h2",{id:"setup"},"Setup"),(0,a.kt)("p",null,"First, create a directory for your project and ",(0,a.kt)("inlineCode",{parentName:"p"},"cd")," into the directory."),(0,a.kt)("p",null,"Then, install the TigerBeetle client:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-console"},"dotnet new console\ndotnet add package tigerbeetle\n")),(0,a.kt)("p",null,"Now, create ",(0,a.kt)("inlineCode",{parentName:"p"},"Program.cs")," and copy this into it:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-cs"},'using System;\n\nusing TigerBeetle;\n\n// Validate import works.\nConsole.WriteLine("SUCCESS");\n')),(0,a.kt)("p",null,"Finally, build and run:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-console"},"dotnet run\n")),(0,a.kt)("p",null,"Now that all prerequisites and dependencies are correctly set\nup, let's dig into using TigerBeetle."),(0,a.kt)("h2",{id:"sample-projects"},"Sample projects"),(0,a.kt)("p",null,"This document is primarily a reference guide to\nthe client. Below are various sample projects demonstrating\nfeatures of TigerBeetle."),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"https://github.com/tigerbeetle/tigerbeetle/blob/main/src/clients/dotnet/samples/basic/"},"Basic"),": Create two accounts and transfer an amount between them."),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"https://github.com/tigerbeetle/tigerbeetle/blob/main/src/clients/dotnet/samples/two-phase/"},"Two-Phase Transfer"),": Create two accounts and start a pending transfer between\nthem, then post the transfer."),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"https://github.com/tigerbeetle/tigerbeetle/blob/main/src/clients/dotnet/samples/two-phase-many/"},"Many Two-Phase Transfers"),": Create two accounts and start a number of pending transfer\nbetween them, posting and voiding alternating transfers.")),(0,a.kt)("h2",{id:"creating-a-client"},"Creating a Client"),(0,a.kt)("p",null,"A client is created with a cluster ID and replica\naddresses for all replicas in the cluster. The cluster\nID and replica addresses are both chosen by the system that\nstarts the TigerBeetle cluster."),(0,a.kt)("p",null,"Clients are thread-safe and a single instance should be shared\nbetween multiple concurrent tasks."),(0,a.kt)("p",null,"Multiple clients are useful when connecting to more than\none TigerBeetle cluster."),(0,a.kt)("p",null,"In this example the cluster ID is ",(0,a.kt)("inlineCode",{parentName:"p"},"0")," and there is one\nreplica. The address is read from the ",(0,a.kt)("inlineCode",{parentName:"p"},"TB_ADDRESS"),"\nenvironment variable and defaults to port ",(0,a.kt)("inlineCode",{parentName:"p"},"3000"),"."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-cs"},'var tbAddress = Environment.GetEnvironmentVariable("TB_ADDRESS");\nvar clusterID = UInt128.Zero;\nvar addresses = new[] { tbAddress != null ? tbAddress : "3000" };\nusing (var client = new Client(clusterID, addresses))\n{\n // Use client\n}\n')),(0,a.kt)("p",null,"The ",(0,a.kt)("inlineCode",{parentName:"p"},"Client")," class is thread-safe and for better performance, a\nsingle instance should be shared between multiple concurrent\ntasks. Multiple clients can be instantiated in case of connecting\nto more than one TigerBeetle cluster."),(0,a.kt)("p",null,"The following are valid addresses:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"3000")," (interpreted as ",(0,a.kt)("inlineCode",{parentName:"li"},"127.0.0.1:3000"),")"),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"127.0.0.1:3000")," (interpreted as ",(0,a.kt)("inlineCode",{parentName:"li"},"127.0.0.1:3000"),")"),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"127.0.0.1")," (interpreted as ",(0,a.kt)("inlineCode",{parentName:"li"},"127.0.0.1:3001"),", ",(0,a.kt)("inlineCode",{parentName:"li"},"3001")," is the default port)")),(0,a.kt)("h2",{id:"creating-accounts"},"Creating Accounts"),(0,a.kt)("p",null,"See details for account fields in the ",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/account"},"Accounts\nreference"),"."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-cs"},"var accounts = new[] {\n new Account\n {\n Id = 137,\n UserData128 = Guid.NewGuid().ToUInt128(),\n UserData64 = 1000,\n UserData32 = 100,\n Ledger = 1,\n Code = 718,\n Flags = AccountFlags.None,\n },\n};\n\nvar createAccountsError = client.CreateAccounts(accounts);\n")),(0,a.kt)("p",null,"The ",(0,a.kt)("inlineCode",{parentName:"p"},"UInt128")," fields like ",(0,a.kt)("inlineCode",{parentName:"p"},"ID"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"UserData128"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"Amount")," and\naccount balances have a few extension methods to make it easier\nto convert 128-bit little-endian unsigned integers between\n",(0,a.kt)("inlineCode",{parentName:"p"},"BigInteger"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"byte[]"),", and ",(0,a.kt)("inlineCode",{parentName:"p"},"Guid"),"."),(0,a.kt)("p",null,"See the class ",(0,a.kt)("a",{parentName:"p",href:"https://github.com/tigerbeetle/tigerbeetle/blob/main/src/clients/dotnet/TigerBeetle/UInt128Extensions.cs"},"UInt128Extensions"),"\nfor more details."),(0,a.kt)("h3",{id:"account-flags"},"Account Flags"),(0,a.kt)("p",null,"The account flags value is a bitfield. See details for\nthese flags in the ",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/account#flags"},"Accounts\nreference"),"."),(0,a.kt)("p",null,"To toggle behavior for an account, combine enum values stored in the\n",(0,a.kt)("inlineCode",{parentName:"p"},"AccountFlags")," object with bitwise-or:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"AccountFlags.None")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"AccountFlags.Linked")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"AccountFlags.DebitsMustNotExceedCredits")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"AccountFlags.CreditsMustNotExceedDebits")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"AccountFlags.History"))),(0,a.kt)("p",null,"For example, to link two accounts where the first account\nadditionally has the ",(0,a.kt)("inlineCode",{parentName:"p"},"debits_must_not_exceed_credits")," constraint:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-cs"},"var account0 = new Account { /* ... account values ... */ };\nvar account1 = new Account { /* ... account values ... */ };\naccount0.Flags = AccountFlags.Linked;\n\ncreateAccountsError = client.CreateAccounts(new[] { account0, account1 });\n")),(0,a.kt)("h3",{id:"response-and-errors"},"Response and Errors"),(0,a.kt)("p",null,"The response is an empty array if all accounts were\ncreated successfully. If the response is non-empty, each\nobject in the response array contains error information\nfor an account that failed. The error object contains an\nerror code and the index of the account in the request\nbatch."),(0,a.kt)("p",null,"See all error conditions in the ",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/requests/create_accounts"},"create_accounts\nreference"),"."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-cs"},'var account2 = new Account { /* ... account values ... */ };\nvar account3 = new Account { /* ... account values ... */ };\nvar account4 = new Account { /* ... account values ... */ };\n\ncreateAccountsError = client.CreateAccounts(new[] { account2, account3, account4 });\nforeach (var error in createAccountsError)\n{\n Console.WriteLine("Error creating account {0}: {1}", error.Index, error.Result);\n return;\n}\n')),(0,a.kt)("h2",{id:"account-lookup"},"Account Lookup"),(0,a.kt)("p",null,"Account lookup is batched, like account creation. Pass\nin all IDs to fetch. The account for each matched ID is returned."),(0,a.kt)("p",null,"If no account matches an ID, no object is returned for\nthat account. So the order of accounts in the response is\nnot necessarily the same as the order of IDs in the\nrequest. You can refer to the ID field in the response to\ndistinguish accounts."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-cs"},"accounts = client.LookupAccounts(new UInt128[] { 137, 138 });\n")),(0,a.kt)("h2",{id:"create-transfers"},"Create Transfers"),(0,a.kt)("p",null,"This creates a journal entry between two accounts."),(0,a.kt)("p",null,"See details for transfer fields in the ",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/transfer"},"Transfers\nreference"),"."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-cs"},"var transfers = new[] {\n new Transfer\n {\n Id = 1,\n DebitAccountId = 1,\n CreditAccountId = 2,\n Amount = 10,\n UserData128 = 2000,\n UserData64 = 200,\n UserData32 = 2,\n Timeout = 0,\n Ledger = 1,\n Code = 1,\n Flags = TransferFlags.None,\n }\n};\n\nvar createTransfersError = client.CreateTransfers(transfers);\n")),(0,a.kt)("h3",{id:"response-and-errors-1"},"Response and Errors"),(0,a.kt)("p",null,"The response is an empty array if all transfers were created\nsuccessfully. If the response is non-empty, each object in the\nresponse array contains error information for a transfer that\nfailed. The error object contains an error code and the index of the\ntransfer in the request batch."),(0,a.kt)("p",null,"See all error conditions in the ",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/requests/create_transfers"},"create_transfers\nreference"),"."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-cs"},'foreach (var error in createTransfersError)\n{\n Console.WriteLine("Error creating account {0}: {1}", error.Index, error.Result);\n return;\n}\n')),(0,a.kt)("h2",{id:"batching"},"Batching"),(0,a.kt)("p",null,"TigerBeetle performance is maximized when you batch\nAPI requests. The client does not do this automatically for\nyou. So, for example, you ",(0,a.kt)("em",{parentName:"p"},"can")," insert 1 million transfers\none at a time like so:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-cs"},"foreach (var t in transfers)\n{\n createTransfersError = client.CreateTransfers(new[] { t });\n // Error handling omitted.\n}\n")),(0,a.kt)("p",null,"But the insert rate will be a ",(0,a.kt)("em",{parentName:"p"},"fraction")," of\npotential. Instead, ",(0,a.kt)("strong",{parentName:"p"},"always batch what you can"),"."),(0,a.kt)("p",null,"The maximum batch size is set in the TigerBeetle server. The default\nis 8190."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-cs"},"var BATCH_SIZE = 8190;\nfor (int i = 0; i < transfers.Length; i += BATCH_SIZE)\n{\n var batchSize = BATCH_SIZE;\n if (i + BATCH_SIZE > transfers.Length)\n {\n batchSize = transfers.Length - i;\n }\n createTransfersError = client.CreateTransfers(transfers[i..batchSize]);\n // Error handling omitted.\n}\n")),(0,a.kt)("h3",{id:"queues-and-workers"},"Queues and Workers"),(0,a.kt)("p",null,"If you are making requests to TigerBeetle from workers\npulling jobs from a queue, you can batch requests to\nTigerBeetle by having the worker act on multiple jobs from\nthe queue at once rather than one at a time. i.e. pulling\nmultiple jobs from the queue rather than just one."),(0,a.kt)("h2",{id:"transfer-flags"},"Transfer Flags"),(0,a.kt)("p",null,"The transfer ",(0,a.kt)("inlineCode",{parentName:"p"},"flags")," value is a bitfield. See details for these flags in\nthe ",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/transfer#flags"},"Transfers\nreference"),"."),(0,a.kt)("p",null,"To toggle behavior for an account, combine enum values stored in the\n",(0,a.kt)("inlineCode",{parentName:"p"},"TransferFlags")," object with bitwise-or:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"TransferFlags.None")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"TransferFlags.Linked")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"TransferFlags.Pending")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"TransferFlags.PostPendingTransfer")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"TransferFlags.VoidPendingTransfer"))),(0,a.kt)("p",null,"For example, to link ",(0,a.kt)("inlineCode",{parentName:"p"},"transfer0")," and ",(0,a.kt)("inlineCode",{parentName:"p"},"transfer1"),":"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-cs"},"var transfer0 = new Transfer { /* ... account values ... */ };\nvar transfer1 = new Transfer { /* ... account values ... */ };\ntransfer0.Flags = TransferFlags.Linked;\ncreateTransfersError = client.CreateTransfers(new Transfer[] { transfer0, transfer1 });\n\n")),(0,a.kt)("h3",{id:"two-phase-transfers"},"Two-Phase Transfers"),(0,a.kt)("p",null,"Two-phase transfers are supported natively by toggling the appropriate\nflag. TigerBeetle will then adjust the ",(0,a.kt)("inlineCode",{parentName:"p"},"credits_pending")," and\n",(0,a.kt)("inlineCode",{parentName:"p"},"debits_pending")," fields of the appropriate accounts. A corresponding\npost pending transfer then needs to be sent to post or void the\ntransfer."),(0,a.kt)("h4",{id:"post-a-pending-transfer"},"Post a Pending Transfer"),(0,a.kt)("p",null,"With ",(0,a.kt)("inlineCode",{parentName:"p"},"flags")," set to ",(0,a.kt)("inlineCode",{parentName:"p"},"post_pending_transfer"),",\nTigerBeetle will post the transfer. TigerBeetle will atomically roll\nback the changes to ",(0,a.kt)("inlineCode",{parentName:"p"},"debits_pending")," and ",(0,a.kt)("inlineCode",{parentName:"p"},"credits_pending")," of the\nappropriate accounts and apply them to the ",(0,a.kt)("inlineCode",{parentName:"p"},"debits_posted")," and\n",(0,a.kt)("inlineCode",{parentName:"p"},"credits_posted")," balances."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-cs"},"transfers = new Transfer[] { new Transfer {\n Id = 2,\n // Post the entire pending amount.\n Amount = Transfer.AmountMax,\n PendingId = 1,\n Flags = TransferFlags.PostPendingTransfer,\n}};\n\ncreateTransfersError = client.CreateTransfers(transfers);\n// Error handling omitted.\n")),(0,a.kt)("h4",{id:"void-a-pending-transfer"},"Void a Pending Transfer"),(0,a.kt)("p",null,"In contrast, with ",(0,a.kt)("inlineCode",{parentName:"p"},"flags")," set to ",(0,a.kt)("inlineCode",{parentName:"p"},"void_pending_transfer"),",\nTigerBeetle will void the transfer. TigerBeetle will roll\nback the changes to ",(0,a.kt)("inlineCode",{parentName:"p"},"debits_pending")," and ",(0,a.kt)("inlineCode",{parentName:"p"},"credits_pending")," of the\nappropriate accounts and ",(0,a.kt)("strong",{parentName:"p"},"not")," apply them to the ",(0,a.kt)("inlineCode",{parentName:"p"},"debits_posted")," and\n",(0,a.kt)("inlineCode",{parentName:"p"},"credits_posted")," balances."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-cs"},"transfers = new Transfer[] { new Transfer {\n Id = 2,\n PendingId = 1,\n Flags = TransferFlags.PostPendingTransfer,\n}};\n\ncreateTransfersError = client.CreateTransfers(transfers);\n// Error handling omitted.\n")),(0,a.kt)("h2",{id:"transfer-lookup"},"Transfer Lookup"),(0,a.kt)("p",null,"NOTE: While transfer lookup exists, it is not a flexible query API. We\nare developing query APIs and there will be new methods for querying\ntransfers in the future."),(0,a.kt)("p",null,"Transfer lookup is batched, like transfer creation. Pass in all ",(0,a.kt)("inlineCode",{parentName:"p"},"id"),"s to\nfetch, and matched transfers are returned."),(0,a.kt)("p",null,"If no transfer matches an ",(0,a.kt)("inlineCode",{parentName:"p"},"id"),", no object is returned for that\ntransfer. So the order of transfers in the response is not necessarily\nthe same as the order of ",(0,a.kt)("inlineCode",{parentName:"p"},"id"),"s in the request. You can refer to the\n",(0,a.kt)("inlineCode",{parentName:"p"},"id")," field in the response to distinguish transfers."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-cs"},"transfers = client.LookupTransfers(new UInt128[] { 1, 2 });\n")),(0,a.kt)("h2",{id:"get-account-transfers"},"Get Account Transfers"),(0,a.kt)("p",null,"NOTE: This is a preview API that is subject to breaking changes once we have\na stable querying API."),(0,a.kt)("p",null,"Fetches the transfers involving a given account, allowing basic filter and pagination\ncapabilities."),(0,a.kt)("p",null,"The transfers in the response are sorted by ",(0,a.kt)("inlineCode",{parentName:"p"},"timestamp")," in chronological or\nreverse-chronological order."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-cs"},"var filter = new AccountFilter\n{\n AccountId = 2,\n TimestampMin = 0, // No filter by Timestamp.\n TimestampMax = 0, // No filter by Timestamp.\n Limit = 10, // Limit to ten transfers at most.\n Flags = AccountFilterFlags.Debits | // Include transfer from the debit side.\n AccountFilterFlags.Credits | // Include transfer from the credit side.\n AccountFilterFlags.Reversed, // Sort by timestamp in reverse-chronological order.\n};\n\ntransfers = client.GetAccountTransfers(filter);\n")),(0,a.kt)("h2",{id:"get-account-balances"},"Get Account Balances"),(0,a.kt)("p",null,"NOTE: This is a preview API that is subject to breaking changes once we have\na stable querying API."),(0,a.kt)("p",null,"Fetches the point-in-time balances of a given account, allowing basic filter and\npagination capabilities."),(0,a.kt)("p",null,"Only accounts created with the flag\n",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/account#flagshistory"},(0,a.kt)("inlineCode",{parentName:"a"},"history"))," set retain\n",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/requests/get_account_balances"},"historical balances"),"."),(0,a.kt)("p",null,"The balances in the response are sorted by ",(0,a.kt)("inlineCode",{parentName:"p"},"timestamp")," in chronological or\nreverse-chronological order."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-cs"},"filter = new AccountFilter\n{\n AccountId = 2,\n TimestampMin = 0, // No filter by Timestamp.\n TimestampMax = 0, // No filter by Timestamp.\n Limit = 10, // Limit to ten balances at most.\n Flags = AccountFilterFlags.Debits | // Include transfer from the debit side.\n AccountFilterFlags.Credits | // Include transfer from the credit side.\n AccountFilterFlags.Reversed, // Sort by timestamp in reverse-chronological order.\n};\n\nvar account_balances = client.GetAccountBalances(filter);\n")),(0,a.kt)("h2",{id:"query-accounts"},"Query Accounts"),(0,a.kt)("p",null,"NOTE: This is a preview API that is subject to breaking changes once we have\na stable querying API."),(0,a.kt)("p",null,"Query accounts by the intersection of some fields and by timestamp range."),(0,a.kt)("p",null,"The accounts in the response are sorted by ",(0,a.kt)("inlineCode",{parentName:"p"},"timestamp")," in chronological or\nreverse-chronological order."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-cs"},"var query_filter = new QueryFilter\n{\n UserData128 = 1000, // Filter by UserData.\n UserData64 = 100,\n UserData32 = 10,\n Code = 1, // Filter by Code.\n Ledger = 0, // No filter by Ledger.\n TimestampMin = 0, // No filter by Timestamp.\n TimestampMax = 0, // No filter by Timestamp.\n Limit = 10, // Limit to ten balances at most.\n Flags = QueryFilterFlags.Reversed, // Sort by timestamp in reverse-chronological order.\n};\n\nvar query_accounts = client.QueryAccounts(query_filter);\n")),(0,a.kt)("h2",{id:"query-transfers"},"Query Transfers"),(0,a.kt)("p",null,"NOTE: This is a preview API that is subject to breaking changes once we have\na stable querying API."),(0,a.kt)("p",null,"Query transfers by the intersection of some fields and by timestamp range."),(0,a.kt)("p",null,"The transfers in the response are sorted by ",(0,a.kt)("inlineCode",{parentName:"p"},"timestamp")," in chronological or\nreverse-chronological order."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-cs"},"query_filter = new QueryFilter\n{\n UserData128 = 1000, // Filter by UserData\n UserData64 = 100,\n UserData32 = 10,\n Code = 1, // Filter by Code\n Ledger = 0, // No filter by Ledger\n TimestampMin = 0, // No filter by Timestamp.\n TimestampMax = 0, // No filter by Timestamp.\n Limit = 10, // Limit to ten balances at most.\n Flags = QueryFilterFlags.Reversed, // Sort by timestamp in reverse-chronological order.\n};\n\nvar query_transfers = client.QueryTransfers(query_filter);\n")),(0,a.kt)("h2",{id:"linked-events"},"Linked Events"),(0,a.kt)("p",null,"When the ",(0,a.kt)("inlineCode",{parentName:"p"},"linked")," flag is specified for an account when creating accounts or\na transfer when creating transfers, it links that event with the next event in the\nbatch, to create a chain of events, of arbitrary length, which all\nsucceed or fail together. The tail of a chain is denoted by the first\nevent without this flag. The last event in a batch may therefore never\nhave the ",(0,a.kt)("inlineCode",{parentName:"p"},"linked")," flag set as this would leave a chain\nopen-ended. Multiple chains or individual events may coexist within a\nbatch to succeed or fail independently."),(0,a.kt)("p",null,"Events within a chain are executed within order, or are rolled back on\nerror, so that the effect of each event in the chain is visible to the\nnext, and so that the chain is either visible or invisible as a unit\nto subsequent events after the chain. The event that was the first to\nbreak the chain will have a unique error result. Other events in the\nchain will have their error result set to ",(0,a.kt)("inlineCode",{parentName:"p"},"linked_event_failed"),"."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-cs"},"var batch = new System.Collections.Generic.List();\n\n// An individual transfer (successful):\nbatch.Add(new Transfer { Id = 1, /* ... rest of transfer ... */ });\n\n// A chain of 4 transfers (the last transfer in the chain closes the chain with linked=false):\nbatch.Add(new Transfer { Id = 2, /* ... rest of transfer ... */ Flags = TransferFlags.Linked }); // Commit/rollback.\nbatch.Add(new Transfer { Id = 3, /* ... rest of transfer ... */ Flags = TransferFlags.Linked }); // Commit/rollback.\nbatch.Add(new Transfer { Id = 2, /* ... rest of transfer ... */ Flags = TransferFlags.Linked }); // Fail with exists\nbatch.Add(new Transfer { Id = 4, /* ... rest of transfer ... */ }); // Fail without committing\n\n// An individual transfer (successful):\n// This should not see any effect from the failed chain above.\nbatch.Add(new Transfer { Id = 2, /* ... rest of transfer ... */ });\n\n// A chain of 2 transfers (the first transfer fails the chain):\nbatch.Add(new Transfer { Id = 2, /* ... rest of transfer ... */ Flags = TransferFlags.Linked });\nbatch.Add(new Transfer { Id = 3, /* ... rest of transfer ... */ });\n\n// A chain of 2 transfers (successful):\nbatch.Add(new Transfer { Id = 3, /* ... rest of transfer ... */ Flags = TransferFlags.Linked });\nbatch.Add(new Transfer { Id = 4, /* ... rest of transfer ... */ });\n\ncreateTransfersError = client.CreateTransfers(batch.ToArray());\n// Error handling omitted.\n")),(0,a.kt)("h2",{id:"imported-events"},"Imported Events"),(0,a.kt)("p",null,"When the ",(0,a.kt)("inlineCode",{parentName:"p"},"imported")," flag is specified for an account when creating accounts or\na transfer when creating transfers, it allows importing historical events with\na user-defined timestamp."),(0,a.kt)("p",null,"The entire batch of events must be set with the flag ",(0,a.kt)("inlineCode",{parentName:"p"},"imported"),"."),(0,a.kt)("p",null,"It's recommended to submit the whole batch as a ",(0,a.kt)("inlineCode",{parentName:"p"},"linked")," chain of events, ensuring that\nif any event fails, none of them are committed, preserving the last timestamp unchanged.\nThis approach gives the application a chance to correct failed imported events, re-submitting\nthe batch again with the same user-defined timestamps."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-cs"},"// First, load and import all accounts with their timestamps from the historical source.\nvar accountsBatch = new System.Collections.Generic.List();\nfor (var index = 0; index < historicalAccounts.Length; index++)\n{\n var account = historicalAccounts[index];\n\n // Set a unique and strictly increasing timestamp.\n historicalTimestamp += 1;\n account.Timestamp = historicalTimestamp;\n // Set the account as `imported`.\n account.Flags = AccountFlags.Imported;\n // To ensure atomicity, the entire batch (except the last event in the chain)\n // must be `linked`.\n if (index < historicalAccounts.Length - 1)\n {\n account.Flags |= AccountFlags.Linked;\n }\n\n accountsBatch.Add(account);\n}\n\ncreateAccountsError = client.CreateAccounts(accountsBatch.ToArray());\n// Error handling omitted.\n\n// Then, load and import all transfers with their timestamps from the historical source.\nvar transfersBatch = new System.Collections.Generic.List();\nfor (var index = 0; index < historicalTransfers.Length; index++)\n{\n var transfer = historicalTransfers[index];\n\n // Set a unique and strictly increasing timestamp.\n historicalTimestamp += 1;\n transfer.Timestamp = historicalTimestamp;\n // Set the account as `imported`.\n transfer.Flags = TransferFlags.Imported;\n // To ensure atomicity, the entire batch (except the last event in the chain)\n // must be `linked`.\n if (index < historicalTransfers.Length - 1)\n {\n transfer.Flags |= TransferFlags.Linked;\n }\n\n transfersBatch.Add(transfer);\n}\n\ncreateTransfersError = client.CreateTransfers(transfersBatch.ToArray());\n// Error handling omitted.\n// Since it is a linked chain, in case of any error the entire batch is rolled back and can be retried\n// with the same historical timestamps without regressing the cluster timestamp.\n")))}p.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[6994],{3905:(e,n,t)=>{t.d(n,{Zo:()=>d,kt:()=>f});var r=t(7294);function a(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function s(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);n&&(r=r.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,r)}return t}function i(e){for(var n=1;n=0||(a[t]=e[t]);return a}(e,n);if(Object.getOwnPropertySymbols){var s=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(a[t]=e[t])}return a}var o=r.createContext({}),c=function(e){var n=r.useContext(o),t=n;return e&&(t="function"==typeof e?e(n):i(i({},n),e)),t},d=function(e){var n=c(e.components);return r.createElement(o.Provider,{value:n},e.children)},u="mdxType",p={inlineCode:"code",wrapper:function(e){var n=e.children;return r.createElement(r.Fragment,{},n)}},h=r.forwardRef((function(e,n){var t=e.components,a=e.mdxType,s=e.originalType,o=e.parentName,d=l(e,["components","mdxType","originalType","parentName"]),u=c(t),h=a,f=u["".concat(o,".").concat(h)]||u[h]||p[h]||s;return t?r.createElement(f,i(i({ref:n},d),{},{components:t})):r.createElement(f,i({ref:n},d))}));function f(e,n){var t=arguments,a=n&&n.mdxType;if("string"==typeof e||a){var s=t.length,i=new Array(s);i[0]=h;var l={};for(var o in n)hasOwnProperty.call(n,o)&&(l[o]=n[o]);l.originalType=e,l[u]="string"==typeof e?e:a,i[1]=l;for(var c=2;c{t.r(n),t.d(n,{assets:()=>o,contentTitle:()=>i,default:()=>p,frontMatter:()=>s,metadata:()=>l,toc:()=>c});var r=t(7462),a=(t(7294),t(3905));const s={title:".NET"},i=void 0,l={unversionedId:"clients/dotnet",id:"clients/dotnet",title:".NET",description:"The TigerBeetle client for .NET.",source:"@site/pages/clients/dotnet.md",sourceDirName:"clients",slug:"/clients/dotnet",permalink:"/clients/dotnet",draft:!1,editUrl:"https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/clients/dotnet.md",tags:[],version:"current",frontMatter:{title:".NET"},sidebar:"tutorialSidebar",previous:{title:"Upgrading",permalink:"/operating/upgrading"},next:{title:"Go",permalink:"/clients/go"}},o={},c=[{value:"Prerequisites",id:"prerequisites",level:2},{value:"Setup",id:"setup",level:2},{value:"Sample projects",id:"sample-projects",level:2},{value:"Creating a Client",id:"creating-a-client",level:2},{value:"Creating Accounts",id:"creating-accounts",level:2},{value:"Account Flags",id:"account-flags",level:3},{value:"Response and Errors",id:"response-and-errors",level:3},{value:"Account Lookup",id:"account-lookup",level:2},{value:"Create Transfers",id:"create-transfers",level:2},{value:"Response and Errors",id:"response-and-errors-1",level:3},{value:"Batching",id:"batching",level:2},{value:"Queues and Workers",id:"queues-and-workers",level:3},{value:"Transfer Flags",id:"transfer-flags",level:2},{value:"Two-Phase Transfers",id:"two-phase-transfers",level:3},{value:"Post a Pending Transfer",id:"post-a-pending-transfer",level:4},{value:"Void a Pending Transfer",id:"void-a-pending-transfer",level:4},{value:"Transfer Lookup",id:"transfer-lookup",level:2},{value:"Get Account Transfers",id:"get-account-transfers",level:2},{value:"Get Account Balances",id:"get-account-balances",level:2},{value:"Query Accounts",id:"query-accounts",level:2},{value:"Query Transfers",id:"query-transfers",level:2},{value:"Linked Events",id:"linked-events",level:2},{value:"Imported Events",id:"imported-events",level:2}],d={toc:c},u="wrapper";function p(e){let{components:n,...t}=e;return(0,a.kt)(u,(0,r.Z)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,a.kt)("h1",{id:"tigerbeetle-dotnet"},"tigerbeetle-dotnet"),(0,a.kt)("p",null,"The TigerBeetle client for .NET."),(0,a.kt)("h2",{id:"prerequisites"},"Prerequisites"),(0,a.kt)("p",null,"Linux >= 5.6 is the only production environment we\nsupport. But for ease of development we also support macOS and Windows."),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},".NET >= 8.0.")),(0,a.kt)("p",null,"And if you do not already have NuGet.org as a package\nsource, make sure to add it:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-console"},"dotnet nuget add source https://api.nuget.org/v3/index.json -n nuget.org\n")),(0,a.kt)("h2",{id:"setup"},"Setup"),(0,a.kt)("p",null,"First, create a directory for your project and ",(0,a.kt)("inlineCode",{parentName:"p"},"cd")," into the directory."),(0,a.kt)("p",null,"Then, install the TigerBeetle client:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-console"},"dotnet new console\ndotnet add package tigerbeetle\n")),(0,a.kt)("p",null,"Now, create ",(0,a.kt)("inlineCode",{parentName:"p"},"Program.cs")," and copy this into it:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-cs"},'using System;\n\nusing TigerBeetle;\n\n// Validate import works.\nConsole.WriteLine("SUCCESS");\n')),(0,a.kt)("p",null,"Finally, build and run:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-console"},"dotnet run\n")),(0,a.kt)("p",null,"Now that all prerequisites and dependencies are correctly set\nup, let's dig into using TigerBeetle."),(0,a.kt)("h2",{id:"sample-projects"},"Sample projects"),(0,a.kt)("p",null,"This document is primarily a reference guide to\nthe client. Below are various sample projects demonstrating\nfeatures of TigerBeetle."),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"https://github.com/tigerbeetle/tigerbeetle/blob/main/src/clients/dotnet/samples/basic/"},"Basic"),": Create two accounts and transfer an amount between them."),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"https://github.com/tigerbeetle/tigerbeetle/blob/main/src/clients/dotnet/samples/two-phase/"},"Two-Phase Transfer"),": Create two accounts and start a pending transfer between\nthem, then post the transfer."),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"https://github.com/tigerbeetle/tigerbeetle/blob/main/src/clients/dotnet/samples/two-phase-many/"},"Many Two-Phase Transfers"),": Create two accounts and start a number of pending transfer\nbetween them, posting and voiding alternating transfers.")),(0,a.kt)("h2",{id:"creating-a-client"},"Creating a Client"),(0,a.kt)("p",null,"A client is created with a cluster ID and replica\naddresses for all replicas in the cluster. The cluster\nID and replica addresses are both chosen by the system that\nstarts the TigerBeetle cluster."),(0,a.kt)("p",null,"Clients are thread-safe and a single instance should be shared\nbetween multiple concurrent tasks."),(0,a.kt)("p",null,"Multiple clients are useful when connecting to more than\none TigerBeetle cluster."),(0,a.kt)("p",null,"In this example the cluster ID is ",(0,a.kt)("inlineCode",{parentName:"p"},"0")," and there is one\nreplica. The address is read from the ",(0,a.kt)("inlineCode",{parentName:"p"},"TB_ADDRESS"),"\nenvironment variable and defaults to port ",(0,a.kt)("inlineCode",{parentName:"p"},"3000"),"."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-cs"},'var tbAddress = Environment.GetEnvironmentVariable("TB_ADDRESS");\nvar clusterID = UInt128.Zero;\nvar addresses = new[] { tbAddress != null ? tbAddress : "3000" };\nusing (var client = new Client(clusterID, addresses))\n{\n // Use client\n}\n')),(0,a.kt)("p",null,"The ",(0,a.kt)("inlineCode",{parentName:"p"},"Client")," class is thread-safe and for better performance, a\nsingle instance should be shared between multiple concurrent\ntasks. Multiple clients can be instantiated in case of connecting\nto more than one TigerBeetle cluster."),(0,a.kt)("p",null,"The following are valid addresses:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"3000")," (interpreted as ",(0,a.kt)("inlineCode",{parentName:"li"},"127.0.0.1:3000"),")"),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"127.0.0.1:3000")," (interpreted as ",(0,a.kt)("inlineCode",{parentName:"li"},"127.0.0.1:3000"),")"),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"127.0.0.1")," (interpreted as ",(0,a.kt)("inlineCode",{parentName:"li"},"127.0.0.1:3001"),", ",(0,a.kt)("inlineCode",{parentName:"li"},"3001")," is the default port)")),(0,a.kt)("h2",{id:"creating-accounts"},"Creating Accounts"),(0,a.kt)("p",null,"See details for account fields in the ",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/account"},"Accounts\nreference"),"."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-cs"},"var accounts = new[] {\n new Account\n {\n Id = 137,\n UserData128 = Guid.NewGuid().ToUInt128(),\n UserData64 = 1000,\n UserData32 = 100,\n Ledger = 1,\n Code = 718,\n Flags = AccountFlags.None,\n },\n};\n\nvar createAccountsError = client.CreateAccounts(accounts);\n")),(0,a.kt)("p",null,"The ",(0,a.kt)("inlineCode",{parentName:"p"},"UInt128")," fields like ",(0,a.kt)("inlineCode",{parentName:"p"},"ID"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"UserData128"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"Amount")," and\naccount balances have a few extension methods to make it easier\nto convert 128-bit little-endian unsigned integers between\n",(0,a.kt)("inlineCode",{parentName:"p"},"BigInteger"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"byte[]"),", and ",(0,a.kt)("inlineCode",{parentName:"p"},"Guid"),"."),(0,a.kt)("p",null,"See the class ",(0,a.kt)("a",{parentName:"p",href:"https://github.com/tigerbeetle/tigerbeetle/blob/main/src/clients/dotnet/TigerBeetle/UInt128Extensions.cs"},"UInt128Extensions"),"\nfor more details."),(0,a.kt)("h3",{id:"account-flags"},"Account Flags"),(0,a.kt)("p",null,"The account flags value is a bitfield. See details for\nthese flags in the ",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/account#flags"},"Accounts\nreference"),"."),(0,a.kt)("p",null,"To toggle behavior for an account, combine enum values stored in the\n",(0,a.kt)("inlineCode",{parentName:"p"},"AccountFlags")," object with bitwise-or:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"AccountFlags.None")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"AccountFlags.Linked")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"AccountFlags.DebitsMustNotExceedCredits")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"AccountFlags.CreditsMustNotExceedDebits")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"AccountFlags.History"))),(0,a.kt)("p",null,"For example, to link two accounts where the first account\nadditionally has the ",(0,a.kt)("inlineCode",{parentName:"p"},"debits_must_not_exceed_credits")," constraint:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-cs"},"var account0 = new Account { /* ... account values ... */ };\nvar account1 = new Account { /* ... account values ... */ };\naccount0.Flags = AccountFlags.Linked;\n\ncreateAccountsError = client.CreateAccounts(new[] { account0, account1 });\n")),(0,a.kt)("h3",{id:"response-and-errors"},"Response and Errors"),(0,a.kt)("p",null,"The response is an empty array if all accounts were\ncreated successfully. If the response is non-empty, each\nobject in the response array contains error information\nfor an account that failed. The error object contains an\nerror code and the index of the account in the request\nbatch."),(0,a.kt)("p",null,"See all error conditions in the ",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/requests/create_accounts"},"create_accounts\nreference"),"."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-cs"},'var account2 = new Account { /* ... account values ... */ };\nvar account3 = new Account { /* ... account values ... */ };\nvar account4 = new Account { /* ... account values ... */ };\n\ncreateAccountsError = client.CreateAccounts(new[] { account2, account3, account4 });\nforeach (var error in createAccountsError)\n{\n Console.WriteLine("Error creating account {0}: {1}", error.Index, error.Result);\n return;\n}\n')),(0,a.kt)("h2",{id:"account-lookup"},"Account Lookup"),(0,a.kt)("p",null,"Account lookup is batched, like account creation. Pass\nin all IDs to fetch. The account for each matched ID is returned."),(0,a.kt)("p",null,"If no account matches an ID, no object is returned for\nthat account. So the order of accounts in the response is\nnot necessarily the same as the order of IDs in the\nrequest. You can refer to the ID field in the response to\ndistinguish accounts."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-cs"},"accounts = client.LookupAccounts(new UInt128[] { 137, 138 });\n")),(0,a.kt)("h2",{id:"create-transfers"},"Create Transfers"),(0,a.kt)("p",null,"This creates a journal entry between two accounts."),(0,a.kt)("p",null,"See details for transfer fields in the ",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/transfer"},"Transfers\nreference"),"."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-cs"},"var transfers = new[] {\n new Transfer\n {\n Id = 1,\n DebitAccountId = 1,\n CreditAccountId = 2,\n Amount = 10,\n UserData128 = 2000,\n UserData64 = 200,\n UserData32 = 2,\n Timeout = 0,\n Ledger = 1,\n Code = 1,\n Flags = TransferFlags.None,\n }\n};\n\nvar createTransfersError = client.CreateTransfers(transfers);\n")),(0,a.kt)("h3",{id:"response-and-errors-1"},"Response and Errors"),(0,a.kt)("p",null,"The response is an empty array if all transfers were created\nsuccessfully. If the response is non-empty, each object in the\nresponse array contains error information for a transfer that\nfailed. The error object contains an error code and the index of the\ntransfer in the request batch."),(0,a.kt)("p",null,"See all error conditions in the ",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/requests/create_transfers"},"create_transfers\nreference"),"."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-cs"},'foreach (var error in createTransfersError)\n{\n Console.WriteLine("Error creating account {0}: {1}", error.Index, error.Result);\n return;\n}\n')),(0,a.kt)("h2",{id:"batching"},"Batching"),(0,a.kt)("p",null,"TigerBeetle performance is maximized when you batch\nAPI requests. The client does not do this automatically for\nyou. So, for example, you ",(0,a.kt)("em",{parentName:"p"},"can")," insert 1 million transfers\none at a time like so:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-cs"},"foreach (var t in transfers)\n{\n createTransfersError = client.CreateTransfers(new[] { t });\n // Error handling omitted.\n}\n")),(0,a.kt)("p",null,"But the insert rate will be a ",(0,a.kt)("em",{parentName:"p"},"fraction")," of\npotential. Instead, ",(0,a.kt)("strong",{parentName:"p"},"always batch what you can"),"."),(0,a.kt)("p",null,"The maximum batch size is set in the TigerBeetle server. The default\nis 8190."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-cs"},"var BATCH_SIZE = 8190;\nfor (int i = 0; i < transfers.Length; i += BATCH_SIZE)\n{\n var batchSize = BATCH_SIZE;\n if (i + BATCH_SIZE > transfers.Length)\n {\n batchSize = transfers.Length - i;\n }\n createTransfersError = client.CreateTransfers(transfers[i..batchSize]);\n // Error handling omitted.\n}\n")),(0,a.kt)("h3",{id:"queues-and-workers"},"Queues and Workers"),(0,a.kt)("p",null,"If you are making requests to TigerBeetle from workers\npulling jobs from a queue, you can batch requests to\nTigerBeetle by having the worker act on multiple jobs from\nthe queue at once rather than one at a time. i.e. pulling\nmultiple jobs from the queue rather than just one."),(0,a.kt)("h2",{id:"transfer-flags"},"Transfer Flags"),(0,a.kt)("p",null,"The transfer ",(0,a.kt)("inlineCode",{parentName:"p"},"flags")," value is a bitfield. See details for these flags in\nthe ",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/transfer#flags"},"Transfers\nreference"),"."),(0,a.kt)("p",null,"To toggle behavior for an account, combine enum values stored in the\n",(0,a.kt)("inlineCode",{parentName:"p"},"TransferFlags")," object with bitwise-or:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"TransferFlags.None")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"TransferFlags.Linked")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"TransferFlags.Pending")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"TransferFlags.PostPendingTransfer")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"TransferFlags.VoidPendingTransfer"))),(0,a.kt)("p",null,"For example, to link ",(0,a.kt)("inlineCode",{parentName:"p"},"transfer0")," and ",(0,a.kt)("inlineCode",{parentName:"p"},"transfer1"),":"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-cs"},"var transfer0 = new Transfer { /* ... account values ... */ };\nvar transfer1 = new Transfer { /* ... account values ... */ };\ntransfer0.Flags = TransferFlags.Linked;\ncreateTransfersError = client.CreateTransfers(new Transfer[] { transfer0, transfer1 });\n\n")),(0,a.kt)("h3",{id:"two-phase-transfers"},"Two-Phase Transfers"),(0,a.kt)("p",null,"Two-phase transfers are supported natively by toggling the appropriate\nflag. TigerBeetle will then adjust the ",(0,a.kt)("inlineCode",{parentName:"p"},"credits_pending")," and\n",(0,a.kt)("inlineCode",{parentName:"p"},"debits_pending")," fields of the appropriate accounts. A corresponding\npost pending transfer then needs to be sent to post or void the\ntransfer."),(0,a.kt)("h4",{id:"post-a-pending-transfer"},"Post a Pending Transfer"),(0,a.kt)("p",null,"With ",(0,a.kt)("inlineCode",{parentName:"p"},"flags")," set to ",(0,a.kt)("inlineCode",{parentName:"p"},"post_pending_transfer"),",\nTigerBeetle will post the transfer. TigerBeetle will atomically roll\nback the changes to ",(0,a.kt)("inlineCode",{parentName:"p"},"debits_pending")," and ",(0,a.kt)("inlineCode",{parentName:"p"},"credits_pending")," of the\nappropriate accounts and apply them to the ",(0,a.kt)("inlineCode",{parentName:"p"},"debits_posted")," and\n",(0,a.kt)("inlineCode",{parentName:"p"},"credits_posted")," balances."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-cs"},"transfers = new Transfer[] { new Transfer {\n Id = 2,\n // Post the entire pending amount.\n Amount = Transfer.AmountMax,\n PendingId = 1,\n Flags = TransferFlags.PostPendingTransfer,\n}};\n\ncreateTransfersError = client.CreateTransfers(transfers);\n// Error handling omitted.\n")),(0,a.kt)("h4",{id:"void-a-pending-transfer"},"Void a Pending Transfer"),(0,a.kt)("p",null,"In contrast, with ",(0,a.kt)("inlineCode",{parentName:"p"},"flags")," set to ",(0,a.kt)("inlineCode",{parentName:"p"},"void_pending_transfer"),",\nTigerBeetle will void the transfer. TigerBeetle will roll\nback the changes to ",(0,a.kt)("inlineCode",{parentName:"p"},"debits_pending")," and ",(0,a.kt)("inlineCode",{parentName:"p"},"credits_pending")," of the\nappropriate accounts and ",(0,a.kt)("strong",{parentName:"p"},"not")," apply them to the ",(0,a.kt)("inlineCode",{parentName:"p"},"debits_posted")," and\n",(0,a.kt)("inlineCode",{parentName:"p"},"credits_posted")," balances."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-cs"},"transfers = new Transfer[] { new Transfer {\n Id = 2,\n PendingId = 1,\n Flags = TransferFlags.PostPendingTransfer,\n}};\n\ncreateTransfersError = client.CreateTransfers(transfers);\n// Error handling omitted.\n")),(0,a.kt)("h2",{id:"transfer-lookup"},"Transfer Lookup"),(0,a.kt)("p",null,"NOTE: While transfer lookup exists, it is not a flexible query API. We\nare developing query APIs and there will be new methods for querying\ntransfers in the future."),(0,a.kt)("p",null,"Transfer lookup is batched, like transfer creation. Pass in all ",(0,a.kt)("inlineCode",{parentName:"p"},"id"),"s to\nfetch, and matched transfers are returned."),(0,a.kt)("p",null,"If no transfer matches an ",(0,a.kt)("inlineCode",{parentName:"p"},"id"),", no object is returned for that\ntransfer. So the order of transfers in the response is not necessarily\nthe same as the order of ",(0,a.kt)("inlineCode",{parentName:"p"},"id"),"s in the request. You can refer to the\n",(0,a.kt)("inlineCode",{parentName:"p"},"id")," field in the response to distinguish transfers."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-cs"},"transfers = client.LookupTransfers(new UInt128[] { 1, 2 });\n")),(0,a.kt)("h2",{id:"get-account-transfers"},"Get Account Transfers"),(0,a.kt)("p",null,"NOTE: This is a preview API that is subject to breaking changes once we have\na stable querying API."),(0,a.kt)("p",null,"Fetches the transfers involving a given account, allowing basic filter and pagination\ncapabilities."),(0,a.kt)("p",null,"The transfers in the response are sorted by ",(0,a.kt)("inlineCode",{parentName:"p"},"timestamp")," in chronological or\nreverse-chronological order."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-cs"},"var filter = new AccountFilter\n{\n AccountId = 2,\n UserData128 = 0, // No filter by UserData.\n UserData64 = 0,\n UserData32 = 0,\n Code = 0, // No filter by Code.\n TimestampMin = 0, // No filter by Timestamp.\n TimestampMax = 0, // No filter by Timestamp.\n Limit = 10, // Limit to ten transfers at most.\n Flags = AccountFilterFlags.Debits | // Include transfer from the debit side.\n AccountFilterFlags.Credits | // Include transfer from the credit side.\n AccountFilterFlags.Reversed, // Sort by timestamp in reverse-chronological order.\n};\n\ntransfers = client.GetAccountTransfers(filter);\n")),(0,a.kt)("h2",{id:"get-account-balances"},"Get Account Balances"),(0,a.kt)("p",null,"NOTE: This is a preview API that is subject to breaking changes once we have\na stable querying API."),(0,a.kt)("p",null,"Fetches the point-in-time balances of a given account, allowing basic filter and\npagination capabilities."),(0,a.kt)("p",null,"Only accounts created with the flag\n",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/account#flagshistory"},(0,a.kt)("inlineCode",{parentName:"a"},"history"))," set retain\n",(0,a.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/requests/get_account_balances"},"historical balances"),"."),(0,a.kt)("p",null,"The balances in the response are sorted by ",(0,a.kt)("inlineCode",{parentName:"p"},"timestamp")," in chronological or\nreverse-chronological order."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-cs"},"filter = new AccountFilter\n{\n AccountId = 2,\n UserData128 = 0, // No filter by UserData.\n UserData64 = 0,\n UserData32 = 0,\n Code = 0, // No filter by Code.\n TimestampMin = 0, // No filter by Timestamp.\n TimestampMax = 0, // No filter by Timestamp.\n Limit = 10, // Limit to ten balances at most.\n Flags = AccountFilterFlags.Debits | // Include transfer from the debit side.\n AccountFilterFlags.Credits | // Include transfer from the credit side.\n AccountFilterFlags.Reversed, // Sort by timestamp in reverse-chronological order.\n};\n\nvar account_balances = client.GetAccountBalances(filter);\n")),(0,a.kt)("h2",{id:"query-accounts"},"Query Accounts"),(0,a.kt)("p",null,"NOTE: This is a preview API that is subject to breaking changes once we have\na stable querying API."),(0,a.kt)("p",null,"Query accounts by the intersection of some fields and by timestamp range."),(0,a.kt)("p",null,"The accounts in the response are sorted by ",(0,a.kt)("inlineCode",{parentName:"p"},"timestamp")," in chronological or\nreverse-chronological order."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-cs"},"var query_filter = new QueryFilter\n{\n UserData128 = 1000, // Filter by UserData.\n UserData64 = 100,\n UserData32 = 10,\n Code = 1, // Filter by Code.\n Ledger = 0, // No filter by Ledger.\n TimestampMin = 0, // No filter by Timestamp.\n TimestampMax = 0, // No filter by Timestamp.\n Limit = 10, // Limit to ten balances at most.\n Flags = QueryFilterFlags.Reversed, // Sort by timestamp in reverse-chronological order.\n};\n\nvar query_accounts = client.QueryAccounts(query_filter);\n")),(0,a.kt)("h2",{id:"query-transfers"},"Query Transfers"),(0,a.kt)("p",null,"NOTE: This is a preview API that is subject to breaking changes once we have\na stable querying API."),(0,a.kt)("p",null,"Query transfers by the intersection of some fields and by timestamp range."),(0,a.kt)("p",null,"The transfers in the response are sorted by ",(0,a.kt)("inlineCode",{parentName:"p"},"timestamp")," in chronological or\nreverse-chronological order."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-cs"},"query_filter = new QueryFilter\n{\n UserData128 = 1000, // Filter by UserData\n UserData64 = 100,\n UserData32 = 10,\n Code = 1, // Filter by Code\n Ledger = 0, // No filter by Ledger\n TimestampMin = 0, // No filter by Timestamp.\n TimestampMax = 0, // No filter by Timestamp.\n Limit = 10, // Limit to ten balances at most.\n Flags = QueryFilterFlags.Reversed, // Sort by timestamp in reverse-chronological order.\n};\n\nvar query_transfers = client.QueryTransfers(query_filter);\n")),(0,a.kt)("h2",{id:"linked-events"},"Linked Events"),(0,a.kt)("p",null,"When the ",(0,a.kt)("inlineCode",{parentName:"p"},"linked")," flag is specified for an account when creating accounts or\na transfer when creating transfers, it links that event with the next event in the\nbatch, to create a chain of events, of arbitrary length, which all\nsucceed or fail together. The tail of a chain is denoted by the first\nevent without this flag. The last event in a batch may therefore never\nhave the ",(0,a.kt)("inlineCode",{parentName:"p"},"linked")," flag set as this would leave a chain\nopen-ended. Multiple chains or individual events may coexist within a\nbatch to succeed or fail independently."),(0,a.kt)("p",null,"Events within a chain are executed within order, or are rolled back on\nerror, so that the effect of each event in the chain is visible to the\nnext, and so that the chain is either visible or invisible as a unit\nto subsequent events after the chain. The event that was the first to\nbreak the chain will have a unique error result. Other events in the\nchain will have their error result set to ",(0,a.kt)("inlineCode",{parentName:"p"},"linked_event_failed"),"."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-cs"},"var batch = new System.Collections.Generic.List();\n\n// An individual transfer (successful):\nbatch.Add(new Transfer { Id = 1, /* ... rest of transfer ... */ });\n\n// A chain of 4 transfers (the last transfer in the chain closes the chain with linked=false):\nbatch.Add(new Transfer { Id = 2, /* ... rest of transfer ... */ Flags = TransferFlags.Linked }); // Commit/rollback.\nbatch.Add(new Transfer { Id = 3, /* ... rest of transfer ... */ Flags = TransferFlags.Linked }); // Commit/rollback.\nbatch.Add(new Transfer { Id = 2, /* ... rest of transfer ... */ Flags = TransferFlags.Linked }); // Fail with exists\nbatch.Add(new Transfer { Id = 4, /* ... rest of transfer ... */ }); // Fail without committing\n\n// An individual transfer (successful):\n// This should not see any effect from the failed chain above.\nbatch.Add(new Transfer { Id = 2, /* ... rest of transfer ... */ });\n\n// A chain of 2 transfers (the first transfer fails the chain):\nbatch.Add(new Transfer { Id = 2, /* ... rest of transfer ... */ Flags = TransferFlags.Linked });\nbatch.Add(new Transfer { Id = 3, /* ... rest of transfer ... */ });\n\n// A chain of 2 transfers (successful):\nbatch.Add(new Transfer { Id = 3, /* ... rest of transfer ... */ Flags = TransferFlags.Linked });\nbatch.Add(new Transfer { Id = 4, /* ... rest of transfer ... */ });\n\ncreateTransfersError = client.CreateTransfers(batch.ToArray());\n// Error handling omitted.\n")),(0,a.kt)("h2",{id:"imported-events"},"Imported Events"),(0,a.kt)("p",null,"When the ",(0,a.kt)("inlineCode",{parentName:"p"},"imported")," flag is specified for an account when creating accounts or\na transfer when creating transfers, it allows importing historical events with\na user-defined timestamp."),(0,a.kt)("p",null,"The entire batch of events must be set with the flag ",(0,a.kt)("inlineCode",{parentName:"p"},"imported"),"."),(0,a.kt)("p",null,"It's recommended to submit the whole batch as a ",(0,a.kt)("inlineCode",{parentName:"p"},"linked")," chain of events, ensuring that\nif any event fails, none of them are committed, preserving the last timestamp unchanged.\nThis approach gives the application a chance to correct failed imported events, re-submitting\nthe batch again with the same user-defined timestamps."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-cs"},"// First, load and import all accounts with their timestamps from the historical source.\nvar accountsBatch = new System.Collections.Generic.List();\nfor (var index = 0; index < historicalAccounts.Length; index++)\n{\n var account = historicalAccounts[index];\n\n // Set a unique and strictly increasing timestamp.\n historicalTimestamp += 1;\n account.Timestamp = historicalTimestamp;\n // Set the account as `imported`.\n account.Flags = AccountFlags.Imported;\n // To ensure atomicity, the entire batch (except the last event in the chain)\n // must be `linked`.\n if (index < historicalAccounts.Length - 1)\n {\n account.Flags |= AccountFlags.Linked;\n }\n\n accountsBatch.Add(account);\n}\n\ncreateAccountsError = client.CreateAccounts(accountsBatch.ToArray());\n// Error handling omitted.\n\n// Then, load and import all transfers with their timestamps from the historical source.\nvar transfersBatch = new System.Collections.Generic.List();\nfor (var index = 0; index < historicalTransfers.Length; index++)\n{\n var transfer = historicalTransfers[index];\n\n // Set a unique and strictly increasing timestamp.\n historicalTimestamp += 1;\n transfer.Timestamp = historicalTimestamp;\n // Set the account as `imported`.\n transfer.Flags = TransferFlags.Imported;\n // To ensure atomicity, the entire batch (except the last event in the chain)\n // must be `linked`.\n if (index < historicalTransfers.Length - 1)\n {\n transfer.Flags |= TransferFlags.Linked;\n }\n\n transfersBatch.Add(transfer);\n}\n\ncreateTransfersError = client.CreateTransfers(transfersBatch.ToArray());\n// Error handling omitted.\n// Since it is a linked chain, in case of any error the entire batch is rolled back and can be retried\n// with the same historical timestamps without regressing the cluster timestamp.\n")))}p.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/675d658f.d2918bb2.js b/assets/js/675d658f.3ac0bf5c.js similarity index 72% rename from assets/js/675d658f.d2918bb2.js rename to assets/js/675d658f.3ac0bf5c.js index d9fa231..f6c7d9d 100644 --- a/assets/js/675d658f.d2918bb2.js +++ b/assets/js/675d658f.3ac0bf5c.js @@ -1 +1 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[6221],{3905:(e,n,t)=>{t.d(n,{Zo:()=>d,kt:()=>h});var a=t(7294);function r(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function i(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);n&&(a=a.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,a)}return t}function s(e){for(var n=1;n=0||(r[t]=e[t]);return r}(e,n);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(r[t]=e[t])}return r}var o=a.createContext({}),c=function(e){var n=a.useContext(o),t=n;return e&&(t="function"==typeof e?e(n):s(s({},n),e)),t},d=function(e){var n=c(e.components);return a.createElement(o.Provider,{value:n},e.children)},p="mdxType",u={inlineCode:"code",wrapper:function(e){var n=e.children;return a.createElement(a.Fragment,{},n)}},m=a.forwardRef((function(e,n){var t=e.components,r=e.mdxType,i=e.originalType,o=e.parentName,d=l(e,["components","mdxType","originalType","parentName"]),p=c(t),m=r,h=p["".concat(o,".").concat(m)]||p[m]||u[m]||i;return t?a.createElement(h,s(s({ref:n},d),{},{components:t})):a.createElement(h,s({ref:n},d))}));function h(e,n){var t=arguments,r=n&&n.mdxType;if("string"==typeof e||r){var i=t.length,s=new Array(i);s[0]=m;var l={};for(var o in n)hasOwnProperty.call(n,o)&&(l[o]=n[o]);l.originalType=e,l[p]="string"==typeof e?e:r,s[1]=l;for(var c=2;c{t.r(n),t.d(n,{assets:()=>o,contentTitle:()=>s,default:()=>u,frontMatter:()=>i,metadata:()=>l,toc:()=>c});var a=t(7462),r=(t(7294),t(3905));const i={title:"Node.js"},s=void 0,l={unversionedId:"clients/node",id:"clients/node",title:"Node.js",description:"The TigerBeetle client for Node.js.",source:"@site/pages/clients/node.md",sourceDirName:"clients",slug:"/clients/node",permalink:"/clients/node",draft:!1,editUrl:"https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/clients/node.md",tags:[],version:"current",frontMatter:{title:"Node.js"},sidebar:"tutorialSidebar",previous:{title:"Java",permalink:"/clients/java"},next:{title:"Account",permalink:"/reference/account"}},o={},c=[{value:"Prerequisites",id:"prerequisites",level:2},{value:"Setup",id:"setup",level:2},{value:"Sample projects",id:"sample-projects",level:2},{value:"Sidenote: BigInt",id:"sidenote-bigint",level:3},{value:"Creating a Client",id:"creating-a-client",level:2},{value:"Creating Accounts",id:"creating-accounts",level:2},{value:"Account Flags",id:"account-flags",level:3},{value:"Response and Errors",id:"response-and-errors",level:3},{value:"Account Lookup",id:"account-lookup",level:2},{value:"Create Transfers",id:"create-transfers",level:2},{value:"Response and Errors",id:"response-and-errors-1",level:3},{value:"Batching",id:"batching",level:2},{value:"Queues and Workers",id:"queues-and-workers",level:3},{value:"Transfer Flags",id:"transfer-flags",level:2},{value:"Two-Phase Transfers",id:"two-phase-transfers",level:3},{value:"Post a Pending Transfer",id:"post-a-pending-transfer",level:4},{value:"Void a Pending Transfer",id:"void-a-pending-transfer",level:4},{value:"Transfer Lookup",id:"transfer-lookup",level:2},{value:"Get Account Transfers",id:"get-account-transfers",level:2},{value:"Get Account Balances",id:"get-account-balances",level:2},{value:"Query Accounts",id:"query-accounts",level:2},{value:"Query Transfers",id:"query-transfers",level:2},{value:"Linked Events",id:"linked-events",level:2},{value:"Imported Events",id:"imported-events",level:2}],d={toc:c},p="wrapper";function u(e){let{components:n,...t}=e;return(0,r.kt)(p,(0,a.Z)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,r.kt)("h1",{id:"tigerbeetle-node"},"tigerbeetle-node"),(0,r.kt)("p",null,"The TigerBeetle client for Node.js."),(0,r.kt)("h2",{id:"prerequisites"},"Prerequisites"),(0,r.kt)("p",null,"Linux >= 5.6 is the only production environment we\nsupport. But for ease of development we also support macOS and Windows."),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"NodeJS >= ",(0,r.kt)("inlineCode",{parentName:"li"},"18"))),(0,r.kt)("h2",{id:"setup"},"Setup"),(0,r.kt)("p",null,"First, create a directory for your project and ",(0,r.kt)("inlineCode",{parentName:"p"},"cd")," into the directory."),(0,r.kt)("p",null,"Then, install the TigerBeetle client:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},"npm install tigerbeetle-node\n")),(0,r.kt)("p",null,"Now, create ",(0,r.kt)("inlineCode",{parentName:"p"},"main.js")," and copy this into it:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-javascript"},'const { createClient } = require("tigerbeetle-node");\n\nconsole.log("Import ok!");\n')),(0,r.kt)("p",null,"Finally, build and run:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},"node main.js\n")),(0,r.kt)("p",null,"Now that all prerequisites and dependencies are correctly set\nup, let's dig into using TigerBeetle."),(0,r.kt)("h2",{id:"sample-projects"},"Sample projects"),(0,r.kt)("p",null,"This document is primarily a reference guide to\nthe client. Below are various sample projects demonstrating\nfeatures of TigerBeetle."),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"https://github.com/tigerbeetle/tigerbeetle/blob/main/src/clients/node/samples/basic/"},"Basic"),": Create two accounts and transfer an amount between them."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"https://github.com/tigerbeetle/tigerbeetle/blob/main/src/clients/node/samples/two-phase/"},"Two-Phase Transfer"),": Create two accounts and start a pending transfer between\nthem, then post the transfer."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"https://github.com/tigerbeetle/tigerbeetle/blob/main/src/clients/node/samples/two-phase-many/"},"Many Two-Phase Transfers"),": Create two accounts and start a number of pending transfer\nbetween them, posting and voiding alternating transfers.")),(0,r.kt)("h3",{id:"sidenote-bigint"},"Sidenote: ",(0,r.kt)("inlineCode",{parentName:"h3"},"BigInt")),(0,r.kt)("p",null,"TigerBeetle uses 64-bit integers for many fields while JavaScript's\nbuiltin ",(0,r.kt)("inlineCode",{parentName:"p"},"Number")," maximum value is ",(0,r.kt)("inlineCode",{parentName:"p"},"2^53-1"),". The ",(0,r.kt)("inlineCode",{parentName:"p"},"n")," suffix in JavaScript\nmeans the value is a ",(0,r.kt)("inlineCode",{parentName:"p"},"BigInt"),". This is useful for literal numbers. If\nyou already have a ",(0,r.kt)("inlineCode",{parentName:"p"},"Number")," variable though, you can call the ",(0,r.kt)("inlineCode",{parentName:"p"},"BigInt"),"\nconstructor to get a ",(0,r.kt)("inlineCode",{parentName:"p"},"BigInt")," from it. For example, ",(0,r.kt)("inlineCode",{parentName:"p"},"1n")," is the same as\n",(0,r.kt)("inlineCode",{parentName:"p"},"BigInt(1)"),"."),(0,r.kt)("h2",{id:"creating-a-client"},"Creating a Client"),(0,r.kt)("p",null,"A client is created with a cluster ID and replica\naddresses for all replicas in the cluster. The cluster\nID and replica addresses are both chosen by the system that\nstarts the TigerBeetle cluster."),(0,r.kt)("p",null,"Clients are thread-safe and a single instance should be shared\nbetween multiple concurrent tasks."),(0,r.kt)("p",null,"Multiple clients are useful when connecting to more than\none TigerBeetle cluster."),(0,r.kt)("p",null,"In this example the cluster ID is ",(0,r.kt)("inlineCode",{parentName:"p"},"0")," and there is one\nreplica. The address is read from the ",(0,r.kt)("inlineCode",{parentName:"p"},"TB_ADDRESS"),"\nenvironment variable and defaults to port ",(0,r.kt)("inlineCode",{parentName:"p"},"3000"),"."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-javascript"},'const client = createClient({\n cluster_id: 0n,\n replica_addresses: [process.env.TB_ADDRESS || "3000"],\n});\n')),(0,r.kt)("p",null,"The following are valid addresses:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"3000")," (interpreted as ",(0,r.kt)("inlineCode",{parentName:"li"},"127.0.0.1:3000"),")"),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"127.0.0.1:3000")," (interpreted as ",(0,r.kt)("inlineCode",{parentName:"li"},"127.0.0.1:3000"),")"),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"127.0.0.1")," (interpreted as ",(0,r.kt)("inlineCode",{parentName:"li"},"127.0.0.1:3001"),", ",(0,r.kt)("inlineCode",{parentName:"li"},"3001")," is the default port)")),(0,r.kt)("h2",{id:"creating-accounts"},"Creating Accounts"),(0,r.kt)("p",null,"See details for account fields in the ",(0,r.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/account"},"Accounts\nreference"),"."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-javascript"},"let account = {\n id: 137n,\n debits_pending: 0n,\n debits_posted: 0n,\n credits_pending: 0n,\n credits_posted: 0n,\n user_data_128: 0n,\n user_data_64: 0n,\n user_data_32: 0,\n reserved: 0,\n ledger: 1,\n code: 718,\n flags: 0,\n timestamp: 0n,\n};\n\nlet accountErrors = await client.createAccounts([account]);\n")),(0,r.kt)("h3",{id:"account-flags"},"Account Flags"),(0,r.kt)("p",null,"The account flags value is a bitfield. See details for\nthese flags in the ",(0,r.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/account#flags"},"Accounts\nreference"),"."),(0,r.kt)("p",null,"To toggle behavior for an account, combine enum values stored in the\n",(0,r.kt)("inlineCode",{parentName:"p"},"AccountFlags")," object (in TypeScript it is an actual enum) with\nbitwise-or:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"AccountFlags.linked")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"AccountFlags.debits_must_not_exceed_credits")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"AccountFlags.credits_must_not_exceed_credits")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"AccountFlags.history"))),(0,r.kt)("p",null,"For example, to link two accounts where the first account\nadditionally has the ",(0,r.kt)("inlineCode",{parentName:"p"},"debits_must_not_exceed_credits")," constraint:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-javascript"},"let account0 = {\n id: 100n,\n debits_pending: 0n,\n debits_posted: 0n,\n credits_pending: 0n,\n credits_posted: 0n,\n user_data_128: 0n,\n user_data_64: 0n,\n user_data_32: 0,\n reserved: 0,\n ledger: 1,\n code: 1,\n timestamp: 0n,\n flags: 0,\n};\nlet account1 = {\n id: 101n,\n debits_pending: 0n,\n debits_posted: 0n,\n credits_pending: 0n,\n credits_posted: 0n,\n user_data_128: 0n,\n user_data_64: 0n,\n user_data_32: 0,\n reserved: 0,\n ledger: 1,\n code: 1,\n timestamp: 0n,\n flags: 0,\n};\naccount0.flags = AccountFlags.linked |\n AccountFlags.debits_must_not_exceed_credits;\naccountErrors = await client.createAccounts([account0, account1]);\n")),(0,r.kt)("h3",{id:"response-and-errors"},"Response and Errors"),(0,r.kt)("p",null,"The response is an empty array if all accounts were\ncreated successfully. If the response is non-empty, each\nobject in the response array contains error information\nfor an account that failed. The error object contains an\nerror code and the index of the account in the request\nbatch."),(0,r.kt)("p",null,"See all error conditions in the ",(0,r.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/requests/create_accounts"},"create_accounts\nreference"),"."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-javascript"},"let account2 = {\n id: 102n,\n debits_pending: 0n,\n debits_posted: 0n,\n credits_pending: 0n,\n credits_posted: 0n,\n user_data_128: 0n,\n user_data_64: 0n,\n user_data_32: 0,\n reserved: 0,\n ledger: 1,\n code: 1,\n timestamp: 0n,\n flags: 0,\n};\nlet account3 = {\n id: 103n,\n debits_pending: 0n,\n debits_posted: 0n,\n credits_pending: 0n,\n credits_posted: 0n,\n user_data_128: 0n,\n user_data_64: 0n,\n user_data_32: 0,\n reserved: 0,\n ledger: 1,\n code: 1,\n timestamp: 0n,\n flags: 0,\n};\nlet account4 = {\n id: 104n,\n debits_pending: 0n,\n debits_posted: 0n,\n credits_pending: 0n,\n credits_posted: 0n,\n user_data_128: 0n,\n user_data_64: 0n,\n user_data_32: 0,\n reserved: 0,\n ledger: 1,\n code: 1,\n timestamp: 0n,\n flags: 0,\n};\naccountErrors = await client.createAccounts([account2, account3, account4]);\nfor (const error of accountErrors) {\n switch (error.result) {\n case CreateAccountError.exists:\n console.error(`Batch account at ${error.index} already exists.`);\n break;\n default:\n console.error(\n `Batch account at ${error.index} failed to create: ${\n CreateAccountError[error.result]\n }.`,\n );\n }\n}\n")),(0,r.kt)("p",null,"To handle errors you can either 1) exactly match error codes returned\nfrom ",(0,r.kt)("inlineCode",{parentName:"p"},"client.createAccounts")," with enum values in the\n",(0,r.kt)("inlineCode",{parentName:"p"},"CreateAccountError")," object, or you can 2) look up the error code in\nthe ",(0,r.kt)("inlineCode",{parentName:"p"},"CreateAccountError")," object for a human-readable string."),(0,r.kt)("h2",{id:"account-lookup"},"Account Lookup"),(0,r.kt)("p",null,"Account lookup is batched, like account creation. Pass\nin all IDs to fetch. The account for each matched ID is returned."),(0,r.kt)("p",null,"If no account matches an ID, no object is returned for\nthat account. So the order of accounts in the response is\nnot necessarily the same as the order of IDs in the\nrequest. You can refer to the ID field in the response to\ndistinguish accounts."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-javascript"},"const accounts = await client.lookupAccounts([137n, 138n]);\nconsole.log(accounts);\n/*\n * [{\n * id: 137n,\n * debits_pending: 0n,\n * debits_posted: 0n,\n * credits_pending: 0n,\n * credits_posted: 0n,\n * user_data_128: 0n,\n * user_data_64: 0n,\n * user_data_32: 0,\n * reserved: 0,\n * ledger: 1,\n * code: 718,\n * flags: 0,\n * timestamp: 1623062009212508993n,\n * }]\n */\n")),(0,r.kt)("h2",{id:"create-transfers"},"Create Transfers"),(0,r.kt)("p",null,"This creates a journal entry between two accounts."),(0,r.kt)("p",null,"See details for transfer fields in the ",(0,r.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/transfer"},"Transfers\nreference"),"."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-javascript"},"let transfers = [{\n id: 1n,\n debit_account_id: 102n,\n credit_account_id: 103n,\n amount: 10n,\n pending_id: 0n,\n user_data_128: 0n,\n user_data_64: 0n,\n user_data_32: 0,\n timeout: 0,\n ledger: 1,\n code: 720,\n flags: 0,\n timestamp: 0n,\n}];\nlet transferErrors = await client.createTransfers(transfers);\n")),(0,r.kt)("h3",{id:"response-and-errors-1"},"Response and Errors"),(0,r.kt)("p",null,"The response is an empty array if all transfers were created\nsuccessfully. If the response is non-empty, each object in the\nresponse array contains error information for a transfer that\nfailed. The error object contains an error code and the index of the\ntransfer in the request batch."),(0,r.kt)("p",null,"See all error conditions in the ",(0,r.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/requests/create_transfers"},"create_transfers\nreference"),"."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-javascript"},"for (const error of transferErrors) {\n switch (error.result) {\n case CreateTransferError.exists:\n console.error(`Batch transfer at ${error.index} already exists.`);\n break;\n default:\n console.error(\n `Batch transfer at ${error.index} failed to create: ${\n CreateTransferError[error.result]\n }.`,\n );\n }\n}\n")),(0,r.kt)("p",null,"To handle errors you can either 1) exactly match error codes returned\nfrom ",(0,r.kt)("inlineCode",{parentName:"p"},"client.createTransfers")," with enum values in the\n",(0,r.kt)("inlineCode",{parentName:"p"},"CreateTransferError")," object, or you can 2) look up the error code in\nthe ",(0,r.kt)("inlineCode",{parentName:"p"},"CreateTransferError")," object for a human-readable string."),(0,r.kt)("h2",{id:"batching"},"Batching"),(0,r.kt)("p",null,"TigerBeetle performance is maximized when you batch\nAPI requests. The client does not do this automatically for\nyou. So, for example, you ",(0,r.kt)("em",{parentName:"p"},"can")," insert 1 million transfers\none at a time like so:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-javascript"},"for (let i = 0; i < transfers.len; i++) {\n const transferErrors = await client.createTransfers(transfers[i]);\n // Error handling omitted.\n}\n")),(0,r.kt)("p",null,"But the insert rate will be a ",(0,r.kt)("em",{parentName:"p"},"fraction")," of\npotential. Instead, ",(0,r.kt)("strong",{parentName:"p"},"always batch what you can"),"."),(0,r.kt)("p",null,"The maximum batch size is set in the TigerBeetle server. The default\nis 8190."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-javascript"},"const BATCH_SIZE = 8190;\nfor (let i = 0; i < transfers.length; i += BATCH_SIZE) {\n const transferErrors = await client.createTransfers(\n transfers.slice(i, Math.min(transfers.length, BATCH_SIZE)),\n );\n // Error handling omitted.\n}\n")),(0,r.kt)("h3",{id:"queues-and-workers"},"Queues and Workers"),(0,r.kt)("p",null,"If you are making requests to TigerBeetle from workers\npulling jobs from a queue, you can batch requests to\nTigerBeetle by having the worker act on multiple jobs from\nthe queue at once rather than one at a time. i.e. pulling\nmultiple jobs from the queue rather than just one."),(0,r.kt)("h2",{id:"transfer-flags"},"Transfer Flags"),(0,r.kt)("p",null,"The transfer ",(0,r.kt)("inlineCode",{parentName:"p"},"flags")," value is a bitfield. See details for these flags in\nthe ",(0,r.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/transfer#flags"},"Transfers\nreference"),"."),(0,r.kt)("p",null,"To toggle behavior for a transfer, combine enum values stored in the\n",(0,r.kt)("inlineCode",{parentName:"p"},"TransferFlags")," object (in TypeScript it is an actual enum) with\nbitwise-or:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"TransferFlags.linked")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"TransferFlags.pending")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"TransferFlags.post_pending_transfer")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"TransferFlags.void_pending_transfer"))),(0,r.kt)("p",null,"For example, to link ",(0,r.kt)("inlineCode",{parentName:"p"},"transfer0")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"transfer1"),":"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-javascript"},"let transfer0 = {\n id: 2n,\n debit_account_id: 102n,\n credit_account_id: 103n,\n amount: 10n,\n pending_id: 0n,\n user_data_128: 0n,\n user_data_64: 0n,\n user_data_32: 0,\n timeout: 0,\n ledger: 1,\n code: 720,\n flags: 0,\n timestamp: 0n,\n};\nlet transfer1 = {\n id: 3n,\n debit_account_id: 102n,\n credit_account_id: 103n,\n amount: 10n,\n pending_id: 0n,\n user_data_128: 0n,\n user_data_64: 0n,\n user_data_32: 0,\n timeout: 0,\n ledger: 1,\n code: 720,\n flags: 0,\n timestamp: 0n,\n};\ntransfer0.flags = TransferFlags.linked;\n// Create the transfer\ntransferErrors = await client.createTransfers([transfer0, transfer1]);\n")),(0,r.kt)("h3",{id:"two-phase-transfers"},"Two-Phase Transfers"),(0,r.kt)("p",null,"Two-phase transfers are supported natively by toggling the appropriate\nflag. TigerBeetle will then adjust the ",(0,r.kt)("inlineCode",{parentName:"p"},"credits_pending")," and\n",(0,r.kt)("inlineCode",{parentName:"p"},"debits_pending")," fields of the appropriate accounts. A corresponding\npost pending transfer then needs to be sent to post or void the\ntransfer."),(0,r.kt)("h4",{id:"post-a-pending-transfer"},"Post a Pending Transfer"),(0,r.kt)("p",null,"With ",(0,r.kt)("inlineCode",{parentName:"p"},"flags")," set to ",(0,r.kt)("inlineCode",{parentName:"p"},"post_pending_transfer"),",\nTigerBeetle will post the transfer. TigerBeetle will atomically roll\nback the changes to ",(0,r.kt)("inlineCode",{parentName:"p"},"debits_pending")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"credits_pending")," of the\nappropriate accounts and apply them to the ",(0,r.kt)("inlineCode",{parentName:"p"},"debits_posted")," and\n",(0,r.kt)("inlineCode",{parentName:"p"},"credits_posted")," balances."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-javascript"},"let transfer2 = {\n id: 4n,\n debit_account_id: 102n,\n credit_account_id: 103n,\n amount: 10n,\n pending_id: 0n,\n user_data_128: 0n,\n user_data_64: 0n,\n user_data_32: 0,\n timeout: 0,\n ledger: 1,\n code: 720,\n flags: TransferFlags.pending,\n timestamp: 0n,\n};\ntransferErrors = await client.createTransfers([transfer2]);\n\nlet transfer3 = {\n id: 5n,\n debit_account_id: 102n,\n credit_account_id: 103n,\n // Post the entire pending amount.\n amount: amount_max,\n pending_id: 4n,\n user_data_128: 0n,\n user_data_64: 0n,\n user_data_32: 0,\n timeout: 0,\n ledger: 1,\n code: 720,\n flags: TransferFlags.post_pending_transfer,\n timestamp: 0n,\n};\ntransferErrors = await client.createTransfers([transfer3]);\n")),(0,r.kt)("h4",{id:"void-a-pending-transfer"},"Void a Pending Transfer"),(0,r.kt)("p",null,"In contrast, with ",(0,r.kt)("inlineCode",{parentName:"p"},"flags")," set to ",(0,r.kt)("inlineCode",{parentName:"p"},"void_pending_transfer"),",\nTigerBeetle will void the transfer. TigerBeetle will roll\nback the changes to ",(0,r.kt)("inlineCode",{parentName:"p"},"debits_pending")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"credits_pending")," of the\nappropriate accounts and ",(0,r.kt)("strong",{parentName:"p"},"not")," apply them to the ",(0,r.kt)("inlineCode",{parentName:"p"},"debits_posted")," and\n",(0,r.kt)("inlineCode",{parentName:"p"},"credits_posted")," balances."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-javascript"},"let transfer4 = {\n id: 4n,\n debit_account_id: 102n,\n credit_account_id: 103n,\n amount: 10n,\n pending_id: 0n,\n user_data_128: 0n,\n user_data_64: 0n,\n user_data_32: 0,\n timeout: 0,\n ledger: 1,\n code: 720,\n flags: TransferFlags.pending,\n timestamp: 0n,\n};\ntransferErrors = await client.createTransfers([transfer4]);\n\nlet transfer5 = {\n id: 7n,\n debit_account_id: 102n,\n credit_account_id: 103n,\n amount: 10n,\n pending_id: 6n,\n user_data_128: 0n,\n user_data_64: 0n,\n user_data_32: 0,\n timeout: 0,\n ledger: 1,\n code: 720,\n flags: TransferFlags.void_pending_transfer,\n timestamp: 0n,\n};\ntransferErrors = await client.createTransfers([transfer5]);\n")),(0,r.kt)("h2",{id:"transfer-lookup"},"Transfer Lookup"),(0,r.kt)("p",null,"NOTE: While transfer lookup exists, it is not a flexible query API. We\nare developing query APIs and there will be new methods for querying\ntransfers in the future."),(0,r.kt)("p",null,"Transfer lookup is batched, like transfer creation. Pass in all ",(0,r.kt)("inlineCode",{parentName:"p"},"id"),"s to\nfetch, and matched transfers are returned."),(0,r.kt)("p",null,"If no transfer matches an ",(0,r.kt)("inlineCode",{parentName:"p"},"id"),", no object is returned for that\ntransfer. So the order of transfers in the response is not necessarily\nthe same as the order of ",(0,r.kt)("inlineCode",{parentName:"p"},"id"),"s in the request. You can refer to the\n",(0,r.kt)("inlineCode",{parentName:"p"},"id")," field in the response to distinguish transfers."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-javascript"},"transfers = await client.lookupTransfers([1n, 2n]);\nconsole.log(transfers);\n/*\n * [{\n * id: 1n,\n * debit_account_id: 102n,\n * credit_account_id: 103n,\n * amount: 10n,\n * pending_id: 0n,\n * user_data_128: 0n,\n * user_data_64: 0n,\n * user_data_32: 0,\n * timeout: 0,\n * ledger: 1,\n * code: 720,\n * flags: 0,\n * timestamp: 1623062009212508993n,\n * }]\n */\n")),(0,r.kt)("h2",{id:"get-account-transfers"},"Get Account Transfers"),(0,r.kt)("p",null,"NOTE: This is a preview API that is subject to breaking changes once we have\na stable querying API."),(0,r.kt)("p",null,"Fetches the transfers involving a given account, allowing basic filter and pagination\ncapabilities."),(0,r.kt)("p",null,"The transfers in the response are sorted by ",(0,r.kt)("inlineCode",{parentName:"p"},"timestamp")," in chronological or\nreverse-chronological order."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-javascript"},"let filter = {\n account_id: 2n,\n timestamp_min: 0n, // No filter by Timestamp.\n timestamp_max: 0n, // No filter by Timestamp.\n limit: 10, // Limit to ten balances at most.\n flags: AccountFilterFlags.debits | // Include transfer from the debit side.\n AccountFilterFlags.credits | // Include transfer from the credit side.\n AccountFilterFlags.reversed, // Sort by timestamp in reverse-chronological order.\n};\n\nconst account_transfers = await client.getAccountTransfers(filter);\n")),(0,r.kt)("h2",{id:"get-account-balances"},"Get Account Balances"),(0,r.kt)("p",null,"NOTE: This is a preview API that is subject to breaking changes once we have\na stable querying API."),(0,r.kt)("p",null,"Fetches the point-in-time balances of a given account, allowing basic filter and\npagination capabilities."),(0,r.kt)("p",null,"Only accounts created with the flag\n",(0,r.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/account#flagshistory"},(0,r.kt)("inlineCode",{parentName:"a"},"history"))," set retain\n",(0,r.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/requests/get_account_balances"},"historical balances"),"."),(0,r.kt)("p",null,"The balances in the response are sorted by ",(0,r.kt)("inlineCode",{parentName:"p"},"timestamp")," in chronological or\nreverse-chronological order."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-javascript"},"filter = {\n account_id: 2n,\n timestamp_min: 0n, // No filter by Timestamp.\n timestamp_max: 0n, // No filter by Timestamp.\n limit: 10, // Limit to ten balances at most.\n flags: AccountFilterFlags.debits | // Include transfer from the debit side.\n AccountFilterFlags.credits | // Include transfer from the credit side.\n AccountFilterFlags.reversed, // Sort by timestamp in reverse-chronological order.\n};\n\nconst account_balances = await client.getAccountBalances(filter);\n")),(0,r.kt)("h2",{id:"query-accounts"},"Query Accounts"),(0,r.kt)("p",null,"NOTE: This is a preview API that is subject to breaking changes once we have\na stable querying API."),(0,r.kt)("p",null,"Query accounts by the intersection of some fields and by timestamp range."),(0,r.kt)("p",null,"The accounts in the response are sorted by ",(0,r.kt)("inlineCode",{parentName:"p"},"timestamp")," in chronological or\nreverse-chronological order."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-javascript"},"var query_filter = {\n user_data_128: 1000n, // Filter by UserData.\n user_data_64: 100n,\n user_data_32: 10,\n code: 1, // Filter by Code.\n ledger: 0, // No filter by Ledger.\n timestamp_min: 0n, // No filter by Timestamp.\n timestamp_max: 0n, // No filter by Timestamp.\n limit: 10, // Limit to ten balances at most.\n flags: AccountFilterFlags.debits | // Include transfer from the debit side.\n AccountFilterFlags.credits | // Include transfer from the credit side.\n AccountFilterFlags.reversed, // Sort by timestamp in reverse-chronological order.\n};\nconst query_accounts = await client.queryAccounts(query_filter);\n")),(0,r.kt)("h2",{id:"query-transfers"},"Query Transfers"),(0,r.kt)("p",null,"NOTE: This is a preview API that is subject to breaking changes once we have\na stable querying API."),(0,r.kt)("p",null,"Query transfers by the intersection of some fields and by timestamp range."),(0,r.kt)("p",null,"The transfers in the response are sorted by ",(0,r.kt)("inlineCode",{parentName:"p"},"timestamp")," in chronological or\nreverse-chronological order."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-javascript"},"query_filter = {\n user_data_128: 1000n, // Filter by UserData.\n user_data_64: 100n,\n user_data_32: 10,\n code: 1, // Filter by Code.\n ledger: 0, // No filter by Ledger.\n timestamp_min: 0n, // No filter by Timestamp.\n timestamp_max: 0n, // No filter by Timestamp.\n limit: 10, // Limit to ten balances at most.\n flags: AccountFilterFlags.debits | // Include transfer from the debit side.\n AccountFilterFlags.credits | // Include transfer from the credit side.\n AccountFilterFlags.reversed, // Sort by timestamp in reverse-chronological order.\n};\nconst query_transfers = await client.queryTransfers(query_filter);\n")),(0,r.kt)("h2",{id:"linked-events"},"Linked Events"),(0,r.kt)("p",null,"When the ",(0,r.kt)("inlineCode",{parentName:"p"},"linked")," flag is specified for an account when creating accounts or\na transfer when creating transfers, it links that event with the next event in the\nbatch, to create a chain of events, of arbitrary length, which all\nsucceed or fail together. The tail of a chain is denoted by the first\nevent without this flag. The last event in a batch may therefore never\nhave the ",(0,r.kt)("inlineCode",{parentName:"p"},"linked")," flag set as this would leave a chain\nopen-ended. Multiple chains or individual events may coexist within a\nbatch to succeed or fail independently."),(0,r.kt)("p",null,"Events within a chain are executed within order, or are rolled back on\nerror, so that the effect of each event in the chain is visible to the\nnext, and so that the chain is either visible or invisible as a unit\nto subsequent events after the chain. The event that was the first to\nbreak the chain will have a unique error result. Other events in the\nchain will have their error result set to ",(0,r.kt)("inlineCode",{parentName:"p"},"linked_event_failed"),"."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-javascript"},"const batch = [];\nlet linkedFlag = 0;\nlinkedFlag |= TransferFlags.linked;\n\n// An individual transfer (successful):\nbatch.push({ id: 1n /* , ... */ });\n\n// A chain of 4 transfers (the last transfer in the chain closes the chain with linked=false):\nbatch.push({ id: 2n, /* ..., */ flags: linkedFlag }); // Commit/rollback.\nbatch.push({ id: 3n, /* ..., */ flags: linkedFlag }); // Commit/rollback.\nbatch.push({ id: 2n, /* ..., */ flags: linkedFlag }); // Fail with exists\nbatch.push({ id: 4n, /* ..., */ flags: 0 }); // Fail without committing.\n\n// An individual transfer (successful):\n// This should not see any effect from the failed chain above.\nbatch.push({ id: 2n, /* ..., */ flags: 0 });\n\n// A chain of 2 transfers (the first transfer fails the chain):\nbatch.push({ id: 2n, /* ..., */ flags: linkedFlag });\nbatch.push({ id: 3n, /* ..., */ flags: 0 });\n\n// A chain of 2 transfers (successful):\nbatch.push({ id: 3n, /* ..., */ flags: linkedFlag });\nbatch.push({ id: 4n, /* ..., */ flags: 0 });\n\nconst errors = await client.createTransfers(batch);\n\n/**\n * console.log(errors);\n * [\n * { index: 1, error: 1 }, // linked_event_failed\n * { index: 2, error: 1 }, // linked_event_failed\n * { index: 3, error: 25 }, // exists\n * { index: 4, error: 1 }, // linked_event_failed\n *\n * { index: 6, error: 17 }, // exists_with_different_flags\n * { index: 7, error: 1 }, // linked_event_failed\n * ]\n */\n")),(0,r.kt)("h2",{id:"imported-events"},"Imported Events"),(0,r.kt)("p",null,"When the ",(0,r.kt)("inlineCode",{parentName:"p"},"imported")," flag is specified for an account when creating accounts or\na transfer when creating transfers, it allows importing historical events with\na user-defined timestamp."),(0,r.kt)("p",null,"The entire batch of events must be set with the flag ",(0,r.kt)("inlineCode",{parentName:"p"},"imported"),"."),(0,r.kt)("p",null,"It's recommended to submit the whole batch as a ",(0,r.kt)("inlineCode",{parentName:"p"},"linked")," chain of events, ensuring that\nif any event fails, none of them are committed, preserving the last timestamp unchanged.\nThis approach gives the application a chance to correct failed imported events, re-submitting\nthe batch again with the same user-defined timestamps."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-javascript"},"// First, load and import all accounts with their timestamps from the historical source.\nconst accountsBatch = [];\nfor (let index = 0; i < historicalAccounts.length; i++) {\n let account = historicalAccounts[i];\n // Set a unique and strictly increasing timestamp.\n historicalTimestamp += 1;\n account.timestamp = historicalTimestamp;\n // Set the account as `imported`.\n account.flags = AccountFlags.imported;\n // To ensure atomicity, the entire batch (except the last event in the chain)\n // must be `linked`.\n if (index < historicalAccounts.length - 1) {\n account.flags |= AccountFlags.linked;\n }\n\n accountsBatch.push(account);\n}\naccountErrors = await client.createAccounts(accountsBatch);\n\n// Error handling omitted.\n// Then, load and import all transfers with their timestamps from the historical source.\nconst transfersBatch = [];\nfor (let index = 0; i < historicalTransfers.length; i++) {\n let transfer = historicalTransfers[i];\n // Set a unique and strictly increasing timestamp.\n historicalTimestamp += 1;\n transfer.timestamp = historicalTimestamp;\n // Set the account as `imported`.\n transfer.flags = TransferFlags.imported;\n // To ensure atomicity, the entire batch (except the last event in the chain)\n // must be `linked`.\n if (index < historicalTransfers.length - 1) {\n transfer.flags |= TransferFlags.linked;\n }\n\n transfersBatch.push(transfer);\n}\ntransferErrors = await client.createAccounts(transfersBatch);\n// Error handling omitted.\n// Since it is a linked chain, in case of any error the entire batch is rolled back and can be retried\n// with the same historical timestamps without regressing the cluster timestamp.\n")))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[6221],{3905:(e,n,t)=>{t.d(n,{Zo:()=>d,kt:()=>h});var a=t(7294);function r(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function i(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);n&&(a=a.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,a)}return t}function s(e){for(var n=1;n=0||(r[t]=e[t]);return r}(e,n);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(r[t]=e[t])}return r}var o=a.createContext({}),c=function(e){var n=a.useContext(o),t=n;return e&&(t="function"==typeof e?e(n):s(s({},n),e)),t},d=function(e){var n=c(e.components);return a.createElement(o.Provider,{value:n},e.children)},p="mdxType",u={inlineCode:"code",wrapper:function(e){var n=e.children;return a.createElement(a.Fragment,{},n)}},m=a.forwardRef((function(e,n){var t=e.components,r=e.mdxType,i=e.originalType,o=e.parentName,d=l(e,["components","mdxType","originalType","parentName"]),p=c(t),m=r,h=p["".concat(o,".").concat(m)]||p[m]||u[m]||i;return t?a.createElement(h,s(s({ref:n},d),{},{components:t})):a.createElement(h,s({ref:n},d))}));function h(e,n){var t=arguments,r=n&&n.mdxType;if("string"==typeof e||r){var i=t.length,s=new Array(i);s[0]=m;var l={};for(var o in n)hasOwnProperty.call(n,o)&&(l[o]=n[o]);l.originalType=e,l[p]="string"==typeof e?e:r,s[1]=l;for(var c=2;c{t.r(n),t.d(n,{assets:()=>o,contentTitle:()=>s,default:()=>u,frontMatter:()=>i,metadata:()=>l,toc:()=>c});var a=t(7462),r=(t(7294),t(3905));const i={title:"Node.js"},s=void 0,l={unversionedId:"clients/node",id:"clients/node",title:"Node.js",description:"The TigerBeetle client for Node.js.",source:"@site/pages/clients/node.md",sourceDirName:"clients",slug:"/clients/node",permalink:"/clients/node",draft:!1,editUrl:"https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/clients/node.md",tags:[],version:"current",frontMatter:{title:"Node.js"},sidebar:"tutorialSidebar",previous:{title:"Java",permalink:"/clients/java"},next:{title:"Account",permalink:"/reference/account"}},o={},c=[{value:"Prerequisites",id:"prerequisites",level:2},{value:"Setup",id:"setup",level:2},{value:"Sample projects",id:"sample-projects",level:2},{value:"Sidenote: BigInt",id:"sidenote-bigint",level:3},{value:"Creating a Client",id:"creating-a-client",level:2},{value:"Creating Accounts",id:"creating-accounts",level:2},{value:"Account Flags",id:"account-flags",level:3},{value:"Response and Errors",id:"response-and-errors",level:3},{value:"Account Lookup",id:"account-lookup",level:2},{value:"Create Transfers",id:"create-transfers",level:2},{value:"Response and Errors",id:"response-and-errors-1",level:3},{value:"Batching",id:"batching",level:2},{value:"Queues and Workers",id:"queues-and-workers",level:3},{value:"Transfer Flags",id:"transfer-flags",level:2},{value:"Two-Phase Transfers",id:"two-phase-transfers",level:3},{value:"Post a Pending Transfer",id:"post-a-pending-transfer",level:4},{value:"Void a Pending Transfer",id:"void-a-pending-transfer",level:4},{value:"Transfer Lookup",id:"transfer-lookup",level:2},{value:"Get Account Transfers",id:"get-account-transfers",level:2},{value:"Get Account Balances",id:"get-account-balances",level:2},{value:"Query Accounts",id:"query-accounts",level:2},{value:"Query Transfers",id:"query-transfers",level:2},{value:"Linked Events",id:"linked-events",level:2},{value:"Imported Events",id:"imported-events",level:2}],d={toc:c},p="wrapper";function u(e){let{components:n,...t}=e;return(0,r.kt)(p,(0,a.Z)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,r.kt)("h1",{id:"tigerbeetle-node"},"tigerbeetle-node"),(0,r.kt)("p",null,"The TigerBeetle client for Node.js."),(0,r.kt)("h2",{id:"prerequisites"},"Prerequisites"),(0,r.kt)("p",null,"Linux >= 5.6 is the only production environment we\nsupport. But for ease of development we also support macOS and Windows."),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"NodeJS >= ",(0,r.kt)("inlineCode",{parentName:"li"},"18"))),(0,r.kt)("h2",{id:"setup"},"Setup"),(0,r.kt)("p",null,"First, create a directory for your project and ",(0,r.kt)("inlineCode",{parentName:"p"},"cd")," into the directory."),(0,r.kt)("p",null,"Then, install the TigerBeetle client:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},"npm install tigerbeetle-node\n")),(0,r.kt)("p",null,"Now, create ",(0,r.kt)("inlineCode",{parentName:"p"},"main.js")," and copy this into it:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-javascript"},'const { createClient } = require("tigerbeetle-node");\n\nconsole.log("Import ok!");\n')),(0,r.kt)("p",null,"Finally, build and run:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},"node main.js\n")),(0,r.kt)("p",null,"Now that all prerequisites and dependencies are correctly set\nup, let's dig into using TigerBeetle."),(0,r.kt)("h2",{id:"sample-projects"},"Sample projects"),(0,r.kt)("p",null,"This document is primarily a reference guide to\nthe client. Below are various sample projects demonstrating\nfeatures of TigerBeetle."),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"https://github.com/tigerbeetle/tigerbeetle/blob/main/src/clients/node/samples/basic/"},"Basic"),": Create two accounts and transfer an amount between them."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"https://github.com/tigerbeetle/tigerbeetle/blob/main/src/clients/node/samples/two-phase/"},"Two-Phase Transfer"),": Create two accounts and start a pending transfer between\nthem, then post the transfer."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"https://github.com/tigerbeetle/tigerbeetle/blob/main/src/clients/node/samples/two-phase-many/"},"Many Two-Phase Transfers"),": Create two accounts and start a number of pending transfer\nbetween them, posting and voiding alternating transfers.")),(0,r.kt)("h3",{id:"sidenote-bigint"},"Sidenote: ",(0,r.kt)("inlineCode",{parentName:"h3"},"BigInt")),(0,r.kt)("p",null,"TigerBeetle uses 64-bit integers for many fields while JavaScript's\nbuiltin ",(0,r.kt)("inlineCode",{parentName:"p"},"Number")," maximum value is ",(0,r.kt)("inlineCode",{parentName:"p"},"2^53-1"),". The ",(0,r.kt)("inlineCode",{parentName:"p"},"n")," suffix in JavaScript\nmeans the value is a ",(0,r.kt)("inlineCode",{parentName:"p"},"BigInt"),". This is useful for literal numbers. If\nyou already have a ",(0,r.kt)("inlineCode",{parentName:"p"},"Number")," variable though, you can call the ",(0,r.kt)("inlineCode",{parentName:"p"},"BigInt"),"\nconstructor to get a ",(0,r.kt)("inlineCode",{parentName:"p"},"BigInt")," from it. For example, ",(0,r.kt)("inlineCode",{parentName:"p"},"1n")," is the same as\n",(0,r.kt)("inlineCode",{parentName:"p"},"BigInt(1)"),"."),(0,r.kt)("h2",{id:"creating-a-client"},"Creating a Client"),(0,r.kt)("p",null,"A client is created with a cluster ID and replica\naddresses for all replicas in the cluster. The cluster\nID and replica addresses are both chosen by the system that\nstarts the TigerBeetle cluster."),(0,r.kt)("p",null,"Clients are thread-safe and a single instance should be shared\nbetween multiple concurrent tasks."),(0,r.kt)("p",null,"Multiple clients are useful when connecting to more than\none TigerBeetle cluster."),(0,r.kt)("p",null,"In this example the cluster ID is ",(0,r.kt)("inlineCode",{parentName:"p"},"0")," and there is one\nreplica. The address is read from the ",(0,r.kt)("inlineCode",{parentName:"p"},"TB_ADDRESS"),"\nenvironment variable and defaults to port ",(0,r.kt)("inlineCode",{parentName:"p"},"3000"),"."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-javascript"},'const client = createClient({\n cluster_id: 0n,\n replica_addresses: [process.env.TB_ADDRESS || "3000"],\n});\n')),(0,r.kt)("p",null,"The following are valid addresses:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"3000")," (interpreted as ",(0,r.kt)("inlineCode",{parentName:"li"},"127.0.0.1:3000"),")"),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"127.0.0.1:3000")," (interpreted as ",(0,r.kt)("inlineCode",{parentName:"li"},"127.0.0.1:3000"),")"),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"127.0.0.1")," (interpreted as ",(0,r.kt)("inlineCode",{parentName:"li"},"127.0.0.1:3001"),", ",(0,r.kt)("inlineCode",{parentName:"li"},"3001")," is the default port)")),(0,r.kt)("h2",{id:"creating-accounts"},"Creating Accounts"),(0,r.kt)("p",null,"See details for account fields in the ",(0,r.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/account"},"Accounts\nreference"),"."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-javascript"},"let account = {\n id: 137n,\n debits_pending: 0n,\n debits_posted: 0n,\n credits_pending: 0n,\n credits_posted: 0n,\n user_data_128: 0n,\n user_data_64: 0n,\n user_data_32: 0,\n reserved: 0,\n ledger: 1,\n code: 718,\n flags: 0,\n timestamp: 0n,\n};\n\nlet accountErrors = await client.createAccounts([account]);\n")),(0,r.kt)("h3",{id:"account-flags"},"Account Flags"),(0,r.kt)("p",null,"The account flags value is a bitfield. See details for\nthese flags in the ",(0,r.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/account#flags"},"Accounts\nreference"),"."),(0,r.kt)("p",null,"To toggle behavior for an account, combine enum values stored in the\n",(0,r.kt)("inlineCode",{parentName:"p"},"AccountFlags")," object (in TypeScript it is an actual enum) with\nbitwise-or:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"AccountFlags.linked")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"AccountFlags.debits_must_not_exceed_credits")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"AccountFlags.credits_must_not_exceed_credits")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"AccountFlags.history"))),(0,r.kt)("p",null,"For example, to link two accounts where the first account\nadditionally has the ",(0,r.kt)("inlineCode",{parentName:"p"},"debits_must_not_exceed_credits")," constraint:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-javascript"},"let account0 = {\n id: 100n,\n debits_pending: 0n,\n debits_posted: 0n,\n credits_pending: 0n,\n credits_posted: 0n,\n user_data_128: 0n,\n user_data_64: 0n,\n user_data_32: 0,\n reserved: 0,\n ledger: 1,\n code: 1,\n timestamp: 0n,\n flags: 0,\n};\nlet account1 = {\n id: 101n,\n debits_pending: 0n,\n debits_posted: 0n,\n credits_pending: 0n,\n credits_posted: 0n,\n user_data_128: 0n,\n user_data_64: 0n,\n user_data_32: 0,\n reserved: 0,\n ledger: 1,\n code: 1,\n timestamp: 0n,\n flags: 0,\n};\naccount0.flags = AccountFlags.linked |\n AccountFlags.debits_must_not_exceed_credits;\naccountErrors = await client.createAccounts([account0, account1]);\n")),(0,r.kt)("h3",{id:"response-and-errors"},"Response and Errors"),(0,r.kt)("p",null,"The response is an empty array if all accounts were\ncreated successfully. If the response is non-empty, each\nobject in the response array contains error information\nfor an account that failed. The error object contains an\nerror code and the index of the account in the request\nbatch."),(0,r.kt)("p",null,"See all error conditions in the ",(0,r.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/requests/create_accounts"},"create_accounts\nreference"),"."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-javascript"},"let account2 = {\n id: 102n,\n debits_pending: 0n,\n debits_posted: 0n,\n credits_pending: 0n,\n credits_posted: 0n,\n user_data_128: 0n,\n user_data_64: 0n,\n user_data_32: 0,\n reserved: 0,\n ledger: 1,\n code: 1,\n timestamp: 0n,\n flags: 0,\n};\nlet account3 = {\n id: 103n,\n debits_pending: 0n,\n debits_posted: 0n,\n credits_pending: 0n,\n credits_posted: 0n,\n user_data_128: 0n,\n user_data_64: 0n,\n user_data_32: 0,\n reserved: 0,\n ledger: 1,\n code: 1,\n timestamp: 0n,\n flags: 0,\n};\nlet account4 = {\n id: 104n,\n debits_pending: 0n,\n debits_posted: 0n,\n credits_pending: 0n,\n credits_posted: 0n,\n user_data_128: 0n,\n user_data_64: 0n,\n user_data_32: 0,\n reserved: 0,\n ledger: 1,\n code: 1,\n timestamp: 0n,\n flags: 0,\n};\naccountErrors = await client.createAccounts([account2, account3, account4]);\nfor (const error of accountErrors) {\n switch (error.result) {\n case CreateAccountError.exists:\n console.error(`Batch account at ${error.index} already exists.`);\n break;\n default:\n console.error(\n `Batch account at ${error.index} failed to create: ${\n CreateAccountError[error.result]\n }.`,\n );\n }\n}\n")),(0,r.kt)("p",null,"To handle errors you can either 1) exactly match error codes returned\nfrom ",(0,r.kt)("inlineCode",{parentName:"p"},"client.createAccounts")," with enum values in the\n",(0,r.kt)("inlineCode",{parentName:"p"},"CreateAccountError")," object, or you can 2) look up the error code in\nthe ",(0,r.kt)("inlineCode",{parentName:"p"},"CreateAccountError")," object for a human-readable string."),(0,r.kt)("h2",{id:"account-lookup"},"Account Lookup"),(0,r.kt)("p",null,"Account lookup is batched, like account creation. Pass\nin all IDs to fetch. The account for each matched ID is returned."),(0,r.kt)("p",null,"If no account matches an ID, no object is returned for\nthat account. So the order of accounts in the response is\nnot necessarily the same as the order of IDs in the\nrequest. You can refer to the ID field in the response to\ndistinguish accounts."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-javascript"},"const accounts = await client.lookupAccounts([137n, 138n]);\nconsole.log(accounts);\n/*\n * [{\n * id: 137n,\n * debits_pending: 0n,\n * debits_posted: 0n,\n * credits_pending: 0n,\n * credits_posted: 0n,\n * user_data_128: 0n,\n * user_data_64: 0n,\n * user_data_32: 0,\n * reserved: 0,\n * ledger: 1,\n * code: 718,\n * flags: 0,\n * timestamp: 1623062009212508993n,\n * }]\n */\n")),(0,r.kt)("h2",{id:"create-transfers"},"Create Transfers"),(0,r.kt)("p",null,"This creates a journal entry between two accounts."),(0,r.kt)("p",null,"See details for transfer fields in the ",(0,r.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/transfer"},"Transfers\nreference"),"."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-javascript"},"let transfers = [{\n id: 1n,\n debit_account_id: 102n,\n credit_account_id: 103n,\n amount: 10n,\n pending_id: 0n,\n user_data_128: 0n,\n user_data_64: 0n,\n user_data_32: 0,\n timeout: 0,\n ledger: 1,\n code: 720,\n flags: 0,\n timestamp: 0n,\n}];\nlet transferErrors = await client.createTransfers(transfers);\n")),(0,r.kt)("h3",{id:"response-and-errors-1"},"Response and Errors"),(0,r.kt)("p",null,"The response is an empty array if all transfers were created\nsuccessfully. If the response is non-empty, each object in the\nresponse array contains error information for a transfer that\nfailed. The error object contains an error code and the index of the\ntransfer in the request batch."),(0,r.kt)("p",null,"See all error conditions in the ",(0,r.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/requests/create_transfers"},"create_transfers\nreference"),"."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-javascript"},"for (const error of transferErrors) {\n switch (error.result) {\n case CreateTransferError.exists:\n console.error(`Batch transfer at ${error.index} already exists.`);\n break;\n default:\n console.error(\n `Batch transfer at ${error.index} failed to create: ${\n CreateTransferError[error.result]\n }.`,\n );\n }\n}\n")),(0,r.kt)("p",null,"To handle errors you can either 1) exactly match error codes returned\nfrom ",(0,r.kt)("inlineCode",{parentName:"p"},"client.createTransfers")," with enum values in the\n",(0,r.kt)("inlineCode",{parentName:"p"},"CreateTransferError")," object, or you can 2) look up the error code in\nthe ",(0,r.kt)("inlineCode",{parentName:"p"},"CreateTransferError")," object for a human-readable string."),(0,r.kt)("h2",{id:"batching"},"Batching"),(0,r.kt)("p",null,"TigerBeetle performance is maximized when you batch\nAPI requests. The client does not do this automatically for\nyou. So, for example, you ",(0,r.kt)("em",{parentName:"p"},"can")," insert 1 million transfers\none at a time like so:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-javascript"},"for (let i = 0; i < transfers.len; i++) {\n const transferErrors = await client.createTransfers(transfers[i]);\n // Error handling omitted.\n}\n")),(0,r.kt)("p",null,"But the insert rate will be a ",(0,r.kt)("em",{parentName:"p"},"fraction")," of\npotential. Instead, ",(0,r.kt)("strong",{parentName:"p"},"always batch what you can"),"."),(0,r.kt)("p",null,"The maximum batch size is set in the TigerBeetle server. The default\nis 8190."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-javascript"},"const BATCH_SIZE = 8190;\nfor (let i = 0; i < transfers.length; i += BATCH_SIZE) {\n const transferErrors = await client.createTransfers(\n transfers.slice(i, Math.min(transfers.length, BATCH_SIZE)),\n );\n // Error handling omitted.\n}\n")),(0,r.kt)("h3",{id:"queues-and-workers"},"Queues and Workers"),(0,r.kt)("p",null,"If you are making requests to TigerBeetle from workers\npulling jobs from a queue, you can batch requests to\nTigerBeetle by having the worker act on multiple jobs from\nthe queue at once rather than one at a time. i.e. pulling\nmultiple jobs from the queue rather than just one."),(0,r.kt)("h2",{id:"transfer-flags"},"Transfer Flags"),(0,r.kt)("p",null,"The transfer ",(0,r.kt)("inlineCode",{parentName:"p"},"flags")," value is a bitfield. See details for these flags in\nthe ",(0,r.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/transfer#flags"},"Transfers\nreference"),"."),(0,r.kt)("p",null,"To toggle behavior for a transfer, combine enum values stored in the\n",(0,r.kt)("inlineCode",{parentName:"p"},"TransferFlags")," object (in TypeScript it is an actual enum) with\nbitwise-or:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"TransferFlags.linked")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"TransferFlags.pending")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"TransferFlags.post_pending_transfer")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"TransferFlags.void_pending_transfer"))),(0,r.kt)("p",null,"For example, to link ",(0,r.kt)("inlineCode",{parentName:"p"},"transfer0")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"transfer1"),":"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-javascript"},"let transfer0 = {\n id: 2n,\n debit_account_id: 102n,\n credit_account_id: 103n,\n amount: 10n,\n pending_id: 0n,\n user_data_128: 0n,\n user_data_64: 0n,\n user_data_32: 0,\n timeout: 0,\n ledger: 1,\n code: 720,\n flags: 0,\n timestamp: 0n,\n};\nlet transfer1 = {\n id: 3n,\n debit_account_id: 102n,\n credit_account_id: 103n,\n amount: 10n,\n pending_id: 0n,\n user_data_128: 0n,\n user_data_64: 0n,\n user_data_32: 0,\n timeout: 0,\n ledger: 1,\n code: 720,\n flags: 0,\n timestamp: 0n,\n};\ntransfer0.flags = TransferFlags.linked;\n// Create the transfer\ntransferErrors = await client.createTransfers([transfer0, transfer1]);\n")),(0,r.kt)("h3",{id:"two-phase-transfers"},"Two-Phase Transfers"),(0,r.kt)("p",null,"Two-phase transfers are supported natively by toggling the appropriate\nflag. TigerBeetle will then adjust the ",(0,r.kt)("inlineCode",{parentName:"p"},"credits_pending")," and\n",(0,r.kt)("inlineCode",{parentName:"p"},"debits_pending")," fields of the appropriate accounts. A corresponding\npost pending transfer then needs to be sent to post or void the\ntransfer."),(0,r.kt)("h4",{id:"post-a-pending-transfer"},"Post a Pending Transfer"),(0,r.kt)("p",null,"With ",(0,r.kt)("inlineCode",{parentName:"p"},"flags")," set to ",(0,r.kt)("inlineCode",{parentName:"p"},"post_pending_transfer"),",\nTigerBeetle will post the transfer. TigerBeetle will atomically roll\nback the changes to ",(0,r.kt)("inlineCode",{parentName:"p"},"debits_pending")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"credits_pending")," of the\nappropriate accounts and apply them to the ",(0,r.kt)("inlineCode",{parentName:"p"},"debits_posted")," and\n",(0,r.kt)("inlineCode",{parentName:"p"},"credits_posted")," balances."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-javascript"},"let transfer2 = {\n id: 4n,\n debit_account_id: 102n,\n credit_account_id: 103n,\n amount: 10n,\n pending_id: 0n,\n user_data_128: 0n,\n user_data_64: 0n,\n user_data_32: 0,\n timeout: 0,\n ledger: 1,\n code: 720,\n flags: TransferFlags.pending,\n timestamp: 0n,\n};\ntransferErrors = await client.createTransfers([transfer2]);\n\nlet transfer3 = {\n id: 5n,\n debit_account_id: 102n,\n credit_account_id: 103n,\n // Post the entire pending amount.\n amount: amount_max,\n pending_id: 4n,\n user_data_128: 0n,\n user_data_64: 0n,\n user_data_32: 0,\n timeout: 0,\n ledger: 1,\n code: 720,\n flags: TransferFlags.post_pending_transfer,\n timestamp: 0n,\n};\ntransferErrors = await client.createTransfers([transfer3]);\n")),(0,r.kt)("h4",{id:"void-a-pending-transfer"},"Void a Pending Transfer"),(0,r.kt)("p",null,"In contrast, with ",(0,r.kt)("inlineCode",{parentName:"p"},"flags")," set to ",(0,r.kt)("inlineCode",{parentName:"p"},"void_pending_transfer"),",\nTigerBeetle will void the transfer. TigerBeetle will roll\nback the changes to ",(0,r.kt)("inlineCode",{parentName:"p"},"debits_pending")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"credits_pending")," of the\nappropriate accounts and ",(0,r.kt)("strong",{parentName:"p"},"not")," apply them to the ",(0,r.kt)("inlineCode",{parentName:"p"},"debits_posted")," and\n",(0,r.kt)("inlineCode",{parentName:"p"},"credits_posted")," balances."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-javascript"},"let transfer4 = {\n id: 4n,\n debit_account_id: 102n,\n credit_account_id: 103n,\n amount: 10n,\n pending_id: 0n,\n user_data_128: 0n,\n user_data_64: 0n,\n user_data_32: 0,\n timeout: 0,\n ledger: 1,\n code: 720,\n flags: TransferFlags.pending,\n timestamp: 0n,\n};\ntransferErrors = await client.createTransfers([transfer4]);\n\nlet transfer5 = {\n id: 7n,\n debit_account_id: 102n,\n credit_account_id: 103n,\n amount: 10n,\n pending_id: 6n,\n user_data_128: 0n,\n user_data_64: 0n,\n user_data_32: 0,\n timeout: 0,\n ledger: 1,\n code: 720,\n flags: TransferFlags.void_pending_transfer,\n timestamp: 0n,\n};\ntransferErrors = await client.createTransfers([transfer5]);\n")),(0,r.kt)("h2",{id:"transfer-lookup"},"Transfer Lookup"),(0,r.kt)("p",null,"NOTE: While transfer lookup exists, it is not a flexible query API. We\nare developing query APIs and there will be new methods for querying\ntransfers in the future."),(0,r.kt)("p",null,"Transfer lookup is batched, like transfer creation. Pass in all ",(0,r.kt)("inlineCode",{parentName:"p"},"id"),"s to\nfetch, and matched transfers are returned."),(0,r.kt)("p",null,"If no transfer matches an ",(0,r.kt)("inlineCode",{parentName:"p"},"id"),", no object is returned for that\ntransfer. So the order of transfers in the response is not necessarily\nthe same as the order of ",(0,r.kt)("inlineCode",{parentName:"p"},"id"),"s in the request. You can refer to the\n",(0,r.kt)("inlineCode",{parentName:"p"},"id")," field in the response to distinguish transfers."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-javascript"},"transfers = await client.lookupTransfers([1n, 2n]);\nconsole.log(transfers);\n/*\n * [{\n * id: 1n,\n * debit_account_id: 102n,\n * credit_account_id: 103n,\n * amount: 10n,\n * pending_id: 0n,\n * user_data_128: 0n,\n * user_data_64: 0n,\n * user_data_32: 0,\n * timeout: 0,\n * ledger: 1,\n * code: 720,\n * flags: 0,\n * timestamp: 1623062009212508993n,\n * }]\n */\n")),(0,r.kt)("h2",{id:"get-account-transfers"},"Get Account Transfers"),(0,r.kt)("p",null,"NOTE: This is a preview API that is subject to breaking changes once we have\na stable querying API."),(0,r.kt)("p",null,"Fetches the transfers involving a given account, allowing basic filter and pagination\ncapabilities."),(0,r.kt)("p",null,"The transfers in the response are sorted by ",(0,r.kt)("inlineCode",{parentName:"p"},"timestamp")," in chronological or\nreverse-chronological order."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-javascript"},"let filter = {\n account_id: 2n,\n user_data_128: 0n, // No filter by UserData.\n user_data_64: 0n,\n user_data_32: 0,\n code: 0, // No filter by Code.\n timestamp_min: 0n, // No filter by Timestamp.\n timestamp_max: 0n, // No filter by Timestamp.\n limit: 10, // Limit to ten balances at most.\n flags: AccountFilterFlags.debits | // Include transfer from the debit side.\n AccountFilterFlags.credits | // Include transfer from the credit side.\n AccountFilterFlags.reversed, // Sort by timestamp in reverse-chronological order.\n};\n\nconst account_transfers = await client.getAccountTransfers(filter);\n")),(0,r.kt)("h2",{id:"get-account-balances"},"Get Account Balances"),(0,r.kt)("p",null,"NOTE: This is a preview API that is subject to breaking changes once we have\na stable querying API."),(0,r.kt)("p",null,"Fetches the point-in-time balances of a given account, allowing basic filter and\npagination capabilities."),(0,r.kt)("p",null,"Only accounts created with the flag\n",(0,r.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/account#flagshistory"},(0,r.kt)("inlineCode",{parentName:"a"},"history"))," set retain\n",(0,r.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/requests/get_account_balances"},"historical balances"),"."),(0,r.kt)("p",null,"The balances in the response are sorted by ",(0,r.kt)("inlineCode",{parentName:"p"},"timestamp")," in chronological or\nreverse-chronological order."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-javascript"},"filter = {\n account_id: 2n,\n user_data_128: 0n, // No filter by UserData.\n user_data_64: 0n,\n user_data_32: 0,\n code: 0, // No filter by Code.\n timestamp_min: 0n, // No filter by Timestamp.\n timestamp_max: 0n, // No filter by Timestamp.\n limit: 10, // Limit to ten balances at most.\n flags: AccountFilterFlags.debits | // Include transfer from the debit side.\n AccountFilterFlags.credits | // Include transfer from the credit side.\n AccountFilterFlags.reversed, // Sort by timestamp in reverse-chronological order.\n};\n\nconst account_balances = await client.getAccountBalances(filter);\n")),(0,r.kt)("h2",{id:"query-accounts"},"Query Accounts"),(0,r.kt)("p",null,"NOTE: This is a preview API that is subject to breaking changes once we have\na stable querying API."),(0,r.kt)("p",null,"Query accounts by the intersection of some fields and by timestamp range."),(0,r.kt)("p",null,"The accounts in the response are sorted by ",(0,r.kt)("inlineCode",{parentName:"p"},"timestamp")," in chronological or\nreverse-chronological order."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-javascript"},"var query_filter = {\n user_data_128: 1000n, // Filter by UserData.\n user_data_64: 100n,\n user_data_32: 10,\n code: 1, // Filter by Code.\n ledger: 0, // No filter by Ledger.\n timestamp_min: 0n, // No filter by Timestamp.\n timestamp_max: 0n, // No filter by Timestamp.\n limit: 10, // Limit to ten balances at most.\n flags: QueryFilterFlags.reversed, // Sort by timestamp in reverse-chronological order.\n};\nconst query_accounts = await client.queryAccounts(query_filter);\n")),(0,r.kt)("h2",{id:"query-transfers"},"Query Transfers"),(0,r.kt)("p",null,"NOTE: This is a preview API that is subject to breaking changes once we have\na stable querying API."),(0,r.kt)("p",null,"Query transfers by the intersection of some fields and by timestamp range."),(0,r.kt)("p",null,"The transfers in the response are sorted by ",(0,r.kt)("inlineCode",{parentName:"p"},"timestamp")," in chronological or\nreverse-chronological order."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-javascript"},"query_filter = {\n user_data_128: 1000n, // Filter by UserData.\n user_data_64: 100n,\n user_data_32: 10,\n code: 1, // Filter by Code.\n ledger: 0, // No filter by Ledger.\n timestamp_min: 0n, // No filter by Timestamp.\n timestamp_max: 0n, // No filter by Timestamp.\n limit: 10, // Limit to ten balances at most.\n flags: QueryFilterFlags.reversed, // Sort by timestamp in reverse-chronological order.\n};\nconst query_transfers = await client.queryTransfers(query_filter);\n")),(0,r.kt)("h2",{id:"linked-events"},"Linked Events"),(0,r.kt)("p",null,"When the ",(0,r.kt)("inlineCode",{parentName:"p"},"linked")," flag is specified for an account when creating accounts or\na transfer when creating transfers, it links that event with the next event in the\nbatch, to create a chain of events, of arbitrary length, which all\nsucceed or fail together. The tail of a chain is denoted by the first\nevent without this flag. The last event in a batch may therefore never\nhave the ",(0,r.kt)("inlineCode",{parentName:"p"},"linked")," flag set as this would leave a chain\nopen-ended. Multiple chains or individual events may coexist within a\nbatch to succeed or fail independently."),(0,r.kt)("p",null,"Events within a chain are executed within order, or are rolled back on\nerror, so that the effect of each event in the chain is visible to the\nnext, and so that the chain is either visible or invisible as a unit\nto subsequent events after the chain. The event that was the first to\nbreak the chain will have a unique error result. Other events in the\nchain will have their error result set to ",(0,r.kt)("inlineCode",{parentName:"p"},"linked_event_failed"),"."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-javascript"},"const batch = [];\nlet linkedFlag = 0;\nlinkedFlag |= TransferFlags.linked;\n\n// An individual transfer (successful):\nbatch.push({ id: 1n /* , ... */ });\n\n// A chain of 4 transfers (the last transfer in the chain closes the chain with linked=false):\nbatch.push({ id: 2n, /* ..., */ flags: linkedFlag }); // Commit/rollback.\nbatch.push({ id: 3n, /* ..., */ flags: linkedFlag }); // Commit/rollback.\nbatch.push({ id: 2n, /* ..., */ flags: linkedFlag }); // Fail with exists\nbatch.push({ id: 4n, /* ..., */ flags: 0 }); // Fail without committing.\n\n// An individual transfer (successful):\n// This should not see any effect from the failed chain above.\nbatch.push({ id: 2n, /* ..., */ flags: 0 });\n\n// A chain of 2 transfers (the first transfer fails the chain):\nbatch.push({ id: 2n, /* ..., */ flags: linkedFlag });\nbatch.push({ id: 3n, /* ..., */ flags: 0 });\n\n// A chain of 2 transfers (successful):\nbatch.push({ id: 3n, /* ..., */ flags: linkedFlag });\nbatch.push({ id: 4n, /* ..., */ flags: 0 });\n\nconst errors = await client.createTransfers(batch);\n\n/**\n * console.log(errors);\n * [\n * { index: 1, error: 1 }, // linked_event_failed\n * { index: 2, error: 1 }, // linked_event_failed\n * { index: 3, error: 25 }, // exists\n * { index: 4, error: 1 }, // linked_event_failed\n *\n * { index: 6, error: 17 }, // exists_with_different_flags\n * { index: 7, error: 1 }, // linked_event_failed\n * ]\n */\n")),(0,r.kt)("h2",{id:"imported-events"},"Imported Events"),(0,r.kt)("p",null,"When the ",(0,r.kt)("inlineCode",{parentName:"p"},"imported")," flag is specified for an account when creating accounts or\na transfer when creating transfers, it allows importing historical events with\na user-defined timestamp."),(0,r.kt)("p",null,"The entire batch of events must be set with the flag ",(0,r.kt)("inlineCode",{parentName:"p"},"imported"),"."),(0,r.kt)("p",null,"It's recommended to submit the whole batch as a ",(0,r.kt)("inlineCode",{parentName:"p"},"linked")," chain of events, ensuring that\nif any event fails, none of them are committed, preserving the last timestamp unchanged.\nThis approach gives the application a chance to correct failed imported events, re-submitting\nthe batch again with the same user-defined timestamps."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-javascript"},"// First, load and import all accounts with their timestamps from the historical source.\nconst accountsBatch = [];\nfor (let index = 0; i < historicalAccounts.length; i++) {\n let account = historicalAccounts[i];\n // Set a unique and strictly increasing timestamp.\n historicalTimestamp += 1;\n account.timestamp = historicalTimestamp;\n // Set the account as `imported`.\n account.flags = AccountFlags.imported;\n // To ensure atomicity, the entire batch (except the last event in the chain)\n // must be `linked`.\n if (index < historicalAccounts.length - 1) {\n account.flags |= AccountFlags.linked;\n }\n\n accountsBatch.push(account);\n}\naccountErrors = await client.createAccounts(accountsBatch);\n\n// Error handling omitted.\n// Then, load and import all transfers with their timestamps from the historical source.\nconst transfersBatch = [];\nfor (let index = 0; i < historicalTransfers.length; i++) {\n let transfer = historicalTransfers[i];\n // Set a unique and strictly increasing timestamp.\n historicalTimestamp += 1;\n transfer.timestamp = historicalTimestamp;\n // Set the account as `imported`.\n transfer.flags = TransferFlags.imported;\n // To ensure atomicity, the entire batch (except the last event in the chain)\n // must be `linked`.\n if (index < historicalTransfers.length - 1) {\n transfer.flags |= TransferFlags.linked;\n }\n\n transfersBatch.push(transfer);\n}\ntransferErrors = await client.createAccounts(transfersBatch);\n// Error handling omitted.\n// Since it is a linked chain, in case of any error the entire batch is rolled back and can be retried\n// with the same historical timestamps without regressing the cluster timestamp.\n")))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/99843d3e.25678062.js b/assets/js/99843d3e.4c3af039.js similarity index 72% rename from assets/js/99843d3e.25678062.js rename to assets/js/99843d3e.4c3af039.js index 03ebc07..2d9b7d2 100644 --- a/assets/js/99843d3e.25678062.js +++ b/assets/js/99843d3e.4c3af039.js @@ -1 +1 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[7736],{3905:(e,t,n)=>{n.d(t,{Zo:()=>d,kt:()=>h});var a=n(7294);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function s(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function i(e){for(var t=1;t=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var s=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var o=a.createContext({}),c=function(e){var t=a.useContext(o),n=t;return e&&(n="function"==typeof e?e(t):i(i({},t),e)),n},d=function(e){var t=c(e.components);return a.createElement(o.Provider,{value:t},e.children)},p="mdxType",u={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},f=a.forwardRef((function(e,t){var n=e.components,r=e.mdxType,s=e.originalType,o=e.parentName,d=l(e,["components","mdxType","originalType","parentName"]),p=c(n),f=r,h=p["".concat(o,".").concat(f)]||p[f]||u[f]||s;return n?a.createElement(h,i(i({ref:t},d),{},{components:n})):a.createElement(h,i({ref:t},d))}));function h(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var s=n.length,i=new Array(s);i[0]=f;var l={};for(var o in t)hasOwnProperty.call(t,o)&&(l[o]=t[o]);l.originalType=e,l[p]="string"==typeof e?e:r,i[1]=l;for(var c=2;c{n.r(t),n.d(t,{assets:()=>o,contentTitle:()=>i,default:()=>u,frontMatter:()=>s,metadata:()=>l,toc:()=>c});var a=n(7462),r=(n(7294),n(3905));const s={title:"Java"},i=void 0,l={unversionedId:"clients/java",id:"clients/java",title:"Java",description:"The TigerBeetle client for Java.",source:"@site/pages/clients/java.md",sourceDirName:"clients",slug:"/clients/java",permalink:"/clients/java",draft:!1,editUrl:"https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/clients/java.md",tags:[],version:"current",frontMatter:{title:"Java"},sidebar:"tutorialSidebar",previous:{title:"Go",permalink:"/clients/go"},next:{title:"Node.js",permalink:"/clients/node"}},o={},c=[{value:"Prerequisites",id:"prerequisites",level:2},{value:"Setup",id:"setup",level:2},{value:"Sample projects",id:"sample-projects",level:2},{value:"Creating a Client",id:"creating-a-client",level:2},{value:"Creating Accounts",id:"creating-accounts",level:2},{value:"Account Flags",id:"account-flags",level:3},{value:"Response and Errors",id:"response-and-errors",level:3},{value:"Account Lookup",id:"account-lookup",level:2},{value:"Create Transfers",id:"create-transfers",level:2},{value:"Response and Errors",id:"response-and-errors-1",level:3},{value:"Batching",id:"batching",level:2},{value:"Queues and Workers",id:"queues-and-workers",level:3},{value:"Transfer Flags",id:"transfer-flags",level:2},{value:"Two-Phase Transfers",id:"two-phase-transfers",level:3},{value:"Post a Pending Transfer",id:"post-a-pending-transfer",level:4},{value:"Void a Pending Transfer",id:"void-a-pending-transfer",level:4},{value:"Transfer Lookup",id:"transfer-lookup",level:2},{value:"Get Account Transfers",id:"get-account-transfers",level:2},{value:"Get Account Balances",id:"get-account-balances",level:2},{value:"Query Accounts",id:"query-accounts",level:2},{value:"Query Transfers",id:"query-transfers",level:2},{value:"Linked Events",id:"linked-events",level:2},{value:"Imported Events",id:"imported-events",level:2}],d={toc:c},p="wrapper";function u(e){let{components:t,...n}=e;return(0,r.kt)(p,(0,a.Z)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("h1",{id:"tigerbeetle-java"},"tigerbeetle-java"),(0,r.kt)("p",null,"The TigerBeetle client for Java."),(0,r.kt)("p",null,(0,r.kt)("a",{parentName:"p",href:"https://javadoc.io/doc/com.tigerbeetle/tigerbeetle-java"},(0,r.kt)("img",{parentName:"a",src:"https://javadoc.io/badge2/com.tigerbeetle/tigerbeetle-java/javadoc.svg",alt:"javadoc"}))),(0,r.kt)("p",null,(0,r.kt)("a",{parentName:"p",href:"https://central.sonatype.com/namespace/com.tigerbeetle"},(0,r.kt)("img",{parentName:"a",src:"https://img.shields.io/maven-central/v/com.tigerbeetle/tigerbeetle-java",alt:"maven-central"}))),(0,r.kt)("h2",{id:"prerequisites"},"Prerequisites"),(0,r.kt)("p",null,"Linux >= 5.6 is the only production environment we\nsupport. But for ease of development we also support macOS and Windows."),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"Java >= 11"),(0,r.kt)("li",{parentName:"ul"},"Maven >= 3.6 (not strictly necessary but it's what our guides assume)")),(0,r.kt)("h2",{id:"setup"},"Setup"),(0,r.kt)("p",null,"First, create a directory for your project and ",(0,r.kt)("inlineCode",{parentName:"p"},"cd")," into the directory."),(0,r.kt)("p",null,"Then create ",(0,r.kt)("inlineCode",{parentName:"p"},"pom.xml")," and copy this into it:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-xml"},'\n 4.0.0\n\n com.tigerbeetle\n samples\n 1.0-SNAPSHOT\n\n \n 11\n 11\n \n\n \n \n \n org.apache.maven.plugins\n maven-compiler-plugin\n 3.8.1\n \n \n -Xlint:all,-options,-path\n \n \n \n\n \n org.codehaus.mojo\n exec-maven-plugin\n 1.6.0\n \n com.tigerbeetle.samples.Main\n \n \n \n \n\n \n \n com.tigerbeetle\n tigerbeetle-java\n \x3c!-- Grab the latest commit from: https://repo1.maven.org/maven2/com/tigerbeetle/tigerbeetle-java/maven-metadata.xml --\x3e\n 0.0.1-3431\n \n \n\n')),(0,r.kt)("p",null,"Then, install the TigerBeetle client:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},"mvn install\n")),(0,r.kt)("p",null,"Now, create ",(0,r.kt)("inlineCode",{parentName:"p"},"src/main/java/Main.java")," and copy this into it:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-java"},'package com.tigerbeetle.samples;\n\nimport com.tigerbeetle.*;\n\npublic final class Main {\n public static void main(String[] args) throws Exception {\n System.out.println("Import ok!");\n }\n}\n')),(0,r.kt)("p",null,"Finally, build and run:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},"mvn exec:java\n")),(0,r.kt)("p",null,"Now that all prerequisites and dependencies are correctly set\nup, let's dig into using TigerBeetle."),(0,r.kt)("h2",{id:"sample-projects"},"Sample projects"),(0,r.kt)("p",null,"This document is primarily a reference guide to\nthe client. Below are various sample projects demonstrating\nfeatures of TigerBeetle."),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"https://github.com/tigerbeetle/tigerbeetle/blob/main/src/clients/java/samples/basic/"},"Basic"),": Create two accounts and transfer an amount between them."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"https://github.com/tigerbeetle/tigerbeetle/blob/main/src/clients/java/samples/two-phase/"},"Two-Phase Transfer"),": Create two accounts and start a pending transfer between\nthem, then post the transfer."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"https://github.com/tigerbeetle/tigerbeetle/blob/main/src/clients/java/samples/two-phase-many/"},"Many Two-Phase Transfers"),": Create two accounts and start a number of pending transfer\nbetween them, posting and voiding alternating transfers.")),(0,r.kt)("h2",{id:"creating-a-client"},"Creating a Client"),(0,r.kt)("p",null,"A client is created with a cluster ID and replica\naddresses for all replicas in the cluster. The cluster\nID and replica addresses are both chosen by the system that\nstarts the TigerBeetle cluster."),(0,r.kt)("p",null,"Clients are thread-safe and a single instance should be shared\nbetween multiple concurrent tasks."),(0,r.kt)("p",null,"Multiple clients are useful when connecting to more than\none TigerBeetle cluster."),(0,r.kt)("p",null,"In this example the cluster ID is ",(0,r.kt)("inlineCode",{parentName:"p"},"0")," and there is one\nreplica. The address is read from the ",(0,r.kt)("inlineCode",{parentName:"p"},"TB_ADDRESS"),"\nenvironment variable and defaults to port ",(0,r.kt)("inlineCode",{parentName:"p"},"3000"),"."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-java"},'String replicaAddress = System.getenv("TB_ADDRESS");\nbyte[] clusterID = UInt128.asBytes(0);\nString[] replicaAddresses = new String[] {replicaAddress == null ? "3000" : replicaAddress};\ntry (var client = new Client(clusterID, replicaAddresses)) {\n // Use client\n}\n')),(0,r.kt)("p",null,"The following are valid addresses:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"3000")," (interpreted as ",(0,r.kt)("inlineCode",{parentName:"li"},"127.0.0.1:3000"),")"),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"127.0.0.1:3000")," (interpreted as ",(0,r.kt)("inlineCode",{parentName:"li"},"127.0.0.1:3000"),")"),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"127.0.0.1")," (interpreted as ",(0,r.kt)("inlineCode",{parentName:"li"},"127.0.0.1:3001"),", ",(0,r.kt)("inlineCode",{parentName:"li"},"3001")," is the default port)")),(0,r.kt)("h2",{id:"creating-accounts"},"Creating Accounts"),(0,r.kt)("p",null,"See details for account fields in the ",(0,r.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/account"},"Accounts\nreference"),"."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-java"},"AccountBatch accounts = new AccountBatch(1);\naccounts.add();\naccounts.setId(137);\naccounts.setUserData128(UInt128.asBytes(java.util.UUID.randomUUID()));\naccounts.setUserData64(1234567890);\naccounts.setUserData32(42);\naccounts.setLedger(1);\naccounts.setCode(718);\naccounts.setFlags(0);\n\nCreateAccountResultBatch accountErrors = client.createAccounts(accounts);\n")),(0,r.kt)("p",null,"The 128-bit fields like ",(0,r.kt)("inlineCode",{parentName:"p"},"id")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"user_data_128")," have a few\noverrides to make it easier to integrate. You can either\npass in a long, a pair of longs (least and most\nsignificant bits), or a ",(0,r.kt)("inlineCode",{parentName:"p"},"byte[]"),"."),(0,r.kt)("p",null,"There is also a ",(0,r.kt)("inlineCode",{parentName:"p"},"com.tigerbeetle.UInt128")," helper with static\nmethods for converting 128-bit little-endian unsigned integers\nbetween instances of ",(0,r.kt)("inlineCode",{parentName:"p"},"long"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"java.util.UUID"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"java.math.BigInteger")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"byte[]"),"."),(0,r.kt)("p",null,"The fields for transfer amounts and account balances are also 128-bit,\nbut they are always represented as a ",(0,r.kt)("inlineCode",{parentName:"p"},"java.math.BigInteger"),"."),(0,r.kt)("h3",{id:"account-flags"},"Account Flags"),(0,r.kt)("p",null,"The account flags value is a bitfield. See details for\nthese flags in the ",(0,r.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/account#flags"},"Accounts\nreference"),"."),(0,r.kt)("p",null,"To toggle behavior for an account, combine enum values stored in the\n",(0,r.kt)("inlineCode",{parentName:"p"},"AccountFlags")," object with bitwise-or:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"AccountFlags.LINKED")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"AccountFlags.DEBITS_MUST_NOT_EXCEED_CREDITS")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"AccountFlags.CREDITS_MUST_NOT_EXCEED_CREDITS")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"AccountFlags.HISTORY"))),(0,r.kt)("p",null,"For example, to link two accounts where the first account\nadditionally has the ",(0,r.kt)("inlineCode",{parentName:"p"},"debits_must_not_exceed_credits")," constraint:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-java"},"accounts = new AccountBatch(3);\n\n// First account\naccounts.add();\n// Code to fill out fields for first account\naccounts.setFlags(AccountFlags.LINKED | AccountFlags.DEBITS_MUST_NOT_EXCEED_CREDITS);\n\n// Second account\naccounts.add();\n// Code to fill out fields for second account\n\naccountErrors = client.createAccounts(accounts);\n")),(0,r.kt)("h3",{id:"response-and-errors"},"Response and Errors"),(0,r.kt)("p",null,"The response is an empty array if all accounts were\ncreated successfully. If the response is non-empty, each\nobject in the response array contains error information\nfor an account that failed. The error object contains an\nerror code and the index of the account in the request\nbatch."),(0,r.kt)("p",null,"See all error conditions in the ",(0,r.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/requests/create_accounts"},"create_accounts\nreference"),"."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-java"},'while (accountErrors.next()) {\n switch (accountErrors.getResult()) {\n case Exists:\n System.err.printf("Account at %d already exists.\\n",\n accountErrors.getIndex());\n break;\n\n default:\n System.err.printf("Error creating account at %d: %s\\n",\n accountErrors.getIndex(), accountErrors.getResult());\n break;\n }\n}\n')),(0,r.kt)("h2",{id:"account-lookup"},"Account Lookup"),(0,r.kt)("p",null,"Account lookup is batched, like account creation. Pass\nin all IDs to fetch. The account for each matched ID is returned."),(0,r.kt)("p",null,"If no account matches an ID, no object is returned for\nthat account. So the order of accounts in the response is\nnot necessarily the same as the order of IDs in the\nrequest. You can refer to the ID field in the response to\ndistinguish accounts."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-java"},"IdBatch ids = new IdBatch(2);\nids.add(137);\nids.add(138);\naccounts = client.lookupAccounts(ids);\n")),(0,r.kt)("h2",{id:"create-transfers"},"Create Transfers"),(0,r.kt)("p",null,"This creates a journal entry between two accounts."),(0,r.kt)("p",null,"See details for transfer fields in the ",(0,r.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/transfer"},"Transfers\nreference"),"."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-java"},"TransferBatch transfers = new TransferBatch(1);\ntransfers.add();\ntransfers.setId(1);\ntransfers.setDebitAccountId(1);\ntransfers.setCreditAccountId(2);\ntransfers.setAmount(10);\ntransfers.setUserData128(UInt128.asBytes(java.util.UUID.randomUUID()));\ntransfers.setUserData64(1234567890);\ntransfers.setUserData32(42);\ntransfers.setTimeout(0);\ntransfers.setLedger(1);\ntransfers.setCode(1);\ntransfers.setFlags(0);\n\nCreateTransferResultBatch transferErrors = client.createTransfers(transfers);\n")),(0,r.kt)("h3",{id:"response-and-errors-1"},"Response and Errors"),(0,r.kt)("p",null,"The response is an empty array if all transfers were created\nsuccessfully. If the response is non-empty, each object in the\nresponse array contains error information for a transfer that\nfailed. The error object contains an error code and the index of the\ntransfer in the request batch."),(0,r.kt)("p",null,"See all error conditions in the ",(0,r.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/requests/create_transfers"},"create_transfers\nreference"),"."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-java"},'while (transferErrors.next()) {\n switch (transferErrors.getResult()) {\n case ExceedsCredits:\n System.err.printf("Transfer at %d exceeds credits.\\n",\n transferErrors.getIndex());\n break;\n\n default:\n System.err.printf("Error creating transfer at %d: %s\\n",\n transferErrors.getIndex(), transferErrors.getResult());\n break;\n }\n}\n')),(0,r.kt)("h2",{id:"batching"},"Batching"),(0,r.kt)("p",null,"TigerBeetle performance is maximized when you batch\nAPI requests. The client does not do this automatically for\nyou. So, for example, you ",(0,r.kt)("em",{parentName:"p"},"can")," insert 1 million transfers\none at a time like so:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-java"},"var transferIds = new long[] {100, 101, 102};\nvar debitIds = new long[] {1, 2, 3};\nvar creditIds = new long[] {4, 5, 6};\nvar amounts = new long[] {1000, 29, 11};\nfor (int i = 0; i < transferIds.length; i++) {\n TransferBatch batch = new TransferBatch(1);\n batch.add();\n batch.setId(transferIds[i]);\n batch.setDebitAccountId(debitIds[i]);\n batch.setCreditAccountId(creditIds[i]);\n batch.setAmount(amounts[i]);\n\n CreateTransferResultBatch errors = client.createTransfers(batch);\n // Error handling omitted.\n}\n")),(0,r.kt)("p",null,"But the insert rate will be a ",(0,r.kt)("em",{parentName:"p"},"fraction")," of\npotential. Instead, ",(0,r.kt)("strong",{parentName:"p"},"always batch what you can"),"."),(0,r.kt)("p",null,"The maximum batch size is set in the TigerBeetle server. The default\nis 8190."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-java"},"var BATCH_SIZE = 8190;\nfor (int i = 0; i < transferIds.length; i += BATCH_SIZE) {\n TransferBatch batch = new TransferBatch(BATCH_SIZE);\n\n for (int j = 0; j < BATCH_SIZE && i + j < transferIds.length; j++) {\n batch.add();\n batch.setId(transferIds[i + j]);\n batch.setDebitAccountId(debitIds[i + j]);\n batch.setCreditAccountId(creditIds[i + j]);\n batch.setAmount(amounts[i + j]);\n }\n\n CreateTransferResultBatch errors = client.createTransfers(batch);\n // Error handling omitted.\n}\n")),(0,r.kt)("h3",{id:"queues-and-workers"},"Queues and Workers"),(0,r.kt)("p",null,"If you are making requests to TigerBeetle from workers\npulling jobs from a queue, you can batch requests to\nTigerBeetle by having the worker act on multiple jobs from\nthe queue at once rather than one at a time. i.e. pulling\nmultiple jobs from the queue rather than just one."),(0,r.kt)("h2",{id:"transfer-flags"},"Transfer Flags"),(0,r.kt)("p",null,"The transfer ",(0,r.kt)("inlineCode",{parentName:"p"},"flags")," value is a bitfield. See details for these flags in\nthe ",(0,r.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/transfer#flags"},"Transfers\nreference"),"."),(0,r.kt)("p",null,"To toggle behavior for an account, combine enum values stored in the\n",(0,r.kt)("inlineCode",{parentName:"p"},"TransferFlags")," object with bitwise-or:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"TransferFlags.NONE")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"TransferFlags.LINKED")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"TransferFlags.PENDING")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"TransferFlags.POST_PENDING_TRANSFER")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"TransferFlags.VOID_PENDING_TRANSFER"))),(0,r.kt)("p",null,"For example, to link ",(0,r.kt)("inlineCode",{parentName:"p"},"transfer0")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"transfer1"),":"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-java"},"transfers = new TransferBatch(2);\n\n// First transfer\ntransfers.add();\n// Code to fill out fields for first transfer\ntransfers.setFlags(TransferFlags.LINKED);\n\n// Second transfer\ntransfers.add();\n// Code to fill out fields for second transfer\ntransferErrors = client.createTransfers(transfers);\n")),(0,r.kt)("h3",{id:"two-phase-transfers"},"Two-Phase Transfers"),(0,r.kt)("p",null,"Two-phase transfers are supported natively by toggling the appropriate\nflag. TigerBeetle will then adjust the ",(0,r.kt)("inlineCode",{parentName:"p"},"credits_pending")," and\n",(0,r.kt)("inlineCode",{parentName:"p"},"debits_pending")," fields of the appropriate accounts. A corresponding\npost pending transfer then needs to be sent to post or void the\ntransfer."),(0,r.kt)("h4",{id:"post-a-pending-transfer"},"Post a Pending Transfer"),(0,r.kt)("p",null,"With ",(0,r.kt)("inlineCode",{parentName:"p"},"flags")," set to ",(0,r.kt)("inlineCode",{parentName:"p"},"post_pending_transfer"),",\nTigerBeetle will post the transfer. TigerBeetle will atomically roll\nback the changes to ",(0,r.kt)("inlineCode",{parentName:"p"},"debits_pending")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"credits_pending")," of the\nappropriate accounts and apply them to the ",(0,r.kt)("inlineCode",{parentName:"p"},"debits_posted")," and\n",(0,r.kt)("inlineCode",{parentName:"p"},"credits_posted")," balances."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-java"},"transfers = new TransferBatch(1);\n\n// First transfer\ntransfers.add();\n// Code to fill out fields for first transfer\ntransfers.setFlags(TransferFlags.POST_PENDING_TRANSFER);\n// Post the entire pending amount.\ntransfers.setAmount(TransferBatch.AMOUNT_MAX);\ntransferErrors = client.createTransfers(transfers);\n")),(0,r.kt)("h4",{id:"void-a-pending-transfer"},"Void a Pending Transfer"),(0,r.kt)("p",null,"In contrast, with ",(0,r.kt)("inlineCode",{parentName:"p"},"flags")," set to ",(0,r.kt)("inlineCode",{parentName:"p"},"void_pending_transfer"),",\nTigerBeetle will void the transfer. TigerBeetle will roll\nback the changes to ",(0,r.kt)("inlineCode",{parentName:"p"},"debits_pending")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"credits_pending")," of the\nappropriate accounts and ",(0,r.kt)("strong",{parentName:"p"},"not")," apply them to the ",(0,r.kt)("inlineCode",{parentName:"p"},"debits_posted")," and\n",(0,r.kt)("inlineCode",{parentName:"p"},"credits_posted")," balances."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-java"},"transfers = new TransferBatch(1);\n\n// First transfer\ntransfers.add();\n// Code to fill out fields for first transfer\ntransfers.setFlags(TransferFlags.VOID_PENDING_TRANSFER);\ntransferErrors = client.createTransfers(transfers);\n")),(0,r.kt)("h2",{id:"transfer-lookup"},"Transfer Lookup"),(0,r.kt)("p",null,"NOTE: While transfer lookup exists, it is not a flexible query API. We\nare developing query APIs and there will be new methods for querying\ntransfers in the future."),(0,r.kt)("p",null,"Transfer lookup is batched, like transfer creation. Pass in all ",(0,r.kt)("inlineCode",{parentName:"p"},"id"),"s to\nfetch, and matched transfers are returned."),(0,r.kt)("p",null,"If no transfer matches an ",(0,r.kt)("inlineCode",{parentName:"p"},"id"),", no object is returned for that\ntransfer. So the order of transfers in the response is not necessarily\nthe same as the order of ",(0,r.kt)("inlineCode",{parentName:"p"},"id"),"s in the request. You can refer to the\n",(0,r.kt)("inlineCode",{parentName:"p"},"id")," field in the response to distinguish transfers."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-java"},"ids = new IdBatch(2);\nids.add(1);\nids.add(2);\n\ntransfers = client.lookupTransfers(ids);\n")),(0,r.kt)("h2",{id:"get-account-transfers"},"Get Account Transfers"),(0,r.kt)("p",null,"NOTE: This is a preview API that is subject to breaking changes once we have\na stable querying API."),(0,r.kt)("p",null,"Fetches the transfers involving a given account, allowing basic filter and pagination\ncapabilities."),(0,r.kt)("p",null,"The transfers in the response are sorted by ",(0,r.kt)("inlineCode",{parentName:"p"},"timestamp")," in chronological or\nreverse-chronological order."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-java"},"AccountFilter filter = new AccountFilter();\nfilter.setAccountId(2);\nfilter.setTimestampMin(0); // No filter by Timestamp.\nfilter.setTimestampMax(0); // No filter by Timestamp.\nfilter.setLimit(10); // Limit to ten transfers at most.\nfilter.setDebits(true); // Include transfer from the debit side.\nfilter.setCredits(true); // Include transfer from the credit side.\nfilter.setReversed(true); // Sort by timestamp in reverse-chronological order.\n\ntransfers = client.getAccountTransfers(filter);\n")),(0,r.kt)("h2",{id:"get-account-balances"},"Get Account Balances"),(0,r.kt)("p",null,"NOTE: This is a preview API that is subject to breaking changes once we have\na stable querying API."),(0,r.kt)("p",null,"Fetches the point-in-time balances of a given account, allowing basic filter and\npagination capabilities."),(0,r.kt)("p",null,"Only accounts created with the flag\n",(0,r.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/account#flagshistory"},(0,r.kt)("inlineCode",{parentName:"a"},"history"))," set retain\n",(0,r.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/requests/get_account_balances"},"historical balances"),"."),(0,r.kt)("p",null,"The balances in the response are sorted by ",(0,r.kt)("inlineCode",{parentName:"p"},"timestamp")," in chronological or\nreverse-chronological order."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-java"},"filter = new AccountFilter();\nfilter.setAccountId(2);\nfilter.setTimestampMin(0); // No filter by Timestamp.\nfilter.setTimestampMax(0); // No filter by Timestamp.\nfilter.setLimit(10); // Limit to ten balances at most.\nfilter.setDebits(true); // Include transfer from the debit side.\nfilter.setCredits(true); // Include transfer from the credit side.\nfilter.setReversed(true); // Sort by timestamp in reverse-chronological order.\n\nAccountBalanceBatch account_balances = client.getAccountBalances(filter);\n")),(0,r.kt)("h2",{id:"query-accounts"},"Query Accounts"),(0,r.kt)("p",null,"NOTE: This is a preview API that is subject to breaking changes once we have\na stable querying API."),(0,r.kt)("p",null,"Query accounts by the intersection of some fields and by timestamp range."),(0,r.kt)("p",null,"The accounts in the response are sorted by ",(0,r.kt)("inlineCode",{parentName:"p"},"timestamp")," in chronological or\nreverse-chronological order."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-java"},"var query_filter = new QueryFilter();\nquery_filter.setUserData128(1000); // Filter by UserData.\nquery_filter.setUserData64(100);\nquery_filter.setUserData32(10);\nquery_filter.setCode(1); // Filter by Code.\nquery_filter.setLedger(0); // No filter by Ledger.\nquery_filter.setTimestampMin(0); // No filter by Timestamp.\nquery_filter.setTimestampMax(0); // No filter by Timestamp.\nquery_filter.setLimit(10); // Limit to ten balances at most.\nquery_filter.setReversed(true); // Sort by timestamp in reverse-chronological order.\n\nAccountBatch query_accounts = client.queryAccounts(query_filter);\n")),(0,r.kt)("h2",{id:"query-transfers"},"Query Transfers"),(0,r.kt)("p",null,"NOTE: This is a preview API that is subject to breaking changes once we have\na stable querying API."),(0,r.kt)("p",null,"Query transfers by the intersection of some fields and by timestamp range."),(0,r.kt)("p",null,"The transfers in the response are sorted by ",(0,r.kt)("inlineCode",{parentName:"p"},"timestamp")," in chronological or\nreverse-chronological order."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-java"},"query_filter = new QueryFilter();\nquery_filter.setUserData128(1000); // Filter by UserData.\nquery_filter.setUserData64(100);\nquery_filter.setUserData32(10);\nquery_filter.setCode(1); // Filter by Code.\nquery_filter.setLedger(0); // No filter by Ledger.\nquery_filter.setTimestampMin(0); // No filter by Timestamp.\nquery_filter.setTimestampMax(0); // No filter by Timestamp.\nquery_filter.setLimit(10); // Limit to ten balances at most.\nquery_filter.setReversed(true); // Sort by timestamp in reverse-chronological order.\n\nTransferBatch query_transfers = client.queryTransfers(query_filter);\n")),(0,r.kt)("h2",{id:"linked-events"},"Linked Events"),(0,r.kt)("p",null,"When the ",(0,r.kt)("inlineCode",{parentName:"p"},"linked")," flag is specified for an account when creating accounts or\na transfer when creating transfers, it links that event with the next event in the\nbatch, to create a chain of events, of arbitrary length, which all\nsucceed or fail together. The tail of a chain is denoted by the first\nevent without this flag. The last event in a batch may therefore never\nhave the ",(0,r.kt)("inlineCode",{parentName:"p"},"linked")," flag set as this would leave a chain\nopen-ended. Multiple chains or individual events may coexist within a\nbatch to succeed or fail independently."),(0,r.kt)("p",null,"Events within a chain are executed within order, or are rolled back on\nerror, so that the effect of each event in the chain is visible to the\nnext, and so that the chain is either visible or invisible as a unit\nto subsequent events after the chain. The event that was the first to\nbreak the chain will have a unique error result. Other events in the\nchain will have their error result set to ",(0,r.kt)("inlineCode",{parentName:"p"},"linked_event_failed"),"."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-java"},"transfers = new TransferBatch(10);\n\n// An individual transfer (successful):\ntransfers.add();\ntransfers.setId(1);\n\n// A chain of 4 transfers (the last transfer in the chain closes the chain with\n// linked=false):\ntransfers.add();\ntransfers.setId(2); // Commit/rollback.\ntransfers.setFlags(TransferFlags.LINKED);\n\ntransfers.add();\ntransfers.setId(3); // Commit/rollback.\ntransfers.setFlags(TransferFlags.LINKED);\n\ntransfers.add();\ntransfers.setId(2); // Fail with exists\ntransfers.setFlags(TransferFlags.LINKED);\n\ntransfers.add();\ntransfers.setId(4); // Fail without committing\n\n// An individual transfer (successful):\n// This should not see any effect from the failed chain above.\ntransfers.add();\ntransfers.setId(2);\n\n// A chain of 2 transfers (the first transfer fails the chain):\ntransfers.add();\ntransfers.setId(2);\ntransfers.setFlags(TransferFlags.LINKED);\n\ntransfers.add();\ntransfers.setId(3);\n\n// A chain of 2 transfers (successful):\ntransfers.add();\ntransfers.setId(3);\ntransfers.setFlags(TransferFlags.LINKED);\n\ntransfers.add();\ntransfers.setId(4);\n\ntransferErrors = client.createTransfers(transfers);\n")),(0,r.kt)("h2",{id:"imported-events"},"Imported Events"),(0,r.kt)("p",null,"When the ",(0,r.kt)("inlineCode",{parentName:"p"},"imported")," flag is specified for an account when creating accounts or\na transfer when creating transfers, it allows importing historical events with\na user-defined timestamp."),(0,r.kt)("p",null,"The entire batch of events must be set with the flag ",(0,r.kt)("inlineCode",{parentName:"p"},"imported"),"."),(0,r.kt)("p",null,"It's recommended to submit the whole batch as a ",(0,r.kt)("inlineCode",{parentName:"p"},"linked")," chain of events, ensuring that\nif any event fails, none of them are committed, preserving the last timestamp unchanged.\nThis approach gives the application a chance to correct failed imported events, re-submitting\nthe batch again with the same user-defined timestamps."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-java"},"// First, load and import all accounts with their timestamps from the historical source.\naccounts = new AccountBatch(historicalAccounts.length);\nfor(int index = 0; index < historicalAccounts.length; index += 1) {\n accounts.add();\n\n // Set a unique and strictly increasing timestamp.\n historicalTimestamp += 1;\n accounts.setTimestamp(historicalTimestamp);\n // Set the account as `imported`.\n // To ensure atomicity, the entire batch (except the last event in the chain)\n // must be `linked`.\n if (index < historicalAccounts.length - 1) {\n accounts.setFlags(AccountFlags.IMPORTED | AccountFlags.LINKED);\n } else {\n accounts.setFlags(AccountFlags.IMPORTED);\n }\n\n // Populate the rest of the account:\n // accounts.setId(historicalAccounts[index].Id);\n // accounts.setCode(historicalAccounts[index].Code);\n}\naccountErrors = client.createAccounts(accounts);\n// Error handling omitted.\n\n// Then, load and import all transfers with their timestamps from the historical source.\ntransfers = new TransferBatch(historicalTransfers.length);\nfor(int index = 0; index < historicalTransfers.length; index += 1) {\n transfers.add();\n\n // Set a unique and strictly increasing timestamp.\n historicalTimestamp += 1;\n transfers.setTimestamp(historicalTimestamp);\n // Set the account as `imported`.\n // To ensure atomicity, the entire batch (except the last event in the chain)\n // must be `linked`.\n if (index < historicalTransfers.length - 1) {\n transfers.setFlags(TransferFlags.IMPORTED | TransferFlags.LINKED);\n } else {\n transfers.setFlags(TransferFlags.IMPORTED);\n }\n\n // Populate the rest of the transfer:\n // transfers.setId(historicalTransfers[index].Id);\n // transfers.setCode(historicalTransfers[index].Code);\n}\ntransferErrors = client.createTransfers(transfers);\n// Error handling omitted.\n// Since it is a linked chain, in case of any error the entire batch is rolled back and can be retried\n// with the same historical timestamps without regressing the cluster timestamp.\n")))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[7736],{3905:(e,t,n)=>{n.d(t,{Zo:()=>d,kt:()=>h});var a=n(7294);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function s(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function i(e){for(var t=1;t=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var s=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var o=a.createContext({}),c=function(e){var t=a.useContext(o),n=t;return e&&(n="function"==typeof e?e(t):i(i({},t),e)),n},d=function(e){var t=c(e.components);return a.createElement(o.Provider,{value:t},e.children)},p="mdxType",u={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},f=a.forwardRef((function(e,t){var n=e.components,r=e.mdxType,s=e.originalType,o=e.parentName,d=l(e,["components","mdxType","originalType","parentName"]),p=c(n),f=r,h=p["".concat(o,".").concat(f)]||p[f]||u[f]||s;return n?a.createElement(h,i(i({ref:t},d),{},{components:n})):a.createElement(h,i({ref:t},d))}));function h(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var s=n.length,i=new Array(s);i[0]=f;var l={};for(var o in t)hasOwnProperty.call(t,o)&&(l[o]=t[o]);l.originalType=e,l[p]="string"==typeof e?e:r,i[1]=l;for(var c=2;c{n.r(t),n.d(t,{assets:()=>o,contentTitle:()=>i,default:()=>u,frontMatter:()=>s,metadata:()=>l,toc:()=>c});var a=n(7462),r=(n(7294),n(3905));const s={title:"Java"},i=void 0,l={unversionedId:"clients/java",id:"clients/java",title:"Java",description:"The TigerBeetle client for Java.",source:"@site/pages/clients/java.md",sourceDirName:"clients",slug:"/clients/java",permalink:"/clients/java",draft:!1,editUrl:"https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/clients/java.md",tags:[],version:"current",frontMatter:{title:"Java"},sidebar:"tutorialSidebar",previous:{title:"Go",permalink:"/clients/go"},next:{title:"Node.js",permalink:"/clients/node"}},o={},c=[{value:"Prerequisites",id:"prerequisites",level:2},{value:"Setup",id:"setup",level:2},{value:"Sample projects",id:"sample-projects",level:2},{value:"Creating a Client",id:"creating-a-client",level:2},{value:"Creating Accounts",id:"creating-accounts",level:2},{value:"Account Flags",id:"account-flags",level:3},{value:"Response and Errors",id:"response-and-errors",level:3},{value:"Account Lookup",id:"account-lookup",level:2},{value:"Create Transfers",id:"create-transfers",level:2},{value:"Response and Errors",id:"response-and-errors-1",level:3},{value:"Batching",id:"batching",level:2},{value:"Queues and Workers",id:"queues-and-workers",level:3},{value:"Transfer Flags",id:"transfer-flags",level:2},{value:"Two-Phase Transfers",id:"two-phase-transfers",level:3},{value:"Post a Pending Transfer",id:"post-a-pending-transfer",level:4},{value:"Void a Pending Transfer",id:"void-a-pending-transfer",level:4},{value:"Transfer Lookup",id:"transfer-lookup",level:2},{value:"Get Account Transfers",id:"get-account-transfers",level:2},{value:"Get Account Balances",id:"get-account-balances",level:2},{value:"Query Accounts",id:"query-accounts",level:2},{value:"Query Transfers",id:"query-transfers",level:2},{value:"Linked Events",id:"linked-events",level:2},{value:"Imported Events",id:"imported-events",level:2}],d={toc:c},p="wrapper";function u(e){let{components:t,...n}=e;return(0,r.kt)(p,(0,a.Z)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("h1",{id:"tigerbeetle-java"},"tigerbeetle-java"),(0,r.kt)("p",null,"The TigerBeetle client for Java."),(0,r.kt)("p",null,(0,r.kt)("a",{parentName:"p",href:"https://javadoc.io/doc/com.tigerbeetle/tigerbeetle-java"},(0,r.kt)("img",{parentName:"a",src:"https://javadoc.io/badge2/com.tigerbeetle/tigerbeetle-java/javadoc.svg",alt:"javadoc"}))),(0,r.kt)("p",null,(0,r.kt)("a",{parentName:"p",href:"https://central.sonatype.com/namespace/com.tigerbeetle"},(0,r.kt)("img",{parentName:"a",src:"https://img.shields.io/maven-central/v/com.tigerbeetle/tigerbeetle-java",alt:"maven-central"}))),(0,r.kt)("h2",{id:"prerequisites"},"Prerequisites"),(0,r.kt)("p",null,"Linux >= 5.6 is the only production environment we\nsupport. But for ease of development we also support macOS and Windows."),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"Java >= 11"),(0,r.kt)("li",{parentName:"ul"},"Maven >= 3.6 (not strictly necessary but it's what our guides assume)")),(0,r.kt)("h2",{id:"setup"},"Setup"),(0,r.kt)("p",null,"First, create a directory for your project and ",(0,r.kt)("inlineCode",{parentName:"p"},"cd")," into the directory."),(0,r.kt)("p",null,"Then create ",(0,r.kt)("inlineCode",{parentName:"p"},"pom.xml")," and copy this into it:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-xml"},'\n 4.0.0\n\n com.tigerbeetle\n samples\n 1.0-SNAPSHOT\n\n \n 11\n 11\n \n\n \n \n \n org.apache.maven.plugins\n maven-compiler-plugin\n 3.8.1\n \n \n -Xlint:all,-options,-path\n \n \n \n\n \n org.codehaus.mojo\n exec-maven-plugin\n 1.6.0\n \n com.tigerbeetle.samples.Main\n \n \n \n \n\n \n \n com.tigerbeetle\n tigerbeetle-java\n \x3c!-- Grab the latest commit from: https://repo1.maven.org/maven2/com/tigerbeetle/tigerbeetle-java/maven-metadata.xml --\x3e\n 0.0.1-3431\n \n \n\n')),(0,r.kt)("p",null,"Then, install the TigerBeetle client:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},"mvn install\n")),(0,r.kt)("p",null,"Now, create ",(0,r.kt)("inlineCode",{parentName:"p"},"src/main/java/Main.java")," and copy this into it:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-java"},'package com.tigerbeetle.samples;\n\nimport com.tigerbeetle.*;\n\npublic final class Main {\n public static void main(String[] args) throws Exception {\n System.out.println("Import ok!");\n }\n}\n')),(0,r.kt)("p",null,"Finally, build and run:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},"mvn exec:java\n")),(0,r.kt)("p",null,"Now that all prerequisites and dependencies are correctly set\nup, let's dig into using TigerBeetle."),(0,r.kt)("h2",{id:"sample-projects"},"Sample projects"),(0,r.kt)("p",null,"This document is primarily a reference guide to\nthe client. Below are various sample projects demonstrating\nfeatures of TigerBeetle."),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"https://github.com/tigerbeetle/tigerbeetle/blob/main/src/clients/java/samples/basic/"},"Basic"),": Create two accounts and transfer an amount between them."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"https://github.com/tigerbeetle/tigerbeetle/blob/main/src/clients/java/samples/two-phase/"},"Two-Phase Transfer"),": Create two accounts and start a pending transfer between\nthem, then post the transfer."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"https://github.com/tigerbeetle/tigerbeetle/blob/main/src/clients/java/samples/two-phase-many/"},"Many Two-Phase Transfers"),": Create two accounts and start a number of pending transfer\nbetween them, posting and voiding alternating transfers.")),(0,r.kt)("h2",{id:"creating-a-client"},"Creating a Client"),(0,r.kt)("p",null,"A client is created with a cluster ID and replica\naddresses for all replicas in the cluster. The cluster\nID and replica addresses are both chosen by the system that\nstarts the TigerBeetle cluster."),(0,r.kt)("p",null,"Clients are thread-safe and a single instance should be shared\nbetween multiple concurrent tasks."),(0,r.kt)("p",null,"Multiple clients are useful when connecting to more than\none TigerBeetle cluster."),(0,r.kt)("p",null,"In this example the cluster ID is ",(0,r.kt)("inlineCode",{parentName:"p"},"0")," and there is one\nreplica. The address is read from the ",(0,r.kt)("inlineCode",{parentName:"p"},"TB_ADDRESS"),"\nenvironment variable and defaults to port ",(0,r.kt)("inlineCode",{parentName:"p"},"3000"),"."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-java"},'String replicaAddress = System.getenv("TB_ADDRESS");\nbyte[] clusterID = UInt128.asBytes(0);\nString[] replicaAddresses = new String[] {replicaAddress == null ? "3000" : replicaAddress};\ntry (var client = new Client(clusterID, replicaAddresses)) {\n // Use client\n}\n')),(0,r.kt)("p",null,"The following are valid addresses:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"3000")," (interpreted as ",(0,r.kt)("inlineCode",{parentName:"li"},"127.0.0.1:3000"),")"),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"127.0.0.1:3000")," (interpreted as ",(0,r.kt)("inlineCode",{parentName:"li"},"127.0.0.1:3000"),")"),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"127.0.0.1")," (interpreted as ",(0,r.kt)("inlineCode",{parentName:"li"},"127.0.0.1:3001"),", ",(0,r.kt)("inlineCode",{parentName:"li"},"3001")," is the default port)")),(0,r.kt)("h2",{id:"creating-accounts"},"Creating Accounts"),(0,r.kt)("p",null,"See details for account fields in the ",(0,r.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/account"},"Accounts\nreference"),"."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-java"},"AccountBatch accounts = new AccountBatch(1);\naccounts.add();\naccounts.setId(137);\naccounts.setUserData128(UInt128.asBytes(java.util.UUID.randomUUID()));\naccounts.setUserData64(1234567890);\naccounts.setUserData32(42);\naccounts.setLedger(1);\naccounts.setCode(718);\naccounts.setFlags(0);\n\nCreateAccountResultBatch accountErrors = client.createAccounts(accounts);\n")),(0,r.kt)("p",null,"The 128-bit fields like ",(0,r.kt)("inlineCode",{parentName:"p"},"id")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"user_data_128")," have a few\noverrides to make it easier to integrate. You can either\npass in a long, a pair of longs (least and most\nsignificant bits), or a ",(0,r.kt)("inlineCode",{parentName:"p"},"byte[]"),"."),(0,r.kt)("p",null,"There is also a ",(0,r.kt)("inlineCode",{parentName:"p"},"com.tigerbeetle.UInt128")," helper with static\nmethods for converting 128-bit little-endian unsigned integers\nbetween instances of ",(0,r.kt)("inlineCode",{parentName:"p"},"long"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"java.util.UUID"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"java.math.BigInteger")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"byte[]"),"."),(0,r.kt)("p",null,"The fields for transfer amounts and account balances are also 128-bit,\nbut they are always represented as a ",(0,r.kt)("inlineCode",{parentName:"p"},"java.math.BigInteger"),"."),(0,r.kt)("h3",{id:"account-flags"},"Account Flags"),(0,r.kt)("p",null,"The account flags value is a bitfield. See details for\nthese flags in the ",(0,r.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/account#flags"},"Accounts\nreference"),"."),(0,r.kt)("p",null,"To toggle behavior for an account, combine enum values stored in the\n",(0,r.kt)("inlineCode",{parentName:"p"},"AccountFlags")," object with bitwise-or:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"AccountFlags.LINKED")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"AccountFlags.DEBITS_MUST_NOT_EXCEED_CREDITS")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"AccountFlags.CREDITS_MUST_NOT_EXCEED_CREDITS")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"AccountFlags.HISTORY"))),(0,r.kt)("p",null,"For example, to link two accounts where the first account\nadditionally has the ",(0,r.kt)("inlineCode",{parentName:"p"},"debits_must_not_exceed_credits")," constraint:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-java"},"accounts = new AccountBatch(3);\n\n// First account\naccounts.add();\n// Code to fill out fields for first account\naccounts.setFlags(AccountFlags.LINKED | AccountFlags.DEBITS_MUST_NOT_EXCEED_CREDITS);\n\n// Second account\naccounts.add();\n// Code to fill out fields for second account\n\naccountErrors = client.createAccounts(accounts);\n")),(0,r.kt)("h3",{id:"response-and-errors"},"Response and Errors"),(0,r.kt)("p",null,"The response is an empty array if all accounts were\ncreated successfully. If the response is non-empty, each\nobject in the response array contains error information\nfor an account that failed. The error object contains an\nerror code and the index of the account in the request\nbatch."),(0,r.kt)("p",null,"See all error conditions in the ",(0,r.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/requests/create_accounts"},"create_accounts\nreference"),"."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-java"},'while (accountErrors.next()) {\n switch (accountErrors.getResult()) {\n case Exists:\n System.err.printf("Account at %d already exists.\\n",\n accountErrors.getIndex());\n break;\n\n default:\n System.err.printf("Error creating account at %d: %s\\n",\n accountErrors.getIndex(), accountErrors.getResult());\n break;\n }\n}\n')),(0,r.kt)("h2",{id:"account-lookup"},"Account Lookup"),(0,r.kt)("p",null,"Account lookup is batched, like account creation. Pass\nin all IDs to fetch. The account for each matched ID is returned."),(0,r.kt)("p",null,"If no account matches an ID, no object is returned for\nthat account. So the order of accounts in the response is\nnot necessarily the same as the order of IDs in the\nrequest. You can refer to the ID field in the response to\ndistinguish accounts."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-java"},"IdBatch ids = new IdBatch(2);\nids.add(137);\nids.add(138);\naccounts = client.lookupAccounts(ids);\n")),(0,r.kt)("h2",{id:"create-transfers"},"Create Transfers"),(0,r.kt)("p",null,"This creates a journal entry between two accounts."),(0,r.kt)("p",null,"See details for transfer fields in the ",(0,r.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/transfer"},"Transfers\nreference"),"."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-java"},"TransferBatch transfers = new TransferBatch(1);\ntransfers.add();\ntransfers.setId(1);\ntransfers.setDebitAccountId(1);\ntransfers.setCreditAccountId(2);\ntransfers.setAmount(10);\ntransfers.setUserData128(UInt128.asBytes(java.util.UUID.randomUUID()));\ntransfers.setUserData64(1234567890);\ntransfers.setUserData32(42);\ntransfers.setTimeout(0);\ntransfers.setLedger(1);\ntransfers.setCode(1);\ntransfers.setFlags(0);\n\nCreateTransferResultBatch transferErrors = client.createTransfers(transfers);\n")),(0,r.kt)("h3",{id:"response-and-errors-1"},"Response and Errors"),(0,r.kt)("p",null,"The response is an empty array if all transfers were created\nsuccessfully. If the response is non-empty, each object in the\nresponse array contains error information for a transfer that\nfailed. The error object contains an error code and the index of the\ntransfer in the request batch."),(0,r.kt)("p",null,"See all error conditions in the ",(0,r.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/requests/create_transfers"},"create_transfers\nreference"),"."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-java"},'while (transferErrors.next()) {\n switch (transferErrors.getResult()) {\n case ExceedsCredits:\n System.err.printf("Transfer at %d exceeds credits.\\n",\n transferErrors.getIndex());\n break;\n\n default:\n System.err.printf("Error creating transfer at %d: %s\\n",\n transferErrors.getIndex(), transferErrors.getResult());\n break;\n }\n}\n')),(0,r.kt)("h2",{id:"batching"},"Batching"),(0,r.kt)("p",null,"TigerBeetle performance is maximized when you batch\nAPI requests. The client does not do this automatically for\nyou. So, for example, you ",(0,r.kt)("em",{parentName:"p"},"can")," insert 1 million transfers\none at a time like so:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-java"},"var transferIds = new long[] {100, 101, 102};\nvar debitIds = new long[] {1, 2, 3};\nvar creditIds = new long[] {4, 5, 6};\nvar amounts = new long[] {1000, 29, 11};\nfor (int i = 0; i < transferIds.length; i++) {\n TransferBatch batch = new TransferBatch(1);\n batch.add();\n batch.setId(transferIds[i]);\n batch.setDebitAccountId(debitIds[i]);\n batch.setCreditAccountId(creditIds[i]);\n batch.setAmount(amounts[i]);\n\n CreateTransferResultBatch errors = client.createTransfers(batch);\n // Error handling omitted.\n}\n")),(0,r.kt)("p",null,"But the insert rate will be a ",(0,r.kt)("em",{parentName:"p"},"fraction")," of\npotential. Instead, ",(0,r.kt)("strong",{parentName:"p"},"always batch what you can"),"."),(0,r.kt)("p",null,"The maximum batch size is set in the TigerBeetle server. The default\nis 8190."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-java"},"var BATCH_SIZE = 8190;\nfor (int i = 0; i < transferIds.length; i += BATCH_SIZE) {\n TransferBatch batch = new TransferBatch(BATCH_SIZE);\n\n for (int j = 0; j < BATCH_SIZE && i + j < transferIds.length; j++) {\n batch.add();\n batch.setId(transferIds[i + j]);\n batch.setDebitAccountId(debitIds[i + j]);\n batch.setCreditAccountId(creditIds[i + j]);\n batch.setAmount(amounts[i + j]);\n }\n\n CreateTransferResultBatch errors = client.createTransfers(batch);\n // Error handling omitted.\n}\n")),(0,r.kt)("h3",{id:"queues-and-workers"},"Queues and Workers"),(0,r.kt)("p",null,"If you are making requests to TigerBeetle from workers\npulling jobs from a queue, you can batch requests to\nTigerBeetle by having the worker act on multiple jobs from\nthe queue at once rather than one at a time. i.e. pulling\nmultiple jobs from the queue rather than just one."),(0,r.kt)("h2",{id:"transfer-flags"},"Transfer Flags"),(0,r.kt)("p",null,"The transfer ",(0,r.kt)("inlineCode",{parentName:"p"},"flags")," value is a bitfield. See details for these flags in\nthe ",(0,r.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/transfer#flags"},"Transfers\nreference"),"."),(0,r.kt)("p",null,"To toggle behavior for an account, combine enum values stored in the\n",(0,r.kt)("inlineCode",{parentName:"p"},"TransferFlags")," object with bitwise-or:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"TransferFlags.NONE")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"TransferFlags.LINKED")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"TransferFlags.PENDING")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"TransferFlags.POST_PENDING_TRANSFER")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"TransferFlags.VOID_PENDING_TRANSFER"))),(0,r.kt)("p",null,"For example, to link ",(0,r.kt)("inlineCode",{parentName:"p"},"transfer0")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"transfer1"),":"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-java"},"transfers = new TransferBatch(2);\n\n// First transfer\ntransfers.add();\n// Code to fill out fields for first transfer\ntransfers.setFlags(TransferFlags.LINKED);\n\n// Second transfer\ntransfers.add();\n// Code to fill out fields for second transfer\ntransferErrors = client.createTransfers(transfers);\n")),(0,r.kt)("h3",{id:"two-phase-transfers"},"Two-Phase Transfers"),(0,r.kt)("p",null,"Two-phase transfers are supported natively by toggling the appropriate\nflag. TigerBeetle will then adjust the ",(0,r.kt)("inlineCode",{parentName:"p"},"credits_pending")," and\n",(0,r.kt)("inlineCode",{parentName:"p"},"debits_pending")," fields of the appropriate accounts. A corresponding\npost pending transfer then needs to be sent to post or void the\ntransfer."),(0,r.kt)("h4",{id:"post-a-pending-transfer"},"Post a Pending Transfer"),(0,r.kt)("p",null,"With ",(0,r.kt)("inlineCode",{parentName:"p"},"flags")," set to ",(0,r.kt)("inlineCode",{parentName:"p"},"post_pending_transfer"),",\nTigerBeetle will post the transfer. TigerBeetle will atomically roll\nback the changes to ",(0,r.kt)("inlineCode",{parentName:"p"},"debits_pending")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"credits_pending")," of the\nappropriate accounts and apply them to the ",(0,r.kt)("inlineCode",{parentName:"p"},"debits_posted")," and\n",(0,r.kt)("inlineCode",{parentName:"p"},"credits_posted")," balances."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-java"},"transfers = new TransferBatch(1);\n\n// First transfer\ntransfers.add();\n// Code to fill out fields for first transfer\ntransfers.setFlags(TransferFlags.POST_PENDING_TRANSFER);\n// Post the entire pending amount.\ntransfers.setAmount(TransferBatch.AMOUNT_MAX);\ntransferErrors = client.createTransfers(transfers);\n")),(0,r.kt)("h4",{id:"void-a-pending-transfer"},"Void a Pending Transfer"),(0,r.kt)("p",null,"In contrast, with ",(0,r.kt)("inlineCode",{parentName:"p"},"flags")," set to ",(0,r.kt)("inlineCode",{parentName:"p"},"void_pending_transfer"),",\nTigerBeetle will void the transfer. TigerBeetle will roll\nback the changes to ",(0,r.kt)("inlineCode",{parentName:"p"},"debits_pending")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"credits_pending")," of the\nappropriate accounts and ",(0,r.kt)("strong",{parentName:"p"},"not")," apply them to the ",(0,r.kt)("inlineCode",{parentName:"p"},"debits_posted")," and\n",(0,r.kt)("inlineCode",{parentName:"p"},"credits_posted")," balances."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-java"},"transfers = new TransferBatch(1);\n\n// First transfer\ntransfers.add();\n// Code to fill out fields for first transfer\ntransfers.setFlags(TransferFlags.VOID_PENDING_TRANSFER);\ntransferErrors = client.createTransfers(transfers);\n")),(0,r.kt)("h2",{id:"transfer-lookup"},"Transfer Lookup"),(0,r.kt)("p",null,"NOTE: While transfer lookup exists, it is not a flexible query API. We\nare developing query APIs and there will be new methods for querying\ntransfers in the future."),(0,r.kt)("p",null,"Transfer lookup is batched, like transfer creation. Pass in all ",(0,r.kt)("inlineCode",{parentName:"p"},"id"),"s to\nfetch, and matched transfers are returned."),(0,r.kt)("p",null,"If no transfer matches an ",(0,r.kt)("inlineCode",{parentName:"p"},"id"),", no object is returned for that\ntransfer. So the order of transfers in the response is not necessarily\nthe same as the order of ",(0,r.kt)("inlineCode",{parentName:"p"},"id"),"s in the request. You can refer to the\n",(0,r.kt)("inlineCode",{parentName:"p"},"id")," field in the response to distinguish transfers."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-java"},"ids = new IdBatch(2);\nids.add(1);\nids.add(2);\n\ntransfers = client.lookupTransfers(ids);\n")),(0,r.kt)("h2",{id:"get-account-transfers"},"Get Account Transfers"),(0,r.kt)("p",null,"NOTE: This is a preview API that is subject to breaking changes once we have\na stable querying API."),(0,r.kt)("p",null,"Fetches the transfers involving a given account, allowing basic filter and pagination\ncapabilities."),(0,r.kt)("p",null,"The transfers in the response are sorted by ",(0,r.kt)("inlineCode",{parentName:"p"},"timestamp")," in chronological or\nreverse-chronological order."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-java"},"AccountFilter filter = new AccountFilter();\nfilter.setAccountId(2);\nfilter.setUserData128(0); // No filter by UserData.\nfilter.setUserData64(0);\nfilter.setUserData32(0);\nfilter.setCode(0); // No filter by Code.\nfilter.setTimestampMin(0); // No filter by Timestamp.\nfilter.setTimestampMax(0); // No filter by Timestamp.\nfilter.setLimit(10); // Limit to ten transfers at most.\nfilter.setDebits(true); // Include transfer from the debit side.\nfilter.setCredits(true); // Include transfer from the credit side.\nfilter.setReversed(true); // Sort by timestamp in reverse-chronological order.\n\ntransfers = client.getAccountTransfers(filter);\n")),(0,r.kt)("h2",{id:"get-account-balances"},"Get Account Balances"),(0,r.kt)("p",null,"NOTE: This is a preview API that is subject to breaking changes once we have\na stable querying API."),(0,r.kt)("p",null,"Fetches the point-in-time balances of a given account, allowing basic filter and\npagination capabilities."),(0,r.kt)("p",null,"Only accounts created with the flag\n",(0,r.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/account#flagshistory"},(0,r.kt)("inlineCode",{parentName:"a"},"history"))," set retain\n",(0,r.kt)("a",{parentName:"p",href:"https://docs.tigerbeetle.com/reference/requests/get_account_balances"},"historical balances"),"."),(0,r.kt)("p",null,"The balances in the response are sorted by ",(0,r.kt)("inlineCode",{parentName:"p"},"timestamp")," in chronological or\nreverse-chronological order."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-java"},"filter = new AccountFilter();\nfilter.setAccountId(2);\nfilter.setUserData128(0); // No filter by UserData.\nfilter.setUserData64(0);\nfilter.setUserData32(0);\nfilter.setCode(0); // No filter by Code.\nfilter.setTimestampMin(0); // No filter by Timestamp.\nfilter.setTimestampMax(0); // No filter by Timestamp.\nfilter.setLimit(10); // Limit to ten balances at most.\nfilter.setDebits(true); // Include transfer from the debit side.\nfilter.setCredits(true); // Include transfer from the credit side.\nfilter.setReversed(true); // Sort by timestamp in reverse-chronological order.\n\nAccountBalanceBatch account_balances = client.getAccountBalances(filter);\n")),(0,r.kt)("h2",{id:"query-accounts"},"Query Accounts"),(0,r.kt)("p",null,"NOTE: This is a preview API that is subject to breaking changes once we have\na stable querying API."),(0,r.kt)("p",null,"Query accounts by the intersection of some fields and by timestamp range."),(0,r.kt)("p",null,"The accounts in the response are sorted by ",(0,r.kt)("inlineCode",{parentName:"p"},"timestamp")," in chronological or\nreverse-chronological order."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-java"},"var query_filter = new QueryFilter();\nquery_filter.setUserData128(1000); // Filter by UserData.\nquery_filter.setUserData64(100);\nquery_filter.setUserData32(10);\nquery_filter.setCode(1); // Filter by Code.\nquery_filter.setLedger(0); // No filter by Ledger.\nquery_filter.setTimestampMin(0); // No filter by Timestamp.\nquery_filter.setTimestampMax(0); // No filter by Timestamp.\nquery_filter.setLimit(10); // Limit to ten balances at most.\nquery_filter.setReversed(true); // Sort by timestamp in reverse-chronological order.\n\nAccountBatch query_accounts = client.queryAccounts(query_filter);\n")),(0,r.kt)("h2",{id:"query-transfers"},"Query Transfers"),(0,r.kt)("p",null,"NOTE: This is a preview API that is subject to breaking changes once we have\na stable querying API."),(0,r.kt)("p",null,"Query transfers by the intersection of some fields and by timestamp range."),(0,r.kt)("p",null,"The transfers in the response are sorted by ",(0,r.kt)("inlineCode",{parentName:"p"},"timestamp")," in chronological or\nreverse-chronological order."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-java"},"query_filter = new QueryFilter();\nquery_filter.setUserData128(1000); // Filter by UserData.\nquery_filter.setUserData64(100);\nquery_filter.setUserData32(10);\nquery_filter.setCode(1); // Filter by Code.\nquery_filter.setLedger(0); // No filter by Ledger.\nquery_filter.setTimestampMin(0); // No filter by Timestamp.\nquery_filter.setTimestampMax(0); // No filter by Timestamp.\nquery_filter.setLimit(10); // Limit to ten balances at most.\nquery_filter.setReversed(true); // Sort by timestamp in reverse-chronological order.\n\nTransferBatch query_transfers = client.queryTransfers(query_filter);\n")),(0,r.kt)("h2",{id:"linked-events"},"Linked Events"),(0,r.kt)("p",null,"When the ",(0,r.kt)("inlineCode",{parentName:"p"},"linked")," flag is specified for an account when creating accounts or\na transfer when creating transfers, it links that event with the next event in the\nbatch, to create a chain of events, of arbitrary length, which all\nsucceed or fail together. The tail of a chain is denoted by the first\nevent without this flag. The last event in a batch may therefore never\nhave the ",(0,r.kt)("inlineCode",{parentName:"p"},"linked")," flag set as this would leave a chain\nopen-ended. Multiple chains or individual events may coexist within a\nbatch to succeed or fail independently."),(0,r.kt)("p",null,"Events within a chain are executed within order, or are rolled back on\nerror, so that the effect of each event in the chain is visible to the\nnext, and so that the chain is either visible or invisible as a unit\nto subsequent events after the chain. The event that was the first to\nbreak the chain will have a unique error result. Other events in the\nchain will have their error result set to ",(0,r.kt)("inlineCode",{parentName:"p"},"linked_event_failed"),"."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-java"},"transfers = new TransferBatch(10);\n\n// An individual transfer (successful):\ntransfers.add();\ntransfers.setId(1);\n\n// A chain of 4 transfers (the last transfer in the chain closes the chain with\n// linked=false):\ntransfers.add();\ntransfers.setId(2); // Commit/rollback.\ntransfers.setFlags(TransferFlags.LINKED);\n\ntransfers.add();\ntransfers.setId(3); // Commit/rollback.\ntransfers.setFlags(TransferFlags.LINKED);\n\ntransfers.add();\ntransfers.setId(2); // Fail with exists\ntransfers.setFlags(TransferFlags.LINKED);\n\ntransfers.add();\ntransfers.setId(4); // Fail without committing\n\n// An individual transfer (successful):\n// This should not see any effect from the failed chain above.\ntransfers.add();\ntransfers.setId(2);\n\n// A chain of 2 transfers (the first transfer fails the chain):\ntransfers.add();\ntransfers.setId(2);\ntransfers.setFlags(TransferFlags.LINKED);\n\ntransfers.add();\ntransfers.setId(3);\n\n// A chain of 2 transfers (successful):\ntransfers.add();\ntransfers.setId(3);\ntransfers.setFlags(TransferFlags.LINKED);\n\ntransfers.add();\ntransfers.setId(4);\n\ntransferErrors = client.createTransfers(transfers);\n")),(0,r.kt)("h2",{id:"imported-events"},"Imported Events"),(0,r.kt)("p",null,"When the ",(0,r.kt)("inlineCode",{parentName:"p"},"imported")," flag is specified for an account when creating accounts or\na transfer when creating transfers, it allows importing historical events with\na user-defined timestamp."),(0,r.kt)("p",null,"The entire batch of events must be set with the flag ",(0,r.kt)("inlineCode",{parentName:"p"},"imported"),"."),(0,r.kt)("p",null,"It's recommended to submit the whole batch as a ",(0,r.kt)("inlineCode",{parentName:"p"},"linked")," chain of events, ensuring that\nif any event fails, none of them are committed, preserving the last timestamp unchanged.\nThis approach gives the application a chance to correct failed imported events, re-submitting\nthe batch again with the same user-defined timestamps."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-java"},"// First, load and import all accounts with their timestamps from the historical source.\naccounts = new AccountBatch(historicalAccounts.length);\nfor(int index = 0; index < historicalAccounts.length; index += 1) {\n accounts.add();\n\n // Set a unique and strictly increasing timestamp.\n historicalTimestamp += 1;\n accounts.setTimestamp(historicalTimestamp);\n // Set the account as `imported`.\n // To ensure atomicity, the entire batch (except the last event in the chain)\n // must be `linked`.\n if (index < historicalAccounts.length - 1) {\n accounts.setFlags(AccountFlags.IMPORTED | AccountFlags.LINKED);\n } else {\n accounts.setFlags(AccountFlags.IMPORTED);\n }\n\n // Populate the rest of the account:\n // accounts.setId(historicalAccounts[index].Id);\n // accounts.setCode(historicalAccounts[index].Code);\n}\naccountErrors = client.createAccounts(accounts);\n// Error handling omitted.\n\n// Then, load and import all transfers with their timestamps from the historical source.\ntransfers = new TransferBatch(historicalTransfers.length);\nfor(int index = 0; index < historicalTransfers.length; index += 1) {\n transfers.add();\n\n // Set a unique and strictly increasing timestamp.\n historicalTimestamp += 1;\n transfers.setTimestamp(historicalTimestamp);\n // Set the account as `imported`.\n // To ensure atomicity, the entire batch (except the last event in the chain)\n // must be `linked`.\n if (index < historicalTransfers.length - 1) {\n transfers.setFlags(TransferFlags.IMPORTED | TransferFlags.LINKED);\n } else {\n transfers.setFlags(TransferFlags.IMPORTED);\n }\n\n // Populate the rest of the transfer:\n // transfers.setId(historicalTransfers[index].Id);\n // transfers.setCode(historicalTransfers[index].Code);\n}\ntransferErrors = client.createTransfers(transfers);\n// Error handling omitted.\n// Since it is a linked chain, in case of any error the entire batch is rolled back and can be retried\n// with the same historical timestamps without regressing the cluster timestamp.\n")))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/d3a362a2.c844f429.js b/assets/js/d3a362a2.c844f429.js new file mode 100644 index 0000000..ab12070 --- /dev/null +++ b/assets/js/d3a362a2.c844f429.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[9530],{3905:(e,t,r)=>{r.d(t,{Zo:()=>c,kt:()=>f});var n=r(7294);function a(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function i(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function l(e){for(var t=1;t=0||(a[r]=e[r]);return a}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(a[r]=e[r])}return a}var o=n.createContext({}),u=function(e){var t=n.useContext(o),r=t;return e&&(r="function"==typeof e?e(t):l(l({},t),e)),r},c=function(e){var t=u(e.components);return n.createElement(o.Provider,{value:t},e.children)},d="mdxType",p={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},m=n.forwardRef((function(e,t){var r=e.components,a=e.mdxType,i=e.originalType,o=e.parentName,c=s(e,["components","mdxType","originalType","parentName"]),d=u(r),m=a,f=d["".concat(o,".").concat(m)]||d[m]||p[m]||i;return r?n.createElement(f,l(l({ref:t},c),{},{components:r})):n.createElement(f,l({ref:t},c))}));function f(e,t){var r=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var i=r.length,l=new Array(i);l[0]=m;var s={};for(var o in t)hasOwnProperty.call(t,o)&&(s[o]=t[o]);s.originalType=e,s[d]="string"==typeof e?e:a,l[1]=s;for(var u=2;u{r.r(t),r.d(t,{assets:()=>o,contentTitle:()=>l,default:()=>p,frontMatter:()=>i,metadata:()=>s,toc:()=>u});var n=r(7462),a=(r(7294),r(3905));const i={sidebar_position:4},l="AccountFilter",s={unversionedId:"reference/account-filter",id:"reference/account-filter",title:"AccountFilter",description:"An AccountFilter is a record containing the filter parameters for querying",source:"@site/pages/reference/account-filter.md",sourceDirName:"reference",slug:"/reference/account-filter",permalink:"/reference/account-filter",draft:!1,editUrl:"https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/reference/account-filter.md",tags:[],version:"current",sidebarPosition:4,frontMatter:{sidebar_position:4},sidebar:"tutorialSidebar",previous:{title:"AccountBalance",permalink:"/reference/account-balance"},next:{title:"QueryFilter",permalink:"/reference/query-filter"}},o={},u=[{value:"Fields",id:"fields",level:2},{value:"account_id",id:"account_id",level:3},{value:"user_data_128",id:"user_data_128",level:3},{value:"user_data_64",id:"user_data_64",level:3},{value:"user_data_32",id:"user_data_32",level:3},{value:"code",id:"code",level:3},{value:"reserved",id:"reserved",level:3},{value:"timestamp_min",id:"timestamp_min",level:3},{value:"timestamp_max",id:"timestamp_max",level:3},{value:"limit",id:"limit",level:3},{value:"flags",id:"flags",level:3},{value:"flags.debits",id:"flagsdebits",level:4},{value:"flags.credits",id:"flagscredits",level:4},{value:"flags.reversed",id:"flagsreversed",level:4}],c={toc:u},d="wrapper";function p(e){let{components:t,...r}=e;return(0,a.kt)(d,(0,n.Z)({},c,r,{components:t,mdxType:"MDXLayout"}),(0,a.kt)("h1",{id:"accountfilter"},(0,a.kt)("inlineCode",{parentName:"h1"},"AccountFilter")),(0,a.kt)("p",null,"An ",(0,a.kt)("inlineCode",{parentName:"p"},"AccountFilter")," is a record containing the filter parameters for querying\nthe ",(0,a.kt)("a",{parentName:"p",href:"/reference/requests/get_account_transfers"},"account transfers"),"\nand the ",(0,a.kt)("a",{parentName:"p",href:"/reference/requests/get_account_balances"},"account historical balances"),"."),(0,a.kt)("h2",{id:"fields"},"Fields"),(0,a.kt)("h3",{id:"account_id"},(0,a.kt)("inlineCode",{parentName:"h3"},"account_id")),(0,a.kt)("p",null,"The unique ",(0,a.kt)("a",{parentName:"p",href:"/reference/account#id"},"identifier")," of the account for which the results will be retrieved."),(0,a.kt)("p",null,"Constraints:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Type is 128-bit unsigned integer (16 bytes)"),(0,a.kt)("li",{parentName:"ul"},"Must not be zero or ",(0,a.kt)("inlineCode",{parentName:"li"},"2^128 - 1"))),(0,a.kt)("h3",{id:"user_data_128"},(0,a.kt)("inlineCode",{parentName:"h3"},"user_data_128")),(0,a.kt)("p",null,"Filter the results by the field ",(0,a.kt)("a",{parentName:"p",href:"/reference/transfer#user_data_128"},(0,a.kt)("inlineCode",{parentName:"a"},"Transfer.user_data_128")),".\nOptional; set to zero to disable the filter."),(0,a.kt)("p",null,"Constraints:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Type is 128-bit unsigned integer (16 bytes)")),(0,a.kt)("h3",{id:"user_data_64"},(0,a.kt)("inlineCode",{parentName:"h3"},"user_data_64")),(0,a.kt)("p",null,"Filter the results by the field ",(0,a.kt)("a",{parentName:"p",href:"/reference/transfer#user_data_64"},(0,a.kt)("inlineCode",{parentName:"a"},"Transfer.user_data_64")),".\nOptional; set to zero to disable the filter."),(0,a.kt)("p",null,"Constraints:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Type is 64-bit unsigned integer (8 bytes)")),(0,a.kt)("h3",{id:"user_data_32"},(0,a.kt)("inlineCode",{parentName:"h3"},"user_data_32")),(0,a.kt)("p",null,"Filter the results by the field ",(0,a.kt)("a",{parentName:"p",href:"/reference/transfer#user_data_32"},(0,a.kt)("inlineCode",{parentName:"a"},"Transfer.user_data_32")),".\nOptional; set to zero to disable the filter."),(0,a.kt)("p",null,"Constraints:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Type is 32-bit unsigned integer (4 bytes)")),(0,a.kt)("h3",{id:"code"},(0,a.kt)("inlineCode",{parentName:"h3"},"code")),(0,a.kt)("p",null,"Filter the results by the ",(0,a.kt)("a",{parentName:"p",href:"/reference/transfer#code"},(0,a.kt)("inlineCode",{parentName:"a"},"Transfer.code")),".\nOptional; set to zero to disable the filter."),(0,a.kt)("p",null,"Constraints:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Type is 16-bit unsigned integer (2 bytes)")),(0,a.kt)("h3",{id:"reserved"},(0,a.kt)("inlineCode",{parentName:"h3"},"reserved")),(0,a.kt)("p",null,"This space may be used for additional data in the future."),(0,a.kt)("p",null,"Constraints:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Type is 58 bytes"),(0,a.kt)("li",{parentName:"ul"},"Must be zero")),(0,a.kt)("h3",{id:"timestamp_min"},(0,a.kt)("inlineCode",{parentName:"h3"},"timestamp_min")),(0,a.kt)("p",null,"The minimum ",(0,a.kt)("a",{parentName:"p",href:"/reference/transfer#timestamp"},(0,a.kt)("inlineCode",{parentName:"a"},"Transfer.timestamp"))," from which results will be returned, inclusive range.\nOptional; set to zero to disable the lower-bound filter."),(0,a.kt)("p",null,"Constraints:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Type is 64-bit unsigned integer (8 bytes)"),(0,a.kt)("li",{parentName:"ul"},"Must be less than ",(0,a.kt)("inlineCode",{parentName:"li"},"2^63"),".")),(0,a.kt)("h3",{id:"timestamp_max"},(0,a.kt)("inlineCode",{parentName:"h3"},"timestamp_max")),(0,a.kt)("p",null,"The maximum ",(0,a.kt)("a",{parentName:"p",href:"/reference/transfer#timestamp"},(0,a.kt)("inlineCode",{parentName:"a"},"Transfer.timestamp"))," from which results will be returned, inclusive range.\nOptional; set to zero to disable the upper-bound filter."),(0,a.kt)("p",null,"Constraints:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Type is 64-bit unsigned integer (8 bytes)"),(0,a.kt)("li",{parentName:"ul"},"Must be less than ",(0,a.kt)("inlineCode",{parentName:"li"},"2^63"),".")),(0,a.kt)("h3",{id:"limit"},(0,a.kt)("inlineCode",{parentName:"h3"},"limit")),(0,a.kt)("p",null,"The maximum number of results that can be returned by this query."),(0,a.kt)("p",null,"Limited by the ",(0,a.kt)("a",{parentName:"p",href:"/reference/requests/#batching-events"},"maximum message size"),"."),(0,a.kt)("p",null,"Constraints:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Type is 32-bit unsigned integer (4 bytes)"),(0,a.kt)("li",{parentName:"ul"},"Must not be zero")),(0,a.kt)("h3",{id:"flags"},(0,a.kt)("inlineCode",{parentName:"h3"},"flags")),(0,a.kt)("p",null,"A bitfield that specifies querying behavior."),(0,a.kt)("p",null,"Constraints:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Type is 32-bit unsigned integer (4 bytes)")),(0,a.kt)("h4",{id:"flagsdebits"},(0,a.kt)("inlineCode",{parentName:"h4"},"flags.debits")),(0,a.kt)("p",null,"Whether or not to include results where the field ",(0,a.kt)("a",{parentName:"p",href:"/reference/transfer#debit_account_id"},(0,a.kt)("inlineCode",{parentName:"a"},"debit_account_id")),"\nmatches the parameter ",(0,a.kt)("a",{parentName:"p",href:"#account_id"},(0,a.kt)("inlineCode",{parentName:"a"},"account_id")),"."),(0,a.kt)("h4",{id:"flagscredits"},(0,a.kt)("inlineCode",{parentName:"h4"},"flags.credits")),(0,a.kt)("p",null,"Whether or not to include results where the field ",(0,a.kt)("a",{parentName:"p",href:"/reference/transfer#credit_account_id"},(0,a.kt)("inlineCode",{parentName:"a"},"credit_account_id")),"\nmatches the parameter ",(0,a.kt)("a",{parentName:"p",href:"#account_id"},(0,a.kt)("inlineCode",{parentName:"a"},"account_id")),"."),(0,a.kt)("h4",{id:"flagsreversed"},(0,a.kt)("inlineCode",{parentName:"h4"},"flags.reversed")),(0,a.kt)("p",null,"Whether the results are sorted by timestamp in chronological or reverse-chronological order. If the\nflag is not set, the event that happened first (has the smallest timestamp) will come first. If the\nflag is set, the event that happened last (has the largest timestamp) will come first."))}p.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/d3a362a2.f623f861.js b/assets/js/d3a362a2.f623f861.js deleted file mode 100644 index 61e1009..0000000 --- a/assets/js/d3a362a2.f623f861.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[9530],{3905:(e,t,n)=>{n.d(t,{Zo:()=>u,kt:()=>f});var r=n(7294);function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function l(e){for(var t=1;t=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var s=r.createContext({}),c=function(e){var t=r.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):l(l({},t),e)),n},u=function(e){var t=c(e.components);return r.createElement(s.Provider,{value:t},e.children)},p="mdxType",d={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},m=r.forwardRef((function(e,t){var n=e.components,a=e.mdxType,i=e.originalType,s=e.parentName,u=o(e,["components","mdxType","originalType","parentName"]),p=c(n),m=a,f=p["".concat(s,".").concat(m)]||p[m]||d[m]||i;return n?r.createElement(f,l(l({ref:t},u),{},{components:n})):r.createElement(f,l({ref:t},u))}));function f(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var i=n.length,l=new Array(i);l[0]=m;var o={};for(var s in t)hasOwnProperty.call(t,s)&&(o[s]=t[s]);o.originalType=e,o[p]="string"==typeof e?e:a,l[1]=o;for(var c=2;c{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>l,default:()=>d,frontMatter:()=>i,metadata:()=>o,toc:()=>c});var r=n(7462),a=(n(7294),n(3905));const i={sidebar_position:4},l="AccountFilter",o={unversionedId:"reference/account-filter",id:"reference/account-filter",title:"AccountFilter",description:"An AccountFilter is a record containing the filter parameters for querying",source:"@site/pages/reference/account-filter.md",sourceDirName:"reference",slug:"/reference/account-filter",permalink:"/reference/account-filter",draft:!1,editUrl:"https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/reference/account-filter.md",tags:[],version:"current",sidebarPosition:4,frontMatter:{sidebar_position:4},sidebar:"tutorialSidebar",previous:{title:"AccountBalance",permalink:"/reference/account-balance"},next:{title:"QueryFilter",permalink:"/reference/query-filter"}},s={},c=[{value:"Fields",id:"fields",level:2},{value:"account_id",id:"account_id",level:3},{value:"timestamp_min",id:"timestamp_min",level:3},{value:"timestamp_max",id:"timestamp_max",level:3},{value:"limit",id:"limit",level:3},{value:"flags",id:"flags",level:3},{value:"flags.debits",id:"flagsdebits",level:4},{value:"flags.credits",id:"flagscredits",level:4},{value:"flags.reversed",id:"flagsreversed",level:4},{value:"reserved",id:"reserved",level:3}],u={toc:c},p="wrapper";function d(e){let{components:t,...n}=e;return(0,a.kt)(p,(0,r.Z)({},u,n,{components:t,mdxType:"MDXLayout"}),(0,a.kt)("h1",{id:"accountfilter"},(0,a.kt)("inlineCode",{parentName:"h1"},"AccountFilter")),(0,a.kt)("p",null,"An ",(0,a.kt)("inlineCode",{parentName:"p"},"AccountFilter")," is a record containing the filter parameters for querying\nthe ",(0,a.kt)("a",{parentName:"p",href:"/reference/requests/get_account_transfers"},"account transfers"),"\nand the ",(0,a.kt)("a",{parentName:"p",href:"/reference/requests/get_account_balances"},"account historical balances"),"."),(0,a.kt)("h2",{id:"fields"},"Fields"),(0,a.kt)("h3",{id:"account_id"},(0,a.kt)("inlineCode",{parentName:"h3"},"account_id")),(0,a.kt)("p",null,"The unique ",(0,a.kt)("a",{parentName:"p",href:"/reference/account#id"},"identifier")," of the account for which the results will be retrieved."),(0,a.kt)("p",null,"Constraints:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Type is 128-bit unsigned integer (16 bytes)"),(0,a.kt)("li",{parentName:"ul"},"Must not be zero or ",(0,a.kt)("inlineCode",{parentName:"li"},"2^128 - 1"))),(0,a.kt)("h3",{id:"timestamp_min"},(0,a.kt)("inlineCode",{parentName:"h3"},"timestamp_min")),(0,a.kt)("p",null,"The minimum ",(0,a.kt)("a",{parentName:"p",href:"/reference/transfer#timestamp"},(0,a.kt)("inlineCode",{parentName:"a"},"Transfer.timestamp"))," from which results will be returned, inclusive range.\nOptional; set to zero to disable the lower-bound filter."),(0,a.kt)("p",null,"Constraints:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Type is 64-bit unsigned integer (8 bytes)"),(0,a.kt)("li",{parentName:"ul"},"Must be less than ",(0,a.kt)("inlineCode",{parentName:"li"},"2^63"),".")),(0,a.kt)("h3",{id:"timestamp_max"},(0,a.kt)("inlineCode",{parentName:"h3"},"timestamp_max")),(0,a.kt)("p",null,"The maximum ",(0,a.kt)("a",{parentName:"p",href:"/reference/transfer#timestamp"},(0,a.kt)("inlineCode",{parentName:"a"},"Transfer.timestamp"))," from which results will be returned, inclusive range.\nOptional; set to zero to disable the upper-bound filter."),(0,a.kt)("p",null,"Constraints:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Type is 64-bit unsigned integer (8 bytes)"),(0,a.kt)("li",{parentName:"ul"},"Must be less than ",(0,a.kt)("inlineCode",{parentName:"li"},"2^63"),".")),(0,a.kt)("h3",{id:"limit"},(0,a.kt)("inlineCode",{parentName:"h3"},"limit")),(0,a.kt)("p",null,"The maximum number of results that can be returned by this query."),(0,a.kt)("p",null,"Limited by the ",(0,a.kt)("a",{parentName:"p",href:"/reference/requests/#batching-events"},"maximum message size"),"."),(0,a.kt)("p",null,"Constraints:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Type is 32-bit unsigned integer (4 bytes)"),(0,a.kt)("li",{parentName:"ul"},"Must not be zero")),(0,a.kt)("h3",{id:"flags"},(0,a.kt)("inlineCode",{parentName:"h3"},"flags")),(0,a.kt)("p",null,"A bitfield that specifies querying behavior."),(0,a.kt)("p",null,"Constraints:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Type is 32-bit unsigned integer (4 bytes)")),(0,a.kt)("h4",{id:"flagsdebits"},(0,a.kt)("inlineCode",{parentName:"h4"},"flags.debits")),(0,a.kt)("p",null,"Whether or not to include results where the field ",(0,a.kt)("a",{parentName:"p",href:"/reference/transfer#debit_account_id"},(0,a.kt)("inlineCode",{parentName:"a"},"debit_account_id")),"\nmatches the parameter ",(0,a.kt)("a",{parentName:"p",href:"#account_id"},(0,a.kt)("inlineCode",{parentName:"a"},"account_id")),"."),(0,a.kt)("h4",{id:"flagscredits"},(0,a.kt)("inlineCode",{parentName:"h4"},"flags.credits")),(0,a.kt)("p",null,"Whether or not to include results where the field ",(0,a.kt)("a",{parentName:"p",href:"/reference/transfer#credit_account_id"},(0,a.kt)("inlineCode",{parentName:"a"},"credit_account_id")),"\nmatches the parameter ",(0,a.kt)("a",{parentName:"p",href:"#account_id"},(0,a.kt)("inlineCode",{parentName:"a"},"account_id")),"."),(0,a.kt)("h4",{id:"flagsreversed"},(0,a.kt)("inlineCode",{parentName:"h4"},"flags.reversed")),(0,a.kt)("p",null,"Whether the results are sorted by timestamp in chronological or reverse-chronological order. If the\nflag is not set, the event that happened first (has the smallest timestamp) will come first. If the\nflag is set, the event that happened last (has the largest timestamp) will come first."),(0,a.kt)("h3",{id:"reserved"},(0,a.kt)("inlineCode",{parentName:"h3"},"reserved")),(0,a.kt)("p",null,"This space may be used for additional data in the future."),(0,a.kt)("p",null,"Constraints:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Type is 24 bytes"),(0,a.kt)("li",{parentName:"ul"},"Must be zero")))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/runtime~main.54525745.js b/assets/js/runtime~main.68046854.js similarity index 93% rename from assets/js/runtime~main.54525745.js rename to assets/js/runtime~main.68046854.js index e7f09af..525ec26 100644 --- a/assets/js/runtime~main.54525745.js +++ b/assets/js/runtime~main.68046854.js @@ -1 +1 @@ -(()=>{"use strict";var e,c,a,f,t,b={},r={};function d(e){var c=r[e];if(void 0!==c)return c.exports;var a=r[e]={exports:{}};return b[e].call(a.exports,a,a.exports,d),a.exports}d.m=b,e=[],d.O=(c,a,f,t)=>{if(!a){var b=1/0;for(i=0;i=t)&&Object.keys(d.O).every((e=>d.O[e](a[o])))?a.splice(o--,1):(r=!1,t0&&e[i-1][2]>t;i--)e[i]=e[i-1];e[i]=[a,f,t]},d.n=e=>{var c=e&&e.__esModule?()=>e.default:()=>e;return d.d(c,{a:c}),c},a=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,d.t=function(e,f){if(1&f&&(e=this(e)),8&f)return e;if("object"==typeof e&&e){if(4&f&&e.__esModule)return e;if(16&f&&"function"==typeof e.then)return e}var t=Object.create(null);d.r(t);var b={};c=c||[null,a({}),a([]),a(a)];for(var r=2&f&&e;"object"==typeof r&&!~c.indexOf(r);r=a(r))Object.getOwnPropertyNames(r).forEach((c=>b[c]=()=>e[c]));return b.default=()=>e,d.d(t,b),t},d.d=(e,c)=>{for(var a in c)d.o(c,a)&&!d.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:c[a]})},d.f={},d.e=e=>Promise.all(Object.keys(d.f).reduce(((c,a)=>(d.f[a](e,c),c)),[])),d.u=e=>"assets/js/"+({53:"935f2afb",113:"f6069575",199:"583d611f",333:"8cb2464c",379:"f7ad50ac",400:"350546f3",499:"3004abec",771:"e22d4ec9",885:"9b0eb4fb",962:"51c41399",1372:"13a99faa",1488:"7398e46d",1564:"7c1a6224",1677:"feb55396",2024:"c227177d",2032:"79002dcb",2132:"b51c73cc",2500:"de64a4bc",2519:"a4a8880c",2539:"b373b0c3",2738:"2fce5429",3042:"cab96357",3489:"b0f69697",4038:"34d38065",4317:"59cf55a9",4396:"4051a670",4698:"280500df",5511:"ac624c80",5610:"d135e03c",5614:"970deb07",5616:"af51e3cb",5681:"2209f33f",5885:"53635fc1",6201:"2d5c62ec",6214:"85b859d5",6221:"675d658f",6441:"b2e1ac4e",6641:"8388e350",6834:"214199fe",6994:"4fb5df53",7042:"625a5f2a",7092:"1feeaa2e",7112:"c76e3e11",7382:"f4744cb4",7596:"2b4a1ee0",7736:"99843d3e",7784:"d1ef64c4",7918:"17896441",7920:"1a4e3797",7938:"3c8e1e2c",7996:"27273ae6",8071:"38d047e3",8227:"69a6ec57",8404:"eeac8955",8775:"b2d6321c",9007:"f482cb0c",9049:"8b046af8",9154:"13142b44",9259:"0be03cf2",9290:"48f60023",9491:"f310e26a",9514:"1be78505",9530:"d3a362a2",9743:"2cf7febb"}[e]||e)+"."+{53:"b6f4cb6e",113:"fe2ecf32",199:"d5fa9b78",333:"60d68d0d",379:"420d4d9b",400:"c1b6cd69",499:"358e5244",771:"70ef1af3",885:"ae095492",962:"0fb6fb58",1372:"46b4d6cd",1426:"5959ada0",1488:"612528e3",1564:"647f2fba",1677:"d1c1c491",2024:"71f7e4dc",2032:"fce3a44a",2132:"9851cc63",2500:"451fd145",2519:"cd03e428",2539:"fc1667dd",2738:"0b2bd283",3042:"71d29d6c",3489:"9e4b8def",4038:"038c237e",4317:"eeffad2b",4396:"c0366401",4698:"aae615aa",4972:"16297f58",5511:"87bfbbd1",5610:"4e33dbe0",5614:"3a9b1cd3",5616:"d9a58c29",5681:"9d675f26",5885:"3c68c3e0",6201:"8f3fba57",6214:"b2a12386",6221:"d2918bb2",6316:"fdbc5257",6441:"9deb9562",6641:"63e0b00a",6834:"907f6d80",6945:"e6ca558a",6994:"33c30321",7042:"0128e97a",7092:"17039da5",7112:"1ee2c9d2",7382:"6027eb4c",7596:"032d72ca",7724:"492d96d5",7736:"25678062",7784:"e5aff3c4",7918:"51037d8a",7920:"aed7c3e9",7938:"55cd56a9",7996:"75904e74",8071:"79054ebd",8227:"9d873408",8404:"2ec07622",8775:"c1915e3d",8894:"547a1c8d",9007:"67df2f0a",9049:"a982777d",9154:"2744f1bc",9259:"ba5f69c3",9290:"81cedd6f",9487:"8ebb94ec",9491:"28734c2e",9514:"f4f69e1a",9530:"f623f861",9743:"dd3e76e4"}[e]+".js",d.miniCssF=e=>{},d.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),d.o=(e,c)=>Object.prototype.hasOwnProperty.call(e,c),f={},t="docs:",d.l=(e,c,a,b)=>{if(f[e])f[e].push(c);else{var r,o;if(void 0!==a)for(var n=document.getElementsByTagName("script"),i=0;i{r.onerror=r.onload=null,clearTimeout(s);var t=f[e];if(delete f[e],r.parentNode&&r.parentNode.removeChild(r),t&&t.forEach((e=>e(a))),c)return c(a)},s=setTimeout(l.bind(null,void 0,{type:"timeout",target:r}),12e4);r.onerror=l.bind(null,r.onerror),r.onload=l.bind(null,r.onload),o&&document.head.appendChild(r)}},d.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},d.p="/",d.gca=function(e){return e={17896441:"7918","935f2afb":"53",f6069575:"113","583d611f":"199","8cb2464c":"333",f7ad50ac:"379","350546f3":"400","3004abec":"499",e22d4ec9:"771","9b0eb4fb":"885","51c41399":"962","13a99faa":"1372","7398e46d":"1488","7c1a6224":"1564",feb55396:"1677",c227177d:"2024","79002dcb":"2032",b51c73cc:"2132",de64a4bc:"2500",a4a8880c:"2519",b373b0c3:"2539","2fce5429":"2738",cab96357:"3042",b0f69697:"3489","34d38065":"4038","59cf55a9":"4317","4051a670":"4396","280500df":"4698",ac624c80:"5511",d135e03c:"5610","970deb07":"5614",af51e3cb:"5616","2209f33f":"5681","53635fc1":"5885","2d5c62ec":"6201","85b859d5":"6214","675d658f":"6221",b2e1ac4e:"6441","8388e350":"6641","214199fe":"6834","4fb5df53":"6994","625a5f2a":"7042","1feeaa2e":"7092",c76e3e11:"7112",f4744cb4:"7382","2b4a1ee0":"7596","99843d3e":"7736",d1ef64c4:"7784","1a4e3797":"7920","3c8e1e2c":"7938","27273ae6":"7996","38d047e3":"8071","69a6ec57":"8227",eeac8955:"8404",b2d6321c:"8775",f482cb0c:"9007","8b046af8":"9049","13142b44":"9154","0be03cf2":"9259","48f60023":"9290",f310e26a:"9491","1be78505":"9514",d3a362a2:"9530","2cf7febb":"9743"}[e]||e,d.p+d.u(e)},(()=>{var e={1303:0,532:0};d.f.j=(c,a)=>{var f=d.o(e,c)?e[c]:void 0;if(0!==f)if(f)a.push(f[2]);else if(/^(1303|532)$/.test(c))e[c]=0;else{var t=new Promise(((a,t)=>f=e[c]=[a,t]));a.push(f[2]=t);var b=d.p+d.u(c),r=new Error;d.l(b,(a=>{if(d.o(e,c)&&(0!==(f=e[c])&&(e[c]=void 0),f)){var t=a&&("load"===a.type?"missing":a.type),b=a&&a.target&&a.target.src;r.message="Loading chunk "+c+" failed.\n("+t+": "+b+")",r.name="ChunkLoadError",r.type=t,r.request=b,f[1](r)}}),"chunk-"+c,c)}},d.O.j=c=>0===e[c];var c=(c,a)=>{var f,t,b=a[0],r=a[1],o=a[2],n=0;if(b.some((c=>0!==e[c]))){for(f in r)d.o(r,f)&&(d.m[f]=r[f]);if(o)var i=o(d)}for(c&&c(a);n{"use strict";var e,c,a,f,t,b={},r={};function d(e){var c=r[e];if(void 0!==c)return c.exports;var a=r[e]={exports:{}};return b[e].call(a.exports,a,a.exports,d),a.exports}d.m=b,e=[],d.O=(c,a,f,t)=>{if(!a){var b=1/0;for(i=0;i=t)&&Object.keys(d.O).every((e=>d.O[e](a[o])))?a.splice(o--,1):(r=!1,t0&&e[i-1][2]>t;i--)e[i]=e[i-1];e[i]=[a,f,t]},d.n=e=>{var c=e&&e.__esModule?()=>e.default:()=>e;return d.d(c,{a:c}),c},a=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,d.t=function(e,f){if(1&f&&(e=this(e)),8&f)return e;if("object"==typeof e&&e){if(4&f&&e.__esModule)return e;if(16&f&&"function"==typeof e.then)return e}var t=Object.create(null);d.r(t);var b={};c=c||[null,a({}),a([]),a(a)];for(var r=2&f&&e;"object"==typeof r&&!~c.indexOf(r);r=a(r))Object.getOwnPropertyNames(r).forEach((c=>b[c]=()=>e[c]));return b.default=()=>e,d.d(t,b),t},d.d=(e,c)=>{for(var a in c)d.o(c,a)&&!d.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:c[a]})},d.f={},d.e=e=>Promise.all(Object.keys(d.f).reduce(((c,a)=>(d.f[a](e,c),c)),[])),d.u=e=>"assets/js/"+({53:"935f2afb",113:"f6069575",199:"583d611f",333:"8cb2464c",379:"f7ad50ac",400:"350546f3",499:"3004abec",771:"e22d4ec9",885:"9b0eb4fb",962:"51c41399",1372:"13a99faa",1488:"7398e46d",1564:"7c1a6224",1677:"feb55396",2024:"c227177d",2032:"79002dcb",2132:"b51c73cc",2500:"de64a4bc",2519:"a4a8880c",2539:"b373b0c3",2738:"2fce5429",3042:"cab96357",3489:"b0f69697",4038:"34d38065",4317:"59cf55a9",4396:"4051a670",4698:"280500df",5511:"ac624c80",5610:"d135e03c",5614:"970deb07",5616:"af51e3cb",5681:"2209f33f",5885:"53635fc1",6201:"2d5c62ec",6214:"85b859d5",6221:"675d658f",6441:"b2e1ac4e",6641:"8388e350",6834:"214199fe",6994:"4fb5df53",7042:"625a5f2a",7092:"1feeaa2e",7112:"c76e3e11",7382:"f4744cb4",7596:"2b4a1ee0",7736:"99843d3e",7784:"d1ef64c4",7918:"17896441",7920:"1a4e3797",7938:"3c8e1e2c",7996:"27273ae6",8071:"38d047e3",8227:"69a6ec57",8404:"eeac8955",8775:"b2d6321c",9007:"f482cb0c",9049:"8b046af8",9154:"13142b44",9259:"0be03cf2",9290:"48f60023",9491:"f310e26a",9514:"1be78505",9530:"d3a362a2",9743:"2cf7febb"}[e]||e)+"."+{53:"b6f4cb6e",113:"fe2ecf32",199:"d5fa9b78",333:"60d68d0d",379:"420d4d9b",400:"c1b6cd69",499:"358e5244",771:"70ef1af3",885:"ae095492",962:"0fb6fb58",1372:"7a5718e2",1426:"5959ada0",1488:"612528e3",1564:"647f2fba",1677:"d1c1c491",2024:"71f7e4dc",2032:"fce3a44a",2132:"9851cc63",2500:"451fd145",2519:"cd03e428",2539:"fc1667dd",2738:"0b2bd283",3042:"71d29d6c",3489:"9e4b8def",4038:"038c237e",4317:"eeffad2b",4396:"c0366401",4698:"aae615aa",4972:"16297f58",5511:"87bfbbd1",5610:"4e33dbe0",5614:"3a9b1cd3",5616:"d9a58c29",5681:"9d675f26",5885:"3c68c3e0",6201:"8f3fba57",6214:"b2a12386",6221:"3ac0bf5c",6316:"fdbc5257",6441:"9deb9562",6641:"63e0b00a",6834:"907f6d80",6945:"e6ca558a",6994:"a9e8a810",7042:"0128e97a",7092:"17039da5",7112:"1ee2c9d2",7382:"6027eb4c",7596:"032d72ca",7724:"492d96d5",7736:"4c3af039",7784:"e5aff3c4",7918:"51037d8a",7920:"aed7c3e9",7938:"55cd56a9",7996:"75904e74",8071:"79054ebd",8227:"9d873408",8404:"2ec07622",8775:"c1915e3d",8894:"547a1c8d",9007:"67df2f0a",9049:"a982777d",9154:"2744f1bc",9259:"ba5f69c3",9290:"81cedd6f",9487:"8ebb94ec",9491:"28734c2e",9514:"f4f69e1a",9530:"c844f429",9743:"dd3e76e4"}[e]+".js",d.miniCssF=e=>{},d.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),d.o=(e,c)=>Object.prototype.hasOwnProperty.call(e,c),f={},t="docs:",d.l=(e,c,a,b)=>{if(f[e])f[e].push(c);else{var r,o;if(void 0!==a)for(var n=document.getElementsByTagName("script"),i=0;i{r.onerror=r.onload=null,clearTimeout(s);var t=f[e];if(delete f[e],r.parentNode&&r.parentNode.removeChild(r),t&&t.forEach((e=>e(a))),c)return c(a)},s=setTimeout(l.bind(null,void 0,{type:"timeout",target:r}),12e4);r.onerror=l.bind(null,r.onerror),r.onload=l.bind(null,r.onload),o&&document.head.appendChild(r)}},d.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},d.p="/",d.gca=function(e){return e={17896441:"7918","935f2afb":"53",f6069575:"113","583d611f":"199","8cb2464c":"333",f7ad50ac:"379","350546f3":"400","3004abec":"499",e22d4ec9:"771","9b0eb4fb":"885","51c41399":"962","13a99faa":"1372","7398e46d":"1488","7c1a6224":"1564",feb55396:"1677",c227177d:"2024","79002dcb":"2032",b51c73cc:"2132",de64a4bc:"2500",a4a8880c:"2519",b373b0c3:"2539","2fce5429":"2738",cab96357:"3042",b0f69697:"3489","34d38065":"4038","59cf55a9":"4317","4051a670":"4396","280500df":"4698",ac624c80:"5511",d135e03c:"5610","970deb07":"5614",af51e3cb:"5616","2209f33f":"5681","53635fc1":"5885","2d5c62ec":"6201","85b859d5":"6214","675d658f":"6221",b2e1ac4e:"6441","8388e350":"6641","214199fe":"6834","4fb5df53":"6994","625a5f2a":"7042","1feeaa2e":"7092",c76e3e11:"7112",f4744cb4:"7382","2b4a1ee0":"7596","99843d3e":"7736",d1ef64c4:"7784","1a4e3797":"7920","3c8e1e2c":"7938","27273ae6":"7996","38d047e3":"8071","69a6ec57":"8227",eeac8955:"8404",b2d6321c:"8775",f482cb0c:"9007","8b046af8":"9049","13142b44":"9154","0be03cf2":"9259","48f60023":"9290",f310e26a:"9491","1be78505":"9514",d3a362a2:"9530","2cf7febb":"9743"}[e]||e,d.p+d.u(e)},(()=>{var e={1303:0,532:0};d.f.j=(c,a)=>{var f=d.o(e,c)?e[c]:void 0;if(0!==f)if(f)a.push(f[2]);else if(/^(1303|532)$/.test(c))e[c]=0;else{var t=new Promise(((a,t)=>f=e[c]=[a,t]));a.push(f[2]=t);var b=d.p+d.u(c),r=new Error;d.l(b,(a=>{if(d.o(e,c)&&(0!==(f=e[c])&&(e[c]=void 0),f)){var t=a&&("load"===a.type?"missing":a.type),b=a&&a.target&&a.target.src;r.message="Loading chunk "+c+" failed.\n("+t+": "+b+")",r.name="ChunkLoadError",r.type=t,r.request=b,f[1](r)}}),"chunk-"+c,c)}},d.O.j=c=>0===e[c];var c=(c,a)=>{var f,t,b=a[0],r=a[1],o=a[2],n=0;if(b.some((c=>0!==e[c]))){for(f in r)d.o(r,f)&&(d.m[f]=r[f]);if(o)var i=o(d)}for(c&&c(a);n.NET | TigerBeetle Docs - + @@ -87,12 +87,12 @@ id field in the response to distinguish transfers.

    transfers = client.LookupTransfers(new UInt128[] { 1, 2 });

    Get Account Transfers

    NOTE: This is a preview API that is subject to breaking changes once we have a stable querying API.

    Fetches the transfers involving a given account, allowing basic filter and pagination capabilities.

    The transfers in the response are sorted by timestamp in chronological or -reverse-chronological order.

    var filter = new AccountFilter
    {
    AccountId = 2,
    TimestampMin = 0, // No filter by Timestamp.
    TimestampMax = 0, // No filter by Timestamp.
    Limit = 10, // Limit to ten transfers at most.
    Flags = AccountFilterFlags.Debits | // Include transfer from the debit side.
    AccountFilterFlags.Credits | // Include transfer from the credit side.
    AccountFilterFlags.Reversed, // Sort by timestamp in reverse-chronological order.
    };

    transfers = client.GetAccountTransfers(filter);

    Get Account Balances

    NOTE: This is a preview API that is subject to breaking changes once we have +reverse-chronological order.

    var filter = new AccountFilter
    {
    AccountId = 2,
    UserData128 = 0, // No filter by UserData.
    UserData64 = 0,
    UserData32 = 0,
    Code = 0, // No filter by Code.
    TimestampMin = 0, // No filter by Timestamp.
    TimestampMax = 0, // No filter by Timestamp.
    Limit = 10, // Limit to ten transfers at most.
    Flags = AccountFilterFlags.Debits | // Include transfer from the debit side.
    AccountFilterFlags.Credits | // Include transfer from the credit side.
    AccountFilterFlags.Reversed, // Sort by timestamp in reverse-chronological order.
    };

    transfers = client.GetAccountTransfers(filter);

    Get Account Balances

    NOTE: This is a preview API that is subject to breaking changes once we have a stable querying API.

    Fetches the point-in-time balances of a given account, allowing basic filter and pagination capabilities.

    Only accounts created with the flag history set retain historical balances.

    The balances in the response are sorted by timestamp in chronological or -reverse-chronological order.

    filter = new AccountFilter
    {
    AccountId = 2,
    TimestampMin = 0, // No filter by Timestamp.
    TimestampMax = 0, // No filter by Timestamp.
    Limit = 10, // Limit to ten balances at most.
    Flags = AccountFilterFlags.Debits | // Include transfer from the debit side.
    AccountFilterFlags.Credits | // Include transfer from the credit side.
    AccountFilterFlags.Reversed, // Sort by timestamp in reverse-chronological order.
    };

    var account_balances = client.GetAccountBalances(filter);

    Query Accounts

    NOTE: This is a preview API that is subject to breaking changes once we have +reverse-chronological order.

    filter = new AccountFilter
    {
    AccountId = 2,
    UserData128 = 0, // No filter by UserData.
    UserData64 = 0,
    UserData32 = 0,
    Code = 0, // No filter by Code.
    TimestampMin = 0, // No filter by Timestamp.
    TimestampMax = 0, // No filter by Timestamp.
    Limit = 10, // Limit to ten balances at most.
    Flags = AccountFilterFlags.Debits | // Include transfer from the debit side.
    AccountFilterFlags.Credits | // Include transfer from the credit side.
    AccountFilterFlags.Reversed, // Sort by timestamp in reverse-chronological order.
    };

    var account_balances = client.GetAccountBalances(filter);

    Query Accounts

    NOTE: This is a preview API that is subject to breaking changes once we have a stable querying API.

    Query accounts by the intersection of some fields and by timestamp range.

    The accounts in the response are sorted by timestamp in chronological or reverse-chronological order.

    var query_filter = new QueryFilter
    {
    UserData128 = 1000, // Filter by UserData.
    UserData64 = 100,
    UserData32 = 10,
    Code = 1, // Filter by Code.
    Ledger = 0, // No filter by Ledger.
    TimestampMin = 0, // No filter by Timestamp.
    TimestampMax = 0, // No filter by Timestamp.
    Limit = 10, // Limit to ten balances at most.
    Flags = QueryFilterFlags.Reversed, // Sort by timestamp in reverse-chronological order.
    };

    var query_accounts = client.QueryAccounts(query_filter);

    Query Transfers

    NOTE: This is a preview API that is subject to breaking changes once we have a stable querying API.

    Query transfers by the intersection of some fields and by timestamp range.

    The transfers in the response are sorted by timestamp in chronological or @@ -114,7 +114,7 @@ if any event fails, none of them are committed, preserving the last timestamp unchanged. This approach gives the application a chance to correct failed imported events, re-submitting the batch again with the same user-defined timestamps.

    // First, load and import all accounts with their timestamps from the historical source.
    var accountsBatch = new System.Collections.Generic.List<Account>();
    for (var index = 0; index < historicalAccounts.Length; index++)
    {
    var account = historicalAccounts[index];

    // Set a unique and strictly increasing timestamp.
    historicalTimestamp += 1;
    account.Timestamp = historicalTimestamp;
    // Set the account as `imported`.
    account.Flags = AccountFlags.Imported;
    // To ensure atomicity, the entire batch (except the last event in the chain)
    // must be `linked`.
    if (index < historicalAccounts.Length - 1)
    {
    account.Flags |= AccountFlags.Linked;
    }

    accountsBatch.Add(account);
    }

    createAccountsError = client.CreateAccounts(accountsBatch.ToArray());
    // Error handling omitted.

    // Then, load and import all transfers with their timestamps from the historical source.
    var transfersBatch = new System.Collections.Generic.List<Transfer>();
    for (var index = 0; index < historicalTransfers.Length; index++)
    {
    var transfer = historicalTransfers[index];

    // Set a unique and strictly increasing timestamp.
    historicalTimestamp += 1;
    transfer.Timestamp = historicalTimestamp;
    // Set the account as `imported`.
    transfer.Flags = TransferFlags.Imported;
    // To ensure atomicity, the entire batch (except the last event in the chain)
    // must be `linked`.
    if (index < historicalTransfers.Length - 1)
    {
    transfer.Flags |= TransferFlags.Linked;
    }

    transfersBatch.Add(transfer);
    }

    createTransfersError = client.CreateTransfers(transfersBatch.ToArray());
    // Error handling omitted.
    // Since it is a linked chain, in case of any error the entire batch is rolled back and can be retried
    // with the same historical timestamps without regressing the cluster timestamp.
    - + \ No newline at end of file diff --git a/clients/go/index.html b/clients/go/index.html index 9ede42e..e557066 100644 --- a/clients/go/index.html +++ b/clients/go/index.html @@ -6,7 +6,7 @@ Go | TigerBeetle Docs - + @@ -91,12 +91,12 @@ id field in the response to distinguish transfers.

    transfers, err = client.LookupTransfers([]Uint128{ToUint128(1), ToUint128(2)})
    if err != nil {
    log.Printf("Could not fetch transfers: %s", err)
    return
    }
    log.Println(transfers)

    Get Account Transfers

    NOTE: This is a preview API that is subject to breaking changes once we have a stable querying API.

    Fetches the transfers involving a given account, allowing basic filter and pagination capabilities.

    The transfers in the response are sorted by timestamp in chronological or -reverse-chronological order.

    filter := AccountFilter{
    AccountID: ToUint128(2),
    TimestampMin: 0, // No filter by Timestamp.
    TimestampMax: 0, // No filter by Timestamp.
    Limit: 10, // Limit to ten transfers at most.
    Flags: AccountFilterFlags{
    Debits: true, // Include transfer from the debit side.
    Credits: true, // Include transfer from the credit side.
    Reversed: true, // Sort by timestamp in reverse-chronological order.
    }.ToUint32(),
    }

    transfers, err = client.GetAccountTransfers(filter)
    if err != nil {
    log.Printf("Could not fetch transfers: %s", err)
    return
    }
    log.Println(transfers)

    Get Account Balances

    NOTE: This is a preview API that is subject to breaking changes once we have +reverse-chronological order.

    filter := AccountFilter{
    AccountID: ToUint128(2),
    UserData128: ToUint128(0), // No filter by UserData.
    UserData64: 0,
    UserData32: 0,
    Code: 0, // No filter by Code.
    TimestampMin: 0, // No filter by Timestamp.
    TimestampMax: 0, // No filter by Timestamp.
    Limit: 10, // Limit to ten transfers at most.
    Flags: AccountFilterFlags{
    Debits: true, // Include transfer from the debit side.
    Credits: true, // Include transfer from the credit side.
    Reversed: true, // Sort by timestamp in reverse-chronological order.
    }.ToUint32(),
    }

    transfers, err = client.GetAccountTransfers(filter)
    if err != nil {
    log.Printf("Could not fetch transfers: %s", err)
    return
    }
    log.Println(transfers)

    Get Account Balances

    NOTE: This is a preview API that is subject to breaking changes once we have a stable querying API.

    Fetches the point-in-time balances of a given account, allowing basic filter and pagination capabilities.

    Only accounts created with the flag history set retain historical balances.

    The balances in the response are sorted by timestamp in chronological or -reverse-chronological order.

    filter = AccountFilter{
    AccountID: ToUint128(2),
    TimestampMin: 0, // No filter by Timestamp.
    TimestampMax: 0, // No filter by Timestamp.
    Limit: 10, // Limit to ten balances at most.
    Flags: AccountFilterFlags{
    Debits: true, // Include transfer from the debit side.
    Credits: true, // Include transfer from the credit side.
    Reversed: true, // Sort by timestamp in reverse-chronological order.
    }.ToUint32(),
    }

    account_balances, err := client.GetAccountBalances(filter)
    if err != nil {
    log.Printf("Could not fetch the history: %s", err)
    return
    }
    log.Println(account_balances)

    Query Accounts

    NOTE: This is a preview API that is subject to breaking changes once we have +reverse-chronological order.

    filter = AccountFilter{
    AccountID: ToUint128(2),
    UserData128: ToUint128(0), // No filter by UserData.
    UserData64: 0,
    UserData32: 0,
    Code: 0, // No filter by Code.
    TimestampMin: 0, // No filter by Timestamp.
    TimestampMax: 0, // No filter by Timestamp.
    Limit: 10, // Limit to ten balances at most.
    Flags: AccountFilterFlags{
    Debits: true, // Include transfer from the debit side.
    Credits: true, // Include transfer from the credit side.
    Reversed: true, // Sort by timestamp in reverse-chronological order.
    }.ToUint32(),
    }

    account_balances, err := client.GetAccountBalances(filter)
    if err != nil {
    log.Printf("Could not fetch the history: %s", err)
    return
    }
    log.Println(account_balances)

    Query Accounts

    NOTE: This is a preview API that is subject to breaking changes once we have a stable querying API.

    Query accounts by the intersection of some fields and by timestamp range.

    The accounts in the response are sorted by timestamp in chronological or reverse-chronological order.

    query_filter := QueryFilter{
    UserData128: ToUint128(1000), // Filter by UserData
    UserData64: 100,
    UserData32: 10,
    Code: 1, // Filter by Code
    Ledger: 0, // No filter by Ledger
    TimestampMin: 0, // No filter by Timestamp.
    TimestampMax: 0, // No filter by Timestamp.
    Limit: 10, // Limit to ten balances at most.
    Flags: QueryFilterFlags{
    Reversed: true, // Sort by timestamp in reverse-chronological order.
    }.ToUint32(),
    }

    query_accounts, err := client.QueryAccounts(query_filter)
    if err != nil {
    log.Printf("Could not query accounts: %s", err)
    return
    }
    log.Println(query_accounts)

    Query Transfers

    NOTE: This is a preview API that is subject to breaking changes once we have a stable querying API.

    Query transfers by the intersection of some fields and by timestamp range.

    The transfers in the response are sorted by timestamp in chronological or @@ -118,7 +118,7 @@ if any event fails, none of them are committed, preserving the last timestamp unchanged. This approach gives the application a chance to correct failed imported events, re-submitting the batch again with the same user-defined timestamps.

    // First, load and import all accounts with their timestamps from the historical source.
    accountsBatch := []Account{}
    for index, account := range historicalAccounts {
    // Set a unique and strictly increasing timestamp.
    historicalTimestamp += 1
    account.Timestamp = historicalTimestamp

    account.Flags = AccountFlags{
    // Set the account as `imported`.
    Imported: true,
    // To ensure atomicity, the entire batch (except the last event in the chain)
    // must be `linked`.
    Linked: index < len(historicalAccounts)-1,
    }.ToUint16()

    accountsBatch = append(accountsBatch, account)
    }
    accountsRes, err = client.CreateAccounts(accountsBatch)

    // Then, load and import all transfers with their timestamps from the historical source.
    transfersBatch := []Transfer{}
    for index, transfer := range historicalTransfers {
    // Set a unique and strictly increasing timestamp.
    historicalTimestamp += 1
    transfer.Timestamp = historicalTimestamp

    transfer.Flags = TransferFlags{
    // Set the transfer as `imported`.
    Imported: true,
    // To ensure atomicity, the entire batch (except the last event in the chain)
    // must be `linked`.
    Linked: index < len(historicalAccounts)-1,
    }.ToUint16()

    transfersBatch = append(transfersBatch, transfer)
    }
    transfersRes, err = client.CreateTransfers(transfersBatch)
    // Error handling omitted..
    // Since it is a linked chain, in case of any error the entire batch is rolled back and can be retried
    // with the same historical timestamps without regressing the cluster timestamp.
    - + \ No newline at end of file diff --git a/clients/java/index.html b/clients/java/index.html index eade4ae..67759c6 100644 --- a/clients/java/index.html +++ b/clients/java/index.html @@ -6,7 +6,7 @@ Java | TigerBeetle Docs - + @@ -85,12 +85,12 @@ id field in the response to distinguish transfers.

    ids = new IdBatch(2);
    ids.add(1);
    ids.add(2);

    transfers = client.lookupTransfers(ids);

    Get Account Transfers

    NOTE: This is a preview API that is subject to breaking changes once we have a stable querying API.

    Fetches the transfers involving a given account, allowing basic filter and pagination capabilities.

    The transfers in the response are sorted by timestamp in chronological or -reverse-chronological order.

    AccountFilter filter = new AccountFilter();
    filter.setAccountId(2);
    filter.setTimestampMin(0); // No filter by Timestamp.
    filter.setTimestampMax(0); // No filter by Timestamp.
    filter.setLimit(10); // Limit to ten transfers at most.
    filter.setDebits(true); // Include transfer from the debit side.
    filter.setCredits(true); // Include transfer from the credit side.
    filter.setReversed(true); // Sort by timestamp in reverse-chronological order.

    transfers = client.getAccountTransfers(filter);

    Get Account Balances

    NOTE: This is a preview API that is subject to breaking changes once we have +reverse-chronological order.

    AccountFilter filter = new AccountFilter();
    filter.setAccountId(2);
    filter.setUserData128(0); // No filter by UserData.
    filter.setUserData64(0);
    filter.setUserData32(0);
    filter.setCode(0); // No filter by Code.
    filter.setTimestampMin(0); // No filter by Timestamp.
    filter.setTimestampMax(0); // No filter by Timestamp.
    filter.setLimit(10); // Limit to ten transfers at most.
    filter.setDebits(true); // Include transfer from the debit side.
    filter.setCredits(true); // Include transfer from the credit side.
    filter.setReversed(true); // Sort by timestamp in reverse-chronological order.

    transfers = client.getAccountTransfers(filter);

    Get Account Balances

    NOTE: This is a preview API that is subject to breaking changes once we have a stable querying API.

    Fetches the point-in-time balances of a given account, allowing basic filter and pagination capabilities.

    Only accounts created with the flag history set retain historical balances.

    The balances in the response are sorted by timestamp in chronological or -reverse-chronological order.

    filter = new AccountFilter();
    filter.setAccountId(2);
    filter.setTimestampMin(0); // No filter by Timestamp.
    filter.setTimestampMax(0); // No filter by Timestamp.
    filter.setLimit(10); // Limit to ten balances at most.
    filter.setDebits(true); // Include transfer from the debit side.
    filter.setCredits(true); // Include transfer from the credit side.
    filter.setReversed(true); // Sort by timestamp in reverse-chronological order.

    AccountBalanceBatch account_balances = client.getAccountBalances(filter);

    Query Accounts

    NOTE: This is a preview API that is subject to breaking changes once we have +reverse-chronological order.

    filter = new AccountFilter();
    filter.setAccountId(2);
    filter.setUserData128(0); // No filter by UserData.
    filter.setUserData64(0);
    filter.setUserData32(0);
    filter.setCode(0); // No filter by Code.
    filter.setTimestampMin(0); // No filter by Timestamp.
    filter.setTimestampMax(0); // No filter by Timestamp.
    filter.setLimit(10); // Limit to ten balances at most.
    filter.setDebits(true); // Include transfer from the debit side.
    filter.setCredits(true); // Include transfer from the credit side.
    filter.setReversed(true); // Sort by timestamp in reverse-chronological order.

    AccountBalanceBatch account_balances = client.getAccountBalances(filter);

    Query Accounts

    NOTE: This is a preview API that is subject to breaking changes once we have a stable querying API.

    Query accounts by the intersection of some fields and by timestamp range.

    The accounts in the response are sorted by timestamp in chronological or reverse-chronological order.

    var query_filter = new QueryFilter();
    query_filter.setUserData128(1000); // Filter by UserData.
    query_filter.setUserData64(100);
    query_filter.setUserData32(10);
    query_filter.setCode(1); // Filter by Code.
    query_filter.setLedger(0); // No filter by Ledger.
    query_filter.setTimestampMin(0); // No filter by Timestamp.
    query_filter.setTimestampMax(0); // No filter by Timestamp.
    query_filter.setLimit(10); // Limit to ten balances at most.
    query_filter.setReversed(true); // Sort by timestamp in reverse-chronological order.

    AccountBatch query_accounts = client.queryAccounts(query_filter);

    Query Transfers

    NOTE: This is a preview API that is subject to breaking changes once we have a stable querying API.

    Query transfers by the intersection of some fields and by timestamp range.

    The transfers in the response are sorted by timestamp in chronological or @@ -112,7 +112,7 @@ if any event fails, none of them are committed, preserving the last timestamp unchanged. This approach gives the application a chance to correct failed imported events, re-submitting the batch again with the same user-defined timestamps.

    // First, load and import all accounts with their timestamps from the historical source.
    accounts = new AccountBatch(historicalAccounts.length);
    for(int index = 0; index < historicalAccounts.length; index += 1) {
    accounts.add();

    // Set a unique and strictly increasing timestamp.
    historicalTimestamp += 1;
    accounts.setTimestamp(historicalTimestamp);
    // Set the account as `imported`.
    // To ensure atomicity, the entire batch (except the last event in the chain)
    // must be `linked`.
    if (index < historicalAccounts.length - 1) {
    accounts.setFlags(AccountFlags.IMPORTED | AccountFlags.LINKED);
    } else {
    accounts.setFlags(AccountFlags.IMPORTED);
    }

    // Populate the rest of the account:
    // accounts.setId(historicalAccounts[index].Id);
    // accounts.setCode(historicalAccounts[index].Code);
    }
    accountErrors = client.createAccounts(accounts);
    // Error handling omitted.

    // Then, load and import all transfers with their timestamps from the historical source.
    transfers = new TransferBatch(historicalTransfers.length);
    for(int index = 0; index < historicalTransfers.length; index += 1) {
    transfers.add();

    // Set a unique and strictly increasing timestamp.
    historicalTimestamp += 1;
    transfers.setTimestamp(historicalTimestamp);
    // Set the account as `imported`.
    // To ensure atomicity, the entire batch (except the last event in the chain)
    // must be `linked`.
    if (index < historicalTransfers.length - 1) {
    transfers.setFlags(TransferFlags.IMPORTED | TransferFlags.LINKED);
    } else {
    transfers.setFlags(TransferFlags.IMPORTED);
    }

    // Populate the rest of the transfer:
    // transfers.setId(historicalTransfers[index].Id);
    // transfers.setCode(historicalTransfers[index].Code);
    }
    transferErrors = client.createTransfers(transfers);
    // Error handling omitted.
    // Since it is a linked chain, in case of any error the entire batch is rolled back and can be retried
    // with the same historical timestamps without regressing the cluster timestamp.
    - + \ No newline at end of file diff --git a/clients/node/index.html b/clients/node/index.html index 7ad7115..160d38b 100644 --- a/clients/node/index.html +++ b/clients/node/index.html @@ -6,7 +6,7 @@ Node.js | TigerBeetle Docs - + @@ -92,16 +92,16 @@ id field in the response to distinguish transfers.

    transfers = await client.lookupTransfers([1n, 2n]);
    console.log(transfers);
    /*
    * [{
    * id: 1n,
    * debit_account_id: 102n,
    * credit_account_id: 103n,
    * amount: 10n,
    * pending_id: 0n,
    * user_data_128: 0n,
    * user_data_64: 0n,
    * user_data_32: 0,
    * timeout: 0,
    * ledger: 1,
    * code: 720,
    * flags: 0,
    * timestamp: 1623062009212508993n,
    * }]
    */

    Get Account Transfers

    NOTE: This is a preview API that is subject to breaking changes once we have a stable querying API.

    Fetches the transfers involving a given account, allowing basic filter and pagination capabilities.

    The transfers in the response are sorted by timestamp in chronological or -reverse-chronological order.

    let filter = {
    account_id: 2n,
    timestamp_min: 0n, // No filter by Timestamp.
    timestamp_max: 0n, // No filter by Timestamp.
    limit: 10, // Limit to ten balances at most.
    flags: AccountFilterFlags.debits | // Include transfer from the debit side.
    AccountFilterFlags.credits | // Include transfer from the credit side.
    AccountFilterFlags.reversed, // Sort by timestamp in reverse-chronological order.
    };

    const account_transfers = await client.getAccountTransfers(filter);

    Get Account Balances

    NOTE: This is a preview API that is subject to breaking changes once we have +reverse-chronological order.

    let filter = {
    account_id: 2n,
    user_data_128: 0n, // No filter by UserData.
    user_data_64: 0n,
    user_data_32: 0,
    code: 0, // No filter by Code.
    timestamp_min: 0n, // No filter by Timestamp.
    timestamp_max: 0n, // No filter by Timestamp.
    limit: 10, // Limit to ten balances at most.
    flags: AccountFilterFlags.debits | // Include transfer from the debit side.
    AccountFilterFlags.credits | // Include transfer from the credit side.
    AccountFilterFlags.reversed, // Sort by timestamp in reverse-chronological order.
    };

    const account_transfers = await client.getAccountTransfers(filter);

    Get Account Balances

    NOTE: This is a preview API that is subject to breaking changes once we have a stable querying API.

    Fetches the point-in-time balances of a given account, allowing basic filter and pagination capabilities.

    Only accounts created with the flag history set retain historical balances.

    The balances in the response are sorted by timestamp in chronological or -reverse-chronological order.

    filter = {
    account_id: 2n,
    timestamp_min: 0n, // No filter by Timestamp.
    timestamp_max: 0n, // No filter by Timestamp.
    limit: 10, // Limit to ten balances at most.
    flags: AccountFilterFlags.debits | // Include transfer from the debit side.
    AccountFilterFlags.credits | // Include transfer from the credit side.
    AccountFilterFlags.reversed, // Sort by timestamp in reverse-chronological order.
    };

    const account_balances = await client.getAccountBalances(filter);

    Query Accounts

    NOTE: This is a preview API that is subject to breaking changes once we have +reverse-chronological order.

    filter = {
    account_id: 2n,
    user_data_128: 0n, // No filter by UserData.
    user_data_64: 0n,
    user_data_32: 0,
    code: 0, // No filter by Code.
    timestamp_min: 0n, // No filter by Timestamp.
    timestamp_max: 0n, // No filter by Timestamp.
    limit: 10, // Limit to ten balances at most.
    flags: AccountFilterFlags.debits | // Include transfer from the debit side.
    AccountFilterFlags.credits | // Include transfer from the credit side.
    AccountFilterFlags.reversed, // Sort by timestamp in reverse-chronological order.
    };

    const account_balances = await client.getAccountBalances(filter);

    Query Accounts

    NOTE: This is a preview API that is subject to breaking changes once we have a stable querying API.

    Query accounts by the intersection of some fields and by timestamp range.

    The accounts in the response are sorted by timestamp in chronological or -reverse-chronological order.

    var query_filter = {
    user_data_128: 1000n, // Filter by UserData.
    user_data_64: 100n,
    user_data_32: 10,
    code: 1, // Filter by Code.
    ledger: 0, // No filter by Ledger.
    timestamp_min: 0n, // No filter by Timestamp.
    timestamp_max: 0n, // No filter by Timestamp.
    limit: 10, // Limit to ten balances at most.
    flags: AccountFilterFlags.debits | // Include transfer from the debit side.
    AccountFilterFlags.credits | // Include transfer from the credit side.
    AccountFilterFlags.reversed, // Sort by timestamp in reverse-chronological order.
    };
    const query_accounts = await client.queryAccounts(query_filter);

    Query Transfers

    NOTE: This is a preview API that is subject to breaking changes once we have +reverse-chronological order.

    var query_filter = {
    user_data_128: 1000n, // Filter by UserData.
    user_data_64: 100n,
    user_data_32: 10,
    code: 1, // Filter by Code.
    ledger: 0, // No filter by Ledger.
    timestamp_min: 0n, // No filter by Timestamp.
    timestamp_max: 0n, // No filter by Timestamp.
    limit: 10, // Limit to ten balances at most.
    flags: QueryFilterFlags.reversed, // Sort by timestamp in reverse-chronological order.
    };
    const query_accounts = await client.queryAccounts(query_filter);

    Query Transfers

    NOTE: This is a preview API that is subject to breaking changes once we have a stable querying API.

    Query transfers by the intersection of some fields and by timestamp range.

    The transfers in the response are sorted by timestamp in chronological or -reverse-chronological order.

    query_filter = {
    user_data_128: 1000n, // Filter by UserData.
    user_data_64: 100n,
    user_data_32: 10,
    code: 1, // Filter by Code.
    ledger: 0, // No filter by Ledger.
    timestamp_min: 0n, // No filter by Timestamp.
    timestamp_max: 0n, // No filter by Timestamp.
    limit: 10, // Limit to ten balances at most.
    flags: AccountFilterFlags.debits | // Include transfer from the debit side.
    AccountFilterFlags.credits | // Include transfer from the credit side.
    AccountFilterFlags.reversed, // Sort by timestamp in reverse-chronological order.
    };
    const query_transfers = await client.queryTransfers(query_filter);

    Linked Events

    When the linked flag is specified for an account when creating accounts or +reverse-chronological order.

    query_filter = {
    user_data_128: 1000n, // Filter by UserData.
    user_data_64: 100n,
    user_data_32: 10,
    code: 1, // Filter by Code.
    ledger: 0, // No filter by Ledger.
    timestamp_min: 0n, // No filter by Timestamp.
    timestamp_max: 0n, // No filter by Timestamp.
    limit: 10, // Limit to ten balances at most.
    flags: QueryFilterFlags.reversed, // Sort by timestamp in reverse-chronological order.
    };
    const query_transfers = await client.queryTransfers(query_filter);

    Linked Events

    When the linked flag is specified for an account when creating accounts or a transfer when creating transfers, it links that event with the next event in the batch, to create a chain of events, of arbitrary length, which all succeed or fail together. The tail of a chain is denoted by the first @@ -119,7 +119,7 @@ if any event fails, none of them are committed, preserving the last timestamp unchanged. This approach gives the application a chance to correct failed imported events, re-submitting the batch again with the same user-defined timestamps.

    // First, load and import all accounts with their timestamps from the historical source.
    const accountsBatch = [];
    for (let index = 0; i < historicalAccounts.length; i++) {
    let account = historicalAccounts[i];
    // Set a unique and strictly increasing timestamp.
    historicalTimestamp += 1;
    account.timestamp = historicalTimestamp;
    // Set the account as `imported`.
    account.flags = AccountFlags.imported;
    // To ensure atomicity, the entire batch (except the last event in the chain)
    // must be `linked`.
    if (index < historicalAccounts.length - 1) {
    account.flags |= AccountFlags.linked;
    }

    accountsBatch.push(account);
    }
    accountErrors = await client.createAccounts(accountsBatch);

    // Error handling omitted.
    // Then, load and import all transfers with their timestamps from the historical source.
    const transfersBatch = [];
    for (let index = 0; i < historicalTransfers.length; i++) {
    let transfer = historicalTransfers[i];
    // Set a unique and strictly increasing timestamp.
    historicalTimestamp += 1;
    transfer.timestamp = historicalTimestamp;
    // Set the account as `imported`.
    transfer.flags = TransferFlags.imported;
    // To ensure atomicity, the entire batch (except the last event in the chain)
    // must be `linked`.
    if (index < historicalTransfers.length - 1) {
    transfer.flags |= TransferFlags.linked;
    }

    transfersBatch.push(transfer);
    }
    transferErrors = await client.createAccounts(transfersBatch);
    // Error handling omitted.
    // Since it is a linked chain, in case of any error the entire batch is rolled back and can be retried
    // with the same historical timestamps without regressing the cluster timestamp.
    - + \ No newline at end of file diff --git a/coding/data-modeling/index.html b/coding/data-modeling/index.html index 4e26e43..bf9cc7b 100644 --- a/coding/data-modeling/index.html +++ b/coding/data-modeling/index.html @@ -6,7 +6,7 @@ Data Modeling | TigerBeetle Docs - + @@ -90,7 +90,7 @@ happening, such as a purchase, refund, currency exchange, etc.

    When you start building out your application on top of TigerBeetle, you may find it helpful to list out all of the known types of accounts and movements of funds and mapping each of these to code numbers or ranges.

    - + \ No newline at end of file diff --git a/coding/financial-accounting/index.html b/coding/financial-accounting/index.html index 3a86b18..48ba15e 100644 --- a/coding/financial-accounting/index.html +++ b/coding/financial-accounting/index.html @@ -6,7 +6,7 @@ Financial Accounting | TigerBeetle Docs - + @@ -82,7 +82,7 @@ of TigerBeetle in your architecture?

    Let us help you get it right. Contact our CEO, Joran Dirk Greef, at joran@tigerbeetle.com to set up a call.

    - + \ No newline at end of file diff --git a/coding/index.html b/coding/index.html index e689456..e6e2542 100644 --- a/coding/index.html +++ b/coding/index.html @@ -6,7 +6,7 @@ Developing Applications on TigerBeetle | TigerBeetle Docs - + @@ -29,7 +29,7 @@ you might have!

    Dedicated Consultation

    Would you like the TigerBeetle team to help you design your chart of accounts and leverage the power of TigerBeetle in your architecture?

    Let us help you get it right. Contact our CEO, Joran Dirk Greef, at joran@tigerbeetle.com to set up a call.

    - + \ No newline at end of file diff --git a/coding/recipes/balance-bounds/index.html b/coding/recipes/balance-bounds/index.html index cc57026..cf03064 100644 --- a/coding/recipes/balance-bounds/index.html +++ b/coding/recipes/balance-bounds/index.html @@ -6,7 +6,7 @@ Balance Bounds | TigerBeetle Docs - + @@ -32,7 +32,7 @@ Account's funds, even if it succeeds.

    If everything to this point succeeds, the fourth and fifth transfers simply undo the effects of the second and third transfers. The fourth transfer voids the pending transfer. And the fifth transfer resets the Control Account's net balance to zero.

    - + \ No newline at end of file diff --git a/coding/recipes/balance-conditional-transfers/index.html b/coding/recipes/balance-conditional-transfers/index.html index 7ed8aa4..cbee488 100644 --- a/coding/recipes/balance-conditional-transfers/index.html +++ b/coding/recipes/balance-conditional-transfers/index.html @@ -6,7 +6,7 @@ Balance-Conditional Transfers | TigerBeetle Docs - + @@ -31,7 +31,7 @@ account from the control account. Then, the third transfer would execute the desired transfer to the ultimate destination account.

    Note that in the tables above, we do the balance check on the source account. The balance check could also be applied to the destination account instead.

    - + \ No newline at end of file diff --git a/coding/recipes/close-account/index.html b/coding/recipes/close-account/index.html index 534659b..2c3a854 100644 --- a/coding/recipes/close-account/index.html +++ b/coding/recipes/close-account/index.html @@ -6,7 +6,7 @@ Close Account | TigerBeetle Docs - + @@ -20,7 +20,7 @@ application does not need to know (or query) the balance prior to closing the account.

    The stored transfer's amount will be set to the actual amount transferred.

  • T2 and T4 are closing transfers that will cause the respective account to be closed.

    The closing transfer must be also a pending transfer so the action can be reversible.

  • After committing these transfers, A and B are closed with net balance zero, and will reject any further transfers.

    AccountDebits PendingDebits PostedCredits PendingCredits PostedFlags
    A020020debits_must_not_exceed_credits, closed
    B030030credits_must_not_exceed_debits, closed
    C025010

    To re-open the closed account, the pending closing transfer can be voided, reverting the closing action (but not reverting the net balance):

    TransferDebit AccountCredit AccountAmountPending TransferFlags
    T5AC0T2void_pending_transfer
    T6CB0T4void_pending_transfer

    After committing these transfers, A and B are re-opened and can accept transfers again:

    AccountDebits PendingDebits PostedCredits PendingCredits PostedFlags
    A020020debits_must_not_exceed_credits
    B030030credits_must_not_exceed_debits
    C025010
    - + \ No newline at end of file diff --git a/coding/recipes/correcting-transfers/index.html b/coding/recipes/correcting-transfers/index.html index a5f6248..172efad 100644 --- a/coding/recipes/correcting-transfers/index.html +++ b/coding/recipes/correcting-transfers/index.html @@ -6,7 +6,7 @@ Correcting Transfers | TigerBeetle Docs - + @@ -31,7 +31,7 @@ would submit two additional transfers going in the opposite direction:

    LedgerDebit AccountCredit AccountAmountcodeuser_data_128flags.linked
    USDXA100010000123456true
    USDYA510000123456false

    Note that the codes used here don't have any actual meaning, but you would want to enumerate your business events and map each to a numeric code value, including the initial reasons for transfers and the reasons they might be corrected.

    - + \ No newline at end of file diff --git a/coding/recipes/currency-exchange/index.html b/coding/recipes/currency-exchange/index.html index 18b2aac..e12b28e 100644 --- a/coding/recipes/currency-exchange/index.html +++ b/coding/recipes/currency-exchange/index.html @@ -6,7 +6,7 @@ Currency Exchange | TigerBeetle Docs - + @@ -29,7 +29,7 @@ it implicitly records the exchange rate and spread at the time of the exchange — information that cannot be derived if the two are combined.

    Example

    This depicts the same scenario as the prior example, except the liquidity provider charges a $0.10 fee for the transaction.

    LedgerDebit AccountCredit AccountAmountflags.linked
    USDA₁L₁10000true
    USDA₁L₁10true
    INRL₂A₂8242135false
    - + \ No newline at end of file diff --git a/coding/recipes/multi-debit-credit-transfers/index.html b/coding/recipes/multi-debit-credit-transfers/index.html index 6c8bd7f..63db676 100644 --- a/coding/recipes/multi-debit-credit-transfers/index.html +++ b/coding/recipes/multi-debit-credit-transfers/index.html @@ -6,7 +6,7 @@ Multi-Debit, Multi-Credit Transfers | TigerBeetle Docs - + @@ -28,7 +28,7 @@ entry.

    However, if you're just getting started, you can avoid premature optimizations (we've all been there!). You may find it easier to program these compound journal entries always using a control account -- and you can then come back to squeeze this performance out later!

    - + \ No newline at end of file diff --git a/coding/recipes/rate-limiting/index.html b/coding/recipes/rate-limiting/index.html index 45d3644..8770129 100644 --- a/coding/recipes/rate-limiting/index.html +++ b/coding/recipes/rate-limiting/index.html @@ -6,7 +6,7 @@ Rate Limiting | TigerBeetle Docs - + @@ -31,7 +31,7 @@ money per time window. We can do that using 2 ledgers and linked transfers.

    LedgerAccountFlags
    Rate LimitingOperator0
    Rate LimitingUserdebits_must_not_exceed_credits
    USDOperator0
    USDUserdebits_must_not_exceed_credits

    Let's say we wanted to limit each account to sending no more than 1000 USD per day.

    To set up, we transfer 1000 from the Operator to the User on the Rate Limiting ledger:

    TransferLedgerDebit AccountCredit AccountAmount
    1Rate LimitingOperatorUser1000

    For each transfer the user wants to do, we will create 2 transfers that are linked:

    TransferLedgerDebit AccountCredit AccountAmountTimeoutFlags (Note \| sets multiple flags)
    2NRate LimitingUserOperatorTransfer Amount86400pending | linked
    2N + 1USDUserDestinationTransfer Amount00

    Note that we are using a timeout of 86400 seconds, because this is the number of seconds in a day.

    These are linked such that if the first transfer fails, because the user has already transferred too much money in the past day, the second transfer will also fail.

    - + \ No newline at end of file diff --git a/coding/reliable-transaction-submission/index.html b/coding/reliable-transaction-submission/index.html index b1b9056..d0bba32 100644 --- a/coding/reliable-transaction-submission/index.html +++ b/coding/reliable-transaction-submission/index.html @@ -6,7 +6,7 @@ Reliable Transaction Submission | TigerBeetle Docs - + @@ -30,7 +30,7 @@ TigerBeetle will respond with the ok if a transfer is newly created and exists if an object with the same id was already created.

    - + \ No newline at end of file diff --git a/coding/system-architecture/index.html b/coding/system-architecture/index.html index 599685e..cd1b346 100644 --- a/coding/system-architecture/index.html +++ b/coding/system-architecture/index.html @@ -6,7 +6,7 @@ TigerBeetle in Your System Architecture | TigerBeetle Docs - + @@ -26,7 +26,7 @@ purpose database. If it does, that database will become the bottleneck and will negate the performance gains from using TigerBeetle.

    Specifically, the types of information that fit into this category include:

    Hard-coded in app or cachedIn TigerBeetle
    Currency or asset code's string representation (for example, "USD")ledger and asset scale
    Account type's string representation (for example, "cash")code
    Transfer type's string representation (for example, "refund")code

    Authentication

    TigerBeetle does not support authentication. You should never allow untrusted users or services to interact with it directly.

    Also, untrusted processes must not be able to access or modify TigerBeetle's on-disk data file.

    - + \ No newline at end of file diff --git a/coding/time/index.html b/coding/time/index.html index f3e4754..ac6eeca 100644 --- a/coding/time/index.html +++ b/coding/time/index.html @@ -6,7 +6,7 @@ Time | TigerBeetle Docs - + @@ -41,7 +41,7 @@ on YouTube for more details.

    You can also read the blog post Three Clocks are Better than One for more on how nodes determine their own time and clock skew.

    - + \ No newline at end of file diff --git a/coding/two-phase-transfers/index.html b/coding/two-phase-transfers/index.html index 5253815..d979627 100644 --- a/coding/two-phase-transfers/index.html +++ b/coding/two-phase-transfers/index.html @@ -6,7 +6,7 @@ Two-Phase Transfers | TigerBeetle Docs - + @@ -57,7 +57,7 @@ id of the first transfer. The id of the second transfer will be unique, not the same id as the initial pending transfer.

    Examples

    The following examples show the state of two accounts in three steps:

    1. Initially, before any transfers
    2. After a pending transfer
    3. And after the pending transfer is posted or voided

    Post Full Pending Amount

    Account AAccount BTransfers
    debitscredits
    pendingpostedpendingposteddebit_account_idcredit_account_idamountflags
    wxyz----
    123 + wx123 + yzAB123pending
    w123 + xy123 + zAB123post_pending_transfer

    Post Partial Pending Amount

    Account AAccount BTransfers
    debitscredits
    pendingpostedpendingposteddebit_account_idcredit_account_idamountflags
    wxyz----
    123 + wx123 + yzAB123pending
    w100 + xy100 + zAB100post_pending_transfer

    Void Pending Transfer

    Account AAccount BTransfers
    debitscredits
    pendingpostedpendingposteddebit_account_idcredit_account_idamountflags
    wxyz----
    123 + wx123 + yzAB123pending
    wxyzAB123void_pending_transfer

    Client Documentation

    Read more about how two-phase transfers work with each client.

    Client Samples

    Or take a look at how it works with real code.

    - + \ No newline at end of file diff --git a/index.html b/index.html index 063960d..2fb1d12 100644 --- a/index.html +++ b/index.html @@ -6,7 +6,7 @@ TigerBeetle Docs | TigerBeetle Docs - + @@ -17,7 +17,7 @@ architecture, approach to safety and performance in the About section.

    Contributing

    Community

    - + \ No newline at end of file diff --git a/operating/deploy/index.html b/operating/deploy/index.html index a9c57ba..31d4447 100644 --- a/operating/deploy/index.html +++ b/operating/deploy/index.html @@ -6,7 +6,7 @@ Deploying for Production | TigerBeetle Docs - + @@ -30,7 +30,7 @@ replicated across sites before being committed.

    Hardware Fault Tolerance

    It is important to ensure independent fault domains for each replica's data file, that each replica's data file is stored on a separate disk (required), machine (required), rack (recommended), data center (recommended) etc.

    - + \ No newline at end of file diff --git a/operating/docker/index.html b/operating/docker/index.html index 311060d..d510607 100644 --- a/operating/docker/index.html +++ b/operating/docker/index.html @@ -6,7 +6,7 @@ Docker | TigerBeetle Docs - + @@ -26,7 +26,7 @@ you will need to do one of the following:

    1. Run docker run with --cap-add IPC_LOCK
    2. Run docker run with --ulimit memlock=-1:-1
    3. Or modify the defaults in $HOME/.docker/daemon.json and restart the Docker for Mac application:
    {
    ... other settings ...
    "default-ulimits": {
    "memlock": {
    "Hard": -1,
    "Name": "memlock",
    "Soft": -1
    }
    },
    ... other settings ...
    }

    If you are running TigerBeetle with Docker Compose, you will need to add the IPC_LOCK capability like this:

    ... rest of docker-compose.yml ...

    services:
    tigerbeetle_0:
    image: ghcr.io/tigerbeetle/tigerbeetle
    command: "start --addresses=0.0.0.0:3001,0.0.0.0:3002,0.0.0.0:3003 /data/0_0.tigerbeetle"
    network_mode: host
    cap_add: # HERE
    - IPC_LOCK # HERE
    volumes:
    - ./data:/data

    ... rest of docker-compose.yml ...

    See https://github.com/tigerbeetle/tigerbeetle/issues/92 for discussion.

    - + \ No newline at end of file diff --git a/operating/hardware/index.html b/operating/hardware/index.html index fc25387..6d0b833 100644 --- a/operating/hardware/index.html +++ b/operating/hardware/index.html @@ -6,7 +6,7 @@ Hardware | TigerBeetle Docs - + @@ -24,7 +24,7 @@ allocation and will use exactly how much memory is explicitly allocated to it for caching via command line argument.

    CPU

    TigerBeetle requires only a single core per replica machine. TigerBeetle at present does not utilize more cores, but may in future.

    Multitenancy

    There are no restrictions on sharing a server with other tenant processes.

    - + \ No newline at end of file diff --git a/operating/linux/index.html b/operating/linux/index.html index 83e88f9..260df11 100644 --- a/operating/linux/index.html +++ b/operating/linux/index.html @@ -6,7 +6,7 @@ Deploying on Linux | TigerBeetle Docs - + @@ -17,7 +17,7 @@ single-node cluster, so you may need to adjust it for other cluster configurations.

    See the Quick Start for an example of how to run a single- vs multi-node cluster.

    - + \ No newline at end of file diff --git a/operating/managed-service/index.html b/operating/managed-service/index.html index 4b5f17d..2742356 100644 --- a/operating/managed-service/index.html +++ b/operating/managed-service/index.html @@ -6,7 +6,7 @@ Managed Service | TigerBeetle Docs - + @@ -14,7 +14,7 @@

    Managed Service

    Want to use TigerBeetle in production, along with automated disaster recovery, monitoring, and dedicated support from the TigerBeetle team?

    Let us help you get up and running faster! Contact our CEO, Joran Dirk Greef, at joran@tigerbeetle.com to set up a call.

    - + \ No newline at end of file diff --git a/operating/upgrading/index.html b/operating/upgrading/index.html index 05351de..d8e63bf 100644 --- a/operating/upgrading/index.html +++ b/operating/upgrading/index.html @@ -6,7 +6,7 @@ Upgrading | TigerBeetle Docs - + @@ -38,7 +38,7 @@ to upgrade, with two options:

    • Upgrade the replicas to the latest version. In this case, the clients will stop working for the duration of the upgrade and unavailability will be extended.
    • Upgrade the replicas to the latest release that supports the client version in use, then upgrade the clients to that version. Repeat this until you're on the latest release.
    - + \ No newline at end of file diff --git a/quick-start/index.html b/quick-start/index.html index 59d7b5a..3b3a548 100644 --- a/quick-start/index.html +++ b/quick-start/index.html @@ -6,7 +6,7 @@ Quick Start | TigerBeetle Docs - + @@ -22,7 +22,7 @@ provided.

    You can connect to the REPL as described above try creating accounts and transfers in this cluster.

    You can also read more about deploying TigerBeetle in production.

    Next: Designing for TigerBeetle

    Now that you've created some accounts and transfers, you may want to read about how TigerBeetle fits into your system architecture and dig into the data model.

    - + \ No newline at end of file diff --git a/reference/account-balance/index.html b/reference/account-balance/index.html index 84990f7..f2a8d96 100644 --- a/reference/account-balance/index.html +++ b/reference/account-balance/index.html @@ -6,7 +6,7 @@ AccountBalance | TigerBeetle Docs - + @@ -15,7 +15,7 @@ time.

    Only Accounts with the flag history set retain historical balances.

    Fields

    timestamp

    This is the time the account balance was updated, as nanoseconds since UNIX epoch.

    The timestamp refers to the same Transfer.timestamp which changed the Account.

    The amounts refer to the account balance recorded after the transfer execution.

    Constraints:

    • Type is 64-bit unsigned integer (8 bytes)

    debits_pending

    Amount of pending debits.

    Constraints:

    • Type is 128-bit unsigned integer (16 bytes)

    debits_posted

    Amount of posted debits.

    Constraints:

    • Type is 128-bit unsigned integer (16 bytes)

    credits_pending

    Amount of pending credits.

    Constraints:

    • Type is 128-bit unsigned integer (16 bytes)

    credits_posted

    Amount of posted credits.

    Constraints:

    • Type is 128-bit unsigned integer (16 bytes)

    reserved

    This space may be used for additional data in the future.

    Constraints:

    • Type is 56 bytes
    • Must be zero
    - + \ No newline at end of file diff --git a/reference/account-filter/index.html b/reference/account-filter/index.html index fe73dab..6d217ad 100644 --- a/reference/account-filter/index.html +++ b/reference/account-filter/index.html @@ -6,21 +6,25 @@ AccountFilter | TigerBeetle Docs - +

    AccountFilter

    An AccountFilter is a record containing the filter parameters for querying the account transfers -and the account historical balances.

    Fields

    account_id

    The unique identifier of the account for which the results will be retrieved.

    Constraints:

    • Type is 128-bit unsigned integer (16 bytes)
    • Must not be zero or 2^128 - 1

    timestamp_min

    The minimum Transfer.timestamp from which results will be returned, inclusive range. +and the account historical balances.

    Fields

    account_id

    The unique identifier of the account for which the results will be retrieved.

    Constraints:

    • Type is 128-bit unsigned integer (16 bytes)
    • Must not be zero or 2^128 - 1

    user_data_128

    Filter the results by the field Transfer.user_data_128. +Optional; set to zero to disable the filter.

    Constraints:

    • Type is 128-bit unsigned integer (16 bytes)

    user_data_64

    Filter the results by the field Transfer.user_data_64. +Optional; set to zero to disable the filter.

    Constraints:

    • Type is 64-bit unsigned integer (8 bytes)

    user_data_32

    Filter the results by the field Transfer.user_data_32. +Optional; set to zero to disable the filter.

    Constraints:

    • Type is 32-bit unsigned integer (4 bytes)

    code

    Filter the results by the Transfer.code. +Optional; set to zero to disable the filter.

    Constraints:

    • Type is 16-bit unsigned integer (2 bytes)

    reserved

    This space may be used for additional data in the future.

    Constraints:

    • Type is 58 bytes
    • Must be zero

    timestamp_min

    The minimum Transfer.timestamp from which results will be returned, inclusive range. Optional; set to zero to disable the lower-bound filter.

    Constraints:

    • Type is 64-bit unsigned integer (8 bytes)
    • Must be less than 2^63.

    timestamp_max

    The maximum Transfer.timestamp from which results will be returned, inclusive range. Optional; set to zero to disable the upper-bound filter.

    Constraints:

    • Type is 64-bit unsigned integer (8 bytes)
    • Must be less than 2^63.

    limit

    The maximum number of results that can be returned by this query.

    Limited by the maximum message size.

    Constraints:

    • Type is 32-bit unsigned integer (4 bytes)
    • Must not be zero

    flags

    A bitfield that specifies querying behavior.

    Constraints:

    • Type is 32-bit unsigned integer (4 bytes)

    flags.debits

    Whether or not to include results where the field debit_account_id matches the parameter account_id.

    flags.credits

    Whether or not to include results where the field credit_account_id matches the parameter account_id.

    flags.reversed

    Whether the results are sorted by timestamp in chronological or reverse-chronological order. If the flag is not set, the event that happened first (has the smallest timestamp) will come first. If the -flag is set, the event that happened last (has the largest timestamp) will come first.

    reserved

    This space may be used for additional data in the future.

    Constraints:

    • Type is 24 bytes
    • Must be zero
    - +flag is set, the event that happened last (has the largest timestamp) will come first.

    + \ No newline at end of file diff --git a/reference/account/index.html b/reference/account/index.html index e4f79df..df38858 100644 --- a/reference/account/index.html +++ b/reference/account/index.html @@ -6,7 +6,7 @@ Account | TigerBeetle Docs - + @@ -63,7 +63,7 @@ Search for const Account = extern struct {.

    You can find the source code for creating an account in src/state_machine.zig. Search for fn create_account(.

    - + \ No newline at end of file diff --git a/reference/query-filter/index.html b/reference/query-filter/index.html index 3f1774a..01d9590 100644 --- a/reference/query-filter/index.html +++ b/reference/query-filter/index.html @@ -6,7 +6,7 @@ QueryFilter | TigerBeetle Docs - + @@ -32,7 +32,7 @@ Optional; set to zero to disable the upper-bound filter.

    Constraints:

    • Type is 64-bit unsigned integer (8 bytes)
    • Must not be 2^64 - 1

    limit

    The maximum number of results that can be returned by this query.

    Limited by the maximum message size.

    Constraints:

    • Type is 32-bit unsigned integer (4 bytes)
    • Must not be zero

    flags

    A bitfield that specifies querying behavior.

    Constraints:

    • Type is 32-bit unsigned integer (4 bytes)

    flags.reversed

    Whether the results are sorted by timestamp in chronological or reverse-chronological order. If the flag is not set, the event that happened first (has the smallest timestamp) will come first. If the flag is set, the event that happened last (has the largest timestamp) will come first.

    reserved

    This space may be used for additional data in the future.

    Constraints:

    • Type is 6 bytes
    • Must be zero
    - + \ No newline at end of file diff --git a/reference/requests/create_accounts/index.html b/reference/requests/create_accounts/index.html index 068ed5e..6fdabf8 100644 --- a/reference/requests/create_accounts/index.html +++ b/reference/requests/create_accounts/index.html @@ -6,7 +6,7 @@ create_accounts | TigerBeetle Docs - + @@ -40,7 +40,7 @@ regressed, but it must be greater than the last timestamp assigned to any Account in the cluster and cannot be equal to the timestamp of any existing Transfer.

    Client libraries

    For language-specific docs see:

    Internals

    If you're curious and want to learn more, you can find the source code for creating an account in src/state_machine.zig. Search for fn create_account( and fn execute(.

    - + \ No newline at end of file diff --git a/reference/requests/create_transfers/index.html b/reference/requests/create_transfers/index.html index 26dd2d4..d41c714 100644 --- a/reference/requests/create_transfers/index.html +++ b/reference/requests/create_transfers/index.html @@ -6,7 +6,7 @@ create_transfers | TigerBeetle Docs - + @@ -140,7 +140,7 @@ credit_account.debits_posted.

    Client libraries

    For language-specific docs see:

    Internals

    If you're curious and want to learn more, you can find the source code for creating a transfer in src/state_machine.zig. Search for fn create_transfer( and fn execute(.

    - + \ No newline at end of file diff --git a/reference/requests/get_account_balances/index.html b/reference/requests/get_account_balances/index.html index dd475f5..82450a6 100644 --- a/reference/requests/get_account_balances/index.html +++ b/reference/requests/get_account_balances/index.html @@ -6,7 +6,7 @@ get_account_balances | TigerBeetle Docs - + @@ -19,7 +19,7 @@ See AccountFilter for constraints.

    Result

    • If the account has the flag history set and any matching balances exist, return an array of AccountBalances.
    • If the account does not have the flag history set, return nothing.
    • If no matching balances exist, return nothing.
    • If any constraint is violated, return nothing.

    Client libraries

    For language-specific docs see:

    - + \ No newline at end of file diff --git a/reference/requests/get_account_transfers/index.html b/reference/requests/get_account_transfers/index.html index c4aee3a..75fd4b9 100644 --- a/reference/requests/get_account_transfers/index.html +++ b/reference/requests/get_account_transfers/index.html @@ -6,7 +6,7 @@ get_account_transfers | TigerBeetle Docs - + @@ -16,7 +16,7 @@ reversed to change this.
  • The result is always limited in size. If there are more results, you need to page through them using the AccountFilter's timestamp_min and/or timestamp_max.
  • Client libraries

    For language-specific docs see:

    - + \ No newline at end of file diff --git a/reference/requests/index.html b/reference/requests/index.html index 9967f60..7d6a025 100644 --- a/reference/requests/index.html +++ b/reference/requests/index.html @@ -6,7 +6,7 @@ Requests | TigerBeetle Docs - + @@ -49,7 +49,7 @@ timestamps are unique and strictly increasing. No two objects within the same cluster will have the same timestamp. Furthermore, the order of the timestamps indicates the order in which the objects were committed. - + \ No newline at end of file diff --git a/reference/requests/lookup_accounts/index.html b/reference/requests/lookup_accounts/index.html index e70130f..7c212d3 100644 --- a/reference/requests/lookup_accounts/index.html +++ b/reference/requests/lookup_accounts/index.html @@ -6,7 +6,7 @@ lookup_accounts | TigerBeetle Docs - + @@ -20,7 +20,7 @@ balance-conditional transfers.

    Event

    An id belonging to a Account.

    Result

    • If the account exists, return the Account.
    • If the account does not exist, return nothing.

    Client libraries

    For language-specific docs see:

    Internals

    If you're curious and want to learn more, you can find the source code for looking up an account in src/state_machine.zig. Search for fn execute_lookup_accounts(.

    - + \ No newline at end of file diff --git a/reference/requests/lookup_transfers/index.html b/reference/requests/lookup_transfers/index.html index fba12c0..42d80bc 100644 --- a/reference/requests/lookup_transfers/index.html +++ b/reference/requests/lookup_transfers/index.html @@ -6,7 +6,7 @@ lookup_transfers | TigerBeetle Docs - + @@ -15,7 +15,7 @@ for looking up a transfer in src/state_machine.zig. Search for fn execute_lookup_transfers(.

    - + \ No newline at end of file diff --git a/reference/requests/query_accounts/index.html b/reference/requests/query_accounts/index.html index ff3e3cf..f89ce69 100644 --- a/reference/requests/query_accounts/index.html +++ b/reference/requests/query_accounts/index.html @@ -6,7 +6,7 @@ query_accounts | TigerBeetle Docs - + @@ -16,7 +16,7 @@ reversed to change this.
  • The result is always limited in size. If there are more results, you need to page through them using the QueryFilter's timestamp_min and/or timestamp_max.
  • Client libraries

    For language-specific docs see:

    - + \ No newline at end of file diff --git a/reference/requests/query_transfers/index.html b/reference/requests/query_transfers/index.html index 7428e0f..deab3ec 100644 --- a/reference/requests/query_transfers/index.html +++ b/reference/requests/query_transfers/index.html @@ -6,7 +6,7 @@ query_transfers | TigerBeetle Docs - + @@ -16,7 +16,7 @@ reversed to change this.
  • The result is always limited in size. If there are more results, you need to page through them using the QueryFilter's timestamp_min and/or timestamp_max.
  • Client libraries

    For language-specific docs see:

    - + \ No newline at end of file diff --git a/reference/sessions/index.html b/reference/sessions/index.html index cacbea2..a0c7d64 100644 --- a/reference/sessions/index.html +++ b/reference/sessions/index.html @@ -6,7 +6,7 @@ Client Sessions | TigerBeetle Docs - + @@ -44,7 +44,7 @@ updates for which the corresponding reply was not received prior to the restart. Those updates may occur at any point in the future, or never. Handling application crash recovery safely requires using ids to idempotently retry events. - + \ No newline at end of file diff --git a/reference/transfer/index.html b/reference/transfer/index.html index eb89232..aed65cf 100644 --- a/reference/transfer/index.html +++ b/reference/transfer/index.html @@ -6,7 +6,7 @@ Transfer | TigerBeetle Docs - + @@ -142,7 +142,7 @@ Search for const Transfer = extern struct {.

    You can find the source code for creating a transfer in src/state_machine.zig. Search for fn create_transfer(.

    - + \ No newline at end of file diff --git a/search/index.html b/search/index.html index 4faae9c..e7680b1 100644 --- a/search/index.html +++ b/search/index.html @@ -6,13 +6,13 @@ Search the documentation | TigerBeetle Docs - +

    Search the documentation

    - + \ No newline at end of file