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

@promr-acorda/core

v0.6.8

Published

Framework-agnostic electronic contract document engine

Readme

@promr-acorda/core

전자계약 문서를 다루는 프레임워크 독립적인 엔진입니다. 불변(immutable) 문서/필드 조작, 좌표 변환, 검증, 공유(source/mirror) 필드 처리, PDF 내보내기 기능을 제공합니다. 모든 조작 함수는 문서를 직접 변경하지 않고 다음 상태의 새 ContractDocument를 반환합니다.

설치

npm install @promr-acorda/core

빠른 시작

import {
  createDocument,
  createField,
  setFieldValue,
  validateDocument,
  getResolvedValues,
  exportToPdf,
  type ContractDocument,
} from '@promr-acorda/core';

let document: ContractDocument = createDocument({
  id: 'contract-001',
  title: '근로계약서',
  pdfData: pdfBytes,
  pageCount: 1,
  pages: [{ index: 0, width: 612, height: 792 }],
});

document = createField(document, {
  id: 'employeeName',
  name: '직원명',
  type: 'text',
  page: 0,
  x: 0.1,
  y: 0.2,
  width: 0.35,
  height: 0.05,
  placeholder: '이름 입력',
  required: true,
});

document = setFieldValue(document, 'employeeName', '홍길동');

const validation = validateDocument(document);
const values = getResolvedValues(document);

const pdfBytes2 = await exportToPdf(document);

ContractDocument 구조

interface ContractDocument {
  readonly id: string;
  readonly title: string;
  readonly pdfData: Uint8Array;
  readonly pageImages?: readonly Uint8Array[];
  readonly pageCount: number;
  readonly pages: readonly PageInfo[];
  readonly fields: readonly ContractField[];
  readonly fieldValues: FieldValueMap;
  readonly sharedValues: SharedValueMap;
  readonly createdAt: string;
  readonly updatedAt: string;
}
  • pdfData는 PDF 내보내기와 PDF 기반 렌더링에 사용되는 원본 PDF 바이트입니다.
  • pageImages가 있으면(@promr-acorda/react 사용 시) 이미지 기반 렌더링을 우선 사용합니다.
  • fieldValues는 각 필드에 직접 입력된 값, sharedValues는 공유 필드 그룹의 값입니다. 둘 다 직접 다루기보다는 아래 함수들을 통해 조작/조회하는 것을 권장합니다.

필드 좌표

필드의 위치와 크기는 픽셀이 아니라 페이지 대비 비율(0~1)로 저장됩니다.

| 필드 | 설명 | | --- | --- | | page | 0부터 시작하는 페이지 인덱스 | | x, y | 페이지 너비/높이 대비 좌측·상단 기준 시작 위치 비율 | | width, height | 페이지 너비/높이 대비 크기 비율 |

import { toAbsoluteRect, toNormalizedRect, normalizeRect } from '@promr-acorda/core';

const abs = toAbsoluteRect({ page: 0, x: 0.1, y: 0.2, width: 0.35, height: 0.05 }, 612, 792);

const normalized = toNormalizedRect(
  { page: 0, x: 61.2, y: 158.4, width: 214.2, height: 39.6 },
  612,
  792
);

// 범위를 벗어난 좌표/크기를 유효 범위로 보정
const safeRect = normalizeRect(
  { page: 0, x: -1, y: 0.2, width: 0.3, height: 0.05 },
  document.pageCount
);

그 외 좌표 유틸리티: clampToPage(value, pageCount), clampCoord(value), ensureMinSize(size).

필드 타입

공통 속성:

| 필드 | 타입 | 설명 | | --- | --- | --- | | id | string | 필드 고유 ID | | name | string | 필드 이름 | | type | ContractFieldType | 필드 종류 | | page | number | 페이지 번호 | | x, y, width, height | number | 정규화 좌표 | | label | string | 표시 라벨 | | required | boolean | 필수 여부 | | placeholder | string | 입력 안내 문구 | | textAlign | 'left' \| 'center' \| 'right' | 텍스트 계열 필드의 정렬. 기본값 'left' | | textSize | number | 텍스트 계열 필드의 글자 크기. 기본값 10. React 뷰어와 PDF export 양쪽에 적용 | | highlightColor | string | 필드를 강조할 반투명 오버레이 색상(CSS color). React 뷰어의 모든 모드에 적용되며 PDF export에는 반영되지 않음 | | readonly | boolean | 값만 표시하고 수정은 금지 | | disabled | boolean | 필드 UI(테두리, placeholder)는 유지하면서 입력/이동/크기조절 상호작용만 차단 | | hidden | boolean | true면 PDF export에서 제외 | | defaultValue | - | 값이 없을 때 사용할 기본값 | | validation | FieldValidation | 검증 규칙 | | sharedKey | string | 공유 필드 그룹 키 | | sharedMode | 'source' \| 'mirror' | 공유 필드 역할 |

타입별 값과 추가 속성:

| 타입 | 값 타입 | 추가 속성 | | --- | --- | --- | | text | string | maxLength? | | textarea | string | maxLength?, rows? | | date | string(yyyy-mm-dd) | dateFormat?(표시/출력용 포맷) | | checkbox | boolean | 없음 | | signature | SignatureValue | signatureMode?, signatureActions? | | email | string | 없음 | | phone | string | 없음 | | number | number | min?, max?, step? |

textSize, textAligntext, textarea, date, email, phone, number 필드에서 사용할 수 있습니다.

document = createField(document, {
  id: 'employeeName',
  name: '직원명',
  type: 'text',
  page: 0,
  x: 0.1,
  y: 0.2,
  width: 0.35,
  height: 0.05,
  textSize: 14,
  textAlign: 'center',
});

readonly는 값을 문서 위에 표시하기만 할 때, disabled는 필드 테두리와 placeholder 같은 UI는 유지하되 사용자가 입력하거나 빌더에서 이동/크기조절하지 못하게 할 때 사용합니다.

document = createField(document, {
  id: 'lockedDepartment',
  name: '부서',
  type: 'text',
  page: 0,
  x: 0.1,
  y: 0.32,
  width: 0.35,
  height: 0.05,
  placeholder: '부서명',
  disabled: true,
});

서명 필드는 signatureMode('all' | 'sign-only' | 'stamp-only')와 signatureActions({ edit?: boolean; remove?: boolean })로 표시 방식과 기존 값 위의 액션 버튼을 제어합니다. 기본값은 액션을 표시하지 않는 동작입니다.

document = createField(document, {
  id: 'employeeSignature',
  name: '직원 서명',
  type: 'signature',
  page: 0,
  x: 0.1,
  y: 0.72,
  width: 0.3,
  height: 0.1,
  signatureMode: 'all',
  signatureActions: { edit: true, remove: true },
});

문서/필드 조작

| 함수 | 설명 | | --- | --- | | createDocument(input) | 빈 문서 생성 | | createDocumentFromPdfData(input) | 원본 PDF에서 페이지 크기를 읽어 문서 생성 (비동기) | | getPdfPageInfo(pdfData) | PDF에서 페이지별 크기 정보만 조회 (비동기) | | createField(document, field) | 필드 추가 | | updateField(document, fieldId, patch) | 필드 속성 수정 (id, type은 수정 대상 제외) | | removeField(document, fieldId) | 필드 삭제 (해당 필드 값도 함께 제거) | | moveField(document, fieldId, position) | 필드 위치(page, x, y) 이동 | | resizeField(document, fieldId, size) | 필드 크기(width, height) 변경 |

const nextDocument = updateField(document, 'employeeName', {
  label: 'Employee Name',
  required: true,
});

const moved = moveField(document, 'employeeName', { page: 0, x: 0.2, y: 0.3 });
const resized = resizeField(document, 'employeeName', { width: 0.4, height: 0.06 });

필드 정의 조회

getField/getFields는 값이 아니라 필드 정의를 조회합니다.

import { getField, getFields } from '@promr-acorda/core';

const field = getField(document, 'employeeName'); // 없으면 undefined

const fields = getFields(document);
const requiredFields = getFields(document, { required: true });
const visibleFields = getFields(document, { hidden: false });
const disabledFields = getFields(document, { disabled: true });
const pageOneFields = getFields(document, { page: 0 });
const textFields = getFields(document, { type: 'text' });
const sharedMirrors = getFields(document, { sharedKey: 'partyName', sharedMode: 'mirror' });

getFields 필터 옵션: page, type, required, hidden, readonly, disabled, sharedKey, sharedMode.

필드 값 조작 및 조회

| 함수 | 설명 | | --- | --- | | setFieldValue(document, fieldId, value) | 필드 값 설정 (source 필드면 sharedValues도 갱신) | | clearFieldValue(document, fieldId) | 필드 값 제거 | | getResolvedFieldValue(document, fieldId) | 공유값/defaultValue를 반영한 단일 필드 값 조회 | | getResolvedValues(document, options?) | 공유값/defaultValue를 반영한 전체 필드 값 조회 |

const values = getResolvedValues(document); // 기본: 값이 있는 필드만 반환

const valuesWithEmpty = getResolvedValues(document, { includeEmpty: true });
if (valuesWithEmpty.employeeName === undefined) {
  console.log('employeeName is empty.');
}

공유(source/mirror) 필드

document = createField(document, {
  id: 'partyNameSource',
  name: '회사명 원본',
  type: 'text',
  page: 0,
  x: 0.1,
  y: 0.15,
  width: 0.35,
  height: 0.05,
  sharedKey: 'partyName',
  sharedMode: 'source',
});

document = createField(document, {
  id: 'partyNameMirror',
  name: '회사명 복제',
  type: 'text',
  page: 0,
  x: 0.1,
  y: 0.25,
  width: 0.35,
  height: 0.05,
  sharedKey: 'partyName',
  sharedMode: 'mirror',
});

document = setFieldValue(document, 'partyNameSource', '주식회사 프로엠알');
// partyNameMirror도 같은 값을 갖게 됩니다.

저수준 공유 필드 유틸리티도 함께 제공됩니다:

| 함수 | 설명 | | --- | --- | | getSourceField(fields, sharedKey) | 공유 키의 source 필드 조회 | | getMirrorFields(fields, sharedKey) | 공유 키의 mirror 필드 목록 조회 | | resolveFieldValue(field, fieldValues, sharedValues) | 단일 필드 값 해석 | | setSharedFieldValue(fields, sharedValues, sharedKey, value) | 공유 값 설정 후 새 SharedValueMap 반환 | | resolveAllSharedValues(fields, fieldValues, sharedValues) | 공유 필드를 포함한 전체 값 해석 |

검증

import { validateField, validateDocument, validateSharedFieldGroup } from '@promr-acorda/core';

const fieldResult = validateField(field, value);

const result = validateDocument(document);
if (!result.valid) {
  console.log(result.errors); // FieldValidationError[]
}

const groupResult = validateSharedFieldGroup(
  document.fields,
  document.fieldValues,
  document.sharedValues,
  'partyName'
);

날짜 포맷

날짜 필드는 내부적으로 yyyy-mm-dd 형식을 값으로 사용하고, 표시나 PDF 출력에는 필드의 dateFormat을 적용합니다.

import {
  formatDateValue,
  isIsoDateString,
  matchesDateFormat,
  dateFormatToRegexPattern,
} from '@promr-acorda/core';

formatDateValue('2026-04-22', 'yyyy.mm.dd'); // '2026.04.22'
isIsoDateString('2026-04-22'); // true
matchesDateFormat('yyyy.mm.dd', '2026.04.22'); // true
dateFormatToRegexPattern('yyyy.mm.dd'); // 검증용 정규식 패턴

값 타입 가드

import { isSignatureValue, isPrimitiveValue } from '@promr-acorda/core';

if (isSignatureValue(value)) {
  console.log(value.mimeType);
}

isPrimitiveValue(value); // string | number | boolean 여부

PDF 내보내기

import { exportToPdf } from '@promr-acorda/core';

const pdfBytes = await exportToPdf(document);
  • document.pdfData를 원본 PDF로 사용해 각 필드의 해석된 값을 페이지 위치에 그립니다.
  • hidden: true인 필드는 출력에서 제외됩니다.
  • 체크박스는 값이 true일 때만 원본 PDF 위에 체크 표시를 그립니다.
  • 기본적으로 표준 Helvetica 폰트를 사용하며, 이 폰트는 라틴(WinAnsi) 문자만 지원합니다.

한글/일본어/중국어 등 비 라틴 문자

options.fontBytes로 TrueType/OpenType 폰트 데이터를 전달하면 (내부적으로 @pdf-lib/fontkit을 사용해) Helvetica 대신 유니코드 폰트를 임베드합니다.

const fontBytes = await fetch('/fonts/NotoSansKR.ttf').then((r) => r.arrayBuffer());

const pdfBytes = await exportToPdf(document, {
  fontBytes: new Uint8Array(fontBytes),
});

에러 처리:

  • fontBytes 없이 Helvetica가 인코딩할 수 없는 값(예: 한글)을 내보내려 하면 options.fontBytes가 필요하다는 것을 안내하는 에러를 던집니다.
  • fontBytes로 유효하지 않은 폰트 데이터를 전달하면 폰트를 임베드할 수 없다는 것을 안내하는 에러를 던집니다.
  • 두 경우 모두 pdf-lib의 원본(저수준) 에러 메시지를 그대로 노출하지 않고, 원인과 해결 방법을 포함한 메시지로 감싸서 던집니다.

라이선스

MIT