npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

antietcd

v1.1.1

Published

Simplistic etcd replacement based on TinyRaft

Downloads

110

Readme

AntiEtcd

Simplistic miniature etcd replacement based on TinyRaft

  • Embeddable
  • REST API only, gRPC is shit and will never be supported
  • TinyRaft-based leader election
  • Websocket-based cluster communication
  • Supports a limited subset of etcd REST APIs
  • With optional persistence

Contents

CLI Usage

npm install antietcd

node_modules/.bin/antietcd \
    [--cert ssl.crt] [--key ssl.key] [--port 12379] \
    [--data data.gz] [--persist_interval 500] \
    [--node_id node1 --cluster_key abcdef --cluster node1=http://localhost:12379,node2=http://localhost:12380,node3=http://localhost:12381]
    [other options]

Antietcd doesn't background itself, so use systemd or start-stop-daemon to run it as a background service.

CLI Client

node_modules/.bin/anticli [OPTIONS] put <key> [<value>]
node_modules/.bin/anticli [OPTIONS] get <key> [-p|--prefix] [-v|--print-value-only] [-k|--keys-only] [--no-temp]
node_modules/.bin/anticli [OPTIONS] del <key> [-p|--prefix]
node_modules/.bin/anticli [OPTIONS] load [--with-lease] < dump.json

For put, if <value> is not specified, it will be read from STDIN.

Options:

Options

HTTP

Persistence

Clustering

Embedded Usage

const AntiEtcd = require('antietcd');

// Configuration may contain all the same options like in CLI, without "--"
// Except that persist_filter should be a callback (key, value) => newValue
const srv = new AntiEtcd({ ...configuration });

// Start server
srv.start();

// Make a local API call in generic style:
let res = await srv.api('kv_txn'|'kv_range'|'kv_put'|'kv_deleterange'|'lease_grant'|'lease_revoke'|'lease_keepalive', { ...params });

// Or function-style:
res = await srv.txn(params);
res = await srv.range(params);
res = await srv.put(params);
res = await srv.deleterange(params);
res = await srv.lease_grant(params);
res = await srv.lease_revoke(params);
res = await srv.lease_keepalive(params);

// Error handling:
try
{
    res = await srv.txn(params);
}
catch (e)
{
    if (e instanceof AntiEtcd.RequestError)
    {
        // e.code is HTTP code
        // e.message is error message
    }
}

// Watch API:
const watch_id = await srv.create_watch(params, (message) => console.log(message));
await srv.cancel_watch(watch_id);

// Stop server
srv.stop();

About Persistence

Persistence is very simple: full database is dumped into JSON, gzipped and saved as file.

By default, it is written and fsynced on disk on every change, but it can be configured to dump DB on disk at fixed intervals, for example, at most every 500 ms - of course, at expense of slightly reduced crash resiliency (example: --persist_interval 500).

You can also specify a filter to exclude some data from persistence by using the option --persist_filter ./filter.js. Persistence filter code example:

function example_filter(cfg)
{
    // <cfg> contains all command-line options
    const prefix = cfg.exclude_keys;
    if (!prefix)
    {
        return null;
    }
    return (key, value) =>
    {
        if (key.substr(0, prefix.length) == prefix)
        {
            // Skip all keys with prefix from persistence
            return undefined;
        }
        if (key === '/statistics')
        {
            // Return <unneeded_key> from inside value
            const decoded = JSON.parse(value);
            return JSON.stringify({ ...decoded, unneeded_key: undefined });
        }
        return value;
    };
}

module.exports = example_filter;

Supported etcd APIs

NOTE: key, value and range_end are always encoded in base64, like in original etcd.

Range requests are only supported across "directories" separated by /.

It means that in range requests key must always end with / and range_end must always end with 0, and that such request will return a whole subtree of keys.

/v3/kv/txn

Request:

type TxnRequest = {
  compare?: (
    { key: string, target: "MOD", mod_revision: number, result?: "LESS" }
    | { key: string, target: "CREATE", create_revision: number, result?: "LESS" }
    | { key: string, target: "VERSION", version: number, result?: "LESS" }
    | { key: string, target: "LEASE", lease: string, result?: "LESS" }
    | { key: string, target: "VALUE", value: string }
  )[],
  success?: (
    { request_put: PutRequest }
    | { request_range: RangeRequest }
    | { request_delete_range: DeleteRangeRequest }
  )[],
  failure?: (
    { request_put: PutRequest }
    | { request_range: RangeRequest }
    | { request_delete_range: DeleteRangeRequest }
  )[],
  serializable?: boolean,
}

serializable allows to serve read-only requests from follower even if stale_read is not enabled.

Response:

type TxnResponse = {
  header: { revision: number },
  succeeded: boolean,
  responses: (
    { response_put: PutResponse }
    | { response_range: RangeResponse }
    | { response_delete_range: DeleteRangeResponse }
  )[],
}

/v3/kv/put

Request:

type PutRequest = {
  key: string,
  value: string,
  lease?: string,
}

Other parameters are not supported: prev_kv, ignore_value, ignore_lease.

Response:

type PutResponse = {
  header: { revision: number },
}

/v3/kv/range

Request:

type RangeRequest = {
  key: string,
  range_end?: string,
  keys_only?: boolean,
  serializable?: boolean,
}

serializable allows to serve read-only requests from follower even if stale_read is not enabled.

Other parameters are not supported: revision, limit, sort_order, sort_target, count_only, min_mod_revision, max_mod_revision, min_create_revision, max_create_revision.

Response:

type RangeResponse = {
  header: { revision: number },
  kvs: { key: string }[] | {
    key: string,
    value: string,
    lease?: string,
    mod_revision: number,
  }[],
}

/v3/kv/deleterange

Request:

type DeleteRangeRequest = {
  key: string,
  range_end?: string,
}

Other parameters are not supported: prev_kv.

Response:

type DeleteRangeResponse = {
  header: { revision: number },
  // number of deleted keys
  deleted: number,
}

/v3/lease/grant

Request:

type LeaseGrantRequest = {
  ID?: string,
  TTL: number,
}

Response:

type LeaseGrantResponse = {
  header: { revision: number },
  ID: string,
  TTL: number,
}

/v3/lease/keepalive

Request:

type LeaseKeepaliveRequest = {
  ID: string,
}

Response:

type LeaseKeepaliveResponse = {
  result: {
    header: { revision: number },
    ID: string,
    TTL: number,
  }
}

/v3/lease/revoke or /v3/kv/lease/revoke

Request:

type LeaseRevokeRequest = {
  ID: string,
}

Response:

type LeaseRevokeResponse = {
  header: { revision: number },
}

/v3/maintenance/status

Request:

{}

Response:

type MaintenanceStatusResponse = {
  header: {
    member_id?: string,
    revision: number,
    compact_revision: number,
    raft_term?: number,
  },
  version: string,
  cluster?: { [string]: string },
  leader?: string,
  followers?: string[],
  raftTerm?: string,
  raftState?: 'leader'|'follower'|'candidate',
  // dbSize actually reports process memory usage
  dbSize: number,
}

Websocket-based watch APIs

Client-to-server message format:

type ClientMessage =
  { create_request: {
    key: string,
    range_end?: string,
    start_revision?: number,
    watch_id?: string,
  } }
  | { cancel_request: {
    watch_id: string,
  } }
  | { progress_request: {} }

Server-to-client message format:

type ServerMessage = {
  result: {
    header: { revision: number },
    watch_id: string,
    created?: boolean,
    canceled?: boolean,
    compact_revision?: number,
    events?: {
      type: 'PUT'|'DELETE',
      kv: {
        key: string,
        value: string,
        lease?: string,
        mod_revision: number,
      },
    }[],
  }
} | { error: 'bad-json' } | { error: 'empty-message' }

HTTP Error Codes

  • 400 for invalid requests
  • 404 for unsupported API / URL not found
  • 405 for non-POST request method
  • 501 for unsupported API feature - non-directory range queries and so on
  • 502 for server is stopping
  • 503 for quorum-related errors - quorum not available and so on

Author and License

Author: Vitaliy Filippov, 2024

License: Mozilla Public License 2.0 or Vitastor Network Public License 1.1