docs / Biblioteca TypeScriptTypeScript library
@mini-format/core — biblioteca TypeScript de .mini#
Versión de software 1.1.0. Implementación TypeScript modular y tipada del núcleo
.mini (SPEC 1.0):
parser, serializador, bloque de especificación para prompts, registro de familias
(forks) y una API de lectura en streaming para respuestas de modelos token a token.
Sin dependencias.
Incluye las catorce familias del núcleo. Los contratos adaptados a muestras JSON
por mini build usan otro perfil, mini-domain/1, y el parser Python que se genera
con ellos; esta biblioteca no interpreta ese perfil. Consulta la
guía del constructor y la
especificación del perfil generado.
Autores: A. E. J. Palma Obispo, E. J. Palomino Santa Cruz (UPC). Licencia MIT.
Instalación del paquete#
Necesitas Node.js ≥ 22.6. Descarga mini-format-core-1.1.0.tgz desde Descargas
o extrae ese archivo del ZIP del toolkit. Instala el archivo local:
npm install --offline --ignore-scripts --no-audit --no-fund ./mini-format-core-1.1.0.tgz
El archivo contiene JavaScript ESM compilado en dist/, los contratos de las
catorce familias y fuentes TypeScript para tipos. Se importa desde
@mini-format/core: no necesita ejecutar TypeScript dentro de node_modules,
usar --experimental-strip-types ni conectarse al registro npm.
Desarrollo desde las fuentes#
Las fuentes ts/src/ usan TypeScript borrable, sin enum, namespace ni
parámetros-propiedad, e imports con extensión .ts. Node.js ≥ 22.6 puede ejecutar
estas fuentes con --experimental-strip-types; esto es distinto del paquete
distribuido, que ya contiene JavaScript. Pruebas verificadas con Node.js 22.14.0.
Para generar ESM desde la raíz del repositorio, utiliza Node.js ≥ 22.13:
node --no-warnings tools/build_node.mjs
El script elimina tipos y convierte imports relativos .ts a .js en
ts/dist/. La distribución incluye las fuentes tipadas; este paso no genera
archivos .d.ts.
Estructura#
ts/
├── package.json
├── src/
│ ├── errors.ts MiniError (código, línea, campo), MiniValidationError, códigos E01–E21
│ ├── contract.ts tipos ContractJSON/Contract/Field, normalizeContract, firmas, checkFork
│ ├── codec.ts tokenización, escapes, comillas, división de campos y listas
│ ├── values.ts decodificación/codificación de escalares, formatNumber
│ ├── parser.ts parse, Document, LineEngine (motor por líneas), detectPrefix
│ ├── serializer.ts dumps
│ ├── prompt.ts specBlock(contract, lang)
│ ├── registry.ts Registry (desde objetos o desde forks/ con node:fs)
│ ├── stream.ts createReader, readRecords
│ └── index.ts API pública
└── test/
├── fixtures.test.ts paridad con forks/*/fixtures y reglas de validación
├── roundtrip.test.ts parse(dumps(obj)) == obj, codec, formato numérico
├── stream.test.ts fragmentos aleatorios de 1–7 caracteres == parse
├── truncation.test.ts salidas truncadas
├── conformance.test.ts runner de ../conformance/ (se omite si no existe)
└── helpers.ts
Uso#
import { Registry, parse, dumps, specBlock, createReader } from '@mini-format/core';
const reg = Registry.load(); // carga las familias incluidas en el paquete
const a = reg.get('a');
const doc = parse(texto, a); // estricto: lanza MiniValidationError con todos los errores
doc.records; // registros válidos
doc.toCanonical(); // { prefix, header, items: [...] }
const lenient = parse(texto, a, { strict: false });
lenient.errors; // MiniError[] con code, line, field, message
lenient.invalidLines(); // líneas rechazadas (las que hay que regenerar)
lenient.missingRecords; // n − líneas de registro (mínimo 0)
lenient.diagnostics(); // informe con las mismas claves que la referencia Python
const texto2 = dumps(doc.toCanonical(), a); // ida y vuelta exacta
const prompt = specBlock(a, 'es'); // bloque para el prompt de sistema
Los contratos pueden pasarse como objeto contract.json o ya normalizados
(normalizeContract); todas las funciones aceptan ambos.
Para ejecutar el ejemplo directamente desde ts/ durante el desarrollo,
cambia el import por ./src/index.ts y activa --experimental-strip-types.
Streaming#
const reader = createReader(a, { // strict: false por defecto
onRecord: ({ record, line, index }) => mostrar(record),
onError: (e) => console.warn(String(e)),
});
for await (const token of respuestaDelModelo) reader.push(token); // string o Uint8Array
const res = reader.end();
res.document; // idéntico a parse(textoCompleto, a, { strict: false })
res.expected; // n declarado en la cabecera
res.received; // líneas de registro recibidas
res.missing; // registros que faltan respecto de n
res.incomplete; // última línea sin LF que no validó (texto, línea, errores) o null
res.truncated; // incompleto o menos líneas que n
res.terminated; // el flujo terminó en LF
Un registro se emite en cuanto su línea se cierra con LF. readRecords(iterable, contrato)
envuelve lo mismo como generador asíncrono. El lector usa el mismo motor por
líneas que parse, por lo que el resultado final es idéntico sin importar cómo
se fragmente la entrada.
Nota: si la última línea no termina en LF y su último campo sigue siendo válido
(por ejemplo, un texto cortado), el registro es indistinguible de uno completo;
en ese caso terminated es false y conviene tratarlo como sospechoso.
Pruebas y tipos#
cd ts
npm test
# o bien
node --experimental-strip-types --no-warnings --test test/*.test.ts
El chequeo estricto de la API pública y sus módulos importados pasa con TypeScript 7.0.2. Desde la raíz del repositorio:
npx --yes --package typescript@7.0.2 tsc --noEmit --strict --module NodeNext --moduleResolution NodeNext --target ES2022 --allowImportingTsExtensions ts/src/index.ts
Este comando puede descargar el compilador si no está en la caché; la instalación
y ejecución del .tgz no lo requieren. El borrado de tipos de Node no sustituye
este chequeo.
La suite de conformidad compartida se lee de ../conformance/cases/ (o de la ruta en
MINI_CONFORMANCE_DIR); si la carpeta no existe, la suite se omite. El runner
reproduce la lógica de conformance/run_python.py. Única correspondencia de API:
en modo tolerante la referencia lanza excepción cuando el documento no se puede
construir (E01); TS devuelve un Document sin cabecera con E01, y el runner lo
trata como «no construido».
Como en la referencia, los escapes se validan también en modo tolerante (E09 y
el registro se descarta), un escape inválido en la cabecera es E09 dentro de la
validación y una línea rechazada no reserva su valor unique.
Decisiones donde js/mini.js, la referencia Python y la SPEC difieren#
Criterio: si la SPEC decide, se sigue la SPEC; si no, se sigue la referencia Python.
| Tema | Python | JS | TS |
|---|---|---|---|
| Documento vacío en modo tolerante | lanza siempre | devuelve documento sin canonical() |
devuelve Document con E01 (SPEC §8) |
| Escape inválido en la cabecera | E09 dentro de la validación (corregido) | MiniError crudo fuera de parse |
E09 dentro de la validación |
| Escape inválido en un registro, modo tolerante | E09 y registro descartado (corregido) | conserva el texto literal | E09 y registro descartado |
Línea rechazada con valor unique |
no lo reserva (corregido) | lo reserva | no lo reserva |
\n final en un entero/flotante (5\n) |
acepta ($ de re + int()) |
rechaza | rechaza, E06 (SPEC: -?[0-9]+) |
| Dígitos Unicode en números | acepta (\d Unicode) |
rechaza | rechaza |
clave\=valor en cabecera (tolerante) |
clave rota | E12 | E12 (primer = no escapado, SPEC §3.2) |
| Espacio en blanco | str.isspace() |
\s de JS |
str.isspace() |
| Validación del contrato (tipos desconocidos, separador, marcador, tuplas) | estricta (E20) | laxa | estricta (E20) |
| Prefijo con letras no ASCII | acepta | rechaza | rechaza (SPEC §4) |
| Formato de flotantes (1e21, 1e-7) | expande sin exponente cuando es exacto | String(x) |
como Python |
Firma/spec de float con max: 3.0 |
3.0 |
3 |
3 (JSON.parse no distingue) |
Rango de listas en specBlock con max: 0 |
∞ |
0 |
como Python |
Claves __proto__ en cabecera/registros |
se conservan | se pierden | se conservan |
Limitaciones#
- Enteros fuera de ±2^53 pierden precisión (números de JavaScript).
Registry.loadrequiere Node (usaprocess.getBuiltinModule); el resto de la biblioteca no depende de Node.
@mini-format/core — .mini TypeScript library#
Software release 1.1.0. Modular, typed TypeScript implementation of the .mini
core notation (SPEC 1.0):
parser, serializer, specification block for prompts, family (fork) registry and a
streaming read API for token-by-token model responses. No dependencies.
Includes the fourteen core families. Contracts adapted from JSON samples by
mini build use a separate mini-domain/1 profile and their generated Python
parser; this library does not interpret that profile. See the
builder guide and
generated profile specification.
Authors: A. E. J. Palma Obispo, E. J. Palomino Santa Cruz (UPC). MIT license.
Install the package#
Requires Node.js ≥ 22.6. Download mini-format-core-1.1.0.tgz from Downloads or
extract it from the toolkit ZIP. Install the local file:
npm install --offline --ignore-scripts --no-audit --no-fund ./mini-format-core-1.1.0.tgz
The archive contains compiled JavaScript ESM in dist/, contracts for all fourteen
families and TypeScript sources for types. Import it as @mini-format/core:
running TypeScript inside node_modules, --experimental-strip-types and an npm
registry connection are not required.
Develop from source#
Files in ts/src/ use erasable TypeScript without enum, namespace or
parameter-properties, and explicit .ts import extensions. Node.js ≥ 22.6 can
run these sources with --experimental-strip-types; the distributed package
already contains JavaScript. Tests were verified with Node.js 22.14.0.
To generate ESM from the repository root, use Node.js ≥ 22.13:
node --no-warnings tools/build_node.mjs
The script strips types and rewrites relative .ts imports to .js in
ts/dist/. The distribution includes typed source files; this step does not
generate .d.ts declarations.
Layout#
ts/
├── package.json
├── src/
│ ├── errors.ts MiniError (code, line, field), MiniValidationError, E01–E21 codes
│ ├── contract.ts ContractJSON/Contract/Field types, normalizeContract, signatures, checkFork
│ ├── codec.ts tokenization, escapes, quotes, field and list splitting
│ ├── values.ts scalar decoding/encoding, formatNumber
│ ├── parser.ts parse, Document, LineEngine (line engine), detectPrefix
│ ├── serializer.ts dumps
│ ├── prompt.ts specBlock(contract, lang)
│ ├── registry.ts Registry (from objects or from forks/ with node:fs)
│ ├── stream.ts createReader, readRecords
│ └── index.ts public API
└── test/
├── fixtures.test.ts parity with forks/*/fixtures and validation rules
├── roundtrip.test.ts parse(dumps(obj)) == obj, codec, number formatting
├── stream.test.ts random 1–7 character chunks == parse
├── truncation.test.ts truncated outputs
├── conformance.test.ts ../conformance/ runner (skipped if missing)
└── helpers.ts
Usage#
import { Registry, parse, dumps, specBlock, createReader } from '@mini-format/core';
const reg = Registry.load(); // loads the families bundled with the package
const a = reg.get('a');
const doc = parse(texto, a); // strict: throws MiniValidationError with every error
doc.records; // valid records
doc.toCanonical(); // { prefix, header, items: [...] }
const lenient = parse(texto, a, { strict: false });
lenient.errors; // MiniError[] with code, line, field, message
lenient.invalidLines(); // rejected lines (the ones to regenerate)
lenient.missingRecords; // n − record lines (minimum 0)
lenient.diagnostics(); // report with the same keys as the Python reference
const texto2 = dumps(doc.toCanonical(), a); // exact round-trip
const prompt = specBlock(a, 'es'); // block for the system prompt
Contracts can be passed as a contract.json object or already normalized
(normalizeContract); every function accepts both.
To run the example directly from ts/ during development, change the import to
./src/index.ts and enable --experimental-strip-types.
Streaming#
const reader = createReader(a, { // strict: false by default
onRecord: ({ record, line, index }) => mostrar(record),
onError: (e) => console.warn(String(e)),
});
for await (const token of respuestaDelModelo) reader.push(token); // string or Uint8Array
const res = reader.end();
res.document; // identical to parse(textoCompleto, a, { strict: false })
res.expected; // n declared in the header
res.received; // record lines received
res.missing; // records missing relative to n
res.incomplete; // last line without LF that did not validate (text, line, errors) or null
res.truncated; // incomplete or fewer lines than n
res.terminated; // the stream ended on LF
A record is emitted as soon as its line closes with LF. readRecords(iterable, contrato)
wraps the same thing as an async generator. The reader uses the same line engine as
parse, so the final result is identical no matter how the input is chunked.
Note: if the last line does not end in LF and its last field is still valid
(e.g. clipped text), the record is indistinguishable from a complete one; in that
case terminated is false and it should be treated as suspect.
Tests and types#
cd ts
npm test
# or
node --experimental-strip-types --no-warnings --test test/*.test.ts
The public API and its imported modules pass strict checking with TypeScript 7.0.2. Run this from the repository root:
npx --yes --package typescript@7.0.2 tsc --noEmit --strict --module NodeNext --moduleResolution NodeNext --target ES2022 --allowImportingTsExtensions ts/src/index.ts
This command may download the compiler if it is not cached; installing and
running the .tgz does not require it. Node's type stripping does not replace
this check.
The shared conformance suite is read from ../conformance/cases/ (or the path in
MINI_CONFORMANCE_DIR); if the folder does not exist, the suite is skipped. The runner
reproduces the logic of conformance/run_python.py. The only API mapping: in lenient
mode the reference throws when the document cannot be built (E01); TS returns a
headerless Document with E01, and the runner treats it as "not built".
As in the reference, escapes are also validated in lenient mode (E09 and the record
is discarded), an invalid escape in the header is E09 inside validation, and a
rejected line does not reserve its unique value.
Decisions where js/mini.js, the Python reference and the SPEC differ#
Criterion: if the SPEC decides, follow the SPEC; otherwise follow the Python reference.
| Topic | Python | JS | TS |
|---|---|---|---|
| Empty document in lenient mode | always throws | returns document without canonical() |
returns Document with E01 (SPEC §8) |
| Invalid escape in the header | E09 inside validation (fixed) | raw MiniError outside parse |
E09 inside validation |
| Invalid escape in a record, lenient mode | E09 and record discarded (fixed) | keeps the literal text | E09 and record discarded |
Rejected line with unique value |
does not reserve it (fixed) | reserves it | does not reserve it |
Trailing \n on an int/float (5\n) |
accepts ($ of re + int()) |
rejects | rejects, E06 (SPEC: -?[0-9]+) |
| Unicode digits in numbers | accepts (\d Unicode) |
rejects | rejects |
clave\=valor in header (lenient) |
broken key | E12 | E12 (first unescaped =, SPEC §3.2) |
| Whitespace | str.isspace() |
JS \s |
str.isspace() |
| Contract validation (unknown types, separator, marker, tuples) | strict (E20) | lax | strict (E20) |
| Non-ASCII prefix letters | accepts | rejects | rejects (SPEC §4) |
| Float formatting (1e21, 1e-7) | expands without exponent when exact | String(x) |
like Python |
float signature/spec with max: 3.0 |
3.0 |
3 |
3 (JSON.parse cannot tell them apart) |
List range in specBlock with max: 0 |
∞ |
0 |
like Python |
__proto__ keys in header/records |
kept | lost | kept |
Limitations#
- Integers outside ±2^53 lose precision (JavaScript numbers).
Registry.loadrequires Node (usesprocess.getBuiltinModule); the rest of the library does not depend on Node.