@kwirthmagnify/kwirth-common-back 0.5.54 → 0.5.55

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.
@@ -0,0 +1,8 @@
1
+ /** The property the core stamps a consumer instance with. A plain string key, so any producer can read it. */
2
+ export declare const KWIRTH_CONSUMER_ID = "__kwirthConsumerId";
3
+ /** Extension types whose consumer id carries a prefix. Channels do not: their id is the bare channel id. */
4
+ export type TConsumerType = 'provider' | 'sender' | 'webhook' | 'homepage' | 'login' | 'idp' | 'theme' | 'docs' | 'pack';
5
+ export declare const consumerIdFor: (type: TConsumerType, id: string) => string;
6
+ /** Stamps an instance with its qualified consumer id. The core's job; an extension has no reason to call it. */
7
+ export declare const stampConsumerId: (instance: object, consumerId: string) => void;
8
+ export declare const consumerIdOf: (consumer: unknown) => string | undefined;
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ /*
3
+ Who is consuming a provider.
4
+
5
+ Anything that subscribes to a producer has to be able to NAME itself, or the core's registry of
6
+ who-consumes-what — the one the status graph is drawn from — ends up with anonymous edges, and a
7
+ producer that keeps one credential per consumer (the cloud accounts, say) cannot tell who is asking.
8
+
9
+ 🔴 The identity is written by the CORE, never by the extension author: when the core wires an extension
10
+ up to the providers it consumes, it STAMPS the instance with its qualified id — `sender:ses`,
11
+ `webhook:gitlab`, `homepage:status`, `provider:aws` — and hands out a provider access already bound
12
+ to it. A consumer therefore cannot claim to be somebody else and be handed that somebody's secret.
13
+
14
+ Channels are the exception, for history: they name themselves through getChannelData() and their
15
+ consumer id is the bare channel id. A pluvider is a channel that also produces, so it is filed as a
16
+ channel — and the `plugin:` prefix is its id as a PRODUCER, a different thing.
17
+
18
+ This lives in the published contract so that a PRODUCER can read the identity of whoever subscribes
19
+ to it with the very same rule the core applies, instead of each one inventing its own.
20
+ */
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.consumerIdOf = exports.stampConsumerId = exports.consumerIdFor = exports.KWIRTH_CONSUMER_ID = void 0;
23
+ /** The property the core stamps a consumer instance with. A plain string key, so any producer can read it. */
24
+ exports.KWIRTH_CONSUMER_ID = '__kwirthConsumerId';
25
+ const consumerIdFor = (type, id) => `${type}:${id}`;
26
+ exports.consumerIdFor = consumerIdFor;
27
+ /** Stamps an instance with its qualified consumer id. The core's job; an extension has no reason to call it. */
28
+ const stampConsumerId = (instance, consumerId) => {
29
+ Object.defineProperty(instance, exports.KWIRTH_CONSUMER_ID, { value: consumerId, enumerable: false, configurable: true, writable: false });
30
+ };
31
+ exports.stampConsumerId = stampConsumerId;
32
+ /*
33
+ The qualified id of whoever subscribed. The stamp wins; without it, the two legacy shapes: a channel
34
+ names itself through getChannelData() (checked FIRST, because a pluvider also carries an `id`), and a
35
+ bare `id` is taken for a provider wired by a core that did not stamp yet.
36
+
37
+ Returns undefined when the consumer cannot name itself at all. The caller decides: a producer hands an
38
+ anonymous consumer only what is meant for everyone, and the core warns and carries on.
39
+ */
40
+ const consumerIdOf = (consumer) => {
41
+ if (!consumer || typeof consumer !== 'object')
42
+ return undefined;
43
+ const stamped = consumer[exports.KWIRTH_CONSUMER_ID];
44
+ if (typeof stamped === 'string' && stamped.length > 0)
45
+ return stamped;
46
+ const c = consumer;
47
+ if (typeof c.getChannelData === 'function') {
48
+ const id = c.getChannelData()?.id;
49
+ return typeof id === 'string' && id.length > 0 ? id : undefined;
50
+ }
51
+ return typeof c.id === 'string' && c.id.length > 0 ? (0, exports.consumerIdFor)('provider', c.id) : undefined;
52
+ };
53
+ exports.consumerIdOf = consumerIdOf;
@@ -1,4 +1,5 @@
1
1
  import { IExtensionExportOptions, IExtensionImportResult } from '@kwirthmagnify/kwirth-common';
2
+ import type { IProviderAccess } from './IProvider';
2
3
  /**
3
4
  * What an extension writes its log with. The core builds it knowing who the extension is, so the
4
5
  * line comes out identified — '[prov] [ERRO] [longhorn] ...' — and the extension only writes the
@@ -17,7 +18,36 @@ export interface IExtensionLogger {
17
18
  warning(message: unknown): void;
18
19
  error(message: unknown): void;
19
20
  }
21
+ /**
22
+ * What an extension needs from the rest of the core. The same shape for every family: the `providers`
23
+ * list a channel or a provider already declares, now available to all eleven.
24
+ */
25
+ export interface IExtensionRequirements {
26
+ /** Ids of the providers this extension consumes (never a pluvider 'plugin:<name>' id). */
27
+ providers?: string[];
28
+ }
20
29
  export interface IExtension {
30
+ /**
31
+ * The providers this extension CONSUMES. The core instantiates them even if no channel asks for
32
+ * them, the same way it does for a channel's or a provider's requirements. The dependency stays
33
+ * SOFT: one that is not installed is a warning, and the consumer must survive its absence.
34
+ *
35
+ * OPTIONAL: an extension that consumes nothing leaves it out, and an older core ignores it.
36
+ */
37
+ requirements?: IExtensionRequirements;
38
+ /**
39
+ * Called by the core once EVERY provider is registered and started, with an access already bound
40
+ * to this extension's identity. This is where an extension subscribes to the providers it consumes —
41
+ * never from its own start hook, because whether a producer exists at that point depends on the
42
+ * startup order, which the author cannot see.
43
+ *
44
+ * Any family may implement it: a sender that emails through SES reaching the cloud accounts, a
45
+ * homepage showing the state of an account, an IdP reading Cognito or B2C. Whoever subscribes MUST
46
+ * unsubscribe in its own stop hook, or the producer goes on handing events to a dead instance.
47
+ *
48
+ * OPTIONAL: an extension that consumes nothing leaves it out, and an older core never calls it.
49
+ */
50
+ onProvidersReady?(access: IProviderAccess): void | Promise<void>;
21
51
  exportConfig?(options: IExtensionExportOptions): Promise<unknown>;
22
52
  importConfig?(config: unknown): Promise<IExtensionImportResult>;
23
53
  }
@@ -6,9 +6,9 @@ export interface IClusterEndpoint {
6
6
  id?: string;
7
7
  }
8
8
  export declare enum ERemoteConnState {
9
- CONNECTED = "connected",// socket abierto Y instance válido capturado → operativo
10
- HANDSHAKING = "handshaking",// socket abierto pero SIN instance aún (canal remoto arrancando / re-handshake)
11
- RECONNECTING = "reconnecting",// sin socket, reintentando con backoff
9
+ CONNECTED = "connected",// socket open AND a valid instance captured → operational
10
+ HANDSHAKING = "handshaking",// socket open but with NO instance yet (remote channel starting / re-handshake)
11
+ RECONNECTING = "reconnecting",// no socket, retrying with backoff
12
12
  DOWN = "down"
13
13
  }
14
14
  export interface IRemoteChannelHandlers {
@@ -1,14 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ERemoteConnState = void 0;
4
- // Estado de una conexión remota gestionada por el core. Nunca string literals.
5
- // Progresión al establecerse: DOWN → RECONNECTING (buscando socket) → HANDSHAKING (socket abierto, pidiendo
6
- // instance al canal remoto) → CONNECTED (socket + instance = operativo). CONNECTED es el ÚNICO estado en el
7
- // que un comando llega de verdad al canal remoto.
4
+ // State of a remote connection managed by the core. Never string literals.
5
+ // Progression as it comes up: DOWN → RECONNECTING (looking for a socket) → HANDSHAKING (socket open,
6
+ // asking the remote channel for an instance) → CONNECTED (socket + instance = operational). CONNECTED is
7
+ // the ONLY state in which a command actually reaches the remote channel.
8
8
  var ERemoteConnState;
9
9
  (function (ERemoteConnState) {
10
10
  ERemoteConnState["CONNECTED"] = "connected";
11
11
  ERemoteConnState["HANDSHAKING"] = "handshaking";
12
12
  ERemoteConnState["RECONNECTING"] = "reconnecting";
13
- ERemoteConnState["DOWN"] = "down"; // conexión cerrada / nunca establecida (terminal o sin credenciales)
13
+ ERemoteConnState["DOWN"] = "down"; // connection closed / never established (terminal or no credentials)
14
14
  })(ERemoteConnState || (exports.ERemoteConnState = ERemoteConnState = {}));
@@ -4,7 +4,7 @@ export declare enum EIdpConnectorKind {
4
4
  OIDC = "oidc",
5
5
  OAUTH2 = "oauth2"
6
6
  }
7
- /** @deprecated usa TConfigFieldType, comun a todas las extensiones. */
7
+ /** @deprecated use TConfigFieldType, common to every extension. */
8
8
  export type IdpFieldType = TConfigFieldType;
9
9
  export type IIdpConfigFieldDef = IConfigFieldDef;
10
10
  export interface IIdpIdentity {
@@ -1,13 +1,13 @@
1
1
  "use strict";
2
2
  /*
3
- Interfaz de conector de Identity Provider (IdP) para Kwirth.
3
+ The Identity Provider (IdP) connector interface for Kwirth.
4
4
 
5
- Un conector es LOGICA PURA (sin rutas propias): construye la URL de autorizacion del IdP
6
- y procesa el callback devolviendo la identidad verificada. El flujo HTTP pre-login y la
7
- emision de AccessKey viven en el core de Kwirth, nunca en el conector.
5
+ A connector is PURE LOGIC (with no routes of its own): it builds the IdP's authorization URL and
6
+ processes the callback, returning the verified identity. The pre-login HTTP flow and the issuing of
7
+ the AccessKey live in Kwirth's core, never in the connector.
8
8
 
9
- Vive en common-back para que los conectores empaquetados por separado (idps/<id>/) puedan
10
- implementarlo importando '@kwirthmagnify/kwirth-common-back', igual que ISender/IProvider.
9
+ It lives in common-back so that connectors packaged separately (idps/<id>/) can implement it by
10
+ importing '@kwirthmagnify/kwirth-common-back', just like ISender/IProvider.
11
11
  */
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
13
  exports.EIdpConnectorKind = void 0;
package/dist/ILogin.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { TConfigFieldType, IConfigFieldDef } from '@kwirthmagnify/kwirth-common';
2
- /** @deprecated usa TConfigFieldType, comun a todas las extensiones. */
2
+ /** @deprecated use TConfigFieldType, common to every extension. */
3
3
  export type LoginFieldType = TConfigFieldType;
4
- /** Campo de configuracion de un login. Es el contrato comun IConfigFieldDef, sin nada propio. */
4
+ /** A login's configuration field. It is the common contract IConfigFieldDef, with nothing of its own. */
5
5
  export type ILoginFieldDef = IConfigFieldDef;
@@ -1,53 +1,53 @@
1
1
  import { IProviderSubscriber, IProviderSubscriptionHelp } from './IProvider';
2
2
  /**
3
- * Lo que el gestor de extensiones y provider-debug enseñan de un pluvider. El consumidor de un
4
- * pluvider es OTRO EQUIPO, asi que hace falta algo que mostrar sin leerse el codigo.
3
+ * What the extension manager and provider-debug show about a pluvider. The consumer of a pluvider is
4
+ * ANOTHER TEAM, so there has to be something to show without reading the code.
5
5
  */
6
6
  export interface IPluviderData {
7
- /** Que produce, en una linea. */
7
+ /** What it produces, in one line. */
8
8
  description: string;
9
- /** Nombre del tipo del evento que emite (p.ej. 'IAgoraAlert'), para orientar al consumidor. */
9
+ /** Name of the type of event it emits (e.g. 'IAgoraAlert'), to orient the consumer. */
10
10
  eventTypeName?: string;
11
11
  }
12
12
  /**
13
- * Un plugin que ADEMAS produce: expone in-process la informacion que ya genera, para que otros
14
- * plugins se suscriban a ella. No es una segunda extension empaquetada dentro del plugin: lo
15
- * implementa la MISMA clase del canal, sobre la misma instancia y los mismos datos.
13
+ * A plugin that ALSO produces: it exposes in-process the information it already generates, so other
14
+ * plugins can subscribe to it. It is not a second extension packaged inside the plugin: it is
15
+ * implemented by the channel's SAME class, on the same instance and the same data.
16
16
  *
17
- * NO extiende IProvider a proposito. Un pluvider no pasa por la maquinaria de providers —no se mete
18
- * en 'clusterInfo.providers', que es lo que recorren los bucles que montan routers, escriben
19
- * 'apiKeyApi' o marcan 'started'—, asi que no tiene 'id', 'router', 'routerAlias', 'providesRouter',
20
- * 'requiresApiKeyApi' ni 'apiKeyApi'.
17
+ * It does NOT extend IProvider, on purpose. A pluvider does not go through the provider machinery — it
18
+ * is not put into 'clusterInfo.providers', which is what the loops that mount routers, write
19
+ * 'apiKeyApi' or set 'started' walk over — so it has no 'id', 'router', 'routerAlias',
20
+ * 'providesRouter', 'requiresApiKeyApi' or 'apiKeyApi'.
21
21
  *
22
- * El id tampoco lo escribe el autor: lo compone el core como '<PLUVIDER_ID_PREFIX><channelId>', para
23
- * que nadie se equivoque con el prefijo.
22
+ * Nor does the author write the id: the core composes it as '<PLUVIDER_ID_PREFIX><channelId>', so
23
+ * nobody gets the prefix wrong.
24
24
  *
25
- * 'TSub' es la forma del filtro de suscripcion. Como en los providers, cada pluvider decide si
26
- * filtra y con que forma; si no filtra, se deja el generico sin especificar.
25
+ * 'TSub' is the shape of the subscription filter. As with providers, each pluvider decides whether it
26
+ * filters and with what shape; if it does not filter, the generic is left unspecified.
27
27
  *
28
- * Ejemplo:
28
+ * Example:
29
29
  *
30
30
  * class AgoraChannel implements IChannel, IPluvider<IAgoraAlertSubscription> { … }
31
31
  */
32
32
  export interface IPluvider<TSub = unknown> {
33
33
  /**
34
- * Metadatos del pluvider. Su PRESENCIA es la declaracion: un canal que implementa este metodo se
35
- * ofrece como productor, y el core lo registra. No hay flags ni deteccion por duck-typing.
34
+ * The pluvider's metadata. Its PRESENCE is the declaration: a channel implementing this method
35
+ * offers itself as a producer, and the core registers it. No flags, no duck-typing detection.
36
36
  */
37
37
  getPluviderData(): IPluviderData;
38
38
  addSubscriber(c: IProviderSubscriber, data: TSub): Promise<void>;
39
39
  removeSubscriber(c: IProviderSubscriber): Promise<void>;
40
40
  updateSubscription?(c: IProviderSubscriber, data: TSub): Promise<void>;
41
41
  /**
42
- * Arranca la produccion. El core lo llama en la fase de pluviders, es decir ANTES de
43
- * 'startChannel()': el trabajo de fondo vive en este lado y el front se engancha despues.
42
+ * Starts production. The core calls it in the pluviders phase, that is BEFORE 'startChannel()':
43
+ * the background work lives on this side and the front end hooks in afterwards.
44
44
  */
45
45
  startProvider(): Promise<void>;
46
46
  stopProvider(): Promise<void>;
47
47
  /**
48
- * Obligatorio, a diferencia del homonimo de IProvider, que es opcional. Un provider suele
49
- * consumirlo quien lo escribio; un pluvider lo consume gente de fuera, y sin esto no tiene como
50
- * saber que escribir en la suscripcion ni que va a recibir.
48
+ * Mandatory, unlike its namesake on IProvider, which is optional. A provider is usually consumed
49
+ * by whoever wrote it; a pluvider is consumed by outsiders, and without this they have no way of
50
+ * knowing what to write in the subscription or what they are going to receive.
51
51
  */
52
52
  getSubscriptionHelp(): IProviderSubscriptionHelp;
53
53
  }
@@ -45,9 +45,9 @@ export interface IProviderHandle {
45
45
  unsubscribe(subscriber: IProviderSubscriber): unknown;
46
46
  }
47
47
  /**
48
- * Persistencia que el core inyecta al provider (mismo mecanismo que reciben los canales).
49
- * El booleano 'secret' decide el destino: true -> Secret de Kubernetes, false -> ConfigMap.
50
- * Las variantes 'Common' escriben en el almacen compartido entre extensiones.
48
+ * Persistence the core injects into the provider (the same mechanism channels receive).
49
+ * The 'secret' boolean decides the destination: true -> Kubernetes Secret, false -> ConfigMap.
50
+ * The 'Common' variants write to the store shared between extensions.
51
51
  */
52
52
  export interface IProviderStorage {
53
53
  writeStorage(id: string, secret: boolean, data: any): Promise<void>;
@@ -56,9 +56,9 @@ export interface IProviderStorage {
56
56
  readStorageCommon(id: string, secret: boolean): Promise<any>;
57
57
  }
58
58
  /**
59
- * Un campo del payload de suscripcion, descrito para que un consumidor pueda pintar un formulario
60
- * en vez de exigir JSON a mano. Solo tiene sentido declararlos cuando el payload es plano; si es
61
- * anidado (p.ej. otel, con 'spaces'), basta con 'usage' y 'example'.
59
+ * A field of the subscription payload, described so a consumer can draw a form instead of demanding
60
+ * hand-written JSON. Declaring them only makes sense when the payload is flat; when it is nested
61
+ * (otel, for instance, with 'spaces'), 'usage' and 'example' are enough.
62
62
  */
63
63
  export interface IProviderSubscriptionField {
64
64
  name: string;
@@ -67,60 +67,81 @@ export interface IProviderSubscriptionField {
67
67
  description: string;
68
68
  }
69
69
  /**
70
- * Ayuda que un provider publica sobre COMO SUSCRIBIRSE a el, es decir sobre el argumento 'data' de
71
- * addSubscriber. No confundir con el 'schema' que un provider exporta desde su back.js, que
72
- * describe la configuracion del propio provider (configure/configRouter).
70
+ * Help a provider publishes about HOW TO SUBSCRIBE to it, that is, about the 'data' argument of
71
+ * addSubscriber. Not to be confused with the 'schema' a provider exports from its back.js, which
72
+ * describes the provider's own configuration (configure/configRouter).
73
73
  *
74
- * La consume provider-debug para explicarle al usuario que escribir, pero cualquier canal que
75
- * ofrezca elegir provider puede usarla.
74
+ * provider-debug consumes it to explain to the user what to write, but any channel that offers a
75
+ * choice of provider can use it.
76
76
  */
77
77
  export interface IProviderSubscriptionHelp {
78
- /** Como se usa, en prosa: que entrega, que hace falta para recibir algo, gotchas. */
78
+ /** How it is used, in prose: what it delivers, what it takes to receive anything, gotchas. */
79
79
  usage: string;
80
- /** Payload de ejemplo, listo para pasar tal cual a addSubscriber. */
80
+ /** Example payload, ready to pass to addSubscriber as it is. */
81
81
  example: Record<string, unknown>;
82
- /** Descripcion campo a campo. Opcional: solo para payloads planos. */
82
+ /** Field-by-field description. Optional: only for flat payloads. */
83
83
  fields?: IProviderSubscriptionField[];
84
84
  }
85
85
  /**
86
- * Un campo de la configuracion del PROPIO provider (no de la suscripcion). Es el contrato comun
87
- * IConfigFieldDef, el mismo que usan senders, webhooks, idps y logins.
86
+ * A field of the provider's OWN configuration (not of the subscription). It is the common contract
87
+ * IConfigFieldDef, the same one senders, webhooks, idps and logins use.
88
88
  */
89
89
  export type IProviderFieldDef = IConfigFieldDef;
90
90
  /**
91
- * Lo que un provider sabe contar de si mismo.
91
+ * What a provider needs from the rest of the core. Same shape as the 'providers' list of a channel's
92
+ * requirements, so both read alike; see IProvider.requirements.
93
+ */
94
+ export interface IProviderRequirements {
95
+ /** Ids of the providers this one consumes (never a pluvider 'plugin:<name>' id). */
96
+ providers: string[];
97
+ }
98
+ /**
99
+ * What the core hands ANY extension that consumes providers, in its onProvidersReady(). It is already
100
+ * bound to the consumer's identity — the core stamps the instance and builds this for it — so the
101
+ * extension names nobody: it asks for a producer by id and gets a handle, or undefined if it is not
102
+ * installed (a SOFT dependency, to be survived).
103
+ *
104
+ * It is the same door for every family: a sender that emails through SES, a webhook, a homepage that
105
+ * shows the state of a cloud account, an IdP that reads Cognito or B2C — all of them reach a provider
106
+ * through this and nothing else, so the core's registry of who consumes what stays true.
107
+ */
108
+ export interface IProviderAccess {
109
+ getProvider(providerId: string): IProviderHandle | undefined;
110
+ }
111
+ /**
112
+ * What a provider can tell about itself.
92
113
  *
93
- * Existe para que kwirth pueda decir si algo esta siendo consumido o esta emitiendo para nadie, que
94
- * es de las pocas preguntas que NADIE puede responder desde fuera: cada provider guarda sus
95
- * suscriptores en su propia estructura y hasta ahora no habia forma de preguntarselo.
114
+ * It exists so kwirth can say whether something is being consumed or emitting to nobody, which is one
115
+ * of the few questions NOBODY can answer from the outside: each provider keeps its subscribers in its
116
+ * own structure, and until now there was no way to ask it.
96
117
  *
97
- * ⚠️ Solo el NUMERO, no quienes son: 'IProviderSubscriber' es una interfaz de un solo metodo y no
98
- * lleva identidad, asi que un provider no tiene con que identificarlos. Dibujar el grafo de quien
99
- * consume a quien pedira ampliar ese contrato, y es una decision aparte.
118
+ * ⚠️ Only the NUMBER, not who they are: 'IProviderSubscriber' is a single-method interface and carries
119
+ * no identity, so a provider has nothing to identify them with. Drawing the graph of who consumes whom
120
+ * will require widening that contract, and that is a separate decision.
100
121
  */
101
122
  export interface IProviderStats {
102
- /** Cuantos suscriptores tiene AHORA. Cero significa que esta emitiendo para nadie. */
123
+ /** How many subscribers it has RIGHT NOW. Zero means it is emitting to nobody. */
103
124
  subscribers: number;
104
125
  /**
105
- * ENTREGAS hechas desde que el provider arranco: una por cada vez que se llama a
106
- * processProviderEvent, no una por evento producido. OPCIONAL: quien no lo lleve se muestra como
107
- * "no informa", igual que el resto.
126
+ * DELIVERIES made since the provider started: one per call to processProviderEvent, not one per
127
+ * event produced. OPTIONAL: whoever does not keep it is shown as "not reported", like the rest.
108
128
  *
109
- * Se cuentan entregas y no eventos a proposito. Un provider que produce mil eventos y los filtra
110
- * todos no esta moviendo nada, y el numero util para quien opera es el trabajo que SE HACE. Ademas
111
- * el sitio donde incrementar es inequivoco —justo donde ya se llama al suscriptor—, y eso hace que
112
- * cablearlo en dieciseis providers no dependa de interpretar el codigo de cada uno.
129
+ * Deliveries are counted instead of events on purpose. A provider that produces a thousand events
130
+ * and filters them all out is moving nothing, and the number that is useful to whoever operates is
131
+ * the work that ACTUALLY HAPPENS. Besides, the place to increment is unambiguous — right where the
132
+ * subscriber is already called — and that means wiring it in sixteen providers does not depend on
133
+ * interpreting each one's code.
113
134
  *
114
- * Es un ACUMULADO, no una tasa: quien lo lea resta dos lecturas y divide por el tiempo. El provider
115
- * no debe saber nada de ventanas ni de medias — eso obligaria a guardar historia en el camino
116
- * caliente, que es justo lo que no puede pasar.
135
+ * It is a RUNNING TOTAL, not a rate: whoever reads it subtracts two readings and divides by time.
136
+ * The provider must know nothing about windows or averages — that would force keeping history in
137
+ * the hot path, which is exactly what must not happen.
117
138
  *
118
- * ⚠️ El incremento va JUNTO a la llamada al suscriptor, y es un entero. Nada
119
- * de timestamps por evento, nada de arrays que crezcan, nada de objetos nuevos: lo que duele en
120
- * Node no es el contador, es la basura que genera.
139
+ * ⚠️ The increment goes RIGHT NEXT to the subscriber call, and it is an integer. No per-event
140
+ * timestamps, no growing arrays, no new objects: what hurts in Node is not the counter, it is the
141
+ * garbage it generates.
121
142
  */
122
143
  events?: number;
123
- /** Errores al entregar, con el mismo criterio: acumulado y barato. */
144
+ /** Delivery errors, under the same criterion: a running total, and cheap. */
124
145
  errors?: number;
125
146
  }
126
147
  /**
@@ -135,41 +156,40 @@ export interface IProvider extends IExtension {
135
156
  removeSubscriber(c: IProviderSubscriber): Promise<void>;
136
157
  updateSubscription?(c: IProviderSubscriber, data: any): Promise<void>;
137
158
  /**
138
- * @deprecated El core deja de alimentar este metodo: un provider es dueño de su propia
139
- * configuracion y la sirve por 'configRouter'. Se mantiene por compatibilidad con providers
140
- * de terceros que aun usen la config gestionada por el core.
159
+ * @deprecated The core no longer feeds this method: a provider owns its own configuration and
160
+ * serves it through 'configRouter'. It is kept for compatibility with third-party providers that
161
+ * still use the core-managed config.
141
162
  */
142
163
  configure?(config: Record<string, unknown>): void;
143
164
  /**
144
- * Ayuda de suscripcion. OPCIONAL: quien escriba un provider la añade si quiere. Sin ella el
145
- * consumidor sigue funcionando, simplemente no tiene nada que enseñarle al usuario sobre que
146
- * payload escribir.
165
+ * Subscription help. OPTIONAL: whoever writes a provider adds it if they want to. Without it the
166
+ * consumer keeps working, it simply has nothing to show the user about which payload to write.
147
167
  */
148
168
  getSubscriptionHelp?(): IProviderSubscriptionHelp;
149
169
  /**
150
- * Nombres de las configuraciones que el provider tiene definidas (equivalente a
151
- * ISender.getConfigNames). OPCIONAL: solo tiene sentido en un provider que sea dueño de su
152
- * configuracion. El gestor de extensiones lo usa para mostrar cuantas hay en la tarjeta, igual
153
- * que hace con los senders. No expone valores, solo nombres.
170
+ * Names of the configurations the provider has defined (the equivalent of
171
+ * ISender.getConfigNames). OPTIONAL: it only makes sense on a provider that owns its
172
+ * configuration. The extension manager uses it to show how many there are on the card, just as it
173
+ * does with senders. It exposes no values, only names.
154
174
  */
155
175
  getConfigNames?(): string[];
156
176
  /**
157
- * Schema de configuracion del propio provider, con el que kwirth pinta un formulario generico.
158
- * Es la forma ESTANDAR de declararlo, la misma que ISender.getConfigSchema e IWebhook.
177
+ * Configuration schema of the provider itself, which kwirth uses to draw a generic form.
178
+ * This is the STANDARD way to declare it, the same as ISender.getConfigSchema and IWebhook.
159
179
  *
160
- * Un provider al que nadie se suscribe y que no expone router NO se instancia nunca, asi que en
161
- * ese caso no hay a quien preguntarselo: para esos, exporta ademas una constante 'schema' con el
162
- * mismo array desde el back.js, que el core lee al instalar sin instanciar nada.
180
+ * A provider nobody subscribes to and that exposes no router is NEVER instantiated, so in that
181
+ * case there is nobody to ask: for those, also export a 'schema' constant with the same array from
182
+ * the back.js, which the core reads at install time without instantiating anything.
163
183
  */
164
184
  getConfigSchema?(): IProviderFieldDef[];
165
185
  /**
166
- * Que sabe el provider de si mismo ahora mismo. OPCIONAL, como el resto de este bloque: quien no
167
- * lo implemente se muestra como "no informa", que es distinto de cero — un cero seria una
168
- * afirmacion que nadie puede sostener.
186
+ * What the provider knows about itself right now. OPTIONAL, like the rest of this block: whoever
187
+ * does not implement it is shown as "not reported", which is different from zero — a zero would be
188
+ * a claim nobody can back up.
169
189
  *
170
- * ⚠️ Tiene que ser BARATO: devuelve lo que ya tienes, no lo calcules. Se llama cuando alguien
171
- * abre una pantalla de estado, pero un provider no sabe con que frecuencia, y recorrer
172
- * estructuras aqui convierte una consulta en trabajo para todos.
190
+ * ⚠️ It has to be CHEAP: return what you already have, do not compute it. It is called when
191
+ * somebody opens a status screen, but a provider does not know how often, and walking structures
192
+ * here turns a query into work for everyone.
173
193
  */
174
194
  getStats?(): IProviderStats;
175
195
  /**
@@ -202,27 +222,41 @@ export interface IProvider extends IExtension {
202
222
  * and an older core that does not know about it simply never calls anyone.
203
223
  */
204
224
  onProvidersReady?(): void | Promise<void>;
225
+ /**
226
+ * The providers this provider CONSUMES. The core instantiates them even if no channel asks for
227
+ * them, the same way it instantiates the ones a channel lists in its own requirements.
228
+ *
229
+ * Without it, a producer nobody else asks for and that exposes no router is never instantiated,
230
+ * and getProvider() hands the consumer 'undefined' in onProvidersReady(). The dependency stays
231
+ * SOFT: one that is not installed is a warning, and the consumer must survive its absence.
232
+ *
233
+ * Pluvider ids ('plugin:<name>') are not listed here: a pluvider exists when its plugin is
234
+ * installed, the core cannot create it.
235
+ *
236
+ * OPTIONAL: a provider that consumes nothing leaves it out, and an older core ignores it.
237
+ */
238
+ requirements?: IProviderRequirements;
205
239
  startProvider(): Promise<void>;
206
240
  stopProvider(): Promise<void>;
207
241
  router: any;
208
242
  routerAlias: string | undefined;
209
243
  /**
210
- * El provider quiere el cuerpo de las peticiones de su router publico EN CRUDO (Buffer), sin que
211
- * el bodyParser global del core lo toque.
244
+ * The provider wants the body of the requests to its public router RAW (a Buffer), untouched by
245
+ * the core's global bodyParser.
212
246
  *
213
- * Hace falta para todo lo que no sea JSON plano: ndjson, msgpack, protobuf, o verificar una firma
214
- * sobre los bytes exactos que llegaron. Sin esto, una extension que INGIERE recibe el cuerpo ya
215
- * parseado —y con el limite del parser global—, que es justo lo que el core resolvio para los
216
- * webhooks montandolos por delante.
247
+ * Needed for anything that is not plain JSON: ndjson, msgpack, protobuf, or verifying a signature
248
+ * over the exact bytes that arrived. Without this, an extension that INGESTS receives the body
249
+ * already parsed — and with the global parser's limit — which is exactly what the core solved for
250
+ * webhooks by mounting them in front.
217
251
  *
218
- * Por defecto es false: los providers que hoy leen 'req.body' como objeto siguen igual.
252
+ * It defaults to false: providers that read 'req.body' as an object today are unaffected.
219
253
  */
220
254
  readonly rawBody?: boolean;
221
255
  /**
222
- * Router de gestion del provider (su propia configuracion). El core lo monta SIEMPRE detras de
223
- * validacion de accessKey, igual que hace con los endpoints de un canal, en la ruta
224
- * '/core/providerconfig/<providerId>'. Es una via distinta de 'router', que es publica y puede
225
- * recibir trafico externo (OTLP, POSTs de terceros) y por tanto no puede exigir accessKey.
256
+ * The provider's management router (its own configuration). The core ALWAYS mounts it behind
257
+ * accessKey validation, just as it does with a channel's endpoints, at the route
258
+ * '/core/providerconfig/<providerId>'. It is a separate path from 'router', which is public and may
259
+ * receive external traffic (OTLP, third-party POSTs) and therefore cannot demand an accessKey.
226
260
  */
227
261
  configRouter?: any;
228
262
  apiKeyApi: any | undefined;
package/dist/ISender.d.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  import { ISenderMessage, ISenderConfig, ISenderAccess, ISenderStoredConfig, ISenderResult, TConfigFieldType, IConfigFieldDef, IExtensionNodeMeta } from '@kwirthmagnify/kwirth-common';
2
2
  import { IExtension, IExtensionLogger } from './IExtension';
3
3
  export { ISenderMessage, ISenderConfig, ISenderAccess, ISenderStoredConfig, ISenderResult };
4
- /** @deprecated usa TConfigFieldType, comun a todas las extensiones. */
4
+ /** @deprecated use TConfigFieldType, common to every extension. */
5
5
  export type SenderFieldType = TConfigFieldType;
6
- /** Campo de configuracion de un sender. Es el contrato comun IConfigFieldDef, sin nada propio. */
6
+ /** A sender's configuration field. It is the common contract IConfigFieldDef, with nothing of its own. */
7
7
  export type ISenderFieldDef = IConfigFieldDef;
8
- /** @deprecated usa IExtensionNodeMeta, comun a todas las extensiones. */
8
+ /** @deprecated use IExtensionNodeMeta, common to every extension. */
9
9
  export type ISenderNodeMeta = IExtensionNodeMeta;
10
10
  export interface ISender extends IExtension {
11
11
  readonly id: string;
@@ -1,11 +1,11 @@
1
1
  import { IWebhookEvent, IWebhookConfig, IWebhookAccess, IWebhookConsumer, IWebhookStoredConfig, TConfigFieldType, IConfigFieldDef, IExtensionNodeMeta } from '@kwirthmagnify/kwirth-common';
2
2
  import { IExtension } from './IExtension';
3
3
  export { IWebhookEvent, IWebhookConfig, IWebhookAccess, IWebhookConsumer, IWebhookStoredConfig };
4
- /** @deprecated usa TConfigFieldType, comun a todas las extensiones. */
4
+ /** @deprecated use TConfigFieldType, common to every extension. */
5
5
  export type WebhookFieldType = TConfigFieldType;
6
- /** Campo de configuracion de un webhook. Es el contrato comun IConfigFieldDef, sin nada propio. */
6
+ /** A webhook's configuration field. It is the common contract IConfigFieldDef, with nothing of its own. */
7
7
  export type IWebhookFieldDef = IConfigFieldDef;
8
- /** @deprecated usa IExtensionNodeMeta, comun a todas las extensiones. */
8
+ /** @deprecated use IExtensionNodeMeta, common to every extension. */
9
9
  export type IWebhookNodeMeta = IExtensionNodeMeta;
10
10
  export interface IWebhook extends IExtension {
11
11
  readonly id: string;
@@ -7,9 +7,9 @@ export interface ICrdInformerHandlers {
7
7
  onError?: (err: any) => void;
8
8
  }
9
9
  export declare function createCrdInformer(clusterInfo: any, apiGroup: string, apiVersion: string, plural: string, handlers: ICrdInformerHandlers): any;
10
- /** Informer genérico para CUALQUIER recurso (core o grupo): el llamante aporta el `watchPath` (p.ej.
11
- * '/api/v1/services', '/apis/networking.k8s.io/v1/ingresses') y la `listFn` que devuelve {items}.
12
- * Reutilizado por createCrdInformer y por consumidores de recursos core (p.ej. exposure). */
10
+ /** Generic informer for ANY resource (core or group): the caller supplies the `watchPath` (e.g.
11
+ * '/api/v1/services', '/apis/networking.k8s.io/v1/ingresses') and the `listFn` that returns {items}.
12
+ * Reused by createCrdInformer and by consumers of core resources (exposure, for instance). */
13
13
  export declare function createInformer(clusterInfo: any, watchPath: string, listFn: () => Promise<{
14
14
  items: any[];
15
15
  }>, handlers: ICrdInformerHandlers): any;
@@ -127,9 +127,9 @@ function createCrdInformer(clusterInfo, apiGroup, apiVersion, plural, handlers)
127
127
  .then((res) => res);
128
128
  return createInformer(clusterInfo, path, listFunction, handlers);
129
129
  }
130
- /** Informer genérico para CUALQUIER recurso (core o grupo): el llamante aporta el `watchPath` (p.ej.
131
- * '/api/v1/services', '/apis/networking.k8s.io/v1/ingresses') y la `listFn` que devuelve {items}.
132
- * Reutilizado por createCrdInformer y por consumidores de recursos core (p.ej. exposure). */
130
+ /** Generic informer for ANY resource (core or group): the caller supplies the `watchPath` (e.g.
131
+ * '/api/v1/services', '/apis/networking.k8s.io/v1/ingresses') and the `listFn` that returns {items}.
132
+ * Reused by createCrdInformer and by consumers of core resources (exposure, for instance). */
133
133
  function createInformer(clusterInfo, watchPath, listFn, handlers) {
134
134
  const informer = k8s.makeInformer(clusterInfo.kubeConfig, watchPath, listFn);
135
135
  if (handlers.onAdd)
package/dist/github.js CHANGED
@@ -16,9 +16,9 @@ async function ghGet(base, resource, accessToken) {
16
16
  async function githubIdentityFromToken(apiBaseUrl, accessToken) {
17
17
  const base = apiBaseUrl.replace(/\/+$/, '');
18
18
  const user = await ghGet(base, '/user', accessToken);
19
- // /user/emails puede fallar si falta el scope user:email; en ese caso caemos al email público
19
+ // /user/emails can fail when the user:email scope is missing; in that case we fall back to the public email
20
20
  const emails = await ghGet(base, '/user/emails', accessToken).catch(() => []);
21
- // preferimos el email primary; si no, el primero verificado; si no, el primero que haya
21
+ // we prefer the primary email; failing that, the first verified one; failing that, the first there is
22
22
  const chosen = emails.find(e => e.primary) ?? emails.find(e => e.verified) ?? emails[0];
23
23
  return {
24
24
  email: chosen?.email ?? user.email ?? '',
package/dist/index.d.ts CHANGED
@@ -8,6 +8,7 @@ export * from './ISender';
8
8
  export * from './IWebhook';
9
9
  export * from './IIdpConnector';
10
10
  export * from './IExtension';
11
+ export * from './Consumer';
11
12
  export * from './oidc';
12
13
  export * from './oauth2';
13
14
  export * from './github';
package/dist/index.js CHANGED
@@ -24,6 +24,7 @@ __exportStar(require("./ISender"), exports);
24
24
  __exportStar(require("./IWebhook"), exports);
25
25
  __exportStar(require("./IIdpConnector"), exports);
26
26
  __exportStar(require("./IExtension"), exports);
27
+ __exportStar(require("./Consumer"), exports);
27
28
  __exportStar(require("./oidc"), exports);
28
29
  __exportStar(require("./oauth2"), exports);
29
30
  __exportStar(require("./github"), exports);
package/dist/oauth2.js CHANGED
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.oauth2ConfigSchema = oauth2ConfigSchema;
4
4
  exports.oauth2BuildAuthorizationUrl = oauth2BuildAuthorizationUrl;
5
5
  exports.oauth2HandleCallback = oauth2HandleCallback;
6
- // esquema base de un IdP OAuth2 (el conector añade sus URLs si aplica; clientSecret 'password' → se enmascara)
6
+ // base schema of an OAuth2 IdP (the connector adds its URLs where applicable; clientSecret 'password' → masked)
7
7
  function oauth2ConfigSchema() {
8
8
  return [
9
9
  { name: 'clientId', label: 'Client ID', type: 'text', required: true },
@@ -26,7 +26,7 @@ function oauth2BuildAuthorizationUrl(config, ctx, ep) {
26
26
  }
27
27
  return url.toString();
28
28
  }
29
- // intercambia el 'code' por access_token (back-channel) y delega el userinfo en fetchIdentity(accessToken).
29
+ // exchanges the 'code' for an access_token (back-channel) and delegates userinfo to fetchIdentity(accessToken).
30
30
  async function oauth2HandleCallback(config, ctx, ep, fetchIdentity) {
31
31
  const body = new URLSearchParams();
32
32
  body.set('grant_type', 'authorization_code');
@@ -36,7 +36,7 @@ async function oauth2HandleCallback(config, ctx, ep, fetchIdentity) {
36
36
  body.set('redirect_uri', ctx.redirectUri);
37
37
  if (ep.usePkce)
38
38
  body.set('code_verifier', ctx.codeVerifier);
39
- // Accept: application/json → algunos IdP (GitHub) devuelven form-urlencoded sin esta cabecera
39
+ // Accept: application/json → some IdPs (GitHub) return form-urlencoded without this header
40
40
  const res = await fetch(ep.tokenEndpoint, {
41
41
  method: 'POST',
42
42
  headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Accept': 'application/json' },
package/dist/oidc.js CHANGED
@@ -40,7 +40,7 @@ exports.oidcBuildAuthorizationUrl = oidcBuildAuthorizationUrl;
40
40
  exports.oidcHandleCallback = oidcHandleCallback;
41
41
  const openid_client_1 = require("openid-client");
42
42
  const jose = __importStar(require("jose"));
43
- // esquema de config estándar de un IdP OIDC (clientSecret es 'password' → se enmascara en la UI)
43
+ // standard config schema of an OIDC IdP (clientSecret is 'password' → masked in the UI)
44
44
  function oidcConfigSchema() {
45
45
  return [
46
46
  { name: 'clientId', label: 'Client ID', type: 'text', required: true },
@@ -62,7 +62,7 @@ async function makeClient(config, redirectUri, defaultIssuer) {
62
62
  });
63
63
  return { issuer, client };
64
64
  }
65
- // mapea los claims del id_token a la identidad de Kwirth (fallback de email + verified asumido)
65
+ // maps the id_token claims to Kwirth's identity (email fallback + assumed verified)
66
66
  function mapOidcIdentity(claims, opts) {
67
67
  const emailClaims = opts?.emailClaims ?? ['email'];
68
68
  let email = '';
@@ -80,7 +80,7 @@ function mapOidcIdentity(claims, opts) {
80
80
  sub: claims.sub !== undefined ? String(claims.sub) : undefined
81
81
  };
82
82
  }
83
- // ¿el tenant (tid) está permitido? (allowlist vacía = cualquier tenant)
83
+ // is the tenant (tid) allowed? (an empty allowlist means any tenant)
84
84
  function tenantAllowed(tid, allowed) {
85
85
  if (!allowed || allowed.length === 0)
86
86
  return true;
@@ -103,9 +103,10 @@ async function oidcBuildAuthorizationUrl(config, ctx, defaultIssuer) {
103
103
  async function oidcHandleCallback(config, ctx, defaultIssuer, opts) {
104
104
  const { issuer, client } = await makeClient(config, ctx.redirectUri, defaultIssuer);
105
105
  if (opts?.multiTenant) {
106
- // openid-client valida el iss de forma LITERAL y en multi-tenant el issuer descubierto lleva el
107
- // placeholder {tenantid}; hacemos el intercambio crudo (grant, sin validar id_token) y validamos
108
- // el id_token a mano con jose: firma (JWKS del issuer) + iss contra el tid concreto del token.
106
+ // openid-client validates iss LITERALLY, and in multi-tenant the discovered issuer carries the
107
+ // {tenantid} placeholder; so we do the raw exchange (grant, without validating the id_token) and
108
+ // validate the id_token by hand with jose: signature (the issuer's JWKS) + iss against the
109
+ // token's concrete tid.
109
110
  const tokenSet = await client.grant({
110
111
  grant_type: 'authorization_code',
111
112
  code: ctx.code,
@@ -114,7 +115,7 @@ async function oidcHandleCallback(config, ctx, defaultIssuer, opts) {
114
115
  });
115
116
  if (!tokenSet.id_token)
116
117
  throw new Error('OIDC multi-tenant: token response has no id_token');
117
- const tid = tokenSet.claims().tid; // sin verificar; solo para conocer el tenant
118
+ const tid = tokenSet.claims().tid; // unverified; only to learn the tenant
118
119
  if (!tid)
119
120
  throw new Error('OIDC multi-tenant: id_token has no tid claim');
120
121
  if (!tenantAllowed(tid, opts.allowedTenants))
@@ -130,8 +131,8 @@ async function oidcHandleCallback(config, ctx, defaultIssuer, opts) {
130
131
  });
131
132
  return mapOidcIdentity(payload, opts);
132
133
  }
133
- // single-tenant: pasamos los params crudos del callback (incluyen iss para RFC 9207 y state); el state
134
- // ya lo valida el core, pero openid-client exige checks.state si el param state viene presente.
134
+ // single-tenant: we pass the callback's raw params (they include iss for RFC 9207, and state); the
135
+ // core already validates state, but openid-client demands checks.state when the param is present.
135
136
  const params = ctx.params ?? { code: ctx.code };
136
137
  const checks = { code_verifier: ctx.codeVerifier };
137
138
  if (params.state !== undefined)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kwirthmagnify/kwirth-common-back",
3
- "version": "0.5.54",
3
+ "version": "0.5.55",
4
4
  "description": "Backend interfaces for building Kwirth provider and channel plugins",
5
5
  "scripts": {
6
6
  "build": "tsc",