@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.
- package/README.md +55 -0
- package/dist/_virtual/{_@oxc-project_runtime@0.155.0 → _@oxc-project_runtime@0.158.0}/helpers/esm/decorate.js +1 -1
- package/dist/index.js +91 -12
- package/dist/nodefony/config/config.js +7 -0
- package/dist/nodefony/decorators/realtimeDecorators.js +341 -43
- package/dist/nodefony/interfaces/IRealtimeConnection.js +1 -0
- package/dist/nodefony/src/backplane/ClusterBackplane.js +32 -4
- package/dist/nodefony/src/backplane/RedisBackplane.js +146 -41
- package/dist/nodefony/src/backplane/controlOrder.js +86 -0
- package/dist/nodefony/src/backplane/envelope.js +505 -27
- package/dist/nodefony/src/backplane/ingress.js +93 -0
- package/dist/nodefony/src/backplane/presenceMessage.js +205 -0
- package/dist/nodefony/src/backplane/publishQueue.js +97 -3
- package/dist/nodefony/src/cluster/ClusterProbeClient.js +2 -0
- package/dist/nodefony/src/server/AnonymousRealtimeToken.js +23 -2
- package/dist/nodefony/src/server/ContractRealtimeController.js +75 -0
- package/dist/nodefony/src/server/RealtimeConnection.js +135 -0
- package/dist/nodefony/src/server/RealtimeController.js +409 -107
- package/dist/nodefony/src/server/RealtimeHub.js +1158 -111
- package/dist/nodefony/src/server/ServerRealtimeSocket.js +2 -0
- package/dist/nodefony/src/server/admissionBudget.js +176 -0
- package/dist/nodefony/src/server/channelPolicyRegistry.js +95 -0
- package/dist/nodefony/src/server/channelPolicyResolution.js +191 -0
- package/dist/nodefony/src/server/channelPresence.js +488 -0
- package/dist/nodefony/src/server/connectionAdmission.js +242 -0
- package/dist/nodefony/src/server/contractRoutes.js +75 -0
- package/dist/nodefony/src/server/deniedDetail.js +10 -3
- package/dist/nodefony/src/server/frameAdmission.js +160 -0
- package/dist/nodefony/src/server/presenceBus.js +486 -0
- package/dist/nodefony/src/server/realtimeShutdown.js +31 -0
- package/dist/nodefony/src/server/remotePresence.js +541 -0
- package/dist/nodefony/src/server/subscribedAck.js +13 -1
- package/dist/nodefony/src/server/syslogUplink.js +3 -4
- package/dist/nodefony/src/server/welcomeIdentity.js +37 -0
- package/dist/nodefony/src/service/RealtimeService.js +51 -1
- package/dist/nodefony/src/service/publicOrigin.js +40 -0
- package/dist/nodefony/src/transport/WsConnectionTransport.js +27 -4
- package/dist/nodefony/testing/backplaneConformance.js +347 -0
- package/dist/nodefony/testing/index.js +43 -12
- package/dist/nodefony/testing/presenceClient.js +54 -0
- package/dist/types/index.d.ts +39 -7
- package/dist/types/nodefony/config/config.d.ts +9 -0
- package/dist/types/nodefony/decorators/realtimeDecorators.d.ts +198 -17
- package/dist/types/nodefony/interfaces/IBackplane.d.ts +46 -51
- package/dist/types/nodefony/interfaces/IChannelPolicy.d.ts +5 -25
- package/dist/types/nodefony/interfaces/IRealtimeConnection.d.ts +22 -0
- package/dist/types/nodefony/interfaces/IRealtimeConnectionState.d.ts +41 -2
- package/dist/types/nodefony/interfaces/IRealtimeController.d.ts +64 -3
- package/dist/types/nodefony/interfaces/IRealtimeProbe.d.ts +7 -166
- package/dist/types/nodefony/interfaces/IRealtimeToken.d.ts +21 -0
- package/dist/types/nodefony/interfaces/index.d.ts +2 -1
- package/dist/types/nodefony/src/backplane/ClusterBackplane.d.ts +22 -4
- package/dist/types/nodefony/src/backplane/RedisBackplane.d.ts +89 -18
- package/dist/types/nodefony/src/backplane/controlOrder.d.ts +75 -0
- package/dist/types/nodefony/src/backplane/envelope.d.ts +77 -0
- package/dist/types/nodefony/src/backplane/ingress.d.ts +48 -0
- package/dist/types/nodefony/src/backplane/presenceMessage.d.ts +150 -0
- package/dist/types/nodefony/src/backplane/publishQueue.d.ts +25 -1
- package/dist/types/nodefony/src/server/AnonymousRealtimeToken.d.ts +10 -0
- package/dist/types/nodefony/src/server/ContractRealtimeController.d.ts +138 -0
- package/dist/types/nodefony/src/server/RealtimeConnection.d.ts +20 -0
- package/dist/types/nodefony/src/server/RealtimeController.d.ts +110 -12
- package/dist/types/nodefony/src/server/RealtimeHub.d.ts +448 -42
- package/dist/types/nodefony/src/server/ServerRealtimeSocket.d.ts +1 -1
- package/dist/types/nodefony/src/server/admissionBudget.d.ts +71 -0
- package/dist/types/nodefony/src/server/channelPolicyRegistry.d.ts +41 -0
- package/dist/types/nodefony/src/server/channelPolicyResolution.d.ts +112 -0
- package/dist/types/nodefony/src/server/channelPresence.d.ts +155 -0
- package/dist/types/nodefony/src/server/connectionAdmission.d.ts +68 -0
- package/dist/types/nodefony/src/server/contractRoutes.d.ts +42 -0
- package/dist/types/nodefony/src/server/deniedDetail.d.ts +6 -0
- package/dist/types/nodefony/src/server/frameAdmission.d.ts +66 -0
- package/dist/types/nodefony/src/server/presenceBus.d.ts +112 -0
- package/dist/types/nodefony/src/server/realtimeShutdown.d.ts +21 -0
- package/dist/types/nodefony/src/server/remotePresence.d.ts +151 -0
- package/dist/types/nodefony/src/server/subscribedAck.d.ts +10 -0
- package/dist/types/nodefony/src/server/welcomeIdentity.d.ts +19 -0
- package/dist/types/nodefony/src/service/RealtimeService.d.ts +17 -2
- package/dist/types/nodefony/src/service/publicOrigin.d.ts +19 -0
- package/dist/types/nodefony/src/transport/WsConnectionTransport.d.ts +16 -30
- package/dist/types/nodefony/testing/backplaneConformance.d.ts +78 -0
- package/dist/types/nodefony/testing/index.d.ts +13 -2
- package/dist/types/nodefony/testing/presenceClient.d.ts +20 -0
- package/docs/actions.md +420 -18
- package/docs/architecture.md +101 -11
- package/docs/cadence-et-contre-pression.md +87 -11
- package/docs/canaux-a-membres.md +277 -0
- package/docs/cas-d-usage.md +1703 -0
- package/docs/configuration.md +175 -19
- package/docs/cookbook-chat.md +701 -224
- package/docs/index.md +11 -1
- package/docs/observabilite.md +97 -21
- package/docs/protocole.md +142 -33
- package/docs/securite.md +347 -41
- package/docs/vocabulaire.md +13 -12
- 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 };
|
package/dist/types/index.d.ts
CHANGED
|
@@ -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
|
|
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
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
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
|
-
*
|
|
94
|
-
*
|
|
95
|
-
|
|
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
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
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
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
|
|
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
|
-
|
|
30
|
-
|
|
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
|
-
/**
|
|
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
|
|
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
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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
|
|
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
|
+
}
|