{"id":16685944,"url":"https://github.com/dehesa/package-conbini","last_synced_at":"2025-12-11T22:57:25.836Z","repository":{"id":63907803,"uuid":"217179298","full_name":"dehesa/package-conbini","owner":"dehesa","description":"Publishers, operators, and subscribers to supplement Combine.","archived":false,"fork":false,"pushed_at":"2024-05-07T19:57:08.000Z","size":198,"stargazers_count":138,"open_issues_count":0,"forks_count":4,"subscribers_count":5,"default_branch":"main","last_synced_at":"2025-10-29T21:31:51.244Z","etag":null,"topics":["apple","combine","combine-framework","ios","macos","reactive-programming","swift","swiftui","tvos","watchos","xctest"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dehesa.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"docs/CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"docs/CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":"docs/SUPPORT.md","governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-10-24T00:40:51.000Z","updated_at":"2025-04-29T09:20:48.000Z","dependencies_parsed_at":"2024-05-07T20:43:48.459Z","dependency_job_id":null,"html_url":"https://github.com/dehesa/package-conbini","commit_stats":null,"previous_names":["dehesa/package-conbini","dehesa/conbini"],"tags_count":13,"template":false,"template_full_name":null,"purl":"pkg:github/dehesa/package-conbini","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dehesa%2Fpackage-conbini","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dehesa%2Fpackage-conbini/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dehesa%2Fpackage-conbini/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dehesa%2Fpackage-conbini/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dehesa","download_url":"https://codeload.github.com/dehesa/package-conbini/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dehesa%2Fpackage-conbini/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":27672064,"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-12-11T02:00:11.302Z","response_time":56,"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":["apple","combine","combine-framework","ios","macos","reactive-programming","swift","swiftui","tvos","watchos","xctest"],"created_at":"2024-10-12T15:03:55.115Z","updated_at":"2025-12-11T22:57:25.819Z","avatar_url":"https://github.com/dehesa.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n    \u003cimg src=\"docs/assets/Conbini.svg\" alt=\"Conbini icon\"/\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n    \u003cimg src=\"docs/assets/badges/Swift.svg\" alt=\"Swift 5.2\"\u003e\n    \u003cimg src=\"docs/assets/badges/Apple.svg\" alt=\"macOS 10.15+ - iOS 13+ - tvOS 13+ - watchOS 6+\"\u003e\n    \u003ca href=\"http://doge.mit-license.org\"\u003e\u003cimg src=\"docs/assets/badges/License.svg\" alt=\"MIT License\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\nConbini provides convenience `Publisher`s, operators, and `Subscriber`s to squeeze the most out of Apple's [Combine framework](https://developer.apple.com/documentation/combine).\n\n# Usage\n\nTo use this library, you need to:\n\n\u003cul\u003e\n\u003cdetails\u003e\u003csummary\u003eAdd \u003ccode\u003eConbini\u003c/code\u003e to your project through \u003ca href=\"https://github.com/apple/swift-package-manager/tree/master/Documentation\"\u003eSPM\u003c/a\u003e.\u003c/summary\u003e\u003cp\u003e\n\n```swift\n// swift-tools-version:5.2\nimport PackageDescription\n\nlet package = Package(\n    /* Your package name, supported platforms, and generated products go here */\n    dependencies: [\n        .package(url: \"https://github.com/dehesa/package-conbini.git\", from: \"0.6.2\")\n    ],\n    targets: [\n        .target(name: /* Your target name here */, dependencies: [\"Conbini\"])\n    ]\n)\n```\n\nIf you want to use Conbini's [testing](#testing) extension, you need to define the `CONBINI_FOR_TESTING` flag on your SPM targets or testing targets. Conbini testing extensions require `XCTest`, which is not available in runtime on some platforms (such as watchOS), or you may not want to link to such dynamic library (e.g. when building command-line tools).\n\n```swift\ntargets: [\n    .testTarget(name: /* Your target name here */, dependencies: [\"Conbini\"], swiftSettings: [.define(\"CONBINI_FOR_TESTING\")])\n]\n```\n\n\u003c/p\u003e\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003eImport \u003ccode\u003eConbini\u003c/code\u003e in the file that needs it.\u003c/summary\u003e\u003cp\u003e\n\n```swift\nimport Conbini\n```\n\nThe testing conveniences depend on [XCTest](https://developer.apple.com/documentation/xctest), which is not available on regular execution. That is why Conbini is offered in two flavors:\n\n-   `import Conbini` includes all code excepts the testing conveniences.\n-   `import ConbiniForTesting` includes the testing functionality only.\n\n\u003c/p\u003e\u003c/details\u003e\n\u003c/ul\u003e\n\n## Operators\n\nPublisher Operators:\n\n\u003cul\u003e\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003ehandleEnd(_:)\u003c/code\u003e\u003c/summary\u003e\u003cp\u003e\n\nExecutes (only once) the provided closure when the publisher completes (whether successfully or with a failure) or when the publisher gets cancelled.\n\nIt performs the same operation that the standard `handleEvents(receiveSubscription:receiveOutput:receiveCompletion:receiveCancel:receiveRequest:)` would perform if you add similar closures to `receiveCompletion` and `receiveCancel`.\n\n```swift\nlet publisher = upstream.handleEnd { (completion) in\n    switch completion {\n    case .none: // The publisher got cancelled.\n    case .finished: // The publisher finished successfully.\n    case .failure(let error): // The publisher generated an error.\n    }\n}\n```\n\n\u003c/p\u003e\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003eretry(on:intervals:)\u003c/code\u003e\u003c/summary\u003e\u003cp\u003e\n\nAttempts to recreate a failed subscription with the upstream publisher a given amount of times waiting the specified number of seconds between failed attempts.\n\n```swift\nlet apiCallPublisher.retry(on: queue, intervals: [0.5, 2, 5])\n// Same functionality to retry(3), but waiting between attemps 0.5, 2, and 5 seconds after each failed attempt.\n```\n\nThis operator accept any scheduler conforming to `Scheduler` (e.g. `DispatchQueue`, `RunLoop`, etc). You can also optionally tweak the tolerance and scheduler operations.\n\n\u003c/p\u003e\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003ethen(maxDemand:_:)\u003c/code\u003e\u003c/summary\u003e\u003cp\u003e\n\nIgnores all values and executes the provided publisher once a successful completion is received. If a failed completion is emitted, it is forwarded downstream.\n\n```swift\nlet publisher = setConfigurationOnServer.then {\n    subscribeToWebsocket.publisher\n}\n```\n\nThis operator optionally lets you control backpressure with its `maxDemand` parameter. The parameter behaves like `flatMap`'s `maxPublishers`, which specifies the maximum demand requested to the upstream at any given time.\n\n\u003c/p\u003e\u003c/details\u003e\n\u003c/ul\u003e\n\nSubscriber Operators:\n\n\u003cul\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003eassign(to:on:)\u003c/code\u003e variants.\u003c/summary\u003e\u003cp\u003e\n\nCombine's `assign(to:on:)` operation creates memory cycles when the \"on\" object also holds the publisher's cancellable. A common situation happens when assigning a value to `self`.\n\n```swift\nclass CustomObject {\n    var value: Int = 0\n    var cancellable: AnyCancellable? = nil\n\n    func performOperation() {\n        cancellable = numberPublisher.assign(to: \\.value, on: self)\n    }\n}\n```\n\nConbini's `assign(to:onWeak:)` operator points to the given object weakly with the added benefit of cancelling the pipeline when the object is deinitialized.\n\nConbini also introduces the `assign(to:onUnowned:)` operator which also avoids memory cycles, but uses `unowned` instead.\n\n\u003c/p\u003e\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003eawait\u003c/code\u003e\u003c/summary\u003e\u003cp\u003e\n\nWait synchronously for the response of the receiving publisher.\n\n```swift\nlet publisher = Just(\"Hello\")\n    .delay(for: 2, scheduler: DispatchQueue.global())\n\nlet greeting = publisher.await\n```\n\nThe synchronous wait is performed through `DispatchGroup`s. Please, consider where are you using `await`, since the executing queue stops and waits for an answer:\n- Never call this property from `DispatchQueue.main` or any other queue who is performing any background tasks.\n- Awaiting publishers should never process events in the same queue as the executing queue (or the queue will become stalled).\n\n\u003c/p\u003e\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003einvoke(_:on:)\u003c/code\u003e variants.\u003c/summary\u003e\u003cp\u003e\n\nThis operator calls the specified function on the given value/reference passing the upstream value.\n\n```swift\nstruct Custom {\n    func performOperation(_ value: Int) { /* do something */ }\n}\n\nlet instance = Custom()\nlet cancellable = [1, 2, 3].publisher.invoke(Custom.performOperation, on: instance)\n```\n\nConbini also offers the variants `invoke(_:onWeak:)` and `invoke(_:onUnowned:)`, which avoid memory cycles on reference types.\n\n\u003c/p\u003e\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003eresult(onEmpty:_:)\u003c/code\u003e\u003c/summary\u003e\u003cp\u003e\n\nIt subscribes to the receiving publisher and executes the provided closure when a value is received. In case of failure, the handler is executed with such failure.\n\n```swift\nlet cancellable = serverRequest.result { (result) in\n    switch result {\n    case .success(let value): ...\n    case .failure(let error): ...\n    }\n}\n```\n\nThe operator lets you optionally generate an error (which will be consumed by your `handler`) for cases where upstream completes without a value.\n\n\u003c/p\u003e\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003esink(fixedDemand:)\u003c/code\u003e\u003c/summary\u003e\u003cp\u003e\n\nIt subscribes upstream and request exactly `fixedDemand` values (after which the subscriber completes). The subscriber may receive zero to `fixedDemand` of values before completing, but never more than that.\n\n```swift\nlet cancellable = upstream.sink(fixedDemand: 5, receiveCompletion: { (completion) in ... }) { (value) in ... }\n```\n\n\u003c/p\u003e\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003esink(maxDemand:)\u003c/code\u003e\u003c/summary\u003e\u003cp\u003e\n\nIt subscribes upstream requesting `maxDemand` values and always keeping the same backpressure.\n\n```swift\nlet cancellable = upstream.sink(maxDemand: 3) { (value) in ... }\n```\n\n\u003c/p\u003e\u003c/details\u003e\n\u003c/ul\u003e\n\n## Publishers\n\n\u003cul\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003eDeferred\u003c/code\u003e variants.\u003c/summary\u003e\u003cp\u003e\n\nThese publishers accept a closure that is executed once a _greater-than-zero_ demand is requested. There are several flavors:\n\n\u003cul\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003eDeferredValue\u003c/code\u003e emits a single value and then completes.\u003c/summary\u003e\u003cp\u003e\n\nThe value is not provided/cached, but instead a closure will generate it. The closure is executed once a positive subscription is received.\n\n```swift\nlet publisher = DeferredValue\u003cInt,CustomError\u003e {\n    return intenseProcessing()\n}\n```\n\nA `Try` variant is also offered, enabling you to `throw` from within the closure. It loses the concrete error type (i.e. it gets converted to `Swift.Error`).\n\n\u003c/p\u003e\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003eDeferredResult\u003c/code\u003e forwards downstream a value or a failure depending on the generated \u003ccode\u003eResult\u003c/code\u003e.\u003c/summary\u003e\u003cp\u003e\n\n```swift\nlet publisher = DeferredResult {\n    guard someExpression else { return .failure(CustomError()) }\n    return .success(someValue)\n}\n```\n\n\u003c/p\u003e\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003eDeferredComplete\u003c/code\u003e forwards a completion event (whether success or failure).\u003c/summary\u003e\u003cp\u003e\n\n```swift\nlet publisher = DeferredComplete {\n    return errorOrNil\n}\n```\n\nA `Try` variant is also offered, enabling you to `throw` from within the closure; but it loses the concrete error type (i.e. gets converted to `Swift.Error`).\n\n\u003c/p\u003e\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003eDeferredPassthrough\u003c/code\u003e provides a passthrough subject in a closure to be used to send values downstream.\u003c/summary\u003e\u003cp\u003e\n\nIt is similar to wrapping a `Passthrough` subject on a `Deferred` closure, with the diferrence that the `Passthrough` given on the closure is already _wired_ on the publisher chain and can start sending values right away. Also, the memory management is taken care of and every new subscriber receives a new subject (closure re-execution).\n\n```swift\nlet publisher = DeferredPassthrough { (subject) in\n    subject.send(something)\n    subject.send(somethingElse)\n    subject.send(completion: .finished)\n}\n```\n\n\u003c/p\u003e\u003c/details\u003e\n\u003c/ul\u003e\n\nThere are several reason for these publishers to exist instead of using other `Combine`-provided closure such as `Just`, `Future`, or `Deferred`:\n\n-   Combine's `Just` forwards a value immediately and each new subscriber always receive the same value.\n-   Combine's `Future` executes its closure right away (upon initialization) and then cache the returned value. That value is then forwarded for any future subscription.\n    \u003c/br\u003e`Deferred...` publishers await for subscriptions and a _greater-than-zero_ demand before executing. This also means, the closure will re-execute for any new subscriber.\n-   Combine's `Deferred` has similar functionality to Conbini's, but it only accepts a publisher.\n    \u003c/br\u003eThis becomes annoying when compounding operators.\n\n\u003c/p\u003e\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003eDelayedRetry\u003c/code\u003e\u003c/summary\u003e\u003cp\u003e\n\nIt provides the functionality of the `retry(on:intervals:)` operator.\n\n\u003c/p\u003e\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003eThen\u003c/code\u003e\u003c/summary\u003e\u003cp\u003e\n\nIt provides the functionality of the `then` operator.\n\n\u003c/p\u003e\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003eHandleEnd\u003c/code\u003e\u003c/summary\u003e\u003cp\u003e\n\nIt provides the functionality of the `handleEnd(_:)` operator.\n\n\u003c/p\u003e\u003c/details\u003e\n\u003c/ul\u003e\n\nExtra Functionality:\n\n\u003cul\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003ePublishers.PrefetchStrategy\u003c/code\u003e\u003c/summary\u003e\u003cp\u003e\n\nIt has been extended with a `.fatalError(message:file:line:)` option to stop execution if the buffer is filled. This is useful during development and debugging and for cases when you are sure the buffer will never be filled.\n\n```swift\npublisher.buffer(size: 10, prefetch: .keepFull, whenFull: .fatalError())\n```\n\n\u003c/p\u003e\u003c/details\u003e\n\u003c/ul\u003e\n\n## Subscribers\n\n\u003cul\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003eFixedSink\u003c/code\u003e\u003c/summary\u003e\u003cp\u003e\n\nIt requests a fixed amount of values upon subscription and once if has received them all it completes/cancel the pipeline.\nThe values are requested through backpressure, so no more than the allowed amount of values are generated upstream.\n\n```swift\nlet subscriber = FixedSink(demand: 5) { (value) in ... }\nupstream.subscribe(subscriber)\n```\n\n\u003c/p\u003e\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003eGraduatedSink\u003c/code\u003e\u003c/summary\u003e\u003cp\u003e\n\nIt requests a fixed amount of values upon subscription and always keep the same demand by asking one more value upon input reception. The standard `Subscribers.Sink` requests an `.unlimited` amount of values upon subscription. This might not be what we want since some times a control of in-flight values might be desirable (e.g. allowing only _n_ in-flight\\* API calls at the same time).\n\n```swift\nlet subscriber = GraduatedSink(maxDemand: 3) { (value) in ... }\nupstream.subscribe(subscriber)\n```\n\n\u003c/p\u003e\u003c/details\u003e\n\u003c/ul\u003e\n\n\u003e The names for these subscribers are not very good/accurate. Any suggestion is appreciated.\n\n## Testing\n\nConbini provides convenience subscribers to ease code testing. These subscribers make the test wait till a specific expectation is fulfilled (or making the test fail in a negative case). Furthermore, if a timeout ellapses or a expectation is not fulfilled, the affected test line will be marked _in red_ correctly in Xcode.\n\n\u003cul\u003e\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003eexpectsAll(timeout:on:)\u003c/code\u003e\u003c/summary\u003e\u003cp\u003e\n\nIt subscribes to a publisher making the running test wait for zero or more values and a successful completion.\n\n```swift\nlet emittedValues = publisherChain.expectsAll(timeout: 0.8, on: test)\n```\n\n\u003c/p\u003e\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003eexpectsAtLeast(timeout:on:)\u003c/code\u003e\u003c/summary\u003e\u003cp\u003e\n\nIt subscribes to a publisher making the running test wait for at least the provided amount of values. Once the provided amount of values is received, the publisher gets cancelled and the values are returned.\n\n```swift\nlet emittedValues = publisherChain.expectsAtLeast(values: 5, timeout: 0.8, on: test)\n```\n\nThis operator/subscriber accepts an optional closure to check every value received.\n\n```swift\nlet emittedValues = publisherChain.expectsAtLeast(values: 5, timeout: 0.8, on: test) { (value) in\n    XCTAssert...\n}\n```\n\n\u003c/p\u003e\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003eexpectsCompletion(timeout:on:)\u003c/code\u003e\u003c/summary\u003e\u003cp\u003e\n\nIt subscribes to a publisher making the running test wait for a successful completion while ignoring all emitted values.\n\n```swift\npublisherChain.expectsCompletion(timeout: 0.8, on: test)\n```\n\n\u003c/p\u003e\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003eexpectsFailure(timeout:on:)\u003c/code\u003e\u003c/summary\u003e\u003cp\u003e\n\nIt subscribes to a publisher making the running test wait for a failed completion while ignoring all emitted values.\n\n```swift\npublisherChain.expectsFailure(timeout: 0.8, on: test)\n```\n\n\u003c/p\u003e\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003eexpectsOne(timeout:on:)\u003c/code\u003e\u003c/summary\u003e\u003cp\u003e\n\nIt subscribes to a publisher making the running test wait for a single value and a successful completion. If more than one value are emitted or the publisher fails, the subscription gets cancelled and the test fails.\n\n```swift\nlet emittedValue = publisherChain.expectsOne(timeout: 0.8, on: test)\n```\n\n\u003c/p\u003e\u003c/details\u003e\n\u003c/ul\u003e\n\n`XCTestCase` has been _extended_ to support the following functionality.\n\n\u003cul\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003ewait(seconds:)\u003c/code\u003e\u003c/summary\u003e\u003cp\u003e\n\nLocks the receiving test for `interval` amount of seconds.\n\n```swift\nfinal class CustomTests: XCTestCase {\n    func testSomething() {\n        let subject = PassthroughSubject\u003cInt,Never\u003e()\n        let cancellable = subject.sink { print($0) }\n        \n        let queue = DispatchQueue.main\n        queue.asyncAfter(.now() + 1) { subject.send(1) }\n        queue.asyncAfter(.now() + 2) { subject.send(2) }\n\n        self.wait(seconds: 3)\n        cancellable.cancel()\n    }\n}\n```\n\n\u003c/p\u003e\u003c/details\u003e\n\u003c/ul\u003e\n\n# References\n\n-   Apple's [Combine documentation](https://developer.apple.com/documentation/combine).\n-   [The Combine book](https://store.raywenderlich.com/products/combine-asynchronous-programming-with-swift) is an excellent Ray Wenderlich book about the Combine framework.\n-   [Cocoa with love](https://www.cocoawithlove.com) has a great series of articles about the inner workings of Combine: [1](https://www.cocoawithlove.com/blog/twenty-two-short-tests-of-combine-part-1.html), [2](https://www.cocoawithlove.com/blog/twenty-two-short-tests-of-combine-part-2.html), [3](https://www.cocoawithlove.com/blog/twenty-two-short-tests-of-combine-part-3.html).\n-   [OpenCombine](https://github.com/broadwaylamb/OpenCombine) is an open source implementation of Apple's Combine framework.\n-   [CombineX](https://github.com/cx-org/CombineX) is an open source implementation of Apple's Combine framework.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdehesa%2Fpackage-conbini","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdehesa%2Fpackage-conbini","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdehesa%2Fpackage-conbini/lists"}