@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-runWhy / 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 fileQuick 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 kaynakConfig 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/usersDaily 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.yml → framework 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: expressimport 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: nodeimport 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: fetchimport 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.yml → configs.service.framework picks the HTTP layer.
TR: Kopyala-yapıştır örnekler için Framework hızlı başlangıç. tmp.yml → framework HTTP katmanını seçer.
Express
configs:
service:
framework: expressimport 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: elysiaimport { 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.yml → loaders.system.gStore.vars.
TR: Uygulama genelinde paylaşılan state (Zustand). Boot'ta tmp.yml → gStore.vars.
loaders:
system:
gStore:
vars:
maintenance: falseimport { 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 pinExpress — 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: emittmp.yml — protocols
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.yml → loaders.dto.
TR: Varsayılan emit event'i Dto config'den gelir (socket.event, default 'response'). tmp.yml → loaders.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.
