@fiado/api-invoker 5.61.0 → 5.62.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
  import type { IHttpRequest } from "@fiado/http-client";
2
2
  import { StandardResponse } from "@fiado/gateway-adapter";
3
- import { ImeiProductLookupResponse, IRetailCatalogBusinessApi, ReleaseReservationBody, ReserveInventoryBody, ReserveInventoryResult, SellInventoryBody, StoreCatalogItem } from "./interfaces/IRetailCatalogBusinessApi.js";
3
+ import { ImeiProductLookupResponse, IRetailCatalogBusinessApi, ProductsByRetailersResponse, ReleaseReservationBody, ReserveInventoryBody, ReserveInventoryResult, SellInventoryBody, StoreCatalogItem } from "./interfaces/IRetailCatalogBusinessApi.js";
4
4
  /**
5
5
  * Publisher HTTP del lambda `retail-catalog-business` (SureKeep Fase 1, pista Retail) para sus
6
6
  * endpoints privados del ciclo de venta del IMEI. Contrato y semántica completos →
@@ -21,6 +21,7 @@ export default class RetailCatalogBusinessApi implements IRetailCatalogBusinessA
21
21
  private tenantHeader;
22
22
  getStoreCatalog(storeId: string, issuer: string): Promise<StandardResponse<StoreCatalogItem[]>>;
23
23
  getProductsByImei(imeis: string[], tenantId: string): Promise<StandardResponse<ImeiProductLookupResponse>>;
24
+ getProductsByRetailers(retailerIds: string[], tenantId: string): Promise<StandardResponse<ProductsByRetailersResponse>>;
24
25
  reserveInventory(imei: string, body: ReserveInventoryBody, issuer: string): Promise<StandardResponse<ReserveInventoryResult>>;
25
26
  sellInventory(imei: string, body: SellInventoryBody, issuer: string): Promise<StandardResponse<void>>;
26
27
  releaseInventory(imei: string, body: ReleaseReservationBody, issuer: string): Promise<StandardResponse<void>>;
@@ -11,7 +11,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
11
11
  return function (target, key) { decorator(target, key, paramIndex); }
12
12
  };
13
13
  import { inject, injectable } from "inversify";
14
- import { IMEI_PRODUCT_BATCH_MAX, } from "./interfaces/IRetailCatalogBusinessApi.js";
14
+ import { IMEI_PRODUCT_BATCH_MAX, RETAILER_PRODUCTS_BATCH_MAX, } from "./interfaces/IRetailCatalogBusinessApi.js";
15
15
  /** Header por el que el destino resuelve el silo (issuer Cognito forwardeado por el caller). */
16
16
  const TENANT_ISSUER_HEADER = "x-tenant-issuer";
17
17
  /**
@@ -51,6 +51,15 @@ let RetailCatalogBusinessApi = class RetailCatalogBusinessApi {
51
51
  const query = new URLSearchParams({ imeis: imeis.join(","), tenantId });
52
52
  return await this.httpRequest.get(`${this.baseUrl}/private/products/by-imei?${query.toString()}`);
53
53
  }
54
+ async getProductsByRetailers(retailerIds, tenantId) {
55
+ // Se corta aquí: mandar un lote que el destino va a rechazar solo gasta un round-trip.
56
+ if (retailerIds.length > RETAILER_PRODUCTS_BATCH_MAX) {
57
+ throw new Error(`El lote admite hasta ${RETAILER_PRODUCTS_BATCH_MAX} cadenas y llegaron ${retailerIds.length}`);
58
+ }
59
+ const query = new URLSearchParams({ tenantId });
60
+ const body = { retailerIds };
61
+ return await this.httpRequest.post(`${this.baseUrl}/private/products/by-retailers?${query.toString()}`, body);
62
+ }
54
63
  async reserveInventory(imei, body, issuer) {
55
64
  const url = `${this.baseUrl}/private/inventory/${encodeURIComponent(imei)}/reserve`;
56
65
  return await this.httpRequest.put(url, body, this.tenantHeader(issuer));
@@ -80,6 +80,30 @@ export interface ImeiProductLookupEntry {
80
80
  export interface ImeiProductLookupResponse {
81
81
  items: ImeiProductLookupEntry[];
82
82
  }
83
+ /** Techo de cadenas por request del lote cadena->catálogo. Lo declara y lo honra el destino. */
84
+ export declare const RETAILER_PRODUCTS_BATCH_MAX = 100;
85
+ /** Body de POST `/private/products/by-retailers`. */
86
+ export interface ProductsByRetailersBody {
87
+ retailerIds: string[];
88
+ }
89
+ /** Un producto vendible de una cadena (espeja `RetailerProductItem` del destino). */
90
+ export interface RetailerProductItem {
91
+ /** Cadena a la que pertenece el producto; es la llave con la que el caller agrupa. */
92
+ retailerId: string;
93
+ sku: string;
94
+ /** Marca comercial (p. ej. "Samsung"). */
95
+ brand: string;
96
+ /** Modelo (p. ej. "A54"). */
97
+ model: string;
98
+ }
99
+ /** Respuesta del lote cadena->catálogo: los productos de TODAS las cadenas pedidas, en una lista plana. */
100
+ export interface ProductsByRetailersResponse {
101
+ items: RetailerProductItem[];
102
+ /** Cuántos productos trae `items`. */
103
+ count: number;
104
+ /** `true` si el destino recortó el resultado; pide las cadenas en lotes más chicos. */
105
+ truncated: boolean;
106
+ }
83
107
  /** Resultado de la reserva (espeja `ReserveInventoryResponse` del destino). */
84
108
  export interface ReserveInventoryResult {
85
109
  reservationExpiresAt: string;
@@ -125,6 +149,32 @@ export interface IRetailCatalogBusinessApi {
125
149
  * `400 IMEI_BATCH_REQUIRED` si `imeis` viene vacío · `400 UNKNOWN_TENANT` sin `tenantId`.
126
150
  */
127
151
  getProductsByImei(imeis: string[], tenantId: string): Promise<StandardResponse<ImeiProductLookupResponse>>;
152
+ /**
153
+ * POST `/private/products/by-retailers?tenantId=` — catálogo de productos de VARIAS cadenas en
154
+ * una sola llamada, para el consumidor que arma su vista con N cadenas y no quiere N requests.
155
+ *
156
+ * ─────────────────────────────────────────────────────────────────────────────
157
+ * ⚠️ El tenant va por `?tenantId=`, NO por el header `x-tenant-issuer` del resto de este publisher.
158
+ * ─────────────────────────────────────────────────────────────────────────────
159
+ * Es obligatorio. El destino entra por `TenantContextService.withContextByTenantId`, igual que
160
+ * `getProductsByImei`.
161
+ *
162
+ * ── El contrato del lote ────────────────────────────────────────────────────────────────────
163
+ * `retailerIds` no puede venir vacío, sin repetidos y con un techo de
164
+ * `RETAILER_PRODUCTS_BATCH_MAX` cadenas; el caller parte los lotes mayores. La respuesta es una
165
+ * lista PLANA de productos con su `retailerId` — el caller agrupa. Una cadena sin catálogo
166
+ * simplemente no aporta ítems.
167
+ *
168
+ * O el lote sale completo o no sale: si falla la lectura de al menos una cadena el destino
169
+ * responde `INTERNAL_ERROR`. Nunca recibes una lista parcial.
170
+ *
171
+ * @throws Error si el lote supera el techo (se corta aquí, sin salir a la red) ·
172
+ * `400 VALIDATION_ERROR` body vacío, con repetidos, con no-strings o de más de 100 ids ·
173
+ * `400 UNKNOWN_TENANT` falta o no se reconoce el `tenantId` ·
174
+ * `500 TENANT_ASSUME_ROLE_FAILED` fallo de infra del silo ·
175
+ * `500 INTERNAL_ERROR` no se pudo leer alguna cadena.
176
+ */
177
+ getProductsByRetailers(retailerIds: string[], tenantId: string): Promise<StandardResponse<ProductsByRetailersResponse>>;
128
178
  /** PUT `/private/inventory/{imei}/reserve` — reserva atómica del IMEI para una sesión (AVAILABLE → RESERVED). */
129
179
  reserveInventory(imei: string, body: ReserveInventoryBody, issuer: string): Promise<StandardResponse<ReserveInventoryResult>>;
130
180
  /** PUT `/private/inventory/{imei}/sell` — vende el IMEI (RESERVED → SOLD). */
@@ -1,2 +1,4 @@
1
1
  /** Techo de IMEIs por request del lote IMEI->producto. Lo declara y lo honra el destino. */
2
2
  export const IMEI_PRODUCT_BATCH_MAX = 100;
3
+ /** Techo de cadenas por request del lote cadena->catálogo. Lo declara y lo honra el destino. */
4
+ export const RETAILER_PRODUCTS_BATCH_MAX = 100;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fiado/api-invoker",
3
- "version": "5.61.0",
3
+ "version": "5.62.0",
4
4
  "description": "Sirve como un puente entre diferentes funciones lambda, facilitando la comunicación entre ellas a través de invocaciones http",
5
5
  "type": "module",
6
6
  "main": "bin/index.js",
@@ -5,9 +5,12 @@ import {
5
5
  IMEI_PRODUCT_BATCH_MAX,
6
6
  ImeiProductLookupResponse,
7
7
  IRetailCatalogBusinessApi,
8
+ ProductsByRetailersBody,
9
+ ProductsByRetailersResponse,
8
10
  ReleaseReservationBody,
9
11
  ReserveInventoryBody,
10
12
  ReserveInventoryResult,
13
+ RETAILER_PRODUCTS_BATCH_MAX,
11
14
  SellInventoryBody,
12
15
  StoreCatalogItem,
13
16
  } from "./interfaces/IRetailCatalogBusinessApi.js";
@@ -58,6 +61,21 @@ export default class RetailCatalogBusinessApi implements IRetailCatalogBusinessA
58
61
  return await this.httpRequest.get(`${this.baseUrl}/private/products/by-imei?${query.toString()}`);
59
62
  }
60
63
 
64
+ async getProductsByRetailers(
65
+ retailerIds: string[],
66
+ tenantId: string,
67
+ ): Promise<StandardResponse<ProductsByRetailersResponse>> {
68
+ // Se corta aquí: mandar un lote que el destino va a rechazar solo gasta un round-trip.
69
+ if (retailerIds.length > RETAILER_PRODUCTS_BATCH_MAX) {
70
+ throw new Error(
71
+ `El lote admite hasta ${RETAILER_PRODUCTS_BATCH_MAX} cadenas y llegaron ${retailerIds.length}`,
72
+ );
73
+ }
74
+ const query = new URLSearchParams({ tenantId });
75
+ const body: ProductsByRetailersBody = { retailerIds };
76
+ return await this.httpRequest.post(`${this.baseUrl}/private/products/by-retailers?${query.toString()}`, body);
77
+ }
78
+
61
79
  async reserveInventory(
62
80
  imei: string,
63
81
  body: ReserveInventoryBody,
@@ -93,6 +93,34 @@ export interface ImeiProductLookupResponse {
93
93
  items: ImeiProductLookupEntry[];
94
94
  }
95
95
 
96
+ /** Techo de cadenas por request del lote cadena->catálogo. Lo declara y lo honra el destino. */
97
+ export const RETAILER_PRODUCTS_BATCH_MAX = 100;
98
+
99
+ /** Body de POST `/private/products/by-retailers`. */
100
+ export interface ProductsByRetailersBody {
101
+ retailerIds: string[];
102
+ }
103
+
104
+ /** Un producto vendible de una cadena (espeja `RetailerProductItem` del destino). */
105
+ export interface RetailerProductItem {
106
+ /** Cadena a la que pertenece el producto; es la llave con la que el caller agrupa. */
107
+ retailerId: string;
108
+ sku: string;
109
+ /** Marca comercial (p. ej. "Samsung"). */
110
+ brand: string;
111
+ /** Modelo (p. ej. "A54"). */
112
+ model: string;
113
+ }
114
+
115
+ /** Respuesta del lote cadena->catálogo: los productos de TODAS las cadenas pedidas, en una lista plana. */
116
+ export interface ProductsByRetailersResponse {
117
+ items: RetailerProductItem[];
118
+ /** Cuántos productos trae `items`. */
119
+ count: number;
120
+ /** `true` si el destino recortó el resultado; pide las cadenas en lotes más chicos. */
121
+ truncated: boolean;
122
+ }
123
+
96
124
  /** Resultado de la reserva (espeja `ReserveInventoryResponse` del destino). */
97
125
  export interface ReserveInventoryResult {
98
126
  reservationExpiresAt: string;
@@ -144,6 +172,36 @@ export interface IRetailCatalogBusinessApi {
144
172
  */
145
173
  getProductsByImei(imeis: string[], tenantId: string): Promise<StandardResponse<ImeiProductLookupResponse>>;
146
174
 
175
+ /**
176
+ * POST `/private/products/by-retailers?tenantId=` — catálogo de productos de VARIAS cadenas en
177
+ * una sola llamada, para el consumidor que arma su vista con N cadenas y no quiere N requests.
178
+ *
179
+ * ─────────────────────────────────────────────────────────────────────────────
180
+ * ⚠️ El tenant va por `?tenantId=`, NO por el header `x-tenant-issuer` del resto de este publisher.
181
+ * ─────────────────────────────────────────────────────────────────────────────
182
+ * Es obligatorio. El destino entra por `TenantContextService.withContextByTenantId`, igual que
183
+ * `getProductsByImei`.
184
+ *
185
+ * ── El contrato del lote ────────────────────────────────────────────────────────────────────
186
+ * `retailerIds` no puede venir vacío, sin repetidos y con un techo de
187
+ * `RETAILER_PRODUCTS_BATCH_MAX` cadenas; el caller parte los lotes mayores. La respuesta es una
188
+ * lista PLANA de productos con su `retailerId` — el caller agrupa. Una cadena sin catálogo
189
+ * simplemente no aporta ítems.
190
+ *
191
+ * O el lote sale completo o no sale: si falla la lectura de al menos una cadena el destino
192
+ * responde `INTERNAL_ERROR`. Nunca recibes una lista parcial.
193
+ *
194
+ * @throws Error si el lote supera el techo (se corta aquí, sin salir a la red) ·
195
+ * `400 VALIDATION_ERROR` body vacío, con repetidos, con no-strings o de más de 100 ids ·
196
+ * `400 UNKNOWN_TENANT` falta o no se reconoce el `tenantId` ·
197
+ * `500 TENANT_ASSUME_ROLE_FAILED` fallo de infra del silo ·
198
+ * `500 INTERNAL_ERROR` no se pudo leer alguna cadena.
199
+ */
200
+ getProductsByRetailers(
201
+ retailerIds: string[],
202
+ tenantId: string,
203
+ ): Promise<StandardResponse<ProductsByRetailersResponse>>;
204
+
147
205
  /** PUT `/private/inventory/{imei}/reserve` — reserva atómica del IMEI para una sesión (AVAILABLE → RESERVED). */
148
206
  reserveInventory(imei: string, body: ReserveInventoryBody, issuer: string): Promise<StandardResponse<ReserveInventoryResult>>;
149
207