@opensas/xml-rest-adapter
v0.2.3
Published
Proyecto _de uso interno_ que expone mediante una API REST las bases de datos que utilizan el formato propio de `xmlEntidad_Accion` para encapsular la lógica de negocio.
Readme
xml-rest-adapter
Proyecto de uso interno que expone mediante una API REST las bases de datos que utilizan el
formato propio de xmlEntidad_Accion para encapsular la lógica de negocio.
npm: https://www.npmjs.com/package/@opensas/xml-rest-adapter
pnpm add @opensas/xml-rest-adapterCómo funciona, paso a paso
createXmlRestAdapter(app) recibe la definición de una aplicación (nombre, cómo autenticar,
cómo autorizar, a qué base conectarse, qué objetos exponer) y devuelve dos funciones:
resolveUser y handle. No importa qué framework o runtime uses — ambas trabajan con Request/
Response de la Fetch API estándar, nada específico de SvelteKit ni de Node.
1. Creá el adapter una sola vez, con la config de tu app:
import { createXmlRestAdapter, endpoints } from '@opensas/xml-rest-adapter';
const adapter = createXmlRestAdapter({
name: 'MiApp', // tiene que coincidir con el Codigo de la app en Meta (o usá aplicacionCodigo)
authentication: { type: 'iis-node' }, // o { type: 'none' } para apps públicas/anónimas
authorization: { type: 'metaSSC', dbInfo: process.env.META_DATABASE_URL },
dbInfo: process.env.MI_APP_DATABASE_URL, // connection string ADO.NET, o un objeto DbInfo
pagination: 'sql', // 'com+' solo para SPs viejos sin @SqlPaginate
endpoints: endpoints(['Provincia', 'TipoDato']), // qué objetos expone esta app
});2. Por cada request, resolvé quién está llamando — resolveUser acepta el Request tal cual
(extrae la identidad según authentication.type) o un string ya resuelto:
const result = await adapter.resolveUser(request);
if (!result.ok) {
/* result.error: AppError con status/code/title — devolvelo como respuesta */
}
const usuario = result.data;3. Dejá que el adapter atienda el request — handle necesita el Request, el segmento de
URL después de tu ruta (objeto, y opcionalmente /id), y el usuario del paso anterior:
const response = await adapter.handle(request, path, usuario);Eso es todo — handle decide la acción (Consulta/Alta/Modificacion/Baja) según el método HTTP
(GET/POST/PUT/DELETE), valida permisos, arma y ejecuta el xml{Objeto}_{Accion} contra
SQL Server, y devuelve la Response ya lista (JSON o el error correspondiente).
Ejemplo con SvelteKit
Un solo archivo alcanza — la carpeta tiene que ser [...path] (rest parameter), porque
objeto/id no son segmentos fijos:
// src/routes/mi-app/[...path]/+server.ts
import { createXmlRestAdapter, endpoints, jsonError } from '@opensas/xml-rest-adapter';
import { env } from '$env/dynamic/private';
import type { RequestHandler } from './$types.js';
const adapter = createXmlRestAdapter({
name: 'MiApp',
authentication: { type: 'iis-node' },
authorization: { type: 'metaSSC', dbInfo: env.META_DATABASE_URL },
dbInfo: env.MI_APP_DATABASE_URL,
pagination: 'sql',
endpoints: endpoints(['Provincia', 'TipoDato']),
});
// Un solo handler para los 4 verbos — `fallback` los atrapa a todos, `.handle()` decide la
// acción según `event.request.method`.
export const fallback: RequestHandler = async (event) => {
const result = await adapter.resolveUser(event.request);
if (!result.ok) return jsonError(result.error);
return adapter.handle(event.request, event.params.path, result.data);
};Con esto, GET /mi-app/Provincia?_limit=10, POST /mi-app/TipoDato, PUT /mi-app/Provincia/5,
DELETE /mi-app/Provincia/5 ya funcionan. Ejemplo completo y corrible en
examples/consumer-svelte/.
Ejemplo con Node puro (sin ningún framework)
handle()/resolveUser() no requieren nada de SvelteKit — alcanza con las Request/Response
globales que trae Node 18+. Un servidor mínimo con node:http:
import { createServer } from 'node:http';
import { createXmlRestAdapter, endpoints, jsonError } from '@opensas/xml-rest-adapter';
const adapter = createXmlRestAdapter({
name: 'MiApp',
authentication: { type: 'none' }, // sin IIS: no hay identidad real que extraer del request
authorization: { type: 'none' },
dbInfo: process.env.MI_APP_DATABASE_URL,
pagination: 'sql',
endpoints: endpoints(['Provincia']),
});
const server = createServer(async (req, res) => {
const url = new URL(req.url, `http://${req.headers.host}`);
const path = url.pathname.replace(/^\//, ''); // 'Provincia' o 'Provincia/5'
const hasBody = req.method !== 'GET' && req.method !== 'HEAD';
const request = new Request(url, {
method: req.method,
headers: req.headers,
body: hasBody ? req : undefined,
duplex: hasBody ? 'half' : undefined,
});
const userResult = await adapter.resolveUser(request);
const response = userResult.ok
? await adapter.handle(request, path, userResult.data)
: jsonError(userResult.error);
res.writeHead(response.status, Object.fromEntries(response.headers));
res.end(Buffer.from(await response.arrayBuffer()));
});
server.listen(3000);Prueba mínima sin siquiera levantar un servidor HTTP (construyendo los Request a mano y
llamando a handle() directo) en examples/consumer/test.mjs.
Más
docs/uso-interno.md— gotchas y detalles que no son obvios a la primera (qué campo tiene que matchear con Meta, cómo mapeaendpoints/URL/SP, etc.).docs/proximos-pasos.md— el porqué de cada decisión de diseño.CHANGELOG.md— qué cambió entre versiones.
