WIP: bootstrap and partial real Solana watcher implementation
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
import { URIRegExps } from "./uri";
|
||||
declare const _default: URIRegExps;
|
||||
export default _default;
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,3 @@
|
||||
import z4 from "./classic/index.cjs";
|
||||
export * from "./classic/index.cjs";
|
||||
export default z4;
|
||||
@@ -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
|
||||
@@ -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>;
|
||||
@@ -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[2r−1]
|
||||
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...Bp−1] ← 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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())
|
||||
})
|
||||
})
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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')
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env node
|
||||
import './dist/cli.js'
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
@@ -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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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()
|
||||
})
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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')
|
||||
})
|
||||
Reference in New Issue
Block a user