plasmite 0.1.8 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,92 +1,198 @@
1
- # Plasmite Node Bindings (v0)
1
+ # plasmite
2
2
 
3
- These bindings wrap the `libplasmite` C ABI via a N-API addon.
3
+ Persistent JSON message queues for Node.js. No broker, no daemon — just files on disk.
4
4
 
5
- ## Installation
5
+ Plasmite gives you fast, crash-safe, disk-backed ring buffers ("pools") that
6
+ multiple processes can read and write concurrently. Use it for IPC, event
7
+ sourcing, job queues, or anywhere you'd reach for Redis but don't want to run
8
+ a server.
6
9
 
7
- Install from npm (canonical package name):
10
+ ## Install
8
11
 
9
12
  ```bash
10
13
  npm install plasmite
11
14
  ```
12
15
 
13
- Published package artifacts bundle:
14
- - `index.node` N-API addon
15
- - `libplasmite.(dylib|so)` beside the addon
16
- - `plasmite` CLI binary exposed as `npx plasmite`
16
+ Requires Node 20+. The package ships pre-built native binaries — no Rust
17
+ toolchain or compile step needed.
17
18
 
18
- For development/testing from this repo, see Build & Test below.
19
+ ## Quick start
19
20
 
20
- ## Build Requirements
21
+ ### Local pools (native, in-process)
21
22
 
22
- - Node 20+
23
- - Rust toolchain (for building the addon)
24
- - `libplasmite` built from this repo (`cargo build -p plasmite`)
23
+ ```js
24
+ const { Client, Durability } = require("plasmite");
25
+
26
+ // Open a client pointed at a directory (created if it doesn't exist)
27
+ const client = new Client("./data");
28
+
29
+ // Create a 64 MB pool called "events"
30
+ const pool = client.createPool("events", 64 * 1024 * 1024);
31
+
32
+ // Append a JSON message with tags
33
+ pool.appendJson(
34
+ Buffer.from(JSON.stringify({ kind: "signup", user: "alice" })),
35
+ ["user-event"],
36
+ Durability.Flush,
37
+ );
38
+
39
+ // Read it back by sequence number
40
+ const msg = pool.getJson(1n);
41
+ console.log(JSON.parse(msg.toString()));
42
+ // => { kind: "signup", user: "alice" }
43
+
44
+ // Stream messages as they arrive
45
+ const stream = pool.openStream();
46
+ let frame;
47
+ while ((frame = stream.nextJson()) !== null) {
48
+ console.log(JSON.parse(frame.toString()));
49
+ }
50
+ stream.close();
51
+
52
+ pool.close();
53
+ client.close();
54
+ ```
25
55
 
26
- ## Build & Test
56
+ ### Remote pools (HTTP/JSON)
27
57
 
28
- From the repo root:
58
+ Connect to a plasmite server (`npx plasmite serve` or `pls serve`) to read
59
+ and write pools over the network.
29
60
 
30
- ```bash
31
- cargo build -p plasmite
61
+ ```js
62
+ const { RemoteClient } = require("plasmite");
63
+
64
+ const client = new RemoteClient("http://127.0.0.1:9700");
65
+ // With auth: new RemoteClient("http://...", { token: "secret" })
66
+
67
+ const pool = await client.openPool("events");
68
+
69
+ // Append — accepts plain objects, serialized as JSON for you
70
+ const message = await pool.append(
71
+ { kind: "deploy", sha: "abc123" },
72
+ ["ops"],
73
+ );
74
+ console.log(message.seq); // => 1
75
+
76
+ // Read by sequence number
77
+ const got = await pool.get(1);
78
+ console.log(got.data); // => { kind: "deploy", sha: "abc123" }
79
+
80
+ // Tail — live-stream new messages (JSONL under the hood)
81
+ const tail = await pool.tail({ sinceSeq: 0, tags: ["ops"] });
82
+ const next = await tail.next(); // resolves on next matching message
83
+ console.log(next);
84
+ tail.cancel();
32
85
  ```
33
86
 
34
- Canonical repo-root command:
87
+ ## API
88
+
89
+ ### Local client
90
+
91
+ | Class | Method | Description |
92
+ |---|---|---|
93
+ | `Client(dir)` | | Open a pool directory |
94
+ | | `.createPool(name, sizeBytes)` | Create a new pool (returns `Pool`) |
95
+ | | `.openPool(name)` | Open an existing pool (returns `Pool`) |
96
+ | | `.close()` | Release resources |
97
+ | `Pool` | `.appendJson(buf, tags, durability)` | Append a JSON message; returns the stored envelope as `Buffer` |
98
+ | | `.appendLite3(buf, durability)` | Append raw bytes (lite3 framing); returns sequence `bigint` |
99
+ | | `.getJson(seq)` | Get message by sequence number; returns `Buffer` |
100
+ | | `.getLite3(seq)` | Get lite3 frame by sequence number |
101
+ | | `.openStream(sinceSeq?, max?, timeoutMs?)` | Open a message stream |
102
+ | | `.openLite3Stream(sinceSeq?, max?, timeoutMs?)` | Open a lite3 frame stream |
103
+ | | `.close()` | Close the pool |
104
+ | `Stream` | `.nextJson()` | Next message as `Buffer`, or `null` at end |
105
+ | | `.close()` | Close the stream |
106
+
107
+ **Durability** controls fsync behavior:
108
+ - `Durability.Fast` — buffered writes (higher throughput)
109
+ - `Durability.Flush` — fsync after write (crash-safe)
110
+
111
+ Sequence numbers accept `number` or `bigint`.
112
+
113
+ ### Remote client
114
+
115
+ | Class | Method | Description |
116
+ |---|---|---|
117
+ | `RemoteClient(url, opts?)` | | Connect to a plasmite server |
118
+ | | `.withToken(token)` | Set bearer token (returns `this`) |
119
+ | | `.createPool(name, sizeBytes)` | Create a pool on the server |
120
+ | | `.openPool(name)` | Open a remote pool (returns `RemotePool`) |
121
+ | | `.poolInfo(name)` | Get pool metadata |
122
+ | | `.listPools()` | List all pools |
123
+ | | `.deletePool(name)` | Delete a pool |
124
+ | `RemotePool` | `.append(data, tags?, durability?)` | Append a message (data is any JSON-serializable value) |
125
+ | | `.get(seq)` | Get a message by sequence number |
126
+ | | `.tail(opts?)` | Live-tail messages (returns `RemoteTail`) |
127
+ | `RemoteTail` | `.next()` | Await next message (resolves to message or `null`) |
128
+ | | `.cancel()` | Stop the tail stream |
129
+
130
+ ### Tail options
35
131
 
36
- ```bash
37
- just bindings-node-test
132
+ ```js
133
+ await pool.tail({
134
+ sinceSeq: 0, // start after this sequence number
135
+ maxMessages: 100, // stop after N messages
136
+ timeoutMs: 5000, // stop after N ms of inactivity
137
+ tags: ["signup"], // filter by exact tag match (AND across tags)
138
+ });
38
139
  ```
39
140
 
40
- Equivalent manual command (from `bindings/node`):
141
+ ### Error handling
41
142
 
42
- ```bash
43
- PLASMITE_LIB_DIR="$(pwd)/../../target/debug" npm test
143
+ Local operations throw `PlasmiteNativeError` with structured fields:
144
+
145
+ ```js
146
+ try {
147
+ pool.getJson(999n);
148
+ } catch (err) {
149
+ if (err instanceof PlasmiteNativeError) {
150
+ console.log(err.kind); // "NotFound"
151
+ console.log(err.seq); // 999
152
+ }
153
+ }
44
154
  ```
45
155
 
46
- Pack/install smoke (ensures bundled assets work without env vars):
156
+ Remote operations throw `RemoteError` with `status`, `kind`, and optional
157
+ `hint`.
158
+
159
+ ### CLI
160
+
161
+ The package includes the `plasmite` CLI:
47
162
 
48
163
  ```bash
49
- ./scripts/node_pack_smoke.sh
164
+ npx plasmite --version
165
+ npx plasmite serve ./data --port 9700
50
166
  ```
51
167
 
52
- ## Usage
168
+ ## TypeScript
53
169
 
54
- ```js
55
- const { Client, Durability } = require("plasmite")
170
+ Type declarations ship with the package (`types.d.ts`). No `@types/` install
171
+ needed.
56
172
 
57
- const client = new Client("./data")
58
- const pool = client.createPool("docs", 64 * 1024 * 1024)
59
- const payload = Buffer.from(JSON.stringify({ kind: "note", text: "hi" }))
60
- const message = pool.appendJson(payload, ["note"], Durability.Fast)
61
- console.log(message.toString("utf8"))
173
+ ```ts
174
+ import { Client, Durability, Pool, RemoteClient } from "plasmite";
175
+ ```
62
176
 
63
- const frame = pool.getLite3(BigInt(1))
64
- console.log(frame.payload.length)
177
+ ## Platform support
65
178
 
66
- pool.close()
67
- client.close()
68
- ```
179
+ Pre-built binaries are included for Linux x86_64. macOS and Windows users
180
+ should install via Homebrew (`brew install sandover/tap/plasmite`) or build
181
+ from source.
69
182
 
70
- Local binding failures throw `PlasmiteNativeError` with structured metadata fields (`kind`, `path`, `seq`, `offset`) when available.
183
+ ## Contributing
71
184
 
72
- ## Remote Client (HTTP/JSON)
185
+ Development requires a Rust toolchain to build the native addon:
73
186
 
74
- ```js
75
- const { RemoteClient } = require("plasmite")
76
-
77
- const client = new RemoteClient("http://127.0.0.1:9700")
78
- const pool = await client.openPool("docs")
79
- const message = await pool.append({ kind: "note", text: "hi" }, ["note"])
80
- console.log(message.seq, message.data)
81
-
82
- const tail = await pool.tail({
83
- sinceSeq: message.seq,
84
- tags: ["note"],
85
- maxMessages: 1,
86
- timeoutMs: 500,
87
- })
88
- console.log(await tail.next())
89
- tail.cancel()
187
+ ```bash
188
+ # From the repo root
189
+ cargo build -p plasmite
190
+ cd bindings/node && PLASMITE_LIB_DIR=../../target/debug npm test
90
191
  ```
91
192
 
92
- `tail({ tags: [...] })` performs exact tag matching and composes with other filters via AND semantics.
193
+ See the [main repo](https://github.com/sandover/plasmite) for full build
194
+ instructions.
195
+
196
+ ## License
197
+
198
+ MIT
package/bin/plasmite.js CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  /*
3
- Purpose: Provide an npm bin entrypoint that runs the bundled plasmite CLI.
3
+ Purpose: Provide an npm bin entrypoint that runs the packaged platform CLI binary.
4
4
  Key Exports: CLI entrypoint only.
5
5
  Role: Ensure `npx plasmite` works from npm-installed package artifacts.
6
- Invariants: Uses packaged binary first, then falls back to PATH.
6
+ Invariants: Resolves CLI from native/{platform}/ for supported platforms.
7
7
  Invariants: Forwards argv verbatim and propagates child exit status.
8
8
  Notes: Prints a concise error when no CLI binary is available.
9
9
  */
@@ -12,9 +12,32 @@ const { spawnSync } = require("node:child_process");
12
12
  const fs = require("node:fs");
13
13
  const path = require("node:path");
14
14
 
15
+ const PLATFORM_DIRS = Object.freeze({
16
+ linux: Object.freeze({ x64: "linux-x64" }),
17
+ darwin: Object.freeze({ x64: "darwin-x64", arm64: "darwin-arm64" }),
18
+ });
19
+
20
+ function resolvePlatformDir() {
21
+ const byArch = PLATFORM_DIRS[process.platform];
22
+ if (!byArch) {
23
+ return null;
24
+ }
25
+ return byArch[process.arch] ?? null;
26
+ }
27
+
15
28
  const packageRoot = path.resolve(__dirname, "..");
16
- const bundled = path.join(packageRoot, "plasmite");
17
- const target = fs.existsSync(bundled) ? bundled : "plasmite";
29
+ const platformDir = resolvePlatformDir();
30
+ const target = platformDir
31
+ ? path.join(packageRoot, "native", platformDir, "plasmite")
32
+ : null;
33
+
34
+ if (!target || !fs.existsSync(target)) {
35
+ console.error(
36
+ `plasmite: native CLI is unavailable for ${process.platform}-${process.arch}. ` +
37
+ "This package supports remote-only mode without native binaries on unsupported platforms.",
38
+ );
39
+ process.exit(1);
40
+ }
18
41
 
19
42
  const result = spawnSync(target, process.argv.slice(2), { stdio: "inherit" });
20
43
  if (result.error) {
package/index.js CHANGED
@@ -6,10 +6,55 @@ Invariants: Exports align with native symbols and v0 API semantics.
6
6
  Notes: Requires libplasmite to be discoverable at runtime.
7
7
  */
8
8
 
9
- const native = require("./index.node");
10
9
  const { RemoteClient, RemoteError, RemotePool, RemoteTail } = require("./remote");
11
10
 
12
11
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
12
+ const path = require("node:path");
13
+
14
+ const PLATFORM_DIRS = Object.freeze({
15
+ linux: Object.freeze({ x64: "linux-x64" }),
16
+ darwin: Object.freeze({ x64: "darwin-x64", arm64: "darwin-arm64" }),
17
+ });
18
+
19
+ function resolvePlatformDir() {
20
+ const byArch = PLATFORM_DIRS[process.platform];
21
+ if (!byArch) {
22
+ return null;
23
+ }
24
+ return byArch[process.arch] ?? null;
25
+ }
26
+
27
+ function resolveNativeAddonPath() {
28
+ const platformDir = resolvePlatformDir();
29
+ if (!platformDir) {
30
+ return null;
31
+ }
32
+ return path.join(__dirname, "native", platformDir, "index.node");
33
+ }
34
+
35
+ let native = null;
36
+ let nativeLoadError = null;
37
+ let nativeAddonPath = null;
38
+
39
+ try {
40
+ nativeAddonPath = resolveNativeAddonPath();
41
+ if (nativeAddonPath) {
42
+ native = require(nativeAddonPath);
43
+ } else {
44
+ nativeLoadError = new Error(`unsupported platform: ${process.platform}-${process.arch}`);
45
+ }
46
+ } catch (err) {
47
+ nativeLoadError = err;
48
+ }
49
+
50
+ function makeNativeUnavailableError() {
51
+ const reason = nativeLoadError instanceof Error ? nativeLoadError.message : "unknown load error";
52
+ return new Error(
53
+ `plasmite native addon is unavailable for ${process.platform}-${process.arch} (${reason}). ` +
54
+ `expected addon path: ${nativeAddonPath ?? "unsupported platform"}. ` +
55
+ "RemoteClient remains supported without native artifacts.",
56
+ );
57
+ }
13
58
 
14
59
  class PlasmiteNativeError extends Error {
15
60
  constructor(message, details = {}, cause = undefined) {
@@ -62,6 +107,9 @@ function wrapNativeError(err) {
62
107
 
63
108
  class Client {
64
109
  constructor(poolDir) {
110
+ if (!native) {
111
+ throw makeNativeUnavailableError();
112
+ }
65
113
  this._inner = new native.Client(poolDir);
66
114
  }
67
115
 
@@ -88,6 +136,9 @@ class Client {
88
136
 
89
137
  class Pool {
90
138
  constructor(inner) {
139
+ if (!native) {
140
+ throw makeNativeUnavailableError();
141
+ }
91
142
  this._inner = inner;
92
143
  }
93
144
 
@@ -148,6 +199,9 @@ class Pool {
148
199
 
149
200
  class Stream {
150
201
  constructor(inner) {
202
+ if (!native) {
203
+ throw makeNativeUnavailableError();
204
+ }
151
205
  this._inner = inner;
152
206
  }
153
207
 
@@ -166,6 +220,9 @@ class Stream {
166
220
 
167
221
  class Lite3Stream {
168
222
  constructor(inner) {
223
+ if (!native) {
224
+ throw makeNativeUnavailableError();
225
+ }
169
226
  this._inner = inner;
170
227
  }
171
228
 
@@ -222,8 +279,8 @@ module.exports = {
222
279
  Pool,
223
280
  Stream,
224
281
  Lite3Stream,
225
- Durability: native.Durability,
226
- ErrorKind: native.ErrorKind,
282
+ Durability: native ? native.Durability : Object.freeze({}),
283
+ ErrorKind: native ? native.ErrorKind : Object.freeze({}),
227
284
  PlasmiteNativeError,
228
285
  RemoteClient,
229
286
  RemoteError,
Binary file
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plasmite",
3
- "version": "0.1.8",
3
+ "version": "0.1.13",
4
4
  "description": "Persistent JSON message queues for Node.js - native bindings for local and remote IPC",
5
5
  "license": "MIT",
6
6
  "main": "index.js",
@@ -10,14 +10,13 @@
10
10
  },
11
11
  "files": [
12
12
  "index.js",
13
- "index.node",
14
13
  "index.d.ts",
15
14
  "types.d.ts",
16
15
  "remote.js",
17
16
  "bin/plasmite.js",
18
- "libplasmite.dylib",
19
- "libplasmite.so",
20
- "plasmite"
17
+ "native/linux-x64/**",
18
+ "native/darwin-x64/**",
19
+ "native/darwin-arm64/**"
21
20
  ],
22
21
  "keywords": [
23
22
  "ipc",