@hostwebhook/node-types 1.71.1 → 1.72.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.
@@ -1,6 +1,6 @@
1
1
  /**
2
- * El nodo de bucket: guardar, firmar y borrar objetos en un almacenamiento
3
- * compatible con S3.
2
+ * El nodo de bucket: mirar, leer, guardar, firmar y borrar objetos en un
3
+ * almacenamiento compatible con S3.
4
4
  *
5
5
  * ## Por qué se llama `bucket` y no `r2`
6
6
  *
@@ -19,24 +19,51 @@
19
19
  * un enlace que se manda por correo. Sin esto no había forma de entregarle a un
20
20
  * comprador una descarga temporal desde un flujo.
21
21
  *
22
- * ## Por qué sólo cuatro operaciones
22
+ * ## Por qué siete, y qué sigue fuera
23
23
  *
24
- * Listar y subida multiparte son las dos que piden el SDK de AWS de verdad
25
- * —paginación y reanudación— y el ejecutor hoy no tiene ni un `@aws-sdk/*`.
26
- * Entrar sin esa dependencia y traerla cuando aparezca la operación que la
27
- * justifique es más barato que al revés. Y un nodo con veinte operaciones es un
28
- * nodo que nadie entiende.
24
+ * Las cuatro primeras —`presignDownload`, `putObject`, `headObject`,
25
+ * `deleteObject`— actúan todas sobre un objeto que ya sabes nombrar. Faltaba lo
26
+ * contrario: **no había forma de mirar dentro del bucket**, ni de traerse el
27
+ * contenido de un objeto al flujo, ni de dejar que un tercero SUBA sin darle la
28
+ * credencial. Eso son las tres nuevas:
29
+ *
30
+ * - `listObjects` — qué ficheros y qué CARPETAS hay bajo un prefijo. S3 no
31
+ * tiene carpetas de verdad; con `delimiter` te las sintetiza como prefijos
32
+ * comunes, que es exactamente lo que una persona quiere ver.
33
+ * - `getObject` — el contenido, no un enlace al contenido. `presignDownload`
34
+ * da una URL para que la abra OTRO; cuando el paso de abajo necesita los
35
+ * bytes, una URL no sirve.
36
+ * - `presignUpload` — el simétrico de `presignDownload`.
37
+ *
38
+ * Aquí se decía que listar «pedía el SDK de AWS de verdad». No lo pide:
39
+ * `ListObjectsV2` contesta XML plano y un raspador corto lo lee. Lo que sí
40
+ * sigue pidiendo el SDK es la **subida multiparte** —reanudación, partes,
41
+ * reintentos por parte— y por eso ésa no está. Traer `@aws-sdk/*` el día que
42
+ * aparezca la operación que lo justifique sigue siendo más barato que al revés.
43
+ * Y un nodo con veinte operaciones es un nodo que nadie entiende.
29
44
  */
30
- export declare const BUCKET_OPERATIONS: readonly ["presignDownload", "putObject", "headObject", "deleteObject"];
45
+ export declare const BUCKET_OPERATIONS: readonly ["presignDownload", "putObject", "headObject", "deleteObject", "listObjects", "getObject", "presignUpload"];
31
46
  export type BucketOperation = (typeof BUCKET_OPERATIONS)[number];
32
47
  export declare function isBucketOperation(value: unknown): value is BucketOperation;
33
48
  /**
34
- * Ninguna es iterable.
49
+ * Sólo `listObjects`.
35
50
  *
36
51
  * Una operación iterable es la que devuelve una COLECCIÓN sobre la que el nodo
37
- * de abajo repite. Las cuatro de aquí actúan sobre UN objeto, así que el que
38
- * viene detrás recibe un resultado, no una lista. El día que entre `listObjects`
39
- * ésa sí entra en esta lista.
52
+ * de abajo repite. Las otras seis actúan sobre UN objeto —o devuelven UN
53
+ * enlace—, así que el que viene detrás recibe un resultado, no una lista.
54
+ *
55
+ * ⚠️ Dos cosas que hay que saber antes de implementarla, porque descubrirlas
56
+ * por las malas cuesta una tarde:
57
+ *
58
+ * 1. **Se itera `objects`, y sólo `objects`.** `folders` viaja en el
59
+ * contenedor junto a `truncated`, `nextToken`, `prefix` y `count`. Una
60
+ * operación tiene un campo iterable, y aquí es el de los ficheros; si las
61
+ * carpetas se iteraran también, el aviso de «hay más» se perdería justo en
62
+ * la operación que existe para darlo.
63
+ * 2. **Un prefijo sin nada da `count: 0`, y el nodo de abajo corre CERO
64
+ * veces**, sin error. Es la semántica correcta de una lista vacía, y es
65
+ * también la trampa clásica: parece que el flujo «no hizo nada» cuando en
66
+ * realidad hizo lo que debía.
40
67
  */
41
68
  export declare const BUCKET_ITERABLE_OPERATIONS: readonly BucketOperation[];
42
69
  export type BucketParamType =
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  /**
3
- * El nodo de bucket: guardar, firmar y borrar objetos en un almacenamiento
4
- * compatible con S3.
3
+ * El nodo de bucket: mirar, leer, guardar, firmar y borrar objetos en un
4
+ * almacenamiento compatible con S3.
5
5
  *
6
6
  * ## Por qué se llama `bucket` y no `r2`
7
7
  *
@@ -20,13 +20,28 @@
20
20
  * un enlace que se manda por correo. Sin esto no había forma de entregarle a un
21
21
  * comprador una descarga temporal desde un flujo.
22
22
  *
23
- * ## Por qué sólo cuatro operaciones
23
+ * ## Por qué siete, y qué sigue fuera
24
24
  *
25
- * Listar y subida multiparte son las dos que piden el SDK de AWS de verdad
26
- * —paginación y reanudación— y el ejecutor hoy no tiene ni un `@aws-sdk/*`.
27
- * Entrar sin esa dependencia y traerla cuando aparezca la operación que la
28
- * justifique es más barato que al revés. Y un nodo con veinte operaciones es un
29
- * nodo que nadie entiende.
25
+ * Las cuatro primeras —`presignDownload`, `putObject`, `headObject`,
26
+ * `deleteObject`— actúan todas sobre un objeto que ya sabes nombrar. Faltaba lo
27
+ * contrario: **no había forma de mirar dentro del bucket**, ni de traerse el
28
+ * contenido de un objeto al flujo, ni de dejar que un tercero SUBA sin darle la
29
+ * credencial. Eso son las tres nuevas:
30
+ *
31
+ * - `listObjects` — qué ficheros y qué CARPETAS hay bajo un prefijo. S3 no
32
+ * tiene carpetas de verdad; con `delimiter` te las sintetiza como prefijos
33
+ * comunes, que es exactamente lo que una persona quiere ver.
34
+ * - `getObject` — el contenido, no un enlace al contenido. `presignDownload`
35
+ * da una URL para que la abra OTRO; cuando el paso de abajo necesita los
36
+ * bytes, una URL no sirve.
37
+ * - `presignUpload` — el simétrico de `presignDownload`.
38
+ *
39
+ * Aquí se decía que listar «pedía el SDK de AWS de verdad». No lo pide:
40
+ * `ListObjectsV2` contesta XML plano y un raspador corto lo lee. Lo que sí
41
+ * sigue pidiendo el SDK es la **subida multiparte** —reanudación, partes,
42
+ * reintentos por parte— y por eso ésa no está. Traer `@aws-sdk/*` el día que
43
+ * aparezca la operación que lo justifique sigue siendo más barato que al revés.
44
+ * Y un nodo con veinte operaciones es un nodo que nadie entiende.
30
45
  */
31
46
  Object.defineProperty(exports, "__esModule", { value: true });
32
47
  exports.BUCKET_OPERATION_SPECS = exports.BUCKET_ITERABLE_OPERATIONS = exports.BUCKET_OPERATIONS = void 0;
@@ -36,20 +51,42 @@ exports.BUCKET_OPERATIONS = [
36
51
  "putObject",
37
52
  "headObject",
38
53
  "deleteObject",
54
+ /* Las tres nuevas van AL FINAL y no intercaladas por tema: el orden de este
55
+ array es el orden del desplegable, y reordenar las cuatro de arriba le
56
+ mueve las filas de sitio a quien ya tiene el nodo en pantalla por un
57
+ cambio que no le aporta nada. El agrupado por tema vive en el dashboard,
58
+ que es donde se puede hacer sin tocar el contrato. */
59
+ "listObjects",
60
+ "getObject",
61
+ "presignUpload",
39
62
  ];
40
63
  function isBucketOperation(value) {
41
64
  return (typeof value === "string" &&
42
65
  exports.BUCKET_OPERATIONS.includes(value));
43
66
  }
44
67
  /**
45
- * Ninguna es iterable.
68
+ * Sólo `listObjects`.
46
69
  *
47
70
  * Una operación iterable es la que devuelve una COLECCIÓN sobre la que el nodo
48
- * de abajo repite. Las cuatro de aquí actúan sobre UN objeto, así que el que
49
- * viene detrás recibe un resultado, no una lista. El día que entre `listObjects`
50
- * ésa sí entra en esta lista.
71
+ * de abajo repite. Las otras seis actúan sobre UN objeto —o devuelven UN
72
+ * enlace—, así que el que viene detrás recibe un resultado, no una lista.
73
+ *
74
+ * ⚠️ Dos cosas que hay que saber antes de implementarla, porque descubrirlas
75
+ * por las malas cuesta una tarde:
76
+ *
77
+ * 1. **Se itera `objects`, y sólo `objects`.** `folders` viaja en el
78
+ * contenedor junto a `truncated`, `nextToken`, `prefix` y `count`. Una
79
+ * operación tiene un campo iterable, y aquí es el de los ficheros; si las
80
+ * carpetas se iteraran también, el aviso de «hay más» se perdería justo en
81
+ * la operación que existe para darlo.
82
+ * 2. **Un prefijo sin nada da `count: 0`, y el nodo de abajo corre CERO
83
+ * veces**, sin error. Es la semántica correcta de una lista vacía, y es
84
+ * también la trampa clásica: parece que el flujo «no hizo nada» cuando en
85
+ * realidad hizo lo que debía.
51
86
  */
52
- exports.BUCKET_ITERABLE_OPERATIONS = [];
87
+ exports.BUCKET_ITERABLE_OPERATIONS = [
88
+ "listObjects",
89
+ ];
53
90
  /* Ayudantes — repetir el mismo objeto cuatro veces es cómo se desincronizan las
54
91
  descripciones. */
55
92
  const objectKey = (placeholder) => ({
@@ -60,6 +97,33 @@ const objectKey = (placeholder) => ({
60
97
  placeholder,
61
98
  description: "The path inside the bucket. No leading slash. Supports {{payload.x}}.",
62
99
  });
100
+ /* El TTL de las dos operaciones que firman. El tope de siete días es de SigV4 y
101
+ no se puede pasar: firmar por más tiempo produce una URL que el proveedor
102
+ rechaza, y el error llega tarde y sin explicación. Va en un ayudante porque
103
+ con dos operaciones firmando, dos copias del número es una copia de más. */
104
+ const signatureTtl = () => ({
105
+ name: "expiresIn",
106
+ label: "Link valid for (seconds)",
107
+ type: "number",
108
+ required: true,
109
+ default: 3600,
110
+ min: 60,
111
+ max: 604800,
112
+ description: "From 60 s up to 7 days, the maximum the signature allows. Past that, the link stops working.",
113
+ });
114
+ /* El mismo interruptor en `headObject` y en `getObject`: las dos preguntan por
115
+ un objeto que puede no estar, y las dos lo resuelven con el `status` 404/200
116
+ y no con una excepción. Mismas etiquetas a propósito — son el mismo botón. */
117
+ const failIfMissing = () => ({
118
+ name: "failIfMissing",
119
+ label: "Fail when the object is missing",
120
+ type: "booleanSelect",
121
+ options: [
122
+ { value: "true", label: "Yes — stop the flow" },
123
+ { value: "false", label: "No — carry on with exists: false" },
124
+ ],
125
+ description: 'With "No", the step succeeds and the node below decides. With "Yes", a missing object stops the flow.',
126
+ });
63
127
  exports.BUCKET_OPERATION_SPECS = {
64
128
  presignDownload: {
65
129
  label: "Sign download link",
@@ -67,19 +131,7 @@ exports.BUCKET_OPERATION_SPECS = {
67
131
  apiRoute: "GET (presigned)",
68
132
  params: [
69
133
  objectKey("cuadernillos/{{payload.orderId}}.pdf"),
70
- {
71
- name: "expiresIn",
72
- label: "Link valid for (seconds)",
73
- type: "number",
74
- required: true,
75
- default: 3600,
76
- min: 60,
77
- /* El tope de SigV4 es de siete días y no se puede pasar: firmar por
78
- más tiempo produce una URL que el proveedor rechaza, y el error
79
- llega tarde y sin explicación. */
80
- max: 604800,
81
- description: "From 60 s up to 7 days, the maximum the signature allows. Past that, the link stops working.",
82
- },
134
+ signatureTtl(),
83
135
  {
84
136
  name: "downloadAs",
85
137
  label: "Download as (filename)",
@@ -117,24 +169,110 @@ exports.BUCKET_OPERATION_SPECS = {
117
169
  label: "Check object exists",
118
170
  description: "Checks whether an object exists, and its size, without downloading it.",
119
171
  apiRoute: "HEAD /{bucket}/{key}",
172
+ params: [objectKey("cuadernillos/{{payload.orderId}}.pdf"), failIfMissing()],
173
+ },
174
+ deleteObject: {
175
+ label: "Delete object",
176
+ description: "Deletes an object from the bucket. It cannot be undone.",
177
+ apiRoute: "DELETE /{bucket}/{key}",
178
+ params: [objectKey("temporales/{{payload.id}}.tmp")],
179
+ },
180
+ listObjects: {
181
+ label: "List objects",
182
+ description: "Lists the files and folders under a prefix. The node below runs once per file; folders travel alongside the list, they are not iterated. An empty prefix means zero runs, not an error.",
183
+ apiRoute: "GET /{bucket}?list-type=2",
120
184
  params: [
121
- objectKey("cuadernillos/{{payload.orderId}}.pdf"),
185
+ /* NO usa `objectKey()`, y no es un descuido: ese ayudante marca
186
+ `required: true` y la validación de clave rechaza la cadena vacía. Un
187
+ prefijo puede ser vacío —el bucket entero— y puede acabar en barra,
188
+ que es justo lo que lo hace un prefijo y no una clave. */
122
189
  {
123
- name: "failIfMissing",
124
- label: "Fail when the object is missing",
190
+ name: "prefix",
191
+ label: "Prefix",
192
+ type: "template",
193
+ placeholder: "cuadernillos/2026/",
194
+ description: "Only keys starting with this. No leading slash. Leave it empty to list the whole bucket. Supports {{payload.x}}.",
195
+ },
196
+ {
197
+ name: "groupIntoFolders",
198
+ label: "Group into folders",
125
199
  type: "booleanSelect",
126
200
  options: [
127
- { value: "true", label: "Yes — stop the flow" },
128
- { value: "false", label: "No — carry on with exists: false" },
201
+ { value: "true", label: "Yes — one level, with folders" },
202
+ { value: "false", label: "No — every key under the prefix" },
129
203
  ],
130
- description: 'With "No", the step succeeds and the node below decides. With "Yes", a missing object stops the flow.',
204
+ description: 'With "Yes" you get the files at this level plus the folder names below it. With "No" you get every key under the prefix, however deep. Defaults to Yes.',
205
+ },
206
+ {
207
+ name: "maxKeys",
208
+ label: "Maximum objects",
209
+ type: "number",
210
+ default: 1000,
211
+ min: 1,
212
+ max: 5000,
213
+ /* El proveedor devuelve 1.000 por llamada como mucho, así que de 1.000
214
+ en adelante el ejecutor pagina por dentro. El techo de 5.000 no es
215
+ timidez: el contenedor viaja como JSON al nodo de abajo y se guarda
216
+ en el historial, y ahí ya hay un recorte a 256 KB por payload. */
217
+ description: "Up to 5000, paginated for you. When there are more, the step still succeeds: truncated says so and nextToken lets the next run carry on from there.",
218
+ },
219
+ {
220
+ name: "startAfter",
221
+ label: "Continue after",
222
+ type: "template",
223
+ advanced: true,
224
+ placeholder: "{{payload.nextToken}}",
225
+ description: "Resumes a listing: feed it the nextToken of the previous run. Leave it empty to start from the top.",
131
226
  },
132
227
  ],
133
228
  },
134
- deleteObject: {
135
- label: "Delete object",
136
- description: "Deletes an object from the bucket. It cannot be undone.",
137
- apiRoute: "DELETE /{bucket}/{key}",
138
- params: [objectKey("temporales/{{payload.id}}.tmp")],
229
+ /* El tope de lectura NO es un parámetro: es del ejecutor, no una preferencia
230
+ por nodo. Y se dice en la descripción, no se descubre — al superarlo el
231
+ paso FALLA con el tamaño real y un puntero a `presignDownload`; nunca
232
+ devuelve un `content` a medias. Un contenido truncado se parsea, se manda
233
+ por correo y nadie se entera. */
234
+ getObject: {
235
+ label: "Read object",
236
+ description: "Downloads an object and puts its contents in the payload. Up to 5 MB; past that the step fails and points you at Sign download link.",
237
+ apiRoute: "GET /{bucket}/{key}",
238
+ params: [
239
+ objectKey("recibos/{{payload.orderId}}.json"),
240
+ {
241
+ name: "as",
242
+ label: "Read as",
243
+ type: "select",
244
+ required: true,
245
+ default: "text",
246
+ options: [
247
+ { value: "text", label: "Text — UTF-8 into content" },
248
+ { value: "json", label: "JSON — parsed into content" },
249
+ { value: "base64", label: "Base64 — bytes into contentBase64" },
250
+ ],
251
+ /* Lo binario se PIDE, no se adivina: un PDF leído como texto llega
252
+ como UTF-8 roto y el fallo aparece tres nodos más abajo. */
253
+ description: "Binary files need Base64 — read as text they arrive corrupted, and Base64 weighs a third more than the file. With JSON, a body that does not parse fails the step. Over 256 KB the contents still flow on whole, but the run history stores them trimmed.",
254
+ },
255
+ failIfMissing(),
256
+ ],
257
+ },
258
+ presignUpload: {
259
+ label: "Sign upload link",
260
+ description: "Returns a temporary URL for someone else to PUT a file into the bucket, without giving them the credential.",
261
+ apiRoute: "PUT (presigned)",
262
+ params: [
263
+ objectKey("subidas/{{payload.orderId}}.pdf"),
264
+ signatureTtl(),
265
+ {
266
+ name: "contentType",
267
+ label: "Content type",
268
+ type: "template",
269
+ default: "application/octet-stream",
270
+ placeholder: "application/pdf",
271
+ /* NO se firma. Meterlo en `signedHeaders` obliga al que sube a mandar
272
+ esa cabecera exacta o comerse un 403 que no puede depurar, y quien
273
+ recibe el enlace no suele controlar su cliente HTTP. */
274
+ description: "What the object will be stored as. It is not enforced — the bucket stores whatever arrives.",
275
+ },
276
+ ],
139
277
  },
140
278
  };
@@ -1,6 +1,6 @@
1
1
  /**
2
- * El nodo de bucket: guardar, firmar y borrar objetos en un almacenamiento
3
- * compatible con S3.
2
+ * El nodo de bucket: mirar, leer, guardar, firmar y borrar objetos en un
3
+ * almacenamiento compatible con S3.
4
4
  *
5
5
  * ## Por qué se llama `bucket` y no `r2`
6
6
  *
@@ -19,24 +19,51 @@
19
19
  * un enlace que se manda por correo. Sin esto no había forma de entregarle a un
20
20
  * comprador una descarga temporal desde un flujo.
21
21
  *
22
- * ## Por qué sólo cuatro operaciones
22
+ * ## Por qué siete, y qué sigue fuera
23
23
  *
24
- * Listar y subida multiparte son las dos que piden el SDK de AWS de verdad
25
- * —paginación y reanudación— y el ejecutor hoy no tiene ni un `@aws-sdk/*`.
26
- * Entrar sin esa dependencia y traerla cuando aparezca la operación que la
27
- * justifique es más barato que al revés. Y un nodo con veinte operaciones es un
28
- * nodo que nadie entiende.
24
+ * Las cuatro primeras —`presignDownload`, `putObject`, `headObject`,
25
+ * `deleteObject`— actúan todas sobre un objeto que ya sabes nombrar. Faltaba lo
26
+ * contrario: **no había forma de mirar dentro del bucket**, ni de traerse el
27
+ * contenido de un objeto al flujo, ni de dejar que un tercero SUBA sin darle la
28
+ * credencial. Eso son las tres nuevas:
29
+ *
30
+ * - `listObjects` — qué ficheros y qué CARPETAS hay bajo un prefijo. S3 no
31
+ * tiene carpetas de verdad; con `delimiter` te las sintetiza como prefijos
32
+ * comunes, que es exactamente lo que una persona quiere ver.
33
+ * - `getObject` — el contenido, no un enlace al contenido. `presignDownload`
34
+ * da una URL para que la abra OTRO; cuando el paso de abajo necesita los
35
+ * bytes, una URL no sirve.
36
+ * - `presignUpload` — el simétrico de `presignDownload`.
37
+ *
38
+ * Aquí se decía que listar «pedía el SDK de AWS de verdad». No lo pide:
39
+ * `ListObjectsV2` contesta XML plano y un raspador corto lo lee. Lo que sí
40
+ * sigue pidiendo el SDK es la **subida multiparte** —reanudación, partes,
41
+ * reintentos por parte— y por eso ésa no está. Traer `@aws-sdk/*` el día que
42
+ * aparezca la operación que lo justifique sigue siendo más barato que al revés.
43
+ * Y un nodo con veinte operaciones es un nodo que nadie entiende.
29
44
  */
30
- export declare const BUCKET_OPERATIONS: readonly ["presignDownload", "putObject", "headObject", "deleteObject"];
45
+ export declare const BUCKET_OPERATIONS: readonly ["presignDownload", "putObject", "headObject", "deleteObject", "listObjects", "getObject", "presignUpload"];
31
46
  export type BucketOperation = (typeof BUCKET_OPERATIONS)[number];
32
47
  export declare function isBucketOperation(value: unknown): value is BucketOperation;
33
48
  /**
34
- * Ninguna es iterable.
49
+ * Sólo `listObjects`.
35
50
  *
36
51
  * Una operación iterable es la que devuelve una COLECCIÓN sobre la que el nodo
37
- * de abajo repite. Las cuatro de aquí actúan sobre UN objeto, así que el que
38
- * viene detrás recibe un resultado, no una lista. El día que entre `listObjects`
39
- * ésa sí entra en esta lista.
52
+ * de abajo repite. Las otras seis actúan sobre UN objeto —o devuelven UN
53
+ * enlace—, así que el que viene detrás recibe un resultado, no una lista.
54
+ *
55
+ * ⚠️ Dos cosas que hay que saber antes de implementarla, porque descubrirlas
56
+ * por las malas cuesta una tarde:
57
+ *
58
+ * 1. **Se itera `objects`, y sólo `objects`.** `folders` viaja en el
59
+ * contenedor junto a `truncated`, `nextToken`, `prefix` y `count`. Una
60
+ * operación tiene un campo iterable, y aquí es el de los ficheros; si las
61
+ * carpetas se iteraran también, el aviso de «hay más» se perdería justo en
62
+ * la operación que existe para darlo.
63
+ * 2. **Un prefijo sin nada da `count: 0`, y el nodo de abajo corre CERO
64
+ * veces**, sin error. Es la semántica correcta de una lista vacía, y es
65
+ * también la trampa clásica: parece que el flujo «no hizo nada» cuando en
66
+ * realidad hizo lo que debía.
40
67
  */
41
68
  export declare const BUCKET_ITERABLE_OPERATIONS: readonly BucketOperation[];
42
69
  export type BucketParamType =
@@ -1,6 +1,6 @@
1
1
  /**
2
- * El nodo de bucket: guardar, firmar y borrar objetos en un almacenamiento
3
- * compatible con S3.
2
+ * El nodo de bucket: mirar, leer, guardar, firmar y borrar objetos en un
3
+ * almacenamiento compatible con S3.
4
4
  *
5
5
  * ## Por qué se llama `bucket` y no `r2`
6
6
  *
@@ -19,33 +19,70 @@
19
19
  * un enlace que se manda por correo. Sin esto no había forma de entregarle a un
20
20
  * comprador una descarga temporal desde un flujo.
21
21
  *
22
- * ## Por qué sólo cuatro operaciones
22
+ * ## Por qué siete, y qué sigue fuera
23
23
  *
24
- * Listar y subida multiparte son las dos que piden el SDK de AWS de verdad
25
- * —paginación y reanudación— y el ejecutor hoy no tiene ni un `@aws-sdk/*`.
26
- * Entrar sin esa dependencia y traerla cuando aparezca la operación que la
27
- * justifique es más barato que al revés. Y un nodo con veinte operaciones es un
28
- * nodo que nadie entiende.
24
+ * Las cuatro primeras —`presignDownload`, `putObject`, `headObject`,
25
+ * `deleteObject`— actúan todas sobre un objeto que ya sabes nombrar. Faltaba lo
26
+ * contrario: **no había forma de mirar dentro del bucket**, ni de traerse el
27
+ * contenido de un objeto al flujo, ni de dejar que un tercero SUBA sin darle la
28
+ * credencial. Eso son las tres nuevas:
29
+ *
30
+ * - `listObjects` — qué ficheros y qué CARPETAS hay bajo un prefijo. S3 no
31
+ * tiene carpetas de verdad; con `delimiter` te las sintetiza como prefijos
32
+ * comunes, que es exactamente lo que una persona quiere ver.
33
+ * - `getObject` — el contenido, no un enlace al contenido. `presignDownload`
34
+ * da una URL para que la abra OTRO; cuando el paso de abajo necesita los
35
+ * bytes, una URL no sirve.
36
+ * - `presignUpload` — el simétrico de `presignDownload`.
37
+ *
38
+ * Aquí se decía que listar «pedía el SDK de AWS de verdad». No lo pide:
39
+ * `ListObjectsV2` contesta XML plano y un raspador corto lo lee. Lo que sí
40
+ * sigue pidiendo el SDK es la **subida multiparte** —reanudación, partes,
41
+ * reintentos por parte— y por eso ésa no está. Traer `@aws-sdk/*` el día que
42
+ * aparezca la operación que lo justifique sigue siendo más barato que al revés.
43
+ * Y un nodo con veinte operaciones es un nodo que nadie entiende.
29
44
  */
30
45
  export const BUCKET_OPERATIONS = [
31
46
  "presignDownload",
32
47
  "putObject",
33
48
  "headObject",
34
49
  "deleteObject",
50
+ /* Las tres nuevas van AL FINAL y no intercaladas por tema: el orden de este
51
+ array es el orden del desplegable, y reordenar las cuatro de arriba le
52
+ mueve las filas de sitio a quien ya tiene el nodo en pantalla por un
53
+ cambio que no le aporta nada. El agrupado por tema vive en el dashboard,
54
+ que es donde se puede hacer sin tocar el contrato. */
55
+ "listObjects",
56
+ "getObject",
57
+ "presignUpload",
35
58
  ];
36
59
  export function isBucketOperation(value) {
37
60
  return (typeof value === "string" &&
38
61
  BUCKET_OPERATIONS.includes(value));
39
62
  }
40
63
  /**
41
- * Ninguna es iterable.
64
+ * Sólo `listObjects`.
42
65
  *
43
66
  * Una operación iterable es la que devuelve una COLECCIÓN sobre la que el nodo
44
- * de abajo repite. Las cuatro de aquí actúan sobre UN objeto, así que el que
45
- * viene detrás recibe un resultado, no una lista. El día que entre `listObjects`
46
- * ésa sí entra en esta lista.
67
+ * de abajo repite. Las otras seis actúan sobre UN objeto —o devuelven UN
68
+ * enlace—, así que el que viene detrás recibe un resultado, no una lista.
69
+ *
70
+ * ⚠️ Dos cosas que hay que saber antes de implementarla, porque descubrirlas
71
+ * por las malas cuesta una tarde:
72
+ *
73
+ * 1. **Se itera `objects`, y sólo `objects`.** `folders` viaja en el
74
+ * contenedor junto a `truncated`, `nextToken`, `prefix` y `count`. Una
75
+ * operación tiene un campo iterable, y aquí es el de los ficheros; si las
76
+ * carpetas se iteraran también, el aviso de «hay más» se perdería justo en
77
+ * la operación que existe para darlo.
78
+ * 2. **Un prefijo sin nada da `count: 0`, y el nodo de abajo corre CERO
79
+ * veces**, sin error. Es la semántica correcta de una lista vacía, y es
80
+ * también la trampa clásica: parece que el flujo «no hizo nada» cuando en
81
+ * realidad hizo lo que debía.
47
82
  */
48
- export const BUCKET_ITERABLE_OPERATIONS = [];
83
+ export const BUCKET_ITERABLE_OPERATIONS = [
84
+ "listObjects",
85
+ ];
49
86
  /* Ayudantes — repetir el mismo objeto cuatro veces es cómo se desincronizan las
50
87
  descripciones. */
51
88
  const objectKey = (placeholder) => ({
@@ -56,6 +93,33 @@ const objectKey = (placeholder) => ({
56
93
  placeholder,
57
94
  description: "The path inside the bucket. No leading slash. Supports {{payload.x}}.",
58
95
  });
96
+ /* El TTL de las dos operaciones que firman. El tope de siete días es de SigV4 y
97
+ no se puede pasar: firmar por más tiempo produce una URL que el proveedor
98
+ rechaza, y el error llega tarde y sin explicación. Va en un ayudante porque
99
+ con dos operaciones firmando, dos copias del número es una copia de más. */
100
+ const signatureTtl = () => ({
101
+ name: "expiresIn",
102
+ label: "Link valid for (seconds)",
103
+ type: "number",
104
+ required: true,
105
+ default: 3600,
106
+ min: 60,
107
+ max: 604800,
108
+ description: "From 60 s up to 7 days, the maximum the signature allows. Past that, the link stops working.",
109
+ });
110
+ /* El mismo interruptor en `headObject` y en `getObject`: las dos preguntan por
111
+ un objeto que puede no estar, y las dos lo resuelven con el `status` 404/200
112
+ y no con una excepción. Mismas etiquetas a propósito — son el mismo botón. */
113
+ const failIfMissing = () => ({
114
+ name: "failIfMissing",
115
+ label: "Fail when the object is missing",
116
+ type: "booleanSelect",
117
+ options: [
118
+ { value: "true", label: "Yes — stop the flow" },
119
+ { value: "false", label: "No — carry on with exists: false" },
120
+ ],
121
+ description: 'With "No", the step succeeds and the node below decides. With "Yes", a missing object stops the flow.',
122
+ });
59
123
  export const BUCKET_OPERATION_SPECS = {
60
124
  presignDownload: {
61
125
  label: "Sign download link",
@@ -63,19 +127,7 @@ export const BUCKET_OPERATION_SPECS = {
63
127
  apiRoute: "GET (presigned)",
64
128
  params: [
65
129
  objectKey("cuadernillos/{{payload.orderId}}.pdf"),
66
- {
67
- name: "expiresIn",
68
- label: "Link valid for (seconds)",
69
- type: "number",
70
- required: true,
71
- default: 3600,
72
- min: 60,
73
- /* El tope de SigV4 es de siete días y no se puede pasar: firmar por
74
- más tiempo produce una URL que el proveedor rechaza, y el error
75
- llega tarde y sin explicación. */
76
- max: 604800,
77
- description: "From 60 s up to 7 days, the maximum the signature allows. Past that, the link stops working.",
78
- },
130
+ signatureTtl(),
79
131
  {
80
132
  name: "downloadAs",
81
133
  label: "Download as (filename)",
@@ -113,24 +165,110 @@ export const BUCKET_OPERATION_SPECS = {
113
165
  label: "Check object exists",
114
166
  description: "Checks whether an object exists, and its size, without downloading it.",
115
167
  apiRoute: "HEAD /{bucket}/{key}",
168
+ params: [objectKey("cuadernillos/{{payload.orderId}}.pdf"), failIfMissing()],
169
+ },
170
+ deleteObject: {
171
+ label: "Delete object",
172
+ description: "Deletes an object from the bucket. It cannot be undone.",
173
+ apiRoute: "DELETE /{bucket}/{key}",
174
+ params: [objectKey("temporales/{{payload.id}}.tmp")],
175
+ },
176
+ listObjects: {
177
+ label: "List objects",
178
+ description: "Lists the files and folders under a prefix. The node below runs once per file; folders travel alongside the list, they are not iterated. An empty prefix means zero runs, not an error.",
179
+ apiRoute: "GET /{bucket}?list-type=2",
116
180
  params: [
117
- objectKey("cuadernillos/{{payload.orderId}}.pdf"),
181
+ /* NO usa `objectKey()`, y no es un descuido: ese ayudante marca
182
+ `required: true` y la validación de clave rechaza la cadena vacía. Un
183
+ prefijo puede ser vacío —el bucket entero— y puede acabar en barra,
184
+ que es justo lo que lo hace un prefijo y no una clave. */
118
185
  {
119
- name: "failIfMissing",
120
- label: "Fail when the object is missing",
186
+ name: "prefix",
187
+ label: "Prefix",
188
+ type: "template",
189
+ placeholder: "cuadernillos/2026/",
190
+ description: "Only keys starting with this. No leading slash. Leave it empty to list the whole bucket. Supports {{payload.x}}.",
191
+ },
192
+ {
193
+ name: "groupIntoFolders",
194
+ label: "Group into folders",
121
195
  type: "booleanSelect",
122
196
  options: [
123
- { value: "true", label: "Yes — stop the flow" },
124
- { value: "false", label: "No — carry on with exists: false" },
197
+ { value: "true", label: "Yes — one level, with folders" },
198
+ { value: "false", label: "No — every key under the prefix" },
125
199
  ],
126
- description: 'With "No", the step succeeds and the node below decides. With "Yes", a missing object stops the flow.',
200
+ description: 'With "Yes" you get the files at this level plus the folder names below it. With "No" you get every key under the prefix, however deep. Defaults to Yes.',
201
+ },
202
+ {
203
+ name: "maxKeys",
204
+ label: "Maximum objects",
205
+ type: "number",
206
+ default: 1000,
207
+ min: 1,
208
+ max: 5000,
209
+ /* El proveedor devuelve 1.000 por llamada como mucho, así que de 1.000
210
+ en adelante el ejecutor pagina por dentro. El techo de 5.000 no es
211
+ timidez: el contenedor viaja como JSON al nodo de abajo y se guarda
212
+ en el historial, y ahí ya hay un recorte a 256 KB por payload. */
213
+ description: "Up to 5000, paginated for you. When there are more, the step still succeeds: truncated says so and nextToken lets the next run carry on from there.",
214
+ },
215
+ {
216
+ name: "startAfter",
217
+ label: "Continue after",
218
+ type: "template",
219
+ advanced: true,
220
+ placeholder: "{{payload.nextToken}}",
221
+ description: "Resumes a listing: feed it the nextToken of the previous run. Leave it empty to start from the top.",
127
222
  },
128
223
  ],
129
224
  },
130
- deleteObject: {
131
- label: "Delete object",
132
- description: "Deletes an object from the bucket. It cannot be undone.",
133
- apiRoute: "DELETE /{bucket}/{key}",
134
- params: [objectKey("temporales/{{payload.id}}.tmp")],
225
+ /* El tope de lectura NO es un parámetro: es del ejecutor, no una preferencia
226
+ por nodo. Y se dice en la descripción, no se descubre — al superarlo el
227
+ paso FALLA con el tamaño real y un puntero a `presignDownload`; nunca
228
+ devuelve un `content` a medias. Un contenido truncado se parsea, se manda
229
+ por correo y nadie se entera. */
230
+ getObject: {
231
+ label: "Read object",
232
+ description: "Downloads an object and puts its contents in the payload. Up to 5 MB; past that the step fails and points you at Sign download link.",
233
+ apiRoute: "GET /{bucket}/{key}",
234
+ params: [
235
+ objectKey("recibos/{{payload.orderId}}.json"),
236
+ {
237
+ name: "as",
238
+ label: "Read as",
239
+ type: "select",
240
+ required: true,
241
+ default: "text",
242
+ options: [
243
+ { value: "text", label: "Text — UTF-8 into content" },
244
+ { value: "json", label: "JSON — parsed into content" },
245
+ { value: "base64", label: "Base64 — bytes into contentBase64" },
246
+ ],
247
+ /* Lo binario se PIDE, no se adivina: un PDF leído como texto llega
248
+ como UTF-8 roto y el fallo aparece tres nodos más abajo. */
249
+ description: "Binary files need Base64 — read as text they arrive corrupted, and Base64 weighs a third more than the file. With JSON, a body that does not parse fails the step. Over 256 KB the contents still flow on whole, but the run history stores them trimmed.",
250
+ },
251
+ failIfMissing(),
252
+ ],
253
+ },
254
+ presignUpload: {
255
+ label: "Sign upload link",
256
+ description: "Returns a temporary URL for someone else to PUT a file into the bucket, without giving them the credential.",
257
+ apiRoute: "PUT (presigned)",
258
+ params: [
259
+ objectKey("subidas/{{payload.orderId}}.pdf"),
260
+ signatureTtl(),
261
+ {
262
+ name: "contentType",
263
+ label: "Content type",
264
+ type: "template",
265
+ default: "application/octet-stream",
266
+ placeholder: "application/pdf",
267
+ /* NO se firma. Meterlo en `signedHeaders` obliga al que sube a mandar
268
+ esa cabecera exacta o comerse un 403 que no puede depurar, y quien
269
+ recibe el enlace no suele controlar su cliente HTTP. */
270
+ description: "What the object will be stored as. It is not enforced — the bucket stores whatever arrives.",
271
+ },
272
+ ],
135
273
  },
136
274
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hostwebhook/node-types",
3
- "version": "1.71.1",
3
+ "version": "1.72.0",
4
4
  "description": "Shared node type definitions, connection rules, and dispatch config for HostWebhook",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/esm/index.js",