@nodefony/realtime 10.0.0-beta.5 → 10.0.0-beta.7

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 (96) hide show
  1. package/README.md +55 -0
  2. package/dist/_virtual/{_@oxc-project_runtime@0.155.0 → _@oxc-project_runtime@0.158.0}/helpers/esm/decorate.js +1 -1
  3. package/dist/index.js +91 -12
  4. package/dist/nodefony/config/config.js +7 -0
  5. package/dist/nodefony/decorators/realtimeDecorators.js +341 -43
  6. package/dist/nodefony/interfaces/IRealtimeConnection.js +1 -0
  7. package/dist/nodefony/src/backplane/ClusterBackplane.js +32 -4
  8. package/dist/nodefony/src/backplane/RedisBackplane.js +146 -41
  9. package/dist/nodefony/src/backplane/controlOrder.js +86 -0
  10. package/dist/nodefony/src/backplane/envelope.js +505 -27
  11. package/dist/nodefony/src/backplane/ingress.js +93 -0
  12. package/dist/nodefony/src/backplane/presenceMessage.js +205 -0
  13. package/dist/nodefony/src/backplane/publishQueue.js +97 -3
  14. package/dist/nodefony/src/cluster/ClusterProbeClient.js +2 -0
  15. package/dist/nodefony/src/server/AnonymousRealtimeToken.js +23 -2
  16. package/dist/nodefony/src/server/ContractRealtimeController.js +75 -0
  17. package/dist/nodefony/src/server/RealtimeConnection.js +135 -0
  18. package/dist/nodefony/src/server/RealtimeController.js +409 -107
  19. package/dist/nodefony/src/server/RealtimeHub.js +1158 -111
  20. package/dist/nodefony/src/server/ServerRealtimeSocket.js +2 -0
  21. package/dist/nodefony/src/server/admissionBudget.js +176 -0
  22. package/dist/nodefony/src/server/channelPolicyRegistry.js +95 -0
  23. package/dist/nodefony/src/server/channelPolicyResolution.js +191 -0
  24. package/dist/nodefony/src/server/channelPresence.js +488 -0
  25. package/dist/nodefony/src/server/connectionAdmission.js +242 -0
  26. package/dist/nodefony/src/server/contractRoutes.js +75 -0
  27. package/dist/nodefony/src/server/deniedDetail.js +10 -3
  28. package/dist/nodefony/src/server/frameAdmission.js +160 -0
  29. package/dist/nodefony/src/server/presenceBus.js +486 -0
  30. package/dist/nodefony/src/server/realtimeShutdown.js +31 -0
  31. package/dist/nodefony/src/server/remotePresence.js +541 -0
  32. package/dist/nodefony/src/server/subscribedAck.js +13 -1
  33. package/dist/nodefony/src/server/syslogUplink.js +3 -4
  34. package/dist/nodefony/src/server/welcomeIdentity.js +37 -0
  35. package/dist/nodefony/src/service/RealtimeService.js +51 -1
  36. package/dist/nodefony/src/service/publicOrigin.js +40 -0
  37. package/dist/nodefony/src/transport/WsConnectionTransport.js +27 -4
  38. package/dist/nodefony/testing/backplaneConformance.js +347 -0
  39. package/dist/nodefony/testing/index.js +43 -12
  40. package/dist/nodefony/testing/presenceClient.js +54 -0
  41. package/dist/types/index.d.ts +39 -7
  42. package/dist/types/nodefony/config/config.d.ts +9 -0
  43. package/dist/types/nodefony/decorators/realtimeDecorators.d.ts +198 -17
  44. package/dist/types/nodefony/interfaces/IBackplane.d.ts +46 -51
  45. package/dist/types/nodefony/interfaces/IChannelPolicy.d.ts +5 -25
  46. package/dist/types/nodefony/interfaces/IRealtimeConnection.d.ts +22 -0
  47. package/dist/types/nodefony/interfaces/IRealtimeConnectionState.d.ts +41 -2
  48. package/dist/types/nodefony/interfaces/IRealtimeController.d.ts +64 -3
  49. package/dist/types/nodefony/interfaces/IRealtimeProbe.d.ts +7 -166
  50. package/dist/types/nodefony/interfaces/IRealtimeToken.d.ts +21 -0
  51. package/dist/types/nodefony/interfaces/index.d.ts +2 -1
  52. package/dist/types/nodefony/src/backplane/ClusterBackplane.d.ts +22 -4
  53. package/dist/types/nodefony/src/backplane/RedisBackplane.d.ts +89 -18
  54. package/dist/types/nodefony/src/backplane/controlOrder.d.ts +75 -0
  55. package/dist/types/nodefony/src/backplane/envelope.d.ts +77 -0
  56. package/dist/types/nodefony/src/backplane/ingress.d.ts +48 -0
  57. package/dist/types/nodefony/src/backplane/presenceMessage.d.ts +150 -0
  58. package/dist/types/nodefony/src/backplane/publishQueue.d.ts +25 -1
  59. package/dist/types/nodefony/src/server/AnonymousRealtimeToken.d.ts +10 -0
  60. package/dist/types/nodefony/src/server/ContractRealtimeController.d.ts +138 -0
  61. package/dist/types/nodefony/src/server/RealtimeConnection.d.ts +20 -0
  62. package/dist/types/nodefony/src/server/RealtimeController.d.ts +110 -12
  63. package/dist/types/nodefony/src/server/RealtimeHub.d.ts +448 -42
  64. package/dist/types/nodefony/src/server/ServerRealtimeSocket.d.ts +1 -1
  65. package/dist/types/nodefony/src/server/admissionBudget.d.ts +71 -0
  66. package/dist/types/nodefony/src/server/channelPolicyRegistry.d.ts +41 -0
  67. package/dist/types/nodefony/src/server/channelPolicyResolution.d.ts +112 -0
  68. package/dist/types/nodefony/src/server/channelPresence.d.ts +155 -0
  69. package/dist/types/nodefony/src/server/connectionAdmission.d.ts +68 -0
  70. package/dist/types/nodefony/src/server/contractRoutes.d.ts +42 -0
  71. package/dist/types/nodefony/src/server/deniedDetail.d.ts +6 -0
  72. package/dist/types/nodefony/src/server/frameAdmission.d.ts +66 -0
  73. package/dist/types/nodefony/src/server/presenceBus.d.ts +112 -0
  74. package/dist/types/nodefony/src/server/realtimeShutdown.d.ts +21 -0
  75. package/dist/types/nodefony/src/server/remotePresence.d.ts +151 -0
  76. package/dist/types/nodefony/src/server/subscribedAck.d.ts +10 -0
  77. package/dist/types/nodefony/src/server/welcomeIdentity.d.ts +19 -0
  78. package/dist/types/nodefony/src/service/RealtimeService.d.ts +17 -2
  79. package/dist/types/nodefony/src/service/publicOrigin.d.ts +19 -0
  80. package/dist/types/nodefony/src/transport/WsConnectionTransport.d.ts +16 -30
  81. package/dist/types/nodefony/testing/backplaneConformance.d.ts +78 -0
  82. package/dist/types/nodefony/testing/index.d.ts +13 -2
  83. package/dist/types/nodefony/testing/presenceClient.d.ts +20 -0
  84. package/docs/actions.md +420 -18
  85. package/docs/architecture.md +101 -11
  86. package/docs/cadence-et-contre-pression.md +87 -11
  87. package/docs/canaux-a-membres.md +277 -0
  88. package/docs/cas-d-usage.md +1703 -0
  89. package/docs/configuration.md +175 -19
  90. package/docs/cookbook-chat.md +701 -224
  91. package/docs/index.md +11 -1
  92. package/docs/observabilite.md +97 -21
  93. package/docs/protocole.md +142 -33
  94. package/docs/securite.md +347 -41
  95. package/docs/vocabulaire.md +13 -12
  96. package/package.json +16 -13
@@ -0,0 +1,54 @@
1
+ import { RealtimeConnection } from "../src/server/RealtimeConnection.js";
2
+ import { PresenceView, REALTIME_PRESENCE_METHOD, isPresenceFrame, ownField } from "nodefony";
3
+ // nodefony/testing/presenceClient.ts
4
+ /**
5
+ * Abonne à `channel` une connexion de `hub` — nommée `user`, ou anonyme (`null`) — et relève les trames de présence qu'elle reçoit, lues et appliquées par le lecteur et la vue du cœur, comme le ferait un navigateur. Une trame que le lecteur refuse est un défaut du serveur : le banc lève en la citant.
6
+ */
7
+ function presenceClient(hub, channel, user) {
8
+ const frames = [];
9
+ const outcomes = [];
10
+ const view = new PresenceView(channel);
11
+ const codec = hub.textFrameCodec;
12
+ const conn = new RealtimeConnection({
13
+ impose: () => {},
14
+ end: () => false,
15
+ send: (frame) => {
16
+ const data = frame.encodeFor(codec);
17
+ if (typeof data !== "string") return;
18
+ const msg = JSON.parse(data);
19
+ if (ownField(msg, "method") !== REALTIME_PRESENCE_METHOD) return;
20
+ const params = ownField(msg, "params");
21
+ if (!isPresenceFrame(params)) throw new Error(`trame de présence mal formée : ${data}`);
22
+ frames.push(params);
23
+ outcomes.push(view.apply(params));
24
+ }
25
+ }, () => ({
26
+ type: user === null ? "anonymous" : "test",
27
+ authenticated: user !== null,
28
+ id: user,
29
+ userIdentifier: user ?? "anon.",
30
+ roles: [],
31
+ scopes: [],
32
+ issuer: null,
33
+ account: user !== null
34
+ }));
35
+ const sink = () => {};
36
+ if (!hub.subscribeClient(channel, sink, () => () => {}, void 0, conn)) throw new Error(`abonnement à « ${channel} » refusé`);
37
+ hub.presenceSubscribed(channel, conn);
38
+ return {
39
+ frames,
40
+ outcomes,
41
+ view,
42
+ leave: () => {
43
+ hub.unsubscribe(channel, sink);
44
+ }
45
+ };
46
+ }
47
+ /** Présents nommés d'un canal vus par `hub` (identité → onglets), triés. */
48
+ function presenceView(hub, channel) {
49
+ const v = hub.presence(channel);
50
+ const out = {};
51
+ if (v !== null) for (const [k, e] of [...v.members].sort(([a], [b]) => a.localeCompare(b))) out[k] = e.tabs;
52
+ return out;
53
+ }
54
+ export { presenceClient, presenceView };
@@ -27,7 +27,7 @@ import RealtimeController from "./nodefony/src/server/RealtimeController.js";
27
27
  import ServerRealtimeSocket, { serverSocket } from "./nodefony/src/server/ServerRealtimeSocket.js";
28
28
  import RealtimeHub, { getRealtimeHub, SLOW_CONSUMER_BYTES, RESERVED_SYSTEM_PREFIXES, isReservedSystemChannel } from "./nodefony/src/server/RealtimeHub.js";
29
29
  import LoopbackBackplane from "./nodefony/src/backplane/LoopbackBackplane.js";
30
- import RedisBackplane, { createRedisServiceTransport, resolveRedisChannel, REDIS_RT_CHANNEL } from "./nodefony/src/backplane/RedisBackplane.js";
30
+ import RedisBackplane, { createRedisServiceTransport, resolveRedisChannel, resolveRedisBytesChannel, REDIS_RT_CHANNEL } from "./nodefony/src/backplane/RedisBackplane.js";
31
31
  import { resolveBackplaneOriginId } from "./nodefony/src/backplane/originId.js";
32
32
  import ClusterBackplane, { processIpcTransport } from "./nodefony/src/backplane/ClusterBackplane.js";
33
33
  import { registerBackplaneDriver, getBackplaneDriver, listBackplaneDrivers } from "./nodefony/src/backplane/backplaneRegistry.js";
@@ -70,26 +70,58 @@ declare class Realtime extends Module<IRealtimeConfig> {
70
70
  * `onKernelBoot` court donc avant et garantit l'ordre.
71
71
  */
72
72
  onKernelBoot(): Promise<this>;
73
+ /**
74
+ * Phase `onReady` — inscrit au hub les politiques de canal de TOUS les contrôleurs temps réel connus, et résout leurs classes d'admission, avant que le moindre serveur n'écoute.
75
+ *
76
+ * Pas à `onKernelBoot` : chaque module enregistre ses contrôleurs dans SON
77
+ * `onBoot` (`@controllers`), et ceux d'un module listé après celui-ci n'y
78
+ * seraient pas encore. `onReady` passe après tous les `onBoot` et avant
79
+ * `initServers` : aucune poignée de main ne précède l'inscription, et un
80
+ * exemplaire qui n'émet que des webhooks a ses politiques comme les autres.
81
+ *
82
+ * @throws BootConfigurationError Politique illisible, `allow` sur une
83
+ * action, classe d'admission sans singleton, sans `allow` ou dont une
84
+ * dépendance manque — la classe et le canal sont nommés.
85
+ */
86
+ onKernelReady(): Promise<this>;
87
+ /**
88
+ * Les routes montées par le routeur du framework, vues par le contrôle des
89
+ * routes de contrat : chemin, méthodes, schémas d'entrée.
90
+ *
91
+ * @returns la liste, vide si le routeur du framework est absent.
92
+ */
93
+ private mountedRoutes;
73
94
  }
74
95
  export default Realtime;
75
96
  export { Realtime };
76
- export { RealtimeController, ServerRealtimeSocket, serverSocket, RealtimeHub, getRealtimeHub, SLOW_CONSUMER_BYTES, RESERVED_SYSTEM_PREFIXES, isReservedSystemChannel, LoopbackBackplane, ClusterBackplane, RedisBackplane, createRedisServiceTransport, resolveRedisChannel, resolveBackplaneOriginId, REDIS_RT_CHANNEL, registerBackplaneDriver, getBackplaneDriver, listBackplaneDrivers, processIpcTransport, ClusterProbeClient, setClusterProbeClient, processProbeTransport, clusterProbeHealth, clusterProbeRequestEnrich, clusterProbeInstance, mergeClusterHealth, WsConnectionTransport, createRealtimeAdminApi, buildRealtimeHealth, buildOwnHealth, RealtimeService, };
77
- export { ANONYMOUS_REALTIME_TOKEN } from "./nodefony/src/server/AnonymousRealtimeToken.js";
97
+ export { RealtimeController, ServerRealtimeSocket, serverSocket, RealtimeHub, getRealtimeHub, SLOW_CONSUMER_BYTES, RESERVED_SYSTEM_PREFIXES, isReservedSystemChannel, LoopbackBackplane, ClusterBackplane, RedisBackplane, createRedisServiceTransport, resolveRedisChannel, resolveRedisBytesChannel, resolveBackplaneOriginId, REDIS_RT_CHANNEL, registerBackplaneDriver, getBackplaneDriver, listBackplaneDrivers, processIpcTransport, ClusterProbeClient, setClusterProbeClient, processProbeTransport, clusterProbeHealth, clusterProbeRequestEnrich, clusterProbeInstance, mergeClusterHealth, WsConnectionTransport, createRealtimeAdminApi, buildRealtimeHealth, buildOwnHealth, RealtimeService, };
98
+ export { ANONYMOUS_REALTIME_TOKEN, ANONYMOUS_SUBSCRIBER_IDENTITY, } from "./nodefony/src/server/AnonymousRealtimeToken.js";
78
99
  export { defineRealtimeConfig, realtimeConfigJsonSchema, } from "./nodefony/config/defineModuleConfig.js";
79
100
  export type { IRealtimeConfig, IRealtimeConfigInput, } from "./nodefony/config/defineModuleConfig.js";
80
- export { RealtimeAction, RealtimeBroadcast, RealtimeChannel, RealtimeInbound, getDeclaredBroadcastPrefixes, } from "./nodefony/decorators/realtimeDecorators.js";
101
+ export { RealtimeAction, RealtimeBroadcast, RealtimeChannel, RealtimeInbound, RealtimePolicy, getDeclaredBroadcastPrefixes, } from "./nodefony/decorators/realtimeDecorators.js";
81
102
  export type { RealtimeChannelFactory } from "./nodefony/decorators/realtimeDecorators.js";
82
- export type { IRealtimeController, RealtimePublish, RealtimeInboundHandler, } from "./nodefony/interfaces/IRealtimeController.js";
103
+ export { ContractRealtimeController, ContractRealtimeControllerBase, ContractRealtimeControllerStrict, } from "./nodefony/src/server/ContractRealtimeController.js";
104
+ export type { ContractRealtimeMethodHandlers, ContractPolicyNames, RealtimeContractOf, } from "./nodefony/src/server/ContractRealtimeController.js";
105
+ export type { IContractRealtimeHub } from "./nodefony/src/server/RealtimeHub.js";
106
+ export type { IRealtimeController, RealtimePublish, ContractRealtimePublish, RealtimeInboundHandler, TypedRealtimeInboundHandler, RealtimeInboundHandlers, RealtimeActionHandler, } from "./nodefony/interfaces/IRealtimeController.js";
83
107
  export type { IChannelPolicy } from "./nodefony/interfaces/IChannelPolicy.js";
84
108
  export type { IRealtimeConnectionState } from "./nodefony/interfaces/IRealtimeConnectionState.js";
109
+ export type { IRealtimeConnection } from "./nodefony/interfaces/IRealtimeConnection.js";
85
110
  export type { IClientLogsLimits } from "./nodefony/interfaces/IClientLogsLimits.js";
86
111
  export { createSyslogUplinkHandler, BROWSER_ORIGIN, MAX_CLIENT_SEVERITY, } from "./nodefony/src/server/syslogUplink.js";
87
112
  export type { SyslogUplinkHandlerOptions, ClientSeverity, } from "./nodefony/src/server/syslogUplink.js";
88
- export type { ChannelSink, ChannelFactory, } from "./nodefony/src/server/RealtimeHub.js";
113
+ export type { ChannelSink, ChannelFactory, ChannelFrameBuilder, } from "./nodefony/src/server/RealtimeHub.js";
114
+ export { ChannelFrame } from "./nodefony/src/server/RealtimeHub.js";
115
+ export { PRESENCE_WINDOW_MS, DEFAULT_PRESENCE_MAX_NAMED, } from "./nodefony/src/server/channelPresence.js";
116
+ export type { IChannelPresenceView } from "./nodefony/src/server/channelPresence.js";
89
117
  export type { IBackplane, IBackplaneMessage, BackplaneHandler, IBackplaneInfo, } from "./nodefony/interfaces/IBackplane.js";
90
118
  export type { IClusterBackplaneTransport, ClusterBackplaneEnvelope, } from "./nodefony/src/backplane/ClusterBackplane.js";
91
- export type { IRedisBackplaneTransport, IRedisPublisher, IRedisSubscriber, } from "./nodefony/src/backplane/RedisBackplane.js";
119
+ export type { IRedisBackplaneTransport, IRedisPublisher, IRedisSubscriber, IRedisBytesSubscriber, } from "./nodefony/src/backplane/RedisBackplane.js";
92
120
  export type { BackplaneFactory, IBackplaneFactoryContext, } from "./nodefony/src/backplane/backplaneRegistry.js";
121
+ export { sealBackplaneEnvelope, openBackplaneEnvelope, BackplaneEcho, } from "./nodefony/src/backplane/envelope.js";
122
+ export { BACKPLANE_STOP_TIMEOUT_MS, shutdownRealtime, } from "./nodefony/src/server/realtimeShutdown.js";
123
+ export { BackplanePublishQueue, DEFAULT_MAX_QUEUE_BYTES, type BackplaneNotice, type BackplaneNoticeSeverity, } from "./nodefony/src/backplane/publishQueue.js";
124
+ export { BackplaneIngress } from "./nodefony/src/backplane/ingress.js";
93
125
  export type { IClusterProbeTransport } from "./nodefony/src/cluster/ClusterProbeClient.js";
94
126
  export type { RawWsConnection } from "./nodefony/src/transport/WsConnectionTransport.js";
95
127
  export type { IRealtimeProbe, IRealtimeHealth, IRealtimeClusterHealth, IRealtimeChannelStat, IRealtimeConnProbe, } from "./nodefony/interfaces/IRealtimeProbe.js";
@@ -1,6 +1,10 @@
1
1
  import { z } from "zod";
2
2
  export declare const realtimeConfigSchema: z.ZodObject<{
3
3
  enabled: z.ZodDefault<z.ZodBoolean>;
4
+ codec: z.ZodDefault<z.ZodEnum<{
5
+ cbor: "cbor";
6
+ json: "json";
7
+ }>>;
4
8
  backplane: z.ZodDefault<z.ZodObject<{
5
9
  driver: z.ZodDefault<z.ZodString>;
6
10
  namespace: z.ZodOptional<z.ZodString>;
@@ -18,6 +22,11 @@ export declare const realtimeConfigSchema: z.ZodObject<{
18
22
  limits: z.ZodDefault<z.ZodObject<{
19
23
  maxChannelsPerConnection: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
20
24
  }, z.core.$strict>>;
25
+ authenticateTimeoutMs: z.ZodDefault<z.ZodNumber>;
26
+ allowTimeoutMs: z.ZodDefault<z.ZodNumber>;
27
+ maxPendingAllowsPerConnection: z.ZodDefault<z.ZodNumber>;
28
+ maxPendingAllowsTotal: z.ZodDefault<z.ZodNumber>;
29
+ publicOrigin: z.ZodOptional<z.ZodString>;
21
30
  csrf: z.ZodDefault<z.ZodObject<{
22
31
  checkOrigin: z.ZodDefault<z.ZodObject<{
23
32
  enabled: z.ZodDefault<z.ZodBoolean>;
@@ -19,9 +19,40 @@
19
19
  * `undefined` (pas d'allocation), fallback sur les overrides classiques.
20
20
  */
21
21
  import "reflect-metadata";
22
- import type { RpcActionHandler } from "nodefony";
22
+ import type { ChannelAdmissionClass, ChannelAllow, RpcActionHandler } from "nodefony";
23
23
  import type { RealtimePublish, RealtimeInboundHandler } from "../interfaces/IRealtimeController.js";
24
24
  import type { IChannelPolicy } from "../interfaces/IChannelPolicy.js";
25
+ import type { ContractPolicyNames, RealtimeContractOf } from "../src/server/ContractRealtimeController.js";
26
+ /**
27
+ * `allow` désigne-t-il une classe d'admission (`@injectable`) plutôt qu'une flèche ?
28
+ *
29
+ * Tranché par la métadonnée `@injectable`, jamais par le texte de la fonction
30
+ * (`class …`), qu'un minifieur réécrit (ADR-0021 §S10 bis).
31
+ *
32
+ * @param allow - valeur du membre `allow` d'une politique déjà validée.
33
+ * @returns `true` pour une classe à résoudre par l'injecteur.
34
+ */
35
+ export declare function isAdmissionClass(allow: ChannelAllow): allow is ChannelAdmissionClass;
36
+ /**
37
+ * Défaut d'une politique de canal, ou `null` si elle est recevable — en clair, pour que le refus au démarrage dise QUOI corriger.
38
+ *
39
+ * @param value - valeur lue sous la métadonnée des politiques (`unknown` :
40
+ * `Reflect.defineMetadata` est public).
41
+ * @returns la raison du refus, ou `null`.
42
+ */
43
+ export declare function channelPolicyDefect(value: unknown): string | null;
44
+ /**
45
+ * Deux politiques exigent-elles exactement la même chose ? Structurel : deux littéraux identiques écrits dans deux classes le sont ; `allow` se compare par référence.
46
+ *
47
+ * Sert à refuser deux déclarations CONTRADICTOIRES d'un même nom (une seule
48
+ * s'appliquerait) sans refuser deux déclarations ÉQUIVALENTES, et à ne juger
49
+ * l'écriture que si elle diffère de la lecture. Démarrage seulement.
50
+ *
51
+ * @param a - une politique recevable.
52
+ * @param b - une politique recevable.
53
+ * @returns `true` si aucun pair ne peut satisfaire l'une sans l'autre.
54
+ */
55
+ export declare function samePolicy(a: IChannelPolicy, b: IChannelPolicy): boolean;
25
56
  /**
26
57
  * Politique appliquée à une action RPC dont l'auteur n'a rien déclaré :
27
58
  * **exiger une connexion authentifiée**. Une action est une méthode que le pair
@@ -62,9 +93,12 @@ export type RealtimeChannelFactory = (channel: string, publish: RealtimePublish)
62
93
  * défaut, toute action applicative serait publique sans que rien ne le dise.
63
94
  * Pour une action délibérément ouverte, l'écrire : `{ authenticated: false }`.
64
95
  *
65
- * Si une politique est **déjà déclarée sous ce nom** (canal homonyme via
66
- * `@RealtimeChannel`/`@RealtimeInbound`), elle est conservée : le défaut ne
67
- * rétrograde jamais une politique existante, souvent plus stricte.
96
+ * Une politique d'action est une politique d'**écriture** : l'appel exige aussi
97
+ * la lecture déclarée sous le même nom. Sans politique, l'action **hérite** de la
98
+ * lecture d'un canal homonyme (`@RealtimeChannel`) si elle existe — le défaut ne
99
+ * rétrograde jamais une politique déclarée —, et sinon du défaut fermé, quel que
100
+ * soit l'ordre des méthodes. Deux politiques explicites différentes sur le même
101
+ * nom (action et `@RealtimeInbound`) refusent le démarrage.
68
102
  *
69
103
  * ⚠️ Le NOM peut primer sur ta politique : le plancher système de
70
104
  * `@nodefony/security` réserve les préfixes plateforme (`kernel:`, `syslog:`…)
@@ -90,16 +124,66 @@ export type RealtimeChannelFactory = (channel: string, publish: RealtimePublish)
90
124
  */
91
125
  export declare function RealtimeAction(method: string, policy?: IChannelPolicy): MethodDecorator;
92
126
  /**
93
- * Décorateur **méthode** — expose un **canal pub/sub** sous le nom `channel`
94
- * (match EXACT, sans préfixe `:<ms>` ni regex). La méthode décorée DOIT renvoyer
95
- * un `dispose: () => void` (la base le mémorise et l'appelle au dernier
127
+ * Noms d'une table de politiques absents du contrat de la classe décorée —
128
+ * `never` quand tous y sont, ou que la classe n'a pas de contrat.
129
+ */
130
+ type NamesNotInContract<P, T> = [RealtimeContractOf<T>] extends [never] ? never : Exclude<keyof P, ContractPolicyNames<RealtimeContractOf<T>>>;
131
+ /**
132
+ * Contrainte posée sur la classe décorée : aucune si les noms sont tous dans le
133
+ * contrat ; sinon un membre `notInContract` exigé, que l'erreur affiche avec les
134
+ * noms fautifs.
135
+ */
136
+ type PolicyNamesCheck<P, T> = [NamesNotInContract<P, T>] extends [never] ? unknown : {
137
+ readonly notInContract: NamesNotInContract<P, T>;
138
+ };
139
+ /**
140
+ * Décorateur de **classe** — pose les politiques d'accès d'actions ou de canaux
141
+ * par leur NOM, sans déclarer de handler.
142
+ *
143
+ * C'est la voie des actions servies par la table de `realtimeActions()` : cette
144
+ * table ne sait pas porter de politique, et un `@RealtimeAction` ajouté pour la
145
+ * seule politique ferait fabriquer, à chaque connexion, un handler aussitôt
146
+ * écrasé par la table (et contournerait l'injection de ses paramètres décorés).
147
+ * Ici, rien par connexion : les politiques sont écrites UNE fois, à la
148
+ * définition de la classe, dans le même registre que celles des décorateurs de
149
+ * méthode. Une action de la table sans politique garde le défaut FERMÉ
150
+ * (connexion authentifiée).
151
+ *
152
+ * Un nom qui porte `*` est un motif (famille de canaux), comme partout ailleurs.
153
+ *
154
+ * La politique vaut pour la LECTURE et l'ÉCRITURE du nom (le même objet, jugé
155
+ * une fois). Elle prime sur le défaut fermé d'une action ; contredire une
156
+ * politique explicite d'un décorateur de méthode refuse le démarrage.
157
+ *
158
+ * **Sur un contrôleur de contrat** (`extends ContractRealtimeController<C>`), le
159
+ * décorateur lit le contrat sur la classe qu'il décore : chaque nom doit être
160
+ * une méthode ou un canal de `C`, sinon la classe ne compile pas, et l'erreur
161
+ * nomme le fautif (`notInContract`). Le contrat ne se répète pas. Sur un
162
+ * contrôleur sans contrat, tout nom est accepté. Vérification à la compilation
163
+ * seulement : coût d'exécution nul.
164
+ *
165
+ * @param policies - nom d'action ou de canal → politique.
166
+ *
167
+ * @example
168
+ * @RealtimePolicy({ "orders:quote": { roles: ["ROLE_SALES"] } })
169
+ * class OrdersRt extends ContractRealtimeController<OrdersContract> { … }
170
+ */
171
+ export declare function RealtimePolicy<P extends {
172
+ readonly [name: string]: IChannelPolicy | undefined;
173
+ }>(policies: P): <T extends abstract new (...args: never[]) => object>(target: T & PolicyNamesCheck<P, T>) => void;
174
+ /**
175
+ * Décorateur **méthode** — expose un **canal pub/sub** sous le nom `channel`, ou
176
+ * une FAMILLE de canaux sous un motif (`chat:room:*`). La méthode décorée DOIT
177
+ * renvoyer un `dispose: () => void` (la base le mémorise et l'appelle au dernier
96
178
  * désabonné, comme avec l'override classique).
97
179
  *
98
- * v1 — match EXACT uniquement. Les canaux à pattern (`/^nodefony:orm:rich@(\d+)/`) ou à
99
- * suffixe de granularité (`nodefony:dashboard:1000`) restent dans l'override
100
- * `createRealtimeChannel()` (la base donne PRIORITÉ aux décorateurs, puis tombe
101
- * sur l'override en fallback — coexistence sans casse). Un mode `pattern: RegExp`
102
- * arrivera plus tard sans rompre cette API.
180
+ * Un motif (`*` = toute suite de caractères, la grammaire des politiques) sert
181
+ * chaque nom qu'il couvre : `@RealtimeChannel("chat:room:*", { allow })` déclare
182
+ * la règle ET la fabrique de tous les salons, et la méthode reçoit le nom réel
183
+ * (`chat:room:42`) en premier argument. Ordre de résolution au `subscribe` : le
184
+ * nom exact décoré, puis l'override `createRealtimeChannel()` (canaux à suffixe
185
+ * de granularité, regex — un contrôleur qui en a un garde son comportement),
186
+ * puis les motifs décorés, du plus spécifique au plus général.
103
187
  *
104
188
  * Le `this` est lié à l'instance ; le factory peut donc lire `this.kernel`, etc.
105
189
  * Mais ATTENTION : un provider est **partagé** entre toutes les connexions au
@@ -107,8 +191,20 @@ export declare function RealtimeAction(method: string, policy?: IChannelPolicy):
107
191
  * créé. Capturer des **valeurs long-lived** (broker, syslog) dans la closure,
108
192
  * jamais `this.context` (lié à la connexion créatrice qui peut fermer).
109
193
  *
194
+ * La `policy` est celle de **LECTURE** : exigée au `subscribe`, et en plus de
195
+ * l'écriture sur toute trame entrante du même nom. Absente, la lecture hérite de
196
+ * l'écriture (`@RealtimeInbound`) si elle est déclarée — une application qui ne
197
+ * garde que l'écriture reste protégée en lecture. Pour un canal que tout le
198
+ * monde lit et que seuls certains écrivent, l'écrire : `{ authenticated: false }`.
199
+ *
110
200
  * @example
111
201
  * class ChatController extends RealtimeController {
202
+ * // Tous les salons : la règle et la fabrique, une seule fois.
203
+ * @RealtimeChannel("chat:room:*", { allow: RoomAdmission })
204
+ * room(_channel: string): () => void {
205
+ * return () => {};
206
+ * }
207
+ *
112
208
  * @RealtimeChannel("chat:room42")
113
209
  * room42(_channel: string, publish: RealtimePublish): () => void {
114
210
  * const timer = setInterval(() => publish("chat:room42", { ts: Date.now() }), 1000);
@@ -129,6 +225,20 @@ export declare function RealtimeChannel(channel: string, policy?: IChannelPolicy
129
225
  * pousser tant qu'aucun handler n'est enregistré ici). Cf seam P13.8a
130
226
  * (`beforeDispatch`) pour la gate sécurité par voter P6.
131
227
  *
228
+ * La `policy` est celle d'**ÉCRITURE** : une trame entrante exige la LECTURE du
229
+ * même nom (`@RealtimeChannel`) ET elle — l'écriture n'est jamais moins stricte
230
+ * que la lecture. Sans lecture déclarée, la lecture en hérite.
231
+ *
232
+ * @example
233
+ * class ChatController extends RealtimeController {
234
+ * @RealtimeChannel("chat:typing", { authenticated: false }) // tout le monde lit
235
+ * typing(_channel: string, _publish: RealtimePublish): () => void {
236
+ * return () => {};
237
+ * }
238
+ * @RealtimeInbound("chat:typing", { authenticated: true }) // seuls les connectés écrivent
239
+ * onTyping(): void {}
240
+ * }
241
+ *
132
242
  * @example
133
243
  * class ChatController extends RealtimeController {
134
244
  * @RealtimeInbound("chat:send")
@@ -161,14 +271,84 @@ export type RealtimeActionWrapper = (method: RealtimeActionMethod, propertyKey:
161
271
  export declare function getRealtimeActions(instance: object, wrap?: RealtimeActionWrapper): Record<string, RpcActionHandler> | null;
162
272
  /** Lecture (lazy, au handshake) du registre des canaux décorés. */
163
273
  export declare function getRealtimeChannels(instance: object): Record<string, RealtimeChannelFactory> | null;
274
+ /**
275
+ * La fabrique décorée sous un MOTIF qui couvre `channel` — `@RealtimeChannel("chat:room:*")` sert `chat:room:42` —, ou `null` si aucun motif ne le couvre.
276
+ *
277
+ * Même grammaire que les politiques (`compileChannelPattern`) et même
278
+ * départage (`channelPatternWeight`, le plus spécifique d'abord) : un nom
279
+ * reçoit la fabrique du motif dont il reçoit la règle. Un motif refusé par la
280
+ * grammaire (trop d'étoiles) ne sert rien — le hub le dit déjà au démarrage.
281
+ * Cold path : appelé au premier abonné d'un canal qu'aucun nom exact ni
282
+ * `createRealtimeChannel` ne produit.
283
+ *
284
+ * @param instance - le contrôleur ; ses motifs se lisent sur sa classe.
285
+ * @param channels - le registre rendu par {@link getRealtimeChannels}.
286
+ * @param channel - le nom demandé.
287
+ */
288
+ export declare function patternChannelFactory(instance: object, channels: Readonly<Record<string, RealtimeChannelFactory>> | null, channel: string): RealtimeChannelFactory | null;
164
289
  /** Lecture (lazy, au handshake) du registre des canaux inbound décorés. */
165
290
  export declare function getRealtimeInbound(instance: object): Record<string, RealtimeInboundHandler> | null;
166
291
  /**
167
- * Lecture (lazy, au handshake) des politiques d'autorisation déclarées
168
- * (`@RealtimeChannel`/`@RealtimeInbound` avec opts). `null` si aucune → le
169
- * controller n'enregistre rien au hub (0 alloc). Donnée pure (pas de `bind` :
170
- * une policy n'est pas une méthode), renvoyée telle quelle pour publication au
171
- * hub realtime, qui l'expose à `@nodefony/security`.
292
+ * La classe déclare-t-elle une ACTION (`@RealtimeAction`) sous ce nom ? Sert à refuser un `allow` posé sur une action, que rien n'appliquerait.
293
+ *
294
+ * @param ctor - constructeur du contrôleur.
295
+ * @param name - nom d'action ou de canal.
296
+ */
297
+ export declare function declaresAction(ctor: object, name: string): boolean;
298
+ /** Ce qu'une classe DÉCLARE sous un nom, par sorte — `null` : elle ne pose pas cette sorte. */
299
+ export interface IDeclaredPolicyKinds {
300
+ readonly read: IChannelPolicy | null;
301
+ readonly write: IChannelPolicy | null;
302
+ }
303
+ /**
304
+ * Politiques déclarées par une classe de contrôleur (ADR-0022 §6, décision 1 quater) : ce qu'elle pose par sorte — que le hub résout ensuite avec les motifs des autres classes — et sa vue seule.
305
+ */
306
+ export interface IDeclaredChannelPolicies {
307
+ /** Vue de la classe SEULE, nom → politique de LECTURE (déclarée, sinon son écriture, sinon le défaut fermé d'une action). */
308
+ readonly read: Record<string, IChannelPolicy>;
309
+ /** Nom → ce que la classe déclare, sorte par sorte (sans héritage : le hub le calcule, motifs compris). */
310
+ readonly kinds: Record<string, IDeclaredPolicyKinds>;
311
+ /** Noms des actions décorées : chacune reçoit le défaut fermé comme REPLI au hub. */
312
+ readonly actions: readonly string[];
313
+ }
314
+ /**
315
+ * Politiques déclarées sur une CLASSE de contrôleur, validées et résolues par sorte — lues au démarrage du noyau, jamais par trame.
316
+ *
317
+ * Résolution indépendante de l'ordre des méthodes :
318
+ * - l'écriture d'un nom est celle déclarée (`@RealtimeInbound`, `@RealtimeAction`
319
+ * avec politique, `@RealtimePolicy`) ; pour une action SANS politique, le
320
+ * défaut fermé, sauf si une lecture est déclarée — l'action en hérite alors ;
321
+ * - la lecture est celle déclarée (`@RealtimeChannel`, `@RealtimePolicy`),
322
+ * sinon l'écriture : une application qui ne garde que l'écriture reste
323
+ * protégée en lecture ;
324
+ * - `kinds` garde ce que la classe déclare, SANS héritage : le hub le fusionne
325
+ * avec les motifs de toutes les classes, sorte par sorte.
326
+ *
327
+ * @param ctor - constructeur du contrôleur.
328
+ * @returns les deux tables, ou `null` si la classe ne déclare aucune politique.
329
+ * @throws BootConfigurationError Une politique est illisible, ou deux
330
+ * déclarations d'une même sorte se contredisent : la classe, le canal et le
331
+ * défaut sont nommés.
332
+ */
333
+ export declare function getDeclaredChannelPolicies(ctor: object): IDeclaredChannelPolicies | null;
334
+ /**
335
+ * Politiques de LECTURE déclarées sur une CLASSE de contrôleur (`@RealtimeChannel`/`@RealtimeInbound`/`@RealtimeAction`/`@RealtimePolicy`), validées — lues au démarrage du noyau, jamais par trame.
336
+ *
337
+ * La lecture d'un nom est celle exigée au `subscribe` ; une écriture distincte
338
+ * se lit par {@link getDeclaredChannelPolicies}.
339
+ *
340
+ * @param ctor - constructeur du contrôleur.
341
+ * @returns nom de canal ou de méthode → politique de lecture, ou `null` si la
342
+ * classe n'en déclare aucune.
343
+ * @throws BootConfigurationError Une politique est illisible ou contredite :
344
+ * la classe, le canal et le défaut sont nommés.
345
+ */
346
+ export declare function getChannelPoliciesOf(ctor: object): Record<string, IChannelPolicy> | null;
347
+ /**
348
+ * Politiques déclarées sur la classe d'une instance de contrôleur — même lecture que {@link getChannelPoliciesOf}.
349
+ *
350
+ * @param instance - contrôleur instancié.
351
+ * @returns nom → politique, ou `null` si aucune.
172
352
  */
173
353
  export declare function getRealtimeChannelPolicies(instance: object): Record<string, IChannelPolicy> | null;
174
354
  /**
@@ -205,3 +385,4 @@ export declare function getDeclaredBroadcastPrefixes(): string[];
205
385
  export declare function applyDeclaredBroadcastPrefixes(hub: {
206
386
  markBroadcastChannel(prefix: string): void;
207
387
  }): void;
388
+ export {};
@@ -25,22 +25,9 @@
25
25
  *
26
26
  * Cf vision « la socket Nodefony » + mémoire `project_cluster_backplane_vision`.
27
27
  */
28
- /**
29
- * Compteurs de la **file d'envoi** d'un backplane à transport réseau : ce qui est
30
- * publié mais pas encore acquitté par le bus. Sans borne, cette file (interne au
31
- * client réseau) grossit sans limite sous rafale — d'où le seuil et ses compteurs,
32
- * exposés jusqu'à la sonde et Studio. Cf `BackplanePublishQueue`.
33
- */
34
- export interface IBackplaneQueueInfo {
35
- /** Octets publiés en attente d'acquittement par le transport (instantané). */
36
- bytes: number;
37
- /** Seuil au-dessus duquel une publication est jetée ; `0` = illimité. */
38
- maxBytes: number;
39
- /** Publications JETÉES faute de place (monotone). `0` en régime sain. */
40
- droppedTotal: number;
41
- /** Publications REFUSÉES par le transport (bus coupé…) (monotone). */
42
- failedTotal: number;
43
- }
28
+ import type { IFrameCodec } from "nodefony";
29
+ import type { IBackplaneInfo, IBackplaneQueueInfo } from "nodefony";
30
+ export type { IBackplaneInfo, IBackplaneQueueInfo };
44
31
  /**
45
32
  * Message transporté entre pairs par le backplane. Sérialisé une fois à l'émission
46
33
  * (IPC `structuredClone` / Redis JSON) ; le `payload` doit donc être structurellement
@@ -60,44 +47,32 @@ export interface IBackplaneMessage {
60
47
  * condition (mais en fan-out **local only**).
61
48
  */
62
49
  export type BackplaneHandler = (msg: IBackplaneMessage) => void;
63
- /**
64
- * **Carte d'identité** d'un backplane — descripteur de DONNÉE (pas un log) décrivant
65
- * l'état du fond de panier. Source unique alimentant trois sorties cohérentes : la
66
- * ligne stdout au boot, le champ `backplane` de la sonde (`/api/health`) et la vue
67
- * Studio. Réutilisable par les tests / un agent IA sans dupliquer la connaissance
68
- * « c'est quoi ce backplane ».
69
- */
70
- export interface IBackplaneInfo {
71
- /** Nom du driver (clé du registre). Ex. `redis`, `cluster`, `loopback`. */
72
- driver: string;
73
- /** Nature du transport. Ex. `redis-pubsub`, `ipc`, `local`. */
74
- kind: string;
75
- /** Identité de CE pair (process/pod). Ex. `String(process.pid)`. */
76
- originId: string;
77
- /** `true` si le fan-out franchit les frontières de pod (multi-host). */
78
- crossPod: boolean;
79
- /** Canal/topic de transport, si applicable (canal Redis, topic Kafka). */
80
- channel?: string;
81
- /**
82
- * Les messages sont-ils **scellés** (authenticité de l'émetteur vérifiée) ?
83
- * Renseigné par les drivers à transport PARTAGÉ, où un tiers peut écrire :
84
- * `false` y signale un bus ouvert. Absent quand la question n'a pas de sens
85
- * (mono-process, IPC master↔workers — authentifié par construction).
86
- */
87
- sealed?: boolean;
88
- /**
89
- * État de la **file d'envoi** vers le bus (octets en vol, seuil, jetés). Renseigné
90
- * par les drivers à transport RÉSEAU, dont les publications sont acquittées de
91
- * façon asynchrone : c'est là qu'un bus lent fait grossir la mémoire du pod.
92
- * `droppedTotal > 0` = du fan-out cross-pod a été sacrifié pour tenir la mémoire.
93
- * Absent quand le transport n'a pas de file (mono-process, IPC synchrone).
94
- */
95
- queue?: IBackplaneQueueInfo;
96
- }
97
50
  /**
98
51
  * Contrat d'un backplane realtime. Implémentation **stateless du point de vue métier**
99
52
  * (l'état des canaux vit dans le hub) ; le backplane ne porte que le transport vers les
100
53
  * pairs + son identité.
54
+ *
55
+ * Exigences d'un driver (ADR-0022 §6, décision 4) — aucune ne se voit à la
56
+ * compilation, toutes se perdent en silence :
57
+ *
58
+ * - **chaque pair reçoit TOUT** : sur un journal partitionné, un groupe de lecteurs
59
+ * PAR pair — un groupe partagé répartirait les messages entre serveurs ;
60
+ * - **l'ordre par émetteur** est tenu : clé de partition = `originId`, jamais le
61
+ * canal (ADR-0021 S5) ;
62
+ * - **aucun rejeu au démarrage** : lire à partir du dernier message, jamais depuis le
63
+ * début — un ordre de fin rejoué couperait une partie rouverte ;
64
+ * - **aucune attente de regroupement** à l'émission : `publish` part tout de suite ;
65
+ * - **un ordre de contrôle n'est jamais jeté pour faire place aux publications**
66
+ * (`PLATFORM_BUS_CHANNELS.control` : terminer un canal, éjecter un compte) —
67
+ * une file d'envoi bornée lui laisse une voie hors quota ; un ordre perdu malgré
68
+ * tout (bus injoignable) se compte et s'annonce, jamais en silence ;
69
+ * - **le sceau appliqué** dès que le bus quitte la machine (`crossPod`) :
70
+ * `sealBackplaneEnvelope` à l'émission, `openBackplaneEnvelope` à la réception ;
71
+ * - `start` après `stop` remet la réception en route sans doubler les messages ;
72
+ * - la dépendance npm du transport vit dans le driver, jamais au cœur.
73
+ *
74
+ * Un driver s'y prouve par la suite de conformité, la même que jouent les drivers
75
+ * livrés : `runBackplaneConformance` de `@nodefony/realtime/testing`.
101
76
  */
102
77
  export interface IBackplane {
103
78
  /** Identité stable de CE pair (process/pod). Discrimine l'echo. Ex. `String(process.pid)`. */
@@ -117,7 +92,27 @@ export interface IBackplane {
117
92
  * Un seul handler à la fois — un appel ultérieur remplace le précédent.
118
93
  */
119
94
  onMessage(handler: BackplaneHandler): void;
120
- /** Arrête le transport et libère les ressources (connexions, listeners). Idempotent. */
95
+ /**
96
+ * Arme le transport des **octets** (ADR-0021 S6) : après un `true`, une charge qui
97
+ * est ou contient un `Uint8Array` traverse {@link publish} intacte jusqu'aux autres
98
+ * pairs. Appelé par le hub, une fois, quand le serveur a un codec binaire.
99
+ *
100
+ * Optionnel : un driver qui ne l'implémente pas — ou qui rend `false` — garde ses
101
+ * messages texte inchangés, et le hub retient les octets sur ce serveur en
102
+ * prévenant l'exploitant une fois, plutôt que de les laisser se déformer.
103
+ *
104
+ * @param codec - le codec binaire du serveur (`CBOR_FRAME_CODEC` de `nodefony/cbor`).
105
+ * @returns `true` si les octets traverseront le bus intacts.
106
+ */
107
+ enableBytes?(codec: IFrameCodec): boolean;
108
+ /**
109
+ * Arrête le transport et libère les ressources (connexions, listeners). Idempotent.
110
+ *
111
+ * Un transport asynchrone laisse d'abord partir ce qui est déjà émis (la promesse
112
+ * se résout une fois ces envois acquittés ou refusés) : à l'arrêt du processus,
113
+ * la preuve de vie finale de la présence part juste avant, et la sortie suit au
114
+ * tick suivant. L'appelant borne l'attente (`shutdownRealtime`).
115
+ */
121
116
  stop(): void | Promise<void>;
122
117
  /**
123
118
  * Carte d'identité du backplane (driver, transport, origine, cross-pod, canal).
@@ -1,28 +1,8 @@
1
1
  /**
2
- * Politique d'autorisation d'un **canal** WebSocket — exigences à satisfaire
3
- * pour s'y abonner (`subscribe`) ou y pousser (canal inbound full-duplex).
2
+ * Politique d'autorisation d'un canal — déclaration UNIQUE, au cœur (`nodefony`).
4
3
  *
5
- * Déclarée côté métier via {@link RealtimeChannel}/{@link RealtimeInbound}
6
- * (`@RealtimeChannel("admin:x", { roles: ["ROLE_ADMIN"] })`), agrégée par le
7
- * {@link RealtimeHub} et **lue par `@nodefony/security`** (P6) au moment de la
8
- * frame via le seam `resolveChannelPolicy`. Le module realtime NE décide PAS de
9
- * l'autorisation (il ne connaît ni la hiérarchie de rôles ni l'identité réelle) :
10
- * il ne fait que **transporter la déclaration** jusqu'au décideur (le firewall).
11
- *
12
- * ⚠️ Cette interface est un **miroir structurel** de `IChannelPolicy` côté
13
- * `@nodefony/security` (`realtimeContracts.ts`) — les deux modules ne s'importent
14
- * pas (`package.json` disjoints). Le typage structurel de TS fait le pont à
15
- * l'exécution. Toute dérive de forme casserait le seam silencieusement.
16
- *
17
- * Champs cumulatifs (ET) ; un champ absent = pas de contrainte sur cet axe ;
18
- * objet entièrement vide = canal libre (équivaut à « pas de politique »).
4
+ * Réexportée ici pour que `import type { IChannelPolicy } from "@nodefony/realtime"`
5
+ * continue de compiler. Elle n'est plus recopiée : `@nodefony/security`, qui la
6
+ * juge, lit la même déclaration — deux copies dérivaient en silence.
19
7
  */
20
- export interface IChannelPolicy {
21
- /** Exige une connexion authentifiée (token non anonyme). */
22
- readonly authenticated?: boolean | undefined;
23
- /** Un de ces rôles suffit (évalué AVEC la hiérarchie de rôles, côté security). */
24
- readonly roles?: readonly string[] | undefined;
25
- /** Un de ces scopes suffit (axe API : JWT/clé API ; session BFF n'en porte pas). */
26
- readonly scopes?: readonly string[] | undefined;
27
- }
28
- export default IChannelPolicy;
8
+ export type { IChannelPolicy, IChannelPolicy as default } from "nodefony";
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Une connexion temps réel servie par CE processus — la poignée qu'un
3
+ * gestionnaire `@RealtimeInbound` reçoit, et que les gestes du hub sur les
4
+ * membres d'un canal désignent (`RealtimeHub.subscribeConnection`,
5
+ * `RealtimeHub.evictConnection`, `RealtimeHub.channelMembers`).
6
+ *
7
+ * Elle n'existe que dans la mémoire du processus qui a accepté la socket : elle
8
+ * ne traverse pas le backplane, et un autre exemplaire (pod, `nodefony cluster
9
+ * -w N`) ne la connaît pas. Une poignée fermée, ou venue d'ailleurs, fait rendre
10
+ * `false` aux gestes du hub — jamais un geste muet.
11
+ */
12
+ export interface IRealtimeConnection {
13
+ /**
14
+ * Identifiant du compte connecté (`RealtimeIdentity.id`) — la clé des canaux
15
+ * personnels et de `RealtimeHub.evictUser`, qui l'atteint sur tous les
16
+ * exemplaires ; `null` pour l'anonyme (qu'aucun ordre par compte n'atteint),
17
+ * et pour un authentificateur qui ne produit pas d'identité d'admission.
18
+ */
19
+ readonly userId: string | null;
20
+ /** `false` une fois la socket fermée : la poignée ne désigne plus rien. */
21
+ readonly open: boolean;
22
+ }