@ressjs/platform 0.6.0-experimental.0 → 0.6.0-rc.1

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/LICENSE ADDED
@@ -0,0 +1,69 @@
1
+ ress.js Proprietary License
2
+
3
+ Copyright (c) 2025-2026 gabrielspisso. All rights reserved.
4
+
5
+ This license applies to the ress.js packages (the "Software"), published on npm
6
+ under the @ressjs and @gzzy scopes, in every version released from 0.6.0-rc.0
7
+ onwards. Earlier versions were released under the MIT License and remain
8
+ available under those terms.
9
+
10
+ 1. GRANT
11
+ Subject to this license, the Licensor grants you a worldwide, non-exclusive,
12
+ non-transferable, non-sublicensable, royalty-free license to install and use
13
+ the Software, as distributed by the Licensor, to build, run and deploy your
14
+ own applications and services, including commercial ones, and to charge your
15
+ own customers for them.
16
+
17
+ 2. RESTRICTIONS
18
+ You may not:
19
+ a) fork, copy, or redistribute the Software, in whole or in part, except for
20
+ the portions that are necessarily included in the build output of your own
21
+ application in order to run it;
22
+ b) publish, distribute or make available a modified version of the Software
23
+ or any work derived from it. Modifications for your own internal use are
24
+ allowed, but they remain subject to this license;
25
+ c) use the Software, or any part of it, to build, offer or support a
26
+ framework, library, tool or service that competes with the Software;
27
+ d) sell, sublicense, rent or host the Software itself as a product or service
28
+ for third parties, as opposed to hosting your own application built with
29
+ it;
30
+ e) remove or alter copyright, license or attribution notices;
31
+ f) use the name "ress.js" or the Licensor's names or marks to endorse or
32
+ promote your products without written permission.
33
+
34
+ 3. SCAFFOLDING OUTPUT
35
+ Files that @ressjs/create-ress-app copies into your project (templates and
36
+ generated configuration) are yours: you may use, modify and distribute them
37
+ without restriction. This does not extend to the Software packages that the
38
+ generated project installs as dependencies.
39
+
40
+ 4. SUPPORT
41
+ The Software is provided without any obligation of support, maintenance or
42
+ updates. The Licensor may offer support, maintenance, training or consulting
43
+ under a separate paid agreement; that agreement does not change this license
44
+ unless it says so explicitly and is signed by the Licensor.
45
+
46
+ 5. OWNERSHIP
47
+ The Software is licensed, not sold. The Licensor retains all rights not
48
+ expressly granted here. Feedback and suggestions you send may be used by the
49
+ Licensor without obligation to you.
50
+
51
+ 6. TERMINATION
52
+ This license ends automatically if you breach it. On termination you must
53
+ stop using the Software and delete your copies. Sections 4 to 8 survive
54
+ termination.
55
+
56
+ 7. NO WARRANTY
57
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
58
+ IMPLIED, INCLUDING THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
59
+ PARTICULAR PURPOSE AND NON-INFRINGEMENT.
60
+
61
+ 8. LIMITATION OF LIABILITY
62
+ TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE LICENSOR WILL NOT BE LIABLE FOR
63
+ ANY INDIRECT, INCIDENTAL, SPECIAL OR CONSEQUENTIAL DAMAGES, OR FOR LOSS OF
64
+ PROFITS, DATA OR BUSINESS, ARISING FROM THE USE OF OR INABILITY TO USE THE
65
+ SOFTWARE. THE LICENSOR'S TOTAL LIABILITY IS LIMITED TO THE AMOUNT YOU PAID
66
+ FOR THE SOFTWARE, WHICH MAY BE ZERO.
67
+
68
+ For other uses, including exceptions to section 2, contact the Licensor to
69
+ agree a separate license.
package/README.md CHANGED
@@ -1,95 +1,23 @@
1
1
  # @ressjs/platform
2
2
 
3
- Responde una sola pregunta: **qué es el cliente que está del otro lado**.
3
+ Detección de la plataforma del cliente (sistema operativo, tipo de dispositivo y si corre dentro de un WebView) para ress.js.
4
4
 
5
- La responden igual el router, el puente del WebView, el cliente y cualquier
6
- consumidor externo. Por eso vive en su propio paquete y no depende de nada.
5
+ > Pre-release (`0.6.0-rc`). La API puede cambiar antes de `0.6.0`.
7
6
 
8
- ```ts
9
- import { detect } from '@ressjs/platform'
7
+ ## Instalación
10
8
 
11
- const platform = detect({ userAgent, headers })
12
- // { webview: false, os: 'tizen', device: 'tv', ... }
9
+ ```bash
10
+ npm install @ressjs/platform
13
11
  ```
14
12
 
15
- ## El modelo
16
-
17
- Tres ejes ortogonales. Cualquier combinación es expresable.
18
-
19
- | Eje | Valores | Qué describe |
20
- |---|---|---|
21
- | `context` | `webview` | la página está incrustada dentro de una app; su ausencia es un navegador |
22
- | `os` | `ios` `android` `tizen` `webos` `windows` `macos` `linux` | sistema operativo |
23
- | `device` | `phone` `tablet` `tv` `desktop` | forma del aparato y modo de operación |
24
-
25
- Los datos de la app que embebe la página —cuál es, en qué versión, sobre qué
26
- versión del sistema— llegan en `nativeApp` y **nunca** eligen qué archivo servir.
27
-
28
- ## Cómo se decide
29
-
30
- Seis capas, de lo que el cliente afirma a lo que se adivina. Cada una aporta
31
- sólo los campos que puede determinar, y un campo ya resuelto no se sobrescribe.
32
- Ninguna capa devuelve un resultado completo ni corta la evaluación: por eso
33
- ninguna puede volver inalcanzable a la siguiente.
34
-
35
- 1. `override` — forzado para depurar, apagado salvo que se habilite
36
- 2. `explicit-headers` — el contrato v2
37
- 3. `legacy-headers` — el contrato v1 de los clientes ya publicados
38
- 4. `client-hints` — lo que el navegador manda por su cuenta
39
- 5. `user-agent` — la tabla de reglas
40
- 6. `default` — lo que quede sin resolver
41
-
42
- Cada campo del resultado declara de qué capa salió y con cuánta confianza:
13
+ ## Uso
43
14
 
44
15
  ```ts
45
- platform.detection.fields.device
46
- // { layer: 'user-agent', confidence: 'high', rule: 'ua:android-tv' }
47
- ```
48
-
49
- Un `webview: false` que nadie afirmó se distingue así de uno detectado.
50
-
51
- ## Cabeceras
52
-
53
- | Cabecera | Aporta |
54
- |---|---|
55
- | `x-ressjs-webview` | `webview` |
56
- | `x-ressjs-os` | `os` |
57
- | `x-ressjs-device` | `device` |
58
- | `x-ressjs-app-id` | `nativeApp.id`, e implica `webview: true` |
59
- | `x-ressjs-app-version` | `nativeApp.version` |
60
- | `x-ressjs-os-version` | `nativeApp.osVersion` |
61
- | `x-ressjs-capabilities` | `capabilities`, separadas por comas |
62
-
63
- Las del contrato anterior —`x-ressjs-platform`, `x-webview`, `x-rn-platform`,
64
- `x-ressjs-version`— se siguen aceptando y se traducen.
65
-
66
- ## Agregar una plataforma
67
-
68
- Es un dato, no un cambio de código:
16
+ import { detect } from '@ressjs/platform'
69
17
 
70
- ```ts
71
- const registry = createPlatformRegistry({
72
- values: [{ token: 'kaios', axis: 'os' }],
73
- detect: [
74
- {
75
- name: 'ua:kaios',
76
- test: /KAIOS/i,
77
- set: { os: 'kaios', device: 'phone' },
78
- confidence: 'high',
79
- priority: 150,
80
- },
81
- ],
82
- })
18
+ const platform = detect({ userAgent, headers })
83
19
  ```
84
20
 
85
- A partir de ahí `index.kaios.scss` es una variante válida.
86
-
87
- ## Desde el navegador
88
-
89
- ```ts
90
- import { getPlatform } from '@ressjs/platform/client'
91
- ```
21
+ ## Licencia
92
22
 
93
- Lee lo que el servidor serializó en `window.__RESS_PLATFORM__`, de modo que
94
- cliente y servidor coinciden exactamente. Sólo detecta por su cuenta si no hay
95
- nada serializado.
23
+ Proprietary. See [LICENSE](./LICENSE).
package/dist/index.d.ts CHANGED
@@ -11,7 +11,7 @@ export { detectPlatform } from './compat';
11
11
  export { DETECTION_LAYERS } from './detect/layers';
12
12
  export type { DetectionInput, DetectionLayer } from './detect/layers';
13
13
  export { createPlatformRegistry, defaultPlatformRegistry } from './registry/create';
14
- export { parseVariantSuffix } from './registry/parse';
14
+ export { parseVariantSuffix, PlatformTokenError } from './registry/parse';
15
15
  export { ALL_AXES, DEFAULT_AXIS_PRECEDENCE, DEFAULT_VALUES, RETIRED_TOKENS, } from './registry/defaults';
16
16
  export { DEFAULT_RULES } from './rules/user-agent';
17
17
  export { EDGE_PRESETS, readEdgeHeaders } from './rules/edge';
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@
8
8
  * de nada.
9
9
  */
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
- exports.serializePlatform = exports.platformScriptTag = exports.isWebView = exports.isTV = exports.isHandheld = exports.hasCapability = exports.DEFAULT_PLATFORM = exports.varyHeadersForAxes = exports.HEADERS_BY_AXIS = exports.PLATFORM_VARY_HEADERS = exports.PLATFORM_REQUEST_HEADERS = exports.HEADERS_V2 = exports.HEADERS_LEGACY = exports.HEADERS_CLIENT_HINTS = exports.HEADER_OVERRIDE = exports.readEdgeHeaders = exports.EDGE_PRESETS = exports.DEFAULT_RULES = exports.RETIRED_TOKENS = exports.DEFAULT_VALUES = exports.DEFAULT_AXIS_PRECEDENCE = exports.ALL_AXES = exports.parseVariantSuffix = exports.defaultPlatformRegistry = exports.createPlatformRegistry = exports.DETECTION_LAYERS = exports.detectPlatform = exports.detect = void 0;
11
+ exports.serializePlatform = exports.platformScriptTag = exports.isWebView = exports.isTV = exports.isHandheld = exports.hasCapability = exports.DEFAULT_PLATFORM = exports.varyHeadersForAxes = exports.HEADERS_BY_AXIS = exports.PLATFORM_VARY_HEADERS = exports.PLATFORM_REQUEST_HEADERS = exports.HEADERS_V2 = exports.HEADERS_LEGACY = exports.HEADERS_CLIENT_HINTS = exports.HEADER_OVERRIDE = exports.readEdgeHeaders = exports.EDGE_PRESETS = exports.DEFAULT_RULES = exports.RETIRED_TOKENS = exports.DEFAULT_VALUES = exports.DEFAULT_AXIS_PRECEDENCE = exports.ALL_AXES = exports.PlatformTokenError = exports.parseVariantSuffix = exports.defaultPlatformRegistry = exports.createPlatformRegistry = exports.DETECTION_LAYERS = exports.detectPlatform = exports.detect = void 0;
12
12
  var pipeline_1 = require("./detect/pipeline");
13
13
  Object.defineProperty(exports, "detect", { enumerable: true, get: function () { return pipeline_1.detect; } });
14
14
  var compat_1 = require("./compat");
@@ -20,6 +20,7 @@ Object.defineProperty(exports, "createPlatformRegistry", { enumerable: true, get
20
20
  Object.defineProperty(exports, "defaultPlatformRegistry", { enumerable: true, get: function () { return create_1.defaultPlatformRegistry; } });
21
21
  var parse_1 = require("./registry/parse");
22
22
  Object.defineProperty(exports, "parseVariantSuffix", { enumerable: true, get: function () { return parse_1.parseVariantSuffix; } });
23
+ Object.defineProperty(exports, "PlatformTokenError", { enumerable: true, get: function () { return parse_1.PlatformTokenError; } });
23
24
  var defaults_1 = require("./registry/defaults");
24
25
  Object.defineProperty(exports, "ALL_AXES", { enumerable: true, get: function () { return defaults_1.ALL_AXES; } });
25
26
  Object.defineProperty(exports, "DEFAULT_AXIS_PRECEDENCE", { enumerable: true, get: function () { return defaults_1.DEFAULT_AXIS_PRECEDENCE; } });
@@ -1,4 +1,32 @@
1
1
  import type { PlatformRegistry, VariantTag } from './types';
2
+ /**
3
+ * Un token de plataforma del nombre de un archivo no se pudo clasificar.
4
+ *
5
+ * Lleva los datos además del texto, para que quien lo muestre (la CLI) pueda
6
+ * darle el formato de sus demás errores sin interpretar el mensaje.
7
+ */
8
+ export declare class PlatformTokenError extends Error {
9
+ /** Estable: lo reconocen quienes no pueden importar esta clase. */
10
+ readonly code: "RESS_PLATFORM_TOKEN";
11
+ readonly kind: 'retired' | 'unknown';
12
+ readonly token: string;
13
+ readonly origin?: string;
14
+ /** Sólo `retired`: con qué se reemplaza y por qué se retiró. */
15
+ readonly replacement?: string;
16
+ readonly reason?: string;
17
+ /** Sólo `unknown`: el token válido más parecido. */
18
+ readonly suggestion?: string;
19
+ readonly valid: readonly string[];
20
+ constructor(message: string, details: {
21
+ kind: 'retired' | 'unknown';
22
+ token: string;
23
+ origin?: string;
24
+ replacement?: string;
25
+ reason?: string;
26
+ suggestion?: string;
27
+ valid: readonly string[];
28
+ });
29
+ }
2
30
  /**
3
31
  * Convierte el sufijo de un archivo de variante en tags clasificados.
4
32
  *
@@ -1,6 +1,38 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PlatformTokenError = void 0;
3
4
  exports.parseVariantSuffix = parseVariantSuffix;
5
+ /**
6
+ * Un token de plataforma del nombre de un archivo no se pudo clasificar.
7
+ *
8
+ * Lleva los datos además del texto, para que quien lo muestre (la CLI) pueda
9
+ * darle el formato de sus demás errores sin interpretar el mensaje.
10
+ */
11
+ class PlatformTokenError extends Error {
12
+ /** Estable: lo reconocen quienes no pueden importar esta clase. */
13
+ code = 'RESS_PLATFORM_TOKEN';
14
+ kind;
15
+ token;
16
+ origin;
17
+ /** Sólo `retired`: con qué se reemplaza y por qué se retiró. */
18
+ replacement;
19
+ reason;
20
+ /** Sólo `unknown`: el token válido más parecido. */
21
+ suggestion;
22
+ valid;
23
+ constructor(message, details) {
24
+ super(message);
25
+ this.name = 'PlatformTokenError';
26
+ this.kind = details.kind;
27
+ this.token = details.token;
28
+ this.origin = details.origin;
29
+ this.replacement = details.replacement;
30
+ this.reason = details.reason;
31
+ this.suggestion = details.suggestion;
32
+ this.valid = details.valid;
33
+ }
34
+ }
35
+ exports.PlatformTokenError = PlatformTokenError;
4
36
  /**
5
37
  * Convierte el sufijo de un archivo de variante en tags clasificados.
6
38
  *
@@ -40,17 +72,17 @@ function parseVariantSuffix(suffix, registry, origin) {
40
72
  * desconocido con la lista de válidos y la sugerencia más cercana.
41
73
  */
42
74
  function unknownToken(token, registry, origin) {
75
+ const valid = allTokens(registry);
43
76
  const retired = registry.retired(token);
44
77
  if (retired) {
45
- return new Error(`[ress] El token de plataforma "${token}"${where(origin)} ya no existe. ` +
78
+ return new PlatformTokenError(`[ress] El token de plataforma "${token}"${where(origin)} ya no existe. ` +
46
79
  `Usá ${retired.replacement}.\n` +
47
- `Se retiró porque ${retired.reason}.`);
80
+ `Se retiró porque ${retired.reason}.`, { kind: 'retired', token, origin, replacement: retired.replacement, reason: retired.reason, valid });
48
81
  }
49
- const valid = allTokens(registry);
50
82
  const suggestion = closest(token, valid);
51
- return new Error(`[ress] Token de plataforma desconocido: "${token}"${where(origin)}.` +
83
+ return new PlatformTokenError(`[ress] Token de plataforma desconocido: "${token}"${where(origin)}.` +
52
84
  (suggestion ? ` ¿Quisiste decir "${suggestion}"?` : '') +
53
- `\nTokens válidos: ${valid.join(', ')}.`);
85
+ `\nTokens válidos: ${valid.join(', ')}.`, { kind: 'unknown', token, origin, suggestion, valid });
54
86
  }
55
87
  const where = (origin) => (origin ? ` en "${origin}"` : '');
56
88
  function allTokens(registry) {
@@ -43,12 +43,13 @@ export declare const HEADERS_BY_AXIS: {
43
43
  /**
44
44
  * Las cabeceras a declarar para una respuesta que varía por estos ejes.
45
45
  *
46
- * `User-Agent` afecta a los tres y se lista primero: es el de mayor cardinalidad
47
- * y el que conviene normalizar en el borde. Sin ejes, la lista es vacía — y una
48
- * respuesta que no varía por nada no debe declarar `Vary`.
46
+ * Nunca incluye `User-Agent`: la detección lo usa para resolver (R-10), pero
47
+ * tiene tantos valores que una caché que separe por él casi nunca acierta. No
48
+ * declararlo es una política: lo que depende de la plataforma no se declara
49
+ * compartible (`security/headers.ts` en vite-router). Sin ejes, la lista es
50
+ * vacía — y una respuesta que no varía por nada no debe declarar `Vary`.
49
51
  */
50
52
  export declare function varyHeadersForAxes(axes: readonly ('context' | 'os' | 'device')[], options?: {
51
- includeUserAgent?: boolean;
52
53
  /**
53
54
  * Cabeceras que el proyecto declaró para su red de distribución. Se suman
54
55
  * porque también cambian la respuesta; si no declaró ninguna, no aparece
@@ -91,16 +91,16 @@ exports.HEADERS_BY_AXIS = {
91
91
  /**
92
92
  * Las cabeceras a declarar para una respuesta que varía por estos ejes.
93
93
  *
94
- * `User-Agent` afecta a los tres y se lista primero: es el de mayor cardinalidad
95
- * y el que conviene normalizar en el borde. Sin ejes, la lista es vacía — y una
96
- * respuesta que no varía por nada no debe declarar `Vary`.
94
+ * Nunca incluye `User-Agent`: la detección lo usa para resolver (R-10), pero
95
+ * tiene tantos valores que una caché que separe por él casi nunca acierta. No
96
+ * declararlo es una política: lo que depende de la plataforma no se declara
97
+ * compartible (`security/headers.ts` en vite-router). Sin ejes, la lista es
98
+ * vacía — y una respuesta que no varía por nada no debe declarar `Vary`.
97
99
  */
98
100
  function varyHeadersForAxes(axes, options = {}) {
99
101
  if (axes.length === 0)
100
102
  return [];
101
103
  const fields = new Set();
102
- if (options.includeUserAgent ?? true)
103
- fields.add('User-Agent');
104
104
  for (const axis of axes) {
105
105
  for (const header of exports.HEADERS_BY_AXIS[axis])
106
106
  fields.add(header);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ressjs/platform",
3
- "version": "0.6.0-experimental.0",
3
+ "version": "0.6.0-rc.1",
4
4
  "description": "Modelo de plataforma de ress.js: registro de tokens y detección del cliente. Sin dependencias.",
5
5
  "types": "dist/index.d.ts",
6
6
  "main": "dist/index.js",
@@ -40,7 +40,7 @@
40
40
  "tv",
41
41
  "ressjs"
42
42
  ],
43
- "license": "MIT",
43
+ "license": "SEE LICENSE IN LICENSE",
44
44
  "devDependencies": {
45
45
  "typescript": "^5.9.3",
46
46
  "vitest": "^3.2.4"