@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/docs/wire-format.md
CHANGED
|
@@ -8,18 +8,25 @@ client from the generated FlatBuffers schema — no Bun, no Rust, no FFI needed.
|
|
|
8
8
|
Every WebSocket **binary** frame is:
|
|
9
9
|
|
|
10
10
|
```
|
|
11
|
-
|
|
12
|
-
│ version │ event_id │ size-prefixed FlatBuffer │
|
|
13
|
-
│ 1 byte │ u32 (LE) │ (flatc: 4-byte size prefix + buf)│
|
|
14
|
-
|
|
15
|
-
offset 0 1..5
|
|
11
|
+
┌──────────┬───────────────────┬─────────┬────────────┬──────────────────────────────────┐
|
|
12
|
+
│ version │ event_id │ flags │ seq │ size-prefixed FlatBuffer │
|
|
13
|
+
│ 1 byte │ u32 (LE) │ 1 byte │ u64 (LE) │ (flatc: 4-byte size prefix + buf)│
|
|
14
|
+
└──────────┴───────────────────┴─────────┴────────────┴──────────────────────────────────┘
|
|
15
|
+
offset 0 1..5 5 6..14 14..
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
- `version` = `WIRE_VERSION` (currently `
|
|
19
|
-
version it doesn't recognize (the decoders
|
|
18
|
+
- `version` = `WIRE_VERSION` (currently `2` — v2 added the delivery header).
|
|
19
|
+
A peer MUST drop frames whose version it doesn't recognize (the decoders
|
|
20
|
+
return `null`).
|
|
20
21
|
- `event_id` = **FNV-1a 32-bit hash of the event name** (e.g.
|
|
21
22
|
`fnv1a32("quote")`). Stable across schema reordering; collisions are rejected
|
|
22
23
|
at generate time. The id→name table is emitted in `src/generated/registry.ts`.
|
|
24
|
+
- `flags` — bit0 = "seq-valid" (see below); all other bits reserved, send 0.
|
|
25
|
+
- `seq` — per-CONNECTION delivery sequence, stamped by the SERVER on every
|
|
26
|
+
**app** frame just before `ws.send` (Bun copies synchronously). Clients use
|
|
27
|
+
it for gap detection + resume. Control frames are never stamped; frames that
|
|
28
|
+
were not per-destination stamped carry `flags=0, seq=0`. Offsets are derived
|
|
29
|
+
from `WIRE_HEADER_LEN`: flags at `len-9`, seq at `len-8..len`.
|
|
23
30
|
- The payload is a **size-prefixed FlatBuffer** (built by flatc, both Rust
|
|
24
31
|
builders and the TS object API) with the root table for that event id.
|
|
25
32
|
|
|
@@ -27,6 +34,18 @@ There is **no per-frame checksum and no authentication** — the envelope id byt
|
|
|
27
34
|
is trusted. Integrity and AuthN are the application's job (see
|
|
28
35
|
[Security](#security)).
|
|
29
36
|
|
|
37
|
+
## String fields carry raw UTF-8
|
|
38
|
+
|
|
39
|
+
A FlatBuffer `string` field stores its content as raw UTF-8 bytes in the frame,
|
|
40
|
+
so any JSON document placed in a string field appears **verbatim** inside the
|
|
41
|
+
(binary) frame — expected behavior, not corruption. If a captured frame shows
|
|
42
|
+
`{"items":[...]}` text in the payload, the event schema models that data as a
|
|
43
|
+
`string` field (a JSON-in-a-string envelope, or a field written as
|
|
44
|
+
`Type.Any()`/`Type.Unknown()`, which the codegen now rejects at generate time —
|
|
45
|
+
see docs/generic-bindings.md). Prefer typed fields — `Type.Array(Table)` for
|
|
46
|
+
lists, `Type.Integer()` for counts — so the frame carries structure instead of
|
|
47
|
+
JSON text.
|
|
48
|
+
|
|
30
49
|
## Event ids
|
|
31
50
|
|
|
32
51
|
Event ids are stable hashes, so adding events, reordering the registry, or
|
|
@@ -46,13 +65,17 @@ SAME codegen, but are routed internally (never delivered to app handlers):
|
|
|
46
65
|
|
|
47
66
|
| name | direction | payload |
|
|
48
67
|
| --- | --- | --- |
|
|
49
|
-
| `hello` | both | `{ version, caps: string[], lastSeq }` — sent on connect; version mismatch → close(1002) |
|
|
68
|
+
| `hello` | both | `{ version, caps: string[], lastSeq }` — sent on connect; version mismatch → close(1002). `lastSeq > 0` asks the server to resume this client id's delivery stream after that seq (cross-session resume) |
|
|
50
69
|
| `welcome` | server→client | `{ clientId, groups: string[] }` — identity assigned to this connection (auth metadata or UUID) + its server-side groups |
|
|
51
70
|
| `subscribe` | client→server | `{ topic }` — join a room (server replies with replay history) |
|
|
52
71
|
| `unsubscribe` | client→server | `{ topic }` |
|
|
53
72
|
| `joinGroup` | client→server | `{ group }` — join a server-side group |
|
|
54
73
|
| `leaveGroup` | client→server | `{ group }` |
|
|
55
|
-
| `snapshotRequest` | client→server | `{ topic }` —
|
|
74
|
+
| `snapshotRequest` | client→server | `{ topic, fromSeq }` — replay recorded topic history STRICTLY after `fromSeq` (0 = from the beginning of retained history); hydrates from the durable topic log when the in-memory ring has moved on |
|
|
75
|
+
| `resume` | client→server | `{ lastSeq }` — same-connection gap recovery: re-send everything after the last CONTIGUOUS delivery seq (original seqs preserved) |
|
|
76
|
+
| `resumed` | server→client | `{ ok: boolean, from: number }` — ack before the replayed frames; `ok:false` = the hole is older than the retained history (partial recovery — resubscribe topics for a fresh snapshot) |
|
|
77
|
+
| `rpcCall` | client→server | `{ id, name, payloadB64 }` — request/response: `payloadB64` is base64 of a full wire frame encoded with event `name`'s schema; the response reuses the SAME schema |
|
|
78
|
+
| `rpcResult` | server→client | `{ id, ok, err, payloadB64 }` — correlated by `id`; `ok:false` carries a plain-text `err` |
|
|
56
79
|
| `ping` | client→server | `{ ts }` — heartbeat |
|
|
57
80
|
| `pong` | server→client | `{ ts }` |
|
|
58
81
|
|
|
@@ -113,16 +136,23 @@ Bridge state is observable via `server.getMetrics()`
|
|
|
113
136
|
### Decoding bridged frames (external consumers)
|
|
114
137
|
|
|
115
138
|
`bun run generate` emits `src/generated/wire-registry.json` — a machine-readable
|
|
116
|
-
`{ version, events: { name: id } }` map of the FNV-1a ids. A
|
|
117
|
-
language:
|
|
139
|
+
`{ version, fingerprint, events: { name: id } }` map of the FNV-1a ids. A
|
|
140
|
+
consumer in any language:
|
|
118
141
|
|
|
119
142
|
1. reads `version` (byte 0) and `event_id` (bytes 1..5) from the frame,
|
|
120
143
|
2. maps `event_id` → name via `wire-registry.json`,
|
|
121
|
-
3. decodes the size-prefixed FlatBuffer from `frame[
|
|
144
|
+
3. decodes the size-prefixed FlatBuffer from `frame[14..]` with a flatc build of
|
|
122
145
|
`src/generated/fbs/backend.fbs`.
|
|
123
146
|
|
|
124
147
|
See `examples/nats-consumer.ts` for a working reference.
|
|
125
148
|
|
|
149
|
+
For YOUR OWN schema, `generateBindings` (see
|
|
150
|
+
[docs/generic-bindings.md](generic-bindings.md)) emits the same
|
|
151
|
+
`backend.fbs` + `wire-registry.json` into your project, plus a `fingerprint`
|
|
152
|
+
field — the cdylib and the generated registry share it, so a schema-mismatched
|
|
153
|
+
native addon fails the bind-time self-test instead of emitting undecodable
|
|
154
|
+
frames.
|
|
155
|
+
|
|
126
156
|
## Heartbeat
|
|
127
157
|
|
|
128
158
|
Clients send `ping` every `heartbeatMs` (default 15000). The server replies
|
|
@@ -130,12 +160,26 @@ Clients send `ping` every `heartbeatMs` (default 15000). The server replies
|
|
|
130
160
|
long-idle sockets alive; a client that misses `heartbeatMisses` pongs force-
|
|
131
161
|
closes and reconnects.
|
|
132
162
|
|
|
133
|
-
## Sequence /
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
163
|
+
## Sequence / resume / request-response
|
|
164
|
+
|
|
165
|
+
- **Delivery seqs (v2)**: when the server starts with `resume`, every app frame
|
|
166
|
+
it writes to a socket carries an increasing per-connection `seq` (envelope
|
|
167
|
+
flags bit0). Clients track the stream; a GAP (lost frame — backpressure drop,
|
|
168
|
+
transport hiccup) is recovered by sending `resume { lastSeq }`; the server
|
|
169
|
+
replays from the connection's bounded sent-history ring with ORIGINAL seqs so
|
|
170
|
+
redelivery is in-order and duplicate-free. On disconnect the ring parks in a
|
|
171
|
+
per-client-id graveyard (bounded, TTL'd); a reconnecting session with the
|
|
172
|
+
same auth-pinned id continues the stream via `hello { lastSeq }`.
|
|
173
|
+
- **Topic snapshots**: `snapshotRequest { topic, fromSeq }` replays recorded
|
|
174
|
+
topic history strictly after a topic seq; a durable `topicLog`
|
|
175
|
+
(`createServer({ topicLog })`, e.g. `createMemoryTopicLog()`) hydrates ranges
|
|
176
|
+
the replay ring has already forgotten. Topic-history seqs are a SEPARATE
|
|
177
|
+
counter (global replay order) from per-connection delivery seqs.
|
|
178
|
+
- **Request/response**: `client.request(name, payload)` wraps a full wire frame
|
|
179
|
+
(base64) in an `rpcCall` control frame with a correlation id; the server's
|
|
180
|
+
registered responder (via `server.handle(name, fn)` or `hub.onRequest`)
|
|
181
|
+
returns the response payload encoded with the SAME event schema. Timeouts and
|
|
182
|
+
errors are correlated client-side.
|
|
139
183
|
|
|
140
184
|
## Security
|
|
141
185
|
|
|
@@ -144,7 +188,17 @@ the envelope reserves no space for a per-frame seq today.
|
|
|
144
188
|
happily decode the payload as the claimed type. Add integrity at the
|
|
145
189
|
application layer if the wire crosses an untrusted boundary.
|
|
146
190
|
- Server-side guards (all optional): `authenticate(req)` async hook, origin
|
|
147
|
-
allowlist, bearer `token
|
|
191
|
+
allowlist, bearer `token` (literal tokens compared in constant time),
|
|
192
|
+
`maxConnections`, `maxMessageSize`, per-connection inbound rate limiting
|
|
193
|
+
(`rateLimit`: token bucket over app AND control frames — drop or close 1008,
|
|
194
|
+
counted in `metrics.rateLimited`).
|
|
195
|
+
- Join authorization: `authorizeTopic(topic, ws)` / `authorizeGroup(group, ws)`
|
|
196
|
+
gate EVERY room/group join path (control frames, programmatic joins, and
|
|
197
|
+
auth-seeded membership); rejections are counted in `metrics.rejectedJoins`.
|
|
198
|
+
Leaving is always allowed.
|
|
199
|
+
- Introspection: `GET /clients` is gated by the same token/`authenticate`
|
|
200
|
+
surface when one is configured (public only on servers with no auth at all);
|
|
201
|
+
`/health` stays public (counters only).
|
|
148
202
|
- Slow consumers: `backpressure` policy (`drop-oldest`/`drop-newest`/
|
|
149
203
|
`disconnect`) bounds per-socket buffering; without it a hot publish loop can
|
|
150
204
|
balloon memory.
|
|
@@ -161,7 +215,7 @@ the envelope reserves no space for a per-frame seq today.
|
|
|
161
215
|
3. Read `version` (byte 0) and `event_id` (bytes 1..5) from each binary frame.
|
|
162
216
|
Compute ids with FNV-1a 32 over the name (see above) or read the emitted
|
|
163
217
|
`src/generated/registry.ts` table.
|
|
164
|
-
4. `flatbuffers.ByteBuffer(frame[
|
|
218
|
+
4. `flatbuffers.ByteBuffer(frame[14..])` + `getSizePrefixedRootAs<T>` (or the
|
|
165
219
|
language equivalent) gives you the payload.
|
|
166
220
|
5. Implement the control frames (at minimum `hello` + `ping`/`pong`) to be a
|
|
167
221
|
good citizen; `subscribe` for rooms.
|
package/index.ts
CHANGED
|
@@ -2,45 +2,61 @@
|
|
|
2
2
|
* ignex-nova — public package root.
|
|
3
3
|
*
|
|
4
4
|
* Re-exports the full typed pub/sub API (server + client + NATS bridge +
|
|
5
|
-
* schema types) so a single `import ... from
|
|
6
|
-
* projects.
|
|
5
|
+
* schema types + generic bindings codegen) so a single `import ... from
|
|
6
|
+
* "@ignex/nova"` works in Bun projects.
|
|
7
7
|
*
|
|
8
8
|
* For leaner / target-specific imports use the subpath entrypoints — each
|
|
9
9
|
* resolves to its own source file and tree-shakes independently:
|
|
10
10
|
*
|
|
11
|
-
* import { createServer } from "ignex
|
|
12
|
-
* import { createClient } from "ignex
|
|
13
|
-
* import { createNatsBridge } from "ignex
|
|
11
|
+
* import { createServer } from "@ignex/nova/server"; // Bun-only (Rust FFI)
|
|
12
|
+
* import { createClient } from "@ignex/nova/client"; // browser + Bun
|
|
13
|
+
* import { createNatsBridge } from "@ignex/nova/nats"; // standalone bridge
|
|
14
|
+
* import { on, emit } from "@ignex/nova/events"; // events layer + global emit
|
|
15
|
+
* import { generateBindings } from "@ignex/nova/generate"; // ANY-schema codegen
|
|
16
|
+
* import { assembleBindings, defaultBindings } from "@ignex/nova/bindings";
|
|
17
|
+
* import { encodeUtf8Into } from "@ignex/nova/internal"; // codegen helpers
|
|
14
18
|
*
|
|
15
19
|
* Note: the root entry also pulls in the Bun-only server + FFI path, so
|
|
16
|
-
* browser bundles should import "ignex
|
|
20
|
+
* browser bundles should import "@ignex/nova/client" instead.
|
|
17
21
|
*
|
|
18
22
|
* The README (and docs/publishing.md) covers consuming this from an npm
|
|
19
|
-
* package: `bun add ignex
|
|
23
|
+
* package: `bun add @ignex/nova`, then use the subpaths above.
|
|
20
24
|
*/
|
|
21
|
-
export { createServer } from "./public/server";
|
|
22
|
-
export type {
|
|
23
|
-
IgnServer,
|
|
24
|
-
ClientInfo,
|
|
25
|
-
IgnServerOptions,
|
|
26
|
-
IgnBackpressureOptions,
|
|
27
|
-
BackpressurePolicy,
|
|
28
|
-
WsData,
|
|
29
|
-
ClientMeta,
|
|
30
|
-
AuthResult,
|
|
31
|
-
MetricsSnapshot,
|
|
32
|
-
Int64GuardMode,
|
|
33
|
-
} from "./public/server";
|
|
34
25
|
|
|
35
|
-
export { createClient } from "./public/client";
|
|
36
26
|
export type {
|
|
27
|
+
AssembleOptions,
|
|
28
|
+
Bindings,
|
|
29
|
+
BindingsParts,
|
|
30
|
+
ControlEventNameOf,
|
|
31
|
+
ControlEventsOf,
|
|
32
|
+
DefaultBindings,
|
|
33
|
+
DirectCall,
|
|
34
|
+
DirectEncoder,
|
|
35
|
+
DirectTables,
|
|
36
|
+
EventNameOf,
|
|
37
|
+
EventsOf,
|
|
38
|
+
} from "./public/bindings";
|
|
39
|
+
export { assembleBindings, defaultBindings } from "./public/bindings";
|
|
40
|
+
export type {
|
|
41
|
+
ClientStatus,
|
|
37
42
|
IgnClient,
|
|
38
43
|
IgnClientOptions,
|
|
39
44
|
IgnReconnectOptions,
|
|
40
|
-
ClientStatus,
|
|
41
45
|
} from "./public/client";
|
|
42
46
|
|
|
43
|
-
export {
|
|
47
|
+
export { createClient } from "./public/client";
|
|
48
|
+
export type { GeneratedBindings, GenerateOptions, SchemaRegistry } from "./public/generate";
|
|
49
|
+
// ── generic bindings (ANY schema) ──────────────────────────────────────────
|
|
50
|
+
export { generateBindings } from "./public/generate";
|
|
51
|
+
// runtime helpers used by generated code (also exported via `@ignex/nova/internal`)
|
|
52
|
+
export {
|
|
53
|
+
checkInt64,
|
|
54
|
+
encodeUtf8Into,
|
|
55
|
+
ensureCapacity,
|
|
56
|
+
pooledByteBuffer,
|
|
57
|
+
setInt64GuardMode,
|
|
58
|
+
utf8Len,
|
|
59
|
+
} from "./public/internal";
|
|
44
60
|
export type {
|
|
45
61
|
NatsBridge,
|
|
46
62
|
NatsBridgeOptions,
|
|
@@ -49,13 +65,45 @@ export type {
|
|
|
49
65
|
NatsTransport,
|
|
50
66
|
SubjectBuilder,
|
|
51
67
|
} from "./public/nats";
|
|
68
|
+
export { createNatsBridge, createSubjectBuilder } from "./public/nats";
|
|
69
|
+
// events layer types (the runtime singleton API is `@ignex/nova/events`)
|
|
70
|
+
export type {
|
|
71
|
+
AuthResult,
|
|
72
|
+
BackpressurePolicy,
|
|
73
|
+
ClientData,
|
|
74
|
+
ClientGroup,
|
|
75
|
+
ClientInfo,
|
|
76
|
+
ClientMeta,
|
|
77
|
+
ClusterStateStore,
|
|
78
|
+
ClusterTransport,
|
|
79
|
+
EmitTarget,
|
|
80
|
+
EmitTargetKind,
|
|
81
|
+
EventClient,
|
|
82
|
+
EventContext,
|
|
83
|
+
EventHandler,
|
|
84
|
+
EventsClusterOptions,
|
|
85
|
+
EventsHub,
|
|
86
|
+
EventsMetricsSnapshot,
|
|
87
|
+
EventsOptions,
|
|
88
|
+
IgnBackpressureOptions,
|
|
89
|
+
IgnServer,
|
|
90
|
+
IgnServerOptions,
|
|
91
|
+
Int64GuardMode,
|
|
92
|
+
MetricsSnapshot,
|
|
93
|
+
RedisConnectionOptions,
|
|
94
|
+
RemoteClient,
|
|
95
|
+
ServerEventHandler,
|
|
96
|
+
UserGroup,
|
|
97
|
+
WsData,
|
|
98
|
+
} from "./public/server";
|
|
99
|
+
export { createServer } from "./public/server";
|
|
52
100
|
|
|
53
101
|
// Plain-object payload types consumers type against (e.g. `Events["quote"]`).
|
|
54
102
|
// Import as types only — the runtime `events` registry is transport-internal.
|
|
55
103
|
export type {
|
|
56
|
-
Events,
|
|
57
|
-
EventName,
|
|
58
104
|
AnyEventName,
|
|
59
|
-
ControlEvents,
|
|
60
105
|
ControlEventName,
|
|
61
|
-
|
|
106
|
+
ControlEvents,
|
|
107
|
+
EventName,
|
|
108
|
+
Events,
|
|
109
|
+
} from "./src/schema";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ignex/nova",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "TypeBox-driven FlatBuffer transport: Rust FFI serializer + Bun WebSocket + typed pub/sub API for server and FE.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -48,6 +48,10 @@
|
|
|
48
48
|
"./server": "./public/server.ts",
|
|
49
49
|
"./client": "./public/client.ts",
|
|
50
50
|
"./nats": "./public/nats.ts",
|
|
51
|
+
"./events": "./public/events.ts",
|
|
52
|
+
"./bindings": "./public/bindings.ts",
|
|
53
|
+
"./generate": "./public/generate.ts",
|
|
54
|
+
"./internal": "./public/internal.ts",
|
|
51
55
|
"./package.json": "./package.json"
|
|
52
56
|
},
|
|
53
57
|
"publishConfig": {
|
|
@@ -57,7 +61,7 @@
|
|
|
57
61
|
"generate": "bun scripts/generate.ts",
|
|
58
62
|
"build:rust": "cargo build --release --manifest-path rust/Cargo.toml",
|
|
59
63
|
"build:client": "bun build ./client/main.ts --outdir ./client-dist --target=browser --splitting",
|
|
60
|
-
"build:dist": "bun build ./index.ts ./public/server.ts ./public/client.ts ./public/nats.ts --outdir ./dist --target=bun --splitting && bunx tsc --emitDeclarationOnly -p tsconfig.dist.json",
|
|
64
|
+
"build:dist": "bun build ./index.ts ./public/server.ts ./public/client.ts ./public/nats.ts ./public/events.ts ./public/bindings.ts ./public/generate.ts ./public/internal.ts --outdir ./dist --target=bun --splitting && bunx tsc --emitDeclarationOnly -p tsconfig.dist.json",
|
|
61
65
|
"build": "bun run generate && bun run build:rust && bun run build:client",
|
|
62
66
|
"prebuild": "bun scripts/build-prebuild.ts",
|
|
63
67
|
"test": "bun test",
|
|
@@ -65,8 +69,10 @@
|
|
|
65
69
|
"typecheck": "bunx tsc --noEmit -p tsconfig.json",
|
|
66
70
|
"verify": "bun run typecheck && bun run lint && bun test",
|
|
67
71
|
"pack:check": "bun scripts/check-pack.ts",
|
|
72
|
+
"gen:ai-map": "bun scripts/gen-ai-map.ts",
|
|
68
73
|
"serve": "bun run src/server.ts",
|
|
69
74
|
"bench:serialize": "bun run bench/serialize.ts",
|
|
75
|
+
"bench:dispatch": "bun run bench/dispatch.ts",
|
|
70
76
|
"bench:throughput": "bun run bench/throughput.ts",
|
|
71
77
|
"release": "bun scripts/release.ts",
|
|
72
78
|
"release:dry": "bun scripts/release.ts --dry-run",
|
|
@@ -85,5 +91,10 @@
|
|
|
85
91
|
},
|
|
86
92
|
"peerDependencies": {
|
|
87
93
|
"typescript": "^7"
|
|
94
|
+
},
|
|
95
|
+
"peerDependenciesMeta": {
|
|
96
|
+
"ioredis": {
|
|
97
|
+
"optional": true
|
|
98
|
+
}
|
|
88
99
|
}
|
|
89
100
|
}
|
|
Binary file
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public bindings API — `@ignex/nova/bindings`.
|
|
3
|
+
*
|
|
4
|
+
* import { assembleBindings, defaultBindings } from "@ignex/nova/bindings";
|
|
5
|
+
* import type { Bindings, EventsOf } from "@ignex/nova/bindings";
|
|
6
|
+
*
|
|
7
|
+
* `defaultBindings` is the built-in registry's wire stack; `assembleBindings`
|
|
8
|
+
* builds a `Bindings` from generated parts (see `@ignex/nova/generate`).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export type { AssembleOptions, BindingsParts } from "../src/bindings/assemble";
|
|
12
|
+
export { assembleBindings } from "../src/bindings/assemble";
|
|
13
|
+
export { defaultBindings } from "../src/bindings/default";
|
|
14
|
+
export type {
|
|
15
|
+
Bindings,
|
|
16
|
+
ControlEventNameOf,
|
|
17
|
+
ControlEventsOf,
|
|
18
|
+
DefaultBindings,
|
|
19
|
+
DirectCall,
|
|
20
|
+
DirectEncoder,
|
|
21
|
+
DirectTables,
|
|
22
|
+
EventNameOf,
|
|
23
|
+
EventsOf,
|
|
24
|
+
} from "../src/bindings/types";
|
package/public/client.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* build stable). The implementation lives in the functional modules under
|
|
4
4
|
* `src/core/`; this file just exposes the public surface.
|
|
5
5
|
*
|
|
6
|
-
* import { createClient } from "ignex
|
|
6
|
+
* import { createClient } from "@ignex/nova/client";
|
|
7
7
|
*
|
|
8
8
|
* const client = createClient("ws://localhost:3000/ws", { reconnect: true });
|
|
9
9
|
* client.on("quote", (q) => { /* q: Events["quote"] — a plain object *\/ });
|
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
* client.send("chat", {...}); // typed client→server (server must allow it)
|
|
12
12
|
* client.connect();
|
|
13
13
|
*
|
|
14
|
+
* // your own schema (see @ignex/nova/generate):
|
|
15
|
+
* const client = createClient("ws://localhost:3000/ws", { bindings });
|
|
16
|
+
* client.on("yourEvent", (e) => {...}); // typed against YOUR Events
|
|
17
|
+
*
|
|
14
18
|
* Works in the browser (bundle with `bun build --target=browser`) AND in Bun.
|
|
15
19
|
* Outgoing frames are encoded by the generated PURE-JS encoder — no Rust FFI
|
|
16
20
|
* needed, so the browser can send too.
|
package/public/events.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public events API — the application-facing event-driven layer on top of the
|
|
3
|
+
* FlatBuffer transport core.
|
|
4
|
+
*
|
|
5
|
+
* import { createServer } from "@ignex/nova/server";
|
|
6
|
+
* import { on, emit, emitToUser, emitToGroup } from "@ignex/nova/events";
|
|
7
|
+
*
|
|
8
|
+
* const server = createServer({ port: 3000, events: {} });
|
|
9
|
+
*
|
|
10
|
+
* // the "events file": where events are received
|
|
11
|
+
* on("chat.message", (ctx) => {
|
|
12
|
+
* emitToUser(ctx.client.userId ?? "", "chat.ack", { ok: true });
|
|
13
|
+
* });
|
|
14
|
+
*
|
|
15
|
+
* // the global emit: send events through websockets from anywhere
|
|
16
|
+
* emit("quote", { symbol: "AAPL", bid: 180.1, ask: 180.2, ... }); // broadcast
|
|
17
|
+
* emitToGroup("traders", "alert", { ... }); // to a group
|
|
18
|
+
* emitToUser("u-42", "order.update", { ... }); // to a user's sockets
|
|
19
|
+
*
|
|
20
|
+
* Typed against the BUILT-IN event registry; custom-schema apps use
|
|
21
|
+
* `server.events` (fully typed against YOUR events) — or pass your bindings to
|
|
22
|
+
* `createServer({ bindings, events })` and use `server.events`.
|
|
23
|
+
*
|
|
24
|
+
* The events layer is opt-in (`createServer({ events })`); without it there is
|
|
25
|
+
* zero overhead and the singleton throws a descriptive error on use.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
export {
|
|
29
|
+
createMemoryStateStore,
|
|
30
|
+
createNatsClusterTransport,
|
|
31
|
+
createRedisClusterTransport,
|
|
32
|
+
createRedisStateStore,
|
|
33
|
+
} from "../src/events/cluster";
|
|
34
|
+
export {
|
|
35
|
+
bindEvents,
|
|
36
|
+
emit,
|
|
37
|
+
emitToClient,
|
|
38
|
+
emitToGroup,
|
|
39
|
+
emitToTopic,
|
|
40
|
+
emitToUser,
|
|
41
|
+
getEventsHub,
|
|
42
|
+
isEventsBound,
|
|
43
|
+
off,
|
|
44
|
+
offAny,
|
|
45
|
+
offServerEvent,
|
|
46
|
+
on,
|
|
47
|
+
onAny,
|
|
48
|
+
once,
|
|
49
|
+
onServerEvent,
|
|
50
|
+
unbindEvents,
|
|
51
|
+
} from "../src/events/global";
|
|
52
|
+
export type {
|
|
53
|
+
ClientData,
|
|
54
|
+
ClientGroup,
|
|
55
|
+
ClusterStateStore,
|
|
56
|
+
ClusterTransport,
|
|
57
|
+
EmitTarget,
|
|
58
|
+
EmitTargetKind,
|
|
59
|
+
EventClient,
|
|
60
|
+
EventContext,
|
|
61
|
+
EventHandler,
|
|
62
|
+
EventSource,
|
|
63
|
+
EventsClusterOptions,
|
|
64
|
+
EventsHub,
|
|
65
|
+
EventsMetricsSnapshot,
|
|
66
|
+
EventsOptions,
|
|
67
|
+
RedisConnectionOptions,
|
|
68
|
+
RemoteClient,
|
|
69
|
+
ServerEventHandler,
|
|
70
|
+
UserGroup,
|
|
71
|
+
} from "../src/events/types";
|