docs / Biblioteca TypeScriptTypeScript library
@mini-format/core — biblioteca TypeScript de .mini#
Versión de software 1.3.3. Implementación TypeScript modular y tipada del núcleo
.mini (SPEC 1.1):
parser, serializador, bloque de especificación para prompts, registro de familias
(forks), una API de lectura en streaming para respuestas de modelos token a token,
reparación selectiva, contratos desde Zod / JSON Schema y adaptadores de modelos
intercambiables (OpenAI, Anthropic, Groq, simulado) sobre fetch. Sin dependencias
de ejecución; todo salvo Registry.load funciona también en el navegador.
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.3.3.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.3.3.tgz
El archivo contiene JavaScript ESM compilado y declaraciones .d.ts en dist/
(exports.types apunta a dist/index.d.ts), los contratos de las catorce familias
y las fuentes TypeScript. 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/, y después emite las declaraciones .d.ts con el compilador de
TypeScript instalado en ts/node_modules (ejecuta antes npm ci en ts/).
--no-types genera solo el JavaScript. Desde ts/, npm run build hace lo mismo.
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
│ ├── repair.ts extractDocument, repairRequest, mergeRepair (reparación selectiva)
│ ├── schema.ts fromJsonSchema, fromZod
│ ├── adapters.ts ModelAdapter, OpenAI/Responses/Anthropic/Groq/Simulated, streamRecords
│ ├── sse.ts parseSSE (Server-Sent Events sobre fetch)
│ └── index.ts API pública
├── tsconfig.json chequeo estricto de src/ (tsconfig.test.json añade test/)
└── test/
├── fixtures.test.ts paridad con forks/*/fixtures y reglas de validación
├── repair.test.ts reparación selectiva y paridad con minifmt.ai.repair (fixtures/)
├── schema.test.ts contratos desde esquemas Zod reales
├── adapters.test.ts proveedores con fetch simulado y flujos SSE sintéticos (sin red)
├── dist.test.ts ESM compilado sin APIs de Node y declaraciones .d.ts
├── 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)
├── cortes.test.ts cortes de bytes en cada frontera (ts/src y js/mini.js) == parse
├── defectos.test.ts defectos corregidos tras la auditoría V5 (unique en la reparación, max 0, finalRecords) y propuestas del ADR 0017
├── bigint.test.ts comportamiento actual con enteros fuera de ±2^53 (ADR 0018, propuesta)
├── esquema_ramas.test.ts ramas de fromJsonSchema/fromZod
├── registro.test.ts Registry sobre disco y en memoria, roundtripOk
└── helpers.ts
Uso#
import { Registry, parse, dumps, specBlock, createReader } from '@mini-format/core';
const reg = Registry.load('./forks'); // ZIP opcional de familias extraído
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.finalRecords; // registros que completó end() (última línea válida sin LF); ya cuentan en valid/records
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.
En el navegador#
dist/ solo importa sus propios módulos relativos (sin node:, require ni
Buffer), así que puede servirse tal cual e importarse desde una página:
<script type="module">
import { parse } from './mini-format/dist/index.js';
const contrato = await (await fetch('./forks/cls/contract.json')).json();
const doc = parse(texto, contrato, { strict: false });
console.log(doc.records, doc.errors.map(String));
</script>
Pasa los contratos como objetos (fetch + json()) o con Registry.from([...]):
Registry.load, loadContract y defaultForksDir leen el disco y requieren Node.
test/dist.test.ts lo verifica cargando dist en un contexto vm que solo tiene
TextEncoder, TextDecoder y URL.
Reparación selectiva#
Mismo algoritmo y mismo texto de solicitud que minifmt.ai.repair (Python),
verificado con fixtures de paridad:
import { repairRequest, mergeRepair } from '@mini-format/core';
const req = repairRequest(respuestaDelModelo, contrato, 'es'); // solo las líneas inválidas y sus códigos
if (req.needed) {
const fix = await adaptador.generate({ system: req.system, user: req.user, maxTokens: req.maxTokensHint * 2, temperature: 0 });
const fusion = mergeRepair(respuestaDelModelo, fix.text, contrato, req);
fusion.text; fusion.ok; fusion.replaced; fusion.dropped; fusion.unresolved;
}
extractDocument quita la prosa y las cercas de código previas a la cabecera;
mergeRepair acepta una corrección solo si es válida por sí sola, conserva el orden
original y nunca reescribe n (los registros perdidos siguen visibles como E04).
Contratos desde Zod o JSON Schema#
import { z } from 'zod';
import { fromZod } from '@mini-format/core';
const Ticket = z.object({
id: z.string(),
prioridad: z.number().int().min(1).max(5),
estado: z.enum(['abierto', 'cerrado']),
etiquetas: z.array(z.string()).max(4),
nota: z.string().optional(),
});
const contrato = fromZod(Ticket, { prefix: 'tk', unique: ['id'] });
Zod no es dependencia: fromZod llama a toJSONSchema() del esquema (Zod v4) o al
conversor pasado en options.toJSONSchema, y luego a fromJsonSchema. Solo se
admiten registros planos (SPEC §12): string, integer (con minimum/maximum),
number, boolean, enums/literales de texto, listas de escalares
(minItems/maxItems) y campos opcionales, anulables o con valor por defecto. Los
objetos anidados, listas de objetos, uniones y fechas lanzan MiniError E20; las
restricciones que .mini no valida (pattern, format, minLength...) también,
salvo con unsupported: 'ignore' (entonces se informan en warnings). Un campo
vacío se decodifica como null: en campos .optional() que no son .nullable(),
elimina las claves null antes de llamar a parse de Zod.
Adaptadores de modelos#
Todos los proveedores implementan la misma interfaz, de modo que reemplazar uno no cambia el resto del código:
import { AnthropicAdapter, OpenAIAdapter, streamRecords, specBlock } from '@mini-format/core';
import type { ModelAdapter } from '@mini-format/core';
const adaptador: ModelAdapter = new OpenAIAdapter('gpt-4.1-mini'); // o new AnthropicAdapter('claude-...')
const solicitud = { system: specBlock(contrato, 'es'), user: 'Clasifica...', maxTokens: 2000, temperature: 0 };
const it = streamRecords(adaptador, contrato, solicitud); // SSE -> createReader: registros a medida que llegan
let paso = await it.next();
for (; !paso.done; paso = await it.next()) mostrar(paso.value.record);
paso.value.reader; // ReaderResult (documento, errores, truncated...)
paso.value.generation; // { text, inputTokens, outputTokens, stopReason, ... }
| Clase | Endpoint | Clave |
|---|---|---|
OpenAIAdapter |
Chat Completions /v1/chat/completions |
OPENAI_API_KEY |
OpenAIResponsesAdapter |
Responses /v1/responses |
OPENAI_API_KEY |
AnthropicAdapter |
Messages /v1/messages (browser: true para llamadas directas desde el navegador) |
ANTHROPIC_API_KEY |
GroqAdapter |
compatible con OpenAI /openai/v1/chat/completions |
GROQ_API_KEY |
SimulatedAdapter |
sin red: respuestas fijas o calculadas, troceado determinista | ninguna |
generate(solicitud) devuelve el resultado completo; stream(solicitud) devuelve
un iterable asíncrono de fragmentos de texto más result. Opciones: apiKey (si
no, la variable de entorno, cuando existe process.env), fetch (inyectable),
url, retries, sleep, structured, sendTemperature, extraBody, headers.
responseFormat ({type: 'json_schema', name, schema}) se traduce al modo
estructurado de cada proveedor. Los mensajes de error están depurados: las claves
nunca aparecen en ellos. getAdapter(proveedor, modelo, opciones) crea adaptadores
por nombre. streamRecords descarta de forma incremental la prosa y las cercas
iniciales (como extractDocument); extract: false lo desactiva. En el navegador,
no expongas claves: usa un proxy propio mediante url.
Pruebas y tipos#
cd ts
npm ci # typescript, zod y @types/node (solo desarrollo)
npm test
npm run typecheck # tsc con strict: true sobre src/ y test/
npm test no usa red ni claves: los adaptadores se prueban con un fetch
simulado y flujos SSE sintéticos por proveedor. Sin npm ci, se omiten las pruebas
con Zod y la verificación de .d.ts. Los fixtures de paridad de la reparación se
regeneran desde la referencia Python con
PYTHONPATH=src python ts/test/fixtures/gen_repair_parity.py.
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.
Implementaciones y motor del playground#
js/mini.js, el motor del playground, ya no es un port escrito a mano: desde 1.2.0
se genera desde ts/src con node --no-warnings tools/build_js.mjs (un paquete UMD
sin dependencias que expone el global MINI). node tests/test_js_port.mjs falla si
el archivo versionado no está al día y ejecuta todos los casos de conformidad contra
él (node conformance/run_js.mjs imprime el mismo informe por categoría). Así, el
navegador valida exactamente como esta biblioteca.
Diferencias que quedan entre la referencia Python y TypeScript (criterio: si la SPEC decide, se sigue la SPEC; si no, se sigue la referencia Python):
| Tema | Python | TS y js/mini.js |
|---|---|---|
| Documento vacío en modo tolerante | lanza siempre | devuelve Document con E01 (SPEC §8) |
| Prefijo con letras no ASCII | acepta | rechaza (SPEC §4) |
Firma/spec de float con max: 3.0 |
3.0 |
3 (JSON.parse no distingue) |
| Enteros fuera de ±2^53 | exactos | pierden precisión sin error ni aviso (usa decimal para valores exactos); decisión pendiente: ADR 0018 (propuesta), comportamiento fijado por ts/test/bigint.test.ts |
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.fromZod/fromJsonSchemano generan camposmlistnituple.SimulatedAdapterno reproduce los perfiles de fallas del simulador Python: devuelve respuestas fijas o calculadas (para pruebas y demostraciones).
@mini-format/core — .mini TypeScript library#
Software release 1.3.3. Modular, typed TypeScript implementation of the .mini
core notation (SPEC 1.1):
parser, serializer, specification block for prompts, family (fork) registry, a
streaming read API for token-by-token model responses, selective repair,
contracts from Zod / JSON Schema and interchangeable model adapters (OpenAI,
Anthropic, Groq, simulated) over fetch. No runtime dependencies; everything except
Registry.load also runs in the browser.
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.3.3.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.3.3.tgz
The archive contains compiled JavaScript ESM and .d.ts declarations in dist/
(exports.types points to dist/index.d.ts), contracts for all fourteen families
and the TypeScript sources. 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/, then emits .d.ts declarations with the TypeScript compiler installed
in ts/node_modules (run npm ci in ts/ first). --no-types builds only the
JavaScript. From ts/, npm run build does the same.
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
│ ├── repair.ts extractDocument, repairRequest, mergeRepair (selective repair)
│ ├── schema.ts fromJsonSchema, fromZod
│ ├── adapters.ts ModelAdapter, OpenAI/Responses/Anthropic/Groq/Simulated, streamRecords
│ ├── sse.ts parseSSE (Server-Sent Events over fetch)
│ └── index.ts public API
├── tsconfig.json strict checking of src/ (tsconfig.test.json adds test/)
└── test/
├── fixtures.test.ts parity with forks/*/fixtures and validation rules
├── repair.test.ts selective repair and parity with minifmt.ai.repair (fixtures/)
├── schema.test.ts contracts from real Zod schemas
├── adapters.test.ts providers with mocked fetch and synthetic SSE streams (no network)
├── dist.test.ts compiled ESM without Node APIs and .d.ts declarations
├── 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)
├── cortes.test.ts byte cuts at every boundary (ts/src and js/mini.js) == parse
├── defectos.test.ts defects fixed after the V5 audit (unique in repair, max 0, finalRecords) and ADR 0017 proposals
├── bigint.test.ts current behaviour with integers beyond ±2^53 (ADR 0018, proposal)
├── esquema_ramas.test.ts branches of fromJsonSchema/fromZod
├── registro.test.ts Registry on disk and in memory, roundtripOk
└── helpers.ts
Usage#
import { Registry, parse, dumps, specBlock, createReader } from '@mini-format/core';
const reg = Registry.load('./forks'); // extracted optional families ZIP
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.finalRecords; // records completed by end() (a valid last line without LF); already in valid/records
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.
In the browser#
dist/ only imports its own relative modules (no node:, require or Buffer),
so it can be served as-is and imported from a page:
<script type="module">
import { parse } from './mini-format/dist/index.js';
const contract = await (await fetch('./forks/cls/contract.json')).json();
const doc = parse(text, contract, { strict: false });
console.log(doc.records, doc.errors.map(String));
</script>
Pass contracts as objects (fetch + json()) or with Registry.from([...]):
Registry.load, loadContract and defaultForksDir read the disk and require Node.
test/dist.test.ts verifies this by loading dist in a vm context that only has
TextEncoder, TextDecoder and URL.
Selective repair#
Same algorithm and request text as minifmt.ai.repair (Python), verified by parity
fixtures:
import { repairRequest, mergeRepair } from '@mini-format/core';
const req = repairRequest(modelAnswer, contract, 'en'); // only invalid lines + their error codes
if (req.needed) {
const fix = await adapter.generate({ system: req.system, user: req.user, maxTokens: req.maxTokensHint * 2, temperature: 0 });
const merged = mergeRepair(modelAnswer, fix.text, contract, req);
merged.text; merged.ok; merged.replaced; merged.dropped; merged.unresolved;
}
extractDocument removes prose and code fences before the header; mergeRepair
accepts a correction only if it is valid on its own, keeps the original order and
never rewrites n (lost records stay visible as E04).
Contracts from Zod or JSON Schema#
import { z } from 'zod';
import { fromZod } from '@mini-format/core';
const Ticket = z.object({
id: z.string(),
priority: z.number().int().min(1).max(5),
status: z.enum(['open', 'closed']),
tags: z.array(z.string()).max(4),
note: z.string().optional(),
});
const contract = fromZod(Ticket, { prefix: 'tk', unique: ['id'] });
Zod is not a dependency: fromZod calls the schema's toJSONSchema() (Zod v4) or the
converter passed in options.toJSONSchema, and then fromJsonSchema. Only flat
records are supported (SPEC §12): string, integer (with minimum/maximum), number,
boolean, string enums/literals, lists of scalars (minItems/maxItems), optional,
nullable or defaulted fields. Nested objects, lists of objects, unions and dates
throw MiniError E20; constraints that .mini does not validate (pattern, format,
minLength...) also throw unless unsupported: 'ignore' is set (they are then
reported in warnings). An empty field decodes to null: for .optional() fields
that are not .nullable(), drop null keys before calling Zod's parse.
Model adapters#
All providers implement the same interface, so replacing one does not change the rest of the code:
import { AnthropicAdapter, OpenAIAdapter, streamRecords, specBlock } from '@mini-format/core';
import type { ModelAdapter } from '@mini-format/core';
const adapter: ModelAdapter = new OpenAIAdapter('gpt-4.1-mini'); // or new AnthropicAdapter('claude-...')
const request = { system: specBlock(contract, 'en'), user: 'Classify...', maxTokens: 2000, temperature: 0 };
const it = streamRecords(adapter, contract, request); // SSE -> createReader: records as they arrive
let step = await it.next();
for (; !step.done; step = await it.next()) show(step.value.record);
step.value.reader; // ReaderResult (document, errors, truncated...)
step.value.generation; // { text, inputTokens, outputTokens, stopReason, ... }
| Class | Endpoint | Key |
|---|---|---|
OpenAIAdapter |
Chat Completions /v1/chat/completions |
OPENAI_API_KEY |
OpenAIResponsesAdapter |
Responses /v1/responses |
OPENAI_API_KEY |
AnthropicAdapter |
Messages /v1/messages (browser: true for direct browser calls) |
ANTHROPIC_API_KEY |
GroqAdapter |
OpenAI-compatible /openai/v1/chat/completions |
GROQ_API_KEY |
SimulatedAdapter |
no network: fixed or computed answers, deterministic chunking | none |
generate(request) returns the full result; stream(request) returns an async
iterable of text deltas plus result. Options: apiKey (otherwise the environment
variable, if process.env exists), fetch (injectable), url, retries, sleep,
structured, sendTemperature, extraBody, headers. responseFormat
({type: 'json_schema', name, schema}) is translated to each provider's structured
mode. Error messages are redacted: keys never appear in them. getAdapter(provider,
model, options) creates adapters by name. streamRecords drops leading prose and
code fences incrementally (like extractDocument); pass extract: false to disable it.
In the browser, avoid exposing keys: prefer your own proxy via url.
Tests and types#
cd ts
npm ci # typescript, zod and @types/node (development only)
npm test
npm run typecheck # tsc with strict: true over src/ and test/
npm test uses no network or keys: adapters are exercised with a mocked fetch
and synthetic SSE streams per provider. Without npm ci, the Zod tests and the
.d.ts check are skipped. Repair parity fixtures are regenerated from the Python
reference with PYTHONPATH=src python ts/test/fixtures/gen_repair_parity.py.
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.
Implementations and the playground engine#
js/mini.js, the engine of the playground, is no longer a hand-written port: since
1.2.0 it is generated from ts/src by node --no-warnings tools/build_js.mjs (a
dependency-free UMD bundle exposing the global MINI). node tests/test_js_port.mjs
fails when the committed file is out of date and runs every conformance case against
it (node conformance/run_js.mjs prints the same report per category). The browser
therefore validates exactly as this library does.
Remaining differences between the Python reference and TypeScript (criterion: if the SPEC decides, follow the SPEC; otherwise follow the Python reference):
| Topic | Python | TS and js/mini.js |
|---|---|---|
| Empty document in lenient mode | always throws | returns Document with E01 (SPEC §8) |
| Non-ASCII prefix letters | accepts | rejects (SPEC §4) |
float signature/spec with max: 3.0 |
3.0 |
3 (JSON.parse cannot tell them apart) |
| Integers outside ±2^53 | exact | lose precision silently (use decimal for exact values); pending decision: ADR 0018 (proposal), behaviour pinned by ts/test/bigint.test.ts |
Limitations#
- Integers outside ±2^53 lose precision (JavaScript numbers).
Registry.loadrequires Node (usesprocess.getBuiltinModule); the rest of the library does not depend on Node.fromZod/fromJsonSchemado not generatemlistortuplefields.SimulatedAdapterdoes not reproduce the fault profiles of the Python simulator: it returns fixed or computed answers (for tests and demos).