@ignex/nova 0.1.3 → 0.1.6
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 +4 -1
- package/docs/ai/TREE.md +69 -9
- package/docs/architecture.md +75 -27
- package/docs/events.md +83 -1
- package/docs/generic-bindings.md +10 -0
- package/docs/wire-format.md +65 -18
- package/package.json +2 -1
- package/prebuilds/linux-x64/libignex_ffi.so +0 -0
- package/public/generate.ts +97 -3
- package/public/server.ts +10 -0
- package/rust/src/generated/backend.rs +503 -0
- package/rust/src/transcode/generated.rs +376 -17
- 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/codegen/constants.ts +14 -4
- package/src/codegen/direct-gen.ts +20 -6
- package/src/codegen/registry-gen.ts +10 -6
- package/src/codegen/rust-glue-gen.ts +10 -3
- package/src/codegen/schema-model.ts +28 -3
- package/src/codegen/ts-ser-gen.ts +12 -3
- package/src/core/auth.ts +65 -4
- package/src/core/client-rpc.ts +75 -0
- package/src/core/client-state.ts +53 -0
- package/src/core/client-wire.ts +183 -8
- package/src/core/client.ts +84 -4
- package/src/core/groups.ts +5 -0
- package/src/core/metrics.ts +38 -21
- package/src/core/outbound.ts +50 -6
- 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 +128 -5
- package/src/core/server/client-info.ts +37 -0
- package/src/core/server/http-routes.ts +59 -0
- package/src/core/{server.ts → server/index.ts} +112 -120
- package/src/core/server/metrics-view.ts +53 -0
- package/src/core/server/socket-lifecycle.ts +57 -0
- package/src/core/state.ts +73 -1
- package/src/core/topic-log.ts +86 -0
- package/src/events/clients.ts +18 -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/delivery.ts +83 -0
- package/src/events/emit.ts +57 -11
- 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/queue.ts +36 -9
- package/src/events/registry.ts +90 -54
- 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 +146 -59
- package/src/generated/fbs/backend.fbs +23 -0
- package/src/generated/registry.ts +92 -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 +109 -16
- package/src/generated/wire-registry.json +7 -3
- package/src/schema/index.ts +45 -1
- package/src/transport/transport.ts +117 -77
- package/src/bridge/nats.ts +0 -309
- package/src/events/cluster.ts +0 -732
- package/src/events/hub.ts +0 -481
- package/src/events/types.ts +0 -378
- package/src/transport/stats.ts +0 -48
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Events-layer metrics snapshot type (folded into `server.getMetrics().events`).
|
|
3
|
+
*
|
|
4
|
+
* Type-only module (part of the `src/events/types` barrel). The assembly
|
|
5
|
+
* runtime lives in `src/events/hub/metrics-snapshot.ts`.
|
|
6
|
+
*/
|
|
7
|
+
import type { EmitTargetKind } from "./emit-target";
|
|
8
|
+
|
|
9
|
+
/** Events-layer counters (folded into `server.getMetrics().events`). */
|
|
10
|
+
export interface EventsMetricsSnapshot {
|
|
11
|
+
/** emit calls (all targets) */
|
|
12
|
+
emitted: number;
|
|
13
|
+
emittedByTarget: Record<EmitTargetKind, number>;
|
|
14
|
+
/** frames written to local sockets by the events layer */
|
|
15
|
+
deliveredLocal: number;
|
|
16
|
+
/** frames handed to the cluster transport */
|
|
17
|
+
clusterPublished: number;
|
|
18
|
+
/** frames received from other instances */
|
|
19
|
+
clusterReceived: number;
|
|
20
|
+
/** frames dropped because they originated on this instance */
|
|
21
|
+
clusterDroppedSelf: number;
|
|
22
|
+
/** duplicate cluster messages dropped (broker redelivery dedupe window) */
|
|
23
|
+
clusterDroppedDupe: number;
|
|
24
|
+
/** targeted emits routed ONLY to owning instances instead of the full mesh */
|
|
25
|
+
clusterRouted: number;
|
|
26
|
+
/** cluster transport / decode failures */
|
|
27
|
+
clusterErrors: number;
|
|
28
|
+
/** offload-queue: tasks accepted */
|
|
29
|
+
queueQueued: number;
|
|
30
|
+
/** offload-queue: tasks dropped (overflow) */
|
|
31
|
+
queueDropped: number;
|
|
32
|
+
/** offload-queue: task failures */
|
|
33
|
+
queueErrors: number;
|
|
34
|
+
/** handler exceptions (caught + isolated) */
|
|
35
|
+
handlerErrors: number;
|
|
36
|
+
/** handler retries scheduled by the reliability layer (`handlers.retries`) */
|
|
37
|
+
handlerRetries: number;
|
|
38
|
+
/** events dead-lettered after exhausting the retry budget */
|
|
39
|
+
dlqCount: number;
|
|
40
|
+
/** emits currently scheduled via `hub.schedule` */
|
|
41
|
+
scheduledActive: number;
|
|
42
|
+
/** cross-instance rpc calls sent / received */
|
|
43
|
+
rpcSent: number;
|
|
44
|
+
rpcReceived: number;
|
|
45
|
+
/** local connected clients */
|
|
46
|
+
connectedClients: number;
|
|
47
|
+
/** remote clients known via cluster presence */
|
|
48
|
+
remoteClients: number;
|
|
49
|
+
/** active client groups / user groups */
|
|
50
|
+
clientGroups: number;
|
|
51
|
+
userGroups: number;
|
|
52
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Events-layer option types — `createServer({ events })` input.
|
|
3
|
+
*
|
|
4
|
+
* Type-only module (part of the `src/events/types` barrel). Resolved by
|
|
5
|
+
* `createEventsHub` (`src/events/hub/index.ts`).
|
|
6
|
+
*/
|
|
7
|
+
import type { Bindings, DefaultBindings, EventNameOf } from "../../bindings/types";
|
|
8
|
+
import type { NatsBridgeOptions, NatsBridge } from "../../bridge/nats";
|
|
9
|
+
import type { ClusterTransport, ClusterStateStore, RedisConnectionOptions } from "./cluster";
|
|
10
|
+
import type { EventClient } from "./client";
|
|
11
|
+
import type { DeliveryPolicy } from "../delivery";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* `createServer({ events: EventsOptions })` — enables the events layer and
|
|
15
|
+
* exposes it as `server.events`. The module-global `emit` / `on` singleton is
|
|
16
|
+
* bound by default (`global: false` to opt out).
|
|
17
|
+
*/
|
|
18
|
+
export interface EventsOptions<B extends Bindings = DefaultBindings> {
|
|
19
|
+
/**
|
|
20
|
+
* App events clients are allowed to send to the hub. Default: every event
|
|
21
|
+
* registered via `hub.on(...)` is auto-allowed on first use.
|
|
22
|
+
*/
|
|
23
|
+
inbound?: EventNameOf<B>[];
|
|
24
|
+
/** bind the module-global `emit`/`on` singleton (default true) */
|
|
25
|
+
global?: boolean;
|
|
26
|
+
/** called after a connection's client record is attached (seed `data`, …) */
|
|
27
|
+
onConnect?: (client: EventClient) => void;
|
|
28
|
+
/** called after a connection's client record is detached (cleanup) */
|
|
29
|
+
onDisconnect?: (client: EventClient) => void;
|
|
30
|
+
/** handler reliability: retries with backoff + dead-letter sink (default off) */
|
|
31
|
+
handlers?: DeliveryPolicy;
|
|
32
|
+
/** horizontal-scaling sync (server ⇄ server) */
|
|
33
|
+
cluster?: EventsClusterOptions;
|
|
34
|
+
/** offload-queue limits (the queue keeps cluster/state work off hot paths) */
|
|
35
|
+
queue?: { workers?: number; maxPending?: number };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface EventsClusterOptions {
|
|
39
|
+
/** stable id of this instance (default: random UUID) — self-dedupe */
|
|
40
|
+
instanceId?: string;
|
|
41
|
+
/** subject prefix (default: bindings subject prefix or "ignex") */
|
|
42
|
+
prefix?: string;
|
|
43
|
+
/**
|
|
44
|
+
* NATS-based cluster messaging. `true` reuses the server's NATS bridge
|
|
45
|
+
* connection (set `options.nats` too); pass `NatsBridgeOptions` to create a
|
|
46
|
+
* dedicated bridge; pass a `NatsBridge` to reuse an existing one.
|
|
47
|
+
*/
|
|
48
|
+
nats?: boolean | NatsBridgeOptions | NatsBridge;
|
|
49
|
+
/** Redis pub/sub cluster messaging (lazy `ioredis`; subjects mirror NATS) */
|
|
50
|
+
redis?: RedisConnectionOptions;
|
|
51
|
+
/** pluggable messaging transport (tests / custom brokers) */
|
|
52
|
+
transport?: ClusterTransport;
|
|
53
|
+
/**
|
|
54
|
+
* Shared-state store for presence / cluster group membership / client data.
|
|
55
|
+
* Default: per-instance memory. Production: `createRedisStateStore(...)`.
|
|
56
|
+
*/
|
|
57
|
+
state?: ClusterStateStore;
|
|
58
|
+
/** presence re-announce + prune cadence (ms, default 15_000) */
|
|
59
|
+
heartbeatMs?: number;
|
|
60
|
+
/** remote presence TTL (ms, default 60_000) */
|
|
61
|
+
presenceTtlMs?: number;
|
|
62
|
+
}
|
|
@@ -11,62 +11,78 @@ export type DirectCall = (...args: unknown[]) => number;
|
|
|
11
11
|
* output tail `(ptr, usize)` is upgraded to `(buffer, buffer_length)` by
|
|
12
12
|
* the `abi()` transformer in `src/native/ffi.ts` when supported). */
|
|
13
13
|
export const directSymbols = {
|
|
14
|
-
fb_quote_serialize: {
|
|
14
|
+
"fb_quote_serialize": {
|
|
15
15
|
args: ["cstring", "f64", "f64", "i64", "i64", "i64", "ptr", "usize"],
|
|
16
16
|
returns: "u64_fast",
|
|
17
17
|
},
|
|
18
|
-
fb_trade_serialize: {
|
|
18
|
+
"fb_trade_serialize": {
|
|
19
19
|
args: ["cstring", "f64", "i64", "u32", "i64", "ptr", "usize"],
|
|
20
20
|
returns: "u64_fast",
|
|
21
21
|
},
|
|
22
|
-
fb_portfolio_serialize: {
|
|
22
|
+
"fb_portfolio_serialize": {
|
|
23
23
|
args: ["cstring", "buffer", "usize", "f64", "f64", "i64", "cstring", "ptr", "usize"],
|
|
24
24
|
returns: "u64_fast",
|
|
25
25
|
},
|
|
26
|
-
fb_complex_serialize: {
|
|
26
|
+
"fb_complex_serialize": {
|
|
27
27
|
args: ["cstring", "buffer", "usize", "buffer", "usize", "buffer", "usize", "buffer", "usize", "buffer", "usize", "u8", "f64", "i64", "ptr", "usize"],
|
|
28
28
|
returns: "u64_fast",
|
|
29
29
|
},
|
|
30
|
-
fb_big_val_serialize: {
|
|
30
|
+
"fb_big_val_serialize": {
|
|
31
31
|
args: ["cstring", "i64", "i64", "ptr", "usize"],
|
|
32
32
|
returns: "u64_fast",
|
|
33
33
|
},
|
|
34
|
-
fb_hello_serialize: {
|
|
34
|
+
"fb_hello_serialize": {
|
|
35
35
|
args: ["i64", "buffer", "usize", "i64", "ptr", "usize"],
|
|
36
36
|
returns: "u64_fast",
|
|
37
37
|
},
|
|
38
|
-
fb_welcome_serialize: {
|
|
38
|
+
"fb_welcome_serialize": {
|
|
39
39
|
args: ["cstring", "buffer", "usize", "ptr", "usize"],
|
|
40
40
|
returns: "u64_fast",
|
|
41
41
|
},
|
|
42
|
-
fb_subscribe_serialize: {
|
|
42
|
+
"fb_subscribe_serialize": {
|
|
43
43
|
args: ["cstring", "ptr", "usize"],
|
|
44
44
|
returns: "u64_fast",
|
|
45
45
|
},
|
|
46
|
-
fb_unsubscribe_serialize: {
|
|
46
|
+
"fb_unsubscribe_serialize": {
|
|
47
47
|
args: ["cstring", "ptr", "usize"],
|
|
48
48
|
returns: "u64_fast",
|
|
49
49
|
},
|
|
50
|
-
fb_join_group_serialize: {
|
|
50
|
+
"fb_join_group_serialize": {
|
|
51
51
|
args: ["cstring", "ptr", "usize"],
|
|
52
52
|
returns: "u64_fast",
|
|
53
53
|
},
|
|
54
|
-
fb_leave_group_serialize: {
|
|
54
|
+
"fb_leave_group_serialize": {
|
|
55
55
|
args: ["cstring", "ptr", "usize"],
|
|
56
56
|
returns: "u64_fast",
|
|
57
57
|
},
|
|
58
|
-
fb_snapshot_request_serialize: {
|
|
59
|
-
args: ["cstring", "ptr", "usize"],
|
|
58
|
+
"fb_snapshot_request_serialize": {
|
|
59
|
+
args: ["cstring", "i64", "ptr", "usize"],
|
|
60
60
|
returns: "u64_fast",
|
|
61
61
|
},
|
|
62
|
-
fb_ping_serialize: {
|
|
62
|
+
"fb_ping_serialize": {
|
|
63
63
|
args: ["i64", "ptr", "usize"],
|
|
64
64
|
returns: "u64_fast",
|
|
65
65
|
},
|
|
66
|
-
fb_pong_serialize: {
|
|
66
|
+
"fb_pong_serialize": {
|
|
67
67
|
args: ["i64", "ptr", "usize"],
|
|
68
68
|
returns: "u64_fast",
|
|
69
69
|
},
|
|
70
|
+
"fb_resume_serialize": {
|
|
71
|
+
args: ["i64", "ptr", "usize"],
|
|
72
|
+
returns: "u64_fast",
|
|
73
|
+
},
|
|
74
|
+
"fb_resumed_serialize": {
|
|
75
|
+
args: ["u8", "i64", "ptr", "usize"],
|
|
76
|
+
returns: "u64_fast",
|
|
77
|
+
},
|
|
78
|
+
"fb_rpc_call_serialize": {
|
|
79
|
+
args: ["cstring", "cstring", "cstring", "ptr", "usize"],
|
|
80
|
+
returns: "u64_fast",
|
|
81
|
+
},
|
|
82
|
+
"fb_rpc_result_serialize": {
|
|
83
|
+
args: ["cstring", "u8", "cstring", "cstring", "ptr", "usize"],
|
|
84
|
+
returns: "u64_fast",
|
|
85
|
+
},
|
|
70
86
|
} as const;
|
|
71
87
|
|
|
72
88
|
/** sample objects for the bind-time self-test (representative values). */
|
|
@@ -82,9 +98,13 @@ const directSamples: Record<string, unknown> = {
|
|
|
82
98
|
"unsubscribe": { "topic": "SELFTEST" },
|
|
83
99
|
"joinGroup": { "group": "SELFTEST" },
|
|
84
100
|
"leaveGroup": { "group": "SELFTEST" },
|
|
85
|
-
"snapshotRequest": { "topic": "SELFTEST" },
|
|
101
|
+
"snapshotRequest": { "topic": "SELFTEST", "fromSeq": 123456 },
|
|
86
102
|
"ping": { "ts": 123456 },
|
|
87
103
|
"pong": { "ts": 123456 },
|
|
104
|
+
"resume": { "lastSeq": 123456 },
|
|
105
|
+
"resumed": { "ok": true, "from": 123456 },
|
|
106
|
+
"rpcCall": { "id": "SELFTEST", "name": "SELFTEST", "payloadB64": "SELFTEST" },
|
|
107
|
+
"rpcResult": { "id": "SELFTEST", "ok": true, "err": "SELFTEST", "payloadB64": "SELFTEST" },
|
|
88
108
|
} as const;
|
|
89
109
|
|
|
90
110
|
/** grow the packer scratch, write the u32 count, return the DataView. */
|
|
@@ -292,7 +312,8 @@ export function encodeLeaveGroup(call: DirectCall, o: unknown, out: Uint8Array):
|
|
|
292
312
|
/** zero-alloc encoder: fields straight into the (reusable) out buffer. */
|
|
293
313
|
export function encodeSnapshotRequest(call: DirectCall, o: unknown, out: Uint8Array): number {
|
|
294
314
|
const p = o as ControlEvents["snapshotRequest"];
|
|
295
|
-
|
|
315
|
+
checkInt64("snapshotRequest.fromSeq", p.fromSeq);
|
|
316
|
+
return call(p.topic, p.fromSeq, out, out) as number;
|
|
296
317
|
}
|
|
297
318
|
|
|
298
319
|
/** zero-alloc encoder: fields straight into the (reusable) out buffer. */
|
|
@@ -309,6 +330,32 @@ export function encodePong(call: DirectCall, o: unknown, out: Uint8Array): numbe
|
|
|
309
330
|
return call(p.ts, out, out) as number;
|
|
310
331
|
}
|
|
311
332
|
|
|
333
|
+
/** zero-alloc encoder: fields straight into the (reusable) out buffer. */
|
|
334
|
+
export function encodeResume(call: DirectCall, o: unknown, out: Uint8Array): number {
|
|
335
|
+
const p = o as ControlEvents["resume"];
|
|
336
|
+
checkInt64("resume.lastSeq", p.lastSeq);
|
|
337
|
+
return call(p.lastSeq, out, out) as number;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/** zero-alloc encoder: fields straight into the (reusable) out buffer. */
|
|
341
|
+
export function encodeResumed(call: DirectCall, o: unknown, out: Uint8Array): number {
|
|
342
|
+
const p = o as ControlEvents["resumed"];
|
|
343
|
+
checkInt64("resumed.from", p.from);
|
|
344
|
+
return call(p.ok ? 1 : 0, p.from, out, out) as number;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/** zero-alloc encoder: fields straight into the (reusable) out buffer. */
|
|
348
|
+
export function encodeRpcCall(call: DirectCall, o: unknown, out: Uint8Array): number {
|
|
349
|
+
const p = o as ControlEvents["rpcCall"];
|
|
350
|
+
return call(p.id, p.name, p.payloadB64, out, out) as number;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/** zero-alloc encoder: fields straight into the (reusable) out buffer. */
|
|
354
|
+
export function encodeRpcResult(call: DirectCall, o: unknown, out: Uint8Array): number {
|
|
355
|
+
const p = o as ControlEvents["rpcResult"];
|
|
356
|
+
return call(p.id, p.ok ? 1 : 0, p.err, p.payloadB64, out, out) as number;
|
|
357
|
+
}
|
|
358
|
+
|
|
312
359
|
function hasNulQuote(o: unknown): boolean {
|
|
313
360
|
const p = o as Events["quote"];
|
|
314
361
|
return (
|
|
@@ -407,58 +454,98 @@ function hasNulPong(o: unknown): boolean {
|
|
|
407
454
|
return false;
|
|
408
455
|
}
|
|
409
456
|
|
|
457
|
+
function hasNulResume(o: unknown): boolean {
|
|
458
|
+
const p = o as ControlEvents["resume"];
|
|
459
|
+
return false;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function hasNulResumed(o: unknown): boolean {
|
|
463
|
+
const p = o as ControlEvents["resumed"];
|
|
464
|
+
return false;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function hasNulRpcCall(o: unknown): boolean {
|
|
468
|
+
const p = o as ControlEvents["rpcCall"];
|
|
469
|
+
return (
|
|
470
|
+
(typeof p.id === "string" && p.id.includes("\0")) ||
|
|
471
|
+
(typeof p.name === "string" && p.name.includes("\0")) ||
|
|
472
|
+
(typeof p.payloadB64 === "string" && p.payloadB64.includes("\0"))
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function hasNulRpcResult(o: unknown): boolean {
|
|
477
|
+
const p = o as ControlEvents["rpcResult"];
|
|
478
|
+
return (
|
|
479
|
+
(typeof p.id === "string" && p.id.includes("\0")) ||
|
|
480
|
+
(typeof p.err === "string" && p.err.includes("\0")) ||
|
|
481
|
+
(typeof p.payloadB64 === "string" && p.payloadB64.includes("\0"))
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
|
|
410
485
|
/** per-event NUL pre-scan — true routes the payload to the JSON path (cstring truncates `\0`). */
|
|
411
486
|
export const hasNulEncoders: Partial<Record<AnyEventName, (o: unknown) => boolean>> = {
|
|
412
|
-
quote: hasNulQuote,
|
|
413
|
-
trade: hasNulTrade,
|
|
414
|
-
portfolio: hasNulPortfolio,
|
|
415
|
-
complex: hasNulComplex,
|
|
416
|
-
bigVal: hasNulBigVal,
|
|
417
|
-
hello: hasNulHello,
|
|
418
|
-
welcome: hasNulWelcome,
|
|
419
|
-
subscribe: hasNulSubscribe,
|
|
420
|
-
unsubscribe: hasNulUnsubscribe,
|
|
421
|
-
joinGroup: hasNulJoinGroup,
|
|
422
|
-
leaveGroup: hasNulLeaveGroup,
|
|
423
|
-
snapshotRequest: hasNulSnapshotRequest,
|
|
424
|
-
ping: hasNulPing,
|
|
425
|
-
pong: hasNulPong,
|
|
487
|
+
"quote": hasNulQuote,
|
|
488
|
+
"trade": hasNulTrade,
|
|
489
|
+
"portfolio": hasNulPortfolio,
|
|
490
|
+
"complex": hasNulComplex,
|
|
491
|
+
"bigVal": hasNulBigVal,
|
|
492
|
+
"hello": hasNulHello,
|
|
493
|
+
"welcome": hasNulWelcome,
|
|
494
|
+
"subscribe": hasNulSubscribe,
|
|
495
|
+
"unsubscribe": hasNulUnsubscribe,
|
|
496
|
+
"joinGroup": hasNulJoinGroup,
|
|
497
|
+
"leaveGroup": hasNulLeaveGroup,
|
|
498
|
+
"snapshotRequest": hasNulSnapshotRequest,
|
|
499
|
+
"ping": hasNulPing,
|
|
500
|
+
"pong": hasNulPong,
|
|
501
|
+
"resume": hasNulResume,
|
|
502
|
+
"resumed": hasNulResumed,
|
|
503
|
+
"rpcCall": hasNulRpcCall,
|
|
504
|
+
"rpcResult": hasNulRpcResult,
|
|
426
505
|
};
|
|
427
506
|
|
|
428
507
|
export const directSymbolNames: Partial<Record<AnyEventName, string>> = {
|
|
429
|
-
quote: "fb_quote_serialize",
|
|
430
|
-
trade: "fb_trade_serialize",
|
|
431
|
-
portfolio: "fb_portfolio_serialize",
|
|
432
|
-
complex: "fb_complex_serialize",
|
|
433
|
-
bigVal: "fb_big_val_serialize",
|
|
434
|
-
hello: "fb_hello_serialize",
|
|
435
|
-
welcome: "fb_welcome_serialize",
|
|
436
|
-
subscribe: "fb_subscribe_serialize",
|
|
437
|
-
unsubscribe: "fb_unsubscribe_serialize",
|
|
438
|
-
joinGroup: "fb_join_group_serialize",
|
|
439
|
-
leaveGroup: "fb_leave_group_serialize",
|
|
440
|
-
snapshotRequest: "fb_snapshot_request_serialize",
|
|
441
|
-
ping: "fb_ping_serialize",
|
|
442
|
-
pong: "fb_pong_serialize",
|
|
508
|
+
"quote": "fb_quote_serialize",
|
|
509
|
+
"trade": "fb_trade_serialize",
|
|
510
|
+
"portfolio": "fb_portfolio_serialize",
|
|
511
|
+
"complex": "fb_complex_serialize",
|
|
512
|
+
"bigVal": "fb_big_val_serialize",
|
|
513
|
+
"hello": "fb_hello_serialize",
|
|
514
|
+
"welcome": "fb_welcome_serialize",
|
|
515
|
+
"subscribe": "fb_subscribe_serialize",
|
|
516
|
+
"unsubscribe": "fb_unsubscribe_serialize",
|
|
517
|
+
"joinGroup": "fb_join_group_serialize",
|
|
518
|
+
"leaveGroup": "fb_leave_group_serialize",
|
|
519
|
+
"snapshotRequest": "fb_snapshot_request_serialize",
|
|
520
|
+
"ping": "fb_ping_serialize",
|
|
521
|
+
"pong": "fb_pong_serialize",
|
|
522
|
+
"resume": "fb_resume_serialize",
|
|
523
|
+
"resumed": "fb_resumed_serialize",
|
|
524
|
+
"rpcCall": "fb_rpc_call_serialize",
|
|
525
|
+
"rpcResult": "fb_rpc_result_serialize",
|
|
443
526
|
};
|
|
444
527
|
|
|
445
528
|
export type DirectEncoder = (call: DirectCall, o: unknown, out: Uint8Array) => number;
|
|
446
529
|
|
|
447
530
|
export const directEncoders: Partial<Record<AnyEventName, DirectEncoder>> = {
|
|
448
|
-
quote: encodeQuote,
|
|
449
|
-
trade: encodeTrade,
|
|
450
|
-
portfolio: encodePortfolio,
|
|
451
|
-
complex: encodeComplex,
|
|
452
|
-
bigVal: encodeBigVal,
|
|
453
|
-
hello: encodeHello,
|
|
454
|
-
welcome: encodeWelcome,
|
|
455
|
-
subscribe: encodeSubscribe,
|
|
456
|
-
unsubscribe: encodeUnsubscribe,
|
|
457
|
-
joinGroup: encodeJoinGroup,
|
|
458
|
-
leaveGroup: encodeLeaveGroup,
|
|
459
|
-
snapshotRequest: encodeSnapshotRequest,
|
|
460
|
-
ping: encodePing,
|
|
461
|
-
pong: encodePong,
|
|
531
|
+
"quote": encodeQuote,
|
|
532
|
+
"trade": encodeTrade,
|
|
533
|
+
"portfolio": encodePortfolio,
|
|
534
|
+
"complex": encodeComplex,
|
|
535
|
+
"bigVal": encodeBigVal,
|
|
536
|
+
"hello": encodeHello,
|
|
537
|
+
"welcome": encodeWelcome,
|
|
538
|
+
"subscribe": encodeSubscribe,
|
|
539
|
+
"unsubscribe": encodeUnsubscribe,
|
|
540
|
+
"joinGroup": encodeJoinGroup,
|
|
541
|
+
"leaveGroup": encodeLeaveGroup,
|
|
542
|
+
"snapshotRequest": encodeSnapshotRequest,
|
|
543
|
+
"ping": encodePing,
|
|
544
|
+
"pong": encodePong,
|
|
545
|
+
"resume": encodeResume,
|
|
546
|
+
"resumed": encodeResumed,
|
|
547
|
+
"rpcCall": encodeRpcCall,
|
|
548
|
+
"rpcResult": encodeRpcResult,
|
|
462
549
|
};
|
|
463
550
|
|
|
464
551
|
/**
|
|
@@ -128,6 +128,7 @@ table LeaveGroup {
|
|
|
128
128
|
|
|
129
129
|
table SnapshotRequest {
|
|
130
130
|
topic:string;
|
|
131
|
+
from_seq:int64;
|
|
131
132
|
}
|
|
132
133
|
|
|
133
134
|
table Ping {
|
|
@@ -137,3 +138,25 @@ table Ping {
|
|
|
137
138
|
table Pong {
|
|
138
139
|
ts:int64;
|
|
139
140
|
}
|
|
141
|
+
|
|
142
|
+
table Resume {
|
|
143
|
+
last_seq:int64;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
table Resumed {
|
|
147
|
+
ok:bool;
|
|
148
|
+
from:int64;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
table RpcCall {
|
|
152
|
+
id:string;
|
|
153
|
+
name:string;
|
|
154
|
+
payload_b64:string;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
table RpcResult {
|
|
158
|
+
id:string;
|
|
159
|
+
ok:bool;
|
|
160
|
+
err:string;
|
|
161
|
+
payload_b64:string;
|
|
162
|
+
}
|
|
@@ -23,6 +23,10 @@ import {
|
|
|
23
23
|
SnapshotRequest,
|
|
24
24
|
Ping,
|
|
25
25
|
Pong,
|
|
26
|
+
Resume,
|
|
27
|
+
Resumed,
|
|
28
|
+
RpcCall,
|
|
29
|
+
RpcResult,
|
|
26
30
|
type QuoteT,
|
|
27
31
|
type TradeT,
|
|
28
32
|
type PortfolioSnapshotT,
|
|
@@ -42,51 +46,63 @@ import {
|
|
|
42
46
|
type SnapshotRequestT,
|
|
43
47
|
type PingT,
|
|
44
48
|
type PongT,
|
|
49
|
+
type ResumeT,
|
|
50
|
+
type ResumedT,
|
|
51
|
+
type RpcCallT,
|
|
52
|
+
type RpcResultT,
|
|
45
53
|
} from "./ts/backend";
|
|
46
54
|
import type { AnyEventName, ControlEventName, ControlEvents, Events, EventName } from "../schema";
|
|
47
55
|
|
|
48
|
-
export const WIRE_VERSION =
|
|
49
|
-
export const WIRE_HEADER_LEN =
|
|
50
|
-
export const SCHEMA_FINGERPRINT =
|
|
56
|
+
export const WIRE_VERSION = 2;
|
|
57
|
+
export const WIRE_HEADER_LEN = 14; // [version:1][event_id:u32 LE]
|
|
58
|
+
export const SCHEMA_FINGERPRINT = 3647747841; // fnv1a32(canonical model)
|
|
51
59
|
|
|
52
60
|
export const eventNameToId: Record<EventName, number> = {
|
|
53
|
-
quote: 2995289047, // fnv1a32("quote")
|
|
54
|
-
trade: 1362521689, // fnv1a32("trade")
|
|
55
|
-
portfolio: 464274147, // fnv1a32("portfolio")
|
|
56
|
-
complex: 2318166199, // fnv1a32("complex")
|
|
57
|
-
order: 1932267671, // fnv1a32("order")
|
|
58
|
-
bigVal: 3211729064, // fnv1a32("bigVal")
|
|
61
|
+
"quote": 2995289047, // fnv1a32("quote")
|
|
62
|
+
"trade": 1362521689, // fnv1a32("trade")
|
|
63
|
+
"portfolio": 464274147, // fnv1a32("portfolio")
|
|
64
|
+
"complex": 2318166199, // fnv1a32("complex")
|
|
65
|
+
"order": 1932267671, // fnv1a32("order")
|
|
66
|
+
"bigVal": 3211729064, // fnv1a32("bigVal")
|
|
59
67
|
};
|
|
60
68
|
|
|
61
69
|
export const controlEventNameToId: Record<ControlEventName, number> = {
|
|
62
|
-
hello: 1335831723, // fnv1a32("hello")
|
|
63
|
-
welcome: 669089267, // fnv1a32("welcome")
|
|
64
|
-
subscribe: 2946386435, // fnv1a32("subscribe")
|
|
65
|
-
unsubscribe: 4190043798, // fnv1a32("unsubscribe")
|
|
66
|
-
joinGroup: 3026600144, // fnv1a32("joinGroup")
|
|
67
|
-
leaveGroup: 3755061443, // fnv1a32("leaveGroup")
|
|
68
|
-
snapshotRequest: 1249175018, // fnv1a32("snapshotRequest")
|
|
69
|
-
ping: 375255177, // fnv1a32("ping")
|
|
70
|
-
pong: 2061178551, // fnv1a32("pong")
|
|
70
|
+
"hello": 1335831723, // fnv1a32("hello")
|
|
71
|
+
"welcome": 669089267, // fnv1a32("welcome")
|
|
72
|
+
"subscribe": 2946386435, // fnv1a32("subscribe")
|
|
73
|
+
"unsubscribe": 4190043798, // fnv1a32("unsubscribe")
|
|
74
|
+
"joinGroup": 3026600144, // fnv1a32("joinGroup")
|
|
75
|
+
"leaveGroup": 3755061443, // fnv1a32("leaveGroup")
|
|
76
|
+
"snapshotRequest": 1249175018, // fnv1a32("snapshotRequest")
|
|
77
|
+
"ping": 375255177, // fnv1a32("ping")
|
|
78
|
+
"pong": 2061178551, // fnv1a32("pong")
|
|
79
|
+
"resume": 4281967466, // fnv1a32("resume")
|
|
80
|
+
"resumed": 3585847050, // fnv1a32("resumed")
|
|
81
|
+
"rpcCall": 986404564, // fnv1a32("rpcCall")
|
|
82
|
+
"rpcResult": 648609069, // fnv1a32("rpcResult")
|
|
71
83
|
};
|
|
72
84
|
|
|
73
85
|
/** merged app + control registry (used by encodeToScratch / JS encoder). */
|
|
74
86
|
export const anyEventNameToId: Record<string, number> = {
|
|
75
|
-
quote: 2995289047,
|
|
76
|
-
trade: 1362521689,
|
|
77
|
-
portfolio: 464274147,
|
|
78
|
-
complex: 2318166199,
|
|
79
|
-
order: 1932267671,
|
|
80
|
-
bigVal: 3211729064,
|
|
81
|
-
hello: 1335831723,
|
|
82
|
-
welcome: 669089267,
|
|
83
|
-
subscribe: 2946386435,
|
|
84
|
-
unsubscribe: 4190043798,
|
|
85
|
-
joinGroup: 3026600144,
|
|
86
|
-
leaveGroup: 3755061443,
|
|
87
|
-
snapshotRequest: 1249175018,
|
|
88
|
-
ping: 375255177,
|
|
89
|
-
pong: 2061178551,
|
|
87
|
+
"quote": 2995289047,
|
|
88
|
+
"trade": 1362521689,
|
|
89
|
+
"portfolio": 464274147,
|
|
90
|
+
"complex": 2318166199,
|
|
91
|
+
"order": 1932267671,
|
|
92
|
+
"bigVal": 3211729064,
|
|
93
|
+
"hello": 1335831723,
|
|
94
|
+
"welcome": 669089267,
|
|
95
|
+
"subscribe": 2946386435,
|
|
96
|
+
"unsubscribe": 4190043798,
|
|
97
|
+
"joinGroup": 3026600144,
|
|
98
|
+
"leaveGroup": 3755061443,
|
|
99
|
+
"snapshotRequest": 1249175018,
|
|
100
|
+
"ping": 375255177,
|
|
101
|
+
"pong": 2061178551,
|
|
102
|
+
"resume": 4281967466,
|
|
103
|
+
"resumed": 3585847050,
|
|
104
|
+
"rpcCall": 986404564,
|
|
105
|
+
"rpcResult": 648609069,
|
|
90
106
|
};
|
|
91
107
|
|
|
92
108
|
export const idToEventName: Record<number, EventName> = {
|
|
@@ -115,6 +131,10 @@ export const idToAnyEventName: Record<number, AnyEventName> = {
|
|
|
115
131
|
1249175018: "snapshotRequest",
|
|
116
132
|
375255177: "ping",
|
|
117
133
|
2061178551: "pong",
|
|
134
|
+
4281967466: "resume",
|
|
135
|
+
3585847050: "resumed",
|
|
136
|
+
986404564: "rpcCall",
|
|
137
|
+
648609069: "rpcResult",
|
|
118
138
|
};
|
|
119
139
|
|
|
120
140
|
export type DecodeFn = (bb: flatbuffers.ByteBuffer) => unknown;
|
|
@@ -135,6 +155,10 @@ export const eventIdToDecoder: Record<number, DecodeFn> = {
|
|
|
135
155
|
1249175018: (bb) => snapshot_requestToPlain(SnapshotRequest.getSizePrefixedRootAsSnapshotRequest(bb).unpack()),
|
|
136
156
|
375255177: (bb) => pingToPlain(Ping.getSizePrefixedRootAsPing(bb).unpack()),
|
|
137
157
|
2061178551: (bb) => pongToPlain(Pong.getSizePrefixedRootAsPong(bb).unpack()),
|
|
158
|
+
4281967466: (bb) => resumeToPlain(Resume.getSizePrefixedRootAsResume(bb).unpack()),
|
|
159
|
+
3585847050: (bb) => resumedToPlain(Resumed.getSizePrefixedRootAsResumed(bb).unpack()),
|
|
160
|
+
986404564: (bb) => rpc_callToPlain(RpcCall.getSizePrefixedRootAsRpcCall(bb).unpack()),
|
|
161
|
+
648609069: (bb) => rpc_resultToPlain(RpcResult.getSizePrefixedRootAsRpcResult(bb).unpack()),
|
|
138
162
|
};
|
|
139
163
|
|
|
140
164
|
const controlIds = new Set<number>([
|
|
@@ -147,6 +171,10 @@ const controlIds = new Set<number>([
|
|
|
147
171
|
1249175018,
|
|
148
172
|
375255177,
|
|
149
173
|
2061178551,
|
|
174
|
+
4281967466,
|
|
175
|
+
3585847050,
|
|
176
|
+
986404564,
|
|
177
|
+
648609069,
|
|
150
178
|
]);
|
|
151
179
|
export function isControlId(id: number): boolean { return controlIds.has(id); }
|
|
152
180
|
|
|
@@ -327,6 +355,7 @@ function leave_groupToPlain(o: LeaveGroupT): ControlEvents["leaveGroup"] {
|
|
|
327
355
|
function snapshot_requestToPlain(o: SnapshotRequestT): ControlEvents["snapshotRequest"] {
|
|
328
356
|
return {
|
|
329
357
|
topic: str(o.topic),
|
|
358
|
+
fromSeq: Number(o.fromSeq),
|
|
330
359
|
};
|
|
331
360
|
}
|
|
332
361
|
|
|
@@ -341,3 +370,33 @@ function pongToPlain(o: PongT): ControlEvents["pong"] {
|
|
|
341
370
|
ts: Number(o.ts),
|
|
342
371
|
};
|
|
343
372
|
}
|
|
373
|
+
|
|
374
|
+
function resumeToPlain(o: ResumeT): ControlEvents["resume"] {
|
|
375
|
+
return {
|
|
376
|
+
lastSeq: Number(o.lastSeq),
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function resumedToPlain(o: ResumedT): ControlEvents["resumed"] {
|
|
381
|
+
return {
|
|
382
|
+
ok: o.ok,
|
|
383
|
+
from: Number(o.from),
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function rpc_callToPlain(o: RpcCallT): ControlEvents["rpcCall"] {
|
|
388
|
+
return {
|
|
389
|
+
id: str(o.id),
|
|
390
|
+
name: str(o.name),
|
|
391
|
+
payloadB64: str(o.payloadB64),
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function rpc_resultToPlain(o: RpcResultT): ControlEvents["rpcResult"] {
|
|
396
|
+
return {
|
|
397
|
+
id: str(o.id),
|
|
398
|
+
ok: o.ok,
|
|
399
|
+
err: str(o.err),
|
|
400
|
+
payloadB64: str(o.payloadB64),
|
|
401
|
+
};
|
|
402
|
+
}
|