Nitro Modules 2026: คู่มือสร้าง Native Module ประสิทธิภาพสูงด้วย Swift, Kotlin และ Type-Safe JSI

คู่มือสร้าง Nitro Modules v0.36 ตั้งแต่ .nitro.ts spec จนถึง Swift/Kotlin implementation, Nitro Views บน Fabric และการ ship ลง production พร้อมตัวอย่างจริงและวิธีแก้ JNI crash ที่พบบ่อย

Nitro Modules: คู่มือ Swift + Kotlin 2026

อัปเดต: 18 กรกฎาคม 2026

Nitro Modules คือระบบสร้าง Native Module รุ่นใหม่สำหรับ React Native ที่เขียน spec เป็น TypeScript แล้ว codegen ออกมาเป็น C++/Swift/Kotlin โดยตรงบน JSI, ไม่มี bridge, ไม่มี Codegen ของ core, และเรียก native ได้เร็วกว่า TurboModules ประมาณ 15 เท่าในการเรียกเชิงสังเคราะห์ ตอนที่เขียนอยู่นี้ ตัวไลบรารีอยู่ที่ v0.36.1 (กรกฎาคม 2026), ต้องการ React Native 0.78+ พร้อม New Architecture, และมี template ใน create-react-native-library เรียบร้อยแล้ว บทความนี้จะพาคุณสร้าง Nitro Module ที่ใช้งานจริงตั้งแต่ .nitro.ts จนถึง ship ลง production ผมเคยเจ็บตัวกับการย้ายระบบ native ในแอปที่มี user 400k+ มาแล้ว จะเล่าให้ฟังเป็นระยะ ๆ ว่าอันไหนควรระวัง

  • Nitro Modules v0.36 ใช้ Hybrid Objects (JSI NativeState) แทน HostObject เดิม ทำให้ GC จัดการ memory pressure ได้แม่นยำขึ้น
  • Spec เขียนเป็น .nitro.ts แล้ว Nitrogen gen โค้ด C++ glue กับ protocol/interface สำหรับ Swift และ Kotlin ให้อัตโนมัติ
  • iOS ต่อกับ C++ ผ่าน Swift โดยตรง (ไม่ต้องผ่าน Objective-C), ส่วน Android ใช้ Kotlin + fbjni โดยไม่ต้องเขียน Java
  • Promise.async ทำงานกับ Swift/Kotlin coroutines ได้เนทีฟ ทำให้ long-running work ปลอดภัยและอ่านง่าย
  • Zero-copy ArrayBuffer ให้ผลชัดเจนบน payload ใหญ่ (image, file, crypto), ประมาณ 8–40% เร็วกว่า TurboModules
  • รองรับ Nitro Views บน Fabric สำหรับ RN 0.78+ ใช้แทน Fabric Native Component ที่โค้ดเยอะกว่ามาก

Nitro Modules คืออะไร และต่างจาก TurboModules อย่างไร

ในทีมที่ผมดูแล เราย้าย native module บางตัวจาก TurboModules ไป Nitro ในไตรมาสแรกของ 2026 เหตุผลหลักไม่ใช่แค่ความเร็ว แต่คือ type safety จริง ๆ ระหว่าง TS ↔ Swift ↔ Kotlin. spec เขียนที่เดียว codegen ผูก contract เดียวกันทั้งสองฝั่งของ bridgeless boundary

Nitro Modules ถูกออกแบบโดย Marc Rousavy (คนเดียวกับ VisionCamera และ MMKV V4) ให้เป็นชั้นที่บางกว่า TurboModules ทั้งในโค้ดและ runtime ต่างกันสำคัญ ๆ ได้แก่:

  • Codegen แยกจาก core: Nitrogen ทำ codegen ของตัวเอง ไม่ต้องรอ RN core release cycle การเพิ่ม type ใหม่ เช่น union หรือ ArrayBuffer จึงมาถึง user เร็วกว่า
  • Hybrid Objects แทน HostObject: ใช้ jsi::NativeState ที่บอก JS engine ได้ว่าสถานะเนทีฟกินหน่วยความจำเท่าไร ทำให้ Hermes GC ตัดสินใจได้แม่นขึ้น (ประเด็นนี้จริงจังในแอปที่มี image buffer เยอะ)
  • Swift แบบตรง ๆ: ฝั่ง iOS ไม่ต้องเขียน Objective-C wrapper ให้ Swift Nitro ใช้ Swift-C++ interop ที่ Apple เปิดตัวมาตั้งแต่ Xcode 15
  • Kotlin แบบตรง ๆ: ฝั่ง Android ใช้ Kotlin + fbjni ตรงกับ C++ ไม่ต้องเขียน Java bridge ระดับกลาง

ถ้าอยากเห็นภาพรวมของ New Architecture ที่ Nitro นั่งอยู่ข้างบน อ่านต่อได้ใน คู่มือ React Native New Architecture 2025-2026: Fabric, TurboModules, JSI และ Hermes บทความนั้นอธิบาย JSI, bridgeless mode และ Fabric ที่ Nitro ใช้เป็นฐาน

ตัวเลขที่คนอ้างบ่อย ๆ (100k calls บน iPhone 15 Pro): Nitro 7.27ms, TurboModules 115.86ms, Expo Modules 434.85ms สำหรับ addNumbers ผมย้ำเสมอว่าตัวเลขนี้เป็น micro-benchmark เมื่อ workload จริง (I/O + compression + serialization) ช่องว่าง Turbo↔Nitro หดเหลือประมาณ 8–40% ไม่ใช่ 15×

ข้อกำหนดเบื้องต้นและเวอร์ชันที่รองรับ

ก่อนเริ่มโปรเจ็กต์ Nitro Module ตรวจสิ่งเหล่านี้ก่อน จากประสบการณ์การอัปเกรด monorepo หลายตัวในปีนี้ ปัญหา 80% ตอน start มาจาก mismatch ของเวอร์ชัน:

  • React Native ≥ 0.78 พร้อม newArchEnabled=true (Nitro Views ต้องการ Fabric)
  • Xcode ≥ 15.3 เพื่อใช้ Swift-C++ interop แบบ mixed target
  • Android Gradle Plugin ≥ 8.5, Kotlin ≥ 2.0 (Nitro ใช้ K2 compiler features)
  • Node ≥ 20 สำหรับ Nitrogen CLI
  • Hermes: แนะนำเปิด ใช้ JSC ได้แต่จะเสีย NativeState memory hints บางส่วน

ทีมที่ยังใช้ Old Architecture ใช้ Nitro Modules ไม่ได้ เพราะ Nitro ผูกกับ JSI runtime และ Fabric renderer โดยตรง ถ้ายังต้อง support paper renderer อยู่ ให้ใช้ TurboModules ที่มี supportsInterop ไปก่อน แล้วค่อยย้ายเมื่อ opt-in เสร็จ

ติดตั้ง Nitro และตั้งค่า nitro.json

เอาจริง ๆ วิธีที่เร็วที่สุดคือใช้ template ของ create-react-native-library ซึ่ง Callstack ใส่ Nitro entry มาให้ตั้งแต่ต้นปี 2026 อ่าน rationale ของ bridgeless-first tooling ได้จาก Callstack: Bridgeless Native Development

# สร้าง library ใหม่แบบ Nitro
npx create-react-native-library@latest react-native-image-resizer \
  --type nitro-module \
  --languages kotlin-swift

cd react-native-image-resizer
yarn

ในโปรเจ็กต์แอปที่จะใช้งาน ติดตั้งทั้ง runtime และ library ของคุณ:

yarn add react-native-nitro-modules react-native-image-resizer
cd ios && pod install

ไฟล์ nitro.json ที่รูทของ library คือหัวใจของ autolinking, บอกให้ Nitrogen รู้ว่าจะ scan spec ไหน gen C++ ไปไว้ที่ไหน และแมป platform interface ยังไง

{
  "cxxNamespace": ["margelo", "nitro", "imageresizer"],
  "ios": {
    "iosModuleName": "ImageResizer"
  },
  "android": {
    "androidNamespace": ["imageresizer"],
    "androidCxxLibName": "ImageResizer"
  },
  "autolinking": {
    "ImageResizer": {
      "swift": "HybridImageResizer",
      "kotlin": "HybridImageResizer"
    }
  }
}

autolinking key คือชื่อ Hybrid Object ที่ JS จะเรียก ส่วน swift/kotlin คือชื่อ class ที่ Nitrogen จะสร้าง protocol/abstract class ให้เรา implement ต่อ

เขียน .nitro.ts spec สำหรับ Hybrid Object

Spec คือ contract แบบ TypeScript ที่ Nitrogen ใช้ generate ทั้งฝั่ง C++ glue และ signature ให้ Swift/Kotlin หัวใจอยู่ที่ interface ที่ extends HybridObject<T> ตรง T ให้บอกว่าจะ implement ด้วยภาษาอะไรบน platform ไหน

ตัวอย่างจริง ผมยกงานที่พบบ่อยที่สุดในแอป fintech ของทีม, resize รูปก่อนอัปโหลด KYC ซึ่งควรทำใน native เพราะ image buffer ใหญ่และ blocking JS thread ไม่ได้:

// src/specs/ImageResizer.nitro.ts
import type { HybridObject } from 'react-native-nitro-modules'

export interface ImageResizer
  extends HybridObject<{ ios: 'swift'; android: 'kotlin' }> {
  // metadata property (getter อัตโนมัติ)
  readonly maxTextureSize: number

  // sync, เหมาะกับงานเล็ก ๆ ไม่ blocking
  computeAspectRatio(width: number, height: number): number

  // async, ใช้ Promise ให้ Nitrogen gen coroutines ให้ทั้งสองฝั่ง
  resize(
    input: ArrayBuffer,
    targetWidth: number,
    targetHeight: number,
    quality: number
  ): Promise<ArrayBuffer>
}

รัน Nitrogen เพื่อ generate โค้ด, output จะไปอยู่ที่ nitrogen/generated/:

yarn nitrogen
# หรือถ้า pin เวอร์ชัน
yarn nitro-codegen

Implement บน iOS ด้วย Swift และ Promise.async

Nitrogen จะ generate HybridImageResizerSpec ให้เป็น Swift protocol ที่เรา conform โดยไม่ต้องแตะ Objective-C เลย ใช้ Accelerate framework (vImage) เพื่อ resize บน CPU อย่างมีประสิทธิภาพ:

// ios/HybridImageResizer.swift
import Accelerate
import NitroModules

class HybridImageResizer: HybridImageResizerSpec {
  var maxTextureSize: Double { 8192 }

  func computeAspectRatio(width: Double, height: Double) throws -> Double {
    guard height > 0 else { throw RuntimeError.error(withMessage: "height=0") }
    return width / height
  }

  func resize(
    input: ArrayBufferHolder,
    targetWidth: Double,
    targetHeight: Double,
    quality: Double
  ) throws -> Promise<ArrayBufferHolder> {
    // Promise.async รันบน background queue โดยอัตโนมัติ
    return Promise.async {
      let bytes = input.data
      let count = input.size

      // อ่านเป็น UIImage → resize → เขียนกลับเป็น JPEG
      guard let src = self.decode(bytes: bytes, count: count) else {
        throw RuntimeError.error(withMessage: "decode failed")
      }

      let size = CGSize(width: targetWidth, height: targetHeight)
      let resized = self.resizeUsingVImage(src, to: size)

      guard let jpeg = resized.jpegData(compressionQuality: quality) else {
        throw RuntimeError.error(withMessage: "jpeg encode failed")
      }

      // คืนเป็น ArrayBuffer ไปฝั่ง JS แบบ zero-copy
      return ArrayBufferHolder.copy(data: jpeg)
    }
  }
}

สังเกตว่า Promise.async { ... } รัน closure บน Swift Concurrency executor. ถ้ามี await ในนั้นก็เขียนได้ตรง ๆ Nitrogen แปลง throw ให้กลายเป็น JS Promise rejection อัตโนมัติ ไม่ต้อง manual reject

Implement บน Android ด้วย Kotlin coroutines

ฝั่ง Android เขียน Kotlin ตรงกับ Nitrogen-generated abstract class โดยไม่มี Java layer กลาง เลือกใช้ coil หรือ BitmapFactory ตามความสะดวก ผมชอบ BitmapFactory + Bitmap.createScaledBitmap เพราะ dependency น้อย:

// android/src/main/java/com/imageresizer/HybridImageResizer.kt
package com.imageresizer

import android.graphics.Bitmap
import android.graphics.BitmapFactory
import com.margelo.nitro.core.ArrayBuffer
import com.margelo.nitro.core.Promise
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer

class HybridImageResizer : HybridImageResizerSpec() {
  override val maxTextureSize: Double = 8192.0

  override fun computeAspectRatio(width: Double, height: Double): Double {
    require(height > 0) { "height=0" }
    return width / height
  }

  override fun resize(
    input: ArrayBuffer,
    targetWidth: Double,
    targetHeight: Double,
    quality: Double
  ): Promise<ArrayBuffer> = Promise.async {
    withContext(Dispatchers.IO) {
      val bytes = ByteArray(input.size).also {
        input.getBuffer(false).get(it)
      }
      val src = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
        ?: error("decode failed")
      val scaled = Bitmap.createScaledBitmap(
        src, targetWidth.toInt(), targetHeight.toInt(), true
      )
      val out = ByteArrayOutputStream()
      scaled.compress(Bitmap.CompressFormat.JPEG, (quality * 100).toInt(), out)
      val encoded = out.toByteArray()

      // ArrayBuffer.copy จะ allocate direct buffer ไปฝั่ง JS
      val buf = ByteBuffer.allocateDirect(encoded.size).put(encoded)
      ArrayBuffer.wrap(buf)
    }
  }
}

ลงทะเบียนใน HybridObjectRegistry และใช้งานจาก JS

Autolinking จะ register class ให้อัตโนมัติในกรณีทั่วไป แต่ถ้ามีหลาย instance หรืออยาก inject dependency จาก DI container ให้ register manually ใน entry ของ platform:

// ios/ImageResizerPackage.swift
import NitroModules

@objc(ImageResizerPackage)
class ImageResizerPackage: NSObject {
  @objc static func registerNitroTypes() {
    HybridObjectRegistry.registerHybridObjectConstructor("ImageResizer") {
      HybridImageResizer()
    }
  }
}
// android/src/main/java/com/imageresizer/ImageResizerPackage.kt
import com.margelo.nitro.core.HybridObjectRegistry

object ImageResizerPackage {
  fun registerNitroTypes() {
    HybridObjectRegistry.registerHybridObjectConstructor("ImageResizer") {
      HybridImageResizer()
    }
  }
}

ฝั่ง TypeScript สร้าง facade ตัวเดียวเพื่อไม่ให้ consumer เห็น NitroModules API ตรง ๆ ทำให้ mock ใน test ง่ายและเปลี่ยน implementation ในอนาคตได้ (พอ MMKV V4 ทำแบบนี้ก็ทำให้ migration จาก v3 เรียบมาก อ่านเรื่อง storage ทั้งหมดได้ใน คู่มือจัดเก็บข้อมูล React Native 2026: MMKV V4, AsyncStorage, expo-sqlite):

// src/index.ts
import { NitroModules } from 'react-native-nitro-modules'
import type { ImageResizer as Spec } from './specs/ImageResizer.nitro'

let cached: Spec | null = null

export function getImageResizer(): Spec {
  if (!cached) {
    cached = NitroModules.createHybridObject<Spec>('ImageResizer')
  }
  return cached
}

export async function resizeJpeg(
  input: ArrayBuffer,
  w: number,
  h: number,
  quality = 0.85
) {
  return getImageResizer().resize(input, w, h, quality)
}

Nitro Views: สร้าง Fabric View แบบสั้นและ type-safe

ก่อน Nitro Views ออกมาปลายปี 2025 การเขียน Fabric Native Component ต้องแตะ 6–7 ไฟล์ต่อ platform ได้แก่ CodegenSchema, ComponentDescriptor, ShadowNode, EventEmitter และอื่น ๆ Nitro Views ยุบทั้งหมดให้เหลือ spec เดียวกับ interface เดียว

// src/specs/BlurView.nitro.ts
import type { HybridView, HybridViewProps } from 'react-native-nitro-modules'

export interface BlurViewProps extends HybridViewProps {
  radius: number
  tint: 'light' | 'dark' | 'systemMaterial'
}

export interface BlurViewMethods {
  refresh(): void
}

export type BlurView = HybridView<BlurViewProps, BlurViewMethods>
// src/BlurView.tsx
import { getHostComponent } from 'react-native-nitro-modules'
import type { BlurView, BlurViewProps } from './specs/BlurView.nitro'

const BlurViewComponent = getHostComponent<BlurViewProps, BlurView>('BlurView')

export function AppBlurView(props: BlurViewProps) {
  return <BlurViewComponent {...props} />
}

ฝั่ง native implement HybridBlurViewSpec ก็เป็น UIView/ViewGroup ปกติ Nitro จัดการ prop diffing และ event emitting ให้ผ่าน Fabric renderer ผมย้ายชิ้น bottom sheet + skeleton loader ในแอปด้วย pattern นี้แล้วโค้ดหายไปเกิน 60%

ใช้ Nitro Modules กับ Expo และ prebuild

Nitro Modules ใช้กับ Expo ได้ตั้งแต่ SDK 51 ขึ้นไป โดยเงื่อนไขคือต้อง ใช้ prebuild หรือ development build เพราะ Nitro ต้องแทรกโค้ด native หลัง pod install/gradle sync Expo Go ปกติใช้ไม่ได้

ในโปรเจ็กต์ Expo ทั่วไป workflow ที่ผมใช้:

  1. ติดตั้ง library ใน package.json ปกติ
  2. ถ้า library ต้องการ config plugin (เช่น permission) เพิ่มใน app.json
  3. รัน npx expo prebuild --clean เพื่อ regenerate ios/android
  4. build ผ่าน eas build --profile development. อ่าน pipeline เต็มได้ใน คู่มือ EAS Build, Submit และ Update 2026
// app.json (ตัวอย่าง)
{
  "expo": {
    "plugins": [
      "react-native-image-resizer/plugin"
    ],
    "newArchEnabled": true
  }
}

Pitfalls, JNI crash และ workflow การ regen spec

เอาล่ะ ปัญหาที่ผมเจอบ่อยที่สุดตอน audit codebase ของทีมอื่น (และตอน onboarding เพื่อนใหม่เข้าทีมตัวเอง) ก็มีสี่ข้อหลัก ๆ นี้แหละ:

  • ลืมรัน Nitrogen หลังแก้ spec: C++ glue จะไม่ตรงกับ native class ทำให้ symbol not found ตอน link แก้ด้วยการใส่ nitrogen ใน pre-commit hook หรือ bob build pipeline
  • Android JNI thread-attach crash: ถ้าเรียก JS callback จาก coroutine ที่รันนอก main dispatcher โดยไม่ใช้ Promise/Callback helper ที่ Nitro ให้ จะเจอ crash attaching to VM failed ให้ผ่าน NitroModules.currentRuntime หรือใช้ Callback primitive แทนการ hold JSI ref เอง
  • ArrayBuffer ที่ hold ไว้ยาวเกิน: ถ้าคุณเก็บ ArrayBufferHolder ไว้เป็น property นาน ๆ JS side อาจ GC underlying memory ก่อน ให้ copy ทันทีที่ได้รับ ถ้าจะ hold ยาว
  • Deprecated API ระหว่าง v0.34 → v0.36: THIS pointer capture pattern เก่าใช้ไม่ได้ ต้องเปลี่ยนเป็น weak_ptr-based CxxPart ทั้งหมด

สำหรับการ debug ผมใช้ React Native DevTools + Hermes profiler เป็นหลัก. ถ้ายังไม่เคยตั้งค่า อ่าน React Native DevTools 2026: คู่มือดีบักแอปฉบับสมบูรณ์ ก่อน จะช่วยหา bottleneck ก่อนตัดสินใจว่าจะย้ายไป Nitro จริง ๆ ไหม

เมื่อไหร่ที่ไม่ควรใช้ Nitro Modules

เพราะเป็น architect ผมโดนถามคำถามนี้บ่อยกว่า "จะย้ายเมื่อไร" ด้วยซ้ำ Nitro Modules ไม่ใช่ silver bullet:

  • ต้อง support Old Architecture: ใช้ TurboModules ที่มี fallback ไปได้ก่อน
  • ต้องใช้ Expo Go: Nitro ต้อง native build เท่านั้น. Expo Go จะ error ทันที
  • Team ไม่มีคนเขียน Swift/Kotlin: Nitro ตัดชั้น Obj-C/Java ออก ถ้าไม่มีคนอ่านโค้ดสองภาษาใหม่นี้ maintenance cost จะสูง
  • Library ที่ Turbo/Expo Modules ทำงานได้ดีอยู่แล้ว: ประโยชน์จากการย้ายอาจไม่คุ้ม refactor cost โดยเฉพาะ library ที่ไม่ hot-path
  • งานที่ blocking JS thread ไม่ใช่ปัญหา: เช่น setup flow ที่ทำครั้งเดียว การได้ 15× ตรงนั้นไม่รู้สึก

เกณฑ์ตัดสินใจของผม: ย้ายเมื่อ (1) native ถูกเรียกในลูป (frame processor, gesture handler), (2) payload ใหญ่ (image, file), หรือ (3) มี type safety issue ระหว่าง TS ↔ native ที่ทำ bug production มาก่อน ในกรณีอื่น TurboModules/Expo Modules ก็เพียงพอ ถ้าเทียบ list rendering performance ที่ user รู้สึก impact โดยตรง อ่าน FlashList v2 vs FlatList vs LegendList 2026 ประกอบ. บางที bottleneck จริงอยู่ที่ render ไม่ใช่ native bridge

เอกสารเป็นทางการที่ผมเปิดค้างไว้ตอนเขียน spec: Nitro Modules Documentation และ mrousavy/nitro on GitHub. ทั้งสอง update ค่อนข้างเร็ว ให้ track release notes อย่างน้อยเดือนละครั้ง

คำถามที่พบบ่อย

Nitro Modules ใช้ใน production ได้หรือยังในปี 2026?

ได้ครับ v0.36.1 stable และ MMKV V4, VisionCamera V5 รวมถึง library อื่นในเครือ Margelo ใช้ใน production มาแล้วเกินหนึ่งปี ข้อควรระวังคือ API ยังไม่ 1.0 อาจมี breaking change เล็ก ๆ ในระดับ minor version. pin version และ read changelog ทุกครั้งที่อัปเดต

Nitro Modules ทำงานร่วมกับ Expo ได้ไหม?

ได้ ตั้งแต่ Expo SDK 51 ขึ้นไป โดยต้องใช้ expo prebuild หรือ EAS development build เท่านั้น Expo Go ใช้ไม่ได้เพราะ Nitro ต้องแทรก native code หลัง prebuild/pod install

Nitro Modules รันบน Old Architecture ได้ไหม?

ไม่ได้ Nitro ผูกกับ JSI runtime + Fabric renderer โดยตรง ต้องเปิด New Architecture (newArchEnabled=true) และใช้ React Native ≥ 0.78 ถ้ายัง support Old Architecture อยู่ ให้ใช้ TurboModules ก่อน

Nitro Modules เร็วกว่า TurboModules เท่าไร (ตัวเลขจริง)?

บน micro-benchmark 100k calls บน iPhone 15 Pro Nitro เร็วประมาณ 15× (Nitro 7.27ms vs TurboModules 115.86ms สำหรับ addNumbers) บน workload จริงที่มี I/O หรือ compression ช่องว่างหดเหลือประมาณ 8–40% ให้ profile ของแอปตัวเองก่อนตัดสินใจย้าย

ต้องรู้ Swift และ Kotlin ก่อนใช้ Nitro Modules ไหม?

ควรครับ Nitro ตัดชั้น Obj-C และ Java ออกทั้งหมด ทำให้ผู้ใช้ต้องเขียน Swift บน iOS และ Kotlin บน Android โดยตรง ทีมที่ไม่มีคนอ่านสองภาษานี้ต้อง onboarding คน mobile native หรือใช้ TurboModules ที่ทีมเดิม maintain ได้อยู่

Nitro Views ต่างจาก Fabric Native Component เดิมยังไง?

Nitro Views ใช้ spec ไฟล์เดียว (.nitro.ts) แทนการเขียน CodegenSchema + ComponentDescriptor + ShadowNode + EventEmitter แยกกัน ปริมาณโค้ดลดลงประมาณ 60–70% ต่อ component โดยยังใช้ Fabric renderer เหมือนเดิม จึงได้ประสิทธิภาพเท่ากันแต่ maintenance cost ต่ำกว่ามาก

Yelena Petrov
เกี่ยวกับผู้เขียน Yelena Petrov

React Native architect at a fintech. Builds platform teams, type-safe bridges, and runs the upgrade playbook so others don't have to.