@hostwebhook/node-sdk 0.5.0 → 0.7.0
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.
|
@@ -37,23 +37,70 @@ export type CredRefShape =
|
|
|
37
37
|
{
|
|
38
38
|
kind: 'scalar';
|
|
39
39
|
path: string;
|
|
40
|
+
soloLectura?: boolean;
|
|
40
41
|
}
|
|
41
42
|
/** `extraCredentialIds: ObjectId[]` */
|
|
42
43
|
| {
|
|
43
44
|
kind: 'array';
|
|
44
45
|
path: string;
|
|
46
|
+
soloLectura?: boolean;
|
|
45
47
|
}
|
|
46
48
|
/** `credentialIds: { plataforma: id | id[] }` */
|
|
47
49
|
| {
|
|
48
50
|
kind: 'map';
|
|
49
51
|
path: string;
|
|
52
|
+
soloLectura?: boolean;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* `tools: [{ credentialId, ... }]` — un array de OBJETOS, cada uno con su
|
|
56
|
+
* propia referencia. No es `array`: ahí los elementos SON el id.
|
|
57
|
+
*
|
|
58
|
+
* ⚠️ `campo` no es opcional a propósito. Una forma que no dice dónde mirar
|
|
59
|
+
* dentro del objeto no puede mirar en ningún sitio, y el fallo sería
|
|
60
|
+
* silencioso: `formasQueReferencian` devolvería vacío y el documento pasaría
|
|
61
|
+
* por «no usa esta credencial».
|
|
62
|
+
*/
|
|
63
|
+
| {
|
|
64
|
+
kind: 'arrayDeObjetos';
|
|
65
|
+
path: string;
|
|
66
|
+
campo: string;
|
|
67
|
+
soloLectura?: boolean;
|
|
50
68
|
};
|
|
51
69
|
/**
|
|
52
|
-
* Las
|
|
70
|
+
* Las nueve formas que existen hoy.
|
|
53
71
|
*
|
|
54
72
|
* Se barren en TODAS las colecciones de nodos, no sólo en las que declaran el
|
|
55
73
|
* campo: una consulta por un campo que la colección no tiene simplemente no
|
|
56
74
|
* devuelve nada, y así añadir el campo a otro nodo no obliga a tocar esta lista.
|
|
75
|
+
*
|
|
76
|
+
* ## Las cuatro que faltaban, y lo que costaban
|
|
77
|
+
*
|
|
78
|
+
* Eran cinco, y se quedaban cortas exactamente igual que la versión anterior a
|
|
79
|
+
* ésta — aquélla miraba dos campos escritos a mano en la consulta, y por eso
|
|
80
|
+
* nació este fichero. Las que faltaban:
|
|
81
|
+
*
|
|
82
|
+
* `tools[].credentialId` las credenciales de las TOOLS del AI Node:
|
|
83
|
+
* toda tool MCP y todo puente de audio
|
|
84
|
+
* `audioTranscribeCredentialId` el puente de transcripción del AI Node
|
|
85
|
+
* `embedding.credentialId` la clave que paga cada `embed` del vector store
|
|
86
|
+
* `backend.config.credentialId` la conexión del backend del vector store
|
|
87
|
+
* `results[].credentialId` a qué cuenta se publicó cada fila de un post
|
|
88
|
+
*
|
|
89
|
+
* Consecuencia medible y VIVA: `findConsumers` usa esta misma lista, así que
|
|
90
|
+
* borrar una credencial `mcp_server` usada sólo por una tool encontraba CERO
|
|
91
|
+
* consumidores — ni 409, ni desatachado, ni marcador. La credencial
|
|
92
|
+
* desaparecía y el nodo se quedaba con un id que ya no resuelve. Es el mismo
|
|
93
|
+
* fallo por el que se perdió la de Mastodon el 12-ago, con otro campo.
|
|
94
|
+
*
|
|
95
|
+
* ## ⚠️ `soloLectura`: referenciar no es lo mismo que desatachar
|
|
96
|
+
*
|
|
97
|
+
* `results[]` de un post es un REGISTRO HISTÓRICO: dice a qué cuenta se
|
|
98
|
+
* publicó cada fila. Tiene que contar para encontrar consumidores —que salte
|
|
99
|
+
* el 409— pero reescribirlo al desatachar sería falsear lo que pasó: una
|
|
100
|
+
* publicación que SÍ ocurrió quedaría sin decir con qué cuenta.
|
|
101
|
+
*
|
|
102
|
+
* Sin esta marca, ampliar la lista habría arreglado el borrado silencioso y
|
|
103
|
+
* roto el historial en el mismo movimiento.
|
|
57
104
|
*/
|
|
58
105
|
export declare const CREDENTIAL_REF_SHAPES: readonly CredRefShape[];
|
|
59
106
|
/** Lee un camino con puntos. Devuelve `undefined` si algo del medio falta. */
|
|
@@ -65,7 +112,7 @@ export declare function leerCamino(doc: unknown, path: string): unknown;
|
|
|
65
112
|
* formas conviven en datos reales (`credentialIds` está declarado como
|
|
66
113
|
* `Record<string, string[] | string>` justamente por eso).
|
|
67
114
|
*/
|
|
68
|
-
export declare function valorReferencia(valor: unknown, id: string,
|
|
115
|
+
export declare function valorReferencia(valor: unknown, id: string, forma: CredRefShape): boolean;
|
|
69
116
|
/** Las formas por las que ESTE documento referencia la credencial. */
|
|
70
117
|
export declare function formasQueReferencian(doc: Record<string, unknown>, id: string): CredRefShape[];
|
|
71
118
|
/**
|
|
@@ -78,7 +125,7 @@ export declare function formasQueReferencian(doc: Record<string, unknown>, id: s
|
|
|
78
125
|
* con `[]` haría que el trigger siguiera anunciando que vigila Mastodon sin
|
|
79
126
|
* ninguna cuenta con la que hacerlo.
|
|
80
127
|
*/
|
|
81
|
-
export declare function valorTrasDesatacar(valor: unknown, id: string,
|
|
128
|
+
export declare function valorTrasDesatacar(valor: unknown, id: string, forma: CredRefShape): unknown;
|
|
82
129
|
/**
|
|
83
130
|
* Los dos campos que `credentials.service.remove` escribe en cada nodo que se
|
|
84
131
|
* queda huérfano, para que la pantalla pueda decir QUÉ credencial era y
|
|
@@ -88,7 +135,7 @@ export declare const CAMPOS_MARCADOR_DETACH: readonly ["credentialDetachedAt", "
|
|
|
88
135
|
/** El `$unset` que los quita. */
|
|
89
136
|
export declare const UNSET_MARCADOR_DETACH: Record<string, ''>;
|
|
90
137
|
/** ¿Este valor trae al menos una credencial puesta? */
|
|
91
|
-
export declare function valorTieneCredencial(valor: unknown,
|
|
138
|
+
export declare function valorTieneCredencial(valor: unknown, forma: CredRefShape): boolean;
|
|
92
139
|
/**
|
|
93
140
|
* ¿El nodo tiene AHORA alguna credencial, por cualquiera de las cinco formas?
|
|
94
141
|
*
|
|
@@ -43,11 +43,40 @@ exports.tieneAlgunaCredencial = tieneAlgunaCredencial;
|
|
|
43
43
|
exports.marcadorEsFosil = marcadorEsFosil;
|
|
44
44
|
exports.dtoTocaCredencial = dtoTocaCredencial;
|
|
45
45
|
/**
|
|
46
|
-
* Las
|
|
46
|
+
* Las nueve formas que existen hoy.
|
|
47
47
|
*
|
|
48
48
|
* Se barren en TODAS las colecciones de nodos, no sólo en las que declaran el
|
|
49
49
|
* campo: una consulta por un campo que la colección no tiene simplemente no
|
|
50
50
|
* devuelve nada, y así añadir el campo a otro nodo no obliga a tocar esta lista.
|
|
51
|
+
*
|
|
52
|
+
* ## Las cuatro que faltaban, y lo que costaban
|
|
53
|
+
*
|
|
54
|
+
* Eran cinco, y se quedaban cortas exactamente igual que la versión anterior a
|
|
55
|
+
* ésta — aquélla miraba dos campos escritos a mano en la consulta, y por eso
|
|
56
|
+
* nació este fichero. Las que faltaban:
|
|
57
|
+
*
|
|
58
|
+
* `tools[].credentialId` las credenciales de las TOOLS del AI Node:
|
|
59
|
+
* toda tool MCP y todo puente de audio
|
|
60
|
+
* `audioTranscribeCredentialId` el puente de transcripción del AI Node
|
|
61
|
+
* `embedding.credentialId` la clave que paga cada `embed` del vector store
|
|
62
|
+
* `backend.config.credentialId` la conexión del backend del vector store
|
|
63
|
+
* `results[].credentialId` a qué cuenta se publicó cada fila de un post
|
|
64
|
+
*
|
|
65
|
+
* Consecuencia medible y VIVA: `findConsumers` usa esta misma lista, así que
|
|
66
|
+
* borrar una credencial `mcp_server` usada sólo por una tool encontraba CERO
|
|
67
|
+
* consumidores — ni 409, ni desatachado, ni marcador. La credencial
|
|
68
|
+
* desaparecía y el nodo se quedaba con un id que ya no resuelve. Es el mismo
|
|
69
|
+
* fallo por el que se perdió la de Mastodon el 12-ago, con otro campo.
|
|
70
|
+
*
|
|
71
|
+
* ## ⚠️ `soloLectura`: referenciar no es lo mismo que desatachar
|
|
72
|
+
*
|
|
73
|
+
* `results[]` de un post es un REGISTRO HISTÓRICO: dice a qué cuenta se
|
|
74
|
+
* publicó cada fila. Tiene que contar para encontrar consumidores —que salte
|
|
75
|
+
* el 409— pero reescribirlo al desatachar sería falsear lo que pasó: una
|
|
76
|
+
* publicación que SÍ ocurrió quedaría sin decir con qué cuenta.
|
|
77
|
+
*
|
|
78
|
+
* Sin esta marca, ampliar la lista habría arreglado el borrado silencioso y
|
|
79
|
+
* roto el historial en el mismo movimiento.
|
|
51
80
|
*/
|
|
52
81
|
exports.CREDENTIAL_REF_SHAPES = [
|
|
53
82
|
{ kind: 'scalar', path: 'credentialId' },
|
|
@@ -55,6 +84,16 @@ exports.CREDENTIAL_REF_SHAPES = [
|
|
|
55
84
|
{ kind: 'array', path: 'extraCredentialIds' },
|
|
56
85
|
{ kind: 'map', path: 'credentialIds' },
|
|
57
86
|
{ kind: 'map', path: 'serviceConfig.social_media.credentialIds' },
|
|
87
|
+
{ kind: 'arrayDeObjetos', path: 'tools', campo: 'credentialId' },
|
|
88
|
+
{ kind: 'scalar', path: 'audioTranscribeCredentialId' },
|
|
89
|
+
{ kind: 'scalar', path: 'embedding.credentialId' },
|
|
90
|
+
{ kind: 'scalar', path: 'backend.config.credentialId' },
|
|
91
|
+
{
|
|
92
|
+
kind: 'arrayDeObjetos',
|
|
93
|
+
path: 'results',
|
|
94
|
+
campo: 'credentialId',
|
|
95
|
+
soloLectura: true,
|
|
96
|
+
},
|
|
58
97
|
];
|
|
59
98
|
const mismoId = (v, id) => v !== null && v !== undefined && String(v) === id;
|
|
60
99
|
/** Lee un camino con puntos. Devuelve `undefined` si algo del medio falta. */
|
|
@@ -74,13 +113,19 @@ function leerCamino(doc, path) {
|
|
|
74
113
|
* formas conviven en datos reales (`credentialIds` está declarado como
|
|
75
114
|
* `Record<string, string[] | string>` justamente por eso).
|
|
76
115
|
*/
|
|
77
|
-
function valorReferencia(valor, id,
|
|
116
|
+
function valorReferencia(valor, id, forma) {
|
|
78
117
|
if (valor === null || valor === undefined)
|
|
79
118
|
return false;
|
|
80
|
-
if (kind === 'scalar')
|
|
119
|
+
if (forma.kind === 'scalar')
|
|
81
120
|
return mismoId(valor, id);
|
|
82
|
-
if (kind === 'array')
|
|
121
|
+
if (forma.kind === 'array')
|
|
83
122
|
return Array.isArray(valor) && valor.some((v) => mismoId(v, id));
|
|
123
|
+
if (forma.kind === 'arrayDeObjetos') {
|
|
124
|
+
return (Array.isArray(valor) &&
|
|
125
|
+
valor.some((o) => o !== null &&
|
|
126
|
+
typeof o === 'object' &&
|
|
127
|
+
mismoId(o[forma.campo], id)));
|
|
128
|
+
}
|
|
84
129
|
// map
|
|
85
130
|
if (typeof valor !== 'object' || Array.isArray(valor))
|
|
86
131
|
return false;
|
|
@@ -88,7 +133,7 @@ function valorReferencia(valor, id, kind) {
|
|
|
88
133
|
}
|
|
89
134
|
/** Las formas por las que ESTE documento referencia la credencial. */
|
|
90
135
|
function formasQueReferencian(doc, id) {
|
|
91
|
-
return exports.CREDENTIAL_REF_SHAPES.filter((forma) => valorReferencia(leerCamino(doc, forma.path), id, forma
|
|
136
|
+
return exports.CREDENTIAL_REF_SHAPES.filter((forma) => valorReferencia(leerCamino(doc, forma.path), id, forma));
|
|
92
137
|
}
|
|
93
138
|
/**
|
|
94
139
|
* El valor que hay que dejar en el campo al quitar la credencial.
|
|
@@ -100,7 +145,22 @@ function formasQueReferencian(doc, id) {
|
|
|
100
145
|
* con `[]` haría que el trigger siguiera anunciando que vigila Mastodon sin
|
|
101
146
|
* ninguna cuenta con la que hacerlo.
|
|
102
147
|
*/
|
|
103
|
-
function valorTrasDesatacar(valor, id,
|
|
148
|
+
function valorTrasDesatacar(valor, id, forma) {
|
|
149
|
+
const kind = forma.kind;
|
|
150
|
+
if (kind === 'arrayDeObjetos') {
|
|
151
|
+
/* ⚠️ Se le quita el id a la fila, pero la fila SE QUEDA. Borrar la tool
|
|
152
|
+
entera sería borrarle al usuario algo que él configuró porque nosotros
|
|
153
|
+
borramos otra cosa; sin credencial dará un error con nombre, que es lo
|
|
154
|
+
que toca. */
|
|
155
|
+
if (!Array.isArray(valor))
|
|
156
|
+
return null;
|
|
157
|
+
const resto = valor.map((o) => o !== null &&
|
|
158
|
+
typeof o === 'object' &&
|
|
159
|
+
mismoId(o[forma.campo], id)
|
|
160
|
+
? { ...o, [forma.campo]: null }
|
|
161
|
+
: o);
|
|
162
|
+
return resto.length > 0 ? resto : null;
|
|
163
|
+
}
|
|
104
164
|
if (kind === 'scalar')
|
|
105
165
|
return null;
|
|
106
166
|
if (kind === 'array') {
|
|
@@ -141,9 +201,19 @@ exports.UNSET_MARCADOR_DETACH = {
|
|
|
141
201
|
credentialDetachedName: '',
|
|
142
202
|
};
|
|
143
203
|
/** ¿Este valor trae al menos una credencial puesta? */
|
|
144
|
-
function valorTieneCredencial(valor,
|
|
204
|
+
function valorTieneCredencial(valor, forma) {
|
|
145
205
|
if (valor === null || valor === undefined || valor === '')
|
|
146
206
|
return false;
|
|
207
|
+
const kind = forma.kind;
|
|
208
|
+
if (kind === 'arrayDeObjetos') {
|
|
209
|
+
return (Array.isArray(valor) &&
|
|
210
|
+
valor.some((o) => {
|
|
211
|
+
if (o === null || typeof o !== 'object')
|
|
212
|
+
return false;
|
|
213
|
+
const v = o[forma.campo];
|
|
214
|
+
return v !== null && v !== undefined && v !== '';
|
|
215
|
+
}));
|
|
216
|
+
}
|
|
147
217
|
if (kind === 'scalar')
|
|
148
218
|
return true;
|
|
149
219
|
if (kind === 'array')
|
|
@@ -166,7 +236,7 @@ function valorTieneCredencial(valor, kind) {
|
|
|
166
236
|
function tieneAlgunaCredencial(doc) {
|
|
167
237
|
if (!doc || typeof doc !== 'object')
|
|
168
238
|
return false;
|
|
169
|
-
return exports.CREDENTIAL_REF_SHAPES.some((forma) => valorTieneCredencial(leerCamino(doc, forma.path), forma
|
|
239
|
+
return exports.CREDENTIAL_REF_SHAPES.some((forma) => valorTieneCredencial(leerCamino(doc, forma.path), forma));
|
|
170
240
|
}
|
|
171
241
|
/**
|
|
172
242
|
* ¿Hay que quitarle el marcador a este documento?
|
|
@@ -5,14 +5,7 @@
|
|
|
5
5
|
* _meta.iterable and automatically iterates, aggregating results.
|
|
6
6
|
* Nodes never see _meta — they receive clean individual payloads.
|
|
7
7
|
*/
|
|
8
|
-
type
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
latencyMs: number;
|
|
12
|
-
}>;
|
|
13
|
-
export declare function executeWithIteration(payload: Record<string, unknown>, executeSingle: SingleExecutor): Promise<{
|
|
14
|
-
statusCode: number;
|
|
15
|
-
responseBody: string;
|
|
16
|
-
latencyMs: number;
|
|
17
|
-
}>;
|
|
8
|
+
import type { NodeResult } from './retry-transient';
|
|
9
|
+
type SingleExecutor = (payload: Record<string, unknown>) => Promise<NodeResult>;
|
|
10
|
+
export declare function executeWithIteration(payload: Record<string, unknown>, executeSingle: SingleExecutor): Promise<NodeResult>;
|
|
18
11
|
export {};
|
|
@@ -14,7 +14,46 @@ async function executeWithIteration(payload, executeSingle) {
|
|
|
14
14
|
const items = payload[meta.iterateField] ?? [];
|
|
15
15
|
const results = [];
|
|
16
16
|
let totalLatency = 0;
|
|
17
|
-
|
|
17
|
+
/* El estado que se reporta hacia arriba.
|
|
18
|
+
*
|
|
19
|
+
* ⚠️ Antes se guardaba el del ÚLTIMO item, a secas. Cuatro llamadas donde
|
|
20
|
+
* la 2ª daba 500 y la 4ª daba 200 se reportaban como ÉXITO —el fallo
|
|
21
|
+
* desaparecía sin dejar rastro— y al revés, tres éxitos y un fallo al final
|
|
22
|
+
* teñían de rojo la tanda entera.
|
|
23
|
+
*
|
|
24
|
+
* Ahora manda el PRIMER fallo; si no hubo ninguno se conserva el último
|
|
25
|
+
* estado, que es exactamente lo que ya se devolvía cuando todo iba bien.
|
|
26
|
+
*
|
|
27
|
+
* ## Lo que esto NO pisa, y lo que sí
|
|
28
|
+
*
|
|
29
|
+
* NO pisa el «Never error on non-2xx» del nodo HTTP, y el motivo es el
|
|
30
|
+
* orden de anidamiento, no una casualidad: la máscara vive en
|
|
31
|
+
* `executeOnce`, la función MÁS interna —dentro del retry, que a su vez
|
|
32
|
+
* está dentro de este bucle—, y axios va con `validateStatus: () => true`,
|
|
33
|
+
* así que TODO código de estado pasa por ella antes de que nadie más lo
|
|
34
|
+
* vea. Aquí llega un 200 y `primerFallo` no se marca.
|
|
35
|
+
*
|
|
36
|
+
* ⚠️ Lo que sí cambia, y conviene saberlo: el ajuste sólo enmascara
|
|
37
|
+
* `response.status >= 400`. Un timeout, un DNS caído o un fallo de
|
|
38
|
+
* configuración salen por otro camino (500 del `catch`, 520 del guion de
|
|
39
|
+
* pre-petición o del firmado) sin pasar por la máscara. Antes esos se
|
|
40
|
+
* tragaban si no eran el último item; ahora detienen la tanda aunque el
|
|
41
|
+
* ajuste esté encendido. Es el comportamiento correcto —un timeout no es
|
|
42
|
+
* «un non-2xx que quiero ignorar»— pero es un cambio observable. */
|
|
43
|
+
let ultimoStatus = 200;
|
|
44
|
+
let primerFallo = null;
|
|
45
|
+
/* Plano de control. No viaja en el payload, pero decide qué hace el Loop
|
|
46
|
+
* cuando una iteración falla.
|
|
47
|
+
*
|
|
48
|
+
* ⚠️ La agregación construía un objeto literal nuevo y estos dos campos se
|
|
49
|
+
* caían por el camino. Daba casi igual mientras el fallo de enmedio se
|
|
50
|
+
* perdía, porque esa rama no se alcanzaba; en cuanto manda el primer
|
|
51
|
+
* fallo, `node-lifecycle` lee `_retriedTransient` para degradar la política
|
|
52
|
+
* `retry` a `skip`, y sin él RE-DESPACHA la iteración entera: se reenvían
|
|
53
|
+
* también los items que ya habían salido bien. Un 429 que ya quemó sus
|
|
54
|
+
* tres intentos le llegaba al Loop como «esto ni se ha reintentado». */
|
|
55
|
+
let algunoReintento = false;
|
|
56
|
+
let esperaPedida;
|
|
18
57
|
for (const item of items) {
|
|
19
58
|
const clean = typeof item === 'object' && item !== null
|
|
20
59
|
? { ...item }
|
|
@@ -22,7 +61,17 @@ async function executeWithIteration(payload, executeSingle) {
|
|
|
22
61
|
delete clean._meta;
|
|
23
62
|
const res = await executeSingle(clean);
|
|
24
63
|
totalLatency += res.latencyMs;
|
|
25
|
-
|
|
64
|
+
ultimoStatus = res.statusCode;
|
|
65
|
+
if (primerFallo === null && res.statusCode >= 400) {
|
|
66
|
+
primerFallo = res.statusCode;
|
|
67
|
+
}
|
|
68
|
+
if (res._retriedTransient)
|
|
69
|
+
algunoReintento = true;
|
|
70
|
+
if (typeof res.retryAfterMs === 'number') {
|
|
71
|
+
/* El mayor de los que se pidieron: esperar MENOS de lo que el servidor
|
|
72
|
+
dijo sólo quema un intento. */
|
|
73
|
+
esperaPedida = Math.max(esperaPedida ?? 0, res.retryAfterMs);
|
|
74
|
+
}
|
|
26
75
|
try {
|
|
27
76
|
const parsed = JSON.parse(res.responseBody);
|
|
28
77
|
// If executor returns an array, spread its items instead of nesting [[...]]
|
|
@@ -38,15 +87,23 @@ async function executeWithIteration(payload, executeSingle) {
|
|
|
38
87
|
results.push(res.responseBody);
|
|
39
88
|
}
|
|
40
89
|
}
|
|
90
|
+
const estadoDeLaTanda = primerFallo ?? ultimoStatus;
|
|
91
|
+
/* Se omiten cuando no hay nada que decir, para no ensuciar el resultado con
|
|
92
|
+
`_retriedTransient: false` donde antes no había clave ninguna. */
|
|
93
|
+
const planoDeControl = {
|
|
94
|
+
...(algunoReintento ? { _retriedTransient: true } : {}),
|
|
95
|
+
...(esperaPedida !== undefined ? { retryAfterMs: esperaPedida } : {}),
|
|
96
|
+
};
|
|
41
97
|
// Single result from single iteration → unwrap as plain object
|
|
42
98
|
if (results.length === 1 &&
|
|
43
99
|
typeof results[0] === 'object' &&
|
|
44
100
|
results[0] !== null) {
|
|
45
101
|
const single = { _meta: { iterable: false, count: 1 }, ...results[0] };
|
|
46
102
|
return {
|
|
47
|
-
statusCode:
|
|
103
|
+
statusCode: estadoDeLaTanda,
|
|
48
104
|
responseBody: JSON.stringify(single),
|
|
49
105
|
latencyMs: totalLatency,
|
|
106
|
+
...planoDeControl,
|
|
50
107
|
};
|
|
51
108
|
}
|
|
52
109
|
const aggregated = {
|
|
@@ -54,9 +111,10 @@ async function executeWithIteration(payload, executeSingle) {
|
|
|
54
111
|
results,
|
|
55
112
|
};
|
|
56
113
|
return {
|
|
57
|
-
statusCode:
|
|
114
|
+
statusCode: estadoDeLaTanda,
|
|
58
115
|
responseBody: JSON.stringify(aggregated),
|
|
59
116
|
latencyMs: totalLatency,
|
|
117
|
+
...planoDeControl,
|
|
60
118
|
};
|
|
61
119
|
}
|
|
62
120
|
// Single item — strip _meta and execute
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hostwebhook/node-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "El SDK de un nodo de HostWebhook: ciclo de vida, ejecución con iteración, reintentos, filtros y validación de esquema — lo que comparten la api y el futuro servicio de nodos",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|