@nodefony/realtime 10.0.0-beta.3 → 10.0.0-beta.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 +29 -10
- package/dist/_virtual/{_@oxc-project_runtime@0.153.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorate.js +1 -2
- package/dist/index.js +4 -6
- package/dist/nodefony/config/config.js +1 -2
- package/dist/nodefony/config/configSchema.js +1 -2
- package/dist/nodefony/config/defineModuleConfig.js +1 -2
- package/dist/nodefony/decorators/realtimeDecorators.js +1 -2
- package/dist/nodefony/interfaces/IRealtimeConnectionState.js +1 -0
- package/dist/nodefony/src/backplane/ClusterBackplane.js +1 -2
- package/dist/nodefony/src/backplane/LoopbackBackplane.js +1 -2
- package/dist/nodefony/src/backplane/RedisBackplane.js +1 -2
- package/dist/nodefony/src/backplane/backplaneRegistry.js +1 -2
- package/dist/nodefony/src/backplane/bootNotices.js +1 -2
- package/dist/nodefony/src/backplane/envelope.js +1 -2
- package/dist/nodefony/src/backplane/originId.js +1 -2
- package/dist/nodefony/src/backplane/publishQueue.js +0 -1
- package/dist/nodefony/src/cluster/ClusterProbeClient.js +1 -2
- package/dist/nodefony/src/guards.js +1 -2
- package/dist/nodefony/src/server/AnonymousRealtimeToken.js +1 -2
- package/dist/nodefony/src/server/RealtimeAdminApi.js +1 -2
- package/dist/nodefony/src/server/RealtimeController.js +158 -84
- package/dist/nodefony/src/server/RealtimeHub.js +3 -3
- package/dist/nodefony/src/server/ServerRealtimeSocket.js +18 -9
- package/dist/nodefony/src/server/deniedDetail.js +1 -2
- package/dist/nodefony/src/server/subscribedAck.js +29 -0
- package/dist/nodefony/src/server/syslogUplink.js +1 -2
- package/dist/nodefony/src/server/welcomeEnv.js +1 -2
- package/dist/nodefony/src/service/RealtimeService.js +1 -2
- package/dist/nodefony/src/transport/WsConnectionTransport.js +42 -2
- package/dist/nodefony/testing/index.js +77 -4
- package/dist/types/index.d.ts +1 -0
- package/dist/types/nodefony/interfaces/IRealtimeConnectionState.d.ts +40 -0
- package/dist/types/nodefony/interfaces/IRealtimeToken.d.ts +13 -0
- package/dist/types/nodefony/interfaces/index.d.ts +1 -0
- package/dist/types/nodefony/src/server/RealtimeController.d.ts +60 -2
- package/dist/types/nodefony/src/server/ServerRealtimeSocket.d.ts +18 -8
- package/dist/types/nodefony/src/server/subscribedAck.d.ts +23 -0
- package/dist/types/nodefony/src/transport/WsConnectionTransport.d.ts +26 -1
- package/dist/types/nodefony/testing/index.d.ts +11 -4
- package/docs/actions.md +67 -44
- package/docs/architecture.md +125 -119
- package/docs/cadence-et-contre-pression.md +31 -22
- package/docs/configuration.md +106 -82
- package/docs/cookbook-chat.md +56 -34
- package/docs/index.md +35 -24
- package/docs/observabilite.md +49 -49
- package/docs/protocole.md +91 -68
- package/docs/securite.md +135 -115
- package/docs/vocabulaire.md +117 -114
- package/package.json +10 -10
- package/dist/_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.js +0 -6
package/README.md
CHANGED
|
@@ -163,20 +163,39 @@ authenticator aurait résolue, et le verrou de frame que `@nodefony/security` po
|
|
|
163
163
|
Sans verrou, une `policy` déclarée n'est appliquée par personne — c'est vrai en test comme
|
|
164
164
|
en production, `@nodefony/realtime` ne connaissant pas `@nodefony/security`.
|
|
165
165
|
|
|
166
|
+
Les deux sont fournis par `@nodefony/security/testing` — le verrou est celui du pare-feu, pas
|
|
167
|
+
une copie :
|
|
168
|
+
|
|
166
169
|
```ts
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
170
|
+
import {
|
|
171
|
+
createTestFrameAuthorizer,
|
|
172
|
+
createTestIdentity,
|
|
173
|
+
} from "@nodefony/security/testing";
|
|
174
|
+
|
|
175
|
+
// `chat:room` exige une connexion authentifiée. Sans `identity`, le harnais
|
|
176
|
+
// pose le jeton anonyme, comme le repli du runtime : le canal est refusé.
|
|
177
|
+
const anonyme = createRealtimeHarness((ctx) => new ChatController(ctx), {
|
|
178
|
+
frameAuthorizer: createTestFrameAuthorizer(getRealtimeHub()),
|
|
174
179
|
});
|
|
175
|
-
await
|
|
176
|
-
await
|
|
177
|
-
expect(
|
|
180
|
+
await anonyme.connect();
|
|
181
|
+
await anonyme.subscribe("chat:room");
|
|
182
|
+
expect(anonyme.denials().map((d) => d.channel)).toEqual(["chat:room"]);
|
|
183
|
+
anonyme.dispose();
|
|
184
|
+
|
|
185
|
+
// Avec une identité, le même canal est accordé.
|
|
186
|
+
const alice = createRealtimeHarness((ctx) => new ChatController(ctx), {
|
|
187
|
+
identity: createTestIdentity({ identifier: "alice", roles: ["ROLE_USER"] }),
|
|
188
|
+
frameAuthorizer: createTestFrameAuthorizer(getRealtimeHub()),
|
|
189
|
+
});
|
|
190
|
+
await alice.connect();
|
|
191
|
+
await alice.subscribe("chat:room");
|
|
192
|
+
expect(alice.denials()).toHaveLength(0);
|
|
193
|
+
alice.dispose();
|
|
178
194
|
```
|
|
179
195
|
|
|
196
|
+
Les deux cas vont ensemble : le refus seul passerait aussi avec un canal fermé à tous, l'accord
|
|
197
|
+
seul avec un canal ouvert à tous.
|
|
198
|
+
|
|
180
199
|
| Membre | Rôle |
|
|
181
200
|
| ------------------------------------------- | --------------------------------------------------------------- |
|
|
182
201
|
| `connect()` | Handshake ; rend le `realtime:welcome`, jette s'il n'arrive pas |
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
// \0@oxc-project+runtime@0.155.0/helpers/esm/decorate.js
|
|
2
2
|
function __decorate(decorators, target, key, desc) {
|
|
3
3
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
4
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
5
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
6
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
7
|
}
|
|
8
|
-
//#endregion
|
|
9
8
|
export { __decorate as default };
|
package/dist/index.js
CHANGED
|
@@ -17,10 +17,9 @@ import { ClusterBackplane, processIpcTransport } from "./nodefony/src/backplane/
|
|
|
17
17
|
import { getBackplaneDriver, listBackplaneDrivers, registerBackplaneDriver } from "./nodefony/src/backplane/backplaneRegistry.js";
|
|
18
18
|
import { ClusterProbeClient, clusterProbeHealth, clusterProbeInstance, clusterProbeRequestEnrich, mergeClusterHealth, processProbeTransport, setClusterProbeClient } from "./nodefony/src/cluster/ClusterProbeClient.js";
|
|
19
19
|
import { buildOwnHealth, buildRealtimeHealth, createRealtimeAdminApi } from "./nodefony/src/server/RealtimeAdminApi.js";
|
|
20
|
-
import
|
|
21
|
-
import
|
|
22
|
-
|
|
23
|
-
//#region index.ts
|
|
20
|
+
import __decorate from "./_virtual/_@oxc-project_runtime@0.155.0/helpers/esm/decorate.js";
|
|
21
|
+
import { IDENTITY_CHANGED_EVENT, Module, services, withTimeout } from "nodefony";
|
|
22
|
+
// index.ts
|
|
24
23
|
/**
|
|
25
24
|
* @nodefony/realtime — Couche realtime serveur Nodefony.
|
|
26
25
|
*
|
|
@@ -219,7 +218,6 @@ let Realtime = class Realtime extends Module {
|
|
|
219
218
|
}
|
|
220
219
|
}
|
|
221
220
|
};
|
|
222
|
-
Realtime = __decorate([services([RealtimeService])
|
|
221
|
+
Realtime = __decorate([services([RealtimeService])], Realtime);
|
|
223
222
|
var realtime_default = Realtime;
|
|
224
|
-
//#endregion
|
|
225
223
|
export { ANONYMOUS_REALTIME_TOKEN, BROWSER_ORIGIN, ClusterBackplane, ClusterProbeClient, LoopbackBackplane, MAX_CLIENT_SEVERITY, REDIS_RT_CHANNEL, RESERVED_SYSTEM_PREFIXES, Realtime, RealtimeAction, RealtimeBroadcast, RealtimeChannel, RealtimeController, RealtimeHub, RealtimeInbound, RealtimeService, RedisBackplane, SLOW_CONSUMER_BYTES, ServerRealtimeSocket, WsConnectionTransport, buildOwnHealth, buildRealtimeHealth, clusterProbeHealth, clusterProbeInstance, clusterProbeRequestEnrich, createRealtimeAdminApi, createRedisServiceTransport, createSyslogUplinkHandler, realtime_default as default, defineRealtimeConfig, getBackplaneDriver, getDeclaredBroadcastPrefixes, getRealtimeHub, isReservedSystemChannel, listBackplaneDrivers, mergeClusterHealth, processIpcTransport, processProbeTransport, realtimeConfigJsonSchema, registerBackplaneDriver, resolveBackplaneOriginId, resolveRedisChannel, serverSocket, setClusterProbeClient };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
|
|
2
|
+
// nodefony/config/config.ts
|
|
3
3
|
/**
|
|
4
4
|
* @nodefony/realtime — CONFIGURATION DU MODULE (schéma Zod = source unique).
|
|
5
5
|
*
|
|
@@ -62,5 +62,4 @@ const realtimeConfigSchema = z.strictObject({
|
|
|
62
62
|
* valides par construction ; passés au `super(..., config)` du Module class.
|
|
63
63
|
*/
|
|
64
64
|
const config = realtimeConfigSchema.parse({});
|
|
65
|
-
//#endregion
|
|
66
65
|
export { config as default, realtimeConfigSchema };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { realtimeConfigSchema } from "./config.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
|
|
3
|
+
// nodefony/config/configSchema.ts
|
|
4
4
|
/**
|
|
5
5
|
* JSON Schema de la configuration de `@nodefony/realtime` — lu SANS démarrer
|
|
6
6
|
* l'application, par `nodefony env` et la notice `.env.example`, qui y
|
|
@@ -15,5 +15,4 @@ import { z } from "zod";
|
|
|
15
15
|
function configJsonSchema() {
|
|
16
16
|
return z.toJSONSchema(realtimeConfigSchema);
|
|
17
17
|
}
|
|
18
|
-
//#endregion
|
|
19
18
|
export { configJsonSchema };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { realtimeConfigSchema } from "./config.js";
|
|
2
2
|
import { configJsonSchema } from "./configSchema.js";
|
|
3
3
|
import { parseModuleConfig } from "nodefony";
|
|
4
|
-
|
|
4
|
+
// nodefony/config/defineModuleConfig.ts
|
|
5
5
|
/**
|
|
6
6
|
* Builder type-safe de la configuration de `@nodefony/realtime`.
|
|
7
7
|
*
|
|
@@ -83,5 +83,4 @@ function isRealtimeConfig(value) {
|
|
|
83
83
|
function realtimeConfigJsonSchema() {
|
|
84
84
|
return configJsonSchema();
|
|
85
85
|
}
|
|
86
|
-
//#endregion
|
|
87
86
|
export { buildRealtimeConfig, defineRealtimeConfig, isRealtimeConfig, realtimeConfigJsonSchema };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isCallable, isRecord } from "../src/guards.js";
|
|
2
2
|
import "reflect-metadata";
|
|
3
|
-
|
|
3
|
+
// nodefony/decorators/realtimeDecorators.ts
|
|
4
4
|
/** Clés de métadonnées posées sur le constructor de la classe controller. */
|
|
5
5
|
const ACTIONS_KEY = "realtime:actions";
|
|
6
6
|
const CHANNELS_KEY = "realtime:channels";
|
|
@@ -287,5 +287,4 @@ function getDeclaredBroadcastPrefixes() {
|
|
|
287
287
|
function applyDeclaredBroadcastPrefixes(hub) {
|
|
288
288
|
for (const prefix of declaredBroadcastPrefixes) hub.markBroadcastChannel(prefix);
|
|
289
289
|
}
|
|
290
|
-
//#endregion
|
|
291
290
|
export { DEFAULT_ACTION_POLICY, RealtimeAction, RealtimeBroadcast, RealtimeChannel, RealtimeInbound, applyDeclaredBroadcastPrefixes, getDeclaredBroadcastPrefixes, getRealtimeActions, getRealtimeChannelPolicies, getRealtimeChannels, getRealtimeInbound };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import resolveBackplaneOriginId from "./originId.js";
|
|
2
2
|
import { CLUSTER_RT_KIND } from "nodefony";
|
|
3
|
-
|
|
3
|
+
// nodefony/src/backplane/ClusterBackplane.ts
|
|
4
4
|
/**
|
|
5
5
|
* Transport de production : `process.send` (émission) + `process.on("message")`
|
|
6
6
|
* (réception). Standalone — utilisable tel quel. En vrai câblage cluster (Phase 3c),
|
|
@@ -101,5 +101,4 @@ var ClusterBackplane = class ClusterBackplane {
|
|
|
101
101
|
};
|
|
102
102
|
}
|
|
103
103
|
};
|
|
104
|
-
//#endregion
|
|
105
104
|
export { ClusterBackplane, ClusterBackplane as default, processIpcTransport };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import resolveBackplaneOriginId from "./originId.js";
|
|
2
|
-
|
|
2
|
+
// nodefony/src/backplane/LoopbackBackplane.ts
|
|
3
3
|
/**
|
|
4
4
|
* Backplane **mono-process no-op** — implémentation de RÉFÉRENCE du port
|
|
5
5
|
* {@link IBackplane} quand il n'existe **aucun pair** (1 process = 1 pod).
|
|
@@ -36,5 +36,4 @@ var LoopbackBackplane = class LoopbackBackplane {
|
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
38
|
};
|
|
39
|
-
//#endregion
|
|
40
39
|
export { LoopbackBackplane, LoopbackBackplane as default };
|
|
@@ -2,7 +2,7 @@ import { isCallable, isRecord, isThenable } from "../guards.js";
|
|
|
2
2
|
import resolveBackplaneOriginId from "./originId.js";
|
|
3
3
|
import { openBackplaneEnvelope, sealBackplaneEnvelope } from "./envelope.js";
|
|
4
4
|
import { BackplanePublishQueue } from "./publishQueue.js";
|
|
5
|
-
|
|
5
|
+
// nodefony/src/backplane/RedisBackplane.ts
|
|
6
6
|
/**
|
|
7
7
|
* BASE du canal Redis **dédié** transportant les enveloppes realtime entre pods.
|
|
8
8
|
* Un seul canal pub/sub par app (les canaux logiques voyagent DANS l'enveloppe,
|
|
@@ -189,5 +189,4 @@ var RedisBackplane = class RedisBackplane {
|
|
|
189
189
|
};
|
|
190
190
|
}
|
|
191
191
|
};
|
|
192
|
-
//#endregion
|
|
193
192
|
export { REDIS_RT_CHANNEL, RedisBackplane, RedisBackplane as default, createRedisServiceTransport, isRedisPublisher, isRedisSubscriber, resolveRedisChannel };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// nodefony/src/backplane/backplaneRegistry.ts
|
|
2
2
|
const drivers = /* @__PURE__ */ new Map();
|
|
3
3
|
/**
|
|
4
4
|
* Enregistre (ou remplace) la fabrique d'un driver backplane. Appelé par les
|
|
@@ -16,5 +16,4 @@ function getBackplaneDriver(name) {
|
|
|
16
16
|
function listBackplaneDrivers() {
|
|
17
17
|
return [...drivers.keys()];
|
|
18
18
|
}
|
|
19
|
-
//#endregion
|
|
20
19
|
export { getBackplaneDriver, listBackplaneDrivers, registerBackplaneDriver };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// nodefony/src/backplane/bootNotices.ts
|
|
2
2
|
/** Le geste qui rend le temps réel partagé entre processus et pods. */
|
|
3
3
|
const REDIS_BACKPLANE_FIX = "use(\"@nodefony/realtime\", { backplane: { driver: \"redis\" } }) + NF_REDIS_URL=redis://…";
|
|
4
4
|
/**
|
|
@@ -39,5 +39,4 @@ function realtimeBackplaneDownNotice(driver, reason) {
|
|
|
39
39
|
fix: driver === "redis" ? "vérifier NF_REDIS_URL et que Redis répond" : void 0
|
|
40
40
|
};
|
|
41
41
|
}
|
|
42
|
-
//#endregion
|
|
43
42
|
export { realtimeBackplaneDownNotice, realtimeLocalOnlyNotice };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
2
|
-
|
|
2
|
+
// nodefony/src/backplane/envelope.ts
|
|
3
3
|
/**
|
|
4
4
|
* **Sceau d'enveloppe backplane** — authenticité des messages transportés sur un
|
|
5
5
|
* bus **PARTAGÉ** (Redis pub/sub, et tout driver userland cross-host : NATS,
|
|
@@ -106,5 +106,4 @@ function openBackplaneEnvelope(raw, secret) {
|
|
|
106
106
|
if (a.length !== b.length || !timingSafeEqual(a, b)) return null;
|
|
107
107
|
return msg;
|
|
108
108
|
}
|
|
109
|
-
//#endregion
|
|
110
109
|
export { openBackplaneEnvelope, sealBackplaneEnvelope };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { hostname } from "node:os";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
|
-
|
|
3
|
+
// nodefony/src/backplane/originId.ts
|
|
4
4
|
/**
|
|
5
5
|
* Résout l'identifiant d'origine d'un backplane — l'étiquette que l'anti-écho
|
|
6
6
|
* compare pour jeter les messages qui reviennent à leur émetteur.
|
|
@@ -31,5 +31,4 @@ function resolveBackplaneOriginId() {
|
|
|
31
31
|
if (!host) return randomUUID();
|
|
32
32
|
return `${host}:${process.pid}`;
|
|
33
33
|
}
|
|
34
|
-
//#endregion
|
|
35
34
|
export { resolveBackplaneOriginId as default, resolveBackplaneOriginId };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isRecord } from "../guards.js";
|
|
2
2
|
import { CLUSTER_PROBE_CTL_KIND, CLUSTER_PROBE_KIND, CLUSTER_PROBE_SNAPSHOT_KIND, RichProcessProbe, isClusterMessage, isClusterProbeEnrich, readOrmRich } from "nodefony";
|
|
3
|
-
|
|
3
|
+
// nodefony/src/cluster/ClusterProbeClient.ts
|
|
4
4
|
/**
|
|
5
5
|
* Vrai si la valeur est une liste de santés d'instance. Le snapshot vient du
|
|
6
6
|
* master du cluster par IPC — un process de la MÊME application, pas le
|
|
@@ -291,5 +291,4 @@ function clusterProbeInstance(pid) {
|
|
|
291
291
|
if (!health) return null;
|
|
292
292
|
return health.instances.find((i) => i.process?.pid === pid || i.instanceId === String(pid)) ?? null;
|
|
293
293
|
}
|
|
294
|
-
//#endregion
|
|
295
294
|
export { ClusterProbeClient, ClusterProbeClient as default, clusterProbeHealth, clusterProbeInstance, clusterProbeRequestEnrich, mergeClusterHealth, processProbeTransport, setClusterProbeClient };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// nodefony/src/guards.ts
|
|
2
2
|
/**
|
|
3
3
|
* Gardes de type internes au paquet — elles remplacent une conversion (`as`)
|
|
4
4
|
* par une vérification faite à l'exécution, pour qu'une valeur d'une autre
|
|
@@ -37,5 +37,4 @@ function isCallable(value) {
|
|
|
37
37
|
function isThenable(value) {
|
|
38
38
|
return isRecord(value) && typeof value.then === "function";
|
|
39
39
|
}
|
|
40
|
-
//#endregion
|
|
41
40
|
export { isCallable, isRecord, isThenable };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// nodefony/src/server/AnonymousRealtimeToken.ts
|
|
2
2
|
/**
|
|
3
3
|
* Token realtime ANONYME — fallback Zero Trust quand aucun matcher
|
|
4
4
|
* `IRealtimeAuthenticatorMatcher` ne capture le handshake.
|
|
@@ -28,5 +28,4 @@ const ANONYMOUS_REALTIME_TOKEN = Object.freeze({
|
|
|
28
28
|
},
|
|
29
29
|
getAttribute(_key) {}
|
|
30
30
|
});
|
|
31
|
-
//#endregion
|
|
32
31
|
export { ANONYMOUS_REALTIME_TOKEN, ANONYMOUS_REALTIME_TOKEN as default };
|
|
@@ -2,7 +2,7 @@ import { getRealtimeHub } from "./RealtimeHub.js";
|
|
|
2
2
|
import { listBackplaneDrivers } from "../backplane/backplaneRegistry.js";
|
|
3
3
|
import { clusterProbeHealth } from "../cluster/ClusterProbeClient.js";
|
|
4
4
|
import { Nodefony, ProcessProbe, readOrmHealth } from "nodefony";
|
|
5
|
-
|
|
5
|
+
// nodefony/src/server/RealtimeAdminApi.ts
|
|
6
6
|
const processProbe = new ProcessProbe();
|
|
7
7
|
/**
|
|
8
8
|
* Producteur `IAdminApi` de la **socket Nodefony** — exposé sous
|
|
@@ -83,5 +83,4 @@ function createRealtimeAdminApi() {
|
|
|
83
83
|
adminEndpoints: () => endpoints
|
|
84
84
|
};
|
|
85
85
|
}
|
|
86
|
-
//#endregion
|
|
87
86
|
export { buildOwnHealth, buildRealtimeHealth, createRealtimeAdminApi };
|