@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 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.
@@ -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
+ }
@@ -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
+ }