@ignex/nova 0.1.1 → 0.1.5
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 +136 -33
- package/docs/ai/LOCAL_DEV.md +81 -0
- package/docs/ai/TREE.md +292 -0
- package/docs/architecture.md +101 -28
- package/docs/events.md +252 -0
- package/docs/generic-bindings.md +207 -0
- package/docs/publishing.md +2 -2
- package/docs/wire-format.md +74 -20
- package/index.ts +75 -27
- package/package.json +13 -2
- package/prebuilds/linux-x64/libignex_ffi.so +0 -0
- package/public/bindings.ts +24 -0
- package/public/client.ts +5 -1
- package/public/events.ts +71 -0
- package/public/generate.ts +510 -0
- package/public/internal.ts +16 -0
- package/public/nats.ts +9 -5
- package/public/server.ts +52 -16
- package/rust/src/ffi.rs +10 -0
- package/rust/src/generated/backend.rs +503 -0
- package/rust/src/transcode/generated.rs +377 -17
- package/src/bindings/assemble.ts +73 -0
- package/src/bindings/default.ts +65 -0
- package/src/bindings/types.ts +113 -0
- package/src/bridge/nats/inbound.ts +46 -0
- package/src/bridge/nats/index.ts +131 -0
- package/src/bridge/nats/real-transport.ts +133 -0
- package/src/bridge/nats/types.ts +80 -0
- package/src/bridge/subjects.ts +3 -0
- package/src/codegen/constants.ts +28 -0
- package/src/codegen/direct-gen.ts +564 -0
- package/src/codegen/fingerprint.ts +44 -0
- package/src/codegen/hash.ts +25 -0
- package/src/codegen/registry-gen.ts +246 -0
- package/src/codegen/rust-glue-gen.ts +552 -0
- package/src/codegen/schema-model.ts +363 -0
- package/src/codegen/ts-ser-gen.ts +230 -0
- package/src/codegen/typebox-to-fbs.ts +60 -0
- package/src/core/auth.ts +67 -5
- package/src/core/client-heartbeat.ts +2 -1
- package/src/core/client-reconnect.ts +9 -2
- package/src/core/client-rpc.ts +75 -0
- package/src/core/client-state.ts +63 -8
- package/src/core/client-wire.ts +148 -15
- package/src/core/client.ts +105 -31
- package/src/core/groups.ts +8 -0
- package/src/core/metrics.ts +42 -21
- package/src/core/outbound.ts +62 -11
- package/src/core/rate-limit.ts +69 -0
- package/src/core/replay.ts +41 -1
- package/src/core/resume.ts +181 -0
- package/src/core/rooms.ts +10 -3
- package/src/core/routing.ts +144 -12
- package/src/core/server/client-info.ts +37 -0
- package/src/core/server/http-routes.ts +59 -0
- package/src/core/server/index.ts +360 -0
- package/src/core/server/metrics-view.ts +53 -0
- package/src/core/server/socket-lifecycle.ts +57 -0
- package/src/core/state.ts +124 -14
- package/src/core/topic-log.ts +86 -0
- package/src/events/clients.ts +174 -0
- package/src/events/cluster/dedupe.ts +43 -0
- package/src/events/cluster/envelope.ts +149 -0
- package/src/events/cluster/index.ts +50 -0
- package/src/events/cluster/keys.ts +33 -0
- package/src/events/cluster/kinds.ts +32 -0
- package/src/events/cluster/presence-table.ts +99 -0
- package/src/events/cluster/presence.ts +53 -0
- package/src/events/cluster/redis-client.ts +50 -0
- package/src/events/cluster/store-memory.ts +67 -0
- package/src/events/cluster/store-redis.ts +44 -0
- package/src/events/cluster/subjects.ts +30 -0
- package/src/events/cluster/sync.ts +476 -0
- package/src/events/cluster/transport-nats.ts +24 -0
- package/src/events/cluster/transport-redis.ts +120 -0
- package/src/events/cluster-rpc.ts +196 -0
- package/src/events/data.ts +38 -0
- package/src/events/delivery.ts +83 -0
- package/src/events/emit.ts +173 -0
- package/src/events/global.ts +117 -0
- package/src/events/groups.ts +118 -0
- package/src/events/hub/context-factory.ts +79 -0
- package/src/events/hub/dispatch.ts +86 -0
- package/src/events/hub/index.ts +536 -0
- package/src/events/hub/internal.ts +31 -0
- package/src/events/hub/metrics-snapshot.ts +84 -0
- package/src/events/hub/resolve-cluster.ts +49 -0
- package/src/events/index.ts +61 -0
- package/src/events/queue.ts +123 -0
- package/src/events/registry.ts +214 -0
- package/src/events/schedule.ts +73 -0
- package/src/events/trace.ts +283 -0
- package/src/events/types/client.ts +68 -0
- package/src/events/types/cluster.ts +40 -0
- package/src/events/types/context.ts +50 -0
- package/src/events/types/emit-target.ts +29 -0
- package/src/events/types/groups.ts +35 -0
- package/src/events/types/hub.ts +124 -0
- package/src/events/types/index.ts +30 -0
- package/src/events/types/metrics.ts +52 -0
- package/src/events/types/options.ts +62 -0
- package/src/generated/direct-ser.ts +148 -60
- package/src/generated/fbs/backend.fbs +24 -1
- package/src/generated/registry.ts +94 -33
- package/src/generated/rust/backend_generated.rs +503 -0
- package/src/generated/ts/backend.ts +4 -0
- package/src/generated/ts/resume.ts +74 -0
- package/src/generated/ts/resumed.ts +88 -0
- package/src/generated/ts/rpc-call.ts +112 -0
- package/src/generated/ts/rpc-result.ts +126 -0
- package/src/generated/ts/snapshot-request.ts +19 -5
- package/src/generated/ts-ser.ts +110 -17
- package/src/generated/wire-registry.json +7 -2
- package/src/native/ffi.ts +85 -28
- package/src/schema/index.ts +49 -2
- package/src/server.ts +7 -3
- package/src/transport/transport.ts +200 -79
- package/src/bridge/nats.ts +0 -269
- package/src/core/server.ts +0 -294
- package/src/transport/stats.ts +0 -44
package/README.md
CHANGED
|
@@ -5,25 +5,59 @@ serializer — and a typed pub/sub API that hides all of it from developers.
|
|
|
5
5
|
|
|
6
6
|
```ts
|
|
7
7
|
// server (Bun)
|
|
8
|
-
import { createServer } from "ignex
|
|
9
|
-
const server = createServer({ port: 3000, inbound: ["
|
|
8
|
+
import { createServer } from "@ignex/nova/server";
|
|
9
|
+
const server = createServer({ port: 3000, inbound: ["trade"] });
|
|
10
10
|
server.publish("quote", { symbol: "AAPL", bid: 180.1, ask: 180.2, bidSize: 100, askSize: 200, ts: Date.now() });
|
|
11
11
|
server.publishToTopic("equities", "quote", {...}); // rooms
|
|
12
|
-
server.on("
|
|
12
|
+
server.on("trade", (msg, ws) =>
|
|
13
|
+
server.publishTo(ws, "quote", { symbol: msg.symbol, bid: msg.price, ask: msg.price, bidSize: 1, askSize: 1, ts: Date.now() }),
|
|
14
|
+
);
|
|
13
15
|
|
|
14
16
|
// FE (browser or Bun)
|
|
15
|
-
import { createClient } from "ignex
|
|
17
|
+
import { createClient } from "@ignex/nova/client";
|
|
16
18
|
const client = createClient("ws://localhost:3000/ws", { reconnect: true });
|
|
17
19
|
client.on("quote", (q) => console.log(q.symbol, q.bid)); // q is a plain typed object
|
|
18
20
|
client.subscribe("equities"); // rooms + last-value replay
|
|
19
|
-
client.send("
|
|
21
|
+
client.send("trade", { symbol: "AAPL", price: 180.5, volume: 10, side: "buy", ts: Date.now() }); // typed client→server (pure-JS encoder — works in the browser)
|
|
20
22
|
client.connect();
|
|
21
23
|
```
|
|
22
24
|
|
|
25
|
+
> The built-in registry ships `quote`/`trade`/`portfolio`/`complex`/`order`/`bigVal` — custom events like `chat` require your own schema via `generateBindings` (see below).
|
|
26
|
+
|
|
23
27
|
No `flatbuffers.Builder`, no `getRootAs*`, no FFI — developers only ever see
|
|
24
28
|
plain, type-checked objects. The FlatBuffer + Rust machinery is internal, and
|
|
25
29
|
the **browser can send typed frames too** (via a generated pure-JS encoder).
|
|
26
30
|
|
|
31
|
+
## Bring your own schema (generic bindings)
|
|
32
|
+
|
|
33
|
+
The transport is **schema-driven**: the built-in events (quote/trade/…) are
|
|
34
|
+
just the default registry. Define **any** TypeBox schema in your app and
|
|
35
|
+
`generateBindings(schema)` produces a complete, typed wire stack for it —
|
|
36
|
+
same APIs, same NATS story, your events:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
// scripts/generate-bindings.ts
|
|
40
|
+
import { generateBindings } from "@ignex/nova/generate";
|
|
41
|
+
import { schemas, events, controlEvents } from "../src/schema"; // YOUR TypeBox
|
|
42
|
+
generateBindings({ schemas, events, controlEvents }, { outDir: "./ignex/generated" }).write();
|
|
43
|
+
|
|
44
|
+
// bindings.ts
|
|
45
|
+
import { makeBindings } from "./ignex/generated";
|
|
46
|
+
import * as schema from "../src/schema";
|
|
47
|
+
export const bindings = makeBindings(schema);
|
|
48
|
+
|
|
49
|
+
// server.ts — publish/on typed against YOUR events; NATS bridging included
|
|
50
|
+
const server = createServer({ port: 3000, bindings, inbound: ["chat"],
|
|
51
|
+
nats: { servers: ["nats://localhost:4222"], inbound: true, bridgeClientEvents: true } });
|
|
52
|
+
server.publish("chat", { room: "lobby", text: "hi", ts: Date.now() });
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The generated stack (flatc TS decoders + pure-JS encoder + direct fast-path
|
|
56
|
+
serde + `wire-registry.json` for NATS consumers + an optional Rust crate for
|
|
57
|
+
the FFI fast path) works without a Rust toolchain — the server falls back to
|
|
58
|
+
the pure-JS encoder when no addon is present. Full guide:
|
|
59
|
+
[docs/generic-bindings.md](docs/generic-bindings.md).
|
|
60
|
+
|
|
27
61
|
## Features
|
|
28
62
|
|
|
29
63
|
- **Typed pub/sub, both directions** — `publish`/`publishTo`/`publishToTopic`
|
|
@@ -46,12 +80,30 @@ the **browser can send typed frames too** (via a generated pure-JS encoder).
|
|
|
46
80
|
server-side groups (`publishToGroup(group, …)`, joined via auth metadata,
|
|
47
81
|
`joinGroup(id, group)`, or client `joinGroup` frames) target sets of clients.
|
|
48
82
|
Active clients are listed via `getClients()` / `GET /clients`.
|
|
83
|
+
- **Generic, schema-driven** — `generateBindings(schema)` (from
|
|
84
|
+
`@ignex/nova/generate`) builds the whole wire stack for ANY TypeBox schema in
|
|
85
|
+
your app; `createServer` / `createClient` / `createNatsBridge` accept the
|
|
86
|
+
resulting `bindings` and are fully typed against your events. The built-in
|
|
87
|
+
events are just the default registry.
|
|
49
88
|
- **NATS bridge (bidirectional)** — every broadcast/topic/group publish is also
|
|
50
89
|
published to NATS as the **same FlatBuffer wire frame** (`ignex.broadcast.*`,
|
|
51
90
|
`ignex.topic.*`, `ignex.group.*`) so other applications can consume it;
|
|
52
91
|
external apps push events into the hub via `ignex.inbound.>` and the server
|
|
53
|
-
forwards them to clients.
|
|
54
|
-
|
|
92
|
+
forwards them to clients. `bridgeClientEvents: true` re-publishes client-sent
|
|
93
|
+
events to the cluster (horizontal scaling). Best-effort (never blocks the WS
|
|
94
|
+
hot path), observable via metrics.
|
|
95
|
+
- **Events layer** (`@ignex/nova/events`, opt-in via `createServer({ events })`)
|
|
96
|
+
— the application-facing event-driven system on top of the transport: an
|
|
97
|
+
**events file** receives events (`server.events.on(...)` with a context
|
|
98
|
+
carrying the sender's client record), a **global emit** sends events through
|
|
99
|
+
websockets from anywhere (`emit` / `emitToGroup` / `emitToUser` /
|
|
100
|
+
`emitToClient`), **client records** model "who is connected, on whose behalf
|
|
101
|
+
(`userId`), and what to remember per connection (`client.data`)", **client
|
|
102
|
+
groups vs user groups** make group-vs-user targeting explicit, and an
|
|
103
|
+
optional **cluster sync** (NATS and/or Redis, offloaded from hot paths)
|
|
104
|
+
delivers every emit to the target's clients across horizontally scaled
|
|
105
|
+
instances, with presence + shared-state indexes. See
|
|
106
|
+
[docs/events.md](docs/events.md).
|
|
55
107
|
- **Bun-only server, browser+Bun client.** Wire spec documented for independent
|
|
56
108
|
clients: [docs/wire-format.md](docs/wire-format.md).
|
|
57
109
|
|
|
@@ -63,10 +115,10 @@ src/schema/index.ts (TypeBox — source of truth: app events + control events)
|
|
|
63
115
|
▼
|
|
64
116
|
backend.fbs ──flatc --ts──▶ src/generated/ts/ (decoders)
|
|
65
117
|
└───flatc --rust──▶ rust/src/generated/ (table builders)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
118
|
+
src/codegen/rust-glue-gen.ts ─▶ rust/src/transcode/ (JSON glue + direct-args FFI)
|
|
119
|
+
src/codegen/direct-gen.ts ────▶ src/generated/direct-ser.ts (direct fast-path serde)
|
|
120
|
+
src/codegen/ts-ser-gen.ts ─────▶ src/generated/ts-ser.ts (pure-JS browser encoder)
|
|
121
|
+
src/codegen/registry-gen.ts ───▶ src/generated/registry.ts (event routing, both sides)
|
|
70
122
|
|
|
71
123
|
server: JS object ─▶ (flat event) fields as direct FFI args ─▶ Rust
|
|
72
124
|
└▶ (vector/nested) JSON.stringify → cstring ─▶ Rust
|
|
@@ -140,15 +192,15 @@ Published as **TypeScript source** (no build step needed — Bun runs `.ts`
|
|
|
140
192
|
natively), with subpath entrypoints. Works in any Bun ≥ 1.4 project:
|
|
141
193
|
|
|
142
194
|
```bash
|
|
143
|
-
bun add ignex
|
|
195
|
+
bun add @ignex/nova
|
|
144
196
|
```
|
|
145
197
|
|
|
146
198
|
```ts
|
|
147
199
|
// server (Bun-only — needs the native addon, see below)
|
|
148
|
-
import { createServer } from "ignex
|
|
200
|
+
import { createServer } from "@ignex/nova/server";
|
|
149
201
|
|
|
150
202
|
// client (browser + Bun) — the root entry also re-exports everything
|
|
151
|
-
import { createClient, type Events } from "ignex
|
|
203
|
+
import { createClient, type Events } from "@ignex/nova/client";
|
|
152
204
|
|
|
153
205
|
const q: Events["quote"] = { symbol: "AAPL", bid: 180.1, ask: 180.2, bidSize: 100, askSize: 200, ts: Date.now() };
|
|
154
206
|
```
|
|
@@ -157,17 +209,22 @@ Entrypoints:
|
|
|
157
209
|
|
|
158
210
|
| Import | Resolves to |
|
|
159
211
|
| --- | --- |
|
|
160
|
-
|
|
|
161
|
-
|
|
|
162
|
-
|
|
|
163
|
-
|
|
|
212
|
+
| `@ignex/nova` | `index.ts` — everything (server + client + nats + schema types + generic codegen) |
|
|
213
|
+
| `@ignex/nova/server` | `public/server.ts` — `createServer` (Bun-only) |
|
|
214
|
+
| `@ignex/nova/client` | `public/client.ts` — `createClient` (browser + Bun) |
|
|
215
|
+
| `@ignex/nova/nats` | `public/nats.ts` — standalone `createNatsBridge` |
|
|
216
|
+
| `@ignex/nova/events` | `public/events.ts` — events layer + global emit |
|
|
217
|
+
| `@ignex/nova/generate` | `public/generate.ts` — `generateBindings` (ANY-schema codegen) |
|
|
218
|
+
| `@ignex/nova/bindings` | `public/bindings.ts` — `assembleBindings` / `defaultBindings` + types |
|
|
219
|
+
| `@ignex/nova/internal` | `public/internal.ts` — runtime helpers used by generated code |
|
|
220
|
+
| `@ignex/nova/package.json` | `package.json` — version / metadata for tooling |
|
|
164
221
|
|
|
165
222
|
**Native addon:** the tarball ships `rust/` source + `prebuilds/<platform>-<arch>/`
|
|
166
223
|
for the platforms built at release (see CI). If your platform has a prebuild it
|
|
167
224
|
just works. Otherwise either rebuild from the shipped source:
|
|
168
225
|
|
|
169
226
|
```bash
|
|
170
|
-
cargo build --release --manifest-path node_modules/
|
|
227
|
+
cargo build --release --manifest-path node_modules/@ignex/nova/rust/Cargo.toml
|
|
171
228
|
# loader finds it at <pkg>/rust/target/release/…
|
|
172
229
|
# or point at any build explicitly:
|
|
173
230
|
IGNEX_FFI_PATH=/abs/path/to/libignex_ffi.so bun run your-server.ts
|
|
@@ -180,9 +237,11 @@ staged, and published to npm.
|
|
|
180
237
|
|
|
181
238
|
| Path | Role |
|
|
182
239
|
| --- | --- |
|
|
183
|
-
| `src/schema/index.ts` | TypeBox schemas + `events`/`controlEvents` registries (source of truth) |
|
|
184
|
-
| `
|
|
185
|
-
| `src/
|
|
240
|
+
| `src/schema/index.ts` | built-in TypeBox schemas + `events`/`controlEvents` registries (source of truth for the DEFAULT registry) |
|
|
241
|
+
| `src/bindings/` | the generic `Bindings` contract: `types.ts` (runtime wire-stack interface + `EventNameOf`/`EventsOf`), `assemble.ts`, `default.ts` (built-in bindings) |
|
|
242
|
+
| `src/codegen/` | the schema→wire emitters (published — `@ignex/nova/generate` uses them at runtime) |
|
|
243
|
+
| `scripts/` | dev tooling: `generate.ts` orchestrator, prebuild/pack/release helpers (not published) |
|
|
244
|
+
| `src/generated/` | flatc `--ts`/`--rust` output + `registry.ts` + `direct-ser.ts` + `ts-ser.ts` (built-in schema) |
|
|
186
245
|
| `rust/` | cdylib: `ffi.rs` (C-ABI), `transcode/generated.rs` (glue) |
|
|
187
246
|
| `src/native/` | `bun:ffi` binding, self-tests, per-platform addon loader |
|
|
188
247
|
| `src/transport/` | `transport.ts` (`encodeToScratch`), `scratch.ts` (reusable zero-alloc buffer), `stats.ts` |
|
|
@@ -190,21 +249,25 @@ staged, and published to npm.
|
|
|
190
249
|
| `src/bridge/` | optional NATS bridge: `nats.ts` (injectable transport, eager non-blocking connect), `subjects.ts` (subject naming) |
|
|
191
250
|
| `public/server.ts` | entrypoint shim: `createServer` (publish/rooms/groups/targeting/NATS/auth/backpressure/metrics/drain) |
|
|
192
251
|
| `public/client.ts` | entrypoint shim: `createClient` (on/send/subscribe/joinGroup/reconnect/heartbeat/status) |
|
|
193
|
-
| `public/nats.ts` | entrypoint shim: `createNatsBridge` standalone (
|
|
252
|
+
| `public/nats.ts` | entrypoint shim: `createNatsBridge` standalone (`@ignex/nova/nats`) |
|
|
194
253
|
| `client/` | browser demo (built to `client-dist/`) |
|
|
195
254
|
| `bench/` | serialize latency + end-to-end throughput (+ `BASELINE.md` perf gate) |
|
|
196
255
|
| `examples/` | `nats-consumer.ts` — independent NATS consumer for bridged frames |
|
|
197
256
|
| `prebuilds/` | staged native addons per platform (`<platform>-<arch>/`), built by `bun run prebuild` / CI |
|
|
198
|
-
| `docs/` | `wire-format.md`, `architecture.md`, `publishing.md` |
|
|
257
|
+
| `docs/` | `wire-format.md`, `architecture.md`, `publishing.md`, `events.md`, `generic-bindings.md` |
|
|
199
258
|
|
|
200
259
|
## Adding an event
|
|
201
260
|
|
|
202
|
-
1. Define the payload in `schema/index.ts` (TypeBox), add
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
261
|
+
**Built-in registry:** 1. Define the payload in `schema/index.ts` (TypeBox), add
|
|
262
|
+
it to `schemas` (if it's a named table) and to `events` (name → schema). For
|
|
263
|
+
exact integer values use `Type.BigInt()`. 2. Re-run `bun run generate`,
|
|
264
|
+
`cargo build --release`, `bun run build:client`. 3.
|
|
265
|
+
`server.publish("yourEvent", payload)`, `client.on("yourEvent", cb)`, and
|
|
266
|
+
`client.send("yourEvent", payload)` are now fully typed on both sides.
|
|
267
|
+
|
|
268
|
+
**Your own registry:** you don't edit this repo at all — define the schema in
|
|
269
|
+
your app and run `generateBindings` (see
|
|
270
|
+
[docs/generic-bindings.md](docs/generic-bindings.md)).
|
|
208
271
|
|
|
209
272
|
## Performance (min-of-N ns/op on this machine, `bun run bench:serialize`)
|
|
210
273
|
|
|
@@ -216,7 +279,7 @@ staged, and published to npm.
|
|
|
216
279
|
|
|
217
280
|
Directable events serialize with **~0 B/op** (reusable scratch, no per-call
|
|
218
281
|
allocations, no JSON). End-to-end over a real WebSocket (`bench:throughput`):
|
|
219
|
-
**~1.
|
|
282
|
+
**~1.09M msg/s** — see `bench/BASELINE.md` for the perf gate.
|
|
220
283
|
|
|
221
284
|
## Server options (all optional)
|
|
222
285
|
|
|
@@ -229,9 +292,12 @@ createServer({
|
|
|
229
292
|
replay: { historySize: 64 }, // per-topic last-value replay on subscribe
|
|
230
293
|
authenticate: async (req) => checkToken(req.headers.get("authorization")),
|
|
231
294
|
allowedOrigins: ["http://localhost:3000"],
|
|
232
|
-
token: "shared-secret", // or (tok) => boolean
|
|
295
|
+
token: "shared-secret", // or (tok) => boolean — constant-time compared
|
|
233
296
|
maxConnections: 10_000,
|
|
234
297
|
maxMessageSize: 64 * 1024,
|
|
298
|
+
rateLimit: { messagesPerSecond: 100, burst: 200, policy: "drop" }, // per-conn inbound bucket ("close" → 1008)
|
|
299
|
+
authorizeTopic: (topic, ws) => topic !== "admin", // gate room joins (all paths)
|
|
300
|
+
authorizeGroup: (group, ws) => ws.data.userId != null, // gate group joins
|
|
235
301
|
int64Guard: "warn", // "off" | "throw" | "warn"
|
|
236
302
|
nats: { servers: ["nats://localhost:4222"], inbound: true }, // optional NATS bridge
|
|
237
303
|
tls: { keyFile, certFile }, // enables wss://
|
|
@@ -275,6 +341,43 @@ client.onStatus(() => console.log("my id:", client.clientId));
|
|
|
275
341
|
NATS consumers (any language) decode bridged frames from
|
|
276
342
|
`src/generated/fbs/backend.fbs` + the FNV-1a id map in `src/generated/wire-registry.json`
|
|
277
343
|
— see [docs/wire-format.md](docs/wire-format.md) and `examples/nats-consumer.ts`.
|
|
344
|
+
For your OWN schema, `generateBindings` emits the same `backend.fbs` +
|
|
345
|
+
`wire-registry.json` into your project — see
|
|
346
|
+
[docs/generic-bindings.md](docs/generic-bindings.md).
|
|
347
|
+
|
|
348
|
+
## Events layer (the event-driven system)
|
|
349
|
+
|
|
350
|
+
```ts
|
|
351
|
+
import { createServer } from "@ignex/nova/server";
|
|
352
|
+
import { on, emit, emitToUser } from "@ignex/nova/events"; // global singleton
|
|
353
|
+
|
|
354
|
+
const server = createServer({
|
|
355
|
+
port: 3000,
|
|
356
|
+
events: {
|
|
357
|
+
onConnect: (client) => client.data.set("since", client.connectedAt),
|
|
358
|
+
cluster: { nats: true }, // optional: cross-instance sync
|
|
359
|
+
},
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
// the events file — receive events (ctx carries the sender's client record)
|
|
363
|
+
on("chat.message", (payload, ctx) => {
|
|
364
|
+
emitToUser(payload.to, "chat.delivered", { id: payload.id });
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
// the global emit — send events through websockets from anywhere
|
|
368
|
+
emit("quote", { symbol: "AAPL", bid: 180.1, ask: 180.2 }); // broadcast
|
|
369
|
+
emitToUser("u-42", "order.update", { orderId: "o-1" }); // a user's sockets
|
|
370
|
+
emit("alert", { text: "halt" }, { type: "group", group: "traders" }); // to a group
|
|
371
|
+
|
|
372
|
+
// client records: id / userId (on whose behalf) / data / groups
|
|
373
|
+
server.events.client("c-1")?.data.set("tier", "gold");
|
|
374
|
+
server.events.clientsByUser("u-42"); // every device of u-42
|
|
375
|
+
server.events.userGroup("ops").add("u-42").emit("pager", { text: "…" });
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
Full API, cluster semantics, presence, shared-state (Redis) indexes and
|
|
379
|
+
performance notes: **[docs/events.md](docs/events.md)**. The events layer is
|
|
380
|
+
opt-in — without `events`, there is zero overhead.
|
|
278
381
|
|
|
279
382
|
## Publishing to npm
|
|
280
383
|
|
|
@@ -283,7 +386,7 @@ Publish directly from source — `bun publish` runs the release gate
|
|
|
283
386
|
|
|
284
387
|
```bash
|
|
285
388
|
bun run release:dry # plan only — print what would happen
|
|
286
|
-
bun run release # patch bump → verify → publish → commit
|
|
389
|
+
bun run release # patch bump → verify → publish → commit + tag (push only with `--push`)
|
|
287
390
|
bun run release minor # minor bump
|
|
288
391
|
bun run release --version 0.2.0 # explicit version
|
|
289
392
|
```
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Local Development with the Core Projects — `bun link`
|
|
2
|
+
|
|
3
|
+
> **Scope**: maintainers and AI agents working across the IgnEX core stack.
|
|
4
|
+
> Application developers consume published versions from npm and do **not**
|
|
5
|
+
> need this file.
|
|
6
|
+
|
|
7
|
+
## Why
|
|
8
|
+
|
|
9
|
+
The IgnEX core packages live side-by-side, one directory back from this repo,
|
|
10
|
+
in `/home/adeel/poc/`. When a change in this repo must be tested against the
|
|
11
|
+
local source of a core project (or a core change must be tested against this
|
|
12
|
+
repo), use `bun link` so the consumer resolves the local directory instead of
|
|
13
|
+
the npm registry. This is the supported Bun ≥ 1.4 (Rust-based runtime)
|
|
14
|
+
local-development mechanism
|
|
15
|
+
([`bun link` docs](https://bun.com/docs/cli/link),
|
|
16
|
+
[bun.com/blog/bun-v1.4](https://bun.com/blog/bun-v1.4)).
|
|
17
|
+
|
|
18
|
+
This workflow is **only for maintainers and AI agents working with the core
|
|
19
|
+
projects**. CI and release pipelines always resolve from the registry; local
|
|
20
|
+
links are a development-only convenience.
|
|
21
|
+
|
|
22
|
+
## Core packages (one directory back)
|
|
23
|
+
|
|
24
|
+
| Repo (`/home/adeel/poc/`) | Package(s) | `bun link` name |
|
|
25
|
+
| --- | --- | --- |
|
|
26
|
+
| `ignus` — ignex monorepo, workspaces `packages/*` | `@ignex/core`, `@ignex/cli`, `@ignex/compiler`, `@ignex/native`, `@ignex/shared`, `@ignex/mcp`, `@ignex/app`, `create-ignex` | run `bun link` inside each package dir |
|
|
27
|
+
| `bun-rust-runtime-bench` | `castrum` (Rust addon `castrum.<platform>-<arch>.node`) | `castrum` |
|
|
28
|
+
| `ignex-mongodb` | `@ignex/ninox` | `@ignex/ninox` |
|
|
29
|
+
| `ignex-nova` | `@ignex/nova` | `@ignex/nova` |
|
|
30
|
+
|
|
31
|
+
Known cross-repo edges (verify with `grep` in `package.json` before assuming):
|
|
32
|
+
|
|
33
|
+
- `@ignex/native` (in `ignus/packages/native`) depends on `castrum` — link
|
|
34
|
+
`castrum` there when changing the addon wire (`createNativeRoute`).
|
|
35
|
+
- `@ignex/core` (in `ignus/packages/core`) optionally depends on
|
|
36
|
+
`@ignex/nova`; `@ignex/app` depends on `@ignex/core`, `@ignex/cli`, and
|
|
37
|
+
`@ignex/ninox`.
|
|
38
|
+
|
|
39
|
+
## How to link
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# 1. Register the core package (once per machine, from the core repo):
|
|
43
|
+
cd /home/adeel/poc/ignex-mongodb
|
|
44
|
+
bun link # → Success! Registered "@ignex/ninox"
|
|
45
|
+
|
|
46
|
+
# 2. Link it into the consumer project:
|
|
47
|
+
cd /home/adeel/poc/ignex-app
|
|
48
|
+
bun link @ignex/ninox # symlinks node_modules/@ignex/ninox → ../ignex-mongodb
|
|
49
|
+
bun link @ignex/ninox --save # also writes "link:@ignex/ninox" into package.json deps
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
- `bun link` (no args) in a package directory registers that package globally
|
|
53
|
+
for this user.
|
|
54
|
+
- `bun link <name>` in a consumer creates a symlink in the consumer's
|
|
55
|
+
`node_modules` pointing at the registered directory. `--save` additionally
|
|
56
|
+
records `"<name>": "link:<name>"` in `package.json` dependencies.
|
|
57
|
+
- Unregister a package: `bun unlink` from the core repo dir.
|
|
58
|
+
- Return to registry versions: remove the `link:` entry (or `bun unlink
|
|
59
|
+
<name>` in the consumer) and `bun install`.
|
|
60
|
+
|
|
61
|
+
## Rust-core caveats (castrum & @ignex/nova)
|
|
62
|
+
|
|
63
|
+
Both `castrum` and `@ignex/nova` ship a Rust cdylib. After changing Rust
|
|
64
|
+
source, rebuild the addon **before** linking or using:
|
|
65
|
+
|
|
66
|
+
- `castrum`: `bun run build` (release `napi build`) or `bun run build:debug`.
|
|
67
|
+
Under Bun the addon is also called through `bun:ffi` from the same cdylib.
|
|
68
|
+
- `@ignex/nova`: `bun run build:rust`
|
|
69
|
+
(`cargo build --release --manifest-path rust/Cargo.toml`), or rely on
|
|
70
|
+
`prepack`/`prebuild` to stage `prebuilds/<platform>-<arch>/`.
|
|
71
|
+
|
|
72
|
+
A stale `.node`/`.so` silently serves old behavior — rebuild, then re-test.
|
|
73
|
+
`@ignex/nova` additionally fails its bind-time self-test on schema/wire-version
|
|
74
|
+
mismatch (`IGNEX_FFI_PATH` can point at a specific build); `castrum` falls back
|
|
75
|
+
to the napi transport when the `bun:ffi` self-test fails.
|
|
76
|
+
|
|
77
|
+
## Never publish from a linked tree
|
|
78
|
+
|
|
79
|
+
Publishing a consumer whose dependencies are `link:` entries ships symlinks,
|
|
80
|
+
not packages. Releases always run against registry versions (`prepublishOnly` /
|
|
81
|
+
CI re-verify with a clean install). Keep `bun link` strictly local.
|
package/docs/ai/TREE.md
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
# TREE — generated scaffolding map
|
|
2
|
+
|
|
3
|
+
> AUTO-GENERATED by `bun run gen:ai-map` (`scripts/gen-ai-map.ts`).
|
|
4
|
+
> Do not edit by hand — regenerate after structural changes. The
|
|
5
|
+
> curated maps live in `docs/architecture.md` + `docs/wire-format.md`
|
|
6
|
+
> (and `AGENTS.md` for agents).
|
|
7
|
+
|
|
8
|
+
- package: `@ignex/nova` v0.1.3
|
|
9
|
+
- engines: {"bun":">=1.4"}
|
|
10
|
+
- rust crate: `ignex-nova-ffi` v0.1.0
|
|
11
|
+
- scripts (20): `generate`, `build:rust`, `build:client`, `build:dist`, `build`, `prebuild`, `test`, `lint`, `typecheck`, `verify`, `pack:check`, `gen:ai-map`, …
|
|
12
|
+
|
|
13
|
+
## src/
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
src/
|
|
17
|
+
├─ bindings/
|
|
18
|
+
│ ├─ assemble.ts
|
|
19
|
+
│ ├─ default.ts
|
|
20
|
+
│ └─ types.ts
|
|
21
|
+
├─ bridge/
|
|
22
|
+
│ ├─ nats/
|
|
23
|
+
│ │ ├─ inbound.ts
|
|
24
|
+
│ │ ├─ index.ts
|
|
25
|
+
│ │ ├─ real-transport.ts
|
|
26
|
+
│ │ └─ types.ts
|
|
27
|
+
│ └─ subjects.ts
|
|
28
|
+
├─ codegen/
|
|
29
|
+
│ ├─ constants.ts
|
|
30
|
+
│ ├─ direct-gen.ts
|
|
31
|
+
│ ├─ fingerprint.ts
|
|
32
|
+
│ ├─ hash.ts
|
|
33
|
+
│ ├─ registry-gen.ts
|
|
34
|
+
│ ├─ rust-glue-gen.ts
|
|
35
|
+
│ ├─ schema-model.ts
|
|
36
|
+
│ ├─ ts-ser-gen.ts
|
|
37
|
+
│ └─ typebox-to-fbs.ts
|
|
38
|
+
├─ core/
|
|
39
|
+
│ ├─ server/
|
|
40
|
+
│ │ ├─ client-info.ts
|
|
41
|
+
│ │ ├─ http-routes.ts
|
|
42
|
+
│ │ ├─ index.ts
|
|
43
|
+
│ │ ├─ metrics-view.ts
|
|
44
|
+
│ │ └─ socket-lifecycle.ts
|
|
45
|
+
│ ├─ auth.ts
|
|
46
|
+
│ ├─ backpressure.ts
|
|
47
|
+
│ ├─ client-heartbeat.ts
|
|
48
|
+
│ ├─ client-reconnect.ts
|
|
49
|
+
│ ├─ client-rpc.ts
|
|
50
|
+
│ ├─ client-state.ts
|
|
51
|
+
│ ├─ client-wire.ts
|
|
52
|
+
│ ├─ client.ts
|
|
53
|
+
│ ├─ groups.ts
|
|
54
|
+
│ ├─ int64-guard.ts
|
|
55
|
+
│ ├─ metrics.ts
|
|
56
|
+
│ ├─ outbound.ts
|
|
57
|
+
│ ├─ rate-limit.ts
|
|
58
|
+
│ ├─ replay.ts
|
|
59
|
+
│ ├─ resume.ts
|
|
60
|
+
│ ├─ ring.ts
|
|
61
|
+
│ ├─ rooms.ts
|
|
62
|
+
│ ├─ routing.ts
|
|
63
|
+
│ ├─ state.ts
|
|
64
|
+
│ └─ topic-log.ts
|
|
65
|
+
├─ events/
|
|
66
|
+
│ ├─ cluster/
|
|
67
|
+
│ │ ├─ dedupe.ts
|
|
68
|
+
│ │ ├─ envelope.ts
|
|
69
|
+
│ │ ├─ index.ts
|
|
70
|
+
│ │ ├─ keys.ts
|
|
71
|
+
│ │ ├─ kinds.ts
|
|
72
|
+
│ │ ├─ presence-table.ts
|
|
73
|
+
│ │ ├─ presence.ts
|
|
74
|
+
│ │ ├─ redis-client.ts
|
|
75
|
+
│ │ ├─ store-memory.ts
|
|
76
|
+
│ │ ├─ store-redis.ts
|
|
77
|
+
│ │ ├─ subjects.ts
|
|
78
|
+
│ │ ├─ sync.ts
|
|
79
|
+
│ │ ├─ transport-nats.ts
|
|
80
|
+
│ │ └─ transport-redis.ts
|
|
81
|
+
│ ├─ hub/
|
|
82
|
+
│ │ ├─ context-factory.ts
|
|
83
|
+
│ │ ├─ dispatch.ts
|
|
84
|
+
│ │ ├─ index.ts
|
|
85
|
+
│ │ ├─ internal.ts
|
|
86
|
+
│ │ ├─ metrics-snapshot.ts
|
|
87
|
+
│ │ └─ resolve-cluster.ts
|
|
88
|
+
│ ├─ types/
|
|
89
|
+
│ │ ├─ client.ts
|
|
90
|
+
│ │ ├─ cluster.ts
|
|
91
|
+
│ │ ├─ context.ts
|
|
92
|
+
│ │ ├─ emit-target.ts
|
|
93
|
+
│ │ ├─ groups.ts
|
|
94
|
+
│ │ ├─ hub.ts
|
|
95
|
+
│ │ ├─ index.ts
|
|
96
|
+
│ │ ├─ metrics.ts
|
|
97
|
+
│ │ └─ options.ts
|
|
98
|
+
│ ├─ clients.ts
|
|
99
|
+
│ ├─ cluster-rpc.ts
|
|
100
|
+
│ ├─ data.ts
|
|
101
|
+
│ ├─ delivery.ts
|
|
102
|
+
│ ├─ emit.ts
|
|
103
|
+
│ ├─ global.ts
|
|
104
|
+
│ ├─ groups.ts
|
|
105
|
+
│ ├─ index.ts
|
|
106
|
+
│ ├─ queue.ts
|
|
107
|
+
│ ├─ registry.ts
|
|
108
|
+
│ ├─ schedule.ts
|
|
109
|
+
│ └─ trace.ts
|
|
110
|
+
├─ generated/
|
|
111
|
+
│ ├─ fbs/
|
|
112
|
+
│ │ └─ backend.fbs
|
|
113
|
+
│ ├─ rust/
|
|
114
|
+
│ │ └─ backend_generated.rs
|
|
115
|
+
│ ├─ ts/
|
|
116
|
+
│ │ ├─ backend.ts
|
|
117
|
+
│ │ ├─ big-val.ts
|
|
118
|
+
│ │ ├─ complex.ts
|
|
119
|
+
│ │ ├─ customer.ts
|
|
120
|
+
│ │ ├─ hello.ts
|
|
121
|
+
│ │ ├─ join-group.ts
|
|
122
|
+
│ │ ├─ leave-group.ts
|
|
123
|
+
│ │ ├─ order-billing.ts
|
|
124
|
+
│ │ ├─ order-line.ts
|
|
125
|
+
│ │ ├─ order.ts
|
|
126
|
+
│ │ ├─ ping.ts
|
|
127
|
+
│ │ ├─ pong.ts
|
|
128
|
+
│ │ ├─ portfolio-position.ts
|
|
129
|
+
│ │ ├─ portfolio-snapshot.ts
|
|
130
|
+
│ │ ├─ quote.ts
|
|
131
|
+
│ │ ├─ resume.ts
|
|
132
|
+
│ │ ├─ resumed.ts
|
|
133
|
+
│ │ ├─ rpc-call.ts
|
|
134
|
+
│ │ ├─ rpc-result.ts
|
|
135
|
+
│ │ ├─ side.ts
|
|
136
|
+
│ │ ├─ snapshot-request.ts
|
|
137
|
+
│ │ ├─ subscribe.ts
|
|
138
|
+
│ │ ├─ tags.ts
|
|
139
|
+
│ │ ├─ trade.ts
|
|
140
|
+
│ │ ├─ unsubscribe.ts
|
|
141
|
+
│ │ └─ welcome.ts
|
|
142
|
+
│ ├─ direct-ser.ts
|
|
143
|
+
│ ├─ registry.ts
|
|
144
|
+
│ ├─ ts-ser.ts
|
|
145
|
+
│ └─ wire-registry.json
|
|
146
|
+
├─ native/
|
|
147
|
+
│ ├─ codec.ts
|
|
148
|
+
│ ├─ ffi.ts
|
|
149
|
+
│ └─ loader.ts
|
|
150
|
+
├─ schema/
|
|
151
|
+
│ └─ index.ts
|
|
152
|
+
├─ transport/
|
|
153
|
+
│ ├─ byte-buffer-pool.ts
|
|
154
|
+
│ ├─ scratch.ts
|
|
155
|
+
│ └─ transport.ts
|
|
156
|
+
└─ server.ts
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## rust/
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
rust/
|
|
163
|
+
├─ examples/
|
|
164
|
+
│ └─ dump.rs
|
|
165
|
+
├─ src/
|
|
166
|
+
│ ├─ generated/
|
|
167
|
+
│ │ ├─ backend.rs
|
|
168
|
+
│ │ └─ mod.rs
|
|
169
|
+
│ ├─ transcode/
|
|
170
|
+
│ │ ├─ generated.rs
|
|
171
|
+
│ │ └─ mod.rs
|
|
172
|
+
│ ├─ ffi.rs
|
|
173
|
+
│ └─ lib.rs
|
|
174
|
+
├─ .npmignore
|
|
175
|
+
├─ Cargo.lock
|
|
176
|
+
└─ Cargo.toml
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## public/
|
|
180
|
+
|
|
181
|
+
```
|
|
182
|
+
public/
|
|
183
|
+
├─ bindings.ts
|
|
184
|
+
├─ client.ts
|
|
185
|
+
├─ events.ts
|
|
186
|
+
├─ generate.ts
|
|
187
|
+
├─ internal.ts
|
|
188
|
+
├─ nats.ts
|
|
189
|
+
└─ server.ts
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## client/
|
|
193
|
+
|
|
194
|
+
```
|
|
195
|
+
client/
|
|
196
|
+
├─ index.html
|
|
197
|
+
└─ main.ts
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## test/
|
|
201
|
+
|
|
202
|
+
```
|
|
203
|
+
test/
|
|
204
|
+
├─ api.test.ts
|
|
205
|
+
├─ auth.test.ts
|
|
206
|
+
├─ backpressure.test.ts
|
|
207
|
+
├─ bidirectional.test.ts
|
|
208
|
+
├─ bindings-gen.test.ts
|
|
209
|
+
├─ byte-buffer-pool.test.ts
|
|
210
|
+
├─ cluster-v2.test.ts
|
|
211
|
+
├─ data-integrity-edge.test.ts
|
|
212
|
+
├─ direct.test.ts
|
|
213
|
+
├─ e2e.test.ts
|
|
214
|
+
├─ efficiency.test.ts
|
|
215
|
+
├─ events-cluster.test.ts
|
|
216
|
+
├─ events.test.ts
|
|
217
|
+
├─ ffi.test.ts
|
|
218
|
+
├─ groups.test.ts
|
|
219
|
+
├─ helpers.ts
|
|
220
|
+
├─ int64.test.ts
|
|
221
|
+
├─ integrity.test.ts
|
|
222
|
+
├─ loader.test.ts
|
|
223
|
+
├─ metrics.test.ts
|
|
224
|
+
├─ nats-bridge.test.ts
|
|
225
|
+
├─ nats-integration.test.ts
|
|
226
|
+
├─ performance.test.ts
|
|
227
|
+
├─ reconnect.test.ts
|
|
228
|
+
├─ resume.test.ts
|
|
229
|
+
├─ ring.test.ts
|
|
230
|
+
├─ rooms.test.ts
|
|
231
|
+
├─ roundtrip.test.ts
|
|
232
|
+
├─ rpc.test.ts
|
|
233
|
+
├─ security-hardening.test.ts
|
|
234
|
+
├─ security-live-fuzz.test.ts
|
|
235
|
+
├─ security.test.ts
|
|
236
|
+
├─ stability-resilience.test.ts
|
|
237
|
+
├─ targeting.test.ts
|
|
238
|
+
├─ trace.test.ts
|
|
239
|
+
└─ wire.test.ts
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## bench/
|
|
243
|
+
|
|
244
|
+
```
|
|
245
|
+
bench/
|
|
246
|
+
├─ BASELINE.md
|
|
247
|
+
├─ dispatch.ts
|
|
248
|
+
├─ ffi-margin.ts
|
|
249
|
+
├─ measure.ts
|
|
250
|
+
├─ serialize.ts
|
|
251
|
+
└─ throughput.ts
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## scripts/
|
|
255
|
+
|
|
256
|
+
```
|
|
257
|
+
scripts/
|
|
258
|
+
├─ build-prebuild.ts
|
|
259
|
+
├─ check-pack.ts
|
|
260
|
+
├─ gen-ai-map.ts
|
|
261
|
+
├─ generate.ts
|
|
262
|
+
└─ release.ts
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## examples/
|
|
266
|
+
|
|
267
|
+
```
|
|
268
|
+
examples/
|
|
269
|
+
├─ consumer.ts
|
|
270
|
+
├─ events.ts
|
|
271
|
+
├─ generic-schema.ts
|
|
272
|
+
└─ nats-consumer.ts
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## docs/
|
|
276
|
+
|
|
277
|
+
```
|
|
278
|
+
docs/
|
|
279
|
+
├─ ai/
|
|
280
|
+
│ ├─ LOCAL_DEV.md
|
|
281
|
+
│ └─ TREE.md
|
|
282
|
+
├─ architecture.md
|
|
283
|
+
├─ events.md
|
|
284
|
+
├─ generic-bindings.md
|
|
285
|
+
├─ publishing.md
|
|
286
|
+
└─ wire-format.md
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## Notes
|
|
290
|
+
|
|
291
|
+
- `SKIP` set in the script excludes build output (target/, dist/, client-dist/, prebuilds/, node_modules/).
|
|
292
|
+
- `src/generated/`, `rust/src/generated/`, `rust/src/transcode/generated.rs` are regenerated by `bun run generate` — never hand-edit.
|