@ignex/nova 0.1.3 → 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 (98) hide show
  1. package/README.md +4 -1
  2. package/docs/ai/TREE.md +69 -9
  3. package/docs/architecture.md +75 -27
  4. package/docs/events.md +83 -1
  5. package/docs/generic-bindings.md +10 -0
  6. package/docs/wire-format.md +65 -18
  7. package/package.json +2 -1
  8. package/prebuilds/linux-x64/libignex_ffi.so +0 -0
  9. package/public/generate.ts +97 -3
  10. package/public/server.ts +10 -0
  11. package/rust/src/generated/backend.rs +503 -0
  12. package/rust/src/transcode/generated.rs +376 -17
  13. package/src/bridge/nats/inbound.ts +46 -0
  14. package/src/bridge/nats/index.ts +131 -0
  15. package/src/bridge/nats/real-transport.ts +133 -0
  16. package/src/bridge/nats/types.ts +80 -0
  17. package/src/codegen/constants.ts +14 -4
  18. package/src/codegen/direct-gen.ts +20 -6
  19. package/src/codegen/registry-gen.ts +10 -6
  20. package/src/codegen/rust-glue-gen.ts +10 -3
  21. package/src/codegen/schema-model.ts +28 -3
  22. package/src/codegen/ts-ser-gen.ts +12 -3
  23. package/src/core/auth.ts +65 -4
  24. package/src/core/client-rpc.ts +75 -0
  25. package/src/core/client-state.ts +42 -0
  26. package/src/core/client-wire.ts +142 -8
  27. package/src/core/client.ts +72 -3
  28. package/src/core/groups.ts +5 -0
  29. package/src/core/metrics.ts +38 -21
  30. package/src/core/outbound.ts +50 -6
  31. package/src/core/rate-limit.ts +69 -0
  32. package/src/core/replay.ts +41 -1
  33. package/src/core/resume.ts +181 -0
  34. package/src/core/rooms.ts +10 -3
  35. package/src/core/routing.ts +128 -5
  36. package/src/core/server/client-info.ts +37 -0
  37. package/src/core/server/http-routes.ts +59 -0
  38. package/src/core/{server.ts → server/index.ts} +112 -120
  39. package/src/core/server/metrics-view.ts +53 -0
  40. package/src/core/server/socket-lifecycle.ts +57 -0
  41. package/src/core/state.ts +73 -1
  42. package/src/core/topic-log.ts +86 -0
  43. package/src/events/clients.ts +18 -0
  44. package/src/events/cluster/dedupe.ts +43 -0
  45. package/src/events/cluster/envelope.ts +149 -0
  46. package/src/events/cluster/index.ts +50 -0
  47. package/src/events/cluster/keys.ts +33 -0
  48. package/src/events/cluster/kinds.ts +32 -0
  49. package/src/events/cluster/presence-table.ts +99 -0
  50. package/src/events/cluster/presence.ts +53 -0
  51. package/src/events/cluster/redis-client.ts +50 -0
  52. package/src/events/cluster/store-memory.ts +67 -0
  53. package/src/events/cluster/store-redis.ts +44 -0
  54. package/src/events/cluster/subjects.ts +30 -0
  55. package/src/events/cluster/sync.ts +476 -0
  56. package/src/events/cluster/transport-nats.ts +24 -0
  57. package/src/events/cluster/transport-redis.ts +120 -0
  58. package/src/events/cluster-rpc.ts +196 -0
  59. package/src/events/delivery.ts +83 -0
  60. package/src/events/emit.ts +57 -11
  61. package/src/events/hub/context-factory.ts +79 -0
  62. package/src/events/hub/dispatch.ts +86 -0
  63. package/src/events/hub/index.ts +536 -0
  64. package/src/events/hub/internal.ts +31 -0
  65. package/src/events/hub/metrics-snapshot.ts +84 -0
  66. package/src/events/hub/resolve-cluster.ts +49 -0
  67. package/src/events/queue.ts +36 -9
  68. package/src/events/registry.ts +90 -54
  69. package/src/events/schedule.ts +73 -0
  70. package/src/events/trace.ts +283 -0
  71. package/src/events/types/client.ts +68 -0
  72. package/src/events/types/cluster.ts +40 -0
  73. package/src/events/types/context.ts +50 -0
  74. package/src/events/types/emit-target.ts +29 -0
  75. package/src/events/types/groups.ts +35 -0
  76. package/src/events/types/hub.ts +124 -0
  77. package/src/events/types/index.ts +30 -0
  78. package/src/events/types/metrics.ts +52 -0
  79. package/src/events/types/options.ts +62 -0
  80. package/src/generated/direct-ser.ts +146 -59
  81. package/src/generated/fbs/backend.fbs +23 -0
  82. package/src/generated/registry.ts +92 -33
  83. package/src/generated/rust/backend_generated.rs +503 -0
  84. package/src/generated/ts/backend.ts +4 -0
  85. package/src/generated/ts/resume.ts +74 -0
  86. package/src/generated/ts/resumed.ts +88 -0
  87. package/src/generated/ts/rpc-call.ts +112 -0
  88. package/src/generated/ts/rpc-result.ts +126 -0
  89. package/src/generated/ts/snapshot-request.ts +19 -5
  90. package/src/generated/ts-ser.ts +109 -16
  91. package/src/generated/wire-registry.json +7 -3
  92. package/src/schema/index.ts +45 -1
  93. package/src/transport/transport.ts +117 -77
  94. package/src/bridge/nats.ts +0 -309
  95. package/src/events/cluster.ts +0 -732
  96. package/src/events/hub.ts +0 -481
  97. package/src/events/types.ts +0 -378
  98. package/src/transport/stats.ts +0 -48
@@ -95,6 +95,32 @@ export interface GenerateOptions {
95
95
  wireVersion?: number;
96
96
  /** overwrite `outDir` if it exists, default true. */
97
97
  force?: boolean;
98
+ /**
99
+ * Emit a typed FRONTEND client (`<outDir>/client.gen.ts`): the assembled
100
+ * app bindings + a `createRealtimeClient(url, options?)` factory typed
101
+ * against YOUR events (the realtime analog of the generated OpenAPI SDK).
102
+ *
103
+ * ```ts
104
+ * // generated client.gen.ts
105
+ * import { createRealtimeClient } from "./generated/client.gen";
106
+ * const client = createRealtimeClient("ws://host/ws", { reconnect: true });
107
+ * client.on("chat.message", (m) => /* m: ChatMessagePayload *\/);
108
+ * client.send("chat.send", { orderId, body, clientTs });
109
+ * ```
110
+ *
111
+ * `schemaImport` is required: the emitted client assembles bindings from
112
+ * YOUR TypeBox registry (`makeBindings(schema)`), so it must point at the
113
+ * same `{ schemas, events, controlEvents }` object passed here (relative
114
+ * to the emitted file's location in `outDir`).
115
+ */
116
+ client?: {
117
+ /** specifier for the generated wire stack (`makeBindings`), default "./index". */
118
+ wireImport?: string;
119
+ /** specifier for YOUR TypeBox registry, relative to the emitted file. Required. */
120
+ schemaImport: string;
121
+ /** emitted filename inside `outDir`, default "client.gen.ts". */
122
+ outFile?: string;
123
+ };
98
124
  }
99
125
 
100
126
  export interface GeneratedBindings {
@@ -159,12 +185,62 @@ codegen-units = 1
159
185
  return { lib, ffi, cargo };
160
186
  }
161
187
 
188
+ /**
189
+ * Emit the typed FRONTEND client module (see `GenerateOptions.client`).
190
+ * The client assembles the app bindings from YOUR schema and exposes a
191
+ * typed `createRealtimeClient` factory — `on`/`send` are statically checked
192
+ * against the app's event names, so no `as never` casts are needed on the FE.
193
+ */
194
+ function emitClientModel(
195
+ model: Model,
196
+ wireImport: string,
197
+ schemaImport: string,
198
+ libraryImport: string,
199
+ ): string {
200
+ const eventNames = model.events
201
+ .filter((e) => !e.control)
202
+ .map((e) => `"${e.name}"`)
203
+ .join(" | ");
204
+ const lines: string[] = [];
205
+ lines.push("// @generated by ignex-nova generate — DO NOT EDIT");
206
+ lines.push("// Typed frontend realtime client (the realtime analog of the generated OpenAPI SDK).");
207
+ lines.push(`import { createClient } from ${JSON.stringify(libraryImport.replace(/\/bindings$/, "/client"))};`);
208
+ lines.push(`import type { IgnClient, IgnClientOptions } from ${JSON.stringify(libraryImport.replace(/\/bindings$/, "/client"))};`);
209
+ lines.push(`import { makeBindings } from ${JSON.stringify(wireImport)};`);
210
+ lines.push(`import * as schema from ${JSON.stringify(schemaImport)};`);
211
+ lines.push("");
212
+ lines.push("/** The app's assembled runtime bindings (typed against your events). */");
213
+ lines.push("export const bindings = makeBindings(schema);");
214
+ lines.push("export type RealtimeBindings = typeof bindings;");
215
+ lines.push("export type RealtimeClient = IgnClient<RealtimeBindings>;");
216
+ lines.push("");
217
+ lines.push("/** Event names your client can send/observe (derived from the schema). */");
218
+ lines.push(`export type RealtimeEventName = ${eventNames || "never"};`);
219
+ lines.push("");
220
+ lines.push("/**");
221
+ lines.push(" * Create a typed realtime client. `on`/`send` are checked against");
222
+ lines.push(" * RealtimeEventName — payload types flow from the generated registry.");
223
+ lines.push(" */");
224
+ lines.push("export const createRealtimeClient = (");
225
+ lines.push(" url: string,");
226
+ lines.push(' options?: Omit<IgnClientOptions<RealtimeBindings>, "bindings">,');
227
+ lines.push("): RealtimeClient => createClient(url, { ...options, bindings });");
228
+ lines.push("");
229
+ return lines.join("\n");
230
+ }
231
+
162
232
  export function generateBindings(
163
233
  schema: SchemaRegistry,
164
234
  options: GenerateOptions = {},
165
235
  ): GeneratedBindings {
166
236
  const wireVersion = options.wireVersion ?? WIRE_VERSION;
167
- const libraryImport = options.libraryImport ?? "@ignex/nova";
237
+ // Default: the FFI-free entry points. The package barrel ("@ignex/nova")
238
+ // pulls in bun:ffi at import time, which breaks vitest/node/browser module
239
+ // runners consuming generated bindings. Custom libraryImport values (e.g. a
240
+ // relative path to the repo in tests) keep the legacy single-import behavior.
241
+ const isDefaultLib = options.libraryImport === undefined || options.libraryImport === "@ignex/nova";
242
+ const libraryImport = options.libraryImport ?? "@ignex/nova/bindings";
243
+ const helperImport = isDefaultLib ? "@ignex/nova/internal" : libraryImport;
168
244
 
169
245
  // The standard transport control protocol is always present; users may add
170
246
  // custom control events but never override the standard ones.
@@ -246,8 +322,16 @@ export function generateBindings(
246
322
  }
247
323
 
248
324
  // Generated TS stack (user mode: self-contained local types + library imports)
249
- files["registry.ts"] = emitRegistry(model, fingerprint, { schemaImport: null, libraryImport });
250
- files["direct-ser.ts"] = emitDirectSer(model, { schemaImport: null, libraryImport });
325
+ // registry + direct-ser need the runtime HELPERS (@ignex/nova/internal in the
326
+ // default case); index.ts needs assembleBindings (@ignex/nova/bindings).
327
+ files["registry.ts"] = emitRegistry(model, fingerprint, {
328
+ schemaImport: null,
329
+ libraryImport: helperImport,
330
+ });
331
+ files["direct-ser.ts"] = emitDirectSer(model, {
332
+ schemaImport: null,
333
+ libraryImport: helperImport,
334
+ });
251
335
  files["ts-ser.ts"] = emitTsSer(model, { schemaImport: null });
252
336
 
253
337
  const subjectPrefix = options.subjectPrefix ?? "ignex";
@@ -261,6 +345,16 @@ export function generateBindings(
261
345
  fingerprint,
262
346
  );
263
347
 
348
+ if (options.client) {
349
+ const clientFile = options.client.outFile ?? "client.gen.ts";
350
+ files[clientFile] = emitClientModel(
351
+ model,
352
+ options.client.wireImport ?? "./index",
353
+ options.client.schemaImport,
354
+ libraryImport,
355
+ );
356
+ }
357
+
264
358
  files["README.md"] = emitReadme();
265
359
 
266
360
  const write = (): string[] => {
package/public/server.ts CHANGED
@@ -30,6 +30,8 @@ export { createNatsBridge } from "../src/bridge/nats";
30
30
  export type { Int64GuardMode } from "../src/core/int64-guard";
31
31
  export type { MetricsSnapshot } from "../src/core/metrics";
32
32
  export { type ClientInfo, createServer, type IgnServer } from "../src/core/server";
33
+ // Durable topic log (replay seam) — `createServer({ topicLog })` + adapters.
34
+ export { createMemoryTopicLog, type LoggedFrame, type TopicLog } from "../src/core/topic-log";
33
35
  export type {
34
36
  AuthResult,
35
37
  BackpressurePolicy,
@@ -40,6 +42,7 @@ export type {
40
42
  } from "../src/core/state";
41
43
  // Events layer — re-exported so `createServer({ events })` is fully typed
42
44
  // without a separate import (the runtime API is `@ignex/nova/events`).
45
+ export type { DeliveryPolicy } from "../src/events/delivery";
43
46
  export type {
44
47
  ClientData,
45
48
  ClientGroup,
@@ -59,3 +62,10 @@ export type {
59
62
  ServerEventHandler,
60
63
  UserGroup,
61
64
  } from "../src/events/types";
65
+ // Event trace (debugger visibility) — `server.getEventTrace()` surface types.
66
+ export type {
67
+ EventTraceOptions,
68
+ EventTraceRow,
69
+ EventTraceStats,
70
+ TraceDirection,
71
+ } from "../src/events/trace";