forked from olcxjas-softworks/LarpixClient
299 lines
7.4 KiB
Markdown
299 lines
7.4 KiB
Markdown
# @peculiar/utils
|
|
|
|
[](https://www.npmjs.com/package/@peculiar/utils)
|
|
[](https://github.com/PeculiarVentures/pvtsutils/actions/workflows/test.yml)
|
|
[](https://coveralls.io/github/PeculiarVentures/pvtsutils?branch=master)
|
|
[](https://github.com/PeculiarVentures/pvtsutils/blob/master/LICENSE)
|
|
|
|
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
|
|
|
|
```bash
|
|
npm install @peculiar/utils
|
|
```
|
|
|
|
## Entry Points
|
|
|
|
```ts
|
|
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.
|
|
|
|
```ts
|
|
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
|
|
|
|
```ts
|
|
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`
|
|
|
|
```ts
|
|
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
|
|
|
|
```ts
|
|
import { bytes } from "@peculiar/utils";
|
|
|
|
bytes.startsWith(data, "-----BEGIN", { encoding: "ascii" });
|
|
bytes.endsWith(data, "%%EOF", { encoding: "ascii" });
|
|
```
|
|
|
|
### Compare Byte Sequences
|
|
|
|
```ts
|
|
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.
|
|
|
|
```ts
|
|
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.
|
|
|
|
```ts
|
|
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.
|
|
|
|
```ts
|
|
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.
|
|
|
|
```ts
|
|
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:
|
|
|
|
```ts
|
|
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.
|
|
|
|
```ts
|
|
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
|
|
|
|
```ts
|
|
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.
|
|
|
|
```ts
|
|
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.
|
|
|
|
```ts
|
|
import { convert } from "@peculiar/utils/converters";
|
|
|
|
convert.encode("hex", new Uint8Array([1, 2, 3]), {
|
|
case: "upper",
|
|
});
|
|
```
|
|
|
|
Wrong options are rejected by TypeScript:
|
|
|
|
```ts
|
|
convert.encode("hex", new Uint8Array([1, 2, 3]), {
|
|
label: "CERTIFICATE",
|
|
});
|
|
```
|
|
|
|
Custom converters can extend the options map via module augmentation:
|
|
|
|
```ts
|
|
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.
|
|
|
|
```ts
|
|
import { BufferSourceConverter, Convert, assign, combine, isEqual } from "@peculiar/utils/legacy";
|
|
```
|