/** * 엔티티 목록 조회 파라미터입니다. * * ```ts * client.list("post", { * page: 1, limit: 10, * orderBy: "created_time", orderDir: "DESC", * fields: ["seq", "title", "created_time"], * conditions: { status: "active" }, * }); * ``` */ export interface EntityListParams { /** 조회 페이지 번호. 기본값: `1` */ page?: number; /** * 페이지당 레코드 수. 기본값: `20`. * * 단일 요청 상한은 기본 `1000` 이며, 이보다 큰 값을 넘겨도 서버에서 상한으로 잘립니다. * 상한은 엔티티 설정의 `max_limit` 으로 엔티티별 상향 조정할 수 있고, * 기본 페이지 크기는 `default_limit` 으로 조정할 수 있습니다. * 전체 데이터를 받으려면 `page` 를 1씩 늘리며 끝까지 순회하세요. */ limit?: number; /** 정렬 기준 필드명 */ orderBy?: string; /** 정렬 방향. 기본값: `"ASC"` */ orderDir?: "ASC" | "DESC"; /** * 반환할 필드 목록. * * - **미지정 (기본값)**: 엔티티의 인덱스 필드만 반환합니다. * 복호화를 건너뛰기 때문에 **가장 빠릅니다**. * - `["*"]`: 전체 필드 반환 (복호화 수행). * - 필드명 목록: 해당 필드만 반환합니다. * 엔티티 설정에 `index`로 선언된 필드만 지정 가능합니다. * 존재하지 않는 필드명을 지정하면 서버 에러가 발생합니다. * - `seq`, `created_time`, `updated_time`, `license_seq`는 필드에 관계없이 항상 포함됩니다. * * ```ts * // 기본값 (인덱스 필드만, 가장 빠름) * client.list("account") * // 전체 필드 * client.list("account", { fields: ["*"] }) * // seq, name, email만 * client.list("account", { fields: ["seq", "name", "email"] }) * ``` */ fields?: string[]; /** 필터 조건. POST body로 전달됩니다. (예: `{ status: "active" }`) */ conditions?: Record; } /** * `list()`, `history()` 응답의 `data` 필드 구조입니다. * * 서버는 항상 이 구조로 반환합니다: * ```json * { "ok": true, "data": { "items": [...], "total": 100, "page": 1, "limit": 20 } } * ``` */ export interface EntityListResult { items: T[]; /** 전체 레코드 수 */ total: number; /** 현재 페이지 번호 */ page: number; /** 페이지당 레코드 수 */ limit: number; } /** * `query()` 메서드에 전달하는 SQL 쿼리 요청입니다. * * - `sql`: SELECT 전용 SQL. 인덱스 테이블만 조회 가능하며 JOIN 지원. * - `params`: SQL 바인딩 파라미터 (`?` 플레이스홀더 대응). * - `limit`: 최대 반환 건수 (최대 1000. 미지정 시 서버 기본값 적용). * * ```ts * client.query("order", { * sql: `SELECT o.seq, o.status, u.name * FROM order o * JOIN account u ON u.data_seq = o.account_seq * WHERE o.status = ?`, * params: ["pending"], * limit: 100, * }); * ``` */ export interface EntityQueryRequest { sql: string; params?: unknown[]; limit?: number; } /** * `history()` 응답의 개별 이력 레코드 구조입니다. * * - `action`: `"INSERT"` | `"UPDATE"` | `"DELETE_SOFT"` | `"DELETE_HARD"` | `"ROLLBACK"` * - `data_snapshot`: 변경 당시 엔티티 데이터 스냅샷 */ export interface EntityHistoryRecord { seq: number; action: "INSERT" | "UPDATE" | "DELETE_SOFT" | "DELETE_HARD" | "ROLLBACK" | string; data_snapshot: T | null; changed_by: number | null; changed_time: string; } export interface RegisterPushDeviceOptions { platform?: string; deviceType?: string; browser?: string; browserVersion?: string; pushEnabled?: boolean; transactionId?: string; } export type RealtimeConnectionStatus = "disabled" | "idle" | "connecting" | "open" | "closed"; export type RealtimeMessageType = "hello" | "event" | "notification" | "message" | "chat" | "ack" | "error" | "ping" | "pong" | "subscribe" | "unsubscribe"; export interface RealtimeEnvelope { v: number; id: string; ts: string; type: RealtimeMessageType; channel: string; event: string; data?: T; meta?: Record; reply_to?: string; error?: { code: string; message: string; details?: unknown; }; } export interface RealtimeStatusChange { status: RealtimeConnectionStatus; previousStatus: RealtimeConnectionStatus; reason?: string; error?: Error; } export interface RealtimeClientOptions { enabled?: boolean; path?: string; autoReconnect?: boolean; reconnectDelayMs?: number; } export type RealtimeMessageListener = (envelope: RealtimeEnvelope) => void; export type RealtimeStatusListener = (change: RealtimeStatusChange) => void; /** EntityServerClient 생성/설정 옵션입니다. */ export interface EntityServerClientOptions { baseUrl?: string; token?: string; realtime?: boolean | RealtimeClientOptions; /** * 익명 패킷 암호화용 부트스트랩 토큰입니다. * entity-app-server의 `/v1/health` 응답으로 설정되는 용도입니다. */ anonymousPacketToken?: string; csrfEnabled?: boolean; csrfHeaderName?: string; /** * CSRF 토큰이 저장되는 쿠키 이름입니다. * 서버의 `cookie_name` 설정과 일치해야 합니다. * * 기본값: `"_csrf"` */ csrfCookieName?: string; /** * `true`이면 인증된 POST/PUT 요청 바디를 XChaCha20-Poly1305로 암호화합니다. * * 서버의 `EnablePacketEncryption`이 활성화된 경우 필수로 설정해야 합니다. * 로그인(`login()`)·토큰 갱신(`refreshToken()`, `tokenRefresh()`)은 인증 전 요청이므로 자동으로 건너뜁니다. * * 기본값: `false` */ encryptRequests?: boolean; /** * dev 디버그 평문 시크릿입니다. * * 설정하면 모든 요청을 **암호화하지 않고 평문으로** 보내며 `X-Debug-Plain: <시크릿>` 헤더를 * 함께 전송합니다. 서버(AS)의 `DEBUG_PLAIN_SECRET` 환경변수와 값이 일치하면 서버도 해당 * 요청/응답을 평문으로 처리해(패킷 암호화 우회) 운영 환경에서도 통신 내용을 그대로 디버깅할 수 있습니다. * * 보통 `npm run dev`(개발 모드)에서만 주입합니다. 시크릿을 모르면 켤 수 없으므로 운영에서도 안전합니다. * * 기본값: `""`(미설정 → 비활성) */ debugPlainSecret?: string; /** * `true`이면 health tick 시 `X-Session-Bootstrap: 1`로 세션 연장을 함께 시도합니다. * 브라우저 직접 통신에서는 refresh API를 따로 스케줄링하는 대신 이 방식을 권장합니다. * * `healthTickInterval`이 설정되어 있지 않으면 `login()` 성공 후 기본 5분 주기로 health tick이 시작됩니다. * * 연장 성공 시 `onTokenRefreshed`, 더 이상 연장할 수 없으면 `onSessionExpired` 콜백이 호출될 수 있습니다. * * 기본값: `false` */ keepSession?: boolean; /** * Deprecated: timer 기반 refresh를 쓰지 않으므로 더 이상 사용하지 않습니다. * * 기본값: `60` */ refreshBuffer?: number; /** * health tick 자동 실행 주기(ms)입니다. * 설정하면 클라이언트 생성 직후부터 주기적으로 `/v1/health`를 호출합니다. * CSRF 쿠키 갱신과 서버 상태 확인을 자동화합니다. * `keepSession: true`이면 같은 tick에서 세션 연장도 함께 시도합니다. * * 예: `healthTickInterval: 5 * 60 * 1000` → 5분마다 health 호출 * * 기본값: 없음 (자동 실행 안 함) */ healthTickInterval?: number; /** * health 기반 세션 연장 성공 시 호출되는 콜백입니다. * 새 `access_token`과 `expires_in`이 전달됩니다. * health 기반 부트스트랩에서는 `expires_in`이 0일 수 있습니다. */ onTokenRefreshed?: (accessToken: string, expiresIn: number) => void; /** * health 기반 세션 연장 실패 시 호출되는 콜백입니다. * refresh_token 만료 등으로 더 이상 재발급이 불가능한 경우입니다. * 앱은 이 콜백에서 로그인 페이지로 이동하는 등의 처리를 해야 합니다. */ onSessionExpired?: (error: Error) => void; /** * health tick 또는 자동 부트스트랩 health 호출 결과를 알려줍니다. * * - `online: true` — health 호출 성공 * - `online: false` — health 호출 실패 (서버 오류 / 네트워크 단절) */ onHealthChange?: (online: boolean) => void; /** * HMAC 인증용 API Key (`X-API-Key` 헤더). * `hmacSecret`과 함께 설정하면 HMAC 인증 모드로 동작합니다. * **서버 사이드(Node.js 등) 전용. 브라우저에서는 사용하지 마세요.** */ apiKey?: string; /** * HMAC 인증 시크릿. `apiKey`와 함께 설정하면 HMAC 인증 모드로 동작합니다. * * 패킷 암호화 키도 이 값에서 HKDF-SHA256으로 유도합니다: * `key = HKDF-SHA256(hmac_secret, info="entity-server:packet-encryption", salt="entity-server:hkdf:v1")` * * **서버 사이드(Node.js 등) 전용. 브라우저에서는 사용하지 마세요.** */ hmacSecret?: string; } /** `smtpSend()` 요청 파라미터입니다. */ export interface SmtpSendRequest { /** provider 식별자 (생략 시 기본 provider 사용) */ provider?: string; from?: string; to: string[]; cc?: string[]; bcc?: string[]; subject?: string; body_text?: string; body_html?: string; /** 이메일 템플릿 이름 */ template_name?: string; /** 템플릿 변수 */ template_data?: Record; /** 첨부 파일 seq 배열 */ attachments?: number[]; reply_to?: string; ref_entity?: string; ref_seq?: number; } /** `qrcode()` / `qrcodeBase64()` / `qrcodeText()` 공통 옵션 */ export interface QRCodeOptions { /** PNG 크기 픽셀 (기본 256, 최대 2048) */ size?: number; /** 오류 복구 수준 (기본 `"medium"`) */ error_correction?: "low" | "medium" | "high" | "highest"; /** 전경색 hex (기본 `"#000000"`) */ fg_color?: string; /** 배경색 hex (기본 `"#ffffff"`) */ bg_color?: string; } /** `barcode()` 옵션 */ export interface BarcodeOptions { /** 바코드 타입 (기본 `"code128"`) */ type?: "code128" | "code39" | "ean13" | "ean8" | "codabar" | "datamatrix" | "itf"; /** 너비 픽셀 (기본 300, 최대 2048) */ width?: number; /** 높이 픽셀 (기본 100, 최대 2048) */ height?: number; } /** `pdf2png()` 옵션 */ export interface Pdf2PngOptions { /** 해상도 DPI (기본 300) */ dpi?: number; /** 시작 페이지 1-based (기본: 첫 번째 페이지) */ firstPage?: number; /** 종료 페이지 1-based (기본: 마지막 페이지) */ lastPage?: number; } /** 파일 메타 정보 */ export interface FileMeta { uuid: string; original_name: string; size: number; mime_type: string; entity: string; ref_seq?: number; is_public?: boolean; created_time: string; url?: string; } /** `fileUpload()` 옵션 */ export interface FileUploadOptions { /** 파일에 연결할 ref_seq */ refSeq?: number; /** 공개 파일 여부 (기본 서버 설정 따름) */ isPublic?: boolean; } /** 스토리지 메타 정보 별칭 타입 */ export type StorageMeta = FileMeta; /** `storageUpload()` 옵션 */ export interface StorageUploadOptions extends FileUploadOptions { }