{"id":19195264,"url":"https://github.com/liftbridge-io/go-liftbridge","last_synced_at":"2025-10-12T04:21:22.556Z","repository":{"id":33644904,"uuid":"132057030","full_name":"liftbridge-io/go-liftbridge","owner":"liftbridge-io","description":"Go client for Liftbridge. https://github.com/liftbridge-io/liftbridge","archived":false,"fork":false,"pushed_at":"2023-04-03T19:39:04.000Z","size":686,"stargazers_count":70,"open_issues_count":13,"forks_count":18,"subscribers_count":10,"default_branch":"master","last_synced_at":"2025-04-15T14:43:42.746Z","etag":null,"topics":["client","cloud-native","go","go-liftbridge","golang","grpc","liftbridge","messaging","nats","pubsub","streaming"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/liftbridge-io.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2018-05-03T22:44:21.000Z","updated_at":"2025-03-23T03:22:56.000Z","dependencies_parsed_at":"2023-11-27T16:03:16.532Z","dependency_job_id":"b7f48419-0a6f-44a0-b37f-efe8fc4ce1aa","html_url":"https://github.com/liftbridge-io/go-liftbridge","commit_stats":{"total_commits":274,"total_committers":16,"mean_commits":17.125,"dds":"0.36496350364963503","last_synced_commit":"47b522309e3e000346c68547dc8b9b1e7b6823a9"},"previous_names":["tylertreat/go-liftbridge"],"tags_count":10,"template":false,"template_full_name":null,"purl":"pkg:github/liftbridge-io/go-liftbridge","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/liftbridge-io%2Fgo-liftbridge","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/liftbridge-io%2Fgo-liftbridge/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/liftbridge-io%2Fgo-liftbridge/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/liftbridge-io%2Fgo-liftbridge/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/liftbridge-io","download_url":"https://codeload.github.com/liftbridge-io/go-liftbridge/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/liftbridge-io%2Fgo-liftbridge/sbom","scorecard":{"id":588949,"data":{"date":"2025-08-11","repo":{"name":"github.com/liftbridge-io/go-liftbridge","commit":"47b522309e3e000346c68547dc8b9b1e7b6823a9"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":1.9,"checks":[{"name":"Code-Review","score":1,"reason":"Found 1/8 approved changesets -- score normalized to 1","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: Apache License 2.0: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 29 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":0,"reason":"23 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GO-2022-0386 / GHSA-62mh-w5cv-p88c / GHSA-j756-f273-xhp4","Warn: Project is vulnerable to: GHSA-2c64-vj8g-vwrq / GHSA-4w5x-x539-ppf5","Warn: Project is vulnerable to: GHSA-h2fg-54x9-5qhq / GHSA-hmm9-r2m2-qg9w","Warn: Project is vulnerable to: GO-2022-0398 / GHSA-gwj5-3vfq-q992","Warn: Project is vulnerable to: GO-2022-0852 / GHSA-jp4j-47f9-2vc3","Warn: Project is vulnerable to: GO-2022-0855 / GHSA-m4jx-6526-vvhm","Warn: Project is vulnerable to: GO-2024-2850 / GHSA-jj54-5q2m-q7pj","Warn: Project is vulnerable to: GO-2022-0307 / GHSA-g6w6-r76c-28j7","Warn: Project is vulnerable to: GO-2024-2980 / GHSA-2h2x-8hh2-mfq8","Warn: Project is vulnerable to: GO-2023-2402 / GHSA-45x7-px36-x8w8","Warn: Project is vulnerable to: GO-2024-3321 / GHSA-v778-237x-gjrc","Warn: Project is vulnerable to: GO-2025-3487 / GHSA-hcg3-q754-cr77","Warn: Project is vulnerable to: GO-2022-0969 / GHSA-69cg-p879-7622","Warn: Project is vulnerable to: GO-2023-1495 / GHSA-fxg5-wq6x-vr4w","Warn: Project is vulnerable to: GO-2022-1144 / GHSA-xrjj-mj9h-534m","Warn: Project is vulnerable to: GO-2023-1571 / GHSA-vvpx-j8f3-3w6h","Warn: Project is vulnerable to: GO-2023-1988 / GHSA-2wrh-6pvc-2jm9","Warn: Project is vulnerable to: GO-2023-2102 / GHSA-4374-p667-p6c8","Warn: Project is vulnerable to: GO-2023-2153 / GHSA-m425-mq94-257g / GHSA-qppj-fm5r-hxr3","Warn: Project is vulnerable to: GO-2024-2687 / GHSA-4v7x-pqxf-cx7m","Warn: Project is vulnerable to: GO-2024-3333","Warn: Project is vulnerable to: GO-2025-3503 / GHSA-qxp5-gwg8-xv66","Warn: Project is vulnerable to: GO-2025-3595 / GHSA-vvgc-356p-c3xw"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-20T21:20:21.689Z","repository_id":33644904,"created_at":"2025-08-20T21:20:21.689Z","updated_at":"2025-08-20T21:20:21.689Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279010257,"owners_count":26084718,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-12T02:00:06.719Z","response_time":53,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["client","cloud-native","go","go-liftbridge","golang","grpc","liftbridge","messaging","nats","pubsub","streaming"],"created_at":"2024-11-09T12:09:20.036Z","updated_at":"2025-10-12T04:21:22.497Z","avatar_url":"https://github.com/liftbridge-io.png","language":"Go","funding_links":[],"categories":["Go"],"sub_categories":[],"readme":"# go-liftbridge [![CircleCI](https://circleci.com/gh/liftbridge-io/go-liftbridge.svg?style=svg)](https://circleci.com/gh/liftbridge-io/go-liftbridge) [![GoDoc](https://godoc.org/github.com/liftbridge-io/go-liftbridge/v2?status.svg)](https://godoc.org/github.com/liftbridge-io/go-liftbridge/v2)\n\nGo client for [Liftbridge](https://github.com/liftbridge-io/liftbridge), a\nsystem that provides lightweight, fault-tolerant message streams for\n[NATS](https://nats.io).\n\nLiftbridge provides the following high-level features:\n\n- Log-based API for NATS\n- Replicated for fault-tolerance\n- Horizontally scalable\n- Wildcard subscription support\n- At-least-once delivery support and message replay\n- Message key-value support\n- Log compaction by key\n\n## Installation\n\n```\n$ go get github.com/liftbridge-io/go-liftbridge/v2\n```\n\n## Basic Usage\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\n\tlift \"github.com/liftbridge-io/go-liftbridge/v2\"\n\t\"golang.org/x/net/context\"\n)\n\nfunc main() {\n\t// Create Liftbridge client.\n\taddrs := []string{\"localhost:9292\", \"localhost:9293\", \"localhost:9294\"}\n\tclient, err := lift.Connect(addrs)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer client.Close()\n\n\t// Create a stream attached to the NATS subject \"foo\".\n\tvar (\n        \tsubject = \"foo\"\n        \tname    = \"foo-stream\"\n\t)\n\tif err := client.CreateStream(context.Background(), subject, name); err != nil {\n\t\tif err != lift.ErrStreamExists {\n\t\t\tpanic(err)\n\t\t}\n\t}\n\t\n\t// Publish a message to \"foo\".\n\tif _, err := client.Publish(context.Background(), name, []byte(\"hello\")); err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Subscribe to the stream starting from the beginning.\n\tctx := context.Background()\n\tif err := client.Subscribe(ctx, name, func(msg *lift.Message, err error) {\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tfmt.Println(msg.Offset(), string(msg.Value()))\n\t}, lift.StartAtEarliestReceived()); err != nil {\n\t\tpanic(err)\n\t}\n\n\t\u003c-ctx.Done()\n}\n```\n\n### Create Stream\n\n[Streams](https://github.com/liftbridge-io/liftbridge/blob/master/documentation/concepts.md#stream)\nare a durable message log attached to a NATS subject. They record messages\npublished to the subject for consumption.\n\nStreams have a few key properties: a subject, which is the corresponding NATS\nsubject, a name, which is a human-readable identifier for the stream, and a\nreplication factor, which is the number of nodes the stream should be\nreplicated to for redundancy.  Optionally, there is a group which is the name\nof a load-balance group for the stream to join. When there are multiple streams\nin the same group, messages will be balanced among them.\n\n```go\n// Create a stream attached to the NATS subject \"foo.*\" that is replicated to\n// all the brokers in the cluster. ErrStreamExists is returned if a stream with\n// the given name already exists.\nclient.CreateStream(context.Background(), \"foo.*\", \"my-stream\", lift.MaxReplication())\n\nWe can also configure different properties of the stream with options, which\nallow overriding the server settings.\n\n// Create a stream and set the rentention to 134217728 bytes (128MB) and enable\n// stream compaction.\nclient.CreateStream(context.Background(), subject, name,\n    lift.RetentionMaxBytes(134217728), lift.CompactEnabled(true))\n```\n\nAlso, the client has the possibility to create a stream with enforced Optimistic Concurrency Control.\nThis will create a stream and enable Optimistic Concurrency Control on that specific stream.\n\n\n```go\n// Create a stream with Optimistic Concurrency Control enabled\nerr = client.CreateStream(context.Background(), \"foo\", stream, lift.OptimisticConcurrencyControl(true))\n\n```\n### Subscription Start/Replay Options\n\n[Subscriptions](https://github.com/liftbridge-io/liftbridge/blob/master/documentation/concepts.md#subscription)\nare how Liftbridge streams are consumed. Clients can choose where to start\nconsuming messages from in a stream. This is controlled using options passed to\nSubscribe. Client can also choose to subscribe from partition's leader (by default)\nor from a random ISR replica\n\n```go\n// Subscribe starting with new messages only.\nclient.Subscribe(ctx, name, func(msg *lift.Message, err error) {\n\tfmt.Println(msg.Offset(), string(msg.Value()))\n})\n\n// Subscribe starting with the most recently published value.\nclient.Subscribe(ctx, name, func(msg *lift.Message, err error) {\n\tfmt.Println(msg.Offset(), string(msg.Value()))\n}, lift.StartAtLatestReceived())\n\n// Subscribe starting with the oldest published value.\nclient.Subscribe(ctx, name, func(msg *lift.Message, err error) {\n\tfmt.Println(msg.Offset(), string(msg.Value()))\n}, lift.StartAtEarliestReceived())\n\n// Subscribe starting at a specific offset.\nclient.Subscribe(ctx, name, func(msg *lift.Message, err error) {\n\tfmt.Println(msg.Offset(), string(msg.Value()))\n}, lift.StartAtOffset(42))\n\n// Subscribe starting at a specific time.\nclient.Subscribe(ctx, name, func(msg *lift.Message, err error) {\n\tfmt.Println(msg.Offset(), string(msg.Value()))\n}, lift.StartAtTime(time.Now()))\n\n// Subscribe starting at a specific amount of time in the past.\nclient.Subscribe(ctx, name, func(msg *lift.Message, err error) {\n\tfmt.Println(msg.Offset(), string(msg.Value()))\n}, lift.StartAtTimeDelta(time.Minute))\n\n// Subscribe to a random ISR replica\n// this helps reduce the work load\n// for partition leader\nclient.Subscribe(ctx, name, func(msg *lift.Message, err error) {\n\tfmt.Println(msg.Offset(), string(msg.Value()))\n}, lift.ReadISRReplica())\n```\n\n### Publishing\n\nThere are two publish APIs provided to make it easy to write messages to\nstreams, `Publish` and `PublishToSubject`. These include a number of options\nfor decorating messages with metadata like a message key and headers as well as\nconfiguring acking behavior from the server.\n\n`Publish` sends a message to a Liftbridge stream. The stream partition that\ngets published to is determined by the provided partition or `Partitioner`\nstrategy passed through `MessageOptions`, if any. If a partition or\n`Partitioner` is not provided, it will publish to the base partition (partition\n0). This partition determines the underlying NATS subject that gets published\nto. To publish directly to a specific NATS subject, use the low-level\n`PublishToSubject` API described below.\n\nKeys are used by Liftbridge's log compaction. When enabled, Liftbridge streams\nwill retain only the last message for a given key.\n\n```go\n// Publish a message with a key and header set.\nclient.Publish(context.Background(), \"foo-stream\", []byte(\"hello\"),\n\tlift.Key([]byte(\"key\"),\n\tlift.Header(\"foo\", []byte(\"bar\")),\n)\n```\n\nAn `AckPolicy` tells the server when to send an ack. If a deadline is provided\nto `Publish`, it will block up to this amount of time waiting for the ack.\n\n```go\nctx, _ := context.WithTimeout(context.Background(), 2*time.Second)\nclient.Publish(ctx, \"foo-stream\", []byte(\"hello\"),\n\tlift.AckPolicyAll(), // Wait for all stream replicas to get the message\n)\n\nctx, _ = context.WithTimeout(context.Background(), 2*time.Second)\nclient.Publish(ctx, \"foo-stream\", []byte(\"hello\"),\n\tlift.AckPolicyLeader(), // Wait for just the stream leader to get the message\n)\n\nclient.Publish(context.Background(), \"foo-stream\", []byte(\"hello\"),\n\tlift.AckPolicyNone(), // Don't send an ack\n)\n```\n\nAlso, on specific streams where Optimistic Concurrency Control is enabled,\nthe client has to publish a message with `ExpectedOffset`. The `ExpectedOffset` is the\noffset that should be on the stream's partition if the message is published successfully.\n\n*Note*: as the error of concurrency is piggybacked via the `Ack`, so `AckPolicy` has to be set\nto use this feature.\n\n*Note*: by default, Liftbridge concurrency control considers the `ExpectedOffset` value -1 to be the signil\nto indicate default behavior: next offset, i.e: simply skip the concurrency control verification. By default,\nto provide compatibility, the client should make sure that this value is always set to -1 when concurrency control is not used.\n\n```golang\n\t// Publish Async with expected offset\n\terr = client.PublishAsync(context.Background(), \"foo\", []byte(\"hello\"),\n\t\tfunc(ack *lift.Ack, err error) {\n\t\t\terrorC \u003c- err\n\t\t},\n\t\tlift.AckPolicyLeader(),\n\t\t// Correct offset is 0 (first message)\n\t\tlift.SetExpectedOffset(0),\n\t)\n\trequire.NoError(t, err)\n\n```\n\n`PublishToSubject` sends a message directly to the provided NATS subject. Note\nthat because this publishes directly to a subject, there may be multiple (or\nno) streams that receive the message. As a result, `MessageOptions` related to\npartitioning will be ignored. To publish at the stream/partition level, use the\nhigh-level `Publish` API described above.\n\n```go\n// Publish a message directly to a NATS subject.\nclient.PublishToSubject(context.Background(), \"foo.bar\", []byte(\"hello\"))\n```\n\nAlternatively, messages can be [published directly to\nNATS](#publishing-directly-with-nats) using a NATS client and Liftbridge\nhelpers detailed below.\n\n#### Publishing Directly with NATS\n\nSince Liftbridge is an extension of [NATS](https://github.com/nats-io/gnatsd),\na [NATS client](https://github.com/nats-io/nats.go) can also be used to publish\nmessages. This means existing NATS publishers do not need any changes for\nmessages to be consumed in Liftbridge.\n\n```go\npackage main\n\nimport \"github.com/nats-io/go-nats\"\n\nfunc main() {\n\t// Connect to NATS.\n\tnc, _ := nats.Connect(nats.DefaultURL)\n\n\t// Publish a message.\n\tnc.Publish(\"foo.bar\", []byte(\"Hello, world!\")) \n\tnc.Flush()\n}\n```\n\nAs shown with the publish APIs above, Liftbridge allows publishers to add\nmetadata to messages, including a key, ack inbox, correlation ID, and ack\npolicy. The message key can be used for stream compaction in Liftbridge.\nAcks are used to guarantee Liftbridge has recorded a message to ensure\nat-least-once delivery. The ack inbox determines a NATS subject to publish\nan acknowledgement to once Liftbridge has committed the message. The\ncorrelation id is used to correlate an ack back to the original message.\nThe ack policy determines when Liftbridge acknowledges the message:\nwhen the stream leader has stored the message, when all replicas have stored\nit, or no ack at all.\n\nThis additional metadata is sent using a message envelope which is a\n[protobuf](https://github.com/liftbridge-io/liftbridge-api). The publish APIs\nhandle this for you, but this client library also provides helper APIs to make\nit easy to create envelopes and deal with acks yourself using a NATS client\ndirectly.\n\n```go\nvar (\n\tackInbox = \"foo.acks\"\n\tcid      = \"some-random-id\"\n)\n\n// Create a message envelope to publish.\nmsg := lift.NewMessage([]byte(\"Hello, world!\"),\n\tlift.Key([]byte(\"foo\")), // Key to set on the message\n\tlift.AckInbox(ackInbox), // Send ack to this NATS subject\n\tlift.AckPolicyAll(),     // Send ack once message is fully replicated\n\tlift.CorrelationID(cid), // Set the ID which will be sent on the ack\n)\n\n// Setup a NATS subscription for acks.\nsub, _ := nc.SubscribeSync(ackInbox)\n\n// Publish the message.\nnc.Publish(\"foo.bar\", msg)\n\n// Wait for ack from Liftbridge.\nresp, _ := sub.NextMsg(5*time.Second)\nack, _ := lift.UnmarshalAck(resp.Data)\nif ack.CorrelationID() == cid {\n\tfmt.Println(\"message acked!\")\n}\n```\n\n### Partitioning\n\nLiftbridge streams are partitioned to allow for increased parallelism. By\ndefault, a stream consists of a single partition, but the number of\npartitions can be configured when the stream is created.\n\n#### Creating Partitioned Streams\n\n```go\n// Create stream with three partitions.\nclient.CreateStream(context.Background(), \"bar\", \"bar-stream\", lift.Partitions(3))\n```\n\nEach partition maps to a NATS subject derived from the base stream subject. For\nexample, the partitions for a stream with three partitions attached to the\nsubject \"bar\" map to the NATS subjects \"bar\", \"bar.1\", and \"bar.2\",\nrespectively.\n\n#### Publishing to Stream Partitions\n\nBy default, clients will publish to the base partition, but this can be\nconfigured by providing a `Partitioner`.\n\n```go\n// Publish to partition based on message key hash.\nclient.Publish(context.Background(), \"bar-stream\", []byte(\"hello\"),\n\tlift.Key([]byte(\"key\")),\n\tlift.PartitionByKey(),\n)\n\n// Publish to partitions in a round-robin fashion.\nclient.Publish(context.Background(), \"bar-stream\", []byte(\"hello\"),\n\tlift.Key([]byte(\"key\")),\n\tlift.PartitionByRoundRobin(),\n)\n\n// Publish to a specific partition.\nclient.Publish(context.Background(), \"bar-stream\", []byte(\"hello\"),\n\tlift.Key([]byte(\"key\")),\n\tlift.ToPartition(1),\n)\n\n// Publish directly to a partition NATS subject.\nclient.PublishToSubject(context.Background(), \"bar.1\", []byte(\"hello\"),\n\tlift.Key([]byte(\"key\")))\n```\n\nA custom `Partitioner` implementation can also be provided to `Publish`.\n`PublishToSubject` will ignore any partition-related `MessageOption`.\n\n#### Subscribing to Stream Partitions\n\nLike publishing, clients will subscribe to the base partition by default.\nHowever, a specific partition to consume from can be specified at subscribe\ntime.\n\n```go\n// Subscribe to a specific partition.\nclient.Subscribe(ctx, \"bar-stream\", func(msg *lift.Message, err error) {\n\tfmt.Println(msg.Offset, string(msg.Value))\n}, lift.Partition(1))\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fliftbridge-io%2Fgo-liftbridge","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fliftbridge-io%2Fgo-liftbridge","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fliftbridge-io%2Fgo-liftbridge/lists"}