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

@lumir-company/dms-client

v0.3.0

Published

DMS 브라우저 클라이언트 SDK (업로드/다운로드/재개/미리보기/progress).

Downloads

1,487

Readme

@lumir-company/dms-client

브라우저 환경에서 DMS 서버와 연동하기 위한 TypeScript SDK입니다.

  • 파일 업로드/다운로드
  • 브라우저 saveAs 다운로드
  • 파일 미리보기 (인라인 표시용 Blob)
  • 폴더 조회/생성/이동/이름 변경/삭제
  • 파일 이동/이름 변경/삭제
  • DRM 복호화 결재 대리결재권자(위임) 등록/조회/삭제 (관리자)
  • 휴가/휴가취소 이벤트 수신 → 대결권자 자동 등록·재산정 (서버 전용 진입점)
  • NAS 직접 접근 (폴더 ID 없이 경로 기준 업로드/다운로드/폴더 생성, 대용량 멀티파트 자동)
  • 업로드/다운로드/미리보기 진행률 콜백
  • SDK 이벤트 관찰
  • 전체 요청 취소

설치

npm install @lumir-company/dms-client

빠른 시작

import { DmsClient } from '@lumir-company/dms-client';

const client = new DmsClient({
  baseUrl: 'https://dms.example.com',
  getToken: async () => '...jwt...',
});

const uploaded = await client.upload(file, {
  folderId: 'folder-123',
  onProgress: (progress) => {
    console.log('upload', progress.percent);
  },
});

const blob = await client.download(uploaded.id, {
  onProgress: (progress) => {
    console.log('download', progress.percent);
  },
});

클라이언트 생성

import { DmsClient } from '@lumir-company/dms-client';

const client = new DmsClient({
  baseUrl: 'https://dms.example.com',
  getToken: async ({ forceRefresh } = {}) => {
    return forceRefresh ? 'new-token' : 'cached-token';
  },
  apiKey: 'dmsp_xxx',
  tags: {
    app: 'browser-admin',
    screen: 'file-manager',
  },
  observer: {
    onEvent(event) {
      console.log(event.type, event.operation, event.traceId);
    },
  },
  multipartThreshold: 100 * 1024 * 1024,
  chunkSize: 5 * 1024 * 1024,
  concurrency: 3,
  retry: {
    maxAttempts: 3,
    initialDelayMs: 500,
    maxDelayMs: 10000,
  },
});

주요 옵션

  • baseUrl: DMS 서버 절대 URL. http 또는 https만 허용됩니다.
  • getToken: 요청마다 Bearer 토큰을 반환하는 비동기 함수입니다.
  • apiKey: 서비스 프로젝트 API 키입니다. 설정하면 업로드/폴더 생성/파일 및 폴더 쓰기 요청에 자동 포함됩니다.
  • tags: 모든 요청에 공통으로 붙는 추적 태그입니다.
  • observer.onEvent: SDK 이벤트 수신기입니다.
  • multipartThreshold: 이 크기 이상 파일은 multipart 업로드를 사용합니다.
  • chunkSize: multipart 업로드 청크 크기입니다.
  • concurrency: multipart 업로드 동시 전송 수입니다.
  • retry: 네트워크/서버 오류 재시도 정책입니다.

업로드

기본 업로드

const result = await client.upload(file, {
  folderId: 'folder-123',
});

진행률 표시

const result = await client.upload(file, {
  folderId: 'folder-123',
  onProgress: ({ bytesUploaded, bytesTotal, percent, partsCompleted, partsTotal }) => {
    console.log(bytesUploaded, bytesTotal, percent, partsCompleted, partsTotal);
  },
});

업로드 모드 강제

await client.upload(file, {
  folderId: 'folder-123',
  force: 'simple',
});

await client.upload(file, {
  folderId: 'folder-123',
  force: 'multipart',
});

큐 대기 처리

서버가 multipart 업로드를 바로 시작하지 못하면 onQueued가 호출될 수 있습니다.

await client.upload(file, {
  folderId: 'folder-123',
  onQueued: async ({ ticket, proceed, cancel }) => {
    console.log('queued', ticket);

    const userAccepted = true;
    if (userAccepted) {
      await proceed();
      return;
    }

    await cancel();
  },
});

lockPassword 사용

apiKey를 사용하지 않는 환경이면 쓰기 요청에서 lockPassword를 넘길 수 있습니다.

await client.upload(file, {
  folderId: 'folder-123',
  lockPassword: 'my-secret',
});

업로드 반환값

upload()는 서버 응답을 그대로 포함한 UploadResult를 반환합니다.

const uploaded = await client.upload(file, { folderId: 'folder-123' });

console.log(uploaded.id);
console.log(uploaded.name);
console.log(uploaded.size);
console.log(uploaded.state);
console.log(uploaded.syncEventId);

다운로드

Blob 다운로드

const blob = await client.download('file-123');

진행률 표시

const blob = await client.download('file-123', {
  onProgress: ({ bytesDownloaded, bytesTotal, percent }) => {
    console.log(bytesDownloaded, bytesTotal, percent);
  },
});

브라우저 저장

await client.saveAs('file-123');
await client.saveAs('file-123', 'report.pdf');

이어받기

const partialBlob = new Blob([partialBytes], { type: 'application/pdf' });

const completed = await client.download('file-123', {
  resume: {
    fromBlob: partialBlob,
    etag: '"etag-value"',
  },
});

미리보기

서버 GET /v1/files/:fileId/preview 를 호출해 파일 바이트를 그대로 받습니다. 서버는 Content-Disposition: inline 으로 응답하므로, 브라우저가 렌더링 가능한 타입(이미지/PDF/비디오/오디오/텍스트 등)은 그대로 화면에 띄울 수 있고, 그렇지 않은 타입은 폴백 처리할 수 있습니다.

Bearer 인증을 쓰는 SDK 특성상 <img src="..."> 에 URL 을 직접 박을 수 없습니다. 대신 받아온 Blob 을 URL.createObjectURL 로 변환해서 사용합니다.

기본 사용

const blob = await client.preview('file-123');

const objectUrl = URL.createObjectURL(blob);
imgEl.src = objectUrl;

// 사용이 끝나면 메모리 해제
URL.revokeObjectURL(objectUrl);

진행률 표시

const blob = await client.preview('file-123', {
  onProgress: ({ bytesDownloaded, bytesTotal, percent }) => {
    console.log(bytesDownloaded, bytesTotal, percent);
  },
});

타입별 렌더링 예시

const blob = await client.preview('file-123');
const url = URL.createObjectURL(blob);

if (blob.type.startsWith('image/')) {
  const img = document.createElement('img');
  img.src = url;
  document.body.appendChild(img);
} else if (blob.type === 'application/pdf') {
  const frame = document.createElement('iframe');
  frame.src = url;
  document.body.appendChild(frame);
} else if (blob.type.startsWith('video/')) {
  const video = document.createElement('video');
  video.src = url;
  video.controls = true;
  document.body.appendChild(video);
}

preview() 는 내부적으로 다운로드와 동일한 스트리밍 파이프라인을 사용하므로 signal, tags, resume 옵션도 동일하게 받습니다.

폴더 API

조회

const info = await client.folders.getInfo('folder-123');
const root = await client.folders.getRoot();
const qms = await client.folders.getQms();

const contents = await client.folders.listContents('folder-123', {
  page: 1,
  pageSize: 50,
  sort: 'name,asc',
});

const tree = await client.folders.getTree('folder-123');
const treeByPath = await client.folders.getTreeByPath('/projects/2026');

const searched = await client.folders.search('folder-123', {
  keyword: 'report',
  page: 1,
  pageSize: 20,
});

생성

const created = await client.folders.create({
  name: '2026 계획',
  parentId: 'parent-folder-id',
});

const createdWithPassword = await client.folders.create({
  name: '보안 폴더',
  parentId: 'parent-folder-id',
  lockPassword: 'my-secret',
});

이동, 이름 변경, 삭제

await client.folders.move('folder-123', {
  targetParentId: 'target-parent-id',
});

await client.folders.rename('folder-123', {
  name: '변경된 폴더명',
});

await client.folders.delete('folder-123');

파일 API

await client.files.move('file-123', {
  targetFolderId: 'target-folder-id',
});

await client.files.rename('file-123', {
  name: 'renamed-report.pdf',
});

await client.files.delete('file-123');

DRM 대리결재권자 API

DRM 복호화 요청 결재의 대리결재권자(위임) 를 관리합니다. 원결재자(위임자)의 부재 기간 동안 결재 권한을 대리자(수임자)에게 위임하면, 기간 중 신규 상신되는 복호화 요청은 상신 시점에 대리자로 라우팅되고 기간이 끝나면 신규 건은 자동으로 원결재자로 원복됩니다. 이미 대리자에게 배정된 미결 건은 대리자가 계속 처리합니다.

서버 엔드포인트(/v1/drm-approval-delegations)는 관리자용입니다. 다만 현재 서버는 JWT 인증만 검증하며 역할(Role) 가드는 미적용 상태이므로, 일반 사용자 토큰으로도 호출이 성공할 수 있습니다. 화면 노출은 호출 측에서 관리자 권한으로 제한하세요. apiKey 는 사용하지 않으며 요청 body 에도 주입되지 않습니다.

등록

const delegation = await client.approvalDelegations.create({
  delegatorEmployeeNumber: '20028', // 원결재자(위임자) 사번
  delegateEmployeeNumber: '23027', // 대리자(수임자) 사번
  startAt: '2026-07-01T00:00:00+09:00',
  endAt: '2026-07-07T23:59:59+09:00',
  reason: '하계 휴가', // 선택 (최대 500자)
});

console.log(delegation.id, delegation.source); // 'MANUAL'

검증 규칙 — 위반 시 서버가 400 을 반환하며 SDK 는 DmsValidationError 로 throw 합니다.

  • delegatorEmployeeNumber !== delegateEmployeeNumber (원결재자와 대리자가 같을 수 없음)
  • endAt > startAt

등록은 서버에서 중복 방어(멱등 처리)를 하지 않습니다. 네트워크 오류로 SDK 가 자동 재시도하면 동일 구간이 두 건 등록될 수 있습니다(겹치는 위임은 가장 최근 생성건이 적용되므로 동작은 동일). 중복이 신경 쓰이는 화면이라면 등록 후 list() 로 확인하거나 retry.maxAttempts: 1 로 클라이언트를 생성하세요.

목록 조회

const all = await client.approvalDelegations.list();

const active = await client.approvalDelegations.list({
  delegatorEmployeeNumber: '20028',
  activeOnly: true, // 조회 시점에 기간 내인 위임만
  page: 1,
  pageSize: 20, // 최대 100
});

console.log(active.items, active.totalItems, active.hasNext);

응답은 items + 페이지네이션 필드(page, pageSize, totalItems, totalPages, hasNext, hasPrev)로 구성되며 startAt 내림차순으로 정렬됩니다.

삭제(취소)

await client.approvalDelegations.delete(delegation.id);

서버가 204(본문 없음)를 반환하므로 반환값은 없습니다. 미등록 id 는 DmsNotFoundError 입니다. 삭제 후 해당 원결재자의 신규 건은 원결재자로 처리됩니다.

등록 출처(source)

대결권자를 등록하는 경로는 2개입니다.

| 값 | 등록 경로 | 인증 | | --- | --- | --- | | MANUAL | 관리자가 이 API 로 직접 등록 | 사용자 JWT | | LEAVE_EVENT | 휴가 이벤트 수신으로 서버가 자동 등록 | 사전 공유 토큰 (아래 참조) |

휴가 이벤트로 자동 등록된 건(LEAVE_EVENT)도 목록에 함께 조회되며 이 API 로 삭제할 수 있습니다. source 컬럼 도입 이전 버전의 서버는 응답에 source 를 포함하지 않으므로 타입상 optional 입니다.

휴가 이벤트 API (서버 전용)

그룹웨어·인사 시스템이 휴가 일정을 DMS 로 push 하면 대결권자 위임이 자동 등록·재산정됩니다. 이 API 는 사전 공유 토큰 헤더로 인증하므로 브라우저에서 호출할 수 없습니다 — 토큰이 번들에 포함되면 그대로 유출됩니다. 그래서 별도 서브패스로만 노출됩니다.

import { DmsServerClient } from '@lumir-company/dms-client/server';

const server = new DmsServerClient({
  baseUrl: process.env.DMS_BASE_URL!,     // 운영 https://ldms.lumir.space
  authToken: process.env.DMS_LEAVE_EVENT_TOKEN!, // 서버 LEAVE_EVENT_AUTH_TOKEN 과 일치
  // authHeaderName: 'x-leave-event-auth', // 기본값. 서버 설정을 바꿨을 때만 지정
});

토큰은 x-leave-event-auth 헤더에 Bearer 접두사 없이 실립니다.

휴가 등록

const res = await server.leaveEvents.submit({
  delegatorEmployeeNumber: '20028', // 휴가자(원결재자)
  delegateEmployeeNumber: '23027', // 대결권자(대리자)
  dates: ['2026-07-01', '2026-07-02', '2026-07-04'], // 하루 단위, KST
  reason: '하계 휴가', // 선택
});

// 이어지는 날짜는 하나의 구간으로 묶임
// res.ranges → [{ 7-01 ~ 7-02 }, { 7-04 ~ 7-04 }], res.createdCount → 2
  • 날짜는 하루 단위 배열(YYYY-MM-DD), 순서·중복 무관
  • 동일 구간 재전송은 멱등 — 중복 생성 없이 createdCount: 0
  • 400: 빈 배열 / 형식 위반 / 휴가자=대결권자 / 미존재 사번, 401: 토큰 불일치(errorCode 10409)

휴가 취소

const res = await server.leaveEvents.cancel({
  delegatorEmployeeNumber: '20028',
  dates: ['2026-07-02'], // 취소일만
});

// [7/1~7/3] 에서 7/2 취소 → [7/1], [7/3] 로 분할 재등록
// res.deletedCount → 1, res.createdCount → 2, res.remaining → 남은 구간

취소는 휴가 이벤트 출처(LEAVE_EVENT) 위임만 재산정하며, 관리자가 수동 등록한 MANUAL 위임은 건드리지 않습니다.

연동 문서

이벤트를 전송하는 측 담당자에게 전달할 API 계약서는 DMS 서버 저장소에 있습니다.

| 위치 | 용도 | | --- | --- | | docs/leave-events-api/README.md | 연동 안내(엔드포인트·인증·규칙·curl 예시) | | docs/leave-events-api/openapi.yaml | OpenAPI 3.0 정본 (Swagger Editor/Postman import) | | docs/leave-events-api/redoc.html | 더블클릭으로 여는 오프라인 ReDoc 문서 | | https://ldms.lumir.space/api-docs-redoc | 운영 서버가 서빙하는 전체 API 문서 (태그 724.휴가/대결권자 이벤트 수신 API) | | https://ldms-dev.lumir.space/api-docs-redoc | 개발 서버 동일 |

NAS 직접 접근

client.nas 는 Lumir Drive 싱크 엔진과 같은 경로 기반 계약(/nas/*)을 씁니다. 폴더 UUID 를 먼저 조회해야 하는 client.upload() 와 달리, 폴더 경로 + 파일명만으로 NAS 에 바로 씁니다. 인증은 동일한 사용자 Bearer 토큰(getToken)입니다.

업로드 대상 경로 찾기

folderPath 는 서버가 만들어 주는 값이라 임의로 조합할 수 없습니다. 진입점 목록에서 가져옵니다.

const roots = await client.nas.myRoots();
// [{ folderPath: '/공유문서', canWrite: true, kind: 'shared', name: '공유문서' }, ...]

const target = roots.find((r) => r.canWrite);

폴더 만들기

업로드는 폴더를 자동으로 만들지 않습니다. 없는 경로로 올리면 404 로 거부되므로 먼저 만듭니다. 멱등이라 이미 있으면 그대로 성공합니다.

await client.nas.createFolder({ path: 'ProjectNas', name: '2026' });
await client.nas.createFolder({ path: 'ProjectNas/2026', name: '설계' });

여러 단계를 한 번에 만들 수는 없어 위에서부터 한 단계씩 호출해야 합니다. 루트(진입점) 자체는 만들 수 없습니다 — 새 루트가 필요하면 공유폴더 관리에서 등록해야 합니다.

업로드

const result = await client.nas.upload({
  folderPath: `${target.folderPath}/2026`,
  filename: 'report.pdf',
  data: file,            // File | Blob | ArrayBuffer | Uint8Array
  overwrite: false,      // 기본값. 같은 이름이 있으면 412
});

// { etag: 'W/"..."', mode: 'simple' | 'multipart', size: 1234, parts?: 3 }

크기에 따라 전송 방식을 자동으로 고릅니다. 8MB(NAS_MULTIPART_THRESHOLD) 미만은 단일 요청, 그 이상은 멀티파트로 전환해 서버가 알려준 partSize 로 나눠 순차 전송한 뒤 조립합니다. 중간에 실패하면 서버 세션을 정리하는 중단 요청을 보냅니다.

기준이 8MB 로 낮은 이유: 서버의 단일 업로드 핸들러가 본문을 통째로 버퍼링해 요청이 길어집니다. 실측에서 25MB 단일 업로드가 183초 걸렸고, 그 사이 응답이 유실되면 재시도가 이미 만들어진 파일과 충돌해 파일은 저장됐는데 412 로 실패하는 상태가 됩니다. 큰 파일은 멀티파트로 보내세요.

// 이 호출에 한해 전환 기준을 바꾼다
await client.nas.upload(input, { multipartThreshold: 32 * 1024 * 1024 });

덮어쓰기와 낙관적 잠금

try {
  await client.nas.upload({ folderPath, filename, data, overwrite: false });
} catch (e) {
  if (e instanceof DmsConflictError) {
    // 412 — 같은 이름이 이미 있음. 사용자에게 덮어쓰기 여부를 묻고 overwrite: true 로 재시도.
  }
}

// 내가 읽은 그 버전일 때만 덮어쓰기 (단일 업로드 전용)
await client.nas.upload({ folderPath, filename, data, overwrite: true, ifMatch: prevEtag });

다운로드

경로로 바로 내려받습니다. 포털 파일 API 의 client.download(fileId) 와 달리 UUID 가 필요 없습니다.

const { blob, contentType, etag } = await client.nas.download('ProjectNas/2026/report.pdf');

// 부분 다운로드 / 이어받기 — 앞서 받은 etag 를 ifRange 로 넘기면
// 그 사이 파일이 바뀌었을 때 서버가 전체를 다시 보냅니다.
const rest = await client.nas.download(path, {
  range: { start: received.size },
  ifRange: etag,
});
console.log(rest.partial);  // 206 이면 true

진행률

업로드 진행률은 observer 로 관찰합니다(멀티파트는 파트마다 발행).

const client = new DmsClient({
  baseUrl,
  getToken,
  observer: {
    onEvent: (e) => {
      if (e.operation === 'nas:upload' && e.type === 'upload:progress') {
        console.log(`${e.percent}% (${e.partsCompleted}/${e.partsTotal})`);
      }
    },
  },
});

주의사항

  • 동시 업로드: 멀티파트 세션은 서버에서 (folderPath, filename) 으로 식별됩니다(세션 ID 없음). 같은 폴더에 같은 파일명으로 동시에 올리면 서로의 파트를 덮어쓰고, 중단 요청도 상대 세션을 지웁니다. 파일명을 다르게 하거나 호출 측에서 직렬화하세요.
  • 슬롯 대기: 서버가 동시 업로드 수를 제한합니다. 슬롯이 없으면 503 이 오고, SDK 가 기본 5회 × 3초 간격으로 기다렸다 재시도합니다(slotWaitAttempts, slotWaitDelayMs 로 조정).
  • 잠긴 폴더: canWrite: false 인 루트에는 쓸 수 없습니다.

공통 옵션

업로드/다운로드/폴더/파일 API 일부는 아래 옵션을 공통으로 받습니다.

  • signal: AbortController로 요청 취소
  • tags: 요청별 추적 태그 추가
  • lockPassword: 쓰기 요청 시 문서 잠금 비밀번호 전달

예시:

const controller = new AbortController();

await client.files.rename(
  'file-123',
  { name: 'report-final.pdf' },
  {
    signal: controller.signal,
    tags: { feature: 'bulk-rename' },
    lockPassword: 'my-secret',
  },
);

취소

단일 요청 취소

const controller = new AbortController();

const promise = client.download('file-123', {
  signal: controller.signal,
});

controller.abort();
await promise;

전체 요청 취소

client.cancelAll();

이벤트 관찰

observer.onEvent로 SDK 내부 이벤트를 구독할 수 있습니다.

const client = new DmsClient({
  baseUrl: 'https://dms.example.com',
  getToken: async () => 'token',
  observer: {
    onEvent(event) {
      switch (event.type) {
        case 'session:start':
        case 'session:complete':
        case 'session:aborted':
        case 'upload:progress':
        case 'download:progress':
        case 'request:start':
        case 'request:complete':
        case 'request:error':
        case 'request:retry':
        case 'queue:wait':
          console.log(event);
          break;
      }
    },
  },
});

대표 event.type:

  • session:start
  • session:complete
  • session:aborted
  • request:start
  • request:complete
  • request:error
  • request:retry
  • upload:progress
  • download:progress (다운로드와 미리보기 모두에서 발생, event.operation 으로 구분)
  • queue:wait
  • resume:persisted
  • resume:matched
  • resume:mismatch

resumePending

resumePending()은 현재 IndexedDB에 저장된 보류 업로드 세션 목록만 반환합니다. 실제 업로드 재개 실행까지 자동으로 처리하지는 않습니다.

const pendingSessions = await client.resumePending();
console.log(pendingSessions);

에러 처리

SDK는 다음 에러 클래스를 export 합니다.

  • DmsSdkError
  • DmsNetworkError
  • DmsHttpError
  • DmsAuthError
  • DmsNotFoundError
  • DmsValidationError
  • DmsAbortError
  • DmsQueueError
  • DmsResumeError
  • DmsConfigError
import { DmsAuthError, DmsHttpError } from '@lumir-company/dms-client';

try {
  await client.download('file-123');
} catch (error) {
  if (error instanceof DmsAuthError) {
    console.error('auth failed', error.status, error.traceId);
  } else if (error instanceof DmsHttpError) {
    console.error('http error', error.status, error.body);
  } else {
    console.error(error);
  }
}

TypeScript export

패키지는 아래 주요 타입을 함께 export 합니다.

import type {
  DmsClientConfig,
  UploadOptions,
  DownloadOptions,
  UploadProgress,
  DownloadProgress,
  UploadResult,
  WriteOpts,
  FolderReadOpts,
  FolderInfo,
  RootFolderInfo,
  FolderTreeNode,
  FolderFlatNode,
  FolderContents,
  FolderItem,
  GetFolderContentsQuery,
  SearchFolderContentsQuery,
  CreateFolderRequest,
  CreateFolderResponse,
  MoveFolderRequest,
  RenameFolderRequest,
  MoveFileRequest,
  RenameFileRequest,
  ApprovalDelegation,
  ApprovalDelegationList,
  ApprovalDelegationOpts,
  CreateApprovalDelegationRequest,
  ListApprovalDelegationsQuery,
  DelegationSource,
  SdkEvent,
  SdkEventBase,
  SdkOperation,
} from '@lumir-company/dms-client';

휴가 이벤트 타입은 서버 전용 진입점에서 export 됩니다.

import type {
  DmsServerClientConfig,
  LeaveEventOpts,
  LeaveEventRequest,
  LeaveEventResult,
  LeaveCancelEventRequest,
  LeaveCancelEventResult,
  DateRun,
  DelegateDateRun,
} from '@lumir-company/dms-client/server';

개발

npm install
npm run build
npm test
npm run test:int
npm run demo

참고

  • 브라우저 데모: examples/browser-demo
  • 상세 스펙 문서: docs/specs/

📦 배포 (자동)

이 패키지(@lumir-company/dms-client)는 release-please + self-hosted GitHub Actions 러너로 자동 배포됩니다. 버전을 수동으로 올리거나 npm publish를 직접 실행할 필요가 없습니다.

버전 규칙 (Semantic Versioning)

커밋 메시지(Conventional Commits)가 다음 버전을 자동 결정합니다:

| 커밋 prefix | 버전 증가 | 예시 | | --- | --- | --- | | fix: … | patch | 1.2.3 → 1.2.4 | | feat: … | minor | 1.2.3 → 1.3.0 | | feat!: … 또는 본문 BREAKING CHANGE: | major | 1.2.3 → 2.0.0 | | chore: docs: refactor: test: 등 | 릴리스 없음 | — |

배포 흐름

  1. main 브랜치에 conventional commit을 push 한다.
  2. release-pleasechore(main): release X.Y.Z 형태의 Release PR을 자동 생성한다 (버전 bump + CHANGELOG.md 갱신).
  3. 그 PR을 검토 후 머지한다. ← 사람이 하는 유일한 단계
  4. 머지되면 자동으로 Git 태그 + GitHub Release 생성 + npm publish 가 실행된다 (.github/workflows/release.yml, self-hosted 러너 lumir-ci).

npm 인증은 repo secret NPM_TOKEN(Automation/Publish 토큰), release-please는 RELEASE_PLEASE_TOKEN(PAT)을 사용한다.