@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.
Files changed (51) hide show
  1. package/README.md +29 -10
  2. package/dist/_virtual/{_@oxc-project_runtime@0.153.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorate.js +1 -2
  3. package/dist/index.js +4 -6
  4. package/dist/nodefony/config/config.js +1 -2
  5. package/dist/nodefony/config/configSchema.js +1 -2
  6. package/dist/nodefony/config/defineModuleConfig.js +1 -2
  7. package/dist/nodefony/decorators/realtimeDecorators.js +1 -2
  8. package/dist/nodefony/interfaces/IRealtimeConnectionState.js +1 -0
  9. package/dist/nodefony/src/backplane/ClusterBackplane.js +1 -2
  10. package/dist/nodefony/src/backplane/LoopbackBackplane.js +1 -2
  11. package/dist/nodefony/src/backplane/RedisBackplane.js +1 -2
  12. package/dist/nodefony/src/backplane/backplaneRegistry.js +1 -2
  13. package/dist/nodefony/src/backplane/bootNotices.js +1 -2
  14. package/dist/nodefony/src/backplane/envelope.js +1 -2
  15. package/dist/nodefony/src/backplane/originId.js +1 -2
  16. package/dist/nodefony/src/backplane/publishQueue.js +0 -1
  17. package/dist/nodefony/src/cluster/ClusterProbeClient.js +1 -2
  18. package/dist/nodefony/src/guards.js +1 -2
  19. package/dist/nodefony/src/server/AnonymousRealtimeToken.js +1 -2
  20. package/dist/nodefony/src/server/RealtimeAdminApi.js +1 -2
  21. package/dist/nodefony/src/server/RealtimeController.js +158 -84
  22. package/dist/nodefony/src/server/RealtimeHub.js +3 -3
  23. package/dist/nodefony/src/server/ServerRealtimeSocket.js +18 -9
  24. package/dist/nodefony/src/server/deniedDetail.js +1 -2
  25. package/dist/nodefony/src/server/subscribedAck.js +29 -0
  26. package/dist/nodefony/src/server/syslogUplink.js +1 -2
  27. package/dist/nodefony/src/server/welcomeEnv.js +1 -2
  28. package/dist/nodefony/src/service/RealtimeService.js +1 -2
  29. package/dist/nodefony/src/transport/WsConnectionTransport.js +42 -2
  30. package/dist/nodefony/testing/index.js +77 -4
  31. package/dist/types/index.d.ts +1 -0
  32. package/dist/types/nodefony/interfaces/IRealtimeConnectionState.d.ts +40 -0
  33. package/dist/types/nodefony/interfaces/IRealtimeToken.d.ts +13 -0
  34. package/dist/types/nodefony/interfaces/index.d.ts +1 -0
  35. package/dist/types/nodefony/src/server/RealtimeController.d.ts +60 -2
  36. package/dist/types/nodefony/src/server/ServerRealtimeSocket.d.ts +18 -8
  37. package/dist/types/nodefony/src/server/subscribedAck.d.ts +23 -0
  38. package/dist/types/nodefony/src/transport/WsConnectionTransport.d.ts +26 -1
  39. package/dist/types/nodefony/testing/index.d.ts +11 -4
  40. package/docs/actions.md +67 -44
  41. package/docs/architecture.md +125 -119
  42. package/docs/cadence-et-contre-pression.md +31 -22
  43. package/docs/configuration.md +106 -82
  44. package/docs/cookbook-chat.md +56 -34
  45. package/docs/index.md +35 -24
  46. package/docs/observabilite.md +49 -49
  47. package/docs/protocole.md +91 -68
  48. package/docs/securite.md +135 -115
  49. package/docs/vocabulaire.md +117 -114
  50. package/package.json +10 -10
  51. 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
- const h = createRealtimeHarness((ctx) => new ChatController(ctx), {
168
- identity: monToken, // IRealtimeToken
169
- frameAuthorizer: buildFrameAuthorizer(firewall, {
170
- // @nodefony/security
171
- channelResolver: getRealtimeHub(),
172
- systemRules: DEFAULT_SYSTEM_RULES,
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 h.connect();
176
- await h.subscribe("chat:admin");
177
- expect(h.denials().map((d) => d.channel)).toEqual(["chat:admin"]); // refusé
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
- //#region \0@oxc-project+runtime@0.153.0/helpers/esm/decorate.js
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 __decorateMetadata from "./_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.js";
21
- import __decorate from "./_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.js";
22
- import { IDENTITY_CHANGED_EVENT, Kernel, Module, services, withTimeout } from "nodefony";
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]), __decorateMetadata("design:paramtypes", [typeof Kernel === "undefined" ? Object : Kernel])], Realtime);
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
- //#region nodefony/config/config.ts
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
- //#region nodefony/config/configSchema.ts
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
- //#region nodefony/config/defineModuleConfig.ts
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
- //#region nodefony/decorators/realtimeDecorators.ts
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 };
@@ -1,6 +1,6 @@
1
1
  import resolveBackplaneOriginId from "./originId.js";
2
2
  import { CLUSTER_RT_KIND } from "nodefony";
3
- //#region nodefony/src/backplane/ClusterBackplane.ts
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
- //#region nodefony/src/backplane/LoopbackBackplane.ts
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
- //#region nodefony/src/backplane/RedisBackplane.ts
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
- //#region nodefony/src/backplane/backplaneRegistry.ts
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
- //#region nodefony/src/backplane/bootNotices.ts
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
- //#region nodefony/src/backplane/envelope.ts
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
- //#region nodefony/src/backplane/originId.ts
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 };
@@ -89,5 +89,4 @@ var BackplanePublishQueue = class {
89
89
  };
90
90
  }
91
91
  };
92
- //#endregion
93
92
  export { BackplanePublishQueue, BackplanePublishQueue as default, DEFAULT_MAX_QUEUE_BYTES };
@@ -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
- //#region nodefony/src/cluster/ClusterProbeClient.ts
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
- //#region nodefony/src/guards.ts
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
- //#region nodefony/src/server/AnonymousRealtimeToken.ts
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
- //#region nodefony/src/server/RealtimeAdminApi.ts
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 };