Skip to content

Presentamos Metacore v3 kernel v0.20

El manifest es el contrato. El código es un detalle de implementación.

El Module Contract v3 es el salto más grande en cómo describís un addon desde que existe Metacore. Un único documento JSON estricto y declarativo ahora maneja el schema, la API, los permisos, la superficie de UI, el listado del catálogo y el scoping regional — y el kernel lo corre de punta a punta. Esta página es el tour.

TL;DR

Declarás apiVersion: asteby.com/v3 y obtenés: un contrato de modelos tipado, seguridad capability + RBAC, action modals y line-items declarativos, Presets verticales instalables, UI bespoke federada, i18n de catálogo y scoping por país — con dual-read v2 para que nada se rompa mientras migrás.

Por qué v3

v2 hacía que el manifest describiera un addon. v3 hace que el manifest sea el addon. Tres principios lo guían:

  1. El manifest es el contrato. Cada capability que el kernel otorga, cada evento que rutea, cada tabla que materializa y cada slot de UI que renderiza está declarado. Si no está en el manifest, no existe — el kernel nunca introspecciona tu binario para descubrir comportamiento.
  2. Acoplamiento por eventos tipados y slots nombrados — nunca por imports. Dos vendors no relacionados publican addons que componen, porque se encuentran en un slot_kind o nombre de evento publicado, no en un path de import Go/TS.
  3. El aislamiento de tenant es shared + RLS de Postgres por defecto. El default correcto para ~95% de los addons; schema-per-tenant es un escape hatch opt-in.

Qué trae

CapacidadQué te daDesde
Contrato Manifest v3apiVersion/kind/metadata/compatibility/models/contributions/rbac estricto y validado por schemav0.13
Dual-read v2Los manifests planos legacy siguen instalando — migración sin romper nadav0.13
Action modalsfields declarativos / modal custom / confirm — UI de acciones rica, sin código frontendv0.14
Frontend federationModals y páginas bespoke federadas dentro del hostv0.14
Extras de Settings & ColumnSetting.description, Setting.type: "number", Column.comment, compiled handlersv0.15
Line-itemsGrupo repetible como action field declarativo (líneas de factura, ítems de pedido)v0.16
Presetskind: "Preset" — instalá una vertical entera como una unidadv0.17
Triggers de acción WASMLos handlers wasm declarados validan sin un bloque backendv0.18
i18n de catálogometadata.i18n — localizá el listado del marketplace (es/en)v0.19
Scoping por paísmetadata.countries — acotá un addon a regionesv0.20
Hook de monedaMoneda por org resuelta al INSERT, fallback USD agnóstico de geografíaunreleased

Todo lo de abajo es verificable contra el kernel v0.20 y el contrato docs/spec/v3.

El contrato, de un vistazo

jsonc
{
  "apiVersion": "asteby.com/v3",
  "kind":       "Addon",
  "metadata":        { "key": "inventory", "version": "1.0.0", "i18n": {}, "countries": ["MX","CO"] },
  "compatibility":   { "requires": [ { "key": "kernel", "version": ">=3.0.0 <4.0.0" } ] },
  "tenancy":         { "isolation": "shared", "rls_column": "organization_id" },
  "capabilities":    [ { "kind": "db:write", "target": "addon_inventory.*" } ],
  "models":          [ { "key": "Product", "table": "products", "columns": [] } ],
  "contributions":   { "navigation": [], "slots": [], "actions": [], "subscriptions": [] },
  "extension_points":{ "events": [], "slot_kinds": [], "model_extensions_accepted": [] },
  "rbac":            { "roles": [], "permissions": [] },
  "settings":        [ { "key": "low_stock_threshold", "type": "number", "description": "…" } ]
}

Antes y después

El mismo addon, a la manera v2 y a la manera v3. v3 cambia una bolsa plana por un contrato tipado y seccionado — y desbloquea todo lo demás de esta página.

json
{
  "key": "tickets",
  "name": "Tickets",
  "version": "1.0.0",
  "kernel": ">=2.0.0 <3.0.0",
  "tenant_isolation": "shared",
  "model_definitions": [
    {
      "table_name": "tickets", "model_key": "tickets",
      "org_scoped": true, "soft_delete": true,
      "columns": [
        { "name": "title",  "type": "string", "size": 255, "required": true },
        { "name": "status", "type": "string", "size": 20, "default": "'open'" }
      ]
    }
  ],
  "capabilities": [ { "kind": "db:write", "target": "tickets" } ]
}
json
{
  "apiVersion": "asteby.com/v3",
  "kind": "Addon",
  "metadata": { "key": "tickets", "name": "Tickets", "version": "1.0.0" },
  "compatibility": { "requires": [ { "key": "kernel", "version": ">=3.0.0 <4.0.0" } ] },
  "tenancy": { "isolation": "shared", "rls_column": "organization_id" },
  "capabilities": [ { "kind": "db:write", "target": "addon_tickets.*" } ],
  "models": [
    {
      "key": "Ticket", "table": "tickets",
      "columns": [
        { "name": "id",     "type": "uuid", "primary_key": true, "default": "gen_random_uuid()" },
        { "name": "organization_id", "type": "uuid", "not_null": true },
        { "name": "title",  "type": "text", "not_null": true },
        { "name": "status", "type": "text", "default": "'open'" }
      ]
    }
  ],
  "rbac": {
    "permissions": [ { "key": "tickets.write", "label": "Gestionar tickets" } ],
    "roles": [ { "key": "tickets_agent", "permissions": ["tickets.write"] } ]
  }
}

Presets — instalá una vertical como una unidad

kind: "Preset" agrupa un set curado de addons con defaults sensatos. Instalás uno, y el kernel resuelve e instala cada entrada de preset.addons[] en orden de dependencias, y después aplica los defaults del preset encima. Así una vertical entera se distribuye con un solo click.

json
{
  "apiVersion": "asteby.com/v3",
  "kind": "Preset",
  "metadata": { "key": "retail_starter", "name": "Retail Starter", "version": "1.0.0" },
  "preset": {
    "addons": [
      { "key": "core_catalog", "version": "^1.2.0" },
      { "key": "inventory",    "version": "^1.0.0" },
      { "key": "pos_terminal", "version": "^1.0.0" },
      { "key": "reporting_basic", "version": "^1.0.0", "optional": true }
    ],
    "defaults": {
      "inventory.low_stock_threshold": 5,
      "pos_terminal.default_currency": "MXN"
    }
  }
}

Action modals & line-items — UI rica, cero frontend

Una action declara cómo recolecta input. El runtime renderiza la UI correcta gratis:

  • confirm — un prompt sí/no.
  • fields — un modal autogenerado con inputs tipados.
  • modal — un modal frontend custom, federado, referenciado por slug.

Los fields pueden incluir line-items: un grupo repetible renderizado como filas add/remove — líneas de factura, ítems de pedido, cualquier cosa tabular.

json
{
  "contributions": {
    "actions": [
      {
        "key": "create_invoice",
        "label": "Nueva factura",
        "target_model": "Customer",
        "fields": [
          { "name": "due_date", "type": "date", "required": true },
          {
            "name": "lines", "type": "line_items",
            "fields": [
              { "name": "description", "type": "text", "required": true },
              { "name": "qty",         "type": "number", "default": 1 },
              { "name": "unit_price",  "type": "number", "required": true }
            ]
          }
        ],
        "handler": { "type": "wasm", "function": "CreateInvoice" }
      }
    ]
  }
}

El kernel monta POST /api/dynamic/customers/:id/actions/create_invoice; el SDK renderiza el modal con una tabla repetible de line-items. Vos escribís el cuerpo del handler — nada más.

i18n de catálogo & scoping por país

El listado del marketplace habla el idioma del usuario y respeta la geografía, declarado en metadata:

json
"metadata": {
  "key": "facturacion",
  "name": "Electronic Invoicing",
  "countries": ["MX", "CO", "AR"],
  "i18n": {
    "es": { "name": "Facturación Electrónica", "description": "Timbrado y CFDI." },
    "en": { "name": "Electronic Invoicing", "description": "Stamping and tax receipts." }
  }
}

Específico de locale por config, no por hardcode

Los identificadores fiscales, formatos de dirección y la moneda son config de la org, nunca horneados en el kernel ni en un addon. metadata.countries acota la disponibilidad; el hook de moneda por org resuelve el dinero al escribir, con un fallback USD agnóstico de geografía.

Compatible hacia atrás por diseño

Migrá sin reescribir

El kernel 3.x es dual-read. Acepta tanto manifests v2 (sin apiVersion) como v3, up-convirtiendo transparentemente v2 a la forma v3 en memoria — así los addons existentes siguen instalando y corriendo mientras migrás a tu ritmo. El kernel 4.x quita v2. Escribí los addons nuevos en v3 hoy.

El mapeo completo campo por campo de v2 → v3 está en la guía de migración del kernel.

Empezá

Construí tu primer addon v3

  1. Leé el concepto de Manifest — el contrato v3 en profundidad.
  2. Seguí Construir un addon — scaffold, declarás, instalás.
  3. Levantá una app completa: npm create @asteby/metacore-app my-app -- --example fullstack-starter.
  4. Recorré el suite del SDK — cada package que renderiza v3.

Metacore es open-source. Apache-2.0.