Ir al contenido

Contrato de Binding Nativo (Lado TypeScript)

Este documento define el contrato del lado TypeScript que se sitúa entre los consumidores de @f5-sales-demo/pi-natives y el addon N-API cargado.

Se centra en tres piezas:

  1. forma del contrato (NativeBindings + augmentación de módulo),
  2. comportamiento del wrapper (src/<module>/index.ts),
  3. superficie de exportación pública (src/index.ts).
  • packages/natives/src/bindings.ts
  • packages/natives/src/native.ts
  • packages/natives/src/index.ts
  • packages/natives/src/clipboard/types.ts
  • packages/natives/src/clipboard/index.ts
  • packages/natives/src/glob/types.ts
  • packages/natives/src/glob/index.ts
  • packages/natives/src/grep/types.ts
  • packages/natives/src/grep/index.ts
  • packages/natives/src/highlight/types.ts
  • packages/natives/src/highlight/index.ts
  • packages/natives/src/html/types.ts
  • packages/natives/src/html/index.ts
  • packages/natives/src/image/types.ts
  • packages/natives/src/image/index.ts
  • packages/natives/src/keys/types.ts
  • packages/natives/src/keys/index.ts
  • packages/natives/src/ps/types.ts
  • packages/natives/src/ps/index.ts
  • packages/natives/src/pty/types.ts
  • packages/natives/src/pty/index.ts
  • packages/natives/src/shell/types.ts
  • packages/natives/src/shell/index.ts
  • packages/natives/src/system-info/types.ts
  • packages/natives/src/system-info/index.ts
  • packages/natives/src/text/types.ts
  • packages/natives/src/text/index.ts
  • packages/natives/src/work/types.ts
  • packages/natives/src/work/index.ts

packages/natives/src/bindings.ts define el contrato base:

  • NativeBindings (interfaz base, actualmente incluye cancelWork(id: number): void)
  • Cancellable (timeoutMs?: number, signal?: AbortSignal)
  • TsFunc<T> forma de callback utilizada por los callbacks threadsafe de N-API

Cada módulo agrega sus propios campos mediante fusión de declaraciones (declaration merging):

// packages/natives/src/<module>/types.ts
declare module "../bindings" {
interface NativeBindings {
grep(options: GrepOptions, onMatch?: TsFunc<GrepMatch>): Promise<GrepResult>;
}
}

Esto mantiene una única interfaz de binding agregada sin un archivo de tipos monolítico central.

Ciclo de vida de la fusión de declaraciones y transiciones de estado

Sección titulada «Ciclo de vida de la fusión de declaraciones y transiciones de estado»

1) Ensamblaje de tipos en tiempo de compilación

Sección titulada «1) Ensamblaje de tipos en tiempo de compilación»
  • bindings.ts proporciona el símbolo base NativeBindings.
  • Cada src/<module>/types.ts augmenta NativeBindings.
  • src/native.ts importa todos los archivos ./<module>/types por sus efectos secundarios para que el contrato fusionado esté en alcance donde se utiliza NativeBindings.

Transición de estado: Contrato baseContrato fusionado.

2) Carga del addon en tiempo de ejecución y puerta de validación

Sección titulada «2) Carga del addon en tiempo de ejecución y puerta de validación»
  • src/native.ts carga los binarios .node candidatos.
  • El objeto cargado se trata como NativeBindings y se pasa inmediatamente a través de validateNative(...).
  • validateNative verifica las claves de exportación requeridas mediante typeof bindings[name] === "function".

Transición de estado: Objeto addon no confiableObjeto de binding nativo validado (o fallo definitivo).

  • Los wrappers de módulo en src/<module>/index.ts llaman a native.<export>.
  • Los wrappers adaptan valores por defecto y la forma del callback (de (err, value) a patrones de callback solo con valor en las APIs de JS).
  • src/index.ts reexporta los wrappers/tipos de módulo como la API pública del paquete.

Transición de estado: Bindings crudos validadosAPI pública ergonómica.

Los wrappers son intencionalmente delgados; no reimplementan la lógica nativa.

Responsabilidades principales:

  • Normalización/valores por defecto de argumentos
    • glob() resuelve options.path a una ruta absoluta y establece valores por defecto para hidden, gitignore, recursive.
    • hasMatch() completa los flags por defecto (ignoreCase, multiline) antes de la llamada nativa.
  • Adaptación de callbacks
    • grep(), glob(), executeShell() convierten TsFunc<T> (error, value) en un callback de usuario que recibe solo valores exitosos.
  • Comportamiento de entorno o política alrededor de las llamadas nativas
    • El wrapper del portapapeles agrega manejo de OSC52/Termux/headless y trata la copia como mejor esfuerzo.
  • Nombrado público y curación de reexportaciones
    • searchContent() se mapea a la exportación nativa search.

Organización de la superficie de exportación pública

Sección titulada «Organización de la superficie de exportación pública»

packages/natives/src/index.ts es el barrel público canónico. Agrupa las exportaciones por dominio de capacidad:

  • Búsqueda/texto: grep, glob, text, highlight
  • Ejecución/proceso/terminal: shell, pty, ps, keys
  • Sistema/medios/conversión: image, html, clipboard, system-info, work

Regla para mantenedores: si un wrapper no se reexporta desde src/index.ts, no forma parte de la superficie pública intencionada del paquete.

Mapeo de API JS ↔ exportación nativa (representativo)

Sección titulada «Mapeo de API JS ↔ exportación nativa (representativo)»

El lado Rust utiliza nombres de exportación N-API (típicamente de la conversión #[napi] snake_case -> camelCase, con alias explícitos ocasionales) que deben coincidir con estas claves de binding.

CategoríaAPI JS pública (wrapper)Clave de binding nativoTipo de retorno¿Async?
Grepgrep(options, onMatch?)grepPromise<GrepResult>
GrepsearchContent(content, options)searchSearchResultNo
GrephasMatch(content, pattern, opts?)hasMatchbooleanNo
GrepfuzzyFind(options)fuzzyFindPromise<FuzzyFindResult>
Globglob(options, onMatch?)globPromise<GlobResult>
GlobinvalidateFsScanCache(path?)invalidateFsScanCachevoidNo
ShellexecuteShell(options, onChunk?)executeShellPromise<ShellExecuteResult>
ShellShellShellconstructor de claseN/A
PTYPtySessionPtySessionconstructor de claseN/A
TexttruncateToWidth(...)truncateToWidthstringNo
TextsliceWithWidth(...)sliceWithWidthSliceWithWidthResultNo
TextvisibleWidth(text)visibleWidthnumberNo
HighlighthighlightCode(code, lang, colors)highlightCodestringNo
HTMLhtmlToMarkdown(html, options?)htmlToMarkdownPromise<string>
SystemgetSystemInfo()getSystemInfoSystemInfoNo
WorkgetWorkProfile(lastSeconds)getWorkProfileWorkProfileNo
ProcesskillTree(pid, signal)killTreenumberNo
ProcesslistDescendants(pid)listDescendantsnumber[]No
ClipboardcopyToClipboard(text)copyToClipboardPromise<void> (comportamiento de mejor esfuerzo del wrapper)
ClipboardreadImageFromClipboard()readImageFromClipboardPromise<ClipboardImage | null>
KeysparseKey(data, kittyProtocolActive)parseKeystring | nullNo

El contrato mezcla APIs síncronas y asíncronas; los wrappers preservan el estilo de llamada nativo en lugar de forzar un solo modelo:

  • Exportaciones async basadas en Promise para I/O o trabajo de larga duración (grep, glob, htmlToMarkdown, executeShell, portapapeles, operaciones de imagen).
  • Exportaciones síncronas para transformaciones/parsers determinísticos en memoria (search, hasMatch, resaltado de sintaxis, ancho/segmentación de texto, parseo de teclas, consultas de procesos).
  • Exportaciones de constructores para objetos con estado en tiempo de ejecución (Shell, PtySession, PhotonImage).

Implicación para mantenedores: cambiar sync ↔ async para una exportación existente es un cambio de API y contrato que rompe compatibilidad a través de wrappers y consumidores.

Patrones de objetos (objetos JS estilo #[napi(object)])

Sección titulada «Patrones de objetos (objetos JS estilo #[napi(object)])»

TS modela los valores nativos con forma de objeto como interfaces, por ejemplo:

  • GrepResult, SearchResult, GlobResult
  • SystemInfo, WorkProfile
  • ClipboardImage, ParsedKittyResult

Estos son contratos estructurales en tiempo de compilación; la corrección de la forma en tiempo de ejecución es responsabilidad de la implementación nativa.

Los enums nativos numéricos se representan como valores const enum en TS:

  • FileType (1=file, 2=dir, 3=symlink)
  • ImageFormat (0=PNG, 1=JPEG, 2=WEBP, 3=GIF)
  • SamplingFilter, Ellipsis, KeyEventType

Los consumidores ven miembros nombrados del enum; la frontera del binding pasa números.

La detección de inconsistencias ocurre en dos capas:

  1. Verificaciones del contrato TypeScript en tiempo de compilación

    • Los wrappers llaman a native.<name> contra NativeBindings fusionado.
    • Las claves de binding faltantes/renombradas rompen la verificación de tipos TS en los wrappers.
  2. Validación en tiempo de ejecución en validateNative

    • Después de la carga, native.ts verifica las exportaciones requeridas y lanza una excepción si alguna falta.
    • El mensaje de error incluye las claves faltantes e instrucciones de reconstrucción.

Esto detecta la deriva común de binarios obsoletos: el wrapper/tipo existe pero el .node cargado carece de la exportación.

Fallos de carga/validación (fallos definitivos)

Sección titulada «Fallos de carga/validación (fallos definitivos)»
  • El fallo de carga del addon o una plataforma no soportada lanza una excepción durante la inicialización del módulo en native.ts.
  • Las exportaciones requeridas faltantes lanzan una excepción antes de que los wrappers sean utilizables.

Efecto: el paquete falla rápidamente en lugar de diferir el fallo a la primera llamada.

Diferencias de comportamiento a nivel de wrapper

Sección titulada «Diferencias de comportamiento a nivel de wrapper»
  • Algunos wrappers intencionalmente suavizan los fallos (copyToClipboard es de mejor esfuerzo y absorbe el fallo nativo).
  • Los callbacks de streaming ignoran los payloads de error del callback y solo reenvían eventos de valor exitosos.

Advertencias a nivel de tipos (el runtime es más estricto que TS)

Sección titulada «Advertencias a nivel de tipos (el runtime es más estricto que TS)»
  • Los campos opcionales en TS no garantizan validez semántica; la capa nativa aún puede rechazar valores malformados.
  • El tipado const enum no previene que valores numéricos fuera de rango sean pasados por consumidores sin tipado en tiempo de ejecución.
  • validateNative verifica solo la presencia y que sean funciones las exportaciones requeridas, no la compatibilidad profunda de forma de argumentos/retorno.
  • bindings.ts incluye cancelWork(id) en la interfaz base, pero la lista de validación en tiempo de ejecución actual no aplica esa clave.

Lista de verificación para mantenedores ante cambios de binding

Sección titulada «Lista de verificación para mantenedores ante cambios de binding»

Al agregar/cambiar una exportación, actualice todos los siguientes:

  1. src/<module>/types.ts (augmentación + tipos del contrato)
  2. src/<module>/index.ts (comportamiento del wrapper)
  3. Importaciones de src/native.ts para los tipos del módulo (si es un módulo nuevo)
  4. Verificaciones de exportaciones requeridas en validateNative
  5. Reexportaciones públicas en src/index.ts

Omitir cualquier paso crea ya sea deriva en tiempo de compilación o fallo en tiempo de ejecución durante la carga.