@hostwebhook/platform-contracts 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 +22 -0
- package/dist/addons.d.ts +68 -0
- package/dist/addons.js +81 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +28 -0
- package/package.json +25 -0
package/README.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# @hostwebhook/platform-contracts
|
|
2
|
+
|
|
3
|
+
Contratos compartidos entre los servicios de HostWebhook: lo que cruza una
|
|
4
|
+
frontera y no puede estar escrito dos veces.
|
|
5
|
+
|
|
6
|
+
Hoy contiene el registro de **addons** — los complementos que se venden aparte
|
|
7
|
+
del plan. La api decide si una petición pasa; el dashboard decide si pinta la
|
|
8
|
+
pantalla o el candado. Los dos leen de aquí.
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import { ADDONS, tieneAddon, normalizarAddons } from '@hostwebhook/platform-contracts';
|
|
12
|
+
|
|
13
|
+
tieneAddon(user.plan.addons, 'social'); // → boolean, falla cerrado
|
|
14
|
+
normalizarAddons(['social', 'social']); // → ['social']
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Qué no va aquí
|
|
18
|
+
|
|
19
|
+
- Precios e ids de Stripe: viven en la configuración de la api, que es la única
|
|
20
|
+
que habla con Stripe. Cambiarlos no puede obligar a publicar un paquete.
|
|
21
|
+
- Límites por plan: `plan.constants.ts` en la api.
|
|
22
|
+
- Cualquier cosa que sólo lea un servicio.
|
package/dist/addons.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Los complementos que se venden aparte del plan.
|
|
3
|
+
*
|
|
4
|
+
* ## Por qué esto vive en un paquete y no en la api
|
|
5
|
+
*
|
|
6
|
+
* Porque lo leen los dos lados. La api decide si una petición pasa; el
|
|
7
|
+
* dashboard decide si pinta la pantalla o el candado. Si cada uno escribe la
|
|
8
|
+
* cadena `'social'` por su cuenta, un día uno de los dos la escribe distinta y
|
|
9
|
+
* el síntoma es una pantalla que se ve pero no carga — o peor, una que no se
|
|
10
|
+
* ve aunque esté pagada.
|
|
11
|
+
*
|
|
12
|
+
* Es el mismo motivo por el que `NODE_REGISTRY` vive en `@hostwebhook/node-types`
|
|
13
|
+
* y no en cada repo.
|
|
14
|
+
*
|
|
15
|
+
* ## Un addon NO es un plan
|
|
16
|
+
*
|
|
17
|
+
* `tier` (free / pro / enterprise) y `addons` son ejes distintos y a propósito.
|
|
18
|
+
* El Asset Manager y el calendario social no se compran subiendo de plan: se
|
|
19
|
+
* compran aparte, y quien está en `free` puede tenerlos. `plan.constants.ts`
|
|
20
|
+
* de la api ya lo dice desde que `maxStorageMB` vale lo mismo en los tres
|
|
21
|
+
* planes.
|
|
22
|
+
*/
|
|
23
|
+
/** La clave de cada complemento. Es la que viaja en `plan.addons`. */
|
|
24
|
+
export declare const ADDON_KEYS: readonly ["social"];
|
|
25
|
+
export type AddonKey = (typeof ADDON_KEYS)[number];
|
|
26
|
+
export interface AddonDef {
|
|
27
|
+
key: AddonKey;
|
|
28
|
+
/** Lo que ve quien lo compra. */
|
|
29
|
+
label: string;
|
|
30
|
+
/**
|
|
31
|
+
* Qué desbloquea, en una línea y en el idioma del usuario, no en el del
|
|
32
|
+
* código. Sale en la pantalla de venta cuando la puerta está cerrada.
|
|
33
|
+
*/
|
|
34
|
+
descripcion: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* El registro. Añadir un complemento es añadir aquí y en `ADDON_KEYS`.
|
|
38
|
+
*
|
|
39
|
+
* ⚠️ Lo que NO va aquí es el precio ni el id de Stripe: eso vive en la
|
|
40
|
+
* configuración de la api, que es la única que habla con Stripe, y cambiarlo no
|
|
41
|
+
* puede obligar a publicar un paquete y subir la dependencia en dos repos.
|
|
42
|
+
*/
|
|
43
|
+
export declare const ADDONS: Record<AddonKey, AddonDef>;
|
|
44
|
+
/** ¿Es esta cadena un complemento que existe? Guarda de tipo. */
|
|
45
|
+
export declare function esAddonConocido(valor: unknown): valor is AddonKey;
|
|
46
|
+
/**
|
|
47
|
+
* ¿Esta cuenta tiene el complemento?
|
|
48
|
+
*
|
|
49
|
+
* ⚠️ **Falla cerrado a propósito.** Sin lista, o con una lista que no es un
|
|
50
|
+
* array, la respuesta es `false` y no `true`. La tentación contraria —«si no
|
|
51
|
+
* puedo leerlo, que pase»— es la que regala el producto cuando una consulta
|
|
52
|
+
* falla, y es exactamente el fail-open que ya hubo que quitar de
|
|
53
|
+
* `topeDeSubida`.
|
|
54
|
+
*
|
|
55
|
+
* Se acepta `unknown` porque quien llama suele traerlo de un documento de
|
|
56
|
+
* Mongo o de un JSON de la api, y ahí el tipo es una promesa, no un hecho.
|
|
57
|
+
*/
|
|
58
|
+
export declare function tieneAddon(addons: unknown, cual: AddonKey): boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Deja una lista de complementos en su forma canónica: sólo claves conocidas,
|
|
61
|
+
* sin repetidos y en el orden de `ADDON_KEYS`.
|
|
62
|
+
*
|
|
63
|
+
* Existe porque la lista se construye desde las líneas de una suscripción de
|
|
64
|
+
* Stripe, y de ahí puede llegar con duplicados, con una clave que ya se retiró,
|
|
65
|
+
* o en el orden en que el webhook las mandó. Guardar eso tal cual hace que dos
|
|
66
|
+
* cuentas con lo mismo comprado se vean distintas en la base de datos.
|
|
67
|
+
*/
|
|
68
|
+
export declare function normalizarAddons(addons: unknown): AddonKey[];
|
package/dist/addons.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Los complementos que se venden aparte del plan.
|
|
4
|
+
*
|
|
5
|
+
* ## Por qué esto vive en un paquete y no en la api
|
|
6
|
+
*
|
|
7
|
+
* Porque lo leen los dos lados. La api decide si una petición pasa; el
|
|
8
|
+
* dashboard decide si pinta la pantalla o el candado. Si cada uno escribe la
|
|
9
|
+
* cadena `'social'` por su cuenta, un día uno de los dos la escribe distinta y
|
|
10
|
+
* el síntoma es una pantalla que se ve pero no carga — o peor, una que no se
|
|
11
|
+
* ve aunque esté pagada.
|
|
12
|
+
*
|
|
13
|
+
* Es el mismo motivo por el que `NODE_REGISTRY` vive en `@hostwebhook/node-types`
|
|
14
|
+
* y no en cada repo.
|
|
15
|
+
*
|
|
16
|
+
* ## Un addon NO es un plan
|
|
17
|
+
*
|
|
18
|
+
* `tier` (free / pro / enterprise) y `addons` son ejes distintos y a propósito.
|
|
19
|
+
* El Asset Manager y el calendario social no se compran subiendo de plan: se
|
|
20
|
+
* compran aparte, y quien está en `free` puede tenerlos. `plan.constants.ts`
|
|
21
|
+
* de la api ya lo dice desde que `maxStorageMB` vale lo mismo en los tres
|
|
22
|
+
* planes.
|
|
23
|
+
*/
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
exports.ADDONS = exports.ADDON_KEYS = void 0;
|
|
26
|
+
exports.esAddonConocido = esAddonConocido;
|
|
27
|
+
exports.tieneAddon = tieneAddon;
|
|
28
|
+
exports.normalizarAddons = normalizarAddons;
|
|
29
|
+
/** La clave de cada complemento. Es la que viaja en `plan.addons`. */
|
|
30
|
+
exports.ADDON_KEYS = ['social'];
|
|
31
|
+
/**
|
|
32
|
+
* El registro. Añadir un complemento es añadir aquí y en `ADDON_KEYS`.
|
|
33
|
+
*
|
|
34
|
+
* ⚠️ Lo que NO va aquí es el precio ni el id de Stripe: eso vive en la
|
|
35
|
+
* configuración de la api, que es la única que habla con Stripe, y cambiarlo no
|
|
36
|
+
* puede obligar a publicar un paquete y subir la dependencia en dos repos.
|
|
37
|
+
*/
|
|
38
|
+
exports.ADDONS = {
|
|
39
|
+
social: {
|
|
40
|
+
key: 'social',
|
|
41
|
+
label: 'Social',
|
|
42
|
+
descripcion: 'Publishing calendar, media library, and the Calendar tab inside the Social node.',
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
/** ¿Es esta cadena un complemento que existe? Guarda de tipo. */
|
|
46
|
+
function esAddonConocido(valor) {
|
|
47
|
+
return typeof valor === 'string' && exports.ADDON_KEYS.includes(valor);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* ¿Esta cuenta tiene el complemento?
|
|
51
|
+
*
|
|
52
|
+
* ⚠️ **Falla cerrado a propósito.** Sin lista, o con una lista que no es un
|
|
53
|
+
* array, la respuesta es `false` y no `true`. La tentación contraria —«si no
|
|
54
|
+
* puedo leerlo, que pase»— es la que regala el producto cuando una consulta
|
|
55
|
+
* falla, y es exactamente el fail-open que ya hubo que quitar de
|
|
56
|
+
* `topeDeSubida`.
|
|
57
|
+
*
|
|
58
|
+
* Se acepta `unknown` porque quien llama suele traerlo de un documento de
|
|
59
|
+
* Mongo o de un JSON de la api, y ahí el tipo es una promesa, no un hecho.
|
|
60
|
+
*/
|
|
61
|
+
function tieneAddon(addons, cual) {
|
|
62
|
+
return Array.isArray(addons) && addons.includes(cual);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Deja una lista de complementos en su forma canónica: sólo claves conocidas,
|
|
66
|
+
* sin repetidos y en el orden de `ADDON_KEYS`.
|
|
67
|
+
*
|
|
68
|
+
* Existe porque la lista se construye desde las líneas de una suscripción de
|
|
69
|
+
* Stripe, y de ahí puede llegar con duplicados, con una clave que ya se retiró,
|
|
70
|
+
* o en el orden en que el webhook las mandó. Guardar eso tal cual hace que dos
|
|
71
|
+
* cuentas con lo mismo comprado se vean distintas en la base de datos.
|
|
72
|
+
*/
|
|
73
|
+
function normalizarAddons(addons) {
|
|
74
|
+
if (!Array.isArray(addons))
|
|
75
|
+
return [];
|
|
76
|
+
const vistos = new Set();
|
|
77
|
+
for (const a of addons)
|
|
78
|
+
if (esAddonConocido(a))
|
|
79
|
+
vistos.add(a);
|
|
80
|
+
return exports.ADDON_KEYS.filter((k) => vistos.has(k));
|
|
81
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contratos compartidos entre los servicios de HostWebhook.
|
|
3
|
+
*
|
|
4
|
+
* Lo que entra aquí es lo que **cruza una frontera** —entre la api y el
|
|
5
|
+
* dashboard hoy, y entre servicios cuando se parta el monolito— y que si se
|
|
6
|
+
* escribe dos veces, un día se escribe distinto.
|
|
7
|
+
*
|
|
8
|
+
* Lo que NO entra: nada específico de un servicio, nada con secretos, y nada
|
|
9
|
+
* cuyo cambio deba poder desplegarse sin publicar un paquete (precios, ids de
|
|
10
|
+
* Stripe, límites por plan).
|
|
11
|
+
*/
|
|
12
|
+
export * from './addons';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
/**
|
|
18
|
+
* Contratos compartidos entre los servicios de HostWebhook.
|
|
19
|
+
*
|
|
20
|
+
* Lo que entra aquí es lo que **cruza una frontera** —entre la api y el
|
|
21
|
+
* dashboard hoy, y entre servicios cuando se parta el monolito— y que si se
|
|
22
|
+
* escribe dos veces, un día se escribe distinto.
|
|
23
|
+
*
|
|
24
|
+
* Lo que NO entra: nada específico de un servicio, nada con secretos, y nada
|
|
25
|
+
* cuyo cambio deba poder desplegarse sin publicar un paquete (precios, ids de
|
|
26
|
+
* Stripe, límites por plan).
|
|
27
|
+
*/
|
|
28
|
+
__exportStar(require("./addons"), exports);
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hostwebhook/platform-contracts",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Contratos compartidos entre los servicios de HostWebhook: addons del plan, identidad interna, y las formas que cruzan una frontera de red",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"test": "vitest run",
|
|
13
|
+
"prepublishOnly": "npm run build"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"hostwebhook",
|
|
17
|
+
"contracts",
|
|
18
|
+
"addons"
|
|
19
|
+
],
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"typescript": "^5.0.0",
|
|
23
|
+
"vitest": "^3.0.0"
|
|
24
|
+
}
|
|
25
|
+
}
|