TypeScript / Node SDK
@junyul/core (runtime-agnostic) + 런타임별 얇은 어댑터. 노드 서버·Next.js· 브라우저·엣지 런타임에서 같은 이벤트 모델을 공유합니다.
릴리스 채널. 공개 npm 배포 전에는 도입 고객에게 제공되는 고객 전용 registry 또는 검증된 tarball로 설치합니다. 아래 명령은 공식 registry 또는 고객 registry가 연결된 환경 기준이며, 접근 권한은 support@junyul.com에서 확인합니다.
패키지 구조
| 패키지 | 런타임 | 비고 |
|---|---|---|
| @junyul/core | universal (fetch + crypto.subtle) | 타입 + UUIDv7 + CircuitBreaker + MemoryOutbox |
| @junyul/node | Node 20+ | Express/Fastify 미들웨어, NestJS 모듈, SqliteOutbox |
| @junyul/browser | 브라우저 | 고지 배너 + 원격 로그 (제한적) |
| @junyul/react | React 18+ | 훅: useTrack, useDisclosure |
| @junyul/vue | Vue 3 | Composable: useJunyul |
설치
# 공개 npm 릴리스 또는 고객 전용 registry 연결 후
pnpm add @junyul/node
# Edge, Worker, 라이브러리처럼 런타임 중립 코어만 필요한 경우
pnpm add @junyul/core브라우저 전용:
pnpm add @junyul/browser
pnpm add @junyul/react # React 앱
pnpm add @junyul/vue # Vue 앱초기화
import { init, recordDecision } from '@junyul/node';
init({
apiKey: process.env.JUNYUL_API_KEY!,
tenantSecret: process.env.JUNYUL_TENANT_SECRET!, // HMAC anonymization
environment: 'production',
});
recordDecision('automated_decision_made', {
assetId: 'credit_scoring_v3',
legal_bases: ['ai_basic_law_kr', 'credit_info_36_2_kr'],
risk_tier: 'high',
});Express 통합
import express from 'express';
import { complyMiddleware, attachDisclosure } from '@junyul/node';
const app = express();
app.use(complyMiddleware({ defaultAssetId: 'chatbot_v1', locale: 'ko' }));
app.get('/chat', attachDisclosure('chatbot_v1'), handler);Next.js (App Router) 통합
// app/api/ai/route.ts
import { track } from '@junyul/core';
export async function POST(req: Request) {
return track('chatbot_v1', async () => {
const body = await req.json();
return Response.json(await callLLM(body.message));
});
}성능 & 신뢰성
- 32 event types discriminated union · Zod 런타임 검증
- UUIDv7 (time-sortable) · UUIDv5(idempotencyKey) 결정적 event_id
- CircuitBreaker (5 실패 → OPEN 30s cooldown → HALF_OPEN)
- Outbox: 브라우저/엣지 Memory, Node는
@junyul/node의SqliteOutbox(better-sqlite3) 선택 가능 - HMAC-SHA256 subject_hash: 원본 user_id는 절대 전송되지 않음