WIP: bootstrap and partial real Solana watcher implementation
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
|
||||
1.2.1 / 2017-05-19
|
||||
==================
|
||||
|
||||
* fix: package.json to reduce vulnerabilities (#3)
|
||||
|
||||
1.2.0 / 2016-05-21
|
||||
==================
|
||||
|
||||
* feat: warn with stack
|
||||
|
||||
1.1.0 / 2016-04-04
|
||||
==================
|
||||
|
||||
* deps: upgrade ms to 0.7.0
|
||||
|
||||
1.0.1 / 2014-12-31
|
||||
==================
|
||||
|
||||
* feat(index.js): warn when result is undefined
|
||||
|
||||
1.0.0 / 2014-08-14
|
||||
==================
|
||||
|
||||
* init
|
||||
@@ -0,0 +1,133 @@
|
||||
import {Connection} from './connection';
|
||||
import {TransactionSignature} from './transaction';
|
||||
|
||||
export class SendTransactionError extends Error {
|
||||
private signature: TransactionSignature;
|
||||
private transactionMessage: string;
|
||||
private transactionLogs: string[] | Promise<string[]> | undefined;
|
||||
|
||||
constructor({
|
||||
action,
|
||||
signature,
|
||||
transactionMessage,
|
||||
logs,
|
||||
}: {
|
||||
action: 'send' | 'simulate';
|
||||
signature: TransactionSignature;
|
||||
transactionMessage: string;
|
||||
logs?: string[];
|
||||
}) {
|
||||
const maybeLogsOutput = logs
|
||||
? `Logs: \n${JSON.stringify(logs.slice(-10), null, 2)}. `
|
||||
: '';
|
||||
const guideText =
|
||||
'\nCatch the `SendTransactionError` and call `getLogs()` on it for full details.';
|
||||
let message: string;
|
||||
switch (action) {
|
||||
case 'send':
|
||||
message =
|
||||
`Transaction ${signature} resulted in an error. \n` +
|
||||
`${transactionMessage}. ` +
|
||||
maybeLogsOutput +
|
||||
guideText;
|
||||
break;
|
||||
case 'simulate':
|
||||
message =
|
||||
`Simulation failed. \nMessage: ${transactionMessage}. \n` +
|
||||
maybeLogsOutput +
|
||||
guideText;
|
||||
break;
|
||||
default: {
|
||||
message = `Unknown action '${((a: never) => a)(action)}'`;
|
||||
}
|
||||
}
|
||||
super(message);
|
||||
|
||||
this.signature = signature;
|
||||
this.transactionMessage = transactionMessage;
|
||||
this.transactionLogs = logs ? logs : undefined;
|
||||
}
|
||||
|
||||
get transactionError(): {message: string; logs?: string[]} {
|
||||
return {
|
||||
message: this.transactionMessage,
|
||||
logs: Array.isArray(this.transactionLogs)
|
||||
? this.transactionLogs
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
/* @deprecated Use `await getLogs()` instead */
|
||||
get logs(): string[] | undefined {
|
||||
const cachedLogs = this.transactionLogs;
|
||||
if (
|
||||
cachedLogs != null &&
|
||||
typeof cachedLogs === 'object' &&
|
||||
'then' in cachedLogs
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
return cachedLogs;
|
||||
}
|
||||
|
||||
async getLogs(connection: Connection): Promise<string[]> {
|
||||
if (!Array.isArray(this.transactionLogs)) {
|
||||
this.transactionLogs = new Promise((resolve, reject) => {
|
||||
connection
|
||||
.getTransaction(this.signature)
|
||||
.then(tx => {
|
||||
if (tx && tx.meta && tx.meta.logMessages) {
|
||||
const logs = tx.meta.logMessages;
|
||||
this.transactionLogs = logs;
|
||||
resolve(logs);
|
||||
} else {
|
||||
reject(new Error('Log messages not found'));
|
||||
}
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
return await this.transactionLogs;
|
||||
}
|
||||
}
|
||||
|
||||
// Keep in sync with client/src/rpc_custom_errors.rs
|
||||
// Typescript `enums` thwart tree-shaking. See https://bargsten.org/jsts/enums/
|
||||
export const SolanaJSONRPCErrorCode = {
|
||||
JSON_RPC_SERVER_ERROR_BLOCK_CLEANED_UP: -32001,
|
||||
JSON_RPC_SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE: -32002,
|
||||
JSON_RPC_SERVER_ERROR_TRANSACTION_SIGNATURE_VERIFICATION_FAILURE: -32003,
|
||||
JSON_RPC_SERVER_ERROR_BLOCK_NOT_AVAILABLE: -32004,
|
||||
JSON_RPC_SERVER_ERROR_NODE_UNHEALTHY: -32005,
|
||||
JSON_RPC_SERVER_ERROR_TRANSACTION_PRECOMPILE_VERIFICATION_FAILURE: -32006,
|
||||
JSON_RPC_SERVER_ERROR_SLOT_SKIPPED: -32007,
|
||||
JSON_RPC_SERVER_ERROR_NO_SNAPSHOT: -32008,
|
||||
JSON_RPC_SERVER_ERROR_LONG_TERM_STORAGE_SLOT_SKIPPED: -32009,
|
||||
JSON_RPC_SERVER_ERROR_KEY_EXCLUDED_FROM_SECONDARY_INDEX: -32010,
|
||||
JSON_RPC_SERVER_ERROR_TRANSACTION_HISTORY_NOT_AVAILABLE: -32011,
|
||||
JSON_RPC_SCAN_ERROR: -32012,
|
||||
JSON_RPC_SERVER_ERROR_TRANSACTION_SIGNATURE_LEN_MISMATCH: -32013,
|
||||
JSON_RPC_SERVER_ERROR_BLOCK_STATUS_NOT_AVAILABLE_YET: -32014,
|
||||
JSON_RPC_SERVER_ERROR_UNSUPPORTED_TRANSACTION_VERSION: -32015,
|
||||
JSON_RPC_SERVER_ERROR_MIN_CONTEXT_SLOT_NOT_REACHED: -32016,
|
||||
} as const;
|
||||
export type SolanaJSONRPCErrorCodeEnum =
|
||||
(typeof SolanaJSONRPCErrorCode)[keyof typeof SolanaJSONRPCErrorCode];
|
||||
|
||||
export class SolanaJSONRPCError extends Error {
|
||||
code: SolanaJSONRPCErrorCodeEnum | unknown;
|
||||
data?: any;
|
||||
constructor(
|
||||
{
|
||||
code,
|
||||
message,
|
||||
data,
|
||||
}: Readonly<{code: unknown; message: string; data?: any}>,
|
||||
customMessage?: string,
|
||||
) {
|
||||
super(customMessage != null ? `${customMessage}: ${message}` : message);
|
||||
this.code = code;
|
||||
this.data = data;
|
||||
this.name = 'SolanaJSONRPCError';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Audited & minimal JS implementation of elliptic curve cryptography.
|
||||
* @module
|
||||
* @example
|
||||
```js
|
||||
import { secp256k1, schnorr } from '@noble/curves/secp256k1.js';
|
||||
import { ed25519, ed25519ph, ed25519ctx, x25519, RistrettoPoint } from '@noble/curves/ed25519.js';
|
||||
import { ed448, ed448ph, ed448ctx, x448 } from '@noble/curves/ed448.js';
|
||||
import { p256, p384, p521 } from '@noble/curves/nist.js';
|
||||
import { bls12_381 } from '@noble/curves/bls12-381.js';
|
||||
import { bn254 } from '@noble/curves/bn254.js';
|
||||
import { bytesToHex, hexToBytes, concatBytes, utf8ToBytes } from '@noble/curves/abstract/utils.js';
|
||||
```
|
||||
*/
|
||||
throw new Error('root module cannot be imported: import submodules instead. Check out README');
|
||||
export {};
|
||||
//# sourceMappingURL=index.js.map
|
||||
@@ -0,0 +1,50 @@
|
||||
import * as core from "../core/index.js";
|
||||
import type { ZodType } from "./schemas.js";
|
||||
export type {
|
||||
/** @deprecated Use `z.output<T>` instead. */
|
||||
output as TypeOf,
|
||||
/** @deprecated Use `z.output<T>` instead. */
|
||||
output as Infer,
|
||||
/** @deprecated Use `z.core.$$ZodFirstPartyTypes` instead */
|
||||
$ZodTypes as ZodFirstPartySchemaTypes, } from "../core/index.js";
|
||||
/** @deprecated Use the raw string literal codes instead, e.g. "invalid_type". */
|
||||
export declare const ZodIssueCode: {
|
||||
readonly invalid_type: "invalid_type";
|
||||
readonly too_big: "too_big";
|
||||
readonly too_small: "too_small";
|
||||
readonly invalid_format: "invalid_format";
|
||||
readonly not_multiple_of: "not_multiple_of";
|
||||
readonly unrecognized_keys: "unrecognized_keys";
|
||||
readonly invalid_union: "invalid_union";
|
||||
readonly invalid_key: "invalid_key";
|
||||
readonly invalid_element: "invalid_element";
|
||||
readonly invalid_value: "invalid_value";
|
||||
readonly custom: "custom";
|
||||
};
|
||||
/** @deprecated Use `z.$ZodFlattenedError` */
|
||||
export type inferFlattenedErrors<T extends core.$ZodType, U = string> = core.$ZodFlattenedError<core.output<T>, U>;
|
||||
/** @deprecated Use `z.$ZodFormattedError` */
|
||||
export type inferFormattedError<T extends core.$ZodType<any, any>, U = string> = core.$ZodFormattedError<core.output<T>, U>;
|
||||
/** Use `z.$brand` instead */
|
||||
export type BRAND<T extends string | number | symbol = string | number | symbol> = {
|
||||
[core.$brand]: {
|
||||
[k in T]: true;
|
||||
};
|
||||
};
|
||||
export { $brand, config } from "../core/index.js";
|
||||
/** @deprecated Use `z.config(params)` instead. */
|
||||
export declare function setErrorMap(map: core.$ZodErrorMap): void;
|
||||
/** @deprecated Use `z.config()` instead. */
|
||||
export declare function getErrorMap(): core.$ZodErrorMap<core.$ZodIssue> | undefined;
|
||||
export type {
|
||||
/** @deprecated Use z.ZodType (without generics) instead. */
|
||||
ZodType as ZodTypeAny,
|
||||
/** @deprecated Use `z.ZodType` */
|
||||
ZodType as ZodSchema,
|
||||
/** @deprecated Use `z.ZodType` */
|
||||
ZodType as Schema, };
|
||||
/** Included for Zod 3 compatibility */
|
||||
export type ZodRawShape = core.$ZodShape;
|
||||
/** @deprecated Do not use. Stub definition, only included for zod-to-json-schema compatibility. */
|
||||
export declare enum ZodFirstPartyTypeKind {
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- 0.6
|
||||
- 0.8
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* @fileoverview SourceCodeVisitor class
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
const emptyArray = Object.freeze([]);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Exports
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* A structure to hold a list of functions to call for a given name.
|
||||
* This is used to allow multiple rules to register functions for a given name
|
||||
* without having to know about each other.
|
||||
*/
|
||||
class SourceCodeVisitor {
|
||||
/**
|
||||
* The functions to call for a given name.
|
||||
* @type {Map<string, Function[]>}
|
||||
*/
|
||||
#functions = new Map();
|
||||
|
||||
/**
|
||||
* Adds a function to the list of functions to call for a given name.
|
||||
* @param {string} name The name of the function to call.
|
||||
* @param {Function} func The function to call.
|
||||
* @returns {void}
|
||||
*/
|
||||
add(name, func) {
|
||||
if (this.#functions.has(name)) {
|
||||
this.#functions.get(name).push(func);
|
||||
} else {
|
||||
this.#functions.set(name, [func]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of functions to call for a given name.
|
||||
* @param {string} name The name of the function to call.
|
||||
* @returns {Function[]} The list of functions to call.
|
||||
*/
|
||||
get(name) {
|
||||
if (this.#functions.has(name)) {
|
||||
return this.#functions.get(name);
|
||||
}
|
||||
|
||||
return emptyArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates over all names and calls the callback with the name.
|
||||
* @param {(name:string) => void} callback The callback to call for each name.
|
||||
* @returns {void}
|
||||
*/
|
||||
forEachName(callback) {
|
||||
this.#functions.forEach((funcs, name) => {
|
||||
callback(name);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the functions for a given name with the given arguments.
|
||||
* @param {string} name The name of the function to call.
|
||||
* @param {any[]} args The arguments to pass to the function.
|
||||
* @returns {void}
|
||||
*/
|
||||
callSync(name, ...args) {
|
||||
if (this.#functions.has(name)) {
|
||||
this.#functions.get(name).forEach(func => func(...args));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { SourceCodeVisitor };
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"for + if": {
|
||||
"name": "for + if",
|
||||
"browser": "Edge 14.14393.0 (Windows 10 0.0.0)",
|
||||
"suite": "itar-short",
|
||||
"hz": 258326.20076670017,
|
||||
"success": true,
|
||||
"fastest": true,
|
||||
"rme": 0.01562542930728137,
|
||||
"rhz": 1,
|
||||
"sampleSize": 210
|
||||
},
|
||||
"while + if": {
|
||||
"name": "while + if",
|
||||
"browser": "Edge 14.14393.0 (Windows 10 0.0.0)",
|
||||
"suite": "itar-short",
|
||||
"hz": 250131.81743630744,
|
||||
"success": true,
|
||||
"fastest": false,
|
||||
"rme": 0.015426062316980953,
|
||||
"rhz": 0.9682789306463216,
|
||||
"sampleSize": 210
|
||||
},
|
||||
"array join": {
|
||||
"name": "array join",
|
||||
"browser": "Edge 14.14393.0 (Windows 10 0.0.0)",
|
||||
"suite": "itar-short",
|
||||
"hz": 257660.10445245806,
|
||||
"success": true,
|
||||
"fastest": true,
|
||||
"rme": 0.012689221094028156,
|
||||
"rhz": 0.9974214914620926,
|
||||
"sampleSize": 212
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,461 @@
|
||||
/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^net|tls|https$" }] */
|
||||
|
||||
'use strict';
|
||||
|
||||
const EventEmitter = require('events');
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
const net = require('net');
|
||||
const tls = require('tls');
|
||||
const { createHash } = require('crypto');
|
||||
|
||||
const PerMessageDeflate = require('./permessage-deflate');
|
||||
const WebSocket = require('./websocket');
|
||||
const { format, parse } = require('./extension');
|
||||
const { GUID, kWebSocket } = require('./constants');
|
||||
|
||||
const keyRegex = /^[+/0-9A-Za-z]{22}==$/;
|
||||
|
||||
const RUNNING = 0;
|
||||
const CLOSING = 1;
|
||||
const CLOSED = 2;
|
||||
|
||||
/**
|
||||
* Class representing a WebSocket server.
|
||||
*
|
||||
* @extends EventEmitter
|
||||
*/
|
||||
class WebSocketServer extends EventEmitter {
|
||||
/**
|
||||
* Create a `WebSocketServer` instance.
|
||||
*
|
||||
* @param {Object} options Configuration options
|
||||
* @param {Number} [options.backlog=511] The maximum length of the queue of
|
||||
* pending connections
|
||||
* @param {Boolean} [options.clientTracking=true] Specifies whether or not to
|
||||
* track clients
|
||||
* @param {Function} [options.handleProtocols] A hook to handle protocols
|
||||
* @param {String} [options.host] The hostname where to bind the server
|
||||
* @param {Number} [options.maxBufferedChunks=262144] The maximum number of
|
||||
* buffered data chunks
|
||||
* @param {Number} [options.maxFragments=16384] The maximum number of message
|
||||
* fragments
|
||||
* @param {Number} [options.maxPayload=104857600] The maximum allowed message
|
||||
* size
|
||||
* @param {Boolean} [options.noServer=false] Enable no server mode
|
||||
* @param {String} [options.path] Accept only connections matching this path
|
||||
* @param {(Boolean|Object)} [options.perMessageDeflate=false] Enable/disable
|
||||
* permessage-deflate
|
||||
* @param {Number} [options.port] The port where to bind the server
|
||||
* @param {(http.Server|https.Server)} [options.server] A pre-created HTTP/S
|
||||
* server to use
|
||||
* @param {Function} [options.verifyClient] A hook to reject connections
|
||||
* @param {Function} [callback] A listener for the `listening` event
|
||||
*/
|
||||
constructor(options, callback) {
|
||||
super();
|
||||
|
||||
options = {
|
||||
maxBufferedChunks: 256 * 1024,
|
||||
maxFragments: 16 * 1024,
|
||||
maxPayload: 100 * 1024 * 1024,
|
||||
perMessageDeflate: false,
|
||||
handleProtocols: null,
|
||||
clientTracking: true,
|
||||
verifyClient: null,
|
||||
noServer: false,
|
||||
backlog: null, // use default (511 as implemented in net.js)
|
||||
server: null,
|
||||
host: null,
|
||||
path: null,
|
||||
port: null,
|
||||
...options
|
||||
};
|
||||
|
||||
if (
|
||||
(options.port == null && !options.server && !options.noServer) ||
|
||||
(options.port != null && (options.server || options.noServer)) ||
|
||||
(options.server && options.noServer)
|
||||
) {
|
||||
throw new TypeError(
|
||||
'One and only one of the "port", "server", or "noServer" options ' +
|
||||
'must be specified'
|
||||
);
|
||||
}
|
||||
|
||||
if (options.port != null) {
|
||||
this._server = http.createServer((req, res) => {
|
||||
const body = http.STATUS_CODES[426];
|
||||
|
||||
res.writeHead(426, {
|
||||
'Content-Length': body.length,
|
||||
'Content-Type': 'text/plain'
|
||||
});
|
||||
res.end(body);
|
||||
});
|
||||
this._server.listen(
|
||||
options.port,
|
||||
options.host,
|
||||
options.backlog,
|
||||
callback
|
||||
);
|
||||
} else if (options.server) {
|
||||
this._server = options.server;
|
||||
}
|
||||
|
||||
if (this._server) {
|
||||
const emitConnection = this.emit.bind(this, 'connection');
|
||||
|
||||
this._removeListeners = addListeners(this._server, {
|
||||
listening: this.emit.bind(this, 'listening'),
|
||||
error: this.emit.bind(this, 'error'),
|
||||
upgrade: (req, socket, head) => {
|
||||
this.handleUpgrade(req, socket, head, emitConnection);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (options.perMessageDeflate === true) options.perMessageDeflate = {};
|
||||
if (options.clientTracking) this.clients = new Set();
|
||||
this.options = options;
|
||||
this._state = RUNNING;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bound address, the address family name, and port of the server
|
||||
* as reported by the operating system if listening on an IP socket.
|
||||
* If the server is listening on a pipe or UNIX domain socket, the name is
|
||||
* returned as a string.
|
||||
*
|
||||
* @return {(Object|String|null)} The address of the server
|
||||
* @public
|
||||
*/
|
||||
address() {
|
||||
if (this.options.noServer) {
|
||||
throw new Error('The server is operating in "noServer" mode');
|
||||
}
|
||||
|
||||
if (!this._server) return null;
|
||||
return this._server.address();
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the server.
|
||||
*
|
||||
* @param {Function} [cb] Callback
|
||||
* @public
|
||||
*/
|
||||
close(cb) {
|
||||
if (cb) this.once('close', cb);
|
||||
|
||||
if (this._state === CLOSED) {
|
||||
process.nextTick(emitClose, this);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._state === CLOSING) return;
|
||||
this._state = CLOSING;
|
||||
|
||||
//
|
||||
// Terminate all associated clients.
|
||||
//
|
||||
if (this.clients) {
|
||||
for (const client of this.clients) client.terminate();
|
||||
}
|
||||
|
||||
const server = this._server;
|
||||
|
||||
if (server) {
|
||||
this._removeListeners();
|
||||
this._removeListeners = this._server = null;
|
||||
|
||||
//
|
||||
// Close the http server if it was internally created.
|
||||
//
|
||||
if (this.options.port != null) {
|
||||
server.close(emitClose.bind(undefined, this));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
process.nextTick(emitClose, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* See if a given request should be handled by this server instance.
|
||||
*
|
||||
* @param {http.IncomingMessage} req Request object to inspect
|
||||
* @return {Boolean} `true` if the request is valid, else `false`
|
||||
* @public
|
||||
*/
|
||||
shouldHandle(req) {
|
||||
if (this.options.path) {
|
||||
const index = req.url.indexOf('?');
|
||||
const pathname = index !== -1 ? req.url.slice(0, index) : req.url;
|
||||
|
||||
if (pathname !== this.options.path) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a HTTP Upgrade request.
|
||||
*
|
||||
* @param {http.IncomingMessage} req The request object
|
||||
* @param {(net.Socket|tls.Socket)} socket The network socket between the
|
||||
* server and client
|
||||
* @param {Buffer} head The first packet of the upgraded stream
|
||||
* @param {Function} cb Callback
|
||||
* @public
|
||||
*/
|
||||
handleUpgrade(req, socket, head, cb) {
|
||||
socket.on('error', socketOnError);
|
||||
|
||||
const key =
|
||||
req.headers['sec-websocket-key'] !== undefined
|
||||
? req.headers['sec-websocket-key'].trim()
|
||||
: false;
|
||||
const upgrade = req.headers.upgrade;
|
||||
const version = +req.headers['sec-websocket-version'];
|
||||
const extensions = {};
|
||||
|
||||
if (
|
||||
req.method !== 'GET' ||
|
||||
upgrade === undefined ||
|
||||
upgrade.toLowerCase() !== 'websocket' ||
|
||||
!key ||
|
||||
!keyRegex.test(key) ||
|
||||
(version !== 8 && version !== 13) ||
|
||||
!this.shouldHandle(req)
|
||||
) {
|
||||
return abortHandshake(socket, 400);
|
||||
}
|
||||
|
||||
if (this.options.perMessageDeflate) {
|
||||
const perMessageDeflate = new PerMessageDeflate(
|
||||
this.options.perMessageDeflate,
|
||||
true,
|
||||
this.options.maxPayload
|
||||
);
|
||||
|
||||
try {
|
||||
const offers = parse(req.headers['sec-websocket-extensions']);
|
||||
|
||||
if (offers[PerMessageDeflate.extensionName]) {
|
||||
perMessageDeflate.accept(offers[PerMessageDeflate.extensionName]);
|
||||
extensions[PerMessageDeflate.extensionName] = perMessageDeflate;
|
||||
}
|
||||
} catch (err) {
|
||||
return abortHandshake(socket, 400);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Optionally call external client verification handler.
|
||||
//
|
||||
if (this.options.verifyClient) {
|
||||
const info = {
|
||||
origin:
|
||||
req.headers[`${version === 8 ? 'sec-websocket-origin' : 'origin'}`],
|
||||
secure: !!(req.socket.authorized || req.socket.encrypted),
|
||||
req
|
||||
};
|
||||
|
||||
if (this.options.verifyClient.length === 2) {
|
||||
this.options.verifyClient(info, (verified, code, message, headers) => {
|
||||
if (!verified) {
|
||||
return abortHandshake(socket, code || 401, message, headers);
|
||||
}
|
||||
|
||||
this.completeUpgrade(key, extensions, req, socket, head, cb);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.options.verifyClient(info)) return abortHandshake(socket, 401);
|
||||
}
|
||||
|
||||
this.completeUpgrade(key, extensions, req, socket, head, cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Upgrade the connection to WebSocket.
|
||||
*
|
||||
* @param {String} key The value of the `Sec-WebSocket-Key` header
|
||||
* @param {Object} extensions The accepted extensions
|
||||
* @param {http.IncomingMessage} req The request object
|
||||
* @param {(net.Socket|tls.Socket)} socket The network socket between the
|
||||
* server and client
|
||||
* @param {Buffer} head The first packet of the upgraded stream
|
||||
* @param {Function} cb Callback
|
||||
* @throws {Error} If called more than once with the same socket
|
||||
* @private
|
||||
*/
|
||||
completeUpgrade(key, extensions, req, socket, head, cb) {
|
||||
//
|
||||
// Destroy the socket if the client has already sent a FIN packet.
|
||||
//
|
||||
if (!socket.readable || !socket.writable) return socket.destroy();
|
||||
|
||||
if (socket[kWebSocket]) {
|
||||
throw new Error(
|
||||
'server.handleUpgrade() was called more than once with the same ' +
|
||||
'socket, possibly due to a misconfiguration'
|
||||
);
|
||||
}
|
||||
|
||||
if (this._state > RUNNING) return abortHandshake(socket, 503);
|
||||
|
||||
const digest = createHash('sha1')
|
||||
.update(key + GUID)
|
||||
.digest('base64');
|
||||
|
||||
const headers = [
|
||||
'HTTP/1.1 101 Switching Protocols',
|
||||
'Upgrade: websocket',
|
||||
'Connection: Upgrade',
|
||||
`Sec-WebSocket-Accept: ${digest}`
|
||||
];
|
||||
|
||||
const ws = new WebSocket(null);
|
||||
let protocol = req.headers['sec-websocket-protocol'];
|
||||
|
||||
if (protocol) {
|
||||
protocol = protocol.split(',').map(trim);
|
||||
|
||||
//
|
||||
// Optionally call external protocol selection handler.
|
||||
//
|
||||
if (this.options.handleProtocols) {
|
||||
protocol = this.options.handleProtocols(protocol, req);
|
||||
} else {
|
||||
protocol = protocol[0];
|
||||
}
|
||||
|
||||
if (protocol) {
|
||||
headers.push(`Sec-WebSocket-Protocol: ${protocol}`);
|
||||
ws._protocol = protocol;
|
||||
}
|
||||
}
|
||||
|
||||
if (extensions[PerMessageDeflate.extensionName]) {
|
||||
const params = extensions[PerMessageDeflate.extensionName].params;
|
||||
const value = format({
|
||||
[PerMessageDeflate.extensionName]: [params]
|
||||
});
|
||||
headers.push(`Sec-WebSocket-Extensions: ${value}`);
|
||||
ws._extensions = extensions;
|
||||
}
|
||||
|
||||
//
|
||||
// Allow external modification/inspection of handshake headers.
|
||||
//
|
||||
this.emit('headers', headers, req);
|
||||
|
||||
socket.write(headers.concat('\r\n').join('\r\n'));
|
||||
socket.removeListener('error', socketOnError);
|
||||
|
||||
ws.setSocket(
|
||||
socket,
|
||||
head,
|
||||
this.options.maxPayload,
|
||||
this.options.maxBufferedChunks,
|
||||
this.options.maxFragments
|
||||
);
|
||||
|
||||
if (this.clients) {
|
||||
this.clients.add(ws);
|
||||
ws.on('close', () => this.clients.delete(ws));
|
||||
}
|
||||
|
||||
cb(ws, req);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WebSocketServer;
|
||||
|
||||
/**
|
||||
* Add event listeners on an `EventEmitter` using a map of <event, listener>
|
||||
* pairs.
|
||||
*
|
||||
* @param {EventEmitter} server The event emitter
|
||||
* @param {Object.<String, Function>} map The listeners to add
|
||||
* @return {Function} A function that will remove the added listeners when
|
||||
* called
|
||||
* @private
|
||||
*/
|
||||
function addListeners(server, map) {
|
||||
for (const event of Object.keys(map)) server.on(event, map[event]);
|
||||
|
||||
return function removeListeners() {
|
||||
for (const event of Object.keys(map)) {
|
||||
server.removeListener(event, map[event]);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit a `'close'` event on an `EventEmitter`.
|
||||
*
|
||||
* @param {EventEmitter} server The event emitter
|
||||
* @private
|
||||
*/
|
||||
function emitClose(server) {
|
||||
server._state = CLOSED;
|
||||
server.emit('close');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle premature socket errors.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function socketOnError() {
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the connection when preconditions are not fulfilled.
|
||||
*
|
||||
* @param {(net.Socket|tls.Socket)} socket The socket of the upgrade request
|
||||
* @param {Number} code The HTTP response status code
|
||||
* @param {String} [message] The HTTP response body
|
||||
* @param {Object} [headers] Additional HTTP response headers
|
||||
* @private
|
||||
*/
|
||||
function abortHandshake(socket, code, message, headers) {
|
||||
if (socket.writable) {
|
||||
message = message || http.STATUS_CODES[code];
|
||||
headers = {
|
||||
Connection: 'close',
|
||||
'Content-Type': 'text/html',
|
||||
'Content-Length': Buffer.byteLength(message),
|
||||
...headers
|
||||
};
|
||||
|
||||
socket.write(
|
||||
`HTTP/1.1 ${code} ${http.STATUS_CODES[code]}\r\n` +
|
||||
Object.keys(headers)
|
||||
.map((h) => `${h}: ${headers[h]}`)
|
||||
.join('\r\n') +
|
||||
'\r\n\r\n' +
|
||||
message
|
||||
);
|
||||
}
|
||||
|
||||
socket.removeListener('error', socketOnError);
|
||||
socket.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove whitespace characters from both ends of a string.
|
||||
*
|
||||
* @param {String} str The string
|
||||
* @return {String} A new string representing `str` stripped of whitespace
|
||||
* characters from both its beginning and end
|
||||
* @private
|
||||
*/
|
||||
function trim(str) {
|
||||
return str.trim();
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
'use strict'
|
||||
|
||||
const bench = require('fastbench')
|
||||
const pino = require('../../')
|
||||
const fs = require('node:fs')
|
||||
const dest = fs.createWriteStream('/dev/null')
|
||||
const plog = pino(dest)
|
||||
delete require.cache[require.resolve('../../')]
|
||||
const plogDest = require('../../')(pino.destination('/dev/null'))
|
||||
delete require.cache[require.resolve('../../')]
|
||||
const plogAsync = require('../../')(pino.destination({ dest: '/dev/null', sync: false }))
|
||||
const plogChild = plog.child({ a: 'property' })
|
||||
const plogDestChild = plogDest.child({ a: 'property' })
|
||||
const plogAsyncChild = plogAsync.child({ a: 'property' })
|
||||
const plogChildChild = plog.child({ a: 'property' }).child({ sub: 'child' })
|
||||
const plogDestChildChild = plogDest.child({ a: 'property' }).child({ sub: 'child' })
|
||||
const plogAsyncChildChild = plogAsync.child({ a: 'property' }).child({ sub: 'child' })
|
||||
|
||||
const max = 10
|
||||
|
||||
const run = bench([
|
||||
function benchPino (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plog.info('hello world')
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoDest (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogDest.info('hello world')
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoExtreme (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogAsync.info('hello world')
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoObj (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plog.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoDestObj (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogDest.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoAsyncObj (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogAsync.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoChild (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogChild.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoDestChild (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogDestChild.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoAsyncChild (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogAsyncChild.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoChildChild (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogChildChild.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoDestChildChild (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogDestChildChild.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoAsyncChildChild (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogAsyncChildChild.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoChildCreation (cb) {
|
||||
const child = plog.child({ a: 'property' })
|
||||
for (var i = 0; i < max; i++) {
|
||||
child.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoDestChildCreation (cb) {
|
||||
const child = plogDest.child({ a: 'property' })
|
||||
for (var i = 0; i < max; i++) {
|
||||
child.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoMulti (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plog.info('hello', 'world')
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoDestMulti (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogDest.info('hello', 'world')
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoAsyncMulti (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogAsync.info('hello', 'world')
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoInterpolate (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plog.info('hello %s', 'world')
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoDestInterpolate (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogDest.info('hello %s', 'world')
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoDestInterpolate (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogDest.info('hello %s', 'world')
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoInterpolateAll (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plog.info('hello %s %j %d', 'world', { obj: true }, 4)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoDestInterpolateAll (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogDest.info('hello %s %j %d', 'world', { obj: true }, 4)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoAsyncInterpolateAll (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogAsync.info('hello %s %j %d', 'world', { obj: true }, 4)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoInterpolateExtra (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plog.info('hello %s %j %d', 'world', { obj: true }, 4, { another: 'obj' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoDestInterpolateExtra (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogDest.info('hello %s %j %d', 'world', { obj: true }, 4, { another: 'obj' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoAsyncInterpolateExtra (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogAsync.info('hello %s %j %d', 'world', { obj: true }, 4, { another: 'obj' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
}
|
||||
], 10000)
|
||||
|
||||
run(run)
|
||||
@@ -0,0 +1,110 @@
|
||||
import * as util from "../core/util.js";
|
||||
const error = () => {
|
||||
const Sizable = {
|
||||
string: { unit: "ตัวอักษร", verb: "ควรมี" },
|
||||
file: { unit: "ไบต์", verb: "ควรมี" },
|
||||
array: { unit: "รายการ", verb: "ควรมี" },
|
||||
set: { unit: "รายการ", verb: "ควรมี" },
|
||||
};
|
||||
function getSizing(origin) {
|
||||
return Sizable[origin] ?? null;
|
||||
}
|
||||
const FormatDictionary = {
|
||||
regex: "ข้อมูลที่ป้อน",
|
||||
email: "ที่อยู่อีเมล",
|
||||
url: "URL",
|
||||
emoji: "อิโมจิ",
|
||||
uuid: "UUID",
|
||||
uuidv4: "UUIDv4",
|
||||
uuidv6: "UUIDv6",
|
||||
nanoid: "nanoid",
|
||||
guid: "GUID",
|
||||
cuid: "cuid",
|
||||
cuid2: "cuid2",
|
||||
ulid: "ULID",
|
||||
xid: "XID",
|
||||
ksuid: "KSUID",
|
||||
datetime: "วันที่เวลาแบบ ISO",
|
||||
date: "วันที่แบบ ISO",
|
||||
time: "เวลาแบบ ISO",
|
||||
duration: "ช่วงเวลาแบบ ISO",
|
||||
ipv4: "ที่อยู่ IPv4",
|
||||
ipv6: "ที่อยู่ IPv6",
|
||||
cidrv4: "ช่วง IP แบบ IPv4",
|
||||
cidrv6: "ช่วง IP แบบ IPv6",
|
||||
base64: "ข้อความแบบ Base64",
|
||||
base64url: "ข้อความแบบ Base64 สำหรับ URL",
|
||||
json_string: "ข้อความแบบ JSON",
|
||||
e164: "เบอร์โทรศัพท์ระหว่างประเทศ (E.164)",
|
||||
jwt: "โทเคน JWT",
|
||||
template_literal: "ข้อมูลที่ป้อน",
|
||||
};
|
||||
const TypeDictionary = {
|
||||
nan: "NaN",
|
||||
number: "ตัวเลข",
|
||||
array: "อาร์เรย์ (Array)",
|
||||
null: "ไม่มีค่า (null)",
|
||||
};
|
||||
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 `ประเภทข้อมูลไม่ถูกต้อง: ควรเป็น instanceof ${issue.expected} แต่ได้รับ ${received}`;
|
||||
}
|
||||
return `ประเภทข้อมูลไม่ถูกต้อง: ควรเป็น ${expected} แต่ได้รับ ${received}`;
|
||||
}
|
||||
case "invalid_value":
|
||||
if (issue.values.length === 1)
|
||||
return `ค่าไม่ถูกต้อง: ควรเป็น ${util.stringifyPrimitive(issue.values[0])}`;
|
||||
return `ตัวเลือกไม่ถูกต้อง: ควรเป็นหนึ่งใน ${util.joinValues(issue.values, "|")}`;
|
||||
case "too_big": {
|
||||
const adj = issue.inclusive ? "ไม่เกิน" : "น้อยกว่า";
|
||||
const sizing = getSizing(issue.origin);
|
||||
if (sizing)
|
||||
return `เกินกำหนด: ${issue.origin ?? "ค่า"} ควรมี${adj} ${issue.maximum.toString()} ${sizing.unit ?? "รายการ"}`;
|
||||
return `เกินกำหนด: ${issue.origin ?? "ค่า"} ควรมี${adj} ${issue.maximum.toString()}`;
|
||||
}
|
||||
case "too_small": {
|
||||
const adj = issue.inclusive ? "อย่างน้อย" : "มากกว่า";
|
||||
const sizing = getSizing(issue.origin);
|
||||
if (sizing) {
|
||||
return `น้อยกว่ากำหนด: ${issue.origin} ควรมี${adj} ${issue.minimum.toString()} ${sizing.unit}`;
|
||||
}
|
||||
return `น้อยกว่ากำหนด: ${issue.origin} ควรมี${adj} ${issue.minimum.toString()}`;
|
||||
}
|
||||
case "invalid_format": {
|
||||
const _issue = issue;
|
||||
if (_issue.format === "starts_with") {
|
||||
return `รูปแบบไม่ถูกต้อง: ข้อความต้องขึ้นต้นด้วย "${_issue.prefix}"`;
|
||||
}
|
||||
if (_issue.format === "ends_with")
|
||||
return `รูปแบบไม่ถูกต้อง: ข้อความต้องลงท้ายด้วย "${_issue.suffix}"`;
|
||||
if (_issue.format === "includes")
|
||||
return `รูปแบบไม่ถูกต้อง: ข้อความต้องมี "${_issue.includes}" อยู่ในข้อความ`;
|
||||
if (_issue.format === "regex")
|
||||
return `รูปแบบไม่ถูกต้อง: ต้องตรงกับรูปแบบที่กำหนด ${_issue.pattern}`;
|
||||
return `รูปแบบไม่ถูกต้อง: ${FormatDictionary[_issue.format] ?? issue.format}`;
|
||||
}
|
||||
case "not_multiple_of":
|
||||
return `ตัวเลขไม่ถูกต้อง: ต้องเป็นจำนวนที่หารด้วย ${issue.divisor} ได้ลงตัว`;
|
||||
case "unrecognized_keys":
|
||||
return `พบคีย์ที่ไม่รู้จัก: ${util.joinValues(issue.keys, ", ")}`;
|
||||
case "invalid_key":
|
||||
return `คีย์ไม่ถูกต้องใน ${issue.origin}`;
|
||||
case "invalid_union":
|
||||
return "ข้อมูลไม่ถูกต้อง: ไม่ตรงกับรูปแบบยูเนียนที่กำหนดไว้";
|
||||
case "invalid_element":
|
||||
return `ข้อมูลไม่ถูกต้องใน ${issue.origin}`;
|
||||
default:
|
||||
return `ข้อมูลไม่ถูกต้อง`;
|
||||
}
|
||||
};
|
||||
};
|
||||
export default function () {
|
||||
return {
|
||||
localeError: error(),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { _ as default } from "../esm/_ts_rewrite_relative_import_extension.js";
|
||||
@@ -0,0 +1,202 @@
|
||||
/**
|
||||
* @fileoverview Disallow renaming import, export, and destructured assignments to the same name.
|
||||
* @author Kai Cataldo
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const astUtils = require("./utils/ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** @type {import('../types').Rule.RuleModule} */
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: "suggestion",
|
||||
|
||||
defaultOptions: [
|
||||
{
|
||||
ignoreDestructuring: false,
|
||||
ignoreImport: false,
|
||||
ignoreExport: false,
|
||||
},
|
||||
],
|
||||
|
||||
docs: {
|
||||
description:
|
||||
"Disallow renaming import, export, and destructured assignments to the same name",
|
||||
recommended: false,
|
||||
url: "https://eslint.org/docs/latest/rules/no-useless-rename",
|
||||
},
|
||||
|
||||
fixable: "code",
|
||||
|
||||
schema: [
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
ignoreDestructuring: { type: "boolean" },
|
||||
ignoreImport: { type: "boolean" },
|
||||
ignoreExport: { type: "boolean" },
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
],
|
||||
|
||||
messages: {
|
||||
unnecessarilyRenamed: "{{type}} {{name}} unnecessarily renamed.",
|
||||
},
|
||||
},
|
||||
|
||||
create(context) {
|
||||
const sourceCode = context.sourceCode;
|
||||
const [{ ignoreDestructuring, ignoreImport, ignoreExport }] =
|
||||
context.options;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Reports error for unnecessarily renamed assignments
|
||||
* @param {ASTNode} node node to report
|
||||
* @param {ASTNode} initial node with initial name value
|
||||
* @param {string} type the type of the offending node
|
||||
* @returns {void}
|
||||
*/
|
||||
function reportError(node, initial, type) {
|
||||
const name =
|
||||
initial.type === "Identifier" ? initial.name : initial.value;
|
||||
|
||||
return context.report({
|
||||
node,
|
||||
messageId: "unnecessarilyRenamed",
|
||||
data: {
|
||||
name,
|
||||
type,
|
||||
},
|
||||
fix(fixer) {
|
||||
const replacementNode =
|
||||
node.type === "Property" ? node.value : node.local;
|
||||
|
||||
if (
|
||||
sourceCode.getCommentsInside(node).length >
|
||||
sourceCode.getCommentsInside(replacementNode).length
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Don't autofix code such as `({foo: (foo) = a} = obj);`, parens are not allowed in shorthand properties.
|
||||
if (
|
||||
replacementNode.type === "AssignmentPattern" &&
|
||||
astUtils.isParenthesised(
|
||||
sourceCode,
|
||||
replacementNode.left,
|
||||
)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return fixer.replaceText(
|
||||
node,
|
||||
sourceCode.getText(replacementNode),
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a destructured assignment is unnecessarily renamed
|
||||
* @param {ASTNode} node node to check
|
||||
* @returns {void}
|
||||
*/
|
||||
function checkDestructured(node) {
|
||||
if (ignoreDestructuring) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const property of node.properties) {
|
||||
/**
|
||||
* Properties using shorthand syntax and rest elements can not be renamed.
|
||||
* If the property is computed, we have no idea if a rename is useless or not.
|
||||
*/
|
||||
if (
|
||||
property.type !== "Property" ||
|
||||
property.shorthand ||
|
||||
property.computed
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const key =
|
||||
(property.key.type === "Identifier" && property.key.name) ||
|
||||
(property.key.type === "Literal" && property.key.value);
|
||||
const renamedKey =
|
||||
property.value.type === "AssignmentPattern"
|
||||
? property.value.left.name
|
||||
: property.value.name;
|
||||
|
||||
if (key === renamedKey) {
|
||||
reportError(
|
||||
property,
|
||||
property.key,
|
||||
"Destructuring assignment",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether an import is unnecessarily renamed
|
||||
* @param {ASTNode} node node to check
|
||||
* @returns {void}
|
||||
*/
|
||||
function checkImport(node) {
|
||||
if (ignoreImport) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
node.imported.range[0] !== node.local.range[0] &&
|
||||
astUtils.getModuleExportName(node.imported) === node.local.name
|
||||
) {
|
||||
reportError(node, node.imported, "Import");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether an export is unnecessarily renamed
|
||||
* @param {ASTNode} node node to check
|
||||
* @returns {void}
|
||||
*/
|
||||
function checkExport(node) {
|
||||
if (ignoreExport) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
node.local.range[0] !== node.exported.range[0] &&
|
||||
astUtils.getModuleExportName(node.local) ===
|
||||
astUtils.getModuleExportName(node.exported)
|
||||
) {
|
||||
reportError(node, node.local, "Export");
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
return {
|
||||
ObjectPattern: checkDestructured,
|
||||
ImportSpecifier: checkImport,
|
||||
ExportSpecifier: checkExport,
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,548 @@
|
||||
import { expect, expectTypeOf, test } from "vitest";
|
||||
import { en } from "zod/locales";
|
||||
import * as z from "zod/mini";
|
||||
|
||||
z.config(en());
|
||||
|
||||
const isoDateCodec = z.codec(
|
||||
z.iso.datetime(), // Input: ISO string (validates to string)
|
||||
z.date(), // Output: Date object
|
||||
{
|
||||
decode: (isoString) => new Date(isoString), // Forward: ISO string → Date
|
||||
encode: (date) => date.toISOString(), // Backward: Date → ISO string
|
||||
}
|
||||
);
|
||||
|
||||
test("instanceof", () => {
|
||||
expect(isoDateCodec instanceof z.ZodMiniCodec).toBe(true);
|
||||
expect(isoDateCodec instanceof z.ZodMiniPipe).toBe(true);
|
||||
expect(isoDateCodec instanceof z.ZodMiniType).toBe(true);
|
||||
expect(isoDateCodec instanceof z.core.$ZodCodec).toBe(true);
|
||||
expect(isoDateCodec instanceof z.core.$ZodPipe).toBe(true);
|
||||
expect(isoDateCodec instanceof z.core.$ZodType).toBe(true);
|
||||
|
||||
expectTypeOf(isoDateCodec.def).toEqualTypeOf<z.core.$ZodCodecDef<z.ZodMiniISODateTime, z.ZodMiniDate<Date>>>();
|
||||
});
|
||||
|
||||
test("codec basic functionality", () => {
|
||||
// ISO string -> Date codec using z.iso.datetime() for input validation
|
||||
|
||||
const testIsoString = "2024-01-15T10:30:00.000Z";
|
||||
const testDate = new Date("2024-01-15T10:30:00.000Z");
|
||||
|
||||
// Forward decoding (ISO string -> Date)
|
||||
const decodedResult = z.decode(isoDateCodec, testIsoString);
|
||||
expect(decodedResult).toBeInstanceOf(Date);
|
||||
expect(decodedResult.toISOString()).toMatchInlineSnapshot(`"2024-01-15T10:30:00.000Z"`);
|
||||
|
||||
// Backward encoding (Date -> ISO string)
|
||||
const encodedResult = z.encode(isoDateCodec, testDate);
|
||||
expect(typeof encodedResult).toBe("string");
|
||||
expect(encodedResult).toMatchInlineSnapshot(`"2024-01-15T10:30:00.000Z"`);
|
||||
});
|
||||
|
||||
test("codec round trip", () => {
|
||||
const isoDateCodec = z.codec(z.iso.datetime(), z.date(), {
|
||||
decode: (isoString) => new Date(isoString),
|
||||
encode: (date) => date.toISOString(),
|
||||
});
|
||||
|
||||
const original = "2024-12-25T15:45:30.123Z";
|
||||
const toDate = z.decode(isoDateCodec, original);
|
||||
const backToString = z.encode(isoDateCodec, toDate);
|
||||
|
||||
expect(backToString).toMatchInlineSnapshot(`"2024-12-25T15:45:30.123Z"`);
|
||||
expect(toDate).toBeInstanceOf(Date);
|
||||
expect(toDate.getTime()).toMatchInlineSnapshot(`1735141530123`);
|
||||
});
|
||||
|
||||
test("codec with refinement", () => {
|
||||
const isoDateCodec = z
|
||||
.codec(z.iso.datetime(), z.date(), {
|
||||
decode: (isoString) => new Date(isoString),
|
||||
encode: (date) => date.toISOString(),
|
||||
})
|
||||
.check(z.refine((val) => val.getFullYear() === 2024, { error: "Year must be 2024" }));
|
||||
|
||||
// Valid 2024 date
|
||||
const validDate = z.decode(isoDateCodec, "2024-01-15T10:30:00.000Z");
|
||||
expect(validDate.getFullYear()).toMatchInlineSnapshot(`2024`);
|
||||
expect(validDate.getTime()).toMatchInlineSnapshot(`1705314600000`);
|
||||
|
||||
// Invalid year should fail safely
|
||||
const invalidYearResult = z.safeDecode(isoDateCodec, "2023-01-15T10:30:00.000Z");
|
||||
expect(invalidYearResult.success).toBe(false);
|
||||
if (!invalidYearResult.success) {
|
||||
expect(invalidYearResult.error.issues).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"code": "custom",
|
||||
"message": "Year must be 2024",
|
||||
"path": [],
|
||||
},
|
||||
]
|
||||
`);
|
||||
}
|
||||
});
|
||||
|
||||
test("safe codec operations", () => {
|
||||
const isoDateCodec = z.codec(z.iso.datetime(), z.date(), {
|
||||
decode: (isoString) => new Date(isoString),
|
||||
encode: (date) => date.toISOString(),
|
||||
});
|
||||
|
||||
// Safe decode with invalid input
|
||||
const safeDecodeResult = z.safeDecode(isoDateCodec, "invalid-date");
|
||||
expect(safeDecodeResult.success).toBe(false);
|
||||
if (!safeDecodeResult.success) {
|
||||
expect(safeDecodeResult.error.issues).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"code": "invalid_format",
|
||||
"format": "datetime",
|
||||
"message": "Invalid ISO datetime",
|
||||
"origin": "string",
|
||||
"path": [],
|
||||
"pattern": "/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/",
|
||||
},
|
||||
]
|
||||
`);
|
||||
}
|
||||
|
||||
// Safe decode with valid input
|
||||
const safeDecodeValid = z.safeDecode(isoDateCodec, "2024-01-15T10:30:00.000Z");
|
||||
expect(safeDecodeValid.success).toBe(true);
|
||||
if (safeDecodeValid.success) {
|
||||
expect(safeDecodeValid.data).toBeInstanceOf(Date);
|
||||
expect(safeDecodeValid.data.getTime()).toMatchInlineSnapshot(`1705314600000`);
|
||||
}
|
||||
|
||||
// Safe encode with valid input
|
||||
const safeEncodeResult = z.safeEncode(isoDateCodec, new Date("2024-01-01"));
|
||||
expect(safeEncodeResult.success).toBe(true);
|
||||
if (safeEncodeResult.success) {
|
||||
expect(safeEncodeResult.data).toMatchInlineSnapshot(`"2024-01-01T00:00:00.000Z"`);
|
||||
}
|
||||
});
|
||||
|
||||
test("codec with different types", () => {
|
||||
// String -> Number codec
|
||||
const stringNumberCodec = z.codec(z.string(), z.number(), {
|
||||
decode: (str) => Number.parseFloat(str),
|
||||
encode: (num) => num.toString(),
|
||||
});
|
||||
|
||||
const decodedNumber = z.decode(stringNumberCodec, "42.5");
|
||||
expect(decodedNumber).toMatchInlineSnapshot(`42.5`);
|
||||
expect(typeof decodedNumber).toBe("number");
|
||||
|
||||
const encodedString = z.encode(stringNumberCodec, 42.5);
|
||||
expect(encodedString).toMatchInlineSnapshot(`"42.5"`);
|
||||
expect(typeof encodedString).toBe("string");
|
||||
});
|
||||
|
||||
test("async codec operations", async () => {
|
||||
const isoDateCodec = z.codec(z.iso.datetime(), z.date(), {
|
||||
decode: (isoString) => new Date(isoString),
|
||||
encode: (date) => date.toISOString(),
|
||||
});
|
||||
|
||||
// Async decode
|
||||
const decodedResult = await z.decodeAsync(isoDateCodec, "2024-01-15T10:30:00.000Z");
|
||||
expect(decodedResult).toBeInstanceOf(Date);
|
||||
expect(decodedResult.getTime()).toMatchInlineSnapshot(`1705314600000`);
|
||||
|
||||
// Async encode
|
||||
const encodedResult = await z.encodeAsync(isoDateCodec, new Date("2024-01-15T10:30:00.000Z"));
|
||||
expect(typeof encodedResult).toBe("string");
|
||||
expect(encodedResult).toMatchInlineSnapshot(`"2024-01-15T10:30:00.000Z"`);
|
||||
|
||||
// Safe async operations
|
||||
const safeDecodeResult = await z.safeDecodeAsync(isoDateCodec, "2024-01-15T10:30:00.000Z");
|
||||
expect(safeDecodeResult.success).toBe(true);
|
||||
if (safeDecodeResult.success) {
|
||||
expect(safeDecodeResult.data.getTime()).toMatchInlineSnapshot(`1705314600000`);
|
||||
}
|
||||
|
||||
const safeEncodeResult = await z.safeEncodeAsync(isoDateCodec, new Date("2024-01-15T10:30:00.000Z"));
|
||||
expect(safeEncodeResult.success).toBe(true);
|
||||
if (safeEncodeResult.success) {
|
||||
expect(safeEncodeResult.data).toMatchInlineSnapshot(`"2024-01-15T10:30:00.000Z"`);
|
||||
}
|
||||
});
|
||||
|
||||
test("codec type inference", () => {
|
||||
const codec = z.codec(z.string(), z.number(), {
|
||||
decode: (str) => Number.parseInt(str),
|
||||
encode: (num) => num.toString(),
|
||||
});
|
||||
|
||||
// These should compile without type errors
|
||||
const decoded: number = z.decode(codec, "123");
|
||||
const encoded: string = z.encode(codec, 123);
|
||||
|
||||
expect(decoded).toMatchInlineSnapshot(`123`);
|
||||
expect(encoded).toMatchInlineSnapshot(`"123"`);
|
||||
});
|
||||
|
||||
test("nested codec with object containing codec property", () => {
|
||||
// Nested schema: object containing a codec as one of its properties, with refinements at all levels
|
||||
const waypointSchema = z
|
||||
.object({
|
||||
name: z.string().check(z.minLength(1, "Waypoint name required")),
|
||||
difficulty: z.enum(["easy", "medium", "hard"]),
|
||||
coordinate: z
|
||||
.codec(
|
||||
z
|
||||
.string()
|
||||
.check(z.regex(/^-?\d+,-?\d+$/, "Must be 'x,y' format")), // Input: coordinate string
|
||||
z
|
||||
.object({ x: z.number(), y: z.number() })
|
||||
.check(z.refine((coord) => coord.x >= 0 && coord.y >= 0, { error: "Coordinates must be non-negative" })), // Output: coordinate object
|
||||
{
|
||||
decode: (coordString: string) => {
|
||||
const [x, y] = coordString.split(",").map(Number);
|
||||
return { x, y };
|
||||
},
|
||||
encode: (coord: { x: number; y: number }) => `${coord.x},${coord.y}`,
|
||||
}
|
||||
)
|
||||
.check(z.refine((coord) => coord.x <= 1000 && coord.y <= 1000, { error: "Coordinates must be within bounds" })),
|
||||
})
|
||||
.check(
|
||||
z.refine((waypoint) => waypoint.difficulty !== "hard" || waypoint.coordinate.x >= 100, {
|
||||
error: "Hard waypoints must be at least 100 units from origin",
|
||||
})
|
||||
);
|
||||
|
||||
// Test data
|
||||
const inputWaypoint = {
|
||||
name: "Summit Point",
|
||||
difficulty: "medium" as const,
|
||||
coordinate: "150,200",
|
||||
};
|
||||
|
||||
// Forward decoding (object with string coordinate -> object with coordinate object)
|
||||
const decodedWaypoint = z.decode(waypointSchema, inputWaypoint);
|
||||
expect(decodedWaypoint).toMatchInlineSnapshot(`
|
||||
{
|
||||
"coordinate": {
|
||||
"x": 150,
|
||||
"y": 200,
|
||||
},
|
||||
"difficulty": "medium",
|
||||
"name": "Summit Point",
|
||||
}
|
||||
`);
|
||||
|
||||
// Backward encoding (object with coordinate object -> object with string coordinate)
|
||||
const encodedWaypoint = z.encode(waypointSchema, decodedWaypoint);
|
||||
expect(encodedWaypoint).toMatchInlineSnapshot(`
|
||||
{
|
||||
"coordinate": "150,200",
|
||||
"difficulty": "medium",
|
||||
"name": "Summit Point",
|
||||
}
|
||||
`);
|
||||
|
||||
// Test refinements at all levels
|
||||
// String validation (empty waypoint name)
|
||||
const emptyNameResult = z.safeDecode(waypointSchema, {
|
||||
name: "",
|
||||
difficulty: "easy",
|
||||
coordinate: "10,20",
|
||||
});
|
||||
expect(emptyNameResult.success).toBe(false);
|
||||
if (!emptyNameResult.success) {
|
||||
expect(emptyNameResult.error.issues).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"code": "too_small",
|
||||
"inclusive": true,
|
||||
"message": "Waypoint name required",
|
||||
"minimum": 1,
|
||||
"origin": "string",
|
||||
"path": [
|
||||
"name",
|
||||
],
|
||||
},
|
||||
]
|
||||
`);
|
||||
}
|
||||
|
||||
// Enum validation (invalid difficulty)
|
||||
const invalidDifficultyResult = z.safeDecode(waypointSchema, {
|
||||
name: "Test Point",
|
||||
difficulty: "impossible" as any,
|
||||
coordinate: "10,20",
|
||||
});
|
||||
expect(invalidDifficultyResult.success).toBe(false);
|
||||
if (!invalidDifficultyResult.success) {
|
||||
expect(invalidDifficultyResult.error.issues).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"code": "invalid_value",
|
||||
"message": "Invalid option: expected one of "easy"|"medium"|"hard"",
|
||||
"path": [
|
||||
"difficulty",
|
||||
],
|
||||
"values": [
|
||||
"easy",
|
||||
"medium",
|
||||
"hard",
|
||||
],
|
||||
},
|
||||
]
|
||||
`);
|
||||
}
|
||||
|
||||
// Codec string format validation (invalid coordinate format)
|
||||
const invalidFormatResult = z.safeDecode(waypointSchema, {
|
||||
name: "Test Point",
|
||||
difficulty: "easy",
|
||||
coordinate: "invalid",
|
||||
});
|
||||
expect(invalidFormatResult.success).toBe(false);
|
||||
if (!invalidFormatResult.success) {
|
||||
expect(invalidFormatResult.error.issues).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"code": "invalid_format",
|
||||
"format": "regex",
|
||||
"message": "Must be 'x,y' format",
|
||||
"origin": "string",
|
||||
"path": [
|
||||
"coordinate",
|
||||
],
|
||||
"pattern": "/^-?\\d+,-?\\d+$/",
|
||||
},
|
||||
]
|
||||
`);
|
||||
}
|
||||
|
||||
// Codec object refinement (negative coordinates)
|
||||
const negativeCoordResult = z.safeDecode(waypointSchema, {
|
||||
name: "Test Point",
|
||||
difficulty: "easy",
|
||||
coordinate: "-5,10",
|
||||
});
|
||||
expect(negativeCoordResult.success).toBe(false);
|
||||
if (!negativeCoordResult.success) {
|
||||
expect(negativeCoordResult.error.issues).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"code": "custom",
|
||||
"message": "Coordinates must be non-negative",
|
||||
"path": [
|
||||
"coordinate",
|
||||
],
|
||||
},
|
||||
]
|
||||
`);
|
||||
}
|
||||
|
||||
// Codec-level refinement (coordinates out of bounds)
|
||||
const outOfBoundsResult = z.safeDecode(waypointSchema, {
|
||||
name: "Test Point",
|
||||
difficulty: "easy",
|
||||
coordinate: "1500,2000",
|
||||
});
|
||||
expect(outOfBoundsResult.success).toBe(false);
|
||||
if (!outOfBoundsResult.success) {
|
||||
expect(outOfBoundsResult.error.issues).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"code": "custom",
|
||||
"message": "Coordinates must be within bounds",
|
||||
"path": [
|
||||
"coordinate",
|
||||
],
|
||||
},
|
||||
]
|
||||
`);
|
||||
}
|
||||
|
||||
// Object-level refinement (hard waypoint too close to origin)
|
||||
const hardWaypointResult = z.safeDecode(waypointSchema, {
|
||||
name: "Expert Point",
|
||||
difficulty: "hard",
|
||||
coordinate: "50,60", // x < 100, but hard waypoints need x >= 100
|
||||
});
|
||||
expect(hardWaypointResult.success).toBe(false);
|
||||
if (!hardWaypointResult.success) {
|
||||
expect(hardWaypointResult.error.issues).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"code": "custom",
|
||||
"message": "Hard waypoints must be at least 100 units from origin",
|
||||
"path": [],
|
||||
},
|
||||
]
|
||||
`);
|
||||
}
|
||||
|
||||
// Round trip test
|
||||
const roundTripResult = z.encode(waypointSchema, z.decode(waypointSchema, inputWaypoint));
|
||||
expect(roundTripResult).toMatchInlineSnapshot(`
|
||||
{
|
||||
"coordinate": "150,200",
|
||||
"difficulty": "medium",
|
||||
"name": "Summit Point",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test("mutating refinements", () => {
|
||||
const A = z.codec(z.string(), z.string().check(z.trim()), {
|
||||
decode: (val) => val,
|
||||
encode: (val) => val,
|
||||
});
|
||||
|
||||
expect(z.decode(A, " asdf ")).toMatchInlineSnapshot(`"asdf"`);
|
||||
expect(z.encode(A, " asdf ")).toMatchInlineSnapshot(`"asdf"`);
|
||||
});
|
||||
|
||||
test("codec type enforcement - correct encode/decode signatures", () => {
|
||||
// Test that codec functions have correct type signatures
|
||||
const stringToNumberCodec = z.codec(z.string(), z.number(), {
|
||||
decode: (value: string) => Number(value), // core.output<A> -> core.input<B>
|
||||
encode: (value: number) => String(value), // core.input<B> -> core.output<A>
|
||||
});
|
||||
|
||||
// These should compile without errors - correct types (async support)
|
||||
expectTypeOf<(value: string, payload: z.core.ParsePayload<string>) => z.core.util.MaybeAsync<number>>(
|
||||
stringToNumberCodec.def.transform
|
||||
).toBeFunction();
|
||||
expectTypeOf<(value: number, payload: z.core.ParsePayload<number>) => z.core.util.MaybeAsync<string>>(
|
||||
stringToNumberCodec.def.reverseTransform
|
||||
).toBeFunction();
|
||||
|
||||
// Test that decode parameter type is core.output<A> (string)
|
||||
const validDecode = (value: string) => Number(value);
|
||||
expectTypeOf(validDecode).toMatchTypeOf<(value: string) => number>();
|
||||
|
||||
// Test that encode parameter type is core.input<B> (number)
|
||||
const validEncode = (value: number) => String(value);
|
||||
expectTypeOf(validEncode).toMatchTypeOf<(value: number) => string>();
|
||||
|
||||
z.codec(z.string(), z.number(), {
|
||||
// @ts-expect-error - decode should NOT accept core.input<A> as parameter
|
||||
decode: (value: never, _payload) => Number(value), // Wrong: should be string, not unknown
|
||||
encode: (value: number, _payload) => String(value),
|
||||
});
|
||||
|
||||
z.codec(z.string(), z.number(), {
|
||||
decode: (value: string) => Number(value),
|
||||
// @ts-expect-error - encode should NOT accept core.output<B> as parameter
|
||||
encode: (value: never) => String(value), // Wrong: should be number, not unknown
|
||||
});
|
||||
|
||||
z.codec(z.string(), z.number(), {
|
||||
// @ts-expect-error - decode return type should be core.input<B>
|
||||
decode: (value: string) => String(value), // Wrong: should return number, not string
|
||||
encode: (value: number) => String(value),
|
||||
});
|
||||
|
||||
z.codec(z.string(), z.number(), {
|
||||
decode: (value: string) => Number(value),
|
||||
// @ts-expect-error - encode return type should be core.output<A>
|
||||
encode: (value: number) => Number(value), // Wrong: should return string, not number
|
||||
});
|
||||
});
|
||||
|
||||
test("async codec functionality", async () => {
|
||||
// Test that async encode/decode functions work properly
|
||||
const asyncCodec = z.codec(z.string(), z.number(), {
|
||||
decode: async (str) => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 1)); // Simulate async work
|
||||
return Number.parseFloat(str);
|
||||
},
|
||||
encode: async (num) => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 1)); // Simulate async work
|
||||
return num.toString();
|
||||
},
|
||||
});
|
||||
|
||||
// Test async decode/encode
|
||||
const decoded = await z.decodeAsync(asyncCodec, "42.5");
|
||||
expect(decoded).toBe(42.5);
|
||||
|
||||
const encoded = await z.encodeAsync(asyncCodec, 42.5);
|
||||
expect(encoded).toBe("42.5");
|
||||
|
||||
// Test that both sync and async work
|
||||
const mixedCodec = z.codec(z.string(), z.number(), {
|
||||
decode: async (str) => Number.parseFloat(str),
|
||||
encode: (num) => num.toString(), // sync encode
|
||||
});
|
||||
|
||||
const mixedResult = await z.decodeAsync(mixedCodec, "123");
|
||||
expect(mixedResult).toBe(123);
|
||||
});
|
||||
|
||||
test("codec type enforcement - complex types", () => {
|
||||
type User = { id: number; name: string };
|
||||
type UserInput = { id: string; name: string };
|
||||
|
||||
const userCodec = z.codec(
|
||||
z.object({ id: z.string(), name: z.string() }),
|
||||
z.object({ id: z.number(), name: z.string() }),
|
||||
{
|
||||
decode: (input: UserInput) => ({ id: Number(input.id), name: input.name }),
|
||||
encode: (user: User) => ({ id: String(user.id), name: user.name }),
|
||||
}
|
||||
);
|
||||
|
||||
// Verify correct types are inferred (async support)
|
||||
expectTypeOf<(input: UserInput, payload: z.core.ParsePayload<UserInput>) => z.core.util.MaybeAsync<User>>(
|
||||
userCodec.def.transform
|
||||
).toBeFunction();
|
||||
expectTypeOf<(user: User, payload: z.core.ParsePayload<User>) => z.core.util.MaybeAsync<UserInput>>(
|
||||
userCodec.def.reverseTransform
|
||||
).toBeFunction();
|
||||
|
||||
z.codec(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
}),
|
||||
z.object({ id: z.number(), name: z.string() }),
|
||||
{
|
||||
// @ts-expect-error - decode parameter should be UserInput, not User
|
||||
decode: (input: User) => ({ id: Number(input.id), name: input.name }), // Wrong type
|
||||
encode: (user: User) => ({ id: String(user.id), name: user.name }),
|
||||
}
|
||||
);
|
||||
|
||||
z.codec(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
}),
|
||||
z.object({ id: z.number(), name: z.string() }),
|
||||
{
|
||||
decode: (input: UserInput) => ({ id: Number(input.id), name: input.name }),
|
||||
// @ts-expect-error - encode parameter should be User, not UserInput
|
||||
encode: (user: UserInput) => ({ id: String(user.id), name: user.name }), // Wrong type
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test("invertCodec", () => {
|
||||
const inverted = z.invertCodec(isoDateCodec);
|
||||
|
||||
type InvIn = z.input<typeof inverted>;
|
||||
type InvOut = z.output<typeof inverted>;
|
||||
expectTypeOf<InvIn>().toEqualTypeOf<Date>();
|
||||
expectTypeOf<InvOut>().toEqualTypeOf<string>();
|
||||
|
||||
const testDate = new Date("2024-01-15T10:30:00.000Z");
|
||||
expect(z.decode(inverted, testDate)).toBe("2024-01-15T10:30:00.000Z");
|
||||
|
||||
const encoded = z.encode(inverted, "2024-01-15T10:30:00.000Z");
|
||||
expect(encoded).toBeInstanceOf(Date);
|
||||
expect(encoded.toISOString()).toBe("2024-01-15T10:30:00.000Z");
|
||||
|
||||
const doubleInverted = z.invertCodec(z.invertCodec(isoDateCodec));
|
||||
expect(z.decode(doubleInverted, "2024-01-15T10:30:00.000Z")).toBeInstanceOf(Date);
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
// deno-fmt-ignore-file
|
||||
// deno-lint-ignore-file
|
||||
// This code was bundled using `deno bundle` and it's not recommended to edit it manually
|
||||
|
||||
const canElideFrames = "captureStackTrace" in Error;
|
||||
class AssertionError extends Error {
|
||||
message;
|
||||
get name() {
|
||||
return "AssertionError";
|
||||
}
|
||||
get ok() {
|
||||
return false;
|
||||
}
|
||||
constructor(message = "Unspecified AssertionError", props, ssf){
|
||||
super(message);
|
||||
this.message = message;
|
||||
if (canElideFrames) {
|
||||
Error.captureStackTrace(this, ssf || AssertionError);
|
||||
}
|
||||
for(const key in props){
|
||||
if (!(key in this)) {
|
||||
this[key] = props[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
toJSON(stack) {
|
||||
return {
|
||||
...this,
|
||||
name: this.name,
|
||||
message: this.message,
|
||||
ok: false,
|
||||
stack: stack !== false ? this.stack : undefined
|
||||
};
|
||||
}
|
||||
}
|
||||
class AssertionResult {
|
||||
get name() {
|
||||
return "AssertionResult";
|
||||
}
|
||||
get ok() {
|
||||
return true;
|
||||
}
|
||||
constructor(props){
|
||||
for(const key in props){
|
||||
if (!(key in this)) {
|
||||
this[key] = props[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
toJSON() {
|
||||
return {
|
||||
...this,
|
||||
name: this.name,
|
||||
ok: this.ok
|
||||
};
|
||||
}
|
||||
}
|
||||
export { AssertionError as AssertionError };
|
||||
export { AssertionResult as AssertionResult };
|
||||
|
||||
@@ -0,0 +1,283 @@
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v4";
|
||||
|
||||
test("string length", async () => {
|
||||
try {
|
||||
await z.string().length(4).parseAsync("asd");
|
||||
} catch (err) {
|
||||
// ("String must contain exactly 4 character(s)");
|
||||
expect((err as z.ZodError).issues).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"code": "too_small",
|
||||
"exact": true,
|
||||
"inclusive": true,
|
||||
"message": "Too small: expected string to have >=4 characters",
|
||||
"minimum": 4,
|
||||
"origin": "string",
|
||||
"path": [],
|
||||
},
|
||||
]
|
||||
`);
|
||||
}
|
||||
|
||||
try {
|
||||
await z.string().length(4).parseAsync("asdaa");
|
||||
} catch (err) {
|
||||
// ("String must contain exactly 4 character(s)");
|
||||
expect((err as z.ZodError).issues).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"code": "too_big",
|
||||
"exact": true,
|
||||
"inclusive": true,
|
||||
"maximum": 4,
|
||||
"message": "Too big: expected string to have <=4 characters",
|
||||
"origin": "string",
|
||||
"path": [],
|
||||
},
|
||||
]
|
||||
`);
|
||||
}
|
||||
});
|
||||
|
||||
test("string min/max", async () => {
|
||||
try {
|
||||
await z.string().min(4).parseAsync("asd");
|
||||
} catch (err) {
|
||||
// ("String must contain at least 4 character(s)");
|
||||
expect((err as z.ZodError).issues).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"code": "too_small",
|
||||
"inclusive": true,
|
||||
"message": "Too small: expected string to have >=4 characters",
|
||||
"minimum": 4,
|
||||
"origin": "string",
|
||||
"path": [],
|
||||
},
|
||||
]
|
||||
`);
|
||||
}
|
||||
});
|
||||
|
||||
test("string max", async () => {
|
||||
try {
|
||||
await z.string().max(4).parseAsync("aasdfsdfsd");
|
||||
} catch (err) {
|
||||
// ("String must contain at most 4 character(s)");
|
||||
expect((err as z.ZodError).issues).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"code": "too_big",
|
||||
"inclusive": true,
|
||||
"maximum": 4,
|
||||
"message": "Too big: expected string to have <=4 characters",
|
||||
"origin": "string",
|
||||
"path": [],
|
||||
},
|
||||
]
|
||||
`);
|
||||
}
|
||||
});
|
||||
|
||||
test("number min", async () => {
|
||||
try {
|
||||
await z.number().min(3).parseAsync(2);
|
||||
} catch (err) {
|
||||
// ("Number must be greater than or equal to 3");
|
||||
expect((err as z.ZodError).issues).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"code": "too_small",
|
||||
"inclusive": true,
|
||||
"message": "Too small: expected number to be >=3",
|
||||
"minimum": 3,
|
||||
"origin": "number",
|
||||
"path": [],
|
||||
},
|
||||
]
|
||||
`);
|
||||
}
|
||||
});
|
||||
|
||||
test("number gte", async () => {
|
||||
try {
|
||||
await z.number().gte(3).parseAsync(2);
|
||||
} catch (err) {
|
||||
// ("Number must be greater than or equal to 3");
|
||||
expect((err as z.ZodError).issues).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"code": "too_small",
|
||||
"inclusive": true,
|
||||
"message": "Too small: expected number to be >=3",
|
||||
"minimum": 3,
|
||||
"origin": "number",
|
||||
"path": [],
|
||||
},
|
||||
]
|
||||
`);
|
||||
}
|
||||
});
|
||||
|
||||
test("number gt", async () => {
|
||||
try {
|
||||
await z.number().gt(3).parseAsync(3);
|
||||
} catch (err) {
|
||||
// ("Number must be greater than or equal to 3");
|
||||
expect((err as z.ZodError).issues).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"code": "too_small",
|
||||
"inclusive": false,
|
||||
"message": "Too small: expected number to be >3",
|
||||
"minimum": 3,
|
||||
"origin": "number",
|
||||
"path": [],
|
||||
},
|
||||
]
|
||||
`);
|
||||
}
|
||||
});
|
||||
|
||||
test("number max", async () => {
|
||||
try {
|
||||
await z.number().max(3).parseAsync(4);
|
||||
} catch (err) {
|
||||
// ("Number must be less than or equal to 3");
|
||||
expect((err as z.ZodError).issues).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"code": "too_big",
|
||||
"inclusive": true,
|
||||
"maximum": 3,
|
||||
"message": "Too big: expected number to be <=3",
|
||||
"origin": "number",
|
||||
"path": [],
|
||||
},
|
||||
]
|
||||
`);
|
||||
}
|
||||
});
|
||||
|
||||
test("number lte", async () => {
|
||||
try {
|
||||
await z.number().lte(3).parseAsync(4);
|
||||
} catch (err) {
|
||||
// ("Number must be less than or equal to 3");
|
||||
expect((err as z.ZodError).issues).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"code": "too_big",
|
||||
"inclusive": true,
|
||||
"maximum": 3,
|
||||
"message": "Too big: expected number to be <=3",
|
||||
"origin": "number",
|
||||
"path": [],
|
||||
},
|
||||
]
|
||||
`);
|
||||
}
|
||||
});
|
||||
|
||||
test("number lt", async () => {
|
||||
try {
|
||||
await z.number().lt(3).parseAsync(3);
|
||||
} catch (err) {
|
||||
// ("Number must be less than or equal to 3");
|
||||
expect((err as z.ZodError).issues).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"code": "too_big",
|
||||
"inclusive": false,
|
||||
"maximum": 3,
|
||||
"message": "Too big: expected number to be <3",
|
||||
"origin": "number",
|
||||
"path": [],
|
||||
},
|
||||
]
|
||||
`);
|
||||
}
|
||||
});
|
||||
|
||||
test("number nonnegative", async () => {
|
||||
try {
|
||||
await z.number().nonnegative().parseAsync(-1);
|
||||
} catch (err) {
|
||||
// ("Number must be greater than or equal to 0");
|
||||
expect((err as z.ZodError).issues).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"code": "too_small",
|
||||
"inclusive": true,
|
||||
"message": "Too small: expected number to be >=0",
|
||||
"minimum": 0,
|
||||
"origin": "number",
|
||||
"path": [],
|
||||
},
|
||||
]
|
||||
`);
|
||||
}
|
||||
});
|
||||
|
||||
test("number nonpositive", async () => {
|
||||
try {
|
||||
await z.number().nonpositive().parseAsync(1);
|
||||
} catch (err) {
|
||||
// ("Number must be less than or equal to 0");
|
||||
expect((err as z.ZodError).issues).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"code": "too_big",
|
||||
"inclusive": true,
|
||||
"maximum": 0,
|
||||
"message": "Too big: expected number to be <=0",
|
||||
"origin": "number",
|
||||
"path": [],
|
||||
},
|
||||
]
|
||||
`);
|
||||
}
|
||||
});
|
||||
|
||||
test("number negative", async () => {
|
||||
try {
|
||||
await z.number().negative().parseAsync(1);
|
||||
} catch (err) {
|
||||
// ("Number must be less than 0");
|
||||
expect((err as z.ZodError).issues).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"code": "too_big",
|
||||
"inclusive": false,
|
||||
"maximum": 0,
|
||||
"message": "Too big: expected number to be <0",
|
||||
"origin": "number",
|
||||
"path": [],
|
||||
},
|
||||
]
|
||||
`);
|
||||
}
|
||||
});
|
||||
|
||||
test("number positive", async () => {
|
||||
try {
|
||||
await z.number().positive().parseAsync(-1);
|
||||
} catch (err) {
|
||||
// ("Number must be greater than 0");
|
||||
expect((err as z.ZodError).issues).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"code": "too_small",
|
||||
"inclusive": false,
|
||||
"message": "Too small: expected number to be >0",
|
||||
"minimum": 0,
|
||||
"origin": "number",
|
||||
"path": [],
|
||||
},
|
||||
]
|
||||
`);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,142 @@
|
||||
import { Infer, Struct } from '../struct.js';
|
||||
import { ObjectSchema, ObjectType, AnyStruct, InferStructTuple, UnionToIntersection } from '../utils.js';
|
||||
/**
|
||||
* Ensure that any value passes validation.
|
||||
*/
|
||||
export declare function any(): Struct<any, null>;
|
||||
/**
|
||||
* Ensure that a value is an array and that its elements are of a specific type.
|
||||
*
|
||||
* Note: If you omit the element struct, the arrays elements will not be
|
||||
* iterated at all. This can be helpful for cases where performance is critical,
|
||||
* and it is preferred to using `array(any())`.
|
||||
*/
|
||||
export declare function array<T extends Struct<any>>(Element: T): Struct<Infer<T>[], T>;
|
||||
export declare function array(): Struct<unknown[], undefined>;
|
||||
/**
|
||||
* Ensure that a value is a bigint.
|
||||
*/
|
||||
export declare function bigint(): Struct<bigint, null>;
|
||||
/**
|
||||
* Ensure that a value is a boolean.
|
||||
*/
|
||||
export declare function boolean(): Struct<boolean, null>;
|
||||
/**
|
||||
* Ensure that a value is a valid `Date`.
|
||||
*
|
||||
* Note: this also ensures that the value is *not* an invalid `Date` object,
|
||||
* which can occur when parsing a date fails but still returns a `Date`.
|
||||
*/
|
||||
export declare function date(): Struct<Date, null>;
|
||||
/**
|
||||
* Ensure that a value is one of a set of potential values.
|
||||
*
|
||||
* Note: after creating the struct, you can access the definition of the
|
||||
* potential values as `struct.schema`.
|
||||
*/
|
||||
export declare function enums<U extends number, T extends readonly U[]>(values: T): Struct<T[number], {
|
||||
[K in T[number]]: K;
|
||||
}>;
|
||||
export declare function enums<U extends string, T extends readonly U[]>(values: T): Struct<T[number], {
|
||||
[K in T[number]]: K;
|
||||
}>;
|
||||
/**
|
||||
* Ensure that a value is a function.
|
||||
*/
|
||||
export declare function func(): Struct<Function, null>;
|
||||
/**
|
||||
* Ensure that a value is an instance of a specific class.
|
||||
*/
|
||||
export declare function instance<T extends {
|
||||
new (...args: any): any;
|
||||
}>(Class: T): Struct<InstanceType<T>, null>;
|
||||
/**
|
||||
* Ensure that a value is an integer.
|
||||
*/
|
||||
export declare function integer(): Struct<number, null>;
|
||||
/**
|
||||
* Ensure that a value matches all of a set of types.
|
||||
*/
|
||||
export declare function intersection<A extends AnyStruct, B extends AnyStruct[]>(Structs: [A, ...B]): Struct<Infer<A> & UnionToIntersection<InferStructTuple<B>[number]>, null>;
|
||||
/**
|
||||
* Ensure that a value is an exact value, using `===` for comparison.
|
||||
*/
|
||||
export declare function literal<T extends boolean>(constant: T): Struct<T, T>;
|
||||
export declare function literal<T extends number>(constant: T): Struct<T, T>;
|
||||
export declare function literal<T extends string>(constant: T): Struct<T, T>;
|
||||
export declare function literal<T>(constant: T): Struct<T, null>;
|
||||
/**
|
||||
* Ensure that a value is a `Map` object, and that its keys and values are of
|
||||
* specific types.
|
||||
*/
|
||||
export declare function map(): Struct<Map<unknown, unknown>, null>;
|
||||
export declare function map<K, V>(Key: Struct<K>, Value: Struct<V>): Struct<Map<K, V>, null>;
|
||||
/**
|
||||
* Ensure that no value ever passes validation.
|
||||
*/
|
||||
export declare function never(): Struct<never, null>;
|
||||
/**
|
||||
* Augment an existing struct to allow `null` values.
|
||||
*/
|
||||
export declare function nullable<T, S>(struct: Struct<T, S>): Struct<T | null, S>;
|
||||
/**
|
||||
* Ensure that a value is a number.
|
||||
*/
|
||||
export declare function number(): Struct<number, null>;
|
||||
/**
|
||||
* Ensure that a value is an object, that is has a known set of properties,
|
||||
* and that its properties are of specific types.
|
||||
*
|
||||
* Note: Unrecognized properties will fail validation.
|
||||
*/
|
||||
export declare function object(): Struct<Record<string, unknown>, null>;
|
||||
export declare function object<S extends ObjectSchema>(schema: S): Struct<ObjectType<S>, S>;
|
||||
/**
|
||||
* Augment a struct to allow `undefined` values.
|
||||
*/
|
||||
export declare function optional<T, S>(struct: Struct<T, S>): Struct<T | undefined, S>;
|
||||
/**
|
||||
* Ensure that a value is an object with keys and values of specific types, but
|
||||
* without ensuring any specific shape of properties.
|
||||
*
|
||||
* Like TypeScript's `Record` utility.
|
||||
*/
|
||||
export declare function record<K extends string, V>(Key: Struct<K>, Value: Struct<V>): Struct<Record<K, V>, null>;
|
||||
/**
|
||||
* Ensure that a value is a `RegExp`.
|
||||
*
|
||||
* Note: this does not test the value against the regular expression! For that
|
||||
* you need to use the `pattern()` refinement.
|
||||
*/
|
||||
export declare function regexp(): Struct<RegExp, null>;
|
||||
/**
|
||||
* Ensure that a value is a `Set` object, and that its elements are of a
|
||||
* specific type.
|
||||
*/
|
||||
export declare function set(): Struct<Set<unknown>, null>;
|
||||
export declare function set<T>(Element: Struct<T>): Struct<Set<T>, null>;
|
||||
/**
|
||||
* Ensure that a value is a string.
|
||||
*/
|
||||
export declare function string(): Struct<string, null>;
|
||||
/**
|
||||
* Ensure that a value is a tuple of a specific length, and that each of its
|
||||
* elements is of a specific type.
|
||||
*/
|
||||
export declare function tuple<A extends AnyStruct, B extends AnyStruct[]>(Structs: [A, ...B]): Struct<[Infer<A>, ...InferStructTuple<B>], null>;
|
||||
/**
|
||||
* Ensure that a value has a set of known properties of specific types.
|
||||
*
|
||||
* Note: Unrecognized properties are allowed and untouched. This is similar to
|
||||
* how TypeScript's structural typing works.
|
||||
*/
|
||||
export declare function type<S extends ObjectSchema>(schema: S): Struct<ObjectType<S>, S>;
|
||||
/**
|
||||
* Ensure that a value matches one of a set of types.
|
||||
*/
|
||||
export declare function union<A extends AnyStruct, B extends AnyStruct[]>(Structs: [A, ...B]): Struct<Infer<A> | InferStructTuple<B>[number], null>;
|
||||
/**
|
||||
* Ensure that any value passes validation, without widening its type to `any`.
|
||||
*/
|
||||
export declare function unknown(): Struct<unknown, null>;
|
||||
//# sourceMappingURL=types.d.ts.map
|
||||
@@ -0,0 +1,52 @@
|
||||
export { default as ar } from "./ar.js";
|
||||
export { default as az } from "./az.js";
|
||||
export { default as be } from "./be.js";
|
||||
export { default as bg } from "./bg.js";
|
||||
export { default as ca } from "./ca.js";
|
||||
export { default as cs } from "./cs.js";
|
||||
export { default as da } from "./da.js";
|
||||
export { default as de } from "./de.js";
|
||||
export { default as el } from "./el.js";
|
||||
export { default as en } from "./en.js";
|
||||
export { default as eo } from "./eo.js";
|
||||
export { default as es } from "./es.js";
|
||||
export { default as fa } from "./fa.js";
|
||||
export { default as fi } from "./fi.js";
|
||||
export { default as fr } from "./fr.js";
|
||||
export { default as frCA } from "./fr-CA.js";
|
||||
export { default as he } from "./he.js";
|
||||
export { default as hr } from "./hr.js";
|
||||
export { default as hu } from "./hu.js";
|
||||
export { default as hy } from "./hy.js";
|
||||
export { default as id } from "./id.js";
|
||||
export { default as is } from "./is.js";
|
||||
export { default as it } from "./it.js";
|
||||
export { default as ja } from "./ja.js";
|
||||
export { default as ka } from "./ka.js";
|
||||
export { default as kh } from "./kh.js";
|
||||
export { default as km } from "./km.js";
|
||||
export { default as ko } from "./ko.js";
|
||||
export { default as lt } from "./lt.js";
|
||||
export { default as mk } from "./mk.js";
|
||||
export { default as ms } from "./ms.js";
|
||||
export { default as nl } from "./nl.js";
|
||||
export { default as no } from "./no.js";
|
||||
export { default as ota } from "./ota.js";
|
||||
export { default as ps } from "./ps.js";
|
||||
export { default as pl } from "./pl.js";
|
||||
export { default as pt } from "./pt.js";
|
||||
export { default as ro } from "./ro.js";
|
||||
export { default as ru } from "./ru.js";
|
||||
export { default as sl } from "./sl.js";
|
||||
export { default as sv } from "./sv.js";
|
||||
export { default as ta } from "./ta.js";
|
||||
export { default as th } from "./th.js";
|
||||
export { default as tr } from "./tr.js";
|
||||
export { default as ua } from "./ua.js";
|
||||
export { default as uk } from "./uk.js";
|
||||
export { default as ur } from "./ur.js";
|
||||
export { default as uz } from "./uz.js";
|
||||
export { default as vi } from "./vi.js";
|
||||
export { default as zhCN } from "./zh-CN.js";
|
||||
export { default as zhTW } from "./zh-TW.js";
|
||||
export { default as yo } from "./yo.js";
|
||||
@@ -0,0 +1,63 @@
|
||||
import Benchmark from "benchmark";
|
||||
|
||||
import { z } from "zod/v3";
|
||||
|
||||
const shortSuite = new Benchmark.Suite("realworld");
|
||||
|
||||
const People = z.array(
|
||||
z.object({
|
||||
type: z.literal("person"),
|
||||
hair: z.enum(["blue", "brown"]),
|
||||
active: z.boolean(),
|
||||
name: z.string(),
|
||||
age: z.number().int(),
|
||||
hobbies: z.array(z.string()),
|
||||
address: z.object({
|
||||
street: z.string(),
|
||||
zip: z.string(),
|
||||
country: z.string(),
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
let i = 0;
|
||||
|
||||
function num() {
|
||||
return ++i;
|
||||
}
|
||||
|
||||
function str() {
|
||||
return (++i % 100).toString(16);
|
||||
}
|
||||
|
||||
function array<T>(fn: () => T): T[] {
|
||||
return Array.from({ length: ++i % 10 }, () => fn());
|
||||
}
|
||||
|
||||
const people = Array.from({ length: 100 }, () => {
|
||||
return {
|
||||
type: "person",
|
||||
hair: i % 2 ? "blue" : "brown",
|
||||
active: !!(i % 2),
|
||||
name: str(),
|
||||
age: num(),
|
||||
hobbies: array(str),
|
||||
address: {
|
||||
street: str(),
|
||||
zip: str(),
|
||||
country: str(),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
shortSuite
|
||||
.add("valid", () => {
|
||||
People.parse(people);
|
||||
})
|
||||
.on("cycle", (e: Benchmark.Event) => {
|
||||
console.log(`${(shortSuite as any).name}: ${e.target}`);
|
||||
});
|
||||
|
||||
export default {
|
||||
suites: [shortSuite],
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* @author Toru Nagashima <https://github.com/mysticatea>
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Check whether a given character is a combining mark or not.
|
||||
* @param {number} codePoint The character code to check.
|
||||
* @returns {boolean} `true` if the character has the General Category of Combining Mark (M), consisting of `Mc`, `Me`, and `Mn`.
|
||||
*/
|
||||
module.exports = function isCombiningCharacter(codePoint) {
|
||||
return /^\p{M}$/u.test(String.fromCodePoint(codePoint));
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
import type { LibDefinition } from '../variable';
|
||||
export declare const es2022_error: LibDefinition;
|
||||
@@ -0,0 +1,50 @@
|
||||
type Line = number;
|
||||
type Column = number;
|
||||
type Kind = number;
|
||||
type Name = number;
|
||||
type Var = number;
|
||||
type SourcesIndex = number;
|
||||
type ScopesIndex = number;
|
||||
type Mix<A, B, O> = (A & O) | (B & O);
|
||||
export type OriginalScope = Mix<[
|
||||
Line,
|
||||
Column,
|
||||
Line,
|
||||
Column,
|
||||
Kind
|
||||
], [
|
||||
Line,
|
||||
Column,
|
||||
Line,
|
||||
Column,
|
||||
Kind,
|
||||
Name
|
||||
], {
|
||||
vars: Var[];
|
||||
}>;
|
||||
export type GeneratedRange = Mix<[
|
||||
Line,
|
||||
Column,
|
||||
Line,
|
||||
Column
|
||||
], [
|
||||
Line,
|
||||
Column,
|
||||
Line,
|
||||
Column,
|
||||
SourcesIndex,
|
||||
ScopesIndex
|
||||
], {
|
||||
callsite: CallSite | null;
|
||||
bindings: Binding[];
|
||||
isScope: boolean;
|
||||
}>;
|
||||
export type CallSite = [SourcesIndex, Line, Column];
|
||||
type Binding = BindingExpressionRange[];
|
||||
export type BindingExpressionRange = [Name] | [Name, Line, Column];
|
||||
export declare function decodeOriginalScopes(input: string): OriginalScope[];
|
||||
export declare function encodeOriginalScopes(scopes: OriginalScope[]): string;
|
||||
export declare function decodeGeneratedRanges(input: string): GeneratedRange[];
|
||||
export declare function encodeGeneratedRanges(ranges: GeneratedRange[]): string;
|
||||
export {};
|
||||
//# sourceMappingURL=scopes.d.ts.map
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,17 @@
|
||||
import { AbstractMessageReader, DataCallback, AbstractMessageWriter, Message, Disposable, ConnectionStrategy, ConnectionOptions, MessageReader, MessageWriter, Logger, MessageConnection } from '../common/api';
|
||||
export * from '../common/api';
|
||||
export declare class BrowserMessageReader extends AbstractMessageReader implements MessageReader {
|
||||
private _onData;
|
||||
private _messageListener;
|
||||
constructor(port: MessagePort | Worker | DedicatedWorkerGlobalScope);
|
||||
listen(callback: DataCallback): Disposable;
|
||||
}
|
||||
export declare class BrowserMessageWriter extends AbstractMessageWriter implements MessageWriter {
|
||||
private port;
|
||||
private errorCount;
|
||||
constructor(port: MessagePort | Worker | DedicatedWorkerGlobalScope);
|
||||
write(msg: Message): Promise<void>;
|
||||
private handleError;
|
||||
end(): void;
|
||||
}
|
||||
export declare function createMessageConnection(reader: MessageReader, writer: MessageWriter, logger?: Logger, options?: ConnectionStrategy | ConnectionOptions): MessageConnection;
|
||||
@@ -0,0 +1,16 @@
|
||||
export * as core from "../core/index.cjs";
|
||||
export * from "./schemas.cjs";
|
||||
export * from "./checks.cjs";
|
||||
export * from "./errors.cjs";
|
||||
export * from "./parse.cjs";
|
||||
export * from "./compat.cjs";
|
||||
export type { infer, output, input } from "../core/index.cjs";
|
||||
export type { JSONType } from "../core/util.cjs";
|
||||
export { globalRegistry, type GlobalMeta, registry, config, $output, $input, $brand, clone, regexes, treeifyError, prettifyError, formatError, flattenError, TimePrecision, util, NEVER, } from "../core/index.cjs";
|
||||
export { toJSONSchema } from "../core/json-schema-processors.cjs";
|
||||
export { fromJSONSchema } from "./from-json-schema.cjs";
|
||||
export * as locales from "../locales/index.cjs";
|
||||
export { ZodISODateTime, ZodISODate, ZodISOTime, ZodISODuration } from "./iso.cjs";
|
||||
export * as iso from "./iso.cjs";
|
||||
export type { ZodCoercedString, ZodCoercedNumber, ZodCoercedBigInt, ZodCoercedBoolean, ZodCoercedDate, } from "./coerce.cjs";
|
||||
export * as coerce from "./coerce.cjs";
|
||||
Reference in New Issue
Block a user