zheng 6c3f2333fd 修改 il y a 13 heures
..
build 6c3f2333fd 修改 il y a 13 heures
LICENSE 6c3f2333fd 修改 il y a 13 heures
README.md 6c3f2333fd 修改 il y a 13 heures
package.json 6c3f2333fd 修改 il y a 13 heures

README.md

@peculiar/utils

npm version Test Coverage Status

Modern byte, text, converter registry, and PEM utilities for TypeScript projects.

The package is designed around a modular v2 API:

  • multi-entry exports for tree-shake-friendly imports;
  • encode and decode terminology;
  • an extensible runtime converter registry;
  • generic PEM helpers without PKI-specific parsing;
  • a legacy compatibility layer for historical pvtsutils consumers.

Install

npm install @peculiar/utils

Entry Points

import { bytes } from "@peculiar/utils";
import { hex, base64, base64url } from "@peculiar/utils/encoding";
import { pem } from "@peculiar/utils/pem";
import { convert, createConverterRegistry, defaultConverters } from "@peculiar/utils/converters";
import { Convert } from "@peculiar/utils/legacy";

Bytes Helpers

@peculiar/utils/bytes stays focused on stateless byte sequence utilities. It does not include stateful readers, writers, ASN.1 parsing, PDF parsing, or other structured binary readers. For structured binary parsing, use a dedicated binary reader package.

import { bytes } from "@peculiar/utils";

const offset = bytes.indexOf(new Uint8Array([0x25, 0x25, 0x45, 0x4f, 0x46]), "%%EOF", {
 encoding: "ascii",
});

const suffix = bytes.endsWith(new Uint8Array([0x25, 0x25, 0x45, 0x4f, 0x46]), "%%EOF", {
 encoding: "ascii",
});

Find startxref In A PDF Tail

import { lastIndexOf } from "@peculiar/utils/bytes";

const tailStart = Math.max(0, pdf.byteLength - 4096);

const offset = lastIndexOf(pdf, "startxref", {
 encoding: "ascii",
 start: pdf.byteLength,
 end: tailStart,
});

if (offset === -1) {
 throw new Error("PDF startxref marker not found");
}

Find startxref Via tail

import { lastIndexOf, tail } from "@peculiar/utils/bytes";

const pdfTail = tail(pdf, 4096);

const localOffset = lastIndexOf(pdfTail, "startxref", {
 encoding: "ascii",
});

const offset =
 localOffset === -1
    ? -1
    : pdf.byteLength - pdfTail.byteLength + localOffset;

Check Prefixes And Suffixes

import { bytes } from "@peculiar/utils";

bytes.startsWith(data, "-----BEGIN", { encoding: "ascii" });
bytes.endsWith(data, "%%EOF", { encoding: "ascii" });

Compare Byte Sequences

import { bytes } from "@peculiar/utils";

const result = bytes.compare(a, b);

if (result === 0) {
 console.log("equal");
}

Convert API

The default convert facade is a convenience singleton backed by the built-in registry.

import { convert } from "@peculiar/utils/converters";

const bytes = convert.decode("base64", "AQID");
const text = convert.encode("hex", bytes, { case: "upper" });

Deprecated convert.to(...) and convert.from(...) aliases are still available for temporary migration, but the primary v2 API is encode and decode.

Transcode

Direct text-to-text transcoding goes through the registry without a manual intermediate step.

import { convert } from "@peculiar/utils/converters";
import { hex } from "@peculiar/utils/encoding";

const pemText = convert.transcode("AQID", {
 from: "base64",
 to: "pem",
 toOptions: {
  label: "CERTIFICATE",
 },
});

const hexText = convert.transcode(pemText, {
 from: "pem",
 fromOptions: { label: "CERTIFICATE" },
 to: "hex",
 toOptions: hex.formats.colonUpper,
});

There is intentionally no chain API.

Hex Formatting

The hex codec accepts common input styles and can format output explicitly.

import { hex } from "@peculiar/utils/encoding";

hex.decode("0102030405060708090a0b0c");
hex.decode("01020304 05060708 090a0b0c");
hex.decode("01:02:03:04:05:06:07:08:09:0A:0B:0C");
hex.decode("0x0102030405060708090a0b0c");

hex.encode(new Uint8Array([1, 2, 3, 4]), hex.formats.colonUpper);
hex.encode(new Uint8Array([1, 2, 3, 4]), {
 prefix: "0x",
 group: {
  size: 2,
  separator: " ",
 },
});

Available presets:

  • hex.formats.compact
  • hex.formats.upper
  • hex.formats.colon
  • hex.formats.colonUpper
  • hex.formats.groupsOf4
  • hex.formats.prefixed

Preserve Formatting

Use parse and format when you want to keep the original visual style of a hex string.

import { hex } from "@peculiar/utils/encoding";

const parsed = hex.parse("01:02:03:04:05:06");

parsed.bytes;
parsed.format;
parsed.normalized;

const updated = hex.format(new Uint8Array([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]), parsed.format);

The same capabilities are available through the registry facade:

import { convert } from "@peculiar/utils/converters";

const parsed = convert.parse("hex", "01:02:03:04");
const formatted = convert.format("hex", new Uint8Array([0xaa, 0xbb, 0xcc, 0xdd]), parsed.format);

PEM Helpers

PEM support stays generic. The package does not parse ASN.1, validate PKI semantics, or handle encrypted PEM containers.

import { pem } from "@peculiar/utils/pem";

const text = pem.encode("CERTIFICATE", new Uint8Array([1, 2, 3]));
const blocks = pem.decode(text);
const block = pem.find(text, "CERTIFICATE");
const matches = pem.findAll(text, "CERTIFICATE");

const bundle = pem.encodeMany([
 { label: "CERTIFICATE", data: new Uint8Array([1, 2, 3]) },
 { label: "PRIVATE KEY", data: new Uint8Array([4, 5, 6]) },
]);

Safe Decode And Detection

import { convert } from "@peculiar/utils/converters";

const result = convert.tryDecode("hex", "01:02:03");

if (result.ok) {
 console.log(result.bytes);
} else {
 console.error(result.error);
}

const candidates = convert.detect("-----BEGIN DATA-----\nAQID\n-----END DATA-----\n", {
 formats: ["pem", "base64", "hex"],
});

Custom Registries

Applications can create isolated registries instead of mutating global state.

import { createConverterRegistry, defaultConverters } from "@peculiar/utils/converters";

const registry = createConverterRegistry(defaultConverters);

registry.register({
 name: "base58btc",
 aliases: ["b58"],
 encode(data) {
  return base58btcEncode(data);
 },
 decode(text) {
  return base58btcDecode(text);
 },
});

Name and alias conflicts throw by default. Use { override: true } only when replacement is intentional.

Typed Converter Options

Built-in converters expose typed options through the registry facade.

import { convert } from "@peculiar/utils/converters";

convert.encode("hex", new Uint8Array([1, 2, 3]), {
 case: "upper",
});

Wrong options are rejected by TypeScript:

convert.encode("hex", new Uint8Array([1, 2, 3]), {
 label: "CERTIFICATE",
});

Custom converters can extend the options map via module augmentation:

declare module "@peculiar/utils/converters" {
 interface ConverterOptionsMap {
  base58btc: {
   encode: Base58EncodeOptions;
   decode: Base58DecodeOptions;
  };
 }
}

Legacy Compatibility

The old pvtsutils-style surface is preserved under the legacy entry point.

import { BufferSourceConverter, Convert, assign, combine, isEqual } from "@peculiar/utils/legacy";