@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.
Files changed (120) hide show
  1. package/README.md +136 -33
  2. package/docs/ai/LOCAL_DEV.md +81 -0
  3. package/docs/ai/TREE.md +292 -0
  4. package/docs/architecture.md +101 -28
  5. package/docs/events.md +252 -0
  6. package/docs/generic-bindings.md +207 -0
  7. package/docs/publishing.md +2 -2
  8. package/docs/wire-format.md +74 -20
  9. package/index.ts +75 -27
  10. package/package.json +13 -2
  11. package/prebuilds/linux-x64/libignex_ffi.so +0 -0
  12. package/public/bindings.ts +24 -0
  13. package/public/client.ts +5 -1
  14. package/public/events.ts +71 -0
  15. package/public/generate.ts +510 -0
  16. package/public/internal.ts +16 -0
  17. package/public/nats.ts +9 -5
  18. package/public/server.ts +52 -16
  19. package/rust/src/ffi.rs +10 -0
  20. package/rust/src/generated/backend.rs +503 -0
  21. package/rust/src/transcode/generated.rs +377 -17
  22. package/src/bindings/assemble.ts +73 -0
  23. package/src/bindings/default.ts +65 -0
  24. package/src/bindings/types.ts +113 -0
  25. package/src/bridge/nats/inbound.ts +46 -0
  26. package/src/bridge/nats/index.ts +131 -0
  27. package/src/bridge/nats/real-transport.ts +133 -0
  28. package/src/bridge/nats/types.ts +80 -0
  29. package/src/bridge/subjects.ts +3 -0
  30. package/src/codegen/constants.ts +28 -0
  31. package/src/codegen/direct-gen.ts +564 -0
  32. package/src/codegen/fingerprint.ts +44 -0
  33. package/src/codegen/hash.ts +25 -0
  34. package/src/codegen/registry-gen.ts +246 -0
  35. package/src/codegen/rust-glue-gen.ts +552 -0
  36. package/src/codegen/schema-model.ts +363 -0
  37. package/src/codegen/ts-ser-gen.ts +230 -0
  38. package/src/codegen/typebox-to-fbs.ts +60 -0
  39. package/src/core/auth.ts +67 -5
  40. package/src/core/client-heartbeat.ts +2 -1
  41. package/src/core/client-reconnect.ts +9 -2
  42. package/src/core/client-rpc.ts +75 -0
  43. package/src/core/client-state.ts +63 -8
  44. package/src/core/client-wire.ts +148 -15
  45. package/src/core/client.ts +105 -31
  46. package/src/core/groups.ts +8 -0
  47. package/src/core/metrics.ts +42 -21
  48. package/src/core/outbound.ts +62 -11
  49. package/src/core/rate-limit.ts +69 -0
  50. package/src/core/replay.ts +41 -1
  51. package/src/core/resume.ts +181 -0
  52. package/src/core/rooms.ts +10 -3
  53. package/src/core/routing.ts +144 -12
  54. package/src/core/server/client-info.ts +37 -0
  55. package/src/core/server/http-routes.ts +59 -0
  56. package/src/core/server/index.ts +360 -0
  57. package/src/core/server/metrics-view.ts +53 -0
  58. package/src/core/server/socket-lifecycle.ts +57 -0
  59. package/src/core/state.ts +124 -14
  60. package/src/core/topic-log.ts +86 -0
  61. package/src/events/clients.ts +174 -0
  62. package/src/events/cluster/dedupe.ts +43 -0
  63. package/src/events/cluster/envelope.ts +149 -0
  64. package/src/events/cluster/index.ts +50 -0
  65. package/src/events/cluster/keys.ts +33 -0
  66. package/src/events/cluster/kinds.ts +32 -0
  67. package/src/events/cluster/presence-table.ts +99 -0
  68. package/src/events/cluster/presence.ts +53 -0
  69. package/src/events/cluster/redis-client.ts +50 -0
  70. package/src/events/cluster/store-memory.ts +67 -0
  71. package/src/events/cluster/store-redis.ts +44 -0
  72. package/src/events/cluster/subjects.ts +30 -0
  73. package/src/events/cluster/sync.ts +476 -0
  74. package/src/events/cluster/transport-nats.ts +24 -0
  75. package/src/events/cluster/transport-redis.ts +120 -0
  76. package/src/events/cluster-rpc.ts +196 -0
  77. package/src/events/data.ts +38 -0
  78. package/src/events/delivery.ts +83 -0
  79. package/src/events/emit.ts +173 -0
  80. package/src/events/global.ts +117 -0
  81. package/src/events/groups.ts +118 -0
  82. package/src/events/hub/context-factory.ts +79 -0
  83. package/src/events/hub/dispatch.ts +86 -0
  84. package/src/events/hub/index.ts +536 -0
  85. package/src/events/hub/internal.ts +31 -0
  86. package/src/events/hub/metrics-snapshot.ts +84 -0
  87. package/src/events/hub/resolve-cluster.ts +49 -0
  88. package/src/events/index.ts +61 -0
  89. package/src/events/queue.ts +123 -0
  90. package/src/events/registry.ts +214 -0
  91. package/src/events/schedule.ts +73 -0
  92. package/src/events/trace.ts +283 -0
  93. package/src/events/types/client.ts +68 -0
  94. package/src/events/types/cluster.ts +40 -0
  95. package/src/events/types/context.ts +50 -0
  96. package/src/events/types/emit-target.ts +29 -0
  97. package/src/events/types/groups.ts +35 -0
  98. package/src/events/types/hub.ts +124 -0
  99. package/src/events/types/index.ts +30 -0
  100. package/src/events/types/metrics.ts +52 -0
  101. package/src/events/types/options.ts +62 -0
  102. package/src/generated/direct-ser.ts +148 -60
  103. package/src/generated/fbs/backend.fbs +24 -1
  104. package/src/generated/registry.ts +94 -33
  105. package/src/generated/rust/backend_generated.rs +503 -0
  106. package/src/generated/ts/backend.ts +4 -0
  107. package/src/generated/ts/resume.ts +74 -0
  108. package/src/generated/ts/resumed.ts +88 -0
  109. package/src/generated/ts/rpc-call.ts +112 -0
  110. package/src/generated/ts/rpc-result.ts +126 -0
  111. package/src/generated/ts/snapshot-request.ts +19 -5
  112. package/src/generated/ts-ser.ts +110 -17
  113. package/src/generated/wire-registry.json +7 -2
  114. package/src/native/ffi.ts +85 -28
  115. package/src/schema/index.ts +49 -2
  116. package/src/server.ts +7 -3
  117. package/src/transport/transport.ts +200 -79
  118. package/src/bridge/nats.ts +0 -269
  119. package/src/core/server.ts +0 -294
  120. package/src/transport/stats.ts +0 -44
@@ -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 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 `1`). A peer MUST drop frames whose
19
- version it doesn't recognize (the decoders return `null`).
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 }` — reserved for future replay control |
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 consumer in any
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[5..]` with a flatc build of
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 / replay
134
-
135
- The current model replays **recent history on subscribe** (bounded ring per
136
- topic, last-value snapshot). Full gap-based replay (per-frame sequence numbers
137
- in the envelope + `hello.lastSeq` negotiation) is a documented future extension;
138
- the envelope reserves no space for a per-frame seq today.
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`, `maxConnections`, `maxMessageSize`.
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[5..])` + `getSizePrefixedRootAs<T>` (or the
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 "ignex-nova"` works in Bun
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-nova/server"; // Bun-only (Rust FFI)
12
- * import { createClient } from "ignex-nova/client"; // browser + Bun
13
- * import { createNatsBridge } from "ignex-nova/nats"; // standalone bridge
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-nova/client" instead.
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-nova`, then use the subpaths above.
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 { createNatsBridge, createSubjectBuilder } from "./public/nats";
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
- } from "./src/schema";
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.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-nova/client";
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.
@@ -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";