WIP: bootstrap and partial real Solana watcher implementation

This commit is contained in:
2026-08-16 09:17:45 +00:00
commit dc23412c3f
7232 changed files with 1687637 additions and 0 deletions

View File

@@ -0,0 +1,57 @@
{
"reg": {
"name": "reg",
"browser": "Mobile Safari 10.0.0 (iOS 10.3.0)",
"suite": "escape-short",
"hz": 537701.626064145,
"success": true,
"fastest": false,
"rme": 0.03257562097595429,
"rhz": 0.26353460226563175,
"sampleSize": 171
},
"fn if": {
"name": "fn if",
"browser": "Mobile Safari 10.0.0 (iOS 10.3.0)",
"suite": "escape-short",
"hz": 553756.7978187922,
"success": true,
"fastest": false,
"rme": 0.012983308427960112,
"rhz": 0.27140345200975097,
"sampleSize": 177
},
"fn if reverse": {
"name": "fn if reverse",
"browser": "Mobile Safari 10.0.0 (iOS 10.3.0)",
"suite": "escape-short",
"hz": 583552.7504135764,
"success": true,
"fastest": false,
"rme": 0.010066045577636453,
"rhz": 0.28600683822911,
"sampleSize": 177
},
"escape31": {
"name": "escape31",
"browser": "Mobile Safari 10.0.0 (iOS 10.3.0)",
"suite": "escape-short",
"hz": 570858.7989009853,
"success": true,
"fastest": false,
"rme": 0.025407559708725852,
"rhz": 0.27978536650410013,
"sampleSize": 175
},
"native": {
"name": "native",
"browser": "Mobile Safari 10.0.0 (iOS 10.3.0)",
"suite": "escape-short",
"hz": 2040345.4477760172,
"success": true,
"fastest": true,
"rme": 0.025360418301269214,
"rhz": 1,
"sampleSize": 168
}
}

View File

@@ -0,0 +1,47 @@
/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABILITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */
/// <reference lib="es2025.collection" />
interface Map<K, V> {
/**
* Returns a specified element from the Map object.
* If no element is associated with the specified key, a new element with the value `defaultValue` will be inserted into the Map and returned.
* @returns The element associated with the specified key, which will be `defaultValue` if no element previously existed.
*/
getOrInsert(key: K, defaultValue: V): V;
/**
* Returns a specified element from the Map object.
* If no element is associated with the specified key, the result of passing the specified key to the `callback` function will be inserted into the Map and returned.
* @returns The element associated with the specific key, which will be the newly computed value if no element previously existed.
*/
getOrInsertComputed(key: K, callback: (key: K) => V): V;
}
interface WeakMap<K extends WeakKey, V> {
/**
* Returns a specified element from the WeakMap object.
* If no element is associated with the specified key, a new element with the value `defaultValue` will be inserted into the WeakMap and returned.
* @returns The element associated with the specified key, which will be `defaultValue` if no element previously existed.
*/
getOrInsert(key: K, defaultValue: V): V;
/**
* Returns a specified element from the WeakMap object.
* If no element is associated with the specified key, the result of passing the specified key to the `callback` function will be inserted into the WeakMap and returned.
* @returns The element associated with the specific key, which will be the newly computed value if no element previously existed.
*/
getOrInsertComputed(key: K, callback: (key: K) => V): V;
}

View File

@@ -0,0 +1,83 @@
import rng from './rng.js';
import { unsafeStringify } from './stringify.js';
const _state = {};
function v1(options, buf, offset) {
let bytes;
const isV6 = options?._v6 ?? false;
if (options) {
const optionsKeys = Object.keys(options);
if (optionsKeys.length === 1 && optionsKeys[0] === '_v6') {
options = undefined;
}
}
if (options) {
bytes = v1Bytes(options.random ?? options.rng?.() ?? rng(), options.msecs, options.nsecs, options.clockseq, options.node, buf, offset);
}
else {
const now = Date.now();
const rnds = rng();
updateV1State(_state, now, rnds);
bytes = v1Bytes(rnds, _state.msecs, _state.nsecs, isV6 ? undefined : _state.clockseq, isV6 ? undefined : _state.node, buf, offset);
}
return buf ?? unsafeStringify(bytes);
}
export function updateV1State(state, now, rnds) {
state.msecs ??= -Infinity;
state.nsecs ??= 0;
if (now === state.msecs) {
state.nsecs++;
if (state.nsecs >= 10000) {
state.node = undefined;
state.nsecs = 0;
}
}
else if (now > state.msecs) {
state.nsecs = 0;
}
else if (now < state.msecs) {
state.node = undefined;
}
if (!state.node) {
state.node = rnds.slice(10, 16);
state.node[0] |= 0x01;
state.clockseq = ((rnds[8] << 8) | rnds[9]) & 0x3fff;
}
state.msecs = now;
return state;
}
function v1Bytes(rnds, msecs, nsecs, clockseq, node, buf, offset = 0) {
if (rnds.length < 16) {
throw new Error('Random bytes length must be >= 16');
}
if (!buf) {
buf = new Uint8Array(16);
offset = 0;
}
else {
if (offset < 0 || offset + 16 > buf.length) {
throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
}
}
msecs ??= Date.now();
nsecs ??= 0;
clockseq ??= ((rnds[8] << 8) | rnds[9]) & 0x3fff;
node ??= rnds.slice(10, 16);
msecs += 12219292800000;
const tl = ((msecs & 0xfffffff) * 10000 + nsecs) % 0x100000000;
buf[offset++] = (tl >>> 24) & 0xff;
buf[offset++] = (tl >>> 16) & 0xff;
buf[offset++] = (tl >>> 8) & 0xff;
buf[offset++] = tl & 0xff;
const tmh = ((msecs / 0x100000000) * 10000) & 0xfffffff;
buf[offset++] = (tmh >>> 8) & 0xff;
buf[offset++] = tmh & 0xff;
buf[offset++] = ((tmh >>> 24) & 0xf) | 0x10;
buf[offset++] = (tmh >>> 16) & 0xff;
buf[offset++] = (clockseq >>> 8) | 0x80;
buf[offset++] = clockseq & 0xff;
for (let n = 0; n < 6; ++n) {
buf[offset++] = node[n];
}
return buf;
}
export default v1;

View File

@@ -0,0 +1,140 @@
/**
* @fileoverview Rule to flag blocks with no reason to exist
* @author Brandon Mills
*/
"use strict";
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../types').Rule.RuleModule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "Disallow unnecessary nested blocks",
recommended: false,
url: "https://eslint.org/docs/latest/rules/no-lone-blocks",
},
schema: [],
messages: {
redundantBlock: "Block is redundant.",
redundantNestedBlock: "Nested block is redundant.",
},
},
create(context) {
// A stack of lone blocks to be checked for block-level bindings
const loneBlocks = [];
let ruleDef;
const sourceCode = context.sourceCode;
/**
* Reports a node as invalid.
* @param {ASTNode} node The node to be reported.
* @returns {void}
*/
function report(node) {
const messageId =
node.parent.type === "BlockStatement" ||
node.parent.type === "StaticBlock"
? "redundantNestedBlock"
: "redundantBlock";
context.report({
node,
messageId,
});
}
/**
* Checks for any occurrence of a BlockStatement in a place where lists of statements can appear
* @param {ASTNode} node The node to check
* @returns {boolean} True if the node is a lone block.
*/
function isLoneBlock(node) {
return (
node.parent.type === "BlockStatement" ||
node.parent.type === "StaticBlock" ||
node.parent.type === "Program" ||
// Don't report blocks in switch cases if the block is the only statement of the case.
(node.parent.type === "SwitchCase" &&
!(
node.parent.consequent[0] === node &&
node.parent.consequent.length === 1
))
);
}
/**
* Checks the enclosing block of the current node for block-level bindings,
* and "marks it" as valid if any.
* @param {ASTNode} node The current node to check.
* @returns {void}
*/
function markLoneBlock(node) {
if (loneBlocks.length === 0) {
return;
}
const block = node.parent;
if (loneBlocks.at(-1) === block) {
loneBlocks.pop();
}
}
// Default rule definition: report all lone blocks
ruleDef = {
BlockStatement(node) {
if (isLoneBlock(node)) {
report(node);
}
},
};
// ES6: report blocks without block-level bindings, or that's only child of another block
if (context.languageOptions.ecmaVersion >= 2015) {
ruleDef = {
BlockStatement(node) {
if (isLoneBlock(node)) {
loneBlocks.push(node);
}
},
"BlockStatement:exit"(node) {
if (loneBlocks.length > 0 && loneBlocks.at(-1) === node) {
loneBlocks.pop();
report(node);
} else if (
(node.parent.type === "BlockStatement" ||
node.parent.type === "StaticBlock") &&
node.parent.body.length === 1
) {
report(node);
}
},
};
ruleDef.VariableDeclaration = function (node) {
if (node.kind !== "var") {
markLoneBlock(node);
}
};
ruleDef.FunctionDeclaration = function (node) {
if (sourceCode.getScope(node).isStrict) {
markLoneBlock(node);
}
};
ruleDef.ClassDeclaration = markLoneBlock;
}
return ruleDef;
},
};

View File

@@ -0,0 +1,3 @@
import { URIRegExps } from "./uri";
declare const _default: URIRegExps;
export default _default;

View File

@@ -0,0 +1,3 @@
import z4 from "./classic/index.cjs";
export * from "./classic/index.cjs";
export default z4;

View File

@@ -0,0 +1,36 @@
import { type CHash, type Input } from './utils.ts';
/**
* HKDF-extract from spec. Less important part. `HKDF-Extract(IKM, salt) -> PRK`
* Arguments position differs from spec (IKM is first one, since it is not optional)
* @param hash - hash function that would be used (e.g. sha256)
* @param ikm - input keying material, the initial key
* @param salt - optional salt value (a non-secret random value)
*/
export declare function extract(hash: CHash, ikm: Input, salt?: Input): Uint8Array;
/**
* HKDF-expand from the spec. The most important part. `HKDF-Expand(PRK, info, L) -> OKM`
* @param hash - hash function that would be used (e.g. sha256)
* @param prk - a pseudorandom key of at least HashLen octets (usually, the output from the extract step)
* @param info - optional context and application specific information (can be a zero-length string)
* @param length - length of output keying material in bytes
*/
export declare function expand(hash: CHash, prk: Input, info?: Input, length?: number): Uint8Array;
/**
* HKDF (RFC 5869): derive keys from an initial input.
* Combines hkdf_extract + hkdf_expand in one step
* @param hash - hash function that would be used (e.g. sha256)
* @param ikm - input keying material, the initial key
* @param salt - optional salt value (a non-secret random value)
* @param info - optional context and application specific information (can be a zero-length string)
* @param length - length of output keying material in bytes
* @example
* import { hkdf } from '@noble/hashes/hkdf';
* import { sha256 } from '@noble/hashes/sha2';
* import { randomBytes } from '@noble/hashes/utils';
* const inputKey = randomBytes(32);
* const salt = randomBytes(32);
* const info = 'application-key';
* const hk1 = hkdf(sha256, inputKey, salt, info, 32);
*/
export declare const hkdf: (hash: CHash, ikm: Input, salt: Input | undefined, info: Input | undefined, length: number) => Uint8Array;
//# sourceMappingURL=hkdf.d.ts.map

View File

@@ -0,0 +1,78 @@
import type { IssueData, ZodErrorMap, ZodIssue } from "../ZodError.js";
import type { ZodParsedType } from "./util.js";
export declare const makeIssue: (params: {
data: any;
path: (string | number)[];
errorMaps: ZodErrorMap[];
issueData: IssueData;
}) => ZodIssue;
export type ParseParams = {
path: (string | number)[];
errorMap: ZodErrorMap;
async: boolean;
};
export type ParsePathComponent = string | number;
export type ParsePath = ParsePathComponent[];
export declare const EMPTY_PATH: ParsePath;
export interface ParseContext {
readonly common: {
readonly issues: ZodIssue[];
readonly contextualErrorMap?: ZodErrorMap | undefined;
readonly async: boolean;
};
readonly path: ParsePath;
readonly schemaErrorMap?: ZodErrorMap | undefined;
readonly parent: ParseContext | null;
readonly data: any;
readonly parsedType: ZodParsedType;
}
export type ParseInput = {
data: any;
path: (string | number)[];
parent: ParseContext;
};
export declare function addIssueToContext(ctx: ParseContext, issueData: IssueData): void;
export type ObjectPair = {
key: SyncParseReturnType<any>;
value: SyncParseReturnType<any>;
};
export declare class ParseStatus {
value: "aborted" | "dirty" | "valid";
dirty(): void;
abort(): void;
static mergeArray(status: ParseStatus, results: SyncParseReturnType<any>[]): SyncParseReturnType;
static mergeObjectAsync(status: ParseStatus, pairs: {
key: ParseReturnType<any>;
value: ParseReturnType<any>;
}[]): Promise<SyncParseReturnType<any>>;
static mergeObjectSync(status: ParseStatus, pairs: {
key: SyncParseReturnType<any>;
value: SyncParseReturnType<any>;
alwaysSet?: boolean;
}[]): SyncParseReturnType;
}
export interface ParseResult {
status: "aborted" | "dirty" | "valid";
data: any;
}
export type INVALID = {
status: "aborted";
};
export declare const INVALID: INVALID;
export type DIRTY<T> = {
status: "dirty";
value: T;
};
export declare const DIRTY: <T>(value: T) => DIRTY<T>;
export type OK<T> = {
status: "valid";
value: T;
};
export declare const OK: <T>(value: T) => OK<T>;
export type SyncParseReturnType<T = any> = OK<T> | DIRTY<T> | INVALID;
export type AsyncParseReturnType<T> = Promise<SyncParseReturnType<T>>;
export type ParseReturnType<T> = SyncParseReturnType<T> | AsyncParseReturnType<T>;
export declare const isAborted: (x: ParseReturnType<any>) => x is INVALID;
export declare const isDirty: <T>(x: ParseReturnType<T>) => x is OK<T> | DIRTY<T>;
export declare const isValid: <T>(x: ParseReturnType<T>) => x is OK<T>;
export declare const isAsync: <T>(x: ParseReturnType<T>) => x is AsyncParseReturnType<T>;

View File

@@ -0,0 +1,228 @@
/**
* RFC 7914 Scrypt KDF. Can be used to create a key from password and salt.
* @module
*/
import { pbkdf2 } from "./pbkdf2.js";
import { sha256 } from "./sha2.js";
// prettier-ignore
import { anumber, asyncLoop, checkOpts, clean, rotl, swap32IfBE, u32 } from "./utils.js";
// The main Scrypt loop: uses Salsa extensively.
// Six versions of the function were tried, this is the fastest one.
// prettier-ignore
function XorAndSalsa(prev, pi, input, ii, out, oi) {
// Based on https://cr.yp.to/salsa20.html
// Xor blocks
let y00 = prev[pi++] ^ input[ii++], y01 = prev[pi++] ^ input[ii++];
let y02 = prev[pi++] ^ input[ii++], y03 = prev[pi++] ^ input[ii++];
let y04 = prev[pi++] ^ input[ii++], y05 = prev[pi++] ^ input[ii++];
let y06 = prev[pi++] ^ input[ii++], y07 = prev[pi++] ^ input[ii++];
let y08 = prev[pi++] ^ input[ii++], y09 = prev[pi++] ^ input[ii++];
let y10 = prev[pi++] ^ input[ii++], y11 = prev[pi++] ^ input[ii++];
let y12 = prev[pi++] ^ input[ii++], y13 = prev[pi++] ^ input[ii++];
let y14 = prev[pi++] ^ input[ii++], y15 = prev[pi++] ^ input[ii++];
// Save state to temporary variables (salsa)
let x00 = y00, x01 = y01, x02 = y02, x03 = y03, x04 = y04, x05 = y05, x06 = y06, x07 = y07, x08 = y08, x09 = y09, x10 = y10, x11 = y11, x12 = y12, x13 = y13, x14 = y14, x15 = y15;
// Main loop (salsa)
for (let i = 0; i < 8; i += 2) {
x04 ^= rotl(x00 + x12 | 0, 7);
x08 ^= rotl(x04 + x00 | 0, 9);
x12 ^= rotl(x08 + x04 | 0, 13);
x00 ^= rotl(x12 + x08 | 0, 18);
x09 ^= rotl(x05 + x01 | 0, 7);
x13 ^= rotl(x09 + x05 | 0, 9);
x01 ^= rotl(x13 + x09 | 0, 13);
x05 ^= rotl(x01 + x13 | 0, 18);
x14 ^= rotl(x10 + x06 | 0, 7);
x02 ^= rotl(x14 + x10 | 0, 9);
x06 ^= rotl(x02 + x14 | 0, 13);
x10 ^= rotl(x06 + x02 | 0, 18);
x03 ^= rotl(x15 + x11 | 0, 7);
x07 ^= rotl(x03 + x15 | 0, 9);
x11 ^= rotl(x07 + x03 | 0, 13);
x15 ^= rotl(x11 + x07 | 0, 18);
x01 ^= rotl(x00 + x03 | 0, 7);
x02 ^= rotl(x01 + x00 | 0, 9);
x03 ^= rotl(x02 + x01 | 0, 13);
x00 ^= rotl(x03 + x02 | 0, 18);
x06 ^= rotl(x05 + x04 | 0, 7);
x07 ^= rotl(x06 + x05 | 0, 9);
x04 ^= rotl(x07 + x06 | 0, 13);
x05 ^= rotl(x04 + x07 | 0, 18);
x11 ^= rotl(x10 + x09 | 0, 7);
x08 ^= rotl(x11 + x10 | 0, 9);
x09 ^= rotl(x08 + x11 | 0, 13);
x10 ^= rotl(x09 + x08 | 0, 18);
x12 ^= rotl(x15 + x14 | 0, 7);
x13 ^= rotl(x12 + x15 | 0, 9);
x14 ^= rotl(x13 + x12 | 0, 13);
x15 ^= rotl(x14 + x13 | 0, 18);
}
// Write output (salsa)
out[oi++] = (y00 + x00) | 0;
out[oi++] = (y01 + x01) | 0;
out[oi++] = (y02 + x02) | 0;
out[oi++] = (y03 + x03) | 0;
out[oi++] = (y04 + x04) | 0;
out[oi++] = (y05 + x05) | 0;
out[oi++] = (y06 + x06) | 0;
out[oi++] = (y07 + x07) | 0;
out[oi++] = (y08 + x08) | 0;
out[oi++] = (y09 + x09) | 0;
out[oi++] = (y10 + x10) | 0;
out[oi++] = (y11 + x11) | 0;
out[oi++] = (y12 + x12) | 0;
out[oi++] = (y13 + x13) | 0;
out[oi++] = (y14 + x14) | 0;
out[oi++] = (y15 + x15) | 0;
}
function BlockMix(input, ii, out, oi, r) {
// The block B is r 128-byte chunks (which is equivalent of 2r 64-byte chunks)
let head = oi + 0;
let tail = oi + 16 * r;
for (let i = 0; i < 16; i++)
out[tail + i] = input[ii + (2 * r - 1) * 16 + i]; // X ← B[2r1]
for (let i = 0; i < r; i++, head += 16, ii += 16) {
// We write odd & even Yi at same time. Even: 0bXXXXX0 Odd: 0bXXXXX1
XorAndSalsa(out, tail, input, ii, out, head); // head[i] = Salsa(blockIn[2*i] ^ tail[i-1])
if (i > 0)
tail += 16; // First iteration overwrites tmp value in tail
XorAndSalsa(out, head, input, (ii += 16), out, tail); // tail[i] = Salsa(blockIn[2*i+1] ^ head[i])
}
}
// Common prologue and epilogue for sync/async functions
function scryptInit(password, salt, _opts) {
// Maxmem - 1GB+1KB by default
const opts = checkOpts({
dkLen: 32,
asyncTick: 10,
maxmem: 1024 ** 3 + 1024,
}, _opts);
const { N, r, p, dkLen, asyncTick, maxmem, onProgress } = opts;
anumber(N);
anumber(r);
anumber(p);
anumber(dkLen);
anumber(asyncTick);
anumber(maxmem);
if (onProgress !== undefined && typeof onProgress !== 'function')
throw new Error('progressCb should be function');
const blockSize = 128 * r;
const blockSize32 = blockSize / 4;
// Max N is 2^32 (Integrify is 32-bit). Real limit is 2^22: JS engines Uint8Array limit is 4GB in 2024.
// Spec check `N >= 2^(blockSize / 8)` is not done for compat with popular libs,
// which used incorrect r: 1, p: 8. Also, the check seems to be a spec error:
// https://www.rfc-editor.org/errata_search.php?rfc=7914
const pow32 = Math.pow(2, 32);
if (N <= 1 || (N & (N - 1)) !== 0 || N > pow32) {
throw new Error('Scrypt: N must be larger than 1, a power of 2, and less than 2^32');
}
if (p < 0 || p > ((pow32 - 1) * 32) / blockSize) {
throw new Error('Scrypt: p must be a positive integer less than or equal to ((2^32 - 1) * 32) / (128 * r)');
}
if (dkLen < 0 || dkLen > (pow32 - 1) * 32) {
throw new Error('Scrypt: dkLen should be positive integer less than or equal to (2^32 - 1) * 32');
}
const memUsed = blockSize * (N + p);
if (memUsed > maxmem) {
throw new Error('Scrypt: memused is bigger than maxMem. Expected 128 * r * (N + p) > maxmem of ' + maxmem);
}
// [B0...Bp1] ← PBKDF2HMAC-SHA256(Passphrase, Salt, 1, blockSize*ParallelizationFactor)
// Since it has only one iteration there is no reason to use async variant
const B = pbkdf2(sha256, password, salt, { c: 1, dkLen: blockSize * p });
const B32 = u32(B);
// Re-used between parallel iterations. Array(iterations) of B
const V = u32(new Uint8Array(blockSize * N));
const tmp = u32(new Uint8Array(blockSize));
let blockMixCb = () => { };
if (onProgress) {
const totalBlockMix = 2 * N * p;
// Invoke callback if progress changes from 10.01 to 10.02
// Allows to draw smooth progress bar on up to 8K screen
const callbackPer = Math.max(Math.floor(totalBlockMix / 10000), 1);
let blockMixCnt = 0;
blockMixCb = () => {
blockMixCnt++;
if (onProgress && (!(blockMixCnt % callbackPer) || blockMixCnt === totalBlockMix))
onProgress(blockMixCnt / totalBlockMix);
};
}
return { N, r, p, dkLen, blockSize32, V, B32, B, tmp, blockMixCb, asyncTick };
}
function scryptOutput(password, dkLen, B, V, tmp) {
const res = pbkdf2(sha256, password, B, { c: 1, dkLen });
clean(B, V, tmp);
return res;
}
/**
* Scrypt KDF from RFC 7914.
* @param password - pass
* @param salt - salt
* @param opts - parameters
* - `N` is cpu/mem work factor (power of 2 e.g. 2**18)
* - `r` is block size (8 is common), fine-tunes sequential memory read size and performance
* - `p` is parallelization factor (1 is common)
* - `dkLen` is output key length in bytes e.g. 32.
* - `asyncTick` - (default: 10) max time in ms for which async function can block execution
* - `maxmem` - (default: `1024 ** 3 + 1024` aka 1GB+1KB). A limit that the app could use for scrypt
* - `onProgress` - callback function that would be executed for progress report
* @returns Derived key
* @example
* scrypt('password', 'salt', { N: 2**18, r: 8, p: 1, dkLen: 32 });
*/
export function scrypt(password, salt, opts) {
const { N, r, p, dkLen, blockSize32, V, B32, B, tmp, blockMixCb } = scryptInit(password, salt, opts);
swap32IfBE(B32);
for (let pi = 0; pi < p; pi++) {
const Pi = blockSize32 * pi;
for (let i = 0; i < blockSize32; i++)
V[i] = B32[Pi + i]; // V[0] = B[i]
for (let i = 0, pos = 0; i < N - 1; i++) {
BlockMix(V, pos, V, (pos += blockSize32), r); // V[i] = BlockMix(V[i-1]);
blockMixCb();
}
BlockMix(V, (N - 1) * blockSize32, B32, Pi, r); // Process last element
blockMixCb();
for (let i = 0; i < N; i++) {
// First u32 of the last 64-byte block (u32 is LE)
const j = B32[Pi + blockSize32 - 16] % N; // j = Integrify(X) % iterations
for (let k = 0; k < blockSize32; k++)
tmp[k] = B32[Pi + k] ^ V[j * blockSize32 + k]; // tmp = B ^ V[j]
BlockMix(tmp, 0, B32, Pi, r); // B = BlockMix(B ^ V[j])
blockMixCb();
}
}
swap32IfBE(B32);
return scryptOutput(password, dkLen, B, V, tmp);
}
/**
* Scrypt KDF from RFC 7914. Async version.
* @example
* await scryptAsync('password', 'salt', { N: 2**18, r: 8, p: 1, dkLen: 32 });
*/
export async function scryptAsync(password, salt, opts) {
const { N, r, p, dkLen, blockSize32, V, B32, B, tmp, blockMixCb, asyncTick } = scryptInit(password, salt, opts);
swap32IfBE(B32);
for (let pi = 0; pi < p; pi++) {
const Pi = blockSize32 * pi;
for (let i = 0; i < blockSize32; i++)
V[i] = B32[Pi + i]; // V[0] = B[i]
let pos = 0;
await asyncLoop(N - 1, asyncTick, () => {
BlockMix(V, pos, V, (pos += blockSize32), r); // V[i] = BlockMix(V[i-1]);
blockMixCb();
});
BlockMix(V, (N - 1) * blockSize32, B32, Pi, r); // Process last element
blockMixCb();
await asyncLoop(N, asyncTick, () => {
// First u32 of the last 64-byte block (u32 is LE)
const j = B32[Pi + blockSize32 - 16] % N; // j = Integrify(X) % iterations
for (let k = 0; k < blockSize32; k++)
tmp[k] = B32[Pi + k] ^ V[j * blockSize32 + k]; // tmp = B ^ V[j]
BlockMix(tmp, 0, B32, Pi, r); // B = BlockMix(B ^ V[j])
blockMixCb();
});
}
swap32IfBE(B32);
return scryptOutput(password, dkLen, B, V, tmp);
}
//# sourceMappingURL=scrypt.js.map

View File

@@ -0,0 +1,538 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const test_buffers_1 = __importDefault(require("./testing/test-buffers"));
const buffer_list_1 = __importDefault(require("./testing/buffer-list"));
const _1 = require(".");
const assert_1 = __importDefault(require("assert"));
const stream_1 = require("stream");
const parser_1 = require("./parser");
const authOkBuffer = test_buffers_1.default.authenticationOk();
const paramStatusBuffer = test_buffers_1.default.parameterStatus('client_encoding', 'UTF8');
const readyForQueryBuffer = test_buffers_1.default.readyForQuery();
const backendKeyDataBuffer = test_buffers_1.default.backendKeyData(1, 2);
const commandCompleteBuffer = test_buffers_1.default.commandComplete('SELECT 3');
const parseCompleteBuffer = test_buffers_1.default.parseComplete();
const bindCompleteBuffer = test_buffers_1.default.bindComplete();
const portalSuspendedBuffer = test_buffers_1.default.portalSuspended();
const row1 = {
name: 'id',
tableID: 1,
attributeNumber: 2,
dataTypeID: 3,
dataTypeSize: 4,
typeModifier: 5,
formatCode: 0,
};
const oneRowDescBuff = test_buffers_1.default.rowDescription([row1]);
row1.name = 'bang';
const twoRowBuf = test_buffers_1.default.rowDescription([
row1,
{
name: 'whoah',
tableID: 10,
attributeNumber: 11,
dataTypeID: 12,
dataTypeSize: 13,
typeModifier: 14,
formatCode: 0,
},
]);
const rowWithBigOids = {
name: 'bigoid',
tableID: 3000000001,
attributeNumber: 2,
dataTypeID: 3000000003,
dataTypeSize: 4,
typeModifier: 5,
formatCode: 0,
};
const bigOidDescBuff = test_buffers_1.default.rowDescription([rowWithBigOids]);
const emptyRowFieldBuf = test_buffers_1.default.dataRow([]);
const oneFieldBuf = test_buffers_1.default.dataRow(['test']);
const expectedAuthenticationOkayMessage = {
name: 'authenticationOk',
length: 8,
};
const expectedParameterStatusMessage = {
name: 'parameterStatus',
parameterName: 'client_encoding',
parameterValue: 'UTF8',
length: 25,
};
const expectedBackendKeyDataMessage = {
name: 'backendKeyData',
processID: 1,
secretKey: 2,
};
const expectedReadyForQueryMessage = {
name: 'readyForQuery',
length: 5,
status: 'I',
};
const expectedCommandCompleteMessage = {
name: 'commandComplete',
length: 13,
text: 'SELECT 3',
};
const emptyRowDescriptionBuffer = new buffer_list_1.default()
.addInt16(0) // number of fields
.join(true, 'T');
const expectedEmptyRowDescriptionMessage = {
name: 'rowDescription',
length: 6,
fieldCount: 0,
fields: [],
};
const expectedOneRowMessage = {
name: 'rowDescription',
length: 27,
fieldCount: 1,
fields: [
{
name: 'id',
tableID: 1,
columnID: 2,
dataTypeID: 3,
dataTypeSize: 4,
dataTypeModifier: 5,
format: 'text',
},
],
};
const expectedTwoRowMessage = {
name: 'rowDescription',
length: 53,
fieldCount: 2,
fields: [
{
name: 'bang',
tableID: 1,
columnID: 2,
dataTypeID: 3,
dataTypeSize: 4,
dataTypeModifier: 5,
format: 'text',
},
{
name: 'whoah',
tableID: 10,
columnID: 11,
dataTypeID: 12,
dataTypeSize: 13,
dataTypeModifier: 14,
format: 'text',
},
],
};
const expectedBigOidMessage = {
name: 'rowDescription',
length: 31,
fieldCount: 1,
fields: [
{
name: 'bigoid',
tableID: 3000000001,
columnID: 2,
dataTypeID: 3000000003,
dataTypeSize: 4,
dataTypeModifier: 5,
format: 'text',
},
],
};
const emptyParameterDescriptionBuffer = new buffer_list_1.default()
.addInt16(0) // number of parameters
.join(true, 't');
const oneParameterDescBuf = test_buffers_1.default.parameterDescription([1111]);
const twoParameterDescBuf = test_buffers_1.default.parameterDescription([2222, 3333]);
const bigOidParameterDescBuf = test_buffers_1.default.parameterDescription([3000000003]);
const expectedEmptyParameterDescriptionMessage = {
name: 'parameterDescription',
length: 6,
parameterCount: 0,
dataTypeIDs: [],
};
const expectedOneParameterMessage = {
name: 'parameterDescription',
length: 10,
parameterCount: 1,
dataTypeIDs: [1111],
};
const expectedTwoParameterMessage = {
name: 'parameterDescription',
length: 14,
parameterCount: 2,
dataTypeIDs: [2222, 3333],
};
const expectedBigOidParameterMessage = {
name: 'parameterDescription',
length: 10,
parameterCount: 1,
dataTypeIDs: [3000000003],
};
const testForMessage = function (buffer, expectedMessage) {
it('receives and parses ' + expectedMessage.name, () => __awaiter(this, void 0, void 0, function* () {
const messages = yield parseBuffers([buffer]);
const [lastMessage] = messages;
for (const key in expectedMessage) {
assert_1.default.deepEqual(lastMessage[key], expectedMessage[key]);
}
}));
};
const plainPasswordBuffer = test_buffers_1.default.authenticationCleartextPassword();
const md5PasswordBuffer = test_buffers_1.default.authenticationMD5Password();
const SASLBuffer = test_buffers_1.default.authenticationSASL();
const SASLContinueBuffer = test_buffers_1.default.authenticationSASLContinue();
const SASLFinalBuffer = test_buffers_1.default.authenticationSASLFinal();
const expectedPlainPasswordMessage = {
name: 'authenticationCleartextPassword',
};
const expectedMD5PasswordMessage = {
name: 'authenticationMD5Password',
salt: Buffer.from([1, 2, 3, 4]),
};
const expectedSASLMessage = {
name: 'authenticationSASL',
mechanisms: ['SCRAM-SHA-256'],
};
const expectedSASLContinueMessage = {
name: 'authenticationSASLContinue',
data: 'data',
};
const expectedSASLFinalMessage = {
name: 'authenticationSASLFinal',
data: 'data',
};
const notificationResponseBuffer = test_buffers_1.default.notification(4, 'hi', 'boom');
const expectedNotificationResponseMessage = {
name: 'notification',
processId: 4,
channel: 'hi',
payload: 'boom',
};
const parseBuffers = (buffers) => __awaiter(void 0, void 0, void 0, function* () {
const stream = new stream_1.PassThrough();
for (const buffer of buffers) {
stream.write(buffer);
}
stream.end();
const msgs = [];
yield (0, _1.parse)(stream, (msg) => msgs.push(msg));
return msgs;
});
describe('PgPacketStream', function () {
testForMessage(authOkBuffer, expectedAuthenticationOkayMessage);
testForMessage(plainPasswordBuffer, expectedPlainPasswordMessage);
testForMessage(md5PasswordBuffer, expectedMD5PasswordMessage);
testForMessage(SASLBuffer, expectedSASLMessage);
testForMessage(SASLContinueBuffer, expectedSASLContinueMessage);
// this exercises a found bug in the parser:
// https://github.com/brianc/node-postgres/pull/2210#issuecomment-627626084
// and adds a test which is deterministic, rather than relying on network packet chunking
const extendedSASLContinueBuffer = Buffer.concat([SASLContinueBuffer, Buffer.from([1, 2, 3, 4])]);
testForMessage(extendedSASLContinueBuffer, expectedSASLContinueMessage);
testForMessage(SASLFinalBuffer, expectedSASLFinalMessage);
// this exercises a found bug in the parser:
// https://github.com/brianc/node-postgres/pull/2210#issuecomment-627626084
// and adds a test which is deterministic, rather than relying on network packet chunking
const extendedSASLFinalBuffer = Buffer.concat([SASLFinalBuffer, Buffer.from([1, 2, 4, 5])]);
testForMessage(extendedSASLFinalBuffer, expectedSASLFinalMessage);
testForMessage(paramStatusBuffer, expectedParameterStatusMessage);
testForMessage(backendKeyDataBuffer, expectedBackendKeyDataMessage);
testForMessage(readyForQueryBuffer, expectedReadyForQueryMessage);
testForMessage(commandCompleteBuffer, expectedCommandCompleteMessage);
testForMessage(notificationResponseBuffer, expectedNotificationResponseMessage);
testForMessage(test_buffers_1.default.emptyQuery(), {
name: 'emptyQuery',
length: 4,
});
testForMessage(Buffer.from([0x6e, 0, 0, 0, 4]), {
name: 'noData',
});
describe('rowDescription messages', function () {
testForMessage(emptyRowDescriptionBuffer, expectedEmptyRowDescriptionMessage);
testForMessage(oneRowDescBuff, expectedOneRowMessage);
testForMessage(twoRowBuf, expectedTwoRowMessage);
testForMessage(bigOidDescBuff, expectedBigOidMessage);
});
describe('parameterDescription messages', function () {
testForMessage(emptyParameterDescriptionBuffer, expectedEmptyParameterDescriptionMessage);
testForMessage(oneParameterDescBuf, expectedOneParameterMessage);
testForMessage(twoParameterDescBuf, expectedTwoParameterMessage);
testForMessage(bigOidParameterDescBuf, expectedBigOidParameterMessage);
});
describe('parsing rows', function () {
describe('parsing empty row', function () {
testForMessage(emptyRowFieldBuf, {
name: 'dataRow',
fieldCount: 0,
});
});
describe('parsing data row with fields', function () {
testForMessage(oneFieldBuf, {
name: 'dataRow',
fieldCount: 1,
fields: ['test'],
});
});
});
describe('notice message', function () {
// this uses the same logic as error message
const buff = test_buffers_1.default.notice([{ type: 'C', value: 'code' }]);
testForMessage(buff, {
name: 'notice',
code: 'code',
});
});
testForMessage(test_buffers_1.default.error([]), {
name: 'error',
});
describe('with all the fields', function () {
const buffer = test_buffers_1.default.error([
{
type: 'S',
value: 'ERROR',
},
{
type: 'C',
value: 'code',
},
{
type: 'M',
value: 'message',
},
{
type: 'D',
value: 'details',
},
{
type: 'H',
value: 'hint',
},
{
type: 'P',
value: '100',
},
{
type: 'p',
value: '101',
},
{
type: 'q',
value: 'query',
},
{
type: 'W',
value: 'where',
},
{
type: 'F',
value: 'file',
},
{
type: 'L',
value: 'line',
},
{
type: 'R',
value: 'routine',
},
{
type: 'Z', // ignored
value: 'alsdkf',
},
]);
testForMessage(buffer, {
name: 'error',
severity: 'ERROR',
code: 'code',
message: 'message',
detail: 'details',
hint: 'hint',
position: '100',
internalPosition: '101',
internalQuery: 'query',
where: 'where',
file: 'file',
line: 'line',
routine: 'routine',
});
});
testForMessage(parseCompleteBuffer, {
name: 'parseComplete',
});
testForMessage(bindCompleteBuffer, {
name: 'bindComplete',
});
testForMessage(bindCompleteBuffer, {
name: 'bindComplete',
});
testForMessage(test_buffers_1.default.closeComplete(), {
name: 'closeComplete',
});
describe('parses portal suspended message', function () {
testForMessage(portalSuspendedBuffer, {
name: 'portalSuspended',
});
});
describe('parses replication start message', function () {
testForMessage(Buffer.from([0x57, 0x00, 0x00, 0x00, 0x04]), {
name: 'replicationStart',
length: 4,
});
});
describe('copy', () => {
testForMessage(test_buffers_1.default.copyIn(0), {
name: 'copyInResponse',
length: 7,
binary: false,
columnTypes: [],
});
testForMessage(test_buffers_1.default.copyIn(2), {
name: 'copyInResponse',
length: 11,
binary: false,
columnTypes: [0, 1],
});
testForMessage(test_buffers_1.default.copyOut(0), {
name: 'copyOutResponse',
length: 7,
binary: false,
columnTypes: [],
});
testForMessage(test_buffers_1.default.copyOut(3), {
name: 'copyOutResponse',
length: 13,
binary: false,
columnTypes: [0, 1, 2],
});
testForMessage(test_buffers_1.default.copyDone(), {
name: 'copyDone',
length: 4,
});
testForMessage(test_buffers_1.default.copyData(Buffer.from([5, 6, 7])), {
name: 'copyData',
length: 7,
chunk: Buffer.from([5, 6, 7]),
});
});
// since the data message on a stream can randomly divide the incomming
// tcp packets anywhere, we need to make sure we can parse every single
// split on a tcp message
describe('split buffer, single message parsing', function () {
const fullBuffer = test_buffers_1.default.dataRow([null, 'bang', 'zug zug', null, '!']);
it('parses when full buffer comes in', function () {
return __awaiter(this, void 0, void 0, function* () {
const messages = yield parseBuffers([fullBuffer]);
const message = messages[0];
assert_1.default.equal(message.fields.length, 5);
assert_1.default.equal(message.fields[0], null);
assert_1.default.equal(message.fields[1], 'bang');
assert_1.default.equal(message.fields[2], 'zug zug');
assert_1.default.equal(message.fields[3], null);
assert_1.default.equal(message.fields[4], '!');
});
});
const testMessageReceivedAfterSplitAt = function (split) {
return __awaiter(this, void 0, void 0, function* () {
const firstBuffer = Buffer.alloc(fullBuffer.length - split);
const secondBuffer = Buffer.alloc(fullBuffer.length - firstBuffer.length);
fullBuffer.copy(firstBuffer, 0, 0);
fullBuffer.copy(secondBuffer, 0, firstBuffer.length);
const messages = yield parseBuffers([firstBuffer, secondBuffer]);
const message = messages[0];
assert_1.default.equal(message.fields.length, 5);
assert_1.default.equal(message.fields[0], null);
assert_1.default.equal(message.fields[1], 'bang');
assert_1.default.equal(message.fields[2], 'zug zug');
assert_1.default.equal(message.fields[3], null);
assert_1.default.equal(message.fields[4], '!');
});
};
it('parses when split in the middle', function () {
return testMessageReceivedAfterSplitAt(6);
});
it('parses when split at end', function () {
return testMessageReceivedAfterSplitAt(2);
});
it('parses when split at beginning', function () {
return Promise.all([
testMessageReceivedAfterSplitAt(fullBuffer.length - 2),
testMessageReceivedAfterSplitAt(fullBuffer.length - 1),
testMessageReceivedAfterSplitAt(fullBuffer.length - 5),
]);
});
});
describe('split buffer, multiple message parsing', function () {
const dataRowBuffer = test_buffers_1.default.dataRow(['!']);
const readyForQueryBuffer = test_buffers_1.default.readyForQuery();
const fullBuffer = Buffer.alloc(dataRowBuffer.length + readyForQueryBuffer.length);
dataRowBuffer.copy(fullBuffer, 0, 0);
readyForQueryBuffer.copy(fullBuffer, dataRowBuffer.length, 0);
const verifyMessages = function (messages) {
assert_1.default.strictEqual(messages.length, 2);
assert_1.default.deepEqual(messages[0], {
name: 'dataRow',
fieldCount: 1,
length: 11,
fields: ['!'],
});
assert_1.default.equal(messages[0].fields[0], '!');
assert_1.default.deepEqual(messages[1], {
name: 'readyForQuery',
length: 5,
status: 'I',
});
};
// sanity check
it('receives both messages when packet is not split', function () {
return __awaiter(this, void 0, void 0, function* () {
const messages = yield parseBuffers([fullBuffer]);
verifyMessages(messages);
});
});
const splitAndVerifyTwoMessages = function (split) {
return __awaiter(this, void 0, void 0, function* () {
const firstBuffer = Buffer.alloc(fullBuffer.length - split);
const secondBuffer = Buffer.alloc(fullBuffer.length - firstBuffer.length);
fullBuffer.copy(firstBuffer, 0, 0);
fullBuffer.copy(secondBuffer, 0, firstBuffer.length);
const messages = yield parseBuffers([firstBuffer, secondBuffer]);
verifyMessages(messages);
});
};
describe('receives both messages when packet is split', function () {
it('in the middle', function () {
return splitAndVerifyTwoMessages(11);
});
it('at the front', function () {
return Promise.all([
splitAndVerifyTwoMessages(fullBuffer.length - 1),
splitAndVerifyTwoMessages(fullBuffer.length - 4),
splitAndVerifyTwoMessages(fullBuffer.length - 6),
]);
});
it('at the end', function () {
return Promise.all([splitAndVerifyTwoMessages(8), splitAndVerifyTwoMessages(1)]);
});
});
});
it('cleans up the reader after handling a packet', function () {
const parser = new parser_1.Parser();
parser.parse(oneFieldBuf, () => { });
assert_1.default.strictEqual(parser.reader.buffer.byteLength, 0);
});
});
//# sourceMappingURL=inbound-parser.test.js.map

View File

@@ -0,0 +1,645 @@
'use strict'
const { version } = require('./package.json')
const { EventEmitter } = require('events')
const { Worker } = require('worker_threads')
const { join } = require('path')
const { pathToFileURL } = require('url')
const { wait } = require('./lib/wait')
const {
WRITE_INDEX,
READ_INDEX,
SEQ_INDEX
} = require('./lib/indexes')
const buffer = require('buffer')
const assert = require('assert')
const kImpl = Symbol('kImpl')
// Maximum pending buffered data before forcing a synchronous drain
const MAX_STRING = buffer.constants.MAX_STRING_LENGTH
function noop () {}
function updateState (stream, fn) {
Atomics.add(stream[kImpl].state, SEQ_INDEX, 1)
fn()
Atomics.add(stream[kImpl].state, SEQ_INDEX, 1)
Atomics.notify(stream[kImpl].state, SEQ_INDEX)
}
function resetIndexes (stream) {
updateState(stream, () => {
Atomics.store(stream[kImpl].state, READ_INDEX, 0)
Atomics.store(stream[kImpl].state, WRITE_INDEX, 0)
})
}
class FakeWeakRef {
constructor (value) {
this._value = value
}
deref () {
return this._value
}
}
class FakeFinalizationRegistry {
register () {}
unregister () {}
}
// Currently using FinalizationRegistry with code coverage breaks the world
// Ref: https://github.com/nodejs/node/issues/49344
const FinalizationRegistry = process.env.NODE_V8_COVERAGE ? FakeFinalizationRegistry : global.FinalizationRegistry || FakeFinalizationRegistry
const WeakRef = process.env.NODE_V8_COVERAGE ? FakeWeakRef : global.WeakRef || FakeWeakRef
const registry = new FinalizationRegistry((worker) => {
if (worker.exited) {
return
}
worker.terminate()
})
function createWorker (stream, opts) {
const { filename, workerData } = opts
const bundlerOverrides = '__bundlerPathsOverrides' in globalThis ? globalThis.__bundlerPathsOverrides : {}
const toExecute = bundlerOverrides['thread-stream-worker'] || join(__dirname, 'lib', 'worker.js')
const worker = new Worker(toExecute, {
...opts.workerOpts,
name: opts.workerOpts?.name || 'thread-stream',
trackUnmanagedFds: false,
workerData: {
filename: filename.indexOf('file://') === 0
? filename
: pathToFileURL(filename).href,
dataBuf: stream[kImpl].dataBuf,
stateBuf: stream[kImpl].stateBuf,
workerData: {
$context: {
threadStreamVersion: version
},
...workerData
}
}
})
// We keep a strong reference for now,
// we need to start writing first
worker.stream = new FakeWeakRef(stream)
worker.on('message', onWorkerMessage)
worker.on('exit', onWorkerExit)
registry.register(stream, worker)
return worker
}
function drain (stream) {
assert(!stream[kImpl].sync)
if (stream[kImpl].needDrain) {
stream[kImpl].needDrain = false
stream.emit('drain')
}
}
function nextFlush (stream) {
while (true) {
const writeIndex = Atomics.load(stream[kImpl].state, WRITE_INDEX)
const leftover = stream[kImpl].data.length - writeIndex
if (leftover > 0) {
if (stream[kImpl].bufLen === 0) {
stream[kImpl].flushing = false
if (stream[kImpl].ending) {
end(stream)
} else if (stream[kImpl].needDrain) {
process.nextTick(drain, stream)
}
return
}
write(stream, leftover, noop)
continue
}
if (leftover === 0) {
if (writeIndex === 0 && stream[kImpl].bufLen === 0) {
// we had a flushSync in the meanwhile
return
}
waitForRead(stream, () => {
if (stream.destroyed) {
return
}
resetIndexes(stream)
nextFlush(stream)
})
return
}
// This should never happen
destroy(stream, new Error('overwritten'))
return
}
}
function onWorkerMessage (msg) {
const stream = this.stream.deref()
if (stream === undefined) {
this.exited = true
// Terminate the worker.
this.terminate()
return
}
// Node.js watch mode may send internal worker messages that do not
// participate in thread-stream's worker protocol.
if (msg?.code == null) {
return
}
switch (msg.code) {
case 'READY':
// Replace the FakeWeakRef with a
// proper one.
this.stream = new WeakRef(stream)
waitForRead(stream, () => {
stream[kImpl].ready = true
stream.emit('ready')
})
break
case 'ERROR':
destroy(stream, msg.err)
break
case 'EVENT':
if (Array.isArray(msg.args)) {
stream.emit(msg.name, ...msg.args)
} else {
stream.emit(msg.name, msg.args)
}
break
case 'FLUSHED': {
if (msg.context !== 'thread-stream') {
destroy(stream, new Error('this should not happen: ' + msg.code))
break
}
const cb = stream[kImpl].flushCallbacks.get(msg.id)
if (cb) {
stream[kImpl].flushCallbacks.delete(msg.id)
process.nextTick(cb)
}
break
}
case 'WARNING':
process.emitWarning(msg.err)
break
default:
destroy(stream, new Error('this should not happen: ' + msg.code))
}
}
function onWorkerExit (code) {
const stream = this.stream.deref()
if (stream === undefined) {
// Nothing to do, the worker already exit
return
}
registry.unregister(stream)
stream.worker.exited = true
stream.worker.off('exit', onWorkerExit)
destroy(stream, code !== 0 ? new Error('the worker thread exited') : null)
}
class ThreadStream extends EventEmitter {
constructor (opts = {}) {
super()
if (opts.bufferSize < 4) {
throw new Error('bufferSize must at least fit a 4-byte utf-8 char')
}
this[kImpl] = {}
this[kImpl].stateBuf = new SharedArrayBuffer(128)
this[kImpl].state = new Int32Array(this[kImpl].stateBuf)
this[kImpl].dataBuf = new SharedArrayBuffer(opts.bufferSize || 4 * 1024 * 1024)
this[kImpl].data = Buffer.from(this[kImpl].dataBuf)
this[kImpl].sync = opts.sync || false
this[kImpl].ending = false
this[kImpl].ended = false
this[kImpl].needDrain = false
this[kImpl].destroyed = false
this[kImpl].flushing = false
this[kImpl].ready = false
this[kImpl].finished = false
this[kImpl].errored = null
this[kImpl].closed = false
this[kImpl].buf = []
this[kImpl].bufHead = 0
this[kImpl].bufLen = 0
this[kImpl].flushCallbacks = new Map()
this[kImpl].nextFlushId = 0
// TODO (fix): Make private?
this.worker = createWorker(this, opts) // TODO (fix): make private
this.on('message', (message, transferList) => {
this.worker.postMessage(message, transferList)
})
}
write (data) {
const dataBuf = Buffer.isBuffer(data) ? data : Buffer.from(data)
if (this[kImpl].destroyed) {
error(this, new Error('the worker has exited'))
return false
}
if (this[kImpl].ending) {
error(this, new Error('the worker is ending'))
return false
}
if (this[kImpl].flushing && this[kImpl].bufLen + dataBuf.length >= MAX_STRING) {
try {
writeSync(this)
this[kImpl].flushing = true
} catch (err) {
destroy(this, err)
return false
}
}
this[kImpl].buf.push(dataBuf)
this[kImpl].bufLen += dataBuf.length
if (this[kImpl].sync) {
try {
writeSync(this)
return true
} catch (err) {
destroy(this, err)
return false
}
}
if (!this[kImpl].flushing) {
this[kImpl].flushing = true
setImmediate(nextFlush, this)
}
this[kImpl].needDrain = this[kImpl].data.length - this[kImpl].bufLen - Atomics.load(this[kImpl].state, WRITE_INDEX) <= 0
return !this[kImpl].needDrain
}
end () {
if (this[kImpl].destroyed) {
return
}
this[kImpl].ending = true
end(this)
}
flush (cb) {
cb = typeof cb === 'function' ? cb : noop
flushBuffer(this, (err) => {
if (err) {
process.nextTick(cb, err)
return
}
requestWorkerFlush(this, cb)
})
}
flushSync () {
if (this[kImpl].destroyed) {
return
}
writeSync(this)
flushSync(this)
}
unref () {
this.worker.unref()
}
ref () {
this.worker.ref()
}
get ready () {
return this[kImpl].ready
}
get destroyed () {
return this[kImpl].destroyed
}
get closed () {
return this[kImpl].closed
}
get writable () {
return !this[kImpl].destroyed && !this[kImpl].ending
}
get writableEnded () {
return this[kImpl].ending
}
get writableFinished () {
return this[kImpl].finished
}
get writableNeedDrain () {
return this[kImpl].needDrain
}
get writableObjectMode () {
return false
}
get writableErrored () {
return this[kImpl].errored
}
}
function flushBuffer (stream, cb) {
if (stream[kImpl].destroyed) {
process.nextTick(cb, new Error('the worker has exited'))
return
}
if (!stream[kImpl].sync && (stream[kImpl].flushing || stream[kImpl].bufLen > 0)) {
setImmediate(flushBuffer, stream, cb)
return
}
waitForRead(stream, cb)
}
function waitForRead (stream, cb) {
const writeIndex = Atomics.load(stream[kImpl].state, WRITE_INDEX)
wait(stream[kImpl].state, READ_INDEX, writeIndex, Infinity, (err, res) => {
if (err) {
destroy(stream, err)
cb(err)
return
}
if (res !== 'ok') {
waitForRead(stream, cb)
return
}
cb()
})
}
function requestWorkerFlush (stream, cb) {
if (stream[kImpl].destroyed) {
process.nextTick(cb, new Error('the worker has exited'))
return
}
if (!stream[kImpl].ready) {
const onReady = () => {
cleanup()
requestWorkerFlush(stream, cb)
}
const onClose = () => {
cleanup()
process.nextTick(cb, new Error('the worker has exited'))
}
const cleanup = () => {
stream.off('ready', onReady)
stream.off('close', onClose)
}
stream.once('ready', onReady)
stream.once('close', onClose)
return
}
const id = ++stream[kImpl].nextFlushId
stream[kImpl].flushCallbacks.set(id, cb)
try {
stream.worker.postMessage({
code: 'FLUSH',
context: 'thread-stream',
id
})
} catch (err) {
stream[kImpl].flushCallbacks.delete(id)
destroy(stream, err)
process.nextTick(cb, err)
}
}
function failPendingFlushCallbacks (stream, err) {
const callbacks = stream[kImpl].flushCallbacks
if (callbacks.size === 0) {
return
}
const flushErr = err || new Error('the worker has exited')
for (const cb of callbacks.values()) {
process.nextTick(cb, flushErr)
}
callbacks.clear()
}
function error (stream, err) {
setImmediate(() => {
stream.emit('error', err)
})
}
function destroy (stream, err) {
if (stream[kImpl].destroyed) {
return
}
stream[kImpl].destroyed = true
failPendingFlushCallbacks(stream, err)
if (err) {
stream[kImpl].errored = err
error(stream, err)
}
if (!stream.worker.exited) {
stream.worker.terminate()
.catch(() => {})
.then(() => {
stream[kImpl].closed = true
stream.emit('close')
})
} else {
setImmediate(() => {
stream[kImpl].closed = true
stream.emit('close')
})
}
}
function write (stream, maxBytes, cb) {
// data is smaller than the shared buffer length
const current = Atomics.load(stream[kImpl].state, WRITE_INDEX)
let offset = current
let remaining = maxBytes
while (remaining > 0 && stream[kImpl].bufLen !== 0) {
const head = stream[kImpl].bufHead
const buf = stream[kImpl].buf[head]
if (buf.length <= remaining) {
buf.copy(stream[kImpl].data, offset)
offset += buf.length
remaining -= buf.length
stream[kImpl].bufLen -= buf.length
stream[kImpl].bufHead = head + 1
if (stream[kImpl].bufHead === stream[kImpl].buf.length) {
stream[kImpl].buf.length = 0
stream[kImpl].bufHead = 0
} else if (stream[kImpl].bufHead >= 1024 && stream[kImpl].bufHead * 2 >= stream[kImpl].buf.length) {
stream[kImpl].buf.splice(0, stream[kImpl].bufHead)
stream[kImpl].bufHead = 0
}
continue
}
buf.copy(stream[kImpl].data, offset, 0, remaining)
stream[kImpl].buf[head] = buf.subarray(remaining)
stream[kImpl].bufLen -= remaining
offset += remaining
remaining = 0
}
updateState(stream, () => {
Atomics.store(stream[kImpl].state, WRITE_INDEX, offset)
})
cb()
return true
}
function end (stream) {
if (stream[kImpl].ended || !stream[kImpl].ending || stream[kImpl].flushing) {
return
}
stream[kImpl].ended = true
try {
stream.flushSync()
let readIndex = Atomics.load(stream[kImpl].state, READ_INDEX)
// process._rawDebug('writing index')
updateState(stream, () => {
Atomics.store(stream[kImpl].state, WRITE_INDEX, -1)
})
// process._rawDebug(`(end) readIndex (${Atomics.load(stream.state, READ_INDEX)}) writeIndex (${Atomics.load(stream.state, WRITE_INDEX)})`)
// Wait for the process to complete
let spins = 0
while (readIndex !== -1) {
// process._rawDebug(`read = ${read}`)
Atomics.wait(stream[kImpl].state, READ_INDEX, readIndex, 1000)
readIndex = Atomics.load(stream[kImpl].state, READ_INDEX)
if (readIndex === -2) {
destroy(stream, new Error('end() failed'))
return
}
if (++spins === 10) {
destroy(stream, new Error('end() took too long (10s)'))
return
}
}
process.nextTick(() => {
stream[kImpl].finished = true
stream.emit('finish')
})
} catch (err) {
destroy(stream, err)
}
// process._rawDebug('end finished...')
}
function writeSync (stream) {
const cb = () => {
if (stream[kImpl].ending) {
end(stream)
} else if (stream[kImpl].needDrain) {
process.nextTick(drain, stream)
}
}
stream[kImpl].flushing = false
while (stream[kImpl].bufLen !== 0) {
const writeIndex = Atomics.load(stream[kImpl].state, WRITE_INDEX)
const leftover = stream[kImpl].data.length - writeIndex
if (leftover === 0) {
flushSync(stream)
resetIndexes(stream)
continue
} else if (leftover < 0) {
// stream should never happen
throw new Error('overwritten')
}
write(stream, leftover, cb)
}
}
function flushSync (stream) {
if (stream[kImpl].flushing) {
throw new Error('unable to flush while flushing')
}
// process._rawDebug('flushSync started')
const writeIndex = Atomics.load(stream[kImpl].state, WRITE_INDEX)
let spins = 0
// TODO handle deadlock
while (true) {
const readIndex = Atomics.load(stream[kImpl].state, READ_INDEX)
if (readIndex === -2) {
throw Error('_flushSync failed')
}
// process._rawDebug(`(flushSync) readIndex (${readIndex}) writeIndex (${writeIndex})`)
if (readIndex !== writeIndex) {
// TODO stream timeouts for some reason.
Atomics.wait(stream[kImpl].state, READ_INDEX, readIndex, 1000)
} else {
break
}
if (++spins === 10) {
throw new Error('_flushSync took too long (10s)')
}
}
// process._rawDebug('flushSync finished')
}
module.exports = ThreadStream

View File

@@ -0,0 +1,89 @@
import { type CurveFnWithCreate } from './_shortw_utils.ts';
import type { CurveLengths } from './abstract/curve.ts';
import { type H2CHasher, type H2CMethod } from './abstract/hash-to-curve.ts';
import { mod } from './abstract/modular.ts';
import { type WeierstrassPoint as PointType, type WeierstrassPointCons } from './abstract/weierstrass.ts';
import type { Hex, PrivKey } from './utils.ts';
import { bytesToNumberBE, numberToBytesBE } from './utils.ts';
/**
* secp256k1 curve, ECDSA and ECDH methods.
*
* Field: `2n**256n - 2n**32n - 2n**9n - 2n**8n - 2n**7n - 2n**6n - 2n**4n - 1n`
*
* @example
* ```js
* import { secp256k1 } from '@noble/curves/secp256k1';
* const { secretKey, publicKey } = secp256k1.keygen();
* const msg = new TextEncoder().encode('hello');
* const sig = secp256k1.sign(msg, secretKey);
* const isValid = secp256k1.verify(sig, msg, publicKey) === true;
* ```
*/
export declare const secp256k1: CurveFnWithCreate;
declare function taggedHash(tag: string, ...messages: Uint8Array[]): Uint8Array;
/**
* lift_x from BIP340. Convert 32-byte x coordinate to elliptic curve point.
* @returns valid point checked for being on-curve
*/
declare function lift_x(x: bigint): PointType<bigint>;
/**
* Schnorr public key is just `x` coordinate of Point as per BIP340.
*/
declare function schnorrGetPublicKey(secretKey: Hex): Uint8Array;
/**
* Creates Schnorr signature as per BIP340. Verifies itself before returning anything.
* auxRand is optional and is not the sole source of k generation: bad CSPRNG won't be dangerous.
*/
declare function schnorrSign(message: Hex, secretKey: PrivKey, auxRand?: Hex): Uint8Array;
/**
* Verifies Schnorr signature.
* Will swallow errors & return false except for initial type validation of arguments.
*/
declare function schnorrVerify(signature: Hex, message: Hex, publicKey: Hex): boolean;
export type SecpSchnorr = {
keygen: (seed?: Uint8Array) => {
secretKey: Uint8Array;
publicKey: Uint8Array;
};
getPublicKey: typeof schnorrGetPublicKey;
sign: typeof schnorrSign;
verify: typeof schnorrVerify;
Point: WeierstrassPointCons<bigint>;
utils: {
randomSecretKey: (seed?: Uint8Array) => Uint8Array;
pointToBytes: (point: PointType<bigint>) => Uint8Array;
lift_x: typeof lift_x;
taggedHash: typeof taggedHash;
/** @deprecated use `randomSecretKey` */
randomPrivateKey: (seed?: Uint8Array) => Uint8Array;
/** @deprecated use `utils` */
numberToBytesBE: typeof numberToBytesBE;
/** @deprecated use `utils` */
bytesToNumberBE: typeof bytesToNumberBE;
/** @deprecated use `modular` */
mod: typeof mod;
};
lengths: CurveLengths;
};
/**
* Schnorr signatures over secp256k1.
* https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki
* @example
* ```js
* import { schnorr } from '@noble/curves/secp256k1';
* const { secretKey, publicKey } = schnorr.keygen();
* // const publicKey = schnorr.getPublicKey(secretKey);
* const msg = new TextEncoder().encode('hello');
* const sig = schnorr.sign(msg, secretKey);
* const isValid = schnorr.verify(sig, msg, publicKey);
* ```
*/
export declare const schnorr: SecpSchnorr;
/** Hashing / encoding to secp256k1 points / field. RFC 9380 methods. */
export declare const secp256k1_hasher: H2CHasher<bigint>;
/** @deprecated use `import { secp256k1_hasher } from '@noble/curves/secp256k1.js';` */
export declare const hashToCurve: H2CMethod<bigint>;
/** @deprecated use `import { secp256k1_hasher } from '@noble/curves/secp256k1.js';` */
export declare const encodeToCurve: H2CMethod<bigint>;
export {};
//# sourceMappingURL=secp256k1.d.ts.map

View File

@@ -0,0 +1,360 @@
'use strict'
process.env.TZ = 'UTC'
const path = require('node:path')
const { spawn } = require('node:child_process')
const { describe, test } = require('node:test')
const { once } = require('./helper')
const bin = require.resolve(path.join(__dirname, '..', 'bin.js'))
const epoch = 1522431328992
const logLine = '{"level":30,"time":1522431328992,"msg":"hello world","pid":42,"hostname":"foo"}\n'
const env = { TERM: 'dumb', TZ: 'UTC' }
const formattedEpoch = '17:35:28.992'
describe('cli', () => {
test('does basic reformatting', async (t) => {
t.plan(1)
const child = spawn(process.argv[0], [bin], { env })
child.on('error', t.assert.fail)
const endPromise = once(child.stdout, 'data', (data) => {
t.assert.strictEqual(data.toString(), `[${formattedEpoch}] INFO (42): hello world\n`)
})
child.stdin.write(logLine)
await endPromise
t.after(() => child.kill())
})
for (const optionName of ['--levelFirst', '-l']) {
test(`flips epoch and level via ${optionName}`, async (t) => {
t.plan(1)
const child = spawn(process.argv[0], [bin, optionName], { env })
child.on('error', t.assert.fail)
const endPromise = once(child.stdout, 'data', (data) => {
t.assert.strictEqual(data.toString(), `INFO [${formattedEpoch}] (42): hello world\n`)
})
child.stdin.write(logLine)
await endPromise
t.after(() => child.kill())
})
}
for (const optionName of ['--translateTime', '-t']) {
test(`translates time to default format via ${optionName}`, async (t) => {
t.plan(1)
const child = spawn(process.argv[0], [bin, optionName], { env })
child.on('error', t.assert.fail)
const endPromise = once(child.stdout, 'data', (data) => {
t.assert.strictEqual(data.toString(), `[${formattedEpoch}] INFO (42): hello world\n`)
})
child.stdin.write(logLine)
await endPromise
t.after(() => child.kill())
})
}
for (const optionName of ['--ignore', '-i']) {
test('does ignore multiple keys', async (t) => {
t.plan(1)
const child = spawn(process.argv[0], [bin, optionName, 'pid,hostname'], { env })
child.on('error', t.assert.fail)
const endPromise = once(child.stdout, 'data', (data) => {
t.assert.strictEqual(data.toString(), `[${formattedEpoch}] INFO: hello world\n`)
})
child.stdin.write(logLine)
await endPromise
t.after(() => child.kill())
})
}
for (const optionName of ['--customLevels', '-x']) {
test(`customize levels via ${optionName}`, async (t) => {
t.plan(1)
const logLine = '{"level":1,"time":1522431328992,"msg":"hello world","pid":42,"hostname":"foo"}\n'
const child = spawn(process.argv[0], [bin, optionName, 'err:99,info:1'], { env })
child.on('error', t.assert.fail)
const endPromise = once(child.stdout, 'data', (data) => {
t.assert.strictEqual(data.toString(), `[${formattedEpoch}] INFO (42): hello world\n`)
})
child.stdin.write(logLine)
await endPromise
t.after(() => child.kill())
})
test(`customize levels via ${optionName} without index`, async (t) => {
t.plan(1)
const logLine = '{"level":1,"time":1522431328992,"msg":"hello world","pid":42,"hostname":"foo"}\n'
const child = spawn(process.argv[0], [bin, optionName, 'err:99,info'], { env })
child.on('error', t.assert.fail)
const endPromise = once(child.stdout, 'data', (data) => {
t.assert.strictEqual(data.toString(), `[${formattedEpoch}] INFO (42): hello world\n`)
})
child.stdin.write(logLine)
await endPromise
t.after(() => child.kill())
})
test(`customize levels via ${optionName} with minimumLevel`, async (t) => {
t.plan(1)
const child = spawn(process.argv[0], [bin, '--minimumLevel', 'err', optionName, 'err:99,info:1'], { env })
child.on('error', t.assert.fail)
const endPromise = once(child.stdout, 'data', (data) => {
t.assert.strictEqual(data.toString(), `[${formattedEpoch}] ERR (42): hello world\n`)
})
child.stdin.write('{"level":1,"time":1522431328992,"msg":"hello world","pid":42,"hostname":"foo"}\n')
child.stdin.write('{"level":99,"time":1522431328992,"msg":"hello world","pid":42,"hostname":"foo"}\n')
await endPromise
t.after(() => child.kill())
})
test(`customize levels via ${optionName} with minimumLevel, customLevels and useOnlyCustomProps false`, async (t) => {
t.plan(1)
const child = spawn(process.argv[0], [bin, '--minimumLevel', 'custom', '--useOnlyCustomProps', 'false', optionName, 'custom:99,info:1'], { env })
child.on('error', t.assert.fail)
const endPromise = once(child.stdout, 'data', (data) => {
t.assert.strictEqual(data.toString(), `[${formattedEpoch}] CUSTOM (42): hello world\n`)
})
child.stdin.write('{"level":1,"time":1522431328992,"msg":"hello world","pid":42,"hostname":"foo"}\n')
child.stdin.write('{"level":99,"time":1522431328992,"msg":"hello world","pid":42,"hostname":"foo"}\n')
await endPromise
t.after(() => child.kill())
})
test(`customize levels via ${optionName} with minimumLevel, customLevels and useOnlyCustomProps true`, async (t) => {
t.plan(1)
const child = spawn(process.argv[0], [bin, '--minimumLevel', 'custom', '--useOnlyCustomProps', 'true', optionName, 'custom:99,info:1'], { env })
child.on('error', t.assert.fail)
const endPromise = once(child.stdout, 'data', (data) => {
t.assert.strictEqual(data.toString(), `[${formattedEpoch}] CUSTOM (42): hello world\n`)
})
child.stdin.write('{"level":1,"time":1522431328992,"msg":"hello world","pid":42,"hostname":"foo"}\n')
child.stdin.write('{"level":99,"time":1522431328992,"msg":"hello world","pid":42,"hostname":"foo"}\n')
await endPromise
t.after(() => child.kill())
})
}
for (const optionName of ['--customColors', '-X']) {
test(`customize levels via ${optionName}`, async (t) => {
t.plan(1)
const child = spawn(process.argv[0], [bin, optionName, 'info:blue,message:red'], { env })
child.on('error', t.assert.fail)
const endPromise = once(child.stdout, 'data', (data) => {
t.assert.strictEqual(data.toString(), `[${formattedEpoch}] INFO (42): hello world\n`)
})
child.stdin.write(logLine)
await endPromise
t.after(() => child.kill())
})
test(`customize levels via ${optionName} with customLevels`, async (t) => {
t.plan(1)
const logLine = '{"level":1,"time":1522431328992,"msg":"hello world","pid":42,"hostname":"foo"}\n'
const child = spawn(process.argv[0], [bin, '--customLevels', 'err:99,info', optionName, 'info:blue,message:red'], { env })
child.on('error', t.assert.fail)
const endPromise = once(child.stdout, 'data', (data) => {
t.assert.strictEqual(data.toString(), `[${formattedEpoch}] INFO (42): hello world\n`)
})
child.stdin.write(logLine)
await endPromise
t.after(() => child.kill())
})
}
for (const optionName of ['--useOnlyCustomProps', '-U']) {
test(`customize levels via ${optionName} false and customColors`, async (t) => {
t.plan(1)
const child = spawn(process.argv[0], [bin, '--customColors', 'err:blue,info:red', optionName, 'false'], { env })
child.on('error', t.assert.fail)
const endPromise = once(child.stdout, 'data', (data) => {
t.assert.strictEqual(data.toString(), `[${formattedEpoch}] INFO (42): hello world\n`)
})
child.stdin.write(logLine)
await endPromise
t.after(() => child.kill())
})
test(`customize levels via ${optionName} true and customColors`, async (t) => {
t.plan(1)
const child = spawn(process.argv[0], [bin, '--customColors', 'err:blue,info:red', optionName, 'true'], { env })
child.on('error', t.assert.fail)
const endPromise = once(child.stdout, 'data', (data) => {
t.assert.strictEqual(data.toString(), `[${formattedEpoch}] INFO (42): hello world\n`)
})
child.stdin.write(logLine)
await endPromise
t.after(() => child.kill())
})
test(`customize levels via ${optionName} true and customLevels`, async (t) => {
t.plan(1)
const child = spawn(process.argv[0], [bin, '--customLevels', 'err:99,custom:30', optionName, 'true'], { env })
child.on('error', t.assert.fail)
const endPromise = once(child.stdout, 'data', (data) => {
t.assert.strictEqual(data.toString(), `[${formattedEpoch}] CUSTOM (42): hello world\n`)
})
child.stdin.write(logLine)
await endPromise
t.after(() => child.kill())
})
test(`customize levels via ${optionName} true and no customLevels`, async (t) => {
t.plan(1)
const child = spawn(process.argv[0], [bin, optionName, 'true'], { env })
child.on('error', t.assert.fail)
const endPromise = once(child.stdout, 'data', (data) => {
t.assert.strictEqual(data.toString(), `[${formattedEpoch}] INFO (42): hello world\n`)
})
child.stdin.write(logLine)
await endPromise
t.after(() => child.kill())
})
test(`customize levels via ${optionName} false and customLevels`, async (t) => {
t.plan(1)
const child = spawn(process.argv[0], [bin, '--customLevels', 'err:99,custom:25', optionName, 'false'], { env })
child.on('error', t.assert.fail)
const endPromise = once(child.stdout, 'data', (data) => {
t.assert.strictEqual(data.toString(), `[${formattedEpoch}] INFO (42): hello world\n`)
})
child.stdin.write(logLine)
await endPromise
t.after(() => child.kill())
})
test(`customize levels via ${optionName} false and no customLevels`, async (t) => {
t.plan(1)
const child = spawn(process.argv[0], [bin, optionName, 'false'], { env })
child.on('error', t.assert.fail)
const endPromise = once(child.stdout, 'data', (data) => {
t.assert.strictEqual(data.toString(), `[${formattedEpoch}] INFO (42): hello world\n`)
})
child.stdin.write(logLine)
await endPromise
t.after(() => child.kill())
})
}
test('does ignore escaped keys', async (t) => {
t.plan(1)
const child = spawn(process.argv[0], [bin, '-i', 'log\\.domain\\.corp/foo'], { env })
child.on('error', t.assert.fail)
const endPromise = once(child.stdout, 'data', (data) => {
t.assert.strictEqual(data.toString(), `[${formattedEpoch}] INFO: hello world\n`)
})
const logLine = '{"level":30,"time":1522431328992,"msg":"hello world","log.domain.corp/foo":"bar"}\n'
child.stdin.write(logLine)
await endPromise
t.after(() => child.kill())
})
test('passes through stringified date as string', async (t) => {
t.plan(1)
const child = spawn(process.argv[0], [bin], { env })
child.on('error', t.assert.fail)
const date = JSON.stringify(new Date(epoch))
const endPromise = once(child.stdout, 'data', (data) => {
t.assert.strictEqual(data.toString(), date + '\n')
})
child.stdin.write(date)
child.stdin.write('\n')
await endPromise
t.after(() => child.kill())
})
test('end stdin does not end the destination', async (t) => {
t.plan(2)
const child = spawn(process.argv[0], [bin], { env })
child.on('error', t.assert.fail)
const endPromise1 = once(child.stdout, 'data', (data) => {
t.assert.strictEqual(data.toString(), 'aaa\n')
})
child.stdin.end('aaa\n')
const endPromise2 = once(child, 'exit', (code) => {
t.assert.strictEqual(code, 0)
})
await Promise.all([endPromise1, endPromise2])
t.after(() => child.kill())
})
for (const optionName of ['--timestampKey', '-a']) {
test(`uses specified timestamp key via ${optionName}`, async (t) => {
t.plan(1)
const child = spawn(process.argv[0], [bin, optionName, '@timestamp'], { env })
child.on('error', t.assert.fail)
const endPromise = once(child.stdout, 'data', (data) => {
t.assert.strictEqual(data.toString(), `[${formattedEpoch}] INFO: hello world\n`)
})
const logLine = '{"level":30,"@timestamp":1522431328992,"msg":"hello world"}\n'
child.stdin.write(logLine)
await endPromise
t.after(() => child.kill())
})
}
for (const optionName of ['--singleLine', '-S']) {
test(`singleLine=true via ${optionName}`, async (t) => {
t.plan(1)
const logLineWithExtra = JSON.stringify(Object.assign(JSON.parse(logLine), {
extra: {
foo: 'bar',
number: 42
}
})) + '\n'
const child = spawn(process.argv[0], [bin, optionName], { env })
child.on('error', t.assert.fail)
const endPromise = once(child.stdout, 'data', (data) => {
t.assert.strictEqual(data.toString(), `[${formattedEpoch}] INFO (42): hello world {"extra":{"foo":"bar","number":42}}\n`)
})
child.stdin.write(logLineWithExtra)
await endPromise
t.after(() => child.kill())
})
}
test('does ignore nested keys', async (t) => {
t.plan(1)
const logLineNested = JSON.stringify(Object.assign(JSON.parse(logLine), {
extra: {
foo: 'bar',
number: 42,
nested: {
foo2: 'bar2'
}
}
})) + '\n'
const child = spawn(process.argv[0], [bin, '-S', '-i', 'extra.foo,extra.nested,extra.nested.miss'], { env })
child.on('error', t.assert.fail)
const endPromise = once(child.stdout, 'data', (data) => {
t.assert.strictEqual(data.toString(), `[${formattedEpoch}] INFO (42 on foo): hello world {"extra":{"number":42}}\n`)
})
child.stdin.write(logLineNested)
await endPromise
t.after(() => child.kill())
})
test('change TZ', async (t) => {
t.plan(1)
const child = spawn(process.argv[0], [bin], { env: { ...env, TZ: 'Europe/Amsterdam' } })
child.on('error', t.assert.fail)
const endPromise = once(child.stdout, 'data', (data) => {
t.assert.strictEqual(data.toString(), '[19:35:28.992] INFO (42): hello world\n')
})
child.stdin.write(logLine)
await endPromise
t.after(() => child.kill())
})
})

View File

@@ -0,0 +1,21 @@
/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */
if (typeof module !== "undefined" && module.exports) {
module.exports = require("./typescript.js");
}
else {
throw new Error("tsserverlibrary requires CommonJS; use typescript.js instead");
}

View File

@@ -0,0 +1,601 @@
import { extractDefs, finalize, initializeContext, process, } from "./to-json-schema.js";
import { getEnumValues } from "./util.js";
const formatMap = {
guid: "uuid",
url: "uri",
datetime: "date-time",
json_string: "json-string",
regex: "", // do not set
};
// ==================== SIMPLE TYPE PROCESSORS ====================
export const stringProcessor = (schema, ctx, _json, _params) => {
const json = _json;
json.type = "string";
const { minimum, maximum, format, patterns, contentEncoding } = schema._zod
.bag;
if (typeof minimum === "number")
json.minLength = minimum;
if (typeof maximum === "number")
json.maxLength = maximum;
// custom pattern overrides format
if (format) {
json.format = formatMap[format] ?? format;
if (json.format === "")
delete json.format; // empty format is not valid
// JSON Schema format: "time" requires a full time with offset or Z
// z.iso.time() does not include timezone information, so format: "time" should never be used
if (format === "time") {
delete json.format;
}
}
if (contentEncoding)
json.contentEncoding = contentEncoding;
if (patterns && patterns.size > 0) {
const regexes = [...patterns];
if (regexes.length === 1)
json.pattern = regexes[0].source;
else if (regexes.length > 1) {
json.allOf = [
...regexes.map((regex) => ({
...(ctx.target === "draft-07" || ctx.target === "draft-04" || ctx.target === "openapi-3.0"
? { type: "string" }
: {}),
pattern: regex.source,
})),
];
}
}
};
export const numberProcessor = (schema, ctx, _json, _params) => {
const json = _json;
const { minimum, maximum, format, multipleOf, exclusiveMaximum, exclusiveMinimum } = schema._zod.bag;
if (typeof format === "string" && format.includes("int"))
json.type = "integer";
else
json.type = "number";
// when both minimum and exclusiveMinimum exist, pick the more restrictive one
const exMin = typeof exclusiveMinimum === "number" && exclusiveMinimum >= (minimum ?? Number.NEGATIVE_INFINITY);
const exMax = typeof exclusiveMaximum === "number" && exclusiveMaximum <= (maximum ?? Number.POSITIVE_INFINITY);
const legacy = ctx.target === "draft-04" || ctx.target === "openapi-3.0";
if (exMin) {
if (legacy) {
json.minimum = exclusiveMinimum;
json.exclusiveMinimum = true;
}
else {
json.exclusiveMinimum = exclusiveMinimum;
}
}
else if (typeof minimum === "number") {
json.minimum = minimum;
}
if (exMax) {
if (legacy) {
json.maximum = exclusiveMaximum;
json.exclusiveMaximum = true;
}
else {
json.exclusiveMaximum = exclusiveMaximum;
}
}
else if (typeof maximum === "number") {
json.maximum = maximum;
}
if (typeof multipleOf === "number")
json.multipleOf = multipleOf;
};
export const booleanProcessor = (_schema, _ctx, json, _params) => {
json.type = "boolean";
};
export const bigintProcessor = (_schema, ctx, _json, _params) => {
if (ctx.unrepresentable === "throw") {
throw new Error("BigInt cannot be represented in JSON Schema");
}
};
export const symbolProcessor = (_schema, ctx, _json, _params) => {
if (ctx.unrepresentable === "throw") {
throw new Error("Symbols cannot be represented in JSON Schema");
}
};
export const nullProcessor = (_schema, ctx, json, _params) => {
if (ctx.target === "openapi-3.0") {
json.type = "string";
json.nullable = true;
json.enum = [null];
}
else {
json.type = "null";
}
};
export const undefinedProcessor = (_schema, ctx, _json, _params) => {
if (ctx.unrepresentable === "throw") {
throw new Error("Undefined cannot be represented in JSON Schema");
}
};
export const voidProcessor = (_schema, ctx, _json, _params) => {
if (ctx.unrepresentable === "throw") {
throw new Error("Void cannot be represented in JSON Schema");
}
};
export const neverProcessor = (_schema, _ctx, json, _params) => {
json.not = {};
};
export const anyProcessor = (_schema, _ctx, _json, _params) => {
// empty schema accepts anything
};
export const unknownProcessor = (_schema, _ctx, _json, _params) => {
// empty schema accepts anything
};
export const dateProcessor = (_schema, ctx, _json, _params) => {
if (ctx.unrepresentable === "throw") {
throw new Error("Date cannot be represented in JSON Schema");
}
};
export const enumProcessor = (schema, _ctx, json, _params) => {
const def = schema._zod.def;
const values = getEnumValues(def.entries);
// Number enums can have both string and number values
if (values.every((v) => typeof v === "number"))
json.type = "number";
if (values.every((v) => typeof v === "string"))
json.type = "string";
json.enum = values;
};
export const literalProcessor = (schema, ctx, json, _params) => {
const def = schema._zod.def;
const vals = [];
for (const val of def.values) {
if (val === undefined) {
if (ctx.unrepresentable === "throw") {
throw new Error("Literal `undefined` cannot be represented in JSON Schema");
}
else {
// do not add to vals
}
}
else if (typeof val === "bigint") {
if (ctx.unrepresentable === "throw") {
throw new Error("BigInt literals cannot be represented in JSON Schema");
}
else {
vals.push(Number(val));
}
}
else {
vals.push(val);
}
}
if (vals.length === 0) {
// do nothing (an undefined literal was stripped)
}
else if (vals.length === 1) {
const val = vals[0];
json.type = val === null ? "null" : typeof val;
if (ctx.target === "draft-04" || ctx.target === "openapi-3.0") {
json.enum = [val];
}
else {
json.const = val;
}
}
else {
if (vals.every((v) => typeof v === "number"))
json.type = "number";
if (vals.every((v) => typeof v === "string"))
json.type = "string";
if (vals.every((v) => typeof v === "boolean"))
json.type = "boolean";
if (vals.every((v) => v === null))
json.type = "null";
json.enum = vals;
}
};
export const nanProcessor = (_schema, ctx, _json, _params) => {
if (ctx.unrepresentable === "throw") {
throw new Error("NaN cannot be represented in JSON Schema");
}
};
export const templateLiteralProcessor = (schema, _ctx, json, _params) => {
const _json = json;
const pattern = schema._zod.pattern;
if (!pattern)
throw new Error("Pattern not found in template literal");
_json.type = "string";
_json.pattern = pattern.source;
};
export const fileProcessor = (schema, _ctx, json, _params) => {
const _json = json;
const file = {
type: "string",
format: "binary",
contentEncoding: "binary",
};
const { minimum, maximum, mime } = schema._zod.bag;
if (minimum !== undefined)
file.minLength = minimum;
if (maximum !== undefined)
file.maxLength = maximum;
if (mime) {
if (mime.length === 1) {
file.contentMediaType = mime[0];
Object.assign(_json, file);
}
else {
Object.assign(_json, file); // shared props at root
_json.anyOf = mime.map((m) => ({ contentMediaType: m })); // only contentMediaType differs
}
}
else {
Object.assign(_json, file);
}
};
export const successProcessor = (_schema, _ctx, json, _params) => {
json.type = "boolean";
};
export const customProcessor = (_schema, ctx, _json, _params) => {
if (ctx.unrepresentable === "throw") {
throw new Error("Custom types cannot be represented in JSON Schema");
}
};
export const functionProcessor = (_schema, ctx, _json, _params) => {
if (ctx.unrepresentable === "throw") {
throw new Error("Function types cannot be represented in JSON Schema");
}
};
export const transformProcessor = (_schema, ctx, _json, _params) => {
if (ctx.unrepresentable === "throw") {
throw new Error("Transforms cannot be represented in JSON Schema");
}
};
export const mapProcessor = (_schema, ctx, _json, _params) => {
if (ctx.unrepresentable === "throw") {
throw new Error("Map cannot be represented in JSON Schema");
}
};
export const setProcessor = (_schema, ctx, _json, _params) => {
if (ctx.unrepresentable === "throw") {
throw new Error("Set cannot be represented in JSON Schema");
}
};
// ==================== COMPOSITE TYPE PROCESSORS ====================
export const arrayProcessor = (schema, ctx, _json, params) => {
const json = _json;
const def = schema._zod.def;
const { minimum, maximum } = schema._zod.bag;
if (typeof minimum === "number")
json.minItems = minimum;
if (typeof maximum === "number")
json.maxItems = maximum;
json.type = "array";
json.items = process(def.element, ctx, {
...params,
path: [...params.path, "items"],
});
};
export const objectProcessor = (schema, ctx, _json, params) => {
const json = _json;
const def = schema._zod.def;
json.type = "object";
json.properties = {};
const shape = def.shape;
for (const key in shape) {
json.properties[key] = process(shape[key], ctx, {
...params,
path: [...params.path, "properties", key],
});
}
// required keys
const allKeys = new Set(Object.keys(shape));
const requiredKeys = new Set([...allKeys].filter((key) => {
const v = def.shape[key]._zod;
if (ctx.io === "input") {
return v.optin === undefined;
}
else {
return v.optout === undefined;
}
}));
if (requiredKeys.size > 0) {
json.required = Array.from(requiredKeys);
}
// catchall
if (def.catchall?._zod.def.type === "never") {
// strict
json.additionalProperties = false;
}
else if (!def.catchall) {
// regular
if (ctx.io === "output")
json.additionalProperties = false;
}
else if (def.catchall) {
json.additionalProperties = process(def.catchall, ctx, {
...params,
path: [...params.path, "additionalProperties"],
});
}
};
export const unionProcessor = (schema, ctx, json, params) => {
const def = schema._zod.def;
// Exclusive unions (inclusive === false) use oneOf (exactly one match) instead of anyOf (one or more matches)
// This includes both z.xor() and discriminated unions
const isExclusive = def.inclusive === false;
const options = def.options.map((x, i) => process(x, ctx, {
...params,
path: [...params.path, isExclusive ? "oneOf" : "anyOf", i],
}));
if (isExclusive) {
json.oneOf = options;
}
else {
json.anyOf = options;
}
};
export const intersectionProcessor = (schema, ctx, json, params) => {
const def = schema._zod.def;
const a = process(def.left, ctx, {
...params,
path: [...params.path, "allOf", 0],
});
const b = process(def.right, ctx, {
...params,
path: [...params.path, "allOf", 1],
});
const isSimpleIntersection = (val) => "allOf" in val && Object.keys(val).length === 1;
const allOf = [
...(isSimpleIntersection(a) ? a.allOf : [a]),
...(isSimpleIntersection(b) ? b.allOf : [b]),
];
json.allOf = allOf;
};
export const tupleProcessor = (schema, ctx, _json, params) => {
const json = _json;
const def = schema._zod.def;
json.type = "array";
const prefixPath = ctx.target === "draft-2020-12" ? "prefixItems" : "items";
const restPath = ctx.target === "draft-2020-12" ? "items" : ctx.target === "openapi-3.0" ? "items" : "additionalItems";
const prefixItems = def.items.map((x, i) => process(x, ctx, {
...params,
path: [...params.path, prefixPath, i],
}));
const rest = def.rest
? process(def.rest, ctx, {
...params,
path: [...params.path, restPath, ...(ctx.target === "openapi-3.0" ? [def.items.length] : [])],
})
: null;
if (ctx.target === "draft-2020-12") {
json.prefixItems = prefixItems;
if (rest) {
json.items = rest;
}
}
else if (ctx.target === "openapi-3.0") {
json.items = {
anyOf: prefixItems,
};
if (rest) {
json.items.anyOf.push(rest);
}
json.minItems = prefixItems.length;
if (!rest) {
json.maxItems = prefixItems.length;
}
}
else {
json.items = prefixItems;
if (rest) {
json.additionalItems = rest;
}
}
// length
const { minimum, maximum } = schema._zod.bag;
if (typeof minimum === "number")
json.minItems = minimum;
if (typeof maximum === "number")
json.maxItems = maximum;
};
export const recordProcessor = (schema, ctx, _json, params) => {
const json = _json;
const def = schema._zod.def;
json.type = "object";
// For looseRecord with regex patterns, use patternProperties
// This correctly represents "only validate keys matching the pattern" semantics
// and composes well with allOf (intersections)
const keyType = def.keyType;
const keyBag = keyType._zod.bag;
const patterns = keyBag?.patterns;
if (def.mode === "loose" && patterns && patterns.size > 0) {
// Use patternProperties for looseRecord with regex patterns
const valueSchema = process(def.valueType, ctx, {
...params,
path: [...params.path, "patternProperties", "*"],
});
json.patternProperties = {};
for (const pattern of patterns) {
json.patternProperties[pattern.source] = valueSchema;
}
}
else {
// Default behavior: use propertyNames + additionalProperties
if (ctx.target === "draft-07" || ctx.target === "draft-2020-12") {
json.propertyNames = process(def.keyType, ctx, {
...params,
path: [...params.path, "propertyNames"],
});
}
json.additionalProperties = process(def.valueType, ctx, {
...params,
path: [...params.path, "additionalProperties"],
});
}
// Add required for keys with discrete values (enum, literal, etc.)
const keyValues = keyType._zod.values;
if (keyValues) {
const validKeyValues = [...keyValues].filter((v) => typeof v === "string" || typeof v === "number");
if (validKeyValues.length > 0) {
json.required = validKeyValues;
}
}
};
export const nullableProcessor = (schema, ctx, json, params) => {
const def = schema._zod.def;
const inner = process(def.innerType, ctx, params);
const seen = ctx.seen.get(schema);
if (ctx.target === "openapi-3.0") {
seen.ref = def.innerType;
json.nullable = true;
}
else {
json.anyOf = [inner, { type: "null" }];
}
};
export const nonoptionalProcessor = (schema, ctx, _json, params) => {
const def = schema._zod.def;
process(def.innerType, ctx, params);
const seen = ctx.seen.get(schema);
seen.ref = def.innerType;
};
export const defaultProcessor = (schema, ctx, json, params) => {
const def = schema._zod.def;
process(def.innerType, ctx, params);
const seen = ctx.seen.get(schema);
seen.ref = def.innerType;
json.default = JSON.parse(JSON.stringify(def.defaultValue));
};
export const prefaultProcessor = (schema, ctx, json, params) => {
const def = schema._zod.def;
process(def.innerType, ctx, params);
const seen = ctx.seen.get(schema);
seen.ref = def.innerType;
if (ctx.io === "input")
json._prefault = JSON.parse(JSON.stringify(def.defaultValue));
};
export const catchProcessor = (schema, ctx, json, params) => {
const def = schema._zod.def;
process(def.innerType, ctx, params);
const seen = ctx.seen.get(schema);
seen.ref = def.innerType;
let catchValue;
try {
catchValue = def.catchValue(undefined);
}
catch {
throw new Error("Dynamic catch values are not supported in JSON Schema");
}
json.default = catchValue;
};
export const pipeProcessor = (schema, ctx, _json, params) => {
const def = schema._zod.def;
const inIsTransform = def.in._zod.traits.has("$ZodTransform");
const innerType = ctx.io === "input" ? (inIsTransform ? def.out : def.in) : def.out;
process(innerType, ctx, params);
const seen = ctx.seen.get(schema);
seen.ref = innerType;
};
export const readonlyProcessor = (schema, ctx, json, params) => {
const def = schema._zod.def;
process(def.innerType, ctx, params);
const seen = ctx.seen.get(schema);
seen.ref = def.innerType;
json.readOnly = true;
};
export const promiseProcessor = (schema, ctx, _json, params) => {
const def = schema._zod.def;
process(def.innerType, ctx, params);
const seen = ctx.seen.get(schema);
seen.ref = def.innerType;
};
export const optionalProcessor = (schema, ctx, _json, params) => {
const def = schema._zod.def;
process(def.innerType, ctx, params);
const seen = ctx.seen.get(schema);
seen.ref = def.innerType;
};
export const lazyProcessor = (schema, ctx, _json, params) => {
const innerType = schema._zod.innerType;
process(innerType, ctx, params);
const seen = ctx.seen.get(schema);
seen.ref = innerType;
};
// ==================== ALL PROCESSORS ====================
export const allProcessors = {
string: stringProcessor,
number: numberProcessor,
boolean: booleanProcessor,
bigint: bigintProcessor,
symbol: symbolProcessor,
null: nullProcessor,
undefined: undefinedProcessor,
void: voidProcessor,
never: neverProcessor,
any: anyProcessor,
unknown: unknownProcessor,
date: dateProcessor,
enum: enumProcessor,
literal: literalProcessor,
nan: nanProcessor,
template_literal: templateLiteralProcessor,
file: fileProcessor,
success: successProcessor,
custom: customProcessor,
function: functionProcessor,
transform: transformProcessor,
map: mapProcessor,
set: setProcessor,
array: arrayProcessor,
object: objectProcessor,
union: unionProcessor,
intersection: intersectionProcessor,
tuple: tupleProcessor,
record: recordProcessor,
nullable: nullableProcessor,
nonoptional: nonoptionalProcessor,
default: defaultProcessor,
prefault: prefaultProcessor,
catch: catchProcessor,
pipe: pipeProcessor,
readonly: readonlyProcessor,
promise: promiseProcessor,
optional: optionalProcessor,
lazy: lazyProcessor,
};
export function toJSONSchema(input, params) {
if ("_idmap" in input) {
// Registry case
const registry = input;
const ctx = initializeContext({ ...params, processors: allProcessors });
const defs = {};
// First pass: process all schemas to build the seen map
for (const entry of registry._idmap.entries()) {
const [_, schema] = entry;
process(schema, ctx);
}
const schemas = {};
const external = {
registry,
uri: params?.uri,
defs,
};
// Update the context with external configuration
ctx.external = external;
// Second pass: emit each schema
for (const entry of registry._idmap.entries()) {
const [key, schema] = entry;
extractDefs(ctx, schema);
schemas[key] = finalize(ctx, schema);
}
if (Object.keys(defs).length > 0) {
const defsSegment = ctx.target === "draft-2020-12" ? "$defs" : "definitions";
schemas.__shared = {
[defsSegment]: defs,
};
}
return { schemas };
}
// Single schema case
const ctx = initializeContext({ ...params, processors: allProcessors });
process(input, ctx);
extractDefs(ctx, input);
return finalize(ctx, input);
}

View File

@@ -0,0 +1,33 @@
'use strict'
var bufferFrom = Buffer.from || Buffer
module.exports = function parseBytea (input) {
if (/^\\x/.test(input)) {
// new 'hex' style response (pg >9.0)
return bufferFrom(input.substr(2), 'hex')
}
var output = ''
var i = 0
while (i < input.length) {
if (input[i] !== '\\') {
output += input[i]
++i
} else {
if (/[0-7]{3}/.test(input.substr(i + 1, 3))) {
output += String.fromCharCode(parseInt(input.substr(i + 1, 3), 8))
i += 4
} else {
var backslashes = 1
while (i + backslashes < input.length && input[i + backslashes] === '\\') {
backslashes++
}
for (var k = 0; k < Math.floor(backslashes / 2); ++k) {
output += '\\'
}
i += Math.floor(backslashes / 2) * 2
}
}
}
return bufferFrom(output, 'binary')
}

View File

@@ -0,0 +1,39 @@
{
"name": "pg-cloudflare",
"version": "1.4.0",
"description": "A socket implementation that can run on Cloudflare Workers using native TCP connections.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"license": "MIT",
"devDependencies": {
"ts-node": "^8.5.4",
"typescript": "^6.0.3"
},
"exports": {
".": {
"workerd": {
"import": "./esm/index.mjs",
"require": "./dist/index.js"
},
"default": "./dist/empty.js"
},
"./package.json": "./package.json"
},
"scripts": {
"build": "tsc",
"build:watch": "tsc --watch",
"prepublish": "yarn build",
"test": "echo e2e test in pg package"
},
"repository": {
"type": "git",
"url": "git://github.com/brianc/node-postgres.git",
"directory": "packages/pg-cloudflare"
},
"files": [
"/dist/*{js,ts,map}",
"/src",
"/esm"
],
"gitHead": "544b1ce8152bc280e398dc1e8a66920abe6a640e"
}

View File

@@ -0,0 +1,61 @@
/**
* @typedef { import('estree').Node} Node
* @typedef {{
* skip: () => void;
* remove: () => void;
* replace: (node: Node) => void;
* }} WalkerContext
*/
export class WalkerBase {
constructor() {
/** @type {boolean} */
this.should_skip = false;
/** @type {boolean} */
this.should_remove = false;
/** @type {Node | null} */
this.replacement = null;
/** @type {WalkerContext} */
this.context = {
skip: () => (this.should_skip = true),
remove: () => (this.should_remove = true),
replace: (node) => (this.replacement = node)
};
}
/**
* @template {Node} Parent
* @param {Parent | null | undefined} parent
* @param {keyof Parent | null | undefined} prop
* @param {number | null | undefined} index
* @param {Node} node
*/
replace(parent, prop, index, node) {
if (parent && prop) {
if (index != null) {
/** @type {Array<Node>} */ (parent[prop])[index] = node;
} else {
/** @type {Node} */ (parent[prop]) = node;
}
}
}
/**
* @template {Node} Parent
* @param {Parent | null | undefined} parent
* @param {keyof Parent | null | undefined} prop
* @param {number | null | undefined} index
*/
remove(parent, prop, index) {
if (parent && prop) {
if (index !== null && index !== undefined) {
/** @type {Array<Node>} */ (parent[prop]).splice(index, 1);
} else {
delete parent[prop];
}
}
}
}

View File

@@ -0,0 +1,410 @@
import { spawn, spawnSync } from "node:child_process";
import { cwd } from "node:process";
import { basename, delimiter, dirname, normalize, resolve } from "node:path";
import { pipeline } from "node:stream/promises";
import { PassThrough } from "node:stream";
import readline from "node:readline";
import { closeSync, openSync, readSync, statSync } from "node:fs";
//#region src/env.ts
const isPathLikePattern = /^path$/i;
const defaultEnvPathInfo = {
key: "PATH",
value: ""
};
function getPathFromEnv(env) {
for (const key in env) {
if (!Object.prototype.hasOwnProperty.call(env, key) || !isPathLikePattern.test(key)) continue;
const value = env[key];
if (!value) return defaultEnvPathInfo;
return {
key,
value
};
}
return defaultEnvPathInfo;
}
function addNodeBinToPath(cwd, path) {
const parts = path.value.split(delimiter);
const nodeBinPaths = [];
let currentPath = cwd;
let lastPath;
do {
nodeBinPaths.push(resolve(currentPath, "node_modules", ".bin"));
lastPath = currentPath;
currentPath = dirname(currentPath);
} while (currentPath !== lastPath);
nodeBinPaths.push(dirname(process.execPath));
const newPath = nodeBinPaths.concat(parts).join(delimiter);
return {
key: path.key,
value: newPath
};
}
function computeEnv(cwd, env, nodePath = true) {
const envWithDefault = {
...process.env,
...env
};
if (!nodePath) return envWithDefault;
const envPathInfo = addNodeBinToPath(cwd, getPathFromEnv(envWithDefault));
envWithDefault[envPathInfo.key] = envPathInfo.value;
return envWithDefault;
}
//#endregion
//#region src/stream.ts
const combineStreams = (streams) => {
let streamCount = streams.length;
const combined = new PassThrough();
const maybeEmitEnd = () => {
if (--streamCount === 0) combined.end();
};
for (const stream of streams) pipeline(stream, combined, { end: false }).then(maybeEmitEnd).catch(maybeEmitEnd);
return combined;
};
//#endregion
//#region src/normalize.ts
const metaCharsRegExp = /([()\][%!^"`<>&|;, *?])/g;
const shebangRegExp = /^#!\s*(.+)/;
const isWindowsExecutableRegExp = /\.(?:com|exe)$/i;
const isNodeModulesCmdRegExp = /node_modules[\\/]\.bin[\\/][^\\/]+\.cmd$/i;
const isWindows = process.platform === "win32";
const defaultPathExt = [
".EXE",
".CMD",
".BAT",
".COM"
];
const noPathExt = [""];
/**
* Normalizes the command and arguments to work cross-platform.
* On Windows, this basically handles things like shebangs, calling
* `node_modules/.bin` commands, and escaping meta characters.
* On other platforms, it just returns the command and arguments as-is.
*/
function normalizeSpawnCommand(command, args = [], options = {}) {
if (options.shell === true || !isWindows) return {
command,
args,
options
};
let file = resolveCommand(command, options);
let shebang = null;
if (file !== null) {
const size = 150;
const buffer = Buffer.alloc(size);
let fd = null;
try {
fd = openSync(file, "r");
readSync(fd, buffer, 0, size, 0);
} catch {} finally {
if (fd !== null) closeSync(fd);
}
const match = buffer.toString().match(shebangRegExp);
if (match !== null) {
const line = match[1].trim();
const separatorIndex = line.indexOf(" ");
const path = separatorIndex !== -1 ? line.slice(0, separatorIndex) : line;
const argument = separatorIndex !== -1 ? line.slice(separatorIndex + 1) : "";
const binary = basename(path);
shebang = binary === "env" ? argument || null : binary;
}
}
if (shebang !== null && file !== null) {
args = [file, ...args];
command = shebang;
file = resolveCommand(command, options);
}
if (file === null || !isWindowsExecutableRegExp.test(file)) {
const needsDoubleEscapeMetaChars = file !== null && isNodeModulesCmdRegExp.test(file);
command = normalize(command);
command = command.replace(metaCharsRegExp, "^$1");
args = args.map((arg) => {
arg = arg.replace(/(?=(\\+?)?)\1"/g, "$1$1\\\"");
arg = arg.replace(/(?=(\\+?)?)\1$/, "$1$1");
arg = `"${arg}"`;
arg = arg.replace(metaCharsRegExp, "^$1");
if (needsDoubleEscapeMetaChars) arg = arg.replace(metaCharsRegExp, "^$1");
return arg;
});
args = [
"/d",
"/s",
"/c",
`"${[command, ...args].join(" ")}"`
];
command = options.env?.comspec ?? "cmd.exe";
options = {
...options,
windowsVerbatimArguments: true
};
}
return {
command,
args,
options
};
}
/**
* Resolves the command to an absolute path if possible.
* Handles things like traversing PATH and adding extensions from PATHEXT
*/
function resolveCommand(command, options) {
const cwd$3 = (options.cwd ?? cwd()).toString();
const env = options.env ?? process.env;
const PATH = getPathFromEnv(env).value;
const pathEnv = command.includes("/") || command.includes("\\") ? [""] : [cwd$3, ...PATH.split(delimiter)];
let pathExt = env.PATHEXT ? env.PATHEXT.split(delimiter) : defaultPathExt;
if (command.includes(".") && pathExt[0] !== "") pathExt = ["", ...pathExt];
for (const extensions of [pathExt, noPathExt]) for (const path of pathEnv) {
const dest = resolve(cwd$3, path.startsWith("\"") && path.endsWith("\"") && path.length > 1 ? path.slice(1, -1) : path, command);
for (const ext of extensions) {
const destWithExt = dest + ext;
try {
if (statSync(destWithExt).isFile()) return destWithExt;
} catch {}
}
}
return null;
}
//#endregion
//#region src/non-zero-exit-error.ts
var NonZeroExitError = class extends Error {
result;
output;
exitCode;
get signalCode() {
return this.result.signalCode;
}
constructor(result, output, command, args) {
let target = "The process";
if (command) target = `The command \`${args?.length ? `${command} ${args.map((a) => /[ "'`()]/.test(a) ? JSON.stringify(a) : a).join(" ")}` : command}\``;
const exitCode = result.exitCode ?? 1;
super(result.signalCode !== null ? `${target} was killed by the signal ${result.signalCode}` : `${target} exited with a non-zero status (${exitCode})`);
this.result = result;
this.output = output;
this.exitCode = exitCode;
Object.defineProperty(this, "result", {
enumerable: false,
writable: false,
configurable: false
});
}
};
//#endregion
//#region src/main.ts
const LINE_SEPARATOR_REGEX = /\r?\n/;
const defaultOptions = {
timeout: void 0,
persist: false
};
const defaultSyncOptions = { timeout: void 0 };
const defaultNodeOptions = { windowsHide: true };
function combineSignals(signals) {
const controller = new AbortController();
for (const signal of signals) {
if (signal.aborted) {
controller.abort();
return signal;
}
const onAbort = () => {
controller.abort(signal.reason);
};
signal.addEventListener("abort", onAbort, { signal: controller.signal });
}
return controller.signal;
}
async function readStream(stream) {
let output = "";
try {
for await (const chunk of stream) output += chunk.toString();
} catch {}
return output;
}
var ExecProcess = class {
_process;
_aborted = false;
_options;
_command;
_args;
_resolveClose;
_processClosed;
_thrownError;
get process() {
return this._process;
}
get pid() {
return this._process?.pid;
}
get exitCode() {
if (this._process && this._process.exitCode !== null) return this._process.exitCode;
}
get signalCode() {
return this._process?.signalCode ?? null;
}
constructor(command, args, options) {
this._options = {
...defaultOptions,
...options
};
this._command = command;
this._args = args ?? [];
this._processClosed = new Promise((resolve) => {
this._resolveClose = resolve;
});
}
kill(signal) {
return this._process?.kill(signal) === true;
}
get aborted() {
return this._aborted;
}
get killed() {
return this._process?.killed === true;
}
pipe(command, args, options) {
return exec(command, args, {
...options,
stdin: this
});
}
async *[Symbol.asyncIterator]() {
const proc = this._process;
if (!proc) return;
const streams = [];
if (this._streamErr) streams.push(this._streamErr);
if (this._streamOut) streams.push(this._streamOut);
const streamCombined = combineStreams(streams);
const rl = readline.createInterface({ input: streamCombined });
for await (const chunk of rl) yield chunk.toString();
await this._processClosed;
proc.removeAllListeners();
if (this._thrownError) throw this._thrownError;
if (this._options?.throwOnError && (this.exitCode !== 0 && this.exitCode !== void 0 || this.signalCode !== null)) throw new NonZeroExitError(this, void 0, this._command, this._args);
}
async _waitForOutput() {
const proc = this._process;
if (!proc) throw new Error("No process was started");
const [stdout, stderr] = await Promise.all([this._streamOut ? readStream(this._streamOut) : "", this._streamErr ? readStream(this._streamErr) : ""]);
await this._processClosed;
const { stdin } = this._options;
if (stdin && typeof stdin !== "string") await stdin;
proc.removeAllListeners();
if (this._thrownError) throw this._thrownError;
const result = {
stderr,
stdout,
exitCode: this.exitCode
};
if (this._options.throwOnError && (this.exitCode !== 0 && this.exitCode !== void 0 || this.signalCode !== null)) throw new NonZeroExitError(this, result, this._command, this._args);
return result;
}
then(onfulfilled, onrejected) {
return this._waitForOutput().then(onfulfilled, onrejected);
}
_streamOut;
_streamErr;
spawn() {
const cwd$1 = cwd();
const options = this._options;
const nodeOptions = {
...defaultNodeOptions,
...options.nodeOptions
};
const signals = [];
this._resetState();
if (options.timeout !== void 0) signals.push(AbortSignal.timeout(options.timeout));
if (options.signal !== void 0) signals.push(options.signal);
if (options.persist === true) nodeOptions.detached = true;
if (signals.length > 0) nodeOptions.signal = combineSignals(signals);
nodeOptions.env = computeEnv(cwd$1, nodeOptions.env, options.nodePath);
const crossResult = normalizeSpawnCommand(this._command, this._args, nodeOptions);
const handle = spawn(crossResult.command, crossResult.args, crossResult.options);
if (handle.stderr) this._streamErr = handle.stderr;
if (handle.stdout) this._streamOut = handle.stdout;
this._process = handle;
handle.once("error", this._onError);
handle.once("close", this._onClose);
if (handle.stdin) {
const { stdin } = options;
if (typeof stdin === "string") handle.stdin.end(stdin);
else stdin?.process?.stdout?.pipe(handle.stdin);
}
}
_resetState() {
this._aborted = false;
this._processClosed = new Promise((resolve) => {
this._resolveClose = resolve;
});
this._thrownError = void 0;
}
_onError = (err) => {
if (err.name === "AbortError" && (!(err.cause instanceof Error) || err.cause.name !== "TimeoutError")) {
this._aborted = true;
return;
}
this._thrownError = err;
};
_onClose = () => {
if (this._resolveClose) this._resolveClose();
};
};
function xSync(command, args, options) {
const opts = {
...defaultSyncOptions,
...options
};
const cwd$2 = cwd();
const nodeOptions = {
windowsHide: true,
...opts.nodeOptions
};
if (opts.timeout !== void 0) nodeOptions.timeout = opts.timeout;
nodeOptions.env = computeEnv(cwd$2, nodeOptions.env, opts.nodePath);
const crossResult = normalizeSpawnCommand(command, args ?? [], nodeOptions);
const spawnResult = spawnSync(crossResult.command, crossResult.args, crossResult.options);
if (spawnResult.error) throw spawnResult.error;
const stdout = spawnResult.stdout?.toString() ?? "";
const stderr = spawnResult.stderr?.toString() ?? "";
const exitCode = spawnResult.status ?? void 0;
const signalCode = spawnResult.signal ?? null;
const killed = signalCode !== null;
const result = {
stdout,
stderr,
get exitCode() {
return exitCode;
},
get signalCode() {
return signalCode;
},
get pid() {
return spawnResult.pid;
},
get killed() {
return killed;
},
*[Symbol.iterator]() {
for (const text of [stdout, stderr]) {
if (!text) continue;
const lines = text.split(LINE_SEPARATOR_REGEX);
if (lines[lines.length - 1] === "") lines.pop();
yield* lines;
}
}
};
if (opts.throwOnError && (exitCode !== 0 && exitCode !== void 0 || signalCode !== null)) throw new NonZeroExitError(result, {
stdout: result.stdout,
stderr: result.stderr,
exitCode: result.exitCode
}, command, args);
return result;
}
const x = (command, args, userOptions) => {
const proc = new ExecProcess(command, args, userOptions);
proc.spawn();
return proc;
};
const exec = x;
const execSync = xSync;
//#endregion
export { ExecProcess, NonZeroExitError, exec, execSync, normalizeSpawnCommand, x, xSync };

View File

@@ -0,0 +1,157 @@
/**
* Montgomery curve methods. It's not really whole montgomery curve,
* just bunch of very specific methods for X25519 / X448 from
* [RFC 7748](https://www.rfc-editor.org/rfc/rfc7748)
* @module
*/
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { _validateObject, abytes, aInRange, bytesToNumberLE, ensureBytes, numberToBytesLE, randomBytes, } from "../utils.js";
import { mod } from "./modular.js";
const _0n = BigInt(0);
const _1n = BigInt(1);
const _2n = BigInt(2);
function validateOpts(curve) {
_validateObject(curve, {
adjustScalarBytes: 'function',
powPminus2: 'function',
});
return Object.freeze({ ...curve });
}
export function montgomery(curveDef) {
const CURVE = validateOpts(curveDef);
const { P, type, adjustScalarBytes, powPminus2, randomBytes: rand } = CURVE;
const is25519 = type === 'x25519';
if (!is25519 && type !== 'x448')
throw new Error('invalid type');
const randomBytes_ = rand || randomBytes;
const montgomeryBits = is25519 ? 255 : 448;
const fieldLen = is25519 ? 32 : 56;
const Gu = is25519 ? BigInt(9) : BigInt(5);
// RFC 7748 #5:
// The constant a24 is (486662 - 2) / 4 = 121665 for curve25519/X25519 and
// (156326 - 2) / 4 = 39081 for curve448/X448
// const a = is25519 ? 156326n : 486662n;
const a24 = is25519 ? BigInt(121665) : BigInt(39081);
// RFC: x25519 "the resulting integer is of the form 2^254 plus
// eight times a value between 0 and 2^251 - 1 (inclusive)"
// x448: "2^447 plus four times a value between 0 and 2^445 - 1 (inclusive)"
const minScalar = is25519 ? _2n ** BigInt(254) : _2n ** BigInt(447);
const maxAdded = is25519
? BigInt(8) * _2n ** BigInt(251) - _1n
: BigInt(4) * _2n ** BigInt(445) - _1n;
const maxScalar = minScalar + maxAdded + _1n; // (inclusive)
const modP = (n) => mod(n, P);
const GuBytes = encodeU(Gu);
function encodeU(u) {
return numberToBytesLE(modP(u), fieldLen);
}
function decodeU(u) {
const _u = ensureBytes('u coordinate', u, fieldLen);
// RFC: When receiving such an array, implementations of X25519
// (but not X448) MUST mask the most significant bit in the final byte.
if (is25519)
_u[31] &= 127; // 0b0111_1111
// RFC: Implementations MUST accept non-canonical values and process them as
// if they had been reduced modulo the field prime. The non-canonical
// values are 2^255 - 19 through 2^255 - 1 for X25519 and 2^448 - 2^224
// - 1 through 2^448 - 1 for X448.
return modP(bytesToNumberLE(_u));
}
function decodeScalar(scalar) {
return bytesToNumberLE(adjustScalarBytes(ensureBytes('scalar', scalar, fieldLen)));
}
function scalarMult(scalar, u) {
const pu = montgomeryLadder(decodeU(u), decodeScalar(scalar));
// Some public keys are useless, of low-order. Curve author doesn't think
// it needs to be validated, but we do it nonetheless.
// https://cr.yp.to/ecdh.html#validate
if (pu === _0n)
throw new Error('invalid private or public key received');
return encodeU(pu);
}
// Computes public key from private. By doing scalar multiplication of base point.
function scalarMultBase(scalar) {
return scalarMult(scalar, GuBytes);
}
// cswap from RFC7748 "example code"
function cswap(swap, x_2, x_3) {
// dummy = mask(swap) AND (x_2 XOR x_3)
// Where mask(swap) is the all-1 or all-0 word of the same length as x_2
// and x_3, computed, e.g., as mask(swap) = 0 - swap.
const dummy = modP(swap * (x_2 - x_3));
x_2 = modP(x_2 - dummy); // x_2 = x_2 XOR dummy
x_3 = modP(x_3 + dummy); // x_3 = x_3 XOR dummy
return { x_2, x_3 };
}
/**
* Montgomery x-only multiplication ladder.
* @param pointU u coordinate (x) on Montgomery Curve 25519
* @param scalar by which the point would be multiplied
* @returns new Point on Montgomery curve
*/
function montgomeryLadder(u, scalar) {
aInRange('u', u, _0n, P);
aInRange('scalar', scalar, minScalar, maxScalar);
const k = scalar;
const x_1 = u;
let x_2 = _1n;
let z_2 = _0n;
let x_3 = u;
let z_3 = _1n;
let swap = _0n;
for (let t = BigInt(montgomeryBits - 1); t >= _0n; t--) {
const k_t = (k >> t) & _1n;
swap ^= k_t;
({ x_2, x_3 } = cswap(swap, x_2, x_3));
({ x_2: z_2, x_3: z_3 } = cswap(swap, z_2, z_3));
swap = k_t;
const A = x_2 + z_2;
const AA = modP(A * A);
const B = x_2 - z_2;
const BB = modP(B * B);
const E = AA - BB;
const C = x_3 + z_3;
const D = x_3 - z_3;
const DA = modP(D * A);
const CB = modP(C * B);
const dacb = DA + CB;
const da_cb = DA - CB;
x_3 = modP(dacb * dacb);
z_3 = modP(x_1 * modP(da_cb * da_cb));
x_2 = modP(AA * BB);
z_2 = modP(E * (AA + modP(a24 * E)));
}
({ x_2, x_3 } = cswap(swap, x_2, x_3));
({ x_2: z_2, x_3: z_3 } = cswap(swap, z_2, z_3));
const z2 = powPminus2(z_2); // `Fp.pow(x, P - _2n)` is much slower equivalent
return modP(x_2 * z2); // Return x_2 * (z_2^(p - 2))
}
const lengths = {
secretKey: fieldLen,
publicKey: fieldLen,
seed: fieldLen,
};
const randomSecretKey = (seed = randomBytes_(fieldLen)) => {
abytes(seed, lengths.seed);
return seed;
};
function keygen(seed) {
const secretKey = randomSecretKey(seed);
return { secretKey, publicKey: scalarMultBase(secretKey) };
}
const utils = {
randomSecretKey,
randomPrivateKey: randomSecretKey,
};
return {
keygen,
getSharedSecret: (secretKey, publicKey) => scalarMult(secretKey, publicKey),
getPublicKey: (secretKey) => scalarMultBase(secretKey),
scalarMult,
scalarMultBase,
utils,
GuBytes: GuBytes.slice(),
lengths,
};
}
//# sourceMappingURL=montgomery.js.map

View File

@@ -0,0 +1,40 @@
#define NAPI_VERSION 1
#include <assert.h>
#include <node_api.h>
#include "../deps/is_utf8/include/is_utf8.h"
napi_value IsValidUTF8(napi_env env, napi_callback_info info) {
napi_status status;
size_t argc = 1;
napi_value argv[1];
status = napi_get_cb_info(env, info, &argc, argv, NULL, NULL);
assert(status == napi_ok);
char *buf;
size_t len;
status = napi_get_buffer_info(env, argv[0], (void **)&buf, &len);
assert(status == napi_ok);
bool is_valid = is_utf8(buf, len);
napi_value result;
status = napi_get_boolean(env, is_valid, &result);
assert(status == napi_ok);
return result;
}
napi_value Init(napi_env env, napi_value exports) {
napi_status status;
napi_value isValidUTF8;
status = napi_create_function(env, NULL, 0, IsValidUTF8, NULL, &isValidUTF8);
assert(status == napi_ok);
return isValidUTF8;
}
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)

View File

@@ -0,0 +1,104 @@
export interface StartOfSourceMap {
file?: string;
sourceRoot?: string;
}
export interface RawSourceMap extends StartOfSourceMap {
version: string;
sources: string[];
names: string[];
sourcesContent?: string[];
mappings: string;
}
export interface Position {
line: number;
column: number;
}
export interface LineRange extends Position {
lastColumn: number;
}
export interface FindPosition extends Position {
// SourceMapConsumer.GREATEST_LOWER_BOUND or SourceMapConsumer.LEAST_UPPER_BOUND
bias?: number;
}
export interface SourceFindPosition extends FindPosition {
source: string;
}
export interface MappedPosition extends Position {
source: string;
name?: string;
}
export interface MappingItem {
source: string | null;
generatedLine: number;
generatedColumn: number;
originalLine: number | null;
originalColumn: number | null;
name: string | null;
}
export class SourceMapConsumer {
static GENERATED_ORDER: number;
static ORIGINAL_ORDER: number;
static GREATEST_LOWER_BOUND: number;
static LEAST_UPPER_BOUND: number;
constructor(rawSourceMap: RawSourceMap);
readonly file: string | undefined | null;
readonly sourceRoot: string | undefined | null;
readonly sourcesContent: readonly string[] | null | undefined;
readonly sources: readonly string[]
computeColumnSpans(): void;
originalPositionFor(generatedPosition: FindPosition): MappedPosition;
generatedPositionFor(originalPosition: SourceFindPosition): LineRange;
allGeneratedPositionsFor(originalPosition: MappedPosition): Position[];
hasContentsOfAllSources(): boolean;
sourceContentFor(source: string, returnNullOnMissing?: boolean): string | null;
eachMapping(callback: (mapping: MappingItem) => void, context?: any, order?: number): void;
}
export interface Mapping {
generated: Position;
original?: Position | null;
source?: string | null;
name?: string | null;
}
export class SourceMapGenerator {
constructor(startOfSourceMap?: StartOfSourceMap);
static fromSourceMap(sourceMapConsumer: SourceMapConsumer, startOfSourceMap?: StartOfSourceMap): SourceMapGenerator;
addMapping(mapping: Mapping): void;
setSourceContent(sourceFile: string, sourceContent: string | null | undefined): void;
applySourceMap(sourceMapConsumer: SourceMapConsumer, sourceFile?: string, sourceMapPath?: string): void;
toString(): string;
toJSON(): RawSourceMap;
}
export interface CodeWithSourceMap {
code: string;
map: SourceMapGenerator;
}
export class SourceNode {
constructor();
constructor(line: number, column: number, source: string);
constructor(line: number, column: number, source: string, chunk?: string, name?: string);
static fromStringWithSourceMap(code: string, sourceMapConsumer: SourceMapConsumer, relativePath?: string): SourceNode;
add(chunk: string): void;
prepend(chunk: string): void;
setSourceContent(sourceFile: string, sourceContent: string): void;
walk(fn: (chunk: string, mapping: MappedPosition) => void): void;
walkSourceContents(fn: (file: string, content: string) => void): void;
join(sep: string): SourceNode;
replaceRight(pattern: string, replacement: string): SourceNode;
toString(): string;
toStringWithSourceMap(startOfSourceMap?: StartOfSourceMap): CodeWithSourceMap;
}

View File

@@ -0,0 +1,9 @@
export declare namespace errorUtil {
type ErrMessage = string | {
message?: string | undefined;
};
const errToObj: (message?: ErrMessage) => {
message?: string | undefined;
};
const toString: (message?: ErrMessage) => string | undefined;
}

View File

@@ -0,0 +1,59 @@
'use strict'
const { test } = require('node:test')
const assert = require('node:assert')
const { join } = require('path')
const { file } = require('./helper')
const ThreadStream = require('..')
test('bundlers support with .js file', function (t, done) {
globalThis.__bundlerPathsOverrides = {
'thread-stream-worker': join(__dirname, 'custom-worker.js')
}
const dest = file()
process.on('uncaughtException', error => {
console.log(error)
})
const stream = new ThreadStream({
filename: join(__dirname, 'to-file.js'),
workerData: { dest },
sync: true
})
stream.worker.removeAllListeners('message')
stream.worker.once('message', message => {
assert.strictEqual(message.code, 'CUSTOM-WORKER-CALLED')
done()
})
stream.end()
})
test('bundlers support with .mjs file', function (t, done) {
globalThis.__bundlerPathsOverrides = {
'thread-stream-worker': join(__dirname, 'custom-worker.js')
}
const dest = file()
process.on('uncaughtException', error => {
console.log(error)
})
const stream = new ThreadStream({
filename: join(__dirname, 'to-file.mjs'),
workerData: { dest },
sync: true
})
stream.worker.removeAllListeners('message')
stream.worker.once('message', message => {
assert.strictEqual(message.code, 'CUSTOM-WORKER-CALLED')
done()
})
stream.end()
})

View File

@@ -0,0 +1,67 @@
'use strict'
const test = require('node:test')
const assert = require('node:assert')
const os = require('node:os')
const { join } = require('node:path')
const { readFile } = require('node:fs').promises
const { promisify } = require('node:util')
const pino = require('../..')
const { watchFileCreated, watchForWrite, file } = require('../helper')
const { pid } = process
const hostname = os.hostname()
test('thread-stream async flush', async () => {
const destination = file()
const transport = pino.transport({
target: join(__dirname, '..', 'fixtures', 'to-file-transport.js'),
options: { destination }
})
const instance = pino(transport)
instance.info('hello')
assert.equal(instance.flush(), undefined)
await watchFileCreated(destination)
const result = JSON.parse(await readFile(destination))
delete result.time
assert.deepEqual(result, {
pid,
hostname,
level: 30,
msg: 'hello'
})
})
test('thread-stream async flush should call the passed callback', async () => {
const outputPath = file()
async function getOutputLogLines () {
return (await readFile(outputPath)).toString().trim().split('\n').map(JSON.parse)
}
const transport = pino.transport({
target: join(__dirname, '..', 'fixtures', 'to-file-transport.js'),
options: { destination: outputPath }
})
const instance = pino(transport)
const flushPromise = promisify(instance.flush).bind(instance)
instance.info('hello')
await flushPromise()
await watchFileCreated(outputPath)
const [firstFlushData] = await getOutputLogLines()
assert.equal(firstFlushData.msg, 'hello')
instance.info('world')
await flushPromise()
await watchForWrite(outputPath, 'world')
// After flush, both messages should be present
const afterSecondFlush = await getOutputLogLines()
assert.equal(afterSecondFlush.length, 2)
assert.equal(afterSecondFlush[1].msg, 'world')
})