docs: added jsdocs for utils
This commit is contained in:
parent
1966c2d6d4
commit
fe342ca48a
@ -1,7 +1,20 @@
|
||||
/**
|
||||
* The [Base58 Encoding](link-base58) scheme allows a **numeric** value
|
||||
* to be encoded as a compact string using a radix of 58 using only
|
||||
* alpha-numeric characters. Confusingly similar characters are omitted
|
||||
* (i.e. ``"l0O"``).
|
||||
*
|
||||
* Note that Base58 encodes a **numeric** value, not arbitrary bytes,
|
||||
* since any zero-bytes on the left would get removed. To mitigate this
|
||||
* issue most schemes that use Base58 choose specific high-order values
|
||||
* to ensure non-zero prefixes.
|
||||
*
|
||||
* @_subsection: api/utils:Base58 Encoding [base58]
|
||||
*/
|
||||
|
||||
import { getBytes } from "./data.js";
|
||||
import { assertArgument } from "./errors.js";
|
||||
import { toBigInt, toHex } from "./maths.js";
|
||||
import { toBigInt } from "./maths.js";
|
||||
|
||||
import type { BytesLike } from "./index.js";
|
||||
|
||||
@ -26,7 +39,7 @@ const BN_0 = BigInt(0);
|
||||
const BN_58 = BigInt(58);
|
||||
|
||||
/**
|
||||
* Encode %%value%% as Base58-encoded data.
|
||||
* Encode %%value%% as a Base58-encoded string.
|
||||
*/
|
||||
export function encodeBase58(_value: BytesLike): string {
|
||||
let value = toBigInt(getBytes(_value));
|
||||
@ -41,11 +54,11 @@ export function encodeBase58(_value: BytesLike): string {
|
||||
/**
|
||||
* Decode the Base58-encoded %%value%%.
|
||||
*/
|
||||
export function decodeBase58(value: string): Uint8Array {
|
||||
export function decodeBase58(value: string): bigint {
|
||||
let result = BN_0;
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
result *= BN_58;
|
||||
result += getAlpha(value[i]);
|
||||
}
|
||||
return getBytes(toHex(result));
|
||||
return result;
|
||||
}
|
||||
|
@ -1,17 +1,25 @@
|
||||
/**
|
||||
* [Base64 encoding](link-wiki-base64) using 6-bit words to encode
|
||||
* arbitrary bytes into a string using 65 printable symbols, the
|
||||
* upper-case and lower-case alphabet, the digits ``0`` through ``9``,
|
||||
* ``"+"`` and ``"/"`` with the ``"="`` used for padding.
|
||||
*
|
||||
* @_subsection: api/utils:Base64 Encoding [base64]
|
||||
*/
|
||||
import { getBytes, getBytesCopy } from "./data.js";
|
||||
|
||||
import type { BytesLike } from "./data.js";
|
||||
|
||||
|
||||
/**
|
||||
* Decodes the base-64 encoded %%base64Data%%.
|
||||
* Decodes the base-64 encoded %%value%%.
|
||||
*/
|
||||
export function decodeBase64(base64Data: string): Uint8Array {
|
||||
return getBytesCopy(Buffer.from(base64Data, "base64"));
|
||||
export function decodeBase64(value: string): Uint8Array {
|
||||
return getBytesCopy(Buffer.from(value, "base64"));
|
||||
};
|
||||
|
||||
/**
|
||||
* Encodes %%data%% as base-64 encoded data.
|
||||
* Encodes %%data%% as a base-64 encoded string.
|
||||
*/
|
||||
export function encodeBase64(data: BytesLike): string {
|
||||
return Buffer.from(getBytes(data)).toString("base64");
|
||||
|
@ -1,7 +1,29 @@
|
||||
/**
|
||||
* Some data helpers.
|
||||
*
|
||||
*
|
||||
* @_subsection api/utils:Data Helpers [data]
|
||||
*/
|
||||
import { assert, assertArgument } from "./errors.js";
|
||||
|
||||
/**
|
||||
* A [[HexString]] whose length is even, which ensures it is a valid
|
||||
* representation of binary data.
|
||||
*/
|
||||
export type DataHexString = string;
|
||||
|
||||
export type BytesLike = string | Uint8Array;
|
||||
/**
|
||||
* A string which is prefixed with ``0x`` and followed by any number
|
||||
* of case-agnostic hexadecimal characters.
|
||||
*
|
||||
* It must match the regular expression ``/0x[0-9A-Fa-f]*\/``.
|
||||
*/
|
||||
export type HexString = string;
|
||||
|
||||
/**
|
||||
* An object that can be used to represent binary data.
|
||||
*/
|
||||
export type BytesLike = DataHexString | Uint8Array;
|
||||
|
||||
function _getBytes(value: BytesLike, name?: string, copy?: boolean): Uint8Array {
|
||||
if (value instanceof Uint8Array) {
|
||||
@ -46,13 +68,10 @@ export function getBytesCopy(value: BytesLike, name?: string): Uint8Array {
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if %%value%% is a valid [[HexString]], with additional
|
||||
* optional constraints depending on %%length%%.
|
||||
* Returns true if %%value%% is a valid [[HexString]].
|
||||
*
|
||||
* If %%length%% is //true//, then %%value%% must additionally be a valid
|
||||
* [[HexDataString]] (i.e. even length).
|
||||
*
|
||||
* If %%length%% is //a number//, then %%value%% must represent that many
|
||||
* If %%length%% is ``true`` or a //number//, it also checks that
|
||||
* %%value%% is a valid [[DataHexString]] of %%length%% (if a //number//)
|
||||
* bytes of data (e.g. ``0x1234`` is 2 bytes).
|
||||
*/
|
||||
export function isHexString(value: any, length?: number | boolean): value is `0x${ string }` {
|
||||
@ -68,7 +87,7 @@ export function isHexString(value: any, length?: number | boolean): value is `0x
|
||||
|
||||
/**
|
||||
* Returns true if %%value%% is a valid representation of arbitrary
|
||||
* data (i.e. a valid [[HexDataString]] or a Uint8Array).
|
||||
* data (i.e. a valid [[DataHexString]] or a Uint8Array).
|
||||
*/
|
||||
export function isBytesLike(value: any): value is BytesLike {
|
||||
return (isHexString(value, true) || (value instanceof Uint8Array));
|
||||
@ -77,7 +96,7 @@ export function isBytesLike(value: any): value is BytesLike {
|
||||
const HexCharacters: string = "0123456789abcdef";
|
||||
|
||||
/**
|
||||
* Returns a [[HexDataString]] representation of %%data%%.
|
||||
* Returns a [[DataHexString]] representation of %%data%%.
|
||||
*/
|
||||
export function hexlify(data: BytesLike): string {
|
||||
const bytes = getBytes(data);
|
||||
@ -91,7 +110,7 @@ export function hexlify(data: BytesLike): string {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a [[HexDataString]] by concatenating all values
|
||||
* Returns a [[DataHexString]] by concatenating all values
|
||||
* within %%data%%.
|
||||
*/
|
||||
export function concat(datas: ReadonlyArray<BytesLike>): string {
|
||||
@ -107,7 +126,7 @@ export function dataLength(data: BytesLike): number {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a [[HexDataString]] by slicing %%data%% from the %%start%%
|
||||
* Returns a [[DataHexString]] by slicing %%data%% from the %%start%%
|
||||
* offset to the %%end%% offset.
|
||||
*
|
||||
* By default %%start%% is 0 and %%end%% is the length of %%data%%.
|
||||
@ -123,7 +142,7 @@ export function dataSlice(data: BytesLike, start?: number, end?: number): string
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the [[HexDataString]] result by stripping all **leading**
|
||||
* Return the [[DataHexString]] result by stripping all **leading**
|
||||
** zero bytes from %%data%%.
|
||||
*/
|
||||
export function stripZerosLeft(data: BytesLike): string {
|
||||
@ -152,7 +171,7 @@ function zeroPad(data: BytesLike, length: number, left: boolean): string {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the [[HexDataString]] of %%data%% padded on the **left**
|
||||
* Return the [[DataHexString]] of %%data%% padded on the **left**
|
||||
* to %%length%% bytes.
|
||||
*/
|
||||
export function zeroPadValue(data: BytesLike, length: number): string {
|
||||
@ -160,7 +179,7 @@ export function zeroPadValue(data: BytesLike, length: number): string {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the [[HexDataString]] of %%data%% padded on the **right**
|
||||
* Return the [[DataHexString]] of %%data%% padded on the **right**
|
||||
* to %%length%% bytes.
|
||||
*/
|
||||
export function zeroPadBytes(data: BytesLike, length: number): string {
|
||||
|
@ -1,6 +1,12 @@
|
||||
/**
|
||||
* About Errors.
|
||||
*
|
||||
* @_section: api/utils/errors:Errors [errors]
|
||||
*/
|
||||
|
||||
import { version } from "../_version.js";
|
||||
|
||||
import { defineReadOnly } from "./properties.js";
|
||||
import { defineProperties } from "./properties.js";
|
||||
|
||||
import type {
|
||||
TransactionRequest, TransactionReceipt, TransactionResponse
|
||||
@ -11,11 +17,46 @@ import type { FetchRequest, FetchResponse } from "./fetch.js";
|
||||
|
||||
export type ErrorInfo<T> = Omit<T, "code" | "name" | "message">;
|
||||
|
||||
// The type of error to use for various error codes
|
||||
const ErrorConstructors: Record<string, { new (...args: Array<any>): Error }> = { };
|
||||
ErrorConstructors.INVALID_ARGUMENT = TypeError;
|
||||
ErrorConstructors.NUMERIC_FAULT = RangeError;
|
||||
ErrorConstructors.BUFFER_OVERRUN = RangeError;
|
||||
|
||||
function stringify(value: any): any {
|
||||
if (value == null) { return "null"; }
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return "[ " + (value.map(stringify)).join(", ") + " ]";
|
||||
}
|
||||
|
||||
if (value instanceof Uint8Array) {
|
||||
const HEX = "0123456789abcdef";
|
||||
let result = "0x";
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
result += HEX[value[i] >> 4];
|
||||
result += HEX[value[i] & 0xf];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
if (typeof(value) === "object" && typeof(value.toJSON) === "function") {
|
||||
return stringify(value.toJSON());
|
||||
}
|
||||
|
||||
switch (typeof(value)) {
|
||||
case "boolean": case "symbol":
|
||||
return value.toString();
|
||||
case "bigint":
|
||||
return BigInt(value).toString();
|
||||
case "number":
|
||||
return (value).toString();
|
||||
case "string":
|
||||
return JSON.stringify(value);
|
||||
case "object": {
|
||||
const keys = Object.keys(value);
|
||||
keys.sort();
|
||||
return "{ " + keys.map((k) => `${ stringify(k) }: ${ stringify(value[k]) }`).join(", ") + " }";
|
||||
}
|
||||
}
|
||||
|
||||
return `[ COULD NOT SERIALIZE ]`;
|
||||
}
|
||||
|
||||
/**
|
||||
* All errors emitted by ethers have an **ErrorCode** to help
|
||||
@ -59,15 +100,39 @@ export interface EthersError<T extends ErrorCode = ErrorCode> extends Error {
|
||||
|
||||
// Generic Errors
|
||||
|
||||
/**
|
||||
* This Error is a catch-all for when there is no way for Ethers to
|
||||
* know what the underlying problem is.
|
||||
*/
|
||||
export interface UnknownError extends EthersError<"UNKNOWN_ERROR"> {
|
||||
[ key: string ]: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* This Error is mostly used as a stub for functionality that is
|
||||
* intended for the future, but is currently not implemented.
|
||||
*/
|
||||
export interface NotImplementedError extends EthersError<"NOT_IMPLEMENTED"> {
|
||||
/**
|
||||
* The attempted operation.
|
||||
*/
|
||||
operation: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* This Error indicates that the attempted operation is not supported.
|
||||
*
|
||||
* This could range from a specifc JSON-RPC end-point not supporting
|
||||
* a feature to a specific configuration of an object prohibiting the
|
||||
* operation.
|
||||
*
|
||||
* For example, a [[Wallet]] with no connected [[Provider]] is unable
|
||||
* to send a transaction.
|
||||
*/
|
||||
export interface UnsupportedOperationError extends EthersError<"UNSUPPORTED_OPERATION"> {
|
||||
/**
|
||||
* The attempted operation.
|
||||
*/
|
||||
operation: string;
|
||||
}
|
||||
|
||||
@ -75,45 +140,134 @@ export interface NetworkError extends EthersError<"NETWORK_ERROR"> {
|
||||
event: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* This Error indicates there was a problem fetching a resource from
|
||||
* a server.
|
||||
*/
|
||||
export interface ServerError extends EthersError<"SERVER_ERROR"> {
|
||||
/**
|
||||
* The requested resource.
|
||||
*/
|
||||
request: FetchRequest | string;
|
||||
|
||||
/**
|
||||
* The response received from the server, if available.
|
||||
*/
|
||||
response?: FetchResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* This Error indicates that the timeout duration has expired and
|
||||
* that the operation has been implicitly cancelled.
|
||||
*
|
||||
* The side-effect of the operation may still occur, as this
|
||||
* generally means a request has been sent and there has simply
|
||||
* been no response to indicate whether it was processed or not.
|
||||
*/
|
||||
export interface TimeoutError extends EthersError<"TIMEOUT"> {
|
||||
/**
|
||||
* The attempted operation.
|
||||
*/
|
||||
operation: string;
|
||||
|
||||
/**
|
||||
* The reason.
|
||||
*/
|
||||
reason: string;
|
||||
|
||||
/**
|
||||
* The resource request, if available.
|
||||
*/
|
||||
request?: FetchRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* This Error indicates that a provided set of data cannot
|
||||
* be correctly interpretted.
|
||||
*/
|
||||
export interface BadDataError extends EthersError<"BAD_DATA"> {
|
||||
/**
|
||||
* The data.
|
||||
*/
|
||||
value: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* This Error indicates that the operation was cancelled by a
|
||||
* programmatic call, for example to ``cancel()``.
|
||||
*/
|
||||
export interface CancelledError extends EthersError<"CANCELLED"> {
|
||||
}
|
||||
|
||||
|
||||
// Operational Errors
|
||||
|
||||
/**
|
||||
* This Error indicates an attempt was made to read outside the bounds
|
||||
* of protected data.
|
||||
*
|
||||
* Most operations in Ethers are protected by bounds checks, to mitigate
|
||||
* exploits when parsing data.
|
||||
*/
|
||||
export interface BufferOverrunError extends EthersError<"BUFFER_OVERRUN"> {
|
||||
/**
|
||||
* The buffer that was overrun.
|
||||
*/
|
||||
buffer: Uint8Array;
|
||||
|
||||
/**
|
||||
* The length of the buffer.
|
||||
*/
|
||||
length: number;
|
||||
|
||||
/**
|
||||
* The offset that was requested.
|
||||
*/
|
||||
offset: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* This Error indicates an operation which would result in incorrect
|
||||
* arithmetic output has occurred.
|
||||
*
|
||||
* For example, trying to divide by zero or using a ``uint8`` to store
|
||||
* a negative value.
|
||||
*/
|
||||
export interface NumericFaultError extends EthersError<"NUMERIC_FAULT"> {
|
||||
/**
|
||||
* The attempted operation.
|
||||
*/
|
||||
operation: string;
|
||||
|
||||
/**
|
||||
* The fault reported.
|
||||
*/
|
||||
fault: string;
|
||||
|
||||
/**
|
||||
* The value the operation was attempted against.
|
||||
*/
|
||||
value: any;
|
||||
}
|
||||
|
||||
|
||||
// Argument Errors
|
||||
|
||||
/**
|
||||
* This Error indicates an incorrect type or value was passed to
|
||||
* a function or method.
|
||||
*/
|
||||
export interface InvalidArgumentError extends EthersError<"INVALID_ARGUMENT"> {
|
||||
/**
|
||||
* The name of the argument.
|
||||
*/
|
||||
argument: string;
|
||||
|
||||
/**
|
||||
* The value that was provided.
|
||||
*/
|
||||
value: any;
|
||||
|
||||
info?: Record<string, any>
|
||||
}
|
||||
|
||||
@ -168,6 +322,7 @@ export interface CallExceptionError extends EthersError<"CALL_EXCEPTION"> {
|
||||
// transaction: any;//ErrorTransaction;
|
||||
//}
|
||||
|
||||
|
||||
export interface InsufficientFundsError extends EthersError<"INSUFFICIENT_FUNDS"> {
|
||||
transaction: TransactionRequest;
|
||||
}
|
||||
@ -193,12 +348,41 @@ export interface TransactionReplacedError extends EthersError<"TRANSACTION_REPLA
|
||||
receipt: TransactionReceipt;
|
||||
}
|
||||
|
||||
/**
|
||||
* This Error indicates an ENS name was used, but the name has not
|
||||
* been configured.
|
||||
*
|
||||
* This could indicate an ENS name is unowned or that the current
|
||||
* address being pointed to is the [[Zero]].
|
||||
*/
|
||||
export interface UnconfiguredNameError extends EthersError<"UNCONFIGURED_NAME"> {
|
||||
/**
|
||||
* The ENS name that was requested
|
||||
*/
|
||||
value: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* This Error indicates a request was rejected by the user.
|
||||
*
|
||||
* In most clients (such as MetaMask), when an operation requires user
|
||||
* authorization (such as ``signer.sendTransaction``), the client
|
||||
* presents a dialog box to the user. If the user denies the request
|
||||
* this error is thrown.
|
||||
*/
|
||||
export interface ActionRejectedError extends EthersError<"ACTION_REJECTED"> {
|
||||
/**
|
||||
* The requested action.
|
||||
*/
|
||||
action: "requestAccess" | "sendTransaction" | "signMessage" | "signTransaction" | "signTypedData" | "unknown",
|
||||
|
||||
/**
|
||||
* The reason the action was rejected.
|
||||
*
|
||||
* If there is already a pending request, some clients may indicate
|
||||
* there is already a ``"pending"`` action. This prevents an app
|
||||
* from spamming the user.
|
||||
*/
|
||||
reason: "expired" | "rejected" | "pending"
|
||||
}
|
||||
|
||||
@ -252,7 +436,7 @@ export type CodedEthersError<T> =
|
||||
* @See [ErrorCodes](api:ErrorCode)
|
||||
* @example
|
||||
* try {
|
||||
* // code....
|
||||
* / / code....
|
||||
* } catch (e) {
|
||||
* if (isError(e, "CALL_EXCEPTION")) {
|
||||
* console.log(e.data);
|
||||
@ -285,15 +469,16 @@ export function makeError<K extends ErrorCode, T extends CodedEthersError<K>>(me
|
||||
const details: Array<string> = [];
|
||||
if (info) {
|
||||
if ("message" in info || "code" in info || "name" in info) {
|
||||
throw new Error(`value will overwrite populated values: ${ JSON.stringify(info) }`);
|
||||
throw new Error(`value will overwrite populated values: ${ stringify(info) }`);
|
||||
}
|
||||
for (const key in info) {
|
||||
const value = <any>(info[<keyof ErrorInfo<T>>key]);
|
||||
try {
|
||||
details.push(key + "=" + JSON.stringify(value));
|
||||
} catch (error) {
|
||||
details.push(key + "=[could not serialize object]");
|
||||
}
|
||||
// try {
|
||||
details.push(key + "=" + stringify(value));
|
||||
// } catch (error: any) {
|
||||
// console.log("MMM", error.message);
|
||||
// details.push(key + "=[could not serialize object]");
|
||||
// }
|
||||
}
|
||||
}
|
||||
details.push(`code=${ code }`);
|
||||
@ -304,16 +489,23 @@ export function makeError<K extends ErrorCode, T extends CodedEthersError<K>>(me
|
||||
}
|
||||
}
|
||||
|
||||
const create = ErrorConstructors[code] || Error;
|
||||
const error = <T>(new create(message));
|
||||
defineReadOnly(error, "code", code);
|
||||
|
||||
if (info) {
|
||||
for (const key in info) {
|
||||
defineReadOnly(error, <keyof T>key, <any>(info[<keyof ErrorInfo<T>>key]));
|
||||
}
|
||||
let error;
|
||||
switch (code) {
|
||||
case "INVALID_ARGUMENT":
|
||||
error = new TypeError(message);
|
||||
break;
|
||||
case "NUMERIC_FAULT":
|
||||
case "BUFFER_OVERRUN":
|
||||
error = new RangeError(message);
|
||||
break;
|
||||
default:
|
||||
error = new Error(message);
|
||||
}
|
||||
|
||||
defineProperties<EthersError>(<EthersError>error, { code });
|
||||
|
||||
if (info) { defineProperties<any>(error, info); }
|
||||
|
||||
return <T>error;
|
||||
}
|
||||
|
||||
@ -339,7 +531,8 @@ export function assertArgument(check: unknown, message: string, name: string, va
|
||||
assert(check, message, "INVALID_ARGUMENT", { argument: name, value: value });
|
||||
}
|
||||
|
||||
export function assertArgumentCount(count: number, expectedCount: number, message: string = ""): void {
|
||||
export function assertArgumentCount(count: number, expectedCount: number, message?: string): void {
|
||||
if (message == null) { message = ""; }
|
||||
if (message) { message = ": " + message; }
|
||||
|
||||
assert(count >= expectedCount, "missing arguemnt" + message, "MISSING_ARGUMENT", {
|
||||
@ -389,7 +582,8 @@ export function assertNormalize(form: string): void {
|
||||
* by ensuring the %%givenGaurd%% matches the file-scoped %%guard%%,
|
||||
* throwing if not, indicating the %%className%% if provided.
|
||||
*/
|
||||
export function assertPrivate(givenGuard: any, guard: any, className: string = ""): void {
|
||||
export function assertPrivate(givenGuard: any, guard: any, className?: string): void {
|
||||
if (className == null) { className = ""; }
|
||||
if (givenGuard !== guard) {
|
||||
let method = className, operation = "new";
|
||||
if (className) {
|
||||
|
@ -1,3 +1,8 @@
|
||||
/**
|
||||
* Explain events...
|
||||
*
|
||||
* @_section api/utils/events:Events [events]
|
||||
*/
|
||||
import { defineProperties } from "./properties.js";
|
||||
|
||||
export type Listener = (...args: Array<any>) => void;
|
||||
|
@ -1,3 +1,8 @@
|
||||
/**
|
||||
* Explain fetching here...
|
||||
*
|
||||
* @_section api/utils/fetching:Fetching Web Content [fetching]
|
||||
*/
|
||||
import { decodeBase64, encodeBase64 } from "./base64.js";
|
||||
import { hexlify } from "./data.js";
|
||||
import { assert, assertArgument } from "./errors.js";
|
||||
@ -21,17 +26,17 @@ export type FetchThrottleParams = {
|
||||
/**
|
||||
* Called before any network request, allowing updated headers (e.g. Bearer tokens), etc.
|
||||
*/
|
||||
export type FetchPreflightFunc = (request: FetchRequest) => Promise<FetchRequest>;
|
||||
export type FetchPreflightFunc = (req: FetchRequest) => Promise<FetchRequest>;
|
||||
|
||||
/**
|
||||
* Called on the response, allowing client-based throttling logic or post-processing.
|
||||
*/
|
||||
export type FetchProcessFunc = (request: FetchRequest, response: FetchResponse) => Promise<FetchResponse>;
|
||||
export type FetchProcessFunc = (req: FetchRequest, resp: FetchResponse) => Promise<FetchResponse>;
|
||||
|
||||
/**
|
||||
* Called prior to each retry; return true to retry, false to abort.
|
||||
*/
|
||||
export type FetchRetryFunc = (request: FetchRequest, response: FetchResponse, attempt: number) => Promise<boolean>;
|
||||
export type FetchRetryFunc = (req: FetchRequest, resp: FetchResponse, attempt: number) => Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Called on Gateway URLs.
|
||||
@ -44,7 +49,7 @@ export type FetchGatewayFunc = (url: string, signal?: FetchCancelSignal) => Prom
|
||||
* and in the browser ``fetch`` is used. If you wish to use Axios, this is
|
||||
* how you would register it.
|
||||
*/
|
||||
export type FetchGetUrlFunc = (request: FetchRequest, signal?: FetchCancelSignal) => Promise<GetUrlResponse>;
|
||||
export type FetchGetUrlFunc = (req: FetchRequest, signal?: FetchCancelSignal) => Promise<GetUrlResponse>;
|
||||
|
||||
|
||||
const MAX_ATTEMPTS = 12;
|
||||
@ -60,7 +65,7 @@ const reIpfs = new RegExp("^ipfs:/\/(ipfs/)?(.*)$", "i");
|
||||
let locked = false;
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs
|
||||
async function gatewayData(url: string, signal?: FetchCancelSignal): Promise<FetchResponse> {
|
||||
async function dataGatewayFunc(url: string, signal?: FetchCancelSignal): Promise<FetchResponse> {
|
||||
try {
|
||||
const match = url.match(reData);
|
||||
if (!match) { throw new Error("invalid data"); }
|
||||
@ -76,7 +81,7 @@ async function gatewayData(url: string, signal?: FetchCancelSignal): Promise<Fet
|
||||
* Returns a [[FetchGatewayFunc]] for fetching content from a standard
|
||||
* IPFS gateway hosted at %%baseUrl%%.
|
||||
*/
|
||||
export function getIpfsGatewayFunc(baseUrl: string): FetchGatewayFunc {
|
||||
function getIpfsGatewayFunc(baseUrl: string): FetchGatewayFunc {
|
||||
async function gatewayIpfs(url: string, signal?: FetchCancelSignal): Promise<FetchRequest | FetchResponse> {
|
||||
try {
|
||||
const match = url.match(reIpfs);
|
||||
@ -91,12 +96,15 @@ export function getIpfsGatewayFunc(baseUrl: string): FetchGatewayFunc {
|
||||
}
|
||||
|
||||
const Gateways: Record<string, FetchGatewayFunc> = {
|
||||
"data": gatewayData,
|
||||
"data": dataGatewayFunc,
|
||||
"ipfs": getIpfsGatewayFunc("https:/\/gateway.ipfs.io/ipfs/")
|
||||
};
|
||||
|
||||
const fetchSignals: WeakMap<FetchRequest, () => void> = new WeakMap();
|
||||
|
||||
/**
|
||||
* @_ignore
|
||||
*/
|
||||
export class FetchCancelSignal {
|
||||
#listeners: Array<() => void>;
|
||||
#cancelled: boolean;
|
||||
@ -141,8 +149,10 @@ function checkSignal(signal?: FetchCancelSignal): FetchCancelSignal {
|
||||
/**
|
||||
* Represents a request for a resource using a URI.
|
||||
*
|
||||
* Requests can occur over http/https, data: URI or any
|
||||
* URI scheme registered via the static [[register]] method.
|
||||
* By default, the supported schemes are ``HTTP``, ``HTTPS``, ``data:``,
|
||||
* and ``IPFS:``.
|
||||
*
|
||||
* Additional schemes can be added globally using [[registerGateway]].
|
||||
*/
|
||||
export class FetchRequest implements Iterable<[ key: string, value: string ]> {
|
||||
#allowInsecure: boolean;
|
||||
@ -174,7 +184,7 @@ export class FetchRequest implements Iterable<[ key: string, value: string ]> {
|
||||
}
|
||||
|
||||
/**
|
||||
* The fetch body, if any, to send as the request body.
|
||||
* The fetch body, if any, to send as the request body. //(default: null)//
|
||||
*
|
||||
* When setting a body, the intrinsic ``Content-Type`` is automatically
|
||||
* set and will be used if **not overridden** by setting a custom
|
||||
@ -237,9 +247,15 @@ export class FetchRequest implements Iterable<[ key: string, value: string ]> {
|
||||
}
|
||||
|
||||
/**
|
||||
* The headers that will be used when requesting the URI.
|
||||
* The headers that will be used when requesting the URI. All
|
||||
* keys are lower-case.
|
||||
*
|
||||
* This object is a copy, so any chnages will **NOT** be reflected
|
||||
* in the ``FetchRequest``.
|
||||
*
|
||||
* To set a header entry, use the ``setHeader`` method.
|
||||
*/
|
||||
get headers(): Readonly<Record<string, string>> {
|
||||
get headers(): Record<string, string> {
|
||||
const headers = Object.assign({ }, this.#headers);
|
||||
|
||||
if (this.#creds) {
|
||||
@ -255,11 +271,11 @@ export class FetchRequest implements Iterable<[ key: string, value: string ]> {
|
||||
}
|
||||
if (this.body) { headers["content-length"] = String(this.body.length); }
|
||||
|
||||
return Object.freeze(headers);
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the header for %%key%%.
|
||||
* Get the header for %%key%%, ignoring case.
|
||||
*/
|
||||
getHeader(key: string): string {
|
||||
return this.headers[key.toLowerCase()];
|
||||
@ -274,7 +290,7 @@ export class FetchRequest implements Iterable<[ key: string, value: string ]> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all headers.
|
||||
* Clear all headers, resetting all intrinsic headers.
|
||||
*/
|
||||
clearHeaders(): void {
|
||||
this.#headers = { };
|
||||
@ -299,6 +315,8 @@ export class FetchRequest implements Iterable<[ key: string, value: string ]> {
|
||||
|
||||
/**
|
||||
* The value that will be sent for the ``Authorization`` header.
|
||||
*
|
||||
* To set the credentials, use the ``setCredentials`` method.
|
||||
*/
|
||||
get credentials(): null | string {
|
||||
return this.#creds || null;
|
||||
@ -313,7 +331,8 @@ export class FetchRequest implements Iterable<[ key: string, value: string ]> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow gzip-encoded responses.
|
||||
* Enable and request gzip-encoded responses. The response will
|
||||
* automatically be decompressed. //(default: true)//
|
||||
*/
|
||||
get allowGzip(): boolean {
|
||||
return this.#gzip;
|
||||
@ -324,7 +343,7 @@ export class FetchRequest implements Iterable<[ key: string, value: string ]> {
|
||||
|
||||
/**
|
||||
* Allow ``Authentication`` credentials to be sent over insecure
|
||||
* channels.
|
||||
* channels. //(default: false)//
|
||||
*/
|
||||
get allowInsecureAuthentication(): boolean {
|
||||
return !!this.#allowInsecure;
|
||||
@ -335,6 +354,7 @@ export class FetchRequest implements Iterable<[ key: string, value: string ]> {
|
||||
|
||||
/**
|
||||
* The timeout (in milliseconds) to wait for a complere response.
|
||||
* //(default: 5 minutes)//
|
||||
*/
|
||||
get timeout(): number { return this.#timeout; }
|
||||
set timeout(timeout: number) {
|
||||
@ -383,11 +403,17 @@ export class FetchRequest implements Iterable<[ key: string, value: string ]> {
|
||||
this.#retry = retry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new FetchRequest instance with default values.
|
||||
*
|
||||
* Once created, each property may be set before issuing a
|
||||
* ``.send()`` to make teh request.
|
||||
*/
|
||||
constructor(url: string) {
|
||||
this.#url = String(url);
|
||||
|
||||
this.#allowInsecure = false;
|
||||
this.#gzip = false;
|
||||
this.#gzip = true;
|
||||
this.#headers = { };
|
||||
this.#method = "";
|
||||
this.#timeout = 300000;
|
||||
@ -398,6 +424,10 @@ export class FetchRequest implements Iterable<[ key: string, value: string ]> {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the throttle parameters used to determine maximum
|
||||
* attempts and exponential-backoff properties.
|
||||
*/
|
||||
setThrottleParams(params: FetchThrottleParams): void {
|
||||
if (params.slotInterval != null) {
|
||||
this.#throttle.slotInterval = params.slotInterval;
|
||||
@ -600,7 +630,12 @@ export class FetchRequest implements Iterable<[ key: string, value: string ]> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the FetchGatewayFunc for %%scheme%% to %%func%%.
|
||||
* Use the %%func%% when fetching URIs using %%scheme%%.
|
||||
*
|
||||
* This method affects all requests globally.
|
||||
*
|
||||
* If [[lockConfig]] has been called, no change is made and this
|
||||
* throws.
|
||||
*/
|
||||
static registerGateway(scheme: string, func: FetchGatewayFunc): void {
|
||||
scheme = scheme.toLowerCase();
|
||||
@ -612,12 +647,41 @@ export class FetchRequest implements Iterable<[ key: string, value: string ]> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a custom function for fetching HTTP and HTTPS requests.
|
||||
* Use %%getUrl%% when fetching URIs over HTTP and HTTPS requests.
|
||||
*
|
||||
* This method affects all requests globally.
|
||||
*
|
||||
* If [[lockConfig]] has been called, no change is made and this
|
||||
* throws.
|
||||
*/
|
||||
static registerGetUrl(getUrl: FetchGetUrlFunc): void {
|
||||
if (locked) { throw new Error("gateways locked"); }
|
||||
getUrlFunc = getUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a function that can "fetch" data URIs.
|
||||
*
|
||||
* Note that this is automatically done internally to support
|
||||
* data URIs, so it is not necessary to register it.
|
||||
*
|
||||
* This is not generally something that is needed, but may
|
||||
* be useful in a wrapper to perfom custom data URI functionality.
|
||||
*/
|
||||
static createDataGateway(): FetchGatewayFunc {
|
||||
return dataGatewayFunc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a function that will fetch IPFS (unvalidated) from
|
||||
* a custom gateway baseUrl.
|
||||
*
|
||||
* The default IPFS gateway used internally is
|
||||
* ``"https:/\/gateway.ipfs.io/ipfs/"``.
|
||||
*/
|
||||
static createIpfsGatewayFunc(baseUrl: string): FetchGatewayFunc {
|
||||
return getIpfsGatewayFunc(baseUrl);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -632,7 +696,7 @@ interface ThrottleError extends Error {
|
||||
export class FetchResponse implements Iterable<[ key: string, value: string ]> {
|
||||
#statusCode: number;
|
||||
#statusMessage: string;
|
||||
#headers: Readonly<Record<string, string>>;
|
||||
#headers: Record<string, string>;
|
||||
#body: null | Readonly<Uint8Array>;
|
||||
#request: null | FetchRequest;
|
||||
|
||||
@ -653,21 +717,22 @@ export class FetchResponse implements Iterable<[ key: string, value: string ]> {
|
||||
get statusMessage(): string { return this.#statusMessage; }
|
||||
|
||||
/**
|
||||
* The response headers.
|
||||
* The response headers. All keys are lower-case.
|
||||
*/
|
||||
get headers(): Record<string, string> { return this.#headers; }
|
||||
get headers(): Record<string, string> { return Object.assign({ }, this.#headers); }
|
||||
|
||||
/**
|
||||
* The response body.
|
||||
* The response body, or ``null`` if there was no body.
|
||||
*/
|
||||
get body(): null | Readonly<Uint8Array> {
|
||||
return (this.#body == null) ? null: new Uint8Array(this.#body);
|
||||
}
|
||||
|
||||
/**
|
||||
* The response body as a UTF-8 encoded string.
|
||||
* The response body as a UTF-8 encoded string, or the empty
|
||||
* string (i.e. ``""``) if there was no body.
|
||||
*
|
||||
* An error is thrown if the body is invalid UTF-8 data.
|
||||
* An error is thrown if the body is invalid UTF-8 data.
|
||||
*/
|
||||
get bodyText(): string {
|
||||
try {
|
||||
@ -682,7 +747,8 @@ export class FetchResponse implements Iterable<[ key: string, value: string ]> {
|
||||
/**
|
||||
* The response body, decoded as JSON.
|
||||
*
|
||||
* An error is thrown if the body is invalid JSON-encoded data.
|
||||
* An error is thrown if the body is invalid JSON-encoded data
|
||||
* or if there was no body.
|
||||
*/
|
||||
get bodyJson(): any {
|
||||
try {
|
||||
@ -714,10 +780,10 @@ export class FetchResponse implements Iterable<[ key: string, value: string ]> {
|
||||
constructor(statusCode: number, statusMessage: string, headers: Readonly<Record<string, string>>, body: null | Uint8Array, request?: FetchRequest) {
|
||||
this.#statusCode = statusCode;
|
||||
this.#statusMessage = statusMessage;
|
||||
this.#headers = Object.freeze(Object.assign({ }, Object.keys(headers).reduce((accum, k) => {
|
||||
this.#headers = Object.keys(headers).reduce((accum, k) => {
|
||||
accum[k.toLowerCase()] = String(headers[k]);
|
||||
return accum;
|
||||
}, <Record<string, string>>{ })));
|
||||
}, <Record<string, string>>{ });
|
||||
this.#body = ((body == null) ? null: new Uint8Array(body));
|
||||
this.#request = (request || null);
|
||||
|
||||
@ -744,8 +810,9 @@ export class FetchResponse implements Iterable<[ key: string, value: string ]> {
|
||||
}
|
||||
|
||||
/**
|
||||
* If called within the [[processFunc]], causes the request to
|
||||
* retry as if throttled for %%stall%% milliseconds.
|
||||
* If called within a [request.processFunc](FetchRequest-processFunc)
|
||||
* call, causes the request to retry as if throttled for %%stall%%
|
||||
* milliseconds.
|
||||
*/
|
||||
throwThrottleError(message?: string, stall?: number): never {
|
||||
if (stall == null) {
|
||||
@ -762,7 +829,7 @@ export class FetchResponse implements Iterable<[ key: string, value: string ]> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the header value for %%key%%.
|
||||
* Get the header value for %%key%%, ignoring case.
|
||||
*/
|
||||
getHeader(key: string): string {
|
||||
return this.headers[key.toLowerCase()];
|
||||
@ -781,7 +848,7 @@ export class FetchResponse implements Iterable<[ key: string, value: string ]> {
|
||||
get request(): null | FetchRequest { return this.#request; }
|
||||
|
||||
/**
|
||||
* Returns true if this response was a success statuscode.
|
||||
* Returns true if this response was a success statusCode.
|
||||
*/
|
||||
ok(): boolean {
|
||||
return (this.#error.message === "" && this.statusCode >= 200 && this.statusCode < 300);
|
||||
|
@ -1,3 +1,8 @@
|
||||
/**
|
||||
* About fixed-point math...
|
||||
*
|
||||
* @_section: api/utils/fixed-point-math:Fixed-Point Maths [fixed-point-math]
|
||||
*/
|
||||
import { getBytes } from "./data.js";
|
||||
import { assert, assertArgument, assertPrivate } from "./errors.js";
|
||||
import { getBigInt, getNumber, fromTwos, toBigInt, toHex, toTwos } from "./maths.js";
|
||||
@ -28,6 +33,12 @@ function getMultiplier(decimals: number): bigint {
|
||||
return BigInt("1" + zeros.substring(0, decimals));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the fixed-point string representation of %%value%% to
|
||||
* divided by %%decimal%% places.
|
||||
*
|
||||
* @param {Numeric = 18} decimals
|
||||
*/
|
||||
export function formatFixed(_value: BigNumberish, _decimals?: Numeric): string {
|
||||
if (_decimals == null) { _decimals = 18; }
|
||||
|
||||
@ -58,24 +69,29 @@ export function formatFixed(_value: BigNumberish, _decimals?: Numeric): string {
|
||||
return result;
|
||||
}
|
||||
|
||||
export function parseFixed(value: string, _decimals: Numeric): bigint {
|
||||
/**
|
||||
* Returns the value of %%value%% multiplied by %%decimal%% places.
|
||||
*
|
||||
* @param {Numeric = 18} decimals
|
||||
*/
|
||||
export function parseFixed(str: string, _decimals: Numeric): bigint {
|
||||
if (_decimals == null) { _decimals = 18; }
|
||||
const decimals = getNumber(_decimals, "decimals");
|
||||
|
||||
const multiplier = getMultiplier(decimals);
|
||||
|
||||
assertArgument(typeof(value) === "string" && value.match(/^-?[0-9.]+$/),
|
||||
"invalid decimal value", "value", value);
|
||||
assertArgument(typeof(str) === "string" && str.match(/^-?[0-9.]+$/),
|
||||
"invalid decimal value", "str", str);
|
||||
|
||||
// Is it negative?
|
||||
const negative = (value.substring(0, 1) === "-");
|
||||
if (negative) { value = value.substring(1); }
|
||||
const negative = (str.substring(0, 1) === "-");
|
||||
if (negative) { str = str.substring(1); }
|
||||
|
||||
assertArgument(value !== ".", "missing value", "value", value);
|
||||
assertArgument(str !== ".", "missing value", "str", str);
|
||||
|
||||
// Split it into a whole and fractional part
|
||||
const comps = value.split(".");
|
||||
assertArgument(comps.length <= 2, "too many decimal points", "value", value);
|
||||
const comps = str.split(".");
|
||||
assertArgument(comps.length <= 2, "too many decimal points", "str", str);
|
||||
|
||||
let whole = (comps[0] || "0"), fraction = (comps[1] || "0");
|
||||
|
||||
@ -105,14 +121,41 @@ export function parseFixed(value: string, _decimals: Numeric): bigint {
|
||||
return wei;
|
||||
}
|
||||
|
||||
/**
|
||||
* A FixedFormat encapsulates the properties required to describe
|
||||
* a fixed-point arithmetic field.
|
||||
*/
|
||||
export class FixedFormat {
|
||||
/**
|
||||
* If true, negative values are permitted, otherwise only
|
||||
* positive values and zero are allowed.
|
||||
*/
|
||||
readonly signed: boolean;
|
||||
|
||||
/**
|
||||
* The number of bits available to store the value in the
|
||||
* fixed-point arithmetic field.
|
||||
*/
|
||||
readonly width: number;
|
||||
|
||||
/**
|
||||
* The number of decimal places in the fixed-point arithment field.
|
||||
*/
|
||||
readonly decimals: number;
|
||||
|
||||
/**
|
||||
* A human-readable representation of the fixed-point arithmetic field.
|
||||
*/
|
||||
readonly name: string;
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
readonly _multiplier: bigint;
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
constructor(guard: any, signed: boolean, width: number, decimals: number) {
|
||||
assertPrivate(guard, _guard, "FixedFormat");
|
||||
|
||||
@ -127,6 +170,25 @@ export class FixedFormat {
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new FixedFormat for %%value%%.
|
||||
*
|
||||
* If %%value%% is specified as a ``number``, the bit-width is
|
||||
* 128 bits and %%value%% is used for the ``decimals``.
|
||||
*
|
||||
* A string %%value%% may begin with ``fixed`` or ``ufixed``
|
||||
* for signed and unsigned respectfully. If no other properties
|
||||
* are specified, the bit-width is 128-bits with 18 decimals.
|
||||
*
|
||||
* To specify the bit-width and demicals, append them separated
|
||||
* by an ``"x"`` to the %%value%%.
|
||||
*
|
||||
* For example, ``ufixed128x18`` describes an unsigned, 128-bit
|
||||
* wide format with 18 decimals.
|
||||
*
|
||||
* If %%value%% is an other object, its properties for ``signed``,
|
||||
* ``width`` and ``decimals`` are checked.
|
||||
*/
|
||||
static from(value: any): FixedFormat {
|
||||
if (value instanceof FixedFormat) { return value; }
|
||||
|
||||
@ -170,16 +232,26 @@ export class FixedFormat {
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixed Number class
|
||||
* A FixedNumber represents a value over its [[FixedFormat]]
|
||||
* arithmetic field.
|
||||
*
|
||||
* A FixedNumber can be used to perform math, losslessly, on
|
||||
* values which have decmial places.
|
||||
*/
|
||||
export class FixedNumber {
|
||||
readonly format: FixedFormat;
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
readonly _isFixedNumber: boolean;
|
||||
|
||||
//#hex: string;
|
||||
#value: string;
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
constructor(guard: any, hex: string, value: string, format?: FixedFormat) {
|
||||
assertPrivate(guard, _guard, "FixedNumber");
|
||||
|
||||
@ -198,7 +270,7 @@ export class FixedNumber {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new [[FixedNumber]] with the result of this added
|
||||
* Returns a new [[FixedNumber]] with the result of %%this%% added
|
||||
* to %%other%%.
|
||||
*/
|
||||
addUnsafe(other: FixedNumber): FixedNumber {
|
||||
@ -208,6 +280,10 @@ export class FixedNumber {
|
||||
return FixedNumber.fromValue(a + b, this.format.decimals, this.format);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new [[FixedNumber]] with the result of %%other%% subtracted
|
||||
* %%this%%.
|
||||
*/
|
||||
subUnsafe(other: FixedNumber): FixedNumber {
|
||||
this.#checkFormat(other);
|
||||
const a = parseFixed(this.#value, this.format.decimals);
|
||||
@ -215,6 +291,10 @@ export class FixedNumber {
|
||||
return FixedNumber.fromValue(a - b, this.format.decimals, this.format);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new [[FixedNumber]] with the result of %%this%% multiplied
|
||||
* by %%other%%.
|
||||
*/
|
||||
mulUnsafe(other: FixedNumber): FixedNumber {
|
||||
this.#checkFormat(other);
|
||||
const a = parseFixed(this.#value, this.format.decimals);
|
||||
@ -222,6 +302,10 @@ export class FixedNumber {
|
||||
return FixedNumber.fromValue((a * b) / this.format._multiplier, this.format.decimals, this.format);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new [[FixedNumber]] with the result of %%this%% divided
|
||||
* by %%other%%.
|
||||
*/
|
||||
divUnsafe(other: FixedNumber): FixedNumber {
|
||||
this.#checkFormat(other);
|
||||
const a = parseFixed(this.#value, this.format.decimals);
|
||||
@ -229,6 +313,12 @@ export class FixedNumber {
|
||||
return FixedNumber.fromValue((a * this.format._multiplier) / b, this.format.decimals, this.format);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new [[FixedNumber]] which is the largest **integer**
|
||||
* that is less than or equal to %%this%%.
|
||||
*
|
||||
* The decimal component of the result will always be ``0``.
|
||||
*/
|
||||
floor(): FixedNumber {
|
||||
const comps = this.toString().split(".");
|
||||
if (comps.length === 1) { comps.push("0"); }
|
||||
@ -243,6 +333,12 @@ export class FixedNumber {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new [[FixedNumber]] which is the smallest **integer**
|
||||
* that is greater than or equal to %%this%%.
|
||||
*
|
||||
* The decimal component of the result will always be ``0``.
|
||||
*/
|
||||
ceiling(): FixedNumber {
|
||||
const comps = this.toString().split(".");
|
||||
if (comps.length === 1) { comps.push("0"); }
|
||||
@ -257,7 +353,14 @@ export class FixedNumber {
|
||||
return result;
|
||||
}
|
||||
|
||||
// @TODO: Support other rounding algorithms
|
||||
/**
|
||||
* Returns a new [[FixedNumber]] with the decimal component
|
||||
* rounded up on ties.
|
||||
*
|
||||
* The decimal component of the result will always be ``0``.
|
||||
*
|
||||
* @param {number = 0} decimals
|
||||
*/
|
||||
round(decimals?: number): FixedNumber {
|
||||
if (decimals == null) { decimals = 0; }
|
||||
|
||||
@ -276,14 +379,23 @@ export class FixedNumber {
|
||||
return this.mulUnsafe(factor).addUnsafe(bump).floor().divUnsafe(factor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if %%this%% is equal to ``0``.
|
||||
*/
|
||||
isZero(): boolean {
|
||||
return (this.#value === "0.0" || this.#value === "0");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if %%this%% is less than ``0``.
|
||||
*/
|
||||
isNegative(): boolean {
|
||||
return (this.#value[0] === "-");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the string representation of %%this%%.
|
||||
*/
|
||||
toString(): string { return this.#value; }
|
||||
|
||||
toHexString(_width: Numeric): string {
|
||||
@ -300,19 +412,45 @@ export class FixedNumber {
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a float approximation.
|
||||
*
|
||||
* Due to IEEE 754 precission (or lack thereof), this function
|
||||
* can only return an approximation and most values will contain
|
||||
* rounding errors.
|
||||
*/
|
||||
toUnsafeFloat(): number { return parseFloat(this.toString()); }
|
||||
|
||||
/**
|
||||
* Return a new [[FixedNumber]] with the same value but has had
|
||||
* its field set to %%format%%.
|
||||
*
|
||||
* This will throw if the value cannot fit into %%format%%.
|
||||
*/
|
||||
toFormat(format: FixedFormat | string): FixedNumber {
|
||||
return FixedNumber.fromString(this.#value, format);
|
||||
}
|
||||
|
||||
|
||||
static fromValue(value: BigNumberish, decimals: number = 0, format: FixedFormat | string | number = "fixed"): FixedNumber {
|
||||
/**
|
||||
* Creates a new [[FixedNumber]] for %%value%% multiplied by
|
||||
* %%decimal%% places with %%format%%.
|
||||
*
|
||||
* @param {number = 0} decimals
|
||||
* @param {FixedFormat | string | number = "fixed"} format
|
||||
*/
|
||||
static fromValue(value: BigNumberish, decimals?: number, format?: FixedFormat | string | number): FixedNumber {
|
||||
if (decimals == null) { decimals = 0; }
|
||||
if (format == null) { format = "fixed"; }
|
||||
return FixedNumber.fromString(formatFixed(value, decimals), FixedFormat.from(format));
|
||||
}
|
||||
|
||||
|
||||
static fromString(value: string, format: FixedFormat | string | number = "fixed"): FixedNumber {
|
||||
/**
|
||||
* Creates a new [[FixedNumber]] for %%value%% with %%format%%.
|
||||
*
|
||||
* @param {FixedFormat | string | number = "fixed"} format
|
||||
*/
|
||||
static fromString(value: string, format?: FixedFormat | string | number): FixedNumber {
|
||||
if (format == null) { format = "fixed"; }
|
||||
const fixedFormat = FixedFormat.from(format);
|
||||
const numeric = parseFixed(value, fixedFormat.decimals);
|
||||
|
||||
@ -332,7 +470,12 @@ export class FixedNumber {
|
||||
return new FixedNumber(_guard, hex, decimal, fixedFormat);
|
||||
}
|
||||
|
||||
static fromBytes(_value: BytesLike, format: FixedFormat | string | number = "fixed"): FixedNumber {
|
||||
/**
|
||||
* Creates a new [[FixedNumber]] with the big-endian representation
|
||||
* %%value%% with %%format%%.
|
||||
*/
|
||||
static fromBytes(_value: BytesLike, format?: FixedFormat | string | number): FixedNumber {
|
||||
if (format == null) { format = "fixed"; }
|
||||
const value = getBytes(_value, "value");
|
||||
const fixedFormat = FixedFormat.from(format);
|
||||
|
||||
@ -349,6 +492,9 @@ export class FixedNumber {
|
||||
return new FixedNumber(_guard, hex, decimal, fixedFormat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new [[FixedNumber]].
|
||||
*/
|
||||
static from(value: any, format?: FixedFormat | string | number): FixedNumber {
|
||||
if (typeof(value) === "string") {
|
||||
return FixedNumber.fromString(value, format);
|
||||
@ -370,6 +516,9 @@ export class FixedNumber {
|
||||
assertArgument(false, "invalid FixedNumber value", "value", value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if %%value%% is a [[FixedNumber]].
|
||||
*/
|
||||
static isFixedNumber(value: any): value is FixedNumber {
|
||||
return !!(value && value._isFixedNumber);
|
||||
}
|
||||
|
@ -1,18 +1,10 @@
|
||||
|
||||
////
|
||||
|
||||
export interface Freezable<T> {
|
||||
clone(): T;
|
||||
freeze(): Frozen<T>;
|
||||
isFrozen(): boolean;
|
||||
}
|
||||
|
||||
export type Frozen<T> = Readonly<{
|
||||
[ P in keyof T ]: T[P] extends (...args: Array<any>) => any ? T[P]:
|
||||
T[P] extends Freezable<any> ? Frozen<T[P]>:
|
||||
Readonly<T[P]>;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* There are many simple utilities required to interact with
|
||||
* Ethereum and to simplify the library, without increasing
|
||||
* the library dependencies for simple functions.
|
||||
*
|
||||
* @_section api/utils:Utilities [utils]
|
||||
*/
|
||||
|
||||
export { decodeBase58, encodeBase58 } from "./base58.js";
|
||||
|
||||
@ -31,7 +23,6 @@ export {
|
||||
export { EventPayload } from "./events.js";
|
||||
|
||||
export {
|
||||
getIpfsGatewayFunc,
|
||||
FetchRequest, FetchResponse, FetchCancelSignal,
|
||||
} from "./fetch.js";
|
||||
|
||||
@ -42,13 +33,11 @@ export {
|
||||
getBigInt, getNumber, toBigInt, toNumber, toHex, toArray, toQuantity
|
||||
} from "./maths.js";
|
||||
|
||||
export { resolveProperties, defineReadOnly, defineProperties} from "./properties.js";
|
||||
export { resolveProperties, defineProperties} from "./properties.js";
|
||||
|
||||
export { decodeRlp } from "./rlp-decode.js";
|
||||
export { encodeRlp } from "./rlp-encode.js";
|
||||
|
||||
export { getStore, setStore} from "./storage.js";
|
||||
|
||||
export { formatEther, parseEther, formatUnits, parseUnits } from "./units.js";
|
||||
|
||||
export {
|
||||
@ -59,6 +48,7 @@ export {
|
||||
Utf8ErrorFuncs,
|
||||
} from "./utf8.js";
|
||||
|
||||
export { uuidV4 } from "./uuid.js";
|
||||
|
||||
/////////////////////////////
|
||||
// Types
|
||||
|
@ -1,3 +1,8 @@
|
||||
/**
|
||||
* Some mathematic operations.
|
||||
*
|
||||
* @_subsection: api/utils:Math Helpers [maths]
|
||||
*/
|
||||
import { hexlify, isBytesLike } from "./data.js";
|
||||
import { assertArgument } from "./errors.js";
|
||||
|
||||
@ -21,7 +26,10 @@ const BN_1 = BigInt(1);
|
||||
const maxValue = 0x1fffffffffffff;
|
||||
|
||||
/**
|
||||
* Convert %%value%% from a twos-compliment value of %%width%% bits.
|
||||
* Convert %%value%% from a twos-compliment representation of %%width%%
|
||||
* bits to its value.
|
||||
*
|
||||
* If the highest bit is ``1``, the result will be negative.
|
||||
*/
|
||||
export function fromTwos(_value: BigNumberish, _width: Numeric): bigint {
|
||||
const value = getBigInt(_value, "value");
|
||||
@ -37,7 +45,10 @@ export function fromTwos(_value: BigNumberish, _width: Numeric): bigint {
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert %%value%% to a twos-compliment value of %%width%% bits.
|
||||
* Convert %%value%% to a twos-compliment representation of
|
||||
* %%width%% bits.
|
||||
*
|
||||
* The result will always be positive.
|
||||
*/
|
||||
export function toTwos(_value: BigNumberish, _width: Numeric): bigint {
|
||||
const value = getBigInt(_value, "value");
|
||||
@ -48,6 +59,7 @@ export function toTwos(_value: BigNumberish, _width: Numeric): bigint {
|
||||
return ((~(-value)) & mask) + BN_1;
|
||||
}
|
||||
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@ -73,6 +85,7 @@ export function getBigInt(value: BigNumberish, name?: string): bigint {
|
||||
return BigInt(value);
|
||||
case "string":
|
||||
try {
|
||||
if (value === "") { throw new Error("empty string"); }
|
||||
if (value[0] === "-" && value[1] !== "-") {
|
||||
return -BigInt(value.substring(1));
|
||||
}
|
||||
@ -119,6 +132,7 @@ export function getNumber(value: BigNumberish, name?: string): number {
|
||||
return value;
|
||||
case "string":
|
||||
try {
|
||||
if (value === "") { throw new Error("empty string"); }
|
||||
return getNumber(BigInt(value), name);
|
||||
} catch(e: any) {
|
||||
assertArgument(false, `invalid numeric string: ${ e.message }`, name || "value", value);
|
||||
@ -128,9 +142,9 @@ export function getNumber(value: BigNumberish, name?: string): number {
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Converts %%value%% to a number. If %%value%% is a Uint8Array, it
|
||||
* is treated as Big Endian data. Throws if the value is not safe.
|
||||
/**
|
||||
* Converts %%value%% to a number. If %%value%% is a Uint8Array, it
|
||||
* is treated as Big Endian data. Throws if the value is not safe.
|
||||
*/
|
||||
export function toNumber(value: BigNumberish | Uint8Array): number {
|
||||
return getNumber(toBigInt(value));
|
||||
@ -142,7 +156,7 @@ export function toNumber(value: BigNumberish | Uint8Array): number {
|
||||
*/
|
||||
export function toHex(_value: BigNumberish, _width?: Numeric): string {
|
||||
const value = getBigInt(_value, "value");
|
||||
if (value < 0) { throw new Error("cannot convert negative value to hex"); }
|
||||
assertArgument(value >= 0, "cannot toHex negative value", "value", _value);
|
||||
|
||||
let result = value.toString(16);
|
||||
|
||||
@ -151,7 +165,7 @@ export function toHex(_value: BigNumberish, _width?: Numeric): string {
|
||||
if (result.length % 2) { result = "0" + result; }
|
||||
} else {
|
||||
const width = getNumber(_width, "width");
|
||||
if (width * 2 < result.length) { throw new Error(`value ${ value } exceeds width ${ width }`); }
|
||||
assertArgument(width * 2 >= result.length, `value exceeds width`, "[ value, width ]", [ _value, _width ]);
|
||||
|
||||
// Pad the value to the required width
|
||||
while (result.length < (width * 2)) { result = "0" + result; }
|
||||
@ -166,7 +180,7 @@ export function toHex(_value: BigNumberish, _width?: Numeric): string {
|
||||
*/
|
||||
export function toArray(_value: BigNumberish): Uint8Array {
|
||||
const value = getBigInt(_value, "value");
|
||||
if (value < 0) { throw new Error("cannot convert negative value to hex"); }
|
||||
assertArgument(value >= 0, "cannot toArray negative value", "value", _value);
|
||||
|
||||
if (value === BN_0) { return new Uint8Array([ ]); }
|
||||
|
||||
|
@ -1,3 +1,13 @@
|
||||
/**
|
||||
* Property helper functions.
|
||||
*
|
||||
* @_subsection api/utils:Properties [properties]
|
||||
*/
|
||||
|
||||
/**
|
||||
* Resolves to a new object that is a copy of %%value%%, but with all
|
||||
* values resolved.
|
||||
*/
|
||||
export async function resolveProperties<T>(value: { [ P in keyof T ]: T[P] | Promise<T[P]>}): Promise<T> {
|
||||
const keys = Object.keys(value);
|
||||
const results = await Promise.all(keys.map((k) => Promise.resolve(value[<keyof T>k])));
|
||||
@ -7,104 +17,44 @@ export async function resolveProperties<T>(value: { [ P in keyof T ]: T[P] | Pro
|
||||
}, <{ [ P in keyof T]: T[P] }>{ });
|
||||
}
|
||||
|
||||
export function defineReadOnly<T, P extends keyof T>(object: T, name: P, value: T[P]): void {
|
||||
Object.defineProperty(object, name, {
|
||||
enumerable: true,
|
||||
value: value,
|
||||
writable: false,
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
export interface CancellablePromise<T> extends Promise<T> {
|
||||
cancel(): Promise<void>;
|
||||
}
|
||||
export type IsCancelled = () => Promise<boolean>;
|
||||
|
||||
export function createPromise<T>(resolve: (isCancelled: IsCancelled, (result: T) => void) => void, reject: (error: Error) => void, isCancelled: IsCancelled): CancellablePromise<T> {
|
||||
let cancelled = false;
|
||||
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
|
||||
});
|
||||
|
||||
(<CancellablePromise<T>>promise).cancel = function() {
|
||||
cancelled = true;
|
||||
};
|
||||
|
||||
return (<CancellablePromise<T>>promise);
|
||||
}
|
||||
*/
|
||||
/*
|
||||
export class A implements Freezable<A> {
|
||||
foo: number;
|
||||
constructor(foo: number) {
|
||||
this.foo = foo;
|
||||
}
|
||||
freeze(): Frozen<A> {
|
||||
Object.freeze(this);
|
||||
return this;
|
||||
}
|
||||
clone(): A {
|
||||
return new A(this.foo);
|
||||
}
|
||||
}
|
||||
|
||||
export class B implements Freezable<B> {
|
||||
a: A;
|
||||
constructor(a: A) {
|
||||
this.a = a;
|
||||
}
|
||||
freeze(): Frozen<B> {
|
||||
this.a.freeze();
|
||||
Object.freeze(this);
|
||||
return this;
|
||||
}
|
||||
clone(): B {
|
||||
return new B(this.a);
|
||||
}
|
||||
}
|
||||
|
||||
export function test() {
|
||||
const a = new A(123);
|
||||
const b = new B(a);
|
||||
b.a = new A(234);
|
||||
const b2 = b.freeze();
|
||||
b2.a.foo = 123; // = a;
|
||||
}
|
||||
*/
|
||||
|
||||
function checkType(value: any, type: string): void {
|
||||
function checkType(value: any, type: string, name: string): void {
|
||||
const types = type.split("|").map(t => t.trim());
|
||||
for (let i = 0; i < types.length; i++) {
|
||||
switch (type) {
|
||||
case "any":
|
||||
return;
|
||||
case "bigint":
|
||||
case "boolean":
|
||||
case "number":
|
||||
case "string":
|
||||
if (typeof(value) === type) { return; }
|
||||
}
|
||||
}
|
||||
throw new Error("invalid value for type");
|
||||
|
||||
const error: any = new Error(`invalid value for type ${ type }`);
|
||||
error.code = "INVALID_ARGUMENT";
|
||||
error.argument = `value.${ name }`;
|
||||
error.value = value;
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns the %%values%% to %%target%% as read-only values.
|
||||
*
|
||||
* It %%types%% is specified, the values are checked.
|
||||
*/
|
||||
export function defineProperties<T>(
|
||||
target: T,
|
||||
values: { [ K in keyof T ]?: undefined | T[K] },
|
||||
types?: { [ K in keyof T ]?: string },
|
||||
defaults?: { [ K in keyof T ]?: T[K] }): void {
|
||||
values: { [ K in keyof T ]?: T[K] },
|
||||
types?: { [ K in keyof T ]?: string }): void {
|
||||
|
||||
for (let key in values) {
|
||||
let value = values[key];
|
||||
|
||||
const fallback = (defaults ? defaults[key]: undefined);
|
||||
if (fallback !== undefined) {
|
||||
value = fallback;
|
||||
} else {
|
||||
const type = (types ? types[key]: null);
|
||||
if (type) { checkType(value, type); }
|
||||
}
|
||||
const type = (types ? types[key]: null);
|
||||
if (type) { checkType(value, type, key); }
|
||||
|
||||
Object.defineProperty(target, key, { enumerable: true, value, writable: false });
|
||||
}
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ function _encode(object: Array<any> | string): Array<number> {
|
||||
const nibbles = "0123456789abcdef";
|
||||
|
||||
/**
|
||||
* Encodes %%object%% as an RLP-encoded [[HexDataString]].
|
||||
* Encodes %%object%% as an RLP-encoded [[DataHexString]].
|
||||
*/
|
||||
export function encodeRlp(object: RlpStructuredData): string {
|
||||
let result = "0x";
|
||||
|
@ -1,8 +1,15 @@
|
||||
/**
|
||||
* The [[link-rlp]] (RLP) encoding is used throughout Ethereum
|
||||
* to serialize nested structures of Arrays and data.
|
||||
*
|
||||
* @_subsection api/utils:Recursive-Length Prefix [rlp]
|
||||
*/
|
||||
|
||||
export { decodeRlp } from "./rlp-decode.js";
|
||||
export { encodeRlp } from "./rlp-encode.js";
|
||||
|
||||
/**
|
||||
* An RLP-encoded structure.
|
||||
*/
|
||||
export type RlpStructuredData = string | Array<RlpStructuredData>;
|
||||
|
||||
export { decodeRlp } from "./rlp-decode.js";
|
||||
export { encodeRlp } from "./rlp-encode.js";
|
||||
|
@ -1,3 +1,24 @@
|
||||
/**
|
||||
* Most interactions with Ethereum requires integer values, which use
|
||||
* the smallest magnitude unit.
|
||||
*
|
||||
* For example, imagine dealing with dollars and cents. Since dollars
|
||||
* are divisible, non-integer values are possible, such as ``$10.77``.
|
||||
* By using the smallest indivisible unit (i.e. cents), the value can
|
||||
* be kept as the integer ``1077``.
|
||||
*
|
||||
* When receiving decimal input from the user (as a decimal string),
|
||||
* the value should be converted to an integer and when showing a user
|
||||
* a value, the integer value should be converted to a decimal string.
|
||||
*
|
||||
* This creates a clear distinction, between values to be used by code
|
||||
* (integers) and values used for display logic to users (decimals).
|
||||
*
|
||||
* The native unit in Ethereum, //ether// is divisible to 18 decimal places,
|
||||
* where each individual unit is called a //wei//.
|
||||
*
|
||||
* @_subsection api/utils:Unit Conversion [units]
|
||||
*/
|
||||
import { formatFixed, parseFixed } from "./fixednumber.js";
|
||||
import { assertArgument } from "./errors.js";
|
||||
|
||||
|
@ -1,3 +1,11 @@
|
||||
/**
|
||||
* Using strings in Ethereum (or any security-basd system) requires
|
||||
* additional care. These utilities attempt to mitigate some of the
|
||||
* safety issues as well as provide the ability to recover and analyse
|
||||
* strings.
|
||||
*
|
||||
* @_subsection api/utils:Strings and UTF-8 [strings]
|
||||
*/
|
||||
import { getBytes } from "./data.js";
|
||||
import { assertArgument, assertNormalize } from "./errors.js";
|
||||
|
||||
@ -6,49 +14,75 @@ import type { BytesLike } from "./index.js";
|
||||
|
||||
///////////////////////////////
|
||||
|
||||
/**
|
||||
* The stanard normalization forms.
|
||||
*/
|
||||
export type UnicodeNormalizationForm = "NFC" | "NFD" | "NFKC" | "NFKD";
|
||||
|
||||
export type Utf8ErrorReason =
|
||||
// A continuation byte was present where there was nothing to continue
|
||||
// - offset = the index the codepoint began in
|
||||
"UNEXPECTED_CONTINUE" |
|
||||
|
||||
// An invalid (non-continuation) byte to start a UTF-8 codepoint was found
|
||||
// - offset = the index the codepoint began in
|
||||
"BAD_PREFIX" |
|
||||
|
||||
// The string is too short to process the expected codepoint
|
||||
// - offset = the index the codepoint began in
|
||||
"OVERRUN" |
|
||||
|
||||
// A missing continuation byte was expected but not found
|
||||
// - offset = the index the continuation byte was expected at
|
||||
"MISSING_CONTINUE" |
|
||||
|
||||
// The computed code point is outside the range for UTF-8
|
||||
// - offset = start of this codepoint
|
||||
// - badCodepoint = the computed codepoint; outside the UTF-8 range
|
||||
"OUT_OF_RANGE" |
|
||||
|
||||
// UTF-8 strings may not contain UTF-16 surrogate pairs
|
||||
// - offset = start of this codepoint
|
||||
// - badCodepoint = the computed codepoint; inside the UTF-16 surrogate range
|
||||
"UTF16_SURROGATE" |
|
||||
|
||||
// The string is an overlong representation
|
||||
// - offset = start of this codepoint
|
||||
// - badCodepoint = the computed codepoint; already bounds checked
|
||||
"OVERLONG";
|
||||
/**
|
||||
* When using the UTF-8 error API the following errors can be intercepted
|
||||
* and processed as the %%reason%% passed to the [[Utf8ErrorFunc]].
|
||||
*
|
||||
* **``"UNEXPECTED_CONTINUE"``** - a continuation byte was present where there
|
||||
* was nothing to continue.
|
||||
*
|
||||
* **``"BAD_PREFIX"``** - an invalid (non-continuation) byte to start a
|
||||
* UTF-8 codepoint was found.
|
||||
*
|
||||
* **``"OVERRUN"``** - the string is too short to process the expected
|
||||
* codepoint length.
|
||||
*
|
||||
* **``"MISSING_CONTINUE"``** - a missing continuation byte was expected but
|
||||
* not found. The %%offset%% indicates the index the continuation byte
|
||||
* was expected at.
|
||||
*
|
||||
* **``"OUT_OF_RANGE"``** - the computed code point is outside the range
|
||||
* for UTF-8. The %%badCodepoint%% indicates the computed codepoint, which was
|
||||
* outside the valid UTF-8 range.
|
||||
*
|
||||
* **``"UTF16_SURROGATE"``** - the UTF-8 strings contained a UTF-16 surrogate
|
||||
* pair. The %%badCodepoint%% is the computed codepoint, which was inside the
|
||||
* UTF-16 surrogate range.
|
||||
*
|
||||
* **``"OVERLONG"``** - the string is an overlong representation. The
|
||||
* %%badCodepoint%% indicates the computed codepoint, which has already
|
||||
* been bounds checked.
|
||||
*
|
||||
*
|
||||
* @returns string
|
||||
*/
|
||||
export type Utf8ErrorReason = "UNEXPECTED_CONTINUE" | "BAD_PREFIX" | "OVERRUN" |
|
||||
"MISSING_CONTINUE" | "OUT_OF_RANGE" | "UTF16_SURROGATE" | "OVERLONG";
|
||||
|
||||
|
||||
export type Utf8ErrorFunc = (reason: Utf8ErrorReason, offset: number, bytes: ArrayLike<number>, output: Array<number>, badCodepoint?: number) => number;
|
||||
/**
|
||||
* A callback that can be used with [[toUtf8String]] to analysis or
|
||||
* recovery from invalid UTF-8 data.
|
||||
*
|
||||
* Parsing UTF-8 data is done through a simple Finite-State Machine (FSM)
|
||||
* which calls the ``Utf8ErrorFunc`` if a fault is detected.
|
||||
*
|
||||
* The %%reason%% indicates where in the FSM execution the fault
|
||||
* occurred and the %%offset%% indicates where the input failed.
|
||||
*
|
||||
* The %%bytes%% represents the raw UTF-8 data that was provided and
|
||||
* %%output%% is the current array of UTF-8 code-points, which may
|
||||
* be updated by the ``Utf8ErrorFunc``.
|
||||
*
|
||||
* The value of the %%badCodepoint%% depends on the %%reason%%. See
|
||||
* [[Utf8ErrorReason]] for details.
|
||||
*
|
||||
* The function should return the number of bytes that should be skipped
|
||||
* when control resumes to the FSM.
|
||||
*/
|
||||
export type Utf8ErrorFunc = (reason: Utf8ErrorReason, offset: number, bytes: Uint8Array, output: Array<number>, badCodepoint?: number) => number;
|
||||
|
||||
|
||||
function errorFunc(reason: Utf8ErrorReason, offset: number, bytes: ArrayLike<number>, output: Array<number>, badCodepoint?: number): number {
|
||||
function errorFunc(reason: Utf8ErrorReason, offset: number, bytes: Uint8Array, output: Array<number>, badCodepoint?: number): number {
|
||||
assertArgument(false, `invalid codepoint at offset ${ offset }; ${ reason }`, "bytes", bytes);
|
||||
}
|
||||
|
||||
function ignoreFunc(reason: Utf8ErrorReason, offset: number, bytes: ArrayLike<number>, output: Array<number>, badCodepoint?: number): number {
|
||||
function ignoreFunc(reason: Utf8ErrorReason, offset: number, bytes: Uint8Array, output: Array<number>, badCodepoint?: number): number {
|
||||
|
||||
// If there is an invalid prefix (including stray continuation), skip any additional continuation bytes
|
||||
if (reason === "BAD_PREFIX" || reason === "UNEXPECTED_CONTINUE") {
|
||||
@ -70,11 +104,12 @@ function ignoreFunc(reason: Utf8ErrorReason, offset: number, bytes: ArrayLike<nu
|
||||
return 0;
|
||||
}
|
||||
|
||||
function replaceFunc(reason: Utf8ErrorReason, offset: number, bytes: ArrayLike<number>, output: Array<number>, badCodepoint?: number): number {
|
||||
function replaceFunc(reason: Utf8ErrorReason, offset: number, bytes: Uint8Array, output: Array<number>, badCodepoint?: number): number {
|
||||
|
||||
// Overlong representations are otherwise "valid" code points; just non-deistingtished
|
||||
if (reason === "OVERLONG") {
|
||||
output.push((badCodepoint != null) ? badCodepoint: -1);
|
||||
assertArgument(typeof(badCodepoint) === "number", "invalid bad code point for replacement", "badCodepoint", badCodepoint);
|
||||
output.push(badCodepoint);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -191,6 +226,12 @@ function getUtf8CodePoints(_bytes: BytesLike, onError?: Utf8ErrorFunc): Array<nu
|
||||
}
|
||||
|
||||
// http://stackoverflow.com/questions/18729405/how-to-convert-utf8-string-to-byte-array
|
||||
|
||||
/**
|
||||
* Returns the UTF-8 byte representation of %%str%%.
|
||||
*
|
||||
* If %%form%% is specified, the string is normalized.
|
||||
*/
|
||||
export function toUtf8Bytes(str: string, form?: UnicodeNormalizationForm): Uint8Array {
|
||||
|
||||
if (form != null) {
|
||||
@ -233,7 +274,8 @@ export function toUtf8Bytes(str: string, form?: UnicodeNormalizationForm): Uint8
|
||||
return new Uint8Array(result);
|
||||
};
|
||||
|
||||
export function _toUtf8String(codePoints: Array<number>): string {
|
||||
//export
|
||||
function _toUtf8String(codePoints: Array<number>): string {
|
||||
return codePoints.map((codePoint) => {
|
||||
if (codePoint <= 0xffff) {
|
||||
return String.fromCharCode(codePoint);
|
||||
@ -246,10 +288,22 @@ export function _toUtf8String(codePoints: Array<number>): string {
|
||||
}).join("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the string represented by the UTF-8 data %%bytes%%.
|
||||
*
|
||||
* When %%onError%% function is specified, it is called on UTF-8
|
||||
* errors allowing recovery using the [[Utf8ErrorFunc]] API.
|
||||
* (default: [error](Utf8ErrorFuncs-error))
|
||||
*/
|
||||
export function toUtf8String(bytes: BytesLike, onError?: Utf8ErrorFunc): string {
|
||||
return _toUtf8String(getUtf8CodePoints(bytes, onError));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the UTF-8 code-points for %%str%%.
|
||||
*
|
||||
* If %%form%% is specified, the string is normalized.
|
||||
*/
|
||||
export function toUtf8CodePoints(str: string, form?: UnicodeNormalizationForm): Array<number> {
|
||||
return getUtf8CodePoints(toUtf8Bytes(str, form));
|
||||
}
|
||||
|
31
src.ts/utils/uuid.ts
Normal file
31
src.ts/utils/uuid.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { getBytes, hexlify } from "./data.js";
|
||||
|
||||
import type { BytesLike } from "./index.js";
|
||||
|
||||
/**
|
||||
* Returns the version 4 [[link-uuid]] for the %%randomBytes%%.
|
||||
*
|
||||
* @see: https://www.ietf.org/rfc/rfc4122.txt (Section 4.4)
|
||||
*/
|
||||
export function uuidV4(randomBytes: BytesLike): string {
|
||||
const bytes = getBytes(randomBytes, "randomBytes");
|
||||
|
||||
// Section: 4.1.3:
|
||||
// - time_hi_and_version[12:16] = 0b0100
|
||||
bytes[6] = (bytes[6] & 0x0f) | 0x40;
|
||||
|
||||
// Section 4.4
|
||||
// - clock_seq_hi_and_reserved[6] = 0b0
|
||||
// - clock_seq_hi_and_reserved[7] = 0b1
|
||||
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
||||
|
||||
const value = hexlify(bytes);
|
||||
|
||||
return [
|
||||
value.substring(2, 10),
|
||||
value.substring(10, 14),
|
||||
value.substring(14, 18),
|
||||
value.substring(18, 22),
|
||||
value.substring(22, 34),
|
||||
].join("-");
|
||||
}
|
Loading…
Reference in New Issue
Block a user