{"id":13593122,"url":"https://github.com/cabal-club/cabal-core","last_synced_at":"2025-04-04T14:02:46.402Z","repository":{"id":32467321,"uuid":"133696955","full_name":"cabal-club/cabal-core","owner":"cabal-club","description":"Core database and replication for cabal.","archived":false,"fork":false,"pushed_at":"2023-10-18T18:46:58.000Z","size":675,"stargazers_count":302,"open_issues_count":25,"forks_count":43,"subscribers_count":24,"default_branch":"master","last_synced_at":"2024-10-29T18:47:56.480Z","etag":null,"topics":["p2p","replication","swarm"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cabal-club.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null},"funding":{"github":null,"patreon":null,"open_collective":"cabal-club","ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":null}},"created_at":"2018-05-16T16:48:11.000Z","updated_at":"2024-06-04T20:06:34.000Z","dependencies_parsed_at":"2023-12-18T02:13:15.838Z","dependency_job_id":null,"html_url":"https://github.com/cabal-club/cabal-core","commit_stats":{"total_commits":378,"total_committers":23,"mean_commits":"16.434782608695652","dds":0.7063492063492063,"last_synced_commit":"6fe78849aaf7122250f53ed5df2025fe49f1fc11"},"previous_names":["cabal-club/cabal-node"],"tags_count":61,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cabal-club%2Fcabal-core","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cabal-club%2Fcabal-core/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cabal-club%2Fcabal-core/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cabal-club%2Fcabal-core/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cabal-club","download_url":"https://codeload.github.com/cabal-club/cabal-core/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246034289,"owners_count":20712855,"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","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":["p2p","replication","swarm"],"created_at":"2024-08-01T16:01:16.771Z","updated_at":"2025-03-28T13:07:58.623Z","avatar_url":"https://github.com/cabal-club.png","language":"JavaScript","funding_links":["https://opencollective.com/cabal-club"],"categories":["JavaScript"],"sub_categories":[],"readme":"# cabal-core\n\nCore database, replication, swarming, and chat APIs for cabal.\n\n## Table of Contents\n\n\u003cdetails\u003e\u003csummary\u003eClick to expand\u003c/summary\u003e\n\n- [Usage](#usage)\n- [API](#api)\n  - [`var cabal = Cabal([storage][, key][, opts])`](#var-cabal--cabalstorage-key-opts)\n  - [`cabal.getLocalKey(cb)`](#cabalgetlocalkeycb)\n  - [`var ds = cabal.replicate(isInitiator[, opts])`](#var-ds--cabalreplicateisinitiator-opts)\n  - [`cabal.ready(cb)`](#cabalreadycb)\n  - [`cabal.close(cb)`](#cabalclosecb)\n  - [`cabal.getMessage(key, cb)`](#cabalgetmessagekey-cb)\n  - [Channels](#channels)\n    - [`cabal.channels.get(function (error, channels) {})`](#cabalchannelsgetfunction-error-channels-)\n    - [`cabal.channels.events.on('add', function (channel) {})`](#cabalchannelseventsonadd-function-channel-)\n  - [Messages](#messages)\n    - [`var rs = cabal.messages.read(channel, opts)`](#var-rs--cabalmessagesreadchannel-opts)\n    - [`cabal.messages.events.on('message', fn)`](#cabalmessageseventsonmessage-fn)\n    - [`cabal.messages.events.on(channel, fn)`](#cabalmessageseventsonchannel-fn)\n  - [Network](#network)\n    - [`cabal.swarm(cb)`](#cabalswarmcb)\n    - [`cabal.on('peer-added', function (key) {})`](#cabalonpeer-added-function-key-)\n    - [`cabal.on('peer-dropped', function (key) {})`](#cabalonpeer-dropped-function-key-)\n- [Moderation](#moderation)\n  - - [`cabal.moderation.listByFlag({ channel, flag })`](#cabalmoderationlistbyflag-channel-flag-)\n    - [`cabal.moderation.list(cb)`](#cabalmoderationlistcb)\n    - [`cabal.moderation.listBlocks(channel, cb)`](#cabalmoderationlistblockschannel-cb)\n    - [`cabal.moderation.listHides(channel, cb)`](#cabalmoderationlisthideschannel-cb)\n    - [`cabal.moderation.listMutes(channel, cb)`](#cabalmoderationlistmuteschannel-cb)\n    - [`cabal.moderation.listModerationBy(key, cb)`](#cabalmoderationlistmoderationbykey-cb)\n    - [`cabal.moderation.getFlags({ id, channel }, cb)`](#cabalmoderationgetflags-id-channel--cb)\n    - [`cabal.moderation.setFlags({ id, channel, flags }, cb)`](#cabalmoderationsetflags-id-channel-flags--cb)\n    - [`cabal.moderation.addFlags({ id, channel, flags }, cb)`](#cabalmoderationaddflags-id-channel-flags--cb)\n    - [`cabal.moderation.removeFlags({ id, channel, flags }, cb)`](#cabalmoderationremoveflags-id-channel-flags--cb)\n    - [`cabal.moderation.events.on('update', function (update) {})`](#cabalmoderationeventsonupdate-function-update-)\n    - [`cabal.moderation.events.on('skip', function (skip) {})`](#cabalmoderationeventsonskip-function-skip-)\n  - [Private Messages](#private-messages)\n    - [`cabal.publishPrivate(message, recipientKey, cb)`](#cabalpublishprivatemessage-recipientkey-cb)\n    - [`cabal.privateMessages.list(cb)`](#cabalprivatemessageslistcb)\n    - [`var rs = cabal.privateMessages.read(channel, opts)`](#var-rs--cabalprivatemessagesreadchannel-opts)\n    - [`cabal.privateMessages.events.on('message', fn)`](#cabalprivatemessageseventsonmessage-fn)\n    - [`cabal.privateMessages.events.on(publicKey, fn)`](#cabalprivatemessageseventsonpublickey-fn)\n  - [Publishing](#publishing)\n    - [`cabal.publish(message, opts, cb)`](#cabalpublishmessage-opts-cb)\n    - [chat/text](#chattext)\n- [Developing](#developing)\n  - [Changelog](#changelog)\n- [License](#license)\n\n\u003c/details\u003e\n\n## Usage\n\n```\nnpm install cabal-core\n```\n\n## API\n\n\u003e var Cabal = require('cabal-core')\n\n### `var cabal = Cabal([storage][, key][, opts])`\n\nCreate a cabal p2p database using storage `storage`, which must be either a\nstring (filepath to directory on disk) or an instance of\n[random-access-storage](https://github.com/random-access-storage/).\n\n`key` is a cabal key, as a string or Buffer.\n\nIf this is a new cabal, `key` can be omitted and will be generated.\n\nYou can pass `opts.db` as a levelup or leveldown instance to use persistent\nstorage for indexing instead of using memory. For example:\n\n```js\nvar level = require('level')\nvar cabal = Cabal(storage, key, { db: level('/tmp/bot.db') })\n```\n\nOther `opts` include:\n\n- `opts.modKeys`: an array of keys to be considered moderators from this user's perspective.\n- `opts.adminKeys`: an array of keys to be considered administrators from this user's perspective.\n\n### `cabal.getLocalKey(cb)`\n\nReturns the local user's key (as a hex string).\n\n### `var ds = cabal.replicate(isInitiator[, opts])`\n\nCreates a new, live replication stream. This duplex stream can be piped into any\ntransport expressed as a node stream (tcp, websockets, udp, utp, etc).\n\nEnsure that `isInitiator` to `true` to one side, and `false` on the other. This is necessary for setting up the encryption mechanism.\n\n`opts` are passed down into the underlying `hypercore` replication.\n\n### `cabal.ready(cb)`\n\nCalls `cb()` when the underlying indexes are caught up.\n\n### `cabal.close(cb)`\n\nCalls `cb()` when the cabal and its resources have been closed. This also leaves the swarm, if joined.\n\n### `cabal.getMessage(key, cb)`\n\nRead a message from `key`, a string of `feedKey@seq` or an object of\n`{ key, seq }` as `cb(err, node)` from the underlying hypercore.\n\n### Channels\n\n#### `cabal.channels.get(function (error, channels) {})`\n\nRetrieve a list of all channel names that exist in this cabal.\n\n#### `cabal.channels.events.on('add', function (channel) {})`\n\nEmitted when a new channel is added to the cabal.\n\n### Messages\n\n#### `var rs = cabal.messages.read(channel, opts)`\n\nReturns a readable stream of messages (most recent first) from a channel.\n\nPass `opts.limit` to set a maximum number of messages to read.\n\n#### `cabal.messages.events.on('message', fn)`\n\nCalls `fn` with every new message that arrives, regardless of channel.\n\n#### `cabal.messages.events.on(channel, fn)`\n\nCalls `fn` with every new message that arrives in `channel`.\n\n### Network\n\n\u003e var swarm = require('cabal-core/swarm')\n\n#### `cabal.swarm(cb)`\n\nJoins the P2P swarm for a cabal. This seeks out peers who are also part of this cabal by various means (internet, local network), connects to them, and replicates cabal messages between them.\n\nThe returned object is an instance of [discovery-swarm](https://github.com/mafintosh/discovery-swarm).\n\n#### `cabal.on('peer-added', function (key) {})`\n\nEmitted when you connect to a peer. `key` is a hex string of their public key.\n\n#### `cabal.on('peer-dropped', function (key) {})`\n\nEmitted when you lose a connection to a peer. `key` is a hex string of their\npublic key.\n\n## Moderation\n\nCabal has a _subjective moderation system_.\n\nThe three roles are \"admin\", \"moderator\", and \"ban/key\".\n\nAny admin/mod/ban operation can be per-channel, or cabal-wide (the `@` group).\n\nEvery user sees themselves as an administrator across the entire cabal. This\nmeans they can grant admin or moderator powers to anyone, and ban anyone, but\nonly they will see its affects on their own computer. That is, until someone\n_adds_ them as an administrator or moderation from _their_ perspective.\n\nA cabal can be instantiated with a _moderation key_. This is an additional key\nto have your local node consider a user (the user whose key matches the\nmoderation key) as a cabal-wide administrator (in addition to yourself).\n\nThis means that if a group of people all specify the same _moderation key_,\nthey will collectively see the same set of administrators, moderators, and\nbanned users.\n\n#### `cabal.moderation.listByFlag({ channel, flag })`\n\nReturn a readable object stream of records for `channel` that for each user with\n`flag` set. Flags used by cabal-core include: \"hide\", \"mute\", \"block\", \"admin\",\nand \"mod\".\n\nEach `row` object in the output stream has:\n\n- `row.id` - string user key\n- `row.flags` - array of string flags\n- `row.key` - string of `key@seq` referring to log records\n\nOptionally collect results into `cb(err, rows)`.\n\n#### `cabal.moderation.list(cb)`\n\nReturn a readable object stream of records for all moderation actions across all\nchannels.\n\nEach `row` object in the output stream has:\n\n- `row.id` - string key which is the target of this moderation operation\n- `row.flags` - array of string flags set for this user\n- `row.channel` - string channel name this operation applies to\n- `row.key` - key of log record (not defined for self-admin and admins added by modkey)\n\nOptionally collect results into `cb(err, rows)`.\n\n#### `cabal.moderation.listBlocks(channel, cb)`\n\nReturn a readable object stream of records for the blocks in `channel`.\n\nThe objects in the output have the same form as `listByFlag()`.\n\nOptionally collect results into `cb(err, rows)`.\n\n#### `cabal.moderation.listHides(channel, cb)`\n\nReturn a readable object stream of records for the hides in `channel`.\n\nThe objects in the output have the same form as `listByFlag()`.\n\nOptionally collect results into `cb(err, rows)`.\n\n#### `cabal.moderation.listMutes(channel, cb)`\n\nReturn a readable object stream of records for the mutes in `channel`.\n\nThe objects in the output have the same form as `listByFlag()`.\n\nOptionally collect results into `cb(err, rows)`.\n\n#### `cabal.moderation.listModerationBy(key, cb)`\n\nReturn a readable object stream of moderation documents authored by `key`.\n\nEach `row` object in the output is a document used for adding, removing, and\nsetting flags.\n\n- `row.type` - `\"flags/add\"`, `\"flags/set\"`, or `\"flags/remove\"`\n- `row.content.id` - string key target of this moderation operation\n- `row.content.flags` - array of string flags for this operation\n- `row.content.reason` - array of string flags for this operation\n- `row.content.channel` - string channel name this operation applies to\n- `row.timestamp` - number, when this action was made in milliseconds since 1970\n\nOptionally collect results into `cb(err, rows)`.\n\n#### `cabal.moderation.getFlags({ id, channel }, cb)`\n\nGet a list of flags set for the user identified by `id` in `channel` as\n`cb(err, flags)`.\n\n#### `cabal.moderation.setFlags({ id, channel, flags }, cb)`\n\nSet an array of `flags` for `id` in `channel`.\n\n#### `cabal.moderation.addFlags({ id, channel, flags }, cb)`\n\nAdd an array of `flags` to the existing set of flags for `id` in `channel`.\n\n#### `cabal.moderation.removeFlags({ id, channel, flags }, cb)`\n\nRemove an array of `flags` from the existing set of flags for `id` in `channel`.\n\n#### `cabal.moderation.events.on('update', function (update) {})`\n\nThis event happens when a user's flags change with `update`, the log record\nresponsible for the state change.\n\n#### `cabal.moderation.events.on('skip', function (skip) {})`\n\nThis event happens when a moderation update was skipped with `skip`, the log\nrecord responsible for the state change.\n\n### Private Messages\n\n#### `cabal.publishPrivate(message, recipientKey, cb)`\n\nWrite a message `message`, but encrypted so that only `recipientKey` (the\npublic key of its recipient) and the sender can read it.\n\nA `timestamp` field is set automatically with the current local system time.\n\n#### `cabal.privateMessages.list(cb)`\n\nReturns a list of strings of all users' public keys that have sent a PM to you,\nor who you have sent a PM to.\n\n#### `var rs = cabal.privateMessages.read(channel, opts)`\n\nReturns a readable stream of messages (most recent first) from a channel.\n\nPass `opts.limit` to set a maximum number of messages to read.\n\n#### `cabal.privateMessages.events.on('message', fn)`\n\nCalls `fn` with every new private message that arrives.\n\n#### `cabal.privateMessages.events.on(publicKey, fn)`\n\nCalls `fn` with every new message that arrives to or from `publicKey`.\n\n### Publishing\n\n#### `cabal.publish(message, opts, cb)`\n\nPublish `message` to your feed. `message` must have a `type` field set. If not,\nit defaults to `chat/text`. In general, a message is formatted as\n\n```js\n{\n  type: 'chat/text',\n  content: {\n    text: 'hello world',\n    channel: 'cabal-dev'\n  }\n}\n```\n\nA `timestamp` field is set automatically with the current system time.\n\n`type` is an unrestricted field: you can make up new message types and clients\nwill happily ignore them until someone implements support for them. Well\ndocumented types include\n\n#### chat/text\n\n```js\n{\n  type: 'chat/text',\n  content: {\n    text: 'whatever the user wants to say',\n    channel: 'some channel name. if it didnt exist before, it does now!'\n  }\n}\n```\n\n## Developing\n\n### Changelog\n\nWhen releasing a new version the [`CHANGELOG.md`](./CHANGELOG.md) should be updated. There are three scripts in `package.json` for bumping the version:\n\n- `npm run changelog:patch` -\u003e add new patch section\n- `npm run changelog:minor` -\u003e add new minor section\n- `npm run changelog:major` -\u003e add new major section\n\nAdditionally, you can do `npm run changelog:fix`, which will expand references to pull requests and github users to urls. For example, referencing `@hackergrrl` would expand to `https://github.com/hackergrrl` and referencing `#314` would expand to `https://github.com/cabal-club/cabal-core/pulls/314`. Also, the table of contents in this document will be updated.\n\nTo release a new version you would typically do something like:\n\n```\nnpm run changelog:patch\n# Manually fill in details and/or tweak CHANGELOG.md\nnpm run changelog:fix\ngit add CHANGELOG.md \u0026\u0026 git commit -m 'Update changelog for x.y.z'\nnpm version patch\ngit push \u0026\u0026 git push --tags\nnpm publish\n```\n\nTry to keep the pull request titles short and descriptive and the changelog will automatically reflect this. A pull request may contain many small commits, but if it's squashed, the git history and the changelog will look clean. At the same time, all commits will still be around on GitHub if digging into details is required.\n\n## License\n\nAGPLv3\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcabal-club%2Fcabal-core","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcabal-club%2Fcabal-core","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcabal-club%2Fcabal-core/lists"}