@ecomiq/core 0.1.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.
- package/README.md +42 -0
- package/dist/api.cjs +99 -0
- package/dist/api.cjs.map +1 -0
- package/dist/api.d.cts +52 -0
- package/dist/api.d.ts +52 -0
- package/dist/api.js +93 -0
- package/dist/api.js.map +1 -0
- package/dist/client.cjs +1344 -0
- package/dist/client.cjs.map +1 -0
- package/dist/client.d.cts +82 -0
- package/dist/client.d.ts +82 -0
- package/dist/client.js +1341 -0
- package/dist/client.js.map +1 -0
- package/dist/index.cjs +1611 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1109 -0
- package/dist/index.d.ts +1109 -0
- package/dist/index.js +1450 -0
- package/dist/index.js.map +1 -0
- package/dist/models/collections.cjs +20 -0
- package/dist/models/collections.cjs.map +1 -0
- package/dist/models/collections.d.cts +56 -0
- package/dist/models/collections.d.ts +56 -0
- package/dist/models/collections.js +17 -0
- package/dist/models/collections.js.map +1 -0
- package/dist/models/geo.cjs +62 -0
- package/dist/models/geo.cjs.map +1 -0
- package/dist/models/geo.d.cts +279 -0
- package/dist/models/geo.d.ts +279 -0
- package/dist/models/geo.js +48 -0
- package/dist/models/geo.js.map +1 -0
- package/dist/models/products.cjs +212 -0
- package/dist/models/products.cjs.map +1 -0
- package/dist/models/products.d.cts +1421 -0
- package/dist/models/products.d.ts +1421 -0
- package/dist/models/products.js +187 -0
- package/dist/models/products.js.map +1 -0
- package/dist/models/search.cjs +200 -0
- package/dist/models/search.cjs.map +1 -0
- package/dist/models/search.d.cts +51 -0
- package/dist/models/search.d.ts +51 -0
- package/dist/models/search.js +196 -0
- package/dist/models/search.js.map +1 -0
- package/dist/models/settings.cjs +285 -0
- package/dist/models/settings.cjs.map +1 -0
- package/dist/models/settings.d.cts +7039 -0
- package/dist/models/settings.d.ts +7039 -0
- package/dist/models/settings.js +239 -0
- package/dist/models/settings.js.map +1 -0
- package/dist/models/storefront-cart.cjs +148 -0
- package/dist/models/storefront-cart.cjs.map +1 -0
- package/dist/models/storefront-cart.d.cts +747 -0
- package/dist/models/storefront-cart.d.ts +747 -0
- package/dist/models/storefront-cart.js +133 -0
- package/dist/models/storefront-cart.js.map +1 -0
- package/dist/models/storefront-checkout.cjs +307 -0
- package/dist/models/storefront-checkout.cjs.map +1 -0
- package/dist/models/storefront-checkout.d.cts +4122 -0
- package/dist/models/storefront-checkout.d.ts +4122 -0
- package/dist/models/storefront-checkout.js +288 -0
- package/dist/models/storefront-checkout.js.map +1 -0
- package/dist/models/storefront-reviews.cjs +223 -0
- package/dist/models/storefront-reviews.cjs.map +1 -0
- package/dist/models/storefront-reviews.d.cts +147 -0
- package/dist/models/storefront-reviews.d.ts +147 -0
- package/dist/models/storefront-reviews.js +218 -0
- package/dist/models/storefront-reviews.js.map +1 -0
- package/package.json +109 -0
package/README.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# @ecomiq/core
|
|
2
|
+
|
|
3
|
+
Modelos Zod, cliente HTTP y servicios storefront compartidos de Ecomiq.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @ecomiq/core
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { createClient, createApi } from '@ecomiq/core'
|
|
15
|
+
|
|
16
|
+
const api = createClient({
|
|
17
|
+
slug: 'mi-tienda',
|
|
18
|
+
baseUrl: 'https://storefront.ecomiq.pe',
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const settings = await api.getSettings()
|
|
22
|
+
const products = await api.getProducts({ page: 1, pageSize: 12 })
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Subpaths (opcional):
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { createApi } from '@ecomiq/core/api'
|
|
29
|
+
import { StorefrontCartSchema } from '@ecomiq/core/models/storefront-cart'
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Build
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pnpm --filter @ecomiq/core build
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Publish
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pnpm --filter @ecomiq/core publish --access public
|
|
42
|
+
```
|
package/dist/api.cjs
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/libs/api.ts
|
|
4
|
+
var DEFAULT_API_BASE_URL = "https://storefront.ecomiq.pe";
|
|
5
|
+
var DEFAULT_API_VERSION = "v1";
|
|
6
|
+
var ApiError = class extends Error {
|
|
7
|
+
constructor(message, status, url, body) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.status = status;
|
|
10
|
+
this.url = url;
|
|
11
|
+
this.body = body;
|
|
12
|
+
this.name = "ApiError";
|
|
13
|
+
}
|
|
14
|
+
status;
|
|
15
|
+
url;
|
|
16
|
+
body;
|
|
17
|
+
};
|
|
18
|
+
function resolveApiBaseUrl(baseUrl = DEFAULT_API_BASE_URL, version = DEFAULT_API_VERSION) {
|
|
19
|
+
const root = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
20
|
+
const segment = version.replace(/^\/+|\/+$/g, "");
|
|
21
|
+
return new URL(`${segment}/`, root).toString();
|
|
22
|
+
}
|
|
23
|
+
function createApi(options = {}) {
|
|
24
|
+
const {
|
|
25
|
+
baseUrl = DEFAULT_API_BASE_URL,
|
|
26
|
+
version = DEFAULT_API_VERSION,
|
|
27
|
+
headers: baseHeaders = {},
|
|
28
|
+
timeoutMs = 1e4
|
|
29
|
+
} = options;
|
|
30
|
+
const effectiveBaseUrl = resolveApiBaseUrl(baseUrl, version);
|
|
31
|
+
async function request(method, path, schema, opts = {}) {
|
|
32
|
+
const url = new URL(path.replace(/^\//, ""), effectiveBaseUrl);
|
|
33
|
+
for (const [key, value] of Object.entries(opts.query ?? {})) {
|
|
34
|
+
if (value !== void 0) url.searchParams.set(key, String(value));
|
|
35
|
+
}
|
|
36
|
+
const controller = new AbortController();
|
|
37
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
38
|
+
if (opts.signal) opts.signal.addEventListener("abort", () => controller.abort(), { once: true });
|
|
39
|
+
const isFormData = typeof FormData !== "undefined" && opts.body instanceof FormData;
|
|
40
|
+
let response;
|
|
41
|
+
try {
|
|
42
|
+
response = await fetch(url.toString(), {
|
|
43
|
+
method,
|
|
44
|
+
headers: {
|
|
45
|
+
accept: "application/json",
|
|
46
|
+
...opts.body !== void 0 && !isFormData ? { "content-type": "application/json" } : {},
|
|
47
|
+
...baseHeaders,
|
|
48
|
+
...opts.headers
|
|
49
|
+
},
|
|
50
|
+
body: opts.body === void 0 ? void 0 : isFormData ? opts.body : JSON.stringify(opts.body),
|
|
51
|
+
signal: controller.signal
|
|
52
|
+
});
|
|
53
|
+
} catch (err) {
|
|
54
|
+
throw new ApiError(
|
|
55
|
+
err instanceof DOMException && err.name === "AbortError" ? `Timeout tras ${timeoutMs}ms: ${url}` : `No se pudo conectar con ${url}`,
|
|
56
|
+
0,
|
|
57
|
+
url.toString()
|
|
58
|
+
);
|
|
59
|
+
} finally {
|
|
60
|
+
clearTimeout(timeout);
|
|
61
|
+
}
|
|
62
|
+
if (!response.ok) {
|
|
63
|
+
const bodyText = await response.text().catch(() => void 0);
|
|
64
|
+
let body = bodyText;
|
|
65
|
+
if (bodyText) {
|
|
66
|
+
try {
|
|
67
|
+
body = JSON.parse(bodyText);
|
|
68
|
+
} catch {
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
throw new ApiError(`${method} ${url} \u2192 ${response.status}`, response.status, url.toString(), body);
|
|
72
|
+
}
|
|
73
|
+
const json = await response.json();
|
|
74
|
+
const parsed = schema.safeParse(json);
|
|
75
|
+
if (!parsed.success) {
|
|
76
|
+
throw new ApiError(
|
|
77
|
+
`Respuesta inesperada de ${url}: ${parsed.error.issues[0]?.path.join(".")} ${parsed.error.issues[0]?.message}`,
|
|
78
|
+
response.status,
|
|
79
|
+
url.toString(),
|
|
80
|
+
json
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
return parsed.data;
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
request,
|
|
87
|
+
get: (path, schema, opts) => request("GET", path, schema, opts),
|
|
88
|
+
post: (path, body, schema, opts) => request("POST", path, schema, { ...opts, body }),
|
|
89
|
+
postForm: (path, body, schema, opts) => request("POST", path, schema, { ...opts, body })
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
exports.ApiError = ApiError;
|
|
94
|
+
exports.DEFAULT_API_BASE_URL = DEFAULT_API_BASE_URL;
|
|
95
|
+
exports.DEFAULT_API_VERSION = DEFAULT_API_VERSION;
|
|
96
|
+
exports.createApi = createApi;
|
|
97
|
+
exports.resolveApiBaseUrl = resolveApiBaseUrl;
|
|
98
|
+
//# sourceMappingURL=api.cjs.map
|
|
99
|
+
//# sourceMappingURL=api.cjs.map
|
package/dist/api.cjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/libs/api.ts"],"names":[],"mappings":"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","file":"api.cjs","sourcesContent":["/**\n * Cliente HTTP de Ecomiq para servicios externos.\n * fetch nativo (funciona en Node, navegador y Cloudflare Workers)\n * + validación de respuestas con Zod.\n */\nimport type { z } from 'zod'\n\nexport const DEFAULT_API_BASE_URL = 'https://storefront.ecomiq.pe'\nexport const DEFAULT_API_VERSION = 'v1'\n\nexport class ApiError extends Error {\n constructor(\n message: string,\n public readonly status: number,\n public readonly url: string,\n public readonly body?: unknown,\n ) {\n super(message)\n this.name = 'ApiError'\n }\n}\n\nexport interface CreateApiOptions {\n /** Default: https://storefront.ecomiq.pe */\n baseUrl?: string\n /** Segmento de path, default 'v1' → {baseUrl}/v1/... */\n version?: string\n headers?: Record<string, string>\n timeoutMs?: number\n}\n\ninterface RequestOptions {\n query?: Record<string, string | number | boolean | undefined>\n headers?: Record<string, string>\n signal?: AbortSignal\n}\n\nexport interface Api {\n get<S extends z.ZodTypeAny>(\n path: string,\n schema: S,\n options?: RequestOptions,\n ): Promise<z.output<S>>\n post<S extends z.ZodTypeAny>(\n path: string,\n body: unknown,\n schema: S,\n options?: RequestOptions,\n ): Promise<z.output<S>>\n /** POST multipart; no fuerza `content-type: application/json`. */\n postForm<S extends z.ZodTypeAny>(\n path: string,\n body: FormData,\n schema: S,\n options?: RequestOptions,\n ): Promise<z.output<S>>\n request<S extends z.ZodTypeAny>(\n method: string,\n path: string,\n schema: S,\n options?: RequestOptions & { body?: unknown },\n ): Promise<z.output<S>>\n}\n\n/** Une baseUrl + version en la base efectiva del cliente, ej. https://storefront.ecomiq.pe/v1/ */\nexport function resolveApiBaseUrl(\n baseUrl: string = DEFAULT_API_BASE_URL,\n version: string = DEFAULT_API_VERSION,\n): string {\n const root = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`\n const segment = version.replace(/^\\/+|\\/+$/g, '')\n return new URL(`${segment}/`, root).toString()\n}\n\n/**\n * Crea un cliente HTTP tipado con Zod.\n *\n * ```ts\n * const api = createApi()\n * const data = await api.get('settings', schema)\n * // → GET https://storefront.ecomiq.pe/v1/settings\n * ```\n */\nexport function createApi(options: CreateApiOptions = {}): Api {\n const {\n baseUrl = DEFAULT_API_BASE_URL,\n version = DEFAULT_API_VERSION,\n headers: baseHeaders = {},\n timeoutMs = 10_000,\n } = options\n\n const effectiveBaseUrl = resolveApiBaseUrl(baseUrl, version)\n\n async function request<S extends z.ZodTypeAny>(\n method: string,\n path: string,\n schema: S,\n opts: RequestOptions & { body?: unknown } = {},\n ): Promise<z.output<S>> {\n const url = new URL(path.replace(/^\\//, ''), effectiveBaseUrl)\n for (const [key, value] of Object.entries(opts.query ?? {})) {\n if (value !== undefined) url.searchParams.set(key, String(value))\n }\n\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), timeoutMs)\n if (opts.signal) opts.signal.addEventListener('abort', () => controller.abort(), { once: true })\n\n const isFormData =\n typeof FormData !== 'undefined' && opts.body instanceof FormData\n\n let response: Response\n try {\n response = await fetch(url.toString(), {\n method,\n headers: {\n accept: 'application/json',\n ...(opts.body !== undefined && !isFormData\n ? { 'content-type': 'application/json' }\n : {}),\n ...baseHeaders,\n ...opts.headers,\n },\n body:\n opts.body === undefined\n ? undefined\n : isFormData\n ? (opts.body as FormData)\n : JSON.stringify(opts.body),\n signal: controller.signal,\n })\n } catch (err) {\n throw new ApiError(\n err instanceof DOMException && err.name === 'AbortError'\n ? `Timeout tras ${timeoutMs}ms: ${url}`\n : `No se pudo conectar con ${url}`,\n 0,\n url.toString(),\n )\n } finally {\n clearTimeout(timeout)\n }\n\n if (!response.ok) {\n const bodyText = await response.text().catch(() => undefined)\n let body: unknown = bodyText\n if (bodyText) {\n try {\n body = JSON.parse(bodyText)\n } catch {}\n }\n throw new ApiError(`${method} ${url} → ${response.status}`, response.status, url.toString(), body)\n }\n\n const json = await response.json()\n const parsed = schema.safeParse(json)\n if (!parsed.success) {\n throw new ApiError(\n `Respuesta inesperada de ${url}: ${parsed.error.issues[0]?.path.join('.')} ${parsed.error.issues[0]?.message}`,\n response.status,\n url.toString(),\n json,\n )\n }\n return parsed.data as z.output<S>\n }\n\n return {\n request,\n get: (path, schema, opts) => request('GET', path, schema, opts),\n post: (path, body, schema, opts) => request('POST', path, schema, { ...opts, body }),\n postForm: (path, body, schema, opts) =>\n request('POST', path, schema, { ...opts, body }),\n }\n}\n"]}
|
package/dist/api.d.cts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Cliente HTTP de Ecomiq para servicios externos.
|
|
5
|
+
* fetch nativo (funciona en Node, navegador y Cloudflare Workers)
|
|
6
|
+
* + validación de respuestas con Zod.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
declare const DEFAULT_API_BASE_URL = "https://storefront.ecomiq.pe";
|
|
10
|
+
declare const DEFAULT_API_VERSION = "v1";
|
|
11
|
+
declare class ApiError extends Error {
|
|
12
|
+
readonly status: number;
|
|
13
|
+
readonly url: string;
|
|
14
|
+
readonly body?: unknown | undefined;
|
|
15
|
+
constructor(message: string, status: number, url: string, body?: unknown | undefined);
|
|
16
|
+
}
|
|
17
|
+
interface CreateApiOptions {
|
|
18
|
+
/** Default: https://storefront.ecomiq.pe */
|
|
19
|
+
baseUrl?: string;
|
|
20
|
+
/** Segmento de path, default 'v1' → {baseUrl}/v1/... */
|
|
21
|
+
version?: string;
|
|
22
|
+
headers?: Record<string, string>;
|
|
23
|
+
timeoutMs?: number;
|
|
24
|
+
}
|
|
25
|
+
interface RequestOptions {
|
|
26
|
+
query?: Record<string, string | number | boolean | undefined>;
|
|
27
|
+
headers?: Record<string, string>;
|
|
28
|
+
signal?: AbortSignal;
|
|
29
|
+
}
|
|
30
|
+
interface Api {
|
|
31
|
+
get<S extends z.ZodTypeAny>(path: string, schema: S, options?: RequestOptions): Promise<z.output<S>>;
|
|
32
|
+
post<S extends z.ZodTypeAny>(path: string, body: unknown, schema: S, options?: RequestOptions): Promise<z.output<S>>;
|
|
33
|
+
/** POST multipart; no fuerza `content-type: application/json`. */
|
|
34
|
+
postForm<S extends z.ZodTypeAny>(path: string, body: FormData, schema: S, options?: RequestOptions): Promise<z.output<S>>;
|
|
35
|
+
request<S extends z.ZodTypeAny>(method: string, path: string, schema: S, options?: RequestOptions & {
|
|
36
|
+
body?: unknown;
|
|
37
|
+
}): Promise<z.output<S>>;
|
|
38
|
+
}
|
|
39
|
+
/** Une baseUrl + version en la base efectiva del cliente, ej. https://storefront.ecomiq.pe/v1/ */
|
|
40
|
+
declare function resolveApiBaseUrl(baseUrl?: string, version?: string): string;
|
|
41
|
+
/**
|
|
42
|
+
* Crea un cliente HTTP tipado con Zod.
|
|
43
|
+
*
|
|
44
|
+
* ```ts
|
|
45
|
+
* const api = createApi()
|
|
46
|
+
* const data = await api.get('settings', schema)
|
|
47
|
+
* // → GET https://storefront.ecomiq.pe/v1/settings
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
declare function createApi(options?: CreateApiOptions): Api;
|
|
51
|
+
|
|
52
|
+
export { type Api, ApiError, type CreateApiOptions, DEFAULT_API_BASE_URL, DEFAULT_API_VERSION, createApi, resolveApiBaseUrl };
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Cliente HTTP de Ecomiq para servicios externos.
|
|
5
|
+
* fetch nativo (funciona en Node, navegador y Cloudflare Workers)
|
|
6
|
+
* + validación de respuestas con Zod.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
declare const DEFAULT_API_BASE_URL = "https://storefront.ecomiq.pe";
|
|
10
|
+
declare const DEFAULT_API_VERSION = "v1";
|
|
11
|
+
declare class ApiError extends Error {
|
|
12
|
+
readonly status: number;
|
|
13
|
+
readonly url: string;
|
|
14
|
+
readonly body?: unknown | undefined;
|
|
15
|
+
constructor(message: string, status: number, url: string, body?: unknown | undefined);
|
|
16
|
+
}
|
|
17
|
+
interface CreateApiOptions {
|
|
18
|
+
/** Default: https://storefront.ecomiq.pe */
|
|
19
|
+
baseUrl?: string;
|
|
20
|
+
/** Segmento de path, default 'v1' → {baseUrl}/v1/... */
|
|
21
|
+
version?: string;
|
|
22
|
+
headers?: Record<string, string>;
|
|
23
|
+
timeoutMs?: number;
|
|
24
|
+
}
|
|
25
|
+
interface RequestOptions {
|
|
26
|
+
query?: Record<string, string | number | boolean | undefined>;
|
|
27
|
+
headers?: Record<string, string>;
|
|
28
|
+
signal?: AbortSignal;
|
|
29
|
+
}
|
|
30
|
+
interface Api {
|
|
31
|
+
get<S extends z.ZodTypeAny>(path: string, schema: S, options?: RequestOptions): Promise<z.output<S>>;
|
|
32
|
+
post<S extends z.ZodTypeAny>(path: string, body: unknown, schema: S, options?: RequestOptions): Promise<z.output<S>>;
|
|
33
|
+
/** POST multipart; no fuerza `content-type: application/json`. */
|
|
34
|
+
postForm<S extends z.ZodTypeAny>(path: string, body: FormData, schema: S, options?: RequestOptions): Promise<z.output<S>>;
|
|
35
|
+
request<S extends z.ZodTypeAny>(method: string, path: string, schema: S, options?: RequestOptions & {
|
|
36
|
+
body?: unknown;
|
|
37
|
+
}): Promise<z.output<S>>;
|
|
38
|
+
}
|
|
39
|
+
/** Une baseUrl + version en la base efectiva del cliente, ej. https://storefront.ecomiq.pe/v1/ */
|
|
40
|
+
declare function resolveApiBaseUrl(baseUrl?: string, version?: string): string;
|
|
41
|
+
/**
|
|
42
|
+
* Crea un cliente HTTP tipado con Zod.
|
|
43
|
+
*
|
|
44
|
+
* ```ts
|
|
45
|
+
* const api = createApi()
|
|
46
|
+
* const data = await api.get('settings', schema)
|
|
47
|
+
* // → GET https://storefront.ecomiq.pe/v1/settings
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
declare function createApi(options?: CreateApiOptions): Api;
|
|
51
|
+
|
|
52
|
+
export { type Api, ApiError, type CreateApiOptions, DEFAULT_API_BASE_URL, DEFAULT_API_VERSION, createApi, resolveApiBaseUrl };
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// src/libs/api.ts
|
|
2
|
+
var DEFAULT_API_BASE_URL = "https://storefront.ecomiq.pe";
|
|
3
|
+
var DEFAULT_API_VERSION = "v1";
|
|
4
|
+
var ApiError = class extends Error {
|
|
5
|
+
constructor(message, status, url, body) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.status = status;
|
|
8
|
+
this.url = url;
|
|
9
|
+
this.body = body;
|
|
10
|
+
this.name = "ApiError";
|
|
11
|
+
}
|
|
12
|
+
status;
|
|
13
|
+
url;
|
|
14
|
+
body;
|
|
15
|
+
};
|
|
16
|
+
function resolveApiBaseUrl(baseUrl = DEFAULT_API_BASE_URL, version = DEFAULT_API_VERSION) {
|
|
17
|
+
const root = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
18
|
+
const segment = version.replace(/^\/+|\/+$/g, "");
|
|
19
|
+
return new URL(`${segment}/`, root).toString();
|
|
20
|
+
}
|
|
21
|
+
function createApi(options = {}) {
|
|
22
|
+
const {
|
|
23
|
+
baseUrl = DEFAULT_API_BASE_URL,
|
|
24
|
+
version = DEFAULT_API_VERSION,
|
|
25
|
+
headers: baseHeaders = {},
|
|
26
|
+
timeoutMs = 1e4
|
|
27
|
+
} = options;
|
|
28
|
+
const effectiveBaseUrl = resolveApiBaseUrl(baseUrl, version);
|
|
29
|
+
async function request(method, path, schema, opts = {}) {
|
|
30
|
+
const url = new URL(path.replace(/^\//, ""), effectiveBaseUrl);
|
|
31
|
+
for (const [key, value] of Object.entries(opts.query ?? {})) {
|
|
32
|
+
if (value !== void 0) url.searchParams.set(key, String(value));
|
|
33
|
+
}
|
|
34
|
+
const controller = new AbortController();
|
|
35
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
36
|
+
if (opts.signal) opts.signal.addEventListener("abort", () => controller.abort(), { once: true });
|
|
37
|
+
const isFormData = typeof FormData !== "undefined" && opts.body instanceof FormData;
|
|
38
|
+
let response;
|
|
39
|
+
try {
|
|
40
|
+
response = await fetch(url.toString(), {
|
|
41
|
+
method,
|
|
42
|
+
headers: {
|
|
43
|
+
accept: "application/json",
|
|
44
|
+
...opts.body !== void 0 && !isFormData ? { "content-type": "application/json" } : {},
|
|
45
|
+
...baseHeaders,
|
|
46
|
+
...opts.headers
|
|
47
|
+
},
|
|
48
|
+
body: opts.body === void 0 ? void 0 : isFormData ? opts.body : JSON.stringify(opts.body),
|
|
49
|
+
signal: controller.signal
|
|
50
|
+
});
|
|
51
|
+
} catch (err) {
|
|
52
|
+
throw new ApiError(
|
|
53
|
+
err instanceof DOMException && err.name === "AbortError" ? `Timeout tras ${timeoutMs}ms: ${url}` : `No se pudo conectar con ${url}`,
|
|
54
|
+
0,
|
|
55
|
+
url.toString()
|
|
56
|
+
);
|
|
57
|
+
} finally {
|
|
58
|
+
clearTimeout(timeout);
|
|
59
|
+
}
|
|
60
|
+
if (!response.ok) {
|
|
61
|
+
const bodyText = await response.text().catch(() => void 0);
|
|
62
|
+
let body = bodyText;
|
|
63
|
+
if (bodyText) {
|
|
64
|
+
try {
|
|
65
|
+
body = JSON.parse(bodyText);
|
|
66
|
+
} catch {
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
throw new ApiError(`${method} ${url} \u2192 ${response.status}`, response.status, url.toString(), body);
|
|
70
|
+
}
|
|
71
|
+
const json = await response.json();
|
|
72
|
+
const parsed = schema.safeParse(json);
|
|
73
|
+
if (!parsed.success) {
|
|
74
|
+
throw new ApiError(
|
|
75
|
+
`Respuesta inesperada de ${url}: ${parsed.error.issues[0]?.path.join(".")} ${parsed.error.issues[0]?.message}`,
|
|
76
|
+
response.status,
|
|
77
|
+
url.toString(),
|
|
78
|
+
json
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
return parsed.data;
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
request,
|
|
85
|
+
get: (path, schema, opts) => request("GET", path, schema, opts),
|
|
86
|
+
post: (path, body, schema, opts) => request("POST", path, schema, { ...opts, body }),
|
|
87
|
+
postForm: (path, body, schema, opts) => request("POST", path, schema, { ...opts, body })
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export { ApiError, DEFAULT_API_BASE_URL, DEFAULT_API_VERSION, createApi, resolveApiBaseUrl };
|
|
92
|
+
//# sourceMappingURL=api.js.map
|
|
93
|
+
//# sourceMappingURL=api.js.map
|
package/dist/api.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/libs/api.ts"],"names":[],"mappings":"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","file":"api.js","sourcesContent":["/**\n * Cliente HTTP de Ecomiq para servicios externos.\n * fetch nativo (funciona en Node, navegador y Cloudflare Workers)\n * + validación de respuestas con Zod.\n */\nimport type { z } from 'zod'\n\nexport const DEFAULT_API_BASE_URL = 'https://storefront.ecomiq.pe'\nexport const DEFAULT_API_VERSION = 'v1'\n\nexport class ApiError extends Error {\n constructor(\n message: string,\n public readonly status: number,\n public readonly url: string,\n public readonly body?: unknown,\n ) {\n super(message)\n this.name = 'ApiError'\n }\n}\n\nexport interface CreateApiOptions {\n /** Default: https://storefront.ecomiq.pe */\n baseUrl?: string\n /** Segmento de path, default 'v1' → {baseUrl}/v1/... */\n version?: string\n headers?: Record<string, string>\n timeoutMs?: number\n}\n\ninterface RequestOptions {\n query?: Record<string, string | number | boolean | undefined>\n headers?: Record<string, string>\n signal?: AbortSignal\n}\n\nexport interface Api {\n get<S extends z.ZodTypeAny>(\n path: string,\n schema: S,\n options?: RequestOptions,\n ): Promise<z.output<S>>\n post<S extends z.ZodTypeAny>(\n path: string,\n body: unknown,\n schema: S,\n options?: RequestOptions,\n ): Promise<z.output<S>>\n /** POST multipart; no fuerza `content-type: application/json`. */\n postForm<S extends z.ZodTypeAny>(\n path: string,\n body: FormData,\n schema: S,\n options?: RequestOptions,\n ): Promise<z.output<S>>\n request<S extends z.ZodTypeAny>(\n method: string,\n path: string,\n schema: S,\n options?: RequestOptions & { body?: unknown },\n ): Promise<z.output<S>>\n}\n\n/** Une baseUrl + version en la base efectiva del cliente, ej. https://storefront.ecomiq.pe/v1/ */\nexport function resolveApiBaseUrl(\n baseUrl: string = DEFAULT_API_BASE_URL,\n version: string = DEFAULT_API_VERSION,\n): string {\n const root = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`\n const segment = version.replace(/^\\/+|\\/+$/g, '')\n return new URL(`${segment}/`, root).toString()\n}\n\n/**\n * Crea un cliente HTTP tipado con Zod.\n *\n * ```ts\n * const api = createApi()\n * const data = await api.get('settings', schema)\n * // → GET https://storefront.ecomiq.pe/v1/settings\n * ```\n */\nexport function createApi(options: CreateApiOptions = {}): Api {\n const {\n baseUrl = DEFAULT_API_BASE_URL,\n version = DEFAULT_API_VERSION,\n headers: baseHeaders = {},\n timeoutMs = 10_000,\n } = options\n\n const effectiveBaseUrl = resolveApiBaseUrl(baseUrl, version)\n\n async function request<S extends z.ZodTypeAny>(\n method: string,\n path: string,\n schema: S,\n opts: RequestOptions & { body?: unknown } = {},\n ): Promise<z.output<S>> {\n const url = new URL(path.replace(/^\\//, ''), effectiveBaseUrl)\n for (const [key, value] of Object.entries(opts.query ?? {})) {\n if (value !== undefined) url.searchParams.set(key, String(value))\n }\n\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), timeoutMs)\n if (opts.signal) opts.signal.addEventListener('abort', () => controller.abort(), { once: true })\n\n const isFormData =\n typeof FormData !== 'undefined' && opts.body instanceof FormData\n\n let response: Response\n try {\n response = await fetch(url.toString(), {\n method,\n headers: {\n accept: 'application/json',\n ...(opts.body !== undefined && !isFormData\n ? { 'content-type': 'application/json' }\n : {}),\n ...baseHeaders,\n ...opts.headers,\n },\n body:\n opts.body === undefined\n ? undefined\n : isFormData\n ? (opts.body as FormData)\n : JSON.stringify(opts.body),\n signal: controller.signal,\n })\n } catch (err) {\n throw new ApiError(\n err instanceof DOMException && err.name === 'AbortError'\n ? `Timeout tras ${timeoutMs}ms: ${url}`\n : `No se pudo conectar con ${url}`,\n 0,\n url.toString(),\n )\n } finally {\n clearTimeout(timeout)\n }\n\n if (!response.ok) {\n const bodyText = await response.text().catch(() => undefined)\n let body: unknown = bodyText\n if (bodyText) {\n try {\n body = JSON.parse(bodyText)\n } catch {}\n }\n throw new ApiError(`${method} ${url} → ${response.status}`, response.status, url.toString(), body)\n }\n\n const json = await response.json()\n const parsed = schema.safeParse(json)\n if (!parsed.success) {\n throw new ApiError(\n `Respuesta inesperada de ${url}: ${parsed.error.issues[0]?.path.join('.')} ${parsed.error.issues[0]?.message}`,\n response.status,\n url.toString(),\n json,\n )\n }\n return parsed.data as z.output<S>\n }\n\n return {\n request,\n get: (path, schema, opts) => request('GET', path, schema, opts),\n post: (path, body, schema, opts) => request('POST', path, schema, { ...opts, body }),\n postForm: (path, body, schema, opts) =>\n request('POST', path, schema, { ...opts, body }),\n }\n}\n"]}
|