Security News
UK Officials Consider Banning Ransomware Payments from Public Entities
The UK is proposing a bold ban on ransomware payments by public entities to disrupt cybercrime, protect critical services, and lead global cybersecurity efforts.
kappa-core
Advanced tools
kappa-core is a minimal peer-to-peer database, based on append-only logs and materialized views.
kappa-core is built on an abstraction called a kappa architecture, or "event sourcing". This differs from the traditional approach to databases, which is centered on storing the latest value for each key in the database. You might have a table like this:
id | key | value |
---|---|---|
51387 | soup | cold |
82303 | sandwich | warm |
23092 | berries | room temp |
If you wanted to change the value of soup
to warm
, you would modify the
entry with id=51387
so that the table was now
id | key | value |
---|---|---|
51387 | soup | warm |
82303 | sandwich | warm |
23092 | berries | room temp |
This table now, once again, represents the current state of the data.
There are some consequences to this style of data representation:
In contrast, kappa architecture centers on a primitive called the "append-only log" as its single source of truth.
An append-only log is a data structure that can only be added to. Each entry in a log is addressable by its "sequence number" (starting at 0, then 1, 2, 3, ...). In the case of kappa-core, which uses hypercore underneath, each log is also identified by a cryptographic public key, which allows each log entry to be digitally signed with that log's private key, certifying that each entry in the log was indeed authored by the same person or device. A single kappa-core database can have one, ten, or hundreds of append-only logs comprising it.
kappa-core still uses tables like the above, though. However, instead of being the source of truth, these tables are generated (or materialized) from the log data, providing a view of the log data in a new or optimized context. These are called materialized views.
The twin concepts of append-only logs and materialized views are the key concepts of kappa-core. Any kappa-core database does only a few things:
Let's look at an example of how the traditional table from the beginning of this section could be represented as a kappa architecture. The three initial rows would begin as log entries first:
[
{
id: 51387,
key: 'soup',
value: 'cold'
},
{
id: 82303,
key: 'sandwich',
value: 'warm'
},
{
id: 23092,
key: 'berries',
value: 'room temp'
}
]
These might be written to one log, or perhaps spread across several. They all get fed into materialized views in a nondeterministic order anyway, so it doesn't matter.
To produce a look-up table like before, a view might be defined like this:
when new log entry E:
table.put(E.key, E.value)
This would map each key
from the full set of log entries to its value
,
producing this table:
key | value |
---|---|
soup | cold |
sandwich | warm |
berries | room temp |
Notice id
isn't present. We didn't need it, so we didn't bother writing it to
the view. It's still stored in each log entry it came from though.
Now let's say an entry like { id: 51387, key: 'soup', value: 'warm' }
is
written to a log. The view logic above the table dictates that the key
is
mapped to the value
for this view, so the a table would be produced:
key | value |
---|---|
soup | warm |
sandwich | warm |
berries | room temp |
Like the traditional database, the table is mutated in-place to produce the new current state. The difference is that this table was derived from immutable log data, instead of being the truth source itself.
This is all very useful:
#4 is really powerful and worth examination: views can be regenerated. In kappa-core, views are versioned: the view we just generated was version 1, and was defined by the logic
when new log entry E:
table.put(E.key, E.value)
What if we wanted to change this view at some point, to instead map the entry's
id
to its value
? Maybe like this:
when new log entry E:
table.put(E.id, E.value)
With kappa-core, this would mean bumping the view's version to 2
.
kappa-core will purge the existing table, and regenerate it from scratch by
processing all of the entries in all of the logs all over again. This makes
views cheap, and also means no table migrations! Your data structures can
evolve as you program evolves, and peers won't need to worry about migrating to
new formats.
Lastly, a kappa-core database is able to replicate itself to another
kappa-core database. The replicate
API (below) returns a Node Duplex
stream. This stream can operate over any stream-compatible transport medium,
such as TCP, UTP, Bluetooth, a Unix pipe, or even audio waves sent over the
air! When two kappa-core databases replicate, they exchange the logs and the
entries in the logs, so that both sides end up with the same full set of log
entries. This will trigger your database's materialized views to process these
new entries to update themselves and reflect the latest state.
Because this is all built on hypercore, replication can be done over an encrypted channel.
Thanks for reading! You can also try the kappa-core workshop to use kappa-core yourself, or get support and/or chat about development on
cabal://0201400f1aa2e3076a3f17f4521b2cc41e258c446cdaa44742afe6e1b9fd5f82
This example sets up an on-disk log store and an in-memory view store. The view tallies the sum of all of the numbers in the logs, and provides an API for getting that sum.
var kappa = require('kappa-core')
var view = require('kappa-view')
var memdb = require('memdb')
// Store logs in a directory called "log". Store views in memory.
var core = kappa('./log', { valueEncoding: 'json' })
var store = memdb()
// View definition
var sumview = view(store, function (db) {
// Called with a batch of log entries to be processed by the view.
// No further entries are processed by this view until 'next()' is called.
map: function (entries, next) {
db.get('sum', function (err, value) {
var sum
if (err && err.notFound) sum = 0
else if (err) return next(err)
else sum = value
})
entries.forEach(function (entry) {
if (typeof entry.value === 'number') sum += entry.value
})
db.put('sum', sum, next)
}
// Whatever is defined in the "api" object is publicly accessible
api: {
get: function (core, cb) {
this.ready(function () { // wait for all views to catch up
cb(null, sum)
})
}
},
})
// the api will be mounted at core.api.sum
core.use('sum', 1, sumview) // name the view 'sum' and consider the 'sumview' logic as version 1
core.writer('default', function (err, writer) {
writer.append(1, function (err) {
core.api.sum.get(function (err, value) {
console.log(value) // 1
})
})
})
var kappa = require('kappa-core')
Create a new kappa-core database.
storage
is an instance of
random-access-storage. If a string
is given,
random-access-file
is used with the string as the filename.opts
include:
valueEncoding
: a string describing how the data will be encoded.multifeed
: A preconfigured instance of multifeedGet or create a local writable log called name
. If it already exists, it is
returned, otherwise it is created. A writer is an instance of
hypercore.
Fetch a log / feed by its public key (a Buffer
or hex string).
An array of all hypercores in the kappa-core. Check a feed's key
to find the
one you want, or check its writable
/ readable
properties.
Only populated once core.ready(fn)
is fired.
Install a view called name
to the kappa-core instance. A view is an object of
the form
// All are optional except "map"
{
// Process each batch of entries
map: function (entries, next) {
entries.forEach(function (entry) {
// ...
})
next()
},
// Your useful functions for users of this view to call
api: {
someSyncFunction: function (core) { return ... },
someAsyncFunction: function (core, cb) { process.nextTick(cb, ...) }
},
// Save progress state so processing can resume on later runs of the program.
// Not required if you're using the "kappa-view" module, which handles this for you.
fetchState: function (cb) { ... },
storeState: function (state, cb) { ... },
clearState: function (cb) { ... }
// Runs after each batch of entries is done processing and progress is persisted
indexed: function (entries) { ... },
// Number of entries to process in a batch
maxBatch: 100,
}
NOTE: The kappa-core instance core
is always passed as the fist parameter
in all of the api
functions you define.
version
is an integer that represents what version you want to consider the
view logic as. Whenever you change it (generally by incrementing it by 1), the
underlying data generated by the view will be wiped, and the view will be
regenerated again from scratch. This provides a means to change the logic or
data structure of a view over time in a way that is future-compatible.
The fetchState
, storeState
, and clearState
functions are optional: they
tell the view where to store its state information about what log entries have
been indexed thus far. If not passed in, they will be stored in memory (i.e.
reprocessed on each fresh run of the program). You can use any backend you want
(like leveldb) to store the Buffer
object state
. If you use a module like
kappa-view, it will handle state
management on your behalf.
indexed
is an optional function to run whenever a new batch of entries have
been indexed and written to storage. Receives an array of entries.
Wait until all views named by viewNames
are caught up. e.g.
// one
core.ready('sum', function () { ... })
// or several
core.ready(['kv', 'refs', 'spatial'], function () { ... })
If viewNames is []
or not included, all views will be waited on.
Pause some or all of the views' indexing process. If no viewNames
are given,
they will all be paused. cb
is called once the views finish up any entries
they're in the middle of processing and are fully stopped.
Resume some or all paused views. If no viewNames
is given, all views are
resumed.
Create a duplex replication stream. opts
are passed in to
multifeed's API of the same name.
Event emitted when an error within kappa-core has occurred. This is very important to listen on, lest things suddenly seem to break and it's not immediately clear why.
With npm installed, run
$ npm install kappa-core
Here are some useful modules that play well with kappa-core for building materialized views:
kappa-core is built atop two major building blocks:
hypercore provides some very useful superpowers:
Building views in arbitrary sequence is more challenging than when order is known to be topographic or sorted in some way, but confers some benefits:
kappa-core is built atop ideas from a huge body of others' work:
ISC
[git-shallow]: https://www.git-scm.com/docs/gitconsole.log(one#gitconsole.log(one---depthltdepthgt) [kappa]: http://kappa-architecture.com
FAQs
Minimal peer-to-peer database, based on kappa architecture.
The npm package kappa-core receives a total of 100 weekly downloads. As such, kappa-core popularity was classified as not popular.
We found that kappa-core demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 6 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
The UK is proposing a bold ban on ransomware payments by public entities to disrupt cybercrime, protect critical services, and lead global cybersecurity efforts.
Security News
Snyk's use of malicious npm packages for research raises ethical concerns, highlighting risks in public deployment, data exfiltration, and unauthorized testing.
Research
Security News
Socket researchers found several malicious npm packages typosquatting Chalk and Chokidar, targeting Node.js developers with kill switches and data theft.