npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@xrystal/core

v3.46.0

Published

microservice runtime library

Readme

@xrystal/core

EN — Microservice runtime: Express or Elysia HTTP, DI loaders, live tmp.yml, standardized API responses.
TR — Microservice runtime: Express veya Elysia HTTP, DI loader'lar, canlı tmp.yml, standart API cevapları.

CLI: xry · Version: 3.46.0 · npm


What's new in 3.46.0 / 3.46.0'da ne değişti

EN: SSOT architecture is closed — identity, runtime tooling, paths, and CLI share one source of truth. Missing values throw; layout drift auto-heals.

TR: SSOT mimarisi kapandı — kimlik, runtime tooling, path ve CLI tek kaynaktan. Eksik değer throw; path drift otomatik heal.

| Change / Değişiklik | EN | TR | |---------------------|----|----| | Identity | CLI display = @xrystal/core; invoke bin = xry | Kimlik paket adı, bin xry | | defaults.json | Package name, CLI, brand, runtimes{}, bundlers[] | Paket kimliği + tooling haritası | | tmp.yml | Layout, global.runtime, vars, build.bundler | Proje layout + runtime | | Live pulse | tmp watch → loader sync; path drift → tsconfig heal | Canlı sync + otomatik heal | | Strict normalize | Unknown/missing runtime / bundler throws (empty bundler → auto) | Sessiz ?? 'bun' yok | | Tooling map | Scripts/build/CLI use defaults.runtimes (no bun / x/dist literals) | Hardcode temiz | | xry sync | Force / CI only — daily ritual değil | Force / CI |

npx xry sync          # force-apply tmp.yml → tsconfig (usually auto)
npx xry sync --dry-run

Why / Neden

EN: @xrystal/core is a kernel, not only a GitHub template. Most teams already have their own repo layout, CI, Docker, or monorepo — they only need the runtime: DI, tmp.yml, ctr(), DTO, logger, i18n, trace. Use xry create if you want a starter; otherwise add the package to your project and seed x/tmp.yml.

TR: @xrystal/core bir kernel — sadece GitHub template değil. Çoğu ekipte zaten kendi repo düzeni, CI, Docker veya monorepo vardır; ihtiyaç duyulan şey runtime: DI, tmp.yml, ctr(), DTO, logger, i18n, trace. Sıfırdan başlıyorsan xry create; mevcut projende sadece paketi kur + xry tmp yeter.

# EN: existing project — kernel only · TR: mevcut proje — sadece kernel
bun add @xrystal/core
npx xry tmp          # seed x/tmp.yml into YOUR layout
# tsconfig paths auto-heal from tmp.yml (xry sync = force / CI)
# wire coreLoader + dtoMiddleware in your entry file

Quick start / Hızlı başlangıç

Path A — create (starter template / başlangıç şablonu)

EN: Optional — clone GitHub template when you have no project yet.

TR: İsteğe bağlı — henüz proje yoksa GitHub template klonla.

mkdir my-api && cd my-api
bun init -y
bun add @xrystal/core
npx xry create my-api .
bun add elysia          # EN: or express · TR: veya express
bun run dev
npx xry doctor        # EN: setup check · TR: kurulum kontrolü

Path B — Your project + kernel (most common / en yaygın)

EN: Keep your folder names — only add tmp.yml, coreLoader, controllers.

TR: Kendi klasör adların kalsın — sadece tmp.yml, coreLoader, controller ekle.

mkdir my-api && cd my-api
bun init -y
bun add @xrystal/core elysia
npx xry tmp           # EN: create x/tmp.yml from seed if missing · TR: yoksa seed'den oluşturur
npx xry doctor        # EN: optional health check · TR: isteğe bağlı sağlık kontrolü

package.json:

{
  "dependencies": {
    "@xrystal/core": "^3.46.0",
    "elysia": "^1.4.19"
  },
  "scripts": {
    "dev": "xry dev",
    "build": "xry build",
    "start": "xry start",
    "test": "xry test",
    "sync": "xry sync",
    "doctor": "xry doctor"
  }
}

EN: No global xry install — runs via node_modules/.bin after bun add @xrystal/core.

TR: Global xry gerekmez — @xrystal/core kurulunca node_modules/.bin üzerinden çalışır.

Project layout / Proje düzeni

my-api/
├── package.json
├── source/
│   ├── index.ts          # EN: dev entry (xry dev) · TR: dev girişi
│   └── controllers/
└── x/
    └── tmp.yml           # EN: runtime + path SSOT · TR: runtime + path tek kaynak

Config sources / Config kaynakları

| File / Dosya | Owns / Sahip olduğu | |--------------|---------------------| | defaults.json (package) | Identity (@xrystal/core), CLI bin (xry), brand, runtimes / bundlers tooling | | x/tmp.yml | Service paths, loaders, protocols, global.runtime, bundler · layout seed | | tsconfig.json | Compiler language options; path layout auto-heals from tmp.yml |

EN: Edit tmp.yml for your project. Package defaults stay inside @xrystal/core — do not hardcode runtime/bin/path names in app code.

TR: Projede tmp.yml düzenle. Paket default'ları @xrystal/core içinde kalır — uygulama kodunda runtime/bin/path hardcode etme.

Minimal x/tmp.yml

configs:
  global:
    runtime: bun            # bun | node
  service:
    name: my-api
    framework: elysia       # express | elysia | node | fetch
    paths:
      root: ./x
      source: ./source
    protocols: [http]
    build:
      bundler: auto         # auto | bun | tsc | esbuild
    loaders:
      dto: { withDefaultHeaders: true }
      loggers: { logLevel: info }
      controller: {}

First endpoint / İlk endpoint (~5 min)

// source/controllers/user.ts
import Controller from '@xrystal/core/loader/controller'

class UserController extends Controller {
  list = this.ctr({
    logic: async () => ({ data: [{ id: 1 }], message: '@successfully user' }),
  })
}
export const userCtrl = new UserController()
// source/index.ts
import { Elysia } from 'elysia'
import coreLoader from '@xrystal/core'
import { dtoMiddleware } from '@xrystal/core/utils'
import { userCtrl } from './controllers/user'

await coreLoader({})
new Elysia().use(dtoMiddleware()).get('/users', userCtrl.list).listen(3000)
bun run dev
# GET http://localhost:3000/users

Daily commands / Günlük komutlar

| Command / Komut | EN | TR | |-----------------|----|----| | xry create [name] [path] | Start project from template + tmp.yml | Template + tmp.yml ile proje başlat | | xry tmp | Create tmp.yml from seed if missing | tmp.yml yoksa seed'den oluştur | | xry sync | Force-sync tsconfig from tmp.yml (auto-heals otherwise) | Force sync (normalde otomatik) | | xry dev | Watch source/index.ts + dev env | source/index.ts watch + dev env | | xry build | Output → x/dist | Çıktı → x/dist | | xry start | Run x/dist + prod env | x/dist + prod env | | xry test | Run x/tests | x/tests çalıştır | | xry doctor | CLI, runtime, package health | CLI, runtime, paket uyumu |


Platform features / Platform özellikleri

EN: What the kernel gives you — one line each + minimal example.

TR: Kernel'in verdiği yetenekler — tek satır + kısa örnek.

| Feature / Özellik | EN | TR | Example / Örnek | |-------------------|----|----|-----------------| | Boot / DI | Awilix loaders: System, Logger, Kafka, Dto, i18n, … | Awilix loader'lar | await coreLoader({}) | | Live tmp.yml | Edit file → loaders re-sync; path changes heal tsconfig | Dosya → loader sync; path → tsconfig heal | paths.source rename → tsconfig updates | | ctr() pipeline | checks → logic → response, no res.json() | Endpoint pipeline | list = this.ctr({ logic: async () => ({ data: [] }) }) | | DTO responses | Standard API shape via @xrystal/dto | Standart API cevabı | return { data: [], message: '@successfully user' } | | Trace | requestId per request/job (ALS) | İstek/job trace id | req.requestId · runWithTrace('job-1', fn) | | i18n | i18next + 50+ business message keys | Çok dilli mesajlar | '@successfully user' · req.t('responseMessage.successfully') | | G store | App-wide state from tmp.yml vars | Global state | G.get('maintenance') · checks: () => G.get('x') ? fail : true | | Logger | Winston: console + daily rotate files | Dosya + konsol log | this.logger.log(LoggerLayerEnum.INFO, 'ok') | | Kafka logs | Structured logs → Kafka topic | Log → Kafka | loaders.loggers + configs.kafkaLogsTopic in env | | HTTP client | Retries + circuit breaker + trace header | Dayanıklı outbound HTTP | extend BaseApiClient from loader/clients | | Scope validate | OAuth-style resource:permission check | Scope doğrulama | validateScopes({ scopes, resources, permissions }) | | Realtime | Same ctr() for ws / socket.io | HTTP + ws aynı kalıp | ctr({ logic }, { protocol: 'ws' })guide | | CLI (xry) | create, tmp, sync, dev, build, start, test, doctor | Servis toolchain | "dev": "xry dev" · "sync": "xry sync" | | Shutdown | Graceful cleanup on SIGTERM | Temiz kapanış | auto via registerGracefulShutdown() on boot |

// EN: minimal kernel boot in ANY entry file · TR: herhangi bir giriş dosyasında
import coreLoader from '@xrystal/core'
import { dtoMiddleware, G, runWithTrace } from '@xrystal/core/utils'
import Controller from '@xrystal/core/loader/controller'

await coreLoader({})

class HealthController extends Controller {
  check = this.ctr({
    checks: () => G.get('maintenance') ? { message: '@unsuccessful maintenance' } : true,
    logic: async ({ req }) => ({
      data: { ok: true, trace: req.requestId },
      message: '@successfully',
    }),
  })
}
export const healthCtrl = new HealthController()

await runWithTrace('nightly-job', async () => {
  // EN: cron/queue jobs get trace too · TR: cron/queue job'ları da trace alır
})

Framework quickstarts / Framework hızlı başlangıç

EN: Same kernel — swap only framework in tmp.yml + middleware line.

TR: Aynı kernel — sadece tmp.ymlframework ve middleware satırı değişir.

Elysia (default)

# x/tmp.yml
configs:
  service:
    framework: elysia
    paths: { root: ./x, source: ./source }
import { Elysia } from 'elysia'
import coreLoader from '@xrystal/core'
import { dtoMiddleware } from '@xrystal/core/utils'
import { userCtrl } from './controllers/user'

await coreLoader({})
new Elysia().use(dtoMiddleware()).get('/users', userCtrl.list).listen(3000)

Express

configs:
  service:
    framework: express
import express from 'express'
import coreLoader from '@xrystal/core'
import { dtoMiddleware } from '@xrystal/core/utils'
import { userCtrl } from './controllers/user'

await coreLoader({})
const app = express()
app.use(dtoMiddleware('express'))
app.get('/users', userCtrl.list)
app.listen(3000)

Node (raw http)

configs:
  service:
    framework: node
import http from 'node:http'
import coreLoader from '@xrystal/core'
import { userCtrl } from './controllers/user'

await coreLoader({})
http.createServer((req, res) => userCtrl.list(req, res)).listen(3000)

Fetch / Bun.serve

configs:
  service:
    framework: fetch
import coreLoader from '@xrystal/core'
import { userCtrl } from './controllers/user'

await coreLoader({})
Bun.serve({ fetch: (req) => userCtrl.list(req) })

fetch — no global middleware; ctr() attaches builder per request.

EN: WebSocket & Socket.IO — full guide at the end: Realtime.

TR: WebSocket & Socket.IO — tam rehber sonda: Realtime.

| Framework | tmp.yml | Middleware | |-----------|-----------|------------| | elysia | framework: elysia | dtoMiddleware() | | express | framework: express | dtoMiddleware('express') | | node | framework: node | dtoMiddleware('node') | | fetch | framework: fetch | — (per-request in ctr) |


Reference / Referans

| Topic / Konu | Section / Bölüm | |--------------|-----------------| | Why kernel vs template | Why | | All platform capabilities | Platform features | | Per-framework boot | Framework quickstarts | | Controller, ctr() | Controller | | Express / Elysia detail | Framework | | DI, boot, tmp watch | DI | | Locale / dil | i18n | | Global state | G store | | WebSocket & Socket.IO | Realtime | | API response shape | DTO | | End-to-end flow | Usage guide |


Controller

EN: One endpoint = one class property + this.ctr(). Wire URL → property. No manual res.json().

TR: Bir endpoint = bir property + this.ctr(). URL → property bağla. res.json() yazmazsın.

import Controller from '@xrystal/core/loader/controller'

class UserController extends Controller {
  list = this.ctr({
    logic: async ({ req }) => {
      return { data: [], message: '@successfully user' }
    },
  })
}
export const userCtrl = new UserController()
app.use(dtoMiddleware())
app.get('/users', userCtrl.list)

| In logic / Logic içinde | EN | TR | |---------------------------|----|----| | req.body, req.query, req.params | Normalized input | Normalize girdi | | req.requestId | Trace id | İzleme id | | req.t | i18n function | Çeviri fonksiyonu | | this.logger | Logger from DI | DI logger |


Express & Elysia

EN: See Framework quickstarts for copy-paste per stack. tmp.ymlconfigs.service.framework picks the HTTP layer.

TR: Kopyala-yapıştır örnekler için Framework hızlı başlangıç. tmp.ymlframework HTTP katmanını seçer.

Express

configs:
  service:
    framework: express
import express from 'express'
import coreLoader from '@xrystal/core'
import { dtoMiddleware } from '@xrystal/core/utils'

await coreLoader({})
const app = express()
app.use(dtoMiddleware('express'))
app.get('/users', userCtrl.list)
app.listen(3000)

Elysia

configs:
  service:
    framework: elysia
import { Elysia } from 'elysia'
import coreLoader from '@xrystal/core'
import { dtoMiddleware } from '@xrystal/core/utils'

await coreLoader({})
new Elysia().use(dtoMiddleware()).get('/users', userCtrl.list).listen(3000)

| Framework | Middleware | EN | TR | |-----------|------------|----|----| | express | dtoMiddleware('express') | Default Express stack | Express stack | | elysia | dtoMiddleware() | Default Elysia stack | Elysia stack | | node | dtoMiddleware('node') | Raw createServer | Ham HTTP | | fetch | none | ctr() attaches builder per request | Request başına builder |

EN: Realtime (ws / socket.io) is separate from HTTP — full guide at the end: Realtime.

TR: Realtime (ws / socket.io) HTTP'den ayrı — tam rehber sonda: Realtime.


DI & tmp.yml

EN: Single source of truth: x/tmp.yml at project root. Loaders read a live snapshot, not the file directly.

TR: Tek kaynak: proje kökündeki x/tmp.yml. Loader'lar dosyayı değil canlı snapshot'ı okur.

| Step / Adım | EN | TR | |-------------|----|----| | 1 | await coreLoader({}) at boot — Awilix DI (System, Logger, Dto, Controller, …) | Boot'ta coreLoader — Awilix DI | | 2 | Edit tmp.yml → loaders auto-sync (log level, i18n, dto, …) | tmp.yml değişince loader'lar sync | | 3 | Read config at runtime: getRuntimeTmpConfigs() or DI System | Runtime config: getRuntimeTmpConfigs() veya System |

import coreLoader from '@xrystal/core'
import { getRuntimeTmpConfigs } from '@xrystal/core/utils'

await coreLoader({})
const tmp = getRuntimeTmpConfigs()

EN: loaders.* in tmp.yml enables subsystems (logger, localization, dto, system/gStore, …).

TR: tmp.yml içindeki loaders.* hangi alt sistemlerin açılacağını belirler.


i18n / locale

EN: i18next via loaders.localization in tmp.yml. Use @key in responses or req.t().

TR: loaders.localization ile i18next. Cevaplarda @anahtar veya req.t() kullan.

loaders:
  localization:
    fallbackLang: en
    preloadLangs: [en, tr]
return { message: '@successfully user' }
// or / veya req.t('responseMessage.successfully')

EN: Express — mount i18next middleware before dtoMiddleware.

TR: Express — i18next middleware'i dtoMiddleware'den önce tak.

app.use(i18nextMiddleware.handle(i18n))
app.use(dtoMiddleware('express'))

EN: Client sends Accept-Language: tr → Turkish message.

TR: Client Accept-Language: tr gönderir → Türkçe mesaj döner.


G store

EN: App-wide shared state (Zustand). Bootstrapped from tmp.ymlloaders.system.gStore.vars.

TR: Uygulama genelinde paylaşılan state (Zustand). Boot'ta tmp.ymlgStore.vars.

loaders:
  system:
    gStore:
      vars:
        maintenance: false
import { G } from '@xrystal/core/utils'

G.get('maintenance')
G.set('maintenance', true)

checks: () => G.get('maintenance') ? { message: '@unsuccessful maintenance' } : true

| Store | Lifetime / Ömür | EN | TR | |-------|-----------------|----|----| | G | App / Uygulama | Global flags, cache, maintenance | Global flag, cache, maintenance | | Controller context | Request / İstek | this.req, builder | this.req, builder | | Run context | Request or job / İstek veya job | requestId, log trace | requestId, log trace |


DTO responses / DTO cevapları

EN: Standard { success, message, data, … } via @xrystal/dto. dtoMiddleware() attaches builder; ctr() dispatches the result.

TR: @xrystal/dto ile standart { success, message, data, … }. dtoMiddleware() builder takar; ctr() sonucu dispatch eder.

Details / detay: @xrystal/dto


Usage guide / Kullanım rehberi

EN: Typical flow when building a service.

TR: Servis yazarken tipik akış.

1. Your repo + bun add @xrystal/core + xry tmp   (or xry create for greenfield)
2. tmp.yml       → framework, runtime, loaders, paths (tsconfig auto-heals)
3. coreLoader()  → once at top of YOUR index.ts
4. Controller    → ctr() per endpoint
5. Framework     → dtoMiddleware + routes
6. xry dev       → develop
7. xry build → start → production
8. xry sync      → only when you need a force / dry-run / CI pin

Express — end-to-end / uçtan uca

// source/index.ts
import express from 'express'
import coreLoader from '@xrystal/core'
import { dtoMiddleware } from '@xrystal/core/utils'
import { userCtrl } from './controllers/user'

await coreLoader({})

const app = express()
app.use(dtoMiddleware('express'))
app.get('/users', userCtrl.list)
app.listen(3000)

Elysia — end-to-end / uçtan uca

// source/index.ts
import { Elysia } from 'elysia'
import coreLoader from '@xrystal/core'
import { dtoMiddleware } from '@xrystal/core/utils'
import { userCtrl } from './controllers/user'

await coreLoader({})

new Elysia()
  .use(dtoMiddleware())
  .get('/users', userCtrl.list)
  .listen(3000)

DI + locale + G store together / birlikte

# x/tmp.yml
configs:
  service:
    framework: express
    loaders:
      localization: { fallbackLang: en, preloadLangs: [en, tr] }
      system:
        gStore:
          vars: { maintenance: false }
      dto: { withDefaultHeaders: true }
      loggers: { logLevel: info }
      controller: {}
class UserController extends Controller {
  list = this.ctr({
    checks: () => G.get('maintenance') ? { message: '@unsuccessful maintenance' } : true,
    logic: async ({ req }) => ({
      data: [],
      message: '@successfully user',
    }),
  })
}

Import summary / Import özeti

| What / Ne | Import | |-----------|--------| | Boot | import coreLoader from '@xrystal/core' | | Controller base | import Controller from '@xrystal/core/loader/controller' | | Middleware, G, tmp helpers | import { … } from '@xrystal/core/utils' |


Realtime — WebSocket & Socket.IO

EN: HTTP framework (express, elysia, …) and realtime are separate axes. Realtime uses protocol: 'ws' or 'socketio' on ctr() — not framework in tmp.yml. Integration tests cover both pipelines (34 tests pass).

TR: HTTP framework (express, elysia, …) ile realtime ayrı eksenler. Realtime ctr() üzerinde protocol: 'ws' veya 'socketio' kullanır — tmp.yml'deki framework değil. Her iki pipeline integration test ile doğrulanır (34 test geçer).

HTTP vs realtime / HTTP vs realtime

| Axis / Eksen | tmp.yml | Middleware | Dispatch | |--------------|-----------|------------|----------| | HTTP | framework: elysia | dtoMiddleware() | res.json() / Elysia set | | WebSocket | protocols: [http, ws] | realtimeMiddleware() | socket.send(JSON) | | Socket.IO | protocols: [http, socketio] | realtimeMiddleware('socketio') | socket.emit(event, payload) |

EN: Same controller class can serve HTTP and realtime — different properties, different protocol on ctr().

TR: Aynı controller sınıfı HTTP ve realtime verebilir — farklı property, farklı protocol.

3-step pattern / 3 adım kalıbı

1. realtimeMiddleware()           → socket'a builder tak
2. ctr({ logic }, { protocol })   → checks + logic + i18n (isteğe bağlı)
3. dispatch otomatik              → ws: send · socketio: emit

tmp.ymlprotocols

configs:
  service:
    framework: elysia          # EN: HTTP only · TR: sadece HTTP
    protocols: [http, ws]      # EN: or [http, socketio] · TR: veya [http, socketio]

| protocols in tmp.yml | realtimeMiddleware() | |------------------------|-------------------------| | [http, ws] | realtimeMiddleware() → auto ws | | [http, socketio] | realtimeMiddleware() → auto socketio | | [http, ws, socketio] | Must specify: realtimeMiddleware('ws') or realtimeMiddleware('socketio') |

Native WebSocket (ws)

// source/controllers/chat.ts
import Controller from '@xrystal/core/loader/controller'

class ChatController extends Controller {
  onMessage = this.ctr({
    logic: async ({ req }) => ({
      data: { echo: req.body },
      message: '@successfully',
    }),
  }, { protocol: 'ws' })
}
export const chatCtrl = new ChatController()
// source/index.ts — Express + ws on same port (example)
import { WebSocketServer } from 'ws'
import express from 'express'
import coreLoader from '@xrystal/core'
import { dtoMiddleware, realtimeMiddleware } from '@xrystal/core/utils'
import { chatCtrl } from './controllers/chat'

await coreLoader({})

const app = express()
app.use(dtoMiddleware('express'))

const server = app.listen(3000)
const wss = new WebSocketServer({ server })

wss.on('connection', (socket) => {
  realtimeMiddleware()(socket, () => {})                    // 1) builder attach
  socket.on('message', chatCtrl.onMessage.with(socket))     // 2) ctr + 3) auto send
})

EN: with(socket) binds the socket for that event — core calls socket.send(JSON) after ctr() finishes.

TR: with(socket) socket'ı o event'e bağlar — ctr() bitince core socket.send(JSON) çağırır.

Socket.IO

class ChatController extends Controller {
  onChat = this.ctr({
    logic: async () => ({ data: { ok: true }, message: '@successfully' }),
  }, { protocol: 'socketio' })
}
import { Server } from 'socket.io'
import coreLoader from '@xrystal/core'
import { realtimeMiddleware } from '@xrystal/core/utils'
import { chatCtrl } from './controllers/chat'

await coreLoader({})

const io = new Server(3001, { cors: { origin: '*' } })

io.use((socket, next) => realtimeMiddleware('socketio')(socket, next))  // 1) builder
io.on('connection', (socket) => {
  socket.on('chat', chatCtrl.onChat.with(socket))                        // 2+3) ctr → emit
})

EN: Default emit event comes from Dto config (socket.event, default 'response'). Override in tmp.ymlloaders.dto.

TR: Varsayılan emit event'i Dto config'den gelir (socket.event, default 'response'). tmp.ymlloaders.dto ile override.

Without controller / Controller olmadan

EN: Plain function — use runAction when you don't need checks / i18n / this.logger.

TR: Düz fonksiyon — checks / i18n / this.logger gerekmiyorsa runAction.

import { runAction, getDtoRuntime } from '@xrystal/core/utils'

// WebSocket
socket.on('message', () =>
  runAction({ protocol: 'ws', socket }, async () => {
    return socket.builder.as.ok({ ping: 1 }).build()
  }),
)

// Socket.IO
socket.on('ping', () =>
  runAction({ protocol: 'socketio', socket }, async () => {
    return getDtoRuntime().forSocket(socket).builder.as.ok({ pong: true }).build()
  }),
)

ws vs Socket.IO — transport farkı

| | WebSocket (ws) | Socket.IO | |---|----------------|-----------| | Wire / Kablo | socket.send(JSON string) | socket.emit(event, object) | | ctr() protocol | { protocol: 'ws' } | { protocol: 'socketio' } | | Middleware | realtimeMiddleware() or realtimeMiddleware('ws') | realtimeMiddleware('socketio') | | Same FinalResult | ✅ same builder | ✅ same builder |

EN: Pick ws for native WebSocket clients. Pick socket.io when you need rooms, fallback transport, or existing Socket.IO clients.

TR: Native WebSocket client → ws. Room, fallback transport veya mevcut Socket.IO client → socket.io.

Same logic, two protocols / Aynı logic, iki protocol

class ChatController extends Controller {
  onWs  = this.ctr({ logic: async () => ({ data: { via: 'ws' } }) }, { protocol: 'ws' })
  onSio = this.ctr({ logic: async () => ({ data: { via: 'socketio' } }) }, { protocol: 'socketio' })
}

Maintainer note / Geliştirici notu

EN: This repo is the @xrystal/core source. Bump package.json → commit → xry tag (runs prepare + pushes vX.Y.Z; GitHub Publish workflow releases to npm). Consumers only need bun add @xrystal/core.

TR: Bu repo @xrystal/core kaynağıdır. package.json bump → commit → xry tag (prepare + vX.Y.Z push; GitHub Publish → npm). Tüketici için sadece bun add @xrystal/core yeter.