WIP: bootstrap and partial real Solana watcher implementation
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
# thread-stream
|
||||
[](https://www.npmjs.com/package/thread-stream)
|
||||
[](https://github.com/pinojs/thread-stream/actions)
|
||||
[](https://standardjs.com/)
|
||||
|
||||
A streaming way to send data to a Node.js Worker Thread.
|
||||
|
||||
## install
|
||||
|
||||
```sh
|
||||
npm i thread-stream
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
'use strict'
|
||||
|
||||
const ThreadStream = require('thread-stream')
|
||||
const { join } = require('path')
|
||||
|
||||
const stream = new ThreadStream({
|
||||
filename: join(__dirname, 'worker.js'),
|
||||
workerData: { dest },
|
||||
workerOpts: {}, // Other options to be passed to Worker
|
||||
sync: false, // default
|
||||
})
|
||||
|
||||
stream.write('hello')
|
||||
|
||||
// Asynchronous flushing
|
||||
stream.flush(function () {
|
||||
stream.write(' ')
|
||||
stream.write('world')
|
||||
|
||||
// Synchronous flushing
|
||||
stream.flushSync()
|
||||
stream.end()
|
||||
})
|
||||
```
|
||||
|
||||
`flush(cb)` waits for the worker destination flush when supported (`flush`, `flushSync`, or pending `drain`).
|
||||
|
||||
In `worker.js`:
|
||||
|
||||
```js
|
||||
'use strict'
|
||||
|
||||
const fs = require('fs')
|
||||
const { once } = require('events')
|
||||
|
||||
async function run (opts) {
|
||||
const stream = fs.createWriteStream(opts.dest)
|
||||
await once(stream, 'open')
|
||||
return stream
|
||||
}
|
||||
|
||||
module.exports = run
|
||||
```
|
||||
|
||||
Make sure that the stream emits `'close'` when the stream completes.
|
||||
This can usually be achieved by passing the [`autoDestroy: true`](https://nodejs.org/api/stream.html#stream_new_stream_writable_options)
|
||||
flag your stream classes.
|
||||
|
||||
The underlining worker is automatically closed if the stream is garbage collected.
|
||||
|
||||
|
||||
### External modules
|
||||
|
||||
You may use this module within compatible external modules, that exports the `worker.js` interface.
|
||||
|
||||
```js
|
||||
const ThreadStream = require('thread-stream')
|
||||
|
||||
const modulePath = require.resolve('pino-elasticsearch')
|
||||
|
||||
const stream = new ThreadStream({
|
||||
filename: modulePath,
|
||||
workerData: { node: 'http://localhost:9200' }
|
||||
})
|
||||
|
||||
stream.write('log to elasticsearch!')
|
||||
stream.flushSync()
|
||||
stream.end()
|
||||
```
|
||||
|
||||
This module works with `yarn` in PnP (plug'n play) mode too!
|
||||
|
||||
### Emit events
|
||||
|
||||
You can emit events on the ThreadStream from your worker using [`worker.parentPort.postMessage()`](https://nodejs.org/api/worker_threads.html#workerparentport).
|
||||
Messages that do not carry a thread-stream protocol `code` are ignored.
|
||||
For custom events, the message (JSON object) must have the following data structure:
|
||||
|
||||
```js
|
||||
parentPort.postMessage({
|
||||
code: 'EVENT',
|
||||
name: 'eventName',
|
||||
args: ['list', 'of', 'args', 123, new Error('Boom')]
|
||||
})
|
||||
```
|
||||
|
||||
On your ThreadStream, you can add a listener function for this event name:
|
||||
|
||||
```js
|
||||
const stream = new ThreadStream({
|
||||
filename: join(__dirname, 'worker.js'),
|
||||
workerData: {},
|
||||
})
|
||||
stream.on('eventName', function (a, b, c, n, err) {
|
||||
console.log('received:', a, b, c, n, err) // received: list of args 123 Error: Boom
|
||||
})
|
||||
```
|
||||
|
||||
### Post Messages
|
||||
|
||||
You can post messages to the worker by emitting a `message` event on the ThreadStream.
|
||||
|
||||
```js
|
||||
const stream = new ThreadStream({
|
||||
filename: join(__dirname, 'worker.js'),
|
||||
workerData: {},
|
||||
})
|
||||
stream.emit('message', message)
|
||||
```
|
||||
|
||||
On your worker, you can listen for this message using [`worker.parentPort.on('message', cb)`](https://nodejs.org/api/worker_threads.html#event-message).
|
||||
|
||||
```js
|
||||
const { parentPort } = require('worker_threads')
|
||||
parentPort.on('message', function (message) {
|
||||
console.log('received:', message)
|
||||
})
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
@@ -0,0 +1,499 @@
|
||||
/**
|
||||
* SHA3 (keccak) addons.
|
||||
*
|
||||
* * Full [NIST SP 800-185](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-185.pdf):
|
||||
* cSHAKE, KMAC, TupleHash, ParallelHash + XOF variants
|
||||
* * Reduced-round Keccak [(draft)](https://datatracker.ietf.org/doc/draft-irtf-cfrg-kangarootwelve/):
|
||||
* * 🦘 K12 aka KangarooTwelve
|
||||
* * M14 aka MarsupilamiFourteen
|
||||
* * TurboSHAKE
|
||||
* * KeccakPRG: Pseudo-random generator based on Keccak [(pdf)](https://keccak.team/files/CSF-0.1.pdf)
|
||||
* @module
|
||||
*/
|
||||
import { Keccak, type ShakeOpts } from './sha3.ts';
|
||||
import {
|
||||
abytes,
|
||||
anumber,
|
||||
type CHashO,
|
||||
type CHashXO,
|
||||
createOptHasher,
|
||||
createXOFer,
|
||||
Hash,
|
||||
type HashXOF,
|
||||
type Input,
|
||||
toBytes,
|
||||
u32,
|
||||
} from './utils.ts';
|
||||
|
||||
// cSHAKE && KMAC (NIST SP800-185)
|
||||
const _8n = BigInt(8);
|
||||
const _ffn = BigInt(0xff);
|
||||
|
||||
// NOTE: it is safe to use bigints here, since they used only for length encoding (not actual data).
|
||||
// We use bigints in sha256 for lengths too.
|
||||
function leftEncode(n: number | bigint): Uint8Array {
|
||||
n = BigInt(n);
|
||||
const res = [Number(n & _ffn)];
|
||||
n >>= _8n;
|
||||
for (; n > 0; n >>= _8n) res.unshift(Number(n & _ffn));
|
||||
res.unshift(res.length);
|
||||
return new Uint8Array(res);
|
||||
}
|
||||
|
||||
function rightEncode(n: number | bigint): Uint8Array {
|
||||
n = BigInt(n);
|
||||
const res = [Number(n & _ffn)];
|
||||
n >>= _8n;
|
||||
for (; n > 0; n >>= _8n) res.unshift(Number(n & _ffn));
|
||||
res.push(res.length);
|
||||
return new Uint8Array(res);
|
||||
}
|
||||
|
||||
function chooseLen(opts: ShakeOpts, outputLen: number): number {
|
||||
return opts.dkLen === undefined ? outputLen : opts.dkLen;
|
||||
}
|
||||
|
||||
const abytesOrZero = (buf?: Input) => {
|
||||
if (buf === undefined) return Uint8Array.of();
|
||||
return toBytes(buf);
|
||||
};
|
||||
// NOTE: second modulo is necessary since we don't need to add padding if current element takes whole block
|
||||
const getPadding = (len: number, block: number) => new Uint8Array((block - (len % block)) % block);
|
||||
export type cShakeOpts = ShakeOpts & { personalization?: Input; NISTfn?: Input };
|
||||
|
||||
// Personalization
|
||||
function cshakePers(hash: Keccak, opts: cShakeOpts = {}): Keccak {
|
||||
if (!opts || (!opts.personalization && !opts.NISTfn)) return hash;
|
||||
// Encode and pad inplace to avoid unneccesary memory copies/slices (so we don't need to zero them later)
|
||||
// bytepad(encode_string(N) || encode_string(S), 168)
|
||||
const blockLenBytes = leftEncode(hash.blockLen);
|
||||
const fn = abytesOrZero(opts.NISTfn);
|
||||
const fnLen = leftEncode(_8n * BigInt(fn.length)); // length in bits
|
||||
const pers = abytesOrZero(opts.personalization);
|
||||
const persLen = leftEncode(_8n * BigInt(pers.length)); // length in bits
|
||||
if (!fn.length && !pers.length) return hash;
|
||||
hash.suffix = 0x04;
|
||||
hash.update(blockLenBytes).update(fnLen).update(fn).update(persLen).update(pers);
|
||||
let totalLen = blockLenBytes.length + fnLen.length + fn.length + persLen.length + pers.length;
|
||||
hash.update(getPadding(totalLen, hash.blockLen));
|
||||
return hash;
|
||||
}
|
||||
|
||||
const gencShake = (suffix: number, blockLen: number, outputLen: number) =>
|
||||
createXOFer<Keccak, cShakeOpts>((opts: cShakeOpts = {}) =>
|
||||
cshakePers(new Keccak(blockLen, suffix, chooseLen(opts, outputLen), true), opts)
|
||||
);
|
||||
|
||||
// TODO: refactor
|
||||
export type ICShake = {
|
||||
(msg: Input, opts?: cShakeOpts): Uint8Array;
|
||||
outputLen: number;
|
||||
blockLen: number;
|
||||
create(opts: cShakeOpts): HashXOF<Keccak>;
|
||||
};
|
||||
export type ITupleHash = {
|
||||
(messages: Input[], opts?: cShakeOpts): Uint8Array;
|
||||
create(opts?: cShakeOpts): TupleHash;
|
||||
};
|
||||
export type IParHash = {
|
||||
(message: Input, opts?: ParallelOpts): Uint8Array;
|
||||
create(opts?: ParallelOpts): ParallelHash;
|
||||
};
|
||||
export const cshake128: ICShake = /* @__PURE__ */ (() => gencShake(0x1f, 168, 128 / 8))();
|
||||
export const cshake256: ICShake = /* @__PURE__ */ (() => gencShake(0x1f, 136, 256 / 8))();
|
||||
|
||||
export class KMAC extends Keccak implements HashXOF<KMAC> {
|
||||
constructor(
|
||||
blockLen: number,
|
||||
outputLen: number,
|
||||
enableXOF: boolean,
|
||||
key: Input,
|
||||
opts: cShakeOpts = {}
|
||||
) {
|
||||
super(blockLen, 0x1f, outputLen, enableXOF);
|
||||
cshakePers(this, { NISTfn: 'KMAC', personalization: opts.personalization });
|
||||
key = toBytes(key);
|
||||
abytes(key);
|
||||
// 1. newX = bytepad(encode_string(K), 168) || X || right_encode(L).
|
||||
const blockLenBytes = leftEncode(this.blockLen);
|
||||
const keyLen = leftEncode(_8n * BigInt(key.length));
|
||||
this.update(blockLenBytes).update(keyLen).update(key);
|
||||
const totalLen = blockLenBytes.length + keyLen.length + key.length;
|
||||
this.update(getPadding(totalLen, this.blockLen));
|
||||
}
|
||||
protected finish(): void {
|
||||
if (!this.finished) this.update(rightEncode(this.enableXOF ? 0 : _8n * BigInt(this.outputLen))); // outputLen in bits
|
||||
super.finish();
|
||||
}
|
||||
_cloneInto(to?: KMAC): KMAC {
|
||||
// Create new instance without calling constructor since key already in state and we don't know it.
|
||||
// Force "to" to be instance of KMAC instead of Sha3.
|
||||
if (!to) {
|
||||
to = Object.create(Object.getPrototypeOf(this), {}) as KMAC;
|
||||
to.state = this.state.slice();
|
||||
to.blockLen = this.blockLen;
|
||||
to.state32 = u32(to.state);
|
||||
}
|
||||
return super._cloneInto(to) as KMAC;
|
||||
}
|
||||
clone(): KMAC {
|
||||
return this._cloneInto();
|
||||
}
|
||||
}
|
||||
|
||||
function genKmac(blockLen: number, outputLen: number, xof = false) {
|
||||
const kmac = (key: Input, message: Input, opts?: cShakeOpts): Uint8Array =>
|
||||
kmac.create(key, opts).update(message).digest();
|
||||
kmac.create = (key: Input, opts: cShakeOpts = {}) =>
|
||||
new KMAC(blockLen, chooseLen(opts, outputLen), xof, key, opts);
|
||||
return kmac;
|
||||
}
|
||||
|
||||
export const kmac128: {
|
||||
(key: Input, message: Input, opts?: cShakeOpts): Uint8Array;
|
||||
create(key: Input, opts?: cShakeOpts): KMAC;
|
||||
} = /* @__PURE__ */ (() => genKmac(168, 128 / 8))();
|
||||
export const kmac256: {
|
||||
(key: Input, message: Input, opts?: cShakeOpts): Uint8Array;
|
||||
create(key: Input, opts?: cShakeOpts): KMAC;
|
||||
} = /* @__PURE__ */ (() => genKmac(136, 256 / 8))();
|
||||
export const kmac128xof: {
|
||||
(key: Input, message: Input, opts?: cShakeOpts): Uint8Array;
|
||||
create(key: Input, opts?: cShakeOpts): KMAC;
|
||||
} = /* @__PURE__ */ (() => genKmac(168, 128 / 8, true))();
|
||||
export const kmac256xof: {
|
||||
(key: Input, message: Input, opts?: cShakeOpts): Uint8Array;
|
||||
create(key: Input, opts?: cShakeOpts): KMAC;
|
||||
} = /* @__PURE__ */ (() => genKmac(136, 256 / 8, true))();
|
||||
|
||||
// TupleHash
|
||||
// Usage: tuple(['ab', 'cd']) != tuple(['a', 'bcd'])
|
||||
export class TupleHash extends Keccak implements HashXOF<TupleHash> {
|
||||
constructor(blockLen: number, outputLen: number, enableXOF: boolean, opts: cShakeOpts = {}) {
|
||||
super(blockLen, 0x1f, outputLen, enableXOF);
|
||||
cshakePers(this, { NISTfn: 'TupleHash', personalization: opts.personalization });
|
||||
// Change update after cshake processed
|
||||
this.update = (data: Input) => {
|
||||
data = toBytes(data);
|
||||
abytes(data);
|
||||
super.update(leftEncode(_8n * BigInt(data.length)));
|
||||
super.update(data);
|
||||
return this;
|
||||
};
|
||||
}
|
||||
protected finish(): void {
|
||||
if (!this.finished)
|
||||
super.update(rightEncode(this.enableXOF ? 0 : _8n * BigInt(this.outputLen))); // outputLen in bits
|
||||
super.finish();
|
||||
}
|
||||
_cloneInto(to?: TupleHash): TupleHash {
|
||||
to ||= new TupleHash(this.blockLen, this.outputLen, this.enableXOF);
|
||||
return super._cloneInto(to) as TupleHash;
|
||||
}
|
||||
clone(): TupleHash {
|
||||
return this._cloneInto();
|
||||
}
|
||||
}
|
||||
|
||||
function genTuple(blockLen: number, outputLen: number, xof = false) {
|
||||
const tuple = (messages: Input[], opts?: cShakeOpts): Uint8Array => {
|
||||
const h = tuple.create(opts);
|
||||
for (const msg of messages) h.update(msg);
|
||||
return h.digest();
|
||||
};
|
||||
tuple.create = (opts: cShakeOpts = {}) =>
|
||||
new TupleHash(blockLen, chooseLen(opts, outputLen), xof, opts);
|
||||
return tuple;
|
||||
}
|
||||
|
||||
/** 128-bit TupleHASH. */
|
||||
export const tuplehash128: ITupleHash = /* @__PURE__ */ (() => genTuple(168, 128 / 8))();
|
||||
/** 256-bit TupleHASH. */
|
||||
export const tuplehash256: ITupleHash = /* @__PURE__ */ (() => genTuple(136, 256 / 8))();
|
||||
/** 128-bit TupleHASH XOF. */
|
||||
export const tuplehash128xof: ITupleHash = /* @__PURE__ */ (() => genTuple(168, 128 / 8, true))();
|
||||
/** 256-bit TupleHASH XOF. */
|
||||
export const tuplehash256xof: ITupleHash = /* @__PURE__ */ (() => genTuple(136, 256 / 8, true))();
|
||||
|
||||
// ParallelHash (same as K12/M14, but without speedup for inputs less 8kb, reduced number of rounds and more simple)
|
||||
type ParallelOpts = cShakeOpts & { blockLen?: number };
|
||||
|
||||
export class ParallelHash extends Keccak implements HashXOF<ParallelHash> {
|
||||
private leafHash?: Hash<Keccak>;
|
||||
protected leafCons: () => Hash<Keccak>;
|
||||
private chunkPos = 0; // Position of current block in chunk
|
||||
private chunksDone = 0; // How many chunks we already have
|
||||
private chunkLen: number;
|
||||
constructor(
|
||||
blockLen: number,
|
||||
outputLen: number,
|
||||
leafCons: () => Hash<Keccak>,
|
||||
enableXOF: boolean,
|
||||
opts: ParallelOpts = {}
|
||||
) {
|
||||
super(blockLen, 0x1f, outputLen, enableXOF);
|
||||
cshakePers(this, { NISTfn: 'ParallelHash', personalization: opts.personalization });
|
||||
this.leafCons = leafCons;
|
||||
let { blockLen: B } = opts;
|
||||
B ||= 8;
|
||||
anumber(B);
|
||||
this.chunkLen = B;
|
||||
super.update(leftEncode(B));
|
||||
// Change update after cshake processed
|
||||
this.update = (data: Input) => {
|
||||
data = toBytes(data);
|
||||
abytes(data);
|
||||
const { chunkLen, leafCons } = this;
|
||||
for (let pos = 0, len = data.length; pos < len; ) {
|
||||
if (this.chunkPos == chunkLen || !this.leafHash) {
|
||||
if (this.leafHash) {
|
||||
super.update(this.leafHash.digest());
|
||||
this.chunksDone++;
|
||||
}
|
||||
this.leafHash = leafCons();
|
||||
this.chunkPos = 0;
|
||||
}
|
||||
const take = Math.min(chunkLen - this.chunkPos, len - pos);
|
||||
this.leafHash.update(data.subarray(pos, pos + take));
|
||||
this.chunkPos += take;
|
||||
pos += take;
|
||||
}
|
||||
return this;
|
||||
};
|
||||
}
|
||||
protected finish(): void {
|
||||
if (this.finished) return;
|
||||
if (this.leafHash) {
|
||||
super.update(this.leafHash.digest());
|
||||
this.chunksDone++;
|
||||
}
|
||||
super.update(rightEncode(this.chunksDone));
|
||||
super.update(rightEncode(this.enableXOF ? 0 : _8n * BigInt(this.outputLen))); // outputLen in bits
|
||||
super.finish();
|
||||
}
|
||||
_cloneInto(to?: ParallelHash): ParallelHash {
|
||||
to ||= new ParallelHash(this.blockLen, this.outputLen, this.leafCons, this.enableXOF);
|
||||
if (this.leafHash) to.leafHash = this.leafHash._cloneInto(to.leafHash as Keccak);
|
||||
to.chunkPos = this.chunkPos;
|
||||
to.chunkLen = this.chunkLen;
|
||||
to.chunksDone = this.chunksDone;
|
||||
return super._cloneInto(to) as ParallelHash;
|
||||
}
|
||||
destroy(): void {
|
||||
super.destroy.call(this);
|
||||
if (this.leafHash) this.leafHash.destroy();
|
||||
}
|
||||
clone(): ParallelHash {
|
||||
return this._cloneInto();
|
||||
}
|
||||
}
|
||||
|
||||
function genPrl(
|
||||
blockLen: number,
|
||||
outputLen: number,
|
||||
leaf: ReturnType<typeof gencShake>,
|
||||
xof = false
|
||||
) {
|
||||
const parallel = (message: Input, opts?: ParallelOpts): Uint8Array =>
|
||||
parallel.create(opts).update(message).digest();
|
||||
parallel.create = (opts: ParallelOpts = {}) =>
|
||||
new ParallelHash(
|
||||
blockLen,
|
||||
chooseLen(opts, outputLen),
|
||||
() => leaf.create({ dkLen: 2 * outputLen }),
|
||||
xof,
|
||||
opts
|
||||
);
|
||||
return parallel;
|
||||
}
|
||||
|
||||
/** 128-bit ParallelHash. In JS, it is not parallel. */
|
||||
export const parallelhash128: IParHash = /* @__PURE__ */ (() => genPrl(168, 128 / 8, cshake128))();
|
||||
/** 256-bit ParallelHash. In JS, it is not parallel. */
|
||||
export const parallelhash256: IParHash = /* @__PURE__ */ (() => genPrl(136, 256 / 8, cshake256))();
|
||||
/** 128-bit ParallelHash XOF. In JS, it is not parallel. */
|
||||
export const parallelhash128xof: IParHash = /* @__PURE__ */ (() =>
|
||||
genPrl(168, 128 / 8, cshake128, true))();
|
||||
/** 256-bit ParallelHash. In JS, it is not parallel. */
|
||||
export const parallelhash256xof: IParHash = /* @__PURE__ */ (() =>
|
||||
genPrl(136, 256 / 8, cshake256, true))();
|
||||
|
||||
// Should be simple 'shake with 12 rounds', but no, we got whole new spec about Turbo SHAKE Pro MAX.
|
||||
export type TurboshakeOpts = ShakeOpts & {
|
||||
D?: number; // Domain separation byte
|
||||
};
|
||||
|
||||
const genTurboshake = (blockLen: number, outputLen: number) =>
|
||||
createXOFer<HashXOF<Keccak>, TurboshakeOpts>((opts: TurboshakeOpts = {}) => {
|
||||
const D = opts.D === undefined ? 0x1f : opts.D;
|
||||
// Section 2.1 of https://datatracker.ietf.org/doc/draft-irtf-cfrg-kangarootwelve/
|
||||
if (!Number.isSafeInteger(D) || D < 0x01 || D > 0x7f)
|
||||
throw new Error('invalid domain separation byte must be 0x01..0x7f, got: ' + D);
|
||||
return new Keccak(blockLen, D, opts.dkLen === undefined ? outputLen : opts.dkLen, true, 12);
|
||||
});
|
||||
|
||||
/** TurboSHAKE 128-bit: reduced 12-round keccak. */
|
||||
export const turboshake128: CHashXO = /* @__PURE__ */ genTurboshake(168, 256 / 8);
|
||||
/** TurboSHAKE 256-bit: reduced 12-round keccak. */
|
||||
export const turboshake256: CHashXO = /* @__PURE__ */ genTurboshake(136, 512 / 8);
|
||||
|
||||
// Kangaroo
|
||||
// Same as NIST rightEncode, but returns [0] for zero string
|
||||
function rightEncodeK12(n: number | bigint): Uint8Array {
|
||||
n = BigInt(n);
|
||||
const res: number[] = [];
|
||||
for (; n > 0; n >>= _8n) res.unshift(Number(n & _ffn));
|
||||
res.push(res.length);
|
||||
return Uint8Array.from(res);
|
||||
}
|
||||
|
||||
export type KangarooOpts = { dkLen?: number; personalization?: Input };
|
||||
const EMPTY_BUFFER = /* @__PURE__ */ Uint8Array.of();
|
||||
|
||||
export class KangarooTwelve extends Keccak implements HashXOF<KangarooTwelve> {
|
||||
readonly chunkLen = 8192;
|
||||
private leafHash?: Keccak;
|
||||
protected leafLen: number;
|
||||
private personalization: Uint8Array;
|
||||
private chunkPos = 0; // Position of current block in chunk
|
||||
private chunksDone = 0; // How many chunks we already have
|
||||
constructor(
|
||||
blockLen: number,
|
||||
leafLen: number,
|
||||
outputLen: number,
|
||||
rounds: number,
|
||||
opts: KangarooOpts
|
||||
) {
|
||||
super(blockLen, 0x07, outputLen, true, rounds);
|
||||
this.leafLen = leafLen;
|
||||
this.personalization = abytesOrZero(opts.personalization);
|
||||
}
|
||||
update(data: Input): this {
|
||||
data = toBytes(data);
|
||||
abytes(data);
|
||||
const { chunkLen, blockLen, leafLen, rounds } = this;
|
||||
for (let pos = 0, len = data.length; pos < len; ) {
|
||||
if (this.chunkPos == chunkLen) {
|
||||
if (this.leafHash) super.update(this.leafHash.digest());
|
||||
else {
|
||||
this.suffix = 0x06; // Its safe to change suffix here since its used only in digest()
|
||||
super.update(Uint8Array.from([3, 0, 0, 0, 0, 0, 0, 0]));
|
||||
}
|
||||
this.leafHash = new Keccak(blockLen, 0x0b, leafLen, false, rounds);
|
||||
this.chunksDone++;
|
||||
this.chunkPos = 0;
|
||||
}
|
||||
const take = Math.min(chunkLen - this.chunkPos, len - pos);
|
||||
const chunk = data.subarray(pos, pos + take);
|
||||
if (this.leafHash) this.leafHash.update(chunk);
|
||||
else super.update(chunk);
|
||||
this.chunkPos += take;
|
||||
pos += take;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
protected finish(): void {
|
||||
if (this.finished) return;
|
||||
const { personalization } = this;
|
||||
this.update(personalization).update(rightEncodeK12(personalization.length));
|
||||
// Leaf hash
|
||||
if (this.leafHash) {
|
||||
super.update(this.leafHash.digest());
|
||||
super.update(rightEncodeK12(this.chunksDone));
|
||||
super.update(Uint8Array.from([0xff, 0xff]));
|
||||
}
|
||||
super.finish.call(this);
|
||||
}
|
||||
destroy(): void {
|
||||
super.destroy.call(this);
|
||||
if (this.leafHash) this.leafHash.destroy();
|
||||
// We cannot zero personalization buffer since it is user provided and we don't want to mutate user input
|
||||
this.personalization = EMPTY_BUFFER;
|
||||
}
|
||||
_cloneInto(to?: KangarooTwelve): KangarooTwelve {
|
||||
const { blockLen, leafLen, leafHash, outputLen, rounds } = this;
|
||||
to ||= new KangarooTwelve(blockLen, leafLen, outputLen, rounds, {});
|
||||
super._cloneInto(to);
|
||||
if (leafHash) to.leafHash = leafHash._cloneInto(to.leafHash);
|
||||
to.personalization.set(this.personalization);
|
||||
to.leafLen = this.leafLen;
|
||||
to.chunkPos = this.chunkPos;
|
||||
to.chunksDone = this.chunksDone;
|
||||
return to;
|
||||
}
|
||||
clone(): KangarooTwelve {
|
||||
return this._cloneInto();
|
||||
}
|
||||
}
|
||||
/** KangarooTwelve: reduced 12-round keccak. */
|
||||
export const k12: CHashO = /* @__PURE__ */ (() =>
|
||||
createOptHasher<KangarooTwelve, KangarooOpts>(
|
||||
(opts: KangarooOpts = {}) => new KangarooTwelve(168, 32, chooseLen(opts, 32), 12, opts)
|
||||
))();
|
||||
/** MarsupilamiFourteen: reduced 14-round keccak. */
|
||||
export const m14: CHashO = /* @__PURE__ */ (() =>
|
||||
createOptHasher<KangarooTwelve, KangarooOpts>(
|
||||
(opts: KangarooOpts = {}) => new KangarooTwelve(136, 64, chooseLen(opts, 64), 14, opts)
|
||||
))();
|
||||
|
||||
/**
|
||||
* More at https://github.com/XKCP/XKCP/tree/master/lib/high/Keccak/PRG.
|
||||
*/
|
||||
export class KeccakPRG extends Keccak {
|
||||
protected rate: number;
|
||||
constructor(capacity: number) {
|
||||
anumber(capacity);
|
||||
// Rho should be full bytes
|
||||
if (capacity < 0 || capacity > 1600 - 10 || (1600 - capacity - 2) % 8)
|
||||
throw new Error('invalid capacity');
|
||||
// blockLen = rho in bytes
|
||||
super((1600 - capacity - 2) / 8, 0, 0, true);
|
||||
this.rate = 1600 - capacity;
|
||||
this.posOut = Math.floor((this.rate + 7) / 8);
|
||||
}
|
||||
keccak(): void {
|
||||
// Duplex padding
|
||||
this.state[this.pos] ^= 0x01;
|
||||
this.state[this.blockLen] ^= 0x02; // Rho is full bytes
|
||||
super.keccak();
|
||||
this.pos = 0;
|
||||
this.posOut = 0;
|
||||
}
|
||||
update(data: Input): this {
|
||||
super.update(data);
|
||||
this.posOut = this.blockLen;
|
||||
return this;
|
||||
}
|
||||
feed(data: Input): this {
|
||||
return this.update(data);
|
||||
}
|
||||
protected finish(): void {}
|
||||
digestInto(_out: Uint8Array): Uint8Array {
|
||||
throw new Error('digest is not allowed, use .fetch instead');
|
||||
}
|
||||
fetch(bytes: number): Uint8Array {
|
||||
return this.xof(bytes);
|
||||
}
|
||||
// Ensure irreversibility (even if state leaked previous outputs cannot be computed)
|
||||
forget(): void {
|
||||
if (this.rate < 1600 / 2 + 1) throw new Error('rate is too low to use .forget()');
|
||||
this.keccak();
|
||||
for (let i = 0; i < this.blockLen; i++) this.state[i] = 0;
|
||||
this.pos = this.blockLen;
|
||||
this.keccak();
|
||||
this.posOut = this.blockLen;
|
||||
}
|
||||
_cloneInto(to?: KeccakPRG): KeccakPRG {
|
||||
const { rate } = this;
|
||||
to ||= new KeccakPRG(1600 - rate);
|
||||
super._cloneInto(to);
|
||||
to.rate = rate;
|
||||
return to;
|
||||
}
|
||||
clone(): KeccakPRG {
|
||||
return this._cloneInto();
|
||||
}
|
||||
}
|
||||
|
||||
/** KeccakPRG: Pseudo-random generator based on Keccak. https://keccak.team/files/CSF-0.1.pdf */
|
||||
export const keccakprg = (capacity = 254): KeccakPRG => new KeccakPRG(capacity);
|
||||
@@ -0,0 +1,61 @@
|
||||
4.0.0 / 2016-12-3
|
||||
------------------
|
||||
- `decode` now returns a `Buffer` again, to avoid potential cryptographic errors. [Daniel Cousens / #21](https://github.com/cryptocoinjs/bs58/pull/21)
|
||||
|
||||
3.0.0 / 2015-08-18
|
||||
------------------
|
||||
- refactored module into generic [`base-x`](https://github.com/cryptocoinjs/base-x).
|
||||
|
||||
2.0.1 / 2014-12-23
|
||||
------------------
|
||||
- performance boost in `encode()` [#10](https://github.com/cryptocoinjs/bs58/pull/10)
|
||||
|
||||
2.0.0 / 2014-10-03
|
||||
------------------
|
||||
- `decode` now returns an `Array` instead of `Buffer` to keep things simple. [Daniel Cousens / #9](https://github.com/cryptocoinjs/bs58/pull/9)
|
||||
|
||||
1.2.1 / 2014-07-24
|
||||
------------------
|
||||
* speed optimizations [Daniel Cousens / #8](https://github.com/cryptocoinjs/bs58/pull/8)
|
||||
|
||||
1.2.0 / 2014-06-29
|
||||
------------------
|
||||
* removed `bigi` dep, implemented direct byte conversion [Jared Deckard / #6](https://github.com/cryptocoinjs/bs58/pull/6)
|
||||
|
||||
1.1.0 / 2014-06-26
|
||||
------------------
|
||||
* user `Buffer` internally for calculations, providing cleaner code and a performance increase. [Daniel Cousens](https://github.com/cryptocoinjs/bs58/commit/129c71de8bc1e36f113bce06da0616066f41c5ca)
|
||||
|
||||
1.0.0 / 2014-05-27
|
||||
------------------
|
||||
* removed `binstring` dep, `Buffer` now only input to `encode()` and output of `decode()`
|
||||
* update `bigi` from `~0.3.0` to `^1.1.0`
|
||||
* added travis-ci support
|
||||
* added coveralls support
|
||||
* modified tests and library to handle fixture style testing (thanks to bitcoinjs-lib devs and [Daniel Cousens](https://github.com/dcousens))
|
||||
|
||||
|
||||
0.3.0 / 2014-02-24
|
||||
------------------
|
||||
* duck type input to `encode` and change output of `decode` to `Buffer`.
|
||||
|
||||
|
||||
0.2.1 / 2014-02-24
|
||||
------------------
|
||||
* removed bower and component support. Closes #1
|
||||
* convert from 4 spaces to 2
|
||||
|
||||
|
||||
0.2.0 / 2013-12-07
|
||||
------------------
|
||||
* renamed from `cryptocoin-base58` to `bs58`
|
||||
|
||||
|
||||
0.1.0 / 2013-11-20
|
||||
------------------
|
||||
* removed AMD support
|
||||
|
||||
|
||||
0.0.1 / 2013-11-04
|
||||
------------------
|
||||
* initial release
|
||||
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* @fileoverview Disallow shadowing of globalThis, NaN, undefined, and Infinity (ES2020 section 18.1)
|
||||
* @author Michael Ficarra
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Determines if a variable safely shadows undefined.
|
||||
* This is the case when a variable named `undefined` is never assigned to a value (i.e. it always shares the same value
|
||||
* as the global).
|
||||
* @param {eslintScope.Variable} variable The variable to check
|
||||
* @returns {boolean} true if this variable safely shadows `undefined`
|
||||
*/
|
||||
function safelyShadowsUndefined(variable) {
|
||||
return (
|
||||
variable.name === "undefined" &&
|
||||
variable.references.every(ref => !ref.isWrite()) &&
|
||||
variable.defs.every(
|
||||
def =>
|
||||
def.node.type === "VariableDeclarator" &&
|
||||
def.node.init === null,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** @type {import('../types').Rule.RuleModule} */
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: "suggestion",
|
||||
|
||||
defaultOptions: [
|
||||
{
|
||||
reportGlobalThis: true,
|
||||
},
|
||||
],
|
||||
|
||||
docs: {
|
||||
description: "Disallow identifiers from shadowing restricted names",
|
||||
recommended: true,
|
||||
url: "https://eslint.org/docs/latest/rules/no-shadow-restricted-names",
|
||||
},
|
||||
|
||||
schema: [
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
reportGlobalThis: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
],
|
||||
|
||||
messages: {
|
||||
shadowingRestrictedName: "Shadowing of global property '{{name}}'.",
|
||||
},
|
||||
},
|
||||
|
||||
create(context) {
|
||||
const [{ reportGlobalThis }] = context.options;
|
||||
|
||||
const RESTRICTED = new Set([
|
||||
"undefined",
|
||||
"NaN",
|
||||
"Infinity",
|
||||
"arguments",
|
||||
"eval",
|
||||
]);
|
||||
|
||||
if (reportGlobalThis) {
|
||||
RESTRICTED.add("globalThis");
|
||||
}
|
||||
|
||||
const sourceCode = context.sourceCode;
|
||||
|
||||
// Track reported nodes to avoid duplicate reports. For example, on class declarations.
|
||||
const reportedNodes = new Set();
|
||||
|
||||
return {
|
||||
"VariableDeclaration, :function, CatchClause, ImportDeclaration, ClassDeclaration, ClassExpression"(
|
||||
node,
|
||||
) {
|
||||
for (const variable of sourceCode.getDeclaredVariables(node)) {
|
||||
if (
|
||||
variable.defs.length > 0 &&
|
||||
RESTRICTED.has(variable.name) &&
|
||||
!safelyShadowsUndefined(variable)
|
||||
) {
|
||||
for (const def of variable.defs) {
|
||||
const nodeToReport = def.name;
|
||||
|
||||
if (!reportedNodes.has(nodeToReport)) {
|
||||
reportedNodes.add(nodeToReport);
|
||||
context.report({
|
||||
node: nodeToReport,
|
||||
messageId: "shadowingRestrictedName",
|
||||
data: {
|
||||
name: variable.name,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
'use strict';
|
||||
|
||||
// do not edit .js files directly - edit src/index.jst
|
||||
|
||||
|
||||
|
||||
module.exports = function equal(a, b) {
|
||||
if (a === b) return true;
|
||||
|
||||
if (a && b && typeof a == 'object' && typeof b == 'object') {
|
||||
if (a.constructor !== b.constructor) return false;
|
||||
|
||||
var length, i, keys;
|
||||
if (Array.isArray(a)) {
|
||||
length = a.length;
|
||||
if (length != b.length) return false;
|
||||
for (i = length; i-- !== 0;)
|
||||
if (!equal(a[i], b[i])) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (a.constructor === RegExp) return a.source === b.source && a.flags === b.flags;
|
||||
if (a.valueOf !== Object.prototype.valueOf) return a.valueOf() === b.valueOf();
|
||||
if (a.toString !== Object.prototype.toString) return a.toString() === b.toString();
|
||||
|
||||
keys = Object.keys(a);
|
||||
length = keys.length;
|
||||
if (length !== Object.keys(b).length) return false;
|
||||
|
||||
for (i = length; i-- !== 0;)
|
||||
if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false;
|
||||
|
||||
for (i = length; i-- !== 0;) {
|
||||
var key = keys[i];
|
||||
|
||||
if (key === '_owner' && a.$$typeof) {
|
||||
// React-specific: avoid traversing React elements' _owner.
|
||||
// _owner contains circular references
|
||||
// and is not needed when comparing the actual elements (and not their owners)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!equal(a[key], b[key])) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// true if both NaN, false otherwise
|
||||
return a!==a && b!==b;
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"moduleDetectionKind.d.ts","sourceRoot":"","sources":["../../src/enums/moduleDetectionKind.ts"],"names":[],"mappings":"AACA,eAAO,IAAI,mBAAmB,EAAE,GAAG,CAAC"}
|
||||
@@ -0,0 +1,48 @@
|
||||
export class TransactionExpiredBlockheightExceededError extends Error {
|
||||
signature: string;
|
||||
|
||||
constructor(signature: string) {
|
||||
super(`Signature ${signature} has expired: block height exceeded.`);
|
||||
this.signature = signature;
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperty(
|
||||
TransactionExpiredBlockheightExceededError.prototype,
|
||||
'name',
|
||||
{
|
||||
value: 'TransactionExpiredBlockheightExceededError',
|
||||
},
|
||||
);
|
||||
|
||||
export class TransactionExpiredTimeoutError extends Error {
|
||||
signature: string;
|
||||
|
||||
constructor(signature: string, timeoutSeconds: number) {
|
||||
super(
|
||||
`Transaction was not confirmed in ${timeoutSeconds.toFixed(
|
||||
2,
|
||||
)} seconds. It is ` +
|
||||
'unknown if it succeeded or failed. Check signature ' +
|
||||
`${signature} using the Solana Explorer or CLI tools.`,
|
||||
);
|
||||
this.signature = signature;
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperty(TransactionExpiredTimeoutError.prototype, 'name', {
|
||||
value: 'TransactionExpiredTimeoutError',
|
||||
});
|
||||
|
||||
export class TransactionExpiredNonceInvalidError extends Error {
|
||||
signature: string;
|
||||
|
||||
constructor(signature: string) {
|
||||
super(`Signature ${signature} has expired: the nonce is no longer valid.`);
|
||||
this.signature = signature;
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperty(TransactionExpiredNonceInvalidError.prototype, 'name', {
|
||||
value: 'TransactionExpiredNonceInvalidError',
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
import { Parser, Printer } from "../index.js";
|
||||
|
||||
export declare const parsers: {
|
||||
markdown: Parser;
|
||||
mdx: Parser;
|
||||
remark: Parser;
|
||||
};
|
||||
|
||||
export declare const printers: {
|
||||
mdast: Printer;
|
||||
};
|
||||
@@ -0,0 +1,77 @@
|
||||
var OverloadYield = require("./OverloadYield.js");
|
||||
var regenerator = require("./regenerator.js");
|
||||
var regeneratorAsync = require("./regeneratorAsync.js");
|
||||
var regeneratorAsyncGen = require("./regeneratorAsyncGen.js");
|
||||
var regeneratorAsyncIterator = require("./regeneratorAsyncIterator.js");
|
||||
var regeneratorKeys = require("./regeneratorKeys.js");
|
||||
var regeneratorValues = require("./regeneratorValues.js");
|
||||
function _regeneratorRuntime() {
|
||||
"use strict";
|
||||
|
||||
var r = regenerator(),
|
||||
e = r.m(_regeneratorRuntime),
|
||||
t = (Object.getPrototypeOf ? Object.getPrototypeOf(e) : e.__proto__).constructor;
|
||||
function n(r) {
|
||||
var e = "function" == typeof r && r.constructor;
|
||||
return !!e && (e === t || "GeneratorFunction" === (e.displayName || e.name));
|
||||
}
|
||||
var o = {
|
||||
"throw": 1,
|
||||
"return": 2,
|
||||
"break": 3,
|
||||
"continue": 3
|
||||
};
|
||||
function a(r) {
|
||||
var e, t;
|
||||
return function (n) {
|
||||
e || (e = {
|
||||
stop: function stop() {
|
||||
return t(n.a, 2);
|
||||
},
|
||||
"catch": function _catch() {
|
||||
return n.v;
|
||||
},
|
||||
abrupt: function abrupt(r, e) {
|
||||
return t(n.a, o[r], e);
|
||||
},
|
||||
delegateYield: function delegateYield(r, o, a) {
|
||||
return e.resultName = o, t(n.d, regeneratorValues(r), a);
|
||||
},
|
||||
finish: function finish(r) {
|
||||
return t(n.f, r);
|
||||
}
|
||||
}, t = function t(r, _t, o) {
|
||||
n.p = e.prev, n.n = e.next;
|
||||
try {
|
||||
return r(_t, o);
|
||||
} finally {
|
||||
e.next = n.n;
|
||||
}
|
||||
}), e.resultName && (e[e.resultName] = n.v, e.resultName = void 0), e.sent = n.v, e.next = n.n;
|
||||
try {
|
||||
return r.call(this, e);
|
||||
} finally {
|
||||
n.p = e.prev, n.n = e.next;
|
||||
}
|
||||
};
|
||||
}
|
||||
return (module.exports = _regeneratorRuntime = function _regeneratorRuntime() {
|
||||
return {
|
||||
wrap: function wrap(e, t, n, o) {
|
||||
return r.w(a(e), t, n, o && o.reverse());
|
||||
},
|
||||
isGeneratorFunction: n,
|
||||
mark: r.m,
|
||||
awrap: function awrap(r, e) {
|
||||
return new OverloadYield(r, e);
|
||||
},
|
||||
AsyncIterator: regeneratorAsyncIterator,
|
||||
async: function async(r, e, t, o, u) {
|
||||
return (n(e) ? regeneratorAsyncGen : regeneratorAsync)(a(r), e, t, o, u);
|
||||
},
|
||||
keys: regeneratorKeys,
|
||||
values: regeneratorValues
|
||||
};
|
||||
}, module.exports.__esModule = true, module.exports["default"] = module.exports)();
|
||||
}
|
||||
module.exports = _regeneratorRuntime, module.exports.__esModule = true, module.exports["default"] = module.exports;
|
||||
@@ -0,0 +1,27 @@
|
||||
import * as core from "../core/index.js";
|
||||
import * as schemas from "./schemas.js";
|
||||
|
||||
// @__NO_SIDE_EFFECTS__
|
||||
export function string<T = unknown>(params?: string | core.$ZodStringParams): schemas.ZodMiniString<T> {
|
||||
return core._coercedString(schemas.ZodMiniString, params) as schemas.ZodMiniString<T>;
|
||||
}
|
||||
|
||||
// @__NO_SIDE_EFFECTS__
|
||||
export function number<T = unknown>(params?: string | core.$ZodNumberParams): schemas.ZodMiniNumber<T> {
|
||||
return core._coercedNumber(schemas.ZodMiniNumber, params) as schemas.ZodMiniNumber<T>;
|
||||
}
|
||||
|
||||
// @__NO_SIDE_EFFECTS__
|
||||
export function boolean<T = unknown>(params?: string | core.$ZodBooleanParams): schemas.ZodMiniBoolean<T> {
|
||||
return core._coercedBoolean(schemas.ZodMiniBoolean, params) as schemas.ZodMiniBoolean<T>;
|
||||
}
|
||||
|
||||
// @__NO_SIDE_EFFECTS__
|
||||
export function bigint<T = unknown>(params?: string | core.$ZodBigIntParams): schemas.ZodMiniBigInt<T> {
|
||||
return core._coercedBigint(schemas.ZodMiniBigInt, params) as schemas.ZodMiniBigInt<T>;
|
||||
}
|
||||
|
||||
// @__NO_SIDE_EFFECTS__
|
||||
export function date<T = unknown>(params?: string | core.$ZodDateParams): schemas.ZodMiniDate<T> {
|
||||
return core._coercedDate(schemas.ZodMiniDate, params) as schemas.ZodMiniDate<T>;
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
'use strict'
|
||||
const crypto = require('./utils')
|
||||
const { signatureAlgorithmHashFromCertificate } = require('./cert-signatures')
|
||||
|
||||
// SASLprep (RFC 4013) — minimal in-tree implementation.
|
||||
//
|
||||
// Per RFC 5802 §2.2, the SCRAM-SHA-256 client must normalize the password via
|
||||
// SASLprep before feeding it into PBKDF2. PostgreSQL's server applies the same
|
||||
// SASLprep when computing the stored verifier, and libpq does the same client
|
||||
// side, so passwords whose NFKC form differs from the raw form
|
||||
// would otherwise authenticate against psql/libpq but fail against pg with `28P01`.
|
||||
//
|
||||
// We deliberately implement only the three steps that change the byte content:
|
||||
// 1. RFC 3454 Table C.1.2 (non-ASCII space) → U+0020 SPACE.
|
||||
// 2. RFC 3454 Table B.1 (commonly mapped to nothing) → empty.
|
||||
// 3. NFKC normalization.
|
||||
// We skip the prohibition (RFC 4013 §2.3) and bidi (RFC 3454 §6) checks.
|
||||
// libpq is forgiving on those paths and Postgres's own SASLprep matches that
|
||||
// leniency for legacy roles, so omitting the rejection logic keeps existing
|
||||
// roles working without adding complexity.
|
||||
function saslprep(password) {
|
||||
// RFC 3454 Table C.1.2 — non-ASCII space characters, mapped to U+0020.
|
||||
const nonAsciiSpace = /[\u00A0\u1680\u2000-\u200B\u202F\u205F\u3000]/g
|
||||
// RFC 3454 Table B.1 — "commonly mapped to nothing". The set intentionally
|
||||
// contains zero-width joiners and variation selectors — the very characters
|
||||
// ESLint's no-misleading-character-class warns about — because they combine
|
||||
// with their neighbors and the RFC strips them for that reason.
|
||||
// eslint-disable-next-line no-misleading-character-class
|
||||
const mappedToNothing = /[\u00AD\u034F\u1806\u180B\u180C\u180D\u200C\u200D\u2060\uFE00-\uFE0F\uFEFF]/g
|
||||
return password.replace(nonAsciiSpace, ' ').replace(mappedToNothing, '').normalize('NFKC')
|
||||
}
|
||||
|
||||
const DEFAULT_MAX_SCRAM_ITERATIONS = 100000
|
||||
|
||||
function startSession(mechanisms, stream, scramMaxIterations = DEFAULT_MAX_SCRAM_ITERATIONS) {
|
||||
const candidates = ['SCRAM-SHA-256']
|
||||
if (stream) candidates.unshift('SCRAM-SHA-256-PLUS') // higher-priority, so placed first
|
||||
|
||||
const mechanism = candidates.find((candidate) => mechanisms.includes(candidate))
|
||||
|
||||
if (!mechanism) {
|
||||
throw new Error('SASL: Only mechanism(s) ' + candidates.join(' and ') + ' are supported')
|
||||
}
|
||||
|
||||
if (mechanism === 'SCRAM-SHA-256-PLUS' && typeof stream.getPeerCertificate !== 'function') {
|
||||
// this should never happen if we are really talking to a Postgres server
|
||||
throw new Error('SASL: Mechanism SCRAM-SHA-256-PLUS requires a certificate')
|
||||
}
|
||||
|
||||
const clientNonce = crypto.randomBytes(18).toString('base64')
|
||||
const gs2Header = mechanism === 'SCRAM-SHA-256-PLUS' ? 'p=tls-server-end-point' : stream ? 'y' : 'n'
|
||||
|
||||
return {
|
||||
mechanism,
|
||||
clientNonce,
|
||||
response: gs2Header + ',,n=*,r=' + clientNonce,
|
||||
message: 'SASLInitialResponse',
|
||||
scramMaxIterations,
|
||||
}
|
||||
}
|
||||
|
||||
async function continueSession(session, password, serverData, stream) {
|
||||
if (session.message !== 'SASLInitialResponse') {
|
||||
throw new Error('SASL: Last message was not SASLInitialResponse')
|
||||
}
|
||||
if (typeof password !== 'string') {
|
||||
throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: client password must be a string')
|
||||
}
|
||||
if (password === '') {
|
||||
throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: client password must be a non-empty string')
|
||||
}
|
||||
if (typeof serverData !== 'string') {
|
||||
throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: serverData must be a string')
|
||||
}
|
||||
|
||||
const sv = parseServerFirstMessage(serverData)
|
||||
|
||||
if (!sv.nonce.startsWith(session.clientNonce)) {
|
||||
throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: server nonce does not start with client nonce')
|
||||
} else if (sv.nonce.length === session.clientNonce.length) {
|
||||
throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: server nonce is too short')
|
||||
}
|
||||
|
||||
const scramMaxIterations =
|
||||
typeof session.scramMaxIterations === 'number' ? session.scramMaxIterations : DEFAULT_MAX_SCRAM_ITERATIONS
|
||||
// a value of 0 disables the iteration count check
|
||||
if (scramMaxIterations !== 0 && sv.iteration > scramMaxIterations) {
|
||||
throw new Error(
|
||||
'SASL: SCRAM-SERVER-FIRST-MESSAGE: iteration count ' +
|
||||
sv.iteration +
|
||||
' exceeds scramMaxIterations of ' +
|
||||
scramMaxIterations
|
||||
)
|
||||
}
|
||||
|
||||
const clientFirstMessageBare = 'n=*,r=' + session.clientNonce
|
||||
const serverFirstMessage = 'r=' + sv.nonce + ',s=' + sv.salt + ',i=' + sv.iteration
|
||||
|
||||
// without channel binding:
|
||||
let channelBinding = stream ? 'eSws' : 'biws' // 'y,,' or 'n,,', base64-encoded
|
||||
|
||||
// override if channel binding is in use:
|
||||
if (session.mechanism === 'SCRAM-SHA-256-PLUS') {
|
||||
const peerCert = stream.getPeerCertificate().raw
|
||||
let hashName = signatureAlgorithmHashFromCertificate(peerCert)
|
||||
if (hashName === 'MD5' || hashName === 'SHA-1') hashName = 'SHA-256'
|
||||
const certHash = await crypto.hashByName(hashName, peerCert)
|
||||
const bindingData = Buffer.concat([Buffer.from('p=tls-server-end-point,,'), Buffer.from(certHash)])
|
||||
channelBinding = bindingData.toString('base64')
|
||||
}
|
||||
|
||||
const clientFinalMessageWithoutProof = 'c=' + channelBinding + ',r=' + sv.nonce
|
||||
const authMessage = clientFirstMessageBare + ',' + serverFirstMessage + ',' + clientFinalMessageWithoutProof
|
||||
|
||||
const saltBytes = Buffer.from(sv.salt, 'base64')
|
||||
const saltedPassword = await crypto.deriveKey(saslprep(password), saltBytes, sv.iteration)
|
||||
const clientKey = await crypto.hmacSha256(saltedPassword, 'Client Key')
|
||||
const storedKey = await crypto.sha256(clientKey)
|
||||
const clientSignature = await crypto.hmacSha256(storedKey, authMessage)
|
||||
const clientProof = xorBuffers(Buffer.from(clientKey), Buffer.from(clientSignature)).toString('base64')
|
||||
const serverKey = await crypto.hmacSha256(saltedPassword, 'Server Key')
|
||||
const serverSignatureBytes = await crypto.hmacSha256(serverKey, authMessage)
|
||||
|
||||
session.message = 'SASLResponse'
|
||||
session.serverSignature = Buffer.from(serverSignatureBytes).toString('base64')
|
||||
session.response = clientFinalMessageWithoutProof + ',p=' + clientProof
|
||||
}
|
||||
|
||||
function finalizeSession(session, serverData) {
|
||||
if (session.message !== 'SASLResponse') {
|
||||
throw new Error('SASL: Last message was not SASLResponse')
|
||||
}
|
||||
if (typeof serverData !== 'string') {
|
||||
throw new Error('SASL: SCRAM-SERVER-FINAL-MESSAGE: serverData must be a string')
|
||||
}
|
||||
|
||||
const { serverSignature } = parseServerFinalMessage(serverData)
|
||||
|
||||
if (serverSignature !== session.serverSignature) {
|
||||
throw new Error('SASL: SCRAM-SERVER-FINAL-MESSAGE: server signature does not match')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* printable = %x21-2B / %x2D-7E
|
||||
* ;; Printable ASCII except ",".
|
||||
* ;; Note that any "printable" is also
|
||||
* ;; a valid "value".
|
||||
*/
|
||||
function isPrintableChars(text) {
|
||||
if (typeof text !== 'string') {
|
||||
throw new TypeError('SASL: text must be a string')
|
||||
}
|
||||
return text
|
||||
.split('')
|
||||
.map((_, i) => text.charCodeAt(i))
|
||||
.every((c) => (c >= 0x21 && c <= 0x2b) || (c >= 0x2d && c <= 0x7e))
|
||||
}
|
||||
|
||||
/**
|
||||
* base64-char = ALPHA / DIGIT / "/" / "+"
|
||||
*
|
||||
* base64-4 = 4base64-char
|
||||
*
|
||||
* base64-3 = 3base64-char "="
|
||||
*
|
||||
* base64-2 = 2base64-char "=="
|
||||
*
|
||||
* base64 = *base64-4 [base64-3 / base64-2]
|
||||
*/
|
||||
function isBase64(text) {
|
||||
return /^(?:[a-zA-Z0-9+/]{4})*(?:[a-zA-Z0-9+/]{2}==|[a-zA-Z0-9+/]{3}=)?$/.test(text)
|
||||
}
|
||||
|
||||
function parseAttributePairs(text) {
|
||||
if (typeof text !== 'string') {
|
||||
throw new TypeError('SASL: attribute pairs text must be a string')
|
||||
}
|
||||
|
||||
return new Map(
|
||||
text.split(',').map((attrValue) => {
|
||||
if (!/^.=/.test(attrValue)) {
|
||||
throw new Error('SASL: Invalid attribute pair entry')
|
||||
}
|
||||
const name = attrValue[0]
|
||||
const value = attrValue.substring(2)
|
||||
return [name, value]
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
function parseServerFirstMessage(data) {
|
||||
const attrPairs = parseAttributePairs(data)
|
||||
|
||||
const nonce = attrPairs.get('r')
|
||||
if (!nonce) {
|
||||
throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: nonce missing')
|
||||
} else if (!isPrintableChars(nonce)) {
|
||||
throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: nonce must only contain printable characters')
|
||||
}
|
||||
const salt = attrPairs.get('s')
|
||||
if (!salt) {
|
||||
throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: salt missing')
|
||||
} else if (!isBase64(salt)) {
|
||||
throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: salt must be base64')
|
||||
}
|
||||
const iterationText = attrPairs.get('i')
|
||||
if (!iterationText) {
|
||||
throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: iteration missing')
|
||||
} else if (!/^[1-9][0-9]*$/.test(iterationText)) {
|
||||
throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: invalid iteration count')
|
||||
}
|
||||
const iteration = parseInt(iterationText, 10)
|
||||
|
||||
return {
|
||||
nonce,
|
||||
salt,
|
||||
iteration,
|
||||
}
|
||||
}
|
||||
|
||||
function parseServerFinalMessage(serverData) {
|
||||
const attrPairs = parseAttributePairs(serverData)
|
||||
const error = attrPairs.get('e')
|
||||
const serverSignature = attrPairs.get('v')
|
||||
|
||||
if (error) {
|
||||
throw new Error(`SASL: SCRAM-SERVER-FINAL-MESSAGE: server returned error: "${error}"`)
|
||||
}
|
||||
|
||||
if (!serverSignature) {
|
||||
throw new Error('SASL: SCRAM-SERVER-FINAL-MESSAGE: server signature is missing')
|
||||
} else if (!isBase64(serverSignature)) {
|
||||
throw new Error('SASL: SCRAM-SERVER-FINAL-MESSAGE: server signature must be base64')
|
||||
}
|
||||
return {
|
||||
serverSignature,
|
||||
}
|
||||
}
|
||||
|
||||
function xorBuffers(a, b) {
|
||||
if (!Buffer.isBuffer(a)) {
|
||||
throw new TypeError('first argument must be a Buffer')
|
||||
}
|
||||
if (!Buffer.isBuffer(b)) {
|
||||
throw new TypeError('second argument must be a Buffer')
|
||||
}
|
||||
if (a.length !== b.length) {
|
||||
throw new Error('Buffer lengths must match')
|
||||
}
|
||||
if (a.length === 0) {
|
||||
throw new Error('Buffers cannot be empty')
|
||||
}
|
||||
return Buffer.from(a.map((_, i) => a[i] ^ b[i]))
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
startSession,
|
||||
continueSession,
|
||||
finalizeSession,
|
||||
DEFAULT_MAX_SCRAM_ITERATIONS,
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "fast-levenshtein",
|
||||
"version": "2.0.6",
|
||||
"description": "Efficient implementation of Levenshtein algorithm with locale-specific collator support.",
|
||||
"main": "levenshtein.js",
|
||||
"files": [
|
||||
"levenshtein.js"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "grunt build",
|
||||
"prepublish": "npm run build",
|
||||
"benchmark": "grunt benchmark",
|
||||
"test": "mocha"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "~1.5.0",
|
||||
"grunt": "~0.4.1",
|
||||
"grunt-benchmark": "~0.2.0",
|
||||
"grunt-cli": "^1.2.0",
|
||||
"grunt-contrib-jshint": "~0.4.3",
|
||||
"grunt-contrib-uglify": "~0.2.0",
|
||||
"grunt-mocha-test": "~0.2.2",
|
||||
"grunt-npm-install": "~0.1.0",
|
||||
"load-grunt-tasks": "~0.6.0",
|
||||
"lodash": "^4.0.1",
|
||||
"mocha": "~1.9.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/hiddentao/fast-levenshtein.git"
|
||||
},
|
||||
"keywords": [
|
||||
"levenshtein",
|
||||
"distance",
|
||||
"string"
|
||||
],
|
||||
"author": "Ramesh Nair <ram@hiddentao.com> (http://www.hiddentao.com/)",
|
||||
"license": "MIT"
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export type Options = [];
|
||||
export type MessageIds = 'unaryMinus';
|
||||
declare const _default: import("@typescript-eslint/utils/ts-eslint").RuleModule<"unaryMinus", [], import("../../rules").ESLintPluginDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
||||
name: string;
|
||||
};
|
||||
export default _default;
|
||||
@@ -0,0 +1,158 @@
|
||||
/**
|
||||
* @fileoverview Rule to flag use of comma operator
|
||||
* @author Brandon Mills
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const astUtils = require("./utils/ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** @type {import('../types').Rule.RuleModule} */
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: "suggestion",
|
||||
|
||||
docs: {
|
||||
description: "Disallow comma operators",
|
||||
recommended: false,
|
||||
url: "https://eslint.org/docs/latest/rules/no-sequences",
|
||||
},
|
||||
|
||||
schema: [
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
allowInParentheses: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
],
|
||||
|
||||
defaultOptions: [
|
||||
{
|
||||
allowInParentheses: true,
|
||||
},
|
||||
],
|
||||
|
||||
messages: {
|
||||
unexpectedCommaExpression: "Unexpected use of comma operator.",
|
||||
},
|
||||
},
|
||||
|
||||
create(context) {
|
||||
const [{ allowInParentheses }] = context.options;
|
||||
const sourceCode = context.sourceCode;
|
||||
|
||||
/**
|
||||
* Parts of the grammar that are required to have parens.
|
||||
*/
|
||||
const parenthesized = {
|
||||
DoWhileStatement: "test",
|
||||
IfStatement: "test",
|
||||
SwitchStatement: "discriminant",
|
||||
WhileStatement: "test",
|
||||
WithStatement: "object",
|
||||
ArrowFunctionExpression: "body",
|
||||
|
||||
/*
|
||||
* Omitting CallExpression - commas are parsed as argument separators
|
||||
* Omitting NewExpression - commas are parsed as argument separators
|
||||
* Omitting ForInStatement - parts aren't individually parenthesised
|
||||
* Omitting ForStatement - parts aren't individually parenthesised
|
||||
*/
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines whether a node is required by the grammar to be wrapped in
|
||||
* parens, e.g. the test of an if statement.
|
||||
* @param {ASTNode} node The AST node
|
||||
* @returns {boolean} True if parens around node belong to parent node.
|
||||
*/
|
||||
function requiresExtraParens(node) {
|
||||
return (
|
||||
node.parent &&
|
||||
parenthesized[node.parent.type] &&
|
||||
node === node.parent[parenthesized[node.parent.type]]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a node is wrapped in parens.
|
||||
* @param {ASTNode} node The AST node
|
||||
* @returns {boolean} True if the node has a paren on each side.
|
||||
*/
|
||||
function isParenthesised(node) {
|
||||
return astUtils.isParenthesised(sourceCode, node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a node is wrapped in two levels of parens.
|
||||
* @param {ASTNode} node The AST node
|
||||
* @returns {boolean} True if two parens surround the node on each side.
|
||||
*/
|
||||
function isParenthesisedTwice(node) {
|
||||
const previousToken = sourceCode.getTokenBefore(node, 1),
|
||||
nextToken = sourceCode.getTokenAfter(node, 1);
|
||||
|
||||
return (
|
||||
isParenthesised(node) &&
|
||||
previousToken &&
|
||||
nextToken &&
|
||||
astUtils.isOpeningParenToken(previousToken) &&
|
||||
previousToken.range[1] <= node.range[0] &&
|
||||
astUtils.isClosingParenToken(nextToken) &&
|
||||
nextToken.range[0] >= node.range[1]
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
SequenceExpression(node) {
|
||||
// Always allow sequences in for statement update
|
||||
if (
|
||||
node.parent.type === "ForStatement" &&
|
||||
(node === node.parent.init || node === node.parent.update)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Wrapping a sequence in extra parens indicates intent
|
||||
if (allowInParentheses) {
|
||||
if (requiresExtraParens(node)) {
|
||||
if (isParenthesisedTwice(node)) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (isParenthesised(node)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const firstCommaToken = sourceCode.getTokenAfter(
|
||||
node.expressions[0],
|
||||
astUtils.isCommaToken,
|
||||
);
|
||||
|
||||
context.report({
|
||||
node,
|
||||
loc: firstCommaToken.loc,
|
||||
messageId: "unexpectedCommaExpression",
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,157 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
import { util } from "../helpers/util.js";
|
||||
|
||||
const Test = z.object({
|
||||
f1: z.number(),
|
||||
f2: z.string().optional(),
|
||||
f3: z.string().nullable(),
|
||||
f4: z.array(z.object({ t: z.union([z.string(), z.boolean()]) })),
|
||||
});
|
||||
type TestFlattenedErrors = z.inferFlattenedErrors<typeof Test, { message: string; code: number }>;
|
||||
type TestFormErrors = z.inferFlattenedErrors<typeof Test>;
|
||||
|
||||
test("default flattened errors type inference", () => {
|
||||
type TestTypeErrors = {
|
||||
formErrors: string[];
|
||||
fieldErrors: { [P in keyof z.TypeOf<typeof Test>]?: string[] };
|
||||
};
|
||||
|
||||
util.assertEqual<z.inferFlattenedErrors<typeof Test>, TestTypeErrors>(true);
|
||||
util.assertEqual<z.inferFlattenedErrors<typeof Test, { message: string }>, TestTypeErrors>(false);
|
||||
});
|
||||
|
||||
test("custom flattened errors type inference", () => {
|
||||
type ErrorType = { message: string; code: number };
|
||||
type TestTypeErrors = {
|
||||
formErrors: ErrorType[];
|
||||
fieldErrors: {
|
||||
[P in keyof z.TypeOf<typeof Test>]?: ErrorType[];
|
||||
};
|
||||
};
|
||||
|
||||
util.assertEqual<z.inferFlattenedErrors<typeof Test>, TestTypeErrors>(false);
|
||||
util.assertEqual<z.inferFlattenedErrors<typeof Test, { message: string; code: number }>, TestTypeErrors>(true);
|
||||
util.assertEqual<z.inferFlattenedErrors<typeof Test, { message: string }>, TestTypeErrors>(false);
|
||||
});
|
||||
|
||||
test("form errors type inference", () => {
|
||||
type TestTypeErrors = {
|
||||
formErrors: string[];
|
||||
fieldErrors: { [P in keyof z.TypeOf<typeof Test>]?: string[] };
|
||||
};
|
||||
|
||||
util.assertEqual<z.inferFlattenedErrors<typeof Test>, TestTypeErrors>(true);
|
||||
});
|
||||
|
||||
test(".flatten() type assertion", () => {
|
||||
const parsed = Test.safeParse({}) as z.SafeParseError<void>;
|
||||
const validFlattenedErrors: TestFlattenedErrors = parsed.error.flatten(() => ({ message: "", code: 0 }));
|
||||
// @ts-expect-error should fail assertion between `TestFlattenedErrors` and unmapped `flatten()`.
|
||||
const invalidFlattenedErrors: TestFlattenedErrors = parsed.error.flatten();
|
||||
const validFormErrors: TestFormErrors = parsed.error.flatten();
|
||||
// @ts-expect-error should fail assertion between `TestFormErrors` and mapped `flatten()`.
|
||||
const invalidFormErrors: TestFormErrors = parsed.error.flatten(() => ({
|
||||
message: "string",
|
||||
code: 0,
|
||||
}));
|
||||
|
||||
[validFlattenedErrors, invalidFlattenedErrors, validFormErrors, invalidFormErrors];
|
||||
});
|
||||
|
||||
test(".formErrors type assertion", () => {
|
||||
const parsed = Test.safeParse({}) as z.SafeParseError<void>;
|
||||
const validFormErrors: TestFormErrors = parsed.error.formErrors;
|
||||
// @ts-expect-error should fail assertion between `TestFlattenedErrors` and `.formErrors`.
|
||||
const invalidFlattenedErrors: TestFlattenedErrors = parsed.error.formErrors;
|
||||
|
||||
[validFormErrors, invalidFlattenedErrors];
|
||||
});
|
||||
|
||||
test("all errors", () => {
|
||||
const propertySchema = z.string();
|
||||
const schema = z
|
||||
.object({
|
||||
a: propertySchema,
|
||||
b: propertySchema,
|
||||
})
|
||||
.refine(
|
||||
(val) => {
|
||||
return val.a === val.b;
|
||||
},
|
||||
{ message: "Must be equal" }
|
||||
);
|
||||
|
||||
try {
|
||||
schema.parse({
|
||||
a: "asdf",
|
||||
b: "qwer",
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
expect(error.flatten()).toEqual({
|
||||
formErrors: ["Must be equal"],
|
||||
fieldErrors: {},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
schema.parse({
|
||||
a: null,
|
||||
b: null,
|
||||
});
|
||||
} catch (_error) {
|
||||
const error = _error as z.ZodError;
|
||||
expect(error.flatten()).toEqual({
|
||||
formErrors: [],
|
||||
fieldErrors: {
|
||||
a: ["Expected string, received null"],
|
||||
b: ["Expected string, received null"],
|
||||
},
|
||||
});
|
||||
|
||||
expect(error.flatten((iss) => iss.message.toUpperCase())).toEqual({
|
||||
formErrors: [],
|
||||
fieldErrors: {
|
||||
a: ["EXPECTED STRING, RECEIVED NULL"],
|
||||
b: ["EXPECTED STRING, RECEIVED NULL"],
|
||||
},
|
||||
});
|
||||
// Test identity
|
||||
|
||||
expect(error.flatten((i: z.ZodIssue) => i)).toEqual({
|
||||
formErrors: [],
|
||||
fieldErrors: {
|
||||
a: [
|
||||
{
|
||||
code: "invalid_type",
|
||||
expected: "string",
|
||||
message: "Expected string, received null",
|
||||
path: ["a"],
|
||||
received: "null",
|
||||
},
|
||||
],
|
||||
b: [
|
||||
{
|
||||
code: "invalid_type",
|
||||
expected: "string",
|
||||
message: "Expected string, received null",
|
||||
path: ["b"],
|
||||
received: "null",
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
// Test mapping
|
||||
expect(error.flatten((i: z.ZodIssue) => i.message.length)).toEqual({
|
||||
formErrors: [],
|
||||
fieldErrors: {
|
||||
a: ["Expected string, received null".length],
|
||||
b: ["Expected string, received null".length],
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
||||
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
__exportStar(require("./applyDefault"), exports);
|
||||
__exportStar(require("./deepMerge"), exports);
|
||||
__exportStar(require("./getParserServices"), exports);
|
||||
__exportStar(require("./InferTypesFromRule"), exports);
|
||||
__exportStar(require("./nullThrows"), exports);
|
||||
__exportStar(require("./RuleCreator"), exports);
|
||||
@@ -0,0 +1,163 @@
|
||||
import { expect, test } from "vitest";
|
||||
import { z } from "../../../../index.js";
|
||||
import hr from "../../../locales/hr.js";
|
||||
|
||||
test("Croatian locale - type name translations in too_small errors", () => {
|
||||
z.config(hr());
|
||||
|
||||
// Test string type translation
|
||||
const stringSchema = z.string().min(5);
|
||||
const stringResult = stringSchema.safeParse("abc");
|
||||
expect(stringResult.success).toBe(false);
|
||||
if (!stringResult.success) {
|
||||
expect(stringResult.error.issues[0].message).toBe("Premalo: očekivano da tekst ima >=5 znakova");
|
||||
}
|
||||
|
||||
// Test number type translation
|
||||
const numberSchema = z.number().min(10);
|
||||
const numberResult = numberSchema.safeParse(5);
|
||||
expect(numberResult.success).toBe(false);
|
||||
if (!numberResult.success) {
|
||||
expect(numberResult.error.issues[0].message).toBe("Premalo: očekivano da broj bude >=10");
|
||||
}
|
||||
|
||||
// Test array type translation
|
||||
const arraySchema = z.array(z.string()).min(3);
|
||||
const arrayResult = arraySchema.safeParse(["a", "b"]);
|
||||
expect(arrayResult.success).toBe(false);
|
||||
if (!arrayResult.success) {
|
||||
expect(arrayResult.error.issues[0].message).toBe("Premalo: očekivano da niz ima >=3 stavki");
|
||||
}
|
||||
|
||||
// Test set type translation
|
||||
const setSchema = z.set(z.string()).min(2);
|
||||
const setResult = setSchema.safeParse(new Set(["a"]));
|
||||
expect(setResult.success).toBe(false);
|
||||
if (!setResult.success) {
|
||||
expect(setResult.error.issues[0].message).toBe("Premalo: očekivano da skup ima >=2 stavki");
|
||||
}
|
||||
});
|
||||
|
||||
test("Croatian locale - type name translations in too_big errors", () => {
|
||||
z.config(hr());
|
||||
|
||||
// Test string type translation
|
||||
const stringSchema = z.string().max(3);
|
||||
const stringResult = stringSchema.safeParse("abcde");
|
||||
expect(stringResult.success).toBe(false);
|
||||
if (!stringResult.success) {
|
||||
expect(stringResult.error.issues[0].message).toBe("Preveliko: očekivano da tekst ima <=3 znakova");
|
||||
}
|
||||
|
||||
// Test number type translation
|
||||
const numberSchema = z.number().max(10);
|
||||
const numberResult = numberSchema.safeParse(15);
|
||||
expect(numberResult.success).toBe(false);
|
||||
if (!numberResult.success) {
|
||||
expect(numberResult.error.issues[0].message).toBe("Preveliko: očekivano da broj bude <=10");
|
||||
}
|
||||
|
||||
// Test array type translation
|
||||
const arraySchema = z.array(z.string()).max(2);
|
||||
const arrayResult = arraySchema.safeParse(["a", "b", "c"]);
|
||||
expect(arrayResult.success).toBe(false);
|
||||
if (!arrayResult.success) {
|
||||
expect(arrayResult.error.issues[0].message).toBe("Preveliko: očekivano da niz ima <=2 stavki");
|
||||
}
|
||||
});
|
||||
|
||||
test("Croatian locale - type name translations in invalid_type errors", () => {
|
||||
z.config(hr());
|
||||
|
||||
// Test string expected, number received
|
||||
const stringSchema = z.string();
|
||||
const stringResult = stringSchema.safeParse(123);
|
||||
expect(stringResult.success).toBe(false);
|
||||
if (!stringResult.success) {
|
||||
expect(stringResult.error.issues[0].message).toBe("Neispravan unos: očekuje se tekst, a primljeno je broj");
|
||||
}
|
||||
|
||||
// Test number expected, string received
|
||||
const numberSchema = z.number();
|
||||
const numberResult = numberSchema.safeParse("abc");
|
||||
expect(numberResult.success).toBe(false);
|
||||
if (!numberResult.success) {
|
||||
expect(numberResult.error.issues[0].message).toBe("Neispravan unos: očekuje se broj, a primljeno je tekst");
|
||||
}
|
||||
|
||||
// Test boolean expected, null received
|
||||
const booleanSchema = z.boolean();
|
||||
const booleanResult = booleanSchema.safeParse(null);
|
||||
expect(booleanResult.success).toBe(false);
|
||||
if (!booleanResult.success) {
|
||||
expect(booleanResult.error.issues[0].message).toBe("Neispravan unos: očekuje se boolean, a primljeno je null");
|
||||
}
|
||||
|
||||
// Test array expected, object received
|
||||
const arraySchema = z.array(z.string());
|
||||
const arrayResult = arraySchema.safeParse({});
|
||||
expect(arrayResult.success).toBe(false);
|
||||
if (!arrayResult.success) {
|
||||
expect(arrayResult.error.issues[0].message).toBe("Neispravan unos: očekuje se niz, a primljeno je objekt");
|
||||
}
|
||||
});
|
||||
|
||||
test("Croatian locale - other error cases", () => {
|
||||
z.config(hr());
|
||||
|
||||
// Test invalid_element with tuple
|
||||
const tupleSchema = z.tuple([z.string(), z.number()]);
|
||||
const tupleResult = tupleSchema.safeParse(["abc", "not a number"]);
|
||||
expect(tupleResult.success).toBe(false);
|
||||
if (!tupleResult.success) {
|
||||
expect(tupleResult.error.issues[0].message).toContain("Neispravan unos");
|
||||
}
|
||||
|
||||
// Test invalid_value with enum
|
||||
const enumSchema = z.enum(["a", "b"]);
|
||||
const enumResult = enumSchema.safeParse("c");
|
||||
expect(enumResult.success).toBe(false);
|
||||
if (!enumResult.success) {
|
||||
expect(enumResult.error.issues[0].message).toBe('Neispravna opcija: očekivano jedno od "a"|"b"');
|
||||
}
|
||||
|
||||
// Test not_multiple_of
|
||||
const multipleSchema = z.number().multipleOf(3);
|
||||
const multipleResult = multipleSchema.safeParse(10);
|
||||
expect(multipleResult.success).toBe(false);
|
||||
if (!multipleResult.success) {
|
||||
expect(multipleResult.error.issues[0].message).toBe("Neispravan broj: mora biti višekratnik od 3");
|
||||
}
|
||||
|
||||
// Test unrecognized_keys
|
||||
const strictSchema = z.object({ a: z.string() }).strict();
|
||||
const strictResult = strictSchema.safeParse({ a: "test", b: "extra" });
|
||||
expect(strictResult.success).toBe(false);
|
||||
if (!strictResult.success) {
|
||||
expect(strictResult.error.issues[0].message).toBe('Neprepoznat ključ: "b"');
|
||||
}
|
||||
|
||||
// Test invalid_union
|
||||
const unionSchema = z.union([z.string(), z.number()]);
|
||||
const unionResult = unionSchema.safeParse(true);
|
||||
expect(unionResult.success).toBe(false);
|
||||
if (!unionResult.success) {
|
||||
expect(unionResult.error.issues[0].message).toBe("Neispravan unos");
|
||||
}
|
||||
|
||||
// Test invalid_format with regex
|
||||
const regexSchema = z.string().regex(/^[a-z]+$/);
|
||||
const regexResult = regexSchema.safeParse("ABC123");
|
||||
expect(regexResult.success).toBe(false);
|
||||
if (!regexResult.success) {
|
||||
expect(regexResult.error.issues[0].message).toBe("Neispravan tekst: mora odgovarati uzorku /^[a-z]+$/");
|
||||
}
|
||||
|
||||
// Test invalid_format with startsWith
|
||||
const startsWithSchema = z.string().startsWith("hello");
|
||||
const startsWithResult = startsWithSchema.safeParse("world");
|
||||
expect(startsWithResult.success).toBe(false);
|
||||
if (!startsWithResult.success) {
|
||||
expect(startsWithResult.error.issues[0].message).toBe('Neispravan tekst: mora započinjati s "hello"');
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
export default /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/i;
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,6 @@
|
||||
import type { TSESLint } from '@typescript-eslint/utils';
|
||||
export type MessageId = 'noArrayDelete' | 'useSplice';
|
||||
declare const _default: TSESLint.RuleModule<MessageId, [], import("../../rules").ESLintPluginDocs, TSESLint.RuleListener> & {
|
||||
name: string;
|
||||
};
|
||||
export default _default;
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "isexe",
|
||||
"version": "2.0.0",
|
||||
"description": "Minimal module to check if a file is executable.",
|
||||
"main": "index.js",
|
||||
"directories": {
|
||||
"test": "test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mkdirp": "^0.5.1",
|
||||
"rimraf": "^2.5.0",
|
||||
"tap": "^10.3.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "tap test/*.js --100",
|
||||
"preversion": "npm test",
|
||||
"postversion": "npm publish",
|
||||
"postpublish": "git push origin --all; git push origin --tags"
|
||||
},
|
||||
"author": "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me/)",
|
||||
"license": "ISC",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/isaacs/isexe.git"
|
||||
},
|
||||
"keywords": [],
|
||||
"bugs": {
|
||||
"url": "https://github.com/isaacs/isexe/issues"
|
||||
},
|
||||
"homepage": "https://github.com/isaacs/isexe#readme"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { _ as default } from "../esm/_array_like_to_array.js";
|
||||
@@ -0,0 +1,4 @@
|
||||
function _class_private_method_set() {
|
||||
throw new TypeError("attempted to reassign private method");
|
||||
}
|
||||
export { _class_private_method_set as _ };
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
export * from './dist/reporters.js'
|
||||
@@ -0,0 +1,138 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.default = default_1;
|
||||
const util = __importStar(require("../core/util.cjs"));
|
||||
const error = () => {
|
||||
const Sizable = {
|
||||
string: { unit: "znaků", verb: "mít" },
|
||||
file: { unit: "bajtů", verb: "mít" },
|
||||
array: { unit: "prvků", verb: "mít" },
|
||||
set: { unit: "prvků", verb: "mít" },
|
||||
};
|
||||
function getSizing(origin) {
|
||||
return Sizable[origin] ?? null;
|
||||
}
|
||||
const FormatDictionary = {
|
||||
regex: "regulární výraz",
|
||||
email: "e-mailová adresa",
|
||||
url: "URL",
|
||||
emoji: "emoji",
|
||||
uuid: "UUID",
|
||||
uuidv4: "UUIDv4",
|
||||
uuidv6: "UUIDv6",
|
||||
nanoid: "nanoid",
|
||||
guid: "GUID",
|
||||
cuid: "cuid",
|
||||
cuid2: "cuid2",
|
||||
ulid: "ULID",
|
||||
xid: "XID",
|
||||
ksuid: "KSUID",
|
||||
datetime: "datum a čas ve formátu ISO",
|
||||
date: "datum ve formátu ISO",
|
||||
time: "čas ve formátu ISO",
|
||||
duration: "doba trvání ISO",
|
||||
ipv4: "IPv4 adresa",
|
||||
ipv6: "IPv6 adresa",
|
||||
cidrv4: "rozsah IPv4",
|
||||
cidrv6: "rozsah IPv6",
|
||||
base64: "řetězec zakódovaný ve formátu base64",
|
||||
base64url: "řetězec zakódovaný ve formátu base64url",
|
||||
json_string: "řetězec ve formátu JSON",
|
||||
e164: "číslo E.164",
|
||||
jwt: "JWT",
|
||||
template_literal: "vstup",
|
||||
};
|
||||
const TypeDictionary = {
|
||||
nan: "NaN",
|
||||
number: "číslo",
|
||||
string: "řetězec",
|
||||
function: "funkce",
|
||||
array: "pole",
|
||||
};
|
||||
return (issue) => {
|
||||
switch (issue.code) {
|
||||
case "invalid_type": {
|
||||
const expected = TypeDictionary[issue.expected] ?? issue.expected;
|
||||
const receivedType = util.parsedType(issue.input);
|
||||
const received = TypeDictionary[receivedType] ?? receivedType;
|
||||
if (/^[A-Z]/.test(issue.expected)) {
|
||||
return `Neplatný vstup: očekáváno instanceof ${issue.expected}, obdrženo ${received}`;
|
||||
}
|
||||
return `Neplatný vstup: očekáváno ${expected}, obdrženo ${received}`;
|
||||
}
|
||||
case "invalid_value":
|
||||
if (issue.values.length === 1)
|
||||
return `Neplatný vstup: očekáváno ${util.stringifyPrimitive(issue.values[0])}`;
|
||||
return `Neplatná možnost: očekávána jedna z hodnot ${util.joinValues(issue.values, "|")}`;
|
||||
case "too_big": {
|
||||
const adj = issue.inclusive ? "<=" : "<";
|
||||
const sizing = getSizing(issue.origin);
|
||||
if (sizing) {
|
||||
return `Hodnota je příliš velká: ${issue.origin ?? "hodnota"} musí mít ${adj}${issue.maximum.toString()} ${sizing.unit ?? "prvků"}`;
|
||||
}
|
||||
return `Hodnota je příliš velká: ${issue.origin ?? "hodnota"} musí být ${adj}${issue.maximum.toString()}`;
|
||||
}
|
||||
case "too_small": {
|
||||
const adj = issue.inclusive ? ">=" : ">";
|
||||
const sizing = getSizing(issue.origin);
|
||||
if (sizing) {
|
||||
return `Hodnota je příliš malá: ${issue.origin ?? "hodnota"} musí mít ${adj}${issue.minimum.toString()} ${sizing.unit ?? "prvků"}`;
|
||||
}
|
||||
return `Hodnota je příliš malá: ${issue.origin ?? "hodnota"} musí být ${adj}${issue.minimum.toString()}`;
|
||||
}
|
||||
case "invalid_format": {
|
||||
const _issue = issue;
|
||||
if (_issue.format === "starts_with")
|
||||
return `Neplatný řetězec: musí začínat na "${_issue.prefix}"`;
|
||||
if (_issue.format === "ends_with")
|
||||
return `Neplatný řetězec: musí končit na "${_issue.suffix}"`;
|
||||
if (_issue.format === "includes")
|
||||
return `Neplatný řetězec: musí obsahovat "${_issue.includes}"`;
|
||||
if (_issue.format === "regex")
|
||||
return `Neplatný řetězec: musí odpovídat vzoru ${_issue.pattern}`;
|
||||
return `Neplatný formát ${FormatDictionary[_issue.format] ?? issue.format}`;
|
||||
}
|
||||
case "not_multiple_of":
|
||||
return `Neplatné číslo: musí být násobkem ${issue.divisor}`;
|
||||
case "unrecognized_keys":
|
||||
return `Neznámé klíče: ${util.joinValues(issue.keys, ", ")}`;
|
||||
case "invalid_key":
|
||||
return `Neplatný klíč v ${issue.origin}`;
|
||||
case "invalid_union":
|
||||
return "Neplatný vstup";
|
||||
case "invalid_element":
|
||||
return `Neplatná hodnota v ${issue.origin}`;
|
||||
default:
|
||||
return `Neplatný vstup`;
|
||||
}
|
||||
};
|
||||
};
|
||||
function default_1() {
|
||||
return {
|
||||
localeError: error(),
|
||||
};
|
||||
}
|
||||
module.exports = exports.default;
|
||||
@@ -0,0 +1,32 @@
|
||||
export declare const MSGPACK_FIXARRAY3 = 147;
|
||||
export declare const MSGPACK_BIN8 = 196;
|
||||
export declare const MSGPACK_BIN16 = 197;
|
||||
export declare const MSGPACK_BIN32 = 198;
|
||||
export declare const MSGPACK_UINT8 = 204;
|
||||
/** Compute the MessagePack bin header size for a given data length. */
|
||||
export declare function binHeaderSize(len: number): number;
|
||||
/** Write a MessagePack bin header into `buf` at `off`, return new offset. */
|
||||
export declare function writeBinHeader(buf: Uint8Array, off: number, len: number): number;
|
||||
export declare class MsgpackWriter {
|
||||
private buf;
|
||||
private view;
|
||||
private pos;
|
||||
constructor(initialSize?: number);
|
||||
private ensure;
|
||||
writeArrayHeader(length: number): void;
|
||||
writeUint(value: number): void;
|
||||
writeString(str: string): void;
|
||||
writeBool(value: boolean): void;
|
||||
finish(): Uint8Array;
|
||||
}
|
||||
export declare class MsgpackReader {
|
||||
private buf;
|
||||
private view;
|
||||
private pos;
|
||||
constructor(data: Uint8Array, offset?: number);
|
||||
readArrayHeader(): number;
|
||||
readUint(): number;
|
||||
readString(): string;
|
||||
readBool(): boolean;
|
||||
}
|
||||
//# sourceMappingURL=msgpack.d.ts.map
|
||||
@@ -0,0 +1,6 @@
|
||||
"use strict";
|
||||
|
||||
function _await_value(value) {
|
||||
this.wrapped = value;
|
||||
}
|
||||
exports._ = _await_value;
|
||||
Reference in New Issue
Block a user